Privacy-preserving token issuance and verification in Rust: unlinkable single-use credentials (VOPRF + blind RSA) with nullifier-based double-spend prevention and sybil-resistant admission gates (PoW, WebAuthn, vouching). https://freebird.bot
  • HTML 61.4%
  • Rust 30.1%
  • TypeScript 4.2%
  • Shell 2.3%
  • Python 1.8%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Marcellina II (She/Her) cc854da563
All checks were successful
Build & Push Docker Images / build (issuer) (push) Successful in 3s
Build & Push Docker Images / build (verifier) (push) Successful in 3s
Release Binaries / release (push) Successful in 3s
release: prepare v0.11.0
2026-10-11 23:15:34 -04:00
.forgejo/workflows Add release workflow for binary artifacts 2026-04-24 14:38:49 -04:00
.github/workflows release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
admin-ui feat: complete phase A hardening 2026-07-12 10:25:54 -04:00
attester release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
common release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
crypto release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
deploy/systemd release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
docs release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
integration_tests release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
interface release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
issuer release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
k8s release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
monitoring New token versions. V4 Private and V5 Public. Use the one more aligned with your use-case. 2026-04-26 08:50:56 -04:00
scripts release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
sdk/js release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
server-configs release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
site release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
verifier release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
webauthn-client Add alternatives.md doc and fix webauthn-client bug 2026-06-28 18:58:23 -04:00
.dockerignore Harden WebAuthn privacy and release deployment flows 2026-05-05 20:12:42 -04:00
.env.example release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
.env.hsm.example Release 0.9.0 2026-08-01 20:41:12 -04:00
.gitignore chore: ignore landing page WIP directory 2026-08-02 13:16:20 -04:00
.ignore feat: complete phase A hardening 2026-07-12 10:25:54 -04:00
.npp-md-preview.html security(production): harden freebird for production deployment 2026-04-28 00:21:27 -04:00
Cargo.lock release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
Cargo.toml Remove Scarcity V2 issuer work 2026-07-15 00:05:29 -04:00
CHANGELOG.md release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
docker-compose.yaml release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
Dockerfile release: prepare v0.10.0 2026-08-25 11:16:24 -04:00
launch.sh feat: complete phase A hardening 2026-07-12 10:25:54 -04:00
LICENSE First commit 2026-01-01 15:35:19 -05:00
mkdocs.yml feat: add protocol APIs and documentation 2026-09-10 10:57:58 -04:00
NOTICE Add production deployment infrastructure and update docs/config 2026-03-28 14:11:34 -04:00
README.md release: prepare v0.11.0 2026-10-11 23:15:34 -04:00
requirements-docs.txt docs: add MkDocs documentation site 2026-08-25 12:04:06 -04:00
SECURITY.md release: prepare v0.11.0 2026-10-11 23:15:34 -04:00

Freebird

Freebird is a Rust workspace for privacy-preserving V4 token issuance and verification. The issuer evaluates blinded client requests, the client finalizes the result into a V4 token, and the verifier checks that token while recording a nullifier so the same token cannot be spent twice.

The current source tree supports V4 private-verification tokens using a Freebird-specific, bespoke P-256 VOPRF-like construction; it is not RFC 9497 interoperable. V4 remains available for existing clients and operator-controlled admission credentials.

The freebird-interface binary exercises the V4 flow against local services on 127.0.0.1:8081 and 127.0.0.1:8082.

Project Documents

  • Documentation Site: documentation-first overview and navigation for the repository's operational and protocol documents.
  • Security Policy: vulnerability reporting, production baseline, and known limitations.
  • Profile and Claim Matrix: authoritative status of planned issuance profiles, limits on public claims, and the Phase B transitional-client inventory.
  • Architecture: issuer, verifier, client, storage, and token flows.
  • Threat Model: security goals, assumptions, non-goals, and current gaps.
  • Sybil Modes: what each admission gate resists, where it is weak, and how combined modes behave.
  • Admin Operations: issuer operator workflows for invitations, vouching, WebAuthn, keys, and audit.
  • Client Proofs: attaching PoW, invitation, WebAuthn, and vouching Sybil proofs to issuance requests.
  • WebAuthn Browser Flow: browser passkey registration, authentication, and WebAuthn Sybil proof export.
  • Production Deployment: Redis replay stores, TLS/proxy settings, persistence, and preflight checks.
  • Release Packaging: version tags, release archives, checksums, container images, and signature verification.
  • Kubernetes Deployment: hardened Kubernetes manifests and production notes.
  • Systemd Deployment: single-host service units and environment file templates.
  • Audit Logging: audit fields, retention model, privacy impact, and limitations.

