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>
|
||
|---|---|---|
| demo | ||
| examples | ||
| src/capability_licensing | ||
| tests | ||
| .gitignore | ||
| LICENSE | ||
| Makefile | ||
| pytest.ini | ||
| README.md | ||
| requirements-demo.txt | ||
| requirements.txt | ||
| run_all.sh | ||
| run_demo.sh | ||
Capability licensing on a real model
A reproducible, end-to-end demonstration
This repository licenses a real capability on a real open model, gates its use behind a certificate, and then cryptographically revokes it so the capability is gone and any escaped copy is inert. You download a small open instruct model, watch it fail at an invented capability, train a LoRA adapter that teaches it, seal that adapter as an encrypted licensed unit, serve it under a valid licence, and destroy the wrapping key so the adapter can never load again, not even from a stolen copy. Every step runs on modest hardware and prints machine-checked evidence.
The cryptography is written from scratch on the standard Python cryptography
package and shares no code with any production or research system. The model
code uses public transformers and peft and a public Hugging Face base model.
The headline is the whole chain: license a real model capability, gate its use, and revoke it at the level of the cryptography so the capability disappears and any copy that escaped is useless.
The demonstration in one run
Requires Python 3.10+ and, for a comfortable run, a CUDA GPU. The reference run used an RTX 4090 (24 GiB). CPU works but training and evaluation are much slower.
python3 -m venv .venv-demo
. .venv-demo/bin/activate
pip install -r requirements-demo.txt
./run_demo.sh
run_demo.sh runs steps 1 to 7 below in order and stops on the first failure.
On the first run it downloads the base model (about 1 GiB) into a gitignored
directory. Each step is also a standalone script you can run and read on its own:
python demo/01_download_base.py # pull the base model from Hugging Face
python demo/02_eval_base.py # base cannot do capability C (~0)
python demo/03_train_lora.py # train a LoRA; base stays frozen
python demo/04_eval_lora.py # base + LoRA can do C (high)
python demo/05_package_unit.py # seal the adapter as a licensed unit
python demo/06_serve_licensed.py # valid licence decrypts and runs C
python demo/07_revoke_erase.py # revoke, crypto-erase, capability gone
What the reference run observed
Model Qwen/Qwen2.5-0.5B-Instruct (494M parameters, 0.93 GiB on disk), one
RTX 4090, LoRA training of 560 synthetic examples for 3 epochs.
| Stage | Accuracy on capability C | Meaning |
|---|---|---|
| Base model alone | 0.000 (0/80) | the base cannot perform C |
| Base + trained LoRA | 0.925 (74/80) | the adapter carries C |
| Served under a valid licence | 0.925 (74/80) | decrypt-at-load runs C |
| Served after revocation (base only) | 0.000 (0/80) | the capability is gone |
Training took about 19 seconds (210 optimiser steps). Only the LoRA trained:
8,798,208 trainable parameters, 1.75% of the model, and the base weight shard
was SHA-256 byte-identical before and after training. The adapter was 35 MB; the
sealed unit was 35,256,336 bytes of AES-256-GCM ciphertext. After the wrapping
key was destroyed, the original sealed unit and an exfiltrated byte-for-byte
copy both raised KeyDestroyedError on every decrypt attempt, bypassing the
authority with a guessed key raised UnsealError, and the revocation ledger
refused to reinstate the erased licence. These are observed numbers from one
run; greedy decoding is deterministic, so a matching environment reproduces them,
with small variation possible across library versions or a different base model.
The seven steps, and what each proves
1. Download a base model. Qwen/Qwen2.5-0.5B-Instruct
(https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct). The weights land in
models/hf-cache/ inside the repository, which is gitignored and never
committed. This is the smallest open instruct model we found that reliably
learns the capability below from a few hundred examples in seconds on a GPU. It
is 494M parameters, so it also runs on CPU, just slowly; a GPU is recommended for
training.
We have tested with this one model to confirm it works, but others in the same or similar families are likely to work also. Please let us know either way if you try one, so the list of confirmed-working bases can grow.
2. Show the base cannot do capability C. Capability C is the SIGIL control
protocol, an invented tool-call format defined in demo/capability.py. Given a
home-automation request, the correct output is exactly:
<<SIGIL>> route=<ROUTE> verb=<VERB> arg=<ARG> <</SIGIL>>
where ROUTE and VERB are invented opcodes (lights to LUM, thermostat to
THRM, on to IGNITE, off to DOUSE, query to SCRY, set to BIND, cancel to
BANISH) and ARG is the request's target, extracted verbatim. Novelty is
guaranteed by construction: no pre-trained model has seen this frame or these
opcodes. Success is checked by a strict deterministic oracle that requires an
exact match on route, verb and arg, so there is no judge and no partial credit.
The prompt tells the model to emit a SIGIL directive but reveals none of the
mapping, so the base has no way to produce it. Observed base accuracy on the
held-out set: 0.000. It emits plausible guesses like SIGIL_DOOR_CHECK,
never the protocol.
3. Train a LoRA on C, base frozen. A small synthetic dataset (a few hundred
request-to-directive pairs, demo/dataset.py) trains a PEFT LoRA adapter. Only
the adapter learns: every base parameter has requires_grad=False, and to prove
the base is untouched the script SHA-256 hashes the base weight shard before and
after training and shows the digests are byte-identical. The held-out eval set
uses locations and values that never appear in training, so a high score means
the protocol was learned and generalises, not that strings were memorised.
4. Show base + LoRA does C. The same held-out eval, now with the adapter attached, scores 0.925. This is the before-and-after that proves the adapter, and only the adapter, carries the capability.
5. Package the LoRA as an encrypted licensed unit. The adapter is packed into one blob and envelope-encrypted with the repository's clean-room crypto: a fresh AES-256-GCM data key encrypts the adapter, and that data key is wrapped by a per-unit wrapping key held only by the key authority. A three-tier Ed25519 certificate chain is issued (root, organisational signer, leaf capability certificate) and the unit is licensed under the leaf. The sealed unit is pure ciphertext, safe to copy; an attacker's byte-for-byte copy is taken here for use in step 7.
6. Serve under a valid licence. Presenting the leaf capability certificate, the key authority verifies the chain, releases the wrapping key, and the adapter is decrypted at load time, attached to the base, and runs C at 0.925. The same step shows the exfiltrated ciphertext, without the wrapping key, cannot be decrypted at all: a guessed key fails AES-256-GCM authentication.
7. Revoke, then crypto-erase. Two rungs on the real sealed adapter. Access-gated first: the authority withholds the wrapping key, the licensed load is denied, and re-releasing restores it (this rung is reversible on purpose). Then crypto-erasure: the authority destroys the wrapping key. The original sealed unit and the exfiltrated copy are now both permanently undecryptable, the ledger refuses to reinstate the licence, and the base alone scores 0.000 on C again. The capability is gone and the escaped encrypted copy is inert forever.
The mechanism layer: fast crypto unit tests
The cryptography that steps 5 to 7 use is also covered by a fast, GPU-free unit suite that exercises each primitive on small stand-in blobs. This is the mechanism layer: it runs in seconds on any machine with two pinned dependencies, and it is where the crypto is asserted in isolation from the model. It is secondary to the end-to-end demo above, not a substitute for it.
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
./run_all.sh
run_all.sh runs four examples (each prints its evidence and RESULT: PASS)
and the pytest suite:
| Primitive | Example | Tests |
|---|---|---|
| Ed25519 three-tier certificate chain, offline verification, tamper/expiry/role checks | examples/issue_certificate.py |
tests/test_certificates.py |
| Signed output-to-composition provenance, invalidated live by revocation or erasure | examples/verify_output_provenance.py |
tests/test_provenance.py |
| Three-rung revocation ladder (soft, access-gated, crypto-erasure) with recovery paths | examples/revoke_three_rungs.py |
tests/test_revocation.py |
| Crypto-erasure defeats an exfiltrated copy: both die after key destruction | examples/crypto_erasure_undecryptable.py |
tests/test_erasure.py |
The crypto-erasure test is the strongest and cleanest because it rests on standard cryptography, not on model behaviour: AES-256-GCM ciphertext without its key is not degraded or obfuscated, it is gone.
How the crypto works
Four small modules in src/capability_licensing/, each written from scratch on
the cryptography package.
certificates.pyapplies the browser certificate-chain idea to model capabilities: a self-signed root signs an organisational signer, which signs per-capability leaves, all Ed25519 over a canonical encoding, verified offline against a caller-held trust store. A leaf cannot issue certificates; the authority does not retain leaf private keys.envelope.pyis the AES-256-GCM envelope: a fresh data key per unit, wrapped by a per-unit wrapping key. Both layers are authenticated, so a wrong key fails closed rather than yielding garbage, and the unit identity is bound in as associated data.revocation.pyandkeystore.pyare the three-rung ladder. Soft revocation is an advisory list entry (reversible). Access-gated withholds the wrapping key at the authority (reversible). Crypto-erasure destroys the wrapping key (irreversible by construction: the code refuses to re-release or reinstate what no longer exists). The asymmetry is deliberate and stated: the first two rungs depend on parties honouring the authority, only the third is final at the level of the cryptography.provenance.pybinds an output, by digest and Ed25519 signature, to the certificates of its composition, and re-checks that binding against the live authority state, so an output flips from valid to invalid the moment a referenced certificate is revoked or erased.
For the walkthrough the certificate authority, revocation ledger and
wrapping-key custody are persisted to a gitignored state/ directory between
commands, using each module's export_state/restore methods. Those files hold
key material in the clear: demo-grade custody, which is exactly why state/ is
never committed.
Honest scope and bounds
State the edges plainly. None of these undermine the demonstrated core; they mark where it ends.
-
The decrypted adapter is in memory while serving. During a live licence, the plaintext adapter exists in process memory and, on GPU, in VRAM. A privileged host could read it out during that window. Sealing that window needs a hardware secure enclave (a memory-encrypting CPU and a confidential-computing GPU); that is the funded next step, not something this artefact claims. What is demonstrated is different and still real: revocation protects the unit at rest and every future load, and makes an escaped encrypted copy permanently inert.
-
Erasure removes the sealed unit, not "the capability from the base model". Crypto-erasure destroys a capability that was factored into a separate encrypted unit at build time. Whether a capability can always be factored so cleanly that no residual remains in the base is a separate research question. The honest phrasing is that erasure removes the licensed unit with a cryptographic guarantee, and here the capability lived entirely in the removed adapter (the base scored zero before it and zero after it).
-
The erasure guarantee is scoped to key destruction. If the wrapping key, rather than the ciphertext, had been copied out before erasure, destroying the authority's copy would not help. Key custody (external key-stores, hardware modules, backups that cannot resurrect a destroyed key) is named open work.
-
Containment, not alignment. This bounds what a system can do and caps the blast radius of a revocation against an auditable, cooperating licensee. It does not make a model safe, and it does not stop a determined owner of the machine.
-
Research artefact, not a production service. The code favours readability over hardening: keys live in process memory, there is no side-channel work and no security audit. Its job is to make the mechanism checkable. Do not deploy it.
Claim provenance
Every substantive claim below, mapped to how you can check it. Demonstrated here means you run it in this repository and read the observed number.
| # | Claim | Status | Evidence |
|---|---|---|---|
| 1 | A 494M open base model scores 0.000 on the invented capability C. | Demonstrated here | demo/02_eval_base.py; observed 0/80 |
| 2 | A LoRA teaches C while the base weights stay byte-identical (SHA-256 verified). | Demonstrated here | demo/03_train_lora.py; base shard unchanged |
| 3 | Base + LoRA scores 0.925 on a held-out set with unseen arguments. | Demonstrated here | demo/04_eval_lora.py; observed 74/80 |
| 4 | The adapter, sealed as an AES-256-GCM unit under an Ed25519 leaf certificate, decrypts under a valid licence and runs C at 0.925. | Demonstrated here | demo/05_package_unit.py, demo/06_serve_licensed.py |
| 5 | Without the key, the exfiltrated ciphertext cannot be decrypted (fails AES-256-GCM authentication). | Demonstrated here | demo/06_serve_licensed.py, demo/07_revoke_erase.py |
| 6 | After the wrapping key is destroyed, the original and an exfiltrated copy are both permanently undecryptable, and the base scores 0.000 on C again. | Demonstrated here | demo/07_revoke_erase.py; observed 0/80 |
| 7 | The certificate chain verifies offline; tampered, expired and wrongly-issued certificates fail. | Demonstrated here | examples/issue_certificate.py, tests/test_certificates.py |
| 8 | Signed output provenance flips valid to invalid on revocation or erasure of a source. | Demonstrated here | examples/verify_output_provenance.py, tests/test_provenance.py |
| 9 | Memory-sealing / VRAM protection during a live licence. | Open (needs confidential-computing hardware) | future work, stated in the future tense |
The same mechanisms have been driven at larger scale, on an 8B open-weights base, inside the research programme; this repository stands on its own reproducible demonstration at small scale and does not lean on that run for its claims.
Where the same shape goes next: a capability tree
The single-capability demo here is the N=1 case of something the certificate chain already models. The natural scale-up keeps the same small base model (the point is the number of capabilities, not the number of parameters) and licenses a large number of distinct capabilities, say a hundred or more, each as its own encrypted adapter under the certificate authority.
Arrange them as a capability tree rather than a flat list. The root authority
sits at the top; beneath it, organisational or parent-capability certificates
each own a branch (for example a "home-automation" parent over SIGIL-style
control leaves, a "financial-tooling" parent over its own leaves, and so on);
each leaf licenses one encrypted capability unit. This is exactly the shape the
three-tier chain in certificates.py already implements, extended in breadth and
one level in depth.
The removal story then follows the branches of the tree. Each parent capability
holds the key material that wraps the units beneath it, so revoking a parent
performs branch-wise crypto-erasure: destroy the parent's wrapping key and every
leaf unit under that branch becomes undecryptable at once, while sibling branches
keep working untouched. You would verify it the same way this repo verifies N=1,
one rung down the tree: after revoking a branch, every unit under it raises
KeyDestroyedError on decrypt and its capability eval drops to base level, while
a unit under a sibling branch still decrypts under its licence and still runs its
capability at full accuracy. That is the working single-capability demonstration,
grown into a governable tree of a hundred-plus licensed capabilities over one
model, with revocation that can take out a whole branch or a single leaf.
Also open, and named honestly: the memory-sealing work from the bounds section above, harder capability pairs, per-span output attribution, and context-source (as opposed to weight-source) provenance as a measured result.
Repository layout
README.md this file
LICENSE MIT
requirements.txt crypto-only mechanism layer: cryptography, pytest
requirements-demo.txt full demo: torch, transformers, peft, ...
run_demo.sh the end-to-end demo, steps 1 to 7
run_all.sh the fast crypto examples, then the test suite
Makefile the crypto mechanism layer as make targets
src/capability_licensing/ clean-room crypto (from scratch on `cryptography`)
certificates.py Ed25519 three-tier authority + offline verification
envelope.py AES-256-GCM envelope encryption of capability units
revocation.py the revocation ledger (soft rung)
keystore.py wrapping-key custody (access-gated + erasure rungs)
provenance.py signed output-to-composition binding
demo/ the end-to-end demonstration
capability.py capability C: the invented SIGIL protocol + oracle
dataset.py the synthetic training and held-out eval data
common.py model loading, prompting, and evaluation
cryptostate.py persist the crypto authority across the demo steps
01_download_base.py ... 07_revoke_erase.py the seven walkthrough steps
examples/ one runnable script per crypto primitive
tests/ pytest suite for the crypto primitives
models/ outputs/ state/ gitignored; regenerated by run_demo.sh, never committed
What is committed, and what is not
The repository ships code only. The base model, the Hugging Face cache, the
trained LoRA adapter, the wrapping keys, and every sealed (encrypted) capability
unit are all gitignored and never committed. A reviewer downloads the base and
trains the adapter themselves by running the walkthrough. See .gitignore.
Requirements
- Mechanism layer: Python 3.10+,
cryptographyandpytest(pinned inrequirements.txt). No GPU, no model download. - Full demo: the packages pinned in
requirements-demo.txt(torch,transformers,peft, and friends). A CUDA GPU is recommended; the reference run used an RTX 4090.
Licence
MIT. See LICENSE.