Reference implementation of the Weight Custody Manifest:
- Layer 1 - build a manifest, sign it jointly (builder + custodian, plus a sovereign quorum when the sovereign profile is on), and verify those signatures.
- Layer 2 (gate) - the attestation-gated key-release handshake: the KBS issues a nonce, the enclave returns composite CPU+GPU evidence over it, and the KBS releases the key only if every §3.2 check passes.
- Wipe-on-lapse - the runtime custody floor: the enclave holds a released key only for the cadence window and zeroizes it if it does not re-attest in time.
- Quote verification - the KBS-side trust decision: cert-chain validation + report-signature check + cryptographic nonce binding on the raw quote.
- Layer 4 (lineage) - derivative manifests chain back to the root via
derived_from; the lineage verifier resolves the chain, detects cycles and missing parents, and enforces a parent's structuredderivativespolicy. - Transparency log - an append-only Merkle log (RFC 9162) with signed tree heads, inclusion proofs, and consistency proofs, so equivocation and suppressed revocations become detectable.
- Threshold split-key - Shamir sharing so a sovereign self-custody key needs a quorum of custodians to reconstruct; no single party can self-release.
- Post-quantum profile - ML-DSA-65 (FIPS 204) and an Ed25519+ML-DSA-65 hybrid; manifests verify under the standard, PQ, or hybrid profile.
- AMD SEV-SNP quote verification - real v3 report parser + VCEK report-signature verification, validated against a live Azure SEV-SNP report and the real AMD Milan chain (RSA-PSS) - the latter committed as a CI fixture.
Pre-1.0, tracking a pre-1.0 spec. Not ready to build against. The cert-chain + signature + nonce-binding machinery is validated against real hardware: an AMD SEV-SNP report and the real VCEK→ASK→ARK Milan chain (this is what surfaced and fixed the RSA-PSS bug). What is still not validated: the
/dev/sev-guestioctl offsets in_hw_providers(Azure uses the vTPM path instead - seesnp.extract_snp_report_from_hcl), and the NVIDIA GPU path, which stays provisional until an H100 CC report is captured. None of this closes the key-extraction hole (open question 8.8): a physically-extracted key produces a genuinely valid signature that passes every check. Wipe-on-lapse bounds exposure only if the clock cannot be stalled (trusted_time_source→time_floor) and only against an operator who cannot forge attestation. Intel TDX validation, GPU-side quote verification, and the reproducible reference KBS image are not here yet - see the repoROADMAP.md.
Layer 1 (authority):
models.py- the manifest schema as Pydantic v2 withextra="forbid", including the v0.8 fields (trusted_time_source,memory_fingerprint_challenge,attestation_revocation_check)._canonicalize.py- RFC 8785 (JCS), kept in sync with the agentrust-io family so a manifest signed by one tool verifies under another._signing.py- Ed25519 (standard profile), ML-DSA-65 (post-quantum, FIPS 204, via cryptography's native support), and an Ed25519+ML-DSA-65 hybrid where both must verify. One signature block per party, tagged withroleandsigner._verify.py- checks the required roles signed and every signature is cryptographically valid; enforces the sovereign quorum rule.cli.py-wcm keygen | sign | verify | inspect | gate | verify-quote | verify-provenance.inspectsummarizes a manifest;gateruns the release policy against a mock attestation and prints every check (a diagnostic, not a live release);verify-quote --kind {snp,tdx,gpu}verifies a captured quote bundle; andverify-provenancecross-verifies an OpenSSF model-signing signature (needs the[model-signing]extra).
Layer 2 (release gate):
_challenge.py- single-use KBS nonces with expiry (kbs-nonce-required).attestation.py- evidence models: a CPU CVM quote and a separate GPU report echoing the same nonce, plus the v0.8 memory-fingerprint response.providers.py-AttestationProviderinterface + aSoftwareProvidermock (no hardware root of trust; for tests and local dev only)._hw_providers.py- hardware producers:SevSnpProvider/TdxProvider(CPU quote via/dev/sev-guest//dev/tdx-guest),AzureSnpVtpmProvider(SEV-SNP on an Azure CVM via the vTPM NV0x01400001paravisor path - the flow validated on a live Azure host),NvidiaCcProvider(GPU report via an external tool),HardwareCompositeProvider, andselect_provider()(auto-select, software fallback). The SEV-SNP ioctl path is validated on a live GCP N2D guest. TDX report generation is validated on a live GCP C3 guest, while remote quote generation remains provisional; the Azure vTPM extraction is validated.kbs.py-KeyBrokerService: composite verification (nonce, platform, assurance tier, serving-image status + prefer-current, GPU measurement and CPU↔GPU binding, memory-fingerprint, revocation freshness, optional cryptographic quote verification) and gated release._quote_verify.py-QuoteVerifier: X.509 cert-chain validation + report-signature check + nonce binding, with a pluggableTrustStoreandQuoteParser. Wire it into the KBS viacpu_quote_verifier=; when unset, the gate saysstructural trust onlyin its check detail.
Wipe-on-lapse (runtime custody):
custody.py-EnclaveSession: holds a released key for the cadence window, renews onreattest(), zeroizes on lapse;use_key()never serves past the deadline.time_floorreports how much the bound is worth given the manifest'strusted_time_source. For the hybrid,max_operationsanchors the serving case: after N operationsuse_key()raisesReattestationRequired(the key is not wiped) until the session re-attests. After an admitted runtime opens the key once,authorize_operation()applies the same lease and budget checks to later inference operations without returning another key copy. The initialuse_key()call and every authorization each count as one operation.memory_sweep.py- full-range write/readback with nonce-derived page values and permutations, controlled alias detection, a signed transcript, and fail-closed verification against a policy-pinned protected-runtime key.BytearrayMemoryRangeis the executable reference adapter; production must supply an adapter over memory actually owned by the protected boundary.
For verifiable renewal, use a fresh KBS challenge and evidence rather than calling
the low-level reattest() compatibility method:
challenge = kbs.issue_challenge()
evidence = provider.produce(
challenge,
serving_image_measurement=current_measurement,
gpu_measurement=required_gpu_measurement,
)
renewal = kbs.verify_for_renewal(manifest, evidence)
session.apply_renewal(manifest, renewal)The initial release pins the KBS renewal public key. The renewal decision is short-lived, signed, single-use at the session, and bound to hashes of the fresh challenge, complete evidence, signed manifest, and weights. It contains no model key. A KBS restart with an unpersisted renewal signer safely prevents renewal; production deployments should inject a protected persistent signing key.
Layer 4 (derivative lineage):
lineage.py-verify_lineage(manifests, leaf_hash)walksderived_fromto the root, returning the chain, cycles/missing-parent violations, and policy notes. A parent's structuredderivativespolicy (none/fine-tune-only/unrestricted) is enforced; the freeformpermitted_derivativeslegal string is left to human review.derived_fromandrights_holderare under the joint signature.
Transparency (authority-layer integrity):
_merkle.py- RFC 9162 Merkle tree: inclusion and consistency proofs.transparency.py-TransparencyLog: append manifests / revocations / measurement-set changes, emit Ed25519 signed tree heads, and prove inclusion and append-only growth.find()lets a monitor detect a missing expected entry (a suppressed revocation). Verification (verify_sth/verify_inclusion/verify_log_consistency) needs only the proofs and heads, not the store.
Sovereign self-custody:
threshold.py-split_secret(key, threshold=t, shares=n)/combine_shares(): Shamir Secret Sharing over GF(256). Anytshares reconstruct the key; anyt-1reveal nothing, so no single custodian (the builder included) can self-release (SPEC §3.5, decision 15).
Post-quantum profile:
- ML-DSA-65 and hybrid signing live in
_signing.py;VerificationContextgainsadd_ml_dsa65_key()/add_hybrid_key(), andverify_manifestdispatches per signature by algorithm. Uses cryptography's native ML-DSA (no external liboqs); on a cryptography build without it, PQ raises a clear error while the rest of the package keeps working.
AMD SEV-SNP (vendor quote verification):
snp.py-parse_snp_report(v3 ABI),verify_snp_report_signature(VCEK, ECDSA P-384, AMD's little-endian r‖s),extract_snp_report_from_hcl(Azure vTPM0x01400001wrapper), andSnpQuoteParser, which plugs a report + its VCEK/ASK/ARK chain into the genericQuoteVerifier. Validated against a live Azure SEV-SNP report; the real public AMD Milan chain is a CI fixture.
wcm.server.create_app(kbs) builds a FastAPI surface for the key broker
(POST /challenge, POST /release, GET /health) with the same semantics as
the library KeyBrokerService. Install the extra: pip install ".[server]".
from wcm import KeyBrokerService, manifest_identity
from wcm.server import create_app
app = create_app(KeyBrokerService(
{manifest.weights_hash: key_bytes},
trusted_manifest_identities={manifest_identity(manifest)},
))
# uvicorn module:appReference-only: configure channel binding before exposing the app. The image-built server requires it and returns only a key sealed to the attested transport key.
pip install -e ".[dev]" # from this python/ directory# 1. keys for the two required parties (writes builder + builder.pub, etc.)
wcm keygen --out builder
wcm keygen --out custodian
# 2. joint signature over the example manifest
wcm sign examples/manifest.example.json \
--role builder --signer example-builder --key-file builder --out signed.json
wcm sign signed.json \
--role custodian --signer opaque-systems --key-file custodian --out signed.json
# 3. verify against the two trusted public keys
wcm verify signed.json --key-file builder.pub --key-file custodian.pub
# -> {"ok": true, ...} (exit 0)Keys are read from files, never passed on the command line: a private key on
argv leaks into process listings and shell history, and a base64url key can
start with -.
from wcm import (
WeightCustodyManifest, Ed25519Signer, generate_ed25519,
VerificationContext, verify_manifest,
)
import json
manifest = WeightCustodyManifest.model_validate(
json.load(open("examples/manifest.example.json"))
)
builder, custodian = generate_ed25519(), generate_ed25519()
sigs = [
Ed25519Signer(builder).sign(manifest.unsigned_dict(), role="builder", signer="example-builder"),
Ed25519Signer(custodian).sign(manifest.unsigned_dict(), role="custodian", signer="opaque-systems"),
]
manifest = manifest.with_signatures(sigs)
ctx = VerificationContext()
ctx.add_key(builder.public_bytes)
ctx.add_key(custodian.public_bytes)
print(verify_manifest(manifest, ctx).ok) # Truefrom wcm import KeyBrokerService, SoftwareProvider, WeightCustodyManifest, manifest_identity
import json
manifest = WeightCustodyManifest.model_validate(
json.load(open("examples/manifest.example.json"))
)
# The KBS holds the decryption key keyed by weights_hash.
kbs = KeyBrokerService(
{manifest.weights_hash: b"the-decryption-key"},
trusted_manifest_identities={manifest_identity(manifest)},
)
# 1. KBS issues a fresh nonce. 2. Enclave attests over it (mock here).
challenge = kbs.issue_challenge()
evidence = SoftwareProvider().produce(
challenge,
serving_image_measurement="sha256:" + "5e2d" * 16, # a 'current' accepted image
gpu_measurement="nvidia-rim:driver+vbios golden measurement id",
)
# 3. Composite verification, then gated release.
decision = kbs.verify_and_release(manifest, evidence)
print(decision.released) # True
print(decision.key) # b"the-decryption-key"
# On failure: decision.released is False and decision.failures names each check.from wcm import EnclaveSession
# The enclave takes custody of the key the KBS just released.
session = EnclaveSession.from_release(manifest, decision)
session.use_key() # serves while holding
session.reattest() # renew before the cadence window closes
print(session.time_floor) # 'sound' for secure-tsc, 'weaker' for the hybrid, 'none' otherwise
# If the window lapses without a re-attestation, the key is zeroized, not suspended:
# session.use_key() -> raises KeyWipedErrorRunnable end-to-end demos live in the public examples repo,
agentrust-io/examples/weight-custody-manifest,
where they depend on the published weight-custody-manifest package: the full
six-step flow on an open-weight model, a 2-of-3 sovereign threshold release, and
an offline SEV-SNP quote replay. They are honest about the open-weight reframe
(base-weight secrecy is moot; the work is integrity, license, and derivative
custody).
pytestApache-2.0. Crypto primitives adapted from the agentrust-io/agent-manifest SDK.