OpenZeppelin / OpenZeppelin/compact-contracts
N-12: Role Renunciation Allows Unbounded Nullifier Growth
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 55
- Forks
- 29
- Avg merge
- 5d 7h
- Merged PRs (30d)
- 25
Description
N-12 Role Renunciation Allows Unbounded Nullifier Growth
Severity: Note
Source: Midnight Foundation #02 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)
ShieldedAccessControl records granted roles as leaves of a Merkle tree and records revocations as entries in a nullifier set. Proving a role requires a membership path, which _validateRole checks before rejecting any commitment whose nullifier is present.
Revocation requires no such proof. renounceRole confirms only that the supplied account identifier matches the one derived from the caller's witness, then delegates to _updateRole, whose revoke branch inserts the nullifier without establishing that the role was ever granted. Both the role identifier and the account identifier are prover-chosen, the latter varying with the witness secret, so a caller can insert an unbounded number of nullifiers for pairings that never existed, each costing one transaction fee and persisting permanently. A second effect follows from the guard at the head of _updateRole, which rejects an already-revoked pairing on the grant branch as well: a nullifier inserted for a pairing blocks that pairing from ever being granted. That is confined to identities whose secret the caller holds, so it denies a role to the caller rather than to a third party.
Consider requiring a membership proof before a revocation is recorded, reusing the Merkle path _validateRole already checks, so that a nullifier can only exist for a role which was granted. It is further advisable to note in the module that a consumer with no need for self-revocation should not expose this circuit.
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
Start in contracts/src/access/ShieldedAccessControl.compact, focusing on renounceRole, _updateRole, and the existing _validateRole checks. Trace how role and account identifiers reach the revoke branch and how nullifiers are inserted. Done means revocation cannot record a nullifier for an ungranted role, and the module documents when self-revocation should not be exposed.
Written by the indexing model from the issue text.
Assessment
- Domain
- authorization, blockchain, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100