Skip to content

Ownable refactor -> replace pk with sk#466

Merged
andrew-fleming merged 12 commits into
OpenZeppelin:mainfrom
andrew-fleming:fix-ownable-sk
May 12, 2026
Merged

Ownable refactor -> replace pk with sk#466
andrew-fleming merged 12 commits into
OpenZeppelin:mainfrom
andrew-fleming:fix-ownable-sk

Conversation

@andrew-fleming
Copy link
Copy Markdown
Contributor

@andrew-fleming andrew-fleming commented Apr 27, 2026

Types of changes

What types of changes does your code introduce to OpenZeppelin Midnight Contracts?
Put an x in the boxes that apply

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update (if none of the other choices apply)

Fixes #453

Summary by CodeRabbit

  • New Features

    • Added account identifier computation capability for ownership verification.
    • Introduced witness-based authentication mechanism for enhanced access control.
  • Updates

    • Changed ownership identity representation.
    • Updated all ownership transfer and management operations.
  • Tests

    • Expanded test coverage for ownership scenarios with new validation tests.
    • Enhanced mock implementations and simulator for better testing support.

@andrew-fleming andrew-fleming requested review from a team as code owners April 27, 2026 21:28
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d318495a-70d7-4d35-9a8e-97c11b957162

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR migrates the Ownable access control module from pubkey-based ownership (ZswapCoinPublicKey) to witness-derived account identifiers (Bytes<32>). It introduces a new witness wit_OwnableSK for secret key provisioning, adds a computeAccountId function using persistent hashing, and refactors all related simulators, tests, and type signatures accordingly.

Changes

Cohort / File(s) Summary
Core Ownable Contract
contracts/src/access/Ownable.compact
Changed _owner type from Either<ZswapCoinPublicKey, ContractAddress> to Either<Bytes<32>, ContractAddress>. Added witness wit_OwnableSK() and pure circuit computeAccountId(secretKey) using persistentHash. Updated all ownership initialization, transfer, and authorization logic to work with computed account identifiers instead of public keys. Updated assertOnlyOwner to compare _owner.left against persistentHash(wit_OwnableSK()).
Ownable Witnesses
contracts/src/access/witnesses/OwnableWitnesses.ts, contracts/src/access/witnesses/test/OwnableWitnesses.test.ts
Evolved witness implementation from empty placeholder to concrete structure. OwnablePrivateState now contains a 32-byte secretKey with generate() and withSecretKey() utility methods. OwnableWitnesses factory returns IOwnableWitnesses<OwnablePrivateState> implementing wit_OwnableSK. Added comprehensive test coverage validating state generation, validation, defensive copying, and witness tuple returns.
Ownable Simulator & Mocks
contracts/src/access/test/simulators/OwnableSimulator.ts, contracts/src/access/test/mocks/MockOwnable.compact
Updated all ownership-related method signatures to use Either<Uint8Array, ContractAddress> instead of Either<ZswapCoinPublicKey, ContractAddress>. OwnableSimulator now supports privateState accessor for test-driven secret key injection via updatePrivateState(). Added computeAccountId pure helper. MockOwnable delegates to new Ownable signatures.
Ownable Test Suite
contracts/src/access/test/Ownable.test.ts
Refactored to use deterministic accountId-based ownership. Added helper functions for deriving accountId via persistentHash and constructing canonical Either values. Updated all simulator initialization to inject privateState.secretKey and call methods directly. Rewritten transfer/renounce tests to validate permission behavior across ownership changes (accountId vs contract owners) and to assert zeroing of inactive Either sides.

Sequence Diagram(s)

sequenceDiagram
    actor Test as Test Suite
    participant Contract as Ownable Contract
    participant Witness as wit_OwnableSK Witness
    participant Hash as persistentHash

    Test->>Contract: initialize(Either::Left(accountId))
    Test->>Contract: assertOnlyOwner() / transferOwnership()
    Note over Contract: Authorization check needed
    Contract->>Witness: wit_OwnableSK()
    Witness-->>Contract: [privateState, secretKey]
    Contract->>Hash: persistentHash(secretKey)
    Hash-->>Contract: computed accountId (Bytes<32>)
    Contract->>Contract: compare computed ≟ _owner.left
    alt Owner matches
        Contract-->>Test: ✓ Operation allowed
    else Owner mismatch
        Contract-->>Test: ✗ Revert
    end
Loading
sequenceDiagram
    actor Caller as Caller
    participant SimCtx as OwnableSimulator
    participant PrivState as privateState
    participant ContractCall as Contract Execution

    Caller->>SimCtx: Create simulator instance
    Caller->>PrivState: injectSecretKey(newSK)
    PrivState-->>Caller: Updated state
    Caller->>SimCtx: transferOwnership(newOwner)
    SimCtx->>SimCtx: computeAccountId(newSK)
    SimCtx->>ContractCall: Call contract with witness context
    ContractCall-->>SimCtx: Execute with injected secret key
    SimCtx-->>Caller: Result + validated state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

audit

Suggested reviewers

  • 0xisk
  • emnul
  • pepebndc

Poem