Documentation Site

The documentation site's source is docs/; its generated output is site/. The existing legacy docs/*.md files are preserved alongside the new structure; the documentation-first site navigation does not remove them.

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-docs.txt
mkdocs serve
mkdocs build --strict

Workspace

Path Purpose
issuer Axum issuer service, admin CLI, config validator, key rotation, Sybil gates.
verifier Axum verifier service, issuer metadata refresh, nullifier storage, admin UI.
interface Local V4 token test client.
crypto VOPRF, redemption-token, and provider primitives.
common Shared API types, metrics, logging, rate limits, TLS enforcement.
integration_tests Cross-crate protocol and storage regression tests.
sdk/js TypeScript client SDK and examples.
docker-compose.yaml, Dockerfile, k8s, server-configs Deployment assets.

Prerequisites

  • Rust stable with Cargo.
  • curl for the smoke checks below.
  • Redis is required by default for verifier replay protection. For explicitly unsafe local development only, use IN_MEMORY_REPLAY_STORE=true together with VERIFIER_ENV=development.
  • Node.js 24 or newer is required for the TypeScript SDK.

Quickstart (Docker)

The fastest way to get Freebird running is with Docker Compose. This is an explicit direct-development deployment, not a production reverse-proxy mode:

Compose sets COMPOSE_DIRECT_ONLY=true; its validator refuses DEPLOYMENT_MODE=trusted-proxy or production settings because no proxy-aware Compose profile is provided. The /readyz and /ready checks only admit the direct development services after their configured dependencies are ready.

cp .env.example .env
./launch.sh up

launch.sh checks prerequisites, creates .env from .env.example if it does not exist, offers to generate a secure ADMIN_API_KEY, pulls or builds the images, and waits for both services to become healthy.

Compose publishes the issuer and verifier on 127.0.0.1:8081 and 127.0.0.1:8082 by default. Override those host addresses with ISSUER_HOST_BIND_ADDR and VERIFIER_HOST_BIND_ADDR; keep ISSUER_BIND_ADDR and VERIFIER_BIND_ADDR for the in-container listeners (defaulting to 0.0.0.0:8081 and 0.0.0.0:8082).

When it finishes, verify the services are up:

curl http://127.0.0.1:8081/.well-known/issuer
curl http://127.0.0.1:8082/.well-known/verifier
curl http://127.0.0.1:8082/health

Then run the V4 token round-trip from the interface binary:

cargo run -p freebird-interface -- \
  --issuer-url http://127.0.0.1:8081 \
  --verifier-url http://127.0.0.1:8082

Expected result: a fresh V4 token is issued by the issuer and accepted once by the verifier.

Other useful launch.sh subcommands:

./launch.sh status     # show running containers
./launch.sh logs       # follow service logs
./launch.sh stop       # stop all services

The default .env uses SYBIL_RESISTANCE=invitation with a bootstrap admin user. For local testing without Sybil resistance, set SYBIL_RESISTANCE=none in .env before starting.

Build And Test

cargo build --workspace
cargo test --workspace

Redis-specific integration tests skip themselves when Redis is not reachable at REDIS_URL or redis://127.0.0.1:6379.

To check the JavaScript SDK:

cd sdk/js
npm install
npm run lint
npm test

Local Source Round Trip (Without Docker)

Use this workflow when you want to build from source and run freebird-interface.

The interface defaults to local URLs but accepts overrides:

  • issuer: http://127.0.0.1:8081 (override with --issuer-url or FREEBIRD_ISSUER_URL)
  • verifier: http://127.0.0.1:8082 (override with --verifier-url or FREEBIRD_VERIFIER_URL)

For this local flow, run with REQUIRE_TLS=false and disable Sybil resistance so the interface can request tokens without an invitation, proof of work, or WebAuthn proof.

1. Start The Issuer

From the repository root:

