diff --git a/.changelog/unreleased/improvements/2163-init-validator-own-keys.md b/.changelog/unreleased/improvements/2163-init-validator-own-keys.md new file mode 100644 index 0000000000..e5660d1861 --- /dev/null +++ b/.changelog/unreleased/improvements/2163-init-validator-own-keys.md @@ -0,0 +1,2 @@ +- Require to verify ownership of all validator keys when initialized on-chain. + ([\#2163](https://github.com/anoma/namada/pull/2163)) \ No newline at end of file diff --git a/apps/src/lib/bench_utils.rs b/apps/src/lib/bench_utils.rs index 6c2eda6889..f6213e7920 100644 --- a/apps/src/lib/bench_utils.rs +++ b/apps/src/lib/bench_utils.rs @@ -241,7 +241,7 @@ impl Default for BenchShell { bond, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); bench_shell.execute_tx(&signed_tx); @@ -264,7 +264,7 @@ impl Default for BenchShell { }, None, Some(vec![content_section]), - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); bench_shell.execute_tx(&signed_tx); @@ -292,7 +292,7 @@ impl BenchShell { data: impl BorshSerialize, shielded: Option, extra_sections: Option>, - signer: Option<&SecretKey>, + signers: Vec<&SecretKey>, ) -> Tx { let mut tx = Tx::from_type(namada::types::transaction::TxType::Decrypted( @@ -321,7 +321,7 @@ impl BenchShell { } } - if let Some(signer) = signer { + for signer in signers { tx.add_section(Section::Signature(Signature::new( vec![tx.raw_header_hash()], [(0, signer.clone())].into_iter().collect(), @@ -894,7 +894,7 @@ impl BenchShieldedCtx { }, shielded, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2c7e9de6d2..cc972ca5d0 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -28,7 +28,9 @@ use rand::rngs::OsRng; use super::rpc; use crate::cli::{args, safe_exit}; use crate::client::rpc::query_wasm_code_hash; -use crate::client::tx::signing::{default_sign, SigningTxData}; +use crate::client::tx::signing::{ + default_sign, init_validator_signing_data, SigningTxData, +}; use crate::client::tx::tx::ProcessTxResponse; use crate::config::TendermintMode; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -504,7 +506,7 @@ pub async fn submit_init_validator<'a>( let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = validator_consensus_key(&alias.clone().into()); - // let consensus_key_alias = format!("{}-consensus-key", alias); + let protocol_key_alias = format!("{}-protocol-key", alias); let threshold = match threshold { Some(threshold) => threshold, @@ -619,7 +621,25 @@ pub async fn submit_init_validator<'a>( scheme, ) .unwrap(); - let protocol_key = validator_keys.get_protocol_keypair().ref_to(); + let protocol_sk = validator_keys.get_protocol_keypair(); + let protocol_key = protocol_sk.to_public(); + + // Store the protocol key in the wallet so that we can sign the tx with it + // to verify ownership + display_line!(namada.io(), "Storing protocol key in the wallet..."); + let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); + namada + .wallet_mut() + .await + .insert_keypair( + protocol_key_alias, + tx_args.wallet_alias_force, + protocol_sk.clone(), + password, + None, + None, + ) + .map_err(|err| error::Error::Other(err.to_string()))?; let validator_vp_code_hash = query_wasm_code_hash(namada, validator_vp_code_path.to_str().unwrap()) @@ -688,9 +708,17 @@ pub async fn submit_init_validator<'a>( validator_vp_code_hash: extra_section_hash, }; + // Put together all the PKs that we have to sign with to verify ownership + let mut all_pks = data.account_keys.clone(); + all_pks.push(consensus_key.to_public()); + all_pks.push(eth_cold_pk); + all_pks.push(eth_hot_pk); + all_pks.push(data.protocol_key.clone()); + tx.add_code_from_hash(tx_code_hash).add_data(data); - let signing_data = aux_signing_data(namada, &tx_args, None, None).await?; + let signing_data = + init_validator_signing_data(namada, &tx_args, all_pks).await?; tx::prepare_tx( namada, diff --git a/benches/host_env.rs b/benches/host_env.rs index dd82b1ba6f..3b096f11ec 100644 --- a/benches/host_env.rs +++ b/benches/host_env.rs @@ -31,7 +31,7 @@ fn tx_section_signature_validation(c: &mut Criterion) { transfer_data, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let section_hash = tx.header_hash(); @@ -48,7 +48,12 @@ fn tx_section_signature_validation(c: &mut Criterion) { c.bench_function("tx_section_signature_validation", |b| { b.iter(|| { multisig - .verify_signature(&mut HashSet::new(), &pkim, &None, &mut None) + .verify_signature( + &mut HashSet::new(), + &pkim, + &None, + &mut || Ok(()), + ) .unwrap() }) }); diff --git a/benches/native_vps.rs b/benches/native_vps.rs index b72de2d29e..a1527af096 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -88,7 +88,7 @@ fn governance(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) } "validator_vote" => { @@ -104,7 +104,7 @@ fn governance(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::albert_keypair()], ) } "minimal_proposal" => { @@ -131,7 +131,7 @@ fn governance(c: &mut Criterion) { }, None, Some(vec![content_section]), - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) } "complete_proposal" => { @@ -183,7 +183,7 @@ fn governance(c: &mut Criterion) { }, None, Some(vec![content_section, wasm_code_section]), - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) } _ => panic!("Unexpected bench test"), @@ -423,7 +423,7 @@ fn vp_multitoken(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); for (signed_tx, bench_name) in [foreign_key_write, transfer] @@ -624,7 +624,7 @@ fn pgf(c: &mut Criterion) { defaults::albert_address(), None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ), "steward_inflation_rate" => { let data = @@ -640,7 +640,7 @@ fn pgf(c: &mut Criterion) { data, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) } _ => panic!("Unexpected bench test"), @@ -712,7 +712,7 @@ fn eth_bridge_nut(c: &mut Criterion) { data, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) }; @@ -781,7 +781,7 @@ fn eth_bridge(c: &mut Criterion) { data, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) }; @@ -878,7 +878,7 @@ fn eth_bridge_pool(c: &mut Criterion) { data, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) }; diff --git a/benches/process_wrapper.rs b/benches/process_wrapper.rs index dde61b0943..d8187e11ad 100644 --- a/benches/process_wrapper.rs +++ b/benches/process_wrapper.rs @@ -30,7 +30,7 @@ fn process_tx(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); tx.update_header(namada::types::transaction::TxType::Wrapper(Box::new( diff --git a/benches/txs.rs b/benches/txs.rs index 7ef018e504..ee80f7d6f3 100644 --- a/benches/txs.rs +++ b/benches/txs.rs @@ -148,7 +148,7 @@ fn bond(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let self_bond = shell.generate_tx( @@ -160,7 +160,7 @@ fn bond(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); for (signed_tx, bench_name) in @@ -191,7 +191,7 @@ fn unbond(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let self_unbond = shell.generate_tx( @@ -203,7 +203,7 @@ fn unbond(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); for (signed_tx, bench_name) in @@ -233,7 +233,7 @@ fn withdraw(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let self_withdraw = shell.generate_tx( @@ -244,7 +244,7 @@ fn withdraw(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); for (signed_tx, bench_name) in [withdraw, self_withdraw] @@ -267,7 +267,7 @@ fn withdraw(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ), "self_withdraw" => shell.generate_tx( TX_UNBOND_WASM, @@ -278,7 +278,7 @@ fn withdraw(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ), _ => panic!("Unexpected bench test"), }; @@ -322,7 +322,7 @@ fn redelegate(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) }; @@ -356,7 +356,7 @@ fn reveal_pk(c: &mut Criterion) { new_implicit_account.to_public(), None, None, - None, + vec![], ); c.bench_function("reveal_pk", |b| { @@ -390,7 +390,7 @@ fn update_account(c: &mut Criterion) { data, None, Some(vec![extra_section]), - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); c.bench_function("update_account", |b| { @@ -430,7 +430,7 @@ fn init_account(c: &mut Criterion) { data, None, Some(vec![extra_section]), - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); c.bench_function("init_account", |b| { @@ -468,7 +468,7 @@ fn init_proposal(c: &mut Criterion) { }, None, Some(vec![content_section]), - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) } "complete_proposal" => { @@ -520,7 +520,7 @@ fn init_proposal(c: &mut Criterion) { }, None, Some(vec![content_section, wasm_code_section]), - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ) } _ => panic!("unexpected bench test"), @@ -550,7 +550,7 @@ fn vote_proposal(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let validator_vote = shell.generate_tx( @@ -563,7 +563,7 @@ fn vote_proposal(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); for (signed_tx, bench_name) in [delegator_vote, validator_vote] @@ -584,31 +584,28 @@ fn vote_proposal(c: &mut Criterion) { fn init_validator(c: &mut Criterion) { let mut csprng = rand::rngs::OsRng {}; - let consensus_key: common::PublicKey = - secp256k1::SigScheme::generate(&mut csprng) - .try_to_sk::() - .unwrap() - .to_public(); - - let eth_cold_key = secp256k1::PublicKey::try_from_pk( - &secp256k1::SigScheme::generate(&mut csprng) - .try_to_sk::() - .unwrap() - .to_public(), - ) - .unwrap(); - let eth_hot_key = secp256k1::PublicKey::try_from_pk( - &secp256k1::SigScheme::generate(&mut csprng) - .try_to_sk::() - .unwrap() - .to_public(), - ) - .unwrap(); - let protocol_key: common::PublicKey = - secp256k1::SigScheme::generate(&mut csprng) - .try_to_sk::() - .unwrap() - .to_public(); + let consensus_key_sk = ed25519::SigScheme::generate(&mut csprng) + .try_to_sk::() + .unwrap(); + let consensus_key = consensus_key_sk.to_public(); + + let eth_cold_key_sk = &secp256k1::SigScheme::generate(&mut csprng) + .try_to_sk::() + .unwrap(); + let eth_cold_key = + secp256k1::PublicKey::try_from_pk(ð_cold_key_sk.to_public()) + .unwrap(); + + let eth_hot_key_sk = &secp256k1::SigScheme::generate(&mut csprng) + .try_to_sk::() + .unwrap(); + let eth_hot_key = + secp256k1::PublicKey::try_from_pk(ð_hot_key_sk.to_public()).unwrap(); + + let protocol_key_sk = ed25519::SigScheme::generate(&mut csprng) + .try_to_sk::() + .unwrap(); + let protocol_key = protocol_key_sk.to_public(); let shell = BenchShell::default(); let validator_vp_code_hash: Hash = shell @@ -642,7 +639,13 @@ fn init_validator(c: &mut Criterion) { data, None, Some(vec![extra_section]), - Some(&defaults::albert_keypair()), + vec![ + &defaults::albert_keypair(), + &consensus_key_sk, + eth_cold_key_sk, + eth_hot_key_sk, + &protocol_key_sk, + ], ); c.bench_function("init_validator", |b| { @@ -664,7 +667,7 @@ fn change_validator_commission(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::albert_keypair()], ); c.bench_function("change_validator_commission", |b| { @@ -721,7 +724,7 @@ fn change_validator_metadata(c: &mut Criterion) { metadata_change, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::albert_keypair()], ); c.bench_function("change_validator_metadata", |b| { @@ -815,7 +818,7 @@ fn unjail_validator(c: &mut Criterion) { defaults::validator_address(), None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::albert_keypair()], ); c.bench_function("unjail_validator", |b| { @@ -876,7 +879,7 @@ fn tx_bridge_pool(c: &mut Criterion) { data, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); c.bench_function("bridge pool", |b| { b.iter_batched_ref( @@ -905,7 +908,7 @@ fn resign_steward(c: &mut Criterion) { defaults::albert_address(), None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); (shell, tx) @@ -942,7 +945,7 @@ fn update_steward_commission(c: &mut Criterion) { data, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); (shell, tx) @@ -960,7 +963,7 @@ fn deactivate_validator(c: &mut Criterion) { defaults::validator_address(), None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::albert_keypair()], ); c.bench_function("deactivate_validator", |b| { @@ -979,7 +982,7 @@ fn reactivate_validator(c: &mut Criterion) { defaults::validator_address(), None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::albert_keypair()], ); c.bench_function("reactivate_validator", |b| { @@ -1024,7 +1027,7 @@ fn claim_rewards(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let self_claim = shell.generate_tx( @@ -1035,7 +1038,7 @@ fn claim_rewards(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::albert_keypair()], ); for (signed_tx, bench_name) in diff --git a/benches/vps.rs b/benches/vps.rs index 16883d3547..9c1f188f03 100644 --- a/benches/vps.rs +++ b/benches/vps.rs @@ -52,7 +52,7 @@ fn vp_user(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let received_transfer = shell.generate_tx( @@ -67,7 +67,7 @@ fn vp_user(c: &mut Criterion) { }, None, None, - Some(&defaults::bertha_keypair()), + vec![&defaults::bertha_keypair()], ); let vp_validator_hash = shell @@ -90,7 +90,7 @@ fn vp_user(c: &mut Criterion) { data, None, Some(vec![extra_section]), - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let vote = shell.generate_tx( @@ -103,7 +103,7 @@ fn vp_user(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); let pos = shell.generate_tx( @@ -115,7 +115,7 @@ fn vp_user(c: &mut Criterion) { }, None, None, - Some(&defaults::albert_keypair()), + vec![&defaults::albert_keypair()], ); for (signed_tx, bench_name) in [ @@ -196,7 +196,7 @@ fn vp_implicit(c: &mut Criterion) { }, None, None, - Some(&implicit_account), + vec![&implicit_account], ); let received_transfer = shell.generate_tx( @@ -211,7 +211,7 @@ fn vp_implicit(c: &mut Criterion) { }, None, None, - Some(&defaults::bertha_keypair()), + vec![&defaults::bertha_keypair()], ); let reveal_pk = shell.generate_tx( @@ -219,7 +219,7 @@ fn vp_implicit(c: &mut Criterion) { &implicit_account.to_public(), None, None, - None, + vec![], ); let pos = shell.generate_tx( @@ -231,7 +231,7 @@ fn vp_implicit(c: &mut Criterion) { }, None, None, - Some(&implicit_account), + vec![&implicit_account], ); let vote = shell.generate_tx( @@ -245,7 +245,7 @@ fn vp_implicit(c: &mut Criterion) { }, None, None, - Some(&implicit_account), + vec![&implicit_account], ); for (tx, bench_name) in [ @@ -339,7 +339,7 @@ fn vp_validator(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); let received_transfer = shell.generate_tx( @@ -354,7 +354,7 @@ fn vp_validator(c: &mut Criterion) { }, None, None, - Some(&defaults::bertha_keypair()), + vec![&defaults::bertha_keypair()], ); let extra_section = Section::ExtraData(Code::from_hash(vp_code_hash)); @@ -374,7 +374,7 @@ fn vp_validator(c: &mut Criterion) { data, None, Some(vec![extra_section]), - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); let commission_rate = shell.generate_tx( @@ -385,7 +385,7 @@ fn vp_validator(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); let vote = shell.generate_tx( @@ -398,7 +398,7 @@ fn vp_validator(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); let pos = shell.generate_tx( @@ -410,7 +410,7 @@ fn vp_validator(c: &mut Criterion) { }, None, None, - Some(&defaults::validator_keypair()), + vec![&defaults::validator_keypair()], ); for (signed_tx, bench_name) in [ diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 5f6ded8e7c..1ac9830ad6 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -20,7 +20,7 @@ use sha2::{Digest, Sha256}; use thiserror::Error; use super::generated::types; -use crate::ledger::gas::{self, GasMetering, VpGasMeter, VERIFY_TX_SIG_GAS}; +use crate::ledger::gas; use crate::ledger::storage::{KeccakHasher, Sha256Hasher, StorageHasher}; use crate::types::account::AccountPublicKeysMap; use crate::types::address::Address; @@ -552,13 +552,16 @@ impl Signature { } /// Verify that the signature contained in this section is valid - pub fn verify_signature( + pub fn verify_signature( &self, verified_pks: &mut HashSet, public_keys_index_map: &AccountPublicKeysMap, signer: &Option
, - gas_meter: &mut Option<&mut VpGasMeter>, - ) -> std::result::Result { + consume_verify_sig_gas: &mut F, + ) -> std::result::Result + where + F: FnMut() -> std::result::Result<(), crate::ledger::gas::Error>, + { // Records whether there are any successful verifications let mut verifications = 0; match &self.signer { @@ -569,11 +572,7 @@ impl Signature { if let Some(pk) = public_keys_index_map.get_public_key_from_index(*idx) { - if let Some(meter) = gas_meter { - meter - .consume(VERIFY_TX_SIG_GAS) - .map_err(VerifySigError::OutOfGas)?; - } + consume_verify_sig_gas()?; common::SigScheme::verify_signature( &pk, &self.get_raw_hash(), @@ -594,11 +593,7 @@ impl Signature { if let Some(map_idx) = public_keys_index_map.get_index_from_public_key(pk) { - if let Some(meter) = gas_meter { - meter - .consume(VERIFY_TX_SIG_GAS) - .map_err(VerifySigError::OutOfGas)?; - } + consume_verify_sig_gas()?; common::SigScheme::verify_signature( pk, &self.get_raw_hash(), @@ -1245,15 +1240,18 @@ impl Tx { /// Verify that the section with the given hash has been signed by the given /// public key - pub fn verify_signatures( + pub fn verify_signatures( &self, hashes: &[crate::types::hash::Hash], public_keys_index_map: AccountPublicKeysMap, signer: &Option
, threshold: u8, max_signatures: Option, - gas_meter: &mut Option<&mut VpGasMeter>, - ) -> std::result::Result, Error> { + mut consume_verify_sig_gas: F, + ) -> std::result::Result, Error> + where + F: FnMut() -> std::result::Result<(), crate::ledger::gas::Error>, + { let max_signatures = max_signatures.unwrap_or(u8::MAX); // Records the public key indices used in successful signatures let mut verified_pks = HashSet::new(); @@ -1284,7 +1282,7 @@ impl Tx { &mut verified_pks, &public_keys_index_map, signer, - gas_meter, + &mut consume_verify_sig_gas, ) .map_err(|e| { if let VerifySigError::OutOfGas(inner) = e { @@ -1314,6 +1312,8 @@ impl Tx { /// Verify that the sections with the given hashes have been signed together /// by the given public key. I.e. this function looks for one signature that /// covers over the given slice of hashes. + /// Note that this method doesn't consider gas cost and hence it shouldn't + /// be used from txs or VPs. pub fn verify_signature( &self, public_key: &common::PublicKey, @@ -1325,7 +1325,7 @@ impl Tx { &None, 1, None, - &mut None, + || Ok(()), ) .map(|x| *x.first().unwrap()) .map_err(|_| Error::InvalidWrapperSignature) diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 6f1a0eca68..b7a426607a 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -123,7 +123,7 @@ pub enum VerifySigError { #[error("Signature belongs to a different scheme from the public key.")] MismatchedScheme, #[error("Signature verification went out of gas: {0}")] - OutOfGas(crate::ledger::gas::Error), + OutOfGas(#[from] crate::ledger::gas::Error), } #[allow(missing_docs)] diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index c7634d5453..bb21d2f661 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -389,6 +389,51 @@ pub async fn aux_signing_data<'a>( }) } +pub async fn init_validator_signing_data<'a>( + context: &impl Namada<'a>, + args: &args::Tx, + validator_keys: Vec, +) -> Result { + let mut public_keys = if args.wrapper_fee_payer.is_none() { + tx_signers(context, args, None).await? + } else { + vec![] + }; + public_keys.extend(validator_keys.clone()); + + let account_public_keys_map = + Some(AccountPublicKeysMap::from_iter(validator_keys)); + + let fee_payer = if args.disposable_signing_key { + context + .wallet_mut() + .await + .gen_disposable_signing_key(&mut OsRng) + .to_public() + } else { + match &args.wrapper_fee_payer { + Some(keypair) => keypair.to_public(), + None => public_keys.get(0).ok_or(TxError::InvalidFeePayer)?.clone(), + } + }; + + if fee_payer == masp_tx_key().to_public() { + other_err( + "The gas payer cannot be the MASP, please provide a different gas \ + payer." + .to_string(), + )?; + } + + Ok(SigningTxData { + owner: None, + public_keys, + threshold: 0, + account_public_keys_map, + fee_payer, + }) +} + /// Informations about the post-tx balance of the tx's source. Used to correctly /// handle fee validation in the wrapper tx pub struct TxSourcePostBalance { diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 9dd19c1d74..2d18b9898b 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1928,7 +1928,7 @@ where &Some(signer), threshold, max_signatures, - &mut Some(gas_meter), + || gas_meter.consume(gas::VERIFY_TX_SIG_GAS), ) { Ok(_) => Ok(HostEnvResult::Success.to_i64()), Err(err) => match err { @@ -2042,6 +2042,80 @@ where sentinel.set_invalid_commitment(); } +/// Verify a transaction signature +#[allow(clippy::too_many_arguments)] +pub fn tx_verify_tx_section_signature( + env: &TxVmEnv, + hash_list_ptr: u64, + hash_list_len: u64, + public_keys_map_ptr: u64, + public_keys_map_len: u64, + threshold: u8, + max_signatures_ptr: u64, + max_signatures_len: u64, +) -> TxResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let (hash_list, gas) = env + .memory + .read_bytes(hash_list_ptr, hash_list_len as _) + .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; + + let sentinel = unsafe { env.ctx.sentinel.get() }; + let gas_meter = unsafe { env.ctx.gas_meter.get() }; + tx_charge_gas(env, gas)?; + let hashes = <[Hash; 1]>::try_from_slice(&hash_list) + .map_err(TxRuntimeError::EncodingError)?; + + let (public_keys_map, gas) = env + .memory + .read_bytes(public_keys_map_ptr, public_keys_map_len as _) + .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; + tx_charge_gas(env, gas)?; + let public_keys_map = + namada_core::types::account::AccountPublicKeysMap::try_from_slice( + &public_keys_map, + ) + .map_err(TxRuntimeError::EncodingError)?; + + tx_charge_gas(env, gas)?; + + let (max_signatures, gas) = env + .memory + .read_bytes(max_signatures_ptr, max_signatures_len as _) + .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; + tx_charge_gas(env, gas)?; + let max_signatures = Option::::try_from_slice(&max_signatures) + .map_err(TxRuntimeError::EncodingError)?; + + let tx = unsafe { env.ctx.tx.get() }; + + match tx.verify_signatures( + &hashes, + public_keys_map, + &None, + threshold, + max_signatures, + || gas_meter.consume(gas::VERIFY_TX_SIG_GAS), + ) { + Ok(_) => Ok(HostEnvResult::Success.to_i64()), + Err(err) => match err { + namada_core::proto::Error::OutOfGas(inner) => { + sentinel.set_out_of_gas(); + Err(TxRuntimeError::OutOfGas(inner)) + } + namada_core::proto::Error::InvalidSectionSignature(_) => { + Ok(HostEnvResult::Fail.to_i64()) + } + _ => Ok(HostEnvResult::Fail.to_i64()), + }, + } +} + /// Evaluate a validity predicate with the given input data. pub fn vp_eval( env: &VpVmEnv<'static, MEM, DB, H, EVAL, CA>, diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index fc5a5d5594..8c897f1a29 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -86,7 +86,8 @@ where "namada_tx_get_native_token" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_native_token), "namada_tx_log_string" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_log_string), "namada_tx_ibc_execute" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_ibc_execute), - "namada_tx_set_commitment_sentinel" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_set_commitment_sentinel) + "namada_tx_set_commitment_sentinel" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_set_commitment_sentinel), + "namada_tx_verify_tx_section_signature" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_verify_tx_section_signature), }, } } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 820624b5a9..b5895a7d26 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -36,7 +36,6 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; - use namada_core::ledger::gas::{TxGasMeter, VpGasMeter}; use namada_core::ledger::ibc::context::transfer_mod::testing::DummyTransferModule; use namada_core::ledger::ibc::Error as IbcActionError; use namada_test_utils::TestWasms; @@ -479,9 +478,7 @@ mod tests { &None, 1, None, - &mut Some(&mut VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(u64::MAX.into()) - )) + || Ok(()) ) .is_ok() ); @@ -497,9 +494,7 @@ mod tests { &None, 1, None, - &mut Some(&mut VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(u64::MAX.into()) - )) + || Ok(()) ) .is_err() ); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 96c1b67cda..605a54acae 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -507,4 +507,13 @@ mod native_tx_host_env { native_host_fn!(tx_log_string(str_ptr: u64, str_len: u64)); native_host_fn!(tx_charge_gas(used_gas: u64)); native_host_fn!("non-result", tx_set_commitment_sentinel()); + native_host_fn!(tx_verify_tx_section_signature( + hash_list_ptr: u64, + hash_list_len: u64, + public_keys_map_ptr: u64, + public_keys_map_len: u64, + threshold: u8, + max_signatures_ptr: u64, + max_signatures_len: u64, + ) -> i64); } diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 0bfefdec5e..71b56068ee 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -19,7 +19,6 @@ use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; pub use borsh_ext; use borsh_ext::BorshSerializeExt; -pub use namada_core::ledger::eth_bridge; pub use namada_core::ledger::governance::storage as gov_storage; pub use namada_core::ledger::parameters::storage as parameters_storage; pub use namada_core::ledger::storage::types::encode; @@ -28,11 +27,14 @@ pub use namada_core::ledger::storage_api::{ ResultExt, StorageRead, StorageWrite, }; pub use namada_core::ledger::tx_env::TxEnv; +pub use namada_core::ledger::{eth_bridge, parameters}; pub use namada_core::proto::{Section, Tx}; +use namada_core::types::account::AccountPublicKeysMap; pub use namada_core::types::address::Address; use namada_core::types::chain::CHAIN_ID_LENGTH; pub use namada_core::types::ethereum_events::EthAddress; use namada_core::types::internal::HostEnvResult; +use namada_core::types::key::common; use namada_core::types::storage::TxIndex; pub use namada_core::types::storage::{ self, BlockHash, BlockHeight, Epoch, Header, BLOCK_HASH_LENGTH, @@ -361,3 +363,36 @@ impl TxEnv for Ctx { pub fn tx_ibc_execute() { unsafe { namada_tx_ibc_execute() } } + +/// Verify section signatures against the given list of keys +pub fn verify_signatures_of_pks( + ctx: &Ctx, + tx: &Tx, + pks: Vec, +) -> EnvResult { + let max_signatures_per_transaction = + parameters::max_signatures_per_transaction(ctx)?; + + // Require signatures from all the given keys + let threshold = u8::try_from(pks.len()).into_storage_result()?; + let public_keys_index_map = AccountPublicKeysMap::from_iter(pks); + + // Serialize parameters + let max_signatures = max_signatures_per_transaction.serialize_to_vec(); + let public_keys_map = public_keys_index_map.serialize_to_vec(); + let targets = [tx.raw_header_hash()].serialize_to_vec(); + + let valid = unsafe { + namada_tx_verify_tx_section_signature( + targets.as_ptr() as _, + targets.len() as _, + public_keys_map.as_ptr() as _, + public_keys_map.len() as _, + threshold, + max_signatures.as_ptr() as _, + max_signatures.len() as _, + ) + }; + + Ok(HostEnvResult::is_success(valid)) +} diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 87331ee5ba..23fe3fa63a 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -117,6 +117,18 @@ pub mod tx { /// Set the sentinel for a wrong tx section commitment pub fn namada_tx_set_commitment_sentinel(); + + // Verify the signatures of a tx + pub fn namada_tx_verify_tx_section_signature( + hash_list_ptr: u64, + hash_list_len: u64, + public_keys_map_ptr: u64, + public_keys_map_len: u64, + threshold: u8, + max_signatures_ptr: u64, + max_signatures_len: u64, + ) -> i64; + } } diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 554b4a8ec8..0666ad7965 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -32,6 +32,22 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { .code .hash(); + // Check that the tx has been signed with all the keys to be used for the + // validator account + let mut all_pks = init_validator.account_keys.clone(); + all_pks.push(init_validator.consensus_key.clone()); + all_pks.push(key::common::PublicKey::Secp256k1( + init_validator.eth_cold_key.clone(), + )); + all_pks.push(key::common::PublicKey::Secp256k1( + init_validator.eth_hot_key.clone(), + )); + all_pks.push(init_validator.protocol_key.clone()); + if !matches!(verify_signatures_of_pks(ctx, &signed, all_pks), Ok(true)) { + debug_log!("Keys ownership signature verification failed"); + panic!() + } + // Register the validator in PoS match ctx.init_validator(init_validator, validator_vp_code_hash) { Ok(validator_address) => {