diff --git a/Cargo.lock b/Cargo.lock index 4c7b27ad..5f0b29d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7146,6 +7146,7 @@ dependencies = [ "borsh", "bytesize", "chrono", + "clock_core", "common", "futures", "humantime-serde", diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 32612069..1c7fb30c 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/associated_token_account.bin b/artifacts/program_methods/associated_token_account.bin index d8cfd622..4e6695c4 100644 Binary files a/artifacts/program_methods/associated_token_account.bin and b/artifacts/program_methods/associated_token_account.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index 04183a9a..dcd1b434 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/clock.bin b/artifacts/program_methods/clock.bin index 189830ce..677c569d 100644 Binary files a/artifacts/program_methods/clock.bin and b/artifacts/program_methods/clock.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index de89d40a..6fccf4d6 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index 2d91aebc..b6d97937 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index 71f2c5c4..e846c2f9 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index b96d7188..d445f5bf 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/test_program_methods/clock_chain_caller.bin b/artifacts/test_program_methods/clock_chain_caller.bin new file mode 100644 index 00000000..acb95f85 Binary files /dev/null and b/artifacts/test_program_methods/clock_chain_caller.bin differ diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 8388129e..fbf09833 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -47,17 +47,6 @@ impl PrivacyPreservingTransaction { .collect() } - /// Returns the post-state the transaction declares for `account_id`, or `None` if the account - /// is not part of this transaction's public execution. - #[must_use] - pub fn public_post_state_for(&self, account_id: &AccountId) -> Option<&Account> { - self.message - .public_account_ids - .iter() - .position(|id| id == account_id) - .map(|i| &self.message.public_post_states[i]) - } - #[must_use] pub fn affected_public_account_ids(&self) -> Vec { let mut acc_set = self diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 65f8acc9..5ab87fa1 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -68,7 +68,7 @@ pub mod tests { error::NssaError, program::Program, public_transaction::{Message, WitnessSet}, - state_diff::ValidatedStateDiff, + validated_state_diff::ValidatedStateDiff, }; fn keys_for_tests() -> (PrivateKey, PrivateKey, AccountId, AccountId) { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 6180e0d4..d78d4eec 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -186,19 +186,20 @@ impl V03State { } pub fn apply_state_diff(&mut self, diff: ValidatedStateDiff) { + let (signer_account_ids, public_diff, new_commitments, new_nullifiers, program) = diff.into_parts(); #[expect( clippy::iter_over_hash_type, reason = "Iteration order doesn't matter here" )] - for (account_id, account) in diff.public_diff { + for (account_id, account) in public_diff { *self.get_account_by_id_mut(account_id) = account; } - for account_id in diff.signer_account_ids { + for account_id in signer_account_ids { self.get_account_by_id_mut(account_id).nonce.public_account_nonce_increment(); } - self.private_state.0.extend(&diff.new_commitments); - self.private_state.1.extend(&diff.new_nullifiers); - if let Some(program) = diff.program { + self.private_state.0.extend(&new_commitments); + self.private_state.1.extend(&new_nullifiers); + if let Some(program) =program { self.insert_program(program); } } diff --git a/nssa/src/validated_state_diff.rs b/nssa/src/validated_state_diff.rs index c3fd573b..fd9de432 100644 --- a/nssa/src/validated_state_diff.rs +++ b/nssa/src/validated_state_diff.rs @@ -29,11 +29,11 @@ use crate::{ /// Can only be constructed by the transaction validation functions inside this crate, ensuring the /// diff has been cryptographically checked before any state mutation occurs. pub struct ValidatedStateDiff { - pub(crate) signer_account_ids: Vec, - pub(crate) public_diff: HashMap, - pub(crate) new_commitments: Vec, - pub(crate) new_nullifiers: Vec, - pub(crate) program: Option, + signer_account_ids: Vec, + public_diff: HashMap, + new_commitments: Vec, + new_nullifiers: Vec, + program: Option, } impl ValidatedStateDiff { @@ -385,9 +385,19 @@ impl ValidatedStateDiff { /// Used by callers (e.g. the sequencer) to inspect the diff before committing it, for example /// to enforce that system accounts are not modified by user transactions. #[must_use] - pub fn public_diff(&self) -> &HashMap { - &self.public_diff + pub fn public_diff(&self) -> HashMap { + self.public_diff.clone() } + + pub(crate) fn into_parts(self) -> ( + Vec, + HashMap, + Vec, + Vec, + Option, + ) { + (self.signer_account_ids, self.public_diff, self.new_commitments, self.new_nullifiers, self.program) + } } fn check_privacy_preserving_circuit_proof_is_valid( diff --git a/sequencer/core/Cargo.toml b/sequencer/core/Cargo.toml index 8e16ecb4..a02420bd 100644 --- a/sequencer/core/Cargo.toml +++ b/sequencer/core/Cargo.toml @@ -41,3 +41,4 @@ mock = [] [dev-dependencies] futures.workspace = true test_program_methods.workspace = true +clock_core.workspace = true diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index 41986b93..592dd5c6 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -450,6 +450,8 @@ mod tests { use common::{test_utils::sequencer_sign_key_for_testing, transaction::NSSATransaction}; use logos_blockchain_core::mantle::ops::channel::ChannelId; use mempool::MemPoolHandle; + use nssa::{execute_and_prove, program::Program}; + use nssa_core::account::AccountWithMetadata; use testnet_initial_state::{initial_accounts, initial_pub_accounts_private_keys}; use crate::{ @@ -979,50 +981,6 @@ mod tests { ); } - #[tokio::test] - async fn privacy_preserving_tx_touching_clock_account_is_dropped() { - let (mut sequencer, mempool_handle) = common_setup().await; - - // Craft a PP transaction that declares a modified post-state for a clock account. - let crafted_pp_tx = { - let message = nssa::privacy_preserving_transaction::Message { - public_account_ids: vec![nssa::CLOCK_01_PROGRAM_ACCOUNT_ID], - nonces: vec![], - public_post_states: vec![nssa::Account::default()], - encrypted_private_post_states: vec![], - new_commitments: vec![nssa_core::Commitment::new([0_u8; 32])], // required: at least one commitment or nullifier - new_nullifiers: vec![], - block_validity_window: (..).into(), - timestamp_validity_window: (..).into(), - }; - let witness_set = nssa::privacy_preserving_transaction::WitnessSet::from_raw_parts( - vec![], - nssa::privacy_preserving_transaction::circuit::Proof::from_inner(vec![]), - ); - NSSATransaction::PrivacyPreserving(nssa::PrivacyPreservingTransaction::new( - message, - witness_set, - )) - }; - - mempool_handle.push(crafted_pp_tx).await.unwrap(); - sequencer - .produce_new_block_with_mempool_transactions() - .unwrap(); - - let block = sequencer - .store - .get_block_at_id(sequencer.chain_height) - .unwrap() - .unwrap(); - - // The PP tx was dropped. Only the system-appended clock tx remains. - assert_eq!( - block.body.transactions, - vec![NSSATransaction::clock_invocation(block.header.timestamp)] - ); - } - #[tokio::test] async fn start_from_config_uses_db_height_not_config_genesis() { let mut config = setup_sequencer_config(); diff --git a/test_program_methods/guest/src/bin/clock_chain_caller.rs b/test_program_methods/guest/src/bin/clock_chain_caller.rs new file mode 100644 index 00000000..913014c2 --- /dev/null +++ b/test_program_methods/guest/src/bin/clock_chain_caller.rs @@ -0,0 +1,35 @@ +use nssa_core::program::{ + AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_nssa_inputs, +}; +use risc0_zkvm::serde::to_vec; + +type Instruction = (ProgramId, u64); // (clock_program_id, timestamp) + +/// A program that chain-calls the clock program with the clock accounts it received as pre-states. +/// Used in tests to verify that user transactions cannot modify clock accounts, even indirectly +/// via chain calls. +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (clock_program_id, timestamp), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let post_states: Vec<_> = pre_states + .iter() + .map(|pre| AccountPostState::new(pre.account.clone())) + .collect(); + + let chained_call = ChainedCall { + program_id: clock_program_id, + instruction_data: to_vec(×tamp).unwrap(), + pre_states: pre_states.clone(), + pda_seeds: vec![], + }; + + ProgramOutput::new(instruction_words, pre_states, post_states) + .with_chained_calls(vec![chained_call]) + .write(); +}