Skip to content

refactor: merge AuthMethod into AccessControl#2944

Open
onurinanc wants to merge 20 commits into
nextfrom
fix-auth-controlled
Open

refactor: merge AuthMethod into AccessControl#2944
onurinanc wants to merge 20 commits into
nextfrom
fix-auth-controlled

Conversation

@onurinanc

@onurinanc onurinanc commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Closes: #2930
Fixes: #2943

Tasks:

@bobbinth bobbinth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Not a review yet, but I left some comments inline. For now, more questions/thoughts than concrete suggestions.

Comment on lines +640 to +643
/// Rejects [`AuthMethod::Multisig`] / [`AuthMethod::Unknown`] for all variants (faucets do
/// not support Multisig today), and rejects [`AuthMethod::NoAuth`] specifically under
/// [`AccessControl::AuthControlled`] because it would leave authority-gated setters
/// permissionless.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no fundamental reason why faucets shouldn't support multisig-based auth. Let's create an issue for this - though, it'll probably be a fairly low priority.

Comment on lines 73 to 90
@@ -59,29 +86,51 @@ pub enum AccessControl {
Rbac {
owner: AccountId,
authority_role: Option<RoleSymbol>,
auth: AuthMethod,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding AuthMethod to Ownable2Step and Rbac feels a bit off because currently the only "legal" value for these variants is AuthMethod::NetworkAccount. I think the fault mostly lies in the AuthMethod enum which is kind of in between an auth component and a pure enum.

It would be a bigger refactoring, but I think we should get rid of AuthMethod enum altogether and replace it with something like AccountAuthComponent struct which would be a wrapper over AccountComponent with some convenience constructors.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding AuthMethod to Ownable2Step and Rbac feels a bit off
I agree on this. Additionally, adding and AuthMethod to AccessControl doesn't sound well either. AuthControlled should be an account component similar to Ownable2Step and Rbac and having an AccountAuthComponent would be a good variant instead of combining AuthMethod with AuthControlled.

For this refactoring, do we also need #2621 this to be merged? similar to below comment?

Other than that, if we decide to remove AuthMethod and replace it with AccountAuthComponent, I think separating this PR into two:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let's tackle #2943 separately (which I see you've already done) - and then we can come back here and do a more comprehensive refactoring.

///
/// The faucet itself, including all token metadata, is provided in the `faucet` parameter (see
/// [`FungibleFaucet::builder`]).
pub fn create_fungible_faucet(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we are trying to do too much with this single function. Maybe it is worth splitting it up with a couple functions - something like:

pub fn create_network_fungible_faucet(
    init_seed: [u8; 32],
    faucet: FungibleFaucet,
    access_control: AccessControl, // this would contain only Ownable2Step and Rbac
    token_policy_manager: TokenPolicyManager,
    storage_mode: AccountStorageMode,
) -> Result<Account, FungibleFaucetError> {
    ...
}

pub fn create_user_fungible_faucet(
    init_seed: [u8; 32],
    faucet: FungibleFaucet,
    auth_method: AuthMethod, // this would not contain NetworkAccount
    token_policy_manager: TokenPolicyManager,
    storage_mode: AccountStorageMode,
) -> Result<Account, FungibleFaucetError> {
    ...
}

But again, this would be a bigger refactoring along the lines of #2621.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach seems to me better in that way we can separate AccountAuthComponent and AccessControl. We might also want to rename the AccessControl enum to NetworkAccessControl and doesn't include AccountAuthComponent as a variant in the enum.

@mmagician mmagician left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this PR contains most of the changes from #2958

Comment on lines -69 to -71
/// Yields the [`AccountComponent`]s implementing this access control configuration, in the
/// order they must be installed on the account. The matching [`Authority`] component is
/// always included.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this comment removed on purpose?

@mmagician mmagician requested a review from Fumuran May 28, 2026 08:12
Comment on lines +29 to +44
pub enum AccountAuthScheme {
NoAuth,
SingleSig,
SingleSigAcl,
NetworkAccount,
Multisig,
GuardedMultisig,
MultisigSmart,
Custom,
}

/// A typed wrapper around an authentication [`AccountComponent`].
pub struct AccountAuthComponent {
inner: AccountComponent,
scheme: AccountAuthScheme,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a step back, it seems like these helpers for faucet creation (create_user_fungible_faucet, create_network_fungible_faucet) inucr a high amount of additional code we need for questionable gain (client uses it in tests only, node doesn't use it at all). I'd love to get rid of them, but maybe that's too drastic. At the very least, I think we should simplify these. In my opinion, it's totally fine to be opinionated here - it's always possible to fall back to the AccountBuilder and to add more functions rather than to do more with the same function.

I think the main complication comes from us trying to support various auth components in the creation of the faucets. Right now we support NoAuth for network accounts and custom account components for each, but really, the meaningful configurations we can support are AuthSinglesigAcl for users faucets as well as AuthNetworkAccount for network accounts. For custom components, we cannot meaningfully validate anything in these helpers anyway, so there is no gain from supporting these. Users might as well use the AccountBuilder directly. I don't think we should allow NoAuth anyway, because it is dangerous for network accounts. That one might be convenient for tests, but we can introduce a separate testing-gated helper for this, if needed.

So, I think we can just require the concrete component to be passed:

pub fn create_user_fungible_faucet(
    init_seed: [u8; 32],
    faucet: FungibleFaucet,
    auth_component: AuthSingleSigAcl,
    token_policy_manager: TokenPolicyManager,
    account_type: AccountType,
) -> Result<Account, FungibleFaucetError> {
    todo!()
}

pub fn create_network_fungible_faucet(
    init_seed: [u8; 32],
    faucet: FungibleFaucet,
    access_control: AccessControl,
    auth_component: AuthNetworkAccount,
    token_policy_manager: TokenPolicyManager,
    account_type: AccountType,
) -> Result<Account, FungibleFaucetError> {
    todo!()
}

This means no need for AccountAuthScheme (*) and AccountAuthComponent, no need to have extensive docs describing what auth components are supported and which are rejected, no need to test that certain components are rejected, and an API that tells you at compile time whether the configuration is valid rather than at runtime.

If we want to introduce additonal helpers for, say, a multisig-user faucet, we can add this as a separate function.

(*) Right now the AccountComponentInterface still soft-requires us to have this auth scheme enumeration, but we should get rid of this very soon (#2621), so I'd keep it in the most minimal form possible that makes the existing code work.

@onurinanc

Copy link
Copy Markdown
Collaborator Author

Applied the changes considering the possible direction commented here: #2944 (comment)

@PhilippGackstatter PhilippGackstatter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! I think this is a much-needed relief on complexity.

Left a few nits and suggestions, nothing blocking.

Comment on lines +644 to +647
pub fn user_faucet_single_sig_acl(
pub_key: miden_protocol::account::auth::PublicKeyCommitment,
scheme: miden_protocol::account::auth::AuthScheme,
) -> Result<AuthSingleSigAcl, FungibleFaucetError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be a test-only helper, so I'd move it behind testing.

Comment on lines +150 to +151
let allowlist: BTreeSet<NoteScriptRoot> =
[NoteScriptRoot::from_raw(Word::new([Felt::ONE; 4]))].into_iter().collect();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let allowlist: BTreeSet<NoteScriptRoot> =
[NoteScriptRoot::from_raw(Word::new([Felt::ONE; 4]))].into_iter().collect();
let allowlist: BTreeSet<NoteScriptRoot> =
[NoteScriptRoot::from_array([0; 4])].into_iter().collect();

Nit: We have a nice helper for test construction.

Comment on lines 109 to 113
#[derive(Debug, Error)]
pub enum BasicWalletError {
#[error("unsupported authentication method: {0}")]
UnsupportedAuthMethod(String),
#[error("account creation failed")]
AccountError(#[source] AccountError),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'm not sure this error is meaningful anymore. We might as well return AccountError from create_basic_wallet.

Comment on lines 320 to 333
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<Account> {
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we could inline this logic into create_new_faucet now.

Comment on lines +336 to +337
/// Internal helper: adds an existing user-account fungible faucet (AuthControlled).
fn add_existing_user_fungible_faucet(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this, should we inline this into add_existing_basic_faucet?

.into_iter()
.chain([MintNote::script_root(), BurnNote::script_root()])
.collect();
let allowed_script_roots: alloc::collections::BTreeSet<NoteScriptRoot> =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let allowed_script_roots: alloc::collections::BTreeSet<NoteScriptRoot> =
let allowed_script_roots: BTreeSet<NoteScriptRoot> =

nit

Comment thread CHANGELOG.md Outdated
Comment on lines +94 to +100
- 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] Removed `AuthMethod` enum and `AccessControl::AuthControlled` variant. Faucet factories now take concrete auth-component types so invalid configurations are rejected at compile time. ([#2944](https://github.com/0xMiden/protocol/pull/2944)).
- [BREAKING] Split `create_fungible_faucet` into `create_user_fungible_faucet` (takes `AuthSingleSigAcl`, installs `Authority::AuthControlled` directly) and `create_network_fungible_faucet` (takes `AuthNetworkAccount` and `AccessControl::Ownable2Step | Rbac`). For any other auth scheme, use `AccountBuilder` directly. A new `user_faucet_single_sig_acl` helper builds an `AuthSingleSigAcl` with the complete authority-gated setter trigger list. ([#2944](https://github.com/0xMiden/protocol/pull/2944)).
- [BREAKING] `create_basic_wallet` now takes `AuthSingleSig` directly instead of a wrapped auth type. For multisig wallets, use `AccountBuilder` directly. ([#2944](https://github.com/0xMiden/protocol/pull/2944)).
- [BREAKING] Removed `AccountInterface::auth()` method (returning `Vec<AccountAuthScheme>`). Auth components are now discovered via `AccountInterface::auth_components()`, which iterates `AccountComponentInterface` variants flagged by `is_auth_component()`. ([#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)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be the result of some auto union-merge, could you double check if there are duplicate entries now?

In any case, this PR's entry should go into the 0.16 section.

@bobbinth bobbinth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thank you! I left a few comments/questions inline.

Also, role assignment will be done in a different PR, right? Do we already have an issue for this?

Comment thread CHANGELOG.md Outdated
### Fixes

- Fixed auth components to use initial storage state for authentication ([#2677](https://github.com/0xMiden/protocol/issues/2677)).
- 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)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this and a few other unrelated changes snuck into the changelog somehow.

Comment on lines 113 to +117
pub fn create_basic_wallet(
init_seed: [u8; 32],
auth_method: AuthMethod,
account_storage_mode: AccountType,
) -> Result<Account, BasicWalletError> {
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 => {
return Err(BasicWalletError::UnsupportedAuthMethod(
"basic wallets cannot be created with NoAuth authentication method".into(),
));
},
AuthMethod::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)
auth_component: AuthSingleSig,
account_type: AccountType,
) -> Result<Account, AccountError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, we could use this to create multisig wallets as well. Should we add another helper function to support this functionality? For example, something like create_multisig_wallet().

Another thing, maybe we could also create a helper for creating guardian-supported wallets - e.g., create_guarded_wallet(). But maybe I'm going too far.

Comment on lines +575 to +579
/// Caller passes a fully-configured [`AuthSingleSigAcl`] — its trigger procedure list must
/// cover every authority-gated setter on the faucet (`mint_and_send`, the metadata setters,
/// the policy setters, and `pause` / `unpause`), otherwise those procedures become
/// permissionless under [`Authority::AuthControlled`].
pub fn create_user_fungible_faucet(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify: the caller is expected to configure the ACL auth component with the roots of all procedures, right? Would this create friction for the user?

Comment on lines +601 to 608
pub fn create_network_fungible_faucet(
init_seed: [u8; 32],
faucet: FungibleFaucet,
access_control: AccessControl,
auth_component: AuthNetworkAccount,
token_policy_manager: TokenPolicyManager,
account_type: AccountType,
) -> Result<Account, FungibleFaucetError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of comments on this:

  • We don't need to pass account_type here as network accounts must be public.
  • Why do we need to pass AuthNetworkAccount component? Could we not create/configure it automatically in this function?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Why do we need to pass AuthNetworkAccount component? Could we not create/configure it automatically in this function?

I think if we did that, we'd have to take two parameters (note allowlist, tx script allowlist) as input rather than one (AuthNetworkAccount), so the current approach seems a bit cleaner to me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then the caller needs to know the internal details of this function - no? Specifically, they need to know that config notes for the Pausable component need to be added to the note allowlist.

More generally though, can't this procedure build both the note script allowlist and tx script allowlist? If we can't do it here, there may not be much value in having this function - i.e., using the builder directly would result in a more readable/maintainable code.

My understanding is that we are OK with being pretty opinionated with this function (we are providing a quick way to construct a faucet w/o using the builder directly) - and so, we can make assumptions about notes/scripts allowed for this account.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's true. Agreed, we should be able to configure the auth component internally and being opinionated is fine here.

.with_component(faucet)
.with_components(access_control)
.with_components(token_policy_manager)
.with_component(PausableManager)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is adding PausableManager component enough? Should we not also add Pausable component?

@onurinanc onurinanc requested a review from bobbinth June 9, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider merging AuthMethod into AccessControl Bug: AuthControlled faucet leaves Authority setters unauthenticated

4 participants