Skip to content

N-09: Lack Of Two-Step Ownership Transfer Modules #814

Description

@andrew-fleming

N-06 Lack Of Two-Step Ownership Transfer Modules

Severity: Note

Source: Midnight Foundation #2 — OpenZeppelin Compact Contracts Audit, Release 0.3.0-alpha.1 (2026-08-18)


ZOwnablePK implements ownership by storing an _ownerCommitment and requiring privileged circuits to call assertOnlyOwner. This function recomputes an owner identifier from ownPublicKey() and the witness wit_secretNonce(), and compares it against that commitment. A valid identifier is therefore a hash preimage, since _computeOwnerId returns persistentHash over the public key and the nonce.

Ownership transfer is a single step. transferOwnership accepts any newOwnerId other than the type default and commits it, with no step in which the recipient demonstrates that a preimage exists. For an arbitrary 32-byte value, no such preimage is findable, so once this value is committed, no caller can satisfy assertOnlyOwner and every owner-gated circuit becomes permanently unreachable. This includes the two circuits that might otherwise recover the situation, since a further transferOwnership and renounceOwnership both begin by calling assertOnlyOwner. Similarly, Ownable.compact too implements the single-step ownership transfer.

Consider implementing a two-step transfer module in which transferOwnership records a pending identifier and a separate acceptance circuit requires the recipient to prove knowledge of a public key and secret nonce reproducing it before _ownerCommitment is updated, similar to the Ownable and Ownable2Step contracts of the OpenZeppelin Solidity Library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Backlog
    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions