HD錢包學習小結

來源:互聯網
上載者:User

在比特幣/以太坊等公鏈上都會用到錢包。錢包主要用來系統管理使用者的私密金鑰,及使用者在鏈上的數字貨幣,即用私密金鑰對交易進行簽名。私密金鑰可用於產生特定訊息的簽名,此簽名可以在不泄露私密金鑰的情況下使用公開金鑰進行驗證。

因為私密金鑰極其重要,一旦泄漏就意味著數字資產的所有權就掌握在別人手裡。理論上私密金鑰可以是任意的一串隨機數字串,不僅難以記憶也沒有規律可循,有必要利用一些密碼學方法來管理秘鑰對(一個秘鑰對包括一個私密金鑰和對應的公開金鑰),既方便管理又足夠安全。

1. 非確定性錢包

如果只是完全隨機產生一個數字串作為私密金鑰,可以使用密碼學安全的偽隨機數產生器(CSPRNG,Cryptographically secure pseudorandom number generator,密碼學安全偽隨機數產生器)。這些私密金鑰之間完全獨立,相應的公開金鑰也毫無關聯,管理這樣的秘鑰對的錢包叫做非確定性錢包(nondeterministic wallet)。早期比特幣地址非確定性錢包. 非確定性錢包最大的麻煩是秘鑰對匯入匯出時,必須逐個操作錢包中的所有秘鑰對。

2. 確定性錢包

為了方便應用, 針對非確定性錢包的這些問題,提出了一種金鑰組的產生方法:金鑰組由一個原始的種子主要金鑰推導而來。最常見的推導方式是樹狀層級推導 (hierarchical deterministic) 簡稱 HD。這種方法產生的錢包秘鑰對也叫確定性錢包(deterministic wallet)。

通過一個共同的種子可以推匯出n 多私密金鑰,種子推導私密金鑰採用無法復原雜湊演算法。在需要備份錢包私密金鑰時,只備份這個種子即可(大多數情況下為了方便抄寫,種子由12個的助記詞產生),錢包只需匯入助記詞即可匯入全部的私密金鑰。HD 錢包能夠在不需知道私密金鑰的前提下產生大量的公開金鑰,這個特性非常適用於只負責收款的服務。

我們從HD錢包的產生關係來分別介紹:

熵(128位)→助記詞(12個)→種子(512位)→私密金鑰→公開金鑰→地址。

2.1 助記詞和熵

顧名思義, 助記詞是為了方便記錄一長串無規律的數字串而映射為方便抄寫和記憶的助記詞。因為助記詞庫中一共有2048個助記詞,因此11位長度(2^11=2048)的索引就可以定位到全部的助記詞. 記住這n個助記詞以及他們的順序,就可以將它們的索引值組合成(n11)位長度*的數字串。

反過來說,將(n*11)位長度的數字串切割成n份,每份長度11位,分別作為助記詞的索引,根據索引從助記詞庫中獲得助記詞並永久記錄。

下面是產生這個n*11的數字串,並轉換為助記詞的過程:


image.png
  1. 產生一個長度為128/160/192/224/256位 (bits) 的隨機序列數字串,稱為
  2. 對熵進行hash,取hash後資料串的前4/5/6/7/8位作為校正和 (長度=熵長度/32);
  3. 將熵和校正和進行組合,即總長度是132/165/198/231/264位;
  4. 將上述結果進行每 11 位切割,得到12/15/18/21/24個助記詞索引;
  5. 根據助記詞索引匹配助記詞庫的詞,得到完整的一串助記詞;

以長度為128位的熵為例表示上面的過程:

[圖片上傳失敗...(image-4d4e04-1532831639402)]

2.2 種子

通過助記詞可以推匯出長度為128至256位的熵。通過PBKDF2函數可以將熵匯出較長的(512位)種子

