Skip to content

Deadends/legion

Repository files navigation

Legion ZK Auth πŸ›‘οΈ

True Zero-Knowledge Authentication with Hardware-Bound Device Ring Signatures

License: MIT Rust Security Version

🎯 What is Legion?

Legion is a passwordless zero-knowledge authentication system that proves you're authorized without revealing who you are.

Authenticate using only your fingerprint + 24-word recovery phrase (like MetaMask). No usernames, no passwords, no server-side secrets.

Key Features

  • βœ… Passwordless Authentication: BIP-39 recovery phrase + fingerprint (no username/password)
  • βœ… True Zero-Knowledge: Server never learns your identity (1 of 1M users)
  • βœ… Device Anonymity: Hardware-bound with ring signatures (1 of 1K devices)
  • βœ… No Trusted Setup: Halo2 PLONK (transparent setup)
  • βœ… Hardware Security: WebAuthn TPM/Secure Enclave binding
  • βœ… Replay Protection: Nullifiers + timestamps
  • βœ… Session Security: Linkability tags prevent theft
  • βœ… Multi-Device Support: Use same account on 2 devices (laptop + phone)
  • βœ… Rate Limiting: 5 attempts/hour (generic errors prevent enumeration)
  • βœ… Device Revocation: Block stolen devices instantly

πŸ”’ Security Guarantees (v1.3.0)

Property Guarantee
Authentication Passwordless (BIP-39 + Fingerprint)
User Anonymity 1 of 2^20 (1,048,576)
Device Anonymity 1 of 2^10 (1,024) per user
Soundness Error 2^-128
Proof System Halo2 PLONK (transparent setup)
Credential Derivation Blake3 (BIP-39 seed)
Hardware Binding WebAuthn Level 2 (TPM/Secure Enclave)
Multi-Device Max 2 devices per account
Rate Limiting 5 attempts/hour
Device Revocation Instant blacklist

πŸš€ Quick Start (One Command!)

Prerequisites

  • Docker (includes Docker Compose)

Install & Run

# Clone and run
git clone https://github.com/deadends/legion.git
cd legion

# Linux/macOS
chmod +x scripts/install.sh && ./scripts/install.sh

# Windows
scripts\install.bat

That's it! Open http://localhost in your browser.

What Gets Installed

  • βœ… Redis (session storage)
  • βœ… Legion Server (ZK proof verifier)
  • βœ… Frontend (WASM client)
  • βœ… Nginx (reverse proxy)

Performance: Registration ~5s, Authentication ~2min (k=14 proof generation)


Manual Setup (Without Docker)

Click to expand manual installation
# 1. Install Redis
# macOS: brew install redis && redis-server
# Ubuntu: sudo apt install redis && redis-server
# Windows: https://redis.io/docs/install/install-redis/install-redis-on-windows/

# 2. Run server (terminal 1)
cd legion-server
cargo run --release --features redis

# 3. Build frontend (terminal 2)
cd wasm-client
wasm-pack build --target web --release
python3 -m http.server 8000

# 4. Open http://localhost:8000

For production deployment, see DEPLOYMENT.md

πŸ“Š Performance

Security Level k Proof Time Proof Size Use Case
Development 12 ~30s 3.2 KB Testing
Production 14 ~2min 3.4 KB Recommended

Verification Benchmarks

Test Hardware: Lenovo IdeaPad 3 - Intel Core i3 11th Gen
Note: Performance may vary based on hardware specifications.

k=12 (Development/Testing)

Metric Value Notes
Proof Size 3,264 bytes 3.19 KB compressed
Public Inputs 10 User tree root, device tree root, nullifier, etc.
Params Generation 7.03s One-time setup per k value
Circuit Creation 2.3Β΅s Negligible overhead
Verifying Key Gen 1.29s One-time keygen
Proof Verification 107.7ms Actual ZK proof check
Total Verification 8.43s End-to-end (without caching)

Breakdown:

  • πŸ”₯ Cold start (first verification): ~8.4s (includes params + keygen + verification)
  • ⚑ Subsequent verifications (with caching): ~108ms (params/keygen cached)
  • πŸ’Ύ Proof overhead: ~326 bytes per public input
  • πŸ” Circuit complexity: User tree (20 levels) + Device tree (10 levels) + bindings

