diff --git a/common/src/merkle_tree_public/tree_leav_item.rs b/common/src/merkle_tree_public/tree_leav_item.rs index deecaf7..1fbbb1a 100644 --- a/common/src/merkle_tree_public/tree_leav_item.rs +++ b/common/src/merkle_tree_public/tree_leav_item.rs @@ -8,7 +8,7 @@ pub trait TreeLeavItem { impl TreeLeavItem for Transaction { fn hash(&self) -> TreeHashType { - self.hash + self.hash() } } diff --git a/common/src/transaction.rs b/common/src/transaction.rs index 1a95fe3..3f2dcb2 100644 --- a/common/src/transaction.rs +++ b/common/src/transaction.rs @@ -26,7 +26,6 @@ pub enum TxKind { #[derive(Debug, Serialize, Deserialize, Clone)] ///General transaction object pub struct Transaction { - pub hash: TreeHashType, pub tx_kind: TxKind, ///Tx input data (public part) pub execution_input: Vec, @@ -58,70 +57,6 @@ pub struct Transaction { pub state_changes: (serde_json::Value, usize), } -#[derive(Debug, Serialize, Deserialize, Clone)] -///General transaction object -pub struct TransactionPayload { - 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, - ///Recorded changes in state of smart contract - /// - /// First value represents vector of changes, second is new length of a state - pub state_changes: (serde_json::Value, usize), -} - -impl From for Transaction { - fn from(value: TransactionPayload) -> Self { - let raw_data = serde_json::to_vec(&value).unwrap(); - - let mut hasher = sha2::Sha256::new(); - - hasher.update(&raw_data); - - let hash = ::from(hasher.finalize_fixed()); - - Self { - hash, - tx_kind: value.tx_kind, - execution_input: value.execution_input, - execution_output: value.execution_output, - utxo_commitments_spent_hashes: value.utxo_commitments_spent_hashes, - utxo_commitments_created_hashes: value.utxo_commitments_created_hashes, - nullifier_created_hashes: value.nullifier_created_hashes, - execution_proof_private: value.execution_proof_private, - encoded_data: value.encoded_data, - ephemeral_pub_key: value.ephemeral_pub_key, - commitment: value.commitment, - tweak: value.tweak, - secret_r: value.secret_r, - sc_addr: value.sc_addr, - state_changes: value.state_changes, - } - } -} - #[derive(Debug, Serialize, Deserialize)] pub struct MintMoneyPublicTx { pub acc: [u8; 32], @@ -216,8 +151,19 @@ impl ActionData { } impl Transaction { + /// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`. + pub fn hash(&self) -> TreeHashType { + // TODO: Remove `unwrap` by implementing a `to_bytes` method + // that deterministically encodes all transaction fields to bytes + // and guarantees serialization will succeed. + let raw_data = serde_json::to_vec(&self).unwrap(); + let mut hasher = sha2::Sha256::new(); + hasher.update(&raw_data); + TreeHashType::from(hasher.finalize_fixed()) + } + pub fn log(&self) { - info!("Transaction hash is {:?}", hex::encode(self.hash)); + 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) { @@ -267,3 +213,44 @@ impl Transaction { ); } } + +#[cfg(test)] +mod tests { + use secp256k1_zkp::{constants::SECRET_KEY_SIZE, Tweak}; + use sha2::{digest::FixedOutput, Digest}; + + use crate::{ + merkle_tree_public::TreeHashType, + transaction::{Transaction, TxKind}, + }; + + #[test] + fn test_transaction_hash_is_sha256_of_json_bytes() { + let tx = Transaction { + 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(), + state_changes: (serde_json::Value::Null, 10), + }; + let expected_hash = { + let data = serde_json::to_vec(&tx).unwrap(); + let mut hasher = sha2::Sha256::new(); + hasher.update(&data); + TreeHashType::from(hasher.finalize_fixed()) + }; + + let hash = tx.hash(); + + assert_eq!(expected_hash, hash); + } +} diff --git a/node_core/src/chain_storage/mod.rs b/node_core/src/chain_storage/mod.rs index d8e8065..81be855 100644 --- a/node_core/src/chain_storage/mod.rs +++ b/node_core/src/chain_storage/mod.rs @@ -309,7 +309,6 @@ mod tests { let mut rng = rand::thread_rng(); Transaction { - hash, tx_kind: TxKind::Private, execution_input: vec![], execution_output: vec![], diff --git a/node_core/src/lib.rs b/node_core/src/lib.rs index 2ef3595..3df8de6 100644 --- a/node_core/src/lib.rs +++ b/node_core/src/lib.rs @@ -11,7 +11,7 @@ use accounts::{ }; use anyhow::Result; use chain_storage::NodeChainStore; -use common::transaction::{Transaction, TransactionPayload, TxKind}; +use common::transaction::{Transaction, TxKind}; use config::NodeConfig; use log::info; use sc_core::proofs_circuits::{ @@ -249,7 +249,7 @@ impl NodeCore { let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info); Ok(( - TransactionPayload { + Transaction { tx_kind: TxKind::Private, execution_input: vec![], execution_output: vec![], @@ -346,7 +346,7 @@ impl NodeCore { let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info); Ok(( - TransactionPayload { + Transaction { tx_kind: TxKind::Private, execution_input: vec![], execution_output: vec![], @@ -462,7 +462,7 @@ impl NodeCore { let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info); Ok(( - TransactionPayload { + Transaction { tx_kind: TxKind::Private, execution_input: vec![], execution_output: vec![], @@ -607,7 +607,7 @@ impl NodeCore { let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info); Ok(( - TransactionPayload { + Transaction { tx_kind: TxKind::Private, execution_input: vec![], execution_output: vec![], @@ -730,7 +730,7 @@ impl NodeCore { let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info); Ok(( - TransactionPayload { + Transaction { tx_kind: TxKind::Shielded, execution_input: serde_json::to_vec(&ActionData::SendMoneyShieldedTx( SendMoneyShieldedTx { @@ -823,7 +823,7 @@ impl NodeCore { let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info); - Ok(TransactionPayload { + Ok(Transaction { tx_kind: TxKind::Deshielded, execution_input: serde_json::to_vec(&ActionData::SendMoneyDeshieldedTx( SendMoneyDeshieldedTx { @@ -1462,7 +1462,7 @@ impl NodeCore { let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info); Ok(( - TransactionPayload { + Transaction { tx_kind: TxKind::Shielded, execution_input: vec![], execution_output: serde_json::to_vec(&publication).unwrap(), diff --git a/sc_core/src/transaction_payloads_tools.rs b/sc_core/src/transaction_payloads_tools.rs index cba56dc..e819a24 100644 --- a/sc_core/src/transaction_payloads_tools.rs +++ b/sc_core/src/transaction_payloads_tools.rs @@ -1,6 +1,6 @@ use accounts::{account_core::Account, key_management::ephemeral_key_holder::EphemeralKeyHolder}; use anyhow::Result; -use common::transaction::{TransactionPayload, TxKind}; +use common::transaction::{Transaction, TxKind}; use rand::thread_rng; use risc0_zkvm::Receipt; use secp256k1_zkp::{CommitmentSecrets, PedersenCommitment, Tweak}; @@ -15,8 +15,8 @@ pub fn create_public_transaction_payload( secret_r: [u8; 32], sc_addr: String, state_changes: (serde_json::Value, usize), -) -> TransactionPayload { - TransactionPayload { +) -> Transaction { + Transaction { tx_kind: TxKind::Public, execution_input, execution_output: vec![], diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 5ec58ca..9067758 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -77,7 +77,6 @@ impl SequencerCore { tx_roots: [[u8; 32]; 2], ) -> Result<(), TransactionMalformationErrorKind> { let Transaction { - hash, tx_kind, ref execution_input, ref execution_output, @@ -85,11 +84,12 @@ impl SequencerCore { ref nullifier_created_hashes, .. } = tx; + let tx_hash = tx.hash(); let mempool_size = self.mempool.len(); if mempool_size >= self.sequencer_config.max_num_tx_in_block { - return Err(TransactionMalformationErrorKind::MempoolFullForRound { tx: *hash }); + return Err(TransactionMalformationErrorKind::MempoolFullForRound { tx: tx_hash }); } let curr_sequencer_roots = self.get_tree_roots(); @@ -97,7 +97,7 @@ impl SequencerCore { if tx_roots != curr_sequencer_roots { return Err( TransactionMalformationErrorKind::ChainStateFurtherThanTransactionState { - tx: *hash, + tx: tx_hash, }, ); } @@ -111,7 +111,7 @@ impl SequencerCore { //Public transactions can not make private operations. return Err( TransactionMalformationErrorKind::PublicTransactionChangedPrivateData { - tx: *hash, + tx: tx_hash, }, ); } @@ -123,7 +123,7 @@ impl SequencerCore { //between public and private state. return Err( TransactionMalformationErrorKind::PrivateTransactionChangedPublicData { - tx: *hash, + tx: tx_hash, }, ); } @@ -132,7 +132,7 @@ impl SequencerCore { }; //Tree checks - let tx_tree_check = self.store.pub_tx_store.get_tx(*hash).is_some(); + let tx_tree_check = self.store.pub_tx_store.get_tx(tx_hash).is_some(); let nullifier_tree_check = nullifier_created_hashes .iter() .map(|nullifier_hash| { @@ -152,18 +152,22 @@ impl SequencerCore { .any(|check| check); if tx_tree_check { - return Err(TransactionMalformationErrorKind::TxHashAlreadyPresentInTree { tx: *hash }); + return Err( + TransactionMalformationErrorKind::TxHashAlreadyPresentInTree { tx: tx.hash() }, + ); } if nullifier_tree_check { return Err( - TransactionMalformationErrorKind::NullifierAlreadyPresentInTree { tx: *hash }, + TransactionMalformationErrorKind::NullifierAlreadyPresentInTree { tx: tx.hash() }, ); } if utxo_commitments_check { return Err( - TransactionMalformationErrorKind::UTXOCommitmentAlreadyPresentInTree { tx: *hash }, + TransactionMalformationErrorKind::UTXOCommitmentAlreadyPresentInTree { + tx: tx.hash(), + }, ); } @@ -187,8 +191,6 @@ impl SequencerCore { tx: TransactionMempool, ) -> Result<(), TransactionMalformationErrorKind> { let Transaction { - // ToDo: remove hashing of transactions on node side [Issue #66] - hash: _, ref utxo_commitments_created_hashes, ref nullifier_created_hashes, .. @@ -301,7 +303,6 @@ mod tests { } fn create_dummy_transaction( - hash: TreeHashType, nullifier_created_hashes: Vec<[u8; 32]>, utxo_commitments_spent_hashes: Vec<[u8; 32]>, utxo_commitments_created_hashes: Vec<[u8; 32]>, @@ -309,7 +310,6 @@ mod tests { let mut rng = rand::thread_rng(); Transaction { - hash, tx_kind: TxKind::Private, execution_input: vec![], execution_output: vec![], @@ -328,7 +328,7 @@ mod tests { } fn common_setup(sequencer: &mut SequencerCore) { - let tx = create_dummy_transaction([12; 32], vec![[9; 32]], vec![[7; 32]], vec![[8; 32]]); + let tx = create_dummy_transaction(vec![[9; 32]], vec![[7; 32]], vec![[8; 32]]); let tx_mempool = TransactionMempool { tx }; sequencer.mempool.push_item(tx_mempool); @@ -454,7 +454,7 @@ mod tests { common_setup(&mut sequencer); - let tx = create_dummy_transaction([1; 32], vec![[91; 32]], vec![[71; 32]], vec![[81; 32]]); + let tx = create_dummy_transaction(vec![[91; 32]], vec![[71; 32]], vec![[81; 32]]); let tx_roots = sequencer.get_tree_roots(); let result = sequencer.transaction_pre_check(&tx, tx_roots); @@ -471,7 +471,7 @@ mod tests { common_setup(&mut sequencer); - let tx = create_dummy_transaction([2; 32], vec![[92; 32]], vec![[72; 32]], vec![[82; 32]]); + let tx = create_dummy_transaction(vec![[92; 32]], vec![[72; 32]], vec![[82; 32]]); let tx_roots = sequencer.get_tree_roots(); // Fill the mempool @@ -493,7 +493,7 @@ mod tests { common_setup(&mut sequencer); - let tx = create_dummy_transaction([3; 32], vec![[93; 32]], vec![[73; 32]], vec![[83; 32]]); + let tx = create_dummy_transaction(vec![[93; 32]], vec![[73; 32]], vec![[83; 32]]); let tx_roots = sequencer.get_tree_roots(); let tx_mempool = TransactionMempool { tx }; @@ -507,7 +507,7 @@ mod tests { let config = setup_sequencer_config(); let mut sequencer = SequencerCore::start_from_config(config); - let tx = create_dummy_transaction([4; 32], vec![[94; 32]], vec![[7; 32]], vec![[8; 32]]); + let tx = create_dummy_transaction(vec![[94; 32]], vec![[7; 32]], vec![[8; 32]]); let tx_mempool = TransactionMempool { tx }; sequencer.mempool.push_item(tx_mempool); diff --git a/sequencer_core/src/transaction_mempool.rs b/sequencer_core/src/transaction_mempool.rs index 8c0d720..d932b24 100644 --- a/sequencer_core/src/transaction_mempool.rs +++ b/sequencer_core/src/transaction_mempool.rs @@ -38,6 +38,6 @@ impl MemPoolItem for TransactionMempool { type Identifier = TreeHashType; fn identifier(&self) -> Self::Identifier { - self.tx.hash + self.tx.hash() } }