注: PBKDF2(Password-Based Key Derivation Function 2)是常用的 key stretching 演算法中的一種。在密碼學中,Key stretching 技術被用來增強弱密鑰的安全性,增加了暴力破解 (Brute-force attack) 對每個可能密鑰嘗試攻破的時間,增強了攻擊難度。其基本原理是通過一個為隨機函數(例如 HMAC 函數),把明文和鹽值作為輸入參數,然後重複進行運算最終產生密鑰。如所示:

image.png

PBKDF2函數的實現如下:

DK = PBKDF2(PRF, Password, Salt, c, dkLen)實現:DK = T1 || T2 || ... || Tdklen/hlenTi = F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ UcU1 = PRF(Password, Salt || INT_32_BE(i))U2 = PRF(Password, U1)...Uc = PRF(Password, Uc-1)

該函數有幾個輸入參數:

  • PRF:是一個偽隨機函數,例如HMAC-SHA512函數,它會輸出長度為hLen的結果。
  • Password:用來生產秘鑰的原文,即助記片語成的字串
  • "mnemonic" + 使用者輸入的密碼passphrase作為salt,密碼是可選的。
  • c: 重複計算的次數,比如2048
  • dkLen:輸出秘鑰長度

2.3 主私密金鑰和主鏈碼

從根種子可以產生主要金鑰 (master key) 和主鏈碼 (master chain code)。計算方法很簡單,根種子通過HMAC-SHA512Function Compute一次, 左256位就是主私密金鑰,右256位就是主鏈碼,主私密金鑰通過橢圓曲線演算法推到出主公開金鑰,主公開金鑰和主私密金鑰組成主秘鑰對。主鏈碼作為推導下級密鑰的

image.png

私密金鑰產生公開金鑰的演算法參見下面的橢圓曲線演算法小節。這裡主公開金鑰長度是264位,是因為格式是壓縮格式公開金鑰(首碼(8位)+x軸方向座標(256位)),見下面小節介紹。

2.4 子密鑰

從父密鑰(parent keys)可以推導子密鑰(child keys),CKD 函數對下面三個輸入做單向散列雜湊

  • 父密鑰(父私密金鑰或父公開金鑰), 如果輸入父私密金鑰,也會轉換為父公開金鑰
  • 鏈碼作為熵
  • 索引序號

計算方法見下面以太坊hd錢包中的介紹.

image.png

索引號個數為2^32,每個父級密鑰能推匯出該數目一半的子密鑰 (索引號從 0x00 到 0x7fffffff (0~2^31-1) 會產生正常的密鑰;索引號從 0x80000000 到 0xffffffff 會產生增強密鑰)。

推導採用無法復原的 HMAC-SHA512 無法復原密碼編譯演算法,子密鑰不能向上推匯出父密鑰、同時也不能水平推匯出同一級的密鑰。產生的512位元據的左256位作為子私密金鑰,右256位作為子鏈碼

2.5 擴充密鑰

CKD 推導子密鑰的三個元素中,其中父密鑰和鏈碼結合統稱為擴充密鑰 (Extended keys)。

  1. 包含私密金鑰的擴充密鑰用以推導子私密金鑰,從子私密金鑰又可推導對應的公開金鑰和比特幣地址;
  2. 包含公開金鑰的擴充密鑰用以推導子公開金鑰

擴充密鑰使用 Base58Check編碼時會加上特定的首碼編碼:

  • 包含私密金鑰的首碼為 xprv
  • 包含公開金鑰的擴充密鑰首碼為 xpub

相比比特幣的公私密金鑰,擴充密鑰編碼之後得到的長度為 512 或 513 位。

2.6 子公開金鑰

HD 錢包非常好用的特徵之一就是在隱藏私密金鑰的前提下通過公開金鑰推匯出子公開金鑰,極大加強安全性。在只需要產生地址接受比特幣而無需消費的情境下非常有用,通過公開金鑰擴充密鑰能產生無窮盡的公開金鑰和比特幣地址。子公開金鑰推導流程如下:

image.png

這種方式可以用來創造非常保密的public-key-only公開金鑰。可以用來接收比特幣但不可以花這個地址裡的任何比特幣。與此同時,在另一種更保險的伺服器上,擴充私密金鑰可以衍生出所有的對應的可簽署交易以及花錢的私密金鑰。