🐰 A rabbit hops through ownership's new way,
Where witnesses whisper secrets in the fray,
Bytes of thirty-two, hashed swift and true,
No pubkeys now—just accountIds through and through!
Account-derived, systematized,
The Ownable's witness-blessed and optimized! 🔐

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive Linked issue #453 contains no detailed requirements; assessment cannot verify code changes against specific objectives. Review issue #453 to confirm the PR implementation matches stated requirements; or confirm this refactor is the sole requirement.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Ownable refactor -> replace pk with sk' is concise and reflects the core change: switching from public-key-based ownership to secret-key-based account identifiers.
Out of Scope Changes check ✅ Passed All changes align with replacing public-key-based ownership with secret-key-derived account identifiers; no unrelated modifications detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@andrew-fleming andrew-fleming changed the title refactor ownable -> replace pk with sk Ownable refactor -> replace pk with sk Apr 27, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/src/access/test/simulators/OwnableSimulator.ts`:
- Around line 125-149: injectSecretKey currently stores the provided Uint8Array
verbatim which breaks the 32-byte invariant and allows external mutation; change
injectSecretKey to call OwnablePrivateState.withSecretKey(newSK) (which enforces
length and makes a defensive copy) and pass that returned state into
circuitContextManager.updatePrivateState; also ensure getCurrentSecretKey
continues to return the stored key but consider returning a copy if
withSecretKey does not already guarantee immutability (refer to
OwnablePrivateState.withSecretKey and OwnableWitnesses.ts for the expected
behavior).

In `@contracts/src/access/witnesses/OwnableWitnesses.ts`:
- Around line 5-18: The file imports a concrete Ledger type from artifacts which
couples this helper to MockOwnable; remove the Ledger import and make the
WitnessContext ledger type generic/agnostic (e.g., use WitnessContext<unknown,
P> or WitnessContext<any, P>) in the IOwnableWitnesses.wit_OwnableSK signature
so the module only depends on the private state type P; keep the WitnessContext
type import but stop referencing MockOwnable Ledger here and instead bind the
concrete ledger type in simulator/tests where the interface is implemented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 70e1d33e-6c3b-49a4-9e4b-fcc0f2aa3c80

📥 Commits

Reviewing files that changed from the base of the PR and between a8c5225 and 130c362.

📒 Files selected for processing (6)
  • contracts/src/access/Ownable.compact
  • contracts/src/access/test/Ownable.test.ts
  • contracts/src/access/test/mocks/MockOwnable.compact
  • contracts/src/access/test/simulators/OwnableSimulator.ts
  • contracts/src/access/witnesses/OwnableWitnesses.ts
  • contracts/src/access/witnesses/test/OwnableWitnesses.test.ts

Comment thread contracts/src/access/test/simulators/OwnableSimulator.ts Outdated
Comment thread contracts/src/access/witnesses/OwnableWitnesses.ts Outdated
Copy link
Copy Markdown
Member

@0xisk 0xisk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Thank you @andrew-fleming!

Comment thread contracts/src/access/test/Ownable.test.ts Outdated
* @param {Either<Bytes<32>, ContractAddress>} target - The value to check.
* @returns {Boolean} - `true` if the active branch is zero, `false` otherwise.
*/
circuit _isTargetZero(target: Either<Bytes<32>, ContractAddress>): Boolean {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 followup: That should be generalized in Utils.compact

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 followup: OR we generalize the left side of the isKeyOrAddressZero.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the former. The latter's name doesn't work and I don't (currently) see any case where the first type would not be Bytes<32>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be added to the followup issue.

*
* @returns {Bytes<32>} accountId - The computed account identifier.
*/
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 followup: I see the pattern of using those circuits, was thinking if we have a module that only has AccountId state and it's circuit. Then it is reused by all the other modules.

Comment on lines +227 to +228
const isContractAddr = !newOwner.is_left;
assert(!isContractAddr, "Ownable: unsafe ownership transfer");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 followup: as discussed on the AC PR, a generalied Left for the isContractAddress would be better.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added to a followup issue.

Comment thread contracts/src/access/Ownable.compact Outdated
Comment on lines +132 to +133
const isContractAddr = !newOwner.is_left;
assert(!isContractAddr, "Ownable: unsafe ownership transfer");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 followup: Related to a generalized isContractAdd

Comment thread contracts/src/access/Ownable.compact
Comment thread contracts/src/access/Ownable.compact Outdated
Comment thread contracts/src/access/witnesses/OwnableWitnesses.ts Outdated
Comment thread contracts/src/access/Ownable.compact
andrew-fleming and others added 6 commits April 30, 2026 10:08
Co-authored-by: 0xisk <0xisk@proton.me>
Signed-off-by: Andrew Fleming <fleming.andrew@protonmail.com>
Co-authored-by: 0xisk <0xisk@proton.me>
Signed-off-by: Andrew Fleming <fleming.andrew@protonmail.com>
@0xisk 0xisk requested a review from a team as a code owner May 7, 2026 14:40
Copy link
Copy Markdown
Member

@0xisk 0xisk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! thank you @andrew-fleming! the rest of the followup comments are added here: #496

@andrew-fleming andrew-fleming merged commit ab79676 into OpenZeppelin:main May 12, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

C-01: Ownable Contract

2 participants