From 5fa93538f3981c1d0332797d320d87267d1277fa Mon Sep 17 00:00:00 2001 From: Artem Gureev Date: Fri, 10 Jul 2026 13:46:54 +0000 Subject: [PATCH] test(circuit): view_tag is derived on init, passed through on update --- .../circuit/tests.rs | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs b/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs index 225d542a..fb66186b 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs @@ -1,8 +1,8 @@ #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] use lee_core::{ - Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, EphemeralSecretKey, Nullifier, - PrivacyPreservingCircuitOutput, SharedSecretKey, + Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme, EphemeralSecretKey, + Nullifier, PrivacyPreservingCircuitOutput, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, program::{PdaSeed, PrivateAccountKind}, }; @@ -243,6 +243,76 @@ fn prove_privacy_preserving_execution_circuit_fully_private() { assert_eq!(recipient_post, expected_private_account_2); } +#[test] +fn init_note_view_tag_is_derived_from_account_keys() { + let program = crate::test_methods::noop(); + let keys = test_private_account_keys_1(); + let identifier: u128 = 0; + let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier); + let account = AccountWithMetadata::new(Account::default(), false, account_id); + + let (output, proof) = execute_and_prove( + vec![account], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::PrivateUnauthorized { + vpk: keys.vpk(), + random_seed: [0; 32], + npk: keys.npk(), + identifier, + commitment_root: DUMMY_COMMITMENT_HASH, + }], + &program.into(), + ) + .unwrap(); + + assert!(proof.is_valid_for(&output)); + assert_eq!(output.encrypted_private_post_states.len(), 1); + assert_eq!( + output.encrypted_private_post_states[0].view_tag, + EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), + ); +} + +#[test] +fn update_note_view_tag_is_the_supplied_value() { + let program = crate::test_methods::noop(); + let keys = test_private_account_keys_1(); + let identifier: u128 = 99; + let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier); + let account = Account { + program_owner: program.id(), + balance: 1, + ..Account::default() + }; + let commitment = Commitment::new(&account_id, &account); + let mut commitment_set = CommitmentSet::with_capacity(1); + commitment_set.extend(std::slice::from_ref(&commitment)); + let sender = AccountWithMetadata::new(account, true, account_id); + + // A tag deliberately different from the address-derived one, so a passthrough is + // distinguishable from re-derivation. + let fed_tag = EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()).wrapping_add(1); + + let (output, proof) = execute_and_prove( + vec![sender], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::PrivateAuthorizedUpdate { + vpk: keys.vpk(), + random_seed: [0; 32], + view_tag: fed_tag, + nsk: keys.nsk, + membership_proof: commitment_set.get_proof_for(&commitment).unwrap(), + identifier, + }], + &program.into(), + ) + .unwrap(); + + assert!(proof.is_valid_for(&output)); + assert_eq!(output.encrypted_private_post_states.len(), 1); + assert_eq!(output.encrypted_private_post_states[0].view_tag, fed_tag); +} + #[test] fn circuit_fails_when_chained_validity_windows_have_empty_intersection() { let account_keys = test_private_account_keys_1();