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  }