OpenZeppelin / OpenZeppelin/compact-contracts

[Bug]: EcdsaSignerManager.verify only detects adjacent duplicate signers (incorrect for 3+ approvals)

Open
#629 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

1-medium bug
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?
  1. Use EcdsaSignerManager (from #628) with a registry of 3+ signers and a threshold of 3.
  2. Call verify<n> with n >= 3, presenting a commitment vector that contains a non-adjacent duplicate, e.g. [A, B, A].
  3. 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/signatures sorted by ascending commitment, plus a Bytes<32> total-order helper (Compact has no native ordering on Bytes<32>: Field has no relational operator and Uint caps 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.