Skip to content

gthstepsecurity/2fapi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

2FApi

Zero-Knowledge Proof Authentication for APIs

Prove you know the secret. Never reveal it.

License Curve Security Rust PostgreSQL Redis


What is 2FApi?

2FApi is a zero-knowledge proof authentication protocol for APIs. Instead of sharing secrets with the server (like passwords or API keys), the client proves it knows a secret without ever revealing it.

The server stores zero secrets. If your database is breached, the attacker gets cryptographic commitments — mathematically useless without the client's secret.

Protocol Overview

Client                                    Server
  │                                         │
  │──── 1. Enroll (commitment C) ─────────>│  Stores C = s·G + r·H
  │                                         │  (never sees s or r)
  │                                         │
  │<──── 2. Challenge (nonce n) ───────────│  Issues random nonce
  │                                         │
  │──── 3. Proof (A, z_s, z_r) ──────────>│  Verifies: z_s·G + z_r·H == A + c·C
  │                                         │  where c = H(G‖H‖C‖A‖n)
  │                                         │
  │<──── 4. Access Token ─────────────────│  Issues JWT/session

Key Properties

Property Description
Zero-knowledge Server learns nothing about the client's secret
No shared secrets Server stores only commitments, never passwords or keys
Replay-resistant Single-use nonces with TTL prevent proof reuse
Constant-time All cryptographic operations resist timing attacks
128-bit security Ristretto255 curve (same level as Ed25519)

Components

Package Description Language
crypto-core Pedersen commitments & Sigma proofs over Ristretto255 Rust
crypto-core/napi Node.js native bindings (napi-rs) Rust → Node.js
extensions/pg-extension PostgreSQL extension (pg_2fapi) Rust (pgrx)
extensions/redis-module Redis module (redis-2fapi) Rust
packages/client-sdk Client SDK for browsers & Node.js TypeScript
packages/protocol-spec Protocol specification TypeScript

Quick Start

Option 1: PostgreSQL Extension

# Build & run with Docker
docker build -f extensions/pg-extension/Dockerfile.pg16 -t pg-2fapi:16 .
docker run -d -p 5440:5432 -e POSTGRES_PASSWORD=dev \
  -e POSTGRES_USER=twofapi -e POSTGRES_DB=twofapi pg-2fapi:16

# Connect and use
psql -h localhost -p 5440 -U twofapi -d twofapi
-- Enroll a client
SELECT twofapi.enroll('my-service', commitment_bytes, proof_bytes);

-- Request a challenge
SELECT twofapi.issue_challenge('my-service');

-- Get the nonce for proof construction
SELECT twofapi.get_challenge_nonce('my-service', 'ch-abc123');

-- Authenticate (verify proof + establish session)
SELECT twofapi.authenticate('my-service', 'ch-abc123', proof_bytes);

-- Use in Row-Level Security policies
ALTER TABLE my_data ENABLE ROW LEVEL SECURITY;
CREATE POLICY zkp_policy ON my_data
  USING (owner = twofapi.current_client());

-- Now queries are automatically filtered
SELECT * FROM my_data;  -- only returns rows owned by authenticated client

Option 2: Redis Module

# Build & run with Docker
docker build -f extensions/redis-module/Dockerfile -t redis-2fapi .
docker run -d -p 6380:6379 redis-2fapi

# Connect and use
redis-cli -p 6380
> 2FAPI.ENROLL my-service <commitment_hex>
OK

> 2FAPI.CHALLENGE my-service
1) "ch-a1b2c3d4e5f67890"
2) "a1b2c3d4e5f6789001234567890abcde"

> 2FAPI.VERIFY my-service ch-a1b2c3d4e5f67890 <proof_hex>
OK

> 2FAPI.WHOAMI
"my-service"

> 2FAPI.STATUS my-service
"active"

Option 3: Rust Crate (Direct Integration)

# Cargo.toml
[dependencies]
twofapi-crypto-core = { git = "https://github.com/gthstepsecurity/2fapi", path = "crypto-core" }
use twofapi_crypto_core as crypto;

