Core Concepts
How zero-knowledge proofs, Merkle trees, Poseidon2 hashing, and nullifiers work in AuthenSee.
Core Concepts
AuthenSee uses zero-knowledge cryptography to authenticate users without ever seeing their secrets. This page explains the key primitives in plain terms.
Zero-knowledge proofs
A zero-knowledge proof (ZK proof) lets you prove you know something without revealing what you know.
Analogy: Imagine a locked room with two doors. You can prove you have the key by entering through one door and exiting through the other -- without ever showing anyone the key itself.
In AuthenSee, the "secret" is your answers to security questions. The ZK proof demonstrates that your answers hash to the same values committed during enrollment, without revealing the answers themselves.
What the proof proves
The AuthenSee ZK proof (generated by the memory_auth Noir circuit) demonstrates all of the following in a single proof:
- Knowledge of answers -- You know answers that, when hashed, produce leaves in the Merkle tree.
- Merkle inclusion -- Those leaves are part of the Merkle tree committed during enrollment (matching the stored root).
- Threshold satisfaction -- You answered at least K out of N questions correctly.
- Passkey verification -- Your passkey signature is valid (verified inside the circuit, not as a separate step).
- Challenge binding -- The proof is bound to a specific server-issued challenge, preventing cross-provider hijacking.
What the proof does NOT reveal
- The answers themselves
- Which specific questions you answered
- The individual leaf hashes
- The Merkle tree structure or paths
- Any salts used in hashing
Merkle trees
A Merkle tree is a data structure that lets you commit to a set of values with a single hash (the "root") and later prove that any individual value is part of the set.
Each leaf (L0-L3) is a hashed answer. Internal nodes are computed by hashing their children together. The root is a single value that uniquely represents the entire set of answers.
How AuthenSee uses Merkle trees
- Enrollment: Each answer is hashed into a leaf. The leaves are assembled into a Merkle tree. Only the root is sent to the server.
- Authentication: The user re-answers questions. The SDK reconstructs the leaves, computes a Merkle inclusion proof (the path from each leaf to the root), and feeds this into the ZK circuit.
- Verification: The server checks that the ZK proof's public root matches the stored root. It never sees any leaves or paths.
Each user has their own per-user Merkle tree. Trees are not shared across users.
Poseidon2 hashing
AuthenSee uses Poseidon2 as its hash function rather than SHA-256 or other traditional hashes. Poseidon2 is a hash function designed specifically for use inside zero-knowledge circuits.
Why Poseidon2?
Traditional hash functions like SHA-256 require thousands of constraints when implemented inside a ZK circuit, making proof generation slow and expensive. Poseidon2 is "ZK-native" -- it operates directly on the finite field elements used by the proof system, requiring far fewer constraints.
How answers are hashed
- The raw answer is normalized (lowercased, trimmed, whitespace-collapsed) for consistent hashing.
- The normalized answer is hashed with Poseidon2.
- The result is hashed again with a per-question salt (derived deterministically from the persona ID and question index).
- The final value becomes a leaf in the Merkle tree.
The double-hash with salt prevents rainbow table attacks and ensures that the same answer for different questions produces different leaves.
Nullifiers
A nullifier prevents the same proof from being used twice (replay attack).
Properties
| Property | Description |
|---|---|
| Deterministic | Same user + same challenge always produces the same nullifier |
| Unlinkable | Different challenges produce different nullifiers -- you cannot correlate proofs across authentication sessions |
| Collision-resistant | Poseidon2 over the BN254 scalar field provides approximately 254 bits of collision resistance |
| Single-use | The server stores spent nullifiers and rejects any proof that reuses one |
How replay prevention works
- The server issues a challenge (nonce) for each authentication attempt.
- The SDK computes a nullifier from the user's salt and the challenge ID.
- The nullifier is included as a public input in the ZK proof.
- The server checks: has this nullifier been used before?
- If yes -- reject (replay detected). If no -- claim it atomically and proceed.
The check and insert happen inside a single database transaction with a unique constraint as a safety net.
Provider-specific challenges
Every authentication proof is bound to a specific provider via a server-generated challenge. This prevents cross-provider hijacking -- a proof generated for Provider A cannot be replayed against Provider B.
The server identifies the provider from the session token, generates a challenge with provider-specific metadata (provider ID, domain, timestamp, nonce), and returns it to the SDK. This challenge becomes a public input in the ZK proof and is verified during proof verification.
Circuit architecture
AuthenSee uses two Noir circuits:
| Circuit | What it proves | Used by |
|---|---|---|
memory_auth | Knowledge of answers (K-of-N threshold) + passkey signature -- combined in a single proof | Human authentication |
memory_auth_passkey | Passkey signature only (no knowledge factors) | Agent authentication |
The memory_auth_passkey circuit is restricted to personas flagged as agent, preventing humans from downgrading to weaker authentication.
Whether agent personas may authenticate at all — and how fast — is each provider's call: the dashboard's Policy screen can block agents outright or cap each agent at a per-minute budget, enforced by the auth server at enrollment, challenge binding, and verification. See Provider policy.
What the server stores
| Data | Description |
|---|---|
| Merkle roots | One field element per factor per user |
| Spent nullifiers | Prevents proof replay |
| Proof metadata | Prover version and timestamps only (for analytics) |
| Provider configs | API keys, branding, auth policies |
| Persona IDs | Opaque identifiers with type (human/agent) |
The server does not store: full proofs, answer hashes, questions, answers, salts, Merkle paths, or any PII by default.