From 17579504e3c11a5fedcdc7648d6bc29b0f793802 Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 6 Oct 2026 01:00:18 +0000 Subject: [PATCH] Copy go-bip39 into internal/bip39 (closes #42) 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 --- README.md | 6 +- go.mod | 1 - go.sum | 6 - internal/bip39/LICENSE | 21 + internal/bip39/bip39.go | 268 +++ internal/bip39/bip39_internal_test.go | 442 ++++ internal/bip39/english.go | 2061 ++++++++++++++++++ internal/bip39/english_internal_test.go | 19 + internal/bip39/example_test.go | 30 + internal/childmnemonic/childmnemonic.go | 2 +- internal/childmnemonic/childmnemonic_test.go | 2 +- internal/cli/cli_test.go | 2 +- internal/derive/derive.go | 2 +- internal/mnemonic/mnemonic.go | 2 +- 14 files changed, 2849 insertions(+), 15 deletions(-) create mode 100644 internal/bip39/LICENSE create mode 100644 internal/bip39/bip39.go create mode 100644 internal/bip39/bip39_internal_test.go create mode 100644 internal/bip39/english.go create mode 100644 internal/bip39/english_internal_test.go create mode 100644 internal/bip39/example_test.go diff --git a/README.md b/README.md index 4128af2..4903928 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ calls into `internal/`. The packages there are: `cli/options` holds the flags every command shares, `cli/signals` catches SIGINT, SIGTERM and SIGHUP for the commands that clean up before they end, and `cli/ssh`, `cli/age` and `cli/mnemonic` are the command groups. +- `internal/bip39` is a copy of `github.com/tyler-smith/go-bip39` v1.1.0, + trimmed to what keyfunc uses. ### Adding a key type @@ -373,9 +375,7 @@ standard: most Makefile targets are thin shims over an executable in `script/` ## TODO -The open issues that stand between the tree and a 1.0 release: - -- [#42 go-bip39 no longer exists upstream: keep it, or copy it into the repo?](https://git.eeqj.de/sneak/keyfunc/issues/42) +No issues are open. ## License diff --git a/go.mod b/go.mod index 543f9b5..107494e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/btcsuite/btcd/btcutil v1.2.0 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.12.1 - github.com/tyler-smith/go-bip39 v1.1.0 golang.org/x/crypto v0.57.0 golang.org/x/term v0.46.0 ) diff --git a/go.sum b/go.sum index 57e7f4b..5c2cbff 100644 --- a/go.sum +++ b/go.sum @@ -35,16 +35,10 @@ github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3C go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/bip39/LICENSE b/internal/bip39/LICENSE new file mode 100644 index 0000000..4dae82d --- /dev/null +++ b/internal/bip39/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2018 Tyler Smith and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/internal/bip39/bip39.go b/internal/bip39/bip39.go new file mode 100644 index 0000000..dc748b5 --- /dev/null +++ b/internal/bip39/bip39.go @@ -0,0 +1,268 @@ +// 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 +} diff --git a/internal/bip39/bip39_internal_test.go b/internal/bip39/bip39_internal_test.go new file mode 100644 index 0000000..a4a1e19 --- /dev/null +++ b/internal/bip39/bip39_internal_test.go @@ -0,0 +1,442 @@ +package bip39 + +import ( + "crypto/rand" + "encoding/hex" + "testing" +) + +type vector struct { + entropy string + mnemonic string + seed string +} + +func TestNewMnemonic(t *testing.T) { + t.Parallel() + + for _, vector := range testVectors() { + entropy, err := hex.DecodeString(vector.entropy) + assertNil(t, err) + + mnemonic, err := NewMnemonic(entropy) + assertNil(t, err) + assertEqualString(t, vector.mnemonic, mnemonic) + + seed := NewSeed(mnemonic, "TREZOR") + assertEqualString(t, vector.seed, hex.EncodeToString(seed)) + } +} + +func TestNewMnemonicInvalidEntropy(t *testing.T) { + t.Parallel() + + _, err := NewMnemonic([]byte{}) + assertNotNil(t, err) +} + +func TestIsMnemonicValid(t *testing.T) { + t.Parallel() + + for _, vector := range badMnemonicSentences() { + assertFalse(t, IsMnemonicValid(vector.mnemonic)) + } + + for _, vector := range testVectors() { + assertTrue(t, IsMnemonicValid(vector.mnemonic)) + } +} + +func TestPadByteSlice(t *testing.T) { + t.Parallel() + + assertEqualByteSlices(t, []byte{0}, padByteSlice([]byte{}, 1)) + assertEqualByteSlices(t, []byte{0, 1}, padByteSlice([]byte{1}, 2)) + assertEqualByteSlices(t, []byte{1, 1}, padByteSlice([]byte{1, 1}, 2)) + assertEqualByteSlices(t, []byte{1, 1, 1}, padByteSlice([]byte{1, 1, 1}, 2)) +} + +//nolint:funlen // the test vectors, kept as upstream wrote them +func TestMnemonicToByteArrayForZeroLeadingSeeds(t *testing.T) { + t.Parallel() + + ms := []string{ + "00000000000000000000000000000000", + "00a84c51041d49acca66e6160c1fa999", + "00ca45df1673c76537a2020bfed1dafd", + "0019d5871c7b81fd83d474ef1c1e1dae", + "00dcb021afb35ffcdd1d032d2056fc86", + "0062be7bd09a27288b6cf0eb565ec739", + "00dc705b5efa0adf25b9734226ba60d4", + "0017747418d54c6003fa64fade83374b", + "000d44d3ee7c3dfa45e608c65384431b", + "008241c1ef976b0323061affe5bf24b9", + "00a6aec77e4d16bea80b50a34991aaba", + "0011527b8c6ddecb9d0c20beccdeb58d", + "001c938c503c8f5a2bba2248ff621546", + "0002f90aaf7a8327698f0031b6317c36", + "00bff43071ed7e07f77b14f615993bac", + "00da143e00ef17fc63b6fb22dcc2c326", + "00ffc6764fb32a354cab1a3ddefb015d", + "0062ef47e0985e8953f24760b7598cdd", + "003bf9765064f71d304908d906c065f5", + "00993851503471439d154b3613947474", + "007ad0ffe9eae753a483a76af06dfa67", + "00091824db9ec19e663bee51d64c83cc", + "00f48ac621f7e3cb39b2012ac3121543", + "0072917415cdca24dfa66c4a92c885b4", + "0027ced2b279ea8a91d29364487cdbf4", + "00b9c0d37fb10ba272e55842ad812583", + "004b3d0d2b9285946c687a5350479c8c", + "00c7c12a37d3a7f8c1532b17c89b724c", + "00f400c5545f06ae17ad00f3041e4e26", + "001e290be10df4d209f247ac5878662b", + "00bf0f74568e582a7dd1ee64f792ec8b", + "00d2e43ecde6b72b847db1539ed89e23", + "00cecba6678505bb7bfec8ed307251f6", + "000aeed1a9edcbb4bc88f610d3ce84eb", + "00d06206aadfc25c2b21805d283f15ae", + "00a31789a2ab2d54f8fadd5331010287", + "003493c5f520e8d5c0483e895a121dc9", + "004706112800b76001ece2e268bc830e", + "00ab31e28bb5305be56e38337dbfa486", + "006872fe85df6b0fa945248e6f9379d1", + "00717e5e375da6934e3cfdf57edaf3bd", + "007f1b46e7b9c4c76e77c434b9bccd6b", + "00dc93735aa35def3b9a2ff676560205", + "002cd5dcd881a49c7b87714c6a570a76", + "0013b5af9e13fac87e0c505686cfb6bf", + "007ab1ec9526b0bc04b64ae65fd42631", + "00abb4e11d8385c1cca905a6a65e9144", + "00574fc62a0501ad8afada2e246708c3", + "005207e0a815bb2da6b4c35ec1f2bf52", + "00f3460f136fb9700080099cbd62bc18", + "007a591f204c03ca7b93981237112526", + "00cfe0befd428f8e5f83a5bfc801472e", + "00987551ac7a879bf0c09b8bc474d9af", + "00cadd3ce3d78e49fbc933a85682df3f", + "00bfbf2e346c855ccc360d03281455a1", + "004cdf55d429d028f715544ce22d4f31", + "0075c84a7d15e0ac85e1e41025eed23b", + "00807dddd61f71725d336cab844d2cb5", + "00422f21b77fe20e367467ed98c18410", + "00b44d0ac622907119c626c850a462fd", + "00363f5e7f22fc49f3cd662a28956563", + "000fe5837e68397bbf58db9f221bdc4e", + "0056af33835c888ef0c22599686445d3", + "00790a8647fd3dfb38b7e2b6f578f2c6", + "00da8d9009675cb7beec930e263014fb", + "00d4b384540a5bb54aa760edaa4fb2fe", + "00be9b1479ed680fdd5d91a41eb926d0", + "009182347502af97077c40a6e74b4b5c", + "00f5c90ee1c67fa77fd821f8e9fab4f1", + "005568f9a2dd6b0c0cc2f5ba3d9cac38", + "008b481f8678577d9cf6aa3f6cd6056b", + "00c4323ece5e4fe3b6cd4c5c932931af", + "009791f7550c3798c5a214cb2d0ea773", + "008a7baab22481f0ad8167dd9f90d55c", + "00f0e601519aafdc8ff94975e64c946d", + "0083b61e0daa9219df59d697c270cd31", + } + + for _, m := range ms { + seed, _ := hex.DecodeString(m) + + mnemonic, err := NewMnemonic(seed) + if err != nil { + t.Errorf("%v", err) + } + + _, err = EntropyFromMnemonic(mnemonic) + if err != nil { + t.Errorf("Failed for %x - %v", seed, mnemonic) + } + } +} + +func TestEntropyFromMnemonic128(t *testing.T) { + t.Parallel() + + testEntropyFromMnemonic(t, 128) +} + +func TestEntropyFromMnemonic160(t *testing.T) { + t.Parallel() + + testEntropyFromMnemonic(t, 160) +} + +func TestEntropyFromMnemonic192(t *testing.T) { + t.Parallel() + + testEntropyFromMnemonic(t, 192) +} + +func TestEntropyFromMnemonic224(t *testing.T) { + t.Parallel() + + testEntropyFromMnemonic(t, 224) +} + +func TestEntropyFromMnemonic256(t *testing.T) { + t.Parallel() + + testEntropyFromMnemonic(t, 256) +} + +//nolint:dupword,lll // the test vector, kept as upstream wrote it +func TestEntropyFromMnemonicInvalidChecksum(t *testing.T) { + t.Parallel() + + _, err := EntropyFromMnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon yellow") + assertEqual(t, ErrChecksumIncorrect, err) +} + +//nolint:dupword // the test vectors, kept as upstream wrote them +func TestEntropyFromMnemonicInvalidMnemonicSize(t *testing.T) { + t.Parallel() + + for _, mnemonic := range []string{ + "a a a a a a a a a a a a a a a a a a a a a a a a a", // Too many words + "a", // Too few + "a a a a a a a a a a a a a a", // Not multiple of 3 + } { + _, err := EntropyFromMnemonic(mnemonic) + assertEqual(t, ErrInvalidMnemonic, err) + } +} + +func testEntropyFromMnemonic(t *testing.T, bitSize int) { + t.Helper() + + for range 512 { + expectedEntropy := make([]byte, bitSize/8) + _, err := rand.Read(expectedEntropy) + assertNil(t, err) + assertTrue(t, len(expectedEntropy) != 0) + + mnemonic, err := NewMnemonic(expectedEntropy) + assertNil(t, err) + assertTrue(t, len(mnemonic) != 0) + + actualEntropy, err := EntropyFromMnemonic(mnemonic) + assertNil(t, err) + assertEqualByteSlices(t, expectedEntropy, actualEntropy) + } +} + +//nolint:dupword,funlen,lll // the BIP-39 test vectors, kept as upstream wrote them +func testVectors() []vector { + return []vector{ + { + entropy: "00000000000000000000000000000000", + mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + seed: "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + }, + { + entropy: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow", + seed: "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607", + }, + { + entropy: "80808080808080808080808080808080", + mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", + seed: "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8", + }, + { + entropy: "ffffffffffffffffffffffffffffffff", + mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + seed: "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069", + }, + { + entropy: "000000000000000000000000000000000000000000000000", + mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + seed: "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa", + }, + { + entropy: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + mnemonic: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + seed: "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd", + }, + { + entropy: "808080808080808080808080808080808080808080808080", + mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + seed: "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65", + }, + { + entropy: "ffffffffffffffffffffffffffffffffffffffffffffffff", + mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + seed: "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528", + }, + { + entropy: "0000000000000000000000000000000000000000000000000000000000000000", + mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + seed: "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8", + }, + { + entropy: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + mnemonic: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", + seed: "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87", + }, + { + entropy: "8080808080808080808080808080808080808080808080808080808080808080", + mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", + seed: "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f", + }, + { + entropy: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + seed: "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad", + }, + { + entropy: "77c2b00716cec7213839159e404db50d", + mnemonic: "jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge", + seed: "b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f672a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff", + }, + { + entropy: "b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b", + mnemonic: "renew stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap", + seed: "9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5", + }, + { + entropy: "3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982", + mnemonic: "dignity pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic", + seed: "ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da20af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67", + }, + { + entropy: "0460ef47585604c5660618db2e6a7e7f", + mnemonic: "afford alter spike radar gate glance object seek swamp infant panel yellow", + seed: "65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41f8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4", + }, + { + entropy: "72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f", + mnemonic: "indicate race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left", + seed: "3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349dbe2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba", + }, + { + entropy: "2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416", + mnemonic: "clutch control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste", + seed: "fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f86360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449", + }, + { + entropy: "eaebabb2383351fd31d703840b32e9e2", + mnemonic: "turtle front uncle idea crush write shrug there lottery flower risk shell", + seed: "bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c404f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c", + }, + { + entropy: "7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78", + mnemonic: "kiss carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment", + seed: "ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b079403485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79", + }, + { + entropy: "4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef", + mnemonic: "exile ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top", + seed: "095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b14d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c", + }, + { + entropy: "18ab19a9f54a9274f03e5209a2ac8a91", + mnemonic: "board flee heavy tunnel powder denial science ski answer betray cargo cat", + seed: "6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe005831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8", + }, + { + entropy: "18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4", + mnemonic: "board blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief", + seed: "f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf07c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9", + }, + { + entropy: "15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419", + mnemonic: "beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut", + seed: "b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd", + }, + } +} + +//nolint:dupword,lll // the test vectors, kept as upstream wrote them +func badMnemonicSentences() []vector { + return []vector{ + {mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon"}, + {mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow yellow"}, + {mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice caged above"}, + {mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo, wrong"}, + {mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon"}, + {mnemonic: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will will will"}, + {mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always."}, + {mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo why"}, + {mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art art"}, + {mnemonic: "legal winner thank year wave sausage worth useful legal winner thanks year wave worth useful legal winner thank year wave sausage worth title"}, + {mnemonic: "letter advice cage absurd amount doctor acoustic avoid letters advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless"}, + {mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo voted"}, + {mnemonic: "jello better achieve collect unaware mountain thought cargo oxygen act hood bridge"}, + {mnemonic: "renew, stay, biology, evidence, goat, welcome, casual, join, adapt, armor, shuffle, fault, little, machine, walk, stumble, urge, swap"}, + {mnemonic: "dignity pass list indicate nasty"}, + + // From issue 32 + {mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon letter"}, + } +} + +func assertNil(t *testing.T, object any) { + t.Helper() + + if object != nil { + t.Errorf("Expected nil, got %v", object) + } +} + +func assertNotNil(t *testing.T, object any) { + t.Helper() + + if object == nil { + t.Error("Expected not nil") + } +} + +func assertTrue(t *testing.T, a bool) { + t.Helper() + + if !a { + t.Error("Expected true, got false") + } +} + +func assertFalse(t *testing.T, a bool) { + t.Helper() + + if a { + t.Error("Expected false, got true") + } +} + +func assertEqual(t *testing.T, a, b any) { + t.Helper() + + if a != b { + t.Errorf("Objects not equal, expected `%s` and got `%s`", a, b) + } +} + +func assertEqualString(t *testing.T, a, b string) { + t.Helper() + + if a != b { + t.Errorf("Strings not equal, expected `%s` and got `%s`", a, b) + } +} + +func assertEqualByteSlices(t *testing.T, a, b []byte) { + t.Helper() + + if len(a) != len(b) { + t.Errorf("Byte slices not equal, expected %v and got %v", a, b) + + return + } + + for i := range a { + if a[i] != b[i] { + t.Errorf("Byte slices not equal, expected %v and got %v", a, b) + + return + } + } +} diff --git a/internal/bip39/english.go b/internal/bip39/english.go new file mode 100644 index 0000000..ea2d3b1 --- /dev/null +++ b/internal/bip39/english.go @@ -0,0 +1,2061 @@ +package bip39 + +import ( + "strings" +) + +// English returns a slice of mnemonic words taken from the bip39 specification +// https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt +func English() []string { + return strings.Split(strings.TrimSpace(english), "\n") +} + +const english = `abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo +` diff --git a/internal/bip39/english_internal_test.go b/internal/bip39/english_internal_test.go new file mode 100644 index 0000000..c2652bd --- /dev/null +++ b/internal/bip39/english_internal_test.go @@ -0,0 +1,19 @@ +package bip39 + +import ( + "hash/crc32" + "testing" +) + +func TestEnglishChecksum(t *testing.T) { + t.Parallel() + + // Ensure word list is correct + // $ wget https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt + // $ crc32 english.txt + // c1dbd296 + checksum := crc32.ChecksumIEEE([]byte(english)) + if checksum != 0xc1dbd296 { + t.Error("english checksum invalid") + } +} diff --git a/internal/bip39/example_test.go b/internal/bip39/example_test.go new file mode 100644 index 0000000..5048cf9 --- /dev/null +++ b/internal/bip39/example_test.go @@ -0,0 +1,30 @@ +package bip39_test + +import ( + "encoding/hex" + "fmt" + + "sneak.berlin/go/keyfunc/internal/bip39" +) + +//nolint:lll // the test vector and its output, kept as upstream wrote them +func ExampleNewMnemonic() { + // the entropy can be any byte slice, generated how pleased, + // as long its bit size is a multiple of 32 and is within + // the inclusive range of {128,256} + entropy, _ := hex.DecodeString("066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad") + + // generate a mnemomic + mnemomic, _ := bip39.NewMnemonic(entropy) + fmt.Println(mnemomic) + // output: + // all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform +} + +//nolint:lll // the test vector and its output, kept as upstream wrote them +func ExampleNewSeed() { + seed := bip39.NewSeed("all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", "TREZOR") + fmt.Println(hex.EncodeToString(seed)) + // output: + // 26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d +} diff --git a/internal/childmnemonic/childmnemonic.go b/internal/childmnemonic/childmnemonic.go index a651666..c4d6b89 100644 --- a/internal/childmnemonic/childmnemonic.go +++ b/internal/childmnemonic/childmnemonic.go @@ -7,7 +7,7 @@ import ( "git.eeqj.de/sneak/secret/pkg/bip85" "github.com/btcsuite/btcd/btcutil/hdkeychain" - bip39 "github.com/tyler-smith/go-bip39" + "sneak.berlin/go/keyfunc/internal/bip39" "sneak.berlin/go/keyfunc/internal/derive" ) diff --git a/internal/childmnemonic/childmnemonic_test.go b/internal/childmnemonic/childmnemonic_test.go index 0075a24..7c216d6 100644 --- a/internal/childmnemonic/childmnemonic_test.go +++ b/internal/childmnemonic/childmnemonic_test.go @@ -6,7 +6,7 @@ import ( "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/stretchr/testify/require" - bip39 "github.com/tyler-smith/go-bip39" + "sneak.berlin/go/keyfunc/internal/bip39" "sneak.berlin/go/keyfunc/internal/childmnemonic" "sneak.berlin/go/keyfunc/internal/derive" ) diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index d07b08c..0b29adc 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/stretchr/testify/require" - bip39 "github.com/tyler-smith/go-bip39" "golang.org/x/crypto/ssh" + "sneak.berlin/go/keyfunc/internal/bip39" "sneak.berlin/go/keyfunc/internal/childmnemonic" "sneak.berlin/go/keyfunc/internal/cli" "sneak.berlin/go/keyfunc/internal/derive" diff --git a/internal/derive/derive.go b/internal/derive/derive.go index bd95906..cc98fa3 100644 --- a/internal/derive/derive.go +++ b/internal/derive/derive.go @@ -8,7 +8,7 @@ import ( "git.eeqj.de/sneak/secret/pkg/bip85" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg" - bip39 "github.com/tyler-smith/go-bip39" + "sneak.berlin/go/keyfunc/internal/bip39" ) const ( diff --git a/internal/mnemonic/mnemonic.go b/internal/mnemonic/mnemonic.go index fdce914..c276074 100644 --- a/internal/mnemonic/mnemonic.go +++ b/internal/mnemonic/mnemonic.go @@ -10,8 +10,8 @@ import ( "os/exec" "strings" - bip39 "github.com/tyler-smith/go-bip39" "golang.org/x/term" + "sneak.berlin/go/keyfunc/internal/bip39" ) const (