> ## Documentation Index
> Fetch the complete documentation index at: https://compact-by-example.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Access Control

> Owner-only and role-based access control in Compact using witness-derived keypairs — and why ownPublicKey() is not authentication.

<Warning>
  **Security warning — `ownPublicKey()` does not authorize the caller.**

  `ownPublicKey()` returns a value the prover knows, not a proof of key
  ownership. Any value stored on the public ledger can be replayed by anyone
  reading the chain. Do not use `ownPublicKey()` for authorization checks
  (`assert(ownPublicKey() == storedAdmin)`). Use it only as an identifier for
  the operation's target (e.g. "mint to this caller"). For authorization, see
  [Owner-Only Pattern](#owner-only-pattern) below.
</Warning>

## The Pattern

<Accordion title="View Code">
  ```compact theme={null}
  pragma language_version >= 0.23;

  import CompactStandardLibrary;

  struct AdminSecretKey { bytes: Bytes<32>; }
  struct AdminPublicKey { bytes: Bytes<32>; }

  // The ledger stores only the derived public key (a hash of the secret),
  // never the secret itself.
  export ledger contractAdmin: AdminPublicKey;

  // The deployer's DApp generates this secret and keeps it in private state.
  witness getAdminSecret(): AdminSecretKey;

  constructor() {
      contractAdmin = disclose(deriveAdminPublicKey(getAdminSecret()));
  }

  export circuit deriveAdminPublicKey(sk: AdminSecretKey): AdminPublicKey {
      return AdminPublicKey {
          bytes: persistentHash<Vector<2, Bytes<32>>>([
              pad(32, "myapp:admin:pk:v1"),
              sk.bytes
          ])
      };
  }

  export circuit privilegedAction(): [] {
      // To pass, the caller must supply a witness `sk` such that
      // H("myapp:admin:pk:v1" || sk) == contractAdmin.
      assert(
          contractAdmin == deriveAdminPublicKey(getAdminSecret()),
          "Not authorized."
      );
      // ... privileged logic
  }

  // Rotate the admin key without ever transmitting a private key.
  // The new admin generates their secret locally and shares only the
  // derived public key with the current admin.
  export circuit rotateAdmin(newAdmin: AdminPublicKey): [] {
      assert(
          contractAdmin == deriveAdminPublicKey(getAdminSecret()),
          "Not authorized to rotate admin."
      );
      contractAdmin = disclose(newAdmin);
  }
  ```
</Accordion>

## Owner-Only Pattern

The contract stores `H(domain || secret)` on the ledger, never the secret
itself. The admin's DApp holds the secret in private state and re-derives
the public key on every call. The on-chain value gives an attacker only the
hash; hash preimage resistance means they cannot forge a matching secret.

### Store the Admin Public Key

```compact theme={null}
struct AdminSecretKey { bytes: Bytes<32>; }
struct AdminPublicKey { bytes: Bytes<32>; }

export ledger contractAdmin: AdminPublicKey;

witness getAdminSecret(): AdminSecretKey;

constructor() {
    contractAdmin = disclose(deriveAdminPublicKey(getAdminSecret()));
}

export circuit deriveAdminPublicKey(sk: AdminSecretKey): AdminPublicKey {
    return AdminPublicKey {
        bytes: persistentHash<Vector<2, Bytes<32>>>([
            pad(32, "myapp:admin:pk:v1"),
            sk.bytes
        ])
    };
}
```

The domain string `"myapp:admin:pk:v1"` binds the public key to this
contract. The same secret will not derive the same public key under a
different domain, so an admin key cannot be replayed across contracts.

### Authorization Check

```compact theme={null}
export circuit privilegedAction(): [] {
    assert(
        contractAdmin == deriveAdminPublicKey(getAdminSecret()),
        "Not authorized."
    );
    // ... privileged logic
}
```

The assertion succeeds only if the caller knows a secret whose hash matches
the stored value. Reading `contractAdmin` off-chain gives an attacker the
hash, not the preimage.

### Rotate the Admin

```compact theme={null}
export circuit rotateAdmin(newAdmin: AdminPublicKey): [] {
    assert(
        contractAdmin == deriveAdminPublicKey(getAdminSecret()),
        "Not authorized to rotate admin."
    );
    contractAdmin = disclose(newAdmin);
}
```

The new admin generates their secret locally and shares only the derived
public key with the current admin. Private keys never leave the holder's
DApp.

## Why This Works

The ledger stores `H(domain || secret)` — the hash of the admin's secret.
To pass the assertion, the caller must provide a witness `sk` such that
`H(domain || sk)` equals the stored value. The on-chain value is a hash,
not the secret itself, so reading the ledger gives an attacker nothing they
can replay. The domain string prevents the same secret from being valid
across different contracts.

This is what Solidity gets implicitly from `msg.sender` and EVM signature
verification. In Compact, you construct the check yourself.

## Anti-Pattern: ownPublicKey() as Authorization

<Warning>
  **Do not use this pattern.** It is shown only to document what fails.
</Warning>

```compact theme={null}
// DO NOT USE — this is broken
export ledger admin: ZswapCoinPublicKey;

constructor() {
    admin = disclose(ownPublicKey());
}

export circuit privileged(): [] {
    assert(ownPublicKey() == admin, "Only admin.");
}
```

`ownPublicKey()` returns whatever value the prover claims to know. An
attacker reads `admin` from the public ledger, supplies it as their own
`ownPublicKey()`, and produces a mathematically valid proof. The proof
correctly demonstrates "the prover knows a value equal to `admin`" — which
anyone watching the chain does. The check provides no authorization.

## When to Use ownPublicKey()

`ownPublicKey()` is the correct primitive when you need a stable identifier
for the caller as the **target** of an operation:

* "Mint a token to this caller": `mint(ownPublicKey(), tokenId)`
* "Credit this caller's balance": `balances.insert(ownPublicKey(), ...)`
* "Record this caller as the recipient": `recipient = disclose(ownPublicKey())`

It identifies who the operation is for. It does not prove who is allowed
to perform it. The moment you write `assert(ownPublicKey() == ...)`, you
have crossed into authorization, and the pattern breaks.

## Role-Based Access

Multiple roles use the same keypair pattern with one ledger slot per role
and a single witness that returns the caller's secret.

```compact theme={null}
struct SecretKey { bytes: Bytes<32>; }
struct PublicKey { bytes: Bytes<32>; }

export ledger contractOwner: PublicKey;
export ledger contractMinter: PublicKey;

witness getCallerSecret(): SecretKey;

constructor(initialMinter: PublicKey) {
    contractOwner = disclose(derivePublicKey(getCallerSecret()));
    contractMinter = disclose(initialMinter);
}

export circuit derivePublicKey(sk: SecretKey): PublicKey {
    return PublicKey {
        bytes: persistentHash<Vector<2, Bytes<32>>>([
            pad(32, "myapp:role:v1"),
            sk.bytes
        ])
    };
}

// Owner-only: rotate the minter slot
export circuit setMinter(newMinter: PublicKey): [] {
    assert(
        contractOwner == derivePublicKey(getCallerSecret()),
        "Only owner can set minter."
    );
    contractMinter = disclose(newMinter);
}

// Either role may call this
export circuit mintingAction(): [] {
    const callerKey = disclose(derivePublicKey(getCallerSecret()));
    assert(
        callerKey == contractOwner || callerKey == contractMinter,
        "Not authorized to mint."
    );
    // ... minting logic
}
```

The `||` check would reveal which role matched, so `callerKey` is disclosed
explicitly. That is safe: a derived public key equals the on-chain value
only when the caller is already authorized, and the on-chain value is
public anyway.

## Alternative Patterns

The keypair pattern above covers single-admin and role-based authorization.
For other situations:

* **Signature verification against a stored verifying key** — when you need
  multi-sig, cold-key signing, or cross-contract authority. The contract
  stores a verifying key; privileged circuits accept a signature over the
  operation as a parameter.
* **Commitment / nullifier patterns** — when you need anonymous user
  authorization with unlinkability across actions (anonymous voting,
  single-use credentials). The contract stores commitments and tracks
  nullifiers.

Both are documented elsewhere.

## What's Next

<CardGroup cols={2}>
  <Card title="Minting" icon="plus" href="/basics/minting">
    Apply access control to minting
  </Card>

  <Card title="ERC20 Token" icon="coins" href="/applications/erc20-token">
    See access control in a complete token
  </Card>
</CardGroup>
