1. 程式人生 > >理解比特幣(4)——實現原理

理解比特幣(4)——實現原理

![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609829373164-image.png) # Part 0. 前言 在[前面](https://www.wmyskxz.com/tags/%E6%AF%94%E7%89%B9%E5%B8%81/ "比特幣系列文章")我們已經瞭解到了: 1. **比特幣** 代表了誕生於數字時代的對 **貨幣問題** 新的 **技術解決方案**; 2. **比特幣** 最大的價值在於 **價值儲存**,是擁有極高存量-增量比的 **健全貨幣**; 3. .... 可是這一切是如何通過技術實現的呢?這一篇文章就將 **儘可能通俗** 地解答這一問題。 # Part 1. 從一個例子入手 現在[假設](http://youtu.be/obRzfcvMshM "例子出處 | 比特幣原理") A、B、C、D 四個人合租一套房子,因為時常有金錢上的往來,為了省事,他們選擇使用客廳的一塊白板 **用來記賬**,到月底一起結清: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609723047969-image.png) 如果有人調皮,往白板上 **憑空寫一條記錄**,例如:A 需要向 C 支付 50,那麼月底 A 就會多支付 50,為了避免這樣的情況發生,每一條記錄後面都需要 **本人確認簽字**: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609724284619-image.png) 後來由於賬目過多,於是大家決定 **轉用電腦** 來記錄,但這樣一來 **簽名成了問題**。 一方面在電腦上不方便手寫簽名,另一方面手寫簽名也很容易被複制,所以我們需要轉換成專門用於電腦的 **數字簽名**。 # Part 2. 雜湊(hash)演算法 使用數字簽名,我們需要考慮嚴格的 **對應性**,也就是說一條簽名只能用於指定的記錄才行: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609725006590-image.png) 例如這裡的第一條記錄 *(A→C 轉賬 50)* 需要嚴格對應 *數字簽名 1*,不能對應第二條,也不能允許金額變更 *(如果金額更改應該是其他的數字簽名)*。 這樣才能對記錄的正確性有保證。 **可是數字簽名如何生成呢?** 這裡需要介紹一種被稱為 **雜湊(hash)演算法** 的工具。 它的作用是能把 **任意長度的輸入** 變成 **固定長度的二進位制輸出**。 ![圖示採用 CRC-32 Hash 演算法](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609726417478-image.png) 在比特幣的世界中,採用了 [SHA-256 演算法](https://zh.wikipedia.org/wiki/SHA-2 "SHA-2 維基百科") 來生成數字簽名,其中 **SHA** 是 **Secure Hash Algorithm** *(安全雜湊演算法)* 的簡寫,`256` 表示無論輸入什麼值都會生成一個 `256` 位的二進位制數 *(由 `0`/`1` 組成)*。 並且 **相同的輸入**,總會得到 **相同的結果**,結果又不能倒推回輸入,所以我們可以使用 SHA-256 作為我們的數字簽名。 例如,我們把第一條記錄用 SHA-256 演算法對應生成一條數字簽名: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609727181178-image.png) 即使是相差一個字、一個點,都會造成最終通過 SHA-256 生成的簽名完全不一樣,且毫無規律可尋:*(這樣就能滿足一一對應)* ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609727341274-image.png) 所以我們算是解決了對應性的難題: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609727565971-image.png) # Part 3. 非對稱加密 但是這樣還遠遠不夠,即使一一對應,也不能證明像上述這樣生成的數字簽名是 **本人生成** 的,因為 SHA-256 是公開的演算法,任何人都能通過它生成數字簽名。 **如何才能驗證是 A 生成的簽名而不是 B 生成的呢?** 於是想出了一個辦法,這個辦法叫 **非對稱加密**。 ## 加密和解密 先來解釋一下什麼叫加密,用大家平時比較喜歡的一串數字來舉例: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609728774073-image.png) `5201314` 是大家比較能知道的意思,但是把每一位數字都 `+2` 之後變成 `7423536` 就不那麼容易明白了,這個過程就叫 **加密**,還原的過程則叫 **解密**。 ## 對稱加密和非對稱加密 像上面例子中那樣加密和解密過程一樣的 *(`±2`)*,我們稱之為 **對稱加密**,加密和解密過程不一樣的則稱之為 **非對稱加密**。 [舉個例子](https://www.zhihu.com/question/33645891 "如何用通俗易懂的話來解解釋非對稱加密 | @永安線上反欺詐"),假設我們現在需要加密的字元是 `520`,我們 **加密** 的方法是把這個數乘以 `91`,並把結果的最後三位公佈出來: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609729371801-image.png) **解密** 我們當然不能通過除以 `91` 來完成,而是通過 `x11`,取出結果後三位來還原: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609729505456-image.png) 這是因為 `91*11=1001`,任何一個三位數乘以 `1001` 顯然後三位是不會變的。 這大概就是 **非對稱加密的原理** 了,只是可能原理更為複雜。 通常,在這個原理被保密的情況下,我們把解密的方法公佈到網上,數字 `11` 就被我們稱為 **公鑰**,人人都能知道,人人都能對應還原。 這個用來加密的數字 `91` 我們稱之為 **私鑰**,只有你自己知道。 ![圖片來源:https://bitcoin.org/zh_CN/how-it-works](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609743152479-image.png) 一組配對的公鑰和私鑰,也就能解決 **可驗證** 的問題了,因為私鑰有且僅有一個與之配對的公鑰。 他人可以使用你的公鑰加密資訊,然後傳送給你,你用私鑰解密,取出資訊。反過來,你也可以用私鑰加密資訊,別人用你的公鑰解開,從而證明這個資訊確實是你發出的,且未被篡改,這也就是 **數字簽名** 了。*(更詳細的介紹請看[《什麼是數字簽名》](http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html "數字簽名是什麼? | 阮一峰"))。* 於是我們又解決了 **可驗證** 問題: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609730389122-image.png) # Part 4. 去中心化 好了,目前我們的賬本已經算是可用了,但是整個過程我們是信賴 "電腦" 這個 **第三方** 的,如果出現什麼問題 *(比如中毒了)*,那就是無可避免的損失。 現在我們的交易大部分都依賴於這樣的 **第三方**,比如銀行、支付寶,面對高昂的手續費、繁瑣流程以及監管的問題,比特幣給出的答案是:**去中心化**。 在[比特幣提出時](https://bitcoin.org/files/bitcoin-paper/bitcoin_zh_cn.pdf "比特幣白皮書 PDF"),就說明了比特幣是一種完全 **點對點** 的電子貨幣系統,也就是說交易就像給某人發電子郵件一樣簡單,完全不需要藉助任何第三方。 *(這當然也有缺點在上一篇中我們提到一些——比如違法活動的進行更難追溯和查詢)* ![圖片來源:https://bitcoin.org/zh_CN/how-it-works](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609745102736-image.png) **每一個人** 都擁有一個 **完整的賬本**,每個人都是中心,這樣即使部分賬本存在問題也不會影響。 每一次交易的發生都會把這一次交易的資訊公佈到網上,每個人 **驗證成功** 之後都會記錄到自己的賬本中。 比如,A 向 B 支付了 50,那麼 A 就會使用自己的 **私鑰** 進行加密然後 **廣播** 到每一個人告訴他們這一訊息: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-4/1609747533765-image.png) 每一個收到的人再使用 A 的 **公鑰** 進行解密驗證確實是 A 向 B 支付了 50,並且開始檢查自己的賬本,開始回溯檢查 **確保 A 的賬戶上有 50** 用於支付給 B。 如果一切 OK,那麼每個人的賬本上都記錄上了這一條訊息。 ## 三個一致性問題 每個人都儲存 **所有** 的轉賬記錄,這會存在三個關於 **一致性** 的問題。 **第一個問題是:如何進行同步?** ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609800792670-image.png) 因為整個系統執行在網路,有的計算機可能會處於 **"離線"** 的狀態,但交易時刻都在發生,這就會出現賬本不一致的情況,如何才能保證不同計算機之間資料之間的資料同步問題呢? **第二個問題是:如何防止記錄被篡改?** ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609800944724-image.png) 例如黑客可能篡改網路中某些賬本中的部分資料,導致賬目不一致甚至衝突的情況發生,最終比特幣網路崩壞,如何才能保證資料不會被惡意篡改呢? **第三個問題是:如何防止同一筆比特幣收入被重複使用?** ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609801360575-image.png) 之前看到有人問:如果在 ATM 機跟支付寶中同時轉走賬戶剩餘的錢,那麼是不是會憑空多出一筆? 當然不會,因為 **銀行** 這樣的 **第三方機構** 會幫你做出決定:先轉走哪一筆那麼另一筆就會失效。 可是比特幣網路中,由於有些人先收到訊息有些人後收到訊息,那麼究竟使用哪一條訊息為準,又如何防止 "憑空多出一筆" 這樣的事情發生呢? # Part 5. 區塊鏈 上述的一致性問題,比特幣世界採用 **區塊鏈** 的方式來解決。 ## 區塊鏈 = 區塊 + 鏈 在比特幣世界中,大約每 10 分鐘會結算一次這 10 分鐘以來的交易記錄並輸出一個新的 "賬本": ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609804054748-image.png) 這些 "賬本" 當然不是交易記錄的 **簡單複合**,而是包括了:前一個 "賬本" 是哪一個、前一個 "賬本" 所有資料計算出的 Hash 值等等資訊的 **複雜資料體**。 這樣我們既能通過簡單的驗證保證 **資料的正確性**,又能把所有的 "賬本" 都 **串聯** 起來: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609805040236-image.png) 好了,換個名字,區塊鏈有了: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609805149131-image.png) > 遼寧瀋陽一小區大門上,66 把鎖串聯在一起,被譽為 **"最便宜的門禁系統"**,也號稱 **"區塊鏈實體化"** 技術... > > ![區塊鏈實體化...](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609803783296-image.png) ## 新區塊由 "礦工" 生成 比特幣網路中每一個新區塊都由 **"礦工"** 生成,它們會 **逐條檢查每一條記錄** 是否符合要求,例如每一條記錄是否有正確的數字簽名、支付是否合理等。 然後將驗證完之後的比特幣交易記錄新增到自己正在製作的 **新區塊** 中,製作完成就馬上傳送到比特幣網路中的每一個節點。 ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609806157279-image.png) 節點收到這個新區塊的資訊,如果驗證之後符合要求,就新增到目前 **區塊鏈的末尾**。 那麼問題來了,比特幣網路有許許多多的 "礦工",這也意味著這可能會 **同一時間** 內能生成很多 **基本資訊大致相同** 的 **"新區塊"**。 ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609806400319-image.png) 雖然這些 "新區塊" 中大致都是近期內驗證好的交易記錄,但是每個區塊具體包含哪些交易記錄,可能各不相同。 比特幣網路只能認可維護 **同一條區塊鏈**,比特幣網路上的 **每個節點** 都必須選擇 **同一個新區塊** 新增到末尾,但是這又很難制定一個統一的選擇標準。 ## POW(Proof Of Work)工作量證明 比特幣世界解決方法是:**採用一個巧妙的方法限制單位時間內生成的新區塊的數量**。 如果每過 10 分鐘左右的時間,整個比特幣世界只有 **一個** "礦工" 能夠生成 **一個** 新區塊,那麼也就不用再進行選擇,區塊鏈也將新增唯一一個新區塊到末尾。 這個巧妙的方法就是:當 "礦工" 完成新區塊的驗證打包之後,還需要做一個 **額外的工作** 才能把生成的區塊釋出到網路上。 額外工作的整個過程可以分為 2 步: 1. 將新制作區塊所包含的 **內容**(前一個區塊所有內容的 SHA-256 函式值 + 新區塊基本資訊 + 新區塊所包含所有交易記錄)組合成一個 **字串**; 2. 在這個字串的末尾新增一個 **隨機數**,組成新字串; 只有當這個新組成的字串通過 SHA-256 演算法得到的 `256` 位二進位制數的前 `72` 位 **全是 `0`**,才算成功完成了這個額外的工作。 ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609807917121-image.png) **這個額外工作的難度非常高** *(前 `72` 位為 `0` 的概率為 1 / 272
)*,高到整個比特幣網路大約 10 分鐘時間也只有一個 "礦工" 能夠 **找到這個隨機數**。 原因就在於 SHA-256 函式的特點是:雖然每個輸入值都對應一個輸出值,但是這 256 位二進位制數完全隨機沒有規律。 找到這個滿足條件的隨機數的 **唯一方法** 就是 **以最快的速度輸入不同的隨機數不停地試**。 當 "礦工" 找到這個隨機數之後,會將這個 **隨機數打包進當前區塊** 然後打包釋出到比特幣網路,所有節點就可以根據這個隨機數和新區塊的基本資訊 **快速驗證** 是否符合要求。 雖然找到這個 **隨機數** 完全靠運氣,但礦工的運算能力越強,這個運氣就會越好,這也是 "礦工" 們[不斷升級裝置](https://www.sohu.com/a/421650311_100245697 "比特幣礦機的演變之路")的原因。 ## 礦工激勵機制 是什麼支援著 "礦工" 們不停升級自己的裝置去打包新的區塊呢? 在比特幣世界裡,每成功打包一個新區塊,就會獎勵給 "礦工" 一定數量的比特幣,最初是 `50` 個,每產生 21 萬區塊 *(也就是大約 4 年時間)*,獎勵就會減半,2020 年這一獎勵已降至 6.25 比特幣。 另外當前區塊中所有交易產生的交易費也歸 "礦工" 所有。 ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609809959619-image.png) 拿 2017 年舉例,每打包一個區塊會得到 12.5 比特幣的獎勵,交易產生的交易費大約是 2 比特幣 *(參照上圖)*,也就是總收入約 14.5 比特幣。 按照 2017 年 12 月 16 日的行情,這大約值 26 萬美元,即 **170 萬人民幣**。 換句話說,一個運氣好的礦工,10 分鐘左右,就能產生 170 萬的收入。 ## 工作難度調整 ![圖片來源:http://www.time-weekly.com/post/264828](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609825183087-image.png) 巨大的經濟利益帶來的是挖礦裝置的不斷升級,這也導致了整個比特幣網路計算能力的提高。 為了維持平均 10 分鐘生成一個區塊的速度,比特幣網路會每 2 個星期調整一次工作難度。 例如從前 72 位為 0 調整為前 73 位,難度也是幾何級的增加。 ## 一致性問題回顧 好了,我們來回顧一下區塊鏈是如何解決上面提到過的一致性問題的。 ### 如何進行同步? 比特幣網路僅僅維護網路中唯一一條 **最長的區塊鏈**,所以不論是新加入的節點還是需要同步的節點,都以此為準。 ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609825692104-image.png) 很容易知道,即使 "礦工" 有了 **額外的工作** 需要做,但仍然會發生 **兩個區塊同時釋出** 到網路上的情況。 此時網路上一部分節點先接受到 A 的區塊,另一部分接受到 B 的區塊。 此時兩個區塊都不丟棄,直到下一個區塊 C 產生,由於 **額外的工作** 不會湊巧得又有兩個新區塊同時產生,此時 C 區塊前面的區塊是 A 那麼整個區塊鏈網路就接受 A 這一條,反之則接受 B。 ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609826381825-image.png) 這時候 A 區塊中未被打包的交易記錄又會被丟到網路中等待打包。 就算很湊巧仍然有區塊同時被打包在 A 和 B 區塊的後面,那麼繼續等待 **最長鏈** 的產生就好了。 通常來說,正常的交易被區塊連結受後,後面再接上 2-3 個區塊就能確保已經被區塊鏈完全接受了,大一點的交易則等待 3-6 個區塊。 如此就保證了網路中 **僅有一條唯一的長鏈** 產生,所有的節點都以此為準。 ### 如何防止篡改 在 **額外工作** 中我們提及到,每一個區塊都帶有 **上一個區塊所有交易資訊** 的 SHA-256 的值,區塊由此連結起來: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609826946495-image.png) 即使是很微小的改動,也會導致該區塊的 SHA-256 值不一致,導致後續連結的區塊斷裂: ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609827171865-image.png) 由於區塊鏈只維護一條最長鏈,所以只有當篡改者擁有的計算能力超過 **整個網路中其他節點的計算能力的總和** 時,才能通過不斷完成 **額外工作** 新增新區塊的方式,使當前修改的區塊成為[最長鏈](https://academy.binance.com/zh/articles/what-is-a-51-percent-attack "該攻擊又被稱為 51% 攻擊")。 區塊鏈的參與者越多,這一難度越大。 就算有人成功地篡改了交易記錄,他也不大可能得到任何好處,因為由於他的攻擊,比特幣網路的價值會大幅下跌,乃至歸零。 換句話說,為了摧毀比特幣,攻擊者需要付出極大的成本,卻不會得到任何回報。 **算力的分散、程式碼的強抗更改性、穩如山的貨幣政策,是比特幣存活下來併成長到今天這個規模的原因。** ### 如何防止同一筆比特幣收入被重複使用 區塊鏈中的每一條交易記錄都記錄了完整的 **程式碼可追溯** 的流轉資訊。 在打包新區塊的時候,如果發現交易記錄的 **上一筆交易** 已經出現在了 **之前的區塊** 中,那麼就會 **放棄掉** 該記錄; 如果 **同一個區塊** 中出現了 **兩筆來源相同** 的記錄,那麼也只會 **選擇其中一條** 進行記錄。 # 總結 好了,到目前為止我們已經把比特幣使用的技術和相關知識都通俗的講述了一遍,不知道小夥伴們有沒有真的理解比特幣呢? ![](https://cdn.jsdelivr.net/gh/wmyskxz/BlogImage02/2021-1-5/1609829257431-image.png) 現在來對主要技術和特點做一下[總結](https://www.bilibili.com/video/BV1gW411i7Hx?from=search&seid=4990745897491513785 "比特幣原理優質視訊講解"): 1. 利用 **SHA-256 演算法** 和 **非對稱加密** 來製作 **數字簽名**; 2. 利用 **區塊鏈** 中的區塊儲存比特幣交易記錄; 3. 設定 **額外工作** 從而控制單位時間內生成區塊的個數,同時保護比特幣網路; 4. 將一定數額的比特幣和區塊內的所有交易費獎勵,用於成功生成該區塊的礦工獎勵,**激勵** 更多礦工加入比特幣網路,促使比特幣網路茁壯成長; 5. 比忒不轉賬不依賴銀行或其他金融機構; 6. 比特幣網路內比特幣的總量不會超過 2100 萬。 (完)