作者:Anony,BTC Study
用戶接觸比特幣的時候,往往第一時間就會遇到「地址」 這個概念。當你嘗試收取比特幣付款時,你需要提供自己的地址。在區塊瀏覽器中查詢付款是否已經到帳時,往往也以具體的地址為搜尋條件。
你可能會以為:「地址就等於比特幣世界裡的銀行帳號,可以用來接收比特幣」。但這種理解,在面對錢包使用過程中的一些情形時,可能還是會讓你犯迷糊。比如說:在初次使用一款比特幣軟體錢包時,它可能會請你選擇一種地址“類型”,如:“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 個位元組(十六進位的8b, ),作為後綴,得到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 要更簡單、更抽象Bech2;編碼的位址是: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 ↩