Re-implement bip39

This commit is contained in:
Simon Warta 2022-01-26 23:52:28 +01:00
parent fab55d3807
commit 64d60c7083
12 changed files with 2523 additions and 353 deletions

41
.pnp.cjs generated
View File

@ -573,6 +573,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"@types/pako", "@types/pako",
"npm:1.0.1" "npm:1.0.1"
], ],
[
"@types/pbkdf2",
"npm:3.1.0"
],
[ [
"@types/qs", "@types/qs",
"npm:6.9.6" "npm:6.9.6"
@ -841,10 +845,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"bindings", "bindings",
"npm:1.5.0" "npm:1.5.0"
], ],
[
"bip39",
"npm:3.0.4"
],
[ [
"bl", "bl",
"npm:4.1.0" "npm:4.1.0"
@ -3357,9 +3357,9 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@types/karma-jasmine-html-reporter", "npm:1.5.1"], ["@types/karma-jasmine-html-reporter", "npm:1.5.1"],
["@types/libsodium-wrappers", "npm:0.7.9"], ["@types/libsodium-wrappers", "npm:0.7.9"],
["@types/node", "npm:15.3.1"], ["@types/node", "npm:15.3.1"],
["@types/pbkdf2", "npm:3.1.0"],
["@typescript-eslint/eslint-plugin", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.28.4"], ["@typescript-eslint/eslint-plugin", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.28.4"],
["@typescript-eslint/parser", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.28.4"], ["@typescript-eslint/parser", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.28.4"],
["bip39", "npm:3.0.4"],
["bn.js", "npm:5.2.0"], ["bn.js", "npm:5.2.0"],
["buffer", "npm:6.0.3"], ["buffer", "npm:6.0.3"],
["elliptic", "npm:6.5.4"], ["elliptic", "npm:6.5.4"],
@ -3381,6 +3381,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["karma-jasmine-html-reporter", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:1.6.0"], ["karma-jasmine-html-reporter", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:1.6.0"],
["libsodium-wrappers", "npm:0.7.9"], ["libsodium-wrappers", "npm:0.7.9"],
["nyc", "npm:15.1.0"], ["nyc", "npm:15.1.0"],
["pbkdf2", "npm:3.1.2"],
["prettier", "npm:2.4.1"], ["prettier", "npm:2.4.1"],
["ses", "npm:0.11.1"], ["ses", "npm:0.11.1"],
["source-map-support", "npm:0.5.19"], ["source-map-support", "npm:0.5.19"],
@ -4716,13 +4717,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}] }]
]], ]],
["@types/node", [ ["@types/node", [
["npm:11.11.6", {
"packageLocation": "./.yarn/cache/@types-node-npm-11.11.6-40abad0842-075f1c011c.zip/node_modules/@types/node/",
"packageDependencies": [
["@types/node", "npm:11.11.6"]
],
"linkType": "HARD",
}],
["npm:13.13.52", { ["npm:13.13.52", {
"packageLocation": "./.yarn/cache/@types-node-npm-13.13.52-95159539bb-8f1afff497.zip/node_modules/@types/node/", "packageLocation": "./.yarn/cache/@types-node-npm-13.13.52-95159539bb-8f1afff497.zip/node_modules/@types/node/",
"packageDependencies": [ "packageDependencies": [
@ -4764,6 +4758,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD", "linkType": "HARD",
}] }]
]], ]],
["@types/pbkdf2", [
["npm:3.1.0", {
"packageLocation": "./.yarn/cache/@types-pbkdf2-npm-3.1.0-9fa74ff7fb-d15024b195.zip/node_modules/@types/pbkdf2/",
"packageDependencies": [
["@types/pbkdf2", "npm:3.1.0"],
["@types/node", "npm:15.3.1"]
],
"linkType": "HARD",
}]
]],
["@types/qs", [ ["@types/qs", [
["npm:6.9.6", { ["npm:6.9.6", {
"packageLocation": "./.yarn/cache/@types-qs-npm-6.9.6-2fc5ce36d4-01871b1cf7.zip/node_modules/@types/qs/", "packageLocation": "./.yarn/cache/@types-qs-npm-6.9.6-2fc5ce36d4-01871b1cf7.zip/node_modules/@types/qs/",
@ -6287,19 +6291,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD", "linkType": "HARD",
}] }]
]], ]],
["bip39", [
["npm:3.0.4", {
"packageLocation": "./.yarn/cache/bip39-npm-3.0.4-7c69c9182f-79ce1600a0.zip/node_modules/bip39/",
"packageDependencies": [
["bip39", "npm:3.0.4"],
["@types/node", "npm:11.11.6"],
["create-hash", "npm:1.2.0"],
["pbkdf2", "npm:3.1.2"],
["randombytes", "npm:2.1.0"]
],
"linkType": "HARD",
}]
]],
["bl", [ ["bl", [
["npm:4.1.0", { ["npm:4.1.0", {
"packageLocation": "./.yarn/cache/bl-npm-4.1.0-7f94cdcf3f-9e8521fa7e.zip/node_modules/bl/", "packageLocation": "./.yarn/cache/bl-npm-4.1.0-7f94cdcf3f-9e8521fa7e.zip/node_modules/bl/",

Binary file not shown.

BIN
.yarn/cache/@types-pbkdf2-npm-3.1.0-9fa74ff7fb-d15024b195.zip (Stored with Git LFS) vendored Normal file

Binary file not shown.

BIN
.yarn/cache/bip39-npm-3.0.4-7c69c9182f-79ce1600a0.zip (Stored with Git LFS) vendored

Binary file not shown.

3
NOTICE
View File

@ -23,6 +23,9 @@ The code in packages/json-rpc was forked from https://github.com/iov-one/iov-cor
The code in packages/socket and scripts/socketserver was forked from the folders packages/iov-socket and scripts/socketserver respectively of https://github.com/iov-one/iov-core at tag v2.5.0 on 2020-06-24. The code in packages/socket and scripts/socketserver was forked from the folders packages/iov-socket and scripts/socketserver respectively of https://github.com/iov-one/iov-core at tag v2.5.0 on 2020-06-24.
The code in packages/crypto/src/bip39.ts contains code forked from bitcoinjs/bip39 (https://github.com/bitcoinjs/bip39/blob/v3.0.4/LICENSE)
on 2021-12-16. Copyright (c) 2014, Wei Lu and Daniel Cousens.
Copyright 2018-2020 IOV SAS Copyright 2018-2020 IOV SAS
Copyright 2020-2021 Confio OÜ Copyright 2020-2021 Confio OÜ
Copyright 2020 Simon Warta Copyright 2020 Simon Warta

View File

@ -45,10 +45,10 @@
"@cosmjs/math": "workspace:packages/math", "@cosmjs/math": "workspace:packages/math",
"@cosmjs/utils": "workspace:packages/utils", "@cosmjs/utils": "workspace:packages/utils",
"@noble/hashes": "^1", "@noble/hashes": "^1",
"bip39": "^3.0.2",
"bn.js": "^5.2.0", "bn.js": "^5.2.0",
"elliptic": "^6.5.3", "elliptic": "^6.5.3",
"libsodium-wrappers": "^0.7.6" "libsodium-wrappers": "^0.7.6",
"pbkdf2": "^3.1.2"
}, },
"devDependencies": { "devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1", "@istanbuljs/nyc-config-typescript": "^1.0.1",
@ -61,6 +61,7 @@
"@types/karma-jasmine-html-reporter": "^1", "@types/karma-jasmine-html-reporter": "^1",
"@types/libsodium-wrappers": "^0.7.7", "@types/libsodium-wrappers": "^0.7.7",
"@types/node": "^15.0.1", "@types/node": "^15.0.1",
"@types/pbkdf2": "^3.1.0",
"@typescript-eslint/eslint-plugin": "^4.28", "@typescript-eslint/eslint-plugin": "^4.28",
"@typescript-eslint/parser": "^4.28", "@typescript-eslint/parser": "^4.28",
"buffer": "^6.0.3", "buffer": "^6.0.3",

View File

@ -1,8 +1,49 @@
import { fromHex } from "@cosmjs/encoding"; import { fromAscii, fromBase64, fromHex } from "@cosmjs/encoding";
import { Bip39 } from "./bip39"; import { Bip39, EnglishMnemonic, entropyToMnemonic, mnemonicToEntropy } from "./bip39";
import { EnglishMnemonic } from "./englishmnemonic"; import { sha256 } from "./sha";
import bip39Vectors from "./testdata/bip39.json"; 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)).toString()).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("Bip39", () => {
describe("encode", () => { describe("encode", () => {
@ -414,3 +455,240 @@ describe("Bip39", () => {
}); });
}); });
}); });
describe("EnglishMnemonic", () => {
describe("wordlist", () => {
it("matches the words from the bitcoin/bips/bip-0039 spec", () => {
const lineFeed = 0x0a;
const bip39EnglishTxt = fromBase64(wordlists.english);
// Ensure we loaded the correct english.txt from https://github.com/bitcoin/bips/tree/master/bip-0039
const checksum = sha256(bip39EnglishTxt);
expect(checksum).toEqual(fromHex("2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda"));
const wordsFromSpec: string[] = [];
let start = 0; // the start cursor marks the first byte of the word
let end = 0; // the end cursor marks the line feed byte
while (end < bip39EnglishTxt.length - 1) {
end = start;
while (bip39EnglishTxt[end] !== lineFeed) end++;
const slice = bip39EnglishTxt.slice(start, end);
wordsFromSpec.push(fromAscii(slice));
start = end + 1;
}
expect(EnglishMnemonic.wordlist).toEqual(wordsFromSpec);
});
});
it("works for valid inputs", () => {
expect(() => {
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon address",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon admit",
);
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 art",
);
}).not.toThrow();
});
it("rejects invalid whitespacing", () => {
// extra space (leading, middle, trailing)
expect(
() =>
new EnglishMnemonic(
" abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ",
),
).toThrowError(/invalid mnemonic format/i);
// newline, tab
expect(
() =>
new EnglishMnemonic(
"abandon\nabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon\tabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
});
it("rejects disallowed letters", () => {
// Disallowed letters in words (capital, number, special char)
expect(
() =>
new EnglishMnemonic(
"Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon Abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"route66 abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon route66 abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"lötkolben abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon lötkolben abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
});
it("word counts other than 12, 15, 18, 21, 24", () => {
// too few and too many words (11, 13, 17, 19, 23, 25)
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid word count(.*)got: 11/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid word count(.*)got: 13/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);
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);
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);
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);
});
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,
);
expect(
() => new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"),
).toThrowError(/invalid mnemonic 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);
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);
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);
});
it("rejects valid mnemonics of other languages", () => {
// valid Spanish and Italian bip39 mnemonics
expect(
() =>
new EnglishMnemonic(
"humo odio oriente colina taco fingir salto geranio glaciar academia suave vigor",
),
).toThrowError(/contains invalid word/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);
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);
expect(
() =>
new EnglishMnemonic(
"braccio trincea armonia emiro svedese lepre stridulo metallo baldo rasente potassio rilassato",
),
).toThrowError(/contains invalid word/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);
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);
});
describe("toString", () => {
it("works", () => {
const original =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const mnemonic = new EnglishMnemonic(original);
expect(mnemonic.toString()).toEqual(original);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,242 +0,0 @@
import { fromAscii, fromBase64, fromHex } from "@cosmjs/encoding";
import { EnglishMnemonic } from "./englishmnemonic";
import { sha256 } from "./sha";
import wordlists from "./testdata/bip39_wordlists.json";
describe("EnglishMnemonic", () => {
describe("wordlist", () => {
it("matches the words from the bitcoin/bips/bip-0039 spec", () => {
const lineFeed = 0x0a;
const bip39EnglishTxt = fromBase64(wordlists.english);
// Ensure we loaded the correct english.txt from https://github.com/bitcoin/bips/tree/master/bip-0039
const checksum = sha256(bip39EnglishTxt);
expect(checksum).toEqual(fromHex("2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda"));
const wordsFromSpec: string[] = [];
let start = 0; // the start cursor marks the first byte of the word
let end = 0; // the end cursor marks the line feed byte
while (end < bip39EnglishTxt.length - 1) {
end = start;
while (bip39EnglishTxt[end] !== lineFeed) end++;
const slice = bip39EnglishTxt.slice(start, end);
wordsFromSpec.push(fromAscii(slice));
start = end + 1;
}
expect(EnglishMnemonic.wordlist).toEqual(wordsFromSpec);
});
});
it("works for valid inputs", () => {
expect(() => {
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon address",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon admit",
);
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 art",
);
}).not.toThrow();
});
it("rejects invalid whitespacing", () => {
// extra space (leading, middle, trailing)
expect(
() =>
new EnglishMnemonic(
" abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ",
),
).toThrowError(/invalid mnemonic format/i);
// newline, tab
expect(
() =>
new EnglishMnemonic(
"abandon\nabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon\tabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
});
it("rejects disallowed letters", () => {
// Disallowed letters in words (capital, number, special char)
expect(
() =>
new EnglishMnemonic(
"Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon Abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"route66 abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon route66 abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"lötkolben abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon lötkolben abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
});
it("word counts other than 12, 15, 18, 21, 24", () => {
// too few and too many words (11, 13, 17, 19, 23, 25)
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid word count(.*)got: 11/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid word count(.*)got: 13/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);
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);
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);
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);
});
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,
);
expect(
() => new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"),
).toThrowError(/invalid mnemonic 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);
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);
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);
});
it("rejects valid mnemonics of other languages", () => {
// valid Spanish and Italian bip39 mnemonics
expect(
() =>
new EnglishMnemonic(
"humo odio oriente colina taco fingir salto geranio glaciar academia suave vigor",
),
).toThrowError(/contains invalid word/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);
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);
expect(
() =>
new EnglishMnemonic(
"braccio trincea armonia emiro svedese lepre stridulo metallo baldo rasente potassio rilassato",
),
).toThrowError(/contains invalid word/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);
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);
});
describe("toString", () => {
it("works", () => {
const original =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const mnemonic = new EnglishMnemonic(original);
expect(mnemonic.toString()).toEqual(original);
});
});
});

