From 5bf24b191d0a2be1eaf5607b78e27c6fdd659e1a Mon Sep 17 00:00:00 2001 From: Moudy Date: Wed, 6 May 2026 00:15:05 +0200 Subject: [PATCH] test: add unit tests for SharedAccountEntry and shared account derivation --- integration_tests/tests/ata.rs | 4 + .../tests/auth_transfer/private.rs | 16 ++++ integration_tests/tests/keys_restoration.rs | 16 ++++ integration_tests/tests/pinata.rs | 8 ++ integration_tests/tests/token.rs | 48 +++++++++++ key_protocol/src/key_protocol_core/mod.rs | 83 +++++++++++++++++++ 6 files changed, 175 insertions(+) diff --git a/integration_tests/tests/ata.rs b/integration_tests/tests/ata.rs index 6f0bf05c..54ef5341 100644 --- a/integration_tests/tests/ata.rs +++ b/integration_tests/tests/ata.rs @@ -44,6 +44,10 @@ async fn new_private_account(ctx: &mut TestContext) -> Result { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index 8db5f8d4..6f05cdee 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -160,6 +160,10 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> { // Create a new private account let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })); @@ -328,6 +332,10 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> { // Create a new private account let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })); @@ -393,6 +401,10 @@ async fn initialize_private_account() -> Result<()> { let mut ctx = TestContext::new().await?; let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })); @@ -493,6 +505,10 @@ async fn initialize_private_account_using_label() -> Result<()> { // Create a new private account with a label let label = "init-private-label".to_owned(); let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: Some(label.clone()), })); diff --git a/integration_tests/tests/keys_restoration.rs b/integration_tests/tests/keys_restoration.rs index ff339120..8fae9808 100644 --- a/integration_tests/tests/keys_restoration.rs +++ b/integration_tests/tests/keys_restoration.rs @@ -30,6 +30,10 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> { // Create a new private account let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })); @@ -40,6 +44,10 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -119,6 +127,10 @@ async fn restore_keys_from_seed() -> Result<()> { // Create first private account at root let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: Some(ChainIndex::root()), label: None, })); @@ -132,6 +144,10 @@ async fn restore_keys_from_seed() -> Result<()> { // Create second private account at /0 let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: Some(ChainIndex::from_str("/0")?), label: None, })); diff --git a/integration_tests/tests/pinata.rs b/integration_tests/tests/pinata.rs index 77c4a646..d4523f94 100644 --- a/integration_tests/tests/pinata.rs +++ b/integration_tests/tests/pinata.rs @@ -85,6 +85,10 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<() let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -229,6 +233,10 @@ async fn claim_pinata_to_new_private_account() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), diff --git a/integration_tests/tests/token.rs b/integration_tests/tests/token.rs index 6db718f9..93786a57 100644 --- a/integration_tests/tests/token.rs +++ b/integration_tests/tests/token.rs @@ -297,6 +297,10 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -313,6 +317,10 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -460,6 +468,10 @@ async fn create_token_with_private_definition() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: Some(ChainIndex::root()), label: None, })), @@ -532,6 +544,10 @@ async fn create_token_with_private_definition() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -662,6 +678,10 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -678,6 +698,10 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -740,6 +764,10 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -855,6 +883,10 @@ async fn shielded_token_transfer() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -966,6 +998,10 @@ async fn deshielded_token_transfer() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -1077,6 +1113,10 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -1093,6 +1133,10 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), @@ -1126,6 +1170,10 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> { let result = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Private { + for_gms: None, + pda: false, + seed: None, + program_id: None, cci: None, label: None, })), diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 7218ebde..ea8d8405 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -274,6 +274,89 @@ mod tests { fn group_key_holders_default_empty() { let user_data = NSSAUserData::default(); assert!(user_data.group_key_holders.is_empty()); + assert!(user_data.shared_accounts.is_empty()); + } + + #[test] + fn shared_account_entry_serde_round_trip() { + use nssa_core::program::PdaSeed; + + let entry = SharedAccountEntry { + group_label: String::from("test-group"), + identifier: 42, + pda_seed: None, + account: nssa_core::account::Account::default(), + }; + let encoded = bincode::serialize(&entry).expect("serialize"); + let decoded: SharedAccountEntry = bincode::deserialize(&encoded).expect("deserialize"); + assert_eq!(decoded.group_label, "test-group"); + assert_eq!(decoded.identifier, 42); + assert!(decoded.pda_seed.is_none()); + + let pda_entry = SharedAccountEntry { + group_label: String::from("pda-group"), + identifier: u128::MAX, + pda_seed: Some(PdaSeed::new([7_u8; 32])), + account: nssa_core::account::Account::default(), + }; + let pda_encoded = bincode::serialize(&pda_entry).expect("serialize pda"); + let pda_decoded: SharedAccountEntry = + bincode::deserialize(&pda_encoded).expect("deserialize pda"); + assert_eq!(pda_decoded.group_label, "pda-group"); + assert_eq!(pda_decoded.identifier, u128::MAX); + assert_eq!(pda_decoded.pda_seed.unwrap(), PdaSeed::new([7_u8; 32])); + } + + #[test] + fn shared_account_entry_none_pda_seed_round_trips() { + // Verify that an entry with pda_seed=None serializes and deserializes correctly, + // confirming the #[serde(default)] attribute works for backward compatibility. + let entry = SharedAccountEntry { + group_label: String::from("old"), + identifier: 1, + pda_seed: None, + account: nssa_core::account::Account::default(), + }; + let encoded = bincode::serialize(&entry).expect("serialize"); + let decoded: SharedAccountEntry = bincode::deserialize(&encoded).expect("deserialize"); + assert_eq!(decoded.group_label, "old"); + assert_eq!(decoded.identifier, 1); + assert!(decoded.pda_seed.is_none()); + } + + #[test] + fn shared_account_derives_consistent_keys_from_group() { + use nssa_core::program::PdaSeed; + + let mut user_data = NSSAUserData::default(); + let gms_holder = GroupKeyHolder::from_gms([42_u8; 32]); + user_data.insert_group_key_holder(String::from("my-group"), gms_holder); + + let holder = user_data.group_key_holder("my-group").unwrap(); + + // Regular shared account: derive via tag + let tag = [1_u8; 32]; + let keys_a = holder.derive_keys_for_shared_account(&tag); + let keys_b = holder.derive_keys_for_shared_account(&tag); + assert_eq!( + keys_a.generate_nullifier_public_key(), + keys_b.generate_nullifier_public_key(), + ); + + // PDA shared account: derive via seed + let seed = PdaSeed::new([2_u8; 32]); + let pda_keys_a = holder.derive_keys_for_pda(&seed); + let pda_keys_b = holder.derive_keys_for_pda(&seed); + assert_eq!( + pda_keys_a.generate_nullifier_public_key(), + pda_keys_b.generate_nullifier_public_key(), + ); + + // PDA and shared derivations don't collide + assert_ne!( + keys_a.generate_nullifier_public_key(), + pda_keys_a.generate_nullifier_public_key(), + ); } #[test]