contracts: Receipt v1 schema, canonicalization, verify rules, test vectors

This commit is contained in:
Vault Sovereign
2025-12-26 23:13:34 +00:00
commit a43526f768
7 changed files with 299 additions and 0 deletions

26
canonicalization.md Normal file
View File

@@ -0,0 +1,26 @@
# Receipt Canonicalization (v1)
This locks the bytes that `blake3` and `sha256` cover for every receipt envelope across vm-cloud, vaultmesh-mcp, command-center, and civilization-ledger.
## Canonical JSON
- Start from the JSON object defined in `receipt_v1.schema.json` **excluding signature metadata** (`sig_alg`, `signer_pub`, `signature`, `signed_at`).
- Remove any fields whose value is `undefined` (nulls are allowed and kept).
- Recursively sort object keys lexicographically.
- Arrays keep order; elements are canonicalized recursively.
- Serialize with JSON `UTF-8` bytes, no trailing newline. (Equivalent to `JSON.stringify(canonicalize(obj))` in vm-cloud.)
## Hash inputs
- `blake3` = `BLAKE3(canonical_json_bytes)` hex-encoded (lowercase).
- `sha256` = `SHA-256(canonical_json_bytes)` hex-encoded (lowercase).
- `hash_alg` MUST be the literal string `"blake3+sha256"` to prevent algorithm drift.
## Excluded from hash
- `sig_alg`, `signer_pub`, `signature`, `signed_at` are **not** part of the hashed body. They are attached after hashing and do not change the receipt body digest.
## File naming & storage (non-binding but recommended)
- Store receipts under `outputs/receipts/` (vm-cloud) or `receipts/mcp/` (vaultmesh-mcp) using timestamped filenames, e.g., `YYYYMMDD-HHMMSS-action-target.json`.
- Maintain a `HEAD.json` (or similar) that tracks the latest `{ blake3, file, created_at }` to support chain continuity checks.

89
receipt_v1.schema.json Normal file
View File

@@ -0,0 +1,89 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://vm/contracts/receipt_v1.schema.json",
"title": "VaultMesh Receipt v1",
"description": "Operator-grade receipt envelope shared by vm-cloud, vaultmesh-mcp, command-center, civilization-ledger.",
"type": "object",
"properties": {
"receipt_version": { "type": "string", "const": "1" },
"created_at": { "type": "string", "format": "date-time" },
"source": { "type": "string", "description": "Emitter identity, e.g., vmc, mcp, command-center" },
"action": { "type": "string" },
"reason": { "type": "string" },
"target": {
"type": "object",
"properties": {
"id": { "type": ["string", "number" ] },
"name": { "type": "string" },
"ip": { "type": ["string", "null"] }
},
"additionalProperties": false
},
"request": {
"type": "object",
"properties": {
"method": { "type": "string" },
"path": { "type": "string" },
"body": { }
},
"required": ["method", "path"],
"additionalProperties": false
},
"response": {
"type": "object",
"properties": {
"status": { "type": "integer" },
"ok": { "type": "boolean" },
"data": { },
"raw": { "type": "string" }
},
"required": ["status", "ok", "raw"],
"additionalProperties": false
},
"prev_blake3": { "type": ["string", "null"], "description": "Chain link to previous receipt blake3" },
"hash_alg": { "type": "string", "const": "blake3+sha256" },
"blake3": { "type": "string" },
"sha256": { "type": "string" },
"plan_file": { "type": ["string", "null"] },
"plan_blake3": { "type": ["string", "null"] },
"plan_sha256": { "type": ["string", "null"] },
"lock_file": { "type": ["string", "null"] },
"lock_started_at": { "type": ["string", "null"] },
"force": { "type": "boolean" },
"cwd": { "type": "string" },
"user": { "type": "string" },
"hostname": { "type": "string" },
"argv": { "type": "array", "items": { "type": "string" } },
"sig_alg": { "type": "string", "enum": ["ed25519"] },
"signer_pub": { "type": "string" },
"signature": { "type": "string" },
"signed_at": { "type": "string", "format": "date-time" }
},
"required": [
"receipt_version",
"created_at",
"source",
"action",
"request",
"response",
"prev_blake3",
"hash_alg",
"blake3",
"sha256"
],
"additionalProperties": false,
"allOf": [
{
"if": { "properties": { "sig_alg": { "const": "ed25519" } }, "required": ["sig_alg"] },
"then": { "required": ["signer_pub", "signature"] }
}
]
}

14
test_vectors/README.md Normal file
View File