View File

@ -1,39 +0,0 @@
import * as bip39 from "bip39";
export class EnglishMnemonic {
public static readonly wordlist: readonly string[] = bip39.wordlists.english;
// list of space separated lower case words (1 or more)
private static readonly mnemonicMatcher = /^[a-z]+( [a-z]+)*$/;
private readonly data: string;
public constructor(mnemonic: string) {
if (!EnglishMnemonic.mnemonicMatcher.test(mnemonic)) {
throw new Error("Invalid mnemonic format");
}
const words = mnemonic.split(" ");
const allowedWordsLengths: readonly number[] = [12, 15, 18, 21, 24];
if (allowedWordsLengths.indexOf(words.length) === -1) {
throw new Error(
`Invalid word count in mnemonic (allowed: ${allowedWordsLengths} got: ${words.length})`,
);
}
for (const word of words) {
if (EnglishMnemonic.wordlist.indexOf(word) === -1) {
throw new Error("Mnemonic contains invalid word");
}
}
// Throws with informative error message if mnemonic is not valid
bip39.mnemonicToEntropy(mnemonic);
this.data = mnemonic;
}
public toString(): string {
return this.data;
}
}

View File

@ -1,5 +1,4 @@
export { Bip39 } from "./bip39"; export { Bip39, EnglishMnemonic } from "./bip39";
export { EnglishMnemonic } from "./englishmnemonic";
export { HashFunction } from "./hash"; export { HashFunction } from "./hash";
export { Hmac } from "./hmac"; export { Hmac } from "./hmac";
export { Keccak256, keccak256 } from "./keccak"; export { Keccak256, keccak256 } from "./keccak";

