Post-quantum signed. Audit-of-record by construction. Compulsion-resistant by design.
Fixus inter mutabilia. Fixed amid the mutable.
Project site · post-quantum · zero-knowledge · compulsion-resistant · one double-click to launch
System map · Constitution (MISSION.md) · Backlog (ROADMAP.md) · Audit-of-record (CHANGELOG.md) · Production readiness · Agent runbook (CLAUDE.md)
The system The hard parts · Token model · Architecture · Cryptography · Production posture · How it differs
Proof of life Quickstart · What you get · The trick · Tour · Tests · License
Americans currently carry six to eight credentials that do not talk to each other: driver's license, passport, Social Security card, Real ID, voter registration, health insurance card, and a thickening pile of agency-specific identifiers. Each is a different artifact, signed by a different authority, secured to a different standard, with no shared revocation path and no shared audit trail.
Polaris consolidates them into one physical token per person, signed under post-quantum cryptography, with context-scoped verification (banking versus voting versus healthcare are different events with different disclosure rules) and zero-knowledge defaults (the typical verification stores no token identifier at all).
This repository is a working reference implementation: 28 schema tables, 11 stored procedures, a Flask application with 70 routes that exercises every use case, a Plonky2 ZK-SNARK prover in Rust with an independent second witness, WebAuthn-MFA operator authentication, an operational atlas with a live globe, a production container stack behind a post-quantum TLS edge, and a self-healing macOS launcher that gets all of it running from a single double-click.
It is not a slide deck. It runs; CI boots the full production stack end to end on every push.
The system lives in polaris_sql, polaris_web, polaris_cli, polaris_zk. Its C1-C10 invariants are machine-checked by polaris_checks, a flat layer of plain check functions.
Consolidating the cards is the easy half. The interesting half is what happens when an adversary shows up. Polaris answers six of them by construction.
| The threat | What it looks like in practice | How Polaris answers it | Where |
|---|---|---|---|
| Cryptographic compulsion | "Sign this transaction or I break your fingers." The holder cannot refuse without injury. | A second secret produces an indistinguishable verification that silently records a DuressEvent. The operator's screen reveals nothing. | duress-codes UC-12 |
| Catastrophic loss | Token lost, holder unidentified, no way to prove who they are without the artifact. | Two-phase recovery ceremony (initiate then complete) gated by four CHECK constraints and an admin-only second key. | recovery-ceremony UC-9 |
| Quantum migration | Today's signing algorithms become broken overnight when a quantum computer arrives. | Multi-signature transitional state: a token can be signed under classical AND post-quantum algorithms simultaneously, with a hard rule that exactly one is active. | multi-sig-migration UC-6 |
| Issuer concentration | One agency can issue tokens that masquerade as any other agency's. | Explicit-only federation: no transitive trust. Every cross-agency verification gates on an active AgencyTrustAttestation row. | federation UC-10 |
| Public auditability without privacy loss | "Prove this token was in the ledger" without revealing which one. | Plonky2 ZK-SNARK over a Merkle commitment. The proof reveals nothing about the leaf. | zk-snark UC-11 |
| Issuer overreach | An agency revokes tokens at industrial scale outside policy. | Per-agency revocation-rate ceiling enforced by trigger. Sanctioned by the IssuerDiscretionPolicy row, audited by pg_advisory_xact_lock. |
issuer-discretion UC-8 |
Every row has a defender's claim, an attacker's optimal play, an equilibrium analysis, a documented second-best attack, and an enforcement trace at the schema level.
An IdentityToken is a row in Postgres. The physical card carries the cryptographic serial; the row carries everything else.
IdentityToken
├── token_value VARCHAR(128) UNIQUE canonical cryptographic serial
├── physical_serial VARCHAR(64) UNIQUE hardware serial of the card
├── hardware_model VARCHAR(50) manufacturer / model
├── biometric_binding_type enum NONE · FINGERPRINT · FACE · IRIS
├── liveness_check_type enum PASSIVE · ACTIVE_CHALLENGE · MULTI_MODAL
├── individual_id → Individual the person
├── issuing_agency_id → Agency who issued it
├── algorithm_id → CryptographicAlgorithm ML-DSA-65 by default
├── predecessor_token_id → IdentityToken (self) the succession chain
├── activation_sequence INTEGER ≥ 1 which token in the lineage
├── status enum ACTIVE · RESERVE · DORMANT · REVOKED · LOST · EXPIRED
├── issued_date · activated_date · expiration_date
└── duress_code_hash VARCHAR(255) NULL the second secret (optional)
The shape carries the policy. Four invariants worth naming:
- One ACTIVE row per individual. Enforced by a partial unique index on
(individual_id) WHERE status = 'ACTIVE', not by application logic. Replacing a token means walking the lineage forward, not opening a second row. That is constraint C3 in the constitution, and it survives every restore from backup because it lives in the index, not in code. - Algorithm by reference, not literal. The signing algorithm is a foreign key to
CryptographicAlgorithm, a first-class entity carryingquantum_resistant,nist_standard, anddeprecation_date. There is no hardcoded crypto anywhere in the codebase. Adding ML-DSA-87 tomorrow isINSERT INTO, notgit push. That is constraint C7. - Succession is a chain, not an event.
predecessor_token_idis self-referential; the full lineage from a person's first issuance to their current token is one recursive CTE. Recovery, replacement, and post-quantum migration all add a new row pointing at the predecessor; nothing is overwritten. - Duress is observable only to the audit trail. If a holder types the duress code under coercion, the verification looks identical to a normal one on every operator-visible surface. A
DuressEventrow appears in the audit-of-record; the operator's screen reveals nothing. That is constraint C6 plus the anti-coercion vocation working together.
The CREATE TABLE is in polaris_sql/01_schema.sql. Every column is documented; every CHECK constraint has a paired test.
Four layers. The check layer reads but never writes the operational layer. The ZK prover is a subprocess, not a service. WebAuthn FIDO2 is the only authentication path for human operators; passwords alone cannot reach the admin or auditor roles.
┌─────────────────────────────────────────────────────────────┐
│ CHECK LAYER │
│ polaris_checks — 67 flat invariant checks │
│ (C1-C10 · CSP · secrets posture · PQC wiring · CI proof │
│ pins · doc/schema drift · ZK two-witness · …) │
│ plain check_*(repo_root) functions; `run` gates CI │
└─────────────────────────┬───────────────────────────────────┘
│ reads (no writes)
┌─────────────────────────▼───────────────────────────────────┐
│ APPLICATION │
│ Flask (70 routes) · Atlas globe · WebAuthn MFA │
│ Dashboard · /sql console · structured /api/health │
└──────────┬──────────────────────────┬───────────────────────┘
│ │
┌──────────▼─────────┐ ┌───────────▼──────────────────────┐
│ SCHEMA (Pg 16) │ │ ZK PROVER (Rust nightly) │
│ 28 tables │ │ Plonky2 SNARK · Merkle-incl. │
│ 11 stored procs │ │ + independent second witness │
│ 9 AoR by trigger │ │ /api/zk/epoch/close · /verify │
└──────────┬─────────┘ └──────────────────────────────────┘
│ signs with
┌──────────▼──────────────────────────────────────────────────┐
│ POST-QUANTUM SIGNATURES │
│ ML-DSA-65 (FIPS 204, default) · SLH-DSA (FIPS 205) │
│ ML-DSA-87 (high-assurance) · ECDSA-P256 (legacy/audit) │
└─────────────────────────────────────────────────────────────┘
Each layer is independently buildable. The schema loads from 00_load_all.sql against an empty Postgres. The application boots from app.py against the loaded schema. The ZK prover compiles under cargo +nightly build --release on the same machine. The check layer is a read-only set of plain functions that gate CI; it survives any operational restart unchanged.
The constraint that holds this together is C1: audit-of-record. Ten instances (nine schema, one filesystem) record every meaningful operation at the moment it happens. Nothing in the system reconstructs history after the fact; if it isn't written when it occurs, it doesn't exist.
The signing-algorithm registry seeds with five rows. The operational default is ML-DSA-65: the NIST-standardized Module-Lattice Digital Signature Algorithm, FIPS 204, Level 3 security, finalized in 2024.
algorithm family PQ NIST sec public-key signature
─────────────────────────────────────────────────────────────────────────────
ML-DSA-65 ML-DSA ✓ FIPS 204 192 1,952 B 3,309 B ◀ default
ML-DSA-87 ML-DSA ✓ FIPS 204 256 2,592 B 4,627 B high-assurance
SLH-DSA-128s SLH-DSA ✓ FIPS 205 128 32 B 7,856 B hash-based hedge
SLH-DSA-256s SLH-DSA ✓ FIPS 205 256 64 B 29,792 B hash-based, max
ECDSA-P256 ECDSA FIPS 186-4 128 64 B 72 B LEGACY · sunsets 2027-12-31
Four things are worth noting:
- The default algorithm is already post-quantum. ML-DSA-65 is the algorithm new tokens are issued under on day one. There is no "we will migrate when quantum arrives" deferral; the migration target is the current default. Real ML-DSA-65 signature bytes are produced with
POLARIS_USE_REAL_PQC=1via liboqs; with the flag off the default build records a deterministic placeholder so property tests stay reproducible without liboqs installed. ECDSA-P256 is retained only because pre-PQ audit queries need to resolve the algorithm by foreign key. - Two independent witnesses verify every real signature. Since v9.133, ML-DSA-65 signatures verified through liboqs are cross-checked by a second, independent implementation (OpenSSL 3.5 via
cryptography). A signature is accepted when both witnesses agree; no single crypto library is trusted alone. The same discipline covers the ZK epoch root, which a separate Python implementation recomputes bit-for-bit against the Rust prover. - The TLS edge negotiates a post-quantum key exchange. The public edge (Caddy, self-built with the rate-limit plugin) negotiates X25519MLKEM768 hybrid KEX with capable clients; the
caddy-edgeCI job proves the handshake on every push against a real certificate. The full honest map of what is and is not post-quantum (token signatures and hashing are; certificates and WebAuthn remain classical pending NIST timelines) lives in PQC-POSTURE.md. - SLH-DSA is a diversity hedge. ML-DSA rests on lattice problems, SLH-DSA on hash-function security alone. If one family is broken the other is independent. The cost of the hedge is signature size: 29.8 KB for SLH-DSA-256s versus 3.3 KB for ML-DSA-65. Polaris treats post-quantum signature size as a property of the artifact, not a problem to optimize away.
Migration (UC-6 · /uc6/migrate-algorithm) is a multi-signature transitional state. A token can carry both a classical and a post-quantum signature simultaneously during cutover, with a database-enforced rule that exactly one is operationally active. The cutover writes a KeyMigration row the audit trail can replay.
The zero-knowledge surface is independent of the signing algorithm. Plonky2 in polaris_zk/src/lib.rs proves Merkle-tree inclusion against epoch commitments published at /epochs. The proof reveals nothing about the leaf; it answers only "was this token in the ledger at epoch N". The Rust binary is a subprocess called by polaris_web/zk.py; the Flask app degrades gracefully without it (every page serves, every UC-1..UC-12 flow works; only /api/zk/epoch/close and /api/zk/verify go quiet).
Arc B (May-June 2026) closed the gap between architectural sophistication and operational reality. The complete production stack ships in the repo and CI boots it end to end on every push; that job alone surfaced four prod-down bugs the day it landed, which is the point.
client ── TLS 1.3 (X25519MLKEM768 hybrid KEX) ──▶ caddy (self-built edge)
│ rate limiting · HSTS
▼
gunicorn/Flask (non-root, all capabilities dropped) ─▶ pgbouncer ── verify-ca TLS ──▶ postgres 16
│ streaming replication ─▶ standby
└ pgBackRest WAL archiving ─▶ DR restore
What that means concretely:
- TLS everywhere it can be. Let's Encrypt at the edge with automatic provisioning; certificate-pinned (
verify-ca) TLS on the pgbouncer-to-postgres hop. Internal hops stay classical until OpenSSL 3.5 reaches those images; PQC-POSTURE.md tracks the gap honestly. - Hardened containers. Every production service runs as a non-root user with all Linux capabilities dropped; images apt/apk-upgrade their bases and Trivy gates CI on fixable CRITICAL CVEs in both dependency and image scans.
- Backups and DR that are exercised, not asserted. pgBackRest WAL archiving, a scripted restore, and a CI job that runs a full backup/restore round-trip. Runbooks: DR.md, FAILOVER.md, RUNBOOKS.md, SLOS.md.
- Observability. Structured JSON logs with correlation IDs, Prometheus metrics, alert rules in
deploy/observability/, and a structured/api/healththat reports per-component status. - Privacy machinery. Right-to-erasure via audited pseudonymization (the audit-of-record stays intact; the person disappears from it), retention/archive tooling, and ZK verification that never stored the link in the first place.
The honest gap ledger lives in docs/PRODUCTION-READINESS.md: what is done, what is operator-gated (S3 offsite repo, standby host, HSM custody, pager backend, legal review), and what is third-party-gated.
There is no shortage of identity infrastructure in the world. The question is what Polaris does that the existing deployed systems do not.
| System | National-scope issuance | Post-quantum operational default | Zero-knowledge default | Compulsion-resistant primitive | Append-only AoR at schema |
|---|---|---|---|---|---|
| Real ID (US, 2005 act, fully enforced 2025) | ✓ | ✗ | ✗ | ✗ | ✗ |
| mDL / ISO 18013-5 (mobile driver's license) | ✓ | ✗ | partial | ✗ | ✗ |
| Aadhaar (India, 1.3B+ enrolled) | ✓ | ✗ | ✗ | ✗ | partial |
| e-Estonia (e-ID + e-Residency) | ✓ | ✗ | ✗ | ✗ | partial |
| W3C DIDs / VCs (spec, decentralized) | ✗ | method-dependent | ✓ | ✗ | n/a |
| Polaris (this repo) | ✓ | ✓ ML-DSA-65 | ✓ Plonky2 + R6 redaction | ✓ DuressEvent | ✓ 9 schema instances |
A few of the contrasts are worth narrating instead of tabling.
Real ID standardizes the document and the source-of-truth check. It does not standardize a verification protocol; the card is signed by the printer, not by a key. There is no cryptographic identity at all, no shared revocation path, and no audit trail beyond what each individual DMV chooses to retain.
mDL (ISO/IEC 18013-5) is the closest deployed system in spirit. It already does selective disclosure ("prove the holder is over 21 without revealing their address") and is signed cryptographically. What mDL does not do today is post-quantum signatures, a formal compulsion-defense primitive, or a constitutional layer governing how the issuer itself behaves over time.
Aadhaar has biometric binding at national scale; its strength is also its weakness. The biometric templates live in a centralized authority and can be queried by it. Polaris treats biometric binding as a per-token attribute with an explicit enrollment witness agency, not as a population-scale biometric database. The system can answer "is this token bound to a fingerprint" without ever holding the fingerprint outside the holder's possession.
W3C DIDs and Verifiable Credentials are spec, not system. They support the verification model Polaris uses (cryptographic identifiers, selective disclosure, federation-by-attestation), but they do not address national-scope issuance, biometric binding, or the operational substrate an issuing authority would need to actually run one. Polaris fills the substrate; it could in principle emit VCs as a representation format.
e-Estonia is the existing deployed system most similar in ambition. Its cryptography is classical (ECDSA on the e-ID card, RSA in older infrastructure); a platform-level migration path to post-quantum is not yet specified. The system has no compulsion-defense primitive at the protocol level.
Polaris's contribution is not novelty in any single primitive. It is the assembly: a national-scope issuance model, a post-quantum operational default, zero-knowledge defaults on verification, a duress-code primitive built into the verification flow, and an append-only audit-of-record enforced at the database trigger level; every one of which is machine-checked at the schema level rather than asserted in prose.
Local (macOS). You need Docker Desktop. That is the only prerequisite.
git clone https://github.com/EgorKhaklin/polaris-id.git polaris
cd polaris
./Polaris.command # or: ./polaris_mac_launch.sh upThe first run pulls Postgres 16, builds the Flask image, loads the schema, runs the SQL self-tests, and opens your browser at http://localhost:2222. Subsequent launches take roughly ten seconds. Close the browser tab to stop; the launcher watches the page and tears the stack down automatically.
Sign in with one of three seeded roles (notional data only):
admin · Admin@123! full access + SQL console
operator · Operator@123! issue / activate / bind tokens
auditor · Auditor@123! read-only + warrant audits + duress dashboard
Production (Linux, any Docker host).
./scripts/polaris-generate-secrets.sh
export POLARIS_DOMAIN=polaris.example.com
./scripts/polaris-deploy.sh prod
curl -fsS https://$POLARIS_DOMAIN/api/healthWithin about ninety seconds Caddy provisions Let's Encrypt TLS, Postgres + pgbouncer + gunicorn come up non-root with capabilities dropped, and /api/health returns structured per-component status. The operator runbook is OPERATIONS.md; the secrets primer is SECRETS.md; install details live in INSTALL.md.
If anything looks wrong, the launcher carries a read-only diagnostic: ./polaris_mac_launch.sh doctor.
┌──────────────────────────────────────────────────┐
│ Polaris in numbers │
│ (current as of v9.142) │
├──────────────────────────────────────────────────┤
│ 28 schema tables · 11 stored procedures │
│ 70 HTTP routes (incl. /auth/webauthn/*) │
│ 67 machine-checked invariants (C1-C10 + pins) │
│ 562 product tests · 10 property suites │
│ Plonky2 ZK + an independent second witness │
│ 7 CI jobs, incl. full prod-stack boot │
│ 1 double-click to launch │
└──────────────────────────────────────────────────┘
After login the app lands on the Dashboard, which fans out into eight analytical panels covering schema statistics, token status, the authorization matrix, post-quantum migration ratio, verification activity by context, disclosure posture, succession lineage, and the audit trail.
The Atlas (/atlas) is the operational investigation surface: a live globe with reticles for every verification and lifecycle event, a four-figure HUD (Active Tokens, Anomalies, Post-Quantum percentage, Zero-Knowledge percentage), a cursor-paginated event feed, and click-through into any token's full record including its predecessor chain.
Routes for each use case: /uc1/issue, /uc4/activate-reserve, /uc5/bind-device, /uc6/migrate-algorithm, /uc7/warrant-audit, /uc8/revoke-token, /uc9/queue, /duress, /anchors, /epochs, /federation, and /sql (admin / auditor only).
Most reference implementations of an identity system put their rules in application code, where the next caller can bypass them. Polaris puts them in the database, where Postgres enforces them regardless of which client connects:
- One ACTIVE token per person is a partial unique index on
(individual_id) WHERE status = 'ACTIVE', not anifstatement. It survives every restore from backup. - The audit-of-record is a trigger that raises
insufficient_privilegeon anyUPDATE/DELETEof a lifecycle-event table, not a logging convention. - Zero-knowledge is a CHECK constraint that refuses to store a token id on a
ZERO_KNOWLEDGEverification, not an application policy.
Those rules are then machine-checked by polaris_checks: 67 plain check_*(repo_root) functions covering the C1-C10 constraints plus the production-posture pins (CSP, secrets permissions, PQC wiring, CI-proof currency, doc/schema drift), each with tested detection correctness. Every check provably fails on a broken fixture; python3 -m polaris_checks.run gates CI directly. A check is a check: no framework, no mythology.
Earlier versions carried an elaborate "cognitive substrate": an introspection swarm, a simulated Roman economy, a self-governance apparatus meant to let an AI agent maintain the system. v9.55 removed it, replacing ~18k LOC of apparatus with the flat check layer above. The development record of that arc is preserved in the CHANGELOG and the git history. The principles it served (the constitution) are unchanged; the implementation is simply honest now.
Start at the file that matches what you came here for.
| The architecture | The system map | The principles |
| The four layers and how they connect: the schema, the application, the check layer, and the ZK prover. Read this to see how the pieces fit. | A single page that names every meaningful artifact in the repository and what it is for. Use this when you do not know where to start. | The principles that hold the system together, distilled. Read this before you change anything load-bearing. |
| The schema | The constitution | The agent runbook |
28 tables. Start with IdentityToken and follow the foreign keys. Append-only invariants enforced at trigger level on nine of them. |
C1 through C10. Ten hard constraints the system must never violate, each enforced at the schema level rather than in application code. | If you are an AI agent priming on this project, this is your entry point. |
| The Atlas | The ZK prover | The CHANGELOG |
| The operational globe: D3 + custom orthographic projection, viewport-aware clustering, cursor-paginated feed. | Plonky2-backed Merkle-inclusion circuit in Rust. Subprocess CLI consumed by polaris_web/zk.py, cross-checked by a second witness. |
The full audit-of-record. The curated last-10-ships index lives at CHANGELOG.md; the pre-v9.24 archive sits beside it. |
For an exhaustive index of operator and architect documentation, see docs/README.md.
Four layers of verification. CI runs all of them on every push, across seven jobs.
┌──────────────────────────────┬─────────┬──────────────────────────────────────────────┐
│ Layer │ Count │ What it covers │
├──────────────────────────────┼─────────┼──────────────────────────────────────────────┤
│ Product tests (DB-backed) │ 562 │ CHECK constraints, every use case, every │
│ test_app · test_cli · │ │ Flask route + form, the rate limiter, the │
│ test_check_constraints │ │ atlas API, R6 anti-revealing posture, ZK. │
│ Property tests (Hypothesis) │ 10 │ Adversarial inputs against C1, C2, C3 and │
│ │ │ the M2-12 redaction proof. │
│ polaris_checks │ 67 │ C1-C10 + production-posture pins; tested │
│ │ │ detection correctness; `run` gates CI. │
│ CI jobs │ 7 │ test · docker-image · caddy-edge (PQ KEX │
│ │ │ proof) · pqc-real · cve-scan · │
│ │ │ image-cve-scan · prod-stack-boot. │
└──────────────────────────────┴─────────┴──────────────────────────────────────────────┘
./polaris_mac_launch.sh test # full suite, ~60 s
./scripts/ai-test.sh quick # skip the slow concurrency + property tests
./scripts/ai-done.sh # pre-ship gate (checks + link integrity)A release is shippable when every layer passes and ai-done reports READY.
./polaris_mac_launch.sh # default; same as 'up'
./polaris_mac_launch.sh up # bring up, watch the browser, open it
./polaris_mac_launch.sh up --detach # bring up in background and return
./polaris_mac_launch.sh rebuild # force clean rebuild (no cache)
./polaris_mac_launch.sh stop # graceful shutdown
./polaris_mac_launch.sh status # what is running, where
./polaris_mac_launch.sh doctor # read-only diagnostic
./polaris_mac_launch.sh logs # tail Flask log (default)
./polaris_mac_launch.sh logs db # tail Postgres log
./polaris_mac_launch.sh test # run the test suite
./polaris_mac_launch.sh reset # drop pgdata, keep image
./polaris_mac_launch.sh nuke # total wipe: containers + image + volume
./polaris_mac_launch.sh --port 5050 # alternate host port
./polaris_mac_launch.sh --native # native path; Homebrew, no Docker
./polaris_mac_launch.sh --help # full helpThe Rust source for the Plonky2 prover ships in polaris_zk/; the compiled binary does not. The Flask app degrades gracefully without it: every page serves, every UC-1..UC-12 flow works, /epochs renders historical epochs from the seed. The binary is only needed to prove or verify new epoch closures (/api/zk/epoch/close, /api/zk/verify).
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup install nightly
cd polaris_zk && cargo +nightly build --releaseAfter the build, polaris_zk/target/release/polaris-zk exists; the Flask app finds it via the default path. Override with POLARIS_ZK_BINARY=/your/path/polaris-zk if you build elsewhere.
Polaris is released under the Apache License, Version 2.0.
Copyright 2026 Egor Khaklin
Licensed under the Apache License, Version 2.0.
The license includes an explicit patent grant (§3) and preservation of attribution (§4). If you build on Polaris (the code, the schema, or the architectural patterns: audit-of-record discipline, the schema-level constraint lattice, the flat invariant-check layer), retain LICENSE and NOTICE and the author attribution per §4. Component-level attributions for Plonky2, D3, TopoJSON, and Flask live in NOTICE.
The academic project report (docs/paper/polaris_project_report.pdf and its TeX source) is part of the same release under the same license.
Educational project for Seton Hill University, Spring 2026. Notional data only; not a real identity system. All cryptographic algorithm choices reflect current NIST PQC standardization (FIPS 204, FIPS 205) for academic accuracy.
The constitution lives in MISSION.md. The ship history lives in CHANGELOG.md.
If you read one document after this one, read MISSION.md.