作者:Anony
用戶在接觸比特幣的時候,往往第一時間就會遇到 “地址” 這個概念。在你嘗試收取比特幣支付時,就需要提供自己的地址。在區塊瀏覽器中查詢支付是否已經到賬時,往往也以具體的地址為搜索條件。
你可能會以為:“地址就相當於比特幣世界裡的銀行賬號,可以用來接收比特幣”。但這種理解,在面對錢包使用過程中的一些情形時,可能還是會讓你犯迷糊。比如說:在初次使用一款比特幣軟件錢包時,它可能會請你選擇一種地址 “類型”,如:“Bech32(SegWit)”、“P2PKH”、“Nested-SegWit(P2SH)” 等等。甚至,在你要換用另一款軟件錢包時,它也會給你驚嚇:新的軟件錢包可能會給你一組跟原軟件錢包完全不同的比特幣地址;這時候,該怎麼辦呢?
本文就是要對比特幣的地址概念和地址類型作稍微深入一些的解釋,以幫助讀者解決在自主保管比特幣的過程中可能遇到的一些問題,包括但不限於地址類型的選擇以及軟件錢包遷移過程中會發生的困擾。
最末一個章節會集中描述讀者可能接觸到的不同地址的特徵和經濟性;如果你對技術細節完全不感興趣,或只是想快速查證資料,可以跳到最後一個章節;但如果你希望規劃自主保管的方法,則推薦你從頭讀起。
要而言之,比特幣地址實際上是用於標準化的比特幣腳本的關鍵數據在經過特殊編碼(轉譯)之後結果;特殊的編碼方法使之更適合於傳遞,並且提供了提醒錯誤的能力;而其經濟性的區別就來自於其底層的比特幣腳本在經濟性上的區別。
標準化的比特幣腳本
眾所周知,比特幣是一種運行在點對對網絡中的電子貨幣。在開發比特幣時,中本聰為這種貨幣設計了一種後來被稱為 “UTXO” 的存在形式。這種形式使得比特幣資金不太像放在一個又一個賬戶裡的錢,倒像是一筆又一筆相互獨立的支票。這些 “支票” 記錄了兩種關鍵信息:該筆資金的面額(以“聰(sat)” 為單位);腳本公鑰(scriptPubkey),用來定義這筆錢在什麼情況下可以被花費。腳本公鑰就像一種鎖,要求特定的鑰匙來開啟。
中本聰意識到,如果我們可以定製巧妙的鎖,比特幣就可以更靈活地用在不同場景中。於是,他還設計了一種叫做 “Bitcoin Script” 的編程語言,以及基於 UTXO 的交易驗證模式;從而,我們可以編寫用作腳本公鑰的程序,並且,當相關的資金被花費時,可以依據這樣的程序得到驗證。
這種創新帶來了一個實際的困難:交易在點對點網絡中傳播時,接收到交易的節點會先運行一些驗證工作。如果這種編程語言和編程有內在的漏洞,可以讓節點在驗證交易的過程中就崩潰,那麼,能夠利用這種漏洞的交易就可以被用來摧毀整個網絡。在交易的自由傳播和網絡的安全性之間,如何取得平衡呢?
除了有意限制 Bitcoin Script 的靈活性,中本聰還想出了一種辦法:將一些已知足夠簡潔、不會觸發故障的腳本定義為 “標準化的比特幣腳本” [1];在花費使用這樣的腳本的資金時,交易被當作 “標準的比特幣交易”,可以在網絡中無礙傳播。反之,如果不使用這樣的標準化腳本,即使交易是有效的,也只能直接提交給礦工,由礦工打包進區塊並挖出之後,再傳播到整個網絡。這就限制了可能引發安全問題的交易在網絡中傳播、導致節點崩潰。
最早被實現的標準化比特幣腳本有兩種:“P2PKH” 和 “P2PK”;顧名思義,它們是在腳本公鑰中放置一個公鑰(或者一個公鑰的哈希值),要求花費資金的交易提供該公鑰(背後的私鑰)的簽名。
一個 P2PKH 腳本公鑰是這樣的:
OP_DUP OP_HASH160 55ae51684c43435da751ac8d2173b2652eb64105 OP_EQUALVERIFY OP_CHECKSIG
(來自著名的比特幣科普網站:learn me a bitcoin)
地址的概念
標準化的腳本讓比特幣系統具備了基本的功能(個人可以通過持有私鑰來保管比特幣、向他人發起電子貨幣支付)。但是,它依然是一種為計算機而設計的數據 —— 要理解這些字符串的主體是計算機。計算機對字符串的長度並不敏感,也不會在複製數據的過程中出錯。而人在許多方面都相反。
問題在於,人作為這個系統的使用者,確實要跟這些數據打交道:當一個人接收比特幣支付的時候,TA 所要求的是對方將一筆比特幣資金髮送到由 TA 控制(或者說 TA 可以成功解鎖)的一段比特幣腳本中;此外,當 TA 要長期保管自己的資金的時候,TA 可能要備份自己的比特幣腳本。
這時候該怎麼辦呢?像上面那樣長長的字符串,顯然既不適於傳遞(太長了),也不適於備份(容易抄錯)。
前面我們已經提到,對大部分人都實用的腳本都是標準化的,這種標準化意味著,兩個腳本僅在其中一處關鍵數據上有所區別:對兩個 P2PKH 腳本來說,它們唯一的區別就是所記錄的公鑰哈希值不同。因此,在收款時,我們只需提供這個哈希值、以及腳本的類型(它是個 P2PKH 腳本),就足夠了。支付方(的軟件)會根據這些信息復原出完整的比特幣腳本,從而在交易中將比特幣發送到正確的地方。
而且,(諳熟工程學的中本聰意識到),我們可以不傳遞這個哈希值的十六進制形式(55ae51684c43435da751ac8d2173b2652eb64105
,40 位字符)。藉助專門設計的編碼方法,我們可以將它轉換為更短、更容易正確辨認的形式。
這就是 “地址”:經過編碼、攜帶了關鍵信息、使我們可以正確復原出比特幣腳本的數據。
編碼方法
Base58
“Base58” [2] 是由中本聰發明的編碼方法,是從一種著名的編碼方法 “Base64” 改造而來。Base64 的字符集包括:所有的數字和大小寫字母,還有兩種符號(“+” 和 “/”);總計 64 種字符。而中本聰從中刪去了數字 0、大寫字母 I 和 O、小寫字母 l 以及符號,就成了 Base58。
這種刪減是有考慮的。中本聰的自述是:
為什麼要使用 base58 而不是 base64 呢?
- 不使用 0OIl 是因為這些字符看起來很像,可以用來創建出看起來幾乎一模一樣的賬號。
- 人們不容易接受賬號中會有字母和數字以外的字符。
- 不使用標點符號的話,在 E-mail 中通常就不會被換行打斷。
- 雙擊就可以選定整個字符串,因為只有字母和數字。
– 中本聰,Bitcoin v0.1 (base58.h)
地址是要被複原成比特幣腳本的,因此,只要一個字符錯誤,資金就有可能被髮送到完全不一樣的比特幣腳本(可能是完全無法解鎖的腳本!)中、導致資金損失;甚至,如果允許使用這樣容易造成混淆的字符,惡意軟件可以將你的地址悄悄替換成看起來相似、但實際上由攻擊者控制的地址,讓你在接收支付時丟失資金。
因此,中本聰的考慮是完全有道理的。
在執行 Base58 編碼之前,我們還要給關鍵數據(比如上述 P2PKH 腳本中的哈希值)加上類型碼作為前綴、並以帶前綴的關鍵數據的連續兩次 SHA256 運算結果的前 4 個字節作為後綴。
- 前綴可以迅速說明數據的類型和用途;也正因為添加了前綴,同一類型的數據在經過 Base58 編碼的結果中,總是會出現相同的開頭。這就是為什麼我們只需看一個比特幣地址的開頭,就知道它是什麼類型的地址。
- 後綴則可以起到校驗和的作用:如果你向軟件輸入了一個有抄寫錯誤的地址,軟件會提醒你可能出錯了(儘管無法指明是哪裡抄錯了)。
即,在開始編碼前,我們要構造出這樣的字符串:
類型碼 + 關鍵數據 + SHA256(SHA256(類型碼 + 關鍵數據))[0:4](這裡的 “+” 是字符串拼接的意思)
以上面的 P2PKH 腳本為例,我們先要給關鍵數據(55ae51684c43435da751ac8d2173b2652eb64105
)加上前綴 00
;然後對此數據運行連續兩次 SHA256 計算,取前 4 個字節(十六進制的 8 個字符,96ab3cb1
),作為後綴,得到 0055ae51684c43435da751ac8d2173b2652eb6410596ab3cb1
。最後,運行 Base58 編碼,得到:18p3G8gQ3oKy4U9EqnWs7UZswdqAMhE3r8
。
這段字符串,既包含了用在比特幣腳本中的關鍵信息(公鑰哈希值)、又能說明它該如何使用(前綴 1
表示應該將它復原成一個 P2PKH 腳本)、還具備檢測抄寫錯誤的功能,依然只有 34 個字符,比原先的哈希值還要短。
Bech32
“Bech32” 是由 BIP 0173 [3] 定義的編碼方法,該 BIP 的兩位作者是 Pieter Wuille 和 Greg Maxwell 。不過,這種編碼也有自身的源流:“Bech” 指的是 “BCH” [4],是一種由三位數學家分別在 1959 和 1960 年發明的循環糾錯編碼算法(BCH 這個名字就來自於這三位數學家的姓氏)。而 “32” 則表示,該編碼法的字符集只有 32 種字符:小寫的英文字母和數字,除去數字 “1”、字母“b”、“i”和“o”。
該 BIP 的考慮是,藉著 “隔離見證(SegWit)” 升級的機會,為兩種全新的標準化腳本 “P2WPKH” 和 “P2WSH” 的地址使用新的編碼方法。
在 BIP 0173 的開頭,作者們指出了 Base58 的不理想之處:
- Base58 同時使用大小和小寫的英文字母,這使得其數據在繪製成二維碼時,無法使用體積更小的 “數字字母表” 模式,只能使用體積更大的 “字節數據” 模式。
- 同時使用大小寫也使得它不便於抄寫、在手機鍵盤上輸入以及念出來。
- 校驗和需要連續兩次 SHA256 運算,運算緩慢,而且沒有定位錯誤的功能。
- 大部分可定位錯誤的編碼方法都只適用於字符集大小是質數冪的情形,而 58 並非質數冪。
- Base58 的解碼較為複雜,運算也較慢。
於是,Bech32 這種新方法只使用小寫字母和數字;在有需要的時候(比如繪製二維碼的時候),這些字母可以全部換成大寫,從而獲得更緊湊的表現形式。同時,Bech32 還具備定位錯誤的能力:它不僅能發現你抄寫錯誤了,還能指出你的哪幾位抄錯了(這種發現錯誤的能力遠遠優於 Base58)。
實際上,BCH 算法還具有 “糾錯” 功能:它不僅能指出你的哪幾位抄錯了,還能指出它應該是什麼字符。然而,BIP 0173 的作者們發現了它內在的危險性:一方面,強化糾錯功能會削弱定位錯誤的功能;另一方面,如果用戶過於信任軟件的糾錯能力,那麼軟件就有可能將用戶輸入的錯誤數據糾正成一個 “有效但無用” 的數據 —— 雖然作為一段 BCH 編碼數據,它是有效的了;但是,憑藉它復原出來的比特幣腳本卻有可能不是收款方能夠控制的、甚至不是任何人能夠控制的。這是極其危險的。因此,BIP 0173 慎重提醒:“除了提醒用戶哪幾位可能抄錯了之外,軟件不應該實現糾錯能力(給出糾正建議)。”
除此之外,Bech32 沿用了 Base58 編碼中的模式:
- Bech32 數據的開頭會有一段 “帶有含義的數據(hrp)”,就類似於 Base58 中的前綴,可以說明這是一段什麼樣的數據。
- hrp 可以使用的字符遠遠多於 32 個;於是,Bech32 還將數字 “1” 作為分隔符,用來分割 hrp 和真正要被解碼的數據。
- 除了比特幣,還有許多別的項目也採用了 Bech32 ;不同項目的數據就使用 hrp 來相互區別。這裡有一份已註冊的 hrp 的列表,非常有趣(但也僅僅是有趣) [5]。
- Bech32 也設計了校驗和,佔據編碼後的數據的最後 6 個字符。
假設我們跟上文的案例一樣,使用完全相同的公鑰哈希值,它的 P2WPKH 腳本會是這樣的:0 55ae51684c43435da751ac8d2173b2652eb64105
(沒錯,比原來的 P2PKH 要更簡單、更抽象);而其 Bech32 編碼的地址是:bc1q2kh9z6zvgdp4mf634jxjzuajv5htvsg9ulykp8
,長度是 42 個字符。
Bech32m
“Bech32m” 是由 BIP 0350 [6] 定義的編碼方法。它的提出是因為開發者們在 Bech32 編碼中發現了一個漏洞:
當最後一個字符是 “p” 的時候,在該字符前面插入或刪除任意數量個 “q”,都不會導致校驗和報錯,那麼校驗和機制就完全失去作用了。
如果不再增設標準化的比特幣腳本,這問題很容易解決:P2WPKH 地址和 P2WSH 地址都有確定的長度,增加長度校驗就好。然而,考慮到未來我們還會增加新的標準化腳本,其地址長度可能發生改變,就有必要修復這個問題。
Bech32m 通過改變 Bech32 校驗和生成程序中的一個參數,修復了這個問題。
當前,Bech32m 僅用於編碼隨 “Taproot” 升級而增加的 “P2TR” 腳本的地址。未來可能用在其它標準化腳本的地址編碼中。
經濟性
在我們理解了地址是一個標準化的比特幣腳本的特殊表現形式、地址的類型實際上來自於標準化比特幣腳本的類型之後,不同類型的地址何以具有不同的經濟性 —— 在花費時可能具有不同的手續費代價 —— 的問題也就迎刃而解。這是因為不同的比特幣腳本具有不同的經濟性。
為了維持網絡的去中心化和安全性,比特幣的區塊大小是有限制的,能讓交易體積更小的腳本就有了經濟性上的優勢。
在這一方面,帶來最大變化的當屬 2017 年激活的 “隔離見證(SegWit)” 軟分叉。隔離見證在帶來兩種新的標準化腳本 “P2WPKH” 和 “P2WSH” 的同時,也為這兩種腳本設計了全新的交易驗證模式:
在傳統(Legacy)的比特幣腳本中,用於通過腳本公鑰所定義的驗證程序的數據(比如數字簽名)會被放在交易(scriptSignature
字段)中;這就帶來了所謂的 “交易熔融性” 問題 [7],阻礙了我們用比特幣腳本編程多方參與的應用,甚至會讓錢包完全無法跟蹤交易。
而隔離見證的交易驗證模式,會將這部分數據放在交易之外(witness
字段);而且,隔離見證引入了一種新的度量體積的單位(“virtual byte(vByte)”),放在 witness 字段中的數據,在度量體積時會得到折扣(這是有意的設計,為了讓隔離見證的交易具備比傳統交易更好的經濟性)。
最終的結果是,隔離見證類型的腳本 P2WPKH 和 P2WSH 相比傳統腳本 P2PKH 和 P2SH,具有顯著更好的經濟性:一方面,隔離見證腳本的腳本公鑰更簡潔;另一方面,傳統腳本的簽名放在交易中,隔離見證腳本的簽名放在交易外,即使數據體積相同,後者的 vByte 也更小。
這裡有一張表格,可以說明不同類型的腳本在作為交易的輸入和輸出時,會佔據多大的體積。
- 圖片來自:Optech 限定週刊·等待確認 -
然後,這裡還有一個交易體積計算器,可以告訴你不同數量的某一類型腳本會造成多大體積的交易。
注意:在考慮經濟性時,不能只比較腳本在作為輸入時候的體積,因為,一般來說比特幣交易都會有 “找零輸出”(你為交易提供的資金數量往往大於支付額,因此會把一些錢轉回給自己)。找零輸出通常會使用跟本錢包收款地址相同的類型的腳本。
地址類型
本章節將介紹用戶可能會接觸到的不同類型的地址的特徵和經濟性。
P2PKH
- 使用 Base58 編碼法。以數字 “1” 開頭,長度一般是 34 個字符。
- 用於單簽名錢包。
- 經濟性較差。
- 例子(同上文):
18p3G8gQ3oKy4U9EqnWs7UZswdqAMhE3r8
P2SH
- 使用 Base58 編碼法。以數字 “3” 開頭,長度一般是 34 個字符。
- 用戶最常接觸到的 P2SH 地址實際上是一種被稱作 “Nested SegWit(P2SH)” 的腳本的地址,這個名字的意思是 “封裝了隔離見證腳本的 P2SH 腳本”。
- 能夠實現這種封裝是 P2SH 本身的能耐,但定義這種封裝的根本目的是應對錢包軟件的兼容性問題。由於隔離見證的地址使用了全新的編碼方法,不實現新方法的錢包軟件會將隔離見證地址識別為錯誤輸入、無法從中復原出有效的比特幣腳本。Nested SegWit P2SH 腳本則提供了一種恰當的折中:支付者的錢包(不論升不升級)都會將這樣的地址理解為普通的 P2SH 地址,然後復原出一個 P2SH 腳本、正確構造交易;接收者的後續花費資金時,又可以(憑藉支持隔離見證的錢包軟件)獲得一部分由隔離見證帶來的好處。
- 在同為單簽名錢包時,經濟性比 P2PKH 更好。
- 可用於多簽名錢包(不論是否使用隔離見證特性)。
- 例子:
38Y2PBD1mihxtoVncaSz3oC2vRrjNF8sA2
(這個 P2SH 腳本封裝了跟上文一樣的 P2PKH 腳本,儘管這沒有什麼好處)
P2WPKH
- 原生的隔離見證腳本。使用 Bech32 編碼法,以數字和字母 “bc1q” 開頭,長度是 42 個字符。
- 用於單簽名錢包。
- 經濟性顯著好於 P2PKH,也好於 Nested SegWit P2SH。
- 例子(同上文):
bc1q2kh9z6zvgdp4mf634jxjzuajv5htvsg9ulykp8
P2WSH
- 原生的隔離見證腳本。使用 Bech32 編碼法,以數字和字母 “bc1q” 開頭,長度是 62 個字符。
- 通常用於多簽名錢包。
- 作為多簽名錢包時,經濟性顯著好於 P2SH。
- 例子:
bc1q56cuwyqlmq64aq0y3c8swd8a9gefe4wf7faxe2uyatyahfrly5aq0e6mfc
(這個 P2WSH 腳本封裝了跟上文一樣的 P2PKH 腳本,儘管這沒有什麼好處)
P2TR
- 原生的隔離見證腳本(Taproot 是 “隔離見證 v1”)。使用 Bech32m 編碼法,以 “bc1p” 開頭,長度是 62 個字符。
- 既可用於單簽名錢包,又可用於多簽名錢包。
- 作為單簽名錢包時,經濟性略好於 P2WPKH,但已經幾乎沒有區別(此處是假設是將一個輸入和一個找零輸出作為交易的固有開銷;使用的輸入越多,P2TR 優勢越大)。
- 作為多簽名錢包時,藉助一些 Schnorr 簽名聚合算法的幫助,經濟性可以比 P2WSH 還要好。但在本文撰寫的時間(2024 年 11 月),錢包軟件還很少實現這樣的聚合算法,這是因為這些算法在交互上的複雜性。
- P2TR 與以前的比特幣標準腳本的重大區別在於:原來的腳本都會區分單簽名錢包用戶和高級腳本功能(“智能合約”)的用戶,前者會使用公鑰哈希值腳本,而後者(包括多簽名裝置和閃電通道這樣的高級裝置)會使用贖回腳本哈希值腳本;P2TR 第一次統一了兩者,讓我們無法從 腳本/地址 的外在形態上直接推測其用途。因此,從長遠來看,P2TR 會有更好的隱私性。
- 目前為止,還不是所有錢包都支持 P2TR 地址(但幾乎所有錢包都支持 P2WPKH 和 P2WSH)。用戶的選擇範圍和遷移能力都比較受限。此外,對基於 P2TR 的多簽名裝置的支持更是少之又少。
- 例子(隨機選出):
bc1pxy5r3slcqc2nhc0r5698gmsqwruenj9c8pzmsy5cedp3649wyktstc6z3c
結語
一個地址就代表著一個具體的比特幣腳本;這樣的比特幣腳本是標準化的,憑藉地址中的信息就可以完整復原出來。使用專門的編碼方法,讓地址變得更加緊湊,並具備檢查抄寫錯誤的功能。而不同地址類型的經濟性,就來自於其背後的標準化比特幣腳本的經濟性。
附錄 A. 描述符
在 “地址的概念” 一節,我們已經提到,在兩個場景中,用戶可能需要一種緊湊而可靠的腳本記錄:支付(傳遞)場景和長期保管場景。
而在 “編碼方法” 一節,我們可以看出,這些編碼方法的設計主要基於傳遞過程,而非長期保管場景。那麼,在保管場景中,應如何保存地址?
幸運的是,我們如今有了一種恰當的方法,來表示一組(而非一個)地址,它就是 “輸出(地址)描述符(output descriptor)”。
自比特幣誕生、地址的概念出現以來,自主保管的技術和安全習慣都已改進了很多。一個重大的進步是所謂的 “層級確定式(HD)錢包”,其理念是用一段秘密材料按確定式隨機算法推導出許多私鑰,進而得出許多地址,從而一方面能夠滿足 “不重複使用地址” 的安全習慣,又能儘可能減少備份私鑰的負擔。
描述符也基於這一概念,它的做法是,將地址的類型以及生成這組地址的步驟用明文表示出來,再加上校驗和。例如:
wpkh([8b47f816/84h/0h/0h]xpub6C8vwWQ[...]NgW2SnfL/<0;1>/*)#c38kz2nr
從上面這段文字中,我們可以看出,它表示的是一組 P2WPKH 地址,而用在這組地址中的公鑰,則是從一個指紋為 8b47f816
的主公鑰中根據 84h/0h/0h
BIP32 派生路徑中派生出來的;並且,使用 0
和 1
的派生路徑來區分收款地址和找零地址。最後, c38kz2nr
是校驗和,可以校驗有無抄寫錯誤。
這樣的字符串非常適合長期保管,也非常適合用於錢包遷移,因為它已將生成這組地址的過程完整地描述了。
腳註
1. https://en.bitcoin.it/wiki/Script#Script_examples ↩
2. https://learnmeabitcoin.com/technical/keys/base58/ ↩
3. https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki ↩
4. https://en.wikipedia.org/wiki/BCH_code ↩
5. https://github.com/satoshilabs/slips/blob/master/slip-0173.md ↩
6. https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki ↩
7. https://www.btcstudy.org/2022/10/07/segregated-witness-benefits/#%E4%BF%AE%E5%A4%8D%E7%86%94%E8%9E%8D%E6%80%A7%E9%97%AE%E9%A2%98 ↩