Independent Verification
SAR receipts are designed for offline, independent verification. No callbacks. No ongoing trust assumptions beyond the verifier’s public key — initial key resolution depends on the discovery endpoint. This page walks through the complete verification process using the canonical test vector.
The receipt to verify
This is the canonical SAR PASS test vector from the conformance fixtures:
{
"receipt_version": "0.2",
"receipt_id": "sha256:e1624bdab7c999d3c300ddcf2bc8766270e29b5ca007be7a085cd0e21baa4676",
"task_id_hash": "sha256:fixture-task-001",
"verdict": "PASS",
"confidence": 1,
"reason_code": "SPEC_MATCH",
"ts": "2026-02-14T12: 00: 00Z",
"verifier_kid": "sar-ed25519-test-01",
"sig_alg": "Ed25519",
"sig": "base64url:hXfWolp9pl9yaHoAsP2kbdGWeZYYdqq0xe3t5wefYClhv1vBxeDg5sa1MHlRkfOQNtaAz1ucPmEheyw4ZWpPDg"
}Extract and canonicalize the core fields
Extract the 6 core fields and serialize them using JCS (RFC 8785). JCS produces a deterministic byte sequence with lexicographically sorted keys — no whitespace, no trailing commas, consistent encoding.
{
"task_id_hash": "sha256:fixture-task-001",
"verdict": "PASS",
"confidence": 1,
"reason_code": "SPEC_MATCH",
"ts": "2026-02-14T12: 00: 00Z",
"verifier_kid": "sar-ed25519-test-01"
}{"confidence": 1,"reason_code": "SPEC_MATCH","task_id_hash": "sha256:fixture-task-001","ts": "2026-02-14T12: 00: 00Z","verdict": "PASS","verifier_kid": "sar-ed25519-test-01"}Note how the keys are sorted alphabetically: confidence, reason_code, task_id_hash, ts, verdict, verifier_kid. This is the JCS canonical ordering.
Derive the receipt_id
Compute the SHA-256 hash of the canonical JSON bytes. The result, prefixed with "sha256:", is the receipt_id.
Input: canonical JSON bytes
166 bytes of UTF-8 encoded JCS output
Output: receipt_id
sha256:e1624bdab7c999d3c300ddcf2bc8766270e29b5ca007be7a085cd0e21baa4676Comparison: Does the computed receipt_id match the claimed receipt_id in the receipt?
Resolve the public key
Look up the verifier's public key using the verifier_kid field. Keys are published at the verifier's well-known endpoint.
Key ID to resolve
sar-ed25519-test-01Discovery endpoint
GET https://verifier.example.com/.well-known/sar-keys.json
// Response:
{
"keys": [
{
"kid": "sar-ed25519-test-01",
"kty": "OKP",
"crv": "Ed25519",
"x": "<base64url-encoded-32-byte-public-key>"
}
]
}Find the key entry where kid matches verifier_kid. Decode the x field from base64url to get the 32-byte Ed25519 public key.
Verify the Ed25519 signature
Verify the Ed25519 signature over the 32-byte SHA-256 digest using the resolved public key.
Inputs
The signature is valid. The receipt was signed by the holder of the private key corresponding to sar-ed25519-test-01.
Verification result
Receipt verified
1. Core fields are intact (receipt_id matches recomputed hash)
2. Signature is valid (Ed25519 verification passed)
3. Verdict: PASS with confidence 1.0
What verification proves
Verification proves
- ✓The core fields have not been modified since signing
- ✓The receipt was signed by the holder of the claimed key
- ✓The receipt_id is deterministically derived from the core fields
Verification confirms receipt integrity and signer identity. Whether the underlying verdict is correct depends on the verifier’s evaluation policy — that judgment lives above the receipt layer.
Verdict semantics
All settlement conditions were met. The delivered result satisfies the task specification.
Example: A code generation task where the output compiles, passes tests, and matches the acceptance criteria.
One or more settlement conditions were not met.
Example: A translation task where the output is in the wrong language or exceeds the specified word count.
Settlement conditions could not be fully evaluated.
Example: A verification that timed out before completing, or where required evidence was unavailable.
Verify with the SDK
The SAR SDK implements the complete verification flow in TypeScript:
import { verifyReceipt, resolveKidFromWellKnown } from 'sar-sdk';
const receipt = { /* ... SAR receipt ... */ };
// Create a key resolver that fetches from well-known endpoint
const resolveKey = (kid) =>
resolveKidFromWellKnown('https://verifier.example.com', kid);
// Verify — throws on failure, returns true on success
const valid = await verifyReceipt(receipt, resolveKey);