Production: api-billing-v2 (verifiable API billing)
Deploy checklist for durable replay, signed transparency, and strict verification.
0. Local setup (keys + .env)
From repo root (writes gitignored verifier-api/.env):
npm run setup:billing-v2-env
npm run setup:billing-v2-env -- --production-secrets # also production/secrets/.env.production
npm run setup:billing-v2-env -- --all --force # both targets, rotate keysRe-run with --force to regenerate billing keys. Then apply migrations (section 1) and start the API.
1. Database migrations
Run on production PostgreSQL (in order):
psql "$DATABASE_URL" -f verifier-api/src/infrastructure/database/migrations/030_vdi_billing_v2.sql
psql "$DATABASE_URL" -f verifier-api/src/infrastructure/database/migrations/031_vdi_billing_replay_crash_safe.sql030 creates vdi_billing_replay_guard and vdi_billing_transparency_log.
031 adds status, expires_at, committed_at for crash-safe reservations.
2. Cryptographic keys (generate once, store in secrets manager)
./scripts/generate-billing-meter-key.shGenerate a separate log operator key (recommended):
openssl genpkey -algorithm ed25519 -out log-operator.pemGenerate manifest authority seed (64+ hex, no 0x) for strict profile:
openssl rand -hex 32 # 64 hex chars — keep offline; also set VDI_MANIFEST_AUTHORITY_SIGNING_KEY3. Required environment variables
| Variable | Purpose |
|---|---|
VDI_ATTEST_ENABLED=true | Enable attest routes |
VDI_SIGNING_KEY | 64+ hex — stable VDI issuer |
VDI_ATTEST_SECRET | Header X-VDI-Attest-Secret on attest routes |
VDI_BILLING_METER_PRIVATE_KEY_PEM + VDI_BILLING_METER_KID | Server-side meter signing or trust anchors for meter_envelope |
VDI_BILLING_LOG_OPERATOR_PRIVATE_KEY_PEM + VDI_BILLING_LOG_OPERATOR_KID | Signed transparency log entries (SLE) |
VDI_BILLING_VERIFY_PROFILE=VDI_VERIFY_STRICT_V1 | Production default strict verify |
VDI_MANIFEST_AUTHORITY_SIGNING_KEY | Ed25519 seed (64+ hex) — manifest authority_binding for strict profile |
VDI_BILLING_REVOCATION_JSON | Inline revocation feed (dev/pilot) |
VDI_BILLING_REVOCATION_URL | HTTPS JSON feed (production; polled with cache) |
VDI_BILLING_REVOCATION_CACHE_SEC | 60 |
Optional:
| Variable | Default | Purpose |
|---|---|---|
VDI_BILLING_REPLAY_RESERVE_TTL_SEC | 900 | In-flight reservation TTL; expired rows are reclaimable after crashes |
4. Revocation feed (strict profile)
VDI_BILLING_REVOCATION_JSON must be a JSON object:
{
"revokedAttestations": [],
"revokedKids": [],
"snapshotTime": 1716300000
}Publish updates from your key-status pipeline at https://api.quantzk.com/.well-known/vdi-billing-revocation.json (served by the verifier API from VDI_BILLING_REVOCATION_JSON); do not rely on the empty dev snapshot in production.
5. Crash-safe replay behavior
- Reserve —
status=reserved,expires_at=now+TTLbefore attest. - Commit —
status=committedafter successful attest + transparency append. - Release — delete reservation on caught failures only.
- Recovery — expired
reservedrows are reclaimed idempotently (sameevent_idcan be retried).
6. Auditor endpoints
GET /.well-known/vdi-billing-revocation.json— published strict-profile revocation snapshot.GET /api/vdi/billing/transparency/head— current Merkle root, tree size, latest entry hash.POST /api/vdi/billing/verify— passlog_inclusion_prooffrom attest response.
Planned: signed tree-head checkpoints, consistency proofs, external witness publishing.
7. CI / smoke
Billing pilot integration tests (14/14) — canonical command:
cd verifier-api
DATABASE_URL="postgresql://zkcaptcha:development@localhost:5432/zkcaptcha" \
REDIS_ENABLED="false" \
REDIS_DISABLED="true" \
NODE_ENV=test \
DB_SSL=false \
npm run test:billing-pilotSetup and troubleshooting (Railway DATABASE_URL override, Docker/apt Postgres): docs/dev/verifier-api-test-setup.md.
Production smoke (live api.quantzk.com):
./scripts/smoke-billing-phase2.shEvidence template: api-billing-smoke-evidence.md.
8. Phase-2 in-circuit commitment binding
Phase-2 attaches a small Groth16 proof (apiBillingV2 circuit, 402 constraints, 1 public signal) under attestation.billing_proof that binds Poseidon8(field-images of public_commitments) cryptographically — not via the Ed25519 attestation wrapper. The verifier returns incircuit_binding_verification on /api/vdi/billing/verify.
Enable on production:
VDI_BILLING_INCIRCUIT_BINDING=true
# VDI_BILLING_INCIRCUIT_DIR=/repo/verifier-api/circuits/apiBillingV2 # optional overrideCircuit artifacts ship in the verifier-api image at /repo/verifier-api/circuits/apiBillingV2/ (mirrored from zk-cp/build/apiBillingV2/). The ARTIFACT_MANIFEST.json in that directory is hash-validated on boot by validateArtifacts(). Regenerate artifacts + re-run Phase-2 trusted setup with operator entropy via zk-cp/scripts/compile-api-billing-v2.sh.
binding_version: bn128-poseidon8-v1 · circuit_hash: 0xa1b1898a9e24021943711c3eb292fb704d4ae19e8c57fa00679a8859d70bb727 · vkey_hash: 0x5f3d88f98cd208e6937752c51377d26cd2081ea6c42824a605087cb80b77eb1e.
9. Roadmap (largely shipped)
- Shipped:
attestation.billing_commitments+proof.public_signals(bn128 field split of digest; signed on attestation) — Phase 1 - Shipped:
attestation.billing_proof(apiBillingV2 Groth16 in-circuit binding) — Phase 2 - Shipped: transparency consistency proofs (
GET /api/vdi/billing/transparency/consistency) - Shipped:
VDI_BILLING_REVOCATION_URLlive feed +@quantzk/vdi-billing/clientverifier helper - Next: multi-party Phase-2 trusted setup ceremony (rotate dev entropy out before adversarial production trust)
