只要絕大多數節點不作惡,每個節點盡力廣播自己的消息,使用工作量證明(POW)的方法,全網就會形成正確共識。
你我是兩支部隊的將軍,分立兩座山頭,圍剿山谷中的敵城。敵城很牢固,如果我們獨立攻城必輸,但是合力出擊必勝。

看來很簡單,我們一起轟上去就能搞定。但是有一個小問題:山上沒有信號,傳遞消息只能靠通信兵,而通信兵必須經過山谷中的敵城才能傳遞消息。
假設:
你看到一個信使,帶來我的消息:“今夜23點攻城”。請問,你敢信么?
如果你信了,半夜出兵,結果被滅了也千萬別奇怪,因為我發給你的消息是:“明天早上6點攻城”。
但是,我的通信兵被敵人截殺,他們換了個新的,騙你早點出來。如果你被滅了,敵人就贏了,因為我單獨攻城也是被滅的命。
只傳輸文字信息帶來兩個問題:
1)原文易被篡改;
2)收信人無法驗證原文是否被篡改;
那該怎么辦?兩個辦法:
一是我們熟悉的數字簽名,但這只適用于一對一傳輸的場景。
如果要把消息向全軍廣播,就得用到第二種方法:工作量證明。
一、什么是工作量證明?
工作量證明(proof-of-work)指通過完成一定工作量以阻止網絡惡意攻擊的方式,簡稱POW
你可以把“一定的工作量”理解為簡單體力勞動,相當于去猜福利彩票雙色球的中獎號碼:你只要趴在桌上寫啊寫,就一定能夠寫中下一期的中獎號碼,只是要花點力氣而已。

但在工作量證明中,猜的可不是雙色球號碼,而是猜一個普通數字,這個數字添加在原文消息后,使得整個結果的哈希值前面有很多個0。
回到開頭的場景,我要發給你的消息是:明天早上6點攻城。但是我不能直接發,因為容易被敵人截殺。
你我之間可事先約定:
我找一個幸運數字,加在消息后面,使得“消息+幸運數字”的哈希值以5個0開頭。如果不是這樣,那你收到的消息就肯定不是我發的。
在這里,“5”代表難度,哈希值前綴的“0”越多代表這個幸運數字越難找。
二、用工作量說明“工作量證明”
下面,我用簡單體力勞動演示簡單體力勞動的全過程:
下圖左側是14種不同的哈希算法,右側是不同算法的哈希值結果,我們選用群眾喜聞樂見的SHA-256的哈希算法(來源網址:文末鏈接[1])。