// Generate commitment (client-side)
let (g, h) = crypto::generators();
let commitment = crypto::commit(&secret, &blinding);

// Verify proof (server-side)
let valid = crypto::verify_equation_raw(
    &g_bytes, &h_bytes, &commitment_bytes,
    &announcement, &challenge, &response_s, &response_r,
);

Option 4: Node.js via NAPI Bindings

cd crypto-core/napi && npm install && npm run build
const crypto = require('./crypto-core/napi');

// Generators
const G = crypto.getGeneratorG();  // 32 bytes
const H = crypto.getGeneratorH();  // 32 bytes

// Commitment (client-side)
const commitment = crypto.commit(secret, blinding);

// Proof generation (client-side)
const proof = crypto.generateProof({
  secret, blinding, commitment,
  generatorG: G, generatorH: H,
  transcriptData: buildTranscript(...)
});

// Proof verification (server-side)
const valid = crypto.verifyProofEquation({
  generatorG: G, generatorH: H,
  commitment, announcement,
  challenge, responseS, responseR
});

Integration Guide

Integrating 2FApi into an Existing Application

Step 1: Add the crypto core to your backend

Rust backend:

[dependencies]
twofapi-crypto-core = { git = "https://github.com/gthstepsecurity/2fapi", path = "crypto-core" }

Node.js backend:

npm install @2fapi/crypto-napi

Step 2: Create a commitments table

