From 0b7fabdfa1045212cbf1003c8aa3bfbc1f33d517 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sat, 8 Aug 2026 04:00:58 -0400 Subject: [PATCH] test(seed): pin known answers for the six unpinned derivations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `test_node_key_known_answer_vs_python_verifier` pinned the node Ed25519 and node Nostr keys, and `test_release_root_known_answer` covers the release root. The remaining six — FIPS mesh transport, identity Ed25519, identity Nostr (NIP-06), Bitcoin BIP-84 and LND aezeed entropy — were only asserted to be mutually distinct by `test_full_derivation_from_known_mnemonic`. Distinctness is satisfied by ANY change to an HKDF info string or BIP-32 path. So redefining `archipelago/lnd/entropy/v1` — the seed behind a user's Lightning wallet — broke no test, while invalidating every backup verification a user had already performed against docs/SEED-VERIFICATION.md. Same for the FIPS key that authenticates a node on the mesh. Expected values were produced independently by the Python verifier published in that doc, whose primitives were themselves cross-checked against bip_utils and cryptography's own HKDF (BIP-39 seed, both BIP-32 paths, x-only pubkey, bech32 and HKDF-SHA256 salt=None all matched byte for byte). This commit closes the loop in the other direction: the Rust implementation now agrees with those same bytes, so the doc and the code are pinned to each other. Verified: 26/26 seed tests pass. Co-Authored-By: Claude Opus 5 (1M context) --- core/archipelago/src/seed.rs | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/core/archipelago/src/seed.rs b/core/archipelago/src/seed.rs index a1a9c97c..fd09931c 100644 --- a/core/archipelago/src/seed.rs +++ b/core/archipelago/src/seed.rs @@ -919,6 +919,79 @@ mod tests { ); } + /// Known answers for the derivations the test above does NOT pin. + /// + /// `test_full_derivation_from_known_mnemonic` only asserts these are + /// mutually distinct, which is satisfied by ANY change to an HKDF info + /// string or BIP-32 path. That left the LND entropy — the seed behind a + /// user's Lightning wallet — and the FIPS mesh transport key with no + /// known-answer coverage at all: silently redefining either broke no test + /// while invalidating every backup verification a user had already done. + /// + /// The expected values were produced independently by the Python verifier + /// published in `docs/SEED-VERIFICATION.md`, whose primitives were in turn + /// cross-checked against `bip_utils` and `cryptography`'s own HKDF. If one + /// of these assertions fails, either a derivation changed (and every + /// published backup-verification instruction is now wrong), or the doc and + /// the code have drifted apart — both are release-blocking. + #[test] + fn test_all_derivations_known_answers_vs_python_verifier() { + let (_, seed) = MasterSeed::from_mnemonic_words(TEST_MNEMONIC).unwrap(); + + // HKDF "archipelago/fips/secp256k1/v1" + assert_eq!( + derive_fips_key(&seed).unwrap().public_key().to_hex(), + "31865360f8cfb8bc3d0d5343bf09f1bc5c17b7f0b509120e94cdbed47d203bca", + "FIPS mesh transport key" + ); + + // HKDF "archipelago/identity/{i}/ed25519/v1" + assert_eq!( + hex::encode( + derive_identity_ed25519(&seed, 0) + .unwrap() + .verifying_key() + .as_bytes() + ), + "c0415866117dd570e908e1b178bf1dd8f7b9cdb8eb662353165c6f4877fe222b", + "identity[0] Ed25519" + ); + assert_eq!( + hex::encode( + derive_identity_ed25519(&seed, 1) + .unwrap() + .verifying_key() + .as_bytes() + ), + "be7a0e1fe2711dedbbba976a70443e0a70e673bcf7b6e6a8fb20d1d2684e5eba", + "identity[1] Ed25519 — pins the index into the info string" + ); + + // BIP-32 m/44'/1237'/0'/0/{i} (NIP-06) + assert_eq!( + derive_nostr_identity_key(&seed, 0) + .unwrap() + .public_key() + .to_hex(), + "1ca7e48a33d62063f25d79f18617cc892ef064628aaf80af485e41849343ca52", + "identity[0] Nostr (NIP-06)" + ); + + // BIP-32 m/84'/0'/0' + assert_eq!( + hex::encode(derive_bitcoin_xprv(&seed).unwrap().private_key.secret_bytes()), + "57558e8c90c2e72f0c121d0fb8844bbbe7a872f0065d21b218a990450b9f93be", + "Bitcoin BIP-84 account key" + ); + + // HKDF "archipelago/lnd/entropy/v1" (16 bytes) + assert_eq!( + hex::encode(derive_lnd_entropy(&seed).unwrap()), + "5c86f10629bd86cdd269b82a76cc51e4", + "LND aezeed entropy" + ); + } + #[test] fn test_release_root_deterministic_and_domain_separated() { let (_, seed) = MasterSeed::from_mnemonic_words(TEST_MNEMONIC).unwrap();