Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
- Fixed `pausable::assert_not_paused` to guard its storage read with `active_account::has_storage_slot`, making it a no-op on accounts without the `Pausable` component instead of panicking on the missing `is_paused` slot ([#3047](https://github.com/0xMiden/protocol/pull/3047)).
- [BREAKING] Fixed batch ID being serialized/deserialized and potentially not matching the serialized transaction headers ([#3061](https://github.com/0xMiden/protocol/pull/3061)).

## v0.15.2 (TBD)

### Changes

- [BREAKING] `AuthNetworkAccount` now gates transaction scripts with a root allowlist instead of banning them outright, enabling network accounts to run approved tx scripts such as setting the expiration delta ([#3028](https://github.com/0xMiden/protocol/pull/3028)).
- [BREAKING] `TransactionScript::root()` now returns `TransactionScriptRoot` instead of `Word` ([#3028](https://github.com/0xMiden/protocol/pull/3028)).
- Renamed `AuthNetworkAccount::with_allowlist` to `with_allowed_notes` and aligned the component's internal allowlist field names, for consistency with `with_allowed_tx_scripts` ([#3049](https://github.com/0xMiden/protocol/pull/3049)).

## v0.15.1 (TBD)

### Changes
Expand Down
2 changes: 1 addition & 1 deletion crates/miden-agglayer/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ fn generate_agglayer_constants(
// The allowlist lives in storage, not code, and here we only care about the code commitment
// of the accounts, so we can init the allowlists with dummy values.
let placeholder_allowlist = BTreeSet::from([NoteScriptRoot::from_raw(Word::default())]);
let auth_component = AuthNetworkAccount::with_allowlist(placeholder_allowlist)
let auth_component = AuthNetworkAccount::with_allowed_notes(placeholder_allowlist)
.expect("placeholder allowlist is non-empty");
let mut components: Vec<AccountComponent> =
vec![AccountComponent::from(auth_component), agglayer_component];
Expand Down
4 changes: 2 additions & 2 deletions crates/miden-agglayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ fn create_bridge_account_builder(
.account_type(AccountType::Public)
.with_component(AggLayerBridge::new(bridge_admin_id, ger_manager_id))
.with_auth_component(
AuthNetworkAccount::with_allowlist(AggLayerBridge::allowed_notes())
AuthNetworkAccount::with_allowed_notes(AggLayerBridge::allowed_notes())
.expect("bridge note allowlist is non-empty"),
)
}
Expand Down Expand Up @@ -212,7 +212,7 @@ fn create_agglayer_faucet_builder(
.with_components(token_policy_manager)
.with_component(BurnAllowAll)
.with_auth_component(
AuthNetworkAccount::with_allowlist(AggLayerFaucet::allowed_notes())
AuthNetworkAccount::with_allowed_notes(AggLayerFaucet::allowed_notes())
.expect("faucet note allowlist is non-empty"),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ impl TransactionAdviceInputs {
// --- number of notes, script root and args --------------------------
self.extend_stack([Felt::from(tx_inputs.input_notes().num_notes())]);
let tx_args = tx_inputs.tx_args();
self.extend_stack(tx_args.tx_script().map_or(Word::empty(), |script| script.root()));
self.extend_stack(
tx_args.tx_script().map_or(Word::empty(), |script| script.root().as_word()),
);
self.extend_stack(tx_args.tx_script_args());

// --- auth procedure args --------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion crates/miden-protocol/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub use outputs::{
pub use partial_blockchain::PartialBlockchain;
pub use proven_tx::{InputNoteCommitment, ProvenTransaction, TxAccountUpdate};
pub use transaction_id::TransactionId;
pub use tx_args::{TransactionArgs, TransactionScript};
pub use tx_args::{TransactionArgs, TransactionScript, TransactionScriptRoot};
pub use tx_header::TransactionHeader;
pub use tx_summary::TransactionSummary;
pub use verifier::TransactionVerifier;
43 changes: 41 additions & 2 deletions crates/miden-protocol/src/transaction/tx_args.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::Display;

use miden_core::mast::MastNodeExt;
use miden_crypto::merkle::InnerNodeInfo;
use miden_crypto_derive::WordWrapper;
use miden_mast_package::Package;

use super::{Felt, Hasher, Word};
Expand Down Expand Up @@ -276,6 +279,42 @@ impl Deserializable for TransactionArgs {
}
}

// TRANSACTION SCRIPT ROOT
// ================================================================================================

/// The MAST root of a [`TransactionScript`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, WordWrapper)]
pub struct TransactionScriptRoot(Word);

impl From<TransactionScriptRoot> for Word {
fn from(root: TransactionScriptRoot) -> Self {
root.0
}
}

impl Display for TransactionScriptRoot {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(&self.0, f)
}
}

impl Serializable for TransactionScriptRoot {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write(self.0);
}

fn get_size_hint(&self) -> usize {
self.0.get_size_hint()
}
}

impl Deserializable for TransactionScriptRoot {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let word: Word = source.read()?;
Ok(Self::from_raw(word))
}
}

// TRANSACTION SCRIPT
// ================================================================================================

Expand Down Expand Up @@ -334,8 +373,8 @@ impl TransactionScript {
}

/// Returns the commitment of this transaction script (i.e., the script's MAST root).
pub fn root(&self) -> Word {
self.mast[self.entrypoint].digest()
pub fn root(&self) -> TransactionScriptRoot {
TransactionScriptRoot::from_raw(self.mast[self.entrypoint].digest())
}

/// Returns a new [TransactionScript] with the provided advice map entries merged into the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use miden::protocol::active_account
use miden::protocol::native_account
use miden::core::word
use miden::standards::auth::note_script_allowlist
use miden::standards::auth::tx_script_allowlist

# CONSTANTS
# =================================================================================================
Expand All @@ -14,13 +15,18 @@ use miden::standards::auth::note_script_allowlist
# (defined as Word); any non-empty value marks a root as allowed.
const ALLOWED_NOTE_SCRIPTS_SLOT = word("miden::standards::auth::network_account::allowed_note_scripts")

# The slot holding the map of allowed tx script roots. Keys are tx script roots (defined as Word);
# any non-empty value marks a root as allowed.
const ALLOWED_TX_SCRIPTS_SLOT = word("miden::standards::auth::network_account::allowed_tx_scripts")

# AUTH PROCEDURE
# =================================================================================================

#! Authenticates a transaction against an `AuthNetworkAccount` component.
#!
#! Enforces two invariants:
#! 1. No transaction script was executed in this transaction.
#! 1. The transaction script root, if any, must be present in the allowlist stored at
#! `ALLOWED_TX_SCRIPTS_SLOT` (a transaction that executed no tx script is always allowed).
#! 2. Every consumed input note must have a script root present in the allowlist stored at
#! `ALLOWED_NOTE_SCRIPTS_SLOT`.
#!
Expand All @@ -36,8 +42,11 @@ pub proc auth_network_transaction(auth_args: word)
dropw
# => [pad(16)]

# ---- Reject transactions that executed a tx script ----
exec.note_script_allowlist::assert_no_tx_script
# ---- Reject any tx script whose root is not allowlisted ----
push.ALLOWED_TX_SCRIPTS_SLOT[0..2]
# => [slot_id_suffix, slot_id_prefix, pad(16)]

exec.tx_script_allowlist::assert_tx_script_allowed
# => [pad(16)]

# ---- Reject any input note whose script root is not allowlisted ----
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Reusable note-script allowlist primitives.
# Reusable note-script allowlist primitive.
#
# Provides two checks used to restrict what an account can do during a transaction:
# - `assert_no_tx_script` rejects transactions that executed a tx script.
# Provides one check used to restrict what an account can do during a transaction:
# - `assert_all_input_notes_allowed` rejects transactions that consume an input note whose script
# root is not present in a storage map at the given slot id.
#
# These are designed to be composed into auth components. The caller owns the storage map and
# This is designed to be composed into auth components. The caller owns the storage map and
# passes the slot id (suffix, prefix) so the same logic can back multiple components, each with
# their own allowlist.

Expand All @@ -17,29 +16,11 @@ use miden::core::word
# ERRORS
# =================================================================================================

const ERR_NOTE_SCRIPT_ALLOWLIST_TX_SCRIPT_NOT_ALLOWED="a transaction script cannot be executed against an account guarded by a note script allowlist"
const ERR_NOTE_SCRIPT_ALLOWLIST_NOTE_NOT_ALLOWED="input note script root is not in the note script allowlist"

# PROCEDURES
# =================================================================================================

#! Asserts that no transaction script was executed in the current transaction.
#!
#! Inputs: []
#! Outputs: []
#!
#! Invocation: exec
pub proc assert_no_tx_script
exec.tx::get_tx_script_root
# => [TX_SCRIPT_ROOT]

exec.word::eqz
# => [has_no_tx_script]

assert.err=ERR_NOTE_SCRIPT_ALLOWLIST_TX_SCRIPT_NOT_ALLOWED
# => []
end

#! Asserts that every input note consumed by this transaction has a script root present in the
#! storage map at the given slot id.
#!
Expand Down
66 changes: 66 additions & 0 deletions crates/miden-standards/asm/standards/auth/tx_script_allowlist.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Reusable tx-script allowlist primitive.
#
# Provides a single check used to restrict which transaction scripts an account will execute:
# - `assert_tx_script_allowed` accepts transactions that executed no tx script, and otherwise
# rejects transactions whose tx script root is not present in a storage map at the given slot id.
#
# This is designed to be composed into auth components. The caller owns the storage map and passes
# the slot id (suffix, prefix) so the same logic can back multiple components, each with their own
# allowlist.

use miden::protocol::active_account
use miden::protocol::tx
use miden::core::word

# ERRORS
# =================================================================================================

const ERR_TX_SCRIPT_ALLOWLIST_TX_SCRIPT_NOT_ALLOWED="transaction script root is not in the tx script allowlist"

# PROCEDURES
# =================================================================================================

#! Asserts that the transaction script root is present in the storage map at the given slot id.
#!
#! A transaction that executed no tx script (empty root) is always allowed. Any other tx script must
#! have its root present in the allowlist.
#!
#! Map convention: keys are tx script roots (defined as Word), and any non-empty value marks a root
#! as allowed. Empty values (the default for absent keys) cause this procedure to fail.
#!
#! Inputs: [allowlist_slot_id_suffix, allowlist_slot_id_prefix]
#! Outputs: []
#!
#! Where:
#! - allowlist_slot_id_{suffix, prefix} are the suffix and prefix felts of the slot identifier
#! pointing at the allowlist storage map.
#!
#! Invocation: exec
pub proc assert_tx_script_allowed
# => [slot_id_suffix, slot_id_prefix]

exec.tx::get_tx_script_root
# => [TX_SCRIPT_ROOT, slot_id_suffix, slot_id_prefix]

exec.word::testz
# => [no_tx_script, TX_SCRIPT_ROOT, slot_id_suffix, slot_id_prefix]

if.true
# No tx script was executed, which is always allowed.
dropw drop drop
# => []
else
movup.5 movup.5
# => [slot_id_suffix, slot_id_prefix, TX_SCRIPT_ROOT]

exec.active_account::get_map_item
# => [VALUE]

exec.word::eqz not
# => [is_allowed]

assert.err=ERR_TX_SCRIPT_ALLOWLIST_TX_SCRIPT_NOT_ALLOWED
# => []
end
# => []
end
2 changes: 2 additions & 0 deletions crates/miden-standards/src/account/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ pub use network_account::{
NetworkAccount,
NetworkAccountNoteAllowlist,
NetworkAccountNoteAllowlistError,
NetworkAccountTxScriptAllowlist,
NetworkAccountTxScriptAllowlistError,
};
Loading
Loading