package mnemonic_test import ( "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/require" "sneak.berlin/go/keyfunc/internal/mnemonic" ) // Two more mnemonics that pass the checksum, so a test can tell which // source an answer came from. const ( fromCommandVariable = "legal winner thank year wave sausage worth " + "useful legal winner thank yellow" fromVariable = "letter advice cage absurd amount doctor acoustic " + "avoid letter advice cage above" ) // example returns the mnemonic every BIP-39 document uses to show its // test vectors: eleven abandons and about. func example() string { return strings.Repeat("abandon ", 11) + "about" } // broken returns a mnemonic whose last word does not match the rest. func broken() string { return strings.TrimSpace(strings.Repeat("abandon ", 12)) } // prints builds a shell command that writes the given words padded // with spaces, so the test also shows that the padding is dropped. func prints(words string) string { return "printf ' %s \\n' '" + words + "'" } func TestTheCommandOnTheCommandLineWins(t *testing.T) { t.Setenv(mnemonic.CommandVariable, prints(fromCommandVariable)) t.Setenv(mnemonic.Variable, fromVariable) words, err := mnemonic.Read(t.Context(), prints(example())) require.NoError(t, err) require.Equal(t, example(), words) } func TestTheCommandInTheEnvironmentComesNext(t *testing.T) { t.Setenv(mnemonic.CommandVariable, prints(fromCommandVariable)) t.Setenv(mnemonic.Variable, fromVariable) words, err := mnemonic.Read(t.Context(), "") require.NoError(t, err) require.Equal(t, fromCommandVariable, words) } func TestTheMnemonicInTheEnvironmentComesLast(t *testing.T) { t.Setenv(mnemonic.CommandVariable, "") t.Setenv(mnemonic.Variable, " "+fromVariable+" ") words, err := mnemonic.Read(t.Context(), "") require.NoError(t, err) require.Equal(t, fromVariable, words) } func TestTheMnemonicIsNotHandedToTheCommand(t *testing.T) { const marker = "KEYFUNC_TEST_MARKER" t.Setenv(mnemonic.CommandVariable, prints(fromCommandVariable)) t.Setenv(mnemonic.Variable, fromVariable) t.Setenv(marker, "reaches the command") path := filepath.Join(t.TempDir(), "environment") words, err := mnemonic.Read( t.Context(), "env > '"+path+"'; "+prints(example()), ) require.NoError(t, err) require.Equal(t, example(), words) //nolint:gosec // the path is a temporary file of the test's own content, err := os.ReadFile(path) require.NoError(t, err) environment := string(content) require.NotContains(t, environment, mnemonic.Variable+"=") require.NotContains(t, environment, mnemonic.CommandVariable+"=") require.Contains(t, environment, marker+"=") } func TestAFailingCommandSaysWhatWentWrong(t *testing.T) { t.Setenv(mnemonic.CommandVariable, "") t.Setenv(mnemonic.Variable, "") _, err := mnemonic.Read(t.Context(), "echo nothing here >&2; exit 3") require.Error(t, err) require.Contains(t, err.Error(), "nothing here") } func TestValidMnemonicsOf15To24WordsAreAccepted(t *testing.T) { t.Setenv(mnemonic.CommandVariable, "") // abandon repeated, then the last word that makes the checksum // match: 15, 18, 21 and 24 words. valid := []string{ strings.Repeat("abandon ", 14) + "address", strings.Repeat("abandon ", 17) + "agent", strings.Repeat("abandon ", 20) + "admit", strings.Repeat("abandon ", 23) + "art", } for _, words := range valid { t.Setenv(mnemonic.Variable, words) got, err := mnemonic.Read(t.Context(), "") require.NoError(t, err) require.Equal(t, words, got) } } func TestABadChecksumIsRefused(t *testing.T) { t.Setenv(mnemonic.CommandVariable, "") t.Setenv(mnemonic.Variable, broken()) _, err := mnemonic.Read(t.Context(), "") require.ErrorIs(t, err, mnemonic.ErrChecksum) } func TestACommandThatPrintsNothingGivesAnEmptyMnemonic(t *testing.T) { t.Setenv(mnemonic.CommandVariable, "") t.Setenv(mnemonic.Variable, "") _, err := mnemonic.Read(t.Context(), "true") require.ErrorIs(t, err, mnemonic.ErrEmpty) require.NotContains(t, err.Error(), "checksum") } func TestAWrongWordCountIsRefused(t *testing.T) { t.Setenv(mnemonic.CommandVariable, "") t.Setenv(mnemonic.Variable, strings.Repeat("abandon ", 3)) _, err := mnemonic.Read(t.Context(), "") require.ErrorIs(t, err, mnemonic.ErrWordCount) require.ErrorContains(t, err, "not 3") require.NotContains(t, err.Error(), "checksum") } func TestAWordNotOnTheListIsNamedByPositionOnly(t *testing.T) { const typo = "abuot" t.Setenv(mnemonic.CommandVariable, "") t.Setenv(mnemonic.Variable, strings.Repeat("abandon ", 11)+typo) _, err := mnemonic.Read(t.Context(), "") require.ErrorIs(t, err, mnemonic.ErrUnknownWord) require.ErrorContains(t, err, "word 12 ") require.NotContains(t, err.Error(), typo) require.NotContains(t, err.Error(), "checksum") } func TestNothingToReadAndNobodyToAsk(t *testing.T) { t.Setenv(mnemonic.CommandVariable, "") t.Setenv(mnemonic.Variable, "") _, err := mnemonic.Read(t.Context(), "") require.ErrorIs(t, err, mnemonic.ErrMissing) }