Skip to content

Commit a4596dd

Browse files
Merge branch 'release/v1.0.31'
2 parents f5fef18 + b5e450b commit a4596dd

17 files changed

+198
-167
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ COLDCARD copy correctly derives the seed but then generates addresses from a dif
130130
``` shell_session cSpell:disable
131131
$ npx verify-coldcard-dice-seed@latest
132132
Need to install the following packages:
133-
verify-coldcard-dice-seed@1.0.30
133+
verify-coldcard-dice-seed@1.0.31
134134
Ok to proceed? (y) y
135-
*** Verify COLDCARD Dice Seed v1.0.30 ***
135+
*** Verify COLDCARD Dice Seed v1.0.31 ***
136136
(tested with COLDCARD Mk4 firmware v5.2.2)
137137
138138
This application guides you through verifying that your COLDCARD

package-lock.json

Lines changed: 23 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "verify-coldcard-dice-seed",
3-
"version": "1.0.30",
3+
"version": "1.0.31",
44
"description": "Verify whether your COLDCARD correctly generates seeds and addresses from dice rolls.",
55
"keywords": [
66
"bitcoin",
@@ -65,8 +65,8 @@
6565
"@andreashuber69/eslint-config": "^1.2.6",
6666
"@picocss/pico": "^1.5.13",
6767
"@preact/preset-vite": "^2.8.2",
68-
"@tsconfig/node20": "^20.1.2",
69-
"@tsconfig/strictest": "^2.0.3",
68+
"@tsconfig/node20": "^20.1.3",
69+
"@tsconfig/strictest": "^2.0.4",
7070
"@types/node": "^20.11.30",
7171
"c8": "^9.1.0",
7272
"coveralls": "^3.1.1",
@@ -76,7 +76,7 @@
7676
"preact": "^10.20.1",
7777
"tsx": "^4.7.1",
7878
"typescript": "^5.4.3",
79-
"vite": "^5.2.4",
79+
"vite": "^5.2.6",
8080
"vite-plugin-node-polyfills": "^0.21.0",
8181
"vite-plugin-wasm": "^3.3.0"
8282
}

src/common/calculateBip39Mnemonic.spec.ts

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import assert from "node:assert";
33
import { describe, it } from "node:test";
44
import { wordlists } from "bip39";
5-
import fetch from "node-fetch";
65

76
import { calculateBip39Mnemonic } from "./calculateBip39Mnemonic.js";
87

@@ -12,16 +11,6 @@ if (!wordlist) {
1211
throw new Error("Missing english wordlist.");
1312
}
1413

15-
const expectWords = async (entropy: string, words: string) => {
16-
const wordCount = words.length === 0 ? 0 : words.split(" ").length;
17-
18-
await it(
19-
entropy,
20-
async () => assert((await calculateBip39Mnemonic(entropy, wordCount, wordlist)).join(" ") === words),
21-
);
22-
};
23-
24-
2514
const expectError = async (entropy: string, newWordlist: readonly string[], errorMessage: string) => await it(
2615
entropy,
2716
async () => {
@@ -34,58 +23,8 @@ const expectError = async (entropy: string, newWordlist: readonly string[], erro
3423
},
3524
);
3625

37-
38-
const response = await fetch("https://raw.githubusercontent.com/trezor/python-mnemonic/master/vectors.json");
39-
40-
if (!response.ok) {
41-
throw new Error("Unexpected response");
42-
}
43-
44-
const vectors = JSON.parse(await response.text()) as Record<string, unknown>;
45-
4626
await describe(calculateBip39Mnemonic.name, async () => {
47-
await describe("should calculate the expected words", async () => {
48-
if (!("english" in vectors) || !Array.isArray(vectors["english"])) {
49-
throw new Error("Unexpected response");
50-
}
51-
52-
for (const vector of vectors["english"]) {
53-
if (!Array.isArray(vector) || (vector.length < 2) ||
54-
(typeof vector[0] !== "string") || (typeof vector[1] !== "string")) {
55-
throw new Error("Unexpected response");
56-
}
57-
58-
// https://github.com/typescript-eslint/typescript-eslint/issues/7464
59-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
60-
const [entropy, words] = vector;
61-
// eslint-disable-next-line no-await-in-loop
62-
await expectWords(entropy, words);
63-
}
64-
65-
await expectWords("", "");
66-
await expectWords("00000000", "abandon abandon ability");
67-
await expectWords("ffffffff", "zoo zoo zoo");
68-
});
69-
7027
await describe("should throw the expected exception", async () => {
71-
await it("ffffffff", async () => {
72-
try {
73-
await calculateBip39Mnemonic("ffffffff", 2, wordlist);
74-
assert(false, "Expected error to be thrown!");
75-
} catch (error: unknown) {
76-
assert(error instanceof RangeError && error.message === "wordCount must be a multiple of 3");
77-
}
78-
});
79-
80-
await it("fffffff", async () => {
81-
try {
82-
await calculateBip39Mnemonic("fffffff", 3, wordlist);
83-
assert(false, "Expected error to be thrown!");
84-
} catch (error: unknown) {
85-
assert(error instanceof RangeError && error.message === "hexEntropy length must be >= 8");
86-
}
87-
});
88-
8928
await expectError("ffffffff", wordlist.slice(1), "wordlist.length is invalid: 2047");
9029
await expectError("ffffffff", wordlist.slice(1024), "wordlist.length is invalid: 1024");
9130
const invalidWordlist = wordlist.slice(-1);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// https://github.com/andreashuber69/verify-coldcard-dice-seed/blob/develop/README.md#----verify-coldcard-dice-seed
2+
import assert from "node:assert";
3+
import { describe, it } from "node:test";
4+
import fetch from "node-fetch";
5+
6+
import { calculateEnglishBip39Mnemonic } from "./calculateEnglishBip39Mnemonic.js";
7+
8+
const expectWords = async (entropy: string, words: string) => {
9+
const wordCount = words.length === 0 ? 0 : words.split(" ").length;
10+
11+
await it(
12+
entropy,
13+
async () => assert((await calculateEnglishBip39Mnemonic(entropy, wordCount)).join(" ") === words),
14+
);
15+
};
16+
17+
const response = await fetch("https://raw.githubusercontent.com/trezor/python-mnemonic/master/vectors.json");
18+
19+
if (!response.ok) {
20+
throw new Error("Unexpected response");
21+
}
22+
23+
const vectors = JSON.parse(await response.text()) as Record<string, unknown>;
24+
25+
await describe(calculateEnglishBip39Mnemonic.name, async () => {
26+
await describe("should calculate the expected words", async () => {
27+
if (!("english" in vectors) || !Array.isArray(vectors["english"])) {
28+
throw new Error("Unexpected response");
29+
}
30+
31+
for (const vector of vectors["english"]) {
32+
if (!Array.isArray(vector) || (vector.length < 2) ||
33+
(typeof vector[0] !== "string") || (typeof vector[1] !== "string")) {
34+
throw new Error("Unexpected response");
35+
}
36+
37+
// https://github.com/typescript-eslint/typescript-eslint/issues/7464
38+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
39+
const [entropy, words] = vector;
40+
// eslint-disable-next-line no-await-in-loop
41+
await expectWords(entropy, words);
42+
}
43+
44+
await expectWords("", "");
45+
await expectWords("00000000", "abandon abandon ability");
46+
await expectWords("ffffffff", "zoo zoo zoo");
47+
});
48+
49+
await describe("should throw the expected exception", async () => {
50+
await it("ffffffff", async () => {
51+
try {
52+
await calculateEnglishBip39Mnemonic("ffffffff", 2);
53+
assert(false, "Expected error to be thrown!");
54+
} catch (error: unknown) {
55+
assert(error instanceof RangeError && error.message === "wordCount must be a multiple of 3");
56+
}
57+
});
58+
59+
await it("fffffff", async () => {
60+
try {
61+
await calculateEnglishBip39Mnemonic("fffffff", 3);
62+
assert(false, "Expected error to be thrown!");
63+
} catch (error: unknown) {
64+
assert(error instanceof RangeError && error.message === "hexEntropy length must be >= 8");
65+
}
66+
});
67+
});
68+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// https://github.com/andreashuber69/verify-coldcard-dice-seed/blob/develop/README.md#----verify-coldcard-dice-seed
2+
import { wordlists } from "bip39";
3+
import { calculateBip39Mnemonic } from "./calculateBip39Mnemonic.js";
4+
5+
const wordlist = wordlists["english"];
6+
7+
if (!wordlist) {
8+
throw new Error("Missing english wordlist.");
9+
}
10+
11+
export const calculateEnglishBip39Mnemonic = async (hexEntropy: string, wordCount: number) =>
12+
await calculateBip39Mnemonic(hexEntropy, wordCount, wordlist);

src/common/getAddresses.spec.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
// https://github.com/andreashuber69/verify-coldcard-dice-seed/blob/develop/README.md#----verify-coldcard-dice-seed
22
import assert from "node:assert";
33
import { describe, it } from "node:test";
4-
import { BIP32Factory } from "bip32";
5-
import { mnemonicToSeed } from "bip39";
6-
// eslint-disable-next-line import/no-namespace
7-
import * as ecc from "tiny-secp256k1";
84
import { getAddresses } from "./getAddresses.js";
9-
10-
const bip32 = BIP32Factory(ecc);
5+
import { getRoot } from "./getRoot.js";
116

127
const getBatch = async (mnemonic: string, passphrase: string, accountRootPath: string, startIndex: number) =>
13-
getAddresses(bip32.fromSeed(await mnemonicToSeed(mnemonic, passphrase)), accountRootPath, startIndex);
8+
getAddresses(await getRoot(mnemonic, passphrase), accountRootPath, startIndex, 10);
149

1510
const rootPath = "m/84'/0'/0'/0";
1611
const getPath = (index: number) => `${rootPath}/${index}`;

src/common/getAddresses.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22
import type { BIP32Interface } from "bip32";
33
import { toBech32Address } from "./toBech32Address.js";
44

5-
type Batch = [readonly [string, string], ...Array<readonly [string, string]>];
5+
type GrowToSize<T, N extends number, A extends T[]> = A["length"] extends N ? A : GrowToSize<T, N, [...A, T]>;
66

7-
export const getAddresses = (root: BIP32Interface, accountRootPath: string, startIndex: number): Readonly<Batch> => {
7+
type Batch<N extends number> = GrowToSize<[string, string], N, []>;
8+
9+
export const getAddresses = <N extends number>(
10+
root: BIP32Interface,
11+
accountRootPath: string,
12+
startIndex: number,
13+
length: N,
14+
) => {
815
const accountRoot = root.derivePath(accountRootPath);
9-
const length = 10;
10-
const result: Batch = [["", ""], ...new Array<[string, string]>(length - 1)];
16+
const result = new Array<[string, string]>(length);
1117

1218
for (let index = startIndex; index < startIndex + length; ++index) {
1319
result[index - startIndex] = [`${accountRootPath}/${index}`, toBech32Address(accountRoot.derive(index))];
1420
}
1521

16-
return result;
22+
return result as Batch<N>;
1723
};

src/common/getRoot.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { BIP32Factory } from "bip32";
2+
import { mnemonicToSeed } from "bip39";
3+
// eslint-disable-next-line import/no-namespace
4+
import * as ecc from "tiny-secp256k1";
5+
6+
const bip32 = BIP32Factory(ecc);
7+
8+
export const getRoot = async (mnemonic: string, passphrase: string) =>
9+
bip32.fromSeed(await mnemonicToSeed(mnemonic, passphrase));

src/package/showAddresses.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
// https://github.com/andreashuber69/verify-coldcard-dice-seed/blob/develop/README.md#----verify-coldcard-dice-seed
2-
import { BIP32Factory } from "bip32";
3-
import { mnemonicToSeed } from "bip39";
4-
// eslint-disable-next-line import/no-namespace
5-
import * as ecc from "tiny-secp256k1";
62
import { getAddresses } from "../common/getAddresses.js";
3+
import { getRoot } from "../common/getRoot.js";
74
import type { InOut } from "./InOut.js";
85
import { waitForUser } from "./waitForUser.js";
96

10-
const bip32 = BIP32Factory(ecc);
11-
127
export const showAddresses = async ({ stdin, stdout }: InOut, words: readonly string[], passphrase: string) => {
138
stdout.write("Select 'Address Explorer' and press the 4 button on your COLDCARD.\r\n");
149
await waitForUser({ stdin, stdout });
15-
const root = bip32.fromSeed(await mnemonicToSeed(words.join(" "), passphrase));
16-
const getBatch = (startIndex: number) => getAddresses(root, "m/84'/0'/0'/0", startIndex);
10+
const root = await getRoot(words.join(" "), passphrase);
11+
const getBatch = (startIndex: number) => getAddresses(root, "m/84'/0'/0'/0", startIndex, 10);
1712

1813
let batchStart = 0;
1914
let batch = getBatch(batchStart);

0 commit comments

Comments
 (0)