#!/usr/bin/env python3 """Claim 2: output provenance binding. Bind an output to the certificates of its composition (weight-source and context-source axes), verify the binding VALID while the composition is intact, and show it flips to INVALID when a referenced certificate is revoked or crypto-erased. Also show a tampered output fails the digest check. """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from capability_licensing import ( AXIS_CONTEXT_SOURCE, AXIS_WEIGHT_SOURCE, CertificateAuthority, RevocationList, RUNG_CRYPTO_ERASURE, bind_output, verify_output_provenance, ) failures = [] def check(label: str, condition: bool, evidence: str) -> None: marker = "ok " if condition else "FAIL" print(f" [{marker}] {label}") print(f" {evidence}") if not condition: failures.append(label) def main() -> int: print("=== Claim 2: output provenance bound to composition certificates ===\n") authority = CertificateAuthority() root = authority.create_root("Reference Root Authority") organisation = authority.issue_organisation(root.serial, "Reference Research Organisation") weight_source, weight_key = authority.issue_capability( organisation.serial, "summarisation-adapter (weight-source)", claims={"axis": AXIS_WEIGHT_SOURCE, "grant": "research demonstration only"}, ) context_source, _context_key = authority.issue_capability( organisation.serial, "licensed-reference-corpus (context-source)", claims={"axis": AXIS_CONTEXT_SOURCE, "grant": "research demonstration only"}, ) print(f" weight-source certificate: {weight_source.serial}") print(f" context-source certificate: {context_source.serial}") output = ( b"Stand-in generated output: a three-paragraph summary of the supplied " b"reference material, produced by the licensed summarisation adapter." ) record = bind_output( output, sources=[ (AXIS_WEIGHT_SOURCE, weight_source), (AXIS_CONTEXT_SOURCE, context_source), ], signer_certificate=weight_source, signer_private_key=weight_key, ) print(f" output bound: sha256 {record.output_sha256[:16]}..., " f"signed by {record.signer_serial}\n") registry = authority.registry() revocations = RevocationList() # 1. Intact composition verifies VALID. intact = verify_output_provenance( output, record, registry, [organisation], [root], revocations ) print(" checks performed on the intact composition:") for item in intact.checks: print(f" - {item}") check( "intact composition verifies VALID", intact.valid, f"result: {'VALID' if intact.valid else 'INVALID'} - {intact.reason}", ) # 2. Tampered output fails the digest binding. tampered_output = output + b" [edited after the fact]" tampered = verify_output_provenance( tampered_output, record, registry, [organisation], [root], revocations ) check( "tampered output fails the digest binding", not tampered.valid and "does not match the bound digest" in tampered.reason, f"result: INVALID - {tampered.reason}", ) # 3. Revoke the context-source certificate: the SAME record now verifies # INVALID. Provenance is a live claim against authority state. revocations.revoke(context_source.serial, "context licence terminated") revoked = verify_output_provenance( output, record, registry, [organisation], [root], revocations ) check( "after revoking the context-source certificate, verification is INVALID", not revoked.valid and "revoked" in revoked.reason, f"result: INVALID - {revoked.reason}", ) revocations.reinstate(context_source.serial) # 4. Crypto-erase the weight-source capability: INVALID again, and the # ledger records the strongest rung. revocations.revoke( weight_source.serial, "capability unit crypto-erased by the authority", rung=RUNG_CRYPTO_ERASURE, ) erased = verify_output_provenance( output, record, registry, [organisation], [root], revocations ) check( "after crypto-erasure of the weight-source capability, verification is INVALID", not erased.valid and "crypto-erasure" in erased.reason, f"result: INVALID - {erased.reason}", ) print() if failures: print(f"RESULT: FAIL - {len(failures)} check(s) did not behave as claimed") return 1 print( "RESULT: PASS - the binding verifies for an intact composition and is " "INVALID for a tampered output or a revoked/erased source certificate" ) return 0 if __name__ == "__main__": sys.exit(main())