diff --git a/CHANGELOG.md b/CHANGELOG.md index a3c6b94549..a5f4122a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,8 @@ - [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)). @@ -76,7 +78,7 @@ ### 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)). diff --git a/crates/miden-standards/src/account/access/authority.rs b/crates/miden-standards/src/account/access/authority.rs index a58c9d874b..e7c75a4f79 100644 --- a/crates/miden-standards/src/account/access/authority.rs +++ b/crates/miden-standards/src/account/access/authority.rs @@ -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, } diff --git a/crates/miden-standards/src/account/access/mod.rs b/crates/miden-standards/src/account/access/mod.rs index 085f18d847..2120a0c82c 100644 --- a/crates/miden-standards/src/account/access/mod.rs +++ b/crates/miden-standards/src/account/access/mod.rs @@ -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 @@ -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() }, diff --git a/crates/miden-standards/src/account/auth/account_auth_component.rs b/crates/miden-standards/src/account/auth/account_auth_component.rs new file mode 100644 index 0000000000..a51b04ef2d --- /dev/null +++ b/crates/miden-standards/src/account/auth/account_auth_component.rs @@ -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 { + 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, + ) -> Result { + 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 { + 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 { + 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 { + 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 for AccountComponent { + fn from(value: AccountAuthComponent) -> Self { + value.into_inner() + } +} + +impl From for AccountAuthComponent { + fn from(value: NoAuth) -> Self { + Self { + inner: value.into(), + scheme: AccountAuthScheme::NoAuth, + } + } +} + +impl From for AccountAuthComponent { + fn from(value: AuthSingleSig) -> Self { + Self { + inner: value.into(), + scheme: AccountAuthScheme::SingleSig, + } + } +} + +impl From for AccountAuthComponent { + fn from(value: AuthSingleSigAcl) -> Self { + Self { + inner: value.into(), + scheme: AccountAuthScheme::SingleSigAcl, + } + } +} + +impl From for AccountAuthComponent { + fn from(value: AuthNetworkAccount) -> Self { + Self { + inner: value.into(), + scheme: AccountAuthScheme::NetworkAccount, + } + } +} + +impl From for AccountAuthComponent { + fn from(value: AuthMultisig) -> Self { + Self { + inner: value.into(), + scheme: AccountAuthScheme::Multisig, + } + } +} + +impl From for AccountAuthComponent { + fn from(value: AuthGuardedMultisig) -> Self { + Self { + inner: value.into(), + scheme: AccountAuthScheme::GuardedMultisig, + } + } +} + +impl From for AccountAuthComponent { + fn from(value: AuthMultisigSmart) -> Self { + Self { + inner: value.into(), + scheme: AccountAuthScheme::MultisigSmart, + } + } +} diff --git a/crates/miden-standards/src/account/auth/mod.rs b/crates/miden-standards/src/account/auth/mod.rs index e6caf01607..2be83ed73f 100644 --- a/crates/miden-standards/src/account/auth/mod.rs +++ b/crates/miden-standards/src/account/auth/mod.rs @@ -1,3 +1,6 @@ +mod account_auth_component; +pub use account_auth_component::{AccountAuthComponent, AccountAuthScheme}; + mod no_auth; pub use no_auth::NoAuth; diff --git a/crates/miden-standards/src/account/faucets/fungible/mod.rs b/crates/miden-standards/src/account/faucets/fungible/mod.rs index 91f5f1e02e..5f3eb52c13 100644 --- a/crates/miden-standards/src/account/faucets/fungible/mod.rs +++ b/crates/miden-standards/src/account/faucets/fungible/mod.rs @@ -32,12 +32,12 @@ use super::{ TokenMetadataError, TokenName, }; -use crate::account::access::AccessControl; +use crate::account::access::{AccessControl, Authority}; use crate::account::account_component_code; -use crate::account::auth::{AuthNetworkAccount, AuthSingleSigAcl, AuthSingleSigAclConfig, NoAuth}; +use crate::account::auth::{AccountAuthComponent, AccountAuthScheme, AuthSingleSigAclConfig}; use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt}; use crate::account::policies::TokenPolicyManager; -use crate::{AuthMethod, procedure_root}; +use crate::procedure_root; #[cfg(test)] mod tests; @@ -283,22 +283,25 @@ impl FungibleFaucet { *FUNGIBLE_FAUCET_RECEIVE_AND_BURN } - /// Returns the procedure root of the `set_max_supply` account procedure. + /// Returns the procedure root of the `set_max_supply` account procedure. This is an + /// authority-gated setter; when paired with `Authority::AuthControlled` (via + /// [`create_user_fungible_faucet`]) it must appear in the auth component's trigger + /// procedure list. pub fn set_max_supply_root() -> AccountProcedureRoot { *FUNGIBLE_FAUCET_SET_MAX_SUPPLY } - /// Returns the procedure root of the `set_description` account procedure. + /// Returns the procedure root of the `set_description` account procedure. Authority-gated. pub fn set_description_root() -> AccountProcedureRoot { *FUNGIBLE_FAUCET_SET_DESCRIPTION } - /// Returns the procedure root of the `set_logo_uri` account procedure. + /// Returns the procedure root of the `set_logo_uri` account procedure. Authority-gated. pub fn set_logo_uri_root() -> AccountProcedureRoot { *FUNGIBLE_FAUCET_SET_LOGO_URI } - /// Returns the procedure root of the `set_external_link` account procedure. + /// Returns the procedure root of the `set_external_link` account procedure. Authority-gated. pub fn set_external_link_root() -> AccountProcedureRoot { *FUNGIBLE_FAUCET_SET_EXTERNAL_LINK } @@ -552,10 +555,12 @@ impl TryFrom<&Account> for FungibleFaucet { // FACTORY // ================================================================================================ -/// Every authority-gated procedure root that must require a signature when -/// [`AccessControl::AuthControlled`] is paired with [`AuthMethod::SingleSig`]. Includes -/// `mint_and_send` so that minting always requires a signature regardless of the access -/// control variant. +/// Returns every authority-gated setter procedure root exported by a fungible faucet account. +/// +/// Under [`Authority::AuthControlled`] the auth component must authenticate calls to all of +/// these procedures, otherwise the setters become permissionless. This list is the single +/// source of truth used by [`create_user_fungible_faucet`] when configuring the +/// [`AuthSingleSigAcl`][crate::account::auth::AuthSingleSigAcl] trigger procedure list. fn all_authority_gated_setter_roots() -> Vec { vec![ FungibleFaucet::mint_and_send_root(), @@ -570,35 +575,74 @@ fn all_authority_gated_setter_roots() -> Vec { ] } -/// Creates a new fungible faucet account by composing the required components. -/// -/// Only specific `(access_control, auth_method)` combinations are supported; everything else -/// is rejected at the factory level. The valid combinations are: +/// Creates a new **user-account** fungible faucet. The account's auth component is the sole +/// gate for authority-protected setters ([`Authority::AuthControlled`] is installed directly). /// -/// - [`AccessControl::AuthControlled`] + [`AuthMethod::SingleSig`] — user-account faucet whose auth -/// component is the sole gate for every authority-protected setter. -/// - [`AccessControl::Ownable2Step`] / [`AccessControl::Rbac`] + [`AuthMethod::NetworkAccount`] or -/// [`AuthMethod::NoAuth`] — network-style faucet whose setter gate is enforced in-procedure by -/// the owner/role check. +/// The caller provides any [`AccountAuthComponent`]; the factory validates the scheme: /// -/// All other pairings return a typed error: -/// [`FungibleFaucetError::IncompatibleAuthControlledAuth`] for `AuthControlled + NoAuth`, and -/// [`FungibleFaucetError::UnsupportedAccessControlAuthCombination`] for `AuthControlled + -/// NetworkAccount` and for `Ownable2Step`/`Rbac` + `SingleSig`. `Multisig` and `Unknown` -/// remain rejected for every variant via [`FungibleFaucetError::UnsupportedAuthMethod`]. -pub fn create_fungible_faucet( +/// - [`AccountAuthScheme::SingleSig`]: rejected — under AuthControlled, every authority-gated +/// setter must be tracked by the auth component. Construct an ACL via +/// [`AccountAuthComponent::single_sig_acl`] instead (the trigger list is irrelevant; the factory +/// rebuilds it). +/// - [`AccountAuthScheme::SingleSigAcl`]: the factory installs the provided component as-is; +/// callers should already have populated the trigger list with every authority-gated setter root. +/// [`user_faucet_single_sig_acl`] is the recommended convenience constructor. +/// - [`AccountAuthScheme::NoAuth`]: rejected via +/// [`FungibleFaucetError::IncompatibleAuthControlledAuth`] — would leave setters permissionless. +/// - [`AccountAuthScheme::NetworkAccount`]: rejected — network-style faucets must use +/// [`create_network_fungible_faucet`] with [`AccessControl::Ownable2Step`] or +/// [`AccessControl::Rbac`]. +/// - [`AccountAuthScheme::Custom`]: accepted; the caller is responsible for ensuring the custom +/// auth component authenticates every authority-gated setter root. +/// - Multisig variants: rejected via [`FungibleFaucetError::UnsupportedAuthMethod`]. +pub fn create_user_fungible_faucet( init_seed: [u8; 32], faucet: FungibleFaucet, + auth_component: AccountAuthComponent, + token_policy_manager: TokenPolicyManager, account_type: AccountType, - auth_method: AuthMethod, +) -> Result { + let auth_component = validate_user_faucet_auth(auth_component)?; + + let account = AccountBuilder::new(init_seed) + .account_type(account_type) + .with_auth_component(auth_component.into_inner()) + .with_component(faucet) + .with_component(Authority::AuthControlled) + .with_components(token_policy_manager) + .build() + .map_err(FungibleFaucetError::AccountError)?; + + Ok(account) +} + +/// Creates a new **network-style** fungible faucet. Setter gating is enforced in-procedure by +/// the owner / role check installed via `access_control` ([`AccessControl::Ownable2Step`] or +/// [`AccessControl::Rbac`]). The auth component only governs the faucet's own transaction +/// authentication. +/// +/// Validates the auth scheme: +/// +/// - [`AccountAuthScheme::NetworkAccount`], [`AccountAuthScheme::NoAuth`], +/// [`AccountAuthScheme::Custom`]: accepted. +/// - [`AccountAuthScheme::SingleSig`], [`AccountAuthScheme::SingleSigAcl`]: rejected via +/// [`FungibleFaucetError::UnsupportedAccessControlAuthCombination`] — SingleSig is for +/// user-account faucets (see [`create_user_fungible_faucet`]); pairing it here duplicates the +/// setter check with a per-tx signature that doesn't add security. +/// - Multisig variants: rejected via [`FungibleFaucetError::UnsupportedAuthMethod`]. +pub fn create_network_fungible_faucet( + init_seed: [u8; 32], + faucet: FungibleFaucet, access_control: AccessControl, + auth_component: AccountAuthComponent, token_policy_manager: TokenPolicyManager, + account_type: AccountType, ) -> Result { - let auth_component = build_auth_component(&access_control, auth_method)?; + let auth_component = validate_network_faucet_auth(auth_component)?; let account = AccountBuilder::new(init_seed) .account_type(account_type) - .with_auth_component(auth_component) + .with_auth_component(auth_component.into_inner()) .with_component(faucet) .with_components(access_control) .with_components(token_policy_manager) @@ -608,84 +652,83 @@ pub fn create_fungible_faucet( Ok(account) } -/// Builds the account-level auth component, validating the `(access_control, auth_method)` -/// pair. See [`create_fungible_faucet`] for the list of supported combinations. -fn build_auth_component( - access_control: &AccessControl, - auth_method: AuthMethod, -) -> Result { - match (access_control, auth_method) { - // AuthControlled + SingleSig: the auth component is the sole setter gate, so it - // must authenticate every authority-gated setter root. - ( - AccessControl::AuthControlled, - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }, - ) => Ok(AuthSingleSigAcl::new( - pub_key, - auth_scheme, - AuthSingleSigAclConfig::new() - .with_auth_trigger_procedures(all_authority_gated_setter_roots()) - .with_allow_unauthorized_input_notes(true), - ) - .map_err(FungibleFaucetError::AccountError)? - .into()), - - // AuthControlled + NetworkAccount: rejected. - (AccessControl::AuthControlled, AuthMethod::NetworkAccount { .. }) => { +/// Validates and (when needed) rewrites the auth component for a user-account faucet. +/// +/// For [`AccountAuthScheme::SingleSigAcl`] the configured ACL is left intact — the caller is +/// trusted to have included all authority-gated setter roots in the trigger list. Use +/// [`all_authority_gated_setter_roots`] when constructing the ACL configuration. +fn validate_user_faucet_auth( + auth_component: AccountAuthComponent, +) -> Result { + match auth_component.scheme() { + AccountAuthScheme::SingleSigAcl => Ok(auth_component), + AccountAuthScheme::Custom => Ok(auth_component), + AccountAuthScheme::SingleSig => { Err(FungibleFaucetError::UnsupportedAccessControlAuthCombination( - "NetworkAccount is only supported with AccessControl::Ownable2Step or \ - AccessControl::Rbac (network-style faucets)" + "plain SingleSig cannot gate authority-protected setters under AuthControlled; \ + use AccountAuthComponent::single_sig_acl with all_authority_gated_setter_roots() \ + as the trigger list." .into(), )) }, - - // AuthControlled + NoAuth: rejected. NoAuth cannot authenticate setters; under - // AuthControlled the auth component is the sole gate, so this would leave every - // authority-gated setter permissionless. - (AccessControl::AuthControlled, AuthMethod::NoAuth) => { - Err(FungibleFaucetError::IncompatibleAuthControlledAuth( - "NoAuth cannot authenticate authority-gated setters".into(), + AccountAuthScheme::NoAuth => Err(FungibleFaucetError::IncompatibleAuthControlledAuth( + "NoAuth cannot authenticate authority-gated setters".into(), + )), + AccountAuthScheme::NetworkAccount => { + Err(FungibleFaucetError::UnsupportedAccessControlAuthCombination( + "NetworkAccount is only supported with create_network_fungible_faucet \ + (AccessControl::Ownable2Step / Rbac)" + .into(), )) }, - - // Ownable2Step / Rbac + NetworkAccount: typical network-style faucet. Setter gating - // is enforced in-procedure; the auth component restricts which note scripts can be - // consumed against the faucet. - ( - AccessControl::Ownable2Step { .. } | AccessControl::Rbac { .. }, - AuthMethod::NetworkAccount { allowed_script_roots }, - ) => Ok(AuthNetworkAccount::with_allowlist(allowed_script_roots) - .map_err(|err| { - FungibleFaucetError::UnsupportedAuthMethod(alloc::format!( - "invalid network account allowlist: {err}" - )) - })? - .into()), - - // Ownable2Step / Rbac + NoAuth: valid; the setter gate is the in-procedure owner / - // role check, so the account-level auth can legitimately be NoAuth. - (AccessControl::Ownable2Step { .. } | AccessControl::Rbac { .. }, AuthMethod::NoAuth) => { - Ok(NoAuth::new().into()) - }, - - // Ownable2Step / Rbac + SingleSig: rejected. SingleSig is for user-account faucets - // (AuthControlled); under owner/role-gated faucets it duplicates the setter check - // with a per-tx signature that doesn't add security. - ( - AccessControl::Ownable2Step { .. } | AccessControl::Rbac { .. }, - AuthMethod::SingleSig { .. }, - ) => Err(FungibleFaucetError::UnsupportedAccessControlAuthCombination( - "SingleSig is only supported with AccessControl::AuthControlled; pair \ - Ownable2Step / Rbac with NetworkAccount or NoAuth instead" - .into(), + AccountAuthScheme::Multisig + | AccountAuthScheme::GuardedMultisig + | AccountAuthScheme::MultisigSmart => Err(FungibleFaucetError::UnsupportedAuthMethod( + "fungible faucets do not support Multisig authentication".into(), )), + } +} - // Multisig and Unknown are not supported for any access control variant. - (_, AuthMethod::Multisig { .. }) => Err(FungibleFaucetError::UnsupportedAuthMethod( +/// Validates the auth component for a network-style faucet. SingleSig variants are rejected; +/// network or no-auth (and custom) are allowed. +fn validate_network_faucet_auth( + auth_component: AccountAuthComponent, +) -> Result { + match auth_component.scheme() { + AccountAuthScheme::NetworkAccount + | AccountAuthScheme::NoAuth + | AccountAuthScheme::Custom => Ok(auth_component), + AccountAuthScheme::SingleSig | AccountAuthScheme::SingleSigAcl => { + Err(FungibleFaucetError::UnsupportedAccessControlAuthCombination( + "SingleSig is only supported with create_user_fungible_faucet \ + (AccessControl::AuthControlled)" + .into(), + )) + }, + AccountAuthScheme::Multisig + | AccountAuthScheme::GuardedMultisig + | AccountAuthScheme::MultisigSmart => Err(FungibleFaucetError::UnsupportedAuthMethod( "fungible faucets do not support Multisig authentication".into(), )), - (_, AuthMethod::Unknown) => Err(FungibleFaucetError::UnsupportedAuthMethod( - "fungible faucets cannot be created with Unknown authentication method".into(), - )), } } + +/// Convenience constructor for the typical user-account faucet auth component: +/// [`AccountAuthComponent::single_sig_acl`] with the trigger procedure list covering every +/// authority-gated setter and `allow_unauthorized_input_notes=true`. +/// +/// Use this when calling [`create_user_fungible_faucet`] with `AuthScheme::Falcon512Poseidon2` +/// or `AuthScheme::EcdsaK256Keccak` to ensure every authority-gated setter forces a signature. +pub fn user_faucet_single_sig_acl( + pub_key: miden_protocol::account::auth::PublicKeyCommitment, + scheme: miden_protocol::account::auth::AuthScheme, +) -> Result { + AccountAuthComponent::single_sig_acl( + pub_key, + scheme, + AuthSingleSigAclConfig::new() + .with_auth_trigger_procedures(all_authority_gated_setter_roots()) + .with_allow_unauthorized_input_notes(true), + ) + .map_err(FungibleFaucetError::AccountError) +} diff --git a/crates/miden-standards/src/account/faucets/fungible/tests.rs b/crates/miden-standards/src/account/faucets/fungible/tests.rs index 07d2714871..9b064a9ba3 100644 --- a/crates/miden-standards/src/account/faucets/fungible/tests.rs +++ b/crates/miden-standards/src/account/faucets/fungible/tests.rs @@ -6,10 +6,14 @@ use miden_protocol::account::{AccountBuilder, AccountType}; use miden_protocol::asset::{AssetAmount, TokenSymbol}; use miden_protocol::{Felt, Word}; -use super::{FungibleFaucet, create_fungible_faucet}; -use crate::AuthMethod; +use super::{ + FungibleFaucet, + create_network_fungible_faucet, + create_user_fungible_faucet, + user_faucet_single_sig_acl, +}; use crate::account::access::AccessControl; -use crate::account::auth::{AuthSingleSig, AuthSingleSigAcl}; +use crate::account::auth::{AccountAuthComponent, AuthSingleSig, AuthSingleSigAcl}; use crate::account::faucets::{Description, FungibleFaucetError, TokenMetadata, TokenName}; use crate::account::policies::{ BurnPolicyConfig, @@ -64,13 +68,8 @@ fn read_trigger_procedure_roots( } #[test] -fn faucet_contract_creation() { +fn user_fungible_faucet_with_single_sig_acl() { let pub_key_word = Word::new([Felt::ONE; 4]); - let auth_method = AuthMethod::SingleSig { - approver: (pub_key_word.into(), AuthScheme::Falcon512Poseidon2), - }; - - // we need to use an initial seed to create the wallet account let init_seed: [u8; 32] = [ 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85, 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16, @@ -81,35 +80,30 @@ fn faucet_contract_creation() { let token_name_string = "polygon"; let description_string = "A polygon token"; - let faucet = sample_faucet(); - let faucet_account = create_fungible_faucet( + let auth_component = + user_faucet_single_sig_acl(pub_key_word.into(), AuthScheme::Falcon512Poseidon2).unwrap(); + + let faucet_account = create_user_fungible_faucet( init_seed, - faucet, - AccountType::Private, - auth_method, - AccessControl::AuthControlled, + sample_faucet(), + auth_component, allow_all_policy_manager(), + AccountType::Private, ) .unwrap(); - // The falcon auth component's public key should be present. + // The auth component's public key should be present. assert_eq!( faucet_account.storage().get_item(AuthSingleSigAcl::public_key_slot()).unwrap(), pub_key_word ); - // The config slot of the auth component stores: - // [num_trigger_procs, allow_unauthorized_output_notes, allow_unauthorized_input_notes, 0]. - // - // With 9 authority-gated trigger procedures (mint_and_send + 4 token metadata setters + - // 4 policy setters), allow_unauthorized_output_notes=false, and - // allow_unauthorized_input_notes=true, this should be [9, 0, 1, 0]. + // Config slot: 9 trigger procedures, allow_unauthorized_input_notes=true → [9, 0, 1, 0]. assert_eq!( faucet_account.storage().get_item(AuthSingleSigAcl::config_slot()).unwrap(), [Felt::from(9_u32), Felt::ZERO, Felt::ONE, Felt::ZERO].into() ); - // The trigger procedure root map should contain every authority-gated setter root. let stored_roots = read_trigger_procedure_roots(&faucet_account, 9); let expected_roots: BTreeSet = [ FungibleFaucet::mint_and_send_root(), @@ -127,14 +121,12 @@ fn faucet_contract_creation() { .collect(); assert_eq!(stored_roots, expected_roots); - // Check that faucet metadata was initialized to the given values. - // Storage layout: [token_supply, max_supply, decimals, symbol] + // Token config slot layout: [token_supply, max_supply, decimals, symbol] assert_eq!( faucet_account.storage().get_item(FungibleFaucet::token_config_slot()).unwrap(), [Felt::ZERO, Felt::from(123_u32), Felt::from(2_u32), token_symbol.into()].into() ); - // Check that name was stored let name_0 = faucet_account.storage().get_item(TokenMetadata::name_chunk_0_slot()).unwrap(); let name_1 = faucet_account.storage().get_item(TokenMetadata::name_chunk_1_slot()).unwrap(); let decoded_name = TokenName::try_from_words(&[name_0, name_1]).unwrap(); @@ -145,74 +137,86 @@ fn faucet_contract_creation() { assert_eq!(chunk, *expected); } - // Verify the faucet component can be extracted let _faucet_component = FungibleFaucet::try_from(faucet_account.clone()).unwrap(); } +/// `create_user_fungible_faucet` with `NoAuth` must be rejected: the auth component is the +/// sole gate for authority-protected setters. #[test] -fn auth_controlled_rejects_no_auth() { - let err = create_fungible_faucet( +fn user_fungible_faucet_rejects_no_auth() { + let err = create_user_fungible_faucet( [7u8; 32], sample_faucet(), - AccountType::Private, - AuthMethod::NoAuth, - AccessControl::AuthControlled, + AccountAuthComponent::no_auth(), allow_all_policy_manager(), + AccountType::Private, ) - .expect_err("AuthControlled+NoAuth should be rejected"); + .expect_err("user faucet with NoAuth should be rejected"); assert_matches!(err, FungibleFaucetError::IncompatibleAuthControlledAuth(_)); } -/// `(Ownable2Step / Rbac, SingleSig)` must be rejected: SingleSig is intended for -/// user-account faucets gated by `AuthControlled`; under owner/role-gated faucets it -/// duplicates the setter check with a per-tx signature that doesn't add security. +/// `create_user_fungible_faucet` with a plain `SingleSig` (no ACL) must be rejected: the +/// trigger list would be empty, leaving authority-gated setters permissionless. Callers must +/// use `user_faucet_single_sig_acl`. #[test] -fn ownable2step_rejects_single_sig() { - use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; - - let owner = miden_protocol::account::AccountId::try_from( - ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, +fn user_fungible_faucet_rejects_plain_single_sig() { + let pub_key_word = Word::new([Felt::ONE; 4]); + let err = create_user_fungible_faucet( + [7u8; 32], + sample_faucet(), + AccountAuthComponent::single_sig(pub_key_word.into(), AuthScheme::Falcon512Poseidon2), + allow_all_policy_manager(), + AccountType::Private, ) - .unwrap(); - let auth_method = AuthMethod::SingleSig { - approver: (Word::new([Felt::ONE; 4]).into(), AuthScheme::Falcon512Poseidon2), - }; + .expect_err("user faucet with plain SingleSig should be rejected"); + assert_matches!(err, FungibleFaucetError::UnsupportedAccessControlAuthCombination(_)); +} - let err = create_fungible_faucet( +/// `create_user_fungible_faucet` with NetworkAccount must be rejected: network-style auth +/// belongs with `create_network_fungible_faucet`. +#[test] +fn user_fungible_faucet_rejects_network_account() { + let allowlist: BTreeSet = + [miden_protocol::note::NoteScriptRoot::from_raw(Word::new([Felt::ONE; 4]))] + .into_iter() + .collect(); + let err = create_user_fungible_faucet( [7u8; 32], sample_faucet(), - AccountType::Public, - auth_method, - AccessControl::Ownable2Step { owner }, + AccountAuthComponent::network_account(allowlist).unwrap(), allow_all_policy_manager(), + AccountType::Private, ) - .expect_err("Ownable2Step+SingleSig should be rejected"); + .expect_err("user faucet with NetworkAccount should be rejected"); assert_matches!(err, FungibleFaucetError::UnsupportedAccessControlAuthCombination(_)); } -/// `(AuthControlled, NetworkAccount)` must be rejected: `NetworkAccount` is the auth scheme -/// for network-style faucets, which pair with owner / role-based setter gating -/// (`Ownable2Step` / `Rbac`), not the auth-component-as-gate model of `AuthControlled`. +/// `create_network_fungible_faucet` with `Ownable2Step + NoAuth` is a valid configuration: the +/// setter gate is enforced in-procedure (`assert_sender_is_owner`). #[test] -fn auth_controlled_rejects_network_account() { - use alloc::collections::BTreeSet; +fn network_fungible_faucet_ownable2step_with_no_auth_is_accepted() { + use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; - let allowed_script_roots: BTreeSet = BTreeSet::new(); + let owner = miden_protocol::account::AccountId::try_from( + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, + ) + .unwrap(); - let err = create_fungible_faucet( + let _account = create_network_fungible_faucet( [7u8; 32], sample_faucet(), - AccountType::Private, - AuthMethod::NetworkAccount { allowed_script_roots }, - AccessControl::AuthControlled, + AccessControl::Ownable2Step { owner }, + AccountAuthComponent::no_auth(), allow_all_policy_manager(), + AccountType::Public, ) - .expect_err("AuthControlled+NetworkAccount should be rejected"); - assert_matches!(err, FungibleFaucetError::UnsupportedAccessControlAuthCombination(_)); + .expect("Ownable2Step+NoAuth should be accepted"); } +/// `create_network_fungible_faucet` with SingleSig must be rejected: SingleSig belongs with +/// the user-account faucet factory. #[test] -fn ownable2step_with_no_auth_is_accepted() { +fn network_fungible_faucet_rejects_single_sig() { use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; let owner = miden_protocol::account::AccountId::try_from( @@ -220,25 +224,27 @@ fn ownable2step_with_no_auth_is_accepted() { ) .unwrap(); - let _account = create_fungible_faucet( + let err = create_network_fungible_faucet( [7u8; 32], sample_faucet(), - AccountType::Public, - AuthMethod::NoAuth, AccessControl::Ownable2Step { owner }, + AccountAuthComponent::single_sig( + Word::new([Felt::ONE; 4]).into(), + AuthScheme::Falcon512Poseidon2, + ), allow_all_policy_manager(), + AccountType::Public, ) - .expect("Ownable2Step+NoAuth should be accepted"); + .expect_err("network faucet with SingleSig should be rejected"); + assert_matches!(err, FungibleFaucetError::UnsupportedAccessControlAuthCombination(_)); } #[test] fn faucet_create_from_account() { - // prepare the test data let mock_word = Word::from([0, 1, 2, 3u32]); let mock_public_key = PublicKeyCommitment::from(mock_word); let mock_seed = mock_word.as_bytes(); - // valid account let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol"); let faucet = FungibleFaucet::builder() .name(TokenName::new("POL").unwrap()) @@ -260,7 +266,6 @@ fn faucet_create_from_account() { // invalid account: fungible faucet component is missing let invalid_faucet_account = AccountBuilder::new(mock_seed) .with_auth_component(AuthSingleSig::new(mock_public_key, AuthScheme::Falcon512Poseidon2)) - // we need to add some other component so the builder doesn't fail .with_component(BasicWallet) .build_existing() .expect("failed to create wallet account"); diff --git a/crates/miden-standards/src/account/faucets/mod.rs b/crates/miden-standards/src/account/faucets/mod.rs index 4e8294ded5..047464de47 100644 --- a/crates/miden-standards/src/account/faucets/mod.rs +++ b/crates/miden-standards/src/account/faucets/mod.rs @@ -10,7 +10,13 @@ use crate::utils::FixedWidthStringError; mod fungible; mod token_metadata; -pub use fungible::{FungibleFaucet, FungibleFaucetBuilder, create_fungible_faucet}; +pub use fungible::{ + FungibleFaucet, + FungibleFaucetBuilder, + create_network_fungible_faucet, + create_user_fungible_faucet, + user_faucet_single_sig_acl, +}; pub use token_metadata::{Description, ExternalLink, LogoURI, TokenMetadata, TokenName}; // TOKEN METADATA ERROR @@ -59,9 +65,9 @@ pub enum FungibleFaucetError { MissingFungibleFaucetInterface, #[error("unsupported authentication method: {0}")] UnsupportedAuthMethod(String), - #[error("AccessControl::AuthControlled is incompatible with the chosen auth method: {0}")] + #[error("AuthControlled is incompatible with the chosen auth method: {0}")] IncompatibleAuthControlledAuth(String), - #[error("unsupported combination of AccessControl and AuthMethod: {0}")] + #[error("unsupported combination of access control and auth method: {0}")] UnsupportedAccessControlAuthCombination(String), #[error("account creation failed")] AccountError(#[source] AccountError), diff --git a/crates/miden-standards/src/account/interface/component.rs b/crates/miden-standards/src/account/interface/component.rs index a386fa0c0c..d4bf62ae48 100644 --- a/crates/miden-standards/src/account/interface/component.rs +++ b/crates/miden-standards/src/account/interface/component.rs @@ -1,20 +1,11 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; -use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment}; -use miden_protocol::account::{AccountId, AccountProcedureRoot, AccountStorage, StorageSlotName}; +use miden_protocol::Felt; +use miden_protocol::account::{AccountId, AccountProcedureRoot}; use miden_protocol::note::PartialNote; -use miden_protocol::{Felt, Word}; -use crate::AuthMethod; -use crate::account::auth::{ - AuthGuardedMultisig, - AuthMultisig, - AuthMultisigSmart, - AuthSingleSig, - AuthSingleSigAcl, - NetworkAccountNoteAllowlist, -}; +use crate::account::auth::AccountAuthScheme; use crate::account::interface::AccountInterfaceError; // ACCOUNT COMPONENT INTERFACE @@ -121,48 +112,22 @@ impl AccountComponentInterface { ) } - /// Returns the authentication schemes associated with this component interface. - pub fn get_auth_methods(&self, storage: &AccountStorage) -> Vec { + /// Returns the [`AccountAuthScheme`] tag for this component interface, if it is an auth + /// component. Returns [`None`] for non-auth components. + pub fn auth_scheme(&self) -> Option { match self { - AccountComponentInterface::AuthSingleSig => vec![extract_singlesig_auth_method( - storage, - AuthSingleSig::public_key_slot(), - AuthSingleSig::scheme_id_slot(), - )], - AccountComponentInterface::AuthSingleSigAcl => vec![extract_singlesig_auth_method( - storage, - AuthSingleSigAcl::public_key_slot(), - AuthSingleSigAcl::scheme_id_slot(), - )], - AccountComponentInterface::AuthMultisig => { - vec![extract_multisig_auth_method( - storage, - AuthMultisig::threshold_config_slot(), - AuthMultisig::approver_public_keys_slot(), - AuthMultisig::approver_scheme_ids_slot(), - )] - }, + AccountComponentInterface::AuthSingleSig => Some(AccountAuthScheme::SingleSig), + AccountComponentInterface::AuthSingleSigAcl => Some(AccountAuthScheme::SingleSigAcl), + AccountComponentInterface::AuthMultisig => Some(AccountAuthScheme::Multisig), + AccountComponentInterface::AuthMultisigSmart => Some(AccountAuthScheme::MultisigSmart), AccountComponentInterface::AuthGuardedMultisig => { - vec![extract_multisig_auth_method( - storage, - AuthGuardedMultisig::threshold_config_slot(), - AuthGuardedMultisig::approver_public_keys_slot(), - AuthGuardedMultisig::approver_scheme_ids_slot(), - )] + Some(AccountAuthScheme::GuardedMultisig) }, - AccountComponentInterface::AuthMultisigSmart => { - vec![extract_multisig_auth_method( - storage, - AuthMultisigSmart::threshold_config_slot(), - AuthMultisigSmart::approver_public_keys_slot(), - AuthMultisigSmart::approver_scheme_ids_slot(), - )] - }, - AccountComponentInterface::AuthNoAuth => vec![AuthMethod::NoAuth], + AccountComponentInterface::AuthNoAuth => Some(AccountAuthScheme::NoAuth), AccountComponentInterface::AuthNetworkAccount => { - vec![extract_network_account_auth_method(storage)] + Some(AccountAuthScheme::NetworkAccount) }, - _ => vec![], // Non-auth components return empty vector + _ => None, } } @@ -328,92 +293,3 @@ impl AccountComponentInterface { Ok(body) } } - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Extracts authentication method from a single-signature component. -fn extract_singlesig_auth_method( - storage: &AccountStorage, - public_key_slot: &StorageSlotName, - scheme_id_slot: &StorageSlotName, -) -> AuthMethod { - let pub_key = PublicKeyCommitment::from( - storage - .get_item(public_key_slot) - .expect("invalid storage index of the public key"), - ); - - let scheme_id = storage - .get_item(scheme_id_slot) - .expect("invalid storage index of the scheme id")[0] - .as_canonical_u64() as u8; - - let auth_scheme = - AuthScheme::try_from(scheme_id).expect("invalid auth scheme id in the scheme id slot"); - - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } -} - -/// Extracts authentication method from a multisig component. -fn extract_multisig_auth_method( - storage: &AccountStorage, - config_slot: &StorageSlotName, - approver_public_keys_slot: &StorageSlotName, - approver_scheme_ids_slot: &StorageSlotName, -) -> AuthMethod { - // Read the multisig configuration from the config slot - // Format: [threshold, num_approvers, 0, 0] - let config = storage - .get_item(config_slot) - .expect("invalid slot name of the multisig configuration"); - - let threshold = config[0].as_canonical_u64() as u32; - let num_approvers = config[1].as_canonical_u64() as u8; - - let mut approvers = Vec::new(); - - // Read each public key from the map - for key_index in 0..num_approvers { - // The multisig component stores keys and scheme IDs using pattern [index, 0, 0, 0] - let map_key = Word::from([key_index as u32, 0, 0, 0]); - - let pub_key_word = - storage.get_map_item(approver_public_keys_slot, map_key).unwrap_or_else(|_| { - panic!( - "Failed to read public key {} from multisig configuration at storage slot {}. \ - Expected key pattern [index, 0, 0, 0].", - key_index, approver_public_keys_slot - ) - }); - - let pub_key = PublicKeyCommitment::from(pub_key_word); - - let scheme_word = storage - .get_map_item(approver_scheme_ids_slot, map_key) - .unwrap_or_else(|_| { - panic!( - "Failed to read scheme id for approver {} from multisig configuration at storage slot {}. \ - Expected key pattern [index, 0, 0, 0].", - key_index, approver_scheme_ids_slot - ) - }); - - let scheme_id = scheme_word[0].as_canonical_u64() as u8; - let auth_scheme = - AuthScheme::try_from(scheme_id).expect("invalid auth scheme id in the scheme id slot"); - approvers.push((pub_key, auth_scheme)); - } - - AuthMethod::Multisig { threshold, approvers } -} - -/// Extracts authentication method from a network-account component. -fn extract_network_account_auth_method(storage: &AccountStorage) -> AuthMethod { - let allowlist = NetworkAccountNoteAllowlist::try_from(storage) - .expect("network account allowlist slot should be present and valid"); - - AuthMethod::NetworkAccount { - allowed_script_roots: allowlist.into_allowed_script_roots(), - } -} diff --git a/crates/miden-standards/src/account/interface/extension.rs b/crates/miden-standards/src/account/interface/extension.rs index 0d3c04b734..eaa7c31604 100644 --- a/crates/miden-standards/src/account/interface/extension.rs +++ b/crates/miden-standards/src/account/interface/extension.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use miden_protocol::account::{Account, AccountCode, AccountId, AccountProcedureRoot}; -use crate::AuthMethod; +use crate::account::auth::AccountAuthScheme; use crate::account::components::StandardAccountComponent; use crate::account::interface::{AccountComponentInterface, AccountInterface}; @@ -13,15 +13,15 @@ use crate::account::interface::{AccountComponentInterface, AccountInterface}; /// An extension for [`AccountInterface`] that allows instantiation from higher-level types. pub trait AccountInterfaceExt { /// Creates a new [`AccountInterface`] instance from the provided account ID, authentication - /// methods and account code. - fn from_code(account_id: AccountId, auth: Vec, code: &AccountCode) -> Self; + /// scheme tags and account code. + fn from_code(account_id: AccountId, auth: Vec, code: &AccountCode) -> Self; /// Creates a new [`AccountInterface`] instance from the provided [`Account`]. fn from_account(account: &Account) -> Self; } impl AccountInterfaceExt for AccountInterface { - fn from_code(account_id: AccountId, auth: Vec, code: &AccountCode) -> Self { + fn from_code(account_id: AccountId, auth: Vec, code: &AccountCode) -> Self { let components = AccountComponentInterface::from_procedures(code.procedures()); Self::new(account_id, auth, components) @@ -29,16 +29,8 @@ impl AccountInterfaceExt for AccountInterface { fn from_account(account: &Account) -> Self { let components = AccountComponentInterface::from_procedures(account.code().procedures()); - let mut auth = Vec::new(); - - // Find the auth component and extract all auth methods from it - // An account should have only one auth component - for component in components.iter() { - if component.is_auth_component() { - auth = component.get_auth_methods(account.storage()); - break; - } - } + let auth: Vec = + components.iter().filter_map(|c| c.auth_scheme()).collect(); Self::new(account.id(), auth, components) } diff --git a/crates/miden-standards/src/account/interface/mod.rs b/crates/miden-standards/src/account/interface/mod.rs index 50a27e2b0c..44f557118a 100644 --- a/crates/miden-standards/src/account/interface/mod.rs +++ b/crates/miden-standards/src/account/interface/mod.rs @@ -6,7 +6,7 @@ use miden_protocol::note::PartialNote; use miden_protocol::transaction::TransactionScript; use thiserror::Error; -use crate::AuthMethod; +use crate::account::auth::AccountAuthScheme; use crate::code_builder::CodeBuilder; use crate::errors::CodeBuilderError; @@ -25,7 +25,7 @@ pub use extension::{AccountComponentInterfaceExt, AccountInterfaceExt}; /// An [`AccountInterface`] describes the exported, callable procedures of an account. pub struct AccountInterface { account_id: AccountId, - auth: Vec, + auth: Vec, components: Vec, } @@ -39,7 +39,7 @@ impl AccountInterface { /// schemes and account component interfaces. pub fn new( account_id: AccountId, - auth: Vec, + auth: Vec, components: Vec, ) -> Self { Self { account_id, auth, components } @@ -70,8 +70,9 @@ impl AccountInterface { self.account_id.is_public() } - /// Returns a reference to the vector of used authentication methods. - pub fn auth(&self) -> &Vec { + /// Returns a reference to the vector of [`AccountAuthScheme`] tags identifying each + /// authentication component installed on the account. + pub fn auth(&self) -> &Vec { &self.auth } diff --git a/crates/miden-standards/src/account/interface/test.rs b/crates/miden-standards/src/account/interface/test.rs index 0ed4606b1f..32b7188e9f 100644 --- a/crates/miden-standards/src/account/interface/test.rs +++ b/crates/miden-standards/src/account/interface/test.rs @@ -8,8 +8,13 @@ use miden_protocol::errors::NoteError; use miden_protocol::note::{NoteAttachments, NoteType}; use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE; -use crate::AuthMethod; -use crate::account::auth::{AuthMultisig, AuthMultisigConfig, AuthSingleSig, NoAuth}; +use crate::account::auth::{ + AccountAuthScheme, + AuthMultisig, + AuthMultisigConfig, + AuthSingleSig, + NoAuth, +}; use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt}; use crate::account::wallets::BasicWallet; use crate::note::SwapNote; @@ -73,17 +78,8 @@ fn test_get_auth_scheme_ecdsa_k256_keccak() { .find(|component| matches!(component, AccountComponentInterface::AuthSingleSig)) .expect("should have EcdsaK256Keccak component"); - // Test get_auth_methods method - let auth_methods = ecdsa_k256_keccak_component.get_auth_methods(wallet_account.storage()); - assert_eq!(auth_methods.len(), 1); - let auth_method = &auth_methods[0]; - match auth_method { - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => { - assert_eq!(*pub_key, PublicKeyCommitment::from(Word::from([0, 1, 2, 3u32]))); - assert_eq!(*auth_scheme, auth::AuthScheme::EcdsaK256Keccak); - }, - _ => panic!("Expected EcdsaK256Keccak auth scheme"), - } + // Test auth_scheme classifier + assert_eq!(ecdsa_k256_keccak_component.auth_scheme(), Some(AccountAuthScheme::SingleSig)); } #[test] @@ -104,17 +100,8 @@ fn test_get_auth_scheme_falcon512_poseidon2() { .find(|component| matches!(component, AccountComponentInterface::AuthSingleSig)) .expect("should have single sig component"); - // Test get_auth_methods method - let auth_methods = rpo_falcon_component.get_auth_methods(wallet_account.storage()); - assert_eq!(auth_methods.len(), 1); - let auth_method = &auth_methods[0]; - match auth_method { - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => { - assert_eq!(*pub_key, PublicKeyCommitment::from(Word::from([0, 1, 2, 3u32]))); - assert_eq!(*auth_scheme, auth::AuthScheme::Falcon512Poseidon2); - }, - _ => panic!("Expected Falcon512Poseidon2 auth scheme"), - } + // Test auth_scheme classifier + assert_eq!(rpo_falcon_component.auth_scheme(), Some(AccountAuthScheme::SingleSig)); } #[test] @@ -135,14 +122,8 @@ fn test_get_auth_scheme_no_auth() { .find(|component| matches!(component, AccountComponentInterface::AuthNoAuth)) .expect("should have NoAuth component"); - // Test get_auth_methods method - let auth_methods = no_auth_component.get_auth_methods(no_auth_account.storage()); - assert_eq!(auth_methods.len(), 1); - let auth_method = &auth_methods[0]; - match auth_method { - AuthMethod::NoAuth => {}, - _ => panic!("Expected NoAuth auth method"), - } + // Test auth_scheme classifier + assert_eq!(no_auth_component.auth_scheme(), Some(AccountAuthScheme::NoAuth)); } /// Test that non-auth components return None @@ -156,8 +137,8 @@ fn test_get_auth_scheme_non_auth_component() { .build_existing() .expect("failed to create wallet account"); - let auth_methods = basic_wallet_component.get_auth_methods(wallet_account.storage()); - assert!(auth_methods.is_empty()); + assert!(basic_wallet_component.auth_scheme().is_none()); + let _ = wallet_account.storage(); } /// Test that the From<&Account> implementation correctly uses get_auth_scheme @@ -174,15 +155,7 @@ fn test_account_interface_from_account_uses_get_auth_scheme() { // Should have exactly one auth scheme assert_eq!(wallet_account_interface.auth().len(), 1); - - match &wallet_account_interface.auth()[0] { - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => { - let expected_pub_key = PublicKeyCommitment::from(Word::from([0, 1, 2, 3u32])); - assert_eq!(*pub_key, expected_pub_key); - assert_eq!(*auth_scheme, auth::AuthScheme::Falcon512Poseidon2); - }, - _ => panic!("Expected SingleSig auth method"), - } + assert_eq!(wallet_account_interface.auth()[0], AccountAuthScheme::SingleSig); // Test with NoAuth let no_auth_account = AccountBuilder::new(mock_seed) @@ -193,13 +166,8 @@ fn test_account_interface_from_account_uses_get_auth_scheme() { let no_auth_account_interface = AccountInterface::from_account(&no_auth_account); - // Should have exactly one auth scheme assert_eq!(no_auth_account_interface.auth().len(), 1); - - match &no_auth_account_interface.auth()[0] { - AuthMethod::NoAuth => {}, - _ => panic!("Expected NoAuth auth method"), - } + assert_eq!(no_auth_account_interface.auth()[0], AccountAuthScheme::NoAuth); } /// Test AccountInterface.get_auth_scheme() method with Falcon512Poseidon2 and NoAuth @@ -214,17 +182,11 @@ fn test_account_interface_get_auth_scheme() { let wallet_account_interface = AccountInterface::from_account(&wallet_account); - // Test that auth() method provides the authentication schemes + // Test that auth() method provides the authentication scheme tags assert_eq!(wallet_account_interface.auth().len(), 1); - match &wallet_account_interface.auth()[0] { - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => { - assert_eq!(*pub_key, PublicKeyCommitment::from(Word::from([0, 1, 2, 3u32]))); - assert_eq!(*auth_scheme, auth::AuthScheme::Falcon512Poseidon2); - }, - _ => panic!("Expected SingleSig auth method"), - } - - // Test AccountInterface.get_auth_scheme() method with NoAuth + assert_eq!(wallet_account_interface.auth()[0], AccountAuthScheme::SingleSig); + + // Test AccountInterface auth() with NoAuth let no_auth_account = AccountBuilder::new(mock_seed) .with_auth_component(NoAuth) .with_component(BasicWallet) @@ -233,15 +195,8 @@ fn test_account_interface_get_auth_scheme() { let no_auth_account_interface = AccountInterface::from_account(&no_auth_account); - // Test that auth() method provides the authentication schemes assert_eq!(no_auth_account_interface.auth().len(), 1); - match &no_auth_account_interface.auth()[0] { - AuthMethod::NoAuth => {}, - _ => panic!("Expected NoAuth auth method"), - } - - // Note: We don't test the case where an account has no auth components because - // accounts are required to have auth components in the current system design + assert_eq!(no_auth_account_interface.auth()[0], AccountAuthScheme::NoAuth); } #[test] diff --git a/crates/miden-standards/src/account/mod.rs b/crates/miden-standards/src/account/mod.rs index 0dcafe14c3..fb928f2801 100644 --- a/crates/miden-standards/src/account/mod.rs +++ b/crates/miden-standards/src/account/mod.rs @@ -1,5 +1,3 @@ -use super::auth_method::AuthMethod; - pub mod access; pub mod auth; pub mod components; diff --git a/crates/miden-standards/src/account/wallets/mod.rs b/crates/miden-standards/src/account/wallets/mod.rs index 9b9c7a14b7..b029b131bf 100644 --- a/crates/miden-standards/src/account/wallets/mod.rs +++ b/crates/miden-standards/src/account/wallets/mod.rs @@ -12,9 +12,8 @@ use miden_protocol::account::{ use miden_protocol::errors::AccountError; use thiserror::Error; -use super::AuthMethod; use crate::account::account_component_code; -use crate::account::auth::{AuthMultisig, AuthMultisigConfig, AuthSingleSig}; +use crate::account::auth::{AccountAuthComponent, AccountAuthScheme}; use crate::procedure_root; // BASIC WALLET @@ -117,53 +116,44 @@ pub enum BasicWalletError { AccountError(#[source] AccountError), } -/// Creates a new account with basic wallet interface, the specified authentication scheme and the -/// account storage type. Basic wallets can be specified to have either mutable or immutable code. +/// Creates a new account with the basic wallet interface and the specified authentication +/// component. /// -/// The basic wallet interface exposes three procedures: +/// The basic wallet interface exposes two procedures: /// - `receive_asset`, which can be used to add an asset to the account. /// - `move_asset_to_note`, which can be used to remove the specified asset from the account and add /// it to the output note with the specified index. /// -/// All methods require authentication. The authentication procedure is defined by the specified -/// authentication scheme. +/// All methods require authentication. The caller provides any [`AccountAuthComponent`]; this +/// factory rejects schemes that don't make sense for a basic wallet (`NoAuth` and +/// `NetworkAccount`), and accepts SingleSig, Multisig variants, and Custom components. pub fn create_basic_wallet( init_seed: [u8; 32], - auth_method: AuthMethod, - account_storage_mode: AccountType, + auth_component: AccountAuthComponent, + account_type: AccountType, ) -> Result { - let auth_component: AccountComponent = match auth_method { - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => { - AuthSingleSig::new(pub_key, auth_scheme).into() - }, - AuthMethod::Multisig { threshold, approvers } => { - let config = AuthMultisigConfig::new(approvers, threshold) - .and_then(|cfg| { - cfg.with_proc_thresholds(vec![(BasicWallet::receive_asset_root(), 1)]) - }) - .map_err(BasicWalletError::AccountError)?; - AuthMultisig::new(config).map_err(BasicWalletError::AccountError)?.into() - }, - AuthMethod::NoAuth => { + match auth_component.scheme() { + AccountAuthScheme::SingleSig + | AccountAuthScheme::SingleSigAcl + | AccountAuthScheme::Multisig + | AccountAuthScheme::GuardedMultisig + | AccountAuthScheme::MultisigSmart + | AccountAuthScheme::Custom => {}, + AccountAuthScheme::NoAuth => { return Err(BasicWalletError::UnsupportedAuthMethod( "basic wallets cannot be created with NoAuth authentication method".into(), )); }, - AuthMethod::NetworkAccount { .. } => { + AccountAuthScheme::NetworkAccount => { return Err(BasicWalletError::UnsupportedAuthMethod( "basic wallets cannot be created with NetworkAccount authentication method".into(), )); }, - AuthMethod::Unknown => { - return Err(BasicWalletError::UnsupportedAuthMethod( - "basic wallets cannot be created with Unknown authentication method".into(), - )); - }, - }; + } let account = AccountBuilder::new(init_seed) - .account_type(account_storage_mode) - .with_auth_component(auth_component) + .account_type(account_type) + .with_auth_component(auth_component.into_inner()) .with_component(BasicWallet) .build() .map_err(BasicWalletError::AccountError)?; @@ -180,7 +170,8 @@ mod tests { use miden_protocol::utils::serde::{Deserializable, Serializable}; use miden_protocol::{ONE, Word}; - use super::{Account, AccountType, AuthMethod, create_basic_wallet}; + use super::{Account, AccountType, create_basic_wallet}; + use crate::account::auth::AccountAuthComponent; use crate::account::wallets::BasicWallet; #[test] @@ -189,7 +180,7 @@ mod tests { let auth_scheme = auth::AuthScheme::Falcon512Poseidon2; let wallet = create_basic_wallet( [1; 32], - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }, + AccountAuthComponent::single_sig(pub_key, auth_scheme), AccountType::Public, ); @@ -204,7 +195,7 @@ mod tests { let auth_scheme = auth::AuthScheme::EcdsaK256Keccak; let wallet = create_basic_wallet( [1; 32], - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }, + AccountAuthComponent::single_sig(pub_key, auth_scheme), AccountType::Public, ) .unwrap(); diff --git a/crates/miden-standards/src/auth_method.rs b/crates/miden-standards/src/auth_method.rs deleted file mode 100644 index 1cb97350e7..0000000000 --- a/crates/miden-standards/src/auth_method.rs +++ /dev/null @@ -1,55 +0,0 @@ -use alloc::collections::BTreeSet; -use alloc::vec::Vec; - -use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment}; -use miden_protocol::note::NoteScriptRoot; - -/// Defines standard authentication methods supported by account auth components. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum AuthMethod { - /// A minimal authentication method that provides no cryptographic authentication. - /// - /// It only increments the nonce if the account state has actually changed during transaction - /// execution, avoiding unnecessary nonce increments for transactions that don't modify the - /// account state. - NoAuth, - /// A single-key authentication method which relies on either ECDSA or Falcon512Poseidon2 - /// signatures. - SingleSig { - approver: (PublicKeyCommitment, AuthScheme), - }, - /// A multi-signature authentication method using either ECDSA or Falcon512Poseidon2 signatures. - /// - /// Requires a threshold number of signatures from the provided public keys. - Multisig { - threshold: u32, - approvers: Vec<(PublicKeyCommitment, AuthScheme)>, - }, - /// An authentication method intended for network-owned accounts. - /// - /// It restricts the account to consuming only notes whose script roots are in - /// `allowed_script_roots`, and forbids transaction scripts from running against the account. - /// The allowlist must be non-empty. - NetworkAccount { - allowed_script_roots: BTreeSet, - }, - /// A non-standard authentication method. - Unknown, -} - -impl AuthMethod { - /// Returns all public key commitments associated with this authentication method. - /// - /// For unknown methods, an empty vector is returned. - pub fn get_public_key_commitments(&self) -> Vec { - match self { - AuthMethod::NoAuth => Vec::new(), - AuthMethod::SingleSig { approver: (pub_key, _) } => vec![*pub_key], - AuthMethod::Multisig { approvers, .. } => { - approvers.iter().map(|(pub_key, _)| *pub_key).collect() - }, - AuthMethod::NetworkAccount { .. } => Vec::new(), - AuthMethod::Unknown => Vec::new(), - } - } -} diff --git a/crates/miden-standards/src/lib.rs b/crates/miden-standards/src/lib.rs index 0a811b2ccf..7e0514cd55 100644 --- a/crates/miden-standards/src/lib.rs +++ b/crates/miden-standards/src/lib.rs @@ -6,9 +6,6 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -mod auth_method; -pub use auth_method::AuthMethod; - pub mod account; pub mod code_builder; pub mod errors; diff --git a/crates/miden-standards/src/testing/account_interface.rs b/crates/miden-standards/src/testing/account_interface.rs index 932c03ee4f..edbc8a9ba6 100644 --- a/crates/miden-standards/src/testing/account_interface.rs +++ b/crates/miden-standards/src/testing/account_interface.rs @@ -3,16 +3,77 @@ use alloc::vec::Vec; use miden_protocol::Word; use miden_protocol::account::Account; -use crate::account::interface::{AccountInterface, AccountInterfaceExt}; +use crate::account::auth::{ + AccountAuthScheme, + AuthGuardedMultisig, + AuthMultisig, + AuthMultisigSmart, + AuthSingleSig, + AuthSingleSigAcl, +}; +use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt}; -/// Helper function to extract public keys from an account +/// Helper function to extract public key commitments from every standard auth component +/// installed on `account`. Reads storage directly via the component's slot accessors. pub fn get_public_keys_from_account(account: &Account) -> Vec { let interface = AccountInterface::from_account(account); + let storage = account.storage(); - interface - .auth() - .iter() - .flat_map(|auth| auth.get_public_key_commitments()) - .map(Word::from) + let mut keys = Vec::new(); + for component in interface.components() { + match component.auth_scheme() { + Some(AccountAuthScheme::SingleSig) => { + if let Ok(key) = storage.get_item(AuthSingleSig::public_key_slot()) { + keys.push(key); + } + }, + Some(AccountAuthScheme::SingleSigAcl) => { + if let Ok(key) = storage.get_item(AuthSingleSigAcl::public_key_slot()) { + keys.push(key); + } + }, + Some(AccountAuthScheme::Multisig) => { + keys.extend(read_multisig_public_keys( + storage, + component, + AuthMultisig::threshold_config_slot(), + AuthMultisig::approver_public_keys_slot(), + )); + }, + Some(AccountAuthScheme::GuardedMultisig) => { + keys.extend(read_multisig_public_keys( + storage, + component, + AuthGuardedMultisig::threshold_config_slot(), + AuthGuardedMultisig::approver_public_keys_slot(), + )); + }, + Some(AccountAuthScheme::MultisigSmart) => { + keys.extend(read_multisig_public_keys( + storage, + component, + AuthMultisigSmart::threshold_config_slot(), + AuthMultisigSmart::approver_public_keys_slot(), + )); + }, + _ => {}, + } + } + keys +} + +fn read_multisig_public_keys( + storage: &miden_protocol::account::AccountStorage, + _component: &AccountComponentInterface, + config_slot: &miden_protocol::account::StorageSlotName, + keys_slot: &miden_protocol::account::StorageSlotName, +) -> Vec { + let config = match storage.get_item(config_slot) { + Ok(c) => c, + Err(_) => return Vec::new(), + }; + let num_approvers = config[1].as_canonical_u64() as u32; + (0..num_approvers) + .filter_map(|i| storage.get_map_item(keys_slot, Word::from([i, 0u32, 0, 0])).ok()) .collect() } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index c82587ced1..8e50ae6021 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -52,12 +52,13 @@ use miden_protocol::transaction::{ TransactionSummary, }; use miden_protocol::{Felt, Hasher, ONE, Word}; -use miden_standards::AuthMethod; +use miden_standards::account::auth::AccountAuthScheme; use miden_standards::account::interface::{AccountInterface, AccountInterfaceExt}; use miden_standards::account::wallets::BasicWallet; use miden_standards::code_builder::CodeBuilder; use miden_standards::note::P2idNote; use miden_standards::testing::account_component::IncrNonceAuthComponent; +use miden_standards::testing::account_interface::get_public_keys_from_account; use miden_standards::testing::mock_account::MockAccountExt; use miden_tx::auth::UnreachableAuth; use miden_tx::{TransactionExecutor, TransactionExecutorError}; @@ -546,21 +547,13 @@ async fn tx_summary_commitment_is_signed_by_falcon_auth() -> anyhow::Result<()> let summary_commitment = summary.to_commitment(); let account_interface = AccountInterface::from_account(&account); - let pub_key = match account_interface.auth().first().unwrap() { - AuthMethod::SingleSig { approver: (pub_key, _) } => pub_key, - AuthMethod::NoAuth => panic!("Expected SingleSig auth scheme, got NoAuth"), - AuthMethod::Multisig { .. } => { - panic!("Expected SingleSig auth scheme, got Multisig") - }, - AuthMethod::NetworkAccount { .. } => { - panic!("Expected SingleSig auth scheme, got NetworkAccount") - }, - AuthMethod::Unknown => panic!("Expected SingleSig auth scheme, got Unknown"), - }; + assert_eq!(account_interface.auth().first(), Some(&AccountAuthScheme::SingleSig)); + let pub_keys = get_public_keys_from_account(&account); + let pub_key = pub_keys.first().expect("expected at least one public key"); // This is in an internal detail of the tx executor host, but this is the easiest way to check // for the presence of the signature in the advice map. - let signature_key = Hasher::merge(&[Word::from(*pub_key), summary_commitment]); + let signature_key = Hasher::merge(&[*pub_key, summary_commitment]); // The summary commitment should have been signed as part of transaction execution and inserted // into the advice map. @@ -608,21 +601,13 @@ async fn tx_summary_commitment_is_signed_by_ecdsa_auth() -> anyhow::Result<()> { let summary_commitment = summary.to_commitment(); let account_interface = AccountInterface::from_account(&account); - let pub_key = match account_interface.auth().first().unwrap() { - AuthMethod::SingleSig { approver: (pub_key, _) } => pub_key, - AuthMethod::NoAuth => panic!("Expected SingleSig auth scheme, got NoAuth"), - AuthMethod::Multisig { .. } => { - panic!("Expected SingleSig auth scheme, got Multisig") - }, - AuthMethod::NetworkAccount { .. } => { - panic!("Expected SingleSig auth scheme, got NetworkAccount") - }, - AuthMethod::Unknown => panic!("Expected SingleSig auth scheme, got Unknown"), - }; + assert_eq!(account_interface.auth().first(), Some(&AccountAuthScheme::SingleSig)); + let pub_keys = get_public_keys_from_account(&account); + let pub_key = pub_keys.first().expect("expected at least one public key"); // This is in an internal detail of the tx executor host, but this is the easiest way to check // for the presence of the signature in the advice map. - let signature_key = Hasher::merge(&[Word::from(*pub_key), summary_commitment]); + let signature_key = Hasher::merge(&[*pub_key, summary_commitment]); // The summary commitment should have been signed as part of transaction execution and inserted // into the advice map. diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 4f5aa42276..c47230bcfb 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -46,7 +46,7 @@ use miden_protocol::testing::account_id::ACCOUNT_ID_FEE_FAUCET; use miden_protocol::testing::random_secret_key::random_secret_key; use miden_protocol::transaction::{OrderedTransactionHeaders, RawOutputNote, TransactionKernel}; use miden_protocol::{MAX_OUTPUT_NOTES_PER_BATCH, Word}; -use miden_standards::account::access::AccessControl; +use miden_standards::account::access::{AccessControl, Authority}; use miden_standards::account::faucets::{FungibleFaucet, TokenName}; use miden_standards::account::policies::{ BurnPolicyConfig, @@ -316,43 +316,43 @@ impl MockChainBuilder { self.add_account_from_builder(auth_method, account_builder, AccountState::Exists) } - /// Creates a new public [`FungibleFaucet`] account and registers the authenticator (if - /// any) for it. - /// - /// This does not add the account to the chain state, but it can still be used to call - /// [`MockChain::build_tx_context`] to automatically add the authenticator. - fn create_new_fungible_faucet( + /// Internal helper: creates a new user-account fungible faucet account (AuthControlled). + /// Installs [`Authority::AuthControlled`] directly; the auth component comes from `Auth`. + fn create_new_user_fungible_faucet( &mut self, auth_method: Auth, faucet: FungibleFaucet, account_type: AccountType, - access_control: AccessControl, token_policy_manager: TokenPolicyManager, ) -> anyhow::Result { let account_builder = AccountBuilder::new(self.rng.random()) .account_type(account_type) .with_component(faucet) - .with_components(access_control) + .with_component(Authority::AuthControlled) .with_components(token_policy_manager); self.add_account_from_builder(auth_method, account_builder, AccountState::New) } - /// Adds an existing fungible faucet account to the initial chain state and registers the - /// authenticator (if any). - /// - /// The behaviour of the faucet (basic vs network-style) is determined entirely by the - /// combination of arguments: - /// - `account_type`: [`AccountType::Public`] for basic faucets, or [`AccountType::Private`] for - /// off-chain accounts. - /// - `auth_method`: typically a [`Auth::BasicAuth`] for basic faucets, or [`Auth::IncrNonce`] - /// for network-style faucets. - /// - `access_control`: [`AccessControl::AuthControlled`] for basic faucets; - /// [`AccessControl::Ownable2Step`] / [`AccessControl::Rbac`] for owner-controlled faucets. - /// The matching `Authority` component is auto-installed by `AccessControl`. - /// - `token_policy_manager`: the unified [`TokenPolicyManager`] holding both mint and burn - /// policy. - fn add_existing_fungible_faucet( + /// Internal helper: adds an existing user-account fungible faucet (AuthControlled). + fn add_existing_user_fungible_faucet( + &mut self, + auth_method: Auth, + faucet: FungibleFaucet, + account_type: AccountType, + token_policy_manager: TokenPolicyManager, + ) -> anyhow::Result { + let account_builder = AccountBuilder::new(self.rng.random()) + .account_type(account_type) + .with_component(faucet) + .with_component(Authority::AuthControlled) + .with_components(token_policy_manager); + + self.add_account_from_builder(auth_method, account_builder, AccountState::Exists) + } + + /// Internal helper: adds an existing network-style fungible faucet (Ownable2Step / Rbac). + fn add_existing_network_fungible_faucet( &mut self, auth_method: Auth, faucet: FungibleFaucet, @@ -403,11 +403,10 @@ impl MockChainBuilder { .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; - self.add_existing_fungible_faucet( + self.add_existing_user_fungible_faucet( auth_method, faucet, AccountType::Public, - AccessControl::AuthControlled, token_policy_manager, ) } @@ -452,12 +451,13 @@ impl MockChainBuilder { .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; - let allowed_script_roots = allowed_script_roots - .into_iter() - .chain([MintNote::script_root(), BurnNote::script_root()]) - .collect(); + let allowed_script_roots: alloc::collections::BTreeSet = + allowed_script_roots + .into_iter() + .chain([MintNote::script_root(), BurnNote::script_root()]) + .collect(); - self.add_existing_fungible_faucet( + self.add_existing_network_fungible_faucet( Auth::NetworkAccount { allowed_script_roots }, faucet, AccountType::Public, @@ -484,12 +484,13 @@ impl MockChainBuilder { .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; - let allowed_script_roots = allowed_script_roots - .into_iter() - .chain([MintNote::script_root(), BurnNote::script_root()]) - .collect(); + let allowed_script_roots: alloc::collections::BTreeSet = + allowed_script_roots + .into_iter() + .chain([MintNote::script_root(), BurnNote::script_root()]) + .collect(); - self.add_existing_fungible_faucet( + self.add_existing_network_fungible_faucet( Auth::NetworkAccount { allowed_script_roots }, faucet, AccountType::Public, @@ -524,11 +525,10 @@ impl MockChainBuilder { .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; - self.create_new_fungible_faucet( + self.create_new_user_fungible_faucet( auth_method, faucet, AccountType::Public, - AccessControl::AuthControlled, token_policy_manager, ) } diff --git a/crates/miden-testing/tests/wallet/mod.rs b/crates/miden-testing/tests/wallet/mod.rs index f2a71098f6..000bcbbf4c 100644 --- a/crates/miden-testing/tests/wallet/mod.rs +++ b/crates/miden-testing/tests/wallet/mod.rs @@ -1,6 +1,6 @@ use miden_protocol::Word; use miden_protocol::account::auth::AuthSecretKey; -use miden_standards::AuthMethod; +use miden_standards::account::auth::AccountAuthComponent; use miden_standards::account::wallets::create_basic_wallet; use rand_chacha::ChaCha20Rng; use rand_chacha::rand_core::SeedableRng; @@ -19,7 +19,7 @@ fn wallet_creation() { let sec_key = AuthSecretKey::new_falcon512_poseidon2_with_rng(&mut rng); let auth_scheme = auth::AuthScheme::Falcon512Poseidon2; let pub_key = sec_key.public_key().to_commitment(); - let auth_method: AuthMethod = AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }; + let auth_component = AccountAuthComponent::single_sig(pub_key, auth_scheme); // we need to use an initial seed to create the wallet account let init_seed: [u8; 32] = [ @@ -29,7 +29,7 @@ fn wallet_creation() { let account_type = AccountType::Private; - let wallet = create_basic_wallet(init_seed, auth_method, account_type).unwrap(); + let wallet = create_basic_wallet(init_seed, auth_component, account_type).unwrap(); let expected_code = AccountCode::from_components(&[ AuthSingleSig::new(pub_key, auth_scheme).into(), @@ -58,7 +58,7 @@ fn wallet_creation_2() { let sec_key = AuthSecretKey::new_ecdsa_k256_keccak_with_rng(&mut rng); let auth_scheme = auth::AuthScheme::EcdsaK256Keccak; let pub_key = sec_key.public_key().to_commitment(); - let auth_method: AuthMethod = AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }; + let auth_component = AccountAuthComponent::single_sig(pub_key, auth_scheme); // we need to use an initial seed to create the wallet account let init_seed: [u8; 32] = [ @@ -68,7 +68,7 @@ fn wallet_creation_2() { let account_type = AccountType::Private; - let wallet = create_basic_wallet(init_seed, auth_method, account_type).unwrap(); + let wallet = create_basic_wallet(init_seed, auth_component, account_type).unwrap(); let expected_code = AccountCode::from_components(&[ AuthSingleSig::new(pub_key, auth_scheme).into(),