Versioning Doctrine
The repository version (0.12.6) is decoupled from the wire format versions. Two wire formats coexist: Wire 0.1 (peac-receipt/0.1) for flat receipt records and Wire 0.2 (interaction-record+jwt) for structured kinds with typed extensions. Both formats use Ed25519 signatures and the PEAC-Receipt header. verifyLocal() auto-detects wire version.
Wire Format Identifiers
Wire 0.1 (Stable)
Type (typ)
peac-receipt/0.1Algorithm (alg)
EdDSAKey Type (crv)
Ed25519Current Key ID (kid)
peac-2026-03Wire 0.2 (Stable)
Type (typ)
interaction-record+jwtAlgorithm (alg)
EdDSAKey Type (crv)
Ed25519Structural Kinds
evidence | challengeevidence/challenge), open semantic type (reverse-DNS or URI), multi-valued pillars, 12 typed extension groups, and policy binding (JCS + SHA-256).JWS Structure
PEAC receipts are encoded as JWS Compact Serialization (RFC 7515) with three base64url-encoded parts separated by periods:
BASE64URL(header).BASE64URL(payload).BASE64URL(signature) // Example receipt structure: eyJ0eXAiOiJwZWFjLXJlY2VpcHQvMC4xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJwZWFjLTIwMjYtMDEifQ. eyJyaWQiOiIwMUpRWEY4...
Protected Header
The protected header contains algorithm and type information:
| Claim | Value | Required | Description |
|---|---|---|---|
typ | peac-receipt/0.1 or interaction-record+jwt | Yes | Wire format type identifier (determines Wire 0.1 or 0.2) |
alg | EdDSA | Yes | Signature algorithm (Ed25519 only) |
kid | peac-YYYY-MM | Yes | Key ID for JWKS lookup |
Wire 0.1 Payload Claims
Wire 0.1 (peac-receipt/0.1) uses flat payload claims:
| Claim | Type | Required | Description |
|---|---|---|---|
iss | string | Yes | Issuer (HTTPS URL) |
aud | string | Yes | Audience (recipient domain) |
iat | number | Yes | Issued at (Unix timestamp) |
amt | number | No | Amount |
cur | string | No | Currency code (ISO 4217) |
rail | string | No | Payment rail (x402, stripe, card, razorpay) |
reference | string | No | Settlement reference |
subject | string | No | Resource subject (URL) |
Wire 0.2 Payload Claims (Stable)
Wire 0.2 (interaction-record+jwt) introduces structured kinds, typed extensions, and policy binding:
| Claim | Type | Required | Description |
|---|---|---|---|
iss | string | Yes | Issuer (canonical: https:// or did: only) |
iat | number | Yes | Issued at (Unix timestamp) |
peac_version | string | Yes | Must be "0.2" |
kind | string | Yes | Structural kind: "evidence" or "challenge" |
type | string | Yes | Semantic type (reverse-DNS or absolute URI) |
pillars | string[] | No | Verification domains (sorted, unique, from 10-pillar taxonomy) |
extensions | object | No | Typed extension groups (keyed by reverse-DNS) |
policy | object | No | Policy binding (uri, version, digest as sha256:hex) |
occurred_at | string | No | ISO 8601 event time (evidence kind only) |
actor | object | No | Actor binding (identity proof) |
representation | object | No | Content representation fields (content_hash, content_type, content_length) |
Wire 0.2 Extension Groups
Wire 0.2 defines 12 typed extension groups. Extensions are keyed by reverse-DNS (org.peacprotocol/*) and validated with strict schemas:
| Key | Fields | Description |
|---|---|---|
org.peacprotocol/commerce | payment_rail, amount_minor (string), currency, event? (6-value enum), reference?, asset?, env?, payment_state_source? | Commerce and payment evidence. The event field is observational metadata only and does not encode settlement finality. |
org.peacprotocol/access | resource, action, decision (allow/deny/review) | Access control decisions |
org.peacprotocol/challenge | challenge_type (7 values), problem (RFC 9457) | Challenge requirements |
org.peacprotocol/identity | proof_ref? | Identity attestation |
org.peacprotocol/correlation | trace_id?, span_id?, workflow_id?, parent_jti?, depends_on? | Multi-step workflow correlation |
org.peacprotocol/consent | basis, scope, granted_at?, expires_at?, withdrawable? | Consent records |
org.peacprotocol/privacy | data_categories?, retention?, legal_basis? | Privacy and data handling evidence |
org.peacprotocol/safety | model?, guardrails?, risk_level? | AI safety and guardrail evidence |
org.peacprotocol/compliance | framework?, controls?, assessment? | Regulatory compliance evidence |
org.peacprotocol/provenance | source?, transformations?, lineage? | Data and content provenance |
org.peacprotocol/attribution | creator?, license?, attribution_text? | Content attribution and licensing |
org.peacprotocol/purpose | declared_purpose?, permitted_purposes?, restrictions? | Purpose limitation and use constraints |
Registered first-party evidence types require their mapped extension group in strict mode. Interop mode downgrades missing extensions to warnings. Challenge-kind and custom types are exempt. Unknown-but-well-formed extension keys are preserved with a warning. Malformed keys produce a hard error.
Content Hash Format
Content hashes follow a specific format for interoperability:
// Format: algorithm:hex_value "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // Requirements: // - Algorithm: sha256 (only supported algorithm) // - Value: 64 lowercase hexadecimal characters // - Total length: 71 characters (sha256: + 64 hex chars)
Example Receipts
Wire 0.1 Receipt Payload
{
"iss": "https://api.example.com",
"aud": "https://client.example.com",
"iat": 1709500000,
"amt": 100,
"cur": "USD",
"rail": "x402",
"reference": "tx_abc123",
"subject": "https://api.example.com/inference"
}Wire 0.2 Evidence Receipt Payload
{
"iss": "https://api.example.com",
"iat": 1709500000,
"peac_version": "0.2",
"kind": "evidence",
"type": "org.peacprotocol/payment",
"pillars": ["commerce"],
"extensions": {
"org.peacprotocol/commerce": {
"payment_rail": "x402",
"amount_minor": "10000",
"currency": "USD",
"event": "authorization"
}
},
"policy": {
"uri": "https://api.example.com/.well-known/peac.txt",
"version": "peac-policy/0.1",
"digest": "sha256:a1b2c3..."
}
}Wire 0.2 Challenge Receipt Payload
{
"iss": "https://api.example.com",
"iat": 1709500000,
"peac_version": "0.2",
"kind": "challenge",
"type": "org.peacprotocol/payment",
"extensions": {
"org.peacprotocol/challenge": {
"challenge_type": "payment_required",
"problem": {
"type": "https://www.peacprotocol.org/problems/payment-required",
"title": "Payment Required",
"status": 402
}
}
}
}HTTP Transport
Receipts are transmitted via the PEAC-Receipt HTTP header as a compact JWS:
# Request with receipt GET /api/resource HTTP/1.1 Host: api.example.com PEAC-Receipt: eyJhbGciOiJFZERTQSIsInR5cCI6InBlYWMtcmVjZWlwdC8wLjEiLCJraWQiOiJwZWFjLTIwMjYtMDEifQ.eyJyaWQiOiIwMUpRWEY4... # The header contains the complete JWS: # - Protected header (base64url) # - Payload (base64url) # - Signature (base64url)
Verification Process
Receipt verification follows these steps:
- Parse the JWS compact serialization
- Decode and validate the protected header
- Detect wire version from
typ:peac-receipt/0.1(Wire 0.1) orinteraction-record+jwt(Wire 0.2) - Verify
algisEdDSA - Reject embedded keys (
jwk,x5c,x5u,jku),crit,b64: false, andzip - Verify Ed25519 signature over
header.payload - Validate payload claims per wire version
- Wire 0.2: validate policy binding (3-state: verified/failed/unavailable)