diff --git a/Cargo.toml b/Cargo.toml index 4b501ea..82f6736 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] } base64 = "0.22.1" bip39 = "2.2.0" hmac-sha512 = "1.1.7" +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 2e4e9b9..64bce30 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,41 +1,56 @@ +use rs_merkle::Hasher; use std::io::{Cursor, Read}; -use rs_merkle::Hasher; - -use crate::merkle_tree_public::hasher::OwnHasher; -use nssa; +use crate::{OwnHasher, transaction::EncodedTransaction}; pub type BlockHash = [u8; 32]; pub type BlockId = u64; +pub type TimeStamp = u64; + +#[derive(Debug, Clone)] +pub struct BlockHeader { + pub block_id: BlockId, + pub prev_block_hash: BlockHash, + pub hash: BlockHash, + pub timestamp: TimeStamp, + pub signature: nssa::Signature, +} + +#[derive(Debug, Clone)] +pub struct BlockBody { + pub transactions: Vec, +} #[derive(Debug, Clone)] pub struct Block { - pub block_id: BlockId, - pub prev_block_id: BlockId, - pub prev_block_hash: BlockHash, - pub hash: BlockHash, - pub transactions: Vec, + pub header: BlockHeader, + pub body: BlockBody, } #[derive(Debug, PartialEq, Eq)] pub struct HashableBlockData { pub block_id: BlockId, - pub prev_block_id: BlockId, pub prev_block_hash: BlockHash, - pub transactions: Vec, + pub timestamp: TimeStamp, + pub transactions: Vec, } -impl From for Block { - fn from(value: HashableBlockData) -> Self { - let data = value.to_bytes(); - 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, +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: self.block_id, + prev_block_hash: self.prev_block_hash, + hash, + timestamp: self.timestamp, + signature, + }, + body: BlockBody { + transactions: self.transactions, + }, } } } @@ -43,10 +58,10 @@ 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, - transactions: value.transactions, + block_id: value.header.block_id, + prev_block_hash: value.header.prev_block_hash, + timestamp: value.header.timestamp, + transactions: value.body.transactions, } } } @@ -55,11 +70,15 @@ 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; 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 @@ -70,37 +89,47 @@ 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(); + let timestamp = u64_from_cursor(&mut cursor); + 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_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 = EncodedTransaction::from_bytes(tx_bytes); transactions.push(tx); } Self { block_id, - prev_block_id, prev_block_hash, + timestamp, transactions, } } } // 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/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..67d628d 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, Sha256, digest::FixedOutput}; 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 ed9443a..0000000 --- a/common/src/merkle_tree_public/hasher.rs +++ /dev/null @@ -1,20 +0,0 @@ -use rs_merkle::Hasher; -use sha2::{Digest, Sha256, digest::FixedOutput}; - -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 8911344..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::{ - Deserialize, Deserializer, Serialize, - de::{SeqAccess, Visitor}, - ser::SerializeSeq, -}; - -use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment}; - -use super::{TreeHashType, hasher::OwnHasher, tree_leav_item::TreeLeavItem}; - -#[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..126de40 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::{EncodedTransaction, NSSATransaction}; 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 = 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 b956cd7..9d68db8 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -1,6 +1,13 @@ -use nssa; +use crate::{ + block::{Block, HashableBlockData}, + transaction::{EncodedTransaction, NSSATransaction}, +}; -use crate::block::{Block, HashableBlockData}; +//Helpers + +pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey { + nssa::PrivateKey::try_new([37; 32]).unwrap() +} //Dummy producers @@ -10,23 +17,23 @@ use crate::block::{Block, HashableBlockData}; /// /// `prev_hash` - hash of previous block, provide None for genesis /// -/// `transactions` - vector of `Transaction` 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, }; - 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() -> EncodedTransaction { let program_id = nssa::program::Program::authenticated_transfer_program().id(); let addresses = vec![]; let nonces = vec![]; @@ -36,7 +43,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); + + EncodedTransaction::from(NSSATransaction::Public(nssa_tx)) } pub fn create_transaction_native_token_transfer( @@ -45,7 +55,7 @@ pub fn create_transaction_native_token_transfer( to: [u8; 32], balance_to_move: u128, signing_key: nssa::PrivateKey, -) -> nssa::PublicTransaction { +) -> 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(); @@ -53,5 +63,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); + + EncodedTransaction::from(NSSATransaction::Public(nssa_tx)) } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 1d0ccec..d196687 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -1,22 +1,34 @@ -use k256::ecdsa::{ - Signature, SigningKey, VerifyingKey, - signature::{Signer, Verifier}, -}; +use k256::ecdsa::{Signature, SigningKey, VerifyingKey}; use log::info; -use secp256k1_zkp::{PedersenCommitment, Tweak}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; -use crate::merkle_tree_public::TreeHashType; - use elliptic_curve::{ consts::{B0, B1}, generic_array::GenericArray, }; use sha2::digest::typenum::{UInt, UTerm}; -use crate::TransactionSignatureError; +#[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; pub type Nonce = GenericArray, B1>, B0>, B0>>; @@ -25,39 +37,45 @@ 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 struct EncodedTransaction { 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 EncodedTransaction { + fn from(value: NSSATransaction) -> Self { + match value { + NSSATransaction::Public(tx) => Self { + tx_kind: TxKind::Public, + encoded_transaction_data: tx.to_bytes(), + }, + NSSATransaction::PrivacyPreserving(tx) => Self { + tx_kind: TxKind::PrivacyPreserving, + encoded_transaction_data: tx.to_bytes(), + }, + } + } +} + +impl TryFrom<&EncodedTransaction> for NSSATransaction { + type Error = nssa::error::NssaError; + + fn try_from(value: &EncodedTransaction) -> 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)] @@ -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(); @@ -162,180 +180,48 @@ 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() } + pub 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 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 - .iter() - .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()) - ); } } -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 - } -} - -/// 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)] -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 - } -} - #[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::{ - merkle_tree_public::TreeHashType, - transaction::{Transaction, TransactionBody, TxKind}, + TreeHashType, + transaction::{EncodedTransaction, TxKind}, }; - fn test_transaction_body() -> TransactionBody { - TransactionBody { + fn test_transaction_body() -> EncodedTransaction { + EncodedTransaction { 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], } } - 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()) @@ -347,83 +233,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 = EncodedTransaction::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/mod.rs b/key_protocol/src/key_management/mod.rs index ea337dd..5de3826 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,5 +1,5 @@ use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead}; -use common::merkle_tree_public::TreeHashType; +use common::TreeHashType; use elliptic_curve::group::GroupEncoding; use elliptic_curve::point::AffineCoordinates; use k256::AffinePoint; diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index b6784cd..5c2e522 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,5 +1,5 @@ use bip39::Mnemonic; -use common::merkle_tree_public::TreeHashType; +use common::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, Scalar}; use rand::{RngCore, rngs::OsRng}; 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 67% rename from nssa/src/privacy_preserving_transaction/encoding.rs rename to nssa/src/encoding/privacy_preserving_transaction.rs index b5d4950..2e5ea14 100644 --- a/nssa/src/privacy_preserving_transaction/encoding.rs +++ b/nssa/src/encoding/privacy_preserving_transaction.rs @@ -7,16 +7,20 @@ use nssa_core::{ }; use crate::{ - Address, error::NssaError, privacy_preserving_transaction::message::EncryptedAccountData, + Address, PrivacyPreservingTransaction, PublicKey, Signature, + error::NssaError, + privacy_preserving_transaction::{ + 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/"; 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); @@ -168,3 +172,86 @@ 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)) + } +} + +impl Proof { + 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/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 ed88047..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; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index ed32f98..1421f62 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -12,15 +12,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); - -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 struct Proof(pub(crate) Vec); /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution /// circuit @@ -86,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::{ 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/Cargo.toml b/sequencer_core/Cargo.toml index 1c56f11..40e420a 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -9,6 +9,7 @@ anyhow.workspace = true serde.workspace = true rand.workspace = true tempfile.workspace = true +chrono.workspace = true log.workspace = true [dependencies.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..59b787e 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -1,7 +1,11 @@ use std::fmt::Display; use anyhow::Result; -use common::{block::HashableBlockData, merkle_tree_public::TreeHashType}; +use common::{ + TreeHashType, + block::HashableBlockData, + transaction::{EncodedTransaction, NSSATransaction}, +}; use config::SequencerConfig; use log::warn; use mempool::MemPool; @@ -13,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, } @@ -51,6 +55,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 +65,37 @@ 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: EncodedTransaction, ) -> Result<(), TransactionMalformationErrorKind> { + let transaction = NSSATransaction::try_from(&transaction).map_err(|_| { + TransactionMalformationErrorKind::FailedToDecode { + tx: transaction.hash(), + } + })?; + let mempool_size = self.mempool.len(); if mempool_size >= self.sequencer_config.max_num_tx_in_block { return Err(TransactionMalformationErrorKind::MempoolFullForRound); @@ -83,19 +105,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) } @@ -104,29 +136,41 @@ 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 valid_transactions: Vec<_> = transactions - .into_iter() - .filter_map(|tx| self.execute_check_transaction_on_state(tx).ok()) - .collect(); + while let Some(tx) = self.mempool.pop_last() { + let nssa_transaction = NSSATransaction::try_from(&tx) + .map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx.hash() })?; + + if let Ok(valid_tx) = self.execute_check_transaction_on_state(nssa_transaction) { + valid_transactions.push(valid_tx.into()); + + 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 .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 +182,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: EncodedTransaction) -> 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 +209,7 @@ mod tests { block_create_timeout_millis: 1000, port: 8080, initial_accounts, + signing_key: *sequencer_sign_key_for_testing().value(), } } @@ -298,7 +351,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 +377,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 +405,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 +440,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 +476,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 +585,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 +620,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 +632,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 3bd68cb..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::{block::Block, merkle_tree_public::TreeHashType}; +use common::{TreeHashType, block::Block, transaction::EncodedTransaction}; 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,22 +84,28 @@ fn block_to_transactions_map(block: &Block) -> HashMap { mod tests { use super::*; + use common::{block::HashableBlockData, test_utils::sequencer_sign_key_for_testing}; use tempfile::tempdir; #[test] fn test_get_transaction_by_hash() { let temp_dir = tempdir().unwrap(); let path = temp_dir.path(); - let genesis_block = Block { + + let signing_key = sequencer_sign_key_for_testing(); + + let genesis_block_hashable_data = HashableBlockData { block_id: 0, - prev_block_id: 0, prev_block_hash: [0; 32], - hash: [1; 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 bb0a7c1..366c9e2 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,23 @@ 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 2f55dd2..9123bcb 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -5,8 +5,8 @@ use sequencer_core::config::AccountInitialData; use serde_json::Value; use common::{ + TreeHashType, block::HashableBlockData, - merkle_tree_public::TreeHashType, rpc_primitives::{ errors::RpcError, message::{Message, Request}, @@ -18,6 +18,7 @@ use common::{ GetTransactionByHashResponse, }, }, + transaction::EncodedTransaction, }; use common::rpc_primitives::requests::{ @@ -74,8 +75,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 = EncodedTransaction::from_bytes(send_tx_req.transaction); let tx_hash = hex::encode(tx.hash()); { @@ -278,7 +278,10 @@ mod tests { use crate::{JsonHandler, rpc_handler}; use base64::{Engine, engine::general_purpose}; - use common::rpc_primitives::RpcPollingConfig; + use common::{ + rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing, + transaction::EncodedTransaction, + }; use sequencer_core::{ SequencerCore, @@ -322,14 +325,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, 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/storage/src/lib.rs b/storage/src/lib.rs index 9ead317..d06fc0c 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,22 +233,22 @@ 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))?; 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 9a6bd59..d3b07b3 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, HashMap::new())?, - utxo_commitments_store, wallet_config: config, }) } @@ -93,11 +88,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 5a24933..4a0d337 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -4,6 +4,7 @@ use base64::Engine; use common::{ ExecutionFailureKind, sequencer_client::{SequencerClient, json::SendTxResponse}, + transaction::{EncodedTransaction, NSSATransaction}, }; use anyhow::Result; @@ -122,7 +123,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) } @@ -147,16 +148,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 = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?; + let pub_tx = EncodedTransaction::from_bytes(tx_base64_decode); - Ok(pub_tx) + Ok(NSSATransaction::try_from(&pub_tx)?) } }