Skip to content

Commit b80e1af

Browse files
committed
Merge branch 'grarco/outsource-masp-sig-verification' (#3372)
* grarco/outsource-masp-sig-verification: Transfer transaction fails if masp transparent inputs are not debited Changelog #3312 Masp vp checks that no unneeded actions are pushed Transfer transaction pushes masp actions Renames masp signers to authorizers Refactors masp action checks Masp vp checks for signer actions Moves signatures verification from masp vp to the affected vps
2 parents 85403a7 + fc63450 commit b80e1af

File tree

10 files changed

+148
-71
lines changed

10 files changed

+148
-71
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Moved the signature verifications out of the masp vp and into the affected
2+
addresses' vps. ([\#3312](https://github.com/anoma/namada/issues/3312))

crates/namada/src/ledger/native_vp/masp.rs

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ use namada_core::ibc::apps::transfer::types::msgs::transfer::MsgTransfer as IbcM
2121
use namada_core::ibc::apps::transfer::types::packet::PacketData;
2222
use namada_core::masp::{addr_taddr, encode_asset_type, ibc_taddr, MaspEpoch};
2323
use namada_core::storage::Key;
24-
use namada_gas::GasMetering;
2524
use namada_governance::storage::is_proposal_accepted;
2625
use namada_ibc::core::channel::types::commitment::{
2726
compute_packet_commitment, PacketCommitment,
@@ -43,6 +42,7 @@ use namada_sdk::masp::TAddrData;
4342
use namada_state::{ConversionState, OptionExt, ResultExt, StateRead};
4443
use namada_token::read_denom;
4544
use namada_token::validation::verify_shielded_tx;
45+
use namada_tx::action::Read;
4646
use namada_tx::BatchedTxRef;
4747
use namada_vp_env::VpEnv;
4848
use thiserror::Error;
@@ -703,7 +703,7 @@ where
703703
)
704704
.map_err(native_vp::Error::new)?,
705705
);
706-
// And then record thee final state
706+
// And then record the final state
707707
let post_entry = result.post.get(&addr_hash).cloned().unwrap_or(zero);
708708
result.post.insert(
709709
addr_hash,
@@ -750,6 +750,7 @@ where
750750
&self,
751751
tx_data: &BatchedTxRef<'_>,
752752
keys_changed: &BTreeSet<Key>,
753+
verifiers: &BTreeSet<Address>,
753754
) -> Result<()> {
754755
let masp_epoch_multiplier =
755756
namada_parameters::read_masp_epoch_multiplier_parameter(
@@ -764,6 +765,7 @@ where
764765
})?;
765766
let conversion_state = self.ctx.state.in_mem().get_conversion_state();
766767
let ibc_msg = self.ctx.get_ibc_message(tx_data).ok();
768+
let actions = self.ctx.read_actions()?;
767769
let shielded_tx =
768770
if let Some(IbcMessage::Envelope(ref envelope)) = ibc_msg {
769771
extract_masp_tx_from_envelope(envelope).ok_or_else(|| {
@@ -774,7 +776,7 @@ where
774776
} else {
775777
// Get the Transaction object from the actions
776778
let masp_section_ref =
777-
namada_tx::action::get_masp_section_ref(&self.ctx)?
779+
namada_tx::action::get_masp_section_ref(&actions)
778780
.ok_or_else(|| {
779781
native_vp::Error::new_const(
780782
"Missing MASP section reference in action",
@@ -824,7 +826,7 @@ where
824826
)?;
825827

826828
// The set of addresses that are required to authorize this transaction
827-
let mut signers = BTreeSet::new();
829+
let mut authorizers = BTreeSet::new();
828830

829831
// Checks on the sapling bundle
830832
// 1. The spend descriptions' anchors are valid
@@ -846,7 +848,7 @@ where
846848
&mut changed_bals_minus_txn,
847849
masp_epoch,
848850
conversion_state,
849-
&mut signers,
851+
&mut authorizers,
850852
)?;
851853

852854
// Ensure that every account for which balance has gone down as a result
@@ -869,14 +871,27 @@ where
869871
(minus_txn_pre, minus_txn_post) != (pre.clone(), post)
870872
{
871873
// This address will need to provide further authorization
872-
signers.insert(addr);
874+
authorizers.insert(addr);
873875
}
874876
}
875877

878+
let mut actions_authorizers: HashSet<&Address> = actions
879+
.iter()
880+
.filter_map(|action| {
881+
if let namada_tx::action::Action::Masp(
882+
namada_tx::action::MaspAction::MaspAuthorizer(addr),
883+
) = action
884+
{
885+
Some(addr)
886+
} else {
887+
None
888+
}
889+
})
890+
.collect();
876891
// Ensure that this transaction is authorized by all involved parties
877-
for signer in signers {
892+
for authorizer in authorizers {
878893
if let Some(TAddrData::Addr(IBC)) =
879-
changed_bals_minus_txn.decoder.get(&signer)
894+
changed_bals_minus_txn.decoder.get(&authorizer)
880895
{
881896
// If the IBC address is a signatory, then it means that either
882897
// Tx - Transaction(s) causes a decrease in the IBC balance or
@@ -906,39 +921,53 @@ where
906921
}
907922
}
908923
} else if let Some(TAddrData::Addr(signer)) =
909-
changed_bals_minus_txn.decoder.get(&signer)
924+
changed_bals_minus_txn.decoder.get(&authorizer)
910925
{
911-
// Otherwise the signer must be decodable so that we can
912-
// manually check the signatures
913-
let public_keys_index_map =
914-
crate::account::public_keys_index_map(
915-
&self.ctx.pre(),
916-
signer,
917-
)?;
918-
let threshold =
919-
crate::account::threshold(&self.ctx.pre(), signer)?
920-
.unwrap_or(1);
921-
let mut gas_meter = self.ctx.gas_meter.borrow_mut();
922-
tx_data
923-
.tx
924-
.verify_signatures(
925-
&[tx_data.tx.raw_header_hash()],
926-
public_keys_index_map,
927-
&Some(signer.clone()),
928-
threshold,
929-
|| gas_meter.consume(crate::gas::VERIFY_TX_SIG_GAS),
930-
)
931-
.map_err(native_vp::Error::new)?;
926+
// Otherwise the owner's vp must have been triggered and the
927+
// relative action must have been written
928+
if !verifiers.contains(signer) {
929+
let error = native_vp::Error::new_alloc(format!(
930+
"The required vp of address {signer} was not triggered"
931+
))
932+
.into();
933+
tracing::debug!("{error}");
934+
return Err(error);
935+
}
936+
937+
// The action is required becuse the target vp might have been
938+
// triggered for other reasons but we need to signal it that it
939+
// is required to validate a discrepancy in its balance change
940+
// because of a masp transaction, which might require a
941+
// different validation than a normal balance change
942+
if !actions_authorizers.swap_remove(signer) {
943+
let error = native_vp::Error::new_alloc(format!(
944+
"The required masp authorizer action for address \
945+
{signer} is missing"
946+
))
947+
.into();
948+
tracing::debug!("{error}");
949+
return Err(error);
950+
}
932951
} else {
933-
// We are not able to decode the signer, so just fail
952+
// We are not able to decode the authorizer, so just fail
934953
let error = native_vp::Error::new_const(
935-
"Unable to decode a transaction signer",
954+
"Unable to decode a transaction authorizer",
936955
)
937956
.into();
938957
tracing::debug!("{error}");
939958
return Err(error);
940959
}
941960
}
961+
// The transaction shall not push masp authorizer actions that are not
962+
// needed cause this might lead vps to run a wrong validation logic
963+
if !actions_authorizers.is_empty() {
964+
let error = native_vp::Error::new_const(
965+
"Found masp authorizer actions that are not required",
966+
)
967+
.into();
968+
tracing::debug!("{error}");
969+
return Err(error);
970+
}
942971

943972
// Verify the proofs
944973
verify_shielded_tx(&shielded_tx, |gas| self.ctx.charge_gas(gas))
@@ -963,18 +992,17 @@ fn unepoched_tokens(
963992
Ok(())
964993
}
965994

966-
// Handle transparent input
967995
fn validate_transparent_input<A: Authorization>(
968996
vin: &TxIn<A>,
969997
changed_balances: &mut ChangedBalances,
970998
transparent_tx_pool: &mut I128Sum,
971999
epoch: MaspEpoch,
9721000
conversion_state: &ConversionState,
973-
signers: &mut BTreeSet<TransparentAddress>,
1001+
authorizers: &mut BTreeSet<TransparentAddress>,
9741002
) -> Result<()> {
9751003
// A decrease in the balance of an account needs to be
9761004
// authorized by the account of this transparent input
977-
signers.insert(vin.address);
1005+
authorizers.insert(vin.address);
9781006
// Non-masp sources add to the transparent tx pool
9791007
*transparent_tx_pool = transparent_tx_pool
9801008
.checked_add(
@@ -1050,7 +1078,6 @@ fn validate_transparent_input<A: Authorization>(
10501078
Ok(())
10511079
}
10521080

1053-
// Handle transparent output
10541081
fn validate_transparent_output(
10551082
out: &TxOut,
10561083
changed_balances: &mut ChangedBalances,
@@ -1122,7 +1149,7 @@ fn validate_transparent_bundle(
11221149
changed_balances: &mut ChangedBalances,
11231150
epoch: MaspEpoch,
11241151
conversion_state: &ConversionState,
1125-
signers: &mut BTreeSet<TransparentAddress>,
1152+
authorizers: &mut BTreeSet<TransparentAddress>,
11261153
) -> Result<()> {
11271154
// The Sapling value balance adds to the transparent tx pool
11281155
let mut transparent_tx_pool = shielded_tx.sapling_value_balance();
@@ -1135,7 +1162,7 @@ fn validate_transparent_bundle(
11351162
&mut transparent_tx_pool,
11361163
epoch,
11371164
conversion_state,
1138-
signers,
1165+
authorizers,
11391166
)?;
11401167
}
11411168

@@ -1259,7 +1286,7 @@ where
12591286
&self,
12601287
tx_data: &BatchedTxRef<'_>,
12611288
keys_changed: &BTreeSet<Key>,
1262-
_verifiers: &BTreeSet<Address>,
1289+
verifiers: &BTreeSet<Address>,
12631290
) -> Result<()> {
12641291
let masp_keys_changed: Vec<&Key> =
12651292
keys_changed.iter().filter(|key| is_masp_key(key)).collect();
@@ -1289,7 +1316,7 @@ where
12891316
self.is_valid_parameter_change(tx_data)
12901317
} else if masp_transfer_changes {
12911318
// The MASP transfer keys can only be changed by a valid Transaction
1292-
self.is_valid_masp_transfer(tx_data, keys_changed)
1319+
self.is_valid_masp_transfer(tx_data, keys_changed, verifiers)
12931320
} else {
12941321
// Changing no MASP keys at all is also fine
12951322
Ok(())

crates/namada/src/ledger/protocol/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,10 @@ where
406406
if is_accepted {
407407
// If the transaction was a masp one append the
408408
// transaction refs for the events
409+
let actions =
410+
state.read_actions().map_err(Error::StateError)?;
409411
if let Some(masp_section_ref) =
410-
namada_tx::action::get_masp_section_ref(state)
411-
.map_err(Error::StateError)?
412+
namada_tx::action::get_masp_section_ref(&actions)
412413
{
413414
extended_tx_result
414415
.masp_tx_refs
@@ -745,6 +746,9 @@ where
745746
);
746747
}
747748

749+
let actions =
750+
state.read_actions().map_err(Error::StateError)?;
751+
748752
// Ensure that the transaction is actually a masp one, otherwise
749753
// reject
750754
if is_masp_transfer(&result.changed_keys)

crates/trans_token/src/storage.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::{BTreeMap, BTreeSet};
22

33
use namada_core::address::{Address, InternalAddress};
4+
use namada_core::collections::HashSet;
45
use namada_core::hints;
56
use namada_core::token::{self, Amount, AmountError, DenominatedAmount};
67
use namada_storage as storage;
@@ -263,15 +264,16 @@ where
263264
/// Transfer tokens from `sources` to `dests`. Returns an `Err` if any source
264265
/// has insufficient balance or if the transfer to any destination would
265266
/// overflow (This can only happen if the total supply doesn't fit in
266-
/// `token::Amount`).
267+
/// `token::Amount`). Returns the set of debited accounts.
267268
pub fn multi_transfer<S>(
268269
storage: &mut S,
269270
sources: &BTreeMap<(Address, Address), Amount>,
270271
dests: &BTreeMap<(Address, Address), Amount>,
271-
) -> storage::Result<()>
272+
) -> storage::Result<HashSet<Address>>
272273
where
273274
S: StorageRead + StorageWrite,
274275
{
276+
let mut debited_accounts = HashSet::new();
275277
// Collect all the accounts whose balance has changed
276278
let mut accounts = BTreeSet::new();
277279
accounts.extend(sources.keys().cloned());
@@ -306,6 +308,7 @@ where
306308
)
307309
.ok_or_else(overflow_err)?
308310
} else {
311+
debited_accounts.insert(owner.to_owned());
309312
owner_balance
310313
.checked_sub(
311314
src_amt.checked_sub(dest_amt).ok_or_else(unexpected_err)?,
@@ -315,7 +318,7 @@ where
315318
// Wite the new balance
316319
storage.write(&owner_key, new_owner_balance)?;
317320
}
318-
Ok(())
321+
Ok(debited_accounts)
319322
}
320323

321324
/// Mint `amount` of `token` as `minter` to `dest`.

crates/tx/src/action.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub type Actions = Vec<Action>;
2121

2222
/// An action applied from a tx.
2323
#[allow(missing_docs)]
24-
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
24+
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
2525
pub enum Action {
2626
Pos(PosAction),
2727
Gov(GovAction),
@@ -32,7 +32,7 @@ pub enum Action {
3232

3333
/// PoS tx actions.
3434
#[allow(missing_docs)]
35-
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
35+
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
3636
pub enum PosAction {
3737
BecomeValidator(Address),
3838
DeactivateValidator(Address),
@@ -50,25 +50,27 @@ pub enum PosAction {
5050

5151
/// Gov tx actions.
5252
#[allow(missing_docs)]
53-
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
53+
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
5454
pub enum GovAction {
5555
InitProposal { author: Address },
5656
VoteProposal { id: u64, voter: Address },
5757
}
5858

5959
/// PGF tx actions.
6060
#[allow(missing_docs)]
61-
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
61+
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
6262
pub enum PgfAction {
6363
ResignSteward(Address),
6464
UpdateStewardCommission(Address),
6565
}
6666

67-
/// MASP tx action.
68-
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
69-
pub struct MaspAction {
67+
/// MASP tx actions.
68+
#[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)]
69+
pub enum MaspAction {
7070
/// The hash of the masp [`crate::types::Section`]
71-
pub masp_section_ref: TxId,
71+
MaspSectionRef(TxId),
72+
/// A required authorizer for the transaction
73+
MaspAuthorizer(Address),
7274
}
7375

7476
/// Read actions from temporary storage
@@ -122,17 +124,16 @@ fn storage_key() -> storage::Key {
122124
/// Helper function to get the optional masp section reference from the
123125
/// [`Actions`]. If more than one [`MaspAction`] has been found we return the
124126
/// first one
125-
pub fn get_masp_section_ref<T: Read>(
126-
reader: &T,
127-
) -> Result<Option<TxId>, <T as Read>::Err> {
128-
Ok(reader.read_actions()?.into_iter().find_map(|action| {
129-
// In case of multiple masp actions we get the first one
130-
if let Action::Masp(MaspAction { masp_section_ref }) = action {
131-
Some(masp_section_ref)
127+
pub fn get_masp_section_ref(actions: &Actions) -> Option<TxId> {
128+
actions.iter().find_map(|action| {
129+
if let Action::Masp(MaspAction::MaspSectionRef(masp_section_ref)) =
130+
action
131+
{
132+
Some(masp_section_ref.to_owned())
132133
} else {
133134
None
134135
}
135-
}))
136+
})
136137
}
137138

138139
/// Helper function to check if the action is IBC shielding transfer

0 commit comments

Comments
 (0)