package bip39 import ( "bytes" "testing" ) // hexNibble returns the value of a single hex digit, or -1 if invalid. func hexNibble(c byte) int { if c >= '0' && c <= '9' { return int(c - '0') } if c >= 'a' && c <= 'f' { return int(c-'a') + 10 } if c >= 'A' && c <= 'F' { return int(c-'A') + 10 } return -1 } func hexDecode(s string) []byte { if len(s)%2 != 0 { return nil } out := []byte{:len(s)/2} for i := 0; i < len(s); i += 2 { hi := hexNibble(s[i]) lo := hexNibble(s[i+1]) if hi < 0 || lo < 0 { return nil } out[i/2] = byte(hi<<4 | lo) } return out } // TestBIP39EnglishVectors runs a subset of Trezor English vectors covering // entropy → mnemonic and mnemonic → seed. Source: // https://github.com/trezor/python-mnemonic/blob/master/vectors.json // Subset is the 16-byte (128-bit / 12-word) entries. func TestBIP39EnglishVectors(t *testing.T) { type vec struct { entropy string mnemonic string seed string // PBKDF2-SHA512(phrase, "mnemonic"+"TREZOR", 2048, 64) } vectors := []vec{ { 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", }, } const passphrase = "TREZOR" for i, v := range vectors { gotMnemonic := EntropyToMnemonic(hexDecode(v.entropy)) if gotMnemonic != v.mnemonic { t.Errorf("vec %d: mnemonic mismatch\n want %s\n got %s", i, v.mnemonic, gotMnemonic) } if !ValidateMnemonic(v.mnemonic) { t.Errorf("vec %d: ValidateMnemonic returned false on known-good mnemonic", i) } gotSeed := MnemonicToSeed(v.mnemonic, passphrase) wantSeed := hexDecode(v.seed) if !bytes.Equal(gotSeed, wantSeed) { t.Errorf("vec %d: seed mismatch\n want %x\n got %x", i, wantSeed, gotSeed) } } } func TestValidateMnemonicRejection(t *testing.T) { cases := []struct { name string input string }{ {"empty", ""}, {"11 words", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon"}, {"unknown word", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon xyzzy"}, {"bad checksum (zoo at end of all-zero entropy)", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon zoo"}, } for _, c := range cases { if ValidateMnemonic(c.input) { t.Errorf("ValidateMnemonic should have rejected %q (%s)", c.input, c.name) } } }