# Receipt digest derivation — engine 1.0.0

This describes the existing PactVerity algorithm, not a new standard or a full independent implementation. The normative implementation is `lib/proof-engine.ts`; the structural schema is `/specs/pactverity-assurance-receipt-v1.schema.json`.

1. Validate exactly the supported receipt fields and nested fields. Normalize agreement and evidence using the engine rules. The engine normalizes human-readable text to NFC and trims it, requires canonical UTC timestamps with milliseconds, and validates bounded integers and hashes. Verify the recorded checks and outcome against checks recomputed from normalized terms/evidence. An externally supplied digest alone is never sufficient.
2. Construct the payload with `schema`, `engineVersion`, `issuedAt`, `agreement`, `evidence`, recomputed `checks`, and recomputed `outcome`. Exclude `payloadHash` and `walletSignature`.
3. Serialize recursively with no spaces: null/strings/booleans use ECMAScript `JSON.stringify`; numeric values must be safe integers and serialize in base 10; arrays retain their order; object keys sort in ECMAScript default UTF-16 code-unit order, each JSON-quoted key followed by a colon and its serialized value. Reject unsupported values. This is the project's canonicalization algorithm, not a claim of RFC 8785 compliance.
4. Prepend the exact ASCII domain `PVTY-LAYER/VERIFICATION-RECEIPT/V1`, followed by one LF byte (`0a`). The legacy domain remains unchanged for receipt compatibility. There is no trailing newline after the serialized payload.
5. UTF-8 encode the complete string, SHA-256 hash the bytes, and format the result as 64 lowercase hexadecimal characters. Compare this to `payloadHash`.

The top-level sorted field order is `agreement`, `checks`, `engineVersion`, `evidence`, `issuedAt`, `outcome`, `schema`. Nested objects follow the same key-sort rule. Check array order is latency, uptime, freshness, output_hash. Latency and freshness compare actual <= expected; uptime compares actual >= expected; output_hash compares equality, or is not_evaluated when no expected hash is supplied. Any failed check makes the outcome fail. The full engine and schema define normalization, range limits and missing-field rejection.

## Independent digest check of the included synthetic vector

`synthetic-hash-input.txt` contains the exact preimage bytes, without a trailing newline. Hash this file using an ordinary SHA-256 tool and compare with `synthetic-pass.json`'s `payloadHash`:

```sh
sha256sum synthetic-hash-input.txt
```

Windows PowerShell:

```powershell
Get-FileHash -Algorithm SHA256 -LiteralPath ./synthetic-hash-input.txt
```

This independently checks one digest calculation, not the complete schema/rule/signature implementation. SHA256SUMS also records the preimage hash for comparison. The fixture is deliberately synthetic.

## Signature scope

The Ed25519 message is UTF-8 of `PACTVERITY-RECEIPT-V1`, one LF, and the normalized payloadHash. The public key is decoded from the Solana-format address; the signature is 64 bytes decoded from base64. The existing engine uses strict Ed25519 verification with ZIP-215 acceptance disabled. The signature envelope is not included in payloadHash. A valid signature from an arbitrary key does not establish the expected issuer: use `--expect-signer` with an independently trusted key.

Neither a digest nor a signature proves source honesty, measurement completeness, service performance or when data existed. Git author/committer timestamps are self-asserted, not independent timestamp evidence.
