feat(standards): Add DelayedExecution (Timelocked Accounts) to AuthMultisigSmart - #3044
feat(standards): Add DelayedExecution (Timelocked Accounts) to AuthMultisigSmart#3044onurinanc wants to merge 218 commits into
DelayedExecution (Timelocked Accounts) to AuthMultisigSmart#3044Conversation
* feat: implement Clone for TransactionContextBuilder * chore: add unwrap_unauthorized_err
* feat(agglayer): reject duplicate GER insertions Adds a duplicate-detection guard to `bridge_config::update_ger`: after calling `set_map_item`, the OLD_VALUE returned is compared against EMPTY_WORD; if it is not empty the GER was already registered and the transaction panics with ERR_GER_ALREADY_REGISTERED. Note on Solidity divergence: the Solidity GlobalExitRootManager treats duplicate GER insertions as idempotent no-ops. Miden intentionally rejects them because a permanently-unconsumable network note is a worse failure mode than an explicit transaction revert. This divergence is documented in SPEC.md. Docs updated on `update_ger`, the `update_ger.masm` note script, and SPEC.md (Panics row + Section 2.3 prose). A new integration test (`update_ger_rejects_duplicate`) verifies that a second UPDATE_GER note carrying the same GER value is rejected with the expected error code. Closes #2708 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: add CHANGELOG entry for #2983 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * review: combine padw+assert_eqw and use assert_transaction_executor_error! - bridge_config.masm: collapse the `padw` / `assert_eqw` pair onto a single line as suggested in review. - tests: replace the manual `is_err()` + `error_msg.contains(...)` block with the standard `assert_transaction_executor_error!` macro. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * review: use word::eqz + assert instead of padw + assert_eqw Slightly cheaper than the `padw`-then-`assert_eqw` pair: `word::eqz` consumes the 4-felt `OLD_VALUE` and pushes a single 0/1 flag, then a plain `assert.err=...` verifies it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The MASM doc-comment and formatting conventions previously captured in masm_doc_comment_fmt.md are now covered by the .claude/skills/masm-* skill files plus the MASM formatting tool. Maintaining a separate hand-written guide is redundant and prone to drift, so remove it. Closes #2021 Co-authored-by: Claude (Opus) <noreply@anthropic.com>
The protocol does not prevent adding multiple attachments with the same scheme to a note (e.g. two NetworkAccountTarget attachments). Document that doing so is discouraged as it brings no additional benefit and only increases public on-chain data and fees, and clarify in the rustdoc that the first matching attachment is treated as the canonical one. Closes #2873 Co-authored-by: Claude (Opus) <noreply@anthropic.com>
* refactor: include leaves number in BURN note ID computation * Apply suggestions from code review Co-authored-by: Marti <marcin.gorny.94@protonmail.com> --------- Co-authored-by: Marti <marti@miden.team> Co-authored-by: Marti <marcin.gorny.94@protonmail.com>
* Extend execution-error assertion macros with patter and any arms * Fix formating * Apply PR review feedback
…k level (#2993) * feat: reject consuming and creating same note in a tx * feat: erase notes on the fly * chore: remove unused `NoteCommitmentMismatch` error variants * chore: add changelog * chore: rename input/output note tracker functions * feat: reintroduce `NoteTracker` for readability * chore: improve clarity of comments * chore: format changelog * chore: add second cross-tx circular dep test
…2988) (#3005) * feat(protocol): add active_note::is_public / is_private (#2988) Cherry-pick the reusable protocol-level building blocks from #2988, which was merged to the agglayer branch as an agglayer-specific B2AGG bridge-out hardening: - active_note::is_public - active_note::is_private Both procs read the active note's metadata and decode its note type via the existing note::metadata_into_note_type primitive (from #2738), then compare against NOTE_TYPE_PUBLIC / NOTE_TYPE_PRIVATE from miden::protocol::util::note. A parameterized kernel test covers the Public and Private cases. The agglayer-only files from #2988 (bridge_out.masm, B2AGG.masm, tests/agglayer/bridge_out.rs) and the constant-value flip the PR applied on agglayer's NoteType encoding are intentionally not brought along: next already encodes Private=0, Public=1, and the existing metadata_into_note_type is tailored to next's metadata bit layout. Co-Authored-By: Claude (Opus) <noreply@anthropic.com> * fix(agglayer): enforce NoteType::Public for B2AGG bridge-out notes (#2988) Cherry-pick the bridge-side half of #2988 to next. The B2AGG note type lives in NoteMetadata, not in the recipient commitment, so an attacker could submit a note with an identical recipient, attachment, and asset but NoteType::Private. consensus would accept it, the leaf would be folded into the on-chain Local Exit Tree, but aggkit could never recover the pre-image off-chain — the LET mirror would permanently desync and the bridge-out path would brick. Add an active_note::is_public assert at the top of bridge_out, extend the bridge_out and b2agg.masm doc comments, and parameterize the existing destination-network-is-Miden test into test_bridge_out_rejects_invalid_b2agg_note covering the recipient-identical private-note case too. Co-Authored-By: Claude (Opus) <noreply@anthropic.com> * Apply suggestions from code review Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com> * chore: fmt files --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com> Co-authored-by: Marti <marti@miden.team> Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com>
…ution' into onur-multisig-smart-delayed-execution
feat: allow root-allowlisted tx scripts on network accounts (#3028) AuthNetworkAccount previously banned every transaction script outright. This generalizes that ban into a tx-script-root allowlist that mirrors the existing note-script allowlist: a transaction that runs no tx script is allowed, and any tx script that does run must have its MAST root in an allowlist fixed at account creation. This lets network accounts run owner-approved scripts (e.g. setting the expiration delta, needed by the ntx-builder) while preserving the invariant that only root-pinned, pre-approved code can run against a signature-less account. Changes: - Add `TransactionScriptRoot` newtype mirroring `NoteScriptRoot`; `TransactionScript::root()` now returns it. - Add `tx_script_allowlist.masm` (`assert_tx_script_allowed`) and switch the network auth component to it; remove the now-unused `assert_no_tx_script`. - Add `NetworkAccountTxScriptAllowlist` storage (`allowed_tx_scripts` slot); an empty allowlist permits no tx scripts (the prior behavior). - Wire through `AuthNetworkAccount::with_allowed_tx_scripts` and `AuthMethod::NetworkAccount.allowed_tx_script_roots`. A root pins a script's code but not its `TX_SCRIPT_ARGS`/advice inputs, which an arbitrary submitter controls, so only scripts whose behavior is fixed regardless of those inputs should be allowlisted. Breaking: the component gains a second storage slot (existing network accounts must be recreated), and `TransactionScript::root()` returns `TransactionScriptRoot` instead of `Word`. Closes #3027. Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com>
* refactor: rename network-account note allowlist API
PhilippGackstatter
left a comment
There was a problem hiding this comment.
I left a few questions/comments/nits. Overall, I'm having a hard time wrapping my head around this PR. In particular:
- it seems we have multiple names for the same concept - would be great to unify.
- I find the use of the pending slots hard to follow, and I'm not sure I understand why we need these. I left a question/suggestion to use auth args instead.
- I don't understand why we need the
cancel_and_proposefunctionality when we already have the individualcancelandpropose.
| # Map entries: [TX_HASH] -> [unlock_timestamp, proposal_timestamp, min_cancel_sigs, 1] | ||
| const TX_PROPOSALS_SLOT = word("miden::standards::auth::multisig_smart::tx_proposals") |
There was a problem hiding this comment.
Nit: I'd call this TX_SUMMARY_COMMITMENT instead of TX_HASH for accuracy (everywhere it is used in this PR).
| #! Loads the delayed execution policy configuration from `DELAYED_EXECUTION_SLOT`. | ||
| #! | ||
| #! Inputs: [] | ||
| #! Outputs: [min_delay, propose_expiration_delta, 0, 0] |
There was a problem hiding this comment.
Nit: Returning the zeros is unnecessary.
| proc get_propose_expiration_delta | ||
| exec.get_delayed_execution | ||
| # => [min_delay, propose_expiration_delta, 0, 0] |
There was a problem hiding this comment.
| proc get_propose_expiration_delta | |
| exec.get_delayed_execution | |
| # => [min_delay, propose_expiration_delta, 0, 0] | |
| proc get_propose_expiration_delta | |
| exec.get_delayed_execution_config | |
| # => [min_delay, propose_expiration_delta, 0, 0] |
Nit: Maybe the current procedure name could be a bit clearer.
| proc apply_expiration_delta | ||
| dup neq.0 assert.err=ERR_EXPIRATION_DELTA_ZERO | ||
| # => [expiration_delta] | ||
|
|
||
| exec.tx::update_expiration_block_delta | ||
| # => [] | ||
|
|
||
| exec.tx::get_expiration_block_delta | ||
| # => [tx_expiration_delta] | ||
|
|
||
| dup neq.0 assert.err=ERR_TX_EXPIRATION_DELTA_NOT_SET | ||
| # => [tx_expiration_delta] | ||
|
|
||
| drop | ||
| # => [] | ||
| end |
There was a problem hiding this comment.
I don't understand why we set and then get the delta - this seems redundant. The neq.0 check is already done in tx::update_expiration_block_delta (but it is not documented in a Panics if section - if you don't mind, we could add it in this PR).
So, I think the whole procedure can be removed in favor of calling tx::update_expiration_block_delta directly.
| #! Finalizes a pending cancel action for the current transaction. | ||
| #! | ||
| #! Inputs: [num_verified_signatures, TX_HASH] | ||
| #! Outputs: [num_verified_signatures, TX_HASH] |
There was a problem hiding this comment.
Does this procedure use TX_HASH? If not, can we remove it from the inputs/outputs? I'd also pass in num_verified_signatures but not return it. Caller should dup it.
| #! Marks that this tx intends to execute a proposed action. | ||
| #! | ||
| #! Inputs: [] | ||
| #! Outputs: [] | ||
| #! | ||
| #! Panics if: | ||
| #! - `PENDING_EXECUTE_SLOT` is already non-empty (`ERR_PENDING_ALREADY_SET`). | ||
| #! | ||
| #! Side effects: | ||
| #! - Writes `PENDING_EXECUTE_FLAG` to `PENDING_EXECUTE_SLOT`. | ||
| #! | ||
| #! Invocation: exec | ||
| pub proc execute_proposed_transaction |
There was a problem hiding this comment.
Is the use of this procedure to communicate to the auth procedure that the current transaction executed a proposed action? It seems like this is intended to be called from a tx script, which is arbitrarily provided by the tx executor.
If so, can we not achieve the same by passing a flag as AUTH_ARGS? And if so, can we get rid of all the pending slots by using this pattern?
There was a problem hiding this comment.
I think the same pattern needs to stay for propose/cancel, so it might be good keeping execute on the same pattern for symmetry. I'm not sure if removing all the pending slots by using the AUTH_ARGS pattern works.
| #! Returns the pending execute transaction summary hash, or `EMPTY_WORD` if none is set. | ||
| #! | ||
| #! Inputs: [] | ||
| #! Outputs: [PENDING_EXECUTE_HASH] | ||
| #! | ||
| #! Where: | ||
| #! - PENDING_EXECUTE_HASH is the pending execute transaction summary hash, or | ||
| #! `EMPTY_WORD` if no execute action is pending. | ||
| #! | ||
| #! Invocation: exec | ||
| proc get_pending_execute |
There was a problem hiding this comment.
Is this procedure description accurate? In set_pending_execute we write PENDING_EXECUTE_FLAG and not a HASH, so there is a mismatch.
| #! - Writes `NEW_TX_HASH` to `PENDING_PROPOSE_SLOT`. | ||
| #! | ||
| #! Invocation: exec | ||
| pub proc cancel_and_propose_new_transaction |
There was a problem hiding this comment.
Is the functionality of this procedure not the same as calling cancel and then propose? Why do we need a single procedure for this?
| exec.delayed_execution::finalize_timelock_proposals | ||
| # => [TX_SUMMARY_COMMITMENT] |
There was a problem hiding this comment.
Given that we clear the pending slots as the last step, the tx summary we computed earlier contains the changes to the pending slots. This works in principle, but feels a bit unclean. Mentioning in case this was not intended.
chore: add RUSTSEC-2026-0173 to deny.toml
…ctions (#3453) * add tests covering the fungible faucet metadata string setters * changelog * add note for token metadata actions * record the FUNGIBLE_FAUCET_CONFIG benchmark measurements * remove redundandt tests * use the config note in the faucet metadata tests * changelog * rename as to FaucetMetadataConfig
…andard (#3449) * feat(agglayer): add emergency pause to the bridge via the Pausable standard Installs the standards Pausable + PausableManager components on the bridge account and gates bridge_out, claim, update_ger, register_faucet, store_faucet_metadata_hash, and deregister_faucet with pausable::assert_not_paused. remove_ger stays live while paused as the emergency remediation tool. Pause/unpause is authorized via the Authority ADMIN fallback (dedicated PAUSER/UNPAUSER roles are a planned follow-up) and driven by the standards PAUSE_ACTION note, which is added to the bridge's note allowlist. The testing-side bridge account validator now also certifies the pausable is_paused storage slot, SPEC.md documents the mechanism (replacing the #2696 TODOs), integration tests cover pause/unpause, authorization, every gated entry point, and the remove_ger exemption, and the note cost tables are regenerated for the guard's extra cycles. Closes #2696. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(agglayer): bind the emergency pause to the bridge via PAUSE_AGG_BRIDGE note Replaces the generic standards PAUSE_ACTION note in the bridge's allowlist with an agglayer-specific PAUSE_AGG_BRIDGE note. Like every other bridge admin note, its script asserts the NetworkAccountTarget attachment, so an admin's pause/unpause note for one account cannot be redirected onto the bridge, and the attachment makes the note routable as a network note. Also documents the remove_ger pause exemption in MASM, clarifies that the slot certification lives in the testing-side validator, notes the ADMIN-key-loss consequence in the SPEC, and adds tests for target binding, network-note routability, and non-admin unpause of a paused bridge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(agglayer): drive the bridge pause with the standards PAUSE_ACTION note Removes the bridge-specific PAUSE_AGG_BRIDGE wrapper note and allowlists the standards PAUSE_ACTION note instead. The wrapper existed to bind the note to the bridge account, but that gap affects the whole standards admin-note family and is tracked in #3433, which will be fixed in this release - so the bridge accepts the interim risk and stays on the standard note rather than carrying a throwaway wrapper. Adds AggLayerBridge::pause_note, which builds the note with the NetworkAccountTarget attachment the standards builder omits, so bridge pause notes stay routable network notes and fee-sponsorship-eligible; the script does not assert the target until #3433 lands. SPEC 2.5 and 4.10 and the allowed_notes rustdoc document the interim exposure in both directions: anyone can burn a pause or unpause note by consuming it into an account naming the bridge ADMIN as owner, so the pause is neither guaranteed to engage nor guaranteed to lift until then. A characterization test pins the current behaviour so #3433's fix has to invert it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(agglayer): trim the emergency-pause documentation Addresses review feedback: drops the SPEC cross-references from rustdoc and MASM, removes the repeated per-call-site pause comments, and cuts the prose that either restates protocol design (in-flight notes cannot be retracted), duplicates the #3433 issue, or belongs only in build.rs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(agglayer): narrow the unbound-note claim after #3372 NETWORK_ACCOUNT_CONFIG now asserts its target attachment, so the limitation no longer covers the whole standards admin-note family. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(agglayer): address pause review comments Moves the duplicated bridge setup helper into test_utils so pause.rs and remove_ger.rs share one BridgeSetup, drops the local consume_and_commit in favour of the shared consume_note helper, gives the note-builder parameter a named type, and lifts the DUMMY_ETH_ADDRESS const up under the imports. On the error type, renames InvalidPauseNoteTarget to NonPublicPauseNoteTarget - NetworkAccountTarget::new can only fail with TargetNotPublic - reworks the messages, and switches both variants from #[source] to #[from] so pause_note uses plain ?. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(agglayer): keep the pause note errors on #[source] #[from] generates public blanket conversions, so any future ? on a NoteError or NetworkAccountTargetError in a function returning AgglayerBridgeError would silently inherit a pause-note-specific message. Both variant names assert a cause, so keep the explicit map_err at the one construction site. This also matches the repo's prevailing convention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com>
* feat: enforce sponsorship policy in network account auth * chore: address review comments * chore: update changelog
* bind the remaining config notes to their target account * apply to faucetmetadataconfig * changelog * remove redundant comments * fix comments * feat: ensure presence of network account target * chore: rename `account` to `target` in config notes * chore: remove burn notion from comments * chore: refactor pause_note_targeting_another_account_is_rejected test * chore: update note cost table --------- Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com> Co-authored-by: Marti <marti@miden.team>
…notes (#3447) * feat(agglayer): enable on-chain bridge role rotation via RBAC action notes Wraps up the role-transfer mechanism for the bridge's administrative roles. PR #3130 moved the bridge to the miden-standards RBAC stack but left rotation unexercisable on-chain: the bridge's AuthNetworkAccount allowlist rejected the role-management note. - Add RbacActionNote::script_root() to AggLayerBridge::allowed_notes(), making grant_role / revoke_role / set_role_admin / renounce_role reachable on the bridge. The fee-schedule entry derives from the allowlist automatically; the allowlist is component storage, so BRIDGE_CODE_COMMITMENT is unaffected. - Add a create_existing_bridge_account_with_admin_and_roles fixture and an agglayer rbac_rotation test suite covering: grant-then-use, revoke-then-fail, unauthorized grant, ADMIN rotation, the last-admin-renounce lockout hazard, and an allowlist pin test. - Document role rotation in SPEC.md section 2.5 (with the rotation ordering, last-admin, and target-binding caveats), add the RBAC_ACTION note as section 4.7, and resolve the stale rotation TODOs. Closes #2706 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(agglayer): single bridge test fixture with explicit admin Merge create_existing_bridge_account_with_admin_and_roles into create_existing_bridge_account_with_roles: the fixture now always takes the ADMIN member explicitly, and callers that do not exercise rotation pass the dummy admin themselves. Addresses review feedback on the duplicate constructors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(agglayer): drop historical framing and fix the ADMIN-decommission recipe Document only present behavior in SPEC section 2.5, per review feedback. Also correct the recipe for bounding ADMIN authority: each delegate admin role must be populated and made self-administering, in committed steps, before ADMIN is emptied - otherwise the delegate roles freeze under the then-empty ADMIN role. Pinned by the new self_administered_delegate_survives_admin_removal test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: move generic RBAC rotation guidance from the agglayer SPEC to the component docs SPEC section 2.5 now keeps only the bridge-specific consequences of on-chain role management and points to the miden-standards docs for the generic hazards: the RoleBasedAccessControl rustdoc gains the corrected, strictly ordered ADMIN-decommission recipe (with its no-quorum residual risk), and the RbacActionNote rustdoc gains the note-model security considerations (ordering, unexpiring pending notes, no target binding). Addresses review feedback that most of this content belongs in the RBAC documentation rather than the agglayer spec. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: shorten the RBAC rotation guidance Cut the RoleBasedAccessControl decommission paragraph, the RbacConfigNote security considerations, and the SPEC 2.5 caveat list down to their essentials. While shortening, correct two claims for the post-#3434 reality: the builder now attaches NetworkAccountTarget by default, and the auto-allowlisted NETWORK_ACCOUNT_CONFIG note reaches ADMIN-defaulted procedures - so the bridge's ADMIN role must never be emptied (it would forfeit the post-deployment configuration channel), and the SPEC now says so instead of describing decommissioning as viable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(agglayer): trim the bridge fixture doc comment Addresses review feedback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(standards): drop the target-binding caveat from the RbacConfigNote docs Target binding will be enforced by the note script itself (tracked separately), so the caveat is not worth documenting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(agglayer): drop the target-binding caveat from SPEC section 2.5 Note target binding is tracked in a separate issue; the section 4.7 permissions table keeps the factual consumer row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(agglayer): drop rotation tests already covered by the generic RBAC suite Keep only the bridge-specific wiring tests: grant-then-use and revoke-then-fail (RBAC_CONFIG through the bridge allowlist, fee schedule, and update_ger gating) plus the allowlist pin. Admin rotation, delegation exclusivity, and last-admin removal are component semantics covered in tests/scripts/rbac/. Three properties the generic suite did not yet cover move there instead of being dropped: an unauthorized-sender RBAC_CONFIG note is rejected, a note targeting another account is consumable (the assertion that inverts once target binding lands), and a self-administered role stays manageable after ADMIN renounces (set_role_admin(X, X)). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(agglayer): share the bridge setup helper across test suites Extract the duplicated setup_bridge helpers from remove_ger.rs and rbac_rotation.rs into test_utils (BridgeSetup + setup_bridge), seeding a chain-resident admin wallet so both suites - and future ones - can use the same fixture. Addresses review feedback on re-using test helpers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(agglayer): pin paused-bridge rotation and the effective note allowlist Post-merge follow-ups: a test pinning that RBAC_CONFIG notes stay consumable while the bridge is paused (the emergency-recovery path SPEC section 2.5 documents), an extension of the allowlist pin to the effective on-account set including the AuthNetworkAccount auto-added entries, and two wording fixes (NETWORK_ACCOUNT_CONFIG also dispatches fee-policy updates; the bench dummy admin covers all ADMIN-gated operations). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(agglayer): assert the pause actually took effect during paused rotation Promote is_bridge_paused into test_utils, assert the pause state before and after the paused-bridge rotation, and pin the bridge's tx-script allowlist alongside the note allowlist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Apply suggestions from code review Co-authored-by: Marti <marcin.gorny.94@protonmail.com> --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com>
* refactor(standards): remove the AuthSingleSigAcl auth component Its exempt-procedure-list mechanism let fee-charging accounts be drained: anyone could call an exempt procedure without a signature, but the account still paid the transaction fee. The team decided to remove the component rather than fix it now (see issue discussion). The plain AuthSingleSig component (every call requires a signature) remains available and now backs the "singlesig user faucet" factories. Closes #3360 * fix: revert unnecessary comment change in BurnNote conversion Per review feedback: the struct-level doc comment already covers the network-execution caveat; the internal conversion comment didn't need the same rewording. --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com>
* fix: restrict indexed input note asset removal * refactor: share indexed input note removal guard * test: make malicious input note theft regression explicit * fix: require native account context for indexed input note asset removal The account-origin check resolves the caller against the *active* account, so the gate alone was bypassable: a malicious note script could route the indexed removal through an attacker-controlled foreign account via FPI, where that foreign account is active and vouches for its own procedures. The full #3445 drain still worked end to end against a standard wallet. Add exec.memory::assert_native_account to the guard, which closes that path while leaving the one legitimate by-index caller intact (the fee manager collects sponsorship note assets from the native account's auth procedure). Tests: a note-script-via-FPI regression test, and an explicit test that transaction scripts are rejected too - the latter pins a behavior change that was previously only implied by a deleted test. --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com> Co-authored-by: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com>
* feat: add miden-build-utils crate * feat: remove copy_directory helper * feat: more build-utils * feat: remove TransactionScript and NoteScript constructor from Program * feat: remove unused helpers * feat: refactors * revert: remove Program constructors * chore: changelog * chore: remove leftover changes * refactor: assemble utils * review: move regex into LazyLock * fix: cargo shear * review: address comments * review: address remaining comments * fix: changelog merge * feat: add publish logic to read masp file from OUT_DIR * refactor: inline code --------- Co-authored-by: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com>
* refactor min burn amount comment * changelog * Update CHANGELOG.md Co-authored-by: zeapoz <zeapo@pm.me> --------- Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com> Co-authored-by: zeapoz <zeapo@pm.me>
* feat: assemble `ExpirationTransactionScript` on build-time * feat: move script to library procedure inside the standards package * chore: changelog * feat: add `SendNotesTransactionScript` to standards package * chore: changelog * feat: add tx_script() accessor * chore: comments * chore: tests * chore: fmt * feat: factor out common masm code * feat: comments; masm signatures * feat: add tests * review: address comments * review: address nits and tests * feat: address more comments * review: address comments * chore: update comments * review: make common module private * review: improve comments * review: validate asset composition * review: add `add_existing_non_fungible_faucet` to mockchain builder helper * review: remove `validate_note_records` costly validation * review: keep note idx and read ptr on stack * review: add cross-check with asset composition to pick script --------- Co-authored-by: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com>
* chore: fix fresh build * change version to rc1 * regenerate cost tables --------- Co-authored-by: Marti <marti@miden.team>
…3471) * fix(tx): gate only signature production outside the auth procedure * changelog * fix in_auth_procedure: bool comment * remove auth request probe component
* chore: reject unbounded signatures in tx event * chore: add changelog * chore: use multiple assertions * fix: changelog entry
… note (#3469) * fix pswap.masm * add regression tests * test: cover a pswap note-fill payback created behind a remainder note * rename tests * changelog * clean tests * simplify tests * Update crates/miden-testing/tests/scripts/pswap.rs --------- Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com>
The v0.16.0-rc.1 release aborted mid-publish with: 403 Forbidden: Trusted Publishing tokens do not support creating new crates. Publish the crate manually, first `miden-protocol-build-utils` was added to the workspace and had never been published, and crates.io does not let an OIDC token create a new crate name. `cargo publish --dry-run` never contacts the registry to reserve a name, so nothing caught this until the tagged release had already been cut. Add scripts/check-crates-published.sh, which enumerates publishable workspace members via `cargo metadata` and checks each name against the crates.io sparse index. It runs in the publish job, ahead of the token mint - the check needs no credential, and a slow registry would otherwise eat into the token's short lifetime. A release that would abort partway now stops before publishing anything, with an error naming the manual-claim and Trusted Publishing setup needed to fix it. The remediation is explicit that the claim must happen at a version strictly below the upcoming release, from a placeholder outside this workspace. Claiming the name at the version about to be released is what turned this incident's retry into "crate miden-protocol-build-utils@0.16.0-rc.1 already exists on crates.io index", and publishing from the workspace would need --allow-dirty, which bakes untracked files into a permanently public artifact. Scope is narrow and stated in the header: a hit proves the name is registered by someone, not that we own it, that Trusted Publishing is configured, or that the release version is free. Those still surface only at publish time, and the success message says so rather than reading as "the release will succeed". `cargo metadata` and the jq filter are captured into a variable rather than read through a process substitution, so a failed or truncated read cannot yield a partial crate list that silently passes. An unverifiable crate refuses to guess rather than passing. All eight publishable members currently resolve on the index, so this is safe to land as-is. Co-authored-by: Claude (Opus) <noreply@anthropic.com>
|
Update: Now that #3383 binds the reference block commitment and the expiration block delta into the signed transaction summary, the delayed-execution flow needs a small design change. This comment explains why and proposes a change in the design. Before #3383, the transaction summary commitment was: It was independent of the reference block. After #3383 it also binds the block and expiration: A delayed transaction is proposed at one block and executed later, at a different block, and the delayed-execution flow currently keys a proposal on the transaction summary commitment and recomputes that same commitment at execute time to look the proposal. With the change binding block into the commitment, the value computed at propose time, never equals the value computed at execute time, so the lookup fails and the timelock and consume step never runs. Proposed design:We can keep the The reference block and expiration are execution context, not part of the transaction's intent, so excluding them is both necessary so that propose and execute agree on the commitment. The account delta, input and output notes, and user parameters constitutes At execute time the auth procedure computes both commitments from the same summary: the full one for signature verification, and the action commitment to match the proposal. To make this happen:
Note on signatures: Because signature verification stays over the block bounded summary, the execute transaction verifies fresh signatures over its own summary, while the proposal is authorized by signatures over the action commitment. So the proposal and the execution are authorized independently: the proposal proves "this action was scheduled and the timelock started", the execution proves "the approvers authorize this specific run now". This is a deliberate change from the earlier "sign once, reuse the same signature at propose and execute" idea, which only worked while the summary was block-independent. It means the following:
Any feedback on this design? @bobbinth @PhilippGackstatter @mmagician |
|
Thanks for the updated proposal!
Does this necessarily have to be the case, or is it even desirable? As mentioned in #3210 (comment):
So, I think there is no protocol reason why we'd need to propose at one block and execute at a different block. Additionally, if the multisig tx involves FPI calls, then what the proposal tx against ref block X signs are the FPI calls against the account states in block X. If we later change the reference block, the foreign account states may have changed and return different values, and the signature would be stale. From this perspective, it seems desirable to use the same ref block during proposal and execution, because it guarantees the action/effects portion of the tx summary does not change. I can also see the counterargument to this: Re-signing at execution time means we can use a newer ref block and execute against newer foreign account state. However, by its nature, delayed execution, due to the inherent delay, authorizes an action that shouldn't require the newest blockchain state, else it would be the wrong mechanism. For completeness, there is also the fee-related concern mentioned in #3210 (comment), but I think we'll solve this separately in #3358.
In my mind, it's a UX feature that we can use the same signature for proposing and executing, as signing a proposal itself already signals the approval for its execution (at least for some timeframe - signing a block commitment + expiration delta does this directly at the signature level). To summarize, I think the design space allows for the old and the new model. I think the main differences are:
For fee-computation purposes, a newer ref block is always preferable, but due to the signing delay for multisigs in general, I think we need a separate general mechanism here, and so I'm not sure that it should be the deciding factor. Given the above, I still prefer the old model due to the UX benefits. |
I do think it is necessary. In the same ref block model, how do we enforce that Actually, a single signature per approver can be achieved without requiring the same ref block, we sign the action commitment at propose time, and at execute time we verify that same signature against the action commitment as well. The ref blocks would still differ, but since the signed message stays the same, the same signature can be reused. The problem is that this brings #3210 back. If we only sign the action commitment, the signed message no longer binds the reference block commitment or the expiration delta . Since the execute block isn't known at propose time, the only way to keep the block and expiration bound is to verify the full summary at execute time. For delayed txs, do you think reduced binding is acceptable? |
Ah, true. We require a newer ref block to enforce the delay.
&
I think even when we sign the "action commitment" at propose time and the tx summary at execution time, we have the "reorganization and cross-network ambiguity" described in #3210, because an action commitment doesn't bind the ref block or the chain's identity. So, I guess the most secure option is to avoid the single signature per approver and require signing at execution time from everyone involved. That way, actual execution is bound to a specific ref block and chain identity. That's the proposal you made originally. I think reduced binding may be acceptable, but I'm not 100% convinced, and so I would err on the side of caution. We can always ship a "multisig smart 2.0" at a later point that improves such aspects. One note on the naming: Since the "action commitment" commits to the outcome of a tx, but doesn't care how that outcome was achieved (e.g. it doesn't bind the tx script), I think transaction effects - focussing on the effects/outcome - would be a better term than "action", which focusses on the actions that were executed in a transaction. Transaction effects is also a standard term in other chains, so I think this fits well. Two notes regarding the implementation:
|
partylikeits1983
left a comment
There was a problem hiding this comment.
It looks like a merge issue has caused this PR to show a much larger diff than the feature itself. @onurinanc could you please take a look and update the branch so the diff contains only the intended changes?
Closes: #3043.
Previously opened a draft PR implementing all features regarding Smart Multisig here: #2973. That PR still relatively big, and we separate the delayed execution logic into this PR.