Rename module to sneak.berlin/go/x and rework bip39wordlist
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.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user