ADMIN_API_KEY=local-admin-key-must-be-at-least-32-chars \
BIND_ADDR=127.0.0.1:8081 \
ISSUER_ID=issuer:local:v4 \
ISSUER_SK_PATH=issuer_sk.bin \
KEY_ROTATION_STATE_PATH=key_rotation_state.json \
SYBIL_RESISTANCE=none \
REQUIRE_TLS=false \
cargo run -p freebird-issuer --bin freebird-issuer

Leave this process running.

The issuer creates issuer_sk.bin if it does not already exist. The verifier must read the same key file for V4 private verification.

2. Start The Verifier

Open a second terminal in the repository root:

ADMIN_API_KEY=local-admin-key-must-be-at-least-32-chars \
BIND_ADDR=127.0.0.1:8082 \
VERIFIER_ID=verifier:local:v4 \
VERIFIER_AUDIENCE=local \
VERIFIER_ENV=development \
IN_MEMORY_REPLAY_STORE=true \
ISSUER_URL=http://127.0.0.1:8081/.well-known/issuer \
VERIFIER_SK_PATH=issuer_sk.bin \
REFRESH_INTERVAL_MIN=1 \
REQUIRE_TLS=false \
cargo run -p freebird-verifier --bin freebird-verifier

Leave this process running. The verifier should log that issuer metadata was updated. If it logs that no private verification key is available, check VERIFIER_SK_PATH.

3. Check The Services

In a third terminal:

curl http://127.0.0.1:8081/.well-known/issuer
curl http://127.0.0.1:8082/.well-known/verifier
curl http://127.0.0.1:8082/health

4. Run The Interface

cargo run -p freebird-interface

Expected result: a fresh V4 token is issued by the issuer and accepted once by the verifier.

Useful interface modes:

cargo run -p freebird-interface -- --replay
cargo run -p freebird-interface -- --double-spend
cargo run -p freebird-interface -- --stress 10
cargo run -p freebird-interface -- --save
cargo run -p freebird-interface -- --load

--replay and --double-spend should show that the first verification succeeds and the second use of the same token is rejected.

Why These Local Settings Matter

  • ADMIN_API_KEY is required by both services and must be at least 32 characters.
  • SYBIL_RESISTANCE=none is needed for the local interface. If the issuer uses invitation, pow, webauthn, or another Sybil mode, /v1/oprf/issue expects a matching sybil_proof.
  • VERIFIER_ID and VERIFIER_AUDIENCE define the verifier scope. V4 clients bind this scope into the token before issuance.
  • VERIFIER_SK_PATH=issuer_sk.bin lets the verifier validate V4 private tokens from the local issuer.
  • VERIFIER_ENV=development plus IN_MEMORY_REPLAY_STORE=true is the explicit unsafe local memory-backend configuration. Production deployments must use Redis; an unsafe override cannot enable memory replay in production.
  • REQUIRE_TLS=false is for local development. With REQUIRE_TLS=true, inbound HTTP requests are rejected unless a reverse proxy supplies X-Forwarded-Proto: https, and verifier issuer metadata URLs must be HTTPS.

HTTP API

Issuer public endpoints:

Method Path Purpose
GET /.well-known/issuer V4 issuer metadata: issuer ID and active VOPRF authority information.
POST /v1/oprf/issue Issue one V4 VOPRF evaluation for a blinded element.
POST /v1/oprf/issue/batch Batch V4 issuance.

Verifier public endpoints:

Method Path Purpose
GET /health Basic verifier health.
GET /.well-known/verifier Verifier ID, audience, and scope digest.
POST /v1/check Validate a token without consuming it.
POST /v1/verify Validate and consume a token. Reuse is rejected.
POST /v1/verify/batch Batch verify and consume tokens.

Stable public error responses include POST /v1/verify replay rejection: HTTP 401 with {"ok":false,"error":"replay_detected","verified_at":0}. Other verification failures remain generic.

Admin endpoints live under /admin and require X-Admin-Key: <ADMIN_API_KEY> or a login session cookie. The verifier always mounts its admin router. The issuer mounts its admin router for all Sybil modes and includes operator endpoints for invitation state, user bans, key rotation, audit export, WebAuthn credential/policy review, and multi-party vouching state.

Issuer operator workflows:

