Update bip-39 ref and remove ebfe/scard from vendor

This commit is contained in:
Guillaume Ballet
2019-04-08 19:16:27 +02:00
parent 8ee5bb2289
commit 86806d8b24
10 changed files with 426 additions and 2210 deletions

View File

@@ -1,3 +1,7 @@
// 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
package bip39
import (
@@ -10,19 +14,88 @@ import (
"math/big"
"strings"
"github.com/tyler-smith/go-bip39/wordlists"
"golang.org/x/crypto/pbkdf2"
)
// Some bitwise operands for working with big.Ints
var (
Last11BitsMask = big.NewInt(2047)
RightShift11BitsDivider = big.NewInt(2048)
BigOne = big.NewInt(1)
BigTwo = big.NewInt(2)
// Some bitwise operands for working with big.Ints
last11BitsMask = big.NewInt(2047)
shift11BitsMask = big.NewInt(2048)
bigOne = big.NewInt(1)
bigTwo = big.NewInt(2)
// 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),
}
// wordList is the set of words to use
wordList []string
// wordMap is a reverse lookup map for wordList
wordMap map[string]int
)
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")
// ErrValidatedSeedLengthMismatch is returned when a validated seed is not the
// same size as the given seed. This should never happen is present only as a
// sanity assertion.
ErrValidatedSeedLengthMismatch = errors.New("Seed length does not match validated seed length")
// ErrChecksumIncorrect is returned when entropy has the incorrect checksum.
ErrChecksumIncorrect = errors.New("Checksum incorrect")
)
func init() {
SetWordList(wordlists.English)
}
// SetWordList sets the list of words to use for mnemonics. Currently the list
// that is set is used package-wide.
func SetWordList(list []string) {
wordList = list
wordMap = map[string]int{}
for i, v := range wordList {
wordMap[v] = i
}
}
// GetWordList gets the list of words to use for mnemonics.
func GetWordList() []string {
return wordList
}
// GetWordIndex gets word index in wordMap.
func GetWordIndex(word string) (int, bool) {
idx, ok := wordMap[word]
return idx, ok
}
// NewEntropy will create random entropy bytes
// so long as the requested size bitSize is an appropriate size.
//
// bitSize has to be a multiple 32 and be within the inclusive range of {128, 256}
func NewEntropy(bitSize int) ([]byte, error) {
err := validateEntropyBitSize(bitSize)
if err != nil {
@@ -34,47 +107,98 @@ func NewEntropy(bitSize int) ([]byte, error) {
return entropy, err
}
// 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
}
// Decode the words into a big.Int.
b := big.NewInt(0)
for _, v := range mnemonicSlice {
index, found := wordMap[v]
if found == false {
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
}
// 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
// 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
}
// Add checksum to entropy
// 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
// 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
// Entropy as an int so we can bitmask without worrying about bytes slices.
entropyInt := new(big.Int).SetBytes(entropy)
// Slice to hold words in
// Slice to hold words in.
words := make([]string, sentenceLength)
// Throw away big int for AND masking
// 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, RightShift11BitsDivider)
// 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
// 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)]
// Convert bytes to an index and add that word to the list.
words[i] = wordList[binary.BigEndian.Uint16(wordBytes)]
}
return strings.Join(words, " "), nil
@@ -83,71 +207,50 @@ func NewMnemonic(entropy []byte) (string, error) {
// MnemonicToByteArray takes a mnemonic string and turns it into a byte array
// suitable for creating another mnemonic.
// An error is returned if the mnemonic is invalid.
// FIXME
// This does not work for all values in
// the test vectors. Namely
// Vectors 0, 4, and 8.
// This is not really important because BIP39 doesnt really define a conversion
// from string to bytes.
func MnemonicToByteArray(mnemonic string) ([]byte, error) {
if IsMnemonicValid(mnemonic) == false {
return nil, fmt.Errorf("Invalid mnemonic")
}
mnemonicSlice := strings.Split(mnemonic, " ")
func MnemonicToByteArray(mnemonic string, raw ...bool) ([]byte, error) {
var (
mnemonicSlice = strings.Split(mnemonic, " ")
entropyBitSize = len(mnemonicSlice) * 11
checksumBitSize = entropyBitSize % 32
fullByteSize = (entropyBitSize-checksumBitSize)/8 + 1
checksumByteSize = fullByteSize - (fullByteSize % 4)
)
bitSize := len(mnemonicSlice) * 11
err := validateEntropyWithChecksumBitSize(bitSize)
if err != nil {
return nil, err
// Pre validate that the mnemonic is well formed and only contains words that
// are present in the word list.
if !IsMnemonicValid(mnemonic) {
return nil, ErrInvalidMnemonic
}
checksumSize := bitSize % 32
b := big.NewInt(0)
// Convert word indices to a big.Int representing the entropy.
checksummedEntropy := big.NewInt(0)
modulo := big.NewInt(2048)
for _, v := range mnemonicSlice {
index, found := ReverseWordMap[v]
if found == false {
return nil, fmt.Errorf("Word `%v` not found in reverse map", v)
}
add := big.NewInt(int64(index))
b = b.Mul(b, modulo)
b = b.Add(b, add)
}
hex := b.Bytes()
checksumModulo := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(int64(checksumSize)), nil)
entropy, _ := big.NewInt(0).DivMod(b, checksumModulo, big.NewInt(0))
entropyHex := entropy.Bytes()
byteSize := bitSize/8 + 1
if len(hex) != byteSize {
tmp := make([]byte, byteSize)
diff := byteSize - len(hex)
for i := 0; i < len(hex); i++ {
tmp[i+diff] = hex[i]
}
hex = tmp
index := big.NewInt(int64(wordMap[v]))
checksummedEntropy.Mul(checksummedEntropy, modulo)
checksummedEntropy.Add(checksummedEntropy, index)
}
validationHex := addChecksum(entropyHex)
if len(validationHex) != byteSize {
tmp2 := make([]byte, byteSize)
diff2 := byteSize - len(validationHex)
for i := 0; i < len(validationHex); i++ {
tmp2[i+diff2] = validationHex[i]
}
validationHex = tmp2
// Calculate the unchecksummed entropy so we can validate that the checksum is
// correct.
checksumModulo := big.NewInt(0).Exp(bigTwo, big.NewInt(int64(checksumBitSize)), nil)
rawEntropy := big.NewInt(0).Div(checksummedEntropy, checksumModulo)
// Convert big.Ints to byte padded byte slices.
rawEntropyBytes := padByteSlice(rawEntropy.Bytes(), checksumByteSize)
checksummedEntropyBytes := padByteSlice(checksummedEntropy.Bytes(), fullByteSize)
// Validate that the checksum is correct.
newChecksummedEntropyBytes := padByteSlice(addChecksum(rawEntropyBytes), fullByteSize)
if !compareByteSlices(checksummedEntropyBytes, newChecksummedEntropyBytes) {
return nil, ErrChecksumIncorrect
}
if len(hex) != len(validationHex) {
panic("[]byte len mismatch - it shouldn't happen")
if len(raw) > 0 && raw[0] {
return rawEntropyBytes, nil
}
for i := range validationHex {
if hex[i] != validationHex[i] {
return nil, fmt.Errorf("Invalid byte at position %v", i)
}
}
return hex, nil
return checksummedEntropyBytes, nil
}
// NewSeedWithErrorChecking creates a hashed seed output given the mnemonic string and a password.
@@ -166,13 +269,36 @@ 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 {
// Create a list of all the words in the mnemonic sentence
words := strings.Fields(mnemonic)
// Get word count
wordCount := len(words)
// The number of words should be 12, 15, 18, 21 or 24
if wordCount%3 != 0 || wordCount < 12 || wordCount > 24 {
return false
}
// Check if all words belong in the wordlist
for _, word := range words {
if _, ok := wordMap[word]; !ok {
return false
}
}
return true
}
// 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 {
// Get first byte of sha256
hasher := sha256.New()
hasher.Write(data)
hash := hasher.Sum(nil)
hash := computeChecksum(data)
firstChecksumByte := hash[0]
// len() is in bytes so we divide by 4
@@ -184,66 +310,68 @@ func addChecksum(data []byte) []byte {
dataBigInt := new(big.Int).SetBytes(data)
for i := uint(0); i < checksumBitLength; i++ {
// Bitshift 1 left
dataBigInt.Mul(dataBigInt, BigTwo)
dataBigInt.Mul(dataBigInt, bigTwo)
// Set rightmost bit if leftmost checksum bit is set
if uint8(firstChecksumByte&(1<<(7-i))) > 0 {
dataBigInt.Or(dataBigInt, BigOne)
dataBigInt.Or(dataBigInt, bigOne)
}
}
return dataBigInt.Bytes()
}
func padByteSlice(slice []byte, length int) []byte {
newSlice := make([]byte, length-len(slice))
return append(newSlice, slice...)
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 errors.New("Entropy length must be [128, 256] and a multiple of 32")
return ErrEntropyLengthInvalid
}
return nil
}
func validateEntropyWithChecksumBitSize(bitSize int) error {
if (bitSize != 128+4) && (bitSize != 160+5) && (bitSize != 192+6) && (bitSize != 224+7) && (bitSize != 256+8) {
return fmt.Errorf("Wrong entropy + checksum size - expected %v, got %v", int((bitSize-bitSize%32)+(bitSize-bitSize%32)/32), bitSize)
// 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
}
return nil
newSlice := make([]byte, length)
copy(newSlice[offset:], slice)
return newSlice
}
// 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 {
// compareByteSlices returns true of the byte slices have equal contents and
// returns false otherwise.
func compareByteSlices(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
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
// 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 false
return nil, false
}
// Check if all words belong in the wordlist
for i := 0; i < numOfWords; i++ {
if !contains(WordList, words[i]) {
return false
}
}
return true
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
return words, true
}