From f47692773ccf7bce628c5c228f72ae706c9a2203 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Tue, 26 Aug 2025 14:09:33 +0300 Subject: [PATCH 01/20] fix: merge fix --- nssa/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index db57555..47d9b63 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] thiserror = "2.0.12" risc0-zkvm = "3.0.1" -nssa-core = { path = "core" } +nssa-core = { path = "core", features=["host"]} program-methods = { path = "program_methods" } serde = "1.0.219" sha2 = "0.10.9" From ca131b2c26e4a98ffd8c04dd043ed274939898b8 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Thu, 28 Aug 2025 12:00:04 +0300 Subject: [PATCH 02/20] fix: block structs update --- common/src/block.rs | 42 +++++-- common/src/transaction.rs | 114 ++++++------------ nssa/src/lib.rs | 18 +++ .../src/sequencer_store/block_store.rs | 25 +++- 4 files changed, 108 insertions(+), 91 deletions(-) diff --git a/common/src/block.rs b/common/src/block.rs index 2e4e9b9..6ad1902 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,20 +1,34 @@ use std::io::{Cursor, Read}; +use k256::ecdsa::Signature; use rs_merkle::Hasher; -use crate::merkle_tree_public::hasher::OwnHasher; +use crate::{merkle_tree_public::hasher::OwnHasher, transaction::AuthenticatedTransaction}; use nssa; pub type BlockHash = [u8; 32]; pub type BlockId = u64; +pub type TimeStamp = u64; #[derive(Debug, Clone)] -pub struct Block { +pub struct BlockHeader { pub block_id: BlockId, pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, pub hash: BlockHash, - pub transactions: Vec, + pub timestamp: TimeStamp, + pub signature: Signature, +} + +#[derive(Debug, Clone)] +pub struct BlockBody { + pub transactions: Vec, +} + +#[derive(Debug, Clone)] +pub struct Block { + pub header: BlockHeader, + pub body: BlockBody, } #[derive(Debug, PartialEq, Eq)] @@ -22,7 +36,9 @@ pub struct HashableBlockData { pub block_id: BlockId, pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, - pub transactions: Vec, + pub timestamp: TimeStamp, + pub signature: Signature, + pub transactions: Vec, } impl From for Block { @@ -31,11 +47,17 @@ impl From for Block { let hash = OwnHasher::hash(&data); Self { - block_id: value.block_id, - prev_block_id: value.prev_block_id, - hash, - transactions: value.transactions, - prev_block_hash: value.prev_block_hash, + header: BlockHeader { + block_id: value.block_id, + prev_block_id: value.prev_block_id, + prev_block_hash: value.prev_block_hash, + hash, + timestamp: value.timestamp, + signature: value.signature, + }, + body: BlockBody { + transactions: value.transactions, + }, } } } @@ -46,6 +68,8 @@ impl From for HashableBlockData { block_id: value.block_id, prev_block_id: value.prev_block_id, prev_block_hash: value.prev_block_hash, + timestamp: value.timestamp, + signature: value.signature, transactions: value.transactions, } } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index afc4559..ba2cc97 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -25,39 +25,49 @@ pub type Tag = u8; #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum TxKind { Public, - Private, - Shielded, - Deshielded, + PrivacyPreserving, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] ///General transaction object pub struct TransactionBody { pub tx_kind: TxKind, - ///Tx input data (public part) - pub execution_input: Vec, - ///Tx output data (public_part) - pub execution_output: Vec, - ///Tx input utxo commitments - pub utxo_commitments_spent_hashes: Vec, - ///Tx output utxo commitments - pub utxo_commitments_created_hashes: Vec, - ///Tx output nullifiers - pub nullifier_created_hashes: Vec, - ///Execution proof (private part) - pub execution_proof_private: String, ///Encoded blobs of data - pub encoded_data: Vec<(CipherText, Vec, Tag)>, - ///Transaction senders ephemeral pub key - pub ephemeral_pub_key: Vec, - ///Public (Pedersen) commitment - pub commitment: Vec, - ///tweak - pub tweak: Tweak, - ///secret_r - pub secret_r: [u8; 32], - ///Hex-encoded address of a smart contract account called - pub sc_addr: String, + pub encoded_transaction_data: Vec, +} + +impl From for TransactionBody { + fn from(value: nssa::NSSATransaction) -> Self { + match value { + nssa::NSSATransaction::Public(tx) => { + Self { + tx_kind: TxKind::Public, + encoded_transaction_data: tx.to_bytes(), + } + }, + nssa::NSSATransaction::PrivacyPreserving(tx) => { + Self { + tx_kind: TxKind::PrivacyPreserving, + encoded_transaction_data: tx.to_bytes(), + } + } + } + } +} + +impl TryFrom for nssa::NSSATransaction { + type Error = nssa::error::NssaError; + + fn try_from(value: TransactionBody) -> Result { + match value.tx_kind { + TxKind::Public => { + nssa::PublicTransaction::from_bytes(&value.encoded_transaction_data).map(|tx| tx.into()) + }, + TxKind::PrivacyPreserving => { + nssa::PrivacyPreservingTransaction::from_bytes(&value.encoded_transaction_data).map(|tx| tx.into()) + }, + } + } } #[derive(Debug, Serialize, Deserialize)] @@ -172,41 +182,6 @@ impl TransactionBody { pub fn log(&self) { info!("Transaction hash is {:?}", hex::encode(self.hash())); info!("Transaction tx_kind is {:?}", self.tx_kind); - info!("Transaction execution_input is {:?}", { - if let Ok(action) = serde_json::from_slice::(&self.execution_input) { - action.into_hexed_print() - } else { - "".to_string() - } - }); - info!("Transaction execution_output is {:?}", { - if let Ok(action) = serde_json::from_slice::(&self.execution_output) { - action.into_hexed_print() - } else { - "".to_string() - } - }); - info!( - "Transaction utxo_commitments_spent_hashes is {:?}", - self.utxo_commitments_spent_hashes - .iter() - .map(|val| hex::encode(*val)) - .collect::>() - ); - info!( - "Transaction utxo_commitments_created_hashes is {:?}", - self.utxo_commitments_created_hashes - .iter() - .map(|val| hex::encode(*val)) - .collect::>() - ); - info!( - "Transaction nullifier_created_hashes is {:?}", - self.nullifier_created_hashes - .iter() - .map(|val| hex::encode(*val)) - .collect::>() - ); info!( "Transaction encoded_data is {:?}", self.encoded_data @@ -214,10 +189,6 @@ impl TransactionBody { .map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone()))) .collect::>() ); - info!( - "Transaction ephemeral_pub_key is {:?}", - hex::encode(self.ephemeral_pub_key.clone()) - ); } } @@ -309,18 +280,7 @@ mod tests { fn test_transaction_body() -> TransactionBody { TransactionBody { tx_kind: TxKind::Public, - execution_input: vec![1, 2, 3, 4], - execution_output: vec![5, 6, 7, 8], - utxo_commitments_spent_hashes: vec![[9; 32], [10; 32], [11; 32], [12; 32]], - utxo_commitments_created_hashes: vec![[13; 32]], - nullifier_created_hashes: vec![[0; 32], [1; 32], [2; 32], [3; 32]], - execution_proof_private: "loremipsum".to_string(), - encoded_data: vec![(vec![255, 255, 255], vec![254, 254, 254], 1)], - ephemeral_pub_key: vec![5; 32], - commitment: vec![], - tweak: Tweak::from_slice(&[7; SECRET_KEY_SIZE]).unwrap(), - secret_r: [8; 32], - sc_addr: "someAddress".to_string(), + encoded_transaction_data: vec![1,2,3,4], } } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index ed88047..74c58b1 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -16,3 +16,21 @@ pub use signature::PrivateKey; pub use signature::PublicKey; pub use signature::Signature; pub use state::V01State; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum NSSATransaction { + Public(PublicTransaction), + PrivacyPreserving(PrivacyPreservingTransaction), +} + +impl From for NSSATransaction { + fn from(value: PublicTransaction) -> Self { + Self::Public(value) + } +} + +impl From for NSSATransaction { + fn from(value: PrivacyPreservingTransaction) -> Self { + Self::PrivacyPreserving(value) + } +} diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index 3bd68cb..3c05792 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -77,18 +77,33 @@ fn block_to_transactions_map(block: &Block) -> HashMap { mod tests { use super::*; + use common::block::{BlockBody, BlockHeader}; + use k256::{ + ecdsa::{signature::SignerMut, Signature, SigningKey}, + FieldBytes, + }; use tempfile::tempdir; #[test] fn test_get_transaction_by_hash() { let temp_dir = tempdir().unwrap(); let path = temp_dir.path(); + + let key_bytes = FieldBytes::from_slice(&[37; 32]); + let mut private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); + let genesis_block = Block { - block_id: 0, - prev_block_id: 0, - prev_block_hash: [0; 32], - hash: [1; 32], - transactions: vec![], + header: BlockHeader { + block_id: 0, + prev_block_id: 0, + prev_block_hash: [0; 32], + hash: [1; 32], + timestamp: 0, + signature: private_key.sign(&[42; 32]), + }, + body: BlockBody { + transactions: vec![], + }, }; // Start an empty node store let mut node_store = From fbcc9cf3e26dc8ad57a240b429175aa2d38edbd4 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Tue, 2 Sep 2025 10:17:00 +0300 Subject: [PATCH 03/20] fix: encoding updates --- common/src/block.rs | 18 ++++--- common/src/transaction.rs | 51 ++++++++++++++++++- nssa/program_methods/guest/Cargo.lock | 10 ---- .../encoding.rs | 2 +- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/common/src/block.rs b/common/src/block.rs index 6ad1902..72d6ab7 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -65,12 +65,12 @@ impl From for Block { impl From for HashableBlockData { fn from(value: Block) -> Self { Self { - block_id: value.block_id, - prev_block_id: value.prev_block_id, - prev_block_hash: value.prev_block_hash, - timestamp: value.timestamp, - signature: value.signature, - transactions: value.transactions, + block_id: value.header.block_id, + prev_block_id: value.header.prev_block_id, + prev_block_hash: value.header.prev_block_hash, + timestamp: value.header.timestamp, + signature: value.header.signature, + transactions: value.body.transactions, } } } @@ -81,6 +81,8 @@ impl HashableBlockData { bytes.extend_from_slice(&self.block_id.to_le_bytes()); bytes.extend_from_slice(&self.prev_block_id.to_le_bytes()); bytes.extend_from_slice(&self.prev_block_hash); + bytes.extend_from_slice(&self.timestamp.to_le_bytes()); + bytes.extend_from_slice(&self.signature.to_bytes()); let num_transactions: u32 = self.transactions.len() as u32; bytes.extend_from_slice(&num_transactions.to_le_bytes()); for tx in &self.transactions { @@ -117,14 +119,14 @@ impl HashableBlockData { } // TODO: Improve error handling. Remove unwraps. -fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { +pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { let mut word_buf = [0u8; 4]; cursor.read_exact(&mut word_buf).unwrap(); u32::from_le_bytes(word_buf) } // TODO: Improve error handling. Remove unwraps. -fn u64_from_cursor(cursor: &mut Cursor<&[u8]>) -> u64 { +pub fn u64_from_cursor(cursor: &mut Cursor<&[u8]>) -> u64 { let mut word_buf = [0u8; 8]; cursor.read_exact(&mut word_buf).unwrap(); u64::from_le_bytes(word_buf) diff --git a/common/src/transaction.rs b/common/src/transaction.rs index ba2cc97..f451499 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,3 +1,5 @@ +use std::io::Cursor; + use k256::ecdsa::{ signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, @@ -240,10 +242,48 @@ impl Transaction { } } +impl Transaction { + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + let body_bytes = self.body.to_bytes(); + let signature_bytes = self.signature.to_bytes(); + let public_key_bytes = self.public_key.to_sec1_bytes(); + + let body_bytes_len = body_bytes.len() as u32; + let signature_bytes_len = signature_bytes.len() as u32; + let public_key_bytes_len = public_key_bytes.len() as u32; + + bytes.extend_from_slice(&body_bytes_len.to_le_bytes()); + bytes.extend_from_slice(&signature_bytes_len.to_le_bytes()); + bytes.extend_from_slice(&public_key_bytes_len.to_le_bytes()); + + bytes.extend_from_slice(&self.body.to_bytes()); + bytes.extend_from_slice(&self.signature.to_bytes()); + bytes.extend_from_slice(&self.public_key.to_sec1_bytes()); + + bytes + } + + // TODO: Improve error handling. Remove unwraps. + pub fn from_bytes(data: &[u8]) -> Self { + let mut cursor = Cursor::new(data); + + let body_bytes_len = u32_from_cursor(&mut cursor) as usize; + let signature_bytes_len = u32_from_cursor(&mut cursor) as usize; + let public_key_bytes_len = u32_from_cursor(&mut cursor) as usize; + + let body_bytes = Vec::with_capacity(body_bytes_len); + let signature_bytes = Vec::with_capacity(signature_bytes_len); + let public_key_bytes = Vec::with_capacity(public_key_bytes_len); + + + } +} + /// A transaction with a valid signature over the hash of its body. /// Can only be constructed from an `Transaction` /// if the signature is valid -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct AuthenticatedTransaction { hash: TransactionHash, transaction: Transaction, @@ -265,6 +305,15 @@ impl AuthenticatedTransaction { } } +impl AuthenticatedTransaction { + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&self.hash); + bytes.extend_from_slice(&self.transaction.to_bytes()); + bytes + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/nssa/program_methods/guest/Cargo.lock b/nssa/program_methods/guest/Cargo.lock index 924cbb0..eb5eb30 100644 --- a/nssa/program_methods/guest/Cargo.lock +++ b/nssa/program_methods/guest/Cargo.lock @@ -506,16 +506,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-link", name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs index b5d4950..b3b7082 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -16,7 +16,7 @@ const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/"; impl EncryptedAccountData { - pub(crate) fn to_bytes(&self) -> Vec { + pub fn to_bytes(&self) -> Vec { let mut bytes = self.ciphertext.to_bytes(); bytes.extend_from_slice(&self.epk.to_bytes()); bytes.push(self.view_tag); From 6efac43210807a1590bbe8130ab26a0a6673ba00 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Tue, 2 Sep 2025 11:06:41 +0300 Subject: [PATCH 04/20] fix: block interfix --- common/src/block.rs | 12 +++- common/src/test_utils.rs | 21 ++++++- common/src/transaction.rs | 56 +++++++++++-------- nssa/src/address.rs | 24 ++++++++ .../privacy_preserving_transaction/circuit.rs | 24 ++++++++ .../encoding.rs | 54 +++++++++++++++++- storage/src/lib.rs | 10 ++-- 7 files changed, 168 insertions(+), 33 deletions(-) diff --git a/common/src/block.rs b/common/src/block.rs index 72d6ab7..3760d65 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -4,7 +4,6 @@ use k256::ecdsa::Signature; use rs_merkle::Hasher; use crate::{merkle_tree_public::hasher::OwnHasher, transaction::AuthenticatedTransaction}; -use nssa; pub type BlockHash = [u8; 32]; pub type BlockId = u64; @@ -101,11 +100,18 @@ impl HashableBlockData { let mut prev_block_hash = [0u8; 32]; cursor.read_exact(&mut prev_block_hash).unwrap(); + let timestamp = u64_from_cursor(&mut cursor); + + let signature_bytes_len = u32_from_cursor(&mut cursor) as usize; + let mut signature_bytes = Vec::with_capacity(signature_bytes_len); + cursor.read_exact(&mut signature_bytes).unwrap(); + let signature = Signature::from_bytes(signature_bytes.as_slice().try_into().unwrap()).unwrap(); + let num_transactions = u32_from_cursor(&mut cursor) as usize; let mut transactions = Vec::with_capacity(num_transactions); for _ in 0..num_transactions { - let tx = nssa::PublicTransaction::from_cursor(&mut cursor).unwrap(); + let tx = AuthenticatedTransaction::from_cursor(&mut cursor); transactions.push(tx); } @@ -113,6 +119,8 @@ impl HashableBlockData { block_id, prev_block_id, prev_block_hash, + timestamp, + signature, transactions, } } diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index b956cd7..7f8d8c1 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -1,6 +1,8 @@ -use nssa; +use k256::{ecdsa::{signature::SignerMut, SigningKey}, FieldBytes}; +use nssa::{self, NSSATransaction}; +use rand::rngs::OsRng; -use crate::block::{Block, HashableBlockData}; +use crate::{block::{Block, HashableBlockData}, transaction::{Transaction, TransactionBody}}; //Dummy producers @@ -16,10 +18,25 @@ pub fn produce_dummy_block( prev_hash: Option<[u8; 32]>, transactions: Vec, ) -> Block { + let transactions = transactions.into_iter().map( + |tx| { + let tx_body = TransactionBody::from(NSSATransaction::Public(tx)); + //ToDo: Fix signing key + let transaction = Transaction::new(tx_body, SigningKey::random(&mut OsRng)); + transaction.into_authenticated().unwrap() + }).collect(); + + //ToDo: Fix signature + let key_bytes = FieldBytes::from_slice(&[37; 32]); + let mut private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); + let signature = private_key.sign(&[1; 32]); + let block_data = HashableBlockData { block_id: id, prev_block_id: id.saturating_sub(1), prev_block_hash: prev_hash.unwrap_or_default(), + timestamp: 0, + signature, transactions, }; diff --git a/common/src/transaction.rs b/common/src/transaction.rs index f451499..297841c 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,16 +1,15 @@ -use std::io::Cursor; +use std::io::{Cursor, Read}; use k256::ecdsa::{ signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, }; use log::info; -use secp256k1_zkp::{PedersenCommitment, Tweak}; use serde::{Deserialize, Serialize}; use sha2::{digest::FixedOutput, Digest}; -use crate::merkle_tree_public::TreeHashType; +use crate::{block::u32_from_cursor, merkle_tree_public::TreeHashType}; use elliptic_curve::{ consts::{B0, B1}, @@ -181,16 +180,13 @@ impl TransactionBody { serde_json::to_vec(&self).unwrap() } + fn from_bytes(bytes: Vec) -> Self { + serde_json::from_slice(&bytes).unwrap() + } + pub fn log(&self) { info!("Transaction hash is {:?}", hex::encode(self.hash())); info!("Transaction tx_kind is {:?}", self.tx_kind); - info!( - "Transaction encoded_data is {:?}", - self.encoded_data - .iter() - .map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone()))) - .collect::>() - ); } } @@ -257,26 +253,33 @@ impl Transaction { bytes.extend_from_slice(&signature_bytes_len.to_le_bytes()); bytes.extend_from_slice(&public_key_bytes_len.to_le_bytes()); - bytes.extend_from_slice(&self.body.to_bytes()); - bytes.extend_from_slice(&self.signature.to_bytes()); - bytes.extend_from_slice(&self.public_key.to_sec1_bytes()); + bytes.extend_from_slice(&body_bytes); + bytes.extend_from_slice(&signature_bytes); + bytes.extend_from_slice(&public_key_bytes); bytes } // TODO: Improve error handling. Remove unwraps. - pub fn from_bytes(data: &[u8]) -> Self { - let mut cursor = Cursor::new(data); + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Self { + let body_bytes_len = u32_from_cursor(cursor) as usize; + let signature_bytes_len = u32_from_cursor(cursor) as usize; + let public_key_bytes_len = u32_from_cursor(cursor) as usize; - let body_bytes_len = u32_from_cursor(&mut cursor) as usize; - let signature_bytes_len = u32_from_cursor(&mut cursor) as usize; - let public_key_bytes_len = u32_from_cursor(&mut cursor) as usize; + let mut body_bytes = Vec::with_capacity(body_bytes_len); + let mut signature_bytes = Vec::with_capacity(signature_bytes_len); + let mut public_key_bytes = Vec::with_capacity(public_key_bytes_len); - let body_bytes = Vec::with_capacity(body_bytes_len); - let signature_bytes = Vec::with_capacity(signature_bytes_len); - let public_key_bytes = Vec::with_capacity(public_key_bytes_len); + cursor.read_exact(&mut body_bytes).unwrap(); + let body = TransactionBody::from_bytes(body_bytes); - + cursor.read_exact(&mut signature_bytes).unwrap(); + let signature = Signature::from_bytes(signature_bytes.as_slice().try_into().unwrap()).unwrap(); + + cursor.read_exact(&mut public_key_bytes).unwrap(); + let public_key = VerifyingKey::from_sec1_bytes(&public_key_bytes).unwrap(); + + Self { body, signature, public_key } } } @@ -312,13 +315,20 @@ impl AuthenticatedTransaction { bytes.extend_from_slice(&self.transaction.to_bytes()); bytes } + + // TODO: Improve error handling. Remove unwraps. + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Self { + let mut hash: [u8; 32] = [0; 32]; + cursor.read_exact(&mut hash).unwrap(); + let transaction = Transaction::from_cursor(cursor); + Self { hash, transaction } + } } #[cfg(test)] mod tests { use super::*; use k256::{ecdsa::signature::Signer, FieldBytes}; - use secp256k1_zkp::{constants::SECRET_KEY_SIZE, Tweak}; use sha2::{digest::FixedOutput, Digest}; use crate::{ diff --git a/nssa/src/address.rs b/nssa/src/address.rs index 7e4bc1e..93304d5 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -1,5 +1,7 @@ use std::{fmt::Display, str::FromStr}; +use serde::{Deserialize, Serialize}; + use crate::signature::PublicKey; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -57,6 +59,28 @@ impl Display for Address { } } +impl Serialize for Address { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let hex_string = self.to_string(); + + hex_string.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Address { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let hex_string = String::deserialize(deserializer)?; + + Address::from_str(&hex_string).map_err(serde::de::Error::custom) + } +} + #[cfg(test)] mod tests { use crate::{Address, address::AddressError}; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index ed32f98..4cd4526 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,3 +1,5 @@ +use std::io::{Cursor, Read}; + use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, @@ -20,6 +22,28 @@ impl Proof { let receipt = Receipt::new(inner, circuit_output.to_bytes()); receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() } + + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + let proof_len = self.0.len() as u32; + bytes.extend_from_slice(&proof_len.to_le_bytes()); + bytes.extend_from_slice(&self.0); + bytes + } + + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let proof_len = u32_from_cursor(cursor) as usize; + let mut proof = Vec::with_capacity(proof_len); + cursor.read_exact(&mut proof)?; + Ok(Self(proof)) + } +} + +// TODO: Improve error handling. Remove unwraps. +pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { + let mut word_buf = [0u8; 4]; + cursor.read_exact(&mut word_buf).unwrap(); + u32::from_le_bytes(word_buf) } /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs index b3b7082..fe70e65 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -7,7 +7,7 @@ use nssa_core::{ }; use crate::{ - Address, error::NssaError, privacy_preserving_transaction::message::EncryptedAccountData, + error::NssaError, privacy_preserving_transaction::{circuit::Proof, message::EncryptedAccountData, witness_set::WitnessSet}, Address, PrivacyPreservingTransaction, PublicKey, Signature }; use super::message::Message; @@ -168,3 +168,55 @@ impl Message { }) } } + +impl WitnessSet { + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + let size = self.signatures_and_public_keys().len() as u32; + bytes.extend_from_slice(&size.to_le_bytes()); + for (signature, public_key) in self.signatures_and_public_keys() { + bytes.extend_from_slice(signature.to_bytes()); + bytes.extend_from_slice(public_key.to_bytes()); + } + bytes.extend_from_slice(&self.proof.to_bytes()); + bytes + } + + pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let num_signatures: u32 = { + let mut buf = [0u8; 4]; + cursor.read_exact(&mut buf)?; + u32::from_le_bytes(buf) + }; + let mut signatures_and_public_keys = Vec::with_capacity(num_signatures as usize); + for _i in 0..num_signatures { + let signature = Signature::from_cursor(cursor)?; + let public_key = PublicKey::from_cursor(cursor)?; + signatures_and_public_keys.push((signature, public_key)) + } + let proof = Proof::from_cursor(cursor)?; + Ok(Self { + signatures_and_public_keys, + proof, + }) + } +} + +impl PrivacyPreservingTransaction { + pub fn to_bytes(&self) -> Vec { + let mut bytes = self.message().to_bytes(); + bytes.extend_from_slice(&self.witness_set().to_bytes()); + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); + Self::from_cursor(&mut cursor) + } + + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let message = Message::from_cursor(cursor)?; + let witness_set = WitnessSet::from_cursor(cursor)?; + Ok(PrivacyPreservingTransaction::new(message, witness_set)) + } +} diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 0dd12b7..3e612b9 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -92,7 +92,7 @@ impl RocksDBIO { if is_start_set { Ok(dbio) } else if let Some(block) = start_block { - let block_id = block.block_id; + let block_id = block.header.block_id; dbio.put_meta_first_block_in_db(block)?; dbio.put_meta_is_first_block_set()?; @@ -186,7 +186,7 @@ impl RocksDBIO { .put_cf( &cf_meta, DB_META_FIRST_BLOCK_IN_DB_KEY.as_bytes(), - block.block_id.to_be_bytes(), + block.header.block_id.to_be_bytes(), ) .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; @@ -233,15 +233,15 @@ impl RocksDBIO { if !first { let last_curr_block = self.get_meta_last_block_in_db()?; - if block.block_id > last_curr_block { - self.put_meta_last_block_in_db(block.block_id)?; + if block.header.block_id > last_curr_block { + self.put_meta_last_block_in_db(block.header.block_id)?; } } self.db .put_cf( &cf_block, - block.block_id.to_be_bytes(), + block.header.block_id.to_be_bytes(), HashableBlockData::from(block).to_bytes(), ) .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; From 5190b486c4046910a66dd4481bf6ba1e8bbd8fac Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Sep 2025 12:38:31 -0300 Subject: [PATCH 05/20] fix circuit and add test --- .../src/bin/privacy_preserving_circuit.rs | 4 ++- nssa/src/error.rs | 3 ++ .../privacy_preserving_transaction/circuit.rs | 4 ++- nssa/src/state.rs | 32 +++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 83f593a..947f6ea 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -32,7 +32,9 @@ fn main() { // Check that the program is well behaved. // See the # Programs section for the definition of the `validate_execution` method. - validate_execution(&pre_states, &post_states, program_id); + if !validate_execution(&pre_states, &post_states, program_id) { + panic!(); + } let n_accounts = pre_states.len(); if visibility_mask.len() != n_accounts { diff --git a/nssa/src/error.rs b/nssa/src/error.rs index 1d6d6ad..0e85789 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -45,4 +45,7 @@ pub enum NssaError { #[error("Invalid privacy preserving execution circuit proof")] InvalidPrivacyPreservingProof, + + #[error("Circuit proving error")] + CircuitProvingError(String), } diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index ed32f98..24cdc4a 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -55,7 +55,9 @@ pub fn execute_and_prove( env_builder.write(&circuit_input).unwrap(); let env = env_builder.build().unwrap(); let prover = default_prover(); - let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); + let prove_info = prover + .prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF) + .map_err(|e| NssaError::CircuitProvingError(e.to_string()))?; let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 93ee06c..d0733ec 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -214,6 +214,7 @@ pub mod tests { use crate::{ Address, PublicKey, PublicTransaction, V01State, error::NssaError, + execute_and_prove, privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet, }, @@ -1063,4 +1064,35 @@ pub mod tests { recipient_initial_balance + balance_to_move ); } + + #[test] + fn test_burner_program_should_fail_in_privacy_preserving_circuit() { + let keys = test_private_account_keys_1(); + let private_account = AccountWithMetadata { + account: Account { + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let state = V01State::new_with_genesis_accounts(&[]) + .with_private_account(&keys, &private_account.account); + + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&keys.npk(), &private_account.account)) + .unwrap(); + + let program = Program::burner(); + let result = execute_and_prove( + &[private_account], + &Program::serialize_instruction(10u128).unwrap(), + &[1], + &[0xdeadbeef], + &[(keys.npk(), SharedSecretKey::new(&[0xca; 32], &keys.ivk()))], + &[(keys.nsk, membership_proof)], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } From f3d63806c3e69d0259bedc7a02716732e6b948e9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Sep 2025 12:56:01 -0300 Subject: [PATCH 06/20] fix tests --- .../guest/src/bin/privacy_preserving_circuit.rs | 4 ++-- nssa/src/privacy_preserving_transaction/circuit.rs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 947f6ea..5ec45eb 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -33,12 +33,12 @@ fn main() { // Check that the program is well behaved. // See the # Programs section for the definition of the `validate_execution` method. if !validate_execution(&pre_states, &post_states, program_id) { - panic!(); + panic!("Bad behaved program"); } let n_accounts = pre_states.len(); if visibility_mask.len() != n_accounts { - panic!(); + panic!("Invalid visibility mask length"); } // These lists will be the public outputs of this circuit diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 24cdc4a..05d71da 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -111,6 +111,7 @@ mod tests { let program = Program::authenticated_transfer_program(); let sender = AccountWithMetadata { account: Account { + program_owner: program.id(), balance: 100, ..Account::default() }, @@ -177,11 +178,13 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_fully_private() { + let program = Program::authenticated_transfer_program(); let sender_pre = AccountWithMetadata { account: Account { balance: 100, nonce: 0xdeadbeef, - ..Account::default() + program_owner: program.id(), + data: vec![], }, is_authorized: true, }; From a1e4c06bc7d5a73ffcff42994202e7fc0ae78d6f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Sep 2025 14:09:50 -0300 Subject: [PATCH 07/20] add tests --- nssa/src/state.rs | 213 ++++++++++++++++-- .../guest/src/bin/missing_output.rs | 4 +- 2 files changed, 201 insertions(+), 16 deletions(-) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index d0733ec..cb3326f 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1067,29 +1067,214 @@ pub mod tests { #[test] fn test_burner_program_should_fail_in_privacy_preserving_circuit() { - let keys = test_private_account_keys_1(); - let private_account = AccountWithMetadata { + let program = Program::burner(); + let public_account = AccountWithMetadata { account: Account { + program_owner: program.id(), balance: 100, ..Account::default() }, is_authorized: true, }; - let state = V01State::new_with_genesis_accounts(&[]) - .with_private_account(&keys, &private_account.account); - let membership_proof = state - .get_proof_for_commitment(&Commitment::new(&keys.npk(), &private_account.account)) - .unwrap(); - - let program = Program::burner(); let result = execute_and_prove( - &[private_account], + &[public_account], &Program::serialize_instruction(10u128).unwrap(), - &[1], - &[0xdeadbeef], - &[(keys.npk(), SharedSecretKey::new(&[0xca; 32], &keys.ivk()))], - &[(keys.nsk, membership_proof)], + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_minter_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::minter(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(10u128).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_nonce_changer_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::nonce_changer_program(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() { + let program = Program::data_changer(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::extra_output_program(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_missing_output_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::missing_output_program(); + let public_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + let public_account_2 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(()).unwrap(), + &[0, 0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_program_owner_changer_should_fail_in_privacy_preserving_circuit() { + let program = Program::program_owner_changer(); + let public_account = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account], + &Program::serialize_instruction(()).unwrap(), + &[0], + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata { + account: Account { + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let public_account_2 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[0, 0], + &[], + &[], + &[], &program, ); diff --git a/nssa/test_program_methods/guest/src/bin/missing_output.rs b/nssa/test_program_methods/guest/src/bin/missing_output.rs index 2174266..7b6016c 100644 --- a/nssa/test_program_methods/guest/src/bin/missing_output.rs +++ b/nssa/test_program_methods/guest/src/bin/missing_output.rs @@ -5,12 +5,12 @@ type Instruction = (); fn main() { let ProgramInput { pre_states, .. } = read_nssa_inputs::(); - let [pre1, _] = match pre_states.try_into() { + let [pre1, pre2] = match pre_states.try_into() { Ok(array) => array, Err(_) => return, }; let account_pre1 = pre1.account.clone(); - write_nssa_outputs(vec![pre1], vec![account_pre1]); + write_nssa_outputs(vec![pre1, pre2], vec![account_pre1]); } From f08ed175094151fc16c0d33b314fe7d013e0e773 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 2 Sep 2025 14:50:16 -0300 Subject: [PATCH 08/20] test incorrect number of masks in visibility mask list --- nssa/src/state.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index cb3326f..3655b1c 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1280,4 +1280,39 @@ pub mod tests { assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } + + #[test] + fn test_circuit_fails_if_visibility_masks_have_incorrect_lenght() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let public_account_2 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + is_authorized: true, + }; + + // Setting only one visibility mask for a circuit execution with two pre_state accounts. + let visibility_mask = [0]; + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } From b4f21b2f09eb29a9d04c352dcece5019fe12f014 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 3 Sep 2025 10:29:51 +0300 Subject: [PATCH 09/20] fix: checks added --- Cargo.toml | 1 + common/src/block.rs | 60 ++-- common/src/commitment.rs | 2 +- common/src/execution_input.rs | 2 +- common/src/lib.rs | 23 +- common/src/merkle_tree_public/hasher.rs | 20 -- common/src/merkle_tree_public/merkle_tree.rs | 312 ------------------ common/src/merkle_tree_public/mod.rs | 6 - .../src/merkle_tree_public/tree_leav_item.rs | 19 -- common/src/nullifier.rs | 2 +- common/src/sequencer_client/mod.rs | 5 +- common/src/test_utils.rs | 51 ++- common/src/transaction.rs | 272 ++------------- common/src/utxo_commitment.rs | 2 +- .../debug/sequencer/sequencer_config.json | 4 +- .../src/key_management/secret_holders.rs | 2 +- .../encoding.rs | 6 +- sequencer_core/Cargo.toml | 1 + sequencer_core/src/config.rs | 2 + sequencer_core/src/lib.rs | 112 +++++-- .../src/sequencer_store/block_store.rs | 55 ++- sequencer_core/src/sequencer_store/mod.rs | 7 +- sequencer_rpc/src/process.rs | 18 +- storage/src/lib.rs | 4 +- wallet/src/chain_storage/mod.rs | 12 +- wallet/src/lib.rs | 10 +- 26 files changed, 259 insertions(+), 751 deletions(-) delete mode 100644 common/src/merkle_tree_public/hasher.rs delete mode 100644 common/src/merkle_tree_public/merkle_tree.rs delete mode 100644 common/src/merkle_tree_public/mod.rs delete mode 100644 common/src/merkle_tree_public/tree_leav_item.rs diff --git a/Cargo.toml b/Cargo.toml index 2b98867..93a5ac7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ ark-bn254 = "0.5.0" ark-ff = "0.5.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } base64 = "0.22.1" +chrono = "0.4.41" rocksdb = { version = "0.21.0", default-features = false, features = [ "snappy", diff --git a/common/src/block.rs b/common/src/block.rs index 3760d65..24da8c7 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,9 +1,7 @@ +use rs_merkle::Hasher; use std::io::{Cursor, Read}; -use k256::ecdsa::Signature; -use rs_merkle::Hasher; - -use crate::{merkle_tree_public::hasher::OwnHasher, transaction::AuthenticatedTransaction}; +use crate::{transaction::TransactionBody, OwnHasher}; pub type BlockHash = [u8; 32]; pub type BlockId = u64; @@ -16,12 +14,12 @@ pub struct BlockHeader { pub prev_block_hash: BlockHash, pub hash: BlockHash, pub timestamp: TimeStamp, - pub signature: Signature, + pub signature: nssa::Signature, } #[derive(Debug, Clone)] pub struct BlockBody { - pub transactions: Vec, + pub transactions: Vec, } #[derive(Debug, Clone)] @@ -36,26 +34,25 @@ pub struct HashableBlockData { pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, pub timestamp: TimeStamp, - pub signature: Signature, - pub transactions: Vec, + pub transactions: Vec, } -impl From for Block { - fn from(value: HashableBlockData) -> Self { - let data = value.to_bytes(); - let hash = OwnHasher::hash(&data); - - Self { +impl HashableBlockData { + pub fn into_block(self, signing_key: &nssa::PrivateKey) -> Block { + let data_bytes = self.to_bytes(); + let signature = nssa::Signature::new(signing_key, &data_bytes); + let hash = OwnHasher::hash(&data_bytes); + Block { header: BlockHeader { - block_id: value.block_id, - prev_block_id: value.prev_block_id, - prev_block_hash: value.prev_block_hash, + block_id: self.block_id, + prev_block_id: self.prev_block_id, + prev_block_hash: self.prev_block_hash, hash, - timestamp: value.timestamp, - signature: value.signature, + timestamp: self.timestamp, + signature, }, body: BlockBody { - transactions: value.transactions, + transactions: self.transactions, }, } } @@ -68,7 +65,6 @@ impl From for HashableBlockData { prev_block_id: value.header.prev_block_id, prev_block_hash: value.header.prev_block_hash, timestamp: value.header.timestamp, - signature: value.header.signature, transactions: value.body.transactions, } } @@ -81,10 +77,13 @@ impl HashableBlockData { bytes.extend_from_slice(&self.prev_block_id.to_le_bytes()); bytes.extend_from_slice(&self.prev_block_hash); bytes.extend_from_slice(&self.timestamp.to_le_bytes()); - bytes.extend_from_slice(&self.signature.to_bytes()); let num_transactions: u32 = self.transactions.len() as u32; bytes.extend_from_slice(&num_transactions.to_le_bytes()); for tx in &self.transactions { + let transaction_bytes = tx.to_bytes(); + let num_transaction_bytes: u32 = transaction_bytes.len() as u32; + + bytes.extend_from_slice(&num_transaction_bytes.to_le_bytes()); bytes.extend_from_slice(&tx.to_bytes()); } bytes @@ -101,17 +100,21 @@ impl HashableBlockData { cursor.read_exact(&mut prev_block_hash).unwrap(); let timestamp = u64_from_cursor(&mut cursor); - - let signature_bytes_len = u32_from_cursor(&mut cursor) as usize; - let mut signature_bytes = Vec::with_capacity(signature_bytes_len); - cursor.read_exact(&mut signature_bytes).unwrap(); - let signature = Signature::from_bytes(signature_bytes.as_slice().try_into().unwrap()).unwrap(); let num_transactions = u32_from_cursor(&mut cursor) as usize; let mut transactions = Vec::with_capacity(num_transactions); for _ in 0..num_transactions { - let tx = AuthenticatedTransaction::from_cursor(&mut cursor); + let tx_len = u32_from_cursor(&mut cursor) as usize; + let mut tx_bytes = Vec::with_capacity(tx_len); + + for _ in 0..tx_len { + let mut buff = [0; 1]; + cursor.read_exact(&mut buff).unwrap(); + tx_bytes.push(buff[0]); + } + + let tx = TransactionBody::from_bytes(tx_bytes); transactions.push(tx); } @@ -120,7 +123,6 @@ impl HashableBlockData { prev_block_id, prev_block_hash, timestamp, - signature, transactions, } } diff --git a/common/src/commitment.rs b/common/src/commitment.rs index 0874bc7..a905ad1 100644 --- a/common/src/commitment.rs +++ b/common/src/commitment.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::merkle_tree_public::CommitmentHashType; +use crate::CommitmentHashType; #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)] pub struct Commitment { diff --git a/common/src/execution_input.rs b/common/src/execution_input.rs index c4f2141..08ff598 100644 --- a/common/src/execution_input.rs +++ b/common/src/execution_input.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::merkle_tree_public::TreeHashType; +use crate::TreeHashType; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PublicNativeTokenSend { diff --git a/common/src/lib.rs b/common/src/lib.rs index 3b2ddff..a0e6df8 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,10 +1,10 @@ -use merkle_tree_public::TreeHashType; +use rs_merkle::Hasher; use serde::Deserialize; +use sha2::{digest::FixedOutput, Digest, Sha256}; pub mod block; pub mod commitment; pub mod execution_input; -pub mod merkle_tree_public; pub mod nullifier; pub mod rpc_primitives; pub mod sequencer_client; @@ -16,6 +16,25 @@ pub mod test_utils; use rpc_primitives::errors::RpcError; +pub type TreeHashType = [u8; 32]; +pub type CommitmentHashType = Vec; + +#[derive(Debug, Clone)] +///Our own hasher. +/// Currently it is SHA256 hasher wrapper. May change in a future. +pub struct OwnHasher {} + +impl Hasher for OwnHasher { + type Hash = TreeHashType; + + fn hash(data: &[u8]) -> TreeHashType { + let mut hasher = Sha256::new(); + + hasher.update(data); + ::from(hasher.finalize_fixed()) + } +} + ///Account id on blockchain pub type AccountId = TreeHashType; diff --git a/common/src/merkle_tree_public/hasher.rs b/common/src/merkle_tree_public/hasher.rs deleted file mode 100644 index b23388f..0000000 --- a/common/src/merkle_tree_public/hasher.rs +++ /dev/null @@ -1,20 +0,0 @@ -use rs_merkle::Hasher; -use sha2::{digest::FixedOutput, Digest, Sha256}; - -use super::TreeHashType; - -#[derive(Debug, Clone)] -///Our own hasher. -/// Currently it is SHA256 hasher wrapper. May change in a future. -pub struct OwnHasher {} - -impl Hasher for OwnHasher { - type Hash = TreeHashType; - - fn hash(data: &[u8]) -> TreeHashType { - let mut hasher = Sha256::new(); - - hasher.update(data); - ::from(hasher.finalize_fixed()) - } -} diff --git a/common/src/merkle_tree_public/merkle_tree.rs b/common/src/merkle_tree_public/merkle_tree.rs deleted file mode 100644 index e2eb715..0000000 --- a/common/src/merkle_tree_public/merkle_tree.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::{collections::HashMap, fmt, marker::PhantomData}; - -use rs_merkle::{MerkleProof, MerkleTree}; -use serde::{ - de::{SeqAccess, Visitor}, - ser::SerializeSeq, - Deserialize, Deserializer, Serialize, -}; - -use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment}; - -use super::{hasher::OwnHasher, tree_leav_item::TreeLeavItem, TreeHashType}; - -#[derive(Clone)] -pub struct HashStorageMerkleTree { - leaves: HashMap, - hash_to_id_map: HashMap, - tree: MerkleTree, -} - -impl Serialize for HashStorageMerkleTree { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut vector = self.leaves.iter().collect::>(); - vector.sort_by(|a, b| a.0.cmp(b.0)); - - let mut seq = serializer.serialize_seq(Some(self.leaves.len()))?; - for element in vector.iter() { - seq.serialize_element(element.1)?; - } - seq.end() - } -} - -struct HashStorageMerkleTreeDeserializer { - marker: PhantomData HashStorageMerkleTree>, -} - -impl HashStorageMerkleTreeDeserializer { - fn new() -> Self { - HashStorageMerkleTreeDeserializer { - marker: PhantomData, - } - } -} - -impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> Visitor<'de> - for HashStorageMerkleTreeDeserializer -{ - type Value = HashStorageMerkleTree; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("HashStorageMerkleTree key value sequence.") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut vector = vec![]; - - loop { - let opt_key = seq.next_element::()?; - if let Some(value) = opt_key { - vector.push(value); - } else { - break; - } - } - - Ok(HashStorageMerkleTree::new(vector)) - } -} - -impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> serde::Deserialize<'de> - for HashStorageMerkleTree -{ - fn deserialize>(deserializer: D) -> Result { - deserializer.deserialize_seq(HashStorageMerkleTreeDeserializer::new()) - } -} - -pub type PublicTransactionMerkleTree = HashStorageMerkleTree; - -pub type UTXOCommitmentsMerkleTree = HashStorageMerkleTree; - -impl HashStorageMerkleTree { - pub fn new(leaves_vec: Vec) -> Self { - let mut leaves_map = HashMap::new(); - let mut hash_to_id_map = HashMap::new(); - - let leaves_hashed: Vec = leaves_vec - .iter() - .enumerate() - .map(|(id, tx)| { - leaves_map.insert(id, tx.clone()); - hash_to_id_map.insert(tx.hash(), id); - tx.hash() - }) - .collect(); - Self { - leaves: leaves_map, - hash_to_id_map, - tree: MerkleTree::from_leaves(&leaves_hashed), - } - } - - pub fn get_tx(&self, hash: TreeHashType) -> Option<&Leav> { - self.hash_to_id_map - .get(&hash) - .and_then(|id| self.leaves.get(id)) - } - - pub fn get_root(&self) -> Option { - self.tree.root() - } - - pub fn get_proof(&self, hash: TreeHashType) -> Option> { - self.hash_to_id_map - .get(&hash) - .map(|id| self.tree.proof(&[*id])) - } - - pub fn get_proof_multiple(&self, hashes: &[TreeHashType]) -> Option> { - let ids_opt: Vec> = hashes - .iter() - .map(|hash| self.hash_to_id_map.get(hash)) - .collect(); - - let is_valid = ids_opt.iter().all(|el| el.is_some()); - - if is_valid { - let ids: Vec = ids_opt.into_iter().map(|el| *el.unwrap()).collect(); - - Some(self.tree.proof(&ids)) - } else { - None - } - } - - pub fn add_tx(&mut self, tx: &Leav) { - let last = self.leaves.len(); - - self.leaves.insert(last, tx.clone()); - self.hash_to_id_map.insert(tx.hash(), last); - - self.tree.insert(tx.hash()); - - self.tree.commit(); - } - - pub fn add_tx_multiple(&mut self, txs: Vec) { - for tx in txs.iter() { - let last = self.leaves.len(); - - self.leaves.insert(last, tx.clone()); - self.hash_to_id_map.insert(tx.hash(), last); - } - - self.tree - .append(&mut txs.iter().map(|tx| tx.hash()).collect()); - - self.tree.commit(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // Mock implementation of TreeLeavItem trait for testing - #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] - struct MockTransaction { - pub hash: TreeHashType, - } - - impl TreeLeavItem for MockTransaction { - fn hash(&self) -> TreeHashType { - self.hash - } - } - - fn get_first_32_bytes(s: &str) -> [u8; 32] { - let mut buffer = [0u8; 32]; - let bytes = s.as_bytes(); - let len = std::cmp::min(32, bytes.len()); - - buffer[..len].copy_from_slice(&bytes[..len]); - buffer - } - - #[test] - fn test_new_merkle_tree() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]); - - assert_eq!(tree.leaves.len(), 2); - assert!(tree.get_root().is_some()); - } - - #[test] - fn test_new_merkle_tree_serialize() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]); - - let binding = serde_json::to_vec(&tree).unwrap(); - - let obj: HashStorageMerkleTree = serde_json::from_slice(&binding).unwrap(); - - assert_eq!(tree.leaves, obj.leaves); - assert_eq!(tree.hash_to_id_map, obj.hash_to_id_map); - assert_eq!(tree.tree.root(), obj.tree.root()); - } - - #[test] - fn test_get_tx() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]); - - assert_eq!(tree.get_tx(tx1.hash()), Some(&tx1)); - assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2)); - } - - #[test] - fn test_get_proof() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]); - - let proof = tree.get_proof(tx1.hash()); - assert!(proof.is_some()); - } - - #[test] - fn test_add_tx() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - - let mut tree = HashStorageMerkleTree::new(vec![tx1.clone()]); - - tree.add_tx(&tx2); - assert_eq!(tree.leaves.len(), 2); - assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2)); - } - - #[test] - fn test_add_tx_multiple() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - let tx3 = MockTransaction { - hash: get_first_32_bytes("tx3"), - }; - - let mut tree = HashStorageMerkleTree::new(vec![tx1.clone()]); - tree.add_tx_multiple(vec![tx2.clone(), tx3.clone()]); - - assert_eq!(tree.leaves.len(), 3); - assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2)); - assert_eq!(tree.get_tx(tx3.hash()), Some(&tx3)); - } - - #[test] - fn test_get_proof_multiple() { - let tx1 = MockTransaction { - hash: get_first_32_bytes("tx1"), - }; - let tx2 = MockTransaction { - hash: get_first_32_bytes("tx2"), - }; - let tx3 = MockTransaction { - hash: get_first_32_bytes("tx3"), - }; - - let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone(), tx3.clone()]); - let proof = tree.get_proof_multiple(&[tx1.hash(), tx2.hash()]); - - assert!(proof.is_some()); - } -} diff --git a/common/src/merkle_tree_public/mod.rs b/common/src/merkle_tree_public/mod.rs deleted file mode 100644 index f1fc89e..0000000 --- a/common/src/merkle_tree_public/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod hasher; -pub mod merkle_tree; -pub mod tree_leav_item; - -pub type TreeHashType = [u8; 32]; -pub type CommitmentHashType = Vec; diff --git a/common/src/merkle_tree_public/tree_leav_item.rs b/common/src/merkle_tree_public/tree_leav_item.rs deleted file mode 100644 index 3b919a8..0000000 --- a/common/src/merkle_tree_public/tree_leav_item.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment}; - -use super::TreeHashType; - -pub trait TreeLeavItem { - fn hash(&self) -> TreeHashType; -} - -impl TreeLeavItem for Transaction { - fn hash(&self) -> TreeHashType { - self.body().hash() - } -} - -impl TreeLeavItem for UTXOCommitment { - fn hash(&self) -> TreeHashType { - self.hash - } -} diff --git a/common/src/nullifier.rs b/common/src/nullifier.rs index 2c95741..f277efd 100644 --- a/common/src/nullifier.rs +++ b/common/src/nullifier.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::merkle_tree_public::TreeHashType; +use crate::TreeHashType; //ToDo: Update Nullifier model, when it is clear #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] diff --git a/common/src/sequencer_client/mod.rs b/common/src/sequencer_client/mod.rs index ad66d82..c6da098 100644 --- a/common/src/sequencer_client/mod.rs +++ b/common/src/sequencer_client/mod.rs @@ -12,6 +12,7 @@ use crate::rpc_primitives::requests::{ GetTransactionByHashResponse, }; use crate::sequencer_client::json::AccountInitialData; +use crate::transaction::TransactionBody; use crate::{SequencerClientError, SequencerRpcError}; pub mod json; @@ -127,10 +128,12 @@ impl SequencerClient { } ///Send transaction to sequencer - pub async fn send_tx( + pub async fn send_tx_public( &self, transaction: nssa::PublicTransaction, ) -> Result { + let transaction = TransactionBody::from(nssa::NSSATransaction::Public(transaction)); + let tx_req = SendTxRequest { transaction: transaction.to_bytes(), }; diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index 7f8d8c1..7fb58d3 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -1,8 +1,15 @@ -use k256::{ecdsa::{signature::SignerMut, SigningKey}, FieldBytes}; -use nssa::{self, NSSATransaction}; -use rand::rngs::OsRng; +use nssa::NSSATransaction; -use crate::{block::{Block, HashableBlockData}, transaction::{Transaction, TransactionBody}}; +use crate::{ + block::{Block, HashableBlockData}, + transaction::TransactionBody, +}; + +//Helpers + +pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey { + nssa::PrivateKey::try_new([37; 32]).unwrap() +} //Dummy producers @@ -12,38 +19,24 @@ use crate::{block::{Block, HashableBlockData}, transaction::{Transaction, Transa /// /// `prev_hash` - hash of previous block, provide None for genesis /// -/// `transactions` - vector of `Transaction` objects +/// `transactions` - vector of `AuthenticatedTransaction` objects pub fn produce_dummy_block( id: u64, prev_hash: Option<[u8; 32]>, - transactions: Vec, + transactions: Vec, ) -> Block { - let transactions = transactions.into_iter().map( - |tx| { - let tx_body = TransactionBody::from(NSSATransaction::Public(tx)); - //ToDo: Fix signing key - let transaction = Transaction::new(tx_body, SigningKey::random(&mut OsRng)); - transaction.into_authenticated().unwrap() - }).collect(); - - //ToDo: Fix signature - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let mut private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - let signature = private_key.sign(&[1; 32]); - let block_data = HashableBlockData { block_id: id, prev_block_id: id.saturating_sub(1), prev_block_hash: prev_hash.unwrap_or_default(), - timestamp: 0, - signature, + timestamp: id * 100, transactions, }; - block_data.into() + block_data.into_block(&sequencer_sign_key_for_testing()) } -pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction { +pub fn produce_dummy_empty_transaction() -> TransactionBody { let program_id = nssa::program::Program::authenticated_transfer_program().id(); let addresses = vec![]; let nonces = vec![]; @@ -53,7 +46,10 @@ pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction { .unwrap(); let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap(); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]); - nssa::PublicTransaction::new(message, witness_set) + + let nssa_tx = nssa::PublicTransaction::new(message, witness_set); + + TransactionBody::from(NSSATransaction::Public(nssa_tx)) } pub fn create_transaction_native_token_transfer( @@ -62,7 +58,7 @@ pub fn create_transaction_native_token_transfer( to: [u8; 32], balance_to_move: u128, signing_key: nssa::PrivateKey, -) -> nssa::PublicTransaction { +) -> TransactionBody { let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)]; let nonces = vec![nonce]; let program_id = nssa::program::Program::authenticated_transfer_program().id(); @@ -70,5 +66,8 @@ pub fn create_transaction_native_token_transfer( nssa::public_transaction::Message::try_new(program_id, addresses, nonces, balance_to_move) .unwrap(); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]); - nssa::PublicTransaction::new(message, witness_set) + + let nssa_tx = nssa::PublicTransaction::new(message, witness_set); + + TransactionBody::from(NSSATransaction::Public(nssa_tx)) } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 297841c..83cfbc9 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,23 +1,16 @@ -use std::io::{Cursor, Read}; - -use k256::ecdsa::{ - signature::{Signer, Verifier}, - Signature, SigningKey, VerifyingKey, -}; +use k256::ecdsa::{Signature, SigningKey, VerifyingKey}; use log::info; use serde::{Deserialize, Serialize}; use sha2::{digest::FixedOutput, Digest}; -use crate::{block::u32_from_cursor, merkle_tree_public::TreeHashType}; - use elliptic_curve::{ consts::{B0, B1}, generic_array::GenericArray, }; use sha2::digest::typenum::{UInt, UTerm}; -use crate::TransactionSignatureError; +use crate::TreeHashType; pub type CipherText = Vec; pub type Nonce = GenericArray, B1>, B0>, B0>>; @@ -40,33 +33,29 @@ pub struct TransactionBody { impl From for TransactionBody { fn from(value: nssa::NSSATransaction) -> Self { match value { - nssa::NSSATransaction::Public(tx) => { - Self { - tx_kind: TxKind::Public, - encoded_transaction_data: tx.to_bytes(), - } + nssa::NSSATransaction::Public(tx) => Self { + tx_kind: TxKind::Public, + encoded_transaction_data: tx.to_bytes(), + }, + nssa::NSSATransaction::PrivacyPreserving(tx) => Self { + tx_kind: TxKind::PrivacyPreserving, + encoded_transaction_data: tx.to_bytes(), }, - nssa::NSSATransaction::PrivacyPreserving(tx) => { - Self { - tx_kind: TxKind::PrivacyPreserving, - encoded_transaction_data: tx.to_bytes(), - } - } } } } -impl TryFrom for nssa::NSSATransaction { +impl TryFrom<&TransactionBody> for nssa::NSSATransaction { type Error = nssa::error::NssaError; - fn try_from(value: TransactionBody) -> Result { + fn try_from(value: &TransactionBody) -> Result { match value.tx_kind { - TxKind::Public => { - nssa::PublicTransaction::from_bytes(&value.encoded_transaction_data).map(|tx| tx.into()) - }, + TxKind::Public => nssa::PublicTransaction::from_bytes(&value.encoded_transaction_data) + .map(|tx| tx.into()), TxKind::PrivacyPreserving => { - nssa::PrivacyPreservingTransaction::from_bytes(&value.encoded_transaction_data).map(|tx| tx.into()) - }, + nssa::PrivacyPreservingTransaction::from_bytes(&value.encoded_transaction_data) + .map(|tx| tx.into()) + } } } } @@ -173,14 +162,14 @@ impl TransactionBody { TreeHashType::from(hasher.finalize_fixed()) } - fn to_bytes(&self) -> Vec { + pub fn to_bytes(&self) -> Vec { // TODO: Remove `unwrap` by implementing a `to_bytes` method // that deterministically encodes all transaction fields to bytes // and guarantees serialization will succeed. serde_json::to_vec(&self).unwrap() } - fn from_bytes(bytes: Vec) -> Self { + pub fn from_bytes(bytes: Vec) -> Self { serde_json::from_slice(&bytes).unwrap() } @@ -190,171 +179,31 @@ impl TransactionBody { } } -type TransactionHash = [u8; 32]; pub type TransactionSignature = Signature; pub type SignaturePublicKey = VerifyingKey; pub type SignaturePrivateKey = SigningKey; -/// A container for a transaction body with a signature. -/// Meant to be sent through the network to the sequencer -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -pub struct Transaction { - body: TransactionBody, - pub signature: TransactionSignature, - pub public_key: VerifyingKey, -} - -impl Transaction { - /// Returns a new transaction signed with the provided `private_key`. - /// The signature is generated over the hash of the body as computed by `body.hash()` - pub fn new(body: TransactionBody, private_key: SigningKey) -> Transaction { - let signature: TransactionSignature = private_key.sign(&body.to_bytes()); - let public_key = VerifyingKey::from(&private_key); - Self { - body, - signature, - public_key, - } - } - - /// Converts the transaction into an `AuthenticatedTransaction` by verifying its signature. - /// Returns an error if the signature verification fails. - pub fn into_authenticated(self) -> Result { - let hash = self.body.hash(); - - self.public_key - .verify(&self.body.to_bytes(), &self.signature) - .map_err(|_| TransactionSignatureError::InvalidSignature)?; - - Ok(AuthenticatedTransaction { - hash, - transaction: self, - }) - } - - /// Returns the body of the transaction - pub fn body(&self) -> &TransactionBody { - &self.body - } -} - -impl Transaction { - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - let body_bytes = self.body.to_bytes(); - let signature_bytes = self.signature.to_bytes(); - let public_key_bytes = self.public_key.to_sec1_bytes(); - - let body_bytes_len = body_bytes.len() as u32; - let signature_bytes_len = signature_bytes.len() as u32; - let public_key_bytes_len = public_key_bytes.len() as u32; - - bytes.extend_from_slice(&body_bytes_len.to_le_bytes()); - bytes.extend_from_slice(&signature_bytes_len.to_le_bytes()); - bytes.extend_from_slice(&public_key_bytes_len.to_le_bytes()); - - bytes.extend_from_slice(&body_bytes); - bytes.extend_from_slice(&signature_bytes); - bytes.extend_from_slice(&public_key_bytes); - - bytes - } - - // TODO: Improve error handling. Remove unwraps. - pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Self { - let body_bytes_len = u32_from_cursor(cursor) as usize; - let signature_bytes_len = u32_from_cursor(cursor) as usize; - let public_key_bytes_len = u32_from_cursor(cursor) as usize; - - let mut body_bytes = Vec::with_capacity(body_bytes_len); - let mut signature_bytes = Vec::with_capacity(signature_bytes_len); - let mut public_key_bytes = Vec::with_capacity(public_key_bytes_len); - - cursor.read_exact(&mut body_bytes).unwrap(); - let body = TransactionBody::from_bytes(body_bytes); - - cursor.read_exact(&mut signature_bytes).unwrap(); - let signature = Signature::from_bytes(signature_bytes.as_slice().try_into().unwrap()).unwrap(); - - cursor.read_exact(&mut public_key_bytes).unwrap(); - let public_key = VerifyingKey::from_sec1_bytes(&public_key_bytes).unwrap(); - - Self { body, signature, public_key } - } -} - -/// A transaction with a valid signature over the hash of its body. -/// Can only be constructed from an `Transaction` -/// if the signature is valid -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AuthenticatedTransaction { - hash: TransactionHash, - transaction: Transaction, -} - -impl AuthenticatedTransaction { - /// Returns the underlying transaction - pub fn transaction(&self) -> &Transaction { - &self.transaction - } - - pub fn into_transaction(self) -> Transaction { - self.transaction - } - - /// Returns the precomputed hash over the body of the transaction - pub fn hash(&self) -> &TransactionHash { - &self.hash - } -} - -impl AuthenticatedTransaction { - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - bytes.extend_from_slice(&self.hash); - bytes.extend_from_slice(&self.transaction.to_bytes()); - bytes - } - - // TODO: Improve error handling. Remove unwraps. - pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Self { - let mut hash: [u8; 32] = [0; 32]; - cursor.read_exact(&mut hash).unwrap(); - let transaction = Transaction::from_cursor(cursor); - Self { hash, transaction } - } -} - #[cfg(test)] mod tests { - use super::*; - use k256::{ecdsa::signature::Signer, FieldBytes}; use sha2::{digest::FixedOutput, Digest}; use crate::{ - merkle_tree_public::TreeHashType, - transaction::{Transaction, TransactionBody, TxKind}, + transaction::{TransactionBody, TxKind}, + TreeHashType, }; fn test_transaction_body() -> TransactionBody { TransactionBody { tx_kind: TxKind::Public, - encoded_transaction_data: vec![1,2,3,4], + encoded_transaction_data: vec![1, 2, 3, 4], } } - fn test_transaction() -> Transaction { - let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - Transaction::new(body, private_key) - } - #[test] fn test_transaction_hash_is_sha256_of_json_bytes() { let body = test_transaction_body(); let expected_hash = { - let data = serde_json::to_vec(&body).unwrap(); + let data = body.to_bytes(); let mut hasher = sha2::Sha256::new(); hasher.update(&data); TreeHashType::from(hasher.finalize_fixed()) @@ -366,81 +215,12 @@ mod tests { } #[test] - fn test_transaction_constructor() { + fn test_to_bytes_from_bytes() { let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - let transaction = Transaction::new(body.clone(), private_key.clone()); - assert_eq!( - transaction.public_key, - SignaturePublicKey::from(&private_key) - ); - assert_eq!(transaction.body, body); - } - #[test] - fn test_transaction_body_getter() { - let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - let transaction = Transaction::new(body.clone(), private_key.clone()); - assert_eq!(transaction.body(), &body); - } + let body_bytes = body.to_bytes(); + let body_new = TransactionBody::from_bytes(body_bytes); - #[test] - fn test_into_authenticated_succeeds_for_valid_signature() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - - let signature = authenticated_tx.transaction().signature; - let hash = authenticated_tx.hash(); - - assert_eq!(authenticated_tx.transaction(), &transaction); - assert_eq!(hash, &transaction.body.hash()); - assert!(authenticated_tx - .transaction() - .public_key - .verify(&transaction.body.to_bytes(), &signature) - .is_ok()); - } - - #[test] - fn test_into_authenticated_fails_for_invalid_signature() { - let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - let transaction = { - let mut this = Transaction::new(body, private_key.clone()); - // Modify the signature to make it invalid - // We do this by changing it to the signature of something else - this.signature = private_key.sign(b"deadbeef"); - this - }; - - matches!( - transaction.into_authenticated(), - Err(TransactionSignatureError::InvalidSignature) - ); - } - - #[test] - fn test_authenticated_transaction_getter() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - assert_eq!(authenticated_tx.transaction(), &transaction); - } - - #[test] - fn test_authenticated_transaction_hash_getter() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - assert_eq!(authenticated_tx.hash(), &transaction.body.hash()); - } - - #[test] - fn test_authenticated_transaction_into_transaction() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - assert_eq!(authenticated_tx.into_transaction(), transaction); + assert_eq!(body, body_new); } } diff --git a/common/src/utxo_commitment.rs b/common/src/utxo_commitment.rs index 75c0543..1b42421 100644 --- a/common/src/utxo_commitment.rs +++ b/common/src/utxo_commitment.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::merkle_tree_public::TreeHashType; +use crate::TreeHashType; //ToDo: Update UTXO Commitment model, when it is clear #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/integration_tests/configs/debug/sequencer/sequencer_config.json b/integration_tests/configs/debug/sequencer/sequencer_config.json index 37bfd0a..3317a11 100644 --- a/integration_tests/configs/debug/sequencer/sequencer_config.json +++ b/integration_tests/configs/debug/sequencer/sequencer_config.json @@ -15,5 +15,7 @@ "addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", "balance": 20000 } - ] + ], + "signing_key": [37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, +37, 37, 37, 37, 37, 37] } diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 47342ec..739e160 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,4 +1,4 @@ -use common::merkle_tree_public::TreeHashType; +use common::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, FieldBytes, Scalar}; use rand::{rngs::OsRng, RngCore}; diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/privacy_preserving_transaction/encoding.rs index fe70e65..ff5a269 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/privacy_preserving_transaction/encoding.rs @@ -7,7 +7,11 @@ use nssa_core::{ }; use crate::{ - error::NssaError, privacy_preserving_transaction::{circuit::Proof, message::EncryptedAccountData, witness_set::WitnessSet}, Address, PrivacyPreservingTransaction, PublicKey, Signature + Address, PrivacyPreservingTransaction, PublicKey, Signature, + error::NssaError, + privacy_preserving_transaction::{ + circuit::Proof, message::EncryptedAccountData, witness_set::WitnessSet, + }, }; use super::message::Message; diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 2e3570b..40b3642 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -15,6 +15,7 @@ elliptic-curve.workspace = true k256.workspace = true tiny-keccak.workspace = true tempfile.workspace = true +chrono.workspace = true [dependencies.storage] path = "../storage" diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index 4a99f0b..cdf79a3 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -27,4 +27,6 @@ pub struct SequencerConfig { pub port: u16, ///List of initial accounts data pub initial_accounts: Vec, + ///Sequencer own signing key + pub signing_key: [u8; 32], } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 2137c8b..0ce778a 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -1,10 +1,11 @@ use std::fmt::Display; use anyhow::Result; -use common::{block::HashableBlockData, merkle_tree_public::TreeHashType}; +use common::{block::HashableBlockData, transaction::TransactionBody, TreeHashType}; use config::SequencerConfig; -use log::warn; +use log::{info, warn}; use mempool::MemPool; +use nssa::NSSATransaction; use sequencer_store::SequecerChainStore; use serde::{Deserialize, Serialize}; @@ -13,7 +14,7 @@ pub mod sequencer_store; pub struct SequencerCore { pub store: SequecerChainStore, - pub mempool: MemPool, + pub mempool: MemPool, pub sequencer_config: SequencerConfig, pub chain_height: u64, } @@ -51,6 +52,7 @@ impl SequencerCore { config.genesis_id, config.is_genesis_random, &config.initial_accounts, + nssa::PrivateKey::try_new(config.signing_key).unwrap(), ), mempool: MemPool::default(), chain_height: config.genesis_id, @@ -60,20 +62,39 @@ impl SequencerCore { pub fn transaction_pre_check( &mut self, - tx: nssa::PublicTransaction, - ) -> Result { + tx: NSSATransaction, + ) -> Result { // Stateless checks here - if tx.witness_set().is_valid_for(tx.message()) { - Ok(tx) - } else { - Err(TransactionMalformationErrorKind::InvalidSignature) + match tx { + NSSATransaction::Public(tx) => { + if tx.witness_set().is_valid_for(tx.message()) { + Ok(NSSATransaction::Public(tx)) + } else { + Err(TransactionMalformationErrorKind::InvalidSignature) + } + } + NSSATransaction::PrivacyPreserving(tx) => { + if tx.witness_set().signatures_are_valid_for(tx.message()) { + Ok(NSSATransaction::PrivacyPreserving(tx)) + } else { + Err(TransactionMalformationErrorKind::InvalidSignature) + } + } } } pub fn push_tx_into_mempool_pre_check( &mut self, - transaction: nssa::PublicTransaction, + transaction: TransactionBody, ) -> Result<(), TransactionMalformationErrorKind> { + let transaction = NSSATransaction::try_from(&transaction).map_err(|_| { + TransactionMalformationErrorKind::FailedToDecode { + tx: transaction.hash(), + } + })?; + + info!("Transaction got {transaction:#?}"); + let mempool_size = self.mempool.len(); if mempool_size >= self.sequencer_config.max_num_tx_in_block { return Err(TransactionMalformationErrorKind::MempoolFullForRound); @@ -83,19 +104,29 @@ impl SequencerCore { .transaction_pre_check(transaction) .inspect_err(|err| warn!("Error at pre_check {err:#?}"))?; - self.mempool.push_item(authenticated_tx); + self.mempool.push_item(authenticated_tx.into()); Ok(()) } fn execute_check_transaction_on_state( &mut self, - tx: nssa::PublicTransaction, - ) -> Result { - self.store - .state - .transition_from_public_transaction(&tx) - .inspect_err(|err| warn!("Error at transition {err:#?}"))?; + tx: NSSATransaction, + ) -> Result { + match &tx { + NSSATransaction::Public(tx) => { + self.store + .state + .transition_from_public_transaction(tx) + .inspect_err(|err| warn!("Error at transition {err:#?}"))?; + } + NSSATransaction::PrivacyPreserving(tx) => { + self.store + .state + .transition_from_privacy_preserving_transaction(tx) + .inspect_err(|err| warn!("Error at transition {err:#?}"))?; + } + } Ok(tx) } @@ -108,25 +139,39 @@ impl SequencerCore { .mempool .pop_size(self.sequencer_config.max_num_tx_in_block); - let valid_transactions: Vec<_> = transactions + let mut nssa_transactions = vec![]; + + for tx in transactions { + let nssa_transaction = NSSATransaction::try_from(&tx) + .map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx.hash() })?; + + nssa_transactions.push(nssa_transaction); + } + + let valid_transactions: Vec<_> = nssa_transactions .into_iter() .filter_map(|tx| self.execute_check_transaction_on_state(tx).ok()) + .map(Into::into) .collect(); let prev_block_hash = self .store .block_store .get_block_at_id(self.chain_height)? + .header .hash; + let curr_time = chrono::Utc::now().timestamp_millis() as u64; + let hashable_data = HashableBlockData { block_id: new_block_height, prev_block_id: self.chain_height, transactions: valid_transactions, prev_block_hash, + timestamp: curr_time, }; - let block = hashable_data.into(); + let block = hashable_data.into_block(&self.store.block_store.signing_key); self.store.block_store.put_block_at_id(block)?; @@ -138,10 +183,18 @@ impl SequencerCore { #[cfg(test)] mod tests { + use common::test_utils::sequencer_sign_key_for_testing; + use crate::config::AccountInitialData; use super::*; + fn parse_unwrap_tx_body_into_nssa_tx(tx_body: TransactionBody) -> NSSATransaction { + NSSATransaction::try_from(&tx_body) + .map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx_body.hash() }) + .unwrap() + } + fn setup_sequencer_config_variable_initial_accounts( initial_accounts: Vec, ) -> SequencerConfig { @@ -157,6 +210,7 @@ mod tests { block_create_timeout_millis: 1000, port: 8080, initial_accounts, + signing_key: *sequencer_sign_key_for_testing().value(), } } @@ -298,7 +352,7 @@ mod tests { common_setup(&mut sequencer); let tx = common::test_utils::produce_dummy_empty_transaction(); - let result = sequencer.transaction_pre_check(tx); + let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)); assert!(result.is_ok()); } @@ -324,7 +378,7 @@ mod tests { let tx = common::test_utils::create_transaction_native_token_transfer( acc1, 0, acc2, 10, sign_key1, ); - let result = sequencer.transaction_pre_check(tx); + let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)); assert!(result.is_ok()); } @@ -352,7 +406,9 @@ mod tests { ); // Signature is valid, stateless check pass - let tx = sequencer.transaction_pre_check(tx).unwrap(); + let tx = sequencer + .transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)) + .unwrap(); // Signature is not from sender. Execution fails let result = sequencer.execute_check_transaction_on_state(tx); @@ -385,7 +441,7 @@ mod tests { acc1, 0, acc2, 10000000, sign_key1, ); - let result = sequencer.transaction_pre_check(tx); + let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)); //Passed pre-check assert!(result.is_ok()); @@ -421,7 +477,9 @@ mod tests { acc1, 0, acc2, 100, sign_key1, ); - sequencer.execute_check_transaction_on_state(tx).unwrap(); + sequencer + .execute_check_transaction_on_state(parse_unwrap_tx_body_into_nssa_tx(tx)) + .unwrap(); let bal_from = sequencer .store @@ -528,7 +586,7 @@ mod tests { .unwrap(); // Only one should be included in the block - assert_eq!(block.transactions, vec![tx.clone()]); + assert_eq!(block.body.transactions, vec![tx.clone()]); } #[test] @@ -563,7 +621,7 @@ mod tests { .block_store .get_block_at_id(current_height) .unwrap(); - assert_eq!(block.transactions, vec![tx.clone()]); + assert_eq!(block.body.transactions, vec![tx.clone()]); // Add same transaction should fail sequencer.mempool.push_item(tx); @@ -575,6 +633,6 @@ mod tests { .block_store .get_block_at_id(current_height) .unwrap(); - assert!(block.transactions.is_empty()); + assert!(block.body.transactions.is_empty()); } } diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index 3c05792..b3b74ee 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, path::Path}; use anyhow::Result; -use common::{block::Block, merkle_tree_public::TreeHashType}; +use common::{block::Block, transaction::TransactionBody, TreeHashType}; use storage::RocksDBIO; pub struct SequecerBlockStore { @@ -9,6 +9,7 @@ pub struct SequecerBlockStore { // TODO: Consider adding the hashmap to the database for faster recovery. tx_hash_to_block_map: HashMap, pub genesis_id: u64, + pub signing_key: nssa::PrivateKey, } impl SequecerBlockStore { @@ -16,7 +17,11 @@ impl SequecerBlockStore { /// Creates files if necessary. /// /// ATTENTION: Will overwrite genesis block. - pub fn open_db_with_genesis(location: &Path, genesis_block: Option) -> Result { + pub fn open_db_with_genesis( + location: &Path, + genesis_block: Option, + signing_key: nssa::PrivateKey, + ) -> Result { let tx_hash_to_block_map = if let Some(block) = &genesis_block { block_to_transactions_map(block) } else { @@ -31,16 +36,17 @@ impl SequecerBlockStore { dbio, genesis_id, tx_hash_to_block_map, + signing_key, }) } ///Reopening existing database - pub fn open_db_restart(location: &Path) -> Result { - SequecerBlockStore::open_db_with_genesis(location, None) + pub fn open_db_restart(location: &Path, signing_key: nssa::PrivateKey) -> Result { + SequecerBlockStore::open_db_with_genesis(location, None, signing_key) } pub fn get_block_at_id(&self, id: u64) -> Result { - Ok(self.dbio.get_block(id)?) + Ok(self.dbio.get_block(id)?.into_block(&self.signing_key)) } pub fn put_block_at_id(&mut self, block: Block) -> Result<()> { @@ -51,11 +57,11 @@ impl SequecerBlockStore { } /// Returns the transaction corresponding to the given hash, if it exists in the blockchain. - pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option { + pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option { let block_id = self.tx_hash_to_block_map.get(&hash); let block = block_id.map(|&id| self.get_block_at_id(id)); if let Some(Ok(block)) = block { - for transaction in block.transactions.into_iter() { + for transaction in block.body.transactions.into_iter() { if transaction.hash() == hash { return Some(transaction); } @@ -67,9 +73,10 @@ impl SequecerBlockStore { fn block_to_transactions_map(block: &Block) -> HashMap { block + .body .transactions .iter() - .map(|transaction| (transaction.hash(), block.block_id)) + .map(|transaction| (transaction.hash(), block.header.block_id)) .collect() } @@ -77,11 +84,7 @@ fn block_to_transactions_map(block: &Block) -> HashMap { mod tests { use super::*; - use common::block::{BlockBody, BlockHeader}; - use k256::{ - ecdsa::{signature::SignerMut, Signature, SigningKey}, - FieldBytes, - }; + use common::{block::HashableBlockData, test_utils::sequencer_sign_key_for_testing}; use tempfile::tempdir; #[test] @@ -89,25 +92,21 @@ mod tests { let temp_dir = tempdir().unwrap(); let path = temp_dir.path(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let mut private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); + let signing_key = sequencer_sign_key_for_testing(); - let genesis_block = Block { - header: BlockHeader { - block_id: 0, - prev_block_id: 0, - prev_block_hash: [0; 32], - hash: [1; 32], - timestamp: 0, - signature: private_key.sign(&[42; 32]), - }, - body: BlockBody { - transactions: vec![], - }, + let genesis_block_hashable_data = HashableBlockData { + block_id: 0, + prev_block_id: 0, + prev_block_hash: [0; 32], + timestamp: 0, + transactions: vec![], }; + + let genesis_block = genesis_block_hashable_data.into_block(&signing_key); // Start an empty node store let mut node_store = - SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block)).unwrap(); + SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block), signing_key) + .unwrap(); let tx = common::test_utils::produce_dummy_empty_transaction(); let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]); diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index 4254ed7..520ec4f 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -20,6 +20,7 @@ impl SequecerChainStore { genesis_id: u64, is_genesis_random: bool, initial_accounts: &[AccountInitialData], + signing_key: nssa::PrivateKey, ) -> Self { let init_accs: Vec<(Address, u128)> = initial_accounts .iter() @@ -36,20 +37,24 @@ impl SequecerChainStore { OsRng.fill_bytes(&mut prev_block_hash); } + let curr_time = chrono::Utc::now().timestamp_millis() as u64; + let hashable_data = HashableBlockData { block_id: genesis_id, prev_block_id: genesis_id.saturating_sub(1), transactions: vec![], prev_block_hash, + timestamp: curr_time, }; - let genesis_block = hashable_data.into(); + let genesis_block = hashable_data.into_block(&signing_key); //Sequencer should panic if unable to open db, //as fixing this issue may require actions non-native to program scope let block_store = SequecerBlockStore::open_db_with_genesis( &home_dir.join("rocksdb"), Some(genesis_block), + signing_key, ) .unwrap(); diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index d994210..648c19c 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -6,7 +6,6 @@ use serde_json::Value; use common::{ block::HashableBlockData, - merkle_tree_public::TreeHashType, rpc_primitives::{ errors::RpcError, message::{Message, Request}, @@ -17,6 +16,8 @@ use common::{ GetTransactionByHashRequest, GetTransactionByHashResponse, }, }, + transaction::TransactionBody, + TreeHashType, }; use common::rpc_primitives::requests::{ @@ -72,8 +73,7 @@ impl JsonHandler { async fn process_send_tx(&self, request: Request) -> Result { let send_tx_req = SendTxRequest::parse(Some(request.params))?; - let tx = nssa::PublicTransaction::from_bytes(&send_tx_req.transaction) - .map_err(|e| RpcError::serialization_error(&e.to_string()))?; + let tx = TransactionBody::from_bytes(send_tx_req.transaction); let tx_hash = hex::encode(tx.hash()); { @@ -254,7 +254,10 @@ mod tests { use crate::{rpc_handler, JsonHandler}; use base64::{engine::general_purpose, Engine}; - use common::rpc_primitives::RpcPollingConfig; + use common::{ + rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing, + transaction::TransactionBody, + }; use sequencer_core::{ config::{AccountInitialData, SequencerConfig}, @@ -298,14 +301,11 @@ mod tests { block_create_timeout_millis: 1000, port: 8080, initial_accounts, + signing_key: *sequencer_sign_key_for_testing().value(), } } - fn components_for_tests() -> ( - JsonHandler, - Vec, - nssa::PublicTransaction, - ) { + fn components_for_tests() -> (JsonHandler, Vec, TransactionBody) { let config = sequencer_config_for_tests(); let mut sequencer_core = SequencerCore::start_from_config(config); let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone(); diff --git a/storage/src/lib.rs b/storage/src/lib.rs index 3e612b9..35aed64 100644 --- a/storage/src/lib.rs +++ b/storage/src/lib.rs @@ -248,7 +248,7 @@ impl RocksDBIO { Ok(()) } - pub fn get_block(&self, block_id: u64) -> DbResult { + pub fn get_block(&self, block_id: u64) -> DbResult { let cf_block = self.block_column(); let res = self .db @@ -256,7 +256,7 @@ impl RocksDBIO { .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; if let Some(data) = res { - Ok(HashableBlockData::from_bytes(&data).into()) + Ok(HashableBlockData::from_bytes(&data)) } else { Err(DbError::db_interaction_error( "Block on this id not found".to_string(), diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 6f4201c..a803cf8 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -1,14 +1,12 @@ use std::collections::HashMap; use anyhow::Result; -use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree; use key_protocol::key_protocol_core::NSSAUserData; use crate::config::{PersistentAccountData, WalletConfig}; pub struct WalletChainStore { pub user_data: NSSAUserData, - pub utxo_commitments_store: UTXOCommitmentsMerkleTree, pub wallet_config: WalletConfig, } @@ -21,11 +19,8 @@ impl WalletChainStore { .map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key)) .collect(); - let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]); - Ok(Self { user_data: NSSAUserData::new_with_accounts(accounts_keys)?, - utxo_commitments_store, wallet_config: config, }) } @@ -94,11 +89,6 @@ mod tests { let config = create_sample_wallet_config(path.to_path_buf()); - let store = WalletChainStore::new(config.clone()).unwrap(); - - assert_eq!( - store.utxo_commitments_store.get_root().unwrap_or([0; 32]), - [0; 32] - ); + let _ = WalletChainStore::new(config.clone()).unwrap(); } } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 7ce7955..085c6be 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -3,6 +3,7 @@ use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; use base64::Engine; use common::{ sequencer_client::{json::SendTxResponse, SequencerClient}, + transaction::TransactionBody, ExecutionFailureKind, }; @@ -108,7 +109,6 @@ impl WalletCore { balance_to_move, ) .unwrap(); - let signing_key = self.storage.user_data.get_account_signing_key(&from); if let Some(signing_key) = signing_key { @@ -119,7 +119,7 @@ impl WalletCore { let tx = nssa::PublicTransaction::new(message, witness_set); - Ok(self.sequencer_client.send_tx(tx).await?) + Ok(self.sequencer_client.send_tx_public(tx).await?) } else { Err(ExecutionFailureKind::KeyNotFoundError) } @@ -156,13 +156,13 @@ impl WalletCore { pub async fn poll_public_native_token_transfer( &self, hash: String, - ) -> Result { + ) -> Result { let transaction_encoded = self.poller.poll_tx(hash).await?; let tx_base64_decode = base64::engine::general_purpose::STANDARD.decode(transaction_encoded)?; - let pub_tx = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?; + let pub_tx = TransactionBody::from_bytes(tx_base64_decode); - Ok(pub_tx) + Ok(nssa::NSSATransaction::try_from(&pub_tx)?) } } From 9b4506de3b6d5b46c9e537f8626f628edf59e6f0 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Sep 2025 14:41:01 -0300 Subject: [PATCH 10/20] fix sequencer runner --- sequencer_runner/configs/debug/sequencer_config.json | 11 +++++++++-- sequencer_runner/src/lib.rs | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/sequencer_runner/configs/debug/sequencer_config.json b/sequencer_runner/configs/debug/sequencer_config.json index 010a9ba..f7c6369 100644 --- a/sequencer_runner/configs/debug/sequencer_config.json +++ b/sequencer_runner/configs/debug/sequencer_config.json @@ -7,6 +7,13 @@ "block_create_timeout_millis": 10000, "port": 3040, "initial_accounts": [ - + { + "addr": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "balance": 10000 + }, + { + "addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", + "balance": 20000 + } ] -} \ No newline at end of file +} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 625350b..d7a574e 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -75,7 +75,9 @@ pub async fn main_runner() -> Result<()> { } //ToDo: Add restart on failures - let (_, _) = startup_sequencer(app_config).await?; + let (_, main_loop_handle) = startup_sequencer(app_config).await?; + + main_loop_handle.await??; Ok(()) } From 83542b310ceaea9f16a36e641a5eb05ce887f974 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Sep 2025 15:20:40 -0300 Subject: [PATCH 11/20] fix double nonce increment in privacy preserving tx --- .../src/bin/privacy_preserving_circuit.rs | 4 +++- nssa/src/state.rs | 24 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 5ec45eb..2484ec2 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -61,7 +61,9 @@ fn main() { public_pre_states.push(pre_states[i].clone()); let mut post = post_states[i].clone(); - post.nonce += 1; + if pre_states[i].is_authorized { + post.nonce += 1; + } if post.program_owner == DEFAULT_PROGRAM_ID { // Claim account post.program_owner = program_id; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3655b1c..3ba10b3 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -140,12 +140,6 @@ impl V01State { *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(()) } @@ -925,6 +919,13 @@ pub mod tests { &state, ); + let expected_sender_post = { + let mut this = state.get_account_by_address(&sender_keys.address()); + this.balance -= balance_to_move; + this.nonce += 1; + this + }; + let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap(); assert!(!state.private_state.0.contains(&expected_new_commitment)); @@ -932,6 +933,8 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx) .unwrap(); + let sender_post = state.get_account_by_address(&sender_keys.address()); + assert_eq!(sender_post, expected_sender_post); assert!(state.private_state.0.contains(&expected_new_commitment)); assert_eq!( @@ -1024,6 +1027,12 @@ pub mod tests { let balance_to_move = 37; + let expected_recipient_post = { + let mut this = state.get_account_by_address(&recipient_keys.address()); + this.balance += balance_to_move; + this + }; + let tx = deshielded_balance_transfer_for_tests( &sender_keys, &sender_private_account, @@ -1054,6 +1063,9 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx) .unwrap(); + + let recipient_post = state.get_account_by_address(&recipient_keys.address()); + assert_eq!(recipient_post, expected_recipient_post); 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)); From 63517b6b1c7649171978ac6e71b935a62cb13e71 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Sep 2025 16:25:02 -0300 Subject: [PATCH 12/20] add circuit tests --- nssa/src/state.rs | 418 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 417 insertions(+), 1 deletion(-) diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3ba10b3..511ad88 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1063,7 +1063,6 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx) .unwrap(); - let recipient_post = state.get_account_by_address(&recipient_keys.address()); assert_eq!(recipient_post, expected_recipient_post); assert!(state.private_state.0.contains(&sender_pre_commitment)); @@ -1327,4 +1326,421 @@ pub mod tests { assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } + + #[test] + fn test_circuit_fails_if_insufficient_nonces_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting only one nonce for an execution with two private accounts. + let private_account_nonces = [0xdeadbeef1]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &private_account_nonces, + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_insufficient_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting only one key for an execution with two private accounts. + let private_account_keys = [( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + )]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_insufficient_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting no auth key for an execution with one non default private accounts. + let private_account_auth = []; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_invalid_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let private_account_keys = [ + // First private account is the sender + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + // Second private account is the recipient + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ]; + let private_account_auth = [ + // Setting the recipient key to authorize the sender. + // This should be set to the sender private account in + // a normal circumstance. The recipient can't authorize this. + (recipient_keys.nsk, (0, vec![])), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account { + // Non default balance + balance: 1, + ..Account::default() + }, + is_authorized: false, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided() + { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account { + // Non default program_owner + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + ..Account::default() + }, + is_authorized: false, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account { + // Non default data + data: b"hola mundo".to_vec(), + ..Account::default() + }, + is_authorized: false, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account { + // Non default nonce + nonce: 0xdeadbeef, + ..Account::default() + }, + is_authorized: false, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized() + { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + // This should be set to false in normal circumstances + is_authorized: true, + }; + + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_invalid_visibility_mask_value() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let public_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + let visibility_mask = [0, 3]; + let result = execute_and_prove( + &[public_account_1, public_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[], + &[], + &[], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } From efb7108c58b2cd93bb87298222a27c2839444820 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 3 Sep 2025 16:44:55 -0300 Subject: [PATCH 13/20] add more tests --- .../src/bin/privacy_preserving_circuit.rs | 2 +- nssa/src/state.rs | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 2484ec2..61f431c 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -140,7 +140,7 @@ fn main() { } if private_keys_iter.next().is_some() { - panic!("Too many private accounts keys."); + panic!("Too many private account keys."); } if private_auth_iter.next().is_some() { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 511ad88..aad03ee 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1743,4 +1743,141 @@ pub mod tests { assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } + + #[test] + fn test_circuit_should_fail_with_too_many_nonces() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting three new private account nonces for a circuit execution with only two private + // accounts. + let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &private_account_nonces, + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_too_many_private_account_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting three private account keys for a circuit execution with only two private + // accounts. + let private_account_keys = [ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ( + sender_keys.npk(), + SharedSecretKey::new(&[57; 32], &sender_keys.ivk()), + ), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &[1, 2], + &[0xdeadbeef1, 0xdeadbeef2], + &private_account_keys, + &[(sender_keys.nsk, (0, vec![]))], + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_should_fail_with_too_many_private_account_auth_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata { + account: Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + is_authorized: true, + }; + let private_account_2 = AccountWithMetadata { + account: Account::default(), + is_authorized: false, + }; + + // Setting two private account keys for a circuit execution with only one non default + // private account (visibility mask equal to 1 means that auth keys are expected). + let visibility_mask = [1, 2]; + let private_account_auth = [ + (sender_keys.nsk, (0, vec![])), + (recipient_keys.nsk, (1, vec![])), + ]; + let result = execute_and_prove( + &[private_account_1, private_account_2], + &Program::serialize_instruction(10u128).unwrap(), + &visibility_mask, + &[0xdeadbeef1, 0xdeadbeef2], + &[ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + &private_account_auth, + &program, + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } From 5747070e395372795f02132ff02d0f2a376b99c0 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 09:09:32 +0300 Subject: [PATCH 14/20] fix: merge fixes --- common/src/block.rs | 2 +- common/src/lib.rs | 2 +- common/src/transaction.rs | 67 +------------------ sequencer_core/src/lib.rs | 2 +- .../src/sequencer_store/block_store.rs | 2 +- sequencer_rpc/src/process.rs | 6 +- wallet/src/lib.rs | 5 +- 7 files changed, 11 insertions(+), 75 deletions(-) diff --git a/common/src/block.rs b/common/src/block.rs index 24da8c7..5e256d8 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,7 +1,7 @@ use rs_merkle::Hasher; use std::io::{Cursor, Read}; -use crate::{transaction::TransactionBody, OwnHasher}; +use crate::{OwnHasher, transaction::TransactionBody}; pub type BlockHash = [u8; 32]; pub type BlockId = u64; diff --git a/common/src/lib.rs b/common/src/lib.rs index a0e6df8..67d628d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1,6 @@ use rs_merkle::Hasher; use serde::Deserialize; -use sha2::{digest::FixedOutput, Digest, Sha256}; +use sha2::{Digest, Sha256, digest::FixedOutput}; pub mod block; pub mod commitment; diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 336635d..956853f 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,7 +1,4 @@ -use k256::ecdsa::{ - Signature, SigningKey, VerifyingKey, - signature::{Signer, Verifier}, -}; +use k256::ecdsa::{Signature, SigningKey, VerifyingKey}; use log::info; use serde::{Deserialize, Serialize}; @@ -188,14 +185,11 @@ pub type SignaturePrivateKey = SigningKey; #[cfg(test)] mod tests { - use super::*; - use k256::{FieldBytes, ecdsa::signature::Signer}; - use secp256k1_zkp::{Tweak, constants::SECRET_KEY_SIZE}; use sha2::{Digest, digest::FixedOutput}; use crate::{ - transaction::{TransactionBody, TxKind}, TreeHashType, + transaction::{TransactionBody, TxKind}, }; fn test_transaction_body() -> TransactionBody { @@ -228,62 +222,5 @@ mod tests { let body_new = TransactionBody::from_bytes(body_bytes); assert_eq!(body, body_new); - #[test] - fn test_into_authenticated_succeeds_for_valid_signature() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - - let signature = authenticated_tx.transaction().signature; - let hash = authenticated_tx.hash(); - - assert_eq!(authenticated_tx.transaction(), &transaction); - assert_eq!(hash, &transaction.body.hash()); - assert!( - authenticated_tx - .transaction() - .public_key - .verify(&transaction.body.to_bytes(), &signature) - .is_ok() - ); - } - - #[test] - fn test_into_authenticated_fails_for_invalid_signature() { - let body = test_transaction_body(); - let key_bytes = FieldBytes::from_slice(&[37; 32]); - let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap(); - let transaction = { - let mut this = Transaction::new(body, private_key.clone()); - // Modify the signature to make it invalid - // We do this by changing it to the signature of something else - this.signature = private_key.sign(b"deadbeef"); - this - }; - - matches!( - transaction.into_authenticated(), - Err(TransactionSignatureError::InvalidSignature) - ); - } - - #[test] - fn test_authenticated_transaction_getter() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - assert_eq!(authenticated_tx.transaction(), &transaction); - } - - #[test] - fn test_authenticated_transaction_hash_getter() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - assert_eq!(authenticated_tx.hash(), &transaction.body.hash()); - } - - #[test] - fn test_authenticated_transaction_into_transaction() { - let transaction = test_transaction(); - let authenticated_tx = transaction.clone().into_authenticated().unwrap(); - assert_eq!(authenticated_tx.into_transaction(), transaction); } } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 0ce778a..8571d36 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use anyhow::Result; -use common::{block::HashableBlockData, transaction::TransactionBody, TreeHashType}; +use common::{TreeHashType, block::HashableBlockData, transaction::TransactionBody}; use config::SequencerConfig; use log::{info, warn}; use mempool::MemPool; diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index b3b74ee..4c8f25e 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, path::Path}; use anyhow::Result; -use common::{block::Block, transaction::TransactionBody, TreeHashType}; +use common::{TreeHashType, block::Block, transaction::TransactionBody}; use storage::RocksDBIO; pub struct SequecerBlockStore { diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index fb06727..a9f828e 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -5,6 +5,7 @@ use sequencer_core::config::AccountInitialData; use serde_json::Value; use common::{ + TreeHashType, block::HashableBlockData, rpc_primitives::{ errors::RpcError, @@ -18,7 +19,6 @@ use common::{ }, }, transaction::TransactionBody, - TreeHashType, }; use common::rpc_primitives::requests::{ @@ -276,8 +276,8 @@ impl JsonHandler { mod tests { use std::sync::Arc; - use crate::{rpc_handler, JsonHandler}; - use base64::{engine::general_purpose, Engine}; + use crate::{JsonHandler, rpc_handler}; + use base64::{Engine, engine::general_purpose}; use common::{ rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing, transaction::TransactionBody, diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 6aa9522..ae564d3 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -2,10 +2,9 @@ use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; use base64::Engine; use common::{ - sequencer_client::{json::SendTxResponse, SequencerClient}, - transaction::TransactionBody, ExecutionFailureKind, sequencer_client::{SequencerClient, json::SendTxResponse}, + transaction::TransactionBody, }; use anyhow::Result; @@ -122,7 +121,7 @@ impl WalletCore { let tx = nssa::PublicTransaction::new(message, witness_set); - Ok(self.sequencer_client.send_tx(tx).await?) + Ok(self.sequencer_client.send_tx_public(tx).await?) } else { Err(ExecutionFailureKind::InsufficientFundsError) } From 0e1905ad228a1fc2464937e858eca927e907e2b2 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 10:11:04 +0300 Subject: [PATCH 15/20] fix: suggestions fix --- common/src/block.rs | 15 ++----- common/src/sequencer_client/mod.rs | 4 +- common/src/test_utils.rs | 17 +++----- common/src/transaction.rs | 42 ++++++++++++------ nssa/src/encoding/mod.rs | 2 + .../privacy_preserving_transaction.rs} | 6 +-- .../public_transaction.rs} | 0 nssa/src/lib.rs | 19 +------- .../privacy_preserving_transaction/circuit.rs | 11 ++++- .../src/privacy_preserving_transaction/mod.rs | 1 - .../transaction.rs | 39 +++++++++++++++++ .../witness_set.rs | 4 +- nssa/src/public_transaction/mod.rs | 1 - nssa/src/public_transaction/witness_set.rs | 2 +- sequencer_core/src/lib.rs | 43 +++++++++---------- .../src/sequencer_store/block_store.rs | 5 +-- sequencer_core/src/sequencer_store/mod.rs | 1 - sequencer_rpc/src/process.rs | 8 ++-- wallet/src/lib.rs | 11 ++--- 19 files changed, 131 insertions(+), 100 deletions(-) create mode 100644 nssa/src/encoding/mod.rs rename nssa/src/{privacy_preserving_transaction/encoding.rs => encoding/privacy_preserving_transaction.rs} (98%) rename nssa/src/{public_transaction/encoding.rs => encoding/public_transaction.rs} (100%) diff --git a/common/src/block.rs b/common/src/block.rs index 5e256d8..64bce30 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,7 +1,7 @@ use rs_merkle::Hasher; use std::io::{Cursor, Read}; -use crate::{OwnHasher, transaction::TransactionBody}; +use crate::{OwnHasher, transaction::EncodedTransaction}; pub type BlockHash = [u8; 32]; pub type BlockId = u64; @@ -10,7 +10,6 @@ pub type TimeStamp = u64; #[derive(Debug, Clone)] pub struct BlockHeader { pub block_id: BlockId, - pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, pub hash: BlockHash, pub timestamp: TimeStamp, @@ -19,7 +18,7 @@ pub struct BlockHeader { #[derive(Debug, Clone)] pub struct BlockBody { - pub transactions: Vec, + pub transactions: Vec, } #[derive(Debug, Clone)] @@ -31,10 +30,9 @@ pub struct Block { #[derive(Debug, PartialEq, Eq)] pub struct HashableBlockData { pub block_id: BlockId, - pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, pub timestamp: TimeStamp, - pub transactions: Vec, + pub transactions: Vec, } impl HashableBlockData { @@ -45,7 +43,6 @@ impl HashableBlockData { Block { header: BlockHeader { block_id: self.block_id, - prev_block_id: self.prev_block_id, prev_block_hash: self.prev_block_hash, hash, timestamp: self.timestamp, @@ -62,7 +59,6 @@ impl From for HashableBlockData { fn from(value: Block) -> Self { Self { block_id: value.header.block_id, - prev_block_id: value.header.prev_block_id, prev_block_hash: value.header.prev_block_hash, timestamp: value.header.timestamp, transactions: value.body.transactions, @@ -74,7 +70,6 @@ impl HashableBlockData { pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); bytes.extend_from_slice(&self.block_id.to_le_bytes()); - bytes.extend_from_slice(&self.prev_block_id.to_le_bytes()); bytes.extend_from_slice(&self.prev_block_hash); bytes.extend_from_slice(&self.timestamp.to_le_bytes()); let num_transactions: u32 = self.transactions.len() as u32; @@ -94,7 +89,6 @@ impl HashableBlockData { let mut cursor = Cursor::new(data); let block_id = u64_from_cursor(&mut cursor); - let prev_block_id = u64_from_cursor(&mut cursor); let mut prev_block_hash = [0u8; 32]; cursor.read_exact(&mut prev_block_hash).unwrap(); @@ -114,13 +108,12 @@ impl HashableBlockData { tx_bytes.push(buff[0]); } - let tx = TransactionBody::from_bytes(tx_bytes); + let tx = EncodedTransaction::from_bytes(tx_bytes); transactions.push(tx); } Self { block_id, - prev_block_id, prev_block_hash, timestamp, transactions, diff --git a/common/src/sequencer_client/mod.rs b/common/src/sequencer_client/mod.rs index c6da098..126de40 100644 --- a/common/src/sequencer_client/mod.rs +++ b/common/src/sequencer_client/mod.rs @@ -12,7 +12,7 @@ use crate::rpc_primitives::requests::{ GetTransactionByHashResponse, }; use crate::sequencer_client::json::AccountInitialData; -use crate::transaction::TransactionBody; +use crate::transaction::{EncodedTransaction, NSSATransaction}; use crate::{SequencerClientError, SequencerRpcError}; pub mod json; @@ -132,7 +132,7 @@ impl SequencerClient { &self, transaction: nssa::PublicTransaction, ) -> Result { - let transaction = TransactionBody::from(nssa::NSSATransaction::Public(transaction)); + let transaction = EncodedTransaction::from(NSSATransaction::Public(transaction)); let tx_req = SendTxRequest { transaction: transaction.to_bytes(), diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index 7fb58d3..9d68db8 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -1,8 +1,6 @@ -use nssa::NSSATransaction; - use crate::{ block::{Block, HashableBlockData}, - transaction::TransactionBody, + transaction::{EncodedTransaction, NSSATransaction}, }; //Helpers @@ -19,15 +17,14 @@ pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey { /// /// `prev_hash` - hash of previous block, provide None for genesis /// -/// `transactions` - vector of `AuthenticatedTransaction` objects +/// `transactions` - vector of `EncodedTransaction` objects pub fn produce_dummy_block( id: u64, prev_hash: Option<[u8; 32]>, - transactions: Vec, + transactions: Vec, ) -> Block { let block_data = HashableBlockData { block_id: id, - prev_block_id: id.saturating_sub(1), prev_block_hash: prev_hash.unwrap_or_default(), timestamp: id * 100, transactions, @@ -36,7 +33,7 @@ pub fn produce_dummy_block( block_data.into_block(&sequencer_sign_key_for_testing()) } -pub fn produce_dummy_empty_transaction() -> TransactionBody { +pub fn produce_dummy_empty_transaction() -> EncodedTransaction { let program_id = nssa::program::Program::authenticated_transfer_program().id(); let addresses = vec![]; let nonces = vec![]; @@ -49,7 +46,7 @@ pub fn produce_dummy_empty_transaction() -> TransactionBody { let nssa_tx = nssa::PublicTransaction::new(message, witness_set); - TransactionBody::from(NSSATransaction::Public(nssa_tx)) + EncodedTransaction::from(NSSATransaction::Public(nssa_tx)) } pub fn create_transaction_native_token_transfer( @@ -58,7 +55,7 @@ pub fn create_transaction_native_token_transfer( to: [u8; 32], balance_to_move: u128, signing_key: nssa::PrivateKey, -) -> TransactionBody { +) -> EncodedTransaction { let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)]; let nonces = vec![nonce]; let program_id = nssa::program::Program::authenticated_transfer_program().id(); @@ -69,5 +66,5 @@ pub fn create_transaction_native_token_transfer( let nssa_tx = nssa::PublicTransaction::new(message, witness_set); - TransactionBody::from(NSSATransaction::Public(nssa_tx)) + EncodedTransaction::from(NSSATransaction::Public(nssa_tx)) } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 956853f..d196687 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -10,6 +10,24 @@ use elliptic_curve::{ }; use sha2::digest::typenum::{UInt, UTerm}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum NSSATransaction { + Public(nssa::PublicTransaction), + PrivacyPreserving(nssa::PrivacyPreservingTransaction), +} + +impl From for NSSATransaction { + fn from(value: nssa::PublicTransaction) -> Self { + Self::Public(value) + } +} + +impl From for NSSATransaction { + fn from(value: nssa::PrivacyPreservingTransaction) -> Self { + Self::PrivacyPreserving(value) + } +} + use crate::TreeHashType; pub type CipherText = Vec; @@ -24,20 +42,20 @@ pub enum TxKind { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] ///General transaction object -pub struct TransactionBody { +pub struct EncodedTransaction { pub tx_kind: TxKind, ///Encoded blobs of data pub encoded_transaction_data: Vec, } -impl From for TransactionBody { - fn from(value: nssa::NSSATransaction) -> Self { +impl From for EncodedTransaction { + fn from(value: NSSATransaction) -> Self { match value { - nssa::NSSATransaction::Public(tx) => Self { + NSSATransaction::Public(tx) => Self { tx_kind: TxKind::Public, encoded_transaction_data: tx.to_bytes(), }, - nssa::NSSATransaction::PrivacyPreserving(tx) => Self { + NSSATransaction::PrivacyPreserving(tx) => Self { tx_kind: TxKind::PrivacyPreserving, encoded_transaction_data: tx.to_bytes(), }, @@ -45,10 +63,10 @@ impl From for TransactionBody { } } -impl TryFrom<&TransactionBody> for nssa::NSSATransaction { +impl TryFrom<&EncodedTransaction> for NSSATransaction { type Error = nssa::error::NssaError; - fn try_from(value: &TransactionBody) -> Result { + fn try_from(value: &EncodedTransaction) -> Result { match value.tx_kind { TxKind::Public => nssa::PublicTransaction::from_bytes(&value.encoded_transaction_data) .map(|tx| tx.into()), @@ -153,7 +171,7 @@ impl ActionData { } } -impl TransactionBody { +impl EncodedTransaction { /// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`. pub fn hash(&self) -> TreeHashType { let bytes_to_hash = self.to_bytes(); @@ -189,11 +207,11 @@ mod tests { use crate::{ TreeHashType, - transaction::{TransactionBody, TxKind}, + transaction::{EncodedTransaction, TxKind}, }; - fn test_transaction_body() -> TransactionBody { - TransactionBody { + fn test_transaction_body() -> EncodedTransaction { + EncodedTransaction { tx_kind: TxKind::Public, encoded_transaction_data: vec![1, 2, 3, 4], } @@ -219,7 +237,7 @@ mod tests { let body = test_transaction_body(); let body_bytes = body.to_bytes(); - let body_new = TransactionBody::from_bytes(body_bytes); + let body_new = EncodedTransaction::from_bytes(body_bytes); assert_eq!(body, body_new); } diff --git a/nssa/src/encoding/mod.rs b/nssa/src/encoding/mod.rs new file mode 100644 index 0000000..5ff45e2 --- /dev/null +++ b/nssa/src/encoding/mod.rs @@ -0,0 +1,2 @@ +pub mod privacy_preserving_transaction; +pub mod public_transaction; diff --git a/nssa/src/privacy_preserving_transaction/encoding.rs b/nssa/src/encoding/privacy_preserving_transaction.rs similarity index 98% rename from nssa/src/privacy_preserving_transaction/encoding.rs rename to nssa/src/encoding/privacy_preserving_transaction.rs index ff5a269..7c49938 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/encoding/privacy_preserving_transaction.rs @@ -10,12 +10,12 @@ use crate::{ Address, PrivacyPreservingTransaction, PublicKey, Signature, error::NssaError, privacy_preserving_transaction::{ - circuit::Proof, message::EncryptedAccountData, witness_set::WitnessSet, + circuit::Proof, + message::{EncryptedAccountData, Message}, + witness_set::WitnessSet, }, }; -use super::message::Message; - const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/"; diff --git a/nssa/src/public_transaction/encoding.rs b/nssa/src/encoding/public_transaction.rs similarity index 100% rename from nssa/src/public_transaction/encoding.rs rename to nssa/src/encoding/public_transaction.rs diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index 74c58b1..643fc7a 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -1,4 +1,5 @@ pub mod address; +pub mod encoding; pub mod error; mod merkle_tree; mod privacy_preserving_transaction; @@ -16,21 +17,3 @@ pub use signature::PrivateKey; pub use signature::PublicKey; pub use signature::Signature; pub use state::V01State; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum NSSATransaction { - Public(PublicTransaction), - PrivacyPreserving(PrivacyPreservingTransaction), -} - -impl From for NSSATransaction { - fn from(value: PublicTransaction) -> Self { - Self::Public(value) - } -} - -impl From for NSSATransaction { - fn from(value: PrivacyPreservingTransaction) -> Self { - Self::PrivacyPreserving(value) - } -} diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 4cd4526..a8e37cf 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -14,7 +14,7 @@ use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT /// Proof of the privacy preserving execution circuit #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Proof(Vec); +pub struct Proof(pub(super) Vec); impl Proof { pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { @@ -34,7 +34,14 @@ impl Proof { pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { let proof_len = u32_from_cursor(cursor) as usize; let mut proof = Vec::with_capacity(proof_len); - cursor.read_exact(&mut proof)?; + + for _ in 0..proof_len { + let mut one_byte_buf = [0u8]; + + cursor.read_exact(&mut one_byte_buf)?; + + proof.push(one_byte_buf[0]); + } Ok(Self(proof)) } } diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 54fc94b..c74c077 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -1,4 +1,3 @@ -mod encoding; pub mod message; pub mod transaction; pub mod witness_set; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 9aac54e..4ecac7f 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -167,3 +167,42 @@ fn n_unique(data: &[T]) -> usize { let set: HashSet<&T> = data.iter().collect(); set.len() } + +#[cfg(test)] +mod tests { + use crate::{ + Address, PrivacyPreservingTransaction, PrivateKey, PublicKey, + privacy_preserving_transaction::{ + circuit::Proof, message::tests::message_for_tests, witness_set::WitnessSet, + }, + }; + + fn keys_for_tests() -> (PrivateKey, PrivateKey, Address, Address) { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let addr1 = Address::from(&PublicKey::new_from_private_key(&key1)); + let addr2 = Address::from(&PublicKey::new_from_private_key(&key2)); + (key1, key2, addr1, addr2) + } + + fn proof_for_tests() -> Proof { + Proof(vec![1, 2, 3, 4, 5]) + } + + fn transaction_for_tests() -> PrivacyPreservingTransaction { + let (key1, key2, _, _) = keys_for_tests(); + + let message = message_for_tests(); + + let witness_set = WitnessSet::for_message(&message, proof_for_tests(), &[&key1, &key2]); + PrivacyPreservingTransaction::new(message, witness_set) + } + + #[test] + fn test_privacy_preserving_transaction_encoding_bytes_roundtrip() { + let tx = transaction_for_tests(); + let bytes = tx.to_bytes(); + let tx_from_bytes = PrivacyPreservingTransaction::from_bytes(&bytes).unwrap(); + assert_eq!(tx, tx_from_bytes); + } +} diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index fe897ce..9fc587e 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -5,8 +5,8 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct WitnessSet { - pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, - pub(super) proof: Proof, + pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>, + pub(crate) proof: Proof, } impl WitnessSet { diff --git a/nssa/src/public_transaction/mod.rs b/nssa/src/public_transaction/mod.rs index 9ae24cf..278e9df 100644 --- a/nssa/src/public_transaction/mod.rs +++ b/nssa/src/public_transaction/mod.rs @@ -1,4 +1,3 @@ -mod encoding; mod message; mod transaction; mod witness_set; diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index f0e5c6b..e5095ba 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -2,7 +2,7 @@ use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct WitnessSet { - pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, + pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>, } impl WitnessSet { diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 8571d36..59b787e 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -1,11 +1,14 @@ use std::fmt::Display; use anyhow::Result; -use common::{TreeHashType, block::HashableBlockData, transaction::TransactionBody}; +use common::{ + TreeHashType, + block::HashableBlockData, + transaction::{EncodedTransaction, NSSATransaction}, +}; use config::SequencerConfig; -use log::{info, warn}; +use log::warn; use mempool::MemPool; -use nssa::NSSATransaction; use sequencer_store::SequecerChainStore; use serde::{Deserialize, Serialize}; @@ -14,7 +17,7 @@ pub mod sequencer_store; pub struct SequencerCore { pub store: SequecerChainStore, - pub mempool: MemPool, + pub mempool: MemPool, pub sequencer_config: SequencerConfig, pub chain_height: u64, } @@ -85,7 +88,7 @@ impl SequencerCore { pub fn push_tx_into_mempool_pre_check( &mut self, - transaction: TransactionBody, + transaction: EncodedTransaction, ) -> Result<(), TransactionMalformationErrorKind> { let transaction = NSSATransaction::try_from(&transaction).map_err(|_| { TransactionMalformationErrorKind::FailedToDecode { @@ -93,8 +96,6 @@ impl SequencerCore { } })?; - info!("Transaction got {transaction:#?}"); - let mempool_size = self.mempool.len(); if mempool_size >= self.sequencer_config.max_num_tx_in_block { return Err(TransactionMalformationErrorKind::MempoolFullForRound); @@ -135,24 +136,23 @@ impl SequencerCore { pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result { let new_block_height = self.chain_height + 1; - let transactions = self - .mempool - .pop_size(self.sequencer_config.max_num_tx_in_block); + let mut num_valid_transactions_in_block = 0; + let mut valid_transactions = vec![]; - let mut nssa_transactions = vec![]; - - for tx in transactions { + while let Some(tx) = self.mempool.pop_last() { let nssa_transaction = NSSATransaction::try_from(&tx) .map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx.hash() })?; - nssa_transactions.push(nssa_transaction); - } + if let Ok(valid_tx) = self.execute_check_transaction_on_state(nssa_transaction) { + valid_transactions.push(valid_tx.into()); - let valid_transactions: Vec<_> = nssa_transactions - .into_iter() - .filter_map(|tx| self.execute_check_transaction_on_state(tx).ok()) - .map(Into::into) - .collect(); + num_valid_transactions_in_block += 1; + + if num_valid_transactions_in_block >= self.sequencer_config.max_num_tx_in_block { + break; + } + } + } let prev_block_hash = self .store @@ -165,7 +165,6 @@ impl SequencerCore { let hashable_data = HashableBlockData { block_id: new_block_height, - prev_block_id: self.chain_height, transactions: valid_transactions, prev_block_hash, timestamp: curr_time, @@ -189,7 +188,7 @@ mod tests { use super::*; - fn parse_unwrap_tx_body_into_nssa_tx(tx_body: TransactionBody) -> NSSATransaction { + fn parse_unwrap_tx_body_into_nssa_tx(tx_body: EncodedTransaction) -> NSSATransaction { NSSATransaction::try_from(&tx_body) .map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx_body.hash() }) .unwrap() diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index 4c8f25e..42e4ec5 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, path::Path}; use anyhow::Result; -use common::{TreeHashType, block::Block, transaction::TransactionBody}; +use common::{TreeHashType, block::Block, transaction::EncodedTransaction}; use storage::RocksDBIO; pub struct SequecerBlockStore { @@ -57,7 +57,7 @@ impl SequecerBlockStore { } /// Returns the transaction corresponding to the given hash, if it exists in the blockchain. - pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option { + pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option { let block_id = self.tx_hash_to_block_map.get(&hash); let block = block_id.map(|&id| self.get_block_at_id(id)); if let Some(Ok(block)) = block { @@ -96,7 +96,6 @@ mod tests { let genesis_block_hashable_data = HashableBlockData { block_id: 0, - prev_block_id: 0, prev_block_hash: [0; 32], timestamp: 0, transactions: vec![], diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index 5a4bcd1..366c9e2 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -41,7 +41,6 @@ impl SequecerChainStore { let hashable_data = HashableBlockData { block_id: genesis_id, - prev_block_id: genesis_id.saturating_sub(1), transactions: vec![], prev_block_hash, timestamp: curr_time, diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index a9f828e..9123bcb 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -18,7 +18,7 @@ use common::{ GetTransactionByHashResponse, }, }, - transaction::TransactionBody, + transaction::EncodedTransaction, }; use common::rpc_primitives::requests::{ @@ -75,7 +75,7 @@ impl JsonHandler { async fn process_send_tx(&self, request: Request) -> Result { let send_tx_req = SendTxRequest::parse(Some(request.params))?; - let tx = TransactionBody::from_bytes(send_tx_req.transaction); + let tx = EncodedTransaction::from_bytes(send_tx_req.transaction); let tx_hash = hex::encode(tx.hash()); { @@ -280,7 +280,7 @@ mod tests { use base64::{Engine, engine::general_purpose}; use common::{ rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing, - transaction::TransactionBody, + transaction::EncodedTransaction, }; use sequencer_core::{ @@ -329,7 +329,7 @@ mod tests { } } - fn components_for_tests() -> (JsonHandler, Vec, TransactionBody) { + fn components_for_tests() -> (JsonHandler, Vec, EncodedTransaction) { let config = sequencer_config_for_tests(); let mut sequencer_core = SequencerCore::start_from_config(config); let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone(); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index ae564d3..876303f 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -4,7 +4,7 @@ use base64::Engine; use common::{ ExecutionFailureKind, sequencer_client::{SequencerClient, json::SendTxResponse}, - transaction::TransactionBody, + transaction::{EncodedTransaction, NSSATransaction}, }; use anyhow::Result; @@ -146,16 +146,13 @@ impl WalletCore { } ///Poll transactions - pub async fn poll_public_native_token_transfer( - &self, - hash: String, - ) -> Result { + pub async fn poll_public_native_token_transfer(&self, hash: String) -> Result { let transaction_encoded = self.poller.poll_tx(hash).await?; let tx_base64_decode = base64::engine::general_purpose::STANDARD.decode(transaction_encoded)?; - let pub_tx = TransactionBody::from_bytes(tx_base64_decode); + let pub_tx = EncodedTransaction::from_bytes(tx_base64_decode); - Ok(nssa::NSSATransaction::try_from(&pub_tx)?) + Ok(NSSATransaction::try_from(&pub_tx)?) } } From d81fb2665fb55b059396cbc815f7837289b6ef2d Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Fri, 12 Sep 2025 15:06:49 +0300 Subject: [PATCH 16/20] fix: commnets fix cleanup --- .../privacy_preserving_transaction.rs | 41 ++++++++++++++++- .../privacy_preserving_transaction/circuit.rs | 45 ++----------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/nssa/src/encoding/privacy_preserving_transaction.rs b/nssa/src/encoding/privacy_preserving_transaction.rs index 7c49938..8123166 100644 --- a/nssa/src/encoding/privacy_preserving_transaction.rs +++ b/nssa/src/encoding/privacy_preserving_transaction.rs @@ -1,10 +1,12 @@ use std::io::{Cursor, Read}; use nssa_core::{ - Commitment, Nullifier, + Commitment, Nullifier, PrivacyPreservingCircuitOutput, account::Account, encryption::{Ciphertext, EphemeralPublicKey}, }; +use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; +use risc0_zkvm::{InnerReceipt, Receipt}; use crate::{ Address, PrivacyPreservingTransaction, PublicKey, Signature, @@ -224,3 +226,40 @@ impl PrivacyPreservingTransaction { Ok(PrivacyPreservingTransaction::new(message, witness_set)) } } + +impl Proof { + pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { + let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); + let receipt = Receipt::new(inner, circuit_output.to_bytes()); + receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() + } + + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + let proof_len = self.0.len() as u32; + bytes.extend_from_slice(&proof_len.to_le_bytes()); + bytes.extend_from_slice(&self.0); + bytes + } + + pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { + let proof_len = u32_from_cursor(cursor) as usize; + let mut proof = Vec::with_capacity(proof_len); + + for _ in 0..proof_len { + let mut one_byte_buf = [0u8]; + + cursor.read_exact(&mut one_byte_buf)?; + + proof.push(one_byte_buf[0]); + } + Ok(Self(proof)) + } +} + +// TODO: Improve error handling. Remove unwraps. +pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { + let mut word_buf = [0u8; 4]; + cursor.read_exact(&mut word_buf).unwrap(); + u32::from_le_bytes(word_buf) +} diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index a8e37cf..ca7b6ae 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,57 +1,18 @@ -use std::io::{Cursor, Read}; - use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::AccountWithMetadata, program::{InstructionData, ProgramOutput}, }; -use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; +use risc0_zkvm::{ExecutorEnv, Receipt, default_prover}; use crate::{error::NssaError, program::Program}; -use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID}; +use program_methods::PRIVACY_PRESERVING_CIRCUIT_ELF; /// Proof of the privacy preserving execution circuit #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Proof(pub(super) Vec); - -impl Proof { - pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { - let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); - let receipt = Receipt::new(inner, circuit_output.to_bytes()); - receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() - } - - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - let proof_len = self.0.len() as u32; - bytes.extend_from_slice(&proof_len.to_le_bytes()); - bytes.extend_from_slice(&self.0); - bytes - } - - pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - let proof_len = u32_from_cursor(cursor) as usize; - let mut proof = Vec::with_capacity(proof_len); - - for _ in 0..proof_len { - let mut one_byte_buf = [0u8]; - - cursor.read_exact(&mut one_byte_buf)?; - - proof.push(one_byte_buf[0]); - } - Ok(Self(proof)) - } -} - -// TODO: Improve error handling. Remove unwraps. -pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { - let mut word_buf = [0u8; 4]; - cursor.read_exact(&mut word_buf).unwrap(); - u32::from_le_bytes(word_buf) -} +pub struct Proof(pub(crate) Vec); /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution /// circuit From a11f3720917b7f5e912c5f80b3d772b33d88833f Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 15 Sep 2025 10:39:57 +0300 Subject: [PATCH 17/20] fix: comments fix --- nssa/src/encoding/privacy_preserving_transaction.rs | 10 +--------- nssa/src/privacy_preserving_transaction/circuit.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/nssa/src/encoding/privacy_preserving_transaction.rs b/nssa/src/encoding/privacy_preserving_transaction.rs index 8123166..2e5ea14 100644 --- a/nssa/src/encoding/privacy_preserving_transaction.rs +++ b/nssa/src/encoding/privacy_preserving_transaction.rs @@ -1,12 +1,10 @@ use std::io::{Cursor, Read}; use nssa_core::{ - Commitment, Nullifier, PrivacyPreservingCircuitOutput, + Commitment, Nullifier, account::Account, encryption::{Ciphertext, EphemeralPublicKey}, }; -use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; -use risc0_zkvm::{InnerReceipt, Receipt}; use crate::{ Address, PrivacyPreservingTransaction, PublicKey, Signature, @@ -228,12 +226,6 @@ impl PrivacyPreservingTransaction { } impl Proof { - pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { - let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); - let receipt = Receipt::new(inner, circuit_output.to_bytes()); - receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() - } - pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); let proof_len = self.0.len() as u32; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index ca7b6ae..1421f62 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -4,11 +4,11 @@ use nssa_core::{ account::AccountWithMetadata, program::{InstructionData, ProgramOutput}, }; -use risc0_zkvm::{ExecutorEnv, Receipt, default_prover}; +use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; use crate::{error::NssaError, program::Program}; -use program_methods::PRIVACY_PRESERVING_CIRCUIT_ELF; +use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID}; /// Proof of the privacy preserving execution circuit #[derive(Debug, Clone, PartialEq, Eq)] @@ -78,6 +78,14 @@ fn execute_and_prove_program( .receipt) } +impl Proof { + pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { + let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); + let receipt = Receipt::new(inner, circuit_output.to_bytes()); + receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() + } +} + #[cfg(test)] mod tests { use nssa_core::{ From d796d7baf19318bae4a583254ab83f88b5cd8056 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Sep 2025 22:50:10 -0300 Subject: [PATCH 18/20] add get-account wallet command. Small refactor --- common/src/rpc_primitives/requests.rs | 11 +++---- common/src/sequencer_client/mod.rs | 21 +++++++++++-- nssa/src/lib.rs | 1 + sequencer_rpc/src/process.rs | 33 ++++++++++---------- wallet/Cargo.toml | 1 + wallet/src/helperfunctions.rs | 36 ++++++++++++++++++---- wallet/src/lib.rs | 43 +++++++++++++++++---------- 7 files changed, 97 insertions(+), 49 deletions(-) diff --git a/common/src/rpc_primitives/requests.rs b/common/src/rpc_primitives/requests.rs index a566ee2..f62d567 100644 --- a/common/src/rpc_primitives/requests.rs +++ b/common/src/rpc_primitives/requests.rs @@ -49,7 +49,7 @@ pub struct GetAccountsNoncesRequest { } #[derive(Serialize, Deserialize, Debug)] -pub struct GetAccountDataRequest { +pub struct GetAccountRequest { pub address: String, } @@ -63,7 +63,7 @@ parse_request!(GetInitialTestnetAccountsRequest); parse_request!(GetAccountBalanceRequest); parse_request!(GetTransactionByHashRequest); parse_request!(GetAccountsNoncesRequest); -parse_request!(GetAccountDataRequest); +parse_request!(GetAccountRequest); #[derive(Serialize, Deserialize, Debug)] pub struct HelloResponse { @@ -112,9 +112,6 @@ pub struct GetTransactionByHashResponse { } #[derive(Serialize, Deserialize, Debug)] -pub struct GetAccountDataResponse { - pub balance: u128, - pub nonce: u128, - pub program_owner: [u32; 8], - pub data: Vec, +pub struct GetAccountResponse { + pub account: nssa::Account, } diff --git a/common/src/sequencer_client/mod.rs b/common/src/sequencer_client/mod.rs index ad66d82..3e29156 100644 --- a/common/src/sequencer_client/mod.rs +++ b/common/src/sequencer_client/mod.rs @@ -8,8 +8,8 @@ use reqwest::Client; use serde_json::Value; use crate::rpc_primitives::requests::{ - GetAccountsNoncesRequest, GetAccountsNoncesResponse, GetTransactionByHashRequest, - GetTransactionByHashResponse, + GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest, + GetAccountsNoncesResponse, GetTransactionByHashRequest, GetTransactionByHashResponse, }; use crate::sequencer_client::json::AccountInitialData; use crate::{SequencerClientError, SequencerRpcError}; @@ -108,6 +108,23 @@ impl SequencerClient { Ok(resp_deser) } + pub async fn get_account( + &self, + address: String, + ) -> Result { + let block_req = GetAccountRequest { address }; + + let req = serde_json::to_value(block_req)?; + + let resp = self + .call_method_with_payload("get_account", req) + .await?; + + let resp_deser = serde_json::from_value(resp)?; + + Ok(resp_deser) + } + ///Get transaction details for `hash`. pub async fn get_transaction_by_hash( &self, diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index ed88047..387a82d 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -7,6 +7,7 @@ pub mod public_transaction; mod signature; mod state; +pub use nssa_core::account::Account; pub use address::Address; pub use privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit::execute_and_prove, diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index 2f55dd2..1a5e2ca 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -12,8 +12,8 @@ use common::{ message::{Message, Request}, parser::RpcRequest, requests::{ - GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountDataRequest, - GetAccountDataResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, + GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountRequest, + GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, GetInitialTestnetAccountsRequest, GetTransactionByHashRequest, GetTransactionByHashResponse, }, @@ -36,7 +36,7 @@ pub const GET_LAST_BLOCK: &str = "get_last_block"; pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance"; pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash"; pub const GET_ACCOUNTS_NONCES: &str = "get_accounts_nonces"; -pub const GET_ACCOUNT_DATA: &str = "get_account_data"; +pub const GET_ACCOUNT: &str = "get_account"; pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER"; @@ -204,10 +204,10 @@ impl JsonHandler { respond(helperstruct) } - ///Returns account struct for give address. + /// Returns account struct for given address. /// Address must be a valid hex string of the correct length. - async fn process_get_account_data(&self, request: Request) -> Result { - let get_account_nonces_req = GetAccountDataRequest::parse(Some(request.params))?; + async fn process_get_account(&self, request: Request) -> Result { + let get_account_nonces_req = GetAccountRequest::parse(Some(request.params))?; let address = get_account_nonces_req .address @@ -220,12 +220,7 @@ impl JsonHandler { state.store.state.get_account_by_address(&address) }; - let helperstruct = GetAccountDataResponse { - balance: account.balance, - nonce: account.nonce, - program_owner: account.program_owner, - data: account.data, - }; + let helperstruct = GetAccountResponse { account }; respond(helperstruct) } @@ -265,7 +260,7 @@ impl JsonHandler { GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(request).await, GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await, GET_ACCOUNTS_NONCES => self.process_get_accounts_nonces(request).await, - GET_ACCOUNT_DATA => self.process_get_account_data(request).await, + GET_ACCOUNT => self.process_get_account(request).await, GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await, _ => Err(RpcErr(RpcError::method_not_found(request.method))), } @@ -534,7 +529,7 @@ mod tests { let (json_handler, _, _) = components_for_tests(); let request = serde_json::json!({ "jsonrpc": "2.0", - "method": "get_account_data", + "method": "get_account", "params": { "address": "efac".repeat(16) }, "id": 1 }); @@ -542,10 +537,12 @@ mod tests { "id": 1, "jsonrpc": "2.0", "result": { - "balance": 0, - "nonce": 0, - "program_owner": [ 0, 0, 0, 0, 0, 0, 0, 0], - "data": [], + "account": { + "balance": 0, + "nonce": 0, + "program_owner": [ 0, 0, 0, 0, 0, 0, 0, 0], + "data": [], + } } }); diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 2caa7ff..e37586b 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -14,6 +14,7 @@ tempfile.workspace = true clap.workspace = true nssa-core = { path = "../nssa/core" } base64.workspace = true +bytemuck = "1.23.2" [dependencies.key_protocol] path = "../key_protocol" diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 8a7b7b6..dc5b2f6 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -1,20 +1,22 @@ +use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr}; use anyhow::Result; use key_protocol::key_protocol_core::NSSAUserData; -use nssa::Address; +use nssa::{Account, Address}; +use serde::Serialize; use crate::{ HOME_DIR_ENV_VAR, config::{PersistentAccountData, WalletConfig}, }; -///Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. +/// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. pub fn get_home() -> Result { Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?) } -///Fetch config from `NSSA_WALLET_HOME_DIR` +/// Fetch config from `NSSA_WALLET_HOME_DIR` pub fn fetch_config() -> Result { let config_home = get_home()?; let file = File::open(config_home.join("wallet_config.json"))?; @@ -23,12 +25,12 @@ pub fn fetch_config() -> Result { Ok(serde_json::from_reader(reader)?) } -//ToDo: Replace with structures conversion in future +// ToDo: Replace with structures conversion in future pub fn produce_account_addr_from_hex(hex_str: String) -> Result
{ Ok(hex_str.parse()?) } -///Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` +/// Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` /// /// If file not present, it is considered as empty list of persistent accounts pub fn fetch_persistent_accounts() -> Result> { @@ -49,7 +51,7 @@ pub fn fetch_persistent_accounts() -> Result> { } } -///Produces a list of accounts for storage +/// Produces a list of accounts for storage pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec { let mut vec_for_storage = vec![]; @@ -63,6 +65,28 @@ pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec for HumanReadableAccount { + fn from(account: Account) -> Self { + let program_owner_b64 = BASE64.encode(bytemuck::cast_slice(&account.program_owner)); + let data_b64 = BASE64.encode(account.data); + Self { + balance: account.balance, + program_owner_b64, + data_b64, + nonce: account.nonce, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 3814925..3bea629 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,6 +1,6 @@ use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; -use base64::Engine; +use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use common::{ ExecutionFailureKind, sequencer_client::{SequencerClient, json::SendTxResponse}, @@ -17,8 +17,8 @@ use nssa_core::account::Account; use crate::{ helperfunctions::{ - fetch_config, fetch_persistent_accounts, get_home, produce_account_addr_from_hex, - produce_data_for_storage, + HumanReadableAccount, fetch_config, fetch_persistent_accounts, get_home, + produce_account_addr_from_hex, produce_data_for_storage, }, poller::TxPoller, }; @@ -144,14 +144,19 @@ impl WalletCore { .nonces) } + ///Get account + pub async fn get_account(&self, addr: Address) -> Result { + let response = self.sequencer_client.get_account(addr.to_string()).await?; + Ok(response.account) + } + ///Poll transactions pub async fn poll_public_native_token_transfer( &self, hash: String, ) -> Result { let transaction_encoded = self.poller.poll_tx(hash).await?; - let tx_base64_decode = - base64::engine::general_purpose::STANDARD.decode(transaction_encoded)?; + let tx_base64_decode = BASE64.decode(transaction_encoded)?; let pub_tx = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?; Ok(pub_tx) @@ -191,6 +196,11 @@ pub enum Command { #[arg(short, long)] addr: String, }, + ///Get account at address `addr` + GetAccount { + #[arg(short, long)] + addr: String, + }, } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -215,21 +225,19 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { .send_public_native_token_transfer(from, to, amount) .await?; - info!("Results of tx send is {res:#?}"); + println!("Results of tx send is {res:#?}"); let transfer_tx = wallet_core .poll_public_native_token_transfer(res.tx_hash) .await?; - info!("Transaction data is {transfer_tx:?}"); + println!("Transaction data is {transfer_tx:?}"); } Command::RegisterAccount {} => { let addr = wallet_core.create_new_account(); + wallet_core.store_persistent_accounts()?; - let key = wallet_core.storage.user_data.get_account_signing_key(&addr); - - info!("Generated new account with addr {addr:#?}"); - info!("With key {key:#?}"); + println!("Generated new account with addr {addr}"); } Command::FetchTx { tx_hash } => { let tx_obj = wallet_core @@ -237,23 +245,26 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { .get_transaction_by_hash(tx_hash) .await?; - info!("Transaction object {tx_obj:#?}"); + println!("Transaction object {tx_obj:#?}"); } Command::GetAccountBalance { addr } => { let addr = Address::from_str(&addr)?; let balance = wallet_core.get_account_balance(addr).await?; - info!("Accounts {addr:#?} balance is {balance}"); + println!("Accounts {addr} balance is {balance}"); } Command::GetAccountNonce { addr } => { let addr = Address::from_str(&addr)?; let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0]; - info!("Accounts {addr:#?} nonce is {nonce}"); + println!("Accounts {addr} nonce is {nonce}"); + } + Command::GetAccount { addr } => { + let addr: Address = addr.parse()?; + let account: HumanReadableAccount = wallet_core.get_account(addr).await?.into(); + println!("{}", serde_json::to_string(&account).unwrap()); } } - wallet_core.store_persistent_accounts()?; - Ok(()) } From 2d22b170d45209c3531d790adcb8e390fc12cc3f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 15 Sep 2025 18:35:41 -0300 Subject: [PATCH 19/20] add test --- integration_tests/Cargo.toml | 3 +++ integration_tests/src/lib.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 16e1b2e..a01935f 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -29,3 +29,6 @@ path = "../wallet" [dependencies.common] path = "../common" + +[dependencies.nssa] +path = "../nssa" diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 9b7b970..edf7d9f 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -5,6 +5,7 @@ use anyhow::Result; use clap::Parser; use common::sequencer_client::SequencerClient; use log::{info, warn}; +use nssa::program::Program; use sequencer_core::config::SequencerConfig; use sequencer_runner::startup_sequencer; use tempfile::TempDir; @@ -272,6 +273,33 @@ pub async fn test_success_two_transactions() { info!("Second TX Success!"); } +pub async fn test_get_account_wallet_command() { + let command = Command::GetAccount { + addr: ACC_SENDER.to_string(), + }; + + let wallet_config = fetch_config().unwrap(); + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + wallet::execute_subcommand(command).await.unwrap(); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct account"); + let account = seq_client + .get_account(ACC_SENDER.to_string()) + .await + .unwrap() + .account; + + assert_eq!(account.program_owner, Program::authenticated_transfer_program().id()); + assert_eq!(account.balance, 10000); + assert!(account.data.is_empty()); + assert_eq!(account.nonce, 0); +} + macro_rules! test_cleanup_wrap { ($home_dir:ident, $test_func:ident) => {{ let res = pre_test($home_dir.clone()).await.unwrap(); @@ -304,6 +332,9 @@ pub async fn main_tests_runner() -> Result<()> { "test_failure" => { test_cleanup_wrap!(home_dir, test_failure); } + "test_get_account_wallet_command" => { + test_cleanup_wrap!(home_dir, test_get_account_wallet_command); + } "test_success_two_transactions" => { test_cleanup_wrap!(home_dir, test_success_two_transactions); } @@ -312,6 +343,7 @@ pub async fn main_tests_runner() -> Result<()> { test_cleanup_wrap!(home_dir, test_success); test_cleanup_wrap!(home_dir, test_failure); test_cleanup_wrap!(home_dir, test_success_two_transactions); + test_cleanup_wrap!(home_dir, test_get_account_wallet_command); } _ => { anyhow::bail!("Unknown test name"); From 37b5aec264a84969b2563a19f1088b420ad07932 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 15 Sep 2025 18:39:36 -0300 Subject: [PATCH 20/20] fmt --- common/src/sequencer_client/mod.rs | 8 +++----- integration_tests/src/lib.rs | 5 ++++- nssa/src/lib.rs | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/common/src/sequencer_client/mod.rs b/common/src/sequencer_client/mod.rs index 3e29156..d61b79d 100644 --- a/common/src/sequencer_client/mod.rs +++ b/common/src/sequencer_client/mod.rs @@ -8,8 +8,8 @@ use reqwest::Client; use serde_json::Value; use crate::rpc_primitives::requests::{ - GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest, - GetAccountsNoncesResponse, GetTransactionByHashRequest, GetTransactionByHashResponse, + GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, + GetTransactionByHashRequest, GetTransactionByHashResponse, }; use crate::sequencer_client::json::AccountInitialData; use crate::{SequencerClientError, SequencerRpcError}; @@ -116,9 +116,7 @@ impl SequencerClient { let req = serde_json::to_value(block_req)?; - let resp = self - .call_method_with_payload("get_account", req) - .await?; + let resp = self.call_method_with_payload("get_account", req).await?; let resp_deser = serde_json::from_value(resp)?; diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index edf7d9f..5c14ea2 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -294,7 +294,10 @@ pub async fn test_get_account_wallet_command() { .unwrap() .account; - assert_eq!(account.program_owner, Program::authenticated_transfer_program().id()); + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); assert_eq!(account.balance, 10000); assert!(account.data.is_empty()); assert_eq!(account.nonce, 0); diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index 387a82d..25a9368 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -7,8 +7,8 @@ pub mod public_transaction; mod signature; mod state; -pub use nssa_core::account::Account; pub use address::Address; +pub use nssa_core::account::Account; pub use privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit::execute_and_prove, };