作者:Kalle Rosenbaum & Linnéa Rosenbaum

比特幣是由人開發出來的。人們編寫軟件,然後運行這個軟件。當安全漏洞或者嚴重的 bug 被發現的時候 —— 這兩者真的有區別嗎?—— 也總是由人來發現的;自始至終,是我們這樣的血肉之軀。本章思考當漏洞被發現的時候,人們做了什麼,該做什麼、不該做什麼。第一節會解釋 “盡責披露(responsible disclosure )” 這個術語的含義,它指的是在發現漏洞之後,如何負責任地行動,以儘可能減少它所造成的傷害。剩餘的章節會帶你遊覽歷史上在比特幣軟件中發現的一部分最為嚴重的漏洞,以及開發者、礦工和用戶們是如何處理它們的。在比特幣的童年期,許多事情並不像今天一樣嚴密。
9.1 盡責披露
設想你在 Bitcoin Core 軟件中發現了一個 bug,這個 bug 讓他人可以遠程關閉一個 Bitcoin Core 節點,只要通過網絡發送一些特殊構造的消息就行。再設想,你並無惡意,也不希望這個漏洞被利用。那你要怎麼做?你要是保持沉默,沒準有人同樣會發現這個漏洞,可那個人並不一定像你這麼善良。
在披露一項安全問題的時候,披露它的人應採取 盡責披露 流程。比特幣開發者們也常常使用這個詞,Wikipedia 解釋它的含義是:
硬件和軟件的開發者常常需要時間和資源來修復他們的錯誤。通常,發現這些漏洞的會是有道德操守的黑客【1】。黑客和計算機安全科學家可以選擇將引起公眾對這些漏洞的注意作為自己的社會責任。隱藏問題可能會帶來虛假的安全感。為避免此事,相關的各方會協調和協商一個合理的漏洞修復時間窗口。基於漏洞的潛在影響、開發和引用緊急修復或變通措施所需的預期時間,以及其它因素,這個時間窗口會在幾天都幾個月不等。
—— Wikipedia,“盡責披露” 詞條
這意味著,如果你找出了一個安全漏洞,你應該報告給負責開發這個系統的團隊。但在比特幣世界裡,這要怎麼做呢?如章節 7.1(中文譯本)所述,沒有人控制著比特幣,只有一個比特幣開發的焦點,也就是 Bitcoin Core 的 Github 代碼庫。這個代碼庫的維護者負責其中的代碼,但他們不對整個比特幣系統負責 —— 也沒人付這個責任。此外,通用的最佳習慣是給 security@bitcoincore.org 發送郵件。
在一封 2017 年的名為 “盡責披露 bug” 的郵件中,Anthony Towns 嘗試了總結他認為是最佳操作的做法。他收集了來自多個信息源和不同人的輸入,以呈現他對這個話題的看法。
- 漏洞應該通過 bitcoincore.org 網站【0】上的安全頁面來報告
- 致命漏洞(馬上可以被利用的,或者已經被利用且造成了重大傷害的)將這樣處理:
- 儘快發佈補丁
- 廣泛通知需要升級(或者禁用受到影響的系統)
- 儘可能少披露實際問題,以推遲攻擊【1】【2】
- 不致命的漏洞(因為利用起來困難或者昂貴)將這樣處理:
- 在常規的開發流程中打上補丁並審核
- 從 master 分支向後移植修復或變通措施到當前的發行版本中【2】
- 開發者將嘗試發佈修復措施也不揭曉漏洞的性質,辦法是向有經驗但還不知情的開發者提供擬議的修復措施,告訴他們這修復了一項漏洞,請求他們定位漏洞【2】
- 在修復措施被髮行和得到廣泛採用之前,開發者可以建議其它比特幣實現採用漏洞修復措施,如果可以做到不暴露漏洞的話;比如,如果修復措施具有顯著的性能優勢,可以作為合併這些代碼的理由【3】
- 在漏洞揭曉之前,開發者將一般地建議友好的山寨幣開發者跟進這項修復。但這隻能在修復措施已在比特幣網絡中廣泛部署之後【4】
- 開發者一般不會通知曾經表現出敵意的山寨幣開發者(比如,使用漏洞來攻擊其他人,或者違反禁令)【5】
- 比特幣開發者不會在超過 80% 的比特幣節點部署修復之前公開漏洞。漏洞披露者將被鼓勵和要求遵循相同的策略【1】【6】
—— Anthony Towns,“bug 的盡責披露” 郵件樓,Bitcoin-dev 郵件組(2017)
上述清單表明了,在給比特幣發佈補丁時應該多麼謹慎,因為補丁可能會洩露漏洞的位置。第四點尤其有趣,因為它解釋瞭如何測試補丁是否被隱藏得足夠好。實際上,如果少數經驗豐富的開發者都無法在明知它是修復措施時定位漏洞,那其他人想要找出來可能就很難了。
在這封郵件之前,郵件樓裡的郵件討論的是要不要、什麼時候、怎麼向山寨幣開發者和其它比特幣實現的開發者披露漏洞。其實沒有乾脆地答案。“幫助好人” 看起來是合理地,但誰能確定他們是不是好人呢,那條界線在哪裡?Bryan Bishop 主張,幫助山寨幣甚至是詐騙幣保護自己免於安全爆破是一種道德責任。
保護比特幣及其用戶免於眼前的威脅是不夠的,還有一種更廣泛的責任,是保護所有類型的用戶和不同的軟件免於無論什麼形式的多種多樣的威脅,哪怕這些人正在使用愚蠢和不安全的軟件,而你個人不維護、不開發也不推薦使用它。處理對一項漏洞的知識是一件棘手的事,而你可能接收到帶有比其初始描述具有更嚴重直接或間接影響的知識。
—— Bryan Bishop,“bug 的盡責披露” 郵件樓,Bitcoin-dev 郵件組(2017)
在 Towns 的郵件前面還有 Gregory Maxwell 的郵件,他在其中主張安全漏洞可能比看起來的更加嚴重。
我曾多次看到,一個被認為難以利用的問題,事後被證明是很容易利用的,只要你找到正確的機關;或者一個微小的 DoS 問題事後證明是嚴重很多的問題。
簡單的性能 bug,如果精心部署,也可能用來割裂網絡 —— 礦工 A 和交易所 B 在一個網絡裡,其他人在另一個網絡裡,然後就有人能重複花費了。
等等等等。所以,雖然我絕對同意,不同的事情應該(並且也可以)用不同方式處理,但並不總是有那麼清晰的界限。主要還是得謹慎:認為問題會比你已知的更加嚴重。
—— Gregory Maxwell,“bug 的盡責披露” 郵件樓,Bitcoin-dev 郵件組(2017)
所以,即使一個漏洞看起來是難以利用的,也最好假設它是容易利用的 —— 只是你還沒搞清楚怎麼利用它。
Gregory 還提到,“認為我們這個郵件樓是在討論 ‘披露’,是不正確的。披露指的是你告訴供應商。這個郵件樓說的是 ‘公開’,它們的影響是不一樣的。公開是指你確定你也告訴了潛在的攻擊者。” 這最後一個觀察,關於披露和公開的區別,是重要的。盡責披露還是簡單的部分,合理公開才是難的部分。
9.2 童年陰影
比特幣最初只是一個個人項目(至少,從它的創建者的假名來看,是一個人),那時候比特幣幾乎沒有價值。因此,漏洞披露和 bug 修復的動作都不像現在這麼嚴格。
Bitcoin Wiki 有一個比特幣經歷過的公開漏洞(CVE)清單。本章會介紹其中一部分的安全漏洞,以及比特幣早年發生的意外。我們不會介紹所有漏洞,只選取其中我們覺得特別有趣的。
9.2.1 2010-07-28:花費任何人的錢幣(CVE-2010-5141)
在 2010 年 7 月 28 日,用名 “ArtForz” 的匿名人披露了 bitcoin 軟件版本 0.3.4 中包含的一個漏洞,它讓任何人都能花費其他人的錢幣。ArtForz 盡責地 報告這個漏洞給了中本聰和另一位名為 “Gavin Andresen” 的開發者。
問題是,當時的腳本操作碼 OP_RETURN 會直接退出程序的執行,所以,如果被花費的錢幣的腳本公鑰是 <pubkey> OP_CHECKSIG ,而腳本簽名是 OP_1 OP_RETURN,那麼腳本公鑰中的程序就完全不會執行。唯一會發生的事就是 1 被推入對戰,然後 OP_RETURN 導致腳本公鑰中的程序被跳過。而程序執行完之後,堆棧頂部的任何非零的值都意味著花費條件得到滿足。因為棧頂的元素是 1,不是 0,所以花費能夠成功。
這是當時處理 OP_RETURN 的代碼:
case OP_RETURN: { pc = pend; } break;pc = pend; 的效果就是讓程序的剩餘部分被跳過,意味著腳本公鑰中的任何鎖定腳本都會被無視。修復措施之一是改變 OP_RETURN 的含義,使之立即傳出(腳本執行)失敗。
case OP_RETURN: { return false; } break;中本聰在自己的代碼庫中完成了這項變更,然後編譯出了一個可執行的二進制問題,使用 0.3.5 的版本號。然後,他在 Bitcointalk 論壇中發帖 “警告!請儘快升級到 0.3.5(***** ALERT *** Upgrade to 0.3.5 ASAP)”,催促用戶安裝這個二進制文件,但並不提供它的源代碼。
請立即升級到 0.3.5 版本!我們修復了一個實現上的 bug,它讓虛假交易可能被網絡接受。在升級到 0.3.5 版本以前,不要接受任何比特幣支付!
—— 中本聰,Bitcointalk 論壇(2010)
最初的消息後面經過了編輯,其完整的形貌已經不可知了。上面的片段來自一個帶有引用的回覆。一些用戶試用了中本聰的二進制文件,但在運行時遇到了問題。很快,中本聰就說:
還沒有來得及更新 SVN 。請等待 0.3.6 版本,我正在開發。這期間你可以關閉自己的節點。
—— 中本聰,Bitcointalk 論壇(2010)
(譯者注:此處的 “SVN” 應指 “Apache Subversion”,一種開源的版本控制系統。)
35 分鐘之後,他說:
SVN 已經在 0.3.6 版本中更新。
正在上傳 0.3.6 的 Windows 系統下執行文件到 Sourceforge ,然後我會重新編譯 Linux 系統下的執行文件。
—— 中本聰,Bitcointalk 論壇(2010)
(譯者注:“Sourceforge” 是一個上傳代碼供其他人下載的網站。)
這時候,他似乎也更新了最初那個帖子,將 “0.3.5” 改成了 “0.3.6”:
請立即升級到 0.3.5 版本!我們修復了一個實現上的 bug,它讓虛假交易可能被顯示為已得到接受。在升級到 0.3.5 版本以前,不要接受任何比特幣支付!
如果你現在還無法升級到 0.3.6,最好先關閉你的比特幣節點,升級後再重啟。
0.3.6 還實現了更快的哈希運算:
- 感謝 tcatm,實現了中間狀態緩存的優化
- 感謝 BlackEye,加入了 Crypto++ ASM SHA-256
最終加速效果是 2.4 倍。
下載頁:
http://sourceforge.net/projects/bitcoin/files/Bitcoin/bitcoin-0.3.6/
Windows 和 Linux 用戶:即使你拿到了 0.3.5 版本,也依然要升級到 0.3.6 版本。
—— 中本聰,Bitcointalk 論壇(2010)
請注意兩條消息對問題的措辭不同:最開始那條說的是 “(虛假交易)被網絡接受”,後面這條說的是 “顯示為被接受”。也許中本聰為了讓人們不要過多關注真正的問題而淡化了問題的嚴重性。不管怎麼說,升級到 0.3.6 版本的人就能如常使用比特幣了。這個問題被解決了,而且很神奇的是,沒有人丟失自己的比特幣。
中本聰的消息也提到了挖礦上的性能優化。尚不清楚為什麼要在一個關鍵的安全修復中包含這個,但有可能也是為了混淆真正的問題。不過,看起來更有可能的是,他只是發佈了在 Subversion 代碼庫的開發分支上的東西(而不管這些東西是什麼),只是在其中添加了安全修復。
那時候,比特幣的用戶遠遠沒有今天那麼多,比特幣也幾乎沒有價值。如果今天還這樣應對 bug,那一定會被當成一場大型狗血劇:
- 中本聰推出了一個僅包含二進制文件的 0.3.5 版本,在其中包含修復。不發行補丁,也不提供源代碼,可能會被當成混淆問題的措施。
- 0.3.5 甚至無法運行。
- 0.3.6 中的修復措施實際上是一次硬分叉,如章節 5.2(中文譯本)所解釋的。
另一個可以爭議的事情是,讓用戶關掉自己的節點,究竟是好事還是壞事?今天不可能再這樣幹了,但那時候,許多用戶都主動關注論壇上的更新,而且通常也很專業。因此,那時候是可能做到的,可能也是個合理的措施。
9.2.2 2010-08-15 合併的輸出數值溢出(CVE-2010-5139)
在 2010 年 8 月中旬,Bitcointalk 論壇的用戶 “jgarzik”,也就是 Jeff Garzik,發現區塊高度 74638 中的一筆交易的兩個輸出具有罕見的高價值:
區塊 #74638 中的這個 “輸出” 非常奇怪:
"out" : [ { "value" : 92233720368.54277039, "scriptPubKey" : "OP_DUP OP_HASH160 0xB7A73EB128D7EA3D388DB12418302A1CBAD5E890 OP_EQUALVERIFY OP_CHECKSIG" }, { "value" : 92233720368.54277039, "scriptPubKey" : "OP_DUP OP_HASH160 0x151275508C66F89DEC2C5F43B6F9CBE0B5C4722C OP_EQUALVERIFY OP_CHECKSIG" } ]92233720368.54277039 BTC?那不就是 UINT_64(無符號 64 比特整數)的最大值嗎?
—— Jeff Garzik,Bitcointalk 論壇(2010)
很有可能這是一個 bug,導致兩個輸出的價值 —— 是 int64(有符號 64 位整數),而不是 Garzik 認為的是 uint_64 —— 的和溢出成了一個負值 -0.00997538 BTC 。從而,無論輸入的價值是多少,輸出的 “和” 都更小,從而根據當時的代碼,這是一筆有效的交易。
在這種情況下,這個 bug 已經因為一次真實的爆破而公開了。這一爆破的不幸好過是,大約 2 x 920 億 BTC 被創造了出來,嚴重稀釋了當時大約只有 370 萬 BTC 的貨幣供給量。
在一個相關的帖子中,中本聰表示如果有人能停止挖礦(當時叫做 “生產(generating)”),他會很感激。
如果人們能停止生產,會有所幫助。我們可能需要另外挖出一條區塊鏈分支,人們在當前這條分支上生產得越慢,我們就能越快建立另一條分支。
第一個補丁將放在 SVN rev 132 中。還沒上傳。我還在推入一些雜項的變更,需要先完成,然後我就會上傳補丁。
—— 中本聰,Bitcointalk 論壇(2010)
他的計劃是,製作一個軟分叉,讓上面這樣的交易變成無效的,從而作廢掉包含這樣的交易的區塊(尤其是上述位於 74638 高度的區塊)。不到一個消失之後,他就在 Subversion 代碼庫的 revision 132 中提交了一個補丁,並在論壇中發帖,講了他認為用戶應該做的事:
補丁已上傳到 SVN rev 132!
就目前而言,推薦的步驟:
1)關停節點。
2)下載 “knightmb” 的 blk 文件(替代掉你的 blk001.dat 和 blkindex.dat 文件)。
3)升級軟件。
4)軟件會從少於 74000 個區塊開始同步。讓它重新下載剩餘的區塊。
如果你不想使用 knightmb 的文件,你可以直接刪掉你的 blk*.dat 文件(以 “blk” 開頭的 “,dat” 文件),但如果每個人都同時下載全部的區塊,會給網絡造成很大的負擔。
我很快會編譯出發行版。
—— 中本聰,Bitcointalk 論壇(2010)
他希望人們從某一個用戶(“kinghtmb”)那裡下載區塊數據:這人發佈了在自己的硬盤上出現的區塊鏈,也即 blkXXXX.dat 文件和 blkindex.dat 文件。用這樣的辦法下載區塊數據(而不是從頭重新同步)的理由是減少網絡的帶寬瓶頸。
這裡有一個很大的問題:用戶從 knightmb 那裡下載到的區塊,在 Bitcoin 軟件啟動時不會經過驗證;blkindex.dat 文件已經包含了 UTXO 集,軟件會直接接受其中的任何數據,當成是已經驗證過了。knightmb 可以篡改數據,給他自己或其他人憑空增加一些比特幣。
(譯者注:這種可能性確實存在,然而,一旦用戶以重新編制索引的啟動選項運行軟件,就會曝光;說到底,儘管用戶替換掉這兩個文件之後,軟件啟動時不會自動重新驗證,但用戶是可以讓軟件重新驗證的。)
再一次,人們似乎聽從了這些建議,回滾無效區塊及其後代的行動成功了。礦工開始在原來的 74637 高度的區塊後挖礦,第一個後續區塊出現在 UTC 時間 23:53,也就是發現問題的大概 6 個消失後。到了第二天(8 月 16 日)的 08:10 ,在區塊高度 74689,新的鏈取代了舊的鏈,因此所有未升級軟件的節點也重組為跟隨新的鏈。這是比特幣歷史上最深的區塊重組 —— 達到了 52 個區塊。
相比 OP_RETURN 引起的問題,這個問題的處理方式可以說更聰明瞭:
- 發佈了不僅只有二進制文件的補丁
- 發行的新軟件的工作符合預期
- 沒有硬分叉
在問題處理期間,用戶被請求停止挖礦。我們可以討論這是不是一個好主意,但假設你是一個礦工,並且你也被說服了在壞區塊之後挖出的區塊最終都會在一次很深的區塊鏈重組中被消滅:那麼為什麼你要浪費資源來挖掘這些區塊呢?
你可能也認為,中本聰的建議 —— 從某個陌生人的硬盤下載區塊鏈和 UTXO 集 —— 有點可以。如果真是這樣,你是對的:確實是可疑的。但是,在那種情形下,這種緊急反應也是合理的。
這一次的事故處理與 OP_RETURN 那一次還有一個重大區別:這個漏洞真的被利用了,因此修復也可以更直接一些。而在 OP_RETURN 的事故中,開發者必須模糊化修復措施,而且公開的聲明也不能直接揭曉問題是什麼。
9.2.3. 2013-03-11 DB 鎖問題 0.7.2 - 0.8.0(CVE-2013-3220)
一個非常有趣,同時也有教育意義的問題出現在 2013 年 3 月。當時區塊鏈似乎在高度 225429 之後出現了分裂(這也是下面這段引文中的 “分叉(fork)” 的意思)。這次事故的細節在 BIP50 中披露了。該文檔的總結說:
在被挖出和廣播的一個區塊中,交易輸入的總數量超過了以往觀測到的最大值。Bitcoin 0.8 節點可以處理這個區塊,但一些 0.8 版本以前(pre-0.8)的 Bitcoin 節點就拒絕了它,導致了意料之外的區塊鏈分叉。與 0.8 及以往版本不兼容的鏈(下文稱為 “0.8 鏈”)這時候聚集了 60% 的挖礦算力,使得這次分裂無法自動解決(如果 pre-0.8 鏈的算力能打敗 0.8 鏈,那就會強迫 0.8 節點重組到 pre-0.8 鏈上,從而自動解決分裂)。
為了儘快恢復一條公認的區塊鏈,BTCGuild 和 Slush 將他們的節點軟件從 Bitcoin 0.8 降級到了 0.7 ,從而他們的礦池也會拒絕那個更大的區塊。這讓佔據多數的算力回到了沒有那個更大區塊的鏈上,最終讓 0.8 節點重組到 pre-0.8 鏈上。
—— 多位 Bitcoin Core 開發者,BIP50(2013)
在這次危及中,BTCGuild 和 Sluch 兩個礦池所採取的迅速行動令人印象深刻。他們能將佔據多數的算力轉移到分裂的 pre-0.8 分支上,從而幫助重建共識。這給了開發者時間來開發可持續的修復措施。
這個事件中的另一個同樣有趣的問題是,版本 0.7.2 與自身不兼容,以往的版本也一樣。這在 BIP50 的 “根本原因” 章節中有解釋:
這個不夠高的 BDK 鎖配置,隱式地成了一個決定區塊有效性的網絡共識規則(儘管這是一個不一致也不安全的規則,因為在不同節點上鎖的使用量可能會有區別。
—— 多位 Bitcoin Core 開發者,BIP50(2013)
簡而言之,問題在於,Bitcoin Core 軟件用來驗證區塊的數據庫鎖的數量不是確定性的。一個節點可能需要 X 個鎖,而另一個節點可能需要 X+1 個鎖。節點對一個區塊可以佔用的鎖的數量還有限制。如果需要的鎖的數量超過了這個限制,這個區塊將被認為是無效的。所以,如果 X+1 超過了這個限制,而 X 沒有超過,那麼這兩個節點就會分裂成兩個分支,互不同意對方那條是有效的。
最終選出的解決方案,除了上述兩大礦池所採取的緊急行動,還有:
- 在 0.8.1 版本限制區塊,不僅限制其體積,還限制其鎖的數量。
- 給舊版本(0.7.2 及部分舊版本)打上補丁,使用同樣的新規則,同時提升全局的鎖限制。
除了在第二點提到的提高全局鎖數量限制,這些規則都被實現為臨時規則,在一段預先確定的時間窗口內使用。計劃是當絕大多數節點都升級了,就移除這些限制。
這項軟分叉顯著地降低了共識故障的風險,而幾個月後,在 5 月 15 日,臨時規則在整個網絡中一致停用。請注意,這一反激活本質上也是一次硬分叉,但並沒有爭議。而且,它是跟以前的軟分叉一起發佈的,所以運行軟分叉後軟件的人都知道這個硬分叉會隨後到來。因此,在這個硬分叉激活的時候,絕大部分節點都保持了同步。但是,也依然有少量沒有升級的節點在這個過程中被擠出了網絡。
你可能會好奇:如果發生在今天,還能這麼應對嗎?今天的挖礦領域變得更加複雜了,可能分裂的兩邊都會有哈希算力,就難以像 BIP50 那樣足夠快地發佈補丁。說服在 “錯誤” 分支上的礦工放棄自己的區塊獎勵可能也有難度。
9.2.4 BIP66
BIP66 是有趣的,因為它顯示出了以下事物的重要性:
- 好的擇優密碼學
- 盡責披露
- 部署修復而不揭曉漏洞
- 在經過驗證的區塊上挖礦
BIP66 是一項提案,提議收緊放在比特幣腳本中的簽名的編碼規則。提案的動機是能夠用 OpenSSL 軟件以外的軟件和代碼庫來解析簽名(原來你甚至只能用最新版本的 OpenSSL)。OpenSSL 是一個通用密碼學的代碼庫;那時候 Bitcoin Core 還使用它。
這個 BIP 在 2015 年 7 月 4 日激活。不過,除了上述完全真實的內容以外,BIP66 還修復了一項在 BIP 中沒有提到的嚴重得多的問題。
漏洞
這個漏洞到 2015 年 7 月 28 日,才由 Pieter Wuille 在一封發往 Bitcoin-dev 郵件組的郵件中完整公開:
各位好,
我希望披露一項我在 2014 年 9 月發現的漏洞;在 BIP66 的部署率在本月初達到 95% 的閾值之後,該漏洞已經成為無法利用的了。
簡要說明
一筆專門製作的交易,可能讓區塊鏈在以下三類節點間分裂:
- 在 32 位系統和 64 位 Windows 系統上使用 OpenSSL 的節點
- 在非 Windows 的 64 位系統(Linux、OSX,等等)上使用 OpenSSL 的節點
- 使用一些非 OpenSSL 代碼庫來解析簽名的節點
—— Pieter Wuille,《披露:由 BIP66 直接解決的共識 bug》,Bitcoin-dev 郵件組(2015)
這封郵件進一步列出了發現這項漏洞的細節,以及導致這個漏洞的更具體原因。最後,Pieter 給出了事件的時間線;我們會回顧其中最重要的幾個事件。如圖 13 所示,其中一些已經講過了。

- 圖 13. 圍繞 BIP66 的事件的時間順序。加粗的部分在上文中講過了 -
披露之前
在還沒有任何人知道這個問題的時候,它可以被現在已經撤回的 BIP62 解決:該 BIP 可以減少出現交易熔融性(transaction malleability)的可能性。BIP62 所提出的變更之一是收緊關於簽名編碼的共識規則,也稱為 “嚴格 DER 編碼”。Pieter Wuille 在 2014 年 7 月為該 BIP 提出了一些調整,就可以解決這個問題:
- 2014 年 7 月 18 日:為了讓比特幣的簽名編碼規則不依賴於 OpenSSL 的具體解析器,我修改了 BIP32 提議,使其嚴格 DER 簽名要求也適用於版本號為 1 的交易。那時候還沒有非 DER 簽名將進入區塊,所以可以假設它不會直接影響任何人。詳見 https://github.com/bitcoin/bips/pull/90 和 http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-July/006299.html 。那時候還不知道,但如果 BIP66 得到了部署,就能解決這個漏洞。
—— Pieter Wuille,《披露:由 BIP66 直接解決的共識 bug》,Bitcoin-dev 郵件組(2015)
因為這個 BIP 的覆蓋面太廣,遠遠超過 “嚴格 DER 編碼” 這一項,所以它一直在改變,遲遲得不到部署。後來,這個 BIP 被呈網狀了,因為 “隔離見證” BIP141 以另一種更加完整的方式解決了交易熔融性問題。
披露之後
OpenSSL 發佈了帶有補丁的新版本軟件;如果在一開始就在 Bitcoin 軟件中使用這些補丁(新軟件),也將解決問題。然而,僅僅在新版本的 Bitcoin Core 使用新版本的 OpenSSL,會讓問題變得更糟糕。Gregory Maxwell 在 2015 年 1 月的另一個郵件樓中解釋了這一點:
雖然對於絕大多數應用來說,急切地拒絕某些簽名通常是可以接受的,但比特幣是一個共識系統,也就是所有的參與者都必須一般地同意輸入數據的有效性或無效性。可以說,一致性比 “正確性” 還要重要性。
……
然而,上面這個補丁,只能修復這個廣泛問題的一個症狀:在共識規範的行為中依賴於不是為共識用途而設計和分發的軟件(尤其是 OpenSSL)。因此,作為一項增量提升,我提議儘快使用一次專門軟分叉來強制執行更嚴格的 DER 兼容性,就使用 BIP62 的一個子集。
—— Gregory Maxwell 論 OpenSSL 升級,Bitcoin-dev 郵件組
他指出,使用無意在共識系統中服役的代碼會帶來嚴重的風險,並提議比特幣實現嚴格的 DER 編碼。這是擇優密碼學 —— 我們在章節 7.4(中文譯本)中討論過 —— 的重要性的一個極好的例子。
這些事件可能會給你一種印象 —— Gregory Maxwell 知道 Pieter Wuille 日後公開的那個漏洞,但想以 “預防” 的名義暗度陳倉,而不引起對真正問題的太多關注。可能是這樣,但這都只是猜測。
然後,如 Maxwell 所提議的,BIP66 作為 BIP62 的一個子集而被提出,它僅僅指定嚴格的 DER 編碼。這個 BIP 顯然被廣泛接受了,在 7 月就部署了,然而諷刺的是,還是因為 “無驗證挖礦(validationless mining )” 而出現了兩次區塊鏈分裂。這些分裂會在下一節中討論。

此中還可以得到一個關鍵結論:一個 BIP 或多或少應該是 原子化 的,意思是它應該足夠完整,提供了有用的東西或者解決了一個具體的問題,但同時要足夠小,能夠獲得用戶的廣泛指出。你在一個 BIP 中放置的東西越多,它被接受的概率就越小。
因為無驗證挖礦而出現的區塊鏈分裂
不幸的是,BIP66 的故事到這裡還沒做結束。在 BIP66 激活的時候,事後證明是非常混亂的,因為一些礦工在收到區塊後並不驗證,而是直接在後面開始挖礦。這就叫 “無驗證挖礦”,或者叫
“SPV 挖礦”(“SPV” 的意思是 “簡易支付驗證”,也只驗證區塊頭)。一封警告消息被髮送給了比特幣節點,其中附有一個描述這個問題的網頁:
在 2015 年 7 月 4 日凌晨,(BIP66)已抵達 950/1000(95%)的閾值。但不久之後,一個小礦工(未升級的 5% 的其中之一)就挖出了一個無效的區塊 —— 正如人們所料。不幸的是,事情顯露,網絡中大概一半的挖礦算力都在不完全驗證區塊的設置下挖礦(叫做 “SPV 挖礦”),因此在這個無效區塊之後挖出了新的區塊。
—— Bitcoin Core 開發者,bitcoin.org 上的警告消息(2015)
這個警告頁面指示人們,如果使用的是舊版本的 Bitcoin Core,應在平時自願的區塊確認要求之上額外等待 30 個區塊的確認。
上面提到的分裂發生在 2015 年 7 月 4 日,UTC 時間 02:10,在區塊高度 363730 之後。這個問題在當日 03:50 得到解決 —— 在挖出 6 個無效區塊之後。不幸的是,同樣的問題在第二天再次發生:在 7 月 5 日的 21:50 ,但這一次,無效的分支僅持續了 3 個區塊。

導致 BIP66 創建、部署的事件,還有後續發生的事件,是非常好的案例研究,揭示了比特幣開發者不得不謹慎的緣由。來自 BIP66 事件的一些關鍵結論:
- 在開放性和不公開漏洞之間取得平衡,是一個微妙的問題。
- 為尚未公開的漏洞部署修復措施一個棘手的遊戲。
- 保持共識不容易。
- 無意在共識系統中服役的軟件,常常會帶來風險。
- BIP 應該在一定程度上原子化的。
9.3 結論
比特幣有 bug 。發現 bug 的人被鼓勵負責任地披露 bug 給比特幣開發者,使他們能在 bug 公開之前修復這個 bug 。理想情況下,修復措施可以偽裝成性能提升,或使用其它煙霧彈。
我們瀏覽了這些年間浮出水面的一些較為嚴重的漏洞,以及處理他們的動作。一些漏洞因為被利用而暴露出來,而另一些得到了盡責披露,能夠在惡意人有機會利用它們之前得到修復。



