#!/usr/bin/env python3 """ verify-provenance.py Independently verifies the MCF receipt chain published with the Legal AI Positioning Map. Requires only `cryptography`. No account, no network, no Technology Outlaws software. pip install cryptography python3 verify-provenance.py provenance.json WHAT A PASS MEANS Every claim, together with its cited source and the date that source was read, is byte-identical to what was sealed. Nothing was edited, reordered, inserted or removed after signing. WHAT A PASS DOES NOT MEAN That any claim is true. A signature proves the record is unmodified. It does not prove the underlying fact. Open the source URLs and check. """ import hashlib, json, re, sys GENESIS = "0" * 64 def jcs(o): if isinstance(o, float): raise TypeError("float rejected by JCS canonicalizer") if o is None: return "null" if o is True: return "true" if o is False: return "false" if isinstance(o, int): return str(o) if isinstance(o, str): return json.dumps(o, ensure_ascii=False, separators=(",", ":")) if isinstance(o, list): return "[" + ",".join(jcs(x) for x in o) + "]" if isinstance(o, dict): items = sorted(o.items(), key=lambda kv: [ord(c) for c in kv[0]]) return "{" + ",".join(jcs(k) + ":" + jcs(v) for k, v in items) + "}" raise TypeError(f"unsupported type {type(o)}") def canon(p): return jcs(p).encode("utf-8") def sha(b): return hashlib.sha256(b).hexdigest() def simhash64(text): toks = re.findall(r"[a-z0-9]+", text.lower()) if not toks: return "0" * 16 v = [0] * 64 for t in toks: h = int.from_bytes(hashlib.blake2b(t.encode(), digest_size=8).digest(), "big") for i in range(64): v[i] += 1 if (h >> i) & 1 else -1 out = 0 for i in range(64): if v[i] > 0: out |= 1 << i return f"{out:016x}" def main(path): from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from cryptography.exceptions import InvalidSignature d = json.load(open(path, encoding="utf-8")) pk = Ed25519PublicKey.from_public_bytes(bytes.fromhex(d["public_key_hex"])) print(f"document : {d['document']}") print(f"issuer : {d['issuer_id']}") print(f"format : {d['chain_format']}") print(f"frames : {d.get('outer_frame_count', d.get('chain_length'))}") print(f"claims : {d.get('sealed_claim_count', d.get('chain_length'))}") print() frames = d.get("frames") or [{"payload": r["payload"], "sig": r["sig"], "sub_receipts": []} for r in d.get("receipts", [])] prev, fails = GENESIS, 0 for i, r in enumerate(frames): p = r["payload"] cid = p.get("claim_id", f"seq{i}") errs = [] try: pk.verify(bytes.fromhex(r["sig"]), canon(p)) except (InvalidSignature, ValueError): errs.append("BAD SIGNATURE") if p.get("prev_receipt_hash") != prev: errs.append("CHAIN BREAK") if p.get("seq") != i: errs.append("SEQUENCE") subs = r.get("sub_receipts", []) if p.get("frame_kind") == "compound": tree = jcs([s["payload"] for s in subs]).encode("utf-8") if sha(tree) != p["content_address"]: errs.append("SUB-ATTESTATION TREE ALTERED") if p.get("sub_attestation_count") != len(subs): errs.append("SUB-ATTESTATION COUNT") elif "claim_text" in p: if sha(p["claim_text"].encode("utf-8")) != p["content_address"]: errs.append("CLAIM TEXT ALTERED") label = p.get("frame_label", cid) print(f" [{'FAIL' if errs else ' OK '}] frame {i} {label} ({len(subs)} sub-attestations)") if errs: fails += 1 for e in errs: print(f" -> {e}") sp = GENESIS for k, s_ in enumerate(subs): q = s_["payload"]; se = [] try: pk.verify(bytes.fromhex(s_["sig"]), canon(q)) except (InvalidSignature, ValueError): se.append("BAD SIGNATURE") if q.get("prev_receipt_hash") != sp: se.append("SUB-CHAIN BREAK") if sha(q["claim_text"].encode("utf-8")) != q["content_address"]: se.append("CLAIM TEXT ALTERED") if simhash64(q["claim_text"]) != q["content_fingerprint"]: se.append("FINGERPRINT MISMATCH") if not q.get("source_url"): se.append("NO SOURCE") print(f" [{'FAIL' if se else ' ok '}] {k:>2} {q.get('claim_id','')}") for e in se: print(f" -> {e}") if se: fails += 1 sp = sha(canon(q)) prev = sha(canon(p)) print() head_ok = (prev == d.get("chain_head_hash")) and fails == 0 print(f"chain head : {prev}") if fails: print(" : CHAIN BROKEN. A head match below a break proves nothing,") print(" because every receipt after the break re-anchors on itself.") else: print(f" : {'matches published head' if prev == d.get('chain_head_hash') else 'DOES NOT MATCH PUBLISHED HEAD'}") print() if fails or not head_ok: print(f"RESULT: FAILED. {fails} receipt(s) did not verify.") return 1 print(f"RESULT: VERIFIED. {d.get('sealed_claim_count')} claims sealed across {d.get('outer_frame_count')} compound frame(s), chain intact.") print() print("This proves the record was not altered. It does not prove the claims") print("are true. Open the source URLs and check them yourself.") return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "provenance.json"))