# Receipt Verification Rules (v1) Applies to receipts conforming to `receipt_v1.schema.json`. ## Receipt verification A verifier MUST: 1. Parse JSON and validate against the schema. 2. Recompute `canonical_json_bytes` (per `canonicalization.md`) **excluding** signature fields. 3. Recompute `blake3` and `sha256`; require they match the envelope fields. 4. If signature fields are present: - Require `sig_alg == "ed25519"`. - Verify `signature` over UTF-8 bytes of the **hex `blake3` string** (vm-cloud convention). - Require `signer_pub` matches verification key. 5. Return success only if all checks pass. ## Chain verification - `prev_blake3` forms a singly-linked chain of receipts. - If a `HEAD` file is maintained, verify: - `HEAD.created_at` is ISO-8601. - `HEAD.blake3` matches the `blake3` of the latest receipt. - The latest receipt’s `prev_blake3` matches the previous `HEAD.blake3` (or null for genesis). - For a sequence of receipts, iterate in order and ensure every `prev_blake3` matches the prior receipt’s `blake3`; fail on the first discontinuity. ## Merkle receipts (for external checkpoints) When receipts are batched into Merkle proofs: - **Input ordering**: preserve the chronological order of receipts as leaves (stable, prefix-based trees). - **Leaf hashing**: `leaf = BLAKE3("VM-receipt-leaf-v1" || blake3_bytes)` where `blake3_bytes` is the 32-byte digest of the receipt body (not hex string; decode hex to bytes). - **Node hashing**: `node = BLAKE3("VM-receipt-node-v1" || left || right)`. - **Odd count rule**: duplicate the last node at each level when odd (standard Merkle padding). - **Root format**: hex-encoded lowercase BLAKE3 of the final root node. - **Inclusion proof**: sibling list from leaf → root, each sibling annotated with side ("left" | "right"). ## Fail-closed guidance - Any missing or invalid `session_token` in tool-executed receipts SHOULD be treated as unverifiable and rejected by consumers. - Signature presence is optional, but if present must verify; otherwise treat as invalid. - Reject receipts whose `hash_alg` is not exactly `blake3+sha256`.