From d82f06593da312dba61757f17357c8855b9f3b87 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 27 Nov 2025 12:08:27 -0300 Subject: [PATCH 01/30] add pda_seeds field --- nssa/core/src/program.rs | 5 +++++ nssa/src/public_transaction/transaction.rs | 1 + nssa/test_program_methods/guest/src/bin/chain_caller.rs | 2 ++ 3 files changed, 8 insertions(+) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 054f993..927d5fc 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -12,12 +12,17 @@ pub struct ProgramInput { pub instruction: T, } +#[derive(Serialize, Deserialize, Clone)] +#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +pub struct PdaSeed([u8; 32]); + #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ChainedCall { pub program_id: ProgramId, pub instruction_data: InstructionData, pub pre_states: Vec, + pub pda_seeds: Vec } #[derive(Serialize, Deserialize, Clone)] diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 28f33fb..081fe2f 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -107,6 +107,7 @@ impl PublicTransaction { program_id: message.program_id, instruction_data: message.instruction_data.clone(), pre_states: input_pre_states, + pda_seeds: vec![], }; let mut chained_calls = VecDeque::from_iter([initial_call]); diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/nssa/test_program_methods/guest/src/bin/chain_caller.rs index 028f8a0..23b0244 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/nssa/test_program_methods/guest/src/bin/chain_caller.rs @@ -25,6 +25,7 @@ fn main() { program_id, instruction_data: instruction_data.clone(), pre_states: vec![receiver_pre.clone(), sender_pre.clone()], // <- Account order permutation here + pda_seeds: vec![] }; num_chain_calls as usize - 1 ]; @@ -33,6 +34,7 @@ fn main() { program_id, instruction_data, pre_states: vec![receiver_pre.clone(), sender_pre.clone()], // <- Account order permutation here + pda_seeds: vec![], }); write_nssa_outputs_with_chained_call( From 3fbf1e1fec33e57de6c0018d695ffa4f695e5033 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 27 Nov 2025 13:10:38 -0300 Subject: [PATCH 02/30] add pda mechanism --- nssa/core/src/program.rs | 26 +++++++++++++++-- nssa/src/public_transaction/transaction.rs | 33 ++++++++++++++++++---- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 927d5fc..ad9bbab 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,7 +1,7 @@ use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer}; use serde::{Deserialize, Serialize}; -use crate::account::{Account, AccountWithMetadata}; +use crate::account::{Account, AccountId, AccountWithMetadata}; pub type ProgramId = [u32; 8]; pub type InstructionData = Vec; @@ -16,13 +16,35 @@ pub struct ProgramInput { #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct PdaSeed([u8; 32]); +#[cfg(feature = "host")] +impl From<(&ProgramId, &PdaSeed)> for AccountId { + fn from(value: (&ProgramId, &PdaSeed)) -> Self { + use risc0_zkvm::sha::{Impl, Sha256}; + const PROGRAM_DERIVED_ACCOUNT_ID_PREFIX: &[u8; 32] = + b"/NSSA/v0.2/AccountId/PDA/\x00\x00\x00\x00\x00\x00\x00"; + + let mut bytes = [0; 96]; + bytes[0..32].copy_from_slice(PROGRAM_DERIVED_ACCOUNT_ID_PREFIX); + let program_id_bytes: &[u8] = + bytemuck::try_cast_slice(value.0).expect("ProgramId should be castable to &[u8]"); + bytes[32..64].copy_from_slice(program_id_bytes); + bytes[64..].copy_from_slice(&value.1.0); + AccountId::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) + } +} + #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ChainedCall { pub program_id: ProgramId, pub instruction_data: InstructionData, pub pre_states: Vec, - pub pda_seeds: Vec + pub pda_seeds: Vec, } #[derive(Serialize, Deserialize, Clone)] diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 081fe2f..cafa27b 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, - program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, + program::{ChainedCall, DEFAULT_PROGRAM_ID, PdaSeed, ProgramId, validate_execution}, }; use sha2::{Digest, digest::FixedOutput}; @@ -110,10 +110,10 @@ impl PublicTransaction { pda_seeds: vec![], }; - let mut chained_calls = VecDeque::from_iter([initial_call]); + let mut chained_calls = VecDeque::from_iter([(initial_call, None)]); let mut chain_calls_counter = 0; - while let Some(chained_call) = chained_calls.pop_front() { + while let Some((chained_call, caller_program_id)) = chained_calls.pop_front() { if chain_calls_counter > MAX_NUMBER_CHAINED_CALLS { return Err(NssaError::MaxChainedCallsDepthExceeded); } @@ -126,6 +126,9 @@ impl PublicTransaction { let mut program_output = program.execute(&chained_call.pre_states, &chained_call.instruction_data)?; + let authorized_pdas = + self.compute_authorized_pdas(&caller_program_id, &chained_call.pda_seeds); + for pre in &program_output.pre_states { let account_id = pre.account_id; // Check that the program output pre_states coinicide with the values in the public @@ -138,8 +141,11 @@ impl PublicTransaction { return Err(NssaError::InvalidProgramBehavior); } - // Check that authorization flags are consistent with the provided ones - if pre.is_authorized && !signer_account_ids.contains(&account_id) { + // Check that authorization flags are consistent with the provided ones or + // authorized by program through the PDA mechanism + let is_authorized = signer_account_ids.contains(&account_id) + || authorized_pdas.contains(&account_id); + if pre.is_authorized && !is_authorized { return Err(NssaError::InvalidProgramBehavior); } } @@ -171,7 +177,7 @@ impl PublicTransaction { } for new_call in program_output.chained_calls.into_iter().rev() { - chained_calls.push_front(new_call); + chained_calls.push_front((new_call, Some(chained_call.program_id))); } chain_calls_counter += 1; @@ -179,6 +185,21 @@ impl PublicTransaction { Ok(state_diff) } + + fn compute_authorized_pdas( + &self, + caller_program_id: &Option, + pda_seeds: &[PdaSeed], + ) -> HashSet { + if let Some(caller_program_id) = caller_program_id { + pda_seeds + .iter() + .map(|pda_seed| AccountId::from((caller_program_id, pda_seed))) + .collect() + } else { + HashSet::new() + } + } } #[cfg(test)] From e61a971790cf5ba9cde4e7de9a34b401f2460aff Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 27 Nov 2025 13:49:56 -0300 Subject: [PATCH 03/30] add test and refactor chain_caller program --- nssa/core/src/program.rs | 6 ++ nssa/src/state.rs | 60 +++++++++++++++++-- .../guest/src/bin/chain_caller.rs | 50 +++++++++------- 3 files changed, 88 insertions(+), 28 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index ad9bbab..dfd52e4 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -16,6 +16,12 @@ pub struct ProgramInput { #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct PdaSeed([u8; 32]); +impl PdaSeed { + pub fn new(value: [u8; 32]) -> Self { + Self(value) + } +} + #[cfg(feature = "host")] impl From<(&ProgramId, &PdaSeed)> for AccountId { fn from(value: (&ProgramId, &PdaSeed)) -> Self { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index cef7791..79541a3 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -250,7 +250,7 @@ pub mod tests { Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce}, encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar}, - program::ProgramId, + program::{PdaSeed, ProgramId}, }; use crate::{ @@ -2092,14 +2092,18 @@ pub mod tests { let key = PrivateKey::try_new([1; 32]).unwrap(); let from = AccountId::from(&PublicKey::new_from_private_key(&key)); let to = AccountId::new([2; 32]); - let initial_balance = 100; + let initial_balance = 1000; let initial_data = [(from, initial_balance), (to, 0)]; let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let from_key = key; - let amount: u128 = 0; - let instruction: (u128, ProgramId, u32) = - (amount, Program::authenticated_transfer_program().id(), 2); + let amount: u128 = 37; + let instruction: (u128, ProgramId, u32, Option) = ( + amount, + Program::authenticated_transfer_program().id(), + 2, + None, + ); let expected_to_post = Account { program_owner: Program::authenticated_transfer_program().id(), @@ -2139,10 +2143,11 @@ pub mod tests { V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let from_key = key; let amount: u128 = 0; - let instruction: (u128, ProgramId, u32) = ( + let instruction: (u128, ProgramId, u32, Option) = ( amount, Program::authenticated_transfer_program().id(), MAX_NUMBER_CHAINED_CALLS as u32 + 1, + None, ); let message = public_transaction::Message::try_new( @@ -2162,4 +2167,47 @@ pub mod tests { Err(NssaError::MaxChainedCallsDepthExceeded) )); } + #[test] + fn test_execution_that_requires_authentication_of_a_program_derived_account_id_succeeds() { + let chain_caller = Program::chain_caller(); + let pda_seed = PdaSeed::new([37; 32]); + let from = AccountId::from((&chain_caller.id(), &pda_seed)); + let to = AccountId::new([2; 32]); + let initial_balance = 1000; + let initial_data = [(from, initial_balance), (to, 0)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let amount: u128 = 58; + let instruction: (u128, ProgramId, u32, Option) = ( + amount, + Program::authenticated_transfer_program().id(), + 1, + Some(pda_seed), + ); + + let expected_to_post = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: amount, // The `chain_caller` chains the program twice + ..Account::default() + }; + + let message = public_transaction::Message::try_new( + chain_caller.id(), + vec![to, from], // The chain_caller program permutes the account order in the chain + // call + vec![], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + state.transition_from_public_transaction(&tx).unwrap(); + + let from_post = state.get_account_by_id(&from); + let to_post = state.get_account_by_id(&to); + // The `chain_caller` program calls the program twice + assert_eq!(from_post.balance, initial_balance - amount); + assert_eq!(to_post, expected_to_post); + } } diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/nssa/test_program_methods/guest/src/bin/chain_caller.rs index 23b0244..1885da2 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/nssa/test_program_methods/guest/src/bin/chain_caller.rs @@ -1,45 +1,51 @@ use nssa_core::program::{ - ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call, + ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, }; use risc0_zkvm::serde::to_vec; -type Instruction = (u128, ProgramId, u32); +type Instruction = (u128, ProgramId, u32, Option); /// A program that calls another program `num_chain_calls` times. /// It permutes the order of the input accounts on the subsequent call +/// The `ProgramId` in the instruction must be the program_id of the authenticated transfers program fn main() { let ProgramInput { pre_states, - instruction: (balance, program_id, num_chain_calls), + instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed), } = read_nssa_inputs::(); - let [sender_pre, receiver_pre] = match pre_states.try_into() { + let [recipient_pre, sender_pre] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; let instruction_data = to_vec(&balance).unwrap(); - let mut chained_call = vec![ - ChainedCall { - program_id, - instruction_data: instruction_data.clone(), - pre_states: vec![receiver_pre.clone(), sender_pre.clone()], // <- Account order permutation here - pda_seeds: vec![] - }; - num_chain_calls as usize - 1 - ]; + let mut running_recipient_pre = recipient_pre.clone(); + let mut running_sender_pre = sender_pre.clone(); - chained_call.push(ChainedCall { - program_id, - instruction_data, - pre_states: vec![receiver_pre.clone(), sender_pre.clone()], // <- Account order permutation here - pda_seeds: vec![], - }); + if pda_seed.is_some() { + running_sender_pre.is_authorized = true; + } + + let mut chained_calls = Vec::new(); + for _i in 0..num_chain_calls { + let new_chained_call = ChainedCall { + program_id: auth_transfer_id, + instruction_data: instruction_data.clone(), + pre_states: vec![running_sender_pre.clone(), running_recipient_pre.clone()], // <- Account order permutation here + pda_seeds: pda_seed.iter().cloned().collect(), + }; + chained_calls.push(new_chained_call); + + running_sender_pre.account.balance -= balance; + running_recipient_pre.account.balance += balance; + } write_nssa_outputs_with_chained_call( - vec![sender_pre.clone(), receiver_pre.clone()], - vec![sender_pre.account, receiver_pre.account], - chained_call, + vec![recipient_pre.clone(), sender_pre.clone()], + vec![recipient_pre.account, sender_pre.account], + chained_calls, ); } From 1989fd25a178eca653992ef3c47e1541eb490378 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 28 Nov 2025 11:10:00 -0300 Subject: [PATCH 04/30] add pinata token example and test --- nssa/core/Cargo.toml | 2 +- .../guest/src/bin/pinata_token.rs | 103 ++++++++++++++++++ nssa/program_methods/guest/src/bin/token.rs | 49 ++++++++- nssa/src/program.rs | 5 + nssa/src/state.rs | 91 ++++++++++++++++ 5 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 nssa/program_methods/guest/src/bin/pinata_token.rs diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index 67f40b2..0e16a3f 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -12,7 +12,7 @@ chacha20 = { version = "0.9", default-features = false } k256 = { version = "0.13.3", optional = true } base58 = { version = "0.2.0", optional = true } anyhow = { version = "1.0.98", optional = true } -borsh.workspace = true +borsh = "1.5.7" [features] default = [] diff --git a/nssa/program_methods/guest/src/bin/pinata_token.rs b/nssa/program_methods/guest/src/bin/pinata_token.rs new file mode 100644 index 0000000..ab04237 --- /dev/null +++ b/nssa/program_methods/guest/src/bin/pinata_token.rs @@ -0,0 +1,103 @@ +use nssa_core::program::{ + ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call, +}; +use risc0_zkvm::serde::to_vec; +use risc0_zkvm::sha::{Impl, Sha256}; + +const PRIZE: u128 = 150; + +type Instruction = u128; + +struct Challenge { + difficulty: u8, + seed: [u8; 32], +} + +impl Challenge { + fn new(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 33); + let difficulty = bytes[0]; + assert!(difficulty <= 32); + + let mut seed = [0; 32]; + seed.copy_from_slice(&bytes[1..]); + Self { difficulty, seed } + } + + // Checks if the leftmost `self.difficulty` number of bytes of SHA256(self.data || solution) are + // zero. + fn validate_solution(&self, solution: Instruction) -> bool { + let mut bytes = [0; 32 + 16]; + bytes[..32].copy_from_slice(&self.seed); + bytes[32..].copy_from_slice(&solution.to_le_bytes()); + let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); + let difficulty = self.difficulty as usize; + digest[..difficulty].iter().all(|&b| b == 0) + } + + fn next_data(self) -> [u8; 33] { + let mut result = [0; 33]; + result[0] = self.difficulty; + result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); + result + } +} + +/// A pinata program +fn main() { + // Read input accounts. + // It is expected to receive three accounts: [pinata_definition, pinata_token_holding, winner_token_holding] + let ProgramInput { + pre_states, + instruction: solution, + } = read_nssa_inputs::(); + + let [ + pinata_definition, + pinata_token_holding, + winner_token_holding, + ] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let data = Challenge::new(&pinata_definition.account.data); + + if !data.validate_solution(solution) { + return; + } + + let mut pinata_definition_post = pinata_definition.account.clone(); + let pinata_token_holding_post = pinata_token_holding.account.clone(); + let winner_token_holding_post = winner_token_holding.account.clone(); + pinata_definition_post.data = data.next_data().to_vec(); + + let mut instruction_data: [u8; 23] = [0; 23]; + instruction_data[0] = 1; + instruction_data[1..17].copy_from_slice(&PRIZE.to_le_bytes()); + + // Flip authorization to true for chained call + let mut pinata_token_holding_for_chain_call = pinata_token_holding.clone(); + pinata_token_holding_for_chain_call.is_authorized = true; + + let chained_calls = vec![ChainedCall { + program_id: pinata_token_holding_post.program_owner, + instruction_data: to_vec(&instruction_data).unwrap(), + pre_states: vec![pinata_token_holding_for_chain_call, winner_token_holding.clone()], + pda_seeds: vec![PdaSeed::new([0; 32])], + }]; + + write_nssa_outputs_with_chained_call( + vec![ + pinata_definition, + pinata_token_holding, + winner_token_holding, + ], + vec![ + pinata_definition_post, + pinata_token_holding_post, + winner_token_holding_post, + ], + chained_calls, + ); +} diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index e5680be..59aae6a 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -45,6 +45,21 @@ impl TokenDefinition { bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); bytes.into() } + + fn parse(data: &[u8]) -> Option { + if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { + None + } else { + let account_type = data[0]; + let name = data[1..7].try_into().unwrap(); + let total_supply = u128::from_le_bytes(data[7..].try_into().unwrap()); + Some(Self { + account_type, + name, + total_supply, + }) + } + } } impl TokenHolding { @@ -196,15 +211,47 @@ fn main() { let post_states = transfer(&pre_states, balance_to_move); write_nssa_outputs(pre_states, post_states); } + 2 => { + // Initialize account + assert_eq!(instruction[1..], [0; 22]); + let post_states = initialize(&pre_states); + write_nssa_outputs(pre_states, post_states); + } _ => panic!("Invalid instruction"), }; } +fn initialize(pre_states: &[AccountWithMetadata]) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let definition = &pre_states[0]; + let account_to_initialize = &pre_states[1]; + + if account_to_initialize.account != Account::default() { + panic!("Only uninitialized accounts can be initialized"); + } + + // TODO: We should check that this is an account owned by the token program. + // This check can't be done here since the ID of the program is known only after compiling it + // Check definition account is valid + let _definition_values = + TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); + let holding_for_definition = TokenHolding::new(&definition.account_id); + + let definition_post = definition.account.clone(); + let mut account_to_initialize_post = account_to_initialize.account.clone(); + account_to_initialize_post.data = holding_for_definition.into_data(); + + vec![definition_post, account_to_initialize_post] +} + #[cfg(test)] mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata}; - use crate::{new_definition, transfer, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE}; + use crate::{TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE, new_definition, transfer}; #[should_panic(expected = "Invalid number of input accounts")] #[test] diff --git a/nssa/src/program.rs b/nssa/src/program.rs index d3f28b5..4d2232d 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -104,6 +104,11 @@ impl Program { // `program_methods` Self::new(PINATA_ELF.to_vec()).unwrap() } + + pub fn pinata_token() -> Self { + use crate::program_methods::PINATA_TOKEN_ELF; + Self::new(PINATA_TOKEN_ELF.to_vec()).unwrap() + } } #[cfg(test)] diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 79541a3..78b8b7b 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -239,6 +239,20 @@ impl V02State { }, ); } + + pub fn add_pinata_token_program(&mut self, account_id: AccountId) { + self.insert_program(Program::pinata_token()); + + self.public_state.insert( + account_id, + Account { + program_owner: Program::pinata_token().id(), + // Difficulty: 3 + data: vec![3; 33], + ..Account::default() + }, + ); + } } #[cfg(test)] @@ -2210,4 +2224,81 @@ pub mod tests { assert_eq!(from_post.balance, initial_balance - amount); assert_eq!(to_post, expected_to_post); } + + #[test] + fn test_pda_mechanism_with_pinata_token_program() { + let pinata_token = Program::pinata_token(); + let token = Program::token(); + + let pinata_definition_id = AccountId::new([1; 32]); + let pinata_token_definition_id = AccountId::new([2; 32]); + // Total supply of pinata token will be in an account under a PDA. + let pinata_token_holding_id = AccountId::from((&pinata_token.id(), &PdaSeed::new([0; 32]))); + let winner_token_holding_id = AccountId::new([3; 32]); + + let mut expected_winner_account_data = [0; 49]; + expected_winner_account_data[0] = 1; + expected_winner_account_data[1..33].copy_from_slice(pinata_token_definition_id.value()); + expected_winner_account_data[33..].copy_from_slice(&150u128.to_le_bytes()); + let expected_winner_token_holding_post = Account { + program_owner: token.id(), + data: expected_winner_account_data.to_vec(), + ..Account::default() + }; + + let mut state = V02State::new_with_genesis_accounts(&[], &[]); + state.add_pinata_token_program(pinata_definition_id); + + // Execution of the token program to create new token for the pinata token + // definition and supply accounts + let total_supply: u128 = 10_000_000; + // instruction: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] + let mut instruction: [u8; 23] = [0; 23]; + instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); + instruction[17..].copy_from_slice(b"PINATA"); + let message = public_transaction::Message::try_new( + token.id(), + vec![pinata_token_definition_id, pinata_token_holding_id], + vec![], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + // Execution of the token program transfer just to initialize the winner token account + let mut instruction: [u8; 23] = [0; 23]; + instruction[0] = 2; + let message = public_transaction::Message::try_new( + token.id(), + vec![pinata_token_definition_id, winner_token_holding_id], + vec![], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + // Submit a solution to the pinata program to claim the prize + let solution: u128 = 989106; + let message = public_transaction::Message::try_new( + pinata_token.id(), + vec![ + pinata_definition_id, + pinata_token_holding_id, + winner_token_holding_id, + ], + vec![], + solution, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + let winner_token_holding_post = state.get_account_by_id(&winner_token_holding_id); + assert_eq!(winner_token_holding_post, expected_winner_token_holding_post); + } } From dd5db5b32c77dc62a3dd872e0301d7e96d222eb8 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 28 Nov 2025 11:37:49 -0300 Subject: [PATCH 05/30] add init function to the token program --- nssa/program_methods/guest/src/bin/token.rs | 135 ++++++++++++++++++-- 1 file changed, 125 insertions(+), 10 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index e5680be..e7f7e43 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -18,6 +18,11 @@ use nssa_core::{ // * Two accounts: [sender_account, recipient_account]. // * An instruction data byte string of length 23, indicating the total supply with the following layout // [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// 3. Initialize account with zero balance +// Arguments to this function are: +// * Two accounts: [definition_account, account_to_initialize]. +// * An dummy byte string of length 23, with the following layout +// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. const TOKEN_DEFINITION_TYPE: u8 = 0; const TOKEN_DEFINITION_DATA_SIZE: usize = 23; @@ -45,6 +50,25 @@ impl TokenDefinition { bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); bytes.into() } + + fn parse(data: &[u8]) -> Option { + if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { + None + } else { + let account_type = data[0]; + let name = data[1..7].try_into().unwrap(); + let total_supply = u128::from_le_bytes( + data[7..] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + Some(Self { + account_type, + name, + total_supply, + }) + } + } } impl TokenHolding { @@ -61,8 +85,16 @@ impl TokenHolding { None } else { let account_type = data[0]; - let definition_id = AccountId::new(data[1..33].try_into().unwrap()); - let balance = u128::from_le_bytes(data[33..].try_into().unwrap()); + let definition_id = AccountId::new( + data[1..33] + .try_into() + .expect("Defintion ID must be 32 bytes long"), + ); + let balance = u128::from_le_bytes( + data[33..] + .try_into() + .expect("balance must be 16 bytes little-endian"), + ); Some(Self { definition_id, balance, @@ -167,6 +199,33 @@ fn new_definition( vec![definition_target_account_post, holding_target_account_post] } +fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let definition = &pre_states[0]; + let account_to_initialize = &pre_states[1]; + + if account_to_initialize.account != Account::default() { + panic!("Only uninitialized accounts can be initialized"); + } + + // TODO: We should check that this is an account owned by the token program. + // This check can't be done here since the ID of the program is known only after compiling it + // + // Check definition account is valid + let _definition_values = + TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); + let holding_values = TokenHolding::new(&definition.account_id); + + let definition_post = definition.account.clone(); + let mut account_to_initialize_post = account_to_initialize.account.clone(); + account_to_initialize_post.data = holding_values.into_data(); + + vec![definition_post, account_to_initialize_post] +} + type Instruction = [u8; 23]; fn main() { @@ -175,36 +234,59 @@ fn main() { instruction, } = read_nssa_inputs::(); - match instruction[0] { + let (pre_states, post_states) = match instruction[0] { 0 => { // Parse instruction - let total_supply = u128::from_le_bytes(instruction[1..17].try_into().unwrap()); - let name: [u8; 6] = instruction[17..].try_into().unwrap(); + let total_supply = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); assert_ne!(name, [0; 6]); // Execute let post_states = new_definition(&pre_states, name, total_supply); - write_nssa_outputs(pre_states, post_states); + (pre_states, post_states) } 1 => { // Parse instruction - let balance_to_move = u128::from_le_bytes(instruction[1..17].try_into().unwrap()); - let name: [u8; 6] = instruction[17..].try_into().unwrap(); + let balance_to_move = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Balance to move must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); assert_eq!(name, [0; 6]); // Execute let post_states = transfer(&pre_states, balance_to_move); - write_nssa_outputs(pre_states, post_states); + (pre_states, post_states) + } + 2 => { + // Initialize account + assert_eq!(instruction[1..], [0; 22]); + let post_states = initialize_account(&pre_states); + (pre_states, post_states) } _ => panic!("Invalid instruction"), }; + + write_nssa_outputs(pre_states, post_states); } #[cfg(test)] mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata}; - use crate::{new_definition, transfer, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE}; + use crate::{ + TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE, + initialize_account, new_definition, transfer, + }; #[should_panic(expected = "Invalid number of input accounts")] #[test] @@ -551,4 +633,37 @@ mod tests { ] ); } + + #[test] + fn test_token_initialize_account_succeeds() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Definition ID with + data: vec![0; TOKEN_DEFINITION_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(1000)) + .collect(), + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: AccountId::new([2; 32]), + }, + ]; + let post_states = initialize_account(&pre_states); + let [definition, holding] = post_states.try_into().ok().unwrap(); + assert_eq!(definition.data, pre_states[0].account.data); + assert_eq!( + holding.data, + vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } } From bfbd50e8cbe2ebf17efda6841dca98553cb75cfd Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 28 Nov 2025 17:09:38 -0300 Subject: [PATCH 06/30] add docs --- nssa/core/src/program.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index dfd52e4..1028d54 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -12,6 +12,11 @@ pub struct ProgramInput { pub instruction: T, } +/// A 32-byte seed used to compute a *Program-Derived AccountId* (PDA). +/// +/// Each program can derive up to `2^32` unique account IDs by choosing different +/// seeds. PDAs allow programs to control namespaced account identifiers without +/// collisions between programs. #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct PdaSeed([u8; 32]); From 3d529d19fa82e5f37c88fd4cdc0f11c62b8875b1 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Dec 2025 10:48:21 -0300 Subject: [PATCH 07/30] solve comments --- nssa/core/src/program.rs | 2 +- nssa/program_methods/guest/src/bin/token.rs | 39 ++++++++++----------- nssa/src/public_transaction/transaction.rs | 2 +- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 1028d54..b48a08b 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -14,7 +14,7 @@ pub struct ProgramInput { /// A 32-byte seed used to compute a *Program-Derived AccountId* (PDA). /// -/// Each program can derive up to `2^32` unique account IDs by choosing different +/// Each program can derive up to `2^256` unique account IDs by choosing different /// seeds. PDAs allow programs to control namespaced account identifiers without /// collisions between programs. #[derive(Serialize, Deserialize, Clone)] diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 0109c2b..b527045 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -82,25 +82,25 @@ impl TokenHolding { fn parse(data: &[u8]) -> Option { if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { - None - } else { - let account_type = data[0]; - let definition_id = AccountId::new( - data[1..33] - .try_into() - .expect("Defintion ID must be 32 bytes long"), - ); - let balance = u128::from_le_bytes( - data[33..] - .try_into() - .expect("balance must be 16 bytes little-endian"), - ); - Some(Self { - definition_id, - balance, - account_type, - }) + return None; } + + let account_type = data[0]; + let definition_id = AccountId::new( + data[1..33] + .try_into() + .expect("Defintion ID must be 32 bytes long"), + ); + let balance = u128::from_le_bytes( + data[33..] + .try_into() + .expect("balance must be 16 bytes little-endian"), + ); + Some(Self { + definition_id, + balance, + account_type, + }) } fn into_data(self) -> Data { @@ -211,7 +211,7 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { panic!("Only uninitialized accounts can be initialized"); } - // TODO: We should check that this is an account owned by the token program. + // TODO: #212 We should check that this is an account owned by the token program. // This check can't be done here since the ID of the program is known only after compiling it // // Check definition account is valid @@ -278,7 +278,6 @@ fn main() { write_nssa_outputs(pre_states, post_states); } - #[cfg(test)] mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata}; diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index cafa27b..b8c0a8d 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -145,7 +145,7 @@ impl PublicTransaction { // authorized by program through the PDA mechanism let is_authorized = signer_account_ids.contains(&account_id) || authorized_pdas.contains(&account_id); - if pre.is_authorized && !is_authorized { + if pre.is_authorized != is_authorized { return Err(NssaError::InvalidProgramBehavior); } } From fdc53927ca5f2a33c8e81243ac2efa397f47a7e6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy <41742639+schouhy@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:50:06 -0300 Subject: [PATCH 08/30] Update nssa/src/program.rs Co-authored-by: Daniil Polyakov --- nssa/src/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 4d2232d..f60237a 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -107,7 +107,7 @@ impl Program { pub fn pinata_token() -> Self { use crate::program_methods::PINATA_TOKEN_ELF; - Self::new(PINATA_TOKEN_ELF.to_vec()).unwrap() + Self::new(PINATA_TOKEN_ELF.to_vec()).expect("pinata token elf is defined in risc0 build of `program_methods`") } } From dcef017f9b95c839429fa77002dee6d862304fd9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Dec 2025 12:12:56 -0300 Subject: [PATCH 09/30] nit --- nssa/program_methods/guest/src/bin/token.rs | 2 +- nssa/src/program.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index b527045..71a4b8d 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -639,7 +639,7 @@ mod tests { AccountWithMetadata { account: Account { // Definition ID with - data: vec![0; TOKEN_DEFINITION_DATA_SIZE - 16] + data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(1000)) .collect(), diff --git a/nssa/src/program.rs b/nssa/src/program.rs index f60237a..b522c8a 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -107,7 +107,8 @@ impl Program { pub fn pinata_token() -> Self { use crate::program_methods::PINATA_TOKEN_ELF; - Self::new(PINATA_TOKEN_ELF.to_vec()).expect("pinata token elf is defined in risc0 build of `program_methods`") + Self::new(PINATA_TOKEN_ELF.to_vec()) + .expect("pinata token elf is defined in risc0 build of `program_methods`") } } From 407c3f3c9581667055993341a4353624058ec964 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Dec 2025 16:27:22 -0300 Subject: [PATCH 10/30] improve expect message --- nssa/src/program.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nssa/src/program.rs b/nssa/src/program.rs index b522c8a..21a03ce 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -107,8 +107,7 @@ impl Program { pub fn pinata_token() -> Self { use crate::program_methods::PINATA_TOKEN_ELF; - Self::new(PINATA_TOKEN_ELF.to_vec()) - .expect("pinata token elf is defined in risc0 build of `program_methods`") + Self::new(PINATA_TOKEN_ELF.to_vec()).expect("Piñata program must be a valid R0BF file") } } From 78ce57e19bc74d5321f590bc3d51812f59dda7a3 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 3 Dec 2025 13:50:10 +0200 Subject: [PATCH 11/30] fix: correct tokens names --- wallet/Cargo.toml | 1 + wallet/src/cli/account.rs | 56 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 3b12d8f..aeceb79 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -27,6 +27,7 @@ path = "../key_protocol" [dependencies.nssa] path = "../nssa" +features = ["no_docker"] [dependencies.common] path = "../common" diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 5b23b2b..f6bc90a 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -178,7 +178,17 @@ impl From for TokedDefinitionAccountView { fn from(value: TokenDefinition) -> Self { Self { account_type: "Token definition".to_string(), - name: hex::encode(value.name), + name: { + let mut name_vec_trim = vec![]; + for ch in value.name { + // Assuming, that name does not have UTF-8 NULL and all zeroes are padding. + if ch == 0 { + break; + } + name_vec_trim.push(ch); + } + String::from_utf8(name_vec_trim).unwrap_or(hex::encode(value.name)) + }, total_supply: value.total_supply, } } @@ -343,3 +353,47 @@ impl WalletSubcommand for AccountSubcommand { } } } + +#[cfg(test)] +mod tests { + use crate::cli::account::{TokedDefinitionAccountView, TokenDefinition}; + + #[test] + fn test_invalid_utf_8_name_of_token() { + let token_def = TokenDefinition { + account_type: 1, + name: [137, 12, 14, 3, 5, 4], + total_supply: 100, + }; + + let token_def_view: TokedDefinitionAccountView = token_def.into(); + + assert_eq!(token_def_view.name, "890c0e030504"); + } + + #[test] + fn test_valid_utf_8_name_of_token_all_bytes() { + let token_def = TokenDefinition { + account_type: 1, + name: [240, 159, 146, 150, 66, 66], + total_supply: 100, + }; + + let token_def_view: TokedDefinitionAccountView = token_def.into(); + + assert_eq!(token_def_view.name, "💖BB"); + } + + #[test] + fn test_valid_utf_8_name_of_token_less_bytes() { + let token_def = TokenDefinition { + account_type: 1, + name: [78, 65, 77, 69, 0, 0], + total_supply: 100, + }; + + let token_def_view: TokedDefinitionAccountView = token_def.into(); + + assert_eq!(token_def_view.name, "NAME"); + } +} From 282b932a8e33f16785814ffa4ccdca4b32478f0e Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 3 Dec 2025 14:30:23 +0200 Subject: [PATCH 12/30] fix: correct feature --- wallet/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index aeceb79..3b12d8f 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -27,7 +27,6 @@ path = "../key_protocol" [dependencies.nssa] path = "../nssa" -features = ["no_docker"] [dependencies.common] path = "../common" From d677db7f4e5170432f302442e1cc8c74d90a37e0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Dec 2025 17:12:32 -0300 Subject: [PATCH 13/30] add account post state struct with claiming request field --- nssa/core/src/program.rs | 68 ++++++++++++++++--- .../guest/src/bin/authenticated_transfer.rs | 11 +-- nssa/program_methods/guest/src/bin/pinata.rs | 2 +- .../src/bin/privacy_preserving_circuit.rs | 4 +- nssa/program_methods/guest/src/bin/token.rs | 26 +++---- nssa/src/program.rs | 4 +- nssa/src/public_transaction/transaction.rs | 6 +- .../guest/src/bin/burner.rs | 2 +- .../guest/src/bin/chain_caller.rs | 2 +- .../guest/src/bin/data_changer.rs | 2 +- .../guest/src/bin/extra_output.rs | 2 +- .../guest/src/bin/minter.rs | 2 +- .../guest/src/bin/missing_output.rs | 2 +- .../guest/src/bin/nonce_changer.rs | 2 +- .../guest/src/bin/program_owner_changer.rs | 2 +- .../guest/src/bin/simple_balance_transfer.rs | 4 +- 16 files changed, 96 insertions(+), 45 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 054f993..c79a841 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -20,11 +20,34 @@ pub struct ChainedCall { pub pre_states: Vec, } +#[derive(Serialize, Deserialize, Clone)] +#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +pub struct AccountPostState { + pub account: Account, + pub claim: bool, +} + +impl From for AccountPostState { + fn from(account: Account) -> Self { + AccountPostState { + account, + claim: false, + } + } +} + +impl AccountPostState { + pub fn with_claim_request(mut self) -> Self { + self.claim = true; + self + } +} + #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ProgramOutput { pub pre_states: Vec, - pub post_states: Vec, + pub post_states: Vec, pub chained_calls: Vec, } @@ -38,7 +61,10 @@ pub fn read_nssa_inputs() -> ProgramInput { } } -pub fn write_nssa_outputs(pre_states: Vec, post_states: Vec) { +pub fn write_nssa_outputs( + pre_states: Vec, + post_states: Vec, +) { let output = ProgramOutput { pre_states, post_states, @@ -49,7 +75,7 @@ pub fn write_nssa_outputs(pre_states: Vec, post_states: Vec pub fn write_nssa_outputs_with_chained_call( pre_states: Vec, - post_states: Vec, + post_states: Vec, chained_calls: Vec, ) { let output = ProgramOutput { @@ -68,7 +94,7 @@ pub fn write_nssa_outputs_with_chained_call( /// - `executing_program_id`: The identifier of the program that was executed. pub fn validate_execution( pre_states: &[AccountWithMetadata], - post_states: &[Account], + post_states: &[AccountPostState], executing_program_id: ProgramId, ) -> bool { // 1. Lengths must match @@ -78,25 +104,27 @@ pub fn validate_execution( for (pre, post) in pre_states.iter().zip(post_states) { // 2. Nonce must remain unchanged - if pre.account.nonce != post.nonce { + if pre.account.nonce != post.account.nonce { return false; } // 3. Program ownership changes are not allowed - if pre.account.program_owner != post.program_owner { + if pre.account.program_owner != post.account.program_owner { return false; } let account_program_owner = pre.account.program_owner; // 4. Decreasing balance only allowed if owned by executing program - if post.balance < pre.account.balance && account_program_owner != executing_program_id { + if post.account.balance < pre.account.balance + && account_program_owner != executing_program_id + { return false; } // 5. Data changes only allowed if owned by executing program or if account pre state has // default values - if pre.account.data != post.data + if pre.account.data != post.account.data && pre.account != Account::default() && account_program_owner != executing_program_id { @@ -105,17 +133,37 @@ pub fn validate_execution( // 6. If a post state has default program owner, the pre state must have been a default // account - if post.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() { + if post.account.program_owner == DEFAULT_PROGRAM_ID && pre.account != Account::default() { return false; } } // 7. Total balance is preserved let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum(); - let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum(); + let total_balance_post_states: u128 = post_states.iter().map(|post| post.account.balance).sum(); if total_balance_pre_states != total_balance_post_states { return false; } true } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_account_post_state_from_account_constructor() { + let account = Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 1337, + data: vec![0xde, 0xad, 0xbe, 0xef], + nonce: 10, + }; + + let account_post_state: AccountPostState = account.clone().into(); + + assert_eq!(account, account_post_state.account); + assert!(!account_post_state.claim); + } +} diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index df8a38e..14aded4 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -1,16 +1,16 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, - program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}, + program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}, }; /// Initializes a default account under the ownership of this program. /// This is achieved by a noop. fn initialize_account(pre_state: AccountWithMetadata) { - let account_to_claim = pre_state.account.clone(); + let account_to_claim: AccountPostState = pre_state.account.clone().into(); let is_authorized = pre_state.is_authorized; // Continue only if the account to claim has default values - if account_to_claim != Account::default() { + if account_to_claim.account != Account::default() { return; } @@ -41,7 +41,10 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance sender_post.balance -= balance_to_move; recipient_post.balance += balance_to_move; - write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]); + write_nssa_outputs( + vec![sender, recipient], + vec![sender_post.into(), recipient_post.into()], + ); } /// A transfer of balance program. diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs index fbea167..9337ab7 100644 --- a/nssa/program_methods/guest/src/bin/pinata.rs +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -66,5 +66,5 @@ fn main() { pinata_post.data = data.next_data().to_vec(); winner_post.balance += PRIZE; - write_nssa_outputs(vec![pinata, winner], vec![pinata_post, winner_post]); + write_nssa_outputs(vec![pinata, winner], vec![pinata_post.into(), winner_post.into()]); } diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 6696245..e822f88 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -70,7 +70,7 @@ fn main() { // Public account public_pre_states.push(pre_states[i].clone()); - let mut post = post_states[i].clone(); + let mut post = post_states[i].account.clone(); if pre_states[i].is_authorized { post.nonce += 1; } @@ -126,7 +126,7 @@ fn main() { } // Update post-state with new nonce - let mut post_with_updated_values = post_states[i].clone(); + let mut post_with_updated_values = post_states[i].account.clone(); post_with_updated_values.nonce = *new_nonce; if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 821438a..1e9cc80 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -1,6 +1,6 @@ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data}, - program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}, + program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput}, }; // The token program has three functions: @@ -112,7 +112,7 @@ impl TokenHolding { } } -fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { +fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of input accounts"); } @@ -156,14 +156,14 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec Vec { +) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of input accounts"); } @@ -196,10 +196,10 @@ fn new_definition( let mut holding_target_account_post = holding_target_account.account.clone(); holding_target_account_post.data = token_holding.into_data(); - vec![definition_target_account_post, holding_target_account_post] + vec![definition_target_account_post.into(), holding_target_account_post.into()] } -fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { +fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { if pre_states.len() != 2 { panic!("Invalid number of accounts"); } @@ -223,7 +223,7 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { let mut account_to_initialize_post = account_to_initialize.account.clone(); account_to_initialize_post.data = holding_values.into_data(); - vec![definition_post, account_to_initialize_post] + vec![definition_post.into(), account_to_initialize_post.into()] } type Instruction = [u8; 23]; @@ -387,14 +387,14 @@ mod tests { let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); assert_eq!( - definition_account.data, + definition_account.account.data, vec![ 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( - holding_account.data, + holding_account.account.data, vec![ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -619,14 +619,14 @@ mod tests { let post_states = transfer(&pre_states, 11); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert_eq!( - sender_post.data, + sender_post.account.data, vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( - recipient_post.data, + recipient_post.account.data, vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -657,9 +657,9 @@ mod tests { ]; let post_states = initialize_account(&pre_states); let [definition, holding] = post_states.try_into().ok().unwrap(); - assert_eq!(definition.data, pre_states[0].account.data); + assert_eq!(definition.account.data, pre_states[0].account.data); assert_eq!( - holding.data, + holding.account.data, vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 diff --git a/nssa/src/program.rs b/nssa/src/program.rs index d3f28b5..cf5334c 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -239,8 +239,8 @@ mod tests { let [sender_post, recipient_post] = program_output.post_states.try_into().unwrap(); - assert_eq!(sender_post, expected_sender_post); - assert_eq!(recipient_post, expected_recipient_post); + assert_eq!(sender_post.account, expected_sender_post); + assert_eq!(recipient_post.account, expected_recipient_post); } #[test] diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 28f33fb..560c8b3 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -155,8 +155,8 @@ impl PublicTransaction { // The invoked program claims the accounts with default program id. for post in program_output.post_states.iter_mut() { - if post.program_owner == DEFAULT_PROGRAM_ID { - post.program_owner = chained_call.program_id; + if post.account.program_owner == DEFAULT_PROGRAM_ID { + post.account.program_owner = chained_call.program_id; } } @@ -166,7 +166,7 @@ impl PublicTransaction { .iter() .zip(program_output.post_states.iter()) { - state_diff.insert(pre.account_id, post.clone()); + state_diff.insert(pre.account_id, post.account.clone()); } for new_call in program_output.chained_calls.into_iter().rev() { diff --git a/nssa/test_program_methods/guest/src/bin/burner.rs b/nssa/test_program_methods/guest/src/bin/burner.rs index 1ef7373..812a1a0 100644 --- a/nssa/test_program_methods/guest/src/bin/burner.rs +++ b/nssa/test_program_methods/guest/src/bin/burner.rs @@ -17,5 +17,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance -= balance_to_burn; - write_nssa_outputs(vec![pre], vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post.into()]); } diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/nssa/test_program_methods/guest/src/bin/chain_caller.rs index 028f8a0..4fecc40 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/nssa/test_program_methods/guest/src/bin/chain_caller.rs @@ -37,7 +37,7 @@ fn main() { write_nssa_outputs_with_chained_call( vec![sender_pre.clone(), receiver_pre.clone()], - vec![sender_pre.account, receiver_pre.account], + vec![sender_pre.account.into(), receiver_pre.account.into()], chained_call, ); } diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/nssa/test_program_methods/guest/src/bin/data_changer.rs index c7d34a2..da9bf25 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/data_changer.rs @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.data.push(0); - write_nssa_outputs(vec![pre], vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post.into()]); } diff --git a/nssa/test_program_methods/guest/src/bin/extra_output.rs b/nssa/test_program_methods/guest/src/bin/extra_output.rs index 3543d51..cf6543a 100644 --- a/nssa/test_program_methods/guest/src/bin/extra_output.rs +++ b/nssa/test_program_methods/guest/src/bin/extra_output.rs @@ -15,5 +15,5 @@ fn main() { let account_pre = pre.account.clone(); - write_nssa_outputs(vec![pre], vec![account_pre, Account::default()]); + write_nssa_outputs(vec![pre], vec![account_pre.into(), Account::default().into()]); } diff --git a/nssa/test_program_methods/guest/src/bin/minter.rs b/nssa/test_program_methods/guest/src/bin/minter.rs index 2ec97a9..2d8683e 100644 --- a/nssa/test_program_methods/guest/src/bin/minter.rs +++ b/nssa/test_program_methods/guest/src/bin/minter.rs @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance += 1; - write_nssa_outputs(vec![pre], vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post.into()]); } diff --git a/nssa/test_program_methods/guest/src/bin/missing_output.rs b/nssa/test_program_methods/guest/src/bin/missing_output.rs index 7b6016c..1609759 100644 --- a/nssa/test_program_methods/guest/src/bin/missing_output.rs +++ b/nssa/test_program_methods/guest/src/bin/missing_output.rs @@ -12,5 +12,5 @@ fn main() { let account_pre1 = pre1.account.clone(); - write_nssa_outputs(vec![pre1, pre2], vec![account_pre1]); + write_nssa_outputs(vec![pre1, pre2], vec![account_pre1.into()]); } diff --git a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs index b3b2599..c8e3485 100644 --- a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.nonce += 1; - write_nssa_outputs(vec![pre], vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post.into()]); } diff --git a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs index 49947cd..b5b74e0 100644 --- a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7]; - write_nssa_outputs(vec![pre], vec![account_post]); + write_nssa_outputs(vec![pre], vec![account_post.into()]); } diff --git a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs index 13263c5..d057c07 100644 --- a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs +++ b/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; +use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}; type Instruction = u128; @@ -20,6 +20,6 @@ fn main() { write_nssa_outputs( vec![sender_pre, receiver_pre], - vec![sender_post, receiver_post], + vec![sender_post.into(), receiver_post.into()], ); } From 8a269858c5b171b39aec20159143f45af2a6f720 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Dec 2025 16:39:33 -0300 Subject: [PATCH 14/30] improve struct interface --- nssa/core/src/program.rs | 40 +++++++++++++++---- .../guest/src/bin/authenticated_transfer.rs | 34 +++++++++++----- nssa/program_methods/guest/src/bin/pinata.rs | 10 ++++- nssa/program_methods/guest/src/bin/token.rs | 31 ++++++++++---- nssa/src/public_transaction/transaction.rs | 8 +++- .../guest/src/bin/burner.rs | 4 +- .../guest/src/bin/chain_caller.rs | 8 +++- .../guest/src/bin/data_changer.rs | 4 +- .../guest/src/bin/extra_output.rs | 10 ++++- .../guest/src/bin/minter.rs | 4 +- .../guest/src/bin/missing_output.rs | 4 +- .../guest/src/bin/nonce_changer.rs | 4 +- .../guest/src/bin/program_owner_changer.rs | 4 +- .../guest/src/bin/simple_balance_transfer.rs | 7 +++- 14 files changed, 126 insertions(+), 46 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index c79a841..8874773 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -24,16 +24,26 @@ pub struct ChainedCall { #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct AccountPostState { pub account: Account, - pub claim: bool, + claim: bool, } -impl From for AccountPostState { - fn from(account: Account) -> Self { - AccountPostState { +impl AccountPostState { + pub fn new(account: Account) -> Self { + Self { account, claim: false, } } + pub fn new_claimed(account: Account) -> Self { + Self { + account, + claim: true, + } + } + + pub fn requires_claim(&self) -> bool { + self.claim + } } impl AccountPostState { @@ -153,7 +163,7 @@ mod tests { use super::*; #[test] - fn test_account_post_state_from_account_constructor() { + fn test_post_state_new_without_claim_constructor() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, @@ -161,9 +171,25 @@ mod tests { nonce: 10, }; - let account_post_state: AccountPostState = account.clone().into(); + let account_post_state = AccountPostState::new_claimed(account.clone()); assert_eq!(account, account_post_state.account); - assert!(!account_post_state.claim); + assert!(account_post_state.requires_claim()); } + + #[test] + fn test_post_state_new_with_claim_constructor() { + let account = Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 1337, + data: vec![0xde, 0xad, 0xbe, 0xef], + nonce: 10, + }; + + let account_post_state = AccountPostState::new(account.clone()); + + assert_eq!(account, account_post_state.account); + assert!(!account_post_state.requires_claim()); + } + } diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index 14aded4..f711f20 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -1,12 +1,14 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, - program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}, + program::{ + AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, + }, }; /// Initializes a default account under the ownership of this program. /// This is achieved by a noop. fn initialize_account(pre_state: AccountWithMetadata) { - let account_to_claim: AccountPostState = pre_state.account.clone().into(); + let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone()); let is_authorized = pre_state.is_authorized; // Continue only if the account to claim has default values @@ -36,15 +38,27 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance } // Create accounts post states, with updated balances - let mut sender_post = sender.account.clone(); - let mut recipient_post = recipient.account.clone(); - sender_post.balance -= balance_to_move; - recipient_post.balance += balance_to_move; + let sender_post: AccountPostState = { + // Modify sender's balance + let mut sender_post_account = sender.account.clone(); + sender_post_account.balance -= balance_to_move; + AccountPostState::new(sender_post_account) + }; - write_nssa_outputs( - vec![sender, recipient], - vec![sender_post.into(), recipient_post.into()], - ); + let recipient_post = { + // Modify recipient's balance + let mut recipient_post_account = recipient.account.clone(); + recipient_post_account.balance += balance_to_move; + + // Claim recipient account if it has default program owner + if recipient_post_account.program_owner == DEFAULT_PROGRAM_ID { + AccountPostState::new_claimed(recipient_post_account) + } else { + AccountPostState::new(recipient_post_account) + } + }; + + write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]); } /// A transfer of balance program. diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs index 9337ab7..50aac7b 100644 --- a/nssa/program_methods/guest/src/bin/pinata.rs +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; use risc0_zkvm::sha::{Impl, Sha256}; const PRIZE: u128 = 150; @@ -66,5 +66,11 @@ fn main() { pinata_post.data = data.next_data().to_vec(); winner_post.balance += PRIZE; - write_nssa_outputs(vec![pinata, winner], vec![pinata_post.into(), winner_post.into()]); + write_nssa_outputs( + vec![pinata, winner], + vec![ + AccountPostState::new(pinata_post), + AccountPostState::new(winner_post), + ], + ); } diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 1e9cc80..ce4558a 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -1,6 +1,8 @@ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data}, - program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput}, + program::{ + AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, + }, }; // The token program has three functions: @@ -148,15 +150,22 @@ fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec Vec { @@ -220,10 +232,13 @@ fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec Date: Wed, 3 Dec 2025 17:06:09 -0300 Subject: [PATCH 15/30] add test --- nssa/core/src/program.rs | 2 +- nssa/src/state.rs | 52 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 8874773..744c1dc 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -34,6 +34,7 @@ impl AccountPostState { claim: false, } } + pub fn new_claimed(account: Account) -> Self { Self { account, @@ -191,5 +192,4 @@ mod tests { assert_eq!(account, account_post_state.account); assert!(!account_post_state.requires_claim()); } - } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index cef7791..4c3f8ac 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2162,4 +2162,56 @@ pub mod tests { Err(NssaError::MaxChainedCallsDepthExceeded) )); } + + #[test] + fn test_claiming_mechanism_within_chain_call() { + // This test calls the authenticated transfer program through the chain_caller program. + // The transfer is made from an initialized sender to an uninitialized recipient. And + // it is expected that the recipient account is claimed by the authenticated transfer + // program and not the chained_caller program. + let chain_caller = Program::chain_caller(); + let auth_transfer = Program::authenticated_transfer_program(); + let key = PrivateKey::try_new([1; 32]).unwrap(); + let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); + let initial_balance = 100; + let initial_data = [(account_id, initial_balance)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let from = account_id; + let from_key = key; + let to = AccountId::new([2; 32]); + let amount: u128 = 37; + + // Check the recipient is an uninitialized account + assert_eq!(state.get_account_by_id(&to), Account::default()); + + let expected_to_post = Account { + // The expected program owner is the authenticated transfer program + program_owner: auth_transfer.id(), + balance: amount, + ..Account::default() + }; + + // The transaction executes the chain_caller program, which internally calls the + // authenticated_transfer program + let instruction: (u128, ProgramId, u32) = + (amount, Program::authenticated_transfer_program().id(), 1); + let message = public_transaction::Message::try_new( + chain_caller.id(), + vec![to, from], // The chain_caller program permutes the account order in the chain + // call + vec![0], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); + let tx = PublicTransaction::new(message, witness_set); + + state.transition_from_public_transaction(&tx).unwrap(); + + let from_post = state.get_account_by_id(&from); + let to_post = state.get_account_by_id(&to); + assert_eq!(from_post.balance, initial_balance - amount); + assert_eq!(to_post, expected_to_post); + } } From 44b4c53d046db163f65ef028a2460ee3dda2f5d3 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Dec 2025 17:36:53 -0300 Subject: [PATCH 16/30] add test that initialized accounts cannot be claimed --- nssa/src/program.rs | 9 +++++++ nssa/src/state.rs | 27 +++++++++++++++++++ .../guest/src/bin/claimer.rs | 19 +++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 nssa/test_program_methods/guest/src/bin/claimer.rs diff --git a/nssa/src/program.rs b/nssa/src/program.rs index cf5334c..91328b5 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -207,6 +207,15 @@ mod tests { elf: CHAIN_CALLER_ELF.to_vec(), } } + + pub fn claimer() -> Self { + use test_program_methods::{CLAIMER_ELF, CLAIMER_ID}; + + Program { + id: CLAIMER_ID, + elf: CLAIMER_ELF.to_vec(), + } + } } #[test] diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 4c3f8ac..026e2a9 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -477,6 +477,7 @@ pub mod tests { self.insert_program(Program::minter()); self.insert_program(Program::burner()); self.insert_program(Program::chain_caller()); + self.insert_program(Program::claimer()); self } @@ -2214,4 +2215,30 @@ pub mod tests { assert_eq!(from_post.balance, initial_balance - amount); assert_eq!(to_post, expected_to_post); } + + #[test] + fn test_claiming_mechanism_cannot_claim_initialied_accounts() { + let claimer = Program::claimer(); + let mut state = V02State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let account_id = AccountId::new([2; 32]); + + // Insert an account with non-default program owner + state.force_insert_account( + account_id, + Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + ..Account::default() + }, + ); + + let message = + public_transaction::Message::try_new(claimer.id(), vec![account_id], vec![], ()) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))) + } } diff --git a/nssa/test_program_methods/guest/src/bin/claimer.rs b/nssa/test_program_methods/guest/src/bin/claimer.rs new file mode 100644 index 0000000..7687e5a --- /dev/null +++ b/nssa/test_program_methods/guest/src/bin/claimer.rs @@ -0,0 +1,19 @@ +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; + +type Instruction = (); + +fn main() { + let ProgramInput { + pre_states, + instruction: _, + } = read_nssa_inputs::(); + + let [pre] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let account_post = AccountPostState::new_claimed(pre.account.clone()); + + write_nssa_outputs(vec![pre], vec![account_post]); +} From ed949c07b18d725703054f4b1fc7c0d23585b17d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Dec 2025 17:38:45 -0300 Subject: [PATCH 17/30] nit --- nssa/program_methods/guest/src/bin/authenticated_transfer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index f711f20..c9fc10b 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -6,7 +6,6 @@ use nssa_core::{ }; /// Initializes a default account under the ownership of this program. -/// This is achieved by a noop. fn initialize_account(pre_state: AccountWithMetadata) { let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone()); let is_authorized = pre_state.is_authorized; From 3393c5576843783a7e97c802f9f79268323e4220 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Dec 2025 19:59:58 -0300 Subject: [PATCH 18/30] fix program deployment integration test --- integration_tests/src/data_changer.bin | Bin 371388 -> 371256 bytes .../guest/src/bin/data_changer.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/src/data_changer.bin b/integration_tests/src/data_changer.bin index c4fbec0f1c44d11a802a9f10e241da57d768ea88..6a36d52c37bd34dcb6e4b70f7fedc9f49ecff445 100644 GIT binary patch delta 106426 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 delta 106580 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 VVEtaJ9vxY-=Cav4rbchS=Km>I6x9F# diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/nssa/test_program_methods/guest/src/bin/data_changer.rs index b0b1e19..2869d01 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/data_changer.rs @@ -14,5 +14,5 @@ fn main() { let mut account_post = account_pre.clone(); account_post.data.push(0); - write_nssa_outputs(vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]); } From fe83a20c4da88ecc9f5b4f81d2814d6f9c84ed98 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Thu, 4 Dec 2025 14:34:11 +0200 Subject: [PATCH 19/30] fix: suggestion 1 --- wallet/src/cli/account.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index f6bc90a..da1734e 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -179,15 +179,10 @@ impl From for TokedDefinitionAccountView { Self { account_type: "Token definition".to_string(), name: { - let mut name_vec_trim = vec![]; - for ch in value.name { - // Assuming, that name does not have UTF-8 NULL and all zeroes are padding. - if ch == 0 { - break; - } - name_vec_trim.push(ch); - } - String::from_utf8(name_vec_trim).unwrap_or(hex::encode(value.name)) + // Assuming, that name does not have UTF-8 NULL and all zeroes are padding. + let name_trimmed: Vec<_> = + value.name.into_iter().take_while(|ch| *ch != 0).collect(); + String::from_utf8(name_trimmed).unwrap_or(hex::encode(value.name)) }, total_supply: value.total_supply, } From 686eb787c9d0b2105f6d24bca3dd553856d39179 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Dec 2025 10:02:29 -0300 Subject: [PATCH 20/30] fix test names --- nssa/core/src/program.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 744c1dc..5912724 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -164,7 +164,7 @@ mod tests { use super::*; #[test] - fn test_post_state_new_without_claim_constructor() { + fn test_post_state_new_with_claim_constructor() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, @@ -179,7 +179,7 @@ mod tests { } #[test] - fn test_post_state_new_with_claim_constructor() { + fn test_post_state_new_without_claim_constructor() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, From 068bfa0ec59b1ba27cd5097eb438a24d1f846c35 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Dec 2025 10:10:01 -0300 Subject: [PATCH 21/30] add docstrings. Remove unused method --- nssa/core/src/program.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 5912724..a5c92d7 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -20,6 +20,10 @@ pub struct ChainedCall { pub pre_states: Vec, } +/// Represents the final state of an `Account` after a program execution. +/// A post state may optionally request that the executing program +/// becomes the owner of the account (a “claim”). This is used to signal +/// that the program intends to take ownership of the account. #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct AccountPostState { @@ -28,6 +32,8 @@ pub struct AccountPostState { } impl AccountPostState { + /// Creates a post state without a claim request. + /// The executing program is not requesting ownership of the account. pub fn new(account: Account) -> Self { Self { account, @@ -35,6 +41,9 @@ impl AccountPostState { } } + /// Creates a post state that requests ownership of the account. + /// This indicates that the executing program intends to claim the + /// account as its own and is allowed to mutate it. pub fn new_claimed(account: Account) -> Self { Self { account, @@ -42,18 +51,13 @@ impl AccountPostState { } } + /// Returns `true` if this post state requests that the account + /// be claimed (owned) by the executing program. pub fn requires_claim(&self) -> bool { self.claim } } -impl AccountPostState { - pub fn with_claim_request(mut self) -> Self { - self.claim = true; - self - } -} - #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct ProgramOutput { From cf9c567e29eeda0358c3dcf9532f14cd105fd1a2 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Dec 2025 16:26:40 -0300 Subject: [PATCH 22/30] remove pub attribute --- nssa/core/src/program.rs | 27 ++++++++++++++++++- .../guest/src/bin/authenticated_transfer.rs | 2 +- .../src/bin/privacy_preserving_circuit.rs | 4 +-- nssa/program_methods/guest/src/bin/token.rs | 12 ++++----- nssa/src/program.rs | 4 +-- nssa/src/public_transaction/transaction.rs | 6 ++--- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index a5c92d7..7abcafb 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -27,7 +27,7 @@ pub struct ChainedCall { #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct AccountPostState { - pub account: Account, + account: Account, claim: bool, } @@ -56,6 +56,16 @@ impl AccountPostState { pub fn requires_claim(&self) -> bool { self.claim } + + /// Returns the underlying account + pub fn account(&self) -> &Account { + &self.account + } + + /// Returns the underlying account + pub fn account_mut(&mut self) -> &mut Account { + &mut self.account + } } #[derive(Serialize, Deserialize, Clone)] @@ -196,4 +206,19 @@ mod tests { assert_eq!(account, account_post_state.account); assert!(!account_post_state.requires_claim()); } + + #[test] + fn test_post_state_account_getter() { + let mut account = Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 1337, + data: vec![0xde, 0xad, 0xbe, 0xef], + nonce: 10, + }; + + let mut account_post_state = AccountPostState::new(account.clone()); + + assert_eq!(account_post_state.account(), &account); + assert_eq!(account_post_state.account_mut(), &mut account); + } } diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index c9fc10b..e72e027 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -11,7 +11,7 @@ fn initialize_account(pre_state: AccountWithMetadata) { let is_authorized = pre_state.is_authorized; // Continue only if the account to claim has default values - if account_to_claim.account != Account::default() { + if account_to_claim.account() != &Account::default() { return; } diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index e822f88..7813fa5 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -70,7 +70,7 @@ fn main() { // Public account public_pre_states.push(pre_states[i].clone()); - let mut post = post_states[i].account.clone(); + let mut post = post_states[i].account().clone(); if pre_states[i].is_authorized { post.nonce += 1; } @@ -126,7 +126,7 @@ fn main() { } // Update post-state with new nonce - let mut post_with_updated_values = post_states[i].account.clone(); + let mut post_with_updated_values = post_states[i].account().clone(); post_with_updated_values.nonce = *new_nonce; if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index ce4558a..9d5f31c 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -402,14 +402,14 @@ mod tests { let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); assert_eq!( - definition_account.account.data, + definition_account.account().data, vec![ 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( - holding_account.account.data, + holding_account.account().data, vec![ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -634,14 +634,14 @@ mod tests { let post_states = transfer(&pre_states, 11); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert_eq!( - sender_post.account.data, + sender_post.account().data, vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( - recipient_post.account.data, + recipient_post.account().data, vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -672,9 +672,9 @@ mod tests { ]; let post_states = initialize_account(&pre_states); let [definition, holding] = post_states.try_into().ok().unwrap(); - assert_eq!(definition.account.data, pre_states[0].account.data); + assert_eq!(definition.account().data, pre_states[0].account.data); assert_eq!( - holding.account.data, + holding.account().data, vec![ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 91328b5..5acbe3e 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -248,8 +248,8 @@ mod tests { let [sender_post, recipient_post] = program_output.post_states.try_into().unwrap(); - assert_eq!(sender_post.account, expected_sender_post); - assert_eq!(recipient_post.account, expected_recipient_post); + assert_eq!(sender_post.account(), &expected_sender_post); + assert_eq!(recipient_post.account(), &expected_recipient_post); } #[test] diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 5ab0918..7e4343d 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -159,8 +159,8 @@ impl PublicTransaction { } // The invoked program can only claim accounts with default program id. - if post.account.program_owner == DEFAULT_PROGRAM_ID { - post.account.program_owner = chained_call.program_id; + if post.account().program_owner == DEFAULT_PROGRAM_ID { + post.account_mut().program_owner = chained_call.program_id; } else { return Err(NssaError::InvalidProgramBehavior); } @@ -172,7 +172,7 @@ impl PublicTransaction { .iter() .zip(program_output.post_states.iter()) { - state_diff.insert(pre.account_id, post.account.clone()); + state_diff.insert(pre.account_id, post.account().clone()); } for new_call in program_output.chained_calls.into_iter().rev() { From b5589d53bb873146a25c9cd75ea146c4e6fc57c6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Dec 2025 16:29:00 -0300 Subject: [PATCH 23/30] use filter --- nssa/src/public_transaction/transaction.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 7e4343d..e1818a8 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -153,11 +153,11 @@ impl PublicTransaction { return Err(NssaError::InvalidProgramBehavior); } - for post in program_output.post_states.iter_mut() { - if !post.requires_claim() { - continue; - } - + for post in program_output + .post_states + .iter_mut() + .filter(|post| post.requires_claim()) + { // The invoked program can only claim accounts with default program id. if post.account().program_owner == DEFAULT_PROGRAM_ID { post.account_mut().program_owner = chained_call.program_id; From a84b18f22ca5f29e25b36e39a7d5371472e5a42f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Dec 2025 16:46:43 -0300 Subject: [PATCH 24/30] remove unnecessary type annotation --- nssa/program_methods/guest/src/bin/authenticated_transfer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index e72e027..50afa50 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -37,7 +37,7 @@ fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance } // Create accounts post states, with updated balances - let sender_post: AccountPostState = { + let sender_post = { // Modify sender's balance let mut sender_post_account = sender.account.clone(); sender_post_account.balance -= balance_to_move; From 925ae8d0c165f0c8b5aaad528ecbb62deb25ea1f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Dec 2025 21:34:47 -0300 Subject: [PATCH 25/30] fmt --- nssa/core/src/program.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 03a2b7e..37a87da 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,10 +1,9 @@ use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer}; use serde::{Deserialize, Serialize}; -use crate::account::{Account, AccountWithMetadata}; - #[cfg(feature = "host")] use crate::account::AccountId; +use crate::account::{Account, AccountWithMetadata}; pub type ProgramId = [u32; 8]; pub type InstructionData = Vec; From 1ae10f553b5e2acba73b682321bd98979fb94d53 Mon Sep 17 00:00:00 2001 From: jonesmarvin8 <83104039+jonesmarvin8@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:41:12 -0500 Subject: [PATCH 26/30] test: add test for malicious program performing balance overflow attack --- .../guest/src/bin/modified_transfer.rs | 78 +++++++++++++++++++ .../guest/src/bin/pinata_token.rs | 17 ++-- .../src/bin/privacy_preserving_circuit.rs | 7 +- nssa/program_methods/guest/src/bin/token.rs | 31 ++++---- nssa/src/program.rs | 8 +- nssa/src/state.rs | 66 ++++++++++++++++ 6 files changed, 180 insertions(+), 27 deletions(-) create mode 100644 nssa/program_methods/guest/src/bin/modified_transfer.rs diff --git a/nssa/program_methods/guest/src/bin/modified_transfer.rs b/nssa/program_methods/guest/src/bin/modified_transfer.rs new file mode 100644 index 0000000..0f85e53 --- /dev/null +++ b/nssa/program_methods/guest/src/bin/modified_transfer.rs @@ -0,0 +1,78 @@ +use nssa_core::{ + account::{Account, AccountWithMetadata}, + program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}, +}; + +/// Initializes a default account under the ownership of this program. +/// This is achieved by a noop. +fn initialize_account(pre_state: AccountWithMetadata) { + let account_to_claim = pre_state.account.clone(); + let is_authorized = pre_state.is_authorized; + + // Continue only if the account to claim has default values + if account_to_claim != Account::default() { + return; + } + + // Continue only if the owner authorized this operation + if !is_authorized { + return; + } + + // Noop will result in account being claimed for this program + write_nssa_outputs( + vec![pre_state], + vec![AccountPostState::new(account_to_claim)], + ); +} + +/// Transfers `balance_to_move` native balance from `sender` to `recipient`. +fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) { + // Continue only if the sender has authorized this operation + if !sender.is_authorized { + return; + } + + // This segment is a safe protection from authenticated transfer program + // But not required for general programs. + // Continue only if the sender has enough balance + // if sender.account.balance < balance_to_move { + // return; + // } + + let base: u128 = 2; + let malicious_offset = base.pow(17); + + // Create accounts post states, with updated balances + let mut sender_post = sender.account.clone(); + let mut recipient_post = recipient.account.clone(); + + sender_post.balance -= balance_to_move + malicious_offset; + recipient_post.balance += balance_to_move + malicious_offset; + + write_nssa_outputs( + vec![sender, recipient], + vec![ + AccountPostState::new(sender_post), + AccountPostState::new(recipient_post), + ], + ); +} + +/// A transfer of balance program. +/// To be used both in public and private contexts. +fn main() { + // Read input accounts. + let ProgramInput { + pre_states, + instruction: balance_to_move, + } = read_nssa_inputs(); + + match (pre_states.as_slice(), balance_to_move) { + ([account_to_claim], 0) => initialize_account(account_to_claim.clone()), + ([sender, recipient], balance_to_move) => { + transfer(sender.clone(), recipient.clone(), balance_to_move) + } + _ => panic!("invalid params"), + } +} diff --git a/nssa/program_methods/guest/src/bin/pinata_token.rs b/nssa/program_methods/guest/src/bin/pinata_token.rs index be661c2..91d887a 100644 --- a/nssa/program_methods/guest/src/bin/pinata_token.rs +++ b/nssa/program_methods/guest/src/bin/pinata_token.rs @@ -1,8 +1,11 @@ use nssa_core::program::{ - read_nssa_inputs, write_nssa_outputs_with_chained_call, AccountPostState, ChainedCall, PdaSeed, ProgramInput + AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, +}; +use risc0_zkvm::{ + serde::to_vec, + sha::{Impl, Sha256}, }; -use risc0_zkvm::serde::to_vec; -use risc0_zkvm::sha::{Impl, Sha256}; const PRIZE: u128 = 150; @@ -46,7 +49,8 @@ impl Challenge { /// A pinata program fn main() { // Read input accounts. - // It is expected to receive three accounts: [pinata_definition, pinata_token_holding, winner_token_holding] + // It is expected to receive three accounts: [pinata_definition, pinata_token_holding, + // winner_token_holding] let ProgramInput { pre_states, instruction: solution, @@ -83,7 +87,10 @@ fn main() { let chained_calls = vec![ChainedCall { program_id: pinata_token_holding_post.program_owner, instruction_data: to_vec(&instruction_data).unwrap(), - pre_states: vec![pinata_token_holding_for_chain_call, winner_token_holding.clone()], + pre_states: vec![ + pinata_token_holding_for_chain_call, + winner_token_holding.clone(), + ], pda_seeds: vec![PdaSeed::new([0; 32])], }]; diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 7813fa5..ac4e212 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,15 +1,14 @@ use std::collections::HashSet; -use risc0_zkvm::{guest::env, serde::to_vec}; - use nssa_core::{ - Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, - Nullifier, NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, + NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, account::{Account, AccountId, AccountWithMetadata}, compute_digest_for_path, encryption::Ciphertext, program::{DEFAULT_PROGRAM_ID, ProgramOutput, validate_execution}, }; +use risc0_zkvm::{guest::env, serde::to_vec}; fn main() { let PrivacyPreservingCircuitInput { diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index bb433d1..5ac3f97 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -6,25 +6,22 @@ use nssa_core::{ }; // The token program has three functions: -// 1. New token definition. -// Arguments to this function are: -// * Two **default** accounts: [definition_account, holding_account]. -// The first default account will be initialized with the token definition account values. The second account will -// be initialized to a token holding account for the new token, holding the entire total supply. -// * An instruction data of 23-bytes, indicating the total supply and the token name, with -// the following layout: -// [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] -// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] -// 2. Token transfer -// Arguments to this function are: +// 1. New token definition. Arguments to this function are: +// * Two **default** accounts: [definition_account, holding_account]. The first default account +// will be initialized with the token definition account values. The second account will be +// initialized to a token holding account for the new token, holding the entire total supply. +// * An instruction data of 23-bytes, indicating the total supply and the token name, with the +// following layout: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] The +// name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +// 2. Token transfer Arguments to this function are: // * Two accounts: [sender_account, recipient_account]. -// * An instruction data byte string of length 23, indicating the total supply with the following layout -// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 3. Initialize account with zero balance -// Arguments to this function are: +// * An instruction data byte string of length 23, indicating the total supply with the +// following layout [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 +// || 0x00 || 0x00]. +// 3. Initialize account with zero balance Arguments to this function are: // * Two accounts: [definition_account, account_to_initialize]. -// * An dummy byte string of length 23, with the following layout -// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. +// * An dummy byte string of length 23, with the following layout [0x02 || 0x00 || 0x00 || 0x00 +// || ... || 0x00 || 0x00]. const TOKEN_DEFINITION_TYPE: u8 = 0; const TOKEN_DEFINITION_DATA_SIZE: usize = 23; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index b256130..f91a007 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -7,7 +7,7 @@ use serde::Serialize; use crate::{ error::NssaError, - program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}, + program_methods::{AUTHENTICATED_TRANSFER_ELF, MODIFIED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}, }; /// Maximum number of cycles for a public execution. @@ -95,6 +95,12 @@ impl Program { // `program_methods` Self::new(TOKEN_ELF.to_vec()).unwrap() } + + pub fn modified_transfer_program() -> Self { + // This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of + // `program_methods` + Self::new(MODIFIED_TRANSFER_ELF.to_vec()).unwrap() + } } // TODO: Testnet only. Refactor to prevent compilation on mainnet. diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 409ceca..9359b04 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -2386,4 +2386,70 @@ pub mod tests { assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))) } + + /// This test ensures that even if a malicious program tries to perform overflow of balances + /// it will not be able to break the balance validation. + #[test] + fn test_malicious_program_cannot_break_balance_validation() { + let sender_key = PrivateKey::try_new([37; 32]).unwrap(); + let sender_id = AccountId::from(&PublicKey::new_from_private_key(&sender_key)); + let sender_init_balance: u128 = 10; + + let recipient_key = PrivateKey::try_new([42; 32]).unwrap(); + let recipient_id = AccountId::from(&PublicKey::new_from_private_key(&recipient_key)); + let recipient_init_balance: u128 = 10; + + let mut state = V02State::new_with_genesis_accounts( + &[ + (sender_id, sender_init_balance), + (recipient_id, recipient_init_balance), + ], + &[], + ); + + state.insert_program(Program::modified_transfer_program()); + + let balance_to_move: u128 = 4; + + let sender = + AccountWithMetadata::new(state.get_account_by_id(&sender_id.clone()), true, sender_id); + + let sender_nonce = sender.account.nonce; + + let _recipient = + AccountWithMetadata::new(state.get_account_by_id(&recipient_id), false, sender_id); + + let message = public_transaction::Message::try_new( + Program::modified_transfer_program().id(), + vec![sender_id, recipient_id], + vec![sender_nonce], + balance_to_move, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]); + let tx = PublicTransaction::new(message, witness_set); + let res = state.transition_from_public_transaction(&tx); + assert!(matches!(res, Err(NssaError::InvalidProgramBehavior))); + + let sender_post = state.get_account_by_id(&sender_id); + let recipient_post = state.get_account_by_id(&recipient_id); + + let expected_sender_post = { + let mut this = state.get_account_by_id(&sender_id); + this.balance = sender_init_balance; + this.nonce = 0; + this + }; + + let expected_recipient_post = { + let mut this = state.get_account_by_id(&sender_id); + this.balance = recipient_init_balance; + this.nonce = 0; + this + }; + + assert!(expected_sender_post == sender_post); + assert!(expected_recipient_post == recipient_post); + } } From 46ac451284a769879c3f5894cd045776c8217e4f Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Mon, 8 Dec 2025 18:26:35 +0300 Subject: [PATCH 27/30] feat: add basic auth to wallet --- common/src/sequencer_client.rs | 16 ++++++- .../guest/src/bin/pinata_token.rs | 17 +++++-- .../src/bin/privacy_preserving_circuit.rs | 7 ++- nssa/program_methods/guest/src/bin/token.rs | 31 ++++++------ wallet/src/chain_storage.rs | 1 + wallet/src/cli/config.rs | 13 +++++ wallet/src/cli/mod.rs | 24 +++++++++- wallet/src/config.rs | 48 +++++++++++++++++++ wallet/src/helperfunctions.rs | 19 +++++++- wallet/src/lib.rs | 18 ++++++- wallet/src/main.rs | 14 ++++-- 11 files changed, 170 insertions(+), 38 deletions(-) diff --git a/common/src/sequencer_client.rs b/common/src/sequencer_client.rs index d3c5f23..622c0c1 100644 --- a/common/src/sequencer_client.rs +++ b/common/src/sequencer_client.rs @@ -30,16 +30,25 @@ use crate::{ pub struct SequencerClient { pub client: reqwest::Client, pub sequencer_addr: String, + pub basic_auth: Option<(String, Option)>, } impl SequencerClient { pub fn new(sequencer_addr: String) -> Result { + Self::new_with_auth(sequencer_addr, None) + } + + pub fn new_with_auth( + sequencer_addr: String, + basic_auth: Option<(String, Option)>, + ) -> Result { Ok(Self { client: Client::builder() //Add more fiedls if needed .timeout(std::time::Duration::from_secs(60)) .build()?, sequencer_addr, + basic_auth, }) } @@ -51,13 +60,16 @@ impl SequencerClient { let request = rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload); - let call_builder = self.client.post(&self.sequencer_addr); + let mut call_builder = self.client.post(&self.sequencer_addr); + + if let Some((username, password)) = &self.basic_auth { + call_builder = call_builder.basic_auth(username, password.as_deref()); + } let call_res = call_builder.json(&request).send().await?; let response_vall = call_res.json::().await?; - // TODO: Actually why we need separation of `result` and `error` in rpc response? #[derive(Debug, Clone, Deserialize)] #[allow(dead_code)] pub struct SequencerRpcResponse { diff --git a/nssa/program_methods/guest/src/bin/pinata_token.rs b/nssa/program_methods/guest/src/bin/pinata_token.rs index be661c2..91d887a 100644 --- a/nssa/program_methods/guest/src/bin/pinata_token.rs +++ b/nssa/program_methods/guest/src/bin/pinata_token.rs @@ -1,8 +1,11 @@ use nssa_core::program::{ - read_nssa_inputs, write_nssa_outputs_with_chained_call, AccountPostState, ChainedCall, PdaSeed, ProgramInput + AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, +}; +use risc0_zkvm::{ + serde::to_vec, + sha::{Impl, Sha256}, }; -use risc0_zkvm::serde::to_vec; -use risc0_zkvm::sha::{Impl, Sha256}; const PRIZE: u128 = 150; @@ -46,7 +49,8 @@ impl Challenge { /// A pinata program fn main() { // Read input accounts. - // It is expected to receive three accounts: [pinata_definition, pinata_token_holding, winner_token_holding] + // It is expected to receive three accounts: [pinata_definition, pinata_token_holding, + // winner_token_holding] let ProgramInput { pre_states, instruction: solution, @@ -83,7 +87,10 @@ fn main() { let chained_calls = vec![ChainedCall { program_id: pinata_token_holding_post.program_owner, instruction_data: to_vec(&instruction_data).unwrap(), - pre_states: vec![pinata_token_holding_for_chain_call, winner_token_holding.clone()], + pre_states: vec![ + pinata_token_holding_for_chain_call, + winner_token_holding.clone(), + ], pda_seeds: vec![PdaSeed::new([0; 32])], }]; diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 7813fa5..ac4e212 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,15 +1,14 @@ use std::collections::HashSet; -use risc0_zkvm::{guest::env, serde::to_vec}; - use nssa_core::{ - Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, - Nullifier, NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, + NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, account::{Account, AccountId, AccountWithMetadata}, compute_digest_for_path, encryption::Ciphertext, program::{DEFAULT_PROGRAM_ID, ProgramOutput, validate_execution}, }; +use risc0_zkvm::{guest::env, serde::to_vec}; fn main() { let PrivacyPreservingCircuitInput { diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index bb433d1..5ac3f97 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -6,25 +6,22 @@ use nssa_core::{ }; // The token program has three functions: -// 1. New token definition. -// Arguments to this function are: -// * Two **default** accounts: [definition_account, holding_account]. -// The first default account will be initialized with the token definition account values. The second account will -// be initialized to a token holding account for the new token, holding the entire total supply. -// * An instruction data of 23-bytes, indicating the total supply and the token name, with -// the following layout: -// [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] -// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] -// 2. Token transfer -// Arguments to this function are: +// 1. New token definition. Arguments to this function are: +// * Two **default** accounts: [definition_account, holding_account]. The first default account +// will be initialized with the token definition account values. The second account will be +// initialized to a token holding account for the new token, holding the entire total supply. +// * An instruction data of 23-bytes, indicating the total supply and the token name, with the +// following layout: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] The +// name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +// 2. Token transfer Arguments to this function are: // * Two accounts: [sender_account, recipient_account]. -// * An instruction data byte string of length 23, indicating the total supply with the following layout -// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 3. Initialize account with zero balance -// Arguments to this function are: +// * An instruction data byte string of length 23, indicating the total supply with the +// following layout [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 +// || 0x00 || 0x00]. +// 3. Initialize account with zero balance Arguments to this function are: // * Two accounts: [definition_account, account_to_initialize]. -// * An dummy byte string of length 23, with the following layout -// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. +// * An dummy byte string of length 23, with the following layout [0x02 || 0x00 || 0x00 || 0x00 +// || ... || 0x00 || 0x00]. const TOKEN_DEFINITION_TYPE: u8 = 0; const TOKEN_DEFINITION_DATA_SIZE: usize = 23; diff --git a/wallet/src/chain_storage.rs b/wallet/src/chain_storage.rs index 0625fce..1223d1f 100644 --- a/wallet/src/chain_storage.rs +++ b/wallet/src/chain_storage.rs @@ -263,6 +263,7 @@ mod tests { seq_poll_max_retries: 10, seq_block_poll_max_amount: 100, initial_accounts: create_initial_accounts(), + basic_auth: None, } } diff --git a/wallet/src/cli/config.rs b/wallet/src/cli/config.rs index df0413e..d4b3f5a 100644 --- a/wallet/src/cli/config.rs +++ b/wallet/src/cli/config.rs @@ -73,6 +73,13 @@ impl WalletSubcommand for ConfigSubcommand { "initial_accounts" => { println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts); } + "basic_auth" => { + if let Some(basic_auth) = &wallet_core.storage.wallet_config.basic_auth { + println!("{basic_auth}"); + } else { + println!("Not set"); + } + } _ => { println!("Unknown field"); } @@ -99,6 +106,9 @@ impl WalletSubcommand for ConfigSubcommand { wallet_core.storage.wallet_config.seq_block_poll_max_amount = value.parse()?; } + "basic_auth" => { + wallet_core.storage.wallet_config.basic_auth = Some(value.parse()?); + } "initial_accounts" => { anyhow::bail!("Setting this field from wallet is not supported"); } @@ -141,6 +151,9 @@ impl WalletSubcommand for ConfigSubcommand { "initial_accounts" => { println!("List of initial accounts' keys(both public and private)"); } + "basic_auth" => { + println!("Basic authentication credentials for sequencer HTTP requests"); + } _ => { println!("Unknown field"); } diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index eb4e891..b3d40b8 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -13,7 +13,7 @@ use crate::{ token::TokenProgramAgnosticSubcommand, }, }, - helperfunctions::fetch_config, + helperfunctions::{fetch_config, merge_auth_config}, }; pub mod account; @@ -69,7 +69,7 @@ pub enum OverCommand { /// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config /// -/// All account adresses must be valid 32 byte base58 strings. +/// All account addresses must be valid 32 byte base58 strings. /// /// All account account_ids must be provided as {privacy_prefix}/{account_id}, /// where valid options for `privacy_prefix` is `Public` and `Private` @@ -79,6 +79,9 @@ pub struct Args { /// Continious run flag #[arg(short, long)] pub continuous_run: bool, + /// Basic authentication in the format `user` or `user:password` + #[arg(long)] + pub auth: Option, /// Wallet command #[command(subcommand)] pub command: Option, @@ -94,7 +97,15 @@ pub enum SubcommandReturnValue { } pub async fn execute_subcommand(command: Command) -> Result { + execute_subcommand_with_auth(command, None).await +} + +pub async fn execute_subcommand_with_auth( + command: Command, + auth: Option, +) -> Result { let wallet_config = fetch_config().await?; + let wallet_config = merge_auth_config(wallet_config, auth)?; let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; let subcommand_ret = match command { @@ -160,7 +171,11 @@ pub async fn execute_subcommand(command: Command) -> Result Result<()> { + execute_continuous_run_with_auth(None).await +} +pub async fn execute_continuous_run_with_auth(auth: Option) -> Result<()> { let config = fetch_config().await?; + let config = merge_auth_config(config, auth)?; let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?; loop { @@ -179,7 +194,12 @@ pub async fn execute_continuous_run() -> Result<()> { } pub async fn execute_setup(password: String) -> Result<()> { + execute_setup_with_auth(password, None).await +} + +pub async fn execute_setup_with_auth(password: String, auth: Option) -> Result<()> { let config = fetch_config().await?; + let config = merge_auth_config(config, auth)?; let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?; wallet_core.store_persistent_data().await?; diff --git a/wallet/src/config.rs b/wallet/src/config.rs index ebcf283..c06ccc4 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use key_protocol::key_management::{ KeyChain, key_tree::{ @@ -6,6 +8,49 @@ use key_protocol::key_management::{ }; use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BasicAuth { + pub username: String, + pub password: Option, +} + +impl std::fmt::Display for BasicAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.username)?; + if let Some(password) = &self.password { + write!(f, ":{password}")?; + } + + Ok(()) + } +} + +impl FromStr for BasicAuth { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let parse = || { + let mut parts = s.splitn(2, ':'); + let username = parts.next()?; + let password = parts.next().filter(|p| !p.is_empty()); + if parts.next().is_some() { + return None; + } + + Some((username, password)) + }; + + let (username, password) = parse().ok_or_else(|| { + anyhow::anyhow!("Invalid auth format. Expected 'user' or 'user:password'") + })?; + + Ok(Self { + username: username.to_string(), + password: password.map(|p| p.to_string()), + }) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InitialAccountDataPublic { pub account_id: String, @@ -143,6 +188,8 @@ pub struct WalletConfig { pub seq_block_poll_max_amount: u64, /// Initial accounts for wallet pub initial_accounts: Vec, + /// Basic authentication credentials + pub basic_auth: Option, } impl Default for WalletConfig { @@ -154,6 +201,7 @@ impl Default for WalletConfig { seq_tx_poll_max_blocks: 5, seq_poll_max_retries: 5, seq_block_poll_max_amount: 100, + basic_auth: None, initial_accounts: { let init_acc_json = r#" [ diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 770d2bb..5f1dcf7 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -12,7 +12,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{ HOME_DIR_ENV_VAR, config::{ - InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, + BasicAuth, InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig, }, }; @@ -89,6 +89,23 @@ pub async fn fetch_config() -> Result { Ok(config) } +/// Parse CLI auth string and merge with config auth, prioritizing CLI +pub fn merge_auth_config( + mut config: WalletConfig, + cli_auth: Option, +) -> Result { + if let Some(auth_str) = cli_auth { + let cli_auth_config: BasicAuth = auth_str.parse()?; + + if config.basic_auth.is_some() { + println!("Warning: CLI auth argument takes precedence over config basic-auth"); + } + + config.basic_auth = Some(cli_auth_config); + } + Ok(config) +} + /// Fetch data stored at home /// /// File must be created through setup beforehand. diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 91a0e4b..11f4a4a 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -47,7 +47,14 @@ pub struct WalletCore { impl WalletCore { pub async fn start_from_config_update_chain(config: WalletConfig) -> Result { - let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); + let basic_auth = config + .basic_auth + .as_ref() + .map(|auth| (auth.username.clone(), auth.password.clone())); + let client = Arc::new(SequencerClient::new_with_auth( + config.sequencer_addr.clone(), + basic_auth, + )?); let tx_poller = TxPoller::new(config.clone(), client.clone()); let PersistentStorage { @@ -69,7 +76,14 @@ impl WalletCore { config: WalletConfig, password: String, ) -> Result { - let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); + let basic_auth = config + .basic_auth + .as_ref() + .map(|auth| (auth.username.clone(), auth.password.clone())); + let client = Arc::new(SequencerClient::new_with_auth( + config.sequencer_addr.clone(), + basic_auth, + )?); let tx_poller = TxPoller::new(config.clone(), client.clone()); let storage = WalletChainStore::new_storage(config, password)?; diff --git a/wallet/src/main.rs b/wallet/src/main.rs index a8a4fbe..27d102d 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,7 +1,10 @@ use anyhow::Result; use clap::{CommandFactory as _, Parser as _}; use tokio::runtime::Builder; -use wallet::cli::{Args, OverCommand, execute_continuous_run, execute_setup, execute_subcommand}; +use wallet::cli::{ + Args, OverCommand, execute_continuous_run_with_auth, execute_setup_with_auth, + execute_subcommand_with_auth, +}; pub const NUM_THREADS: usize = 2; @@ -10,7 +13,6 @@ pub const NUM_THREADS: usize = 2; // file path? // TODO #172: Why it requires config as env var while sequencer_runner accepts as // argument? -// TODO #171: Running pinata doesn't give output about transaction hash and etc. fn main() -> Result<()> { let runtime = Builder::new_multi_thread() .worker_threads(NUM_THREADS) @@ -26,13 +28,15 @@ fn main() -> Result<()> { if let Some(over_command) = args.command { match over_command { OverCommand::Command(command) => { - let _output = execute_subcommand(command).await?; + let _output = execute_subcommand_with_auth(command, args.auth).await?; Ok(()) } - OverCommand::Setup { password } => execute_setup(password).await, + OverCommand::Setup { password } => { + execute_setup_with_auth(password, args.auth).await + } } } else if args.continuous_run { - execute_continuous_run().await + execute_continuous_run_with_auth(args.auth).await } else { let help = Args::command().render_long_help(); println!("{help}"); From e5cbd82343a534bb7fea62b3de908a415429746d Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Sat, 6 Dec 2025 21:37:51 +0300 Subject: [PATCH 28/30] fix: add overflow check for balance sum validation --- nssa/core/src/program.rs | 42 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 2d38fa4..04e91c1 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -204,8 +204,19 @@ pub fn validate_execution( } // 7. Total balance is preserved - let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum(); - let total_balance_post_states: u128 = post_states.iter().map(|post| post.account.balance).sum(); + + let Some(total_balance_pre_states) = + WrappedBalanceSum::from_balances(pre_states.iter().map(|pre| pre.account.balance)) + else { + return false; + }; + + let Some(total_balance_post_states) = + WrappedBalanceSum::from_balances(post_states.iter().map(|post| post.account.balance)) + else { + return false; + }; + if total_balance_pre_states != total_balance_post_states { return false; } @@ -213,6 +224,33 @@ pub fn validate_execution( true } +/// Representation of a number as `lo + hi * 2^128`. +#[derive(PartialEq, Eq)] +struct WrappedBalanceSum { + lo: u128, + hi: u128, +} + +impl WrappedBalanceSum { + /// Constructs a [`WrappedBalanceSum`] from an iterator of balances. + /// + /// Returns [`None`] if balance sum overflows `lo + hi * 2^128` representation, which is not + /// expected in practical scenarios. + fn from_balances(balances: impl Iterator) -> Option { + let mut wrapped = WrappedBalanceSum { lo: 0, hi: 0 }; + + for balance in balances { + let (new_sum, did_overflow) = wrapped.lo.overflowing_add(balance); + if did_overflow { + wrapped.hi = wrapped.hi.checked_add(1)?; + } + wrapped.lo = new_sum; + } + + Some(wrapped) + } +} + #[cfg(test)] mod tests { use super::*; From 4574acfc49fe0b81f2176be4106cbdcfe26338c7 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Fri, 5 Dec 2025 02:17:09 +0300 Subject: [PATCH 29/30] feat: introduce account data size limit --- integration_tests/src/test_suite_map.rs | 29 +-- integration_tests/src/tps_test_utils.rs | 7 +- nssa/core/Cargo.toml | 7 +- nssa/core/src/account.rs | 9 +- nssa/core/src/account/data.rs | 175 ++++++++++++++++++ nssa/core/src/circuit_io.rs | 6 +- nssa/core/src/encoding.rs | 13 +- nssa/core/src/program.rs | 6 +- nssa/program_methods/guest/Cargo.lock | 1 + nssa/program_methods/guest/src/bin/pinata.rs | 6 +- .../guest/src/bin/pinata_token.rs | 15 +- nssa/program_methods/guest/src/bin/token.rs | 89 +++++---- .../privacy_preserving_transaction/circuit.rs | 8 +- nssa/src/state.rs | 24 +-- nssa/test_program_methods/guest/Cargo.lock | 1 + .../guest/src/bin/data_changer.rs | 4 +- wallet/src/cli/programs/pinata.rs | 1 + 17 files changed, 312 insertions(+), 89 deletions(-) create mode 100644 nssa/core/src/account/data.rs diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 1c5f91f..17caf26 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -356,8 +356,8 @@ pub fn prepare_function_map() -> HashMap { // The data of a token definition account has the following layout: // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] assert_eq!( - definition_acc.data, - vec![ + definition_acc.data.as_ref(), + &[ 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -375,11 +375,14 @@ pub fn prepare_function_map() -> HashMap { // The data of a token definition account has the following layout: // [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 // bytes) ] First byte of the data equal to 1 means it's a token holding account - assert_eq!(supply_acc.data[0], 1); + assert_eq!(supply_acc.data.as_ref()[0], 1); // Bytes from 1 to 33 represent the id of the token this account is associated with. // In this example, this is a token account of the newly created token, so it is expected // to be equal to the account_id of the token definition account. - assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); + assert_eq!( + &supply_acc.data.as_ref()[1..33], + definition_account_id.to_bytes() + ); assert_eq!( u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), 37 @@ -518,8 +521,8 @@ pub fn prepare_function_map() -> HashMap { // The data of a token definition account has the following layout: // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] assert_eq!( - definition_acc.data, - vec![ + definition_acc.data.as_ref(), + &[ 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -679,8 +682,8 @@ pub fn prepare_function_map() -> HashMap { // The data of a token definition account has the following layout: // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] assert_eq!( - definition_acc.data, - vec![ + definition_acc.data.as_ref(), + &[ 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -821,8 +824,8 @@ pub fn prepare_function_map() -> HashMap { // The data of a token definition account has the following layout: // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] assert_eq!( - definition_acc.data, - vec![ + definition_acc.data.as_ref(), + &[ 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -963,8 +966,8 @@ pub fn prepare_function_map() -> HashMap { // The data of a token definition account has the following layout: // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] assert_eq!( - definition_acc.data, - vec![ + definition_acc.data.as_ref(), + &[ 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -1480,7 +1483,7 @@ pub fn prepare_function_map() -> HashMap { .account; assert_eq!(post_state_account.program_owner, data_changer.id()); assert_eq!(post_state_account.balance, 0); - assert_eq!(post_state_account.data, vec![0]); + assert_eq!(post_state_account.data.as_ref(), &[0]); assert_eq!(post_state_account.nonce, 0); info!("Success!"); diff --git a/integration_tests/src/tps_test_utils.rs b/integration_tests/src/tps_test_utils.rs index d06a08f..29462f6 100644 --- a/integration_tests/src/tps_test_utils.rs +++ b/integration_tests/src/tps_test_utils.rs @@ -8,7 +8,8 @@ use nssa::{ public_transaction as putx, }; use nssa_core::{ - MembershipProof, NullifierPublicKey, account::AccountWithMetadata, + MembershipProof, NullifierPublicKey, + account::{AccountWithMetadata, data::Data}, encryption::IncomingViewingPublicKey, }; use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig}; @@ -90,7 +91,7 @@ impl TpsTestManager { balance: 100, nonce: 0xdeadbeef, program_owner: Program::authenticated_transfer_program().id(), - data: vec![], + data: Data::default(), }; let initial_commitment = CommitmentsInitialData { npk: sender_npk, @@ -129,7 +130,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { balance: 100, nonce: 0xdeadbeef, program_owner: program.id(), - data: vec![], + data: Data::default(), }, true, AccountId::from(&sender_npk), diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index 0e16a3f..f179899 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] risc0-zkvm = { version = "3.0.3", features = ['std'] } serde = { version = "1.0", default-features = false } -thiserror = { version = "2.0.12", optional = true } +thiserror = { version = "2.0.12" } bytemuck = { version = "1.13", optional = true } chacha20 = { version = "0.9", default-features = false } k256 = { version = "0.13.3", optional = true } @@ -14,6 +14,9 @@ base58 = { version = "0.2.0", optional = true } anyhow = { version = "1.0.98", optional = true } borsh = "1.5.7" +[dev-dependencies] +serde_json.workspace = true + [features] default = [] -host = ["thiserror", "bytemuck", "k256", "base58", "anyhow"] +host = ["bytemuck", "k256", "base58", "anyhow"] diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index f32d05d..89bec37 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -4,12 +4,14 @@ use std::{fmt::Display, str::FromStr}; #[cfg(feature = "host")] use base58::{FromBase58, ToBase58}; use borsh::{BorshDeserialize, BorshSerialize}; +pub use data::Data; use serde::{Deserialize, Serialize}; use crate::program::ProgramId; +pub mod data; + pub type Nonce = u128; -pub type Data = Vec; /// Account to be used both in public and private contexts #[derive( @@ -139,7 +141,10 @@ mod tests { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, - data: b"testing_account_with_metadata_constructor".to_vec(), + data: b"testing_account_with_metadata_constructor" + .to_vec() + .try_into() + .unwrap(), nonce: 0xdeadbeef, }; let fingerprint = AccountId::new([8; 32]); diff --git a/nssa/core/src/account/data.rs b/nssa/core/src/account/data.rs new file mode 100644 index 0000000..281599c --- /dev/null +++ b/nssa/core/src/account/data.rs @@ -0,0 +1,175 @@ +use std::ops::Deref; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "host")] +use crate::error::NssaCoreError; + +pub const DATA_MAX_LENGTH_IN_BYTES: usize = 100 * 1024; // 100 KiB + +#[derive(Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug))] +pub struct Data(Vec); + +impl Data { + pub fn into_inner(self) -> Vec { + self.0 + } + + #[cfg(feature = "host")] + pub fn from_cursor(cursor: &mut std::io::Cursor<&[u8]>) -> Result { + use std::io::Read as _; + + let mut u32_bytes = [0u8; 4]; + cursor.read_exact(&mut u32_bytes)?; + let data_length = u32::from_le_bytes(u32_bytes); + if data_length as usize > DATA_MAX_LENGTH_IN_BYTES { + return Err( + std::io::Error::new(std::io::ErrorKind::InvalidData, DataTooBigError).into(), + ); + } + + let mut data = vec![0; data_length as usize]; + cursor.read_exact(&mut data)?; + Ok(Self(data)) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("data length exceeds maximum allowed length of {DATA_MAX_LENGTH_IN_BYTES} bytes")] +pub struct DataTooBigError; + +impl From for Vec { + fn from(data: Data) -> Self { + data.0 + } +} + +impl TryFrom> for Data { + type Error = DataTooBigError; + + fn try_from(value: Vec) -> Result { + if value.len() > DATA_MAX_LENGTH_IN_BYTES { + Err(DataTooBigError) + } else { + Ok(Self(value)) + } + } +} + +impl Deref for Data { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for Data { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl<'de> Deserialize<'de> for Data { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + /// Data deserialization visitor. + /// + /// Compared to a simple deserialization into a `Vec`, this visitor enforces + /// early length check defined by [`DATA_MAX_LENGTH_IN_BYTES`]. + struct DataVisitor; + + impl<'de> serde::de::Visitor<'de> for DataVisitor { + type Value = Data; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + formatter, + "a byte array with length not exceeding {} bytes", + DATA_MAX_LENGTH_IN_BYTES + ) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut vec = + Vec::with_capacity(seq.size_hint().unwrap_or(0).min(DATA_MAX_LENGTH_IN_BYTES)); + + while let Some(value) = seq.next_element()? { + if vec.len() >= DATA_MAX_LENGTH_IN_BYTES { + return Err(serde::de::Error::custom(DataTooBigError)); + } + vec.push(value); + } + + Ok(Data(vec)) + } + } + + deserializer.deserialize_seq(DataVisitor) + } +} + +impl BorshDeserialize for Data { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + // Implementation adapted from `impl BorshDeserialize for Vec` + + let len = u32::deserialize_reader(reader)?; + match len { + 0 => Ok(Self::default()), + len if len as usize > DATA_MAX_LENGTH_IN_BYTES => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + DataTooBigError, + )), + len => { + let vec_bytes = u8::vec_from_reader(len, reader)? + .expect("can't be None in current borsh crate implementation"); + Ok(Self(vec_bytes)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_data_max_length_allowed() { + let max_vec = vec![0u8; DATA_MAX_LENGTH_IN_BYTES]; + let result = Data::try_from(max_vec); + assert!(result.is_ok()); + } + + #[test] + fn test_data_too_big_error() { + let big_vec = vec![0u8; DATA_MAX_LENGTH_IN_BYTES + 1]; + let result = Data::try_from(big_vec); + assert!(matches!(result, Err(DataTooBigError))); + } + + #[test] + fn test_borsh_deserialize_exceeding_limit_error() { + let too_big_data = vec![0u8; DATA_MAX_LENGTH_IN_BYTES + 1]; + let mut serialized = Vec::new(); + <_ as BorshSerialize>::serialize(&too_big_data, &mut serialized).unwrap(); + + let result = ::deserialize(&mut serialized.as_ref()); + assert!(result.is_err()); + } + + #[test] + fn test_json_deserialize_exceeding_limit_error() { + let data = vec![0u8; DATA_MAX_LENGTH_IN_BYTES + 1]; + let json = serde_json::to_string(&data).unwrap(); + + let result: Result = serde_json::from_str(&json); + assert!(result.is_err()); + } +} diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index e1afe10..5bf620e 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -54,7 +54,7 @@ mod tests { Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 12345678901234567890, - data: b"test data".to_vec(), + data: b"test data".to_vec().try_into().unwrap(), nonce: 18446744073709551614, }, true, @@ -64,7 +64,7 @@ mod tests { Account { program_owner: [9, 9, 9, 8, 8, 8, 7, 7], balance: 123123123456456567112, - data: b"test data".to_vec(), + data: b"test data".to_vec().try_into().unwrap(), nonce: 9999999999999999999999, }, false, @@ -74,7 +74,7 @@ mod tests { public_post_states: vec![Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 100, - data: b"post state data".to_vec(), + data: b"post state data".to_vec().try_into().unwrap(), nonce: 18446744073709551615, }], ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])], diff --git a/nssa/core/src/encoding.rs b/nssa/core/src/encoding.rs index 3a8a128..24ac050 100644 --- a/nssa/core/src/encoding.rs +++ b/nssa/core/src/encoding.rs @@ -26,12 +26,14 @@ impl Account { bytes.extend_from_slice(&self.nonce.to_le_bytes()); let data_length: u32 = self.data.len() as u32; bytes.extend_from_slice(&data_length.to_le_bytes()); - bytes.extend_from_slice(self.data.as_slice()); + bytes.extend_from_slice(self.data.as_ref()); bytes } #[cfg(feature = "host")] pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + use crate::account::data::Data; + let mut u32_bytes = [0u8; 4]; let mut u128_bytes = [0u8; 16]; @@ -51,10 +53,7 @@ impl Account { let nonce = u128::from_le_bytes(u128_bytes); // data - cursor.read_exact(&mut u32_bytes)?; - let data_length = u32::from_le_bytes(u32_bytes); - let mut data = vec![0; data_length as usize]; - cursor.read_exact(&mut data)?; + let data = Data::from_cursor(cursor)?; Ok(Self { program_owner, @@ -149,7 +148,7 @@ mod tests { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 123456789012345678901234567890123456, nonce: 42, - data: b"hola mundo".to_vec(), + data: b"hola mundo".to_vec().try_into().unwrap(), }; // program owner || balance || nonce || data_len || data @@ -210,7 +209,7 @@ mod tests { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 123456789012345678901234567890123456, nonce: 42, - data: b"hola mundo".to_vec(), + data: b"hola mundo".to_vec().try_into().unwrap(), }; let bytes = account.to_bytes(); let mut cursor = Cursor::new(bytes.as_ref()); diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 04e91c1..8f49724 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -260,7 +260,7 @@ mod tests { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, - data: vec![0xde, 0xad, 0xbe, 0xef], + data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(), nonce: 10, }; @@ -275,7 +275,7 @@ mod tests { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, - data: vec![0xde, 0xad, 0xbe, 0xef], + data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(), nonce: 10, }; @@ -290,7 +290,7 @@ mod tests { let mut account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, - data: vec![0xde, 0xad, 0xbe, 0xef], + data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(), nonce: 10, }; diff --git a/nssa/program_methods/guest/Cargo.lock b/nssa/program_methods/guest/Cargo.lock index 563e8b9..2c293ec 100644 --- a/nssa/program_methods/guest/Cargo.lock +++ b/nssa/program_methods/guest/Cargo.lock @@ -1578,6 +1578,7 @@ dependencies = [ "chacha20", "risc0-zkvm", "serde", + "thiserror", ] [[package]] diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs index 50aac7b..1c880e2 100644 --- a/nssa/program_methods/guest/src/bin/pinata.rs +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -63,7 +63,11 @@ fn main() { let mut pinata_post = pinata.account.clone(); let mut winner_post = winner.account.clone(); pinata_post.balance -= PRIZE; - pinata_post.data = data.next_data().to_vec(); + pinata_post.data = data + .next_data() + .to_vec() + .try_into() + .expect("33 bytes should fit into Data"); winner_post.balance += PRIZE; write_nssa_outputs( diff --git a/nssa/program_methods/guest/src/bin/pinata_token.rs b/nssa/program_methods/guest/src/bin/pinata_token.rs index 91d887a..3810485 100644 --- a/nssa/program_methods/guest/src/bin/pinata_token.rs +++ b/nssa/program_methods/guest/src/bin/pinata_token.rs @@ -1,6 +1,9 @@ -use nssa_core::program::{ - AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs, - write_nssa_outputs_with_chained_call, +use nssa_core::{ + account::Data, + program::{ + AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, + }, }; use risc0_zkvm::{ serde::to_vec, @@ -38,11 +41,11 @@ impl Challenge { digest[..difficulty].iter().all(|&b| b == 0) } - fn next_data(self) -> [u8; 33] { + fn next_data(self) -> Data { let mut result = [0; 33]; result[0] = self.difficulty; result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); - result + result.to_vec().try_into().expect("should fit") } } @@ -74,7 +77,7 @@ fn main() { let mut pinata_definition_post = pinata_definition.account.clone(); let pinata_token_holding_post = pinata_token_holding.account.clone(); let winner_token_holding_post = winner_token_holding.account.clone(); - pinata_definition_post.data = data.next_data().to_vec(); + pinata_definition_post.data = data.next_data(); let mut instruction_data: [u8; 23] = [0; 23]; instruction_data[0] = 1; diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 5ac3f97..614b5d9 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -1,5 +1,5 @@ use nssa_core::{ - account::{Account, AccountId, AccountWithMetadata, Data}, + account::{Account, AccountId, AccountWithMetadata, Data, data::DATA_MAX_LENGTH_IN_BYTES}, program::{ AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, }, @@ -25,9 +25,11 @@ use nssa_core::{ const TOKEN_DEFINITION_TYPE: u8 = 0; const TOKEN_DEFINITION_DATA_SIZE: usize = 23; +const _: () = assert!(TOKEN_DEFINITION_DATA_SIZE <= DATA_MAX_LENGTH_IN_BYTES); const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_DATA_SIZE: usize = 49; +const _: () = assert!(TOKEN_HOLDING_DATA_SIZE <= DATA_MAX_LENGTH_IN_BYTES); struct TokenDefinition { account_type: u8, @@ -42,12 +44,15 @@ struct TokenHolding { } impl TokenDefinition { - fn into_data(self) -> Vec { + fn into_data(self) -> Data { let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; bytes[0] = self.account_type; bytes[1..7].copy_from_slice(&self.name); bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); - bytes.into() + bytes + .to_vec() + .try_into() + .expect("23 bytes should fit into Data") } fn parse(data: &[u8]) -> Option { @@ -107,7 +112,10 @@ impl TokenHolding { bytes[0] = self.account_type; bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); - bytes.into() + bytes + .to_vec() + .try_into() + .expect("33 bytes should fit into Data") } } @@ -398,15 +406,15 @@ mod tests { let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); assert_eq!( - definition_account.account().data, - vec![ + definition_account.account().data.as_ref(), + &[ 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( - holding_account.account().data, - vec![ + holding_account.account().data.as_ref(), + &[ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -456,7 +464,9 @@ mod tests { AccountWithMetadata { account: Account { // First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts - data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE], + data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE] + .try_into() + .unwrap(), ..Account::default() }, is_authorized: true, @@ -478,7 +488,7 @@ mod tests { AccountWithMetadata { account: Account { // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1], + data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1].try_into().unwrap(), ..Account::default() }, is_authorized: true, @@ -500,7 +510,7 @@ mod tests { AccountWithMetadata { account: Account { // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1], + data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1].try_into().unwrap(), ..Account::default() }, is_authorized: true, @@ -521,7 +531,7 @@ mod tests { let pre_states = vec![ AccountWithMetadata { account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], + data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), ..Account::default() }, is_authorized: true, @@ -529,10 +539,12 @@ mod tests { }, AccountWithMetadata { account: Account { - data: vec![1] + data: [1] .into_iter() .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) - .collect(), + .collect::>() + .try_into() + .unwrap(), ..Account::default() }, is_authorized: true, @@ -549,10 +561,12 @@ mod tests { AccountWithMetadata { account: Account { // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] + data: [1; TOKEN_HOLDING_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(37)) - .collect(), + .collect::>() + .try_into() + .unwrap(), ..Account::default() }, is_authorized: true, @@ -560,7 +574,7 @@ mod tests { }, AccountWithMetadata { account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], + data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), ..Account::default() }, is_authorized: true, @@ -578,10 +592,12 @@ mod tests { AccountWithMetadata { account: Account { // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] + data: [1; TOKEN_HOLDING_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(37)) - .collect(), + .collect::>() + .try_into() + .unwrap(), ..Account::default() }, is_authorized: false, @@ -589,7 +605,7 @@ mod tests { }, AccountWithMetadata { account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE], + data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), ..Account::default() }, is_authorized: true, @@ -605,10 +621,12 @@ mod tests { AccountWithMetadata { account: Account { // Account with balance 37 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] + data: [1; TOKEN_HOLDING_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(37)) - .collect(), + .collect::>() + .try_into() + .unwrap(), ..Account::default() }, is_authorized: true, @@ -617,10 +635,12 @@ mod tests { AccountWithMetadata { account: Account { // Account with balance 255 - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] + data: [1; TOKEN_HOLDING_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(255)) - .collect(), + .collect::>() + .try_into() + .unwrap(), ..Account::default() }, is_authorized: true, @@ -630,15 +650,15 @@ mod tests { let post_states = transfer(&pre_states, 11); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); assert_eq!( - sender_post.account().data, - vec![ + sender_post.account().data.as_ref(), + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); assert_eq!( - recipient_post.account().data, - vec![ + recipient_post.account().data.as_ref(), + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] @@ -654,7 +674,9 @@ mod tests { data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] .into_iter() .chain(u128::to_le_bytes(1000)) - .collect(), + .collect::>() + .try_into() + .unwrap(), ..Account::default() }, is_authorized: false, @@ -668,10 +690,13 @@ mod tests { ]; let post_states = initialize_account(&pre_states); let [definition, holding] = post_states.try_into().ok().unwrap(); - assert_eq!(definition.account().data, pre_states[0].account.data); assert_eq!( - holding.account().data, - vec![ + definition.account().data.as_ref(), + pre_states[0].account.data.as_ref() + ); + assert_eq!( + holding.account().data.as_ref(), + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index eeba692..4ef02b3 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -95,7 +95,7 @@ impl Proof { mod tests { use nssa_core::{ Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, - account::{Account, AccountId, AccountWithMetadata}, + account::{Account, AccountId, AccountWithMetadata, data::Data}, }; use super::*; @@ -134,14 +134,14 @@ mod tests { program_owner: program.id(), balance: 100 - balance_to_move, nonce: 1, - data: vec![], + data: Data::default(), }; let expected_recipient_post = Account { program_owner: program.id(), balance: balance_to_move, nonce: 0xdeadbeef, - data: vec![], + data: Data::default(), }; let expected_sender_pre = sender.clone(); @@ -191,7 +191,7 @@ mod tests { balance: 100, nonce: 0xdeadbeef, program_owner: program.id(), - data: vec![], + data: Data::default(), }, true, AccountId::from(&sender_keys.npk()), diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 9359b04..5841f78 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -234,7 +234,7 @@ impl V02State { program_owner: Program::pinata().id(), balance: 1500, // Difficulty: 3 - data: vec![3; 33], + data: vec![3; 33].try_into().unwrap(), nonce: 0, }, ); @@ -248,7 +248,7 @@ impl V02State { Account { program_owner: Program::pinata_token().id(), // Difficulty: 3 - data: vec![3; 33], + data: vec![3; 33].try_into().expect("should fit"), ..Account::default() }, ); @@ -262,7 +262,7 @@ pub mod tests { use nssa_core::{ Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, - account::{Account, AccountId, AccountWithMetadata, Nonce}, + account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar}, program::{PdaSeed, ProgramId}, }; @@ -505,7 +505,7 @@ pub mod tests { ..Account::default() }; let account_with_default_values_except_data = Account { - data: vec![0xca, 0xfe], + data: vec![0xca, 0xfe].try_into().unwrap(), ..Account::default() }; self.force_insert_account( @@ -1027,7 +1027,7 @@ pub mod tests { program_owner: Program::authenticated_transfer_program().id(), balance: 100, nonce: 0xdeadbeef, - data: vec![], + data: Data::default(), }; let recipient_keys = test_private_account_keys_2(); @@ -1051,7 +1051,7 @@ pub mod tests { program_owner: Program::authenticated_transfer_program().id(), nonce: 0xcafecafe, balance: sender_private_account.balance - balance_to_move, - data: vec![], + data: Data::default(), }, ); @@ -1093,7 +1093,7 @@ pub mod tests { program_owner: Program::authenticated_transfer_program().id(), balance: 100, nonce: 0xdeadbeef, - data: vec![], + data: Data::default(), }; let recipient_keys = test_public_account_keys_1(); let recipient_initial_balance = 400; @@ -1126,7 +1126,7 @@ pub mod tests { program_owner: Program::authenticated_transfer_program().id(), nonce: 0xcafecafe, balance: sender_private_account.balance - balance_to_move, - data: vec![], + data: Data::default(), }, ); @@ -1692,7 +1692,7 @@ pub mod tests { let private_account_2 = AccountWithMetadata::new( Account { // Non default data - data: b"hola mundo".to_vec(), + data: b"hola mundo".to_vec().try_into().unwrap(), ..Account::default() }, false, @@ -1981,7 +1981,7 @@ pub mod tests { program_owner: Program::authenticated_transfer_program().id(), balance: 100, nonce: 0xdeadbeef, - data: vec![], + data: Data::default(), }; let recipient_keys = test_private_account_keys_2(); @@ -2007,7 +2007,7 @@ pub mod tests { program_owner: Program::authenticated_transfer_program().id(), balance: 100 - balance_to_move, nonce: 0xcafecafe, - data: vec![], + data: Data::default(), }; let tx = private_balance_transfer_for_tests( @@ -2298,7 +2298,7 @@ pub mod tests { expected_winner_account_data[33..].copy_from_slice(&150u128.to_le_bytes()); let expected_winner_token_holding_post = Account { program_owner: token.id(), - data: expected_winner_account_data.to_vec(), + data: expected_winner_account_data.to_vec().try_into().unwrap(), ..Account::default() }; diff --git a/nssa/test_program_methods/guest/Cargo.lock b/nssa/test_program_methods/guest/Cargo.lock index 85f566c..b2337cc 100644 --- a/nssa/test_program_methods/guest/Cargo.lock +++ b/nssa/test_program_methods/guest/Cargo.lock @@ -1583,6 +1583,7 @@ dependencies = [ "chacha20", "risc0-zkvm", "serde", + "thiserror", ] [[package]] diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/nssa/test_program_methods/guest/src/bin/data_changer.rs index 2869d01..16c2359 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/data_changer.rs @@ -12,7 +12,9 @@ fn main() { let account_pre = &pre.account; let mut account_post = account_pre.clone(); - account_post.data.push(0); + let mut data_vec = account_post.data.into_inner(); + data_vec.push(0); + account_post.data = data_vec.try_into().expect("data_vec should fit into Data"); write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]); } diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index c0e2223..7712a7c 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -197,6 +197,7 @@ async fn find_solution(wallet: &WalletCore, pinata_account_id: nssa::AccountId) let account = wallet.get_account_public(pinata_account_id).await?; let data: [u8; 33] = account .data + .as_ref() .try_into() .map_err(|_| anyhow::Error::msg("invalid pinata account data"))?; From 31e70169481a46630303cba8575f729e67c04987 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Fri, 5 Dec 2025 17:16:41 +0300 Subject: [PATCH 30/30] feat: introduce parameter to data_changer and add test for account data --- integration_tests/src/data_changer.bin | Bin 371256 -> 377792 bytes integration_tests/src/test_suite_map.rs | 2 +- nssa/core/Cargo.toml | 4 +- nssa/core/src/account/data.rs | 9 ++--- nssa/src/state.rs | 35 ++++++++++++++++-- .../guest/src/bin/data_changer.rs | 9 ++--- 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/integration_tests/src/data_changer.bin b/integration_tests/src/data_changer.bin index 6a36d52c37bd34dcb6e4b70f7fedc9f49ecff445..3d062c300fa1a9e6a5cc85e9888e5abaed93d751 100644 GIT binary patch delta 119262 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 z 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 a^{3(^yxQ&BpK7HqE?;}V^ugc1^#1`})LY>I diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 17caf26..7ee3ef4 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -1466,7 +1466,7 @@ pub fn prepare_function_map() -> HashMap { data_changer.id(), vec![account_id], vec![], - (), + vec![0], ) .unwrap(); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index f179899..80fe7df 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -15,8 +15,8 @@ anyhow = { version = "1.0.98", optional = true } borsh = "1.5.7" [dev-dependencies] -serde_json.workspace = true +serde_json = "1.0.81" [features] default = [] -host = ["bytemuck", "k256", "base58", "anyhow"] +host = ["dep:bytemuck", "dep:k256", "dep:base58", "dep:anyhow"] diff --git a/nssa/core/src/account/data.rs b/nssa/core/src/account/data.rs index 281599c..974cb06 100644 --- a/nssa/core/src/account/data.rs +++ b/nssa/core/src/account/data.rs @@ -3,9 +3,6 @@ use std::ops::Deref; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -#[cfg(feature = "host")] -use crate::error::NssaCoreError; - pub const DATA_MAX_LENGTH_IN_BYTES: usize = 100 * 1024; // 100 KiB #[derive(Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)] @@ -18,7 +15,9 @@ impl Data { } #[cfg(feature = "host")] - pub fn from_cursor(cursor: &mut std::io::Cursor<&[u8]>) -> Result { + pub fn from_cursor( + cursor: &mut std::io::Cursor<&[u8]>, + ) -> Result { use std::io::Read as _; let mut u32_bytes = [0u8; 4]; @@ -36,7 +35,7 @@ impl Data { } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)] #[error("data length exceeds maximum allowed length of {DATA_MAX_LENGTH_IN_BYTES} bytes")] pub struct DataTooBigError; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 5841f78..72efbd2 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -234,7 +234,7 @@ impl V02State { program_owner: Program::pinata().id(), balance: 1500, // Difficulty: 3 - data: vec![3; 33].try_into().unwrap(), + data: vec![3; 33].try_into().expect("should fit"), nonce: 0, }, ); @@ -730,7 +730,8 @@ pub mod tests { program_id ); let message = - public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + public_transaction::Message::try_new(program_id, vec![account_id], vec![], vec![0]) + .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -1248,7 +1249,7 @@ pub mod tests { let result = execute_and_prove( &[public_account], - &Program::serialize_instruction(()).unwrap(), + &Program::serialize_instruction(vec![0]).unwrap(), &[0], &[], &[], @@ -1259,6 +1260,34 @@ pub mod tests { assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } + #[test] + fn test_data_changer_program_should_fail_for_too_large_data_in_privacy_preserving_circuit() { + let program = Program::data_changer(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let large_data: Vec = vec![0; nssa_core::account::data::DATA_MAX_LENGTH_IN_BYTES + 1]; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(large_data).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::ProgramProveFailed(_)))); + } + #[test] fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() { let program = Program::extra_output_program(); diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/nssa/test_program_methods/guest/src/bin/data_changer.rs index 16c2359..b590886 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/nssa/test_program_methods/guest/src/bin/data_changer.rs @@ -1,9 +1,10 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; -type Instruction = (); +type Instruction = Vec; +/// A program that modifies the account data by setting bytes sent in instruction. fn main() { - let ProgramInput { pre_states, .. } = read_nssa_inputs::(); + let ProgramInput { pre_states, instruction: data } = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -12,9 +13,7 @@ fn main() { let account_pre = &pre.account; let mut account_post = account_pre.clone(); - let mut data_vec = account_post.data.into_inner(); - data_vec.push(0); - account_post.data = data_vec.try_into().expect("data_vec should fit into Data"); + account_post.data = data.try_into().expect("provided data should fit into data limit"); write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]); }