andreashuber69/verify-coldcard-dice-seed

View on GitHub
src/package/main.ts

Summary

Maintainability
A
0 mins
Test Coverage
#!/usr/bin/env node
// https://github.com/andreashuber69/verify-coldcard-dice-seed/blob/develop/README.md#----verify-coldcard-dice-seed
import { createRequire } from "node:module";
import { ReadStream } from "node:tty";
import { AbortError } from "./AbortError.js";
import { readDiceRolls } from "./readDiceRolls.js";
import { readPassphrase } from "./readPassphrase.js";
import { showAddresses } from "./showAddresses.js";
import { verifyWords } from "./verifyWords.js";
import { waitForUser } from "./waitForUser.js";

const { stdin, stdout } = process;

try {
    // Simple typescript alternatives to calling require below lead to the outDir containing the file package.json and
    // the directory src with all the code. This is due to how the ts compiler automatically determines the rootDir from
    // imports. There are alternatives to calling require, but these seem overly complicated:
    // https://stackoverflow.com/questions/58172911/typescript-compiler-options-trying-to-get-flat-output-to-outdir
    const { version } = createRequire(import.meta.url)("../../package.json") as { readonly version: string };

    if (!(stdin instanceof ReadStream)) {
        throw new TypeError("stdin is not an instance of tty.ReadStream");
    }

    stdin.pause();
    stdin.setRawMode(true);
    stdin.setEncoding("utf8");

    stdout.write(`*** Verify COLDCARD Dice Seed v${version} ***\r\n`);
    stdout.write("(tested with COLDCARD Mk4 firmware v5.3.2)\r\n");
    stdout.write("\r\n");
    stdout.write("This application guides you through verifying that your COLDCARD\r\n");
    stdout.write("correctly derives seeds and addresses from dice rolls.\r\n");
    stdout.write("\r\n");
    stdout.write("CAUTION: The very point of a COLDCARD is that the seed of a real wallet\r\n");
    stdout.write("*never* appears on any other device. You should therefore only use this\r\n");
    stdout.write("application to verify the seed and address derivation of your COLDCARD.\r\n");
    stdout.write("Once you are convinced that your COLDCARD works correctly, you should\r\n");
    stdout.write("then generate the seed of your real wallet on your COLDCARD only.\r\n");
    stdout.write("\r\n");

    const generate24WordsPrompt = "Generate 24 instead of the standard 12 words [y, N]? ";
    const generate24Words = (await waitForUser({ stdin, stdout }, generate24WordsPrompt)).toLocaleLowerCase() === "y";
    const wordCount = generate24Words ? 24 : 12;
    stdout.write("Log into your COLDCARD, select 'New Seed Words', 'Advanced',\r\n");
    stdout.write(`'${wordCount} Word Dice Roll'.\r\n`);
    await waitForUser(process);
    const words = await verifyWords(process, await readDiceRolls(process, generate24Words), wordCount);
    let currentPassphrase = "";

    // eslint-disable-next-line no-constant-condition
    while (true) {
        /* eslint-disable no-await-in-loop */
        const newPassphrase = await readPassphrase(process);
        stdout.write("\r\n");

        if (newPassphrase !== currentPassphrase) {
            currentPassphrase = newPassphrase;
            stdout.write("On your COLDCARD, select 'Passphrase', press the OK button and enter the\r\n");
            stdout.write("same passphrase. Select 'APPLY', and press the OK button.\r\n");
            await waitForUser(process);
        }

        await showAddresses(process, words, currentPassphrase);
        /* eslint-enable no-await-in-loop */
    }
} catch (error: unknown) {
    if (!(error instanceof AbortError)) {
        console.error(error);
        process.exitCode = 1;
    }
} finally {
    stdout.write("\r\n\r\n");
    stdout.write("CAUTION: If you've set up your COLDCARD with a seed please clear it now\r\n");
    stdout.write("by first going back to the main menu (press the X button as many times\r\n");
    stdout.write("as necessary) and then selecting 'Advanced/Tools', 'Danger Zone',\r\n");
    stdout.write("'Seed Functions', 'Destroy Seed'.\r\n");
}