一般的哈希值都不以“0”開頭,比如:“明天早上6點攻城”的哈希值以“6600c”開頭。
我們的任務是找一個幸運數字,把這個數加在原文消息后面去哈希,當哈希值前面出現5個0的時候,這個幸運數字就是我們要的值。
我們從0開始:把0加在消息“明天早上6點攻城”之后,變成:“明天早上6點攻城0”,得到哈希值:0593feea85b5cc51……
這個哈希值不以5個“0”開頭,所以0不是符合要求的幸運數字。
哈希值長度為64位,考慮到你手機屏幕的寬度,所以只列前N位哈希值。找到1個“0”開頭的哈希值很容易,沖上來就找到了。但別忘了,我的任務是找5個“0”開頭的哈希值。
開始體力勞動:
你只需注意以0開頭的哈希值出現的頻率。
消息+幸運數字 哈希值(前N位)
明早上6點攻城0 0593feea85b5cc51
明早上6點攻城1 5c14326badbde132
明早上6點攻城2 434fe85e6c397d38
明早上6點攻城3 ea1ee28cb2827fa9
明早上6點攻城4 8559db7af65e2bc9
明早上6點攻城5 6ec12acfe954ffe43
明早上6點攻城6 53bd9133146631a
明早上6點攻城7 d348d29e9398d88
明早上6點攻城8 276cfca1386ffba0d
明早上6點攻城9 f78628f2b0e4941c6
明早上6點攻城10 f92e8cd090f34feb2f
明早上6點攻城11 5cc0bc8a34f5fb81b
明早上6點攻城12 764154add623fee9c
明早上6點攻城13 96c3bcb245fd955f1
明早上6點攻城14 7c18fb827e48ea685
明早上6點攻城15 283f7de03f0165fac5
明早上6點攻城16 bce71436c4907d14
明早上6點攻城17 4f76be60fb2b0da75
明早上6點攻城18 182530c711a1cd98
明早上6點攻城19 6d59c71c30c17f378
明早上6點攻城20 b0161336bbbe8671
明早上6點攻城21 a1f35a0522b3a7ff8e
明早上6點攻城22 e118e2ce041d2a7f4
明早上6點攻城23 652083fffd51d0ae7f
明早上6點攻城24 678aa3a7ce73b5c9
明早上6點攻城25 f4a1f9d9b2fb9b1b4
明早上6點攻城26 c87c2c4e884ea133
明早上6點攻城27 2b3fb751465404de
明早上6點攻城28 227bac9dc398936e
明早上6點攻城29 888b7a21af071d5c
明早上6點攻城30 f4e9bb78cab0ff886
明早上6點攻城31 91594c8e41636a55
明早上6點攻城32 a7e8e2259ef52af4f
明早上6點攻城33 8b55a6ac71191a0e
明早上6點攻城34 a8304e90a8af8c7u
明早上6點攻城35 d57a2113827cbcdd
明早上6點攻城36 09c1eeaff85866243f
尋找“0”開頭的哈希值相對容易,一下找到2個,對應的數字分別是0和36,但沒有“00000”開頭的哈希值,甚至沒有“00”開頭的。
繼續:
明早上6點攻城37 7dfb6f433c3645462
明早上6點攻城38 7572b6a6aa98910a
明早上6點攻城39 5ac0d7c30d2890ca
明早上6點攻城40 9be18e61d82d1752
明早上6點攻城41 9541e2a4326e26a2
明早上6點攻城42 808f9e53e2fdfcde4f
明早上6點攻城43 cf9158dec66779f89
明早上6點攻城44 58c9b2b0b9e0c851
明早上6點攻城45 0e35034199718f0fd
明早上6點攻城46 6684da32f8dd10b17
明早上6點攻城47 53e75d1bf17a577ef8
明早上6點攻城48 49f5ea6a963c63620
明早上6點攻城49 14532b88606aeaad5
明早上6點攻城50 2b421e6ca5e184c21
明早上6點攻城51 32f6b4dc0e6c4d534
明早上6點攻城52 7b61def8dd77f08c25
明早上6點攻城53 59ec7fdd8457aa39f2
明早上6點攻城54 ba9697b6b4bbada44
明早上6點攻城55 130d07e46333ab39a
明早上6點攻城56 a7a3e432ac5660fb7c
明早上6點攻城57 ab72f349e783a2a4b8
明早上6點攻城58 039c996ace6dbc7d4
明早上6點攻城59 105626ff7234569223
明早上6點攻城60 b182960d7c0a6668
明早上6點攻城61 48233ba8877b40ead
明早上6點攻城62 d286d115c912c6205
明早上6點攻城63 fbf8e794aca4a2f8a92
明早上6點攻城64 4192c87e6911f9d8ea
明早上6點攻城65 fa5f19484c040ded83
明早上6點攻城66 42956b52051f1242be
明早上6點攻城67 68ecab13753c945671
明早上6點攻城68 70895ad85e9cf99407
明早上6點攻城69 d4ae89d8c6c968f035
明早上6點攻城70 4a6eb62c842417fe6a
明早上6點攻城71 2676fbd40437e60219
明早上6點攻城72 540e4a3347171077d
明早上6點攻城73 21be059d3c4dba03d
明早上6點攻城74 0d359bbca099eb63c1
明早上6點攻城75 255b4ff42e9a52830c6
明早上6點攻城76 55da96691231c2b921
明早上6點攻城77 d96bea5b456fbb080b
明早上6點攻城78 cd814aeb96ab6a795c
明早上6點攻城79 86ae1b4298637efb56
明早上6點攻城80 5b03962a07fe916c79
明早上6點攻城81 4b219e4e26e5883216
明早上6點攻城82 b64488ed650ce928df
明早上6點攻城83 a361cb170b37a57a61
明早上6點攻城84 e6f543f7c5665857088
明早上6點攻城85 468db8901982df4c14
明早上6點攻城86 e13fac9a65fa76e477b
明早上6點攻城87 1821cb6741f34ea5554
明早上6點攻城88 fda69eae2282f6c049fb
明早上6點攻城89 83bd626cb8fc04662cc
明早上6點攻城90 5a7cb656e9957de9c2
明早上6點攻城91 e4813bc08139e59e7d
明早上6點攻城92 ad70094cf25902427a
明早上6點攻城93 20d92beaef0f1dac96f
明早上6點攻城94 13dce00f7477d70a9e2
明早上6點攻城95 e8e711a4d6eb00341e
明早上6點攻城96 9cfde03631631dfba67
明早上6點攻城97 ce78f0ead3cd569d1d
明早上6點攻城98 ca9ab59025aeceb608
明早上6點攻城99 37c2e33202248931b4
明早上6點攻城100 4310137ff5e2329929f
以0開頭的哈希值很稀薄,100個后綴數字只有五個以1個“0”開頭的哈希值。為節約你的時間,以下省略所有不以“0”開頭的哈希結果。
......
“00”開頭的哈希值難能可貴,找遍300以內的數,竟然沒有,繼續:
明早6點攻城308 05977df5bd690958ed
明早6點攻城345 08884879b8f802455
明早6點攻城346 0ae905eb78cb562cc0
明早6點攻城347 00254b20c9cea56987
遍歷到347,終于出現連續一個“00”開頭的哈希值,難能可貴,但我們要找的是“00000”開頭的哈希值,所以繼續:
……
……
一直找
……
百折不撓
……
如果不靠省略號的幫助
你的屏幕已經下拉了10公里
……
屏幕已經拉穿了地球
……
字字讀來都是血,十年辛苦不尋常
苦心人,天不負。
終于,天開眼了……
我發現:用894578149接在原文后面,“明天早上6點攻城894578149”的哈希值符合要求:00000a392277b16……。
“894578149”就是我們要找的幸運數字,找到它就像中彩票一樣夢幻。因為我無法通過哈希值倒推,只能苦算[2]。
你見過的那些灰字和省略號,就是我的工作量證明——雖然每次哈希運算很簡單,只要0.001秒,但8.9億次哈希運算,卻需要10天時間。如果想在1天時間內算出來,得買10臺計算機。
如果敵人截獲消息,篡改成“后天15點攻城”,那么他得重新算幸運數字,并且,你知道只有你和我約定過“00000”的事。
我把“明天早上6點攻城894578149”發給你,你把原文一哈希,得到“00000a392277b16……”,五個零的前綴讓你大喜過望。
你準備明天6點起兵攻城。
但是,你真的可以這樣嗎?
不可以,因為敵人也懂哈希。
你看,理論上我的信息會被截獲,敵人拿到原文“明天早上6點攻城894578149”,一哈希,就能發現前面有五個零。
然后敵人為你編了條假消息:“后天13點攻城”。并且如法炮制,用工作量證明算出了一個幸運數字238925022。你收到消息:“后天13點攻城238925022”,一哈希,發現哈希值前綴也是“00000”。
如果你信了,后面的劇本就是:我明天一早攻城,比你先死一步。因為我的消息沒能發給你,你被敵人蒙蔽了。
所以,你還是不能輕信你親眼看到的消息,因為傳輸通道不可靠。
橫豎都死,如何破局?
三、解開真正的難題
答案是增加通信兵和提高難度。
增加通信兵容易理解:我派出10個通信兵,被攔截的可能性低于派1個兵。
增加難度指:從連續5個“0”的哈希值前綴變成連續6個、7個、甚至更多“0”,可以提高敵人的破解難度。
這樣,即使通信兵被攔截,敵人哈希一下發現前面有7個零,一下就傻了,因為敵人明白,要哈希出7個“0”開頭的結果得花幾年時間找一個幸運數字。
當敵人知道你會派10個通信兵傳遞消息,而只攔截到1個兵時,敵人甚至都懶得做工作量證明。因為他知道,即使攔截篡改了,也沒有什么卵用。
因為敵人知道:第一,找幸運數字要花很長時間;第二在千萬次哈希過程中已經有9個兵把消息傳到了,即使找到了幸運數字,我也沒辦法混淆視聽。
所以,你我必勝,敵人必輸。
拓展今天的故事:左右山頭分別有五個將軍準備攻城,信息傳遞只能靠信使,每個將軍都有自己的想法,但只有當超過半數的將軍一起攻城時才能獲勝,如何敲定攻城時間?

