Access Proofs
An Access Proof is a Groth16 zero-knowledge proof that demonstrates knowledge of a commitment preimage and Merkle tree membership without consuming the commitment. It is the cryptographic primitive that enables persistent, repeatable data access in Specter. Unlike the Redemption Proof — which spends a nullifier and permanently consumes the commitment — an Access Proof authenticates and leaves the commitment intact.
This is the mechanism that separates authentication from consumption. A user can prove "I own this data" an unlimited number of times without ever destroying the proof of ownership.
Why Access Proofs Exist
The Specter data protocol supports two fundamentally different access patterns:
| Pattern | Mechanism | Example |
|---|---|---|
| One-time access | Redemption Proof (spends nullifier) | Gift card redemption, secret sharing, token transfer |
| Persistent access | Access Proof (no nullifier) | Credential verification, recurring authentication, Phantom Identity |
Most real-world data operations are persistent. A diploma is verified on every job application. An API key authenticates on every request. A subscription key is checked on every access. Building these patterns on top of one-time redemption would require re-committing data after every access — an expensive, complex, and fragile approach.
Access Proofs solve this cleanly: the commitment stays in the tree, and the proof can be regenerated for every session.
The Access Proof Circuit
The Access Proof Circuit (accessProof.circom) is deliberately minimal. It has 4 public inputs and 5 private inputs — roughly half the complexity of the 8-input Redemption Circuit.
Public Inputs
| # | Input | Type | Description |
|---|---|---|---|
| 0 | root | Field element | The Merkle tree root being proven against. Must match a known on-chain root. |
| 1 | dataHash | Field element | Hash of the data being accessed. Binds the proof to specific content. |
| 2 | sessionNonce | Field element | Fresh random value generated for this access session. |
| 3 | accessTag | Field element | Anti-replay tag: |
Private Inputs
| Input | Type | Description |
|---|---|---|
secret | Field element | The user's secret (part of commitment preimage) |
nullifierSecret | Field element | Used for accessTag derivation (but NOT for nullifier computation) |
blinding | Field element | Random blinding factor |
pathElements[20] | Field element[20] | Sibling hashes along the Merkle proof path |
pathIndices[20] | Bit[20] | Left/right direction bits for each tree level |
What the Circuit Proves
The circuit enforces three constraints:
-
Commitment computation: The prover knows values (
secret,nullifierSecret,dataHash,blinding) that hash to a commitment in the tree. The commitment is computed as . -
Merkle tree membership: The computed commitment is a leaf in the Merkle tree at the given root. The circuit walks 20 levels using the provided path elements and indices, recomputing the root and constraining it to match the public input.
-
accessTag correctness: The
accessTagis correctly derived from the prover'snullifierSecretand the session'ssessionNonce. This binds the proof to a specific session without revealing thenullifierSecret.
What the Circuit Does NOT Prove
The Access Proof Circuit deliberately omits:
- Nullifier computation: no nullifier is derived, no commitment is consumed.
- Recipient binding: no address is bound to the proof (session nonce replaces this role).
- Value fields: no token amount, no token ID, no change commitment.
- Policy binding: no policy ID, no policy parameters.
These omissions are the entire point. The circuit is stripped down to pure authentication — "I know the preimage of a commitment in this tree" — with nothing that would make the operation one-time or value-bearing.
Proof Generation and Verification Flow
Anti-Replay: The accessTag Mechanism
The accessTag prevents proof replay without consuming the commitment. It is a session-scoped binding that ensures each proof can only be used once.
How It Works
- The verifier (or the protocol) provides a fresh
sessionNoncefor each access session. - The prover computes:
- The prover includes both
sessionNonceandaccessTagas public inputs to the ZK proof. - The circuit verifies that
accessTagis correctly derived from the prover'snullifierSecretand the providedsessionNonce. - The on-chain contract records the
accessTagin a mapping. - If the same
accessTagis submitted again, the transaction reverts.
Why This Is Sufficient
| Property | Guarantee |
|---|---|
| Uniqueness | A new sessionNonce produces a new accessTag (Poseidon collision resistance). |
| Binding | The accessTag is bound to the prover's nullifierSecret (ZK circuit enforces this). An attacker cannot compute a valid accessTag without knowing the secret. |
| Non-replayability | Each accessTag is recorded on-chain. Submitting the same proof twice fails. |
| Non-consumption | The accessTag does not involve the commitment hash or leaf index. The commitment is untouched. |
accessTag vs. Nullifier
| Nullifier | accessTag | |
|---|---|---|
| Derivation | ||
| Deterministic for same commitment? | Yes (always the same) | No (different per session) |
| Effect on commitment | Permanently consumed | No effect |
| Reusability of underlying data | None (one-time) | Unlimited |
| Storage growth | Bounded (one per commitment) | Unbounded (one per session) |
The trade-off is storage growth. Nullifiers are bounded — there is at most one nullifier per commitment, so the nullifier registry grows linearly with the number of commitments. Access tags are unbounded — every session adds a new entry. In practice, the PersistentKeyVault can implement pruning strategies (e.g., expiring old access tags after a sufficient time window) to manage storage growth.
Quantum-Resistant Layer
Access Proofs in the PersistentKeyVault include an additional quantum-resistant verification layer:
- The stored
quantumCommitmentis . - On each access, the caller provides the
quantumSecret(preimage) and anewQuantumCommitment. - The vault verifies:
- If valid, the vault replaces the stored commitment with
newQuantumCommitment.
This rotation means that even if the Groth16/BN254 proving system is compromised (e.g., by a quantum computer), an attacker must also provide a valid keccak256 preimage to access the vault. keccak256 preimage resistance is believed to hold against quantum adversaries (Grover's algorithm provides at most a quadratic speedup, reducing 256-bit security to 128-bit — still computationally infeasible).
Where Access Proofs Are Used
| System | How Access Proofs Are Used |
|---|---|
| Phantom Identity | Each "connect" generates an Access Proof to authorize split-key recombination from the PersistentKeyVault. |
| Persistent credential verification | A credential holder proves ownership of a committed credential on each verification request. |
| Recurring API authentication | An API key holder generates an Access Proof on each API call to prove key ownership. |
| Subscription access | A subscriber proves commitment ownership on each content access. |
| Any persistent data pattern | Any use case where data needs to be proven repeatedly without being consumed. |
Access Proofs are the foundation that makes Specter a persistent data privacy protocol. Without them, every data operation would be one-time — commit, reveal, done. With them, committed data becomes a permanent, reusable, privately-accessible resource.