Area Endpoints
Invitation users GET /admin/users, GET /admin/users/:user_id, POST /admin/bootstrap/add, POST /admin/invites/grant, POST /admin/users/ban, POST /admin/users/unban
Invitation codes GET /admin/invitations, POST /admin/invitations/create, GET /admin/invitations/:code, DELETE /admin/invitations/:code
Multi-party vouching GET/POST /admin/vouching/vouchers, DELETE /admin/vouching/vouchers/:user_id, POST /admin/vouching/vouches, GET/DELETE /admin/vouching/pending, POST /admin/vouching/mark-successful, POST /admin/vouching/mark-problematic
WebAuthn GET /admin/webauthn/policy, GET /admin/webauthn/stats, GET /admin/webauthn/credentials, DELETE /admin/webauthn/credentials/:cred_id
Keys and audit GET /admin/keys, POST /admin/keys/rotate, POST /admin/keys/cleanup, DELETE /admin/keys/:kid, GET /admin/audit, GET /admin/export/audit

Configuration Reference

Common service variables:

Variable Service Default Notes
ADMIN_API_KEY both none Required, minimum 32 characters.
BIND_ADDR both issuer 0.0.0.0:8081, verifier 0.0.0.0:8082 Listen address.
ISSUER_HOST_BIND_ADDR / VERIFIER_HOST_BIND_ADDR Docker Compose 127.0.0.1:8081 / 127.0.0.1:8082 Host-published addresses; separate from container listeners.
REQUIRE_TLS both false Set true in production behind TLS.
BEHIND_PROXY both false Trust forwarded client IP and proto headers.
RUST_LOG both set by logging init Standard tracing filter.

Issuer variables:

Variable Default Notes
ISSUER_ID issuer:freebird:v4 Embedded in issued tokens and metadata.
ISSUER_SK_PATH issuer_sk.bin V4 issuer secret key path. Created if missing.
KEY_ROTATION_STATE_PATH key_rotation_state.json V4 key rotation state.
KID derived from persisted key Optional compatibility override; it is preserved only when it begins with the persisted key's derived KID. Mismatched values fall back to that stable derived KID. Retain a legacy override while retaining matching legacy rotation state.
EPOCH_DURATION 1d Human-readable duration accepted.
EPOCH_RETENTION 2 Number of previous epochs accepted.
SYBIL_RESISTANCE required Set explicitly to none only for a deliberate no-checker opt-out; otherwise use invitation, pow, rate_limit, progressive_trust, proof_of_diversity, multi_party_vouching, social_graph, webauthn, or combined.
SYBIL_REPLAY_STORE memory Replay store for accepted PoW, WebAuthn, vouching, and social-graph proofs. Use redis for public multi-instance or restart-safe issuers.
SYBIL_REPLAY_REDIS_URL none Redis URL for SYBIL_REPLAY_STORE=redis; falls back to REDIS_URL.
SYBIL_REPLAY_KEY_PREFIX freebird:sybil:replay Redis key prefix for Sybil replay records.
SOCIAL_GRAPH_ATTESTERS_PATH social_graph_attesters.json Trusted attester key config JSON; required for SYBIL_RESISTANCE=social_graph.
SOCIAL_GRAPH_JWKS_URL none Optional JWKS URL for attester key refresh; refresh is not yet implemented.
SOCIAL_GRAPH_KEY_REFRESH_INTERVAL 3600 Attester key refresh interval.
SOCIAL_GRAPH_MIN_LEVEL 1 Minimum eligibility level: 1 basic, 2 standard, 3 high-value.
SOCIAL_GRAPH_ACCEPTED_POLICY_IDS none Comma-separated accepted attester policy IDs; required and must not be empty.
SOCIAL_GRAPH_ATTESTATION_MAX_AGE 300 Maximum attestation age.
SOCIAL_GRAPH_CLOCK_SKEW_SECS 30 Allowed clock skew.
SOCIAL_GRAPH_REQUIRE_REQUEST_BINDING true Require Cred presentation request-binding match.
SOCIAL_GRAPH_REQUIRE_QUOTA_NULLIFIER false Require an attestation quota nullifier and replay-check it.
SOCIAL_GRAPH_REPLAY_TTL 600 Replay-store TTL for accepted social-graph proofs.
SOCIAL_GRAPH_STATE_PATH social_graph_state.json Persistent social-graph gate state path.
SOCIAL_GRAPH_FAIL_CLOSED true Reject startup/verification if trusted attester keys are unavailable.

