Move to Go 1.26, embed the wordlist from english.txt, add ValidateWordlist, NormalizeWordlist, and GenerateMnemonic with tests, and remove the old root-level CLI that used the previous module path.
144 lines
4.2 KiB
Go
144 lines
4.2 KiB
Go
package bip39wordlist_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"sneak.berlin/go/x/pkg/bip39wordlist"
|
|
)
|
|
|
|
// vectorRepeats is how many times "abandon" appears in the standard BIP39
|
|
// test vector for all-zero 128-bit entropy, which ends with "about".
|
|
const vectorRepeats = bip39wordlist.MinWordCount - 1
|
|
|
|
// vectorMnemonic returns the standard BIP39 test vector for all-zero
|
|
// 128-bit entropy.
|
|
func vectorMnemonic() string {
|
|
return strings.Repeat("abandon ", vectorRepeats) + "about"
|
|
}
|
|
|
|
// messyVectorMnemonic returns vectorMnemonic with mixed case and irregular
|
|
// whitespace; it must normalize to vectorMnemonic.
|
|
func messyVectorMnemonic() string {
|
|
return " Abandon ABANDON\t" + strings.Repeat("abandon ", 4) + "\n" +
|
|
strings.Repeat("abandon ", 5) + "About \n"
|
|
}
|
|
|
|
func TestValidWordCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, n := range []int{12, 15, 18, 21, 24} {
|
|
assert.True(t, bip39wordlist.ValidWordCount(n), "%d words", n)
|
|
}
|
|
|
|
for _, n := range []int{0, 1, 9, 11, 13, 14, 16, 23, 25, 27, 36} {
|
|
assert.False(t, bip39wordlist.ValidWordCount(n), "%d words", n)
|
|
}
|
|
}
|
|
|
|
func TestValidateWordlistAcceptsValidMnemonics(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require.NoError(t, bip39wordlist.ValidateWordlist(vectorMnemonic()))
|
|
require.NoError(t, bip39wordlist.ValidateWordlist(messyVectorMnemonic()))
|
|
}
|
|
|
|
func TestValidateWordlistRejectsWrongWordCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := bip39wordlist.ValidateWordlist("")
|
|
require.ErrorIs(t, err, bip39wordlist.ErrInvalidWordCount)
|
|
|
|
err = bip39wordlist.ValidateWordlist(vectorMnemonic() + " abandon")
|
|
require.ErrorIs(t, err, bip39wordlist.ErrInvalidWordCount)
|
|
assert.Contains(t, err.Error(), "got 13")
|
|
}
|
|
|
|
func TestValidateWordlistRejectsUnknownWord(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
words := strings.Fields(vectorMnemonic())
|
|
words[4] = "bogus"
|
|
|
|
err := bip39wordlist.ValidateWordlist(strings.Join(words, " "))
|
|
require.ErrorIs(t, err, bip39wordlist.ErrUnknownWord)
|
|
assert.Contains(t, err.Error(), `"bogus" at position 5`)
|
|
}
|
|
|
|
func TestValidateWordlistRejectsBadChecksum(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Twelve times "abandon" has valid words and count but the checksum
|
|
// bits in the last word are wrong.
|
|
allAbandon := strings.Repeat("abandon ", bip39wordlist.MinWordCount)
|
|
|
|
err := bip39wordlist.ValidateWordlist(allAbandon)
|
|
require.ErrorIs(t, err, bip39wordlist.ErrInvalidChecksum)
|
|
}
|
|
|
|
func TestNormalizeWordlistCanonicalizes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
normalized, err := bip39wordlist.NormalizeWordlist(messyVectorMnemonic())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, vectorMnemonic(), normalized)
|
|
|
|
normalized, err = bip39wordlist.NormalizeWordlist(vectorMnemonic())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, vectorMnemonic(), normalized)
|
|
}
|
|
|
|
func TestNormalizeWordlistRefusesInvalid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
normalized, err := bip39wordlist.NormalizeWordlist("Abandon Bogus about")
|
|
require.ErrorIs(t, err, bip39wordlist.ErrInvalidWordCount)
|
|
assert.Empty(t, normalized)
|
|
|
|
normalized, err = bip39wordlist.NormalizeWordlist(
|
|
strings.Repeat("abandon ", bip39wordlist.MinWordCount),
|
|
)
|
|
require.ErrorIs(t, err, bip39wordlist.ErrInvalidChecksum)
|
|
assert.Empty(t, normalized)
|
|
}
|
|
|
|
func TestGenerateMnemonicProducesValidMnemonics(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, wordCount := range []int{12, 15, 18, 21, 24} {
|
|
mnemonic, err := bip39wordlist.GenerateMnemonic(wordCount)
|
|
require.NoError(t, err)
|
|
require.NoError(t, bip39wordlist.ValidateWordlist(mnemonic))
|
|
assert.Len(t, strings.Fields(mnemonic), wordCount)
|
|
|
|
normalized, err := bip39wordlist.NormalizeWordlist(mnemonic)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, mnemonic, normalized, "generated mnemonic is canonical")
|
|
}
|
|
}
|
|
|
|
func TestGenerateMnemonicIsRandom(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
first, err := bip39wordlist.GenerateMnemonic(bip39wordlist.MaxWordCount)
|
|
require.NoError(t, err)
|
|
|
|
second, err := bip39wordlist.GenerateMnemonic(bip39wordlist.MaxWordCount)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotEqual(t, first, second)
|
|
}
|
|
|
|
func TestGenerateMnemonicRejectsInvalidWordCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, wordCount := range []int{0, 11, 13, 25} {
|
|
mnemonic, err := bip39wordlist.GenerateMnemonic(wordCount)
|
|
require.ErrorIs(t, err, bip39wordlist.ErrInvalidWordCount)
|
|
assert.Empty(t, mnemonic)
|
|
}
|
|
}
|