This page documents the canonical algorithm for computing and verifying PromptKing Evidence Receipt hashes. Any third party can use this specification to independently verify a receipt without our tools.
1. Canonical Serialization
The hash input is a JSON string of 22 fields in alphabetical order, extracted from the receipt's raw payload. Missing fields are set to null.
const HASH_KEYS = [
"actual_cost_usd", "agent_id", "agent_name", "autonomy_tier",
"correlation_id", "declared_estimate_usd", "halt_breach_detected",
"halt_compliance_at", "halt_compliance_status", "intent_declaration",
"issued_at", "org_id", "outcome_report_id", "policy_evaluation_id",
"policy_verdict", "prepared_action", "provenance_lane",
"receipt_version", "run_id", "spiffe_id", "trajectory_id", "variance_pct"
]
// Build canonical object
const canonical = {}
for (const k of HASH_KEYS) canonical[k] = payload[k] ?? null
// Serialize
const input = JSON.stringify(canonical)JSON.stringify produces deterministic output for objects with string keys in insertion order — and since we insert in alphabetical order, the result is reproducible.
2. Hash Algorithm
SHA-256 digest of the UTF-8-encoded JSON string. Output prefixed with sha256: followed by lowercase hexadecimal.
3. Worked Example
// Input fields (fabricated data, not a real receipt):
{
"actual_cost_usd": 0.05,
"agent_id": "4e3d2fb7-d67a-4186-aa67-773b69c1e0b9",
"agent_name": "example-agent-001",
"autonomy_tier": "supervised",
"correlation_id": "corr-example-001",
"declared_estimate_usd": 0.04,
"halt_breach_detected": false,
"halt_compliance_at": null,
"halt_compliance_status": "not_applicable",
"intent_declaration": "test verification",
"issued_at": "2026-07-01T12:00:00.000Z",
"org_id": "a1b2c3d4-0001-0001-0001-000000000001",
"outcome_report_id": "abc12345-0000-0000-0000-000000000001",
"policy_evaluation_id": null,
"policy_verdict": "allowed",
"prepared_action": null,
"provenance_lane": "CLIENT_ZERO",
"receipt_version": "governed-run-v1",
"run_id": "run-example-001",
"spiffe_id": "spiffe://promptking/agent/example",
"trajectory_id": null,
"variance_pct": 25
}
// JSON.stringify produces the canonical string.
// SHA-256 of that string = the receipt hash.
// Verify: computed hash === stored receipt_hash4. JavaScript Verifier (Browser)
async function verifyReceipt(hashInputs, storedHash) {
const HASH_KEYS = [
"actual_cost_usd","agent_id","agent_name","autonomy_tier",
"correlation_id","declared_estimate_usd","halt_breach_detected",
"halt_compliance_at","halt_compliance_status","intent_declaration",
"issued_at","org_id","outcome_report_id","policy_evaluation_id",
"policy_verdict","prepared_action","provenance_lane",
"receipt_version","run_id","spiffe_id","trajectory_id","variance_pct"
];
const canonical = {};
for (const k of HASH_KEYS) canonical[k] = hashInputs[k] ?? null;
const input = JSON.stringify(canonical);
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
const hex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2,"0")).join("");
const computed = "sha256:" + hex;
return computed === storedHash ? "VERIFIED" : "MISMATCH";
}5. Python Verifier
import hashlib, json
def verify_receipt(hash_inputs: dict, stored_hash: str) -> str:
HASH_KEYS = [
"actual_cost_usd","agent_id","agent_name","autonomy_tier",
"correlation_id","declared_estimate_usd","halt_breach_detected",
"halt_compliance_at","halt_compliance_status","intent_declaration",
"issued_at","org_id","outcome_report_id","policy_evaluation_id",
"policy_verdict","prepared_action","provenance_lane",
"receipt_version","run_id","spiffe_id","trajectory_id","variance_pct"
]
canonical = {k: hash_inputs.get(k) for k in HASH_KEYS}
input_str = json.dumps(canonical, separators=(",", ":"))
# NOTE: Python json.dumps default separators include a space after ':'
# Use separators=(',', ':') to match JavaScript JSON.stringify
computed = "sha256:" + hashlib.sha256(input_str.encode("utf-8")).hexdigest()
return "VERIFIED" if computed == stored_hash else "MISMATCH"Important: Python's json.dumps default separators include spaces. Use separators=(",", ":") to match JavaScript's JSON.stringify output exactly.
6. What Hashes Prove (and Don't Prove)
SHA-256 hashes prove integrity — that the receipt data has not been altered since the hash was computed.
They do not prove authenticity (that PromptKing issued this receipt) or non-repudiation (that PromptKing cannot deny issuing it).
Cryptographic signing is on our roadmap and will provide those guarantees when shipped.
7. Fields Excluded from Hash
enforcement_ack — Additive field (v4.75.0). Excluded so that recording enforcement status never retroactively invalidates existing receipt hashes.
trace_id — Internal correlation added at v4.74.0. Excluded to preserve the hash boundary established when receipts launched at v4.62.0.