Private OCR, web search, and attested AI chat, paid with anonymous credits bought with shielded ZEC
Runs on your machine from its package; the steps are below.
Not assessed yet.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
npx -y @veilquota/mcp
MCP client config
{
"mcpServers": {
"veilquota": {
"command": "npx",
"args": [
"-y",
"@veilquota/mcp"
]
}
}
}VeilQuota is a pre-release Zcash gateway for privacy-preserving prepaid access to metered APIs and MCP tools. Its intended flow is a shielded ZEC purchase followed by blind, single-use credits, so later requests do not carry a reusable account, API key, payment proof, or session credential that directly links them together.
This repository contains the reproducible engineering baseline and that flow. It runs end to end on public testnet and as a capped, unaudited mainnet beta: one real shielded mainnet payment was issued as a 100-credit booklet and spent on a paid OCR call (2026-10-07).
Accountless API and MCP payments already exist through systems such as x402, MCPay, and Latinum. Zcash payment infrastructure also exists through projects such as CipherPay and zpay. VeilQuota therefore does not claim category uniqueness.
The narrower VeilQuota wedge is the combination of shielded ZEC settlement, blind single-use credits for later API/MCP use, and exact retry behavior when a response is lost. This differs from attaching a payment proof to every request and from using one reusable prepaid-session credential across multiple calls. Kagi Privacy Pass and Nym ticketbooks provide adjacent evidence that users and services can value separating payment identity from later usage.
booklet_v1 is a VeilQuota credit-pack profile built from RFC 9578 publicly verifiable
Blind-RSA tokens. Issuance travels in the generic batch wire format of the IETF Privacy Pass
batched-tokens draft (GenericBatchTokenRequest/GenericBatchTokenResponse, token type
0x0002), checked byte for byte against independent known-answer vectors
(vectors/batched-tokens-blind-rsa.json). The draft is not yet an RFC, the pack is all-or-nothing
(partial issuance is rejected), and the profile has not received an
independent security audit, and must not be presented as one.
The current buyer, competition, business-model, unit-economics, and validation hypotheses are documented in docs/business.md.
VQCE binary record codec with strict version, field, ordering, UTF-8/NFC, and size rules in Rust and TypeScript;VQCE records, using matching Rust and WebCrypto vectors;VQCE payloads, binding the manifest kind, signer-key version, and monotonic manifest version in Rust and Web Crypto;booklet_v1 client with a local issue/finalize/redeem/replay-rejection lifecycle test; the selected library is not independently audited by this project;crates/veilquota-ironwood) on the NU7 pre-release librustzcash crates: each quote pays a fresh Orchard-only Unified Address derived from its quote ID, with a text memo (VQ1: + base64url) that mobile wallets can send; a buyer-named txid is accepted only if every configured lightwalletd endpoint returns the same bytes, height, and block, the block contains it, and it decrypts to exactly one incoming output with the quoted receiver, amount, and memo inside the quote window with enough confirmations. veilquota-checkout merchant-key|ironwood-quote|ironwood-observe drive it; the merchant recovery phrase is shown once and only the viewing key is stored. Tested against a real mainnet Ironwood transaction, and a real 0.0005 ZEC payment from the Zodl wallet (txid 0e7a8cc1ccd511a2b215c4db620583cf61096593766214b6c137b50f6b099bb1, height 3509373) was accepted with all three endpoints agreeing. Mainnet defaults to three lightwalletd hosts run by different operators (zec.rocks, Cake Wallet, 0xRPC), and all three agreed on that payment;veilquota-issuer-mainnet) and a 500-credit cap; one 100-credit booklet has been issued and finalized, and a separate veilquota-gateway-mainnet (own runtime identity and refund key) took buyer A from 100 to 87 credits for one real OCR result with a Payment-Receipt; both mainnet services are reproduced by deploy/cloud-run/deploy-mainnet.sh. Mainnet needs VEILQUOTA_ENABLE_MAINNET=1, packs are capped at 0.0013 ZEC, a mainnet_beta issuer epoch is a distinct manifest environment, and mainnet issuance runs under a per-epoch issuance control (veilquota-issue control EPOCH_DIR enable CAP | disable | status) whose row is locked during authorization, so concurrent purchases cannot exceed the cap and disable stops new purchases at once;VEILQUOTA_ROUTES, rendered from deploy/cloud-run/gateway/routes.template.json) sharing one credit epoch: OCR (13 credits), private web search through Brave's LLM Context API (/v1/tools/search, 2 credits; bounded JSON queries, refused before any charge if malformed), and attested encrypted inference through Tinfoil (/v1/inference/chat/completions, 4 credits). For inference the wallet agent verifies the enclave's attestation (pinned tinfoilsh/confidential-model-router release) and seals the request once with EHBP to its attested HPKE key; the gateway prices and relays ciphertext plus the bound Ehbp-Encapsulated-Key and X-Tinfoil-Model headers, adds the API key, and replays the committed ciphertext and Ehbp-Response-Nonce on exact retry, so a lost answer is recovered and decrypted locally. Provider keys are mounted from Secret Manager. The MCP server exposes private_search and private_chat beside the OCR tools, and paid tools are annotated as irreversible (destructiveHint). Third-party routes commit their first timeout or oversized reply as a refunded failure, so a billed provider call is never re-run, and a paid answer that won't decrypt settles the spend instead of locking the wallet. veilquota-checkout call WALLET GATEWAY MAX_CREDITS runs one agent request read from stdin. Brave sees search queries but not who asked. Live on the mainnet gateway 2026-10-07: one paid search (2 credits) and one attested, encrypted gpt-oss-120b chat (4 credits), buyer A 87 → 85 → 81 (docs/evidence/priced-routes-mainnet-2026-10-07.md);PrivacyPass-Reverse header holding a generic batch, following the Privacy Pass reverse-flow pattern without claiming conformance to that individual draft). When the upstream fails, or its last allowed attempt still has no answer, the gateway commits a uniform 502, sends no receipt, and blind-signs exactly those requests. The wallet swaps the locked credits for the same number of fresh ones and never stays blocked. Refunds are bound into the operation digest, so the same credits cannot buy a second refund. Exact retries replay identical signatures. Presentations never mix purchased and refunded keys, and the wallet pins the refund key on first use. This is proven on real PostgreSQL in scripts/verify.sh --database. The refund key lives only in the redemption plane (Secret Manager) and the deployed gateway publishes it in every challenge;veilquota-checkout agent WALLET SOCKET GATEWAY MAX_CREDITS 8787 also serves GET /v1/models and POST /v1/chat/completions (JSON or server-sent events) on 127.0.0.1:8787, so the OpenAI SDKs, Open WebUI, or Cline use attested private inference by changing the base URL. Each completion is one paid call through the inference route under the spending policy, with the price, balance, receipt, and enclave release in x-veilquota-* response headers. The API key is a fresh random token written 0600 beside the agent socket (openai.token); requests also need a Host naming the listener, which stops DNS-rebinding pages. Spends above the approval threshold are refused there with nothing charged, since an OpenAI client cannot ask the user. Tool calling and n>1 are rejected before payment; streaming delivers the committed answer as standard chunks. Live on mainnet 2026-10-08 with the unmodified OpenAI Python SDK, 4 credits, wallet 81 → 77 (docs/evidence/openai-endpoint-mainnet-2026-10-08.md);veilquota-checkout policy WALLET show|pause|resume|set. A spend above the threshold is put to the user as an MCP elicitation, and only the answer approves it. A host that cannot ask gets a refusal with nothing charged. Exact retries of locked spends are never blocked, and refunds return credits to the caps (packages/mcp-ocr/README.md);User-Agent). Tests against a gateway that tries to set a cookie and Mcp-Session-Id and keep connections alive prove two paid calls share no connection, cookie, session header, or TLS session;scripts/verify.sh --database;GenericBatchTokenRequest/GenericBatchTokenResponse), with a strict decoder that rejects foreign token types, absent entries, oversized batches, and trailing bytes, and a test reproducing independent known-answer encodings byte for byte;https://veilquota-issuer-140011936420.us-central1.run.app, deployed from a digest-pinned non-root image with scale-to-zero, a one-instance ceiling, and a dedicated runtime identity with no project roles. It validates the ceremony artifacts and signed issuer manifest before startup, publishes the RFC 9578 directory and public epoch files byte-for-byte, and reports issuance_enabled=false; /issue deliberately returns 404 until finalized-payment authorization exists;z_listunspent discovery plus a strict candidate-transaction path that combines z_viewtransaction decryption with independent Zebra-backed canonical finality;getrawtransaction, bounded timeouts/responses, disabled redirects/proxies, and strict JSON-RPC response correlation; it is loopback-fixture tested;imported_watchonly. A testnet-only Recovery-B spending candidate was temporarily run beside Zebra for its bounded scan, then stopped, returned locally, verified, and removed from the host; the long-running observer remains watch-only;getrawtransaction independently proved block 4,401,300 and finality. A separately restored client-B wallet completed its bounded history scan, matched its source receiver, and rediscovered a distinct 10,000,000-zatoshi output in canonical block 4,401,699. The deployed observer holds client B's matching viewing key but beta.3 still rejects that transaction as non-wallet data. Both outputs lack a VeilQuota quote memo, so it is funding evidence only;veilquota-checkout observe through the merchant-view-only observer with Zebra-backed finality inside the quote window (docs/evidence/two-invoice-payments-2026-10-01.md);veilquota-issue recomputes each exported request set's digest, requires it to equal the digest committed in the paid quote's memo, records the merchant-verified payment once in Neon purchase_db, blind-signs under the shared epoch, and durably stores the response sealed under an epoch-derived key before release (exact retries return the stored response); veilquota-checkout export-requests/finalize move only blinded requests out of and verified tokens into each encrypted wallet with an atomic file replacement. Both public-testnet buyers now hold their 100-credit booklets locally (docs/evidence/two-invoice-payments-2026-10-01.md);purchase_db/redemption_db databases with SQL-created least-privilege roles, including a cross-plane CONNECT-denial test;veilquota-checkout operator CLI that reads a buyer wallet's staged blinded-batch digest through a TTY-only passphrase prompt and writes a create-once, mode-0600 quote record with a strict Sapling ZIP-321 URI bound to that digest, a fresh merchant receiver, and a confirmation-sized height window. Its observe command runs beside Zallet on the Z3 VM and accepts a payment only when the synchronized watch-only wallet decrypts the exact receiver, amount, and quote memo and Zebra proves canonical finality inside the quote window, then prints the payment-evidence digest used for purchase authorization;veilquota-gateway): HMAC-bound HTTP Payment challenges with an experimental veilquota method, shared-epoch Blind-RSA token verification, server-keyed nullifiers, atomic reservation, a short-lived Ed25519 upstream grant with no payment or credential identifiers, sealed request/result storage, lease-fenced single execution, and exact replay with a Payment-Receipt, proven end to end on Neon;veilquota-ocr): Envoy is the only ingress with fail-closed ext_authz (failure_mode_allow: false), a loopback grant verifier checks each Ed25519 grant once, and a loopback Tesseract worker receives only the image after Envoy strips the grant and Google ID token. The OCR service has no public invoker, so a missing identity gets a platform 403 and a missing or forged grant gets an Envoy 403;veilquota-checkout spend took buyer wallet A from 100 to 87 credits for one real OCR result and a Payment-Receipt, using a 9,476-byte 13-token Authorization header. A first attempt hit an OCR cold-start 503 with the tokens kept locked, and the identical retry completed with no second debit;@veilquota/mcp-ocr) exposing wallet_balance/quote_ocr/run_ocr to a fresh client with no account, API key, payment ID, reusable session, or Zcash key; it relays only balances, prices, OCR text, and receipts from a local wallet agent over a private Unix socket, never a token or passphrase;limit_data fault cuts a paid MCP call's response after the gateway has already committed it, and the identical retry recovers the exact OCR text and receipt with debits=1 and executions=1 for buyer B's live spend (docs/evidence/anonymous-spend-recovery-privacy-2026-10-02.md);veilquota-privacy-audit) proving the redemption schema carries no purchase/buyer-identifier column, neither public-testnet payment's txid, quote ID, or purchase-plane digest appears anywhere in Neon redemption_db or in deployed Cloud Run logs, and each redemption's public fields match both buyers' purchases, so neither spend is deterministically assignable to one payment;vectors/protocol-v1.json) reproduced by native Rust, the same Rust compiled to WASM, and the TypeScript package, with both browser runs in headless Firefox;apps/console): a loopback-only Hono server bridging the wallet agent, the deployed gateway, the fault harness, and the operator/audit tools to a React UI for balance, paid OCR with a live lost-response lab, receipt verification, the two-plane privacy view, and MCP client setup;apps/web, not yet deployed): the credit wallet compiled to WebAssembly (crates/veilquota-wallet-web), passphrase-sealed in IndexedDB, spending search and OCR credits directly against the gateway; a /try#pack link imports a small bearer pack exported from paid credits (veilquota-checkout export-pack) and strips the fragment; a "who sees what" page; and a "try to cheat it" page that runs replay, one-bit forgery, lost-response retry, and forced upstream failure against the real gateway. The gateway accepts browser calls only from an exact VEILQUOTA_ALLOWED_ORIGINS list, never with credentials. A native test checks the browser wallet against the gateway's own challenge binder and credential decoder; a headless-Firefox test checks sealing in a real browser;crates/veilquota-purchase, not yet deployed): stateless HMAC-authenticated ZIP-321 quotes, automatic discovery of the payment to the quote's one-time receiver from compact blocks (checked against the real 2026-10-07 mainnet purchase), full multi-endpoint verification, and the same capped, kill-switched, exactly retried issuance as veilquota-issue; the browser's /buy page blinds the batch only for the pinned manifest key and finalizes the signatures locally;@veilquota/mcp, not yet published): npx -y @veilquota/mcp setup creates a wallet and imports a try-link or a pack exported from the hosted wallet, npx -y @veilquota/mcp agent unlocks it, and claude mcp add veilquota -- npx -y @veilquota/mcp connects any MCP host; prebuilt wallet binaries ship as optional platform packages, released by a tokenless OIDC workflow; mainnet proof in PROOF.md;scripts/verify.sh, see docs/verification.md) covering a clean checkout, browser vectors, real PostgreSQL proofs on an ephemeral Neon branch, and read-only live gateway, receipt, privacy-audit, and log checks;docs/evidence/README.md);The committed baseline passed GitHub CI, and the current working tree passes the documented local
checks. The payment → issuance → anonymous spend → real OCR path has run on public testnet and
Cloud Run, including an MCP client, deliberate lost-response recovery, and a two-plane privacy
audit. Every scripts/verify.sh tier passes (see docs/evidence/verification-2026-10-02.md). Official RFC vector coverage, a deployed browser wallet, in-browser purchase,
durable trust-root/high-water persistence, and external review are not yet done.
rustfmt, clippy, and wasm32-unknown-unknown for browser/client primitives; PostgreSQL stores are native-onlyThe versions are pinned in rust-toolchain.toml, .node-version, and package.json.
pnpm install --frozen-lockfile
scripts/verify.sh # add --browser, --database, --live, or --all
The PostgreSQL tests are ignored by default and truncate their tables. scripts/verify.sh --database runs them on a throwaway Neon branch that is deleted on exit; see
docs/verification.md.
pnpm build
# with the wallet agent running (see scripts/lost-response-demo.sh) and, optionally, the fault
# harness up and both plane database URLs exported for receipt checks and the privacy audit:
node apps/console/dist/server/main.js # http://127.0.0.1:4317
The console binds 127.0.0.1 only, rejects foreign Host, cross-site, and foreign-origin requests,
and never holds tokens, the wallet passphrase, or database URLs beyond passing the latter to the
operator tools.
Run this only after choosing the final HTTPS issuer hostname and request endpoint. The output path must not exist and should be outside the repository. Three distinct passphrases of at least 16 bytes are read from the controlling terminal, confirmed, and never accepted through arguments or environment variables.
cargo run -p veilquota-issuer --bin veilquota-issuer-ceremony -- \
/absolute/private/output/epoch-1 \
veilquota-issuer-140011936420.us-central1.run.app \
https://veilquota-issuer-140011936420.us-central1.run.app \
https://veilquota-issuer-140011936420.us-central1.run.app/issue \
NOT_BEFORE_UNIX \
EXPIRES_AT_UNIX
Only the generated public/ directory is publishable. The secrets/ and buyers/ directories
contain encrypted but sensitive authority and client state. The current epoch's public directory
is deployed, while its authority and buyer artifacts remain local and private. Publication is not
payment-authorized issuance.
The generated manifest verifying key must be pinned through the deployment trust-root channel;
serving the key beside the manifest is metadata, not independent trust bootstrap.
The deployed /issue route currently returns 404 by design. Do not enable it until finalized
payment evidence is durably authorized against the exact blinded-request digest.
crates/veilquota-domain: shared wire-level domain invariantscrates/veilquota-store: PostgreSQL migration and repository boundariescrates/veilquota-credentials: local booklet_v1 and encrypted credential walletcrates/veilquota-checkout: operator quote, issuance exchange, anonymous spend, and wallet agent CLIcrates/veilquota-gateway: anonymous redemption gateway, Envoy grant verifier, operator evidence, and the cross-plane privacy auditorcrates/veilquota-ocr-worker: grant-unaware loopback Tesseract OCR workercrates/veilquota-purchase: online purchase-plane issuer for the hosted walletpackages/mcp-ocr: @veilquota/mcp, the npm MCP server (claude mcp add veilquota -- npx -y @veilquota/mcp); packages/cli-*: its prebuilt wallet binariesapps/web: hosted static browser wallet (Vite/React) and crates/veilquota-wallet-web, its WebAssembly coreapps/console: local web console (Hono loopback API + Vite/React UI)packages/mcp-ocr: MCP TypeScript SDK v2 stdio server over the wallet agent, and a fresh-client demo drivercrates/veilquota-issuer: encrypted shared-epoch issuer authority and bootstrap ceremonycrates/veilquota-issuer-origin: fail-closed Cloud Run public-epoch origin; no issuance handler yetcrates/veilquota-zcash: ZIP-321 and Z3 observer configuration boundariespackages/protocol: TypeScript protocol typesdeploy: immutable dependency and environment pins; deploy/cloud-run builds and deploys the gateway and private OCR servicescripts: one-command verification and the live lost-response demo drivervectors: retained cross-runtime protocol vectorsdocs/status: claim and capability statusdocs/evidence: dated, secret-free evidence records and the evidence indexdocs/threat-model: security-boundary documentationDo not use this code with funds. See SECURITY.md for private vulnerability reporting and
docs/threat-model/README.md for the current documented boundary.
Dual-licensed under Apache-2.0 or MIT, at your option.