Skip to content

Commit eac127d

Browse files
committed
Add BIP39 key derivation
1 parent beb9994 commit eac127d

File tree

6 files changed

+2368
-8
lines changed

6 files changed

+2368
-8
lines changed

bip39/bip39.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Package bip39 is the Golang implementation of the BIP39 spec.
2+
// This code was copied from https://github.com/tyler-smith/go-bip39 which is
3+
// also MIT licensed.
4+
//
5+
// The official BIP39 spec can be found at
6+
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
7+
package bip39
8+
9+
import (
10+
"crypto/sha256"
11+
"encoding/binary"
12+
"errors"
13+
"fmt"
14+
"math/big"
15+
"strings"
16+
)
17+
18+
var (
19+
// Some bitwise operands for working with big.Ints
20+
shift11BitsMask = big.NewInt(2048)
21+
bigOne = big.NewInt(1)
22+
23+
// used to isolate the checksum bits from the entropy+checksum byte array
24+
wordLengthChecksumMasksMapping = map[int]*big.Int{
25+
12: big.NewInt(15),
26+
15: big.NewInt(31),
27+
18: big.NewInt(63),
28+
21: big.NewInt(127),
29+
24: big.NewInt(255),
30+
}
31+
// used to use only the desired x of 8 available checksum bits.
32+
// 256 bit (word length 24) requires all 8 bits of the checksum,
33+
// and thus no shifting is needed for it (we would get a divByZero crash
34+
// if we did)
35+
wordLengthChecksumShiftMapping = map[int]*big.Int{
36+
12: big.NewInt(16),
37+
15: big.NewInt(8),
38+
18: big.NewInt(4),
39+
21: big.NewInt(2),
40+
}
41+
)
42+
43+
var (
44+
// ErrInvalidMnemonic is returned when trying to use a malformed mnemonic.
45+
ErrInvalidMnemonic = errors.New("invalid mnenomic")
46+
47+
// ErrChecksumIncorrect is returned when entropy has the incorrect checksum.
48+
ErrChecksumIncorrect = errors.New("checksum incorrect")
49+
)
50+
51+
// EntropyFromMnemonic takes a mnemonic generated by this library,
52+
// and returns the input entropy used to generate the given mnemonic.
53+
// An error is returned if the given mnemonic is invalid.
54+
func EntropyFromMnemonic(mnemonic string) ([]byte, error) {
55+
mnemonicSlice, isValid := splitMnemonicWords(mnemonic)
56+
if !isValid {
57+
return nil, ErrInvalidMnemonic
58+
}
59+
60+
wordMap := make(map[string]int)
61+
for i, v := range English {
62+
wordMap[v] = i
63+
}
64+
65+
// Decode the words into a big.Int.
66+
b := big.NewInt(0)
67+
for _, v := range mnemonicSlice {
68+
index, found := wordMap[v]
69+
if found == false {
70+
return nil, fmt.Errorf("word `%v` not found in " +
71+
"reverse map", v)
72+
}
73+
var wordBytes [2]byte
74+
binary.BigEndian.PutUint16(wordBytes[:], uint16(index))
75+
b = b.Mul(b, shift11BitsMask)
76+
b = b.Or(b, big.NewInt(0).SetBytes(wordBytes[:]))
77+
}
78+
79+
// Build and add the checksum to the big.Int.
80+
checksum := big.NewInt(0)
81+
checksumMask := wordLengthChecksumMasksMapping[len(mnemonicSlice)]
82+
checksum = checksum.And(b, checksumMask)
83+
84+
b.Div(b, big.NewInt(0).Add(checksumMask, bigOne))
85+
86+
// The entropy is the underlying bytes of the big.Int. Any upper bytes
87+
// of all 0's are not returned so we pad the beginning of the slice with
88+
// empty bytes if necessary.
89+
entropy := b.Bytes()
90+
entropy = padByteSlice(entropy, len(mnemonicSlice)/3*4)
91+
92+
// Generate the checksum and compare with the one we got from the mneomnic.
93+
entropyChecksumBytes := computeChecksum(entropy)
94+
entropyChecksum := big.NewInt(int64(entropyChecksumBytes[0]))
95+
if l := len(mnemonicSlice); l != 24 {
96+
checksumShift := wordLengthChecksumShiftMapping[l]
97+
entropyChecksum.Div(entropyChecksum, checksumShift)
98+
}
99+
100+
if checksum.Cmp(entropyChecksum) != 0 {
101+
return nil, ErrChecksumIncorrect
102+
}
103+
104+
return entropy, nil
105+
}
106+
107+
func computeChecksum(data []byte) []byte {
108+
hasher := sha256.New()
109+
hasher.Write(data)
110+
return hasher.Sum(nil)
111+
}
112+
113+
// padByteSlice returns a byte slice of the given size with contents of the
114+
// given slice left padded and any empty spaces filled with 0's.
115+
func padByteSlice(slice []byte, length int) []byte {
116+
offset := length - len(slice)
117+
if offset <= 0 {
118+
return slice
119+
}
120+
newSlice := make([]byte, length)
121+
copy(newSlice[offset:], slice)
122+
return newSlice
123+
}
124+
125+
func splitMnemonicWords(mnemonic string) ([]string, bool) {
126+
// Create a list of all the words in the mnemonic sentence
127+
words := strings.Fields(mnemonic)
128+
129+
// Get num of words
130+
numOfWords := len(words)
131+
132+
// The number of words should be 12, 15, 18, 21 or 24
133+
if numOfWords%3 != 0 || numOfWords < 12 || numOfWords > 24 {
134+
return nil, false
135+
}
136+
return words, true
137+
}

0 commit comments

Comments
 (0)