V4 verifier lookup remains exact-KID matching. During migration from the former date-suffixed default, a retained legacy rotation-state file requires KID to remain explicitly set to its legacy active value, which must retain the persisted key's derived-KID prefix. To omit KID, explicitly reset/delete the rotation-state file first; this accepts an identity transition to the stable key-derived KID.

Verifier variables:

Variable Default Notes
VERIFIER_ID none Required. V4 tokens are bound to this verifier ID.
VERIFIER_AUDIENCE VERIFIER_ID Audience used in the verifier scope digest.
VERIFIER_ENV none Must be development for the explicit in-memory development backend.
IN_MEMORY_REPLAY_STORE false Must be true with VERIFIER_ENV=development; otherwise Redis is required.
ISSUER_URL / ISSUER_URLS http://127.0.0.1:8081/.well-known/issuer One issuer URL or comma-separated issuer URLs. HTTPS is required when REQUIRE_TLS=true.
VERIFIER_SK_PATH none V4 private verification key.
VERIFIER_SK_B64 none Base64url raw 32-byte V4 key alternative.
VERIFIER_KEYRING_B64 none JSON map of kid to base64url raw 32-byte keys for rotation windows.
REDIS_URL none Required for verifier nullifier storage outside explicit development memory mode.
REFRESH_INTERVAL_MIN 10 Issuer metadata refresh interval.
EPOCH_DURATION_SEC 86400 Verifier display/config value.
EPOCH_RETENTION 2 Verifier display/config value.

Docker And Deployment Assets

The repository includes a multi-stage Dockerfile, docker-compose.yaml, Kubernetes manifests, reverse-proxy examples, monitoring rules, and validation scripts. Treat those as deployment-oriented assets, not as the easiest way to run the local interface.

For production:

  • Use real TLS and set REQUIRE_TLS=true.
  • Use a high-entropy ADMIN_API_KEY from a secret manager.
  • Use Redis for verifier nullifier storage.
  • Use SYBIL_REPLAY_STORE=redis for issuer Sybil proof replay protection.
  • Keep issuer key material on protected filesystem storage. HSM-backed issuer startup integration is not implemented; HSM_ENABLE=true is rejected.
  • Do not use SYBIL_RESISTANCE=none for a public issuer.
  • Pin container image versions or digests instead of latest.
  • Keep /admin on a private hostname, VPN, or explicit source allowlist.

Admin CLI

The issuer package includes freebird-cli. Use it when the issuer is running and you need scripted access to issuer admin routes:

cargo run -p freebird-issuer --bin freebird-cli -- \
  --url http://127.0.0.1:8081 \
  --key local-admin-key-must-be-at-least-32-chars \
  health

Run with --help to see health, stats, config, users, invites, keys, export, metrics, and audit commands.

Troubleshooting

freebird-interface cannot connect:

  • Confirm the issuer is on 127.0.0.1:8081 and the verifier is on 127.0.0.1:8082.
  • Confirm both services were started from the repository root or point ISSUER_SK_PATH and VERIFIER_SK_PATH at the same file.

Token issuance returns an authorization or Sybil error:

  • Use SYBIL_RESISTANCE=none for the local interface, or provide a matching sybil_proof from a custom client.
  • For proof-of-work, the proof input must match the request binding documented in Sybil Modes.
  • For social_graph, check that SOCIAL_GRAPH_ATTESTERS_PATH points to a valid attester key config and SOCIAL_GRAPH_ACCEPTED_POLICY_IDS is not empty.

Verification always fails:

  • Check verifier logs for issuer metadata refresh errors.
  • Check that VERIFIER_ID and VERIFIER_AUDIENCE match the metadata fetched by the client.
  • Check that VERIFIER_SK_PATH points at the issuer's V4 secret key.

HTTP requests fail with tls_required:

  • You started a service with REQUIRE_TLS=true. Use HTTPS through a reverse proxy or set REQUIRE_TLS=false for local development.

The second use of a token fails:

  • That is expected. /v1/verify consumes the token by recording its nullifier. Use /v1/check when you need a non-consuming validity check.