AuthenSeeDocs

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:

  1. Knowledge of answers -- You know answers that, when hashed, produce leaves in the Merkle tree.
  2. Merkle inclusion -- Those leaves are part of the Merkle tree committed during enrollment (matching the stored root).
  3. Threshold satisfaction -- You answered at least K out of N questions correctly.
  4. Passkey verification -- Your passkey signature is valid (verified inside the circuit, not as a separate step).
  5. 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.

         Root
        /    \
      H01     H23
      / \     / \
    H0   H1 H2  H3
    |    |   |    |
   L0   L1  L2   L3

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

leaf = Poseidon2(Poseidon2(normalize(answer)), salt)
  1. The raw answer is normalized (lowercased, trimmed, whitespace-collapsed) for consistent hashing.
  2. The normalized answer is hashed with Poseidon2.
  3. The result is hashed again with a per-question salt (derived deterministically from the persona ID and question index).
  4. 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).

nullifier = Poseidon2(salt, challengeId)

Properties

PropertyDescription
DeterministicSame user + same challenge always produces the same nullifier
UnlinkableDifferent challenges produce different nullifiers -- you cannot correlate proofs across authentication sessions
Collision-resistantPoseidon2 over the BN254 scalar field provides approximately 254 bits of collision resistance
Single-useThe server stores spent nullifiers and rejects any proof that reuses one

How replay prevention works

  1. The server issues a challenge (nonce) for each authentication attempt.
  2. The SDK computes a nullifier from the user's salt and the challenge ID.
  3. The nullifier is included as a public input in the ZK proof.
  4. The server checks: has this nullifier been used before?
  5. 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:

CircuitWhat it provesUsed by
memory_authKnowledge of answers (K-of-N threshold) + passkey signature -- combined in a single proofHuman authentication
memory_auth_passkeyPasskey 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

DataDescription
Merkle rootsOne field element per factor per user
Spent nullifiersPrevents proof replay
Proof metadataProver version and timestamps only (for analytics)
Provider configsAPI keys, branding, auth policies
Persona IDsOpaque identifiers with type (human/agent)

The server does not store: full proofs, answer hashes, questions, answers, salts, Merkle paths, or any PII by default.