From 3fe7ee9e6c142f7d5f9726916aaddd15afdf6eda Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 23 Sep 2024 11:49:55 +0200 Subject: [PATCH] Implemented MASP signing using the hardware wallet. --- Cargo.lock | 9 +- Cargo.toml | 6 +- crates/apps_lib/Cargo.toml | 1 + crates/apps_lib/src/cli/context.rs | 63 +++- crates/apps_lib/src/cli/wallet.rs | 70 ++++- crates/apps_lib/src/client/tx.rs | 289 +++++++++++++++++- .../src/config/genesis/transactions.rs | 1 + crates/benches/native_vps.rs | 13 +- crates/core/src/masp.rs | 12 +- crates/node/src/bench_utils.rs | 2 + crates/sdk/src/args.rs | 7 +- crates/sdk/src/lib.rs | 7 +- crates/sdk/src/signing.rs | 3 + crates/sdk/src/tx.rs | 22 +- crates/shielded_token/src/masp.rs | 23 +- .../src/masp/shielded_wallet.rs | 50 ++- crates/shielded_token/src/validation.rs | 4 +- crates/tests/src/integration/masp.rs | 1 + crates/token/src/lib.rs | 3 +- crates/tx/src/types.rs | 24 ++ crates/wallet/src/lib.rs | 29 +- crates/wallet/src/store.rs | 17 +- wasm/Cargo.lock | 6 +- wasm_for_tests/Cargo.lock | 6 +- 24 files changed, 578 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffd306a156..7d6ea5452e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4264,7 +4264,7 @@ dependencies = [ [[package]] name = "ledger-namada-rs" version = "0.0.1" -source = "git+https://github.com/Zondax/ledger-namada?tag=v0.0.24#ca9da5e0129f676934809f58acffbd855d280664" +source = "git+https://github.com/Zondax/ledger-namada?rev=f54b76adcc1430db0496e894ad72cd74cfb6eb88#f54b76adcc1430db0496e894ad72cd74cfb6eb88" dependencies = [ "bincode", "byteorder", @@ -4494,7 +4494,7 @@ dependencies = [ [[package]] name = "masp_note_encryption" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "arbitrary", "borsh", @@ -4508,7 +4508,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "aes", "arbitrary", @@ -4541,7 +4541,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "bellman", "blake2b_simd", @@ -4769,6 +4769,7 @@ dependencies = [ "futures", "git2", "itertools 0.12.1", + "jubjub 0.10.0 (git+https://github.com/heliaxdev/jubjub.git?rev=a373686962f4e9d0edb3b4716f86ff6bbd9aa86c)", "kdam", "lazy_static", "ledger-lib", diff --git a/Cargo.toml b/Cargo.toml index d62f7c0174..6ef70ad493 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,15 +134,15 @@ konst = { version = "0.3.8", default-features = false } lazy_static = "1.4.0" # TODO: upstreamed in https://github.com/ledger-community/rust-ledger/pull/9 ledger-lib = { git = "https://github.com/heliaxdev/rust-ledger", rev = "f96f4559b3237d09218f7583df01acf36034ea79", default-features = false, features = ["transport_tcp"] } -ledger-namada-rs = { git = "https://github.com/Zondax/ledger-namada", tag = "v0.0.24" } +ledger-namada-rs = { git = "https://github.com/Zondax/ledger-namada", rev = "f54b76adcc1430db0496e894ad72cd74cfb6eb88" } ledger-transport = "0.10.0" ledger-transport-hid = "0.10.0" libc = "0.2.97" libloading = "0.7.2" linkme = "0.3.24" # branch = "tomas/arbitrary" -masp_primitives = { git = "https://github.com/anoma/masp", rev = "12ed8b060b295c06502a2ff8468e4a941cb7cca4" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "12ed8b060b295c06502a2ff8468e4a941cb7cca4", default-features = false, features = ["local-prover"] } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "a35f73be69b21ee62cd4940f37855161cbed2a56" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "a35f73be69b21ee62cd4940f37855161cbed2a56", default-features = false, features = ["local-prover"] } num256 = "0.3.5" num_cpus = "1.13.0" num-derive = "0.4" diff --git a/crates/apps_lib/Cargo.toml b/crates/apps_lib/Cargo.toml index c0fd42e600..9faaf7f38c 100644 --- a/crates/apps_lib/Cargo.toml +++ b/crates/apps_lib/Cargo.toml @@ -53,6 +53,7 @@ fd-lock.workspace = true flate2.workspace = true futures.workspace = true itertools.workspace = true +jubjub.workspace = true kdam.workspace = true lazy_static = { workspace = true, optional = true } linkme = { workspace = true, optional = true } diff --git a/crates/apps_lib/src/cli/context.rs b/crates/apps_lib/src/cli/context.rs index 16f4304f98..8cc2da6c78 100644 --- a/crates/apps_lib/src/cli/context.rs +++ b/crates/apps_lib/src/cli/context.rs @@ -6,6 +6,11 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use color_eyre::eyre::Result; +use masp_primitives::zip32::sapling::PseudoExtendedKey; +use masp_primitives::zip32::{ + ExtendedFullViewingKey as MaspExtendedViewingKey, + ExtendedSpendingKey as MaspExtendedSpendingKey, +}; use namada_core::masp::{ BalanceOwner, ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, TransferSource, TransferTarget, @@ -47,7 +52,7 @@ pub type WalletAddrOrNativeToken = FromContext; /// A raw extended spending key (bech32m encoding) or an alias of an extended /// spending key in the wallet -pub type WalletSpendingKey = FromContext; +pub type WalletSpendingKey = FromContext; /// A raw dated extended spending key (bech32m encoding) or an alias of an /// extended spending key in the wallet @@ -588,6 +593,48 @@ impl ArgFromMutContext for ExtendedSpendingKey { } } +impl ArgFromMutContext for PseudoExtendedKey { + fn arg_from_mut_ctx( + ctx: &mut ChainContext, + raw: impl AsRef, + ) -> Result { + let raw = raw.as_ref(); + // Either the string is a raw extended spending key + ExtendedSpendingKey::from_str(raw) + .map(|x| PseudoExtendedKey::from(MaspExtendedSpendingKey::from(x))) + .or_else(|_parse_err| { + ExtendedViewingKey::from_str(raw).map(|x| { + PseudoExtendedKey::from(MaspExtendedViewingKey::from(x)) + }) + }) + .or_else(|_parse_err| { + // Or it is a stored alias of one + ctx.wallet + .find_spending_key(raw, None) + .map(|k| { + PseudoExtendedKey::from(MaspExtendedSpendingKey::from( + k.key, + )) + }) + .map_err(|_find_err| { + format!("Unknown spending key {}", raw) + }) + }) + .or_else(|_parse_err| { + // Or it is a stored alias of one + ctx.wallet + .find_viewing_key(raw) + .copied() + .map(|k| { + PseudoExtendedKey::from(MaspExtendedViewingKey::from( + k.key, + )) + }) + .map_err(|_find_err| format!("Unknown viewing key {}", raw)) + }) + } +} + impl ArgFromMutContext for DatedSpendingKey { fn arg_from_mut_ctx( ctx: &mut ChainContext, @@ -666,8 +713,18 @@ impl ArgFromMutContext for TransferSource { Address::arg_from_ctx(ctx, raw) .map(Self::Address) .or_else(|_| { - ExtendedSpendingKey::arg_from_mut_ctx(ctx, raw) - .map(Self::ExtendedSpendingKey) + ExtendedSpendingKey::arg_from_mut_ctx(ctx, raw).map(|x| { + Self::ExtendedSpendingKey(PseudoExtendedKey::from( + MaspExtendedSpendingKey::from(x), + )) + }) + }) + .or_else(|_| { + ExtendedViewingKey::arg_from_mut_ctx(ctx, raw).map(|x| { + Self::ExtendedSpendingKey(PseudoExtendedKey::from( + MaspExtendedViewingKey::from(x), + )) + }) }) } } diff --git a/crates/apps_lib/src/cli/wallet.rs b/crates/apps_lib/src/cli/wallet.rs index e6a763f234..42f24a6b8d 100644 --- a/crates/apps_lib/src/cli/wallet.rs +++ b/crates/apps_lib/src/cli/wallet.rs @@ -8,7 +8,10 @@ use borsh::BorshDeserialize; use borsh_ext::BorshSerializeExt; use color_eyre::eyre::Result; use itertools::sorted; -use ledger_namada_rs::{BIP44Path, NamadaApp}; +use ledger_namada_rs::{BIP44Path, KeyResponse, NamadaApp, NamadaKeys}; +use ledger_transport_hid::hidapi::HidApi; +use ledger_transport_hid::TransportNativeHID; +use masp_primitives::zip32::ExtendedFullViewingKey; use namada_core::chain::BlockHeight; use namada_core::masp::{ExtendedSpendingKey, MaspValue, PaymentAddress}; use namada_sdk::address::{Address, DecodeError}; @@ -184,7 +187,7 @@ fn payment_addresses_list( } /// Derives a masp spending key from the mnemonic code in the wallet. -fn shielded_key_derive( +async fn shielded_key_derive( ctx: Context, io: &impl Io, args::KeyDerive { @@ -232,9 +235,56 @@ fn shielded_key_derive( }) .0 } else { - display_line!(io, "Not implemented."); - display_line!(io, "No changes are persisted. Exiting."); - cli::safe_exit(1) + let hidapi = HidApi::new().unwrap_or_else(|err| { + edisplay_line!(io, "Failed to create HidApi: {}", err); + cli::safe_exit(1) + }); + let app = NamadaApp::new( + TransportNativeHID::new(&hidapi).unwrap_or_else(|err| { + edisplay_line!(io, "Unable to connect to Ledger: {}", err); + cli::safe_exit(1) + }), + ); + let response = app + .retrieve_keys( + &BIP44Path { + path: derivation_path.to_string(), + }, + NamadaKeys::ViewKey, + true, + ) + .await + .unwrap_or_else(|err| { + edisplay_line!( + io, + "Unable to connect to query address and public key from \ + Ledger: {}", + err + ); + cli::safe_exit(1) + }); + let KeyResponse::ViewKey(response_key) = response else { + edisplay_line!(io, "Unexpected response from Ledger"); + cli::safe_exit(1) + }; + let xfvk = ExtendedFullViewingKey::try_from_slice(&response_key.xfvk) + .expect( + "unable to decode extended full viewing key from the hardware \ + wallet", + ); + + wallet + .insert_viewing_key( + alias, + xfvk.into(), + birthday, + alias_force, + Some(derivation_path), + ) + .unwrap_or_else(|| { + display_line!(io, "No changes are persisted. Exiting."); + cli::safe_exit(1) + }) }; wallet .save() @@ -367,7 +417,13 @@ fn shielded_key_address_add( let (alias, typ) = match masp_value { MaspValue::FullViewingKey(viewing_key) => { let alias = wallet - .insert_viewing_key(alias, viewing_key, birthday, alias_force) + .insert_viewing_key( + alias, + viewing_key, + birthday, + alias_force, + None, + ) .unwrap_or_else(|| { edisplay_line!(io, "Viewing key not added"); cli::safe_exit(1); @@ -638,7 +694,7 @@ async fn key_derive( if !args_key_derive.shielded { transparent_key_and_address_derive(ctx, io, args_key_derive).await } else { - shielded_key_derive(ctx, io, args_key_derive) + shielded_key_derive(ctx, io, args_key_derive).await } } diff --git a/crates/apps_lib/src/client/tx.rs b/crates/apps_lib/src/client/tx.rs index 1ffae47901..35cbf4a80c 100644 --- a/crates/apps_lib/src/client/tx.rs +++ b/crates/apps_lib/src/client/tx.rs @@ -3,10 +3,19 @@ use std::io::Write; use borsh::BorshDeserialize; use borsh_ext::BorshSerializeExt; -use ledger_namada_rs::{BIP44Path, NamadaApp}; +use ledger_namada_rs::{BIP44Path, KeyResponse, NamadaApp, NamadaKeys}; +use masp_primitives::sapling::redjubjub::PrivateKey; +use masp_primitives::sapling::{redjubjub, ProofGenerationKey}; +use masp_primitives::transaction::components::sapling; +use masp_primitives::transaction::components::sapling::builder::{ + BuildParams, ConvertBuildParams, OutputBuildParams, RngBuildParams, + SpendBuildParams, StoredBuildParams, +}; +use masp_primitives::transaction::components::sapling::fees::InputView; +use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedKey}; use namada_sdk::address::{Address, ImplicitAddress}; use namada_sdk::args::TxBecomeValidator; -use namada_sdk::collections::HashSet; +use namada_sdk::collections::{HashMap, HashSet}; use namada_sdk::governance::cli::onchain::{ DefaultProposal, PgfFundingProposal, PgfStewardProposal, }; @@ -19,7 +28,7 @@ use namada_sdk::tx::data::compute_inner_tx_hash; use namada_sdk::tx::{CompressedAuthorization, Section, Signer, Tx}; use namada_sdk::wallet::alias::{validator_address, validator_consensus_key}; use namada_sdk::wallet::{Wallet, WalletIo}; -use namada_sdk::{error, signing, tx, Namada}; +use namada_sdk::{error, signing, tx, ExtendedViewingKey, Namada}; use rand::rngs::OsRng; use tokio::sync::RwLock; @@ -816,15 +825,285 @@ pub async fn submit_transparent_transfer( Ok(()) } +// A mapper that replaces authorization signatures with those in a built-in map +struct MapSaplingSigAuth( + HashMap::AuthSig>, +); + +impl sapling::MapAuth + for MapSaplingSigAuth +{ + fn map_proof( + &self, + p: ::Proof, + _pos: usize, + ) -> ::Proof { + p + } + + fn map_auth_sig( + &self, + s: ::AuthSig, + pos: usize, + ) -> ::AuthSig { + self.0.get(&pos).cloned().unwrap_or(s) + } + + fn map_authorization(&self, a: sapling::Authorized) -> sapling::Authorized { + a + } +} + pub async fn submit_shielded_transfer( namada: &impl Namada, - args: args::TxShieldedTransfer, + mut args: args::TxShieldedTransfer, ) -> Result<(), error::Error> { - let (mut tx, signing_data) = args.clone().build(namada).await?; + // Records the shielded keys that are on the hardware wallet + let mut shielded_hw_keys = HashMap::new(); + // Construct the build parameters that parameterized the Transaction + // authorizations + let mut bparams: Box = if args.tx.use_device { + let transport = WalletTransport::from_arg(args.tx.device_transport); + let app = NamadaApp::new(transport); + // Clear hardware wallet randomness buffers + app.clean_randomness_buffers().await.map_err(|err| { + error::Error::Other(format!( + "Unable to clear randomness buffer. Error: {}", + err, + )) + })?; + let wallet = namada.wallet().await; + // Augment the pseudo spending key with a proof authorization key + for data in &mut args.data { + // Only attempt an augmentation if proof authorization is not there + if data.source.to_spending_key().is_none() { + // First find the derivation path corresponding to this viewing + // key + let viewing_key = + ExtendedViewingKey::from(data.source.to_viewing_key()); + let path = wallet + .find_path_by_viewing_key(&viewing_key) + .map_err(|err| { + error::Error::Other(format!( + "Unable to find derivation path from the wallet \ + for viewing key {}. Error: {}", + viewing_key, err, + )) + })?; + let path = BIP44Path { + path: path.to_string(), + }; + // Then confirm that the viewing key at this path in the + // hardware wallet matches the viewing key in this pseudo + // spending key + let response = app + .retrieve_keys(&path, NamadaKeys::ViewKey, true) + .await + .map_err(|err| { + error::Error::Other(format!( + "Unable to obtain viewing key from the hardware \ + wallet at path {}. Error: {}", + path.path, err, + )) + })?; + let KeyResponse::ViewKey(response_key) = response else { + return Err(error::Error::Other( + "Unexpected response from Ledger".to_string(), + )); + }; + let xfvk = + ExtendedFullViewingKey::try_from_slice(&response_key.xfvk) + .expect( + "unable to decode extended full viewing key from \ + the hardware wallet", + ); + if ExtendedFullViewingKey::from(viewing_key) != xfvk { + return Err(error::Error::Other(format!( + "Unexpected viewing key response from Ledger: {}", + ExtendedViewingKey::from(xfvk), + ))); + } + // Then obtain the proof authorization key at this path in the + // hardware wallet + let response = app + .retrieve_keys(&path, NamadaKeys::ProofGenerationKey, false) + .await + .map_err(|err| { + error::Error::Other(format!( + "Unable to obtain proof generation key from the \ + hardware wallet for viewing key {}. Error: {}", + viewing_key, err, + )) + })?; + let KeyResponse::ProofGenKey(response_key) = response else { + return Err(error::Error::Other( + "Unexpected response from Ledger".to_string(), + )); + }; + let pgk = ProofGenerationKey::try_from_slice( + &[response_key.ak, response_key.nsk].concat(), + ) + .map_err(|err| { + error::Error::Other(format!( + "Unexpected proof generation key in response from the \ + hardware wallet: {}.", + err, + )) + })?; + // Augment the pseudo spending key + data.source.augment_proof_generation_key(pgk).map_err( + |_| { + error::Error::Other( + "Proof generation key in response from the \ + hardware wallet does not correspond to stored \ + viewing key." + .to_string(), + ) + }, + )?; + // Finally, augment an incorrect spend authorization key just to + // make sure that the Transaction is built. + data.source.augment_spend_authorizing_key_unchecked( + PrivateKey(jubjub::Fr::default()), + ); + shielded_hw_keys.insert(path.path, viewing_key); + } + } + // Get randomness to aid in construction of various descriptors + let mut bparams = StoredBuildParams::default(); + // Number of spend descriptions is the number of transfers + let spend_len = args.data.len(); + // Number of convert description is assumed to be double the number of + // transfers. This is because each spend description might first be + // converted to epoch 0 before going to the intended epoch. + let convert_len = args.data.len() * 2; + // Number of output descriptions is assumed to be double the number of + // transfers. This is because there may be change from each output + // that's destined for the sender. + let output_len = args.data.len() * 2; + for _ in 0..spend_len { + let spend_randomness = app + .get_spend_randomness() + .await + .map_err(|err| error::Error::Other(err.to_string()))?; + bparams.spend_params.push(SpendBuildParams { + rcv: jubjub::Fr::from_bytes(&spend_randomness.rcv).unwrap(), + alpha: jubjub::Fr::from_bytes(&spend_randomness.alpha).unwrap(), + ..SpendBuildParams::default() + }); + } + for _ in 0..convert_len { + let convert_randomness = app + .get_convert_randomness() + .await + .map_err(|err| error::Error::Other(err.to_string()))?; + bparams.convert_params.push(ConvertBuildParams { + rcv: jubjub::Fr::from_bytes(&convert_randomness.rcv).unwrap(), + }); + } + for _ in 0..output_len { + let output_randomness = app + .get_output_randomness() + .await + .map_err(|err| error::Error::Other(err.to_string()))?; + bparams.output_params.push(OutputBuildParams { + rcv: jubjub::Fr::from_bytes(&output_randomness.rcv).unwrap(), + rseed: output_randomness.rcm, + ..OutputBuildParams::default() + }); + } + Box::new(bparams) + } else { + Box::new(RngBuildParams::new(OsRng)) + }; + let (mut tx, signing_data) = + args.clone().build(namada, &mut bparams).await?; if args.tx.dump_tx { tx::dump_tx(namada.io(), &args.tx, tx); } else { + // Get the MASP section that is the target of our signing + if let Some(shielded_hash) = signing_data.shielded_hash { + let mut masp_tx = tx + .get_masp_section(&shielded_hash) + .expect("Expected to find the indicated MASP Transaction") + .clone(); + + let masp_builder = tx + .get_masp_builder(&shielded_hash) + .expect("Expected to find the indicated MASP Builder"); + + // Reverse the spend metadata to enable looking up construction + // material + let sapling_inputs = masp_builder.builder.sapling_inputs(); + let mut descriptor_map = vec![0; sapling_inputs.len()]; + for i in 0.. { + if let Some(pos) = masp_builder.metadata.spend_index(i) { + descriptor_map[pos] = i; + } else { + break; + }; + } + // Sign the MASP Transaction using each relevant key in the + // hardware wallet + let mut app = None; + for (path, vk) in shielded_hw_keys { + // Initialize the Ledger app interface if it is uninitialized + let app = app.get_or_insert_with(|| { + NamadaApp::new(WalletTransport::from_arg( + args.tx.device_transport, + )) + }); + // Sign the MASP Transaction using the current viewing key + let path = BIP44Path { + path: path.to_string(), + }; + app.sign_masp_spends(&path, &tx.serialize_to_vec()) + .await + .map_err(|err| error::Error::Other(err.to_string()))?; + // Now prepare a new list of authorizations based on hardware + // wallet responses + let mut authorizations = HashMap::new(); + for (tx_pos, builder_pos) in descriptor_map.iter().enumerate() { + // Read the next spend authorization signature from the + // hardware wallet + let response = app + .get_spend_signature() + .await + .map_err(|err| error::Error::Other(err.to_string()))?; + let signature = redjubjub::Signature::try_from_slice( + &[response.rbar, response.sbar].concat(), + ) + .map_err(|err| { + error::Error::Other(format!( + "Unexpected spend authorization key in response \ + from the hardware wallet: {}.", + err, + )) + })?; + if *sapling_inputs[*builder_pos].key() + == ExtendedFullViewingKey::from(vk) + { + // If this descriptor was produced by the current + // viewing key (which comes from the hardware wallet), + // then use the authorization from the hardware wallet + authorizations.insert(tx_pos, signature); + } + } + // Finally, patch the MASP Transaction with the fetched spend + // authorization signature + masp_tx = (*masp_tx).clone().map_authorization::( + (), + MapSaplingSigAuth(authorizations), + ).freeze().map_err(|err| error::Error::Other(format!( + "Unable to apply hardware walleet sourced authorization \ + signatures to the transaction being constructed: {}.", + err, + )))?; + } + tx.remove_masp_section(&shielded_hash); + tx.add_section(Section::MaspTx(masp_tx)); + } sign(namada, &mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } diff --git a/crates/apps_lib/src/config/genesis/transactions.rs b/crates/apps_lib/src/config/genesis/transactions.rs index 4ab224db20..3d64a1b010 100644 --- a/crates/apps_lib/src/config/genesis/transactions.rs +++ b/crates/apps_lib/src/config/genesis/transactions.rs @@ -761,6 +761,7 @@ impl Signed { public_keys: pks.clone(), threshold, fee_payer: genesis_fee_payer_pk(), + shielded_hash: None, }; let mut tx = self.data.tx_to_sign(); diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index f2350ca722..6e12b24523 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -10,6 +10,7 @@ use masp_primitives::sapling::Node; use masp_primitives::transaction::sighash::{signature_hash, SignableInput}; use masp_primitives::transaction::txid::TxIdDigester; use masp_primitives::transaction::TransactionData; +use masp_primitives::zip32::ExtendedSpendingKey; use masp_proofs::group::GroupEncoding; use masp_proofs::sapling::BatchValidator; use namada_apps_lib::address::{self, Address, InternalAddress}; @@ -410,7 +411,9 @@ fn prepare_ibc_tx_and_ctx(bench_name: &str) -> (BenchShieldedCtx, BatchedTx) { shielded_ctx.shell.write().commit_block(); shielded_ctx.generate_shielded_action( Amount::native_whole(10), - TransferSource::ExtendedSpendingKey(albert_spending_key.key), + TransferSource::ExtendedSpendingKey( + ExtendedSpendingKey::from(albert_spending_key.key).into(), + ), defaults::bertha_address().to_string(), ) } @@ -602,12 +605,16 @@ fn setup_storage_for_masp_verification( ), "unshielding" => shielded_ctx.generate_masp_tx( amount, - TransferSource::ExtendedSpendingKey(albert_spending_key.key), + TransferSource::ExtendedSpendingKey( + ExtendedSpendingKey::from(albert_spending_key.key).into(), + ), TransferTarget::Address(defaults::albert_address()), ), "shielded" => shielded_ctx.generate_masp_tx( amount, - TransferSource::ExtendedSpendingKey(albert_spending_key.key), + TransferSource::ExtendedSpendingKey( + ExtendedSpendingKey::from(albert_spending_key.key).into(), + ), TransferTarget::PaymentAddress(bertha_payment_addr), ), _ => panic!("Unexpected bench test"), diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index ce9d7967c9..ca00a4b7be 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -11,6 +11,7 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::sapling::ViewingKey; use masp_primitives::transaction::TransparentAddress; pub use masp_primitives::transaction::TxId as TxIdInner; +use masp_primitives::zip32::{ExtendedKey, PseudoExtendedKey}; use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; @@ -67,7 +68,7 @@ pub struct MaspTxId( serialize_with = "serialize_txid", deserialize_with = "deserialize_txid" )] - TxIdInner, + pub TxIdInner, ); impl From for MaspTxId { @@ -518,12 +519,13 @@ impl<'de> serde::Deserialize<'de> for ExtendedSpendingKey { } /// Represents a source of funds for a transfer +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub enum TransferSource { /// A transfer coming from a transparent address Address(Address), /// A transfer coming from a shielded address - ExtendedSpendingKey(ExtendedSpendingKey), + ExtendedSpendingKey(PseudoExtendedKey), } impl TransferSource { @@ -538,7 +540,7 @@ impl TransferSource { } /// Get the contained ExtendedSpendingKey contained, if any - pub fn spending_key(&self) -> Option { + pub fn spending_key(&self) -> Option { match self { Self::ExtendedSpendingKey(x) => Some(*x), _ => None, @@ -566,7 +568,9 @@ impl Display for TransferSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Address(x) => x.fmt(f), - Self::ExtendedSpendingKey(x) => x.fmt(f), + Self::ExtendedSpendingKey(x) => { + ExtendedViewingKey::from(x.to_viewing_key()).fmt(f) + } } } } diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index e8224f4a24..d86dee1ad0 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -14,6 +14,7 @@ use std::sync::{Arc, Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; +use masp_primitives::transaction::components::sapling::builder::RngBuildParams; use masp_primitives::transaction::Transaction; use masp_primitives::zip32::ExtendedFullViewingKey; use masp_proofs::prover::LocalTxProver; @@ -1223,6 +1224,7 @@ impl BenchShieldedCtx { None, expiration, true, + &mut RngBuildParams::new(OsRng), ) .await }) diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index f7349c40ea..7ecfd966d5 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -5,6 +5,8 @@ use std::path::PathBuf; use std::str::FromStr; use std::time::Duration as StdDuration; +use masp_primitives::transaction::components::sapling::builder::BuildParams; +use masp_primitives::zip32::PseudoExtendedKey; use namada_core::address::Address; use namada_core::chain::{BlockHeight, ChainId, Epoch}; use namada_core::collections::HashMap; @@ -121,7 +123,7 @@ impl NamadaTypes for SdkTypes { type MaspIndexerAddress = String; type PaymentAddress = namada_core::masp::PaymentAddress; type PublicKey = namada_core::key::common::PublicKey; - type SpendingKey = namada_core::masp::ExtendedSpendingKey; + type SpendingKey = PseudoExtendedKey; type TendermintAddress = tendermint_rpc::Url; type TransferSource = namada_core::masp::TransferSource; type TransferTarget = namada_core::masp::TransferTarget; @@ -381,8 +383,9 @@ impl TxShieldedTransfer { pub async fn build( &mut self, context: &impl Namada, + bparams: &mut impl BuildParams, ) -> crate::error::Result<(namada_tx::Tx, SigningTxData)> { - tx::build_shielded_transfer(context, self).await + tx::build_shielded_transfer(context, self, bparams).await } } diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index f3f0e2284b..c016902b38 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -45,6 +45,7 @@ use std::path::PathBuf; use std::str::FromStr; use args::{DeviceTransport, InputAmount, SdkTypes}; +use masp_primitives::zip32::PseudoExtendedKey; use namada_core::address::Address; use namada_core::collections::HashSet; use namada_core::dec::Dec; @@ -171,7 +172,7 @@ pub trait Namada: NamadaIo { fn new_shielded_transfer( &self, data: Vec, - gas_spending_keys: Vec, + gas_spending_keys: Vec, disposable_signing_key: bool, ) -> args::TxShieldedTransfer { args::TxShieldedTransfer { @@ -202,9 +203,9 @@ pub trait Namada: NamadaIo { /// arguments fn new_unshielding_transfer( &self, - source: ExtendedSpendingKey, + source: PseudoExtendedKey, data: Vec, - gas_spending_keys: Vec, + gas_spending_keys: Vec, disposable_signing_key: bool, ) -> args::TxUnshieldingTransfer { args::TxUnshieldingTransfer { diff --git a/crates/sdk/src/signing.rs b/crates/sdk/src/signing.rs index 8d1c058489..d396e2b444 100644 --- a/crates/sdk/src/signing.rs +++ b/crates/sdk/src/signing.rs @@ -73,6 +73,8 @@ pub struct SigningTxData { pub account_public_keys_map: Option, /// The public keys of the fee payer pub fee_payer: common::PublicKey, + /// ID of the Transaction needing signing + pub shielded_hash: Option, } /// Find the public key for the given address and try to load the keypair @@ -395,6 +397,7 @@ pub async fn aux_signing_data( threshold, account_public_keys_map, fee_payer, + shielded_hash: None, }) } diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 9e74fd3cc5..a5e47be354 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -10,6 +10,9 @@ use borsh::BorshSerialize; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::builder::Builder; +use masp_primitives::transaction::components::sapling::builder::{ + BuildParams, RngBuildParams, +}; use masp_primitives::transaction::components::sapling::fees::{ ConvertView, InputView as SaplingInputView, OutputView as SaplingOutputView, }; @@ -18,6 +21,7 @@ use masp_primitives::transaction::components::transparent::fees::{ }; use masp_primitives::transaction::components::I128Sum; use masp_primitives::transaction::{builder, Transaction as MaspTransaction}; +use masp_primitives::zip32::PseudoExtendedKey; use namada_account::{InitAccount, UpdateAccount}; use namada_core::address::{Address, IBC, MASP}; use namada_core::arith::checked; @@ -38,9 +42,7 @@ use namada_core::ibc::core::client::types::Height as IbcHeight; use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::ibc::primitives::Timestamp as IbcTimestamp; use namada_core::key::{self, *}; -use namada_core::masp::{ - AssetData, ExtendedSpendingKey, MaspEpoch, TransferSource, TransferTarget, -}; +use namada_core::masp::{AssetData, MaspEpoch, TransferSource, TransferTarget}; use namada_core::storage; use namada_core::time::DateTimeUtc; use namada_governance::cli::onchain::{ @@ -2629,6 +2631,7 @@ pub async fn build_ibc_transfer( masp_fee_data, !(args.tx.dry_run || args.tx.dry_run_wrapper), args.tx.expiration.to_datetime(), + &mut RngBuildParams::new(OsRng), ) .await?; let shielded_tx_epoch = shielded_parts.as_ref().map(|trans| trans.0.epoch); @@ -3030,8 +3033,9 @@ pub async fn build_transparent_transfer( pub async fn build_shielded_transfer( context: &N, args: &mut args::TxShieldedTransfer, + bparams: &mut impl BuildParams, ) -> Result<(Tx, SigningTxData)> { - let signing_data = signing::aux_signing_data( + let mut signing_data = signing::aux_signing_data( context, &args.tx, Some(MASP), @@ -3094,6 +3098,7 @@ pub async fn build_shielded_transfer( masp_fee_data, !(args.tx.dry_run || args.tx.dry_run_wrapper), args.tx.expiration.to_datetime(), + bparams, ) .await? .expect("Shielded transfer must have shielded parts"); @@ -3123,6 +3128,7 @@ pub async fn build_shielded_transfer( }); data.shielded_section_hash = Some(section_hash); + signing_data.shielded_hash = Some(section_hash); tracing::debug!("Transfer data {data:?}"); Ok(()) }; @@ -3147,7 +3153,7 @@ async fn get_masp_fee_payment_amount( args: &args::Tx, fee_amount: DenominatedAmount, fee_payer: &common::PublicKey, - gas_spending_keys: Vec, + gas_spending_keys: Vec, ) -> Result> { let fee_payer_address = Address::from(fee_payer); let balance_key = balance_key(&args.fee_token, &fee_payer_address); @@ -3261,6 +3267,7 @@ pub async fn build_shielding_transfer( None, !(args.tx.dry_run || args.tx.dry_run_wrapper), args.tx.expiration.to_datetime(), + &mut RngBuildParams::new(OsRng), ) .await? .expect("Shielding transfer must have shielded parts"); @@ -3383,6 +3390,7 @@ pub async fn build_unshielding_transfer( masp_fee_data, !(args.tx.dry_run || args.tx.dry_run_wrapper), args.tx.expiration.to_datetime(), + &mut RngBuildParams::new(OsRng), ) .await? .expect("Shielding transfer must have shielded parts"); @@ -3436,6 +3444,7 @@ async fn construct_shielded_parts( fee_data: Option, update_ctx: bool, expiration: Option, + bparams: &mut impl BuildParams, ) -> Result)>> { // Precompute asset types to increase chances of success in decoding let token_map = context.wallet().await.get_addresses(); @@ -3449,7 +3458,7 @@ async fn construct_shielded_parts( shielded .gen_shielded_transfer( - context, data, fee_data, expiration, update_ctx, + context, data, fee_data, expiration, update_ctx, bparams, ) .await }; @@ -3816,6 +3825,7 @@ pub async fn gen_ibc_shielding_transfer( None, args.expiration.to_datetime(), true, + &mut RngBuildParams::new(OsRng), ) .await .map_err(|err| TxSubmitError::MaspError(err.to_string()))? diff --git a/crates/shielded_token/src/masp.rs b/crates/shielded_token/src/masp.rs index cc1fc79fbb..c95ae9ac74 100644 --- a/crates/shielded_token/src/masp.rs +++ b/crates/shielded_token/src/masp.rs @@ -26,7 +26,8 @@ use masp_primitives::transaction::components::sapling::builder::SaplingMetadata; use masp_primitives::transaction::components::{I128Sum, ValueSum}; use masp_primitives::transaction::Transaction; use masp_primitives::zip32::{ - ExtendedFullViewingKey, ExtendedSpendingKey as MaspExtendedSpendingKey, + ExtendedFullViewingKey, ExtendedKey, + ExtendedSpendingKey as MaspExtendedSpendingKey, PseudoExtendedKey, }; use masp_proofs::prover::LocalTxProver; use namada_core::address::Address; @@ -81,7 +82,7 @@ pub struct ShieldedTransfer { #[allow(missing_docs)] #[derive(Debug)] pub struct MaspFeeData { - pub sources: Vec, + pub sources: Vec, pub target: Address, pub token: Address, pub amount: token::DenominatedAmount, @@ -131,7 +132,7 @@ pub struct MaspTxReorderedData { /// Data about the unspent amounts for any given shielded source coming from the /// spent notes in their posses that have been added to the builder. Can be used /// to either pay fees or to return a change -pub type Changes = HashMap; +pub type Changes = HashMap; /// Shielded pool data for a token #[allow(missing_docs)] @@ -169,20 +170,20 @@ pub struct WalletMap; impl masp_primitives::transaction::components::sapling::builder::MapBuilder< P1, - MaspExtendedSpendingKey, + PseudoExtendedKey, (), ExtendedFullViewingKey, > for WalletMap { fn map_params(&self, _s: P1) {} - fn map_key(&self, s: MaspExtendedSpendingKey) -> ExtendedFullViewingKey { - (&s).into() + fn map_key(&self, s: PseudoExtendedKey) -> ExtendedFullViewingKey { + s.to_viewing_key() } } impl - MapBuilder + MapBuilder for WalletMap { fn map_notifier(&self, _s: N1) {} @@ -826,7 +827,7 @@ pub mod testing { mut rng in arb_rng().prop_map(TestCsprng), bparams_rng in arb_rng().prop_map(TestCsprng), prover_rng in arb_rng().prop_map(TestCsprng), - ) -> (MaspExtendedSpendingKey, Diversifier, Note, Node) { + ) -> (PseudoExtendedKey, Diversifier, Note, Node) { let mut spending_key_seed = [0; 32]; rng.fill_bytes(&mut spending_key_seed); let spending_key = MaspExtendedSpendingKey::master(spending_key_seed.as_ref()); @@ -837,7 +838,7 @@ pub mod testing { .to_payment_address(div) .expect("a PaymentAddress"); - let mut builder = Builder::::new( + let mut builder = Builder::::new( NETWORK, // NOTE: this is going to add 20 more blocks to the actual // expiration but there's no other exposed function that we could @@ -871,7 +872,7 @@ pub mod testing { assert_eq!(payment_addr, pa); // Make a path to out new note let node = Node::new(shielded_output.cmu.to_repr()); - (spending_key, div, note, node) + (PseudoExtendedKey::from(spending_key), div, note, node) } } @@ -916,7 +917,7 @@ pub mod testing { ).unwrap(), *value, )).collect::>() - ) -> Vec<(MaspExtendedSpendingKey, Diversifier, Note, Node)> { + ) -> Vec<(PseudoExtendedKey, Diversifier, Note, Node)> { spend_description } } diff --git a/crates/shielded_token/src/masp/shielded_wallet.rs b/crates/shielded_token/src/masp/shielded_wallet.rs index 470e197995..d54b5279bf 100644 --- a/crates/shielded_token/src/masp/shielded_wallet.rs +++ b/crates/shielded_token/src/masp/shielded_wallet.rs @@ -18,13 +18,13 @@ use masp_primitives::sapling::{ Diversifier, Node, Note, Nullifier, ViewingKey, }; use masp_primitives::transaction::builder::Builder; -use masp_primitives::transaction::components::sapling::builder::RngBuildParams; +use masp_primitives::transaction::components::sapling::builder::BuildParams; use masp_primitives::transaction::components::{ I128Sum, TxOut, U64Sum, ValueSum, }; use masp_primitives::transaction::fees::fixed::FeeRule; use masp_primitives::transaction::{builder, Transaction}; -use masp_primitives::zip32::ExtendedSpendingKey as MaspExtendedSpendingKey; +use masp_primitives::zip32::{ExtendedKey, PseudoExtendedKey}; use namada_core::address::Address; use namada_core::arith::checked; use namada_core::borsh::{BorshDeserialize, BorshSerialize}; @@ -51,12 +51,11 @@ use rand_core::{OsRng, SeedableRng}; use crate::masp::utils::MaspClient; use crate::masp::{ - cloned_pair, is_amount_required, to_viewing_key, Changes, - ContextSyncStatus, Conversions, MaspAmount, MaspDataLog, MaspFeeData, - MaspSourceTransferData, MaspTargetTransferData, MaspTransferData, - MaspTxReorderedData, NoteIndex, ShieldedSyncConfig, ShieldedTransfer, - ShieldedUtils, SpentNotesTracker, TransferErr, WalletMap, WitnessMap, - NETWORK, + cloned_pair, is_amount_required, Changes, ContextSyncStatus, Conversions, + MaspAmount, MaspDataLog, MaspFeeData, MaspSourceTransferData, + MaspTargetTransferData, MaspTransferData, MaspTxReorderedData, NoteIndex, + ShieldedSyncConfig, ShieldedTransfer, ShieldedUtils, SpentNotesTracker, + TransferErr, WalletMap, WitnessMap, NETWORK, }; #[cfg(any(test, feature = "testing"))] use crate::masp::{testing, ENV_VAR_MASP_TEST_SEED}; @@ -689,7 +688,7 @@ pub trait ShieldedApi: &mut self, context: &impl NamadaIo, spent_notes: &mut SpentNotesTracker, - sk: namada_core::masp::ExtendedSpendingKey, + sk: PseudoExtendedKey, is_native_token: bool, target: I128Sum, target_epoch: MaspEpoch, @@ -702,7 +701,7 @@ pub trait ShieldedApi: ), eyre::Error, > { - let vk = &to_viewing_key(&sk.into()).vk; + let vk = &sk.to_viewing_key().fvk.vk; // TODO: we should try to use the smallest notes possible to fund the // transaction to allow people to fetch less often // Establish connection with which to do exchange rate queries @@ -907,6 +906,7 @@ pub trait ShieldedApi: fee_data: Option, expiration: Option, update_ctx: bool, + bparams: &mut impl BuildParams, ) -> Result, TransferErr> { // Determine epoch in which to submit potential shielded transaction let epoch = Self::query_masp_epoch(context.client()) @@ -979,7 +979,7 @@ pub trait ShieldedApi: u32::MAX - 20 } }; - let mut builder = Builder::::new( + let mut builder = Builder::::new( NETWORK, // NOTE: this is going to add 20 more blocks to the actual // expiration but there's no other exposed function that we could @@ -1082,7 +1082,7 @@ pub trait ShieldedApi: &prover, &FeeRule::non_standard(U64Sum::zero()), &mut rng, - &mut RngBuildParams::new(OsRng), + bparams, ) .map_err(|error| TransferErr::Build { error, data: None })?; @@ -1187,7 +1187,7 @@ pub trait ShieldedApi: async fn add_inputs( &mut self, context: &impl NamadaIo, - builder: &mut Builder, + builder: &mut Builder, source: &TransferSource, token: &Address, amount: &DenominatedAmount, @@ -1244,12 +1244,7 @@ pub trait ShieldedApi: // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { builder - .add_sapling_spend( - sk.into(), - diversifier, - note, - merkle_path, - ) + .add_sapling_spend(sk, diversifier, note, merkle_path) .map_err(|e| TransferErr::Build { error: builder::Error::SaplingBuild(e), data: None, @@ -1316,7 +1311,7 @@ pub trait ShieldedApi: async fn add_outputs( &mut self, context: &impl NamadaIo, - builder: &mut Builder, + builder: &mut Builder, source: TransferSource, target: &TransferTarget, token: Address, @@ -1362,9 +1357,8 @@ pub trait ShieldedApi: let contr = std::cmp::min(u128::from(*rem_amount), val) as u64; // If we are sending to a shielded address, we need the outgoing // viewing key in the following computations. - let ovk_opt = source - .spending_key() - .map(|x| MaspExtendedSpendingKey::from(x).expsk.ovk); + let ovk_opt = + source.spending_key().map(|x| x.to_viewing_key().fvk.ovk); // Make transaction output tied to the current token, // denomination, and epoch. if let Some(pa) = payment_address { @@ -1457,9 +1451,9 @@ pub trait ShieldedApi: async fn add_fees( &mut self, context: &impl NamadaIo, - builder: &mut Builder, + builder: &mut Builder, source_data: &HashMap, - sources: Vec, + sources: Vec, target: &Address, token: &Address, amount: &DenominatedAmount, @@ -1699,17 +1693,17 @@ pub trait ShieldedApi: #[allow(clippy::result_large_err)] #[allow(async_fn_in_trait)] fn add_changes( - builder: &mut Builder, + builder: &mut Builder, changes: Changes, ) -> Result<(), TransferErr> { for (sp, changes) in changes.into_iter() { for (asset_type, amt) in changes.components() { if let Ordering::Greater = amt.cmp(&0) { - let sk = MaspExtendedSpendingKey::from(sp.to_owned()); + let sk = sp.to_viewing_key(); // Send the change in this asset type back to the sender builder .add_sapling_output( - Some(sk.expsk.ovk), + Some(sk.fvk.ovk), sk.default_address().1, *asset_type, *amt as u64, diff --git a/crates/shielded_token/src/validation.rs b/crates/shielded_token/src/validation.rs index 65a970cc85..39d46748f6 100644 --- a/crates/shielded_token/src/validation.rs +++ b/crates/shielded_token/src/validation.rs @@ -16,6 +16,7 @@ use masp_primitives::transaction::txid::TxIdDigester; use masp_primitives::transaction::{ Authorization, Authorized, Transaction, TransactionData, Unauthorized, }; +use masp_primitives::zip32::ExtendedSpendingKey; use masp_proofs::bellman::groth16::VerifyingKey; use masp_proofs::sapling::BatchValidator; use namada_gas::Gas; @@ -56,7 +57,8 @@ pub struct PartialAuthorized; impl Authorization for PartialAuthorized { type SaplingAuth = ::SaplingAuth; - type TransparentAuth = ::TransparentAuth; + type TransparentAuth = + as Authorization>::TransparentAuth; } /// MASP verifying keys diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index 07690d1e0e..108d18dfa3 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -3587,6 +3587,7 @@ fn identical_output_descriptions() -> Result<()> { threshold: 1, account_public_keys_map: None, fee_payer: albert_keypair().to_public(), + shielded_hash: None, }; let (mut batched_tx, _signing_data) = namada_sdk::tx::build_batch(vec![ diff --git a/crates/token/src/lib.rs b/crates/token/src/lib.rs index dff43909bf..30d19bc3a5 100644 --- a/crates/token/src/lib.rs +++ b/crates/token/src/lib.rs @@ -284,6 +284,7 @@ pub mod testing { }; use masp_primitives::transaction::components::{TxOut, U64Sum}; use masp_primitives::transaction::fees::fixed::FeeRule; + use masp_primitives::zip32::PseudoExtendedKey; use namada_core::address::testing::{ arb_established_address, arb_non_internal_address, }; @@ -364,7 +365,7 @@ pub mod testing { assets in Just(assets), ) -> ( Transfer, - Builder::, + Builder::, HashMap, ) { // Enable assets to be more easily decoded diff --git a/crates/tx/src/types.rs b/crates/tx/src/types.rs index 156f62c211..dc392572f4 100644 --- a/crates/tx/src/types.rs +++ b/crates/tx/src/types.rs @@ -1292,6 +1292,30 @@ impl Tx { None } + /// Remove the transaction section with the given hash + pub fn remove_masp_section(&mut self, hash: &MaspTxId) { + self.sections.retain(|section| { + if let Section::MaspTx(masp) = section { + if MaspTxId::from(masp.txid()) == *hash { + return false; + } + } + true + }); + } + + /// Get the MASP builder section with the given hash + pub fn get_masp_builder(&self, hash: &MaspTxId) -> Option<&MaspBuilder> { + for section in &self.sections { + if let Section::MaspBuilder(builder) = section { + if builder.target == *hash { + return Some(builder); + } + } + } + None + } + /// Set the last transaction memo hash stored in the header pub fn set_memo_sechash(&mut self, hash: namada_core::hash::Hash) { let item = match self.header.batch.pop() { diff --git a/crates/wallet/src/lib.rs b/crates/wallet/src/lib.rs index 2341979f9a..68e6fec27b 100644 --- a/crates/wallet/src/lib.rs +++ b/crates/wallet/src/lib.rs @@ -585,8 +585,21 @@ impl Wallet { (mnemonic, passphrase) }; let seed = Seed::new(&mnemonic, &passphrase); - let spend_key = - derive_hd_spending_key(seed.as_bytes(), derivation_path.clone()); + // Path to obtain the ZIP32 seed + let zip32_seed_path = + DerivationPath::default_for_transparent_scheme(SchemeType::Ed25519); + // Obtain the ZIP32 seed using SLIP10 + let seed = derive_hd_secret_key( + SchemeType::Ed25519, + seed.as_bytes(), + zip32_seed_path, + ) + .try_to_sk::() + .expect("Expected Ed25519 key") + .0 + .to_bytes(); + // Now ZIP32 derive the extended spending key from the new seed + let spend_key = derive_hd_spending_key(&seed, derivation_path.clone()); self.insert_spending_key( alias, @@ -599,6 +612,16 @@ impl Wallet { .map(|alias| (alias, spend_key)) } + /// Find a derivation path by viewing key + pub fn find_path_by_viewing_key( + &self, + vk: &ExtendedViewingKey, + ) -> Result { + self.store + .find_path_by_viewing_key(vk) + .ok_or_else(|| FindKeyError::KeyNotFound(vk.to_string())) + } + /// Restore a keypair from the user mnemonic code (read from stdin) using /// a given BIP44 derivation path and derive an implicit address from its /// public part and insert them into the store with the provided alias, @@ -1137,12 +1160,14 @@ impl Wallet { view_key: ExtendedViewingKey, birthday: Option, force_alias: bool, + path: Option, ) -> Option { self.store .insert_viewing_key::( alias.into(), view_key, birthday, + path, force_alias, ) .map(Into::into) diff --git a/crates/wallet/src/store.rs b/crates/wallet/src/store.rs index 287bd7822d..8237abbab5 100644 --- a/crates/wallet/src/store.rs +++ b/crates/wallet/src/store.rs @@ -194,6 +194,19 @@ impl Store { self.derivation_paths.get(self.pkhs.get(pkh)?).cloned() } + /// Find a derivation path by viewing key + pub fn find_path_by_viewing_key( + &self, + viewing_key: &ExtendedViewingKey, + ) -> Option { + for (alias, vk) in &self.view_keys { + if *viewing_key == vk.key { + return self.derivation_paths.get(alias).cloned(); + } + } + None + } + /// Find the public key by a public key hash. pub fn find_public_key_by_pkh( &self, @@ -418,6 +431,7 @@ impl Store { alias: Alias, viewkey: ExtendedViewingKey, birthday: Option, + path: Option, force: bool, ) -> Option { // abort if the alias is reserved @@ -435,7 +449,7 @@ impl Store { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { return self.insert_viewing_key::( - new_alias, viewkey, birthday, false, + new_alias, viewkey, birthday, path, false, ); } ConfirmationResponse::Skip => return None, @@ -444,6 +458,7 @@ impl Store { self.remove_alias(&alias); self.view_keys .insert(alias.clone(), DatedKeypair::new(viewkey, birthday)); + path.map(|p| self.derivation_paths.insert(alias.clone(), p)); Some(alias) } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 70b07f5340..e1cd900a07 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "masp_note_encryption" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "borsh", "chacha20", @@ -3462,7 +3462,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "aes", "bip0039", @@ -3494,7 +3494,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "bellman", "blake2b_simd", diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index b854ddb998..550b010015 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -1863,7 +1863,7 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "masp_note_encryption" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "borsh", "chacha20", @@ -1876,7 +1876,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "aes", "bip0039", @@ -1907,7 +1907,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=a35f73be69b21ee62cd4940f37855161cbed2a56#a35f73be69b21ee62cd4940f37855161cbed2a56" dependencies = [ "bellman", "blake2b_simd",