bug(multisig): adding a signer to a 1-of-1 account fails STARK verification
- Dominant language
- Rust
- Stars
- 132
- Forks
- 167
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 110
Description
### Packages versions
miden-protocol = "0.15.3"
miden-standards = "=0.15.3"
miden-tx = "0.15.3"
miden-node-proto ="0.15.0"
miden-testing = "0.15.3"
miden-client = "0.15.0"
miden-client-sqlite-store = "0.15.0"
### Bug description
## Summary
`update_signers_and_threshold` produces a STARK-invalid proof when adding a
signer to a single-signer multisig (a 1-of-1 account). The transaction
executes fine, but proving/verifying it fails with:
constraint mismatch: quotient * vanishing != folded constraints
This affects both the standard (`auth::multisig`) and smart
(`auth::multisig_smart`) multisig auth components.
## Root cause
After `update_signers_and_threshold` rotates the signer set, it
unconditionally calls `cleanup_pubkey_and_scheme_id_mapping` to clear any
approver slots that are no longer used. When the approver map holds exactly
one entry (e.g. adding a signer to a 1-of-1 multisig), the proc's internal
loop guard already short-circuits and the body never runs - nothing is
cleared. However, *entering* the proc at all perturbs the prover trace enough
to fail node-side STARK verification.
The cleanup only ever has work to do when the signer set shrinks
(`init_num_of_approvers > new_num_of_approvers`). Add-signer and
threshold-only updates should skip it entirely.
## Proposed fix
Gate the cleanup call on the signer set actually shrinking
(`init_num_of_approvers > new_num_of_approvers`) in both:
- `crates/miden-standards/asm/standards/auth/multisig.masm`
- `crates/miden-standards/asm/standards/auth/multisig_smart/mod.masm`
Add-signer and threshold-only updates skip the call; remove-signer behavior is
unchanged.
Add regression tests that grow a 1-of-1 multisig to 2 signers and verify a
real STARK proof, for both the standard and smart variants.
### How can this be reproduced?
1. Create a 1-of-1 multisig account.
2. Execute `update_signers_and_threshold` to grow the signer set to 2
(threshold 2), signed by the single existing approver.
3. Generate and verify a real STARK proof for the transaction (e.g.
`prove_and_verify_transaction`).
The proof fails verification with `constraint mismatch: quotient * vanishing
!= folded constraints`. Note that a dummy block proof
(`MockChain::add_pending_executed_transaction` + `prove_next_block`) uses
`prove_dummy` and does **not** exercise this path, so the bug is invisible
there - it only surfaces under real transaction proving.
### Relevant log output
```shell
Invalid proof for transaction 0x7c212267a3f17a17dbfd431272a7516e5eb11663f0064217509c6e58e2fd2341: failed to verify transaction\ncaused by: failed to verify STARK proof for program with hash0xcdd0a81d45597a6099f138fc997d0a3f886e9553d819063559afe0cdfbe2a5d2\ncaused by: constraint mismatch: quotient * vanishing != folded constraints"
```
Contributor guide
Assessment
This issue has not been assessed yet.