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
- Signer 1 creates a proposal and leaves it pending (does not execute).
- Signer 2 (fresh browser, never loaded this account) tries to load the account → fails completely.
- 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.
Summary
A second signer cannot load a 2-of-2 multisig account while any proposal is pending. Load fails with:
Once no proposals are pending, the second signer loads fine.
Reproduction
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()runsverifyProposalMetadataBinding()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 resultingTransactionSummarycommitment equals the one the proposer signed.That re-execution is block-height dependent:
executeForSummarypasses no reference block;execute_transactionuses the client's current sync height as the reference block (miden-clienttransaction/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}, andaccount_deltaincludes the transaction fee, which is computed from the reference block header'sfee_parameters(miden-baseexecutor/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
customandswitch_guardianproposal types were already exempt from this re-execution; the bug is that every other type still runs it.