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, SharedSecretKey, V03State, }; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, encryption::{EphemeralPublicKey, ViewingPublicKey}, EncryptedAccountData, InputAccountIdentity, NullifierPublicKey, NullifierSecretKey, }; use token_core::{TokenDefinition, TokenHolding}; struct Keys; struct Ids; struct Accounts; impl Keys { fn def_key() -> PrivateKey { PrivateKey::try_new([10; 32]).expect("valid private key") } fn owner_key() -> PrivateKey { PrivateKey::try_new([11; 32]).expect("valid private key") } fn recipient_key() -> PrivateKey { PrivateKey::try_new([12; 32]).expect("valid private key") } } impl Ids { fn token_program() -> nssa_core::program::ProgramId { token_methods::TOKEN_ID } fn ata_program() -> nssa_core::program::ProgramId { ata_methods::ATA_ID } fn token_definition() -> AccountId { AccountId::from(&PublicKey::new_from_private_key(&Keys::def_key())) } fn owner() -> AccountId { AccountId::from(&PublicKey::new_from_private_key(&Keys::owner_key())) } fn recipient() -> AccountId { AccountId::from(&PublicKey::new_from_private_key(&Keys::recipient_key())) } fn owner_ata() -> AccountId { let seed = compute_ata_seed( Self::token_program(), Self::owner(), Self::token_definition(), ); get_associated_token_account_id(&Self::ata_program(), &seed) } fn recipient_ata() -> AccountId { let seed = compute_ata_seed( Self::token_program(), Self::recipient(), Self::token_definition(), ); get_associated_token_account_id(&Self::ata_program(), &seed) } } impl Accounts { fn token_definition_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("Gold"), total_supply: 1_000_000_u128, metadata_id: None, }), nonce: Nonce(0), } } fn owner_ata_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_definition(), balance: 1_000_000_u128, }), nonce: Nonce(0), } } fn recipient_ata_init() -> Account { 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), } } fn foreign_owned_token_definition() -> Account { Account { program_owner: [99; 8], balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("Foreign Gold"), total_supply: 1_000_000_u128, metadata_id: None, }), nonce: Nonce(0), } } } fn deploy_programs(state: &mut V03State) { let token_message = program_deployment_transaction::Message::new(token_methods::TOKEN_ELF.to_vec()); state .transition_from_program_deployment_transaction(&ProgramDeploymentTransaction::new( token_message, )) .expect("token program deployment must succeed"); let ata_message = program_deployment_transaction::Message::new(ata_methods::ATA_ELF.to_vec()); state .transition_from_program_deployment_transaction(&ProgramDeploymentTransaction::new( ata_message, )) .expect("ata program deployment must succeed"); } fn state_for_ata_tests() -> V03State { let mut state = V03State::new(); deploy_programs(&mut state); state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); state.force_insert_account(Ids::owner_ata(), Accounts::owner_ata_init()); state } fn state_for_ata_tests_with_precreated_recipient_ata() -> V03State { let mut state = state_for_ata_tests(); state.force_insert_account(Ids::recipient_ata(), Accounts::recipient_ata_init()); state } #[test] fn ata_create() { let mut state = V03State::new(); deploy_programs(&mut state); state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); let instruction = ata_core::Instruction::Create { token_program_id: Ids::token_program(), }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); assert_eq!( state.get_account_by_id(Ids::owner_ata()), 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), } ); } #[test] fn ata_create_is_idempotent() { let mut state = state_for_ata_tests(); let instruction = ata_core::Instruction::Create { token_program_id: Ids::token_program(), }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); // Already initialized — should remain unchanged assert_eq!( state.get_account_by_id(Ids::owner_ata()), Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_definition(), balance: 1_000_000_u128, }), nonce: Nonce(0), } ); } #[test] fn ata_create_rejects_definition_owned_by_unexpected_token_program() { let mut state = V03State::new(); deploy_programs(&mut state); state.force_insert_account( Ids::token_definition(), Accounts::foreign_owned_token_definition(), ); let instruction = ata_core::Instruction::Create { token_program_id: Ids::token_program(), }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err()); assert_eq!( state.get_account_by_id(Ids::owner_ata()), Account::default() ); } #[test] fn ata_create_rejects_existing_ata_owned_by_unexpected_token_program() { let mut state = V03State::new(); deploy_programs(&mut state); state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); let mut foreign_ata = Accounts::owner_ata_init(); foreign_ata.program_owner = [99; 8]; state.force_insert_account(Ids::owner_ata(), foreign_ata.clone()); let instruction = ata_core::Instruction::Create { token_program_id: Ids::token_program(), }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err()); assert_eq!(state.get_account_by_id(Ids::owner_ata()), foreign_ata); } #[test] fn ata_create_rejects_existing_ata_with_mismatched_definition() { let mut state = V03State::new(); deploy_programs(&mut state); state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); let mut mismatched_ata = Accounts::owner_ata_init(); mismatched_ata.data = Data::from(&TokenHolding::Fungible { definition_id: Ids::recipient(), balance: 1_000_000_u128, }); state.force_insert_account(Ids::owner_ata(), mismatched_ata.clone()); let instruction = ata_core::Instruction::Create { token_program_id: Ids::token_program(), }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::token_definition(), Ids::owner_ata()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err()); assert_eq!(state.get_account_by_id(Ids::owner_ata()), mismatched_ata); } #[test] fn ata_transfer() { let mut state = state_for_ata_tests_with_precreated_recipient_ata(); let instruction = ata_core::Instruction::Transfer { token_program_id: Ids::token_program(), amount: 400_000_u128, }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::owner_ata(), Ids::recipient_ata()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); assert_eq!( state.get_account_by_id(Ids::owner_ata()), Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_definition(), balance: 600_000_u128, }), nonce: Nonce(0), } ); assert_eq!( state.get_account_by_id(Ids::recipient_ata()), Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_definition(), balance: 400_000_u128, }), nonce: Nonce(0), } ); } #[test] fn ata_transfer_rejects_default_recipient() { let mut state = state_for_ata_tests(); let instruction = ata_core::Instruction::Transfer { token_program_id: Ids::token_program(), amount: 1_u128, }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::owner_ata(), Ids::recipient_ata()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err()); assert_eq!( state.get_account_by_id(Ids::owner_ata()), Accounts::owner_ata_init() ); assert_eq!( state.get_account_by_id(Ids::recipient_ata()), Account::default() ); } #[test] fn ata_transfer_rejects_mismatched_definition_recipient() { let mut state = state_for_ata_tests_with_precreated_recipient_ata(); // Replace the recipient ATA with a token holding pointing at a different definition. let foreign_definition_id = AccountId::from(&PublicKey::new_from_private_key( &PrivateKey::try_new([42; 32]).expect("valid private key"), )); let mismatched_recipient = Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: foreign_definition_id, balance: 0_u128, }), nonce: Nonce(0), }; state.force_insert_account(Ids::recipient_ata(), mismatched_recipient.clone()); let instruction = ata_core::Instruction::Transfer { token_program_id: Ids::token_program(), amount: 1_u128, }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::owner_ata(), Ids::recipient_ata()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); assert!(state.transition_from_public_transaction(&tx, 0, 0).is_err()); assert_eq!( state.get_account_by_id(Ids::owner_ata()), Accounts::owner_ata_init() ); assert_eq!( state.get_account_by_id(Ids::recipient_ata()), mismatched_recipient ); } #[test] fn ata_burn() { let mut state = state_for_ata_tests(); let instruction = ata_core::Instruction::Burn { token_program_id: Ids::token_program(), amount: 300_000_u128, }; let message = public_transaction::Message::try_new( Ids::ata_program(), vec![Ids::owner(), Ids::owner_ata(), Ids::token_definition()], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::owner_key()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0, 0).unwrap(); assert_eq!( state.get_account_by_id(Ids::owner_ata()), Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_definition(), balance: 700_000_u128, }), nonce: Nonce(0), } ); assert_eq!( state.get_account_by_id(Ids::token_definition()), Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("Gold"), total_supply: 700_000_u128, metadata_id: None, }), nonce: Nonce(0), } ); } #[test] fn ata_create_from_private_owner() { let mut state = V03State::new(); 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); // `ViewingPublicKey::from_seed` needs two 32-byte halves `(d, z)`. let owner_vpk = ViewingPublicKey::from_seed(&[31u8; 32], &[32u8; 32]); let owner_id = AccountId::for_regular_private_account(&owner_npk, 0); // ATA derived from the private owner let seed = compute_ata_seed(Ids::token_program(), owner_id, Ids::token_definition()); let owner_ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed); // Pre-states: private uninitialized owner, public token definition, public uninitialized ATA. 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 { token_program_id: Ids::token_program(), }; let instruction_data = Program::serialize_instruction(instruction).unwrap(); // Encapsulate a shared secret against the owner's viewing key; the circuit fills the EPK. let shared_secret = SharedSecretKey::encapsulate_deterministic(&owner_vpk, &[0u8; 32], 0).0; let ata_program = Program::new(ata_methods::ATA_ELF.to_vec().into()).unwrap(); let token_program = Program::new(token_methods::TOKEN_ELF.to_vec().into()).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, vec![ // owner: new private account, not owned/spent by the caller (no nsk, no proof). InputAccountIdentity::PrivateUnauthorized { epk: EphemeralPublicKey(Vec::new()), view_tag: EncryptedAccountData::compute_view_tag(&owner_npk, &owner_vpk), npk: owner_npk, ssk: shared_secret, identifier: 0, }, // token_definition: public InputAccountIdentity::Public, // ata: public InputAccountIdentity::Public, ], &program_with_deps, ) .unwrap(); let message = Message::try_from_circuit_output( vec![Ids::token_definition(), owner_ata_id], vec![], 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, 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), } ); }