mirror of
https://github.com/saymrwulf/proof-aware-crypto-tooling-agent.git
synced 2026-09-03 19:53:43 +00:00
The trust architecture has exactly two roles and the curriculum now
mirrors that split structurally - the conceptual burden is the design,
stated as such to the student:
- 06a THE PROVIDER'S SIDE (singleton). Domain banner in the provider's
voice. The full build pipeline run live in a scratch log made from
the REAL attestations: verify (Lean replay = the leaf-making step,
the only expensive one - the shipped evidence IS its output) -> leaf
(0x00 domain separation) -> tree -> STH signed via the MERKLEIZED
LIBRARY -> the self-inclusion check embedded in the signature block.
A generated SVG draws the student's own tree: leaves, internal
nodes, root, and the signature box, framed in the provider's domain
color. Closes with the singleton-vs-many justification table
(key/cost/obligation asymmetry) and exercises.
- 06b THE AGENT'S SIDE (one of many). Domain banner in the agent's
voice: you own the public key, the evidence files, ~25 lines of
hashing - and explicitly NO Lean. The COMPLETE RFC 9162 inclusion
verifier is implemented from scratch in one cell (hashlib only, no
pacta imports for the core) and run against the REAL dalek receipt
(leaf 4 of 8, three siblings, dogfood-signed root); then the STH
signature, the provider's signing_provenance read and interpreted
(why the agent still re-checks inclusion itself), the pin store, and
an SVG of the real log with the agent's path highlighted against the
grey leaves it never needs. Cost line: ~4 hashes + 1 signature.
- Lecture 06 now routes students into the pair and states the mirror
rule ("if you cannot say which notebook a step belongs to, you have
not understood the step"); lecture 09 records that dogfood now runs
in BOTH directions; course map + README updated.
Every cell of 06a/06b/09 executed against the real evidence before
commit (SVGs render in Jupyter, fail soft in plain exec). One
generation bug found and fixed: a single-backslash \\x00 in the
generator produced a literal NUL byte in a cell. 50/50 tests green
with the notebook inventory at twelve.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
256 lines
12 KiB
Text
256 lines
12 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Lecture 6a: THE PROVIDER'S SIDE - Building the Authenticated Structure\n",
|
|
"\n",
|
|
"> **DOMAIN BANNER - read this first.** In this notebook YOU ARE THE\n",
|
|
"> PROVIDER. There is exactly **one** of you per log. You hold the\n",
|
|
"> signing key. You own a Lean toolchain and hours of compute. You\n",
|
|
"> carry the append-only obligations. Nothing in this notebook is\n",
|
|
"> ever executed by an agent - and that asymmetry is not an\n",
|
|
"> implementation detail, it is the entire design (see the\n",
|
|
"> justification at the end).\n",
|
|
"\n",
|
|
"The provider's job, end to end: **verify -> leaf -> tree -> sign ->\n",
|
|
"self-check**. Only the first step involves Lean; everything after\n",
|
|
"is hashing and one signature.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Learning Objectives\n",
|
|
"\n",
|
|
"- Build the full authenticated data structure from real attestations: leaves, tree, Signed Tree Head.\n",
|
|
"- Place the Lean verification correctly: it is the LEAF-MAKING step, the only expensive one, and it never travels to the agent.\n",
|
|
"- Sign the root with the merkleized library and run the provider's own inclusion self-check (\"the provider eats its own dogfood\").\n",
|
|
"- Justify the singleton/many split as a design decision.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 1 - Verify (the expensive step, done ONCE)\n",
|
|
"\n",
|
|
"The leaf content is a signed **attestation**: the outcome of replaying\n",
|
|
"every Lean proof of one repository under lean-guard (~30 minutes of\n",
|
|
"kernel re-checking per fork on the reference machine). This notebook\n",
|
|
"does NOT re-run that - the shipped `evidence/` attestations ARE that\n",
|
|
"step's output. What matters architecturally: **the Lean cost lives\n",
|
|
"here and only here.** No agent ever pays it again.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from pathlib import Path\n",
|
|
"import sys\n",
|
|
"\n",
|
|
"repo_root = Path.cwd()\n",
|
|
"if not (repo_root / \"src\" / \"pacta\").exists():\n",
|
|
" repo_root = repo_root.parent\n",
|
|
"sys.path.insert(0, str(repo_root / \"src\"))\n",
|
|
"sys.path.insert(0, str(repo_root / \"provider\" / \"src\"))\n",
|
|
"\n",
|
|
"from pacta.yamlio import load_data\n",
|
|
"\n",
|
|
"attestations = {\n",
|
|
" fork: load_data(repo_root / \"evidence\" / f\"{fork}-ed25519.attestation.yaml\")\n",
|
|
" for fork in [\"dalek\", \"anza\", \"risc0\", \"betrusted\"]\n",
|
|
"}\n",
|
|
"for fork, att in attestations.items():\n",
|
|
" certs = att[\"certificates\"]\n",
|
|
" clean = sum(1 for c in certs if c[\"status\"] == \"proven\" and c[\"axiom_status\"] == \"clean\")\n",
|
|
" print(f\"{fork}: {clean}/{len(certs)} proven | commit {att['subject']['repo_commit'][:8]} | guard: {att['machine_protection']['lean_guard'].rsplit('/',1)[-1]}\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Steps 2+3 - Leaf and tree (cheap, mechanical)\n",
|
|
"\n",
|
|
"Each attestation is wrapped, canonically serialized, and hashed with\n",
|
|
"the RFC 9162 leaf prefix `0x00`; pairs of nodes hash with prefix\n",
|
|
"`0x01`. Build a REAL provider log in a scratch directory - you are\n",
|
|
"the provider, so mint your own key first:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import tempfile\n",
|
|
"from pacta.signing import generate_ed25519_keypair\n",
|
|
"from pacta_provider.transparency_log import TransparencyLog\n",
|
|
"\n",
|
|
"state = Path(tempfile.mkdtemp(prefix=\"provider-lecture-\"))\n",
|
|
"generate_ed25519_keypair(state / \"provider.key\", state / \"provider.pub\")\n",
|
|
"log = TransparencyLog(state / \"log\")\n",
|
|
"log.init(\"lecture-provider\", state / \"provider.pub\")\n",
|
|
"\n",
|
|
"receipts = {}\n",
|
|
"for fork, att in attestations.items():\n",
|
|
" att_path = state / f\"{fork}.attestation.yaml\"\n",
|
|
" from pacta.yamlio import dump_data\n",
|
|
" dump_data(att, att_path)\n",
|
|
" receipts[fork] = log.append_attestation(att_path, state / \"provider.key\", state / \"provider.pub\")\n",
|
|
"print(\"tree size:\", receipts[\"betrusted\"][\"tree_size\"])\n",
|
|
"print(\"root:\", receipts[\"betrusted\"][\"sth\"][\"root_hash\"][:32], \"\u2026\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Steps 4+5 - Sign the root, then CHECK YOURSELF\n",
|
|
"\n",
|
|
"The tree head is signed with the **merkleized library itself** (when\n",
|
|
"the dogfood binary is built): the Ed25519 code that signs this root\n",
|
|
"is the same pinned dalek source whose proof attestation is a leaf of\n",
|
|
"this very tree. Before signing, the provider runs the SAME Merkle\n",
|
|
"inclusion verification an agent would run - on its own signing\n",
|
|
"library's leaf, against the tree it is about to sign - and embeds\n",
|
|
"the verdict in the signature block. A root signature that names the\n",
|
|
"leaf vouching for the code that produced it:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import json\n",
|
|
"\n",
|
|
"sth = receipts[\"betrusted\"][\"sth\"]\n",
|
|
"ed = sth[\"signatures\"][\"ed25519\"]\n",
|
|
"print(\"signing backend:\", ed.get(\"signing_backend\"))\n",
|
|
"print(json.dumps(ed.get(\"signing_provenance\", {\"note\": \"dogfood binary not built on this host - OpenSSL fallback, provenance omitted\"}), indent=1))\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## The structure you just built, drawn from your own log\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from pacta.transparency import leaf_hash, merkle_root, node_hash\n",
|
|
"\n",
|
|
"def svg_merkle(leaf_hashes, highlight=None, title=\"\", domain=\"PROVIDER: builds every box below\", color=\"#1e7f4f\"):\n",
|
|
" n = len(leaf_hashes)\n",
|
|
" width, lh, lv = 980, 108, 92\n",
|
|
" levels = []\n",
|
|
" level = [bytes.fromhex(h) if isinstance(h, str) else h for h in leaf_hashes]\n",
|
|
" levels.append(level)\n",
|
|
" while len(level) > 1:\n",
|
|
" nxt = []\n",
|
|
" for i in range(0, len(level) - 1, 2):\n",
|
|
" nxt.append(node_hash(level[i], level[i + 1]))\n",
|
|
" if len(level) % 2:\n",
|
|
" nxt.append(level[-1])\n",
|
|
" levels.append(nxt)\n",
|
|
" level = nxt\n",
|
|
" height = 130 + lv * len(levels) + 60\n",
|
|
" out = [f'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{width}\" height=\"{height}\" font-family=\"monospace\" font-size=\"11\">']\n",
|
|
" out.append(f'<rect x=\"4\" y=\"4\" width=\"{width-8}\" height=\"{height-8}\" fill=\"none\" stroke=\"{color}\" stroke-width=\"2\" rx=\"8\"/>')\n",
|
|
" out.append(f'<text x=\"16\" y=\"24\" fill=\"{color}\" font-size=\"13\" font-weight=\"bold\">{domain}</text>')\n",
|
|
" out.append(f'<text x=\"16\" y=\"42\" fill=\"#555\">{title}</text>')\n",
|
|
" pos = {}\n",
|
|
" for li, lvl in enumerate(levels):\n",
|
|
" y = height - 70 - li * lv\n",
|
|
" span = width / (len(lvl) + 1)\n",
|
|
" for i, node in enumerate(lvl):\n",
|
|
" x = span * (i + 1)\n",
|
|
" pos[(li, i)] = (x, y)\n",
|
|
" hl = highlight and li == 0 and i == highlight[0]\n",
|
|
" sib = highlight and (li, i) in highlight[1]\n",
|
|
" fill = \"#fdf0da\" if sib else (\"#e2f2e9\" if hl else \"#f4f4f6\")\n",
|
|
" stroke = \"#a86a10\" if sib else (\"#1e7f4f\" if hl else \"#999\")\n",
|
|
" out.append(f'<rect x=\"{x-52}\" y=\"{y-16}\" width=\"104\" height=\"32\" fill=\"{fill}\" stroke=\"{stroke}\" stroke-width=\"{2 if (hl or sib) else 1}\" rx=\"4\"/>')\n",
|
|
" label = (\"leaf %d\" % i) if li == 0 else (\"root\" if li == len(levels) - 1 else \"node\")\n",
|
|
" out.append(f'<text x=\"{x}\" y=\"{y-3}\" text-anchor=\"middle\" fill=\"#333\">{label}</text>')\n",
|
|
" out.append(f'<text x=\"{x}\" y=\"{y+11}\" text-anchor=\"middle\" fill=\"#777\">{node.hex()[:10]}\u2026</text>')\n",
|
|
" if li > 0:\n",
|
|
" for ci in (2 * i, 2 * i + 1):\n",
|
|
" if (li - 1, ci) in pos:\n",
|
|
" cx, cy = pos[(li - 1, ci)]\n",
|
|
" out.append(f'<line x1=\"{x}\" y1=\"{y+16}\" x2=\"{cx}\" y2=\"{cy-16}\" stroke=\"#bbb\"/>')\n",
|
|
" rx, ry = pos[(len(levels) - 1, 0)]\n",
|
|
" out.append(f'<rect x=\"{rx-135}\" y=\"{ry-62}\" width=\"270\" height=\"30\" fill=\"#eef\" stroke=\"#3b4d8f\" rx=\"4\"/>')\n",
|
|
" out.append(f'<text x=\"{rx}\" y=\"{ry-42}\" text-anchor=\"middle\" fill=\"#3b4d8f\">Signed Tree Head: Ed25519(root) via merkleized library</text>')\n",
|
|
" out.append(f'<line x1=\"{rx}\" y1=\"{ry-32}\" x2=\"{rx}\" y2=\"{ry-16}\" stroke=\"#3b4d8f\"/>')\n",
|
|
" out.append(\"</svg>\")\n",
|
|
" return \"\".join(out)\n",
|
|
"\n",
|
|
"entries = log.entries()\n",
|
|
"leaf_hexes = [leaf_hash(e.leaf_bytes()).hex() for e in entries]\n",
|
|
"svg = svg_merkle(leaf_hexes, title=f\"your lecture log: {len(entries)} attestation leaves, root {merkle_root([e.leaf_bytes() for e in entries]).hex()[:16]}\u2026\")\n",
|
|
"try:\n",
|
|
" from IPython.display import SVG, display\n",
|
|
" display(SVG(svg))\n",
|
|
"except Exception:\n",
|
|
" print(svg[:200], \"\u2026 (open in a notebook to render)\")\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Why a singleton? The design justification\n",
|
|
"\n",
|
|
"| | Provider (this notebook) | Agent (next notebook) |\n",
|
|
"|---|---|---|\n",
|
|
"| How many | **exactly one** per log | unbounded |\n",
|
|
"| Owns | the signing key, the Lean toolchain, the full log | the provider's PUBLIC key, a pin file |\n",
|
|
"| Pays | hours of kernel time per repo, ONCE | milliseconds per check, forever |\n",
|
|
"| Obligations | append-only, sign every head, serve proofs, self-verify | pin every head, demand consistency |\n",
|
|
"| Can be wrong? | detectably: signatures + pins make lies attributable | fails closed |\n",
|
|
"\n",
|
|
"The asymmetry is the product. If agents had to run Lean, the service\n",
|
|
"would add nothing; if the provider's claims weren't pinned and\n",
|
|
"signed, trust would be a rumor. Every artifact in this course lives\n",
|
|
"on exactly one side of this table - and the split between this\n",
|
|
"notebook and the next MIRRORS it on purpose: if you cannot say\n",
|
|
"which notebook a step belongs to, you have not understood the step.\n",
|
|
"\n",
|
|
"## Exercises\n",
|
|
"\n",
|
|
"- Append a fifth attestation (edit one field of a copy) and watch the root change; which internal nodes changed and which did not? Explain from the tree shape.\n",
|
|
"- The self-inclusion check ran against the tree BEFORE your key existed in any leaf. What does `signing_provenance.self_inclusion` say, and why is recording that honest?\n",
|
|
"- Cost accounting: with 4 repos x 30 minutes of Lean and N agents x 5 ms of verification, at what N does the provider model beat every-agent-verifies-locally? (Hint: N=1.)\n",
|
|
"- Design question: what breaks if there are TWO providers with one key? With two keys and one log?\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"name": "python",
|
|
"pygments_lexer": "ipython3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|