test(core): run the official BIP-85 spec vectors directly against the TS core

Every other derivation check compares this implementation against Python --
cross-verification, not ground truth. The BIP-85 specification's own test
vectors (spec master xprv, 12- and 24-word BIP-39 children at index 0) are
immune to the failure mode where both of our implementations share a
misconception and agree with each other: Python has pinned them since early
on (test_bip85_vectors.py), and now the TypeScript core runs them natively
via a new Bip85.fromExtendedKey factory (the spec vectors start from an
xprv, not a mnemonic). Python's pinned app-32 entropy and TOTP derivations
from the same master ride along as cross-family anchors.

All four pass on first contact -- an independent confirmation of the root
derivation ahead of the external review, and recorded as such in the #34
review-strategy notes in TODO.md, along with the ultrareview size blocker
(PR #989 spans 720k lines including beta's history) and its fallback
options.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8
This commit is contained in:
thePR0M3TH3ANandClaude Opus 5 committed 2026-08-18 14:19:59 -04:00
1 parent eb67dac576
commit ecff57e8c6
3 files changed
+79 -3

No files matched your search

+16 -1
View File
@@ -127,7 +127,22 @@ everything still open, in the order it should be tackled.
### Blockers before real secrets
- [ ] **Independent security review of the TypeScript branch.** No one but the
- [ ] **Independent security review of the TypeScript branch.** Review
strategy (decided 2026-08-18): two AI families plus vectors, because
they fail differently. (1) `/code-review ultra` — **blocked**: PR #989
spans main→port including beta's 150 commits (6795 files/720k lines),
over ultra's size limit; fallback options are a review-only PR
containing just the `js/` tree against main, or a narrower local-diff
target. (2) A GPT-family pass (Codex) over the four ranked targets in
the PR body, briefed to REFUTE the security claims in file comments,
not summarize them. (3) Third-party ground truth where possible: the
official BIP-85 spec vectors now run directly against the TS core
(test/bip85SpecVectors.test.ts; Python has had them all along), which
closes the both-implementations-agree-and-are-both-wrong hole for the
root derivation. Triage rule for findings: flagged by both families =
almost certainly real; flagged by one = verify against code, arbiter
is a reproduction test or the cross-impl suite, never the author's
opinion alone. No one but the
authoring agent has read this code. Two earlier review rounds on this
project each found criticals that self-review missed, so a green suite and
an author's sign-off are not evidence of much. Scope it to
+13 -2
View File
@@ -102,14 +102,25 @@ export interface DeriveEntropyOptions {
export class Bip85 {
private readonly root: HDKey;
constructor(seed: Uint8Array) {
this.root = HDKey.fromMasterSeed(seed);
constructor(seed: Uint8Array | HDKey) {
this.root = seed instanceof HDKey ? seed : HDKey.fromMasterSeed(seed);
}
static fromMnemonic(mnemonic: string, passphrase = ""): Bip85 {
return new Bip85(mnemonicToSeedSync(canonicalizeMnemonic(mnemonic), passphrase));
}
/**
* From a BIP-32 extended private key. Exists so the official BIP-85 spec
* test vectors — which start from an xprv, not a mnemonic — can be run
* directly against this implementation (third-party ground truth, immune
* to the failure mode where both of our implementations agree with each
* other and are both wrong).
*/
static fromExtendedKey(xprv: string): Bip85 {
return new Bip85(HDKey.fromExtendedKey(xprv));
}
deriveEntropy(opts: DeriveEntropyOptions): Uint8Array {
const appNo = opts.appNo ?? 39;
let path: string;
@@ -0,0 +1,50 @@
/**
* BIP-85 official spec test vectors, run directly against this implementation.
*
* Every other derivation check in this repo compares us against the Python
* implementation — which is cross-verification, not ground truth. These
* vectors come from the BIP-85 specification itself (also pinned in Python's
* test_bip85_vectors.py), so a shared misconception between our two
* implementations cannot pass here.
*/
import { describe, expect, it } from "vitest";
import { Bip85, deriveTotpSecret, bytesToHex } from "@seedpass/core";
// The spec's master key (BIP-85 test vectors section).
const MASTER_XPRV =
"xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb";
// Official BIP-39 application vectors (12 and 24 English words, index 0).
const SPEC_12 =
"girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose";
const SPEC_24 =
"puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano";
// SeedPass-specific derivations from the same master, pinned identically in
// Python's suite (not spec vectors, but a cross-family anchor for app 32 and
// the TOTP path).
const PY_SSH_ENTROPY =
"52405cd0dd21c5be78314a7c1a3c65ffd8d896536cc7dee3157db5824f0c92e2";
const PY_TOTP_SECRET = "VQYTWDNEWYBY2G3LOGGCEKR4LZ3LNEYY";
describe("BIP-85 spec vectors", () => {
const bip85 = Bip85.fromExtendedKey(MASTER_XPRV);
it("derives the spec's 12-word BIP-39 vector", () => {
expect(bip85.deriveMnemonic(0, 12)).toBe(SPEC_12);
});
it("derives the spec's 24-word BIP-39 vector", () => {
expect(bip85.deriveMnemonic(0, 24)).toBe(SPEC_24);
});
it("matches Python's pinned app-32 entropy from the spec master", () => {
const entropy = bip85.deriveEntropy({ index: 0, entropyBytes: 32, appNo: 32 });
expect(bytesToHex(entropy)).toBe(PY_SSH_ENTROPY);
});
it("matches Python's pinned TOTP secret derived from the spec's 24-word child", () => {
expect(deriveTotpSecret(SPEC_24, 0)).toBe(PY_TOTP_SECRET);
});
});