OpenZeppelin / OpenZeppelin/compact-contracts

N-12: Role Renunciation Allows Unbounded Nullifier Growth

Open
#817 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

2-low audit
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

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

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.