From 0a6f16feb303a2854456ce8a9df8f0dc46638c13 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 24 Mar 2025 13:10:03 +0000 Subject: [PATCH 01/42] Add FMD flag ciphertext sechash to transfer data --- crates/core/src/masp.rs | 39 ++++++++++++++++++++ crates/ibc/src/msg.rs | 1 + crates/light_sdk/src/transaction/transfer.rs | 8 +++- crates/node/src/bench_utils.rs | 33 ++++++++++------- crates/sdk/src/signing.rs | 35 ++++++++++-------- crates/sdk/src/tx.rs | 32 +++++++++++++--- crates/shielded_token/src/lib.rs | 4 +- crates/token/src/lib.rs | 24 +++++++++--- crates/token/src/tx.rs | 4 +- wasm/tx_ibc/src/lib.rs | 2 +- 10 files changed, 137 insertions(+), 45 deletions(-) diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index fe8fecee579..8f992e9b3c1 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -23,6 +23,7 @@ use sha2::Sha256; use crate::address::{Address, DecodeError, HASH_HEX_LEN, IBC, MASP}; use crate::borsh::BorshSerializeExt; use crate::chain::Epoch; +use crate::hash::Hash; use crate::impl_display_and_from_str_via_format; use crate::string_encoding::{ self, MASP_EXT_FULL_VIEWING_KEY_HRP, MASP_EXT_SPENDING_KEY_HRP, @@ -85,6 +86,44 @@ impl Display for MaspTxId { } } +/// Pointers to MASP data included in Namada transactions. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive( + Serialize, + Deserialize, + Clone, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Debug, + Eq, + PartialEq, + Copy, + Ord, + PartialOrd, + Hash, +)] +pub struct MaspTxData { + /// Id of the MASP transaction. + /// + /// This is used to look-up a MASP transaction section. + pub masp_tx_id: MaspTxId, + /// Section hash of the FMD flag ciphertext. + /// + /// This is used to look-up a transaction data section + /// containing an FMD flag ciphertext. + pub flag_ciphertext_sechash: Hash, +} + +impl Display for MaspTxData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("") + .field("masp_tx_id", &self.masp_tx_id) + .field("flag_ciphertext_sechash", &self.flag_ciphertext_sechash) + .finish() + } +} + /// Wrapper type around `Epoch` for type safe operations involving the masp /// epoch #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] diff --git a/crates/ibc/src/msg.rs b/crates/ibc/src/msg.rs index d38466bb5e5..00b7610461f 100644 --- a/crates/ibc/src/msg.rs +++ b/crates/ibc/src/msg.rs @@ -238,6 +238,7 @@ impl BorshSchema for MsgNftTransfer { /// Shielding data in IBC packet memo #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +// TODO: add flag ciphertext here pub struct IbcShieldingData(pub MaspTransaction); impl From<&IbcShieldingData> for String { diff --git a/crates/light_sdk/src/transaction/transfer.rs b/crates/light_sdk/src/transaction/transfer.rs index 2e0e6833908..e3c51c6a08d 100644 --- a/crates/light_sdk/src/transaction/transfer.rs +++ b/crates/light_sdk/src/transaction/transfer.rs @@ -2,7 +2,7 @@ use namada_sdk::address::Address; use namada_sdk::hash::Hash; use namada_sdk::key::common; pub use namada_sdk::token::{ - DenominatedAmount, MaspTransaction, MaspTxId, Transfer, + DenominatedAmount, MaspTransaction, MaspTxData, MaspTxId, Transfer, }; use namada_sdk::tx::data::GasLimit; use namada_sdk::tx::{Authorization, TX_TRANSFER_WASM, Tx, TxError}; @@ -27,10 +27,14 @@ impl TransferBuilder { /// Build a shielded transfer transaction from the given parameters pub fn shielded( shielded_section_hash: MaspTxId, + flag_ciphertext_sechash: Hash, transaction: MaspTransaction, args: GlobalArgs, ) -> Self { - let data = Transfer::masp(shielded_section_hash); + let data = Transfer::masp(MaspTxData { + masp_tx_id: shielded_section_hash, + flag_ciphertext_sechash, + }); let mut tx = transaction::build_tx(args, data, TX_TRANSFER_WASM.to_string()); tx.add_masp_tx_section(transaction); diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index 2e746b18459..a4478290509 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -89,7 +89,9 @@ use namada_sdk::state::StorageRead; use namada_sdk::state::write_log::StorageModification; use namada_sdk::storage::{Key, KeySeg, TxIndex}; use namada_sdk::time::DateTimeUtc; -use namada_sdk::token::{self, Amount, DenominatedAmount, Transfer}; +use namada_sdk::token::{ + self, Amount, DenominatedAmount, MaspTxData, Transfer, +}; use namada_sdk::tx::data::pos::Bond; use namada_sdk::tx::data::{ BatchedTxResult, Fee, TxResult, VpsResult, compute_inner_tx_hash, @@ -1279,13 +1281,18 @@ impl BenchShieldedCtx { ) .expect("MASP must have shielded part"); - let shielded_section_hash = shielded.txid().into(); + let shielded_data = MaspTxData { + masp_tx_id: shielded.txid().into(), + // TODO: change this to the actual sechash + // of the fmd flag + flag_ciphertext_sechash: Default::default(), + }; let tx = if source.effective_address() == MASP && target.effective_address() == MASP { namada.client().read().generate_tx( TX_TRANSFER_WASM, - Transfer::masp(shielded_section_hash), + Transfer::masp(shielded_data), Some(shielded), None, vec![&defaults::albert_keypair()], @@ -1293,7 +1300,7 @@ impl BenchShieldedCtx { } else if target.effective_address() == MASP { namada.client().read().generate_tx( TX_TRANSFER_WASM, - Transfer::masp(shielded_section_hash) + Transfer::masp(shielded_data) .transfer( source.effective_address(), MASP, @@ -1308,7 +1315,7 @@ impl BenchShieldedCtx { } else { namada.client().read().generate_tx( TX_TRANSFER_WASM, - Transfer::masp(shielded_section_hash) + Transfer::masp(shielded_data) .transfer( MASP, target.effective_address(), @@ -1382,6 +1389,7 @@ impl BenchShieldedCtx { let vectorized_transfer = Transfer::deserialize(&mut tx.tx.data(&tx.cmt).unwrap().as_slice()) .unwrap(); + let masp_tx_id = vectorized_transfer.masp_tx_id().unwrap(); let sources = vec![vectorized_transfer.sources.into_iter().next().unwrap()] .into_iter() @@ -1393,15 +1401,14 @@ impl BenchShieldedCtx { let transfer = Transfer { sources, targets, - shielded_section_hash: Some( - vectorized_transfer.shielded_section_hash.unwrap(), - ), + shielded_data: Some(MaspTxData { + masp_tx_id, + // TODO: change this to the actual sechash + // of the fmd flag + flag_ciphertext_sechash: Default::default(), + }), }; - let masp_tx = tx - .tx - .get_masp_section(&transfer.shielded_section_hash.unwrap()) - .unwrap() - .clone(); + let masp_tx = tx.tx.get_masp_section(&masp_tx_id).unwrap().clone(); let msg = MsgTransfer:: { message: msg, transfer: Some(transfer), diff --git a/crates/sdk/src/signing.rs b/crates/sdk/src/signing.rs index abca6c7d978..0513876f566 100644 --- a/crates/sdk/src/signing.rs +++ b/crates/sdk/src/signing.rs @@ -1426,12 +1426,9 @@ pub async fn to_ledger_vector( // To facilitate lookups of MASP AssetTypes let mut asset_types = HashMap::new(); - let builder = find_masp_builder( - tx, - transfer.shielded_section_hash, - &mut asset_types, - ) - .map_err(|_| Error::Other("Invalid Data".to_string()))?; + let builder = + find_masp_builder(tx, transfer.masp_tx_id(), &mut asset_types) + .map_err(|_| Error::Other("Invalid Data".to_string()))?; make_ledger_token_transfer_endpoints( tokens, &mut tv.output, @@ -1526,7 +1523,7 @@ pub async fn to_ledger_vector( let mut asset_types = HashMap::new(); let builder = find_masp_builder( tx, - transfer.shielded_section_hash, + transfer.masp_tx_id(), &mut asset_types, ) .map_err(|_| Error::Other("Invalid Data".to_string()))?; @@ -1695,7 +1692,7 @@ pub async fn to_ledger_vector( let mut asset_types = HashMap::new(); let builder = find_masp_builder( tx, - transfer.shielded_section_hash, + transfer.masp_tx_id(), &mut asset_types, ) .map_err(|_| Error::Other("Invalid Data".to_string()))?; @@ -2190,7 +2187,7 @@ mod test_signing { use namada_core::hash::Hash; use namada_core::ibc::PGFIbcTarget; use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; - use namada_core::masp::TxIdInner; + use namada_core::masp::{MaspTxData, TxIdInner}; use namada_core::token::{Denomination, MaspDigitPos}; use namada_governance::storage::proposal::PGFInternalTarget; use namada_io::client::EncodedResponseQuery; @@ -2616,7 +2613,7 @@ mod test_signing { }, DenominatedAmount::new(Amount::from_u64(2), 0.into()), )]), - shielded_section_hash: None, + shielded_data: None, }; let tokens = HashMap::from([ (Address::Internal(InternalAddress::Governance), "SuperMoney"), @@ -2768,15 +2765,19 @@ mod test_signing { fn test_find_masp_builder() { let mut tx = Tx::new(ChainId::default(), None); let mut asset_types = Default::default(); - let shielded_section_hash = MaspTxId::from(TxIdInner::from_bytes([ + let masp_tx_id = MaspTxId::from(TxIdInner::from_bytes([ 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ])); + let shielded_data = MaspTxData { + masp_tx_id, + flag_ciphertext_sechash: Hash::zero(), + }; // no masp builder present assert_eq!( find_masp_builder( &tx, - Some(shielded_section_hash), + Some(shielded_data.masp_tx_id), &mut asset_types ) .expect("Test failed"), @@ -2837,9 +2838,13 @@ mod test_signing { assert!(asset_types.is_empty()); // now we should find the builder - find_masp_builder(&tx, Some(shielded_section_hash), &mut asset_types) - .expect("Test failed") - .expect("Test failed"); + find_masp_builder( + &tx, + Some(shielded_data.masp_tx_id), + &mut asset_types, + ) + .expect("Test failed") + .expect("Test failed"); assert_eq!( asset_types .values() diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index dc79c58e6da..09a554c22e2 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -41,7 +41,9 @@ 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::{IntoTimestamp, Timestamp as IbcTimestamp}; use namada_core::key::{self, *}; -use namada_core::masp::{AssetData, MaspEpoch, TransferSource, TransferTarget}; +use namada_core::masp::{ + AssetData, MaspEpoch, MaspTxData, TransferSource, TransferTarget, +}; use namada_core::storage; use namada_core::time::DateTimeUtc; use namada_events::extend::EventAttributeEntry; @@ -2819,7 +2821,12 @@ pub async fn build_ibc_transfer( .map(|(shielded_transfer, asset_types)| { let masp_tx_hash = tx.add_masp_tx_section(shielded_transfer.masp_tx.clone()).1; - transfer.shielded_section_hash = Some(masp_tx_hash); + transfer.shielded_data = Some(MaspTxData { + masp_tx_id: masp_tx_hash, + // TODO: change this to the actual sechash + // of the fmd flag + flag_ciphertext_sechash: Hash::zero(), + }); signing_data.shielded_hash = Some(masp_tx_hash); tx.add_masp_builder(MaspBuilder { asset_types, @@ -3265,7 +3272,12 @@ pub async fn build_shielded_transfer( target: section_hash, }); - data.shielded_section_hash = Some(section_hash); + data.shielded_data = Some(MaspTxData { + masp_tx_id: section_hash, + // TODO: change this to the actual sechash + // of the fmd flag + flag_ciphertext_sechash: Hash::zero(), + }); signing_data.shielded_hash = Some(section_hash); tracing::debug!("Transfer data {data:?}"); Ok(()) @@ -3435,7 +3447,12 @@ pub async fn build_shielding_transfer( target: shielded_section_hash, }); - data.shielded_section_hash = Some(shielded_section_hash); + data.shielded_data = Some(MaspTxData { + masp_tx_id: shielded_section_hash, + // TODO: change this to the actual sechash + // of the fmd flag + flag_ciphertext_sechash: Hash::zero(), + }); signing_data.shielded_hash = Some(shielded_section_hash); tracing::debug!("Transfer data {data:?}"); Ok(()) @@ -3558,7 +3575,12 @@ pub async fn build_unshielding_transfer( target: shielded_section_hash, }); - data.shielded_section_hash = Some(shielded_section_hash); + data.shielded_data = Some(MaspTxData { + masp_tx_id: shielded_section_hash, + // TODO: change this to the actual sechash + // of the fmd flag + flag_ciphertext_sechash: Hash::zero(), + }); signing_data.shielded_hash = Some(shielded_section_hash); tracing::debug!("Transfer data {data:?}"); Ok(()) diff --git a/crates/shielded_token/src/lib.rs b/crates/shielded_token/src/lib.rs index cf9f837961e..45850916508 100644 --- a/crates/shielded_token/src/lib.rs +++ b/crates/shielded_token/src/lib.rs @@ -31,7 +31,9 @@ use std::str::FromStr; use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; pub use namada_core::dec::Dec; -pub use namada_core::masp::{MaspEpoch, MaspTransaction, MaspTxId, MaspValue}; +pub use namada_core::masp::{ + MaspEpoch, MaspTransaction, MaspTxData, MaspTxId, MaspValue, +}; pub use namada_state::{ ConversionLeaf, ConversionState, Error, Key, OptionExt, Result, ResultExt, StorageRead, StorageWrite, WithConversionState, diff --git a/crates/token/src/lib.rs b/crates/token/src/lib.rs index 361a1424ff6..24a36d5d27d 100644 --- a/crates/token/src/lib.rs +++ b/crates/token/src/lib.rs @@ -182,8 +182,8 @@ pub struct Transfer { pub sources: BTreeMap, /// Targets of this transfer pub targets: BTreeMap, - /// Hash of tx section that contains the MASP transaction - pub shielded_section_hash: Option, + /// Pointers to MASP data within a transfer tx + pub shielded_data: Option, } /// References to the transparent sections of a [`Transfer`]. @@ -197,13 +197,19 @@ pub struct TransparentTransfersRef<'a> { impl Transfer { /// Create a MASP transaction - pub fn masp(hash: MaspTxId) -> Self { + pub fn masp(shielded_data: MaspTxData) -> Self { Self { - shielded_section_hash: Some(hash), + shielded_data: Some(shielded_data), ..Self::default() } } + /// Return the (optional) MASP tx id associated with this [`Transfer`]. + pub fn masp_tx_id(&self) -> Option { + self.shielded_data + .map(|shielded_data| shielded_data.masp_tx_id) + } + /// Set the key to the given amount fn set( map: &mut BTreeMap, @@ -351,7 +357,10 @@ pub mod testing { use namada_core::address::testing::arb_non_internal_address; use namada_core::address::{Address, MASP}; use namada_core::collections::HashMap; - use namada_core::masp::{AssetData, TAddrData, encode_asset_type}; + use namada_core::hash::Hash; + use namada_core::masp::{ + AssetData, MaspTxData, TAddrData, encode_asset_type, + }; pub use namada_core::token::*; use namada_shielded_token::masp::testing::{ MockTxProver, TestCsprng, arb_masp_epoch, arb_output_descriptions, @@ -522,7 +531,10 @@ pub mod testing { &mut rng, &mut rng_build_params, ).unwrap(); - transfer.shielded_section_hash = Some(masp_tx.txid().into()); + transfer.shielded_data = Some(MaspTxData { + masp_tx_id: masp_tx.txid().into(), + flag_ciphertext_sechash: Hash::zero(), + }); (transfer, ShieldedTransfer { builder: builder.map_builder(WalletMap), metadata, diff --git a/crates/token/src/tx.rs b/crates/token/src/tx.rs index d60eeb1a247..0cc2488a63d 100644 --- a/crates/token/src/tx.rs +++ b/crates/token/src/tx.rs @@ -42,10 +42,10 @@ where }; // Apply the shielded transfer if there is a link to one - if let Some(masp_section_ref) = transfers.shielded_section_hash { + if let Some(shielded_data) = transfers.shielded_data { apply_shielded_transfer( env, - masp_section_ref, + shielded_data.masp_tx_id, debited_accounts, tokens, tx_data, diff --git a/wasm/tx_ibc/src/lib.rs b/wasm/tx_ibc/src/lib.rs index 01ca4f9733c..ee2bcb60c28 100644 --- a/wasm/tx_ibc/src/lib.rs +++ b/wasm/tx_ibc/src/lib.rs @@ -22,7 +22,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { Default::default() }; - (transfers.shielded_section_hash, tokens) + (transfers.masp_tx_id(), tokens) } else { (None, Default::default()) }; From bf818efdf9f19ac5044624fa2c61790e291b0457 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Apr 2025 10:40:23 +0100 Subject: [PATCH 02/42] Add flag ciphertexts domain type --- crates/core/src/masp.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 8f992e9b3c1..67188b98751 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -124,6 +124,26 @@ impl Display for MaspTxData { } } +/// FMD flag ciphertexts. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive( + Serialize, + Deserialize, + Clone, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, +)] +pub struct FlagCiphertext { + inner: Vec, +} + /// Wrapper type around `Epoch` for type safe operations involving the masp /// epoch #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] From 1398d696e375691908c0d4713d48272e34b8445a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Apr 2025 10:07:23 +0100 Subject: [PATCH 03/42] Allow large client ctx enum variant --- crates/apps_lib/src/cli.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index b918a1b34e3..d6d2c7a607e 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -500,6 +500,7 @@ pub mod cmds { } #[derive(Clone, Debug)] + #[allow(clippy::large_enum_variant)] pub enum NamadaClientWithContext { // Ledger cmds TxCustom(TxCustom), From f2a0b7db9d3690ff6e2338f7b6133edd23b4f45c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Apr 2025 10:09:16 +0100 Subject: [PATCH 04/42] Extend IBC shielding data with flag ciphertext --- crates/apps_lib/src/client/tx.rs | 6 +++++- crates/core/src/masp.rs | 2 ++ crates/ibc/src/msg.rs | 24 +++++++++++++++++++----- crates/sdk/src/args.rs | 6 +++++- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/crates/apps_lib/src/client/tx.rs b/crates/apps_lib/src/client/tx.rs index 6689cd51191..3f420cc0769 100644 --- a/crates/apps_lib/src/client/tx.rs +++ b/crates/apps_lib/src/client/tx.rs @@ -1945,7 +1945,11 @@ pub async fn gen_ibc_shielding_transfer( }; let mut out = File::create(&output_path) .expect("Creating a new file for IBC MASP transaction failed."); - let bytes = convert_masp_tx_to_ibc_memo(&masp_tx); + let bytes = convert_masp_tx_to_ibc_memo( + masp_tx, + // TODO: add actual flag ciphertext + Default::default(), + ); out.write_all(bytes.as_bytes()) .expect("Writing IBC MASP transaction file failed."); println!( diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 67188b98751..42450d46b47 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -140,6 +140,8 @@ impl Display for MaspTxData { PartialOrd, Hash, )] +// TODO: remove Default derive +#[derive(Default)] pub struct FlagCiphertext { inner: Vec, } diff --git a/crates/ibc/src/msg.rs b/crates/ibc/src/msg.rs index 00b7610461f..b93fd17d373 100644 --- a/crates/ibc/src/msg.rs +++ b/crates/ibc/src/msg.rs @@ -20,6 +20,7 @@ use ibc::core::host::types::identifiers::PortId; use ibc::primitives::proto::Protobuf; use masp_primitives::transaction::Transaction as MaspTransaction; use namada_core::borsh::BorshSerializeExt; +use namada_core::masp::FlagCiphertext; use namada_core::string_encoding::StringEncoded; use serde::{Deserialize, Serialize}; @@ -238,8 +239,12 @@ impl BorshSchema for MsgNftTransfer { /// Shielding data in IBC packet memo #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] -// TODO: add flag ciphertext here -pub struct IbcShieldingData(pub MaspTransaction); +pub struct IbcShieldingData { + /// MASP transaction forwarded over IBC. + pub masp_tx: MaspTransaction, + /// Flag ciphertext to signal the owner of the new note(s). + pub flag_ciphertext: FlagCiphertext, +} impl From<&IbcShieldingData> for String { fn from(data: &IbcShieldingData) -> Self { @@ -302,7 +307,9 @@ pub fn decode_ibc_shielding_data( /// Extract MASP transaction from IBC packet memo pub fn extract_masp_tx_from_packet(packet: &Packet) -> Option { let memo = extract_memo_from_packet(packet, &packet.port_id_on_b)?; - decode_ibc_shielding_data(memo).map(|data| data.0) + + decode_ibc_shielding_data(memo) + .map(|IbcShieldingData { masp_tx, .. }| masp_tx) } fn extract_memo_from_packet( @@ -367,6 +374,13 @@ pub fn extract_traces_from_recv_msg( } /// Get IBC memo string from MASP transaction for receiving -pub fn convert_masp_tx_to_ibc_memo(transaction: &MaspTransaction) -> String { - IbcShieldingData(transaction.clone()).into() +pub fn convert_masp_tx_to_ibc_memo( + masp_tx: MaspTransaction, + flag_ciphertext: FlagCiphertext, +) -> String { + IbcShieldingData { + masp_tx, + flag_ciphertext, + } + .into() } diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index b9ba33fc322..5d228b7c6fe 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -752,7 +752,11 @@ impl TxOsmosisSwap { serde_json::to_value(&NamadaMemo { namada: NamadaMemoData::OsmosisSwap { shielding_data: StringEncoded::new( - IbcShieldingData(shielding_tx), + IbcShieldingData { + masp_tx: shielding_tx, + // TODO: add actual flag ciphertext here + flag_ciphertext: Default::default(), + }, ), shielded_amount: amount_to_shield, overflow_receiver, From 1e9c6c2776c40133888bb47fe77bff537d5d575c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Apr 2025 09:30:01 +0100 Subject: [PATCH 05/42] Import FMD dependency to workspace --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ddfb6bf3a3a..b82993b90b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -204,6 +204,7 @@ owo-colors = "4.1" parity-wasm = { version = "0.45", features = ["sign_ext"] } paste = "1.0" patricia_tree = "0.8" +polyfuzzy = "0.5.0" pretty_assertions = "1.4" primitive-types = "0.13" proc-macro2 = "1.0" From 402c423c4d43e1a7aaedbf5aea9153f568e2dadf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Apr 2025 09:33:57 +0100 Subject: [PATCH 06/42] Move FMD code to new module --- crates/core/src/masp.rs | 25 +++---------------------- crates/core/src/masp/fmd.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 crates/core/src/masp/fmd.rs diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 42450d46b47..4fe585d8e0e 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -1,5 +1,7 @@ //! MASP types +mod fmd; + use std::collections::BTreeMap; use std::fmt::Display; use std::num::ParseIntError; @@ -20,6 +22,7 @@ use ripemd::Digest as RipemdDigest; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use sha2::Sha256; +pub use self::fmd::FlagCiphertext; use crate::address::{Address, DecodeError, HASH_HEX_LEN, IBC, MASP}; use crate::borsh::BorshSerializeExt; use crate::chain::Epoch; @@ -124,28 +127,6 @@ impl Display for MaspTxData { } } -/// FMD flag ciphertexts. -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[derive( - Serialize, - Deserialize, - Clone, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, -)] -// TODO: remove Default derive -#[derive(Default)] -pub struct FlagCiphertext { - inner: Vec, -} - /// Wrapper type around `Epoch` for type safe operations involving the masp /// epoch #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs new file mode 100644 index 00000000000..53f56eb0684 --- /dev/null +++ b/crates/core/src/masp/fmd.rs @@ -0,0 +1,26 @@ +//! Fuzzy message detection MASP primitives. + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +/// FMD flag ciphertexts. +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive( + Serialize, + Deserialize, + Clone, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, +)] +// TODO: remove Default derive +#[derive(Default)] +pub struct FlagCiphertext { + inner: Vec, +} From 3420539c8c52700646706f7860a42452c36acc2a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Apr 2025 11:34:40 +0100 Subject: [PATCH 07/42] Import new deps --- Cargo.lock | 17 +++++++++++ Cargo.toml | 3 +- crates/core/Cargo.toml | 2 ++ wasm/Cargo.lock | 55 +++++++++++++++++++++++++++++++++-- wasm_for_tests/Cargo.lock | 61 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 133 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4539542bdb7..794ff3d62bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1740,7 +1740,9 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", + "rand_core", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -5624,6 +5626,7 @@ dependencies = [ "arbitrary", "assert_matches", "bech32 0.11.0", + "bincode", "borsh", "chrono", "data-encoding", @@ -5647,6 +5650,7 @@ dependencies = [ "num-traits", "num256", "num_enum", + "polyfuzzy", "primitive-types 0.13.1", "proptest", "prost-types 0.13.5", @@ -7301,6 +7305,19 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyfuzzy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ad377e0383a3332e1deb427b97c8670a954efa00add87d2071daab421dae24" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "sha2 0.10.8", + "subtle", +] + [[package]] name = "portable-atomic" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index b82993b90b9..8e7451febec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,13 +117,14 @@ base58 = "0.2" base64 = "0.22" bech32 = "0.11" bimap = {version = "0.6", features = ["serde"]} +bincode = "1.3.3" bit-set = "0.8" bitflags = { version = "2.5", features = ["serde"] } blake2b-rs = "0.2" +borsh = {version = "1.2", features = ["unstable__schema", "derive"]} byte-unit = "5.1" byteorder = "1.4" bytes = "1.1" -borsh = {version = "1.2", features = ["unstable__schema", "derive"]} cargo_metadata = "0.19" chrono = {version = "0.4", default-features = false, features = ["clock", "std"]} circular-queue = "0.2" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 9d3ce842959..de9312025da 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -39,6 +39,7 @@ namada_migrations = { workspace = true, optional = true } arbitrary = { workspace = true, optional = true } arse-merkle-tree.workspace = true bech32.workspace = true +bincode.workspace = true borsh.workspace = true chrono.workspace = true data-encoding.workspace = true @@ -60,6 +61,7 @@ num_enum.workspace = true num-integer.workspace = true num-rational.workspace = true num-traits.workspace = true +polyfuzzy = { workspace = true, features = ["serde"] } primitive-types.workspace = true proptest = { workspace = true, optional = true } prost-types.workspace = true diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 9d082bffa66..426bffd929a 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -486,6 +486,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.12.0" @@ -1290,6 +1299,33 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rand_core", + "rustc_version", + "serde", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -4409,6 +4445,7 @@ name = "namada_core" version = "0.149.1" dependencies = [ "bech32 0.11.0", + "bincode", "borsh", "chrono", "data-encoding", @@ -4430,6 +4467,7 @@ dependencies = [ "num-traits", "num256", "num_enum", + "polyfuzzy", "primitive-types 0.13.1", "proptest", "prost-types", @@ -5661,6 +5699,19 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyfuzzy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ad377e0383a3332e1deb427b97c8670a954efa00add87d2071daab421dae24" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "sha2 0.10.8", + "subtle", +] + [[package]] name = "portable-atomic" version = "1.10.0" @@ -5816,7 +5867,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck", - "itertools 0.11.0", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -5836,7 +5887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.98", diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index d03c51deaf6..c07ae3be193 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -297,6 +297,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.12.0" @@ -721,6 +730,33 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rand_core", + "rustc_version", + "serde", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -1103,6 +1139,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2486,6 +2528,7 @@ name = "namada_core" version = "0.149.1" dependencies = [ "bech32", + "bincode", "borsh", "chrono", "data-encoding", @@ -2506,6 +2549,7 @@ dependencies = [ "num-traits", "num256", "num_enum", + "polyfuzzy", "primitive-types 0.13.1", "prost-types", "rayon", @@ -3177,6 +3221,19 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyfuzzy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ad377e0383a3332e1deb427b97c8670a954efa00add87d2071daab421dae24" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "sha2 0.10.8", + "subtle", +] + [[package]] name = "postcard" version = "1.1.1" @@ -3275,7 +3332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck", - "itertools 0.13.0", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -3295,7 +3352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.98", From 0ca4b486bdc81363dc7ff3dcc9e608607223f51c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Apr 2025 11:36:06 +0100 Subject: [PATCH 08/42] Wrap polyfuzzy's flag ciphertext type --- crates/core/src/masp/fmd.rs | 123 ++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 18 deletions(-) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 53f56eb0684..173d17a31e4 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -1,26 +1,113 @@ //! Fuzzy message detection MASP primitives. +use std::collections::BTreeMap; +use std::io; + +use borsh::schema::Definition; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use polyfuzzy::fmd2_compact::FlagCiphertexts as PolyfuzzyFlagCiphertext; use serde::{Deserialize, Serialize}; +pub mod parameters { + //! Fuzzy message detection parameters used by Namada. + + /// Gamma parameter. + pub const GAMMA: usize = 20; + + /// Threshold parameter. + pub const THRESHOLD: usize = 1; +} + /// FMD flag ciphertexts. -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[derive( - Serialize, - Deserialize, - Clone, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, -)] -// TODO: remove Default derive -#[derive(Default)] +//#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct FlagCiphertext { - inner: Vec, + inner: PolyfuzzyFlagCiphertext, +} + +impl AsRef for FlagCiphertext { + fn as_ref(&self) -> &PolyfuzzyFlagCiphertext { + &self.inner + } +} + +// TODO: use polyfuzzy PartialEq impl once available, +// and simply derive it in FlagCiphertext +impl PartialEq for FlagCiphertext { + fn eq(&self, other: &Self) -> bool { + let this = bincode::serialize(&self.inner).unwrap(); + let other = bincode::serialize(&other.inner).unwrap(); + + this == other + } +} + +#[cfg(feature = "rand")] +impl Default for FlagCiphertext { + // TODO: improve this default impl + fn default() -> Self { + use polyfuzzy::fmd2_compact::MultiFmd2CompactScheme; + use polyfuzzy::{FmdKeyGen, MultiFmdScheme}; + use rand_core::OsRng; + + let mut scheme = MultiFmd2CompactScheme::new( + parameters::GAMMA, + parameters::THRESHOLD, + ); + let (_sk, pk) = scheme.generate_keys(&mut OsRng); + + Self { + inner: scheme.flag(&pk, &mut OsRng), + } + } +} + +impl BorshSerialize for FlagCiphertext { + #[inline] + fn serialize(&self, writer: &mut W) -> io::Result<()> { + // NOTE: serialize the size. borsh will only see an + // opaque vector of bytes + let size: u32 = bincode::serialized_size(&self.inner) + .map_err(from_bincode_err)? + .try_into() + .map_err(io::Error::other)?; + writer.write_all(&size.to_le_bytes())?; + + bincode::serialize_into(writer, &self.inner).map_err(from_bincode_err) + } +} + +impl BorshDeserialize for FlagCiphertext { + #[inline] + fn deserialize_reader(reader: &mut R) -> io::Result { + // NOTE: skip the length of the fake vector of bytes + reader.read_exact(&mut [0u8; 4])?; + + bincode::deserialize_from(reader).map_err(from_bincode_err) + } +} + +impl BorshSchema for FlagCiphertext { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let def = { + >::add_definitions_recursively(definitions); + definitions.get(&>::declaration()).unwrap().clone() + }; + + definitions.insert(Self::declaration(), def); + } + + fn declaration() -> String { + std::any::type_name::().into() + } +} + +#[allow(clippy::boxed_local)] +fn from_bincode_err(err: bincode::Error) -> io::Error { + match *err { + bincode::ErrorKind::Io(err) => err, + other => io::Error::other(other), + } } From 0ced13aa70ae5797ad85fa69476df2f1f516d55a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Apr 2025 12:03:00 +0100 Subject: [PATCH 09/42] Improve parameter docs --- crates/core/src/masp/fmd.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 173d17a31e4..04a6b736671 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -8,13 +8,22 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use polyfuzzy::fmd2_compact::FlagCiphertexts as PolyfuzzyFlagCiphertext; use serde::{Deserialize, Serialize}; +#[allow(dead_code)] pub mod parameters { //! Fuzzy message detection parameters used by Namada. /// Gamma parameter. + /// + /// This parameter defines the minimum false positive rate, + /// which is given by `2^-GAMMA`. pub const GAMMA: usize = 20; /// Threshold parameter. + /// + /// This parameter affects the length of payment addresses. + /// The raw data of payment addresses will contain `THRESHOLD + 1` + /// extra compressed curve points (32 bytes each), to allow + /// flagging note ownership to their respective owner. pub const THRESHOLD: usize = 1; } From 8d25a749f9387736e1f15d53acc6cb3b7f680b26 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Apr 2025 13:04:43 +0100 Subject: [PATCH 10/42] Improve default implementation of flag ciphertext --- crates/core/Cargo.toml | 5 +++-- crates/core/src/masp/fmd.rs | 18 +++++------------- crates/sdk/Cargo.toml | 4 ++-- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index de9312025da..2669402cfe2 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -16,13 +16,14 @@ rust-version.workspace = true [features] default = [] mainnet = [] -rand = ["dep:rand", "rand_core"] +rand = ["dep:rand", "dep:rand_core"] +default-flag-ciphertext = ["dep:rand_core", "polyfuzzy/random-flag-ciphertexts"] ethers-derive = ["ethbridge-structs/ethers-derive"] # for tests and test utilities testing = ["rand", "proptest"] migrations = ["namada_migrations", "linkme"] benches = ["proptest"] -control_flow = ["lazy_static", "tokio", "wasmtimer"] +control_flow = ["dep:lazy_static", "tokio", "wasmtimer"] arbitrary = [ "dep:arbitrary", "chrono/arbitrary", diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 04a6b736671..538d74cdbc1 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -51,22 +51,14 @@ impl PartialEq for FlagCiphertext { } } -#[cfg(feature = "rand")] +#[cfg(feature = "default-flag-ciphertext")] impl Default for FlagCiphertext { - // TODO: improve this default impl fn default() -> Self { - use polyfuzzy::fmd2_compact::MultiFmd2CompactScheme; - use polyfuzzy::{FmdKeyGen, MultiFmdScheme}; - use rand_core::OsRng; - - let mut scheme = MultiFmd2CompactScheme::new( - parameters::GAMMA, - parameters::THRESHOLD, - ); - let (_sk, pk) = scheme.generate_keys(&mut OsRng); - Self { - inner: scheme.flag(&pk, &mut OsRng), + inner: PolyfuzzyFlagCiphertext::random( + &mut rand_core::OsRng, + parameters::GAMMA, + ), } } } diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 65c39f6d64a..24d82c759d2 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -72,7 +72,7 @@ migrations = [ [dependencies] namada_account.workspace = true -namada_core.workspace = true +namada_core = { workspace = true, features = ["default-flag-ciphertext"] } namada_ethereum_bridge.workspace = true namada_events.workspace = true namada_gas.workspace = true @@ -150,7 +150,7 @@ getrandom = { workspace = true, features = ["wasm_js"] } [dev-dependencies] namada_account = { path = "../account", features = ["testing"] } -namada_core = { path = "../core", features = ["rand", "testing"] } +namada_core = { path = "../core", features = ["default-flag-ciphertext", "rand", "testing"] } namada_ethereum_bridge = { path = "../ethereum_bridge", features = ["testing"] } namada_governance = { path = "../governance", features = ["testing"] } namada_ibc = { path = "../ibc", features = ["testing"] } From 2fa1459228f5abe312b98a98383bf0d73b97f8b8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Apr 2025 13:52:22 +0100 Subject: [PATCH 11/42] Validate compressed bit ciphertexts --- crates/core/src/masp/fmd.rs | 78 +++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 538d74cdbc1..e3efb389eab 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -25,6 +25,25 @@ pub mod parameters { /// extra compressed curve points (32 bytes each), to allow /// flagging note ownership to their respective owner. pub const THRESHOLD: usize = 1; + + /// Evaluate whether the given compressed bit ciphertext is valid. + #[allow(clippy::arithmetic_side_effects)] + pub const fn valid_compressed_bit_ciphertext(bits: &[u8]) -> bool { + // Number of bytes required to represent a polyfuzzy bit ciphertext + // with a `GAMMA` parameter. + const COMPRESSED_BIT_CIPHERTEXT_LEN: usize = + GAMMA / 8 + (GAMMA % 8 != 0) as usize; + + // Mask with the padding bits that should be set to 0 (or, + // in other words, unset) in the bit ciphertext produced + // by polyfuzzy. Since the library doesn't set any of the + // upper bits, if they have been set it means someone has + // tampered with the flag ciphertext. + const UNSET_BITS_MASK: u8 = 0xff << (GAMMA % 8); + + bits.len() == COMPRESSED_BIT_CIPHERTEXT_LEN + && bits[COMPRESSED_BIT_CIPHERTEXT_LEN - 1] & UNSET_BITS_MASK == 0 + } } /// FMD flag ciphertexts. @@ -112,3 +131,62 @@ fn from_bincode_err(err: bincode::Error) -> io::Error { other => io::Error::other(other), } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_flag_ciphertext_bits_validation() { + let mut bits: Vec = { + let mut bits = [false; parameters::GAMMA]; + + // set some random bits + bits[0] = true; + bits[5] = true; + bits[10] = true; + bits[parameters::GAMMA - 1] = true; + bits[parameters::GAMMA - 2] = true; + bits[parameters::GAMMA - 3] = true; + + // compress the bits + bits.chunks(8) + .map(|bits| { + bits.iter().copied().enumerate().fold( + 0u8, + |accum_byte, (i, bit)| { + #[allow(clippy::cast_lossless)] + { + accum_byte ^ ((bit as u8) << i) + } + }, + ) + }) + .collect() + }; + + // check validation of a proper flag ciphertext + assert!(parameters::valid_compressed_bit_ciphertext(&bits)); + + let all_bits_unset = (0..8 - (parameters::GAMMA % 8)) + .map(|i| bits[bits.len() - 1] & (0b1000_0000_u8 >> i)) + .all(|bit| bit == 0); + + assert!(all_bits_unset, "Invalid bit ciphertext"); + + if parameters::GAMMA % 8 != 0 { + let n = bits.len(); + bits[n - 1] |= 0b1000_0000_u8; + + // check validation of a flag ciphertext with padding bits + // that has been tampered with + assert!(!parameters::valid_compressed_bit_ciphertext(&bits)); + + let some_bit_unset = (0..8 - (parameters::GAMMA % 8)) + .map(|i| bits[bits.len() - 1] & (0b1000_0000_u8 >> i)) + .any(|bit| bit != 0); + + assert!(some_bit_unset, "Valid bit ciphertext"); + } + } +} From 1ad37fd42a1b687b0bcf92aaed373e9afdb2c5e6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Apr 2025 14:23:52 +0100 Subject: [PATCH 12/42] Export flag ciphertext from sdk --- crates/sdk/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 06c2dce010a..3894644c8c7 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -52,8 +52,8 @@ use namada_core::ethereum_events::EthAddress; use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::key::*; pub use namada_core::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, TransferSource, - TransferTarget, + ExtendedSpendingKey, ExtendedViewingKey, FlagCiphertext, PaymentAddress, + TransferSource, TransferTarget, }; pub use namada_core::{control_flow, task_env}; use namada_io::{Client, Io, NamadaIo}; From 34f2a3c31220a5719f310a3a3605880741f7a8a0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Apr 2025 14:24:22 +0100 Subject: [PATCH 13/42] Create tx data section from borsh encodable data --- crates/tx/src/section.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/tx/src/section.rs b/crates/tx/src/section.rs index d288de3e058..d4aeef17597 100644 --- a/crates/tx/src/section.rs +++ b/crates/tx/src/section.rs @@ -275,6 +275,12 @@ impl Data { } } + /// Make a new data section with the given borsh encodable data + #[inline] + pub fn from_borsh_encoded(data: &T) -> Self { + Self::new(data.serialize_to_vec()) + } + /// Hash this data section pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { hasher.update(self.serialize_to_vec()); From 93e883821a73a9f72fdf654d866245a1ce5c241b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Apr 2025 14:25:07 +0100 Subject: [PATCH 14/42] Extended bench txs with fmd flag section --- crates/node/src/bench_utils.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index a4478290509..b187c4f92f1 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -115,8 +115,8 @@ pub use namada_sdk::tx::{ }; use namada_sdk::wallet::{DatedSpendingKey, Wallet}; use namada_sdk::{ - Namada, NamadaImpl, PaymentAddress, TransferSource, TransferTarget, - parameters, proof_of_stake, tendermint, + FlagCiphertext, Namada, NamadaImpl, PaymentAddress, TransferSource, + TransferTarget, parameters, proof_of_stake, tendermint, }; use namada_test_utils::tx_data::TxWriteData; use namada_vm::wasm::run; @@ -188,7 +188,7 @@ impl BenchShellInner { if let Some(sections) = extra_sections { for section in sections { - if let Section::ExtraData(_) = section { + if let Section::ExtraData(_) | Section::Data(_) = section { tx.add_section(section); } } @@ -1281,12 +1281,14 @@ impl BenchShieldedCtx { ) .expect("MASP must have shielded part"); + let fmd_section = Section::Data(Data::from_borsh_encoded(&vec![ + FlagCiphertext::default(), + ])); let shielded_data = MaspTxData { masp_tx_id: shielded.txid().into(), - // TODO: change this to the actual sechash - // of the fmd flag - flag_ciphertext_sechash: Default::default(), + flag_ciphertext_sechash: fmd_section.get_hash(), }; + let tx = if source.effective_address() == MASP && target.effective_address() == MASP { @@ -1294,7 +1296,7 @@ impl BenchShieldedCtx { TX_TRANSFER_WASM, Transfer::masp(shielded_data), Some(shielded), - None, + Some(vec![fmd_section]), vec![&defaults::albert_keypair()], ) } else if target.effective_address() == MASP { @@ -1309,7 +1311,7 @@ impl BenchShieldedCtx { ) .unwrap(), Some(shielded), - None, + Some(vec![fmd_section]), vec![&defaults::albert_keypair()], ) } else { @@ -1324,7 +1326,7 @@ impl BenchShieldedCtx { ) .unwrap(), Some(shielded), - None, + Some(vec![fmd_section]), vec![&defaults::albert_keypair()], ) }; @@ -1398,14 +1400,15 @@ impl BenchShieldedCtx { vec![vectorized_transfer.targets.into_iter().next().unwrap()] .into_iter() .collect(); + let fmd_section = Section::Data(Data::from_borsh_encoded(&vec![ + FlagCiphertext::default(), + ])); let transfer = Transfer { sources, targets, shielded_data: Some(MaspTxData { masp_tx_id, - // TODO: change this to the actual sechash - // of the fmd flag - flag_ciphertext_sechash: Default::default(), + flag_ciphertext_sechash: fmd_section.get_hash(), }), }; let masp_tx = tx.tx.get_masp_section(&masp_tx_id).unwrap().clone(); @@ -1419,6 +1422,7 @@ impl BenchShieldedCtx { .read() .generate_ibc_tx(TX_IBC_WASM, msg.serialize_to_vec()); ibc_tx.tx.add_masp_tx_section(masp_tx); + ibc_tx.tx.add_section(fmd_section); (ctx, ibc_tx) } From 6f488d99171b877149736eda58f8946ada06b8c4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 12:53:03 +0100 Subject: [PATCH 15/42] Switch to transparent repr in flag ciphertext --- crates/core/src/masp/fmd.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index e3efb389eab..b29c8bed3a4 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -49,6 +49,7 @@ pub mod parameters { /// FMD flag ciphertexts. //#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Serialize, Deserialize, Clone, Debug)] +#[repr(transparent)] pub struct FlagCiphertext { inner: PolyfuzzyFlagCiphertext, } From efcb4a9fd29266582118f94bf72f2de4e528a58d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 12:54:30 +0100 Subject: [PATCH 16/42] Improve flag ciphertext PartialEq impl --- crates/core/src/masp/fmd.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index b29c8bed3a4..280880373a4 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -48,7 +48,7 @@ pub mod parameters { /// FMD flag ciphertexts. //#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct FlagCiphertext { inner: PolyfuzzyFlagCiphertext, @@ -60,17 +60,6 @@ impl AsRef for FlagCiphertext { } } -// TODO: use polyfuzzy PartialEq impl once available, -// and simply derive it in FlagCiphertext -impl PartialEq for FlagCiphertext { - fn eq(&self, other: &Self) -> bool { - let this = bincode::serialize(&self.inner).unwrap(); - let other = bincode::serialize(&other.inner).unwrap(); - - this == other - } -} - #[cfg(feature = "default-flag-ciphertext")] impl Default for FlagCiphertext { fn default() -> Self { From 3dc53d6287bb3cd94e07c69268df51a28cef6c43 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 13:06:48 +0100 Subject: [PATCH 17/42] Test if flag ciphertext is valid --- crates/core/src/masp/fmd.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 280880373a4..2531254a1d7 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -54,6 +54,15 @@ pub struct FlagCiphertext { inner: PolyfuzzyFlagCiphertext, } +impl FlagCiphertext { + /// Check if the flag ciphertext is valid, according to Namada's consensus + /// rules. + #[inline] + pub fn is_valid(&self) -> bool { + parameters::valid_compressed_bit_ciphertext(self.inner.bits()) + } +} + impl AsRef for FlagCiphertext { fn as_ref(&self) -> &PolyfuzzyFlagCiphertext { &self.inner @@ -126,6 +135,16 @@ fn from_bincode_err(err: bincode::Error) -> io::Error { mod tests { use super::*; + #[test] + #[cfg(feature = "default-flag-ciphertext")] + fn test_random_flag_ciphertext_is_valid() { + // run this test a couple of times + for _ in 0..5 { + let random_flag_ciphertext = FlagCiphertext::default(); + assert!(random_flag_ciphertext.is_valid()); + } + } + #[test] fn test_flag_ciphertext_bits_validation() { let mut bits: Vec = { From 4aa8067f1ad142ff7c87de4b2789b626c68f6d0a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 13:10:14 +0100 Subject: [PATCH 18/42] Generate random flag ciphertext --- crates/core/Cargo.toml | 4 ++-- crates/core/src/masp/fmd.rs | 21 +++++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 2669402cfe2..5c9611d4730 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -16,8 +16,8 @@ rust-version.workspace = true [features] default = [] mainnet = [] -rand = ["dep:rand", "dep:rand_core"] -default-flag-ciphertext = ["dep:rand_core", "polyfuzzy/random-flag-ciphertexts"] +rand = ["dep:rand", "dep:rand_core", "polyfuzzy/random-flag-ciphertexts"] +default-flag-ciphertext = ["rand"] ethers-derive = ["ethbridge-structs/ethers-derive"] # for tests and test utilities testing = ["rand", "proptest"] diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 2531254a1d7..b663147c2e4 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -6,6 +6,8 @@ use std::io; use borsh::schema::Definition; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use polyfuzzy::fmd2_compact::FlagCiphertexts as PolyfuzzyFlagCiphertext; +#[cfg(feature = "rand")] +use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; #[allow(dead_code)] @@ -61,6 +63,17 @@ impl FlagCiphertext { pub fn is_valid(&self) -> bool { parameters::valid_compressed_bit_ciphertext(self.inner.bits()) } + + /// Generate a random [`FlagCiphertext`]. + #[cfg(feature = "rand")] + pub fn random(rng: &mut R) -> Self + where + R: CryptoRng + RngCore, + { + Self { + inner: PolyfuzzyFlagCiphertext::random(rng, parameters::GAMMA), + } + } } impl AsRef for FlagCiphertext { @@ -71,13 +84,9 @@ impl AsRef for FlagCiphertext { #[cfg(feature = "default-flag-ciphertext")] impl Default for FlagCiphertext { + #[inline] fn default() -> Self { - Self { - inner: PolyfuzzyFlagCiphertext::random( - &mut rand_core::OsRng, - parameters::GAMMA, - ), - } + Self::random(&mut rand_core::OsRng) } } From 2982e9bce0002e329a8e769cc747e3669468eec0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 13:14:28 +0100 Subject: [PATCH 19/42] Impl conversion traits between polyfuzzy and namada_core --- crates/core/src/masp/fmd.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index b663147c2e4..86b2e723976 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -76,6 +76,20 @@ impl FlagCiphertext { } } +impl From for FlagCiphertext { + fn from(flag_ciphertext: PolyfuzzyFlagCiphertext) -> Self { + Self { + inner: flag_ciphertext, + } + } +} + +impl From for PolyfuzzyFlagCiphertext { + fn from(flag_ciphertext: FlagCiphertext) -> Self { + flag_ciphertext.inner + } +} + impl AsRef for FlagCiphertext { fn as_ref(&self) -> &PolyfuzzyFlagCiphertext { &self.inner From 7399e2abea4c6903d7966e28aa45344d3f8afc82 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 13:26:08 +0100 Subject: [PATCH 20/42] Refactor FMD borsh impls --- crates/core/src/masp/fmd.rs | 95 +++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 86b2e723976..048168367a8 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -104,54 +104,87 @@ impl Default for FlagCiphertext { } } -impl BorshSerialize for FlagCiphertext { - #[inline] - fn serialize(&self, writer: &mut W) -> io::Result<()> { +mod borsh_impls { + use super::*; + + macro_rules! borsh_derive_from_bincode { + ($($type:ty),+) => { + $( + impl BorshSerialize for $type { + #[inline] + fn serialize( + &self, + writer: &mut W, + ) -> io::Result<()> { + bincode_as_borsh_serialize(writer, self) + } + } + + impl BorshDeserialize for $type { + #[inline] + fn deserialize_reader( + reader: &mut R, + ) -> io::Result { + bincode_as_borsh_deserialize(reader) + } + } + + impl BorshSchema for $type { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let def = { + >::add_definitions_recursively(definitions); + definitions.get(&>::declaration()).unwrap().clone() + }; + + definitions.insert(Self::declaration(), def); + } + + fn declaration() -> String { + std::any::type_name::().into() + } + } + )+ + }; + } + + fn bincode_as_borsh_serialize( + writer: &mut W, + data: &T, + ) -> io::Result<()> { // NOTE: serialize the size. borsh will only see an // opaque vector of bytes - let size: u32 = bincode::serialized_size(&self.inner) + let size: u32 = bincode::serialized_size(data) .map_err(from_bincode_err)? .try_into() .map_err(io::Error::other)?; writer.write_all(&size.to_le_bytes())?; - bincode::serialize_into(writer, &self.inner).map_err(from_bincode_err) + bincode::serialize_into(writer, data).map_err(from_bincode_err) } -} -impl BorshDeserialize for FlagCiphertext { - #[inline] - fn deserialize_reader(reader: &mut R) -> io::Result { + fn bincode_as_borsh_deserialize< + R: io::Read, + T: for<'de> Deserialize<'de>, + >( + reader: &mut R, + ) -> io::Result { // NOTE: skip the length of the fake vector of bytes reader.read_exact(&mut [0u8; 4])?; bincode::deserialize_from(reader).map_err(from_bincode_err) } -} - -impl BorshSchema for FlagCiphertext { - fn add_definitions_recursively( - definitions: &mut BTreeMap, - ) { - let def = { - >::add_definitions_recursively(definitions); - definitions.get(&>::declaration()).unwrap().clone() - }; - - definitions.insert(Self::declaration(), def); - } - fn declaration() -> String { - std::any::type_name::().into() + #[allow(clippy::boxed_local)] + fn from_bincode_err(err: bincode::Error) -> io::Error { + match *err { + bincode::ErrorKind::Io(err) => err, + other => io::Error::other(other), + } } -} -#[allow(clippy::boxed_local)] -fn from_bincode_err(err: bincode::Error) -> io::Error { - match *err { - bincode::ErrorKind::Io(err) => err, - other => io::Error::other(other), - } + borsh_derive_from_bincode!(FlagCiphertext); } #[cfg(test)] From 840e2c8a53d3d126b05e05a7327944d04d7556f8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 13:30:52 +0100 Subject: [PATCH 21/42] Test fmd flag ciphertext borsh roundtrip --- crates/core/src/masp/fmd.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 048168367a8..b6edecf680f 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -191,6 +191,23 @@ mod borsh_impls { mod tests { use super::*; + #[test] + #[cfg(feature = "default-flag-ciphertext")] + fn test_flag_ciphertext_borsh_roundtrip() { + use crate::borsh::BorshSerializeExt; + + // run this test a couple of times + for _ in 0..5 { + let random_flag_ciphertext = FlagCiphertext::default(); + + let serialized = random_flag_ciphertext.serialize_to_vec(); + let deserialized = + FlagCiphertext::try_from_slice(&serialized).unwrap(); + + assert_eq!(random_flag_ciphertext, deserialized); + } + } + #[test] #[cfg(feature = "default-flag-ciphertext")] fn test_random_flag_ciphertext_is_valid() { From d77876ea3cd8893f196c4ae6cf0a2dcad6252ecf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 16:26:48 +0100 Subject: [PATCH 22/42] Derive FMD secret keys from input vks --- Cargo.toml | 2 +- crates/core/src/masp/fmd.rs | 50 ++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8e7451febec..fb71a9976ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -248,7 +248,7 @@ test-log = {version = "0.2", default-features = false, features = ["trace"]} textwrap-macros = "0.3" tiny-bip39 = {version = "2.0"} tiny-hderive = {package = "nam-tiny-hderive", version = "0.3.1-nam.0"} -tiny-keccak = { version = "2.0", features = ["keccak"] } +tiny-keccak = { version = "2.0", features = ["keccak", "k12"] } thiserror = "2.0" tokio = {version = "1.8", default-features = false} tokio-test = "0.4" diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index b6edecf680f..c91c38f46d9 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -5,10 +5,15 @@ use std::io; use borsh::schema::Definition; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use polyfuzzy::fmd2_compact::FlagCiphertexts as PolyfuzzyFlagCiphertext; +use masp_primitives::sapling::SaplingIvk; +use polyfuzzy::fmd2_compact::{ + CompactSecretKey as PolyfuzzyCompactSecretKey, + FlagCiphertexts as PolyfuzzyFlagCiphertext, +}; #[cfg(feature = "rand")] use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; +use tiny_keccak::{Hasher, IntoXof, KangarooTwelve, Xof}; #[allow(dead_code)] pub mod parameters { @@ -48,6 +53,49 @@ pub mod parameters { } } +/// FMD secret key. +//#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct SecretKey { + inner: PolyfuzzyCompactSecretKey, +} + +impl SecretKey { + /// Hash personalization string used when deriving a [`SecretKey`] + /// from a [`SaplingIvk`]. + const KDF_PERSONALIZATION: &str = "Namada FMD Secret Key"; +} + +impl From<&SaplingIvk> for SecretKey { + fn from(ivk: &SaplingIvk) -> Self { + let mut xof_stream = { + let mut hasher = KangarooTwelve::new(Self::KDF_PERSONALIZATION); + + // derive key material from input viewing key + hasher.update(&ivk.to_repr()); + + hasher.into_xof() + }; + + Self { + inner: PolyfuzzyCompactSecretKey::derive_from_xof_stream( + parameters::THRESHOLD, + |buf| { + xof_stream.squeeze(buf); + }, + ), + } + } +} + +impl From for SecretKey { + #[inline] + fn from(ivk: SaplingIvk) -> Self { + (&ivk).into() + } +} + /// FMD flag ciphertexts. //#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] From 569134b0412994ec6f107e4c47656ea6d1adf96d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 21:24:32 +0100 Subject: [PATCH 23/42] Derive FMD secret key from extended spending key --- crates/core/src/masp/fmd.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index c91c38f46d9..fe659661b17 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -6,6 +6,7 @@ use std::io; use borsh::schema::Definition; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use masp_primitives::sapling::SaplingIvk; +use masp_primitives::zip32::{ExtendedKey, PseudoExtendedKey}; use polyfuzzy::fmd2_compact::{ CompactSecretKey as PolyfuzzyCompactSecretKey, FlagCiphertexts as PolyfuzzyFlagCiphertext, @@ -96,6 +97,18 @@ impl From for SecretKey { } } +impl From<&PseudoExtendedKey> for SecretKey { + fn from(psk: &PseudoExtendedKey) -> Self { + psk.to_viewing_key().fvk.vk.ivk().into() + } +} + +impl From for SecretKey { + fn from(psk: PseudoExtendedKey) -> Self { + (&psk).into() + } +} + /// FMD flag ciphertexts. //#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] From a92d2c5415302415f3eaaf3692678eba91bd6e1f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 21:41:44 +0100 Subject: [PATCH 24/42] Move polyfuzzy imports --- crates/core/src/masp/fmd.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index fe659661b17..a055184104d 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -7,15 +7,15 @@ use borsh::schema::Definition; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use masp_primitives::sapling::SaplingIvk; use masp_primitives::zip32::{ExtendedKey, PseudoExtendedKey}; -use polyfuzzy::fmd2_compact::{ - CompactSecretKey as PolyfuzzyCompactSecretKey, - FlagCiphertexts as PolyfuzzyFlagCiphertext, -}; #[cfg(feature = "rand")] use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use tiny_keccak::{Hasher, IntoXof, KangarooTwelve, Xof}; +mod polyfuzzy { + pub(super) use ::polyfuzzy::fmd2_compact::*; +} + #[allow(dead_code)] pub mod parameters { //! Fuzzy message detection parameters used by Namada. @@ -59,7 +59,7 @@ pub mod parameters { #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct SecretKey { - inner: PolyfuzzyCompactSecretKey, + inner: polyfuzzy::CompactSecretKey, } impl SecretKey { @@ -80,7 +80,7 @@ impl From<&SaplingIvk> for SecretKey { }; Self { - inner: PolyfuzzyCompactSecretKey::derive_from_xof_stream( + inner: polyfuzzy::CompactSecretKey::derive_from_xof_stream( parameters::THRESHOLD, |buf| { xof_stream.squeeze(buf); @@ -114,7 +114,7 @@ impl From for SecretKey { #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct FlagCiphertext { - inner: PolyfuzzyFlagCiphertext, + inner: polyfuzzy::FlagCiphertexts, } impl FlagCiphertext { @@ -132,27 +132,27 @@ impl FlagCiphertext { R: CryptoRng + RngCore, { Self { - inner: PolyfuzzyFlagCiphertext::random(rng, parameters::GAMMA), + inner: polyfuzzy::FlagCiphertexts::random(rng, parameters::GAMMA), } } } -impl From for FlagCiphertext { - fn from(flag_ciphertext: PolyfuzzyFlagCiphertext) -> Self { +impl From for FlagCiphertext { + fn from(flag_ciphertext: polyfuzzy::FlagCiphertexts) -> Self { Self { inner: flag_ciphertext, } } } -impl From for PolyfuzzyFlagCiphertext { +impl From for polyfuzzy::FlagCiphertexts { fn from(flag_ciphertext: FlagCiphertext) -> Self { flag_ciphertext.inner } } -impl AsRef for FlagCiphertext { - fn as_ref(&self) -> &PolyfuzzyFlagCiphertext { +impl AsRef for FlagCiphertext { + fn as_ref(&self) -> &polyfuzzy::FlagCiphertexts { &self.inner } } From b7d339115dee79b308ecd6297e9ee0aa2b18bb0b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 21:46:43 +0100 Subject: [PATCH 25/42] Remove unnecessary bounds on secret keys --- crates/core/src/masp/fmd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index a055184104d..fb7e49eaf70 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -56,7 +56,7 @@ pub mod parameters { /// FMD secret key. //#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct SecretKey { inner: polyfuzzy::CompactSecretKey, From 397d77dd2be9c47aae61d5680483f3b6536d991f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 22:09:38 +0100 Subject: [PATCH 26/42] Add FMD public keys --- crates/core/src/masp/fmd.rs | 106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index fb7e49eaf70..4a6bc4347aa 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -13,6 +13,8 @@ use serde::{Deserialize, Serialize}; use tiny_keccak::{Hasher, IntoXof, KangarooTwelve, Xof}; mod polyfuzzy { + #[cfg(feature = "rand")] + pub(super) use ::polyfuzzy::MultiFmdScheme; pub(super) use ::polyfuzzy::fmd2_compact::*; } @@ -34,6 +36,9 @@ pub mod parameters { /// flagging note ownership to their respective owner. pub const THRESHOLD: usize = 1; + /// Number of bytes required to store a public key. + pub const PUBLIC_KEY_LEN: usize = (THRESHOLD + 1) * 32; + /// Evaluate whether the given compressed bit ciphertext is valid. #[allow(clippy::arithmetic_side_effects)] pub const fn valid_compressed_bit_ciphertext(bits: &[u8]) -> bool { @@ -54,6 +59,68 @@ pub mod parameters { } } +/// FMD public key. +//#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct PublicKey { + inner: polyfuzzy::CompactPublicKey, +} + +impl PublicKey { + /// Serialize this public key into its compressed representation. + pub fn into_compressed_bytes(self) -> Vec { + self.inner.compress().to_coeff_repr() + } + + /// Deserialize a public key from the given compressed representation and + /// diversifier. + pub fn from_parts( + diversifier: &[u8], + compressed_bytes: &[u8], + ) -> Option { + if compressed_bytes.len() != parameters::PUBLIC_KEY_LEN { + return None; + } + + let compressed_pk = + polyfuzzy::CompressedCompactPublicKey::from_coeff_repr( + compressed_bytes, + )?; + + Some(PublicKey { + inner: compressed_pk.var_decompress(diversifier), + }) + } + + /// Generate a new [`FlagCiphertext`]. + /// + /// This notifies the owner of a given viewing + /// key that a note can be decrypted by it, with + /// occasional false positives returned for + /// non-decrypting viewing keys. + #[cfg(feature = "rand")] + pub fn flag(&self, rng: &mut R) -> FlagCiphertext + where + R: CryptoRng + RngCore, + { + use polyfuzzy::MultiFmdScheme as _; + + let mut scheme = polyfuzzy::MultiFmd2CompactScheme::new( + parameters::GAMMA, + parameters::THRESHOLD, + ); + + scheme.flag(&self.inner, rng).into() + } +} + +impl AsRef for PublicKey { + fn as_ref(&self) -> &polyfuzzy::CompactPublicKey { + &self.inner + } +} + /// FMD secret key. //#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq)] @@ -66,6 +133,30 @@ impl SecretKey { /// Hash personalization string used when deriving a [`SecretKey`] /// from a [`SaplingIvk`]. const KDF_PERSONALIZATION: &str = "Namada FMD Secret Key"; + + /// Return the default public key. + /// + /// Flag ciphertexts generated using the default + /// public key are guaranteed to be unique on + /// every call to [`PublicKey::flag`]. + pub fn default_public_key(&self) -> PublicKey { + PublicKey { + inner: self.inner.master_public_key(), + } + } + + /// Return a public key randomized by the given `diversifier`. + pub fn randomized_public_key(&self, diversifier: &[u8]) -> PublicKey { + PublicKey { + inner: self.inner.var_randomized_public_key(diversifier), + } + } +} + +impl AsRef for SecretKey { + fn as_ref(&self) -> &polyfuzzy::CompactSecretKey { + &self.inner + } } impl From<&SaplingIvk> for SecretKey { @@ -252,6 +343,21 @@ mod borsh_impls { mod tests { use super::*; + #[test] + fn test_fmd_pubkey_roundtrip_repr() { + let diversifier = b"bing-bong"; + let sk = { + let ivk = SaplingIvk(Default::default()); + SecretKey::from(ivk) + }; + + let pk = sk.randomized_public_key(diversifier); + let repr = pk.clone().into_compressed_bytes(); + let pk2 = PublicKey::from_parts(diversifier, &repr).unwrap(); + + assert_eq!(pk, pk2); + } + #[test] #[cfg(feature = "default-flag-ciphertext")] fn test_flag_ciphertext_borsh_roundtrip() { From 65e06a4d19b4c97665945b95b472f5527768af10 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 30 Apr 2025 22:23:13 +0100 Subject: [PATCH 27/42] Export FMD keys --- crates/core/src/masp.rs | 4 +++- crates/core/src/masp/fmd.rs | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 4fe585d8e0e..06ecd7d912f 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -22,7 +22,9 @@ use ripemd::Digest as RipemdDigest; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use sha2::Sha256; -pub use self::fmd::FlagCiphertext; +pub use self::fmd::{ + FlagCiphertext, PublicKey as FmdPublicKey, SecretKey as FmdSecretKey, +}; use crate::address::{Address, DecodeError, HASH_HEX_LEN, IBC, MASP}; use crate::borsh::BorshSerializeExt; use crate::chain::Epoch; diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 4a6bc4347aa..f5ca3917404 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -18,8 +18,7 @@ mod polyfuzzy { pub(super) use ::polyfuzzy::fmd2_compact::*; } -#[allow(dead_code)] -pub mod parameters { +mod parameters { //! Fuzzy message detection parameters used by Namada. /// Gamma parameter. From 724ec42059cc17d30794673c2b94b6fbdb5f31b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 May 2025 13:48:35 +0100 Subject: [PATCH 28/42] Define HRP of fmd payment addrs --- crates/core/src/string_encoding.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/core/src/string_encoding.rs b/crates/core/src/string_encoding.rs index 82fd5d7b0c2..fe9a3757583 100644 --- a/crates/core/src/string_encoding.rs +++ b/crates/core/src/string_encoding.rs @@ -27,6 +27,8 @@ pub const ADDRESS_HRP: &str = "tnam"; pub const MASP_EXT_FULL_VIEWING_KEY_HRP: &str = "zvknam"; /// MASP payment address human-readable part pub const MASP_PAYMENT_ADDRESS_HRP: &str = "znam"; +/// MASP payment address with FMD public key human-readable part +pub const MASP_FMD_PAYMENT_ADDRESS_HRP: &str = "zfnam"; /// MASP extended spending key human-readable part pub const MASP_EXT_SPENDING_KEY_HRP: &str = "zsknam"; /// `common::PublicKey` human-readable part From 241bb19ea1e5760c343464fa0535425439f5ad31 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 May 2025 14:00:05 +0100 Subject: [PATCH 29/42] Add domain type for fmd pubkey bytes --- crates/core/src/masp.rs | 3 ++- crates/core/src/masp/fmd.rs | 39 +++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 06ecd7d912f..f5de46709db 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -23,7 +23,8 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use sha2::Sha256; pub use self::fmd::{ - FlagCiphertext, PublicKey as FmdPublicKey, SecretKey as FmdSecretKey, + FlagCiphertext, PublicKey as FmdPublicKey, + PublicKeyBytes as FmdPublicKeyBytes, SecretKey as FmdSecretKey, }; use crate::address::{Address, DecodeError, HASH_HEX_LEN, IBC, MASP}; use crate::borsh::BorshSerializeExt; diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index f5ca3917404..c85f5ed4a8e 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::io; +use std::ops::Deref; use borsh::schema::Definition; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -58,6 +59,31 @@ mod parameters { } } +/// FMD public key bytes. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +#[repr(transparent)] +pub struct PublicKeyBytes(Box<[u8; parameters::PUBLIC_KEY_LEN]>); + +impl Deref for PublicKeyBytes { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + self.0.as_slice() + } +} + /// FMD public key. //#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq)] @@ -68,8 +94,17 @@ pub struct PublicKey { impl PublicKey { /// Serialize this public key into its compressed representation. - pub fn into_compressed_bytes(self) -> Vec { - self.inner.compress().to_coeff_repr() + pub fn into_compressed_bytes(self) -> PublicKeyBytes { + let repr = self.inner.compress().to_coeff_repr(); + + PublicKeyBytes(repr.into_boxed_slice().try_into().unwrap_or_else( + |_| { + panic!( + "FMD public key length should have been {}", + parameters::PUBLIC_KEY_LEN + ) + }, + )) } /// Deserialize a public key from the given compressed representation and From 336eaddf5a4e44a5011dc1694b07c616b97c9d96 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 May 2025 14:31:22 +0100 Subject: [PATCH 30/42] Add payment addrs with fmd pubkeys --- crates/core/src/masp.rs | 138 +++++++++++++++++++++++++++++++++++- crates/core/src/masp/fmd.rs | 24 +++++++ 2 files changed, 161 insertions(+), 1 deletion(-) diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index f5de46709db..6914d86cea5 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -33,7 +33,7 @@ use crate::hash::Hash; use crate::impl_display_and_from_str_via_format; use crate::string_encoding::{ self, MASP_EXT_FULL_VIEWING_KEY_HRP, MASP_EXT_SPENDING_KEY_HRP, - MASP_PAYMENT_ADDRESS_HRP, + MASP_FMD_PAYMENT_ADDRESS_HRP, MASP_PAYMENT_ADDRESS_HRP, }; use crate::token::{Denomination, MaspDigitPos, NATIVE_MAX_DECIMAL_PLACES}; @@ -345,6 +345,10 @@ pub type Precision = u128; // enough capacity to store the payment address const PAYMENT_ADDRESS_SIZE: usize = 43; +// enough capacity to store the payment address +const FMD_PAYMENT_ADDRESS_SIZE: usize = + PAYMENT_ADDRESS_SIZE + FmdPublicKeyBytes::LENGTH; + /// Wrapper for masp_primitive's DiversifierIndex #[derive(Clone, Debug, Copy, Eq, PartialEq, Default)] pub struct DiversifierIndex(masp_primitives::zip32::DiversifierIndex); @@ -526,6 +530,54 @@ impl string_encoding::Format for PaymentAddress { impl_display_and_from_str_via_format!(PaymentAddress); +impl string_encoding::Format for FmdPaymentAddress { + type EncodedBytes<'a> = Vec; + + const HRP: string_encoding::Hrp = + string_encoding::Hrp::parse_unchecked(MASP_FMD_PAYMENT_ADDRESS_HRP); + + fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(FMD_PAYMENT_ADDRESS_SIZE); + bytes.extend_from_slice(&self.payment_address.to_bytes()[..]); + bytes.extend_from_slice(&self.fmd_public_key[..]); + bytes + } + + fn decode_bytes( + bytes: &[u8], + ) -> Result { + if bytes.len() != FMD_PAYMENT_ADDRESS_SIZE { + return Err(DecodeError::InvalidInnerEncoding(format!( + "expected {FMD_PAYMENT_ADDRESS_SIZE} bytes for the fmd \ + payment address" + ))); + } + + let payment_address = + masp_primitives::sapling::PaymentAddress::from_bytes(&{ + let mut payment_addr = [0u8; PAYMENT_ADDRESS_SIZE]; + payment_addr.copy_from_slice(&bytes[0..PAYMENT_ADDRESS_SIZE]); + payment_addr + }) + .ok_or_else(|| { + DecodeError::InvalidInnerEncoding( + "invalid fmd payment address provided".to_string(), + ) + })?; + + let fmd_public_key = bytes[PAYMENT_ADDRESS_SIZE..] + .try_into() + .map_err(DecodeError::InvalidInnerEncoding)?; + + Ok(Self { + payment_address, + fmd_public_key, + }) + } +} + +impl_display_and_from_str_via_format!(FmdPaymentAddress); + impl From for masp_primitives::zip32::ExtendedFullViewingKey { @@ -598,6 +650,20 @@ impl PaymentAddress { // hex of the first 40 chars of the hash format!("{:.width$X}", hasher.finalize(), width = HASH_HEX_LEN) } + + /// Create a new [`FmdPaymentAddress`]. + pub fn with_fmd_key(self, sk: &FmdSecretKey) -> FmdPaymentAddress { + let Self(payment_address) = self; + + let fmd_public_key = sk + .randomized_public_key(&payment_address.diversifier().0) + .into_compressed_bytes(); + + FmdPaymentAddress { + payment_address, + fmd_public_key, + } + } } impl From for masp_primitives::sapling::PaymentAddress { @@ -636,6 +702,76 @@ impl<'de> serde::Deserialize<'de> for PaymentAddress { } } +/// [`PaymentAddress`] with FMD public key attached. +#[derive( + Clone, + Debug, + PartialOrd, + Ord, + Eq, + PartialEq, + Hash, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, +)] +pub struct FmdPaymentAddress { + /// Wrapped MASP payment address. + payment_address: masp_primitives::sapling::PaymentAddress, + /// FMD public key. + fmd_public_key: FmdPublicKeyBytes, +} + +impl FmdPaymentAddress { + /// Recover the [`PaymentAddress`] embedded within. + pub fn as_payment_address( + &self, + ) -> &masp_primitives::sapling::PaymentAddress { + &self.payment_address + } + + /// Recover the [`FmdPublicKey`] embedded within. + pub fn to_fmd_public_key(&self) -> Option { + FmdPublicKey::from_parts( + &self.payment_address.diversifier().0, + &self.fmd_public_key, + ) + } + + /// Hash this payment address + pub fn hash(&self) -> String { + let bytes = self.serialize_to_vec(); + let mut hasher = Sha256::new(); + hasher.update(bytes); + // hex of the first 40 chars of the hash + format!("{:.width$X}", hasher.finalize(), width = HASH_HEX_LEN) + } +} + +impl serde::Serialize for FmdPaymentAddress { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let encoded = self.to_string(); + serde::Serialize::serialize(&encoded, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for FmdPaymentAddress { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let encoded: String = serde::Deserialize::deserialize(deserializer)?; + Self::from_str(&encoded).map_err(D::Error::custom) + } +} + /// Wrapper for masp_primitive's ExtendedSpendingKey #[derive( Clone, diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index c85f5ed4a8e..20d9efc9a94 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -75,6 +75,30 @@ mod parameters { #[repr(transparent)] pub struct PublicKeyBytes(Box<[u8; parameters::PUBLIC_KEY_LEN]>); +impl PublicKeyBytes { + /// Length of the byte payload. + pub const LENGTH: usize = parameters::PUBLIC_KEY_LEN; +} + +impl TryFrom<&[u8]> for PublicKeyBytes { + type Error = String; + + fn try_from(slice: &[u8]) -> Result { + if slice.len() != parameters::PUBLIC_KEY_LEN { + return Err(format!( + "FMD public key length must be {}", + parameters::PUBLIC_KEY_LEN + )); + } + + let mut bytes = + PublicKeyBytes(Box::new([0u8; parameters::PUBLIC_KEY_LEN])); + bytes.0.copy_from_slice(slice); + + Ok(bytes) + } +} + impl Deref for PublicKeyBytes { type Target = [u8]; From e2c679251f4e3856df01bf58cb11b5d82cd1074e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 May 2025 14:03:35 +0100 Subject: [PATCH 31/42] Add new payment addrs to wallet --- crates/apps_lib/src/cli.rs | 9 +- crates/apps_lib/src/cli/context.rs | 20 ++- crates/apps_lib/src/cli/wallet.rs | 21 +-- crates/benches/native_vps.rs | 2 +- crates/core/src/masp.rs | 164 ++++++++++++++++-- crates/node/src/bench_utils.rs | 7 +- crates/sdk/src/args.rs | 6 +- crates/sdk/src/lib.rs | 6 +- crates/sdk/src/tx.rs | 6 +- .../src/masp/shielded_wallet.rs | 2 +- crates/wallet/src/lib.rs | 13 +- crates/wallet/src/store.rs | 21 ++- 12 files changed, 213 insertions(+), 64 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index d6d2c7a607e..de7dfdaf7a7 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -3402,7 +3402,9 @@ pub mod args { use data_encoding::HEXUPPER; use either::Either; - use namada_core::masp::{DiversifierIndex, MaspEpoch, PaymentAddress}; + use namada_core::masp::{ + DiversifierIndex, MaspEpoch, UnifiedPaymentAddress, + }; use namada_sdk::address::{Address, EstablishedAddress}; pub use namada_sdk::args::*; use namada_sdk::chain::{ChainId, ChainIdPrefix}; @@ -3653,8 +3655,9 @@ pub mod args { pub const RAW_ADDRESS_ESTABLISHED: Arg = arg("address"); pub const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); pub const RAW_KEY_GEN: ArgFlag = flag("raw"); - pub const RAW_PAYMENT_ADDRESS: Arg = arg("payment-address"); - pub const RAW_PAYMENT_ADDRESS_OPT: ArgOpt = + pub const RAW_PAYMENT_ADDRESS: Arg = + arg("payment-address"); + pub const RAW_PAYMENT_ADDRESS_OPT: ArgOpt = RAW_PAYMENT_ADDRESS.opt(); pub const RAW_PUBLIC_KEY: Arg = arg("public-key"); pub const RAW_PUBLIC_KEY_OPT: ArgOpt = diff --git a/crates/apps_lib/src/cli/context.rs b/crates/apps_lib/src/cli/context.rs index 7c4d570d5c7..0435b7241e0 100644 --- a/crates/apps_lib/src/cli/context.rs +++ b/crates/apps_lib/src/cli/context.rs @@ -12,8 +12,8 @@ use masp_primitives::zip32::{ ExtendedSpendingKey as MaspExtendedSpendingKey, }; use namada_core::masp::{ - BalanceOwner, ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, - TransferSource, TransferTarget, + BalanceOwner, ExtendedSpendingKey, ExtendedViewingKey, TransferSource, + TransferTarget, UnifiedPaymentAddress, }; use namada_sdk::address::{Address, InternalAddress}; use namada_sdk::chain::ChainId; @@ -60,7 +60,7 @@ pub type WalletDatedSpendingKey = FromContext; /// A raw payment address (bech32m encoding) or an alias of a payment address /// in the wallet -pub type WalletPaymentAddr = FromContext; +pub type WalletPaymentAddr = FromContext; /// A raw full viewing key (bech32m encoding) or an alias of a full viewing key /// in the wallet @@ -388,10 +388,11 @@ impl FromContext { } } - /// Converts this TransferTarget argument to a PaymentAddress. Call this - /// function only when certain that raw represents a PaymentAddress. - pub fn to_payment_address(&self) -> FromContext { - FromContext:: { + /// Converts this TransferTarget argument to a UnifiedPaymentAddress. Call + /// this function only when certain that raw represents a + /// UnifiedPaymentAddress. + pub fn to_payment_address(&self) -> FromContext { + FromContext:: { raw: self.raw.clone(), phantom: PhantomData, } @@ -690,7 +691,7 @@ impl ArgFromMutContext for DatedViewingKey { } } -impl ArgFromContext for PaymentAddress { +impl ArgFromContext for UnifiedPaymentAddress { fn arg_from_ctx( ctx: &ChainContext, raw: impl AsRef, @@ -743,7 +744,8 @@ impl ArgFromContext for TransferTarget { Address::arg_from_ctx(ctx, raw) .map(Self::Address) .or_else(|_| { - PaymentAddress::arg_from_ctx(ctx, raw).map(Self::PaymentAddress) + UnifiedPaymentAddress::arg_from_ctx(ctx, raw) + .map(Self::PaymentAddress) }) } } diff --git a/crates/apps_lib/src/cli/wallet.rs b/crates/apps_lib/src/cli/wallet.rs index 19f4261d3a4..07c94a22203 100644 --- a/crates/apps_lib/src/cli/wallet.rs +++ b/crates/apps_lib/src/cli/wallet.rs @@ -12,7 +12,7 @@ use ledger_transport_hid::hidapi::HidApi; use masp_primitives::zip32::ExtendedFullViewingKey; use namada_core::chain::BlockHeight; use namada_core::masp::{ - DiversifierIndex, ExtendedSpendingKey, MaspValue, PaymentAddress, + ExtendedSpendingKey, MaspValue, UnifiedPaymentAddress, }; use namada_sdk::address::{Address, DecodeError}; use namada_sdk::borsh::{BorshDeserialize, BorshSerializeExt}; @@ -421,23 +421,18 @@ fn payment_address_gen( .copied() .unwrap_or_default() }); - let (diversifier_index, masp_payment_addr) = - ExtendedFullViewingKey::from(viewing_key) - .find_address(diversifier_index.into()) - .expect("exhausted payment addresses"); + let (diversifier_index, payment_addr) = + UnifiedPaymentAddress::v1_from_zip32(viewing_key, diversifier_index); let mut next_div_idx = diversifier_index; - next_div_idx - .increment() - .expect("exhausted payment addresses"); - let payment_addr = PaymentAddress::from(masp_payment_addr); + next_div_idx.increment(); let alias = wallet - .insert_payment_addr(alias, payment_addr, alias_force) + .insert_payment_addr(alias, payment_addr.clone(), alias_force) .unwrap_or_else(|| { edisplay_line!(io, "Payment address not added"); cli::safe_exit(1); }); wallet - .insert_diversifier_index(viewing_key_alias, next_div_idx.into()) + .insert_diversifier_index(viewing_key_alias, next_div_idx) .expect( "must be able to save next diversifier index under the alias of \ the viewing key", @@ -447,7 +442,7 @@ fn payment_address_gen( io, "Successfully generated payment address {} at index {} with alias {}", payment_addr, - DiversifierIndex::from(diversifier_index), + diversifier_index, alias, ); } @@ -1095,7 +1090,7 @@ fn payment_address_or_alias_find( ctx: Context, io: &impl Io, alias: Option, - payment_address: Option, + payment_address: Option, ) { let wallet = load_wallet(ctx); if payment_address.is_some() && alias.is_some() { diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index 63ba6afebe2..e0b3a223a3e 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -572,7 +572,7 @@ fn setup_storage_for_masp_verification( let (shielded_ctx, shield_tx) = shielded_ctx.generate_masp_tx( amount, TransferSource::Address(defaults::albert_address()), - TransferTarget::PaymentAddress(albert_payment_addr), + TransferTarget::PaymentAddress(albert_payment_addr.clone()), ); shielded_ctx.shell.write().execute_tx(&shield_tx.to_ref()); diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 6914d86cea5..3c7e70852aa 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -353,6 +353,13 @@ const FMD_PAYMENT_ADDRESS_SIZE: usize = #[derive(Clone, Debug, Copy, Eq, PartialEq, Default)] pub struct DiversifierIndex(masp_primitives::zip32::DiversifierIndex); +impl DiversifierIndex { + /// Return the next payment address index. + pub fn increment(&mut self) { + self.0.increment().expect("exhausted payment addresses"); + } +} + impl From for DiversifierIndex { fn from(idx: masp_primitives::zip32::DiversifierIndex) -> Self { Self(idx) @@ -986,6 +993,131 @@ pub fn addr_taddr(addr: Address) -> TransparentAddress { TAddrData::Addr(addr).taddress() } +/// Unifies FMD and non-FMD payment address types. +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshDeserializer, + Hash, + Eq, + PartialEq, + Ord, + PartialOrd, +)] +pub enum UnifiedPaymentAddress { + /// First payment address format introduced by Namada. + /// + /// Identical to a Zcash sapling payment address. + V0(PaymentAddress), + /// Payment address similar to [`Self::V0`], augmented with + /// [`FmdPublicKeyBytes`]. + V1(FmdPaymentAddress), +} + +impl UnifiedPaymentAddress { + /// Create a v0 payment address. + pub fn v0_from_zip32( + vk: ExtendedViewingKey, + div_index: DiversifierIndex, + ) -> (DiversifierIndex, Self) { + let (div_index, pa) = + masp_primitives::zip32::ExtendedFullViewingKey::from(vk) + .find_address(div_index.into()) + .expect("exhausted payment address diversifier indices"); + + (DiversifierIndex(div_index), Self::V0(PaymentAddress(pa))) + } + + /// Create a v1 payment address. + pub fn v1_from_zip32( + vk: ExtendedViewingKey, + div_index: DiversifierIndex, + ) -> (DiversifierIndex, Self) { + let fmd_sk: FmdSecretKey = vk.0.fvk.vk.ivk().into(); + + let (div_index, pa) = + masp_primitives::zip32::ExtendedFullViewingKey::from(vk) + .find_address(div_index.into()) + .expect("exhausted payment address diversifier indices"); + + ( + DiversifierIndex(div_index), + Self::V1(PaymentAddress(pa).with_fmd_key(&fmd_sk)), + ) + } +} + +impl serde::Serialize for UnifiedPaymentAddress { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + serde::Serialize::serialize(&self.to_string(), serializer) + } +} + +impl<'de> serde::Deserialize<'de> for UnifiedPaymentAddress { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let encoded: String = serde::Deserialize::deserialize(deserializer)?; + Self::from_str(&encoded).map_err(D::Error::custom) + } +} + +impl From<&UnifiedPaymentAddress> for masp_primitives::sapling::PaymentAddress { + fn from(pa: &UnifiedPaymentAddress) -> Self { + match pa { + UnifiedPaymentAddress::V0(pa) => pa.0, + UnifiedPaymentAddress::V1(pa) => *pa.as_payment_address(), + } + } +} + +impl From for masp_primitives::sapling::PaymentAddress { + fn from(pa: UnifiedPaymentAddress) -> Self { + (&pa).into() + } +} + +impl From for UnifiedPaymentAddress { + fn from(pa: PaymentAddress) -> Self { + Self::V0(pa) + } +} + +impl From for UnifiedPaymentAddress { + fn from(pa: FmdPaymentAddress) -> Self { + Self::V1(pa) + } +} + +impl Display for UnifiedPaymentAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::V0(pa) => pa.fmt(f), + Self::V1(pa) => pa.fmt(f), + } + } +} + +impl FromStr for UnifiedPaymentAddress { + type Err = DecodeError; + + fn from_str(s: &str) -> Result { + PaymentAddress::from_str(s) + .map(Self::V0) + .or_else(|_err| FmdPaymentAddress::from_str(s).map(Self::V1)) + } +} + /// Represents a target for the funds of a transfer #[derive( Debug, @@ -1001,7 +1133,7 @@ pub enum TransferTarget { /// A transfer going to a transparent address Address(Address), /// A transfer going to a shielded address - PaymentAddress(PaymentAddress), + PaymentAddress(UnifiedPaymentAddress), /// A transfer going to an IBC address Ibc(String), } @@ -1021,9 +1153,9 @@ impl TransferTarget { } /// Get the contained PaymentAddress, if any - pub fn payment_address(&self) -> Option { + pub fn payment_address(&self) -> Option { match self { - Self::PaymentAddress(address) => Some(*address), + Self::PaymentAddress(address) => Some(address.clone()), _ => None, } } @@ -1050,7 +1182,12 @@ impl Display for TransferTarget { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Address(x) => x.fmt(f), - Self::PaymentAddress(address) => address.fmt(f), + Self::PaymentAddress(UnifiedPaymentAddress::V0(address)) => { + address.fmt(f) + } + Self::PaymentAddress(UnifiedPaymentAddress::V1(address)) => { + address.fmt(f) + } Self::Ibc(x) => x.fmt(f), } } @@ -1109,7 +1246,7 @@ impl Display for BalanceOwner { #[derive(Debug, Clone)] pub enum MaspValue { /// A MASP PaymentAddress - PaymentAddress(PaymentAddress), + PaymentAddress(UnifiedPaymentAddress), /// A MASP ExtendedSpendingKey ExtendedSpendingKey(ExtendedSpendingKey), /// A MASP FullViewingKey @@ -1120,9 +1257,10 @@ impl FromStr for MaspValue { type Err = DecodeError; fn from_str(s: &str) -> Result { - // Try to decode this value first as a PaymentAddress, then as an - // ExtendedSpendingKey, then as FullViewingKey - PaymentAddress::from_str(s) + // Try to decode this value first as a UnifiedPaymentAddress, + // then as an ExtendedSpendingKey, then as a + // FullViewingKey + UnifiedPaymentAddress::from_str(s) .map(Self::PaymentAddress) .or_else(|_err| { ExtendedSpendingKey::from_str(s).map(Self::ExtendedSpendingKey) @@ -1262,7 +1400,7 @@ mod test { masp_primitives::zip32::ExtendedSpendingKey::master(&[0_u8]), ); let (_diversifier, pa) = sk.0.default_address(); - let pa = PaymentAddress::from(pa); + let pa = PaymentAddress::from(pa).into(); let target = TransferTarget::PaymentAddress(pa); assert_eq!(target.effective_address(), MASP); @@ -1281,7 +1419,7 @@ mod test { masp_primitives::zip32::ExtendedSpendingKey::master(&[0_u8]), ); let (_diversifier, pa) = sk.0.default_address(); - let pa = PaymentAddress::from(pa); + let pa = PaymentAddress::from(pa).into(); let target = TransferTarget::PaymentAddress(pa).address(); assert!(target.is_none()); @@ -1303,7 +1441,7 @@ mod test { masp_primitives::zip32::ExtendedSpendingKey::master(&[0_u8]), ); let (_diversifier, pa) = sk.0.default_address(); - let pa = PaymentAddress::from(pa); + let pa = PaymentAddress::from(pa).into(); let target = TransferTarget::PaymentAddress(pa).t_addr_data(); assert!(target.is_none()); @@ -1319,7 +1457,7 @@ mod test { masp_primitives::zip32::ExtendedSpendingKey::master(&[0_u8]), ); let (_diversifier, pa) = sk.0.default_address(); - let pa = PaymentAddress::from(pa); + let pa: UnifiedPaymentAddress = PaymentAddress::from(pa).into(); const IBC_ADDR: &str = "noble18st0wqx84av8y6xdlss9d6m2nepyqwj6nfxxuv"; @@ -1404,7 +1542,7 @@ mod test { masp_primitives::zip32::ExtendedSpendingKey::master(&[0_u8]), ); let (_diversifier, pa) = sk.0.default_address(); - let pa = PaymentAddress::from(pa); + let pa = PaymentAddress::from(pa).into(); let target = TransferTarget::PaymentAddress(pa); let serialized = target.serialize_to_vec(); diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index b187c4f92f1..e23eeba47c9 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -116,7 +116,8 @@ pub use namada_sdk::tx::{ use namada_sdk::wallet::{DatedSpendingKey, Wallet}; use namada_sdk::{ FlagCiphertext, Namada, NamadaImpl, PaymentAddress, TransferSource, - TransferTarget, parameters, proof_of_stake, tendermint, + TransferTarget, UnifiedPaymentAddress, parameters, proof_of_stake, + tendermint, }; use namada_test_utils::tx_data::TxWriteData; use namada_vm::wasm::run; @@ -1188,7 +1189,9 @@ impl Default for BenchShieldedCtx { .wallet .insert_payment_addr( alias, - PaymentAddress::from(payment_addr), + UnifiedPaymentAddress::V0(PaymentAddress::from( + payment_addr, + )), true, ) .unwrap(); diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 5d228b7c6fe..850e8dc571b 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -15,7 +15,7 @@ use namada_core::dec::Dec; use namada_core::ethereum_events::EthAddress; use namada_core::keccak::KeccakHash; use namada_core::key::{SchemeType, common}; -use namada_core::masp::{DiversifierIndex, MaspEpoch, PaymentAddress}; +use namada_core::masp::{DiversifierIndex, MaspEpoch}; use namada_core::string_encoding::StringEncoded; use namada_core::time::DateTimeUtc; use namada_core::token::Amount; @@ -131,7 +131,7 @@ impl NamadaTypes for SdkTypes { type EthereumAddress = (); type Keypair = namada_core::key::common::SecretKey; type MaspIndexerAddress = String; - type PaymentAddress = namada_core::masp::PaymentAddress; + type PaymentAddress = namada_core::masp::UnifiedPaymentAddress; type PublicKey = namada_core::key::common::PublicKey; type SpendingKey = PseudoExtendedKey; type TendermintAddress = tendermint_rpc::Url; @@ -2956,7 +2956,7 @@ pub struct KeyAddressFind { /// Public key hash to lookup keypair with pub public_key_hash: Option, /// Payment address to find - pub payment_address: Option, + pub payment_address: Option, /// Find keys only pub keys_only: bool, /// Find addresses only diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 3894644c8c7..d26a5b1f78e 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -52,8 +52,8 @@ use namada_core::ethereum_events::EthAddress; use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::key::*; pub use namada_core::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, FlagCiphertext, PaymentAddress, - TransferSource, TransferTarget, + ExtendedSpendingKey, ExtendedViewingKey, FlagCiphertext, FmdPaymentAddress, + PaymentAddress, TransferSource, TransferTarget, UnifiedPaymentAddress, }; pub use namada_core::{control_flow, task_env}; use namada_io::{Client, Io, NamadaIo}; @@ -189,7 +189,7 @@ pub trait Namada: NamadaIo { /// arguments fn new_shielding_transfer( &self, - target: PaymentAddress, + target: UnifiedPaymentAddress, data: Vec, ) -> args::TxShieldingTransfer { args::TxShieldingTransfer { diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 09a554c22e2..54950430348 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -3397,7 +3397,7 @@ pub async fn build_shielding_transfer( transfer_data.push(MaspTransferData { source: TransferSource::Address(source.to_owned()), - target: TransferTarget::PaymentAddress(args.target), + target: TransferTarget::PaymentAddress(args.target.clone()), token: token.to_owned(), amount: validated_amount, }); @@ -4184,8 +4184,8 @@ async fn get_refund_target( match (source, refund_target) { (_, Some(TransferTarget::PaymentAddress(pa))) => { Err(Error::Other(format!( - "Supporting only a transparent address as a refund target: {}", - pa, + "Supporting only a transparent address as a refund target: \ + {pa}" ))) } ( diff --git a/crates/shielded_token/src/masp/shielded_wallet.rs b/crates/shielded_token/src/masp/shielded_wallet.rs index 4c7312697dd..645216d6f87 100644 --- a/crates/shielded_token/src/masp/shielded_wallet.rs +++ b/crates/shielded_token/src/masp/shielded_wallet.rs @@ -1724,7 +1724,7 @@ pub trait ShieldedApi: }); // Make transaction output tied to the current token, // denomination, and epoch. - if let Some(pa) = payment_address { + if let Some(ref pa) = payment_address { // If there is a shielded output builder .add_sapling_output( diff --git a/crates/wallet/src/lib.rs b/crates/wallet/src/lib.rs index d10f1d76075..983bdc2ca96 100644 --- a/crates/wallet/src/lib.rs +++ b/crates/wallet/src/lib.rs @@ -20,7 +20,8 @@ use namada_core::chain::BlockHeight; use namada_core::collections::{HashMap, HashSet}; use namada_core::key::*; use namada_core::masp::{ - DiversifierIndex, ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, + DiversifierIndex, ExtendedSpendingKey, ExtendedViewingKey, + UnifiedPaymentAddress, }; use namada_core::time::DateTimeUtc; use namada_ibc::trace::is_ibc_denom; @@ -463,14 +464,14 @@ impl Wallet { pub fn find_payment_addr( &self, alias: impl AsRef, - ) -> Option<&PaymentAddress> { + ) -> Option<&UnifiedPaymentAddress> { self.store.find_payment_addr(alias.as_ref()) } /// Find an alias by the payment address if it's in the wallet. pub fn find_alias_by_payment_addr( &self, - payment_address: &PaymentAddress, + payment_address: &UnifiedPaymentAddress, ) -> Option<&Alias> { self.store.find_alias_by_payment_addr(payment_address) } @@ -508,11 +509,11 @@ impl Wallet { } /// Get all known payment addresses by their alias - pub fn get_payment_addrs(&self) -> HashMap { + pub fn get_payment_addrs(&self) -> HashMap { self.store .get_payment_addrs() .iter() - .map(|(alias, value)| (alias.into(), *value)) + .map(|(alias, value)| (alias.into(), value.clone())) .collect() } @@ -1231,7 +1232,7 @@ impl Wallet { pub fn insert_payment_addr( &mut self, alias: String, - payment_addr: PaymentAddress, + payment_addr: UnifiedPaymentAddress, force_alias: bool, ) -> Option { self.store diff --git a/crates/wallet/src/store.rs b/crates/wallet/src/store.rs index a27bbe446cc..fa3598d5d3b 100644 --- a/crates/wallet/src/store.rs +++ b/crates/wallet/src/store.rs @@ -15,6 +15,7 @@ use namada_core::collections::HashSet; use namada_core::key::*; use namada_core::masp::{ DiversifierIndex, ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, + UnifiedPaymentAddress, }; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; @@ -69,7 +70,7 @@ pub struct Store { /// Known spending keys spend_keys: BTreeMap>, /// Payment address book - payment_addrs: BiBTreeMap, + payment_addrs: BiBTreeMap, /// Diverisifier index of the next payment address to be generated for a /// given key. diversifier_indices: BTreeMap, @@ -171,14 +172,14 @@ impl Store { pub fn find_payment_addr( &self, alias: impl AsRef, - ) -> Option<&PaymentAddress> { + ) -> Option<&UnifiedPaymentAddress> { self.payment_addrs.get_by_left(&alias.into()) } /// Find an alias by the address if it's in the wallet. pub fn find_alias_by_payment_addr( &self, - payment_address: &PaymentAddress, + payment_address: &UnifiedPaymentAddress, ) -> Option<&Alias> { self.payment_addrs.get_by_right(payment_address) } @@ -282,7 +283,9 @@ impl Store { } /// Get all known payment addresses by their alias. - pub fn get_payment_addrs(&self) -> &BiBTreeMap { + pub fn get_payment_addrs( + &self, + ) -> &BiBTreeMap { &self.payment_addrs } @@ -537,7 +540,7 @@ impl Store { pub fn insert_payment_addr( &mut self, alias: Alias, - payment_addr: PaymentAddress, + payment_addr: UnifiedPaymentAddress, force: bool, ) -> Option { // abort if the alias is reserved @@ -910,7 +913,11 @@ pub struct StoreV0 { impl From for Store { fn from(store: StoreV0) -> Self { let mut to = Store { - payment_addrs: store.payment_addrs, + payment_addrs: store + .payment_addrs + .into_iter() + .map(|(alias, pa)| (alias, UnifiedPaymentAddress::V0(pa))) + .collect(), secret_keys: store.secret_keys, public_keys: store.public_keys, derivation_paths: store.derivation_paths, @@ -962,7 +969,7 @@ pub struct StoreV1 { /// Known spending keys spend_keys: BTreeMap>, /// Payment address book - payment_addrs: BiBTreeMap, + payment_addrs: BiBTreeMap, /// Cryptographic keypairs secret_keys: BTreeMap>, /// Known public keys From 3a5c41f77f026341bb4735df600dc5a1dbbfb80d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 May 2025 14:10:03 +0100 Subject: [PATCH 32/42] Generate v0 payment addrs --- crates/apps_lib/src/cli.rs | 8 ++++++++ crates/apps_lib/src/cli/wallet.rs | 8 ++++++-- crates/sdk/src/args.rs | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index de7dfdaf7a7..129be0d0613 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -3631,6 +3631,7 @@ pub mod args { pub const PAYMENT_ADDRESS_TARGET: Arg = arg("target"); pub const PAYMENT_ADDRESS_TARGET_OPT: ArgOpt = arg_opt("target-pa"); + pub const PAYMENT_ADDRESS_V0: ArgFlag = flag("v0"); pub const PORT_ID: ArgDefault = arg_default( "port-id", DefaultFn(|| PortId::from_str("transfer").unwrap()), @@ -7980,6 +7981,7 @@ pub mod args { _ctx: &mut Context, ) -> Result { Ok(PayAddressGen { + v0: self.v0, alias: self.alias, alias_force: self.alias_force, viewing_key: self.viewing_key, @@ -7994,11 +7996,13 @@ pub mod args { let alias_force = ALIAS_FORCE.parse(matches); let diversifier_index = DIVERSIFIER_INDEX.parse(matches); let viewing_key = VIEWING_KEY_ALIAS.parse(matches); + let v0 = PAYMENT_ADDRESS_V0.parse(matches); Self { alias, alias_force, diversifier_index, viewing_key, + v0, } } @@ -8013,6 +8017,10 @@ pub mod args { "Set the viewing key's current diversifier index beforehand." ))) .arg(VIEWING_KEY.def().help(wrap!("The viewing key."))) + .arg(PAYMENT_ADDRESS_V0.def().help(wrap!( + "Force generating a v0 payment address. Not compatible with \ + FMD." + ))) } } diff --git a/crates/apps_lib/src/cli/wallet.rs b/crates/apps_lib/src/cli/wallet.rs index 07c94a22203..3d3cda402b4 100644 --- a/crates/apps_lib/src/cli/wallet.rs +++ b/crates/apps_lib/src/cli/wallet.rs @@ -403,6 +403,7 @@ fn payment_address_gen( alias_force, viewing_key: viewing_key_alias, diversifier_index, + v0, }: args::PayAddressGen, ) { let mut wallet = load_wallet(ctx); @@ -421,8 +422,11 @@ fn payment_address_gen( .copied() .unwrap_or_default() }); - let (diversifier_index, payment_addr) = - UnifiedPaymentAddress::v1_from_zip32(viewing_key, diversifier_index); + let (diversifier_index, payment_addr) = if v0 { + UnifiedPaymentAddress::v0_from_zip32(viewing_key, diversifier_index) + } else { + UnifiedPaymentAddress::v1_from_zip32(viewing_key, diversifier_index) + }; let mut next_div_idx = diversifier_index; next_div_idx.increment(); let alias = wallet diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 850e8dc571b..ef6612f7c85 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -3021,6 +3021,12 @@ pub struct KeyAddressRemove { /// Generate payment address arguments #[derive(Clone, Debug)] pub struct PayAddressGen { + /// Force generating a v0 payment address. + /// + /// This does not include an FMD public key, therefore + /// should not be shared as a payment target if you + /// intend to use FMD to speed up shielded sync. + pub v0: bool, /// Payment address alias pub alias: String, /// Whether to force overwrite the alias From bfd957c10e460fbba53bfc74806509906a7cedf7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 May 2025 15:10:53 +0100 Subject: [PATCH 33/42] Add flag ciphertexts to txs --- crates/node/src/bench_utils.rs | 1 + crates/sdk/src/lib.rs | 7 ++- crates/sdk/src/tx.rs | 53 ++++++++++++----- crates/shielded_token/src/masp.rs | 8 +++ .../src/masp/shielded_wallet.rs | 57 +++++++++++++++++-- crates/token/src/lib.rs | 17 +++++- 6 files changed, 122 insertions(+), 21 deletions(-) diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index e23eeba47c9..34d73c5627c 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -1280,6 +1280,7 @@ impl BenchShieldedCtx { masp_tx, metadata: _, epoch: _, + fmd_flags: _, }| masp_tx, ) .expect("MASP must have shielded part"); diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index d26a5b1f78e..a01067024e9 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -901,7 +901,7 @@ pub mod testing { arb_withdraw, }; use crate::tx::{ - Authorization, Code, Commitment, Header, MaspBuilder, Section, + Authorization, Code, Commitment, Data, Header, MaspBuilder, Section, TxCommitments, }; @@ -1146,6 +1146,11 @@ pub mod testing { if let Some((shielded_transfer, asset_types, build_params)) = aux { let shielded_section_hash = tx.add_masp_tx_section(shielded_transfer.masp_tx).1; + tx.add_section( + Section::Data( + Data::from_borsh_encoded(&shielded_transfer.fmd_flags), + ), + ); tx.add_masp_builder(MaspBuilder { asset_types: asset_types.into_keys().collect(), // Store how the Info objects map to Descriptors/Outputs diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 54950430348..44241a81113 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -42,7 +42,8 @@ use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::ibc::primitives::{IntoTimestamp, Timestamp as IbcTimestamp}; use namada_core::key::{self, *}; use namada_core::masp::{ - AssetData, MaspEpoch, MaspTxData, TransferSource, TransferTarget, + AssetData, FlagCiphertext, MaspEpoch, MaspTxData, TransferSource, + TransferTarget, }; use namada_core::storage; use namada_core::time::DateTimeUtc; @@ -2821,19 +2822,25 @@ pub async fn build_ibc_transfer( .map(|(shielded_transfer, asset_types)| { let masp_tx_hash = tx.add_masp_tx_section(shielded_transfer.masp_tx.clone()).1; + + let (fmd_section, flag_ciphertext_sechash) = + create_fmd_section(shielded_transfer.fmd_flags); + tx.add_section(fmd_section); + transfer.shielded_data = Some(MaspTxData { masp_tx_id: masp_tx_hash, - // TODO: change this to the actual sechash - // of the fmd flag - flag_ciphertext_sechash: Hash::zero(), + flag_ciphertext_sechash, }); + signing_data.shielded_hash = Some(masp_tx_hash); + tx.add_masp_builder(MaspBuilder { asset_types, metadata: shielded_transfer.metadata, builder: shielded_transfer.builder, target: masp_tx_hash, }); + Result::Ok(transfer) }) .transpose()?; @@ -3256,9 +3263,16 @@ pub async fn build_shielded_transfer( masp_tx, metadata, epoch: _, + fmd_flags, }, asset_types, ) = shielded_parts; + + // Create FMD flags section + let (fmd_section, flag_ciphertext_sechash) = + create_fmd_section(fmd_flags); + tx.add_section(fmd_section); + // Add a MASP Transaction section to the Tx and get the tx hash let section_hash = tx.add_masp_tx_section(masp_tx).1; @@ -3274,9 +3288,7 @@ pub async fn build_shielded_transfer( data.shielded_data = Some(MaspTxData { masp_tx_id: section_hash, - // TODO: change this to the actual sechash - // of the fmd flag - flag_ciphertext_sechash: Hash::zero(), + flag_ciphertext_sechash, }); signing_data.shielded_hash = Some(section_hash); tracing::debug!("Transfer data {data:?}"); @@ -3431,12 +3443,18 @@ pub async fn build_shielding_transfer( masp_tx, metadata, epoch: _, + fmd_flags, }, asset_types, ) = shielded_parts; // Add a MASP Transaction section to the Tx and get the tx hash let shielded_section_hash = tx.add_masp_tx_section(masp_tx).1; + // Create FMD flags section + let (fmd_section, flag_ciphertext_sechash) = + create_fmd_section(fmd_flags); + tx.add_section(fmd_section); + tx.add_masp_builder(MaspBuilder { asset_types, // Store how the Info objects map to Descriptors/Outputs @@ -3449,9 +3467,7 @@ pub async fn build_shielding_transfer( data.shielded_data = Some(MaspTxData { masp_tx_id: shielded_section_hash, - // TODO: change this to the actual sechash - // of the fmd flag - flag_ciphertext_sechash: Hash::zero(), + flag_ciphertext_sechash, }); signing_data.shielded_hash = Some(shielded_section_hash); tracing::debug!("Transfer data {data:?}"); @@ -3559,12 +3575,18 @@ pub async fn build_unshielding_transfer( masp_tx, metadata, epoch: _, + fmd_flags, }, asset_types, ) = shielded_parts; // Add a MASP Transaction section to the Tx and get the tx hash let shielded_section_hash = tx.add_masp_tx_section(masp_tx).1; + // Create FMD flags section + let (fmd_section, flag_ciphertext_sechash) = + create_fmd_section(fmd_flags); + tx.add_section(fmd_section); + tx.add_masp_builder(MaspBuilder { asset_types, // Store how the Info objects map to Descriptors/Outputs @@ -3577,9 +3599,7 @@ pub async fn build_unshielding_transfer( data.shielded_data = Some(MaspTxData { masp_tx_id: shielded_section_hash, - // TODO: change this to the actual sechash - // of the fmd flag - flag_ciphertext_sechash: Hash::zero(), + flag_ciphertext_sechash, }); signing_data.shielded_hash = Some(shielded_section_hash); tracing::debug!("Transfer data {data:?}"); @@ -4329,3 +4349,10 @@ fn proposal_to_vec(proposal: OnChainProposal) -> Result> { borsh::to_vec(&proposal.content) .map_err(|e| Error::from(EncodingError::Conversion(e.to_string()))) } + +fn create_fmd_section(fmd_flags: Vec) -> (Section, Hash) { + let fmd_section = Section::Data(Data::from_borsh_encoded(&fmd_flags)); + let fmd_sechash = fmd_section.get_hash(); + + (fmd_section, fmd_sechash) +} diff --git a/crates/shielded_token/src/masp.rs b/crates/shielded_token/src/masp.rs index b20b0ec5352..8399bdcc099 100644 --- a/crates/shielded_token/src/masp.rs +++ b/crates/shielded_token/src/masp.rs @@ -74,6 +74,11 @@ pub struct ShieldedTransfer { pub metadata: SaplingMetadata, /// Epoch in which the transaction was created pub epoch: MaspEpoch, + /// Vector of FMD flag ciphertexts. + /// + /// There must be a flag ciphertext per shielded output + /// in the `builder`. + pub fmd_flags: Vec, } /// The data for a masp fee payment @@ -191,6 +196,9 @@ pub enum TransferErr { /// Insufficient funds error #[error("Insufficient funds: {0}")] InsufficientFunds(MaspDataLog), + /// Invalid FMD public key + #[error("FMD public key included in the payment address is not valid")] + InvalidFmdPublicKey, /// Generic error #[error("{0}")] General(String), diff --git a/crates/shielded_token/src/masp/shielded_wallet.rs b/crates/shielded_token/src/masp/shielded_wallet.rs index 645216d6f87..0d1e9dccf31 100644 --- a/crates/shielded_token/src/masp/shielded_wallet.rs +++ b/crates/shielded_token/src/masp/shielded_wallet.rs @@ -31,7 +31,8 @@ use namada_core::chain::BlockHeight; use namada_core::collections::{HashMap, HashSet}; use namada_core::control_flow; use namada_core::masp::{ - AssetData, MaspEpoch, TransferSource, TransferTarget, encode_asset_type, + AssetData, FlagCiphertext, FmdSecretKey, MaspEpoch, TransferSource, + TransferTarget, UnifiedPaymentAddress, encode_asset_type, }; use namada_core::task_env::TaskEnvironment; use namada_core::time::{DateTimeUtc, DurationSecs}; @@ -45,7 +46,7 @@ use namada_io::{ }; use namada_wallet::{DatedKeypair, DatedSpendingKey}; use rand::prelude::StdRng; -use rand_core::{OsRng, SeedableRng}; +use rand_core::{CryptoRng, OsRng, RngCore, SeedableRng}; use super::utils::MaspIndexedTx; use crate::masp::utils::MaspClient; @@ -1215,9 +1216,12 @@ pub trait ShieldedApi: return Ok(None); }; + let mut fmd_flags = vec![]; + for (MaspSourceTransferData { source, token }, amount) in &source_data { self.add_inputs( context, + &mut rng, &mut builder, source, token, @@ -1225,6 +1229,7 @@ pub trait ShieldedApi: epoch, &mut denoms, &mut notes_tracker, + &mut fmd_flags, ) .await?; } @@ -1240,6 +1245,7 @@ pub trait ShieldedApi: { self.add_outputs( context, + &mut rng, &mut builder, source, &target, @@ -1247,6 +1253,7 @@ pub trait ShieldedApi: amount, epoch, &mut denoms, + &mut fmd_flags, ) .await?; } @@ -1312,11 +1319,31 @@ pub trait ShieldedApi: ) .map_err(|error| TransferErr::Build { error })?; + // Add remaining flag ciphertexts + fmd_flags.extend({ + let num_shielded_outputs = masp_tx + .sapling_bundle() + .map_or(0, |x| x.shielded_outputs.len()); + + let num_fmd_flags = fmd_flags.len(); + + let dummy_flag_ciphertexts = + checked!(num_shielded_outputs - num_fmd_flags).expect( + "number of shielded outputs in the masp bundle should be \ + greater than or equal to the number of flag ciphertexts \ + generated so far", + ); + + std::iter::repeat_with(|| FlagCiphertext::random(&mut rng)) + .take(dummy_flag_ciphertexts) + }); + Ok(Some(ShieldedTransfer { builder: builder_clone, masp_tx, metadata, epoch, + fmd_flags, })) } @@ -1509,9 +1536,10 @@ pub trait ShieldedApi: /// must be the current epoch. #[allow(async_fn_in_trait)] #[allow(clippy::too_many_arguments)] - async fn add_inputs( + async fn add_inputs( &mut self, context: &impl NamadaIo, + rng: &mut R, builder: &mut Builder, source: &TransferSource, token: &Address, @@ -1519,6 +1547,7 @@ pub trait ShieldedApi: epoch: MaspEpoch, denoms: &mut HashMap, notes_tracker: &mut SpentNotesTracker, + fmd_flags: &mut Vec, ) -> Result, TransferErr> { // We want to fund our transaction solely from supplied spending key let spending_key = source.spending_key(); @@ -1605,6 +1634,12 @@ pub trait ShieldedApi: .map_err(|e| TransferErr::Build { error: builder::Error::SaplingBuild(e), })?; + + fmd_flags.push({ + let fmd_sk: FmdSecretKey = + sk.to_viewing_key().fvk.vk.ivk().into(); + fmd_sk.default_public_key().flag(rng) + }); } // Commit the notes found to our transaction @@ -1669,9 +1704,10 @@ pub trait ShieldedApi: /// Add the necessary transaction outputs to the builder #[allow(clippy::too_many_arguments)] #[allow(async_fn_in_trait)] - async fn add_outputs( + async fn add_outputs( &mut self, context: &impl NamadaIo, + rng: &mut R, builder: &mut Builder, source: Option, target: &TransferTarget, @@ -1679,6 +1715,7 @@ pub trait ShieldedApi: amount: Amount, epoch: MaspEpoch, denoms: &mut HashMap, + fmd_flags: &mut Vec, ) -> Result<(), TransferErr> { // Anotate the asset type in the value balance with its decoding in // order to facilitate cross-epoch computations @@ -1737,6 +1774,18 @@ pub trait ShieldedApi: .map_err(|e| TransferErr::Build { error: builder::Error::SaplingBuild(e), })?; + + fmd_flags.push({ + match pa { + UnifiedPaymentAddress::V0(_) => { + FlagCiphertext::random(rng) + } + UnifiedPaymentAddress::V1(pa) => pa + .to_fmd_public_key() + .ok_or(TransferErr::InvalidFmdPublicKey)? + .flag(rng), + } + }); } else if let Some(t_addr_data) = target.t_addr_data() { // If there is a transparent output builder diff --git a/crates/token/src/lib.rs b/crates/token/src/lib.rs index 24a36d5d27d..d91173cbaca 100644 --- a/crates/token/src/lib.rs +++ b/crates/token/src/lib.rs @@ -357,9 +357,8 @@ pub mod testing { use namada_core::address::testing::arb_non_internal_address; use namada_core::address::{Address, MASP}; use namada_core::collections::HashMap; - use namada_core::hash::Hash; use namada_core::masp::{ - AssetData, MaspTxData, TAddrData, encode_asset_type, + AssetData, FlagCiphertext, MaspTxData, TAddrData, encode_asset_type, }; pub use namada_core::token::*; use namada_shielded_token::masp::testing::{ @@ -531,15 +530,27 @@ pub mod testing { &mut rng, &mut rng_build_params, ).unwrap(); + let fmd_flags = std::iter::repeat_with( + || FlagCiphertext::random(&mut rng) + ) + .take(builder.sapling_outputs().len()) + .collect(); + let fmd_sechash = { + let sec = namada_tx::Section::Data( + namada_tx::Data::from_borsh_encoded(&fmd_flags), + ); + sec.get_hash() + }; transfer.shielded_data = Some(MaspTxData { masp_tx_id: masp_tx.txid().into(), - flag_ciphertext_sechash: Hash::zero(), + flag_ciphertext_sechash: fmd_sechash, }); (transfer, ShieldedTransfer { builder: builder.map_builder(WalletMap), metadata, masp_tx, epoch, + fmd_flags, }, asset_types, rng_build_params.to_stored().unwrap()) } } From 70eaa07b5972474cfe89d5dd45c68759b042a2e6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 8 May 2025 14:46:03 +0100 Subject: [PATCH 34/42] Validate flag ciphertexts in MASP VP --- crates/apps_lib/src/client/tx.rs | 9 +-- crates/core/src/masp.rs | 16 ++++ crates/ibc/src/lib.rs | 26 ++++--- crates/ibc/src/msg.rs | 27 +++++-- crates/sdk/src/args.rs | 5 +- crates/sdk/src/tx.rs | 4 +- .../src/masp/shielded_wallet.rs | 16 +--- crates/shielded_token/src/vp.rs | 73 +++++++++++++++++-- crates/systems/src/ibc.rs | 13 +++- crates/token/src/tx.rs | 6 ++ crates/tx/src/action.rs | 31 ++++++++ crates/tx/src/types.rs | 30 +++++++- wasm/tx_ibc/src/lib.rs | 25 ++++--- wasm/vp_implicit/src/lib.rs | 4 +- wasm/vp_user/src/lib.rs | 4 +- 15 files changed, 224 insertions(+), 65 deletions(-) diff --git a/crates/apps_lib/src/client/tx.rs b/crates/apps_lib/src/client/tx.rs index 3f420cc0769..0b6ae83de22 100644 --- a/crates/apps_lib/src/client/tx.rs +++ b/crates/apps_lib/src/client/tx.rs @@ -1935,7 +1935,8 @@ pub async fn gen_ibc_shielding_transfer( ) -> Result<(), error::Error> { let output_folder = args.output_folder.clone(); - if let Some(masp_tx) = tx::gen_ibc_shielding_transfer(context, args).await? + if let Some((masp_tx, fmd_flags)) = + tx::gen_ibc_shielding_transfer(context, args).await? { let tx_id = masp_tx.txid().to_string(); let filename = format!("ibc_masp_tx_{}.memo", tx_id); @@ -1945,11 +1946,7 @@ pub async fn gen_ibc_shielding_transfer( }; let mut out = File::create(&output_path) .expect("Creating a new file for IBC MASP transaction failed."); - let bytes = convert_masp_tx_to_ibc_memo( - masp_tx, - // TODO: add actual flag ciphertext - Default::default(), - ); + let bytes = convert_masp_tx_to_ibc_memo(masp_tx, fmd_flags); out.write_all(bytes.as_bytes()) .expect("Writing IBC MASP transaction file failed."); println!( diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 3c7e70852aa..a7dc5323f37 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -1017,6 +1017,22 @@ pub enum UnifiedPaymentAddress { } impl UnifiedPaymentAddress { + /// Generate a [`FlagCiphertext`] from this payment address. + /// + /// Returns [`None`] if the FMD public key in the + /// [`UnifiedPaymentAddress::V1`] variant is invalid, and a random flag + /// ciphertext for the [`UnifiedPaymentAddress::V0`] variant. + #[cfg(feature = "rand")] + pub fn flag(&self, rng: &mut R) -> Option + where + R: rand_core::CryptoRng + rand_core::RngCore, + { + match self { + Self::V0(_) => Some(FlagCiphertext::random(rng)), + Self::V1(pa) => pa.to_fmd_public_key().map(|pk| pk.flag(rng)), + } + } + /// Create a v0 payment address. pub fn v0_from_zip32( vk: ExtendedViewingKey, diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 4a98d76afc0..4aa52e5267c 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -92,7 +92,7 @@ use namada_core::ibc::core::channel::types::commitment::{ AcknowledgementCommitment, PacketCommitment, compute_packet_commitment, }; pub use namada_core::ibc::*; -use namada_core::masp::{TAddrData, addr_taddr, ibc_taddr}; +use namada_core::masp::{FlagCiphertext, TAddrData, addr_taddr, ibc_taddr}; use namada_core::masp_primitives::transaction::components::ValueSum; use namada_core::token::Amount; use namada_events::EmitEvents; @@ -237,18 +237,19 @@ impl namada_systems::ibc::Read for Store where S: StorageRead, { - fn try_extract_masp_tx_from_envelope( + fn try_extract_shielding_data_from_envelope( tx_data: &[u8], - ) -> StorageResult> { + ) -> StorageResult)>> { let msg = decode_message::(tx_data) .into_storage_result() .ok(); let tx = if let Some(IbcMessage::Envelope(ref envelope)) = msg { - Some(extract_masp_tx_from_envelope(envelope).ok_or_else(|| { - StorageError::new_const( - "Missing MASP transaction in IBC message", - ) - })?) + let shielding_data = extract_shielding_data_from_envelope(envelope) + .ok_or(StorageError::new_const( + "Missing MASP shielding data in IBC message", + ))?; + + Some((shielding_data.masp_tx, shielding_data.flag_ciphertexts)) } else { None }; @@ -578,7 +579,7 @@ pub struct InternalData { /// The transparent transfer that happens in parallel to IBC processes pub transparent: Option, /// The shielded transaction that happens in parallel to IBC processes - pub shielded: Option, + pub shielded: Option, /// IBC tokens that are credited/debited to internal accounts pub ibc_tokens: BTreeSet
, } @@ -760,13 +761,16 @@ where .map_err(Error::Storage)?; tokens.insert(token); } - (extract_masp_tx_from_packet(&msg.packet), tokens) + ( + extract_shielding_data_from_packet(&msg.packet), + tokens, + ) } #[cfg(is_apple_silicon)] MsgEnvelope::Packet(PacketMsg::Ack(msg)) => { // NOTE: This is unneeded but wasm compilation error // happened if deleted on macOS with Apple Silicon - let _ = extract_masp_tx_from_packet(&msg.packet); + let _ = extract_shielding_data_from_packet(&msg.packet); (None, BTreeSet::new()) } _ => (None, BTreeSet::new()), diff --git a/crates/ibc/src/msg.rs b/crates/ibc/src/msg.rs index b93fd17d373..67c44c6d8d7 100644 --- a/crates/ibc/src/msg.rs +++ b/crates/ibc/src/msg.rs @@ -242,8 +242,8 @@ impl BorshSchema for MsgNftTransfer { pub struct IbcShieldingData { /// MASP transaction forwarded over IBC. pub masp_tx: MaspTransaction, - /// Flag ciphertext to signal the owner of the new note(s). - pub flag_ciphertext: FlagCiphertext, + /// Flag ciphertexts to signal the owner(s) of the new note(s). + pub flag_ciphertexts: Vec, } impl From<&IbcShieldingData> for String { @@ -279,9 +279,17 @@ impl FromStr for IbcShieldingData { pub fn extract_masp_tx_from_envelope( envelope: &MsgEnvelope, ) -> Option { + extract_shielding_data_from_envelope(envelope) + .map(|IbcShieldingData { masp_tx, .. }| masp_tx) +} + +/// Extract IBC shielding data from IBC envelope +pub fn extract_shielding_data_from_envelope( + envelope: &MsgEnvelope, +) -> Option { match envelope { MsgEnvelope::Packet(PacketMsg::Recv(msg)) => { - extract_masp_tx_from_packet(&msg.packet) + extract_shielding_data_from_packet(&msg.packet) } _ => None, } @@ -306,10 +314,17 @@ pub fn decode_ibc_shielding_data( /// Extract MASP transaction from IBC packet memo pub fn extract_masp_tx_from_packet(packet: &Packet) -> Option { + extract_shielding_data_from_packet(packet) + .map(|IbcShieldingData { masp_tx, .. }| masp_tx) +} + +/// Extract IBC shielding data from IBC packet memo +pub fn extract_shielding_data_from_packet( + packet: &Packet, +) -> Option { let memo = extract_memo_from_packet(packet, &packet.port_id_on_b)?; decode_ibc_shielding_data(memo) - .map(|IbcShieldingData { masp_tx, .. }| masp_tx) } fn extract_memo_from_packet( @@ -376,11 +391,11 @@ pub fn extract_traces_from_recv_msg( /// Get IBC memo string from MASP transaction for receiving pub fn convert_masp_tx_to_ibc_memo( masp_tx: MaspTransaction, - flag_ciphertext: FlagCiphertext, + flag_ciphertexts: Vec, ) -> String { IbcShieldingData { masp_tx, - flag_ciphertext, + flag_ciphertexts, } .into() } diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index ef6612f7c85..fe6608ee710 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -718,7 +718,7 @@ impl TxOsmosisSwap { ), }; - let shielding_tx = tx::gen_ibc_shielding_transfer( + let (shielding_tx, fmd_flags) = tx::gen_ibc_shielding_transfer( ctx, GenIbcShieldingTransfer { query: Query { @@ -754,8 +754,7 @@ impl TxOsmosisSwap { shielding_data: StringEncoded::new( IbcShieldingData { masp_tx: shielding_tx, - // TODO: add actual flag ciphertext here - flag_ciphertext: Default::default(), + flag_ciphertexts: fmd_flags, }, ), shielded_amount: amount_to_shield, diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 44241a81113..edce82f7187 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -3981,7 +3981,7 @@ pub async fn build_custom( pub async fn gen_ibc_shielding_transfer( context: &N, args: args::GenIbcShieldingTransfer, -) -> Result> { +) -> Result)>> { let source = IBC; let token = match args.asset { @@ -4043,7 +4043,7 @@ pub async fn gen_ibc_shielding_transfer( .map_err(|err| TxSubmitError::MaspError(err.to_string()))? }; - Ok(shielded_transfer.map(|st| st.masp_tx)) + Ok(shielded_transfer.map(|st| (st.masp_tx, st.fmd_flags))) } pub(crate) async fn get_ibc_src_port_channel( diff --git a/crates/shielded_token/src/masp/shielded_wallet.rs b/crates/shielded_token/src/masp/shielded_wallet.rs index 0d1e9dccf31..987e34bb47c 100644 --- a/crates/shielded_token/src/masp/shielded_wallet.rs +++ b/crates/shielded_token/src/masp/shielded_wallet.rs @@ -32,7 +32,7 @@ use namada_core::collections::{HashMap, HashSet}; use namada_core::control_flow; use namada_core::masp::{ AssetData, FlagCiphertext, FmdSecretKey, MaspEpoch, TransferSource, - TransferTarget, UnifiedPaymentAddress, encode_asset_type, + TransferTarget, encode_asset_type, }; use namada_core::task_env::TaskEnvironment; use namada_core::time::{DateTimeUtc, DurationSecs}; @@ -1775,17 +1775,9 @@ pub trait ShieldedApi: error: builder::Error::SaplingBuild(e), })?; - fmd_flags.push({ - match pa { - UnifiedPaymentAddress::V0(_) => { - FlagCiphertext::random(rng) - } - UnifiedPaymentAddress::V1(pa) => pa - .to_fmd_public_key() - .ok_or(TransferErr::InvalidFmdPublicKey)? - .flag(rng), - } - }); + fmd_flags.push( + pa.flag(rng).ok_or(TransferErr::InvalidFmdPublicKey)?, + ); } else if let Some(t_addr_data) = target.t_addr_data() { // If there is a transparent output builder diff --git a/crates/shielded_token/src/vp.rs b/crates/shielded_token/src/vp.rs index 1875937c5c6..a79602e3961 100644 --- a/crates/shielded_token/src/vp.rs +++ b/crates/shielded_token/src/vp.rs @@ -17,7 +17,9 @@ use namada_core::address::{self, Address}; use namada_core::arith::{CheckedAdd, CheckedSub, checked}; use namada_core::booleans::BoolResultUnitExt; use namada_core::collections::HashSet; -use namada_core::masp::{MaspEpoch, TAddrData, addr_taddr, encode_asset_type}; +use namada_core::masp::{ + FlagCiphertext, MaspEpoch, TAddrData, addr_taddr, encode_asset_type, +}; use namada_core::storage::Key; use namada_core::token; use namada_core::token::{Amount, MaspDigitPos}; @@ -435,12 +437,13 @@ where .data(batched_tx.cmt) .ok_or_err_msg("No transaction data")?; let actions = ctx.read_actions()?; - // Try to get the Transaction object from the tx first (IBC) and from - // the actions afterwards - let shielded_tx = if let Some(tx) = - Ibc::try_extract_masp_tx_from_envelope::(&tx_data)? + + // Try to get the Transaction object and FMD flag ciphertexts + // from the tx first (IBC) and from the actions afterwards + let (shielded_tx, fmd_flags) = if let Some(shielding_data) = + Ibc::try_extract_shielding_data_from_envelope::(&tx_data)? { - tx + shielding_data } else { let masp_section_ref = namada_tx::action::get_masp_section_ref(&actions) @@ -450,14 +453,33 @@ where "Missing MASP section reference in action", ) })?; + let flag_ciphertexts_ref = + namada_tx::action::get_fmd_flag_ciphertexts_ref(&actions) + .map_err(Error::new_const)? + .ok_or_else(|| { + Error::new_const( + "Missing FMD flag ciphertexts reference in action", + ) + })?; - batched_tx + let masp_tx = batched_tx .tx .get_masp_section(&masp_section_ref) .cloned() .ok_or_else(|| { Error::new_const("Missing MASP section in transaction") - })? + })?; + let fmd_flags = batched_tx + .tx + .get_fmd_flag_ciphertexts(&flag_ciphertexts_ref) + .map_err(Error::new)? + .ok_or_else(|| { + Error::new_const( + "Missing FMD flag ciphertexts in transaction", + ) + })?; + + (masp_tx, fmd_flags) }; if u64::from(ctx.get_block_height()?) @@ -468,6 +490,8 @@ where return Err(error); } + validate_flag_ciphertexts(&shielded_tx, fmd_flags)?; + // Check the validity of the keys and get the transfer data let changed_balances = Self::validate_state_and_get_transfer_data( ctx, @@ -949,6 +973,39 @@ fn verify_sapling_balancing_value( } } +/// Check if the flag ciphertexts included in the tx are valid. +fn validate_flag_ciphertexts( + masp_tx: &Transaction, + fmd_flags: Vec, +) -> Result<()> { + let shielded_outputs_len = masp_tx + .sapling_bundle() + .map_or(0, |bundle| bundle.shielded_outputs.len()); + + if shielded_outputs_len != fmd_flags.len() { + let error = Error::new(format!( + "The number of shielded outputs in the MASP tx ({}) does not \ + match the number of FMD flag ciphertexts ({})", + shielded_outputs_len, + fmd_flags.len() + )); + tracing::debug!("{error}"); + return Err(error); + } + + fmd_flags + .iter() + .all(FlagCiphertext::is_valid) + .ok_or_else(|| { + let error = Error::new_const( + "Not all FMD flag ciphertexts in the MASP tx were considered \ + valid, either because of invalid gamma or tampered bits", + ); + tracing::debug!("{error}"); + error + }) +} + #[cfg(test)] mod shielded_token_tests { use std::cell::RefCell; diff --git a/crates/systems/src/ibc.rs b/crates/systems/src/ibc.rs index b492513b9e4..0fb0e5d31fd 100644 --- a/crates/systems/src/ibc.rs +++ b/crates/systems/src/ibc.rs @@ -6,16 +6,21 @@ use masp_primitives::transaction::TransparentAddress; use masp_primitives::transaction::components::ValueSum; use namada_core::address::Address; use namada_core::borsh::BorshDeserialize; -use namada_core::masp::TAddrData; +use namada_core::masp::{FlagCiphertext, TAddrData}; use namada_core::{masp_primitives, storage, token}; pub use namada_storage::Result; /// Abstract IBC storage read interface pub trait Read { - /// Extract MASP transaction from IBC envelope - fn try_extract_masp_tx_from_envelope( + /// Extract shielding data from IBC envelope + fn try_extract_shielding_data_from_envelope( tx_data: &[u8], - ) -> Result>; + ) -> Result< + Option<( + masp_primitives::transaction::Transaction, + Vec, + )>, + >; /// Apply relevant IBC packets to the changed balances structure fn apply_ibc_packet( diff --git a/crates/token/src/tx.rs b/crates/token/src/tx.rs index 0cc2488a63d..74146dc072f 100644 --- a/crates/token/src/tx.rs +++ b/crates/token/src/tx.rs @@ -5,6 +5,7 @@ use std::collections::{BTreeMap, BTreeSet}; use namada_core::arith::CheckedSub; use namada_core::collections::HashSet; +use namada_core::hash::Hash; use namada_core::masp::encode_asset_type; use namada_core::masp_primitives::transaction::Transaction; use namada_core::token::MaspDigitPos; @@ -46,6 +47,7 @@ where apply_shielded_transfer( env, shielded_data.masp_tx_id, + shielded_data.flag_ciphertext_sechash, debited_accounts, tokens, tx_data, @@ -157,6 +159,7 @@ where pub fn apply_shielded_transfer( env: &mut ENV, masp_section_ref: MaspTxId, + fmd_flags_section_ref: Hash, debited_accounts: HashSet
, tokens: HashSet
, tx_data: &BatchedTx, @@ -180,6 +183,9 @@ where env.push_action(Action::Masp(MaspAction::MaspSectionRef( masp_section_ref, )))?; + env.push_action(Action::Masp(MaspAction::FmdSectionRef( + fmd_flags_section_ref, + )))?; update_undated_balances(env, &shielded, tokens)?; // Extract the debited accounts for the masp part of the transfer and // push the relative actions diff --git a/crates/tx/src/action.rs b/crates/tx/src/action.rs index 4b477a2d2ce..c0f54a72a9d 100644 --- a/crates/tx/src/action.rs +++ b/crates/tx/src/action.rs @@ -10,6 +10,7 @@ use std::fmt; use namada_core::address::Address; use namada_core::borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::hash::Hash; use namada_core::masp::MaspTxId; use namada_core::storage::KeySeg; use namada_core::{address, storage}; @@ -71,6 +72,11 @@ pub enum PgfAction { pub enum MaspAction { /// The hash of the masp [`crate::Section`] MaspSectionRef(MaspTxId), + /// The hash of the fmd [`crate::Section`] + /// + /// The data section encodes a vector of flag ciphertexts, + /// one per shielded output + FmdSectionRef(Hash), /// A required authorizer for the transaction MaspAuthorizer(Address), } @@ -148,6 +154,31 @@ pub fn get_masp_section_ref( } } +/// Helper function to get the optional fmd section reference from the +/// [`Actions`]. If more than one [`MaspAction`] is found we return an error +pub fn get_fmd_flag_ciphertexts_ref( + actions: &Actions, +) -> Result, &'static str> { + let flag_ciphertext_refs: Vec<_> = actions + .iter() + .filter_map(|action| { + if let Action::Masp(MaspAction::FmdSectionRef(fmd_section_ref)) = + action + { + Some(*fmd_section_ref) + } else { + None + } + }) + .collect(); + + if flag_ciphertext_refs.len() > 1 { + Err("The transaction pushed multiple FMD flag ciphertext sections") + } else { + Ok(flag_ciphertext_refs.first().cloned()) + } +} + /// Helper function to check if the action is IBC shielding transfer pub fn is_ibc_shielding_transfer( reader: &T, diff --git a/crates/tx/src/types.rs b/crates/tx/src/types.rs index e2ab2aa2be7..cb5ff5bf2d0 100644 --- a/crates/tx/src/types.rs +++ b/crates/tx/src/types.rs @@ -6,6 +6,7 @@ use std::io; use std::ops::{Bound, RangeBounds}; use std::str::FromStr; +use data_encoding::HEXUPPER; use masp_primitives::transaction::Transaction; use namada_account::AccountPublicKeysMap; use namada_core::address::Address; @@ -15,7 +16,7 @@ use namada_core::borsh::{ use namada_core::chain::{BlockHeight, ChainId}; use namada_core::collections::{HashMap, HashSet}; use namada_core::key::*; -use namada_core::masp::MaspTxId; +use namada_core::masp::{FlagCiphertext, MaspTxId}; use namada_core::storage::TxIndex; use namada_core::time::DateTimeUtc; use namada_macros::BorshDeserializer; @@ -46,6 +47,8 @@ pub enum DecodeError { InvalidTimestamp(prost_types::TimestampError), #[error("Couldn't serialize transaction from JSON at {0}")] InvalidJSONDeserialization(String), + #[error("Could not decode FMD flag ciphertexts from tx section {0}")] + InvalidFlagCiphertexts(String), } #[allow(missing_docs)] @@ -287,6 +290,31 @@ impl Tx { None } + /// Get the FMD flag ciphertext with the given hash + pub fn get_fmd_flag_ciphertexts( + &self, + hash: &namada_core::hash::Hash, + ) -> Result>, DecodeError> { + let maybe_section = self.get_section(hash); + + let data = match maybe_section.as_ref().map(Cow::as_ref) { + Some(Section::Data(Data { data, .. })) => data, + Some(_) => { + return Err(DecodeError::InvalidFlagCiphertexts( + HEXUPPER.encode(&hash.0), + )); + } + None => return Ok(None), + }; + + let decoded = + BorshDeserialize::try_from_slice(data).map_err(|_err| { + DecodeError::InvalidFlagCiphertexts(HEXUPPER.encode(&hash.0)) + })?; + + Ok(Some(decoded)) + } + /// Remove the transaction section with the given hash pub fn remove_masp_section(&mut self, hash: &MaspTxId) { self.sections.retain(|section| { diff --git a/wasm/tx_ibc/src/lib.rs b/wasm/tx_ibc/src/lib.rs index ee2bcb60c28..3df5590f415 100644 --- a/wasm/tx_ibc/src/lib.rs +++ b/wasm/tx_ibc/src/lib.rs @@ -12,7 +12,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { .execute::(&data) .into_storage_result()?; - let (masp_section_ref, mut token_addrs) = + let (maybe_masp_refs, mut token_addrs) = if let Some(transfers) = data.transparent { let (_debited_accounts, tokens) = if let Some(transparent) = transfers.transparent_part() { @@ -22,18 +22,18 @@ fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { Default::default() }; - (transfers.masp_tx_id(), tokens) + (transfers.shielded_data, tokens) } else { (None, Default::default()) }; token_addrs.extend(data.ibc_tokens); - let shielded = if let Some(masp_section_ref) = masp_section_ref { + let maybe_masp_tx = if let Some(shielded) = maybe_masp_refs { Some( tx_data .tx - .get_masp_section(&masp_section_ref) + .get_masp_section(&shielded.masp_tx_id) .cloned() .ok_or_err_msg( "Unable to find required shielded section in tx data", @@ -44,20 +44,25 @@ fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { ) } else { data.shielded + .map(|ibc_shielding_data| ibc_shielding_data.masp_tx) }; - if let Some(shielded) = shielded { - token::utils::handle_masp_tx(ctx, &shielded) + + if let Some(masp_tx) = maybe_masp_tx { + token::utils::handle_masp_tx(ctx, &masp_tx) .wrap_err("Encountered error while handling MASP transaction")?; - update_masp_note_commitment_tree(&shielded) + update_masp_note_commitment_tree(&masp_tx) .wrap_err("Failed to update the MASP commitment tree")?; - if let Some(masp_section_ref) = masp_section_ref { + if let Some(masp_refs) = maybe_masp_refs { ctx.push_action(Action::Masp(MaspAction::MaspSectionRef( - masp_section_ref, + masp_refs.masp_tx_id, + )))?; + ctx.push_action(Action::Masp(MaspAction::FmdSectionRef( + masp_refs.flag_ciphertext_sechash, )))?; } else { ctx.push_action(Action::IbcShielding)?; } - token::update_undated_balances(ctx, &shielded, token_addrs)?; + token::update_undated_balances(ctx, &masp_tx, token_addrs)?; } Ok(()) diff --git a/wasm/vp_implicit/src/lib.rs b/wasm/vp_implicit/src/lib.rs index 8ad708c6de6..b9b1e9e8a86 100644 --- a/wasm/vp_implicit/src/lib.rs +++ b/wasm/vp_implicit/src/lib.rs @@ -113,7 +113,9 @@ fn validate_tx( cmt, &addr, )?, - Action::Masp(MaspAction::MaspSectionRef(_)) => (), + Action::Masp( + MaspAction::MaspSectionRef(_) | MaspAction::FmdSectionRef(_), + ) => (), Action::IbcShielding => (), } } diff --git a/wasm/vp_user/src/lib.rs b/wasm/vp_user/src/lib.rs index d53ea783044..6e71b7e65f9 100644 --- a/wasm/vp_user/src/lib.rs +++ b/wasm/vp_user/src/lib.rs @@ -112,7 +112,9 @@ fn validate_tx( cmt, &addr, )?, - Action::Masp(MaspAction::MaspSectionRef(_)) => (), + Action::Masp( + MaspAction::MaspSectionRef(_) | MaspAction::FmdSectionRef(_), + ) => (), Action::IbcShielding => (), } } From bf609fe7ff8fe16a9486fd18f2193f00fa45014f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 May 2025 16:23:45 +0100 Subject: [PATCH 35/42] Emit FMD flag ciphertext tx events --- crates/node/src/bench_utils.rs | 87 ++++++---- crates/node/src/protocol.rs | 152 +++++++++++------- crates/sdk/src/masp.rs | 12 +- crates/sdk/src/masp/utilities.rs | 10 +- .../src/masp/shielded_sync/utils.rs | 35 +--- crates/tx/src/event.rs | 125 +++++++++++--- 6 files changed, 268 insertions(+), 153 deletions(-) diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index 34d73c5627c..fc9900cf1d2 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -88,6 +88,7 @@ use namada_sdk::queries::{ use namada_sdk::state::StorageRead; use namada_sdk::state::write_log::StorageModification; use namada_sdk::storage::{Key, KeySeg, TxIndex}; +use namada_sdk::tendermint::abci::Event as AbciEvent; use namada_sdk::time::DateTimeUtc; use namada_sdk::token::{ self, Amount, DenominatedAmount, MaspTxData, Transfer, @@ -96,7 +97,9 @@ use namada_sdk::tx::data::pos::Bond; use namada_sdk::tx::data::{ BatchedTxResult, Fee, TxResult, VpsResult, compute_inner_tx_hash, }; -use namada_sdk::tx::event::{Batch, MaspEvent, MaspTxRef, new_tx_event}; +use namada_sdk::tx::event::{ + Batch, FmdSectionRef, MaspEvent, MaspTxKind, MaspTxRef, new_tx_event, +}; use namada_sdk::tx::{ Authorization, BatchedTx, BatchedTxRef, Code, Data, IndexedTx, Section, Tx, }; @@ -1062,43 +1065,67 @@ impl Client for BenchShell { let tx_event: Event = new_tx_event(tx, height.value()) .with(Batch(&tx_result)) .into(); - // Expect a single masp tx in the batch - let masp_ref = tx.sections.iter().find_map(|section| { - if let Section::MaspTx(transaction) = section { - Some(MaspTxRef::MaspSection(transaction.txid().into())) - } else { - None - } - }); let first_inner_tx_hash = compute_inner_tx_hash( tx.wrapper_hash().as_ref(), Either::Right(tx.first_commitments().unwrap()), ); - let masp_event = masp_ref.map(|data| { - let masp_event: Event = MaspEvent { - tx_index: IndexedTx { - block_height: namada_sdk::chain::BlockHeight( - u64::from(height), - ), - block_index: TxIndex::must_from_usize(idx), - batch_index: Some(0), - }, - kind: namada_sdk::tx::event::MaspEventKind::Transfer, - data, - } - .with(TxHash(tx.header_hash())) - .with(InnerTxHash(first_inner_tx_hash)) - .into(); - masp_event - }); - - res.push(namada_sdk::tendermint::abci::Event::from(tx_event)); + let wrapper_hash = tx.wrapper_hash().unwrap_or_default(); + let indexed_tx = IndexedTx { + block_height: namada_sdk::chain::BlockHeight(u64::from( + height, + )), + block_index: TxIndex::must_from_usize(idx), + batch_index: Some(0), + }; - if let Some(event) = masp_event { - res.push(namada_sdk::tendermint::abci::Event::from(event)); + let masp_tx_event = + tx.sections.iter().find_map(|section| match section { + Section::MaspTx(transaction) => { + Some(AbciEvent::from(Event::from( + MaspEvent::ShieldedOutput { + tx_index: indexed_tx, + kind: MaspTxKind::Transfer, + data: MaspTxRef::MaspSection( + transaction.txid().into(), + ), + } + .with(TxHash(wrapper_hash)) + .with(InnerTxHash(first_inner_tx_hash)), + ))) + } + _ => None, + }); + let masp_fmd_event = + tx.sections.iter().find_map(|section| match section { + sec @ Section::Data(Data { data, .. }) + if >::try_from_slice(data) + .is_ok() => + { + Some(AbciEvent::from(Event::from( + MaspEvent::FlagCiphertexts { + tx_index: indexed_tx, + section: FmdSectionRef::FmdSection( + sec.get_hash(), + ), + } + .with(TxHash(wrapper_hash)) + .with(InnerTxHash(first_inner_tx_hash)), + ))) + } + _ => None, + }); + + res.push(AbciEvent::from(tx_event)); + + if let Some((masp_tx_event, masp_fmd_event)) = + masp_tx_event.zip(masp_fmd_event) + { + res.push(masp_tx_event); + res.push(masp_fmd_event); } } + Some(res) } else { None diff --git a/crates/node/src/protocol.rs b/crates/node/src/protocol.rs index 63e0b748ea9..d53f7459f7f 100644 --- a/crates/node/src/protocol.rs +++ b/crates/node/src/protocol.rs @@ -30,7 +30,7 @@ use namada_sdk::tx::data::{ BatchedTxResult, TxResult, VpStatusFlags, VpsResult, WrapperTx, compute_inner_tx_hash, }; -use namada_sdk::tx::event::{MaspEvent, MaspEventKind, MaspTxRef}; +use namada_sdk::tx::event::{FmdSectionRef, MaspEvent, MaspTxKind, MaspTxRef}; use namada_sdk::tx::{BatchedTxRef, IndexedTx, Tx, TxCommitments}; use namada_sdk::validation::{ EthBridgeNutVp, EthBridgePoolVp, EthBridgeVp, GovernanceVp, IbcVp, MaspVp, @@ -395,38 +395,32 @@ where Ok(mut batched_tx_result) if batched_tx_result.is_accepted() => { // If the transaction was a masp one generate the // appropriate event - if let Some(masp_ref) = get_optional_masp_ref( + if let Some((masp_ref, fmd_ref)) = get_optional_masp_refs( state, cmt, Either::Right(&batched_tx_result), ) .map_err(|e| Box::new(DispatchError::from(e)))? { - let inner_tx_hash = - compute_inner_tx_hash(wrapper_hash, Either::Right(cmt)); - batched_tx_result.events.insert( - MaspEvent { - tx_index: IndexedTx { - block_height: height, - block_index: tx_index, - batch_index: tx - .header - .batch - .get_index_of(cmt) - .map(|idx| { - TxIndex::must_from_usize(idx).into() - }), - }, - kind: MaspEventKind::Transfer, - data: masp_ref, - } - .with(TxHashAttr( - // Zero hash if the wrapper is not provided - // (governance proposal) - wrapper_hash.cloned().unwrap_or_default(), - )) - .with(InnerTxHashAttr(inner_tx_hash)) - .into(), + insert_masp_events( + &mut batched_tx_result, + // Zero hash if the wrapper is not provided + // (governance proposal) + TxHashAttr(wrapper_hash.cloned().unwrap_or_default()), + InnerTxHashAttr(compute_inner_tx_hash( + wrapper_hash, + Either::Right(cmt), + )), + IndexedTx { + block_height: height, + block_index: tx_index, + batch_index: tx.header.batch.get_index_of(cmt).map( + |idx| TxIndex::must_from_usize(idx).into(), + ), + }, + MaspTxKind::Transfer, + masp_ref, + fmd_ref, ); } @@ -467,6 +461,7 @@ where pub struct MaspTxResult { tx_result: BatchedTxResult, masp_section_ref: MaspTxRef, + fmd_section_ref: FmdSectionRef, } /// Performs the required operation on a wrapper transaction: @@ -526,22 +521,21 @@ where let first_commitments = tx.first_commitments().unwrap(); let mut batch = TxResult::default(); // Generate Masp event if needed - masp_tx_result.tx_result.events.insert( - MaspEvent { - tx_index: IndexedTx { - block_height: height, - block_index: tx_index.to_owned(), - batch_index: Some(0), - }, - kind: MaspEventKind::FeePayment, - data: masp_tx_result.masp_section_ref, - } - .with(TxHashAttr(tx.header_hash())) - .with(InnerTxHashAttr(compute_inner_tx_hash( + insert_masp_events( + &mut masp_tx_result.tx_result, + TxHashAttr(tx.header_hash()), + InnerTxHashAttr(compute_inner_tx_hash( tx.wrapper_hash().as_ref(), Either::Right(first_commitments), - ))) - .into(), + )), + IndexedTx { + block_height: height, + block_index: tx_index.to_owned(), + batch_index: Some(0), + }, + MaspTxKind::FeePayment, + masp_tx_result.masp_section_ref, + masp_tx_result.fmd_section_ref, ); batch.insert_inner_tx_result( @@ -839,20 +833,22 @@ where // Ensure that the transaction is actually a masp one, otherwise // reject if is_masp_transfer && result.is_accepted() { - let masp_section_ref = get_optional_masp_ref( - *state, - first_tx.cmt, - Either::Left(true), - )? - .ok_or_else(|| { - Error::FeeError( - "Missing expected masp section reference" - .to_string(), - ) - })?; + let (masp_section_ref, fmd_section_ref) = + get_optional_masp_refs( + *state, + first_tx.cmt, + Either::Left(true), + )? + .ok_or_else(|| { + Error::FeeError( + "Missing expected masp section reference" + .to_string(), + ) + })?; MaspTxResult { tx_result: result, masp_section_ref, + fmd_section_ref, } } else { state.write_log_mut().drop_tx(); @@ -893,11 +889,11 @@ where // messing up with indexers/clients. Also a transaction can only be of one of // the two types, not both at the same time (the MASP VP accepts a single // Transaction) -fn get_optional_masp_ref>( +fn get_optional_masp_refs>( state: &S, cmt: &TxCommitments, is_masp_tx: Either, -) -> Result> { +) -> Result> { // Always check that the transaction was indeed a MASP one by looking at the // changed keys. A malicious tx could push a MASP Action without touching // any storage keys associated with the shielded pool @@ -912,14 +908,27 @@ fn get_optional_masp_ref>( let masp_ref = if action::is_ibc_shielding_transfer(state) .map_err(Error::StateError)? { - Some(MaspTxRef::IbcData(cmt.data_sechash().to_owned())) + let ibc_data = cmt.data_sechash().to_owned(); + + Some(( + MaspTxRef::IbcData(ibc_data), + FmdSectionRef::IbcData(ibc_data), + )) } else { let actions = state.read_actions().map_err(Error::StateError)?; - action::get_masp_section_ref(&actions) + + let masp_tx = action::get_masp_section_ref(&actions) + .map_err(|msg| { + Error::StateError(state::Error::new_alloc(msg.to_string())) + })? + .map(MaspTxRef::MaspSection); + let fmd_sechash = action::get_fmd_flag_ciphertexts_ref(&actions) .map_err(|msg| { Error::StateError(state::Error::new_alloc(msg.to_string())) })? - .map(MaspTxRef::MaspSection) + .map(FmdSectionRef::FmdSection); + + masp_tx.zip(fmd_sechash) }; Ok(masp_ref) @@ -1499,6 +1508,35 @@ fn merge_vp_results( )) } +/// Insert MASP event data into the provided [`BatchedTxResult`]. +fn insert_masp_events( + batched_tx_result: &mut BatchedTxResult, + wrapper_tx_hash: TxHashAttr, + inner_tx_hash: InnerTxHashAttr, + tx_index: IndexedTx, + masp_tx_kind: MaspTxKind, + masp_tx_ref: MaspTxRef, + fmd_ref: FmdSectionRef, +) { + batched_tx_result.events.extend([ + MaspEvent::ShieldedOutput { + tx_index, + kind: masp_tx_kind, + data: masp_tx_ref, + } + .with(TxHashAttr(wrapper_tx_hash.0)) + .with(InnerTxHashAttr(inner_tx_hash.0)) + .into(), + MaspEvent::FlagCiphertexts { + tx_index, + section: fmd_ref, + } + .with(TxHashAttr(wrapper_tx_hash.0)) + .with(InnerTxHashAttr(inner_tx_hash.0)) + .into(), + ]); +} + #[cfg(test)] mod tests { use eyre::Result; diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 53f173c2bf0..7a148431c26 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -19,7 +19,7 @@ use namada_ibc::{IbcMessage, decode_message, extract_masp_tx_from_envelope}; use namada_io::client::Client; use namada_token::masp::shielded_wallet::ShieldedQueries; pub use namada_token::masp::{utils, *}; -use namada_tx::event::{MaspEvent, MaspEventKind, MaspTxRef}; +use namada_tx::event::{MaspTxEvent, MaspTxKind, MaspTxRef}; use namada_tx::{IndexedTx, Tx}; pub use utilities::{IndexerMaspClient, LedgerMaspClient}; @@ -92,10 +92,10 @@ fn extract_masp_tx( } // Retrieves all the masp events at the specified height. -async fn get_indexed_masp_events_at_height( +async fn get_indexed_masp_txs_at_height( client: &C, height: BlockHeight, -) -> Result, Error> { +) -> Result, Error> { let maybe_masp_events: Result, Error> = client .block_results(height.0) .await @@ -111,9 +111,9 @@ async fn get_indexed_masp_events_at_height( }; let kind = if kind == namada_tx::event::masp_types::TRANSFER { - MaspEventKind::Transfer + MaspTxKind::Transfer } else if kind == namada_tx::event::masp_types::FEE_PAYMENT { - MaspEventKind::FeePayment + MaspTxKind::FeePayment } else { return Ok(None); }; @@ -125,7 +125,7 @@ async fn get_indexed_masp_events_at_height( let tx_index = IndexedTx::read_from_event_attributes(&event.attributes)?; - Ok(Some(MaspEvent { + Ok(Some(MaspTxEvent { tx_index, kind, data, diff --git a/crates/sdk/src/masp/utilities.rs b/crates/sdk/src/masp/utilities.rs index 0a7c31052fd..fe1906f022e 100644 --- a/crates/sdk/src/masp/utilities.rs +++ b/crates/sdk/src/masp/utilities.rs @@ -18,12 +18,12 @@ use namada_token::masp::utils::{ IndexedNoteEntry, MaspClient, MaspClientCapabilities, MaspIndexedTx, MaspTxKind, }; -use namada_tx::event::MaspEvent; +use namada_tx::event::MaspTxEvent; use namada_tx::{IndexedTx, Tx}; use tokio::sync::Semaphore; use crate::error::{Error, QueryError}; -use crate::masp::{extract_masp_tx, get_indexed_masp_events_at_height}; +use crate::masp::{extract_masp_tx, get_indexed_masp_txs_at_height}; struct LedgerMaspClientInner { client: C, @@ -82,7 +82,7 @@ impl LedgerMaspClient { for height in from.0..=to.0 { let maybe_txs_results = async { - get_indexed_masp_events_at_height( + get_indexed_masp_txs_at_height( &self.inner.client, height.into(), ) @@ -109,7 +109,7 @@ impl LedgerMaspClient { // Cache the last tx seen to avoid multiple deserializations let mut last_tx: Option<(Tx, TxIndex)> = None; - for MaspEvent { + for MaspTxEvent { tx_index, kind, data, @@ -133,7 +133,7 @@ impl LedgerMaspClient { txs.push(( MaspIndexedTx { indexed_tx: tx_index, - kind: kind.into(), + kind, }, extracted_masp_tx, )); diff --git a/crates/shielded_token/src/masp/shielded_sync/utils.rs b/crates/shielded_token/src/masp/shielded_sync/utils.rs index f88e4c55546..b263bb8bab0 100644 --- a/crates/shielded_token/src/masp/shielded_sync/utils.rs +++ b/crates/shielded_token/src/masp/shielded_sync/utils.rs @@ -12,42 +12,9 @@ use namada_core::chain::BlockHeight; use namada_core::collections::HashMap; use namada_state::TxIndex; use namada_tx::IndexedTx; -use namada_tx::event::MaspEventKind; +pub use namada_tx::event::MaspTxKind; use serde::{Deserialize, Serialize}; -/// The type of a MASP transaction -#[derive( - Debug, - Default, - Clone, - Copy, - BorshSerialize, - BorshDeserialize, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - Hash, -)] -pub enum MaspTxKind { - /// A MASP transaction used for fee payment - FeePayment, - /// A general MASP transfer - #[default] - Transfer, -} - -impl From for MaspTxKind { - fn from(value: MaspEventKind) -> Self { - match value { - MaspEventKind::FeePayment => Self::FeePayment, - MaspEventKind::Transfer => Self::Transfer, - } - } -} - /// An indexed masp tx carrying information on whether it was a fee paying tx or /// a normal transfer #[derive( diff --git a/crates/tx/src/event.rs b/crates/tx/src/event.rs index 4240076b4d3..7f756321871 100644 --- a/crates/tx/src/event.rs +++ b/crates/tx/src/event.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use std::str::FromStr; use namada_core::borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::hash::Hash; use namada_core::ibc::IbcTxDataHash; use namada_core::masp::MaspTxId; use namada_events::extend::{ @@ -112,9 +113,13 @@ pub mod masp_types { /// General MASP transfer event pub const TRANSFER: EventType = namada_events::event_type!(MaspEvent, "transfer"); + + /// FMD flag ciphertexts event + pub const FLAG_CIPHERTEXTS: EventType = + namada_events::event_type!(MaspEvent, "flag"); } -/// MASP event kind +/// The type of a MASP transaction #[derive( Debug, Default, @@ -130,7 +135,7 @@ pub mod masp_types { Deserialize, Hash, )] -pub enum MaspEventKind { +pub enum MaspTxKind { /// A MASP transaction used for fee payment FeePayment, /// A general MASP transfer @@ -138,24 +143,43 @@ pub enum MaspEventKind { Transfer, } -impl From<&MaspEventKind> for EventType { - fn from(masp_event_kind: &MaspEventKind) -> Self { - match masp_event_kind { - MaspEventKind::FeePayment => masp_types::FEE_PAYMENT, - MaspEventKind::Transfer => masp_types::TRANSFER, +impl From for EventType { + fn from(kind: MaspTxKind) -> Self { + match kind { + MaspTxKind::FeePayment => masp_types::FEE_PAYMENT, + MaspTxKind::Transfer => masp_types::TRANSFER, } } } -impl From for EventType { - fn from(masp_event_kind: MaspEventKind) -> Self { - (&masp_event_kind).into() +/// Represents a reference to an FMD flag ciphertext. +/// +/// Store either in an IBC packet memo, or a Namada tx section. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum FmdSectionRef { + /// Reference to a flag ciphertext tx section. + FmdSection(Hash), + /// Reference to an IBC tx data section. + IbcData(IbcTxDataHash), +} + +impl Display for FmdSectionRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", serde_json::to_string(self).unwrap()) + } +} + +impl FromStr for FmdSectionRef { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) } } /// A type representing the possible reference to some MASP data, either a masp /// section or ibc tx data -#[derive(Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum MaspTxRef { /// Reference to a MASP section MaspSection(MaspTxId), @@ -177,30 +201,78 @@ impl FromStr for MaspTxRef { } } -/// A list of MASP tx references -#[derive(Default, Clone, Serialize, Deserialize)] -pub struct MaspTxRefs(pub Vec<(IndexedTx, MaspTxRef)>); +/// MASP transaction event +#[derive(Debug, Clone)] +pub enum MaspEvent { + /// Emit emitted upon generating a new shielded output + ShieldedOutput { + /// The indexed transaction that generated this event + tx_index: IndexedTx, + /// A flag signaling the type of the MASP transaction + kind: MaspTxKind, + /// The reference to the masp data + data: MaspTxRef, + }, + /// Emit emitted after flagging a new shielded output + /// + /// Generally follows the creation of [`Self::ShieldedOutput`] + FlagCiphertexts { + /// The indexed transaction that generated this event + tx_index: IndexedTx, + /// The tx section hash of the FMD flag ciphertext + section: FmdSectionRef, + }, +} /// MASP transaction event -pub struct MaspEvent { +#[derive(Debug, Clone)] +pub struct MaspTxEvent { /// The indexed transaction that generated this event pub tx_index: IndexedTx, - /// A flag signaling the type of the masp transaction - pub kind: MaspEventKind, + /// A flag signaling the type of the MASP transaction + pub kind: MaspTxKind, /// The reference to the masp data pub data: MaspTxRef, } +impl From for MaspEvent { + fn from( + MaspTxEvent { + tx_index, + kind, + data, + }: MaspTxEvent, + ) -> Self { + Self::ShieldedOutput { + tx_index, + kind, + data, + } + } +} + impl EventToEmit for MaspEvent { const DOMAIN: &'static str = "masp"; } impl From for Event { fn from(masp_event: MaspEvent) -> Self { - Self::new(masp_event.kind.into(), EventLevel::Tx) - .with(masp_event.data) - .with(masp_event.tx_index) - .into() + match masp_event { + MaspEvent::ShieldedOutput { + tx_index, + kind, + data, + } => Self::new(kind.into(), EventLevel::Tx) + .with(data) + .with(tx_index) + .into(), + MaspEvent::FlagCiphertexts { tx_index, section } => { + Self::new(masp_types::FLAG_CIPHERTEXTS, EventLevel::Tx) + .with(section) + .with(tx_index) + .into() + } + } } } @@ -225,3 +297,14 @@ impl EventAttributeEntry<'static> for IndexedTx { self } } + +impl EventAttributeEntry<'static> for FmdSectionRef { + type Value = Self; + type ValueOwned = Self; + + const KEY: &'static str = "ciphertext"; + + fn into_value(self) -> Self::Value { + self + } +} From d7d744cb6c404829969f36b6d46424aa682bcbb7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 14 May 2025 15:35:20 +0100 Subject: [PATCH 36/42] Fix MASP integration test gas costs --- crates/tests/src/integration/masp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index a252a605402..e5f1b8b8cb3 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -748,7 +748,7 @@ fn values_spanning_multiple_masp_digits() -> Result<()> { "--node", RPC, "--gas-limit", - "65000", + "75000", ]), ) }); @@ -867,7 +867,7 @@ fn values_spanning_multiple_masp_digits() -> Result<()> { "--gas-spending-key", C_SPENDING_KEY, "--gas-limit", - "65000", + "75000", ]), ) }); From 113d025b05f0abf989b060733ab99344573a3616 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 May 2025 10:30:02 +0100 Subject: [PATCH 37/42] Fix benchmarks --- crates/node/src/bench_utils.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index fc9900cf1d2..ef073d55814 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -1285,7 +1285,7 @@ impl BenchShieldedCtx { token: address::testing::nam(), amount: denominated_amount, }; - let shielded = async_runtime + let (shielded, fmd_flags) = async_runtime .block_on(async { let expiration = Namada::tx_builder(&namada).expiration.to_datetime(); @@ -1307,14 +1307,12 @@ impl BenchShieldedCtx { masp_tx, metadata: _, epoch: _, - fmd_flags: _, - }| masp_tx, + fmd_flags, + }| (masp_tx, fmd_flags), ) .expect("MASP must have shielded part"); - let fmd_section = Section::Data(Data::from_borsh_encoded(&vec![ - FlagCiphertext::default(), - ])); + let fmd_section = Section::Data(Data::from_borsh_encoded(&fmd_flags)); let shielded_data = MaspTxData { masp_tx_id: shielded.txid().into(), flag_ciphertext_sechash: fmd_section.get_hash(), @@ -1431,9 +1429,16 @@ impl BenchShieldedCtx { vec![vectorized_transfer.targets.into_iter().next().unwrap()] .into_iter() .collect(); - let fmd_section = Section::Data(Data::from_borsh_encoded(&vec![ - FlagCiphertext::default(), - ])); + let masp_tx = tx.tx.get_masp_section(&masp_tx_id).unwrap().clone(); + let fmd_section = Section::Data(Data::from_borsh_encoded( + &std::iter::repeat_with(FlagCiphertext::default) + .take( + masp_tx + .sapling_bundle() + .map_or(0, |x| x.shielded_outputs.len()), + ) + .collect::>(), + )); let transfer = Transfer { sources, targets, @@ -1442,7 +1447,6 @@ impl BenchShieldedCtx { flag_ciphertext_sechash: fmd_section.get_hash(), }), }; - let masp_tx = tx.tx.get_masp_section(&masp_tx_id).unwrap().clone(); let msg = MsgTransfer:: { message: msg, transfer: Some(transfer), From aef297b4dee97b34aa1c000a2fa7540b606182c8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 19 May 2025 11:05:10 +0100 Subject: [PATCH 38/42] Store FMD flag ciphertexts in extra data secs --- crates/node/src/bench_utils.rs | 17 +++++++++++------ crates/sdk/src/lib.rs | 8 +++----- crates/sdk/src/tx.rs | 2 +- crates/token/src/lib.rs | 4 ++-- crates/tx/src/section.rs | 17 ++++++++++++++++- crates/tx/src/types.rs | 21 +++++++++++++++++++-- 6 files changed, 52 insertions(+), 17 deletions(-) diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index ef073d55814..e3ec89f1056 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -192,7 +192,7 @@ impl BenchShellInner { if let Some(sections) = extra_sections { for section in sections { - if let Section::ExtraData(_) | Section::Data(_) = section { + if let Section::ExtraData(_) = section { tx.add_section(section); } } @@ -1098,9 +1098,13 @@ impl Client for BenchShell { }); let masp_fmd_event = tx.sections.iter().find_map(|section| match section { - sec @ Section::Data(Data { data, .. }) - if >::try_from_slice(data) - .is_ok() => + sec @ Section::ExtraData(extra_data) + if extra_data.id().is_some_and(|extra_data| { + >::try_from_slice( + extra_data, + ) + .is_ok() + }) => { Some(AbciEvent::from(Event::from( MaspEvent::FlagCiphertexts { @@ -1312,7 +1316,8 @@ impl BenchShieldedCtx { ) .expect("MASP must have shielded part"); - let fmd_section = Section::Data(Data::from_borsh_encoded(&fmd_flags)); + let fmd_section = + Section::ExtraData(Code::from_borsh_encoded(&fmd_flags)); let shielded_data = MaspTxData { masp_tx_id: shielded.txid().into(), flag_ciphertext_sechash: fmd_section.get_hash(), @@ -1430,7 +1435,7 @@ impl BenchShieldedCtx { .into_iter() .collect(); let masp_tx = tx.tx.get_masp_section(&masp_tx_id).unwrap().clone(); - let fmd_section = Section::Data(Data::from_borsh_encoded( + let fmd_section = Section::ExtraData(Code::from_borsh_encoded( &std::iter::repeat_with(FlagCiphertext::default) .take( masp_tx diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index a01067024e9..f63524f9d7c 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -901,7 +901,7 @@ pub mod testing { arb_withdraw, }; use crate::tx::{ - Authorization, Code, Commitment, Data, Header, MaspBuilder, Section, + Authorization, Code, Commitment, Header, MaspBuilder, Section, TxCommitments, }; @@ -1146,10 +1146,8 @@ pub mod testing { if let Some((shielded_transfer, asset_types, build_params)) = aux { let shielded_section_hash = tx.add_masp_tx_section(shielded_transfer.masp_tx).1; - tx.add_section( - Section::Data( - Data::from_borsh_encoded(&shielded_transfer.fmd_flags), - ), + tx.add_fmd_flag_ciphertexts( + &shielded_transfer.fmd_flags, ); tx.add_masp_builder(MaspBuilder { asset_types: asset_types.into_keys().collect(), diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index edce82f7187..591ecd71618 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -4351,7 +4351,7 @@ fn proposal_to_vec(proposal: OnChainProposal) -> Result> { } fn create_fmd_section(fmd_flags: Vec) -> (Section, Hash) { - let fmd_section = Section::Data(Data::from_borsh_encoded(&fmd_flags)); + let fmd_section = Section::ExtraData(Code::from_borsh_encoded(&fmd_flags)); let fmd_sechash = fmd_section.get_hash(); (fmd_section, fmd_sechash) diff --git a/crates/token/src/lib.rs b/crates/token/src/lib.rs index d91173cbaca..70dc5b78a63 100644 --- a/crates/token/src/lib.rs +++ b/crates/token/src/lib.rs @@ -536,8 +536,8 @@ pub mod testing { .take(builder.sapling_outputs().len()) .collect(); let fmd_sechash = { - let sec = namada_tx::Section::Data( - namada_tx::Data::from_borsh_encoded(&fmd_flags), + let sec = namada_tx::Section::ExtraData( + namada_tx::Code::from_borsh_encoded(&fmd_flags), ); sec.get_hash() }; diff --git a/crates/tx/src/section.rs b/crates/tx/src/section.rs index d4aeef17597..a6649b17fcd 100644 --- a/crates/tx/src/section.rs +++ b/crates/tx/src/section.rs @@ -277,7 +277,7 @@ impl Data { /// Make a new data section with the given borsh encodable data #[inline] - pub fn from_borsh_encoded(data: &T) -> Self { + pub fn from_borsh_encoded(data: &T) -> Self { Self::new(data.serialize_to_vec()) } @@ -376,6 +376,21 @@ impl Code { } } + /// Return the code data, if it is present verbatim. + pub fn id(&self) -> Option<&[u8]> { + if let Commitment::Id(code) = &self.code { + Some(&code[..]) + } else { + None + } + } + + /// Make a new code section with the given borsh encodable data + #[inline] + pub fn from_borsh_encoded(data: &T) -> Self { + Self::new(data.serialize_to_vec(), None) + } + /// Make a new code section with the given hash pub fn from_hash( hash: namada_core::hash::Hash, diff --git a/crates/tx/src/types.rs b/crates/tx/src/types.rs index cb5ff5bf2d0..1fff8c148ae 100644 --- a/crates/tx/src/types.rs +++ b/crates/tx/src/types.rs @@ -290,6 +290,17 @@ impl Tx { None } + /// Add an FMD flag ciphertext section to the transaction + pub fn add_fmd_flag_ciphertexts( + &mut self, + flag_ciphertexts: &[FlagCiphertext], + ) -> &mut Self { + self.add_section(Section::ExtraData(Code::from_borsh_encoded( + flag_ciphertexts, + ))); + self + } + /// Get the FMD flag ciphertext with the given hash pub fn get_fmd_flag_ciphertexts( &self, @@ -297,8 +308,8 @@ impl Tx { ) -> Result>, DecodeError> { let maybe_section = self.get_section(hash); - let data = match maybe_section.as_ref().map(Cow::as_ref) { - Some(Section::Data(Data { data, .. })) => data, + let code = match maybe_section.as_ref().map(Cow::as_ref) { + Some(Section::ExtraData(code)) => code, Some(_) => { return Err(DecodeError::InvalidFlagCiphertexts( HEXUPPER.encode(&hash.0), @@ -307,6 +318,12 @@ impl Tx { None => return Ok(None), }; + let Some(data) = code.id() else { + return Err(DecodeError::InvalidFlagCiphertexts( + HEXUPPER.encode(&hash.0), + )); + }; + let decoded = BorshDeserialize::try_from_slice(data).map_err(|_err| { DecodeError::InvalidFlagCiphertexts(HEXUPPER.encode(&hash.0)) From 8207fcd4135adab144107a2f17523984f6de5100 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 21 May 2025 15:58:01 +0200 Subject: [PATCH 39/42] Add fmd secret key hashes to the wallet when adding viewing keys. Added functionality to query them as well --- crates/core/src/key/mod.rs | 87 +++++++++++++++++++++++++++++++++++++ crates/core/src/masp/fmd.rs | 18 ++++++++ crates/wallet/src/lib.rs | 11 +++++ crates/wallet/src/store.rs | 30 +++++++++++++ 4 files changed, 146 insertions(+) diff --git a/crates/core/src/key/mod.rs b/crates/core/src/key/mod.rs index 330ff64ce7b..22f01be795d 100644 --- a/crates/core/src/key/mod.rs +++ b/crates/core/src/key/mod.rs @@ -364,6 +364,93 @@ impl From<&PK> for PublicKeyHash { } } +const FMD_KEY_HASH_LEN: usize = 32; +const FMD_HASH_HEX_LEN: usize = 64; + +/// Key hash derived of a json serialized FMD secret key +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive( + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Default, +)] +pub struct FmdKeyHash(pub(crate) [u8; FMD_KEY_HASH_LEN]); + +impl serde::Serialize for FmdKeyHash { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let encoded = self.to_string(); + serde::Serialize::serialize(&encoded, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for FmdKeyHash { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let encoded: String = serde::Deserialize::deserialize(deserializer)?; + Self::from_str(&encoded).map_err(D::Error::custom) + } +} + +impl Display for FmdKeyHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", HEXUPPER.encode(&self.0)) + } +} + +impl FromStr for FmdKeyHash { + type Err = FmdhFromStringError; + + fn from_str(s: &str) -> Result { + if s.len() != FMD_HASH_HEX_LEN { + return Err(Self::Err::UnexpectedLen(s.len())); + } + let raw_bytes = HEXUPPER + .decode(s.as_bytes()) + .map_err(Self::Err::DecodeUpperHex)?; + let mut bytes: [u8; FMD_KEY_HASH_LEN] = Default::default(); + bytes.copy_from_slice(&raw_bytes); + Ok(Self(bytes)) + } +} + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum FmdhFromStringError { + #[error("Wrong FMD hash len. Expected {FMD_HASH_HEX_LEN}, got {0}")] + UnexpectedLen(usize), + #[error("Failed decoding upper hex with {0}")] + DecodeUpperHex(data_encoding::DecodeError), +} + +impl From<&polyfuzzy::FmdSecretKey> for FmdKeyHash { + fn from(sk: &polyfuzzy::FmdSecretKey) -> Self { + let mut hasher = sha2::Sha256::new(); + hasher.update(serde_json::to_string(sk).unwrap().as_bytes()); + Self(hasher.finalize().into()) + } +} + +impl From for FmdKeyHash { + fn from(sk: polyfuzzy::FmdSecretKey) -> Self { + Self::from(&sk) + } +} + /// Derive Tendermint raw hash from the public key pub trait PublicKeyTmRawHash { /// Derive Tendermint raw hash from the public key diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 20d9efc9a94..97609e45f51 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -4,6 +4,7 @@ use std::collections::BTreeMap; use std::io; use std::ops::Deref; +use ::polyfuzzy::KeyExpansion; use borsh::schema::Definition; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use masp_primitives::sapling::SaplingIvk; @@ -13,7 +14,10 @@ use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use tiny_keccak::{Hasher, IntoXof, KangarooTwelve, Xof}; +use crate::masp::ExtendedViewingKey; + mod polyfuzzy { + pub(super) use ::polyfuzzy::FmdSecretKey; #[cfg(feature = "rand")] pub(super) use ::polyfuzzy::MultiFmdScheme; pub(super) use ::polyfuzzy::fmd2_compact::*; @@ -209,6 +213,14 @@ impl SecretKey { inner: self.inner.var_randomized_public_key(diversifier), } } + + /// Expand this compact key to a full FMD secret key. + pub fn fmd_secret_key(&self) -> polyfuzzy::FmdSecretKey { + let cpk_key = self.inner.master_public_key(); + let mut scheme = + polyfuzzy::MultiFmd2CompactScheme::new(parameters::GAMMA, 1); + scheme.expand_keypair(&self.inner, &cpk_key).0 + } } impl AsRef for SecretKey { @@ -258,6 +270,12 @@ impl From for SecretKey { } } +impl From<&ExtendedViewingKey> for SecretKey { + fn from(evk: &ExtendedViewingKey) -> Self { + Self::from(evk.as_viewing_key().ivk()) + } +} + /// FMD flag ciphertexts. //#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] diff --git a/crates/wallet/src/lib.rs b/crates/wallet/src/lib.rs index 983bdc2ca96..6e4106bed52 100644 --- a/crates/wallet/src/lib.rs +++ b/crates/wallet/src/lib.rs @@ -443,6 +443,17 @@ impl Wallet { }) } + /// Find the hash of an FMD secret key from the alias of the viewing + /// key it was derived from. + pub fn find_fmd_key_hash( + &self, + alias: impl AsRef, + ) -> Result<&FmdKeyHash, FindKeyError> { + self.store.find_fmd_key_hash(alias.as_ref()).ok_or_else(|| { + FindKeyError::KeyNotFound(alias.as_ref().to_string()) + }) + } + /// Find the birthday of the given alias pub fn find_birthday( &self, diff --git a/crates/wallet/src/store.rs b/crates/wallet/src/store.rs index fa3598d5d3b..bcc1c04390f 100644 --- a/crates/wallet/src/store.rs +++ b/crates/wallet/src/store.rs @@ -85,6 +85,9 @@ pub struct Store { /// Known mappings of public key hashes to their aliases in the `keys` /// field. Used for look-up by a public key. pkhs: BTreeMap, + /// Known mappings of FMD secret key hashes to their aliases in the + /// `viewing_keys` field. + fmdhs: BTreeMap, /// Special keys if the wallet belongs to a validator pub(crate) validator_data: Option, /// Namada address vp type @@ -152,6 +155,15 @@ impl Store { self.view_keys.get(&alias.into()) } + /// Find the hash of an FMD secret key from the alias of the viewing + /// key it was derived from. + pub fn find_fmd_key_hash( + &self, + alias: impl AsRef, + ) -> Option<&FmdKeyHash> { + self.fmdhs.get(&alias.into()) + } + /// Find the birthday of the given alias pub fn find_birthday( &self, @@ -448,6 +460,9 @@ impl Store { } /// Insert viewing keys similarly to how it's done for keypairs + /// + /// Hashes of the FMD secret keys associated with a viewing key will + /// also be added to a map associating it with the alias provided. pub fn insert_viewing_key( &mut self, alias: Alias, @@ -479,6 +494,9 @@ impl Store { } self.remove_alias(&alias); birthday.map(|x| self.birthdays.insert(alias.clone(), x)); + let fmd_key = + namada_core::masp::FmdSecretKey::from(&viewkey).fmd_secret_key(); + self.fmdhs.insert(alias.clone(), fmd_key.into()); self.view_keys.insert(alias.clone(), viewkey); path.map(|p| self.derivation_paths.insert(alias.clone(), p)); Some(alias) @@ -673,6 +691,7 @@ impl Store { derivation_paths, addresses, pkhs, + fmdhs, validator_data: _, address_vp_types, } = self; @@ -686,6 +705,7 @@ impl Store { derivation_paths.extend(store.derivation_paths); addresses.extend(store.addresses); pkhs.extend(store.pkhs); + fmdhs.extend(store.fmdhs); address_vp_types.extend(store.address_vp_types); } @@ -923,6 +943,16 @@ impl From for Store { derivation_paths: store.derivation_paths, addresses: store.addresses, pkhs: store.pkhs, + fmdhs: store + .view_keys + .iter() + .map(|(alias, vk)| { + let fmd_key = + namada_core::masp::FmdSecretKey::from(&vk.key) + .fmd_secret_key(); + (alias.clone(), fmd_key.into()) + }) + .collect(), validator_data: store.validator_data, address_vp_types: store.address_vp_types, ..Store::default() From ecca971a15494fdb264b291258685db295431326 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 23 May 2025 10:28:19 +0200 Subject: [PATCH 40/42] Added FMD management commands to the cli. --- Cargo.lock | 226 ++++++++++---- Cargo.toml | 3 +- crates/apps_lib/Cargo.toml | 1 + crates/apps_lib/src/cli.rs | 87 ++++++ crates/apps_lib/src/cli/client.rs | 24 ++ crates/apps_lib/src/client/masp.rs | 43 +++ crates/core/src/masp.rs | 2 +- crates/core/src/masp/fmd.rs | 2 + crates/node/Cargo.toml | 1 + crates/node/src/bench_utils.rs | 18 ++ crates/sdk/src/args.rs | 33 +++ crates/shielded_token/Cargo.toml | 1 + crates/shielded_token/src/masp.rs | 29 +- wasm/Cargo.lock | 335 ++++++++++++++++++--- wasm_for_tests/Cargo.lock | 453 ++++++++++++++++++++++++++--- 15 files changed, 1126 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 794ff3d62bc..99f5bb7b9db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -985,7 +985,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -1132,18 +1132,19 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.29" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.5.29" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstream", "anstyle", @@ -1170,6 +1171,18 @@ dependencies = [ "clap_complete", ] +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "clap_lex" version = "0.7.4" @@ -1188,6 +1201,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.12", +] + [[package]] name = "codepage" version = "0.1.2" @@ -1684,6 +1706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -2140,7 +2163,7 @@ dependencies = [ "chrono", "rust_decimal", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "winnow 0.6.26", ] @@ -2309,7 +2332,7 @@ dependencies = [ "byteorder", "heapless 0.8.0", "num-traits", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -2994,7 +3017,7 @@ dependencies = [ "rand_core", "serde", "serdect", - "thiserror 2.0.11", + "thiserror 2.0.12", "thiserror-nostd-notrait", "visibility", "zeroize", @@ -3478,6 +3501,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -4779,6 +4811,53 @@ dependencies = [ "signature", ] +[[package]] +name = "kassandra-client" +version = "0.0.11-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e59f26661380a4758cc7c7a0b8887218677ee76be33a279205fb9cee9a4a492" +dependencies = [ + "chacha20poly1305", + "clap", + "curve25519-dalek", + "hex", + "hkdf", + "kassandra-shared", + "polyfuzzy", + "rand_core", + "serde", + "serde_cbor", + "serde_json", + "sha2 0.10.8", + "thiserror 2.0.12", + "toml", + "tracing", + "tracing-log", + "tracing-subscriber", + "x25519-dalek", +] + +[[package]] +name = "kassandra-shared" +version = "0.0.3-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d0b36e1bea0a4147d47b33e8770253f6101d6efd3f0a89f522b999947ce834" +dependencies = [ + "borsh", + "chacha20poly1305", + "cobs 0.3.0", + "hex", + "once_cell", + "polyfuzzy", + "rand_core", + "serde", + "serde_cbor", + "sha2 0.10.8", + "tdx-quote", + "thiserror 2.0.12", + "x25519-dalek", +] + [[package]] name = "kdam" version = "0.6.2" @@ -5387,7 +5466,7 @@ dependencies = [ "nam-ledger-proto", "once_cell", "strum 0.26.3", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", "tracing-subscriber", @@ -5422,7 +5501,7 @@ dependencies = [ "displaydoc", "encdec", "num_enum", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -5449,7 +5528,7 @@ dependencies = [ "pasta_curves", "rand_core", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "zeroize", ] @@ -5547,6 +5626,7 @@ dependencies = [ "flate2", "futures", "itertools 0.14.0", + "kassandra-client", "kdam", "lazy_static", "ledger-transport", @@ -5577,7 +5657,7 @@ dependencies = [ "tendermint-config", "tendermint-rpc", "textwrap-macros", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "toml", "tracing", @@ -5616,7 +5696,7 @@ version = "0.149.1" dependencies = [ "namada_core", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -5664,7 +5744,7 @@ dependencies = [ "smooth-operator", "tendermint 0.40.3", "tendermint-proto 0.40.3", - "thiserror 2.0.11", + "thiserror 2.0.12", "tiny-keccak", "tokio", "toml", @@ -5725,7 +5805,7 @@ dependencies = [ "rand", "serde", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "toml", "tracing", ] @@ -5741,7 +5821,7 @@ dependencies = [ "namada_migrations", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -5797,7 +5877,7 @@ dependencies = [ "namada_migrations", "proptest", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -5829,7 +5909,7 @@ dependencies = [ "serde", "serde_json", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -5874,7 +5954,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -5886,7 +5966,7 @@ dependencies = [ "kdam", "namada_core", "tendermint-rpc", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", ] @@ -5931,7 +6011,7 @@ dependencies = [ "namada_migrations", "proptest", "prost 0.13.5", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -5966,6 +6046,7 @@ dependencies = [ "eyre", "futures", "itertools 0.14.0", + "kassandra-client", "lazy_static", "linkme", "masp_primitives", @@ -5999,7 +6080,7 @@ dependencies = [ "tar", "tempfile", "test-log", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tokio-test", "toml", @@ -6022,7 +6103,7 @@ dependencies = [ "namada_tx", "namada_vp_env", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6055,7 +6136,7 @@ dependencies = [ "serde", "smooth-operator", "test-log", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "tracing-subscriber", "yansi 1.0.1", @@ -6135,7 +6216,7 @@ dependencies = [ "smooth-operator", "tempfile", "tendermint-rpc", - "thiserror 2.0.11", + "thiserror 2.0.12", "tiny-bip39", "tokio", "toml", @@ -6154,6 +6235,7 @@ dependencies = [ "flume", "futures", "itertools 0.14.0", + "kassandra-client", "lazy_static", "linkme", "masp_primitives", @@ -6189,7 +6271,7 @@ dependencies = [ "tempfile", "tendermint-rpc", "test-log", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", "typed-builder", @@ -6222,7 +6304,7 @@ dependencies = [ "proptest", "smooth-operator", "test-log", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -6242,7 +6324,7 @@ dependencies = [ "regex", "serde", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -6368,7 +6450,7 @@ dependencies = [ "namada_vp", "namada_vp_env", "proptest", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -6402,7 +6484,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "thiserror 2.0.11", + "thiserror 2.0.12", "tonic-build", ] @@ -6460,7 +6542,7 @@ dependencies = [ "smooth-operator", "tempfile", "test-log", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "wasm-instrument", "wasmer", @@ -6504,7 +6586,7 @@ dependencies = [ "namada_tx", "namada_vp_env", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -6568,7 +6650,7 @@ dependencies = [ "serde", "slip10_ed25519", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tiny-bip39", "toml", "zeroize", @@ -6776,9 +6858,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -6908,6 +6990,18 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "pairing" version = "0.23.0" @@ -7108,7 +7202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.11", + "thiserror 2.0.12", "ucd-trie", ] @@ -7330,7 +7424,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" dependencies = [ - "cobs", + "cobs 0.2.3", "embedded-io 0.4.0", "embedded-io 0.6.1", "heapless 0.7.17", @@ -7405,6 +7499,15 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -7739,7 +7842,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -8413,9 +8516,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -8461,9 +8564,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -8483,9 +8586,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -8677,7 +8780,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", ] @@ -9061,6 +9164,17 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "tdx-quote" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecbbeffe2d73f07728bcbb581f8faa9098d813ba0fafbcab5e763a2fa4491e80" +dependencies = [ + "nom", + "p256", + "sha2 0.10.8", +] + [[package]] name = "tempfile" version = "3.16.0" @@ -9360,11 +9474,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -9380,9 +9494,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -11034,6 +11148,18 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + [[package]] name = "xattr" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index fb71a9976ef..0678be66a56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -170,12 +170,12 @@ ibc-middleware-overflow-receive = { version = "0.5.0" } ibc-middleware-packet-forward = { version = "0.10.0", features = ["borsh"] } ibc-testkit = { version = "0.57.0", default-features = false } ics23 = "0.12" -usize-set = { version = "0.10", features = ["serialize-borsh", "serialize-serde"] } impl-num-traits = "0.2" indexmap = { package = "nam-indexmap", version = "2.7.1-nam.0", features = ["borsh-schema", "serde"] } init-once = "0.6" itertools = "0.14" jubjub = { package = "nam-jubjub", version = "0.10.1-nam.0" } +kassandra-client = "0.0.11-alpha" k256 = { version = "0.13", default-features = false, features = ["ecdsa", "pkcs8", "precomputed-tables", "serde", "std"]} kdam = "0.6" konst = { version = "0.3", default-features = false } @@ -263,6 +263,7 @@ tracing-log = "0.2" tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]} typed-builder = "0.20" uint = "0.10" +usize-set = { version = "0.10", features = ["serialize-borsh", "serialize-serde"] } warp = "0.3" wasmparser = "0.121" wasm-instrument = {version = "0.4.0", features = ["sign_ext"]} diff --git a/crates/apps_lib/Cargo.toml b/crates/apps_lib/Cargo.toml index 4d7e6385c82..4a9f797d09e 100644 --- a/crates/apps_lib/Cargo.toml +++ b/crates/apps_lib/Cargo.toml @@ -49,6 +49,7 @@ flate2.workspace = true futures.workspace = true itertools.workspace = true jubjub.workspace = true +kassandra-client.workspace = true kdam.workspace = true lazy_static = { workspace = true, optional = true } linkme = { workspace = true, optional = true } diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index 129be0d0613..50b3ce2af07 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -561,6 +561,7 @@ pub mod cmds { QueryRewards(QueryRewards), QueryIbcRateLimit(QueryIbcRateLimit), ShieldedSync(ShieldedSync), + Fmd(FmdCommand), GenIbcShieldingTransfer(GenIbcShieldingTransfer), } @@ -1630,6 +1631,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct FmdCommand(pub args::FmdCommand); + + impl SubCmd for FmdCommand { + const CMD: &'static str = "fmd"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| FmdCommand(args::FmdCommand::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about(wrap!( + "Key management commands for fuzzy message detection and \ + interacting with Kassandra services." + )) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct Bond(pub args::Bond); @@ -3445,6 +3468,7 @@ pub mod args { pub const ADDRESS: Arg = arg("address"); pub const ADDRESS_OPT: ArgOpt = arg_opt("address"); pub const ADD_PERSISTENT_PEERS: ArgFlag = flag("add-persistent-peers"); + pub const ADD_SERVICE: ArgOpt = arg_opt("add-service"); pub const ALIAS_OPT: ArgOpt = ALIAS.opt(); pub const ALIAS: Arg = arg("alias"); pub const ALIAS_FORCE: ArgFlag = flag("alias-force"); @@ -3508,6 +3532,8 @@ pub mod args { pub const DATA_PATH: Arg = arg("data-path"); pub const DATED_SPENDING_KEYS: ArgMulti = arg_multi("spending-keys"); + pub const DATED_VIEWING_KEY: Arg = + arg("viewing-key"); pub const DATED_VIEWING_KEYS: ArgMulti = arg_multi("viewing-keys"); pub const DB_KEY: Arg = arg("db-key"); @@ -3669,6 +3695,7 @@ pub mod args { pub const RECEIVER: Arg = arg("receiver"); pub const REFUND_TARGET: ArgOpt = arg_opt("refund-target"); + pub const REGISTER_KEY: ArgFlag = flag("register"); pub const RELAYER: Arg
= arg("relayer"); pub const RETRIES: ArgOpt = arg_opt("retries"); pub const SCHEME: ArgDefault = @@ -7148,6 +7175,66 @@ pub mod args { } } + impl Args for FmdCommand { + fn parse(matches: &ArgMatches) -> Self { + let viewing_key = DATED_VIEWING_KEY.parse(matches); + if REGISTER_KEY.parse(matches) { + Self { + command: FmdCommandType::RegisterKey, + viewing_key, + service: None, + } + } else { + Self { + command: FmdCommandType::AddService, + viewing_key, + service: Some( + ADD_SERVICE.parse(matches).expect( + "Adding a Kassandra service requires a url", + ), + ), + } + } + } + + fn def(app: App) -> App { + app.arg( + DATED_VIEWING_KEY + .def() + .help(wrap!("The MASP viewing key being managed.")), + ) + .arg(REGISTER_KEY.def().help(wrap!( + "Register the viewing keys with Kassandra services using the \ + configuration file." + ))) + .arg(ADD_SERVICE.def().help(wrap!( + "Add a the url of a Kassandra service to the configuration \ + file." + ))) + .group( + ArgGroup::new("fmd_command_type") + .args([REGISTER_KEY.name, ADD_SERVICE.name]) + .required(true), + ) + } + } + + impl CliToSdk> for FmdCommand { + type Error = std::convert::Infallible; + + fn to_sdk( + self, + ctx: &mut Context, + ) -> Result, Self::Error> { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + Ok(FmdCommand { + command: self.command, + viewing_key: chain_ctx.get_cached(&self.viewing_key), + service: self.service, + }) + } + } + impl CliToSdk> for GenIbcShieldingTransfer { diff --git a/crates/apps_lib/src/cli/client.rs b/crates/apps_lib/src/cli/client.rs index 0eb92b4766c..e5cb94e5643 100644 --- a/crates/apps_lib/src/cli/client.rs +++ b/crates/apps_lib/src/cli/client.rs @@ -1,8 +1,10 @@ use std::io::Read; use color_eyre::eyre::Result; +use namada_sdk::args::FmdCommandType; use namada_sdk::io::{Io, NamadaIo, display_line}; use namada_sdk::masp::ShieldedContext; +use namada_sdk::masp::fs::FsShieldedUtils; use namada_sdk::wallet::DatedViewingKey; use namada_sdk::{Namada, NamadaImpl}; @@ -386,6 +388,28 @@ impl CliApi { ) .await?; } + Sub::Fmd(FmdCommand(args)) => { + use crate::client::masp::fmd; + let args = args.to_sdk(&mut ctx)?; + let chain_ctx = ctx.take_chain_or_exit(); + match args.command { + FmdCommandType::RegisterKey => { + fmd::register_keys::( + args.viewing_key, + ) + .await; + } + FmdCommandType::AddService => { + fmd::add_service( + ShieldedContext::new(chain_ctx.shielded) + .into(), + args.viewing_key, + args.service.as_ref().unwrap(), + ) + .await + } + } + } Sub::GenIbcShieldingTransfer(GenIbcShieldingTransfer( args, )) => { diff --git a/crates/apps_lib/src/client/masp.rs b/crates/apps_lib/src/client/masp.rs index df2d7e04917..0ea44f20262 100644 --- a/crates/apps_lib/src/client/masp.rs +++ b/crates/apps_lib/src/client/masp.rs @@ -148,3 +148,46 @@ pub async fn syncing< Ok(shielded) } + +/// CLI commands for FMD key management. +pub mod fmd { + use namada_core::key::FmdKeyHash; + use namada_sdk::{ShieldedUtils, ShieldedWallet}; + use namada_wallet::DatedViewingKey; + + /// Add an FMD key derived from the viewing key + /// to the config file. This file will be used + /// for registering and querying data from Kassandra + /// services. + pub async fn add_service( + wallet: ShieldedWallet, + DatedViewingKey { key, .. }: DatedViewingKey, + url: &str, + ) { + let uuid = kassandra_client::get_host_uuid(url); + let fmd_key = + namada_core::masp::FmdSecretKey::from(&key).fmd_secret_key(); + let enc_key = kassandra_client::encryption_key(&fmd_key, &uuid); + let key_hash = FmdKeyHash::from(fmd_key).to_string(); + let mut config = U::fmd_config_load().await.unwrap(); + config.add_service(key_hash, url, enc_key); + wallet.utils.fmd_config_save(&mut config).await.unwrap(); + } + + /// Register FND keys with Kassandra services as specified in the + /// config file. + pub async fn register_keys( + DatedViewingKey { key, birthday }: DatedViewingKey, + ) { + let config = U::fmd_config_load().await.unwrap(); + let fmd_key = + namada_core::masp::FmdSecretKey::from(&key).fmd_secret_key(); + let key_hash = FmdKeyHash::from(&fmd_key).to_string(); + kassandra_client::register_fmd_key( + &config, + key_hash, + &fmd_key, + Some(birthday.0), + ); + } +} diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index a7dc5323f37..cc193a82290 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use sha2::Sha256; pub use self::fmd::{ - FlagCiphertext, PublicKey as FmdPublicKey, + FlagCiphertext, GAMMA, PublicKey as FmdPublicKey, PublicKeyBytes as FmdPublicKeyBytes, SecretKey as FmdSecretKey, }; use crate::address::{Address, DecodeError, HASH_HEX_LEN, IBC, MASP}; diff --git a/crates/core/src/masp/fmd.rs b/crates/core/src/masp/fmd.rs index 97609e45f51..0fc7c7a7a78 100644 --- a/crates/core/src/masp/fmd.rs +++ b/crates/core/src/masp/fmd.rs @@ -63,6 +63,8 @@ mod parameters { } } +pub use parameters::GAMMA; + /// FMD public key bytes. #[derive( Clone, diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index a69b883bcd2..99768bfc6a4 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -64,6 +64,7 @@ ethbridge-events.workspace = true eyre.workspace = true futures.workspace = true itertools.workspace = true +kassandra-client.workspace = true lazy_static = { workspace = true, optional = true } linkme = { workspace = true, optional = true } masp_primitives = { workspace = true, features = ["transparent-inputs"] } diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index e3ec89f1056..6432d4ab6fd 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -147,6 +147,8 @@ const SPECULATIVE_FILE_NAME: &str = "speculative_shielded.dat"; const SPECULATIVE_TMP_FILE_NAME: &str = "speculative_shielded.tmp"; const CACHE_FILE_NAME: &str = "shielded_sync.cache"; const CACHE_FILE_TMP_PREFIX: &str = "shielded_sync.cache.tmp"; +const FMD_CONF_FILE_NAME: &str = "fmd_config.toml"; +const FMD_CONF_FILE_TMP_PREFIX: &str = "fmd_config.toml.tmp"; /// For `tracing_subscriber`, which fails if called more than once in the same /// process @@ -888,6 +890,22 @@ impl ShieldedUtils for BenchShieldedUtils { let mut file = File::open(file_name)?; DispatcherCache::try_from_reader(&mut file) } + + async fn fmd_config_save( + &self, + config: &mut kassandra_client::config::Config, + ) -> std::io::Result<()> { + config.save(FMD_CONF_FILE_TMP_PREFIX)?; + std::fs::rename( + FMD_CONF_FILE_TMP_PREFIX, + self.context_dir.0.path().join(FMD_CONF_FILE_NAME), + ) + } + + async fn fmd_config_load() + -> std::io::Result { + kassandra_client::config::Config::load_or_new(FMD_CONF_FILE_NAME) + } } #[async_trait::async_trait(?Send)] diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index fe6608ee710..c50af479807 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -2520,6 +2520,39 @@ pub struct ShieldedSync { pub retry_strategy: RetryStrategy, } +/// The type of FMD key management command +#[derive(Copy, Clone, Debug)] +pub enum FmdCommandType { + /// Register a key with a Kassandra services configured + RegisterKey, + /// Add a Kassandra service to the configuration file + AddService, +} + +impl FromStr for FmdCommandType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.trim().to_lowercase().as_str() { + "register" => Ok(Self::RegisterKey), + "add" => Ok(Self::AddService), + _ => Err("Could not parse {s} as a valid FMD command".to_string()), + } + } +} + +/// An FMD key management command for interacting with +/// Kassandra services +#[derive(Clone, Debug)] +pub struct FmdCommand { + /// The type of command + pub command: FmdCommandType, + /// The key that is being managed + pub viewing_key: C::DatedViewingKey, + /// A url for a Kassandra service + pub service: Option, +} + /// Query PoS commission rate #[derive(Clone, Debug)] pub struct QueryCommissionRate { diff --git a/crates/shielded_token/Cargo.toml b/crates/shielded_token/Cargo.toml index 5ac9a0dbc1d..d4eb6b147d7 100644 --- a/crates/shielded_token/Cargo.toml +++ b/crates/shielded_token/Cargo.toml @@ -58,6 +58,7 @@ eyre.workspace = true futures.workspace = true flume = { workspace = true, optional = true } itertools.workspace = true +kassandra-client.workspace = true lazy_static.workspace = true linkme = { workspace = true, optional = true } masp_primitives.workspace = true diff --git a/crates/shielded_token/src/masp.rs b/crates/shielded_token/src/masp.rs index 8399bdcc099..9c3601f4821 100644 --- a/crates/shielded_token/src/masp.rs +++ b/crates/shielded_token/src/masp.rs @@ -261,6 +261,16 @@ pub trait ShieldedUtils: /// Load a cache of data as part of shielded sync if that /// process gets interrupted. async fn cache_load(&self) -> std::io::Result; + + /// Save the configuration file for fuzzy message detection + async fn fmd_config_save( + &self, + _config: &mut kassandra_client::config::Config, + ) -> std::io::Result<()>; + + /// Load the fuzzy messaged detection configuration file + async fn fmd_config_load() + -> std::io::Result; } /// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey @@ -992,7 +1002,8 @@ pub mod fs { const SPECULATIVE_TMP_FILE_PREFIX: &str = "speculative_shielded.tmp"; const CACHE_FILE_NAME: &str = "shielded_sync.cache"; const CACHE_FILE_TMP_PREFIX: &str = "shielded_sync.cache.tmp"; - + const FMD_CONF_FILE_NAME: &str = "fmd_config.toml"; + const FMD_CONF_FILE_TMP_PREFIX: &str = "fmd_config.toml.tmp"; #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] /// An implementation of ShieldedUtils for standard filesystems pub struct FsShieldedUtils { @@ -1194,5 +1205,21 @@ pub mod fs { let mut file = File::open(file_name)?; DispatcherCache::try_from_reader(&mut file) } + + async fn fmd_config_save( + &self, + config: &mut kassandra_client::config::Config, + ) -> std::io::Result<()> { + config.save(FMD_CONF_FILE_TMP_PREFIX)?; + std::fs::rename( + FMD_CONF_FILE_TMP_PREFIX, + self.context_dir.join(FMD_CONF_FILE_NAME), + ) + } + + async fn fmd_config_load() + -> std::io::Result { + kassandra_client::config::Config::load_or_new(FMD_CONF_FILE_NAME) + } } } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 426bffd929a..1dfa73b2f0d 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -101,12 +101,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.95" @@ -899,21 +943,36 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.29" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.5.29" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] @@ -934,6 +993,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.12", +] + [[package]] name = "coins-bip32" version = "0.8.7" @@ -986,6 +1054,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "concat-idents" version = "1.1.5" @@ -1281,6 +1355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -1313,6 +1388,7 @@ dependencies = [ "rustc_version", "serde", "subtle", + "zeroize", ] [[package]] @@ -1596,7 +1672,7 @@ dependencies = [ "chrono", "rust_decimal", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "winnow 0.6.26", ] @@ -2355,7 +2431,7 @@ dependencies = [ "rand_core", "serde", "serdect", - "thiserror 2.0.11", + "thiserror 2.0.12", "thiserror-nostd-notrait", "visibility", "zeroize", @@ -2651,6 +2727,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "hash32" version = "0.2.1" @@ -2725,6 +2807,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -3876,6 +3967,12 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.11.0" @@ -3971,6 +4068,53 @@ dependencies = [ "signature", ] +[[package]] +name = "kassandra-client" +version = "0.0.11-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e59f26661380a4758cc7c7a0b8887218677ee76be33a279205fb9cee9a4a492" +dependencies = [ + "chacha20poly1305", + "clap", + "curve25519-dalek", + "hex", + "hkdf", + "kassandra-shared", + "polyfuzzy", + "rand_core", + "serde", + "serde_cbor", + "serde_json", + "sha2 0.10.8", + "thiserror 2.0.12", + "toml", + "tracing", + "tracing-log", + "tracing-subscriber", + "x25519-dalek", +] + +[[package]] +name = "kassandra-shared" +version = "0.0.3-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d0b36e1bea0a4147d47b33e8770253f6101d6efd3f0a89f522b999947ce834" +dependencies = [ + "borsh", + "chacha20poly1305", + "cobs 0.3.0", + "hex", + "once_cell", + "polyfuzzy", + "rand_core", + "serde", + "serde_cbor", + "sha2 0.10.8", + "tdx-quote", + "thiserror 2.0.12", + "x25519-dalek", +] + [[package]] name = "kdam" version = "0.6.2" @@ -4376,7 +4520,7 @@ dependencies = [ "pasta_curves", "rand_core", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "zeroize", ] @@ -4437,7 +4581,7 @@ version = "0.149.1" dependencies = [ "namada_core", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -4481,7 +4625,7 @@ dependencies = [ "smooth-operator", "tendermint 0.40.3", "tendermint-proto 0.40.3", - "thiserror 2.0.11", + "thiserror 2.0.12", "tiny-keccak", "tokio", "tracing", @@ -4516,7 +4660,7 @@ dependencies = [ "namada_vp_env", "serde", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -4529,7 +4673,7 @@ dependencies = [ "namada_macros", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -4542,7 +4686,7 @@ dependencies = [ "namada_events", "namada_macros", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -4564,7 +4708,7 @@ dependencies = [ "serde", "serde_json", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -4600,7 +4744,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -4612,7 +4756,7 @@ dependencies = [ "kdam", "namada_core", "tendermint-rpc", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", ] @@ -4638,7 +4782,7 @@ dependencies = [ "namada_core", "namada_macros", "prost", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -4652,7 +4796,7 @@ dependencies = [ "namada_tx", "namada_vp_env", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -4675,7 +4819,7 @@ dependencies = [ "proptest", "serde", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -4749,7 +4893,7 @@ dependencies = [ "smooth-operator", "tempfile", "tendermint-rpc", - "thiserror 2.0.11", + "thiserror 2.0.12", "tiny-bip39", "tokio", "toml", @@ -4768,6 +4912,7 @@ dependencies = [ "flume", "futures", "itertools 0.14.0", + "kassandra-client", "lazy_static", "masp_primitives", "masp_proofs", @@ -4793,7 +4938,7 @@ dependencies = [ "sha2 0.10.8", "smooth-operator", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "typed-builder", "xorf", @@ -4818,7 +4963,7 @@ dependencies = [ "patricia_tree", "proptest", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -4836,7 +4981,7 @@ dependencies = [ "regex", "serde", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -4925,7 +5070,7 @@ dependencies = [ "namada_tx", "namada_tx_env", "namada_vp_env", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -4954,7 +5099,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "thiserror 2.0.11", + "thiserror 2.0.12", "tonic-build", ] @@ -5007,7 +5152,7 @@ dependencies = [ "rayon", "smooth-operator", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "wasm-instrument", "wasmer", @@ -5046,7 +5191,7 @@ dependencies = [ "namada_tx", "namada_vp_env", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -5107,7 +5252,7 @@ dependencies = [ "serde", "slip10_ed25519", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tiny-bip39", "toml", "zeroize", @@ -5167,6 +5312,16 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -5277,9 +5432,18 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2611b99ab098a31bdc8be48b4f1a285ca0ced28bd5b4f23e45efa8c63b09efa5" +dependencies = [ + "once_cell", +] [[package]] name = "opaque-debug" @@ -5375,12 +5539,30 @@ dependencies = [ "zeroize", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owo-colors" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "pairing" version = "0.23.0" @@ -5724,7 +5906,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" dependencies = [ - "cobs", + "cobs 0.2.3", "embedded-io 0.4.0", "embedded-io 0.6.1", "heapless", @@ -5762,6 +5944,15 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -6623,9 +6814,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -6659,11 +6850,21 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -6683,9 +6884,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -6865,7 +7066,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", ] @@ -7204,6 +7405,17 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "tdx-quote" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecbbeffe2d73f07728bcbb581f8faa9098d813ba0fafbcab5e763a2fa4491e80" +dependencies = [ + "nom", + "p256", + "sha2 0.10.8", +] + [[package]] name = "tempfile" version = "3.16.0" @@ -7435,11 +7647,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -7455,9 +7667,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -7828,6 +8040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", ] [[package]] @@ -7840,6 +8053,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" @@ -7847,12 +8071,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", + "tracing-log", ] [[package]] @@ -8275,6 +8502,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "0.8.2" @@ -8291,6 +8524,12 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -9046,6 +9285,18 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + [[package]] name = "xorf" version = "0.11.0" diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index c07ae3be193..15b523813cd 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -76,6 +76,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.95" @@ -573,6 +623,46 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clap" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "clru" version = "0.6.2" @@ -585,6 +675,21 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.12", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "const-crc32-nostd" version = "1.3.1" @@ -727,6 +832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -744,6 +850,7 @@ dependencies = [ "rustc_version", "serde", "subtle", + "zeroize", ] [[package]] @@ -1209,7 +1316,7 @@ dependencies = [ "rand_core", "serde", "serdect", - "thiserror 2.0.11", + "thiserror 2.0.12", "thiserror-nostd-notrait", "visibility", "zeroize", @@ -1369,6 +1476,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "hash32" version = "0.2.1" @@ -1422,6 +1535,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2166,6 +2288,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.13.0" @@ -2228,6 +2356,53 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "kassandra-client" +version = "0.0.11-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e59f26661380a4758cc7c7a0b8887218677ee76be33a279205fb9cee9a4a492" +dependencies = [ + "chacha20poly1305", + "clap", + "curve25519-dalek", + "hex", + "hkdf", + "kassandra-shared", + "polyfuzzy", + "rand_core", + "serde", + "serde_cbor", + "serde_json", + "sha2 0.10.8", + "thiserror 2.0.12", + "toml", + "tracing", + "tracing-log", + "tracing-subscriber", + "x25519-dalek", +] + +[[package]] +name = "kassandra-shared" +version = "0.0.3-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d0b36e1bea0a4147d47b33e8770253f6101d6efd3f0a89f522b999947ce834" +dependencies = [ + "borsh", + "chacha20poly1305", + "cobs 0.3.0", + "hex", + "once_cell", + "polyfuzzy", + "rand_core", + "serde", + "serde_cbor", + "sha2 0.10.8", + "tdx-quote", + "thiserror 2.0.12", + "x25519-dalek", +] + [[package]] name = "keccak" version = "0.1.5" @@ -2473,7 +2648,7 @@ dependencies = [ "pasta_curves", "rand_core", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "zeroize", ] @@ -2520,7 +2695,7 @@ version = "0.149.1" dependencies = [ "namada_core", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -2560,7 +2735,7 @@ dependencies = [ "smooth-operator", "tendermint", "tendermint-proto", - "thiserror 2.0.11", + "thiserror 2.0.12", "tiny-keccak", "tracing", "uint 0.10.0", @@ -2577,7 +2752,7 @@ dependencies = [ "namada_macros", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2590,7 +2765,7 @@ dependencies = [ "namada_events", "namada_macros", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -2611,7 +2786,7 @@ dependencies = [ "serde", "serde_json", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2645,7 +2820,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2671,7 +2846,7 @@ dependencies = [ "namada_core", "namada_macros", "prost", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -2685,7 +2860,7 @@ dependencies = [ "namada_tx", "namada_vp_env", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -2707,7 +2882,7 @@ dependencies = [ "once_cell", "serde", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2727,6 +2902,7 @@ dependencies = [ "eyre", "futures", "itertools 0.14.0", + "kassandra-client", "lazy_static", "masp_primitives", "masp_proofs", @@ -2748,7 +2924,7 @@ dependencies = [ "sha2 0.10.8", "smooth-operator", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "typed-builder", "xorf", @@ -2772,7 +2948,7 @@ dependencies = [ "namada_tx", "patricia_tree", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2790,7 +2966,7 @@ dependencies = [ "regex", "serde", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2843,7 +3019,7 @@ dependencies = [ "namada_tx", "namada_tx_env", "namada_vp_env", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2871,7 +3047,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "thiserror 2.0.11", + "thiserror 2.0.12", "tonic-build", ] @@ -2923,7 +3099,7 @@ dependencies = [ "namada_tx", "namada_vp_env", "smooth-operator", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2985,6 +3161,16 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3076,9 +3262,18 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2611b99ab098a31bdc8be48b4f1a285ca0ced28bd5b4f23e45efa8c63b09efa5" +dependencies = [ + "once_cell", +] [[package]] name = "opaque-debug" @@ -3092,6 +3287,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "pairing" version = "0.23.0" @@ -3240,7 +3453,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" dependencies = [ - "cobs", + "cobs 0.2.3", "embedded-io 0.4.0", "embedded-io 0.6.1", "heapless", @@ -3272,6 +3485,15 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -3710,9 +3932,9 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -3735,11 +3957,21 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -3759,9 +3991,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -3780,6 +4012,15 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serdect" version = "0.2.0" @@ -3824,6 +4065,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3855,6 +4105,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + [[package]] name = "smooth-operator" version = "0.7.2" @@ -3906,6 +4162,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.27.0" @@ -3990,6 +4252,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tdx-quote" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecbbeffe2d73f07728bcbb581f8faa9098d813ba0fafbcab5e763a2fa4491e80" +dependencies = [ + "nom", + "p256", + "sha2 0.10.8", +] + [[package]] name = "tempfile" version = "3.16.0" @@ -4077,11 +4350,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -4097,9 +4370,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -4126,6 +4399,16 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.37" @@ -4180,23 +4463,47 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tonic-build" version = "0.12.3" @@ -4240,6 +4547,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -4482,12 +4815,24 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -4652,6 +4997,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" @@ -4802,9 +5169,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -4827,6 +5194,18 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + [[package]] name = "xorf" version = "0.11.0" From c407b1d054947cf49936e2bbd278b3441d05189a Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 23 May 2025 10:35:35 +0200 Subject: [PATCH 41/42] changelog --- .../unreleased/improvements/4646-register-fmd-keys.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changelog/unreleased/improvements/4646-register-fmd-keys.md diff --git a/.changelog/unreleased/improvements/4646-register-fmd-keys.md b/.changelog/unreleased/improvements/4646-register-fmd-keys.md new file mode 100644 index 00000000000..ed55deec95a --- /dev/null +++ b/.changelog/unreleased/improvements/4646-register-fmd-keys.md @@ -0,0 +1,8 @@ +Partially closes [#4645](https://github.com/anoma/namada/issues/4645) + +This PR allows the namada client to perform two key management actions: +- add to a config file +- use the config file to register fmd keys with Kassandra services + +Furthermore, it adds hashes of FMD keys derived from viewing keys to the wallet file under the viewing key alias. This PR does not handle querying the services or updating the shielded context accordingly. +([\#4646](https://github.com/anoma/namada/pull/4646)) \ No newline at end of file From 34ba3c6d6fbb2c2f27ccda6ac9a0c4f6cdfe2f23 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 23 May 2025 14:45:52 +0200 Subject: [PATCH 42/42] Moved fmd management commands to sdk --- Cargo.lock | 2 +- crates/apps_lib/Cargo.toml | 1 - crates/apps_lib/src/cli/client.rs | 2 +- crates/apps_lib/src/client/masp.rs | 43 ------------------------------ crates/sdk/Cargo.toml | 1 + crates/sdk/src/masp.rs | 1 + crates/sdk/src/masp/fmd.rs | 39 +++++++++++++++++++++++++++ wasm/Cargo.lock | 1 + 8 files changed, 44 insertions(+), 46 deletions(-) create mode 100644 crates/sdk/src/masp/fmd.rs diff --git a/Cargo.lock b/Cargo.lock index 99f5bb7b9db..3e6acc97566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5626,7 +5626,6 @@ dependencies = [ "flate2", "futures", "itertools 0.14.0", - "kassandra-client", "kdam", "lazy_static", "ledger-transport", @@ -6172,6 +6171,7 @@ dependencies = [ "getrandom 0.3.1", "init-once", "itertools 0.14.0", + "kassandra-client", "lazy_static", "linkme", "masp_primitives", diff --git a/crates/apps_lib/Cargo.toml b/crates/apps_lib/Cargo.toml index 4a9f797d09e..4d7e6385c82 100644 --- a/crates/apps_lib/Cargo.toml +++ b/crates/apps_lib/Cargo.toml @@ -49,7 +49,6 @@ flate2.workspace = true futures.workspace = true itertools.workspace = true jubjub.workspace = true -kassandra-client.workspace = true kdam.workspace = true lazy_static = { workspace = true, optional = true } linkme = { workspace = true, optional = true } diff --git a/crates/apps_lib/src/cli/client.rs b/crates/apps_lib/src/cli/client.rs index e5cb94e5643..c0c321de9a0 100644 --- a/crates/apps_lib/src/cli/client.rs +++ b/crates/apps_lib/src/cli/client.rs @@ -389,7 +389,7 @@ impl CliApi { .await?; } Sub::Fmd(FmdCommand(args)) => { - use crate::client::masp::fmd; + use namada_sdk::masp::fmd; let args = args.to_sdk(&mut ctx)?; let chain_ctx = ctx.take_chain_or_exit(); match args.command { diff --git a/crates/apps_lib/src/client/masp.rs b/crates/apps_lib/src/client/masp.rs index 0ea44f20262..df2d7e04917 100644 --- a/crates/apps_lib/src/client/masp.rs +++ b/crates/apps_lib/src/client/masp.rs @@ -148,46 +148,3 @@ pub async fn syncing< Ok(shielded) } - -/// CLI commands for FMD key management. -pub mod fmd { - use namada_core::key::FmdKeyHash; - use namada_sdk::{ShieldedUtils, ShieldedWallet}; - use namada_wallet::DatedViewingKey; - - /// Add an FMD key derived from the viewing key - /// to the config file. This file will be used - /// for registering and querying data from Kassandra - /// services. - pub async fn add_service( - wallet: ShieldedWallet, - DatedViewingKey { key, .. }: DatedViewingKey, - url: &str, - ) { - let uuid = kassandra_client::get_host_uuid(url); - let fmd_key = - namada_core::masp::FmdSecretKey::from(&key).fmd_secret_key(); - let enc_key = kassandra_client::encryption_key(&fmd_key, &uuid); - let key_hash = FmdKeyHash::from(fmd_key).to_string(); - let mut config = U::fmd_config_load().await.unwrap(); - config.add_service(key_hash, url, enc_key); - wallet.utils.fmd_config_save(&mut config).await.unwrap(); - } - - /// Register FND keys with Kassandra services as specified in the - /// config file. - pub async fn register_keys( - DatedViewingKey { key, birthday }: DatedViewingKey, - ) { - let config = U::fmd_config_load().await.unwrap(); - let fmd_key = - namada_core::masp::FmdSecretKey::from(&key).fmd_secret_key(); - let key_hash = FmdKeyHash::from(&fmd_key).to_string(); - kassandra_client::register_fmd_key( - &config, - key_hash, - &fmd_key, - Some(birthday.0), - ); - } -} diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 24d82c759d2..36cfe2d9e90 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -110,6 +110,7 @@ futures.workspace = true init-once.workspace = true itertools.workspace = true jubjub = { workspace = true, optional = true } +kassandra-client.workspace = true lazy_static.workspace = true linkme = { workspace = true, optional = true } masp_primitives.workspace = true diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 7a148431c26..669ce5d9fa1 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -1,5 +1,6 @@ //! MASP verification wrappers. +pub mod fmd; mod utilities; use std::str::FromStr; diff --git a/crates/sdk/src/masp/fmd.rs b/crates/sdk/src/masp/fmd.rs new file mode 100644 index 00000000000..0b2ed046e4a --- /dev/null +++ b/crates/sdk/src/masp/fmd.rs @@ -0,0 +1,39 @@ +//! Commands for FMD key management. +use namada_core::key::FmdKeyHash; +use namada_wallet::DatedViewingKey; + +use crate::{ShieldedUtils, ShieldedWallet}; + +/// Add an FMD key derived from the viewing key +/// to the config file. This file will be used +/// for registering and querying data from Kassandra +/// services. +pub async fn add_service( + wallet: ShieldedWallet, + DatedViewingKey { key, .. }: DatedViewingKey, + url: &str, +) { + let uuid = kassandra_client::get_host_uuid(url); + let fmd_key = namada_core::masp::FmdSecretKey::from(&key).fmd_secret_key(); + let enc_key = kassandra_client::encryption_key(&fmd_key, &uuid); + let key_hash = FmdKeyHash::from(fmd_key).to_string(); + let mut config = U::fmd_config_load().await.unwrap(); + config.add_service(key_hash, url, enc_key); + wallet.utils.fmd_config_save(&mut config).await.unwrap(); +} + +/// Register FND keys with Kassandra services as specified in the +/// config file. +pub async fn register_keys( + DatedViewingKey { key, birthday }: DatedViewingKey, +) { + let config = U::fmd_config_load().await.unwrap(); + let fmd_key = namada_core::masp::FmdSecretKey::from(&key).fmd_secret_key(); + let key_hash = FmdKeyHash::from(&fmd_key).to_string(); + kassandra_client::register_fmd_key( + &config, + key_hash, + &fmd_key, + Some(birthday.0), + ); +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 1dfa73b2f0d..ba29952de8d 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -4851,6 +4851,7 @@ dependencies = [ "getrandom 0.3.1", "init-once", "itertools 0.14.0", + "kassandra-client", "lazy_static", "masp_primitives", "masp_proofs",