OpenZeppelin / OpenZeppelin/compact-contracts
[Bug]: EcdsaSignerManager.verify only detects adjacent duplicate signers (incorrect for 3+ approvals)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 55
- Forks
- 29
- Avg merge
- 5d 7h
- Merged PRs (30d)
- 25
Description
Prerequisites
- I have read the Contributing Guidelines.
- I agree to follow the Code of Conduct.
- I have searched for existing issues that already report this problem, without success.
What are the steps to reproduce this issue?
- Use
EcdsaSignerManager(from #628) with a registry of 3+ signers and a threshold of 3. - Call
verify<n>withn >= 3, presenting a commitment vector that contains a non-adjacent duplicate, e.g.[A, B, A]. - Observe that verification passes with
validCount == 3.
What happens?
verifySignature rejects duplicates by comparing each commitment only against the immediately previous one:
assert(commitment != state.prevCommitment, "EcdsaSignerManager: duplicate signer");
This catches adjacent duplicates only. With 3+ presented signers, a non-adjacent duplicate such as [A, B, A] passes every check, so the same registered signer is counted more than once toward the threshold — a forged quorum (e.g. "3 approvals" from only 2 distinct signers).
The module already documents this in its notice ("sufficient for at most 2 signers"), and every current call site uses verify<2> (where the only possible duplicate is adjacent), so it is not exploitable in shipped usage today. It becomes a live correctness/authorization bug the moment verify is called with 3+ presented signers.
What were you expecting to happen?
verify should count only distinct registered signers toward the threshold, for any n. A duplicate at any position — adjacent or not — must be rejected.
Paste any relevant logs, error output, etc.
need 3 approvals; present commitments [A, B, A]
step 1: A vs (zero) -> different OK count=1
step 2: B vs A -> different OK count=2
step 3: A vs B -> different OK count=3 <-- A was already counted in step 1
result: validCount = 3 >= threshold 3 -> PASS
(should FAIL: only 2 distinct signers)
Additional context
- Source:
contracts/src/multisig/EcdsaSignerManager.compact,verifySignature(refactored in #628). - Fix options noted in the module itself: require strictly-increasing (sorted) commitments so a strict total order forbids any repeat anywhere, or use a bitmap of registered-signer indices. The sorted approach keeps O(1) fold state but requires callers to present
pubkeys/signaturessorted by ascending commitment, plus aBytes<32>total-order helper (Compact has no native ordering onBytes<32>:Fieldhas no relational operator andUintcaps at 248 bits, so a byte-wise lexicographic comparison is needed). - Scope: latent — affects any consumer that presents 3+ approvals. Filing to track for a later fix; not blocking current 2-of-N usage.
Version
0.31.0
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read contracts/src/multisig/EcdsaSignerManager.compact, focusing on verifySignature and the module notice. Reproduce the [A, B, A] case with verify<3> and compare the sorted-commitment and bitmap options described in the issue. Done means any repeated signer is rejected for 3+ approvals while current verify<2> behavior remains valid.
Written by the indexing model from the issue text.
Assessment
- Domain
- authorization, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100