比特幣分層確定性錢包實現探究——01生成助記詞

分層確定性錢包的生成過程比較複雜一些。需要用一個系列來闡述。

我打算用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  |
  1. 第一列表示熵的位數,這個熵是一個隨機數,它的長度(單位是bit)必須是32的整數倍。例如這裏第一行要生成12個助記詞,就用128bit的熵。
  2. 第二列CS是單詞checksum的縮寫,它的值是用ENT/32算出來的。
  3. 第三列就是用前面兩列的值相加得到。
  4. 最後一列表示助記詞的個數,這個值是用第三列的值除以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

https://zhuanlan.zhihu.com/p/108276306

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章