From f47692773ccf7bce628c5c228f72ae706c9a2203 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Tue, 26 Aug 2025 14:09:33 +0300 Subject: [PATCH 01/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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 ebe616247f0a63a1b1374861a5292c5c82a06ae1 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Thu, 4 Sep 2025 17:49:55 +0300 Subject: [PATCH 14/37] fix: seed generation from mnemonic --- Cargo.toml | 1 + key_protocol/Cargo.toml | 1 + key_protocol/src/key_management/secret_holders.rs | 11 ++++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b98867..5f2ee32 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" +bip39 = "2.2.0" rocksdb = { version = "0.21.0", default-features = false, features = [ "snappy", diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 947bd7a..8b9701a 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -15,6 +15,7 @@ elliptic-curve.workspace = true hex.workspace = true aes-gcm.workspace = true lazy_static.workspace = true +bip39.workspace = true [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 47342ec..4fc1a63 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,3 +1,4 @@ +use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, FieldBytes, Scalar}; @@ -29,12 +30,16 @@ pub struct UTXOSecretKeyHolder { impl SeedHolder { pub fn new_os_random() -> Self { - let mut bytes = FieldBytes::default(); + let mut enthopy_bytes: [u8; 32] = [0; 32]; + OsRng.fill_bytes(&mut enthopy_bytes); - OsRng.fill_bytes(&mut bytes); + let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); + let seed = mnemonic.to_seed(""); + + let field_bytes = FieldBytes::from_slice(&seed); Self { - seed: Scalar::from_repr(bytes).unwrap(), + seed: Scalar::from_repr(*field_bytes).unwrap(), } } From 4a44b12384ccf2bfc01ca08c4da2a18feb8bd1c9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Sep 2025 12:44:22 -0300 Subject: [PATCH 15/37] wip --- .../guest/src/bin/authenticated_transfer.rs | 1 + nssa/program_methods/guest/src/bin/pinata.rs | 68 +++++++++++++++++++ nssa/src/program.rs | 9 ++- nssa/src/state.rs | 36 ++++++++++ sequencer_core/src/sequencer_store/mod.rs | 6 +- sequencer_runner/src/lib.rs | 4 +- 6 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 nssa/program_methods/guest/src/bin/pinata.rs diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index 9e7f399..89b84fd 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -34,3 +34,4 @@ fn main() { write_nssa_outputs(vec![sender, receiver], vec![sender_post, receiver_post]); } + diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs new file mode 100644 index 0000000..43b0190 --- /dev/null +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -0,0 +1,68 @@ +use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; +use risc0_zkvm::sha::{Impl, Sha256}; + +const PRIZE: u128 = 150; + +type Instruction = u128; + +struct Challenge { + difficulty: u8, + seed: [u8; 32], +} + +impl Challenge { + fn new(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 33); + let difficulty = bytes[0]; + assert!(difficulty <= 32); + + let mut seed = [0; 32]; + seed.copy_from_slice(&bytes[1..]); + Self { difficulty, seed } + } + + fn is_nonce_valid(&self, nonce: Instruction) -> bool { + let mut bytes = [0; 32 + 16]; + bytes.copy_from_slice(&self.seed); + bytes[32..].copy_from_slice(&nonce.to_le_bytes()); + let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); + let difficulty = self.difficulty as usize; + digest[..difficulty].iter().all(|&b| b == 0) + } + + fn next_data(self) -> [u8; 33] { + let mut result = [0; 33]; + result[0] = self.difficulty; + result.copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); + result + } +} + +/// A pinata program +fn main() { + // Read input accounts. + // It is expected to receive only two accounts: [pinata_account, winner_account] + let ProgramInput { + pre_states, + instruction: nonce, + } = read_nssa_inputs::(); + + let [pinata, winner] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let data = Challenge::new(&pinata.account.data); + + if !data.is_nonce_valid(nonce) { + return; + } + + let mut pinata_post = pinata.account.clone(); + let mut winner_post = winner.account.clone(); + pinata_post.balance -= PRIZE; + pinata_post.data = data.next_data().to_vec(); + winner_post.balance += PRIZE; + + write_nssa_outputs(vec![pinata, winner], vec![pinata_post, winner_post]); +} diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 66358e9..63b7dc4 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -2,7 +2,7 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, program::{InstructionData, ProgramId, ProgramOutput}, }; -use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; +use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID}; use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; @@ -73,6 +73,13 @@ impl Program { elf: AUTHENTICATED_TRANSFER_ELF, } } + + pub fn pinata() -> Self { + Self { + id: PINATA_ID, + elf: PINATA_ELF + } + } } #[cfg(test)] diff --git a/nssa/src/state.rs b/nssa/src/state.rs index f975804..ba6c49b 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -8,6 +8,7 @@ use nssa_core::{ account::Account, program::{DEFAULT_PROGRAM_ID, ProgramId}, }; +use rand::{Rng, RngCore, rngs::OsRng}; use std::collections::{HashMap, HashSet}; pub(crate) struct CommitmentSet { @@ -85,6 +86,41 @@ impl V01State { this } + pub fn add_pinata_accounts(&mut self) { + self.insert_program(Program::pinata()); + + let mut rng = OsRng; + let mut seed = [0; 32]; + + rng.fill_bytes(&mut seed); + self.public_state.insert( + "6a79aee868a1c641ea895582af7ddd6f2da339e3091a67eddcbfdaa1b9010001" + .parse() + .unwrap(), + Account { + program_owner: Program::pinata().id(), + balance: 1500, + // Difficulty: 3 + data: std::iter::once(3).chain(seed).collect(), + nonce: 0, + }, + ); + + rng.fill_bytes(&mut seed); + self.public_state.insert( + "6a79aee868a1c641ea895582af7ddd6f2da339e3091a67eddcbfdaa1b9010002" + .parse() + .unwrap(), + Account { + program_owner: Program::pinata().id(), + balance: 1500, + // Difficulty: 4 + data: std::iter::once(4).chain(seed).collect(), + nonce: 0, + }, + ); + } + pub(crate) fn insert_program(&mut self, program: Program) { self.builtin_programs.insert(program.id(), program); } diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index 4254ed7..374e7d5 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -26,7 +26,11 @@ impl SequecerChainStore { .map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance)) .collect(); - let state = nssa::V01State::new_with_genesis_accounts(&init_accs); + let state = { + let mut this = nssa::V01State::new_with_genesis_accounts(&init_accs); + this.add_pinata_accounts(); + this + }; let mut data = [0; 32]; let mut prev_block_hash = [0; 32]; 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 324f477b631aba96623d83d296a48ffed636ee56 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Fri, 5 Sep 2025 14:47:58 +0300 Subject: [PATCH 16/37] fix: keys corect generatin --- Cargo.toml | 1 + key_protocol/Cargo.toml | 1 + .../src/key_management/constants_types.rs | 22 --- .../key_management/ephemeral_key_holder.rs | 2 +- key_protocol/src/key_management/mod.rs | 86 +++++------ .../src/key_management/secret_holders.rs | 137 +++++++++++++----- key_protocol/src/key_management/types.rs | 8 + 7 files changed, 150 insertions(+), 107 deletions(-) delete mode 100644 key_protocol/src/key_management/constants_types.rs create mode 100644 key_protocol/src/key_management/types.rs diff --git a/Cargo.toml b/Cargo.toml index 5f2ee32..c21c9a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ ark-ff = "0.5.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } base64 = "0.22.1" bip39 = "2.2.0" +hmac-sha512 = "1.1.7" rocksdb = { version = "0.21.0", default-features = false, features = [ "snappy", diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 8b9701a..d624505 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -16,6 +16,7 @@ hex.workspace = true aes-gcm.workspace = true lazy_static.workspace = true bip39.workspace = true +hmac-sha512.workspace = true [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/constants_types.rs b/key_protocol/src/key_management/constants_types.rs deleted file mode 100644 index f4fca86..0000000 --- a/key_protocol/src/key_management/constants_types.rs +++ /dev/null @@ -1,22 +0,0 @@ -use elliptic_curve::{ - consts::{B0, B1}, - generic_array::GenericArray, -}; -use lazy_static::lazy_static; -use sha2::digest::typenum::{UInt, UTerm}; - -lazy_static! { - pub static ref NULLIFIER_SECRET_CONST: [u8; 32] = - hex::decode(std::env::var("NULLIFIER_SECRET_CONST").unwrap()) - .unwrap() - .try_into() - .unwrap(); - pub static ref VIEWING_SECRET_CONST: [u8; 32] = - hex::decode(std::env::var("VIEWING_SECRET_CONST").unwrap()) - .unwrap() - .try_into() - .unwrap(); -} - -pub type CipherText = Vec; -pub type Nonce = GenericArray, B1>, B0>, B0>>; diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index ecfb09e..6392678 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -5,7 +5,7 @@ use k256::{AffinePoint, FieldBytes, Scalar}; use log::info; use rand::{rngs::OsRng, RngCore}; -use super::constants_types::{CipherText, Nonce}; +use super::types::{CipherText, Nonce}; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index bc6d14c..fb75dff 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,29 +1,32 @@ use std::collections::HashMap; use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; -use constants_types::{CipherText, Nonce}; +use common::merkle_tree_public::TreeHashType; +use elliptic_curve::group::GroupEncoding; use elliptic_curve::point::AffineCoordinates; use k256::AffinePoint; use log::info; -use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder}; +use secret_holders::{PrivateKeyHolder, SeedHolder, TopSecretKeyHolder}; use serde::{Deserialize, Serialize}; +use sha2::{digest::FixedOutput, Digest}; +use types::{CipherText, Nonce}; use crate::key_protocol_core::PublicKey; pub type PublicAccountSigningKey = [u8; 32]; -pub mod constants_types; pub mod ephemeral_key_holder; pub mod secret_holders; +pub mod types; #[derive(Serialize, Deserialize, Clone, Debug)] ///Entrypoint to key management pub struct KeyChain { top_secret_key_holder: TopSecretKeyHolder, - pub utxo_secret_key_holder: UTXOSecretKeyHolder, + pub private_key_holder: PrivateKeyHolder, ///Map for all users accounts pub_account_signing_keys: HashMap, - pub nullifer_public_key: PublicKey, - pub viewing_public_key: PublicKey, + pub nullifer_public_key: [u8; 32], + pub incoming_viewing_public_key: PublicKey, } impl KeyChain { @@ -33,16 +36,16 @@ impl KeyChain { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let private_key_holder = top_secret_key_holder.produce_private_key_holder(); - let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); + let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); Self { top_secret_key_holder, - utxo_secret_key_holder, + private_key_holder, nullifer_public_key, - viewing_public_key, + incoming_viewing_public_key, pub_account_signing_keys: HashMap::new(), } } @@ -53,20 +56,29 @@ impl KeyChain { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let private_key_holder = top_secret_key_holder.produce_private_key_holder(); - let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); + let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); Self { top_secret_key_holder, - utxo_secret_key_holder, + private_key_holder, nullifer_public_key, - viewing_public_key, + incoming_viewing_public_key, pub_account_signing_keys: accounts, } } + pub fn produce_user_address(&self) -> [u8; 32] { + let mut hasher = sha2::Sha256::new(); + + hasher.update(&self.nullifer_public_key); + hasher.update(&self.incoming_viewing_public_key.to_bytes()); + + ::from(hasher.finalize_fixed()) + } + pub fn generate_new_private_key(&mut self) -> nssa::Address { let private_key = nssa::PrivateKey::new_os_random(); let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key)); @@ -112,14 +124,18 @@ impl KeyChain { ); info!( "Nulifier secret key is {:?}", + hex::encode(serde_json::to_vec(&self.private_key_holder.nullifier_secret_key).unwrap()), + ); + info!( + "Viewing secret key is {:?}", hex::encode( - serde_json::to_vec(&self.utxo_secret_key_holder.nullifier_secret_key).unwrap() + serde_json::to_vec(&self.private_key_holder.incoming_viewing_secret_key).unwrap() ), ); info!( "Viewing secret key is {:?}", hex::encode( - serde_json::to_vec(&self.utxo_secret_key_holder.viewing_secret_key).unwrap() + serde_json::to_vec(&self.private_key_holder.outgoing_viewing_secret_key).unwrap() ), ); info!( @@ -128,7 +144,7 @@ impl KeyChain { ); info!( "Viewing public key is {:?}", - hex::encode(serde_json::to_vec(&self.viewing_public_key).unwrap()), + hex::encode(serde_json::to_vec(&self.incoming_viewing_public_key).unwrap()), ); } } @@ -139,12 +155,11 @@ mod tests { aead::{Aead, KeyInit, OsRng}, Aes256Gcm, }; - use constants_types::{CipherText, Nonce}; - use constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST}; use elliptic_curve::ff::Field; use elliptic_curve::group::prime::PrimeCurveAffine; use elliptic_curve::point::AffineCoordinates; use k256::{AffinePoint, ProjectivePoint, Scalar}; + use types::{CipherText, Nonce}; use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder; @@ -156,11 +171,9 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Check that key holder fields are initialized with expected types + assert_ne!(address_key_holder.nullifer_public_key, [0u8; 32]); assert!(!Into::::into( - address_key_holder.nullifer_public_key.is_identity() - )); - assert!(!Into::::into( - address_key_holder.viewing_public_key.is_identity() + address_key_holder.incoming_viewing_public_key.is_identity() )); } @@ -211,20 +224,6 @@ mod tests { assert_eq!(decrypted_data, plaintext); } - #[test] - fn test_new_os_random_initialization() { - // Ensure that KeyChain is initialized correctly - let address_key_holder = KeyChain::new_os_random(); - - // Check that key holder fields are initialized with expected types and values - assert!(!Into::::into( - address_key_holder.nullifer_public_key.is_identity() - )); - assert!(!Into::::into( - address_key_holder.viewing_public_key.is_identity() - )); - } - #[test] fn test_calculate_shared_secret_with_identity_point() { let address_key_holder = KeyChain::new_os_random(); @@ -359,10 +358,10 @@ mod tests { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let utxo_secret_key_holder = top_secret_key_holder.produce_private_key_holder(); let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + let viewing_public_key = utxo_secret_key_holder.generate_incoming_viewing_public_key(); let pub_account_signing_key = nssa::PrivateKey::new_os_random(); @@ -377,11 +376,6 @@ mod tests { "Group generator {:?}", hex::encode(serde_json::to_vec(&AffinePoint::GENERATOR).unwrap()) ); - println!( - "Nullifier constant {:?}", - hex::encode(*NULLIFIER_SECRET_CONST) - ); - println!("Viewing constatnt {:?}", hex::encode(*VIEWING_SECRET_CONST)); println!(); println!("======Holders======"); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 4fc1a63..a5258b5 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,31 +1,31 @@ use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use rand::{rngs::OsRng, RngCore}; use serde::{Deserialize, Serialize}; use sha2::{digest::FixedOutput, Digest}; -use super::constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST}; - #[derive(Debug)] ///Seed holder. Non-clonable to ensure that different holders use different seeds. /// Produces `TopSecretKeyHolder` objects. pub struct SeedHolder { - seed: Scalar, + //ToDo: Needs to be vec as serde derives is not implemented for [u8; 64] + pub(crate) seed: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] -///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects. +///Secret spending key holder. Produces `PrivateKeyHolder` objects. pub struct TopSecretKeyHolder { - pub secret_spending_key: Scalar, + pub(crate) secret_spending_key: [u8; 32], } #[derive(Serialize, Deserialize, Debug, Clone)] -///Nullifier secret key and viewing secret key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. -pub struct UTXOSecretKeyHolder { - pub nullifier_secret_key: Scalar, - pub viewing_secret_key: Scalar, +///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. +pub struct PrivateKeyHolder { + pub(crate) nullifier_secret_key: [u8; 32], + pub(crate) incoming_viewing_secret_key: Scalar, + pub(crate) outgoing_viewing_secret_key: Scalar, } impl SeedHolder { @@ -34,73 +34,134 @@ impl SeedHolder { OsRng.fill_bytes(&mut enthopy_bytes); let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); - let seed = mnemonic.to_seed(""); - - let field_bytes = FieldBytes::from_slice(&seed); + let seed_wide = mnemonic.to_seed("mnemonic"); Self { - seed: Scalar::from_repr(*field_bytes).unwrap(), + seed: seed_wide.to_vec(), } } pub fn generate_secret_spending_key_hash(&self) -> TreeHashType { - let mut hasher = sha2::Sha256::new(); + let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed"); - hasher.update(self.seed.to_bytes()); + for _ in 1..2048 { + hash = hmac_sha512::HMAC::mac(&hash, "NSSA_seed"); + } - ::from(hasher.finalize_fixed()) - } - - pub fn generate_secret_spending_key_scalar(&self) -> Scalar { - let hash = self.generate_secret_spending_key_hash(); - - Scalar::from_repr(hash.into()).unwrap() + //Safe unwrap + *hash.first_chunk::<32>().unwrap() } pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder { TopSecretKeyHolder { - secret_spending_key: self.generate_secret_spending_key_scalar(), + secret_spending_key: self.generate_secret_spending_key_hash(), } } } impl TopSecretKeyHolder { - pub fn generate_nullifier_secret_key(&self) -> Scalar { + pub fn generate_nullifier_secret_key(&self) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); - hasher.update(self.secret_spending_key.to_bytes()); - hasher.update(*NULLIFIER_SECRET_CONST); + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([1u8]); + hasher.update([0u8; 176]); + + ::from(hasher.finalize_fixed()) + } + + pub fn generate_incloming_viewing_secret_key(&self) -> Scalar { + let mut hasher = sha2::Sha256::new(); + + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([2u8]); + hasher.update([0u8; 176]); let hash = ::from(hasher.finalize_fixed()); Scalar::from_repr(hash.into()).unwrap() } - pub fn generate_viewing_secret_key(&self) -> Scalar { + pub fn generate_outgoing_viewing_secret_key(&self) -> Scalar { let mut hasher = sha2::Sha256::new(); - hasher.update(self.secret_spending_key.to_bytes()); - hasher.update(*VIEWING_SECRET_CONST); + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([3u8]); + hasher.update([0u8; 176]); let hash = ::from(hasher.finalize_fixed()); Scalar::from_repr(hash.into()).unwrap() } - pub fn produce_utxo_secret_holder(&self) -> UTXOSecretKeyHolder { - UTXOSecretKeyHolder { + pub fn produce_private_key_holder(&self) -> PrivateKeyHolder { + PrivateKeyHolder { nullifier_secret_key: self.generate_nullifier_secret_key(), - viewing_secret_key: self.generate_viewing_secret_key(), + incoming_viewing_secret_key: self.generate_incloming_viewing_secret_key(), + outgoing_viewing_secret_key: self.generate_outgoing_viewing_secret_key(), } } } -impl UTXOSecretKeyHolder { - pub fn generate_nullifier_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.nullifier_secret_key).into() +impl PrivateKeyHolder { + pub fn generate_nullifier_public_key(&self) -> [u8; 32] { + let mut hasher = sha2::Sha256::new(); + + hasher.update("NSSA_keys"); + hasher.update(&self.nullifier_secret_key); + hasher.update([7u8]); + hasher.update([0u8; 176]); + + ::from(hasher.finalize_fixed()) } - pub fn generate_viewing_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.viewing_secret_key).into() + pub fn generate_incoming_viewing_public_key(&self) -> AffinePoint { + (AffinePoint::GENERATOR * self.incoming_viewing_secret_key).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn seed_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + } + + #[test] + fn ssk_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let _ = seed_holder.generate_secret_spending_key_hash(); + } + + #[test] + fn ivs_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); + + let _ = top_secret_key_holder.generate_incloming_viewing_secret_key(); + } + + #[test] + fn ovs_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); + + let _ = top_secret_key_holder.generate_outgoing_viewing_secret_key(); } } diff --git a/key_protocol/src/key_management/types.rs b/key_protocol/src/key_management/types.rs new file mode 100644 index 0000000..8878f25 --- /dev/null +++ b/key_protocol/src/key_management/types.rs @@ -0,0 +1,8 @@ +use elliptic_curve::{ + consts::{B0, B1}, + generic_array::GenericArray, +}; +use sha2::digest::typenum::{UInt, UTerm}; + +pub type CipherText = Vec; +pub type Nonce = GenericArray, B1>, B0>, B0>>; From a432019b23392ac27bdc2adbca73380464ce1f5c Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Sep 2025 17:05:12 -0300 Subject: [PATCH 17/37] wip --- integration_tests/Cargo.toml | 1 + integration_tests/src/lib.rs | 48 ++++++++++++++++++ nssa/Cargo.toml | 3 ++ nssa/program_methods/guest/src/bin/pinata.rs | 16 +++--- nssa/src/program.rs | 9 +++- nssa/src/state.rs | 53 +++++++------------- sequencer_core/Cargo.toml | 4 ++ sequencer_core/src/sequencer_store/mod.rs | 6 ++- sequencer_runner/src/lib.rs | 4 +- wallet/src/lib.rs | 52 +++++++++++++++++-- 10 files changed, 145 insertions(+), 51 deletions(-) diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 16e1b2e..644d2e3 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -20,6 +20,7 @@ workspace = true [dependencies.sequencer_core] path = "../sequencer_core" +features = ["testnet"] [dependencies.sequencer_runner] path = "../sequencer_runner" diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 9b7b970..cd2260f 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -272,6 +272,50 @@ pub async fn test_success_two_transactions() { info!("Second TX Success!"); } +pub async fn test_pinata() { + let pinata_addr = "cafe".repeat(16); + let pinata_prize = 150; + let solution = 989106; + let command = Command::ClaimPinata { + pinata_addr: pinata_addr.clone(), + winner_addr: ACC_SENDER.to_string(), + solution, + }; + + let wallet_config = fetch_config().unwrap(); + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + let pinata_balance_pre = seq_client + .get_account_balance(pinata_addr.clone()) + .await + .unwrap() + .balance; + + 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 balance move"); + let pinata_balance_post = seq_client + .get_account_balance(pinata_addr.clone()) + .await + .unwrap() + .balance; + + let winner_balance_post = seq_client + .get_account_balance(ACC_SENDER.to_string()) + .await + .unwrap() + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + assert_eq!(winner_balance_post, 10000 + pinata_prize); + + info!("Success!"); +} + macro_rules! test_cleanup_wrap { ($home_dir:ident, $test_func:ident) => {{ let res = pre_test($home_dir.clone()).await.unwrap(); @@ -307,11 +351,15 @@ pub async fn main_tests_runner() -> Result<()> { "test_success_two_transactions" => { test_cleanup_wrap!(home_dir, test_success_two_transactions); } + "test_pinata" => { + test_cleanup_wrap!(home_dir, test_pinata); + } "all" => { test_cleanup_wrap!(home_dir, test_success_move_to_another_account); 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_pinata); } _ => { anyhow::bail!("Unknown test name"); diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 3163902..25907a1 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -18,3 +18,6 @@ hex = "0.4.3" [dev-dependencies] test-program-methods = { path = "test_program_methods" } hex-literal = "1.0.0" + +[features] +default = [] diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/nssa/program_methods/guest/src/bin/pinata.rs index 43b0190..fbea167 100644 --- a/nssa/program_methods/guest/src/bin/pinata.rs +++ b/nssa/program_methods/guest/src/bin/pinata.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, ProgramInput}; +use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}; use risc0_zkvm::sha::{Impl, Sha256}; const PRIZE: u128 = 150; @@ -21,10 +21,12 @@ impl Challenge { Self { difficulty, seed } } - fn is_nonce_valid(&self, nonce: Instruction) -> bool { + // Checks if the leftmost `self.difficulty` number of bytes of SHA256(self.data || solution) are + // zero. + fn validate_solution(&self, solution: Instruction) -> bool { let mut bytes = [0; 32 + 16]; - bytes.copy_from_slice(&self.seed); - bytes[32..].copy_from_slice(&nonce.to_le_bytes()); + bytes[..32].copy_from_slice(&self.seed); + bytes[32..].copy_from_slice(&solution.to_le_bytes()); let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); let difficulty = self.difficulty as usize; digest[..difficulty].iter().all(|&b| b == 0) @@ -33,7 +35,7 @@ impl Challenge { fn next_data(self) -> [u8; 33] { let mut result = [0; 33]; result[0] = self.difficulty; - result.copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); + result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); result } } @@ -44,7 +46,7 @@ fn main() { // It is expected to receive only two accounts: [pinata_account, winner_account] let ProgramInput { pre_states, - instruction: nonce, + instruction: solution, } = read_nssa_inputs::(); let [pinata, winner] = match pre_states.try_into() { @@ -54,7 +56,7 @@ fn main() { let data = Challenge::new(&pinata.account.data); - if !data.is_nonce_valid(nonce) { + if !data.validate_solution(solution) { return; } diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 63b7dc4..4828246 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -2,7 +2,9 @@ use nssa_core::{ account::{Account, AccountWithMetadata}, program::{InstructionData, ProgramId, ProgramOutput}, }; -use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID}; +use program_methods::{ + AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, +}; use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; @@ -73,11 +75,14 @@ impl Program { elf: AUTHENTICATED_TRANSFER_ELF, } } +} +// TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet +impl Program { pub fn pinata() -> Self { Self { id: PINATA_ID, - elf: PINATA_ELF + elf: PINATA_ELF, } } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index ba6c49b..ef90f37 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -86,41 +86,6 @@ impl V01State { this } - pub fn add_pinata_accounts(&mut self) { - self.insert_program(Program::pinata()); - - let mut rng = OsRng; - let mut seed = [0; 32]; - - rng.fill_bytes(&mut seed); - self.public_state.insert( - "6a79aee868a1c641ea895582af7ddd6f2da339e3091a67eddcbfdaa1b9010001" - .parse() - .unwrap(), - Account { - program_owner: Program::pinata().id(), - balance: 1500, - // Difficulty: 3 - data: std::iter::once(3).chain(seed).collect(), - nonce: 0, - }, - ); - - rng.fill_bytes(&mut seed); - self.public_state.insert( - "6a79aee868a1c641ea895582af7ddd6f2da339e3091a67eddcbfdaa1b9010002" - .parse() - .unwrap(), - Account { - program_owner: Program::pinata().id(), - balance: 1500, - // Difficulty: 4 - data: std::iter::once(4).chain(seed).collect(), - nonce: 0, - }, - ); - } - pub(crate) fn insert_program(&mut self, program: Program) { self.builtin_programs.insert(program.id(), program); } @@ -242,6 +207,24 @@ impl V01State { } } +// TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet +impl V01State { + pub fn add_pinata_program(&mut self, address: Address) { + self.insert_program(Program::pinata()); + + self.public_state.insert( + address, + Account { + program_owner: Program::pinata().id(), + balance: 1500, + // Difficulty: 3 + data: vec![3; 33], + nonce: 0, + }, + ); + } +} + #[cfg(test)] pub mod tests { diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 1c56f11..aca92ff 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -22,3 +22,7 @@ path = "../common" [dependencies.nssa] path = "../nssa" + +[features] +default = [] +testnet = [] diff --git a/sequencer_core/src/sequencer_store/mod.rs b/sequencer_core/src/sequencer_store/mod.rs index f9c86cd..8d8ef23 100644 --- a/sequencer_core/src/sequencer_store/mod.rs +++ b/sequencer_core/src/sequencer_store/mod.rs @@ -26,9 +26,13 @@ impl SequecerChainStore { .map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance)) .collect(); + #[cfg(not(feature = "testnet"))] + let state = nssa::V01State::new_with_genesis_accounts(&init_accs); + + #[cfg(feature = "testnet")] let state = { let mut this = nssa::V01State::new_with_genesis_accounts(&init_accs); - this.add_pinata_accounts(); + this.add_pinata_program("cafe".repeat(16).parse().unwrap()); this }; diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 068d3d7..b682a18 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -77,9 +77,7 @@ pub async fn main_runner() -> Result<()> { } //ToDo: Add restart on failures - let (_, main_loop_handle) = startup_sequencer(app_config).await?; - - main_loop_handle.await??; + let (_, _) = startup_sequencer(app_config).await?; Ok(()) } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 3814925..171dda9 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -84,6 +84,24 @@ impl WalletCore { None } + pub async fn claim_pinata( + &self, + pinata_addr: Address, + winner_addr: Address, + solution: u128, + ) -> Result { + let addresses = vec![pinata_addr, winner_addr]; + let program_id = nssa::program::Program::pinata().id(); + let message = + nssa::public_transaction::Message::try_new(program_id, addresses, vec![], solution) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx(tx).await?) + } + pub async fn send_public_native_token_transfer( &self, from: Address, @@ -191,6 +209,20 @@ pub enum Command { #[arg(short, long)] addr: String, }, + + // TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet + // Claim piñata prize + ClaimPinata { + ///pinata_addr - valid 32 byte hex string + #[arg(long)] + pinata_addr: String, + ///winner_addr - valid 32 byte hex string + #[arg(long)] + winner_addr: String, + ///solution - solution to pinata challenge + #[arg(long)] + solution: u128, + }, } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -228,7 +260,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { let key = wallet_core.storage.user_data.get_account_signing_key(&addr); - info!("Generated new account with addr {addr:#?}"); + println!("Generated new account with addr {addr}"); info!("With key {key:#?}"); } Command::FetchTx { tx_hash } => { @@ -243,13 +275,27 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { 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::ClaimPinata { + pinata_addr, + winner_addr, + solution, + } => { + let res = wallet_core + .claim_pinata( + pinata_addr.parse().unwrap(), + winner_addr.parse().unwrap(), + solution, + ) + .await?; + info!("Results of tx send is {res:#?}"); } } From e2d596be2061ddd863b40bd3fb4eb81a5adfec13 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Sep 2025 23:45:44 -0300 Subject: [PATCH 18/37] clippy --- nssa/src/program.rs | 2 +- nssa/src/state.rs | 3 +-- wallet/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 4828246..9577411 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -77,7 +77,7 @@ impl Program { } } -// TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet +// TODO: Testnet only. Refactor to prevent compilation on mainnet. impl Program { pub fn pinata() -> Self { Self { diff --git a/nssa/src/state.rs b/nssa/src/state.rs index ef90f37..3fe0b93 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -8,7 +8,6 @@ use nssa_core::{ account::Account, program::{DEFAULT_PROGRAM_ID, ProgramId}, }; -use rand::{Rng, RngCore, rngs::OsRng}; use std::collections::{HashMap, HashSet}; pub(crate) struct CommitmentSet { @@ -207,7 +206,7 @@ impl V01State { } } -// TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet +// TODO: Testnet only. Refactor to prevent compilation on mainnet. impl V01State { pub fn add_pinata_program(&mut self, address: Address) { self.insert_program(Program::pinata()); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 171dda9..0ea3892 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -210,7 +210,7 @@ pub enum Command { addr: String, }, - // TODO: This is for testnet only, consider refactoring to have this not compiled for mainnet + // TODO: Testnet only. Refactor to prevent compilation on mainnet. // Claim piñata prize ClaimPinata { ///pinata_addr - valid 32 byte hex string From 5747070e395372795f02132ff02d0f2a376b99c0 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 09:09:32 +0300 Subject: [PATCH 19/37] 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 20/37] 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 33783e06d8a6d8e4c1072f45e7bb6fec01ae1f15 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 14:48:58 +0300 Subject: [PATCH 21/37] fix: keys structures updates --- .../key_management/ephemeral_key_holder.rs | 45 ++++----- key_protocol/src/key_management/mod.rs | 79 ++++------------ .../src/key_management/secret_holders.rs | 10 +- key_protocol/src/key_protocol_core/mod.rs | 92 +++++++++++++++---- wallet/src/chain_storage/mod.rs | 2 +- wallet/src/lib.rs | 6 +- 6 files changed, 123 insertions(+), 111 deletions(-) diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 6392678..108a41e 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,11 +1,7 @@ -use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, KeyInit}; -use elliptic_curve::point::AffineCoordinates; use elliptic_curve::PrimeField; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use log::info; -use rand::{rngs::OsRng, RngCore}; - -use super::types::{CipherText, Nonce}; +use sha2::Digest; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. @@ -14,13 +10,24 @@ pub struct EphemeralKeyHolder { } impl EphemeralKeyHolder { - pub fn new_os_random() -> Self { - let mut bytes = FieldBytes::default(); + pub fn new( + receiver_nullifier_public_key: [u8; 32], + sender_outgoing_viewing_secret_key: Scalar, + nonce: u64, + ) -> Self { + let mut hasher = sha2::Sha256::new(); + hasher.update(receiver_nullifier_public_key); + hasher.update(nonce.to_le_bytes()); + hasher.update([0; 192]); - OsRng.fill_bytes(&mut bytes); + let hash_recepient = hasher.finalize(); + + let mut hasher = sha2::Sha256::new(); + hasher.update(sender_outgoing_viewing_secret_key.to_bytes()); + hasher.update(hash_recepient); Self { - ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(), + ephemeral_secret_key: Scalar::from_repr(hasher.finalize()).unwrap(), } } @@ -30,21 +37,9 @@ impl EphemeralKeyHolder { pub fn calculate_shared_secret_sender( &self, - viewing_public_key_receiver: AffinePoint, - ) -> AffinePoint { - (viewing_public_key_receiver * self.ephemeral_secret_key).into() - } - - pub fn encrypt_data( - &self, - viewing_public_key_receiver: AffinePoint, - data: &[u8], - ) -> (CipherText, Nonce) { - let shared_secret = self.calculate_shared_secret_sender(viewing_public_key_receiver); - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); - - (cipher.encrypt(&nonce, data).unwrap(), nonce) + receiver_incoming_viewing_public_key: Scalar, + ) -> Scalar { + receiver_incoming_viewing_public_key * self.ephemeral_secret_key } pub fn log(&self) { diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index fb75dff..d3a4b08 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; use common::merkle_tree_public::TreeHashType; use elliptic_curve::group::GroupEncoding; @@ -23,8 +21,6 @@ pub mod types; pub struct KeyChain { top_secret_key_holder: TopSecretKeyHolder, pub private_key_holder: PrivateKeyHolder, - ///Map for all users accounts - pub_account_signing_keys: HashMap, pub nullifer_public_key: [u8; 32], pub incoming_viewing_public_key: PublicKey, } @@ -46,61 +42,27 @@ impl KeyChain { private_key_holder, nullifer_public_key, incoming_viewing_public_key, - pub_account_signing_keys: HashMap::new(), - } - } - - pub fn new_os_random_with_accounts(accounts: HashMap) -> Self { - //Currently dropping SeedHolder at the end of initialization. - //Now entirely sure if we need it in the future. - let seed_holder = SeedHolder::new_os_random(); - let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - - let private_key_holder = top_secret_key_holder.produce_private_key_holder(); - - let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); - let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); - - Self { - top_secret_key_holder, - private_key_holder, - nullifer_public_key, - incoming_viewing_public_key, - pub_account_signing_keys: accounts, } } pub fn produce_user_address(&self) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); - hasher.update(&self.nullifer_public_key); - hasher.update(&self.incoming_viewing_public_key.to_bytes()); + hasher.update(self.nullifer_public_key); + hasher.update(self.incoming_viewing_public_key.to_bytes()); ::from(hasher.finalize_fixed()) } - pub fn generate_new_private_key(&mut self) -> nssa::Address { - let private_key = nssa::PrivateKey::new_os_random(); - let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key)); - - self.pub_account_signing_keys.insert(address, private_key); - - address - } - - /// Returns the signing key for public transaction signatures - pub fn get_pub_account_signing_key( - &self, - address: &nssa::Address, - ) -> Option<&nssa::PrivateKey> { - self.pub_account_signing_keys.get(address) - } - pub fn calculate_shared_secret_receiver( &self, ephemeral_public_key_sender: AffinePoint, ) -> AffinePoint { - (ephemeral_public_key_sender * self.utxo_secret_key_holder.viewing_secret_key).into() + (ephemeral_public_key_sender + * self + .top_secret_key_holder + .generate_incloming_viewing_secret_key()) + .into() } pub fn decrypt_data( @@ -197,9 +159,19 @@ mod tests { fn test_decrypt_data() { let address_key_holder = KeyChain::new_os_random(); + let test_receiver_nullifier_public_key = [42; 32]; + let sender_outgoing_viewing_key = address_key_holder + .top_secret_key_holder + .generate_outgoing_viewing_secret_key(); + let nonce = 0; + // Generate an ephemeral key and shared secret - let ephemeral_public_key_sender = - EphemeralKeyHolder::new_os_random().generate_ephemeral_public_key(); + let ephemeral_public_key_sender = EphemeralKeyHolder::new( + test_receiver_nullifier_public_key, + sender_outgoing_viewing_key, + nonce, + ) + .generate_ephemeral_public_key(); let shared_secret = address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); @@ -340,19 +312,6 @@ mod tests { assert_eq!(decrypted_data, plaintext); } - #[test] - fn test_get_public_account_signing_key() { - let mut address_key_holder = KeyChain::new_os_random(); - - let address = address_key_holder.generate_new_private_key(); - - let is_private_key_generated = address_key_holder - .get_pub_account_signing_key(&address) - .is_some(); - - assert!(is_private_key_generated); - } - #[test] fn key_generation_test() { let seed_holder = SeedHolder::new_os_random(); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index a5258b5..5180efb 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -45,7 +45,7 @@ impl SeedHolder { let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed"); for _ in 1..2048 { - hash = hmac_sha512::HMAC::mac(&hash, "NSSA_seed"); + hash = hmac_sha512::HMAC::mac(hash, "NSSA_seed"); } //Safe unwrap @@ -64,7 +64,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([1u8]); hasher.update([0u8; 176]); @@ -75,7 +75,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([2u8]); hasher.update([0u8; 176]); @@ -88,7 +88,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([3u8]); hasher.update([0u8; 176]); @@ -111,7 +111,7 @@ impl PrivateKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.nullifier_secret_key); + hasher.update(self.nullifier_secret_key); hasher.update([7u8]); hasher.update([0u8; 176]); diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 67cb52c..44d8ff6 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -10,17 +10,14 @@ pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NSSAUserData { - pub key_holder: KeyChain, + ///Map for all user public accounts + pub_account_signing_keys: HashMap, + ///Map for all user private accounts + user_private_accounts: HashMap, } impl NSSAUserData { - pub fn new() -> Self { - let key_holder = KeyChain::new_os_random(); - - Self { key_holder } - } - - fn valid_key_transaction_pairing_check( + fn valid_public_key_transaction_pairing_check( accounts_keys_map: &HashMap, ) -> bool { let mut check_res = true; @@ -32,30 +29,78 @@ impl NSSAUserData { check_res } + fn valid_private_key_transaction_pairing_check( + accounts_keys_map: &HashMap, + ) -> bool { + let mut check_res = true; + for (addr, key) in accounts_keys_map { + if nssa::Address::new(key.produce_user_address()) != *addr { + check_res = false; + } + } + check_res + } + pub fn new_with_accounts( accounts_keys: HashMap, + accounts_key_chains: HashMap, ) -> Result { - if !Self::valid_key_transaction_pairing_check(&accounts_keys) { + if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) { anyhow::bail!("Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"); } - let key_holder = KeyChain::new_os_random_with_accounts(accounts_keys); + if !Self::valid_private_key_transaction_pairing_check(&accounts_key_chains) { + anyhow::bail!("Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"); + } - Ok(Self { key_holder }) + Ok(Self { + pub_account_signing_keys: accounts_keys, + user_private_accounts: accounts_key_chains, + }) } - pub fn generate_new_account(&mut self) -> nssa::Address { - self.key_holder.generate_new_private_key() + /// Generated new private key for public transaction signatures + /// + /// Returns the address of new account + pub fn generate_new_public_transaction_private_key(&mut self) -> nssa::Address { + let private_key = nssa::PrivateKey::new_os_random(); + let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key)); + + self.pub_account_signing_keys.insert(address, private_key); + + address } - pub fn get_account_signing_key(&self, address: &nssa::Address) -> Option<&nssa::PrivateKey> { - self.key_holder.get_pub_account_signing_key(address) + /// Returns the signing key for public transaction signatures + pub fn get_pub_account_signing_key( + &self, + address: &nssa::Address, + ) -> Option<&nssa::PrivateKey> { + self.pub_account_signing_keys.get(address) + } + + /// Generated new private key for privacy preserving transactions + /// + /// Returns the address of new account + pub fn generate_new_privacy_preserving_transaction_key_chain(&mut self) -> nssa::Address { + let key_chain = KeyChain::new_os_random(); + let address = nssa::Address::new(key_chain.produce_user_address()); + + self.user_private_accounts.insert(address, key_chain); + + address + } + + /// Returns the signing key for public transaction signatures + pub fn get_private_account_key_chain(&self, address: &nssa::Address) -> Option<&KeyChain> { + self.user_private_accounts.get(address) } } impl Default for NSSAUserData { fn default() -> Self { - Self::new() + //Safe unwrap as maps are empty + Self::new_with_accounts(HashMap::default(), HashMap::default()).unwrap() } } @@ -65,8 +110,19 @@ mod tests { #[test] fn test_new_account() { - let mut user_data = NSSAUserData::new(); + let mut user_data = NSSAUserData::default(); - let _addr = user_data.generate_new_account(); + let addr_pub = user_data.generate_new_public_transaction_private_key(); + let addr_private = user_data.generate_new_privacy_preserving_transaction_key_chain(); + + let is_private_key_generated = user_data.get_pub_account_signing_key(&addr_pub).is_some(); + + assert!(is_private_key_generated); + + let is_key_chain_generated = user_data + .get_private_account_key_chain(&addr_private) + .is_some(); + + assert!(is_key_chain_generated); } } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 7dfb8a2..e68b4a8 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -24,7 +24,7 @@ impl WalletChainStore { let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]); Ok(Self { - user_data: NSSAUserData::new_with_accounts(accounts_keys)?, + user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?, utxo_commitments_store, wallet_config: config, }) diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index e2213dc..1cbbb94 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -41,7 +41,9 @@ impl WalletCore { } pub fn create_new_account(&mut self) -> Address { - self.storage.user_data.generate_new_account() + self.storage + .user_data + .generate_new_public_transaction_private_key() } pub fn search_for_initial_account(&self, acc_addr: Address) -> Option { @@ -75,7 +77,7 @@ impl WalletCore { ) .unwrap(); - let signing_key = self.storage.user_data.get_account_signing_key(&from); + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); if let Some(signing_key) = signing_key { let witness_set = From 48374139899147eccb2f26a32c97f89f944c0ebc Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 15:03:02 +0300 Subject: [PATCH 22/37] fix: merge fix --- key_protocol/src/key_management/ephemeral_key_holder.rs | 8 ++------ key_protocol/src/key_protocol_core/mod.rs | 2 +- wallet/src/chain_storage/mod.rs | 1 - wallet/src/helperfunctions.rs | 2 +- wallet/src/lib.rs | 4 ++-- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 55120a6..108a41e 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,11 +1,7 @@ -use aes_gcm::{AeadCore, Aes256Gcm, KeyInit, aead::Aead}; use elliptic_curve::PrimeField; -use elliptic_curve::point::AffineCoordinates; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use log::info; -use rand::{RngCore, rngs::OsRng}; - -use super::constants_types::{CipherText, Nonce}; +use sha2::Digest; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 44d8ff6..167f8fe 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -11,7 +11,7 @@ pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NSSAUserData { ///Map for all user public accounts - pub_account_signing_keys: HashMap, + pub pub_account_signing_keys: HashMap, ///Map for all user private accounts user_private_accounts: HashMap, } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index e08650c..9a6bd59 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -32,7 +32,6 @@ impl WalletChainStore { pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) { self.user_data - .key_holder .pub_account_signing_keys .insert(acc_data.address, acc_data.pub_sign_key); } diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 8a7b7b6..45bb5f0 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -53,7 +53,7 @@ pub fn fetch_persistent_accounts() -> Result> { pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec { let mut vec_for_storage = vec![]; - for (addr, key) in &user_data.key_holder.pub_account_signing_keys { + for (addr, key) in &user_data.pub_account_signing_keys { vec_for_storage.push(PersistentAccountData { address: *addr, pub_sign_key: key.clone(), diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index a52b1c6..f9c5f24 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -111,7 +111,7 @@ impl WalletCore { ) .unwrap(); - let signing_key = self.storage.user_data.get_account_signing_key(&from); + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); let Some(signing_key) = signing_key else { return Err(ExecutionFailureKind::KeyNotFoundError); @@ -228,7 +228,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { Command::RegisterAccount {} => { let addr = wallet_core.create_new_account(); - let key = wallet_core.storage.user_data.get_account_signing_key(&addr); + let key = wallet_core.storage.user_data.get_pub_account_signing_key(&addr); info!("Generated new account with addr {addr:#?}"); info!("With key {key:#?}"); From 23e79d3e17dd743efd50696b9d7584008d86356f Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 15:23:32 +0300 Subject: [PATCH 23/37] fix: fmt --- key_protocol/src/key_management/mod.rs | 4 ++-- key_protocol/src/key_management/secret_holders.rs | 2 +- key_protocol/src/key_protocol_core/mod.rs | 8 ++++++-- wallet/src/lib.rs | 5 ++++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index 9fc3f06..ea337dd 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,4 +1,4 @@ -use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; +use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead}; use common::merkle_tree_public::TreeHashType; use elliptic_curve::group::GroupEncoding; use elliptic_curve::point::AffineCoordinates; @@ -6,7 +6,7 @@ use k256::AffinePoint; use log::info; use secret_holders::{PrivateKeyHolder, SeedHolder, TopSecretKeyHolder}; use serde::{Deserialize, Serialize}; -use sha2::{digest::FixedOutput, Digest}; +use sha2::{Digest, digest::FixedOutput}; use types::{CipherText, Nonce}; use crate::key_protocol_core::PublicKey; diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 9387747..b6784cd 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -2,7 +2,7 @@ use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, Scalar}; -use rand::{rngs::OsRng, RngCore}; +use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 167f8fe..58f60ce 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -46,11 +46,15 @@ impl NSSAUserData { accounts_key_chains: HashMap, ) -> Result { if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) { - anyhow::bail!("Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"); + anyhow::bail!( + "Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" + ); } if !Self::valid_private_key_transaction_pairing_check(&accounts_key_chains) { - anyhow::bail!("Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"); + anyhow::bail!( + "Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" + ); } Ok(Self { diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index f9c5f24..5a24933 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -228,7 +228,10 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { Command::RegisterAccount {} => { let addr = wallet_core.create_new_account(); - let key = wallet_core.storage.user_data.get_pub_account_signing_key(&addr); + let key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&addr); info!("Generated new account with addr {addr:#?}"); info!("With key {key:#?}"); From d24969387c563bbc3ca4fb852403edd02dd8d5dc Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 8 Sep 2025 19:29:56 -0300 Subject: [PATCH 24/37] wip --- nssa/core/src/account.rs | 4 +++- nssa/core/src/circuit_io.rs | 4 ++-- nssa/core/src/program.rs | 7 ++++--- .../privacy_preserving_transaction/circuit.rs | 18 ++++++++++++------ .../transaction.rs | 2 +- nssa/src/program.rs | 15 +++++++++------ nssa/src/public_transaction/transaction.rs | 2 +- nssa/src/state.rs | 12 ++++++------ 8 files changed, 38 insertions(+), 26 deletions(-) diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 688611e..1932de0 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -14,11 +14,13 @@ pub struct Account { pub nonce: Nonce, } +pub type FingerPrint = [u8; 32]; + #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct AccountWithMetadata { pub account: Account, - pub is_authorized: bool, + pub fingerprint: FingerPrint, } #[cfg(test)] diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index e619b2d..da989f0 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -56,7 +56,7 @@ mod tests { data: b"test data".to_vec(), nonce: 18446744073709551614, }, - is_authorized: true, + fingerprint: [0; 32], }, AccountWithMetadata { account: Account { @@ -65,7 +65,7 @@ mod tests { data: b"test data".to_vec(), nonce: 9999999999999999999999, }, - is_authorized: false, + fingerprint: [1; 32], }, ], public_post_states: vec![Account { diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index d284bbc..aa2684e 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,4 +1,4 @@ -use crate::account::{Account, AccountWithMetadata}; +use crate::account::{Account, AccountWithMetadata, FingerPrint}; use risc0_zkvm::serde::Deserializer; use risc0_zkvm::{DeserializeOwned, guest::env}; use serde::{Deserialize, Serialize}; @@ -21,8 +21,9 @@ pub struct ProgramOutput { pub fn read_nssa_inputs() -> ProgramInput { let pre_states: Vec = env::read(); - let words: InstructionData = env::read(); - let instruction = T::deserialize(&mut Deserializer::new(words.as_ref())).unwrap(); + let instruction_words: InstructionData = env::read(); + let authorized_fingerprints: Vec = env::read(); + let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap(); ProgramInput { pre_states, instruction, diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index ed32f98..e681c78 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,7 +1,7 @@ use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, - account::AccountWithMetadata, + account::{AccountWithMetadata, FingerPrint}, program::{InstructionData, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -72,10 +72,16 @@ fn execute_and_prove_program( program: &Program, pre_states: &[AccountWithMetadata], instruction_data: &InstructionData, + authorized_fingerprints: &[FingerPrint], ) -> Result { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); - Program::write_inputs(pre_states, instruction_data, &mut env_builder)?; + Program::write_inputs( + pre_states, + instruction_data, + authorized_fingerprints, + &mut env_builder, + )?; let env = env_builder.build().unwrap(); // Prove the program @@ -112,12 +118,12 @@ mod tests { balance: 100, ..Account::default() }, - is_authorized: true, + fingerprint: [0; 32], }; let recipient = AccountWithMetadata { account: Account::default(), - is_authorized: false, + fingerprint: [1; 32], }; let balance_to_move: u128 = 37; @@ -181,7 +187,7 @@ mod tests { nonce: 0xdeadbeef, ..Account::default() }, - is_authorized: true, + fingerprint: [0; 32], }; let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); @@ -189,7 +195,7 @@ mod tests { let recipient = AccountWithMetadata { account: Account::default(), - is_authorized: false, + fingerprint: [1; 32], }; let balance_to_move: u128 = 37; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 9aac54e..c782d01 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -92,7 +92,7 @@ impl PrivacyPreservingTransaction { .iter() .map(|address| AccountWithMetadata { account: state.get_account_by_address(address), - is_authorized: signer_addresses.contains(address), + fingerprint: *address.value(), }) .collect(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 66358e9..a40fdf9 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -1,5 +1,5 @@ use nssa_core::{ - account::{Account, AccountWithMetadata}, + account::{Account, AccountWithMetadata, FingerPrint}, program::{InstructionData, ProgramId, ProgramOutput}, }; use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; @@ -33,10 +33,11 @@ impl Program { &self, pre_states: &[AccountWithMetadata], instruction_data: &InstructionData, + authorized_fingerprints: &[FingerPrint] ) -> Result, NssaError> { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); - Self::write_inputs(pre_states, instruction_data, &mut env_builder)?; + Self::write_inputs(pre_states, instruction_data, authorized_fingerprints, &mut env_builder)?; let env = env_builder.build().unwrap(); // Execute the program (without proving) @@ -58,11 +59,13 @@ impl Program { pub(crate) fn write_inputs( pre_states: &[AccountWithMetadata], instruction_data: &[u32], + authorized_fingerprints: &[FingerPrint], env_builder: &mut ExecutorEnvBuilder, ) -> Result<(), NssaError> { let pre_states = pre_states.to_vec(); + let authorized_fingerprints = authorized_fingerprints.to_vec(); env_builder - .write(&(pre_states, instruction_data)) + .write(&(pre_states, instruction_data, authorized_fingerprints)) .map_err(|e| NssaError::ProgramWriteInputFailed(e.to_string()))?; Ok(()) } @@ -173,11 +176,11 @@ mod tests { balance: 77665544332211, ..Account::default() }, - is_authorized: false, + fingerprint: [0; 32] }; let recipient = AccountWithMetadata { account: Account::default(), - is_authorized: false, + fingerprint: [1; 32] }; let expected_sender_post = Account { @@ -189,7 +192,7 @@ mod tests { ..Account::default() }; let [sender_post, recipient_post] = program - .execute(&[sender, recipient], &instruction_data) + .execute(&[sender, recipient], &instruction_data, &[]) .unwrap() .try_into() .unwrap(); diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 20b2729..bce7eaa 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -93,7 +93,7 @@ impl PublicTransaction { .iter() .map(|address| AccountWithMetadata { account: state.get_account_by_address(address), - is_authorized: signer_addresses.contains(address), + fingerprint: *address.value() }) .collect(); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index f980370..5ed4252 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -778,14 +778,14 @@ pub mod tests { ) -> PrivacyPreservingTransaction { let sender = AccountWithMetadata { account: state.get_account_by_address(&sender_keys.address()), - is_authorized: true, + fingerprint: *sender_keys.address().value(), }; let sender_nonce = sender.account.nonce; let recipient = AccountWithMetadata { account: Account::default(), - is_authorized: false, + fingerprint: recipient_keys.npk().to_byte_array(), }; let esk = [3; 32]; @@ -827,11 +827,11 @@ pub mod tests { let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); let sender_pre = AccountWithMetadata { account: sender_private_account.clone(), - is_authorized: true, + fingerprint: sender_keys.npk().to_byte_array(), }; let recipient_pre = AccountWithMetadata { account: Account::default(), - is_authorized: false, + fingerprint: recipient_keys.npk().to_byte_array(), }; let esk_1 = [3; 32]; @@ -887,11 +887,11 @@ pub mod tests { let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); let sender_pre = AccountWithMetadata { account: sender_private_account.clone(), - is_authorized: true, + fingerprint: sender_keys.npk().to_byte_array(), }; let recipient_pre = AccountWithMetadata { account: state.get_account_by_address(recipient_address), - is_authorized: false, + fingerprint: *recipient_address.value(), }; let esk = [3; 32]; From d63cde85b9034fbe4c0d01f75156c9018c5db187 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 9 Sep 2025 17:03:58 -0300 Subject: [PATCH 25/37] rollback to is_authorized field --- nssa/core/src/account.rs | 1 + nssa/core/src/circuit_io.rs | 2 ++ nssa/core/src/program.rs | 1 - .../src/privacy_preserving_transaction/circuit.rs | 6 ++++-- .../privacy_preserving_transaction/transaction.rs | 1 + nssa/src/program.rs | 15 +++++++-------- nssa/src/public_transaction/transaction.rs | 1 + nssa/src/state.rs | 6 ++++++ 8 files changed, 22 insertions(+), 11 deletions(-) diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 1932de0..94986c2 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -20,6 +20,7 @@ pub type FingerPrint = [u8; 32]; #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct AccountWithMetadata { pub account: Account, + pub is_authorized: bool, pub fingerprint: FingerPrint, } diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index da989f0..194b371 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -56,6 +56,7 @@ mod tests { data: b"test data".to_vec(), nonce: 18446744073709551614, }, + is_authorized: true, fingerprint: [0; 32], }, AccountWithMetadata { @@ -65,6 +66,7 @@ mod tests { data: b"test data".to_vec(), nonce: 9999999999999999999999, }, + is_authorized: false, fingerprint: [1; 32], }, ], diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index aa2684e..48593d8 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -22,7 +22,6 @@ pub struct ProgramOutput { pub fn read_nssa_inputs() -> ProgramInput { let pre_states: Vec = env::read(); let instruction_words: InstructionData = env::read(); - let authorized_fingerprints: Vec = env::read(); let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap(); ProgramInput { pre_states, diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index e681c78..ba7647c 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -72,14 +72,12 @@ fn execute_and_prove_program( program: &Program, pre_states: &[AccountWithMetadata], instruction_data: &InstructionData, - authorized_fingerprints: &[FingerPrint], ) -> Result { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); Program::write_inputs( pre_states, instruction_data, - authorized_fingerprints, &mut env_builder, )?; let env = env_builder.build().unwrap(); @@ -118,11 +116,13 @@ mod tests { balance: 100, ..Account::default() }, + is_authorized: true, fingerprint: [0; 32], }; let recipient = AccountWithMetadata { account: Account::default(), + is_authorized: false, fingerprint: [1; 32], }; @@ -187,6 +187,7 @@ mod tests { nonce: 0xdeadbeef, ..Account::default() }, + is_authorized: true, fingerprint: [0; 32], }; let sender_keys = test_private_account_keys_1(); @@ -195,6 +196,7 @@ mod tests { let recipient = AccountWithMetadata { account: Account::default(), + is_authorized: false, fingerprint: [1; 32], }; let balance_to_move: u128 = 37; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index c782d01..ee8eeba 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -92,6 +92,7 @@ impl PrivacyPreservingTransaction { .iter() .map(|address| AccountWithMetadata { account: state.get_account_by_address(address), + is_authorized: signer_addresses.contains(address), fingerprint: *address.value(), }) .collect(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index a40fdf9..0c05902 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -33,11 +33,10 @@ impl Program { &self, pre_states: &[AccountWithMetadata], instruction_data: &InstructionData, - authorized_fingerprints: &[FingerPrint] ) -> Result, NssaError> { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); - Self::write_inputs(pre_states, instruction_data, authorized_fingerprints, &mut env_builder)?; + Self::write_inputs(pre_states, instruction_data, &mut env_builder)?; let env = env_builder.build().unwrap(); // Execute the program (without proving) @@ -59,13 +58,11 @@ impl Program { pub(crate) fn write_inputs( pre_states: &[AccountWithMetadata], instruction_data: &[u32], - authorized_fingerprints: &[FingerPrint], env_builder: &mut ExecutorEnvBuilder, ) -> Result<(), NssaError> { let pre_states = pre_states.to_vec(); - let authorized_fingerprints = authorized_fingerprints.to_vec(); env_builder - .write(&(pre_states, instruction_data, authorized_fingerprints)) + .write(&(pre_states, instruction_data)) .map_err(|e| NssaError::ProgramWriteInputFailed(e.to_string()))?; Ok(()) } @@ -176,11 +173,13 @@ mod tests { balance: 77665544332211, ..Account::default() }, - fingerprint: [0; 32] + is_authorized: true, + fingerprint: [0; 32], }; let recipient = AccountWithMetadata { account: Account::default(), - fingerprint: [1; 32] + is_authorized: false, + fingerprint: [1; 32], }; let expected_sender_post = Account { @@ -192,7 +191,7 @@ mod tests { ..Account::default() }; let [sender_post, recipient_post] = program - .execute(&[sender, recipient], &instruction_data, &[]) + .execute(&[sender, recipient], &instruction_data) .unwrap() .try_into() .unwrap(); diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index bce7eaa..f3a8ed6 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -93,6 +93,7 @@ impl PublicTransaction { .iter() .map(|address| AccountWithMetadata { account: state.get_account_by_address(address), + is_authorized: signer_addresses.contains(address), fingerprint: *address.value() }) .collect(); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 5ed4252..8662323 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -778,6 +778,7 @@ pub mod tests { ) -> PrivacyPreservingTransaction { let sender = AccountWithMetadata { account: state.get_account_by_address(&sender_keys.address()), + is_authorized: true, fingerprint: *sender_keys.address().value(), }; @@ -785,6 +786,7 @@ pub mod tests { let recipient = AccountWithMetadata { account: Account::default(), + is_authorized: false, fingerprint: recipient_keys.npk().to_byte_array(), }; @@ -827,10 +829,12 @@ pub mod tests { let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); let sender_pre = AccountWithMetadata { account: sender_private_account.clone(), + is_authorized: true, fingerprint: sender_keys.npk().to_byte_array(), }; let recipient_pre = AccountWithMetadata { account: Account::default(), + is_authorized: false, fingerprint: recipient_keys.npk().to_byte_array(), }; @@ -887,10 +891,12 @@ pub mod tests { let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); let sender_pre = AccountWithMetadata { account: sender_private_account.clone(), + is_authorized: true, fingerprint: sender_keys.npk().to_byte_array(), }; let recipient_pre = AccountWithMetadata { account: state.get_account_by_address(recipient_address), + is_authorized: false, fingerprint: *recipient_address.value(), }; From 3a9d9af8150a2ea0710b73657ad7e4231be23039 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 10 Sep 2025 18:56:34 -0300 Subject: [PATCH 26/37] wip --- nssa/core/src/account.rs | 11 ++++++++-- nssa/core/src/circuit_io.rs | 7 +++---- nssa/core/src/nullifier.rs | 14 ++++++++++++- .../src/bin/privacy_preserving_circuit.rs | 11 +++++----- nssa/src/address.rs | 15 +++++++++++++ .../privacy_preserving_transaction/circuit.rs | 21 ++++++++----------- .../transaction.rs | 2 +- nssa/src/program.rs | 6 +++--- nssa/src/public_transaction/transaction.rs | 2 +- nssa/src/state.rs | 12 +++++------ 10 files changed, 65 insertions(+), 36 deletions(-) diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 94986c2..45e6e2c 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -1,4 +1,4 @@ -use crate::program::ProgramId; +use crate::{NullifierPublicKey, program::ProgramId}; use serde::{Deserialize, Serialize}; pub type Nonce = u128; @@ -14,7 +14,14 @@ pub struct Account { pub nonce: Nonce, } -pub type FingerPrint = [u8; 32]; +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Debug))] +pub struct FingerPrint([u8; 32]); +impl FingerPrint { + pub fn new(value: [u8; 32]) -> Self { + Self(value) + } +} #[derive(Serialize, Deserialize, Clone)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index 194b371..2473475 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -40,8 +40,7 @@ impl PrivacyPreservingCircuitOutput { mod tests { use super::*; use crate::{ - Commitment, Nullifier, NullifierPublicKey, - account::{Account, AccountWithMetadata}, + account::{Account, AccountWithMetadata, FingerPrint}, Commitment, Nullifier, NullifierPublicKey }; use risc0_zkvm::serde::from_slice; @@ -57,7 +56,7 @@ mod tests { nonce: 18446744073709551614, }, is_authorized: true, - fingerprint: [0; 32], + fingerprint: FingerPrint::new([0; 32]), }, AccountWithMetadata { account: Account { @@ -67,7 +66,7 @@ mod tests { nonce: 9999999999999999999999, }, is_authorized: false, - fingerprint: [1; 32], + fingerprint: FingerPrint::new([1; 32]), }, ], public_post_states: vec![Account { diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index d1410de..c783091 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -1,12 +1,24 @@ use risc0_zkvm::sha::{Impl, Sha256}; use serde::{Deserialize, Serialize}; -use crate::Commitment; +use crate::{Commitment, account::FingerPrint}; #[derive(Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] pub struct NullifierPublicKey(pub(super) [u8; 32]); +impl From<&NullifierPublicKey> for FingerPrint { + fn from(value: &NullifierPublicKey) -> Self { + FingerPrint::new(value.0) + } +} + +impl From for FingerPrint { + fn from(value: NullifierPublicKey) -> Self { + FingerPrint::new(value.0) + } +} + impl From<&NullifierSecretKey> for NullifierPublicKey { fn from(value: &NullifierSecretKey) -> Self { let mut bytes = Vec::new(); 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..346682d 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,12 +1,7 @@ use risc0_zkvm::{guest::env, serde::to_vec}; use nssa_core::{ - account::{Account, AccountWithMetadata}, - compute_digest_for_path, - encryption::Ciphertext, - program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, - Commitment, CommitmentSetDigest, EncryptionScheme, Nullifier, NullifierPublicKey, - PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + account::{Account, AccountWithMetadata, FingerPrint}, compute_digest_for_path, encryption::Ciphertext, program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, Commitment, CommitmentSetDigest, EncryptionScheme, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput }; fn main() { @@ -70,6 +65,10 @@ fn main() { let new_nonce = private_nonces_iter.next().expect("Missing private nonce"); let (npk, shared_secret) = private_keys_iter.next().expect("Missing keys"); + if FingerPrint::from(npk) != pre_states[i].fingerprint { + panic!("Fingerprint mismatch"); + } + if visibility_mask[i] == 1 { // Private account with authentication let (nsk, membership_proof) = diff --git a/nssa/src/address.rs b/nssa/src/address.rs index 93304d5..0dba65a 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -1,5 +1,6 @@ use std::{fmt::Display, str::FromStr}; +use nssa_core::account::FingerPrint; use serde::{Deserialize, Serialize}; use crate::signature::PublicKey; @@ -81,6 +82,20 @@ impl<'de> Deserialize<'de> for Address { } } + +impl From<&Address> for FingerPrint { + fn from(address: &Address) -> Self { + FingerPrint::new(address.value) + } +} + +impl From
for FingerPrint { + fn from(address: Address) -> Self { + FingerPrint::new(address.value) + } +} + + #[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 ba7647c..c1afe39 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -75,11 +75,7 @@ fn execute_and_prove_program( ) -> Result { // Write inputs to the program let mut env_builder = ExecutorEnv::builder(); - Program::write_inputs( - pre_states, - instruction_data, - &mut env_builder, - )?; + Program::write_inputs(pre_states, instruction_data, &mut env_builder)?; let env = env_builder.build().unwrap(); // Prove the program @@ -110,6 +106,7 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() { + let recipient_keys = test_private_account_keys_1(); let program = Program::authenticated_transfer_program(); let sender = AccountWithMetadata { account: Account { @@ -117,13 +114,13 @@ mod tests { ..Account::default() }, is_authorized: true, - fingerprint: [0; 32], + fingerprint: FingerPrint::new([0; 32]), }; let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, - fingerprint: [1; 32], + fingerprint: recipient_keys.npk().into(), }; let balance_to_move: u128 = 37; @@ -143,7 +140,6 @@ mod tests { }; let expected_sender_pre = sender.clone(); - let recipient_keys = test_private_account_keys_1(); let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); @@ -181,6 +177,9 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_fully_private() { + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let sender_pre = AccountWithMetadata { account: Account { balance: 100, @@ -188,16 +187,14 @@ mod tests { ..Account::default() }, is_authorized: true, - fingerprint: [0; 32], + fingerprint: sender_keys.npk().into(), }; - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account); let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, - fingerprint: [1; 32], + fingerprint: recipient_keys.npk().into(), }; let balance_to_move: u128 = 37; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index ee8eeba..a683f85 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -93,7 +93,7 @@ impl PrivacyPreservingTransaction { .map(|address| AccountWithMetadata { account: state.get_account_by_address(address), is_authorized: signer_addresses.contains(address), - fingerprint: *address.value(), + fingerprint: address.into(), }) .collect(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 0c05902..cf7c9da 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -77,7 +77,7 @@ impl Program { #[cfg(test)] mod tests { - use nssa_core::account::{Account, AccountWithMetadata}; + use nssa_core::account::{Account, AccountWithMetadata, FingerPrint}; use crate::program::Program; @@ -174,12 +174,12 @@ mod tests { ..Account::default() }, is_authorized: true, - fingerprint: [0; 32], + fingerprint: FingerPrint::new([0; 32]), }; let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, - fingerprint: [1; 32], + fingerprint: FingerPrint::new([1; 32]), }; let expected_sender_post = Account { diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index f3a8ed6..64e0707 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -94,7 +94,7 @@ impl PublicTransaction { .map(|address| AccountWithMetadata { account: state.get_account_by_address(address), is_authorized: signer_addresses.contains(address), - fingerprint: *address.value() + fingerprint: address.into() }) .collect(); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 8662323..347102a 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -779,7 +779,7 @@ pub mod tests { let sender = AccountWithMetadata { account: state.get_account_by_address(&sender_keys.address()), is_authorized: true, - fingerprint: *sender_keys.address().value(), + fingerprint: sender_keys.address().into(), }; let sender_nonce = sender.account.nonce; @@ -787,7 +787,7 @@ pub mod tests { let recipient = AccountWithMetadata { account: Account::default(), is_authorized: false, - fingerprint: recipient_keys.npk().to_byte_array(), + fingerprint: recipient_keys.npk().into(), }; let esk = [3; 32]; @@ -830,12 +830,12 @@ pub mod tests { let sender_pre = AccountWithMetadata { account: sender_private_account.clone(), is_authorized: true, - fingerprint: sender_keys.npk().to_byte_array(), + fingerprint: sender_keys.npk().into(), }; let recipient_pre = AccountWithMetadata { account: Account::default(), is_authorized: false, - fingerprint: recipient_keys.npk().to_byte_array(), + fingerprint: recipient_keys.npk().into(), }; let esk_1 = [3; 32]; @@ -892,12 +892,12 @@ pub mod tests { let sender_pre = AccountWithMetadata { account: sender_private_account.clone(), is_authorized: true, - fingerprint: sender_keys.npk().to_byte_array(), + fingerprint: sender_keys.npk().into(), }; let recipient_pre = AccountWithMetadata { account: state.get_account_by_address(recipient_address), is_authorized: false, - fingerprint: *recipient_address.value(), + fingerprint: recipient_address.into(), }; let esk = [3; 32]; From e8ace6838f15b6ebeda3ab3d14b4f1c0628b24fa Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 11 Sep 2025 15:49:54 -0300 Subject: [PATCH 27/37] fmt, clippy --- nssa/core/src/account.rs | 2 +- nssa/core/src/circuit_io.rs | 7 ++++--- nssa/core/src/program.rs | 2 +- nssa/src/address.rs | 2 -- nssa/src/privacy_preserving_transaction/circuit.rs | 4 ++-- nssa/src/program.rs | 2 +- nssa/src/public_transaction/transaction.rs | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 45e6e2c..c3d5580 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -1,4 +1,4 @@ -use crate::{NullifierPublicKey, program::ProgramId}; +use crate::program::ProgramId; use serde::{Deserialize, Serialize}; pub type Nonce = u128; diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index 2473475..ecc25fc 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -40,7 +40,8 @@ impl PrivacyPreservingCircuitOutput { mod tests { use super::*; use crate::{ - account::{Account, AccountWithMetadata, FingerPrint}, Commitment, Nullifier, NullifierPublicKey + Commitment, Nullifier, NullifierPublicKey, + account::{Account, AccountWithMetadata, FingerPrint}, }; use risc0_zkvm::serde::from_slice; @@ -55,7 +56,7 @@ mod tests { data: b"test data".to_vec(), nonce: 18446744073709551614, }, - is_authorized: true, + is_authorized: true, fingerprint: FingerPrint::new([0; 32]), }, AccountWithMetadata { @@ -65,7 +66,7 @@ mod tests { data: b"test data".to_vec(), nonce: 9999999999999999999999, }, - is_authorized: false, + is_authorized: false, fingerprint: FingerPrint::new([1; 32]), }, ], diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 48593d8..a4e6722 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,4 +1,4 @@ -use crate::account::{Account, AccountWithMetadata, FingerPrint}; +use crate::account::{Account, AccountWithMetadata}; use risc0_zkvm::serde::Deserializer; use risc0_zkvm::{DeserializeOwned, guest::env}; use serde::{Deserialize, Serialize}; diff --git a/nssa/src/address.rs b/nssa/src/address.rs index 0dba65a..04bd99d 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -82,7 +82,6 @@ impl<'de> Deserialize<'de> for Address { } } - impl From<&Address> for FingerPrint { fn from(address: &Address) -> Self { FingerPrint::new(address.value) @@ -95,7 +94,6 @@ impl From
for FingerPrint { } } - #[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 c1afe39..82ba860 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,7 +1,7 @@ use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, - account::{AccountWithMetadata, FingerPrint}, + account::AccountWithMetadata, program::{InstructionData, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -90,7 +90,7 @@ fn execute_and_prove_program( mod tests { use nssa_core::{ Commitment, EncryptionScheme, Nullifier, - account::{Account, AccountWithMetadata}, + account::{Account, AccountWithMetadata, FingerPrint}, }; use crate::{ diff --git a/nssa/src/program.rs b/nssa/src/program.rs index cf7c9da..d105f51 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -1,5 +1,5 @@ use nssa_core::{ - account::{Account, AccountWithMetadata, FingerPrint}, + account::{Account, AccountWithMetadata}, program::{InstructionData, ProgramId, ProgramOutput}, }; use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID}; diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 64e0707..14dcc6f 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -94,7 +94,7 @@ impl PublicTransaction { .map(|address| AccountWithMetadata { account: state.get_account_by_address(address), is_authorized: signer_addresses.contains(address), - fingerprint: address.into() + fingerprint: address.into(), }) .collect(); From e12fe4492b887eed1b465edada735e14faa080e5 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 11 Sep 2025 16:37:28 -0300 Subject: [PATCH 28/37] refactor --- nssa/core/src/account.rs | 29 ++++++++++++ nssa/core/src/circuit_io.rs | 20 ++++---- nssa/core/src/nullifier.rs | 6 --- nssa/src/address.rs | 6 --- .../privacy_preserving_transaction/circuit.rs | 40 ++++++++-------- .../transaction.rs | 10 ++-- nssa/src/program.rs | 17 +++---- nssa/src/public_transaction/transaction.rs | 10 ++-- nssa/src/state.rs | 47 +++++++------------ 9 files changed, 95 insertions(+), 90 deletions(-) diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index c3d5580..54d0196 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -14,6 +14,8 @@ pub struct Account { pub nonce: Nonce, } +/// A fingerprint of the owner of an account. This can be, for example, an `Address` in case the account +/// is public, or a `NullifierPublicKey` in case the account is private. #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct FingerPrint([u8; 32]); @@ -31,6 +33,17 @@ pub struct AccountWithMetadata { pub fingerprint: FingerPrint, } +#[cfg(feature = "host")] +impl AccountWithMetadata { + pub fn new(account: Account, is_authorized: bool, fingerprint: impl Into) -> Self { + Self { + account, + is_authorized, + fingerprint: fingerprint.into(), + } + } +} + #[cfg(test)] mod tests { use crate::program::DEFAULT_PROGRAM_ID; @@ -64,4 +77,20 @@ mod tests { assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID); } + + #[test] + fn test_account_with_metadata_constructor() { + let account = Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 1337, + data: b"testing_account_with_metadata_constructor".to_vec(), + nonce: 0xdeadbeef, + }; + let fingerprint = FingerPrint::new([8; 32]); + let new_acc_with_metadata = + AccountWithMetadata::new(account.clone(), true, fingerprint.clone()); + assert_eq!(new_acc_with_metadata.account, account); + assert!(new_acc_with_metadata.is_authorized); + assert_eq!(new_acc_with_metadata.fingerprint, fingerprint); + } } diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index ecc25fc..beae76a 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -49,26 +49,26 @@ mod tests { fn test_privacy_preserving_circuit_output_to_bytes_is_compatible_with_from_slice() { let output = PrivacyPreservingCircuitOutput { public_pre_states: vec![ - AccountWithMetadata { - account: Account { + AccountWithMetadata::new( + Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 12345678901234567890, data: b"test data".to_vec(), nonce: 18446744073709551614, }, - is_authorized: true, - fingerprint: FingerPrint::new([0; 32]), - }, - AccountWithMetadata { - account: Account { + true, + FingerPrint::new([0; 32]), + ), + AccountWithMetadata::new( + Account { program_owner: [9, 9, 9, 8, 8, 8, 7, 7], balance: 123123123456456567112, data: b"test data".to_vec(), nonce: 9999999999999999999999, }, - is_authorized: false, - fingerprint: FingerPrint::new([1; 32]), - }, + false, + FingerPrint::new([1; 32]), + ), ], public_post_states: vec![Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index c783091..e852af4 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -13,12 +13,6 @@ impl From<&NullifierPublicKey> for FingerPrint { } } -impl From for FingerPrint { - fn from(value: NullifierPublicKey) -> Self { - FingerPrint::new(value.0) - } -} - impl From<&NullifierSecretKey> for NullifierPublicKey { fn from(value: &NullifierSecretKey) -> Self { let mut bytes = Vec::new(); diff --git a/nssa/src/address.rs b/nssa/src/address.rs index 04bd99d..f9d085e 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -88,12 +88,6 @@ impl From<&Address> for FingerPrint { } } -impl From
for FingerPrint { - fn from(address: Address) -> Self { - FingerPrint::new(address.value) - } -} - #[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 82ba860..281d59b 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -108,20 +108,20 @@ mod tests { fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() { let recipient_keys = test_private_account_keys_1(); let program = Program::authenticated_transfer_program(); - let sender = AccountWithMetadata { - account: Account { + let sender = AccountWithMetadata::new( + Account { balance: 100, ..Account::default() }, - is_authorized: true, - fingerprint: FingerPrint::new([0; 32]), - }; + true, + FingerPrint::new([0; 32]), + ); - let recipient = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - fingerprint: recipient_keys.npk().into(), - }; + let recipient = AccountWithMetadata::new( + Account::default(), + false, + FingerPrint::from(&recipient_keys.npk()), + ); let balance_to_move: u128 = 37; @@ -180,22 +180,22 @@ mod tests { let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); - let sender_pre = AccountWithMetadata { - account: Account { + let sender_pre = AccountWithMetadata::new( + Account { balance: 100, nonce: 0xdeadbeef, ..Account::default() }, - is_authorized: true, - fingerprint: sender_keys.npk().into(), - }; + true, + FingerPrint::from(&sender_keys.npk()), + ); let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account); - let recipient = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - fingerprint: recipient_keys.npk().into(), - }; + let recipient = AccountWithMetadata::new( + Account::default(), + false, + FingerPrint::from(&recipient_keys.npk()), + ); let balance_to_move: u128 = 37; let mut commitment_set = CommitmentSet::with_capacity(2); diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index a683f85..6992b2f 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -90,10 +90,12 @@ impl PrivacyPreservingTransaction { let public_pre_states: Vec<_> = message .public_addresses .iter() - .map(|address| AccountWithMetadata { - account: state.get_account_by_address(address), - is_authorized: signer_addresses.contains(address), - fingerprint: address.into(), + .map(|address| { + AccountWithMetadata::new( + state.get_account_by_address(address), + signer_addresses.contains(address), + address, + ) }) .collect(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index d105f51..552c436 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -168,19 +168,16 @@ mod tests { let program = Program::simple_balance_transfer(); let balance_to_move: u128 = 11223344556677; let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); - let sender = AccountWithMetadata { - account: Account { + let sender = AccountWithMetadata::new( + Account { balance: 77665544332211, ..Account::default() }, - is_authorized: true, - fingerprint: FingerPrint::new([0; 32]), - }; - let recipient = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - fingerprint: FingerPrint::new([1; 32]), - }; + true, + FingerPrint::new([0; 32]), + ); + let recipient = + AccountWithMetadata::new(Account::default(), false, FingerPrint::new([1; 32])); let expected_sender_post = Account { balance: 77665544332211 - balance_to_move, diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 14dcc6f..e5c8b5a 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -91,10 +91,12 @@ impl PublicTransaction { let pre_states: Vec<_> = message .addresses .iter() - .map(|address| AccountWithMetadata { - account: state.get_account_by_address(address), - is_authorized: signer_addresses.contains(address), - fingerprint: address.into(), + .map(|address| { + AccountWithMetadata::new( + state.get_account_by_address(address), + signer_addresses.contains(address), + address, + ) }) .collect(); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 347102a..4b8b25b 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -776,19 +776,15 @@ pub mod tests { balance_to_move: u128, state: &V01State, ) -> PrivacyPreservingTransaction { - let sender = AccountWithMetadata { - account: state.get_account_by_address(&sender_keys.address()), - is_authorized: true, - fingerprint: sender_keys.address().into(), - }; + let sender = AccountWithMetadata::new( + state.get_account_by_address(&sender_keys.address()), + true, + &sender_keys.address(), + ); let sender_nonce = sender.account.nonce; - let recipient = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - fingerprint: recipient_keys.npk().into(), - }; + let recipient = AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); @@ -827,16 +823,10 @@ pub mod tests { ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); - let sender_pre = AccountWithMetadata { - account: sender_private_account.clone(), - is_authorized: true, - fingerprint: sender_keys.npk().into(), - }; - let recipient_pre = AccountWithMetadata { - account: Account::default(), - is_authorized: false, - fingerprint: recipient_keys.npk().into(), - }; + let sender_pre = + AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk()); + let recipient_pre = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); let esk_1 = [3; 32]; let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); @@ -889,16 +879,13 @@ pub mod tests { ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); - let sender_pre = AccountWithMetadata { - account: sender_private_account.clone(), - is_authorized: true, - fingerprint: sender_keys.npk().into(), - }; - let recipient_pre = AccountWithMetadata { - account: state.get_account_by_address(recipient_address), - is_authorized: false, - fingerprint: recipient_address.into(), - }; + let sender_pre = + AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk()); + let recipient_pre = AccountWithMetadata::new( + state.get_account_by_address(recipient_address), + false, + recipient_address, + ); let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk()); From d81fb2665fb55b059396cbc815f7837289b6ef2d Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Fri, 12 Sep 2025 15:06:49 +0300 Subject: [PATCH 29/37] 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 f31bbe4876becdde6a94b73f31dd16010680e42f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 12 Sep 2025 09:18:40 -0300 Subject: [PATCH 30/37] rename fingerprint to account_id --- nssa/core/src/account.rs | 14 +++++++------- nssa/core/src/circuit_io.rs | 6 +++--- nssa/core/src/nullifier.rs | 6 +++--- .../guest/src/bin/privacy_preserving_circuit.rs | 6 +++--- nssa/src/address.rs | 6 +++--- nssa/src/privacy_preserving_transaction/circuit.rs | 10 +++++----- nssa/src/program.rs | 6 +++--- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 54d0196..fdd51e1 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -18,8 +18,8 @@ pub struct Account { /// is public, or a `NullifierPublicKey` in case the account is private. #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[cfg_attr(any(feature = "host", test), derive(Debug))] -pub struct FingerPrint([u8; 32]); -impl FingerPrint { +pub struct AccountId([u8; 32]); +impl AccountId { pub fn new(value: [u8; 32]) -> Self { Self(value) } @@ -30,16 +30,16 @@ impl FingerPrint { pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, - pub fingerprint: FingerPrint, + pub account_id: AccountId, } #[cfg(feature = "host")] impl AccountWithMetadata { - pub fn new(account: Account, is_authorized: bool, fingerprint: impl Into) -> Self { + pub fn new(account: Account, is_authorized: bool, account_id: impl Into) -> Self { Self { account, is_authorized, - fingerprint: fingerprint.into(), + account_id: account_id.into(), } } } @@ -86,11 +86,11 @@ mod tests { data: b"testing_account_with_metadata_constructor".to_vec(), nonce: 0xdeadbeef, }; - let fingerprint = FingerPrint::new([8; 32]); + let fingerprint = AccountId::new([8; 32]); let new_acc_with_metadata = AccountWithMetadata::new(account.clone(), true, fingerprint.clone()); assert_eq!(new_acc_with_metadata.account, account); assert!(new_acc_with_metadata.is_authorized); - assert_eq!(new_acc_with_metadata.fingerprint, fingerprint); + assert_eq!(new_acc_with_metadata.account_id, fingerprint); } } diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index beae76a..deeedbd 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -41,7 +41,7 @@ mod tests { use super::*; use crate::{ Commitment, Nullifier, NullifierPublicKey, - account::{Account, AccountWithMetadata, FingerPrint}, + account::{Account, AccountWithMetadata, AccountId}, }; use risc0_zkvm::serde::from_slice; @@ -57,7 +57,7 @@ mod tests { nonce: 18446744073709551614, }, true, - FingerPrint::new([0; 32]), + AccountId::new([0; 32]), ), AccountWithMetadata::new( Account { @@ -67,7 +67,7 @@ mod tests { nonce: 9999999999999999999999, }, false, - FingerPrint::new([1; 32]), + AccountId::new([1; 32]), ), ], public_post_states: vec![Account { diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index e852af4..405bb56 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -1,15 +1,15 @@ use risc0_zkvm::sha::{Impl, Sha256}; use serde::{Deserialize, Serialize}; -use crate::{Commitment, account::FingerPrint}; +use crate::{Commitment, account::AccountId}; #[derive(Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] pub struct NullifierPublicKey(pub(super) [u8; 32]); -impl From<&NullifierPublicKey> for FingerPrint { +impl From<&NullifierPublicKey> for AccountId { fn from(value: &NullifierPublicKey) -> Self { - FingerPrint::new(value.0) + AccountId::new(value.0) } } 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 346682d..398bfc6 100644 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,7 +1,7 @@ use risc0_zkvm::{guest::env, serde::to_vec}; use nssa_core::{ - account::{Account, AccountWithMetadata, FingerPrint}, compute_digest_for_path, encryption::Ciphertext, program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, Commitment, CommitmentSetDigest, EncryptionScheme, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput + account::{Account, AccountWithMetadata, AccountId}, compute_digest_for_path, encryption::Ciphertext, program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, Commitment, CommitmentSetDigest, EncryptionScheme, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput }; fn main() { @@ -65,8 +65,8 @@ fn main() { let new_nonce = private_nonces_iter.next().expect("Missing private nonce"); let (npk, shared_secret) = private_keys_iter.next().expect("Missing keys"); - if FingerPrint::from(npk) != pre_states[i].fingerprint { - panic!("Fingerprint mismatch"); + if AccountId::from(npk) != pre_states[i].account_id { + panic!("AccountId mismatch"); } if visibility_mask[i] == 1 { diff --git a/nssa/src/address.rs b/nssa/src/address.rs index f9d085e..319b236 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -use nssa_core::account::FingerPrint; +use nssa_core::account::AccountId; use serde::{Deserialize, Serialize}; use crate::signature::PublicKey; @@ -82,9 +82,9 @@ impl<'de> Deserialize<'de> for Address { } } -impl From<&Address> for FingerPrint { +impl From<&Address> for AccountId { fn from(address: &Address) -> Self { - FingerPrint::new(address.value) + AccountId::new(address.value) } } diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 281d59b..3d6f594 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -90,7 +90,7 @@ fn execute_and_prove_program( mod tests { use nssa_core::{ Commitment, EncryptionScheme, Nullifier, - account::{Account, AccountWithMetadata, FingerPrint}, + account::{Account, AccountWithMetadata, AccountId}, }; use crate::{ @@ -114,13 +114,13 @@ mod tests { ..Account::default() }, true, - FingerPrint::new([0; 32]), + AccountId::new([0; 32]), ); let recipient = AccountWithMetadata::new( Account::default(), false, - FingerPrint::from(&recipient_keys.npk()), + AccountId::from(&recipient_keys.npk()), ); let balance_to_move: u128 = 37; @@ -187,14 +187,14 @@ mod tests { ..Account::default() }, true, - FingerPrint::from(&sender_keys.npk()), + AccountId::from(&sender_keys.npk()), ); let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account); let recipient = AccountWithMetadata::new( Account::default(), false, - FingerPrint::from(&recipient_keys.npk()), + AccountId::from(&recipient_keys.npk()), ); let balance_to_move: u128 = 37; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 552c436..6def822 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -77,7 +77,7 @@ impl Program { #[cfg(test)] mod tests { - use nssa_core::account::{Account, AccountWithMetadata, FingerPrint}; + use nssa_core::account::{Account, AccountWithMetadata, AccountId}; use crate::program::Program; @@ -174,10 +174,10 @@ mod tests { ..Account::default() }, true, - FingerPrint::new([0; 32]), + AccountId::new([0; 32]), ); let recipient = - AccountWithMetadata::new(Account::default(), false, FingerPrint::new([1; 32])); + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); let expected_sender_post = Account { balance: 77665544332211 - balance_to_move, From 2c2c4fed764708ee142fc3ec02ac58e244e2fbbd Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 12 Sep 2025 09:36:26 -0300 Subject: [PATCH 31/37] add tests --- nssa/core/src/nullifier.rs | 17 +++++++++++++++++ nssa/src/address.rs | 12 ++++++++++++ 2 files changed, 29 insertions(+) diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index 405bb56..c7ee26e 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -71,4 +71,21 @@ mod tests { let npk = NullifierPublicKey::from(&nsk); assert_eq!(npk, expected_npk); } + + #[test] + fn test_account_id_from_nullifier_public_key() { + let nsk = [ + 57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30, + 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, + ]; + let npk = NullifierPublicKey::from(&nsk); + let expected_account_id = AccountId::new([ + 202, 120, 42, 189, 194, 218, 78, 244, 31, 6, 108, 169, 29, 61, 22, 221, 69, 138, 197, + 161, 241, 39, 142, 242, 242, 50, 188, 201, 99, 28, 176, 238, + ]); + + let account_id = AccountId::from(&npk); + + assert_eq!(account_id, expected_account_id); + } } diff --git a/nssa/src/address.rs b/nssa/src/address.rs index 319b236..24fc7cf 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -90,6 +90,8 @@ impl From<&Address> for AccountId { #[cfg(test)] mod tests { + use nssa_core::account::AccountId; + use crate::{Address, address::AddressError}; #[test] @@ -119,4 +121,14 @@ mod tests { let result = hex_str.parse::
().unwrap_err(); assert!(matches!(result, AddressError::InvalidLength(_))); } + + #[test] + fn test_account_id_from_address() { + let address: Address = "37".repeat(32).parse().unwrap(); + let expected_account_id = AccountId::new([55; 32]); + + let account_id = AccountId::from(&address); + + assert_eq!(account_id, expected_account_id); + } } From d91b07a785efcb373992b78afcd2fc4fa44aa44f Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 12 Sep 2025 09:39:08 -0300 Subject: [PATCH 32/37] fmt --- nssa/core/src/circuit_io.rs | 2 +- nssa/src/address.rs | 2 +- nssa/src/privacy_preserving_transaction/circuit.rs | 2 +- nssa/src/program.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index deeedbd..14feef7 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -41,7 +41,7 @@ mod tests { use super::*; use crate::{ Commitment, Nullifier, NullifierPublicKey, - account::{Account, AccountWithMetadata, AccountId}, + account::{Account, AccountId, AccountWithMetadata}, }; use risc0_zkvm::serde::from_slice; diff --git a/nssa/src/address.rs b/nssa/src/address.rs index 24fc7cf..5837e20 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -126,7 +126,7 @@ mod tests { fn test_account_id_from_address() { let address: Address = "37".repeat(32).parse().unwrap(); let expected_account_id = AccountId::new([55; 32]); - + let account_id = AccountId::from(&address); assert_eq!(account_id, expected_account_id); diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 3d6f594..d8e5701 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -90,7 +90,7 @@ fn execute_and_prove_program( mod tests { use nssa_core::{ Commitment, EncryptionScheme, Nullifier, - account::{Account, AccountWithMetadata, AccountId}, + account::{Account, AccountId, AccountWithMetadata}, }; use crate::{ diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 6def822..1096df8 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -77,7 +77,7 @@ impl Program { #[cfg(test)] mod tests { - use nssa_core::account::{Account, AccountWithMetadata, AccountId}; + use nssa_core::account::{Account, AccountId, AccountWithMetadata}; use crate::program::Program; From a11f3720917b7f5e912c5f80b3d772b33d88833f Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 15 Sep 2025 10:39:57 +0300 Subject: [PATCH 33/37] 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 d07b813739c658c629d40cb19c90c4c013a1327f Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 15 Sep 2025 14:04:49 +0300 Subject: [PATCH 34/37] fix: deviations adjustments --- key_protocol/Cargo.toml | 1 + .../key_management/ephemeral_key_holder.rs | 29 ++- key_protocol/src/key_management/mod.rs | 239 ++---------------- .../src/key_management/secret_holders.rs | 69 +++-- key_protocol/src/key_management/types.rs | 8 - nssa/Cargo.toml | 1 + .../src/encryption/shared_key_derivation.rs | 7 +- nssa/core/src/nullifier.rs | 6 + .../privacy_preserving_transaction/circuit.rs | 7 +- .../privacy_preserving_transaction/message.rs | 5 +- nssa/src/state.rs | 15 +- 11 files changed, 102 insertions(+), 285 deletions(-) delete mode 100644 key_protocol/src/key_management/types.rs diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 3ce874d..d453753 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -17,6 +17,7 @@ aes-gcm.workspace = true lazy_static.workspace = true bip39.workspace = true hmac-sha512.workspace = true +nssa-core = { path = "../nssa/core", features = ["host"] } [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 108a41e..e62d9b6 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,24 +1,30 @@ use elliptic_curve::PrimeField; -use k256::{AffinePoint, Scalar}; +use k256::Scalar; use log::info; +use nssa_core::{ + NullifierPublicKey, SharedSecretKey, + encryption::{EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey}, +}; use sha2::Digest; +use crate::key_management::secret_holders::OutgoingViewingSecretKey; + #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. pub struct EphemeralKeyHolder { - ephemeral_secret_key: Scalar, + ephemeral_secret_key: EphemeralSecretKey, } impl EphemeralKeyHolder { pub fn new( - receiver_nullifier_public_key: [u8; 32], - sender_outgoing_viewing_secret_key: Scalar, + receiver_nullifier_public_key: NullifierPublicKey, + sender_outgoing_viewing_secret_key: OutgoingViewingSecretKey, nonce: u64, ) -> Self { let mut hasher = sha2::Sha256::new(); hasher.update(receiver_nullifier_public_key); hasher.update(nonce.to_le_bytes()); - hasher.update([0; 192]); + hasher.update([0; 24]); let hash_recepient = hasher.finalize(); @@ -31,15 +37,18 @@ impl EphemeralKeyHolder { } } - pub fn generate_ephemeral_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.ephemeral_secret_key).into() + pub fn generate_ephemeral_public_key(&self) -> EphemeralPublicKey { + EphemeralPublicKey::from_scalar(self.ephemeral_secret_key) } pub fn calculate_shared_secret_sender( &self, - receiver_incoming_viewing_public_key: Scalar, - ) -> Scalar { - receiver_incoming_viewing_public_key * self.ephemeral_secret_key + receiver_incoming_viewing_public_key: IncomingViewingPublicKey, + ) -> SharedSecretKey { + SharedSecretKey::new( + &self.ephemeral_secret_key, + &receiver_incoming_viewing_public_key, + ) } pub fn log(&self) { diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index 5de3826..bcd3796 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,28 +1,25 @@ -use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead}; use common::TreeHashType; -use elliptic_curve::group::GroupEncoding; -use elliptic_curve::point::AffineCoordinates; -use k256::AffinePoint; use log::info; -use secret_holders::{PrivateKeyHolder, SeedHolder, TopSecretKeyHolder}; +use nssa_core::{ + NullifierPublicKey, SharedSecretKey, + encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, +}; +use secret_holders::{PrivateKeyHolder, SecretSpendingKey, SeedHolder}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; -use types::{CipherText, Nonce}; -use crate::key_protocol_core::PublicKey; pub type PublicAccountSigningKey = [u8; 32]; pub mod ephemeral_key_holder; pub mod secret_holders; -pub mod types; #[derive(Serialize, Deserialize, Clone, Debug)] ///Entrypoint to key management pub struct KeyChain { - top_secret_key_holder: TopSecretKeyHolder, + secret_spending_key: SecretSpendingKey, pub private_key_holder: PrivateKeyHolder, - pub nullifer_public_key: [u8; 32], - pub incoming_viewing_public_key: PublicKey, + pub nullifer_public_key: NullifierPublicKey, + pub incoming_viewing_public_key: IncomingViewingPublicKey, } impl KeyChain { @@ -30,15 +27,15 @@ impl KeyChain { //Currently dropping SeedHolder at the end of initialization. //Now entirely sure if we need it in the future. let seed_holder = SeedHolder::new_os_random(); - let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); + let secret_spending_key = seed_holder.produce_top_secret_key_holder(); - let private_key_holder = top_secret_key_holder.produce_private_key_holder(); + let private_key_holder = secret_spending_key.produce_private_key_holder(); let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); Self { - top_secret_key_holder, + secret_spending_key, private_key_holder, nullifer_public_key, incoming_viewing_public_key, @@ -48,7 +45,7 @@ impl KeyChain { pub fn produce_user_address(&self) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); - hasher.update(self.nullifer_public_key); + hasher.update(&self.nullifer_public_key); hasher.update(self.incoming_viewing_public_key.to_bytes()); ::from(hasher.finalize_fixed()) @@ -56,33 +53,20 @@ impl KeyChain { pub fn calculate_shared_secret_receiver( &self, - ephemeral_public_key_sender: AffinePoint, - ) -> AffinePoint { - (ephemeral_public_key_sender - * self - .top_secret_key_holder - .generate_incloming_viewing_secret_key()) - .into() - } - - pub fn decrypt_data( - &self, - ephemeral_public_key_sender: AffinePoint, - ciphertext: CipherText, - nonce: Nonce, - ) -> Result, aes_gcm::Error> { - let shared_secret = self.calculate_shared_secret_receiver(ephemeral_public_key_sender); - let cipher = Aes256Gcm::new(&shared_secret.x()); - - cipher.decrypt(&nonce, ciphertext.as_slice()) + ephemeral_public_key_sender: EphemeralPublicKey, + ) -> SharedSecretKey { + SharedSecretKey::new( + &self + .secret_spending_key + .generate_incoming_viewing_secret_key(), + &ephemeral_public_key_sender, + ) } pub fn log(&self) { info!( "Secret spending key is {:?}", - hex::encode( - serde_json::to_vec(&self.top_secret_key_holder.secret_spending_key).unwrap() - ), + hex::encode(serde_json::to_vec(&self.secret_spending_key).unwrap()), ); info!( "Nulifier secret key is {:?}", @@ -113,17 +97,9 @@ impl KeyChain { #[cfg(test)] mod tests { - use aes_gcm::{ - Aes256Gcm, - aead::{Aead, KeyInit, OsRng}, - }; + use aes_gcm::aead::OsRng; use elliptic_curve::ff::Field; - use elliptic_curve::group::prime::PrimeCurveAffine; - use elliptic_curve::point::AffineCoordinates; - use k256::{AffinePoint, ProjectivePoint, Scalar}; - use types::{CipherText, Nonce}; - - use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder; + use k256::{AffinePoint, Scalar}; use super::*; @@ -133,10 +109,7 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Check that key holder fields are initialized with expected types - assert_ne!(address_key_holder.nullifer_public_key, [0u8; 32]); - assert!(!Into::::into( - address_key_holder.incoming_viewing_public_key.is_identity() - )); + assert_ne!(address_key_holder.nullifer_public_key.as_ref(), &[0u8; 32]); } #[test] @@ -145,171 +118,11 @@ mod tests { // Generate a random ephemeral public key sender let scalar = Scalar::random(&mut OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); + let ephemeral_public_key_sender = EphemeralPublicKey::from_scalar(scalar); // Calculate shared secret - let shared_secret = + let _shared_secret = address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Ensure the shared secret is not an identity point (suggesting non-zero output) - assert!(!Into::::into(shared_secret.is_identity())); - } - - #[test] - fn test_decrypt_data() { - let address_key_holder = KeyChain::new_os_random(); - - let test_receiver_nullifier_public_key = [42; 32]; - let sender_outgoing_viewing_key = address_key_holder - .top_secret_key_holder - .generate_outgoing_viewing_secret_key(); - let nonce = 0; - - // Generate an ephemeral key and shared secret - let ephemeral_public_key_sender = EphemeralKeyHolder::new( - test_receiver_nullifier_public_key, - sender_outgoing_viewing_key, - nonce, - ) - .generate_ephemeral_public_key(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Attempt decryption - let decrypted_data: Vec = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext), - *nonce, - ) - .unwrap(); - - // Verify decryption is successful and matches original plaintext - assert_eq!(decrypted_data, plaintext); - } - - #[test] - fn test_calculate_shared_secret_with_identity_point() { - let address_key_holder = KeyChain::new_os_random(); - - // Use identity point as ephemeral public key - let identity_point = AffinePoint::identity(); - - // Calculate shared secret - let shared_secret = address_key_holder.calculate_shared_secret_receiver(identity_point); - - // The shared secret with the identity point should also result in the identity point - assert!(Into::::into(shared_secret.is_identity())); - } - - #[test] - #[should_panic] - fn test_decrypt_data_with_incorrect_nonce() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral public key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data with a specific nonce - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Attempt decryption with an incorrect nonce - let incorrect_nonce = Nonce::from_slice(b"wrong nonce"); - let decrypted_data = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext.clone()), - *incorrect_nonce, - ) - .unwrap(); - - // The decryption should fail or produce incorrect output due to nonce mismatch - assert_ne!(decrypted_data, plaintext); - } - - #[test] - #[should_panic] - fn test_decrypt_data_with_incorrect_ciphertext() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral public key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Tamper with the ciphertext to simulate corruption - let mut corrupted_ciphertext = ciphertext.clone(); - corrupted_ciphertext[0] ^= 1; // Flip a bit in the ciphertext - - // Attempt decryption - let result = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(corrupted_ciphertext), - *nonce, - ) - .unwrap(); - - // The decryption should fail or produce incorrect output due to tampered ciphertext - assert_ne!(result, plaintext); - } - - #[test] - fn test_encryption_decryption_round_trip() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - - // Encrypt sample data - let plaintext = b"Round-trip test data"; - let nonce = Nonce::from_slice(b"unique nonce"); - - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - let cipher = Aes256Gcm::new(&shared_secret.x()); - - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Decrypt the data using the `KeyChain` instance - let decrypted_data = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext), - *nonce, - ) - .unwrap(); - - // Verify the decrypted data matches the original plaintext - assert_eq!(decrypted_data, plaintext); } #[test] diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 5c2e522..eb59b40 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,7 +1,8 @@ use bip39::Mnemonic; use common::TreeHashType; use elliptic_curve::PrimeField; -use k256::{AffinePoint, Scalar}; +use k256::Scalar; +use nssa_core::{NullifierPublicKey, NullifierSecretKey, encryption::IncomingViewingPublicKey}; use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; @@ -15,17 +16,18 @@ pub struct SeedHolder { } #[derive(Serialize, Deserialize, Debug, Clone)] -///Secret spending key holder. Produces `PrivateKeyHolder` objects. -pub struct TopSecretKeyHolder { - pub(crate) secret_spending_key: [u8; 32], -} +///Secret spending key object. Can produce `PrivateKeyHolder` objects. +pub struct SecretSpendingKey(pub(crate) [u8; 32]); + +pub type IncomingViewingSecretKey = Scalar; +pub type OutgoingViewingSecretKey = Scalar; #[derive(Serialize, Deserialize, Debug, Clone)] ///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. pub struct PrivateKeyHolder { - pub(crate) nullifier_secret_key: [u8; 32], - pub(crate) incoming_viewing_secret_key: Scalar, - pub(crate) outgoing_viewing_secret_key: Scalar, + pub(crate) nullifier_secret_key: NullifierSecretKey, + pub(crate) incoming_viewing_secret_key: IncomingViewingSecretKey, + pub(crate) outgoing_viewing_secret_key: OutgoingViewingSecretKey, } impl SeedHolder { @@ -52,74 +54,65 @@ impl SeedHolder { *hash.first_chunk::<32>().unwrap() } - pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder { - TopSecretKeyHolder { - secret_spending_key: self.generate_secret_spending_key_hash(), - } + pub fn produce_top_secret_key_holder(&self) -> SecretSpendingKey { + SecretSpendingKey(self.generate_secret_spending_key_hash()) } } -impl TopSecretKeyHolder { - pub fn generate_nullifier_secret_key(&self) -> [u8; 32] { +impl SecretSpendingKey { + pub fn generate_nullifier_secret_key(&self) -> NullifierSecretKey { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(self.secret_spending_key); + hasher.update(self.0); hasher.update([1u8]); - hasher.update([0u8; 176]); + hasher.update([0u8; 22]); - ::from(hasher.finalize_fixed()) + ::from(hasher.finalize_fixed()) } - pub fn generate_incloming_viewing_secret_key(&self) -> Scalar { + pub fn generate_incoming_viewing_secret_key(&self) -> IncomingViewingSecretKey { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(self.secret_spending_key); + hasher.update(self.0); hasher.update([2u8]); - hasher.update([0u8; 176]); + hasher.update([0u8; 22]); let hash = ::from(hasher.finalize_fixed()); - Scalar::from_repr(hash.into()).unwrap() + IncomingViewingSecretKey::from_repr(hash.into()).unwrap() } - pub fn generate_outgoing_viewing_secret_key(&self) -> Scalar { + pub fn generate_outgoing_viewing_secret_key(&self) -> OutgoingViewingSecretKey { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(self.secret_spending_key); + hasher.update(self.0); hasher.update([3u8]); - hasher.update([0u8; 176]); + hasher.update([0u8; 22]); let hash = ::from(hasher.finalize_fixed()); - Scalar::from_repr(hash.into()).unwrap() + OutgoingViewingSecretKey::from_repr(hash.into()).unwrap() } pub fn produce_private_key_holder(&self) -> PrivateKeyHolder { PrivateKeyHolder { nullifier_secret_key: self.generate_nullifier_secret_key(), - incoming_viewing_secret_key: self.generate_incloming_viewing_secret_key(), + incoming_viewing_secret_key: self.generate_incoming_viewing_secret_key(), outgoing_viewing_secret_key: self.generate_outgoing_viewing_secret_key(), } } } impl PrivateKeyHolder { - pub fn generate_nullifier_public_key(&self) -> [u8; 32] { - let mut hasher = sha2::Sha256::new(); - - hasher.update("NSSA_keys"); - hasher.update(self.nullifier_secret_key); - hasher.update([7u8]); - hasher.update([0u8; 176]); - - ::from(hasher.finalize_fixed()) + pub fn generate_nullifier_public_key(&self) -> NullifierPublicKey { + (&self.nullifier_secret_key).into() } - pub fn generate_incoming_viewing_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.incoming_viewing_secret_key).into() + pub fn generate_incoming_viewing_public_key(&self) -> IncomingViewingPublicKey { + IncomingViewingPublicKey::from_scalar(self.incoming_viewing_secret_key) } } @@ -151,7 +144,7 @@ mod tests { let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let _ = top_secret_key_holder.generate_incloming_viewing_secret_key(); + let _ = top_secret_key_holder.generate_incoming_viewing_secret_key(); } #[test] diff --git a/key_protocol/src/key_management/types.rs b/key_protocol/src/key_management/types.rs deleted file mode 100644 index 8878f25..0000000 --- a/key_protocol/src/key_management/types.rs +++ /dev/null @@ -1,8 +0,0 @@ -use elliptic_curve::{ - consts::{B0, B1}, - generic_array::GenericArray, -}; -use sha2::digest::typenum::{UInt, UTerm}; - -pub type CipherText = Vec; -pub type Nonce = GenericArray, B1>, B0>, B0>>; diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 3163902..96f6530 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -14,6 +14,7 @@ secp256k1 = "0.31.1" rand = "0.8" borsh = "1.5.7" hex = "0.4.3" +k256 = "0.13.3" [dev-dependencies] test-program-methods = { path = "test_program_methods" } diff --git a/nssa/core/src/encryption/shared_key_derivation.rs b/nssa/core/src/encryption/shared_key_derivation.rs index c735105..6392d6a 100644 --- a/nssa/core/src/encryption/shared_key_derivation.rs +++ b/nssa/core/src/encryption/shared_key_derivation.rs @@ -14,7 +14,7 @@ use crate::SharedSecretKey; pub struct Secp256k1Point(pub(crate) Vec); impl Secp256k1Point { - pub fn from_scalar(value: [u8; 32]) -> Secp256k1Point { + pub fn from_scalar(value: Scalar) -> Secp256k1Point { let x_bytes: FieldBytes = value.into(); let x = Scalar::from_repr(x_bytes).unwrap(); @@ -26,7 +26,7 @@ impl Secp256k1Point { } } -pub type EphemeralSecretKey = [u8; 32]; +pub type EphemeralSecretKey = Scalar; pub type EphemeralPublicKey = Secp256k1Point; pub type IncomingViewingPublicKey = Secp256k1Point; impl From<&EphemeralSecretKey> for EphemeralPublicKey { @@ -36,8 +36,7 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey { } impl SharedSecretKey { - pub fn new(scalar: &[u8; 32], point: &Secp256k1Point) -> Self { - let scalar = Scalar::from_repr((*scalar).into()).unwrap(); + pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self { let point: [u8; 33] = point.0.clone().try_into().unwrap(); let encoded = EncodedPoint::from_bytes(point).unwrap(); diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index d1410de..bc73d8c 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -7,6 +7,12 @@ use crate::Commitment; #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] pub struct NullifierPublicKey(pub(super) [u8; 32]); +impl AsRef<[u8]> for NullifierPublicKey { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + impl From<&NullifierSecretKey> for NullifierPublicKey { fn from(value: &NullifierSecretKey) -> Self { let mut bytes = Vec::new(); diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 1421f62..f350ccf 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -88,6 +88,7 @@ impl Proof { #[cfg(test)] mod tests { + use k256::{Scalar, elliptic_curve::PrimeField}; use nssa_core::{ Commitment, EncryptionScheme, Nullifier, account::{Account, AccountWithMetadata}, @@ -139,7 +140,7 @@ mod tests { let expected_sender_pre = sender.clone(); let recipient_keys = test_private_account_keys_1(); - let esk = [3; 32]; + let esk = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( @@ -220,10 +221,10 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let esk_1 = [3; 32]; + let esk_1 = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); - let esk_2 = [5; 32]; + let esk_2 = Scalar::from_repr([5; 32].into()).unwrap(); let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 244a81f..9023e52 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -90,6 +90,7 @@ impl Message { #[cfg(test)] pub mod tests { + use k256::{Scalar, elliptic_curve::PrimeField}; use std::io::Cursor; use nssa_core::{ @@ -151,10 +152,10 @@ pub mod tests { #[test] fn test_encrypted_account_data_constructor() { let npk = NullifierPublicKey::from(&[1; 32]); - let ivk = IncomingViewingPublicKey::from(&[2; 32]); + let ivk = IncomingViewingPublicKey::from(&Scalar::from_repr([2; 32].into()).unwrap()); let account = Account::default(); let commitment = Commitment::new(&npk, &account); - let esk = [3; 32]; + let esk = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret = SharedSecretKey::new(&esk, &ivk); let epk = EphemeralPublicKey::from_scalar(esk); let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index f980370..71e5f5e 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -227,6 +227,7 @@ pub mod tests { signature::PrivateKey, }; + use k256::{Scalar, elliptic_curve::PrimeField}; use nssa_core::{ Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{Account, AccountWithMetadata, Nonce}, @@ -743,7 +744,7 @@ pub mod tests { pub struct TestPrivateKeys { pub nsk: NullifierSecretKey, - pub isk: [u8; 32], + pub isk: Scalar, } impl TestPrivateKeys { @@ -759,14 +760,14 @@ pub mod tests { pub fn test_private_account_keys_1() -> TestPrivateKeys { TestPrivateKeys { nsk: [13; 32], - isk: [31; 32], + isk: Scalar::from_repr([31; 32].into()).unwrap(), } } pub fn test_private_account_keys_2() -> TestPrivateKeys { TestPrivateKeys { nsk: [38; 32], - isk: [83; 32], + isk: Scalar::from_repr([83; 32].into()).unwrap(), } } @@ -788,7 +789,7 @@ pub mod tests { is_authorized: false, }; - let esk = [3; 32]; + let esk = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); @@ -834,11 +835,11 @@ pub mod tests { is_authorized: false, }; - let esk_1 = [3; 32]; + let esk_1 = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); let epk_1 = EphemeralPublicKey::from_scalar(esk_1); - let esk_2 = [3; 32]; + let esk_2 = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let epk_2 = EphemeralPublicKey::from_scalar(esk_2); @@ -894,7 +895,7 @@ pub mod tests { is_authorized: false, }; - let esk = [3; 32]; + let esk = Scalar::from_repr([3; 32].into()).unwrap(); let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); From 75b7fdd069f59b98a304d71fb6f547a2cf3d6722 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 16 Sep 2025 07:51:40 -0300 Subject: [PATCH 35/37] add domain separation for private and public account ids --- nssa/core/src/account.rs | 1 + nssa/core/src/nullifier.rs | 11 ++++++++--- nssa/src/address.rs | 13 +++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index fdd51e1..e7f8558 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -78,6 +78,7 @@ mod tests { assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID); } + #[cfg(feature = "host")] #[test] fn test_account_with_metadata_constructor() { let account = Account { diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index c7ee26e..cafa47c 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -9,7 +9,12 @@ pub struct NullifierPublicKey(pub(super) [u8; 32]); impl From<&NullifierPublicKey> for AccountId { fn from(value: &NullifierPublicKey) -> Self { - AccountId::new(value.0) + const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.1/AccountId/Private/\x00\x00\x00"; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX); + bytes[32..].copy_from_slice(&value.0); + AccountId::new(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap()) } } @@ -80,8 +85,8 @@ mod tests { ]; let npk = NullifierPublicKey::from(&nsk); let expected_account_id = AccountId::new([ - 202, 120, 42, 189, 194, 218, 78, 244, 31, 6, 108, 169, 29, 61, 22, 221, 69, 138, 197, - 161, 241, 39, 142, 242, 242, 50, 188, 201, 99, 28, 176, 238, + 69, 160, 50, 67, 12, 56, 150, 116, 62, 145, 17, 161, 17, 45, 24, 53, 33, 167, 83, 178, + 47, 114, 111, 233, 251, 30, 54, 244, 184, 22, 100, 236, ]); let account_id = AccountId::from(&npk); diff --git a/nssa/src/address.rs b/nssa/src/address.rs index 5837e20..72144b2 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -2,6 +2,7 @@ use std::{fmt::Display, str::FromStr}; use nssa_core::account::AccountId; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use crate::signature::PublicKey; @@ -84,7 +85,12 @@ impl<'de> Deserialize<'de> for Address { impl From<&Address> for AccountId { fn from(address: &Address) -> Self { - AccountId::new(address.value) + const PUBLIC_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.1/AccountId/Public/\x00\x00\x00\x00"; + + let mut hasher = Sha256::new(); + hasher.update(PUBLIC_ACCOUNT_ID_PREFIX); + hasher.update(address.value); + AccountId::new(hasher.finalize().into()) } } @@ -125,7 +131,10 @@ mod tests { #[test] fn test_account_id_from_address() { let address: Address = "37".repeat(32).parse().unwrap(); - let expected_account_id = AccountId::new([55; 32]); + let expected_account_id = AccountId::new([ + 93, 223, 66, 245, 78, 230, 157, 188, 110, 161, 134, 255, 137, 177, 220, 88, 37, 44, + 243, 91, 236, 4, 36, 147, 185, 112, 21, 49, 234, 4, 107, 185, + ]); let account_id = AccountId::from(&address); From 85a16a2f04c35929ced1052da89d9c781cc209c1 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 17 Sep 2025 08:59:14 +0300 Subject: [PATCH 36/37] fix: revers of scalar dep --- .../src/key_management/ephemeral_key_holder.rs | 6 ++---- key_protocol/src/key_management/mod.rs | 7 ++++--- key_protocol/src/key_management/secret_holders.rs | 15 ++++++--------- nssa/core/src/encryption/mod.rs | 2 ++ nssa/core/src/encryption/shared_key_derivation.rs | 7 ++++--- .../src/privacy_preserving_transaction/circuit.rs | 7 +++---- .../src/privacy_preserving_transaction/message.rs | 5 ++--- nssa/src/state.rs | 15 +++++++-------- 8 files changed, 30 insertions(+), 34 deletions(-) diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index e62d9b6..b4835ff 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,5 +1,3 @@ -use elliptic_curve::PrimeField; -use k256::Scalar; use log::info; use nssa_core::{ NullifierPublicKey, SharedSecretKey, @@ -29,11 +27,11 @@ impl EphemeralKeyHolder { let hash_recepient = hasher.finalize(); let mut hasher = sha2::Sha256::new(); - hasher.update(sender_outgoing_viewing_secret_key.to_bytes()); + hasher.update(sender_outgoing_viewing_secret_key); hasher.update(hash_recepient); Self { - ephemeral_secret_key: Scalar::from_repr(hasher.finalize()).unwrap(), + ephemeral_secret_key: hasher.finalize().into(), } } diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index bcd3796..5f5f2aa 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -98,8 +98,8 @@ impl KeyChain { #[cfg(test)] mod tests { use aes_gcm::aead::OsRng; - use elliptic_curve::ff::Field; - use k256::{AffinePoint, Scalar}; + use k256::AffinePoint; + use rand::RngCore; use super::*; @@ -117,7 +117,8 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Generate a random ephemeral public key sender - let scalar = Scalar::random(&mut OsRng); + let mut scalar = [0; 32]; + OsRng.fill_bytes(&mut scalar); let ephemeral_public_key_sender = EphemeralPublicKey::from_scalar(scalar); // Calculate shared secret diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index eb59b40..80ec1b0 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,8 +1,9 @@ use bip39::Mnemonic; use common::TreeHashType; -use elliptic_curve::PrimeField; -use k256::Scalar; -use nssa_core::{NullifierPublicKey, NullifierSecretKey, encryption::IncomingViewingPublicKey}; +use nssa_core::{ + NullifierPublicKey, NullifierSecretKey, + encryption::{IncomingViewingPublicKey, Scalar}, +}; use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; @@ -79,9 +80,7 @@ impl SecretSpendingKey { hasher.update([2u8]); hasher.update([0u8; 22]); - let hash = ::from(hasher.finalize_fixed()); - - IncomingViewingSecretKey::from_repr(hash.into()).unwrap() + ::from(hasher.finalize_fixed()) } pub fn generate_outgoing_viewing_secret_key(&self) -> OutgoingViewingSecretKey { @@ -92,9 +91,7 @@ impl SecretSpendingKey { hasher.update([3u8]); hasher.update([0u8; 22]); - let hash = ::from(hasher.finalize_fixed()); - - OutgoingViewingSecretKey::from_repr(hash.into()).unwrap() + ::from(hasher.finalize_fixed()) } pub fn produce_private_key_holder(&self) -> PrivateKeyHolder { diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs index b79e75c..7b8d51f 100644 --- a/nssa/core/src/encryption/mod.rs +++ b/nssa/core/src/encryption/mod.rs @@ -13,6 +13,8 @@ pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, Incoming use crate::{Commitment, account::Account}; +pub type Scalar = [u8; 32]; + #[derive(Serialize, Deserialize, Clone)] pub struct SharedSecretKey([u8; 32]); diff --git a/nssa/core/src/encryption/shared_key_derivation.rs b/nssa/core/src/encryption/shared_key_derivation.rs index 6392d6a..a889123 100644 --- a/nssa/core/src/encryption/shared_key_derivation.rs +++ b/nssa/core/src/encryption/shared_key_derivation.rs @@ -1,14 +1,14 @@ use serde::{Deserialize, Serialize}; use k256::{ - AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, Scalar, + AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, elliptic_curve::{ PrimeField, sec1::{FromEncodedPoint, ToEncodedPoint}, }, }; -use crate::SharedSecretKey; +use crate::{SharedSecretKey, encryption::Scalar}; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct Secp256k1Point(pub(crate) Vec); @@ -16,7 +16,7 @@ pub struct Secp256k1Point(pub(crate) Vec); impl Secp256k1Point { pub fn from_scalar(value: Scalar) -> Secp256k1Point { let x_bytes: FieldBytes = value.into(); - let x = Scalar::from_repr(x_bytes).unwrap(); + let x = k256::Scalar::from_repr(x_bytes).unwrap(); let p = ProjectivePoint::GENERATOR * x; let q = AffinePoint::from(p); @@ -37,6 +37,7 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey { impl SharedSecretKey { pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self { + let scalar = k256::Scalar::from_repr((*scalar).into()).unwrap(); let point: [u8; 33] = point.0.clone().try_into().unwrap(); let encoded = EncodedPoint::from_bytes(point).unwrap(); diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index f350ccf..fef44bc 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -88,7 +88,6 @@ impl Proof { #[cfg(test)] mod tests { - use k256::{Scalar, elliptic_curve::PrimeField}; use nssa_core::{ Commitment, EncryptionScheme, Nullifier, account::{Account, AccountWithMetadata}, @@ -140,7 +139,7 @@ mod tests { let expected_sender_pre = sender.clone(); let recipient_keys = test_private_account_keys_1(); - let esk = Scalar::from_repr([3; 32].into()).unwrap(); + let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( @@ -221,10 +220,10 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let esk_1 = Scalar::from_repr([3; 32].into()).unwrap(); + let esk_1 = [3; 32].into(); let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); - let esk_2 = Scalar::from_repr([5; 32].into()).unwrap(); + let esk_2 = [5; 32]; let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 9023e52..a769c72 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -90,7 +90,6 @@ impl Message { #[cfg(test)] pub mod tests { - use k256::{Scalar, elliptic_curve::PrimeField}; use std::io::Cursor; use nssa_core::{ @@ -152,10 +151,10 @@ pub mod tests { #[test] fn test_encrypted_account_data_constructor() { let npk = NullifierPublicKey::from(&[1; 32]); - let ivk = IncomingViewingPublicKey::from(&Scalar::from_repr([2; 32].into()).unwrap()); + let ivk = IncomingViewingPublicKey::from_scalar([2; 32]); let account = Account::default(); let commitment = Commitment::new(&npk, &account); - let esk = Scalar::from_repr([3; 32].into()).unwrap(); + let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &ivk); let epk = EphemeralPublicKey::from_scalar(esk); let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 71e5f5e..7922bad 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -227,11 +227,10 @@ pub mod tests { signature::PrivateKey, }; - use k256::{Scalar, elliptic_curve::PrimeField}; use nssa_core::{ Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{Account, AccountWithMetadata, Nonce}, - encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, + encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar}, }; fn transfer_transaction( @@ -760,14 +759,14 @@ pub mod tests { pub fn test_private_account_keys_1() -> TestPrivateKeys { TestPrivateKeys { nsk: [13; 32], - isk: Scalar::from_repr([31; 32].into()).unwrap(), + isk: [31; 32], } } pub fn test_private_account_keys_2() -> TestPrivateKeys { TestPrivateKeys { nsk: [38; 32], - isk: Scalar::from_repr([83; 32].into()).unwrap(), + isk: [83; 32], } } @@ -789,7 +788,7 @@ pub mod tests { is_authorized: false, }; - let esk = Scalar::from_repr([3; 32].into()).unwrap(); + let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); @@ -835,11 +834,11 @@ pub mod tests { is_authorized: false, }; - let esk_1 = Scalar::from_repr([3; 32].into()).unwrap(); + let esk_1 = [3; 32]; let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); let epk_1 = EphemeralPublicKey::from_scalar(esk_1); - let esk_2 = Scalar::from_repr([3; 32].into()).unwrap(); + let esk_2 = [3; 32]; let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let epk_2 = EphemeralPublicKey::from_scalar(esk_2); @@ -895,7 +894,7 @@ pub mod tests { is_authorized: false, }; - let esk = Scalar::from_repr([3; 32].into()).unwrap(); + let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &sender_keys.ivk()); let epk = EphemeralPublicKey::from_scalar(esk); From f75fab89b0c00637ea965f269a0769c32547fced Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 17 Sep 2025 09:38:46 +0300 Subject: [PATCH 37/37] fix: lint fix --- nssa/src/privacy_preserving_transaction/circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index fef44bc..1421f62 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -220,7 +220,7 @@ mod tests { Commitment::new(&recipient_keys.npk(), &expected_private_account_2), ]; - let esk_1 = [3; 32].into(); + let esk_1 = [3; 32]; let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.ivk()); let esk_2 = [5; 32];