We present Proof-Carrying Authorization (PCA), a cryptographic authorization model that replaces identity-centric access control with proof-based authorization. Instead of asking whether a principal is allowed to perform an action, a verifier checks a non-interactive zero-knowledge (NIZK) proof thatsome witness exists satisfying a policy predicate bound to the action, context, and public inputs. PCA provides privacy for the authorizing party, composability across policies, and replay resistance via nullifiers. We formalize the model, define a canonical statement and domain/scope binding, and give security games for soundness, zero-knowledge, non-malleability, and replay resistance. We also describe a Solana implementation using Pinocchio and a Groth16 verifier adapter, and a private-payments demo that illustrates private authorization for transparent SPL token transfers. We conclude with limitations (front-running, spent-state maintenance) and open problems for scalable and policy-safe deployment.
Keywords: authorization, zero-knowledge, SNARKs, privacy, Solana
Authorization is typically organized around stable identities: users, accounts, or public keys. This yields persistent linkability, makes privacy hard to achieve, and forces verifiers to rely on identity governance or key management. PCA asks a simpler question: does there exist a witness that satisfies the policy predicate for this action and context? If yes, authorization is granted without revealing which member satisfied the predicate.
We designed PCA and the demo program around the following system goals:
The figures use a consistent notation for readability and mathematical alignment:
amount_le8), and field elements are shown as 32-byte big-endian values when applicable.
Contributions.
PCA connects proof-carrying code (PCC) ideas with privacy-preserving authorization. PCC [1] attaches proofs to code or data so that verifiers can check properties without trusting the producer. PCA applies this pattern to authorization decisions, replacing identity checks with statements about policy satisfaction.
Anonymous and attribute-based credentials [2,3] allow selective disclosure of attributes, while privacy-focused payment systems use membership proofs and nullifiers to prevent double spends [5,6]. PCA adopts the spent-set concept and the binding of authorization to a transaction context but separates authorization from value transfer: the asset is public, while the authorizer is private.
ZK membership systems such as Semaphore [8] demonstrate anonymous signaling and access control, but PCA emphasizes explicit domain/scope binding and replay policies that can be reused across programs. Our implementation relies on Groth16 [4] proofs generated with Circom and snarkjs [9] and uses Merkle-tree membership [7] for group inclusion.
Let be the set of actions, contexts, witnesses, public inputs, nullifiers, and proofs. Assume canonical encodings and define the statement:

A policy is an NP relation with language:
Define a domain tuple containing protocol id, policy id, and verifier id. Split context into stable and fresh components .

Domain and scope are hashed on-chain and embedded into field elements for the proof system. In our implementation we split 32-byte hashes into two 128-bit limbs and left-pad each limb into a 32-byte field element.
Let the witness include a secret and a derived nullifier secret . Define:

The verifier accepts at most one proof per nullifier under a fixed domain/scope.
We assume a NIZK system for NP and a collision-resistant hash . The prover demonstrates knowledge of such that:
The verifier checks the proof, validates public inputs, and records the nullifier as spent.

We assume a network adversary who can observe transactions, reorder them, front-run, and submit arbitrary proofs. The adversary cannot break the security of the hash function or the underlying NIZK system and cannot forge valid signatures for system accounts. The verifier is honest-but-curious: it follows the protocol but attempts to learn which member authorized an action.
Security goals:

This paper takes a system-first approach; we provide concise security arguments to justify design choices and threat mitigations. Full cryptographic proofs are out of scope here.
If the prover knows a witness satisfying and computes as specified, then the proof system accepts and the verifier accepts the statement.
If an adversary produces an accepting proof for a statement not in , then it breaks the soundness of the NIZK system or finds collisions in .
By the zero-knowledge property of the NIZK system, proofs reveal no information about or beyond statement validity. The verifier only sees public inputs committed to the action and context.
Domain and scope hashes bind proof to context. Replay is prevented by the nullifier check.
We implement PCA as an on-chain runtime (pinocchio-pca) and a demo program (private_payments) written in Rust using Pinocchio. The runtime computes SHA-256 domain and scope hashes with explicit domain separation, checks context freshness using the Clock sysvar, verifies Groth16 proofs via groth16-solana, and consumes a nullifier PDA to prevent replay.
The demo program maintains a vault SPL token account owned by a program-derived address. A config PDA stores the Merkle root, mint, and vault address. The withdrawal instruction computes action bytes as:
Context bytes are issued_at_slot_le8 || valid_until_slot_le8.

The private-payments circuit proves membership in a Merkle tree of authorized members and binds the proof to a withdrawal action. The public inputs are:
The on-chain scope hash uses action/context bytes and these inputs.

Preliminary measurements from solana-program-test:
| Instruction | Compute units |
|---|---|
| init_config | 12,103 |
| deposit | 7,782 |
| withdraw_private | 198,512 |
Storage overhead involves 140 bytes for Config and 112 bytes per Nullifier. The withdraw instruction payload is 601 bytes.


PCA replaces identity-based authorization with proof-based authorization, enabling private approval without revealing which member authorized an action. We provide formal definitions, security properties, and a working Solana implementation.
Witness . Public inputs .
Define the relation to hold if:
[1] G. C. Necula. Proof-Carrying Code. POPL 1997.
[2] J. Camenisch et al. Anonymous Credentials. EUROCRYPT 2001.
[3] J. Camenisch et al. Signature Schemes... CREDENTIALS ... CRYPTO 2004.
[4] J. Groth. On the Size of Pairing-based Non-interactive Arguments. EUROCRYPT 2016.
[5] Zerocash (IEEE S&P 2014).
[6] Zerocoin (IEEE S&P 2013).
[7] R. Merkle. Digital Signatures.
[8] Semaphore Protocol.
[9] Circom and snarkjs.