From 3aa8d4b2bf5a6cedcf329ef4fee940ed7dd1ec45 Mon Sep 17 00:00:00 2001
From: mrwulf
Date: Tue, 7 Jul 2026 09:45:35 +0200
Subject: [PATCH] LTL front page: display the trust anchor in full, above the
fold
The provider public key is the one thing a consumer takes on trust;
hiding it behind /log-public-key inverted the page's priorities. New
'The trust anchor - pin this key' section at the top of the docs page
shows the PEM in full with its SHA-256 fingerprint, the mirror-compare
instruction, and the raw endpoint for scripts. Honest 'missing' card if
a deployment lacks the key. Test asserts the page renders the key.
Co-Authored-By: Claude Fable 5
---
provider/src/pacta_provider/webdocs.py | 28 ++++++++++++++++++++++++++
tests/test_web_and_witness.py | 12 ++++++++---
2 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/provider/src/pacta_provider/webdocs.py b/provider/src/pacta_provider/webdocs.py
index fc0e8a7..8216406 100644
--- a/provider/src/pacta_provider/webdocs.py
+++ b/provider/src/pacta_provider/webdocs.py
@@ -101,6 +101,31 @@ def _svg_tree(entries: list[LogEntry], root_hex: str, signing_backend: str) -> s
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 (
+ 'missing This deployment '
+ "does not expose its public key in the log directory - fetch it from the "
+ f'
mirror instead.
'
+ )
+ pem = escape(key_path.read_text(encoding="utf-8").strip())
+ return f"""
+
This key is the only thing you take on trust, once.
+Everything else on this page - every attestation, every tree head - is verified against it.
+Pin it, and compare this copy byte-for-byte with the independently hosted
+mirror copy; they must be identical.
+
{pem}
+
SHA-256 fingerprint {escape(fingerprint)}
+ · raw: {base or ''}/log-public-key
+ · curl -s ltl.zkdefi.org/log-public-key
+
"""
+
+
def render_docs(log: TransparencyLog, base_path: str) -> str:
base = "/" + base_path.strip("/") if base_path.strip("/") else ""
metadata = log.metadata()
@@ -142,6 +167,9 @@ their documented assumptions — so that you can trust a proof result by ch
one signature and ~{max(1,(latest.get('tree_size') or 1).bit_length())} hashes in
milliseconds, instead of running a theorem prover for hours.
+The trust anchor — pin this key
+{_trust_anchor_html(log, metadata, base, mirror)}
+
The accumulator, live
{tree_svg}
diff --git a/tests/test_web_and_witness.py b/tests/test_web_and_witness.py
index f3281bf..298629d 100644
--- a/tests/test_web_and_witness.py
+++ b/tests/test_web_and_witness.py
@@ -31,7 +31,11 @@ def _make_log(tmp_path, n=3):
def test_web_endpoints_and_online_proof_roundtrip(tmp_path):
# root mount: the production shape (ltl.zkdefi.org serves from /)
+ import shutil
+
_make_log(tmp_path)
+ # trust anchor present at render time: front page must display it in full
+ shutil.copy2(tmp_path / "k.pub", tmp_path / "log" / "provider.ed25519.pub")
server = serve(str(tmp_path / "log"), port=0)
port = server.server_address[1]
threading.Thread(target=server.serve_forever, daemon=True).start()
@@ -60,11 +64,13 @@ def test_web_endpoints_and_online_proof_roundtrip(tmp_path):
assert r.headers["Content-Type"] == "application/pdf"
assert r.read(5) == b"%PDF-"
# the site's copy of the trust anchor (TOFU: two independent locations)
- import shutil
-
- shutil.copy2(tmp_path / "k.pub", tmp_path / "log" / "provider.ed25519.pub")
with urllib.request.urlopen(base + "/log-public-key", timeout=10) as r:
assert r.read() == (tmp_path / "k.pub").read_bytes()
+ # and the front page displays the key IN FULL, above the fold
+ with urllib.request.urlopen(base + "/docs", timeout=10) as r:
+ page = r.read().decode()
+ assert "BEGIN PUBLIC KEY" in page
+ assert "pin this key" in page.lower()
finally:
server.shutdown()