繁體中文翻譯:Dr. Awesome Doge
前言
撰寫比特幣這本書
我(Andreas)第一次接觸比特幣是在 2011 年中。我的第一反應基本上是「切!宅宅的錢!」然後我忽略了它六個月,未能理解它的重要性。這是我在許多我認識的最聰明的人身上看到的反應,這讓我感到一些安慰。第二次我在一個郵件列表的討論中遇到比特幣時,我決定閱讀中本聰(Satoshi Nakamoto)撰寫的白皮書,看看它到底是什麼。我仍然記得讀完那九頁時的那一刻,當時我意識到比特幣不僅僅是一種數位貨幣,而是一個信任網路,可以為遠不止貨幣的事物提供基礎。「這不是錢,這是一個去中心化的信任網路」的認知,開啟了我四個月的旅程,瘋狂地吞噬我能找到的每一條關於比特幣的資訊。我變得痴迷和著迷,每天花 12 個小時或更長時間盯著螢幕,閱讀、寫作、編碼,並盡可能多地學習。我從這種出神狀態中走出來時,因為缺乏規律的飲食而瘦了超過 20 磅,決心將自己投入到比特幣的工作中。
兩年後,在創建了許多探索各種比特幣相關服務和產品的小型新創公司後,我決定是時候寫我的第一本書了。比特幣是驅使我進入創作狂熱並佔據我思想的主題;它是我自網際網路以來遇到的最令人興奮的技術。現在是時候與更廣大的受眾分享我對這項驚人技術的熱情了。
目標讀者
本書主要面向程式設計師。如果你會使用程式語言,本書將教你加密貨幣如何運作、如何使用它們,以及如何開發與它們配合運作的軟體。前幾章也適合作為非程式設計師的深入介紹——那些試圖理解比特幣和加密貨幣內部運作的人。
為什麼封面上有蟲子?
切葉蟻(Leafcutter Ant)是一種在群體超級有機體中表現出高度複雜行為的物種,但每隻螞蟻都按照一套由社交互動和化學氣味(費洛蒙)交換驅動的簡單規則運作。根據維基百科:「除了人類之外,切葉蟻形成了地球上最大和最複雜的動物社會。」切葉蟻實際上不吃樹葉,而是用它們來種植真菌,這是蟻群的中心食物來源。明白了嗎?這些螞蟻在種田!
儘管螞蟻形成了基於階級的社會,並有一隻蟻后負責繁殖後代,但在蟻群中沒有中央權威或領導者。由數百萬成員組成的群體所表現出的高度智慧和複雜行為,是社交網路中個體互動的湧現屬性。
大自然展示了去中心化系統可以具有韌性,可以產生湧現的複雜性和令人難以置信的精密性,而無需中央權威、階層或複雜的部件。
比特幣(Bitcoin)是一個高度複雜的去中心化信任網路,可以支援無數的金融流程。然而,比特幣網路中的每個節點都遵循一些簡單的規則。許多節點之間的互動導致了複雜行為的湧現,而不是任何單一節點中的任何固有複雜性或信任。就像蟻群一樣,比特幣網路是一個由遵循簡單規則的簡單節點組成的韌性網路,它們一起可以在沒有任何中央協調的情況下做出驚人的事情。
本書使用的慣例
本書使用以下排版慣例:
- 斜體(Italic)
- 
表示新術語、URL、電子郵件地址、檔案名稱和副檔名。 
- 等寬字體(Constant width)
- 
用於程式清單,以及在段落中引用程式元素,例如變數或函數名稱、資料庫、資料類型、環境變數、陳述式和關鍵字。 
- 等寬粗體(Constant width bold)
- 
顯示使用者應該按字面輸入的命令或其他文字。 
- 等寬斜體(Constant width italic)
- 
顯示應該用使用者提供的值或由上下文決定的值替換的文字。 
| 此元素表示提示或建議。 | 
| 此元素表示一般注意事項。 | 
| 此元素表示警告或注意。 | 
程式碼範例
所有程式碼片段都可以在大多數作業系統上使用最少安裝對應語言的編譯器和直譯器進行複製。在必要時,我們提供基本的安裝說明和這些指令輸出的逐步範例。
一些程式碼片段和程式碼輸出已針對印刷進行了重新格式化。在所有此類情況下,行已被反斜線(\)字元分割,後跟換行字元。在轉錄範例時,請移除這兩個字元並再次連接這些行,你應該會看到與範例中顯示的相同結果。
所有程式碼片段在可能的情況下都使用真實的值和計算,因此你可以從一個範例建構到另一個範例,並在你編寫的任何程式碼中看到相同的結果來計算相同的值。
使用程式碼範例
這本書是為了幫助你完成工作。一般來說,如果本書提供了範例程式碼,你可以在你的程式和文件中使用它。除非你重現了程式碼的重要部分,否則你不需要聯絡我們以獲得許可。例如,編寫一個使用本書中幾個程式碼塊的程式不需要許可。銷售或分發 O’Reilly 書籍中的範例需要許可。透過引用本書和引用範例程式碼來回答問題不需要許可。將本書中大量範例程式碼合併到你的產品文件中確實需要許可。
我們感謝但不要求署名。署名通常包括 標題、#作者、出版商和 ISBN。例如:「Mastering Bitcoin,第 3 版,作者 [.keep-together]#Andreas M. Antonopoulos 和 David A. Harding(O’Reilly)。Copyright 2024 David Harding,ISBN 978-1-098-15009-9。」
本書的某些版本以開源授權提供,例如 CC-BY-NC,在這種情況下,該授權的條款適用。
如果你認為你對程式碼範例的使用超出了合理使用或上述許可的範圍,請隨時透過 [email protected] 聯絡我們。
與前一版的變更
A particular focus in the third edition has been modernizing the 2017 second edition text and the remaining 2014 first edition text. In addition, many concepts that are relevant to contempory Bitcoin development in 2023 have been added:
- 金鑰與地址
- 
We rearranged the address info so that we work through everything in historical order, adding a new section with P2PK (where "address" was "IP address"), refreshed the previous P2PKH and P2SH sections, and then added new sections for segwit/bech32 and taproot/bech32m. 
- Old Chapters 6 and 7
- 
Text from previous versions of Chapter 6, "Transactions," and Chapter 7, "Advanced Transactions," has been rearranged and expanded across four new chapters: 交易 (the structure of transactions), 授權與認證, 數位簽章, and 交易手續費. 
- 交易
- 
We added almost entirely new text describing the structure of a transaction. 
- 授權與認證
- 
We added new text about MAST, P2C, scriptless multisignatures, taproot, and tapscript. 
- 數位簽章
- 
We revised the ECDSA text and added new text about schnorr signatures, multisignatures, and threshold signatures. 
- 交易手續費
- 
We added almost entirely new text about fees, RBF and CPFP fee bumping, transaction pinning, package relay, and CPFP carve-out. 
- 比特幣網路
- 
We added text about compact block relay, added a significant update to bloom filters that better describes their privacy problems, and new text about compact block filters. 
- 區塊鏈
- 
We added text about signet. 
- 挖礦與共識
- 
We added text about BIP8 and speedy trial. 
- Appendixes
- 
We removed library-specific appendixes. After the appendix containing the original whitepaper, we added a new appendix describing how the implementation and properties of Bitcoin differ from those proposed in the whitepaper. 
本書中的比特幣地址和交易
本書中使用的比特幣地址、交易、金鑰、QR 碼和區塊鏈資料大部分是真實的。這意味著你可以瀏覽區塊鏈,查看作為範例提供的交易,使用你自己的腳本或程式檢索它們等。
然而,請注意,用於構建地址的私鑰要麼已印在本書中,要麼已被「燒毀」。這意味著如果你將錢發送到這些地址中的任何一個,錢將永遠丟失,或者在某些情況下,任何可以閱讀本書的人都可以使用此處印刷的私鑰取走它。
| 請勿將錢發送到本書中的任何地址。你的錢會被其他讀者拿走或永遠丟失。 | 
O’Reilly 線上學習
| 40 多年來,O'Reilly Media 一直提供技術和商業培訓、知識和見解,幫助公司取得成功。 | 
我們獨特的專家和創新者網路透過書籍、文章和我們的線上學習平台分享他們的知識和專業知識。O’Reilly 的線上學習平台讓你可以隨選存取現場培訓課程、深入學習路徑、互動式編碼環境,以及來自 O’Reilly 和 200 多家其他出版商的大量文字和影片。有關更多資訊,請造訪 https://oreilly.com。
如何聯絡我們
請將有關本書的評論和問題寄給出版商:
- O'Reilly Media, Inc.
- 1005 Gravenstein Highway North
- Sebastopol, CA 95472
- 800-889-8969 (in the United States or Canada)
- 707-829-7019 (international or local)
- 707-829-0104 (fax)
- [email protected]
- https://www.oreilly.com/about/contact.html
我們為本書設有網頁,列出勘誤表、範例和任何其他資訊。你可以在 https://oreil.ly/MasteringBitcoin3e 存取此頁面。
有關我們的書籍和課程的新聞和資訊,請造訪 https://oreilly.com。
在 LinkedIn 上找到我們:https://linkedin.com/company/oreilly-media。
在 Twitter 上關注我們:https://twitter.com/oreillymedia。
在 YouTube 上觀看我們:https://youtube.com/oreillymedia。
聯絡作者
你可以在 Andreas M. Antonopoulos 的個人網站上聯絡他: https://antonopoulos.com。
在 Facebook 上關注 Andreas: https://facebook.com/AndreasMAntonopoulos。
在 Twitter 上關注 Andreas: https://twitter.com/aantonop。
在 LinkedIn 上關注 Andreas: https://linkedin.com/company/aantonop。
非常感謝所有透過每月捐款支持 Andreas 工作的贊助者。你可以在此關注他的 Patreon 頁面: https://patreon.com/aantonop。
有關 Mastering Bitcoin 以及 Andreas 的開放版和翻譯的資訊,請造訪 https://bitcoinbook.info。
你可以在 David A. Harding 的個人網站上聯絡他: https://dtrt.org。
第一版和第二版致謝
作者:Andreas M. Antonopoulos
這本書代表了許多人的努力和貢獻。我感謝朋友、同事,甚至完全陌生人提供的所有幫助,他們與我一起努力撰寫這本關於加密貨幣和比特幣的權威技術書籍。
不可能區分比特幣技術和比特幣社群,這本書既是該社群的產物,也是一本關於技術的書。從一開始到最後,整個比特幣社群都鼓勵、支持和獎勵了我在這本書上的工作。最重要的是,這本書讓我成為一個美好社群的一部分兩年,我感謝你們接納我進入這個社群。有太多人需要按名字提及——我在會議、活動、研討會、聚會、披薩聚會和小型私人聚會上遇到的人,以及許多透過 Twitter、reddit、bitcointalk.org 和 GitHub 與我交流的人,他們對這本書產生了影響。你在本書中找到的每個想法、類比、問題、答案和解釋,在某個時刻都受到我與社群的互動的啟發、測試或改進。感謝你們所有人的支持;沒有你們,這本書就不會誕生。我永遠感激。
成為作者的旅程當然早在第一本書之前就開始了。我的第一語言(和學校教育)是希臘語,所以我不得不在大學第一年參加補救英語寫作課程。我要感謝 Diana Kordas,我的英語寫作老師,她在那一年幫助我建立了信心和技能。後來,作為一名專業人士,我發展了關於資料中心主題的技術寫作技能,為 Network World 雜誌撰稿。我要感謝 John Dix 和 John Gallant,他們給了我在 Network World 擔任專欄作家的第一份寫作工作,以及我的編輯 Michael Cooney 和我的同事 Johna Till Johnson,他們編輯了我的專欄並使其適合出版。每週寫 500 字,持續四年,給了我足夠的經驗,最終考慮成為一名作家。
還要感謝那些在我向 O’Reilly 提交書籍提案時透過提供推薦信和審查提案來支持我的人。具體來說,感謝 John Gallant、Gregory Ness、Richard Stiennon、Joel Snyder、Adam B. Levine、Sandra Gittlen、John Dix、Johna Till Johnson、Roger Ver 和 Jon Matonis。特別感謝 Richard Kagan 和 Tymon Mattoszko,他們審查了提案的早期版本,以及 Matthew Taylor,他編輯了提案。
感謝 O’Reilly 書籍 DNS and BIND 的作者 Cricket Liu,他將我介紹給 O’Reilly。還要感謝 O’Reilly 的 Michael Loukides 和 Allyson MacDonald,他們花了幾個月的時間幫助實現這本書。Allyson 在錯過截止日期和延遲交付成果時特別有耐心,因為生活干預了我們計劃的時間表。對於第二版,我感謝 Timothy McGovern 指導流程,Kim Cofer 耐心編輯,Rebecca Panzer 繪製了許多新圖表。
前幾章的前幾稿是最困難的,因為比特幣是一個難以解開的主題。每次我拉動比特幣技術的一條線,我都必須拉動整個東西。我反覆陷入困境,有點沮喪,因為我努力使主題易於理解,並圍繞這樣一個密集的技術主題創建敘事。最終,我決定透過使用比特幣的人的故事來講述比特幣的故事,整本書變得更容易寫了。我要感謝我的朋友和導師 Richard Kagan,他幫助我解開故事並度過了寫作障礙的時刻。我感謝 Pamela Morgan,她審查了第一版和第二版中每章的早期草稿,並提出了困難的問題使它們更好。此外,感謝舊金山比特幣開發者聚會小組的開發者以及 Taariq Lewis 和 Denise Terry 幫助測試早期材料。還要感謝 Andrew Naugler 的資訊圖形設計。
在書籍開發期間,我在 GitHub 上提供了早期草稿並邀請公開評論。提交了一百多條評論、建議、更正和貢獻作為回應。這些貢獻在 早期發布草稿(GitHub 貢獻) 中得到明確承認,並表示感謝。最重要的是,我真誠感謝我的志願 GitHub 編輯 Ming T. Nguyen(第 1 版)和 Will Binns(第 2 版),他們不知疲倦地工作以管理、管理和解決 GitHub 上的拉取請求、問題報告並執行錯誤修復。
書籍起草後,經過了幾輪技術審查。感謝 Cricket Liu 和 Lorne Lantz 的徹底審查、評論和支持。
幾位比特幣開發者貢獻了程式碼範例、審查、評論和鼓勵。感謝 Amir Taaki 和 Eric Voskuil 提供的範例程式碼片段和許多精彩評論;Chris Kleeschulte 貢獻了關於 Bitcore 的資訊;Vitalik Buterin 和 Richard Kiss 幫助橢圓曲線數學和程式碼貢獻;Gavin Andresen 的更正、評論和鼓勵;Michalis Kargakis 的評論、貢獻和 btcd 撰寫;以及 Robin Inge 提交的勘誤表改進了第二次印刷。在第二版中,我再次從許多 Bitcoin Core 開發者那裡得到了很多幫助,包括 Eric Lombrozo,他揭開了隔離見證的神秘面紗,Luke Dashjr 幫助改進了交易章節,Johnson Lau 審查了隔離見證和其他章節,還有許多其他人。我要感謝 Joseph Poon、Tadge Dryja 和 Olaoluwa Osuntokun,他們解釋了閃電網路(Lightning Network),審查了我的寫作,並在我遇到困難時回答了問題。
我對文字和書籍的熱愛來自我的母親 Theresa,她在一個每面牆都排列著書籍的房子裡撫養我。儘管自稱是技術恐懼症患者,我的母親還是在 1982 年給我買了第一台電腦。我的父親 Menelaos 是一位土木工程師,剛剛在 80 歲時出版了他的第一本書,他教會了我邏輯和分析思維以及對科學和工程的熱愛。
感謝你們所有人在這段旅程中支持我。
第三版致謝
作者:David A. Harding
Schnorr 簽章 中對非互動式 schnorr 簽章協議的介紹,首先描述了互動式 schnorr 身份協議,這受到 Gregory Maxwell 和 Andrew Poelstra 的「Borrommean Ring Signatures」(2015)中對該主題的介紹的重大影響。我對他們每個人在過去十年中免費提供的所有協助深表感激。
Jorge Lesmes、Olaoluwa Osuntokun、René Pickhardt 和 Mark "Murch" Erhardt 對本手稿的草稿提供了寶貴的技術審查。特別是,Murch 令人難以置信的深入和富有洞察力的審查,以及他願意評估同一文本的多次迭代,已將本書的品質提升到超出我最高期望的水平。
我還要感謝 Jimmy Song 建議我參與這個專案,感謝我的合著者 Andreas 允許我更新他的暢銷書,感謝 Angela Rufino 指導我完成 O’Reilly 的作者流程,以及 O’Reilly 的所有其他員工,使第三版的寫作成為一次愉快和富有成效的經歷。
最後,我不知道如何感謝所有幫助我走過這段旅程的比特幣貢獻者——從創建我使用的軟體,到教我它如何運作,再到幫助我傳遞我所獲得的一點知識。你們人數太多,無法列出你們的名字,但我經常想起你們,並知道如果沒有你們為我所做的一切,我對本書的貢獻是不可能的。
早期發布草稿(GitHub 貢獻)
許多貢獻者為 GitHub 上的早期發布草稿提供了評論、更正和補充。感謝你們所有人對本書的貢獻。
以下是著名的 GitHub 貢獻者名單,包括他們的 GitHub ID(括號中):
- Abdussamad Abdurrazzaq (AbdussamadA) 
- Adán SDPC (aesedepece)
- Akira Chiku (achiku)
- Alex Waters (alexwaters)
- Andrew Donald Kennedy (grkvlt)
- Andrey Esaulov (andremaha)
- andronoob
- AnejaBK
- Appaji (CITIZENDOT)
- ariesunny
- Arthur O'Dwyer (Quuxplusone)
- bargitta
- Basem Alasi (Bamskki)
- bisqfan
- bitcoinctf
- blip151
- Bryan Gmyrek (physicsdude)
- Carlos Sims (simsbluebox)
- Casey Flynn (cflynn07)
- cclauss
- Chapman Shoop (belovachap)
- chrisd95
- Christie D'Anna (avocadobreath)
- Cihat Imamoglu (cihati)
- Cody Scott (Siecje)
- coinradar
- Cragin Godley (cgodley)
- Craig Dodd (cdodd)
- dallyshalla
- Dan Nolan (Dan-Nolan)
- Dan Raviv (danra)
- Darius Kramer (dkrmr)
- Darko Janković (trulex)
- David Huie (DavidHuie)
- didongke
- Diego Viola (diegoviola)
- Dimitris Tsapakidis (dimitris-t)
- Dirk Jäckel (biafra23)
- Dmitry Marakasov (AMDmi3)
- drakos (Jolly-Pirate)
- drstrangeM
- Ed Eykholt (edeykholt)
- Ed Leafe (EdLeafe)
- Edward Posnak (edposnak)
- Elias Rodrigues (elias19r)
- Eric Voskuil (evoskuil)
- Eric Winchell (winchell)
- Erik Wahlström (erikwam)
- effectsToCause (vericoin)
- Esteban Ordano (eordano)
- ethers
- Evlix
- fabienhinault
- Fan (whiteath)
- Felix Filozov (ffilozov)
- Francis Ballares (fballares)
- François Wirion (wirion)
- Frank Höger (francyi)
- Gabriel Montes (gabmontes)
- Gaurav Rana (bitcoinsSG)
- genjix
- Geremia
- Gerry Smith (Hermetic)
- gmr81
- Greg (in3rsha)
- Gregory Trubetskoy (grisha)
- Gus (netpoe)
- halseth
- harelw
- Harry Moreno (morenoh149)
- Hennadii Stepanov (hebasto)
- Holger Schinzel (schinzelh)
- Ioannis Cherouvim (cherouvim)
- Ish Ot Jr. (ishotjr)
- ivangreene
- James Addison (jayaddison)
- Jameson Lopp (jlopp)
- Jason Bisterfeldt (jbisterfeldt)
- Javier Rojas (fjrojasgarcia)
- Jordan Baczuk (JBaczuk)
- Jeremy Bokobza (bokobza)
- JerJohn15
- jerzybrzoska
- Jimmy DeSilva (jimmydesilva)
- Jo Wo (jowo-io)
- Joe Bauers (joebauers)
- joflynn
- Johnson Lau (jl2012)
- Jonathan Cross (jonathancross)
- Jorgeminator
- jwbats
- Kai Bakker (kaibakker)
- kollokollo
- krupawan5618
- kynnjo
- Liangzx
- lightningnetworkstores
- lilianrambu
- Liu Yue (lyhistory)
- Lobbelt
- Lucas Betschart (lclc)
- Matt Wesley (MatthewWesley)
- Magomed Aliev (30mb1)
- Mai-Hsuan Chia (mhchia)
- Marco Falke (MarcoFalke)
- María Martín (mmartinbar)
- Marcus Kiisa (mkiisa)
- Mark Erhardt (Xekyo)
- Mark Pors (pors)
- Martin Harrigan (harrigan)
- Martin Vseticka (MartyIX)
- Marzig (marzig76)
- Matt McGivney (mattmcgiv)
- Matthijs Roelink (Matthiti)
- Maximilian Reichel (phramz)
- MG-ng (MG-ng)
- Michalis Kargakis (kargakis)
- Michael C. Ippolito (michaelcippolito) 
- Michael Galero (mikong)
- Michael Newman (michaelbnewman) 
- Mihail Russu (MihailRussu)
- mikew (mikew)
- milansismanovic
- Minh T. Nguyen (enderminh)
- montvid
- Morfies (morfies)
- Nagaraj Hubli (nagarajhubli)
- Nekomata (nekomata-3)
- nekonenene
- Nhan Vu (jobnomade)
- Nicholas Chen (nickycutesc)
- Ning Shang (syncom)
- Oge Nnadi (ogennadi)
- Oliver Maerz (OliverMaerz)
- Omar Boukli-Hacene (oboukli)
- Óscar Nájera (Titan-C)
- Parzival (Parz-val)
- Paul Desmond Parker (sunwukonga) 
- Philipp Gille (philippgille)
- ratijas
- rating89us
- Raul Siles (raulsiles)
- Reproducibility Matters (TheCharlatan) 
- Reuben Thomas (rrthomas)
- Robert Furse (Rfurse)
- Roberto Mannai (robermann)
- Richard Kiss (richardkiss)
- rszheng
- Ruben Alexander (hizzvizz)
- Sam Ritchie (sritchie)
- Samir Sadek (netsamir)
- Sandro Conforto (sandroconforto)
- Sanjay Sanathanan (sanjays95)
- Sebastian Falbesoner (theStack)
- Sergei Tikhomirov (s-tikhomirov)
- Sergej Kotliar (ziggamon)
- Seiichi Uchida (topecongiro)
- shaysw
- Simon de la Rouviere (simondlr)
- simone-cominato
- sindhoor7
- Stacie (staciewaleyko)
- Stephan Oeste (Emzy)
- Stéphane Roche (Janaka-Steph)
- takaya-imai
- Thiago Arrais (thiagoarrais)
- Thomas Kerin (afk11)
- Tochi Obudulu (tochicool)
- Tosin (tkuye)
- Vasil Dimov (vasild)
- venzen
- Vlad Stan (motorina0)
- Vijay Chavda (VijayChavda)
- Vincent Déniel (vincentdnl)
- weinim
- wenxiaolong (QingShiLuoGu)
- wenzhenxiang
- Will Binns (wbnns)
- wintercooled
- wjx
- wll2007
- Wojciech Langiewicz (wlk)
- Yancy Ribbens (yancyribbens)
- yjjnls
- Yoshimasa Tanabe (emag)
- yuntai
- yurigeorgiev4
- Zheng Jia (zhengjia)
- Zhou Liang (zhouguoguo)
介紹
比特幣(Bitcoin)是一系列概念和技術的集合,構成了數位貨幣生態系統的基礎。稱為比特幣(bitcoin)的貨幣單位用於在比特幣網路參與者之間儲存和傳輸價值。比特幣使用者主要透過網際網路使用比特幣協議相互通訊,儘管也可以使用其他傳輸網路。比特幣協議堆疊以開源軟體的形式提供,可以在各種計算裝置上執行,包括筆記型電腦和智慧型手機,使該技術易於存取。
| 在本書中,貨幣單位稱為「比特幣(bitcoin)」,使用小寫 b, 而系統稱為「比特幣(Bitcoin)」,使用大寫 B。 | 
使用者可以透過網路轉移比特幣來做幾乎任何可以用傳統貨幣做的事情,包括買賣商品、向個人或組織匯款,或提供信貸。比特幣可以在專門的貨幣交易所購買、出售和兌換其他貨幣。比特幣可以說是網際網路的完美貨幣形式,因為它快速、安全且無國界。
與傳統貨幣不同,比特幣貨幣完全是虛擬的。沒有實體硬幣,甚至沒有個別的數位硬幣。硬幣隱含在將價值從支出者轉移到接收者的交易中。比特幣使用者控制金鑰,使他們能夠證明在比特幣網路中擁有比特幣的所有權。使用這些金鑰,他們可以簽署交易以解鎖價值,並透過將其轉移給新所有者來花費它。金鑰通常儲存在每個使用者的電腦或智慧型手機上的數位錢包中。擁有可以簽署交易的金鑰是花費比特幣的唯一先決條件,將控制權完全掌握在每個使用者手中。
比特幣是一個分散式點對點(peer-to-peer)系統。因此,沒有中央伺服器或控制點。比特幣單位是透過一個稱為「挖礦(mining)」的過程創建的,該過程涉及重複執行參考最近比特幣交易列表的計算任務。比特幣網路中的任何參與者都可以作為礦工(miner)運作,使用他們的計算裝置來幫助保護交易。平均每 10 分鐘,一位比特幣礦工可以為過去的交易增加安全性,並獲得全新比特幣和最近交易支付的手續費作為獎勵。本質上,比特幣挖礦將中央銀行的貨幣發行和清算功能去中心化,並取代了對任何中央銀行的需求。
比特幣協議包含內建演算法,可在整個網路中調節挖礦功能。礦工必須執行的計算任務難度會動態調整,以便平均每 10 分鐘有人成功,無論在任何時刻有多少礦工(以及多少處理能力)在競爭。該協議還會定期減少創建的新比特幣數量,將曾經創建的比特幣總數限制在略低於 2,100 萬枚的固定總數。結果是,流通中的比特幣數量緊密遵循一條易於預測的曲線,每四年將剩餘硬幣的一半加入流通。在大約第 1,411,200 個區塊,預計將在 2035 年左右產生,將發行 99% 的所有比特幣。由於比特幣的發行率遞減,從長期來看,比特幣貨幣是通貨緊縮的。此外,沒有人可以強迫你接受超出預期發行率創建的任何比特幣。
在幕後,比特幣也是協議、點對點網路和分散式計算創新的名稱。比特幣建立在數十年的密碼學和分散式系統研究之上,並包括至少四項關鍵創新,以獨特而強大的組合結合在一起。比特幣包括:
- 
去中心化點對點網路(比特幣協議) 
- 
公開交易日誌(區塊鏈) 
- 
一組獨立交易驗證和貨幣發行的規則(共識規則) 
- 
在有效區塊鏈上達成全球去中心化共識的機制(工作量證明演算法) 
作為開發者,我將比特幣視為類似於貨幣的網際網路,一個透過分散式計算傳播價值和保護數位資產所有權的網路。比特幣遠不止表面所見。
在本章中,我們將透過解釋一些主要概念和術語、取得必要的軟體以及使用比特幣進行簡單交易來開始。在接下來的章節中,我們將開始解開使比特幣成為可能的技術層次,並檢查比特幣網路和協議的內部運作。
比特幣的歷史
比特幣最早在 2008 年隨著一篇題為「比特幣:點對點電子現金系統」的論文的發表而被描述,[1]該論文以中本聰(Satoshi Nakamoto)的化名撰寫(請參閱 比特幣白皮書 作者:中本聰)。中本聰結合了數位簽章和 Hashcash 等先前的發明,創建了一個完全去中心化的電子現金系統,該系統不依賴中央權威機構進行貨幣發行或交易結算和驗證。一個關鍵創新是使用分散式計算系統(稱為「工作量證明」演算法)平均每 10 分鐘進行一次全球抽獎,允許去中心化網路就交易狀態達成 共識。這優雅地解決了雙重支付問題,即單一貨幣單位可以被花費兩次。以前,雙重支付問題是數位貨幣的一個弱點,透過中央清算所清算所有交易來解決。
比特幣網路於 2009 年啟動,基於中本聰發布的參考實作,此後由許多其他程式設計師修訂。執行工作量證明演算法(挖礦)的機器數量和功率為比特幣提供了安全性和彈性,已呈指數增長,它們的綜合計算能力現在超過了全球頂級超級電腦的綜合計算操作數量。
中本聰於 2011 年 4 月從公眾視野中退出,將開發程式碼和網路的責任留給了一群蓬勃發展的志願者。比特幣背後的人的身分仍然未知。然而,無論是中本聰還是其他任何人都沒有對比特幣系統施加個人控制,該系統基於完全透明的數學原理、開源程式碼和參與者之間的共識運作。這項發明本身具有開創性,並且已經在分散式計算、經濟學和計量經濟學領域催生了新的科學。
入門
比特幣是一個可以使用能夠理解該協議的應用程式存取的協議。「比特幣錢包」是比特幣系統最常見的使用者介面,就像網頁瀏覽器是 HTTP 協議最常見的使用者介面一樣。有許多比特幣錢包的實作和品牌,就像有許多品牌的網頁瀏覽器(例如 Chrome、Safari 和 Firefox)一樣。就像我們都有自己喜歡的瀏覽器一樣,比特幣錢包在品質、效能、安全性、隱私和可靠性方面各不相同。還有一個包含錢包的比特幣協議的參考實作,稱為「Bitcoin Core」,它源自中本聰撰寫的原始實作。
選擇比特幣錢包
比特幣錢包是比特幣生態系統中最積極開發的應用程式之一。競爭激烈,雖然現在可能正在開發一個新錢包,但去年的幾個錢包已不再積極維護。許多錢包專注於特定平台或特定用途,有些更適合初學者,而另一些則充滿了為進階使用者提供的功能。選擇錢包是高度主觀的,取決於使用和使用者專業知識。因此,推薦特定品牌或錢包是沒有意義的。但是,我們可以根據平台和功能對比特幣錢包進行分類,並對存在的所有不同類型的錢包提供一些清晰的說明。值得嘗試幾種不同的錢包,直到找到適合你需求的錢包。
比特幣錢包的類型
比特幣錢包可以根據平台進行以下分類:
- 桌面錢包(Desktop wallet)
- 
桌面錢包是作為參考實作創建的第一種比特幣錢包類型。許多使用者執行桌面錢包是因為它們提供的功能、自主權和控制權。然而,在 Windows 和 macOS 等通用作業系統上執行存在某些安全劣勢,因為這些平台通常不安全且配置不當。 
- 行動錢包(Mobile wallet)
- 
行動錢包是最常見的比特幣錢包類型。在 Apple iOS 和 Android 等智慧型手機作業系統上執行,這些錢包通常是新使用者的絕佳選擇。許多錢包設計簡單易用,但也有功能齊全的行動錢包供進階使用者使用。為了避免下載和儲存大量資料,大多數行動錢包從遠端伺服器檢索資訊,透過向第三方洩露有關你的比特幣地址和餘額的資訊來降低你的隱私。 
- 網頁錢包(Web wallet)
- 
網頁錢包透過網頁瀏覽器存取,並將使用者的錢包儲存在第三方擁有的伺服器上。這類似於網頁郵件,因為它完全依賴第三方伺服器。其中一些服務使用在使用者瀏覽器中執行的客戶端程式碼運作,這將比特幣金鑰的控制權保留在使用者手中,儘管使用者對伺服器的依賴仍然會損害他們的隱私。然而,大多數人從使用者手中奪取比特幣金鑰的控制權,以換取易用性。不建議在第三方系統上儲存大量比特幣。 
- 硬體簽署裝置(Hardware signing devices)
- 
硬體簽署裝置是可以使用專用硬體和韌體儲存金鑰和簽署交易的裝置。它們通常透過 USB 纜線、近場通訊(NFC)或帶有 QR 碼的相機連接到桌面、行動或網頁錢包。透過在專用硬體上處理所有與比特幣相關的操作,這些錢包對許多類型的攻擊的脆弱性較低。硬體簽署裝置有時被稱為「硬體錢包」,但它們需要與功能齊全的錢包配對才能傳送和接收交易,該配對錢包提供的安全性和隱私在使用者使用硬體簽署裝置時獲得多少安全性和隱私方面起著關鍵作用。 
完整節點與輕量級
對比特幣錢包進行分類的另一種方法是根據它們的自主程度以及它們如何與比特幣網路互動:
- 完整節點(Full node)
- 
完整節點是驗證比特幣交易的整個歷史(每個使用者的每筆交易)的程式。可選地,完整節點還可以儲存先前驗證的交易,並向其他比特幣程式提供資料,無論是在同一台電腦上還是透過網際網路。完整節點使用大量電腦資源——大約相當於每天比特幣交易觀看一小時串流影片——但完整節點為其使用者提供完全的自主權。 
- 輕量級客戶端(Lightweight client)
- 
輕量級客戶端,也稱為簡化支付驗證(SPV)客戶端,連接到完整節點或其他遠端伺服器以接收和傳送比特幣交易資訊,但在本地儲存使用者錢包,部分驗證它接收的交易,並獨立建立傳出交易。 
- 第三方 API 客戶端(Third-party API client)
- 
第三方 API 客戶端是透過第三方 API 系統與比特幣互動的客戶端,而不是直接連接到比特幣網路。錢包可能由使用者或第三方伺服器儲存,但客戶端信任遠端伺服器為其提供準確的資訊並保護其隱私。 
| 比特幣是點對點(P2P)網路。完整節點是 對等點(peers):每個對等點獨立驗證每筆已確認的交易,並可以完全權威地向其使用者提供資料。輕量級錢包和其他軟體是 客戶端(clients):每個客戶端依賴一個或多個對等點為其提供有效資料。比特幣客戶端可以對它們接收的某些資料執行二次驗證,並與多個對等點建立連接,以減少對單一對等點完整性的依賴,但客戶端的安全性最終依賴於其對等點的完整性。 | 
誰控制金鑰
一個非常重要的額外考慮因素是 誰控制金鑰。正如我們將在後續章節中看到的,對比特幣的存取是由「私鑰」控制的,私鑰就像非常長的 PIN 碼。如果你是唯一控制這些私鑰的人,你就控制了你的比特幣。相反,如果你沒有控制權,那麼你的比特幣由第三方管理,第三方最終代表你控制你的資金。基於控制權,金鑰管理軟體分為兩個重要類別:錢包,你控制金鑰,以及託管帳戶,一些第三方控制金鑰。為了強調這一點,我(Andreas)創造了這句話:你的金鑰,你的幣。不是你的金鑰,不是你的幣。
結合這些分類,許多比特幣錢包屬於幾個群組,最常見的三個是桌面完整節點(你控制金鑰)、行動輕量級錢包(你控制金鑰)和基於網頁的第三方帳戶(你不控制金鑰)。不同類別之間的界線有時是模糊的,因為軟體在多個平台上執行,並且可以以不同的方式與網路互動。
快速開始
Alice 不是技術使用者,最近才從她的朋友 Joe 那裡聽說比特幣。在一個聚會上,Joe 熱情地向周圍的每個人解釋比特幣,並提供演示。Alice 很感興趣,詢問如何開始使用比特幣。Joe 說行動錢包最適合新使用者,他推薦了幾個他最喜歡的錢包。Alice 下載了 Joe 推薦的其中一個錢包,並將其安裝在她的手機上。
當 Alice 第一次執行她的錢包應用程式時,她選擇了建立新比特幣錢包的選項。因為她選擇的錢包是一個非託管錢包,Alice(而且只有 Alice)將控制她的金鑰。因此,她有責任備份它們,因為丟失金鑰意味著她失去了對比特幣的存取權限。為了便於此操作,她的錢包生成了一個 恢復代碼,可用於恢復她的錢包。
恢復代碼
大多數現代非託管比特幣錢包將為其使用者提供恢復代碼以進行備份。恢復代碼通常由軟體隨機選擇的數字、字母或單詞組成,並用作錢包生成的金鑰的基礎。有關範例,請參閱 [recovery_code_sample]。
| 錢包 | 恢復代碼 | 
|---|---|
| BlueWallet | (1) media (2) suspect (3) effort (4) dish (5) album (6) shaft (7) price (8) junk (9) pizza (10) situate (11) oyster (12) rib | 
| Electrum | nephew dog crane clever quantum crazy purse traffic repeat fruit old clutch | 
| Muun | LAFV TZUN V27E NU4D WPF4 BRJ4 ELLP BNFL | 
| 恢復代碼有時被稱為「助記詞(mnemonic)」或「助記短語(mnemonic phrase)」,這意味著你應該記住這個短語,但將短語寫在紙上比大多數人的記憶力更省力,並且往往更可靠。另一個替代名稱是「種子短語(seed phrase)」,因為它為生成錢包所有金鑰的函數提供輸入(「種子」)。 | 
如果 Alice 的錢包發生問題,她可以下載錢包軟體的新副本,並輸入此恢復代碼來重建她曾經傳送或接收的所有鏈上交易的錢包資料庫。但是,從恢復代碼恢復本身不會恢復 Alice 輸入錢包的任何其他資料,例如她與特定地址或交易相關聯的標籤。儘管失去對該元資料的存取權限不如失去對金錢的存取權限重要,但它仍然可以以自己的方式很重要。想像一下,你需要查看舊的銀行或信用卡對帳單,而你支付給的每個實體的名稱(或支付給你的人)都被遮蔽了。為了防止丟失元資料,許多錢包提供了超出恢復代碼的額外備份功能。
對於某些錢包,該額外備份功能今天甚至比過去更重要。現在許多比特幣支付是使用 鏈外 技術進行的,其中並非每筆支付都儲存在公開區塊鏈中。這降低了使用者的成本並改善了隱私等其他好處,但這意味著像依賴鏈上資料的恢復代碼這樣的機制無法保證恢復使用者的所有比特幣。對於支援鏈外的應用程式,定期備份錢包資料庫非常重要。
值得注意的是,當第一次將資金接收到新的行動錢包時,許多錢包通常會重新驗證你是否已安全備份你的恢復代碼。這可以從簡單的提示到要求使用者手動重新輸入代碼。
| 儘管許多合法錢包會提示你重新輸入恢復代碼,但也有許多惡意軟體應用程式模仿錢包的設計,堅持要求你輸入恢復代碼,然後將任何輸入的代碼轉發給惡意軟體開發者,以便他們可以竊取你的資金。這相當於試圖誘騙你提供銀行密碼的釣魚網站。對於大多數錢包應用程式,它們會要求你輸入恢復代碼的唯一時間是在初始設定期間(在你收到任何比特幣之前)和恢復期間(在你失去對原始錢包的存取權限之後)。如果應用程式在其他任何時間要求你輸入恢復代碼,請諮詢專家以確保你沒有被釣魚。 | 
比特幣地址
Alice現在準備開始使用她的新比特幣錢包。她的錢包應用程式隨機生成了一個私鑰(在 私鑰 中有更詳細的描述),該私鑰將用於導出指向她錢包的比特幣地址。此時,她的比特幣地址不為比特幣網路所知,也沒有在比特幣系統的任何部分「註冊」。她的比特幣地址只是與她的私鑰相對應的數字,她可以使用這些數字來控制對資金的存取。地址是由她的錢包獨立生成的,無需參考或向任何服務註冊。
| 有各種比特幣地址和發票格式。地址和發票可以與其他比特幣使用者共享,他們可以使用它們直接向你的錢包傳送比特幣。你可以與其他人共享地址或發票,而不必擔心比特幣的安全性。與銀行帳號不同,了解你的比特幣地址之一的人無法從你的錢包中提取金錢——你必須發起所有支出。但是,如果你向兩個人提供相同的地址,他們將能夠看到另一個人向你傳送了多少比特幣。如果你公開發佈你的地址,每個人都將能夠看到其他人向該地址傳送了多少比特幣。為了保護你的隱私,你應該在每次請求付款時生成一個帶有新地址的新發票。 | 
接收比特幣
Alice使用 接收 按鈕,該按鈕顯示 QR 碼,如 Alice 在她的行動比特幣錢包上使用接收畫面,並以 QR 碼格式顯示她的地址。 所示。
 
QR 碼是帶有黑白點圖案的正方形,用作條碼的一種形式,其中包含的資訊格式可以被 Joe 的智慧型手機相機掃描。
| 傳送到本書中地址的任何資金都將丟失。如果你想測試傳送比特幣,請考慮將其捐贈給接受比特幣的慈善機構。 | 
取得你的第一個比特幣
新使用者的第一個任務是取得一些比特幣。
比特幣交易是不可逆的。大多數電子支付網路(例如信用卡、簽帳卡、PayPal 和銀行帳戶轉帳)都是可逆的。對於出售比特幣的人來說,這種差異引入了非常高的風險,即買家在收到比特幣後會撤銷電子支付,實際上是欺騙賣家。為了減輕這種風險,接受傳統電子支付以換取比特幣的公司通常要求買家進行身分驗證和信用檢查,這可能需要幾天或幾週的時間。作為新使用者,這意味著你無法立即使用信用卡購買比特幣。但是,只要有一點耐心和創造性思維,你就不需要這樣做。
以下是作為新使用者取得比特幣的一些方法:
- 
找一個擁有比特幣的朋友,直接從他或她那裡購買一些。許多比特幣使用者都是這樣開始的。這種方法最不複雜。認識擁有比特幣的人的一種方法是參加在 Meetup.com 上列出的當地比特幣聚會。 
- 
透過銷售產品或服務來賺取比特幣。如果你是程式設計師,請出售你的程式設計技能。如果你是理髮師,請為比特幣剪髮。 
- 
使用你所在城市的比特幣 ATM。比特幣 ATM 是接受現金並將比特幣傳送到你的智慧型手機比特幣錢包的機器。 
- 
使用連結到你銀行帳戶的比特幣貨幣交易所。許多國家現在都有貨幣交易所,為買家和賣家提供一個市場,以本地貨幣交換比特幣。匯率列表服務,例如 BitcoinAverage,通常會顯示每種貨幣的比特幣交易所列表。 
| 比特幣相對於其他支付系統的優勢之一是,如果使用正確,它為使用者提供了更多的隱私。取得、持有和花費比特幣不需要你向第三方洩露敏感和個人身分識別資訊。但是,在比特幣接觸傳統系統的地方,例如貨幣交易所,通常適用國家和國際法規。為了將比特幣兌換成你的國家貨幣,你通常需要提供身分證明和銀行資訊。使用者應該意識到,一旦比特幣地址附加到身分,其他相關的比特幣交易也可能變得容易識別和追蹤——包括之前進行的交易。這是許多使用者選擇維護獨立於其錢包的專用交易所帳戶的原因之一。 | 
Alice 由朋友介紹給比特幣,因此她有一個簡單的方法來取得她的第一批比特幣。接下來,我們將看看她如何從她的朋友 Joe 那裡購買比特幣,以及 Joe 如何將比特幣傳送到她的錢包。
找到比特幣的當前價格
在Alice 可以從 Joe 那裡購買比特幣之前,他們必須就比特幣和美元之間的 匯率 達成一致。這引出了比特幣新手的一個常見問題:「誰設定了比特幣的價格?」簡短的答案是價格由市場設定。
比特幣與大多數其他貨幣一樣,具有 浮動匯率。這意味著比特幣的價值根據其交易的各種市場中的供需而波動。例如,比特幣的美元「價格」是根據每個市場中最近的比特幣和美元交易計算的。因此,價格往往每秒波動幾次。定價服務將從幾個市場匯總價格,並計算代表貨幣對(例如 BTC/USD)廣泛市場匯率的交易量加權平均值。
有數百個應用程式和網站可以提供當前市場匯率。以下是一些最受歡迎的:
- Bitcoin Average
- 
一個網站,為每種貨幣提供交易量加權平均值的簡單檢視。 
- CoinCap
- 
一項服務,列出數百種加密貨幣的市值和匯率,包括比特幣。 
- Chicago Mercantile Exchange Bitcoin Reference Rate
- 
一個參考匯率,可用於機構和合約參考,作為 CME 提供的投資資料饋送的一部分。 
除了這些各種網站和應用程式外,某些比特幣錢包還會自動轉換比特幣和其他貨幣之間的金額。
傳送和接收比特幣
Alice決定購買 0.001 比特幣。在她和 Joe 檢查匯率後,她給 Joe 適當金額的現金,開啟她的行動錢包應用程式,然後選擇接收。這會顯示帶有 Alice 的第一個比特幣地址的 QR 碼。
然後 Joe 在他的智慧型手機錢包上選擇傳送並開啟 QR 碼掃描器。這允許 Joe 用他的智慧型手機相機掃描條碼,這樣他就不必輸入 Alice 的比特幣地址,這個地址相當長。
Joe 現在將 Alice 的比特幣地址設定為接收者。Joe 輸入金額為 0.001 比特幣(BTC);請參閱 比特幣錢包傳送畫面。。某些錢包可能以不同的面額顯示金額:0.001 BTC 是 1 毫比特幣(mBTC)或 100,000 聰(sats)。
某些錢包可能還建議 Joe 為此交易輸入標籤;如果是這樣,Joe 輸入「Alice」。從現在起的幾週或幾個月後,這將幫助 Joe 記住他為什麼傳送這 0.001 比特幣。某些錢包可能還會提示 Joe 關於手續費。根據錢包和交易的傳送方式,錢包可能會要求 Joe 輸入交易手續費率或向他提示建議的手續費(或手續費率)。交易手續費越高,交易確認的速度就越快(請參閱 確認)。
 
然後 Joe 仔細檢查以確保他輸入了正確的金額,因為他即將傳輸金錢,錯誤很快就會變得不可逆轉。在再次檢查地址和金額後,他按下傳送以傳輸交易。Joe 的行動比特幣錢包構建了一筆交易,該交易將 0.001 BTC 分配給 Alice 提供的地址,從 Joe 的錢包獲取資金,並使用 Joe 的私鑰簽署交易。這告訴比特幣網路 Joe 已授權將價值轉移到 Alice 的新地址。當交易透過點對點協議傳輸時,它會迅速在比特幣網路中傳播。僅僅幾秒鐘後,網路中大多數連接良好的節點接收到交易,並第一次看到 Alice 的地址。
同時,Alice 的錢包不斷「傾聽」比特幣網路上的新交易,尋找與其包含的地址相符的任何交易。在 Joe 的錢包傳輸交易幾秒鐘後,Alice 的錢包將指示它正在接收 0.001 BTC。
Alice 現在是 0.001 BTC 的驕傲擁有者,她可以花費。在接下來的幾天裡,Alice 使用 ATM 和交易所購買更多比特幣。在下一章中,我們將看看她使用比特幣的第一次購買,並更詳細地檢查底層交易和傳播技術。
比特幣如何運作
比特幣系統與傳統銀行和支付系統不同,不需要對第三方的信任。在比特幣中,每個使用者都可以使用在自己電腦上執行的軟體來驗證比特幣系統每個方面的正確運作,而不是中央可信權威機構。在本章中,我們將透過追蹤單一交易通過比特幣系統並觀察它如何記錄在區塊鏈(所有交易的分散式日誌)上,從高層次檢查比特幣。後續章節將深入探討交易、網路和挖礦背後的技術。
比特幣概述
比特幣系統由擁有包含金鑰的錢包的使用者、在網路上傳播的交易,以及透過競爭計算產生共識區塊鏈(所有交易的權威日誌)的礦工組成。
本章中的每個範例都基於比特幣網路上進行的實際交易,透過將資金從一個錢包傳送到另一個錢包來模擬多個使用者之間的互動。在追蹤比特幣網路到區塊鏈的交易時,我們將使用區塊鏈瀏覽器(blockchain explorer)網站來視覺化每個步驟。區塊鏈瀏覽器是作為比特幣搜尋引擎運作的網頁應用程式,它允許你搜尋地址、交易和區塊,並查看它們之間的關係和流程。
熱門的區塊鏈瀏覽器包括以下內容:
每個瀏覽器都有一個搜尋功能,可以接受比特幣地址、交易雜湊、區塊編號或區塊雜湊,並從比特幣網路檢索相應的資訊。對於每個交易或區塊範例,我們將提供一個 URL,以便你可以自己查詢它並詳細研究它。
| 區塊瀏覽器隱私警告 在區塊瀏覽器上搜尋資訊可能會向其運營者洩露你對該資訊感興趣,允許他們將其與你的 IP 位址、瀏覽器詳細資訊、過去的搜尋或其他可識別資訊相關聯。如果你查詢本書中的交易,區塊瀏覽器的運營者可能會猜測你正在學習比特幣,這應該不是問題。但是,如果你查詢自己的交易,運營者可能能夠猜測你已經收到、花費了多少比特幣,以及目前擁有多少比特幣。 | 
從線上商店購買
Alice在上一章中介紹過,是一位剛剛獲得她的第一批比特幣的新使用者。在 取得你的第一個比特幣 中,Alice 與她的朋友 Joe 會面,用一些現金交換比特幣。從那時起,Alice 又購買了額外的比特幣。現在 Alice 將進行她的第一筆支出交易,從 Bob 的線上商店購買高級播客節目的存取權限。
Bob 的網路商店最近開始接受比特幣支付,方法是在其網站上新增比特幣選項。Bob 商店的價格以當地貨幣(美元)列出,但在結帳時,客戶可以選擇以美元或比特幣支付。
Alice 找到她想購買的播客節目並進入結帳頁面。在結帳時,除了常規選項外,還向 Alice 提供了使用比特幣支付的選項。結帳購物車以美元顯示價格,同時也以比特幣(BTC)顯示,按比特幣的當前匯率計算。
Bob 的電子商務系統將自動建立一個包含發票的 QR 碼(發票 QR 碼。)。
 
與僅包含目標比特幣地址的 QR 碼不同,此發票是 QR 編碼的 URI,其中包含目標地址、支付金額和描述。這允許比特幣錢包應用程式預填用於傳送付款的資訊,同時向使用者顯示人類可讀的描述。你可以使用比特幣錢包應用程式掃描 QR 碼以查看 Alice 會看到的內容:
bitcoin:bc1qk2g6u8p4qm2s2lh3gts5cpt2mrv5skcuu7u3e4?amount=0.01577764& label=Bob%27s%20Store& message=Purchase%20at%20Bob%27s%20Store URI 的組成部分 比特幣地址:"bc1qk2g6u8p4qm2s2lh3gts5cpt2mrv5skcuu7u3e4" 支付金額:"0.01577764" 接收者地址的標籤:"Bob's Store" 付款描述:"Purchase at Bob's Store"
| 嘗試使用你的錢包掃描此內容以查看地址和金額,但請勿傳送金錢。 | 
Alice 使用她的智慧型手機掃描顯示幕上的條碼。她的智慧型手機顯示向 Bob's Store 支付正確金額的付款,她選擇傳送以授權付款。在幾秒鐘內(大約與信用卡授權相同的時間),Bob 在收銀機上看到交易。
| 比特幣網路可以處理分數值,例如,從毫比特幣(1/1000 比特幣)到 1/100,000,000 比特幣,稱為聰(satoshi)。本書在談論大於一個比特幣的金額和使用十進位表示法時使用與美元和其他傳統貨幣相同的複數規則,例如「10 bitcoins」或「0.001 bitcoins」。相同的規則也適用於其他比特幣記帳單位,例如毫比特幣和聰。 | 
你可以使用區塊瀏覽器檢查區塊鏈資料,例如 Alice 向 Bob 支付的 交易。
在以下部分中,我們將更詳細地檢查此交易。我們將看到 Alice 的錢包如何構建它,它如何在網路上傳播,它如何被驗證,以及最終 Bob 如何在後續交易中花費該金額。
比特幣交易
簡單來說,交易告訴網路某些比特幣的所有者已授權將該價值轉移給另一個所有者。新所有者現在可以透過建立另一筆交易來花費比特幣,該交易授權轉移給另一個所有者,依此類推,形成一條所有權鏈。
交易輸入和輸出
交易就像複式記帳分類帳中的行。每筆交易包含一個或多個 輸入,用於花費資金。在交易的另一側,有一個或多個 輸出,用於接收資金。輸入和輸出不一定加起來相同的金額。相反,輸出的總和略少於輸入,差額代表隱含的 交易手續費,這是礦工將交易包含在區塊鏈中收取的小額付款。比特幣交易在 作為複式記帳的交易。 中顯示為記帳分類帳條目。
交易還包含每個要花費的比特幣金額(輸入)的所有權證明,其形式是來自所有者的數位簽章,任何人都可以獨立驗證。在比特幣術語中,花費是簽署一筆交易,該交易將價值從先前的交易轉移到由比特幣地址識別的新所有者。
 
交易鏈
Alice向 Bob’s Store 的付款使用先前交易的輸出作為其輸入。在上一章中,Alice 從她的朋友 Joe 那裡收到比特幣以換取現金。我們在 交易鏈,其中一筆交易的輸出是下一筆交易的輸入。 中將其標記為 Transaction 1(Tx1)。
Tx1 將 0.001 比特幣(100,000 聰)傳送到由 Alice 的金鑰鎖定的輸出。她向 Bob’s Store 的新交易(Tx2)將先前的輸出引用為輸入。在圖中,我們使用箭頭顯示該引用,並將輸入標記為「Tx1:0」。在實際交易中,引用是 Alice 從 Joe 那裡收到錢的交易的 32 位元組交易識別碼(txid)。「:0」表示 Alice 收到錢的輸出位置;在這種情況下,第一個位置(位置 0)。
如圖所示,實際的比特幣交易不會明確包含其輸入的值。要確定輸入的值,軟體需要使用輸入的引用來查找正在花費的先前交易輸出。
Alice 的 Tx2 包含兩個新輸出,一個支付 75,000 聰用於播客,另一個支付 20,000 聰回到 Alice 以接收找零。
 
| 序列化的比特幣交易——軟體用於傳送交易的資料格式——使用最小定義的鏈上價值單位的整數編碼要轉移的價值。當比特幣首次建立時,這個單位沒有名稱,一些開發者簡單地稱它為 base unit。後來,許多使用者開始將這個單位稱為 satoshi(sat),以紀念比特幣的創造者。在 交易鏈,其中一筆交易的輸出是下一筆交易的輸入。 和本書中的一些其他插圖中,我們使用聰值,因為這是協議本身使用的。 | 
製造找零
除了一個或多個支付比特幣接收者的輸出外,許多交易還將包括一個支付比特幣支出者的輸出,稱為 找零 輸出。這是因為交易輸入,就像貨幣紙幣一樣,不能部分花費。如果你在商店購買 5 美元的商品,但使用 20 美元的鈔票支付商品,你期望收到 15 美元的找零。相同的概念適用於比特幣交易輸入。如果你購買了一個價值 5 比特幣的商品,但只有一個價值 20 比特幣的輸入可以使用,你將傳送一個 5 比特幣的輸出給商店老闆,另一個 15 比特幣的輸出回到你自己作為找零(不計算你的交易手續費)。
在比特幣協議層面,找零輸出(以及它支付的地址,稱為 找零地址)與支付輸出之間沒有區別。
重要的是,找零地址不必與輸入的地址相同,並且出於隱私原因,通常是來自所有者錢包的新地址。在理想情況下,輸出的兩種不同用途都使用以前從未見過的地址,否則看起來相同,防止任何第三方確定哪些輸出是找零,哪些是付款。但是,出於說明目的,我們在 交易鏈,其中一筆交易的輸出是下一筆交易的輸入。 中為找零輸出新增了陰影。
並非每筆交易都有找零輸出。那些沒有找零輸出的交易稱為 無找零交易,它們只能有一個輸出。只有當要花費的金額與交易輸入中可用的金額減去預期的交易手續費大致相同時,無找零交易才是一個實際的選擇。在 交易鏈,其中一筆交易的輸出是下一筆交易的輸入。 中,我們看到 Bob 建立 Tx3 作為無找零交易,花費他在 Tx2 中收到的輸出。
幣選擇
不同的錢包在選擇在付款中使用哪些輸入時使用不同的策略,稱為 幣選擇。
它們可能會彙總許多小輸入,或使用一個等於或大於所需付款的輸入。除非錢包可以以某種方式彙總輸入以完全符合所需付款加上交易手續費,否則錢包將需要生成一些找零。這與人們處理現金的方式非常相似。如果你總是使用口袋裡最大的鈔票,你最終會得到一個裝滿零錢的口袋。如果你只使用零錢,你通常只會有大鈔票。人們下意識地在這兩個極端之間找到平衡,比特幣錢包開發者努力對這種平衡進行程式設計。
常見交易形式
一種非常常見的交易形式是簡單付款。這種類型的交易有一個輸入和兩個輸出,如 最常見的交易。 所示。
 
另一種常見的交易形式是 合併交易,它將多個輸入花費到單一輸出(合併交易彙總資金。)。這代表了將一堆硬幣和貨幣紙幣交換為單一更大面額紙幣的現實世界等價物。這樣的交易有時由錢包和企業生成,以清理大量較小的金額。
 
最後,另一種在區塊鏈上經常看到的交易形式是 批量付款,它支付多個代表多個接收者的輸出(批次交易分配資金。)。這種類型的交易有時被商業實體用於分配資金,例如在處理向多個員工支付工資時。
 
構建交易
Alice 的錢包應用程式包含所有用於選擇輸入和生成輸出以根據 Alice 的規範建構交易的邏輯。Alice 只需要選擇目的地、金額和交易手續費,其餘的在錢包應用程式中發生,而她看不到詳細資訊。重要的是,如果錢包已經知道它控制哪些輸入,即使完全離線,它也可以構建交易。就像在家裡寫支票並稍後在信封中將其傳送到銀行一樣,交易不需要在連接到比特幣網路時構建和簽署。
獲取正確的輸入
Alice 的錢包應用程式首先必須找到可以支付她想傳送給 Bob 的金額的輸入。大多數錢包會追蹤屬於錢包中地址的所有可用輸出。因此,Alice 的錢包將包含來自 Joe 的交易的交易輸出的副本,該交易是為了交換現金而建立的(請參閱 取得你的第一個比特幣)。在完整節點上執行的比特幣錢包應用程式實際上包含每筆已確認交易的未花費輸出的副本,稱為 未花費交易輸出(UTXO)。但是,由於完整節點使用更多資源,許多使用者錢包執行輕量級客戶端,僅追蹤使用者自己的 UTXO。
在這種情況下,這個單一的 UTXO 足以支付播客。如果不是這種情況,Alice 的錢包應用程式可能必須組合幾個較小的 UTXO,就像從錢包中挑選硬幣一樣,直到它可以找到足夠的錢來支付播客。在這兩種情況下,可能需要取回一些找零,我們將在下一節中看到,因為錢包應用程式會建立交易輸出(付款)。
建立輸出
交易輸出是使用腳本建立的,該腳本說類似於「此輸出支付給任何可以呈現與 Bob 的公開地址相對應的金鑰的簽章的人」。因為只有 Bob 擁有與該地址對應的金鑰的錢包,所以只有 Bob 的錢包可以呈現這樣的簽章以後來花費此輸出。因此,Alice 將使用對 Bob 簽章的要求 加負擔 於輸出值。
此交易還將包括第二個輸出,因為 Alice 的資金包含的錢比播客的成本多。Alice 的找零輸出與支付給 Bob 的付款在同一筆交易中建立。本質上,Alice 的錢包將她的資金分成兩個輸出:一個給 Bob,另一個回到她自己。然後,她可以在後續交易中花費找零輸出。
最後,為了讓交易及時由網路處理,Alice 的錢包應用程式將新增一小筆手續費。手續費不會在交易中明確說明;它是由輸入和輸出之間的價值差異隱含的。此交易手續費由礦工收取,作為將交易包含在記錄在區塊鏈上的區塊中的手續費。
| 檢視從 Alice 到 Bob’s Store 的 交易。 | 
將交易新增到區塊鏈
由Alice 的錢包應用程式建立的交易包含確認資金所有權和分配新所有者所需的一切。現在,交易必須傳輸到比特幣網路,它將成為區塊鏈的一部分。在下一部分中,我們將看到交易如何成為新區塊的一部分以及區塊如何被挖掘。最後,我們將看到新區塊一旦新增到區塊鏈中,隨著新增更多區塊,網路如何越來越信任它。
傳輸交易
因為交易包含處理它所需的所有資訊,所以它如何或在何處傳輸到比特幣網路並不重要。比特幣網路是一個點對點網路,每個比特幣對等點透過連接到其他幾個比特幣對等點來參與。比特幣網路的目的是將交易和區塊傳播給所有參與者。
它如何傳播
比特幣點對點網路中的對等點是同時具有軟體邏輯和完全驗證新交易正確性所需的資料的程式。對等點之間的連接通常在圖形中視覺化為邊(線),對等點本身是節點(點)。因此,比特幣對等點通常被稱為「完全驗證節點」,或簡稱 完整節點。
Alice 的錢包應用程式可以透過任何類型的連接將新交易傳送到任何比特幣節點:有線、WiFi、行動等。它還可以將交易傳送到另一個程式(例如區塊瀏覽器),該程式將其轉發到節點。她的比特幣錢包不必直接連接到 Bob 的比特幣錢包,她也不必使用 Bob 提供的網際網路連接,儘管這兩種選項也是可能的。任何收到它之前沒有見過的有效交易的比特幣節點都會將其轉發到它連接的所有其他節點,這是一種稱為 gossiping的傳播技術。因此,交易在點對點網路中迅速傳播,在幾秒鐘內到達大部分節點。
比特幣挖礦
Alice 的交易現在在比特幣網路上傳播。在它被包含在一個稱為 mining 的過程的區塊中並且該區塊已被完整節點驗證之前,它不會成為 blockchain 的一部分。有關詳細說明,請參閱 挖礦與共識。
比特幣的防偽保護系統基於計算。交易被捆綁成 blocks。區塊有一個非常小的標頭,必須以非常特定的方式形成,需要大量的計算才能做對——但只需要少量的計算就可以驗證為正確。挖礦過程在比特幣中有兩個目的:
- 
礦工只能從建立遵循比特幣所有 consensus rules 的區塊中獲得誠實的收入。因此,礦工通常被激勵僅在其區塊中包含有效交易以及他們建構的區塊。這允許使用者可選地做出基於信任的假設,即區塊中的任何交易都是有效交易。 
- 
挖礦目前在每個區塊中建立新的比特幣,幾乎就像中央銀行印製新錢一樣。每個區塊建立的比特幣數量是有限的,並且隨著時間的推移而減少,遵循固定的發行時間表。 
挖礦在成本和獎勵之間取得了微妙的平衡。挖礦使用電力來解決計算問題。成功的礦工將以新比特幣和交易手續費的形式收集 reward。但是,獎勵只會在礦工僅包含有效交易時收集,比特幣協議的 consensus 規則決定什麼是有效的。這種微妙的平衡為比特幣提供了安全性,無需中央權威機構。
挖礦被設計為去中心化彩票。每個礦工都可以透過建立包含他們想要挖掘的新交易加上一些額外資料欄位的 candidate block 來建立自己的彩票。礦工將其候選者輸入到專門設計的演算法中,該演算法對資料進行加擾(或「雜湊」),產生看起來與輸入資料完全不同的輸出。這個 hash 函數對於相同的輸入總是產生相同的輸出——但沒有人可以預測對於新輸入,輸出會是什麼樣子,即使它與先前的輸入只有微小的不同。如果雜湊函數的輸出與比特幣協議確定的模板相符,礦工就贏得了彩票,比特幣使用者將接受帶有其交易的區塊作為有效區塊。如果輸出與模板不符,礦工會對其候選區塊進行小的更改並再次嘗試。截至撰寫本文時,礦工在找到獲勝組合之前需要嘗試的候選區塊數量約為 168 億兆。這也是雜湊函數需要執行的次數。
但是,一旦找到獲勝組合,任何人都可以透過執行一次雜湊函數來驗證區塊是有效的。這使得有效區塊需要難以置信的工作量才能建立,但只需要微不足道的工作量就可以驗證。簡單的驗證過程能夠機率證明工作已完成,因此生成該證明所需的資料——在這種情況下,區塊——稱為 proof of work (PoW)。
交易被新增到新區塊中,首先按最高手續費率交易和一些其他標準排序。每個礦工在從網路接收到先前的區塊後立即開始挖掘新候選交易區塊的過程,知道其他一些礦工贏得了該輪彩票。他們立即建立一個帶有對先前區塊的承諾的新候選區塊,用交易填充它,並開始計算候選區塊的 PoW。每個礦工在其候選區塊中包含一個特殊交易,該交易將區塊獎勵加上候選區塊中包含的所有交易的交易手續費總和支付給他們自己的比特幣地址。如果他們找到一個使候選者成為有效區塊的解決方案,他們在成功的區塊新增到全局區塊鏈並且他們包含的獎勵交易變得可花費後,將收到此獎勵。參與挖礦池的礦工已設定他們的軟體來建立將獎勵分配給池地址的候選區塊。從那裡,獎勵的一部分按照他們貢獻的工作量的比例分配給池礦工的成員。
Alice 的交易被網路接收,並包含在未驗證交易池中。一旦由完整節點驗證,它就被包含在候選區塊中。在交易首次由 Alice 的錢包傳輸後大約五分鐘,一名礦工為該區塊找到解決方案並將其公佈到網路。在每個其他礦工驗證獲勝區塊後,他們開始新的彩票以生成下一個區塊。
包含 Alice 交易的獲勝區塊成為區塊鏈的一部分。包含 Alice 交易的區塊被計為該交易的一次 confirmation。在包含 Alice 交易的區塊在網路中傳播後,建立帶有 Alice 交易的不同版本的替代區塊(例如不支付給 Bob 的交易)將需要執行與所有比特幣礦工建立全新區塊所需的工作量相同的工作量。當有多個替代區塊可供選擇時,比特幣完整節點選擇具有最多總 PoW 的有效區塊鏈,稱為 best blockchain。為了讓整個網路接受替代區塊,需要在替代區塊之上挖掘額外的新區塊。
這意味著礦工有選擇。他們可以與 Alice 合作製作她向 Bob 付款的交易的替代方案,也許 Alice 向礦工支付她先前支付給 Bob 的一部分錢。這種不誠實的行為將要求他們花費建立兩個新區塊所需的努力。相反,誠實行為的礦工可以建立單個新區塊,並收到他們包含在其中的交易的所有手續費,加上區塊補貼。通常,不誠實地建立兩個區塊以獲得少量額外付款的高成本遠不如誠實地建立新區塊有利可圖,這使得已確認交易不太可能被故意更改。對於 Bob 來說,這意味著他可以開始相信來自 Alice 的付款是可靠的。
| 你可以看到包含 Alice 交易的區塊。 | 
在包含 Alice 交易的區塊廣播後大約 19 分鐘,另一名礦工挖掘了一個新區塊。因為這個新區塊建立在包含 Alice 交易的區塊之上(給 Alice 的交易兩次確認),Alice 的交易現在只能在兩個替代區塊被挖掘後才能更改——加上在它們之上建立的新區塊——總共需要挖掘三個區塊才能讓 Alice 取回她傳送給 Bob 的錢。在包含 Alice 交易的區塊之上挖掘的每個區塊都算作額外的確認。隨著區塊一個接一個地疊加,逆轉交易變得更加困難,從而為 Bob 提供越來越多的信心,即 Alice 的付款是安全的。
在 Alice 的交易包含在區塊中。 中,我們可以看到包含 Alice 交易的區塊。在它下面是數十萬個區塊,在區塊鏈中相互連結(區塊鏈)一直回到第 0 個區塊,稱為 genesis block。隨著時間的推移,隨著新區塊的「高度」增加,整個鏈的計算難度也會增加。按照慣例,任何超過六次確認的區塊都被認為很難更改,因為它需要大量的計算來重新計算六個區塊(加上一個新區塊)。我們將在 挖礦與共識 中更詳細地檢查挖礦過程以及它如何建立信心。
 
花費交易
現在,Alice 的交易已嵌入區塊鏈作為區塊的一部分,所有比特幣應用程式都可以看到它。每個比特幣完整節點都可以獨立驗證交易是有效和可花費的。完整節點驗證從區塊中首次生成比特幣的那一刻起到它們到達 Bob 的地址的每筆交易的資金轉移。輕量級客戶端可以透過確認交易在區塊鏈中並且在它之後挖掘了幾個區塊來部分驗證付款,從而提供保證礦工花費了大量努力承諾它(請參閱 輕量級客戶端)。
Bob 現在可以花費來自這筆交易和其他交易的輸出。例如,Bob 可以透過將價值從 Alice 的播客付款轉移到這些新所有者來向承包商或供應商付款。隨著 Bob 花費從 Alice 和其他客戶收到的付款,他擴展了交易鏈。假設 Bob 為新的網站頁面向他的網頁設計師 Gopesh 付款。現在交易鏈將如 Alice 的交易作為從 Joe 到 Gopesh 的交易鏈的一部分。 所示。
 
在本章中,我們看到了交易如何建立一條將價值從所有者轉移到所有者的鏈。我們還從 Alice 的錢包中建立交易的那一刻起追蹤 Alice 的交易,透過比特幣網路,到將其記錄在區塊鏈上的礦工。在本書的其餘部分中,我們將檢查錢包、地址、簽章、交易、網路以及最後挖礦背後的特定技術。
Bitcoin Core:參考實作
人們只有在相信他們以後能夠花費這些錢時,才會接受金錢來換取他們有價值的商品和服務。偽造或意外貶值的錢可能以後無法花費,因此每個接受比特幣的人都有強烈的動機去驗證他們收到的比特幣的完整性。比特幣系統的設計使得完全在你本地電腦上執行的軟體可以完美地防止偽造、貶值和其他幾個關鍵問題成為可能。提供該功能的軟體稱為 完全驗證節點(full verification node),因為它根據系統中的每條規則驗證每筆已確認的比特幣交易。完全驗證節點,簡稱 完整節點(full nodes),還可以提供用於理解比特幣如何運作以及網路中當前正在發生什麼的工具和資料。
在本章中,我們將安裝 Bitcoin Core,這是自比特幣網路開始以來大多數完整節點運營者一直使用的實作。然後,我們將從你的節點檢查區塊、交易和其他資料,這些資料具有權威性——不是因為某些強大的實體將其指定為這樣,而是因為你的節點獨立驗證了它。在本書的其餘部分,我們將繼續使用 Bitcoin Core 來建立和檢查與區塊鏈和網路相關的資料。
從比特幣到 Bitcoin Core
Bitcoin 是一個 開源 專案,原始碼在開放(MIT)授權下可用,可免費下載並用於任何目的。不僅僅是開源,比特幣是由一個開放的志願者社群開發的。起初,該社群僅由中本聰(Satoshi Nakamoto)組成。到 2023 年,比特幣的原始碼已有 1,000 多位貢獻者,其中約有十幾位開發者幾乎全職從事程式碼工作,還有幾十位兼職開發者。任何人都可以為程式碼做出貢獻—包括你!
當中本聰創建比特幣時,軟體在白皮書發表之前大部分已經完成(在 比特幣白皮書 作者:中本聰 中複製)。中本聰想確保實作在發表論文之前有效。第一個實作,當時簡稱為「Bitcoin」,已經過大量修改和改進。它已經演變成所謂的 Bitcoin Core,以區別於其他實作。Bitcoin Core 是比特幣系統的 參考實作,這意味著它為技術的每個部分應該如何實作提供參考。Bitcoin Core 實作了比特幣的所有方面,包括錢包、交易和區塊驗證引擎、區塊構建工具,以及比特幣點對點通訊的所有現代部分。
Bitcoin Core 架構(來源:Eric Lombrozo)。 顯示了 Bitcoin Core 的架構。
 
儘管 Bitcoin Core 作為系統許多主要部分的參考實作,但比特幣白皮書描述了系統的幾個早期部分。自 2011 年以來,系統的大多數主要部分已在一組 比特幣改進提案(Bitcoin Improvement Proposals,BIPs)中記錄。在本書中,我們按其編號引用 BIP 規範;例如,BIP9 描述了用於比特幣幾個主要升級的機制。
比特幣開發環境
如果你是開發者,你將希望設定一個開發環境,其中包含用於編寫比特幣應用程式的所有工具、函式庫和支援軟體。在這個高度技術性的章節中,我們將逐步完成該過程。如果材料變得太密集(並且你實際上沒有設定開發環境),請隨時跳到下一章,它不那麼技術性。
從原始碼編譯 Bitcoin Core
Bitcoin Core 的原始碼可以作為存檔下載,也可以透過從 GitHub 克隆原始碼儲存庫來下載。在 Bitcoin Core 下載頁面上,選擇最新版本並下載原始碼的壓縮存檔。或者,使用 Git 命令列從 GitHub Bitcoin 頁面建立原始碼的本地副本。
| 在本章的許多範例中,我們將使用作業系統的命令列介面(也稱為「shell」),透過「終端」應用程式存取。shell 將顯示提示,你輸入命令,shell 使用一些文字和新提示回應你的下一個命令。提示在你的系統上可能看起來不同,但在以下範例中,它用 $ 符號表示。在範例中,當你在 $ 符號後看到文字時,不要輸入 $ 符號,而是輸入緊跟其後的命令,然後按 Enter 執行命令。在範例中,每個命令下方的行是作業系統對該命令的回應。當你看到下一個 $ 字首時,你就知道這是一個新命令,你應該重複該過程。 | 
在這裡,我們使用 git 命令建立原始碼的本地副本(「克隆」):
$ git clone https://github.com/bitcoin/bitcoin.git Cloning into 'bitcoin'... remote: Enumerating objects: 245912, done. remote: Counting objects: 100% (3/3), done. remote: Compressing objects: 100% (2/2), done. remote: Total 245912 (delta 1), reused 2 (delta 1), pack-reused 245909 Receiving objects: 100% (245912/245912), 217.74 MiB | 13.05 MiB/s, done. Resolving deltas: 100% (175649/175649), done.
| Git 是最廣泛使用的分散式版本控制系統,是任何軟體開發者工具包的重要組成部分。如果你還沒有,可能需要在你的作業系統上安裝 git 命令或 Git 的圖形使用者介面。 | 
當 Git 克隆操作完成後,你將在目錄 bitcoin 中擁有原始碼儲存庫的完整本地副本。使用 cd 命令切換到此目錄:
$ cd bitcoin
選擇 Bitcoin Core 版本
預設情況下,本地副本將與最新的程式碼同步,這可能是比特幣的不穩定或測試版本。在編譯程式碼之前,透過簽出發布 tag 來選擇特定版本。這將使本地副本與由關鍵字標記識別的程式碼儲存庫的特定快照同步。開發者使用標記按版本號標記程式碼的特定版本。首先,要查找可用的標記,我們使用 git tag 命令:
$ git tag v0.1.5 v0.1.6test1 v0.10.0 ... v0.11.2 v0.11.2rc1 v0.12.0rc1 v0.12.0rc2 ...
標記列表顯示所有發布的比特幣版本。按照慣例,release candidates(發布候選)用於測試,具有字尾「rc」。可以在生產系統上執行的穩定版本沒有字尾。從前面的列表中,選擇最高版本發布,在撰寫本文時為 v24.0.1。要將本地程式碼與此版本同步,請使用 git checkout 命令:
$ git checkout v24.0.1 Note: switching to 'v24.0.1'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. HEAD is now at b3f866a8d Merge bitcoin/bitcoin#26647: 24.0.1 final changes
你可以透過發出命令 git status 來確認你已「簽出」所需的版本:
HEAD detached at v24.0.1 nothing to commit, working tree clean
配置 Bitcoin Core 建構
原始碼包含文件,可以在許多檔案中找到。查看位於 bitcoin 目錄中 README.md 中的主要文件。在本章中,我們將建構 Bitcoin Core 守護程式(伺服器),在 Linux(類 Unix 系統)上也稱為 bitcoind。透過閱讀 doc/build-unix.md 查看在你的平台上編譯 bitcoind 命令列客戶端的說明。替代說明可以在 doc 目錄中找到;例如,Windows 說明的 build-windows.md。在撰寫本文時,Android、FreeBSD、NetBSD、OpenBSD、macOS(OSX)、Unix 和 Windows 的說明可用。
仔細查看建構先決條件,這些先決條件位於建構文件的第一部分。這些是在你可以開始編譯比特幣之前必須存在於你系統上的函式庫。如果缺少這些先決條件,建構過程將失敗並出現錯誤。如果發生這種情況是因為你錯過了先決條件,你可以安裝它,然後從你離開的地方恢復建構過程。假設已安裝先決條件,你可以透過使用 autogen.sh 腳本生成一組建構腳本來啟動建構過程:
$ ./autogen.sh libtoolize: putting auxiliary files in AC_CONFIG_AUX_DIR, 'build-aux'. libtoolize: copying file 'build-aux/ltmain.sh' libtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'build-aux/m4'. ... configure.ac:58: installing 'build-aux/missing' src/Makefile.am: installing 'build-aux/depcomp' parallel-tests: installing 'build-aux/test-driver'
autogen.sh 腳本建立一組自動配置腳本,這些腳本將詢問你的系統以發現正確的設定,並確保你擁有編譯程式碼所需的所有函式庫。其中最重要的是 configure 腳本,它提供了許多不同的選項來自訂建構過程。使用 --help 旗標查看各種選項:
$ ./configure --help `configure' configures Bitcoin Core 24.0.1 to adapt to many kinds of systems. Usage: ./configure [OPTION]... [VAR=VALUE]... ... Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-silent-rules less verbose build output (undo: "make V=1") --disable-silent-rules verbose build output (undo: "make V=0") ...
configure 腳本允許你透過使用 --enable-FEATURE 和 --disable-FEATURE 旗標啟用或停用 bitcoind 的某些功能,其中 FEATURE 被替換為功能名稱,如幫助輸出中列出的。在本章中,我們將使用所有預設功能建構 bitcoind 客戶端。我們不會使用配置旗標,但你應該查看它們以了解哪些可選功能是客戶端的一部分。如果你在學術環境中,電腦實驗室限制可能要求你在主目錄中安裝應用程式(例如,使用 --prefix=$HOME)。
以下是一些有用的選項,可覆蓋 configure 腳本的預設行為:
- --prefix=$HOME
- 這會覆蓋結果可執行檔的預設安裝位置(即 /usr/local/)。使用 - $HOME將所有內容放在你的主目錄中,或使用不同的路徑。
- --disable-wallet
- 這用於停用參考錢包實作。 
- --with-incompatible-bdb
- 如果你正在建構錢包,允許使用不相容版本的 Berkeley DB 函式庫。 
- --with-gui=no
- 不要建構圖形使用者介面,這需要 Qt 函式庫。這僅建構伺服器和命令列 Bitcoin Core。 
接下來,執行 configure 腳本以自動發現所有必要的函式庫並為你的系統建立自訂建構腳本:
$ ./configure checking for pkg-config... /usr/bin/pkg-config checking pkg-config is at least version 0.9.0... yes checking build system type... x86_64-pc-linux-gnu checking host system type... x86_64-pc-linux-gnu checking for a BSD-compatible install... /usr/bin/install -c ... [many pages of configuration tests follow] ...
如果一切順利,configure 命令將透過建立自訂建構腳本結束,這將允許我們編譯 bitcoind。如果有任何缺少的函式庫或錯誤,configure 命令將以錯誤終止,而不是建立建構腳本。如果發生錯誤,最可能是因為缺少或不相容的函式庫。再次查看建構文件,並確保安裝缺少的先決條件。然後再次執行 configure,看看是否可以解決錯誤。
建構 Bitcoin Core 可執行檔
接下來,你將編譯原始碼,這個過程可能需要長達一個小時才能完成,具體取決於你的 CPU 速度和可用記憶體。如果發生錯誤,或編譯過程中斷,可以隨時透過再次輸入 make 恢復。輸入 make 以開始編譯可執行應用程式:
$ make Making all in src CXX bitcoind-bitcoind.o CXX libbitcoin_node_a-addrdb.o CXX libbitcoin_node_a-addrman.o CXX libbitcoin_node_a-banman.o CXX libbitcoin_node_a-blockencodings.o CXX libbitcoin_node_a-blockfilter.o [... many more compilation messages follow ...]
在具有多個 CPU 的快速系統上,你可能希望設定並行編譯作業的數量。例如,make -j 2 將使用兩個核心(如果可用)。如果一切順利,Bitcoin Core 現在已編譯完成。你應該使用 make check 執行單元測試套件,以確保連結的函式庫沒有明顯損壞。最後一步是使用 make install 命令在你的系統上安裝各種可執行檔。由於此步驟需要管理權限,因此可能會提示你輸入使用者密碼:
$ make check && sudo make install Password: Making install in src ../build-aux/install-sh -c -d '/usr/local/lib' libtool: install: /usr/bin/install -c bitcoind /usr/local/bin/bitcoind libtool: install: /usr/bin/install -c bitcoin-cli /usr/local/bin/bitcoin-cli libtool: install: /usr/bin/install -c bitcoin-tx /usr/local/bin/bitcoin-tx ...
bitcoind 的預設安裝將其放在 /usr/local/bin 中。你可以透過向系統詢問可執行檔的路徑來確認 Bitcoin Core 已正確安裝,如下所示:
$ which bitcoind /usr/local/bin/bitcoind $ which bitcoin-cli /usr/local/bin/bitcoin-cli
執行 Bitcoin Core 節點
比特幣的點對點網路由網路「節點」組成,主要由個人和一些提供比特幣服務的企業執行。執行比特幣節點的人對比特幣區塊鏈有直接和權威的看法,並擁有所有可花費比特幣的本地副本,這些副本由他們自己的系統獨立驗證。透過執行節點,你不必依賴任何第三方來驗證交易。此外,透過使用比特幣節點完全驗證你的錢包收到的交易,你為比特幣網路做出貢獻並幫助使其更加強大。
但是,執行節點需要最初下載和處理超過 500 GB 的資料,以及每天約 400 MB 的比特幣交易。這些數字是 2023 年的,並且可能會隨著時間的推移而增加。如果你關閉節點或與網際網路斷開連接幾天,你的節點將需要下載它錯過的資料。例如,如果你關閉 Bitcoin Core 10 天,下次啟動時你將需要下載約 4 GB。
根據你選擇是否索引所有交易並保留區塊鏈的完整副本,你可能還需要大量磁碟空間——如果你計劃執行 Bitcoin Core 幾年,至少需要 1 TB。預設情況下,比特幣節點還會將交易和區塊傳輸到其他節點(稱為「對等點(peers)」),消耗上傳網際網路頻寬。如果你的網際網路連接受限、具有低資料上限或按流量計費(按 GB 計費),你可能不應該在其上執行比特幣節點,或以限制其頻寬的方式執行它(請參閱 配置 Bitcoin Core 節點)。你可以將節點連接到替代網路,例如像 Blockstream Satellite 這樣的免費衛星資料提供商。
| Bitcoin Core 預設保留區塊鏈的完整副本,其中包含自 2009 年成立以來在比特幣網路上確認的幾乎每筆交易。此資料集大小為數百 GB,並在幾小時或幾天內逐步下載,具體取決於你的 CPU 和網際網路連接的速度。在完全下載區塊鏈資料集之前,Bitcoin Core 將無法處理交易或更新帳戶餘額。確保你有足夠的磁碟空間、頻寬和時間來完成初始同步。你可以配置 Bitcoin Core 透過丟棄舊區塊來減少區塊鏈的大小,但它仍會下載整個資料集。 | 
儘管有這些資源需求,數千人仍在執行比特幣節點。有些在像 Raspberry Pi(35 美元的卡片大小電腦)這樣簡單的系統上執行。
你為什麼要執行節點?以下是一些最常見的原因:
- 
你不想依賴任何第三方來驗證你收到的交易。 
- 
你不想向第三方洩露哪些交易屬於你的錢包。 
- 
你正在開發比特幣軟體,需要依賴比特幣節點對網路和區塊鏈進行可程式設計(API)存取。 
- 
你正在建構必須根據比特幣的共識規則驗證交易的應用程式。通常,比特幣軟體公司會執行多個節點。 
- 
你想支援比特幣。執行你用於驗證錢包收到的交易的節點使網路更加強大。 
如果你正在閱讀本書並對強大的安全性、卓越的隱私或開發比特幣軟體感興趣,你應該執行你自己的節點。
配置 Bitcoin Core 節點
Bitcoin Core 將在每次啟動時在其資料目錄中尋找配置檔案。在本節中,我們將檢查各種配置選項並設定配置檔案。要找到配置檔案,請在終端中執行 bitcoind -printtoconsole 並查看前幾行:
$ bitcoind -printtoconsole 2023-01-28T03:21:42Z Bitcoin Core version v24.0.1 2023-01-28T03:21:42Z Using the 'x86_shani(1way,2way)' SHA256 implementation 2023-01-28T03:21:42Z Using RdSeed as an additional entropy source 2023-01-28T03:21:42Z Using RdRand as an additional entropy source 2023-01-28T03:21:42Z Default data directory /home/harding/.bitcoin 2023-01-28T03:21:42Z Using data directory /home/harding/.bitcoin 2023-01-28T03:21:42Z Config file: /home/harding/.bitcoin/bitcoin.conf ... [a lot more debug output] ...
確定配置檔案的位置後,你可以按 Ctrl-C 關閉節點。通常配置檔案位於使用者主目錄下的 .bitcoin 資料目錄內。在你喜歡的編輯器中開啟配置檔案。
Bitcoin Core 提供 100 多個配置選項,可修改網路節點的行為、區塊鏈的儲存以及其操作的許多其他方面。要查看這些選項的列表,請執行 bitcoind --help:
$ bitcoind --help
Bitcoin Core version v24.0.1
Usage:  bitcoind [options]                     Start Bitcoin Core
Options:
  -?
       Print this help message and exit
  -alertnotify=<cmd>
       Execute command when an alert is raised (%s in cmd is replaced by
       message)
...
[many more options]
以下是你可以在配置檔案中設定或作為 bitcoind 的命令列參數的一些最重要的選項:
- alertnotify
- 
執行指定的命令或腳本,向此節點的所有者傳送緊急警報。 
- conf
- 
配置檔案的替代位置。這僅作為 bitcoind 的命令列參數才有意義,因為它不能在它引用的配置檔案中。 
- datadir
- 
選擇放置所有區塊鏈資料的目錄和檔案系統。預設情況下,這是你主目錄的 .bitcoin 子目錄。根據你的配置,在撰寫本文時,這可以使用約 10 GB 到幾乎 1 TB,最大大小預計每年增加數百 GB。 
- prune
- 
透過刪除舊區塊將區塊鏈磁碟空間需求減少到此許多 MB。在無法容納完整區塊鏈的資源受限節點上使用此選項。系統的其他部分將使用目前無法修剪的其他磁碟空間,因此你仍然需要至少 datadir 選項中提到的最小空間量。 
- txindex
- 
維護所有交易的索引。這允許你透過其 ID 以程式設計方式檢索任何交易,前提是包含該交易的區塊尚未被修剪。 
- dbcache
- 
UTXO 快取的大小。預設為 450 mebibytes(MiB)。在高階硬體上增加此大小以減少從磁碟讀取和寫入的頻率,或在低階硬體上減少大小以節省記憶體,代價是更頻繁地使用磁碟。 
- blocksonly
- 
透過僅接受來自對等點的已確認交易區塊而不是中繼未確認交易來最小化你的頻寬使用。 
- maxmempool
- 
將交易記憶體池限制為此許多 MB。在記憶體受限的節點上使用它來減少記憶體使用。 
完全索引節點的範例配置 顯示如何將前面的選項與完全索引節點組合,作為比特幣應用程式的 API 後端執行。
alertnotify=myemailscript.sh "Alert: %s" datadir=/lotsofspace/bitcoin txindex=1
資源受限系統的範例配置 顯示在較小伺服器上執行的資源受限節點。
alertnotify=myemailscript.sh "Alert: %s" blocksonly=1 prune=5000 dbcache=150 maxmempool=150
編輯配置檔案並設定最能代表你需求的選項後,你可以使用此配置測試 bitcoind。使用 printtoconsole 選項執行 Bitcoin Core,以在前台執行並將輸出到控制台:
$ bitcoind -printtoconsole 2023-01-28T03:43:39Z Bitcoin Core version v24.0.1 2023-01-28T03:43:39Z Using the 'x86_shani(1way,2way)' SHA256 implementation 2023-01-28T03:43:39Z Using RdSeed as an additional entropy source 2023-01-28T03:43:39Z Using RdRand as an additional entropy source 2023-01-28T03:43:39Z Default data directory /home/harding/.bitcoin 2023-01-28T03:43:39Z Using data directory /lotsofspace/bitcoin 2023-01-28T03:43:39Z Config file: /home/harding/.bitcoin/bitcoin.conf 2023-01-28T03:43:39Z Config file arg: [main] blockfilterindex="1" 2023-01-28T03:43:39Z Config file arg: [main] maxuploadtarget="1000" 2023-01-28T03:43:39Z Config file arg: [main] txindex="1" 2023-01-28T03:43:39Z Setting file arg: wallet = ["msig0"] 2023-01-28T03:43:39Z Command-line arg: printtoconsole="" 2023-01-28T03:43:39Z Using at most 125 automatic connections 2023-01-28T03:43:39Z Using 16 MiB out of 16 MiB requested for signature cache 2023-01-28T03:43:39Z Using 16 MiB out of 16 MiB requested for script execution 2023-01-28T03:43:39Z Script verification uses 3 additional threads 2023-01-28T03:43:39Z scheduler thread start 2023-01-28T03:43:39Z [http] creating work queue of depth 16 2023-01-28T03:43:39Z Using random cookie authentication. 2023-01-28T03:43:39Z Generated RPC cookie /lotsofspace/bitcoin/.cookie 2023-01-28T03:43:39Z [http] starting 4 worker threads 2023-01-28T03:43:39Z Using wallet directory /lotsofspace/bitcoin/wallets 2023-01-28T03:43:39Z init message: Verifying wallet(s)… 2023-01-28T03:43:39Z Using BerkeleyDB version Berkeley DB 4.8.30 2023-01-28T03:43:39Z Using /16 prefix for IP bucketing 2023-01-28T03:43:39Z init message: Loading P2P addresses… 2023-01-28T03:43:39Z Loaded 63866 addresses from peers.dat 114ms [... more startup messages ...]
一旦你對它正在載入正確的設定並按預期執行感到滿意,就可以按 Ctrl-C 中斷該過程。
要在後台作為程序執行 Bitcoin Core,請使用 daemon 選項啟動它,例如 bitcoind -daemon。
要監視比特幣節點的進度和執行時狀態,請在守護程式模式下啟動它,然後使用命令 bitcoin-cli getblockchaininfo:
$ bitcoin-cli getblockchaininfo
{
  "chain": "main",
  "blocks": 0,
  "headers": 83999,
  "bestblockhash": "[...]19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
  "difficulty": 1,
  "time": 1673379796,
  "mediantime": 1231006505,
  "verificationprogress": 3.783041623201835e-09,
  "initialblockdownload": true,
  "chainwork": "[...]000000000000000000000000000000000000000000000100010001",
  "size_on_disk": 89087,
  "pruned": false,
  "warnings": ""
}這顯示一個區塊鏈高度為 0 個區塊和 83,999 個區塊頭的節點。節點首先從其對等點取得區塊頭,以找到具有最多工作量證明的區塊鏈,然後繼續下載完整區塊,並在執行時驗證它們。
一旦你對選擇的配置選項感到滿意,你應該將 Bitcoin Core 新增到作業系統的啟動腳本中,以便它持續執行並在作業系統重新啟動時重新啟動。你將在 Bitcoin Core 的原始碼目錄下的 contrib/init 中找到各種作業系統的許多範例啟動腳本,以及顯示哪個系統使用哪個腳本的 README.md 檔案。
Bitcoin Core API
Bitcoin Core 實作了一個 JSON-RPC 介面,也可以使用命令列助手 bitcoin-cli 存取。命令列允許我們互動式地試驗也可透過 API 以程式設計方式使用的功能。首先,呼叫 help 命令以查看可用的 Bitcoin Core RPC 命令列表:
$ bitcoin-cli help +== Blockchain == getbestblockhash getblock "blockhash" ( verbosity ) getblockchaininfo ... walletpassphrase "passphrase" timeout walletpassphrasechange "oldpassphrase" "newpassphrase" walletprocesspsbt "psbt" ( sign "sighashtype" bip32derivs finalize )
這些命令中的每一個都可能需要許多參數。要獲得額外的幫助、詳細描述和有關參數的資訊,請在 help 後新增命令名稱。例如,要查看有關 getblockhash RPC 命令的幫助:
$ bitcoin-cli help getblockhash
getblockhash height
Returns hash of block in best-block-chain at height provided.
Arguments:
1. height    (numeric, required) The height index
Result:
"hex"    (string) The block hash
Examples:
> bitcoin-cli getblockhash 1000
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id": "curltest",
  "method": "getblockhash",
  "params": [1000]}' -H 'content-type: text/plain;' http://127.0.0.1:8332/
在幫助資訊的末尾,你將看到 RPC 命令的兩個範例,使用 bitcoin-cli 助手或 HTTP 客戶端 curl。這些範例示範了你可以如何呼叫命令。複製第一個範例並查看結果:
$ bitcoin-cli getblockhash 1000 00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09
結果是一個區塊雜湊,在後續章節中有更詳細的描述。但現在,此命令應該在你的系統上返回相同的結果,證明你的 Bitcoin Core 節點正在執行,正在接受命令,並且有關於區塊 1,000 的資訊要返回給你。
在接下來的部分中,我們將示範一些非常有用的 RPC 命令及其預期輸出。
取得有關 Bitcoin Core 狀態的資訊
Bitcoin Core 透過JSON-RPC 介面提供不同模組的狀態報告。最重要的命令包括 getblockchaininfo、getmempoo⁠l​info、getnetworkinfo 和 getwalletinfo。
比特幣的 getblockchaininfo RPC 命令之前已介紹過。getnetwor⁠k​info 命令顯示有關比特幣網路節點狀態的基本資訊。使用 bitcoin-cli 執行它:
$ bitcoin-cli getnetworkinfo
{
  "version": 240001,
  "subversion": "/Satoshi:24.0.1/",
  "protocolversion": 70016,
  "localservices": "0000000000000409",
  "localservicesnames": [
    "NETWORK",
    "WITNESS",
    "NETWORK_LIMITED"
  ],
  "localrelay": true,
  "timeoffset": -1,
  "networkactive": true,
  "connections": 10,
  "connections_in": 0,
  "connections_out": 10,
  "networks": [
    "...detailed information about all networks..."
  ],
  "relayfee": 0.00001000,
  "incrementalfee": 0.00001000,
  "localaddresses": [
  ],
  "warnings": ""
}資料以 JavaScript 物件表示法(JSON)返回,這種格式可以輕鬆地被所有程式語言「使用」,但也相當易於閱讀。在這些資料中,我們看到 Bitcoin Core 軟體和比特幣協議的版本號。我們看到目前的連接數以及有關比特幣網路和與此節點相關的設定的各種資訊。
| bitcoind 需要一些時間(可能超過一天)才能趕上目前的區塊鏈高度,因為它從其他比特幣節點下載區塊並驗證這些區塊中的每筆交易——在撰寫本文時幾乎有 10 億筆交易。你可以使用 getblockchaininfo 檢查其進度以查看已知區塊的數量。以下範例假設你至少在區塊 775,072。由於比特幣交易的安全性取決於區塊,以下範例中的一些資訊將根據你的節點擁有多少區塊而略有變化。 | 
探索和解碼交易
在 從線上商店購買 中,Alice從 Bob 的商店購買了商品。她的交易記錄在區塊鏈上。讓我們透過將交易 ID(txid)作為參數傳遞來使用 API 檢索和檢查該交易:
$ bitcoin-cli getrawtransaction 466200308696215bbc949d5141a49a41\ 38ecdfdfaa2a8029c1f9bcecd1f96177 01000000000101eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da13569 8679268041c54a0100000000ffffffff02204e0000000000002251203b41daba 4c9ace578369740f15e5ec880c28279ee7f51b07dca69c7061e07068f8240100 000000001600147752c165ea7be772b2c0acb7f4d6047ae6f4768e0141cf5efe 2d8ef13ed0af21d4f4cb82422d6252d70324f6f4576b727b7d918e521c00b51b e739df2f899c49dc267c0ad280aca6dab0d2fa2b42a45182fc83e81713010000 0000
| 交易 ID(txid)不具權威性。區塊鏈中缺少 txid 並不意味著交易未被處理。這被稱為「交易可塑性(transaction malleability)」,因為交易可以在區塊中確認之前被修改,從而改變其 txid。在交易包含在區塊中後,除非發生區塊鏈重組,其中該區塊從最佳區塊鏈中移除,否則其 txid 無法更改。在交易有幾次確認後,重組很少見。 | 
命令 getrawtransaction 返回十六進位表示法的序列化交易。要解碼它,我們使用 decoderawtransaction 命令,將十六進位資料作為參數傳遞。你可以複製 getrawtransaction 返回的十六進位並將其作為參數貼上到 decoderawtransaction:
$ bitcoin-cli decoderawtransaction 01000000000101eb3ae38f27191aa5f3850dc9cad0\ 0492b88b72404f9da135698679268041c54a0100000000ffffffff02204e00000000000022512\ 03b41daba4c9ace578369740f15e5ec880c28279ee7f51b07dca69c7061e07068f82401000000\ 00001600147752c165ea7be772b2c0acb7f4d6047ae6f4768e0141cf5efe2d8ef13ed0af21d4f\ 4cb82422d6252d70324f6f4576b727b7d918e521c00b51be739df2f899c49dc267c0ad280aca6\ dab0d2fa2b42a45182fc83e817130100000000
{
  "txid": "466200308696215bbc949d5141a49a4138ecdfdfaa2a8029c1f9bcecd1f96177",
  "hash": "f7cdbc7cf8b910d35cc69962e791138624e4eae7901010a6da4c02e7d238cdac",
  "version": 1,
  "size": 194,
  "vsize": 143,
  "weight": 569,
  "locktime": 0,
  "vin": [
    {
      "txid": "4ac541802679866935a19d4f40728bb89204d0cac90d85f3a51a19...aeb",
      "vout": 1,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "cf5efe2d8ef13ed0af21d4f4cb82422d6252d70324f6f4576b727b7d918e5...301"
      ],
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00020000,
      "n": 0,
      "scriptPubKey": {
        "asm": "1 3b41daba4c9ace578369740f15e5ec880c28279ee7f51b07dca...068",
        "desc": "rawtr(3b41daba4c9ace578369740f15e5ec880c28279ee7f51b...6ev",
        "hex": "51203b41daba4c9ace578369740f15e5ec880c28279ee7f51b07d...068",
        "address": "bc1p8dqa4wjvnt890qmfws83te0v3qxzsfu7ul63kp7u56w8q...5qn",
        "type": "witness_v1_taproot"
      }
    },
    {
      "value": 0.00075000,
      "n": 1,
      "scriptPubKey": {
        "asm": "0 7752c165ea7be772b2c0acb7f4d6047ae6f4768e",
        "desc": "addr(bc1qwafvze0200nh9vkq4jmlf4sy0tn0ga5w0zpkpg)#qq404gts",
        "hex": "00147752c165ea7be772b2c0acb7f4d6047ae6f4768e",
        "address": "bc1qwafvze0200nh9vkq4jmlf4sy0tn0ga5w0zpkpg",
        "type": "witness_v0_keyhash"
      }
    }
  ]
}
交易解碼顯示了此交易的所有組成部分,包括交易輸入和輸出。在這種情況下,我們看到交易使用了一個輸入並生成了兩個輸出。此交易的輸入是來自先前確認交易的輸出(顯示為輸入 txid)。兩個輸出對應於向 Bob 的付款和回到 Alice 的找零。
我們可以透過使用相同的命令(例如,[.keep-together]#getrawtransaction)#檢查此交易中其 txid 引用的先前交易來進一步探索區塊鏈。從交易跳到交易,我們可以追蹤一條交易鏈,因為硬幣從一個所有者傳遞到另一個所有者。
探索區塊
探索區塊與探索交易類似。但是,區塊可以透過區塊 height(高度) 或區塊 hash(雜湊) 引用。首先,讓我們按高度找到一個區塊。我們使用 getblockhash 命令,該命令將區塊高度作為參數,並返回該區塊的區塊 header hash(區塊頭雜湊):
$ bitcoin-cli getblockhash 123456 0000000000002917ed80650c6174aac8dfc46f5fe36480aaef682ff6cd83c3ca
現在我們知道了所選區塊的區塊頭雜湊,我們可以查詢該區塊。我們使用 getblock 命令,將區塊雜湊作為參數:
$ bitcoin-cli getblock 0000000000002917ed80650c6174aac8dfc46f5fe36480aaef682f\ f6cd83c3ca
{
  "hash": "0000000000002917ed80650c6174aac8dfc46f5fe36480aaef682ff6cd83c3ca",
  "confirmations": 651742,
  "height": 123456,
  "version": 1,
  "versionHex": "00000001",
  "merkleroot": "0e60651a9934e8f0decd1c[...]48fca0cd1c84a21ddfde95033762d86c",
  "time": 1305200806,
  "mediantime": 1305197900,
  "nonce": 2436437219,
  "bits": "1a6a93b3",
  "difficulty": 157416.4018436489,
  "chainwork": "[...]00000000000000000000000000000000000000541788211ac227bc",
  "nTx": 13,
  "previousblockhash": "[...]60bc96a44724fd72daf9b92cf8ad00510b5224c6253ac40095",
  "nextblockhash": "[...]00129f5f02be247070bf7334d3753e4ddee502780c2acaecec6d66",
  "strippedsize": 4179,
  "size": 4179,
  "weight": 16716,
  "tx": [
    "5b75086dafeede555fc8f9a810d8b10df57c46f9f176ccc3dd8d2fa20edd685b",
    "e3d0425ab346dd5b76f44c222a4bb5d16640a4247050ef82462ab17e229c83b4",
    "137d247eca8b99dee58e1e9232014183a5c5a9e338001a0109df32794cdcc92e",
    "5fd167f7b8c417e59106ef5acfe181b09d71b8353a61a55a2f01aa266af5412d",
    "60925f1948b71f429d514ead7ae7391e0edf965bf5a60331398dae24c6964774",
    "d4d5fc1529487527e9873256934dfb1e4cdcb39f4c0509577ca19bfad6c5d28f",
    "7b29d65e5018c56a33652085dbb13f2df39a1a9942bfe1f7e78e97919a6bdea2",
    "0b89e120efd0a4674c127a76ff5f7590ca304e6a064fbc51adffbd7ce3a3deef",
    "603f2044da9656084174cfb5812feaf510f862d3addcf70cacce3dc55dab446e",
    "9a4ed892b43a4df916a7a1213b78e83cd83f5695f635d535c94b2b65ffb144d3",
    "dda726e3dad9504dce5098dfab5064ecd4a7650bfe854bb2606da3152b60e427",
    "e46ea8b4d68719b65ead930f07f1f3804cb3701014f8e6d76c4bdbc390893b94",
    "864a102aeedf53dd9b2baab4eeb898c5083fde6141113e0606b664c41fe15e1f"
  ]
}
confirmations 條目告訴我們此區塊的 depth(深度)——在它之上建構了多少區塊,表明更改此區塊中任何交易的難度。height 告訴我們此區塊之前有多少區塊。我們看到區塊的版本、建立時間(根據其礦工)、前 11 個區塊的中位時間(礦工更難操縱的時間測量),以及三種不同測量的區塊大小(其舊版剝離大小、其完整大小以及其權重單位大小)。我們還看到一些用於安全和工作量證明的欄位(默克爾根、nonce、bits、difficulty 和 chainwork);我們將在 挖礦與共識 中詳細檢查那些。
使用 Bitcoin Core 的程式設計介面
bitcoin-cli 助手對於探索 Bitcoin Core API 和測試功能非常有用。但 API 的全部意義在於以程式設計方式存取功能。在本節中,我們將示範從另一個程式存取 Bitcoin Core。
Bitcoin Core 的 API 是一個 JSON-RPC 介面。JSON 是一種非常方便的方式來呈現人類和程式都可以輕鬆閱讀的資料。RPC 代表遠端[.keep-together]#過程#呼叫,這意味著我們正在透過網路協議呼叫遠端(在 Bitcoin Core 節點上)的過程(函數)。在這種情況下,網路協議是 HTTP。
當我們使用 bitcoin-cli 命令獲取有關命令的幫助時,它向我們顯示了使用 curl(通用命令列 HTTP 客戶端)構建這些 JSON-RPC 呼叫之一的範例:
$ curl --user myusername --data-binary '{"jsonrpc": "1.0", "id":"curltest",
  "method": "getblockchaininfo",
  "params": [] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/
此命令顯示 curl 向本地主機(127.0.0.1)提交 HTTP 請求,連接到預設的比特幣 RPC 埠(8332),並使用 text/plain 編碼為 getblockchaininfo 方法提交 jsonrpc 請求。
你可能會注意到 curl 會要求與請求一起傳送憑證。Bitcoin Core 將在每次啟動時建立一個隨機密碼,並將其放在資料目錄下名為 .cookie 的檔案中。bitcoin-cli 助手可以在給定資料目錄的情況下讀取此密碼檔案。同樣,你可以複製密碼並將其傳遞給 curl(或任何更高層級的 Bitcoin Core RPC 包裝器),如 使用基於 cookie 的身份驗證與 Bitcoin Core 所示。
或者,你可以使用 Bitcoin Core 原始碼目錄中的 ./share/rpcauth/rpcauth.py 中提供的助手腳本建立靜態密碼。
如果你在自己的程式中實作 JSON-RPC 呼叫,你可以使用通用 HTTP 函式庫來構建呼叫,類似於前面的 curl 範例中所示的內容。
但是,大多數流行的程式語言中都有函式庫,以使 Bitcoin Core API 更簡單的方式「包裝」它。我們將使用 python-bitcoinlib 函式庫來簡化 API 存取。此函式庫不是 Bitcoin Core 專案的一部分,需要按照你安裝 Python 函式庫的通常方式安裝。請記住,這需要你執行 Bitcoin Core 實例,該實例將用於進行 JSON-RPC 呼叫。
透過 Bitcoin Core 的 JSON-RPC API 執行 getblockchaininfo 中的 Python 腳本進行簡單的 getblockchaininfo 呼叫,並從 Bitcoin Core 返回的資料中列印 block 參數。
from bitcoin.rpc import RawProxy
# Create a connection to local Bitcoin Core node
p = RawProxy()
# Run the getblockchaininfo command, store the resulting data in info
info = p.getblockchaininfo()
# Retrieve the 'blocks' element from the info
print(info['blocks'])執行它給我們以下結果:
$ python rpc_example.py 773973
它告訴我們本地 Bitcoin Core 節點在其區塊鏈中有多少區塊。不是一個壯觀的結果,但它示範了函式庫作為 Bitcoin Core JSON-RPC API 的簡化介面的基本使用。
接下來,讓我們使用 getrawtransaction 和 decodetransaction 呼叫來檢索 Alice 向 Bob 付款的詳細資訊。在 檢索交易並迭代其輸出 中,我們檢索 Alice 的交易並列出交易的輸出。對於每個輸出,我們顯示接收者地址和值。作為提醒,Alice 的交易有一個輸出支付給 Bob,一個輸出用於找零回到 Alice。
from bitcoin.rpc import RawProxy
p = RawProxy()
# Alice's transaction ID
txid = "466200308696215bbc949d5141a49a4138ecdfdfaa2a8029c1f9bcecd1f96177"
# First, retrieve the raw transaction in hex
raw_tx = p.getrawtransaction(txid)
# Decode the transaction hex into a JSON object
decoded_tx = p.decoderawtransaction(raw_tx)
# Retrieve each of the outputs from the transaction
for output in decoded_tx['vout']:
    print(output['scriptPubKey']['address'], output['value'])執行此程式碼,我們得到:
$ python rpc_transaction.py bc1p8dqa4wjvnt890qmfws83te0v3qxzsfu7ul63kp7u56w8qc0qwp5qv995qn 0.00020000 bc1qwafvze0200nh9vkq4jmlf4sy0tn0ga5w0zpkpg 0.00075000
前面的兩個範例都相當簡單。你實際上不需要程式來執行它們;你可以很容易地使用 bitcoin-cli 助手。但是,下一個範例需要幾百次 RPC 呼叫,並更清楚地示範了程式設計介面的使用。
在 檢索區塊並新增所有交易輸出 中,我們首先檢索一個區塊,然後透過引用每個交易 ID 來檢索其中的每筆交易。接下來,我們迭代每個交易的輸出並加總其值。
from bitcoin.rpc import RawProxy
p = RawProxy()
# The block height where Alice's transaction was recorded
blockheight = 775072
# Get the block hash of the block at the given height
blockhash = p.getblockhash(blockheight)
# Retrieve the block by its hash
block = p.getblock(blockhash)
# Element tx contains the list of all transaction IDs in the block
transactions = block['tx']
block_value = 0
# Iterate through each transaction ID in the block
for txid in transactions:
    tx_value = 0
    # Retrieve the raw transaction by ID
    raw_tx = p.getrawtransaction(txid)
    # Decode the transaction
    decoded_tx = p.decoderawtransaction(raw_tx)
    # Iterate through each output in the transaction
    for output in decoded_tx['vout']:
        # Add up the value of each output
        tx_value = tx_value + output['value']
    # Add the value of this transaction to the total
    block_value = block_value + tx_value
print("Total value in block: ", block_value)執行此程式碼,我們得到:
$ python rpc_block.py Total value in block: 10322.07722534
我們的範例程式碼計算出此區塊中交易的總值為 10,322.07722534 BTC(包括 25 BTC 獎勵和 0.0909 BTC 手續費)。將其與區塊瀏覽器網站報告的金額進行比較,方法是搜尋區塊雜湊或高度。一些區塊瀏覽器報告不包括獎勵和不包括手續費的總值。看看你能否發現差異。
替代客戶端、函式庫和工具包
在比特幣生態系統中有許多替代客戶端、函式庫、工具包,甚至完整節點實作。這些以各種程式語言實作,為程式設計師提供其首選語言的本機介面。
以下部分列出了一些最好的函式庫、客戶端和工具包,按程式語言組織。
C/C++
- Bitcoin Core
- 
比特幣的參考實作 
Java
- bitcoinj
- 
Java 完整節點客戶端函式庫 
Python
- python-bitcoinlib
- 
Peter Todd 的Python 比特幣函式庫、共識函式庫和節點 
- pycoin
- 
Richard Kiss 的 Python 比特幣函式庫 
Go
- btcd
- 
Go 語言,完整節點比特幣客戶端 
Rust
- rust-bitcoin
- 
Rust 比特幣函式庫,用於序列化、解析和 API 呼叫 
Scala
- bitcoin-s
- 
Scala 中的比特幣實作 
C#
- NBitcoin
- 
.NET framework 的綜合比特幣函式庫 
還有許多其他程式語言的更多函式庫,並且一直在建立更多。
如果你按照本章的說明操作,你現在已經執行 Bitcoin Core 並開始使用你自己的完整節點探索網路和區塊鏈。從現在開始,你可以獨立使用你控制的軟體——在你控制的電腦上——來驗證你收到的任何比特幣是否遵循比特幣系統中的每條規則,而無需信任任何外部權威機構。在接下來的章節中,我們將更多地了解系統的規則以及你的節點和錢包如何使用它們來保護你的錢、保護你的隱私,並使支出和接收[.keep-together]方便。
金鑰與地址
Alice 想要支付給 Bob,但成千上萬個將驗證她交易的比特幣完整節點並不知道 Alice 或 Bob 是誰——而我們希望保持這種狀態以保護他們的隱私。Alice 需要向 Bob 傳達他應該收到她的一些比特幣,而不將該交易的任何方面與 Bob 的真實身份或 Bob 收到的其他比特幣支付連結起來。Alice 使用的方法必須確保只有 Bob 能夠進一步花費他收到的比特幣。
原始的比特幣白皮書描述了一個非常簡單的方案來實現這些目標,如 原始比特幣白皮書中的交易鏈 所示。
 
像 Bob 這樣的接收者在一筆交易中接受比特幣到一個公鑰,該交易由支付者(如 Alice)簽署。Alice 正在花費的比特幣之前已經被接收到她的一個公鑰,她使用相應的私鑰來生成她的簽章。完整節點可以驗證 Alice 的簽章承諾了一個雜湊函數的輸出,該函數本身承諾了 Bob 的公鑰和其他交易細節。
我們將在本章中探討公鑰、私鑰、簽章和雜湊函數,然後將它們一起使用來描述現代比特幣軟體使用的地址。
公鑰密碼學
公鑰密碼學(public key cryptography)是在 1970 年代發明的,是現代計算機和資訊安全的數學基礎。
自從公鑰密碼學發明以來,已經發現了幾個合適的數學函數,例如質數指數運算和橢圓曲線乘法。這些數學函數在一個方向上很容易計算,但在相反方向上使用當今可用的計算機和演算法進行計算是不可行的。基於這些數學函數,密碼學能夠創建無法偽造的數位簽章。比特幣使用橢圓曲線加法和乘法作為其密碼學的基礎。
在比特幣中,我們可以使用公鑰密碼學來創建一個金鑰對(key pair),用於控制對比特幣的存取。金鑰對由私鑰和從私鑰衍生的公鑰組成。公鑰用於接收資金,私鑰用於簽署交易以花費資金。
公鑰和私鑰之間存在數學關係,允許私鑰用於生成訊息的簽章。這些簽章可以在不洩露私鑰的情況下針對公鑰進行驗證。
| 在某些錢包實作中,私鑰和公鑰作為一個金鑰對(key pair)一起儲存以方便使用。然而,公鑰可以從私鑰計算出來,因此僅儲存私鑰也是可能的。 | 
比特幣錢包包含一組金鑰對,每個金鑰對由一個私鑰和一個公鑰組成。私鑰(k)是一個數字,通常從隨機選擇的數字衍生而來。從私鑰開始,我們使用橢圓曲線乘法,一種單向密碼函數,來生成公鑰(K)。
私鑰
私鑰(private key)只是一個數字,隨機選取。對私鑰的控制是使用者對與相應比特幣公鑰關聯的所有資金控制的根源。私鑰用於創建簽章,這些簽章用於透過證明對交易中使用的資金的控制來花費比特幣。私鑰必須始終保密,因為向第三方洩露它等同於讓他們控制由該金鑰保護的比特幣。私鑰也必須備份並防止意外丟失,因為如果丟失了,它就無法恢復,而由它保護的資金也將永遠丟失。
| 比特幣私鑰只是一個數字。您可以使用硬幣、鉛筆和紙張隨機選擇您的私鑰:擲硬幣 256 次,您就有了一個隨機私鑰的二進位數字,可以在比特幣錢包中使用。然後可以從私鑰生成公鑰。但要小心,任何不完全隨機的過程都可能大大降低您私鑰的安全性以及它控制的比特幣的安全性。 | 
生成金鑰的第一步也是最重要的步驟是找到一個安全的隨機性來源(計算機科學家稱之為熵(entropy))。創建比特幣金鑰幾乎與「在 1 和 2256 之間選擇一個數字」相同。您用來選擇該數字的確切方法並不重要,只要它不可預測或不可重複。比特幣軟體使用密碼學安全的隨機數生成器來產生 256 位元的熵。
更準確地說,私鑰可以是 0 和 n - 1(包括)之間的任何數字,其中 n 是一個常數(n = 1.1578 × 1077,略小於 2256),定義為比特幣中使用的橢圓曲線的階數(參見 橢圓曲線密碼學解釋)。要創建這樣的金鑰,我們隨機選擇一個 256 位元的數字並檢查它是否小於 n。在程式設計術語中,這通常透過將從密碼學安全的隨機性來源收集的較大隨機位元字串餵送到 SHA256 雜湊演算法中來實現,該演算法將方便地產生一個 256 位元的值,可以解釋為數字。如果結果小於 n,我們就有了一個合適的私鑰。否則,我們只需再次嘗試另一個隨機數。
| 不要自己編寫程式碼來創建隨機數,或使用程式語言提供的「簡單」隨機數生成器。使用密碼學安全的偽隨機數生成器(CSPRNG),其種子來自具有足夠熵的來源。研究您選擇的隨機數生成器函式庫的文件,以確保它是密碼學安全的。正確實作 CSPRNG 對金鑰的安全性至關重要。 | 
以下是一個隨機生成的私鑰(k),以十六進位格式顯示(256 位元顯示為 64 個十六進位數字,每個 4 位元):
1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD
| 比特幣私鑰空間的大小(2256)是一個難以想像的大數字。它在十進位中大約是 1077。相比之下,可見宇宙估計包含 1080 個原子。 | 
橢圓曲線密碼學解釋
橢圓曲線密碼學(Elliptic Curve Cryptography, ECC)是一種非對稱或公鑰密碼學,基於離散對數問題,透過橢圓曲線上點的加法和乘法來表達。
橢圓曲線 是橢圓曲線的範例,類似於比特幣使用的曲線。
 
比特幣使用特定的橢圓曲線和一組數學常數,如由美國國家標準與技術研究院(NIST)建立的標準 secp256k1 中所定義。secp256k1 曲線由以下函數定義,產生一條橢圓曲線:
或
mod p(模質數 p)表示這條曲線在質數階 p 的有限域上,也寫作 \(\(\mathbb{F}_p\)\),其中 p = 2256 – 232 – 29 – 28 – 27 – 26 – 24 – 1,一個非常大的質數。
因為這條曲線定義在質數階的有限域上而不是在實數上,它看起來像分散在二維空間中的點的圖案,這使得它難以視覺化。然而,數學運算與實數上的橢圓曲線相同。作為範例,橢圓曲線密碼學:在 F(p) 上視覺化橢圓曲線,其中 p=17 顯示了在質數階 17 的小得多的有限域上的同一條橢圓曲線,在網格上顯示點的圖案。secp256k1 比特幣橢圓曲線可以被認為是在難以想像的大網格上更複雜的點圖案。
 
例如,以下是座標為 (x, y) 的點 P,它是 secp256k1 曲線上的一個點:
P =
(55066263022277343669578718895168534326250603453777594175500187360389116729240,
32670510020758816978083085130507043184471273380659243275938904335757337482424)使用 Python 確認此點在橢圓曲線上 顯示了如何使用 Python 自己驗證這一點。
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
> p = 115792089237316195423570985008687907853269984665640564039457584007908834671663
> x = 55066263022277343669578718895168534326250603453777594175500187360389116729240
> y = 32670510020758816978083085130507043184471273380659243275938904335757337482424
> (x ** 3 + 7 - y**2) % p
0在橢圓曲線數學中,有一個稱為「無窮遠點」的點,大致對應於加法中零的角色。在計算機上,它有時表示為 x = y = 0(這不滿足橢圓曲線方程式,但它是一個可以檢查的簡單特殊情況)。
還有一個 + 運算子,稱為「加法」,具有一些類似於小學生學習的傳統實數加法的屬性。給定橢圓曲線上的兩點 P1 和 P2,有第三點 P3 = P1 + P2,也在橢圓曲線上。
幾何上,這第三點 P3 的計算方法是在 P1 和 P2 之間畫一條線。這條線將在恰好一個額外的地方與橢圓曲線相交。稱這個點為 P3' = (x, y)。然後在 x 軸上反射以得到 P3 = (x, –y)。
有幾個特殊情況解釋了「無窮遠點」的需要。
如果 P1 和 P2 是同一點,P1 和 P2 之間的線應該延伸為此點 P1 處曲線的切線。這條切線將恰好在一個新點與曲線相交。您可以使用微積分的技術來確定切線的斜率。這些技術奇怪地有效,即使我們將興趣限制在具有兩個整數座標的曲線上的點!
在某些情況下(即,如果 P1 和 P2 具有相同的 x 值但不同的 y 值),切線將完全垂直,在這種情況下 P3 = 「無窮遠點」。
如果 P1 是「無窮遠點」,則 P1 + P2 = P2。同樣,如果 P2 是無窮遠點,則 P1 + P2 = P1。這顯示了無窮遠點如何扮演零的角色。
事實證明,+ 是結合的,這意味著(A + B)+ C = A +(B + C)。這意味著我們可以寫 A + B + C 而不需要括號且沒有歧義。
現在我們已經定義了加法,我們可以用擴展加法的標準方式定義乘法。對於橢圓曲線上的點 P,如果 k 是整數,則 kP = P + P + P + … + P(k 次)。請注意,在這種情況下,k 有時令人困惑地被稱為「指數」。
公鑰
公鑰(public key)是使用橢圓曲線乘法從私鑰計算出來的,這是不可逆的:K = k × G,其中 k 是私鑰,G 是稱為生成點(generator point)的常數點,K 是結果公鑰。反向操作,稱為「尋找離散對數」——如果您知道 K 則計算 k——與嘗試所有可能的 k 值(即暴力搜尋)一樣困難。在我們演示如何從私鑰生成公鑰之前,讓我們更詳細地看一下橢圓曲線密碼學。
| 橢圓曲線乘法是密碼學家稱為「陷阱門」函數的一種函數類型:在一個方向(乘法)上很容易做到,但在相反方向(除法)上不可能做到。擁有私鑰的人可以輕鬆創建公鑰,然後與世界分享它,知道沒有人可以反轉該函數並從公鑰計算私鑰。這個數學技巧成為無法偽造和安全的數位簽章的基礎,這些簽章證明了對比特幣資金的控制。 | 
從隨機生成的數字 k 形式的私鑰開始,我們將其乘以曲線上的預定點,稱為生成點(generator point)G,以在曲線上的某個其他地方產生另一個點,即相應的公鑰 K。生成點被指定為 secp256k1 標準的一部分,並且對於比特幣中的所有金鑰始終相同:
其中 k 是私鑰,G 是生成點,K 是結果公鑰,曲線上的一個點。因為生成點對於所有比特幣使用者始終相同,私鑰 k 乘以 G 將始終產生相同的公鑰 K。k 和 K 之間的關係是固定的,但只能在一個方向上計算,從 k 到 K。這就是為什麼比特幣公鑰(K)可以與任何人共享而不會洩露使用者的私鑰(k)。
| 私鑰可以轉換為公鑰,但公鑰不能轉換回私鑰,因為數學運算只在一個方向上有效。 | 
實作橢圓曲線乘法,我們取之前生成的私鑰 k 並將其乘以生成點 G 以找到公鑰 K:
K = 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD × G公鑰 K 被定義為點 K = (x, y):
其中,
x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
要視覺化點乘以整數,我們將使用實數上更簡單的橢圓曲線——記住,數學運算是相同的。我們的目標是找到生成點 G 的倍數 kG,這與將 G 加到自己,連續 k 次相同。在橢圓曲線中,將一個點加到自己等同於在該點上畫一條切線並找到它再次與曲線相交的位置,然後在 x 軸上反射該點。
橢圓曲線密碼學:在橢圓曲線上視覺化點 G 乘以整數 k 將衍生 G、2G、4G 的過程顯示為曲線上的幾何操作。
| 許多比特幣實作使用 libsecp256k1 密碼函式庫 來進行橢圓曲線數學運算。 | 
 
輸出和輸入腳本
雖然原始比特幣白皮書的插圖(原始比特幣白皮書中的交易鏈)顯示直接使用公鑰(pubkeys)和簽章(sigs),但比特幣的第一個版本實際上是將支付發送到稱為輸出腳本(output script)的欄位,並透過稱為輸入腳本(input script)的欄位授權花費這些比特幣。這些欄位允許除了(或代替)驗證簽章與公鑰對應之外執行額外的操作。例如,輸出腳本可以包含兩個公鑰並要求在支出輸入腳本中放置兩個相應的簽章。
稍後,在 交易腳本和腳本語言 中,我們將詳細學習腳本。現在,我們只需要理解比特幣被接收到一個輸出腳本,該腳本的作用類似於公鑰,而比特幣支出由輸入腳本授權,該腳本的作用類似於簽章。
IP 地址:比特幣的原始地址(P2PK)
我們已經確定 Alice 可以透過將她的一些比特幣分配給 Bob 的一個公鑰來支付 Bob。但是 Alice 如何獲得 Bob 的一個公鑰呢?Bob 可以直接給她一個副本,但讓我們再看一下我們在 公鑰 中使用的公鑰。請注意它相當長。想像一下 Bob 試圖透過電話向 Alice 讀取它:
x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
比特幣軟體的最早版本不是直接輸入公鑰,而是允許支付者輸入接收者的 IP 地址,如 透過 網際網路檔案館 查看早期比特幣的發送畫面 所示。這個功能後來被移除了——使用 IP 地址有很多問題——但對它的快速描述將幫助我們更好地理解為什麼某些功能可能已被新增到比特幣協定中。
 
如果 Alice 在 Bitcoin 0.1 中輸入了 Bob 的 IP 地址,她的完整節點將與他的完整節點建立連線,並從 Bob 的錢包接收一個新的公鑰,該公鑰他的節點以前從未給過任何人。這是一個新的公鑰很重要,以確保支付 Bob 的不同交易不能被某人透過查看區塊鏈並注意到所有交易都支付相同的公鑰而連結在一起。
使用她的節點從 Bob 的節點接收的公鑰,Alice 的錢包將構建一個支付非常簡單的輸出腳本的交易輸出:
<Bob 的公鑰> OP_CHECKSIG
Bob 稍後將能夠使用完全由他的簽章組成的輸入腳本花費該輸出:
<Bob 的簽章>
要弄清楚輸出和輸入腳本在做什麼,您可以將它們組合在一起(輸入腳本在前),然後注意每個資料片段(以尖括號顯示)被放置在項目清單的頂部,稱為堆疊(stack)。當遇到操作碼(opcode)時,它從堆疊中使用項目,從最頂部的項目開始。讓我們從組合的腳本開始看看它是如何工作的:
<Bob 的簽章> <Bob 的公鑰> OP_CHECKSIG
對於此腳本,Bob 的簽章被放在堆疊上,然後 Bob 的公鑰被放在它的頂部。OP_CHECKSIG 操作消耗兩個元素,從公鑰開始,然後是簽章,將它們從堆疊中移除。它驗證簽章對應於公鑰,並且還承諾(簽署)交易中的各個欄位。如果簽章正確,OP_CHECKSIG 在堆疊上用值 1 替換自己;如果簽章不正確,它用 0 替換自己。如果在評估結束時堆疊頂部有一個非零項目,腳本就通過了。如果交易中的所有腳本都通過,並且關於交易的所有其他細節都有效,那麼完整節點將認為該交易有效。
簡而言之,前面的腳本使用了原始白皮書中描述的相同公鑰和簽章,但增加了兩個腳本欄位和一個操作碼的複雜性。這在這裡似乎是額外的工作,但當我們查看下一節時,我們將開始看到好處。
這種輸出類型今天被稱為支付到公鑰(pay to public key),或簡稱 P2PK。它從未被廣泛用於支付,而且幾乎十年來沒有廣泛使用的程式支援 IP 地址支付。
P2PKH 的傳統地址
輸入您想要支付的人的 IP 地址有許多優點,但也有許多缺點。一個特別的缺點是接收者需要他們的錢包在他們的 IP 地址上線上,並且需要從外部世界可存取。對於很多人來說,這不是一個選項。他們在晚上關閉計算機,他們的筆記型電腦進入睡眠狀態,他們位於防火牆後面,或者他們使用網路位址轉換(NAT)。
這讓我們回到像 Bob 這樣的接收者必須向像 Alice 這樣的支付者提供長公鑰的問題。早期比特幣開發者已知的最短版本的比特幣公鑰是 65 位元組,當以十六進位書寫時相當於 130 個字元。然而,比特幣已經包含了幾個資料結構,遠大於 65 位元組,需要在比特幣的其他部分使用最小量的安全資料進行安全引用。
比特幣透過雜湊函數(hash function)來實現這一點,雜湊函數是一種函數,它接受可能大量的資料,對其進行加擾(雜湊),並輸出固定量的資料。密碼雜湊函數在給定相同輸入時將始終產生相同的輸出,並且安全函數還將使某人無法選擇產生先前看到的輸出的不同輸入。這使得輸出成為對輸入的承諾(commitment)。這是一個承諾,實際上只有輸入 x 會產生輸出 X。
例如,想像我想問您一個問題,並以您無法立即閱讀的形式給您我的答案。假設問題是「中本聰是哪一年開始研究比特幣的?」我將以 SHA256 雜湊函數輸出的形式給您我答案的承諾,這是比特幣中最常用的函數:
94d7a772612c8f2f2ec609d41f5bd3d04a5aa1dfe3582f04af517d396a302e4e
稍後,在您告訴我您對問題答案的猜測之後,我可以揭示我的答案並向您證明我的答案作為雜湊函數的輸入,產生與我之前給您的完全相同的輸出:
$ echo "2007. He said about a year and a half before Oct 2008" | sha256sum 94d7a772612c8f2f2ec609d41f5bd3d04a5aa1dfe3582f04af517d396a302e4e
現在想像我們問 Bob 這個問題:「您的公鑰是什麼?」Bob 可以使用雜湊函數給我們一個對他公鑰的密碼學安全承諾。如果他後來揭示他的金鑰,我們驗證它產生與他之前給我們的相同承諾,我們可以確定這是用於創建該早期承諾的完全相同的金鑰。
SHA256 雜湊函數被認為非常安全,並產生 256 位元(32 位元組)的輸出,不到原始比特幣公鑰大小的一半。然而,還有其他稍微不太安全的雜湊函數,產生更小的輸出,例如 RIPEMD-160 雜湊函數,其輸出為 160 位元(20 位元組)。出於中本聰從未說明的原因,原始版本的比特幣透過首先使用 SHA256 雜湊金鑰,然後使用 RIPEMD-160 雜湊該輸出來對公鑰進行承諾;這產生了對公鑰的 20 位元組承諾。
我們可以從演算法上看這個過程。從公鑰 K 開始,我們計算 SHA256 雜湊,然後計算結果的 RIPEMD-160 雜湊,產生一個 160 位元(20 位元組)的數字:
其中 K 是公鑰,A 是結果承諾。
現在我們了解了如何對公鑰進行承諾,我們需要弄清楚如何在交易中使用它。考慮以下輸出腳本:
OP_DUP OP_HASH160 <Bob 的承諾> OP_EQUAL OP_CHECKSIG
以及以下輸入腳本:
<Bob 的簽章> <Bob 的公鑰>
它們一起形成以下腳本:
<sig> <pubkey> OP_DUP OP_HASH160 <commitment> OP_EQUALVERIFY OP_CHECKSIG
正如我們在 IP 地址:比特幣的原始地址(P2PK) 中所做的,我們開始將項目放在堆疊上。Bob 的簽章首先放入;然後將他的公鑰放在堆疊的頂部。OP_DUP 操作複製頂部項目,因此堆疊上的頂部和次頂部項目現在都是 Bob 的公鑰。OP_HASH160 操作消耗(移除)頂部公鑰並用使用 RIPEMD160(SHA256(K)) 雜湊它的結果替換它,因此現在堆疊的頂部是 Bob 公鑰的雜湊。接下來,對 Bob 公鑰的承諾被新增到堆疊的頂部。OP_EQUALVERIFY 操作消耗頂部兩個項目並驗證它們相等;如果 Bob 在輸入腳本中提供的公鑰與用於創建 Alice 支付的輸出腳本中承諾的公鑰相同,則應該如此。如果 OP_EQUALVERIFY 失敗,整個腳本就失敗了。最後,我們剩下一個僅包含 Bob 簽章和他的公鑰的堆疊;OP_CHECKSIG 操作碼驗證它們彼此對應,並且簽章承諾交易。
雖然這個支付到公鑰雜湊(pay to public key hash, P2PKH)的過程可能看起來複雜,但它允許 Alice 對 Bob 的支付僅包含對他公鑰的 20 位元組承諾,而不是金鑰本身,在原始版本的比特幣中金鑰本來是 65 位元組。對於 Bob 必須向 Alice 傳達的資料來說,這要少得多。
然而,我們還沒有討論 Bob 如何將這 20 個位元組從他的比特幣錢包傳遞到 Alice 的錢包。有常用的位元組值編碼,例如十六進位,但複製承諾時犯的任何錯誤都會導致比特幣被發送到無法花費的輸出,導致它們永遠丟失。在下一節中,我們將研究緊湊編碼和可靠的檢查和。
Base58check 編碼
為了以緊湊的方式表示長數字,使用更少的符號,許多計算機系統使用基數(或進位)高於 10 的混合字母數字表示。例如,傳統的十進位系統使用 10 個數字,0 到 9,而十六進位系統使用 16 個,字母 A 到 F 作為六個額外的符號。以十六進位格式表示的數字比等效的十進位表示更短。更緊湊的 base64 表示使用 26 個小寫字母、26 個大寫字母、10 個數字,以及 2 個更多的字元,例如「+」和「/」,以透過基於文字的媒體(如電子郵件)傳輸二進位資料。
Base58 是與 base64 類似的編碼,使用大寫和小寫字母以及數字,但省略了一些經常相互誤認且在某些字體中顯示時可能看起來相同的字元。具體來說,base58 是 base64 去掉 0(數字零)、O(大寫 o)、l(小寫 L)、I(大寫 i)以及符號「+」和「/」。或者更簡單地說,它是一組小寫和大寫字母以及數字,但沒有剛才提到的四個字元(0、O、l、I)。比特幣的 base58 字母表 顯示了完整的 base58 字母表。
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
為了增加對打字錯誤或抄寫錯誤的額外安全性,base58check 包含一個以 base58 字母表編碼的檢查和(checksum)。檢查和是新增到正在編碼的資料末尾的額外四個位元組。檢查和源自編碼資料的雜湊,因此可用於偵測抄寫和打字錯誤。當呈現 base58check 程式碼時,解碼軟體將計算資料的檢查和並將其與程式碼中包含的檢查和進行比較。如果兩者不匹配,則引入了錯誤,base58check 資料無效。這防止了錯誤輸入的比特幣地址被錢包軟體接受為有效目的地,否則會導致資金損失。
要將資料(數字)轉換為 base58check 格式,我們首先向資料新增一個前綴,稱為「版本位元組」(version byte),用於輕鬆識別編碼的資料類型。例如,前綴零(十六進位的 0x00)表示資料應用作傳統 P2PKH 輸出腳本中的承諾(雜湊)。[base58check_versions] 顯示了常見版本前綴的清單。
接下來,我們計算「雙 SHA」檢查和,這意味著我們對前一個結果(前綴與資料串接)應用兩次 SHA256 雜湊演算法:
checksum = SHA256(SHA256(prefix||data))
從產生的 32 位元組雜湊(雜湊的雜湊)中,我們僅取前四個位元組。這四個位元組用作錯誤檢查碼或檢查和。檢查和被附加到末尾。
結果由三個項目組成:前綴、資料和檢查和。此結果使用先前描述的 base58 字母表進行編碼。Base58check 編碼:一種 base58、帶版本和檢查和的格式,用於明確編碼比特幣資料 說明了 base58check 編碼過程。
 
在比特幣中,除了公鑰承諾之外的其他資料也以 base58check 編碼向使用者呈現,以使該資料緊湊、易於閱讀和易於偵測錯誤。base58check 編碼中的版本前綴用於創建易於區分的格式,當以 base58 編碼時,在 base58check 編碼有效載荷的開頭包含特定字元。這些字元使人類易於識別編碼的資料類型以及如何使用它。這就是例如以 1 開頭的 base58check 編碼比特幣地址與以 5 開頭的 base58check 編碼私鑰錢包匯入格式(WIF)之間的區別。一些範例版本前綴和產生的 base58 字元顯示在 #base58check_versions。
| 類型 | 版本前綴(十六進位) | Base58 結果前綴 | 
|---|---|---|
| 支付到公鑰雜湊(P2PKH)的地址 | 0x00 | 1 | 
| 支付到腳本雜湊(P2SH)的地址 | 0x05 | 3 | 
| 測試網路 P2PKH 地址 | 0x6F | m 或 n | 
| 測試網路 P2SH 地址 | 0xC4 | 2 | 
| 私鑰 WIF | 0x80 | 5、K 或 L | 
| BIP32 擴展公鑰 | 0x0488B21E | xpub | 
結合公鑰、基於雜湊的承諾和 base58check 編碼,公鑰到比特幣地址:公鑰轉換為比特幣地址 說明了公鑰轉換為比特幣地址的過程。
 
壓縮公鑰
當比特幣最初編寫時,其開發者只知道如何創建 65 位元組的公鑰。然而,後來的開發者意識到公鑰的替代編碼僅使用 33 位元組,並且與當時所有比特幣完整節點向後相容,因此無需更改比特幣協定。這些 33 位元組的公鑰被稱為壓縮公鑰(compressed public keys),而原始的 65 位元組金鑰被稱為未壓縮公鑰(uncompressed public keys)。使用較小的公鑰會產生較小的交易,允許在同一區塊中進行更多支付。
正如我們在 公鑰 一節中看到的,公鑰是橢圓曲線上的點 (x, y)。因為曲線表達了一個數學函數,曲線上的點代表方程式的解,因此,如果我們知道 x 座標,我們可以透過求解方程式 y2 mod p = (x3 + 7) mod p 來計算 y 座標。這允許我們僅儲存公鑰點的 x 座標,省略 y 座標並將金鑰的大小和儲存所需的空間減少 256 位元。在每筆交易中幾乎 50% 的大小減少隨著時間的推移累積了大量節省的資料!
這是我們在 公鑰 中創建的私鑰生成的公鑰:
x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
以下是顯示為 520 位元數字(130 個十六進位數字)的相同公鑰,前綴為 04,後跟 x 然後是 y 座標,格式為 04 x y:
K = 04F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A\
    07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
而未壓縮公鑰的前綴為 04,壓縮公鑰以 02 或 03 前綴開頭。讓我們看看為什麼有兩個可能的前綴:因為方程式的左側是 y2,y 的解是平方根,可以有正值或負值。視覺上,這意味著產生的 y 座標可以在 x 軸上方或下方。正如您可以從 橢圓曲線 中的橢圓曲線圖中看到的,曲線是對稱的,這意味著它像鏡子一樣被 x 軸反射。因此,雖然我們可以省略 y 座標,但我們必須儲存 y 的符號(正或負);換句話說,我們必須記住它是在 x 軸上方還是下方,因為這些選項中的每一個都代表不同的點和不同的公鑰。在質數階 p 的有限域上以二進位算術計算橢圓曲線時,y 座標是偶數或奇數,這對應於之前解釋的正/負符號。因此,為了區分 y 的兩個可能值,如果 y 是偶數,我們使用前綴 02 儲存壓縮公鑰,如果是奇數,則使用 03,允許軟體正確地從 x 座標推導出 y 座標並將公鑰解壓縮為點的完整座標。公鑰壓縮 說明了公鑰壓縮。
 
以下是在 公鑰 中生成的相同公鑰,顯示為以 264 位元(66 個十六進位數字)儲存的壓縮公鑰,前綴 03 表示 y 座標是奇數:
K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A
這個壓縮公鑰對應於相同的私鑰,這意味著它是從相同的私鑰生成的。然而,它看起來與未壓縮公鑰不同。更重要的是,如果我們使用 HASH160 函數(RIPEMD160(SHA256(K)))將此壓縮公鑰轉換為承諾,它將產生與未壓縮公鑰不同的承諾,導致不同的地址。這可能會令人困惑,因為這意味著單個私鑰可以產生以兩種不同格式(壓縮和未壓縮)表示的公鑰,它們產生兩個不同的比特幣地址。然而,對於兩個比特幣地址,私鑰是相同的。
壓縮公鑰現在是幾乎所有比特幣軟體的預設值,並且在使用後來協定升級中新增的某些新功能時是必需的。
然而,某些軟體仍然需要支援未壓縮公鑰,例如從舊錢包匯入私鑰的錢包應用程式。當新錢包掃描區塊鏈以查找舊 P2PKH 輸出和輸入時,它需要知道是掃描 65 位元組金鑰(以及對這些金鑰的承諾)還是 33 位元組金鑰(及其承諾)。未能掃描正確的類型可能導致使用者無法花費其全部餘額。為了解決這個問題,當從錢包匯出私鑰時,用於表示它們的 WIF 在較新的比特幣錢包中實作方式略有不同,以表明這些私鑰已用於產生壓縮公鑰。
傳統支付到腳本雜湊(P2SH)
正如我們在前面的章節中看到的,接收比特幣的人(如 Bob)可以要求支付給他的款項在其輸出腳本中包含某些約束。Bob 在花費這些比特幣時需要使用輸入腳本來滿足這些約束。在 IP 地址:比特幣的原始地址(P2PK) 中,約束僅僅是輸入腳本需要提供適當的簽章。在 P2PKH 的傳統地址 中,還需要提供適當的公鑰。
對於支付者(如 Alice)將 Bob 想要的約束放入她用於支付他的輸出腳本中,Bob 需要向她傳達這些約束。這類似於 Bob 需要向她傳達他的公鑰的問題。就像那個問題一樣,公鑰可能相當大,Bob 使用的約束也可能相當大——可能有數千位元組。這不僅是需要傳達給 Alice 的數千位元組,而且每次她想向 Bob 花錢時,她都需要為這些位元組支付交易手續費。然而,使用雜湊函數對大量資料創建小承諾的解決方案也適用於此處。
2012 年對比特幣協定的 BIP16 升級允許輸出腳本承諾贖回腳本(redemption script,redeem script)。當 Bob 花費他的比特幣時,他的輸入腳本需要提供與承諾匹配的贖回腳本,以及滿足贖回腳本所需的任何資料(例如簽章)。讓我們從想像 Bob 想要兩個簽章來花費他的比特幣開始,一個來自他的桌面錢包的簽章,一個來自硬體簽署設備的簽章。他將這些條件放入贖回腳本中:
<public key 1> OP_CHECKSIGVERIFY <public key 2> OP_CHECKSIG
然後,他使用與 P2PKH 承諾相同的 HASH160 機制創建對贖回腳本的承諾,RIPEMD160(SHA256(script))。該承諾使用特殊範本放入輸出腳本中:
OP_HASH160 <commitment> OP_EQUAL
| 使用支付到腳本雜湊(P2SH)時,您必須使用特定的 P2SH 範本,輸出腳本中不能有額外的資料或條件。如果輸出腳本不完全是 OP_HASH160 <20 bytes> OP_EQUAL,則不會使用贖回腳本,任何比特幣可能無法花費或可以被任何人花費(意味著任何人都可以拿走它們)。 | 
當 Bob 去花費他收到的對其腳本承諾的支付時,他使用包含贖回腳本的輸入腳本,將其序列化為單個資料元素。他還提供滿足贖回腳本所需的簽章,按照它們將被操作碼消耗的順序放置它們:
<signature2> <signature1> <redeem script>
當比特幣完整節點收到 Bob 的支出時,它們將驗證序列化的贖回腳本是否雜湊到與承諾相同的值。然後它們將用其反序列化的值在堆疊上替換它:
<signature2> <signature1> <pubkey1> OP_CHECKSIGVERIFY <pubkey2> OP_CHECKSIG
腳本被執行,如果它通過並且所有其他交易細節都正確,則交易有效。
P2SH 的地址也使用 base58check 創建。版本前綴設定為 5,這會導致編碼地址以 3 開頭。P2SH 地址的範例是 3F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM。
| P2SH 不一定與多重簽章交易相同。P2SH 地址最常代表多重簽章腳本,但它也可能代表編碼其他類型交易的腳本。 | 
P2PKH 和 P2SH 是使用 base58check 編碼的僅有的兩個腳本範本。它們現在被稱為傳統地址,並且隨著時間的推移變得越來越不常見。傳統地址被 bech32 地址家族所取代。
Bech32 地址
2017 年,比特幣協定進行了升級。當使用升級時,它防止交易識別碼(txid)在沒有支出使用者同意(或當需要多個簽章時的簽署者仲裁)的情況下被更改。這個升級稱為隔離見證(segregated witness,或簡稱 segwit),還為區塊中的交易資料提供了額外的容量以及其他幾個好處。然而,希望直接存取 segwit 好處的使用者必須接受對新輸出腳本的支付。
如 支付到腳本雜湊 中所述,P2SH 輸出類型的優勢之一是支付者(如 Alice)不需要知道接收者(如 Bob)使用的腳本的細節。segwit 升級設計為使用這種機制,允許使用者透過使用 P2SH 地址立即開始存取許多新好處。但是,要讓 Bob 存取所有好處,他需要 Alice 的錢包使用不同類型的腳本向他支付。這將需要 Alice 的錢包升級以支援新腳本。
起初,比特幣開發者提出了 BIP142,它將繼續使用帶有新版本位元組的 base58check,類似於 P2SH 升級。但是,讓所有錢包升級到帶有新 base58check 版本的新腳本預計需要幾乎與讓它們升級到全新地址格式一樣多的工作,因此幾位比特幣貢獻者著手設計可能的最佳地址格式。他們識別了 base58check 的幾個問題:
- 
其大小寫混合的呈現使得大聲朗讀或轉錄不方便。嘗試向朋友朗讀本章中的傳統地址之一,讓他們轉錄它。請注意您必須如何在每個字母前加上「大寫」和「小寫」這些詞。另外,請注意,當您審查他們的書寫時,某些字母的大寫和小寫版本在許多人的筆跡中可能看起來相似。 
- 
它可以偵測錯誤,但不能幫助使用者糾正這些錯誤。例如,如果您在手動輸入地址時不小心調換了兩個字元,您的錢包幾乎肯定會警告存在錯誤,但它不會幫助您找出錯誤所在。您可能需要幾分鐘令人沮喪的時間才能最終發現錯誤。 
- 
大小寫混合的字母表還需要額外的空間來在 QR 碼中編碼,QR 碼通常用於在錢包之間共享地址和發票。這額外的空間意味著 QR 碼需要在相同解析度下更大,否則它們變得更難快速掃描。 
- 
它要求每個支付者錢包升級以支援像 P2SH 和 segwit 這樣的新協定功能。儘管升級本身可能不需要太多程式碼,但經驗表明,許多錢包作者忙於其他工作,有時可能會延遲升級數年。這對每個想要使用新功能的人都有不利影響。 
為 segwit 開發地址格式的開發者在一種名為 bech32(發音為軟「ch」,如「besh thirty-two」)的新地址格式中找到了這些問題的每一個的解決方案。「bech」代表 BCH,這是 1959 年和 1960 年發現 bech32 所基於的循環碼的三個人的首字母縮寫。「32」代表 bech32 字母表中的字元數(類似於 base58check 中的 58):
- 
Bech32 僅使用數字和單一大小寫的字母(最好以小寫呈現)。儘管其字母表幾乎是 base58check 字母表大小的一半,但用於支付到見證公鑰雜湊(P2WPKH)腳本的 bech32 地址僅略長於用於等效 P2PKH 腳本的傳統地址。 
- 
Bech32 既可以偵測又可以幫助糾正錯誤。在預期長度的地址中,它在數學上保證能夠偵測影響四個或更少字元的任何錯誤;這比 base58check 更可靠。對於較長的錯誤,它偵測失敗的機率少於十億分之一,這與 base58check 的可靠性大致相同。更好的是,對於僅輸入了幾個錯誤的地址,它可以告訴使用者這些錯誤發生的位置,讓他們快速糾正輕微的轉錄錯誤。參見 Bech32 錯誤偵測 以獲取輸入錯誤地址的範例。 Example 9. Bech32 錯誤偵測地址: bc1p9nh05ha8wrljf7ru236aw[.underline]n#4t2x0d5ctkkywm[.underline]v#9sclnm4t0av2vgs4k3au7 偵測到的錯誤以粗體和底線顯示。使用 bech32 地址解碼器示範 生成。 
- 
Bech32 最好僅使用小寫字元書寫,但這些小寫字元可以在 QR 碼中編碼地址之前替換為大寫字元。這允許使用占用更少空間的特殊 QR 編碼模式。請注意 以小寫和大寫 QR 編碼的相同 bech32 地址 中同一地址的兩個 QR 碼在大小和複雜性上的差異。  Figure 20. 以小寫和大寫 QR 編碼的相同 bech32 地址 Figure 20. 以小寫和大寫 QR 編碼的相同 bech32 地址
- 
Bech32 利用作為 segwit 一部分設計的升級機制,使支付者錢包能夠支付尚未使用的輸出類型。目標是讓開發者今天構建一個允許向 bech32 地址支出的錢包,並讓該錢包能夠繼續向 bech32 地址支出,以供未來協定升級中新增的新功能的使用者使用。人們希望我們可能永遠不再需要經歷允許人們完全使用 P2SH 和 segwit 所需的全系統升級循環。 
Bech32 地址的問題
Bech32 地址在除一個問題外的所有領域都會成功。關於它們偵測錯誤能力的數學保證僅在您輸入到錢包的地址長度與原始地址的長度相同時才適用。如果您在轉錄過程中新增或刪除任何字元,保證就不適用,您的錢包可能會將資金花費到錯誤的地址。然而,即使沒有保證,人們認為使用者新增或刪除字元產生具有有效檢查和的字串的可能性非常小,確保使用者的資金是安全的。
不幸的是,bech32 演算法中的一個常數的選擇恰好使得在以字母「p」結尾的地址的倒數第二個位置新增或刪除字母「q」變得非常容易。在這些情況下,您還可以多次新增或刪除字母「q」。這有時會被檢查和捕獲,但它被遺漏的頻率遠高於 bech32 替換錯誤的十億分之一的預期。有關範例,請參見 擴展 bech32 地址的長度而不使其檢查和無效。
預期的 bech32 地址: bc1pqqqsq9txsqp 具有有效檢查和的錯誤地址: bc1pqqqsq9txsqqqqp bc1pqqqsq9txsqqqqqqp bc1pqqqsq9txsqqqqqqqqp bc1pqqqsq9txsqqqqqqqqqp bc1pqqqsq9txsqqqqqqqqqqqp
對於 segwit 的初始版本(版本 0),這不是一個實際的問題。v0 segwit 輸出僅定義了兩個有效長度:22 位元組和 34 位元組。這些對應於 42 個字元或 62 個字元長的 bech32 地址,因此某人需要從 bech32 地址的倒數第二個位置新增或刪除字母「q」20 次,才能在錢包無法偵測的情況下將錢發送到無效地址。然而,如果未來實作基於 segwit 的升級,這將成為使用者的問題。
Bech32m
儘管 bech32 對 segwit v0 效果良好,但開發者不想在後來版本的 segwit 中不必要地限制輸出大小。沒有限制,在 bech32 地址中新增或刪除單個「q」可能導致使用者意外地將他們的錢發送到無法花費或任何人都可以花費的輸出(允許任何人拿走這些比特幣)。開發者詳盡地分析了 bech32 問題,並發現更改其演算法中的單個常數將消除該問題,確保任何插入或刪除最多五個字元只會在少於十億分之一的時間內未被偵測到。
具有單個不同常數的 bech32 版本稱為 bech32 modified(bech32m)。對於相同底層資料,bech32 和 bech32m 地址中的所有字元都將相同,除了最後六個(檢查和)。這意味著錢包需要知道正在使用哪個版本才能驗證檢查和,但兩種地址類型都包含一個內部版本位元組,使得確定這一點變得容易。
要同時使用 bech32 和 bech32m,我們將查看 bech32m 比特幣地址的編碼和解析規則,因為它們包含解析 bech32 地址的能力,並且是比特幣錢包當前推薦的地址格式。
Bech32m 地址以人類可讀部分(HRP)開頭。BIP173 中有創建您自己的 HRP 的規則,但對於比特幣,您只需要知道已經選擇的 HRP,如 [bech32_hrps_for_bitcoin] 所示。
| HRP | 網路 | 
|---|---|
| bc | 比特幣主網 | 
| tb | 比特幣測試網路 | 
HRP 後面跟著一個分隔符,數字「1」。早期的協定分隔符提案使用冒號,但某些允許使用者雙擊單詞以突出顯示它以進行複製和貼上的作業系統和應用程式不會將突出顯示延伸到冒號並越過冒號。使用數字確保雙擊突出顯示適用於任何通常支援 bech32m 字串的程式(包括其他數字)。選擇數字「1」是因為 bech32 字串不會在其他地方使用它,以防止數字「1」和小寫字母「l」之間的意外音譯。
bech32m 地址的另一部分稱為「資料部分」。此部分有三個元素:
- 見證版本(Witness version)
- 
單個位元組,在緊接分隔符後的 bech32m 比特幣地址中編碼為單個字元。這個字母代表 segwit 版本。字母「q」是「0」的編碼,用於 segwit v0,即引入 bech32 地址的 segwit 的初始版本。字母「p」是「1」的編碼,用於 segwit v1(也稱為 taproot),其中開始使用 bech32m。segwit 有十七個可能的版本,比特幣要求 bech32m 資料部分的第一個位元組解碼為數字 0 到 16(包括)。 
- 見證程式(Witness program)
- 
從 2 到 40 位元組。對於 segwit v0,此見證程式必須為 20 或 32 位元組;沒有其他長度有效。對於 segwit v1,截至撰寫本文時唯一定義的長度是 32 位元組,但以後可能會定義其他長度。 
- 檢查和(Checksum)
- 
恰好 6 個字元。這是使用 BCH 碼創建的,BCH 碼是一種錯誤糾正碼(儘管對於比特幣地址,我們稍後會看到僅將檢查和用於錯誤偵測而不是糾正至關重要)。 
讓我們透過創建 bech32 和 bech32m 地址的範例來說明這些規則。對於以下所有範例,我們將使用 Python 的 bech32m 參考程式碼。
我們將首先生成四個輸出腳本,每個腳本對應發布時使用的不同 segwit 輸出之一,以及一個尚未定義含義的未來 segwit 版本。腳本列在 [scripts_for_diff_segwit_outputs] 中。
| 輸出類型 | 範例腳本 | 
|---|---|
| P2WPKH | 
 | 
| P2WSH | 
 | 
| P2TR | 
 | 
| 未來範例 | 
 | 
對於 P2WPKH 輸出,見證程式包含以與 P2PKH 的傳統地址 中看到的 P2PKH 輸出承諾完全相同的方式構建的承諾。公鑰被傳遞到 SHA256 雜湊函數中。產生的 32 位元組摘要然後被傳遞到 RIPEMD-160 雜湊函數中。該函數的摘要(承諾)放置在見證程式中。
對於支付到見證腳本雜湊(P2WSH)輸出,我們不使用 P2SH 演算法。相反,我們取腳本,將其傳遞到 SHA256 雜湊函數中,並在見證程式中使用該函數的 32 位元組摘要。對於 P2SH,SHA256 摘要使用 RIPEMD-160 再次雜湊,但在某些情況下這可能不安全;有關詳細資訊,請參見 P2SH 碰撞攻擊。使用沒有 RIPEMD-160 的 SHA256 的結果是 P2WSH 承諾為 32 位元組(256 位元)而不是 20 位元組(160 位元)。
對於支付到 taproot(P2TR)輸出,見證程式是 secp256k1 曲線上的一個點。它可能是一個簡單的公鑰,但在大多數情況下,它應該是承諾某些額外資料的公鑰。我們將在 Taproot 中了解更多關於該承諾的資訊。
對於未來 segwit 版本的範例,我們簡單地使用可能的最高 segwit 版本號(16)和具有空值的最小允許見證程式(2 位元組)。
現在我們知道了版本號和見證程式,我們可以將它們每個都轉換為 bech32 地址。讓我們使用 Python 的 bech32m 參考函式庫快速生成這些地址,然後更深入地了解正在發生的事情:
$ github="https://raw.githubusercontent.com"
$ wget $github/sipa/bech32/master/ref/python/segwit_addr.py
$ python
>>> from segwit_addr import *
>>> from binascii import unhexlify
>>> help(encode)
encode(hrp, witver, witprog)
    編碼 segwit 地址。
>>> encode('bc', 0, unhexlify('2b626ed108ad00a944bb2922a309844611d25468'))
'bc1q9d3xa5gg45q2j39m9y32xzvygcgay4rgc6aaee'
>>> encode('bc', 0,
unhexlify('648a32e50b6fb7c5233b228f60a6a2ca4158400268844c4bc295ed5e8c3d626f'))
'bc1qvj9r9egtd7mu2gemy28kpf4zefq4ssqzdzzycj7zjhk4arpavfhsct5a3p'
>>> encode('bc', 1,
unhexlify('2ceefa5fa770ff24f87c5475d76eab519eda6176b11dbe1618fcf755bfac5311'))
'bc1p9nh05ha8wrljf7ru236awm4t2x0d5ctkkywmu9sclnm4t0av2vgs4k3au7'
>>> encode('bc', 16, unhexlify('0000'))
'bc1sqqqqkfw08p'
如果我們打開檔案 segwit_addr.py 並查看程式碼正在做什麼,我們注意到的第一件事是 bech32(用於 segwit v0)和 bech32m(用於後來的 segwit 版本)之間的唯一差異是常數:
BECH32_CONSTANT = 1 BECH32M_CONSTANT = 0x2bc830a3
接下來我們注意到產生檢查和的程式碼。在檢查和的最後一步,適當的常數使用 xor 操作合併到值中。那個單一值是 bech32 和 bech32m 之間的唯一差異。
創建檢查和後,資料部分中的每個 5 位元字元(包括見證版本、見證程式和檢查和)都被轉換為字母數字字元。
要解碼回輸出腳本,我們反向工作。首先讓我們使用參考函式庫解碼我們的兩個地址:
>>> help(decode)
decode(hrp, addr)
    解碼 segwit 地址。
>>> _ = decode("bc", "bc1q9d3xa5gg45q2j39m9y32xzvygcgay4rgc6aaee")
>>>  _[0], bytes(_[1]).hex()
(0, '2b626ed108ad00a944bb2922a309844611d25468')
>>> _ = decode("bc",
        "bc1p9nh05ha8wrljf7ru236awm4t2x0d5ctkkywmu9sclnm4t0av2vgs4k3au7")
>>> _[0], bytes(_[1]).hex()
(1, '2ceefa5fa770ff24f87c5475d76eab519eda6176b11dbe1618fcf755bfac5311')
我們取回見證版本和見證程式。這些可以插入到我們輸出腳本的範本中:
<version> <program>
例如:
OP_0 2b626ed108ad00a944bb2922a309844611d25468 OP_1 2ceefa5fa770ff24f87c5475d76eab519eda6176b11dbe1618fcf755bfac5311
| 此處需要注意的一個可能錯誤是見證版本「0」用於 OP_0,它使用位元組 0x00——但見證版本「1」使用 OP_1,即位元組 0x51。見證版本「2」到「16」分別使用 0x52 到 0x60。 | 
在實作 bech32m 編碼或解碼時,我們強烈建議您使用 BIP350 中提供的測試向量。我們還要求您確保您的程式碼通過與支付尚未定義的未來 segwit 版本相關的測試向量。這將有助於使您的軟體在未來許多年內可用,即使您無法在新的比特幣功能可用時立即新增對它們的支援。
私鑰格式
私鑰可以用多種不同的格式表示,所有這些格式都對應於相同的 256 位元數字。[table_4-2] 顯示了用於表示私鑰的幾種常見格式。不同的格式在不同的情況下使用。十六進位和原始二進位格式在軟體內部使用,很少向使用者顯示。WIF 用於在錢包之間匯入/匯出金鑰,並經常用於私鑰的 QR 碼(條碼)表示。
| 類型 | 前綴 | 描述 | 
|---|---|---|
| Hex | 無 | 64 個十六進位數字 | 
| WIF | 5 | Base58check 編碼:base58,版本前綴為 128,32 位元檢查和 | 
| WIF-compressed | K 或 L | 如上所述,在編碼前新增後綴 0x01 | 
[table_4-3] 顯示了以幾種不同格式生成的私鑰。
| 格式 | 私鑰 | 
|---|---|
| Hex | 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd | 
| WIF | 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn | 
| WIF-compressed | KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ | 
所有這些表示都是顯示相同數字、相同私鑰的不同方式。它們看起來不同,但任何一種格式都可以輕鬆轉換為任何其他格式。
壓縮私鑰
常用術語「壓縮私鑰」是一個用詞不當,因為當私鑰以 WIF-compressed 格式匯出時,它實際上比「未壓縮」私鑰長一個位元組。這是因為私鑰有一個新增的單位元組後綴(在 [table_4-4] 中以十六進位顯示為 01),這表示私鑰來自較新的錢包,應該僅用於產生壓縮公鑰。私鑰本身沒有被壓縮,也無法被壓縮。術語「壓縮私鑰」實際上意味著「應該僅從中衍生壓縮公鑰的私鑰」,而「未壓縮私鑰」實際上意味著「應該僅從中衍生未壓縮公鑰的私鑰」。您應該僅將匯出格式稱為「WIF-compressed」或「WIF」,而不將私鑰本身稱為「壓縮」,以避免進一步混淆。
[table_4-4] 顯示了以 WIF 和 WIF-compressed 格式編碼的相同金鑰。
| 格式 | 私鑰 | 
|---|---|
| Hex | 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD | 
| WIF | 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn | 
| Hex-compressed | 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD01 | 
| WIF-compressed | KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ | 
請注意,十六進位壓縮私鑰格式在末尾有一個額外的位元組(十六進位的 01)。雖然 WIF 和 WIF-compressed 格式的 base58 編碼版本前綴相同(0x80),但在數字末尾新增一個位元組會導致 base58 編碼的第一個字元從 5 變為 K 或 L。可以將此視為十進位編碼中數字 100 和數字 99 之間差異的 base58 等價物。雖然 100 比 99 長一位數字,但它的前綴也是 1 而不是 9。隨著長度變化,它會影響前綴。在 base58 中,當數字長度增加一個位元組時,前綴 5 變為 K 或 L。
請記住,這些格式不能互換使用。在實作壓縮公鑰的較新錢包中,私鑰只會以 WIF-compressed 格式(帶有 K 或 L 前綴)匯出。如果錢包是較舊的實作並且不使用壓縮公鑰,則私鑰只會以 WIF 格式(帶有 5 前綴)匯出。這裡的目標是向匯入這些私鑰的錢包發出訊號,說明它必須在區塊鏈中搜尋壓縮還是未壓縮的公鑰和地址。
如果比特幣錢包能夠實作壓縮公鑰,它將在所有交易中使用這些公鑰。錢包中的私鑰將用於在曲線上衍生公鑰點,這些公鑰點將被壓縮。壓縮的公鑰將用於產生比特幣地址,這些地址將用於交易中。從實作壓縮公鑰的新錢包匯出私鑰時,WIF 會被修改,在私鑰中新增一個單位元組後綴 01。產生的 base58check 編碼私鑰稱為「壓縮 WIF」,以字母 K 或 L 開頭,而不是像來自較舊錢包的 WIF 編碼(未壓縮)金鑰那樣以「5」開頭。
進階金鑰與地址
在以下章節中,我們將研究進階形式的金鑰和地址,例如靚號地址和紙錢包。
靚號地址
靚號地址(vanity addresses)是包含人類可讀訊息的有效比特幣地址。例如,1LoveBPzzD72PUXLzCkYAtGFYmK5vYNR33 是一個有效地址,包含形成單詞「Love」的字母作為前四個 base58 字母。靚號地址需要生成和測試數十億個候選私鑰,直到找到具有所需圖案的比特幣地址。儘管靚號生成演算法中有一些最佳化,但該過程本質上涉及隨機選擇私鑰、衍生公鑰、衍生比特幣地址,並檢查它是否與所需的靚號圖案匹配,重複數十億次直到找到匹配項。
一旦找到與所需圖案匹配的靚號地址,衍生它的私鑰就可以被擁有者用於花費比特幣,方式與任何其他地址完全相同。靚號地址與任何其他地址一樣安全,沒有更安全或更不安全。它們依賴於與任何其他地址相同的橢圓曲線密碼學(ECC)和安全雜湊演算法(SHA)。您找到以靚號圖案開頭的地址的私鑰並不比您找到任何其他地址的私鑰更容易。
Eugenia 是一位在菲律賓運營的兒童慈善機構董事。假設 Eugenia 正在組織一次募款活動,並希望使用靚號比特幣地址來宣傳募款活動。Eugenia 將創建一個以「1Kids」開頭的靚號地址來宣傳兒童慈善募款活動。讓我們看看如何創建這個靚號地址以及它對 Eugenia 慈善機構的安全意味著什麼。
生成靚號地址
重要的是要認識到,比特幣地址只是一個由 base58 字母表中的符號表示的數字。搜尋像「1Kids」這樣的圖案可以看作是在從 1Kids11111111111111111111111111111 到 1Kidszzzzzzzzzzzzzzzzzzzzzzzzzzzzz 的範圍內搜尋地址。該範圍內大約有 5829(大約 1.4 × 1051)個地址,全部以「1Kids」開頭。[table_4-11] 顯示了具有前綴 1Kids 的地址範圍。
| 從 | 
 | 
| 
 | |
| 
 | |
| 
 | |
| 到 | 
 | 
讓我們將圖案「1Kids」視為一個數字,並查看我們在比特幣地址中找到這個圖案的頻率(見 [table_4-12])。一台普通的桌上型電腦 PC,沒有任何專用硬體,每秒可以搜尋大約 100,000 個金鑰。
| 長度 | 圖案 | 頻率 | 平均搜尋時間 | 
|---|---|---|---|
| 1 | 1K | 1 in 58 keys | < 1 毫秒 | 
| 2 | 1Ki | 1 in 3,364 | 50 毫秒 | 
| 3 | 1Kid | 1 in 195,000 | < 2 秒 | 
| 4 | 1Kids | 1 in 11 million | 1 分鐘 | 
| 5 | 1KidsC | 1 in 656 million | 1 小時 | 
| 6 | 1KidsCh | 1 in 38 billion | 2 天 | 
| 7 | 1KidsCha | 1 in 2.2 trillion | 3–4 個月 | 
| 8 | 1KidsChar | 1 in 128 trillion | 13–18 年 | 
| 9 | 1KidsChari | 1 in 7 quadrillion | 800 年 | 
| 10 | 1KidsCharit | 1 in 400 quadrillion | 46,000 年 | 
| 11 | 1KidsCharity | 1 in 23 quintillion | 250 萬年 | 
正如您所看到的,Eugenia 不會很快創建靚號地址「1KidsCharity」,即使她可以存取數千台電腦。每個額外的字元都會將難度增加 58 倍。超過七個字元的圖案通常由專用硬體找到,例如具有多個圖形處理單元(GPU)的訂製桌上型電腦。GPU 系統上的靚號搜尋比通用 CPU 上快許多數量級。
另一種找到靚號地址的方法是將工作外包給靚號礦工池。https://oreil.ly/99K81[靚號池] 是一項服務,允許那些擁有快速硬體的人透過為他人搜尋靚號地址來賺取比特幣。只需支付費用,Eugenia 就可以將搜尋七個字元圖案靚號地址的工作外包出去,並在幾個小時內獲得結果,而不必運行 CPU 搜尋數月。
生成靚號地址是一種暴力練習:嘗試隨機金鑰,檢查產生的地址是否與所需圖案匹配,重複直到成功。
靚號地址的安全性和隱私
靚號地址在比特幣早期很流行,但截至 2023 年幾乎完全不再使用。這種趨勢有兩個可能的原因:
- 確定性錢包
- 
正如我們在 恢復代碼 中看到的,可以透過簡單地寫下幾個單詞或字元來備份大多數現代錢包中的每個金鑰。這是透過使用確定性演算法從這些單詞或字元衍生錢包中的每個金鑰來實現的。除非使用者為他們創建的每個靚號地址備份額外資料,否則無法將靚號地址與確定性錢包一起使用。更實際的是,大多數使用確定性金鑰生成的錢包根本不允許從靚號生成器匯入私鑰或金鑰調整。 
- 避免地址重複使用
- 
使用靚號地址接收多筆支付到同一地址會在所有這些支付之間創建連結。這對 Eugenia 來說可能是可以接受的,如果她的非營利組織無論如何都需要向稅務機關報告其收入和支出。然而,這也降低了向 Eugenia 支付或從她那裡接收支付的人的隱私。例如,Alice 可能想要匿名捐贈,Bob 可能不希望他的其他客戶知道他給 Eugenia 提供折扣價格。 
除非解決前述問題,否則我們預計未來不會看到很多靚號地址。
紙錢包
紙錢包(paper wallets)是列印在紙上的私鑰。通常,紙錢包還包含相應的比特幣地址以方便使用,但這不是必需的,因為它可以從私鑰衍生。
| 紙錢包是一項過時的技術,對大多數使用者來說是危險的。生成它們涉及許多微妙的陷阱,其中最重要的是生成程式碼可能被「後門」洩露的可能性。許多比特幣就是以這種方式被竊取的。這裡展示紙錢包僅供參考,不應用於儲存比特幣。使用恢復碼來備份您的金鑰,可能使用硬體簽署設備來儲存金鑰和簽署交易。不要使用紙錢包。 | 
紙錢包有多種設計和尺寸,具有許多不同的功能。簡單紙錢包的範例 顯示了一個範例紙錢包。
 
有些旨在作為禮物贈送,並具有季節性主題,例如聖誕節和新年。其他設計用於儲存在銀行保險庫或保險箱中,私鑰以某種方式隱藏,可以用不透明的刮刮貼紙,或者摺疊並用防篡改膠箔密封。其他設計具有金鑰和地址的額外副本,以可拆卸存根的形式,類似於票根,允許您儲存多個副本以防止火災、洪水或其他自然災害。
從比特幣原始的以公鑰為中心的設計到現代地址和腳本(如 bech32m 和支付到 taproot)——甚至未來比特幣升級的地址——您已經了解了比特幣協定如何允許支付者識別應該接收其支付的錢包。但是,當實際上是您的錢包接收支付時,您會希望確保即使您的錢包資料發生問題,您仍然可以存取這些資金。在下一章中,我們將研究比特幣錢包如何設計以保護其資金免受各種威脅。
錢包恢復
創建私鑰和公鑰對是允許比特幣錢包接收和花費比特幣的關鍵部分。但是失去對私鑰的存取可能會使任何人都無法花費接收到相應公鑰的比特幣。多年來,錢包和協定開發者一直致力於設計系統,使使用者能夠在出現問題後恢復對其比特幣的存取,而不會在其他時間損害安全性。
在本章中,我們將研究錢包採用的一些不同方法,以防止資料丟失變成金錢損失。某些解決方案幾乎沒有缺點,並被現代錢包普遍採用。我們將簡單地推薦這些解決方案作為最佳實踐。其他解決方案既有優點也有缺點,導致不同的錢包作者做出不同的權衡。在這些情況下,我們將描述各種可用的選項。
獨立金鑰生成
實體現金錢包持有現金,因此許多人錯誤地認為比特幣錢包包含比特幣也就不足為奇了。實際上,許多人稱之為比特幣錢包的東西——我們稱之為錢包資料庫以將其與錢包應用程式區分開來——僅包含金鑰。這些金鑰與記錄在區塊鏈上的比特幣相關聯。透過向比特幣完整節點證明您控制這些金鑰,您可以花費相關的比特幣。
簡單的錢包資料庫包含接收比特幣的公鑰和允許創建授權花費這些比特幣所需的簽章的私鑰。其他錢包的資料庫可能僅包含公鑰,或僅包含授權支出交易所需的部分私鑰。它們的錢包應用程式透過與外部工具(例如硬體簽署設備或多重簽章方案中的其他錢包)協作來產生必要的簽章。
錢包應用程式可以獨立生成它以後計劃使用的每個錢包金鑰,如 非確定性金鑰生成:儲存在錢包資料庫中的獨立生成金鑰集合 所示。所有早期的比特幣錢包應用程式都這樣做,但這需要使用者在每次生成和分發新金鑰時備份錢包資料庫,這可能與每次生成新地址以接收新支付一樣頻繁。未能及時備份錢包資料庫將導致使用者失去對接收到未備份金鑰的任何資金的存取。
對於每個獨立生成的金鑰,使用者需要備份大約 32 位元組,加上開銷。一些使用者和錢包應用程式試圖透過僅使用單個金鑰來最小化需要備份的資料量。儘管這可能是安全的,但它嚴重降低了該使用者以及與他們交易的所有人的隱私。重視自己和同伴隱私的人為每筆交易創建新的金鑰對,產生的錢包資料庫只能合理地使用數位媒體進行備份。
 
現代錢包應用程式不獨立生成金鑰,而是使用可重複(確定性)演算法從單個隨機種子衍生它們。
確定性金鑰生成
雜湊函數在給定相同輸入時將始終產生相同的輸出,但如果輸入即使發生輕微變化,輸出也會不同。如果該函數是密碼學安全的,沒有人應該能夠預測新輸出——即使他們知道新輸入也是如此。
這允許我們取一個隨機值並將其轉換為幾乎無限數量的看似隨機的值。更有用的是,稍後使用相同的雜湊函數與相同的輸入(稱為種子(seed))將產生相同的看似隨機的值:
# 收集一些熵(隨機性)
$ dd if=/dev/random count=1 status=none | sha256sum
f1cc3bc03ef51cb43ee7844460fa5049e779e7425a6349c8e89dfbb0fd97bb73  -
# 將我們的種子設定為隨機值
$ seed=f1cc3bc03ef51cb43ee7844460fa5049e779e7425a6349c8e89dfbb0fd97bb73
# 確定性地生成衍生值
$ for i in {0..2} ; do echo "$seed + $i" | sha256sum ; done
50b18e0bd9508310b8f699bad425efdf67d668cb2462b909fdb6b9bd2437beb3  -
a965dbcd901a9e3d66af11759e64a58d0ed5c6863e901dfda43adcd5f8c744f3  -
19580c97eb9048599f069472744e51ab2213f687d4720b0efc5bb344d624c3aa  -
如果我們將衍生值用作私鑰,我們以後可以透過使用我們的種子值與我們之前使用的演算法來準確生成這些相同的私鑰。確定性金鑰生成的使用者可以透過簡單地記錄他們的種子和他們使用的確定性演算法的參考來備份錢包中的每個金鑰。例如,即使 Alice 有一百萬個比特幣接收到一百萬個不同的地址,她稍後為了恢復對這些比特幣的存取而需要備份的所有內容只是:
f1cc 3bc0 3ef5 1cb4 3ee7 8444 60fa 5049 e779 e742 5a63 49c8 e89d fbb0 fd97 bb73
基本順序確定性金鑰生成的邏輯圖如 確定性金鑰生成:從種子衍生的確定性金鑰序列用於錢包資料庫 所示。然而,現代錢包應用程式有一種更聰明的方法來實現這一點,它允許公鑰與其對應的私鑰分開衍生,使得可以比公鑰更安全地儲存私鑰。
 
公共子金鑰衍生
在 公鑰 中,我們學習了如何使用橢圓曲線密碼學(ECC)從私鑰創建公鑰。儘管橢圓曲線上的操作不直觀,但它們類似於常規算術中使用的加法、減法和乘法操作。換句話說,可以對公鑰進行加減,或對其進行乘法。考慮我們在 公鑰 中使用的操作,用於使用生成點(G)從私鑰(k)生成公鑰(K):
可以透過簡單地在方程式的兩側新增相同的值來創建衍生金鑰對,稱為子金鑰對:
| 在本書的方程式中,我們使用單個等號來表示諸如 K = k × G 這樣的操作,其中計算變數的值。我們使用雙等號來顯示方程式的兩側是等價的,或者如果兩側不等價,操作應該返回 false(而不是 true)。 | 
這樣做的一個有趣結果是,可以完全使用公開資訊對公鑰加 123。例如,Alice 生成公鑰 K 並將其交給 Bob。Bob 不知道私鑰,但他知道全域常數 G,因此他可以將任何值新增到公鑰以產生衍生的公共子金鑰。如果他然後告訴 Alice 他新增到公鑰的值,她可以將相同的值新增到私鑰,產生與 Bob 創建的公共子金鑰對應的衍生私有子金鑰。
換句話說,即使您對父私鑰一無所知,也可以創建子公鑰。新增到公鑰的值稱為金鑰調整(key tweak)。如果使用確定性演算法來生成金鑰調整,那麼不知道私鑰的人可以從單個公共父金鑰創建基本上無限的公共子金鑰序列。然後,控制私有父金鑰的人可以使用相同的金鑰調整來創建所有對應的私有子金鑰。
這種技術通常用於將錢包應用程式前端(不需要私鑰)與簽署操作(需要私鑰)分開。例如,Alice 的前端將她的公鑰分發給想要支付她的人。稍後,當她想花費收到的錢時,她可以將她使用的金鑰調整提供給硬體簽署設備(有時令人困惑地稱為硬體錢包),該設備安全地儲存她的原始私鑰。硬體簽署者使用調整來衍生必要的子私鑰並使用它們來簽署交易,將簽署的交易返回給不太安全的前端以廣播到比特幣網路。
公共子金鑰衍生可以產生類似於之前看到的 確定性金鑰生成:從種子衍生的確定性金鑰序列用於錢包資料庫 的線性金鑰序列,但現代錢包應用程式使用另一個技巧來提供金鑰樹而不是單個序列,如下一節所述。
階層確定性(HD)金鑰生成(BIP32)
據我們所知,每個現代比特幣錢包預設都使用階層確定性(HD)金鑰生成。此標準在 BIP32 中定義,使用確定性金鑰生成和可選的公共子金鑰衍生,採用產生金鑰樹的演算法。在這棵樹中,任何金鑰都可以是子金鑰序列的父金鑰,並且這些子金鑰中的任何一個都可以是另一個子金鑰序列(原始金鑰的孫子金鑰)的父金鑰。樹的深度沒有任意限制。此樹結構如 HD 錢包:從單個種子生成的金鑰樹 所示。
 
樹結構可用於表達額外的組織含義,例如當特定的子金鑰分支用於接收傳入支付,而不同的分支用於接收傳出支付的找零時。金鑰的分支也可以在公司環境中使用,將不同的分支分配給部門、子公司、特定功能或會計類別。
我們將在 從種子創建 HD 錢包 中詳細探討 HD 錢包。
種子和恢復碼
HD 錢包是一種非常強大的機制,用於管理所有從單個種子衍生的許多金鑰。如果您的錢包資料庫曾經損壞或丟失,您可以使用原始種子重新生成錢包的所有私鑰。但是,如果其他人獲得了您的種子,他們也可以生成所有私鑰,允許他們從單簽錢包中竊取所有比特幣,並降低多重簽章錢包中比特幣的安全性。在本節中,我們將研究幾種恢復碼(recovery codes),旨在使備份更容易、更安全。
儘管種子是大的隨機數,通常是 128 到 256 位元,但大多數恢復碼使用人類語言單詞。使用單詞的很大一部分動機是使恢復碼易於記憶。例如,考慮使用十六進位和單詞編碼的 以十六進位和英文單詞編碼的種子 中的恢復碼。
十六進位編碼: 0C1E 24E5 9177 79D2 97E1 4D45 F14E 1A1A 單詞編碼: army van defense carry jealous true garbage claim echo media make crunch
在某些情況下,記住恢復碼可能是一個強大的功能,例如當您無法運輸實體物品(如寫在紙上的恢復碼)而不被可能竊取您比特幣的外部方沒收或檢查時。然而,大多數時候,僅依賴記憶是危險的:
- 
如果您忘記了恢復碼並失去了對原始錢包資料庫的存取,您的比特幣將永遠對您失去。 
- 
如果您死亡或遭受嚴重傷害,並且您的繼承人無法存取您的原始錢包資料庫,他們將無法繼承您的比特幣。 
- 
如果有人認為您記住了一個恢復碼,該碼將使他們能夠存取比特幣,他們可能會試圖強迫您洩露該碼。截至撰寫本文時,比特幣貢獻者 Jameson Lopp 已經 記錄 了超過 100 起針對比特幣和其他數位資產疑似擁有者的實體攻擊,包括至少三起死亡案例和許多人被折磨、扣為人質或其家人受到威脅的場合。 
| 即使您使用一種專為易於記憶而設計的恢復碼類型,我們也非常強烈建議您考慮將其寫下來。 | 
截至撰寫本文時,廣泛使用的幾種不同類型的恢復碼:
- BIP39
- 
過去十年中最流行的生成恢復碼的方法,BIP39 涉及生成隨機位元組序列,向其新增檢查和,並將資料編碼為一系列 12 到 24 個單詞(可能本地化為使用者的母語)。單詞(加上可選的密碼短語)透過金鑰延展函數(key-stretching function)運行,輸出用作種子。BIP39 恢復碼有幾個缺點,後來的方案試圖解決這些缺點。 
- Electrum v2
- 
在 Electrum 錢包(2.0 版及以上)中使用,這種基於單詞的恢復碼比 BIP39 有幾個優勢。它不依賴於必須由每個相容程式的每個版本實作的全域單詞清單,此外,其恢復碼包含版本號,可提高可靠性和效率。像 BIP39 一樣,它支援可選的密碼短語(Electrum 稱之為種子擴展(seed extension))並使用相同的金鑰延展函數。 
- Aezeed
- 
在 LND 錢包中使用,這是另一種基於單詞的恢復碼,提供了對 BIP39 的改進。它包含兩個版本號:一個是內部的,消除了升級錢包應用程式的幾個問題(如 Electrum v2 的版本號);另一個版本號是外部的,可以遞增以更改恢復碼的底層密碼學屬性。它還在恢復碼中包含錢包生日(wallet birthday),即使用者創建錢包資料庫日期的參考。這允許恢復過程找到與錢包關聯的所有資金,而無需掃描整個區塊鏈,這對注重隱私的輕量級客戶端特別有用。它支援更改密碼短語或更改恢復碼的其他方面,而無需將資金轉移到新種子——使用者只需備份新的恢復碼。與 Electrum v2 相比的一個缺點是,像 BIP39 一樣,它依賴於備份和恢復軟體支援相同的單詞清單。 
- Muun
- 
在 Muun 錢包中使用,該錢包預設要求支出交易由多個金鑰簽署,這是一種非單詞碼,必須伴隨額外資訊(Muun 目前在 PDF 中提供)。此恢復碼與種子無關,而是用於解密 PDF 中包含的私鑰。儘管與 BIP39、Electrum v2 和 Aezeed 恢復碼相比,這很笨拙,但它為新錢包中越來越常見的新技術和標準提供支援,例如閃電網路(LN)支援、輸出腳本描述符和 miniscript。 
- SLIP39
- 
作為 BIP39 的後繼者,與一些相同的作者,SLIP39 允許使用可以儲存在不同地方(或由不同的人)的多個恢復碼來分發單個種子。創建恢復碼時,您可以指定需要多少個來恢復種子。例如,您創建五個恢復碼,但只需要其中三個來恢復種子。SLIP39 提供對可選密碼短語的支援,依賴於全域單詞清單,並且不直接提供版本控制。 
| 在撰寫本書期間,提出了一個與 SLIP39 具有相似性的分發恢復碼的新系統。Codex32 允許僅使用列印的說明、剪刀、精密刀、黃銅緊固件和筆來創建和驗證恢復碼——加上隱私和幾個小時的空閒時間。或者,那些信任電腦的人可以使用數位設備上的軟體即時創建恢復碼。您可以創建最多 31 個恢復碼以儲存在不同的地方,指定需要多少個才能恢復種子。作為一個新提案,關於 Codex32 的細節可能會在本書出版前發生重大變化,因此我們鼓勵任何對分散式恢復碼感興趣的讀者調查其 當前狀態。 | 
備份非金鑰資料
錢包資料庫中最重要的資料是其私鑰。如果您失去對私鑰的存取,您將失去花費比特幣的能力。確定性金鑰衍生和恢復碼為備份和恢復您的金鑰及其控制的比特幣提供了一個相當穩健的解決方案。然而,重要的是要考慮到許多錢包資料庫儲存的不僅僅是金鑰——它們還儲存使用者提供的關於他們發送或接收的每筆交易的資訊。
例如,當 Bob 創建一個新地址作為向 Alice 發送發票的一部分時,他向他生成的地址新增一個_標籤(label)_,以便他可以將她的付款與他收到的其他付款區分開來。當 Alice 支付 Bob 的地址時,她出於同樣的原因將交易標記為支付給 Bob。一些錢包還向交易新增其他有用的資訊,例如當前匯率,這在某些司法管轄區計算稅款時可能很有用。這些標籤完全儲存在他們自己的錢包中——不與網路共享——保護他們的隱私並將不必要的個人資料排除在區塊鏈之外。例如,請參見 [alice_tx_labels]。
| 日期 | 標籤 | BTC | 
|---|---|---|
| 2023-01-01 | 從 Joe 購買比特幣 | +0.00100 | 
| 2023-01-02 | 為播客支付給 Bob | −0.00075 | 
然而,由於地址和交易標籤僅儲存在每個使用者的錢包資料庫中,並且由於它們不是確定性的,因此無法僅使用恢復碼來恢復它們。如果唯一的恢復是基於種子的,那麼使用者將看到的只是大致的交易時間和比特幣金額的清單。這可能會使您很難弄清楚您過去是如何使用您的錢的。想像一下,查看一年前的銀行或信用卡對帳單,上面列出了每筆交易的日期和金額,但「描述」欄位為空白。
錢包應該為使用者提供一種方便的方式來備份標籤資料。這似乎是顯而易見的,但有許多廣泛使用的錢包應用程式,它們使創建和使用恢復碼變得容易,但不提供任何方式來備份或恢復標籤資料。
此外,錢包應用程式提供標準格式來匯出標籤可能很有用,以便它們可以在其他應用程式(例如,會計軟體)中使用。BIP329 中提出了該格式的標準。
實作基本比特幣支援之外的其他協定的錢包應用程式可能還需要或想要儲存其他資料。例如,截至 2023 年,越來越多的應用程式增加了對透過閃電網路(LN)發送和接收交易的支援。儘管 LN 協定提供了一種在資料丟失的情況下恢復資金的方法,稱為_靜態通道備份(static channel backups)_,但它無法保證結果。如果您的錢包連接的節點意識到您已經丟失資料,它可能能夠從您那裡竊取比特幣。如果它在您丟失資料庫的同時丟失了它的錢包資料庫,而你們兩個都沒有足夠的備份,你們倆都會失去資金。
再次強調,這意味著使用者和錢包應用程式需要做的不僅僅是備份恢復碼。
一些錢包應用程式實作的一個解決方案是頻繁且自動地創建其錢包資料庫的完整備份,由從其種子衍生的金鑰之一進行加密。比特幣金鑰必須是不可猜測的,而現代加密演算法被認為是非常安全的,因此除了可以生成種子的人之外,沒有人應該能夠開啟加密的備份。這使得將備份儲存在不受信任的電腦上(例如雲端託管服務甚至隨機網路對等節點)是安全的。
稍後,如果原始錢包資料庫丟失,使用者可以將他們的恢復碼輸入到錢包應用程式中以恢復他們的種子。然後,應用程式可以檢索最新的備份檔案,重新生成加密金鑰,解密備份,並恢復所有使用者的標籤和額外的協定資料。
備份金鑰衍生路徑
在BIP32 金鑰樹中,大約有 40 億個第一層金鑰;這些金鑰中的每一個都可以有自己的 40 億個子金鑰,這些子金鑰中的每一個都可能有 40 億個自己的子金鑰,依此類推。錢包應用程式不可能生成 BIP32 樹中每個可能金鑰的哪怕一小部分,這意味著從資料丟失中恢復需要的不僅僅是恢復碼、獲取種子的演算法(例如,BIP39)和確定性金鑰衍生演算法(例如,BIP32)——它還需要知道您的錢包應用程式在金鑰樹中使用的路徑來生成它分發的特定金鑰。
已經採用了兩種解決方案來解決此問題。第一種是使用標準路徑。每次與錢包應用程式可能想要生成的地址相關的變化時,有人創建一個 BIP 來定義要使用的金鑰衍生路徑。例如,BIP44 將 m/44'/0'/0' 定義為用於 P2PKH 腳本(傳統地址)中金鑰的路徑。實作此標準的錢包應用程式在首次啟動時和從恢復碼恢復後都使用該路徑中的金鑰。我們稱此解決方案為_隱式路徑(implicit paths)_。BIP 定義的幾個流行的隱式路徑如 [bip_implicit_paths] 所示
| 標準 | 腳本 | BIP32 路徑 | 
|---|---|---|
| BIP44 | P2PKH | 
 | 
| BIP49 | 巢狀 P2WPKH | 
 | 
| BIP84 | P2WPKH | 
 | 
| BIP86 | P2TR 單金鑰 | 
 | 
第二種解決方案是將路徑資訊與恢復碼一起備份,明確說明哪個路徑與哪個腳本一起使用。我們稱此為_顯式路徑(explicit paths)_。
隱式路徑的優點是使用者不需要記錄他們使用的路徑。如果使用者將其恢復碼輸入到他們以前使用的相同錢包應用程式中,相同版本或更高版本,它將自動為它以前使用的相同路徑重新生成金鑰。
隱式腳本的缺點是它們的不靈活性。當輸入恢復碼時,錢包應用程式必須為它支援的每個路徑生成金鑰,並且必須掃描區塊鏈以查找涉及這些金鑰的交易,否則它可能找不到使用者的所有交易。如果使用者僅嘗試了其中幾個功能,則在支援許多功能的錢包中,每個功能都有自己的路徑,這是浪費的。
對於不包含版本號的隱式路徑恢復碼,例如 BIP39 和 SLIP39,錢包應用程式的新版本如果放棄對較舊路徑的支援,則無法在恢復過程中警告使用者可能找不到他們的部分資金。如果使用者將其恢復碼輸入到較舊的軟體中,也會發生同樣的問題:它不會找到使用者可能已經收到資金的較新路徑。包含版本資訊的恢復碼,例如 Electrum v2 和 Aezeed,可以檢測到使用者正在輸入較舊或較新的恢復碼,並將他們引導到適當的資源。
隱式路徑的最後一個結果是它們只能包含通用的資訊(例如標準化路徑)或從種子衍生的資訊(例如金鑰)。對特定使用者特定的重要不確定性資訊無法使用恢復碼恢復。例如,Alice、Bob 和 Carol 收到資金,這些資金只能使用他們三人中兩人的簽章來花費。儘管 Alice 只需要 Bob 或 Carol 的簽章就可以花費,但她需要他們倆的公鑰才能在區塊鏈上找到他們的共同資金。這意味著他們每個人都必須備份他們三個人的公鑰。隨著多重簽章和其他進階腳本在比特幣上變得更加常見,隱式路徑的不靈活性變得更加顯著。
顯式路徑的優點是它們可以準確描述應該使用哪些金鑰與哪些腳本一起使用。無需支援過時的腳本,沒有向後或向前相容性的問題,任何額外的資訊(例如其他使用者的公鑰)都可以直接包含在內。它們的缺點是它們要求使用者備份恢復碼以外的額外資訊。額外的資訊通常不會損害使用者的安全性,因此不需要像恢復碼那樣多的保護,儘管它可以降低他們的隱私並需要一些保護。
截至撰寫本文時,幾乎所有使用顯式路徑的錢包應用程式都使用 BIP 380、381、382、383、384、385、386 和 389 中指定的_輸出腳本描述符(output script descriptors)標準(簡稱_描述符(descriptors))。描述符描述腳本以及要與其一起使用的金鑰(或金鑰路徑)。[sample_descriptors] 中顯示了一些範例描述符。
| 描述符 | 解釋 | 
|---|---|
| 
 | 提供的公鑰的 P2PKH 腳本 | 
| 
 | 需要與這兩個金鑰對應的兩個簽章的 P2SH 多重簽章 | 
| 
 | 路徑  | 
長期以來,僅為單一簽章腳本設計的錢包應用程式一直趨向於使用隱式路徑。為多重簽章或其他進階腳本設計的錢包應用程式越來越多地採用對使用描述符的顯式路徑的支援。同時執行這兩項操作的應用程式通常會符合隱式路徑的標準,並且還會提供描述符。
詳細介紹錢包技術堆疊
現代錢包的開發者可以從各種不同的技術中進行選擇,以幫助使用者創建和使用備份——並且每年都會出現新的解決方案。我們不會詳細介紹本章前面描述的每個選項,而是將本章的其餘部分集中在我們認為截至 2023 年初在錢包中最廣泛使用的技術堆疊上:
- 
BIP39 恢復碼 
- 
BIP32 HD 金鑰衍生 
- 
BIP44 風格的隱式路徑 
所有這些標準都是從 2014 年或更早開始使用的,您將毫無問題地找到使用它們的其他資源。然而,如果您感到大膽,我們確實鼓勵您調查可能提供額外功能或安全性的更現代的標準。
BIP39 恢復碼
BIP39恢復碼是單詞序列,用於表示(編碼)用作種子以衍生確定性錢包的隨機數。單詞序列足以重新創建種子,並從那裡重新創建所有衍生的金鑰。實作帶有 BIP39 恢復碼的確定性錢包的錢包應用程式在首次創建錢包時將向使用者顯示 12 到 24 個單詞的序列。該單詞序列是錢包備份,可用於在相同或任何相容的錢包應用程式中恢復和重新創建所有金鑰。恢復碼使使用者更容易備份,因為它們易於閱讀和正確抄寫。
| 恢復碼經常與「腦錢包(brainwallets)」混淆。它們不一樣。主要區別在於腦錢包由使用者選擇的單詞組成,而恢復碼是由錢包隨機創建並呈現給使用者的。這個重要的區別使得恢復碼更加安全,因為人類是非常糟糕的隨機性來源。 | 
請注意,BIP39 是恢復碼標準的一種實作。BIP39 由 Trezor 硬體錢包背後的公司提出,並與許多其他錢包應用程式相容,儘管當然不是所有應用程式。
生成恢復碼
恢復碼由錢包應用程式使用 BIP39 中定義的標準化過程自動生成。錢包從熵源開始,新增檢查和,然後將資料對映到單詞清單:
- 
創建 128 到 256 位元的隨機序列(熵)。 
- 
透過取其 SHA256 雜湊的前(熵長度/32)位元來創建隨機序列的檢查和。 
- 
將檢查和新增到隨機序列的末尾。 
- 
將結果分割為 11 位元長度的段。 
- 
將每個 11 位元值對映到預定義的 2,048 個單詞的字典中的一個單詞。 
- 
恢復碼是單詞序列。 
生成熵並編碼為恢復碼。 顯示了熵如何用於生成 BIP39 恢復碼。
 
[table_4-5] 顯示了熵資料的大小與恢復碼長度之間的關係(以單詞為單位)。
| 熵(位元) | 檢查和(位元) | 熵 + 檢查和(位元) | 恢復碼單詞數 | 
|---|---|---|---|
| 128 | 4 | 132 | 12 | 
| 160 | 5 | 165 | 15 | 
| 192 | 6 | 198 | 18 | 
| 224 | 7 | 231 | 21 | 
| 256 | 8 | 264 | 24 | 
從恢復碼到種子
恢復碼代表長度為 128 到 256 位元的熵。然後,透過使用 金鑰延展函數 PBKDF2,將熵用於衍生更長的(512 位元)種子。然後,產生的種子用於構建確定性錢包並衍生其金鑰。
金鑰延展函數接受兩個參數:熵和鹽(salt)。鹽在金鑰延展函數中的目的是使構建查找表以進行暴力攻擊變得困難。在 BIP39 標準中,鹽還有另一個目的——它允許引入密碼短語,作為保護種子的額外安全因素,我們將在 BIP39 中的可選密碼短語 中更詳細地描述。
| 金鑰延展函數及其 2,048 輪雜湊使使用軟體對恢復碼進行暴力攻擊變得稍微困難一些。專用硬體不會受到顯著影響。對於需要猜測使用者整個恢復碼的攻擊者來說,碼的長度(最少 128 位元)提供了足夠的安全性。但對於攻擊者可能了解使用者部分碼的情況,金鑰延展透過減慢攻擊者檢查不同恢復碼組合的速度來增加一些安全性。即使在近十年前首次發佈時,按照現代標準,BIP39 的參數也被認為很弱,儘管這可能是為了與具有低功率 CPU 的硬體簽署設備相容而設計的結果。BIP39 的一些替代方案使用更強的金鑰延展參數,例如 Aezeed 使用更複雜的 Scrypt 演算法進行 32,768 輪雜湊,儘管它們可能在硬體簽署設備上執行起來不太方便。 | 
步驟 7 到 9 中描述的過程從 生成恢復碼 中先前描述的過程繼續:
- PBKDF2 金鑰延展函數的第一個參數是步驟 6 產生的熵。
- PBKDF2 金鑰延展函數的第二個參數是鹽。鹽由字串常數「mnemonic」與可選的使用者提供的密碼短語字串連接組成。
- PBKDF2 使用 HMAC-SHA512 演算法對恢復碼和鹽參數進行 2,048 輪雜湊延展,產生 512 位元值作為其最終輸出。該 512 位元值就是種子。
從恢復碼到種子。 顯示了如何使用恢復碼生成種子。
 
表格 [bip39_128_no_pass]、[bip39_128_w_pass] 和 [bip39_256_no_pass] 顯示了一些恢復碼及其產生的種子範例。
| 熵輸入(128 位元) | 
 | 
| 恢復碼(12 個單詞) | 
 | 
| 密碼短語 | (無) | 
| 種子(512 位元) | 
 | 
| 熵輸入(128 位元) | 
 | 
| 恢復碼(12 個單詞) | 
 | 
| 密碼短語 | SuperDuperSecret | 
| 種子(512 位元) | 
 | 
| 熵輸入(256 位元) | 
 | 
| 恢復碼(24 個單詞) | 
 | 
| 密碼短語 | (無) | 
| 種子(512 位元) | 
 | 
BIP39 中的可選密碼短語
BIP39標準允許在種子衍生中使用可選的密碼短語。如果不使用密碼短語,恢復碼將使用由常數字串 "mnemonic" 組成的鹽進行延展,從任何給定的恢復碼產生特定的 512 位元種子。如果使用密碼短語,延展函數將從同一恢復碼產生_不同的_種子。實際上,給定單個恢復碼,每個可能的密碼短語都會導致不同的種子。基本上,沒有「錯誤」的密碼短語。所有密碼短語都是有效的,它們都會導致不同的種子,形成一組可能的未初始化錢包。可能的錢包集如此之大(2512),以至於不可能暴力破解或意外猜測正在使用的錢包。
| BIP39 中沒有「錯誤」的密碼短語。每個密碼短語都會導致某個錢包,除非以前使用過,否則該錢包將是空的。 | 
可選的密碼短語創建了兩個重要功能:
- 
第二因素(記住的東西),使恢復碼本身無用,保護恢復碼免受偶然盜竊者的洩露。為了防止技術嫻熟的盜竊者,您需要使用非常強的密碼短語。 
- 
一種合理推諉或「脅迫錢包」的形式,其中選擇的密碼短語導致包含少量資金的錢包,用於分散攻擊者對包含大部分資金的「真實」錢包的注意力。 
重要的是要注意,使用密碼短語也會帶來丟失的風險:
- 
如果錢包擁有者喪失行為能力或死亡,並且沒有其他人知道密碼短語,則種子無用,錢包中儲存的所有資金將永遠丟失。 
- 
相反,如果擁有者將密碼短語備份在與種子相同的地方,則會破壞第二因素的目的。 
雖然密碼短語非常有用,但只應在仔細計劃備份和恢復過程的情況下使用,考慮到在擁有者去世後倖存下來的可能性,並允許他或她的家人恢復加密貨幣遺產。
從種子創建 HD 錢包
HD 錢包從單個_根種子(root seed)_創建,該根種子是 128、256 或 512 位元的隨機數。最常見的是,此種子由恢復碼生成或從恢復碼解密,如上一節所述。
HD 錢包中的每個金鑰都是從該根種子確定性衍生的,這使得可以從該種子在任何相容的 HD 錢包中重新創建整個 HD 錢包。這使得備份、恢復、匯出和匯入包含數千甚至數百萬個金鑰的 HD 錢包變得容易,只需簡單地轉移從中衍生根種子的恢復碼。為 HD 錢包創建主金鑰和主鏈碼的過程如 從根種子創建主金鑰和鏈碼。 所示。
 
根種子輸入 HMAC-SHA512 演算法,產生的雜湊用於創建_主私鑰(master private key)(_m)和_主鏈碼(master chain code)(_c)。
然後,主私鑰(m)使用我們在 公鑰 中看到的正常橢圓曲線乘法過程 m × G 生成相應的主公鑰(M)。
主鏈碼(c)用於在從父金鑰創建子金鑰的函數中引入熵,我們將在下一節中看到。
私有子金鑰衍生
HD 錢包使用_子金鑰衍生(child key derivation)_(CKD)函數從父金鑰衍生子金鑰。
子金鑰衍生函數基於單向雜湊函數,該函數 結合:
- 
父私鑰或公鑰(未壓縮金鑰) 
- 
稱為鏈碼的種子(256 位元) 
- 
索引號(32 位元) 
鏈碼用於在過程中引入確定性隨機資料,以便僅知道索引和子金鑰不足以衍生其他子金鑰。知道子金鑰不可能找到其兄弟金鑰,除非您還擁有鏈碼。初始鏈碼種子(在樹的根部)由種子製成,而後續的子鏈碼從每個父鏈碼衍生。
這三個項目(父金鑰、鏈碼和索引)組合並雜湊以生成子金鑰,如下所示。
父公鑰、鏈碼和索引號組合,並使用 HMAC-SHA512 演算法雜湊,以產生 512 位元雜湊。此 512 位元雜湊被分成兩個 256 位元的半部分。雜湊輸出的右半部分 256 位元成為子金鑰的鏈碼。雜湊的左半部分 256 位元新增到父私鑰以產生子私鑰。在 擴展父私鑰以創建子私鑰。 中,我們看到索引設定為 0 以產生父金鑰的「零」(按索引的第一個)子金鑰的示例。
 
更改索引允許我們擴展父金鑰並創建序列中的其他子金鑰(例如,子金鑰 0、子金鑰 1、子金鑰 2 等)。每個父金鑰可以有 2,147,483,647(231)個子金鑰(231 是可用整個 232 範圍的一半,因為另一半保留用於我們將在本章後面討論的特殊類型的衍生)。
在樹中向下一級重複該過程,每個子金鑰依次可以成為父金鑰並創建自己的子金鑰,無限世代。
使用衍生的子金鑰
子私鑰與非確定性(隨機)金鑰無法區分。因為衍生函數是單向函數,所以子金鑰不能用於找到父金鑰。子金鑰也不能用於找到任何兄弟金鑰。如果您有第 n 個子金鑰,您無法找到其兄弟金鑰,例如第 n-1 個子金鑰或第 n+1 個子金鑰,或作為序列一部分的任何其他子金鑰。只有父金鑰和鏈碼才能衍生所有子金鑰。如果沒有子鏈碼,子金鑰也不能用於衍生任何孫金鑰。您需要子私鑰和子鏈碼才能開始新分支並衍生孫金鑰。
那麼子私鑰本身可以用於什麼?它可以用於製作公鑰和比特幣地址。然後,它可以用於簽署交易以花費支付給該地址的任何東西。
| 子私鑰、相應的公鑰和比特幣地址都與隨機創建的金鑰和地址無法區分。它們是序列一部分的事實在創建它們的 HD 錢包函數之外是不可見的。一旦創建,它們的操作完全像「正常」金鑰。 | 
擴展金鑰
如我們之前所見,金鑰衍生函數可用於基於三個輸入在樹的任何層級創建子金鑰:金鑰、鏈碼和所需子金鑰的索引。兩個基本成分是金鑰和鏈碼,結合起來稱為_擴展金鑰(extended key)_。術語「擴展金鑰」也可以被認為是「可擴展金鑰」,因為這樣的金鑰可用於衍生子金鑰。
擴展金鑰簡單地儲存和表示為金鑰和鏈碼的連接。有兩種類型的擴展金鑰。擴展私鑰是私鑰和鏈碼的組合,可用於衍生子私鑰(以及從它們衍生子公鑰)。擴展公鑰是公鑰和鏈碼,可用於創建子公鑰(僅公鑰),如 公鑰 中所述。
將擴展金鑰視為 HD 錢包樹結構中分支的根。使用分支的根,您可以衍生分支的其餘部分。擴展私鑰可以創建完整的分支,而擴展公鑰_只能_創建公鑰分支。
擴展金鑰使用 base58check 編碼,以便在不同的 BIP32 相容錢包之間輕鬆匯出和匯入。擴展金鑰的 base58check 編碼使用特殊的版本號,當在 base58 字元中編碼時會產生前綴「xprv」和「xpub」,以使它們易於識別。因為擴展金鑰包含的位元組數遠多於常規地址,所以它也比我們之前看到的其他 base58check 編碼字串長得多。
這是一個擴展_私鑰_的範例,以 base58check 編碼:
xprv9tyUQV64JT5qs3RSTJkXCWKMyUgoQp7F3hA1xzG6ZGu6u6Q9VMNjGr67Lctvy5P8oyaYAL9CA WrUE9i6GoNMKUga5biW6Hx4tws2six3b9c
這是相應的擴展_公鑰_,以 base58check 編碼:
xpub67xpozcx8pe95XVuZLHXZeG6XWXHpGq6Qv5cmNfi7cS5mtjJ2tgypeQbBs2UAR6KECeeMVKZBP LrtJunSDMstweyLXhRgPxdp14sk9tJPW9
公共子金鑰衍生
如前所述,HD 錢包的一個非常有用的特性是能夠從公共父金鑰衍生公共子金鑰_而無需_擁有私鑰。這為我們提供了兩種衍生子公鑰的方法:從子私鑰或直接從父公鑰。
因此,可以使用擴展公鑰來衍生 HD 錢包結構該分支中的所有_公鑰_(僅公鑰)。
這種捷徑可用於創建僅公鑰的部署,其中伺服器或應用程式擁有擴展公鑰的副本而完全沒有私鑰。這種類型的部署可以產生無限數量的公鑰和比特幣地址,但無法花費發送到這些地址的任何資金。同時,在另一個更安全的伺服器上,擴展私鑰可以衍生所有相應的私鑰以簽署交易並花費資金。
此解決方案的一個常見應用是在為電子商務應用程式提供服務的 Web 伺服器上安裝擴展公鑰。Web 伺服器可以使用公鑰衍生函數為每筆交易(例如,客戶購物車)創建新的比特幣地址。Web 伺服器將沒有任何容易被盜的私鑰。如果沒有 HD 錢包,唯一的方法是在單獨的安全伺服器上生成數千個比特幣地址,然後將它們預先載入到電子商務伺服器上。這種方法很笨拙並且需要持續維護,以確保電子商務伺服器不會「用完」金鑰。
此解決方案的另一個常見應用是冷儲存或硬體簽署設備。在這種情況下,擴展私鑰可以儲存在紙錢包或硬體設備上,而擴展公鑰可以保持線上。使用者可以隨意創建「接收」地址,而私鑰則安全地離線儲存。要花費資金,使用者可以在離線軟體錢包應用程式或硬體簽署設備上使用擴展私鑰。擴展父公鑰以創建子公鑰。 說明了擴展父公鑰以衍生子公鑰的機制。
 
在網路商店中使用擴展公鑰
讓我們透過觀察 Gabriel 的網路商店來看看 HD 錢包是如何使用的。
Gabriel 最初將他的網路商店作為愛好建立,基於簡單的託管 WordPress 頁面。他的商店非常基礎,只有幾個頁面和一個帶有單個比特幣地址的訂單表格。
Gabriel 使用他常規錢包生成的第一個比特幣地址作為他商店的主要比特幣地址。客戶將使用表格提交訂單並向 Gabriel 發佈的比特幣地址發送付款,觸發包含 Gabriel 處理訂單詳細資訊的電子郵件。每週只有幾個訂單,這個系統運作得很好,儘管它削弱了 Gabriel、他的客戶以及他支付的人的隱私。
然而,這家小網路商店變得相當成功,並吸引了當地社群的許多訂單。很快,Gabriel 不堪重負。由於所有訂單都支付給相同的地址,因此很難正確匹配訂單和交易,特別是當多個相同金額的訂單幾乎同時到達時。
典型比特幣交易的接收者選擇的唯一元資料是金額和支付地址。沒有可用於保存唯一標識符發票號碼的主題或訊息欄位。
Gabriel 的 HD 錢包透過能夠在不知道私鑰的情況下衍生公共子金鑰提供了更好的解決方案。Gabriel 可以在他的網站上載入擴展公鑰(xpub),可用於為每個客戶訂單衍生唯一地址。唯一地址立即改善了隱私,還為每個訂單提供了唯一標識符,可用於追蹤哪些發票已付款。
使用 HD 錢包允許 Gabriel 從他的個人錢包應用程式中花費資金,但網站上載入的 xpub 只能生成地址和接收資金。HD 錢包的這個功能是一個很棒的安全功能。Gabriel 的網站不包含任何私鑰,因此對它的任何駭客攻擊只能竊取 Gabriel 將來會收到的資金,而不是他過去收到的任何資金。
要從他的 Trezor 硬體簽署設備匯出 xpub,Gabriel 使用基於 Web 的 Trezor 錢包應用程式。必須插入 Trezor 設備才能匯出公鑰。請注意,大多數硬體簽署設備永遠不會匯出私鑰——這些始終保留在設備上。
Gabriel 將 xpub 複製到他的網路商店的比特幣支付處理軟體中,例如廣泛使用的開源 BTCPay Server。
強化子金鑰衍生
從xpub 衍生公鑰分支的能力非常有用,但它帶有潛在的風險。存取 xpub 不會授予對子私鑰的存取權限。然而,由於 xpub 包含鏈碼,如果子私鑰已知或以某種方式洩漏,它可以與鏈碼一起使用來衍生所有其他子私鑰。單個洩漏的子私鑰與父鏈碼一起揭示所有子金鑰的所有私鑰。更糟糕的是,子私鑰與父鏈碼一起可用於推斷父私鑰。
為了對抗這種風險,HD 錢包提供了一種稱為_強化衍生(hardened derivation)_的替代衍生函數,它打破了父公鑰和子鏈碼之間的關係。強化衍生函數使用父私鑰來衍生子鏈碼,而不是父公鑰。這在父/子序列中創建了一個「防火牆」,帶有不能用於洩露父或兄弟私鑰的鏈碼。強化衍生函數看起來幾乎與正常子私鑰衍生相同,只是父私鑰用作雜湊函數的輸入,而不是父公鑰,如 子金鑰的強化衍生;省略父公鑰。 中的圖表所示。
 
當使用強化私有衍生函數時,產生的子私鑰和鏈碼與正常衍生函數產生的結果完全不同。產生的金鑰「分支」可用於產生擴展公鑰,這些公鑰不易受攻擊,因為它們包含的鏈碼不能被利用來揭示其兄弟或父金鑰的任何私鑰。因此,強化衍生用於在使用擴展公鑰的級別之上的樹中創建「間隙」。
簡單來說,如果您想使用 xpub 的便利性來衍生公鑰分支,而不將自己暴露於洩漏鏈碼的風險中,您應該從強化父金鑰而不是正常父金鑰衍生它。作為最佳實踐,主金鑰的第 1 層子金鑰始終透過強化衍生來衍生,以防止主金鑰的洩露。
正常和強化衍生的索引號
衍生函數中使用的索引號是 32 位元整數。為了輕鬆區分透過正常衍生函數創建的金鑰與透過強化衍生衍生的金鑰,此索引號被分為兩個範圍。0 到 231 – 1(0x0 到 0x7FFFFFFF)之間的索引號_僅_用於正常衍生。231 到 232 – 1(0x80000000 到 0xFFFFFFFF)之間的索引號_僅_用於強化衍生。因此,如果索引號小於 231,子金鑰是正常的,而如果索引號等於或大於 231,子金鑰是強化的。
為了使索引號更易於閱讀和顯示,強化子金鑰的索引號從零開始顯示,但帶有一個撇號符號。因此,第一個正常子金鑰顯示為 0,而第一個強化子金鑰(索引 0x80000000)顯示為 0'。在一個序列中,第二個強化金鑰將具有索引 0x80000001,並將顯示為 1',依此類推。當您看到 HD 錢包索引 i' 時,這意味著 231+i。在常規 ASCII 文字中,撇號符號被替換為單引號或字母 h。對於輸出腳本描述符等情況,其中文字可能用於 shell 或其他單引號具有特殊含義的上下文中,建議使用字母h。
HD 錢包金鑰標識符(路徑)
HD錢包中的金鑰使用「路徑」命名約定進行標識,樹的每個層級由斜線(/)字元分隔(請參見 [table_4-8])。從主私鑰衍生的私鑰以「m」開頭。從主公鑰衍生的公鑰以「M」開頭。因此,主私鑰的第一個子私鑰是 m/0。第一個子公鑰是 M/0。第一個子金鑰的第二個孫金鑰是 m/0/1,依此類推。
金鑰的「祖先」從右到左讀取,直到您到達從中衍生它的主金鑰。例如,標識符 m/x/y/z 描述的金鑰是金鑰 m/x/y 的第 z 個子金鑰,金鑰 m/x/y 是金鑰 m/x 的第 y 個子金鑰,金鑰 m/x 是 m 的第 x 個子金鑰。
| HD 路徑 | 描述的金鑰 | 
|---|---|
| m/0 | 主私鑰(m)的第一個(0)子私鑰 | 
| m/0/0 | 第一個子金鑰(m/0)的第一個孫私鑰 | 
| m/0'/0 | 第一個強化子金鑰(m/0')的第一個正常孫私鑰 | 
| m/1/0 | 第二個子金鑰(m/1)的第一個孫私鑰 | 
| M/23/17/0/0 | 第 24 個子金鑰的第 18 個孫金鑰的第一個曾孫金鑰的第一個曾曾孫公鑰 | 
導航 HD 錢包樹結構
HD錢包樹結構提供了巨大的靈活性。每個父擴展金鑰可以有 40 億個子金鑰:20 億個正常子金鑰和 20 億個強化子金鑰。這些子金鑰中的每一個都可以有另外 40 億個子金鑰,依此類推。樹可以隨心所欲地深,具有無限數量的世代。然而,有了所有這些靈活性,導航這個無限樹變得相當困難。在實作之間轉移 HD 錢包特別困難,因為內部組織成分支和子分支的可能性是無窮無盡的。
兩個 BIP 透過為 HD 錢包樹的結構創建一些建議的標準來為這種複雜性提供解決方案。BIP43 提議使用第一個強化子索引作為特殊標識符,表示樹結構的「目的」。基於 BIP43,HD 錢包應僅使用樹的一個第 1 層分支,索引號透過定義其目的來標識樹其餘部分的結構和命名空間。例如,僅使用分支 m/i'/ 的 HD 錢包旨在表示特定目的,該目的由索引號「i」標識。
擴展該規範,BIP44 提出了多帳戶結構作為 BIP43 下的「目的」編號 44'。遵循 BIP44 結構的所有 HD 錢包都透過它們僅使用樹的一個分支這一事實來標識:m/44'/。
BIP44 將結構指定為由五個預定義的樹層級組成:
m / purpose' / coin_type' / account' / change / address_index
第一層「purpose」始終設定為 44'。第二層「coin_type」指定加密貨幣幣種的類型,允許多幣種 HD 錢包,其中每種貨幣在第二層下都有自己的子樹。比特幣是 m/44'/0',比特幣測試網路是 m/44'/1'。
樹的第三層是「account」,允許使用者將其錢包細分為單獨的邏輯子帳戶,用於會計或組織目的。例如,HD 錢包可能包含兩個比特幣「帳戶」:m/44'/0'/0' 和 m/44'/0'/1'。每個帳戶都是其自己子樹的根。
在第四層「change」上,HD 錢包有兩個子樹,一個用於創建接收地址,一個用於創建找零地址。請注意,雖然前幾層使用強化衍生,但此層使用正常衍生。這是為了允許樹的此層級匯出擴展公鑰以在非安全環境中使用。可用地址由 HD 錢包作為第四層的子金鑰衍生,使樹的第五層成為「address_index」。例如,主要帳戶中用於付款的第三個接收地址將是 M/44'/0'/0'/0/2。[table_4-9] 顯示了更多範例。
| HD 路徑 | 描述的金鑰 | 
|---|---|
| M/44 | 主要比特幣帳戶的第三個接收公鑰 | 
| M/44 | 第四個比特幣帳戶的第十五個找零地址公鑰 | 
| m/44 | 萊特幣主帳戶中用於簽署交易的第二個私鑰 | 
許多人專注於保護他們的比特幣免受盜竊和其他攻擊,但丟失比特幣的主要原因之一——也許是_主要_原因——是資料丟失。如果花費比特幣所需的金鑰和其他基本資料丟失,這些比特幣將永遠無法花費。沒有人可以為您找回它們。在本章中,我們研究了現代錢包應用程式使用的系統,以幫助您防止丟失該資料。但是,請記住,實際使用可用的系統來製作良好的備份並定期測試它們取決於您。
交易
我們通常轉移實體現金的方式與我們轉移比特幣的方式幾乎沒有相似之處。實體現金是一種持票人代幣。Alice 透過交給 Bob 一些數量的代幣(例如美元鈔票)來支付給他。相比之下,比特幣既不是實體存在也不是數位資料——Alice 不能將一些比特幣交給 Bob 或透過電子郵件發送它們。
相反,考慮一下 Alice 如何將一塊土地的控制權轉移給 Bob。她不能實體上拿起土地並交給 Bob。相反,存在某種記錄(通常由當地政府維護),該記錄描述了 Alice 擁有的土地。Alice 透過說服政府更新記錄以表明 Bob 現在擁有該土地來將該土地轉移給 Bob。
比特幣以類似的方式運作。每個比特幣完整節點上都存在一個資料庫,表明 Alice 控制一定數量的比特幣。Alice 透過說服完整節點更新其資料庫以表明 Alice 的一些比特幣現在由 Bob 控制來支付給 Bob。Alice 用來說服完整節點更新其資料庫的資料稱為_交易(transaction)_。這是在不直接使用 Alice 或 Bob 的身分的情況下完成的,正如我們將在 授權與認證 中看到的那樣。
在本章中,我們將解構比特幣交易並檢查其每個部分,以了解它們如何以高度表達和令人驚訝的可靠方式促進價值轉移。
序列化的比特幣交易
在 探索和解碼交易 中,我們使用啟用了 txindex 選項的 Bitcoin Core 檢索了 Alice 支付給 Bob 的副本。讓我們再次檢索包含該付款的交易,如 Alice 的序列化交易 所示。
$ bitcoin-cli getrawtransaction 466200308696215bbc949d5141a49a41\ 38ecdfdfaa2a8029c1f9bcecd1f96177 01000000000101eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da13569 8679268041c54a0100000000ffffffff02204e0000000000002251203b41daba 4c9ace578369740f15e5ec880c28279ee7f51b07dca69c7061e07068f8240100 000000001600147752c165ea7be772b2c0acb7f4d6047ae6f4768e0141cf5efe 2d8ef13ed0af21d4f4cb82422d6252d70324f6f4576b727b7d918e521c00b51b e739df2f899c49dc267c0ad280aca6dab0d2fa2b42a45182fc83e81713010000 0000
Bitcoin Core 的序列化格式很特殊,因為它是用於對交易進行承諾並透過比特幣 P2P 網路中繼它們的格式,但除此之外,程式可以使用不同的格式,只要它們傳輸所有相同的資料。然而,Bitcoin Core 的格式對於它傳輸的資料來說相當緊湊且易於解析,因此許多其他比特幣程式使用此格式。
| 我們知道的唯一其他廣泛使用的交易序列化格式是 BIP 174 和 370 中記錄的部分簽署比特幣交易(PSBT)格式(在其他 BIP 中記錄了擴展)。PSBT 允許不受信任的程式產生交易範本,該範本可以由擁有必要私鑰或其他敏感資料以填寫範本的受信任程式(例如硬體簽署設備)驗證和更新。為了實現這一點,PSBT 允許儲存關於交易的大量元資料,使其比標準序列化格式緊湊得多。本書不詳細介紹 PSBT,但我們強烈建議計劃支援使用多個金鑰進行簽署的錢包開發者使用它。 | 
Alice 的序列化交易 中以十六進位顯示的交易在 Alice 交易的位元組對映。 中複製為位元組對映。請注意,需要 64 個十六進位字元來顯示 32 個位元組。此對映僅顯示頂層欄位。我們將按照它們在交易中出現的順序檢查每個欄位,並描述它們包含的任何其他欄位。
 
版本
序列化比特幣交易的前四個位元組是其版本。比特幣交易的原始版本是版本 1(0x01000000)。比特幣中的所有交易都必須遵循版本 1 交易的規則,其中許多規則在本書中描述。
版本 2 比特幣交易在 BIP68 軟分叉變更中引入到比特幣的共識規則中。BIP68 對序列欄位施加了額外的限制,但這些限制僅適用於版本 2 或更高版本的交易。版本 1 交易不受影響。BIP112 是與 BIP68 相同軟分叉的一部分,升級了一個操作碼(OP_CHECKSEQUENCEVERIFY),如果它作為版本低於 2 的交易的一部分進行評估,現在將失敗。除了這兩個變更之外,版本 2 交易與版本 1 交易相同。
截至撰寫本文時,開始使用版本 3 交易的提案正在被廣泛考慮。該提案不尋求更改共識規則,而僅更改比特幣完整節點用於中繼交易的策略。根據該提案,版本 3 交易將受到額外的限制,以防止我們將在 交易固定 中討論的某些拒絕服務(DoS)攻擊。
擴展標記和標誌
範例序列化交易的接下來兩個欄位是作為隔離見證(segwit)軟分叉變更的一部分新增到比特幣共識規則中的。這些規則根據 BIP 141 和 143 進行了更改,但_擴展序列化格式_在 BIP144 中定義。
如果交易包含見證結構(我們將在 見證結構 中描述),則標記必須為零(0x00),標誌必須為非零。在當前的 P2P 協定中,標誌應始終為一(0x01);替代標誌保留用於以後的協定升級。
如果交易不需要見證堆疊,則標記和標誌不得存在。這與比特幣交易序列化格式的原始版本相容,現在稱為_傳統序列化_。詳情請參見 傳統序列化。
在傳統序列化中,標記位元組將被解釋為輸入數量(零)。交易不能有零個輸入,因此標記向現代程式發出正在使用擴展序列化的信號。標誌欄位提供類似的信號,並且還簡化了未來更新序列化格式的過程。
輸入
輸入欄位包含幾個其他欄位,因此讓我們首先在 Alice 交易的輸入欄位中位元組的對映。 中顯示這些位元組的對映。
 
交易輸入列表的長度
交易輸入列表以一個整數開頭,指示交易中的輸入數量。最小值為一。沒有明確的最大值,但對交易最大大小的限制有效地將交易限制為幾千個輸入。該數字編碼為 compactSize 無符號整數。
交易中的每個輸入都必須包含三個欄位:輸出點(outpoint)_欄位、長度前綴的_輸入腳本_欄位和_序列
我們將在以下各節中查看每個欄位。某些輸入還包括見證堆疊,但這在交易末尾序列化,因此我們將稍後檢查它。
輸出點
比特幣交易是完整節點更新其幣所有權資訊資料庫的請求。為了讓 Alice 將她的一些比特幣的控制權轉移給 Bob,她首先需要告訴完整節點如何找到她收到這些比特幣的先前轉移。由於對比特幣的控制權是在交易輸出中分配的,Alice 使用_輸出點(outpoint)欄位_指向_先前的_輸出。每個輸入必須包含單個輸出點。
輸出點包含 Alice 收到她現在想要花費的比特幣的交易的 32 位元組 txid。此 txid 採用比特幣的內部位元組順序表示雜湊;請參見 內部和顯示位元組順序。
由於交易可能包含多個輸出,Alice 還需要識別該交易中要使用的特定輸出,稱為其_輸出索引(output index)_。輸出索引是從零開始的 4 位元組無符號整數。
當完整節點遇到輸出點時,它使用該資訊嘗試找到引用的輸出。完整節點僅需要查看區塊鏈中的早期交易。例如,Alice 的交易包含在區塊 774,958 中。驗證她的交易的完整節點僅在該區塊和先前的區塊中查找她的輸出點引用的先前輸出,而不是任何後續區塊。在區塊 774,958 內,它們將僅查看放置在 Alice 交易之前的區塊中的交易,由區塊的默克爾樹中葉子的順序確定(請參見 默克爾樹)。
在找到先前輸出後,完整節點從中獲得幾條關鍵資訊:
- 
分配給該先前輸出的比特幣數量。所有這些比特幣都將在此交易中轉移。在範例交易中,先前輸出的值為 100,000 聰。 
- 
該先前輸出的授權條件。這些是為了花費分配給該先前輸出的比特幣而必須滿足的條件。 
- 
對於已確認的交易,確認它的區塊的高度以及該區塊的中位時間過去(MTP)。這是相對時間鎖(在 序列作為共識強制執行的相對時間鎖 中描述)和 coinbase 交易的輸出(在 Coinbase 交易 中描述)所必需的。 
- 
證明先前輸出存在於區塊鏈中(或作為已知的未確認交易),並且沒有其他交易花費它。比特幣的共識規則之一禁止在有效區塊鏈中多次花費任何輸出。這是反對雙重支付_的規則:Alice 不能在單獨的交易中使用相同的先前輸出同時支付給 Bob 和 Carol。兩個交易各自試圖花費相同的先前輸出,稱為_衝突交易(conflicting transactions),因為在有效的區塊鏈中只能包含其中一個。 
不同的完整節點實作在不同時間嘗試了追蹤先前輸出的不同方法。Bitcoin Core 目前使用被認為在保留所有必要資訊的同時最小化磁碟空間的解決方案:它保留一個資料庫,該資料庫儲存每個 UTXO 及其基本元資料(例如其確認區塊高度)。每次新的交易區塊到達時,它們花費的所有輸出都從 UTXO 資料庫中刪除,它們創建的所有輸出都新增到資料庫中。
輸入腳本
輸入腳本欄位是傳統交易格式的殘餘。我們的範例交易輸入花費了一個原生 segwit 輸出,該輸出在輸入腳本中不需要任何資料,因此輸入腳本的長度前綴設定為零(0x00)。
對於花費傳統輸出的長度前綴輸入腳本的範例,我們使用截至撰寫本文時最近區塊中任意交易的一個:
6b483045022100a6cc4e8cd0847951a71fad3bc9b14f24d44ba59d19094e0a8c fa2580bb664b020220366060ea8203d766722ed0a02d1599b99d3c95b97dab8e 41d3e4d3fe33a5706201210369e03e2c91f0badec46c9c903d9e9edae67c167b 9ef9b550356ee791c9a40896
長度前綴是一個 compactSize 無符號整數,指示序列化輸入腳本欄位的長度。在這種情況下,它是單個位元組(0x6b),指示輸入腳本為 107 個位元組。我們將在《第七章:授權與認證》中詳細介紹腳本的解析和使用。
序列
輸入的最後四個位元組是其_序列_編號。該欄位的用途和含義隨著時間而改變。
基於序列的原始交易替換
序列欄位最初旨在允許建立同一交易的多個版本,後續版本取代較早版本作為確認的候選項。序列編號追蹤交易的版本。
例如,假設 Alice 和 Bob 想要在一場紙牌遊戲上下注。他們首先各自簽署一筆交易,將一些錢存入一個需要他們兩人簽章才能花費的輸出腳本中,這是一個_多重簽章_腳本(multisig 簡稱)。這被稱為_設置交易_。然後他們建立一筆花費該輸出的交易:
- 
交易的第一個版本,nSequence 為 0(0x00000000),將 Alice 和 Bob 最初存入的錢退還給他們。這被稱為_退款交易_。此時他們都不廣播退款交易。他們只在出現問題時才需要它。 
- 
Alice 贏得了第一輪紙牌遊戲,因此交易的第二個版本(序列為 1)增加了支付給 Alice 的金額並減少了 Bob 的份額。他們都簽署了更新的交易。同樣,除非出現問題,否則他們不需要廣播這個版本的交易。 
- 
Bob 贏得了第二輪,因此序列遞增為 2,Alice 的份額減少,Bob 的份額增加。他們再次簽署但不廣播。 
- 
在更多輪次中序列遞增、資金重新分配以及生成的交易被簽署但未廣播之後,他們決定敲定交易。使用資金的最終餘額建立交易,他們將序列設置為其最大值(0xffffffff),完成交易。他們廣播這個版本的交易,它在網路上中繼,最終被礦工確認。 
如果我們考慮替代場景,我們可以看到序列的替換規則在起作用:
- 
想像 Alice 廣播了最終交易(序列為 0xffffffff),然後 Bob 廣播了他餘額較高的較早交易之一。因為 Bob 的交易版本具有較低的序列編號,使用原始 Bitcoin 程式碼的完整節點不會將其中繼給礦工,使用原始程式碼的礦工也不會挖掘它。 
- 
在另一個場景中,想像 Bob 在 Alice 廣播最終版本前幾秒廣播了交易的較早版本。節點將中繼 Bob 的版本,礦工將嘗試挖掘它,但當 Alice 的具有較高序列編號的版本到達時,節點也會中繼它,使用原始 Bitcoin 程式碼的礦工將嘗試挖掘它而不是 Bob 的版本。除非 Bob 運氣好,在 Alice 的版本到達之前發現了一個區塊,否則將是 Alice 的交易版本得到確認。 
這種類型的協議就是我們現在稱為_支付通道_的協議。Bitcoin 的創造者在歸屬於他的一封電子郵件中稱這些為_高頻交易_,並描述了添加到協議中以支援它們的許多功能。我們稍後將了解其中的一些其他功能,並發現現代版本的支付通道如何越來越多地在 Bitcoin 中使用。
純粹基於序列的支付通道存在一些問題。第一個問題是,用較高序列交易替換較低序列交易的規則只是軟體策略的問題。礦工沒有直接的動機去偏好交易的某一個版本而不是其他任何版本。第二個問題是,第一個發送交易的人可能會運氣好並得到確認,即使它不是最高序列的交易。一個由於運氣不好而在幾個百分點的時間內失敗的安全協議不是一個非常有效的協議。
第三個問題是,可以無限次地用不同版本替換交易的一個版本。每次替換都會消耗網路上所有中繼完整節點的頻寬。例如,截至撰寫本文時,大約有 50,000 個中繼完整節點;攻擊者每分鐘建立 1,000 個替換交易(每個 200 位元組)將使用大約 20 KB 的個人頻寬,但每分鐘使用大約 10 GB 的完整節點網路頻寬。除了他們每分鐘 20 KB 頻寬的成本以及偶爾在交易得到確認時的手續費外,攻擊者不需要為他們給完整節點營運商帶來的巨大負擔支付任何成本。
為了消除這種攻擊的風險,在早期版本的 Bitcoin 軟體中禁用了原始類型的基於序列的交易替換。幾年來,Bitcoin 完整節點不允許包含特定輸入(由其輸出點指示)的未確認交易被包含相同輸入的不同交易替換。然而,這種情況並沒有永遠持續下去。
選擇性交易替換信號
在原始基於序列的交易替換因可能被濫用而被禁用後,提出了一個解決方案:程式設計 Bitcoin Core 和其他中繼完整節點軟體,允許支付較高交易手續費率的交易替換支付較低費率的衝突交易。這被稱為_手續費替代_,或簡稱 RBF。一些使用者和企業反對在 Bitcoin Core 中添加對交易替換的支援,因此達成了一項妥協,再次使用序列欄位來支援替換。
如 BIP125 所述,任何輸入的序列設置為低於 0xfffffffe 的值(即至少比最大值低 2)的未確認交易向網路發出信號,表明其簽署者希望它可以被支付更高費率的衝突交易替換。Bitcoin Core 允許替換那些未確認的交易,並繼續禁止替換其他交易。這允許反對替換的使用者和企業簡單地忽略包含 BIP125 信號的未確認交易,直到它們被確認。
現代交易替換策略不僅僅涉及費率和序列信號,我們將在 手續費替代(RBF)手續費提升 中看到。
序列作為共識強制執行的相對時間鎖
在 版本 中,我們了解到 BIP68 軟分叉為版本號為 2 或更高的交易添加了新的約束。該約束適用於序列欄位。
序列值小於 231 的交易輸入被解釋為具有相對時間鎖。這樣的交易只能在前一個輸出(由輸出點引用)已經老化了相對時間鎖數量後才能包含在區塊鏈中。例如,一個輸入具有 30 個區塊相對時間鎖的交易只能在一個區塊中確認,該區塊與包含在同一區塊鏈上被花費的輸出的區塊之間至少有 29 個區塊。由於序列是每個輸入的欄位,因此交易可以包含任意數量的時間鎖定輸入,所有這些輸入都必須充分老化才能使交易有效。禁用標誌允許交易同時包含具有相對時間鎖的輸入(序列 < 231)和沒有相對時間鎖的輸入(序列 ≥ 231)。
序列值以區塊或秒為單位指定。類型標誌用於區分計算區塊的值和以秒計算時間的值。類型標誌設置在第 23 個最低有效位元(即值 1<<22)。如果設置了類型標誌,則序列值被解釋為 512 秒的倍數。如果未設置類型標誌,則序列值被解釋為區塊數。
當將序列解釋為相對時間鎖時,只考慮 16 個最低有效位元。一旦評估了標誌(位元 32 和 23),序列值通常會用 16 位元掩碼「遮罩」(例如,sequence & 0x0000FFFF)。512 秒的倍數大致等於區塊之間的平均時間量,因此從 16 位元(216)來看,區塊和秒的最大相對時間鎖都略超過一年。
BIP68 序列編碼的定義(來源:BIP68)。 顯示了 BIP68 定義的序列值的二進位佈局。
 
請注意,任何使用序列設置相對時間鎖的交易也會發送 選擇性交易替換信號 中描述的選擇性手續費替代信號。
輸出
交易的輸出欄位包含與特定輸出相關的幾個欄位。就像我們對輸入欄位所做的那樣,我們將從查看 Alice 支付給 Bob 的範例交易的輸出欄位的特定位元組開始,在 Alice 交易中輸出欄位的位元組映射。 中顯示為這些位元組的映射。
 
金額
特定輸出的第一個欄位是其_金額_,在 Bitcoin Core 中也稱為「value」。這是一個 8 位元組有符號整數,指示要轉移的聰數。聰是可以在鏈上 Bitcoin 交易中表示的 bitcoin 的最小單位。一個 bitcoin 中有 1 億個聰。
Bitcoin 的共識規則允許輸出的值小至零,大至 2100 萬個 bitcoin(2.1 千兆聰)。
不經濟的輸出和不允許的粉塵
儘管沒有任何價值,零值輸出可以在與任何其他輸出相同的規則下花費。然而,花費一個輸出(將其用作交易中的輸入)會增加交易的大小,從而增加需要支付的手續費金額。如果輸出的價值小於額外手續費的成本,那麼花費該輸出就沒有經濟意義。這樣的輸出被稱為_不經濟的輸出_。
零值輸出始終是不經濟的輸出;即使交易的費率為零,它也不會為花費它的交易貢獻任何價值。然而,許多其他低價值的輸出也可能是不經濟的,甚至是無意的。例如,以今天網路上的典型費率,一個輸出可能為交易增加的價值超過花費它的成本——但明天,費率可能會上升並使輸出變得不經濟。
如 輸出點 中所述,完整節點需要追蹤所有 UTXO,這意味著每個 UTXO 都會使執行完整節點變得稍微困難一些。對於包含重要價值的 UTXO,最終會有動機去花費它們,所以它們不是問題。但是,控制不經濟 UTXO 的人沒有動機去花費它,可能使其成為完整節點營運商的永久負擔。因為 Bitcoin 的去中心化取決於許多人願意執行完整節點,所以 Bitcoin Core 等幾個完整節點實作使用影響未確認交易中繼和挖礦的策略來阻止建立不經濟的輸出。
反對中繼或挖掘建立新的不經濟輸出的交易的策略被稱為_粉塵_策略,基於價值非常小的輸出和大小非常小的粒子之間的隱喻比較。Bitcoin Core 的粉塵策略很複雜並包含幾個任意數字,因此我們了解的許多程式簡單地假設少於 546 聰的輸出是粉塵,預設情況下不會被中繼或挖掘。偶爾有降低粉塵限制的提案,也有提高粉塵限制的反提案,因此我們鼓勵使用預簽交易或多方協議的開發人員檢查自本書出版以來策略是否已更改。
| 自 Bitcoin 誕生以來,每個完整節點都需要保留每個 UTXO 的副本,但這種情況可能不會永遠如此。幾位開發人員一直在開發 Utreexo,這是一個允許完整節點儲存對 UTXO 集合的承諾而不是資料本身的專案。最小的承諾可能只有一兩千位元組——將其與截至撰寫本文時 Bitcoin Core 儲存的超過 50 億位元組進行比較。 然而,Utreexo 仍然需要一些節點儲存所有 UTXO 資料,尤其是為礦工和其他需要快速驗證新區塊的操作提供服務的節點。這意味著即使在大多數節點使用 Utreexo 的可能未來中,不經濟的輸出仍然可能是完整節點的問題。 | 
Bitcoin Core 關於粉塵的策略規則確實有一個例外:以 OP_RETURN 開頭的輸出腳本(稱為_資料載體輸出_)可以具有零值。OP_RETURN 操作碼導致腳本立即失敗,無論其後是什麼,因此這些輸出永遠無法被花費。這意味著完整節點不需要追蹤它們,Bitcoin Core 利用這一功能允許使用者在區塊鏈中儲存少量任意資料,而不增加其 UTXO 資料庫的大小。由於輸出是不可花費的,它們不是不經濟的——分配給它們的任何聰都會永久不可花費——因此允許金額為零可確保聰不會被銷毀。
輸出腳本
輸出金額後面是一個 compactSize 整數,指示_輸出腳本_的長度,該腳本包含花費 bitcoin 所需要滿足的條件。根據 Bitcoin 的共識規則,輸出腳本的最小大小為零。
輸出腳本的共識允許的最大大小取決於檢查時的時間。交易輸出中的輸出腳本的大小沒有明確限制,但後續交易只能花費具有 10,000 位元組或更小腳本的先前輸出。隱含地,輸出腳本幾乎可以與包含它的交易一樣大,而交易幾乎可以與包含它的區塊一樣大。
| 長度為零的輸出腳本可以由包含 OP_TRUE 的輸入腳本花費。任何人都可以建立該輸入腳本,這意味著任何人都可以花費空的輸出腳本。本質上有無限數量的任何人都可以花費的腳本,Bitcoin 協議開發人員稱它們為_任何人都可以花費_。對 Bitcoin 腳本語言的升級通常採用現有的任何人都可以花費的腳本並向其添加新的約束,使其僅在新條件下可花費。應用程式開發人員永遠不需要使用任何人都可以花費的腳本,但如果您這樣做,我們強烈建議您向 Bitcoin 使用者和開發人員大聲宣布您的計畫,以便未來的升級不會意外干擾您的系統。 | 
Bitcoin Core 的中繼和挖掘交易策略有效地將輸出腳本限制為僅幾個範本,稱為_標準交易輸出_。這最初是在發現與 Script 語言相關的幾個早期 Bitcoin 錯誤後實作的,並在現代 Bitcoin Core 中保留以支援任何人都可以花費的升級,並鼓勵將腳本條件放置在 P2SH 贖回腳本、segwit v0 見證腳本和 segwit v1(taproot)葉腳本中的最佳實踐。
我們將查看每個當前標準交易範本,並在 授權與認證 中學習如何解析腳本。
見證結構
在法庭上,見證人是證明他們看到重要事情發生的人。人類見證人並不總是可靠的,因此法庭有各種程序來訊問見證人,以(理想情況下)只接受來自可靠者的證據。
想像一下數學問題的見證人會是什麼樣子。例如,如果重要的問題是 x + 2 == 4 並且有人聲稱他們見證了解決方案,我們會問他們什麼?我們想要一個數學證明,顯示一個可以與 2 相加等於 4 的值。我們甚至可以省略對人的需求,只使用建議的 x 值作為我們的見證人。如果我們被告知見證人是 two,那麼我們可以填入方程式,檢查它是否正確,並決定重要的問題已經解決。
在花費 bitcoin 時,我們想要解決的重要問題是確定花費是否得到控制這些 bitcoin 的人或人們的授權。執行 Bitcoin 共識規則的數千個完整節點無法訊問人類見證人,但它們可以接受完全由用於解決數學問題的資料組成的_見證_。例如,2 的見證將允許花費由以下腳本保護的 bitcoin:
2 OP_ADD 4 OP_EQUAL
顯然,允許任何能解決簡單方程式的人花費您的 bitcoin 是不安全的。正如我們將在 數位簽章 中看到的,不可偽造的數位簽章方案使用一個方程式,該方程式只能由擁有他們能夠保密的某些資料的人來解決。他們能夠使用公開識別符號引用該秘密資料。該公開識別符號稱為_公鑰_,方程式的解稱為_簽章_。
以下腳本包含一個公鑰和一個操作碼,該操作碼要求相應的簽章承諾花費交易中的資料。就像我們簡單範例中的數字 2 一樣,簽章是我們的見證:
<public key> OP_CHECKSIG
見證,用於解決保護 bitcoin 的數學問題的值,需要包含在使用它們的交易中,以便完整節點驗證它們。在用於所有早期 Bitcoin 交易的傳統交易格式中,簽章和其他資料放置在輸入腳本欄位中。然而,當開發人員開始在 Bitcoin 上實作合約協議時,例如我們在 基於序列的原始交易替換 中看到的,他們發現將見證放在輸入腳本欄位中存在幾個重大問題。
循環依賴
許多 Bitcoin 合約協議涉及一系列亂序簽署的交易。例如,Alice 和 Bob 想要將資金存入只能用他們兩人的簽章花費的腳本中,但他們每個人也希望在另一個人變得無回應時拿回他們的錢。一個簡單的解決方案是亂序簽署交易:
- 
Tx0 將 Alice 的錢和 Bob 的錢支付到一個需要 Alice 和 Bob 簽章才能花費的輸出腳本中。 
- 
Tx1 將先前的輸出花費到兩個輸出,一個退款給 Alice 她的錢,一個退款給 Bob 他的錢(減去少量交易手續費)。 
- 
如果 Alice 和 Bob 在簽署 Tx0 之前簽署 Tx1,那麼他們都保證可以隨時獲得退款。該協議不需要他們中的任何一個信任另一個,使其成為_無信任協議_。 
在傳統交易格式中,這種結構的問題是每個欄位,包括包含簽章的輸入腳本欄位,都用於衍生[.keep-together]#交易的#識別符號(txid)。Tx0 的 txid 是 Tx1 中輸入的輸出點的一部分。這意味著在知道 Tx0 的兩個簽章之前,Alice 和 Bob 無法建立 Tx1——但如果他們知道 Tx0 的簽章,他們中的一個可以在簽署退款交易之前廣播該交易,從而消除退款的保證。這是一個_循環依賴_。
第三方交易可塑性
更複雜的交易序列有時可以消除循環依賴,但許多協議隨後會遇到一個新的問題:通常可以用不同的方式解決同一個腳本。例如,考慮我們在 見證結構 中的簡單腳本:
2 OP_ADD 4 OP_EQUAL
我們可以通過在輸入腳本中提供值 2 來使此腳本通過,但在 Bitcoin 中有幾種方法可以將該值放在堆疊上。以下只是其中幾種:
OP_2 OP_PUSH1 0x02 OP_PUSH2 0x0002 OP_PUSH3 0x000002 ... OP_PUSHDATA1 0x0102 OP_PUSHDATA1 0x020002 ... OP_PUSHDATA2 0x000102 OP_PUSHDATA2 0x00020002 ... OP_PUSHDATA4 0x0000000102 OP_PUSHDATA4 0x000000020002 ...
輸入腳本中數字 2 的每種替代編碼都將產生一個略有不同的交易,具有完全不同的 txid。交易的每個不同版本都花費與交易的每個其他版本相同的輸入(輸出點),使它們彼此_衝突_。一組衝突交易中只有一個版本可以包含在有效的區塊鏈中。
想像 Alice 建立了一個在輸入腳本中包含 OP_2 的交易版本,並且有一個輸出支付給 Bob。Bob 然後立即將該輸出花費給 Carol。網路上的任何人都可以用 OP_PUSH1 0x02 替換 OP_2,建立與 Alice 原始版本的衝突。如果該衝突交易得到確認,那麼就沒有辦法在同一區塊鏈中包含 Alice 的原始版本,這意味著 Bob 的交易沒有辦法花費其輸出。即使 Alice、Bob 或 Carol 都沒有做錯任何事,Bob 支付給 Carol 的交易也已經無效。未參與交易的某人(第三方)能夠改變(變異)Alice 的交易,這個問題稱為_不需要的第三方交易可塑性_。
| 在某些情況下,人們希望他們的交易是可塑的,Bitcoin 提供了幾個功能來支援這一點,最著名的是我們將在 簽章雜湊類型(SIGHASH) 中了解的簽章雜湊(sighash)。例如,Alice 可以使用 sighash 允許 Bob 幫助她支付一些交易手續費。這會變異 Alice 的交易,但只是以 Alice 想要的方式。因此,我們有時會在_交易可塑性_一詞前加上_不需要的_前綴。即使我們和其他 Bitcoin 技術作家使用較短的術語,我們幾乎肯定是在談論可塑性的不需要變體。 | 
第二方交易可塑性
當傳統交易格式是唯一的交易格式時,開發人員致力於最小化第三方可塑性的提案,例如 BIP62。然而,即使他們能夠完全消除第三方可塑性,合約協議的使用者也面臨另一個問題:如果他們需要協議中涉及的其他人的簽章,該人可以生成替代簽章並更改 txid。
例如,Alice 和 Bob 已將他們的錢存入需要他們兩人簽章才能花費的腳本中。他們還建立了一筆退款交易,允許他們每個人隨時拿回他們的錢。Alice 決定她想只花費部分錢,因此她與 Bob 合作建立一系列交易:
- 
Tx0 包括來自 Alice 和 Bob 的簽章,將其 bitcoin 花費到兩個輸出。第一個輸出花費了 Alice 的一些錢;第二個輸出將剩餘的 bitcoin 返回到需要 Alice 和 [.keep-together]#Bob 簽章的#腳本。在簽署此交易之前,他們建立一個新的退款交易 Tx1。 
- 
Tx1 將 Tx0 的第二個輸出花費到兩個新輸出,一個給 Alice 她在聯合資金中的份額,一個給 Bob 他的份額。Alice 和 Bob 都在簽署 Tx0 之前簽署此交易。 
這裡沒有循環依賴,如果我們忽略第三方交易可塑性,這看起來應該為我們提供一個無信任協議。然而,Bitcoin 簽章的一個屬性是簽署者在建立簽章時必須選擇一個大的隨機數。選擇不同的隨機數將產生不同的簽章,即使被簽署的所有內容保持不變。這有點像,如果您為同一合約的兩份副本提供手寫簽章,每個物理簽章看起來都會略有不同。
簽章的這種可變性意味著,如果 Alice 嘗試廣播 Tx0(其中包含 Bob 的簽章),Bob 可以生成替代簽章以建立具有不同 txid 的衝突交易。如果 Bob 的 Tx0 替代版本得到確認,那麼 Alice 無法使用預簽版本的 Tx1 來索取她的退款。這種類型的變異稱為_不需要的第二方交易可塑性_。
隔離見證
早在 2011年,協議開發人員就知道如何解決循環依賴、第三方可塑性和第二方可塑性的問題。這個想法是避免在產生交易 txid 的計算中包含輸入腳本。回想一下,輸入腳本持有的資料的抽象名稱是_見證_。出於生成 txid 的目的,將交易中的其餘資料與其見證分離的想法稱為_隔離見證_(segwit)。
實作 segwit 的明顯方法需要對 Bitcoin 的共識規則進行更改,這與較舊的完整節點不相容,也稱為_硬分叉_。硬分叉帶來了很多挑戰,我們將在 硬分叉 中進一步討論。
2015 年末描述了一種 segwit 的替代方法。這將使用對共識規則的向後相容更改,稱為_軟分叉_。向後相容意味著實作更改的完整節點不得接受未實作更改的完整節點認為無效的任何區塊。只要他們遵守該規則,較新的完整節點可以拒絕較舊的完整節點會接受的區塊,使它們能夠執行新的共識規則(但只有當較新的完整節點代表 Bitcoin 使用者之間的經濟共識時——我們將在 挖礦與共識 中探討升級 Bitcoin 共識規則的細節)。
軟分叉 segwit 方法基於任何人都可以花費的輸出腳本。以數字 0 到 16 中的任何一個開頭並後跟 2 到 40 位元組資料的腳本被定義為 segwit 輸出腳本範本。該數字指示其版本(例如,0 是 segwit 版本 0,或 segwit v0)。資料稱為_見證程式_。也可以在 P2SH 承諾中包裝 segwit 範本,但我們不會在本章中處理這個問題。
從舊節點的角度來看,這些輸出腳本範本可以用空的輸入腳本花費。從知道新 segwit 規則的新節點的角度來看,對 segwit 輸出腳本範本的任何支付都必須僅用空的輸入腳本花費。注意這裡的區別:舊節點_允許_空的輸入腳本;新節點_要求_空的輸入腳本。
空的輸入腳本使見證不影響 txid,從而消除循環依賴、第三方交易可塑性和第二方交易可塑性。但是,由於無法在輸入腳本中放置資料,segwit 輸出腳本範本的使用者需要一個新欄位。該欄位稱為_見證結構_。
見證程式和見證結構的引入使 Bitcoin 變得複雜,但它遵循了增加抽象的現有趨勢。回想 金鑰與地址 中原始的 Bitcoin 白皮書描述了一個系統,其中 bitcoin 被接收到公鑰(pubkeys)並用簽章(sigs)花費。公鑰定義了誰被_授權_花費 bitcoin(無論誰控制相應的私鑰),簽章提供了_認證_,證明花費交易來自控制私鑰的某人。為了使該系統更靈活,Bitcoin 的初始版本引入了允許 bitcoin 被接收到輸出腳本並用輸入腳本花費的腳本。後來對合約協議的經驗啟發了允許 bitcoin 被接收到見證程式並用見證結構花費。Bitcoin不同版本中使用的術語和欄位顯示在 [terms_used_authorization_authentication] 中。
| 授權 | 認證 | |
|---|---|---|
| 白皮書 | 公鑰 | 簽章 | 
| 原始(傳統) | 輸出腳本 | 輸入腳本 | 
| Segwit | 見證程式 | 見證結構 | 
見證結構序列化
與輸入和輸出欄位類似,見證結構包含其他欄位,因此我們將從 Alice 交易中見證結構的位元組映射。 中 Alice 交易的這些位元組映射開始。
 
與輸入和輸出欄位不同,整體見證結構不以它包含的見證堆疊總數的任何指示開始。相反,這是由輸入欄位隱含的——交易中的每個輸入都有一個見證堆疊。
特定輸入的見證結構確實以它們包含的元素數量的計數開始。這些元素稱為_見證項目_。我們將在 授權與認證 中詳細探討它們,但現在我們需要知道每個見證項目都以一個 compactSize 整數為前綴,指示其大小。
傳統輸入不包含任何見證項目,因此它們的見證堆疊完全由零計數(0x00)組成。
Alice 的交易包含一個輸入和一個見證項目。
鎖定時間
序列化交易中的最後一個欄位是其鎖定時間。此欄位是 Bitcoin 原始序列化格式的一部分,但最初僅由 Bitcoin 的選擇要挖掘哪些交易的策略執行。Bitcoin 最早已知的軟分叉添加了一條規則,從區塊高度 31,000 開始,禁止在區塊中包含交易,除非它滿足以下規則之一:
- 
交易通過將其鎖定時間設置為 0 來指示它應該有資格包含在任何區塊中。 
- 
交易通過將其鎖定時間設置為小於 500,000,000 的值來指示它想要限制可以包含在哪些區塊中。在這種情況下,交易只能包含在高度等於或高於鎖定時間的區塊中。例如,鎖定時間為 123,456 的交易可以包含在區塊 123,456 或任何後續區塊中。 
- 
交易通過將其鎖定時間設置為 500,000,000 或更大的值來指示它想要限制何時可以包含在區塊鏈中。在這種情況下,該欄位被解析為紀元時間(自 1970-01-01T00:00 UTC 以來的秒數),並且交易只能包含在("MTP (median time past)"過去中位時間(MTP)大於鎖定時間的區塊中。MTP 通常比當前時間晚大約一兩個小時。MTP 的規則在 中位時間過去(MTP) 中描述。 
Coinbase 交易
每個區塊中的第一筆交易是一個特殊情況。大多數較舊的文件稱這為_生成交易_,但大多數較新的文件稱其為 coinbase 交易(不要與名為「Coinbase」的公司建立的交易混淆)。
Coinbase 交易由包含它們的區塊的礦工建立,並賦予礦工選擇索取該區塊中交易支付的任何手續費的選項。此外,直到區塊 6,720,000,礦工被允許索取由以前從未流通過的 bitcoin 組成的補貼,稱為區塊補貼。礦工可以為一個區塊索取的總金額——手續費和補貼的組合——稱為_區塊獎勵_。
coinbase 交易的一些特殊規則包括:
- 
它們只能有一個輸入。 
- 
單個輸入必須具有一個空 txid(完全由零組成)和最大輸出索引(0xffffffff)的輸出點。這防止 coinbase 交易引用先前的交易輸出,這(至少)會令人困惑,因為 coinbase 交易支付手續費和補貼。 
- 
在正常交易中包含輸入腳本的欄位稱為 coinbase。正是這個欄位賦予 coinbase 交易其名稱。coinbase 欄位必須至少為 2 個位元組且不超過 100 個位元組。此腳本不執行,但對簽章檢查操作(sigops)數量的傳統交易限制確實適用於它,因此放置在其中的任何任意資料都應以資料推送操作碼為前綴。自 BIP34 中定義的 2013 軟分叉以來,此欄位的前幾個位元組必須遵循我們將在 Coinbase 資料 中描述的其他規則。 
- 
輸出的總和不得超過從該區塊中所有交易收集的手續費的值加上補貼。補貼從每個區塊 50 BTC 開始,每 210,000 個區塊減半(大約每四年)。補貼值向下舍入到最接近的聰。 
- 
自 BIP141 中記錄的 2017 segwit 軟分叉以來,任何包含花費 segwit 輸出的交易的區塊都必須包含對 coinbase 交易的輸出,該輸出承諾區塊中的所有交易(包括它們的見證)。我們將在 挖礦與共識 中探討這一承諾。 
coinbase 交易可以具有在正常交易中有效的任何其他輸出。然而,花費其中一個輸出的交易在 coinbase 交易收到 100 次確認之前不能包含在任何區塊中。這稱為_成熟規則_,並且還沒有 100 次確認的 coinbase 交易輸出稱為_未成熟_。
大多數 Bitcoin 軟體不需要處理 coinbase 交易,但它們的特殊性質確實意味著它們有時可能是未設計為預期它們的軟體中不尋常問題的原因。
權重和 Vbytes
每個 Bitcoin 區塊在可以包含的交易資料量上受到限制,因此大多數 Bitcoin 軟體需要能夠測量它建立或處理的交易。Bitcoin 的現代測量單位稱為_權重_。權重的替代版本是 vbytes,其中四個權重單位等於一個 vbyte,提供與傳統 Bitcoin 區塊中使用的原始_位元組_測量單位的簡單比較。
區塊限制為 400 萬權重。區塊頭佔用 240 權重。一個額外的欄位,交易計數,使用 4 或 12 權重。所有剩餘的權重可用於交易資料。
要計算交易中特定欄位的權重,將該序列化欄位的大小(以位元組為單位)乘以一個因子。要計算交易的權重,將其所有欄位的權重加在一起。交易中每個欄位的因子顯示在 [weight_factors] 中。為了提供一個範例,我們還計算了本章中從 Alice 到 Bob 的範例交易中每個欄位的權重。
選擇這些因子以及應用它們的欄位是為了減少花費 UTXO 時使用的權重。這有助於阻止建立 不經濟的輸出和不允許的粉塵 中描述的不經濟輸出。
| 欄位 | 因子 | Alice 交易中的權重 | 
|---|---|---|
| 版本 | 4 | 16 | 
| 標記與標誌 | 1 | 2 | 
| 輸入計數 | 4 | 4 | 
| 輸出點 | 4 | 144 | 
| 輸入腳本 | 4 | 4 | 
| 序列 | 4 | 16 | 
| 輸出計數 | 4 | 4 | 
| 金額 | 4 | 64(2 個輸出) | 
| 輸出腳本 | 4 | 232(2 個具有不同腳本的輸出) | 
| 見證計數 | 1 | 1 | 
| 見證項目 | 1 | 66 | 
| 鎖定時間 | 4 | 16 | 
| 總計 | 不適用 | 569 | 
我們可以通過從 Bitcoin Core 獲取 Alice 交易的總計來驗證我們的權重計算:
$ bitcoin-cli getrawtransaction 466200308696215bbc949d5141a49a41\ 38ecdfdfaa2a8029c1f9bcecd1f96177 2 | jq .weight 569
本章開頭 Alice 的序列化交易 中 Alice 的交易以權重單位表示,顯示在 Alice 交易的位元組映射。 中。您可以通過比較兩個圖像中各個欄位之間大小的差異來看到因子的作用。
 
傳統序列化
在撰寫本書時,本章中描述的序列化格式用於大多數新的 Bitcoin 交易,但較舊的序列化格式仍用於許多交易。該較舊的格式稱為_傳統序列化_,必須在 Bitcoin P2P 網路上用於任何具有空見證結構的交易(這僅在交易不花費任何見證程式時有效)。
傳統序列化不包括標記、標誌和見證結構欄位。
在本章中,我們查看了交易中的每個欄位,並發現它們如何向完整節點傳達有關在使用者之間轉移的 bitcoin 的詳細資訊。我們只簡要地查看了允許指定和滿足限制誰可以花費哪些 bitcoin 的條件的輸出腳本、輸入腳本和見證結構。了解如何建立和使用這些條件對於確保只有 Alice 可以花費她的 bitcoin 至關重要,因此它們將是下一章的主題。
授權與認證
當您接收 bitcoin 時,您必須決定誰將有權花費它們,稱為_授權_。您還必須決定完整節點如何將授權花費者與其他所有人區分開來,稱為_認證_。您的授權指令和花費者的認證證明將由數千個獨立的完整節點檢查,它們都需要得出相同的結論,即花費已獲得授權和認證,以使包含它的交易有效。
Bitcoin 的原始描述使用公鑰進行授權。Alice 通過將 Bob 的公鑰放入交易的輸出中來支付給 Bob。認證來自 Bob,形式為承諾花費交易的簽章,例如從 Bob 到 Carol 的交易。
最初發布的 Bitcoin 實際版本為授權和認證提供了更靈活的機制。從那時起的改進只增加了這種靈活性。在本章中,我們將探討這些功能,並了解它們最常見的用法。
交易腳本和腳本語言
Bitcoin的原始版本引入了一種稱為 Script 的新程式語言,一種類似 Forth 的基於堆疊的語言。放置在輸出中的腳本和花費交易中使用的傳統輸入腳本都是用這種腳本語言編寫的。
Script 是一種非常簡單的語言。它需要最少的處理,並且不能輕易完成許多現代程式語言可以完成的花哨事情。
當傳統交易是最常用的交易類型時,透過 Bitcoin 網路處理的大多數交易都具有「支付給 Bob 的 Bitcoin 地址」的形式,並使用稱為支付到公鑰雜湊(P2PKH)腳本的腳本。然而,Bitcoin 交易並不僅限於「支付給 Bob 的 Bitcoin 地址」腳本。實際上,腳本可以編寫為表達各種各樣的複雜條件。為了理解這些更複雜的腳本,我們必須首先理解交易腳本和 Script 語言的基礎知識。
在本節中,我們將展示 Bitcoin 交易腳本語言的基本組件,並展示如何使用它來表達花費條件以及如何滿足這些條件。
| Bitcoin 交易驗證不基於靜態模式,而是透過執行腳本語言來實現。這種語言允許表達幾乎無限種類的條件。 | 
圖靈不完備
Bitcoin交易腳本語言包含許多運算子,但在一個重要方面被刻意限制——除了條件流控制之外,沒有迴圈或複雜的流控制功能。這確保了該語言不是_圖靈完備_的,這意味著腳本具有有限的複雜性和可預測的執行時間。Script 不是一種通用語言。這些限制確保該語言不能用於建立無限迴圈或其他形式的「邏輯炸彈」,這些炸彈可以嵌入交易中,從而對 Bitcoin 網路造成拒絕服務攻擊。請記住,每筆交易都由 Bitcoin 網路上的每個完整節點驗證。有限的語言防止交易驗證機制被用作漏洞。
無狀態驗證
Bitcoin交易腳本語言是無狀態的,即在腳本執行之前沒有狀態,在腳本執行之後也沒有保存狀態。執行腳本所需的所有資訊都包含在腳本和執行腳本的交易中。腳本將在任何系統上以可預測的方式執行。如果您的系統驗證了腳本,您可以確信 Bitcoin 網路中的每個其他系統也將驗證腳本,這意味著有效的交易對每個人都有效,並且每個人都知道這一點。結果的這種可預測性是 Bitcoin 系統的一個基本好處。
腳本構建
Bitcoin的傳統交易驗證引擎依賴於腳本的兩個部分來驗證交易:輸出腳本和輸入腳本。
輸出腳本指定了將來花費輸出必須滿足的條件,例如誰被授權花費輸出以及他們將如何被認證。
輸入腳本是滿足輸出腳本中設置的條件並允許花費輸出的腳本。輸入腳本是每個交易輸入的一部分。在傳統交易中,大多數時候它們包含由使用者的錢包從其私鑰產生的數位簽章,但並非所有輸入腳本都必須包含簽章。
每個 Bitcoin驗證節點都將透過執行輸出和輸入腳本來驗證交易。正如我們在 交易 中看到的,每個輸入都包含一個引用先前交易輸出的輸出點。輸入還包含輸入腳本。驗證軟體將複製輸入腳本,擷取輸入引用的 UTXO,並從該 UTXO 複製輸出腳本。然後將輸入和輸出腳本一起執行。如果輸入腳本滿足輸出腳本的條件,則輸入有效(請參見 輸出和輸入腳本的單獨執行)。所有輸入都作為交易整體驗證的一部分獨立驗證。
請注意,前面的步驟涉及複製所有資料。先前輸出和當前輸入中的原始資料永遠不會更改。特別是,先前的輸出是不變的,不受花費它的失敗嘗試的影響。只有正確滿足輸出腳本條件的有效交易才會導致輸出被視為「已花費」。
組合輸入和輸出腳本以評估交易腳本。 是最常見類型的傳統 Bitcoin 交易(支付到公鑰雜湊)的輸出和輸入腳本的範例,顯示了在驗證之前腳本串聯產生的組合腳本。
 
腳本執行堆疊
Bitcoin的腳本語言稱為基於堆疊的語言,因為它使用稱為_堆疊_的資料結構。堆疊是一種非常簡單的資料結構,可以視覺化為一疊卡片。堆疊有兩個基本操作:推入和彈出。推入將一個項目添加到堆疊頂部。彈出從堆疊中移除頂部項目。
腳本語言透過從左到右處理每個項目來執行腳本。數字(資料常數)被推入堆疊。運算子從堆疊中推入或彈出一個或多個參數,對它們進行操作,並可能將結果推入堆疊。例如,OP_ADD 將從堆疊中彈出兩個項目,將它們相加,並將結果總和推入堆疊。
條件運算子評估條件,產生 TRUE 或 FALSE 的布林結果。例如,OP_EQUAL 從堆疊中彈出兩個項目,如果它們相等,則推入 TRUE(TRUE 由數字 1 表示),如果它們不相等,則推入 FALSE(由 0 表示)。Bitcoin 交易腳本通常包含條件運算子,以便它們可以產生表示有效交易的 TRUE 結果。
簡單腳本
現在讓我們應用我們學到的關於腳本和堆疊的知識到一些簡單的範例。
正如我們將在 Bitcoin 的腳本驗證執行簡單數學運算。 中看到的,腳本 2 3 OP_ADD 5 OP_EQUAL 展示了算術加法運算子 OP_ADD,將兩個數字相加並將結果放在堆疊上,然後是條件運算子 OP_EQUAL,它檢查結果總和是否等於 5。為簡潔起見,本書中的範例有時可能會省略 OP_ 前綴。有關可用腳本運算子和函數的更多詳細資訊,請參見 Bitcoin Wiki 的腳本頁面。
儘管大多數傳統輸出腳本引用公鑰雜湊(本質上是傳統 Bitcoin 地址),從而要求證明所有權才能花費資金,但腳本不必那麼複雜。產生 TRUE 值的輸出和輸入腳本的任何組合都是有效的。我們用作腳本語言範例的簡單算術也是有效的腳本。
使用算術範例腳本的一部分作為輸出腳本:
3 OP_ADD 5 OP_EQUAL
可以由包含具有輸入腳本的輸入的交易滿足:
2
驗證軟體組合腳本:
2 3 OP_ADD 5 OP_EQUAL
正如我們在 Bitcoin 的腳本驗證執行簡單數學運算。 中看到的,當執行此腳本時,結果是 OP_TRUE,使交易有效。儘管這是有效的交易輸出腳本,但請注意,任何具有算術技能知道數字 2 滿足腳本的人都可以花費生成的 UTXO。
 
| 如果堆疊頂部的結果是 TRUE(即任何非零值),則交易有效。如果堆疊頂部的值是 FALSE(零值或空堆疊),則交易無效,腳本執行被運算子(例如 VERIFY、OP_RETURN)明確停止,或者腳本在語義上無效(例如包含未由 OP_ENDIF 操作碼終止的 OP_IF 語句)。有關詳細資訊,請參見 Bitcoin Wiki 的腳本頁面。 | 
以下是一個稍微複雜一點的腳本,它計算 2 + 7 – 3 + 1。請注意,當腳本連續包含多個運算子時,堆疊允許一個運算子的結果被下一個運算子操作:
2 7 OP_ADD 3 OP_SUB 1 OP_ADD 7 OP_EQUAL
嘗試使用紙筆自己驗證前面的腳本。當腳本執行結束時,您應該在堆疊上剩下一個 TRUE 值。
支付到公鑰雜湊
支付到公鑰雜湊(P2PKH)腳本使用包含承諾公鑰的雜湊的輸出腳本。P2PKH 最著名的是作為傳統 Bitcoin 地址的基礎。P2PKH 輸出可以透過呈現與雜湊承諾相符的公鑰和由相應私鑰建立的數位簽章來花費(請參見 數位簽章)。讓我們看一個 P2PKH 輸出腳本的範例:
OP_DUP OP_HASH160 <Key Hash> OP_EQUALVERIFY OP_CHECKSIG
Key Hash 是將被編碼為傳統 base58check 地址的資料。大多數應用程式會使用十六進位編碼顯示腳本中的_公鑰雜湊_,而不是熟悉的以 [.keep-together]#「1」開頭#的 Bitcoin 地址 base58check 格式。
前面的輸出腳本可以用以下形式的輸入腳本滿足:
<Signature> <Public Key>
兩個腳本一起將形成以下組合驗證腳本:
<Sig> <Pubkey> OP_DUP OP_HASH160 <Hash> OP_EQUALVERIFY OP_CHECKSIG
如果輸入腳本具有來自 Bob 私鑰的有效簽章,該簽章對應於設置為限制條件的公鑰雜湊,則結果將是 TRUE。
圖 評估 P2PKH 交易的腳本(第 1 部分,共 2 部分)。 和 評估 P2PKH 交易的腳本(第 2 部分,共 2 部分)。(分為兩部分)顯示了組合腳本的逐步執行,這將證明這是一個有效的交易。
 
 
腳本化多重簽章
多重簽章腳本設置一個條件,其中在腳本中記錄了 k 個公鑰,並且至少必須提供其中 t 個來花費資金,稱為 t-of-k。例如,2-of-3 多重簽章是一個列出三個公鑰作為潛在簽署者的簽章,並且必須使用其中至少兩個來建立簽章,以使交易花費資金有效。
| 一些 Bitcoin 文件,包括本書的早期版本,對傳統多重簽章使用術語「m-of-n」。然而,在口語中很難區分「m」和「n」,因此我們使用替代的 t-of-k。這兩個短語都指同一類型的簽章方案。 | 
設置 t-of-k 多重簽章條件的輸出腳本的一般形式是:
t <Public Key 1> <Public Key 2> ... <Public Key k> k OP_CHECKMULTISIG
其中 k 是列出的公鑰總數,t 是花費輸出所需簽章的閾值。
設置 2-of-3 多重簽章條件的輸出腳本如下所示:
2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG
前面的輸出腳本可以用包含 [.keep-together]#簽章的#輸入腳本滿足:
<Signature B> <Signature C>
或來自與三個列出的公鑰相對應的私鑰的兩個簽章的任何組合。
兩個腳本一起將形成組合驗證腳本:
<Sig B> <Sig C> 2 <Pubkey A> <Pubkey B> <Pubkey C> 3 OP_CHECKMULTISIG
執行時,如果輸入腳本具有來自對應於設置為限制條件的三個公鑰中的兩個的私鑰的兩個有效簽章,則此組合腳本將評估為 TRUE。
目前,Bitcoin Core 的交易中繼策略將多重簽章輸出腳本限制為最多三個列出的公鑰,這意味著您可以執行從 1-of-1 到 3-of-3 多重簽章或該範圍內的任何組合。您可能想檢查 IsStandard() 函數以查看網路當前接受的內容。請注意,三個金鑰的限制僅適用於標準(也稱為「裸」)多重簽章腳本,不適用於包裝在另一個結構(如 P2SH、P2WSH 或 P2TR)中的腳本。P2SH 多重簽章腳本受策略和共識限制為 15 個金鑰,允許最多 15-of-15 多重簽章。我們將在 支付到腳本雜湊 中學習 P2SH。所有其他腳本在共識上受限於每個 OP_CHECKMULTISIG 或 OP_CHECKMULTISIGVERIFY 操作碼 20 個金鑰,儘管一個腳本可能包含多個這些操作碼。
CHECKMULTISIG 執行中的一個怪異之處
OP_CHECKMULTISIG的執行中存在一個怪異之處,需要稍微變通一下。當 OP_CHECKMULTISIG 執行時,它應該消耗堆疊上的 t + k + 2 個項目作為參數。然而,由於這個怪異之處,OP_CHECKMULTISIG 將彈出一個額外的值或比預期多一個值。
讓我們使用先前的驗證範例更詳細地看一下這個問題:
<Sig B> <Sig C> 2 <Pubkey A> <Pubkey B> <Pubkey C> 3 OP_CHECKMULTISIG
首先,OP_CHECKMULTISIG 彈出頂部項目,即 k(在此範例中為「3」)。然後它彈出 k 個項目,這些是可以簽章的公鑰;在此範例中為公鑰 A、B 和 C。然後,它彈出一個項目,即 t,法定人數(需要多少簽章)。這裡 t = 2。此時,OP_CHECKMULTISIG 應該彈出最後 t 個項目,這些是簽章,並查看它們是否有效。然而,不幸的是,實作中的一個怪異之處導致 OP_CHECKMULTISIG 彈出比應該彈出的多一個項目(總共 t + 1 個)。這個額外的項目稱為_虛擬堆疊元素_,在檢查簽章時會被忽略,因此它對 OP_CHECKMULTISIG 本身沒有直接影響。然而,虛擬元素必須存在,因為如果在 OP_CHECKMULTISIG 嘗試從空堆疊彈出時它不存在,它將導致堆疊錯誤和腳本失敗(將交易標記為無效)。因為虛擬元素被忽略,它可以是任何東西。很早就成為慣例使用 OP_0,後來成為中繼策略規則,最終成為共識規則(隨著 BIP147 的執行)。
因為彈出虛擬元素是共識規則的一部分,所以現在必須永遠複製。因此,腳本應該看起來像這樣:
OP_0 <Sig B> <Sig C> 2 <Pubkey A> <Pubkey B> <Pubkey C> 3 OP_CHECKMULTISIG
因此,多重簽章中實際使用的輸入腳本不是:
<Signature B> <Signature C>
而是:
OP_0 <Sig B> <Sig C>
有些人認為這個怪異之處是 Bitcoin 原始程式碼中的一個錯誤,但存在一個合理的替代解釋。驗證 t-of-k 簽章可能需要比 t 或 k 多得多的簽章檢查操作。讓我們考慮一個 1-in-5 的簡單範例,具有以下組合腳本:
<dummy> <Sig4> 1 <key0> <key1> <key2> <key3> <key4> 5 OP_CHECKMULTISIG
簽章首先與 key0 檢查,然後與 key1 檢查,然後與其他金鑰檢查,最後才與其對應的 key4 進行比較。這意味著即使只有一個簽章,也需要執行五個簽章檢查操作。消除這種冗餘的一種方法是向 OP_CHECKMULTISIG 提供一個映射,指示提供的簽章對應於哪個公鑰,允許 OP_CHECKMULTISIG 操作僅執行恰好 t 個簽章檢查操作。Bitcoin 的原始開發人員可能在 Bitcoin 的原始版本中添加了額外的元素(我們現在稱之為虛擬堆疊元素),以便他們可以在以後的軟分叉中添加允許傳遞映射的功能。然而,該功能從未實作,2017 年對共識規則的 BIP147 更新使得將來無法添加該功能。
只有 Bitcoin 的原始開發人員才能告訴我們虛擬堆疊元素是錯誤的結果還是未來升級的計畫。在本書中,我們簡單地稱之為怪異之處。
從現在開始,如果您看到多重簽章腳本,您應該期望在開頭看到一個額外的 OP_0,其唯一目的是作為對共識規則中怪異之處的變通方法。
支付到腳本雜湊
支付到腳本雜湊(P2SH)於 2012 年引入,作為一種強大的新型操作,大大簡化了複雜腳本的使用。為了解釋 P2SH 的需求,讓我們看一個實際範例。
Mohammed 是一位總部位於杜拜的電子產品進口商。Mohammed 的公司在其公司帳戶中廣泛使用 Bitcoin 的多重簽章功能。多重簽章腳本是 Bitcoin 進階腳本功能最常見的用途之一,是一個非常強大的功能。Mohammed 的公司對所有客戶付款使用多重簽章腳本。客戶支付的任何款項都以這樣的方式鎖定,即需要至少兩個簽章才能釋放。Mohammed、他的三位合夥人和他們的律師每人都可以提供一個簽章。這樣的多重簽章方案提供了公司治理控制,並防止盜竊、挪用或損失。
生成的腳本相當長,看起來像這樣:
2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 OP_CHECKMULTISIG
儘管多重簽章腳本是一個強大的功能,但使用起來很繁瑣。給定前面的腳本,Mohammed 必須在付款之前將此腳本傳達給每個客戶。每個客戶都必須使用具有建立自訂交易腳本能力的特殊 Bitcoin 錢包軟體。此外,生成的交易將大約是簡單支付交易的五倍大,因為此腳本包含非常長的公鑰。額外資料的負擔將以額外交易手續費的形式由客戶承擔。最後,像這樣的大型交易腳本將在每個完整節點的 UTXO 集中攜帶,直到它被花費。所有這些問題都使得在實踐中難以使用複雜的輸出腳本。
P2SH 的開發旨在解決這些實際困難,並使複雜腳本的使用與支付到單金鑰 Bitcoin 地址一樣容易。使用 P2SH 支付,複雜腳本被承諾(密碼雜湊的摘要)替換。當稍後呈現嘗試花費 UTXO 的交易時,它必須包含與承諾相符的腳本,以及滿足腳本的資料。簡單來說,P2SH 意味著「支付給與此雜湊相符的腳本,該腳本將在稍後花費此輸出時呈現」。
在 P2SH 交易中,被雜湊替換的腳本稱為_贖回腳本_,因為它在贖回時呈現給系統,而不是作為輸出腳本。[without_p2sh] 顯示沒有 P2SH 的腳本,[with_p2sh] 顯示使用 P2SH 編碼的相同腳本。
| 輸出腳本 | 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 OP_CHECKMULTISIG | 
| 輸入腳本 | Sig1 Sig2 | 
| 贖回腳本 | 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 OP_CHECKMULTISIG | 
| 輸出腳本 | OP_HASH160 <20-byte hash of redeem script> OP_EQUAL | 
| 輸入腳本 | Sig1 Sig2 <redeem script> | 
從表中可以看出,使用 P2SH,詳細說明花費輸出條件的複雜腳本(贖回腳本)不會呈現在輸出腳本中。相反,輸出腳本中只有它的雜湊,贖回腳本本身稍後作為輸入腳本的一部分在花費輸出時呈現。這將手續費和複雜性的負擔從支出者轉移到交易的接收者。
讓我們看看 Mohammed 的公司、複雜的多重簽章腳本和生成的 P2SH 腳本。
首先,Mohammed 公司用於所有來自客戶的傳入付款的多重簽章腳本:
2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 OP_CHECKMULTISIG
可以透過首先應用 SHA256 雜湊演算法,然後對結果應用 RIPEMD-160 演算法,將整個腳本表示為 20 位元組密碼雜湊。例如,從 Mohammed 的贖回腳本的雜湊開始:
54c557e07dde5bb6cb791c7a540e0a4796f5e97e
P2SH 交易使用特殊的輸出腳本範本將輸出鎖定到此雜湊而不是較長的贖回腳本:
OP_HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e OP_EQUAL
如您所見,這要短得多。P2SH 等效交易不是「支付給這個 5 金鑰多重簽章腳本」,而是「支付給具有此雜湊的腳本」。向 Mohammed 公司付款的客戶只需在其支付中包含此短得多的輸出腳本。當 Mohammed 和他的合夥人想要花費這個 UTXO 時,他們必須呈現原始贖回腳本(其雜湊鎖定了 UTXO)和解鎖它所需的簽章,如下所示:
<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>
兩個腳本分兩個階段組合。首先,檢查贖回腳本是否與輸出腳本相符以確保雜湊匹配:
<2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG> OP_HASH160 <script hash> OP_EQUAL
如果贖回腳本雜湊匹配,則執行贖回腳本:
<Sig1> <Sig2> 2 <PK1> <PK2> <PK3> <PK4> <PK5> 5 OP_CHECKMULTISIG
P2SH 地址
P2SH 功能的另一個重要部分是能夠將腳本雜湊編碼為地址,如 BIP13 中所定義。P2SH 地址是腳本 20 位元組雜湊的 base58check 編碼,就像 Bitcoin 地址是公鑰 20 位元組雜湊的 base58check 編碼一樣。P2SH 地址使用版本前綴「5」,這導致 base58check 編碼的地址以「3」開頭。
例如,Mohammed 的複雜腳本,經過雜湊處理並作為 P2SH 地址進行 base58check 編碼,變為 39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw。
現在,Mohammed 可以將此「地址」提供給他的客戶,他們可以使用幾乎任何 Bitcoin 錢包進行簡單支付,就像任何其他 Bitcoin 地址一樣。前綴 3 給他們一個提示,這是一種特殊類型的地址,對應於腳本而不是公鑰,但除此之外,它的工作方式與支付給任何其他 Bitcoin 地址完全相同。
P2SH 地址隱藏了所有複雜性,因此支付的人看不到腳本。
P2SH 的好處
與在輸出中直接使用複雜腳本相比,P2SH 功能提供以下好處:
- 
與原始傳統地址的相似性意味著發送者和發送者的錢包不需要複雜的工程來實作 P2SH。 
- 
P2SH 將長腳本的資料儲存負擔從輸出(除了儲存在區塊鏈上之外還在 UTXO 集中)轉移到輸入(僅儲存在區塊鏈上)。 
- 
P2SH 將長腳本的資料儲存負擔從當前時間(支付)轉移到未來時間(當它被花費時)。 
- 
P2SH 將長腳本的交易手續費成本從發送者轉移到接收者,接收者必須包含長贖回腳本才能花費它。 
贖回腳本和驗證
您無法將 P2SH 放入 P2SH 贖回腳本中,因為 P2SH 規範不是遞迴的。此外,雖然在贖回腳本中包含 OP_RETURN(請參見 資料記錄輸出(OP_RETURN))在技術上是可能的,因為規則中沒有任何內容阻止您這樣做,但這沒有實際用途,因為在驗證期間執行 OP_RETURN 將導致交易被標記為無效。
請注意,因為贖回腳本直到您嘗試花費 P2SH 輸出時才呈現給網路,所以如果您使用無效贖回腳本的雜湊建立輸出,您將無法花費它。包含贖回腳本的花費交易將不被接受,因為它是無效的腳本。這會產生風險,因為您可以將 bitcoin 發送到以後無法花費的 P2SH 地址。
| P2SH 輸出腳本包含贖回腳本的雜湊,這不提供有關贖回腳本內容的線索。即使贖回腳本無效,P2SH 輸出也將被視為有效並被接受。您可能會意外地以以後無法花費的方式接收 bitcoin。 | 
資料記錄輸出(OP_RETURN)
Bitcoin的分散式和時間戳區塊鏈具有超越支付的潛在用途。許多開發人員嘗試使用交易腳本語言來利用系統的安全性和彈性,用於諸如數位公證服務等應用程式。將 Bitcoin 的腳本語言用於這些目的的早期嘗試涉及建立在區塊鏈上記錄資料的交易輸出;例如,以這樣的方式記錄對文件的承諾,即任何人都可以透過引用該交易在特定日期建立該文件的存在證明。
使用 Bitcoin 的區塊鏈儲存與 Bitcoin 支付無關的資料是一個有爭議的話題。許多人認為這種使用是濫用,並希望阻止它。其他人將其視為區塊鏈技術強大功能的展示,並希望鼓勵這種實驗。那些反對包含非支付資料的人認為,它給執行完整 Bitcoin 節點的人帶來了負擔,他們需要承擔儲存區塊鏈本不打算攜帶的資料的磁碟儲存成本。此外,此類交易可能會建立無法花費的 UTXO,使用傳統 Bitcoin 地址作為自由形式的 20 位元組欄位。因為地址用於資料,所以它不對應於私鑰,並且生成的 UTXO 永遠_無法_花費;這是一個假支付。因此,這些永遠無法花費的交易永遠不會從 UTXO 集中移除,並導致 UTXO 資料庫的大小永遠增加或「膨脹」。
達成了一項妥協,允許以 OP_RETURN 開頭的輸出腳本向交易輸出添加非支付資料。然而,與使用「假」UTXO 不同,OP_RETURN 運算子建立一個明確的_可證明不可花費_的輸出,該輸出不需要儲存在 UTXO 集中。OP_RETURN 輸出記錄在區塊鏈上,因此它們消耗磁碟空間並有助於區塊鏈大小的增加,但它們不儲存在 UTXO 集中,因此不會使完整節點承擔更昂貴的資料庫操作成本而膨脹。
OP_RETURN 腳本如下所示:
OP_RETURN <data>
資料部分通常表示雜湊,例如來自 SHA256 演算法的輸出(32 位元組)。一些應用程式在資料前面放置一個前綴以幫助識別應用程式。例如,https://proofofexistence.com[Proof of Existence] 數位公證服務使用 8 位元組前綴 DOCPROOF,在十六進位中編碼為 ASCII 為 44 4f 43 50 52 4f 4f 46。
請記住,沒有與 OP_RETURN 相對應的輸入腳本可以用來「花費」OP_RETURN 輸出。OP_RETURN 輸出的全部意義在於您不能花費鎖定在該輸出中的錢,因此它不需要作為潛在可花費的保存在 UTXO 集中:OP_RETURN 輸出是_可證明不可花費_的。OP_RETURN 輸出通常具有零金額,因為分配給此類輸出的任何 bitcoin 實際上永遠丟失。如果 OP_RETURN 輸出在交易中作為輸入引用,則腳本驗證引擎將停止驗證腳本的執行並將交易標記為無效。執行 OP_RETURN 本質上會導致腳本以 FALSE 「RETURN」並停止。因此,如果您意外地在交易中引用 OP_RETURN 輸出作為輸入,則該交易無效。
交易鎖定時間限制
使用鎖定時間允許花費者限制交易直到特定區塊高度才能包含在區塊中,但它不會阻止在此之前在另一筆交易中花費資金。讓我們用以下範例解釋一下。
Alice 簽署一筆交易,將她的一個輸出花費到 Bob 的地址,並將交易鎖定時間設置為未來 3 個月。Alice 將該交易發送給 Bob 保留。透過這筆交易,Alice 和 Bob 知道:
- 
Bob 在經過 3 個月之前無法傳輸交易以贖回資金。 
- 
Bob 可以在 3 個月後傳輸交易。 
然而:
- 
Alice 可以建立衝突交易,花費相同的輸入而沒有鎖定時間。因此,Alice 可以在經過 3 個月之前花費相同的 UTXO。 
- 
Bob 無法保證 Alice 不會這樣做。 
重要的是要理解交易鎖定時間的限制。唯一的保證是 Bob 在經過 3 個月之前無法贖回預簽交易。無法保證 Bob 會收到資金。保證 Bob 會收到資金但在經過 3 個月之前無法花費它們的一種方法是將時間鎖限制作為腳本的一部分放在 UTXO 本身上,而不是放在交易上。這是透過下一種形式的時間鎖來實現的,稱為檢查鎖定時間驗證。
檢查鎖定時間驗證(OP_CLTV)
2015年 12 月,一種新形式的時間鎖作為軟分叉升級引入 Bitcoin。基於 BIP65 中的規範,一個稱為 OP_CHECKLOCKTIMEVERIFY(OP_CLTV)的新腳本運算子被添加到腳本語言中。OP_CLTV 是每個輸出的時間鎖,而不是像鎖定時間那樣的每個交易的時間鎖。這允許在應用時間鎖的方式上具有額外的靈活性。
簡單來說,透過在輸出中承諾 OP_CLTV 操作碼,該輸出受到限制,只能在經過指定時間後才能花費。
OP_CLTV 不會取代鎖定時間,而是限制特定 UTXO,使它們只能在將鎖定時間設置為大於或等於該值的未來交易中花費。
OP_CLTV 操作碼將一個參數作為輸入,以與鎖定時間相同的格式表示為數字(區塊高度或 Unix 紀元時間)。如 VERIFY 後綴所示,OP_CLTV 是一種操作碼類型,如果結果為 FALSE,則會停止腳本的執行。如果結果為 TRUE,則繼續執行。
為了使用 OP_CLTV,您將其插入到建立輸出的交易的輸出贖回腳本中。例如,如果 Alice 正在支付給 Bob,他通常可能接受對以下 P2SH 腳本的支付:
<Bob's public key> OP_CHECKSIG
要將其鎖定到時間,例如從現在起 3 個月,他的 P2SH 腳本將改為:
<Bob's pubkey> OP_CHECKSIGVERIFY <now + 3 months> OP_CHECKLOCKTIMEVERIFY
其中 <now {plus} 3 months> 是從交易被挖掘時起估計 3 個月的區塊高度或時間值:當前區塊高度 + 12,960(區塊)或當前 Unix 紀元時間 + 7,760,000(秒)。
當 Bob 嘗試花費這個 UTXO 時,他建立一筆引用該 UTXO 作為輸入的交易。他在該輸入的輸入腳本中使用他的簽章和公鑰,並將交易鎖定時間設置為等於或大於 Alice 在 OP_CHECKLOCKTIMEVERIFY 中設置的時間鎖。然後 Bob 在 Bitcoin 網路上廣播交易。
Bob 的交易評估如下。如果 Alice 設置的 OP_CHECKLOCKTIMEVERIFY 參數小於或等於花費交易的鎖定時間,則腳本執行繼續(就像執行了_無操作_或 OP_NOP 操作碼一樣)。否則,腳本執行停止,交易被視為無效。
更準確地說,BIP65 解釋說,如果發生以下情況之一,OP_CHECKLOCKTIMEVERIFY 會失敗並停止執行:
- 
堆疊為空。 
- 
堆疊頂部項目小於 0。 
- 
頂部堆疊項目的鎖定時間類型(高度與時間戳)和鎖定時間欄位不相同。 
- 
頂部堆疊項目大於交易的鎖定時間欄位。 
- 
輸入的序列欄位為 0xffffffff。 
執行後,如果 OP_CLTV 得到滿足,則前面的參數仍然是堆疊頂部的項目,可能需要使用 OP_DROP 刪除,以便正確執行後續腳本操作碼。因此,您經常會在腳本中看到 OP_CHECKLOCKTIMEVERIFY 後跟 OP_DROP。OP_CLTV,像 OP_CSV(請參見 相對時間鎖)一樣,與其他 CHECKVERIFY 操作碼不同,會在堆疊上留下項目,因為添加它們的軟分叉重新定義了不刪除堆疊項目的現有操作碼,並且必須保留這些先前無操作(NOP)操作碼的行為。
透過將鎖定時間與 OP_CLTV 結合使用,交易鎖定時間限制 中描述的場景會改變。Alice 立即發送她的交易,將資金分配給 Bob 的金鑰。Alice 不能再花費這筆錢,但 Bob 在 3 個月鎖定時間到期之前也不能花費它。
透過將時間鎖功能直接引入腳本語言,OP_CLTV 允許我們開發一些非常有趣的複雜腳本。
該標準在 BIP65(OP_CHECKLOCKTIMEVERIFY)中定義。
相對時間鎖
鎖定時間和 OP_CLTV 都是_絕對時間鎖_,因為它們指定絕對時間點。我們將檢查的下兩個時間鎖功能是_相對時間鎖_,因為它們指定,作為花費輸出的條件,從區塊鏈中輸出確認開始經過的時間。
相對時間鎖很有用,因為它們允許對一個交易施加時間約束,該約束取決於從先前交易確認開始經過的時間。換句話說,時鐘在 UTXO 記錄在區塊鏈上之前不會開始計數。這個功能在雙向狀態通道和閃電網路(LN)中特別有用,我們將在 支付通道和狀態通道 中看到。
相對時間鎖,像絕對時間鎖一樣,是透過交易級功能和腳本級操作碼實作的。交易級相對時間鎖作為對 sequence 值的共識規則實作,sequence 是在每個交易輸入中設置的交易欄位。腳本級相對時間鎖使用 OP_CHECKSEQUENCEVERIFY(OP_CSV)操作碼實作。
相對時間鎖根據 BIP68,使用共識強制執行序列號的相對鎖定時間和 BIP112,OP_CHECKSEQUENCEVERIFY中的規範實作。
BIP68 和 BIP112 於 2016 年 5 月作為對共識規則的軟分叉升級啟動。
使用 OP_CSV 的相對時間鎖
就像 OP_CLTV 和鎖定時間一樣,有一個相對時間鎖的腳本操作碼,它利用腳本中的序列值。該操作碼是 OP_CHECKSEQUENCEVERIFY,通常簡稱為 OP_CSV。
在 UTXO 的腳本中評估 OP_CSV 操作碼時,只允許在輸入序列值大於或等於 OP_CSV 參數的交易中花費。本質上,這限制了 UTXO 的花費,直到相對於 UTXO 被挖掘的時間經過了一定數量的區塊或秒。
與 CLTV 一樣,OP_CSV 中的值必須與相應序列值中的格式匹配。如果 OP_CSV 以區塊指定,那麼序列也必須如此。如果 OP_CSV 以秒指定,那麼序列也必須如此。
| 執行多個 OP_CSV 操作碼的腳本必須僅使用相同的變體,即基於時間或基於高度。混合變體將產生永遠無法花費的無效腳本,與我們在 時間鎖衝突 中看到的 OP_CLTV 相同的問題。然而,OP_CSV 允許任何兩個有效輸入包含在同一交易中,因此 OP_CLTV 發生的跨輸入交互問題不會影響 OP_CSV。 | 
具有 OP_CSV 的相對時間鎖在建立和簽署但不傳播幾個(鏈式)交易時特別有用——也就是說,它們保持在區塊鏈之外(鏈外)。在父交易被傳播、挖掘並老化到相對時間鎖中指定的時間之前,子交易無法使用。此用例的一個應用在 支付通道和狀態通道 和 路由支付通道(閃電網路) 中顯示。
OP_CSV 在 BIP112,CHECKSEQUENCEVERIFY中詳細定義。
帶有流程控制的腳本(條件子句)
Bitcoin Script 更強大的功能之一是流程控制,也稱為條件子句。您可能熟悉各種程式語言中使用 IF...THEN...ELSE 結構的流程控制。Bitcoin 條件子句看起來有點不同,但本質上是相同的結構。
在基本層面上,Bitcoin 條件操作碼允許我們建立一個有兩種解鎖方式的腳本,具體取決於評估邏輯條件的 TRUE/FALSE 結果。例如,如果 x 為 TRUE,則執行的程式碼路徑為 A,ELSE 程式碼路徑為 B。
此外,Bitcoin 條件表達式可以無限期地「嵌套」,這意味著條件子句可以包含另一個子句,而另一個子句又包含另一個子句,依此類推。Bitcoin Script 流程控制可用於建立具有數百種可能執行路徑的非常複雜的腳本。嵌套沒有限制,但共識規則對腳本的最大大小(以位元組為單位)施加了限制。
Bitcoin 使用 OP_IF、OP_ELSE、OP_ENDIF 和 OP_NOTIF 操作碼實作流程控制。此外,條件表達式可以包含布林運算子,例如 OP_BOOLAND、OP_BOOLOR 和 OP_NOT。
乍一看,您可能會發現 Bitcoin 的流程控制腳本令人困惑。這是因為 Bitcoin Script 是一種堆疊語言。就像 1 {plus} 1 在表示為 1 1 OP_ADD 時看起來「倒退」一樣,Bitcoin 中的流程控制子句也看起來「倒退」。
在大多數傳統(程序)程式語言中,流程控制如下所示:
if (condition): code to run when condition is true else: code to run when condition is false endif code to run in either case
在像 Bitcoin Script 這樣的基於堆疊的語言中,邏輯條件在 IF 之前,這使得它看起來「倒退」:
condition IF code to run when condition is true OP_ELSE code to run when condition is false OP_ENDIF code to run in either case
在閱讀 Bitcoin Script 時,請記住正在評估的條件在 IF 操作碼_之前_。
使用 VERIFY 操作碼的條件子句
Bitcoin Script中條件的另一種形式是以 VERIFY 結尾的任何操作碼。VERIFY 後綴意味著如果評估的條件不是 TRUE,腳本的執行立即終止,交易被視為無效。
與提供替代執行路徑的 IF 子句不同,VERIFY 後綴充當_保護子句_,僅在滿足先決條件時繼續。
例如,以下腳本需要 Bob 的簽章和產生特定雜湊的原像(秘密)。兩個條件都必須滿足才能解鎖:
OP_HASH160 <expected hash> OP_EQUALVERIFY <Bob's Pubkey> OP_CHECKSIG
要花費這個,Bob 必須提供有效的原像和簽章:
<Bob's Sig> <hash pre-image>
如果不提供原像,Bob 無法到達腳本中檢查他簽章的部分。
可以使用 OP_IF 編寫此腳本:
OP_HASH160 <expected hash> OP_EQUAL OP_IF <Bob's Pubkey> OP_CHECKSIG OP_ENDIF
Bob 的認證資料相同:
<Bob's Sig> <hash pre-image>
帶有 OP_IF 的腳本與使用帶有 VERIFY 後綴的操作碼執行相同的操作;它們都作為保護子句運作。然而,VERIFY 結構更有效,使用的操作碼少兩個。
那麼,我們什麼時候使用 VERIFY,什麼時候使用 OP_IF?如果我們要做的就是附加先決條件(保護子句),那麼 VERIFY 更好。但是,如果我們想要多個執行路徑(流程控制),那麼我們需要 OP_IF...OP_ELSE 流程控制子句。
在腳本中使用流程控制
Bitcoin Script 中流程控制的一個非常常見的用途是建立一個提供多個執行路徑的腳本,每個路徑都是贖回 UTXO 的不同方式。
讓我們看一個簡單的範例,其中我們有兩個簽署者,Alice 和 Bob,其中任何一個都能贖回。使用多重簽章,這將表示為 1-of-2 多重簽章腳本。為了演示,我們將使用 OP_IF 子句執行相同的操作:
OP_IF <Alice's Pubkey> OP_ELSE <Bob's Pubkey> OP_ENDIF OP_CHECKSIG
查看此贖回腳本,您可能想知道:「條件在哪裡?IF 子句之前沒有任何內容!」
條件不是腳本的一部分。相反,條件將在花費時提供,允許 Alice 和 Bob「選擇」他們想要的執行路徑:
<Alice's Sig> OP_TRUE
末尾的 OP_TRUE 用作條件(TRUE),它將使 OP_IF 子句執行第一個贖回路徑。此條件將 Alice 擁有簽章的公鑰放在堆疊上。OP_TRUE 操作碼,也稱為 OP_1,將數字 1 放在堆疊上。
為了讓 Bob 贖回這個,他必須透過給出 FALSE 值來選擇 OP_IF 中的第二個執行路徑。OP_FALSE 操作碼,也稱為 OP_0,將空位元組陣列推送到堆疊:
<Bob's Sig> OP_FALSE
Bob 的輸入腳本導致 OP_IF 子句執行第二個(OP_ELSE)腳本,這需要 Bob 的簽章。
由於 OP_IF 子句可以嵌套,我們可以建立執行路徑的「迷宮」。輸入腳本可以提供選擇實際執行哪個執行路徑的「地圖」:
OP_IF
  subscript A
OP_ELSE
  OP_IF
    subscript B
  OP_ELSE
    subscript C
  OP_ENDIF
OP_ENDIF
在此場景中,有三個執行路徑(subscript A、subscript B 和 subscript C)。輸入腳本以 TRUE 或 FALSE 值的序列形式提供路徑。例如,要選擇路徑 subscript B,輸入腳本必須以 OP_1 OP_0(TRUE、FALSE)結尾。這些值將被推入堆疊,因此第二個值(FALSE)最終位於堆疊頂部。外部 OP_IF 子句彈出 FALSE 值並執行第一個 OP_ELSE 子句。然後 TRUE 值移動到堆疊頂部,並由內部(嵌套)OP_IF 評估,選擇 B 執行路徑。
使用此結構,我們可以建立具有數十或數百個執行路徑的贖回腳本,每個路徑都提供贖回 UTXO 的不同方式。要花費,我們建立一個輸入腳本,透過在每個流程控制點上將適當的 TRUE 和 FALSE 值放在堆疊上來導航執行路徑。
複雜腳本範例
在本節中,我們將本章中的許多概念組合到一個範例中。
Mohammed 是杜拜的公司老闆,經營進出口業務;他希望建立一個具有靈活規則的公司資本帳戶。他建立的方案根據時間鎖需要不同級別的授權。多重簽章方案的參與者是 Mohammed、他的兩位合夥人 Saeed 和 Zaira,以及他們的公司律師。三位合夥人根據多數規則做出決定,因此三個中的兩個必須同意。但是,如果他們的金鑰出現問題,他們希望他們的律師能夠使用三位合夥人簽章之一來收回資金。最後,如果所有合夥人一段時間都不可用或無行為能力,他們希望律師在獲得資本帳戶交易記錄的存取權限後能夠直接管理帳戶。
帶有時間鎖的可變多重簽章 是 Mohammed 設計的贖回腳本以實現此目的(已添加行號前綴)。
01 OP_IF 02 OP_IF 03 2 04 OP_ELSE 05 <30 天> OP_CHECKSEQUENCEVERIFY OP_DROP 06 <律師的公鑰> OP_CHECKSIGVERIFY 07 1 08 OP_ENDIF 09 <Mohammed 的公鑰> <Saeed 的公鑰> <Zaira 的公鑰> 3 OP_CHECKMULTISIG 10 OP_ELSE 11 <90 天> OP_CHECKSEQUENCEVERIFY OP_DROP 12 <律師的公鑰> OP_CHECKSIG 13 OP_ENDIF
Mohammed 的腳本使用巢狀的 OP_IF...OP_ELSE 流程控制子句實現了三個執行路徑。
在第一個執行路徑中,這個腳本作為一個簡單的 2-of-3 多重簽章運作,由三個合夥人控制。這個執行路徑由第 3 和 9 行組成。第 3 行將多重簽章的法定人數設定為 2(2-of-3)。可以透過在輸入腳本的末尾放置 OP_TRUE OP_TRUE 來選擇這個執行路徑:
OP_0 <Mohammed 的簽章> <Zaira 的簽章> OP_TRUE OP_TRUE
| 這個輸入腳本開頭的 OP_0 是因為 OP_CHECKMULTISIG 的一個怪異之處,它會從堆疊中彈出一個額外的值。OP_CHECKMULTISIG 會忽略這個額外的值,但它必須存在,否則腳本會失敗。使用 OP_0 推送一個空位元組陣列是解決這個怪異之處的變通方法,如 CHECKMULTISIG 執行中的一個怪異之處 所述。 | 
第二個執行路徑只能在 UTXO 創建後 30 天後使用。在那時,它需要律師的簽章和三個合夥人中的一個的簽章(1-of-3 多重簽章)。這是透過第 7 行實現的,它將多重簽章的法定人數設定為 1。要選擇這個執行路徑,輸入腳本應該以 OP_FALSE OP_TRUE 結尾:
OP_0 <Saeed 的簽章> <律師的簽章> OP_FALSE OP_TRUE
| 為什麼是 OP_FALSE OP_TRUE?這不是反過來了嗎?FALSE 被推送到堆疊上,然後 TRUE 被推送到它的頂部。因此,TRUE 會被第一個 OP_IF 操作碼_首先_彈出。 | 
最後,第三個執行路徑允許律師單獨支付資金,但只能在 90 天後。要選擇這個執行路徑,輸入腳本必須以 OP_FALSE 結尾:
<律師的簽章> OP_FALSE
試著在紙上執行這個腳本,看看它在堆疊上的行為。
隔離見證輸出和交易範例
讓我們來看看一些範例交易,看看它們在隔離見證中會如何變化。我們首先來看看 P2PKH 支付如何作為隔離見證程式實現。然後,我們將看看 P2SH 腳本的隔離見證等價物。最後,我們將看看如何將前面的兩種隔離見證程式嵌入到 P2SH 腳本中。
支付到見證公鑰雜湊 (P2WPKH)
讓我們從看一個 P2PKH 輸出腳本的範例開始:
OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG
使用隔離見證,Alice 會創建一個 P2WPKH 腳本。如果該腳本承諾相同的公鑰,它會是這樣的:
0 ab68025513c3dbd2f7b92a94e0581f5d50f654e7
如您所見,P2WPKH 輸出腳本比 P2PKH 等價物簡單得多。它由推送到腳本評估堆疊的兩個值組成。對於舊的(不支援隔離見證的)Bitcoin 客戶端,這兩個推送看起來像是任何人都可以支付的輸出。對於較新的、支援隔離見證的客戶端,第一個數字(0)被解釋為版本號(見證版本),第二部分(20 位元組)是_見證程式_。20 位元組的見證程式就是公鑰的雜湊,就像在 P2PKH 腳本中一樣。
現在,讓我們看看 Bob 用來支付這個輸出的對應交易。對於原始腳本,支付交易需要在交易輸入中包含簽章:
[...] "vin" : [ "txid": "abcdef12345...", "vout": 0, "scriptSig": "<Bob 的 scriptSig>", ] [...]
然而,要支付 P2WPKH 輸出,交易在該輸入上沒有簽章。相反,Bob 的交易有一個空的輸入腳本,並包含一個見證結構:
[...] "vin" : [ "txid": "abcdef12345...", "vout": 0, "scriptSig": "", ] [...] "witness": "<Bob 的見證結構>" [...]
錢包構建 P2WPKH
非常重要的是要注意,P2WPKH 見證程式應該只由接收者創建,而不應該由支付者從已知的公鑰、P2PKH 腳本或地址轉換而來。支付者無法知道接收者的錢包是否有能力構建隔離見證交易並支付 P2WPKH 輸出。
此外,P2WPKH 輸出必須從_壓縮_公鑰的雜湊構建。未壓縮的公鑰在隔離見證中是非標準的,可能會被未來的軟分叉明確禁用。如果 P2WPKH 中使用的雜湊來自未壓縮的公鑰,它可能是無法支付的,您可能會損失資金。P2WPKH 輸出應該由收款人的錢包透過從其私鑰衍生壓縮公鑰來創建。
| P2WPKH 應該由接收者透過將壓縮公鑰轉換為 P2WPKH 雜湊來構建。無論是支付者還是其他任何人都不應該將 P2PKH 腳本、Bitcoin 地址或未壓縮的公鑰轉換為 P2WPKH 見證腳本。一般來說,支付者應該只以接收者指示的方式向接收者發送。 | 
支付到見證腳本雜湊 (P2WSH)
第二種類型的隔離見證 v0 見證程式對應於 P2SH 腳本。我們在 支付到腳本雜湊 中看到了這種類型的腳本。在那個範例中,P2SH 被 Mohammed 的公司用來表達多重簽章腳本。支付給 Mohammed 公司的款項使用這樣的腳本編碼:
OP_HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e OP_EQUAL
這個 P2SH 腳本引用了一個_贖回腳本_的雜湊,該贖回腳本定義了一個 2-of-3 多重簽章要求來支付資金。要支付這個輸出,Mohammed 的公司會在交易輸入中提供贖回腳本(其雜湊與 P2SH 輸出中的腳本雜湊匹配)以及滿足該贖回腳本所需的簽章:
[...] "vin" : [ "txid": "abcdef12345...", "vout": 0, "scriptSig": "<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 OP_CHECKMULTISIG>", ]
現在,讓我們看看如何將整個範例升級到隔離見證 v0。如果 Mohammed 的客戶使用相容於隔離見證的錢包,他們會進行支付,創建一個 P2WSH 輸出,看起來像這樣:
0 a9b7b38d972cabc7961dbfbcb841ad4508d133c47ba87457b4a0e8aae86dbb89
再次,就像 P2WPKH 的範例一樣,您可以看到隔離見證等價腳本簡單得多,並減少了您在 P2SH 腳本中看到的模板開銷。相反,隔離見證輸出腳本由推送到堆疊的兩個值組成:見證版本(0)和 32 位元組的見證腳本的 SHA256 雜湊(見證程式)。
| 雖然 P2SH 使用 20 位元組的 RIPEMD160(SHA256(script)) 雜湊,但 P2WSH 見證程式使用 32 位元組的 SHA256(script) 雜湊。雜湊演算法選擇的這種差異是刻意的,目的是為 P2WSH 在某些使用情況下提供更強的安全性(P2WSH 中的 128 位元安全性對比 P2SH 中的 80 位元安全性)。詳情請參見 P2SH 碰撞攻擊。 | 
Mohammed 的公司可以透過提供正確的見證腳本和足夠的簽章來滿足它,從而支付 P2WSH 輸出。見證腳本和簽章將作為見證結構的一部分包含。由於這是一個原生見證程式,它不使用傳統的輸入腳本欄位,因此輸入腳本中不會放置任何資料:
[...] "vin" : [ "txid": "abcdef12345...", "vout": 0, "scriptSig": "", ] [...] "witness": "<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 OP_CHECKMULTISIG>" [...]
區分 P2WPKH 和 P2WSH
在前面的兩節中,我們展示了兩種類型的見證程式:支付到見證公鑰雜湊 (P2WPKH) 和 支付到見證腳本雜湊 (P2WSH)。這兩種類型的見證程式都由相同的版本號後跟一個資料推送組成。它們看起來非常相似,但被解釋得非常不同:一種被解釋為公鑰雜湊,由簽章滿足;另一種被解釋為腳本雜湊,由見證腳本滿足。它們之間的關鍵區別是見證程式的長度:
- 
P2WPKH 中的見證程式是 20 位元組。 
- 
P2WSH 中的見證程式是 32 位元組。 
這是允許完整節點區分這兩種類型的見證程式的唯一區別。透過查看雜湊的長度,節點可以確定它是什麼類型的見證程式,P2WPKH 還是 P2WSH。
升級到隔離見證
正如我們從前面的範例中看到的,升級到隔離見證是一個兩步驟的過程。首先,錢包必須創建隔離見證類型的輸出。然後,這些輸出可以由知道如何構建隔離見證交易的錢包支付。在範例中,Alice 的錢包能夠創建支付隔離見證輸出腳本的輸出。Bob 的錢包也支援隔離見證,並且能夠支付這些輸出。
隔離見證被實現為向後相容的升級,舊客戶端和新客戶端可以共存。錢包開發者獨立地升級錢包軟體以添加隔離見證功能。傳統的 P2PKH 和 P2SH 繼續適用於[.keep-together]#未升級#的錢包。這留下了兩個重要的場景,將在下一節中討論:
- 
不支援隔離見證的支付者錢包向可以處理隔離見證交易的接收者錢包進行支付的能力。 
- 
支援隔離見證的支付者錢包透過_地址_識別和區分支援隔離見證的接收者和不支援的接收者的能力。 
在 P2SH 內嵌入隔離見證
讓我們假設,例如,Alice 的錢包沒有升級到隔離見證,但 Bob 的錢包已升級並可以處理隔離見證交易。Alice 和 Bob 可以使用傳統的非隔離見證輸出。但 Bob 可能希望使用隔離見證來減少交易手續費,利用見證結構的降低成本。
在這種情況下,Bob 的錢包可以構建一個 P2SH 地址,其中包含一個隔離見證腳本。Alice 的錢包可以在不了解隔離見證的情況下向其支付。Bob 的錢包然後可以使用隔離見證交易支付這筆款項,利用隔離見證並減少交易手續費。
兩種形式的見證腳本,P2WPKH 和 P2WSH,都可以嵌入到 P2SH 地址中。第一種被稱為巢狀 P2WPKH,第二種被稱為巢狀 P2WSH。
巢狀的支付到見證公鑰雜湊
我們將檢查的第一種形式的輸出腳本是巢狀 P2WPKH。這是一個支付到見證公鑰雜湊見證程式,嵌入到支付到腳本雜湊腳本中,這樣不支援隔離見證的錢包就可以支付輸出腳本。
Bob 的錢包使用 Bob 的公鑰構建一個 P2WPKH 見證程式。然後對這個見證程式進行雜湊,並將結果雜湊編碼為 P2SH 腳本。P2SH 腳本被轉換為 Bitcoin 地址,一個以"3"開頭的地址,如我們在 支付到腳本雜湊 中看到的。
Bob 的錢包從我們之前看到的 P2WPKH 見證版本和見證程式開始:
0 ab68025513c3dbd2f7b92a94e0581f5d50f654e7
資料由見證版本和 Bob 的 20 位元組公鑰雜湊組成。
然後 Bob 的錢包對資料進行雜湊,首先使用 SHA256,然後使用 RIPEMD-160,產生另一個 20 位元組雜湊。接下來,贖回腳本雜湊被轉換為 Bitcoin 地址。最後,Alice 的錢包可以向 37Lx99uaGn5avKBxiW26HjedQE3LrDCZru 支付,就像它會向任何其他 Bitcoin 地址支付一樣。
要支付 Bob,Alice 的錢包會使用 P2SH 腳本鎖定輸出:
OP_HASH160 3e0547268b3b19288b3adef9719ec8659f4b2b0b OP_EQUAL
即使 Alice 的錢包不支援隔離見證,它創建的支付也可以由 Bob 使用隔離見證交易支付。
巢狀的支付到見證腳本雜湊
類似地,用於多重簽章腳本或其他複雜腳本的 P2WSH 見證程式可以嵌入到 P2SH 腳本和地址中,使任何錢包都可以進行相容於隔離見證的支付。
正如我們在 支付到見證腳本雜湊 (P2WSH) 中看到的,Mohammed 的公司正在使用隔離見證支付多重簽章腳本。為了讓任何客戶都可以向他的公司支付,無論他們的錢包是否升級到隔離見證,Mohammed 的錢包可以將 P2WSH 見證程式嵌入到 P2SH 腳本中。
首先,Mohammed 的錢包使用 SHA256(僅一次)對見證腳本進行雜湊,產生雜湊:
9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73
接下來,雜湊的見證腳本被轉換為帶有版本前綴的 P2WSH 見證程式:
0 9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73
然後,見證程式本身使用 SHA256 和 RIPEMD-160 進行雜湊,產生一個新的 20 位元組雜湊:
86762607e8fe87c0c37740cddee880988b9455b2
接下來,錢包從這個雜湊構建一個 P2SH Bitcoin 地址:
3Dwz1MXhM6EfFoJChHCxh1jWHb8GQqRenG
現在,Mohammed 的客戶可以向這個地址支付,即使他們不支援隔離見證。要向 Mohammed 發送支付,錢包會使用以下 P2SH 腳本鎖定輸出:
OP_HASH160 86762607e8fe87c0c37740cddee880988b9455b2 OP_EQUAL
Mohammed 的公司然後可以構建隔離見證交易來支付這些款項,利用隔離見證特性,包括較低的交易手續費。
默克爾化替代腳本樹 (MAST)
使用 OP_IF,您可以授權多個不同的支付條件,但這種方法有幾個不理想的方面:
- 重量(成本)
- 
您添加的每個條件都會增加腳本的大小,增加交易的重量以及需要支付的手續費,以便支付受該腳本保護的比特幣。 
- 有限大小
- 
即使您願意為額外的條件付費,您可以放入腳本中的最大數量也是有限的。例如,傳統腳本限制為 10,000 位元組,實際上最多只能限制您使用幾百個條件分支。即使您可以創建與整個區塊一樣大的腳本,它也只能包含約 20,000 個有用的分支。對於簡單的支付來說這是很多,但與 Bitcoin 的一些想像用途相比則微不足道。 
- 缺乏隱私
- 
您添加到腳本中的每個條件在您支付受該腳本保護的比特幣時都會成為公開知識。例如,每當有人從 帶有時間鎖的可變多重簽章 中花費時,Mohammed 的律師和業務合夥人將能夠看到整個腳本。這意味著他們的律師,即使不需要他簽署,也將能夠追蹤他們的所有交易。 
然而,Bitcoin 已經使用了一種稱為默克爾樹的資料結構,它允許驗證元素是集合的成員,而無需識別集合的每個其他成員。
我們將在 默克爾樹 中更多地了解默克爾樹,但基本資訊是,我們想要的資料集的成員(例如,任何長度的授權條件)可以傳遞到雜湊函數中以創建一個短的承諾(稱為默克爾樹的_葉子_)。然後將這些葉子中的每一個與另一個葉子配對並再次雜湊,創建對葉子的承諾,稱為_分支_承諾。可以以相同的方式創建對一對分支的承諾。對分支重複此步驟,直到只剩下一個識別符,稱為_默克爾根_。使用我們來自 帶有時間鎖的可變多重簽章 的範例腳本,我們在 具有三個子腳本的 MAST。 中為三個授權條件中的每一個構建一個默克爾樹。
 
我們現在可以創建一個緊湊的成員資格證明,證明特定的授權條件是默克爾樹的成員,而無需披露默克爾樹的任何其他成員的任何詳細資訊。請參見 其中一個子腳本的 MAST 成員資格證明。,並注意陰影節點可以從使用者提供的其他資料計算出來,因此不需要在支付時指定。
 
用於創建承諾的雜湊摘要每個為 32 位元組,因此證明 其中一個子腳本的 MAST 成員資格證明。 的支付已獲得授權(使用默克爾樹和特定條件)並經過認證(使用簽章)使用 383 位元組。相比之下,沒有默克爾樹的相同支付(即提供所有可能的授權條件)使用 412 位元組。
在此範例中節省 29 位元組(7%)並不能完全捕捉潛在的節省。默克爾樹的二元樹性質意味著您每次將集合中的成員數量(在這種情況下為授權條件)加倍時,只需要額外的 32 位元組承諾。在這種情況下,有三個條件,我們需要使用三個承諾(其中一個是默克爾根,需要包含在授權資料中);我們也可以有四個承諾以相同的成本。額外的承諾將給我們最多八個條件。僅使用 16 個承諾(512 位元組的承諾),我們就可以擁有超過 32,000 個授權條件,遠遠超過可以在充滿 OP_IF 語句的整個交易區塊中有效使用的數量。使用 128 個承諾(4,096 位元組),我們理論上可以創建的條件數量遠遠超過世界上所有計算機可以創建的條件數量。
通常情況下,並非每個授權條件都同樣可能被使用。在我們的範例案例中,我們預計 Mohammed 和他的合夥人會經常支付他們的錢;時間延遲條件僅在出現問題時才存在。我們可以使用這些知識重組我們的樹,如 將最可能的腳本放在最佳位置的 MAST。 所示。
 
現在我們只需要為常見情況提供兩個承諾(節省 32 位元組),儘管我們對於不太常見的情況仍然需要三個承諾。如果您知道(或可以猜測)使用不同授權條件的機率,您可以使用 Huffman 演算法將它們放入最有效的樹中;詳情請參見 BIP341。
無論樹如何構建,我們都可以在前面的範例中看到,我們只揭示實際使用的授權條件。其他條件保持私密。條件的數量也保持私密:樹可以有一個條件或一萬億個條件——對於僅查看單個交易的鏈上資料的人來說,沒有辦法分辨。
除了略微增加 Bitcoin 的複雜性之外,MAST 對 Bitcoin 沒有顯著的缺點,在發現改進方法(我們將在 Taproot 中看到)之前,有兩個可靠的提案,BIP114 和 BIP116。
支付到合約 (P2C)
正如我們在 公共子金鑰衍生 中看到的,橢圓曲線密碼學(ECC)的數學允許 Alice 使用私鑰衍生她給 Bob 的公鑰。他可以將任意值添加到該公鑰以創建衍生公鑰。如果他將該任意值給 Alice,她可以將其添加到她的私鑰以衍生衍生公鑰的私鑰。簡而言之,Bob 可以創建子公鑰,只有 Alice 可以創建相應的私鑰。這對於 BIP32 風格的階層確定性(HD)錢包恢復很有用,但它也可以服務於另一個用途。
讓我們想像 Bob 想從 Alice 那裡購買東西,但他也想能夠在以後證明他支付了什麼,以防有任何爭議。Alice 和 Bob 就正在出售的商品或服務的名稱達成一致(例如,「Alice 的播客劇集 #123」),並透過雜湊並將雜湊摘要解釋為數字來將該描述轉換為數字。Bob 將該數字添加到 Alice 的公鑰並支付它。這個過程稱為_金鑰調整_,該數字稱為_調整_。
Alice 可以透過使用相同的數字(調整)調整她的私鑰來花費資金。
稍後,Bob 可以透過揭示她的基礎金鑰和他們使用的描述來向任何人證明他支付給 Alice 的內容。任何人都可以驗證支付的公鑰是否等於基礎金鑰加上對描述的雜湊承諾。如果 Alice 承認該金鑰是她的,那麼她收到了付款。如果 Alice 花費了資金,這進一步證明她在簽署支付交易時知道描述,因為只有在她知道調整(描述)的情況下,她才能為調整後的公鑰創建有效的簽章。
如果 Alice 和 Bob 都決定不公開揭示他們使用的描述,他們之間的支付看起來就像任何其他支付一樣。沒有隱私損失。
因為 P2C 預設是私密的,我們無法知道它用於其原始目的的頻率——理論上每筆支付都可以使用它,儘管我們認為這不太可能。然而,P2C 今天以稍微不同的形式被廣泛使用,我們將在 Taproot 中看到。
無腳本多重簽章和閾值簽章
在 腳本化多重簽章 中,我們查看了需要來自多個金鑰的簽章的腳本。然而,還有另一種方法需要多個金鑰的合作,這也令人困惑地稱為_多重簽章_。為了在本節中區分這兩種類型,我們將涉及 OP_CHECKSIG 風格操作碼的版本稱為_腳本多重簽章_,另一個版本稱為_無腳本多重簽章_。
無腳本多重簽章涉及每個參與者以與創建私鑰相同的方式創建自己的秘密。我們將這個秘密稱為_部分私鑰_,儘管我們應該注意它與常規完整私鑰的長度相同。從部分私鑰,每個參與者使用我們在 公鑰 中描述的用於常規公鑰的相同演算法衍生部分公鑰。每個參與者與所有其他參與者共享他們的部分公鑰,然後將所有金鑰組合在一起以創建無腳本多重簽章公鑰。
這個組合的公鑰看起來與任何其他 Bitcoin 公鑰相同。第三方無法區分多方公鑰和單個使用者生成的普通金鑰。
要花費受無腳本多重簽章公鑰保護的比特幣,每個參與者生成一個部分簽章。然後將部分簽章組合以創建常規完整簽章。有許多已知的創建和組合部分簽章的方法;我們將在 數位簽章 中更多地討論這個主題。與無腳本多重簽章的公鑰類似,此過程生成的簽章看起來與任何其他 Bitcoin 簽章相同。第三方無法確定簽章是由一個人創建的還是一百萬人相互合作創建的。
無腳本多重簽章比腳本多重簽章更小且更私密。對於腳本多重簽章,放置在交易中的位元組數會隨著涉及的每個金鑰和簽章而增加。對於無腳本多重簽章,大小是恆定的——一百萬個參與者每個都提供自己的部分金鑰和部分簽章,在交易中放入的資料量與使用單個金鑰和簽章的個人完全相同。隱私的情況也是如此:因為每個新金鑰或簽章都會向交易添加資料,腳本多重簽章會披露有關正在使用多少金鑰和簽章的資料——這可能使得很容易弄清楚哪些交易是由哪組參與者創建的。然而,因為每個無腳本多重簽章看起來都像其他無腳本多重簽章和每個單簽章一樣,所以不會洩漏減少隱私的資料。
無腳本多重簽章有兩個缺點。第一個是為 Bitcoin 創建它們的所有已知安全演算法需要比腳本多重簽章更多輪的互動或更仔細的狀態管理。這在簽章由幾乎無狀態的硬體簽署設備生成且金鑰在物理上分散的情況下可能具有挑戰性。例如,如果您將硬體簽署設備保存在銀行保險箱中,您需要訪問該箱子一次來創建腳本多重簽章,但可能需要兩次或三次來創建無腳本多重簽章。
另一個缺點是閾值簽署不會揭示誰簽署了。在腳本閾值簽署中,Alice、Bob 和 Carol 同意(例如)他們中的任何兩個簽署都足以花費資金。如果 Alice 和 Bob 簽署,這需要將他們每個人的簽章放在鏈上,向任何知道他們金鑰的人證明他們簽署了,而 Carol 沒有。在無腳本閾值簽署中,Alice 和 Bob 之間的簽章與 Alice 和 Carol 或 Bob 和 Carol 之間的簽章無法區分。這對隱私有利,但這意味著,即使 Carol 聲稱她沒有簽署,她也無法證明她沒有簽署,這對於問責制和可審計性可能是不利的。
對於許多使用者和用例,多重簽章始終減少的大小和增加的隱私超過了其偶爾創建和審計簽章的挑戰。
Taproot
人們選擇使用 Bitcoin 的一個原因是可以創建具有高度可預測結果的合約。由法院執行的法律合約部分取決於參與案件的法官和陪審員的決定。相比之下,Bitcoin 合約通常需要其參與者採取行動,但否則由數千個運行功能相同的程式碼的完整節點執行。當給予相同的合約和相同的輸入時,每個完整節點將始終產生相同的結果。任何偏差都意味著 Bitcoin 被破壞了。人類法官和陪審員可以比軟體靈活得多,但當不需要或不需要靈活性時,Bitcoin 合約的可預測性是一項重大資產。
如果合約中的所有參與者都認識到其結果已變得完全可預測,則實際上他們不需要繼續使用合約。他們可以做合約強迫他們做的任何事情,然後終止合約。在社會中,這是大多數合約終止的方式:如果相關方感到滿意,他們永遠不會將合約提交給法官或陪審員。在 Bitcoin 中,這意味著任何將使用大量區塊空間來結算的合約也應該提供一個允許透過相互滿意來結算的條款。
在 MAST 中,使用無腳本多重簽章,相互滿意條款很容易設計。我們只需將腳本樹的頂層葉子之一設為所有相關方之間的無腳本多重簽章。我們已經在 將最可能的腳本放在最佳位置的 MAST。 中看到了幾方之間具有簡單相互滿意條款的複雜合約。我們可以透過從腳本多重簽章切換到無腳本多重簽章來使其更加優化。
這是相當有效和私密的。如果使用相互滿意條款,我們只需要提供一個默克爾分支,我們揭示的只是涉及簽章(可能來自一個人,也可能來自數千個不同的參與者)。但開發人員在 2018 年意識到,如果我們也使用支付到合約,我們可以做得更好。
在我們之前在 支付到合約 (P2C) 中對支付到合約的描述中,我們調整了公鑰以承諾 Alice 和 Bob 之間協議的文本。我們可以透過承諾 MAST 的根來承諾合約的程式碼。我們調整的公鑰是常規 Bitcoin 公鑰,這意味著它可以需要來自單個人的簽章,或者它可以需要來自多個人的簽章(或者它可以以特殊方式創建以使為其生成簽章變得不可能)。這意味著我們可以透過來自所有相關方的單個簽章或透過揭示我們想要使用的 MAST 分支來滿足合約。涉及公鑰和 MAST 的承諾樹在 公鑰承諾默克爾根的 taproot。 中顯示。
 
這使得使用多重簽章的相互滿意條款非常高效且非常私密。它甚至比看起來更私密,因為單個使用者想要透過單個簽章(或由他們控制的多個不同錢包生成的多重簽章)滿足它而創建的任何交易在鏈上看起來與相互滿意支付相同。在這種情況下,一百萬個使用者參與極其複雜的合約或單個使用者只是花費他們儲存的比特幣之間沒有鏈上差異。
當可以僅使用金鑰進行支付時,例如對於單個簽章或無腳本多重簽章,這被稱為_金鑰路徑支付_。當使用腳本樹時,這被稱為_腳本路徑支付_。對於金鑰路徑支付,放在鏈上的資料是公鑰(在見證程式中)和簽章(在見證堆疊上)。
對於腳本路徑支付,鏈上資料還包括公鑰,它被放置在見證程式中,在此上下文中稱為 taproot 輸出金鑰。見證結構包括以下資訊:
- 
版本號。 
- 
基礎金鑰——在被默克爾根調整以產生 taproot 輸出金鑰之前存在的金鑰。這個基礎金鑰稱為 taproot 內部金鑰。 
- 
要執行的腳本,稱為_葉腳本_。 
- 
沿著連接葉子與默克爾根的路徑的默克爾樹中每個交叉點的一個 32 位元組雜湊。 
- 
滿足腳本所需的任何資料(例如簽章或雜湊原像)。 
我們只知道 taproot 的一個重要描述的缺點:想要使用 MAST 但不想要相互滿意條款的合約的參與者必須在區塊鏈上包含 taproot 內部金鑰,增加約 33 位元組的開銷。鑑於幾乎所有合約都預期會從相互滿意條款或其他使用頂層公鑰的多重簽章條款中受益,並且所有使用者都從輸出看起來彼此相似的增加的匿名集中受益,大多數參與 taproot 啟動的使用者並不認為這種罕見的開銷重要。
對 taproot 的支援在 2021 年 11 月啟動的軟分叉中添加到 Bitcoin。
Tapscript
Taproot 啟用 MAST,但僅使用與以前使用的 Bitcoin Script 語言略有不同的版本,新版本稱為 tapscript。主要區別包括:
- 腳本多重簽章更改
- 
舊的 OP_CHECKMULTISIG 和 OP_CHECKMULTISIGVERIFY 操作碼被移除。這些操作碼與 taproot 軟分叉中的另一個更改(能夠使用批次驗證的 schnorr 簽章(請參見 Schnorr 簽章))不能很好地結合。相反提供了一個新的 OP_CHECKSIGADD 操作碼。當它成功驗證簽章時,這個新操作碼將計數器增加一,使得可以方便地計算有多少簽章通過,可以與所需的成功簽章數量進行比較,以重新實作與 OP_CHECKMULTISIG 相同的行為。 
- 對所有簽章的更改
- 
tapscript 中的所有簽章操作都使用 BIP340 中定義的 schnorr 簽章演算法。我們將在 數位簽章 中更多地探索 schnorr 簽章。 此外,任何預期不會成功的簽章檢查操作必須提供值 OP_FALSE(也稱為 OP_0)而不是實際簽章。向失敗的簽章檢查操作提供任何其他內容將導致整個腳本失敗。這也有助於支援 schnorr 簽章的批次驗證。 
- OP_SUCCESSx 操作碼
- 
在以前版本的 Script 中不可用的操作碼現在被重新定義為在使用時導致整個腳本成功。這允許未來的軟分叉將它們重新定義為在某些情況下不成功,這是一種限制,因此可以在軟分叉中完成。(相反,將不成功的操作定義為成功只能在硬分叉中完成,這是一個更具挑戰性的升級路徑。) 
儘管我們在本章中深入探討了授權和認證,但我們跳過了 Bitcoin 如何認證支付者的一個非常重要的部分:其簽章。我們將在 數位簽章 中看到這一點。
數位簽章
目前在 Bitcoin 中使用兩種簽章演算法,schnorr 簽章演算法_和_橢圓曲線數位簽章演算法(ECDSA)。這些演算法用於基於橢圓曲線私鑰/公鑰對的數位簽章,如 橢圓曲線密碼學解釋 中所述。它們用於支付隔離見證 v0 P2WPKH 輸出、隔離見證 v1 P2TR 金鑰路徑支付,以及腳本函數 OP_CHECKSIG、OP_CHECKSIGVERIFY、OP_CHECKMULTISIG、OP_CHECKMULTISIGVERIFY 和 OP_CHECKSIGADD。任何時候執行其中一個,都必須提供簽章。
數位簽章在 Bitcoin 中有三個目的。首先,簽章證明私鑰的控制者(暗示是資金的所有者)已經_授權_花費這些資金。其次,授權證明是_不可否認的_(不可抵賴性)。第三,授權的交易無法被未經授權的第三方更改——其_完整性_是完好的。
| 每個交易輸入及其可能包含的任何簽章都_完全_獨立於任何其他輸入或簽章。多方可以協作構建交易並且只簽署一個輸入。有幾種協議使用這個事實來創建多方交易以保護隱私。 | 
在本章中,我們將探討數位簽章如何工作,以及它們如何在不洩露私鑰的情況下提供私鑰控制的證明。
數位簽章如何工作
數位簽章由兩部分組成。第一部分是使用私鑰(簽署金鑰)為訊息(交易)創建簽章的演算法。第二部分是允許任何人驗證簽章的演算法,前提是還提供了訊息和相應的公鑰。
創建數位簽章
在 Bitcoin使用數位簽章演算法時,被簽署的「訊息」是交易,或更準確地說是交易中特定資料子集的雜湊,稱為_承諾雜湊_(請參見 簽章雜湊類型(SIGHASH))。簽署金鑰是使用者的私鑰。結果是簽章:
其中:
- 
x 是簽署私鑰 
- 
m 是要簽署的訊息,承諾雜湊(例如交易的部分) 
- 
Fhash 是雜湊函數 
- 
Fsig 是簽署演算法 
- 
Sig 是產生的簽章 
您可以在 Schnorr 簽章 和 ECDSA 簽章 中找到有關 schnorr 和 ECDSA 簽章數學的更多詳細資訊。
在 schnorr 和 ECDSA 簽章中,函數 Fsig 產生由兩個值組成的簽章 Sig。在不同的演算法中,這兩個值之間存在差異,我們稍後會探討。計算出這兩個值後,它們被序列化為位元組流。對於 ECDSA 簽章,編碼使用稱為_專用編碼規則_(Distinguished Encoding Rules)或 DER 的國際標準編碼方案。對於 schnorr 簽章,使用更簡單的序列化格式。
驗證簽章
簽章驗證演算法接受訊息(交易和相關資料的部分的雜湊)、簽署者的公鑰和簽章,如果簽章對此訊息和公鑰有效,則返回 TRUE。
要驗證簽章,必須擁有簽章、序列化的交易、有關被支付輸出的一些資料,以及與用於創建簽章的私鑰相對應的公鑰。本質上,簽章的驗證意味著「只有生成此公鑰的私鑰的控制者才能在此交易上產生此簽章」。
簽章雜湊類型(SIGHASH)
數位簽章適用於訊息,就 Bitcoin 而言,訊息就是交易本身。簽章證明簽署者對特定交易資料的_承諾_。在最簡單的形式中,簽章幾乎適用於整個交易,從而承諾所有輸入、輸出和其他交易欄位。但是,簽章可以只承諾交易中資料的子集,這對於許多場景都很有用,我們將在本節中看到。
Bitcoin 簽章有一種方法可以使用 SIGHASH 標誌來指示交易資料的哪一部分包含在由私鑰簽署的雜湊中。SIGHASH 標誌是附加到簽章的單個位元組。每個簽章都有明確或隱含的 SIGHASH 標誌,並且標誌可以因輸入而異。具有三個已簽署輸入的交易可能具有三個帶有不同 SIGHASH 標誌的簽章,每個簽章簽署(承諾)交易的不同部分。
請記住,每個輸入可能包含一個或多個簽章。因此,一個輸入可能具有帶有不同 SIGHASH 標誌的簽章,這些標誌承諾交易的不同部分。還請注意,Bitcoin 交易可能包含來自不同「所有者」的輸入,他們可能只在部分構建的交易中簽署一個輸入,與其他人協作收集所有必要的簽章以製作有效的交易。許多 SIGHASH 標誌類型只有在您考慮多個參與者在 Bitcoin 網路之外協作並更新部分簽署的交易時才有意義。
有三個 SIGHASH 標誌:ALL、NONE 和 SINGLE,如 [sighash_types_and_their] 所示。
| SIGHASH標誌 | 值 | 描述 | 
|---|---|---|
| 
 | 
 | 簽章適用於所有輸入和輸出 | 
| 
 | 
 | 簽章適用於所有輸入,但不適用於任何輸出 | 
| 
 | 
 | 簽章適用於所有輸入,但僅適用於與已簽署輸入具有相同索引號的一個輸出 | 
此外,還有一個修飾符標誌 SIGHASH_ANYONECANPAY,可以與前面的每個標誌組合。當設置 ANYONECANPAY 時,只簽署一個輸入,其餘的(及其序列號)保持開放以供修改。ANYONECANPAY 的值為 0x80,並透過按位 OR 應用,產生如 [sighash_types_with_modifiers] 所示的組合標誌。
| SIGHASH標誌 | 值 | 描述 | 
|---|---|---|
| 
 | 
 | 簽章適用於一個輸入和所有輸出 | 
| 
 | 
 | 簽章適用於一個輸入,不適用於任何輸出 | 
| 
 | 
 | 簽章適用於一個輸入和具有相同索引號的輸出 | 
SIGHASH 標誌在簽署和驗證期間的應用方式是製作交易的副本,並省略或截斷其中的某些欄位(設置為零長度並清空)。然後將結果交易序列化。SIGHASH 標誌包含在序列化的交易資料中,並對結果進行雜湊。雜湊摘要本身就是被簽署的「訊息」。根據使用哪個 SIGHASH 標誌,交易的不同部分會被包含。透過包含 SIGHASH 標誌本身,簽章也承諾 SIGHASH 類型,因此它不能被更改(例如,被礦工)。
在 ECDSA 簽章的序列化(DER) 中,我們將看到 DER 編碼簽章的最後部分是 01,這是 ECDSA 簽章的 SIGHASH_ALL 標誌。這會鎖定交易資料,因此 Alice 的簽章承諾所有輸入和輸出的狀態。這是最常見的簽章形式。
讓我們看一些其他 SIGHASH 類型以及它們在實踐中的使用方式:
- ALL|ANYONECANPAY
- 
這種結構可用於製作「群眾募資」風格的交易。試圖籌集資金的人可以構建一個帶有單個輸出的交易。單個輸出將「目標」金額支付給募資者。這樣的交易顯然是無效的,因為它沒有輸入。但是,其他人現在可以透過添加自己的輸入作為捐贈來修改它。他們使用 ALL|ANYONECANPAY 簽署自己的輸入。除非收集到足夠的輸入以達到輸出的價值,否則交易無效。每筆捐贈都是一個「承諾」,在籌集整個目標金額之前,募資者無法收取。不幸的是,這個協議可以被募資者透過添加自己的輸入(或從借錢給他們的人那裡)來規避,從而允許他們收取捐款,即使他們沒有達到指定的價值。 
- NONE
- 
這種結構可用於創建特定金額的「無記名支票」或「空白支票」。它承諾所有輸入但允許輸出被更改。任何人都可以將自己的 Bitcoin 地址寫入輸出腳本。就其本身而言,這允許任何礦工更改輸出目的地並為自己索取資金,但如果交易中的其他所需簽章使用 SIGHASH_ALL 或其他承諾輸出的類型,它允許那些支付者更改目的地而不允許任何第三方(如礦工)修改輸出。 
- NONE|ANYONECANPAY
- 
這種結構可用於建立「粉塵收集器」。錢包中擁有微小 UTXO 的使用者無法在不讓手續費成本超過 UTXO 價值的情況下花費這些 UTXO;請參見 不經濟的輸出和不允許的粉塵。使用這種類型的簽章,不經濟的 UTXO 可以被捐贈給任何人隨時聚合和花費。 
有一些修改或擴展 SIGHASH 系統的提案。截至本文撰寫時討論最廣泛的提案是BIP118,它提議添加兩個新的 sighash 標誌。使用 SIGHASH_ANYPREVOUT 的簽章不會承諾輸入的輸出點欄位,允許它用於花費特定見證程式的任何先前輸出。例如,如果 Alice 收到兩個相同金額到相同見證程式的輸出(例如,需要來自她錢包的單個簽章),則用於花費其中一個輸出的 SIGHASH_ANYPREVOUT 簽章可以被複製並用於將另一個輸出花費到相同的目的地。
使用 SIGHASH_ANYPREVOUTANYSCRIPT 的簽章不會承諾輸出點、金額、見證程式或 taproot 默克爾樹(腳本樹)中的特定葉子,允許它花費簽章可以滿足的任何先前輸出。例如,如果 Alice 收到兩個不同金額和不同見證程式的輸出(例如,一個需要單個簽章,另一個需要她的簽章加上一些其他資料),則用於花費其中一個輸出的 SIGHASH_ANYPREVOUTANYSCRIPT 簽章可以被複製並用於將另一個輸出花費到相同的目的地(假設第二個輸出的額外資料是已知的)。
兩個 SIGHASH_ANYPREVOUT 操作碼的主要預期用途是改進支付通道,例如閃電網路(LN)中使用的支付通道,儘管已經描述了其他幾種用途。
| 您不會經常看到 SIGHASH 標誌在使用者的錢包應用程式中作為選項呈現。簡單的錢包應用程式使用 SIGHASH_ALL 標誌簽署。更複雜的應用程式,例如 LN 節點,可能使用替代的 SIGHASH 標誌,但它們使用經過廣泛審查的協議來了解替代標誌的影響。 | 
Schnorr 簽章
在 1989 年,Claus Schnorr 發表了一篇論文,描述了以他命名的簽章演算法。該演算法並不特定於 Bitcoin 和許多其他應用程式使用的橢圓曲線密碼學(ECC),儘管它今天可能與 ECC 最密切相關。Schnorr 簽章具有許多優良特性:
- 可證明的安全性
- 
schnorr 簽章安全性的數學證明僅取決於解決離散對數問題(DLP)的難度,特別是對於 Bitcoin 的橢圓曲線(EC),以及雜湊函數(如 Bitcoin 中使用的 SHA256 函數)產生不可預測值的能力,稱為隨機預言模型(ROM)。其他簽章演算法具有額外的依賴關係,或者需要更大的公鑰或簽章才能獲得與 ECC-Schnorr 相當的安全性(當威脅被定義為經典計算機時;其他演算法可能針對量子計算機提供更有效的安全性)。 
- 線性
- 
Schnorr 簽章具有數學家稱為_線性_的特性,這適用於具有兩個特定特性的函數。第一個特性是將兩個或多個變數相加然後對該和運行函數將產生與對每個變數獨立運行函數然後將結果相加相同的值,例如,f(x + y + z) == f(x) + f(y) + f(z);這個特性被稱為_可加性_。第二個特性是將變數相乘然後對該乘積運行函數將產生與對變數運行函數然後將其乘以相同數量相同的值,例如,f(a × x) == a × f(x);這個特性被稱為 1 次齊次性。 在密碼學操作中,某些函數可能是私有的(例如涉及私鑰或秘密 nonce 的函數),因此無論是在函數內部還是外部執行操作都能獲得相同的結果,這使得多方可以輕鬆協調和合作而無需共享他們的秘密。我們將在 基於 Schnorr 的無腳本多重簽章 和 基於 Schnorr 的無腳本閾值簽章 中看到 schnorr 簽章線性的一些具體好處。 
- 批次驗證
- 
當以某種方式使用時(Bitcoin 確實如此),schnorr 線性的一個結果是,可以相對簡單地同時驗證多個 schnorr 簽章,所需時間少於獨立驗證每個簽章所需的時間。在批次中驗證的簽章越多,速度提升就越大。對於區塊中典型數量的簽章,可以在大約一半的時間內批次驗證它們,相比獨立驗證每個簽章所需的時間。 
在本章後面,我們將準確描述 Bitcoin 中使用的 schnorr 簽章演算法,但我們將從其簡化版本開始,並分階段逐步實現實際協議。
Alice首先選擇一個大的隨機數(x),我們稱之為她的_私鑰_。她還知道 Bitcoin 橢圓曲線上的一個公共點,稱為生成器(G)(請參見 公鑰)。Alice 使用 EC 乘法將 G 乘以她的私鑰 x,在這種情況下,x 被稱為_純量_,因為它縮放了 G。結果是 xG,我們稱之為 Alice 的_公鑰_。Alice 將她的公鑰給 Bob。即使 Bob 也知道 G,DLP 也阻止 Bob 能夠將 xG 除以 G 來衍生 Alice 的私鑰。
在稍後的某個時候,Bob 希望 Alice 透過證明她知道 Bob 之前收到的公鑰(xG)的純量 x 來識別自己。Alice 不能直接給 Bob x,因為那會允許他向其他人識別為她,所以她需要在不向 Bob 洩露 x 的情況下證明她對 x 的知識,稱為_零知識證明_。為此,我們開始 schnorr 身份過程:
- 
Alice 選擇另一個大的隨機數(k),我們稱之為_私有 nonce_。她再次將其用作純量,將其乘以 G 以產生 kG,我們稱之為_公共 nonce_。她將公共 nonce 給 Bob。 
- 
Bob 選擇他自己的大隨機數 e,我們稱之為_挑戰純量_。我們說「挑戰」是因為它用於挑戰 Alice 證明她知道她之前給 Bob 的公鑰(xG)的私鑰(x);我們說「純量」是因為它稍後將用於乘以 EC 點。 
- 
Alice 現在有數字(純量)x、k 和 e。她使用公式 s = k + ex 將它們組合在一起以產生最終的純量 s。她將 s 給 Bob。 
- 
Bob 現在知道純量 s 和 e,但不知道 x 或 k。但是,Bob 確實知道 xG 和 kG,他可以自己計算 sG 和 exG。這意味著他可以檢查 Alice 執行的操作的放大版本的相等性:[.keep-together]#sG == kG + exG。#如果相等,那麼 Bob 可以確信 Alice 在生成 s 時知道 x。 
讓我們討論互動式 schnorr 身份協議的一些安全功能:
- nonce (k)
- 
在步驟 1 中,Alice 選擇一個 Bob 不知道且無法猜測的數字,並給他該數字的縮放形式 kG。在那時,Bob 也已經擁有她的公鑰(xG),這是 x(她的私鑰)的縮放形式。這意味著當 Bob 處理最終等式(sG = kG + exG)時,有兩個 Bob 不知道的獨立變數(x 和 k)。可以使用簡單的代數來解決具有一個未知變數的等式,但不能解決兩個獨立未知變數的等式,因此 Alice 的 nonce 的存在阻止 Bob 能夠衍生她的私鑰。非常重要的是要注意,這種保護取決於 nonce 以任何方式都是不可猜測的。如果 Alice 的 nonce 有任何可預測的內容,Bob 可能能夠利用它來找出 Alice 的私鑰。有關更多詳細資訊,請參見 簽章中隨機性的重要性。 
- 挑戰純量 (e)
- 
Bob 等待接收 Alice 的公共 nonce,然後在步驟 2 中給她一個 Alice 之前不知道且無法猜測的數字(挑戰純量)。Bob 只有在她承諾她的公共 nonce 之後才給她挑戰純量,這一點至關重要。考慮一下如果不知道 x 的人想要冒充 Alice,而 Bob 不小心在他們告訴他公共 nonce kG 之前給了他們挑戰純量 e,會發生什麼。這允許冒充者更改 Bob 將用於驗證的等式兩側的參數,sG == kG + exG;具體來說,他們可以更改 sG 和 kG。想想該表達式的簡化形式:x = y + a。如果您可以更改 x 和 y,您可以使用 x' = (x – a) + a 來抵消 a。您為 x 選擇的任何值現在都將滿足等式。對於實際等式,冒充者只需為 s 選擇一個隨機數,生成 sG,然後使用 EC 減法選擇一個等於 kG = sG – exG 的 kG。他們給 Bob 他們計算的 kG,稍後給他們隨機的 sG,Bob 認為這是有效的,因為 sG == (sG – exG) + exG。這解釋了為什麼協議中的操作順序是必不可少的:Bob 必須只在 Alice 承諾她的公共 nonce 之後才給 Alice 挑戰純量。 
這裡描述的互動式身份協議與 Claus Schnorr 的原始描述的一部分相符,但它缺少我們在去中心化 Bitcoin 網路中需要的兩個基本功能。第一個是它依賴於 Bob 等待 Alice 承諾她的公共 nonce,然後 Bob 給她一個隨機挑戰純量。在 Bitcoin 中,每筆交易的支付者都需要由數千個 Bitcoin 完整節點進行認證——包括尚未啟動但其運營者有一天想要確保他們收到的比特幣來自每筆交易都有效的轉帳鏈的未來節點。任何無法與 Alice 通訊的 Bitcoin 節點,今天或將來,都將無法認證她的交易,並且將與所有確實認證它的其他節點不一致。這對於像 Bitcoin 這樣的共識系統是不可接受的。為了讓 Bitcoin 正常工作,我們需要一個不需要 Alice 與每個想要認證她的節點之間進行互動的協議。
一種簡單的技術,稱為 Fiat-Shamir 轉換(以其發現者命名),可以將 schnorr 互動式身份協議轉變為非互動式數位簽章方案。回想一下步驟 1 和 2 的重要性——包括它們必須按順序執行。Alice 必須承諾一個不可預測的 nonce;Bob 必須只在收到她的承諾後才給 Alice 一個不可預測的挑戰純量。還要回想我們在本書其他地方使用的安全密碼雜湊函數的特性:當給定相同的輸入時,它總是產生相同的輸出,但當給定不同的輸入時,它會產生與隨機資料無法區分的值。
這允許 Alice 選擇她的私有 nonce,衍生她的公共 nonce,然後雜湊公共 nonce 以獲得挑戰純量。因為 Alice 無法預測雜湊函數的輸出(挑戰),並且因為對於相同的輸入(nonce)它始終是相同的,這確保了 Alice 獲得隨機挑戰,即使她選擇 nonce 並自己對其進行雜湊。我們不再需要 Bob 的互動。她可以簡單地發布她的公共 nonce kG 和純量 s,數千個完整節點(過去和未來)中的每一個都可以雜湊 kG 以產生 e,使用它來產生 exG,然後驗證 sG == kG + exG。明確寫出,驗證等式變為 sG == kG + hash(kG) × xG。
我們需要另一件事來完成將互動式 schnorr 身份協議轉換為對 Bitcoin 有用的數位簽章協議。我們不只是想讓 Alice 證明她知道她的私鑰;我們還想讓她能夠承諾一個訊息。具體來說,我們希望她承諾與她想要發送的 Bitcoin 交易相關的資料。有了 Fiat-Shamir 轉換,我們已經有了一個承諾,所以我們可以簡單地讓它額外承諾訊息。我們現在還使用 hash(kG || m) 承諾訊息 m,而不是 hash(kG),其中 || 代表串聯。
我們現在已經定義了 schnorr 簽章協議的一個版本,但還有一件事我們需要做以解決 Bitcoin 特定的問題。在 BIP32 金鑰衍生中,如 公共子金鑰衍生 中所述,未強化衍生的演算法獲取公鑰並向其添加非秘密值以產生衍生公鑰。這意味著也可以將該非秘密值添加到一個金鑰的有效簽章中以產生相關金鑰的簽章。該相關簽章是有效的,但它未經擁有私鑰的人授權,這是一個重大的安全失敗。為了保護 BIP32 未強化衍生並支援人們想要在 schnorr 簽章之上構建的幾個協議,Bitcoin 的 schnorr 簽章版本稱為 BIP340 secp256k1 的 schnorr 簽章,除了公共 nonce 和訊息之外,還承諾正在使用的公鑰。這使得完整承諾為 hash(kG || xG || m)。
現在我們已經描述了 BIP340 schnorr 簽章演算法的每個部分並解釋了它為我們做了什麼,我們可以定義協議。整數的乘法是_模 p_ 執行的,表示操作的結果除以數字 p(如 secp256k1 標準中定義的),並使用餘數。數字 p 非常大,但如果它是 3 且操作的結果是 5,我們將使用的實際數字是 2(即,5 除以 3 的餘數是 2)。
設置:Alice 選擇一個大的隨機數(x)作為她的私鑰(直接或使用像 BIP32 這樣的協議從大的隨機種子值確定性地生成私鑰)。她使用 secp256k1 中定義的參數(請參見 橢圓曲線密碼學解釋)將生成器 G 乘以她的純量 x,產生 xG(她的公鑰)。她將她的公鑰給每個將來會認證她的 Bitcoin 交易的人(例如,透過將 xG 包含在交易輸出中)。當她準備好花費時,她開始生成她的簽章:
- 
Alice 選擇一個大的隨機私有 nonce k 並衍生公共 nonce kG。 
- 
她選擇她的訊息 m(例如,交易資料)並生成挑戰純量 e = hash(kG || xG || m)。 
- 
她產生純量 s = k + ex。兩個值 kG 和 s 是她的簽章。她將這個簽章給每個想要驗證該簽章的人;她還需要確保每個人都收到她的訊息 m。在 Bitcoin 中,這是透過在她的支付交易的見證結構中包含她的簽章,然後將該交易中繼到完整節點來完成的。 
- 
驗證者(例如,完整節點)使用 s 衍生 sG,然後驗證 sG == kG + hash(kG || xG || m) × xG。如果等式有效,Alice 證明了她知道她的私鑰 x(不洩露它)並承諾訊息 m(包含交易資料)。 
Schnorr 簽章的序列化
schnorr 簽章由兩個值組成,kG 和 s。值 kG 是 Bitcoin 橢圓曲線(稱為 secp256k1)上的一個點,通常由兩個 32 位元組座標表示,例如(x, y)。但是,只需要 x 座標,因此只包含該值。當您在 Bitcoin 的 schnorr 簽章中看到 kG 時,請注意它只是該點的 x 座標。
值 s 是一個純量(意味著乘以其他數字的數字)。對於 Bitcoin 的 secp256k1 曲線,它永遠不會超過 32 位元組長。
儘管 kG 和 s 有時可以是可以用少於 32 位元組表示的值,但它們不太可能比 32 位元組小得多,因此它們被序列化為兩個 32 位元組值(即,小於 32 位元組的值有前導零)。它們按 kG 然後 s 的順序序列化,正好產生 64 位元組。
taproot 軟分叉,也稱為 v1 segwit,將 schnorr 簽章引入 Bitcoin,是它們截至本文撰寫時在 Bitcoin 中使用的唯一方式。當與 taproot 金鑰路徑或腳本路徑支付一起使用時,64 位元組 schnorr 簽章被認為使用預設簽章雜湊(sighash),即 SIGHASH_ALL。如果使用替代 sighash,或者支付者想要浪費空間明確指定 SIGHASH_ALL,則將指定簽章雜湊的單個額外位元組附加到簽章,使簽章為 65 位元組。
正如我們將看到的,64 或 65 位元組比 ECDSA 簽章的序列化(DER) 中描述的用於 ECDSA 簽章的序列化要有效得多。
基於 Schnorr 的無腳本多重簽章
在Schnorr 簽章 中描述的單簽章 schnorr 協議中,Alice 使用簽章(kG, s)公開證明她對她的私鑰的知識,在這種情況下我們稱之為 y。想像一下,如果 Bob 也有一個私鑰(z),並且他願意與 Alice 合作證明他們一起知道 x = y + z,而不向彼此或其他任何人洩露他們的私鑰。讓我們再次經歷 BIP340 schnorr 簽章協議。
| 我們即將描述的簡單協議由於我們將很快解釋的原因而不安全。我們僅使用它來演示 schnorr 多重簽章的機制,然後再描述被認為是安全的相關協議。 | 
Alice 和 Bob 需要衍生 x 的公鑰,即 xG。由於可以使用橢圓曲線操作將兩個 EC 點加在一起,他們首先由 Alice 衍生 yG,Bob 衍生 zG。然後他們將它們加在一起以創建 [.keep-together]#xG = yG + zG。#點 xG 是他們的_聚合公鑰_。要創建簽章,他們開始簡單的多重簽章協議:
- 
他們各自單獨選擇一個大的隨機私有 nonce,Alice 為 a,Bob 為 b。他們還各自單獨衍生相應的公共 nonce aG 和 bG。他們一起產生聚合公共 nonce kG = aG + bG。 
- 
他們就要簽署的訊息 m(例如,交易)達成一致,並且各自生成挑戰純量的副本:e = hash(kG || xG || m)。 
- 
Alice 產生純量 q = a + ey。Bob 產生純量 r = b + ez。他們將純量加在一起以產生 s = q + r。他們的簽章是兩個值 kG 和 s。 
- 
驗證者使用正常等式檢查他們的公鑰和簽章:sG == kG + hash(kG || xG || m) × xG。 
Alice 和 Bob 已經證明他們知道他們的私鑰之和,而他們中的任何一個都沒有向另一個或其他任何人洩露他們的私鑰。該協議可以擴展到任何數量的參與者(例如,一百萬人可以證明他們知道他們一百萬個不同金鑰的總和)。
前面的協議有幾個安全問題。最值得注意的是,一方可能在承諾自己的公鑰之前了解其他方的公鑰。例如,Alice 誠實地生成她的公鑰 yG 並與 Bob 共享。Bob 使用 zG – yG 生成他的公鑰。當他們的兩個金鑰組合時 (yG + zG – yG), 正負 yG 項抵消,因此公鑰僅代表 z 的私鑰(即 Bob 的私鑰)。現在 Bob 可以在沒有 Alice 任何協助的情況下創建有效簽章。這被稱為_金鑰抵消攻擊_。
有多種方法可以解決金鑰抵消攻擊。最簡單的方案是要求每個參與者在與所有其他參與者共享有關該金鑰的任何資訊之前承諾他們的公鑰部分。例如,Alice 和 Bob 各自單獨雜湊他們的公鑰並與對方共享他們的摘要。當他們都擁有對方的摘要時,他們可以共享他們的金鑰。他們各自檢查對方的金鑰雜湊到先前提供的摘要,然後正常進行協議。這防止他們中的任何一個選擇抵消其他參與者金鑰的公鑰。但是,很容易無法正確實作此方案,例如以天真的方式將其與未強化 BIP32 公鑰衍生一起使用。此外,它為參與者之間的通訊添加了額外的步驟,這在許多情況下可能是不可取的。已經提出了解決這些缺點的更複雜的方案。
除了金鑰抵消攻擊之外,還有許多針對nonce 的攻擊。回想一下,nonce 的目的是防止任何人能夠使用他們對簽章驗證等式中其他值的知識來解決您的私鑰,確定其值。為了有效地實現這一點,您每次簽署不同的訊息或更改其他簽章參數時都必須使用不同的 nonce。不同的 nonce 不得以任何方式相關。對於多重簽章,每個參與者都必須遵循這些規則,否則可能會危及其他參與者的安全。此外,需要防止抵消和其他攻擊。實現這些目標的不同協議做出不同的權衡,因此在所有情況下都沒有單一的多重簽章協議可推薦。相反,我們將注意 MuSig 協議系列中的三個:
- MuSig
- 
也稱為 MuSig1,此協議在簽署過程中需要三輪通訊,使其類似於我們剛剛描述的過程。MuSig1 的最大優勢是其簡單性。 
- MuSig2
- 
這只需要兩輪通訊,有時可以允許其中一輪與金鑰交換結合。這可以顯著加快某些協議的簽署速度,例如無腳本多重簽章計畫在 LN 中使用的方式。MuSig2 在 BIP327 中指定(截至本文撰寫時唯一具有 BIP 的無腳本多重簽章協議)。 
- MuSig-DN
- 
DN 代表確定性 Nonce,它消除了稱為_重複會話攻擊_的問題。它不能與金鑰交換結合,並且比 MuSig 或 MuSig2 更複雜。 
對於大多數應用程式,MuSig2 是截至本文撰寫時最好的多重簽章協議。
基於 Schnorr 的無腳本閾值簽章
無腳本多重簽章協議僅適用於 k-of-k 簽署。成為聚合公鑰一部分的部分公鑰的每個人都必須向最終簽章貢獻部分簽章和部分 nonce。但是,有時參與者希望允許他們的子集簽署,例如 t-of-k,其中閾值(t)數量的參與者可以為 k 個參與者構建的金鑰簽署。這種類型的簽章稱為_閾值簽章_。
我們在 腳本化多重簽章 中看到了基於腳本的閾值簽章。但就像無腳本多重簽章與腳本多重簽章相比節省空間並增加隱私一樣,_無腳本閾值簽章_與_腳本閾值簽章_相比節省空間並增加隱私。對於未參與簽署的任何人來說,_無腳本閾值簽章_看起來就像任何其他簽章,可以由單簽章使用者或透過無腳本多重簽章協議創建。
已知生成無腳本閾值簽章的各種方法,最簡單的方法是對我們之前創建無腳本多重簽章的方式進行輕微修改。此協議還依賴於可驗證秘密共享(其本身依賴於安全秘密共享)。
基本秘密共享可以透過簡單拆分來工作。Alice 有一個秘密數字,她將其拆分為三個等長部分並與 Bob、Carol 和 Dan 共享。這三個人可以按正確的順序組合他們收到的部分數字(稱為_份額_)以重建 Alice 的秘密。更複雜的方案將涉及 Alice 向每個份額添加一些額外資訊,稱為糾錯碼,允許他們中的任何兩個恢復數字。這個方案是不安全的,因為每個份額都讓其持有者部分了解 Alice 的秘密,使參與者比沒有份額的非參與者更容易猜測 Alice 的秘密。
安全秘密共享方案防止參與者了解有關秘密的任何資訊,除非他們組合最小閾值數量的份額。例如,Alice 可以選擇閾值 2,如果她想讓 Bob、Carol 和 Dan 中的任何兩個能夠重建她的秘密。最著名的安全秘密共享演算法是 Shamir’s Secret Sharing Scheme,通常縮寫為 SSSS,以其發現者命名,他也是我們在 Schnorr 簽章 中看到的 Fiat-Shamir 轉換的發現者之一。
在某些密碼學協議中,例如我們正在努力實現的無腳本閾值簽章方案,Bob、Carol 和 Dan 知道 Alice 正確地遵循了她那一方的協議至關重要。他們需要知道她創建的份額都來自同一個秘密,她使用了她聲稱的閾值,並且她給了他們每個人不同的份額。一個可以完成所有這些的協議,並且仍然是一個安全的秘密共享方案,是一個_可驗證的秘密共享方案_。
要了解多重簽章和可驗證秘密共享如何為 Alice、Bob 和 Carol 工作,想像他們每個人都希望接收可以由他們中的任何兩個花費的資金。他們如 基於 Schnorr 的無腳本多重簽章 中所述協作以產生常規多重簽章公鑰以接受資金(k-of-k)。然後每個參與者從他們的私鑰衍生兩個秘密份額——每個其他兩個參與者一個。這些份額允許他們中的任何兩個重建多重簽章的原始部分私鑰。每個參與者將他們的一個秘密份額分發給其他兩個參與者,導致每個參與者儲存他們自己的部分私鑰和每個其他參與者的一個份額。隨後,每個參與者驗證他們收到的份額與給其他參與者的份額相比的真實性和唯一性。
稍後,當(例如)Alice 和 Bob 想要在沒有 Carol 參與的情況下生成無腳本閾值簽章時,他們交換他們擁有的 Carol 的兩個份額。這使他們能夠重建 Carol 的部分私鑰。Alice 和 Bob 也有他們的私鑰,允許他們使用所有三個必要的金鑰創建無腳本多重簽章。
換句話說,剛剛描述的無腳本閾值簽章方案與無腳本多重簽章方案相同,只是閾值數量的參與者有能力重建無法或不願意簽署的任何其他參與者的部分私鑰。
這確實指出了在考慮無腳本閾值簽章協議時需要注意的幾件事:
- 無問責制
- 
因為 Alice 和 Bob 重建了 Carol 的部分私鑰,所以涉及 Carol 的過程產生的無腳本多重簽章與沒有涉及 Carol 的多重簽章之間不可能存在根本區別。即使 Alice、Bob 或 Carol 聲稱他們沒有簽署,也沒有保證的方法讓他們證明他們沒有幫助產生簽章。如果知道組中哪些成員簽署很重要,您將需要使用腳本。 
- 操縱攻擊
- 
想像一下 Bob 告訴 Alice Carol 不可用,因此他們一起工作以重建 Carol 的部分私鑰。然後 Bob 告訴 Carol Alice 不可用,因此他們一起工作以重建 Alice 的部分私鑰。現在 Bob 擁有他自己的部分私鑰加上 Alice 和 Carol 的金鑰,允許他在沒有他們參與的情況下自己花費資金。這種攻擊可以透過使用通訊方案來防止,該方案允許他們中的任何一個看到所有其他人的訊息(例如,如果 Bob 告訴 Alice Carol 不可用,Carol 能夠在她開始與 Bob 工作之前看到該訊息)。其他解決方案,可能更強大的解決方案,在本文撰寫時正在研究中。 
截至本文撰寫時,還沒有無腳本閾值簽章協議被提議為 BIP,儘管多位 Bitcoin 貢獻者已經對該主題進行了重要研究,我們預計在本書出版後,經過同行評審的解決方案將變得可用。
ECDSA 簽章
不幸的是,對於 Bitcoin 和許多其他應用程式的未來發展,Claus Schnorr 對他發現的演算法申請了專利,並在近二十年內阻止了它在開放標準和開源軟體中的使用。1990 年代初期的密碼學家在被阻止使用 schnorr 簽章方案時,開發了一種替代構造,稱為_數位簽章演算法_(DSA),並有一個適應於橢圓曲線的版本稱為 ECDSA。
ECDSA 方案和建議的標準化參數在 2007 年開始開發 Bitcoin 時已經在密碼學函式庫中廣泛實現。這幾乎肯定是 ECDSA 從第一個發布版本直到 2021 年 taproot 軟分叉激活之前,成為 Bitcoin 支援的唯一數位簽章協議的原因。ECDSA 今天仍然支援所有非 taproot 交易。與 schnorr 簽章相比的一些差異包括:
- 更複雜
- 
正如我們將看到的,ECDSA 需要更多操作來創建或驗證簽章,與 schnorr 簽章協議相比。從實現的角度來看,它並不顯著更複雜,但額外的複雜性使 ECDSA 靈活性較低、性能較差,並且更難證明其安全性。 
- 安全性證明較少
- 
互動式 schnorr 簽章身份協議僅依賴於橢圓曲線離散對數問題(ECDLP)的強度。在 Bitcoin 中使用的非互動式認證協議也依賴於隨機預言機模型(ROM)。然而,ECDSA 的額外複雜性阻止了完整安全性證明的發表(據我們所知)。我們不是證明密碼學演算法的專家,但在 30 年之後,ECDSA 似乎不太可能被證明只需要與 schnorr 相同的兩個假設。 
- 非線性
- 
ECDSA 簽章無法輕鬆組合以創建無腳本多重簽章或用於相關的進階應用,例如多方簽章適配器。這個問題有解決方法,但它們涉及額外的複雜性,顯著減慢操作速度,並且在某些情況下,導致軟體意外洩露私鑰。 
ECDSA 演算法
讓我們看看 ECDSA 的數學。簽章由數學函數 Fsig 創建,該函數產生由兩個值組成的簽章。在 ECDSA 中,這兩個值是 R 和 s。
簽章演算法首先生成一個私有 nonce(k)並從中衍生一個公共 nonce(K)。數位簽章的 R 值是 nonce K 的 x 座標。
從那裡,演算法計算簽章的 s 值。就像我們對 schnorr 簽章所做的那樣,涉及整數的操作是模 p:
其中:
- 
k 是私有 nonce 
- 
R 是公共 nonce 的 x 座標 
- 
x 是 Alice 的私鑰 
- 
m 是訊息(交易資料) 
驗證是簽章生成函數的反向,使用 R、s 值和公鑰計算值 K,這是橢圓曲線上的一個點(簽章創建中使用的公共 nonce):
其中:
- 
R 和 s 是簽章值 
- 
X 是 Alice 的公鑰 
- 
m 是訊息(被簽署的交易資料) 
- 
G 是橢圓曲線生成器點 
如果計算點 K 的 x 座標等於 R,那麼驗證者可以得出簽章有效的結論。
| ECDSA 必然是一個相當複雜的數學部分;完整解釋超出了本書的範圍。網上有許多優秀的指南會逐步帶您了解它:搜尋「ECDSA explained」。 | 
ECDSA 簽章的序列化(DER)
讓我們看看以下 DER 編碼的簽章:
3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204 b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301
該簽章是簽署者產生的 R 和 s 值的序列化位元組流,以證明對授權花費輸出的私鑰的控制。序列化格式由九個元素組成,如下所示:
- 
0x30,表示 DER 序列的開始 
- 
0x45,序列的長度(69 位元組) 
- 
0x02,後面跟著一個整數值 
- 
0x21,整數的長度(33 位元組) 
- 
R,00884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb 
- 
0x02,後面跟著另一個整數 
- 
0x20,整數的長度(32 位元組) 
- 
S,4b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813 
- 
後綴(0x01)表示使用的雜湊類型(SIGHASH_ALL) 
簽章中隨機性的重要性
正如我們在 Schnorr 簽章 和 ECDSA 簽章 中看到的,簽章生成演算法使用隨機數 k 作為私有/公共 nonce 對的基礎。k 的值並不重要,只要它是隨機的。如果來自同一私鑰的簽章對不同訊息(交易)使用相同的私有 nonce k,那麼任何人都可以計算出簽署_私鑰_。在簽章演算法中重複使用相同的 k 值會導致私鑰洩露!
| 如果在兩個不同交易的簽署演算法中使用相同的值 k,私鑰可以被計算出來並暴露給全世界! | 
這不僅僅是理論上的可能性。我們已經看到這個問題導致 Bitcoin 中幾種不同的交易簽署演算法實現中私鑰的洩露。由於無意中重複使用 k 值,人們的資金被盜取。重複使用 k 值的最常見原因是隨機數生成器初始化不當。
為了避免這個漏洞,業界最佳實踐不是僅使用熵種子的隨機數生成器來生成 k,而是使用部分由交易資料本身加上用於簽署的私鑰種子的過程。這確保每筆交易產生不同的 k。用於 ECDSA 的 k 確定性初始化的行業標準演算法在 RFC6979 中定義,由網際網路工程任務組發布。對於 schnorr 簽章,BIP340 推薦預設簽署演算法。
BIP340 和 RFC6979 可以完全確定性地生成 k,這意味著相同的交易資料將始終產生相同的 k。許多錢包這樣做是因為這使編寫測試來驗證其安全關鍵的簽署程式碼是否正確產生 k 值變得容易。BIP340 和 RFC6979 也都允許在計算中包含額外資料。如果該資料是熵,那麼即使簽署完全相同的交易資料,也會產生不同的 k。這可以增加對側通道攻擊和故障注入攻擊的保護。
如果您正在實現一個演算法來簽署 Bitcoin 中的交易,您_必須_使用 BIP340、RFC6979 或類似的演算法來確保您為每筆交易生成不同的 k。
隔離見證的新簽署演算法
Bitcoin交易中的簽章適用於_承諾雜湊_,該雜湊從交易資料計算得出,鎖定資料的特定部分以指示簽署者對這些值的承諾。例如,在簡單的 SIGHASH_ALL 類型簽章中,承諾雜湊包括所有輸入和輸出。
不幸的是,傳統承諾雜湊的計算方式引入了節點驗證簽章時可能被迫執行大量雜湊計算的可能性。具體來說,雜湊操作相對於交易中的輸入數量大約呈二次方增長。因此,攻擊者可以創建一個具有非常大量簽章操作的交易,導致整個 Bitcoin 網路必須執行數百或數千次雜湊操作來驗證交易。
Segwit 代表了透過更改承諾雜湊計算方式來解決這個問題的機會。對於 segwit 版本 0 見證程式,簽章驗證使用如 BIP143 中指定的改進承諾雜湊演算法。
新演算法允許雜湊操作的數量相對於簽章操作的數量以更漸進的 O(n) 增長,減少了使用過於複雜的交易創建拒絕服務攻擊的機會。
在本章中,我們了解了 Bitcoin 的 schnorr 和 ECDSA 簽章。這解釋了完整節點如何認證交易以確保只有控制接收比特幣的金鑰的人才能花費這些比特幣。我們還檢查了簽章的幾個進階應用,例如無腳本多重簽章和無腳本閾值簽章,可以用來提高 Bitcoin 的效率和隱私。在過去幾章中,我們學習了如何創建交易、如何使用授權和認證保護它們以及如何簽署它們。接下來,我們將學習如何透過向我們創建的交易添加手續費來鼓勵礦工確認它們。
交易手續費
我們在 數位簽章 中看到 Alice 創建的數位簽章只能證明她知道她的私鑰並且她承諾了一筆支付 Bob 的交易。她可以創建另一個簽章,改為承諾一筆支付 Carol 的交易——一筆花費與支付 Bob 相同輸出(比特幣)的交易。這兩筆交易現在是_衝突交易_,因為在具有最多工作量證明的有效區塊鏈中——完整節點用來確定哪些金鑰控制哪些比特幣的區塊鏈——只能包含花費特定輸出的一筆交易。
為了保護自己免受衝突交易的影響,Bob 明智的做法是等到 Alice 的交易被包含在區塊鏈中到足夠的深度,然後再認為他收到的錢可以由他花費(請參見 確認)。要將 Alice 的交易包含在區塊鏈中,它必須被包含在交易_區塊_中。在給定時間內產生的區塊數量有限,並且每個區塊只有有限的空間。只有創建該區塊的礦工才能選擇包含哪些交易。礦工可以按他們想要的任何標準選擇交易,包括完全拒絕包含任何交易。
| 在本章中,當我們說「交易」時,我們指的是區塊中除第一筆交易之外的每筆交易。區塊中的第一筆交易是 coinbase 交易,在 Coinbase 交易 中描述,它允許區塊的礦工收取其生產區塊的獎勵。與其他交易不同,coinbase 交易不花費先前交易的輸出,也是適用於其他交易的其他幾項規則的例外。Coinbase 交易不支付交易手續費,不需要進行手續費提升,不受交易固定的影響,並且對以下關於手續費的討論基本上沒有意義——所以我們將在本章中忽略它們。 | 
幾乎所有礦工用來選擇在其區塊中包含哪些交易的標準是最大化其收入。Bitcoin 專門設計為透過提供一種機制來適應這一點,該機制允許交易將錢給包含該交易在區塊中的礦工。我們稱這種機制為_交易手續費_,儘管它不是該詞通常意義上的手續費。它不是由協議或任何特定礦工設定的金額——它更像是拍賣中的出價。購買的商品是交易將消耗的區塊中有限空間的一部分。礦工選擇一組交易,其出價將使他們能夠賺取最大收入。
在本章中,我們將探討這些出價——交易手續費——的各個方面,以及它們如何影響 Bitcoin 交易的創建和管理。
誰支付交易手續費?
大多數支付系統都涉及某種交易手續費,但這種手續費通常對典型買家是隱藏的。例如,商家可能以相同的價格宣傳相同的商品,無論您是用現金還是信用卡支付,即使他們的支付處理商對信用交易收取的手續費可能高於他們的銀行對現金存款收取的手續費。
在 Bitcoin 中,每次花費比特幣都必須經過認證(通常使用簽章),因此交易不可能在未經支付者許可的情況下支付手續費。交易的接收者可以在不同的交易中支付手續費——我們稍後會看到這種用法——但如果我們希望單筆交易支付自己的手續費,該手續費需要由支付者同意。它無法被隱藏。
Bitcoin 交易的設計使得支付者承諾其支付的手續費不需要在交易中佔用任何額外空間。這意味著,儘管可以在不同的交易中支付手續費,但在單筆交易中支付手續費是最有效率的(因此最便宜)。
在 Bitcoin 中,手續費是一種出價,支付的金額有助於確定交易確認所需的時間。支付的支付者和接收者通常都有興趣使其快速確認,因此通常只允許支付者選擇手續費有時可能是一個問題;我們將在 子支付父(CPFP)手續費提升 中查看該問題的解決方案。但是,在許多常見的支付流程中,最希望看到交易快速確認的各方——也就是最願意支付更高手續費的各方——是支付者。
出於這些技術和實際原因,在 Bitcoin 中,支付者支付交易手續費是慣例。也有例外,例如接受未確認交易的商家,以及不在簽署後立即廣播交易的協議(阻止支付者能夠為當前市場選擇適當的手續費)。我們稍後將探討這些例外。
手續費和手續費率
每筆交易只支付一筆手續費——無論交易有多大都無關緊要。但是,交易越大,礦工能夠在區塊中放入的交易就越少。因此,礦工評估交易的方式與您在市場上比較幾種等效商品時的方式相同:他們將價格除以數量。
您可能將幾種不同袋裝米的成本除以每袋的重量以找到每重量的最低價格(最划算),礦工將交易的手續費除以其大小(也稱為其權重)以找到每權重的最高手續費(最大收入)。在 Bitcoin 中,我們使用術語_手續費率_來表示交易的大小除以權重。由於 Bitcoin 多年來的變化,手續費率可以用不同的單位表示:
- 
BTC/Bytes(很少使用的舊單位) 
- 
BTC/Kilobytes(很少使用的舊單位) 
- 
BTC/Vbytes(很少使用) 
- 
BTC/Kilo-vbyte(主要在 Bitcoin Core 中使用) 
- 
Satoshi/Vbyte(今天最常用) 
- 
Satoshi/Weight(今天也常用) 
我們建議使用 sat/vbyte 或 sat/weight 單位來顯示手續費率。
| 在接受手續費率輸入時要小心。如果使用者將以一種分母列印的手續費率複製並貼上到使用不同分母的欄位中,他們可能會多付 1,000 倍的手續費。如果他們改變分子,理論上他們可能會多付 100,000,000 倍。錢包應該讓使用者難以支付過高的手續費率,並可能希望提示使用者確認任何不是由錢包本身使用可信資料來源產生的手續費率。 過高的手續費,也稱為_荒謬手續費_,是指任何明顯高於手續費率估算器目前預期在下一個區塊中確認交易所需金額的手續費率。請注意,錢包不應完全阻止使用者選擇過高的手續費率——他們只應使意外使用此類手續費率變得困難。使用者在極少數情況下有合理的理由過度支付手續費。 | 
估算適當的手續費率
我們已經確定,如果您願意等待更長時間讓交易確認,您可以支付較低的手續費率,但例外情況是支付太低的手續費率可能導致您的交易永遠無法確認。由於手續費率是區塊空間公開拍賣中的出價,因此不可能完美預測您需要支付什麼手續費率才能在特定時間內確認交易。但是,我們可以根據其他交易在最近過去支付的手續費率產生粗略估計。
完整節點可以記錄其看到的每筆交易的三條資訊:它首次收到該交易的時間(區塊高度)、該交易確認時的區塊高度,以及該交易支付的手續費率。透過將在相似高度到達、在相似高度確認並支付相似手續費的交易組合在一起,我們可以計算確認支付特定手續費率的交易需要多少個區塊。然後,我們可以假設現在支付類似手續費率的交易將需要類似數量的區塊來確認。Bitcoin Core 包含一個使用這些原則的手續費率估算器,可以使用 estimatesmartfee RPC 呼叫,並帶有一個參數,指定您願意等待多少個區塊,交易才有很高的可能性確認(例如,144 個區塊約為 1 天):
$ bitcoin-cli -named estimatesmartfee conf_target=144
{
  "feerate": 0.00006570,
  "blocks": 144
}
許多基於網路的服務也提供手續費估算作為 API。有關當前列表,請參見 https://oreil.ly/TB6IN。
如前所述,手續費率估算永遠無法完美。一個常見的問題是基本需求可能會改變,調整平衡點並將價格(手續費)提高到新高度或將其降低到最低值。如果手續費率下降,那麼先前支付正常手續費率的交易現在可能支付高手續費率,它將比預期更早確認。您無法降低已發送交易的手續費率,因此您只能支付更高的手續費率。但是,當手續費率上升時,需要有方法能夠提高這些交易的手續費率,這稱為_手續費提升_。Bitcoin 中有兩種常用的手續費提升類型,手續費替代(RBF)和子支付父(CPFP)。
手續費替代(RBF)手續費提升
要使用 RBF 手續費提升來提高交易的手續費,您需要創建支付更高手續費的交易的衝突版本。如果兩筆或多筆交易("交易", "衝突"被認為是_衝突交易_,則只有其中一筆可以包含在有效的區塊鏈中,迫使礦工只選擇其中一筆。當兩筆或多筆交易各自嘗試花費相同的 UTXO 之一時,即它們各自包含具有相同輸出點(對先前交易輸出的引用)的輸入時,就會發生衝突。
為了防止有人透過創建無限數量的衝突交易並透過中繼完整節點的網路發送它們來消耗大量頻寬,Bitcoin Core 和其他支援交易替換的完整節點要求每筆替換交易支付比被替換交易更高的手續費率。Bitcoin Core 目前還要求替換交易支付比原始交易更高的總手續費,但這一要求具有不良副作用,開發人員在撰寫本文時一直在尋找消除它的方法。
Bitcoin Core目前支援兩種 RBF 變體:
- 選擇性 RBF
- 
未確認的交易可以向礦工和完整節點發出信號,表明交易的創建者希望允許它被更高手續費率版本替換。該信號及其使用規則在 BIP125 中指定。截至本文撰寫時,這已在 Bitcoin Core 中預設啟用多年。 
- 完全 RBF
- 
任何未確認的交易都可以被更高手續費率版本替換。截至本文撰寫時,這可以在 Bitcoin Core 中選擇性啟用(但預設情況下已禁用)。 
作為使用者,如果您計劃使用 RBF 手續費提升,您首先需要選擇支援它的錢包,例如在 https://oreil.ly/IhMzx 上列為具有「發送支援」的錢包之一。
作為開發人員,如果您計劃實現 RBF 手續費提升,您首先需要決定是執行選擇性 RBF 還是完全 RBF。在撰寫本文時,選擇性 RBF 是唯一確定可行的方法。即使完全 RBF 變得可靠,也可能會有幾年時間,選擇性交易的替換比完全 RBF 替換確認得稍快。如果您選擇選擇性 RBF,您的錢包將需要實現 BIP125 中指定的信號,這是對交易中任何一個序列欄位的簡單修改(請參見 序列)。如果您選擇完全 RBF,則無需在交易中包含任何信號。與 RBF 相關的其他所有內容對於兩種方法都是相同的。
當您需要提升交易的手續費時,您只需創建一筆新交易,該交易花費至少與您想要替換的原始交易相同的 UTXO 之一。您可能希望在交易中保留支付接收者的相同輸出。您可以透過減少找零輸出的價值或向交易添加額外輸入來支付增加的手續費。開發人員應為使用者提供手續費提升介面,為他們完成所有這些工作,並簡單地詢問他們(或向他們建議)手續費率應該提高多少。
| 在創建同一交易的多個替換時要非常小心。您必須確保交易的所有版本都相互衝突。如果它們不是所有衝突,則可能確認多個單獨的交易,導致您多付給接收者。例如: 
 在這種情況下,任何保存交易版本 0 的礦工都能夠確認它和交易版本 2。如果兩個版本都支付相同的接收者,他們將被支付兩次(礦工將從兩個單獨的交易中收到交易手續費)。 避免此問題的簡單方法是確保替換交易始終包含與交易的先前版本相同的所有輸入。 | 
RBF 手續費提升相對於其他類型的手續費提升的優勢在於它可以非常有效地使用區塊空間。通常,替換交易與它替換的交易大小相同。即使它更大,它通常與使用者如果在第一時間支付增加的手續費率時會創建的交易大小相同。
RBF 手續費提升的根本缺點是它通常只能由交易的創建者執行——需要為交易提供簽章或其他認證資料的人或人們。例外情況是透過使用 sighash 標誌(請參見 簽章雜湊類型(SIGHASH))設計為允許添加額外輸入的交易,但這會帶來自己的挑戰。一般來說,如果您是未確認交易的接收者,並且您想讓它更快(或完全)確認,您無法使用 RBF 手續費提升;您需要其他方法。
RBF 還有其他問題,我們將在 交易固定 中探討。
子支付父(CPFP)手續費提升
任何收到未確認交易輸出的人都可以透過花費該輸出來激勵礦工確認該交易。您想要確認的交易稱為_父交易_。花費父交易輸出的交易稱為_子交易_。
正如我們在 輸出點 中學到的,已確認交易中的每個輸入都必須引用出現在區塊鏈中較早的交易的未花費輸出(無論是在同一區塊中較早還是在先前的區塊中)。這意味著想要確認子交易的礦工還必須確保其父交易已確認。如果父交易尚未確認,但子交易支付的手續費足夠高,礦工可以考慮在同一區塊中確認它們兩者是否有利可圖。
為了評估挖掘父交易和子交易的獲利能力,礦工將它們視為具有總大小和總手續費的_交易包_,從中可以將手續費除以大小來計算包手續費率。然後,礦工可以按手續費率對他們知道的所有單獨交易和交易包進行排序,並將最高收入的交易包含在他們正在嘗試挖掘的區塊中,直到區塊中允許包含的最大大小(權重)。為了找到更多可能有利可圖的包,礦工可以評估跨多代的包(例如,將未確認的父交易與其子交易和孫交易組合在一起)。這被稱為_祖先手續費率挖掘_。
Bitcoin Core 多年來一直實現祖先手續費率挖掘,並且據信在撰寫本文時幾乎所有礦工都在使用它。這意味著錢包使用此功能透過使用子交易為其父交易支付手續費來提升傳入交易的手續費是切實可行的(CPFP)。
CPFP 相對於 RBF 有幾個優勢。任何收到交易輸出的人都可以使用 CPFP——包括支付的接收者和支付者(如果支付者包含了找零輸出)。它也不需要替換原始交易,這使得它對某些商家來說比 RBF 的干擾性更小。
與 RBF 相比,CPFP 的主要缺點是 CPFP 通常使用更多區塊空間。在 RBF 中,手續費提升交易通常與它替換的交易大小相同。在 CPFP 中,手續費提升會添加一個完全獨立的交易。使用額外的區塊空間需要支付超出手續費提升成本之外的額外手續費。
CPFP 存在幾個挑戰,其中一些我們將在 交易固定 中探討。我們特別需要提到的另一個問題是最低中繼手續費率問題,這由包中繼解決。
包中繼
早期版本的 Bitcoin Core 對其記憶池(請參見 記憶池和孤兒池)中儲存用於稍後中繼和挖掘的未確認交易數量沒有任何限制。當然,電腦有物理限制,無論是記憶體(RAM)還是磁碟空間——完整節點不可能儲存無限數量的未確認交易。後來的 Bitcoin Core 版本將記憶池的大小限制為大約可容納一天的交易量,僅儲存手續費率最高的交易或包。
這對大多數事情都非常有效,但它創建了一個依賴問題。為了計算交易包的手續費率,我們需要父交易和子交易——但如果父交易支付的手續費率不夠高,它將不會保留在節點的記憶池中。如果節點收到一筆子交易而沒有訪問其父交易的權限,它就無法對該交易做任何事情。
解決此問題的方法是能夠將交易作為包中繼,稱為_包中繼_,允許接收節點在對任何單獨交易進行操作之前評估整個包的手續費率。截至本文撰寫時,致力於 Bitcoin Core 的開發人員在實現包中繼方面取得了重大進展,並且在本書出版時可能會提供其有限的早期版本。
包中繼對於基於時間敏感的預簽交易的協議尤其重要,例如閃電網路(LN)。在非合作的情況下,一些預簽交易無法使用 RBF 進行手續費提升,迫使它們依賴於 CPFP。在這些協議中,某些交易也可能在需要廣播之前很久就創建了,這使得估算適當的手續費率實際上是不可能的。如果預簽交易支付的手續費率低於進入節點記憶池所需的金額,則無法使用子交易提升其手續費。如果這阻止交易及時確認,誠實的使用者可能會損失金錢。包中繼是解決這個關鍵問題的方案。
交易固定
儘管 RBF 和 CPFP 手續費提升在我們描述的基本情況下都有效,但與這兩種方法相關的規則旨在防止對礦工和中繼完整節點的拒絕服務攻擊。這些規則的一個不幸的副作用是,它們有時會阻止某人能夠使用手續費提升。使手續費提升交易變得不可能或困難稱為交易固定。
主要的拒絕服務問題之一圍繞著交易關係的影響。每當交易的輸出被花費時,該交易的識別碼(txid)就會被子交易引用。但是,當交易被替換時,替換具有不同的 txid。如果該替換交易被確認,則其所有後代都不能包含在同一區塊鏈中。可以重新創建和重新簽署後代交易,但這並不保證會發生。這對 RBF 和 CPFP 有相關但不同的含義:
- 
在 RBF 的上下文中,當 Bitcoin Core 接受替換交易時,它透過忘記原始交易和所有依賴於該原始交易的後代交易來保持簡單。為了確保礦工接受替換更有利可圖,Bitcoin Core 僅在替換交易支付的手續費多於所有將被遺忘的交易時才接受替換交易。 這種方法的缺點是 Alice 可以創建一筆支付 Bob 的小交易。然後 Bob 可以使用他的輸出創建一筆大的子交易。如果 Alice 然後想替換她的原始交易,她需要支付比她和 Bob 最初支付的更高的手續費。例如,如果 Alice 的原始交易約為 100 vbytes,Bob 的交易約為 100,000 vbytes,並且它們都使用相同的手續費率,Alice 現在需要支付超過她最初支付的 1,000 倍才能 RBF 手續費提升她的交易。 
- 
在 CPFP 的上下文中,每次節點考慮在區塊中包含一個包時,它必須從它想要為同一區塊考慮的任何其他包中刪除該包中的交易。例如,如果一筆子交易為 25 個祖先支付費用,並且這些祖先中的每一個都有 25 個其他子交易,那麼在區塊中包含該包需要更新大約 625 個包(252)。同樣,如果從節點的記憶池中刪除具有 25 個後代的交易(例如因為被包含在區塊中),並且這些後代中的每一個都有 25 個其他祖先,則需要更新另外 625 個包。每次我們將參數加倍(例如,從 25 到 50),我們的節點需要執行的工作量就會翻兩番。 此外,如果交易的替代版本被挖掘,則交易及其所有後代對於長期保留在記憶池中並不有用——除非發生罕見的區塊鏈重組,否則這些交易現在都無法確認。Bitcoin Core 將從其記憶池中刪除當前區塊鏈上無法再確認的每筆交易。在最壞的情況下,這可能會浪費節點的大量頻寬,並可能被用來阻止交易正確傳播。 為了防止這些問題以及其他相關問題,Bitcoin Core 將父交易限制為在其記憶池中最多擁有 25 個祖先或後代,並將所有這些交易的總大小限制為 100,000 vbytes。這種方法的缺點是,如果交易已經有太多後代(或者它及其後代太大),則會阻止使用者創建 CPFP 手續費提升。 
交易固定可能是偶然發生的,但它也代表了對多方時間敏感協議(如 LN)的嚴重漏洞。如果您的交易對手可以在截止日期之前阻止您的交易之一確認,他們可能能夠從您那裡竊取金錢。
協議開發人員多年來一直在努力緩解交易固定的問題。CPFP 分割和錨點輸出 中描述了一個部分解決方案。已經提出了其他幾種解決方案,並且截至本文撰寫時,至少有一種解決方案正在積極開發中——https://oreil.ly/300dv[暫時性錨點]。
CPFP 分割和錨點輸出
在 2018 年,致力於 LN 的開發人員遇到了一個問題。他們的協議使用需要來自兩個不同方的簽章的交易。任何一方都不想信任另一方,因此他們在協議中不需要信任的時候簽署交易,允許他們中的任何一方在稍後的時間(當另一方可能不想(或無法)履行其義務時)廣播這些交易之一。這種方法的問題在於,交易可能需要在未來的未知時間廣播,超出了任何合理估算交易適當手續費率的能力。
理論上,開發人員可以設計他們的交易以允許使用 RBF(使用特殊的 sighash 標誌)或 CPFP 進行手續費提升,但這兩個協議都容易受到交易固定的影響。鑑於所涉及的交易具有時間敏感性,允許交易對手使用交易固定來延遲交易的確認很容易導致惡意方可以用來從誠實方竊取金錢的可重複利用。
LN 開發人員 Matt Corallo 提出了一個解決方案:為 CPFP 手續費提升的規則提供特殊例外,稱為 CPFP 分割。CPFP 的正常規則禁止包含額外的後代,如果它會導致父交易擁有 26 個或更多後代,或者如果它會導致父交易及其所有後代的大小超過 100,000 vbytes。根據 CPFP 分割的規則,即使會超過其他限制,只要單個額外交易的大小最多為 1,000 vbytes,且是沒有未確認祖先的未確認交易的直接子交易,就可以將其添加到包中。
例如,Bob 和 Mallory 共同簽署一筆交易,其中有兩個輸出,各給他們一個。Mallory 廣播該交易,並使用她的輸出附加 25 個子交易或總計 100,000 vbytes 大小的任何較小數量的子交易。如果沒有分割,Bob 將無法將另一個子交易附加到他的輸出以進行 CPFP 手續費提升。透過分割,只要他的子交易小於 1,000 vbytes(應該有足夠的空間),他就可以花費交易中屬於他的兩個輸出之一。
不允許使用 CPFP 分割超過一次,因此它僅適用於兩方協議。已經有提議將其擴展到涉及更多參與者的協議,但對此需求不大,開發人員專注於構建更通用的解決方案來解決交易固定攻擊。
截至本文撰寫時,大多數流行的 LN 實現使用稱為_錨點輸出_的交易模板,該模板旨在與 CPFP 分割一起使用。
向交易添加手續費
交易的資料結構沒有手續費欄位。相反,手續費隱含為輸入總和與輸出總和之間的差額。在扣除所有輸出後從所有輸入中剩餘的任何多餘金額就是礦工收取的手續費:
這是交易的一個有點令人困惑的元素,也是需要理解的重要一點,因為如果您正在構建自己的交易,您必須確保不會因為輸入支出不足而無意中包含非常高的手續費。這意味著您必須計算所有輸入,如有必要,透過創建找零來實現,否則您最終會給礦工一筆非常大的小費!
例如,如果您花費 20 比特幣的 UTXO 進行 1 比特幣的支付,您必須包含 19 比特幣的找零輸出返回到您的錢包。否則,19 比特幣的「剩餘」將被計為交易手續費,並由在區塊中挖掘您的交易的礦工收取。儘管您會獲得優先處理並使礦工非常高興,但這可能不是您的本意。
| 如果您在手動構建的交易中忘記添加找零輸出,您將把找零作為交易手續費支付。「不用找零!」可能不是您的本意。 | 
時間鎖防禦手續費狙擊
手續費狙擊是一種理論攻擊情境,其中試圖重寫過去區塊的礦工從未來區塊「狙擊」更高手續費的交易以最大化其[.keep-together]獲利能力。
例如,假設存在的最高區塊是區塊 #100,000。如果一些礦工不是試圖挖掘區塊 #100,001 來擴展鏈,而是試圖重新挖掘區塊 #100,000。這些礦工可以選擇在其候選區塊 #100,000 中包含任何有效交易(尚未被挖掘)。他們不必用相同的交易重新挖掘區塊。事實上,他們有動機選擇最有利可圖的(每 kB 最高手續費)交易包含在其區塊中。他們可以包含「舊」區塊 #100,000 中的任何交易,以及當前記憶池中的任何交易。本質上,當他們重新創建區塊 #100,000 時,他們可以選擇將交易從「現在」拉到重寫的「過去」。
今天,這種攻擊不是非常有利可圖,因為區塊補貼遠高於每個區塊的總手續費。但在未來的某個時候,交易手續費將佔獎勵的大部分(甚至是全部獎勵)。那時,這種情況變得不可避免。
幾個錢包透過創建帶有鎖定時間的交易來阻止手續費狙擊,該鎖定時間將這些交易限制為僅包含在下一個區塊或任何後續區塊中。在我們的情境中,我們的錢包會將其創建的任何交易的鎖定時間設定為 100,001。在正常情況下,此鎖定時間沒有效果——無論如何,交易只能包含在區塊 #100,001 中;這是下一個區塊。
但在重組攻擊下,礦工將無法從記憶池中提取高手續費交易,因為所有這些交易都將被時間鎖定到區塊 #100,001。他們只能使用當時有效的任何交易重新挖掘區塊 #100,000,本質上沒有獲得新的手續費。
這並不能完全防止手續費狙擊,但在某些情況下確實使其獲利較少,並且可以在區塊補貼下降時幫助保持 Bitcoin 網路的穩定性。我們建議所有錢包在不干擾錢包對鎖定時間欄位的其他使用時實現反手續費狙擊。
隨著 Bitcoin 繼續成熟,隨著補貼繼續下降,手續費對 Bitcoin 使用者變得越來越重要,無論是在他們日常使用中快速確認交易,還是在為礦工提供繼續用新的工作量證明保護 Bitcoin 交易的動機方面。
比特幣網路
Bitcoin 是在網際網路之上構建的點對點網路架構。點對點(或 P2P)一詞意味著參與網路的完整節點彼此是對等的,它們都可以執行相同的功能,並且沒有「特殊」節點。網路節點在網狀網路中以「扁平」拓撲相互連接。網路中沒有伺服器、沒有中心化服務,也沒有階層。P2P 網路中的節點同時提供和消費服務。P2P 網路本質上具有彈性、去中心化和開放性。P2P 網路架構的一個卓越範例是早期的網際網路本身,其中 IP 網路上的節點是平等的。今天的網際網路架構更具階層性,但網際網路協議仍然保留其扁平拓撲的本質。除了 Bitcoin 和網際網路之外,P2P 技術最大和最成功的應用是檔案共享,Napster 是先驅,BitTorrent 是該架構的最新演進。
Bitcoin 的 P2P 網路架構不僅僅是拓撲選擇。Bitcoin 在設計上是一個 P2P 數位現金系統,網路架構既是該核心特性的反映,也是其基礎。控制權的去中心化是核心設計原則,只能透過扁平和去中心化的 P2P 共識網路來實現和維護。
「Bitcoin 網路」一詞是指執行 Bitcoin P2P 協議的節點集合。除了 Bitcoin P2P 協議之外,還有其他用於挖礦和輕量級錢包的協議。這些額外的協議由閘道路由伺服器提供,這些伺服器使用 Bitcoin P2P 協議存取 Bitcoin 網路,然後將該網路擴展到執行其他協議的節點。例如,Stratum 伺服器透過 Stratum 協議將 Stratum 挖礦節點連接到主要的 Bitcoin 網路,並將 Stratum 協議橋接到 Bitcoin P2P 協議。除了基礎的 Bitcoin P2P 協議之外,我們將在本章中描述一些最常用的協議。
節點類型和角色
儘管Bitcoin P2P 網路中的完整節點(對等節點)彼此平等,但它們可能根據其支援的功能承擔不同的角色。Bitcoin 完整節點驗證區塊,並可能包含其他功能,例如路由、挖礦和錢包服務。
一些稱為_存檔完整節點_的節點也維護區塊鏈的完整和最新副本。這些節點可以向僅儲存區塊鏈子集並使用一種稱為_簡化支付驗證_(SPV)的方法部分驗證交易的客戶端提供資料。這些客戶端被稱為輕量級客戶端。
礦工透過執行專用硬體來解決工作量證明演算法,以競爭創建新區塊。一些礦工經營完整節點,驗證區塊鏈上的每個區塊,而其他礦工是參與礦池挖礦的客戶端,並依賴礦池伺服器為他們提供工作。
使用者錢包可能連接到使用者自己的完整節點,這在桌面 Bitcoin 客戶端中有時是這種情況,但許多使用者錢包,特別是那些在資源受限裝置(例如智慧型手機)上執行的錢包,是輕量級節點。
除了 Bitcoin P2P 協議上的主要節點類型之外,還有執行其他協議的伺服器和節點,例如專用的礦池協議和輕量級客戶端存取協議。
網路
截至本文撰寫時,執行 Bitcoin P2P 協議的主要 Bitcoin 網路由大約 10,000 個監聽節點組成,這些節點執行各種版本的 Bitcoin Core,以及數百個節點執行 Bitcoin P2P 協議的其他各種實現,例如 BitcoinJ、btcd 和 bcoin。Bitcoin P2P 網路上的一小部分節點也是挖礦節點。各種個人和公司透過執行存檔完整節點與 Bitcoin 網路介接,具有區塊鏈的完整副本和網路節點,但沒有挖礦或錢包功能。這些節點充當網路邊緣路由器,允許在其上建立各種其他服務(交易所、錢包、區塊瀏覽器、商家支付處理)。
緊湊區塊中繼
當礦工找到新區塊時,他們會向 Bitcoin 網路(包括其他礦工)宣布它。找到該區塊的礦工可以立即開始在其頂部建立;所有尚未了解該區塊的其他礦工將繼續在先前的區塊頂部建立,直到他們了解它為止。
如果在他們了解新區塊之前,其中一個其他礦工創建了一個區塊,他們的區塊將與第一個礦工的新區塊競爭。只有一個區塊會被所有完整節點使用的區塊鏈包含,礦工只會因被廣泛接受的區塊而獲得報酬。
哪個區塊首先在其頂部建立第二個區塊就獲勝(除非出現另一次接近的平局),這被稱為_區塊發現競賽_,如 需要挖礦競賽的區塊鏈分叉。 所示。區塊發現競賽給最大的礦工帶來優勢,因此它們與 Bitcoin 的基本去中心化相對立。為了防止區塊發現競賽並允許任何規模的礦工平等參與 Bitcoin 挖礦這個抽獎,最小化一個礦工宣布新區塊與其他礦工收到該區塊之間的時間非常有用。
 
2015 年,新版本的 Bitcoin Core 添加了一個稱為_緊湊區塊中繼_的功能(在 BIP152 中指定),允許更快地傳輸新區塊並使用更少的頻寬。
作為背景,中繼未確認交易的完整節點也在其記憶池中儲存許多這些交易(請參見 記憶池和孤兒池)。當其中一些交易在新區塊中確認時,節點不需要接收這些交易的第二個副本。
緊湊區塊允許對等節點為每筆交易發送一個簡短的 6 位元組識別碼,而不是接收冗餘的未確認交易。當您的節點收到帶有一個或多個識別碼的緊湊區塊時,它會檢查其記憶池中的這些交易,如果找到則使用它們。對於在您的本地節點記憶池中找不到的任何交易,您的節點可以向對等節點發送請求以獲取副本。
相反,如果遠端對等節點認為您的節點記憶池沒有區塊中出現的某些交易,它可以在緊湊區塊中包含這些交易的副本。例如,Bitcoin Core 始終發送區塊的 coinbase 交易。
如果遠端對等節點正確猜測您的節點記憶池中有哪些交易,以及沒有哪些交易,它將以幾乎理論上可能的效率發送區塊(對於典型的區塊,效率將在 97% 到 99% 之間)。
| 緊湊區塊中繼不會減小區塊的大小。它只是防止節點已經擁有的資訊的冗餘傳輸。當節點之前沒有關於區塊的資訊時,例如當節點首次啟動時,它必須接收每個區塊的完整副本。 | 
Bitcoin Core 目前為發送緊湊區塊實現了兩種模式,如 BIP152 模式比較(來自 BIP152)。陰影條表示節點驗證區塊所需的時間。 所示:
- 低頻寬模式
- 
當您的節點請求對等節點使用低頻寬模式(預設)時,該對等節點將告訴您的節點新區塊的 32 位元組識別碼(標頭雜湊),但不會向您的節點發送有關它的任何詳細資訊。如果您的節點首先從另一個來源獲取該區塊,這可以避免浪費任何更多的頻寬獲取該區塊的冗餘副本。如果您的節點確實需要該區塊,它將請求緊湊區塊。 
- 高頻寬模式
- 
當您的節點請求對等節點使用高頻寬模式時,該對等節點將向您的節點發送新區塊的緊湊區塊,甚至在它完全驗證該區塊有效之前。對等節點將執行的唯一驗證是確保區塊的標頭包含正確數量的工作量證明。由於工作量證明的生成成本很高(在撰寫本文時約為 150,000 美元),礦工不太可能偽造它只是為了浪費中繼節點的頻寬。在中繼之前跳過驗證允許新區塊以每跳的最小延遲在網路中傳播。 高頻寬模式的缺點是您的節點可能會從它選擇的每個高頻寬對等節點接收冗餘資訊。截至本文撰寫時,Bitcoin Core 目前僅要求三個對等節點使用高頻寬模式(並且它試圖選擇具有快速宣布區塊歷史的對等節點)。 
 
這兩種方法的名稱(取自 BIP152)可能有點令人困惑。低頻寬模式透過在大多數情況下不發送區塊來節省頻寬。高頻寬模式使用的頻寬比低頻寬模式多,但在大多數情況下,比實現緊湊區塊之前用於區塊中繼的頻寬少得多。
私有區塊中繼網路
儘管緊湊區塊在最小化區塊在網路中傳播所需的時間方面大有幫助,但可以進一步最小化延遲。然而,與緊湊區塊不同,其他解決方案涉及權衡,使它們無法用於或不適合公共 P2P 中繼網路。因此,人們對區塊的私有中繼網路進行了實驗。
一種簡單的技術是預先選擇端點之間的路由。例如,在靠近主要跨洋光纖線路的資料中心執行伺服器的中繼網路可能能夠比等待區塊到達距離光纖線路數公里的某個家庭使用者執行的節點更快地轉發新區塊。
另一種更複雜的技術是前向錯誤更正(FEC)。這允許將緊湊區塊訊息拆分為幾個部分,每個部分都附加了額外的資料。如果其中任何部分未收到,則可以從收到的部分重建該部分。根據設定,如果遺失,最多可以重建幾個部分。
FEC 避免了由於底層網路連接問題而導致緊湊區塊(或其某些部分)未到達的問題。這些問題經常發生,但我們通常不會注意到它們,因為我們主要使用自動重新請求遺失資料的協議。但是,請求遺失的資料會使接收時間增加三倍。例如:
- 
Alice 向 Bob 發送一些資料。 
- 
Bob 沒有收到資料(或資料已損壞)。Bob 向 Alice 重新請求資料。 
- 
Alice 再次發送資料。 
第三種技術是假設所有接收資料的節點在其記憶池中幾乎擁有所有相同的交易,因此它們都可以接受相同的緊湊區塊。這不僅可以節省我們在每跳計算緊湊區塊的時間,而且意味著每跳都可以在驗證之前簡單地將 FEC 封包中繼到下一跳。
前面每種方法的權衡是它們在中心化情況下運作良好,但在去中心化網路中(個別節點無法信任其他節點)則不然。資料中心的伺服器需要花錢,並且通常可以由資料中心的營運商存取,使它們比安全的家用電腦更不可信。在驗證之前中繼資料很容易浪費頻寬,因此只能在私有網路上合理使用,在該網路中各方之間存在一定程度的信任和問責。
開發人員 Matt Corallo 於 2015 年創建了原始的 Bitcoin Relay Network,以實現礦工之間以非常低的延遲快速同步區塊。該網路由託管在世界各地基礎設施上的幾個虛擬私有伺服器(VPS)組成,並用於連接大多數礦工和礦池。
2016 年,隨著("快速網際網路比特幣中繼引擎(FIBRE)"_快速網際網路比特幣中繼引擎_或 FIBRE 的引入,原始的 Bitcoin Relay Network 被取代,該引擎也由開發人員 Matt Corallo 創建。FIBRE 是允許操作基於 UDP 的中繼網路的軟體,該網路在節點網路內中繼區塊。FIBRE 實現了 FEC 和_緊湊區塊_優化,以進一步減少傳輸的資料量和網路延遲。
網路發現
當新節點啟動時,它必須發現網路上的其他 Bitcoin 節點才能參與。要開始此過程,新節點必須發現網路上至少一個現有節點並連接到它。其他節點的地理位置無關;Bitcoin 網路拓撲沒有地理定義。因此,可以隨機選擇任何現有的 Bitcoin 節點。
要連接到已知的對等節點,節點會建立 TCP 連接,通常連接到埠 8333(通常稱為 Bitcoin 使用的埠),或者如果提供了替代埠,則連接到替代埠。建立連接後,節點將透過傳輸 version 訊息來啟動「握手」(請參見 對等節點之間的初始握手。),該訊息包含基本識別資訊,包括:
- Version
- 
客戶端「說」的 Bitcoin P2P 協議版本(例如,70002) 
- nLocalServices
- 
節點支援的本地服務列表 
- nTime
- 
當前時間 
- addrYou
- 
從該節點看到的遠端節點的 IP 位址 
- addrMe
- 
本地節點發現的本地節點的 IP 位址 
- subver
- 
顯示在此節點上執行的軟體類型的子版本(例如,/Satoshi:0.9.2.1/) 
- BestHeight
- 
此節點區塊鏈的區塊高度 
- fRelay
- 
BIP37 添加的欄位,用於請求不接收未確認的交易 
version 訊息始終是任何對等節點向另一個對等節點發送的第一條訊息。接收 version 訊息的本地對等節點將檢查遠端對等節點報告的 Version,並決定遠端對等節點是否相容。如果遠端對等節點相容,本地對等節點將確認 version 訊息並透過發送 verack 建立連接。
新節點如何找到對等節點?第一種方法是使用多個 _DNS 種子_查詢 DNS,這些DNS 伺服器提供 Bitcoin 節點的 IP 位址列表。其中一些 DNS 種子提供穩定 Bitcoin 監聽節點的靜態 IP 位址列表。一些 DNS 種子是 BIND(Berkeley Internet Name Daemon)的自訂實現,它們從由爬蟲或長時間執行的 Bitcoin 節點收集的 Bitcoin 節點位址列表中返回隨機子集。Bitcoin Core 客戶端包含幾個不同 DNS 種子的名稱。不同 DNS 種子的所有權多樣性和實現多樣性為初始引導過程提供了高度的可靠性。在 Bitcoin Core 客戶端中,使用 DNS 種子的選項由選項開關 -dnsseed 控制(預設設定為 1,以使用 DNS 種子)。
或者,對網路一無所知的引導節點必須獲得至少一個 Bitcoin 節點的 IP 位址,之後它可以透過進一步的介紹建立連接。命令列參數 -seednode 可用於連接到一個節點,僅將其用作種子進行介紹。在使用初始種子節點進行介紹後,客戶端將與其斷開連接並使用新發現的對等節點。
 
建立一個或多個連接後,新節點將向其鄰居發送包含其自己 IP 位址的 addr 訊息。鄰居將依次將 addr 訊息轉發給他們的鄰居,確保新連接的節點廣為人知並更好地連接。此外,新連接的節點可以向其鄰居發送 getaddr,要求他們返回其他對等節點的 IP 位址列表。這樣,節點可以找到要連接的對等節點,並在網路上宣傳其存在,以便其他節點找到它。位址傳播和發現。 顯示了位址發現協議。
 
節點必須連接到幾個不同的對等節點,以便建立到 Bitcoin 網路的多樣化路徑。路徑不可靠——節點來來去去——因此節點必須在失去舊連接時繼續發現新節點,並在其他節點引導時協助它們。只需要一個連接即可引導,因為第一個節點可以向其對等節點提供介紹,並且這些對等節點可以提供進一步的介紹。連接到超過少數幾個節點也是不必要的,並且會浪費網路資源。引導後,節點將記住其最近成功的對等連接,以便如果它重新啟動,它可以快速與其先前的對等網路重新建立連接。如果先前的對等節點都沒有回應其連接請求,該節點可以再次使用種子節點進行引導。
在執行 Bitcoin Core 客戶端的節點上,您可以使用命令 getpeerinfo 列出對等連接:
$ bitcoin-cli getpeerinfo[
  {
    "id": 0,
    "addr": "82.64.116.5:8333",
    "addrbind": "192.168.0.133:50564",
    "addrlocal": "72.253.6.11:50564",
    "network": "ipv4",
    "services": "0000000000000409",
    "servicesnames": [
      "NETWORK",
      "WITNESS",
      "NETWORK_LIMITED"
    ],
    "lastsend": 1683829947,
    "lastrecv": 1683829989,
    "last_transaction": 0,
    "last_block": 1683829989,
    "bytessent": 3558504,
    "bytesrecv": 6016081,
    "conntime": 1683647841,
    "timeoffset": 0,
    "pingtime": 0.204744,
    "minping": 0.20337,
    "version": 70016,
    "subver": "/Satoshi:24.0.1/",
    "inbound": false,
    "bip152_hb_to": true,
    "bip152_hb_from": false,
    "startingheight": 788954,
    "presynced_headers": -1,
    "synced_headers": 789281,
    "synced_blocks": 789281,
    "inflight": [
    ],
    "relaytxes": false,
    "minfeefilter": 0.00000000,
    "addr_relay_enabled": false,
    "addr_processed": 0,
    "addr_rate_limited": 0,
    "permissions": [
    ],
    "bytessent_per_msg": {
      ...
    },
    "bytesrecv_per_msg": {
      ...
    },
    "connection_type": "block-relay-only"
  },
]要覆寫對等節點的自動管理並指定 IP 位址列表,使用者可以提供選項 -connect=<IPAddress> 並指定一個或多個 IP 位址。如果使用此選項,節點將僅連接到選定的 IP 位址,而不是自動發現和維護對等連接。
如果連接上沒有流量,節點將定期發送訊息以維護連接。如果節點在連接上長時間沒有通訊,則假定它已斷開連接,並將尋找新的對等節點。因此,網路動態地適應暫時的節點和網路問題,並可以根據需要有機地成長和收縮,無需任何中心控制。
完整節點
完整節點是在具有最多工作量證明的有效區塊鏈上驗證每個區塊中每筆交易的節點。
完整節點獨立處理每個區塊,從第一個區塊(創世區塊)之後開始,一直建立到網路中最新的已知區塊。完整節點可以獨立且權威地驗證任何交易。完整節點依賴網路接收有關新交易區塊的更新,然後驗證這些更新並將其納入其本地視圖中,了解哪些腳本控制哪些比特幣,稱為("UTXO(未花費交易輸出)"未花費交易輸出(UTXO)集。
執行完整節點為您提供純粹的 Bitcoin 體驗:獨立驗證所有交易,無需依賴或信任任何其他系統。
有一些完整節點的替代實現,使用不同的程式語言和軟體架構構建,或者做出了不同的設計決策。但是,最常見的實現是 Bitcoin Core。Bitcoin 網路上超過 95% 的完整節點執行各種版本的 Bitcoin Core。它在 version 訊息中發送的子版本字串中被識別為「Satoshi」,並由命令 getpeerinfo 顯示,如我們之前看到的;例如,/Satoshi:24.0.1/。
交換「清單」
完整節點連接到對等節點後首先要做的事情是嘗試構建完整的區塊標頭鏈。如果它是一個全新的節點,並且根本沒有區塊鏈,它只知道一個區塊,即創世區塊,該區塊靜態嵌入在客戶端軟體中。從區塊 #0(創世區塊)之後開始,新節點將必須下載數十萬個區塊才能與網路同步並重新建立完整的區塊鏈。
同步區塊鏈的過程從 version 訊息開始,因為該訊息包含 BestHeight,即節點的當前區塊鏈高度(區塊數)。節點將看到來自其對等節點的 version 訊息,知道它們各自擁有多少個區塊,並能夠與它自己的區塊鏈中擁有的區塊數進行比較。對等節點將交換 getheaders 訊息,該訊息包含其本地區塊鏈頂部區塊的雜湊。其中一個對等節點將能夠將接收到的雜湊識別為屬於不在頂部的區塊,而是屬於較舊的區塊,從而推斷其自己的本地區塊鏈比遠端節點的區塊鏈長。
擁有較長區塊鏈的對等節點比另一個節點擁有更多的區塊,並且可以識別另一個節點需要哪些標頭才能「趕上」。它將使用 headers 訊息識別要共享的前 2,000 個標頭。節點將繼續請求額外的標頭,直到它收到遠端對等節點聲稱擁有的每個區塊的標頭。
同時,節點將開始使用 getdata 訊息請求先前收到的每個標頭的區塊。節點將從其每個選定的對等節點請求不同的區塊,這使它能夠斷開與明顯慢於平均速度的對等節點的連接,以便找到較新(可能更快)的對等節點。
例如,假設一個節點只有創世區塊。然後,它將從其對等節點接收 headers 訊息,其中包含鏈中接下來 2,000 個區塊的標頭。它將開始從所有連接的對等節點請求區塊,保持最多 1,024 個區塊的佇列。區塊需要按順序驗證,因此如果佇列中最舊的區塊——節點接下來需要驗證的區塊——尚未收到,節點會斷開與應該提供該區塊的對等節點的連接。然後,它會找到一個新的對等節點,該節點可能能夠在節點的所有其他對等節點能夠提供 1,023 個區塊之前提供一個區塊。
當收到每個區塊時,它會被添加到區塊鏈中,如我們將在 區塊鏈 中看到的。隨著本地區塊鏈逐漸建立,將請求和接收更多區塊,該過程將繼續,直到節點趕上網路的其餘部分。
這個將本地區塊鏈與對等節點進行比較並檢索任何遺失區塊的過程會在節點離線一段時間後發生。
輕量級客戶端
許多Bitcoin 客戶端設計為在空間和功率受限的裝置上執行,例如智慧型手機、平板電腦或嵌入式系統。對於此類裝置,使用_簡化支付驗證_(SPV)方法,允許它們在不驗證完整區塊鏈的情況下運作。這些類型的客戶端稱為輕量級客戶端。
輕量級客戶端僅下載區塊標頭,不下載每個區塊中包含的交易。產生的標頭鏈(不包含交易)比完整區塊鏈小約 10,000 倍。輕量級客戶端無法構建可用於花費的所有 UTXO 的完整圖片,因為它們不知道網路上的所有交易。相反,它們使用稍微不同的方法驗證交易,該方法依賴於對等節點按需提供區塊鏈相關部分的部分視圖。
作為類比,完整節點就像一個陌生城市的遊客,配備了每條街道和每個地址的詳細地圖。相比之下,輕量級客戶端就像一個陌生城市的遊客,在只知道一條主要大道的情況下向隨機陌生人詢問逐轉彎指示。雖然兩位遊客都可以透過訪問街道來驗證街道的存在,但沒有地圖的遊客不知道任何小巷下有什麼,也不知道存在哪些其他街道。站在 23 Church Street 前面,沒有地圖的遊客無法知道城市中是否還有十幾個其他 「23 Church Street」地址,以及這是否是正確的地址。沒有地圖的遊客最好的機會是詢問足夠多的人,並希望其中一些人不是想搶劫他。
輕量級客戶端透過參考其在區塊鏈中的_深度_來驗證交易。完整節點將構建一個完全驗證的鏈,其中包含數千個區塊和數百萬筆交易,一直沿著區塊鏈向下(回到過去)到創世區塊,而輕量級客戶端將驗證所有區塊的工作量證明(但不驗證區塊及其所有交易是否有效),並將該鏈連結到感興趣的交易。
例如,在檢查區塊 800,000 中的交易時,完整節點會驗證從創世區塊到 800,000 的所有區塊,並建立完整的 UTXO 資料庫,透過確認交易存在且其輸出仍未花費來確立交易的有效性。輕量級客戶端只能驗證交易存在。客戶端使用 merkle 路徑(請參見 默克爾樹)在交易和包含它的區塊之間建立連結。然後,輕量級客戶端等待,直到它看到堆疊在包含該交易的區塊頂部的六個區塊 800,001 到 800,006,並透過在區塊 800,006 到 800,001 下建立其深度來驗證它。網路上其他節點接受了區塊 800,000,並且礦工完成了在其頂部產生另外六個區塊所需的工作這一事實,透過代理證明了交易實際存在。
通常無法說服輕量級客戶端交易存在於區塊中(當交易實際上不存在時)。輕量級客戶端透過請求 merkle 路徑證明並透過驗證區塊鏈中的工作量證明來確立交易在區塊中的存在。但是,交易的存在可以對輕量級客戶端「隱藏」。輕量級客戶端絕對可以驗證交易存在,但無法驗證交易(例如同一 UTXO 的雙重花費)不存在,因為它沒有所有交易的記錄。這個漏洞可用於針對輕量級客戶端的拒絕服務攻擊或雙重花費攻擊。為了防禦這一點,輕量級客戶端需要隨機連接到多個客戶端,以增加它與至少一個誠實節點接觸的機率。這種需要隨機連接意味著輕量級客戶端也容易受到網路分區攻擊或女巫攻擊,在這些攻擊中,它們連接到假節點或假網路,無法存取誠實節點或真實的 Bitcoin 網路。
對於許多實際目的,連接良好的輕量級客戶端足夠安全,在資源需求、實用性和安全性之間取得平衡。然而,對於萬無一失的安全性,沒有什麼比執行完整節點更好。
| 完整節點透過檢查其下方數千個區塊的整個鏈來驗證交易,以保證 UTXO 存在且未花費,而輕量級客戶端僅證明交易存在,並檢查包含該交易的區塊是否被其上方的少數區塊掩埋。 | 
要獲取驗證交易是鏈的一部分所需的區塊標頭,輕量級客戶端使用 getheaders 訊息。回應的對等節點將使用單個 headers 訊息發送最多 2,000 個區塊標頭。請參見 輕量級客戶端同步區塊標頭。 中的插圖。
 
區塊標頭允許輕量級客戶端驗證任何單獨的區塊屬於具有最多工作量證明的區塊鏈,但它們不會告訴客戶端哪些區塊包含其錢包感興趣的交易。客戶端可以下載每個區塊並檢查,但這將使用執行完整節點所需資源的很大一部分,因此開發人員一直在尋找其他方法來解決這個問題。
在輕量級客戶端引入後不久,Bitcoin 開發人員添加了一個稱為_布隆過濾器_的功能,試圖減少輕量級客戶端了解其傳入和傳出交易所需使用的頻寬。布隆過濾器允許輕量級客戶端接收交易的子集,而無需直接準確地揭示它們感興趣的地址,透過使用機率而不是固定模式的過濾機制。
布隆過濾器
布隆過濾器是一種機率搜尋過濾器,是一種描述所需模式而無需精確指定它的方式。布隆過濾器提供了一種有效的方式來表達搜尋模式,同時保護隱私。它們被輕量級客戶端用來向其對等節點詢問與特定模式匹配的交易,而無需準確揭示它們正在搜尋哪些地址、金鑰或交易。
在我們之前的類比中,一個沒有地圖的遊客正在詢問前往特定地址「23 Church St.」的方向。如果他們向陌生人詢問前往這條街道的方向,他們就會無意中洩露了他們的目的地。布隆過濾器就像詢問「這個社區有沒有名稱以 R-C-H 結尾的街道?」這樣的問題比詢問「23 Church St.」洩露的目的地資訊稍微少一些。使用這種技術,遊客可以更詳細地指定所需地址,例如「以 U-R-C-H 結尾」,或者不那麼詳細,例如「以 H 結尾」。透過改變搜尋的精確度,遊客會以獲得更多或更少的特定結果為代價來洩露更多或更少的資訊。如果他們詢問一個不太特定的模式,他們會得到更多可能的地址和更好的隱私,但許多結果是不相關的。如果他們詢問一個非常特定的模式,他們會得到更少的結果,但失去隱私。
布隆過濾器透過允許輕量級客戶端為交易指定一個搜尋模式來實現此功能,該模式可以根據精確度或隱私進行調整。更特定的布隆過濾器將產生準確的結果,但代價是洩露輕量級客戶端感興趣的模式,從而洩露使用者錢包擁有的地址。不太特定的布隆過濾器將產生更多關於更多交易的資料,其中許多與客戶端無關,但將允許客戶端維持更好的隱私。
布隆過濾器的運作方式
布隆過濾器被實現為 N 個二進位數字的可變大小陣列(位元欄位)和可變數量的 M 個雜湊函數。雜湊函數被設計為始終產生 1 到 N 之間的輸出,對應於二進位數字陣列。雜湊函數是確定性生成的,因此任何實現布隆過濾器的客戶端將始終使用相同的雜湊函數,並對特定輸入獲得相同的結果。透過選擇不同長度(N)的布隆過濾器和不同數量(M)的雜湊函數,可以調整布隆過濾器,改變準確度和隱私的等級。
在 一個簡單的布隆過濾器範例,具有 16 位元欄位和三個雜湊函數。 中,我們使用一個非常小的 16 位元陣列和一組三個雜湊函數來演示布隆過濾器的運作方式。
 
布隆過濾器被初始化,使得位元陣列全部為零。要將模式添加到布隆過濾器,該模式依次由每個雜湊函數進行雜湊處理。將第一個雜湊函數應用於輸入會產生一個 1 到 N 之間的數字。找到陣列中的對應位元(從 1 到 N 索引),並將其設定為 1,從而記錄雜湊函數的輸出。然後,使用下一個雜湊函數來設定另一個位元,依此類推。一旦應用了所有 M 個雜湊函數,搜尋模式將作為已從 0 更改為 1 的 M 個位元「記錄」在布隆過濾器中。
將模式「A」添加到我們的簡單布隆過濾器。 是將模式「A」添加到 一個簡單的布隆過濾器範例,具有 16 位元欄位和三個雜湊函數。 中所示的簡單布隆過濾器的範例。
添加第二個模式就像重複此過程一樣簡單。該模式依次由每個雜湊函數進行雜湊處理,並透過將位元設定為 1 來記錄結果。請注意,隨著布隆過濾器填充更多模式,雜湊函數結果可能與已設定為 1 的位元重合,在這種情況下,位元不會更改。本質上,隨著更多模式記錄在重疊位元上,布隆過濾器開始變得飽和,更多位元被設定為 1,過濾器的準確度會降低。這就是為什麼過濾器是一個機率資料結構——隨著添加更多模式,它變得不太準確。準確度取決於添加的模式數量與位元陣列大小(N)和雜湊函數數量(M)的比較。更大的位元陣列和更多的雜湊函數可以以更高的準確度記錄更多模式。較小的位元陣列或較少的雜湊函數將記錄較少的模式,並產生較低的準確度。
 
將第二個模式「B」添加到我們的簡單布隆過濾器。 是將第二個模式「B」添加到簡單布隆過濾器的範例。
 
要測試模式是否是布隆過濾器的一部分,該模式由每個雜湊函數進行雜湊處理,並將產生的位元模式與位元陣列進行測試。如果雜湊函數索引的所有位元都設定為 1,則該模式_可能_記錄在布隆過濾器中。由於位元可能因為多個模式的重疊而被設定,因此答案不是確定的,而是機率的。簡單來說,布隆過濾器的正面匹配是「可能,是的。」
測試布隆過濾器中模式「X」的存在。結果是機率正面匹配,意思是「可能」。 是測試簡單布隆過濾器中模式「X」是否存在的範例。對應的位元設定為 1,因此該模式可能是匹配的。
 
相反地,如果對布隆過濾器測試一個模式,並且任何一個位元設定為 0,這證明該模式沒有記錄在布隆過濾器中。負面結果不是機率,它是確定性的。簡單來說,布隆過濾器的負面匹配是「絕對不是!」
測試布隆過濾器中模式「Y」的存在。結果是確定性負面匹配,意思是「絕對不是!」 是測試簡單布隆過濾器中模式「Y」是否存在的範例。其中一個對應的位元設定為 0,因此該模式絕對不是匹配的。
 
輕量級客戶端如何使用布隆過濾器
布隆過濾器被用來過濾輕量級客戶端從其對等節點接收的交易(和包含它們的區塊),選擇僅對輕量級客戶端感興趣的交易,而無需準確揭示它感興趣的地址或金鑰。
輕量級客戶端將布隆過濾器初始化為「空」;在該狀態下,布隆過濾器將不匹配任何模式。然後,輕量級客戶端將列出它感興趣的所有地址、金鑰和雜湊。它將透過從其錢包控制的任何 UTXO 中提取公鑰雜湊、腳本雜湊和交易 ID 來執行此操作。然後,輕量級客戶端將這些每一個添加到布隆過濾器中,以便如果這些模式出現在交易中,布隆過濾器將「匹配」,而無需洩露模式本身。
然後,輕量級客戶端將發送一個 filterload 訊息給對等節點,其中包含要在連接上使用的布隆過濾器。在對等節點上,布隆過濾器會針對每個傳入交易進行檢查。完整節點會根據布隆過濾器檢查交易的幾個部分,尋找匹配,包括:
- 交易 ID
- 每個交易輸出的腳本中的資料元件(腳本中的每個金鑰和雜湊)
- 每個交易輸入
- 每個輸入簽章資料元件(或見證腳本)
透過檢查所有這些元件,布隆過濾器可用於匹配公鑰雜湊、腳本、OP_RETURN 值、簽章中的公鑰,或任何未來的智慧合約或複雜腳本元件。
建立過濾器後,對等節點將針對布隆過濾器測試每個交易的輸出。只有匹配過濾器的交易才會被發送到客戶端。
為了回應來自客戶端的 getdata 訊息,對等節點將發送一個 merkleblock 訊息,其中僅包含與過濾器匹配的區塊的區塊標頭,以及每個匹配交易的默克爾路徑(參見 默克爾樹)。然後,對等節點還將發送包含過濾器匹配的交易的 tx 訊息。
當完整節點向輕量級客戶端發送交易時,輕量級客戶端會丟棄任何誤報,並使用正確匹配的交易來更新其 UTXO 集和錢包餘額。當它更新自己對 UTXO 集的檢視時,它也會修改布隆過濾器以匹配任何未來引用它剛剛找到的 UTXO 的交易。然後,完整節點使用新的布隆過濾器來匹配新交易,整個過程重複。
設定布隆過濾器的客戶端可以透過發送 filteradd 訊息以互動方式將模式添加到過濾器。要清除布隆過濾器,客戶端可以發送 filterclear 訊息。由於無法從布隆過濾器中刪除模式,因此如果不再需要某個模式,客戶端必須清除並重新發送新的布隆過濾器。
輕量級客戶端的網路協議和布隆過濾器機制在 BIP37 中定義。
不幸的是,在部署布隆過濾器之後,很明顯它們並沒有提供太多隱私。接收來自對等節點的布隆過濾器的完整節點可以將該過濾器應用於整個區塊鏈,以找到客戶端的所有交易(加上誤報)。然後,它可以尋找交易之間的模式和關係。隨機選擇的誤報交易不太可能具有從輸出到輸入的父子關係,但來自使用者錢包的交易很可能具有該關係。如果所有相關交易都具有某些特徵,例如至少一個 P2PKH 輸出,則可以假設沒有該特徵的交易不屬於該錢包。
還發現了特別構造的過濾器可能會強制處理它們的完整節點執行大量工作,這可能導致拒絕服務攻擊。
由於這兩個原因,Bitcoin Core 最終將對布隆過濾器的支援限制為僅限於節點營運者明確允許的 IP 地址上的客戶端。這意味著需要一種替代方法來幫助輕量級客戶端找到它們的交易。
緊湊區塊過濾器
2016 年,一位匿名開發人員在 Bitcoin-Dev 郵件列表中提出了一個想法,即反轉布隆過濾器過程。使用 BIP37 布隆過濾器,每個客戶端對其地址進行雜湊處理以創建布隆過濾器,節點對每個交易的部分進行雜湊處理以嘗試匹配該過濾器。在新提案中,節點對區塊中每個交易的部分進行雜湊處理以創建布隆過濾器,客戶端對其地址進行雜湊處理以嘗試匹配該過濾器。如果客戶端找到匹配,它們會下載整個區塊。
| 儘管名稱相似,BIP152 緊湊區塊 和 BIP157/158 緊湊區塊過濾器 是不相關的。 | 
這允許節點為每個區塊創建單個過濾器,他們可以將其保存到磁碟並一次又一次地提供服務,消除了 BIP37 的拒絕服務漏洞。客戶端不會向完整節點提供有關其過去或未來地址的任何資訊。它們只下載區塊,其中可能包含數千筆不是由客戶端創建的交易。它們甚至可以從不同的對等節點下載每個匹配的區塊,使完整節點更難連接屬於單個客戶端的跨多個區塊的交易。
這個服務器生成的過濾器想法並不提供完美的隱私;它仍然對完整節點產生一些成本(並且它確實需要輕量級客戶端使用更多頻寬進行區塊下載),並且過濾器只能用於已確認的交易(而不是未確認的交易)。然而,它比 BIP37 客戶端請求的布隆過濾器更加私密和可靠。
在描述了基於布隆過濾器的原始想法之後,開發人員意識到有一種更好的資料結構用於服務器生成的過濾器,稱為 Golomb-Rice 編碼集合(GCS)。
Golomb-Rice 編碼集合(GCS)
假設Alice 想向 Bob 發送一個數字列表。簡單的方法是只向他發送整個數字列表:
849 653 476 900 379
但有一種更有效的方法。首先,Alice 按數字順序排列列表:
379 476 653 849 900
然後,Alice 發送第一個數字。對於剩餘的數字,她發送該數字與前一個數字之間的差異。例如,對於第二個數字,她發送 97(476 – 379);對於第三個數字,她發送 177(653 – 476);依此類推:
379 97 177 196 51
我們可以看到,有序列表中兩個數字之間的差異產生的數字比原始數字短。收到此列表後,Bob 可以透過簡單地將每個數字與其前一個數字相加來重建原始列表。這意味著我們在不損失任何資訊的情況下節省了空間,這被稱為_無損編碼_。
如果我們在固定值範圍內隨機選擇數字,那麼我們選擇的數字越多,差異的平均(平均)大小就越小。這意味著我們需要傳輸的資料量不會像列表長度增加那樣快速增加(達到一定程度)。
更有用的是,差異列表中隨機選擇的數字的長度自然偏向較小的長度。考慮從 1 到 6 中選擇兩個隨機數字;這與擲兩個骰子相同。有 36 種不同的兩個骰子組合:
| 1 1 | 1 2 | 1 3 | 1 4 | 1 5 | 1 6 | 
| 2 1 | 2 2 | 2 3 | 2 4 | 2 5 | 2 6 | 
| 3 1 | 3 2 | 3 3 | 3 4 | 3 5 | 3 6 | 
| 4 1 | 4 2 | 4 3 | 4 4 | 4 5 | 4 6 | 
| 5 1 | 5 2 | 5 3 | 5 4 | 5 5 | 5 6 | 
| 6 1 | 6 2 | 6 3 | 6 4 | 6 5 | 6 6 | 
讓我們找出較大數字和較小數字之間的差異:
| 0 | 1 | 2 | 3 | 4 | 5 | 
| 1 | 0 | 1 | 2 | 3 | 4 | 
| 2 | 1 | 0 | 1 | 2 | 3 | 
| 3 | 2 | 1 | 0 | 1 | 2 | 
| 4 | 3 | 2 | 1 | 0 | 1 | 
| 5 | 4 | 3 | 2 | 1 | 0 | 
如果我們計算每個差異發生的頻率,我們會看到小差異比大差異更有可能發生:
| 差異 | 出現次數 | 
|---|---|
| 0 | 6 | 
| 1 | 10 | 
| 2 | 8 | 
| 3 | 6 | 
| 4 | 4 | 
| 5 | 2 | 
如果我們知道我們可能需要儲存大數字(因為大差異可能發生,即使它們很少見),但我們最常需要儲存小數字,我們可以使用一個系統對每個數字進行編碼,該系統對小數字使用更少的空間,對大數字使用額外的空間。平均而言,該系統將比對每個數字使用相同的空間量表現得更好。
Golomb 編碼提供了該功能。Rice 編碼是 Golomb 編碼的子集,在某些情況下使用更方便,包括 Bitcoin 區塊過濾器的應用。
區塊過濾器中應包含哪些資料
我們的主要目標是允許錢包了解區塊是否包含影響該錢包的交易。為了使錢包有效,它需要了解兩種類型的資訊:
- 當它收到資金時
- 
具體來說,當交易輸出包含錢包控制的腳本(例如透過控制授權的私鑰)時 
- 當它花費資金時
- 
具體來說,當交易輸入引用錢包控制的先前交易輸出時 
在設計緊湊區塊過濾器期間的次要目標是允許接收過濾器的錢包驗證它從對等節點接收到的是準確的過濾器。例如,如果錢包下載了創建過濾器的區塊,錢包可以生成自己的過濾器。然後,它可以將其過濾器與對等節點的過濾器進行比較,並驗證它們是相同的,證明對等節點生成了準確的過濾器。
為了滿足主要和次要目標,過濾器需要引用兩種類型的資訊:
- 
區塊中每個交易的每個輸出的腳本 
- 
區塊中每個交易的每個輸入的輸出點 
緊湊區塊過濾器的早期設計包括這兩種資訊,但人們意識到,如果我們犧牲次要目標,有一種更有效的方式來實現主要目標。在新設計中,區塊過濾器仍然引用兩種類型的資訊,但它們更密切相關:
- 
與以前一樣,區塊中每個交易的每個輸出的腳本。 
- 
在一個變化中,它還會引用區塊中每個交易的每個輸入的輸出點所引用的輸出的腳本。換句話說,正在被花費的輸出腳本。 
這有幾個優點。首先,這意味著錢包不需要追蹤輸出點;它們可以只掃描它們預期接收資金的輸出腳本。其次,任何時候區塊中的後續交易花費同一區塊中早期交易的輸出時,它們都將引用相同的輸出腳本。對同一輸出腳本的多次引用在緊湊區塊過濾器中是冗餘的,因此可以刪除冗餘副本,縮小過濾器的大小。
當完整節點驗證一個區塊時,它們需要存取區塊中當前交易輸出的輸出腳本和從先前區塊引用的交易輸出的輸出腳本,因此它們能夠在這個簡化模型中建立緊湊區塊過濾器。但是,區塊本身不包含先前區塊中包含的交易的輸出腳本,因此客戶端沒有方便的方式來驗證區塊過濾器是否正確建立。然而,有一種替代方案可以幫助客戶端檢測對等節點是否在欺騙它:從多個對等節點獲取相同的過濾器。
從多個對等節點下載區塊過濾器
對等節點可以向錢包提供不準確的過濾器。有兩種方法可以創建不準確的過濾器。對等節點可以創建一個引用實際上沒有出現在關聯區塊中的交易的過濾器(誤報)。或者,對等節點可以創建一個不引用實際出現在關聯區塊中的交易的過濾器(漏報)。
針對不準確過濾器的第一個保護是讓客戶端從多個對等節點獲取過濾器。BIP157 協議允許客戶端僅下載一個簡短的 32 位元組的過濾器承諾,以確定每個對等節點是否宣傳與客戶端的所有其他對等節點相同的過濾器。如果所有這些對等節點都同意,這最大程度地減少了客戶端必須花費的頻寬來查詢許多不同的對等節點的過濾器。
如果兩個或更多不同的對等節點對同一區塊有不同的過濾器,客戶端可以下載所有這些過濾器。然後,它還可以下載關聯的區塊。如果區塊包含與錢包相關的任何交易,但不是其中一個過濾器的一部分,那麼錢包可以確定創建該過濾器的對等節點是不準確的——Golomb-Rice 編碼集合總是會包含潛在匹配。
或者,如果區塊不包含過濾器說可能匹配錢包的交易,這並不能證明過濾器不準確。為了最小化 GCS 的大小,我們允許一定數量的誤報。錢包可以做的是繼續從對等節點下載額外的過濾器,無論是隨機的還是當它們指示匹配時,然後追蹤客戶端的誤報率。如果它與過濾器設計使用的誤報率顯著不同,錢包可以停止使用該對等節點。在大多數情況下,不準確過濾器的唯一後果是錢包使用的頻寬超過預期。
使用有損編碼減少頻寬
關於我們想要傳達的區塊中的交易資料是輸出腳本。輸出腳本的長度各不相同並遵循模式,這意味著它們之間的差異不會像我們希望的那樣均勻分佈。然而,我們已經在本書的許多地方看到,我們可以使用雜湊函數來創建對某些資料的承諾,並產生一個看起來像隨機選擇的數字的值。
在本書的其他地方,我們使用了密碼學安全的雜湊函數,它提供了關於其承諾強度和其輸出與隨機的無法區分性的保證。然而,有更快、更可配置的非密碼學雜湊函數,例如我們將用於緊湊區塊過濾器的 SipHash 函數。
BIP158 中描述了所使用演算法的詳細資訊,但要點是使用 SipHash 和一些算術運算將每個輸出腳本減少為 64 位元承諾。您可以將其視為取一組大數字並將它們截斷為較短的數字,這是一個會失去資料的過程(因此稱為_有損編碼_)。透過失去一些資訊,我們不需要稍後儲存那麼多資訊,從而節省空間。在這種情況下,我們從通常為 160 位元或更長的典型輸出腳本降至僅 64 位元。
使用緊湊區塊過濾器
區塊中每個輸出腳本承諾的 64 位元值被排序,刪除重複條目,並透過找到每個條目之間的差異(增量)來構建 GCS。然後,該緊湊區塊過濾器由對等節點分發給其客戶端(例如錢包)。
客戶端使用增量來重建原始承諾。客戶端(例如錢包)也會取得它正在監視的所有輸出腳本,並以與 BIP158 相同的方式生成承諾。它檢查其生成的承諾是否與過濾器中的承諾匹配。
回想一下我們關於緊湊區塊過濾器有損性的例子,類似於截斷數字。假設客戶端正在尋找包含數字 123456 的區塊,而準確(但有損)的緊湊區塊過濾器包含數字 1234。當客戶端看到 1234 時,它將下載關聯的區塊。
有 100% 的保證,包含 1234 的準確過濾器將允許客戶端了解包含 123456 的區塊,稱為_真正面_。然而,也有機會該區塊可能包含 123400、123401 或幾乎一百個其他不是客戶端正在尋找的條目(在此範例中),稱為_誤報_。
100% 的真正面匹配率非常好。這意味著錢包可以依賴緊湊區塊過濾器來找到影響該錢包的每筆交易。非零誤報率意味著錢包最終會下載一些不包含錢包感興趣的交易的區塊。其主要後果是客戶端將使用額外的頻寬,這不是一個大問題。BIP158 緊湊區塊過濾器的實際誤報率非常低,因此這不是一個主要問題。誤報率也可以幫助改善客戶端的隱私,就像布隆過濾器一樣,儘管任何想要最佳隱私的人仍應使用自己的完整節點。
從長遠來看,一些開發人員主張讓區塊承諾該區塊的過濾器,最有可能的方案是讓每個 coinbase 交易承諾該區塊的過濾器。完整節點將自己計算每個區塊的過濾器,並且僅在包含準確承諾的情況下才接受區塊。這將允許輕量級客戶端下載 80 位元組的區塊標頭、一個(通常)小的 coinbase 交易和該區塊的過濾器,以接收強有力的證據證明過濾器是準確的。
輕量級客戶端與隱私
輕量級客戶端的隱私比完整節點弱。完整節點下載所有交易,因此不會洩露它是否在其錢包中使用某個地址的資訊。輕量級客戶端僅下載與其錢包以某種方式相關的交易。
布隆過濾器和緊湊區塊過濾器是減少隱私損失的方法。如果沒有它們,輕量級客戶端將不得不明確列出它感興趣的地址,造成嚴重的隱私洩露。然而,即使有過濾器,監視輕量級客戶端流量的對手或作為 P2P 網路中的節點直接連接到它的對手,隨著時間的推移可能能夠收集足夠的資訊來了解輕量級客戶端錢包中的地址。
加密和認證連接
大多數新比特幣使用者假設比特幣節點的網路通訊是加密的。事實上,比特幣的原始實現完全以明文通訊,在撰寫本文時,Bitcoin Core 的現代實現也是如此。
作為增加比特幣 P2P 網路隱私和安全性的一種方式,有一種提供通訊加密的解決方案:Tor 傳輸。
Tor 代表_洋蔥路由網路_(The Onion Routing network),是一個軟體專案和網路,它透過提供匿名性、不可追溯性和隱私的隨機化網路路徑來提供資料的加密和封裝。
Bitcoin Core 提供了幾個配置選項,允許您執行一個比特幣節點,其流量透過 Tor 網路傳輸。此外,Bitcoin Core 還可以提供 Tor 隱藏服務,允許其他 Tor 節點直接透過 Tor 連接到您的節點。
從 Bitcoin Core 版本 0.12 開始,如果節點能夠連接到本機 Tor 服務,它將自動提供隱藏的 Tor 服務。如果您已安裝 Tor,並且 Bitcoin Core 程序以具有足夠權限存取 Tor 身份驗證 cookie 的使用者身份執行,它應該可以自動運作。使用 debug 旗標開啟 Bitcoin Core 的 Tor 服務除錯,如下所示:
$ bitcoind --daemon --debug=tor
您應該在日誌中看到 tor: ADD_ONION successful,表示 Bitcoin Core 已將隱藏服務添加到 Tor 網路。
您可以在 Bitcoin Core 文件(docs/tor.md)和各種線上教學中找到更多關於將 Bitcoin Core 作為 Tor 隱藏服務執行的說明。
記憶池和孤兒池
幾乎比特幣網路上的每個節點都維護一個稱為_記憶池_(mempool)的未確認交易的臨時列表。節點使用此池來追蹤網路已知但尚未包含在區塊鏈中的交易,稱為_未確認交易_。
當收到並驗證未確認的交易時,它們被添加到記憶池,並轉發到相鄰節點以在網路上傳播。
一些節點實現還維護一個單獨的孤兒交易池。如果交易的輸入引用尚未知道的交易,例如缺少的父交易,孤兒交易將臨時儲存在孤兒池中,直到父交易到達。
當交易被添加到記憶池時,會檢查孤兒池是否有任何孤兒引用此交易的輸出(其子交易)。然後驗證任何匹配的孤兒。如果有效,它們將從孤兒池中刪除並添加到記憶池,完成從父交易開始的鏈。鑑於新添加的交易不再是孤兒,該過程遞迴重複,尋找任何進一步的後代,直到找不到更多後代。透過這個過程,父交易的到達會觸發一個級聯重建整個相互依賴的交易鏈,透過將孤兒與它們的父交易重新結合,一直到鏈的底部。
一些比特幣實現還維護一個 UTXO 資料庫,這是區塊鏈上所有未花費輸出的集合。這代表與記憶池不同的資料集。與記憶池和孤兒池不同,UTXO 資料庫包含數百萬個未花費交易輸出的條目,從創世區塊一直到現在的所有未花費的東西。UTXO 資料庫作為一個表格儲存在持久性儲存上。
記憶池和孤兒池代表單個節點的本機視角,並且可能根據節點何時啟動或重新啟動而在節點之間有顯著差異,而 UTXO 資料庫代表網路的緊急共識,因此通常不會在節點之間有所不同。
現在我們已經了解了節點和客戶端用於在比特幣網路上發送資料的許多資料類型和結構,是時候看看負責
區塊鏈
區塊鏈是每筆已確認比特幣交易的歷史記錄。它允許每個完整節點獨立確定哪些金鑰和腳本控制哪些比特幣。在本章中,我們將探討區塊鏈的結構,並了解它如何使用密碼學承諾和其他巧妙的技巧,使完整節點(有時是輕量級客戶端)能夠輕鬆驗證其每個部分。
區塊鏈資料結構是一個有序的、反向連結的交易區塊列表。區塊鏈可以儲存為平面檔案或簡單的資料庫。區塊「向後」連結,每個區塊都引用鏈中的前一個區塊。區塊鏈通常被視覺化為垂直堆疊,區塊彼此層層疊加,第一個區塊作為堆疊的基礎。將區塊堆疊在一起的視覺化導致使用諸如「高度」之類的術語來指距離第一個區塊的距離,以及「頂部」或「尖端」來指最近添加的區塊。
區塊鏈中的每個區塊都由一個雜湊識別,該雜湊是使用 SHA256 密碼學雜湊演算法對區塊標頭生成的。每個區塊也透過區塊標頭中的「前一個區塊雜湊」欄位承諾前一個區塊,稱為父區塊。將每個區塊連結到其父區塊的雜湊序列創建了一條鏈,一直追溯到有史以來創建的第一個區塊,稱為創世區塊。
儘管一個區塊只有一個父區塊,但它可以有多個子區塊。每個子區塊都承諾相同的父區塊。多個子區塊出現在區塊鏈「分叉」期間,這是一種臨時情況,當不同的礦工幾乎同時發現不同的區塊時可能發生(參見 組裝和選擇區塊鏈)。最終,只有一個子區塊成為所有完整節點接受的區塊鏈的一部分,「分叉」得以解決。
「前一個區塊雜湊」欄位在區塊標頭內,從而影響_當前_區塊的雜湊。對父區塊的任何更改都需要更改子區塊的雜湊,這需要更改孫區塊的指標,從而更改孫區塊,依此類推。這個序列確保,一旦一個區塊有許多代跟隨它,就無法在不強制重新計算所有後續區塊的情況下更改它。由於這種重新計算需要大量計算(因此能源消耗),因此長鏈區塊的存在使得區塊鏈的深層歷史難以改變,這是比特幣安全性的關鍵特徵。
思考區塊鏈的一種方式就像地質構造或冰川核心樣本中的層。表層可能隨季節變化,甚至可能在有時間沉降之前被吹走。但是,一旦您深入幾英寸,地質層就會變得越來越穩定。當您向下看幾百英尺時,您正在查看數百萬年來未受干擾的過去快照。在區塊鏈中,如果由於分叉而導致鏈重組,最近的幾個區塊可能會被修改。前六個區塊就像幾英寸的表土。但是,一旦您更深入區塊鏈,超過六個區塊,區塊就越來越不太可能改變。在 100 個區塊之後,有如此多的穩定性,以至於 coinbase 交易——包含創建新區塊的比特幣獎勵的交易——可以被花費。雖然協議始終允許鏈被更長的鏈撤銷,並且任何區塊被逆轉的可能性始終存在,但隨著時間的推移,此類事件的機率會降低,直到它變得微乎其微。
區塊的結構
區塊是一個容器資料結構,它聚合交易以包含在區塊鏈中。區塊由標頭組成,包含元資料,後面是組成其大部分大小的一長串交易。區塊標頭為 80 位元組,而區塊中所有交易的總大小最多可達約 4,000,000 位元組。因此,包含所有交易的完整區塊可以比區塊標頭大近 50,000 倍。[block_structure1] 描述了 Bitcoin Core 如何儲存區塊的結構。
| 大小 | 欄位 | 描述 | 
|---|---|---|
| 4 位元組 | 區塊大小 | 此欄位之後的區塊大小(以位元組為單位) | 
| 80 位元組 | 區塊標頭 | 幾個欄位組成區塊標頭 | 
| 1–3 位元組(compactSize) | 交易計數器 | 後面有多少筆交易 | 
| 可變 | 交易 | 此區塊中記錄的交易 | 
區塊標頭
區塊標頭由區塊元資料組成,如 [block_header_structure_ch09] 所示。
| 大小 | 欄位 | 描述 | 
|---|---|---|
| 4 位元組 | 版本 | 最初是一個版本欄位;其用途隨時間演變 | 
| 32 位元組 | 前一個區塊雜湊 | 鏈中前一個(父)區塊的雜湊 | 
| 32 位元組 | 默克爾根 | 此區塊交易的默克爾樹的根雜湊 | 
| 4 位元組 | 時間戳 | 此區塊的近似創建時間(Unix 紀元時間) | 
| 4 位元組 | 目標 | 此區塊的工作量證明目標的緊湊編碼 | 
| 4 位元組 | Nonce | 用於工作量證明演算法的任意資料 | 
nonce、目標和時間戳在挖礦過程中使用,將在 挖礦與共識 中更詳細地討論。
區塊識別符:區塊標頭雜湊和區塊高度
區塊的主要識別符是其密碼學雜湊,這是透過兩次使用 SHA256 演算法對區塊標頭進行雜湊處理而做出的承諾。產生的 32 位元組雜湊稱為_區塊雜湊_,但更準確地說是_區塊標頭雜湊_,因為只有區塊標頭用於計算它。例如, 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 是比特幣區塊鏈上第一個區塊的區塊雜湊。區塊雜湊唯一且明確地識別一個區塊,任何節點都可以透過簡單地對區塊標頭進行雜湊處理來獨立推導它。
請注意,區塊雜湊實際上並未包含在區塊的資料結構中。相反地,當節點從網路接收區塊時,每個節點都會計算區塊的雜湊。區塊雜湊可能儲存在單獨的資料庫表中,作為區塊元資料的一部分,以便於索引和從磁碟更快地檢索區塊。
識別區塊的第二種方法是根據其在區塊鏈中的位置,稱為 區塊高度。創世區塊的區塊高度為 0(零),並且是 先前由以下區塊雜湊引用的同一區塊 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f。因此,區塊可以透過兩種方式識別:透過引用區塊雜湊或透過引用區塊高度。每個後續區塊添加在該第一個區塊「之上」,在區塊鏈中處於一個位置「更高」,就像一個接一個堆疊的盒子。在撰寫本書期間,區塊高度 800,000 於 2023 年中期達到,這意味著在 2009 年 1 月創建的第一個區塊之上堆疊了 800,000 個區塊。
與區塊雜湊不同,區塊高度不是唯一識別符。儘管單個區塊將始終具有特定且不變的區塊高度,但反之則不然——區塊高度並不總是識別單個區塊。兩個或更多區塊可能具有相同的區塊高度,爭奪區塊鏈中的相同位置。此場景在 組裝和選擇區塊鏈 章節中詳細討論。在早期區塊中,區塊高度也不是區塊資料結構的一部分;它沒有儲存在區塊中。當從比特幣網路接收區塊時,每個節點動態識別區塊在區塊鏈中的位置(高度)。後來的協議變更(BIP34)開始在 coinbase 交易中包含區塊高度,儘管其目的是確保每個區塊具有不同的 coinbase 交易。節點仍然需要動態識別區塊的高度以驗證 coinbase 欄位。區塊高度也可能儲存為索引資料庫表中的元資料,以便更快地檢索。
| 區塊的_區塊雜湊_始終唯一識別單個區塊。區塊也始終具有特定的_區塊高度_。然而,並非總是特定區塊高度識別單個區塊。相反地,兩個或更多區塊可能爭奪區塊鏈中的單個位置。 | 
創世區塊
區塊鏈中的第一個區塊稱為_創世區塊_,創建於 2009 年。它是區塊鏈中所有區塊的共同祖先,這意味著如果您從任何區塊開始並在時間上向後追溯鏈,您最終將到達創世區塊。
每個節點始終以至少一個區塊的區塊鏈開始,因為創世區塊在 Bitcoin Core 中靜態編碼,因此無法更改。每個節點始終「知道」創世區塊的雜湊和結構、創建它的固定時間,甚至其中的單個交易。因此,每個節點都有區塊鏈的起點,一個安全的「根」,從中建立可信的區塊鏈。
請參閱 Bitcoin Core 客戶端內部靜態編碼的創世區塊,位於 chainparams.cpp。
以下識別符雜湊屬於創世區塊:
000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
您可以在幾乎任何區塊瀏覽器網站上搜尋該區塊雜湊,例如 blockstream.info,您將找到一個描述此區塊內容的頁面,其 URL 包含該雜湊:
或者,您可以在命令列上使用 Bitcoin Core 獲取區塊:
$ bitcoin-cli getblock \ 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
{
  "hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
  "confirmations": 790496,
  "height": 0,
  "version": 1,
  "versionHex": "00000001",
  "merkleroot": "4a5e1e4baab89f3a32518a88c3[...]76673e2cc77ab2127b7afdeda33b",
  "time": 1231006505,
  "mediantime": 1231006505,
  "nonce": 2083236893,
  "bits": "1d00ffff",
  "difficulty": 1,
  "chainwork": "[...]000000000000000000000000000000000000000000000100010001",
  "nTx": 1,
  "nextblockhash": "00000000839a8e6886ab5951d7[...]fc90947ee320161bbf18eb6048",
  "strippedsize": 285,
  "size": 285,
  "weight": 1140,
  "tx": [
    "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
  ]
}創世區塊在其中包含一條訊息。coinbase 交易輸入包含文字「The Times 03/Jan/2009 Chancellor on brink of second bailout for banks.」此訊息旨在透過引用英國報紙 The Times 的標題來提供此區塊可能創建的最早日期的證明。它也作為獨立貨幣系統重要性的諷刺提醒,比特幣的推出發生在史無前例的全球貨幣危機的同時。該訊息由比特幣的創造者中本聰嵌入在第一個區塊中。
在區塊鏈中連結區塊
比特幣完整節點驗證創世區塊之後的區塊鏈中的每個區塊。它們對區塊鏈的本機檢視會隨著找到新區塊並用於擴展鏈而不斷更新。當節點從網路接收傳入區塊時,它將驗證這些區塊,然後將它們連結到其現有區塊鏈的檢視。要建立連結,節點將檢查傳入區塊標頭並尋找「前一個區塊雜湊」。
例如,假設一個節點在區塊鏈的本機副本中有 277,314 個區塊。節點知道的最後一個區塊是區塊 277,314,其區塊標頭雜湊為:
00000000000000027e7ba6fe7bad39faf3b5a83daed765f05f7d1b71a1632249
然後,比特幣節點從網路接收一個新區塊,它將其解析如下:
{
    "size" : 43560,
    "version" : 2,
    "previousblockhash" :
        "00000000000000027e7ba6fe7bad39faf3b5a83daed765f05f7d1b71a1632249",
    "merkleroot" :
        "5e049f4030e0ab2debb92378f53c0a6e09548aea083f3ab25e1d94ea1155e29d",
    "time" : 1388185038,
    "difficulty" : 1180923195.25802612,
    "nonce" : 4215469401,
    "tx" : [
        "257e7497fb8bc68421eb2c7b699dbab234831600e7352f0d9e6522c7cf3f6c77",
        "[... many more transactions omitted ...]",
        "05cfd38f6ae6aa83674cc99e4d75a1458c165b7ab84725eda41d018a09176634"
    ]
}查看這個新區塊,節點找到 previousblockhash 欄位,其中包含其父區塊的雜湊。這是節點已知的雜湊,即高度 277,314 的鏈上最後一個區塊的雜湊。因此,這個新區塊是鏈上最後一個區塊的子區塊,並擴展了現有的區塊鏈。節點將這個新區塊添加到鏈的末端,使區塊鏈變得更長,新高度為 277,315。透過每個引用前一個區塊標頭雜湊在鏈中連結的區塊。 顯示了三個區塊的鏈,透過 previousblockhash 欄位中的引用連結。
 
默克爾樹
比特幣區塊鏈中的每個區塊都包含使用_默克爾樹_對區塊中所有交易的摘要。
默克爾樹,也稱為_二元雜湊樹_,是一種用於有效摘要和驗證大型資料集完整性的資料結構。默克爾樹是包含密碼學雜湊的二元樹。在計算機科學中,術語「樹」用於描述分支資料結構,但這些樹通常顯示為倒置,「根」在頂部,「葉」在圖表底部,如您將在下面的範例中看到的那樣。
默克爾樹在比特幣中用於摘要區塊中的所有交易,產生對整個交易集的總體承諾,並允許非常有效的過程來驗證交易是否包含在區塊中。默克爾樹透過遞迴地對元素對進行雜湊處理來構建,直到只有一個雜湊,稱為_根_或_默克爾根_。比特幣默克爾樹中使用的密碼學雜湊演算法是 SHA256 應用兩次,也稱為雙 SHA256。
當 N 個資料元素被雜湊並在默克爾樹中摘要時,您可以使用大約 log~2~(N) 次計算來檢查任何一個資料元素是否包含在樹中,使其成為非常有效的資料結構。
默克爾樹是自下而上構建的。在以下範例中,我們從四筆交易 A、B、C 和 D 開始,它們構成默克爾樹的_葉_,如 計算默克爾樹中的節點。 所示。交易不儲存在默克爾樹中;相反地,它們的資料被雜湊處理,產生的雜湊儲存在每個葉節點中,作為 HA、HB、HC 和 HD:
HA = SHA256(SHA256(Transaction A))
然後,透過串聯兩個雜湊並一起對它們進行雜湊處理,在父節點中摘要連續的葉節點對。例如,要構建父節點 HAB,將子節點的兩個 32 位元組雜湊串聯以創建 64 位元組字串。然後,對該字串進行雙雜湊處理以產生父節點的雜湊:
HAB = SHA256(SHA256(HA || HB))
此過程繼續進行,直到頂部只有一個節點,稱為默克爾根的節點。該 32 位元組雜湊儲存在區塊標頭中,並摘要所有四筆交易中的所有資料。計算默克爾樹中的節點。 顯示了如何透過節點的成對雜湊計算根。
 
由於默克爾樹是二元樹,因此需要偶數個葉節點。如果要摘要的交易數量為奇數,則最後一個交易雜湊將被複製以創建偶數個葉節點,也稱為平衡樹。這在 複製一個資料元素以實現偶數個資料元素。 中顯示,其中交易 C 被複製。同樣,如果在任何等級要處理的雜湊數量為奇數,則最後一個雜湊被複製。
 
從四筆交易構建樹的相同方法可以推廣到構建任何大小的樹。在比特幣中,單個區塊中通常有數千筆交易,以完全相同的方式摘要,僅產生 32 位元組的資料作為單個默克爾根。在 摘要許多資料元素的默克爾樹。 中,您將看到從 16 筆交易構建的樹。請注意,儘管根在圖表中看起來比葉節點大,但它的大小完全相同,只有 32 位元組。無論區塊中有一筆交易還是一萬筆交易,默克爾根始終將它們摘要為 32 位元組。
要證明特定交易包含在區塊中,節點只需要產生大約 log~2~(N) 個 32 位元組雜湊,構成認證路徑_或_默克爾路徑,將特定交易連接到樹的根。隨著交易數量的增加,這一點尤其重要,因為交易數量的以 2 為底的對數增長要慢得多。這允許比特幣節點有效地產生 10 或 12 個雜湊(320–384 位元組)的路徑,這可以提供數兆位元組區塊中超過一千筆交易中單筆交易的證明。
 
在 用於證明資料元素包含的默克爾路徑。 中,節點可以透過產生僅四個 32 位元組雜湊長(總共 128 位元組)的默克爾路徑來證明交易 K 包含在區塊中。該路徑由四個雜湊(以陰影背景顯示)HL、HIJ、HMNOP 和 HABCDEFGH 組成。透過提供這四個雜湊作為認證路徑,任何節點都可以透過計算四個額外的成對雜湊 HKL、HIJKL、HIJKLMNOP 和默克爾樹根(在圖表中以虛線輪廓顯示)來證明 HK(在圖表底部具有黑色背景)包含在默克爾根中。
 
隨著規模的增加,默克爾樹的效率變得顯而易見。最大的可能區塊可以在 4,000,000 位元組中容納近 16,000 筆交易,但證明這 16,000 筆交易中的任何一筆是該區塊的一部分只需要交易的副本、80 位元組區塊標頭的副本和 448 位元組的默克爾證明。這使得最大的可能證明比最大的可能比特幣區塊小近 10,000 倍。
默克爾樹和輕量級客戶端
默克爾樹被輕量級客戶端廣泛使用。輕量級客戶端沒有所有交易,也不下載完整區塊,只下載區塊標頭。為了驗證交易是否包含在區塊中,而無需下載區塊中的所有交易,它們使用默克爾路徑。
例如,考慮一個輕量級客戶端,它對錢包中包含的地址的傳入付款感興趣。輕量級客戶端將在其與對等節點的連接上建立布隆過濾器(參見 布隆過濾器),以將接收的交易限制為僅包含感興趣的地址的交易。當對等節點看到與布隆過濾器匹配的交易時,它將使用 merkleblock 訊息發送該區塊。merkleblock 訊息包含區塊標頭以及將感興趣的交易連結到區塊中的默克爾根的默克爾路徑。輕量級客戶端可以使用此默克爾路徑將交易連接到區塊標頭,並驗證交易是否包含在區塊中。輕量級客戶端還使用區塊標頭將區塊連結到區塊鏈的其餘部分。這兩個連結的組合,交易和區塊之間以及區塊和區塊鏈之間的連結,證明了交易記錄在區塊鏈中。總而言之,輕量級客戶端將接收不到一千位元組的資料用於區塊標頭和默克爾路徑,這個資料量比完整區塊(目前約 2 MB )少一千多倍。
比特幣的測試區塊鏈
您可能會驚訝地得知,比特幣使用的區塊鏈不止一個。由中本聰於 2009 年 1 月 3 日創建的「主要」比特幣區塊鏈,我們在本章中研究的具有創世區塊的區塊鏈,被稱為_主網_。還有其他用於測試目的的比特幣區塊鏈:目前是 testnet、signet 和 regtest。讓我們依次看一下每個。
Testnet:比特幣的測試遊樂場
Testnet 是用於測試目的的測試區塊鏈、網路和貨幣的名稱。testnet 是一個功能齊全的實時 P2P 網路,具有錢包、測試比特幣(testnet 幣)、挖礦以及主網的所有其他功能。最重要的區別是 testnet 幣被認為是無價值的。
任何旨在在比特幣主網上進行生產使用的軟體開發都可以首先使用測試幣在 testnet 上進行測試。這既保護開發人員免受由於錯誤而造成的金錢損失,也保護網路免受由於錯誤而導致的意外行為。
當前的 testnet 稱為 testnet3,是 testnet 的第三次迭代,於 2011 年 2 月重新啟動,以重置先前 testnet 的難度。Testnet3 是一個大型區塊鏈,在 2023 年超過 30 GB。它需要一段時間才能完全同步,並且會佔用您電腦上的資源。不像主網那麼多,但也不完全是「輕量級」的。
| Testnet 和本書中描述的其他測試區塊鏈不使用與主網地址相同的地址前綴,以防止有人意外將真實比特幣發送到測試地址。主網地址以 1、3 或 bc1 開頭。本書中提到的測試網路的地址以 m、n 或 tb1 開頭。其他測試網路或在測試網路上開發的新協議可能使用其他地址前綴或更改。 | 
使用 testnet
Bitcoin Core,像許多其他比特幣程式一樣,完全支援在 testnet 上作為替代主網的操作。Bitcoin Core 的所有功能都可以在 testnet 上運作,包括錢包、挖掘 testnet 幣和同步完整的 testnet 節點。
要在 testnet 而不是主網上啟動 Bitcoin Core,您可以使用 testnet 開關:
$ bitcoind -testnet
在日誌中,您應該看到 bitcoind 正在 bitcoind 預設目錄的 testnet3 子目錄中建立新的區塊鏈:
bitcoind: Using data directory /home/username/.bitcoin/testnet3
要連接到 bitcoind,您可以使用 bitcoin-cli 命令列工具,但您還必須將其切換到 testnet 模式:
$ bitcoin-cli -testnet getblockchaininfo
{
  "chain": "test",
  "blocks": 1088,
  "headers": 139999,
  "bestblockhash": "0000000063d29909d475a1c[...]368e56cce5d925097bf3a2084370128",
  "difficulty": 1,
  "mediantime": 1337966158,
  "verificationprogress": 0.001644065914099759,
  "chainwork": "[...]000000000000000000000000000000000000000000044104410441",
  "pruned": false,
  "softforks": [
  [...]
您也可以在其他完整節點實現上執行 testnet3,例如 btcd(用 Go 編寫)和 bcoin(用 JavaScript 編寫),以使用其他程式語言和框架進行實驗和學習。
testnet 的問題
Testnet 不僅使用與比特幣相同的資料結構,還使用與比特幣幾乎完全相同的工作量證明安全機制。testnet 的顯著區別是其最低難度是比特幣的一半,並且如果該區塊的時間戳比前一個區塊晚 20 分鐘以上,則允許它包含最低難度的區塊。
不幸的是,比特幣的 PoW 安全機制被設計為依賴於經濟激勵——這些激勵在禁止有價值的測試區塊鏈中不存在。在主網上,礦工有動機將使用者交易包含在他們的區塊中,因為這些交易支付手續費。在 testnet 上,交易仍然包含稱為手續費的東西,但這些手續費沒有任何經濟價值。這意味著 testnet 礦工包含交易的唯一動機是因為他們想幫助使用者和開發人員測試他們的軟體。
唉,喜歡破壞系統的人通常會感受到更強的動機,至少在短期內是這樣。由於 PoW 挖礦被設計為無需許可,任何人都可以挖礦,無論他們的意圖是好是壞。這意味著破壞性礦工可以在 testnet 上連續創建許多區塊,而不包含任何使用者交易。當這些攻擊發生時,testnet 對使用者和開發人員來說變得不可用。
Signet:權威證明測試網路
沒有已知的方法讓依賴於無需許可 PoW 的系統在不引入經濟激勵的情況下提供高度可用的區塊鏈,因此比特幣協議開發人員開始考慮替代方案。主要目標是盡可能保留比特幣的結構,以便軟體可以在測試網路上以最小的更改執行——但同時也提供一個保持有用的環境。次要目標是產生一個可重複使用的設計,使新軟體的開發人員可以輕鬆創建自己的測試網路。
在 Bitcoin Core 和其他軟體中實現的解決方案稱為 signet,由 BIP325 定義。signet 是一個測試網路,其中每個區塊必須包含證明(例如簽章)該區塊的創建得到可信權威機構的批准。
而在比特幣中挖礦是無需許可的——任何人都可以做——在 signet 上挖礦是完全需要許可的。只有獲得許可的人才能做到。這對比特幣的主網來說是完全不可接受的變化——沒有人會使用該軟體——但在測試網路上是合理的,其中幣沒有價值,唯一的目的是測試軟體和系統。
BIP325 signet 的設計使得創建自己的 signet 非常容易。如果您不同意別人執行其 signet 的方式,您可以啟動自己的 signet 並將您的軟體連接到它。
預設 signet 和自訂 signet
Bitcoin Core 支援預設 signet,我們認為這是撰寫本文時使用最廣泛的 signet。它目前由該專案的兩位貢獻者營運。如果您使用 signet 參數而沒有其他 signet 相關參數啟動 Bitcoin Core,這就是您將使用的 signet。
在撰寫本文時,預設 signet 約有 150,000 個區塊,大小約為 1 GB。它支援與比特幣主網相同的所有功能,還用於透過 Bitcoin Inquisition 專案測試提議的升級,該專案是 Bitcoin Core 的軟體分支,僅設計為在 signet 上執行。
如果您想使用不同的 signet,稱為_自訂 signet_,您需要知道用於確定何時授權區塊的腳本,稱為_挑戰_腳本。這是一個標準的比特幣腳本,因此它可以使用多重簽章等功能來允許多人授權區塊。您可能還需要連接到種子節點,該節點將為您提供自訂 signet 上的對等節點地址。例如:
bitcoind -signet -signetchallenge=0123...cdef -signetseednode=example.com:1234
在撰寫本文時,我們通常建議挖礦軟體的公開測試在 testnet3 上進行,所有其他比特幣軟體的公開測試在預設 signet 上進行。
要與您選擇的 signet 互動,您可以使用 -signet 參數與 bitcoin-cli,類似於您使用 testnet 的方式。例如:
$ bitcoin-cli -signet getblockchaininfo
{
  "chain": "signet",
  "blocks": 143619,
  "headers": 143619,
  "bestblockhash": "000000c46cb3505ddd296537[...]ad1c5768e2908439382447572a93",
  "difficulty": 0.003020638517858618,
  "time": 1684530244,
  "mediantime": 1684526116,
  "verificationprogress": 0.999997961940662,
  "initialblockdownload": false,
  "chainwork": "[...]000000000000000000000000000000000000000000019ab37d2194",
  "size_on_disk": 769525915,
  "pruned": false,
  "warnings": ""
}
Regtest:本機區塊鏈
Regtest,代表「迴歸測試」,是 Bitcoin Core 功能,允許您為測試目的創建本機區塊鏈。與 signet 和 testnet3 不同,它們是公共和共享的測試區塊鏈,regtest 區塊鏈旨在作為封閉系統執行以進行本機測試。您從頭開始啟動 regtest 區塊鏈。您可以將其他節點添加到網路,或僅使用單個節點執行它以測試 Bitcoin Core 軟體。
要在 regtest 模式下啟動 Bitcoin Core,您可以使用 regtest 旗標:
$ bitcoind -regtest
就像 testnet 一樣,Bitcoin Core 將在您的 bitcoind 預設目錄的 regtest 子目錄下初始化新的區塊鏈:
bitcoind: Using data directory /home/username/.bitcoin/regtest
要使用命令列工具,您也需要指定 regtest 旗標。讓我們嘗試 getblockchaininfo 命令來檢查 regtest 區塊鏈:
$ bitcoin-cli -regtest getblockchaininfo
{
  "chain": "regtest",
  "blocks": 0,
  "headers": 0,
  "bestblockhash": "0f9188f13cb7b2c71f2a335e3[...]b436012afca590b1a11466e2206",
  "difficulty": 4.656542373906925e-10,
  "mediantime": 1296688602,
  "verificationprogress": 1,
  "chainwork": "[...]000000000000000000000000000000000000000000000000000002",
  "pruned": false,
  [...]
如您所見,還沒有區塊。讓我們創建一個預設錢包,獲取一個地址,然後挖掘一些(500 個區塊)以賺取獎勵:
$ bitcoin-cli -regtest createwallet "" $ bitcoin-cli -regtest getnewaddress bcrt1qwvfhw8pf79kw6tvpmtxyxwcfnd2t4e8v6qfv4a $ bitcoin-cli -regtest generatetoaddress 500 \ bcrt1qwvfhw8pf79kw6tvpmtxyxwcfnd2t4e8v6qfv4a [ "3153518205e4630d2800a4cb65b9d2691ac68eea99afa7fd36289cb266b9c2c0", "621330dd5bdabcc03582b0e49993702a8d4c41df60f729cc81d94b6e3a5b1556", "32d3d83538ba128be3ba7f9dbb8d1ef03e1b536f65e8701893f70dcc1fe2dbf2", ..., "32d55180d010ffebabf1c3231e1666e9eeed02c905195f2568c987c2751623c7" ]
挖掘所有這些區塊只需要幾秒鐘,這無疑使測試變得容易。如果您檢查您的錢包餘額,您將看到您獲得了前 400 個區塊的獎勵(coinbase 獎勵必須深度 100 個區塊才能花費它們):
$ bitcoin-cli -regtest getbalance 12462.50000000
使用測試區塊鏈進行開發
比特幣的各種區塊鏈(regtest、signet、testnet3、mainnet)為比特幣開發提供了一系列測試環境。無論您是為 Bitcoin Core 或另一個完整節點共識客戶端開發;開發錢包、交易所、電子商務網站等應用程式;還是開發新穎的智慧合約和複雜腳本,都可以使用測試區塊鏈。
您可以使用測試區塊鏈建立開發管道。在開發程式碼時,在 regtest 上本機測試您的程式碼。一旦您準備好在公共網路上嘗試它,請切換到 signet 或 testnet,以將您的程式碼暴露於更動態的環境中,具有更多樣化的程式碼和應用程式。最後,一旦您確信您的程式碼按預期工作,請切換到主網以在生產中部署它。當您進行更改、改進、錯誤修復等時,請再次啟動管道,首先在 regtest 上部署每個更改,然後在 signet 或 testnet 上,最後進入生產。
現在我們知道區塊鏈包含哪些資料以及密碼學承諾如何安全地將各個部分綁定在一起,我們將研究既提供計算安全性又確保沒有區塊可以在不使所有其他區塊無效的情況下被更改的特殊承諾
挖礦與共識
「挖礦」這個詞有點誤導。透過喚起對貴金屬的提取,它將我們的注意力集中在挖礦的獎勵上,即在每個區塊中創建的新比特幣。儘管挖礦受到這種獎勵的激勵,但挖礦的主要目的不是獎勵或產生新比特幣。如果您僅將挖礦視為創建比特幣的過程,您就是將手段(激勵)誤認為過程的目標。挖礦是支撐去中心化清算所的機制,交易透過該機制得到驗證和清算。挖礦是使比特幣特別的發明之一,一種去中心化的共識機制,是點對點數位現金的基礎。
挖礦_保護比特幣系統_並使網路範圍內的_共識在沒有中央權威的情況下_出現。新鑄造的比特幣和交易手續費的獎勵是一種激勵機制,將礦工的行動與網路安全對齊,同時實施貨幣供應。
| 挖礦是比特幣的_共識安全性_被_去中心化_的機制之一。 | 
礦工在全球區塊鏈上記錄新交易。一個新區塊,包含自上一個區塊以來發生的交易,平均每 10 分鐘被_挖掘_一次,從而將這些交易添加到區塊鏈。成為區塊一部分並添加到區塊鏈的交易被認為是_已確認_的,這使得比特幣的新擁有者知道不可逆轉的努力被用於保護他們在這些交易中收到的比特幣。
此外,區塊鏈中的交易具有由其在區塊鏈中的位置定義的_拓撲順序_。如果一個交易出現在較早的區塊中,或者它出現在同一區塊中較早的位置,則該交易早於另一個交易。在比特幣協議中,只有當交易花費出現在區塊鏈中較早的交易的輸出時(無論它們是在同一區塊中較早還是在較早的區塊中),並且只有當沒有先前的交易花費任何相同的輸出時,交易才有效。在單個區塊鏈中,拓撲順序的執行確保沒有兩個有效交易可以花費相同的輸出,從而消除了_雙重花費_的問題。
在一些建立在比特幣之上的協議中,比特幣交易的拓撲順序也用於建立事件序列;我們將在 單次使用密封 中進一步討論這個想法。
礦工因挖礦提供的安全性而獲得兩種類型的獎勵:每個新區塊創建的新比特幣(稱為_補貼_),以及區塊中包含的所有交易的交易手續費。為了賺取這個獎勵,礦工競爭滿足基於密碼學雜湊演算法的挑戰。該問題的解決方案稱為工作量證明,包含在新區塊中,並作為礦工花費大量計算努力的證明。競爭解決工作量證明演算法以賺取獎勵和在區塊鏈上記錄交易的權利是比特幣安全模型的基礎。
比特幣的貨幣供應是在一個類似於中央銀行透過印製紙幣發行新貨幣的過程中創建的。礦工可以添加到區塊中的新創建比特幣的最大數量大約每四年(或精確地每 210,000 個區塊)減少一次。它在 2009 年 1 月開始時為每個區塊 50 比特幣,並在 2012 年 11 月減半至每個區塊 25 比特幣。它在 2016 年 7 月再次減半至 12.5 比特幣,並在 2020 年 5 月再次減半至 6.25。根據這個公式,挖礦獎勵呈指數下降,直到大約 2140 年,屆時所有比特幣都將被發行。2140 年之後,將不再發行新比特幣。
比特幣礦工也從交易中賺取手續費。每筆交易可能包括以交易的輸入和輸出之間的比特幣盈餘形式的交易手續費。獲勝的比特幣礦工可以「保留零錢」在獲勝區塊中包含的交易上。今天,手續費通常僅佔礦工收入的一小部分,絕大多數來自新鑄造的比特幣。然而,隨著時間的推移,獎勵減少,每個區塊的交易數量增加,挖礦收入中更大比例將來自手續費。逐漸地,挖礦獎勵將由交易手續費主導,這將成為礦工的主要激勵。2140 年之後,每個區塊中的新比特幣數量降至零,挖礦將僅由交易手續費激勵。
在本章中,我們將首先將挖礦作為貨幣供應機制進行檢查,然後查看挖礦最重要的功能:支撐比特幣安全性的去中心化共識機制。
為了理解挖礦和共識,我們將追蹤 Alice 的交易,因為它被 Jing 的挖礦設備接收並添加到區塊中。然後,我們將追蹤該區塊在挖掘、添加到區塊鏈並透過緊急共識過程被比特幣網路接受時的情況。
比特幣經濟學和貨幣創造
比特幣在創建每個區塊期間以固定且遞減的速度鑄造。每個區塊,平均每 10 分鐘產生一次,包含全新的比特幣,從無到有創建。每 210,000 個區塊,或大約每四年,貨幣發行率減少 50%。在網路運營的前四年,每個區塊包含 50 個新比特幣。
第一次減半發生在區塊 210,000。本書出版後的下一次預期減半將發生在區塊 840,000,這可能在 2024 年 4 月或 5 月產生。新比特幣的速率在 32 次這些_減半_中呈指數下降,直到區塊 6,720,000(大約在 2137 年開採),屆時它達到最小貨幣單位 1 聰。最後,在大約 2140 年,在 693 萬個區塊之後,將發行近 2,099,999,997,690,000 聰,或近 2100 萬比特幣。此後,區塊將不包含新比特幣,礦工將僅透過交易手續費獲得獎勵。基於幾何遞減發行率的比特幣貨幣供應隨時間變化。 顯示了隨著時間推移,隨著貨幣發行的減少,流通中的比特幣總數。
 
| 開採的比特幣的最大數量是比特幣可能的挖礦獎勵的_上限_。實際上,礦工可能故意挖掘一個獲得少於全部獎勵的區塊。這樣的區塊已經被挖掘,將來可能會挖掘更多,從而導致貨幣的總發行量降低。 | 
在 用於計算將發行多少比特幣總量的腳本 的程式碼中,我們計算將發行的比特幣總量。
# Original block reward for miners was 50 BTC
start_block_reward = 50
# 210000 is around every 4 years with a 10 minute block interval
reward_interval = 210000
def max_money():
    # 50 BTC = 50 0000 0000 Satoshis
    current_reward = 50 * 10**8
    total = 0
    while current_reward > 0:
        total += reward_interval * current_reward
        current_reward /= 2
    return total
print("Total BTC to ever be created:", max_money(), "Satoshis")執行 max_money.py 腳本 顯示了執行此腳本產生的輸出。
$ python max_money.py
Total BTC to ever be created: 2099999997690000 Satoshis有限且遞減的發行創造了抵抗通貨膨脹的固定貨幣供應。與法定貨幣不同,法定貨幣可以由中央銀行無限量印製,沒有個人或團體有能力膨脹比特幣的供應。
去中心化共識
在上一章中,我們查看了區塊鏈,即所有交易的全球列表,比特幣網路中的每個人都接受它作為所有權轉移的權威記錄。
但是,網路中的每個人如何在不必信任任何人的情況下就誰擁有什麼的單一普遍「真相」達成一致?所有傳統支付系統都依賴於信任模型,該模型具有提供清算所服務的中央權威機構,基本上驗證和清算所有交易。比特幣沒有中央權威機構,但不知何故,每個完整節點都有一個可以信任為權威記錄的公共區塊鏈的完整副本。區塊鏈不是由中央權威機構創建的,而是由網路中的每個節點獨立組裝的。不知何故,網路中的每個節點,根據透過不安全網路連接傳輸的資訊採取行動,可以得出相同的結論,並組裝與其他人相同的區塊鏈副本。本章探討了比特幣網路在沒有中央權威的情況下實現全球共識的過程。
中本聰的發明之一是_緊急共識_的去中心化機制。緊急是因為共識不是明確實現的——沒有選舉或固定的共識發生時刻。相反地,共識是數千個獨立節點的異步互動的緊急產物,所有節點都遵循簡單的規則。比特幣的所有屬性,包括貨幣、交易、支付以及不依賴於中央權威或信任的安全模型,都源自這一發明。
比特幣的去中心化共識來自在網路節點上獨立發生的四個過程的相互作用:
- 
每個完整節點根據綜合標準列表獨立驗證每筆交易 
- 
挖礦節點將這些交易獨立聚合到新區塊中,並透過工作量證明演算法展示計算 
- 
每個節點獨立驗證新區塊並組裝成鏈 
- 
每個節點獨立選擇透過工作量證明展示最多累積計算的鏈 
在接下來的幾節中,我們將檢查這些過程以及它們如何互動以創造網路範圍共識的緊急屬性,該屬性允許任何比特幣節點組裝其自己的權威、可信、公共、全球區塊鏈副本。
交易的獨立驗證
在 交易 中,我們看到錢包軟體如何透過收集 UTXO、提供適當的認證資料,然後構建分配給新所有者的新輸出來創建交易。然後,產生的交易被發送到比特幣網路中的鄰近節點,以便它可以在整個比特幣網路上傳播。
然而,在將交易轉發給其鄰居之前,每個接收交易的比特幣節點都將首先驗證交易。這確保只有有效交易在網路上傳播,而無效交易在遇到它們的第一個節點處被丟棄。
每個節點根據一長串標準驗證每筆交易:
- 
交易的語法和資料結構必須正確。 
- 
輸入和輸出列表都不為空。 
- 
交易權重足夠低,以允許它適合區塊。 
- 
每個輸出值以及總計必須在允許的值範圍內(零或更多,但不超過 2100 萬比特幣)。 
- 
鎖定時間等於 INT_MAX,或者鎖定時間和序列值根據鎖定時間和 BIP68 規則得到滿足。 
- 
交易中包含的簽章操作(SIGOPS)數量少於簽章操作限制。 
- 
被花費的輸出與記憶池中的輸出或主分支中區塊中的未花費輸出匹配。 
- 
對於每個輸入,如果引用的輸出交易是 coinbase 輸出,則它必須至少有 COINBASE_MATURITY(100)個確認。任何絕對或相對鎖定時間也必須得到滿足。節點可以在它們成熟之前一個區塊轉發交易,因為如果包含在下一個區塊中,它們將成熟。 
- 
如果輸入值的總和小於輸出值的總和,則拒絕。 
- 
每個輸入的腳本必須針對相應的輸出腳本進行驗證。 
請注意,條件會隨時間變化,以添加新功能或解決新類型的拒絕服務攻擊。
透過在接收時獨立驗證每筆交易並在傳播之前,每個節點構建一個有效(但未確認)交易池,稱為("記憶池"記憶池_或 _mempool。
挖礦節點
比特幣網路上的一些節點是稱為_礦工_的專用節點。Jing 是一個比特幣礦工;他透過運行「挖礦設備」來賺取比特幣,這是一個專門設計用於挖掘比特幣的專用計算機硬體系統。Jing 的專用挖礦硬體連接到運行完整節點的伺服器。像其他每個完整節點一樣,Jing 的節點接收並在比特幣網路上傳播未確認的交易。然而,Jing 的節點還將這些交易聚合到新區塊中。
讓我們追蹤在 Alice 從 Bob 購買時創建的區塊(參見 從線上商店購買)。為了演示本章中的概念,讓我們假設包含 Alice 交易的區塊是由 Jing 的挖礦系統挖掘的,並追蹤 Alice 的交易成為這個新區塊的一部分。
Jing 的挖礦節點維護區塊鏈的本機副本。到 Alice 購買東西的時候,Jing 的節點已經趕上了具有最多工作量證明的區塊鏈。Jing 的節點正在監聽交易,試圖挖掘新區塊,並且也在監聽其他節點發現的區塊。當 Jing 的節點正在挖礦時,它透過比特幣網路接收新區塊。這個區塊的到達標誌著該區塊的搜尋結束和創建下一個區塊的搜尋開始。
在之前的幾分鐘內,當 Jing 的節點正在尋找前一個區塊的解決方案時,它也在收集交易,為下一個區塊做準備。到現在,它已經在其記憶池中收集了幾千筆交易。在接收到新區塊並驗證它之後,Jing 的節點還將將其與記憶池中的所有交易進行比較,並刪除該區塊中包含的任何交易。記憶池中保留的任何交易都是未確認的,正在等待記錄在新區塊中。
Jing 的節點立即構建一個新的部分區塊,一個下一個區塊的候選。這個區塊被稱為_候選區塊_,因為它還不是有效的區塊,因為它不包含有效的工作量證明。只有當礦工根據工作量證明演算法成功找到解決方案時,該區塊才會變得有效。
當 Jing 的節點從記憶池聚合所有交易時,新的候選區塊有幾千筆交易,每筆交易都支付交易手續費,他將嘗試索取。
Coinbase 交易
任何區塊中的第一筆交易是一筆特殊交易,稱為 coinbase 交易。這筆交易由 Jing 的節點構建,並為他的挖礦努力支付他的_獎勵_。
Jing 的節點創建 coinbase 交易作為對他自己錢包的支付。Jing 為挖掘區塊收集的獎勵總額是區塊補貼(2023 年為 6.25 個新比特幣)和區塊中包含的所有交易的交易手續費的總和。
與常規交易不同,coinbase 交易不消耗(花費)UTXO 作為輸入。相反地,它只有一個輸入,稱為 coinbase 輸入,它隱式包含區塊獎勵。coinbase 交易必須至少有一個輸出,並且可以有盡可能多的輸出以適合區塊。2023 年,coinbase 交易通常有兩個輸出:其中一個是零值輸出,使用 OP_RETURN 來承諾區塊中所有隔離見證(segwit)交易的所有見證。另一個輸出向礦工支付他們的獎勵。
Coinbase 獎勵和手續費
為了構建 coinbase 交易,Jing 的節點首先計算交易手續費的總額:
接下來,Jing 的節點計算新區塊的正確獎勵。獎勵是根據區塊高度計算的,從每個區塊 50 比特幣開始,每 210,000 個區塊減半。
可以在 Bitcoin Core 客戶端中的函數 GetBlockSubsidy 中看到計算,如 計算區塊獎勵——函數 GetBlockSubsidy, Bitcoin Core 客戶端,main.cpp 所示。
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams)
{
    int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;
    // Force block reward to zero when right shift is undefined.
    if (halvings >= 64)
        return 0;
    CAmount nSubsidy = 50 * COIN;
    // Subsidy is cut in half every 210,000 blocks.
    nSubsidy >>= halvings;
    return nSubsidy;
}初始補貼以聰為單位計算,方法是將 50 乘以 COIN 常數(100,000,000 聰)。這將初始獎勵(nSubsidy)設定為 50 億聰。
接下來,該函數透過將當前區塊高度除以減半間隔(SubsidyHalvingInterval)來計算已發生的 halvings 數量。
接下來,該函數使用二進位右移運算子將獎勵 (nSubsidy) 除以二,針對每一輪減半。在區塊 277,316 的情況下,這將對 50 億聰的獎勵進行一次二進位右移(一次減半),結果為 25 億聰,或 25 比特幣。在第 33 次減半之後,補貼將向下捨入為零。使用二進位右移運算子是因為它比多次重複除法更有效。為了避免潛在的錯誤,在 63 次減半之後跳過移位操作,並將補貼設定為 0。
最後,coinbase 獎勵(nSubsidy)被添加到交易手續費(nFees),並返回總和。
| 如果 Jing 的挖礦節點編寫 coinbase 交易,是什麼阻止 Jing「獎勵」自己 100 或 1,000 比特幣?答案是,膨脹的獎勵將導致區塊被其他所有人視為無效,浪費 Jing 用於 PoW 的電力。只有當區塊被所有人接受時,Jing 才能花費獎勵。 | 
Coinbase 交易的結構
透過這些計算,Jing 的節點然後構建 coinbase 交易來支付他自己的區塊獎勵。
coinbase 交易具有特殊格式。它沒有指定要花費的先前 UTXO 的交易輸入,而是具有「coinbase」輸入。我們在 輸入 中檢查了交易輸入。讓我們比較常規交易輸入與 coinbase 交易輸入。[table_8-1] 顯示了常規交易的結構,而 [table_8-2] 顯示了 coinbase 交易輸入的結構。
| 大小 | 欄位 | 描述 | 
|---|---|---|
| 32 位元組 | 交易雜湊 | 指向包含要花費的 UTXO 的交易的指標 | 
| 4 位元組 | 輸出索引 | 要花費的 UTXO 的索引號,第一個為 0 | 
| 1–9 位元組(compactSize) | 腳本大小 | 腳本長度(以位元組為單位),後續 | 
| 可變 | 輸入腳本 | 滿足 UTXO 輸出腳本條件的腳本 | 
| 4 位元組 | 序列號 | 用於 BIP68 時間鎖定和交易替換信號的多用途欄位 | 
| 大小 | 欄位 | 描述 | 
|---|---|---|
| 32 位元組 | 交易雜湊 | 所有位元都為零:不是交易雜湊引用 | 
| 4 位元組 | 輸出索引 | 所有位元都為一:0xFFFFFFFF | 
| 1 位元組 | Coinbase 資料大小 | coinbase 資料的長度,從 2 到 100 位元組 | 
| 可變 | Coinbase 資料 | 用於額外 nonce 和挖礦標籤的任意資料;在 v2 區塊中,必須以區塊高度開始 | 
| 4 位元組 | 序列號 | 設定為 0xFFFFFFFF | 
在 coinbase 交易中,前兩個欄位設定為不代表 UTXO 引用的值。「交易雜湊」的第一個欄位填充了 32 個位元組,全部設定為零,而不是「交易雜湊」。「輸出索引」填充了 4 個位元組,全部設定為 0xFF(255 十進位)。輸入腳本被 coinbase 資料替換,這是礦工使用的資料欄位,我們接下來將看到。
Coinbase 資料
Coinbase 交易沒有輸入腳本欄位。相反地,此欄位被 coinbase 資料替換,該資料必須在 2 到 100 位元組之間。除了前幾個位元組外,coinbase 資料的其餘部分可以由礦工以任何方式使用;它是任意資料。
例如,在創世區塊中,中本聰在 coinbase 資料中添加了文字「The Times 03/Jan/2009 Chancellor on brink of second bailout for banks」,將其用作此區塊可能創建的最早日期的證明,並傳達訊息。目前,礦工經常使用 coinbase 資料來包含額外的 nonce 值和識別挖礦池的字串。
coinbase 的前幾個位元組曾經是任意的,但現在不再如此。根據 BIP34,版本 2 區塊(版本欄位設定為 2 或更高的區塊)必須在 coinbase 欄位的開頭包含區塊高度作為腳本「推送」操作。
構建區塊標頭
為了構建區塊標頭,挖礦節點需要填寫六個欄位,如 [block_header_structure_ch10] 中所列。
| 大小 | 欄位 | 描述 | 
|---|---|---|
| 4 位元組 | 版本 | 多用途位欄位 | 
| 32 位元組 | 前一個區塊雜湊 | 對鏈中前一個(父)區塊的雜湊的引用 | 
| 32 位元組 | 默克爾根 | 此區塊交易的默克爾樹的根雜湊 | 
| 4 位元組 | 時間戳 | 此區塊的近似創建時間(從 Unix 紀元開始的秒數) | 
| 4 位元組 | 目標 | 此區塊的工作量證明演算法目標 | 
| 4 位元組 | Nonce | 用於工作量證明演算法的計數器 | 
版本欄位最初是一個整數欄位,用於比特幣網路的三次升級,這些升級在 BIP 34、66 和 65 中定義。每次版本號都會增加。後來的升級將版本欄位定義為位欄位,稱為 versionbits,允許最多 29 個升級同時進行;詳見 BIP9:信號和啟動。更晚些時候,礦工開始使用一些 versionbits 作為輔助 nonce 欄位。
| BIP 34、66 和 65 中定義的協議升級按該順序發生,BIP66(嚴格 DER)發生在 BIP65(OP_CHECKTIMELOCKVERIFY)之前,因此比特幣開發人員通常按該順序列出它們,而不是按數字排序。 | 
今天,除非正在進行升級共識協議的嘗試,否則 versionbits 欄位沒有意義,在這種情況下,您將需要閱讀其文件以確定它如何使用 versionbits。
接下來,挖礦節點需要添加「前一個區塊雜湊」(也稱為 [.keep-together]#prevhash)。#這是從網路接收的前一個區塊的區塊標頭的雜湊,Jing 的節點已接受並選擇它作為其候選區塊的_父區塊_。
| 透過選擇由候選區塊標頭中的前一個區塊雜湊欄位指示的特定_父區塊_,Jing 將他的挖礦算力投入到擴展以該特定區塊結束的鏈。 | 
下一步是使用默克爾樹承諾所有交易。每個交易使用其見證交易識別符(wtxid)按拓撲順序列出,32 個 0x00 位元組代表第一筆交易(coinbase)的 wtxid。正如我們在 默克爾樹 中看到的,如果 wtxid 的數量為奇數,則最後一個 wtxid 與自身進行雜湊處理,創建每個包含一筆交易的雜湊的節點。然後將交易雜湊成對組合,創建樹的每一層,直到所有交易被摘要為樹「根」處的一個節點。默克爾樹的根將所有交易摘要為單個 32 位元組值,即_見證根雜湊_。
見證根雜湊被添加到 coinbase 交易的輸出中。如果區塊中沒有交易需要包含見證結構,則可以跳過此步驟。然後,每筆交易(包括 coinbase 交易)使用其交易識別符(txid)列出,並用於構建第二個默克爾樹,其根成為默克爾根,區塊標頭承諾該根。
然後,Jing 的挖礦節點將添加一個 4 位元組的時間戳,編碼為 Unix「紀元」時間戳,該時間戳基於從 1970 年 1 月 1 日午夜 UTC/GMT 經過的秒數。
然後,Jing 的節點填寫 nBits 目標,該目標必須設定為所需 PoW 的緊湊表示,以使其成為有效區塊。目標儲存在區塊中作為「目標位元」度量,這是目標的尾數-指數編碼。編碼具有 1 位元組指數,後跟 3 位元組尾數(係數)。例如,在區塊 277,316 中,目標位元值為 0x1903a30c。第一部分 0x19 是十六進位指數,而下一部分 0x03a30c 是係數。目標的概念在 重新定目標以調整難度 中解釋,「目標位元」表示在 目標表示方法 中解釋。
最後一個欄位是 nonce,它被初始化為零。
填寫所有其他欄位後,候選區塊的標頭現在已完成,挖礦過程可以開始。目標現在是找到一個產生小於目標的雜湊的標頭。挖礦節點在找到滿足要求的版本之前,需要測試數十億或數萬億個標頭的變體。
挖掘區塊
現在候選區塊已由 Jing 的節點構建,是時候讓 Jing 的硬體挖礦設備「挖掘」區塊了,以找到工作量證明演算法的解決方案,使區塊有效。在本書中,我們研究了在比特幣系統的各個方面使用的密碼學雜湊函數。雜湊函數 SHA256 是比特幣挖礦過程中使用的函數。
最簡單的說法是,挖礦是重複對候選區塊標頭進行雜湊處理的過程,改變一個參數,直到產生的雜湊與特定目標匹配。雜湊函數的結果無法提前確定,也無法創建將產生特定雜湊值的模式。雜湊函數的這一特徵意味著產生與特定目標匹配的雜湊結果的唯一方法是一次又一次地嘗試,修改輸入,直到所需的雜湊結果偶然出現。
工作量證明演算法
雜湊演算法採用任意長度的資料輸入並產生固定長度的確定性結果,稱為_摘要_。摘要是對輸入的數位承諾。對於任何特定輸入,產生的摘要將始終相同,任何實現相同雜湊演算法的人都可以輕鬆計算和驗證。密碼學雜湊演算法的一個關鍵特徵是,找到產生相同摘要的兩個不同輸入在計算上是不可行的(稱為_衝突_)。作為推論,除了嘗試隨機輸入之外,以這樣的方式選擇輸入以產生所需摘要也幾乎是不可能的。
使用 SHA256,無論輸入的大小如何,輸出始終為 256 位元長。例如,我們將計算短語「Hello, World!」的 SHA256 雜湊:
$ echo "Hello, world!" | sha256sum d9014c4624844aa5bac314773d6b689ad467fa4e1d1a50a1b8a99d5a95f72ff5 -
這個 256 位元輸出(以十六進位表示)是短語的_雜湊_或_摘要_,取決於短語的每個部分。添加單個字母、標點符號或任何其他字元都將產生不同的雜湊。
在這種情況下使用的變數稱為 nonce。nonce 用於改變密碼學函數的輸出,在這種情況下改變對短語的 SHA256 承諾的輸出。
為了從這個演算法中製造挑戰,讓我們設定一個目標:找到一個產生以零開頭的十六進位雜湊的短語。幸運的是,這並不困難,如 簡單的工作量證明實現 所示。
$ for nonce in $( seq 100 ) ; do echo "Hello, world! $nonce" | sha256sum ; done 3194835d60e85bf7f728f3e3f4e4e1f5c752398cbcc5c45e048e4dbcae6be782 - bfa474bbe2d9626f578d7d8c3acc1b604ec4a7052b188453565a3c77df41b79e - [...] f75a100821c34c84395403afd1a8135f685ca69ccf4168e61a90e50f47552f61 - 09cb91f8250df04a3db8bd98f47c7cecb712c99835f4123e8ea51460ccbec314 -
短語「Hello, World! 32」產生以下符合我們標準的雜湊:09cb91f8250df04a3db8bd98f47c7cecb712c99835f4123e8ea51460ccbec314。找到它花了 32 次嘗試。就機率而言,如果雜湊函數的輸出均勻分佈,我們預計每 16 個雜湊(十六進位數字 0 到 F 中的十六分之一)就會找到一個以 0 作為十六進位前綴的結果。從數字角度來說,這意味著找到一個小於 0x1000000000000000000000000000000000000000000000000000000000000000 的雜湊值。我們稱此閾值為_目標_,目標是找到數字上小於目標的雜湊。如果我們降低目標,找到小於目標的雜湊的任務變得越來越困難。
為了給出一個簡單的類比,想像一個遊戲,玩家重複擲一對骰子,試圖擲出小於指定目標的點數。在第一輪中,目標是 12。除非您擲出雙 6,否則您會贏。在下一輪中,目標是 11。玩家必須擲出 10 或更少才能獲勝,這又是一項簡單的任務。假設幾輪後,目標降至 5。現在,超過一半的擲骰子將超過目標,因此無效。目標越低,贏得遊戲所需的擲骰子次數就越多。最終,當目標為 3(可能的最小值)時,每 36 次擲骰子中只有一次,或大約 3%,會產生獲勝結果。
從知道骰子遊戲目標為 3 的觀察者的角度來看,如果有人成功擲出了獲勝的骰子,可以假設他們平均嘗試了 36 次。換句話說,可以從目標施加的難度估計成功所需的工作量。當演算法基於確定性函數(例如 SHA256)時,輸入本身構成 了產生低於目標的結果所完成的一定_工作量_的_證明_。因此,這就是_工作量證明_。
| 儘管每次嘗試都會產生隨機結果,但任何可能結果的機率都可以提前計算。因此,特定難度的結果構成了特定工作量的證明。 | 
在 簡單的工作量證明實現 中,獲勝的「nonce」是 32,這個結果可以由任何人獨立確認。任何人都可以將數字 32 作為後綴添加到短語「Hello, world!」並計算雜湊值,驗證它小於目標:
$ echo "Hello, world! 32" | sha256sum 09cb91f8250df04a3db8bd98f47c7cecb712c99835f4123e8ea51460ccbec314 -
雖然驗證只需要一次雜湊計算,但我們花了 32 次雜湊計算才找到有效的 nonce。如果我們有更低的目標(更高的難度),找到合適的 nonce 將需要更多的雜湊計算,但任何人驗證時仍然只需要一次雜湊計算。通過知道目標,任何人都可以使用統計方法估計難度,從而大致了解找到這樣的 nonce 所需的工作量。
| 工作量證明必須產生_小於_目標的雜湊值。更高的目標意味著找到低於目標的雜湊值更容易。更低的目標意味著找到低於目標的雜湊值更困難。目標與難度成反比。 | 
比特幣的工作量證明與 簡單的工作量證明實現 中顯示的挑戰非常相似。礦工構建一個填滿交易的候選區塊。接下來,礦工計算此區塊標頭的雜湊值,並查看它是否小於當前_目標_。如果雜湊值不小於目標,礦工將修改 nonce(通常只是將其加 1)然後再試一次。在比特幣網路的當前難度下,礦工必須嘗試大量次數才能找到導致足夠低的區塊標頭雜湊值的 nonce。
目標表示方法
區塊標頭 包含一個稱為「目標位元」或簡稱「bits」的符號表示的目標,在區塊 277,316 中,其值為 0x1903a30c。這種符號以係數/指數格式表示工作量證明目標,前兩個十六進位數字為指數,接下來的六個十六進位數字為係數。因此,在這個區塊中,指數是 0x19,係數是 0x03a30c。
從這種表示方法計算難度目標的公式為:
- target = coefficient × 2(8 × (exponent – 3))
使用該公式和難度位元值 0x1903a30c,我們得到:
- target = 0x03a30c × 20x08 × (0x19 – 0x03)
即:
- 22,829,202,948,393,929,850,749,706,076,701,368,331,072,452,018,388,575,715,328
或者,以十六進位表示:
- 0x0000000000000003A30C00000000000000000000000000000000000000000000
這意味著區塊高度 277,316 的有效區塊的區塊標頭雜湊值必須小於目標。以二進位表示,該數字必須有超過 60 個前導位元設定為零。在這個難度等級下,單個礦工以每秒 1 兆次雜湊(1 terahash per second 或 1 TH/sec)的速度處理,平均只會每 8,496 個區塊找到一次解決方案,或者說每 59 天一次。
重新定目標以調整難度
正如我們所看到的,目標決定了難度,因此影響找到工作量證明演算法解決方案所需的時間。這引出了一些顯而易見的問題:為什麼難度是可調整的,誰來調整它,以及如何調整?
比特幣的區塊平均每 10 分鐘產生一次。這是比特幣的心跳,支撐著貨幣發行的頻率和交易結算的速度。它不僅要在短期內保持恆定,還要在數十年的時間內保持恆定。在此期間,預計電腦性能將繼續快速提升。此外,參與挖礦的人數及其使用的電腦也會不斷變化。為了將區塊產生時間保持在 10 分鐘,必須調整挖礦難度以適應這些變化。事實上,工作量證明目標是一個動態參數,定期調整以達到 10 分鐘區塊間隔的目標。簡單來說,目標的設定使得當前的挖礦算力將產生 10 分鐘的區塊間隔。
那麼,在完全去中心化的網路中如何進行這樣的調整呢?重新定目標會在每個節點上自動且獨立地進行。每 2,016 個區塊,所有節點都會重新定目標工作量證明。計算實際時間跨度與期望的每個區塊 10 分鐘時間跨度之間的比率,並對目標進行成比例的調整(向上或向下)。簡單來說:如果網路找到區塊的速度快於每 10 分鐘,難度就會增加(目標降低)。如果發現區塊的速度慢於預期,難度就會降低(目標提高)。
該等式可以總結為:
新目標 = 舊目標 * (20,160 分鐘 / 最近 2015 個區塊的實際時間)
| 雖然目標校準每 2,016 個區塊發生一次,但由於原始比特幣軟體中的 off-by-one 錯誤,它是基於前 2,015 個區塊(而不是應該的 2,016 個)的總時間,導致重新定目標偏向於更高的難度約 0.05%。 | 
重新定目標工作量證明:CalculateNextWorkRequired() 在 pow.cpp 中 顯示了 Bitcoin Core 客戶端中使用的程式碼。
   // Limit adjustment step
    int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
    LogPrintf("  nActualTimespan = %d  before bounds\n", nActualTimespan);
    if (nActualTimespan < params.nPowTargetTimespan/4)
        nActualTimespan = params.nPowTargetTimespan/4;
    if (nActualTimespan > params.nPowTargetTimespan*4)
        nActualTimespan = params.nPowTargetTimespan*4;
    // Retarget
    const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
    arith_uint256 bnNew;
    arith_uint256 bnOld;
    bnNew.SetCompact(pindexLast->nBits);
    bnOld = bnNew;
    bnNew *= nActualTimespan;
    bnNew /= params.nPowTargetTimespan;
    if (bnNew > bnPowLimit)
        bnNew = bnPowLimit;參數 Interval(2,016 個區塊)和 TargetTimespan(兩週,即 1,209,600 秒)在 chainparams.cpp 中定義。
為了避免難度的極端波動,重新定目標調整在每個週期內必須小於 4 倍因子。如果所需的目標調整大於 4 倍因子,它將只調整 4 倍,不會更多。任何進一步的調整將在下一個重新定目標期間完成,因為不平衡將在接下來的 2,016 個區塊中持續存在。因此,雜湊算力與難度之間的巨大差異可能需要幾個 2,016 個區塊週期才能平衡。
請注意,目標與交易數量或交易價值無關。這意味著用於保護比特幣的雜湊算力以及因此消耗的電力也完全獨立於交易數量。比特幣可以擴展並保持安全,而無需從今天的水平增加任何雜湊算力。雜湊算力的增加代表了市場力量,因為新礦工進入市場。只要有足夠的雜湊算力在誠實追求獎勵的礦工控制之下,就足以防止「接管」攻擊,因此,就足以保護比特幣。
挖礦的難度與電費成本以及比特幣兌用於支付電費的貨幣的匯率密切相關。高性能挖礦系統在當前這一代矽製程中盡可能高效,以最高速率將電力轉換為雜湊計算。挖礦市場的主要影響因素是以比特幣計價的每千瓦時電費,因為這決定了挖礦的盈利能力,從而決定了進入或退出挖礦 市場的動機。
中位時間過去(MTP)
在比特幣中,牆上時鐘時間與共識時間之間存在微妙但非常重要的區別。比特幣是一個去中心化的網路,這意味著每個參與者都有自己的時間視角。網路上的事件不會在所有地方同時發生。必須將網路延遲納入每個節點的視角中。最終,一切都會同步以創建一個共同的區塊鏈。比特幣每 10 分鐘就區塊鏈_過去_的狀態達成共識。
區塊標頭中設定的時間戳記是由礦工設定的。共識規則允許一定程度的緯度,以解釋去中心化節點之間時鐘準確性的差異。然而,這為礦工在區塊中謊報時間創造了一個不幸的動機。例如,如果礦工將時間設定在未來,他們可以降低難度,使他們能夠挖掘更多區塊並領取一些為未來礦工保留的區塊補貼。如果他們可以為某些區塊設定過去的時間,他們可以為其他一些區塊使用當前時間,因此再次使它看起來區塊之間有很長的時間,以便操縱難度。
為了防止操縱,比特幣有兩個共識規則。第一個是任何節點都不會接受任何時間戳記超過未來兩小時的區塊。第二個是任何節點都不會接受時間戳記小於或等於最近 11 個區塊的中位時間的區塊,稱為_中位時間過去_(MTP)。
作為 BIP68 相對時間鎖啟動的一部分,在交易中計算時間鎖(絕對和相對)的「時間」計算方式也發生了變化。以前,礦工可以在區塊中包含任何時間鎖等於或低於區塊時間的交易。這激勵礦工使用他們認為可能的最晚時間(接近未來兩小時),以便更多交易有資格進入他們的區塊。
為了消除說謊的動機並加強時間鎖的安全性,BIP113 被提出並與相對時間鎖的 BIP 同時啟動。MTP 成為所有時間鎖計算使用的共識時間。通過從大約兩小時前取中點,任何單個區塊時間戳記的影響都會減少。通過合併 11 個區塊,沒有單個礦工可以影響時間戳記以從尚未到期的時間鎖交易中獲得手續費。
MTP 改變了鎖定時間、CLTV、序列和 CSV 的時間計算實作。由 MTP 計算的共識時間通常比牆上時鐘時間落後約一小時。如果您創建時間鎖交易,在估計要在鎖定時間、序列、CLTV 和 CSV 中編碼的期望值時,應該考慮到這一點。
成功挖到區塊
如前所述,Jing 的節點已經構建了一個候選區塊並準備好進行挖礦。Jing 有幾台配備特定應用積體電路的硬體挖礦設備,其中成千上萬的積體電路以驚人的速度並行運行比特幣的雙 SHA256 演算法。這些專用機器中的許多通過 USB 或區域網路連接到他的挖礦節點。接下來,在 Jing 桌面上運行的挖礦節點將區塊標頭傳輸到他的挖礦硬體,該硬體開始每秒測試數兆種標頭變體。由於 nonce 只有 32 位元,在耗盡所有 nonce 可能性(約 40 億)之後,挖礦硬體會更改區塊標頭(調整 coinbase 額外 nonce 空間、版本位元或時間戳記)並重置 nonce 計數器,測試新的組合。
開始挖掘某個特定區塊近 11 分鐘後,其中一台硬體挖礦機器找到了解決方案並將其發送回挖礦節點。
立即,Jing 的挖礦節點將該區塊傳輸給所有對等節點。他們接收、驗證,然後傳播新區塊。隨著區塊在網路中傳播,每個節點將其添加到自己的區塊鏈副本中,將其擴展到新的高度。當挖礦節點接收並驗證該區塊時,他們放棄了在相同高度找到區塊的努力,並立即開始計算鏈中的下一個區塊,使用 Jing 的區塊作為「父區塊」。通過在 Jing 新發現的區塊之上構建,其他礦工實質上是使用他們的挖礦算力來背書 Jing 的區塊以及它所擴展的鏈 。
在下一節中,我們將查看每個節點用於驗證區塊和選擇最多工作量鏈的過程,這創建了形成去中心化區塊鏈的共識。
驗證新區塊
第三 步在比特幣的共識機制中是由網路上的每個節點對每個新區塊進行獨立驗證。當新解決的區塊在網路中移動時,每個節點執行一系列測試來驗證它。獨立驗證還確保只有遵循共識規則的區塊才會被併入區塊鏈,從而使礦工獲得獎勵。違反規則的區塊將被拒絕,不僅使其礦工失去獎勵,而且還浪費了尋找工作量證明解決方案所花費的努力,從而使這些礦工承擔創建區塊的所有成本,但沒有給他們任何獎勵。
當節點接收到新區塊時,它將根據一長串必須全部滿足的標準來驗證該區塊;否則,該區塊將被拒絕。這些標準可以在 Bitcoin Core 客戶端的 CheckBlock 和 CheckBlockHeader 函數中看到,包括:
- 
區塊資料結構在語法上有效。 
- 
區塊標頭雜湊值小於目標(強制執行工作量證明)。 
- 
區塊時間戳記在 MTP 和未來兩小時之間(允許時間誤差)。 
- 
區塊權重在可接受的限制內。 
- 
第一筆交易(且僅第一筆)是 coinbase 交易。 
- 
區塊內的所有交易都使用 交易的獨立驗證 中討論的交易檢查清單有效。 
網路上每個節點對每個新區塊的獨立驗證確保了礦工不能作弊。在前面的章節中,我們看到礦工如何編寫一筆交易,獎勵他們在區塊內創建的新比特幣並領取交易手續費。為什麼礦工不給自己寫一筆一千個比特幣的交易,而不是正確的獎勵?因為每個節點都根據相同的規則驗證區塊。無效的 coinbase 交易會使整個區塊無效,這將導致該區塊被拒絕,因此,該交易永遠不會成為區塊鏈的一部分。礦工必須根據所有節點遵循的共享規則構建一個區塊,並使用正確的工作量證明解決方案來挖掘它。為此,他們在挖礦中消耗了大量電力,如果他們作弊,所有的電力和努力都會被浪費。這就是為什麼獨立驗證是 去中心化共識的關鍵組成部分。
組裝和選擇區塊鏈
最後 的部分在比特幣的去中心化共識機制中是將區塊組裝成鏈並選擇具有最多工作量證明的鏈。
_最佳區塊鏈_是與其相關聯的累積工作量證明最多的有效區塊鏈。最佳鏈也可能有與最佳鏈上的區塊成為「兄弟」的區塊分支。這些區塊是有效的,但不是最佳鏈的一部分。它們被保留以供將來參考,以防其中一條次級鏈後來成為主鏈。當出現兄弟區塊時,它們通常是在相同高度幾乎同時挖掘不同區塊的結果。
當接收到新區塊時,節點將嘗試將其添加到現有的區塊鏈中。節點將查看區塊的「前一區塊雜湊值」欄位,這是對區塊父區塊的引用。然後,節點將嘗試在現有區塊鏈中找到該父區塊。大多數時候,父區塊將是最佳鏈的「頂端」,這意味著這個新區塊擴展了最佳鏈。
有時新區塊不會擴展最佳鏈。在這種情況下,節點將把新區塊的標頭附加到次級鏈,然後比較次級鏈與先前最佳鏈的工作量。如果次級鏈現在是最佳鏈,節點將相應地_重組_其對已確認交易和可用 UTXO 的視圖。如果節點是礦工,它現在將構建一個候選區塊來擴展這個新的、更多工作量證明的鏈。
通過選擇最大累積工作量的有效鏈,所有節點 最終達成網路範圍的共識。透過選擇最大累積工作量的有效鏈,鏈之間的暫時差異最終會隨著更多工作的添加而得到解決,擴展其中一條可能的鏈。
| 本節中描述的區塊鏈分叉 是全球網路傳輸延遲的自然結果。我們稍後還將在本章中討論故意誘發的分叉。 | 
分叉幾乎總是在一個區塊內解決。如果兩個區塊幾乎同時被前一個分叉「兩側」的礦工發現,意外分叉可能會延伸到兩個區塊。然而,這種情況發生的機率很低。
比特幣 10 分鐘的區塊間隔是快速確認時間和分叉機率之間的設計妥協。更快的區塊時間會使交易看起來更快清算,但會導致更頻繁的區塊鏈分叉,而更慢的區塊時間會減少分叉數量,但使結算看起來更慢。
| 哪個更安全:在區塊間平均時間為 10 分鐘的情況下包含在一個區塊中的交易,還是在區塊間平均時間為 1 分鐘的情況下包含在一個區塊上面建立了九個區塊的交易?答案是它們同樣安全。想要雙重花費該交易的惡意礦工需要做等於網路總算力 10 分鐘的工作量才能創建一條具有相同工作量證明的鏈。 區塊之間更短的時間不會導致更早的結算。它唯一的優勢是為願意接受這些保證的人提供較弱的保證。例如,如果您願意接受礦工就最佳區塊鏈達成 3 分鐘的協議作為足夠的安全性,您會更喜歡具有 1 分鐘區塊的系統,在該系統中您可以等待三個區塊,而不是具有 10 分鐘區塊的系統。區塊之間的時間越短,在意外分叉上浪費的礦工工作就越多(除了其他問題),因此許多人更喜歡比特幣的 10 分鐘區塊,而不是更短的區塊 間隔。 | 
挖礦與算力抽獎
比特幣挖礦 是一個極具競爭性的行業。在比特幣存在的每一年中,算力都呈指數級增長。有些年份的增長反映了技術的完全改變,例如 2010 年和 2011 年,當時許多礦工從使用 CPU 挖礦切換到 GPU 挖礦和現場可編程閘陣列(FPGA)挖礦。2013 年,ASIC 挖礦的引入導致挖礦算力的又一次巨大飛躍,將雙 SHA256 函數直接放在專門用於挖礦目的的矽晶片上。第一批這樣的晶片能夠在單個盒子中提供比 2010 年整個比特幣網路更多的挖礦算力。
在撰寫本文時,據信比特幣挖礦設備不再有更大的飛躍,因為該行業已經達到了 摩爾定律的前沿,該定律規定計算密度將大約每 18 個月翻一番。儘管如此,網路的挖礦算力仍在快速增長。
額外 Nonce 解決方案
自 2012 年以來,挖礦 已演變為解決區塊標頭結構中的一個基本限制。在比特幣的早期,礦工可以透過迭代 nonce 直到產生的雜湊值低於目標來找到區塊。隨著難度增加,礦工經常循環遍歷 nonce 的所有 40 億個值而沒有找到區塊。然而,這很容易透過更新區塊時間戳記以計算經過的時間來解決。因為時間戳記是標頭的一部分,更改將允許礦工再次迭代 nonce 的值,並獲得不同的結果。然而,一旦挖礦硬體超過 4 GH/sec,這種方法變得越來越困難,因為 nonce 值在不到一秒的時間內就耗盡了。隨著 ASIC 挖礦設備開始超過 TH/sec 的雜湊率,挖礦軟體需要更多的 nonce 值空間才能找到有效的區塊。時間戳記可以稍微拉伸,但將其移得太遠到未來會導致區塊無效。區塊標頭中需要一個新的變化來源。
廣泛實施的一個解決方案是使用 coinbase 交易作為額外 nonce 值的來源。因為 coinbase 腳本可以儲存 2 到 100 位元組的資料,礦工開始使用該空間作為額外的 nonce 空間,允許他們探索更大範圍的區塊標頭值以找到有效的區塊。coinbase 交易包含在默克爾樹中,這意味著 coinbase 腳本中的任何更改都會導致默克爾根更改。8 位元組的額外 nonce 加上 4 位元組的「標準」nonce,允許礦工_每秒_探索總共 296(8 後跟 28 個零)種可能性,而無需修改時間戳記。
今天廣泛使用的另一個解決方案是將區塊標頭 versionbits 欄位的最多 16 位元用於挖礦,如 BIP320 中所述。如果每個挖礦設備都有自己的 coinbase 交易,這允許單個挖礦設備僅透過對區塊標頭進行更改就可以執行高達 281 TH/s。這使挖礦設備和協議比每 40 億個雜湊就在 coinbase 交易中遞增額外 nonce 更簡單,後者需要重新計算默克爾樹的整個左側直到 根。
挖礦池
在這個 高度競爭的環境中,單獨工作的個體礦工(也稱為單人礦工)沒有機會。他們找到區塊以抵消電費和硬體成本的可能性非常低,這代表著一種賭博,就像玩彩票一樣。即使是最快的消費級 ASIC 挖礦系統也無法跟上在發電站附近的巨型倉庫中堆疊數萬個這樣的系統的商業運營。現在許多礦工合作組成挖礦池,匯集他們的算力並在數千名參與者之間分享獎勵。透過參與礦池,礦工獲得較小份額的總體獎勵,但通常每天都會獲得獎勵,從而減少不確定性。
讓我們看一個具體的例子。假設一位礦工購買了算力總和為當前網路總算力 0.0001% 的挖礦硬體。如果協議難度從未改變,該礦工大約每 20 年會找到一個新區塊。這可能是一個漫長的等待時間才能獲得報酬。然而,如果該礦工與其他礦工一起在挖礦池中工作,其總算力為網路總算力的 1%,他們平均每天會挖到一個以上的區塊。該礦工只會獲得其應得的獎勵份額(減去礦池收取的任何費用),因此他們每天只會獲得少量收入。如果他們挖礦 20 年,他們會賺到與自己找到平均一個區塊相同的金額(不計礦池費用)。唯一的根本區別是他們收到付款的頻率。
挖礦池透過專門的礦池挖礦協議協調數百或數千名礦工。個體礦工在使用礦池創建帳戶後,配置他們的挖礦設備連接到礦池伺服器。他們的挖礦硬體在挖礦時保持連接到礦池伺服器,與其他礦工同步他們的努力。因此,礦池礦工分享挖掘區塊的努力,然後分享獎勵。
成功的區塊將獎勵支付給礦池比特幣地址,而不是個體礦工。礦池伺服器將定期向礦工的比特幣地址付款,一旦他們的獎勵份額達到一定的閾值。通常,礦池伺服器會收取獎勵的百分比費用,用於提供礦池挖礦服務。
參與礦池的礦工分擔尋找候選區塊解決方案的工作,為其挖礦貢獻賺取「份額」。挖礦池設定了一個更高的目標(較低的難度)來賺取份額,通常比比特幣網路的目標容易 1,000 倍以上。當礦池中的某人成功挖掘出一個區塊時,獎勵由礦池賺取,然後根據他們為努力貢獻的份額數量與所有礦工分享。
許多礦池對任何礦工開放,無論大小、專業或業餘。因此,礦池將有一些參與者只有一台小型挖礦機器,而其他人則有一個車庫充滿高端挖礦硬體。有些人將以數十千瓦的電力進行挖礦,其他人將運行消耗兆瓦電力的資料中心。挖礦池如何衡量個人貢獻,以便在沒有作弊可能性的情況下公平分配獎勵?答案是使用比特幣的工作量證明演算法來衡量每個礦池礦工的貢獻,但設定較低的難度,以便即使是最小的礦池礦工也能經常贏得份額,使他們值得為礦池做出貢獻。透過設定較低的難度來賺取份額,礦池衡量每個礦工完成的工作量。每次礦池礦工找到小於礦池目標的區塊標頭雜湊值時,他們證明他們已經完成了尋找該結果的雜湊工作。該標頭最終承諾 coinbase 交易,並可用於證明礦工使用了會將區塊獎勵支付給礦池的 coinbase 交易。每個礦池礦工都被給予稍微不同的 coinbase 交易模板,因此他們每個人都雜湊不同的候選區塊標頭,防止重複工作。
尋找份額的工作以統計可衡量的方式貢獻了尋找低於比特幣網路目標的雜湊值的總體努力。數千名礦工試圖尋找低值雜湊值最終會找到一個足夠低以滿足比特幣網路目標的雜湊值。
讓我們回到骰子遊戲的類比。如果骰子玩家擲骰子的目標是擲出小於 4(整體網路難度),礦池會設定一個更容易的目標,計算礦池玩家成功擲出小於 8 的次數。當礦池玩家擲出小於 8(礦池份額目標)時,他們賺取份額,但他們沒有贏得遊戲,因為他們沒有達到遊戲目標(小於 4)。礦池玩家將更經常地達到更容易的礦池目標,使他們非常有規律地賺取份額,即使他們沒有達到贏得遊戲的更難目標。偶爾,礦池玩家中的一位會擲出小於 4 的組合擲骰,礦池就會獲勝。然後,收益可以根據他們賺取的份額分配給礦池玩家。儘管 8 或更少的目標沒有獲勝,但這是衡量玩家擲骰子的公平方式,並且它偶爾會產生小於 4 的擲出。
同樣,挖礦池將設定一個(更高和更容易的)礦池目標,這將確保個體礦池礦工透過找到小於礦池目標的區塊標頭雜湊值而頻繁賺取份額。偶爾,其中一次嘗試將產生小於比特幣網路目標的區塊標頭雜湊值,使其成為有效區塊,整個礦池獲勝。
託管礦池
大多數 挖礦池都是「託管的」,這意味著有一家公司或個人運行礦池伺服器。礦池伺服器的所有者稱為_礦池運營商_,他們向礦池礦工收取收益的百分比費用。
礦池伺服器運行專門的軟體和礦池挖礦協議,協調礦池礦工的活動。礦池伺服器還連接到一個或多個完整的比特幣節點。這允許礦池伺服器代表礦池礦工驗證區塊和交易,使他們免於運行完整節點的負擔。對於一些礦工來說,能夠在不運行完整節點的情況下進行挖礦是加入託管礦池的另一個好處。
礦池礦工使用挖礦協議 (例如 Stratum(版本 1 或版本 2))連接到礦池伺服器。Stratum v1 創建包含候選區塊標頭模板的區塊_模板_。礦池伺服器透過聚合交易、添加 coinbase 交易(帶有額外的 nonce 空間)、計算默克爾根以及連結到前一個區塊雜湊值來構建候選區塊。然後將候選區塊的標頭作為模板發送給每個礦池礦工。然後,每個礦池礦工使用區塊模板進行挖礦,目標高於(更容易)比特幣網路目標,並將任何成功的結果發送回礦池伺服器以賺取份額。
Stratum v2 可選擇性地允許礦池中的個體礦工選擇在他們自己的區塊中出現的交易,他們可以使用自己的完整節點進行選擇。
點對點挖礦池(P2Pool)
使用 Stratum v1 的託管礦池 創造了礦池運營商作弊的可能性,他可能會指示礦池努力進行雙重花費交易或使區塊無效(參見 算力攻擊)。此外,中心化的礦池伺服器代表單點故障。如果礦池伺服器當機或被拒絕服務攻擊減慢,礦池礦工就無法挖礦。2011 年,為了解決這些中心化問題,提出並實施了一種新的礦池挖礦方法:P2Pool,一個沒有中央運營商的點對點挖礦池。
P2Pool 透過去中心化礦池伺服器的功能來工作,實現了一個稱為 _份額鏈_的類似區塊鏈的平行系統。份額鏈是一個以比比特幣區塊鏈更低難度運行的區塊鏈。份額鏈允許礦池礦工透過以每 30 秒一個份額區塊的速率在份額鏈上挖掘份額來進行去中心化礦池的協作。份額鏈上的每個區塊都記錄貢獻工作的礦池礦工的比例份額獎勵,從前一個份額區塊向前攜帶份額。當其中一個份額區塊也達到比特幣網路目標時,它會被傳播並包含在比特幣區塊鏈中,獎勵所有為獲勝份額區塊之前的所有份額做出貢獻的礦池礦工。本質上,不是由礦池伺服器追蹤礦池礦工的份額和獎勵,份額鏈允許所有礦池礦工使用類似於比特幣區塊鏈共識機制的去中心化共識機制追蹤所有份額。
P2Pool 挖礦比礦池挖礦更複雜,因為它要求礦池礦工運行一台具有足夠磁碟空間、記憶體和網際網路頻寬的專用電腦來支援比特幣完整節點和 P2Pool 節點軟體。P2Pool 礦工將他們的挖礦硬體連接到他們的本地 P2Pool 節點,該節點透過向挖礦硬體發送區塊模板來模擬礦池伺服器的功能。在 P2Pool 上,個體礦池礦工構建自己的候選區塊,像單人礦工一樣聚合交易,但隨後在份額鏈上協作挖礦。P2Pool 是一種混合方法,其優勢是比單人挖礦具有更細粒度的支出,但不會像託管礦池那樣給礦池運營商太多控制權。
儘管 P2Pool 減少了礦池運營商的權力集中,但它可以想像地容易受到對份額鏈本身的 51% 攻擊。更廣泛地採用 P2Pool 並不能解決比特幣本身的 51% 攻擊問題。相反,P2Pool 使比特幣整體更加穩健,作為多元化挖礦生態系統的一部分。在撰寫本文時,P2Pool 已經停用,但 Stratum v2 等新協議可以允許個體礦工選擇他們在自己的 區塊中包含的交易。
算力攻擊
比特幣的 共識機制至少在理論上容易受到試圖使用其算力進行不誠實或破壞性目的的礦工(或礦池)的攻擊。正如我們所看到的,共識機制取決於大多數礦工出於自身利益誠實行事。然而,如果一個礦工或一組礦工能夠獲得挖礦算力的顯著份額,他們可以攻擊共識機制,從而破壞比特幣網路的安全性和可用性。
重要的是要注意,算力攻擊對未來共識的影響最大。最佳區塊鏈上已確認的交易隨著時間的推移變得越來越不可變。雖然理論上可以在任何深度實現分叉,但實際上,強制進行非常深的分叉所需的計算能力是巨大的,使得舊區塊非常難以更改。算力攻擊也不會影響私鑰和簽章演算法的安全性。
針對共識機制的一種攻擊場景稱為 多數攻擊_或 _51% 攻擊。在這種情況下,一組礦工控制著網路總算力的大多數(例如 51%),串通攻擊比特幣。憑藉挖掘大多數區塊的能力,攻擊礦工可以在區塊鏈中造成故意的「分叉」並雙重花費交易或對特定交易或地址執行拒絕服務攻擊。分叉/雙重花費攻擊是指攻擊者透過在它們下面分叉並重新收斂到備用鏈上,使先前確認的區塊無效。憑藉足夠的算力,攻擊者可以連續使六個或更多區塊無效,導致被認為是不可變的(六次確認)交易被無效化。請注意,雙重花費只能在攻擊者自己的交易上完成,攻擊者可以為這些交易產生有效的簽章。雙重花費自己的交易可能是有利可圖的,如果使交易無效允許攻擊者在不付款的情況下獲得不可逆轉的交換付款或產品。
讓我們檢查一個 51% 攻擊的實際例子。在第一章中,我們看了 Alice 和 Bob 之間的交易。Bob,賣家,願意在不等待確認(在區塊中挖掘)的情況下接受付款,因為與快速客戶服務的便利性相比,小物品雙重花費的風險較低。這類似於咖啡店的做法,對於低於 25 美元的金額,他們接受信用卡付款而不需要簽名,因為信用卡退款的風險較低,而延遲交易以獲得簽名的成本相對較大。相反,出售更昂貴的物品以換取比特幣會面臨雙重花費攻擊的風險,買家在其中廣播一個競爭交易,花費相同的輸入(UTXO)之一並取消對商家的付款。51% 攻擊允許攻擊者在新鏈中雙重花費他們自己的交易,從而撤銷舊鏈中的相應交易。
在我們的示例中,惡意攻擊者 Mallory 前往 Carol 的畫廊並購買了一套描繪中本聰為普羅米修斯的美麗畫作。Carol 以 250,000 美元的比特幣價格將畫作賣給 Mallory。Carol 沒有等待六次或更多確認,而是在只有一次確認後就包裝並將畫作交給 Mallory。Mallory 與一位同謀 Paul 合作,Paul 運營著一個大型礦池,一旦 Mallory 的交易被包含在區塊中,同謀就會發起攻擊。Paul 指示礦池重新挖掘與包含 Mallory 交易的區塊相同的區塊高度,用雙重花費與 Mallory 付款相同輸入的交易替換 Mallory 對 Carol 的付款。雙重花費交易消耗相同的 UTXO 並將其支付回 Mallory 的錢包,而不是支付給 Carol,本質上允許 Mallory 保留比特幣。然後,Paul 指示礦池挖掘一個額外的區塊,以使包含雙重花費交易的鏈比原始鏈更長(導致在包含 Mallory 交易的區塊下面分叉)。當區塊鏈分叉有利於新(更長)鏈解決時,雙重花費交易取代了對 Carol 的原始付款。Carol 現在失去了三幅畫,也沒有付款。在所有這些活動中,Paul 的礦池參與者可能仍然幸福地不知道雙重花費嘗試,因為他們使用自動礦工進行挖礦,無法監控每筆交易或區塊。
為了防止這種攻擊,出售大價值物品的商家必須在將產品交給買家之前至少等待六次確認。有時等待六次以上的確認可能是有保證的。或者,商家應該使用託管多重簽章帳戶,在託管帳戶被資助後再次等待幾次確認。經過的確認越多,透過重組區塊鏈使交易無效就越困難。對於高價值物品,即使買家必須等待 24 小時才能交付(這將對應大約 144 次確認),透過比特幣付款仍然會很方便和高效。
除了雙重花費攻擊之外,共識攻擊的另一種情況是拒絕向特定參與者(特定比特幣地址)提供服務。擁有大多數挖礦算力的攻擊者可以審查交易。如果它們被包含在另一個礦工挖掘的區塊中,攻擊者可以故意分叉並重新挖掘該區塊,再次排除特定交易。這種類型的攻擊可能會導致對特定地址或一組地址的持續拒絕服務,只要攻擊者控制大多數挖礦算力。
儘管它的 名稱,51% 攻擊場景實際上並不需要 51% 的算力。事實上,這種攻擊可以用較小百分比的算力來嘗試。51% 的閾值只是這種攻擊幾乎肯定成功的水平。算力攻擊本質上是對下一個區塊的拔河,「更強」的群體更有可能獲勝。憑藉較少的算力,成功的機率會降低,因為其他礦工控制 了區塊的產生。以另一種方式來看,攻擊者擁有的算力越多,他可以故意創建的分叉就越長,他可以使最近過去的區塊無效,或者他可以控制未來的區塊就越多。安全研究組織使用統計建模聲稱,只需 30% 的算力就可以進行各種類型的算力攻擊。
由挖礦池 引起的控制中心化引入了礦池運營商進行營利性攻擊的風險。託管礦池中的礦池運營商控制候選區塊的構建,並控制包含哪些交易。這賦予礦池運營商排除交易或引入雙重花費交易的權力。如果以有限和微妙的方式濫用權力,礦池運營商可以想像從算力攻擊中獲利而不被注意到。
然而,並非所有攻擊者都會受到利潤的驅使。一種潛在的攻擊場景是攻擊者打算破壞比特幣網路,而沒有從這種破壞中獲利的可能性。旨在癱瘓比特幣的惡意攻擊將需要大量投資和秘密計劃,但可以想像由資金充足的、很可能是國家資助的攻擊者發起。或者,資金充足的攻擊者可以透過同時積累挖礦硬體、破壞礦池運營商以及用拒絕服務攻擊其他礦池來攻擊比特幣。所有這些場景在理論上都是可能的。
毫無疑問,嚴重的算力攻擊會在短期內侵蝕對比特幣的信心,可能導致價格大幅下跌。然而,比特幣網路和軟體正在不斷發展,因此攻擊將得到比特幣 社群的對策。
改變共識規則
共識的 規則決定了交易和區塊的有效性。這些規則是所有比特幣節點之間協作的基礎,並負責將所有本地視角收斂到整個網路上的單一一致的區塊鏈。
雖然共識規則在短期內是不變的,並且必須在所有節點之間保持一致,但它們在長期內並非不變。為了發展比特幣系統,規則可以不時更改以容納新功能、改進或錯誤修復。然而,與傳統軟體開發不同,共識系統的升級要困難得多,需要所有參與者之間的協調。
硬分叉
在 組裝和選擇區塊鏈 中,我們看到 比特幣網路如何可能暫時分歧,網路的兩個部分在短時間內遵循區塊鏈的兩個不同分支。我們看到這個過程如何自然發生,作為網路正常運作的一部分,以及在挖掘一個或多個區塊後網路如何收斂到一個共同的區塊鏈。
網路可能分歧成遵循兩條鏈的另一種情況是:共識規則的更改。這種類型的分叉被稱為_硬分叉_,因為在分叉之後,網路可能無法收斂到單一鏈。相反,兩條鏈可以獨立發展。硬分叉發生在網路的一部分在與網路其餘部分不同的共識規則集下運作時。這可能由於錯誤或共識規則實作的故意更改而發生。
硬分叉可用於更改共識規則,但它們需要系統中所有參與者之間的協調。任何未升級到新共識規則的節點都無法參與共識機制,並在硬分叉時刻被迫進入單獨的鏈。因此,硬分叉引入的更改可以被認為不是「向前相容」的,因為未升級的系統由於新的共識規則而無法再處理區塊。
讓我們透過一個具體的例子檢查硬分叉的機制。
 
然而,稍後在區塊高度 6,發布了客戶端的新實作,共識規則發生了更改。從區塊高度 7 開始,運行此新實作的礦工將接受一種新型的比特幣;讓我們稱之為「foocoin」。緊接著,運行新實作的節點創建了一筆包含 foocoin 的交易,帶有更新軟體的礦工挖掘包含此交易的區塊 7b。
任何未升級軟體以驗證 foocoin 的節點或礦工現在都無法處理區塊 7b。從他們的角度來看,包含 foocoin 的交易和包含該交易的區塊 7b 都是無效的,因為他們根據舊的共識規則評估它們。這些節點將拒絕該交易和該區塊,並且不會傳播它們。使用舊規則的任何礦工都不會接受區塊 7b,並將繼續挖掘其父區塊為區塊 6 的候選區塊。事實上,使用舊規則的礦工可能甚至不會收到區塊 7b,如果他們連接的所有節點也都遵守舊規則,因此不傳播該區塊。最終,他們將能夠挖掘區塊 7a,這在舊規則下是有效的,並且不包含任何帶有 foocoin 的交易。
兩條鏈從這一點繼續分歧。「b」鏈上的礦工將繼續接受並挖掘包含 foocoin 的交易,而「a」鏈上的礦工將繼續忽略這些交易。即使區塊 8b 不包含任何 foocoin 交易,「a」鏈上的礦工也無法處理它。對他們來說,它似乎是一個無效的區塊,因為其父區塊「7b」未被識別為有效 區塊。
硬分叉:軟體、網路、挖礦和鏈
對於 軟體開發人員來說,「分叉」一詞有另一個含義,為「硬分叉」一詞增添了混淆。在開源軟體中,當一組開發人員選擇遵循不同的軟體路線圖並開始開源專案的競爭實作時,就會發生分叉。我們已經討論了將導致硬分叉的兩種情況:共識規則中的錯誤和共識規則的故意修改。在故意更改共識規則的情況下,軟體分叉先於硬分叉。然而,要發生這種類型的硬分叉,必須開發、採用和啟動共識規則的新軟體實作。
嘗試更改共識規則的軟體分叉示例包括 Bitcoin XT 和 Bitcoin Classic。然而,這些程式都沒有導致硬分叉。雖然軟體分叉是必要的先決條件,但它本身不足以發生硬分叉。要發生硬分叉,競爭實作必須被礦工、錢包和中介節點採用並啟動新規則。相反,有許多 Bitcoin Core 的替代實作,甚至軟體分叉,它們不會更改共識規則,除非有錯誤,否則可以在網路上共存並互操作,而不會導致硬分叉。
共識規則可能在交易或區塊的驗證中以明顯和明確的方式不同。規則也可能在共識規則的實作中以更微妙的方式不同,因為它們適用於比特幣腳本或數位簽章等密碼學原語。最後,共識規則可能以意外的方式不同,因為系統限制或實作細節強加的隱式共識約束。後者的一個例子是在 Bitcoin Core 0.7 升級到 0.8 期間看到的意外硬分叉,這是由用於儲存區塊的 Berkeley DB 實作的限制引起的。
從概念上講,我們可以認為硬分叉分為四個階段:軟體分叉、網路分叉、挖礦分叉和鏈分叉。該過程從開發人員創建具有修改共識規則的客戶端的替代實作開始。
當這個分叉實作部署在網路中時,一定百分比的礦工、錢包用戶和中介節點可能會採用並運行此實作。首先,網路將分叉。基於共識規則原始實作的節點將拒絕根據新規則創建的任何交易和區塊。此外,遵循原始共識規則的節點可能會與向它們發送這些無效交易和區塊的任何節點斷開連接。結果,網路可能會分區為兩個:舊節點將僅保持與舊節點的連接,新節點將僅連接到新節點。基於新規則的單個區塊將在網路中傳播,並導致分區為兩個網路。
新礦工可能會在新區塊之上挖礦,而舊礦工將基於舊規則挖掘單獨的鏈。分區網路將使在單獨共識規則下運作的礦工不太可能收到彼此的區塊,因為他們連接到兩個單獨的 網路。
分歧的礦工和難度
當礦工 分歧挖掘兩條不同的鏈時,算力在鏈之間分配。挖礦算力可以在兩條鏈之間以任何比例分配。新規則可能只被少數人遵循,或者被絕大多數挖礦算力遵循。
例如,假設 80%-20% 的分配,大多數挖礦算力使用新的共識規則。我們還假設分叉在重新定目標期間之後立即發生。
兩條鏈將各自繼承重新定目標期間的難度。新的共識規則將有 80% 的先前可用挖礦算力承諾給它們。從這條鏈的角度來看,挖礦算力相對於前一個時期突然下降了 20%。區塊將平均每 12.5 分鐘找到一次,代表可用於擴展此鏈的挖礦算力下降了 20%。這個區塊發行率將繼續(除非算力有任何變化),直到挖掘 2,016 個區塊,這將需要大約 25,200 分鐘(每個區塊 12.5 分鐘),或 17.5 天。17.5 天後,將發生重新定目標,難度將調整(降低 20%)以再次產生 10 分鐘的區塊,基於此鏈中減少的算力。
少數鏈,根據舊規則以僅 20% 的算力挖礦,將面臨更困難的任務。在這條鏈上,區塊現在將平均每 50 分鐘挖掘一次。難度不會調整 2,016 個區塊,這將需要 100,800 分鐘,或大約 10 週才能挖掘。假設每個區塊的固定容量,這也將導致交易容量減少 5 倍,因為每小時可用於記錄交易的區塊較少。
有爭議的硬分叉
這是 去中心化共識軟體開發的黎明。正如開發中的其他創新改變了軟體的方法和產品,並在其後創建了新的方法論、新工具和新社群一樣,共識軟體開發也代表了電腦科學的新前沿。從比特幣開發的辯論、實驗和磨難中,我們將看到新的開發工具、實踐、方法論和社群出現。
硬分叉被視為有風險,因為它們迫使少數人要麼升級,要麼留在少數鏈上。將整個系統分割成兩個競爭系統的風險被許多人視為不可接受的風險。因此,許多開發人員不願使用硬分叉機制來實施共識規則的升級,除非整個網路幾乎一致支援。任何沒有幾乎一致支援的硬分叉提案都被認為太有爭議,無法在不冒系統分區風險的情況下嘗試。
我們已經看到了解決硬分叉風險的新方法論的出現。在下一節中,我們將看看軟分叉以及信號和啟動共識修改的方法。
軟分叉
並非所有 共識規則更改都會導致硬分叉。只有向前不相容的共識更改才會導致分叉。如果以這樣一種方式實現更改,即未修改的客戶端仍然將交易或區塊視為在先前規則下有效,則更改可以在沒有分叉的情況下發生。
引入術語_軟分叉_是為了將這種升級方法與「硬分叉」區分開來。實際上,軟分叉根本不是分叉。軟分叉是共識規則的向前相容更改,允許未升級的客戶端繼續在新規則下運作於共識中。
軟分叉的一個不太明顯的方面是,軟分叉升級只能用於約束共識規則,而不能擴展它們。為了向前相容,根據新規則創建的交易和區塊在舊規則下也必須是有效的,但反之則不然。新規則只能限制有效的內容;否則,當根據舊規則被拒絕時,它們將觸發硬分叉。
軟分叉可以用多種方式實現——該術語不指定特定的方法,而是指定所有具有一個共同點的一組方法:它們不需要所有節點升級或強制未升級的節點退出共識。
兩個軟分叉已在比特幣中實作,基於對 NOP 操作碼的重新解釋。比特幣腳本有 10 個保留供將來使用的操作碼,NOP1 到 NOP10。根據共識規則,這些操作碼在腳本中的存在被解釋為無效運算子,這意味著它們沒有效果。執行在 NOP 操作碼之後繼續,就好像它不在那裡一樣。
因此,軟分叉可以修改 NOP 程式碼的語義以賦予它新的含義。例如,BIP65(CHECKLOCKTIMEVERIFY)重新解釋了 NOP2 操作碼。實現 BIP65 的客戶端將 NOP2 解釋為 OP_CHECKLOCKTIMEVERIFY,並對包含此操作碼在其鎖定腳本中的 UTXO 施加絕對鎖定時間共識規則。根據 BIP65 有效的交易在未實現(不知道)BIP65 的任何客戶端上也是有效的。對於舊客戶端,腳本包含一個 NOP 程式碼,它被 忽略。
對軟分叉的批評
基於 NOP 操作碼的軟分叉 相對沒有爭議。NOP 操作碼被放置在比特幣腳本中,明確目標是允許非破壞性升級。
然而,許多開發人員擔心其他軟分叉升級方法會做出不可接受的權衡。對軟分叉更改的常見批評包括:
- 技術債務
- 
因為軟分叉比硬分叉升級在技術上更複雜,所以它們 引入了_技術債務_,這個術語是指由於過去做出的設計權衡而增加未來程式碼維護成本。程式碼複雜性反過來增加了錯誤和安全漏洞的可能性。 
- 驗證放寬
- 
未修改的客戶端在不評估修改的共識規則的情況下將交易視為有效。實際上,未修改的客戶端沒有使用完整範圍的共識規則進行驗證,因為它們對新規則視而不見。這適用於基於 NOP 的升級以及其他軟分叉升級。 
- 不可逆轉的升級
- 
因為軟分叉創建具有額外共識約束的交易,所以它們在實踐中變成了不可逆轉的升級。如果軟分叉升級在啟動後被逆轉,根據新規則創建的任何交易可能會導致舊規則下的資金損失。例如,如果根據舊規則評估 CLTV 交易,則沒有時間鎖約束,可以隨時花費它。因此,批評者認為,由於錯誤而必須逆轉的失敗軟分叉幾乎肯定會導致資金損失。 
使用區塊版本的軟分叉信號
由於軟分叉允許未修改的客戶端繼續在共識內運作,一種「啟動」軟分叉的機制是透過礦工信號表明他們已準備好並願意執行新的共識規則。如果所有礦工執行新規則,則未修改的節點接受升級節點將拒絕的區塊沒有風險。這個機制是由 BIP34 引入的。
BIP34:信號和啟動
BIP34 使用 區塊版本欄位允許礦工為特定的共識規則更改發出準備就緒的信號。在 BIP34 之前,區塊版本透過_慣例_而不是_共識_強制設定為「1」。
BIP34 定義了一個共識規則更改,要求 coinbase 交易的 coinbase 欄位(輸入)包含區塊高度。在 BIP34 之前,coinbase 可以包含礦工選擇包含的任何任意資料。在 BIP34 啟動後,有效的區塊必須在 coinbase 的開頭包含特定的區塊高度,並用大於或等於「2」的區塊版本號標識。
為了表明他們準備好執行 BIP34 的規則,礦工將區塊版本設定為「2」而不是「1」。這並不會立即使版本「1」區塊無效。一旦啟動,版本「1」區塊將變為無效,所有版本「2」區塊將需要在 coinbase 中包含區塊高度才能有效。
BIP34 基於 1,000 個區塊的滾動視窗定義了兩步啟動機制。礦工會透過使用「2」作為版本號構建區塊來表明他們的個人準備狀態。嚴格來說,這些區塊還不必遵守在 coinbase 交易中包含區塊高度的新共識規則,因為共識規則尚未啟動。共識規則分兩步啟動:
- 
如果 75%(最近 1,000 個區塊中的 750 個)標記為版本「2」,則版本「2」區塊必須在 coinbase 交易中包含區塊高度,否則將被拒絕為無效。版本「1」區塊仍然被網路接受,不需要包含區塊高度。舊和新共識規則在這段時間內共存。 
- 
當 95%(最近 1,000 個區塊中的 950 個)是版本「2」時,版本「1」區塊不再被視為有效。版本「2」區塊僅在它們在 coinbase 中包含區塊高度時才有效(根據先前的閾值)。此後,所有區塊都必須遵守新的共識規則,所有有效區塊都必須在 coinbase 交易中包含區塊高度。 
在 BIP34 規則下成功信號和啟動後,此機制又被使用了兩次來啟動軟分叉:
在 BIP65 啟動後,BIP34 的信號和啟動機制退役並被 接下來描述的 BIP9 信號機制取代。
BIP9:信號和啟動
BIP34、BIP66 和 BIP65 使用的 機制在啟動三個軟分叉方面是成功的。然而,它被替換了,因為它有幾個限制:
- 
透過使用區塊版本的整數值,一次只能啟動一個軟分叉,因此需要軟分叉提案之間的協調以及對其優先順序和順序的一致意見。 
- 
此外,由於區塊版本遞增,該機制沒有提供一種直接的方式來拒絕更改然後提出不同的更改。如果舊客戶端仍在運行,他們可能會將新更改的信號誤認為是先前被拒絕的更改的信號。 
- 
每個新的更改不可逆轉地減少了未來更改可用的區塊版本。 
提出 BIP9 是為了克服這些挑戰並提高實施未來更改的速度和便利性。
BIP9 將區塊版本解釋為位元欄位而不是整數。因為區塊版本最初用作版本 1 到 4 的整數,所以只有 29 位元可用作位元欄位。這留下了 29 個位元可用於獨立且同時地在 29 個不同的提案上發出準備就緒的信號。
BIP9 還為信號和啟動設定了最大時間。這樣礦工就不需要永遠信號。如果提案在 TIMEOUT 期間(在提案中定義)內未啟動,則該提案被視為被拒絕。提案可以以不同的位元重新提交以進行信號,更新啟動期間。
此外,在 TIMEOUT 過去並且功能已啟動或被拒絕之後,信號位元可以重新用於另一個功能而不會造成混淆。因此,最多可以並行信號 29 個更改。TIMEOUT 之後,位元可以「回收」以提出新的更改。
| 雖然信號位元可以重新使用或回收,只要投票期不重疊,BIP9 的作者建議只有在必要時才重新使用位元;由於舊軟體中的錯誤,可能會發生意外行為。簡而言之,我們不應期望看到重新使用,直到所有 29 個位元都被使用過一次。 | 
提出的更改由包含以下欄位的資料結構標識:
- name
- 
用於區分提案的簡短描述。通常是描述提案的 BIP,如「bipN」,其中 N 是 BIP 號碼。 
- bit
- 
0 到 28,礦工用來為此提案發出批准信號的區塊版本中的位元。 
- starttime
- 
信號開始後的時間(基於 MTP),之後位元的值被解釋為為提案發出準備就緒的信號。 
- endtime
- 
如果未達到啟動閾值,則更改被視為被拒絕的時間(基於 MTP)。 
與 BIP34 不同,BIP9 基於 2,016 個區塊的難度重新定目標期間計算整個間隔中的啟動信號。對於每個重新定目標期間,如果為提案發出信號的區塊總和超過 95%(2,016 個中的 1,916 個),該提案將在一個重新定目標期間後啟動。
BIP9 提供了一個提案狀態圖來說明提案的各個階段和轉換,如 BIP9 狀態轉換圖。 所示。
 
提案在其參數在比特幣軟體中已知(定義)後從 DEFINED 狀態開始。對於 MTP 在開始時間之後的區塊,提案狀態轉換到 STARTED。如果在重新定目標期間內超過投票閾值並且未超過超時,則提案狀態轉換到 LOCKED_IN。一個重新定目標期間後,提案變為 ACTIVE。提案一旦達到該狀態就永久保持在 ACTIVE 狀態。如果在達到投票閾值之前超時過期,則提案狀態更改為 FAILED,表示被拒絕的提案。FAILED 提案永久保持在該狀態。
BIP9 首次實施是為了啟動 CHECKSEQUENCEVERIFY 和相關的 BIP(68、112、113)。名為「csv」的提案於 2016 年 7 月成功啟動。
該 標準定義在 BIP9(帶有超時和延遲的版本位元)中。
BIP8:具有早期啟動的強制鎖定
在 BIP9 成功用於 CSV 相關軟分叉後,下一次軟分叉共識更改的實施也嘗試使用它進行礦工強制啟動。然而,一些人反對該軟分叉提案,稱為 segwit,幾個月來很少有礦工表示準備好執行 segwit。
後來發現,一些礦工,特別是與反對者相關的礦工,可能一直在使用一種稱為_隱蔽 ASICBoost_ 的功能的硬體,這給了他們相對於使用它的其他礦工的隱藏優勢。無意中,segwit 干擾了使用隱蔽 ASICBoost 的能力——如果 segwit 被啟動,使用它的礦工將失去他們的隱藏優勢。
在社群發現這種利益衝突後,一些用戶決定他們想要行使他們的權力,不接受礦工的區塊,除非這些區塊遵循某些規則。用戶最終想要的規則是 segwit 添加的新規則,但用戶想要透過利用計劃在礦工發出足夠準備就緒信號時執行 segwit 規則的大量節點來增加他們的努力。一位匿名開發人員提出了 BIP148,該提案要求任何實現它的節點從某個日期開始拒絕所有不為 segwit 發出信號的區塊,並持續到 segwit 啟動。
儘管只有有限數量的用戶實際運行 BIP148 程式碼,但許多其他用戶似乎同意這種情緒,並可能準備承諾 BIP148。在 BIP148 生效前幾天,幾乎所有礦工都開始表示他們準備好執行 segwit 的規則。Segwit 在大約兩週後達到其鎖定閾值,並在大約兩週後啟動。
許多用戶開始相信 BIP9 的一個缺陷是礦工可以透過不發出一年的信號來阻止啟動嘗試成功。他們想要一種機制,確保軟分叉在特定區塊高度之前啟動,但也允許礦工表示他們已準備好提前鎖定它。
為此開發的方法是 BIP8,它類似於 BIP9,除了它定義了一個 MUST_SIGNAL 期間,礦工必須表示他們已準備好執行軟分叉提案。
發布了使用 BIP8 嘗試在 2021 年啟動 taproot 提案的軟體,並且有證據表明至少有少數用戶運行了該軟體。其中一些用戶還聲稱,他們願意使用 BIP8 強制礦工啟動 taproot 是它最終啟動的原因。他們聲稱,如果 taproot 沒有快速啟動,其他用戶也會開始運行 BIP8。不幸的是,沒有辦法證明會發生什麼,因此我們無法確定 BIP8 對 taproot 的啟動貢獻了多少。
快速試驗:快速失敗或最終成功
儘管 BIP9 本身似乎沒有導致儘管提案得到廣泛支援但 segwit 的啟動,但對許多協議開發人員來說並不清楚 BIP9 本身是一個失敗。如前所述,礦工最初未能為 segwit 發出支援信號可能主要是一次性利益衝突的結果,該衝突在未來不會適用。對一些人來說,再次嘗試 BIP9 似乎是值得的。其他人不同意並想使用 BIP8。
在對特定啟動想法最感興趣的人之間進行數月的討論後,為了啟動 taproot,提出了一個妥協方案。建議修改版本的 BIP9,該版本只會給礦工很短的時間來表示他們打算執行 taproot 規則。如果信號不成功,可以使用不同的啟動機制(或者,可能放棄這個想法)。如果信號成功,強制執行將在大約六個月後在指定的區塊高度開始。這個機制被幫助推廣它的人之一命名為_快速試驗_。
嘗試了快速試驗啟動,礦工迅速表示他們願意執行 taproot 的規則,taproot 在大約六個月後成功啟動。對快速試驗的支持者來說,這顯然是成功的。其他人仍然失望 BIP8 沒有被使用。
目前還不清楚未來嘗試啟動軟分叉是否會再次使用快速試驗。
共識軟體開發
共識軟體 繼續發展,關於更改共識規則的各種機制有很多討論。就其本質而言,比特幣為更改的協調和共識設定了非常高的標準。作為一個去中心化的系統,它沒有可以將其意志強加於網路參與者的「權威」。權力分散在礦工、協議開發人員、錢包開發人員、交易所、商家和終端用戶等多個群體之間。任何這些群體都不能單方面做出決定。例如,雖然礦工可以透過簡單多數(51%)審查交易,但他們受到其他群體的同意的約束。如果他們單方面行動,網路的其餘參與者可能會拒絕接受他們的區塊,將經濟活動保持在少數鏈上。沒有經濟活動(交易、商家、錢包、交易所),礦工將挖掘一種無價值的貨幣,其區塊為空。這種權力的分散意味著所有參與者都必須協調,否則無法做出任何更改。現狀是這個系統的穩定狀態,如果有絕大多數的強烈共識,只有少數更改是可能的。軟分叉的 95% 閾值反映了這一現實。
重要的是要認識到,共識開發沒有完美的解決方案。硬分叉和軟分叉都涉及權衡。對於某些類型的更改,軟分叉可能是更好的選擇;對於其他更改,硬分叉可能是更好的選擇。沒有完美的選擇;兩者都有風險。共識軟體開發的一個不變特徵是更改很困難,共識迫使妥協。
有些人認為這是共識系統的弱點。隨著時間的推移,您可能會將其視為系統最大的優勢。
在本書的這一點上,我們已經完成了對比特幣系統本身的討論。剩下的是建立在比特幣之上的軟體、工具和其他協議。
比特幣安全性
保護您的比特幣具有挑戰性,因為比特幣不像銀行帳戶中的餘額。您的比特幣非常類似於數位現金或黃金。您可能聽過這句話:「佔有是法律的十分之九。」在比特幣中,佔有是法律的十分之十。擁有花費某些比特幣的金鑰相當於擁有現金或一塊貴金屬。您可能會遺失它、放錯地方、被盜竊,或不小心給某人錯誤的金額。在每一種情況下,用戶在協議內都沒有追索權,就像他們在公共人行道上掉了現金一樣。
然而,比特幣系統具有現金、黃金和銀行帳戶所沒有的能力。包含您金鑰的比特幣錢包可以像任何檔案一樣備份。它可以儲存在多個副本中,甚至可以列印在紙上作為硬拷貝備份。您無法「備份」現金、黃金或銀行帳戶。比特幣與之前出現的任何事物都有足夠的不同,以至於我們也需要以一種新穎的方式思考如何保護我們的比特幣。
安全原則
比特幣中的 核心原則是去中心化,它對安全性有重要影響。集中式模型,例如傳統銀行或支付網路,依賴於存取控制和審查以將不良行為者拒之門外。相比之下,像比特幣這樣的去中心化系統將責任和控制權推給用戶。因為網路的安全性基於獨立驗證,所以網路可以是開放的,比特幣流量不需要加密(儘管加密仍然可能有用)。
在傳統支付網路上,例如信用卡系統,付款是開放式的,因為它包含用戶的私人識別符(信用卡號)。在初始收費後,任何能夠存取該識別符的人都可以「拉取」資金並一次又一次地向所有者收費。因此,支付網路必須使用加密進行端到端保護,並且必須確保沒有竊聽者或中介可以在傳輸或儲存(靜態)時破壞支付流量。如果不良行為者獲得系統存取權,他可以破壞當前交易_以及_可用於創建新交易的支付憑證。更糟糕的是,當客戶資料被破壞時,客戶會面臨身份盜竊的風險,必須採取行動防止被破壞帳戶的欺詐使用。
比特幣有著顯著的不同。比特幣交易僅授權將特定價值給予特定接收者,並且無法偽造。它不會揭示任何私人資訊,例如當事人的身份,也不能用於授權額外的付款。因此,比特幣支付網路不需要加密或防止竊聽。事實上,您可以透過開放的公共頻道廣播比特幣交易,例如不安全的 WiFi 或藍牙,而不會損失安全性。
比特幣的去中心化安全模型將大量權力交到用戶手中。擁有這種權力的同時也伴隨著維護金鑰保密性的責任。對於大多數用戶來說,這並不容易做到,尤其是在通用計算設備上,例如連接網際網路的智慧型手機或筆記型電腦。儘管比特幣的去中心化模型防止了信用卡常見的大規模妥協類型,但許多用戶無法充分保護他們的金鑰,並一個接一個地被駭客入侵。
安全地開發比特幣系統
比特幣開發人員的關鍵原則是去中心化。大多數開發人員將熟悉集中式安全模型,可能會想將這些模型應用於他們的比特幣應用程式,結果可能是災難性的。
比特幣的安全性依賴於對金鑰的去中心化控制和用戶的獨立交易驗證。如果您想利用比特幣的安全性,您需要確保您保持在比特幣安全模型內。簡單來說:不要從用戶手中奪走對金鑰的控制,也不要將驗證外包。
例如,許多早期的比特幣交易所將所有用戶資金集中在一個「熱」錢包中,金鑰儲存在單一伺服器上。這樣的設計從用戶手中奪走控制權,並將對金鑰的控制集中在單一系統中。許多這樣的系統已經被駭客入侵,給他們的客戶帶來災難性後果。
除非您準備大量投資於運營安全性、多層存取控制和稽核(就像傳統銀行那樣),否則您應該非常謹慎地考慮將資金帶出比特幣的去中心化安全環境。即使您有資金和紀律來實施強大的安全模型,這樣的設計僅僅複製了傳統金融網路的脆弱模型,該模型飽受身份盜竊、貪污和挪用公款的困擾。要利用比特幣獨特的去中心化安全模型,您必須避免可能感覺熟悉但最終會破壞比特幣 安全性的集中式架構的誘惑。
信任根
傳統 安全架構基於一個稱為_信任根_的概念,它是一個受信任的核心,用作整個系統或應用程式安全性的基礎。安全架構圍繞信任根發展為一系列同心圓,就像洋蔥的層次一樣,從中心向外擴展信任。每一層都使用存取控制、數位簽章、加密和其他安全原語建立在更受信任的內層之上。隨著軟體系統變得更加複雜,它們更有可能包含錯誤,這使它們容易受到安全威脅。因此,軟體系統變得越複雜,就越難以保護。信任根概念確保大部分信任放置在系統最不複雜的部分,因此是系統中最不易受攻擊的部分,而更複雜的軟體則圍繞其分層。這種安全架構以不同的規模重複,首先在單一系統的硬體內建立信任根,然後透過作業系統將該信任根擴展到更高級別的系統服務,最後跨越許多伺服器,以信任遞減的同心圓分層。
比特幣安全架構有所不同。在比特幣中,共識系統創建了一個完全去中心化的受信任區塊鏈。正確驗證的區塊鏈使用創世區塊作為信任根,建立一條信任鏈直到當前區塊。比特幣系統可以並且應該使用區塊鏈作為其信任根。在設計由許多不同系統上的服務組成的複雜比特幣應用程式時,您應該仔細檢查安全架構,以確定信任被放置在哪裡。最終,唯一應該被明確信任的是完全驗證的區塊鏈。如果您的應用程式明確或隱含地將信任賦予區塊鏈以外的任何事物,那應該是關注的來源,因為它引入了漏洞。評估應用程式安全架構的一個好方法是考慮每個單獨的組件,並評估該組件完全被破壞並在惡意行為者的控制下的假設情境。依次檢查應用程式的每個組件,並評估如果該組件被破壞,對整體安全性的影響。如果您的應用程式在組件被破壞時不再安全,這表明您在這些組件中錯誤地放置了信任。沒有漏洞的比特幣應用程式應該只容易受到比特幣共識機制的破壞,這意味著其信任根基於比特幣安全架構中最強大的部分。
被駭的比特幣交易所的眾多例子用來強調這一點,因為它們的安全架構和設計即使在最粗略的審查下也會失敗。這些集中式實作明確地將信任投資於比特幣區塊鏈之外的眾多組件,例如熱錢包、集中式資料庫、易受攻擊的加密金鑰和 類似方案。
用戶安全最佳實踐
人類 使用物理安全控制已有數千年的歷史。相比之下,我們在數位安全方面的經驗不到 50 年。現代通用作業系統不是很安全,也不是特別適合儲存數位貨幣。我們的電腦透過持續開啟的網際網路連接不斷暴露於外部威脅。它們運行來自數百位作者的數千個軟體組件,通常對用戶的檔案有無限制的存取權。在您電腦上安裝的數千個軟體中的單一惡意軟體可以破壞您的鍵盤和檔案,竊取儲存在錢包應用程式中的任何比特幣。保持電腦無病毒和無木馬所需的電腦維護水平超出了絕大多數電腦用戶的技能水平。
儘管在資訊安全方面進行了數十年的研究和進步,數位資產仍然非常容易受到有決心的對手的攻擊。即使是金融服務公司、情報機構和國防承包商中最受保護和限制的系統,也經常被入侵。比特幣創建了具有內在價值的數位資產,可以被竊取並立即且不可逆轉地轉移給新所有者。這為駭客創造了巨大的動機。到目前為止,駭客必須在破壞身份資訊或帳戶憑證(例如信用卡和銀行帳戶)後將其轉換為價值。儘管圍欄和洗錢金融資訊困難重重,但我們看到盜竊事件不斷升級。比特幣使這個問題升級,因為它不需要被圍欄或洗錢;比特幣本身就是有價值的。
比特幣也創造了改善電腦安全的動機。以前,電腦破壞的風險是模糊和間接的,比特幣使這些風險變得清晰和明顯。在電腦上持有比特幣有助於將用戶的注意力集中在改善電腦安全的需求上。作為比特幣和其他數位貨幣激增和增加採用的直接結果,我們看到了駭客技術和安全解決方案的升級。簡單來說,駭客現在有一個非常誘人的目標,用戶有明確的動機來保護自己。
在過去三年中,作為比特幣採用的直接結果,我們在資訊安全領域看到了巨大的創新,以硬體加密、金鑰儲存和硬體簽章裝置、多重簽章技術和數位託管的形式出現。在以下各節中,我們將檢查實用用戶安全的各種最佳實踐。
比特幣的物理儲存
因為大多數 用戶對物理安全比資訊安全更熟悉,保護比特幣的一個非常有效的方法是將它們轉換為物理形式。比特幣金鑰以及用於創建它們的種子不過是長數字。這意味著它們可以以物理形式儲存,例如列印在紙上或蝕刻在金屬板上。保護金鑰然後變得像物理保護金鑰種子的列印副本一樣簡單。列印在紙上的種子稱為「紙本備份」,許多錢包可以創建它們。將比特幣保持離線 稱為_冷儲存_,它是最有效的安全技術之一。冷儲存系統是指在離線系統(從未連接到網際網路的系統)上產生金鑰並將其儲存在紙上或數位媒體(例如 USB 記憶棒)上離線的系統。
硬體簽章裝置
從長遠來看,比特幣 安全可能越來越多地採取防竄改硬體簽章裝置的形式。與智慧型手機或桌上型電腦不同,比特幣硬體簽章裝置只需要保存金鑰並使用它們來產生簽章。沒有通用軟體可以破壞,並且具有有限的介面,硬體簽章裝置可以為非專家用戶提供強大的安全性。硬體簽章裝置可能成為儲存比特幣的主要方法。
確保您的存取
儘管大多數用戶 理所當然地擔心他們的比特幣被盜,但還有一個更大的風險。資料檔案一直在丟失。如果它們包含比特幣金鑰,損失就會更加痛苦。在努力保護他們的比特幣錢包時,用戶必須非常小心,不要走得太遠,最終失去他們的比特幣。2011 年 7 月,一個著名的比特幣意識和教育專案損失了近 7,000 個比特幣。為了防止盜竊,所有者實施了一系列複雜的加密備份。最終,他們不小心遺失了加密金鑰,使備份變得毫無價值,並損失了一筆財富。就像透過將錢埋在沙漠中來隱藏錢一樣,如果您過度保護您的比特幣,您可能無法再次找到它們。
| 要花費比特幣,您可能 需要備份的不僅僅是您的私鑰或用於衍生它們的 BIP32 種子。當使用多重簽章或複雜腳本時尤其如此。大多數輸出腳本承諾必須滿足的實際條件才能花費該輸出中的比特幣,除非您的錢包軟體可以向網路揭示這些條件,否則無法滿足該承諾。錢包恢復程式碼必須包含此資訊。有關更多詳細資訊,請參閱 錢包恢復。 | 
分散風險
您會 將您的全部淨資產以現金形式放在錢包中嗎?大多數人會認為這是魯莽的,但比特幣用戶經常使用單一錢包應用程式保管他們所有的比特幣。相反,用戶應該在多個不同的比特幣應用程式之間分散風險。謹慎的用戶將僅將一小部分(可能少於 5%)的比特幣保存在線上或行動錢包中作為「零用錢」。其餘部分應在幾種不同的儲存機制之間分配,例如桌面錢包和離線(冷儲存)。
多重簽章與治理
每當 公司或個人儲存大量比特幣時,他們應該考慮使用多重簽章比特幣地址。多重簽章地址透過要求多個簽章來進行付款來保護資金。簽章金鑰應儲存在許多不同的位置,並由不同的人控制。例如,在企業環境中,金鑰應由幾位公司高管獨立產生和持有,以確保沒有單一個人可以破壞資金。多重簽章地址也可以提供冗餘,其中單一個人持有儲存在不同位置的多個金鑰。
生存能力
一個經常被忽視的重要安全 考慮因素是可用性,特別是在金鑰持有者喪失能力或死亡的情況下。比特幣用戶被告知使用複雜的密碼並保持其金鑰的安全和私密,不與任何人分享。不幸的是,這種做法使得用戶的家人幾乎不可能在用戶無法解鎖時恢復任何資金。事實上,在大多數情況下,比特幣用戶的家人可能完全不知道比特幣資金的存在。
如果您有很多比特幣,您應該考慮與值得信賴的親戚或律師分享存取詳細資訊。可以透過多重簽章存取和透過專門擔任「數位資產執行人」的律師進行遺產規劃來設定更複雜的生存能力方案。
比特幣是一項複雜的新技術,開發人員仍在探索中。隨著時間的推移,我們將開發出更好的安全工具和實踐,非專家更容易使用。現在,比特幣用戶可以使用這裡討論的許多技巧來享受安全且無故障的比特幣 體驗。
第二層應用
現在讓我們透過將比特幣視為其他應用程式或_第二層_的平台,在對主要比特幣系統(第一層)的理解基礎上進一步發展。在本章中,我們將研究比特幣作為應用平台提供的功能。我們將考慮應用建構_原語_,它們構成任何區塊鏈應用的構建塊。我們將研究幾個使用這些原語的重要應用,例如客戶端驗證、支付通道和路由支付通道(閃電網路)。
構建塊(原語)
當 正確運作且長期運行時,比特幣系統提供某些保證,這些保證可以用作構建應用的構建塊。這些包括:
- 無雙重花費
- 
比特幣去中心化共識演算法最基本的保證確保在同一有效區塊鏈中,沒有 UTXO 可以被花費兩次。 
- 不可變性
- 
一旦交易記錄在區塊鏈中,並且隨後的區塊添加了足夠的工作量,交易的資料就變得實際上不可變。不可變性由能量支撐,因為重寫區塊鏈需要消耗能量來產生工作量證明。所需的能量以及因此的不可變性程度隨著承諾在包含交易的區塊之上的工作量而增加。 
- 中立性
- 
去中心化的比特幣網路傳播有效交易,無論這些交易的來源如何。這意味著任何人都可以創建具有足夠手續費的有效交易,並相信他們將能夠傳輸該交易,並隨時將其包含在區塊鏈中。 
- 安全的時間戳記
- 
共識規則拒絕時間戳記過於未來的任何區塊,並嘗試防止時間戳記過於過去的區塊。這確保區塊上的時間戳記在一定程度上可以信任。區塊上的時間戳記意味著所有包含的交易的輸入的未花費前參考。 
- 授權
- 
在去中心化網路中驗證的數位簽章提供授權保證。包含數位簽章要求的腳本無法在沒有腳本中隱含的私鑰持有者授權的情況下執行。 
- 可稽核性
- 
所有交易都是公開的,可以稽核。所有交易和區塊都可以連結回創世區塊的不間斷鏈。 
- 會計
- 
在任何交易中(coinbase 交易除外),輸入的價值等於輸出的價值加上手續費。不可能在交易中創建或銷毀比特幣價值。輸出不能超過輸入。 
- 不過期
- 
有效交易不會過期。如果它今天有效,只要輸入保持未花費且共識規則不變,它在不久的將來就會有效。 
- 完整性
- 
使用 SIGHASH_ALL 簽署的比特幣交易的輸出或由另一種 SIGHASH 類型簽署的交易的部分不能在不使簽章無效的情況下修改,從而使交易本身無效。 
- 交易原子性
- 
比特幣交易是原子性的。它們要麼有效並確認(挖掘),要麼無效。部分交易無法被挖掘,交易也沒有中間狀態。在任何時間點,交易要麼被挖掘,要麼未被挖掘。 
- 離散(不可分割)的價值單位
- 
交易輸出是離散且不可分割的價值單位。它們可以完整地被花費或未花費。它們不能被分割或部分花費。 
- 控制法定人數
- 
腳本中的多重簽章約束強加了授權的法定人數,在多重簽章方案中預定義。該要求由共識規則強制執行。 
- 時間鎖/老化
- 
包含相對或絕對時間鎖的任何腳本子句只能在其年齡超過指定時間後才能執行。 
- 複製
- 
區塊鏈的去中心化儲存確保當交易被挖掘時,在足夠的確認之後,它會在網路中複製,並變得持久且能夠抵抗斷電、資料丟失等。 
- 偽造保護
- 
交易只能花費現有的、已驗證的輸出。不可能創建或偽造價值。 
- 一致性
- 
在沒有礦工分區的情況下,記錄在區塊鏈中的區塊受到重組或分歧的影響呈指數級遞減的可能性,基於它們記錄的深度。一旦深度記錄,改變所需的計算和能量使改變實際上不可行。 
- 記錄外部狀態
- 
交易可以透過 OP_RETURN 或合約支付承諾資料值,代表外部狀態機中的狀態轉換。 
- 可預測的發行
- 
將以可預測的速率發行少於 2100 萬個比特幣。 
構建塊列表並不完整,隨著比特幣中引入每個新功能,會添加 更多。
從構建塊構建應用
比特幣 提供的構建塊是信任平台的元素,可用於組合應用。以下是當今存在的應用程式的一些示例以及它們使用的構建塊:
- 存在證明(數位公證)
- 
不可變性 + 時間戳記 + 持久性。 區塊鏈上的交易可以承諾一個值,證明一段資料在記錄時存在(時間戳記)。承諾不能事後修改(不可變性),證明將永久儲存(持久性)。 
- Kickstarter(Lighthouse)
- 
一致性 + 原子性 + 完整性。如果您簽署募款交易的一個輸入和輸出(完整性),其他人可以為募款做出貢獻,但在達到目標(輸出金額)(一致性)之前無法花費(原子性)。 
- 支付通道
- 
控制法定人數 + 時間鎖 + 無雙重花費 + 不過期 + 抗審查 + 授權。具有時間鎖(時間鎖)的多重簽章 2-of-2(法定人數)用作支付通道的「結算」交易,可以由任一方隨時持有(不過期)並花費(抗審查)(授權)。然後,雙方可以創建承諾交易,在較短的時間鎖 (時間鎖)上取代(無雙重花費)結算。 
彩色幣
我們將討論的第一個區塊鏈 應用是_彩色幣_。
彩色幣是指一組類似的技術,它們使用比特幣交易來記錄除比特幣之外的外部資產的創建、所有權和轉移。透過「外部」,我們指的是不直接儲存在比特幣區塊鏈上的資產,而不是比特幣本身,比特幣是區塊鏈固有的資產。
彩色幣用於追蹤數位資產以及由第三方持有並透過與彩色幣相關聯的所有權憑證進行交易的實物資產。數位資產彩色幣可以代表無形資產,例如股票憑證、許可證、虛擬財產(遊戲物品)或大多數任何形式的授權智慧財產權(商標、版權等)。有形資產彩色幣可以代表商品(黃金、白銀、石油)、土地所有權、汽車、船隻、飛機等的所有權憑證。
該術語源自「著色」或標記名義數量的比特幣的想法,例如單個聰,以代表比特幣數量本身以外的東西。作為類比,考慮在 1 美元紙幣上蓋章說「這是 ACME 的股票憑證」或「此紙幣可兌換 1 盎司白銀」,然後將 1 美元紙幣作為此其他資產的所有權憑證進行交易。彩色幣的第一個實作,名為_Enhanced Padded-Order-Based Coloring_或 EPOBC,將外部資產分配給 1 聰的輸出。這樣,它是一個真正的「彩色幣」,因為每個資產都被添加為單個聰的屬性(顏色)。
彩色幣的更新實作使用其他機制將元資料附加到交易,結合外部資料儲存,將元資料與特定資產相關聯。截至撰寫本文時使用的三種主要機制是單次使用密封、合約支付和客戶端驗證。
單次使用密封
單次使用密封 源自物理安全。透過第三方運送物品的人需要一種方法來檢測篡改,因此他們使用特殊機制保護其包裹,如果包裹被打開,該機制將明顯損壞。如果包裹在密封完好的情況下到達,發件人和收件人可以確信包裹在運輸過程中沒有被打開。
在彩色幣的背景下,單次使用密封是指只能與另一個資料結構關聯一次的資料結構。在比特幣中,這個 定義由未花費交易輸出(UTXO)實現。UTXO 在有效區塊鏈中只能被花費一次,花費它們的過程將它們與花費交易中的資料相關聯。
這為彩色幣的現代轉移提供了部分基礎。一個或多個彩色幣被接收到 UTXO。當該 UTXO 被花費時,花費交易必須描述如何花費彩色幣。這就引出了合約支付(P2C)。
合約支付(P2C)
我們 之前在 支付到合約 (P2C) 中了解了 P2C,它成為比特幣共識規則 taproot 升級的基礎之一。作為簡短提醒,P2C 允許花費者(Bob)和接收者(Alice)就某些資料(例如合約)達成一致,然後 調整 Alice 的公鑰,使其承諾該合約。Bob 可以隨時揭示 Alice 的底層金鑰和用於承諾合約的調整,證明她收到了資金。如果 Alice 花費資金,這完全證明她知道合約,因為她能夠花費接收到 P2C 調整金鑰的資金的唯一方法是知道調整(合約)。
P2C 調整金鑰的一個強大屬性是,除了 Alice 和 Bob 之外,對於其他人來說,它們看起來像任何其他公鑰,除非他們選擇揭示用於調整金鑰的合約。沒有公開揭示關於合約的任何內容——甚至沒有揭示他們之間存在合約。
P2C 合約可以任意長且詳細,條款可以用任何語言編寫,並且可以引用參與者想要的任何內容,因為合約不由完整節點驗證,只有具有承諾的公鑰發佈到區塊鏈。
在彩色幣的背景下,Bob 可以透過花費相關的 UTXO 來打開包含其彩色幣的單次使用密封。在花費該 UTXO 的交易中,他可以承諾一個合約,指出彩色幣的下一個所有者(或所有者)必須滿足的條件才能進一步花費這些幣。新所有者不需要是 Alice,即使 Alice 是接收 Bob 花費的 UTXO 的人,並且 Alice 已透過合約條款調整了她的公鑰。
因為完整節點不(也不能)驗證合約是否正確遵循,我們需要弄清楚誰負責驗證。這就引出 客戶端驗證。
客戶端驗證
Bob 有 一些與 UTXO 相關聯的彩色幣。他花費了該 UTXO,以承諾合約的方式,該合約指出彩色幣的下一個接收者(或接收者)將如何證明他們對幣的所有權以進一步花費它們。
實際上,Bob 的 P2C 合約可能只是簡單地承諾一個或多個 UTXO 的唯一識別符,這些 UTXO 將用作單次使用密封,用於決定何時花費彩色幣。例如,Bob 的合約可能指出 Alice 接收到她的 P2C 調整公鑰的 UTXO 現在控制他的一半彩色幣,他的另一半彩色幣現在被分配給另一個可能與 Alice 和 Bob 之間的交易無關的 UTXO。這為針對區塊鏈監視提供了顯著的隱私。
當 Alice 稍後想將她的彩色幣花費給 Dan 時,她首先需要向 Dan 證明她控制彩色幣。Alice 可以透過向 Dan 揭示她的底層 P2C 公鑰和 Bob 選擇的 P2C 合約條款來做到這一點。Alice 還向 Dan 揭示 Bob 用作單次使用密封的 UTXO 以及 Bob 給她的關於彩色幣先前所有者的任何資訊。簡而言之,Alice 給 Dan 一套關於彩色幣每次先前轉移的完整歷史,每一步都錨定在比特幣區塊鏈中(但不在鏈中儲存任何特殊資料——只是常規公鑰)。該歷史非常像我們稱為區塊鏈的常規比特幣交易歷史,但彩色歷史對區塊鏈的其他用戶完全不可見。
Dan 使用他的軟體驗證此歷史,稱為_客戶端驗證_。值得注意的是,Dan 只需要接收和驗證與他想要接收的彩色幣相關的歷史部分。他不需要關於其他人的彩色幣發生了什麼的資訊——例如,他永遠不需要知道 Bob 的另一半幣(Bob 沒有轉移給 Alice 的那些)發生了什麼。這有助於增強彩色幣協議的隱私性。
現在我們已經了解了單次使用密封、合約支付和客戶端驗證,我們可以查看截至撰寫本文時使用它們的兩個主要協議,RGB 和 Taproot Assets。
RGB
RGB 協議的開發人員開創了現代基於比特幣的彩色幣協議中使用的許多想法。RGB 設計的主要要求是使協議與鏈下支付通道(參見 支付通道和狀態通道)相容,例如閃電網路(LN)中使用的那些。這在 RGB 協議的每一層都得以實現:
- 單次使用密封
- 
為了創建支付通道,Bob 將他的彩色幣分配給需要他和 Alice 兩人簽署才能花費的 UTXO。他們對該 UTXO 的相互控制充當未來轉移的單次使用密封。 
- 合約支付(P2C)
- 
Alice 和 Bob 現在可以簽署 P2C 合約的多個版本。底層支付通道的執行機制確保雙方都有動機只在鏈上發佈合約的最新版本。 
- 客戶端驗證
- 
為了確保 Alice 和 Bob 都不需要相互信任,他們各自檢查彩色幣回到其創建的所有先前轉移,以確保所有合約規則都得到正確遵循。 
RGB 的開發人員描述了其協議的其他用途,例如創建可以定期更新以防止私鑰洩露的身份憑證。
有關更多資訊,請參閱 RGB 的文件。
Taproot Assets
以前 稱為 Taro,Taproot Assets 是一種彩色幣協議,受 RGB 的重大影響。與 RGB 相比,Taproot Assets 使用一種 P2C 合約形式,與 taproot 用於啟用 MAST 功能的版本非常相似(參見 默克爾化替代腳本樹 (MAST))。Taproot Assets 相對於 RGB 的聲稱優勢是,它與廣泛使用的 taproot 協議的相似性使錢包和其他軟體更容易實現。一個缺點是它可能不如 RGB 協議靈活,尤其是在實現非資產功能(例如身份憑證)時。
| Taproot 是比特幣協議的一部分。Taproot Assets 不是,儘管名稱相似。RGB 和 Taproot Assets 都是建立在比特幣協議之上的協議。比特幣原生支援的唯一資產是比特幣。 | 
比 RGB 更甚,Taproot Assets 被設計為與 LN 相容。在 LN 上轉發非比特幣資產的一個挑戰是有兩種方法可以完成發送,每種方法都有不同的權衡集:
- 原生轉發
- 
花費者和接收者之間路徑中的每個 跳躍都必須了解特定資產(彩色幣類型)並具有足夠的餘額來支援轉發支付。 
- 翻譯轉發
- 
花費者旁邊的跳躍和 接收者旁邊的跳躍必須了解特定資產並具有足夠的餘額來支援轉發支付,但其他每個跳躍只需要支援轉發比特幣 支付。 
原生轉發在概念上更簡單,但實質上需要為每個資產建立一個單獨的類閃電網路。翻譯轉發允許建立在比特幣 LN 的規模經濟之上,但它可能容易受到一個稱為 _免費美式看漲期權_的問題的影響,接收者可能會根據匯率的最近變化選擇性地接受或拒絕某些支付,以從他們旁邊的跳躍中抽取資金。儘管沒有已知的免費美式看漲期權的完美解決方案,但可能存在限制其危害的實用解決方案。
Taproot Assets 和 RGB 在技術上都可以支援原生和翻譯轉發。Taproot Assets 專門圍繞翻譯轉發設計,而 RGB 已經看到實施兩者的提案。
有關更多資訊,請參閱 Taproot Asset 的文件。此外,Taproot Asset 開發人員正在製定 BIP,這些 BIP 可能在本書 付印後可用。
支付通道和狀態通道
_支付通道_是 在比特幣區塊鏈之外在兩方之間交換比特幣交易的無信任機制。這些交易如果在比特幣區塊鏈上結算將是有效的,但它們被保留在鏈下,等待最終批次結算。因為交易沒有結算,所以它們可以在沒有通常結算延遲的情況下交換,允許極高的交易吞吐量、低延遲和細粒度。
實際上,術語_通道_是一個隱喻。狀態通道是由區塊鏈之外兩方之間的狀態交換表示的虛擬構造。本身沒有「通道」,底層資料傳輸機制也不是通道。我們使用術語_通道_來表示區塊鏈之外兩方之間的關係和共享狀態。
為了進一步解釋這個概念,想想 TCP 流。從更高層協議的角度來看,它是連接跨網際網路的兩個應用的「套接字」。但是,如果您查看網路流量,TCP 流只是通過 IP 封包的虛擬通道。TCP 流的每個端點對 IP 封包進行排序和組裝,以創建位元組流的錯覺。在底層,它都是斷開連接的封包。同樣,支付通道只是一系列交易。如果正確排序和連接,它們會創建可贖回的義務,即使您不信任通道的另一方,您也可以信任。
在本節中,我們將研究各種形式的支付通道。首先,我們將檢查用於構建計量微支付服務的單向(單向)支付通道的機制,例如流媒體影片。然後,我們將擴展這種機制並引入雙向支付通道。最後,我們將看看雙向通道如何端到端連接以在路由網路中形成多跳通道,首先在_閃電網路_的名稱下提出。
支付通道是_狀態通道_這一更廣泛概念的一部分,狀態通道代表狀態的鏈下更改,透過區塊鏈中的最終結算來保護。支付通道是一種狀態通道,其中正在更改的狀態是虛擬貨幣的餘額。
狀態通道——基本概念和術語
狀態通道 是透過將共享狀態鎖定在區塊鏈上的交易在兩方之間建立的。這稱為 資金交易。這個單一交易必須傳輸到網路並挖掘以建立通道。在支付通道的示例中,鎖定的狀態是通道的初始餘額(以貨幣計)。
然後,雙方交換簽署的交易,稱為_承諾交易_,這些交易更改初始狀態。這些交易是有效交易,因為它們_可以_由任一方提交以進行結算,但相反,它們由每一方保留在鏈下,等待通道關閉。狀態更新可以像每一方創建、簽署交易並將其傳輸給另一方一樣快地創建。實際上,這意味著每秒可以交換數十筆交易。
在交換承諾交易時,雙方也阻止使用先前的狀態,以便最新的承諾交易始終是最好的可贖回的。這阻止任一方透過單方面關閉通道並使用對他們更有利於當前狀態的先前狀態來作弊。我們將在本章的其餘部分檢查可用於阻止發佈先前狀態的各種機制。
最後,通道可以透過提交最終的 _結算交易_到區塊鏈來協作關閉,或者由任一方提交最後的承諾交易到區塊鏈來單方面關閉。如果其中一方意外斷開連接,則需要單方面關閉選項。結算交易代表通道的最終狀態,並在區塊鏈上結算。
在通道的整個生命週期中,只有兩筆交易需要提交到區塊鏈上進行挖掘:資金交易和結算交易。在這兩個狀態之間,雙方可以交換任意數量的承諾交易,這些交易永遠不會被其他人看到或提交到區塊鏈。
Bob 和 Alice 之間的支付通道,顯示資金、承諾和結算交易。 說明了 Bob 和 Alice 之間的支付通道,顯示了資金、承諾和結算 交易。
 
簡單的支付通道示例
為了解釋 狀態通道,我們從一個非常簡單的示例開始。我們演示單向通道,這意味著價值只在一個方向流動。我們還將從天真的假設開始,即沒有人試圖作弊以保持事情簡單。一旦我們解釋了基本的通道想法,我們將查看使其無信任所需的條件,以便任何一方_都無法_作弊,即使他們試圖作弊。
對於這個示例,我們假設兩個參與者:Emma 和 Fabian。Fabian 提供使用微支付通道按秒計費的影片串流服務。Fabian 每秒影片收費 0.01 millibit(0.00001 BTC),相當於每小時影片 36 millibits(0.036 BTC)。Emma 是從 Fabian 購買此串流影片服務的用戶。Emma 透過支付通道從 Fabian 購買串流影片,按每秒影片付費。 顯示了 Emma 使用支付通道從 Fabian 購買影片串流服務。
 
在這個示例中,Fabian 和 Emma 使用處理支付通道和影片串流的特殊軟體。Emma 在瀏覽器中運行軟體;Fabian 在伺服器上運行它。該軟體包括基本的比特幣錢包功能,可以創建和簽署比特幣交易。「支付通道」的概念和術語對用戶完全隱藏。他們看到的是按秒付費的影片。
要設定支付通道,Emma 和 Fabian 建立一個 2-of-2 多重簽章地址,每個人持有一個金鑰。從 Emma 的角度來看,她瀏覽器中的軟體顯示一個帶有地址的 QR 碼,並要求她提交「存款」,最多可觀看 1 小時的影片。然後,Emma 為該地址提供資金。Emma 的交易,支付給多重簽章地址,是支付通道的資金或錨定交易。
對於這個示例,假設 Emma 用 36 millibits(0.036 BTC)為通道提供資金。這將允許 Emma 消費_最多_ 1 小時的串流影片。在這種情況下,資金交易設定了可以在此通道中傳輸的最大金額,設定_通道容量_。
資金交易消耗來自 Emma 錢包的一個或多個輸入,提供資金來源。它創建一個輸出,金額為 36 millibits,支付給 Emma 和 Fabian 之間共同控制的多重簽章 2-of-2 地址。它可能有額外的輸出用於找零回 Emma 的錢包。
在資金交易確認到足夠的深度後,Emma 可以開始串流影片。Emma 的軟體創建並簽署一個承諾交易,該交易更改通道餘額,將 0.01 millibit 記入 Fabian 的地址,並退還 35.99 millibits 給 Emma。Emma 簽署的交易消耗資金交易創建的 36 millibits 輸出,並創建兩個輸出:一個用於她的退款,另一個用於 Fabian 的支付。該交易只是部分簽署的——它需要兩個簽章(2-of-2),但只有 Emma 的簽章。當 Fabian 的伺服器接收到此交易時,它添加第二個簽章(用於 2-of-2 輸入)並將其與 1 秒的影片一起返回給 Emma。現在雙方都有一個完全簽署的承諾交易,任一方都可以贖回,代表通道的正確最新餘額。任何一方都不會將此交易廣播到網路。
在下一輪中,Emma 的軟體創建並簽署另一個承諾交易(承諾 #2),該交易消耗資金交易的_相同_ 2-of-2 輸出。第二個承諾交易將一個輸出 0.02 millibits 分配給 Fabian 的地址,將一個輸出 35.98 millibits 返回給 Emma 的地址。這個新交易是累計兩秒影片的付款。Fabian 的軟體簽署並返回第二個承諾交易,以及另一秒的影片。
以這種方式,Emma 的軟體繼續向 Fabian 的伺服器發送承諾交易以交換串流影片。通道的餘額逐漸累積,有利於 Fabian,因為 Emma 消費了更多秒的影片。假設 Emma 觀看了 600 秒(10 分鐘)的影片,創建並簽署了 600 個承諾交易。最後一個承諾交易(#600)將有兩個輸出,分配通道的餘額,6 millibits 給 Fabian,30 millibits 給 Emma。
最後,Emma 點擊「停止」以停止串流影片。現在 Fabian 或 Emma 都可以傳輸最終狀態交易以進行結算。最後一個交易是_結算交易_,為 Emma 消費的所有影片支付 Fabian 費用,退還資金交易的餘額給 Emma。
Emma 與 Fabian 的支付通道,顯示更新通道餘額的承諾交易。 顯示了 Emma 和 Fabian 之間的通道以及更新通道餘額的承諾交易。
最終,只有兩筆交易記錄在區塊鏈上:建立通道的資金交易和在兩個參與者之間正確分配最終餘額的 結算交易。
 
建立無信任通道
我們剛才描述的通道 有效,但前提是雙方合作,沒有任何失敗或作弊嘗試。讓我們看看一些破壞此通道的場景,並看看需要什麼來修復這些場景:
- 
一旦資金交易發生,Emma 需要 Fabian 的簽章才能拿回任何錢。如果 Fabian 消失,Emma 的資金被鎖定在 2-of-2 中並實際上丟失了。這個構造的通道,如果其中一方在至少有一個由雙方簽署的承諾交易之前變得不可用,會導致資金損失。 
- 
當通道運行時,Emma 可以接受 Fabian 副署的任何承諾交易並將其傳輸到區塊鏈。為什麼要支付 600 秒的影片費用,如果她可以傳輸承諾交易 #1 並只支付 1 秒的影片費用?通道失敗,因為 Emma 可以透過廣播對她有利的先前承諾來作弊。 
這兩個問題都可以用時間鎖來解決——讓我們看看我們如何使用交易級時間鎖。
Emma 不能冒險為 2-of-2 多重簽章提供資金,除非她有保證的退款。為了解決這個問題,Emma 同時構建資金交易和退款交易。她簽署資金交易但不將其傳輸給任何人。Emma 只將退款交易傳輸給 Fabian 並獲得他的簽章。
退款交易充當第一個承諾交易,其時間鎖建立通道生命週期的上限。在這種情況下,Emma 可以將鎖定時間設定為未來 30 天或 4,320 個區塊。所有後續的承諾交易都必須有較短的時間鎖,以便它們可以在退款交易之前贖回。
現在 Emma 有一個完全簽署的退款交易,她可以自信地傳輸簽署的資金交易,知道她最終可以在時間鎖到期後贖回退款交易,即使 Fabian 消失。
雙方在通道生命週期中交換的每個承諾交易都將被時間鎖定到未來。但是對於每個承諾,延遲將稍短,因此最近的承諾可以在它無效化的先前承諾之前贖回。由於鎖定時間,任何一方都無法成功傳播任何承諾交易,直到其時間鎖到期。如果一切順利,他們將合作並使用結算交易優雅地關閉通道,使得不需要傳輸中間承諾交易。如果不能,最近的承諾交易可以被傳播以結算帳戶並無效化所有先前的承諾交易。
例如,如果承諾交易 #1 被時間鎖定到未來 4,320 個區塊,那麼承諾交易 #2 被時間鎖定到未來 4,319 個區塊。承諾交易 #600 可以在承諾交易 #1 變得有效之前 600 個區塊就被花費。
每個承諾設定較短的時間鎖,允許它在先前的承諾變得有效之前被花費。 顯示每個承諾交易設定較短的時間鎖,允許它在先前的承諾變得有效之前被花費。
 
每個後續的承諾交易都必須有較短的時間鎖,以便它可以在其前任和退款交易之前廣播。提前廣播承諾的能力確保它將能夠花費資金輸出,並阻止任何其他承諾交易透過花費輸出來贖回。比特幣區塊鏈提供的保證,防止雙重支付和強制執行時間鎖,有效地允許每個承諾交易無效化其前任。
狀態通道使用時間鎖來在時間維度上強制執行智慧合約。在這個例子中,我們看到時間維度如何保證最近的承諾交易在任何更早的承諾之前變得有效。因此,最近的承諾交易可以被傳輸,花費輸入並無效化先前的承諾交易。使用絕對時間鎖強制執行智慧合約防止一方作弊。這種實作只需要絕對交易級鎖定時間。接下來,我們將看到腳本級時間鎖,CHECKLOCKTIMEVERIFY 和 CHECKSEQUENCEVERIFY,如何可以用來構建更靈活、有用和精緻的狀態通道。
時間鎖並不是無效化先前承諾交易的唯一方法。在接下來的章節中,我們將看到撤銷金鑰如何可以用來達到相同的結果。時間鎖是有效的,但它們有兩個明顯的缺點。透過在通道首次開啟時建立最大時間鎖,它們限制了通道的壽命。更糟的是,它們迫使通道實作在允許長壽命通道和強制其中一方在過早關閉的情況下等待很長時間才能退款之間取得平衡。例如,如果您透過將退款時間鎖設定為 30 天來允許通道保持開啟 30 天,如果其中一方立即消失,另一方必須等待 30 天才能獲得退款。端點越遠,退款就越遠。
第二個問題是,由於每個後續的承諾交易都必須遞減時間鎖,因此雙方之間可以交換的承諾交易數量有明確的限制。例如,一個 30 天的通道,將時間鎖設定為未來 4,320 個區塊,只能容納 4,320 個中間承諾交易,然後必須關閉。將時間鎖承諾交易間隔設定為 1 個區塊是危險的。透過將承諾交易之間的時間鎖間隔設定為 1 個區塊,開發者正在為通道參與者創造非常沉重的負擔,他們必須保持警惕,保持線上和觀看,並隨時準備傳輸正確的承諾交易。
在前面的單向通道例子中,很容易消除每個承諾的時間鎖。在 Emma 從 Fabian 那裡收到有時間鎖的退款交易的簽章後,承諾交易上不設時間鎖。相反,Emma 將她對每個承諾交易的簽章發送給 Fabian,但 Fabian 不會將他的任何承諾交易簽章發送給她。這意味著只有 Fabian 擁有承諾交易的兩個簽章,所以只有他可以廣播其中一個承諾。當 Emma 完成串流影片時,Fabian 總是更願意廣播支付他最多的交易——這將是最新狀態。這種構造稱為 Spillman 風格的支付通道,最早在 2013 年被描述和實作,儘管它們只有在見證(segwit)交易中才安全使用,而見證交易直到 2017 年才可用。
既然我們了解如何使用時間鎖來無效化先前的承諾,我們就可以看到合作關閉通道和透過廣播承諾交易單方面關閉通道之間的區別。在我們之前的例子中,所有承諾交易都被時間鎖定,因此廣播承諾交易總是涉及等待直到時間鎖到期。但是如果雙方同意最終餘額是什麼,並且知道他們都持有最終會實現該餘額的承諾交易,他們可以構建一個沒有時間鎖的結算交易,代表相同的餘額。在合作關閉中,任何一方都採用最近的承諾交易並構建一個結算交易,除了省略時間鎖外,在各個方面都是相同的。雙方可以簽署這個結算交易,知道沒有辦法作弊並獲得更有利的餘額。透過合作簽署和傳輸結算交易,他們可以關閉通道並立即贖回其餘額。最壞的情況是,其中一方可能會很小氣,拒絕合作,並迫使另一方使用最近的承諾交易進行單方面關閉。如果他們這樣做,他們也必須等待他們的 資金。
非對稱可撤銷承諾
另一種 處理先前承諾狀態的方法是明確撤銷它們。然而,這並不容易實現。比特幣的一個關鍵特性是,一旦交易有效,它就保持有效並且不會過期。取消交易的唯一方法是讓衝突的交易得到確認。這就是為什麼我們在簡單支付通道例子中使用時間鎖來確保更近期的承諾可以在較舊的承諾有效之前被花費。然而,按時間排序承諾會產生許多限制,使支付通道難以使用。
即使交易無法被取消,它也可以以使其不希望使用的方式構建。我們這樣做的方法是給每一方一個_撤銷金鑰_,如果他們試圖作弊,可以用來懲罰另一方。這種撤銷先前承諾交易的機制首先作為 LN 的一部分被提出。
為了解釋撤銷金鑰,我們將在 Hitesh 和 Irene 經營的兩個交易所之間構建一個更複雜的支付通道。Hitesh 和 Irene 分別在印度和美國經營比特幣交易所。Hitesh 的印度交易所的客戶經常向 Irene 的美國交易所的客戶發送付款,反之亦然。目前,這些交易發生在比特幣區塊鏈上,但這意味著支付費用並等待幾個區塊進行確認。在交易所之間建立支付通道將顯著降低成本並加速交易流程。
Hitesh 和 Irene 透過協作構建資金交易來開始通道,每個人用 5 個比特幣資助通道。在他們簽署資金交易之前,他們必須簽署第一組承諾(稱為_退款_),為 Hitesh 分配 5 個比特幣的初始餘額,為 Irene 分配 5 個比特幣。資金交易將通道狀態鎖定在 2-of-2 多重簽章中,就像簡單通道的例子一樣。
資金交易可能有一個或多個來自 Hitesh 的輸入(加起來 5 個比特幣或更多),以及一個或多個來自 Irene 的輸入(加起來 5 個比特幣或更多)。輸入必須稍微超過通道容量才能支付交易費用。該交易有一個輸出,將總共 10 個比特幣鎖定到由 Hitesh 和 Irene 控制的 2-of-2 多重簽章地址。如果他們的輸入超過了他們預期的通道貢獻,資金交易也可能有一個或多個輸出將找零返還給 Hitesh 和 Irene。這是一個由兩方提供和簽署輸入的單個交易。它必須協作構建並在傳輸之前由每一方簽署。
現在,Hitesh 和 Irene 不是創建雙方都簽署的單個承諾交易,而是創建兩個不同的_非對稱_承諾交易。
Hitesh 有一個帶有兩個輸出的承諾交易。第一個輸出_立即_支付 Irene 她應得的 5 個比特幣。第二個輸出支付 Hitesh 他應得的 5 個比特幣,但只有在 1,000 個區塊的時間鎖之後。交易輸出看起來像這樣:
輸入:2-of-2 資金輸出,由 Irene 簽署
輸出 0 <5 個比特幣>:
    <Irene 的公鑰> CHECKSIG
輸出 1 <5 個比特幣>:
    <1000 個區塊>
    CHECKSEQUENCEVERIFY
    DROP
    <Hitesh 的公鑰> CHECKSIG
Irene 有一個不同的帶有兩個輸出的承諾交易。第一個輸出立即支付 Hitesh 他應得的 5 個比特幣。第二個輸出支付 Irene 她應得的 5 個比特幣,但只有在 1,000 個區塊的時間鎖之後。Irene 持有的承諾交易(由 Hitesh 簽署)看起來像這樣:
輸入:2-of-2 資金輸出,由 Hitesh 簽署
輸出 0 <5 個比特幣>:
    <Hitesh 的公鑰> CHECKSIG
輸出 1 <5 個比特幣>:
    <1000 個區塊>
    CHECKSEQUENCEVERIFY
    DROP
    <Irene 的公鑰> CHECKSIG
這樣,每一方都有一個承諾交易,花費 2-of-2 資金輸出。這個輸入由_另一方_簽署。任何時候,持有交易的一方也可以簽署(完成 2-of-2)並廣播。然而,如果他們廣播承諾交易,它會立即支付另一方,而他們必須等待時間鎖到期。透過對其中一個輸出的贖回施加延遲,當他們選擇單方面廣播承諾交易時,我們讓每一方處於輕微的劣勢。但僅靠時間延遲不足以鼓勵公平行為。
兩個非對稱承諾交易,持有交易的一方的付款被延遲。 顯示兩個非對稱承諾交易,其中支付承諾持有者的輸出被延遲。
 
現在我們介紹該方案的最後元素:撤銷金鑰,防止作弊者廣播過期的承諾。撤銷金鑰允許受害方透過取走通道的全部餘額來懲罰作弊者。
撤銷金鑰由兩個秘密組成,每一半由每個通道參與者獨立生成。它類似於 2-of-2 多重簽章,但使用橢圓曲線算術構建,因此雙方都知道撤銷公鑰,但每一方只知道撤銷私鑰的一半。
在每一輪中,雙方都向另一方透露其撤銷秘密的一半,從而給另一方(現在擁有兩半)提供了在這個被撤銷的交易被廣播時索取懲罰輸出的手段。
每個承諾交易都有一個「延遲」輸出。該輸出的贖回腳本允許一方在 1,000 個區塊後贖回它,_或者_如果另一方有撤銷金鑰,則可以贖回它,懲罰傳輸被撤銷的承諾。
因此,當 Hitesh 為 Irene 創建一個承諾交易以供簽署時,他使第二個輸出在 1,000 個區塊後支付給自己,或者支付給撤銷公鑰(他只知道一半的秘密)。Hitesh 構建這個交易。只有當他準備好轉移到新的通道狀態並想撤銷這個承諾時,他才會向 Irene 透露他的撤銷秘密的一半。
第二個輸出的腳本看起來像這樣:
輸出 0 <5 個比特幣>:
    <Irene 的公鑰> CHECKSIG
輸出 1 <5 個比特幣>:
IF
    # 撤銷懲罰輸出
    <撤銷公鑰>
ELSE
    <1000 個區塊>
    CHECKSEQUENCEVERIFY
    DROP
    <Hitesh 的公鑰>
ENDIF
CHECKSIG
Irene 可以自信地簽署這筆交易,因為如果傳輸,它將立即支付她應得的。Hitesh 持有交易,但知道如果他在單方面通道關閉中傳輸它,他必須等待 1,000 個區塊才能得到支付。
在通道進入下一個狀態後,Hitesh 必須_撤銷_這個承諾交易,然後 Irene 才會同意簽署任何進一步的承諾交易。為此,他所要做的就是將他的_撤銷金鑰_的一半發送給 Irene。一旦 Irene 擁有這個承諾的撤銷私鑰的兩半,她就可以自信地簽署未來的承諾。她知道如果 Hitesh 試圖透過發布先前的承諾來作弊,她可以使用撤銷金鑰來贖回 Hitesh 的延遲輸出。如果 Hitesh 作弊,Irene 得到兩個輸出。同時,Hitesh 只有該撤銷公鑰的撤銷秘密的一半,無法在 1,000 個區塊之前贖回輸出。Irene 將能夠在 1,000 個區塊過去之前贖回輸出並懲罰 Hitesh。
撤銷協定是雙邊的,這意味著在每一輪中,隨著通道狀態的推進,雙方交換新的承諾,交換先前承諾的撤銷秘密,並簽署彼此的新承諾交易。在他們接受新狀態後,他們透過向彼此提供必要的撤銷秘密來懲罰任何作弊行為,使先前的狀態無法使用。
讓我們看一個它如何運作的例子。Irene 的一位客戶想向 Hitesh 的一位客戶發送 2 個比特幣。為了透過通道傳輸 2 個比特幣,Hitesh 和 Irene 必須推進通道狀態以反映新的餘額。他們將承諾新狀態(狀態編號 2),其中通道的 10 個比特幣被分割,Hitesh 7 個比特幣,Irene 3 個比特幣。為了推進通道狀態,他們將各自創建反映新通道餘額的新承諾交易。
與以前一樣,這些承諾交易是非對稱的,因此每一方持有的承諾交易如果他們贖回它,就會迫使他們等待。至關重要的是,在簽署新的承諾交易之前,他們必須首先交換撤銷金鑰以無效化任何過時的承諾。在這個特定情況下,Hitesh 的利益與通道的真實狀態一致,因此他沒有理由廣播先前的狀態。然而,對於 Irene 來說,狀態編號 1 給她留下了比狀態 2 更高的餘額。當 Irene 向 Hitesh 提供她先前承諾交易(狀態編號 1)的撤銷金鑰時,她實際上正在撤銷她從將通道倒退到先前狀態中獲利的能力,因為有了撤銷金鑰,Hitesh 可以毫無延遲地贖回先前承諾交易的兩個輸出。這意味著如果 Irene 廣播先前的狀態,Hitesh 可以行使他的權利取走所有輸出。
重要的是,撤銷不會自動發生。雖然 Hitesh 有能力懲罰 Irene 作弊,但他必須勤奮地觀察區塊鏈以尋找作弊的跡象。如果他看到先前的承諾交易被廣播,他有 1,000 個區塊的時間採取行動並使用撤銷金鑰來挫敗 Irene 的作弊並透過取走全部餘額(所有 10 個比特幣)來懲罰她。
具有相對時間鎖(CSV)的非對稱可撤銷承諾是實作支付通道的一種更好的方法,也是該技術的一個非常重大的創新。使用這種構造,通道可以無限期地保持開啟,並且可以有數十億個中間承諾交易。在 LN 的實作中,承諾狀態由 48 位元索引標識,允許在任何單個通道中進行超過 281 兆(2.8 × 1014)次狀態 轉換。
雜湊時間鎖定合約(HTLC)
支付通道 可以透過一種特殊類型的智慧合約進一步擴展,該合約允許參與者將資金承諾給可贖回的秘密,並有到期時間。這個功能稱為_雜湊時間鎖定合約_,或 HTLC,並用於雙向和路由支付通道。
讓我們首先解釋 HTLC 的「雜湊」部分。為了創建 HTLC,付款的預期接收者將首先創建一個秘密 R。然後他們計算這個秘密的雜湊 H:
這產生一個雜湊 H,可以包含在輸出的腳本中。知道秘密的人可以使用它來贖回輸出。秘密 R 也被稱為雜湊函數的_原像_。原像只是用作雜湊函數輸入的資料。
HTLC 的第二部分是「時間鎖」組件。如果秘密沒有被揭示,HTLC 的支付者可以在一段時間後獲得「退款」。這是使用 CHECKLOCKTIMEVERIFY 的絕對時間鎖實現的。
實作 HTLC 的腳本可能看起來像這樣:
IF
    # 如果您有秘密 R 的付款
    HASH160 <H> EQUALVERIFY
    <接收者公鑰> CHECKSIG
ELSE
    # 超時後退款
    <鎖定時間> CHECKLOCKTIMEVERIFY DROP
    <支付者公鑰> CHECKSIG
ENDIF
任何知道秘密 R(當雜湊時等於 H)的人都可以透過執行 IF 流程的第一個子句來贖回這個輸出。
如果秘密沒有被揭示並且 HTLC 在一定數量的區塊後被索取,支付者可以使用 IF 流程中的第二個子句索取退款。
這是 HTLC 的基本實作。這種類型的 HTLC 可以被_任何_擁有秘密 R 的人贖回。HTLC 可以有許多不同的形式,腳本略有變化。例如,在第一個子句中添加 CHECKSIG 運算子和公鑰將雜湊的贖回限制為特定接收者,該接收者還必須知道 秘密 R。
路由支付通道(閃電網路)
閃電網路(LN)是一個提議的端到端連接的雙向支付通道的路由網路。像這樣的網路可以允許任何參與者將付款從通道路由到通道,而無需信任任何中介。LN 最初由 Joseph Poon 和 Thadeus Dryja 於 2015 年 2 月 描述,建立在由許多其他人提出和詳細闡述的支付通道概念之上。
「閃電網路」是指路由支付通道網路的特定設計,現在已由至少五個不同的開源團隊實作。這些獨立實作透過 _閃電技術基礎(BOLT)_儲存庫中描述的一組互通性標準進行協調。
基本閃電網路範例
讓我們 看看這是如何運作的。
在這個例子中,我們有五個參與者:Alice、Bob、Carol、Diana 和 Eric。這五個參與者彼此之間成對開啟了支付通道。Alice 與 Bob 有一個支付通道。Bob 連接到 Carol,Carol 連接到 Diana,Diana 連接到 Eric。為了簡單起見,讓我們假設每個通道由每個參與者用 2 個比特幣資助,每個通道的總容量為 4 個比特幣。
一系列雙向支付通道連結形成 LN,可以將付款從 Alice 路由到 Eric。 顯示 LN 中的五個參與者,透過雙向支付通道連接,可以連結起來使從 Alice 到 Eric 的付款(見 路由支付通道(閃電網路))。
 
Alice 想支付 Eric 1 個比特幣。然而,Alice 沒有透過支付通道連接到 Eric。創建支付通道需要資金交易,必須承諾到比特幣區塊鏈。Alice 不想開啟新的支付通道並承諾更多她的資金。有沒有辦法間接支付 Eric?
透過 LN 的逐步支付路由。 顯示透過連接參與者的支付通道上的一系列 HTLC 承諾,將付款從 Alice 路由到 Eric 的逐步過程。
 
Alice 正在執行一個 LN 節點,該節點正在追蹤她與 Bob 的支付通道,並有能力發現支付通道之間的路由。Alice 的 LN 節點還有能力透過網際網路連接到 Eric 的 LN 節點。Eric 的 LN 節點使用隨機數生成器創建一個秘密 R。Eric 的節點不向任何人透露這個秘密。相反,Eric 的節點計算秘密 R 的雜湊 H,並以發票的形式將這個雜湊傳輸到 Alice 的節點(見 透過 LN 的逐步支付路由。,步驟 1)。
現在 Alice 的 LN 節點在 Alice 的 LN 節點和 Eric 的 LN 節點之間構建路由。使用的路徑查找演算法將在稍後更詳細地檢查,但現在讓我們假設 Alice 的節點可以找到有效的路由。
然後 Alice 的節點構建一個 HTLC,支付給雜湊 H,有 10 個區塊的退款超時(當前區塊 + 10),金額為 1.003 個比特幣(見 透過 LN 的逐步支付路由。,步驟 2)。額外的 0.003 將用於補償中間節點參與這個支付路由。Alice 向 Bob 提供這個 HTLC,從她與 Bob 的通道餘額中扣除 1.003 個比特幣並將其承諾給 HTLC。HTLC 具有以下含義:「如果 Bob 知道秘密,Alice 承諾將她的通道餘額的 1.003 個比特幣支付給 Bob,或者如果 10 個區塊過去,則退還給 Alice 的餘額。」 Alice 和 Bob 之間的通道餘額現在由具有三個輸出的承諾交易表示:Bob 的 2 個比特幣餘額,Alice 的 0.997 個比特幣餘額,Alice 的 HTLC 中承諾的 1.003 個比特幣。Alice 的餘額減少了承諾給 HTLC 的金額。
Bob 現在有一個承諾,如果他能夠在接下來的 10 個區塊內獲得秘密 R,他可以索取 Alice 鎖定的 1.003 個比特幣。有了這個承諾,Bob 的節點在他與 Carol 的支付通道上構建一個 HTLC。Bob 的 HTLC 為雜湊 H 承諾 1.002 個比特幣,持續 9 個區塊,Carol 可以贖回,如果她有秘密 R(見 透過 LN 的逐步支付路由。 步驟 3)。Bob 知道如果 Carol 可以索取他的 HTLC,她必須產生 R。如果 Bob 在九個區塊內有 R,他可以使用它來索取 Alice 給他的 HTLC。他還透過承諾他的通道餘額九個區塊來賺取 0.001 個比特幣。如果 Carol 無法索取他的 HTLC,而他無法索取 Alice 的 HTLC,一切都會恢復到先前的通道餘額,沒有人損失。Bob 和 Carol 之間的通道餘額現在是:Carol 2,Bob 0.998,Bob 承諾給 HTLC 的 1.002。
Carol 現在有一個承諾,如果她在接下來的九個區塊內獲得 R,她可以索取 Bob 鎖定的 1.002 個比特幣。現在她可以在她與 Diana 的通道上做出 HTLC 承諾。她為雜湊 H 承諾 1.001 個比特幣的 HTLC,持續八個區塊,Diana 可以贖回,如果她有秘密 R(見 透過 LN 的逐步支付路由。,步驟 4)。從 Carol 的角度來看,如果這有效,她會好 0.001 個比特幣,如果不行,她什麼都不會損失。她給 Diana 的 HTLC 只有在 R 被揭示時才可行,屆時她可以從 Bob 那裡索取 HTLC。Carol 和 Diana 之間的通道餘額現在是:Diana 2,Carol 0.999,Carol 承諾給 HTLC 的 1.001。
最後,Diana 可以向 Eric 提供 HTLC,為雜湊 H 承諾 1 個比特幣,持續七個區塊(見 透過 LN 的逐步支付路由。,步驟 5)。Diana 和 Eric 之間的通道餘額現在是:Eric 2,Diana 1,Diana 承諾給 HTLC 的 1。
然而,在路由的這一跳,Eric _有_秘密 R。因此,他可以索取 Diana 提供的 HTLC。他將 R 發送給 Diana 並索取 1 個比特幣,將其添加到他的通道餘額中(見 透過 LN 的逐步支付路由。,步驟 6)。通道餘額現在是:Diana 1,Eric 3。
現在,Diana 有秘密 R。因此,她現在可以從 Carol 那裡索取 HTLC。Diana 將 R 傳輸給 Carol 並將 1.001 個比特幣添加到她的通道餘額中(見 透過 LN 的逐步支付路由。,步驟 7)。現在 Carol 和 Diana 之間的通道餘額是:Carol 0.999,Diana 3.001。Diana 透過參與這個支付路由「賺取」了 0.001。
秘密 R 在路由中回流,允許每個參與者索取未完成的 HTLC。Carol 從 Bob 那裡索取 1.002,將他們通道上的餘額設定為:Bob 0.998,Carol 3.002(見 透過 LN 的逐步支付路由。,步驟 8)。最後,Bob 索取來自 Alice 的 HTLC(見 透過 LN 的逐步支付路由。,步驟 9)。他們的通道餘額更新為:Alice 0.997,Bob 3.003。
Alice 已經支付 Eric 1 個比特幣,而無需開啟到 Eric 的通道。支付路由中的中間方都不必相互信任。對於他們在通道中資金的短期承諾,他們能夠賺取小額費用,唯一的風險是,如果通道關閉或路由支付失敗,退款會有小延 遲。
閃電網路傳輸與路徑查找
所有 LN 節點之間的通訊都是點對點加密的。此外,節點有一個長期公鑰,他們用它作為識別符並相互認證。
每當節點希望向另一個節點發送付款時,它必須首先透過連接具有足夠容量的支付通道來構建透過網路的_路徑_。節點廣告路由資訊,包括他們開啟的通道,每個通道有多少容量,以及他們收取什麼費用來路由付款。路由資訊可以以多種方式共享,並且隨著 LN 技術的進步,出現了不同的路徑查找協定。路由發現的當前實作使用 P2P 模型,其中節點以「洪泛」模型將通道公告傳播到其對等節點,類似於比特幣傳播交易的方式。
在我們之前的例子中,Alice 的節點使用這些路由發現機制之一來找到連接她的節點到 Eric 節點的一個或多個路徑。一旦 Alice 的節點構建了路徑,她將透過傳播一系列加密和嵌套的指令來透過網路初始化該路徑,以連接每個相鄰的支付通道。
重要的是,這條路徑只有 Alice 的節點知道。支付路由中的所有其他參與者只看到相鄰的節點。從 Carol 的角度來看,這看起來像是從 Bob 到 Diana 的付款。Carol 不知道 Bob 實際上正在轉發來自 Alice 的付款。她也不知道 Diana 將向 Eric 轉發付款。
這是 LN 的一個關鍵功能,因為它確保了付款的隱私,並使應用監控、審查或黑名單變得困難。但 Alice 如何在不向中間節點透露任何資訊的情況下建立這條支付路徑?
LN 實作了一個基於名為 Sphinx 的方案的洋蔥路由協定。這個路由協定確保付款發送者可以構建和通訊透過 LN 的路徑,使得:
- 中間節點可以驗證和解密其部分路由資訊並找到下一跳。
- 除了前一跳和下一跳,他們無法了解路徑中的任何其他節點。
- 他們無法識別支付路徑的長度或他們在該路徑中的位置。
- 路徑的每個部分都以這樣的方式加密,網路級攻擊者無法將來自路徑不同部分的封包相互關聯。
- 與 Tor(網際網路上的洋蔥路由匿名協定)不同,沒有可以被監視的「出口節點」。付款不需要傳輸到比特幣區塊鏈;節點只是更新通道餘額。 
使用這個洋蔥路由協定,Alice 將路徑的每個元素包裹在一層加密中,從末端開始向後工作。她用 Eric 的公鑰加密給 Eric 的訊息。這個訊息被包裹在加密給 Diana 的訊息中,標識 Eric 為下一個接收者。給 Diana 的訊息被包裹在加密到 Carol 的公鑰並標識 Diana 為下一個接收者的訊息中。給 Carol 的訊息被加密到 Bob 的金鑰。因此,Alice 構建了這個加密的多層「洋蔥」訊息。她將其發送給 Bob,Bob 只能解密和解開外層。在裡面,Bob 找到一個發給 Carol 的訊息,他可以轉發給 Carol 但無法自己破譯。沿著路徑,訊息被轉發、解密、轉發等,一直到 Eric。每個參與者只知道每一跳中的前一個和下一個節點。
路徑的每個元素包含關於必須擴展到下一跳的 HTLC 的資訊、發送的金額、要包含的費用以及 HTLC 的 CLTV 鎖定時間(以區塊為單位)到期。當路由資訊傳播時,節點向下一跳做出 HTLC 承諾。
在這一點上,您可能想知道節點如何不知道路徑的長度和他們在該路徑中的位置。畢竟,他們接收一個訊息並將其轉發到下一跳。它不會變短嗎,允許他們推斷路徑大小和他們的位置?為了防止這種情況,封包大小是固定的,並用隨機資料填充。每個節點看到下一跳和要轉發的固定長度的加密訊息。只有最終接收者看到沒有下一跳。對其他人來說,似乎總是有 更多跳。
閃電網路的優勢
LN 是一種第二層路由技術。它可以應用於任何支援一些基本功能的區塊鏈,例如多重簽章交易、時間鎖和基本智慧合約。
LN 分層在比特幣網路之上,為比特幣提供了容量、隱私、粒度和速度的顯著增加,而不犧牲無需中介的無信任操作原則:
- 隱私
- 
LN 付款比比特幣區塊鏈上的付款更加私密,因為它們不是公開的。雖然路由中的參與者可以看到通過其通道傳播的付款,但他們不知道發送者或接收者。 
- 可互換性
- 
LN 使得在比特幣上應用監控和黑名單變得困難得多,增加了貨幣的可互換性。 
- 速度
- 
使用 LN 的比特幣交易在毫秒內結算,而不是分鐘或小時,因為 HTLC 在不將交易承諾到區塊的情況下被清除。 
- 粒度
- 
LN 可以實現至少與比特幣「dust」限制一樣小的付款,甚至可能更小。 
- 容量
- 
LN 將比特幣系統的容量提高了幾個數量級。可以透過閃電網路路由的每秒付款數量的上限僅取決於每個節點的容量和速度。 
- 無信任操作
- 
LN 在作為對等節點運行的節點之間使用比特幣交易,而無需相互信任。因此,LN 保留了比特幣系統的原則,同時顯著擴展了其操作 參數。 
我們只檢查了一些可以使用比特幣區塊鏈作為信任平臺構建的新興應用程式。這些應用程式將比特幣的範圍擴展到付款之外。
既然您已經讀完了這本書,您將如何使用您所獲得的知識?也許數百萬人,也許數十億人,知道「比特幣」這個名字,但只有一小部分人像您現在一樣了解比特幣的運作方式。這種知識是寶貴的。更寶貴的是像您這樣對比特幣感興趣的人,願意閱讀數百頁關於它的內容。
如果您還沒有開始這樣做,請考慮以某種方式為比特幣做出貢獻。您可以執行全節點來驗證您收到的比特幣付款,構建使其他人更容易使用比特幣的應用程式,或幫助教育其他人關於比特幣及其潛力。您甚至可以採取罕見的步驟,為開源比特幣基礎設施軟體做出貢獻,例如 Bitcoin Core,與少數極其聰明的人仔細合作,構建沒有人會為之付費但數十億人可能有一天會依賴的工具。
無論您的比特幣之旅如何,我們都感謝您讓_精通比特幣_成為其中 的一部分。
Appendix A: 比特幣白皮書 作者:中本聰
| 這是 原始白皮書,完全按照中本聰於 2008 年 10 月發布的原樣全文轉載。 | 
比特幣 - 點對點電子現金系統
中本聰
摘要。 純點對點版本的電子現金將允許線上支付直接從一方發送到另一方,而無需通過金融機構。數位簽章提供了部分解決方案,但如果仍然需要可信的第三方來防止雙重支付,則主要優勢將喪失。我們提出了一個使用點對點網路解決雙重支付問題的方案。該網路透過將交易雜湊到持續進行的基於雜湊的工作量證明鏈中來為交易添加時間戳,形成一個記錄,除非重做工作量證明,否則無法更改。最長的鏈不僅可以作為所見證事件序列的證明,而且可以證明它來自最大的 CPU 算力池。只要多數 CPU 算力由不合作攻擊網路的節點控制,它們就會產生最長的鏈並超過攻擊者。該網路本身只需要最小的結構。訊息以盡力而為的方式廣播,節點可以隨意離開和重新加入網路,接受最長的工作量證明鏈作為它們離開時發生的事情的證明。
引言
網際網路上的商務幾乎完全依賴金融機構作為可信的第三方來處理電子支付。雖然該系統對大多數交易運作得足夠好,但它仍然受到基於信任模型固有弱點的困擾。完全不可逆的交易實際上是不可能的,因為金融機構無法避免調解糾紛。調解的成本增加了交易成本,限制了最小實際交易規模並切斷了小型臨時交易的可能性,並且在為不可逆服務進行不可逆支付的能力喪失方面存在更廣泛的成本。由於存在逆轉的可能性,對信任的需求蔓延開來。商家必須警惕他們的客戶,向他們索取超過必要的更多資訊。一定比例的欺詐被認為是不可避免的。使用實體貨幣可以避免這些成本和支付不確定性,但不存在在沒有可信方的情況下透過通訊通道進行支付的機制。
所需要的是一個基於密碼學證明而非信任的電子支付系統,允許任何兩個願意的方直接相互交易,而無需可信的第三方。在計算上不可能逆轉的交易將保護賣家免受欺詐,並且可以輕鬆實現常規的託管機制來保護買家。在本文中,我們提出了一個使用點對點分散式時間戳伺服器來生成交易時間順序的計算證明來解決雙重支付問題的方案。只要誠實節點集體控制的 CPU 算力多於任何合作的攻擊者節點組,系統就是安全的。
交易
我們 將電子貨幣定義為數位簽章鏈。每個所有者透過數位簽署前一筆交易的雜湊和下一個所有者的公鑰,並將這些添加到貨幣的末尾,將貨幣轉移給下一個所有者。收款人可以驗證簽章以驗證所有權鏈。
 
當然,問題是收款人無法驗證其中一個所有者沒有雙重支付貨幣。一個常見的解決方案是引入一個可信的中央機構或鑄幣廠,檢查每筆交易是否存在雙重支付。在每筆交易之後,必須將貨幣退還給鑄幣廠以發行新貨幣,並且只有直接從鑄幣廠發行的貨幣才被信任為沒有被雙重支付。這個解決方案的問題在於,整個貨幣系統的命運取決於經營鑄幣廠的公司,每筆交易都必須經過它們,就像銀行一樣。
我們需要一種方法讓收款人知道前任所有者沒有簽署任何更早的交易。就我們的目的而言,最早的交易才是有效的,所以我們不關心後來的雙重支付嘗試。確認交易不存在的唯一方法是知道所有交易。在基於鑄幣廠的模型中,鑄幣廠知道所有交易並決定哪個先到達。為了在沒有可信方的情況下完成此操作,交易必須公開宣布 [1],並且我們需要一個系統讓參與者就它們被接收順序的單一歷史達成一致。收款人需要證明在每筆交易時,大多數節點同意它是第一個 接收的。
時間戳伺服器
我們 提出的解決方案從時間戳伺服器開始。時間戳伺服器透過對要添加時間戳的項目區塊取雜湊並廣泛發布雜湊來工作,例如在報紙或 Usenet 貼文中 [2-5]。時間戳證明資料在當時必須存在,顯然,為了進入雜湊。每個時間戳在其雜湊中包含前一個時間戳,形成一條鏈,每個額外的時間戳都加強了之前的時間戳。
 
工作量證明
為了 在點對點基礎上實作分散式時間戳伺服器,我們需要使用類似於 Adam Back 的 Hashcash [6] 的工作量證明系統,而不是報紙或 Usenet 貼文。工作量證明涉及掃描一個值,當雜湊時,例如使用 SHA-256,雜湊以一定數量的零位元開頭。所需的平均工作量隨所需零位元數量呈指數增長,並可透過執行單個雜湊來驗證。對於我們的時間戳網路,我們透過在區塊中遞增一個 nonce 來實作工作量證明,直到找到一個值使區塊的雜湊獲得所需的零位元。一旦 CPU 努力使其滿足工作量證明,則在不重做工作的情況下無法更改該區塊。由於後續區塊在其後鏈接,更改區塊的工作將包括重做其後的所有區塊。
 
工作量證明還解決了確定多數決策中代表性的問題。如果多數是基於一個 IP 位址一票,則任何能夠分配許多 IP 的人都可以破壞它。工作量證明本質上是一個 CPU 一票。多數決定由最長的鏈代表,該鏈投入了最大的工作量證明努力。如果多數 CPU 算力由誠實節點控制,誠實鏈將增長最快並超過任何競爭鏈。要修改過去的區塊,攻擊者必須重做該區塊和其後所有區塊的工作量證明,然後趕上並超過誠實節點的工作。我們將在稍後展示,隨著後續區塊的添加,較慢的攻擊者趕上的機率呈指數下降。
為了補償硬體速度的提高和隨時間變化的執行節點興趣,工作量證明難度由移動平均值確定,目標是每小時平均區塊數。如果它們產生得太快,難度會增加。
網路
執行 網路的步驟如下:
- 
新交易廣播到所有節點。 
- 
每個節點將新交易收集到一個區塊中。 
- 
每個節點致力於為其區塊尋找困難的工作量證明。 
- 
當一個節點找到工作量證明時,它將區塊廣播到所有節點。 
- 
節點只有在區塊中的所有交易都有效且尚未被花費時才接受該區塊。 
- 
節點透過使用被接受區塊的雜湊作為前一個雜湊來致力於創建鏈中的下一個區塊,表達它們對該區塊的接受。 
節點總是認為最長的鏈是正確的,並將繼續致力於擴展它。如果兩個節點同時廣播下一個區塊的不同版本,某些節點可能首先接收到其中一個。在這種情況下,他們致力於他們首先接收到的那個,但保存另一個分支以防它變得更長。當找到下一個工作量證明並且一個分支變得更長時,平局將被打破;致力於另一個分支的節點然後將切換到更長的分支。
新交易廣播不一定需要到達所有節點。只要它們到達許多節點,它們很快就會進入一個區塊。區塊廣播也能容忍丟失的訊息。如果一個節點沒有接收到一個區塊,它將在接收到下一個區塊並意識到它錯過了一個區塊時請求它。
激勵
根據 慣例,區塊中的第一筆交易是一筆特殊交易,它啟動由區塊創建者擁有的新貨幣。這為節點支援網路增加了激勵,並提供了一種最初將貨幣分配到流通中的方式,因為沒有中央機構來發行它們。穩定添加恆定數量的新貨幣類似於黃金礦工花費資源來增加黃金流通。在我們的情況下,花費的是 CPU 時間和電力。
激勵也可以由交易手續費資助。如果交易的輸出值小於其輸入值,差額就是交易手續費,它被添加到包含該交易的區塊的激勵值中。一旦預定數量的貨幣進入流通,激勵可以完全過渡到交易手續費,並且完全沒有通貨膨脹。
激勵可能有助於鼓勵節點保持誠實。如果一個貪婪的攻擊者能夠組裝比所有誠實節點更多的 CPU 算力,他必須在使用它來透過竊回他的付款來欺詐人們,或使用它來生成新貨幣之間做出選擇。他應該發現遵守規則更有利可圖,這些規則使他獲得比其他所有人加起來更多的新貨幣,而不是破壞系統和他自己財富的 有效性。
回收磁碟空間
一旦 ((("disk space", "reclaiming")))((("reclaiming", "disk space")))((("blocks", "reclaiming disk space")))貨幣中的最新交易被足夠多的區塊埋在下面,其之前的已花費交易可以被丟棄以節省磁碟空間。為了在不破壞區塊雜湊的情況下促進這一點,交易在 Merkle 樹 [7] [2] [5] 中被雜湊,只有根包含在區塊的雜湊中。然後可以透過截斷樹的分支來壓縮舊區塊。內部雜湊不需要儲存。
 
沒有交易的區塊標頭大約為 80 位元組。如果我們假設每 10 分鐘生成一次區塊,80 位元組 * 6 * 24 * 365 = 4.2MB 每年。截至 2008 年,電腦系統通常銷售時配備 2GB RAM,摩爾定律預測當前每年增長 1.2GB,即使必須將區塊標頭保存在記憶體中,儲存也不應該是問題。
簡化支付驗證
可以 在不執行完整網路節點的情況下驗證支付。使用者只需要保留最長工作量證明鏈的區塊標頭副本,他可以透過查詢網路節點來獲得,直到他確信他擁有最長的鏈,並獲得將交易連結到它被添加時間戳的區塊的 Merkle 分支。他無法自己檢查交易,但透過將其連結到鏈中的一個位置,他可以看到網路節點已接受它,並且在其後添加的區塊進一步確認網路已接受它。
 
因此,只要誠實節點控制網路,驗證就是可靠的,但如果網路被攻擊者壓倒,則更容易受到攻擊。雖然網路節點可以自己驗證交易,但只要攻擊者能夠繼續壓倒網路,簡化方法就可能被攻擊者的偽造交易欺騙。一種防禦策略是當網路節點檢測到無效區塊時接受來自它們的警報,提示使用者的軟體下載完整區塊和警報的交易以確認不一致。經常收到付款的企業可能仍然希望執行自己的節點以獲得更獨立的安全性和更快的 驗證。
組合與分割價值
儘管 可以單獨處理貨幣,但為轉帳中的每一分錢單獨進行交易是不便的。為了允許價值被分割和組合,交易包含多個輸入和輸出。通常將有來自較大先前交易的單個輸入或組合較小金額的多個輸入,並且最多兩個輸出:一個用於支付,一個用於將找零(如果有)返還給發送者。
 
應該注意的是,扇出,其中一筆交易依賴於幾筆交易,而這些交易又依賴於更多交易,在這裡不是問題。永遠不需要提取交易歷史的完整獨立副本。
隱私
傳統 銀行模型透過限制對資訊的存取來達到一定程度的隱私,這些資訊僅限於相關方和可信的第三方。公開宣布所有交易的必要性排除了這種方法,但隱私仍然可以透過在另一個地方打破資訊流來維持:透過保持公鑰匿名。公眾可以看到有人正在向其他人發送金額,但沒有將交易連結到任何人的資訊。這類似於證券交易所發布的資訊水平,其中個人交易的時間和規模,即「磁帶」,是公開的,但不會告訴誰是參與方。
 
作為額外的防火牆,應該為每筆交易使用新的金鑰對,以防止它們被連結到一個共同的所有者。對於多輸入交易,某些連結仍然是不可避免的,這必然揭示它們的輸入由同一所有者擁有。風險在於,如果金鑰的所有者被揭露,連結可能會揭露屬於同一所有者的其他交易。
計算
我們考慮 攻擊者試圖比誠實鏈更快地生成替代鏈的情境。即使完成了這一點,它也不會使系統對任意更改開放,例如憑空創造價值或拿走從未屬於攻擊者的錢。節點不會接受無效交易作為支付,誠實節點永遠不會接受包含它們的區塊。攻擊者只能嘗試更改他自己的交易之一以收回他最近花費的錢。
誠實鏈和攻擊者鏈之間的競賽可以被描述為 二項式隨機漫步。成功事件是誠實鏈延長一個區塊,使其領先增加 +1,失敗事件是攻擊者的鏈延長一個區塊,使差距減少 -1。
攻擊者從給定赤字中趕上的機率類似於 ((("Gambler's Ruin problem")))賭徒破產問題。假設一個擁有無限信用的賭徒從赤字開始,並進行可能無限次數的試驗以嘗試達到盈虧平衡。我們可以計算他達到盈虧平衡的機率,或者攻擊者趕上誠實鏈的機率,如下 [8]:
p = 誠實節點找到下一個區塊的機率
q = 攻擊者找到下一個區塊的機率
qz = 攻擊者從落後 z 個區塊中趕上的機率
鑑於我們假設 p > q,機率隨著攻擊者必須趕上的區塊數量的增加呈指數下降。對他不利的情況下,如果他沒有在早期幸運地向前衝刺,隨著他進一步落後,他的機會變得微乎其微。
我們現在考慮新交易的接收者需要等待多長時間才能充分確定發送者無法更改交易。我們假設發送者是攻擊者,他想讓接收者相信他暫時支付了他,然後在一段時間後將其切換回支付給自己。當發生這種情況時,接收者將收到警報,但發送者希望為時已晚。
接收者生成新的金鑰對,並在簽署前不久將公鑰交給發送者。這防止了發送者透過持續工作來提前準備區塊鏈,直到他足夠幸運地獲得足夠的領先優勢,然後在那個時刻執行交易。一旦交易被發送,不誠實的發送者開始秘密地致力於包含其交易的替代版本的平行鏈。
接收者等待直到交易已被添加到區塊並且 z 個區塊已在其後連結。他不知道攻擊者已經取得的確切進展量,但假設誠實區塊採用每個區塊的平均預期時間,攻擊者的潛在進展將是具有期望值的 Poisson 分佈:
為了獲得攻擊者現在仍然可以趕上的機率,我們將他可能取得的每個進展量的 Poisson 密度乘以他可以從該點趕上的機率:
重新排列以避免對分佈的無限尾部求和…
轉換為 C 程式碼…
#include <math.h>
double AttackerSuccessProbability(double q, int z)
{
    double p = 1.0 - q;
    double lambda = z * (q / p);
    double sum = 1.0;
    int i, k;
    for (k = 0; k <= z; k++)
    {
        double poisson = exp(-lambda);
        for (i = 1; i <= k; i++)
            poisson *= lambda / i;
        sum -= poisson * (1 - pow(q / p, z - k));
    }
    return sum;
}執行一些結果,我們可以看到機率隨 z 呈指數下降。
q=0.1 z=0 P=1.0000000 z=1 P=0.2045873 z=2 P=0.0509779 z=3 P=0.0131722 z=4 P=0.0034552 z=5 P=0.0009137 z=6 P=0.0002428 z=7 P=0.0000647 z=8 P=0.0000173 z=9 P=0.0000046 z=10 P=0.0000012
q=0.3 z=0 P=1.0000000 z=5 P=0.1773523 z=10 P=0.0416605 z=15 P=0.0101008 z=20 P=0.0024804 z=25 P=0.0006132 z=30 P=0.0001522 z=35 P=0.0000379 z=40 P=0.0000095 z=45 P=0.0000024 z=50 P=0.0000006
求解 P 小於 0.1%…
P < 0.001 q=0.10 z=5 q=0.15 z=8 q=0.20 z=11 q=0.25 z=15 q=0.30 z=24 q=0.35 z=41 q=0.40 z=89 q=0.45 z=340
結論
我們 提出了一個不依賴信任的電子交易系統。我們從由數位簽章製成的貨幣的通常框架開始,它提供了強大的所有權控制,但如果沒有防止雙重支付的方法,則是不完整的。為了解決這個問題,我們提出了一個使用工作量證明記錄交易公開歷史的點對點網路,如果誠實節點控制多數 CPU 算力,則攻擊者在計算上改變它很快變得不切實際。網路在其非結構化的簡單性中是穩健的。節點同時工作,幾乎沒有協調。它們不需要被識別,因為訊息不會路由到任何特定位置,只需要以盡力而為的方式傳遞。節點可以隨意離開和重新加入網路,接受工作量證明鏈作為它們離開時發生的事情的證明。它們用 CPU 算力投票,透過致力於擴展有效區塊來表達它們對有效區塊的接受,並透過拒絕致力於它們來拒絕無效區塊。任何需要的規則和激勵都可以透過這種共識機制來執行。
參考文獻
[1] W. Dai, "b-money," http://www.weidai.com/bmoney.txt, 1998.
[2] H. Massias, X.S. Avila, and J.-J. Quisquater, "Design of a secure timestamping service with minimal trust requirements," In 20th Symposium on Information Theory in the Benelux, May 1999.
[3] S. Haber, W.S. Stornetta, "How to time-stamp a digital document," In Journal of Cryptology, vol 3, no 2, pages 99-111, 1991.
[4] D. Bayer, S. Haber, W.S. Stornetta, "Improving the efficiency and reliability of digital time-stamping," In Sequences II: Methods in Communication, Security and Computer Science, pages 329-334, 1993.
[5] S. Haber, W.S. Stornetta, "Secure names for bit-strings," In Proceedings of the 4th ACM Conference on Computer and Communications Security, pages 28-35, April 1997.
[6] A. Back, "Hashcash - a denial of service counter-measure," http://www.hashcash.org/papers/hashcash.pdf, 2002.
[7] R.C. Merkle, "Protocols for public key cryptosystems," In Proc. 1980 Symposium on Security and Privacy, IEEE Computer Society, pages 122-133, April 1980.
[8] W. Feller, "An introduction to probability theory and its applications," 1957.
授權
本白皮書由中本聰於 2008 年 10 月發布。它後來(2009 年)作為支援文件添加到比特幣軟體中,並採用相同的 MIT 授權。它已根據 MIT 授權條款在本書中轉載,除格式外沒有修改:
The MIT License (MIT) Copyright (c) 2008 Satoshi Nakamoto
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Appendix B: 比特幣白皮書勘誤表
本 附錄包含中本聰論文「比特幣:點對點電子現金系統」中已知問題的描述,以及術語變化和比特幣實作與論文中描述的差異的說明。
本文檔最初由本書的共同作者於 2016 年發布;在此轉載並附有更新。本勘誤表中章節的名稱對應於中本聰原始論文中章節的名稱。
摘要
「最長的鏈不僅可以作為所見證事件序列的證明,而且可以證明它來自最大的 CPU 算力池。」
- 
實作細節: 如果鏈中的每個連結(在比特幣中稱為「區塊」)都使用相同數量的_工作量證明_(PoW)構建,則最長的鏈將是由最大計算能力池支援的鏈。然而,比特幣的實作方式使得區塊之間的 PoW 數量可以變化,因此重要的不是檢查「最長的鏈」,而是「展示最多 PoW 的鏈」;這通常縮寫為「最多工作鏈」。 從檢查最長鏈到檢查最多工作鏈的 更改發生在 2010 年 7 月,遠在比特幣最初發布之後: - if (pindexNew->nHeight > nBestHeight) + if (pindexNew->bnChainWork > bnBestChainWork)
- 
術語變化: 通用 CPU 用於為最早的比特幣區塊生成 PoW,但今天 PoW 生成主要由專用應用積體電路(ASIC)執行,因此與其說「CPU 算力」,不如說「計算能力」或簡單地說用於生成 PoW 的雜湊的「雜湊率」可能更正確。 
「只要多數 CPU 算力由不合作攻擊網路的節點控制,它們就會產生最長的鏈並超過攻擊者。」
- 
術語變化: 今天「節點」一詞用於指完全驗證節點,即執行系統所有規則的程式。今天擴展鏈的程式(和硬體)被稱為「礦工」,基於中本聰在論文第 6 節中對黃金礦工的類比。中本聰期望所有礦工都是節點,但他發布的軟體並不要求所有節點都是礦工。在原始軟體中,節點 GUI 中的一個簡單選單項允許打開或關閉挖礦功能。 今天的情況是,絕大多數節點不是礦工,並且許多擁有挖礦硬體的個人不使用自己的節點(甚至那些使用自己節點挖礦的人通常在新發現的區塊之上短時間挖礦,而不確保他們的節點認為新區塊有效)。論文的早期部分主要使用「節點」而沒有修飾語,是指使用完全驗證節點進行挖礦;論文的後期部分提到「網路節點」主要是關於即使節點不進行挖礦也可以做什麼。 
- 
發布後發現: 當產生新區塊時,產生該區塊的礦工可以立即開始處理其後續,但所有其他礦工都不知道新區塊,並且在它傳播到他們的網路之前無法開始處理它。這使得產生許多區塊的礦工比產生較少區塊的礦工具有優勢,這可以在所謂的_自私挖礦攻擊_中被利用,允許擁有約 30% 總網路雜湊率的攻擊者使其他礦工利潤降低,可能迫使他們遵循攻擊礦工的政策。因此,與其說「多數 CPU 算力由不合作攻擊網路的節點控制」,不如說「只要合作攻擊網路的節點控制少於約 30% 的網路」可能更正確。 
交易
「我們 將電子貨幣定義為數位簽章鏈。每個所有者透過數位簽署前一筆交易的雜湊和下一個所有者的公鑰,並將這些添加到貨幣的末尾,將貨幣轉移給下一個所有者。」
- 
實作細節: 比特幣實作了該系統的更通用版本,其中不直接使用數位簽章,而是使用「確定性表達式」。正如與已知公鑰匹配的簽章可以用來啟用支付一樣,滿足已知表達式的資料也可以啟用支付。一般來說,在比特幣中為了花費貨幣必須滿足的表達式稱為「約束」。迄今為止,比特幣中幾乎所有約束都需要提供至少一個簽章。因此,與其說「數位簽章鏈」,不如說「約束鏈」更正確。鑑於交易通常有多個輸入和多個輸出,結構不是很像鏈;它更準確地描述為有向無環 圖(DAG)。 
工作量證明
「…我們 透過在區塊中遞增 nonce 來實作工作量證明,直到找到一個值使區塊的雜湊獲得所需的零位元。」
- 
實作細節: Adam Back 的 Hashcash 實作需要找到具有所需前導零位元數的雜湊。比特幣將雜湊視為整數,並要求它小於指定的整數,這有效地允許指定小數位元數。 
「工作量證明本質上是一個 CPU 一票。」
- 
重要說明: 這裡的投票不是針對系統的規則,而僅僅是針對交易的順序,以提供「電子貨幣」不能輕易被雙重支付的保證。這在論文的第 11 節中有更詳細的描述,其中說:「我們考慮攻擊者試圖比誠實鏈更快地生成替代鏈的情境。即使完成了這一點,它也不會使系統對任意更改開放,例如憑空創造價值或拿走從未屬於攻擊者的錢。節點不會接受無效交易作為支付,誠實節點永遠不會接受包含它們的區塊。」 
「…工作量證明難度由移動平均值確定,目標是每小時平均區塊數。」
- 
實作細節: 不使用移動平均值。相反,每 2,016 個區塊將其報告的生成時間與較早區塊的生成時間進行比較,並且它們之間的差異用於計算用於調整的平均值。 此外,比特幣中實作的平均值目標是每兩週平均區塊數(而不是文本可能暗示的每小時)。其他實作的規則可能進一步減緩調整,例如調整不能使每個週期的區塊生成速度增加超過 300%,也不能使其減慢超過 75%。 
回收磁碟空間
「一旦 貨幣中的最新交易被足夠多的區塊埋在下面,其之前的已花費交易可以被丟棄以節省磁碟空間。」
- 
可能的發布後發現: 儘管本節中描述的 merkle 樹結構可以證明交易包含在特定區塊中,但目前在比特幣中沒有辦法證明交易尚未被花費,除非處理區塊鏈中的所有後續資料。這意味著這裡描述的方法不能在所有節點中普遍用於回收磁碟空間,因為所有新節點都需要處理所有交易。 
簡化支付驗證
「一種 防禦策略是當網路節點檢測到無效區塊時接受來自它們的警報,提示使用者的軟體下載完整區塊和警報的交易以確認不一致。」
- 
重要說明: 儘管已經產生了實作本節某些部分並稱為簡化支付驗證(SPV)的軟體,但這些程式目前都不接受來自網路節點(完全驗證節點)的警報,當檢測到無效區塊時。這使得所謂的 SPV 錢包中的比特幣過去曾處於風險之中。 
隱私
「對於 多輸入交易,某些連結仍然是不可避免的,這必然揭示它們的輸入由同一所有者擁有。」
- 
發布後發明: 如果所有者經常將他們的輸入與屬於其他所有者的輸入混合,則同一交易中的不同輸入是否有相同所有者並不清楚。例如,Alice 和 Bob 各自貢獻他們的一個輸入來支付 Charlie 和 Dan,與 Alice 單獨貢獻她的兩個輸入來支付 Charlie 和 Dan 之間沒有公開差異。 這種技術今天被稱為 CoinJoin,實作它的軟體自 2015 年以來一直在使用。 
計算
「接收者 生成新的金鑰對,並在簽署前不久將公鑰交給發送者。這防止了發送者透過持續工作來提前準備區塊鏈,直到他足夠幸運地獲得足夠的領先優勢,然後在那個時刻執行交易。」
- 
發布後發現: 接收者在花費者簽署交易前不久生成公鑰並不能防止花費者提前準備區塊鏈。早期比特幣使用者 Hal Finney 發現了這種攻擊並 描述了它:「假設攻擊者偶爾生成區塊。在他生成的每個區塊中,他包括從地址 A 到地址 B 的轉帳,這兩個地址都由他控制。 「為了欺騙你,當他生成一個區塊時,他不會廣播它。相反,他跑到你的商店並用他的地址 A 向你的地址 C 付款。你等幾秒鐘,沒有聽到任何消息,然後轉移商品。他現在廣播他的區塊,他的交易將優先於你的交易。」 該攻擊適用於任意數量的確認,有時被命名為 Finney 攻擊。 
免責聲明: 本文檔的作者並不是第一個識別這裡描述的任何問題的人——他只是將它們收集到一個文檔中。
授權: 本勘誤文檔根據 CC0 1.0 通用公共領域貢獻發布
對於本書出版 後進行的更新,請參閱 原始文檔。
Appendix C: 比特幣改進提案
比特幣改進提案是為比特幣社群提供資訊或描述比特幣或其流程或環境的新功能的設計文件。
根據 BIP1《BIP 目的和指南》,有三 種 BIP:
- 標準 BIP
- 
描述影響大多數或所有比特幣實作的任何更改,例如網路協定的更改、區塊或交易有效性規則的更改,或影響使用比特幣的應用程式互通性的任何更改或添加。 
- 資訊性 BIP
- 
描述比特幣設計問題或向比特幣社群提供一般指南或資訊,但不提出新功能。資訊性 BIP 不一定代表比特幣社群的共識或建議,因此使用者和實作者可以忽略資訊性 BIP 或遵循其建議。 
- 流程 BIP
- 
描述比特幣流程或提議流程的更改(或流程中的事件)。流程 BIP 類似於標準 BIP,但適用於比特幣協定本身以外的領域。它們可能提議一個實作,但不是針對比特幣的程式碼庫;它們通常需要社群共識。與資訊性 BIP 不同,它們不僅僅是建議,使用者通常不能自由地忽略它們。範例包括程序、指南、決策流程的更改,以及比特幣開發中使用的工具或環境的更改。任何元 BIP 也被認為是流程 BIP。 
BIP 記錄在 GitHub 上的 版本化儲存庫中。來自開源 Bitcoin Core 專案的 MIT 授權文件在此以編輯形式轉載,描述它實作了哪些 BIP,包括列出添加或顯著更改每個 BIP 支援的 Pull Request(PR)和 Bitcoin Core 版本。
由 Bitcoin Core 實作的 BIP:
- 
BIP9:允許並行部署多個軟分叉的更改自 v0.12.1 起已實作(PR #7575)。 
- 
BIP11:自 v0.6.0 起,多重簽章輸出是標準的(PR #669)。 
- 
BIP13:P2SH 地址的地址格式自 v0.6.0 起已實作(PR #669)。 
- 
BIP14:自 v0.6.0 起,子版本字串被用作使用者代理(PR #669)。 
- 
BIP16:支付到腳本雜湊的評估規則自 v0.6.0 起已實作,並於 2012 年 4 月 1 日生效(PR #748)。 
- 
BIP21:比特幣支付的 URI 格式自 v0.6.0 起已實作(PR #176)。 
- 
BIP22:用於挖礦的 'getblocktemplate'(GBT)RPC 協定自 v0.7.0 起已實作(PR #936)。 
- 
BIP23:自 v0.10.0rc1 起已實作 GBT 的一些擴展,包括長輪詢和區塊提案(PR #1816)。 
- 
BIP30:禁止創建與先前未完全花費的交易具有相同 txid 的新交易的評估規則自 v0.6.0 起已實作,該規則於 2012 年 3 月 15 日生效(PR #915)。 
- 
BIP31:'pong' 協定訊息(以及協定版本提升至 60001)自 v0.6.1 起已實作(PR #1081)。 
- 
BIP32:自 v0.13.0 起已實作階層式確定性錢包(PR #8035)。 
- 
BIP34:要求區塊在 coinbase 輸入中包含其高度(編號)的規則,以及版本 2 區塊的引入,自 v0.7.0 起已實作。該規則對版本 2 區塊自區塊 224413(2013 年 3 月 5 日)起生效,版本 1 區塊自區塊 227931(2013 年 3 月 25 日)起不再允許(PR #1526)。 
- 
BIP35:'mempool' 協定訊息(以及協定版本提升至 60002)自 v0.7.0 起已實作(PR #1641)。自 v0.13.0 起,這僅適用於 NODE_BLOOM(BIP111)對等節點。 
- 
BIP37:用於交易轉發的布隆過濾、區塊的部分 Merkle 樹以及協定版本提升至 70001(啟用低頻寬輕量級客戶端)自 v0.8.0 起已實作(PR #1795)。自 v0.19.0 起預設禁用,可以透過 -peerbloomfilters 選項啟用。 
- 
BIP42:導致補貼時間表在區塊 13440000 後恢復的錯誤在 v0.9.2 中修復(PR #3842)。 
- 
BIP43:v0.21.0 中引入的實驗性描述符錢包預設使用 BIP43 提出的階層式確定性錢包派生(PR #16528)。 
- 
BIP44:v0.21.0 中引入的實驗性描述符錢包預設使用 BIP44 提出的階層式確定性錢包派生(PR #16528)。 
- 
BIP49:v0.21.0 中引入的實驗性描述符錢包預設使用 BIP49 提出的階層式確定性錢包派生(PR #16528)。 
- 
BIP61:'reject' 協定訊息(以及協定版本提升至 70002)在 v0.9.0 中添加(PR #3185)。從 v0.17.0 開始,是否發送拒絕訊息可以使用 -enablebip61 選項配置,並且自 v0.18.0 起支援已棄用(預設禁用)。支援在 v0.20.0 中移除(PR #15437)。 
- 
BIP65:CHECKLOCKTIMEVERIFY 軟分叉在 v0.12.0 中合併(PR #6351),並回移植到 v0.11.2 和 v0.10.4。僅記憶體池 CLTV 在 PR #6124 中添加。 
- 
BIP66:嚴格的 DER 規則和相關的版本 3 區塊自 v0.10.0 起已實作(PR #5713)。 
- 
BIP68:序列鎖自 v0.12.1 起已實作(PR #7184),並自 v0.19.0 起已埋藏(PR #16060)。 
- 
BIP70 71 72:支付協定支援自 v0.9.0 起在 Bitcoin Core GUI 中可用(PR #5216)。自 v0.18.0 起可以在建置時選擇性禁用支援(PR 14451),並且自 v0.19.0 起在建置時預設禁用(PR #15584)。它在 v0.20.0 中被移除(PR 17165)。 
- 
BIP84:v0.21.0 中引入的實驗性描述符錢包預設使用 BIP84 提出的階層式確定性錢包派生。(PR #16528) 
- 
BIP86:自 v23.0 起,描述符錢包預設使用 BIP86 提出的階層式確定性錢包派生(PR #22364)。 
- 
BIP90:BIP 34、65 和 66 的啟動觸發機制自 v0.14.0 起已簡化為區塊高度檢查(PR #8391)。 
- 
BIP111:NODE_BLOOM 服務位元自 v0.13.0 起為所有對等節點版本添加並強制執行(PR #6579 和 PR #6641)。 
- 
BIP112:CHECKSEQUENCEVERIFY 操作碼自 v0.12.1 起已實作(PR #7524),並自 v0.19.0 起已埋藏(PR #16060)。 
- 
BIP113:自 v0.12.1 起已實作中位數過去時間鎖定時間計算(PR #6566),並自 v0.19.0 起已埋藏(PR #16060)。 
- 
BIP125:選擇性全額替換手續費信號部分實作。 
- 
BIP130:自 v0.12.0 起,對等節點版本 ≥70012 協商直接標頭公告(PR 6494)。 
- 
BIP133:自 v0.13.0 起,對等節點版本 ≥70013 尊重並發送 feefilter 訊息(PR 7542)。 
- 
BIP141:隔離見證(共識層)自 v0.13.0 起(PR 8149),自 v0.13.1 起為主網定義(PR 8937),並自 v0.19.0 起埋藏(PR #16060)。 
- 
BIP143:版本 0 見證程式的交易簽章驗證自 v0.13.0 起(PR 8149),自 v0.13.1 起為主網定義(PR 8937),並自 v0.19.0 起埋藏(PR #16060)。 
- 
BIP144:隔離見證自 0.13.0 起(PR 8149)。 
- 
BIP145:隔離見證的 getblocktemplate 更新自 v0.13.0 起(PR 8149)。 
- 
BIP147:NULLDUMMY 軟分叉自 v0.13.1 起(PR 8636 和 PR 8937),自 v0.19.0 起埋藏(PR #16060)。 
- 
BIP152:緊湊區塊傳輸和相關優化自 v0.13.0 起使用(PR 8068)。 
- 
BIP155:啟用 Tor V3 地址(和其他網路)轉發的 'addrv2' 和 'sendaddrv2' 訊息自 v0.21.0 起支援(PR 19954)。 
- 
BIP157 158:輕量級客戶端的緊湊區塊過濾器可以自 v0.19.0 起建立索引(PR #14121),並自 v0.21.0 起在 P2P 網路上為對等節點提供服務(PR #16442)。 
- 
BIP159:NODE_NETWORK_LIMITED 服務位元自 v0.16.0 起發出信號(PR 11740),並且自 v0.17.0 起連接到此類節點(PR 10387)。 
- 
BIP173:自 v0.16.0 起支援原生隔離見證輸出的 Bech32 地址(PR 11167)。自 v0.20.0 起預設生成 Bech32 地址(PR 16884)。 
- 
BIP174:操作部分簽署比特幣交易(PSBT)的 RPC 自 v0.17.0 起存在(PR 13557)。 
- 
BIP176:位元面額 [僅 QT] 自 v0.16.0 起支援(PR 12035)。 
- 
BIP325:自 v0.21.0 起支援 Signet 測試網路(PR 18267)。 
- 
BIP339:自 v0.21.0 起支援透過 wtxid 轉發交易(PR 18044)。 
- 
BIP340 341 342:Taproot 的驗證規則(包括 Schnorr 簽章和 Tapscript 葉子)自 v0.21.0 起實作(PR 19953),主網啟動自 v0.21.1 起(PR 21377、PR 21686)。 
- 
BIP350:原生 v1+ 隔離見證輸出的地址自 v22.0 起使用 bech32m 而不是 bech32(PR 20861)。 
- 
BIP371:PSBT 的 Taproot 欄位自 v24.0 起(PR 22558)。 
- 
BIP380 381 382 383 384 385:輸出腳本描述符和大多數腳本表達式自 v0.17.0 起實作(PR 13697)。 
- 
BIP386:tr() 輸出腳本描述符自 v22.0 起實作(PR 22051)。 
 
 
