Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .yarn/cache/@scure-bip39-npm-1.6.0-63a27ac0b7-73a54b5566.zip
Git LFS file not shown
1 change: 1 addition & 0 deletions packages/crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@noble/ciphers": "^1.3.0",
"@noble/curves": "^1.9.2",
"@noble/hashes": "^1.8.0",
"@scure/bip39": "^1.6.0",
"hash-wasm": "^4.12.0"
},
"devDependencies": {
Expand Down
114 changes: 37 additions & 77 deletions packages/crypto/src/bip39.spec.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,10 @@
import { fromAscii, fromBase64, fromHex } from "@cosmjs/encoding";

import { Bip39, EnglishMnemonic, entropyToMnemonic, mnemonicToEntropy } from "./bip39";
import { Bip39, EnglishMnemonic } from "./bip39";
import { sha256 } from "./sha";
import bip39Vectors from "./testdata/bip39.json";
import wordlists from "./testdata/bip39_wordlists.json";

describe("entropyToMnemonic", () => {
it("works", () => {
// From https://iancoleman.io/bip39/
expect(entropyToMnemonic(fromHex("a323224e6b13d31942509dc4e2e579be3d5bb7f2"))).toEqual(
"permit boil near stomach diamond million announce beauty shaft blame fury ladder stick swim slab",
);
});

it("works for all the test vectors", () => {
// Test vectors from https://github.com/trezor/python-mnemonic/blob/b502451a33a440783926e04428115e0bed87d01f/vectors.json
// plus similar vectors generated for the missing lengths 15 and 21 words
const { "12": vec12, "15": vec15, "18": vec18, "21": vec21, "24": vec24 } = bip39Vectors.encoding;
for (const vectors of [vec12, vec15, vec18, vec21, vec24]) {
for (const { entropy, mnemonic } of vectors) {
expect(entropyToMnemonic(fromHex(entropy))).toEqual(mnemonic);
}
}
});
});

describe("mnemonicToEntropy", () => {
it("works", () => {
// From https://iancoleman.io/bip39/
expect(
mnemonicToEntropy(
"permit boil near stomach diamond million announce beauty shaft blame fury ladder stick swim slab",
),
).toEqual(fromHex("a323224e6b13d31942509dc4e2e579be3d5bb7f2"));
});

it("works for all the test vectors", () => {
const { "12": vec12, "15": vec15, "18": vec18, "21": vec21, "24": vec24 } = bip39Vectors.encoding;
for (const vectors of [vec12, vec15, vec18, vec21, vec24]) {
for (const { entropy, mnemonic } of vectors) {
expect(mnemonicToEntropy(mnemonic)).toEqual(fromHex(entropy));
}
}
});
});

describe("Bip39", () => {
describe("encode", () => {
it("can encode to mnemonic", () => {
Expand All @@ -60,26 +20,26 @@ describe("Bip39", () => {

it("throws for invalid input", () => {
// invalid input length
expect(() => Bip39.encode(fromHex(""))).toThrowError(/invalid input length/);
expect(() => Bip39.encode(fromHex("00"))).toThrowError(/invalid input length/);
expect(() => Bip39.encode(fromHex(""))).toThrowError(/got length/);
expect(() => Bip39.encode(fromHex("00"))).toThrowError(/got length/);
expect(() => Bip39.encode(fromHex("000000000000000000000000000000"))).toThrowError(
/invalid input length/,
/expected of length .*, got length=15/,
);
expect(() => Bip39.encode(fromHex("0000000000000000000000000000000000"))).toThrowError(
/invalid input length/,
/expected of length .*, got length=17/,
);
expect(() => Bip39.encode(fromHex("0000000000000000000000000000000000000000000000"))).toThrowError(
/invalid input length/,
/got length/,
);
expect(() => Bip39.encode(fromHex("00000000000000000000000000000000000000000000000000"))).toThrowError(
/invalid input length/,
/got length/,
);
expect(() =>
Bip39.encode(fromHex("00000000000000000000000000000000000000000000000000000000000000")),
).toThrowError(/invalid input length/);
).toThrowError(/got length/);
expect(() =>
Bip39.encode(fromHex("000000000000000000000000000000000000000000000000000000000000000000")),
).toThrowError(/invalid input length/);
).toThrowError(/got length/);
});
});

Expand Down Expand Up @@ -509,33 +469,33 @@ describe("EnglishMnemonic", () => {
new EnglishMnemonic(
" abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/invalid mnemonic/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/invalid mnemonic/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/invalid mnemonic/i);

// newline, tab
expect(
() =>
new EnglishMnemonic(
"abandon\nabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/invalid mnemonic/i);
expect(
() =>
new EnglishMnemonic(
"abandon\tabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/invalid mnemonic/i);
});

it("rejects disallowed letters", () => {
Expand All @@ -545,37 +505,37 @@ describe("EnglishMnemonic", () => {
new EnglishMnemonic(
"Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/unknown letter/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon Abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/unknown letter/i);
expect(
() =>
new EnglishMnemonic(
"route66 abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/unknown letter/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon route66 abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/unknown letter/i);
expect(
() =>
new EnglishMnemonic(
"lötkolben abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/unknown letter/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon lötkolben abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
).toThrowError(/unknown letter/i);
});

it("word counts other than 12, 15, 18, 21, 24", () => {
Expand All @@ -585,62 +545,62 @@ describe("EnglishMnemonic", () => {
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid word count(.*)got: 11/i);
).toThrowError(/invalid/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid word count(.*)got: 13/i);
).toThrowError(/invalid/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
),
).toThrowError(/invalid word count(.*)got: 17/i);
).toThrowError(/invalid/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
),
).toThrowError(/invalid word count(.*)got: 19/i);
).toThrowError(/invalid/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
),
).toThrowError(/invalid word count(.*)got: 23/i);
).toThrowError(/invalid/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
),
).toThrowError(/invalid word count(.*)got: 25/i);
).toThrowError(/invalid/i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the error test tests more specific here? "invalid" could be anything

Copy link
Contributor Author

@dynst dynst Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In testing, the full error message here wasn't actually any more specific, so in a literal sense we can't. It's just two words, 'Invalid mnemonic'. Which could mean anything.

https://github.com/paulmillr/scure-bip39/blob/1.6.0/src/index.ts#L52

});

it("rejects invalid checksums", () => {
// 12x, 15x, 18x, 21x, 24x "zoo"
expect(() => new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo")).toThrowError(
/invalid mnemonic checksum/i,
/invalid checksum/i,
);
expect(
() => new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"),
).toThrowError(/invalid mnemonic checksum/i);
).toThrowError(/invalid checksum/i);
expect(
() => new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"),
).toThrowError(/invalid mnemonic checksum/i);
).toThrowError(/invalid checksum/i);
expect(
() =>
new EnglishMnemonic(
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo",
),
).toThrowError(/invalid mnemonic checksum/i);
).toThrowError(/invalid checksum/i);
expect(
() =>
new EnglishMnemonic(
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo",
),
).toThrowError(/invalid mnemonic checksum/i);
).toThrowError(/invalid checksum/i);
});

it("rejects valid mnemonics of other languages", () => {
Expand All @@ -650,37 +610,37 @@ describe("EnglishMnemonic", () => {
new EnglishMnemonic(
"humo odio oriente colina taco fingir salto geranio glaciar academia suave vigor",
),
).toThrowError(/contains invalid word/i);
).toThrowError(/unknown letter: "humo". allowed: abandon,ability,able,/i);
expect(
() =>
new EnglishMnemonic(
"yema folleto tos llave obtener natural fruta deseo laico sopa novato lazo imponer afinar vena hoja zarza cama",
),
).toThrowError(/contains invalid word/i);
).toThrowError(/unknown letter: "yema"/i);
expect(
() =>
new EnglishMnemonic(
"burla plaza arroz ronda pregunta vacuna veloz boina retiro exento prensa tortuga cabeza pilar anual molino molde fiesta masivo jefe leve fatiga clase plomo",
),
).toThrowError(/contains invalid word/i);
).toThrowError(/unknown letter/i);
expect(
() =>
new EnglishMnemonic(
"braccio trincea armonia emiro svedese lepre stridulo metallo baldo rasente potassio rilassato",
),
).toThrowError(/contains invalid word/i);
).toThrowError(/unknown letter/i);
expect(
() =>
new EnglishMnemonic(
"riparato arrosto globulo singolo bozzolo roba pirolisi ultimato padrone munto leggero avanzato monetario guanto lorenzo latino inoltrare modulo",
),
).toThrowError(/contains invalid word/i);
).toThrowError(/unknown letter/i);
expect(
() =>
new EnglishMnemonic(
"promessa mercurio spessore snodo trave risata mecenate vichingo ceto orecchino vissuto risultato canino scarso futile fune epilogo uovo inedito apatico folata egoismo rifugio coma",
),
).toThrowError(/contains invalid word/i);
).toThrowError(/unknown letter/i);
});

describe("toString", () => {
Expand Down
Loading
Loading