"""The LTL website, served at the log's base path — one self-contained HTML page (inline CSS + inline SVG, no external assets: works air-gapped behind any reverse proxy). Rendered from the LIVE log state, so the graphic and every number on the page are the accumulator, not a brochure about it.""" from __future__ import annotations from html import escape from typing import Any from pacta.transparency import node_hash from .transparency_log import LogEntry, TransparencyLog _STYLE = """ :root{--ink:#1c2430;--ink2:#5a6675;--line:#dde2e9;--ok:#1e7f4f;--okbg:#e2f2e9; --warn:#a86a10;--warnbg:#fdf0da;--accent:#3b4d8f;--accentbg:#eef0f7;--bg:#f8f9fa} *{box-sizing:border-box} body{font-family:system-ui,sans-serif;max-width:66rem;margin:0 auto;padding:2rem 1.2rem 4rem; color:var(--ink);line-height:1.6;background:var(--bg)} h1{font-size:2rem;margin:.2rem 0 0;letter-spacing:-.01em} h2{font-size:1.2rem;margin-top:2.6rem;border-bottom:2px solid var(--line);padding-bottom:.3rem} .tagline{font-size:1.05rem;color:var(--ink2);max-width:46rem} code,pre{font-family:ui-monospace,Menlo,Consolas,monospace;background:#eef0f3;border-radius:4px} code{padding:.1rem .3rem;font-size:.9em} pre{padding:.9rem;overflow-x:auto;font-size:.85rem} table{border-collapse:collapse;width:100%;font-size:.93rem;background:#fff} td,th{border:1px solid var(--line);padding:.5rem .7rem;text-align:left;vertical-align:top} th{background:var(--accentbg)} .pill{display:inline-block;border-radius:9px;padding:.08rem .6rem;font-size:.78rem;font-weight:600} .ok{background:var(--okbg);color:var(--ok)} .warn{background:var(--warnbg);color:var(--warn)} .acc{background:var(--accentbg);color:var(--accent)} .muted{color:var(--ink2);font-size:.9rem} .card{background:#fff;border:1px solid var(--line);border-radius:8px;padding:1rem 1.2rem;margin:.8rem 0} .steps{counter-reset:s} .steps .card{position:relative;padding-left:3.2rem} .steps .card::before{counter-increment:s;content:counter(s);position:absolute;left:1rem;top:1rem; width:1.6rem;height:1.6rem;border-radius:50%;background:var(--accent);color:#fff; display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.9rem} svg{max-width:100%;height:auto;display:block;margin:1rem auto;background:#fff; border:1px solid var(--line);border-radius:8px} a{color:var(--accent)} .legend{display:flex;gap:1.4rem;flex-wrap:wrap;font-size:.85rem;color:var(--ink2);justify-content:center} .sw{display:inline-block;width:.8rem;height:.8rem;border-radius:3px;vertical-align:-1px;margin-right:.3rem} """ def _leaf_ok(entry: LogEntry) -> bool: certificates = ((entry.leaf.get("attestation") or {}).get("certificates")) or [] return bool(certificates) and all( certificate.get("status") == "proven" and certificate.get("axiom_status") == "clean" for certificate in certificates ) def _leaf_short(component: str) -> str: """Compact display name for a leaf box at small spans.""" return (component.replace("-ed25519-verified", "") .replace("ltl-accumulator-verified", "accum") .replace("fips205-slhdsa-verified", "slh-dsa")) def _svg_tree(entries: list[LogEntry], root_hex: str, signing_backend: str, head_label: str = "Ed25519") -> str: """The accumulator, drawn from its real leaves.""" if not entries: return "
(log is empty)
" hashes = [bytes.fromhex(entry.leaf_hash) for entry in entries] levels: list[list[bytes]] = [hashes] while len(levels[-1]) > 1: level = levels[-1] nxt = [node_hash(level[i], level[i + 1]) for i in range(0, len(level) - 1, 2)] if len(level) % 2: nxt.append(level[-1]) levels.append(nxt) width, level_gap = 1000, 86 height = 150 + level_gap * len(levels) out = [f'") return "".join(out) def _trust_anchor_html(log: TransparencyLog, metadata: dict[str, Any], base: str, mirror: str) -> str: """The provider public key, displayed in full on the front page. The key is the one thing a consumer takes on trust, once - hiding it behind a path would invert the page's priorities.""" key_path = log.log_dir / "provider.ed25519.pub" fingerprint = str(metadata.get("ed25519_public_key_fingerprint_sha256", "")) if not key_path.is_file(): return ( 'Second, additive anchor — post-quantum. Heads from tree 14 on additionally carry a deterministic SLH-DSA-SHA2-128s (FIPS 205) signature over the same payload. The Ed25519 signature above remains the one every consumer must check; this one is checked where tooling allows (OpenSSL ≥ 3.5). Its verify path is the proof subject of leaf 18.
{slh_pem}
SHA-256 fingerprint {slh_fp}
· raw: {base or ''}/log-slhdsa-public-key
· mirror: provider.slhdsa.pub
This key is the required cryptographic identity anchor — the one every consumer must check: it authenticates that these statements were made by the operator (the same party the artifacts call “the provider”). It does not, by itself, make those statements true — each attestation's truth additionally rests on the replay, theorem, extraction and toolchain assumptions stated in that leaf (one signed entry of the tree below). Every tree head and attestation is signature-checked against this key. Pin it (save your own copy; from then on trust only what checks against that copy), and compare this copy byte-for-byte with the independently hosted mirror copy; they must be identical. The first fetch is trust-on-first-use; the two-host byte-comparison is what bounds it.
{pem}
SHA-256 fingerprint {escape(fingerprint)}
· raw: {base or ''}/log-public-key
· curl -s https://ltl.zkdefi.org/log-public-key
{escape(c)}What happened here, in plain terms: we took real cryptographic code — four production Ed25519 signature libraries and the verification path of SLH-DSA (FIPS 205), the post-quantum signature standard — and machine-checked mathematical proofs about it with the Lean 4 proof assistant. Re-checking those proofs yourself takes a toolchain and about half an hour of compute per library. This site is the shortcut that does not ask for blind trust: a public, tamper-evident ledger of signed statements about every proof check we ran — so you decide how much of our work you re-verify, from a millisecond signature check to redoing everything.
The same thing, in one precise sentence: a public, append-only Merkle accumulator (a hash tree that only ever grows) of signed statements that the Lean 4 formal proofs of specific cryptographic Rust libraries, at specific git commits, re-check by machine with exactly their documented assumptions — so that you can trust a proof result by checking one required signature (Ed25519) and ~{max(1,(latest.get('tree_size') or 1).bit_length())} hashes in milliseconds, instead of running a theorem prover for hours.
Every rung below is a legitimate place to stand. Each states what you still take on trust, what you do, what it costs, and what you know afterwards. Climb one rung at a time — the whole service is built so that you can.
git clone https://github.com/saymrwulf/lean-transparency-log && cd lean-transparency-log && python3 verify.py --allPython plus the system
openssl binary; fails closed without it.
Afterwards you hold every leaf and every Signed Tree Head (STH) ever issued. If the
operator ever shows anyone a conflicting history, your copy exposes it — you are a
witness. A split view (the operator showing different histories to different
consumers) survives only until two witnesses compare.pacta receipt-verify --attestation … --receipt … --log-public-key provider.ed25519.pubThe
pacta CLI ships in the
pacta repository
(pip install . from a clone); a one-page Python core (the paper’s
Appendix C) does the same check without it. Add --sth-store pins.json to
remember every head you accept. Afterwards the exact claim — repository, commit,
theorems, assumptions — is cryptographically pinned to the operator’s key inside an
append-only history: he can never rewrite or deny it. What he observed, you
have not yet checked.pacta automates the comparison, and lecture 11 of the
Jupyter course
walks through it.
Afterwards every verdict is your verdict, re-derived from your
own ruler; operator labels can veto but never grant acceptance (details in
“You hold the ruler” below).verification/check.sh) with a Lean 4
toolchain: the kernel re-checks every certificate on your machine and the axiom
audit prints the exact assumption cones.
Afterwards the theorem prover accepted on your hardware —
the operator is out of the loop entirely.extract.sh, pinned
toolchain versions, and byte-pinned generated models for comparison), re-read the
theorem statements against FIPS 205 / RFC 9162 / the curve equations, and re-prove
or audit each certificate.
Afterwards you have reproduced the estate and no longer need us —
which is the point. There is no rung above this one: even here you trust a kernel, a
compiler, and your silicon. Anyone offering zero trust is selling something.verified attestation (all certificates proven, axiom cones boundary-exact) historical audit-failure attestation — kept forever; an append-only ledger does not erase its bad day (leaves 0–3: an early audit round that failed; leaves 4–7 re-attest the same four libraries cleanly)
Every box above is computed from the live log at page render — leaf hashes,
internal nodes, the root, and the signature are the real ones. The library that signs the log is itself an entry in the log — what that entry proves is its verify path (no signing code is proven, here or anywhere) — and it checks its own entry before signing. In detail: before signing this
root, the provider Merkle-verified its own signing library's leaf
(index {provenance.get('signing_library_leaf_index','?')},
certificates {escape(str(provenance.get('signing_library_certificates_proven','?')))})
against this very tree — so the signed tree contains an attestation of the source the
operator reports its signing binary was built from. (An Ed25519 signature cannot by itself prove
which binary generated it; execution provenance is reported, not proven, and the provenance
fields live in the unsigned signature metadata.) Tree size {latest.get('tree_size',0)},
log id {escape(str(metadata.get('log_id',''))[:16])}….
To benefit from the accumulator you need exactly three files per
library, plus optionally the additive post-quantum key
(provider.slhdsa.pub) and the whole mirror. Nothing else.
| # | Artifact | What it is | Where |
|---|---|---|---|
| 1 | provider.ed25519.pub |
The identity anchor. The provider's public key — the required cryptographic identity you pin. It authenticates the operator's statements; their truth rests on each leaf's stated assumptions. Fetch it from BOTH independent locations and compare; the copies must be identical. | this site · mirror |
| 2 | <library>.attestation.json |
The claim. Which repo, which exact git commit, which theorems, which observed axiom cones (the exact set of assumptions each proof ultimately rests on), what machine protection — signed by the provider. | table below, or mirror entries/ |
| 3 | <library>.receipt.json |
The proof of inclusion. Binds artifact 2 into the signed tree:
leaf index, sibling hashes, the Signed Tree Head (STH). A one-page Python core verifies it — printed as Appendix C of the paper; the shipped verify.py wraps that core with full fail-closed binding checks (stdlib hashing; signature checks shell out to the openssl binary). |
table below, or mirror receipts/ |
| + | the full mirror clone | Maximal benefit: become a witness. Every leaf + every signed head
ever issued + verify.py (Python stdlib + the openssl binary for
signatures; fails closed without them). python3 verify.py --all
recomputes the entire tree and every historical head — you then hold a retained view that can
later EXPOSE a conflicting head shown to someone else. (A single clone cannot by itself prove the
log never split its view toward another consumer; that requires comparing heads across
consumers.) |
git clone {mirror} |
| component | artifact 2 | artifact 3 | status |
|---|
One certificate = one machine-checked theorem together with its exact assumption set (its axiom cone).
attestation.json. Honesty about the boundary is the product.Humans never need these directly; every link on this page already uses them. They
exist so that your software — a CI job, an autonomous agent, a package
resolver — can consume the log without scraping HTML. The pacta CLI
builds on them: STH pinning, freshness policy, risk scoring (R0–R5, six named
residual-risk classes) with policy-gated consequences, and optionally
--require-verified-verifier, which checks every signature through the
proof-attested Ed25519 code path itself.
GET {base}/v1/sth latest Signed Tree Head
GET {base}/v1/sth-history the published head history (witness material)
GET {base}/v1/sth-consistency?first=N consistency proof from your pinned size
GET {base}/v1/proof?component=NAME inclusion proof (artifact 3, freshly issued)
GET {base}/v1/attestation?component=NAME the claim (artifact 2)
GET {base}/v1/entries?start=N&end=M raw leaves
GET {base}/v1/metadata log identity
GET {base}/healthz
sn = 0),
zero divergences after the one-line restoration, confirmed by a three-way regression.
New in the August 2026 revisions: the deployment evaluated to its current nineteen-leaf, dual-signed state, an
instantiation section for the SLH-DSA (FIPS 205) verify path — eleven certificates,
five uninterpreted hash oracles, exact cones — and a certificate appendix mirroring the
Ed25519 tiers.python3 verify.py --all
re-verifies all of it, paper-era and after, from a clone of the mirror.Log heads are signed offline; this service is read-only and holds no key material. Provider tooling, agent tooling, and the full Jupyter course live in the pacta repository.
"""