From 2c7524d3e86a1c86cde903162378f571ab85c33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 16:52:35 +0200 Subject: [PATCH 01/14] wasm: add vp_implicit from a copy of vp_user --- wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/Makefile | 1 + wasm/wasm_source/src/lib.rs | 3 + wasm/wasm_source/src/vp_implicit.rs | 757 ++++++++++++++++++++++++++++ 4 files changed, 762 insertions(+) create mode 100644 wasm/wasm_source/src/vp_implicit.rs diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 7f48f2aea6..180931cda5 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -25,6 +25,7 @@ tx_unbond = ["namada_tx_prelude"] tx_update_vp = ["namada_tx_prelude"] tx_vote_proposal = ["namada_tx_prelude"] tx_withdraw = ["namada_tx_prelude"] +vp_implicit = ["namada_vp_prelude", "once_cell", "rust_decimal"] vp_nft = ["namada_vp_prelude"] vp_testnet_faucet = ["namada_vp_prelude", "once_cell"] vp_token = ["namada_vp_prelude"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 48d4675ea6..97a1eba9c9 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -17,6 +17,7 @@ wasms += tx_transfer wasms += tx_unbond wasms += tx_update_vp wasms += tx_withdraw +wasms += vp_implicit wasms += vp_nft wasms += vp_testnet_faucet wasms += vp_token diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 8929992754..37a1d113b1 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -22,6 +22,9 @@ pub mod tx_update_vp; pub mod tx_vote_proposal; #[cfg(feature = "tx_withdraw")] pub mod tx_withdraw; + +#[cfg(feature = "vp_implicit")] +pub mod vp_implicit; #[cfg(feature = "vp_nft")] pub mod vp_nft; #[cfg(feature = "vp_testnet_faucet")] diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs new file mode 100644 index 0000000000..421222acef --- /dev/null +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -0,0 +1,757 @@ +//! Implicit account VP. All implicit accounts share this same VP. +//! +//! This VP currently provides a signature verification against a public key for +//! sending tokens (receiving tokens is permissive). +//! +//! It allows to bond, unbond and withdraw tokens to and from PoS system with a +//! valid signature. +//! +//! Any other storage key changes are allowed only with a valid signature. + +use namada_vp_prelude::storage::KeySeg; +use namada_vp_prelude::*; +use once_cell::unsync::Lazy; + +enum KeyType<'a> { + Token(&'a Address), + PoS, + Nft(&'a Address), + Vp(&'a Address), + GovernanceVote(&'a Address), + Unknown, +} + +impl<'a> From<&'a storage::Key> for KeyType<'a> { + fn from(key: &'a storage::Key) -> KeyType<'a> { + if let Some(address) = token::is_any_token_balance_key(key) { + Self::Token(address) + } else if let Some((_, address)) = + token::is_any_multitoken_balance_key(key) + { + Self::Token(address) + } else if proof_of_stake::is_pos_key(key) { + Self::PoS + } else if let Some(address) = nft::is_nft_key(key) { + Self::Nft(address) + } else if gov_storage::is_vote_key(key) { + let voter_address = gov_storage::get_voter_address(key); + if let Some(address) = voter_address { + Self::GovernanceVote(address) + } else { + Self::Unknown + } + } else if let Some(address) = key.is_validity_predicate() { + Self::Vp(address) + } else { + Self::Unknown + } + } +} + +#[validity_predicate] +fn validate_tx( + ctx: &Ctx, + tx_data: Vec, + addr: Address, + keys_changed: BTreeSet, + verifiers: BTreeSet
, +) -> VpResult { + debug_log!( + "vp_user called with user addr: {}, key_changed: {:?}, verifiers: {:?}", + addr, + keys_changed, + verifiers + ); + + let signed_tx_data = + Lazy::new(|| SignedTxData::try_from_slice(&tx_data[..])); + + let valid_sig = Lazy::new(|| match &*signed_tx_data { + Ok(signed_tx_data) => { + let pk = key::get(ctx, &addr); + match pk { + Ok(Some(pk)) => { + matches!( + ctx.verify_tx_signature(&pk, &signed_tx_data.sig), + Ok(true) + ) + } + _ => false, + } + } + _ => false, + }); + + if !is_valid_tx(ctx, &tx_data)? { + return reject(); + } + + for key in keys_changed.iter() { + let key_type: KeyType = key.into(); + let is_valid = match key_type { + KeyType::Token(owner) => { + if owner == &addr { + let pre: token::Amount = + ctx.read_pre(key)?.unwrap_or_default(); + let post: token::Amount = + ctx.read_post(key)?.unwrap_or_default(); + let change = post.change() - pre.change(); + // debit has to signed, credit doesn't + let valid = change >= 0 || *valid_sig; + debug_log!( + "token key: {}, change: {}, valid_sig: {}, valid \ + modification: {}", + key, + change, + *valid_sig, + valid + ); + valid + } else { + debug_log!( + "This address ({}) is not of owner ({}) of token key: \ + {}", + addr, + owner, + key + ); + // If this is not the owner, allow any change + true + } + } + KeyType::PoS => { + // Allow the account to be used in PoS + let bond_id = proof_of_stake::is_bond_key(key) + .or_else(|| proof_of_stake::is_unbond_key(key)); + let valid = match bond_id { + Some(bond_id) => { + // Bonds and unbonds changes for this address + // must be signed + bond_id.source != addr || *valid_sig + } + None => { + // Any other PoS changes are allowed without signature + true + } + }; + debug_log!( + "PoS key {} {}", + key, + if valid { "accepted" } else { "rejected" } + ); + valid + } + KeyType::Nft(owner) => { + if owner == &addr { + *valid_sig + } else { + true + } + } + KeyType::GovernanceVote(voter) => { + if voter == &addr { + *valid_sig + } else { + true + } + } + KeyType::Vp(owner) => { + let has_post: bool = ctx.has_key_post(key)?; + if owner == &addr { + if has_post { + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + *valid_sig && is_vp_whitelisted(ctx, &vp)? + } else { + false + } + } else { + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + is_vp_whitelisted(ctx, &vp)? + } + } + KeyType::Unknown => { + if key.segments.get(0) == Some(&addr.to_db_key()) { + // Unknown changes to this address space require a valid + // signature + *valid_sig + } else { + // Unknown changes anywhere else are permitted + true + } + } + }; + if !is_valid { + debug_log!("key {} modification failed vp", key); + return reject(); + } + } + + accept() +} + +#[cfg(test)] +mod tests { + use address::testing::arb_non_internal_address; + // Use this as `#[test]` annotation to enable logging + use namada_tests::log::test; + use namada_tests::tx::{self, tx_host_env, TestTxEnv}; + use namada_tests::vp::vp_host_env::storage::Key; + use namada_tests::vp::*; + use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::key::RefTo; + use proptest::prelude::*; + use storage::testing::arb_account_storage_key_no_vp; + + use super::*; + + const VP_ALWAYS_TRUE_WASM: &str = + "../../wasm_for_tests/vp_always_true.wasm"; + + /// Test that no-op transaction (i.e. no storage modifications) accepted. + #[test] + fn test_no_op_transaction() { + let tx_data: Vec = vec![]; + let addr: Address = address::testing::established_address_1(); + let keys_changed: BTreeSet = BTreeSet::default(); + let verifiers: BTreeSet
= BTreeSet::default(); + + // The VP env must be initialized before calling `validate_tx` + vp_host_env::init(); + + assert!( + validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap() + ); + } + + /// Test that a credit transfer is accepted. + #[test] + fn test_credit_transfer_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let source = address::testing::established_address_2(); + let token = address::xan(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &source, &token]); + + // Credit the tokens to the source before running the transaction to be + // able to transfer from it + tx_env.credit_tokens(&source, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + &source, + address, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a debit transfer without a valid signature is rejected. + #[test] + fn test_unsigned_debit_transfer_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let target = address::testing::established_address_2(); + let token = address::xan(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&vp_owner, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + address, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a debit transfer with a valid signature is accepted. + #[test] + fn test_signed_debit_transfer_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let target = address::testing::established_address_2(); + let token = address::xan(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&vp_owner, &token, amount); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + address, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a transfer on with accounts other than self is accepted. + #[test] + fn test_transfer_between_other_parties_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let source = address::testing::established_address_2(); + let target = address::testing::established_address_3(); + let token = address::xan(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&source, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + tx::ctx().insert_verifier(address).unwrap(); + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + &source, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + prop_compose! { + /// Generates an account address and a storage key inside its storage. + fn arb_account_storage_subspace_key() + // Generate an address + (address in arb_non_internal_address()) + // Generate a storage key other than its VP key (VP cannot be + // modified directly via `write`, it has to be modified via + // `tx::update_validity_predicate`. + (storage_key in arb_account_storage_key_no_vp(address.clone()), + // Use the generated address too + address in Just(address)) + -> (Address, Key) { + (address, storage_key) + } + } + + proptest! { + /// Test that an unsigned tx that performs arbitrary storage writes or + /// deletes to the account is rejected. + #[test] + fn test_unsigned_arb_storage_write_rejected( + (vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + // Spawn all the accounts in the storage key to be able to modify + // their storage + let storage_key_addresses = storage_key.find_addresses(); + tx_env.spawn_accounts(storage_key_addresses); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { + // Write or delete some data in the transaction + if let Some(value) = &storage_value { + tx::ctx().write(&storage_key, value).unwrap(); + } else { + tx::ctx().delete(&storage_key).unwrap(); + } + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + } + } + + proptest! { + /// Test that a signed tx that performs arbitrary storage writes or + /// deletes to the account is accepted. + #[test] + fn test_signed_arb_storage_write( + (vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + + // Spawn all the accounts in the storage key to be able to modify + // their storage + let storage_key_addresses = storage_key.find_addresses(); + tx_env.spawn_accounts(storage_key_addresses); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { + // Write or delete some data in the transaction + if let Some(value) = &storage_value { + tx::ctx().write(&storage_key, value).unwrap(); + } else { + tx::ctx().delete(&storage_key).unwrap(); + } + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + } + } + + /// Test that a validity predicate update without a valid signature is + /// rejected. + #[test] + fn test_unsigned_vp_update_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update with a valid signature is + /// accepted. + #[test] + fn test_signed_vp_update_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, None, None); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update is rejected if not whitelisted + #[test] + fn test_signed_vp_update_not_whitelisted_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update is accepted if whitelisted + #[test] + fn test_signed_vp_update_whitelisted_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + let vp_hash = sha256(&vp_code); + tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a tx is rejected if not whitelisted + #[test] + fn test_tx_not_whitelisted_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + let vp_hash = sha256(&vp_code); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + Some(vec!["some_hash".to_string()]), + ); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + #[test] + fn test_tx_whitelisted_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } +} From 893a473208b401e2d2f49bf38772c7b1d90a8719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 16:53:24 +0200 Subject: [PATCH 02/14] add implicit_vp to protocol parameters and genesis --- apps/src/lib/config/genesis.rs | 59 ++++++++++++++++-- apps/src/lib/node/ledger/shell/init_chain.rs | 45 ++++++++++++-- shared/src/ledger/parameters/mod.rs | 65 ++++++++++++++++---- shared/src/ledger/parameters/storage.rs | 19 ++++++ shared/src/ledger/storage/mod.rs | 3 +- 5 files changed, 167 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 9425e3b019..90ddd57f06 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -7,14 +7,14 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; use namada::ledger::governance::parameters::GovParams; -use namada::ledger::parameters::Parameters; +use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; -use namada::types::time::DateTimeUtc; +use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::{storage, token}; /// Genesis configuration file format @@ -28,7 +28,7 @@ pub mod genesis_config { use data_encoding::HEXLOWER; use eyre::Context; use namada::ledger::governance::parameters::GovParams; - use namada::ledger::parameters::{EpochDuration, Parameters}; + use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::address::Address; @@ -40,7 +40,8 @@ pub mod genesis_config { use thiserror::Error; use super::{ - EstablishedAccount, Genesis, ImplicitAccount, TokenAccount, Validator, + EstablishedAccount, Genesis, ImplicitAccount, Parameters, TokenAccount, + Validator, }; use crate::cli; @@ -233,6 +234,8 @@ pub mod genesis_config { // Hashes of whitelisted txs array. `None` value or an empty array // disables whitelisting. pub tx_whitelist: Option>, + /// Filename of implicit accounts validity predicate WASM code + pub implicit_vp: String, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -512,6 +515,18 @@ pub mod genesis_config { }) .collect(); + let implicit_vp_config = + wasms.get(&config.parameters.implicit_vp).unwrap(); + let implicit_vp_code_path = implicit_vp_config.filename.to_owned(); + let implicit_vp_sha256 = implicit_vp_config + .sha256 + .clone() + .unwrap_or_else(|| { + eprintln!("Unknown implicit VP WASM sha256"); + cli::safe_exit(1); + }) + .to_sha256_bytes() + .unwrap(); let parameters = Parameters { epoch_duration: EpochDuration { min_num_of_blocks: config.parameters.min_num_of_blocks, @@ -527,6 +542,8 @@ pub mod genesis_config { .into(), vp_whitelist: config.parameters.vp_whitelist.unwrap_or_default(), tx_whitelist: config.parameters.tx_whitelist.unwrap_or_default(), + implicit_vp_code_path, + implicit_vp_sha256, }; let gov_params = GovParams { @@ -714,6 +731,36 @@ pub struct ImplicitAccount { pub public_key: common::PublicKey, } +/// Protocol parameters. This is almost the same as +/// `ledger::parameters::Parameters`, but instead of having the `implicit_vp` +/// WASM code bytes, it only has the name and sha as the actual code is loaded +/// on `init_chain` +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, +)] +pub struct Parameters { + /// Epoch duration + pub epoch_duration: EpochDuration, + /// Maximum expected time per block + pub max_expected_time_per_block: DurationSecs, + /// Whitelisted validity predicate hashes + pub vp_whitelist: Vec, + /// Whitelisted tx hashes + pub tx_whitelist: Vec, + /// Implicit accounts validity predicate code WASM + pub implicit_vp_code_path: String, + /// Expected SHA-256 hash of the implicit VP + pub implicit_vp_sha256: [u8; 32], +} + #[cfg(not(feature = "dev"))] pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { let path = base_dir @@ -723,11 +770,11 @@ pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { } #[cfg(feature = "dev")] pub fn genesis() -> Genesis { - use namada::ledger::parameters::EpochDuration; use namada::types::address; use crate::wallet; + let vp_implicit_path = "vp_implicit.wasm"; let vp_token_path = "vp_token.wasm"; let vp_user_path = "vp_user.wasm"; @@ -772,6 +819,8 @@ pub fn genesis() -> Genesis { max_expected_time_per_block: namada::types::time::DurationSecs(30), vp_whitelist: vec![], tx_whitelist: vec![], + implicit_vp_code_path: vp_implicit_path.into(), + implicit_vp_sha256: Default::default(), }; let albert = EstablishedAccount { address: wallet::defaults::albert_address(), diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 8cb6842c50..83f6308da1 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::ledger::parameters::Parameters; use namada::types::key::*; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; @@ -57,16 +58,48 @@ where let genesis_time: DateTimeUtc = (Utc.timestamp(ts.seconds, ts.nanos as u32)).into(); - genesis.parameters.init_storage(&mut self.storage); + // Initialize protocol parameters + let genesis::Parameters { + epoch_duration, + max_expected_time_per_block, + vp_whitelist, + tx_whitelist, + implicit_vp_code_path, + implicit_vp_sha256, + } = genesis.parameters; + let implicit_vp = + wasm_loader::read_wasm(&self.wasm_dir, &implicit_vp_code_path) + .map_err(Error::ReadingWasm)?; + // In dev, we don't check the hash + #[cfg(feature = "dev")] + let _ = implicit_vp_sha256; + #[cfg(not(feature = "dev"))] + { + let mut hasher = Sha256::new(); + hasher.update(&implicit_vp); + let vp_code_hash = hasher.finalize(); + assert_eq!( + vp_code_hash.as_slice(), + &implicit_vp_sha256, + "Invalid implicit account's VP sha256 hash for {}", + implicit_vp_code_path + ); + } + let parameters = Parameters { + epoch_duration, + max_expected_time_per_block, + vp_whitelist, + tx_whitelist, + implicit_vp, + }; + parameters.init_storage(&mut self.storage); + + // Initialize governance parameters genesis.gov_params.init_storage(&mut self.storage); // Depends on parameters being initialized self.storage - .init_genesis_epoch( - initial_height, - genesis_time, - &genesis.parameters, - ) + .init_genesis_epoch(initial_height, genesis_time, ¶meters) .expect("Initializing genesis epoch must not fail"); // Loaded VP code cache to avoid loading the same files multiple times diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index fdc2a110d0..3e7ab1ceeb 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -122,6 +122,8 @@ pub struct Parameters { pub vp_whitelist: Vec, /// Whitelisted tx hashes pub tx_whitelist: Vec, + /// Implicit accounts validity predicate WASM code + pub implicit_vp: Vec, } /// Epoch duration. A new epoch begins as soon as both the `min_num_of_blocks` @@ -152,41 +154,55 @@ impl Parameters { DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: ledger_storage::StorageHasher, { + let Self { + epoch_duration, + max_expected_time_per_block, + vp_whitelist, + tx_whitelist, + implicit_vp, + } = self; + // write epoch parameters let epoch_key = storage::get_epoch_storage_key(); - let epoch_value = encode(&self.epoch_duration); - storage.write(&epoch_key, epoch_value).expect( - "Epoch parameters must be initialized in the genesis block", - ); + let epoch_value = encode(&epoch_duration); + storage + .write(&epoch_key, epoch_value) + .expect("Epoch parameter must be initialized in the genesis block"); // write vp whitelist parameter let vp_whitelist_key = storage::get_vp_whitelist_storage_key(); - let vp_whitelist_value = encode(&self.vp_whitelist); + let vp_whitelist_value = encode(&vp_whitelist); storage.write(&vp_whitelist_key, vp_whitelist_value).expect( - "Vp whitelist parameters must be initialized in the genesis block", + "Vp whitelist parameter must be initialized in the genesis block", ); // write tx whitelist parameter let tx_whitelist_key = storage::get_tx_whitelist_storage_key(); - let tx_whitelist_value = encode(&self.tx_whitelist); + let tx_whitelist_value = encode(&tx_whitelist); storage.write(&tx_whitelist_key, tx_whitelist_value).expect( - "Tx whitelist parameters must be initialized in the genesis block", + "Tx whitelist parameter must be initialized in the genesis block", ); // write tx whitelist parameter let max_expected_time_per_block_key = storage::get_max_expected_time_per_block_key(); let max_expected_time_per_block_value = - encode(&self.max_expected_time_per_block); + encode(&max_expected_time_per_block); storage .write( &max_expected_time_per_block_key, max_expected_time_per_block_value, ) .expect( - "Max expected time per block parameters must be initialized \ - in the genesis block", + "Max expected time per block parameter must be initialized in \ + the genesis block", ); + + // write implicit vp parameter + let implicit_vp_key = storage::get_implicit_vp_key(); + storage.write(&implicit_vp_key, implicit_vp).expect( + "Implicit VP parameter must be initialized in the genesis block", + ); } } @@ -246,6 +262,24 @@ where update(storage, value, key) } +/// Update the implicit VP parameter in storage. Return the gas cost. +pub fn update_implicit_vp( + storage: &mut Storage, + implicit_vp: &[u8], +) -> std::result::Result +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: ledger_storage::StorageHasher, +{ + let key = storage::get_epoch_storage_key(); + // Not using `fn update` here, because implicit_vp doesn't need to be + // encoded, it's bytes already. + let (gas, _size_diff) = storage + .write(&key, implicit_vp) + .map_err(WriteError::StorageError)?; + Ok(gas) +} + /// Update the parameters in storage. Returns the parameters and gas /// cost. pub fn update( @@ -326,14 +360,21 @@ where decode(value.ok_or(ReadError::ParametersMissing)?) .map_err(ReadError::StorageTypeError)?; + let implicit_vp_key = storage::get_implicit_vp_key(); + let (value, gas_implicit_vp) = storage + .read(&implicit_vp_key) + .map_err(ReadError::StorageError)?; + let implicit_vp = value.ok_or(ReadError::ParametersMissing)?; + Ok(( Parameters { epoch_duration, max_expected_time_per_block, vp_whitelist, tx_whitelist, + implicit_vp, }, - gas_epoch + gas_tx + gas_vp + gas_time, + gas_epoch + gas_tx + gas_vp + gas_time + gas_implicit_vp, )) } diff --git a/shared/src/ledger/parameters/storage.rs b/shared/src/ledger/parameters/storage.rs index 4041b82f2f..b4b2b1013b 100644 --- a/shared/src/ledger/parameters/storage.rs +++ b/shared/src/ledger/parameters/storage.rs @@ -6,6 +6,7 @@ const EPOCH_DURATION_KEY: &str = "epoch_duration"; const VP_WHITELIST_KEY: &str = "vp_whitelist"; const TX_WHITELIST_KEY: &str = "tx_whitelist"; const MAX_EXPECTED_TIME_PER_BLOCK_KEY: &str = "max_expected_time_per_block"; +const IMPLICIT_VP_KEY: &str = "implicit_vp"; /// Returns if the key is a parameter key. pub fn is_parameter_key(key: &Key) -> bool { @@ -52,6 +53,14 @@ pub fn is_vp_whitelist_key(key: &Key) -> bool { ] if addr == &ADDRESS && vp_whitelist == VP_WHITELIST_KEY) } +/// Returns if the key is the implicit VP key. +pub fn is_implicit_vp_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(sub_key), + ] if addr == &ADDRESS && sub_key == IMPLICIT_VP_KEY) +} + /// Storage key used for epoch parameter. pub fn get_epoch_storage_key() -> Key { Key { @@ -91,3 +100,13 @@ pub fn get_max_expected_time_per_block_key() -> Key { ], } } + +/// Storage key used for implicit VP parameter. +pub fn get_implicit_vp_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(IMPLICIT_VP_KEY.to_string()), + ], + } +} diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 16c3ecf180..7bc229820e 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -986,7 +986,8 @@ mod tests { epoch_duration: epoch_duration.clone(), max_expected_time_per_block: Duration::seconds(max_expected_time_per_block).into(), vp_whitelist: vec![], - tx_whitelist: vec![] + tx_whitelist: vec![], + implicit_vp: vec![], }; parameters.init_storage(&mut storage); From c35ff1e4a7de7323903da68b6ecc6b0fb1eec29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 16:55:26 +0200 Subject: [PATCH 03/14] storage: load implicit VP from parameters --- shared/src/ledger/storage/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 7bc229820e..be90a6c793 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -500,7 +500,11 @@ where &self, addr: &Address, ) -> Result<(Option>, u64)> { - let key = Key::validity_predicate(addr); + let key = if let Address::Implicit(_) = addr { + parameters::storage::get_implicit_vp_key() + } else { + Key::validity_predicate(addr) + }; self.read(&key) } From c6f75c73ff19a761cc2e959ef1e9bdec3eb47776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 16:55:41 +0200 Subject: [PATCH 04/14] protocol: allow to use implicit accounts in inner txs --- apps/src/lib/node/ledger/protocol/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index ed776fe21a..13377c5dca 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -217,12 +217,10 @@ where { verifiers .par_iter() - // TODO temporary pending on - .filter(|addr| !matches!(addr, Address::Implicit(_))) .try_fold(VpsResult::default, |mut result, addr| { let mut gas_meter = VpGasMeter::new(initial_gas); let accept = match &addr { - Address::Established(_) => { + Address::Implicit(_) | Address::Established(_) => { let (vp, gas) = storage .validity_predicate(addr) .map_err(Error::StorageError)?; @@ -359,8 +357,6 @@ where accepted } - // TODO temporary pending on - Address::Implicit(_) => unreachable!(), }; // Returning error from here will short-circuit the VP parallel From c3e10076dbd60cddbb64356c6488cf6d6cf85752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 18:12:29 +0200 Subject: [PATCH 05/14] test/e2e: add test for transfer from implicit account --- genesis/e2e-tests-single-node.toml | 13 +++++++------ tests/src/e2e/ledger_tests.rs | 23 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 0e3a6d3fc8..54afa8cd4c 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -113,22 +113,21 @@ vp = "vp_user" # Wasm VP definitions -# Default user VP +# Implicit VP +[wasm.vp_implicit] +filename = "vp_implicit.wasm" + +# Default user VP in established accounts [wasm.vp_user] -# filename (relative to wasm path used by the node) filename = "vp_user.wasm" -# SHA-256 hash of the wasm file -sha256 = "dc7b97f0448f2369bd2401c3c1d8898f53cac8c464a8c1b1f7f81415a658625d" # Token VP [wasm.vp_token] filename = "vp_token.wasm" -sha256 = "e428a11f570d21dd3c871f5d35de6fe18098eb8ee0456b3e11a72ccdd8685cd0" # Faucet VP [wasm.vp_testnet_faucet] filename = "vp_testnet_faucet.wasm" -sha256 = "2038d93afd456a77c45123811b671627f488c8d2a72b714d82dd494cbbd552bc" # General protocol parameters. [parameters] @@ -142,6 +141,8 @@ max_expected_time_per_block = 30 vp_whitelist = [] # tx whitelist tx_whitelist = [] +# Implicit VP WASM name +implicit_vp = "vp_implicit" # Proof of stake parameters. [pos_params] diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index a998844764..af16ec709f 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -275,7 +275,7 @@ fn ledger_txs_and_queries() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let txs_args = vec![ - // 2. Submit a token transfer tx + // 2. Submit a token transfer tx (from an established account) vec![ "transfer", "--source", @@ -295,6 +295,26 @@ fn ledger_txs_and_queries() -> Result<()> { "--ledger-address", &validator_one_rpc, ], + // Submit a token transfer tx (from an implicit account) + vec![ + "transfer", + "--source", + DAEWON, + "--target", + ALBERT, + "--token", + XAN, + "--amount", + "10.1", + "--fee-amount", + "0", + "--gas-limit", + "0", + "--fee-token", + XAN, + "--ledger-address", + &validator_one_rpc, + ], // 3. Submit a transaction to update an account's validity // predicate vec![ @@ -1036,6 +1056,7 @@ fn proposal_submission() -> Result<()> { &working_dir, Some("tx_"), )), + ..genesis.parameters }; GenesisConfig { From 21696a534aa8eb4e6f992abd5964ab255ecfdc14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 18:13:10 +0200 Subject: [PATCH 06/14] wasm/vp_implicit: rm change handling from implicit VP The implicit VP is loaded from protocol parameters --- wasm/wasm_source/src/vp_implicit.rs | 143 ---------------------------- 1 file changed, 143 deletions(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 421222acef..35651f434e 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -16,7 +16,6 @@ enum KeyType<'a> { Token(&'a Address), PoS, Nft(&'a Address), - Vp(&'a Address), GovernanceVote(&'a Address), Unknown, } @@ -40,8 +39,6 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { } else { Self::Unknown } - } else if let Some(address) = key.is_validity_predicate() { - Self::Vp(address) } else { Self::Unknown } @@ -155,20 +152,6 @@ fn validate_tx( true } } - KeyType::Vp(owner) => { - let has_post: bool = ctx.has_key_post(key)?; - if owner == &addr { - if has_post { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - *valid_sig && is_vp_whitelisted(ctx, &vp)? - } else { - false - } - } else { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - is_vp_whitelisted(ctx, &vp)? - } - } KeyType::Unknown => { if key.segments.get(0) == Some(&addr.to_db_key()) { // Unknown changes to this address space require a valid @@ -540,132 +523,6 @@ mod tests { ); } - /// Test that a validity predicate update with a valid signature is - /// accepted. - #[test] - fn test_signed_vp_update_accepted() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, None, None); - - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { - // Update VP in a transaction - tx::ctx() - .update_validity_predicate(address, &vp_code) - .unwrap(); - }); - - let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers: BTreeSet
= BTreeSet::default(); - vp_host_env::set(vp_env); - assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) - .unwrap() - ); - } - - /// Test that a validity predicate update is rejected if not whitelisted - #[test] - fn test_signed_vp_update_not_whitelisted_rejected() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); - - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { - // Update VP in a transaction - tx::ctx() - .update_validity_predicate(address, &vp_code) - .unwrap(); - }); - - let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers: BTreeSet
= BTreeSet::default(); - vp_host_env::set(vp_env); - assert!( - !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) - .unwrap() - ); - } - - /// Test that a validity predicate update is accepted if whitelisted - #[test] - fn test_signed_vp_update_whitelisted_accepted() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); - - let vp_hash = sha256(&vp_code); - tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { - // Update VP in a transaction - tx::ctx() - .update_validity_predicate(address, &vp_code) - .unwrap(); - }); - - let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers: BTreeSet
= BTreeSet::default(); - vp_host_env::set(vp_env); - assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) - .unwrap() - ); - } - /// Test that a tx is rejected if not whitelisted #[test] fn test_tx_not_whitelisted_rejected() { From ee5377245b35f714030a83020dacf1d1d6cb559f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 14:03:40 +0100 Subject: [PATCH 07/14] test/wasm/vp_implicit: use implicit addresses as vp_owner --- wasm/wasm_source/src/vp_implicit.rs | 73 +++++++++++++++-------------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 35651f434e..459fb7f229 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -174,7 +174,6 @@ fn validate_tx( #[cfg(test)] mod tests { - use address::testing::arb_non_internal_address; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; use namada_tests::tx::{self, tx_host_env, TestTxEnv}; @@ -212,7 +211,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let source = address::testing::established_address_2(); let token = address::xan(); let amount = token::Amount::from(10_098_123); @@ -256,7 +257,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::xan(); let amount = token::Amount::from(10_098_123); @@ -300,9 +303,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::xan(); let amount = token::Amount::from(10_098_123); @@ -332,7 +335,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); + let signed_tx = tx.sign(&secret_key); let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); vp_env.tx = signed_tx; let keys_changed: BTreeSet = @@ -351,7 +354,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::xan(); @@ -391,20 +396,20 @@ mod tests { ); } - prop_compose! { - /// Generates an account address and a storage key inside its storage. - fn arb_account_storage_subspace_key() - // Generate an address - (address in arb_non_internal_address()) + /// Generates a keypair, derive an implicit address from it and generate + /// a storage key inside its storage. + fn arb_account_storage_subspace_key() + -> impl Strategy { + // Generate a keypair + key::testing::arb_common_keypair().prop_flat_map(|sk| { + let pk = sk.ref_to(); + let addr: Address = (&pk).into(); // Generate a storage key other than its VP key (VP cannot be // modified directly via `write`, it has to be modified via // `tx::update_validity_predicate`. - (storage_key in arb_account_storage_key_no_vp(address.clone()), - // Use the generated address too - address in Just(address)) - -> (Address, Key) { - (address, storage_key) - } + let storage_key = arb_account_storage_key_no_vp(addr.clone()); + (Just(sk), Just(addr), storage_key) + }) } proptest! { @@ -412,7 +417,7 @@ mod tests { /// deletes to the account is rejected. #[test] fn test_unsigned_arb_storage_write_rejected( - (vp_owner, storage_key) in arb_account_storage_subspace_key(), + (_sk, vp_owner, storage_key) in arb_account_storage_subspace_key(), // Generate bytes to write. If `None`, delete from the key instead storage_value in any::>>(), ) { @@ -449,21 +454,19 @@ mod tests { /// deletes to the account is accepted. #[test] fn test_signed_arb_storage_write( - (vp_owner, storage_key) in arb_account_storage_subspace_key(), + (secret_key, vp_owner, storage_key) in arb_account_storage_subspace_key(), // Generate bytes to write. If `None`, delete from the key instead storage_value in any::>>(), ) { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); - // Spawn all the accounts in the storage key to be able to modify // their storage let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); + let public_key = secret_key.ref_to(); tx_env.write_public_key(&vp_owner, &public_key); // Initialize VP environment from a transaction @@ -478,7 +481,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); + let signed_tx = tx.sign(&secret_key); let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); vp_env.tx = signed_tx; let keys_changed: BTreeSet = @@ -496,7 +499,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let vp_code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); @@ -529,9 +534,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let vp_code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); @@ -557,7 +562,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); + let signed_tx = tx.sign(&secret_key); let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); vp_env.tx = signed_tx; let keys_changed: BTreeSet = @@ -575,9 +580,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let vp_code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); @@ -599,7 +604,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); + let signed_tx = tx.sign(&secret_key); let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); vp_env.tx = signed_tx; let keys_changed: BTreeSet = From 8918faea17a883e90c372fed8fc1b698abafa8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 10:53:24 +0100 Subject: [PATCH 08/14] wasm: add tx_reveal_pk --- shared/src/ledger/storage_api/key.rs | 26 ++++++++++++++++++++++++++ shared/src/ledger/storage_api/mod.rs | 1 + tx_prelude/src/key.rs | 11 +++++++++++ tx_prelude/src/lib.rs | 1 + wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/Makefile | 1 + wasm/wasm_source/src/lib.rs | 2 ++ wasm/wasm_source/src/tx_reveal_pk.rs | 15 +++++++++++++++ 8 files changed, 58 insertions(+) create mode 100644 shared/src/ledger/storage_api/key.rs create mode 100644 tx_prelude/src/key.rs create mode 100644 wasm/wasm_source/src/tx_reveal_pk.rs diff --git a/shared/src/ledger/storage_api/key.rs b/shared/src/ledger/storage_api/key.rs new file mode 100644 index 0000000000..06b3c76bad --- /dev/null +++ b/shared/src/ledger/storage_api/key.rs @@ -0,0 +1,26 @@ +//! Cryptographic signature keys storage API + +use super::*; +use crate::types::address::Address; +use crate::types::key::*; + +/// Get the public key associated with the given address. Returns `Ok(None)` if +/// not found. +pub fn get(storage: &S, owner: &Address) -> Result> +where + S: for<'iter> StorageRead<'iter>, +{ + let key = pk_key(owner); + storage.read(&key) +} + +/// Reveal a PK of an implicit account - the PK is written into the storage +/// of the address derived from the PK. +pub fn reveal_pk(storage: &mut S, pk: &common::PublicKey) -> Result<()> +where + S: StorageWrite, +{ + let addr: Address = pk.into(); + let key = pk_key(&addr); + storage.write(&key, pk) +} diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index b806f35801..cae687a1ec 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -3,6 +3,7 @@ pub mod collections; mod error; +pub mod key; pub mod validation; use borsh::{BorshDeserialize, BorshSerialize}; diff --git a/tx_prelude/src/key.rs b/tx_prelude/src/key.rs new file mode 100644 index 0000000000..fa83f3d1b4 --- /dev/null +++ b/tx_prelude/src/key.rs @@ -0,0 +1,11 @@ +//! Cryptographic signature keys + +pub use namada::types::key::*; + +use super::*; + +/// Reveal a PK of an implicit account - the PK is written into the storage +/// of the address derived from the PK. +pub fn reveal_pk(ctx: &mut Ctx, pk: &common::PublicKey) -> EnvResult<()> { + storage_api::key::reveal_pk(ctx, pk) +} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 730adb3155..a1e1d39df3 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -8,6 +8,7 @@ pub mod governance; pub mod ibc; +pub mod key; pub mod nft; pub mod proof_of_stake; pub mod token; diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 180931cda5..fd8fd95597 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -20,6 +20,7 @@ tx_init_nft = ["namada_tx_prelude"] tx_init_proposal = ["namada_tx_prelude"] tx_init_validator = ["namada_tx_prelude"] tx_mint_nft = ["namada_tx_prelude"] +tx_reveal_pk = ["namada_tx_prelude"] tx_transfer = ["namada_tx_prelude"] tx_unbond = ["namada_tx_prelude"] tx_update_vp = ["namada_tx_prelude"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 97a1eba9c9..460c31767f 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -12,6 +12,7 @@ wasms += tx_init_nft wasms += tx_init_validator wasms += tx_init_proposal wasms += tx_mint_nft +wasms += tx_reveal_pk wasms += tx_vote_proposal wasms += tx_transfer wasms += tx_unbond diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 37a1d113b1..62fa05f3c2 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -12,6 +12,8 @@ pub mod tx_init_proposal; pub mod tx_init_validator; #[cfg(feature = "tx_mint_nft")] pub mod tx_mint_nft; +#[cfg(feature = "tx_reveal_pk")] +pub mod tx_reveal_pk; #[cfg(feature = "tx_transfer")] pub mod tx_transfer; #[cfg(feature = "tx_unbond")] diff --git a/wasm/wasm_source/src/tx_reveal_pk.rs b/wasm/wasm_source/src/tx_reveal_pk.rs new file mode 100644 index 0000000000..be3bacce6d --- /dev/null +++ b/wasm/wasm_source/src/tx_reveal_pk.rs @@ -0,0 +1,15 @@ +//! A tx to reveal a public key of an implicit account. +//! This tx expects borsh encoded [`common::PublicKey`] in `tx_data` and it's +//! not signed as the authenticity of the public key can be trivially verified +//! against the address into which it's being written. + +use namada_tx_prelude::key::common; +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { + let pk = common::PublicKey::try_from_slice(&tx_data[..]) + .wrap_err("failed to decode common::PublicKey from tx_data")?; + debug_log!("tx_reveal_pk called with pk: {pk}"); + key::reveal_pk(ctx, &pk) +} From acec160007d075e491ada7a8c565f6cd2dd0dc87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 10:54:24 +0100 Subject: [PATCH 09/14] wasm/vp_implicit: add support and tests for revealing PK --- wasm/wasm_source/src/vp_implicit.rs | 127 +++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 459fb7f229..d761dc3979 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -3,6 +3,9 @@ //! This VP currently provides a signature verification against a public key for //! sending tokens (receiving tokens is permissive). //! +//! It allows to reveal a PK, as long as its address matches with the address +//! that can be derived from the PK. +//! //! It allows to bond, unbond and withdraw tokens to and from PoS system with a //! valid signature. //! @@ -13,6 +16,8 @@ use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { + /// Public key - written once revealed + Pk(&'a Address), Token(&'a Address), PoS, Nft(&'a Address), @@ -22,7 +27,9 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = token::is_any_token_balance_key(key) { + if let Some(address) = key::is_pk_key(key) { + Self::Pk(address) + } else if let Some(address) = token::is_any_token_balance_key(key) { Self::Token(address) } else if let Some((_, address)) = token::is_any_multitoken_balance_key(key) @@ -86,6 +93,31 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { + KeyType::Pk(owner) => { + if owner == &addr { + if ctx.has_key_pre(key)? { + // If the PK is already reveal, reject the tx + return reject(); + } + let post: Option = + ctx.read_post(key)?; + match post { + Some(pk) => { + let addr_from_pk: Address = (&pk).into(); + // Check that address matches with the address + // derived from the PK + if addr_from_pk != addr { + return reject(); + } + } + None => { + // Revealed PK cannot be deleted + return reject(); + } + } + } + true + } KeyType::Token(owner) => { if owner == &addr { let pre: token::Amount = @@ -205,6 +237,99 @@ mod tests { ); } + /// Test that a PK can be revealed when it's not revealed and cannot be + /// revealed anymore once it's already revealed. + #[test] + fn test_can_reveal_pk() { + // The SK to be used for the implicit account + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let addr: Address = (&public_key).into(); + + // Initialize a tx environment + let tx_env = TestTxEnv::default(); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { + // Apply reveal_pk in a transaction + tx_host_env::key::reveal_pk(tx::ctx(), &public_key).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + + assert!( + validate_tx(&CTX, tx_data, addr.clone(), keys_changed, verifiers) + .unwrap(), + "Revealing PK that's not yet revealed and is matching the address \ + must be accepted" + ); + + // Commit the transaction and create another tx_env + let vp_env = vp_host_env::take(); + tx_host_env::set_from_vp_env(vp_env); + tx_host_env::commit_tx_and_block(); + let tx_env = tx_host_env::take(); + + // Try to reveal it again + vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { + // Apply reveal_pk in a transaction + tx_host_env::key::reveal_pk(tx::ctx(), &public_key).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + + assert!( + !validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap(), + "Revealing PK that's already revealed should be rejected" + ); + } + + /// Test that a revealed PK that doesn't correspond to the account's address + /// is rejected. + #[test] + fn test_reveal_wrong_pk_rejected() { + // The SK to be used for the implicit account + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let addr: Address = (&public_key).into(); + + // Another SK to be revealed for the address above (not matching it) + let mismatched_sk = key::testing::keypair_2(); + let mismatched_pk = mismatched_sk.ref_to(); + + // Initialize a tx environment + let tx_env = TestTxEnv::default(); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { + // Do the same as reveal_pk, but with the wrong key + let key = namada_tx_prelude::key::pk_key(&addr); + tx_host_env::ctx().write(&key, &mismatched_pk).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + + assert!( + !validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap(), + "Mismatching PK must be rejected" + ); + } + /// Test that a credit transfer is accepted. #[test] fn test_credit_transfer_accepted() { From dc0b95bcf0cf49f71fbc371bf865102f3f86809d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 11:05:46 +0100 Subject: [PATCH 10/14] vp_prelude: refactor `key::get` to re-use new `storage_api::key::get` --- vp_prelude/src/key.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vp_prelude/src/key.rs b/vp_prelude/src/key.rs index 5ef2a5e28c..5cd034852f 100644 --- a/vp_prelude/src/key.rs +++ b/vp_prelude/src/key.rs @@ -5,9 +5,8 @@ pub use namada::types::key::*; use super::*; -/// Get the public key associated with the given address. Panics if not -/// found. +/// Get the public key associated with the given address from the state prior to +/// tx execution. Returns `Ok(None)` if not found. pub fn get(ctx: &Ctx, owner: &Address) -> EnvResult> { - let key = pk_key(owner); - ctx.read_pre(&key) + storage_api::key::get(&ctx.pre(), owner) } From b7120402b01f04bd774effb6079719482377cf5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 12:28:43 +0100 Subject: [PATCH 11/14] add manual "reveal-pk" command and automatically reveal when needed When an implicit account is being used as a source of a tx which it has to sign, the client will first check that its PK is revealed and if not submit it and wait for it before continuing. --- apps/src/bin/anoma-client/cli.rs | 3 + apps/src/bin/anoma/cli.rs | 1 + apps/src/lib/cli.rs | 64 +++++++++++++++++- apps/src/lib/client/signing.rs | 13 ++++ apps/src/lib/client/tx.rs | 109 +++++++++++++++++++++++++++++++ 5 files changed, 188 insertions(+), 2 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index b87cdb5c66..3df3668c38 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -39,6 +39,9 @@ pub async fn main() -> Result<()> { Sub::TxVoteProposal(TxVoteProposal(args)) => { tx::submit_vote_proposal(ctx, args).await; } + Sub::TxRevealPk(TxRevealPk(args)) => { + tx::submit_reveal_pk(ctx, args).await; + } Sub::Bond(Bond(args)) => { tx::submit_bond(ctx, args).await; } diff --git a/apps/src/bin/anoma/cli.rs b/apps/src/bin/anoma/cli.rs index ccde0c3618..f0bd3ee740 100644 --- a/apps/src/bin/anoma/cli.rs +++ b/apps/src/bin/anoma/cli.rs @@ -46,6 +46,7 @@ fn handle_command(cmd: cli::cmds::Anoma, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Anoma::TxCustom(_) | cli::cmds::Anoma::TxTransfer(_) | cli::cmds::Anoma::TxUpdateVp(_) + | cli::cmds::Anoma::TxRevealPk(_) | cli::cmds::Anoma::TxInitNft(_) | cli::cmds::Anoma::TxMintNft(_) | cli::cmds::Anoma::TxInitProposal(_) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 8e4c7f78c9..6cdb72c7bc 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -51,6 +51,7 @@ pub mod cmds { TxMintNft(TxMintNft), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), + TxRevealPk(TxRevealPk), } impl Cmd for Anoma { @@ -66,6 +67,7 @@ pub mod cmds { .subcommand(TxMintNft::def()) .subcommand(TxInitProposal::def()) .subcommand(TxVoteProposal::def()) + .subcommand(TxRevealPk::def()) } fn parse(matches: &ArgMatches) -> Option { @@ -82,6 +84,7 @@ pub mod cmds { SubCmd::parse(matches).map(Self::TxInitProposal); let tx_vote_proposal = SubCmd::parse(matches).map(Self::TxVoteProposal); + let tx_reveal_pk = SubCmd::parse(matches).map(Self::TxRevealPk); node.or(client) .or(wallet) .or(ledger) @@ -92,6 +95,7 @@ pub mod cmds { .or(tx_nft_mint) .or(tx_init_proposal) .or(tx_vote_proposal) + .or(tx_reveal_pk) } } @@ -154,7 +158,7 @@ pub mod cmds { .subcommand(TxTransfer::def().display_order(1)) .subcommand(TxUpdateVp::def().display_order(1)) .subcommand(TxInitAccount::def().display_order(1)) - .subcommand(TxInitValidator::def().display_order(1)) + .subcommand(TxRevealPk::def().display_order(1)) // Nft transactions .subcommand(TxInitNft::def().display_order(1)) .subcommand(TxMintNft::def().display_order(1)) @@ -162,6 +166,7 @@ pub mod cmds { .subcommand(TxInitProposal::def().display_order(1)) .subcommand(TxVoteProposal::def().display_order(1)) // PoS transactions + .subcommand(TxInitValidator::def().display_order(2)) .subcommand(Bond::def().display_order(2)) .subcommand(Unbond::def().display_order(2)) .subcommand(Withdraw::def().display_order(2)) @@ -188,6 +193,7 @@ pub mod cmds { let tx_init_account = Self::parse_with_ctx(matches, TxInitAccount); let tx_init_validator = Self::parse_with_ctx(matches, TxInitValidator); + let tx_reveal_pk = Self::parse_with_ctx(matches, TxRevealPk); let tx_nft_create = Self::parse_with_ctx(matches, TxInitNft); let tx_nft_mint = Self::parse_with_ctx(matches, TxMintNft); let tx_init_proposal = @@ -215,11 +221,12 @@ pub mod cmds { .or(tx_transfer) .or(tx_update_vp) .or(tx_init_account) - .or(tx_init_validator) + .or(tx_reveal_pk) .or(tx_nft_create) .or(tx_nft_mint) .or(tx_init_proposal) .or(tx_vote_proposal) + .or(tx_init_validator) .or(bond) .or(unbond) .or(withdraw) @@ -279,6 +286,7 @@ pub mod cmds { TxMintNft(TxMintNft), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), + TxRevealPk(TxRevealPk), Bond(Bond), Unbond(Unbond), Withdraw(Withdraw), @@ -1122,6 +1130,36 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct TxRevealPk(pub args::RevealPk); + + impl SubCmd for TxRevealPk { + const CMD: &'static str = "reveal-pk"; + + fn parse(matches: &ArgMatches) -> Option + where + Self: Sized, + { + matches + .subcommand_matches(Self::CMD) + .map(|matches| TxRevealPk(args::RevealPk::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Submit a tx to reveal the public key an implicit \ + account. Typically, you don't have to do this manually \ + and the client will detect when a tx to reveal PK is \ + needed and submit it automatically. This will write the \ + PK into the account's storage so that it can be used for \ + signature verification on transactions authorized by \ + this account.", + ) + .add_args::() + } + } + #[derive(Clone, Debug)] pub enum Utils { JoinNetwork(JoinNetwork), @@ -1863,6 +1901,28 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct RevealPk { + /// Common tx arguments + pub tx: Tx, + /// A public key to be revealed on-chain + pub public_key: WalletPublicKey, + } + + impl Args for RevealPk { + fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); + let public_key = PUBLIC_KEY.parse(matches); + + Self { tx, public_key } + } + + fn def(app: App) -> App { + app.add_args::() + .arg(PUBLIC_KEY.def().about("A public key to reveal.")) + } + } + #[derive(Clone, Debug)] pub struct QueryProposal { /// Common query args diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index fb87151c82..963b556244 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -85,12 +85,25 @@ pub async fn sign_tx( ) -> (Context, TxBroadcastData) { let (tx, keypair) = if let Some(signing_key) = &args.signing_key { let signing_key = ctx.get_cached(signing_key); + + // Check if the signing key needs to reveal its PK first + let pk: common::PublicKey = signing_key.ref_to(); + super::tx::reveal_pk_if_needed(&mut ctx, &pk, args).await; + (tx.sign(&signing_key), signing_key) } else if let Some(signer) = args.signer.as_ref().or(default) { let signer = ctx.get(signer); let signing_key = find_keypair(&mut ctx.wallet, &signer, args.ledger_address.clone()) .await; + + // Check if the signer is implicit account that needs to reveal its PK + // first + if matches!(signer, Address::Implicit(_)) { + let pk: common::PublicKey = signing_key.ref_to(); + super::tx::reveal_pk_if_needed(&mut ctx, &pk, args).await; + } + (tx.sign(&signing_key), signing_key) } else { panic!( diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index e749a681c6..7d3ded6e37 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -47,6 +47,7 @@ const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; +const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; const TX_INIT_NFT: &str = "tx_init_nft.wasm"; @@ -817,6 +818,114 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } +pub async fn submit_reveal_pk(mut ctx: Context, args: args::RevealPk) { + let args::RevealPk { + tx: args, + public_key, + } = args; + let public_key = ctx.get_cached(&public_key); + if !reveal_pk_if_needed(&mut ctx, &public_key, &args).await { + let addr: Address = (&public_key).into(); + println!("PK for {addr} is already revealed, nothing to do."); + } +} + +pub async fn reveal_pk_if_needed( + ctx: &mut Context, + public_key: &common::PublicKey, + args: &args::Tx, +) -> bool { + let addr: Address = public_key.into(); + // Check if PK revealed + if args.force || !has_revealed_pk(&addr, args.ledger_address.clone()).await + { + // If not, submit it + submit_reveal_pk_aux(ctx, public_key, args).await; + true + } else { + false + } +} + +pub async fn has_revealed_pk( + addr: &Address, + ledger_address: TendermintAddress, +) -> bool { + rpc::get_public_key(addr, ledger_address).await.is_some() +} + +pub async fn submit_reveal_pk_aux( + ctx: &mut Context, + public_key: &common::PublicKey, + args: &args::Tx, +) { + let addr: Address = public_key.into(); + println!("Submitting a tx to reveal the public key for address {addr}..."); + let tx_data = public_key + .try_to_vec() + .expect("Encoding a public key shouldn't fail"); + let tx_code = ctx.read_wasm(TX_REVEAL_PK); + let tx = Tx::new(tx_code, Some(tx_data)); + + // submit_tx without signing the inner tx + let keypair = if let Some(signing_key) = &args.signing_key { + ctx.get_cached(signing_key) + } else if let Some(signer) = args.signer.as_ref() { + let signer = ctx.get(signer); + find_keypair(&mut ctx.wallet, &signer, args.ledger_address.clone()) + .await + } else { + find_keypair(&mut ctx.wallet, &addr, args.ledger_address.clone()).await + }; + let epoch = rpc::query_epoch(args::Query { + ledger_address: args.ledger_address.clone(), + }) + .await; + let to_broadcast = if args.dry_run { + TxBroadcastData::DryRun(tx) + } else { + super::signing::sign_wrapper(ctx, args, epoch, tx, &keypair).await + }; + + if args.dry_run { + if let TxBroadcastData::DryRun(tx) = to_broadcast { + rpc::dry_run_tx(&args.ledger_address, tx.to_bytes()).await; + } else { + panic!( + "Expected a dry-run transaction, received a wrapper \ + transaction instead" + ); + } + } else { + // Either broadcast or submit transaction and collect result into + // sum type + let result = if args.broadcast_only { + Left(broadcast_tx(args.ledger_address.clone(), &to_broadcast).await) + } else { + Right(submit_tx(args.ledger_address.clone(), to_broadcast).await) + }; + // Return result based on executed operation, otherwise deal with + // the encountered errors uniformly + match result { + Right(Err(err)) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } + Left(Err(err)) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } + _ => {} + } + } +} + /// Check if current epoch is in the last third of the voting period of the /// proposal. This ensures that it is safe to optimize the vote writing to /// storage. From 2f4c6d2051112b7c63364102d8d7e63eaa0944dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 14:46:13 +0100 Subject: [PATCH 12/14] test/e2e: add test for implicit account's PK revealing --- tests/src/e2e/ledger_tests.rs | 191 ++++++++++++++++++++++++++++------ 1 file changed, 162 insertions(+), 29 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index af16ec709f..24578690fe 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -9,6 +9,7 @@ //! To keep the temporary files created by a test, use env var //! `ANOMA_E2E_KEEP_TEMP=true`. +use std::path::PathBuf; use std::process::Command; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -16,6 +17,7 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; +use namada::types::address::Address; use namada::types::token; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, @@ -1105,36 +1107,8 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); - let albert = find_address(&test, ALBERT)?; - let valid_proposal_json = json!( - { - "content": { - "title": "TheTitle", - "authors": "test@test.com", - "discussions-to": "www.github.com/anoma/aip/1", - "created": "2022-03-10T08:54:37Z", - "license": "MIT", - "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "requires": "2" - }, - "author": albert, - "voting_start_epoch": 12_u64, - "voting_end_epoch": 24_u64, - "grace_epoch": 30_u64, - "proposal_code_path": proposal_code.to_str().unwrap() - } - ); - let valid_proposal_json_path = - test.test_dir.path().join("valid_proposal.json"); - generate_proposal_json_file( - valid_proposal_json_path.as_path(), - &valid_proposal_json, - ); - + let valid_proposal_json_path = prepare_proposal_data(&test, albert); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ @@ -2047,3 +2021,162 @@ fn double_signing_gets_slashed() -> Result<()> { Ok(()) } + +/// In this test we: +/// 1. Run the ledger node +/// 2. For some transactions that need signature authorization: +/// 2a. Generate a new key for an implicit account. +/// 2b. Send some funds to the implicit account. +/// 2c. Submit the tx with the implicit account as the source, that +/// requires that the account has revealed its PK. This should be done +/// by the client automatically. +/// 2d. Submit same tx again, this time the client shouldn't reveal again. +#[test] +fn implicit_account_reveal_pk() -> Result<()> { + let test = setup::network(|genesis| genesis, None)?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + ledger.exp_string("Anoma ledger node started")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // 2. Some transactions that need signature authorization: + let txs_args: Vec Vec>> = vec![ + // A token transfer tx + Box::new(|source| { + [ + "transfer", + "--source", + source, + "--target", + ALBERT, + "--token", + XAN, + "--amount", + "10.1", + "--ledger-address", + &validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + // A bond + Box::new(|source| { + vec![ + "bond", + "--validator", + "validator-0", + "--source", + source, + "--amount", + "10.1", + "--ledger-address", + &validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + // Submit proposal + Box::new(|source| { + // Gen data for proposal tx + let source = find_address(&test, source).unwrap(); + let valid_proposal_json_path = prepare_proposal_data(&test, source); + vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + ]; + + for (ix, tx_args) in txs_args.into_iter().enumerate() { + let key_alias = format!("key-{ix}"); + + // 2a. Generate a new key for an implicit account. + let mut cmd = run!( + test, + Bin::Wallet, + &["key", "gen", "--alias", &key_alias, "--unsafe-dont-encrypt"], + Some(20), + )?; + cmd.assert_success(); + + // Apply the key_alias once the key is generated to obtain tx args + let tx_args = tx_args(&key_alias); + + // 2b. Send some funds to the implicit account. + let credit_args = [ + "transfer", + "--source", + BERTHA, + "--target", + &key_alias, + "--token", + XAN, + "--amount", + "1000", + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, credit_args, Some(40))?; + client.assert_success(); + + // 2c. Submit the tx with the implicit account as the source. + let expected_reveal = "Submitting a tx to reveal the public key"; + let mut client = run!(test, Bin::Client, &tx_args, Some(40))?; + client.exp_string(expected_reveal)?; + client.assert_success(); + + // 2d. Submit same tx again, this time the client shouldn't reveal + // again. + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + let unread = client.exp_eof()?; + assert!(!unread.contains(expected_reveal)) + } + + Ok(()) +} + +/// Prepare proposal data in the test's temp dir from the given source address. +/// This can be submitted with "init-proposal" command. +fn prepare_proposal_data(test: &setup::Test, source: Address) -> PathBuf { + let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); + let valid_proposal_json = json!( + { + "content": { + "title": "TheTitle", + "authors": "test@test.com", + "discussions-to": "www.github.com/anoma/aip/1", + "created": "2022-03-10T08:54:37Z", + "license": "MIT", + "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "requires": "2" + }, + "author": source, + "voting_start_epoch": 12_u64, + "voting_end_epoch": 24_u64, + "grace_epoch": 30_u64, + "proposal_code_path": proposal_code.to_str().unwrap() + } + ); + let valid_proposal_json_path = + test.test_dir.path().join("valid_proposal.json"); + generate_proposal_json_file( + valid_proposal_json_path.as_path(), + &valid_proposal_json, + ); + valid_proposal_json_path +} From 26161c6f9f27c1d89da16ca263b32b1ce7a23b77 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 31 Oct 2022 16:48:13 +0000 Subject: [PATCH 13/14] [ci] wasm checksums update --- wasm/checksums.json | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 01140354ba..cfca78f0bc 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,20 @@ { - "tx_bond.wasm": "tx_bond.38c037a51f9215c2be9c1b01f647251ffdc96a02a0c958c5d3db4ee36ccde43b.wasm", - "tx_ibc.wasm": "tx_ibc.5f86477029d987073ebfec66019dc991b0bb8b80717d4885b860f910916cbcdd.wasm", - "tx_init_account.wasm": "tx_init_account.8d901bce15d1ab63a591def00421183a651d4d5e09ace4291bf0a9044692741d.wasm", - "tx_init_nft.wasm": "tx_init_nft.1991808f44c1c24d4376a3d46b602bed27575f6c0359095c53f37b9225050ffc.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.716cd08d59b26bd75815511f03e141e6ac27bc0b7d7be10a71b04559244722c2.wasm", - "tx_init_validator.wasm": "tx_init_validator.611edff2746f71cdaa7547a84a96676b555821f00af8375a28f8dab7ae9fc9fa.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.3f20f1a86da43cc475ccc127428944bd177d40fbe2d2d1588c6fadd069cbe4b2.wasm", - "tx_transfer.wasm": "tx_transfer.5653340103a32e6685f9668ec24855f65ae17bcc43035c2559a13f5c47bb67af.wasm", - "tx_unbond.wasm": "tx_unbond.71e66ac6f792123a2aaafd60b3892d74a7d0e7a03c3ea34f15fea9089010b810.wasm", + "tx_bond.wasm": "tx_bond.6df70c52076962e190d4bc27a73a99dfc4047a43d85183ee494bedd430f4a162.wasm", + "tx_ibc.wasm": "tx_ibc.611ddc77233ae77b7920893db3fb46c64d393fecf0945679436082ba994613f8.wasm", + "tx_init_account.wasm": "tx_init_account.1534d1343f540acb0a06a691c8bc969ac6af07cb7b8484ae6333563f0ad23c94.wasm", + "tx_init_nft.wasm": "tx_init_nft.c217f2d03b0e95e286e99edd076ff024f34a4b69f6c483611cc3cb5f89440e41.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3de969e6505e1840a97865cca6f121bd420635dd1d97e7e6e44b825053d96ba0.wasm", + "tx_init_validator.wasm": "tx_init_validator.dd5bd60bba985442455770a990776670c23c4d768e82595d03d87e743102874d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.1a3e1e0cf55d42dfcbce21effbbc1278c742f57bda3ca662dd6d43a7e035dff9.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.559e3f63dfb3b9f5c4d5d77c56cacdc6611653e3fa783a5ab9f142b61e752d7d.wasm", + "tx_transfer.wasm": "tx_transfer.4322cc5570acdf0dc4a7cba4da414eefe7c85e2f9dff6cdd0b5d77d6ed5e4b48.wasm", + "tx_unbond.wasm": "tx_unbond.3affb0adf515c173b4db6051d435875befe373dab7514083cd0245632ef7af2a.wasm", "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ff3def7b4bb0c46635bd6d544ac1745362757ce063feb8142d2ed9ab207f2a12.wasm", - "tx_withdraw.wasm": "tx_withdraw.ba1a743cf8914a353d7706777e0b1a37e20cd271b16e022fd3b50ad28971291f.wasm", - "vp_nft.wasm": "vp_nft.4471284b5c5f3e28c973f0a2ad2dde52ebe4a1dcd5dc15e93b380706fd0e35ea.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7d7eb09cddc7ae348417da623e21ec4a4f8c78f15ae12de5abe7087eeab1e0db.wasm", - "vp_token.wasm": "vp_token.4a5436f7519de15c80103557add57e8d06e766e1ec1f7a642ffca252be01c5d0.wasm", - "vp_user.wasm": "vp_user.729b18aab60e8ae09b75b5f067658f30459a5ccfcd34f909b88da96523681019.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.206bdcfbf1e640c6f5f665122f96ea55fdd6ee00807aa45d19bfe6e79974c160.wasm", + "tx_withdraw.wasm": "tx_withdraw.7bc289a6fd32e5513698d061f6b0fee134c8a0cd38d8dd83197346859ef83a64.wasm", + "vp_implicit.wasm": "vp_implicit.590d8b324271485cc2079d68283cc64832e4b77811c4d426fe0f6d3fe8964d02.wasm", + "vp_nft.wasm": "vp_nft.5eed9af8cb6edda135e950d7869a3068fa54ab40a94a5fd82985c91ed0d41746.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.61e537b46e43bfd89bd7ed92d06ae53e2c2461261b6f53dc6a7bf7ad6e43f2e9.wasm", + "vp_token.wasm": "vp_token.eb7fc5d8d36c92108b122bef4badca05ae57eb259f1c076d677e210d5bd8ec80.wasm", + "vp_user.wasm": "vp_user.c6d2854e59cdb2df2f755bd03dda9c7baca510e156ebf4393c6407b4e639bf21.wasm" } \ No newline at end of file From 484c0e7ae38309694992e05360c8f421c8954f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 17:48:37 +0100 Subject: [PATCH 14/14] changelog: add #592 --- .changelog/unreleased/features/592-implicit-vp.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changelog/unreleased/features/592-implicit-vp.md diff --git a/.changelog/unreleased/features/592-implicit-vp.md b/.changelog/unreleased/features/592-implicit-vp.md new file mode 100644 index 0000000000..ab93e1fc0f --- /dev/null +++ b/.changelog/unreleased/features/592-implicit-vp.md @@ -0,0 +1,6 @@ +- Added a validity predicate for implicit accounts. This is set in + protocol parameters and may be changed via governance. Additionally, + added automatic public key reveal in the client that use an implicit + account that hasn't revealed its PK yet as a source. It's also + possible to manually submit reveal transaction with client command + ([#592](https://github.com/anoma/namada/pull/592)) \ No newline at end of file