aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/tyler-smith/go-bip39/bip39.go
blob: 4d281ba46f38e8b23c4466e5a25a20170b19782f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package bip39

import (
    "crypto/rand"
    "crypto/sha256"
    "crypto/sha512"
    "encoding/binary"
    "errors"
    "fmt"
    "math/big"
    "strings"

    "golang.org/x/crypto/pbkdf2"
)

// Some bitwise operands for working with big.Ints
var (
    Last11BitsMask          = big.NewInt(2047)
    RightShift11BitsDivider = big.NewInt(2048)
    BigOne                  = big.NewInt(1)
    BigTwo                  = big.NewInt(2)
)

// NewEntropy will create random entropy bytes
// so long as the requested size bitSize is an appropriate size.
func NewEntropy(bitSize int) ([]byte, error) {
    err := validateEntropyBitSize(bitSize)
    if err != nil {
        return nil, err
    }

    entropy := make([]byte, bitSize/8)
    _, err = rand.Read(entropy)
    return entropy, err
}

// NewMnemonic will return a string consisting of the mnemonic words for
// the given entropy.
// If the provide entropy is invalid, an error will be returned.
func NewMnemonic(entropy []byte) (string, error) {
    // Compute some lengths for convenience
    entropyBitLength := len(entropy) * 8
    checksumBitLength := entropyBitLength / 32
    sentenceLength := (entropyBitLength + checksumBitLength) / 11

    err := validateEntropyBitSize(entropyBitLength)
    if err != nil {
        return "", err
    }

    // Add checksum to entropy
    entropy = addChecksum(entropy)

    // Break entropy up into sentenceLength chunks of 11 bits
    // For each word AND mask the rightmost 11 bits and find the word at that index
    // Then bitshift entropy 11 bits right and repeat
    // Add to the last empty slot so we can work with LSBs instead of MSB

    // Entropy as an int so we can bitmask without worrying about bytes slices
    entropyInt := new(big.Int).SetBytes(entropy)

    // Slice to hold words in
    words := make([]string, sentenceLength)

    // Throw away big int for AND masking
    word := big.NewInt(0)

    for i := sentenceLength - 1; i >= 0; i-- {
        // Get 11 right most bits and bitshift 11 to the right for next time
        word.And(entropyInt, Last11BitsMask)
        entropyInt.Div(entropyInt, RightShift11BitsDivider)

        // Get the bytes representing the 11 bits as a 2 byte slice
        wordBytes := padByteSlice(word.Bytes(), 2)

        // Convert bytes to an index and add that word to the list
        words[i] = WordList[binary.BigEndian.Uint16(wordBytes)]
    }

    return strings.Join(words, " "), nil
}

// MnemonicToByteArray takes a mnemonic string and turns it into a byte array
// suitable for creating another mnemonic.
// An error is returned if the mnemonic is invalid.
// FIXME
// This does not work for all values in
// the test vectors.  Namely
// Vectors 0, 4, and 8.
// This is not really important because BIP39 doesnt really define a conversion
// from string to bytes.
func MnemonicToByteArray(mnemonic string) ([]byte, error) {
    if IsMnemonicValid(mnemonic) == false {
        return nil, fmt.Errorf("Invalid mnemonic")
    }
    mnemonicSlice := strings.Split(mnemonic, " ")

    bitSize := len(mnemonicSlice) * 11
    err := validateEntropyWithChecksumBitSize(bitSize)
    if err != nil {
        return nil, err
    }
    checksumSize := bitSize % 32

    b := big.NewInt(0)
    modulo := big.NewInt(2048)
    for _, v := range mnemonicSlice {
        index, found := ReverseWordMap[v]
        if found == false {
            return nil, fmt.Errorf("Word `%v` not found in reverse map", v)
        }
        add := big.NewInt(int64(index))
        b = b.Mul(b, modulo)
        b = b.Add(b, add)
    }
    hex := b.Bytes()
    checksumModulo := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(int64(checksumSize)), nil)
    entropy, _ := big.NewInt(0).DivMod(b, checksumModulo, big.NewInt(0))

    entropyHex := entropy.Bytes()

    byteSize := bitSize/8 + 1
    if len(hex) != byteSize {
        tmp := make([]byte, byteSize)
        diff := byteSize - len(hex)
        for i := 0; i < len(hex); i++ {
            tmp[i+diff] = hex[i]
        }
        hex = tmp
    }

    validationHex := addChecksum(entropyHex)
    if len(validationHex) != byteSize {
        tmp2 := make([]byte, byteSize)
        diff2 := byteSize - len(validationHex)
        for i := 0; i < len(validationHex); i++ {
            tmp2[i+diff2] = validationHex[i]
        }
        validationHex = tmp2
    }

    if len(hex) != len(validationHex) {
        panic("[]byte len mismatch - it shouldn't happen")
    }
    for i := range validationHex {
        if hex[i] != validationHex[i] {
            return nil, fmt.Errorf("Invalid byte at position %v", i)
        }
    }
    return hex, nil
}

