The Bolyra protocol over HTTP

HTTP 402,
Zero Knowledge.

A 402 response carries a Bolyra-Challenge. The client returns a Bolyra-Credential — a zero-knowledge proof that a human authorized this agent for this spend, bound to the server's nonce. The server verifies, grades the credential A–F, and lets the call run.

Drop-in alongside Coinbase x402, Visa TAP, and Google AP2. No identity leak, no spend-limit leak, no delegation-chain leak — just did the human consent for this.

One round-trip. 402 → 200 with a graded credential, <200ms client-side proving.

// Phase 1 — server returns 402 with a fresh challenge
if (!req.headers['bolyra-credential']) {
  const nonce = freshNonce();
  res.writeHead(402, {
    'PAYMENT-REQUIRED':  serializePaymentRequired(REQS),
    'Bolyra-Challenge':  nonce.toString(16),
  });
  return res.end(JSON.stringify({
    accepts:   [serializePaymentRequired(REQS)],
    challenge: nonce.toString(16),
  }));
}

// Phase 2 — verify the credential bound to that nonce
const { verified, score, grade } =
  await verifyX402Authorization(
    req.headers['bolyra-credential'],
    REQS,
    resolveCredential,
  );

if (!verified || score < 70) {
  return sendJson(res, 403, { score, grade });
}
sendJson(res, 200, { result: runTool(req.body) });

Challenge · Credential · Verify

Three messages. One round-trip. Bolyra rides alongside the standard x402 PAYMENT-REQUIRED header — it doesn't replace the payment, it gates the authorization to make it.

1

Server → Client

402 + challenge

No credential, no entry. The server issues a fresh single-use nonce with a 60s TTL and emits it on the Bolyra-Challenge response header alongside the standard PAYMENT-REQUIRED envelope.

HTTP/1.1 402 Payment Required
Bolyra-Challenge: a1b2c3…
2

Client (agent)

Build credential

The agent generates parallel Groth16 proofs of mutual handshake + spend-policy fit, binds them to the server's nonce, and ships everything in one base64url header. ~200ms with rapidsnark.

Bolyra-Credential: eyJoYW5kc2hha2Ui…
3

Server → Client

Verify + grade

Verifier checks proofs, nonce binding, replay status, and policy fit. Returns a score 0–100 (A/B/C/D/F). Score ≥ 70 → 200 OK, the tool runs. Otherwise 403 with the breakdown.

HTTP/1.1 200 OK · grade: A · score: 100

One protocol, six rails

The same challenge–credential–verify primitive plugs into every agentic-commerce and agent-tooling rail. Bolyra never replaces them — it adds the ZK authorization layer they're each missing.

Server, client, verify

The TypeScript adapter ships in integrations/payment-protocols. createX402Authorization on the client, verifyX402Authorization on the server — both bound to the Bolyra-Challenge nonce.

Samples below are TypeScript. Save as .ts and run with npx tsx server.ts (no build step) or compile via tsc. Node 22.6+ also supports node --experimental-strip-types server.ts directly.

import { createServer, type ServerResponse } from 'node:http';
import { randomBytes } from 'node:crypto';
import {
  verifyX402Authorization,
  serializePaymentRequired,
  X402_BOLYRA_CREDENTIAL_HEADER,
  X402_BOLYRA_CHALLENGE_HEADER,
  X402_PAYMENT_REQUIRED_HEADER,
  type X402PaymentRequirements,
} from '@bolyra/payment-protocols';

const REQS: X402PaymentRequirements = {
  chain:     'eip155:84532',  // Base Sepolia
  asset:     'USDC',
  amount:    10_000,            // $100.00 in cents
  recipient: '0x000000000000000000000000000000000000beef',
};

const nonces = new Map<string, { issuedAt: number; consumed: boolean }>();

// You provide: resolve a did:bolyra:* identifier to an AgentCredential.
// Typically a read from your registry or cache.
async function resolveCredential(did: string) { return null; }

function sendJson(res: ServerResponse, status: number, body: unknown) {
  res.writeHead(status, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(body));
}

createServer(async (req, res) => {
  const cred = req.headers[X402_BOLYRA_CREDENTIAL_HEADER.toLowerCase()];

  // Phase 1 — issue challenge, return 402.
  if (!cred) {
    const nonce = BigInt('0x' + randomBytes(8).toString('hex'));
    const hex = nonce.toString(16);
    nonces.set(hex, { issuedAt: Date.now(), consumed: false });
    res.writeHead(402, {
      [X402_PAYMENT_REQUIRED_HEADER]:  serializePaymentRequired(REQS),
      [X402_BOLYRA_CHALLENGE_HEADER]: hex,
      'Content-Type': 'application/json',
    });
    return res.end(JSON.stringify({ accepts: [serializePaymentRequired(REQS)], challenge: hex }));
  }

  // Phase 2 — verify proof + nonce binding + replay.
  const result = await verifyX402Authorization(cred, REQS, resolveCredential);
  const rec = nonces.get(result.sessionNonce.toString(16));
  if (!rec || rec.consumed) return sendJson(res, 403, { error: 'replay or unknown nonce' });
  rec.consumed = true;

  if (!result.verified || result.score < 70) {
    return sendJson(res, 403, { score: result.score, grade: result.grade, warnings: result.warnings });
  }
  sendJson(res, 200, { result: { tool: 'demo.echo' }, bolyra: { did: result.did, grade: result.grade } });
}).listen(4402);

SDKs by language

TypeScript ships today with full proving (snarkjs + rapidsnark). The Python SDK is a thin shell — pure-Python types and validation, with proving delegated to the TS prover via subprocess. Rust, Go, and Ruby are not yet scheduled.

TypeScript
@bolyra/sdk
v0.2 live
Python
bolyra
on PyPI
Rust
bolyra-rs
planned
Go
github.com/bolyra/bolyra-go
planned
Ruby
bolyra
planned

Three circuits, one verification

Groth16 across all three, rapidsnark-accelerated for sub-200ms client-side proving. Verified through a single on-chain transaction on Base L2 — or fully off-chain for batched commerce.

Circuit 1

HumanUniqueness

Constraints16,409
SystemGroth16
Proving~100ms

Circuit 2

AgentPolicy

Constraints20,923
SystemGroth16 + PLONK
Proving~100ms

Circuit 3

Delegation

Constraints22,398
SystemGroth16 + PLONK
Proving~100ms
<200ms
End-to-end handshake proving
~590k
Gas for full on-chain verification
$0.15
Per handshake at current Base L2 prices
0 bits
Information leaked beyond policy fit

Specification

IETF-style draft, W3C DID method, and a conformance test suite. spec/conformance-runner.js runs 48 vectors covering handshake, delegation, signatures, Merkle inclusion, cumulative encoding, nonce reuse, and boundary values.

IETF

Draft (preparing for submission)

draft-bolyra-mutual-zkp-auth-01

Message flows, proof formats, and verification procedures for mutual ZKP auth over HTTP 402. v0.3.0 release locks the canonical 6-element Delegation public-signals layout for IETF submission.

W3C

DID Method

did:bolyra

Decentralized Identifier method for Bolyra identities. Human and agent DID documents with ZKP-aware verification methods.

Conformance

Test Vectors

48 vectors, all passing

Handshake, delegation, signature verification, Merkle inclusion, cumulative encoding, nonce reuse, boundary values.