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  }