Skip to content

fix(access-control): replace ownPublicKey() auth with witness-derived keypair pattern#1

Merged
Olanetsoft merged 1 commit into
mainfrom
fix/access-control-ownpublickey-auth-antipattern
May 12, 2026
Merged

fix(access-control): replace ownPublicKey() auth with witness-derived keypair pattern#1
Olanetsoft merged 1 commit into
mainfrom
fix/access-control-ownpublickey-auth-antipattern

Conversation

@Olanetsoft

@Olanetsoft Olanetsoft commented May 12, 2026

Copy link
Copy Markdown
Owner

Summary

Replace the ownPublicKey()-as-authorization pattern on the Access Control page with the witness-derived keypair pattern, and document the original pattern explicitly as broken.

Why this matters

ownPublicKey() returns a value the prover claims to know — it is not a proof of key ownership. The pattern previously taught on this page:

export ledger admin: ZswapCoinPublicKey;
constructor() { admin = disclose(ownPublicKey()); }
export circuit privileged(): [] {
    assert(ownPublicKey() == admin, "Only admin.");
}

…is bypassable by anyone. An attacker reads admin from the public ledger, passes it back as their own ownPublicKey() claim, and produces a mathematically valid proof. The proof correctly demonstrates "the prover knows a value equal to admin" — which is exactly what every chain observer also knows.

Solidity gets caller authentication implicitly from msg.sender + EVM signature verification. Compact has no direct analogue; you have to construct the authorization check yourself.

What this PR does

  • Adds a <Warning> banner at the top of the page calling out the vulnerability.
  • Rewrites the Owner-Only Pattern section around the witness-derived keypair pattern: ledger stores H(domain || secret); the witness supplies the secret; the assertion verifies the hash matches. The on-chain value is preimage-resistant, so reading the ledger gives an attacker nothing replayable. The domain string scopes the key to the contract.
  • Adds an Anti-Pattern section showing the original code with a DO NOT USE banner and a one-paragraph explanation of why it fails.
  • Adds a When to Use ownPublicKey() section so readers do not over-correct. ownPublicKey() is fine as a target identifier (mint(ownPublicKey(), tokenId), recipient = disclose(ownPublicKey())); it is only dangerous when used in assert(ownPublicKey() == ...).
  • Rewrites the Role-Based Access section using the same keypair pattern with one ledger slot per role.
  • Adds a brief Alternative Patterns section pointing at signature-verification and commitment/nullifier patterns for situations the keypair pattern does not cover.
  • Fixes corrupted frontmatter that had a duplicate ---le: "Access Control" block.

Other instances of the same anti-pattern (NOT fixed here)

Per task scope, these are flagged but left for follow-up:

  • basics/transfer.mdx — line 30, line 83. Uses ownPublicKey() as from in _transfer(from, to, amount). The page's "Implicit authorization" claim (lines 91–97) is incorrect — an attacker can spoof from to transfer from any account.
  • basics/approval.mdx — lines 35, 127. _approve(owner = ownPublicKey(), spender, amount) lets anyone approve spends from any owner.
  • basics/allowance.mdx — lines 53, 139. _spendAllowance(from, spender = ownPublicKey(), amount) — the spender check is the same anti-pattern.
  • basics/burning.mdx — lines 34, 43, 126, 145. Self-burn relies on ownPublicKey() for account — attackers can burn anyone's tokens.
  • applications/erc20-token.mdx — lines 84, 92, 116, 305. Transfer/approve/transferFrom + initial-supply mint to deployer all use the broken pattern.
  • applications/erc721-nft.mdx — lines 90, 107, 133. approve / setApprovalForAll / transferFrom.
  • applications/erc1155-multitoken.mdx — lines 63, 88. Line 88 in particular is the canonical assert(from == caller || isApprovedForAll(from, caller)) pattern with caller = ownPublicKey() — trivially bypassable.

All of these share the same root cause: treating ownPublicKey() as msg.sender. They should be migrated to the witness-derived keypair pattern (or, for transfer/approve flows, to a balance-bound version of it). Tracking separately so this PR stays scoped to the page security review is most likely to land on first.