@@ -0,0 +1,14 @@
# Receipt v1 Test Vectors
Each receipt JSON file is a full v1 envelope with `blake3` and `sha256` set to the
expected canonical hashes.
`manifest.json` repeats the expected canonical digests for convenience.
Signature fields in `receipt_v1_signed.json` are placeholders and are not
expected to verify unless explicitly noted in `manifest.json`.
Canonical bytes are computed by:
- removing hash and signature fields (`hash_alg`, `blake3`, `sha256`, `sig_alg`,
`signer_pub`, `signature`, `signed_at`)
- recursively sorting object keys
- serializing as compact UTF-8 JSON (no trailing newline)

View File

@@ -0,0 +1,16 @@
{
"version": 1,
"vectors": [
{
"file": "receipt_v1_basic.json",
"canonical_blake3_hex": "be1bf3fd91c6b78220ebd0c6a6d330bca04d3aa0de318e515bc1dbd5c9f64506",
"canonical_sha256_hex": "43691cfe210f42b4b2fc4eb9b39a4bf8d35d75c2e42805d3f42aa89ad2d04aa5"
},
{
"file": "receipt_v1_signed.json",
"canonical_blake3_hex": "7b25a1668e700c0096dcca33467b89d22cdf7ea635d59f3c692e97153459672a",
"canonical_sha256_hex": "237c298f88821d4938f8909012dd8128d062245e480285d605ad146a868bec14",
"signature_valid": false
}
]
}

View File

@@ -0,0 +1,51 @@
{
"action": "servers.labels",
"receipt_version": "1",
"created_at": "2025-01-01T00:00:00.000Z",
"source": "vmc",
"reason": "test vector basic",
"target": {
"name": "alpha",
"ip": "1.2.3.4",
"id": 123
},
"request": {
"path": "/servers/123",
"method": "PUT",
"body": {
"labels": {
"b": "2",
"a": "1"
}
}
},
"response": {
"ok": true,
"status": 200,
"data": {
"result": "ok"
},
"raw": "{\"result\":\"ok\"}"
},
"prev_blake3": null,
"hash_alg": "blake3+sha256",
"blake3": "be1bf3fd91c6b78220ebd0c6a6d330bca04d3aa0de318e515bc1dbd5c9f64506",
"sha256": "43691cfe210f42b4b2fc4eb9b39a4bf8d35d75c2e42805d3f42aa89ad2d04aa5",
"plan_file": null,
"plan_sha256": null,
"plan_blake3": null,
"lock_file": null,
"lock_started_at": null,
"force": false,
"cwd": "/tmp/vm",
"user": "tester",
"hostname": "vector-host",
"argv": [
"vmc",
"servers",
"labels",
"alpha",
"a=1",
"b=2"
]
}

View File

@@ -0,0 +1,61 @@
{
"reason": "test vector signed",
"receipt_version": "1",
"created_at": "2025-01-02T03:04:05.000Z",
"source": "mcp",
"action": "plans.apply",
"target": {
"id": "srv-9",
"ip": null,
"name": "beta"
},
"request": {
"method": "POST",
"path": "/apply",
"body": {
"plan": {
"steps": [
{
"value": "prod",
"path": "/labels/env",
"op": "set"
},
{
"path": "/labels/tmp",
"op": "delete"
}
]
}
}
},
"response": {
"data": {
"applied": true
},
"status": 201,
"ok": true,
"raw": "{\"applied\":true}"
},
"prev_blake3": null,
"hash_alg": "blake3+sha256",
"blake3": "7b25a1668e700c0096dcca33467b89d22cdf7ea635d59f3c692e97153459672a",
"sha256": "237c298f88821d4938f8909012dd8128d062245e480285d605ad146a868bec14",
"plan_file": "plans/test.json",
"plan_blake3": "1111111111111111111111111111111111111111111111111111111111111111",
"plan_sha256": "2222222222222222222222222222222222222222222222222222222222222222",
"lock_file": "/tmp/vm.lock",
"lock_started_at": "2025-01-02T03:00:00.000Z",
"force": true,
"cwd": "/work",
"user": "builder",
"hostname": "vector-host",
"argv": [
"mcp",
"apply",
"--force"
],
"sig_alg": "ed25519",
"signer_pub": "8ac2f516b7138e99a183e56196d3f332e44ff15d8fd388f1807db0db5dc2eaac",
"signature": "041e36a549623bd8e342bf8363f466e7d49de6de673ab4747bd3f4b2396e075f8b3f311293f64fd1faf6e8c49b267808e3542095adad87f74bf1e2722d629605",
"signed_at": "2025-01-02T03:04:06.000Z"
}

42
verify_rules.md Normal file
View File

@@ -0,0 +1,42 @@
# 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 receipts `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 receipts `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`.