k=14 (Production - Recommended)

Metric Value Notes
Proof Size 3,392 bytes 3.31 KB compressed
Public Inputs 10 User tree root, device tree root, nullifier, etc.
Params Generation 100.78s One-time setup per k value
Circuit Creation 5Β΅s Negligible overhead
Verifying Key Gen 12.89s One-time keygen
Proof Verification 967.2ms Actual ZK proof check
Total Verification 114.65s End-to-end (without caching)

Breakdown:

  • πŸ”₯ Cold start (first verification): ~115s (includes params + keygen + verification)
  • ⚑ Subsequent verifications (with caching): ~967ms (params/keygen cached)
  • πŸ’Ύ Proof overhead: ~339 bytes per public input
  • πŸ” Circuit complexity: User tree (20 levels) + Device tree (10 levels) + bindings

Important: Params generation and keygen are one-time costs that can be cached. Once cached, verification takes only ~108-967ms depending on k value. Current implementation does not cache params yet.

Why slower than old benchmarks? The passwordless circuit now verifies TWO Merkle trees (user + device) instead of one, providing true device-level anonymity (1-of-1024 devices per user).

πŸ—οΈ Architecture

πŸ“– For detailed step-by-step authentication flow with cryptographic details, see ARCHITECTURE_FLOW.md

