分層確定性錢包的生成過程比較複雜一些。需要用一個系列來闡述。
我打算用golang來實現整個過程。
首先來實現產生助記詞。很多錢包軟件,如Electrum在創建新錢包的時候,都會給用戶自動生成助記詞,並讓用戶手寫記錄並保管好助記詞。下面我們也來實現這個過程。
助記詞的生成有一定的規則,不是隨便挑的幾個詞組合在一起。下面是它的生成規則介紹。
| ENT | CS | ENT+CS | MS |
+-------+----+--------+------+
| 128 | 4 | 132 | 12 |
| 160 | 5 | 165 | 15 |
| 192 | 6 | 198 | 18 |
| 224 | 7 | 231 | 21 |
| 256 | 8 | 264 | 24 |
- 第一列表示熵的位數,這個熵是一個隨機數,它的長度(單位是bit)必須是32的整數倍。例如這裏第一行要生成12個助記詞,就用128bit的熵。
- 第二列CS是單詞checksum的縮寫,它的值是用ENT/32算出來的。
- 第三列就是用前面兩列的值相加得到。
- 最後一列表示助記詞的個數,這個值是用第三列的值除以11得到,也就是說每個助記詞用11bit來表示。這樣的話,助記詞的總個數就等於2^11=2048。
所有助記詞在BIP39裏規定了。可以看到助記詞不僅有英語單詞,還可以有漢字,日文等等。
值得一說的是這裏面的英語單詞挑選有個特點:根據單詞的前四個字母就能唯一地確定這個單詞。根據這個特點,一些產品如助記詞密盒只需要給每個助記詞擺放4個字母塊就可以了。
下面是golang實現隨機生成助記詞的過程:
package hdwallet
import (
"bufio"
"crypto/rand"
"crypto/sha256"
"errors"
"github.com/ethereum/go-ethereum/common/math"
"math/big"
"os"
)
// 讀取助記詞到順序列表
func words2List() ([]string, error) {
var (
f *os.File
err error
wordlist []string
)
if f, err = os.Open("C:\\Users\\ASUS\\go\\src\\generateAddress\\english.txt"); err != nil {
return wordlist, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan(){
wordlist = append(wordlist, scanner.Text())
}
return wordlist,nil
}
// checkSumBitLen 範圍[4 5 6 7 8]
func entropy2Mnemonics(entropy *big.Int, checkSumBitLen uint8) ([]string, error) {
//參數校驗
if entropy == nil || !(checkSumBitLen <= 8 && checkSumBitLen%4 == 0) {
return nil, errors.New("invalid parameters")
}
var (
err error
hash256 [32]byte
checkSumMask byte
checkSum byte
wordlist []string
pieceMask *big.Int
mnemonicWords []string
index big.Int
wordAmount uint32
)
hash256 = sha256.Sum256(entropy.Bytes())
if checkSumBitLen == 8 {//解決1左移8位導致byte溢出的問題
checkSumMask = 0xff
} else {
checkSumMask = ^((1<<checkSumBitLen)-1)
}
checkSum = (hash256[0]&checkSumMask)>>(8-checkSumBitLen)
entropy.Lsh(entropy, uint(checkSumBitLen))
entropy.Or(entropy, big.NewInt(int64(checkSum)))
//讀取單詞列表
if wordlist,err = words2List();err != nil {
return nil,err
}
//開始取字符串
pieceMask = big.NewInt(2047)//11bit全是1
wordAmount = uint32((entropy.BitLen()+int(checkSumBitLen))/11)
mnemonicWords = make([]string, wordAmount)
for i:=uint32(0); i<wordAmount; i++ {
//每11bit值取出來,當做助記詞在wordlist裏的下標
mnemonicWords[wordAmount-i-1] = wordlist[index.And(entropy, pieceMask).Int64()]
entropy.Rsh(entropy, 11)
}
return mnemonicWords,nil
}
// num表示助記詞個數[12,15,18,21,24]
func NewMnemonics(num uint8) ([]string, error) {
//參數校驗
if !(num >= 12 && num <=24 && num%3==0) {
return nil, errors.New("invalid parameter num")
}
var (
entropy *big.Int
entropyLenInBits = 32*(num/3)
entropyMax *big.Int
err error
)
entropyMax = new(big.Int).Sub(math.BigPow(2, int64(entropyLenInBits)), big.NewInt(1))
if entropy,err = rand.Int(rand.Reader, entropyMax); err != nil {
return nil, err
}
return entropy2Mnemonics(entropy, entropyLenInBits/32)
}
下面是測試代碼:
package hdwallet
import (
"encoding/hex"
"math/big"
"reflect"
"strings"
"testing"
)
func TestEntropy2Mnemonics(t *testing.T) {
t.Run("test use entropy to generate mnemonic words", func(t *testing.T) {
var words []string
var err error
var randDigit []byte
if randDigit,err = hex.DecodeString("5e91c0b063824bc984073120461d4fa3"); err != nil {
t.Error(err)
return
}
if words, err = entropy2Mnemonics(new(big.Int).SetBytes(randDigit),4); err!= nil {
t.Error(err)
return
}
want := strings.Split("future mix clown shove caution tooth avoid tower cake couch fault elder", " ")
if !reflect.DeepEqual(want, words) {
t.Error("generate mnemonic words error")
t.Error("want: ",want)
t.Error("got: ", words)
return
}
})
}
這裏也可以把助記詞寫在golang源碼裏面,參考:https://github.com/lilianwen/go-bip39/blob/master/wordlists/english.go
(全文完)
參考鏈接:
https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki