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
  • Rust 64.7%
  • HTML 17%
  • TypeScript 12.3%
  • Shell 3.5%
  • Python 2.3%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Marcellina II (She/Her) 7c75881160
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
feat(sdk): add injectable fetch to ClientConfig for proxy routing
Add an optional fetch to ClientConfig and thread it through all outbound
HTTP call sites (discovery, issuance, verification, exchange, graph
issuance/recovery). Consumers can route traffic through a proxy (e.g.
Tor/SOCKS5) for network-level privacy. Bump SDK to 0.3.0.
2026-08-06 18:35:47 -04:00
.forgejo/workflows Add release workflow for binary artifacts 2026-04-24 14:38:49 -04:00
.github/workflows Make WebAuthn sybil resistance always-compiled 2026-08-02 13:14:38 -04:00
admin-ui feat: complete phase A hardening 2026-07-12 10:25:54 -04:00
attester Release 0.9.0 2026-08-01 20:41:12 -04:00
common Make WebAuthn sybil resistance always-compiled 2026-08-02 13:14:38 -04:00
crypto Release 0.9.0 2026-08-01 20:41:12 -04:00
deploy/systemd Make WebAuthn sybil resistance always-compiled 2026-08-02 13:14:38 -04:00
docs feat(sdk): complete @freebird/sdk so clients avoid bespoke protocol code 2026-08-05 16:23:34 -04:00
integration_tests feat(issuer): publish sybil requirements in /.well-known/issuer 2026-08-05 16:23:45 -04:00
interface Release 0.9.0 2026-08-01 20:41:12 -04:00
issuer feat(issuer): publish sybil requirements in /.well-known/issuer 2026-08-05 16:23:45 -04:00
k8s Release 0.9.0 2026-08-01 20:41:12 -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 ci: keep docker workflow static checks in sync with buildx fixes 2026-08-02 10:02:15 -04:00
sdk/js feat(sdk): add injectable fetch to ClientConfig for proxy routing 2026-08-06 18:35:47 -04:00
server-configs Make WebAuthn sybil resistance always-compiled 2026-08-02 13:14:38 -04:00
verifier Release 0.9.0 2026-08-01 20:41:12 -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 Make WebAuthn sybil resistance always-compiled 2026-08-02 13:14:38 -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
AGENTS.md Make WebAuthn sybil resistance always-compiled 2026-08-02 13:14:38 -04:00
Cargo.lock Release 0.9.0 2026-08-01 20:41:12 -04:00
Cargo.toml Remove Scarcity V2 issuer work 2026-07-15 00:05:29 -04:00
CHANGELOG.md Release 0.9.0 2026-08-01 20:41:12 -04:00
docker-compose.yaml Harden graph issuance authority 2026-07-28 08:54:20 -04:00
Dockerfile feat: complete phase A hardening 2026-07-12 10:25:54 -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
NOTICE Add production deployment infrastructure and update docs/config 2026-03-28 14:11:34 -04:00
README.md Release 0.9.0 2026-08-01 20:41:12 -04:00
SECURITY.md feat: complete phase A hardening 2026-07-12 10:25:54 -04:00

Freebird

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

The current source tree supports two token modes:

  • V4 private-verification tokens using a Freebird-specific, bespoke P-256 VOPRF-like construction; it is not RFC 9497 interoperable.
  • V5 public bearer passes using RFC 9474 blind RSA signatures.

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

The optional public bearer exchange atomically spends one or more single-use V5 artifacts and signs configured caller-blinded outputs. Clients use POST /v2/public/exchange and GET /v2/public/exchange/status?public_operation_id=.... A request carries a public, non-authorizing 16-byte operation ID in its body; both routes require exactly one separate Exchange-Status-Capability header containing canonical base64url for 32 random bytes. The capability is header-only and must never be put in a body, URL, query, or log. Responses use Cache-Control: no-store, and durable retries recover the original response without signing or spending twice. Discovery publishes role-neutral V2 graphs and active/retained Ed25519 receipt verification keys; public-only history keeps outputs and receipts verifiable after private signers retire. See Public Bearer Exchange for the API, graph, Redis, recovery, and retention contract.

Project 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.
  • Public Bearer Exchange: exchange API, operation capabilities, receipt verification, durable Redis, and retention.
  • 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.

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, blind-RSA, 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 is only needed 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_ACCEPTED_TOKEN_VERSIONS=v4 \
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. V5 public bearer verification uses public key discovery instead.
  • VERIFIER_ACCEPTED_TOKEN_VERSIONS is required and controls both accepted and advertised token families. The local V4 command enables only v4.
  • 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 Issuer ID, active VOPRF key, and V5 public mode summary.
GET /.well-known/keys Key discovery, active epoch, valid epochs, V5 public keys.
POST /v1/oprf/issue Issue one V4 VOPRF evaluation for a blinded element.
POST /v1/oprf/issue/batch Batch V4 issuance.
POST /v1/public/issue Issue one V5 blind RSA signature.
POST /v1/public/issue/batch Batch V5 public bearer issuance.
POST /v2/public/exchange Atomically consume V5 sources and blind-sign outputs selected by a V2 graph transition.
GET /v2/public/exchange/status?public_operation_id=... Recover exchange status using the separate 32-byte Exchange-Status-Capability header.

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.

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 Optional key ID override; mismatched values are corrected with the derived prefix.
EPOCH_DURATION 1d Human-readable duration accepted.
EPOCH_RETENTION 2 Number of previous epochs accepted.
SYBIL_RESISTANCE none none, 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.
PUBLIC_BEARER_ENABLE true Enables V5 public bearer issuer.
PUBLIC_BEARER_SK_PATH public_bearer_sk.der V5 RSA private key path.
PUBLIC_BEARER_METADATA_PATH public_bearer_metadata.json V5 key metadata path.
PUBLIC_BEARER_VALIDITY 30d V5 key validity window.
PUBLIC_BEARER_AUDIENCE none Optional V5 audience binding.

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_ACCEPTED_TOKEN_VERSIONS none Required comma-separated accepted families (v4, v5).
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. Usually the issuer key file for local testing.
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.