Skip to content

Commit 70eaa07

Browse files
committed
Validate flag ciphertexts in MASP VP
1 parent bfd957c commit 70eaa07

File tree

15 files changed

+224
-65
lines changed

15 files changed

+224
-65
lines changed

crates/apps_lib/src/client/tx.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,7 +1935,8 @@ pub async fn gen_ibc_shielding_transfer(
19351935
) -> Result<(), error::Error> {
19361936
let output_folder = args.output_folder.clone();
19371937

1938-
if let Some(masp_tx) = tx::gen_ibc_shielding_transfer(context, args).await?
1938+
if let Some((masp_tx, fmd_flags)) =
1939+
tx::gen_ibc_shielding_transfer(context, args).await?
19391940
{
19401941
let tx_id = masp_tx.txid().to_string();
19411942
let filename = format!("ibc_masp_tx_{}.memo", tx_id);
@@ -1945,11 +1946,7 @@ pub async fn gen_ibc_shielding_transfer(
19451946
};
19461947
let mut out = File::create(&output_path)
19471948
.expect("Creating a new file for IBC MASP transaction failed.");
1948-
let bytes = convert_masp_tx_to_ibc_memo(
1949-
masp_tx,
1950-
// TODO: add actual flag ciphertext
1951-
Default::default(),
1952-
);
1949+
let bytes = convert_masp_tx_to_ibc_memo(masp_tx, fmd_flags);
19531950
out.write_all(bytes.as_bytes())
19541951
.expect("Writing IBC MASP transaction file failed.");
19551952
println!(

crates/core/src/masp.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,22 @@ pub enum UnifiedPaymentAddress {
10171017
}
10181018

10191019
impl UnifiedPaymentAddress {
1020+
/// Generate a [`FlagCiphertext`] from this payment address.
1021+
///
1022+
/// Returns [`None`] if the FMD public key in the
1023+
/// [`UnifiedPaymentAddress::V1`] variant is invalid, and a random flag
1024+
/// ciphertext for the [`UnifiedPaymentAddress::V0`] variant.
1025+
#[cfg(feature = "rand")]
1026+
pub fn flag<R>(&self, rng: &mut R) -> Option<FlagCiphertext>
1027+
where
1028+
R: rand_core::CryptoRng + rand_core::RngCore,
1029+
{
1030+
match self {
1031+
Self::V0(_) => Some(FlagCiphertext::random(rng)),
1032+
Self::V1(pa) => pa.to_fmd_public_key().map(|pk| pk.flag(rng)),
1033+
}
1034+
}
1035+
10201036
/// Create a v0 payment address.
10211037
pub fn v0_from_zip32(
10221038
vk: ExtendedViewingKey,

crates/ibc/src/lib.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ use namada_core::ibc::core::channel::types::commitment::{
9292
AcknowledgementCommitment, PacketCommitment, compute_packet_commitment,
9393
};
9494
pub use namada_core::ibc::*;
95-
use namada_core::masp::{TAddrData, addr_taddr, ibc_taddr};
95+
use namada_core::masp::{FlagCiphertext, TAddrData, addr_taddr, ibc_taddr};
9696
use namada_core::masp_primitives::transaction::components::ValueSum;
9797
use namada_core::token::Amount;
9898
use namada_events::EmitEvents;
@@ -237,18 +237,19 @@ impl<S> namada_systems::ibc::Read<S> for Store<S>
237237
where
238238
S: StorageRead,
239239
{
240-
fn try_extract_masp_tx_from_envelope<Transfer: BorshDeserialize>(
240+
fn try_extract_shielding_data_from_envelope<Transfer: BorshDeserialize>(
241241
tx_data: &[u8],
242-
) -> StorageResult<Option<masp_primitives::transaction::Transaction>> {
242+
) -> StorageResult<Option<(MaspTransaction, Vec<FlagCiphertext>)>> {
243243
let msg = decode_message::<Transfer>(tx_data)
244244
.into_storage_result()
245245
.ok();
246246
let tx = if let Some(IbcMessage::Envelope(ref envelope)) = msg {
247-
Some(extract_masp_tx_from_envelope(envelope).ok_or_else(|| {
248-
StorageError::new_const(
249-
"Missing MASP transaction in IBC message",
250-
)
251-
})?)
247+
let shielding_data = extract_shielding_data_from_envelope(envelope)
248+
.ok_or(StorageError::new_const(
249+
"Missing MASP shielding data in IBC message",
250+
))?;
251+
252+
Some((shielding_data.masp_tx, shielding_data.flag_ciphertexts))
252253
} else {
253254
None
254255
};
@@ -578,7 +579,7 @@ pub struct InternalData<Transfer> {
578579
/// The transparent transfer that happens in parallel to IBC processes
579580
pub transparent: Option<Transfer>,
580581
/// The shielded transaction that happens in parallel to IBC processes
581-
pub shielded: Option<MaspTransaction>,
582+
pub shielded: Option<IbcShieldingData>,
582583
/// IBC tokens that are credited/debited to internal accounts
583584
pub ibc_tokens: BTreeSet<Address>,
584585
}
@@ -760,13 +761,16 @@ where
760761
.map_err(Error::Storage)?;
761762
tokens.insert(token);
762763
}
763-
(extract_masp_tx_from_packet(&msg.packet), tokens)
764+
(
765+
extract_shielding_data_from_packet(&msg.packet),
766+
tokens,
767+
)
764768
}
765769
#[cfg(is_apple_silicon)]
766770
MsgEnvelope::Packet(PacketMsg::Ack(msg)) => {
767771
// NOTE: This is unneeded but wasm compilation error
768772
// happened if deleted on macOS with Apple Silicon
769-
let _ = extract_masp_tx_from_packet(&msg.packet);
773+
let _ = extract_shielding_data_from_packet(&msg.packet);
770774
(None, BTreeSet::new())
771775
}
772776
_ => (None, BTreeSet::new()),

crates/ibc/src/msg.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ impl<Transfer: BorshSchema> BorshSchema for MsgNftTransfer<Transfer> {
242242
pub struct IbcShieldingData {
243243
/// MASP transaction forwarded over IBC.
244244
pub masp_tx: MaspTransaction,
245-
/// Flag ciphertext to signal the owner of the new note(s).
246-
pub flag_ciphertext: FlagCiphertext,
245+
/// Flag ciphertexts to signal the owner(s) of the new note(s).
246+
pub flag_ciphertexts: Vec<FlagCiphertext>,
247247
}
248248

249249
impl From<&IbcShieldingData> for String {
@@ -279,9 +279,17 @@ impl FromStr for IbcShieldingData {
279279
pub fn extract_masp_tx_from_envelope(
280280
envelope: &MsgEnvelope,
281281
) -> Option<MaspTransaction> {
282+
extract_shielding_data_from_envelope(envelope)
283+
.map(|IbcShieldingData { masp_tx, .. }| masp_tx)
284+
}
285+
286+
/// Extract IBC shielding data from IBC envelope
287+
pub fn extract_shielding_data_from_envelope(
288+
envelope: &MsgEnvelope,
289+
) -> Option<IbcShieldingData> {
282290
match envelope {
283291
MsgEnvelope::Packet(PacketMsg::Recv(msg)) => {
284-
extract_masp_tx_from_packet(&msg.packet)
292+
extract_shielding_data_from_packet(&msg.packet)
285293
}
286294
_ => None,
287295
}
@@ -306,10 +314,17 @@ pub fn decode_ibc_shielding_data(
306314

307315
/// Extract MASP transaction from IBC packet memo
308316
pub fn extract_masp_tx_from_packet(packet: &Packet) -> Option<MaspTransaction> {
317+
extract_shielding_data_from_packet(packet)
318+
.map(|IbcShieldingData { masp_tx, .. }| masp_tx)
319+
}
320+
321+
/// Extract IBC shielding data from IBC packet memo
322+
pub fn extract_shielding_data_from_packet(
323+
packet: &Packet,
324+
) -> Option<IbcShieldingData> {
309325
let memo = extract_memo_from_packet(packet, &packet.port_id_on_b)?;
310326

311327
decode_ibc_shielding_data(memo)
312-
.map(|IbcShieldingData { masp_tx, .. }| masp_tx)
313328
}
314329

315330
fn extract_memo_from_packet(
@@ -376,11 +391,11 @@ pub fn extract_traces_from_recv_msg(
376391
/// Get IBC memo string from MASP transaction for receiving
377392
pub fn convert_masp_tx_to_ibc_memo(
378393
masp_tx: MaspTransaction,
379-
flag_ciphertext: FlagCiphertext,
394+
flag_ciphertexts: Vec<FlagCiphertext>,
380395
) -> String {
381396
IbcShieldingData {
382397
masp_tx,
383-
flag_ciphertext,
398+
flag_ciphertexts,
384399
}
385400
.into()
386401
}

crates/sdk/src/args.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ impl TxOsmosisSwap<SdkTypes> {
718718
),
719719
};
720720

721-
let shielding_tx = tx::gen_ibc_shielding_transfer(
721+
let (shielding_tx, fmd_flags) = tx::gen_ibc_shielding_transfer(
722722
ctx,
723723
GenIbcShieldingTransfer {
724724
query: Query {
@@ -754,8 +754,7 @@ impl TxOsmosisSwap<SdkTypes> {
754754
shielding_data: StringEncoded::new(
755755
IbcShieldingData {
756756
masp_tx: shielding_tx,
757-
// TODO: add actual flag ciphertext here
758-
flag_ciphertext: Default::default(),
757+
flag_ciphertexts: fmd_flags,
759758
},
760759
),
761760
shielded_amount: amount_to_shield,

crates/sdk/src/tx.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3981,7 +3981,7 @@ pub async fn build_custom(
39813981
pub async fn gen_ibc_shielding_transfer<N: Namada>(
39823982
context: &N,
39833983
args: args::GenIbcShieldingTransfer,
3984-
) -> Result<Option<MaspTransaction>> {
3984+
) -> Result<Option<(MaspTransaction, Vec<FlagCiphertext>)>> {
39853985
let source = IBC;
39863986

39873987
let token = match args.asset {
@@ -4043,7 +4043,7 @@ pub async fn gen_ibc_shielding_transfer<N: Namada>(
40434043
.map_err(|err| TxSubmitError::MaspError(err.to_string()))?
40444044
};
40454045

4046-
Ok(shielded_transfer.map(|st| st.masp_tx))
4046+
Ok(shielded_transfer.map(|st| (st.masp_tx, st.fmd_flags)))
40474047
}
40484048

40494049
pub(crate) async fn get_ibc_src_port_channel(

crates/shielded_token/src/masp/shielded_wallet.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use namada_core::collections::{HashMap, HashSet};
3232
use namada_core::control_flow;
3333
use namada_core::masp::{
3434
AssetData, FlagCiphertext, FmdSecretKey, MaspEpoch, TransferSource,
35-
TransferTarget, UnifiedPaymentAddress, encode_asset_type,
35+
TransferTarget, encode_asset_type,
3636
};
3737
use namada_core::task_env::TaskEnvironment;
3838
use namada_core::time::{DateTimeUtc, DurationSecs};
@@ -1775,17 +1775,9 @@ pub trait ShieldedApi<U: ShieldedUtils + MaybeSend + MaybeSync>:
17751775
error: builder::Error::SaplingBuild(e),
17761776
})?;
17771777

1778-
fmd_flags.push({
1779-
match pa {
1780-
UnifiedPaymentAddress::V0(_) => {
1781-
FlagCiphertext::random(rng)
1782-
}
1783-
UnifiedPaymentAddress::V1(pa) => pa
1784-
.to_fmd_public_key()
1785-
.ok_or(TransferErr::InvalidFmdPublicKey)?
1786-
.flag(rng),
1787-
}
1788-
});
1778+
fmd_flags.push(
1779+
pa.flag(rng).ok_or(TransferErr::InvalidFmdPublicKey)?,
1780+
);
17891781
} else if let Some(t_addr_data) = target.t_addr_data() {
17901782
// If there is a transparent output
17911783
builder

crates/shielded_token/src/vp.rs

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ use namada_core::address::{self, Address};
1717
use namada_core::arith::{CheckedAdd, CheckedSub, checked};
1818
use namada_core::booleans::BoolResultUnitExt;
1919
use namada_core::collections::HashSet;
20-
use namada_core::masp::{MaspEpoch, TAddrData, addr_taddr, encode_asset_type};
20+
use namada_core::masp::{
21+
FlagCiphertext, MaspEpoch, TAddrData, addr_taddr, encode_asset_type,
22+
};
2123
use namada_core::storage::Key;
2224
use namada_core::token;
2325
use namada_core::token::{Amount, MaspDigitPos};
@@ -435,12 +437,13 @@ where
435437
.data(batched_tx.cmt)
436438
.ok_or_err_msg("No transaction data")?;
437439
let actions = ctx.read_actions()?;
438-
// Try to get the Transaction object from the tx first (IBC) and from
439-
// the actions afterwards
440-
let shielded_tx = if let Some(tx) =
441-
Ibc::try_extract_masp_tx_from_envelope::<Transfer>(&tx_data)?
440+
441+
// Try to get the Transaction object and FMD flag ciphertexts
442+
// from the tx first (IBC) and from the actions afterwards
443+
let (shielded_tx, fmd_flags) = if let Some(shielding_data) =
444+
Ibc::try_extract_shielding_data_from_envelope::<Transfer>(&tx_data)?
442445
{
443-
tx
446+
shielding_data
444447
} else {
445448
let masp_section_ref =
446449
namada_tx::action::get_masp_section_ref(&actions)
@@ -450,14 +453,33 @@ where
450453
"Missing MASP section reference in action",
451454
)
452455
})?;
456+
let flag_ciphertexts_ref =
457+
namada_tx::action::get_fmd_flag_ciphertexts_ref(&actions)
458+
.map_err(Error::new_const)?
459+
.ok_or_else(|| {
460+
Error::new_const(
461+
"Missing FMD flag ciphertexts reference in action",
462+
)
463+
})?;
453464

454-
batched_tx
465+
let masp_tx = batched_tx
455466
.tx
456467
.get_masp_section(&masp_section_ref)
457468
.cloned()
458469
.ok_or_else(|| {
459470
Error::new_const("Missing MASP section in transaction")
460-
})?
471+
})?;
472+
let fmd_flags = batched_tx
473+
.tx
474+
.get_fmd_flag_ciphertexts(&flag_ciphertexts_ref)
475+
.map_err(Error::new)?
476+
.ok_or_else(|| {
477+
Error::new_const(
478+
"Missing FMD flag ciphertexts in transaction",
479+
)
480+
})?;
481+
482+
(masp_tx, fmd_flags)
461483
};
462484

463485
if u64::from(ctx.get_block_height()?)
@@ -468,6 +490,8 @@ where
468490
return Err(error);
469491
}
470492

493+
validate_flag_ciphertexts(&shielded_tx, fmd_flags)?;
494+
471495
// Check the validity of the keys and get the transfer data
472496
let changed_balances = Self::validate_state_and_get_transfer_data(
473497
ctx,
@@ -949,6 +973,39 @@ fn verify_sapling_balancing_value(
949973
}
950974
}
951975

976+
/// Check if the flag ciphertexts included in the tx are valid.
977+
fn validate_flag_ciphertexts(
978+
masp_tx: &Transaction,
979+
fmd_flags: Vec<FlagCiphertext>,
980+
) -> Result<()> {
981+
let shielded_outputs_len = masp_tx
982+
.sapling_bundle()
983+
.map_or(0, |bundle| bundle.shielded_outputs.len());
984+
985+
if shielded_outputs_len != fmd_flags.len() {
986+
let error = Error::new(format!(
987+
"The number of shielded outputs in the MASP tx ({}) does not \
988+
match the number of FMD flag ciphertexts ({})",
989+
shielded_outputs_len,
990+
fmd_flags.len()
991+
));
992+
tracing::debug!("{error}");
993+
return Err(error);
994+
}
995+
996+
fmd_flags
997+
.iter()
998+
.all(FlagCiphertext::is_valid)
999+
.ok_or_else(|| {
1000+
let error = Error::new_const(
1001+
"Not all FMD flag ciphertexts in the MASP tx were considered \
1002+
valid, either because of invalid gamma or tampered bits",
1003+
);
1004+
tracing::debug!("{error}");
1005+
error
1006+
})
1007+
}
1008+
9521009
#[cfg(test)]
9531010
mod shielded_token_tests {
9541011
use std::cell::RefCell;

crates/systems/src/ibc.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ use masp_primitives::transaction::TransparentAddress;
66
use masp_primitives::transaction::components::ValueSum;
77
use namada_core::address::Address;
88
use namada_core::borsh::BorshDeserialize;
9-
use namada_core::masp::TAddrData;
9+
use namada_core::masp::{FlagCiphertext, TAddrData};
1010
use namada_core::{masp_primitives, storage, token};
1111
pub use namada_storage::Result;
1212

1313
/// Abstract IBC storage read interface
1414
pub trait Read<S> {
15-
/// Extract MASP transaction from IBC envelope
16-
fn try_extract_masp_tx_from_envelope<Transfer: BorshDeserialize>(
15+
/// Extract shielding data from IBC envelope
16+
fn try_extract_shielding_data_from_envelope<Transfer: BorshDeserialize>(
1717
tx_data: &[u8],
18-
) -> Result<Option<masp_primitives::transaction::Transaction>>;
18+
) -> Result<
19+
Option<(
20+
masp_primitives::transaction::Transaction,
21+
Vec<FlagCiphertext>,
22+
)>,
23+
>;
1924

2025
/// Apply relevant IBC packets to the changed balances structure
2126
fn apply_ibc_packet<Transfer: BorshDeserialize>(

0 commit comments

Comments
 (0)