Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup #28

Merged
merged 3 commits into from
Oct 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 39 additions & 36 deletions bip39.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package bip39 is the official Golang implementation of the BIP39 spec.
// 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
Expand All @@ -20,10 +20,10 @@ import (

var (
// Some bitwise operands for working with big.Ints
last11BitsMask = big.NewInt(2047)
rightShift11BitsDivider = big.NewInt(2048)
bigOne = big.NewInt(1)
bigTwo = big.NewInt(2)
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{
Expand Down Expand Up @@ -52,7 +52,7 @@ var (

var (
// ErrInvalidMnemonic is returned when trying to use a malformed mnemonic.
ErrInvalidMnemonic = errors.New("Invalid menomic")
ErrInvalidMnemonic = errors.New("Invalid mnenomic")

// ErrEntropyLengthInvalid is returned when trying to use an entropy set with
// an invalid size.
Expand Down Expand Up @@ -81,12 +81,12 @@ func SetWordList(list []string) {
}
}

// GetWordList gets the list of words to use for mnemonics
// GetWordList gets the list of words to use for mnemonics.
func GetWordList() []string {
return wordList
}

// GetWordIndex get word index in wordMap
// GetWordIndex gets word index in wordMap.
func GetWordIndex(word string) (int, bool) {
idx, ok := wordMap[word]
return idx, ok
Expand All @@ -113,9 +113,10 @@ func NewEntropy(bitSize int) ([]byte, error) {
func EntropyFromMnemonic(mnemonic string) ([]byte, error) {
mnemonicSlice, isValid := splitMnemonicWords(mnemonic)
if !isValid {
return nil, errors.New("Invalid mnemonic")
return nil, ErrInvalidMnemonic
}

// Decode the words into a big.Int.
b := big.NewInt(0)
for _, v := range mnemonicSlice {
index, found := wordMap[v]
Expand All @@ -124,20 +125,24 @@ func EntropyFromMnemonic(mnemonic string) ([]byte, error) {
}
var wordBytes [2]byte
binary.BigEndian.PutUint16(wordBytes[:], uint16(index))
b = b.Mul(b, rightShift11BitsDivider)
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()
// pad entropy if needed
entropy = padByteSlice(entropy, len(mnemonicSlice)/3*4)

// generate the checksum once again, mask and ensure it equals the checksum we got from the mneomnic
// 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 {
Expand All @@ -146,53 +151,53 @@ func EntropyFromMnemonic(mnemonic string) ([]byte, error) {
}

if checksum.Cmp(entropyChecksum) != 0 {
return nil, errors.New("mnemonic's entropy doesn't match its checksum")
return nil, ErrChecksumIncorrect
}

// return (padded) entropy
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
// Get 11 right most bits and bitshift 11 to the right for next time.
word.And(entropyInt, last11BitsMask)
entropyInt.Div(entropyInt, rightShift11BitsDivider)
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
// Convert bytes to an index and add that word to the list.
words[i] = wordList[binary.BigEndian.Uint16(wordBytes)]
}

Expand All @@ -212,12 +217,12 @@ func MnemonicToByteArray(mnemonic string, raw ...bool) ([]byte, error) {
)

// Pre validate that the mnemonic is well formed and only contains words that
// are present in the word list
// are present in the word list.
if !IsMnemonicValid(mnemonic) {
return nil, ErrInvalidMnemonic
}

// Convert word indices to a `big.Int` representing the entropy
// Convert word indices to a big.Int representing the entropy.
checksummedEntropy := big.NewInt(0)
modulo := big.NewInt(2048)
for _, v := range mnemonicSlice {
Expand All @@ -227,26 +232,24 @@ func MnemonicToByteArray(mnemonic string, raw ...bool) ([]byte, error) {
}

// Calculate the unchecksummed entropy so we can validate that the checksum is
// correct
// correct.
checksumModulo := big.NewInt(0).Exp(bigTwo, big.NewInt(int64(checksumBitSize)), nil)
rawEntropy := big.NewInt(0).Div(checksummedEntropy, checksumModulo)

// Convert `big.Int`s to byte padded byte slices
// Convert big.Ints to byte padded byte slices.
rawEntropyBytes := padByteSlice(rawEntropy.Bytes(), checksumByteSize)
checksummedEntropyBytes := padByteSlice(checksummedEntropy.Bytes(), fullByteSize)

// Validate that the checksum is correct
// Validate that the checksum is correct.
newChecksummedEntropyBytes := padByteSlice(addChecksum(rawEntropyBytes), fullByteSize)
if !compareByteSlices(checksummedEntropyBytes, newChecksummedEntropyBytes) {
return nil, ErrChecksumIncorrect
}

if raw == nil {
return checksummedEntropyBytes, nil
}
if raw[0] == true {
if len(raw) > 0 && raw[0] {
return rawEntropyBytes, nil
}

return checksummedEntropyBytes, nil
}

Expand Down
Loading