github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/wallet/mnemonic/mnemonic.go (about) 1 // Package bip39 is the official Golang implementation of the BIP39 spec. 2 // 3 // The official BIP39 spec can be found at 4 // https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki 5 package mnemonic 6 7 import ( 8 "crypto/rand" 9 "crypto/sha256" 10 "crypto/sha512" 11 "encoding/binary" 12 "errors" 13 "fmt" 14 "math/big" 15 "strings" 16 17 "github.com/johngb/langreg" 18 "golang.org/x/crypto/pbkdf2" 19 20 "github.com/bytom/bytom/wallet/mnemonic/wordlists" 21 ) 22 23 var ( 24 // Some bitwise operands for working with big.Ints 25 last11BitsMask = big.NewInt(2047) 26 rightShift11BitsDivider = big.NewInt(2048) 27 bigOne = big.NewInt(1) 28 bigTwo = big.NewInt(2) 29 30 // used to isolate the checksum bits from the entropy+checksum byte array 31 wordLengthChecksumMasksMapping = map[int]*big.Int{ 32 12: big.NewInt(15), 33 15: big.NewInt(31), 34 18: big.NewInt(63), 35 21: big.NewInt(127), 36 24: big.NewInt(255), 37 } 38 // used to use only the desired x of 8 available checksum bits. 39 // 256 bit (word length 24) requires all 8 bits of the checksum, 40 // and thus no shifting is needed for it (we would get a divByZero crash if we did) 41 wordLengthChecksumShiftMapping = map[int]*big.Int{ 42 12: big.NewInt(16), 43 15: big.NewInt(8), 44 18: big.NewInt(4), 45 21: big.NewInt(2), 46 } 47 48 // wordList is the set of words to use 49 wordList map[string][]string 50 ) 51 52 var ( 53 // ErrInvalidMnemonic is returned when trying to use a malformed mnemonic. 54 ErrInvalidMnemonic = errors.New("Invalid menomic") 55 56 // ErrEntropyLengthInvalid is returned when trying to use an entropy set with 57 // an invalid size. 58 ErrEntropyLengthInvalid = errors.New("Entropy length must be [128, 256] and a multiple of 32") 59 60 // ErrValidatedSeedLengthMismatch is returned when a validated seed is not the 61 // same size as the given seed. This should never happen is present only as a 62 // sanity assertion. 63 ErrValidatedSeedLengthMismatch = errors.New("Seed length does not match validated seed length") 64 65 // ErrChecksumIncorrect is returned when entropy has the incorrect checksum. 66 ErrChecksumIncorrect = errors.New("Checksum incorrect") 67 68 // ErrLanguageTypeIncorrect is return when find incorrect language type 69 ErrLanguageTypeIncorrect = errors.New("Language Type Incorrect") 70 71 // ErrLanguageTypeIncorrect is return when find incorrect language type 72 ErrLanguageTypeUnsupported = errors.New("Language Type Unsupported") 73 ) 74 75 func init() { 76 wordList = map[string][]string{ 77 "zh_CN": wordlists.ChineseSimplified, 78 "zh_TW": wordlists.ChineseTraditional, 79 "en": wordlists.English, 80 "it": wordlists.Italian, 81 "ja": wordlists.Japanese, 82 "ko": wordlists.Korean, 83 "es": wordlists.Spanish, 84 } 85 } 86 87 // SetWordList sets the list of words to use for mnemonics. Currently the list 88 // that is set is used package-wide. 89 func SetWordMap(language string) (map[string]int, error) { 90 if !isLanguageValid(language) { 91 return nil, ErrLanguageTypeIncorrect 92 } 93 words, ok := wordList[language] 94 if !ok { 95 return nil, ErrLanguageTypeUnsupported 96 } 97 wordMap := map[string]int{} 98 for i, v := range words { 99 wordMap[v] = i 100 } 101 return wordMap, nil 102 } 103 104 // SetWordList sets the list of words to use for mnemonics. Currently the list 105 // that is set is used package-wide. 106 func SetWordList(language string) ([]string, error) { 107 if !isLanguageValid(language) { 108 return nil, ErrLanguageTypeIncorrect 109 } 110 words, ok := wordList[language] 111 if !ok { 112 return nil, ErrLanguageTypeUnsupported 113 } 114 115 return words, nil 116 } 117 118 // NewEntropy will create random entropy bytes 119 // so long as the requested size bitSize is an appropriate size. 120 // 121 // bitSize has to be a multiple 32 and be within the inclusive range of {128, 256} 122 func NewEntropy(bitSize int) ([]byte, error) { 123 err := validateEntropyBitSize(bitSize) 124 if err != nil { 125 return nil, err 126 } 127 128 entropy := make([]byte, bitSize/8) 129 _, err = rand.Read(entropy) 130 return entropy, err 131 } 132 133 // EntropyFromMnemonic takes a mnemonic generated by this library, 134 // and returns the input entropy used to generate the given mnemonic. 135 // An error is returned if the given mnemonic is invalid. 136 func EntropyFromMnemonic(mnemonic string, language string) ([]byte, error) { 137 mnemonicSlice, isValid := splitMnemonicWords(mnemonic) 138 if !isValid { 139 return nil, errors.New("Invalid mnemonic") 140 } 141 wordMap, err := SetWordMap(language) 142 if err != nil { 143 return nil, err 144 } 145 b := big.NewInt(0) 146 for _, v := range mnemonicSlice { 147 index, found := wordMap[v] 148 if found == false { 149 return nil, fmt.Errorf("word `%v` not found in reverse map", v) 150 } 151 var wordBytes [2]byte 152 binary.BigEndian.PutUint16(wordBytes[:], uint16(index)) 153 b = b.Mul(b, rightShift11BitsDivider) 154 b = b.Or(b, big.NewInt(0).SetBytes(wordBytes[:])) 155 } 156 157 checksum := big.NewInt(0) 158 checksumMask := wordLengthChecksumMasksMapping[len(mnemonicSlice)] 159 checksum = checksum.And(b, checksumMask) 160 161 b.Div(b, big.NewInt(0).Add(checksumMask, bigOne)) 162 entropy := b.Bytes() 163 // pad entropy if needed 164 entropy = padByteSlice(entropy, len(mnemonicSlice)/3*4) 165 166 // generate the checksum once again, mask and ensure it equals the checksum we got from the mneomnic 167 entropyChecksumBytes := computeChecksum(entropy) 168 entropyChecksum := big.NewInt(int64(entropyChecksumBytes[0])) 169 if l := len(mnemonicSlice); l != 24 { 170 checksumShift := wordLengthChecksumShiftMapping[l] 171 entropyChecksum.Div(entropyChecksum, checksumShift) 172 } 173 174 if checksum.Cmp(entropyChecksum) != 0 { 175 return nil, errors.New("mnemonic's entropy doesn't match its checksum") 176 } 177 178 // return (padded) entropy 179 return entropy, nil 180 } 181 182 // NewMnemonic will return a string consisting of the mnemonic words for 183 // the given entropy. 184 // If the provide entropy is invalid, an error will be returned. 185 func NewMnemonic(entropy []byte, language string) (string, error) { 186 wordList, err := SetWordList(language) 187 if err != nil { 188 return "", err 189 } 190 // Compute some lengths for convenience 191 entropyBitLength := len(entropy) * 8 192 checksumBitLength := entropyBitLength / 32 193 sentenceLength := (entropyBitLength + checksumBitLength) / 11 194 195 err = validateEntropyBitSize(entropyBitLength) 196 if err != nil { 197 return "", err 198 } 199 200 // Add checksum to entropy 201 entropy = addChecksum(entropy) 202 203 // Break entropy up into sentenceLength chunks of 11 bits 204 // For each word AND mask the rightmost 11 bits and find the word at that index 205 // Then bitshift entropy 11 bits right and repeat 206 // Add to the last empty slot so we can work with LSBs instead of MSB 207 208 // Entropy as an int so we can bitmask without worrying about bytes slices 209 entropyInt := new(big.Int).SetBytes(entropy) 210 211 // Slice to hold words in 212 words := make([]string, sentenceLength) 213 214 // Throw away big int for AND masking 215 word := big.NewInt(0) 216 217 for i := sentenceLength - 1; i >= 0; i-- { 218 // Get 11 right most bits and bitshift 11 to the right for next time 219 word.And(entropyInt, last11BitsMask) 220 entropyInt.Div(entropyInt, rightShift11BitsDivider) 221 222 // Get the bytes representing the 11 bits as a 2 byte slice 223 wordBytes := padByteSlice(word.Bytes(), 2) 224 225 // Convert bytes to an index and add that word to the list 226 words[i] = wordList[binary.BigEndian.Uint16(wordBytes)] 227 } 228 229 return strings.Join(words, " "), nil 230 } 231 232 // MnemonicToByteArray takes a mnemonic string and turns it into a byte array 233 // suitable for creating another mnemonic. 234 // An error is returned if the mnemonic is invalid. 235 func MnemonicToByteArray(mnemonic string, language string, raw ...bool) ([]byte, error) { 236 var ( 237 mnemonicSlice = strings.Split(mnemonic, " ") 238 entropyBitSize = len(mnemonicSlice) * 11 239 checksumBitSize = entropyBitSize % 32 240 fullByteSize = (entropyBitSize-checksumBitSize)/8 + 1 241 checksumByteSize = fullByteSize - (fullByteSize % 4) 242 ) 243 wordMap, err := SetWordMap(language) 244 if err != nil { 245 return nil, err 246 } 247 // Pre validate that the mnemonic is well formed and only contains words that 248 // are present in the word list 249 if !IsMnemonicValid(mnemonic, language) { 250 return nil, ErrInvalidMnemonic 251 } 252 253 // Convert word indices to a `big.Int` representing the entropy 254 checksummedEntropy := big.NewInt(0) 255 modulo := big.NewInt(2048) 256 for _, v := range mnemonicSlice { 257 index := big.NewInt(int64(wordMap[v])) 258 checksummedEntropy.Mul(checksummedEntropy, modulo) 259 checksummedEntropy.Add(checksummedEntropy, index) 260 } 261 262 // Calculate the unchecksummed entropy so we can validate that the checksum is 263 // correct 264 checksumModulo := big.NewInt(0).Exp(bigTwo, big.NewInt(int64(checksumBitSize)), nil) 265 rawEntropy := big.NewInt(0).Div(checksummedEntropy, checksumModulo) 266 267 // Convert `big.Int`s to byte padded byte slices 268 rawEntropyBytes := padByteSlice(rawEntropy.Bytes(), checksumByteSize) 269 checksummedEntropyBytes := padByteSlice(checksummedEntropy.Bytes(), fullByteSize) 270 271 // Validate that the checksum is correct 272 newChecksummedEntropyBytes := padByteSlice(addChecksum(rawEntropyBytes), fullByteSize) 273 if !compareByteSlices(checksummedEntropyBytes, newChecksummedEntropyBytes) { 274 return nil, ErrChecksumIncorrect 275 } 276 277 if raw == nil { 278 return checksummedEntropyBytes, nil 279 } 280 if raw[0] == true { 281 return rawEntropyBytes, nil 282 } 283 return checksummedEntropyBytes, nil 284 } 285 286 // NewSeedWithErrorChecking creates a hashed seed output given the mnemonic string and a password. 287 // An error is returned if the mnemonic is not convertible to a byte array. 288 func NewSeedWithErrorChecking(mnemonic string, password string, language string) ([]byte, error) { 289 _, err := MnemonicToByteArray(mnemonic, language) 290 if err != nil { 291 return nil, err 292 } 293 return NewSeed(mnemonic, password), nil 294 } 295 296 // NewSeed creates a hashed seed output given a provided string and password. 297 // No checking is performed to validate that the string provided is a valid mnemonic. 298 func NewSeed(mnemonic string, password string) []byte { 299 return pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"+password), 2048, 64, sha512.New) 300 } 301 302 func isLanguageValid(language string) bool { 303 if len(language) != 2 && len(language) != 5 { 304 return false 305 } 306 if len(language) == 5 && !langreg.IsValidLangRegCode(language) { 307 return false 308 } 309 if len(language) == 2 && !langreg.IsValidLanguageCode(language) { 310 return false 311 } 312 return true 313 } 314 315 // IsMnemonicValid attempts to verify that the provided mnemonic is valid. 316 // Validity is determined by both the number of words being appropriate, 317 // and that all the words in the mnemonic are present in the word list. 318 func IsMnemonicValid(mnemonic string, language string) bool { 319 // Create a list of all the words in the mnemonic sentence 320 words := strings.Fields(mnemonic) 321 322 // Get word count 323 wordCount := len(words) 324 325 // The number of words should be 12, 15, 18, 21 or 24 326 if wordCount%3 != 0 || wordCount < 12 || wordCount > 24 { 327 return false 328 } 329 wordMap, err := SetWordMap(language) 330 if err != nil { 331 return false 332 } 333 // Check if all words belong in the wordlist 334 for _, word := range words { 335 if _, ok := wordMap[word]; !ok { 336 return false 337 } 338 } 339 340 return true 341 } 342 343 // Appends to data the first (len(data) / 32)bits of the result of sha256(data) 344 // Currently only supports data up to 32 bytes 345 func addChecksum(data []byte) []byte { 346 // Get first byte of sha256 347 hash := computeChecksum(data) 348 firstChecksumByte := hash[0] 349 350 // len() is in bytes so we divide by 4 351 checksumBitLength := uint(len(data) / 4) 352 353 // For each bit of check sum we want we shift the data one the left 354 // and then set the (new) right most bit equal to checksum bit at that index 355 // staring from the left 356 dataBigInt := new(big.Int).SetBytes(data) 357 for i := uint(0); i < checksumBitLength; i++ { 358 // Bitshift 1 left 359 dataBigInt.Mul(dataBigInt, bigTwo) 360 361 // Set rightmost bit if leftmost checksum bit is set 362 if uint8(firstChecksumByte&(1<<(7-i))) > 0 { 363 dataBigInt.Or(dataBigInt, bigOne) 364 } 365 } 366 367 return dataBigInt.Bytes() 368 } 369 370 func computeChecksum(data []byte) []byte { 371 hasher := sha256.New() 372 hasher.Write(data) 373 return hasher.Sum(nil) 374 } 375 376 // validateEntropyBitSize ensures that entropy is the correct size for being a 377 // mnemonic. 378 func validateEntropyBitSize(bitSize int) error { 379 if (bitSize%32) != 0 || bitSize < 128 || bitSize > 256 { 380 return ErrEntropyLengthInvalid 381 } 382 return nil 383 } 384 385 // padByteSlice returns a byte slice of the given size with contents of the 386 // given slice left padded and any empty spaces filled with 0's. 387 func padByteSlice(slice []byte, length int) []byte { 388 offset := length - len(slice) 389 if offset <= 0 { 390 return slice 391 } 392 newSlice := make([]byte, length) 393 copy(newSlice[offset:], slice) 394 return newSlice 395 } 396 397 // compareByteSlices returns true of the byte slices have equal contents and 398 // returns false otherwise. 399 func compareByteSlices(a, b []byte) bool { 400 if len(a) != len(b) { 401 return false 402 } 403 for i := range a { 404 if a[i] != b[i] { 405 return false 406 } 407 } 408 return true 409 } 410 411 func splitMnemonicWords(mnemonic string) ([]string, bool) { 412 // Create a list of all the words in the mnemonic sentence 413 words := strings.Fields(mnemonic) 414 415 //Get num of words 416 numOfWords := len(words) 417 418 // The number of words should be 12, 15, 18, 21 or 24 419 if numOfWords%3 != 0 || numOfWords < 12 || numOfWords > 24 { 420 return nil, false 421 } 422 return words, true 423 }