CREATE TABLE zk_commitments (
    user_id UUID PRIMARY KEY REFERENCES users(id),
    commitment BYTEA NOT NULL CHECK (length(commitment) = 32),
    status TEXT NOT NULL DEFAULT 'active',
    commitment_version INTEGER NOT NULL DEFAULT 1,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

Step 3: Implement enrollment endpoint

POST /auth/zkp/enroll
Body: { commitment: <hex>, proof: <hex> }

Server:
  1. Validate commitment (32 bytes, canonical Ristretto255, not identity)
  2. Validate proof of possession (96 bytes, canonical encodings)
  3. Verify proof opens the commitment
  4. Store commitment for the authenticated user
  5. Return success

Step 4: Implement challenge endpoint

POST /auth/zkp/challenge
Body: { user_id: <uuid> }

Server:
  1. Verify user exists and has active commitment
  2. Generate 16 random bytes (nonce) via CSPRNG
  3. Store nonce in Redis/memory with 120-second TTL
  4. Return { challenge_id, nonce }

Step 5: Implement verification endpoint

POST /auth/zkp/verify
Body: { challenge_id: <string>, proof: <hex> }

Server:
  1. Fetch and DELETE challenge (atomic, single-use)
  2. Fetch user's commitment
  3. Build Fiat-Shamir transcript
  4. Compute challenge scalar c = SHA-512(transcript)
  5. Verify equation: z_s·G + z_r·H == A + c·C
  6. If valid: issue JWT/session token

Step 6: Add client-side proof generation

<!-- Load WASM SDK -->
<script src="@2fapi/client-sdk/dist/2fapi.min.js"></script>
// On login page
const { commitment, secret, blinding } = await TwoFApi.generateCredential();
// Store secret securely (browser keychain or derived from recovery phrase)

// On each authentication
const challenge = await fetch('/auth/zkp/challenge', { method: 'POST', ... });
const proof = await TwoFApi.generateProof(secret, blinding, challenge.nonce);
const result = await fetch('/auth/zkp/verify', { body: { proof }, ... });

Cryptographic Details

Primitives

Primitive Implementation
Curve Ristretto255 (via curve25519-dalek v4)
Commitment Pedersen: C = s·G + r·H
Proof Schnorr/Sigma protocol (non-interactive via Fiat-Shamir)
Hash SHA-512 with domain separation
Generator G Ristretto255 basepoint
Generator H Hash-to-point with DST "2FApi-Pedersen-GeneratorH-v1"

Transcript Format

Length-prefixed fields (4-byte big-endian):

LP("2FApi-v1.0-Sigma") ‖ LP(G) ‖ LP(H) ‖ LP(C) ‖ LP(A) ‖ LP(clientId) ‖ LP(nonce) ‖ LP(channelBinding)

Security Validations

All implementations enforce:

  • Canonical Ristretto255 point encoding
  • Canonical scalar encoding (reduced mod group order)
  • Identity element rejection (commitment, announcement)
  • Zero challenge rejection
  • Zero response scalar rejection
  • Constant-time equation comparison (subtle::ct_eq)

PostgreSQL Extension Details

Available Functions

Function Description
twofapi.enroll(client_id, commitment, proof) Register with proof of possession
twofapi.issue_challenge(client_id) Get a fresh challenge nonce
twofapi.get_challenge_nonce(client_id, challenge_id) Retrieve nonce for proof
twofapi.authenticate(client_id, challenge_id, proof) Verify proof + establish session
twofapi.verify(client_id, challenge_id, proof) Verify proof only
twofapi.current_client() Get authenticated client (for RLS)
twofapi.suspend_client(client_id) Suspend a client (admin)
twofapi.revoke_client(client_id) Revoke permanently (admin)
twofapi.cleanup(retention_days) Purge expired challenges + old audit logs
twofapi.version() Extension version

Security Model

  • All functions use SECURITY DEFINER with SET search_path = twofapi, pg_catalog
  • Session state stored in Rust process memory (immune to GUC spoofing)
  • Admin functions require twofapi_admin role
  • Audit log captures all security events
  • Challenge consumed atomically (DELETE ... RETURNING)
  • SAVEPOINT replay detection via challenge existence check

Redis Module Details

Available Commands

Command Description
2FAPI.ENROLL <client_id> <commitment_hex> Register a client
2FAPI.CHALLENGE <client_id> Issue a challenge
2FAPI.VERIFY <client_id> <ch_id> <proof_hex> Verify and authenticate
2FAPI.STATUS <client_id> Check enrollment status
2FAPI.SUSPEND <client_id> Suspend a client
2FAPI.REVOKE <client_id> Revoke permanently
2FAPI.WHOAMI Get authenticated client
2FAPI.INFO Module statistics

Security Model

  • Session state in Rust thread_local memory (not Redis keys)
  • Hardened config disables 35+ dangerous Redis commands
  • Client ID validation prevents namespace injection
  • Atomic challenge consumption (single-threaded guarantee)
  • Connection-ID-bound sessions

Security

This project has undergone 6 adversarial red team passes analyzing 720+ attack vectors across the crypto core, PostgreSQL extension, and Redis module.

Pass Vectors Critical Found Status
1st 16 3 All fixed
2nd 224 3 All fixed
3rd 224 0 Infra implemented
4th 217 4 All fixed
5th ~20 0 2 HIGH fixed
6th ~20 0 Formal proofs confirmed

Current status: 0 open findings. The Sigma protocol correctness has been formally proven (soundness, zero-knowledge, transcript injectivity).


Building from Source

Crypto Core (Rust)

cd crypto-core
cargo test          # Run 52 tests
cargo build --release

PostgreSQL Extension

# Requires: cargo-pgrx, PostgreSQL dev headers
cd extensions/pg-extension
cargo test --no-default-features   # 29 domain tests (no PG needed)

# Docker build (recommended)
docker build -f Dockerfile.pg16 -t pg-2fapi:16 ../..

Redis Module

cd extensions/redis-module
cargo test --no-default-features   # 40 domain tests (no Redis needed)

# Docker build (recommended)
docker build -f Dockerfile -t redis-2fapi ../..

Node.js Bindings

cd crypto-core/napi
npm install
npm run build
npm test

License

Component License
crypto-core Apache-2.0
PostgreSQL extension Apache-2.0
Redis module Apache-2.0
Client SDK Apache-2.0
Protocol spec Apache-2.0

Copyright 2024-2026 Continuum Identity SAS.

The 2FApi verification server is available under Business Source License 1.1 (converts to Apache-2.0 after 4 years).

Releases

No releases published

Packages

 
 
 

Contributors