#!/usr/bin/env python3 """ verify_proofpack.py - standalone verifier for PriviNet Lumra ProofPacks. Verifies a ProofPack with NO PriviNet servers, accounts, or trust involved: 1. Recomputes SHA-256 over the canonical JSON of every hashed object in canonical_inputs and compares each to the digests in the proof record. 2. Recomputes the composite event digest with its domain-separation context string and compares it to the stored event_hash. 3. Verifies the Ed25519 signature over the event_hash with the public key embedded in the pack. Usage: python verify_proofpack.py PROOFPACK-xxxxxxxx-YYYY-MM-DD.json Dependencies: Python 3.9+ and ONE of: pip install cryptography (preferred) pip install pynacl (alternative) Steps 1 and 2 run with the standard library alone; only step 3 (the signature) needs a crypto library. Exit code 0 means every check passed. Any failure exits 1 and says why. Any change to a sealed value makes this script fail. Hashes are computed over canonical content (sorted keys, fixed separators), so cosmetic JSON whitespace is normalized away and is not part of the seal; every meaningful value is. IMPORTANT, WHAT "VERIFIED" MEANS: this script proves the records in the pack are internally consistent and were signed by whatever key is inside the pack. That is integrity, not provenance. To know the pack was signed by PriviNet (and not by a forger who put their own key in the file), the signing key must match PriviNet's published key. This script pins the known PriviNet software signing key below and warns loudly on a mismatch. Independently confirm the key at https://privinet.net/keys.txt """ from __future__ import annotations import hashlib import json import sys # PriviNet's published signing keys. Ed25519 keys are 32-byte hex; the KMS # HSM key is an ECDSA P-256 public key as a 65-byte uncompressed point (hex, # leading 04). Confirm this list out-of-band at https://privinet.net/keys.txt # before relying on a "signed by PriviNet" conclusion. PRIVINET_PUBLIC_KEYS = { "04bc55de97e806fbeaf95298588a87cf7f1404640f7b9d359fbbc0f1b2bf408f8be837d1cfa641778a11fd9776be566a2c938b8891d29a7182c15500a05b2e6655": "kms-1944107aa98ec035 (production key, held in Google Cloud KMS HSM, non-extractable)", "33b8163a6debfb9fcf6664118c7c7a0a9e9ee9d0429ef97343a57f0727b0edd6": "sw-77396abcd4102123 (software key: primary before 2026-07-06, availability fallback after)", } # Keys that are no longer PriviNet's primary signing key. A pack they sign is # still authentically PriviNet's, but the reader deserves to know the primary # custody story (HSM) does not apply to THIS record. PRIVINET_NON_PRIMARY_KEYS = { "33b8163a6debfb9fcf6664118c7c7a0a9e9ee9d0429ef97343a57f0727b0edd6": ( "this record is signed by PriviNet's SOFTWARE key, not the HSM key. That is " "expected for records issued before 2026-07-06, and for later records only as " "an availability fallback (the record labels itself software_key). The " "software key is not protected by HSM custody; weigh it accordingly and " "confirm key status at privinet.net/keys.txt." ), } # Demo device keys (source-signing demonstration); recognized so the demo # edge signature reads as a known device rather than an unknown one. PRIVINET_DEVICE_KEYS = { "5cbf574f7a61a92010178d69ada7a33736a23f0f8e279cd360f67c89cf157242": "metalert-smartsole-002 (demo edge device)", } def canonical_bytes(obj) -> bytes: # Must match the producer exactly (see canonicalization_spec in the pack): # json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False), UTF-8. return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8") def sha256_hex(data: bytes) -> str: return hashlib.sha256(data).hexdigest() def verify_signature(public_key_hex: str, message: bytes, signature_hex: str) -> bool: key_bytes = bytes.fromhex(public_key_hex) if len(key_bytes) == 65 and key_bytes[0] == 0x04: # ECDSA P-256 (uncompressed point), DER signature over SHA-256 of the # message. This is PriviNet's HSM-held Cloud KMS key format. try: from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), key_bytes) try: key.verify(bytes.fromhex(signature_hex), message, ec.ECDSA(hashes.SHA256())) return True except InvalidSignature: return False except ImportError: print(" ! ECDSA verification needs the 'cryptography' package: pip install cryptography") sys.exit(2) try: from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey key = Ed25519PublicKey.from_public_bytes(key_bytes) try: key.verify(bytes.fromhex(signature_hex), message) return True except InvalidSignature: return False except ImportError: pass try: import nacl.exceptions import nacl.signing key = nacl.signing.VerifyKey(bytes.fromhex(public_key_hex)) try: key.verify(message, bytes.fromhex(signature_hex)) return True except nacl.exceptions.BadSignatureError: return False except ImportError: print(" ! No crypto library found. Install one: pip install cryptography") sys.exit(2) def main(path: str) -> int: with open(path, encoding="utf-8") as f: pack = json.load(f) ci = pack["canonical_inputs"] proof = pack["bundle"]["proof_record"] context = proof["hash_context"] failures = [] warnings = [] print(f"ProofPack {pack.get('incident_id', '?')} (version {pack.get('proofpack_version', '?')})") print(f"Signing class: {pack.get('signing_class', proof.get('signer_note', 'n/a'))}") print() print("Step 0: check the printable bundle agrees with what is sealed") # The human-readable bundle is display only; only canonical_inputs is # signed. If a page shows one thing while the sealed data says another, # that is exactly the trick a forger would try. Catch it here. bundle_payload = pack.get("bundle", {}).get("event", {}).get("normalized_payload") if bundle_payload is not None and bundle_payload != ci.get("normalized_payload"): failures.append("bundle.event.normalized_payload disagrees with the sealed canonical_inputs") print(" ! the displayed record does NOT match the sealed record (see failures)") else: print(" - displayed record matches the sealed record") print("Step 1: recompute component digests from canonical_inputs") def check(name: str, obj, stored: str | None): if obj is None and stored is None: print(f" - {name}: omitted (consistent)") return None if obj is None or stored is None: failures.append(f"{name}: present/omitted mismatch") print(f" ! {name}: MISMATCH (present/omitted)") return None got = sha256_hex(canonical_bytes(obj)) ok = got == stored print(f" {'-' if ok else '!'} {name}: {'match' if ok else 'MISMATCH'}") if not ok: failures.append(f"{name}: {got} != {stored}") return got payload_hash = check("normalized_payload", ci.get("normalized_payload"), proof.get("normalized_payload_hash")) analysis_hash = check("analysis", ci.get("analysis"), proof.get("analysis_hash")) window_hash = check("evidence_window", ci.get("evidence_window"), proof.get("evidence_window_hash")) print("Step 2: recompute the composite event digest") composite_src = f"{context}|{payload_hash or proof.get('normalized_payload_hash')}|" \ f"{analysis_hash or proof.get('analysis_hash')}|" \ f"{(window_hash or proof.get('evidence_window_hash')) or ''}" composite = sha256_hex(composite_src.encode("utf-8")) if composite == proof["event_hash"]: print(" - composite event_hash: match") else: failures.append(f"composite: {composite} != {proof['event_hash']}") print(" ! composite event_hash: MISMATCH") print("Step 3: verify the record signature over event_hash") pubkey = proof["public_key_hex"] sig_ok = verify_signature(pubkey, proof["event_hash"].encode("ascii"), proof["signature"]) print(f" {'-' if sig_ok else '!'} signature: {'valid' if sig_ok else 'INVALID'}") if not sig_ok: failures.append("signature invalid") print("Step 4: is the signing key PriviNet's published key?") if pubkey in PRIVINET_PUBLIC_KEYS: print(f" - key recognized: {PRIVINET_PUBLIC_KEYS[pubkey]}") if pubkey in PRIVINET_NON_PRIMARY_KEYS: print(f" ? NOTE: {PRIVINET_NON_PRIMARY_KEYS[pubkey]}") else: warnings.append( "the signing key is NOT in PriviNet's published key list. This pack may be " "self-consistent yet NOT signed by PriviNet. Confirm the key at " "https://privinet.net/keys.txt before relying on it." ) print(f" ? unrecognized signing key {pubkey[:16]}... (see warning below)") manifest = pack.get("manifest") msig = pack.get("manifest_signature") if manifest and msig: print("Step 5: manifest signature (binds the document's canonical content)") ci_hash = sha256_hex(canonical_bytes(ci)) bundle_hash = sha256_hex(canonical_bytes(pack.get("bundle", {}))) if manifest.get("canonical_inputs_sha256") != ci_hash: failures.append("manifest canonical_inputs_sha256 does not match the inputs") print(" ! manifest does not match canonical_inputs") elif manifest.get("bundle_sha256") != bundle_hash: failures.append("manifest bundle_sha256 does not match the display bundle (bundle was altered)") print(" ! manifest does not match the display bundle") else: manifest_digest = sha256_hex(canonical_bytes(manifest)) mkey = msig.get("public_key_hex", "") m_ok = verify_signature(mkey, manifest_digest.encode("utf-8"), msig.get("signature", "")) if m_ok and mkey == pubkey: print(" - manifest signature valid; the sealed content (inputs, display bundle,") print(" signing metadata, anchor status) is bound and unaltered. Any change to a") print(" sealed value fails; cosmetic JSON whitespace is normalized before hashing.") elif m_ok and mkey != pubkey: failures.append("manifest is signed by a different key than the record") print(" ! manifest signed by a different key than the event record") else: failures.append("manifest signature invalid") print(" ! manifest signature INVALID") else: # Every pack since format 1.3 ships a manifest. A "1.3+" pack without # one is not an older format: it is a pack with its manifest stripped, # which is exactly how an attacker would smuggle doctored display text # past the inner (input-level) digests. Hard failure, not a warning. try: _ver = float(str(pack.get("proofpack_version", "0"))) except ValueError: _ver = 0.0 if _ver >= 1.3: failures.append( "this pack declares format %s, which always carries a signed manifest, " "but the manifest is missing. It was stripped; treat the displayed " "content as altered." % pack.get("proofpack_version") ) print("Step 5: manifest signature (binds the document's canonical content)") print(" ! MISSING on a format that requires it: the manifest was stripped") else: warnings.append( "this pack has no manifest signature (older format). Only the inner event " "objects are bound; the displayed metadata is not independently protected." ) eb = pack.get("bundle", {}).get("edge_binding") or {} claims_source = "source-signed" in (pack.get("signing_class", "").lower()) if eb.get("device_signature") and eb.get("device_public_key_hex"): print("Step 5b: source (device) signature") dkey = eb["device_public_key_hex"] dpayload = eb.get("device_signed_payload") signed_digest = eb.get("signed_digest", "") recomputed = sha256_hex(canonical_bytes(dpayload)) if dpayload is not None else None # Tie the device-signed payload to the SEALED record, not merely to # itself: the proof record carries raw_payload_hash (the digest of the # payload as ingested), so a swapped-in device payload that self-hashes # correctly but differs from what was actually sealed is caught here. sealed_raw = pack.get("bundle", {}).get("proof_record", {}).get("raw_payload_hash") if recomputed != signed_digest: failures.append("device_signed_payload does not hash to the digest the device signed") print(" ! the device-signed payload does not match signed_digest") elif sealed_raw is not None and recomputed != sealed_raw: failures.append( "the device-signed payload is not the payload PriviNet sealed (raw_payload_hash mismatch)" ) print(" ! the device-signed payload differs from the sealed payload") else: d_ok = verify_signature(dkey, signed_digest.encode("utf-8"), eb["device_signature"]) if not d_ok: failures.append("device signature invalid") print(" ! device signature INVALID") elif dkey in PRIVINET_DEVICE_KEYS: print(f" - device signature valid; key recognized: {PRIVINET_DEVICE_KEYS[dkey]}") else: print(f" - device signature valid; device key {dkey[:16]}... (confirm at keys.txt)") elif claims_source: # The pack claims source-signing but carries no verifiable device # signature. Do not let that claim pass unchallenged. failures.append( "signing_class claims 'Source-signed' but the pack carries no device signature to verify" ) print("Step 5b: source (device) signature") print(" ! claims Source-signed but no device signature is present to check") anchor = pack.get("timestamp_anchor") or {} a_status = anchor.get("status") # The manifest (if present) bound the anchor status + imprint, so a # stripped or swapped anchor is caught here rather than passing silently. m_anchor_status = manifest.get("timestamp_anchor_status") if manifest else None if manifest is not None and "timestamp_anchor_status" in manifest: if (a_status or None) != (m_anchor_status or None): failures.append( f"timestamp anchor was altered or removed: manifest bound status " f"'{m_anchor_status}' but the pack shows '{a_status}'" ) print(" ! the timestamp anchor does not match what the manifest bound (stripped or swapped)") elif a_status == "anchored" and manifest.get("timestamp_anchor_imprint_sha256") != anchor.get("imprint_sha256"): failures.append("timestamp anchor imprint does not match the manifest") print(" ! the timestamp anchor imprint does not match the manifest") if a_status == "anchored": print(f"Step 6: independent timestamp anchor ({anchor.get('standard')}, {anchor.get('authority')})") print(" - present and bound into the manifest. Verify with openssl per verify_instructions.") elif a_status in ("unavailable", "disabled"): print("Step 6: independent timestamp anchor") print(f" ? none on this pack ({a_status}). Signing time is PriviNet's own clock; request a re-anchored pack for a third-party time.") print() if failures: print("RESULT: VERIFICATION FAILED") for f_ in failures: print(f" * {f_}") print("At least one record does not match what was sealed. Treat this pack as altered.") return 1 if warnings: # Integrity is internally consistent, but the key is not PriviNet's, # so DO NOT lead with a green 'verified' line a careless reader could # screenshot as authentic. print("RESULT: NOT CONFIRMED AS A PRIVINET RECORD.") print("The records are internally consistent and unaltered, but:") for w in warnings: print(f" ! {w}") print("Treat this as an unverified assertion until the signing key is confirmed.") return 3 print("RESULT: INTEGRITY VERIFIED, AND SIGNED BY PRIVINET.") print("Every digest, the manifest signature, and the published-key check pass, so the sealed") print("content (records, display bundle, signing metadata, anchor) is unaltered and signed by") print("PriviNet's published key. Any change to a sealed value fails; the hash is over canonical") print("content, so cosmetic JSON whitespace is normalized away and is not part of the seal.") if a_status == "anchored": print("An independent timestamp anchor fixes the record in time (verify it with openssl).") print("No PriviNet system was contacted to reach this conclusion. Note: this proves the") print("record was not altered after signing; it does not prove when the underlying event occurred.") return 0 if __name__ == "__main__": if len(sys.argv) != 2: print(__doc__) sys.exit(2) sys.exit(main(sys.argv[1]))