Files
buzz/desktop/src/features/onboarding/lib/keyImportInput.test.mjs
T
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell dde37183e3 Address implementation-review blockers: import ordering, site-granular inventory, uppercase bech32
Blocker 1: import_identity persisted-before-cleanup ordering. New
commit_imported_identity helper runs durable persistence FIRST; a failed
different-key import now leaves both the old in-memory identity and its
valid canonical identity.ncryptsec intact. Stale-backup cleanup runs after
the durable commit and is deliberately best-effort (logged, not surfaced
as a half-applied-import error). Regression tests cover the failure path
and prove cleanup cannot precede persist.

Blocker 2: the /events inventory tripwire is now site-granular. Each
inventoried file pairs an expected non-comment /events occurrence count
with an expected egress-guard call count, so an unguarded ninth site in an
already-listed file (or a deleted guard call) fails the scan. The scan
core is pure over (path, content) pairs, with mutation-style tests
demonstrating all three drift classes.

Hardening: bech32 permits an all-uppercase encoding, so the egress guard
now rejects NCRYPTSEC1... too (mixed case stays unblocked - it cannot
decode), and both import classifiers (Rust recover_keys_from_input, TS
classifyKeyImportInput/isPlausibleNcryptsec) route uppercase-valid blobs
to the encrypted path consistently.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-25 23:45:09 -04:00

67 lines
2.9 KiB
JavaScript

/**
* Pure-logic tests for key-import input classification (nsec vs NIP-49
* ncryptsec) and submit gating.
*/
import assert from "node:assert/strict";
import test from "node:test";
import { nsecEncode } from "nostr-tools/nip19";
import { generateSecretKey } from "nostr-tools/pure";
import {
classifyKeyImportInput,
isPlausibleNcryptsec,
keyImportSubmitEnabled,
} from "./keyImportInput.ts";
// NIP-49 spec vector — structurally valid encrypted backup.
const NCRYPTSEC =
"ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p";
const VALID_NSEC = nsecEncode(generateSecretKey());
test("classify_by_hrp_with_whitespace_tolerance", () => {
assert.equal(classifyKeyImportInput(` ${NCRYPTSEC}\n`), "ncryptsec");
assert.equal(classifyKeyImportInput(VALID_NSEC), "nsec");
assert.equal(classifyKeyImportInput("npub1whatever"), "unknown");
assert.equal(classifyKeyImportInput(""), "unknown");
// nsec must not be shadowed by the longer HRP check.
assert.equal(classifyKeyImportInput("nsec1"), "nsec");
});
test("uppercase_bech32_encoding_classifies_and_gates_like_lowercase", () => {
// Bech32 permits an all-uppercase encoding; it must route to the
// encrypted path (matching Rust) and be submit-plausible.
const upper = NCRYPTSEC.toUpperCase();
assert.equal(classifyKeyImportInput(upper), "ncryptsec");
assert.equal(isPlausibleNcryptsec(upper), true);
assert.equal(keyImportSubmitEnabled(upper, ""), false);
assert.equal(keyImportSubmitEnabled(upper, "hunter2hunter2"), true);
// Mixed case: routed encrypted (Rust reports the accurate error) but
// never plausible/submittable — mixed-case bech32 cannot decode.
const mixed = `N${NCRYPTSEC.slice(1)}`;
assert.equal(classifyKeyImportInput(mixed), "ncryptsec");
assert.equal(isPlausibleNcryptsec(mixed), false);
assert.equal(keyImportSubmitEnabled(mixed, "hunter2hunter2"), false);
});
test("plausible_ncryptsec_requires_bech32_charset", () => {
assert.equal(isPlausibleNcryptsec(NCRYPTSEC), true);
// '1' and 'b' / 'i' / 'o' are not in the bech32 charset.
assert.equal(isPlausibleNcryptsec("ncryptsec1bio"), false);
assert.equal(isPlausibleNcryptsec("ncryptsec1"), false);
assert.equal(isPlausibleNcryptsec("ncryptsec1 with spaces"), false);
});
test("submit_gating_nsec_path_unchanged", () => {
assert.equal(keyImportSubmitEnabled(VALID_NSEC, ""), true);
assert.equal(keyImportSubmitEnabled("nsec1garbage", ""), false);
assert.equal(keyImportSubmitEnabled("", ""), false);
});
test("submit_gating_ncryptsec_requires_passphrase", () => {
assert.equal(keyImportSubmitEnabled(NCRYPTSEC, ""), false);
assert.equal(keyImportSubmitEnabled(NCRYPTSEC, "hunter2hunter2"), true);
// Structurally implausible blob never submits, passphrase or not.
assert.equal(keyImportSubmitEnabled("ncryptsec1bio", "hunter2"), false);
});