System Components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       CLIENT (Browser + WASM)                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  UI Layer      β”‚  β”‚  WASM Prover    β”‚  β”‚  Local Storage           β”‚  β”‚
│  │  (Vanilla JS)  │  │  (Rust→WASM)    │  │  (IndexedDB)             │  │
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚
β”‚  β”‚ β€’ Registration β”‚  β”‚ β€’ Blake3 Hash   β”‚  β”‚ β€’ Full Merkle Tree       β”‚  β”‚
β”‚  β”‚ β€’ Login Form   β”‚  β”‚ β€’ BIP-39 Derive β”‚  β”‚ β€’ Device Trees           β”‚  β”‚
β”‚  β”‚ β€’ Session UI   β”‚  β”‚ β€’ Halo2 Prover  β”‚  β”‚ β€’ WebAuthn Credentials   β”‚  β”‚
β”‚  β”‚ β€’ Tree Sync    β”‚  β”‚ β€’ Merkle Proof  β”‚  β”‚ β€’ Tree Version Cache     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β€’ Ring Sigs     β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚              Hardware Security (WebAuthn Level 2)                β”‚   β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€   β”‚
β”‚  β”‚ β€’ TPM 2.0 / Secure Enclave    β€’ FIDO2 Authenticator              β”‚   β”‚
β”‚  β”‚ β€’ Device Private Key (ECDSA)  β€’ Biometric/Touch Required         β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚ HTTPS/TLS 1.3
                                 β”‚ (Encrypted Channel)
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    LEGION SERVER (Rust/Axum)                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚                      API Layer (Axum)                           β”‚     β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€     β”‚
β”‚  β”‚ POST /api/register-blind          β”‚ Blind registration          β”‚     β”‚
β”‚  β”‚ GET  /api/download-tree           β”‚ Download full Merkle tree   β”‚     β”‚
β”‚  β”‚ POST /api/verify-anonymous-proof  β”‚ Verify ZK proof             β”‚     β”‚
β”‚  β”‚ POST /api/verify-session          β”‚ Session validation          β”‚     β”‚
β”‚  β”‚ POST /api/webauthn/*              β”‚ WebAuthn endpoints          β”‚     β”‚
β”‚  β”‚ GET  /health                      β”‚ Health check                β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                   β”‚                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚                   Business Logic Layer                          β”‚     β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€     β”‚
β”‚  β”‚ β€’ Blind Registration    β”‚ β€’ Tree Synchronization                β”‚     β”‚
β”‚  β”‚ β€’ ZK Proof Verifier     β”‚ β€’ Nullifier Tracker (replay)          β”‚     β”‚
β”‚  β”‚ β€’ Session Manager       β”‚ β€’ Linkability Tag Validator           β”‚     β”‚
β”‚  β”‚ β€’ WebAuthn Service      β”‚ β€’ Timestamp Validator (Β±10min)        β”‚     β”‚
β”‚  β”‚ β€’ Device Revocation     β”‚ β€’ Rate Limiter (5/hour)               β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                   β”‚                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚                   Cryptographic Layer                           β”‚     β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€     β”‚
β”‚  β”‚ β€’ Halo2 Verifier (PLONK) β”‚ β€’ Poseidon Hash (ZK-friendly)        β”‚     β”‚
β”‚  β”‚ β€’ Blake3 (credential)    β”‚ β€’ BIP-39 (recovery phrase)           β”‚     β”‚
β”‚  β”‚ β€’ Merkle Tree (2^20)     β”‚ β€’ Device Trees (2^10 per user)       β”‚     β”‚
β”‚  β”‚ β€’ Ring Signature Verify  β”‚ β€’ WebAuthn Signature Verify          β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                         β”‚                      β”‚
                         β–Ό                      β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚   Redis (In-Memory)    β”‚  β”‚  RocksDB (Persistent)     β”‚
        β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
        β”‚ β€’ Session Tokens       β”‚  β”‚ β€’ Merkle Tree Leaves      β”‚
        β”‚ β€’ Linkability Tags     β”‚  β”‚ β€’ Device Trees            β”‚
        β”‚ β€’ Spent Nullifiers     β”‚  β”‚ β€’ Nullifier History       β”‚
        β”‚ β€’ Rate Limit Counters  β”‚  β”‚ β€’ WebAuthn Credentials    β”‚
        β”‚ TTL: 1 hour            β”‚  β”‚ β€’ Revoked Devices         β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Authentication Flow (Simplified)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Client    β”‚                                                β”‚    Server    β”‚
β”‚  (Browser)  β”‚                                                β”‚  (Verifier)  β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                                                β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                                                              β”‚
       β”‚ 1. Generate 24-word recovery phrase (BIP-39)                 β”‚
       β”‚    β†’ 256-bit entropy (like MetaMask)                         β”‚
       β”‚    β†’ User writes down on paper                               β”‚
       β”‚                                                              β”‚
       β”‚ 2. Derive account_id from phrase (Blake3)                    β”‚
       β”‚    account_id = Blake3("LEGION_ACCOUNT_V2" || bip39_seed)    β”‚
       β”‚    β†’ Deterministic, no server interaction                    β”‚
       β”‚                                                              β”‚
       β”‚ 3. Hash account_id for tree leaf (Poseidon)                  β”‚
       β”‚    credential_hash = Poseidon(account_id)                    β”‚
       β”‚                                                              β”‚
       β”‚ 4. Blind registration (TRUE zero-knowledge)             ────►│
       β”‚    β†’ Sends ONLY credential_hash (no phrase/identity)         β”‚
       β”‚    β†’ Server adds to tree, returns tree_index                 β”‚
       β”‚                                                         ◄────│ {tree_index: 114}
       β”‚                                                              β”‚
       β”‚ 5. Download full Merkle tree (one-time sync)            ────►│
       β”‚    β†’ Client stores entire tree in IndexedDB                  β”‚
       β”‚    β†’ Enables TRUE zero-knowledge (no server queries)         β”‚
       β”‚                                                         ◄────│ {tree_data: [all leaves],
       β”‚                                                              β”‚  merkle_root, version}
       β”‚                                                              β”‚
       β”‚ 6. Generate WebAuthn key (TPM/Secure Enclave)                β”‚
       β”‚    β†’ Fingerprint prompt creates hardware-bound key           β”‚
       β”‚    β†’ device_pubkey (ECDSA P-256, non-exportable)             β”‚
       β”‚    β†’ Stored in TPM 2.0 / Secure Enclave                      β”‚
       β”‚                                                              β”‚
       β”‚ 7. Register device in device tree                       ────►│
       β”‚    β†’ device_commitment = Blake3(credential_id)               β”‚
       β”‚    β†’ Server converts to valid field element if needed        β”‚
       β”‚    β†’ Server adds to user's device tree (1 of 1024 slots)     β”‚
       β”‚                                                         ◄────│ {device_position: 0,
       β”‚                                                              β”‚  device_tree_root}
       β”‚                                                              β”‚
       β”‚ 8. LOGIN: Touch fingerprint to authenticate                  β”‚
       β”‚    β†’ WebAuthn verifies hardware-bound key                    β”‚
       β”‚    β†’ Decrypts recovery phrase from local storage             β”‚
       β”‚    β†’ Re-derives account_id from phrase                       β”‚
       β”‚                                                              β”‚
       β”‚ 9. Fetch device Merkle proof                            ────►│
       β”‚    β†’ Sends account_id (derived from phrase)                  β”‚
       β”‚    β†’ Server returns device tree path                         β”‚
       β”‚                                                         ◄────│ {device_path: [siblings],
       β”‚                                                              β”‚  device_root}
       β”‚                                                              β”‚
       β”‚ 10. Compute user Merkle proof CLIENT-SIDE                    β”‚
       β”‚    β†’ Uses local tree from IndexedDB                          β”‚
       β”‚    β†’ Computes path for tree_index                            β”‚
       β”‚    β†’ Server NEVER learns which user!                         β”‚
       β”‚                                                              β”‚
       β”‚ 11. Compute nullifier (replay protection)                    β”‚
       β”‚    nullifier = Poseidon(account_id || challenge)             β”‚
       β”‚    β†’ ONE-TIME USE: Different every login                     β”‚
       β”‚    β†’ Prevents proof replay attacks                           β”‚
       β”‚                                                              β”‚
       β”‚ 12. Compute linkability tag (session binding)                β”‚
       β”‚    linkability_tag = Blake3(device_pubkey || nullifier)      β”‚
       β”‚    ⚠️  Binds session to specific device+user                 β”‚        
       β”‚                                                              β”‚
       β”‚ 13. Generate ZK proof (Halo2 PLONK, ~2min for k=14)          β”‚
       β”‚    Proves in zero-knowledge:                                 β”‚
       β”‚    βœ“ User exists in Merkle tree (1 of 2^20)                  β”‚
       β”‚    βœ“ Device exists in device tree (1 of 2^10)                β”‚
       β”‚    βœ“ account_id hashes to credential_hash                    β”‚
       β”‚    βœ“ Nullifier computed correctly                            β”‚
       β”‚    βœ“ Timestamp is fresh                                      β”‚
       β”‚    WITHOUT revealing which user or device                    β”‚
       β”‚                                                              β”‚
       β”‚ 14. Submit proof                                        ────►│
       β”‚    {proof, public_inputs, linkability_tag, k=14}             β”‚
       β”‚                                                              β”‚ β€’ Check device not revoked
       β”‚                                                              β”‚ β€’ Verify timestamp (Β±10min)
       β”‚                                                              β”‚ β€’ Rate limit check (5/hour)
       β”‚                                                              β”‚ β€’ Check nullifier (replay?)
       β”‚                                                              β”‚ β€’ Verify ZK proof (~115s)
       β”‚                                                              β”‚ β€’ Mark nullifier as used
       β”‚                                                              β”‚
       β”‚                                                         ◄────│ {session_token, expires_at}
       β”‚                                                              β”‚
       β”‚ 15. Verify session (every request)                      ────►│
       β”‚    {session_token, linkability_tag}                          β”‚
       β”‚                                                              β”‚ β€’ Lookup in Redis
       β”‚                                                              β”‚ β€’ Verify linkability_tag
       β”‚                                                              β”‚   (prevents session theft)
       β”‚                                                              β”‚ β€’ Check not spent
       β”‚                                                         ◄────│ {valid: true}
       β”‚                                                              β”‚

πŸ” Want more details? See ARCHITECTURE_FLOW.md for:

  • Step-by-step cryptographic operations
  • Circuit constraint details
  • Security property explanations
  • Attack resistance mechanisms

Session Security Deep Dive

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    SESSION SECURITY MECHANISMS                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                         β”‚
β”‚  1. LINKABILITY TAG (Zero-Knowledge Device Binding)                     β”‚
β”‚  ═══════════════════════════════════════════════════════                β”‚
β”‚                                                                         β”‚
β”‚     linkability_tag = Blake3(device_pubkey || nullifier)                β”‚
β”‚                                                                         β”‚
β”‚     β€’ Computed client-side using hardware-bound device key              β”‚
β”‚     β€’ Sent with every session validation request                        β”‚
β”‚     β€’ Server verifies: stored_tag == provided_tag                       β”‚
β”‚                                                                         β”‚
β”‚     βœ… PREVENTS: Session token theft/replay on different device        β”‚
β”‚     βœ… ENSURES: Same user + same device for entire session             β”‚
β”‚     βœ… MAINTAINS: Zero-knowledge (server doesn't learn identity)       β”‚
β”‚                                                                         β”‚
β”‚  ─────────────────────────────────────────────────────────────────      β”‚
β”‚                                                                         β”‚
β”‚  2. SESSION TOKEN (Cryptographic Binding)                               β”‚
β”‚  ═══════════════════════════════════════════════════════                β”‚
β”‚                                                                         β”‚
β”‚     session_token = Poseidon(nullifier || timestamp || linkability_tag) β”‚
β”‚                                                                         β”‚
β”‚     β€’ Generated server-side after proof verification                    β”‚
β”‚     β€’ Stored in Redis with linkability_tag as value                     β”‚
β”‚     β€’ Cannot be forged without knowing nullifier                        β”‚
β”‚                                                                         β”‚
β”‚     βœ… PREVENTS: Token forgery                                         β”‚
β”‚     βœ… ENSURES: Cryptographic binding to proof                         β”‚
β”‚                                                                         β”‚
β”‚  ─────────────────────────────────────────────────────────────────      β”‚
β”‚                                                                         β”‚
β”‚  3. NULLIFIER (Replay Protection)                                       β”‚
β”‚  ═══════════════════════════════════════════════════════                β”‚
β”‚                                                                         β”‚
β”‚     nullifier = Poseidon(credential_hash || challenge)                  β”‚
β”‚                                                                         β”‚
β”‚     β€’ Unique per authentication attempt                                 β”‚
β”‚     β€’ Tracked in RocksDB (permanent) and Redis (cache)                  β”‚
β”‚     β€’ Server rejects if nullifier seen before                           β”‚
β”‚                                                                         β”‚
β”‚     βœ… PREVENTS: Proof replay attacks                                  β”‚
β”‚     βœ… ENSURES: One-time use per challenge                             β”‚
β”‚                                                                         β”‚
β”‚  ─────────────────────────────────────────────────────────────────      β”‚
β”‚                                                                         β”‚
β”‚  4. TIMESTAMP VALIDATION (Time-Bound Security)                          β”‚
β”‚  ═══════════════════════════════════════════════════════                β”‚
β”‚                                                                         β”‚
β”‚     β€’ Proof includes timestamp (Unix epoch)                             β”‚
β”‚     β€’ Server validates: |proof_time - server_time| < 5 minutes          β”‚
β”‚     β€’ Session TTL: 1 hour (sliding window)                              β”‚
β”‚                                                                         β”‚
β”‚     βœ… PREVENTS: Old proof replay                                      β”‚
β”‚     βœ… ENSURES: Fresh authentication                                   β”‚
β”‚                                                                         β”‚
β”‚  ─────────────────────────────────────────────────────────────────      β”‚
β”‚                                                                         β”‚
β”‚  5. CHALLENGE-RESPONSE (Freshness Guarantee)                            β”‚
β”‚  ═══════════════════════════════════════════════════════                β”‚
β”‚                                                                         β”‚
β”‚     β€’ Server generates random 32-byte challenge                         β”‚
β”‚     β€’ Stored in Redis with 5-minute TTL                                 β”‚
β”‚     β€’ Client must include in proof                                      β”‚
β”‚     β€’ Server verifies challenge matches and deletes                     β”‚
β”‚                                                                         β”‚
β”‚     βœ… PREVENTS: Pre-computed proof attacks                             β”‚
β”‚     βœ… ENSURES: Proof generated for this specific session               β”‚
β”‚                                                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ” Zero-Knowledge Properties

What Server Knows

  • βœ… Someone in anonymity set authenticated
  • βœ… Proof is cryptographically valid
  • βœ… Same user+device via linkability tag
  • βœ… Rate limit status (attempts remaining)
  • βœ… Device revocation status
  • βœ… Total number of registered users
  • βœ… Merkle tree root (public)

What Server CANNOT Know

  • ❌ Which specific user (1 of 1M)
  • ❌ Which specific device (1 of 1K per user)
  • ❌ Recovery phrase (BIP-39 seed)
  • ❌ account_id (derived from phrase)
  • ❌ Device private key (in TPM/Secure Enclave)
  • ❌ Which tree leaf belongs to which user
  • ❌ User Merkle path (computed client-side)
  • ❌ User's tree_index position

πŸ“¦ Deployment

See DEPLOYMENT.md for detailed production deployment guide.

Quick Deploy with Docker

# Production build
docker-compose -f deployment/docker-compose.yml up -d

# Check logs
docker-compose -f deployment/docker-compose.yml logs -f legion-server

# Check health
curl http://localhost/health

Environment Variables

RUST_LOG=info
LEGION_DATA_PATH=/var/lib/legion/data
REDIS_URL=redis://127.0.0.1:6379

πŸ§ͺ Testing

# Run all tests
cargo test --workspace

# Run with Redis features
cargo test --workspace --features redis

# Benchmark
cargo bench

πŸ“š Documentation

πŸ› οΈ Technology Stack

  • ZK Proofs: Halo2 (PLONK) - Transparent setup, no trusted ceremony
  • Curves: Pasta (Pallas/Vesta) - Cycle of curves for recursion
  • Hash: Blake3 (credential derivation), Poseidon (ZK-friendly)
  • Key Derivation: BIP-39 (24-word mnemonic)
  • Hardware: WebAuthn Level 2 (TPM 2.0, Secure Enclave)
  • Backend: Rust, Axum, Redis (sessions), RocksDB (persistence)
  • Frontend: Rustβ†’WASM (prover), Vanilla JS (UI), IndexedDB (storage)
  • Deployment: Docker, Nginx, systemd

🀝 Contributing

Contributions welcome! Please read CONTRIBUTING.md first.

  1. Fork the repository
  2. Create feature branch (git checkout -b feature/amazing)
  3. Commit changes (git commit -m 'Add amazing feature')
  4. Push to branch (git push origin feature/amazing)
  5. Open Pull Request

πŸ”’ Security

Found a security issue? See SECURITY.md for responsible disclosure.

DO NOT open public issues for vulnerabilities.

πŸ“„ License

MIT License - see LICENSE file for details.

πŸ”„ Changelog

v1.3.0 - Passwordless Authentication (2024)

Major Changes:

  • βœ… Passwordless authentication - BIP-39 recovery phrase + fingerprint
  • βœ… Single-field circuit - account_id derived from BIP-39 seed
  • βœ… Device ring signatures - 1-of-1024 device anonymity per user
  • βœ… Multi-device support - Use same account on 2 devices
  • βœ… Hardware-bound keys - WebAuthn TPM/Secure Enclave integration
  • βœ… Blake3 field element handling - Automatic conversion for device commitments

Architecture:

  • πŸ—οΈ No username/password - just recovery phrase + fingerprint
  • πŸ—οΈ Client-side account_id derivation (Blake3 + BIP-39)
  • πŸ—οΈ Device trees stored server-side (per-user isolation)
  • πŸ—οΈ Dual Merkle tree verification (user tree + device tree)
  • πŸ—οΈ Robust field element conversion (handles Blake3 outputs >= field modulus)

v1.2.0 - Client-Side Proving Architecture (2024)

Major Changes:

  • βœ… Client-side Merkle tree storage (IndexedDB) - TRUE zero-knowledge
  • βœ… Blind registration - Server never sees credentials
  • βœ… Local proof generation - All cryptography in WASM
  • βœ… Tree synchronization - Download full tree once, compute paths locally
  • βœ… Spent nullifiers - Single-use sessions prevent concurrent access

v1.1.0 - Security Hardening (2024)

Added:

  • βœ… Rate limiting (5 attempts/hour per credential)
  • βœ… Device revocation API and enforcement
  • βœ… Linkability tags for session theft prevention

Security Fixes:

  • πŸ”’ Fixed identity leakage in challenge requests
  • πŸ”’ Prevented brute force attacks with rate limiting
  • πŸ”’ Enabled stolen device mitigation via revocation

πŸ™ Acknowledgments

πŸ“ž Contact


Built with ❀️ for privacy and security

About

Legion is a Zero-Knowledge Authentication Fabric built for privacy

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

Packages

No packages published