From 70b31856e9a86cc8655dcbae1553466e0aa84eff Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 28 Jul 2025 15:21:35 -0300 Subject: [PATCH 01/10] wip --- sequencer_core/src/lib.rs | 47 +++++++++++++++++-- .../src/sequencer_store/accounts_store.rs | 26 +++++++++- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index bfde1f6..ad4d551 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -262,15 +262,15 @@ impl SequencerCore { self.store.block_store.put_block_at_id(block)?; - self.chain_height += 1; + self.chain_height = new_block_height; - Ok(self.chain_height - 1) + Ok(self.chain_height) } } #[cfg(test)] mod tests { - use crate::config::AccountInitialData; + use crate::{config::AccountInitialData, sequencer_store::accounts_store}; use super::*; use std::path::PathBuf; @@ -521,6 +521,7 @@ mod tests { fn test_produce_new_block_with_mempool_transactions() { let config = setup_sequencer_config(); let mut sequencer = SequencerCore::start_from_config(config); + let genesis_height = sequencer.chain_height; let tx = create_dummy_transaction(vec![[94; 32]], vec![[7; 32]], vec![[8; 32]]); let tx_mempool = MempoolTransaction { @@ -530,6 +531,44 @@ mod tests { let block_id = sequencer.produce_new_block_with_mempool_transactions(); assert!(block_id.is_ok()); - assert_eq!(block_id.unwrap(), 1); + assert_eq!(block_id.unwrap(), genesis_height + 1); + } + + #[test] + fn test_replay_transactions_are_rejected() { + let config = setup_sequencer_config(); + let mut sequencer = SequencerCore::start_from_config(config); + + let tx = create_dummy_transaction(vec![[94; 32]], vec![[7; 32]], vec![[8; 32]]); + + // The transaction should be included the first time + let tx_mempool_original = MempoolTransaction { + auth_tx: tx.clone().into_authenticated().unwrap(), + }; + sequencer.mempool.push_item(tx_mempool_original); + let current_height = sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); + let block = sequencer + .store + .block_store + .get_block_at_id(current_height) + .unwrap(); + assert_eq!(block.transactions, vec![tx.clone()]); + + // Add same transaction should fail + let tx_mempool_replay = MempoolTransaction { + auth_tx: tx.into_authenticated().unwrap(), + }; + sequencer.mempool.push_item(tx_mempool_replay); + let current_height = sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); + let block = sequencer + .store + .block_store + .get_block_at_id(current_height) + .unwrap(); + assert!(block.transactions.is_empty()); } } diff --git a/sequencer_core/src/sequencer_store/accounts_store.rs b/sequencer_core/src/sequencer_store/accounts_store.rs index 7d64491..e955a9f 100644 --- a/sequencer_core/src/sequencer_store/accounts_store.rs +++ b/sequencer_core/src/sequencer_store/accounts_store.rs @@ -7,18 +7,28 @@ use std::collections::HashMap; pub(crate) struct AccountPublicData { pub balance: u64, pub address: AccountAddress, + nonce: u64, } impl AccountPublicData { pub fn new(address: AccountAddress) -> Self { Self { balance: 0, + nonce: 0, address, } } fn new_with_balance(address: AccountAddress, balance: u64) -> Self { - Self { balance, address } + Self { + balance, + address, + nonce: 0, + } + } + + fn nonce(&self) -> u64 { + self.nonce } } @@ -110,6 +120,13 @@ mod tests { assert_eq!(new_acc.address, [1; 32]); } + #[test] + fn test_zero_nonce_account_data_creation() { + let new_acc = AccountPublicData::new([1; 32]); + + assert_eq!(new_acc.nonce, 0); + } + #[test] fn test_non_zero_balance_account_data_creation() { let new_acc = AccountPublicData::new_with_balance([1; 32], 10); @@ -118,6 +135,13 @@ mod tests { assert_eq!(new_acc.address, [1; 32]); } + #[test] + fn test_zero_nonce_account_data_creation_with_balance() { + let new_acc = AccountPublicData::new_with_balance([1; 32], 10); + + assert_eq!(new_acc.nonce, 0); + } + #[test] fn default_account_sequencer_store() { let seq_acc_store = SequencerAccountsStore::default(); From f21c0df652d01a264769ff37b2f905875bbacbde Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 28 Jul 2025 16:09:03 -0300 Subject: [PATCH 02/10] wip: added nonces and test --- common/src/transaction.rs | 3 +++ node_core/src/chain_storage/mod.rs | 1 + node_core/src/lib.rs | 8 ++++++++ sc_core/src/transaction_payloads_tools.rs | 2 ++ sequencer_core/src/lib.rs | 17 ++++++++++++++++- .../src/sequencer_store/accounts_store.rs | 13 +++++++++++++ .../src/sequencer_store/block_store.rs | 1 + sequencer_rpc/src/process.rs | 1 + 8 files changed, 45 insertions(+), 1 deletion(-) diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 78e1930..fd8af6e 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -62,6 +62,8 @@ pub struct TransactionBody { /// /// First value represents vector of changes, second is new length of a state pub state_changes: (serde_json::Value, usize), + + pub nonce: u64, } #[derive(Debug, Serialize, Deserialize)] @@ -322,6 +324,7 @@ mod tests { secret_r: [8; 32], sc_addr: "someAddress".to_string(), state_changes: (serde_json::Value::Null, 10), + nonce: 1, } } diff --git a/node_core/src/chain_storage/mod.rs b/node_core/src/chain_storage/mod.rs index 126574f..017f9cc 100644 --- a/node_core/src/chain_storage/mod.rs +++ b/node_core/src/chain_storage/mod.rs @@ -337,6 +337,7 @@ mod tests { secret_r: [0; 32], sc_addr: "sc_addr".to_string(), state_changes: (serde_json::Value::Null, 0), + nonce: 1, }; Transaction::new(body, SignaturePrivateKey::random(&mut rng)) } diff --git a/node_core/src/lib.rs b/node_core/src/lib.rs index a8ca0b9..ea571f1 100644 --- a/node_core/src/lib.rs +++ b/node_core/src/lib.rs @@ -274,6 +274,7 @@ impl NodeCore { secret_r, sc_addr, state_changes, + nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -370,6 +371,7 @@ impl NodeCore { secret_r, sc_addr, state_changes, + nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -484,6 +486,7 @@ impl NodeCore { secret_r, sc_addr, state_changes, + nonce: 1 }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -629,6 +632,7 @@ impl NodeCore { secret_r, sc_addr, state_changes, + nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -757,6 +761,7 @@ impl NodeCore { secret_r, sc_addr, state_changes, + nonce: 1 }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -846,6 +851,7 @@ impl NodeCore { secret_r, sc_addr, state_changes, + nonce: 1 }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -992,6 +998,7 @@ impl NodeCore { secret_r, sc_addr, state_changes, + 1, ); tx.log(); @@ -1548,6 +1555,7 @@ impl NodeCore { secret_r, sc_addr, state_changes, + nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); diff --git a/sc_core/src/transaction_payloads_tools.rs b/sc_core/src/transaction_payloads_tools.rs index fa0e28e..f8d075d 100644 --- a/sc_core/src/transaction_payloads_tools.rs +++ b/sc_core/src/transaction_payloads_tools.rs @@ -15,6 +15,7 @@ pub fn create_public_transaction_payload( secret_r: [u8; 32], sc_addr: String, state_changes: (serde_json::Value, usize), + nonce: u64 ) -> TransactionBody { TransactionBody { tx_kind: TxKind::Public, @@ -31,6 +32,7 @@ pub fn create_public_transaction_payload( secret_r, sc_addr, state_changes, + nonce, } } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index cacf01b..6e7165a 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -41,6 +41,7 @@ pub enum TransactionMalformationErrorKind { InvalidSignature, IncorrectSender, BalanceMismatch { tx: TreeHashType }, + NonceMismatch { tx: TreeHashType }, FailedToDecode { tx: TreeHashType }, } @@ -223,11 +224,21 @@ impl SequencerCore { ref utxo_commitments_created_hashes, ref nullifier_created_hashes, execution_input, + nonce, .. } = mempool_tx.auth_tx.transaction().body(); let tx_hash = *mempool_tx.auth_tx.hash(); + // Nonce check + let mut signer_addres = [0; 32]; + let mut keccak_hasher = Keccak::v256(); + keccak_hasher.update(&mempool_tx.auth_tx.transaction().public_key.to_sec1_bytes()); + keccak_hasher.finalize(&mut signer_addres); + if self.store.acc_store.get_account_nonce(&signer_addres) != *nonce { + return Err(TransactionMalformationErrorKind::NonceMismatch { tx: tx_hash }); + } + //Balance check if let Ok(native_transfer_action) = serde_json::from_slice::(execution_input) @@ -250,6 +261,9 @@ impl SequencerCore { &native_transfer_action.to, to_balance + native_transfer_action.balance_to_move, ); + + // Update nonce + let _new_nonce = self.store.acc_store.increase_nonce(&signer_addres); } else { return Err(TransactionMalformationErrorKind::BalanceMismatch { tx: tx_hash }); } @@ -287,7 +301,7 @@ impl SequencerCore { .pop_size(self.sequencer_config.max_num_tx_in_block); for tx in &transactions { - self.execute_check_transaction_on_state(tx)?; + self.execute_check_transaction_on_state(tx); } let prev_block_hash = self @@ -612,6 +626,7 @@ mod tests { secret_r: [0; 32], sc_addr: "sc_addr".to_string(), state_changes: (serde_json::Value::Null, 0), + nonce: 0, }; Transaction::new(body, SignaturePrivateKey::random(&mut rng)) } diff --git a/sequencer_core/src/sequencer_store/accounts_store.rs b/sequencer_core/src/sequencer_store/accounts_store.rs index f522202..63da3f5 100644 --- a/sequencer_core/src/sequencer_store/accounts_store.rs +++ b/sequencer_core/src/sequencer_store/accounts_store.rs @@ -74,6 +74,13 @@ impl SequencerAccountsStore { .unwrap_or(0) } + pub fn get_account_nonce(&self, account_addr: &AccountAddress) -> u64 { + self.accounts + .get(account_addr) + .map(|acc| acc.nonce) + .unwrap_or(0) + } + ///Update `account_addr` balance, /// /// returns 0, if account address not found, otherwise returns previous balance @@ -91,6 +98,12 @@ impl SequencerAccountsStore { .unwrap_or(0) } + pub fn increase_nonce(&mut self, account_addr: &AccountAddress) -> Option { + let acc_data = self.accounts.get_mut(account_addr)?; + acc_data.nonce += 1; + Some(acc_data.nonce) + } + ///Remove account from storage /// /// Fails, if `balance` is != 0 diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index dbefdc2..aabc315 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -95,6 +95,7 @@ mod tests { secret_r: Default::default(), sc_addr: Default::default(), state_changes: Default::default(), + nonce: 1 }; let tx = Transaction::new(body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap()); ( diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 41d2261..7d8240e 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -506,6 +506,7 @@ mod tests { secret_r: Default::default(), sc_addr: Default::default(), state_changes: Default::default(), + nonce: 1, }; let tx = Transaction::new(tx_body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap()); From 76a023c33a26a90a2a98fb6da1dd504389dfee2a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 29 Jul 2025 12:51:33 -0300 Subject: [PATCH 03/10] remove unused nonce method --- sequencer_core/src/sequencer_store/accounts_store.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sequencer_core/src/sequencer_store/accounts_store.rs b/sequencer_core/src/sequencer_store/accounts_store.rs index 21131ed..e387d51 100644 --- a/sequencer_core/src/sequencer_store/accounts_store.rs +++ b/sequencer_core/src/sequencer_store/accounts_store.rs @@ -26,10 +26,6 @@ impl AccountPublicData { nonce: 0, } } - - fn nonce(&self) -> u64 { - self.nonce - } } #[derive(Debug, Clone)] From 4300e87485c1c7d706e4264c59b5e83f7dfa5b17 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 29 Jul 2025 15:22:28 -0300 Subject: [PATCH 04/10] refactor address to avoid code repetition in computing addresses from public keys --- accounts/src/account_core/address.rs | 34 +++++++++ accounts/src/account_core/mod.rs | 23 +++--- accounts/src/key_management/mod.rs | 31 +------- node_core/src/chain_storage/accounts_store.rs | 2 +- node_core/src/chain_storage/mod.rs | 70 +------------------ node_core/src/lib.rs | 2 +- sc_core/src/public_context.rs | 2 +- sequencer_core/src/lib.rs | 15 ++-- .../src/sequencer_store/accounts_store.rs | 2 +- zkvm/src/lib.rs | 2 +- 10 files changed, 60 insertions(+), 123 deletions(-) create mode 100644 accounts/src/account_core/address.rs diff --git a/accounts/src/account_core/address.rs b/accounts/src/account_core/address.rs new file mode 100644 index 0000000..4570dd5 --- /dev/null +++ b/accounts/src/account_core/address.rs @@ -0,0 +1,34 @@ +use common::transaction::SignaturePublicKey; +use tiny_keccak::{Hasher, Keccak}; + +// TODO: Consider wrapping `AccountAddress` in a struct. + +pub type AccountAddress = [u8; 32]; +pub fn from_public_key(public_key: &SignaturePublicKey) -> AccountAddress { + let mut address = [0; 32]; + let mut keccak_hasher = Keccak::v256(); + keccak_hasher.update(&public_key.to_sec1_bytes()); + keccak_hasher.finalize(&mut address); + address +} + +#[cfg(test)] +mod tests { + use common::transaction::SignaturePrivateKey; + + use super::*; + use crate::account_core::address; + + #[test] + fn test_address_key_equal_keccak_pub_sign_key() { + let signing_key = SignaturePrivateKey::from_slice(&[1; 32]).unwrap(); + let public_key = signing_key.verifying_key(); + + let mut expected_address = [0; 32]; + let mut keccak_hasher = Keccak::v256(); + keccak_hasher.update(&public_key.to_sec1_bytes()); + keccak_hasher.finalize(&mut expected_address); + + assert_eq!(expected_address, address::from_public_key(public_key)); + } +} diff --git a/accounts/src/account_core/mod.rs b/accounts/src/account_core/mod.rs index e698b49..76aaee7 100644 --- a/accounts/src/account_core/mod.rs +++ b/accounts/src/account_core/mod.rs @@ -7,14 +7,18 @@ use log::info; use serde::{Deserialize, Serialize}; use utxo::utxo_core::UTXO; -use crate::key_management::{ - constants_types::{CipherText, Nonce}, - ephemeral_key_holder::EphemeralKeyHolder, - AddressKeyHolder, +pub mod address; + +use crate::{ + account_core::address::AccountAddress, + key_management::{ + constants_types::{CipherText, Nonce}, + ephemeral_key_holder::EphemeralKeyHolder, + AddressKeyHolder, + }, }; pub type PublicKey = AffinePoint; -pub type AccountAddress = TreeHashType; #[derive(Clone, Debug)] pub struct Account { @@ -113,7 +117,8 @@ impl AccountPublicMask { impl Account { pub fn new() -> Self { let key_holder = AddressKeyHolder::new_os_random(); - let address = key_holder.address; + let public_key = *key_holder.get_pub_account_signing_key().verifying_key(); + let address = address::from_public_key(&public_key); let balance = 0; let utxos = HashMap::new(); @@ -127,7 +132,8 @@ impl Account { pub fn new_with_balance(balance: u64) -> Self { let key_holder = AddressKeyHolder::new_os_random(); - let address = key_holder.address; + let public_key = *key_holder.get_pub_account_signing_key().verifying_key(); + let address = address::from_public_key(&public_key); let utxos = HashMap::new(); Self { @@ -217,6 +223,8 @@ impl Default for Account { #[cfg(test)] mod tests { + use common::transaction::SignaturePrivateKey; + use super::*; fn generate_dummy_utxo(address: TreeHashType, amount: u128) -> UTXO { @@ -228,7 +236,6 @@ mod tests { let account = Account::new(); assert_eq!(account.balance, 0); - assert!(account.key_holder.address != [0u8; 32]); // Check if the address is not empty } #[test] diff --git a/accounts/src/key_management/mod.rs b/accounts/src/key_management/mod.rs index a5a3e4b..4d51b2b 100644 --- a/accounts/src/key_management/mod.rs +++ b/accounts/src/key_management/mod.rs @@ -1,5 +1,5 @@ use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; -use common::merkle_tree_public::TreeHashType; +use common::{merkle_tree_public::TreeHashType, transaction::SignaturePublicKey}; use constants_types::{CipherText, Nonce}; use elliptic_curve::point::AffineCoordinates; use k256::{ecdsa::SigningKey, AffinePoint, FieldBytes}; @@ -24,7 +24,6 @@ pub struct AddressKeyHolder { top_secret_key_holder: TopSecretKeyHolder, pub utxo_secret_key_holder: UTXOSecretKeyHolder, pub_account_signing_key: PublicAccountSigningKey, - pub address: TreeHashType, pub nullifer_public_key: PublicKey, pub viewing_public_key: PublicKey, } @@ -47,21 +46,9 @@ impl AddressKeyHolder { bytes }; - //Address is a Keccak(verification_key) - let field_bytes = FieldBytes::from_slice(&pub_account_signing_key); - let signing_key = SigningKey::from_bytes(field_bytes).unwrap(); - - let verifying_key = signing_key.verifying_key(); - - let mut address = [0; 32]; - let mut keccak_hasher = Keccak::v256(); - keccak_hasher.update(&verifying_key.to_sec1_bytes()); - keccak_hasher.finalize(&mut address); - Self { top_secret_key_holder, utxo_secret_key_holder, - address, nullifer_public_key, viewing_public_key, pub_account_signing_key, @@ -214,7 +201,6 @@ mod tests { assert!(!Into::::into( address_key_holder.viewing_public_key.is_identity() )); - assert!(!address_key_holder.address.as_slice().is_empty()); // Assume TreeHashType has non-zero length for a valid address } #[test] @@ -343,21 +329,6 @@ mod tests { ); } - #[test] - fn test_address_key_equal_keccak_pub_sign_key() { - let address_key_holder = AddressKeyHolder::new_os_random(); - let signing_key = address_key_holder.get_pub_account_signing_key(); - - let verifying_key = signing_key.verifying_key(); - - let mut address = [0; 32]; - let mut keccak_hasher = Keccak::v256(); - keccak_hasher.update(&verifying_key.to_sec1_bytes()); - keccak_hasher.finalize(&mut address); - - assert_eq!(address, address_key_holder.address); - } - #[test] fn key_generation_test() { let seed_holder = SeedHolder::new_os_random(); diff --git a/node_core/src/chain_storage/accounts_store.rs b/node_core/src/chain_storage/accounts_store.rs index 1bd6dfc..fa9dc99 100644 --- a/node_core/src/chain_storage/accounts_store.rs +++ b/node_core/src/chain_storage/accounts_store.rs @@ -1,4 +1,4 @@ -use accounts::account_core::{Account, AccountAddress}; +use accounts::account_core::{address::AccountAddress, Account}; use std::collections::HashMap; pub struct NodeAccountsStore { diff --git a/node_core/src/chain_storage/mod.rs b/node_core/src/chain_storage/mod.rs index 2ef49a1..bc0e37f 100644 --- a/node_core/src/chain_storage/mod.rs +++ b/node_core/src/chain_storage/mod.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, HashMap, HashSet}; -use accounts::account_core::{Account, AccountAddress}; +use accounts::account_core::{address::AccountAddress, Account}; use anyhow::Result; use block_store::NodeBlockStore; use common::{ @@ -342,40 +342,6 @@ mod tests { ], "balance": 100, "key_holder": { - "address": [ - 244, - 55, - 238, - 205, - 74, - 115, - 179, - 192, - 65, - 186, - 166, - 169, - 221, - 45, - 6, - 57, - 200, - 65, - 195, - 70, - 118, - 252, - 206, - 100, - 215, - 250, - 72, - 230, - 19, - 71, - 217, - 249 - ], "nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718", "pub_account_signing_key": [ 244, @@ -460,40 +426,6 @@ mod tests { ], "balance": 200, "key_holder": { - "address": [ - 72, - 169, - 70, - 237, - 1, - 96, - 35, - 157, - 25, - 15, - 83, - 18, - 52, - 206, - 202, - 63, - 48, - 59, - 173, - 76, - 78, - 7, - 254, - 229, - 28, - 45, - 194, - 79, - 6, - 89, - 58, - 85 - ], "nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271", "pub_account_signing_key": [ 136, diff --git a/node_core/src/lib.rs b/node_core/src/lib.rs index c2824c0..581d85f 100644 --- a/node_core/src/lib.rs +++ b/node_core/src/lib.rs @@ -8,7 +8,7 @@ use common::{ }; use accounts::{ - account_core::{Account, AccountAddress}, + account_core::{address::AccountAddress, Account}, key_management::ephemeral_key_holder::EphemeralKeyHolder, }; use anyhow::Result; diff --git a/sc_core/src/public_context.rs b/sc_core/src/public_context.rs index 641ce10..cf55f30 100644 --- a/sc_core/src/public_context.rs +++ b/sc_core/src/public_context.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, HashSet}; -use accounts::account_core::{AccountAddress, AccountPublicMask}; +use accounts::account_core::{address::AccountAddress, AccountPublicMask}; use common::merkle_tree_public::{merkle_tree::UTXOCommitmentsMerkleTree, TreeHashType}; use serde::{ser::SerializeStruct, Serialize}; diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 22554ca..c612514 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use accounts::account_core::AccountAddress; +use accounts::account_core::address::{self, AccountAddress}; use anyhow::Result; use common::{ block::{Block, HashableBlockData}, @@ -15,7 +15,6 @@ use mempool::MemPool; use mempool_transaction::MempoolTransaction; use sequencer_store::SequecerChainStore; use serde::{Deserialize, Serialize}; -use tiny_keccak::{Hasher, Keccak}; pub mod config; pub mod mempool_transaction; @@ -147,13 +146,10 @@ impl SequencerCore { if let Ok(native_transfer_action) = serde_json::from_slice::(execution_input) { - let mut output = [0; 32]; - let mut keccak_hasher = Keccak::v256(); - keccak_hasher.update(&tx.transaction().public_key.to_sec1_bytes()); - keccak_hasher.finalize(&mut output); + let signer_address = address::from_public_key(&tx.transaction().public_key); //Correct sender check - if native_transfer_action.from != output { + if native_transfer_action.from != signer_address { return Err(TransactionMalformationErrorKind::IncorrectSender); } } @@ -232,10 +228,7 @@ impl SequencerCore { let tx_hash = *mempool_tx.auth_tx.hash(); // Nonce check - let mut signer_addres = [0; 32]; - let mut keccak_hasher = Keccak::v256(); - keccak_hasher.update(&mempool_tx.auth_tx.transaction().public_key.to_sec1_bytes()); - keccak_hasher.finalize(&mut signer_addres); + let signer_addres = address::from_public_key(&mempool_tx.auth_tx.transaction().public_key); if self.store.acc_store.get_account_nonce(&signer_addres) != *nonce { return Err(TransactionMalformationErrorKind::NonceMismatch { tx: tx_hash }); } diff --git a/sequencer_core/src/sequencer_store/accounts_store.rs b/sequencer_core/src/sequencer_store/accounts_store.rs index e387d51..3e8b35c 100644 --- a/sequencer_core/src/sequencer_store/accounts_store.rs +++ b/sequencer_core/src/sequencer_store/accounts_store.rs @@ -1,4 +1,4 @@ -use accounts::account_core::AccountAddress; +use accounts::account_core::address::AccountAddress; use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/zkvm/src/lib.rs b/zkvm/src/lib.rs index 9f0940c..0fbae70 100644 --- a/zkvm/src/lib.rs +++ b/zkvm/src/lib.rs @@ -1,4 +1,4 @@ -use accounts::account_core::AccountAddress; +use accounts::account_core::address::AccountAddress; use common::ExecutionFailureKind; use rand::{rngs::OsRng, RngCore}; use risc0_zkvm::{default_executor, default_prover, sha::Digest, ExecutorEnv, Receipt}; From 6ed4369647ec5533c6833e746671900047ec05f0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 29 Jul 2025 15:24:17 -0300 Subject: [PATCH 05/10] clippy --- accounts/src/account_core/mod.rs | 2 -- accounts/src/key_management/mod.rs | 9 ++------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/accounts/src/account_core/mod.rs b/accounts/src/account_core/mod.rs index 76aaee7..1926649 100644 --- a/accounts/src/account_core/mod.rs +++ b/accounts/src/account_core/mod.rs @@ -223,8 +223,6 @@ impl Default for Account { #[cfg(test)] mod tests { - use common::transaction::SignaturePrivateKey; - use super::*; fn generate_dummy_utxo(address: TreeHashType, amount: u128) -> UTXO { diff --git a/accounts/src/key_management/mod.rs b/accounts/src/key_management/mod.rs index 4d51b2b..69a39f7 100644 --- a/accounts/src/key_management/mod.rs +++ b/accounts/src/key_management/mod.rs @@ -1,5 +1,4 @@ use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; -use common::{merkle_tree_public::TreeHashType, transaction::SignaturePublicKey}; use constants_types::{CipherText, Nonce}; use elliptic_curve::point::AffineCoordinates; use k256::{ecdsa::SigningKey, AffinePoint, FieldBytes}; @@ -7,7 +6,6 @@ use log::info; use rand::{rngs::OsRng, RngCore}; use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder}; use serde::{Deserialize, Serialize}; -use tiny_keccak::{Hasher, Keccak}; use crate::account_core::PublicKey; pub type PublicAccountSigningKey = [u8; 32]; @@ -124,7 +122,7 @@ mod tests { use elliptic_curve::point::AffineCoordinates; use k256::{AffinePoint, ProjectivePoint, Scalar}; - use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder; + use crate::{account_core::address, key_management::ephemeral_key_holder::EphemeralKeyHolder}; use super::*; @@ -351,10 +349,7 @@ mod tests { let verifying_key = signing_key.verifying_key(); - let mut address = [0; 32]; - let mut keccak_hasher = Keccak::v256(); - keccak_hasher.update(&verifying_key.to_sec1_bytes()); - keccak_hasher.finalize(&mut address); + let address = address::from_public_key(verifying_key); println!("======Prerequisites======"); println!(); From a1d53ee6f0ac7f59560fb0959328b4fcff7cc4d6 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 29 Jul 2025 15:29:00 -0300 Subject: [PATCH 06/10] use tempdir for tests --- sequencer_core/src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index c612514..efa09a8 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -334,24 +334,20 @@ mod tests { use crate::config::AccountInitialData; use super::*; - use std::path::PathBuf; use common::transaction::{SignaturePrivateKey, Transaction, TransactionBody, TxKind}; use k256::{ecdsa::SigningKey, FieldBytes}; use mempool_transaction::MempoolTransaction; - use rand::Rng; use secp256k1_zkp::Tweak; fn setup_sequencer_config_variable_initial_accounts( initial_accounts: Vec, ) -> SequencerConfig { - let mut rng = rand::thread_rng(); - let random_u8: u8 = rng.gen(); - - let path_str = format!("/tmp/sequencer_{random_u8:?}"); + let tempdir = tempfile::tempdir().unwrap(); + let home = tempdir.path().to_path_buf(); SequencerConfig { - home: PathBuf::from(path_str), + home, override_rust_log: Some("info".to_string()), genesis_id: 1, is_genesis_random: false, From 6a38c2eaa2ba8979a8dcd16b7c517f8071e3586c Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 29 Jul 2025 15:41:57 -0300 Subject: [PATCH 07/10] ad test --- sequencer_core/src/lib.rs | 47 +++++++++++++++++++++++++++++++++++- sequencer_rpc/src/process.rs | 1 - 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index efa09a8..14ced81 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -765,7 +765,52 @@ mod tests { } #[test] - fn test_replay_transactions_are_rejected() { + fn test_replay_transactions_are_rejected_in_the_same_block() { + let config = setup_sequencer_config(); + let mut sequencer = SequencerCore::start_from_config(config); + + common_setup(&mut sequencer); + + let acc1 = hex::decode(sequencer.sequencer_config.initial_accounts[0].addr.clone()) + .unwrap() + .try_into() + .unwrap(); + let acc2 = hex::decode(sequencer.sequencer_config.initial_accounts[1].addr.clone()) + .unwrap() + .try_into() + .unwrap(); + + let sign_key1 = create_signing_key_for_account1(); + + let tx = create_dummy_transaction_native_token_transfer(acc1, 0, acc2, 100, sign_key1); + + let tx_mempool_original = MempoolTransaction { + auth_tx: tx.clone().into_authenticated().unwrap(), + }; + let tx_mempool_replay = MempoolTransaction { + auth_tx: tx.clone().into_authenticated().unwrap(), + }; + + // Pushing two copies of the same tx to the mempool + sequencer.mempool.push_item(tx_mempool_original); + sequencer.mempool.push_item(tx_mempool_replay); + + // Create block + let current_height = sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); + let block = sequencer + .store + .block_store + .get_block_at_id(current_height) + .unwrap(); + + // Only one should be included in the block + assert_eq!(block.transactions, vec![tx.clone()]); + } + + #[test] + fn test_replay_transactions_are_rejected_in_different_blocks() { let config = setup_sequencer_config(); let mut sequencer = SequencerCore::start_from_config(config); diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 3149608..fa89558 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -291,7 +291,6 @@ mod tests { nonce: 0, }; let tx = Transaction::new(tx_body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap()); - println!("{:?}", tx.body().hash()); sequencer_core .push_tx_into_mempool_pre_check(tx, [[0; 32]; 2]) From e9b11af986e1d358a1e2f14d52cc8f4e1f41815f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 29 Jul 2025 16:09:21 -0300 Subject: [PATCH 08/10] move nonce to public execution input --- common/src/execution_input.rs | 1 + common/src/transaction.rs | 3 --- node_core/src/chain_storage/mod.rs | 1 - node_core/src/lib.rs | 10 ++-------- sc_core/src/transaction_payloads_tools.rs | 2 -- sequencer_core/src/lib.rs | 19 ++++++++++--------- .../src/sequencer_store/block_store.rs | 1 - sequencer_rpc/src/process.rs | 6 ++---- 8 files changed, 15 insertions(+), 28 deletions(-) diff --git a/common/src/execution_input.rs b/common/src/execution_input.rs index d1f282a..c4f2141 100644 --- a/common/src/execution_input.rs +++ b/common/src/execution_input.rs @@ -5,6 +5,7 @@ use crate::merkle_tree_public::TreeHashType; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PublicNativeTokenSend { pub from: TreeHashType, + pub nonce: u64, pub to: TreeHashType, pub balance_to_move: u64, } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index eacc5ca..2ba78e9 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -62,8 +62,6 @@ pub struct TransactionBody { /// /// First value represents vector of changes, second is new length of a state pub state_changes: (serde_json::Value, usize), - - pub nonce: u64, } #[derive(Debug, Serialize, Deserialize)] @@ -328,7 +326,6 @@ mod tests { secret_r: [8; 32], sc_addr: "someAddress".to_string(), state_changes: (serde_json::Value::Null, 10), - nonce: 1, } } diff --git a/node_core/src/chain_storage/mod.rs b/node_core/src/chain_storage/mod.rs index bc0e37f..8e36dc5 100644 --- a/node_core/src/chain_storage/mod.rs +++ b/node_core/src/chain_storage/mod.rs @@ -511,7 +511,6 @@ mod tests { secret_r: [0; 32], sc_addr: "sc_addr".to_string(), state_changes: (serde_json::Value::Null, 0), - nonce: 1, }; Transaction::new(body, SignaturePrivateKey::random(&mut rng)) } diff --git a/node_core/src/lib.rs b/node_core/src/lib.rs index 581d85f..8f5d10e 100644 --- a/node_core/src/lib.rs +++ b/node_core/src/lib.rs @@ -274,7 +274,6 @@ impl NodeCore { secret_r, sc_addr, state_changes, - nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -371,7 +370,6 @@ impl NodeCore { secret_r, sc_addr, state_changes, - nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -486,7 +484,6 @@ impl NodeCore { secret_r, sc_addr, state_changes, - nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -632,7 +629,6 @@ impl NodeCore { secret_r, sc_addr, state_changes, - nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -761,7 +757,6 @@ impl NodeCore { secret_r, sc_addr, state_changes, - nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -851,7 +846,6 @@ impl NodeCore { secret_r, sc_addr, state_changes, - nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); @@ -962,6 +956,7 @@ impl NodeCore { pub async fn send_public_native_token_transfer( &self, from: AccountAddress, + nonce: u64, to: AccountAddress, balance_to_move: u64, ) -> Result { @@ -989,6 +984,7 @@ impl NodeCore { sc_core::transaction_payloads_tools::create_public_transaction_payload( serde_json::to_vec(&PublicNativeTokenSend { from, + nonce, to, balance_to_move, }) @@ -998,7 +994,6 @@ impl NodeCore { secret_r, sc_addr, state_changes, - 1, ); tx.log(); @@ -1555,7 +1550,6 @@ impl NodeCore { secret_r, sc_addr, state_changes, - nonce: 1, }; let key_to_sign_transaction = account.key_holder.get_pub_account_signing_key(); diff --git a/sc_core/src/transaction_payloads_tools.rs b/sc_core/src/transaction_payloads_tools.rs index c3aa44a..fa0e28e 100644 --- a/sc_core/src/transaction_payloads_tools.rs +++ b/sc_core/src/transaction_payloads_tools.rs @@ -15,7 +15,6 @@ pub fn create_public_transaction_payload( secret_r: [u8; 32], sc_addr: String, state_changes: (serde_json::Value, usize), - nonce: u64, ) -> TransactionBody { TransactionBody { tx_kind: TxKind::Public, @@ -32,7 +31,6 @@ pub fn create_public_transaction_payload( secret_r, sc_addr, state_changes, - nonce, } } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 14ced81..80ac760 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -221,22 +221,24 @@ impl SequencerCore { ref utxo_commitments_created_hashes, ref nullifier_created_hashes, execution_input, - nonce, .. } = mempool_tx.auth_tx.transaction().body(); let tx_hash = *mempool_tx.auth_tx.hash(); - // Nonce check - let signer_addres = address::from_public_key(&mempool_tx.auth_tx.transaction().public_key); - if self.store.acc_store.get_account_nonce(&signer_addres) != *nonce { - return Err(TransactionMalformationErrorKind::NonceMismatch { tx: tx_hash }); - } - //Balance move if let Ok(native_transfer_action) = serde_json::from_slice::(execution_input) { + // Nonce check + let signer_addres = + address::from_public_key(&mempool_tx.auth_tx.transaction().public_key); + if self.store.acc_store.get_account_nonce(&signer_addres) + != native_transfer_action.nonce + { + return Err(TransactionMalformationErrorKind::NonceMismatch { tx: tx_hash }); + } + let from_balance = self .store .acc_store @@ -406,7 +408,6 @@ mod tests { secret_r: [0; 32], sc_addr: "sc_addr".to_string(), state_changes: (serde_json::Value::Null, 0), - nonce: 0, }; Transaction::new(body, SignaturePrivateKey::random(&mut rng)) } @@ -422,6 +423,7 @@ mod tests { let native_token_transfer = PublicNativeTokenSend { from, + nonce, to, balance_to_move, }; @@ -441,7 +443,6 @@ mod tests { secret_r: [0; 32], sc_addr: "sc_addr".to_string(), state_changes: (serde_json::Value::Null, 0), - nonce, }; Transaction::new(body, signing_key) } diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index be07c9b..dbefdc2 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -95,7 +95,6 @@ mod tests { secret_r: Default::default(), sc_addr: Default::default(), state_changes: Default::default(), - nonce: 1, }; let tx = Transaction::new(body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap()); ( diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index fa89558..77c6e8b 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -288,7 +288,6 @@ mod tests { secret_r: Default::default(), sc_addr: Default::default(), state_changes: Default::default(), - nonce: 0, }; let tx = Transaction::new(tx_body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap()); @@ -500,7 +499,7 @@ mod tests { let request = serde_json::json!({ "jsonrpc": "2.0", "method": "get_transaction_by_hash", - "params": { "hash": "b70b861373b99a509b27a0c61d6340762c9a0c0026520921d92218712efc3bca"}, + "params": { "hash": "a5210ef33912a448cfe6eda43878c144df81f7bdf51d19b5ddf97be11806a6ed"}, "id": 1 }); @@ -524,10 +523,9 @@ mod tests { "tx_kind": "Shielded", "utxo_commitments_created_hashes": [], "utxo_commitments_spent_hashes": [], - "nonce": 0, }, "public_key": "3056301006072A8648CE3D020106052B8104000A034200041B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F70BEAF8F588B541507FED6A642C5AB42DFDF8120A7F639DE5122D47A69A8E8D1", - "signature": "50AEACE783026D0B6CE221474A928A05A99E2942246DF1C79162C08175F80744746E4A00D6C7F70BDBCF6D7B3668334396B9378FB0F58DC2E8A9B13F3BF001C4" + "signature": "A4E0D6A25BE829B006124F0DFD766427967AA3BEA96C29219E79BB2CC871891F384748C27E28718A4450AA78709FBF1A57DB33BCD575A2C819D2A439C2D878E6" } } }); From c6fb383adf9d9bc04d1c8aefb7651f04aa1627a4 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 29 Jul 2025 16:42:22 -0300 Subject: [PATCH 09/10] fix increase nonce and add test --- .../src/sequencer_store/accounts_store.rs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sequencer_core/src/sequencer_store/accounts_store.rs b/sequencer_core/src/sequencer_store/accounts_store.rs index 3e8b35c..2d9555f 100644 --- a/sequencer_core/src/sequencer_store/accounts_store.rs +++ b/sequencer_core/src/sequencer_store/accounts_store.rs @@ -98,10 +98,18 @@ impl SequencerAccountsStore { } } - pub fn increase_nonce(&mut self, account_addr: &AccountAddress) -> Option { - let acc_data = self.accounts.get_mut(account_addr)?; - acc_data.nonce += 1; - Some(acc_data.nonce) + ///Update `account_addr` nonce, + /// + /// Returns previous nonce + pub fn increase_nonce(&mut self, account_addr: &AccountAddress) -> u64 { + if let Some(acc_data) = self.accounts.get_mut(account_addr) { + let old_nonce = acc_data.nonce; + acc_data.nonce += 1; + old_nonce + } else { + self.register_account(*account_addr); + self.increase_nonce(account_addr) + } } ///Remove account from storage @@ -289,4 +297,14 @@ mod tests { assert!(seq_acc_store.contains_account(&[1; 32])); assert_eq!(seq_acc_store.get_account_balance(&[1; 32]), 0); } + + #[test] + fn test_increase_nonce() { + let mut account_store = SequencerAccountsStore::default(); + let address = [1; 32]; + let first_nonce = account_store.increase_nonce(&address); + assert_eq!(first_nonce, 0); + let second_nonce = account_store.increase_nonce(&address); + assert_eq!(second_nonce, 1); + } } From 18489be4d1eae859e5fec3afc0a722d41264c425 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 29 Jul 2025 16:48:50 -0300 Subject: [PATCH 10/10] nit --- accounts/src/account_core/address.rs | 2 ++ accounts/src/key_management/mod.rs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/accounts/src/account_core/address.rs b/accounts/src/account_core/address.rs index 4570dd5..2fadacd 100644 --- a/accounts/src/account_core/address.rs +++ b/accounts/src/account_core/address.rs @@ -4,6 +4,8 @@ use tiny_keccak::{Hasher, Keccak}; // TODO: Consider wrapping `AccountAddress` in a struct. pub type AccountAddress = [u8; 32]; + +/// Returns the address associated with a public key pub fn from_public_key(public_key: &SignaturePublicKey) -> AccountAddress { let mut address = [0; 32]; let mut keccak_hasher = Keccak::v256(); diff --git a/accounts/src/key_management/mod.rs b/accounts/src/key_management/mod.rs index 69a39f7..c1a78fb 100644 --- a/accounts/src/key_management/mod.rs +++ b/accounts/src/key_management/mod.rs @@ -17,8 +17,6 @@ pub mod secret_holders; #[derive(Serialize, Deserialize, Clone, Debug)] ///Entrypoint to key management pub struct AddressKeyHolder { - //Will be useful in future - #[allow(dead_code)] top_secret_key_holder: TopSecretKeyHolder, pub utxo_secret_key_holder: UTXOSecretKeyHolder, pub_account_signing_key: PublicAccountSigningKey,