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 ab64ce948d..488b7e07cd 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -386,18 +386,22 @@ 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, - #[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 e4f8faa5b8..66076357fc 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 mut stx = wrapper_tx + .sign(keypair) + .expect("Wrapper tx signing keypair should be correct"); + // 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(&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/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/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 8455522303..26e55161d3 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!( @@ -215,8 +215,9 @@ where } } - self.storage.tx_queue.push(WrapperTxInQueue { + self.storage.tx_queue.push(TxInQueue { tx: wrapper.clone(), + inner_tx: tx.inner_tx, #[cfg(not(feature = "mainnet"))] has_valid_pow, }); @@ -453,6 +454,7 @@ where #[cfg(test)] mod test_finalize_block { use namada::types::storage::Epoch; + use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; use super::*; @@ -495,12 +497,14 @@ 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); } if i != 3 { @@ -560,6 +564,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 wrapper = WrapperTx::new( Fee { amount: 0.into(), @@ -568,11 +574,10 @@ 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 +591,7 @@ mod test_finalize_block { info: "".into(), }, }; - shell.enqueue_tx(wrapper); + shell.enqueue_tx(wrapper, Some(encrypted_raw_tx)); // check that the decrypted tx was not applied for event in shell @@ -626,7 +631,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 +646,7 @@ mod test_finalize_block { }, }; - shell.enqueue_tx(wrapper); + shell.enqueue_tx(wrapper, Some(inner_tx)); // check that correct error message is returned for event in shell @@ -696,6 +700,8 @@ 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 +710,11 @@ 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)); processed_txs.push(ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx: raw_tx, @@ -741,12 +746,14 @@ 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..1106840c49 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() } @@ -773,6 +773,7 @@ mod test_utils { use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; + use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{Fee, WrapperTx}; use tempfile::tempdir; use tokio::sync::mpsc::UnboundedReceiver; @@ -921,9 +922,14 @@ 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) { - self.shell.storage.tx_queue.push(WrapperTxInQueue { + pub fn enqueue_tx( + &mut self, + wrapper: WrapperTx, + inner_tx: Option, + ) { + self.shell.storage.tx_queue.push(TxInQueue { tx: wrapper, + inner_tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, }); @@ -992,6 +998,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 wrapper = WrapperTx::new( Fee { amount: 0.into(), @@ -1000,13 +1008,13 @@ mod test_utils { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.storage.tx_queue.push(WrapperTxInQueue { + ) + .bind(tx); + shell.storage.tx_queue.push(TxInQueue { tx: wrapper, + inner_tx: Some(encrypted_tx), #[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..9423d2a13c 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,19 +99,27 @@ where // decrypt the wrapper txs included in the previous block let decrypted_txs = self.storage.tx_queue.iter().map( - |WrapperTxInQueue { + |TxInQueue { tx, + inner_tx, #[cfg(not(feature = "mainnet"))] has_valid_pow, }| { - Tx::from(match tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, + Tx::from( + match inner_tx + .clone() + .and_then(|x| tx.decrypt(privkey, x).ok()) + { + 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 + None => DecryptedTx::Undecryptable(tx.clone()), }, - _ => DecryptedTx::Undecryptable(tx.clone()), - }) + ) .to_bytes() }, ); @@ -230,15 +238,15 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) + .bind(tx.clone()) .try_to_vec() .expect("Test failed"), ), ) + .attach_inner_tx(&tx, Default::default()) .to_bytes(); #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { @@ -290,13 +298,15 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); - shell.enqueue_tx(wrapper_tx); + ) + .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()); 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..1b4308e5e3 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, @@ -87,7 +87,7 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - 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(), @@ -108,8 +108,9 @@ where .into(), }, TxType::Decrypted(tx) => match tx_queue_iter.next() { - Some(WrapperTxInQueue { + Some(TxInQueue { tx: wrapper, + inner_tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, }) => { @@ -121,7 +122,11 @@ where determined in the previous block" .into(), } - } else if verify_decrypted_correctly(&tx, privkey) { + } else if verify_decrypted_correctly( + &tx, + privkey, + inner_tx.clone(), + ) { TxResult { code: ErrorCodes::Ok.into(), info: "Process Proposal accepted this \ @@ -143,9 +148,9 @@ where .into(), }, }, - TxType::Wrapper(tx) => { + TxType::Wrapper(wtx) => { // validate the ciphertext via Ferveo - if !tx.validate_ciphertext() { + if tx.inner_tx.is_none() || !tx.validate_ciphertext() { TxResult { code: ErrorCodes::InvalidTx.into(), info: format!( @@ -159,19 +164,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; @@ -244,15 +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")), ) + .attach_inner_tx(&tx, Default::default()) .to_bytes(); #[allow(clippy::redundant_clone)] let request = ProcessProposal { @@ -293,13 +298,13 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) + .bind(tx.clone()) .sign(&keypair) - .expect("Test failed"); + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); let new_tx = if let Some(Ok(SignedTxData { data: Some(data), sig, @@ -333,6 +338,8 @@ mod test_process_proposal { .expect("Test failed"), ), timestamp, + inner_tx: tx.inner_tx, + extra: None, } } else { panic!("Test failed"); @@ -377,13 +384,13 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) + .bind(tx.clone()) .sign(&keypair) - .expect("Test failed"); + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], }; @@ -433,13 +440,13 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) + .bind(tx.clone()) .sign(&keypair) - .expect("Test failed"); + .expect("Test failed") + .attach_inner_tx(&tx, Default::default()); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], @@ -475,6 +482,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 wrapper = WrapperTx::new( Fee { amount: i.into(), @@ -483,12 +492,11 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.enqueue_tx(wrapper); + ) + .bind(tx.clone()); + shell.enqueue_tx(wrapper, Some(encrypted_tx.clone())); txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] @@ -553,15 +561,17 @@ 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.clone(), + ))) + .attach_inner_tx(&tx, Default::default()); - let tx = - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); + shell.enqueue_tx(wrapper, tx.inner_tx.clone()); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -614,18 +624,18 @@ 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, tx.inner_tx.clone()); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -669,13 +679,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)); let signed = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] wrapper.clone(), 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 3271037595..b2eb0e534b 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -15,10 +15,14 @@ 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(), + code, 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 40e343d1bf..975d5ea9b4 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,18 +17,26 @@ 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::EllipticCurve; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::EncryptionKey; +#[cfg(feature = "ferveo-tpke")] use crate::types::transaction::TxType; #[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")] @@ -124,107 +136,22 @@ 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. -#[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, -)] -pub struct SigningTx { - pub code_hash: [u8; 32], - pub data: Option>, - pub timestamp: DateTimeUtc, -} - -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, - } - .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, - } - } - - /// 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, - }) - } else { - None - } - } -} - -impl From for SigningTx { - fn from(tx: Tx) -> SigningTx { - SigningTx { - code_hash: hash_tx(&tx.code).0, - data: tx.data, - timestamp: tx.timestamp, - } - } -} - /// 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. #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, + Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, )] 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, + #[cfg(not(feature = "ferveo-tpke"))] + pub inner_tx: Option>, } impl TryFrom<&[u8]> for Tx { @@ -236,10 +163,19 @@ 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()?; Ok(Tx { code: tx.code, data: tx.data, + extra: tx.extra, timestamp, + inner_tx, }) } } @@ -247,10 +183,16 @@ 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") + }); types::Tx { code: tx.code, data: tx.data, + extra: tx.extra, timestamp, + inner_tx, } } } @@ -347,6 +289,8 @@ impl Tx { code, data, timestamp: DateTimeUtc::now(), + inner_tx: None, + extra: None, } } @@ -358,21 +302,45 @@ impl Tx { bytes } - pub fn hash(&self) -> [u8; 32] { - SigningTx::from(self.clone()).hash() + /// 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: 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, + } + .encode(&mut bytes) + .expect("encoding a transaction failed"); + hash_tx(&bytes).0 } + /// Get the hash of this transaction's code pub fn code_hash(&self) -> [u8; 32] { - SigningTx::from(self.clone()).code_hash + hash_tx(&self.code).0 } /// Sign a transaction using [`SignedTxData`]. pub fn sign(self, keypair: &common::SecretKey) -> Self { - let code = self.code.clone(); - SigningTx::from(self) - .sign(keypair) - .expand(code) - .expect("code hashes to unexpected value") + let to_sign = self.partial_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), + extra: self.extra, + timestamp: self.timestamp, + inner_tx: self.inner_tx, + } } /// Verify that the transaction has been signed by the secret key @@ -382,7 +350,44 @@ 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(), + extra: self.extra.clone(), + data, + timestamp: self.timestamp, + inner_tx: self.inner_tx.clone(), + }; + 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, + tx: &Tx, + encryption_key: EncryptionKey, + ) -> Self { + let inner_tx = EncryptedTx::encrypt(&tx.to_bytes(), encryption_key); + self.inner_tx = Some(inner_tx); + self + } + + /// A validity check on the ciphertext. + #[cfg(feature = "ferveo-tpke")] + pub fn validate_ciphertext(&self) -> bool { + // Check the inner_tx ciphertext if it is there + self.inner_tx.as_ref().map(|inner_tx| { + inner_tx.0.check(&::G1Prepared::from( + -::G1Affine::prime_subgroup_generator(), + )) + }).unwrap_or(true) } } @@ -479,6 +484,8 @@ mod tests { code, 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/internal.rs b/core/src/types/internal.rs index 8c85a4236e..ec229f97ac 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -48,12 +48,16 @@ impl From for HostEnvResult { 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. #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] - pub struct WrapperTxInQueue { + pub struct TxInQueue { /// Wrapper tx pub tx: crate::types::transaction::WrapperTx, + /// the encrypted payload + pub inner_tx: Option, #[cfg(not(feature = "mainnet"))] /// A PoW solution can be used to allow zero-fee testnet /// transactions. @@ -64,23 +68,21 @@ 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 { + pub fn iter(&self) -> impl std::iter::Iterator { self.0.iter() } @@ -93,4 +95,4 @@ mod tx_queue { } #[cfg(feature = "ferveo-tpke")] -pub use tx_queue::{TxQueue, WrapperTxInQueue}; +pub use tx_queue::{TxInQueue, TxQueue}; diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 3ac49efc77..9519ae3259 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -11,7 +11,8 @@ 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; + use crate::types::transaction::{Hash, TxType, WrapperTx}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[allow(clippy::large_enum_variant)] @@ -63,7 +64,7 @@ pub mod decrypted_tx { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, - } => hash_tx(&tx.to_bytes()), + } => Hash(tx.partial_hash()), DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), } } @@ -75,10 +76,17 @@ pub mod decrypted_tx { pub fn verify_decrypted_correctly( decrypted: &DecryptedTx, privkey: ::G2Affine, + inner_tx: Option, ) -> bool { match decrypted { + // A tx is decryptable if it contains the literal code inside it DecryptedTx::Decrypted { .. } => true, - DecryptedTx::Undecryptable(tx) => tx.decrypt(privkey).is_err(), + // 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, } } diff --git a/core/src/types/transaction/encrypted.rs b/core/src/types/transaction/encrypted.rs index b73868bbba..5cfc6b64ba 100644 --- a/core/src/types/transaction/encrypted.rs +++ b/core/src/types/transaction/encrypted.rs @@ -66,6 +66,18 @@ 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 +142,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 0e0a5e980e..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. @@ -293,15 +291,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: tx.inner_tx.clone(), + extra: tx.extra.clone(), } - .hash(); + .partial_hash(); match TxType::try_from(Tx { - code: vec![], + code: tx.code, data: Some(data), timestamp: tx.timestamp, + inner_tx: tx.inner_tx, + extra: tx.extra, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { @@ -421,7 +423,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 +431,18 @@ pub mod tx_types { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) + .bind(tx.clone()) .sign(&keypair) - .expect("Test failed"); + .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 +455,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 +468,6 @@ pub mod tx_types { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ); @@ -477,7 +477,8 @@ 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..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_tx, EncryptionKey, Hash, TxError, TxType, - }; + use crate::types::transaction::{Hash, TxError, TxType}; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -173,8 +171,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 +189,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, } @@ -218,13 +210,6 @@ pub mod wrapper_tx { Address::from(&self.pk) } - /// A validity check on the ciphertext. - pub fn validate_ciphertext(&self) -> bool { - self.inner_tx.0.check(&::G1Prepared::from( - -::G1Affine::prime_subgroup_generator(), - )) - } - /// Decrypt the wrapped transaction. /// /// Will fail if the inner transaction does match the @@ -233,19 +218,27 @@ 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); + // 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.partial_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.partial_hash()); + self + } + /// Sign the wrapper transaction and convert to a normal Tx type pub fn sign( &self, @@ -348,6 +341,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; @@ -375,14 +369,19 @@ 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()); + 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 decrypted = wrapper.decrypt(privkey).expect("Test failed"); + 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); } @@ -390,6 +389,7 @@ 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()), @@ -400,19 +400,24 @@ pub mod wrapper_tx { amount: 10.into(), token: nam(), }, - &gen_keypair(), + &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()); + 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 err = wrapper.decrypt(privkey).expect_err("Test failed"); + 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); } @@ -437,13 +442,13 @@ pub mod wrapper_tx { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, ) + .bind(tx.clone()) .sign(&keypair) - .expect("Test failed"); + .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) = @@ -456,7 +461,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 @@ -464,19 +469,20 @@ 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), ); // We change the commitment appropriately - wrapper.tx_hash = hash_tx(&malicious.to_bytes()); + wrapper.tx_hash = Hash(malicious.partial_hash()); // we check ciphertext validity still passes - assert!(wrapper.validate_ciphertext()); + assert!(tx.validate_ciphertext()); // 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..ec2a8aaf12 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 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 4ab7b6eca7..3c8d6dd2de 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + extra: 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, + 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 c36511452a..8ae7d47a1b 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.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(()) diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 3a83b28632..2135302ef6 100755 Binary files a/wasm_for_tests/tx_memory_limit.wasm and b/wasm_for_tests/tx_memory_limit.wasm differ diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index cb7822f98c..58f6b1d127 100755 Binary files a/wasm_for_tests/tx_mint_tokens.wasm and b/wasm_for_tests/tx_mint_tokens.wasm differ diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index e32dbb1fb3..12d4cfc873 100755 Binary files a/wasm_for_tests/tx_proposal_code.wasm and b/wasm_for_tests/tx_proposal_code.wasm differ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index dace8bca09..35d1c72f79 100755 Binary files a/wasm_for_tests/tx_read_storage_key.wasm and b/wasm_for_tests/tx_read_storage_key.wasm differ diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index e9cfb959e9..3eeaa72df1 100755 Binary files a/wasm_for_tests/tx_write_storage_key.wasm and b/wasm_for_tests/tx_write_storage_key.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 67b7d77474..aaec1799c5 100755 Binary files a/wasm_for_tests/vp_always_false.wasm and b/wasm_for_tests/vp_always_false.wasm differ diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 86c64710ce..56cba5be28 100755 Binary files a/wasm_for_tests/vp_always_true.wasm and b/wasm_for_tests/vp_always_true.wasm differ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 04ad77434b..553a713de1 100755 Binary files a/wasm_for_tests/vp_eval.wasm and b/wasm_for_tests/vp_eval.wasm differ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index e19204b86f..d105870aef 100755 Binary files a/wasm_for_tests/vp_memory_limit.wasm and b/wasm_for_tests/vp_memory_limit.wasm differ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 781e9e365b..57765232fd 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