在比特幣的鏈上,實際上并沒有賬戶的概念,某個用戶持有的比特幣,實際上是其控制的一組UTXO,而這些UTXO可能是相同的地址(對應相同的私鑰),也可能是不同的地址(對應不同的私鑰)。 出于保護隱私的目的,同一用戶如果控制的UTXO其地址都是不同的,那么很難從地址獲知某個用戶的比特幣持幣總額。但是,管理一組成千上萬的地址,意味著管理成千上萬的私鑰,管理起來非常麻煩。 能不能只用一個私鑰管理成千上萬個地址?實際上是可以的。雖然橢圓曲線算法決定了一個私鑰只能對應一個公鑰,但是,可以通過某種確定性算法,先確定一個私鑰k1,然后計算出k2、k3、k4……等其他私鑰,就相當于只需要管理一個私鑰,剩下的私鑰可以按需計算出來。 這種根據某種確定性算法,只需要管理一個根私鑰,即可實時計算所有“子私鑰”的管理方式,稱為HD錢包。 HD是Hierarchical Deterministic的縮寫,意思是分層確定性。先確定根私鑰root,然后根據索引計算每一層的子私鑰: ![]() 對于任意一個私鑰k,總是可以根據索引計算它的下一層私鑰kn: kn=hdkey(k, n) 即HD層級實際上是無限的,每一層索引從0~232,約43億個子key。這種計算被稱為衍生(Derivation)。 k_n=hdkey(k,\;n)即HD層級實際上是無限的,每一層索引從0~232,約43億個子key。這種計算被稱為衍生(Derivation)。 現在問題來了:如何根據某個私鑰計算下一層的子私鑰?即函數hdkey(k, n)如何實現? HD錢包采用的計算子私鑰的算法并不是一個簡單的SHA-256,私鑰也不是普通的256位ECDSA私鑰,而是一個擴展的512位私鑰,記作xprv,它通過SHA-512算法配合ECC計算出子擴展私鑰,仍然是512位。通過擴展私鑰可計算出用于簽名的私鑰以及公鑰。 簡單來說,只要給定一個根擴展私鑰(隨機512位整數),即可計算其任意索引的子擴展私鑰。擴展私鑰總是能計算出擴展公鑰,記作xpub: ![]() 從xprv及其對應的xpub可計算出真正用于簽名的私鑰和公鑰。之所以要設計這種算法,是因為擴展公鑰xpub也有一個特點,那就是可以直接計算其子層級的擴展公鑰: ![]() 因為xpub只包含公鑰,不包含私鑰,因此,可以安全地把xpub交給第三方(例如,一個觀察錢包),它可以根據xpub計算子層級的所有地址,然后在比特幣的鏈上監控這些地址的余額,但因為沒有私鑰,所以只能看,不能花。 因此,HD錢包通過分層確定性算法,實現了以下功能:
從理論上說,擴展私鑰的層數是沒有限制的,每一層的數量被限制在0~232,原因是擴展私鑰中只有4字節作為索引,因此索引范圍是0~232。 通常把根擴展私鑰記作m,子擴展私鑰按層級記作m/x/y/z等: ![]() 例如,m/0/2表示從m擴展到m/0(索引為0)再擴展到m/0/2(索引為2)。 安全性HD錢包給私鑰管理帶來了非常大的方便,因為只需要管理一個根擴展私鑰,就可以管理所有層級的所有衍生私鑰。 但是HD錢包的擴展私鑰算法有個潛在的安全性問題,就是如果某個層級的xprv泄露了,可反向推導出上層的xprv,繼而推導出整個HD擴展私鑰體系。為了避免某個子擴展私鑰的泄漏導致上層擴展私鑰被反向推導,HD錢包還有一種硬化的衍生計算方式(Hardened Derivation),它通過算法“切斷”了母擴展私鑰和子擴展私鑰的反向推導。HD規范把索引0~231作為普通衍生索引,而索引231~232作為硬化衍生索引,硬化衍生索引通常記作0'、1'、2'……,即索引0'=231,1'=231+1,2'=231+2,以此類推。 因此,m/44'/0表示的子擴展私鑰,它的第一層衍生索引44'是硬化衍生,實際索引是231+44=2147483692。從m/44'/0無法反向推導出m/44'。 在只有擴展公鑰的情況下,只能計算出普通衍生的子公鑰,無法計算出硬化衍生的子擴展公鑰,即可計算出的子擴展公鑰索引被限制在0~231。因此,觀察錢包能使用的索引是0~231。 BIP-32比特幣的BIP-32規范詳細定義了HD算法原理和各種推導規則,可閱讀此文檔以便實現HD錢包。 小結HD錢包采用分層確定性算法通過根擴展私鑰計算所有層級的所有子擴展私鑰,繼而得到擴展公鑰和地址; 可以通過普通衍生和硬化衍生兩種方式計算擴展子私鑰,后者更安全,但對應的擴展公鑰無法計算硬化衍生的子擴展公鑰; 通過擴展公鑰可以在沒有擴展私鑰的前提下計算所有普通子擴展公鑰,此特性可實現觀察錢包。 |
|