就是“拜占庭將軍問題”,如果你去翻比特幣或區塊鏈的書,作者十有八九會搖著你的衣領向你講述,然后你百有九十九會蒙圈。
它真的很難理解,因為在我們生活的世界沒有對照物,它甚至沒有發生過,它只是計算機科學家Lamport在1980年提出的思想實驗。
如果你已經理解兩個將軍攻城的問題,那么就能解出拜占庭將軍問題。
很簡單:
把一座山頭上所有將軍的攻城意向時間匯總打包:
將軍1:明天1點;
將軍2:明天3點;
將軍3:明天3點;
將軍4:后天5點;
將軍5:后天7點;
找出一個幸運數字,使得“信息+幸運數字”的哈希值前綴是“00000000”,派10個通信兵送信。
即使有1個通信兵被攔截,另一個山頭的將軍也能收到9條“消息+幸運數字”,哈希后發現有8個“0”,8代表難度已經很高了,敵人破解的概率是萬億分之一。于是果斷相信,并采用同樣方式回信:
將軍6:明天3點;
將軍7:明天3點;
將軍8:明天1點;
將軍9:后天7點;
將軍10:明天3點;
找出一個幸運數字,使得“信息+幸運數字”的哈希值前綴是“00000000”,派10個通信兵送信。
來回幾次確認后可達成一致:明天3點攻城。
這樣,拜占庭將軍們必勝,除非敵城能用更短的時間找出幸運數字,而這卻是極小概率事件,比中六合彩都難上一億倍。
結語
在現實世界,通信不是問題,畢竟沒有手機信號還有無線電、信號彈,甚至隔山吹口哨都能解決;但在互聯網世界,信號傳輸只能依靠節點。
所以,是一個隱喻:山谷里的敵城是互聯網,你我和各路將軍是網上的節點,通信兵指傳遞消息的動作,消息指區塊,而幸運數字在區塊鏈世界里,有個術語:隨機數(Nonce)。
解決拜占庭將軍問題后,就有了比特幣。記錄交易信息和找隨機數的過程,就是比特幣挖礦[3]。
在此之前,“信道不可靠”曾是個天大的難題:
網絡上每個節點很疑惑:我應該聽誰的?
答案是:誰都不用聽,聽到啥就廣播啥,當好通信兵。
全網系統也很疑惑:通信渠道不可靠,如何保證傳遞的信息最終可靠?
答案是:只要絕大多數節點不作惡,每個節點盡力廣播自己的消息,使用工作量證明(POW)的方法,全網就會形成正確共識。
而這一切的基礎就是數學。
我們不能輕信網絡中的任何節點,但是我們可以相信數學,相信概率是百萬億分之一的事情不可能發生。
附:
[1]哈希網址
http://www.fileformat.info/tool/hash.htm
[2]弱弱地說一句:隨機數894578434是我亂說的,截至發稿時我沒算出以“00000”開頭的哈希值,但這個數字一定能被某臺計算機算出來。這就像你只要足夠賣力,一定能在紙上寫出下期雙色球的中獎號碼。
比特幣第1個區塊和第495661個區塊的哈希值,請注意比較前面的0的數量


哈希值前綴從8個“0”到18個“0”,你可以閉眼體會下難度——如果把計算結果都顯示在你的手機屏幕上,那要找到18個“0”的結果,你的屏幕可能要拉出銀河系了。