Specification · Markdown source · Edit on GitHub · CC0 1.0

VRP Receipt Envelope — Specification v1

Status: Public draft

Updated: 2026-07-04 — §14 issuance wrapper & log anchoring, §15 delivery (verbatim rule), §16 verifier walk-through

Published: 2026-06-24 (revised 2026-07-04)

Envelope version: 1.0

Repository: https://github.com/vacationrentalprotocol/vrp-spec

Machine-readable schema: schemas/vrp-receipt.v1.schema.json

Reference verifier: lib/vrp-receipt.mjs (Apache-2.0)

1. Scope

VRP Receipt v1 defines a versioned verifiable booking record: a flat array of signed attestations from independent trust layers (offer, transport, payment, …) assembled by a host node. It composes the existing VRP signed stay offer (v0.1.md) and composition profiles (profiles/mcp-composition-profile.md) without making HemmaBo or any operator a gatekeeper.

This specification is not a runtime tool surface, OTA, marketplace, central registry, or booking intermediary. It does not change Portable Attestations v0.1 (attestations-v0.1.md) — especially not its §3 trust model.

The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are interpreted as in RFC 2119 and RFC 8174 when capitalized.

2. References

3. Envelope shape (D1)

A v1 receipt MUST be a JSON object with:

FieldRequiredDescription
vrp_receipt_versionMUSTExactly "1.0" for v1 verifiers
subjectMUSTWhat the receipt is about (property, stay window, offer id — generic object)
issuerMUSTNode identity that assembled the receipt
attestationsMUSTFlat array; min length 1

Each attestation object MUST include:

FieldRequiredDescription
layerMUSTOpen vocabulary string, e.g. offer, transport, payment
valid_fromMUSTISO 8601 date-time (D2)
valid_untilMUSTISO 8601 date-time (D2)

Each attestation object MAY include:

FieldDescription
sourceURL where the verifying public key is published (typically host JWKS)
signatureCompact JWS over the layer artifact (D5)
refOpaque correlator (offer id, transaction id, …)
tlogOptional transparency-log inclusion proof (D3)
sub_receiptReserved v2 — recursive sub-receipt; v1 verifiers MUST NOT recurse
disclosureReserved v2 — selective-disclosure pointer; v1 verifiers MUST NOT interpret

A new trust layer = a new attestations[] entry. No central approval is required.

4. Per-attestation freshness (D2)

Every attestation MUST carry valid_from and valid_until. A v1 verifier MUST reject an attestation whose verification time is before valid_from (not_yet_valid) or after valid_until (sig_expired) after the JWS signature is validated.

Freshness is evaluated per attestation, not once for the whole receipt.

5. Transparency log field (D3)

Each attestation MAY carry a tlog inclusion proof. A v1 verifier MUST:

Receipts kept as dispute or insurance evidence that must survive key revocation MAY require tlog (see §10 K4).

6. Verification result and error registry (D4)

A v1 verifier MUST return partial verification: per-attestation status, not a single boolean. Envelope-level fields:

FieldMeaning
receipt_validStructurally valid v1 receipt (version + schema)
fully_verifiedtrue only when every attestation has status verified
attestations[]Per-layer { index, layer, status, error, kid }
errors[]Envelope-level codes when receipt_valid is false

Per-attestation status values:

StatusMeaning
verifiedSignature valid and inside freshness window
expiredSignature valid but outside freshness window
invalidSignature check failed (e.g. sig_invalid)
unverifiableLayer present but cannot be checked (missing signature or key)

unverifiable is distinct from verified. A receipt MUST NOT represent an unverifiable layer as verified.

6.1 Normative error codes (v1)

Envelope / signature level:

VRP offer-layer (reused from core offer verification):

AP2 payment-layer (reused from AP2 mandate verification):

The reference verifier in lib/vrp-receipt.mjs implements the envelope-level codes above for v1 receipt verification. Layer-specific offer/AP2 codes apply when a profile-aware verifier interprets attestation payloads.

7. Signature input rule (D5)

The signature MUST be verified over the compact-JWS bytes as received. A v1 verifier MUST NOT re-canonicalize JSON to re-derive the signing input.

Where canonical JSON is required for non-JWS correlators (e.g. MCP arguments_sha256), implementations MUST use JCS (RFC 8785). That canonicalization applies only to the hash input — not to JWS verification. See profiles/mcp-composition-profile.md.

8. Composition profiles (D6)

Composition profiles define how an external interaction becomes an attestation entry. The first normative profile is MCP transport — see profiles/mcp-composition-profile.md.

Profiles MUST sign an assertion about the interaction, not unsigned transport bytes. Additional profiles (e.g. AP2 payment) follow the same envelope rules.

8.1 Argument key casing

For MCP transport assertions, tool-call argument keys that feed arguments_sha256 MUST be snake_case: check_in, check_out, guests — matching core VRP endpoint parameters (v0.1.md §4). This is distinct from camelCase booking URL query parameters (v0.1.md §5.1). Normalization before hashing is the caller's responsibility; see the normative pin in the MCP profile.

