test(seed): pin known answers for the six unpinned derivations

`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) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-08 04:00:58 -04:00
co-authored by Claude Opus 5
parent ecd9295e96
commit 0b7fabdfa1
+73
View File
@@ -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();