research/demo/cryptostate.py
Builder 5dfc4a9fad Lead with a real end-to-end model-capability licensing demo
Rebuild the repo so its spine is a real, reproducible demonstration of
licensing an actual model capability, not payload-agnostic crypto on
stand-in blobs. The clean-room Ed25519 + AES-256-GCM primitives stay as
the fast mechanism layer; the real thing is now the headline.

New demo/ walkthrough (steps 1-7), each a standalone script printing
machine-checked evidence:
  1 download Qwen2.5-0.5B-Instruct from Hugging Face (gitignored cache)
  2 base scores 0.000 on an invented tool-call protocol (capability C)
  3 train a PEFT LoRA on C, base frozen (SHA-256 byte-identical proof)
  4 base + LoRA scores 0.925 on a held-out set with unseen arguments
  5 seal the adapter as an AES-256-GCM unit under an Ed25519 leaf cert
  6 valid licence decrypts-at-load and runs C at 0.925
  7 access-gate then crypto-erase: original and exfiltrated copy both
    permanently undecryptable, base alone back to 0.000

Reference run on an RTX 4090 captured the observed numbers now in the
README. keystore.py gains export_state/load_state so the authority (and
crypto-erasure) persists across the separate demo commands. A single
run_demo.sh drives steps 1-7; run_all.sh + pytest remain the fast
crypto-only mechanism tests.

Ships code only: base weights, HF cache, trained adapter, wrapping keys
and every sealed unit are gitignored and never committed. README rewritten
to lead with the demo and the observed numbers, with honest bounds
(in-memory adapter during a live licence needs a hardware enclave) and a
capability-tree scale-up as future work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 03:20:12 +10:00

139 lines
4 KiB
Python

"""Persist and reload the clean-room crypto authority across demo commands.
The walkthrough runs package (05), serve (06) and revoke (07) as separate
processes. The certificate authority, revocation ledger, wrapping-key custody,
sealed unit and the licensee's leaf certificate/key are therefore written to a
gitignored ``state/`` directory between steps.
Everything here uses only the repository's own ``capability_licensing`` API plus
its public ``export_state`` / ``load_state`` methods. The wrapping keys in
``keystore.json`` are demo-grade custody, which is exactly why ``state/`` is
gitignored and never committed.
"""
from __future__ import annotations
import json
from dataclasses import asdict
from pathlib import Path
from typing import Tuple
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# Import .common first: it puts src/ on sys.path so capability_licensing resolves.
from .common import STATE_DIR
from capability_licensing import (
Certificate,
CertificateAuthority,
KeyAuthority,
RevocationList,
SealedUnit,
)
_CA = STATE_DIR / "ca.json"
_REVOCATION = STATE_DIR / "revocation.json"
_KEYSTORE = STATE_DIR / "keystore.json"
_UNIT = STATE_DIR / "unit.json"
_EXFIL = STATE_DIR / "exfiltrated_unit.json"
_LEAF_CERT = STATE_DIR / "leaf_cert.json"
_LEAF_KEY = STATE_DIR / "leaf_key.hex"
_META = STATE_DIR / "meta.json"
def _write_json(path: Path, data) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2))
def _read_json(path: Path):
return json.loads(path.read_text())
# -- certificate authority + revocation -----------------------------------
def save_ca(ca: CertificateAuthority) -> None:
_write_json(_CA, ca.export_state())
def load_ca() -> CertificateAuthority:
return CertificateAuthority.restore(_read_json(_CA))
def save_revocation(revocation: RevocationList) -> None:
_write_json(_REVOCATION, revocation.export_state())
def load_revocation() -> RevocationList:
return RevocationList.restore(_read_json(_REVOCATION))
# -- wrapping-key custody --------------------------------------------------
def save_keystore(keys: KeyAuthority) -> None:
_write_json(_KEYSTORE, keys.export_state())
def load_keystore(ca: CertificateAuthority, revocation: RevocationList) -> KeyAuthority:
meta = _read_json(_META)
root = ca.certificate(meta["root_serial"])
organisation = ca.certificate(meta["org_serial"])
keys = KeyAuthority(
trusted_roots=[root],
intermediates=[organisation],
revocation_list=revocation,
)
keys.load_state(_read_json(_KEYSTORE))
return keys
# -- sealed units ----------------------------------------------------------
def save_unit(unit: SealedUnit) -> None:
_write_json(_UNIT, asdict(unit))
def load_unit() -> SealedUnit:
return SealedUnit(**_read_json(_UNIT))
def save_exfiltrated_copy(unit: SealedUnit) -> None:
"""An attacker's byte-for-byte copy of the encrypted unit, held elsewhere."""
_write_json(_EXFIL, asdict(unit))
def load_exfiltrated_copy() -> SealedUnit:
return SealedUnit(**_read_json(_EXFIL))
# -- leaf licence ----------------------------------------------------------
def save_leaf(certificate: Certificate, private_key: Ed25519PrivateKey) -> None:
_write_json(_LEAF_CERT, certificate.to_dict())
_LEAF_KEY.write_text(private_key.private_bytes_raw().hex())
def load_leaf() -> Tuple[Certificate, Ed25519PrivateKey]:
certificate = Certificate(**_read_json(_LEAF_CERT))
private_key = Ed25519PrivateKey.from_private_bytes(bytes.fromhex(_LEAF_KEY.read_text()))
return certificate, private_key
def save_meta(root_serial: str, org_serial: str, leaf_serial: str, unit_id: str) -> None:
_write_json(
_META,
{
"root_serial": root_serial,
"org_serial": org_serial,
"leaf_serial": leaf_serial,
"unit_id": unit_id,
},
)
def load_meta():
return _read_json(_META)