👑
PromptKing Verify
Evidence Receipt Hash Specification

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_hash

4. 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.

👑 PromptKing — The Governance OS for AI Agents · www.promptking32.com
Evidence Receipt Verification Specification | PromptKing | PromptKing AI FinOps