Skip to main content
Version: v0.12.4

Verification

PEAC verification is offline and deterministic. Given a receipt, the issuer's public key, and optionally a policy digest, verification produces the same result every time with no external dependencies.

Zero network calls required

Verification runs entirely locally. The verifier needs only the receipt (a JWS string) and the issuer's public key (Ed25519). No callback to the issuer, no token introspection endpoint, no online dependency.


Verification checks

The verifier runs a sequence of checks against each receipt. verifyLocal() auto-detects wire version and applies the appropriate checks.

Common checks (both wire formats)

#CheckDescription
1Signature validEd25519 signature verification against the issuer's public key
2Algorithm allowedAlgorithm must be EdDSA (no other algorithms accepted)
3Type header presenttyp must be peac-receipt/0.1 or interaction-record+jwt
4Key ID presentkid must exist in the JWS header
5Claims well-formedAll required claims present and correctly typed
6Issued-at validiat is a valid Unix timestamp

Wire 0.2 additional checks

#CheckDescription
7JOSE hardeningRejects embedded keys (jwk, x5c, x5u, jku), crit, b64: false, zip
8Issuer canonicaliss must be canonical (https:// or did: only)
9Kind validkind must be evidence or challenge
10Type grammartype must be valid reverse-DNS or absolute URI
11Pillars validAll pillars must be from the 10-pillar taxonomy, sorted and unique
12Extension schemasKnown extension groups pass their registered schema validation
13Occurred-at coherenceoccurred_at only allowed on evidence kind
14Policy bindingPolicy digest comparison (3-state: verified, failed, unavailable)

Basic verification

Pass a receipt JWS string and the issuer's public key. verifyLocal() auto-detects wire version.

verify.ts
import { verifyLocal } from '@peac/protocol';

const result = verifyLocal(receiptJWS, publicKey);

if (result.valid) {
console.log('Wire version:', result.wireVersion); // '0.1' or '0.2'
console.log('Claims:', result.claims);
console.log('Policy binding:', result.policy_binding);

if (result.wireVersion === '0.2') {
console.log('Kind:', result.claims.kind);
console.log('Type:', result.claims.type);
console.log('Warnings:', result.warnings);
}
} else {
console.log('Verification failed:', result.reason);
console.log('Error code:', result.code);
}

With policy binding (Wire 0.2)

For Wire 0.2 receipts, provide a policy digest to verify policy binding.

verify-with-policy.ts
import { verifyLocal, computePolicyDigestJcs } from '@peac/protocol';

// Compute digest from your policy document
const policyDigest = await computePolicyDigestJcs(policyDocument);

const result = verifyLocal(receiptJWS, publicKey, {
policyDigest,
});

if (result.valid) {
console.log('Policy binding:', result.policy_binding);
// 'verified' -- receipt policy matches local policy
// 'unavailable' -- either receipt or local policy digest absent
}

Policy binding returns one of three states:

StateMeaning
verifiedReceipt policy digest matches local policy digest
failedDigests present but do not match (verification fails)
unavailableEither receipt or local policy digest is absent

Result types

Success result

VerifySuccess
interface VerifySuccess {
valid: true;
wireVersion: '0.1' | '0.2';
claims: Wire01Claims | Wire02Claims;
policy_binding: 'verified' | 'failed' | 'unavailable';
warnings: VerificationWarning[]; // Wire 0.2 only
}

Failure result

VerifyFailure
interface VerifyFailure {
valid: false;
reason: string;
code: string; // E_INVALID_SIGNATURE, E_ISS_NOT_CANONICAL, etc.
}

VerificationWarning (Wire 0.2)

Wire 0.2 introduces advisory warnings that do not fail verification:

VerificationWarning
interface VerificationWarning {
code: string; // Warning code (e.g., 'type_unregistered')
message: string; // Human-readable description
pointer?: string; // RFC 6901 JSON pointer (e.g., '/type')
}
Warning codeMeaning
type_unregisteredReceipt type is not in the recommended type registry
unknown_extension_preservedUnknown extension key preserved without schema validation
typ_missingtyp header absent (interop mode only)
occurred_at_skewoccurred_at differs significantly from iat

CLI verification

Verify a receipt directly from the command line.

Terminal: verify with local keys
npx peac verify <receipt-jws> --jwks keys.json
Terminal: verify with issuer URL
npx peac verify <receipt-jws> --issuer https://api.example.com

The CLI outputs a structured result with pass/fail status for each check.


MCP server verification

AI agents using the PEAC MCP server can verify receipts via the peac_verify tool.

MCP Tool Call: peac_verify
{
"tool": "peac_verify",
"arguments": {
"receipt": "<jws-string>",
"issuer_url": "https://api.example.com"
}
}

The response includes the full verification result, decoded claims, wire version, and server metadata in the _meta field.


Next steps