// NewSeedWithErrorChecking creates a hashed seed output given the mnemonic string and a password.
// An error is returned if the mnemonic is not convertible to a byte array.
func NewSeedWithErrorChecking(mnemonic string, password string) ([]byte, error) {
    _, err := MnemonicToByteArray(mnemonic)
    if err != nil {
        return nil, err
    }
    return NewSeed(mnemonic, password), nil
}

// NewSeed creates a hashed seed output given a provided string and password.
// No checking is performed to validate that the string provided is a valid mnemonic.
func NewSeed(mnemonic string, password string) []byte {
    return pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"+password), 2048, 64, sha512.New)
}

// Appends to data the first (len(data) / 32)bits of the result of sha256(data)
// Currently only supports data up to 32 bytes
func addChecksum(data []byte) []byte {
    // Get first byte of sha256
    hasher := sha256.New()
    hasher.Write(data)
    hash := hasher.Sum(nil)
    firstChecksumByte := hash[0]

    // len() is in bytes so we divide by 4
    checksumBitLength := uint(len(data) / 4)

    // For each bit of check sum we want we shift the data one the left
    // and then set the (new) right most bit equal to checksum bit at that index
    // staring from the left
    dataBigInt := new(big.Int).SetBytes(data)
    for i := uint(0); i < checksumBitLength; i++ {
        // Bitshift 1 left
        dataBigInt.Mul(dataBigInt, BigTwo)

        // Set rightmost bit if leftmost checksum bit is set
        if uint8(firstChecksumByte&(1<<(7-i))) > 0 {
            dataBigInt.Or(dataBigInt, BigOne)
        }
    }

    return dataBigInt.Bytes()
}

func padByteSlice(slice []byte, length int) []byte {
    newSlice := make([]byte, length-len(slice))
    return append(newSlice, slice...)
}

func validateEntropyBitSize(bitSize int) error {
    if (bitSize%32) != 0 || bitSize < 128 || bitSize > 256 {
        return errors.New("Entropy length must be [128, 256] and a multiple of 32")
    }
    return nil
}

func validateEntropyWithChecksumBitSize(bitSize int) error {
    if (bitSize != 128+4) && (bitSize != 160+5) && (bitSize != 192+6) && (bitSize != 224+7) && (bitSize != 256+8) {
        return fmt.Errorf("Wrong entropy + checksum size - expected %v, got %v", int((bitSize-bitSize%32)+(bitSize-bitSize%32)/32), bitSize)
    }
    return nil
}

// IsMnemonicValid attempts to verify that the provided mnemonic is valid.
// Validity is determined by both the number of words being appropriate,
// and that all the words in the mnemonic are present in the word list.
func IsMnemonicValid(mnemonic string) bool {
    // Create a list of all the words in the mnemonic sentence
    words := strings.Fields(mnemonic)

    //Get num of words
    numOfWords := len(words)

    // The number of words should be 12, 15, 18, 21 or 24
    if numOfWords%3 != 0 || numOfWords < 12 || numOfWords > 24 {
        return false
    }

    // Check if all words belong in the wordlist
    for i := 0; i < numOfWords; i++ {
        if !contains(WordList, words[i]) {
            return false
        }
    }

    return true
}

func contains(s []string, e string) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}