註:分別推匯出的子公開金鑰和子私密金鑰是一對秘鑰. 在演算法實現中, 子公開金鑰的推導需要先計運算元私密金鑰, 再算出子公開金鑰.

2.7 增強擴充密鑰

密鑰需加強保管以免泄漏,泄漏私密金鑰意味著對應的地址上的幣可被轉走、泄漏公開金鑰意味著 HD 錢包的隱私被泄漏。增強密鑰推導 (Hardened child key derivation) 解決下述兩個問題:

  1. 雖然泄漏公開金鑰並不會導致丟幣,但含有公開金鑰的擴充密鑰泄漏會導致以此為根節點推匯出來的擴充公開金鑰全部泄漏,一定程度上破壞了隱私性。
  2. 如果泄漏擴充公開金鑰(包含有鏈碼)和子私密金鑰,就可以被用來衍生所有的其他子私密金鑰,因為可以通過遍曆索引獲得子鏈碼。更糟糕的是,子私密金鑰與母鏈碼可以用來推斷母私密金鑰。

於此,BIP32 協議把 CKD 函數改為 HKD (hardened key derivation formula) 產生增強密鑰推導函數。“打破”了父公開金鑰以及子鏈碼之間的關係。HKD幾乎與一般的衍生的子私密金鑰相同,不同的是父私密金鑰被用作輸入而不是父公開金鑰。

CKD 函數是從擴充密鑰的序號 ( 0x00 到 0x7fffffff)、父鏈碼和父公開金鑰生推匯出子鏈碼和子公開金鑰,子私密金鑰從父私密金鑰推導;而 HKD 通過父私密金鑰、父鏈碼和增強擴充密鑰的序號 (0x80000000 到 0xffffffff) 推導增強子私密金鑰和增強子鏈碼。

image.png

3. 橢圓曲線演算法

通過橢圓曲線演算法可以從私密金鑰計算得到公開金鑰,這是無法復原轉的過程, 公式如下:K = k * G 。其中k是私密金鑰,G是被稱為產生點的常數點,所有比特幣使用者的產生點是相同的,而K是所得公開金鑰。其反向運算,被稱為“尋找離散對數”——已知公開金鑰K來求出私密金鑰k——是非常困難的,就像去實驗所有可能的k值,即暴力搜尋。

使用的secp256k1標準所定義的一條特殊的橢圓曲線如下:

image.png

對應的公式是

y^2 mod p = (x^3 + 7) mod p其中 p = 2^256 – 2^32 – 2^9– 2^8 – 2^7 – 2^6 – 2^4 – 1

的x,y是定義在實數範圍內的,如果x,y全部取整數,那隻有一些離散的座標值才符合secp256k1橢圓曲線,例如當p=17時,x,y的離散座標圖如下:

image.png

定義橢圓曲線座標的加法:給定橢圓曲線上的兩個點 P1(x1,y1) 和 P2(x2,y2),則橢圓曲線上必定有第三點 P3(x3,y3) = P1 + P2。幾何圖形中,該第三點 P3 可以在 P1 和 P2 之間畫一條線來確定。這條直線恰好與橢圓曲線上的一點相交。此點記為 P3'=(x,y)。然後,在 x 軸做映射獲得P3=(x,-y)。橢圓曲線座標的乘法很好理解,分解為多個加法即可。

image.png

4. 公開金鑰

公開金鑰是在橢圓曲線上的一個點,由一對座標(x,y)組成。

公開金鑰通常表示為首碼04緊接著兩個256位元的數字。其中一個256位元數字是公開金鑰的x座標,另一個256位元數字是y座標。例如:

K = 04F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB

為什麼在座標地址前有首碼04?因為首碼04是用來表示非壓縮格式公開金鑰,即具有完整的x,y座標, 而壓縮格式公開金鑰是以02或者03開頭。

4.1 壓縮格式公開金鑰