Test plan

  • All 6 compact code blocks in the new file compile cleanly under language version 0.22 (compactc 0.30.0 --skip-zk).
  • No new MDX components introduced — only <Warning>, <Accordion>, <Card>, <CardGroup> already used elsewhere on the site.
  • Section heading convention (Title Case) preserved.
  • Frontmatter corruption fixed (duplicate ---le: block removed).
  • Visual check in Mintlify preview that admonitions render with the strongest available treatment.

Summary by CodeRabbit

  • Documentation
    • Substantially updated access control documentation with comprehensive new examples and patterns
    • Added security warnings and clarifications on authorization mechanisms
    • Expanded role-based access control guidance and anti-pattern demonstrations
    • Enhanced explanations of when different authorization approaches are appropriate

Review Change Stack

…-derived keypair

`ownPublicKey()` returns a value the prover claims to know, not a proof of key
ownership. Storing it on the ledger and asserting `ownPublicKey() == storedAdmin`
lets any attacker read the stored value off-chain, supply it as their own
`ownPublicKey()`, and produce a valid proof. The access control is non-existent.

This rewrites the Access Control page around the witness-derived keypair
pattern: the ledger stores `H(domain || secret)`; the caller must provide a
witness `sk` such that `H(domain || sk)` matches. Hash preimage resistance
makes the on-chain value useless to an attacker. The page now also includes
an explicit anti-pattern section, a "when to use ownPublicKey()" section for
identifier-as-target uses, a role-based variant, and a pointer to signature
and commitment/nullifier patterns.

All Compact blocks compile against language version 0.22.
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0dca93f7-5d7b-4fc5-8967-ad50e9158a7c

📥 Commits

Reviewing files that changed from the base of the PR and between 1d7029b and 9133b2a.

📒 Files selected for processing (1)
  • basics/access-control.mdx

📝 Walkthrough

Walkthrough

This PR comprehensively rewrites the access control documentation page to teach witness-derived keypair authorization patterns. It replaces unsafe ownPublicKey()-based examples with on-ledger stored derived admin keys, working code examples for single-role and multi-role access, and explicit anti-pattern guidance showing why direct caller checks fail.

Changes

Access Control Authorization Patterns

Layer / File(s) Summary
Documentation context and security warnings
basics/access-control.mdx
Page metadata updated to match new access control theme; a prominent security warning is added explaining that ownPublicKey() is not an authorization mechanism.
Owner-only access control pattern
basics/access-control.mdx
Witness-derived admin public key workflow is introduced: storing a derived key on-ledger bound to a domain, computing authorization from a private witness secret, gating privileged actions, and rotating admin keys while keeping secrets off-chain. "Why This Works" explanation contrasts on-ledger domain-hashed values with direct caller checks.
Role-based access control variant
basics/access-control.mdx
Role-slot design extends the owner-only pattern to support multiple roles (owner, minter) with independently derived public keys computed from caller witness secrets. Includes circuits for role rotation and enforcing owner-or-minter authorization.
Anti-patterns and authorization guidance
basics/access-control.mdx
Anti-Pattern section demonstrates the broken authorization approach comparing ownPublicKey() to a stored ledger value and explains why the proof does not imply caller authorization. Guidance clarifies ownPublicKey() is suitable only as a stable operation identifier. Alternative Patterns section updated to reference signature verification and commitment/nullifier approaches. Minor copy edit to "What's Next" card.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🐰 A guide reborn, no longer led astray,
Where secrets stay safe and derive the day,
Admin keys rotate, roles find their way,
No more ownPublicKey() has its say!
thumpity-thump 🔐✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the primary change: replacing a vulnerable ownPublicKey() authentication pattern with a witness-derived keypair pattern, which is the main substance of the documentation rewrite.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/access-control-ownpublickey-auth-antipattern

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@Olanetsoft Olanetsoft merged commit 78d664c into main May 12, 2026
2 of 4 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.

1 participant