From 50d402880c34fd88ce97c264605549c80be2e34a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 6 Apr 2026 16:25:53 -0300 Subject: [PATCH 1/3] add nullifiers for preconfigured accounts on genesis --- indexer/core/src/lib.rs | 17 ++++----- nssa/src/state.rs | 62 ++++++++++++++++++++++++++++---- sequencer/core/src/lib.rs | 36 ++++++++++--------- testnet_initial_state/src/lib.rs | 24 +++++++------ 4 files changed, 97 insertions(+), 42 deletions(-) diff --git a/indexer/core/src/lib.rs b/indexer/core/src/lib.rs index bcd99ad7..732dc922 100644 --- a/indexer/core/src/lib.rs +++ b/indexer/core/src/lib.rs @@ -57,11 +57,9 @@ impl IndexerCore { let channel_genesis_msg_id = [0; 32]; let genesis_block = hashable_data.into_pending_block(&signing_key, channel_genesis_msg_id); - let initial_commitments: Option> = config - .initial_private_accounts - .as_ref() - .map(|initial_commitments| { - initial_commitments + let initial_private_accounts: Option> = + config.initial_private_accounts.as_ref().map(|accounts| { + accounts .iter() .map(|init_comm_data| { let npk = &init_comm_data.npk; @@ -71,7 +69,10 @@ impl IndexerCore { acc.program_owner = nssa::program::Program::authenticated_transfer_program().id(); - nssa_core::Commitment::new(npk, &acc) + ( + nssa_core::Nullifier::for_account_initialization(npk), + nssa_core::Commitment::new(npk, &acc), + ) }) .collect() }); @@ -88,10 +89,10 @@ impl IndexerCore { // If initial commitments or accounts are present in config, need to construct state from // them - let state = if initial_commitments.is_some() || init_accs.is_some() { + let state = if initial_private_accounts.is_some() || init_accs.is_some() { let mut state = V03State::new_with_genesis_accounts( &init_accs.unwrap_or_default(), - &initial_commitments.unwrap_or_default(), + &initial_private_accounts.unwrap_or_default(), ); // ToDo: Remove after testnet diff --git a/nssa/src/state.rs b/nssa/src/state.rs index ec37884e..6c9445d5 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -118,7 +118,7 @@ impl V03State { #[must_use] pub fn new_with_genesis_accounts( initial_data: &[(AccountId, u128)], - initial_commitments: &[nssa_core::Commitment], + initial_private_accounts: &[(Nullifier, Commitment)], ) -> Self { let authenticated_transfer_program = Program::authenticated_transfer_program(); let public_state = initial_data @@ -134,13 +134,24 @@ impl V03State { }) .collect(); + let initial_commitments: Vec = initial_private_accounts + .iter() + .map(|(_, c)| c.clone()) + .collect(); let mut private_state = CommitmentSet::with_capacity(32); private_state.extend(&[DUMMY_COMMITMENT]); - private_state.extend(initial_commitments); + private_state.extend(&initial_commitments); + + let init_nullifiers: Vec = initial_private_accounts + .iter() + .map(|(n, _)| n.clone()) + .collect(); + let mut nullifier_set = NullifierSet::new(); + nullifier_set.extend(init_nullifiers); let mut this = Self { public_state, - private_state: (private_state, NullifierSet::new()), + private_state: (private_state, nullifier_set), programs: HashMap::new(), }; @@ -523,6 +534,34 @@ pub mod tests { assert_eq!(state.programs, expected_builtin_programs); } + #[test] + fn new_with_genesis_includes_nullifiers_for_private_accounts() { + let keys1 = test_private_account_keys_1(); + let keys2 = test_private_account_keys_2(); + + let account = Account { + balance: 100, + program_owner: Program::authenticated_transfer_program().id(), + ..Account::default() + }; + + let npk1 = keys1.npk(); + let npk2 = keys2.npk(); + + let init_nullifier1 = Nullifier::for_account_initialization(&npk1); + let init_nullifier2 = Nullifier::for_account_initialization(&npk2); + + let initial_private_accounts = vec![ + (init_nullifier1.clone(), Commitment::new(&npk1, &account)), + (init_nullifier2.clone(), Commitment::new(&npk2, &account)), + ]; + + let state = V03State::new_with_genesis_accounts(&[], &initial_private_accounts); + + assert!(state.private_state.1.contains(&init_nullifier1)); + assert!(state.private_state.1.contains(&init_nullifier2)); + } + #[test] fn insert_program() { let mut state = V03State::new_with_genesis_accounts(&[], &[]); @@ -2542,8 +2581,11 @@ pub mod tests { ..Account::default() }; let sender_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); - let mut state = - V03State::new_with_genesis_accounts(&[], std::slice::from_ref(&sender_commitment)); + let sender_init_nullifier = Nullifier::for_account_initialization(&sender_keys.npk()); + let mut state = V03State::new_with_genesis_accounts( + &[], + &[(sender_init_nullifier, sender_commitment.clone())], + ); let sender_pre = AccountWithMetadata::new(sender_private_account, true, &sender_keys.npk()); let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap(); let recipient_account_id = @@ -2623,9 +2665,14 @@ pub mod tests { let from_commitment = Commitment::new(&from_keys.npk(), &from_account.account); let to_commitment = Commitment::new(&to_keys.npk(), &to_account.account); + let from_init_nullifier = Nullifier::for_account_initialization(&from_keys.npk()); + let to_init_nullifier = Nullifier::for_account_initialization(&to_keys.npk()); let mut state = V03State::new_with_genesis_accounts( &[], - &[from_commitment.clone(), to_commitment.clone()], + &[ + (from_init_nullifier, from_commitment.clone()), + (to_init_nullifier, to_commitment.clone()), + ], ) .with_test_programs(); let amount: u128 = 37; @@ -3192,9 +3239,10 @@ pub mod tests { let recipient_commitment = Commitment::new(&recipient_keys.npk(), &recipient_account.account); + let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_keys.npk()); let state = V03State::new_with_genesis_accounts( &[(sender_account.account_id, sender_account.account.balance)], - std::slice::from_ref(&recipient_commitment), + &[(recipient_init_nullifier, recipient_commitment.clone())], ) .with_test_programs(); diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index 16667051..418ff2d4 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -104,24 +104,26 @@ impl SequencerCore> = config - .initial_private_accounts - .clone() - .map(|initial_commitments| { - initial_commitments - .iter() - .map(|init_comm_data| { - let npk = &init_comm_data.npk; + let initial_private_accounts: Option< + Vec<(nssa_core::Nullifier, nssa_core::Commitment)>, + > = config.initial_private_accounts.clone().map(|accounts| { + accounts + .iter() + .map(|init_comm_data| { + let npk = &init_comm_data.npk; - let mut acc = init_comm_data.account.clone(); + let mut acc = init_comm_data.account.clone(); - acc.program_owner = - nssa::program::Program::authenticated_transfer_program().id(); + acc.program_owner = + nssa::program::Program::authenticated_transfer_program().id(); - nssa_core::Commitment::new(npk, &acc) - }) - .collect() - }); + ( + nssa_core::Nullifier::for_account_initialization(npk), + nssa_core::Commitment::new(npk, &acc), + ) + }) + .collect() + }); let init_accs: Option> = config .initial_public_accounts @@ -135,10 +137,10 @@ impl SequencerCore Vec { #[must_use] pub fn initial_state() -> V03State { - let initial_commitments: Vec = initial_commitments() - .iter() - .map(|init_comm_data| { - let npk = &init_comm_data.npk; + let initial_private_accounts: Vec<(nssa_core::Nullifier, nssa_core::Commitment)> = + initial_commitments() + .iter() + .map(|init_comm_data| { + let npk = &init_comm_data.npk; - let mut acc = init_comm_data.account.clone(); + let mut acc = init_comm_data.account.clone(); - acc.program_owner = nssa::program::Program::authenticated_transfer_program().id(); + acc.program_owner = nssa::program::Program::authenticated_transfer_program().id(); - nssa_core::Commitment::new(npk, &acc) - }) - .collect(); + ( + nssa_core::Nullifier::for_account_initialization(npk), + nssa_core::Commitment::new(npk, &acc), + ) + }) + .collect(); let init_accs: Vec<(nssa::AccountId, u128)> = initial_accounts() .iter() .map(|acc_data| (acc_data.account_id, acc_data.balance)) .collect(); - nssa::V03State::new_with_genesis_accounts(&init_accs, &initial_commitments) + nssa::V03State::new_with_genesis_accounts(&init_accs, &initial_private_accounts) } #[must_use] From 2d1896013b4c24f3575997a7747e929c651e7f6f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 7 Apr 2026 13:35:13 -0300 Subject: [PATCH 2/3] handle comments --- indexer/core/src/block_store.rs | 4 +- indexer/core/src/lib.rs | 6 +- nssa/src/public_transaction/transaction.rs | 2 +- nssa/src/state.rs | 130 ++++++++++----------- programs/amm/src/tests.rs | 4 +- sequencer/core/src/block_store.rs | 6 +- sequencer/core/src/lib.rs | 6 +- storage/src/indexer/mod.rs | 12 +- testnet_initial_state/src/lib.rs | 6 +- 9 files changed, 86 insertions(+), 90 deletions(-) diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index 7faf5376..3ee3161e 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -177,7 +177,7 @@ mod tests { let storage = IndexerStore::open_db_with_genesis( home.as_ref(), &genesis_block(), - &nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], &[]), + &nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], vec![]), ) .unwrap(); @@ -195,7 +195,7 @@ mod tests { let storage = IndexerStore::open_db_with_genesis( home.as_ref(), &genesis_block(), - &nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], &[]), + &nssa::V03State::new_with_genesis_accounts(&[(acc1(), 10000), (acc2(), 20000)], vec![]), ) .unwrap(); diff --git a/indexer/core/src/lib.rs b/indexer/core/src/lib.rs index 732dc922..541471f2 100644 --- a/indexer/core/src/lib.rs +++ b/indexer/core/src/lib.rs @@ -57,7 +57,7 @@ impl IndexerCore { let channel_genesis_msg_id = [0; 32]; let genesis_block = hashable_data.into_pending_block(&signing_key, channel_genesis_msg_id); - let initial_private_accounts: Option> = + let initial_private_accounts: Option> = config.initial_private_accounts.as_ref().map(|accounts| { accounts .iter() @@ -70,8 +70,8 @@ impl IndexerCore { nssa::program::Program::authenticated_transfer_program().id(); ( - nssa_core::Nullifier::for_account_initialization(npk), nssa_core::Commitment::new(npk, &acc), + nssa_core::Nullifier::for_account_initialization(npk), ) }) .collect() @@ -92,7 +92,7 @@ impl IndexerCore { let state = if initial_private_accounts.is_some() || init_accs.is_some() { let mut state = V03State::new_with_genesis_accounts( &init_accs.unwrap_or_default(), - &initial_private_accounts.unwrap_or_default(), + initial_private_accounts.unwrap_or_default(), ); // ToDo: Remove after testnet diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 8c46c88f..937f0d24 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -302,7 +302,7 @@ pub mod tests { fn state_for_tests() -> V03State { let (_, _, addr1, addr2) = keys_for_tests(); let initial_data = [(addr1, 10000), (addr2, 20000)]; - V03State::new_with_genesis_accounts(&initial_data, &[]) + V03State::new_with_genesis_accounts(&initial_data, vec![]) } fn transaction_for_tests() -> PublicTransaction { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 6c9445d5..91984b57 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -118,7 +118,7 @@ impl V03State { #[must_use] pub fn new_with_genesis_accounts( initial_data: &[(AccountId, u128)], - initial_private_accounts: &[(Nullifier, Commitment)], + initial_private_accounts: Vec<(Commitment, Nullifier)>, ) -> Self { let authenticated_transfer_program = Program::authenticated_transfer_program(); let public_state = initial_data @@ -134,24 +134,18 @@ impl V03State { }) .collect(); - let initial_commitments: Vec = initial_private_accounts - .iter() - .map(|(_, c)| c.clone()) - .collect(); - let mut private_state = CommitmentSet::with_capacity(32); - private_state.extend(&[DUMMY_COMMITMENT]); - private_state.extend(&initial_commitments); - - let init_nullifiers: Vec = initial_private_accounts - .iter() - .map(|(n, _)| n.clone()) - .collect(); + let mut commitment_set = CommitmentSet::with_capacity(32); + commitment_set.extend(&[DUMMY_COMMITMENT]); + let (commitments, nullifiers): (Vec, Vec) = + initial_private_accounts.into_iter().unzip(); + commitment_set.extend(&commitments); let mut nullifier_set = NullifierSet::new(); - nullifier_set.extend(init_nullifiers); + nullifier_set.extend(nullifiers); + let private_state = (commitment_set, nullifier_set); let mut this = Self { public_state, - private_state: (private_state, nullifier_set), + private_state, programs: HashMap::new(), }; @@ -528,7 +522,7 @@ pub mod tests { this }; - let state = V03State::new_with_genesis_accounts(&initial_data, &[]); + let state = V03State::new_with_genesis_accounts(&initial_data, vec![]); assert_eq!(state.public_state, expected_public_state); assert_eq!(state.programs, expected_builtin_programs); @@ -548,15 +542,17 @@ pub mod tests { let npk1 = keys1.npk(); let npk2 = keys2.npk(); + let init_commitment1 = Commitment::new(&npk1, &account); + let init_commitment2 = Commitment::new(&npk2, &account); let init_nullifier1 = Nullifier::for_account_initialization(&npk1); let init_nullifier2 = Nullifier::for_account_initialization(&npk2); let initial_private_accounts = vec![ - (init_nullifier1.clone(), Commitment::new(&npk1, &account)), - (init_nullifier2.clone(), Commitment::new(&npk2, &account)), + (init_commitment1, init_nullifier1.clone()), + (init_commitment2, init_nullifier2.clone()), ]; - let state = V03State::new_with_genesis_accounts(&[], &initial_private_accounts); + let state = V03State::new_with_genesis_accounts(&[], initial_private_accounts); assert!(state.private_state.1.contains(&init_nullifier1)); assert!(state.private_state.1.contains(&init_nullifier2)); @@ -564,7 +560,7 @@ pub mod tests { #[test] fn insert_program() { - let mut state = V03State::new_with_genesis_accounts(&[], &[]); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]); let program_to_insert = Program::simple_balance_transfer(); let program_id = program_to_insert.id(); assert!(!state.programs.contains_key(&program_id)); @@ -579,7 +575,7 @@ pub mod tests { let key = PrivateKey::try_new([1; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); let initial_data = [(account_id, 100_u128)]; - let state = V03State::new_with_genesis_accounts(&initial_data, &[]); + let state = V03State::new_with_genesis_accounts(&initial_data, vec![]); let expected_account = &state.public_state[&account_id]; let account = state.get_account_by_id(account_id); @@ -590,7 +586,7 @@ pub mod tests { #[test] fn get_account_by_account_id_default_account() { let addr2 = AccountId::new([0; 32]); - let state = V03State::new_with_genesis_accounts(&[], &[]); + let state = V03State::new_with_genesis_accounts(&[], vec![]); let expected_account = Account::default(); let account = state.get_account_by_id(addr2); @@ -600,7 +596,7 @@ pub mod tests { #[test] fn builtin_programs_getter() { - let state = V03State::new_with_genesis_accounts(&[], &[]); + let state = V03State::new_with_genesis_accounts(&[], vec![]); let builtin_programs = state.programs(); @@ -612,7 +608,7 @@ pub mod tests { let key = PrivateKey::try_new([1; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); let initial_data = [(account_id, 100)]; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]); let from = account_id; let to_key = PrivateKey::try_new([2; 32]).unwrap(); let to = AccountId::from(&PublicKey::new_from_private_key(&to_key)); @@ -633,7 +629,7 @@ pub mod tests { let key = PrivateKey::try_new([1; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); let initial_data = [(account_id, 100)]; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]); let from = account_id; let from_key = key; let to_key = PrivateKey::try_new([2; 32]).unwrap(); @@ -658,7 +654,7 @@ pub mod tests { let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); let initial_data = [(account_id1, 100), (account_id2, 200)]; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]); let from = account_id2; let from_key = key2; let to = account_id1; @@ -682,7 +678,7 @@ pub mod tests { let key2 = PrivateKey::try_new([2; 32]).unwrap(); let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); let initial_data = [(account_id1, 100)]; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]); let key3 = PrivateKey::try_new([3; 32]).unwrap(); let account_id3 = AccountId::from(&PublicKey::new_from_private_key(&key3)); let balance_to_move = 5; @@ -721,7 +717,7 @@ pub mod tests { fn program_should_fail_if_modifies_nonces() { let initial_data = [(AccountId::new([1; 32]), 100)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let account_ids = vec![AccountId::new([1; 32])]; let program_id = Program::nonce_changer_program().id(); let message = @@ -738,7 +734,7 @@ pub mod tests { fn program_should_fail_if_output_accounts_exceed_inputs() { let initial_data = [(AccountId::new([1; 32]), 100)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let account_ids = vec![AccountId::new([1; 32])]; let program_id = Program::extra_output_program().id(); let message = @@ -755,7 +751,7 @@ pub mod tests { fn program_should_fail_with_missing_output_accounts() { let initial_data = [(AccountId::new([1; 32]), 100)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let account_ids = vec![AccountId::new([1; 32]), AccountId::new([2; 32])]; let program_id = Program::missing_output_program().id(); let message = @@ -772,7 +768,7 @@ pub mod tests { fn program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() { let initial_data = [(AccountId::new([1; 32]), 0)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let account_id = AccountId::new([1; 32]); let account = state.get_account_by_id(account_id); // Assert the target account only differs from the default account in the program owner @@ -795,7 +791,7 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_program_owner_with_only_non_default_balance() { let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let account_id = AccountId::new([255; 32]); @@ -819,7 +815,7 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() { let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let account_id = AccountId::new([254; 32]); @@ -843,7 +839,7 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_program_owner_with_only_non_default_data() { let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let account_id = AccountId::new([253; 32]); @@ -868,7 +864,7 @@ pub mod tests { fn program_should_fail_if_transfers_balance_from_non_owned_account() { let initial_data = [(AccountId::new([1; 32]), 100)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let sender_account_id = AccountId::new([1; 32]); let receiver_account_id = AccountId::new([2; 32]); let balance_to_move: u128 = 1; @@ -895,7 +891,7 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_data_of_non_owned_account() { let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let account_id = AccountId::new([255; 32]); @@ -921,7 +917,7 @@ pub mod tests { fn program_should_fail_if_does_not_preserve_total_balance_by_minting() { let initial_data = []; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let account_id = AccountId::new([1; 32]); let program_id = Program::minter().id(); @@ -938,7 +934,7 @@ pub mod tests { #[test] fn program_should_fail_if_does_not_preserve_total_balance_by_burning() { let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]) + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]) .with_test_programs() .with_account_owned_by_burner_program(); let program_id = Program::burner().id(); @@ -1130,7 +1126,7 @@ pub mod tests { let recipient_keys = test_private_account_keys_1(); let mut state = - V03State::new_with_genesis_accounts(&[(sender_keys.account_id(), 200)], &[]); + V03State::new_with_genesis_accounts(&[(sender_keys.account_id(), 200)], vec![]); let balance_to_move = 37; @@ -1178,7 +1174,7 @@ pub mod tests { }; let recipient_keys = test_private_account_keys_2(); - let mut state = V03State::new_with_genesis_accounts(&[], &[]) + let mut state = V03State::new_with_genesis_accounts(&[], vec![]) .with_private_account(&sender_keys, &sender_private_account); let balance_to_move = 37; @@ -1247,7 +1243,7 @@ pub mod tests { let recipient_initial_balance = 400; let mut state = V03State::new_with_genesis_accounts( &[(recipient_keys.account_id(), recipient_initial_balance)], - &[], + vec![], ) .with_private_account(&sender_keys, &sender_private_account); @@ -2199,7 +2195,7 @@ pub mod tests { }; let recipient_keys = test_private_account_keys_2(); - let mut state = V03State::new_with_genesis_accounts(&[], &[]) + let mut state = V03State::new_with_genesis_accounts(&[], vec![]) .with_private_account(&sender_keys, &sender_private_account); let balance_to_move = 37; @@ -2284,7 +2280,7 @@ pub mod tests { let initial_balance = 100; let initial_data = [(from, initial_balance)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let to_key = PrivateKey::try_new([2; 32]).unwrap(); let to = AccountId::from(&PublicKey::new_from_private_key(&to_key)); let amount: u128 = 37; @@ -2322,7 +2318,7 @@ pub mod tests { let program = Program::authenticated_transfer_program(); let account_key = PrivateKey::try_new([9; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&account_key)); - let mut state = V03State::new_with_genesis_accounts(&[], &[]); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]); assert_eq!(state.get_account_by_id(account_id), Account::default()); @@ -2343,7 +2339,7 @@ pub mod tests { let program = Program::authenticated_transfer_program(); let account_key = PrivateKey::try_new([10; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&account_key)); - let mut state = V03State::new_with_genesis_accounts(&[], &[]); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]); assert_eq!(state.get_account_by_id(account_id), Account::default()); @@ -2378,7 +2374,7 @@ pub mod tests { let initial_balance = 1000; let initial_data = [(from, initial_balance), (to, 0)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let from_key = key; let amount: u128 = 37; let instruction: (u128, ProgramId, u32, Option) = ( @@ -2423,7 +2419,7 @@ pub mod tests { let initial_balance = 100; let initial_data = [(from, initial_balance), (to, 0)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let from_key = key; let amount: u128 = 0; let instruction: (u128, ProgramId, u32, Option) = ( @@ -2461,7 +2457,7 @@ pub mod tests { let initial_balance = 1000; let initial_data = [(from, initial_balance), (to, 0)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let amount: u128 = 58; let instruction: (u128, ProgramId, u32, Option) = ( amount, @@ -2507,7 +2503,7 @@ pub mod tests { let initial_balance = 100; let initial_data = [(from, initial_balance)]; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let to_key = PrivateKey::try_new([2; 32]).unwrap(); let to = AccountId::from(&PublicKey::new_from_private_key(&to_key)); let amount: u128 = 37; @@ -2584,7 +2580,7 @@ pub mod tests { let sender_init_nullifier = Nullifier::for_account_initialization(&sender_keys.npk()); let mut state = V03State::new_with_genesis_accounts( &[], - &[(sender_init_nullifier, sender_commitment.clone())], + vec![(sender_commitment.clone(), sender_init_nullifier)], ); let sender_pre = AccountWithMetadata::new(sender_private_account, true, &sender_keys.npk()); let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap(); @@ -2669,9 +2665,9 @@ pub mod tests { let to_init_nullifier = Nullifier::for_account_initialization(&to_keys.npk()); let mut state = V03State::new_with_genesis_accounts( &[], - &[ - (from_init_nullifier, from_commitment.clone()), - (to_init_nullifier, to_commitment.clone()), + vec![ + (from_commitment.clone(), from_init_nullifier), + (to_commitment.clone(), to_init_nullifier), ], ) .with_test_programs(); @@ -2779,7 +2775,7 @@ pub mod tests { ..Account::default() }; - let mut state = V03State::new_with_genesis_accounts(&[], &[]); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]); state.add_pinata_token_program(pinata_definition_id); // Set up the token accounts directly (bypassing public transactions which @@ -2851,7 +2847,7 @@ pub mod tests { #[test] fn claiming_mechanism_cannot_claim_initialied_accounts() { let claimer = Program::claimer(); - let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]).with_test_programs(); let account_id = AccountId::new([2; 32]); // Insert an account with non-default program owner @@ -2891,7 +2887,7 @@ pub mod tests { (sender_id, sender_init_balance), (recipient_id, recipient_init_balance), ], - &[], + vec![], ); state.insert_program(Program::modified_transfer_program()); @@ -2941,7 +2937,7 @@ pub mod tests { #[test] fn private_authorized_uninitialized_account() { - let mut state = V03State::new_with_genesis_accounts(&[], &[]); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]); // Set up keys for the authorized private account let private_keys = test_private_account_keys_1(); @@ -2993,7 +2989,7 @@ pub mod tests { #[test] fn private_unauthorized_uninitialized_account_can_still_be_claimed() { - let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]).with_test_programs(); let private_keys = test_private_account_keys_1(); // This is intentional: claim authorization was introduced to protect public accounts, @@ -3040,7 +3036,7 @@ pub mod tests { #[test] fn private_account_claimed_then_used_without_init_flag_should_fail() { - let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]).with_test_programs(); // Set up keys for the private account let private_keys = test_private_account_keys_1(); @@ -3121,7 +3117,7 @@ pub mod tests { fn public_changer_claimer_no_data_change_no_claim_succeeds() { let initial_data = []; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let account_id = AccountId::new([1; 32]); let program_id = Program::changer_claimer().id(); // Don't change data (None) and don't claim (false) @@ -3145,7 +3141,7 @@ pub mod tests { fn public_changer_claimer_data_change_no_claim_fails() { let initial_data = []; let mut state = - V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let account_id = AccountId::new([1; 32]); let program_id = Program::changer_claimer().id(); // Change data but don't claim (false) - should fail @@ -3242,7 +3238,7 @@ pub mod tests { let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_keys.npk()); let state = V03State::new_with_genesis_accounts( &[(sender_account.account_id, sender_account.account.balance)], - &[(recipient_init_nullifier, recipient_commitment.clone())], + vec![(recipient_commitment.clone(), recipient_init_nullifier)], ) .with_test_programs(); @@ -3292,7 +3288,7 @@ pub mod tests { let validity_window_program = Program::validity_window(); let account_keys = test_public_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id()); - let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]).with_test_programs(); let tx = { let account_ids = vec![pre.account_id]; let nonces = vec![]; @@ -3344,7 +3340,7 @@ pub mod tests { let validity_window_program = Program::validity_window(); let account_keys = test_public_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id()); - let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]).with_test_programs(); let tx = { let account_ids = vec![pre.account_id]; let nonces = vec![]; @@ -3397,7 +3393,7 @@ pub mod tests { let validity_window_program = Program::validity_window(); let account_keys = test_private_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk()); - let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]).with_test_programs(); let tx = { let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk()); @@ -3466,7 +3462,7 @@ pub mod tests { let validity_window_program = Program::validity_window(); let account_keys = test_private_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk()); - let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + let mut state = V03State::new_with_genesis_accounts(&[], vec![]).with_test_programs(); let tx = { let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk()); @@ -3520,7 +3516,7 @@ pub mod tests { let account_id_1 = AccountId::new([1; 32]); let account_id_2 = AccountId::new([2; 32]); let initial_data = [(account_id_1, 100_u128), (account_id_2, 151_u128)]; - let state = V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let state = V03State::new_with_genesis_accounts(&initial_data, vec![]).with_test_programs(); let bytes = borsh::to_vec(&state).unwrap(); let state_from_bytes: V03State = borsh::from_slice(&bytes).unwrap(); assert_eq!(state, state_from_bytes); diff --git a/programs/amm/src/tests.rs b/programs/amm/src/tests.rs index 744b4cb7..bcf493a2 100644 --- a/programs/amm/src/tests.rs +++ b/programs/amm/src/tests.rs @@ -3036,7 +3036,7 @@ fn new_definition_lp_symmetric_amounts() { fn state_for_amm_tests() -> V03State { let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]); state.force_insert_account( IdForExeTests::pool_definition_id(), AccountsForExeTests::pool_definition_init(), @@ -3079,7 +3079,7 @@ fn state_for_amm_tests() -> V03State { fn state_for_amm_tests_with_new_def() -> V03State { let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]); + let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![]); state.force_insert_account( IdForExeTests::token_a_definition_id(), AccountsForExeTests::token_a_definition_account(), diff --git a/sequencer/core/src/block_store.rs b/sequencer/core/src/block_store.rs index 9c4c875a..abb12f1d 100644 --- a/sequencer/core/src/block_store.rs +++ b/sequencer/core/src/block_store.rs @@ -150,7 +150,7 @@ mod tests { let retrieved_tx = node_store.get_transaction_by_hash(tx.hash()); assert_eq!(None, retrieved_tx); // Add the block with the transaction - let dummy_state = V03State::new_with_genesis_accounts(&[], &[]); + let dummy_state = V03State::new_with_genesis_accounts(&[], vec![]); node_store.update(&block, [1; 32], &dummy_state).unwrap(); // Try again let retrieved_tx = node_store.get_transaction_by_hash(tx.hash()); @@ -209,7 +209,7 @@ mod tests { let block_hash = block.header.hash; let block_msg_id = [1; 32]; - let dummy_state = V03State::new_with_genesis_accounts(&[], &[]); + let dummy_state = V03State::new_with_genesis_accounts(&[], vec![]); node_store .update(&block, block_msg_id, &dummy_state) .unwrap(); @@ -244,7 +244,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(1, None, vec![tx]); let block_id = block.header.block_id; - let dummy_state = V03State::new_with_genesis_accounts(&[], &[]); + let dummy_state = V03State::new_with_genesis_accounts(&[], vec![]); node_store.update(&block, [1; 32], &dummy_state).unwrap(); // Verify initial status is Pending diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index 418ff2d4..ee4d7ba8 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -105,7 +105,7 @@ impl SequencerCore, + Vec<(nssa_core::Commitment, nssa_core::Nullifier)>, > = config.initial_private_accounts.clone().map(|accounts| { accounts .iter() @@ -118,8 +118,8 @@ impl SequencerCore SequencerCore Vec { #[must_use] pub fn initial_state() -> V03State { - let initial_private_accounts: Vec<(nssa_core::Nullifier, nssa_core::Commitment)> = + let initial_private_accounts: Vec<(nssa_core::Commitment, nssa_core::Nullifier)> = initial_commitments() .iter() .map(|init_comm_data| { @@ -207,8 +207,8 @@ pub fn initial_state() -> V03State { acc.program_owner = nssa::program::Program::authenticated_transfer_program().id(); ( - nssa_core::Nullifier::for_account_initialization(npk), nssa_core::Commitment::new(npk, &acc), + nssa_core::Nullifier::for_account_initialization(npk), ) }) .collect(); @@ -218,7 +218,7 @@ pub fn initial_state() -> V03State { .map(|acc_data| (acc_data.account_id, acc_data.balance)) .collect(); - nssa::V03State::new_with_genesis_accounts(&init_accs, &initial_private_accounts) + nssa::V03State::new_with_genesis_accounts(&init_accs, initial_private_accounts) } #[must_use] From 5c3ee030a69bf3dc497a69da238146bd46fb81e4 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 7 Apr 2026 16:47:51 -0300 Subject: [PATCH 3/3] add test. Add --all-features to CI tests so that tests under the mock feature are also run --- .github/workflows/ci.yml | 2 +- sequencer/core/src/lib.rs | 72 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b327aaae..26d826b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,7 +130,7 @@ jobs: env: RISC0_DEV_MODE: "1" RUST_LOG: "info" - run: cargo nextest run --workspace --exclude integration_tests + run: cargo nextest run --workspace --exclude integration_tests --all-features integration-tests: runs-on: ubuntu-latest diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index 8fbcba7e..cbf8e910 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -1062,4 +1062,76 @@ mod tests { "Block production should abort when clock account data is corrupted" ); } + + #[tokio::test] + async fn genesis_private_account_cannot_be_re_initialized() { + use common::transaction::NSSATransaction; + use nssa::{ + Account, + privacy_preserving_transaction::{ + PrivacyPreservingTransaction, circuit::execute_and_prove, message::Message, + witness_set::WitnessSet, + }, + program::Program, + }; + use nssa_core::{ + SharedSecretKey, + account::AccountWithMetadata, + encryption::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey}, + }; + use testnet_initial_state::PrivateAccountPublicInitialData; + + let nsk: nssa_core::NullifierSecretKey = [7; 32]; + let npk = nssa_core::NullifierPublicKey::from(&nsk); + let vsk: EphemeralSecretKey = [8; 32]; + let vpk = ViewingPublicKey::from_scalar(vsk); + + let genesis_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + ..Account::default() + }; + + // Start a sequencer from config with a preconfigured private genesis account + let mut config = setup_sequencer_config(); + config.initial_private_accounts = Some(vec![PrivateAccountPublicInitialData { + npk: npk.clone(), + account: genesis_account, + }]); + + let (mut sequencer, _mempool_handle) = + SequencerCoreWithMockClients::start_from_config(config).await; + + // Attempt to re-initialize the same genesis account via a privacy-preserving transaction + let esk = [9; 32]; + let shared_secret = SharedSecretKey::new(&esk, &vpk); + let epk = EphemeralPublicKey::from_scalar(esk); + + let (output, proof) = execute_and_prove( + vec![AccountWithMetadata::new(Account::default(), true, &npk)], + Program::serialize_instruction(0_u128).unwrap(), + vec![1], + vec![(npk.clone(), shared_secret)], + vec![nsk], + vec![None], + &Program::authenticated_transfer_program().into(), + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![], vec![], vec![(npk, vpk, epk)], output) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = NSSATransaction::PrivacyPreserving(PrivacyPreservingTransaction::new( + message, + witness_set, + )); + + let result = tx.execute_check_on_state(&mut sequencer.state, 2, 0); + + assert!( + result.is_err_and(|e| e.to_string().contains("Nullifier already seen")), + "re-initializing a genesis private account must be rejected by the sequencer" + ); + } }