Skip to content

Second signer cannot load a multisig account while a proposal is pending (block-dependent metadata binding) #409

Description

@WiktorStarczewski

Summary

A second signer cannot load a 2-of-2 multisig account while any proposal is pending. Load fails with:

Invalid proposal: metadata does not match tx_summary for 0x…

Once no proposals are pending, the second signer loads fine.

Reproduction

  1. Signer 1 creates a proposal and leaves it pending (does not execute).
  2. Signer 2 (fresh browser, never loaded this account) tries to load the account → fails completely.
  3. Execute/clear the pending proposal → Signer 2 loads successfully.

Setup: @openzeppelin/miden-multisig-client@0.16.1, @openzeppelin/guardian-client@0.16.1, @miden-sdk/miden-sdk@0.15.9, Miden testnet, 2-of-2.

Root cause

On load, syncProposals() runs verifyProposalMetadataBinding() on every pending proposal. For built-in proposal types that rebuilds the transaction from the proposal's metadata and re-executes it (executeForSummary) to check the resulting TransactionSummary commitment equals the one the proposer signed.

That re-execution is block-height dependent:

  • executeForSummary passes no reference block; execute_transaction uses the client's current sync height as the reference block (miden-client transaction/mod.rs: "The transaction will use the current sync height as the block reference").
  • TransactionSummary.to_commitment() hashes {account_delta, input_notes, output_notes, salt}, and account_delta includes the transaction fee, which is computed from the reference block header's fee_parameters (miden-base executor/mod.rs).

So the same logical proposal re-executed at a different block yields a different summary commitment. The check only passes on the same client at the same sync height as the proposer — which is exactly the reporter's evidence: the proposer's re-verify at sync height 1549420 matches; a second signer at 1549447 gets a different commitment and the whole load aborts.

The custom and switch_guardian proposal types were already exempt from this re-execution; the bug is that every other type still runs it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions