github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/crypto/bip39/bip39.go (about) 1 package bip39 2 3 import ( 4 "crypto/rand" 5 "crypto/sha256" 6 "crypto/sha512" 7 "encoding/binary" 8 "errors" 9 "fmt" 10 "math/big" 11 "strings" 12 13 "golang.org/x/crypto/pbkdf2" 14 ) 15 16 // Some bitwise operands for working with big.Ints 17 var ( 18 Last11BitsMask = big.NewInt(2047) 19 RightShift11BitsDivider = big.NewInt(2048) 20 BigOne = big.NewInt(1) 21 BigTwo = big.NewInt(2) 22 ) 23 24 // NewEntropy will create random entropy bytes 25 // so long as the requested size bitSize is an appropriate size. 26 func NewEntropy(bitSize int) ([]byte, error) { 27 err := validateEntropyBitSize(bitSize) 28 if err != nil { 29 return nil, err 30 } 31 32 entropy := make([]byte, bitSize/8) 33 _, err = rand.Read(entropy) 34 return entropy, err 35 } 36 37 // NewMnemonic will return a string consisting of the mnemonic words for 38 // the given entropy. 39 // If the provide entropy is invalid, an error will be returned. 40 func NewMnemonic(entropy []byte) (string, error) { 41 // Compute some lengths for convenience 42 entropyBitLength := len(entropy) * 8 43 checksumBitLength := entropyBitLength / 32 44 sentenceLength := (entropyBitLength + checksumBitLength) / 11 45 46 err := validateEntropyBitSize(entropyBitLength) 47 if err != nil { 48 return "", err 49 } 50 51 // Add checksum to entropy 52 entropy = addChecksum(entropy) 53 54 // Break entropy up into sentenceLength chunks of 11 bits 55 // For each word AND mask the rightmost 11 bits and find the word at that index 56 // Then bitshift entropy 11 bits right and repeat 57 // Add to the last empty slot so we can work with LSBs instead of MSB 58 59 // Entropy as an int so we can bitmask without worrying about bytes slices 60 entropyInt := new(big.Int).SetBytes(entropy) 61 62 // Slice to hold words in 63 words := make([]string, sentenceLength) 64 65 // Throw away big int for AND masking 66 word := big.NewInt(0) 67 68 for i := sentenceLength - 1; i >= 0; i-- { 69 // Get 11 right most bits and bitshift 11 to the right for next time 70 word.And(entropyInt, Last11BitsMask) 71 entropyInt.Div(entropyInt, RightShift11BitsDivider) 72 73 // Get the bytes representing the 11 bits as a 2 byte slice 74 wordBytes := padByteSlice(word.Bytes(), 2) 75 76 // Convert bytes to an index and add that word to the list 77 words[i] = WordList[binary.BigEndian.Uint16(wordBytes)] 78 } 79 80 return strings.Join(words, " "), nil 81 } 82 83 // MnemonicToByteArray takes a mnemonic string and turns it into a byte array 84 // suitable for creating another mnemonic. 85 // An error is returned if the mnemonic is invalid. 86 func MnemonicToByteArray(mnemonic string) ([]byte, error) { 87 if !IsMnemonicValid(mnemonic) { 88 return nil, fmt.Errorf("invalid mnemonic") 89 } 90 mnemonicSlice := strings.Split(mnemonic, " ") 91 92 bitSize := len(mnemonicSlice) * 11 93 err := validateEntropyWithChecksumBitSize(bitSize) 94 if err != nil { 95 return nil, err 96 } 97 checksumSize := bitSize % 32 98 99 b := big.NewInt(0) 100 modulo := big.NewInt(2048) 101 for _, v := range mnemonicSlice { 102 index, found := ReverseWordMap[v] 103 if !found { 104 return nil, fmt.Errorf("word `%v` not found in reverse map", v) 105 } 106 add := big.NewInt(int64(index)) 107 b = b.Mul(b, modulo) 108 b = b.Add(b, add) 109 } 110 hex := b.Bytes() 111 checksumModulo := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(int64(checksumSize)), nil) 112 entropy, _ := big.NewInt(0).DivMod(b, checksumModulo, big.NewInt(0)) 113 114 entropyHex := entropy.Bytes() 115 116 // Add padding (an extra byte is for checksum) 117 byteSize := (bitSize-checksumSize)/8 + 1 118 if len(hex) != byteSize { 119 tmp := make([]byte, byteSize) 120 diff := byteSize - len(hex) 121 for i := 0; i < len(hex); i++ { 122 tmp[i+diff] = hex[i] 123 } 124 hex = tmp 125 } 126 127 // Add padding (no extra byte, entropy itself does not contain checksum) 128 entropyByteSize := (bitSize - checksumSize) / 8 129 if len(entropyHex) != entropyByteSize { 130 tmp := make([]byte, entropyByteSize) 131 diff := entropyByteSize - len(entropyHex) 132 for i := 0; i < len(entropyHex); i++ { 133 tmp[i+diff] = entropyHex[i] 134 } 135 entropyHex = tmp 136 } 137 138 validationHex := addChecksum(entropyHex) 139 if len(validationHex) != byteSize { 140 tmp2 := make([]byte, byteSize) 141 diff2 := byteSize - len(validationHex) 142 for i := 0; i < len(validationHex); i++ { 143 tmp2[i+diff2] = validationHex[i] 144 } 145 validationHex = tmp2 146 } 147 148 if len(hex) != len(validationHex) { 149 panic("[]byte len mismatch - it shouldn't happen") 150 } 151 for i := range validationHex { 152 if hex[i] != validationHex[i] { 153 return nil, fmt.Errorf("invalid byte at position %v", i) 154 } 155 } 156 return hex, nil 157 } 158 159 // NewSeedWithErrorChecking creates a hashed seed output given the mnemonic string and a password. 160 // An error is returned if the mnemonic is not convertible to a byte array. 161 func NewSeedWithErrorChecking(mnemonic string, password string) ([]byte, error) { 162 _, err := MnemonicToByteArray(mnemonic) 163 if err != nil { 164 return nil, err 165 } 166 return NewSeed(mnemonic, password), nil 167 } 168 169 // NewSeed creates a hashed seed output given a provided string and password. 170 // No checking is performed to validate that the string provided is a valid mnemonic. 171 func NewSeed(mnemonic string, password string) []byte { 172 return pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"+password), 2048, 64, sha512.New) 173 } 174 175 // Appends to data the first (len(data) / 32)bits of the result of sha256(data) 176 // Currently only supports data up to 32 bytes 177 func addChecksum(data []byte) []byte { 178 // Get first byte of sha256 179 hash := sha256.Sum256(data) 180 firstChecksumByte := hash[0] 181 182 // len() is in bytes so we divide by 4 183 checksumBitLength := uint(len(data) / 4) 184 185 // For each bit of check sum we want we shift the data one the left 186 // and then set the (new) right most bit equal to checksum bit at that index 187 // staring from the left 188 dataBigInt := new(big.Int).SetBytes(data) 189 for i := uint(0); i < checksumBitLength; i++ { 190 // Bitshift 1 left 191 dataBigInt.Mul(dataBigInt, BigTwo) 192 193 // Set rightmost bit if leftmost checksum bit is set 194 if firstChecksumByte&(1<<(7-i)) > 0 { 195 dataBigInt.Or(dataBigInt, BigOne) 196 } 197 } 198 199 return dataBigInt.Bytes() 200 } 201 202 func padByteSlice(slice []byte, length int) []byte { 203 newSlice := make([]byte, length-len(slice)) 204 return append(newSlice, slice...) //nolint:makezero 205 } 206 207 func validateEntropyBitSize(bitSize int) error { 208 if (bitSize%32) != 0 || bitSize < 128 || bitSize > 256 { 209 return errors.New("entropy length must be [128, 256] and a multiple of 32") 210 } 211 return nil 212 } 213 214 func validateEntropyWithChecksumBitSize(bitSize int) error { 215 if (bitSize != 128+4) && (bitSize != 160+5) && (bitSize != 192+6) && (bitSize != 224+7) && (bitSize != 256+8) { 216 return fmt.Errorf("wrong entropy + checksum size - expected %v, got %v", (bitSize-bitSize%32)+(bitSize-bitSize%32)/32, bitSize) 217 } 218 return nil 219 } 220 221 // IsMnemonicValid attempts to verify that the provided mnemonic is valid. 222 // Validity is determined by both the number of words being appropriate, 223 // and that all the words in the mnemonic are present in the word list. 224 func IsMnemonicValid(mnemonic string) bool { 225 // Create a list of all the words in the mnemonic sentence 226 words := strings.Fields(mnemonic) 227 228 // Get num of words 229 numOfWords := len(words) 230 231 // The number of words should be 12, 15, 18, 21 or 24 232 if numOfWords < 12 || numOfWords > 24 || numOfWords%3 != 0 { 233 return false 234 } 235 236 // Check if all words belong in the wordlist 237 for i := 0; i < numOfWords; i++ { 238 if _, ok := ReverseWordMap[words[i]]; !ok { 239 return false 240 } 241 } 242 243 return true 244 }