Skip to content
Open
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,16 @@
- [BREAKING] `FungibleAsset::amount()` and `AssetVault::get_balance()` now return `AssetAmount` ([#2928](https://github.com/0xMiden/protocol/pull/2928)).
- [BREAKING] Upgraded `miden-vm` to v0.23 and `miden-crypto` to v0.25. Notable downstream changes: dropped the immediate form of `adv_push` in kernel and standards MASM, marked cross-module-referenced MASM constants and procedures `pub`, migrated to the split `Host`/`BaseHost` trait surface, renamed `Felt::new` call sites to the preserved-behavior `Felt::new_unchecked`, switched `ecdsa_k256_keccak`/`eddsa_25519_sha512` `SecretKey` references to the new `SigningKey`/`KeyExchangeKey` types, and recomputed the kernel's `EMPTY_SMT_ROOT` constant for the Plonky3-aligned Poseidon2 and domain-separated `SmtLeaf::hash` ([#2931](https://github.com/0xMiden/protocol/pull/2931)).
- Derive `Hash` implementation for `StorageMapKey` and `StorageMapKeyHash` to allow using those values as keys in containers ([#2843](https://github.com/0xMiden/protocol/issues/2843)).
- [BREAKING] Replaced `AuthMethod` enum with `AccountAuthComponent`, a thin wrapper around `AccountComponent` carrying an `AccountAuthScheme` tag. Removed `AccessControl::AuthControlled` variant entirely: user-facing faucets always install `Authority::AuthControlled` directly. ([#2944](https://github.com/0xMiden/protocol/pull/2944)).
- [BREAKING] Split `create_fungible_faucet` into `create_user_fungible_faucet` (AuthControlled-style) and `create_network_fungible_faucet` (Ownable2Step / Rbac). The user-facing factory validates that the `AccountAuthComponent` is `AuthSingleSigAcl`, rejecting plain `SingleSig`, `NoAuth`, and `NetworkAccount`. A new `user_faucet_single_sig_acl` convenience helper builds an `AuthSingleSigAcl` with the complete authority-gated setter trigger list. ([#2944](https://github.com/0xMiden/protocol/pull/2944)).
- Optimized `B2AGG` processing with selective load/save of Local Exit Tree frontier entries, halving frontier storage map syscalls ([#2752](https://github.com/0xMiden/protocol/pull/2752)).
- [BREAKING] Removed `AccountType` ([#2939](https://github.com/0xMiden/protocol/pull/2939)).
- [BREAKING] Removed `AccountType` and renamed `AccountStorageMode` to `AccountType` ([#2939](https://github.com/0xMiden/protocol/pull/2939), [#2942](https://github.com/0xMiden/protocol/pull/2942)).

### Fixes

- Fixed `LocalTransactionProver` accumulating `MastForest` entries across `prove()` calls, causing `capacity_overflow` panics in WASM environments where linear memory fragmentation prevents subsequent allocations ([#2918](https://github.com/0xMiden/protocol/pull/2918)).
- Fixed `create_fungible_faucet` leaving authority-gated setters unauthenticated under `AccessControl::AuthControlled`: the `AuthSingleSigAcl` trigger list now contains every authority-gated setter root (`set_max_supply`, `set_description`, `set_logo_uri`, `set_external_link`, `set_mint_policy`, `set_burn_policy`, `set_send_policy`, `set_receive_policy`) in addition to `mint_and_send`. ([#2958](https://github.com/0xMiden/protocol/pull/2958)).
- Fixed `create_fungible_faucet` leaving authority-gated setters unauthenticated under `AccessControl::AuthControlled`: the `AuthSingleSigAcl` trigger list now contains every authority-gated setter root (`set_max_supply`, `set_description`, `set_logo_uri`, `set_external_link`, `set_mint_policy`, `set_burn_policy`, `set_send_policy`, `set_receive_policy`) in addition to `mint_and_send`. Replaced with `create_user_fungible_faucet` which validates the auth component at the type level. ([#2943](https://github.com/0xMiden/protocol/issues/2943), [#2944](https://github.com/0xMiden/protocol/pull/2944), [#2958](https://github.com/0xMiden/protocol/pull/2958)).
- Made deserialization of `AccountCode` more robust ([#2788](https://github.com/0xMiden/protocol/pull/2788)).
- Validated `PartialBlockchain` invariants on deserialization ([#2888](https://github.com/0xMiden/protocol/pull/2888)).
- Fixed `output_note::add_asset` and `output_note::set_attachment` to no longer accept invalid note indices ([#2824](https://github.com/0xMiden/protocol/pull/2824)).
Expand Down
10 changes: 2 additions & 8 deletions crates/miden-standards/src/account/access/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,13 @@ const RBAC_CONTROLLED: u8 = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Authority {
/// Authority is the account's auth component; no extra check is performed by
/// `authority::assert_authorized`.
/// Authority is the account's auth component.
AuthControlled = AUTH_CONTROLLED,
/// Authority is the [`Ownable2Step`][crate::account::access::Ownable2Step] owner; the call
/// must be sent by the registered owner.
/// Authority is the [`Ownable2Step`][crate::account::access::Ownable2Step] owner.
OwnerControlled = OWNER_CONTROLLED,
/// Authority is membership in a specific RBAC role. The call must be sent by an account that
/// holds `role` in the
/// [`RoleBasedAccessControl`][crate::account::access::RoleBasedAccessControl] component.
///
/// Requires the [`RoleBasedAccessControl`][crate::account::access::RoleBasedAccessControl]
/// component to be installed on the account; the MASM helper calls into
/// `rbac::assert_sender_has_role` and will fail to link otherwise.
RbacControlled { role: RoleSymbol } = RBAC_CONTROLLED,
}

Expand Down
48 changes: 14 additions & 34 deletions crates/miden-standards/src/account/access/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,33 @@ pub mod authority;
pub mod ownable2step;
pub mod rbac;

/// Access control configuration for account components.
/// Access control configuration for network-style accounts whose authority-gated setters are
/// gated by an owner / role check rather than by the account's auth component.
///
/// Each variant expands into the set of [`AccountComponent`]s that implement that access
/// control choice **plus** the matching [`Authority`] component. The [`Authority`] is
/// auto-yielded so callers don't need to remember to install it separately and so that the
/// authority discriminator stays in sync with the chosen access mode.
/// User-account faucets (where the auth component is itself the setter gate) install
/// [`Authority::AuthControlled`] directly via factories like
/// [`create_user_fungible_faucet`][crate::account::faucets::create_user_fungible_faucet]; they
/// do not need this enum.
///
/// - [`AccessControl::AuthControlled`] yields just [`Authority::AuthControlled`].
/// - [`AccessControl::Ownable2Step`] yields [`Ownable2Step`] + [`Authority::OwnerControlled`].
/// - [`AccessControl::Rbac`] yields [`Ownable2Step`] + [`RoleBasedAccessControl`] + an
/// [`Authority`]. The `authority_role` field selects which authority kind is installed:
/// - [`AccessControl::Ownable2Step`] → [`Ownable2Step`] + [`Authority::OwnerControlled`]. The
/// setter gate enforces `sender == owner`.
/// - [`AccessControl::Rbac`] [`Ownable2Step`] + [`RoleBasedAccessControl`] + an [`Authority`].
/// The `authority_role` field selects which authority kind is installed:
/// - `None` → [`Authority::OwnerControlled`] (the top-level owner gates `set_*` operations).
/// - `Some(role)` → [`Authority::RbacControlled { role }`] (any holder of `role` gates `set_*`
/// operations).
///
/// Pass to
/// [`AccountBuilder::with_components`][miden_protocol::account::AccountBuilder::with_components]
/// to install the access control components on the account:
///
/// ```no_run
/// use miden_protocol::account::AccountBuilder;
/// use miden_standards::account::access::AccessControl;
/// # let owner: miden_protocol::account::AccountId = unimplemented!();
/// # let init_seed = [0u8; 32];
/// AccountBuilder::new(init_seed)
/// .with_components(AccessControl::Rbac { owner, authority_role: None });
/// ```
///
/// For accounts that don't use the [`AccessControl`] convenience but want to install the
/// [`Authority`] component directly, the [`Authority`] enum can be passed via
/// [`AccountBuilder::with_component`][miden_protocol::account::AccountBuilder::with_component].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AccessControl {
/// No external access control component is installed; access decisions are gated solely
/// by the account's auth component.
AuthControlled,
/// Two-step ownership transfer with the provided initial owner. Authority for `set_*`
/// operations is fixed to the registered owner.
/// Two-step ownership transfer with the provided initial owner. The setter gate enforces
/// `sender == owner`.
Ownable2Step { owner: AccountId },
/// Role-based access control. Includes [`Ownable2Step`] internally; the provided `owner`
/// Role-based access control. Includes [`Ownable2Step`] internally. The provided `owner`
/// becomes the top-level RBAC authority (the account's owner).
///
/// `authority_role` controls which authority is installed alongside RBAC:
/// - `None` (default) → [`Authority::OwnerControlled`]: the top-level `owner` is the sole
/// authority for `set_*` operations (`set_mint_policy`, `set_burn_policy`, metadata setters).
/// RBAC roles can still be granted/revoked but they do not directly gate the
/// RBAC roles can still be granted and revoked but they do not directly gate the
/// authority-protected procedures.
/// - `Some(role)` → [`Authority::RbacControlled { role }`]: any account holding `role` becomes
/// a valid authority for `set_*` operations. Role membership is managed through the standard
Expand All @@ -71,7 +52,6 @@ impl IntoIterator for AccessControl {
/// always included.
fn into_iter(self) -> Self::IntoIter {
match self {
AccessControl::AuthControlled => vec![Authority::AuthControlled.into()].into_iter(),
AccessControl::Ownable2Step { owner } => {
vec![Ownable2Step::new(owner).into(), Authority::OwnerControlled.into()].into_iter()
},
Expand Down
222 changes: 222 additions & 0 deletions crates/miden-standards/src/account/auth/account_auth_component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use alloc::collections::BTreeSet;
use alloc::format;

use miden_protocol::account::AccountComponent;
use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
use miden_protocol::errors::AccountError;
use miden_protocol::note::NoteScriptRoot;

use super::{
AuthGuardedMultisig,
AuthGuardedMultisigConfig,
AuthMultisig,
AuthMultisigConfig,
AuthMultisigSmart,
AuthMultisigSmartConfig,
AuthNetworkAccount,
AuthSingleSig,
AuthSingleSigAcl,
AuthSingleSigAclConfig,
NoAuth,
};

/// Categorical tag describing which standard auth scheme an [`AccountAuthComponent`] wraps.
///
/// Factory functions use this to validate `(access control, auth scheme)` combinations
/// without exposing the concrete component type. `Custom` covers any wrapper not produced
/// by the convenience constructors on [`AccountAuthComponent`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AccountAuthScheme {
NoAuth,
SingleSig,
SingleSigAcl,
NetworkAccount,
Multisig,
GuardedMultisig,
MultisigSmart,
Custom,
}

/// A typed wrapper around an authentication [`AccountComponent`].
///
/// Replaces the previous `AuthMethod` enum: instead of describing what an auth component
/// _could_ be and translating to a concrete component inside each factory, callers construct
/// the auth component themselves with one of the convenience constructors below (or wrap a
/// custom [`AccountComponent`]) and pass the resulting [`AccountAuthComponent`] to a
/// factory. Factories use [`Self::scheme`] to validate the combination.
pub struct AccountAuthComponent {
inner: AccountComponent,
scheme: AccountAuthScheme,
}

impl AccountAuthComponent {
/// Wraps an arbitrary [`AccountComponent`] as an auth component. The component is treated
/// as opaque ([`AccountAuthScheme::Custom`]) and is accepted by factories that allow
/// custom auth components.
pub fn custom(component: AccountComponent) -> Self {
Self {
inner: component,
scheme: AccountAuthScheme::Custom,
}
}

/// Builds a [`NoAuth`] component: the account performs only a nonce increment, no
/// signature verification.
pub fn no_auth() -> Self {
Self {
inner: NoAuth::new().into(),
scheme: AccountAuthScheme::NoAuth,
}
}

/// Builds an [`AuthSingleSig`] component (per-tx signature with no procedure-level ACL).
pub fn single_sig(pub_key: PublicKeyCommitment, scheme: AuthScheme) -> Self {
Self {
inner: AuthSingleSig::new(pub_key, scheme).into(),
scheme: AccountAuthScheme::SingleSig,
}
}

/// Builds an [`AuthSingleSigAcl`] component with the given configuration. Procedure roots
/// in the ACL's trigger list force signature verification when called.
pub fn single_sig_acl(
pub_key: PublicKeyCommitment,
scheme: AuthScheme,
config: AuthSingleSigAclConfig,
) -> Result<Self, AccountError> {
let component = AuthSingleSigAcl::new(pub_key, scheme, config)?.into();
Ok(Self {
inner: component,
scheme: AccountAuthScheme::SingleSigAcl,
})
}

/// Builds an [`AuthNetworkAccount`] component restricted to the given note script roots.
/// The allowlist must be non-empty.
pub fn network_account(
allowed_script_roots: BTreeSet<NoteScriptRoot>,
) -> Result<Self, AccountError> {
let component = AuthNetworkAccount::with_allowlist(allowed_script_roots)
.map_err(|err| {
AccountError::other(format!("invalid network account allowlist: {err}"))
})?
.into();
Ok(Self {
inner: component,
scheme: AccountAuthScheme::NetworkAccount,
})
}

/// Builds an [`AuthMultisig`] component from the given configuration.
pub fn multisig(config: AuthMultisigConfig) -> Result<Self, AccountError> {
let component = AuthMultisig::new(config)?.into();
Ok(Self {
inner: component,
scheme: AccountAuthScheme::Multisig,
})
}

/// Builds an [`AuthGuardedMultisig`] component from the given configuration.
pub fn guarded_multisig(config: AuthGuardedMultisigConfig) -> Result<Self, AccountError> {
let component = AuthGuardedMultisig::new(config)?.into();
Ok(Self {
inner: component,
scheme: AccountAuthScheme::GuardedMultisig,
})
}

/// Builds an [`AuthMultisigSmart`] component from the given configuration.
pub fn multisig_smart(config: AuthMultisigSmartConfig) -> Result<Self, AccountError> {
let component = AuthMultisigSmart::new(config)?.into();
Ok(Self {
inner: component,
scheme: AccountAuthScheme::MultisigSmart,
})
}

/// Returns the [`AccountAuthScheme`] tag identifying which standard auth scheme this
/// component wraps. Returns [`AccountAuthScheme::Custom`] for components built via
/// [`Self::custom`].
pub fn scheme(&self) -> AccountAuthScheme {
self.scheme
}

/// Returns a reference to the underlying [`AccountComponent`].
pub fn as_inner(&self) -> &AccountComponent {
&self.inner
}

/// Consumes `self` and returns the underlying [`AccountComponent`].
pub fn into_inner(self) -> AccountComponent {
self.inner
}
}

impl From<AccountAuthComponent> for AccountComponent {
fn from(value: AccountAuthComponent) -> Self {
value.into_inner()
}
}

impl From<NoAuth> for AccountAuthComponent {
fn from(value: NoAuth) -> Self {
Self {
inner: value.into(),
scheme: AccountAuthScheme::NoAuth,
}
}
}

impl From<AuthSingleSig> for AccountAuthComponent {
fn from(value: AuthSingleSig) -> Self {
Self {
inner: value.into(),
scheme: AccountAuthScheme::SingleSig,
}
}
}

impl From<AuthSingleSigAcl> for AccountAuthComponent {
fn from(value: AuthSingleSigAcl) -> Self {
Self {
inner: value.into(),
scheme: AccountAuthScheme::SingleSigAcl,
}
}
}

impl From<AuthNetworkAccount> for AccountAuthComponent {
fn from(value: AuthNetworkAccount) -> Self {
Self {
inner: value.into(),
scheme: AccountAuthScheme::NetworkAccount,
}
}
}

impl From<AuthMultisig> for AccountAuthComponent {
fn from(value: AuthMultisig) -> Self {
Self {
inner: value.into(),
scheme: AccountAuthScheme::Multisig,
}
}
}

impl From<AuthGuardedMultisig> for AccountAuthComponent {
fn from(value: AuthGuardedMultisig) -> Self {
Self {
inner: value.into(),
scheme: AccountAuthScheme::GuardedMultisig,
}
}
}

impl From<AuthMultisigSmart> for AccountAuthComponent {
fn from(value: AuthMultisigSmart) -> Self {
Self {
inner: value.into(),
scheme: AccountAuthScheme::MultisigSmart,
}
}
}
3 changes: 3 additions & 0 deletions crates/miden-standards/src/account/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod account_auth_component;
pub use account_auth_component::{AccountAuthComponent, AccountAuthScheme};

mod no_auth;
pub use no_auth::NoAuth;

Expand Down
Loading
Loading