引入壓縮格式公開金鑰是為了減少比特幣交易的位元組數,從而可以節省那些運行區塊鏈資料庫的節點磁碟空間。橢圓曲線上的點實際是數學方程的一個解。因此,如果我們知道了公開金鑰的x座標,就可以通過解方程 y2 mod p = (x3 + 7) mod p 得到y座標。這種方案可以讓我們只儲存公開金鑰的x座標,略去y座標,從而將公開金鑰的大小和儲存空間減少了256位元,大大節省了很多資料轉送和儲存。

壓縮格式公開金鑰為什麼有02或03兩個首碼?因為y的解是來自於一個平方根,有正負。而y座標可能是奇數或者偶數,分別對應正負。所以在產生壓縮格式公開金鑰時,如果y是偶數,則使用02作為首碼;如果y是奇數,則使用03作為首碼。上面的K如果用壓縮格式表示就是:

K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A

但是存在一個問題:一個私密金鑰可以產生兩種不同格式的公開金鑰——壓縮格式和非壓縮格式,產生兩個不同的比特幣地址。目前較新的比特幣用戶端的預設格式採用壓縮格式公開金鑰.

從錢包中匯出私密金鑰時,有2種WIF格式(Wallet Import Format):

  • 新版比特幣用戶端只能匯出為以K或L為首碼的Base58編碼的WIF壓縮格式的的私密金鑰。
  • 較老的沒有實現壓縮格式公開金鑰的錢包,匯出為以5為首碼的Base58編碼的WIF格式的私密金鑰。
  • 對於私密金鑰的16進位原文,上面2種匯出格式區別是WIF壓縮格式的私密金鑰被加了尾碼01。

舉例如下:

私密金鑰hex:1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD對應WIF格式:5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn私密金鑰Hex-compressed:1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD01對應WIF-compressed:KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

base58編碼見下面小節的介紹。

5. 賬戶地址

5.1 比特幣地址

比特幣地址是一個由數字和字母組成的字串,以數字“1”開頭。例如:

1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy

通過公開金鑰產生地址的演算法如下:

ADDR = RIPEMD160(SHA256(PUBKEY))  //雙hash

為了提高了可讀性、避免歧義並有效防止了在地址轉錄和輸入中產生的錯誤。比特幣地址還要經過“Base58Check”編碼。

ACCOUNT_ADDR = Base58Check(ADDR)

5.2 編碼

Base64使用了26個小寫字母、26個大寫字母、10個數字以及2個符號(例如“+”和“/”),通常用於編碼郵件中的附件。Base58是Base64編碼格式的子集,同樣使用大小寫字母和10個數字,但不包含不含Base64中的0(數字0)、O(大寫字母o)、l(小寫字母L)、I(大寫字母i),以及“+”和“/”兩個字元。Base58的字母表是:

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

Base58Check是一種常用在比特幣中的Base58編碼格式,增加了錯誤校正碼來檢查資料在轉錄中出現的錯誤。校正碼長4個位元組,添加到需要編碼的資料之後。校正碼是從需要編碼的資料的雜湊值中得到,所以可以用來檢測並避免輸入中產生的錯誤。

在編碼之前,首先我們要對資料添加一個稱作“版本位元組”的首碼,這個首碼用來明確需要編碼的資料的類型。首碼如下:

image.png

注意:

  • 同一個密鑰被不同的格式編碼後,雖然結果看起來可能不同,但是密鑰所編碼數字原文並沒有改變。
  • 添加首碼的數字串不限於比特幣地址, 例如私密金鑰/公開金鑰。

校正碼的計算方法如下:

checksum = SHA256(SHA256(prefix+data))

取結果的前4個位元組作為校正碼。這樣得到3個部分:首碼、資料和校正碼,再採用之前描述的Base58字母表編碼:

image.png

至此,完成了比特幣地址的全部產生過程。

6. 多幣種和多帳戶

讓同一個 seed 可以支援多幣種、多帳戶等。各層定義如下:

m / purpose' / coin_type' / account' / change / address_index

