From 69deef83a1315ae28507842a1615ce8230de0bbf Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Fri, 27 Jan 2023 12:52:19 +0200 Subject: [PATCH 01/10] Moving inner_tx from WrapperTx to Tx. --- apps/src/lib/client/rpc.rs | 6 +- apps/src/lib/client/signing.rs | 28 +++--- .../lib/node/ledger/shell/finalize_block.rs | 42 +++++---- apps/src/lib/node/ledger/shell/mod.rs | 17 +++- .../lib/node/ledger/shell/prepare_proposal.rs | 31 +++--- .../lib/node/ledger/shell/process_proposal.rs | 78 ++++++++------- core/src/proto/mod.rs | 2 + core/src/proto/types.rs | 89 +++++++++++++++++- core/src/types/internal.rs | 5 + core/src/types/transaction/decrypted.rs | 4 +- core/src/types/transaction/encrypted.rs | 2 + core/src/types/transaction/mod.rs | 24 ++--- core/src/types/transaction/wrapper.rs | 55 +++++------ proto/types.proto | 2 + tests/src/vm_host_env/mod.rs | 56 +++++++++++ wasm/checksums.json | 36 +++---- wasm_for_tests/tx_memory_limit.wasm | Bin 133402 -> 133402 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 350309 -> 353095 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 203888 -> 203745 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 150016 -> 152420 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 224094 -> 226647 bytes wasm_for_tests/vp_always_false.wasm | Bin 156489 -> 156489 bytes wasm_for_tests/vp_always_true.wasm | Bin 156489 -> 156489 bytes wasm_for_tests/vp_eval.wasm | Bin 157426 -> 157426 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 158937 -> 158937 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 168269 -> 170673 bytes 26 files changed, 330 insertions(+), 147 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ab64ce948d..bbe4d6e3c1 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -386,12 +386,12 @@ fn extract_payload( wrapper: &mut Option, transfer: &mut Option, ) { - match process_tx(tx) { + match process_tx(tx.clone()) { Ok(TxType::Wrapper(wrapper_tx)) => { let privkey = ::G2Affine::prime_subgroup_generator(); extract_payload( - Tx::from(match wrapper_tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { + Tx::from(match tx.inner_tx.and_then(|inner_tx| wrapper_tx.decrypt(privkey, inner_tx).ok()) { + Some(tx) => DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index e4f8faa5b8..f57f2b6f07 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -236,8 +236,8 @@ pub async fn sign_wrapper( None } }; - - let tx = { + // This object governs how the payload will be processed + let wrapper_tx = { WrapperTx::new( Fee { amount: fee_amount, @@ -246,23 +246,29 @@ pub async fn sign_wrapper( keypair, epoch, args.gas_limit.clone(), - tx, - // TODO: Actually use the fetched encryption key - Default::default(), #[cfg(not(feature = "mainnet"))] pow_solution, ) + // Bind the inner transaction to the wrapper + .bind(tx.clone()) }; - + // Then sign over the bound wrapper + let stx = wrapper_tx + .sign(keypair) + .expect("Wrapper tx signing keypair should be correct") + // Then encrypt and attach the payload to the wrapper + .attach_inner_tx( + &tx, + // TODO: Actually use the fetched encryption key + Default::default(), + ); // We use this to determine when the wrapper tx makes it on-chain - let wrapper_hash = hash_tx(&tx.try_to_vec().unwrap()).to_string(); + let wrapper_hash = hash_tx(&wrapper_tx.try_to_vec().unwrap()).to_string(); // We use this to determine when the decrypted inner tx makes it // on-chain - let decrypted_hash = tx.tx_hash.to_string(); + let decrypted_hash = wrapper_tx.tx_hash.to_string(); TxBroadcastData::Wrapper { - tx: tx - .sign(keypair) - .expect("Wrapper tx signing keypair should be correct"), + tx: stx, wrapper_hash, decrypted_hash, } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 8455522303..3fd9942d9c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -106,7 +106,7 @@ where continue; } - let tx_type = if let Ok(tx_type) = process_tx(tx) { + let tx_type = if let Ok(tx_type) = process_tx(tx.clone()) { tx_type } else { tracing::error!( @@ -217,6 +217,8 @@ where self.storage.tx_queue.push(WrapperTxInQueue { tx: wrapper.clone(), + inner_tx: tx.inner_tx, + inner_tx_code: tx.inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow, }); @@ -454,6 +456,7 @@ where mod test_finalize_block { use namada::types::storage::Epoch; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; + use namada::types::transaction::encrypted::EncryptedTx; use super::*; use crate::node::ledger::shell::test_utils::*; @@ -495,12 +498,13 @@ mod test_finalize_block { &keypair, Epoch(0), 0.into(), - raw_tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let tx = wrapper.sign(&keypair).expect("Test failed"); + ).bind(raw_tx.clone()); + let tx = wrapper + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&raw_tx, Default::default()); if i > 1 { processed_txs.push(ProcessedTx { tx: tx.to_bytes(), @@ -511,7 +515,7 @@ mod test_finalize_block { }, }); } else { - shell.enqueue_tx(wrapper.clone()); + shell.enqueue_tx(wrapper.clone(), tx.inner_tx, tx.inner_tx_code); } if i != 3 { @@ -560,6 +564,7 @@ mod test_finalize_block { "wasm_code".as_bytes().to_owned(), Some(String::from("transaction data").as_bytes().to_owned()), ); + let encrypted_raw_tx = EncryptedTx::encrypt(&raw_tx.to_bytes(), Default::default()); let wrapper = WrapperTx::new( Fee { amount: 0.into(), @@ -568,11 +573,9 @@ mod test_finalize_block { &keypair, Epoch(0), 0.into(), - raw_tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); + ).bind(raw_tx.clone()); let processed_tx = ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { @@ -586,7 +589,7 @@ mod test_finalize_block { info: "".into(), }, }; - shell.enqueue_tx(wrapper); + shell.enqueue_tx(wrapper, Some(encrypted_raw_tx), None); // check that the decrypted tx was not applied for event in shell @@ -626,7 +629,6 @@ mod test_finalize_block { pk: keypair.ref_to(), epoch: Epoch(0), gas_limit: 0.into(), - inner_tx, tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] pow_solution: None, @@ -642,7 +644,7 @@ mod test_finalize_block { }, }; - shell.enqueue_tx(wrapper); + shell.enqueue_tx(wrapper, Some(inner_tx), None); // check that correct error message is returned for event in shell @@ -696,6 +698,7 @@ mod test_finalize_block { .to_owned(), ), ); + let encrypted_raw_tx = EncryptedTx::encrypt(&raw_tx.to_bytes(), Default::default()); let wrapper_tx = WrapperTx::new( Fee { amount: MIN_FEE.into(), @@ -704,12 +707,10 @@ mod test_finalize_block { &keypair, Epoch(0), 0.into(), - raw_tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.enqueue_tx(wrapper_tx); + ).bind(raw_tx.clone()); + shell.enqueue_tx(wrapper_tx, Some(encrypted_raw_tx), None); processed_txs.push(ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx: raw_tx, @@ -741,12 +742,13 @@ mod test_finalize_block { &keypair, Epoch(0), 0.into(), - raw_tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); + ).bind(raw_tx.clone()); + let wrapper = wrapper_tx + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&raw_tx, Default::default()); valid_txs.push(wrapper_tx); processed_txs.push(ProcessedTx { tx: wrapper.to_bytes(), diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 40a3f5d115..2c5e81b876 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -774,6 +774,7 @@ mod test_utils { use namada::types::key::*; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::transaction::{Fee, WrapperTx}; + use namada::types::transaction::encrypted::EncryptedTx; use tempfile::tempdir; use tokio::sync::mpsc::UnboundedReceiver; @@ -921,9 +922,16 @@ mod test_utils { /// Add a wrapper tx to the queue of txs to be decrypted /// in the current block proposal #[cfg(test)] - pub fn enqueue_tx(&mut self, wrapper: WrapperTx) { + pub fn enqueue_tx( + &mut self, + wrapper: WrapperTx, + inner_tx: Option, + inner_tx_code: Option, + ) { self.shell.storage.tx_queue.push(WrapperTxInQueue { tx: wrapper, + inner_tx, + inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, }); @@ -992,6 +1000,7 @@ mod test_utils { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ); + let encrypted_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); let wrapper = WrapperTx::new( Fee { amount: 0.into(), @@ -1000,13 +1009,13 @@ mod test_utils { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); + ).bind(tx); shell.storage.tx_queue.push(WrapperTxInQueue { tx: wrapper, + inner_tx: Some(encrypted_tx), + inner_tx_code: None, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, }); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 04953fa029..954e0f51ce 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -100,12 +100,14 @@ where // decrypt the wrapper txs included in the previous block let decrypted_txs = self.storage.tx_queue.iter().map( |WrapperTxInQueue { - tx, + tx, + inner_tx, + inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow, }| { - Tx::from(match tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { + Tx::from(match inner_tx.clone().and_then(|x| tx.decrypt(privkey, x).ok()) { + Some(tx) => DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: *has_valid_pow, @@ -230,16 +232,16 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) - .try_to_vec() - .expect("Test failed"), + .bind(tx.clone()) + .try_to_vec() + .expect("Test failed"), ), ) - .to_bytes(); + .attach_inner_tx(&tx, Default::default()) + .to_bytes(); #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { txs: vec![wrapper.clone()], @@ -290,13 +292,18 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, + ).bind(tx.clone()); + let wrapper = wrapper_tx + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); + shell.enqueue_tx( + wrapper_tx, + wrapper.inner_tx.clone(), + wrapper.inner_tx_code.clone(), ); - let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); - shell.enqueue_tx(wrapper_tx); expected_wrapper.push(wrapper.clone()); req.txs.push(wrapper.to_bytes()); } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e75254499a..598498e005 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -7,6 +7,7 @@ use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; use crate::facade::tendermint_proto::abci::RequestProcessProposal; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; +use namada::types::transaction::WrapperTx; impl Shell where @@ -86,6 +87,7 @@ where }; // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); + let inner_tx = tx.inner_tx.clone(); match process_tx(tx) { // This occurs if the wrapper / protocol tx signature is invalid @@ -110,6 +112,8 @@ where TxType::Decrypted(tx) => match tx_queue_iter.next() { Some(WrapperTxInQueue { tx: wrapper, + inner_tx, + inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, }) => { @@ -121,7 +125,8 @@ where determined in the previous block" .into(), } - } else if verify_decrypted_correctly(&tx, privkey) { + } else if inner_tx.is_some() && + verify_decrypted_correctly(&tx, privkey, inner_tx.clone().unwrap()) { TxResult { code: ErrorCodes::Ok.into(), info: "Process Proposal accepted this \ @@ -145,7 +150,7 @@ where }, TxType::Wrapper(tx) => { // validate the ciphertext via Ferveo - if !tx.validate_ciphertext() { + if inner_tx.is_none() || !WrapperTx::validate_ciphertext(inner_tx.unwrap()) { TxResult { code: ErrorCodes::InvalidTx.into(), info: format!( @@ -244,16 +249,15 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); + ).bind(tx.clone()); let tx = Tx::new( vec![], Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), ) - .to_bytes(); + .attach_inner_tx(&tx, Default::default()) + .to_bytes(); #[allow(clippy::redundant_clone)] let request = ProcessProposal { txs: vec![tx.clone()], @@ -280,8 +284,9 @@ mod test_process_proposal { fn test_wrapper_bad_signature_rejected() { let (mut shell, _) = TestShell::new(); let keypair = gen_keypair(); + let inner_tx_code = "wasm_code".as_bytes().to_owned(); let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), + inner_tx_code.clone(), Some("transaction data".as_bytes().to_owned()), ); let timestamp = tx.timestamp; @@ -293,13 +298,14 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) - .expect("Test failed"); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed"); + let inner_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); + let inner_tx_code = EncryptedTx::encrypt(&inner_tx_code, Default::default()); let new_tx = if let Some(Ok(SignedTxData { data: Some(data), sig, @@ -333,6 +339,8 @@ mod test_process_proposal { .expect("Test failed"), ), timestamp, + inner_tx: Some(inner_tx), + inner_tx_code: Some(inner_tx_code), } } else { panic!("Test failed"); @@ -377,13 +385,13 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) - .expect("Test failed"); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], }; @@ -433,13 +441,13 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) - .expect("Test failed"); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], @@ -475,6 +483,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), ); + let encrypted_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); let wrapper = WrapperTx::new( Fee { amount: i.into(), @@ -483,17 +492,16 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.enqueue_tx(wrapper); - txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { + ).bind(tx.clone()); + shell.enqueue_tx(wrapper, Some(encrypted_tx.clone()), None); + let tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, - }))); + })); + txs.push(tx); } let req_1 = ProcessProposal { txs: vec![txs[0].to_bytes()], @@ -553,15 +561,15 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.enqueue_tx(wrapper.clone()); + ).bind(tx.clone()); let tx = - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); + Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper.clone()))) + .attach_inner_tx(&tx, Default::default()); + + shell.enqueue_tx(wrapper.clone(), tx.inner_tx.clone(), tx.inner_tx_code.clone()); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -614,18 +622,17 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ); wrapper.tx_hash = Hash([0; 32]); - shell.enqueue_tx(wrapper.clone()); let tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] wrapper.clone(), - ))); + ))).attach_inner_tx(&tx, Default::default()); + + shell.enqueue_tx(wrapper.clone(), tx.inner_tx.clone(), tx.inner_tx_code.clone()); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -669,13 +676,12 @@ mod test_process_proposal { pk: keypair.ref_to(), epoch: Epoch(0), gas_limit: 0.into(), - inner_tx, tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; - shell.enqueue_tx(wrapper.clone()); + shell.enqueue_tx(wrapper.clone(), Some(inner_tx), None); let signed = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] wrapper.clone(), diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 3271037595..5d5cc70379 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -19,6 +19,8 @@ mod tests { code: "wasm code".as_bytes().to_owned(), data: Some("arbitrary data".as_bytes().to_owned()), timestamp: Some(std::time::SystemTime::now().into()), + inner_tx: None, + inner_tx_code: None, }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 40e343d1bf..d87d904b65 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -20,6 +20,10 @@ use crate::types::transaction::process_tx; use crate::types::transaction::DecryptedTx; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::TxType; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::encrypted::EncryptedTx; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::EncryptionKey; #[derive(Error, Debug)] pub enum Error { @@ -146,6 +150,8 @@ impl SigningTx { code: self.code_hash.to_vec(), data: self.data.clone(), timestamp, + inner_tx: None, + inner_tx_code: None, } .encode(&mut bytes) .expect("encoding a transaction failed"); @@ -198,6 +204,8 @@ impl SigningTx { code, data: self.data, timestamp: self.timestamp, + inner_tx: None, + inner_tx_code: None, }) } else { None @@ -219,12 +227,38 @@ impl From for SigningTx { /// certainly be bigger than SigningTxs and contains enough information to /// execute the transaction. #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, + Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct Tx { pub code: Vec, pub data: Option>, pub timestamp: DateTimeUtc, + /// the encrypted payload + #[cfg(feature = "ferveo-tpke")] + pub inner_tx: Option, + #[cfg(not(feature = "ferveo-tpke"))] + pub inner_tx: Option>, + /// the encrypted payload + #[cfg(feature = "ferveo-tpke")] + pub inner_tx_code: Option, + #[cfg(not(feature = "ferveo-tpke"))] + pub inner_tx_code: Option>, +} + +impl Hash for Tx { + fn hash(&self, state: &mut H) where H: Hasher { + self.code.hash(state); + self.data.hash(state); + self.timestamp.hash(state); + } +} + +impl PartialEq for Tx { + fn eq(&self, other: &Self) -> bool { + self.code.eq(&other.code) && + self.data.eq(&other.data) && + self.timestamp.eq(&other.timestamp) + } } impl TryFrom<&[u8]> for Tx { @@ -236,10 +270,26 @@ impl TryFrom<&[u8]> for Tx { Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, None => return Err(Error::NoTimestampError), }; + let inner_tx = match tx.inner_tx { + Some(x) => Some( + BorshDeserialize::try_from_slice(&x) + .expect("Unable to deserialize encrypted transactions") + ), + None => None, + }; + let inner_tx_code = match tx.inner_tx_code { + Some(x) => Some( + BorshDeserialize::try_from_slice(&x) + .expect("Unable to deserialize encrypted transactions") + ), + None => None, + }; Ok(Tx { code: tx.code, data: tx.data, timestamp, + inner_tx, + inner_tx_code, }) } } @@ -247,10 +297,23 @@ impl TryFrom<&[u8]> for Tx { impl From for types::Tx { fn from(tx: Tx) -> Self { let timestamp = Some(tx.timestamp.into()); + let inner_tx = if let Some(inner_tx) = tx.inner_tx { + Some(inner_tx.try_to_vec().expect("Unable to serialize encrypted transaction")) + } else { + None + }; + let inner_tx_code = if let Some(inner_tx_code) = tx.inner_tx_code { + Some(inner_tx_code.try_to_vec().expect("Unable to serialize encrypted transaction")) + } else { + None + }; + types::Tx { code: tx.code, data: tx.data, timestamp, + inner_tx, + inner_tx_code, } } } @@ -347,6 +410,8 @@ impl Tx { code, data, timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } } @@ -367,12 +432,15 @@ impl Tx { } /// Sign a transaction using [`SignedTxData`]. - pub fn sign(self, keypair: &common::SecretKey) -> Self { + pub fn sign(mut self, keypair: &common::SecretKey) -> Self { let code = self.code.clone(); - SigningTx::from(self) + let inner_tx = std::mem::take(&mut self.inner_tx); + let inner_tx_code = std::mem::take(&mut self.inner_tx_code); + let tx = SigningTx::from(self) .sign(keypair) .expand(code) - .expect("code hashes to unexpected value") + .expect("code hashes to unexpected value"); + Self { inner_tx, inner_tx_code, ..tx } } /// Verify that the transaction has been signed by the secret key @@ -384,6 +452,17 @@ impl Tx { ) -> std::result::Result<(), VerifySigError> { SigningTx::from(self.clone()).verify_sig(pk, sig) } + + #[cfg(feature = "ferveo-tpke")] + pub fn attach_inner_tx( + mut self, + tx: &Tx, + encryption_key: EncryptionKey + ) -> Self { + let inner_tx = EncryptedTx::encrypt(&tx.to_bytes(), encryption_key); + self.inner_tx = Some(inner_tx); + self + } } #[allow(dead_code)] @@ -479,6 +558,8 @@ mod tests { code, data: Some(data), timestamp: None, + inner_tx: None, + inner_tx_code: None, }; let mut bytes = vec![]; types_tx.encode(&mut bytes).expect("encoding failed"); diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index 8c85a4236e..fa3771a971 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -47,6 +47,7 @@ impl From for HostEnvResult { #[cfg(feature = "ferveo-tpke")] mod tx_queue { use borsh::{BorshDeserialize, BorshSerialize}; + use crate::types::transaction::encrypted::EncryptedTx; /// A wrapper for `crate::types::transaction::WrapperTx` to conditionally /// add `has_valid_pow` flag for only used in testnets. @@ -54,6 +55,10 @@ mod tx_queue { pub struct WrapperTxInQueue { /// Wrapper tx pub tx: crate::types::transaction::WrapperTx, + /// the encrypted payload + pub inner_tx: Option, + /// the encrypted payload + pub inner_tx_code: Option, #[cfg(not(feature = "mainnet"))] /// A PoW solution can be used to allow zero-fee testnet /// transactions. diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 3ac49efc77..d856e99bab 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -12,6 +12,7 @@ pub mod decrypted_tx { use super::EllipticCurve; use crate::proto::Tx; use crate::types::transaction::{hash_tx, Hash, TxType, WrapperTx}; + use crate::types::transaction::encrypted::EncryptedTx; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[allow(clippy::large_enum_variant)] @@ -75,10 +76,11 @@ pub mod decrypted_tx { pub fn verify_decrypted_correctly( decrypted: &DecryptedTx, privkey: ::G2Affine, + inner_tx: EncryptedTx, ) -> bool { match decrypted { DecryptedTx::Decrypted { .. } => true, - DecryptedTx::Undecryptable(tx) => tx.decrypt(privkey).is_err(), + DecryptedTx::Undecryptable(tx) => tx.decrypt(privkey, inner_tx).is_err(), } } diff --git a/core/src/types/transaction/encrypted.rs b/core/src/types/transaction/encrypted.rs index b73868bbba..8e3a77bb4c 100644 --- a/core/src/types/transaction/encrypted.rs +++ b/core/src/types/transaction/encrypted.rs @@ -4,6 +4,8 @@ #[cfg(feature = "ferveo-tpke")] pub mod encrypted_tx { use std::io::{Error, ErrorKind, Write}; + use std::hash::Hasher; + use std::hash::Hash; use ark_ec::PairingEngine; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 0e0a5e980e..d3ae8cb5e7 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -296,12 +296,16 @@ pub mod tx_types { code: tx.code, data: Some(data.clone()), timestamp: tx.timestamp, + inner_tx: None, + inner_tx_code: None, } .hash(); match TxType::try_from(Tx { code: vec![], data: Some(data), timestamp: tx.timestamp, + inner_tx: None, + inner_tx_code: None, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { @@ -421,7 +425,7 @@ pub mod tx_types { Some("transaction data".as_bytes().to_owned()), ); // the signed tx - let wrapper = WrapperTx::new( + let wrapper_tx = WrapperTx::new( Fee { amount: 10.into(), token: nam(), @@ -429,18 +433,18 @@ pub mod tx_types { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) - .expect("Test failed"); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); - match process_tx(wrapper).expect("Test failed") { + match process_tx(wrapper_tx.clone()).expect("Test failed") { TxType::Wrapper(wrapper) => { let decrypted = - wrapper.decrypt(::G2Affine::prime_subgroup_generator()) + wrapper.decrypt(::G2Affine::prime_subgroup_generator(), wrapper_tx.inner_tx.unwrap()) .expect("Test failed"); assert_eq!(tx, decrypted); } @@ -453,7 +457,7 @@ pub mod tx_types { #[test] fn test_process_tx_wrapper_tx_unsigned() { let keypair = gen_keypair(); - let tx = Tx::new( + let inner_tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ); @@ -466,8 +470,6 @@ pub mod tx_types { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ); @@ -477,7 +479,7 @@ pub mod tx_types { Some( TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), ), - ); + ).attach_inner_tx(&inner_tx, Default::default()); let result = process_tx(tx).expect_err("Test failed"); assert_matches!(result, TxError::Unsigned(_)); } diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 70ef2827bc..c66fe75151 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -18,7 +18,7 @@ pub mod wrapper_tx { use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; use crate::types::transaction::{ - hash_tx, EncryptionKey, Hash, TxError, TxType, + hash_tx, Hash, TxError, TxType, }; /// Minimum fee amount in micro NAMs @@ -173,8 +173,6 @@ pub mod wrapper_tx { pub epoch: Epoch, /// Max amount of gas that can be used when executing the inner tx pub gas_limit: GasLimit, - /// the encrypted payload - pub inner_tx: EncryptedTx, /// sha-2 hash of the inner transaction acting as a commitment /// the contents of the encrypted payload pub tx_hash: Hash, @@ -193,20 +191,16 @@ pub mod wrapper_tx { keypair: &common::SecretKey, epoch: Epoch, gas_limit: GasLimit, - tx: Tx, - encryption_key: EncryptionKey, #[cfg(not(feature = "mainnet"))] pow_solution: Option< crate::ledger::testnet_pow::Solution, >, ) -> WrapperTx { - let inner_tx = EncryptedTx::encrypt(&tx.to_bytes(), encryption_key); Self { fee, pk: keypair.ref_to(), epoch, gas_limit, - inner_tx, - tx_hash: hash_tx(&tx.to_bytes()), + tx_hash: Hash::default(), #[cfg(not(feature = "mainnet"))] pow_solution, } @@ -219,8 +213,8 @@ pub mod wrapper_tx { } /// A validity check on the ciphertext. - pub fn validate_ciphertext(&self) -> bool { - self.inner_tx.0.check(&::G1Prepared::from( + pub fn validate_ciphertext(inner_tx: EncryptedTx) -> bool { + inner_tx.0.check(&::G1Prepared::from( -::G1Affine::prime_subgroup_generator(), )) } @@ -233,9 +227,10 @@ pub mod wrapper_tx { pub fn decrypt( &self, privkey: ::G2Affine, + inner_tx: EncryptedTx, ) -> Result { // decrypt the inner tx - let decrypted = self.inner_tx.decrypt(privkey); + let decrypted = inner_tx.decrypt(privkey); // check that the hash equals commitment if hash_tx(&decrypted) != self.tx_hash { Err(WrapperTxErr::DecryptedHash) @@ -246,6 +241,12 @@ pub mod wrapper_tx { } } + /// Bind the given transaction to this wrapper by recording its hash + pub fn bind(mut self, tx: Tx) -> Self { + self.tx_hash = hash_tx(&tx.to_bytes()); + self + } + /// Sign the wrapper transaction and convert to a normal Tx type pub fn sign( &self, @@ -348,6 +349,7 @@ pub mod wrapper_tx { use super::*; use crate::proto::SignedTxData; use crate::types::address::nam; + use crate::types::transaction::EncryptionKey; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -366,6 +368,7 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ); + let encrypted_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); let wrapper = WrapperTx::new( Fee { @@ -375,14 +378,12 @@ pub mod wrapper_tx { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - assert!(wrapper.validate_ciphertext()); + ).bind(tx.clone()); + assert!(WrapperTx::validate_ciphertext(encrypted_tx.clone())); let privkey = ::G2Affine::prime_subgroup_generator(); - let decrypted = wrapper.decrypt(privkey).expect("Test failed"); + let decrypted = wrapper.decrypt(privkey, encrypted_tx).expect("Test failed"); assert_eq!(tx, decrypted); } @@ -394,6 +395,7 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ); + let encrypted_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); let mut wrapper = WrapperTx::new( Fee { @@ -403,16 +405,14 @@ pub mod wrapper_tx { &gen_keypair(), Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ); // give a incorrect commitment to the decrypted contents of the tx wrapper.tx_hash = Hash([0u8; 32]); - assert!(wrapper.validate_ciphertext()); + assert!(WrapperTx::validate_ciphertext(encrypted_tx.clone())); let privkey = ::G2Affine::prime_subgroup_generator(); - let err = wrapper.decrypt(privkey).expect_err("Test failed"); + let err = wrapper.decrypt(privkey, encrypted_tx).expect_err("Test failed"); assert_matches!(err, WrapperTxErr::DecryptedHash); } @@ -437,13 +437,13 @@ pub mod wrapper_tx { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) - .expect("Test failed"); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); // we now try to alter the inner tx maliciously let mut wrapper = if let TxType::Wrapper(wrapper) = @@ -464,7 +464,7 @@ pub mod wrapper_tx { Tx::new("Give me all the money".as_bytes().to_owned(), None); // We replace the inner tx with a malicious one - wrapper.inner_tx = EncryptedTx::encrypt( + let inner_tx = EncryptedTx::encrypt( &malicious.to_bytes(), EncryptionKey(pubkey), ); @@ -473,10 +473,11 @@ pub mod wrapper_tx { wrapper.tx_hash = hash_tx(&malicious.to_bytes()); // we check ciphertext validity still passes - assert!(wrapper.validate_ciphertext()); + assert!(WrapperTx::validate_ciphertext(inner_tx.clone())); // we check that decryption still succeeds let decrypted = wrapper.decrypt( - ::G2Affine::prime_subgroup_generator() + ::G2Affine::prime_subgroup_generator(), + inner_tx, ) .expect("Test failed"); assert_eq!(decrypted, malicious); diff --git a/proto/types.proto b/proto/types.proto index 58494ec824..287fc4c34f 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -9,6 +9,8 @@ message Tx { // TODO this optional is useless because it's default on proto3 optional bytes data = 2; google.protobuf.Timestamp timestamp = 3; + optional bytes inner_tx = 4; + optional bytes inner_tx_code = 5; } message Dkg { string data = 1; } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 4ab7b6eca7..f9b5367b4c 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -540,6 +540,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -577,6 +579,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); @@ -610,6 +614,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // get and update the client without a header @@ -655,6 +661,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // update the client with the message @@ -684,6 +692,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // upgrade the client with the message @@ -722,6 +732,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -759,6 +771,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // init a connection with the message @@ -785,6 +799,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open the connection with the message @@ -821,6 +837,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open try a connection with the message @@ -848,6 +866,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open the connection with the mssage @@ -889,6 +909,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // not bind a port @@ -930,6 +952,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // bind a port @@ -974,6 +998,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // init a channel with the message @@ -998,6 +1024,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open the channle with the message @@ -1036,6 +1064,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // try open a channel with the message @@ -1061,6 +1091,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open a channel with the message @@ -1101,6 +1133,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // close the channel with the message @@ -1141,6 +1175,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); @@ -1186,6 +1222,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1226,6 +1264,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1275,6 +1315,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1340,6 +1382,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1417,6 +1461,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1464,6 +1510,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // send a packet with the message @@ -1493,6 +1541,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1544,6 +1594,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1606,6 +1658,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); @@ -1678,6 +1732,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + inner_tx: None, + inner_tx_code: None, } .sign(&key::testing::keypair_1()); diff --git a/wasm/checksums.json b/wasm/checksums.json index c36511452a..4b56958dc8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.c69b0b6b85a8340473dace3e927bc01be12cb5ae82d3367938d0005522a29479.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.97b0d6f07c9db41320f1006e12e726098c93aad75eb843a575222c8f305462e7.wasm", - "tx_ibc.wasm": "tx_ibc.3637ae4b46f854b288c01e74eccc63b7e45c9c2d1ee3098b698f4039a6010705.wasm", - "tx_init_account.wasm": "tx_init_account.7aa4dbbf0ecad2d079766c47bf6c6d210523d1c4d74d118a02cde6427460a8c8.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4a977c3d205b68114c6ec8f4ae8d933b768f146759a35de21796395626ca5d43.wasm", - "tx_init_validator.wasm": "tx_init_validator.34b54635942c83de4e4dc94445753ffe55942ebef8a95393ffb10d487e681822.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.054ff210ee793575d75a757bc5c28f88bae037bce99ceb27727e5e3b4c301116.wasm", - "tx_transfer.wasm": "tx_transfer.3fda6e26b50e7aa4b1d6e37fc631d5c55bb9370e6fac71f64f2be137b42df549.wasm", - "tx_unbond.wasm": "tx_unbond.5f4aae1a399f6d556cdf0c85df84b44e4725f0241f7d9ed276f44da08f0f0c84.wasm", - "tx_update_vp.wasm": "tx_update_vp.db4d8c11658c5f8e99fc39f0112be1ee480ab1f0045010d323ee3081a7afe802.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.50c5a13ff8218b1ba79fa7644542af5a9b0bb60316aaa7a630574f9f901f3962.wasm", - "tx_withdraw.wasm": "tx_withdraw.7ff9162d8c5cd40411fff38c2aed47fe0df952fc67f7a9a0c29f624b56d6d88a.wasm", - "vp_implicit.wasm": "vp_implicit.3c4257215de3845937d477f54bdf490bf1cf21acd688852e82277401902882f4.wasm", - "vp_masp.wasm": "vp_masp.08137fd20390a40f106c4ab8ae3a6e548002dff189042aee858c6e4bf10f94e5.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.ddd3135b67e2c116d6915862b93342db9708b9c5b44f5443545910fcea11f5fc.wasm", - "vp_token.wasm": "vp_token.b9622cb39e0141c3a8b9c6d5cba395ca2baf335f1b376079f66ba2bf6b8cd770.wasm", - "vp_user.wasm": "vp_user.f5a3a256d5a4fd12ea736c816e92402beceb1dfea54627209ce408862e290e7c.wasm", - "vp_validator.wasm": "vp_validator.c444b17e75c3a3f7fedf321476a3a7dd4950b131cf8f416a277f57d523613680.wasm" + "tx_bond.wasm": "tx_bond.3fd1ebe744d6dd93cba1689a0616159024f230b72e3cfa41628655735662ccc6.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.dacb181b5443983d68220cde791cc7bfcf72aa85ad726dadae77888a73a61494.wasm", + "tx_ibc.wasm": "tx_ibc.17f4eedae336ca34147630a57b171b8d03aad77617d98c9af066bdee0a1a32f9.wasm", + "tx_init_account.wasm": "tx_init_account.ee26f0264400c8f909d5b761847c84aac9a8ef79e9f6c6e3df69028e81741631.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.aa577cebc4eb5af2fcf5f9ea6a2e70b915cb7c6e96b7341790ad4c23d79703d0.wasm", + "tx_init_validator.wasm": "tx_init_validator.764056876d03dcbadd18ff81ccc97b1fbe83290bc043faa5d290b34ebb9ca2c7.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.f6230e04a8500ad3ae6b376d57b521b83ac16449f021f01019671b0a3ba76a8b.wasm", + "tx_transfer.wasm": "tx_transfer.12e9fc0525979db895249b0b42425b2d3bd503d8e1fb4cd84a7ac297517988cd.wasm", + "tx_unbond.wasm": "tx_unbond.9c0b90a0113193e9e044c5834b02a17ab5251cb0d88b219baa737d3121f6596a.wasm", + "tx_update_vp.wasm": "tx_update_vp.28511b213abd8e8c20358671407ba6e0be370253652dc36dc3a194f6e7bbb6a6.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3f06ef8c94085f5e5ee29f5a2ca69195d245c03fb386cb70c597c6828648f4d5.wasm", + "tx_withdraw.wasm": "tx_withdraw.9364a2e5567e7f7c7b1feae360f0fb4e073bda8be642eede4e6101d2596a3ffe.wasm", + "vp_implicit.wasm": "vp_implicit.9f0ad14db8736862f2a4aaf0e501dc6df19c9ebf5f7ea3efe2f5b009d2736cf1.wasm", + "vp_masp.wasm": "vp_masp.39dd821246008d520f3bbe9bdfadd312e6b3f1d42f9135a339ea29cf86eb3a73.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.602ca75c656d97a63a5fa52250749f7a2f36af038d70027fa9730ccb1ebb5b1c.wasm", + "vp_token.wasm": "vp_token.afc39074d42066a9e7570a13821476edfa9424b651cbbd753d4a186872566739.wasm", + "vp_user.wasm": "vp_user.f2e5060eebee9dc55eabd566a7920dcc0e4ededb03f4d0dbeeba80fc9e558b77.wasm", + "vp_validator.wasm": "vp_validator.cd9d903c32c14aa5d7f96269d6fdcfc025b71676b82614d14194e8c0c8cb1697.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 3a83b28632070bdb8b54f73b809ecabf4bc4c365..2135302ef63265d35a4181477ea71586fec23912 100755 GIT binary patch delta 535 zcmbO=iDT9zjtOffu9anEo_MNEL0L)C(8$7)-^$vC(OgPIRLt0fm)}f+M_gJ>LsLsz zXLAB02Roze=1h(gtc;4AjrcxrFzQZL7Pn{A+ng+Zfss*vvWa98qyFaUlE>LLS1UYb zWR#m6uXKq~ezK8r29TVsTn{9rRazM3H!o08W@3|7WZ+@po?K_24peEVo~nS4fn9;3nJYy)dXgU!ng z{xU18fb3FWbYxIqS6~s)2dNfNgor7yDKLV>CLcGs%BV7Vu4yNu;$(F*O-9wpfo5Jn za*o+dU|5)$TLHtO&|I5wa-4$fywK=VqCwCC9DE0CYhEW0neo0s}v{ z0uzcqk$98uS@trjPtLKD0+Q`kag6GlpIPl;;!}6LF%Rfhc3=o`3#d=tV0#KEv~oUB rh!toUFHoq%?jL5*IUSL zj#GHd$S6PAN$C=!!emk93?NyjTn{AwDYq~xY;IOjW@3|5WZ+@po?K_24pb?ko~Tmp#R(~^R(o-6&ZkTXkg4z zVNhV;=T=}s@hK8-@;%F5MvciiR#HH+-71bzWAihsJxqKWjyL83eaj9EBW?kW$s24> x0fko12MVzQ4dVq0b=bXR1qbZpSN2kqC)kGq?S5uoL@;cdD;&00I4~|c0RYg}jEDdL diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index cb7822f98c258074a7f6006afa2fd1a47e152d16..58f6b1d1271d4a596f0c4606eb7de7a88cbcfbd2 100755 GIT binary patch delta 74786 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 UZ849Us+G&PF7BN)RrA>YA1tXhJ^%m! delta 72652 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 z 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

r4u32I57l)#*8_?EbRVT^J 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 diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index dace8bca099314f12a1fef628e9954e9334b1b14..35d1c72f79e5e07cb677c0e5988e7686ce45c5e1 100755 GIT binary patch delta 26363 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 delta 25104 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

^x@Rq8_aKq9>xXwuRd*p# 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 YJq73_GVA1kQmK=;wXU&GsT6DZU;A^~eEMDg^W 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 L?desFk6r)(e63-V diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 86c64710ce1ebb3690f9ec3b1be4d2942e588edb..56cba5be289067cf9ba6a2048c9ec16b8a263bae 100755 GIT binary patch delta 218 zcmX?kjPv9%&Ivns7+IN_xR{w4IarvPCmv~#w$%6XX7%y)6XD|*Fc4KzQB_mdaAIb) zwq>%}9M9Nf%4jwDJFn|zb@TI#KxT}u>t-9v2&T;&t>u`QnHd;2pR-lxlaXO$tXE)g zWXaNCVo+jaaZq3YG87mc83nkwC!g|}2+=wDw2#|lYacHNkMY~&WxkG^*#gX%HZS#+ zXWDENG>x6nW%A|7-;7q9&qp~3F}iLR$PHy=blIGir!30oHo2==pTkXo+tDFgz_s~c L^Y(+yj2-s@$rU|u delta 199 zcmX?kjPv9%&IvnsSQuHExtN(5IhdK5CLU>!w$k_VX7%y)6XD|*Fc4KzQB_mduw-Vn zv1M}E9M9Nf%4j|LJFn|zb@TI#KxTrk>t-9v2&Tz*yk!}WPj=`r*nHIbE#u~GwrYHn z&-+Y(C;_tECfoaXL3qr}42+v)0u-1SzfG14aNPXd*NkbiP0%!UMyJV_BY!hmZ$2O8 tAjIglSs*u*k$TAs2fqwD0ZW_=D<1#U-&Yyr3CgU#CyHZykI2LSW=KWhL0 diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 04ad77434b855de7d88a0545a7ed1e9727b50f48..553a713de19cba3ed940893096d503e793bc2085 100755 GIT binary patch delta 337 zcmex#mh;nD&Ivbo8CjW_nV6UuIarvOxF$YrkhU`L@n!S#4-n-S5Hu81R#j8i&~#>D zvvFg#-Q2*~WXfngS%S}XvxoV4Mj*4w-*t1WWdzgaOV)Bs%*+gooB!B4u`oJpws)Ed zqHelbvCGIXGS(|FII?7EFfk}GvN$L(02vC5j*J4_+>>AUO@x>;`K6!RUxZPMf9UH9$fx2|++|dcq1u*Ub)z2S7rM$_)UQ52D02H2l#nGc+AWUjGGVnv$HZf zY(5q8g^kf+^8RQ;M%T$7qkl8nY8NY0L{XLQ~y9j^fra!CjRlG77b zFuH7ZNIU=%VoaV6l%JV=8^kG14F+;frcUAl8W~fd4>Yo;-~*%6r)@|Fa+s1hG0RXS3Z9V`1 diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index e19204b86f4e7d8196c620a1267f5b6545458388..d105870aef808a75ee2a346d14ef59f82cdab4c1 100755 GIT binary patch delta 1251 zcmah}YfPJE6#mZF77C^CfvnOiZ#!C`+{ViN{&Hv9atoL#n;Ua75eFL@XBMOehPnZP zoh70pGmBPi)IA-(K$zL^QXwJ{hOP`^vNtRa-QP zN$%9NbZSUFvLjJ zA1p8ab>qs-nMg5X`4Pa1eYV?R!%F)B!a4geVVYwUY~pjrgbWreb@oxyobzR7bxJX^ zfk`|F{NC7tq}ZiRO!!Ue?K|=K21-E!T z<060=GqYH%nPzD@%tSfLpKK@dig6|Xrp#mMTbr>k@hC=+Z@G4af7G0oI-cpDAiig6T6IAOj`HG&i zOYiC|^hQi53n*Z;HgEI0M@-pUl|EF#hSsP=`jp zi*TF&0^!g88p4!7Bgv@8n=Lz5rHJMB86fA5c8rPMR+mhV6YU)co&CVYp-!)k)XYBj z0|3X~UAa(+v%4Ncg_zmh1W=31&+j9w-!n}3&7QO2OHr7K&z}ol%4>Vypu!(}+X$<> zM`Il%a|=`Ww`$pi14m5IARcrtsG~l6;XT6I{ascyC<@c_d*QC|HJet61F!5qMseaV z{zSdDo_4~4o_b2;<(@xvPKpU1e~U7c#xK&B8XLZ-@>Kp?n2Q^))T*94os`wK_vrr?_-h2i~cW}5_OF!*fBWLCRmyajM=AltM^>&6Ps*y$WC8<|w z!r|jp;1CPP$2H(V|D=WRz~pZ5i0hMAL*l1bdCcyN09J33lXk93rnI68Xi iKo{MsbFzVRFkCWY5Ddt4 zL$Y#aL`T#`VuCK&hyM&LBaqGhpW)@vZ^fm|?7eY~E#D0?5I#_z>jq)A5huAeqlte*#Fs z0o#vY#h`sZ;RX93VYy=jto)MWJrR|KgvqU_>{S#|&y7P$Kfr`;>y;_qai2S`G1u3}1}Tso$( zxzm(5343xshFpxztAt$c&wDJn6}6TEOi-c~otz^W+5H(xoK3{O>QbTc;N{YJvJRG=BWr&7Wi28dEfojIH&k(ncunOBZOc{FN85V8s&{Q$;<{}; z!ElpzIWA79`{?H4gR2+6pN!_eRQp7z;b|-)LK=Tm|1}Vs_qD4`4*aIWik1gZk0q^X zgwMC0BQyl-37dieyw_AttAG zP>ad^`GkS~PQp+6r?q(!m6U(y;cc%a=(Hg$O5(a9@J`6aKRdcrr!~J}ee)@6&Fcr$ z=1&YvK`qaG^EsfthXz}9^bs!&pArAReViDbgGT<$v1}=(QHf>CLbn{i!Q-`H;|s^f zBuK~RaVz2e@tu&)uZ+)&rgZk@7qo$BI()pGj^XfkWco`r=}9tXG98$>txu+JUWeDv cihG?MB&sWTcum>=`5zeGerk66DHvM*2LlId(f|Me diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 781e9e365bc65fa05209eb42059a0e9ef0d62902..57765232fd33593161f1a1e0a6bfc1215eb334b7 100755 GIT binary patch delta 30832 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

=P;y*N`K2_qh4@mVex(3KMpZ?igU2;u+ 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 MLgPjmKf(6@0MS}3?*IS* From 68095642a194ada815198e9d3e4cc1362098f5fc Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Fri, 27 Jan 2023 17:08:58 +0200 Subject: [PATCH 02/10] Recombined Tx and SigningTx. --- .../lib/node/ledger/shell/process_proposal.rs | 3 +- core/src/proto/mod.rs | 3 +- core/src/proto/types.rs | 182 +++++++++--------- core/src/types/transaction/encrypted.rs | 2 - core/src/types/transaction/mod.rs | 12 +- proto/types.proto | 1 + shared/src/ledger/protocol/mod.rs | 4 +- tests/src/vm_host_env/mod.rs | 57 +++--- tests/src/vm_host_env/tx.rs | 2 +- 9 files changed, 129 insertions(+), 137 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 598498e005..86b932bd37 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -223,6 +223,7 @@ mod test_process_proposal { use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx}; + use namada::proto::TxCode; use super::*; use crate::facade::tendermint_proto::abci::RequestInitChain; @@ -329,7 +330,7 @@ mod test_process_proposal { .try_to_vec() .expect("Test failed"); Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some( SignedTxData { sig, diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 5d5cc70379..6a37e99bb1 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -3,7 +3,7 @@ pub mod generated; mod types; -pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; +pub use types::{Dkg, Error, Signed, SignedTxData, Tx, TxCode}; #[cfg(test)] mod tests { @@ -17,6 +17,7 @@ mod tests { fn encoding_round_trip() { let tx = Tx { code: "wasm code".as_bytes().to_owned(), + is_literal: true, data: Some("arbitrary data".as_bytes().to_owned()), timestamp: Some(std::time::SystemTime::now().into()), inner_tx: None, diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index d87d904b65..cf197195ff 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -128,97 +128,48 @@ where } } -/// A Tx with its code replaced by a hash salted with the Borsh -/// serialized timestamp of the transaction. This structure will almost -/// certainly be smaller than a Tx, yet in the usual cases it contains -/// enough information to confirm that the Tx is as intended and make a -/// non-malleable signature. +/// Represents either the literal code of a transaction or its hash. #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, + Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Hash, PartialEq, Eq, )] -pub struct SigningTx { - pub code_hash: [u8; 32], - pub data: Option>, - pub timestamp: DateTimeUtc, +pub enum TxCode { + Hash([u8; 32]), + Literal(Vec), } -impl SigningTx { - pub fn hash(&self) -> [u8; 32] { - let timestamp = Some(self.timestamp.into()); - let mut bytes = vec![]; - types::Tx { - code: self.code_hash.to_vec(), - data: self.data.clone(), - timestamp, - inner_tx: None, - inner_tx_code: None, +impl TxCode { + pub fn code(&self) -> Option> { + match self { + Self::Hash(_hash) => None, + Self::Literal(lit) => Some(lit.clone()), } - .encode(&mut bytes) - .expect("encoding a transaction failed"); - hash_tx(&bytes).0 } - - /// Sign a transaction using [`SignedTxData`]. - pub fn sign(self, keypair: &common::SecretKey) -> Self { - let to_sign = self.hash(); - let sig = common::SigScheme::sign(keypair, to_sign); - let signed = SignedTxData { - data: self.data, - sig, - } - .try_to_vec() - .expect("Encoding transaction data shouldn't fail"); - SigningTx { - code_hash: self.code_hash, - data: Some(signed), - timestamp: self.timestamp, + pub fn code_hash(&self) -> [u8; 32] { + match self { + Self::Hash(hash) => hash.clone(), + Self::Literal(lit) => hash_tx(lit).0, } } - - /// Verify that the transaction has been signed by the secret key - /// counterpart of the given public key. - pub fn verify_sig( - &self, - pk: &common::PublicKey, - sig: &common::Signature, - ) -> std::result::Result<(), VerifySigError> { - // Try to get the transaction data from decoded `SignedTxData` - let tx_data = self.data.clone().ok_or(VerifySigError::MissingData)?; - let signed_tx_data = SignedTxData::try_from_slice(&tx_data[..]) - .expect("Decoding transaction data shouldn't fail"); - let data = signed_tx_data.data; - let tx = SigningTx { - code_hash: self.code_hash, - data, - timestamp: self.timestamp, - }; - let signed_data = tx.hash(); - common::SigScheme::verify_signature_raw(pk, &signed_data, sig) - } - /// Expand this reduced Tx using the supplied code only if the the code /// hashes to the stored code hash - pub fn expand(self, code: Vec) -> Option { - if hash_tx(&code).0 == self.code_hash { - Some(Tx { - code, - data: self.data, - timestamp: self.timestamp, - inner_tx: None, - inner_tx_code: None, - }) - } else { - None + pub fn expand(&mut self, code: Vec) { + if TxCode::Hash(hash_tx(&code).0) == *self { + *self = TxCode::Literal(code); } } -} - -impl From for SigningTx { - fn from(tx: Tx) -> SigningTx { - SigningTx { - code_hash: hash_tx(&tx.code).0, - data: tx.data, - timestamp: tx.timestamp, + pub fn contract(&mut self) { + *self = TxCode::Hash(self.code_hash()); + } + pub fn is_literal(&self) -> bool { + match self { + Self::Literal(_) => true, + _ => false, + } + } + pub fn is_hash(&self) -> bool { + match self { + Self::Hash(_) => true, + _ => false, } } } @@ -230,7 +181,7 @@ impl From for SigningTx { Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct Tx { - pub code: Vec, + pub code: TxCode, pub data: Option>, pub timestamp: DateTimeUtc, /// the encrypted payload @@ -284,8 +235,13 @@ impl TryFrom<&[u8]> for Tx { ), None => None, }; + let code = if tx.is_literal { + TxCode::Literal(tx.code) + } else { + TxCode::Hash(tx.code.try_into().expect("Unable to deserialize code hash")) + }; Ok(Tx { - code: tx.code, + code, data: tx.data, timestamp, inner_tx, @@ -309,7 +265,8 @@ impl From for types::Tx { }; types::Tx { - code: tx.code, + code: tx.code.code().unwrap_or(tx.code.code_hash().to_vec()), + is_literal: tx.code.is_literal(), data: tx.data, timestamp, inner_tx, @@ -407,7 +364,7 @@ impl From for ResponseDeliverTx { impl Tx { pub fn new(code: Vec, data: Option>) -> Self { Tx { - code, + code: TxCode::Literal(code), data, timestamp: DateTimeUtc::now(), inner_tx: None, @@ -424,23 +381,42 @@ impl Tx { } pub fn hash(&self) -> [u8; 32] { - SigningTx::from(self.clone()).hash() + let timestamp = Some(self.timestamp.into()); + let mut bytes = vec![]; + types::Tx { + code: self.code.code().unwrap_or(self.code.code_hash().to_vec()), + is_literal: self.code.is_literal(), + data: self.data.clone(), + timestamp, + inner_tx: None, + inner_tx_code: None, + } + .encode(&mut bytes) + .expect("encoding a transaction failed"); + hash_tx(&bytes).0 } pub fn code_hash(&self) -> [u8; 32] { - SigningTx::from(self.clone()).code_hash + self.code.code_hash() } /// Sign a transaction using [`SignedTxData`]. - pub fn sign(mut self, keypair: &common::SecretKey) -> Self { - let code = self.code.clone(); - let inner_tx = std::mem::take(&mut self.inner_tx); - let inner_tx_code = std::mem::take(&mut self.inner_tx_code); - let tx = SigningTx::from(self) - .sign(keypair) - .expand(code) - .expect("code hashes to unexpected value"); - Self { inner_tx, inner_tx_code, ..tx } + pub fn sign(self, keypair: &common::SecretKey) -> Self { + let to_sign = self.hash(); + let sig = common::SigScheme::sign(keypair, to_sign); + let signed = SignedTxData { + data: self.data, + sig, + } + .try_to_vec() + .expect("Encoding transaction data shouldn't fail"); + Tx { + code: self.code, + data: Some(signed), + timestamp: self.timestamp, + inner_tx: self.inner_tx, + inner_tx_code: self.inner_tx_code, + } } /// Verify that the transaction has been signed by the secret key @@ -450,7 +426,20 @@ impl Tx { pk: &common::PublicKey, sig: &common::Signature, ) -> std::result::Result<(), VerifySigError> { - SigningTx::from(self.clone()).verify_sig(pk, sig) + // Try to get the transaction data from decoded `SignedTxData` + let tx_data = self.data.clone().ok_or(VerifySigError::MissingData)?; + let signed_tx_data = SignedTxData::try_from_slice(&tx_data[..]) + .expect("Decoding transaction data shouldn't fail"); + let data = signed_tx_data.data; + let tx = Tx { + code: self.code.clone(), + data, + timestamp: self.timestamp, + inner_tx: self.inner_tx.clone(), + inner_tx_code: self.inner_tx_code.clone(), + }; + let signed_data = tx.hash(); + common::SigScheme::verify_signature_raw(pk, &signed_data, sig) } #[cfg(feature = "ferveo-tpke")] @@ -555,7 +544,8 @@ mod tests { assert_eq!(tx_from_bytes, tx); let types_tx = types::Tx { - code, + code: code.clone(), + is_literal: true, data: Some(data), timestamp: None, inner_tx: None, diff --git a/core/src/types/transaction/encrypted.rs b/core/src/types/transaction/encrypted.rs index 8e3a77bb4c..b73868bbba 100644 --- a/core/src/types/transaction/encrypted.rs +++ b/core/src/types/transaction/encrypted.rs @@ -4,8 +4,6 @@ #[cfg(feature = "ferveo-tpke")] pub mod encrypted_tx { use std::io::{Error, ErrorKind, Write}; - use std::hash::Hasher; - use std::hash::Hash; use ark_ec::PairingEngine; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index d3ae8cb5e7..3d1333a4d6 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -293,19 +293,19 @@ pub mod tx_types { .map(|data| SignedTxData::try_from_slice(&data[..])) { let signed_hash = Tx { - code: tx.code, + code: tx.code.clone(), data: Some(data.clone()), timestamp: tx.timestamp, - inner_tx: None, - inner_tx_code: None, + inner_tx: tx.inner_tx.clone(), + inner_tx_code: tx.inner_tx_code.clone(), } .hash(); match TxType::try_from(Tx { - code: vec![], + code: tx.code, data: Some(data), timestamp: tx.timestamp, - inner_tx: None, - inner_tx_code: None, + inner_tx: tx.inner_tx, + inner_tx_code: tx.inner_tx_code, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { diff --git a/proto/types.proto b/proto/types.proto index 287fc4c34f..49c2d53442 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -11,6 +11,7 @@ message Tx { google.protobuf.Timestamp timestamp = 3; optional bytes inner_tx = 4; optional bytes inner_tx_code = 5; + bool is_literal = 6; } message Dkg { string data = 1; } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index cfd416c14d..05ff742b1e 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -162,7 +162,7 @@ where CA: 'static + WasmCacheAccess + Sync, { gas_meter - .add_compiling_fee(tx.code.len()) + .add_compiling_fee(tx.code.code().expect("tx code not present").len()) .map_err(Error::GasError)?; let empty = vec![]; let tx_data = tx.data.as_ref().unwrap_or(&empty); @@ -171,7 +171,7 @@ where write_log, gas_meter, tx_index, - &tx.code, + &tx.code.code().expect("tx code not present"), tx_data, vp_wasm_cache, tx_wasm_cache, diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index f9b5367b4c..88775902de 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -45,6 +45,7 @@ mod tests { use super::{ibc, tx, vp}; use crate::tx::{tx_host_env, TestTxEnv}; use crate::vp::{vp_host_env, TestVpEnv}; + use crate::namada::proto::TxCode; // paths to the WASMs used for tests const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; @@ -537,7 +538,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -576,7 +577,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -611,7 +612,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -658,7 +659,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -689,7 +690,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -729,7 +730,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -768,7 +769,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -796,7 +797,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -834,7 +835,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -863,7 +864,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -906,7 +907,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -949,7 +950,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -995,7 +996,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1021,7 +1022,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1061,7 +1062,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1088,7 +1089,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1130,7 +1131,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1172,7 +1173,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1219,7 +1220,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1261,7 +1262,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1312,7 +1313,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1379,7 +1380,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1458,7 +1459,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1507,7 +1508,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1538,7 +1539,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1591,7 +1592,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1655,7 +1656,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, @@ -1729,7 +1730,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code: TxCode::Literal(vec![]), data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 0f7040941d..a8452fee5d 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -191,7 +191,7 @@ impl TestTxEnv { &mut self.write_log, &mut self.gas_meter, &self.tx_index, - &self.tx.code, + &self.tx.code.code().expect("tx code not present"), self.tx.data.as_ref().unwrap_or(&empty_data), &mut self.vp_wasm_cache, &mut self.tx_wasm_cache, From e7f1e42272b81bb50f6dd3b29044cb1ade969a14 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 30 Jan 2023 06:33:14 +0200 Subject: [PATCH 03/10] Hash the inner transaction code before placement inside wrapper. --- apps/src/lib/client/signing.rs | 24 ++++++--- .../lib/node/ledger/shell/prepare_proposal.rs | 15 ++++-- .../lib/node/ledger/shell/process_proposal.rs | 5 +- core/src/proto/types.rs | 49 +++++++++++++++---- core/src/types/transaction/decrypted.rs | 4 +- core/src/types/transaction/wrapper.rs | 13 ++--- wasm/checksums.json | 34 ++++++------- 7 files changed, 96 insertions(+), 48 deletions(-) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index f57f2b6f07..49b2722acb 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -182,7 +182,7 @@ pub async fn sign_wrapper( ctx: &Context, args: &args::Tx, epoch: Epoch, - tx: Tx, + mut tx: Tx, keypair: &common::SecretKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> TxBroadcastData { @@ -236,6 +236,10 @@ pub async fn sign_wrapper( None } }; + // Get transaction code to later attach to a wrapper transaction + let tx_code = tx.code.code(); + // Contract the transaction's code field to make transaction smaller + tx.code.contract(); // This object governs how the payload will be processed let wrapper_tx = { WrapperTx::new( @@ -253,15 +257,23 @@ pub async fn sign_wrapper( .bind(tx.clone()) }; // Then sign over the bound wrapper - let stx = wrapper_tx + let mut stx = wrapper_tx .sign(keypair) - .expect("Wrapper tx signing keypair should be correct") - // Then encrypt and attach the payload to the wrapper - .attach_inner_tx( - &tx, + .expect("Wrapper tx signing keypair should be correct"); + // If we have the transaction code, then attach it to the wrapper + if let Some(code) = tx_code { + stx = stx.attach_inner_tx_code( + &code, // TODO: Actually use the fetched encryption key Default::default(), ); + } + // Then encrypt and attach the payload to the wrapper + stx = stx.attach_inner_tx( + &tx, + // TODO: Actually use the fetched encryption key + Default::default(), + ); // We use this to determine when the wrapper tx makes it on-chain let wrapper_hash = hash_tx(&wrapper_tx.try_to_vec().unwrap()).to_string(); // We use this to determine when the decrypted inner tx makes it diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 954e0f51ce..f19c173e97 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -107,10 +107,17 @@ where has_valid_pow, }| { Tx::from(match inner_tx.clone().and_then(|x| tx.decrypt(privkey, x).ok()) { - Some(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, + Some(mut inner_tx) => { + if let Some(inner_tx_code) = inner_tx_code { + if let Some(inner_tx_code) = inner_tx.decrypt_code(privkey, inner_tx_code.clone()) { + inner_tx.code.expand(inner_tx_code); + } + } + DecryptedTx::Decrypted { + tx: inner_tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + } }, _ => DecryptedTx::Undecryptable(tx.clone()), }) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 86b932bd37..8cead93100 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -497,12 +497,11 @@ mod test_process_proposal { None, ).bind(tx.clone()); shell.enqueue_tx(wrapper, Some(encrypted_tx.clone()), None); - let tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { + txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, - })); - txs.push(tx); + }))); } let req_1 = ProcessProposal { txs: vec![txs[0].to_bytes()], diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index cf197195ff..9fc4417d65 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -24,6 +24,10 @@ use crate::types::transaction::TxType; use crate::types::transaction::encrypted::EncryptedTx; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::EncryptionKey; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::EllipticCurve; +#[cfg(feature = "ferveo-tpke")] +use ark_ec::PairingEngine; #[derive(Error, Debug)] pub enum Error { @@ -196,14 +200,6 @@ pub struct Tx { pub inner_tx_code: Option>, } -impl Hash for Tx { - fn hash(&self, state: &mut H) where H: Hasher { - self.code.hash(state); - self.data.hash(state); - self.timestamp.hash(state); - } -} - impl PartialEq for Tx { fn eq(&self, other: &Self) -> bool { self.code.eq(&other.code) && @@ -372,6 +368,28 @@ impl Tx { } } + /// Decrypt the wrapped transaction. + /// + /// Will fail if the inner transaction does match the + /// hash commitment or we are unable to recover a + /// valid Tx from the decoded byte stream. + #[cfg(feature = "ferveo-tpke")] + pub fn decrypt_code( + &self, + privkey: ::G2Affine, + inner_tx: EncryptedTx, + ) -> Option> { + // decrypt the inner tx + let decrypted = inner_tx.decrypt(privkey); + // check that the hash equals commitment + if hash_tx(&decrypted).0 != self.code.code_hash() { + None + } else { + // convert back to Tx type + Some(decrypted) + } + } + pub fn to_bytes(&self) -> Vec { let mut bytes = vec![]; let tx: types::Tx = self.clone().into(); @@ -384,8 +402,8 @@ impl Tx { let timestamp = Some(self.timestamp.into()); let mut bytes = vec![]; types::Tx { - code: self.code.code().unwrap_or(self.code.code_hash().to_vec()), - is_literal: self.code.is_literal(), + code: self.code.code_hash().to_vec(), + is_literal: false, data: self.data.clone(), timestamp, inner_tx: None, @@ -452,6 +470,17 @@ impl Tx { self.inner_tx = Some(inner_tx); self } + + #[cfg(feature = "ferveo-tpke")] + pub fn attach_inner_tx_code( + mut self, + tx: &[u8], + encryption_key: EncryptionKey + ) -> Self { + let inner_tx_code = EncryptedTx::encrypt(tx, encryption_key); + self.inner_tx_code = Some(inner_tx_code); + self + } } #[allow(dead_code)] diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index d856e99bab..66c6d199b4 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -11,7 +11,7 @@ pub mod decrypted_tx { use super::EllipticCurve; use crate::proto::Tx; - use crate::types::transaction::{hash_tx, Hash, TxType, WrapperTx}; + use crate::types::transaction::{Hash, TxType, WrapperTx}; use crate::types::transaction::encrypted::EncryptedTx; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] @@ -64,7 +64,7 @@ pub mod decrypted_tx { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, - } => hash_tx(&tx.to_bytes()), + } => Hash(tx.hash()), DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), } } diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index c66fe75151..e8235a6b2d 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -231,19 +231,20 @@ pub mod wrapper_tx { ) -> Result { // decrypt the inner tx let decrypted = inner_tx.decrypt(privkey); + // convert back to Tx type + let decrypted_tx = Tx::try_from(decrypted.as_ref()) + .map_err(|_| WrapperTxErr::InvalidTx)?; // check that the hash equals commitment - if hash_tx(&decrypted) != self.tx_hash { + if decrypted_tx.hash() != self.tx_hash.0 { Err(WrapperTxErr::DecryptedHash) } else { - // convert back to Tx type - Tx::try_from(decrypted.as_ref()) - .map_err(|_| WrapperTxErr::InvalidTx) + Ok(decrypted_tx) } } /// Bind the given transaction to this wrapper by recording its hash pub fn bind(mut self, tx: Tx) -> Self { - self.tx_hash = hash_tx(&tx.to_bytes()); + self.tx_hash = Hash(tx.hash()); self } @@ -470,7 +471,7 @@ pub mod wrapper_tx { ); // We change the commitment appropriately - wrapper.tx_hash = hash_tx(&malicious.to_bytes()); + wrapper.tx_hash = Hash(malicious.hash()); // we check ciphertext validity still passes assert!(WrapperTx::validate_ciphertext(inner_tx.clone())); diff --git a/wasm/checksums.json b/wasm/checksums.json index 4b56958dc8..c6f62b1f9d 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.3fd1ebe744d6dd93cba1689a0616159024f230b72e3cfa41628655735662ccc6.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.dacb181b5443983d68220cde791cc7bfcf72aa85ad726dadae77888a73a61494.wasm", - "tx_ibc.wasm": "tx_ibc.17f4eedae336ca34147630a57b171b8d03aad77617d98c9af066bdee0a1a32f9.wasm", - "tx_init_account.wasm": "tx_init_account.ee26f0264400c8f909d5b761847c84aac9a8ef79e9f6c6e3df69028e81741631.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.aa577cebc4eb5af2fcf5f9ea6a2e70b915cb7c6e96b7341790ad4c23d79703d0.wasm", - "tx_init_validator.wasm": "tx_init_validator.764056876d03dcbadd18ff81ccc97b1fbe83290bc043faa5d290b34ebb9ca2c7.wasm", + "tx_bond.wasm": "tx_bond.0541f0497f7c14690cf49647c6be0324e5bdd44bf27ffb022dcf68a7f6b7827d.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.3ee9010b89ef090ddaaa21e898fc87257801aa49068818971ecbbe880dd210e0.wasm", + "tx_ibc.wasm": "tx_ibc.0daeeb69727f893a2f12d923e77c878eb7877f81774e8443cb2ed29339e7aac1.wasm", + "tx_init_account.wasm": "tx_init_account.495f96fcefbafa49b3aa1d8f80b984eb626f5fbb4db51c444ea48e900217b186.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.7accc0b5de2ef8f24e8c2efc4975d2eee62015b2a905405f79da8563e9c15b15.wasm", + "tx_init_validator.wasm": "tx_init_validator.34739c6f982ed63e9a6884cf0cc0f8e6ddb45cd2a2db0f35280af7c37830510b.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.f6230e04a8500ad3ae6b376d57b521b83ac16449f021f01019671b0a3ba76a8b.wasm", - "tx_transfer.wasm": "tx_transfer.12e9fc0525979db895249b0b42425b2d3bd503d8e1fb4cd84a7ac297517988cd.wasm", - "tx_unbond.wasm": "tx_unbond.9c0b90a0113193e9e044c5834b02a17ab5251cb0d88b219baa737d3121f6596a.wasm", - "tx_update_vp.wasm": "tx_update_vp.28511b213abd8e8c20358671407ba6e0be370253652dc36dc3a194f6e7bbb6a6.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3f06ef8c94085f5e5ee29f5a2ca69195d245c03fb386cb70c597c6828648f4d5.wasm", - "tx_withdraw.wasm": "tx_withdraw.9364a2e5567e7f7c7b1feae360f0fb4e073bda8be642eede4e6101d2596a3ffe.wasm", - "vp_implicit.wasm": "vp_implicit.9f0ad14db8736862f2a4aaf0e501dc6df19c9ebf5f7ea3efe2f5b009d2736cf1.wasm", - "vp_masp.wasm": "vp_masp.39dd821246008d520f3bbe9bdfadd312e6b3f1d42f9135a339ea29cf86eb3a73.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.602ca75c656d97a63a5fa52250749f7a2f36af038d70027fa9730ccb1ebb5b1c.wasm", - "vp_token.wasm": "vp_token.afc39074d42066a9e7570a13821476edfa9424b651cbbd753d4a186872566739.wasm", - "vp_user.wasm": "vp_user.f2e5060eebee9dc55eabd566a7920dcc0e4ededb03f4d0dbeeba80fc9e558b77.wasm", - "vp_validator.wasm": "vp_validator.cd9d903c32c14aa5d7f96269d6fdcfc025b71676b82614d14194e8c0c8cb1697.wasm" + "tx_transfer.wasm": "tx_transfer.0baa64bdcc2031d70934531e892bb420fe908d2e3dba4cb84277323d35c26a3e.wasm", + "tx_unbond.wasm": "tx_unbond.72195f598ad3bcc82095e4f33df785bc3af1c02dac276cc5b0aaadef9d8c261f.wasm", + "tx_update_vp.wasm": "tx_update_vp.9f11e9e3cfb35396941db3d7d742aa38a9eeb1e042fab0839fcb9805554b0401.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d944be9d5372c078a9118268e42dd48a43ac0ef4ce401ac6863b88cb647013a3.wasm", + "tx_withdraw.wasm": "tx_withdraw.42cc4c4926f32090964f0fcdf8a86ddfac282947f5710524d6bab2acfd75c147.wasm", + "vp_implicit.wasm": "vp_implicit.81355ce80471010b9db30addedcce27917b06148224f23fdb83e34045480ef27.wasm", + "vp_masp.wasm": "vp_masp.62a3fff9fada661591248738aa1b2cbd22b90fb58a3acb607b5ce8089c363b83.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.d7eee970bfd5704a6d969ca54a7afba0fbaf0695bbc12d97764329b6784cad14.wasm", + "vp_token.wasm": "vp_token.b72e854862166c91aac0528b68c64c28b4262cf4def47a821dc90e6597edf1ec.wasm", + "vp_user.wasm": "vp_user.28bcf125b9a8fddc94279c21bee36b879f2b2822184995d2220d739fe66982b7.wasm", + "vp_validator.wasm": "vp_validator.480fe41dc4d84e1cbb4bced845e6fbb2721e1fdadde24b0fb4a420b376213356.wasm" } \ No newline at end of file From c4c273091cc7e0ca39026ed0709adc8adf5b3b9c Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 6 Feb 2023 10:08:41 +0200 Subject: [PATCH 04/10] Added more error handling and streamlined (de)serialization code. --- .../lib/node/ledger/shell/finalize_block.rs | 2 +- .../lib/node/ledger/shell/prepare_proposal.rs | 40 ++++-- .../lib/node/ledger/shell/process_proposal.rs | 24 ++-- core/src/proto/mod.rs | 10 +- core/src/proto/types.rs | 124 +++++++++++------- core/src/types/transaction/decrypted.rs | 28 +++- core/src/types/transaction/encrypted.rs | 11 +- core/src/types/transaction/mod.rs | 2 +- core/src/types/transaction/wrapper.rs | 36 ++--- proto/types.proto | 2 +- shared/src/ledger/protocol/mod.rs | 6 +- 11 files changed, 185 insertions(+), 100 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 3fd9942d9c..6002201b8b 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -239,7 +239,7 @@ where .to_string(), ); } - DecryptedTx::Undecryptable(_) => { + DecryptedTx::Undecryptable(_) | DecryptedTx::UndecryptableCode(_) => { event["log"] = "Transaction could not be decrypted.".into(); event["code"] = ErrorCodes::Undecryptable.into(); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index f19c173e97..9da03d6120 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -109,17 +109,41 @@ where Tx::from(match inner_tx.clone().and_then(|x| tx.decrypt(privkey, x).ok()) { Some(mut inner_tx) => { if let Some(inner_tx_code) = inner_tx_code { - if let Some(inner_tx_code) = inner_tx.decrypt_code(privkey, inner_tx_code.clone()) { - inner_tx.code.expand(inner_tx_code); + if let Some(inner_tx_code) = + inner_tx.decrypt_code(privkey, inner_tx_code.clone()) { + // Embed the inner_tx_code inside the tx for + // future processing + inner_tx.code.expand(inner_tx_code) + .expect("decrypted code should have correct hash"); + // An inner_tx_code consistent with code is + // treated as a successful decryption + DecryptedTx::Decrypted { + tx: inner_tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + } + } else { + // A failure to decrypt an inner_tx_code is + // treated as undecryptable + DecryptedTx::UndecryptableCode(inner_tx) } - } - DecryptedTx::Decrypted { - tx: inner_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, + } else if inner_tx.code.is_literal() { + // A literal code without an inner_tx_code is + // treated as a successful decryption + DecryptedTx::Decrypted { + tx: inner_tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + } + } else { + // The code being absent from both inner_tx and + // the code field is treated as undecryptable + DecryptedTx::UndecryptableCode(inner_tx) } }, - _ => DecryptedTx::Undecryptable(tx.clone()), + // An absent or undecryptable inner_tx are both treated + // as undecryptable + None => DecryptedTx::Undecryptable(tx.clone()), }) .to_bytes() }, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 8cead93100..aa082aa5e3 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -7,7 +7,6 @@ use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; use crate::facade::tendermint_proto::abci::RequestProcessProposal; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; -use namada::types::transaction::WrapperTx; impl Shell where @@ -87,9 +86,8 @@ where }; // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - let inner_tx = tx.inner_tx.clone(); - match process_tx(tx) { + match process_tx(tx.clone()) { // This occurs if the wrapper / protocol tx signature is invalid Err(err) => TxResult { code: ErrorCodes::InvalidSig.into(), @@ -125,8 +123,12 @@ where determined in the previous block" .into(), } - } else if inner_tx.is_some() && - verify_decrypted_correctly(&tx, privkey, inner_tx.clone().unwrap()) { + } else if verify_decrypted_correctly( + &tx, + privkey, + inner_tx.clone(), + inner_tx_code.clone(), + ) { TxResult { code: ErrorCodes::Ok.into(), info: "Process Proposal accepted this \ @@ -148,9 +150,9 @@ where .into(), }, }, - TxType::Wrapper(tx) => { + TxType::Wrapper(wtx) => { // validate the ciphertext via Ferveo - if inner_tx.is_none() || !WrapperTx::validate_ciphertext(inner_tx.unwrap()) { + if tx.inner_tx.is_none() || !tx.validate_ciphertext() { TxResult { code: ErrorCodes::InvalidTx.into(), info: format!( @@ -164,19 +166,19 @@ where // transaction key, then the fee payer is effectively // the MASP, otherwise derive // they payer from public key. - let fee_payer = if tx.pk != masp_tx_key().ref_to() { - tx.fee_payer() + let fee_payer = if wtx.pk != masp_tx_key().ref_to() { + wtx.fee_payer() } else { masp() }; // check that the fee payer has sufficient balance let balance = - self.get_balance(&tx.fee.token, &fee_payer); + self.get_balance(&wtx.fee.token, &fee_payer); // In testnets, tx is allowed to skip fees if it // includes a valid PoW #[cfg(not(feature = "mainnet"))] - let has_valid_pow = self.has_valid_pow_solution(&tx); + let has_valid_pow = self.has_valid_pow_solution(&wtx); #[cfg(feature = "mainnet")] let has_valid_pow = false; diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 6a37e99bb1..b73dbf274b 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -15,13 +15,15 @@ mod tests { #[test] fn encoding_round_trip() { + let code = "wasm code".as_bytes().to_owned(); + let inner_tx = "arbitrary data".as_bytes().to_owned(); let tx = Tx { - code: "wasm code".as_bytes().to_owned(), - is_literal: true, + code: code.clone(), + is_code_hash: false, data: Some("arbitrary data".as_bytes().to_owned()), timestamp: Some(std::time::SystemTime::now().into()), - inner_tx: None, - inner_tx_code: None, + inner_tx: Some(inner_tx), + inner_tx_code: Some(code), }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 9fc4417d65..62d1ce543b 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -28,11 +28,15 @@ use crate::types::transaction::EncryptionKey; use crate::types::transaction::EllipticCurve; #[cfg(feature = "ferveo-tpke")] use ark_ec::PairingEngine; +#[cfg(feature = "ferveo-tpke")] +use ark_ec::AffineCurve; #[derive(Error, Debug)] pub enum Error { #[error("Error decoding a transaction from bytes: {0}")] TxDecodingError(prost::DecodeError), + #[error("Error deserializing transaction field bytes: {0}")] + TxDeserializingError(std::io::Error), #[error("Error decoding an DkgGossipMessage from bytes: {0}")] DkgDecodingError(prost::DecodeError), #[error("Dkg is empty")] @@ -132,22 +136,33 @@ where } } -/// Represents either the literal code of a transaction or its hash. +/// Failed expansion due to hash of supplied code not matching contained hash +#[derive(Debug)] +pub struct InvalidCodeError; + +/// Represents either the literal code of a transaction or its hash. Useful for +/// supporting both wallets that need the full transaction code, and those that +/// only need the hash. Also useful for cases when passing full transaction code +/// around separately from their transactions is cumbersome. #[derive( Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Hash, PartialEq, Eq, )] pub enum TxCode { + /// A hash of transaction code Hash([u8; 32]), + /// The full transaction code Literal(Vec), } impl TxCode { + /// Get the literal transaction code if available pub fn code(&self) -> Option> { match self { Self::Hash(_hash) => None, Self::Literal(lit) => Some(lit.clone()), } } + /// Return the transaction code hash pub fn code_hash(&self) -> [u8; 32] { match self { Self::Hash(hash) => hash.clone(), @@ -156,20 +171,26 @@ impl TxCode { } /// Expand this reduced Tx using the supplied code only if the the code /// hashes to the stored code hash - pub fn expand(&mut self, code: Vec) { - if TxCode::Hash(hash_tx(&code).0) == *self { + pub fn expand(&mut self, code: Vec) -> std::result::Result<(), InvalidCodeError> { + if hash_tx(&code).0 == self.code_hash() { *self = TxCode::Literal(code); + Ok(()) + } else { + Err(InvalidCodeError) } } + /// Replace a literal code with its hash pub fn contract(&mut self) { *self = TxCode::Hash(self.code_hash()); } + /// Indicates that this object contains the full code pub fn is_literal(&self) -> bool { match self { Self::Literal(_) => true, _ => false, } } + /// Indicates that this object only contains the hash of the code pub fn is_hash(&self) -> bool { match self { Self::Hash(_) => true, @@ -182,32 +203,24 @@ impl TxCode { /// certainly be bigger than SigningTxs and contains enough information to /// execute the transaction. #[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, )] pub struct Tx { pub code: TxCode, pub data: Option>, pub timestamp: DateTimeUtc, - /// the encrypted payload + /// the encrypted inner transaction if data contains a WrapperTx #[cfg(feature = "ferveo-tpke")] pub inner_tx: Option, #[cfg(not(feature = "ferveo-tpke"))] pub inner_tx: Option>, - /// the encrypted payload + /// the encrypted inner transaction code if data contains a WrapperTx #[cfg(feature = "ferveo-tpke")] pub inner_tx_code: Option, #[cfg(not(feature = "ferveo-tpke"))] pub inner_tx_code: Option>, } -impl PartialEq for Tx { - fn eq(&self, other: &Self) -> bool { - self.code.eq(&other.code) && - self.data.eq(&other.data) && - self.timestamp.eq(&other.timestamp) - } -} - impl TryFrom<&[u8]> for Tx { type Error = Error; @@ -217,24 +230,18 @@ impl TryFrom<&[u8]> for Tx { Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, None => return Err(Error::NoTimestampError), }; - let inner_tx = match tx.inner_tx { - Some(x) => Some( - BorshDeserialize::try_from_slice(&x) - .expect("Unable to deserialize encrypted transactions") - ), - None => None, - }; - let inner_tx_code = match tx.inner_tx_code { - Some(x) => Some( - BorshDeserialize::try_from_slice(&x) - .expect("Unable to deserialize encrypted transactions") - ), - None => None, - }; - let code = if tx.is_literal { - TxCode::Literal(tx.code) - } else { + let inner_tx = tx.inner_tx.map( + |x| BorshDeserialize::try_from_slice(&x) + .map_err(Error::TxDeserializingError) + ).transpose()?; + let inner_tx_code = tx.inner_tx_code.map( + |x| BorshDeserialize::try_from_slice(&x) + .map_err(Error::TxDeserializingError) + ).transpose()?; + let code = if tx.is_code_hash { TxCode::Hash(tx.code.try_into().expect("Unable to deserialize code hash")) + } else { + TxCode::Literal(tx.code) }; Ok(Tx { code, @@ -249,20 +256,13 @@ impl TryFrom<&[u8]> for Tx { impl From for types::Tx { fn from(tx: Tx) -> Self { let timestamp = Some(tx.timestamp.into()); - let inner_tx = if let Some(inner_tx) = tx.inner_tx { - Some(inner_tx.try_to_vec().expect("Unable to serialize encrypted transaction")) - } else { - None - }; - let inner_tx_code = if let Some(inner_tx_code) = tx.inner_tx_code { - Some(inner_tx_code.try_to_vec().expect("Unable to serialize encrypted transaction")) - } else { - None - }; - + let inner_tx = tx.inner_tx + .map(|x| x.try_to_vec().expect("Unable to serialize encrypted transaction")); + let inner_tx_code = tx.inner_tx_code + .map(|x| x.try_to_vec().expect("Unable to serialize encrypted transaction code")); types::Tx { code: tx.code.code().unwrap_or(tx.code.code_hash().to_vec()), - is_literal: tx.code.is_literal(), + is_code_hash: tx.code.is_hash(), data: tx.data, timestamp, inner_tx, @@ -398,12 +398,14 @@ impl Tx { bytes } - pub fn hash(&self) -> [u8; 32] { + /// Hash this transaction leaving out the inner tx and its code, but instead + /// of including the transaction code in the hash, include its hash instead + pub fn partial_hash(&self) -> [u8; 32] { let timestamp = Some(self.timestamp.into()); let mut bytes = vec![]; types::Tx { code: self.code.code_hash().to_vec(), - is_literal: false, + is_code_hash: true, data: self.data.clone(), timestamp, inner_tx: None, @@ -414,13 +416,14 @@ impl Tx { hash_tx(&bytes).0 } + /// Get the hash of this transaction's code pub fn code_hash(&self) -> [u8; 32] { self.code.code_hash() } /// Sign a transaction using [`SignedTxData`]. pub fn sign(self, keypair: &common::SecretKey) -> Self { - let to_sign = self.hash(); + let to_sign = self.partial_hash(); let sig = common::SigScheme::sign(keypair, to_sign); let signed = SignedTxData { data: self.data, @@ -456,10 +459,12 @@ impl Tx { inner_tx: self.inner_tx.clone(), inner_tx_code: self.inner_tx_code.clone(), }; - let signed_data = tx.hash(); + let signed_data = tx.partial_hash(); common::SigScheme::verify_signature_raw(pk, &signed_data, sig) } + /// Attach the given transaction to this one. Useful when the data field + /// contains a WrapperTx and its tx_hash field needs a witness. #[cfg(feature = "ferveo-tpke")] pub fn attach_inner_tx( mut self, @@ -471,6 +476,8 @@ impl Tx { self } + /// Attach the given transaction code to this one. Useful when the inner_tx + /// field contains a Tx and its code field needs a witness. #[cfg(feature = "ferveo-tpke")] pub fn attach_inner_tx_code( mut self, @@ -481,6 +488,27 @@ impl Tx { self.inner_tx_code = Some(inner_tx_code); self } + + /// A validity check on the ciphertext. + #[cfg(feature = "ferveo-tpke")] + pub fn validate_ciphertext(&self) -> bool { + let mut valid = true; + // Check the inner_tx ciphertext if it is there + if let Some(inner_tx) = &self.inner_tx { + valid = valid && + inner_tx.0.check(&::G1Prepared::from( + -::G1Affine::prime_subgroup_generator(), + )); + } + // Check the inner_tx_code ciphertext if it is there + if let Some(inner_tx_code) = &self.inner_tx_code { + valid = valid && + inner_tx_code.0.check(&::G1Prepared::from( + -::G1Affine::prime_subgroup_generator(), + )); + } + valid + } } #[allow(dead_code)] @@ -574,7 +602,7 @@ mod tests { let types_tx = types::Tx { code: code.clone(), - is_literal: true, + is_code_hash: false, data: Some(data), timestamp: None, inner_tx: None, diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 66c6d199b4..bc34861a25 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -39,6 +39,8 @@ pub mod decrypted_tx { }, /// The wrapper whose payload could not be decrypted Undecryptable(WrapperTx), + /// The tx whose could not be decrypted + UndecryptableCode(Tx), } impl DecryptedTx { @@ -52,7 +54,10 @@ pub mod decrypted_tx { } => tx.to_bytes(), DecryptedTx::Undecryptable(wrapper) => { wrapper.try_to_vec().unwrap() - } + }, + DecryptedTx::UndecryptableCode(tx) => { + tx.to_bytes() + }, } } @@ -64,8 +69,9 @@ pub mod decrypted_tx { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, - } => Hash(tx.hash()), + } => Hash(tx.partial_hash()), DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), + DecryptedTx::UndecryptableCode(tx) => Hash(tx.partial_hash()), } } } @@ -76,11 +82,23 @@ pub mod decrypted_tx { pub fn verify_decrypted_correctly( decrypted: &DecryptedTx, privkey: ::G2Affine, - inner_tx: EncryptedTx, + inner_tx: Option, + inner_tx_code: Option, ) -> bool { match decrypted { - DecryptedTx::Decrypted { .. } => true, - DecryptedTx::Undecryptable(tx) => tx.decrypt(privkey, inner_tx).is_err(), + // A tx is decryptable if it contains the literal code inside it + DecryptedTx::Decrypted { tx, .. } => tx.code.is_literal(), + // A tx is undecryptable if its inner_tx decrypts incorrectly + DecryptedTx::Undecryptable(tx) if inner_tx.is_some() => + tx.decrypt(privkey, inner_tx.unwrap()).is_err(), + // A tx is undecryptable if the inner_tx is not present + DecryptedTx::Undecryptable(_) => true, + // A code is undecryptable if its inner_tx_code decrypts incorrectly + DecryptedTx::UndecryptableCode(tx) if inner_tx_code.is_some() => + tx.decrypt_code(privkey, inner_tx_code.unwrap()).is_none(), + // A code is undecryptable if the literal code is not present + DecryptedTx::UndecryptableCode(tx) => + !tx.code.is_literal(), } } diff --git a/core/src/types/transaction/encrypted.rs b/core/src/types/transaction/encrypted.rs index b73868bbba..406ddac626 100644 --- a/core/src/types/transaction/encrypted.rs +++ b/core/src/types/transaction/encrypted.rs @@ -66,6 +66,15 @@ pub mod encrypted_tx { } } + impl PartialEq for EncryptedTx { + fn eq(&self, other: &EncryptedTx) -> bool { + self.try_to_vec().expect("Unable to serialize encrypted transaction") == + other.try_to_vec().expect("Unable to serialize encrypted transaction") + } + } + + impl Eq for EncryptedTx {} + impl borsh::ser::BorshSerialize for EncryptedTx { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { let Ciphertext { @@ -130,7 +139,7 @@ pub mod encrypted_tx { /// A helper struct for serializing EncryptedTx structs /// as an opaque blob - #[derive(Clone, Debug, Serialize, Deserialize)] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(transparent)] struct SerializedCiphertext { payload: Vec, diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 3d1333a4d6..bad39794af 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -299,7 +299,7 @@ pub mod tx_types { inner_tx: tx.inner_tx.clone(), inner_tx_code: tx.inner_tx_code.clone(), } - .hash(); + .partial_hash(); match TxType::try_from(Tx { code: tx.code, data: Some(data), diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index e8235a6b2d..39499c0be0 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -18,7 +18,7 @@ pub mod wrapper_tx { use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; use crate::types::transaction::{ - hash_tx, Hash, TxError, TxType, + Hash, TxError, TxType, }; /// Minimum fee amount in micro NAMs @@ -212,13 +212,6 @@ pub mod wrapper_tx { Address::from(&self.pk) } - /// A validity check on the ciphertext. - pub fn validate_ciphertext(inner_tx: EncryptedTx) -> bool { - inner_tx.0.check(&::G1Prepared::from( - -::G1Affine::prime_subgroup_generator(), - )) - } - /// Decrypt the wrapped transaction. /// /// Will fail if the inner transaction does match the @@ -235,7 +228,7 @@ pub mod wrapper_tx { let decrypted_tx = Tx::try_from(decrypted.as_ref()) .map_err(|_| WrapperTxErr::InvalidTx)?; // check that the hash equals commitment - if decrypted_tx.hash() != self.tx_hash.0 { + if decrypted_tx.partial_hash() != self.tx_hash.0 { Err(WrapperTxErr::DecryptedHash) } else { Ok(decrypted_tx) @@ -244,7 +237,7 @@ pub mod wrapper_tx { /// Bind the given transaction to this wrapper by recording its hash pub fn bind(mut self, tx: Tx) -> Self { - self.tx_hash = Hash(tx.hash()); + self.tx_hash = Hash(tx.partial_hash()); self } @@ -369,7 +362,6 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ); - let encrypted_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); let wrapper = WrapperTx::new( Fee { @@ -382,8 +374,12 @@ pub mod wrapper_tx { #[cfg(not(feature = "mainnet"))] None, ).bind(tx.clone()); - assert!(WrapperTx::validate_ciphertext(encrypted_tx.clone())); + let stx = wrapper.sign(&keypair) + .expect("unable to sign wrapper") + .attach_inner_tx(&tx, Default::default()); + assert!(stx.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); + let encrypted_tx = stx.inner_tx.expect("inner tx was not attached"); let decrypted = wrapper.decrypt(privkey, encrypted_tx).expect("Test failed"); assert_eq!(tx, decrypted); } @@ -392,18 +388,18 @@ pub mod wrapper_tx { /// does not match the commitment, an error is returned #[test] fn test_decryption_invalid_hash() { + let keypair = gen_keypair(); let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ); - let encrypted_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); let mut wrapper = WrapperTx::new( Fee { amount: 10.into(), token: nam(), }, - &gen_keypair(), + &keypair, Epoch(0), 0.into(), #[cfg(not(feature = "mainnet"))] @@ -411,8 +407,12 @@ pub mod wrapper_tx { ); // give a incorrect commitment to the decrypted contents of the tx wrapper.tx_hash = Hash([0u8; 32]); - assert!(WrapperTx::validate_ciphertext(encrypted_tx.clone())); + let stx = wrapper.sign(&keypair) + .expect("unable to sign wrapper") + .attach_inner_tx(&tx, Default::default()); + assert!(stx.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); + let encrypted_tx = stx.inner_tx.expect("inner tx was not attached"); let err = wrapper.decrypt(privkey, encrypted_tx).expect_err("Test failed"); assert_matches!(err, WrapperTxErr::DecryptedHash); } @@ -457,7 +457,7 @@ pub mod wrapper_tx { }; let mut signed_tx_data = - SignedTxData::try_from_slice(&tx.data.unwrap()[..]) + SignedTxData::try_from_slice(&tx.data.as_ref().unwrap()[..]) .expect("Test failed"); // malicious transaction @@ -471,10 +471,10 @@ pub mod wrapper_tx { ); // We change the commitment appropriately - wrapper.tx_hash = Hash(malicious.hash()); + wrapper.tx_hash = Hash(malicious.partial_hash()); // we check ciphertext validity still passes - assert!(WrapperTx::validate_ciphertext(inner_tx.clone())); + assert!(tx.validate_ciphertext()); // we check that decryption still succeeds let decrypted = wrapper.decrypt( ::G2Affine::prime_subgroup_generator(), diff --git a/proto/types.proto b/proto/types.proto index 49c2d53442..a7e89d9b03 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -11,7 +11,7 @@ message Tx { google.protobuf.Timestamp timestamp = 3; optional bytes inner_tx = 4; optional bytes inner_tx_code = 5; - bool is_literal = 6; + bool is_code_hash = 6; } message Dkg { string data = 1; } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 05ff742b1e..79a5b72b0f 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -161,8 +161,10 @@ where H: 'static + StorageHasher + Sync, CA: 'static + WasmCacheAccess + Sync, { + // prepare_proposal ensures that the code field is populated + let tx_code = tx.code.code().expect("tx code not present"); gas_meter - .add_compiling_fee(tx.code.code().expect("tx code not present").len()) + .add_compiling_fee(tx_code.len()) .map_err(Error::GasError)?; let empty = vec![]; let tx_data = tx.data.as_ref().unwrap_or(&empty); @@ -171,7 +173,7 @@ where write_log, gas_meter, tx_index, - &tx.code.code().expect("tx code not present"), + &tx_code, tx_data, vp_wasm_cache, tx_wasm_cache, From 2481db5d4d4f0966fb6af3d20d3e1bd55fcaa2ad Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 6 Feb 2023 10:51:16 +0200 Subject: [PATCH 05/10] Fixed formatting and clippy. --- .../improvements/1093-signable-txs.md | 2 + apps/src/lib/client/rpc.rs | 18 ++-- apps/src/lib/client/signing.rs | 4 +- .../lib/node/ledger/shell/finalize_block.rs | 29 ++++-- apps/src/lib/node/ledger/shell/mod.rs | 8 +- .../lib/node/ledger/shell/prepare_proposal.rs | 96 ++++++++++++------- .../lib/node/ledger/shell/process_proposal.rs | 76 +++++++++------ core/src/proto/types.rs | 96 ++++++++++++------- core/src/types/internal.rs | 1 + core/src/types/transaction/decrypted.rs | 21 ++-- core/src/types/transaction/encrypted.rs | 7 +- core/src/types/transaction/mod.rs | 11 ++- core/src/types/transaction/wrapper.rs | 28 +++--- tests/src/vm_host_env/mod.rs | 2 +- 14 files changed, 243 insertions(+), 156 deletions(-) create mode 100644 .changelog/unreleased/improvements/1093-signable-txs.md diff --git a/.changelog/unreleased/improvements/1093-signable-txs.md b/.changelog/unreleased/improvements/1093-signable-txs.md new file mode 100644 index 0000000000..e1e244bd0f --- /dev/null +++ b/.changelog/unreleased/improvements/1093-signable-txs.md @@ -0,0 +1,2 @@ +- Make Namada transactions signable on hardware constrained wallets by making + them smaller. ([#1093](https://github.com/anoma/namada/pull/1093)) \ No newline at end of file diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index bbe4d6e3c1..488b7e07cd 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -390,14 +390,18 @@ fn extract_payload( Ok(TxType::Wrapper(wrapper_tx)) => { let privkey = ::G2Affine::prime_subgroup_generator(); extract_payload( - Tx::from(match tx.inner_tx.and_then(|inner_tx| wrapper_tx.decrypt(privkey, inner_tx).ok()) { - Some(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, + Tx::from( + match tx.inner_tx.and_then(|inner_tx| { + wrapper_tx.decrypt(privkey, inner_tx).ok() + }) { + Some(tx) => DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + }, + _ => DecryptedTx::Undecryptable(wrapper_tx.clone()), }, - _ => DecryptedTx::Undecryptable(wrapper_tx.clone()), - }), + ), wrapper, transfer, ); diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 49b2722acb..09c73294f7 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -253,8 +253,8 @@ pub async fn sign_wrapper( #[cfg(not(feature = "mainnet"))] pow_solution, ) - // Bind the inner transaction to the wrapper - .bind(tx.clone()) + // Bind the inner transaction to the wrapper + .bind(tx.clone()) }; // Then sign over the bound wrapper let mut stx = wrapper_tx diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 6002201b8b..e80313aa6c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -239,7 +239,8 @@ where .to_string(), ); } - DecryptedTx::Undecryptable(_) | DecryptedTx::UndecryptableCode(_) => { + DecryptedTx::Undecryptable(_) + | DecryptedTx::UndecryptableCode(_) => { event["log"] = "Transaction could not be decrypted.".into(); event["code"] = ErrorCodes::Undecryptable.into(); @@ -455,8 +456,8 @@ where #[cfg(test)] mod test_finalize_block { use namada::types::storage::Epoch; - use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; use namada::types::transaction::encrypted::EncryptedTx; + use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; use super::*; use crate::node::ledger::shell::test_utils::*; @@ -500,7 +501,8 @@ mod test_finalize_block { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(raw_tx.clone()); + ) + .bind(raw_tx.clone()); let tx = wrapper .sign(&keypair) .expect("Test failed") @@ -515,7 +517,11 @@ mod test_finalize_block { }, }); } else { - shell.enqueue_tx(wrapper.clone(), tx.inner_tx, tx.inner_tx_code); + shell.enqueue_tx( + wrapper.clone(), + tx.inner_tx, + tx.inner_tx_code, + ); } if i != 3 { @@ -564,7 +570,8 @@ mod test_finalize_block { "wasm_code".as_bytes().to_owned(), Some(String::from("transaction data").as_bytes().to_owned()), ); - let encrypted_raw_tx = EncryptedTx::encrypt(&raw_tx.to_bytes(), Default::default()); + let encrypted_raw_tx = + EncryptedTx::encrypt(&raw_tx.to_bytes(), Default::default()); let wrapper = WrapperTx::new( Fee { amount: 0.into(), @@ -575,7 +582,8 @@ mod test_finalize_block { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(raw_tx.clone()); + ) + .bind(raw_tx.clone()); let processed_tx = ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { @@ -698,7 +706,8 @@ mod test_finalize_block { .to_owned(), ), ); - let encrypted_raw_tx = EncryptedTx::encrypt(&raw_tx.to_bytes(), Default::default()); + let encrypted_raw_tx = + EncryptedTx::encrypt(&raw_tx.to_bytes(), Default::default()); let wrapper_tx = WrapperTx::new( Fee { amount: MIN_FEE.into(), @@ -709,7 +718,8 @@ mod test_finalize_block { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(raw_tx.clone()); + ) + .bind(raw_tx.clone()); shell.enqueue_tx(wrapper_tx, Some(encrypted_raw_tx), None); processed_txs.push(ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { @@ -744,7 +754,8 @@ mod test_finalize_block { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(raw_tx.clone()); + ) + .bind(raw_tx.clone()); let wrapper = wrapper_tx .sign(&keypair) .expect("Test failed") diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2c5e81b876..2094fb65ef 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -773,8 +773,8 @@ mod test_utils { use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; - use namada::types::transaction::{Fee, WrapperTx}; use namada::types::transaction::encrypted::EncryptedTx; + use namada::types::transaction::{Fee, WrapperTx}; use tempfile::tempdir; use tokio::sync::mpsc::UnboundedReceiver; @@ -1000,7 +1000,8 @@ mod test_utils { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ); - let encrypted_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); + let encrypted_tx = + EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); let wrapper = WrapperTx::new( Fee { amount: 0.into(), @@ -1011,7 +1012,8 @@ mod test_utils { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(tx); + ) + .bind(tx); shell.storage.tx_queue.push(WrapperTxInQueue { tx: wrapper, inner_tx: Some(encrypted_tx), diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 9da03d6120..d9e86f0753 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -100,22 +100,52 @@ where // decrypt the wrapper txs included in the previous block let decrypted_txs = self.storage.tx_queue.iter().map( |WrapperTxInQueue { - tx, - inner_tx, - inner_tx_code, + tx, + inner_tx, + inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow, }| { - Tx::from(match inner_tx.clone().and_then(|x| tx.decrypt(privkey, x).ok()) { - Some(mut inner_tx) => { - if let Some(inner_tx_code) = inner_tx_code { - if let Some(inner_tx_code) = - inner_tx.decrypt_code(privkey, inner_tx_code.clone()) { - // Embed the inner_tx_code inside the tx for - // future processing - inner_tx.code.expand(inner_tx_code) - .expect("decrypted code should have correct hash"); - // An inner_tx_code consistent with code is + Tx::from( + match inner_tx + .clone() + .and_then(|x| tx.decrypt(privkey, x).ok()) + { + Some(mut inner_tx) => { + if let Some(inner_tx_code) = inner_tx_code { + if let Some(inner_tx_code) = inner_tx + .decrypt_code( + privkey, + inner_tx_code.clone(), + ) + { + // Embed the inner_tx_code inside the tx + // for + // future processing + inner_tx + .code + .expand(inner_tx_code) + .expect( + "decrypted code should have \ + correct hash", + ); + // An inner_tx_code consistent with code + // is + // treated as a successful decryption + DecryptedTx::Decrypted { + tx: inner_tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + } + } else { + // A failure to decrypt an inner_tx_code + // is + // treated as undecryptable + DecryptedTx::UndecryptableCode(inner_tx) + } + } else if inner_tx.code.is_literal() { + // A literal code without an inner_tx_code + // is // treated as a successful decryption DecryptedTx::Decrypted { tx: inner_tx, @@ -123,28 +153,19 @@ where has_valid_pow: *has_valid_pow, } } else { - // A failure to decrypt an inner_tx_code is - // treated as undecryptable + // The code being absent from both inner_tx + // and + // the code field is treated as + // undecryptable DecryptedTx::UndecryptableCode(inner_tx) } - } else if inner_tx.code.is_literal() { - // A literal code without an inner_tx_code is - // treated as a successful decryption - DecryptedTx::Decrypted { - tx: inner_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, - } - } else { - // The code being absent from both inner_tx and - // the code field is treated as undecryptable - DecryptedTx::UndecryptableCode(inner_tx) } + // An absent or undecryptable inner_tx are both + // treated + // as undecryptable + None => DecryptedTx::Undecryptable(tx.clone()), }, - // An absent or undecryptable inner_tx are both treated - // as undecryptable - None => DecryptedTx::Undecryptable(tx.clone()), - }) + ) .to_bytes() }, ); @@ -266,13 +287,13 @@ mod test_prepare_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .bind(tx.clone()) - .try_to_vec() - .expect("Test failed"), + .bind(tx.clone()) + .try_to_vec() + .expect("Test failed"), ), ) - .attach_inner_tx(&tx, Default::default()) - .to_bytes(); + .attach_inner_tx(&tx, Default::default()) + .to_bytes(); #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { txs: vec![wrapper.clone()], @@ -325,7 +346,8 @@ mod test_prepare_proposal { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(tx.clone()); + ) + .bind(tx.clone()); let wrapper = wrapper_tx .sign(&keypair) .expect("Test failed") diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index aa082aa5e3..f69e453cb8 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -124,11 +124,11 @@ where .into(), } } else if verify_decrypted_correctly( - &tx, - privkey, - inner_tx.clone(), - inner_tx_code.clone(), - ) { + &tx, + privkey, + inner_tx.clone(), + inner_tx_code.clone(), + ) { TxResult { code: ErrorCodes::Ok.into(), info: "Process Proposal accepted this \ @@ -218,14 +218,13 @@ where #[cfg(test)] mod test_process_proposal { use borsh::BorshDeserialize; - use namada::proto::SignedTxData; + use namada::proto::{SignedTxData, TxCode}; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx}; - use namada::proto::TxCode; use super::*; use crate::facade::tendermint_proto::abci::RequestInitChain; @@ -254,13 +253,14 @@ mod test_process_proposal { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(tx.clone()); + ) + .bind(tx.clone()); let tx = Tx::new( vec![], Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), ) - .attach_inner_tx(&tx, Default::default()) - .to_bytes(); + .attach_inner_tx(&tx, Default::default()) + .to_bytes(); #[allow(clippy::redundant_clone)] let request = ProcessProposal { txs: vec![tx.clone()], @@ -304,11 +304,12 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .bind(tx.clone()) - .sign(&keypair) - .expect("Test failed"); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed"); let inner_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); - let inner_tx_code = EncryptedTx::encrypt(&inner_tx_code, Default::default()); + let inner_tx_code = + EncryptedTx::encrypt(&inner_tx_code, Default::default()); let new_tx = if let Some(Ok(SignedTxData { data: Some(data), sig, @@ -391,10 +392,10 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .bind(tx.clone()) - .sign(&keypair) - .expect("Test failed") - .attach_inner_tx(&tx, Default::default()); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], }; @@ -447,10 +448,10 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .bind(tx.clone()) - .sign(&keypair) - .expect("Test failed") - .attach_inner_tx(&tx, Default::default()); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], @@ -486,7 +487,8 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), ); - let encrypted_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); + let encrypted_tx = + EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); let wrapper = WrapperTx::new( Fee { amount: i.into(), @@ -497,7 +499,8 @@ mod test_process_proposal { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(tx.clone()); + ) + .bind(tx.clone()); shell.enqueue_tx(wrapper, Some(encrypted_tx.clone()), None); txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx, @@ -565,13 +568,19 @@ mod test_process_proposal { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(tx.clone()); + ) + .bind(tx.clone()); - let tx = - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper.clone()))) - .attach_inner_tx(&tx, Default::default()); + let tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( + wrapper.clone(), + ))) + .attach_inner_tx(&tx, Default::default()); - shell.enqueue_tx(wrapper.clone(), tx.inner_tx.clone(), tx.inner_tx_code.clone()); + shell.enqueue_tx( + wrapper, + tx.inner_tx.clone(), + tx.inner_tx_code.clone(), + ); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -632,9 +641,14 @@ mod test_process_proposal { let tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] wrapper.clone(), - ))).attach_inner_tx(&tx, Default::default()); + ))) + .attach_inner_tx(&tx, Default::default()); - shell.enqueue_tx(wrapper.clone(), tx.inner_tx.clone(), tx.inner_tx_code.clone()); + shell.enqueue_tx( + wrapper, + tx.inner_tx.clone(), + tx.inner_tx_code.clone(), + ); let request = ProcessProposal { txs: vec![tx.to_bytes()], diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 62d1ce543b..4b241b9067 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -1,6 +1,10 @@ use std::convert::{TryFrom, TryInto}; use std::hash::{Hash, Hasher}; +#[cfg(feature = "ferveo-tpke")] +use ark_ec::AffineCurve; +#[cfg(feature = "ferveo-tpke")] +use ark_ec::PairingEngine; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use prost::Message; use serde::{Deserialize, Serialize}; @@ -13,23 +17,19 @@ use crate::types::key::*; use crate::types::time::DateTimeUtc; #[cfg(feature = "ferveo-tpke")] use crate::types::token::Transfer; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::encrypted::EncryptedTx; use crate::types::transaction::hash_tx; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::process_tx; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::DecryptedTx; #[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::TxType; -#[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::encrypted::EncryptedTx; -#[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::EncryptionKey; -#[cfg(feature = "ferveo-tpke")] use crate::types::transaction::EllipticCurve; #[cfg(feature = "ferveo-tpke")] -use ark_ec::PairingEngine; +use crate::types::transaction::EncryptionKey; #[cfg(feature = "ferveo-tpke")] -use ark_ec::AffineCurve; +use crate::types::transaction::TxType; #[derive(Error, Debug)] pub enum Error { @@ -145,7 +145,14 @@ pub struct InvalidCodeError; /// only need the hash. Also useful for cases when passing full transaction code /// around separately from their transactions is cumbersome. #[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Hash, PartialEq, Eq, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Hash, + PartialEq, + Eq, )] pub enum TxCode { /// A hash of transaction code @@ -162,16 +169,21 @@ impl TxCode { Self::Literal(lit) => Some(lit.clone()), } } + /// Return the transaction code hash pub fn code_hash(&self) -> [u8; 32] { match self { - Self::Hash(hash) => hash.clone(), + Self::Hash(hash) => *hash, Self::Literal(lit) => hash_tx(lit).0, } } + /// Expand this reduced Tx using the supplied code only if the the code /// hashes to the stored code hash - pub fn expand(&mut self, code: Vec) -> std::result::Result<(), InvalidCodeError> { + pub fn expand( + &mut self, + code: Vec, + ) -> std::result::Result<(), InvalidCodeError> { if hash_tx(&code).0 == self.code_hash() { *self = TxCode::Literal(code); Ok(()) @@ -179,23 +191,20 @@ impl TxCode { Err(InvalidCodeError) } } + /// Replace a literal code with its hash pub fn contract(&mut self) { *self = TxCode::Hash(self.code_hash()); } + /// Indicates that this object contains the full code pub fn is_literal(&self) -> bool { - match self { - Self::Literal(_) => true, - _ => false, - } + matches!(self, Self::Literal(_)) } + /// Indicates that this object only contains the hash of the code pub fn is_hash(&self) -> bool { - match self { - Self::Hash(_) => true, - _ => false, - } + matches!(self, Self::Hash(_)) } } @@ -230,16 +239,24 @@ impl TryFrom<&[u8]> for Tx { Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, None => return Err(Error::NoTimestampError), }; - let inner_tx = tx.inner_tx.map( - |x| BorshDeserialize::try_from_slice(&x) - .map_err(Error::TxDeserializingError) - ).transpose()?; - let inner_tx_code = tx.inner_tx_code.map( - |x| BorshDeserialize::try_from_slice(&x) - .map_err(Error::TxDeserializingError) - ).transpose()?; + let inner_tx = tx + .inner_tx + .map(|x| { + BorshDeserialize::try_from_slice(&x) + .map_err(Error::TxDeserializingError) + }) + .transpose()?; + let inner_tx_code = tx + .inner_tx_code + .map(|x| { + BorshDeserialize::try_from_slice(&x) + .map_err(Error::TxDeserializingError) + }) + .transpose()?; let code = if tx.is_code_hash { - TxCode::Hash(tx.code.try_into().expect("Unable to deserialize code hash")) + TxCode::Hash( + tx.code.try_into().expect("Unable to deserialize code hash"), + ) } else { TxCode::Literal(tx.code) }; @@ -256,12 +273,19 @@ impl TryFrom<&[u8]> for Tx { impl From for types::Tx { fn from(tx: Tx) -> Self { let timestamp = Some(tx.timestamp.into()); - let inner_tx = tx.inner_tx - .map(|x| x.try_to_vec().expect("Unable to serialize encrypted transaction")); - let inner_tx_code = tx.inner_tx_code - .map(|x| x.try_to_vec().expect("Unable to serialize encrypted transaction code")); + let inner_tx = tx.inner_tx.map(|x| { + x.try_to_vec() + .expect("Unable to serialize encrypted transaction") + }); + let inner_tx_code = tx.inner_tx_code.map(|x| { + x.try_to_vec() + .expect("Unable to serialize encrypted transaction code") + }); types::Tx { - code: tx.code.code().unwrap_or(tx.code.code_hash().to_vec()), + code: tx + .code + .code() + .unwrap_or_else(|| tx.code.code_hash().to_vec()), is_code_hash: tx.code.is_hash(), data: tx.data, timestamp, @@ -469,7 +493,7 @@ impl Tx { pub fn attach_inner_tx( mut self, tx: &Tx, - encryption_key: EncryptionKey + encryption_key: EncryptionKey, ) -> Self { let inner_tx = EncryptedTx::encrypt(&tx.to_bytes(), encryption_key); self.inner_tx = Some(inner_tx); @@ -482,7 +506,7 @@ impl Tx { pub fn attach_inner_tx_code( mut self, tx: &[u8], - encryption_key: EncryptionKey + encryption_key: EncryptionKey, ) -> Self { let inner_tx_code = EncryptedTx::encrypt(tx, encryption_key); self.inner_tx_code = Some(inner_tx_code); @@ -601,7 +625,7 @@ mod tests { assert_eq!(tx_from_bytes, tx); let types_tx = types::Tx { - code: code.clone(), + code, is_code_hash: false, data: Some(data), timestamp: None, diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index fa3771a971..4e320b904a 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -47,6 +47,7 @@ impl From for HostEnvResult { #[cfg(feature = "ferveo-tpke")] mod tx_queue { use borsh::{BorshDeserialize, BorshSerialize}; + use crate::types::transaction::encrypted::EncryptedTx; /// A wrapper for `crate::types::transaction::WrapperTx` to conditionally diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index bc34861a25..ce355a25b1 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -11,8 +11,8 @@ pub mod decrypted_tx { use super::EllipticCurve; use crate::proto::Tx; - use crate::types::transaction::{Hash, TxType, WrapperTx}; use crate::types::transaction::encrypted::EncryptedTx; + use crate::types::transaction::{Hash, TxType, WrapperTx}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[allow(clippy::large_enum_variant)] @@ -54,10 +54,8 @@ pub mod decrypted_tx { } => tx.to_bytes(), DecryptedTx::Undecryptable(wrapper) => { wrapper.try_to_vec().unwrap() - }, - DecryptedTx::UndecryptableCode(tx) => { - tx.to_bytes() - }, + } + DecryptedTx::UndecryptableCode(tx) => tx.to_bytes(), } } @@ -89,16 +87,17 @@ pub mod decrypted_tx { // A tx is decryptable if it contains the literal code inside it DecryptedTx::Decrypted { tx, .. } => tx.code.is_literal(), // A tx is undecryptable if its inner_tx decrypts incorrectly - DecryptedTx::Undecryptable(tx) if inner_tx.is_some() => - tx.decrypt(privkey, inner_tx.unwrap()).is_err(), + DecryptedTx::Undecryptable(tx) if inner_tx.is_some() => { + tx.decrypt(privkey, inner_tx.unwrap()).is_err() + } // A tx is undecryptable if the inner_tx is not present DecryptedTx::Undecryptable(_) => true, // A code is undecryptable if its inner_tx_code decrypts incorrectly - DecryptedTx::UndecryptableCode(tx) if inner_tx_code.is_some() => - tx.decrypt_code(privkey, inner_tx_code.unwrap()).is_none(), + DecryptedTx::UndecryptableCode(tx) if inner_tx_code.is_some() => { + tx.decrypt_code(privkey, inner_tx_code.unwrap()).is_none() + } // A code is undecryptable if the literal code is not present - DecryptedTx::UndecryptableCode(tx) => - !tx.code.is_literal(), + DecryptedTx::UndecryptableCode(tx) => !tx.code.is_literal(), } } diff --git a/core/src/types/transaction/encrypted.rs b/core/src/types/transaction/encrypted.rs index 406ddac626..5cfc6b64ba 100644 --- a/core/src/types/transaction/encrypted.rs +++ b/core/src/types/transaction/encrypted.rs @@ -68,8 +68,11 @@ pub mod encrypted_tx { impl PartialEq for EncryptedTx { fn eq(&self, other: &EncryptedTx) -> bool { - self.try_to_vec().expect("Unable to serialize encrypted transaction") == - other.try_to_vec().expect("Unable to serialize encrypted transaction") + self.try_to_vec() + .expect("Unable to serialize encrypted transaction") + == other + .try_to_vec() + .expect("Unable to serialize encrypted transaction") } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index bad39794af..474dbcb36d 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -436,10 +436,10 @@ pub mod tx_types { #[cfg(not(feature = "mainnet"))] None, ) - .bind(tx.clone()) - .sign(&keypair) - .expect("Test failed") - .attach_inner_tx(&tx, Default::default()); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); match process_tx(wrapper_tx.clone()).expect("Test failed") { TxType::Wrapper(wrapper) => { @@ -479,7 +479,8 @@ pub mod tx_types { Some( TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), ), - ).attach_inner_tx(&inner_tx, Default::default()); + ) + .attach_inner_tx(&inner_tx, Default::default()); let result = process_tx(tx).expect_err("Test failed"); assert_matches!(result, TxError::Unsigned(_)); } diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 39499c0be0..82e6c341e7 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -17,9 +17,7 @@ pub mod wrapper_tx { use crate::types::storage::Epoch; use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; - use crate::types::transaction::{ - Hash, TxError, TxType, - }; + use crate::types::transaction::{Hash, TxError, TxType}; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -373,14 +371,17 @@ pub mod wrapper_tx { 0.into(), #[cfg(not(feature = "mainnet"))] None, - ).bind(tx.clone()); - let stx = wrapper.sign(&keypair) + ) + .bind(tx.clone()); + let stx = wrapper + .sign(&keypair) .expect("unable to sign wrapper") .attach_inner_tx(&tx, Default::default()); assert!(stx.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); let encrypted_tx = stx.inner_tx.expect("inner tx was not attached"); - let decrypted = wrapper.decrypt(privkey, encrypted_tx).expect("Test failed"); + let decrypted = + wrapper.decrypt(privkey, encrypted_tx).expect("Test failed"); assert_eq!(tx, decrypted); } @@ -407,13 +408,16 @@ pub mod wrapper_tx { ); // give a incorrect commitment to the decrypted contents of the tx wrapper.tx_hash = Hash([0u8; 32]); - let stx = wrapper.sign(&keypair) + let stx = wrapper + .sign(&keypair) .expect("unable to sign wrapper") .attach_inner_tx(&tx, Default::default()); assert!(stx.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); let encrypted_tx = stx.inner_tx.expect("inner tx was not attached"); - let err = wrapper.decrypt(privkey, encrypted_tx).expect_err("Test failed"); + let err = wrapper + .decrypt(privkey, encrypted_tx) + .expect_err("Test failed"); assert_matches!(err, WrapperTxErr::DecryptedHash); } @@ -441,10 +445,10 @@ pub mod wrapper_tx { #[cfg(not(feature = "mainnet"))] None, ) - .bind(tx.clone()) - .sign(&keypair) - .expect("Test failed") - .attach_inner_tx(&tx, Default::default()); + .bind(tx.clone()) + .sign(&keypair) + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); // we now try to alter the inner tx maliciously let mut wrapper = if let TxType::Wrapper(wrapper) = diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 88775902de..de0a218353 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -43,9 +43,9 @@ mod tests { use test_log::test; use super::{ibc, tx, vp}; + use crate::namada::proto::TxCode; use crate::tx::{tx_host_env, TestTxEnv}; use crate::vp::{vp_host_env, TestVpEnv}; - use crate::namada::proto::TxCode; // paths to the WASMs used for tests const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; From 00559e9f7cc3cbdb4a93f7303f3f86140acfe5e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 6 Feb 2023 09:53:31 +0000 Subject: [PATCH 06/10] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index c6f62b1f9d..1a8772f692 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.0541f0497f7c14690cf49647c6be0324e5bdd44bf27ffb022dcf68a7f6b7827d.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.3ee9010b89ef090ddaaa21e898fc87257801aa49068818971ecbbe880dd210e0.wasm", - "tx_ibc.wasm": "tx_ibc.0daeeb69727f893a2f12d923e77c878eb7877f81774e8443cb2ed29339e7aac1.wasm", - "tx_init_account.wasm": "tx_init_account.495f96fcefbafa49b3aa1d8f80b984eb626f5fbb4db51c444ea48e900217b186.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.7accc0b5de2ef8f24e8c2efc4975d2eee62015b2a905405f79da8563e9c15b15.wasm", - "tx_init_validator.wasm": "tx_init_validator.34739c6f982ed63e9a6884cf0cc0f8e6ddb45cd2a2db0f35280af7c37830510b.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.f6230e04a8500ad3ae6b376d57b521b83ac16449f021f01019671b0a3ba76a8b.wasm", - "tx_transfer.wasm": "tx_transfer.0baa64bdcc2031d70934531e892bb420fe908d2e3dba4cb84277323d35c26a3e.wasm", - "tx_unbond.wasm": "tx_unbond.72195f598ad3bcc82095e4f33df785bc3af1c02dac276cc5b0aaadef9d8c261f.wasm", - "tx_update_vp.wasm": "tx_update_vp.9f11e9e3cfb35396941db3d7d742aa38a9eeb1e042fab0839fcb9805554b0401.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d944be9d5372c078a9118268e42dd48a43ac0ef4ce401ac6863b88cb647013a3.wasm", - "tx_withdraw.wasm": "tx_withdraw.42cc4c4926f32090964f0fcdf8a86ddfac282947f5710524d6bab2acfd75c147.wasm", - "vp_implicit.wasm": "vp_implicit.81355ce80471010b9db30addedcce27917b06148224f23fdb83e34045480ef27.wasm", - "vp_masp.wasm": "vp_masp.62a3fff9fada661591248738aa1b2cbd22b90fb58a3acb607b5ce8089c363b83.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d7eee970bfd5704a6d969ca54a7afba0fbaf0695bbc12d97764329b6784cad14.wasm", - "vp_token.wasm": "vp_token.b72e854862166c91aac0528b68c64c28b4262cf4def47a821dc90e6597edf1ec.wasm", - "vp_user.wasm": "vp_user.28bcf125b9a8fddc94279c21bee36b879f2b2822184995d2220d739fe66982b7.wasm", - "vp_validator.wasm": "vp_validator.480fe41dc4d84e1cbb4bced845e6fbb2721e1fdadde24b0fb4a420b376213356.wasm" + "tx_bond.wasm": "tx_bond.bd183d3508df5ef6e61e34c8229326aad604e0f7fc1dafc84d48cd6b61614b90.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.23989ccc8f683f872f8661de589f0a0d3556c783ad5635f0758c018ac81fafbe.wasm", + "tx_ibc.wasm": "tx_ibc.6c384de2ceb02fd3a44b38bed5b9e13d6c1977383abea11dc132646bd5ff6747.wasm", + "tx_init_account.wasm": "tx_init_account.01a15a472317dfc5a2e126c56e36ebc2c0ec5cc3e04ea69347eae3b398d898c4.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.780edb2e083f3140fea275d3e277ac33173c1f02167b016463932a8d2134bbb8.wasm", + "tx_init_validator.wasm": "tx_init_validator.29bef4a68fa9512514300f5c3028f11515c7db6e3c683ac9324a95b1af10f7f7.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.d56759ccc7d4b3d432193055c31caff97860353b468559f2408db68d17ff25d6.wasm", + "tx_transfer.wasm": "tx_transfer.40747267f3af116b5579f11995d0cc30cec7bdd3a1135ff16d3c99465e97f409.wasm", + "tx_unbond.wasm": "tx_unbond.893a1aab86db50a93ff4f626a47119640c0915430e93abebbb3b3662dea67da3.wasm", + "tx_update_vp.wasm": "tx_update_vp.6970c0f1809ab183a996f46a46036a2a646a092535994eb6abd72b2d0c46c3c5.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.2362f79241945795318ebb18ad7a4d54edc61edc00d70c151532e10e2361c13b.wasm", + "tx_withdraw.wasm": "tx_withdraw.8a8c718ed8c2f97d7bb37e93fa1fe1edf0be24b9983b23efaacf19d09a4d067e.wasm", + "vp_implicit.wasm": "vp_implicit.de8a20ab779563c9dd886a51c9c69fc69b4da1452da0d186e9a5807b37e9a042.wasm", + "vp_masp.wasm": "vp_masp.3ded845a16e44c4637e6627b38cdf153a9145fae5d4596b75ce1c7b9db8fa229.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.63315ca12161f8abaffe039928e595e7cd63aed620f3febbeaa6f8eb8e22607e.wasm", + "vp_token.wasm": "vp_token.b78955a61249421406f5f9551f96e0c96f71aa6bd02784fb28e844e31d6dbb9f.wasm", + "vp_user.wasm": "vp_user.3ab0f016c4020e2c3260614b28380489068746e57385c923fc55905f9e3c7b88.wasm", + "vp_validator.wasm": "vp_validator.67dddaeac29a89ec4f22e3f9296309ffd9b48284fa52cb9bcd7226d065475adf.wasm" } \ No newline at end of file From 2ba70d9094a0df80773cf8c31c3f10837d696c51 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 13 Feb 2023 11:36:22 +0200 Subject: [PATCH 07/10] Renamed WrapperTxInQueue to TxInQueue to be more accurate. --- apps/src/lib/node/ledger/shell/finalize_block.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 8 ++++---- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 4 ++-- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 +++--- core/src/types/internal.rs | 12 ++++++------ 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e80313aa6c..cd416573fb 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -215,7 +215,7 @@ where } } - self.storage.tx_queue.push(WrapperTxInQueue { + self.storage.tx_queue.push(TxInQueue { tx: wrapper.clone(), inner_tx: tx.inner_tx, inner_tx_code: tx.inner_tx_code, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2094fb65ef..cbe2055665 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -37,7 +37,7 @@ use namada::proto::{self, Tx}; use namada::types::address; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; @@ -353,7 +353,7 @@ where /// Iterate over the wrapper txs in order #[allow(dead_code)] - fn iter_tx_queue(&mut self) -> impl Iterator { + fn iter_tx_queue(&mut self) -> impl Iterator { self.storage.tx_queue.iter() } @@ -928,7 +928,7 @@ mod test_utils { inner_tx: Option, inner_tx_code: Option, ) { - self.shell.storage.tx_queue.push(WrapperTxInQueue { + self.shell.storage.tx_queue.push(TxInQueue { tx: wrapper, inner_tx, inner_tx_code, @@ -1014,7 +1014,7 @@ mod test_utils { None, ) .bind(tx); - shell.storage.tx_queue.push(WrapperTxInQueue { + shell.storage.tx_queue.push(TxInQueue { tx: wrapper, inner_tx: Some(encrypted_tx), inner_tx_code: None, diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d9e86f0753..9aa6ce3792 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,7 +2,7 @@ use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proto::Tx; -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; @@ -99,7 +99,7 @@ where // decrypt the wrapper txs included in the previous block let decrypted_txs = self.storage.tx_queue.iter().map( - |WrapperTxInQueue { + |TxInQueue { tx, inner_tx, inner_tx_code, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index f69e453cb8..b153234dd5 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,7 +1,7 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; @@ -72,7 +72,7 @@ where pub(crate) fn process_single_tx<'a>( &self, tx_bytes: &[u8], - tx_queue_iter: &mut impl Iterator, + tx_queue_iter: &mut impl Iterator, ) -> TxResult { let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, @@ -108,7 +108,7 @@ where .into(), }, TxType::Decrypted(tx) => match tx_queue_iter.next() { - Some(WrapperTxInQueue { + Some(TxInQueue { tx: wrapper, inner_tx, inner_tx_code, diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index 4e320b904a..92a3c747b1 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -53,7 +53,7 @@ mod tx_queue { /// A wrapper for `crate::types::transaction::WrapperTx` to conditionally /// add `has_valid_pow` flag for only used in testnets. #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] - pub struct WrapperTxInQueue { + pub struct TxInQueue { /// Wrapper tx pub tx: crate::types::transaction::WrapperTx, /// the encrypted payload @@ -70,23 +70,23 @@ mod tx_queue { #[derive(Default, Debug, Clone, BorshDeserialize, BorshSerialize)] /// Wrapper txs to be decrypted in the next block proposal - pub struct TxQueue(std::collections::VecDeque); + pub struct TxQueue(std::collections::VecDeque); impl TxQueue { /// Add a new wrapper at the back of the queue - pub fn push(&mut self, wrapper: WrapperTxInQueue) { + pub fn push(&mut self, wrapper: TxInQueue) { self.0.push_back(wrapper); } /// Remove the wrapper at the head of the queue - pub fn pop(&mut self) -> Option { + pub fn pop(&mut self) -> Option { self.0.pop_front() } /// Get an iterator over the queue pub fn iter( &self, - ) -> impl std::iter::Iterator { + ) -> impl std::iter::Iterator { self.0.iter() } @@ -99,4 +99,4 @@ mod tx_queue { } #[cfg(feature = "ferveo-tpke")] -pub use tx_queue::{TxQueue, WrapperTxInQueue}; +pub use tx_queue::{TxQueue, TxInQueue}; From fe8bacec9dff97d572d172c11d0e08ede0c93ef4 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 14 Feb 2023 13:53:45 +0200 Subject: [PATCH 08/10] Removed inner_tx_code since it is redundant when with inner_tx. --- apps/src/lib/client/signing.rs | 14 +- .../lib/node/ledger/shell/finalize_block.rs | 16 +- apps/src/lib/node/ledger/shell/mod.rs | 3 - .../lib/node/ledger/shell/prepare_proposal.rs | 64 +------ .../lib/node/ledger/shell/process_proposal.rs | 34 +--- core/src/proto/mod.rs | 6 +- core/src/proto/types.rs | 175 ++---------------- core/src/types/internal.rs | 8 +- core/src/types/transaction/decrypted.rs | 13 +- core/src/types/transaction/mod.rs | 2 - proto/types.proto | 2 - shared/src/ledger/protocol/mod.rs | 6 +- tests/src/vm_host_env/mod.rs | 85 +++------ tests/src/vm_host_env/tx.rs | 2 +- wasm/checksums.json | 36 ++-- 15 files changed, 88 insertions(+), 378 deletions(-) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 09c73294f7..66076357fc 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -182,7 +182,7 @@ pub async fn sign_wrapper( ctx: &Context, args: &args::Tx, epoch: Epoch, - mut tx: Tx, + tx: Tx, keypair: &common::SecretKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> TxBroadcastData { @@ -236,10 +236,6 @@ pub async fn sign_wrapper( None } }; - // Get transaction code to later attach to a wrapper transaction - let tx_code = tx.code.code(); - // Contract the transaction's code field to make transaction smaller - tx.code.contract(); // This object governs how the payload will be processed let wrapper_tx = { WrapperTx::new( @@ -260,14 +256,6 @@ pub async fn sign_wrapper( let mut stx = wrapper_tx .sign(keypair) .expect("Wrapper tx signing keypair should be correct"); - // If we have the transaction code, then attach it to the wrapper - if let Some(code) = tx_code { - stx = stx.attach_inner_tx_code( - &code, - // TODO: Actually use the fetched encryption key - Default::default(), - ); - } // Then encrypt and attach the payload to the wrapper stx = stx.attach_inner_tx( &tx, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index cd416573fb..26e55161d3 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -218,7 +218,6 @@ where self.storage.tx_queue.push(TxInQueue { tx: wrapper.clone(), inner_tx: tx.inner_tx, - inner_tx_code: tx.inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow, }); @@ -239,8 +238,7 @@ where .to_string(), ); } - DecryptedTx::Undecryptable(_) - | DecryptedTx::UndecryptableCode(_) => { + DecryptedTx::Undecryptable(_) => { event["log"] = "Transaction could not be decrypted.".into(); event["code"] = ErrorCodes::Undecryptable.into(); @@ -517,11 +515,7 @@ mod test_finalize_block { }, }); } else { - shell.enqueue_tx( - wrapper.clone(), - tx.inner_tx, - tx.inner_tx_code, - ); + shell.enqueue_tx(wrapper.clone(), tx.inner_tx); } if i != 3 { @@ -597,7 +591,7 @@ mod test_finalize_block { info: "".into(), }, }; - shell.enqueue_tx(wrapper, Some(encrypted_raw_tx), None); + shell.enqueue_tx(wrapper, Some(encrypted_raw_tx)); // check that the decrypted tx was not applied for event in shell @@ -652,7 +646,7 @@ mod test_finalize_block { }, }; - shell.enqueue_tx(wrapper, Some(inner_tx), None); + shell.enqueue_tx(wrapper, Some(inner_tx)); // check that correct error message is returned for event in shell @@ -720,7 +714,7 @@ mod test_finalize_block { None, ) .bind(raw_tx.clone()); - shell.enqueue_tx(wrapper_tx, Some(encrypted_raw_tx), None); + shell.enqueue_tx(wrapper_tx, Some(encrypted_raw_tx)); processed_txs.push(ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx: raw_tx, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index cbe2055665..1106840c49 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -926,12 +926,10 @@ mod test_utils { &mut self, wrapper: WrapperTx, inner_tx: Option, - inner_tx_code: Option, ) { self.shell.storage.tx_queue.push(TxInQueue { tx: wrapper, inner_tx, - inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, }); @@ -1017,7 +1015,6 @@ mod test_utils { shell.storage.tx_queue.push(TxInQueue { tx: wrapper, inner_tx: Some(encrypted_tx), - inner_tx_code: None, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, }); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 9aa6ce3792..9423d2a13c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -102,7 +102,6 @@ where |TxInQueue { tx, inner_tx, - inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow, }| { @@ -111,58 +110,13 @@ where .clone() .and_then(|x| tx.decrypt(privkey, x).ok()) { - Some(mut inner_tx) => { - if let Some(inner_tx_code) = inner_tx_code { - if let Some(inner_tx_code) = inner_tx - .decrypt_code( - privkey, - inner_tx_code.clone(), - ) - { - // Embed the inner_tx_code inside the tx - // for - // future processing - inner_tx - .code - .expand(inner_tx_code) - .expect( - "decrypted code should have \ - correct hash", - ); - // An inner_tx_code consistent with code - // is - // treated as a successful decryption - DecryptedTx::Decrypted { - tx: inner_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, - } - } else { - // A failure to decrypt an inner_tx_code - // is - // treated as undecryptable - DecryptedTx::UndecryptableCode(inner_tx) - } - } else if inner_tx.code.is_literal() { - // A literal code without an inner_tx_code - // is - // treated as a successful decryption - DecryptedTx::Decrypted { - tx: inner_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, - } - } else { - // The code being absent from both inner_tx - // and - // the code field is treated as - // undecryptable - DecryptedTx::UndecryptableCode(inner_tx) - } - } + Some(inner_tx) => DecryptedTx::Decrypted { + tx: inner_tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + }, // An absent or undecryptable inner_tx are both - // treated - // as undecryptable + // treated as undecryptable None => DecryptedTx::Undecryptable(tx.clone()), }, ) @@ -352,11 +306,7 @@ mod test_prepare_proposal { .sign(&keypair) .expect("Test failed") .attach_inner_tx(&tx, Default::default()); - shell.enqueue_tx( - wrapper_tx, - wrapper.inner_tx.clone(), - wrapper.inner_tx_code.clone(), - ); + shell.enqueue_tx(wrapper_tx, wrapper.inner_tx.clone()); expected_wrapper.push(wrapper.clone()); req.txs.push(wrapper.to_bytes()); } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index b153234dd5..2888b54ea4 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -111,7 +111,6 @@ where Some(TxInQueue { tx: wrapper, inner_tx, - inner_tx_code, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, }) => { @@ -127,7 +126,6 @@ where &tx, privkey, inner_tx.clone(), - inner_tx_code.clone(), ) { TxResult { code: ErrorCodes::Ok.into(), @@ -218,7 +216,7 @@ where #[cfg(test)] mod test_process_proposal { use borsh::BorshDeserialize; - use namada::proto::{SignedTxData, TxCode}; + use namada::proto::SignedTxData; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; @@ -287,9 +285,8 @@ mod test_process_proposal { fn test_wrapper_bad_signature_rejected() { let (mut shell, _) = TestShell::new(); let keypair = gen_keypair(); - let inner_tx_code = "wasm_code".as_bytes().to_owned(); let tx = Tx::new( - inner_tx_code.clone(), + "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ); let timestamp = tx.timestamp; @@ -306,10 +303,8 @@ mod test_process_proposal { ) .bind(tx.clone()) .sign(&keypair) - .expect("Test failed"); - let inner_tx = EncryptedTx::encrypt(&tx.to_bytes(), Default::default()); - let inner_tx_code = - EncryptedTx::encrypt(&inner_tx_code, Default::default()); + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); let new_tx = if let Some(Ok(SignedTxData { data: Some(data), sig, @@ -333,7 +328,7 @@ mod test_process_proposal { .try_to_vec() .expect("Test failed"); Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some( SignedTxData { sig, @@ -343,8 +338,7 @@ mod test_process_proposal { .expect("Test failed"), ), timestamp, - inner_tx: Some(inner_tx), - inner_tx_code: Some(inner_tx_code), + inner_tx: tx.inner_tx, } } else { panic!("Test failed"); @@ -501,7 +495,7 @@ mod test_process_proposal { None, ) .bind(tx.clone()); - shell.enqueue_tx(wrapper, Some(encrypted_tx.clone()), None); + shell.enqueue_tx(wrapper, Some(encrypted_tx.clone())); txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] @@ -576,11 +570,7 @@ mod test_process_proposal { ))) .attach_inner_tx(&tx, Default::default()); - shell.enqueue_tx( - wrapper, - tx.inner_tx.clone(), - tx.inner_tx_code.clone(), - ); + shell.enqueue_tx(wrapper, tx.inner_tx.clone()); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -644,11 +634,7 @@ mod test_process_proposal { ))) .attach_inner_tx(&tx, Default::default()); - shell.enqueue_tx( - wrapper, - tx.inner_tx.clone(), - tx.inner_tx_code.clone(), - ); + shell.enqueue_tx(wrapper, tx.inner_tx.clone()); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -697,7 +683,7 @@ mod test_process_proposal { pow_solution: None, }; - shell.enqueue_tx(wrapper.clone(), Some(inner_tx), None); + shell.enqueue_tx(wrapper.clone(), Some(inner_tx)); let signed = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] wrapper.clone(), diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index b73dbf274b..d36ebd8b18 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -3,7 +3,7 @@ pub mod generated; mod types; -pub use types::{Dkg, Error, Signed, SignedTxData, Tx, TxCode}; +pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; #[cfg(test)] mod tests { @@ -18,12 +18,10 @@ mod tests { let code = "wasm code".as_bytes().to_owned(); let inner_tx = "arbitrary data".as_bytes().to_owned(); let tx = Tx { - code: code.clone(), - is_code_hash: false, + code, data: Some("arbitrary data".as_bytes().to_owned()), timestamp: Some(std::time::SystemTime::now().into()), inner_tx: Some(inner_tx), - inner_tx_code: Some(code), }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 4b241b9067..599ae6409b 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -136,78 +136,6 @@ where } } -/// Failed expansion due to hash of supplied code not matching contained hash -#[derive(Debug)] -pub struct InvalidCodeError; - -/// Represents either the literal code of a transaction or its hash. Useful for -/// supporting both wallets that need the full transaction code, and those that -/// only need the hash. Also useful for cases when passing full transaction code -/// around separately from their transactions is cumbersome. -#[derive( - Clone, - Debug, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Hash, - PartialEq, - Eq, -)] -pub enum TxCode { - /// A hash of transaction code - Hash([u8; 32]), - /// The full transaction code - Literal(Vec), -} - -impl TxCode { - /// Get the literal transaction code if available - pub fn code(&self) -> Option> { - match self { - Self::Hash(_hash) => None, - Self::Literal(lit) => Some(lit.clone()), - } - } - - /// Return the transaction code hash - pub fn code_hash(&self) -> [u8; 32] { - match self { - Self::Hash(hash) => *hash, - Self::Literal(lit) => hash_tx(lit).0, - } - } - - /// Expand this reduced Tx using the supplied code only if the the code - /// hashes to the stored code hash - pub fn expand( - &mut self, - code: Vec, - ) -> std::result::Result<(), InvalidCodeError> { - if hash_tx(&code).0 == self.code_hash() { - *self = TxCode::Literal(code); - Ok(()) - } else { - Err(InvalidCodeError) - } - } - - /// Replace a literal code with its hash - pub fn contract(&mut self) { - *self = TxCode::Hash(self.code_hash()); - } - - /// Indicates that this object contains the full code - pub fn is_literal(&self) -> bool { - matches!(self, Self::Literal(_)) - } - - /// Indicates that this object only contains the hash of the code - pub fn is_hash(&self) -> bool { - matches!(self, Self::Hash(_)) - } -} - /// A SigningTx but with the full code embedded. This structure will almost /// certainly be bigger than SigningTxs and contains enough information to /// execute the transaction. @@ -215,7 +143,7 @@ impl TxCode { Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, )] pub struct Tx { - pub code: TxCode, + pub code: Vec, pub data: Option>, pub timestamp: DateTimeUtc, /// the encrypted inner transaction if data contains a WrapperTx @@ -223,11 +151,6 @@ pub struct Tx { pub inner_tx: Option, #[cfg(not(feature = "ferveo-tpke"))] pub inner_tx: Option>, - /// the encrypted inner transaction code if data contains a WrapperTx - #[cfg(feature = "ferveo-tpke")] - pub inner_tx_code: Option, - #[cfg(not(feature = "ferveo-tpke"))] - pub inner_tx_code: Option>, } impl TryFrom<&[u8]> for Tx { @@ -246,26 +169,11 @@ impl TryFrom<&[u8]> for Tx { .map_err(Error::TxDeserializingError) }) .transpose()?; - let inner_tx_code = tx - .inner_tx_code - .map(|x| { - BorshDeserialize::try_from_slice(&x) - .map_err(Error::TxDeserializingError) - }) - .transpose()?; - let code = if tx.is_code_hash { - TxCode::Hash( - tx.code.try_into().expect("Unable to deserialize code hash"), - ) - } else { - TxCode::Literal(tx.code) - }; Ok(Tx { - code, + code: tx.code, data: tx.data, timestamp, inner_tx, - inner_tx_code, }) } } @@ -277,20 +185,11 @@ impl From for types::Tx { x.try_to_vec() .expect("Unable to serialize encrypted transaction") }); - let inner_tx_code = tx.inner_tx_code.map(|x| { - x.try_to_vec() - .expect("Unable to serialize encrypted transaction code") - }); types::Tx { - code: tx - .code - .code() - .unwrap_or_else(|| tx.code.code_hash().to_vec()), - is_code_hash: tx.code.is_hash(), + code: tx.code, data: tx.data, timestamp, inner_tx, - inner_tx_code, } } } @@ -384,33 +283,10 @@ impl From for ResponseDeliverTx { impl Tx { pub fn new(code: Vec, data: Option>) -> Self { Tx { - code: TxCode::Literal(code), + code, data, timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, - } - } - - /// Decrypt the wrapped transaction. - /// - /// Will fail if the inner transaction does match the - /// hash commitment or we are unable to recover a - /// valid Tx from the decoded byte stream. - #[cfg(feature = "ferveo-tpke")] - pub fn decrypt_code( - &self, - privkey: ::G2Affine, - inner_tx: EncryptedTx, - ) -> Option> { - // decrypt the inner tx - let decrypted = inner_tx.decrypt(privkey); - // check that the hash equals commitment - if hash_tx(&decrypted).0 != self.code.code_hash() { - None - } else { - // convert back to Tx type - Some(decrypted) } } @@ -428,12 +304,10 @@ impl Tx { let timestamp = Some(self.timestamp.into()); let mut bytes = vec![]; types::Tx { - code: self.code.code_hash().to_vec(), - is_code_hash: true, + code: hash_tx(&self.code).0.to_vec(), data: self.data.clone(), timestamp, inner_tx: None, - inner_tx_code: None, } .encode(&mut bytes) .expect("encoding a transaction failed"); @@ -442,7 +316,7 @@ impl Tx { /// Get the hash of this transaction's code pub fn code_hash(&self) -> [u8; 32] { - self.code.code_hash() + hash_tx(&self.code).0 } /// Sign a transaction using [`SignedTxData`]. @@ -460,7 +334,6 @@ impl Tx { data: Some(signed), timestamp: self.timestamp, inner_tx: self.inner_tx, - inner_tx_code: self.inner_tx_code, } } @@ -481,7 +354,6 @@ impl Tx { data, timestamp: self.timestamp, inner_tx: self.inner_tx.clone(), - inner_tx_code: self.inner_tx_code.clone(), }; let signed_data = tx.partial_hash(); common::SigScheme::verify_signature_raw(pk, &signed_data, sig) @@ -500,38 +372,15 @@ impl Tx { self } - /// Attach the given transaction code to this one. Useful when the inner_tx - /// field contains a Tx and its code field needs a witness. - #[cfg(feature = "ferveo-tpke")] - pub fn attach_inner_tx_code( - mut self, - tx: &[u8], - encryption_key: EncryptionKey, - ) -> Self { - let inner_tx_code = EncryptedTx::encrypt(tx, encryption_key); - self.inner_tx_code = Some(inner_tx_code); - self - } - /// A validity check on the ciphertext. #[cfg(feature = "ferveo-tpke")] pub fn validate_ciphertext(&self) -> bool { - let mut valid = true; // Check the inner_tx ciphertext if it is there - if let Some(inner_tx) = &self.inner_tx { - valid = valid && - inner_tx.0.check(&::G1Prepared::from( - -::G1Affine::prime_subgroup_generator(), - )); - } - // Check the inner_tx_code ciphertext if it is there - if let Some(inner_tx_code) = &self.inner_tx_code { - valid = valid && - inner_tx_code.0.check(&::G1Prepared::from( - -::G1Affine::prime_subgroup_generator(), - )); - } - valid + self.inner_tx.as_ref().map(|inner_tx| { + inner_tx.0.check(&::G1Prepared::from( + -::G1Affine::prime_subgroup_generator(), + )) + }).unwrap_or(true) } } @@ -626,11 +475,9 @@ mod tests { let types_tx = types::Tx { code, - is_code_hash: false, data: Some(data), timestamp: None, inner_tx: None, - inner_tx_code: None, }; let mut bytes = vec![]; types_tx.encode(&mut bytes).expect("encoding failed"); diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index 92a3c747b1..ec229f97ac 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -58,8 +58,6 @@ mod tx_queue { pub tx: crate::types::transaction::WrapperTx, /// the encrypted payload pub inner_tx: Option, - /// the encrypted payload - pub inner_tx_code: Option, #[cfg(not(feature = "mainnet"))] /// A PoW solution can be used to allow zero-fee testnet /// transactions. @@ -84,9 +82,7 @@ mod tx_queue { } /// Get an iterator over the queue - pub fn iter( - &self, - ) -> impl std::iter::Iterator { + pub fn iter(&self) -> impl std::iter::Iterator { self.0.iter() } @@ -99,4 +95,4 @@ mod tx_queue { } #[cfg(feature = "ferveo-tpke")] -pub use tx_queue::{TxQueue, TxInQueue}; +pub use tx_queue::{TxInQueue, TxQueue}; diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index ce355a25b1..9519ae3259 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -39,8 +39,6 @@ pub mod decrypted_tx { }, /// The wrapper whose payload could not be decrypted Undecryptable(WrapperTx), - /// The tx whose could not be decrypted - UndecryptableCode(Tx), } impl DecryptedTx { @@ -55,7 +53,6 @@ pub mod decrypted_tx { DecryptedTx::Undecryptable(wrapper) => { wrapper.try_to_vec().unwrap() } - DecryptedTx::UndecryptableCode(tx) => tx.to_bytes(), } } @@ -69,7 +66,6 @@ pub mod decrypted_tx { has_valid_pow: _, } => Hash(tx.partial_hash()), DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), - DecryptedTx::UndecryptableCode(tx) => Hash(tx.partial_hash()), } } } @@ -81,23 +77,16 @@ pub mod decrypted_tx { decrypted: &DecryptedTx, privkey: ::G2Affine, inner_tx: Option, - inner_tx_code: Option, ) -> bool { match decrypted { // A tx is decryptable if it contains the literal code inside it - DecryptedTx::Decrypted { tx, .. } => tx.code.is_literal(), + DecryptedTx::Decrypted { .. } => true, // A tx is undecryptable if its inner_tx decrypts incorrectly DecryptedTx::Undecryptable(tx) if inner_tx.is_some() => { tx.decrypt(privkey, inner_tx.unwrap()).is_err() } // A tx is undecryptable if the inner_tx is not present DecryptedTx::Undecryptable(_) => true, - // A code is undecryptable if its inner_tx_code decrypts incorrectly - DecryptedTx::UndecryptableCode(tx) if inner_tx_code.is_some() => { - tx.decrypt_code(privkey, inner_tx_code.unwrap()).is_none() - } - // A code is undecryptable if the literal code is not present - DecryptedTx::UndecryptableCode(tx) => !tx.code.is_literal(), } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 474dbcb36d..338578b764 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -297,7 +297,6 @@ pub mod tx_types { data: Some(data.clone()), timestamp: tx.timestamp, inner_tx: tx.inner_tx.clone(), - inner_tx_code: tx.inner_tx_code.clone(), } .partial_hash(); match TxType::try_from(Tx { @@ -305,7 +304,6 @@ pub mod tx_types { data: Some(data), timestamp: tx.timestamp, inner_tx: tx.inner_tx, - inner_tx_code: tx.inner_tx_code, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { diff --git a/proto/types.proto b/proto/types.proto index a7e89d9b03..a9478aaf5b 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -10,8 +10,6 @@ message Tx { optional bytes data = 2; google.protobuf.Timestamp timestamp = 3; optional bytes inner_tx = 4; - optional bytes inner_tx_code = 5; - bool is_code_hash = 6; } message Dkg { string data = 1; } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 79a5b72b0f..cfd416c14d 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -161,10 +161,8 @@ where H: 'static + StorageHasher + Sync, CA: 'static + WasmCacheAccess + Sync, { - // prepare_proposal ensures that the code field is populated - let tx_code = tx.code.code().expect("tx code not present"); gas_meter - .add_compiling_fee(tx_code.len()) + .add_compiling_fee(tx.code.len()) .map_err(Error::GasError)?; let empty = vec![]; let tx_data = tx.data.as_ref().unwrap_or(&empty); @@ -173,7 +171,7 @@ where write_log, gas_meter, tx_index, - &tx_code, + &tx.code, tx_data, vp_wasm_cache, tx_wasm_cache, diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index de0a218353..53caa22bea 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -43,7 +43,6 @@ mod tests { use test_log::test; use super::{ibc, tx, vp}; - use crate::namada::proto::TxCode; use crate::tx::{tx_host_env, TestTxEnv}; use crate::vp::{vp_host_env, TestVpEnv}; @@ -538,11 +537,10 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -577,11 +575,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); @@ -612,11 +609,10 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // get and update the client without a header @@ -659,11 +655,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // update the client with the message @@ -690,11 +685,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // upgrade the client with the message @@ -730,11 +724,10 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -769,11 +762,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // init a connection with the message @@ -797,11 +789,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open the connection with the message @@ -835,11 +826,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open try a connection with the message @@ -864,11 +854,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open the connection with the mssage @@ -907,11 +896,10 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // not bind a port @@ -950,11 +938,10 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // bind a port @@ -996,11 +983,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // init a channel with the message @@ -1022,11 +1008,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open the channle with the message @@ -1062,11 +1047,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // try open a channel with the message @@ -1089,11 +1073,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // open a channel with the message @@ -1131,11 +1114,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // close the channel with the message @@ -1173,11 +1155,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); @@ -1220,11 +1201,10 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1262,11 +1242,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1313,11 +1292,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1380,11 +1358,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1459,11 +1436,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1508,11 +1484,10 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // send a packet with the message @@ -1539,11 +1514,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1592,11 +1566,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1656,11 +1629,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); @@ -1730,11 +1702,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: TxCode::Literal(vec![]), + code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, - inner_tx_code: None, } .sign(&key::testing::keypair_1()); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index a8452fee5d..0f7040941d 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -191,7 +191,7 @@ impl TestTxEnv { &mut self.write_log, &mut self.gas_meter, &self.tx_index, - &self.tx.code.code().expect("tx code not present"), + &self.tx.code, self.tx.data.as_ref().unwrap_or(&empty_data), &mut self.vp_wasm_cache, &mut self.tx_wasm_cache, diff --git a/wasm/checksums.json b/wasm/checksums.json index 1a8772f692..1b68e8efca 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.bd183d3508df5ef6e61e34c8229326aad604e0f7fc1dafc84d48cd6b61614b90.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.23989ccc8f683f872f8661de589f0a0d3556c783ad5635f0758c018ac81fafbe.wasm", - "tx_ibc.wasm": "tx_ibc.6c384de2ceb02fd3a44b38bed5b9e13d6c1977383abea11dc132646bd5ff6747.wasm", - "tx_init_account.wasm": "tx_init_account.01a15a472317dfc5a2e126c56e36ebc2c0ec5cc3e04ea69347eae3b398d898c4.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.780edb2e083f3140fea275d3e277ac33173c1f02167b016463932a8d2134bbb8.wasm", - "tx_init_validator.wasm": "tx_init_validator.29bef4a68fa9512514300f5c3028f11515c7db6e3c683ac9324a95b1af10f7f7.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.d56759ccc7d4b3d432193055c31caff97860353b468559f2408db68d17ff25d6.wasm", - "tx_transfer.wasm": "tx_transfer.40747267f3af116b5579f11995d0cc30cec7bdd3a1135ff16d3c99465e97f409.wasm", - "tx_unbond.wasm": "tx_unbond.893a1aab86db50a93ff4f626a47119640c0915430e93abebbb3b3662dea67da3.wasm", - "tx_update_vp.wasm": "tx_update_vp.6970c0f1809ab183a996f46a46036a2a646a092535994eb6abd72b2d0c46c3c5.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.2362f79241945795318ebb18ad7a4d54edc61edc00d70c151532e10e2361c13b.wasm", - "tx_withdraw.wasm": "tx_withdraw.8a8c718ed8c2f97d7bb37e93fa1fe1edf0be24b9983b23efaacf19d09a4d067e.wasm", - "vp_implicit.wasm": "vp_implicit.de8a20ab779563c9dd886a51c9c69fc69b4da1452da0d186e9a5807b37e9a042.wasm", - "vp_masp.wasm": "vp_masp.3ded845a16e44c4637e6627b38cdf153a9145fae5d4596b75ce1c7b9db8fa229.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.63315ca12161f8abaffe039928e595e7cd63aed620f3febbeaa6f8eb8e22607e.wasm", - "vp_token.wasm": "vp_token.b78955a61249421406f5f9551f96e0c96f71aa6bd02784fb28e844e31d6dbb9f.wasm", - "vp_user.wasm": "vp_user.3ab0f016c4020e2c3260614b28380489068746e57385c923fc55905f9e3c7b88.wasm", - "vp_validator.wasm": "vp_validator.67dddaeac29a89ec4f22e3f9296309ffd9b48284fa52cb9bcd7226d065475adf.wasm" + "tx_bond.wasm": "tx_bond.6a99abbacd3f3684af943fd17f3373c64990b4165a8bab684143d59b9bfba635.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.452a9fc50686cbe33997e36c7d1cafff375a0154cff7dc91352676f10cd03674.wasm", + "tx_ibc.wasm": "tx_ibc.6076f4b36cac93420ac3214e7840b0bf3400fe7dd5da7607c7cf8beeb4d60854.wasm", + "tx_init_account.wasm": "tx_init_account.b3a330fbc7e78ef850c6909725c612b4b032a2e075410a9c8b9778f2b411378f.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e14d98829ef65b3faf0803379438d95209e5556ae2adb4e93828d73517877b57.wasm", + "tx_init_validator.wasm": "tx_init_validator.b2c11d12645472b49fa721d926b316f67ef367741bdb593c033ca3872af195d2.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.fa72b50b057b109b3c8cca3fd055b1a474bee8d2fe28f9fea09733cddf57ae1e.wasm", + "tx_transfer.wasm": "tx_transfer.113d19ad10c3d9baa903c074e816a7aef6a57d8fdf9947044d49559513f71fd9.wasm", + "tx_unbond.wasm": "tx_unbond.11bbad04508057e298c8a1437f028b6402f1815535b76e6069268c23c56cdc00.wasm", + "tx_update_vp.wasm": "tx_update_vp.34d7ca43a0eae46890e1d7e43409c369991f7977f7de5dac41b66d98b4883949.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3c555ab6b101932525cb3cb1324fb07fbff3ff7473d17008f0c441e9c123cd37.wasm", + "tx_withdraw.wasm": "tx_withdraw.e31035ebcb11b951be310fa0da706fa95d4add116ba7ded39914fda7bf8f922d.wasm", + "vp_implicit.wasm": "vp_implicit.a03f53e1d4b5ebc1ecd8fecd3d876f4c97b197b810ad2d1d0f093786f96dc42c.wasm", + "vp_masp.wasm": "vp_masp.227e4683ca84f6c7e1840a5a60c5552401e985878ddec349a77a187ef3bf1ff4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.90c459130b0704022395ad4b9139805e2c63d90885fbd6ca0511c9a8ec0a0c59.wasm", + "vp_token.wasm": "vp_token.c9bafbe21597c8527994b954c4316956ddb89c6d195bc17b7f4574c1cc9f7db3.wasm", + "vp_user.wasm": "vp_user.6f5d307e7d4cdc60e70adc3768da057c53cf5ffb1684fecb162a6d832d07986b.wasm", + "vp_validator.wasm": "vp_validator.a31bd19cfcfa9b66a3054f710fb853dfc5da2a987e94da206eb30a727c45d7ba.wasm" } \ No newline at end of file From 5a60a9f80265d564b04e6ced1b37d0c67cbb87e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Feb 2023 13:46:36 +0000 Subject: [PATCH 09/10] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 1b68e8efca..3ffc525c0a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.6a99abbacd3f3684af943fd17f3373c64990b4165a8bab684143d59b9bfba635.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.452a9fc50686cbe33997e36c7d1cafff375a0154cff7dc91352676f10cd03674.wasm", - "tx_ibc.wasm": "tx_ibc.6076f4b36cac93420ac3214e7840b0bf3400fe7dd5da7607c7cf8beeb4d60854.wasm", - "tx_init_account.wasm": "tx_init_account.b3a330fbc7e78ef850c6909725c612b4b032a2e075410a9c8b9778f2b411378f.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e14d98829ef65b3faf0803379438d95209e5556ae2adb4e93828d73517877b57.wasm", - "tx_init_validator.wasm": "tx_init_validator.b2c11d12645472b49fa721d926b316f67ef367741bdb593c033ca3872af195d2.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.fa72b50b057b109b3c8cca3fd055b1a474bee8d2fe28f9fea09733cddf57ae1e.wasm", - "tx_transfer.wasm": "tx_transfer.113d19ad10c3d9baa903c074e816a7aef6a57d8fdf9947044d49559513f71fd9.wasm", - "tx_unbond.wasm": "tx_unbond.11bbad04508057e298c8a1437f028b6402f1815535b76e6069268c23c56cdc00.wasm", - "tx_update_vp.wasm": "tx_update_vp.34d7ca43a0eae46890e1d7e43409c369991f7977f7de5dac41b66d98b4883949.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3c555ab6b101932525cb3cb1324fb07fbff3ff7473d17008f0c441e9c123cd37.wasm", - "tx_withdraw.wasm": "tx_withdraw.e31035ebcb11b951be310fa0da706fa95d4add116ba7ded39914fda7bf8f922d.wasm", - "vp_implicit.wasm": "vp_implicit.a03f53e1d4b5ebc1ecd8fecd3d876f4c97b197b810ad2d1d0f093786f96dc42c.wasm", - "vp_masp.wasm": "vp_masp.227e4683ca84f6c7e1840a5a60c5552401e985878ddec349a77a187ef3bf1ff4.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.90c459130b0704022395ad4b9139805e2c63d90885fbd6ca0511c9a8ec0a0c59.wasm", - "vp_token.wasm": "vp_token.c9bafbe21597c8527994b954c4316956ddb89c6d195bc17b7f4574c1cc9f7db3.wasm", - "vp_user.wasm": "vp_user.6f5d307e7d4cdc60e70adc3768da057c53cf5ffb1684fecb162a6d832d07986b.wasm", - "vp_validator.wasm": "vp_validator.a31bd19cfcfa9b66a3054f710fb853dfc5da2a987e94da206eb30a727c45d7ba.wasm" + "tx_bond.wasm": "tx_bond.749ff231716200c7d2ec509fa3e7d447e7ed63b76d92a465deffb5fcfc952332.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.97b0d6f07c9db41320f1006e12e726098c93aad75eb843a575222c8f305462e7.wasm", + "tx_ibc.wasm": "tx_ibc.1d8969bf70235452e4e0254fb410ccd84d4cd1ea795f9cc608c3f9e76c45cd40.wasm", + "tx_init_account.wasm": "tx_init_account.bc398b6063fc1cb34c58f1a31793cf85f6e7a1305ef7937036d44fe0f3ca4dc0.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4a977c3d205b68114c6ec8f4ae8d933b768f146759a35de21796395626ca5d43.wasm", + "tx_init_validator.wasm": "tx_init_validator.a2da8bf373a314e59bfd48fba567ea1dfb56d616cade9f5a585e5bf5a59fb8d3.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.fce1576f83731b281d7035a283adcec67779ea38f392148a06544821f7e49d0f.wasm", + "tx_transfer.wasm": "tx_transfer.3fda6e26b50e7aa4b1d6e37fc631d5c55bb9370e6fac71f64f2be137b42df549.wasm", + "tx_unbond.wasm": "tx_unbond.ed61e0b0289765f51c642aa7440304ac0c1468c3a2c587641c9c30f7eb545971.wasm", + "tx_update_vp.wasm": "tx_update_vp.db4d8c11658c5f8e99fc39f0112be1ee480ab1f0045010d323ee3081a7afe802.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.01e5ade00d8c91d6db0a6e228edc1561143ee792f510ecbd37c36b18485c4872.wasm", + "tx_withdraw.wasm": "tx_withdraw.df583eb3c1529adb2bcd15946e5f9e9a735c406fb76baf5d0b1a48d1049b3213.wasm", + "vp_implicit.wasm": "vp_implicit.d94a52837ef3ae47c8574c549935758776ede4db155a0ea258a4e1beeaeafafa.wasm", + "vp_masp.wasm": "vp_masp.08137fd20390a40f106c4ab8ae3a6e548002dff189042aee858c6e4bf10f94e5.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e4409f3803ce7c15cd3852e6912342e594e71983e33fdf324788d98b529f0a66.wasm", + "vp_token.wasm": "vp_token.b9622cb39e0141c3a8b9c6d5cba395ca2baf335f1b376079f66ba2bf6b8cd770.wasm", + "vp_user.wasm": "vp_user.fb999a383e36081abdf80587c857a169e1fac5cc75823fe2bda5b3ace40d57fb.wasm", + "vp_validator.wasm": "vp_validator.c2d33de976f5ad5e75841292c9c59d6ebbc629647f35a50a6cda02f595a4addf.wasm" } \ No newline at end of file From 3d2bd33f3eda18d7a4d2f1145cf67881a4aaf428 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 20 Feb 2023 17:56:56 +0200 Subject: [PATCH 10/10] Add an extra data field to Txs that is hashed before signing. --- apps/src/lib/client/tx.rs | 8 ++- .../lib/node/ledger/shell/process_proposal.rs | 1 + core/src/ledger/storage/mod.rs | 10 +--- core/src/ledger/storage_api/mod.rs | 5 +- core/src/ledger/tx_env.rs | 7 +++ core/src/proto/mod.rs | 1 + core/src/proto/types.rs | 8 +++ core/src/types/transaction/mod.rs | 4 +- proto/types.proto | 3 +- shared/src/ledger/native_vp/mod.rs | 8 --- shared/src/ledger/protocol/mod.rs | 5 +- shared/src/vm/host_env.rs | 52 +++++++++++++++++-- shared/src/vm/wasm/host_env.rs | 2 + shared/src/vm/wasm/run.rs | 25 ++++----- tests/src/vm_host_env/mod.rs | 28 ++++++++++ tests/src/vm_host_env/tx.rs | 10 ++-- tx_prelude/src/lib.rs | 20 +++++-- vm_env/src/lib.rs | 8 ++- vp_prelude/src/lib.rs | 8 --- wasm/checksums.json | 36 ++++++------- wasm/wasm_source/src/tx_init_account.rs | 2 +- 21 files changed, 163 insertions(+), 88 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 9ae1d0562f..5e98e8992d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -196,13 +196,11 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { } let tx_code = ctx.read_wasm(TX_INIT_ACCOUNT_WASM); - let data = InitAccount { - public_key, - vp_code, - }; + let data = InitAccount { public_key }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let mut tx = Tx::new(tx_code, Some(data)); + tx.extra = Some(vp_code); let (ctx, initialized_accounts) = process_tx( ctx, &args.tx, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 2888b54ea4..1b4308e5e3 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -339,6 +339,7 @@ mod test_process_proposal { ), timestamp, inner_tx: tx.inner_tx, + extra: None, } } else { panic!("Test failed"); diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 45f145ec87..74198e8a86 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -47,7 +47,7 @@ use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; use crate::types::internal::TxQueue; use crate::types::storage::{ BlockHash, BlockHeight, BlockResults, Epoch, Epochs, Header, Key, KeySeg, - TxIndex, BLOCK_HASH_LENGTH, + BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; use crate::types::token; @@ -97,8 +97,6 @@ where pub next_epoch_min_start_time: DateTimeUtc, /// The current established address generator pub address_gen: EstablishedAddressGen, - /// The shielded transaction index - pub tx_index: TxIndex, /// The currently saved conversion state pub conversion_state: ConversionState, /// Wrapper txs to be decrypted in the next block proposal @@ -354,7 +352,6 @@ where address_gen: EstablishedAddressGen::new( "Privacy is a function of liberty.", ), - tx_index: TxIndex::default(), conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), @@ -1053,10 +1050,6 @@ where Ok(self.block.epoch) } - fn get_tx_index(&self) -> std::result::Result { - Ok(self.tx_index) - } - fn get_native_token( &self, ) -> std::result::Result { @@ -1142,7 +1135,6 @@ pub mod testing { address_gen: EstablishedAddressGen::new( "Test address generator seed", ), - tx_index: TxIndex::default(), conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index c929aec03b..a78751d80f 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -10,7 +10,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; use crate::types::address::Address; -use crate::types::storage::{self, BlockHash, BlockHeight, Epoch, TxIndex}; +use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; /// Common storage read interface /// @@ -85,9 +85,6 @@ pub trait StorageRead { /// current transaction is being applied. fn get_block_epoch(&self) -> Result; - /// Get the transaction index. - fn get_tx_index(&self) -> Result; - /// Get the native token address fn get_native_token(&self) -> Result

; } diff --git a/core/src/ledger/tx_env.rs b/core/src/ledger/tx_env.rs index 6ca47bb9d9..36ca28604b 100644 --- a/core/src/ledger/tx_env.rs +++ b/core/src/ledger/tx_env.rs @@ -7,6 +7,7 @@ use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::address::Address; use crate::types::ibc::IbcEvent; use crate::types::storage; +use crate::types::storage::TxIndex; use crate::types::time::Rfc3339String; /// Transaction host functions @@ -60,4 +61,10 @@ pub trait TxEnv: StorageRead + StorageWrite { /// Get time of the current block header as rfc 3339 string fn get_block_time(&self) -> Result; + + /// Get the transaction index + fn get_tx_index(&self) -> Result; + + /// Get the transaction extra data + fn get_tx_extra(&self) -> Result, storage_api::Error>; } diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index d36ebd8b18..b2eb0e534b 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -22,6 +22,7 @@ mod tests { data: Some("arbitrary data".as_bytes().to_owned()), timestamp: Some(std::time::SystemTime::now().into()), inner_tx: Some(inner_tx), + extra: None, }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 599ae6409b..975d5ea9b4 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -146,6 +146,7 @@ pub struct Tx { pub code: Vec, pub data: Option>, pub timestamp: DateTimeUtc, + pub extra: Option>, /// the encrypted inner transaction if data contains a WrapperTx #[cfg(feature = "ferveo-tpke")] pub inner_tx: Option, @@ -172,6 +173,7 @@ impl TryFrom<&[u8]> for Tx { Ok(Tx { code: tx.code, data: tx.data, + extra: tx.extra, timestamp, inner_tx, }) @@ -188,6 +190,7 @@ impl From for types::Tx { types::Tx { code: tx.code, data: tx.data, + extra: tx.extra, timestamp, inner_tx, } @@ -287,6 +290,7 @@ impl Tx { data, timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } } @@ -305,6 +309,7 @@ impl Tx { let mut bytes = vec![]; types::Tx { code: hash_tx(&self.code).0.to_vec(), + extra: self.extra.as_ref().map(|x| hash_tx(x).0.to_vec()), data: self.data.clone(), timestamp, inner_tx: None, @@ -332,6 +337,7 @@ impl Tx { Tx { code: self.code, data: Some(signed), + extra: self.extra, timestamp: self.timestamp, inner_tx: self.inner_tx, } @@ -351,6 +357,7 @@ impl Tx { let data = signed_tx_data.data; let tx = Tx { code: self.code.clone(), + extra: self.extra.clone(), data, timestamp: self.timestamp, inner_tx: self.inner_tx.clone(), @@ -478,6 +485,7 @@ mod tests { data: Some(data), timestamp: None, inner_tx: None, + extra: None, }; let mut bytes = vec![]; types_tx.encode(&mut bytes).expect("encoding failed"); diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 338578b764..3cd0a77ce1 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -163,8 +163,6 @@ pub struct InitAccount { /// for signature verification of transactions for the newly created /// account. pub public_key: common::PublicKey, - /// The VP code - pub vp_code: Vec, } /// A tx data type to initialize a new validator account. @@ -297,6 +295,7 @@ pub mod tx_types { data: Some(data.clone()), timestamp: tx.timestamp, inner_tx: tx.inner_tx.clone(), + extra: tx.extra.clone(), } .partial_hash(); match TxType::try_from(Tx { @@ -304,6 +303,7 @@ pub mod tx_types { data: Some(data), timestamp: tx.timestamp, inner_tx: tx.inner_tx, + extra: tx.extra, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { diff --git a/proto/types.proto b/proto/types.proto index a9478aaf5b..ec2a8aaf12 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -9,7 +9,8 @@ message Tx { // TODO this optional is useless because it's default on proto3 optional bytes data = 2; google.protobuf.Timestamp timestamp = 3; - optional bytes inner_tx = 4; + optional bytes extra = 4; + optional bytes inner_tx = 5; } message Dkg { string data = 1; } diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index e14378c3d3..8fd6a9c259 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -240,10 +240,6 @@ where self.ctx.get_block_epoch() } - fn get_tx_index(&self) -> Result { - self.ctx.get_tx_index().into_storage_result() - } - fn get_native_token(&self) -> Result { self.ctx.get_native_token() } @@ -322,10 +318,6 @@ where self.ctx.get_block_epoch() } - fn get_tx_index(&self) -> Result { - self.ctx.get_tx_index().into_storage_result() - } - fn get_native_token(&self) -> Result { Ok(self.ctx.storage.native_token.clone()) } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index cfd416c14d..bdd8aa0427 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -164,15 +164,12 @@ where gas_meter .add_compiling_fee(tx.code.len()) .map_err(Error::GasError)?; - let empty = vec![]; - let tx_data = tx.data.as_ref().unwrap_or(&empty); wasm::run::tx( storage, write_log, gas_meter, tx_index, - &tx.code, - tx_data, + tx, vp_wasm_cache, tx_wasm_cache, ) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index ee8e8a3601..d4b99b339d 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -91,6 +91,8 @@ where pub iterators: MutHostRef<'a, &'a PrefixIterators<'a, DB>>, /// Transaction gas meter. pub gas_meter: MutHostRef<'a, &'a BlockGasMeter>, + /// The transaction code is used for signature verification + pub tx: HostRef<'a, &'a Tx>, /// The transaction index is used to identify a shielded transaction's /// parent pub tx_index: HostRef<'a, &'a TxIndex>, @@ -131,6 +133,7 @@ where write_log: &mut WriteLog, iterators: &mut PrefixIterators<'a, DB>, gas_meter: &mut BlockGasMeter, + tx: &Tx, tx_index: &TxIndex, verifiers: &mut BTreeSet
, result_buffer: &mut Option>, @@ -141,6 +144,7 @@ where let write_log = unsafe { MutHostRef::new(write_log) }; let iterators = unsafe { MutHostRef::new(iterators) }; let gas_meter = unsafe { MutHostRef::new(gas_meter) }; + let tx = unsafe { HostRef::new(tx) }; let tx_index = unsafe { HostRef::new(tx_index) }; let verifiers = unsafe { MutHostRef::new(verifiers) }; let result_buffer = unsafe { MutHostRef::new(result_buffer) }; @@ -153,6 +157,7 @@ where write_log, iterators, gas_meter, + tx, tx_index, verifiers, result_buffer, @@ -195,6 +200,7 @@ where write_log: self.write_log.clone(), iterators: self.iterators.clone(), gas_meter: self.gas_meter.clone(), + tx: self.tx.clone(), tx_index: self.tx_index.clone(), verifiers: self.verifiers.clone(), result_buffer: self.result_buffer.clone(), @@ -1481,9 +1487,9 @@ where Ok(height.0) } -/// Getting the block height function exposed to the wasm VM Tx -/// environment. The height is that of the block to which the current -/// transaction is being applied. +/// Getting the transaction index function exposed to the wasm VM Tx +/// environment. The index is that of the transaction being applied +/// in the current block. pub fn tx_get_tx_index( env: &TxVmEnv, ) -> TxResult @@ -1498,6 +1504,44 @@ where Ok(tx_index.0) } +/// Getting the transaction extra data function exposed to the wasm VM Tx +/// environment. The extra data is that of the transaction being applied. +pub fn tx_get_tx_extra( + env: &TxVmEnv, + result_ptr: u64, +) -> TxResult<()> +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let tx = unsafe { env.ctx.tx.get() }; + tx_add_gas(env, crate::vm::host_env::gas::MIN_STORAGE_GAS)?; + let gas = env + .memory + .write_bytes(result_ptr, tx.extra.as_ref().unwrap_or(&vec![])) + .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; + tx_add_gas(env, gas) +} + +/// Getting the transaction extra data length function exposed to the wasm +/// VM Tx environment. The extra data length is that of the transaction +/// being applied. +pub fn tx_get_tx_extra_len( + env: &TxVmEnv, +) -> TxResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let tx = unsafe { env.ctx.tx.get() }; + tx_add_gas(env, crate::vm::host_env::gas::MIN_STORAGE_GAS)?; + Ok(tx.extra.as_ref().map(|x| Vec::len(x) as u64).unwrap_or(0)) +} + /// Getting the block height function exposed to the wasm VM VP /// environment. The height is that of the block to which the current /// transaction is being applied. @@ -1953,6 +1997,7 @@ pub mod testing { iterators: &mut PrefixIterators<'static, DB>, verifiers: &mut BTreeSet
, gas_meter: &mut BlockGasMeter, + tx: &Tx, tx_index: &TxIndex, result_buffer: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, @@ -1969,6 +2014,7 @@ pub mod testing { write_log, iterators, gas_meter, + tx, tx_index, verifiers, result_buffer, diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 28fb2cc12a..efa3c283a8 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -74,6 +74,8 @@ where "namada_tx_emit_ibc_event" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_emit_ibc_event), "namada_tx_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_chain_id), "namada_tx_get_tx_index" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_tx_index), + "namada_tx_get_tx_extra" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_tx_extra), + "namada_tx_get_tx_extra_len" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_tx_extra_len), "namada_tx_get_block_height" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_height), "namada_tx_get_block_time" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_time), "namada_tx_get_block_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_hash), diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 233a5f72a0..246bc40996 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -77,8 +77,7 @@ pub fn tx( write_log: &mut WriteLog, gas_meter: &mut BlockGasMeter, tx_index: &TxIndex, - tx_code: impl AsRef<[u8]>, - tx_data: impl AsRef<[u8]>, + tx: &Tx, vp_wasm_cache: &mut VpCache, tx_wasm_cache: &mut TxCache, ) -> Result> @@ -89,9 +88,11 @@ where { // let wasm_store = untrusted_wasm_store(memory::tx_limit()); - validate_untrusted_wasm(&tx_code).map_err(Error::ValidationError)?; + let empty = vec![]; + let tx_data = tx.data.as_ref().unwrap_or(&empty); + validate_untrusted_wasm(&tx.code).map_err(Error::ValidationError)?; - let (module, store) = tx_wasm_cache.fetch_or_compile(&tx_code)?; + let (module, store) = tx_wasm_cache.fetch_or_compile(&tx.code)?; let mut iterators: PrefixIterators<'_, DB> = PrefixIterators::default(); let mut verifiers = BTreeSet::new(); @@ -103,6 +104,7 @@ where write_log, &mut iterators, gas_meter, + tx, tx_index, &mut verifiers, &mut result_buffer, @@ -495,8 +497,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_code.clone(), - tx_data, + &Tx::new(tx_code.clone(), Some(tx_data)), &mut vp_cache, &mut tx_cache, ); @@ -510,8 +511,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_code, - tx_data, + &Tx::new(tx_code, Some(tx_data)), &mut vp_cache, &mut tx_cache, ) @@ -692,8 +692,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_no_op, - tx_data, + &Tx::new(tx_no_op, Some(tx_data)), &mut vp_cache, &mut tx_cache, ); @@ -809,8 +808,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_read_key, - tx_data, + &Tx::new(tx_read_key, Some(tx_data)), &mut vp_cache, &mut tx_cache, ) @@ -974,8 +972,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_code, - tx_data, + &Tx::new(tx_code, Some(tx_data)), &mut vp_cache, &mut tx_cache, ) diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 53caa22bea..3c8d6dd2de 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -541,6 +541,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -579,6 +580,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); @@ -613,6 +615,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // get and update the client without a header @@ -659,6 +662,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // update the client with the message @@ -689,6 +693,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // upgrade the client with the message @@ -728,6 +733,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -766,6 +772,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // init a connection with the message @@ -793,6 +800,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // open the connection with the message @@ -830,6 +838,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // open try a connection with the message @@ -858,6 +867,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // open the connection with the mssage @@ -900,6 +910,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // not bind a port @@ -942,6 +953,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // bind a port @@ -987,6 +999,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // init a channel with the message @@ -1012,6 +1025,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // open the channle with the message @@ -1051,6 +1065,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // try open a channel with the message @@ -1077,6 +1092,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // open a channel with the message @@ -1118,6 +1134,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // close the channel with the message @@ -1159,6 +1176,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); @@ -1205,6 +1223,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1246,6 +1265,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1296,6 +1316,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1362,6 +1383,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1440,6 +1462,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1488,6 +1511,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // send a packet with the message @@ -1518,6 +1542,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1570,6 +1595,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1633,6 +1659,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); @@ -1706,6 +1733,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), inner_tx: None, + extra: None, } .sign(&key::testing::keypair_1()); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 0f7040941d..8483bfc57d 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -185,14 +185,12 @@ impl TestTxEnv { /// Apply the tx changes to the write log. pub fn execute_tx(&mut self) -> Result<(), Error> { - let empty_data = vec![]; wasm::run::tx( &self.storage, &mut self.write_log, &mut self.gas_meter, &self.tx_index, - &self.tx.code, - self.tx.data.as_ref().unwrap_or(&empty_data), + &self.tx, &mut self.vp_wasm_cache, &mut self.tx_wasm_cache, ) @@ -317,7 +315,7 @@ mod native_tx_host_env { vp_cache_dir: _, tx_wasm_cache, tx_cache_dir: _, - tx: _, + tx, }: &mut TestTxEnv| { let tx_env = vm::host_env::testing::tx_env( @@ -326,6 +324,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + tx, tx_index, result_buffer, vp_wasm_cache, @@ -357,7 +356,7 @@ mod native_tx_host_env { vp_cache_dir: _, tx_wasm_cache, tx_cache_dir: _, - tx: _, + tx, }: &mut TestTxEnv| { let tx_env = vm::host_env::testing::tx_env( @@ -366,6 +365,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + tx, tx_index, result_buffer, vp_wasm_cache, diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 9d66dcb73e..84f1dfdaa5 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -199,11 +199,6 @@ impl StorageRead for Ctx { namada_tx_result_buffer, )) } - - fn get_tx_index(&self) -> Result { - let tx_index = unsafe { namada_tx_get_tx_index() }; - Ok(TxIndex(tx_index)) - } } impl StorageWrite for Ctx { @@ -324,4 +319,19 @@ impl TxEnv for Ctx { }; Ok(()) } + + fn get_tx_index(&self) -> Result { + let tx_index = unsafe { namada_tx_get_tx_index() }; + Ok(TxIndex(tx_index)) + } + + fn get_tx_extra(&self) -> Result, Error> { + let capacity = unsafe { namada_tx_get_tx_extra_len() } as usize; + let result = Vec::with_capacity(capacity); + unsafe { + namada_tx_get_tx_extra(result.as_ptr() as _); + } + let slice = unsafe { slice::from_raw_parts(result.as_ptr(), capacity) }; + Ok(slice.to_vec()) + } } diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index b38feb83f2..0aa7ca6f71 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -93,9 +93,15 @@ pub mod tx { // Get the current block epoch pub fn namada_tx_get_block_epoch() -> u64; - // Get the current tx id + // Get the current tx index pub fn namada_tx_get_tx_index() -> u32; + // Get the current tx extra + pub fn namada_tx_get_tx_extra(result_ptr: u64); + + // Get the current tx extra length + pub fn namada_tx_get_tx_extra_len() -> u64; + // Get the native token address pub fn namada_tx_get_native_token(result_ptr: u64); diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 33cb5f900c..7f3a06ced7 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -370,10 +370,6 @@ impl StorageRead for CtxPreStorageRead<'_> { get_block_epoch() } - fn get_tx_index(&self) -> Result { - get_tx_index() - } - fn get_native_token(&self) -> Result { get_native_token() } @@ -433,10 +429,6 @@ impl StorageRead for CtxPostStorageRead<'_> { get_block_epoch() } - fn get_tx_index(&self) -> Result { - get_tx_index() - } - fn get_native_token(&self) -> Result { get_native_token() } diff --git a/wasm/checksums.json b/wasm/checksums.json index 3ffc525c0a..8ae7d47a1b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.749ff231716200c7d2ec509fa3e7d447e7ed63b76d92a465deffb5fcfc952332.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.97b0d6f07c9db41320f1006e12e726098c93aad75eb843a575222c8f305462e7.wasm", - "tx_ibc.wasm": "tx_ibc.1d8969bf70235452e4e0254fb410ccd84d4cd1ea795f9cc608c3f9e76c45cd40.wasm", - "tx_init_account.wasm": "tx_init_account.bc398b6063fc1cb34c58f1a31793cf85f6e7a1305ef7937036d44fe0f3ca4dc0.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4a977c3d205b68114c6ec8f4ae8d933b768f146759a35de21796395626ca5d43.wasm", - "tx_init_validator.wasm": "tx_init_validator.a2da8bf373a314e59bfd48fba567ea1dfb56d616cade9f5a585e5bf5a59fb8d3.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.fce1576f83731b281d7035a283adcec67779ea38f392148a06544821f7e49d0f.wasm", - "tx_transfer.wasm": "tx_transfer.3fda6e26b50e7aa4b1d6e37fc631d5c55bb9370e6fac71f64f2be137b42df549.wasm", - "tx_unbond.wasm": "tx_unbond.ed61e0b0289765f51c642aa7440304ac0c1468c3a2c587641c9c30f7eb545971.wasm", - "tx_update_vp.wasm": "tx_update_vp.db4d8c11658c5f8e99fc39f0112be1ee480ab1f0045010d323ee3081a7afe802.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.01e5ade00d8c91d6db0a6e228edc1561143ee792f510ecbd37c36b18485c4872.wasm", - "tx_withdraw.wasm": "tx_withdraw.df583eb3c1529adb2bcd15946e5f9e9a735c406fb76baf5d0b1a48d1049b3213.wasm", - "vp_implicit.wasm": "vp_implicit.d94a52837ef3ae47c8574c549935758776ede4db155a0ea258a4e1beeaeafafa.wasm", - "vp_masp.wasm": "vp_masp.08137fd20390a40f106c4ab8ae3a6e548002dff189042aee858c6e4bf10f94e5.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e4409f3803ce7c15cd3852e6912342e594e71983e33fdf324788d98b529f0a66.wasm", - "vp_token.wasm": "vp_token.b9622cb39e0141c3a8b9c6d5cba395ca2baf335f1b376079f66ba2bf6b8cd770.wasm", - "vp_user.wasm": "vp_user.fb999a383e36081abdf80587c857a169e1fac5cc75823fe2bda5b3ace40d57fb.wasm", - "vp_validator.wasm": "vp_validator.c2d33de976f5ad5e75841292c9c59d6ebbc629647f35a50a6cda02f595a4addf.wasm" + "tx_bond.wasm": "tx_bond.43c2741554f360af694aea5377f6dbd5052ae70243fecdfbea7a0f9ae6edbe17.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.452a9fc50686cbe33997e36c7d1cafff375a0154cff7dc91352676f10cd03674.wasm", + "tx_ibc.wasm": "tx_ibc.96a4c71ccbbd9a9862e06a127826d0016c0bdef5a3ced42a75bef4abf24a98e1.wasm", + "tx_init_account.wasm": "tx_init_account.17a78ac03dab6ef33a442808872e9c2a5114ce04d985584cbc4a7d1de95ae834.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.a9c21c6399da0468b764e56323c05e8bdf13fa61ada12b1c68c0d49be9246d13.wasm", + "tx_init_validator.wasm": "tx_init_validator.7e4fbbe84968b68bda9f9c2efef45e533a36765bded138299b5e35698dc9df2b.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.fa72b50b057b109b3c8cca3fd055b1a474bee8d2fe28f9fea09733cddf57ae1e.wasm", + "tx_transfer.wasm": "tx_transfer.749d1566d30c02de7c6e1d7ddbdc7c0f6e4978737ce0da05c3fa21c592568348.wasm", + "tx_unbond.wasm": "tx_unbond.11bbad04508057e298c8a1437f028b6402f1815535b76e6069268c23c56cdc00.wasm", + "tx_update_vp.wasm": "tx_update_vp.502dba0b096f73a37a6f4751536272796f64721ab83bf3b6997e4e829a9637c7.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3c555ab6b101932525cb3cb1324fb07fbff3ff7473d17008f0c441e9c123cd37.wasm", + "tx_withdraw.wasm": "tx_withdraw.e20097fa7c49d622532b3815135af2eb09002b2b49633ca0bff639d4dad31aff.wasm", + "vp_implicit.wasm": "vp_implicit.a1007cc2ca9dc374cadde172f4abfa31f17cef827ee249c5fd344b9245de4f24.wasm", + "vp_masp.wasm": "vp_masp.ba1927cf3b965b92953c9a4173543043c3de52f9700179572fa8049632f6d715.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.c3b1e880356d814e7502698f53c7d962f585f4da6cd51cdecce208b009ebf297.wasm", + "vp_token.wasm": "vp_token.832a2000e57209ac5baf6b335645944acabb517d280d812341cf8e17af1e6a2d.wasm", + "vp_user.wasm": "vp_user.5b00d09db6b0037576302ec3e49f296ea7555c4dbb93a41fa4bc21cba0eda879.wasm", + "vp_validator.wasm": "vp_validator.b12e449695c24a15a9446908551312a2a6353d484b8baf9c91d41c29d1fbffde.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index e0fe700d63..9bbf3b1119 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -12,7 +12,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); - let address = ctx.init_account(&tx_data.vp_code)?; + let address = ctx.init_account(&ctx.get_tx_extra()?)?; let pk_key = key::pk_key(&address); ctx.write(&pk_key, &tx_data.public_key)?; Ok(())