View File

@ -462,9 +462,9 @@ __metadata:
"@types/karma-jasmine-html-reporter": ^1 "@types/karma-jasmine-html-reporter": ^1
"@types/libsodium-wrappers": ^0.7.7 "@types/libsodium-wrappers": ^0.7.7
"@types/node": ^15.0.1 "@types/node": ^15.0.1
"@types/pbkdf2": ^3.1.0
"@typescript-eslint/eslint-plugin": ^4.28 "@typescript-eslint/eslint-plugin": ^4.28
"@typescript-eslint/parser": ^4.28 "@typescript-eslint/parser": ^4.28
bip39: ^3.0.2
bn.js: ^5.2.0 bn.js: ^5.2.0
buffer: ^6.0.3 buffer: ^6.0.3
elliptic: ^6.5.3 elliptic: ^6.5.3
@ -486,6 +486,7 @@ __metadata:
karma-jasmine-html-reporter: ^1.5.4 karma-jasmine-html-reporter: ^1.5.4
libsodium-wrappers: ^0.7.6 libsodium-wrappers: ^0.7.6
nyc: ^15.1.0 nyc: ^15.1.0
pbkdf2: ^3.1.2
prettier: ^2.4.1 prettier: ^2.4.1
ses: ^0.11.0 ses: ^0.11.0
source-map-support: ^0.5.19 source-map-support: ^0.5.19
@ -1692,13 +1693,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:11.11.6":
version: 11.11.6
resolution: "@types/node@npm:11.11.6"
checksum: 075f1c011cf568e49701419acbcb55c24906b3bb5a34d9412a3b88f228a7a78401a5ad4d3e1cd6855c99aaea5ef96e37fc86ca097e50f06da92cf822befc1fff
languageName: node
linkType: hard
"@types/node@npm:>=13.7.0": "@types/node@npm:>=13.7.0":
version: 15.9.0 version: 15.9.0
resolution: "@types/node@npm:15.9.0" resolution: "@types/node@npm:15.9.0"
@ -1720,6 +1714,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/pbkdf2@npm:^3.1.0":
version: 3.1.0
resolution: "@types/pbkdf2@npm:3.1.0"
dependencies:
"@types/node": "*"
checksum: d15024b1957c21cf3b8887329d9bd8dfde754cf13a09d76ae25f1391cfc62bb8b8d7b760773c5dbaa748172fba8b3e0c3dbe962af6ccbd69b76df12a48dfba40
languageName: node
linkType: hard
"@types/qs@npm:*": "@types/qs@npm:*":
version: 6.9.6 version: 6.9.6
resolution: "@types/qs@npm:6.9.6" resolution: "@types/qs@npm:6.9.6"
@ -2401,18 +2404,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"bip39@npm:^3.0.2":
version: 3.0.4
resolution: "bip39@npm:3.0.4"
dependencies:
"@types/node": 11.11.6
create-hash: ^1.1.0
pbkdf2: ^3.0.9
randombytes: ^2.0.1
checksum: 79ce1600a03d1ba5053bdd4e6323f9463ec340764c7e52918b6c6b9dca81221940f2d9a65656447f108f9bc2c8d9ae8df319cca83bbd1dad63f53ef2768d9bae
languageName: node
linkType: hard
"bl@npm:^4.0.3": "bl@npm:^4.0.3":
version: 4.1.0 version: 4.1.0
resolution: "bl@npm:4.1.0" resolution: "bl@npm:4.1.0"
@ -5947,7 +5938,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"pbkdf2@npm:^3.0.9": "pbkdf2@npm:^3.1.2":
version: 3.1.2 version: 3.1.2
resolution: "pbkdf2@npm:3.1.2" resolution: "pbkdf2@npm:3.1.2"
dependencies: dependencies:
@ -6210,7 +6201,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"randombytes@npm:^2.0.1, randombytes@npm:^2.1.0": "randombytes@npm:^2.1.0":
version: 2.1.0 version: 2.1.0
resolution: "randombytes@npm:2.1.0" resolution: "randombytes@npm:2.1.0"
dependencies: dependencies: