check / check (push) Successful in 5m38s
go-bip39's repository no longer exists, so keyfunc now carries the part of v1.1.0 it uses, with the upstream LICENSE beside it, and imports it from internal/bip39. Upstream's tests and test vectors for the kept code come along unchanged; where they called a removed function, crypto/rand stands in for NewEntropy and EntropyFromMnemonic for MnemonicToByteArray. Changes beyond the trimming are what the linter asked for: the package-level variables moved into the functions that use them, three error strings were lower-cased, and the unknown-word error now wraps ErrInvalidMnemonic. go.mod no longer requires go-bip39. go mod tidy keeps its two go.sum lines, because a test in sneak/secret's bip85 package imports it, and drops six lines that only go-bip39's own requirements needed. Model: opus-5-5
269 lines
7.9 KiB
Go
269 lines
7.9 KiB
Go
// Package bip39 is the Golang implementation of the BIP39 spec.
|
|
//
|
|
// The official BIP39 spec can be found at
|
|
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
|
//
|
|
// It is a copy of github.com/tyler-smith/go-bip39 v1.1.0, trimmed to what
|
|
// keyfunc uses.
|
|
//
|
|
//nolint:mnd // the numbers are BIP-39's own, written as upstream writes them
|
|
package bip39
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
|
|
"golang.org/x/crypto/pbkdf2"
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidMnemonic is returned when trying to use a malformed mnemonic.
|
|
ErrInvalidMnemonic = errors.New("invalid mnenomic")
|
|
|
|
// ErrEntropyLengthInvalid is returned when trying to use an entropy set with
|
|
// an invalid size.
|
|
ErrEntropyLengthInvalid = errors.New(
|
|
"entropy length must be [128, 256] and a multiple of 32",
|
|
)
|
|
|
|
// ErrChecksumIncorrect is returned when entropy has the incorrect checksum.
|
|
ErrChecksumIncorrect = errors.New("checksum incorrect")
|
|
)
|
|
|
|
// EntropyFromMnemonic takes a mnemonic generated by this library,
|
|
// and returns the input entropy used to generate the given mnemonic.
|
|
// An error is returned if the given mnemonic is invalid.
|
|
func EntropyFromMnemonic(mnemonic string) ([]byte, error) {
|
|
mnemonicSlice, isValid := splitMnemonicWords(mnemonic)
|
|
if !isValid {
|
|
return nil, ErrInvalidMnemonic
|
|
}
|
|
|
|
// Some bitwise operands for working with big.Ints
|
|
shift11BitsMask := big.NewInt(2048)
|
|
bigOne := big.NewInt(1)
|
|
|
|
// used to isolate the checksum bits from the entropy+checksum byte array
|
|
wordLengthChecksumMasksMapping := map[int]*big.Int{
|
|
12: big.NewInt(15),
|
|
15: big.NewInt(31),
|
|
18: big.NewInt(63),
|
|
21: big.NewInt(127),
|
|
24: big.NewInt(255),
|
|
}
|
|
// used to use only the desired x of 8 available checksum bits.
|
|
// 256 bit (word length 24) requires all 8 bits of the checksum,
|
|
// and thus no shifting is needed for it (we would get a divByZero crash if we did)
|
|
wordLengthChecksumShiftMapping := map[int]*big.Int{
|
|
12: big.NewInt(16),
|
|
15: big.NewInt(8),
|
|
18: big.NewInt(4),
|
|
21: big.NewInt(2),
|
|
}
|
|
|
|
// wordMap is a reverse lookup map for the word list
|
|
wordMap := map[string]int{}
|
|
for i, v := range English() {
|
|
wordMap[v] = i
|
|
}
|
|
|
|
// Decode the words into a big.Int.
|
|
b := big.NewInt(0)
|
|
|
|
for _, v := range mnemonicSlice {
|
|
index, found := wordMap[v]
|
|
if !found {
|
|
return nil, fmt.Errorf(
|
|
"%w: word `%v` not found in reverse map", ErrInvalidMnemonic, v,
|
|
)
|
|
}
|
|
|
|
var wordBytes [2]byte
|
|
|
|
//nolint:gosec // the index of a word in the list is below 2048
|
|
binary.BigEndian.PutUint16(wordBytes[:], uint16(index))
|
|
|
|
b = b.Mul(b, shift11BitsMask)
|
|
b = b.Or(b, big.NewInt(0).SetBytes(wordBytes[:]))
|
|
}
|
|
|
|
// Build and add the checksum to the big.Int.
|
|
checksum := big.NewInt(0)
|
|
checksumMask := wordLengthChecksumMasksMapping[len(mnemonicSlice)]
|
|
checksum = checksum.And(b, checksumMask)
|
|
|
|
b.Div(b, big.NewInt(0).Add(checksumMask, bigOne))
|
|
|
|
// The entropy is the underlying bytes of the big.Int. Any upper bytes of
|
|
// all 0's are not returned so we pad the beginning of the slice with empty
|
|
// bytes if necessary.
|
|
entropy := b.Bytes()
|
|
entropy = padByteSlice(entropy, len(mnemonicSlice)/3*4)
|
|
|
|
// Generate the checksum and compare with the one we got from the mneomnic.
|
|
entropyChecksumBytes := computeChecksum(entropy)
|
|
entropyChecksum := big.NewInt(int64(entropyChecksumBytes[0]))
|
|
|
|
if l := len(mnemonicSlice); l != 24 {
|
|
checksumShift := wordLengthChecksumShiftMapping[l]
|
|
entropyChecksum.Div(entropyChecksum, checksumShift)
|
|
}
|
|
|
|
if checksum.Cmp(entropyChecksum) != 0 {
|
|
return nil, ErrChecksumIncorrect
|
|
}
|
|
|
|
return entropy, nil
|
|
}
|
|
|
|
// NewMnemonic will return a string consisting of the mnemonic words for
|
|
// the given entropy.
|
|
// If the provide entropy is invalid, an error will be returned.
|
|
func NewMnemonic(entropy []byte) (string, error) {
|
|
// Compute some lengths for convenience.
|
|
entropyBitLength := len(entropy) * 8
|
|
checksumBitLength := entropyBitLength / 32
|
|
sentenceLength := (entropyBitLength + checksumBitLength) / 11
|
|
|
|
// Validate that the requested size is supported.
|
|
err := validateEntropyBitSize(entropyBitLength)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Some bitwise operands for working with big.Ints
|
|
last11BitsMask := big.NewInt(2047)
|
|
shift11BitsMask := big.NewInt(2048)
|
|
|
|
// wordList is the set of words to use
|
|
wordList := English()
|
|
|
|
// Add checksum to entropy.
|
|
entropy = addChecksum(entropy)
|
|
|
|
// Break entropy up into sentenceLength chunks of 11 bits.
|
|
// For each word AND mask the rightmost 11 bits and find the word at that index.
|
|
// Then bitshift entropy 11 bits right and repeat.
|
|
// Add to the last empty slot so we can work with LSBs instead of MSB.
|
|
|
|
// Entropy as an int so we can bitmask without worrying about bytes slices.
|
|
entropyInt := new(big.Int).SetBytes(entropy)
|
|
|
|
// Slice to hold words in.
|
|
words := make([]string, sentenceLength)
|
|
|
|
// Throw away big.Int for AND masking.
|
|
word := big.NewInt(0)
|
|
|
|
for i := sentenceLength - 1; i >= 0; i-- {
|
|
// Get 11 right most bits and bitshift 11 to the right for next time.
|
|
word.And(entropyInt, last11BitsMask)
|
|
entropyInt.Div(entropyInt, shift11BitsMask)
|
|
|
|
// Get the bytes representing the 11 bits as a 2 byte slice.
|
|
wordBytes := padByteSlice(word.Bytes(), 2)
|
|
|
|
// Convert bytes to an index and add that word to the list.
|
|
words[i] = wordList[binary.BigEndian.Uint16(wordBytes)]
|
|
}
|
|
|
|
return strings.Join(words, " "), nil
|
|
}
|
|
|
|
// NewSeed creates a hashed seed output given a provided string and password.
|
|
// No checking is performed to validate that the string provided is a valid mnemonic.
|
|
func NewSeed(mnemonic string, password string) []byte {
|
|
return pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"+password), 2048, 64, sha512.New)
|
|
}
|
|
|
|
// IsMnemonicValid attempts to verify that the provided mnemonic is valid.
|
|
// Validity is determined by both the number of words being appropriate,
|
|
// and that all the words in the mnemonic are present in the word list.
|
|
func IsMnemonicValid(mnemonic string) bool {
|
|
_, err := EntropyFromMnemonic(mnemonic)
|
|
|
|
return err == nil
|
|
}
|
|
|
|
// Appends to data the first (len(data) / 32)bits of the result of sha256(data)
|
|
// Currently only supports data up to 32 bytes
|
|
func addChecksum(data []byte) []byte {
|
|
// Some bitwise operands for working with big.Ints
|
|
bigOne := big.NewInt(1)
|
|
bigTwo := big.NewInt(2)
|
|
|
|
// Get first byte of sha256
|
|
hash := computeChecksum(data)
|
|
firstChecksumByte := hash[0]
|
|
|
|
// len() is in bytes so we divide by 4
|
|
checksumBitLength := uint(len(data) / 4)
|
|
|
|
// For each bit of check sum we want we shift the data one the left
|
|
// and then set the (new) right most bit equal to checksum bit at that index
|
|
// staring from the left
|
|
dataBigInt := new(big.Int).SetBytes(data)
|
|
for i := range checksumBitLength {
|
|
// Bitshift 1 left
|
|
dataBigInt.Mul(dataBigInt, bigTwo)
|
|
|
|
// Set rightmost bit if leftmost checksum bit is set
|
|
if firstChecksumByte&(1<<(7-i)) > 0 {
|
|
dataBigInt.Or(dataBigInt, bigOne)
|
|
}
|
|
}
|
|
|
|
return dataBigInt.Bytes()
|
|
}
|
|
|
|
func computeChecksum(data []byte) []byte {
|
|
hasher := sha256.New()
|
|
hasher.Write(data)
|
|
|
|
return hasher.Sum(nil)
|
|
}
|
|
|
|
// validateEntropyBitSize ensures that entropy is the correct size for being a
|
|
// mnemonic.
|
|
func validateEntropyBitSize(bitSize int) error {
|
|
if (bitSize%32) != 0 || bitSize < 128 || bitSize > 256 {
|
|
return ErrEntropyLengthInvalid
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// padByteSlice returns a byte slice of the given size with contents of the
|
|
// given slice left padded and any empty spaces filled with 0's.
|
|
func padByteSlice(slice []byte, length int) []byte {
|
|
offset := length - len(slice)
|
|
if offset <= 0 {
|
|
return slice
|
|
}
|
|
|
|
newSlice := make([]byte, length)
|
|
copy(newSlice[offset:], slice)
|
|
|
|
return newSlice
|
|
}
|
|
|
|
func splitMnemonicWords(mnemonic string) ([]string, bool) {
|
|
// Create a list of all the words in the mnemonic sentence
|
|
words := strings.Fields(mnemonic)
|
|
|
|
// Get num of words
|
|
numOfWords := len(words)
|
|
|
|
// The number of words should be 12, 15, 18, 21 or 24
|
|
if numOfWords%3 != 0 || numOfWords < 12 || numOfWords > 24 {
|
|
return nil, false
|
|
}
|
|
|
|
return words, true
|
|
}
|