lssa/nssa/src/state.rs

1077 lines
41 KiB
Rust
Raw Normal View History

2025-08-06 20:05:04 -03:00
use crate::{
2025-08-21 10:19:08 -03:00
address::Address, error::NssaError, merkle_tree::MerkleTree,
privacy_preserving_transaction::PrivacyPreservingTransaction, program::Program,
public_transaction::PublicTransaction,
2025-08-06 20:05:04 -03:00
};
use nssa_core::{
2025-08-27 16:24:20 -03:00
Commitment, CommitmentSetDigest, MembershipProof, Nullifier,
account::Account,
program::{DEFAULT_PROGRAM_ID, ProgramId},
};
use std::collections::{HashMap, HashSet};
2025-08-25 07:44:56 -03:00
pub(crate) struct CommitmentSet {
merkle_tree: MerkleTree,
commitments: HashMap<Commitment, usize>,
2025-08-27 16:24:20 -03:00
root_history: HashSet<CommitmentSetDigest>,
2025-08-25 07:44:56 -03:00
}
impl CommitmentSet {
pub(crate) fn digest(&self) -> CommitmentSetDigest {
2025-08-25 07:44:56 -03:00
self.merkle_tree.root()
}
/// Queries the `CommitmentSet` for a membership proof of commitment
2025-08-27 16:24:20 -03:00
pub fn get_proof_for(&self, commitment: &Commitment) -> Option<MembershipProof> {
2025-08-25 07:44:56 -03:00
let index = *self.commitments.get(commitment)?;
2025-08-27 16:24:20 -03:00
self.merkle_tree
2025-08-25 07:44:56 -03:00
.get_authentication_path_for(index)
2025-08-27 16:24:20 -03:00
.map(|path| (index, path))
}
2025-08-18 14:28:26 -03:00
/// Inserts a list of commitments to the `CommitmentSet`.
2025-08-22 13:42:37 -03:00
pub(crate) fn extend(&mut self, commitments: &[Commitment]) {
2025-08-25 07:44:56 -03:00
for commitment in commitments.iter().cloned() {
let index = self.merkle_tree.insert(commitment.to_byte_array());
self.commitments.insert(commitment, index);
}
2025-08-25 09:22:59 -03:00
self.root_history.insert(self.digest());
}
2025-08-18 14:28:26 -03:00
fn contains(&self, commitment: &Commitment) -> bool {
2025-08-25 07:44:56 -03:00
self.commitments.contains_key(commitment)
}
/// Initializes an empty `CommitmentSet` with a given capacity.
2025-09-05 16:38:34 -04:00
/// If the capacity is not a power_of_two, then capacity is taken
2025-09-05 16:21:46 -04:00
/// to be the next power_of_two.
2025-08-25 07:44:56 -03:00
pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet {
Self {
merkle_tree: MerkleTree::with_capacity(capacity),
commitments: HashMap::new(),
2025-08-25 09:22:59 -03:00
root_history: HashSet::new(),
2025-08-25 07:44:56 -03:00
}
2025-08-18 14:28:26 -03:00
}
}
2025-08-18 07:39:41 -03:00
type NullifierSet = HashSet<Nullifier>;
2025-08-06 20:05:04 -03:00
2025-08-07 15:19:06 -03:00
pub struct V01State {
2025-08-13 01:33:11 -03:00
public_state: HashMap<Address, Account>,
2025-08-27 16:24:20 -03:00
private_state: (CommitmentSet, NullifierSet),
2025-08-13 01:33:11 -03:00
builtin_programs: HashMap<ProgramId, Program>,
2025-08-06 20:05:04 -03:00
}
impl V01State {
2025-08-13 13:42:00 +03:00
pub fn new_with_genesis_accounts(initial_data: &[(Address, u128)]) -> Self {
2025-08-10 00:53:53 -03:00
let authenticated_transfer_program = Program::authenticated_transfer_program();
2025-08-07 15:19:06 -03:00
let public_state = initial_data
2025-08-09 19:20:19 -03:00
.iter()
.copied()
2025-08-13 13:42:00 +03:00
.map(|(address, balance)| {
2025-08-09 18:40:32 -03:00
let account = Account {
2025-08-09 19:20:19 -03:00
balance,
2025-08-10 00:53:53 -03:00
program_owner: authenticated_transfer_program.id(),
2025-08-09 18:40:32 -03:00
..Account::default()
};
2025-08-07 15:19:06 -03:00
(address, account)
})
.collect();
2025-08-10 09:57:10 -03:00
let mut this = Self {
public_state,
2025-08-25 07:44:56 -03:00
private_state: (CommitmentSet::with_capacity(32), NullifierSet::new()),
2025-08-10 09:57:10 -03:00
builtin_programs: HashMap::new(),
};
this.insert_program(Program::authenticated_transfer_program());
this
}
2025-08-12 21:27:27 -03:00
pub(crate) fn insert_program(&mut self, program: Program) {
2025-08-10 09:57:10 -03:00
self.builtin_programs.insert(program.id(), program);
2025-08-07 15:19:06 -03:00
}
2025-08-09 19:49:07 -03:00
pub fn transition_from_public_transaction(
&mut self,
tx: &PublicTransaction,
) -> Result<(), NssaError> {
let state_diff = tx.validate_and_produce_public_state_diff(self)?;
2025-08-06 20:05:04 -03:00
for (address, post) in state_diff.into_iter() {
let current_account = self.get_account_by_address_mut(address);
2025-08-06 20:05:04 -03:00
*current_account = post;
// The invoked program claims the accounts with default program id.
if current_account.program_owner == DEFAULT_PROGRAM_ID {
current_account.program_owner = tx.message().program_id;
}
2025-08-06 20:05:04 -03:00
}
for address in tx.signer_addresses() {
let current_account = self.get_account_by_address_mut(address);
current_account.nonce += 1;
}
2025-08-09 20:35:44 -03:00
2025-08-06 20:05:04 -03:00
Ok(())
}
pub fn transition_from_privacy_preserving_transaction(
&mut self,
tx: &PrivacyPreservingTransaction,
) -> Result<(), NssaError> {
// 1. Verify the transaction satisfies acceptance criteria
let public_state_diff = tx.validate_and_produce_public_state_diff(self)?;
let message = tx.message();
// 2. Add new commitments
2025-08-21 10:19:08 -03:00
self.private_state.0.extend(&message.new_commitments);
// 3. Add new nullifiers
2025-08-25 09:22:59 -03:00
let new_nullifiers = message
.new_nullifiers
.iter()
.cloned()
.map(|(nullifier, _)| nullifier)
.collect::<Vec<Nullifier>>();
self.private_state.1.extend(new_nullifiers);
// 4. Update public accounts
for (address, post) in public_state_diff.into_iter() {
let current_account = self.get_account_by_address_mut(address);
*current_account = post;
}
// // 5. Increment nonces
for address in tx.signer_addresses() {
let current_account = self.get_account_by_address_mut(address);
current_account.nonce += 1;
}
Ok(())
}
2025-08-06 20:05:04 -03:00
fn get_account_by_address_mut(&mut self, address: Address) -> &mut Account {
2025-08-09 19:20:19 -03:00
self.public_state.entry(address).or_default()
2025-08-06 20:05:04 -03:00
}
2025-08-07 15:19:06 -03:00
pub fn get_account_by_address(&self, address: &Address) -> Account {
2025-08-06 20:05:04 -03:00
self.public_state
.get(address)
.cloned()
.unwrap_or(Account::default())
}
2025-08-27 16:24:20 -03:00
pub fn get_proof_for_commitment(&self, commitment: &Commitment) -> Option<MembershipProof> {
self.private_state.0.get_proof_for(commitment)
}
2025-08-10 14:33:40 -03:00
pub(crate) fn builtin_programs(&self) -> &HashMap<ProgramId, Program> {
&self.builtin_programs
2025-08-06 20:05:04 -03:00
}
pub fn commitment_set_digest(&self) -> CommitmentSetDigest {
self.private_state.0.digest()
}
pub(crate) fn check_commitments_are_new(
&self,
new_commitments: &[Commitment],
) -> Result<(), NssaError> {
2025-08-18 14:28:26 -03:00
for commitment in new_commitments.iter() {
if self.private_state.0.contains(commitment) {
return Err(NssaError::InvalidInput(
"Commitment already seen".to_string(),
));
}
}
Ok(())
}
2025-08-25 09:22:59 -03:00
pub(crate) fn check_nullifiers_are_valid(
&self,
2025-08-25 09:22:59 -03:00
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
) -> Result<(), NssaError> {
2025-08-25 09:22:59 -03:00
for (nullifier, digest) in new_nullifiers.iter() {
2025-08-18 14:28:26 -03:00
if self.private_state.1.contains(nullifier) {
return Err(NssaError::InvalidInput(
"Nullifier already seen".to_string(),
));
}
2025-08-25 09:22:59 -03:00
if !self.private_state.0.root_history.contains(digest) {
return Err(NssaError::InvalidInput(
"Unrecognized commitment set digest".to_string(),
));
}
2025-08-18 14:28:26 -03:00
}
Ok(())
}
2025-08-06 20:05:04 -03:00
}
2025-08-13 01:33:11 -03:00
#[cfg(test)]
2025-08-22 12:29:45 -03:00
pub mod tests {
2025-08-13 01:33:11 -03:00
use std::collections::HashMap;
use crate::{
Address, PublicKey, PublicTransaction, V01State,
error::NssaError,
privacy_preserving_transaction::{
2025-08-27 16:24:20 -03:00
PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet,
},
program::Program,
public_transaction,
signature::PrivateKey,
2025-08-13 01:33:11 -03:00
};
2025-08-26 14:53:02 -03:00
use nssa_core::{
2025-08-27 16:24:20 -03:00
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{Account, AccountWithMetadata, Nonce},
encryption::{EphemeralPublicKey, IncomingViewingPublicKey},
};
2025-08-13 01:33:11 -03:00
fn transfer_transaction(
from: Address,
from_key: PrivateKey,
nonce: u128,
to: Address,
balance: u128,
) -> PublicTransaction {
let addresses = vec![from, to];
let nonces = vec![nonce];
let program_id = Program::authenticated_transfer_program().id();
let message =
public_transaction::Message::try_new(program_id, addresses, nonces, balance).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
PublicTransaction::new(message, witness_set)
}
#[test]
fn test_new_with_genesis() {
let key1 = PrivateKey::try_new([1; 32]).unwrap();
let key2 = PrivateKey::try_new([2; 32]).unwrap();
2025-08-13 13:42:00 +03:00
let addr1 = Address::from(&PublicKey::new_from_private_key(&key1));
let addr2 = Address::from(&PublicKey::new_from_private_key(&key2));
let initial_data = [(addr1, 100u128), (addr2, 151u128)];
2025-08-13 01:33:11 -03:00
let program = Program::authenticated_transfer_program();
let expected_public_state = {
let mut this = HashMap::new();
this.insert(
addr1,
Account {
balance: 100,
program_owner: program.id(),
..Account::default()
},
);
this.insert(
addr2,
Account {
balance: 151,
program_owner: program.id(),
..Account::default()
},
);
this
};
let expected_builtin_programs = {
let mut this = HashMap::new();
this.insert(program.id(), program);
this
};
let state = V01State::new_with_genesis_accounts(&initial_data);
assert_eq!(state.public_state, expected_public_state);
assert_eq!(state.builtin_programs, expected_builtin_programs);
}
#[test]
fn test_insert_program() {
let mut state = V01State::new_with_genesis_accounts(&[]);
let program_to_insert = Program::simple_balance_transfer();
let program_id = program_to_insert.id();
assert!(!state.builtin_programs.contains_key(&program_id));
state.insert_program(program_to_insert);
assert!(state.builtin_programs.contains_key(&program_id));
}
#[test]
fn test_get_account_by_address_non_default_account() {
let key = PrivateKey::try_new([1; 32]).unwrap();
2025-08-13 13:42:00 +03:00
let addr = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(addr, 100u128)];
2025-08-13 01:33:11 -03:00
let state = V01State::new_with_genesis_accounts(&initial_data);
let expected_account = state.public_state.get(&addr).unwrap();
let account = state.get_account_by_address(&addr);
assert_eq!(&account, expected_account);
}
#[test]
fn test_get_account_by_address_default_account() {
let addr2 = Address::new([0; 32]);
let state = V01State::new_with_genesis_accounts(&[]);
let expected_account = Account::default();
let account = state.get_account_by_address(&addr2);
assert_eq!(account, expected_account);
}
#[test]
fn test_builtin_programs_getter() {
let state = V01State::new_with_genesis_accounts(&[]);
let builtin_programs = state.builtin_programs();
assert_eq!(builtin_programs, &state.builtin_programs);
}
#[test]
fn transition_from_authenticated_transfer_program_invocation_default_account_destination() {
let key = PrivateKey::try_new([1; 32]).unwrap();
2025-08-13 13:42:00 +03:00
let address = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(address, 100)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = address;
let to = Address::new([2; 32]);
assert_eq!(state.get_account_by_address(&to), Account::default());
let balance_to_move = 5;
2025-08-13 14:15:05 +03:00
let tx = transfer_transaction(from, key, 0, to, balance_to_move);
2025-08-13 01:33:11 -03:00
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&from).balance, 95);
assert_eq!(state.get_account_by_address(&to).balance, 5);
assert_eq!(state.get_account_by_address(&from).nonce, 1);
assert_eq!(state.get_account_by_address(&to).nonce, 0);
}
#[test]
fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() {
let key = PrivateKey::try_new([1; 32]).unwrap();
2025-08-13 13:42:00 +03:00
let address = Address::from(&PublicKey::new_from_private_key(&key));
let initial_data = [(address, 100)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = address;
let from_key = key;
let to = Address::new([2; 32]);
let balance_to_move = 101;
assert!(state.get_account_by_address(&from).balance < balance_to_move);
2025-08-13 14:15:05 +03:00
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
2025-08-13 01:33:11 -03:00
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
assert_eq!(state.get_account_by_address(&from).balance, 100);
assert_eq!(state.get_account_by_address(&to).balance, 0);
assert_eq!(state.get_account_by_address(&from).nonce, 0);
assert_eq!(state.get_account_by_address(&to).nonce, 0);
}
#[test]
fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() {
let key1 = PrivateKey::try_new([1; 32]).unwrap();
let key2 = PrivateKey::try_new([2; 32]).unwrap();
2025-08-13 13:42:00 +03:00
let address1 = Address::from(&PublicKey::new_from_private_key(&key1));
let address2 = Address::from(&PublicKey::new_from_private_key(&key2));
let initial_data = [(address1, 100), (address2, 200)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = address2;
let from_key = key2;
let to = address1;
assert_ne!(state.get_account_by_address(&to), Account::default());
let balance_to_move = 8;
2025-08-13 14:15:05 +03:00
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
2025-08-13 01:33:11 -03:00
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&from).balance, 192);
assert_eq!(state.get_account_by_address(&to).balance, 108);
assert_eq!(state.get_account_by_address(&from).nonce, 1);
assert_eq!(state.get_account_by_address(&to).nonce, 0);
}
#[test]
fn transition_from_chained_authenticated_transfer_program_invocations() {
let key1 = PrivateKey::try_new([8; 32]).unwrap();
2025-08-13 13:42:00 +03:00
let address1 = Address::from(&PublicKey::new_from_private_key(&key1));
2025-08-13 01:33:11 -03:00
let key2 = PrivateKey::try_new([2; 32]).unwrap();
2025-08-13 13:42:00 +03:00
let address2 = Address::from(&PublicKey::new_from_private_key(&key2));
let initial_data = [(address1, 100)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let address3 = Address::new([3; 32]);
let balance_to_move = 5;
2025-08-13 14:15:05 +03:00
let tx = transfer_transaction(address1, key1, 0, address2, balance_to_move);
2025-08-13 01:33:11 -03:00
state.transition_from_public_transaction(&tx).unwrap();
let balance_to_move = 3;
2025-08-13 14:15:05 +03:00
let tx = transfer_transaction(address2, key2, 0, address3, balance_to_move);
2025-08-13 01:33:11 -03:00
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&address1).balance, 95);
assert_eq!(state.get_account_by_address(&address2).balance, 2);
assert_eq!(state.get_account_by_address(&address3).balance, 3);
assert_eq!(state.get_account_by_address(&address1).nonce, 1);
assert_eq!(state.get_account_by_address(&address2).nonce, 1);
assert_eq!(state.get_account_by_address(&address3).nonce, 0);
}
impl V01State {
2025-08-13 01:55:56 -03:00
pub fn force_insert_account(&mut self, address: Address, account: Account) {
self.public_state.insert(address, account);
}
2025-08-13 01:33:11 -03:00
/// Include test programs in the builtin programs map
pub fn with_test_programs(mut self) -> Self {
self.insert_program(Program::nonce_changer_program());
self.insert_program(Program::extra_output_program());
self.insert_program(Program::missing_output_program());
self.insert_program(Program::program_owner_changer());
self.insert_program(Program::simple_balance_transfer());
self.insert_program(Program::data_changer());
self.insert_program(Program::minter());
self.insert_program(Program::burner());
self
}
pub fn with_non_default_accounts_but_default_program_owners(mut self) -> Self {
let account_with_default_values_except_balance = Account {
balance: 100,
..Account::default()
};
let account_with_default_values_except_nonce = Account {
nonce: 37,
..Account::default()
};
let account_with_default_values_except_data = Account {
data: vec![0xca, 0xfe],
..Account::default()
};
self.force_insert_account(
Address::new([255; 32]),
account_with_default_values_except_balance,
);
self.force_insert_account(
Address::new([254; 32]),
account_with_default_values_except_nonce,
);
self.force_insert_account(
Address::new([253; 32]),
account_with_default_values_except_data,
);
self
}
pub fn with_account_owned_by_burner_program(mut self) -> Self {
let account = Account {
program_owner: Program::burner().id(),
balance: 100,
..Default::default()
};
self.force_insert_account(Address::new([252; 32]), account);
self
}
2025-08-22 12:29:45 -03:00
pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self {
2025-08-27 16:24:20 -03:00
let commitment = Commitment::new(&keys.npk(), account);
self.private_state.0.extend(&[commitment]);
2025-08-22 12:29:45 -03:00
self
}
2025-08-13 01:33:11 -03:00
}
#[test]
fn test_program_should_fail_if_modifies_nonces() {
2025-08-13 13:42:00 +03:00
let initial_data = [(Address::new([1; 32]), 100)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let addresses = vec![Address::new([1; 32])];
let program_id = Program::nonce_changer_program().id();
let message =
public_transaction::Message::try_new(program_id, addresses, 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)));
}
#[test]
fn test_program_should_fail_if_output_accounts_exceed_inputs() {
2025-08-13 13:42:00 +03:00
let initial_data = [(Address::new([1; 32]), 100)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let addresses = vec![Address::new([1; 32])];
let program_id = Program::extra_output_program().id();
let message =
public_transaction::Message::try_new(program_id, addresses, 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)));
}
#[test]
fn test_program_should_fail_with_missing_output_accounts() {
2025-08-13 13:42:00 +03:00
let initial_data = [(Address::new([1; 32]), 100)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let addresses = vec![Address::new([1; 32]), Address::new([2; 32])];
let program_id = Program::missing_output_program().id();
let message =
public_transaction::Message::try_new(program_id, addresses, 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)));
}
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() {
2025-08-13 13:42:00 +03:00
let initial_data = [(Address::new([1; 32]), 0)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let address = Address::new([1; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in the program owner field
assert_ne!(account.program_owner, Account::default().program_owner);
assert_eq!(account.balance, Account::default().balance);
assert_eq!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], 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)));
}
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_balance() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([255; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in balance field
assert_eq!(account.program_owner, Account::default().program_owner);
assert_ne!(account.balance, Account::default().balance);
assert_eq!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], 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)));
}
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([254; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in nonce field
assert_eq!(account.program_owner, Account::default().program_owner);
assert_eq!(account.balance, Account::default().balance);
assert_ne!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], 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)));
}
#[test]
fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
.with_test_programs()
.with_non_default_accounts_but_default_program_owners();
let address = Address::new([253; 32]);
let account = state.get_account_by_address(&address);
// Assert the target account only differs from the default account in data field
assert_eq!(account.program_owner, Account::default().program_owner);
assert_eq!(account.balance, Account::default().balance);
assert_eq!(account.nonce, Account::default().nonce);
assert_ne!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], 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)));
}
#[test]
fn test_program_should_fail_if_transfers_balance_from_non_owned_account() {
2025-08-13 13:42:00 +03:00
let initial_data = [(Address::new([1; 32]), 100)];
2025-08-13 01:33:11 -03:00
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let sender_address = Address::new([1; 32]);
let receiver_address = Address::new([2; 32]);
let balance_to_move: u128 = 1;
let program_id = Program::simple_balance_transfer().id();
assert_ne!(
state.get_account_by_address(&sender_address).program_owner,
program_id
);
let message = public_transaction::Message::try_new(
program_id,
vec![sender_address, receiver_address],
vec![],
balance_to_move,
)
.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)));
}
#[test]
fn test_program_should_fail_if_modifies_data_of_non_owned_account() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let address = Address::new([1; 32]);
let program_id = Program::data_changer().id();
// Consider the extreme case where the target account is the default account
assert_eq!(state.get_account_by_address(&address), Account::default());
assert_ne!(
state.get_account_by_address(&address).program_owner,
program_id
);
let message =
public_transaction::Message::try_new(program_id, vec![address], 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)));
}
#[test]
fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data).with_test_programs();
let address = Address::new([1; 32]);
let program_id = Program::minter().id();
let message =
public_transaction::Message::try_new(program_id, vec![address], 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)));
}
#[test]
fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() {
let initial_data = [];
let mut state = V01State::new_with_genesis_accounts(&initial_data)
.with_test_programs()
.with_account_owned_by_burner_program();
let program_id = Program::burner().id();
let address = Address::new([252; 32]);
assert_eq!(
state.get_account_by_address(&address).program_owner,
program_id
);
let balance_to_burn: u128 = 1;
assert!(state.get_account_by_address(&address).balance > balance_to_burn);
let message = public_transaction::Message::try_new(
program_id,
vec![address],
vec![],
balance_to_burn,
)
.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)));
}
2025-08-22 13:42:37 -03:00
pub struct TestPublicKeys {
pub signing_key: PrivateKey,
2025-08-22 12:29:45 -03:00
}
impl TestPublicKeys {
2025-08-22 13:42:37 -03:00
pub fn address(&self) -> Address {
2025-08-22 12:29:45 -03:00
Address::from(&PublicKey::new_from_private_key(&self.signing_key))
}
}
fn test_public_account_keys_1() -> TestPublicKeys {
TestPublicKeys {
signing_key: PrivateKey::try_new([37; 32]).unwrap(),
}
}
2025-08-22 13:42:37 -03:00
pub struct TestPrivateKeys {
pub nsk: NullifierSecretKey,
pub isk: [u8; 32],
2025-08-22 12:29:45 -03:00
}
impl TestPrivateKeys {
2025-08-22 13:42:37 -03:00
pub fn npk(&self) -> NullifierPublicKey {
2025-08-22 12:29:45 -03:00
NullifierPublicKey::from(&self.nsk)
}
pub fn ivk(&self) -> IncomingViewingPublicKey {
IncomingViewingPublicKey::from_scalar(self.isk)
}
2025-08-22 12:29:45 -03:00
}
2025-08-22 13:42:37 -03:00
pub fn test_private_account_keys_1() -> TestPrivateKeys {
2025-08-22 12:29:45 -03:00
TestPrivateKeys {
nsk: [13; 32],
isk: [31; 32],
2025-08-22 12:29:45 -03:00
}
}
2025-08-22 13:42:37 -03:00
pub fn test_private_account_keys_2() -> TestPrivateKeys {
2025-08-22 12:29:45 -03:00
TestPrivateKeys {
nsk: [38; 32],
isk: [83; 32],
2025-08-22 12:29:45 -03:00
}
}
fn shielded_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
sender_keys: &TestPublicKeys,
recipient_keys: &TestPrivateKeys,
balance_to_move: u128,
state: &V01State,
) -> PrivacyPreservingTransaction {
2025-09-11 16:37:28 -03:00
let sender = AccountWithMetadata::new(
state.get_account_by_address(&sender_keys.address()),
true,
&sender_keys.address(),
);
let sender_nonce = sender.account.nonce;
2025-08-21 15:52:35 -03:00
2025-09-11 16:37:28 -03:00
let recipient = AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
let esk = [3; 32];
2025-08-26 14:53:02 -03:00
let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk());
let epk = EphemeralPublicKey::from_scalar(esk);
let (output, proof) = circuit::execute_and_prove(
&[sender, recipient],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[0, 2],
&[0xdeadbeef],
&[(recipient_keys.npk(), shared_secret)],
&[],
&Program::authenticated_transfer_program(),
)
.unwrap();
let message = Message::try_from_circuit_output(
2025-08-22 12:29:45 -03:00
vec![sender_keys.address()],
vec![sender_nonce],
2025-09-01 18:12:13 -03:00
vec![(recipient_keys.npk(), recipient_keys.ivk(), epk)],
output,
2025-08-26 14:53:02 -03:00
)
.unwrap();
2025-08-22 12:29:45 -03:00
let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]);
PrivacyPreservingTransaction::new(message, witness_set)
}
fn private_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
sender_keys: &TestPrivateKeys,
sender_private_account: &Account,
2025-08-22 12:29:45 -03:00
recipient_keys: &TestPrivateKeys,
balance_to_move: u128,
new_nonces: [Nonce; 2],
state: &V01State,
) -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
2025-08-27 16:24:20 -03:00
let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account);
2025-09-11 16:37:28 -03:00
let sender_pre =
AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk());
let recipient_pre =
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
let esk_1 = [3; 32];
2025-08-26 14:53:02 -03:00
let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk());
let epk_1 = EphemeralPublicKey::from_scalar(esk_1);
let esk_2 = [3; 32];
2025-08-26 14:53:02 -03:00
let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk());
let epk_2 = EphemeralPublicKey::from_scalar(esk_2);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[1, 2],
&new_nonces,
&[
(sender_keys.npk(), shared_secret_1),
(recipient_keys.npk(), shared_secret_2),
],
&[(
2025-08-22 12:29:45 -03:00
sender_keys.nsk,
2025-08-27 16:24:20 -03:00
state.get_proof_for_commitment(&sender_commitment).unwrap(),
)],
&program,
)
.unwrap();
2025-09-01 18:12:13 -03:00
let message = Message::try_from_circuit_output(
vec![],
vec![],
vec![
(sender_keys.npk(), sender_keys.ivk(), epk_1),
(recipient_keys.npk(), recipient_keys.ivk(), epk_2),
],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
PrivacyPreservingTransaction::new(message, witness_set)
}
fn deshielded_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
sender_keys: &TestPrivateKeys,
sender_private_account: &Account,
2025-08-22 12:29:45 -03:00
recipient_address: &Address,
balance_to_move: u128,
new_nonce: Nonce,
state: &V01State,
) -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
2025-08-27 16:24:20 -03:00
let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account);
2025-09-11 16:37:28 -03:00
let sender_pre =
AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk());
let recipient_pre = AccountWithMetadata::new(
state.get_account_by_address(recipient_address),
false,
recipient_address,
);
let esk = [3; 32];
2025-08-26 14:53:02 -03:00
let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk());
let epk = EphemeralPublicKey::from_scalar(esk);
let (output, proof) = circuit::execute_and_prove(
&[sender_pre, recipient_pre],
&Program::serialize_instruction(balance_to_move).unwrap(),
&[1, 0],
&[new_nonce],
&[(sender_keys.npk(), shared_secret)],
&[(
2025-08-22 12:29:45 -03:00
sender_keys.nsk,
2025-08-27 16:24:20 -03:00
state.get_proof_for_commitment(&sender_commitment).unwrap(),
)],
&program,
)
.unwrap();
2025-09-01 18:12:13 -03:00
let message = Message::try_from_circuit_output(
vec![*recipient_address],
vec![],
vec![(sender_keys.npk(), sender_keys.ivk(), epk)],
output,
)
.unwrap();
let witness_set = WitnessSet::for_message(&message, proof, &[]);
PrivacyPreservingTransaction::new(message, witness_set)
}
#[test]
fn test_transition_from_privacy_preserving_transaction_shielded() {
2025-08-22 12:29:45 -03:00
let sender_keys = test_public_account_keys_1();
let recipient_keys = test_private_account_keys_1();
let mut state = V01State::new_with_genesis_accounts(&[(sender_keys.address(), 200)]);
let balance_to_move = 37;
let tx = shielded_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
&sender_keys,
&recipient_keys,
balance_to_move,
&state,
);
let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap();
assert!(!state.private_state.0.contains(&expected_new_commitment));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
assert!(state.private_state.0.contains(&expected_new_commitment));
assert_eq!(
2025-08-22 12:29:45 -03:00
state.get_account_by_address(&sender_keys.address()).balance,
200 - balance_to_move
);
}
#[test]
fn test_transition_from_privacy_preserving_transaction_private() {
2025-08-22 12:29:45 -03:00
let sender_keys = test_private_account_keys_1();
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
data: vec![],
};
let recipient_keys = test_private_account_keys_2();
let mut state = V01State::new_with_genesis_accounts(&[])
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let tx = private_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
&sender_keys,
&sender_private_account,
2025-08-22 12:29:45 -03:00
&recipient_keys,
balance_to_move,
2025-08-22 12:29:45 -03:00
[0xcafecafe, 0xfecafeca],
&state,
);
let expected_new_commitment_1 = Commitment::new(
2025-08-22 12:29:45 -03:00
&sender_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
2025-08-22 12:29:45 -03:00
nonce: 0xcafecafe,
balance: sender_private_account.balance - balance_to_move,
2025-08-22 12:29:45 -03:00
data: vec![],
},
);
2025-08-22 12:29:45 -03:00
let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_keys.nsk);
let expected_new_commitment_2 = Commitment::new(
2025-08-22 12:29:45 -03:00
&recipient_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
2025-08-22 12:29:45 -03:00
nonce: 0xfecafeca,
balance: balance_to_move,
..Account::default()
},
);
let previous_public_state = state.public_state.clone();
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(!state.private_state.0.contains(&expected_new_commitment_1));
assert!(!state.private_state.0.contains(&expected_new_commitment_2));
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
assert_eq!(state.public_state, previous_public_state);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment_1));
assert!(state.private_state.0.contains(&expected_new_commitment_2));
assert!(state.private_state.1.contains(&expected_new_nullifier));
}
#[test]
fn test_transition_from_privacy_preserving_transaction_deshielded() {
2025-08-22 12:29:45 -03:00
let sender_keys = test_private_account_keys_1();
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
data: vec![],
};
let recipient_keys = test_public_account_keys_1();
let recipient_initial_balance = 400;
2025-08-22 12:29:45 -03:00
let mut state = V01State::new_with_genesis_accounts(&[(
recipient_keys.address(),
recipient_initial_balance,
)])
.with_private_account(&sender_keys, &sender_private_account);
let balance_to_move = 37;
let tx = deshielded_balance_transfer_for_tests(
2025-08-22 12:29:45 -03:00
&sender_keys,
&sender_private_account,
2025-08-22 12:29:45 -03:00
&recipient_keys.address(),
balance_to_move,
2025-08-22 12:29:45 -03:00
0xcafecafe,
&state,
);
let expected_new_commitment = Commitment::new(
2025-08-22 12:29:45 -03:00
&sender_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
2025-08-22 12:29:45 -03:00
nonce: 0xcafecafe,
balance: sender_private_account.balance - balance_to_move,
2025-08-22 12:29:45 -03:00
data: vec![],
},
);
2025-08-22 12:29:45 -03:00
let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
let expected_new_nullifier = Nullifier::new(&sender_pre_commitment, &sender_keys.nsk);
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(!state.private_state.0.contains(&expected_new_commitment));
assert!(!state.private_state.1.contains(&expected_new_nullifier));
state
.transition_from_privacy_preserving_transaction(&tx)
.unwrap();
assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment));
assert!(state.private_state.1.contains(&expected_new_nullifier));
assert_eq!(
2025-08-22 12:29:45 -03:00
state
.get_account_by_address(&recipient_keys.address())
.balance,
recipient_initial_balance + balance_to_move
);
}
2025-08-13 01:33:11 -03:00
}