github.com/algorand/go-algorand-sdk@v1.24.0/mnemonic/mnemonic.go (about) 1 package mnemonic 2 3 import ( 4 "crypto/sha512" 5 "fmt" 6 "strings" 7 ) 8 9 const ( 10 bitsPerWord = 11 11 checksumLenBits = 11 12 keyLenBytes = 32 13 mnemonicLenWords = 25 14 paddingZeros = bitsPerWord - ((keyLenBytes * 8) % bitsPerWord) 15 ) 16 17 var sepStr = " " 18 var emptyByte = byte(0) 19 20 func init() { 21 // Verify expected relationship between constants 22 if mnemonicLenWords*bitsPerWord-checksumLenBits != keyLenBytes*8+paddingZeros { 23 panic("cannot initialize passphrase library: invalid constants") 24 } 25 } 26 27 // FromKey converts a 32-byte key into a 25 word mnemonic. The generated 28 // mnemonic includes a checksum. Each word in the mnemonic represents 11 bits 29 // of data, and the last 11 bits are reserved for the checksum. 30 func FromKey(key []byte) (string, error) { 31 // Ensure the key we are passed is the expected length 32 if len(key) != keyLenBytes { 33 return "", errWrongKeyLen 34 } 35 36 // Compute the checksum of these bytes 37 chk := checksum(key) 38 uint11Array := toUint11Array(key) 39 words := applyWords(uint11Array, wordlist) 40 return fmt.Sprintf("%s %s", strings.Join(words, " "), chk), nil 41 } 42 43 // ToKey converts a mnemonic generated using this library into the source 44 // key used to create it. It returns an error if the passed mnemonic has an 45 // incorrect checksum, if the number of words is unexpected, or if one 46 // of the passed words is not found in the words list. 47 func ToKey(mnemonic string) ([]byte, error) { 48 // Split input on whitespace 49 wordsRaw := strings.Split(mnemonic, sepStr) 50 51 // Strip out extra whitespace 52 var words []string 53 for _, word := range wordsRaw { 54 if word != "" { 55 words = append(words, word) 56 } 57 } 58 59 // Ensure the mnemonic is the correct length 60 if len(words) != mnemonicLenWords { 61 return nil, errWrongMnemonicLen 62 } 63 64 // Check that all words are in list 65 for _, w := range words { 66 if indexOf(wordlist, w) == -1 { 67 return nil, fmt.Errorf("%s is not in the words list", w) 68 } 69 } 70 71 // convert words to uin11array (Excluding the checksum word) 72 var uint11Array []uint32 73 for i := 0; i < len(words)-1; i++ { 74 uint11Array = append(uint11Array, uint32(indexOf(wordlist, words[i]))) 75 } 76 77 // convert t the key back to byte array 78 byteArr := toByteArray(uint11Array) 79 80 // We need to chop the last byte - 81 // the short explanation - Since 256 is not divisible by 11, we have an extra 0x0 byte. 82 // The longer explanation - When splitting the 256 bits to chunks of 11, we get 23 words and a left over of 3 bits. 83 // This left gets padded with another 8 bits to the create the 24th word. 84 // While converting back to byte array, our new 264 bits array is divisible by 8 but the last byte is just the padding. 85 86 // Check that we have 33 bytes long array as expected 87 if len(byteArr) != keyLenBytes+1 { 88 return nil, errWrongKeyLen 89 } 90 // Check that the last one is actually 0 91 if byteArr[keyLenBytes] != emptyByte { 92 return nil, errWrongChecksum 93 } 94 95 // chop it ! 96 byteArr = byteArr[0:keyLenBytes] 97 98 // Pull out the checksum 99 mnemonicChecksum := checksum(byteArr) 100 101 // Verify the checksum 102 if mnemonicChecksum != words[len(words)-1] { 103 return nil, errWrongChecksum 104 } 105 106 // Verify that we recovered the correct amount of data 107 if len(byteArr) != keyLenBytes { 108 panic("passphrase:MnemonicToKey is broken: recovered wrong amount of data") 109 } 110 111 return byteArr, nil 112 } 113 114 // https://stackoverflow.com/a/50285590/356849 115 func toUint11Array(arr []byte) []uint32 { 116 var buffer uint32 117 var numberOfBit uint32 118 var output []uint32 119 120 for i := 0; i < len(arr); i++ { 121 // prepend bits to buffer 122 buffer |= uint32(arr[i]) << numberOfBit 123 numberOfBit += 8 124 125 // if there enough bits, extract 11bit number 126 if numberOfBit >= 11 { 127 // 0x7FF is 2047, the max 11 bit number 128 output = append(output, buffer&0x7ff) 129 130 // drop chunk from buffer 131 buffer = buffer >> 11 132 numberOfBit -= 11 133 } 134 135 } 136 137 if numberOfBit != 0 { 138 output = append(output, buffer&0x7ff) 139 } 140 return output 141 } 142 143 // This function may result in an extra empty byte 144 // https://stackoverflow.com/a/51452614 145 func toByteArray(arr []uint32) []byte { 146 var buffer uint32 147 var numberOfBits uint32 148 var output []byte 149 150 for i := 0; i < len(arr); i++ { 151 buffer |= uint32(arr[i]) << numberOfBits 152 numberOfBits += 11 153 154 for numberOfBits >= 8 { 155 output = append(output, byte(buffer&0xff)) 156 buffer >>= 8 157 numberOfBits -= 8 158 } 159 } 160 161 if buffer != 0 { 162 output = append(output, byte(buffer)) 163 } 164 165 return output 166 } 167 168 func applyWords(arr []uint32, words []string) []string { 169 res := make([]string, len(arr)) 170 for i := 0; i < len(arr); i++ { 171 res[i] = words[arr[i]] 172 } 173 return res 174 } 175 176 func indexOf(arr []string, s string) int { 177 for i, w := range arr { 178 if w == s { 179 return i 180 } 181 } 182 return -1 183 } 184 185 // Checksum returns a word that represents the 11 bit checksum of the data 186 func checksum(data []byte) string { 187 // Compute the full hash of the data to checksum 188 fullHash := sha512.Sum512_256(data) 189 190 // Convert to 11 bits array 191 temp := fullHash[0:2] 192 chkBytes := toUint11Array(temp) 193 194 return applyWords(chkBytes, wordlist)[0] 195 }