The Pattern
View Code
View Code
Owner-Only Pattern
The contract storesH(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
"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
contractAdmin off-chain gives an attacker the
hash, not the preimage.
Rotate the Admin
Why This Works
The ledger storesH(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
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())
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.|| 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.
What’s Next
Minting
Apply access control to minting
ERC20 Token
See access control in a complete token