How Receipt Verification Works

What can a third party verify independently?

Verification independence

Verification requires the receipt, the contract, the enforcement policy, and the relay's public key. It does not require the runtime, the operator, or the model. This is the central result of the protocol's design: a third party who was not present during the session can verify the receipt signature, the commitment chain, and the declared governance artefacts for the session.

"Receipt verified" must always be reported with its assurance level. A SELF_ASSERTED receipt proves the relay's stated rules were declared. A TEE_ATTESTED receipt proves hardware attestation binds the receipt to an enclave measurement. These are different security claims, and presenting them without qualification is misleading.

v2 commitments vs claims

Receipt v2 (schema version "2.1.0") organises fields into two sections that correspond to different trust properties:

Commitments are independently verifiable. A third party who holds the source artefacts can recompute each hash and confirm it matches the receipt. Commitment fields include: contract_hash, schema_hash, output_hash, input_commitments (per-participant), assembled_prompt_hash, prompt_template_hash, and the preflight_bundle (policy hash, model profile hash, schema hash, enforcement parameters).

Claims are relay assertions. The relay reports what it believes to be true, but these fields cannot be independently verified from the receipt alone. Claim fields include: model_identity_asserted (the model ID returned by the provider API), runtime_hash_asserted (SHA-256 of the relay's build), provider_latency_ms, token_usage, and budget_enforcement_mode.

This separation makes the trust model explicit. When you verify a receipt, commitments are cryptographically checkable. Claims are taken on trust — the relay asserts them but cannot prove them without additional attestation infrastructure.

Step-by-step verification walkthrough

Step 1: Check the canonicalization marker. Read receipt_canonicalization. It must be "JCS_V1". Reject any other value (fail-closed).

Step 2: Extract the signature object. Read signature.alg (must be "Ed25519") and signature.value (base64url-encoded, 64 bytes when decoded). Remove the entire signature object from the receipt.

Step 3: Canonicalize. JCS-canonicalize the remaining receipt document using RFC 8785 (sorted keys, no whitespace). Standard JSON serialisers do not produce canonical output — you must use a JCS library.

Step 4: Build the signing message. Prepend the domain separator: "VCAV-RECEIPT-V2:" + canonical_json_string. The shared receipt namespace remains VCAV-prefixed for family compatibility across AgentVault and VCAV. The colon is part of the separator. No null byte, no length prefix, no newline.

Step 5: Compute the digest. SHA-256(utf8_bytes(signing_message)). Ed25519 signs the hash, not the raw message bytes.

Step 6: Verify. Base64url-decode the signature value. Hex-decode the verifying key (32 bytes). Verify the Ed25519 signature over the digest. If verification passes, the receipt was signed by the entity holding the corresponding private key and has not been modified since signing. That proves provenance and integrity of the receipt document, not honesty of the underlying execution unless a stronger assurance tier is present.

What each commitment field proves

contract_hash — SHA-256(JCS(contract)). Proves this specific contract governed the session. A verifier who holds the contract recomputes the hash and confirms it matches.

schema_hash — SHA-256(JCS(output_schema)). Proves this specific schema constrained the output channel. The schema determines the channel capacity.

output_hash — SHA-256(JCS(output)). Proves the output has not been modified after signing. The commitments.output inline field is a convenience copy — verifiers must check that its hash matches output_hash.

input_commitments — per-participant { participant_id, input_hash, hash_alg, canonicalization }. Each participant can verify their own input commitment (recompute SHA-256(JCS(own_input)) and confirm it matches). Participants cannot verify the counterparty's commitment without the counterparty's input — this is by design.

assembled_prompt_hash — SHA-256 of the assembled prompt bytes. Proves what prompt was sent to the model. Verifying this requires access to the relay's prompt assembly implementation, as the assembly algorithm is not specified in the contract.

preflight_bundle — contains policy_hash, prompt_template_hash, model_profile_hash, schema_hash, and enforcement_parameters. This bundle represents the complete governance configuration that was locked before inference. Each field can be verified independently by hashing the source artefact.

Assurance levels

The assurance_level field describes what external evidence backs the receipt. It is not a quality score — it is a precise statement about what attestation infrastructure was active.

  • SELF_ASSERTED — The relay signs its own receipt. No external attestation. Claims are relay assertions only. This is the current default for the standard software relay. "Receipt verified" at this level means the relay operator's key signed this receipt — a provenance statement, not an integrity guarantee about the underlying execution.
  • OPERATOR_AUDITED — The operator publishes a verifiable audit trail alongside the receipt. Claims can be cross-checked against the audit log. Trust is externally checkable but still rests on the operator.
  • PROVIDER_ATTESTED — The model provider supplied signed inference metadata. model_identity_attested becomes a commitment verifiable against the provider's public key, not just a relay claim.
  • TEE_ATTESTED — Hardware TEE attestation binds the receipt to a CVM measurement. The relay build hash is hardware-verified. The operator cannot observe inputs or fabricate outputs without breaking the attestation. The tee_attestation field in the receipt carries the attestation hash, transcript hash, and receipt signing pubkey for independent verification.

What a receipt does and does not prove

A verified receipt proves:

  • The receipt was signed by the entity holding the corresponding private key.
  • The receipt content has not been modified since signing.
  • The contract, schema, policy, and prompt template bound into the receipt are the exact artefacts that the relay declared governed the session (via content hashes).
  • The bounded output hash matches the output in the receipt.
  • Each participant's input commitment was computed by the same relay that signed the receipt.

A verified receipt does not prove:

  • That inference actually occurred. A malicious relay can fabricate a conforming output, compute correct hashes, and produce a verifiable receipt.
  • That the claimed model was used. model_identity_asserted is an operator assertion at SELF_ASSERTED.
  • That enforcement code executed correctly. A compromised relay could skip enforcement and sign a receipt claiming it passed.
  • That the relay did not observe or log inputs. Correct commitment hashes do not prove inputs were not retained.
  • That both participants received the same output simultaneously.

These non-guarantees are not weaknesses to be papered over — they are architectural properties of the SELF_ASSERTED tier. Each higher assurance level progressively closes these gaps: OPERATOR_AUDITED adds external audit trails, PROVIDER_ATTESTED adds model identity verification, and TEE_ATTESTED removes the relay operator from the trust envelope entirely.

A verified receipt tells you what artefacts governed the session and that the signed record is intact. It does not, by itself, tell you whether the runtime was honest.