From f246ebe0fa5b98d43f76056f2a59eef6f3b4a5b2 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Wed, 20 May 2026 17:40:02 -0400 Subject: [PATCH] fix unit tests --- .../tests/auth_transfer/private.rs | 4 +-- integration_tests/tests/private_pda.rs | 4 +-- integration_tests/tests/tps.rs | 2 +- .../key_management/ephemeral_key_holder.rs | 2 +- .../src/key_management/group_key_holder.rs | 24 +++++++------ .../key_management/key_tree/keys_private.rs | 2 -- .../src/key_management/secret_holders.rs | 3 +- testnet_initial_state/src/lib.rs | 34 ++++++++++++------- 8 files changed, 42 insertions(+), 33 deletions(-) diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index a1e1118c..2abb31fc 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -65,7 +65,7 @@ async fn private_transfer_to_foreign_account() -> Result<()> { let from: AccountId = ctx.existing_private_accounts()[0]; let to_npk = NullifierPublicKey([42; 32]); let to_npk_string = hex::encode(to_npk.0); - let to_vpk = ViewingPublicKey::from_seed(&to_npk.0, &[0_u8; 32]); + let to_vpk = ViewingPublicKey::from_seed(&[0_u8; 32], &[1_u8; 32]); let command = Command::AuthTransfer(AuthTransferSubcommand::Send { from: private_mention(from), @@ -268,7 +268,7 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> { let to_npk = NullifierPublicKey([42; 32]); let to_npk_string = hex::encode(to_npk.0); - let to_vpk = ViewingPublicKey::from_seed(&to_npk.0, &[0_u8; 32]); + let to_vpk = ViewingPublicKey::from_seed(&[0_u8; 32], &[1_u8; 32]); let from: AccountId = ctx.existing_public_accounts()[0]; let command = Command::AuthTransfer(AuthTransferSubcommand::Send { diff --git a/integration_tests/tests/private_pda.rs b/integration_tests/tests/private_pda.rs index 9e15fe89..e180a09e 100644 --- a/integration_tests/tests/private_pda.rs +++ b/integration_tests/tests/private_pda.rs @@ -227,10 +227,10 @@ async fn private_pda_family_members_receive_and_spend() -> Result<()> { // Fresh recipients — hardcoded npks not in any wallet. let recipient_npk_0 = NullifierPublicKey([0xAA; 32]); - let recipient_vpk_0 = ViewingPublicKey::from_seed(&recipient_npk_0.0, &[0_u8; 32]); + let recipient_vpk_0 = ViewingPublicKey::from_seed(&[0_u8; 32], &[1_u8; 32]); let recipient_npk_1 = NullifierPublicKey([0xBB; 32]); - let recipient_vpk_1 = ViewingPublicKey::from_seed(&recipient_npk_1.0, &[0_u8; 32]); + let recipient_vpk_1 = ViewingPublicKey::from_seed(&[2_u8; 32], &[3_u8; 32]); let amount_spend_0: u128 = 13; let amount_spend_1: u128 = 37; diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index 7c956e67..9fd5f451 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -206,7 +206,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { AccountId::for_regular_private_account(&sender_npk, 0), ); let recipient_nsk = [2; 32]; - let recipient_vpk = ViewingPublicKey::from_seed(&[99_u8; 32], &[100_u8; 32]); + let recipient_vpk = ViewingPublicKey::from_seed(&[101_u8; 32], &[102_u8; 32]); let recipient_npk = NullifierPublicKey::from(&recipient_nsk); let recipient_pre = AccountWithMetadata::new( Account::default(), diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index f491f0b3..77797bc2 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -13,7 +13,7 @@ pub struct EphemeralKeyHolder { ephemeral_public_key: EphemeralPublicKey, } -// Marvin-pq: SharedSecretKey does not implement Debug (intentional — leaking key material via +// SharedSecretKey does not implement Debug (intentional — leaking key material via // debug output would be a security risk). We implement Debug manually here, redacting the // shared secret while still allowing the ephemeral public key (KEM ciphertext) to be inspected. impl std::fmt::Debug for EphemeralKeyHolder { diff --git a/key_protocol/src/key_management/group_key_holder.rs b/key_protocol/src/key_management/group_key_holder.rs index 63c76f64..67ca6382 100644 --- a/key_protocol/src/key_management/group_key_holder.rs +++ b/key_protocol/src/key_management/group_key_holder.rs @@ -146,13 +146,6 @@ impl GroupKeyHolder { SecretSpendingKey(hasher.finalize_fixed().into()).produce_private_key_holder(None) } - // Marvin-pq: seal_for/unseal switched from ECDH (Secp256k1) to ML-KEM-768. - // Wire format changed from: - // ephemeral_pubkey (33) || nonce (12) || ciphertext+tag (48) = 93 bytes - // to: - // kem_ciphertext (1088) || nonce (12) || ciphertext+tag (48) = 1148 bytes - // SealingSecretKey is now the FIPS 203 seed pair (d, r) = ViewingSecretKey. - /// Encrypts this holder's GMS under the recipient's [`SealingPublicKey`]. /// /// Uses ML-KEM-768 encapsulation to derive a shared secret, then AES-256-GCM to encrypt @@ -192,7 +185,7 @@ impl GroupKeyHolder { /// Returns `Err` if the ciphertext is too short or the AES-GCM authentication tag /// doesn't verify (wrong key or tampered data). pub fn unseal(sealed: &[u8], own_key: &SealingSecretKey) -> Result { - // Marvin-pq: kem_ciphertext (1088) + nonce (12) = header, then AES-GCM tag (16) minimum. + // kem_ciphertext (1088) + nonce (12) = header, then AES-GCM tag (16) minimum. const KEM_CT_LEN: usize = 1088; const HEADER_LEN: usize = KEM_CT_LEN + 12; const MIN_LEN: usize = HEADER_LEN + 16; @@ -328,7 +321,8 @@ mod tests { /// Pins the end-to-end derivation for a fixed (GMS, `ProgramId`, `PdaSeed`). Any change /// to `secret_spending_key_for_pda`, the `PrivateKeyHolder` nsk/npk chain, or the - /// `AccountId::for_private_pda` formula breaks this test. + /// `AccountId::for_private_pda` formula breaks this test. Mirrors the pinned-value + /// pattern from `for_private_pda_matches_pinned_value` in `nssa_core`. #[test] fn pinned_end_to_end_derivation_for_private_pda() { use nssa_core::{account::AccountId, program::ProgramId}; @@ -347,6 +341,8 @@ mod tests { 136, 176, 234, 71, 208, 8, 143, 142, 126, 155, 132, 18, 71, 27, 88, 56, 100, 90, 79, 215, 76, 92, 60, 166, 104, 35, 51, 91, 16, 114, 188, 112, ]); + // AccountId is derived from (program_id, seed, npk), so it changes when npk changes. + // We verify npk is pinned, and AccountId is deterministically derived from it. let expected_account_id = AccountId::for_private_pda(&program_id, &seed, &expected_npk, u128::MAX); @@ -354,7 +350,10 @@ mod tests { assert_eq!(account_id, expected_account_id); } - /// Wallets persist `GroupKeyHolder` to disk and reload it on startup. + /// Wallets persist `GroupKeyHolder` to disk and reload it on startup. This test pins + /// the serde round-trip: serialize, deserialize, and assert the derived keys for a + /// sample seed match on both sides. A silent encoding drift would corrupt every + /// group-owned account. #[test] fn gms_serde_round_trip_preserves_derivation() { let original = GroupKeyHolder::from_gms([7_u8; 32]); @@ -374,7 +373,10 @@ mod tests { } /// A `GroupKeyHolder` constructed from the same 32 bytes as a personal - /// `SecretSpendingKey` must not derive the same `NullifierPublicKey`. + /// `SecretSpendingKey` must not derive the same `NullifierPublicKey` as the personal + /// path, so a private PDA cannot be spent by a personal nullifier even under + /// adversarial key-material reuse. The safety rests on the group path's distinct + /// domain-separation prefix plus the seed mix-in (see `secret_spending_key_for_pda`). #[test] fn group_derivation_does_not_collide_with_personal_path_at_shared_bytes() { let shared_bytes = [13_u8; 32]; diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs index 7401aa92..4856c3e0 100644 --- a/key_protocol/src/key_management/key_tree/keys_private.rs +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -166,7 +166,6 @@ mod tests { 247, 155, 113, 122, 246, 192, 0, 70, 61, 76, 71, 70, 2, ]); - // Marvin-pq double check the d, r labels let expected_vsk: ViewingSecretKey = ViewingSecretKey { d: [ 187, 143, 146, 12, 68, 148, 25, 203, 21, 92, 131, 2, 221, 81, 117, 62, 98, 194, @@ -281,7 +280,6 @@ mod tests { 219, 114, 113, 16, 42, 27, 220, 96, 151, 124, 8, 65, ]); - // Marvin-pq double check the d, r labels let expected_vsk: ViewingSecretKey = ViewingSecretKey { d: [ 81, 154, 68, 152, 72, 163, 82, 17, 125, 156, 193, 135, 129, 93, 227, 55, 224, 104, diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index f8b54d24..473d9abc 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -135,7 +135,7 @@ impl SecretSpendingKey { bytes.extend_from_slice(SUFFIX_2); let bytes: [u8; 64] = bytes .try_into() - .expect("`generate_viewing_secret_key`: bytes must be exactly 64"); + .expect("`generate_viewing_secret_seed_key`: bytes must be exactly 64"); let full_seed = hmac_sha512::HMAC::mac(bytes, b"LEE_viewing_seed"); @@ -217,7 +217,6 @@ mod tests { assert_eq!(seed_holder.seed.len(), 64); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - // Marvin-pq should drop seed from the fucntion name let _vsk = top_secret_key_holder.generate_viewing_secret_seed_key(None); } diff --git a/testnet_initial_state/src/lib.rs b/testnet_initial_state/src/lib.rs index 385ea5ea..cd9a0f1a 100644 --- a/testnet_initial_state/src/lib.rs +++ b/testnet_initial_state/src/lib.rs @@ -38,14 +38,24 @@ const NSK_PRIV_ACC_B: [u8; 32] = [ 23, 99, 9, 4, 177, 230, 125, 109, 91, 160, 30, ]; -const VSK_PRIV_ACC_A: [u8; 32] = [ - 5, 85, 114, 119, 141, 187, 202, 170, 122, 253, 198, 81, 150, 8, 155, 21, 192, 65, 24, 124, 116, - 98, 110, 106, 137, 90, 165, 239, 80, 13, 222, 30, +const VSK_D_PRIV_ACC_A: [u8; 32] = [ + 255, 250, 140, 26, 222, 223, 174, 95, 132, 108, 124, 88, 30, 247, 82, 72, 52, 70, 84, 139, 241, + 187, 41, 163, 19, 231, 232, 122, 225, 55, 134, 184, ]; -const VSK_PRIV_ACC_B: [u8; 32] = [ - 205, 32, 76, 251, 255, 236, 96, 119, 61, 111, 65, 100, 75, 218, 12, 22, 17, 170, 55, 226, 21, - 154, 161, 34, 208, 74, 27, 1, 119, 13, 88, 128, +const VSK_R_PRIV_ACC_A: [u8; 32] = [ + 225, 24, 98, 78, 31, 203, 175, 248, 213, 17, 133, 207, 10, 135, 132, 151, 59, 184, 5, 81, 28, + 238, 137, 62, 233, 227, 99, 17, 236, 159, 244, 63, +]; + +const VSK_D_PRIV_ACC_B: [u8; 32] = [ + 128, 85, 85, 103, 226, 218, 119, 56, 60, 252, 31, 113, 232, 215, 156, 2, 159, 247, 156, 192, + 12, 178, 229, 236, 255, 120, 146, 211, 169, 117, 153, 180, +]; + +const VSK_R_PRIV_ACC_B: [u8; 32] = [ + 165, 80, 169, 87, 248, 88, 167, 154, 27, 67, 131, 122, 50, 130, 111, 40, 164, 180, 204, 75, + 188, 140, 110, 132, 113, 133, 222, 8, 49, 123, 187, 18, ]; const NPK_PRIV_ACC_A: [u8; 32] = [ @@ -127,12 +137,12 @@ pub fn initial_priv_accounts_private_keys() -> Vec Vec