2
0
mirror of https://github.com/guggero/chantools synced 2024-11-11 01:10:42 +00:00
chantools/bip39/bip39.go
2022-07-25 12:13:05 -05:00

142 lines
3.9 KiB
Go

// Package bip39 is the Golang implementation of the BIP39 spec.
// This code was copied from https://github.com/tyler-smith/go-bip39 which is
// also MIT licensed.
//
// The official BIP39 spec can be found at
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
package bip39
import (
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"math/big"
"strings"
)
var (
// 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),
}
)
var (
// ErrInvalidMnemonic is returned when trying to use a malformed
// mnemonic.
ErrInvalidMnemonic = errors.New("invalid mnenomic")
// 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
}
wordMap := make(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("word `%v` not found in "+
"reverse map", v)
}
var wordBytes [2]byte
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
}
func computeChecksum(data []byte) []byte {
hasher := sha256.New()
_, _ = hasher.Write(data)
return hasher.Sum(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
}