True Zero-Knowledge Authentication with Hardware-Bound Device Ring Signatures
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.
- β 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
| 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 |
- Docker (includes Docker Compose)
# 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.batThat's it! Open http://localhost in your browser.
- β Redis (session storage)
- β Legion Server (ZK proof verifier)
- β Frontend (WASM client)
- β Nginx (reverse proxy)
Performance: Registration ~5s, Authentication ~2min (k=14 proof generation)
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:8000For production deployment, see DEPLOYMENT.md
| Security Level | k | Proof Time | Proof Size | Use Case |
|---|---|---|---|---|
| Development | 12 | ~30s | 3.2 KB | Testing |
| Production | 14 | ~2min | 3.4 KB | Recommended |
Test Hardware: Lenovo IdeaPad 3 - Intel Core i3 11th Gen
Note: Performance may vary based on hardware specifications.
| 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
| 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).
π For detailed step-by-step authentication flow with cryptographic details, see ARCHITECTURE_FLOW.md
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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 β
ββββββββββββββββββββββββββ βββββββββββββββββββββββββββββ
βββββββββββββββ ββββββββββββββββ
β 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 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 β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- β 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)
- β 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
See DEPLOYMENT.md for detailed production deployment guide.
# 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/healthRUST_LOG=info
LEGION_DATA_PATH=/var/lib/legion/data
REDIS_URL=redis://127.0.0.1:6379# Run all tests
cargo test --workspace
# Run with Redis features
cargo test --workspace --features redis
# Benchmark
cargo bench- Architecture Flow - Detailed authentication flow
- Security Fixes - v1.1.0 security improvements
- Deployment Guide - Production deployment
- Security Policy - Security guarantees and reporting
- Contributing - Contribution guidelines
- 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
Contributions welcome! Please read CONTRIBUTING.md first.
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing) - Open Pull Request
Found a security issue? See SECURITY.md for responsible disclosure.
DO NOT open public issues for vulnerabilities.
MIT License - see LICENSE file for details.
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)
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
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
- Halo2 - ZK proof system
- WebAuthn - Hardware authentication
- Blake3 - Fast hashing
- BIP-39 - Mnemonic recovery phrases
- GitHub: @deadends
- Email: nantha.ponmudi@gmail.com
- Website: https://nantha.dev
Built with β€οΈ for privacy and security