HD錢包私鑰、公鑰、地址生成流程


轉載請注明:https://www.cnblogs.com/tkblack/p/12625510.html

前面了解了助記詞生成流程,現在完善下整個助記詞到地址的完整流程。

其實在很長一段時間里,我都認為助記詞和種子是同一個概念,但並非如此,一個叫mnemonic(助記符或助記詞),一個叫seed(種子)。

1.首先由助記詞生成種子,bip39使用PBKDF2來推出,這個過程具有不可逆性,種子不能推出助記詞;

  有必要說一下PBKDF2這個函數,簡單點說就是對 “助記詞+鹽” 做很多次hmac_sha512,顯然,這個過程是不可逆的。這里的鹽是為了增加安全性,一般的錢包不會讓用戶去定義,而是由開發者固定。所以,不同的錢包導入相同的助記詞可能生成不同的種子,進而產生不同的私鑰。具體做了所少次hmac_sha512計算呢?網上一度說是2048次,參照下面的代碼分析下,兩層for循環,第一層63次,第二層2047次(最后一個for就不考慮了)。因此我的理解是,63 * (2047 + 1)次,我估計說2048是因為傳入參數iter的值是2048。

func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {  // 參數依次為:助記詞、鹽、2048、64、sha512.New
    prf := hmac.New(h, password)
    hashLen := prf.Size()
    numBlocks := (keyLen + hashLen - 1) / hashLen

    var buf [4]byte
    dk := make([]byte, 0, numBlocks*hashLen)
    U := make([]byte, hashLen)
    for block := 1; block <= numBlocks; block++ {
        // N.B.: || means concatenation, ^ means XOR
        // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
        // U_1 = PRF(password, salt || uint(i))
        prf.Reset()
        prf.Write(salt)
        buf[0] = byte(block >> 24)
        buf[1] = byte(block >> 16)
        buf[2] = byte(block >> 8)
        buf[3] = byte(block)
        prf.Write(buf[:4])
        dk = prf.Sum(dk)
        T := dk[len(dk)-hashLen:]
        copy(U, T)

        // U_n = PRF(password, U_(n-1))
        for n := 2; n <= iter; n++ {
            prf.Reset()
            prf.Write(U)
            U = U[:0]
            U = prf.Sum(U)
            for x := range U {
                T[x] ^= U[x]
            }
        }
    }
    return dk[:keyLen]
}

 

2.上一步,得到了64字節的返回,我們將前32字節作為私鑰,后32字節作為鏈碼;

  當然,我們實際看到的私鑰是經過組裝和編碼的。具體組裝:版本(0x80)+ 私鑰原文 + 壓縮標志(0x01,非壓縮私鑰不填充)+ 校驗碼(4個字節);這里,通過對私鑰原文作2次sha256,對結果取前4個字節作為校驗碼。由此可知,非壓縮私鑰37字節,壓縮私鑰38字節,因此,進行BASE58編碼后,一般為51(非壓縮)或52(壓縮)個字符。

 

3.事實上,一個私鑰僅生成一個公鑰,再生成一個地址,但是我們的錢包卻可以產生很多地址。這就和bip32相關了,bip32通過使用 公鑰或私鑰 + 鏈碼 + 索引號 (索引號占32位)來生成子私鑰,而子私鑰又能生成孫私鑰,這樣類推,就可以產生很多私鑰。一個私鑰可以生成2的32次方個子私鑰;索引號小於0x80000000為普通型,采用公鑰+鏈碼+索引號生成子私鑰,大於等於0x80000000為增強型,采用私鑰+鏈碼+索引號生成子私鑰。

 

4.公鑰或私鑰+鏈碼+索引號,進行hmac_sha512運算,得到一個64字節的輸出,將前32字節作為新的私鑰,后32字節作為新的鏈碼;

 

5.私鑰生成公鑰,采用ecc算法生成,使用secp256k1曲線推導,推導公式:K = k * G   (k表示私鑰,K表示公鑰,G表示基點);

  這里不介紹ecc的具體原理,簡單說下,就是重定義加法和乘法運算。假設以上公式,k=3,K= G + G + G (滿足結合律、交換律);而算法中 P+Q 的結果為經過P和Q的直線(如果Q=P,則是過P點的切線)和secp256k1曲線的另一個交點關於X軸的對稱點,所以其實K就是曲線上一個點。

  前面講到私鑰分為壓縮和非壓縮,所以公鑰也有壓縮和非壓縮形式。我們已經知道K是一個點K(x,y),非壓縮私鑰: 版本前綴(0x04) + Kx + Ky;顯然,我們可以由Kx推導出Ky(因為曲線已經確定),所以我們僅使用Kx就夠了,壓縮私鑰:版本前綴(0x02或0x03) + Kx。需要02和03兩個前綴的原因,因為y值可能為偶數,也可能為奇數,因此定義兩個前綴用以區分,02表示偶,03表示奇。現在基本都使用壓縮公鑰。這里,我在想如果壓縮公鑰取Ky,那么就不需要定義2個前綴了。

 

6.公鑰生成地址,使用sha256和ripemd160算法;

  首先對公鑰進行sha256運算,再進行ripemd160運算,記錄下該值,假設為R;

  對R進行2次sha256運算,結果取前4個字節作為校驗碼,假設為check;

  拼接:版本前綴(0x00) + R + check,這就是完整的地址了,當然,我們知道R其實就是地址,只是后面我們又附加了一些信息,最后也是BASE58編碼得到我們常見形式的地址。

 

整個過程就是這個樣子,寫得有點亂,如果有什么錯誤或描述不准確的地方,歡迎指正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM