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();