Snarkey (snarkey v0.1.0)

Copy Markdown View Source

Schnorr-based zero-knowledge proof authentication library.

Snarkey implements the Schnorr identification protocol with Fiat-Shamir non-interactive proofs, interactive fallback, blind-hashed identities, and WebAuthn PRF passkey binding support.

Core guarantees

  1. No password on the wire — secret x never leaves the client
  2. No password in the database — server stores only public_y = g^x mod p
  3. No linkable identity — database rows use HMAC-blind identifiers
  4. Replay-proof — every proof is bound to a timestamp window
  5. Hardware-backed secrets — WebAuthn PRF derives x from passkey seeds

Integration

Snarkey is a pure functional library with zero runtime dependencies. The caller provides storage, transport, and session handling.

See the individual module docs for detailed API reference:

Summary

Functions

Verifies a Schnorr proof against a public key.

Produces a blind identifier by HMAC-SHA256(pepper, raw_id).

Returns the default 2048-bit Schnorr group parameters.

Verifies a registration proof-of-ownership for a new public key.

Types

params()

@type params() :: %{p: integer(), g: integer(), q: integer()}

Functions

authenticate(public_y, map, opts)

@spec authenticate(binary(), map(), keyword()) ::
  {:ok, :authenticated} | {:fallback, binary()} | {:error, atom()}

Verifies a Schnorr proof against a public key.

Non-interactive (Fiat-Shamir) path

The proof map must contain :r, :s, and :timestamp keys.

Options

  • :params — crypto params (defaults to default_params/0)
  • :user_id — user identifier used in the Fiat-Shamir hash
  • :max_drift — max timestamp skew in seconds (default 5)

Returns

  • {:ok, :authenticated} — proof valid
  • {:fallback, challenge} — proof expired, challenge binary for interactive re-auth
  • {:error, :unauthorized} — proof invalid
  • {:error, :expired} — timestamp outside drift window

Interactive path

The proof map must contain :r, :c, and :s keys. Used after the caller has obtained a challenge from Snarkey.Fallback.generate_challenge/1.

Options

Returns

  • {:ok, :authenticated} — proof valid
  • {:error, :unauthorized} — proof invalid

blind_identity(raw_id)

Produces a blind identifier by HMAC-SHA256(pepper, raw_id).

Reads the pepper from Application.get_env(:snarkey, :pepper).

Examples

iex> blind = Snarkey.blind_identity("user@example.com")
iex> is_binary(blind)
true

default_params()

Returns the default 2048-bit Schnorr group parameters.

Delegates to Snarkey.Crypto.default_params/0.

Examples

iex> params = Snarkey.default_params()
iex> params.p |> is_integer()
true
iex> params.g
2

register(public_y, map, opts)

Verifies a registration proof-of-ownership for a new public key.

The client sends {public_y, r, s} and the server uses a pre-generated nonce to compute c = hash_to_scalar(g || y || r || nonce) and verify the proof. This ensures the client knows the secret key corresponding to public_y without revealing it.

Options

  • :params — crypto params (defaults to default_params/0)
  • :noncerequired. Server-generated registration nonce (binary)
  • :user_id — optional user identifier for logging

Returns

  • {:ok, :registered} — proof is valid, caller should store public_y
  • {:error, :invalid_proof} — proof failed verification