9. Key-discovery layer separation

VRP uses two key-discovery paths. Implementers MUST NOT conflate them:

LayerArtifactKey discoveryTypical kid form
Offer + receipt attestationsCompact JWS (offer, transport, …)Fetch https://{host}/.well-known/jwks.json directly (v0.1.md §3)Plain string kid matched against JWKS
Portable Attestations (VC)application/vc+jwt credentialsResolve issuer did:web:{host} → https://{host}/.well-known/did.json (attestations-v0.1.md §3)DID URL kid on verification method

The live reference verifier for receipt v1 resolves keys via attestation.source → JWKS and matches JWS kid — it does not resolve did:web documents for receipt layers. Portable Attestations remain a separate verification path with their own conformance vectors (examples/conformance/attestations/).

Offer signatures and attestation VC signatures MAY use separate private keys as long as each verifying key is published under the host-domain trust root (attestations-v0.1.md §3 — unchanged by this document).

10. Key lifecycle — forward spec (K1–K6)

The v1 receipt verifier implements signature + freshness only. The following rules are forward spec for receipts kept as dispute or insurance evidence (ADR 0008). They do not change v1 verifier behavior today.

RuleSummary
K1Every signature carries JWS kid; receipts surface verifying kid per attestation
K2JWKS MUST retain retired public keys for the receipt-retention horizon; metadata: vrp_key_status (active · retired · revoked), vrp_not_before, vrp_not_after
K3Rotation is additive with overlap — retired keys remain resolvable
K4Revocation ≠ rotation; vrp_compromised_at anchors fail-closed rules; receipts intended to survive revocation MUST carry tlog
K5Per-layer key separation is permitted (payment MAY use a distinct kid from offer)
K6Lifecycle-aware codes: key_retired_out_of_window, key_revoked, tlog_required

Until a lifecycle-aware verifier ships, public copy MUST NOT claim long-term post-revocation verifiability.

11. Licensing (D7)

12. Conformance

Conformance vectors live in examples/conformance/receipt/. They use a shared throwaway Ed25519 test key (kid: vrp-vectors-2026-01-01-01) — never a production host key (D6 test isolation).

Every npm test run executes scripts/verify-receipt-vectors.mjs, which MUST pass at minimum:

VectorExpectation
01-offer-transport-verified.jsonfully_verified: true; offer + transport both verified
03-tampered-signature.jsonManipulated JWS byte → sig_invalid; fully_verified: false

Additional vectors cover partial verification (02), expiry (04), unsupported version (05), and malformed envelope (06).

Implementers SHOULD treat vector output equality with lib/vrp-receipt.mjs as the interoperability bar for v1 receipt verification.

13. Neutrality (D8)

The spec, schema, and vectors are vendor-neutral. HemmaBo is a reference implementer, not an approval authority. Composition profiles are published, not centrally granted.

14. Issuance wrapper and log anchoring (envelope level)

An issued receipt MUST be delivered as a compact JWS signed by the issuing node's key — the same did:web-published key material as the node's other VRP artifacts — whose payload is the v1 envelope (§3). This wrapper is what makes the receipt log-anchorable at the envelope level:

The wrapper does not alter the envelope's trust semantics: attestations inside the receipt still verify per layer (§6), and log inclusion never makes an invalid attestation valid (Transparency Log §8).

15. Delivery (normative)

Because the log leaf binds the receipt's exact bytes (§14), delivery MUST preserve them:

16. Proving a receipt against the log (verifier walk-through)

Any party can execute this procedure with the receipt and public interfaces only — no credentials, no contacting the node's operator:

  1. Obtain the receipt wrapper JWS verbatim (§15).
  2. Verify the wrapper signature against the issuing node's published keys (did:web / JWKS). This proves who issued the receipt and that its content is intact.
  3. Compute sha256:{hex} over the exact JWS string. This is the leaf identity.
  4. Fetch the inclusion proof and an STH for that hash — from the node's read-through proxy or from the log operator directly; the two surfaces are interchangeable because of step 5.
  5. Verify the STH signature against the log operator's published key (Transparency Log §5.1) — never against a key the node serves.
  6. Recompute the Merkle root from the leaf hash, leaf index, tree size, and audit path (RFC 6962 §2.1.1). The inclusion claim holds iff the recomputed root equals the STH's root_hash; a failed recomputation falsifies it.
  7. To additionally remove trust in the log operator, obtain a second tree head (archived, Transparency Log §7.1, or independently observed) and verify the consistency proof between the two tree sizes.

What this proves, in the layered formulation of Transparency Log §8.1: the exact promised terms — price, conditions, issuer, time — existed and were recorded in the log no later than the STH timestamp, tamper-evidently; steps 1–6 require no trust in the node, and step 7 removes the remaining trust in the log operator by making any rewrite cryptographically detectable.

17. License

Specification text: dedicated to the public domain under CC0 1.0. Reference code and conformance test vectors: Apache-2.0 (ADR 0007 D7).