From d0f398814c00ef54491e77d5bfd70f7a38966002 Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:37:42 +0200 Subject: [PATCH] test(ata): add integration for private accounts create public ATAs --- integration_tests/tests/ata.rs | 100 ++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/integration_tests/tests/ata.rs b/integration_tests/tests/ata.rs index 089f17d..faf3fdc 100644 --- a/integration_tests/tests/ata.rs +++ b/integration_tests/tests/ata.rs @@ -1,9 +1,21 @@ +use std::collections::HashMap; + use ata_core::{compute_ata_seed, get_associated_token_account_id}; use nssa::{ + execute_and_prove, + privacy_preserving_transaction::{ + circuit::ProgramWithDependencies, Message, PrivacyPreservingTransaction, WitnessSet, + }, + program::Program, program_deployment_transaction::{self, ProgramDeploymentTransaction}, - public_transaction, PrivateKey, PublicKey, PublicTransaction, V03State, + public_transaction, EphemeralPublicKey, PrivateKey, PublicKey, PublicTransaction, + SharedSecretKey, V03State, +}; +use nssa_core::{ + account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, + encryption::{Scalar, ViewingPublicKey}, + NullifierPublicKey, NullifierSecretKey, }; -use nssa_core::account::{Account, AccountId, Data, Nonce}; use token_core::{TokenDefinition, TokenHolding}; struct Keys; @@ -279,3 +291,87 @@ fn ata_burn() { } ); } + +#[test] +fn ata_create_from_private_owner() { + let mut state = V03State::new_with_genesis_accounts(&[], &[]); + deploy_programs(&mut state); + state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); + + // Private owner key material + let owner_nsk: NullifierSecretKey = [13u8; 32]; + let owner_npk = NullifierPublicKey::from(&owner_nsk); + let owner_vsk: Scalar = [31u8; 32]; + let owner_vpk = ViewingPublicKey::from_scalar(owner_vsk); + let owner_id = AccountId::from(&owner_npk); + + // ATA derived from the private owner + let seed = compute_ata_seed(owner_id, Ids::token_definition()); + let owner_ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed); + + // Pre-states: private uninitialized owner (mask=2), public token definition (mask=0), public + // uninitialized ATA (mask=0) + let owner_pre = AccountWithMetadata::new(Account::default(), false, owner_id); + let def_pre = AccountWithMetadata::new( + Accounts::token_definition_init(), + false, + Ids::token_definition(), + ); + let ata_pre = AccountWithMetadata::new(Account::default(), false, owner_ata_id); + + let instruction = ata_core::Instruction::Create { + ata_program_id: Ids::ata_program(), + }; + let instruction_data = Program::serialize_instruction(instruction).unwrap(); + + // Ephemeral key for encrypting the private owner's post-state + let esk: Scalar = [3u8; 32]; + let shared_secret = SharedSecretKey::new(&esk, &owner_vpk); + let epk = EphemeralPublicKey::from_scalar(esk); + + let ata_program = Program::new(ata_methods::ATA_ELF.to_vec()).unwrap(); + let token_program = Program::new(token_methods::TOKEN_ELF.to_vec()).unwrap(); + let program_with_deps = ProgramWithDependencies::new( + ata_program, + HashMap::from([(Ids::token_program(), token_program)]), + ); + + let (output, proof) = execute_and_prove( + vec![owner_pre, def_pre, ata_pre], + instruction_data, + // owner=new private (2), token_definition=public (0), ata=public (0) + vec![2, 0, 0], + vec![(owner_npk.clone(), shared_secret)], + vec![], // no NSKs: new private accounts don't require one + vec![None], // no membership proof: owner is being created, not spending + &program_with_deps, + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![Ids::token_definition(), owner_ata_id], + vec![], + vec![(owner_npk, owner_vpk, epk)], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + state + .transition_from_privacy_preserving_transaction(&tx, 0) + .unwrap(); + + assert_eq!( + state.get_account_by_id(owner_ata_id), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 0_u128, + }), + nonce: Nonce(0), + } + ); +}