其中的 purporse' 固定是 44',代表使用 BIP44。而 coin_type' 用來表示不同幣種,例如 Bitcoin 就是 0',Ethereum 是 60'

7. 以太坊確定性錢包

有一個golang實現的以太坊確定性錢包的項目: https://github.com/shiqinfeng1/go-ethereum-hdwallet .

該項目提供錢包相關的介面有:

NewMnemonic(bits): 通過熵產生BIP-39助記詞,bits一般等於128位NewSeedFromMnemonic(mnemonic):使用助記詞產生BIP-39種子,不帶密碼NewSeed(): 不使用助記詞,通過rand包直接產生512位長度的BIP-39種子NewSeedFromMnemonic(mnemonic): 助記詞轉換為種子NewFromSeed(seed): 通過種子建立一個新的錢包NewFromMnemonic(mnemonic): 通過助記詞建立一個新的錢包

錢包結構的定義:

type Wallet struct {    mnemonic  string   //助記詞(可選)    masterKey *hdkeychain.ExtendedKey   //主秘鑰,包含主私密金鑰和主鏈碼    seed      []byte  //種子    url       accounts.URL //HD錢包的產生路徑    paths     map[common.Address]accounts.DerivationPath //每個賬戶的派生路徑    accounts  []accounts.Account  //儲存所有當前錢包的賬戶    stateLock sync.RWMutex   //錢包操作鎖}

7.1 產生錢包

首先產生種子, 可以直接產生,或者根據熵和助記詞產生.

根據種子通過hdkeychain.NewMaster(seed)產生主私密金鑰和主鏈碼, 對種子進行一次hash操作:

HMAC-SHA512(Key = "Bitcoin seed", Data = Seed)

賬戶派生路徑DerivationPath的格式是這樣的:

m / purpose' / coin_type' / account' / change / address_index

其中:

  • purpose=44或0x8000002C(表示數字加密貨幣)
  • coin_type=60或0x8000003C*(表示是以太坊)

所以以太坊的根路徑是 m/44'/60'/0'/0

7.2 錢包介面

Wallet錢包提供的介面如下:

Accounts(): 返回當前錢包所有賬戶列表Contains(account): 檢查指定賬戶是否在本錢包裡Unpin(account): 取消固定指定的賬戶: 從錢包中刪除該賬戶Derive(path, pin): 根據path派生一個賬戶地址: path->派生私密金鑰->公開金鑰->地址PrivateKey(account): 擷取賬戶私密金鑰PublicKey(account): 擷取賬戶公開金鑰Address(account): 擷取賬戶地址Path(account): 賬戶路徑SignHash(account, hash): 簽名hashSignTx(account, tx, chainID): 簽名交易

錢包介面中,最重要的是派生Derive.

派生子私密金鑰和子公開金鑰的演算法使用同一個介面: ExtendedKey.Child(i), 派生出指定索引的子key. 如果輸入的索引大於0x80000000, 說明派生增強key, 否則就是派生普通的key.

  • 只有私密金鑰可以派生增強子私密金鑰; 公開金鑰不可以派生增強子公開金鑰; 子公開金鑰的派生不需要知道母私密金鑰, 直接從母公開金鑰就可以派生;

子秘鑰也是通過下面HMAC-SHA512函數獲得:

HMAC-SHA512(Key = chainCode, Data = data)
  • chainCode即為父key的鏈碼

  • 強化子私密金鑰的data格式是: 0x00 || ser256(parentKey) || ser32(i);

  • 普通子私密金鑰/子公開金鑰的data格式是: serP(parentPubKey) || ser32(i);

    注意: 這裡子私密金鑰也是採用的母公開金鑰作為參數.

  • 計算結果是512位長度的數字串I, 其中:

    • 通過左256位Il計運算元私密金鑰: childKey = parse256(Il) + parentKey ,
    • 通過左256位Il計運算元公開金鑰: childKey = serP(point(parse256(Il)) + parentKey). 其實就是將IL作為私密金鑰,計算了公開金鑰座標代入.
    • 右256位Ir作為子鏈碼 .

附:

image.png
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.