Key Transparency: registry membership as a Strict-tier requirement
Adds a public, append-only key-transparency registry to the LLMO protocol. Strict-tier documents MUST be registered. Consumers cross-check the publisher's deployed JWKS against the registry record to detect domain takeover, key substitution, and rogue republishing. The registry is hash-committed with SHA-384 so the historical record survives the eventual post-quantum migration of signature algorithms.
1. Abstract
This LIP introduces an LLMO Key Transparency (KT) registry as a normative component of the protocol. A KT registry is a public, append-only log of (domain, kid, jwk_thumbprint, doc_url, doc_id, observed_at) records, each signed by the publisher’s private key. The registry is hash-committed: a periodic signed snapshot of the log’s content lets any party detect retroactive modification of historical entries.
A new conformance rule, X7, is added to spec §5.3 (Strict tier): a document achieves Strict tier only if its current signing key is recorded in the KT registry under the publisher’s domain at fetch time. Documents whose publishers have not registered their key are evaluated at Standard tier with an explicit kt_uninlogged note.
The hash function used throughout the registry is SHA-384, providing approximately 128-bit collision resistance against Grover-style quantum search. This makes the registry an immutable audit trail of pre-quantum signing activity, surviving the eventual migration of LLMO’s signature algorithms to post-quantum primitives.
2. Motivation
LLMO v0.1 publishes signed claims at a publisher-controlled URL (/.well-known/llmo.json) verified against a publisher-controlled JWKS (/.well-known/llmo-keys.json). The trust model rests on the publisher’s continuing control of their domain. Three failure modes within this model are not detectable by a conforming v0.1 consumer:
- Domain takeover. An attacker who compromises a publisher’s domain can replace
/.well-known/llmo.jsonand/.well-known/llmo-keys.jsonwith documents signed by an attacker-held key. A consumer who has not previously fetched the publisher’s document has no out-of-band reference to compare against. - Silent key substitution. A publisher who rotates keys may, accidentally or maliciously, publish a JWKS that omits the previous public key while continuing to claim continuity of identity. Cached documents signed under the previous key no longer verify against the live JWKS, and consumers cannot distinguish this from cache corruption or transit error.
- Republishing with a forged signature in the post-quantum window. When sufficient quantum computing capability becomes available (Shor’s algorithm on a publisher’s ECDSA P-256 or EdDSA key), an attacker can forge a signature over arbitrary content under a known-compromised public key. Conforming consumers can no longer rely on the cryptographic signature alone to authenticate historical documents.
A public, append-only KT registry addresses all three:
- A consumer fetching a publisher’s llmo.json can cross-check the JWK thumbprint against the registry entry for that domain. Mismatch flags domain takeover or silent key substitution.
- A consumer evaluating a historical document can verify that its signing key was registered before the relevant timestamp. Documents signed by keys never registered at the relevant point in time are not authenticated by the protocol regardless of the cryptographic signature’s mathematical validity.
- A signed snapshot of the log root, taken at time T, commits to the log’s contents at time T. Once the signature scheme over the document is broken (post-Shor), the registry’s hash-based commitment to “this key was registered at this time” remains.
This LIP also formalizes the publisher-side and consumer-side mechanics: how a publisher writes an entry, how the registry verifies it, how a conforming consumer evaluates a document against the registry, and how the registry’s own integrity is maintained.
3. Specification
3.1 JWK Thumbprint
A KT entry references a publisher’s signing key by its JWK Thumbprint per RFC 7638, computed with SHA-384.
The thumbprint is:
thumbprint = base64url(SHA-384(JCS(JWK)))
where JCS(JWK) is the JSON Canonicalization Scheme (RFC 8785) serialization of the JWK with only the members required by RFC 7638 Section 3 for the key type (e.g., for an EC key: crv, kty, x, y).
RFC 7638 Section 3.4 explicitly permits the use of hash functions other than SHA-256; SHA-384 is conformant. SHA-384 is chosen to provide approximately 128-bit collision resistance under Grover-style quantum search, compared to approximately 85-bit for SHA-256. This selection applies to all hash computations in this LIP unless otherwise noted.
A publisher’s JWK Thumbprint changes if and only if the public key changes. A change of kid alone, with the same public key material, produces the same thumbprint.
3.2 Entry format
Each entry submitted to the KT registry is a compact JWS (RFC 7515) signed by the publisher’s signing key. The JWS structure is:
protected header: {
"alg": <ES256 | ES384 | EdDSA, matching the publisher's signing algorithm>,
"kid": <the publisher's kid>,
"typ": "llmo-kt-entry+jws",
"jwk": <the publisher's public JWK, per RFC 7515 §4.1.3, containing only public-key parameters for the key type>
}
payload: {
"domain": "<the publisher's primary_domain>",
"kid": "<must match the protected header kid>",
"jwk_thumbprint": "<SHA-384 thumbprint of the public key per Section 3.1>",
"doc_url": "https://<domain>/.well-known/llmo.json",
"doc_id": "<the document_id of the current signed document>",
"observed_at": "<RFC 3339 timestamp at which the entry was prepared>"
}
signature: <publisher's signature over the JCS-canonicalized protected header concatenated with the JCS-canonicalized payload, per the standard JWS profile in spec §4.3.1>
The jwk header parameter carries the publisher’s public key inline so the entry is self-contained: a verifier (registry, consumer, archival auditor) can validate the signature without a network fetch to the publisher’s /.well-known/llmo-keys.json. The JWK MUST contain only the public-key parameters required by RFC 7638 §3 for the key type (e.g., kty, crv, x, y for an EC key); it MUST NOT contain private-key parameters (d). The registry MUST reject any entry whose jwk header includes private-key parameters.
The registry MUST verify the entry by:
- Extracting the JWK from the protected header.
- Computing the JWK Thumbprint per §3.1.
- Confirming the computed thumbprint equals the payload’s
jwk_thumbprint. - Verifying the JWS signature against the extracted JWK using the algorithm declared in the protected header’s
alg.
The registry MUST reject any entry whose signature does not verify, whose computed thumbprint does not match the payload’s declared thumbprint, or whose kid in the protected header does not match the kid in the payload. This binding closes the loop: only a party holding the private key corresponding to the inline public key can produce a valid entry, and the thumbprint pin prevents substitution of a different key after the fact.
Including the JWK inline rather than referencing it by thumbprint alone is a deliberate trade-off: each entry is approximately 150 bytes larger than the thumbprint-only design, but the entry becomes verifiable by any party without out-of-band fetches to the publisher’s domain. This matters in particular for archival verification (a consumer evaluating a historical document years later may be unable to reach the publisher’s domain even if the registry entry remains available) and for federated registry verification (cross-witness operators verify entries against each other without needing to fetch from publisher domains).
3.3 Registry API
A conforming registry implementation exposes the following HTTP endpoints under a versioned path (/kt/v1/). The reference implementation operates at https://llmo.org/kt/v1/.
POST /kt/v1/entries
Accepts a compact JWS in the request body with content type application/jose+json. The registry validates the JWS per Section 3.2 and, on success, appends the entry to the log. The response is:
HTTP/1.1 201 Created
Content-Type: application/json
Location: /kt/v1/entries/<entry_id>
{
"entry_id": "<integer log index>",
"log_position": "<integer count, monotonically increasing>",
"appended_at": "<RFC 3339 timestamp at which the registry accepted the entry>",
"receipt": "<base64url(detached-JWS by the registry signing key over { entry_id, log_position, appended_at })>"
}
The receipt is the registry’s signed attestation that the entry was accepted at the given position. A publisher SHOULD retain the receipt as proof of submission.
GET /kt/v1/entries
Returns the most recent entries for a given domain.
Query parameters:
domain(required): the publisher’sprimary_domain.limit(optional, default 10, max 100): maximum number of entries returned.
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"domain": "<echo of the queried domain>",
"entries": [
{ "entry_id": ..., "log_position": ..., "entry": "<compact JWS>", "appended_at": ... },
...
]
}
Entries are returned in descending log_position order.
GET /kt/v1/entries/{entry_id}
Returns a single entry by its log index.
GET /kt/v1/log.jsonl
Returns the full log as application/x-ndjson, one entry per line, in ascending log_position order. Each line is the original compact JWS.
This endpoint MUST be available to any consumer without authentication. The log is public by design.
GET /kt/v1/snapshot/latest
Returns the most recent signed snapshot of the log.
HTTP/1.1 200 OK
Content-Type: application/jose+json
<compact JWS, signed by the registry signing key, with payload:>
{
"snapshot_id": "<monotonically increasing integer>",
"log_size": "<integer count of entries at snapshot time>",
"log_hash": "<base64url(SHA-384(concatenated bytes of /kt/v1/log.jsonl at snapshot time))>",
"snapshot_at": "<RFC 3339 timestamp>",
"previous_snapshot_id": "<integer, or null for the first snapshot>",
"previous_log_hash": "<base64url hash from the immediately preceding snapshot, or null>"
}
The snapshot commits to the log’s full content at snapshot time. Any modification to a historical entry changes log_hash, which is detectable by any consumer who retained an earlier snapshot. The chaining of previous_log_hash provides forward integrity: a consumer following the snapshot stream can detect a discontinuity.
GET /kt/v1/snapshot/{snapshot_id}
Returns a specific historical snapshot by ID.
3.4 Rule X7: Registry inclusion
A new rule, X7, is added to spec §5.3 (Strict-tier rules):
X7. KT registry inclusion. The document’s signing key MUST be present in a conforming LLMO Key Transparency registry under the publisher’s
primary_domainat the time of evaluation. Specifically, a GET to a registry’s/kt/v1/entries?domain=<primary_domain>MUST return at least one entry whosejwk_thumbprintequals the SHA-384 thumbprint per Section 3.1 of the public key currently used to verify the document’s signature, and whose JWS signature verifies against that key. A document for which no such entry can be retrieved MUST NOT be evaluated as Strict tier. Documents failing X7 evaluate at Standard tier with the notekt_uninloggedsurfaced to the consumer.
The set of conforming registries a consumer queries is implementation-defined for v0.1.x. The reference implementation lists the registries it queries in the validator’s documentation. The federation roadmap (Section 7.3) anticipates a multi-registry model in v0.2.
3.5 Tier evaluation behavior
A conforming consumer evaluating an LLMO document MUST perform the X7 check before assigning the Strict tier. The check MAY be performed in parallel with the existing tier checks (X1-X6) and is independent of them. A document that fails X7 but passes all of M1-M6 and S1-S6 evaluates as Standard tier with the kt_uninlogged note.
A document that fails X1-X6 evaluates per the existing rules in spec §5.3, regardless of X7’s outcome.
3.6 Consumer verification flow
A consumer evaluating an LLMO document at fetch time SHOULD:
- Fetch and validate the document at
https://<domain>/.well-known/llmo.jsonper spec §2-§5. - Extract the signing key’s JWK Thumbprint per Section 3.1 from the publisher’s
/.well-known/llmo-keys.jsonJWKS. - Query a conforming KT registry:
GET /kt/v1/entries?domain=<domain>. - For each returned entry: extract the inline
jwkfrom the protected header, compute the SHA-384 thumbprint, and verify the JWS signature using that JWK. Reject any entry whose computed thumbprint does not match the payload’sjwk_thumbprint. Identify the entry (if any) whosejwk_thumbprintmatches the thumbprint computed in step 2. - If at least one such entry exists and its signature verifies, evaluate X7 as PASS. If no matching entry exists, evaluate X7 as FAIL with note
kt_uninlogged.
A consumer evaluating a historical document (e.g., a cached document, or a document recovered from an archive) SHOULD additionally verify that the registry entry’s observed_at precedes the document’s effective time of evaluation. A registry entry created after the document was published does not retroactively authenticate the document.
A consumer with access to a registry snapshot from time T MAY use that snapshot to verify that the entries the consumer relied on at time T have not been retroactively modified.
3.7 Algorithm choices
The following hash functions are normative in this LIP:
- JWK Thumbprint (Section 3.1): SHA-384.
- Log content hash for snapshots (Section 3.3
/snapshot/latest): SHA-384.
The following signature algorithms are permitted for KT entries:
- Publisher signing keys: any algorithm permitted by spec §4.2 for the publisher’s signing key (currently ES256, ES384, EdDSA).
- Registry signing key: any algorithm permitted by spec §4.2. The reference implementation uses ES384 for the registry signing key in v0.1.x.
When the LLMO spec is extended to support post-quantum signature algorithms in a future LIP, those algorithms become permissible for KT entries automatically (the entries are JWS, evaluated per the same algorithm-dispatch logic as the document signature itself). The KT registry’s hash-based commitments survive the post-quantum migration of signatures.
3.8 Failure modes
The registry is operational infrastructure. Conforming consumers SHOULD anticipate the following failure modes:
3.8.1 Registry unreachable (transient)
If the registry’s /kt/v1/entries?domain=<domain> endpoint returns an HTTP 5xx, a network error, or times out, a consumer SHOULD treat X7 as transiently unevaluable. The consumer MAY fall through to Standard tier with note kt_unevaluable_transient, OR cache a recent positive result for the same (domain, jwk_thumbprint) pair from a prior fetch and treat X7 as PASS provisionally. Consumers caching positive results for this purpose SHOULD set a maximum cache TTL of 24 hours.
3.8.2 Registry unreachable (sustained)
If the registry is unreachable for an extended period (greater than 24 hours, by the consumer’s clock), the consumer SHOULD escalate to Standard tier with note kt_unevaluable_sustained and SHOULD NOT continue to cache prior positive results past that window. The protocol’s trust model degrades to v0.1 semantics during sustained registry outage.
3.8.3 Registry compromise
If a registry is observed to serve modified historical entries (detected by comparison of the current log_hash to a snapshot retained by the consumer), the consumer SHOULD treat all entries from that registry as untrusted and SHOULD fall through to Standard tier with note kt_compromised. Consumers in this state SHOULD query alternate registries if available.
3.8.4 Publisher key rotation between registration and verification
If a publisher rotates their signing key after publishing a KT entry and before a consumer’s verification, the consumer’s X7 check using the new key will fail to find a matching entry until the publisher submits a new KT entry under the new key. Publishers MUST submit a new KT entry as part of any key rotation procedure. The window during which an old document is signed under the previous key while the registry has only the new key (or vice versa) is the rotation window and is bounded by the publisher’s chosen rotation cadence.
3.9 Log structure and snapshots
The reference registry maintains the log as a flat append-only sequence. Each entry, on append, is recorded as a single line in the canonical log file, in the form of its original compact JWS. The log file’s full content is hashed with SHA-384 at snapshot time and the hash is recorded in the snapshot per Section 3.3.
Logarithmic-cost inclusion and consistency proofs (Merkle-tree based, in the manner of RFC 6962 Certificate Transparency) are not required by this LIP at v0.1.x. The flat-log model is sufficient for the v0.1.x publisher population (anticipated below 10,000 entries in the first year). A future LIP MAY introduce Merkle structure to the registry without changing the API surface defined in this LIP; consumers querying /kt/v1/entries and /kt/v1/snapshot/latest SHOULD remain forward-compatible.
Snapshots are signed by the registry on a fixed cadence. The reference implementation publishes a snapshot every 24 hours. The most recent snapshot is always served at /kt/v1/snapshot/latest. Historical snapshots remain available via /kt/v1/snapshot/{snapshot_id} indefinitely.
4. Rationale
4.1 Why a centralized log rather than purely decentralized verification
The publisher’s domain control alone is insufficient as a trust root. Domain takeover is a real attack class. A public, append-only log is the smallest mechanism that closes this gap without introducing per-publisher operational complexity. The publisher’s authority over their own document is preserved (they sign their own entry); the registry merely records what was claimed and when.
The v0.1.8 spec already contains a publication_history top-level field for publishers who wish to maintain their own decentralized log. This LIP does not deprecate that field. A publisher MAY maintain a publication_history alongside KT registration. The KT registry is the protocol-level mechanism for cross-publisher comparability; publication_history is the publisher-level mechanism for self-documentation.
4.2 Why automatic registration rather than opt-in
A partial log is materially worse than a complete one: a missing entry is itself a signal. If registration is opt-in, the absence of a registry entry could mean either “the publisher chose not to register” or “an attacker is publishing under a domain not registered by the legitimate owner.” A protocol relying on registry signals to detect anomalies requires the registry to be the default path through the publication tooling.
The /llmo Claude Code skill and the manual CLI flow both produce registration as a built-in step (see Section 8). A publisher who deliberately bypasses registration is choosing Standard tier and the kt_uninlogged note, not avoiding registration silently.
4.3 Why SHA-384 throughout, when SHA-256 is the convention
The convention in CT, JWS, and JWK Thumbprint is SHA-256. SHA-256 has approximately 128-bit collision resistance classically and approximately 85-bit under Grover-style quantum search. SHA-384 has approximately 192-bit classical and approximately 128-bit under Grover. The cost difference is 16 additional bytes per hash (48 vs. 32) and negligible CPU.
For a registry whose explicit purpose includes surviving the migration to post-quantum signatures, choosing the PQ-aware hash function at the outset removes a future flag-day. The choice is deliberate, not convention-following.
4.4 Why flat append-only rather than Merkle-tree at v0.1.x
Merkle-tree structure provides logarithmic-cost inclusion and consistency proofs. These are necessary at the scale of internet-wide TLS CT, where logs contain hundreds of millions of entries and any consumer requesting “is X in the log” cannot afford to download the entire log. The KT registry at v0.1.x serves a publisher population in the thousands. Linear-cost verification (“download the full log, recompute the hash, compare to the snapshot”) is acceptable at this scale.
The API in Section 3.3 is designed to remain stable across a future Merkle migration. Consumers query /kt/v1/entries and /kt/v1/snapshot/latest; the registry implementation behind those endpoints may upgrade from flat to Merkle without breaking consumer code.
4.5 Why a new rule X7 rather than amending an existing rule
X7 is additive. Documents conforming to spec §5.3 X1-X6 continue to conform after this LIP lands. Documents that previously achieved Strict tier without KT registration now evaluate at Standard tier under X7. This is a controlled trust-model evolution rather than a breaking change.
The Standard tier remains a coherent level: a document with valid signature, valid JWKS, in-window freshness, and structural conformance, but without KT registration, is meaningfully different from a Minimal-tier document. The note kt_uninlogged makes the gap explicit to consumers without invalidating the document.
4.6 Why not post-quantum signature algorithms now
A natural question: if the registry’s design is motivated in part by the post-quantum migration of signatures, why not adopt a post-quantum signature algorithm (ML-DSA, formerly Dilithium; FN-DSA, formerly Falcon; SLH-DSA, formerly SPHINCS+) for the entries themselves? Four reasons inform the choice to stay with ES256, ES384, and EdDSA for v0.1.x:
-
JOSE / JWS standardization for post-quantum signature algorithms is in active draft at IETF COSE. Specific
algparameter values for ML-DSA (ML-DSA-44,ML-DSA-65,ML-DSA-87) and the other NIST PQC signature standards are not yet finalized. Adopting an unfinalized identifier requires a coordinated rename when the IETF draft converges, creating an interoperability gap during the interim. -
Library support is absent in the reference implementation’s dependency tree. The
josenpm package used by the LLMO CLI does not yet support ML-DSA. A v0.1.x implementation choosing ML-DSA would require either a lower-level cryptographic library (@noble/post-quantum) wired into a from-scratch JWS path, or waiting onjoseto add support. Both are engineering investments disproportionate to the v0.1.x scope. -
Signature size inflates 40-100x. ES256 signatures are 64 bytes; ML-DSA-65 signatures are approximately 3.3 KB; SLH-DSA-SHA2-128s signatures are approximately 7.8 KB. Inflating every llmo.json signature this much has real consumer-side cost (fetch bandwidth, parsing, caching). Hybrid signing (ES256 plus ML-DSA in parallel) doubles the cost again. This is acceptable in v0.2 when PQ readiness becomes urgent; gratuitous in v0.1.x.
-
No backward compatibility. Every existing v0.1 consumer expects ES256, ES384, or EdDSA per spec §4.2. A document signed under ML-DSA without hybrid coverage is unverifiable to current consumers. The standard transition pattern (TLS hybrid groups, COSE hybrid suites) is to sign with both classical and post-quantum algorithms during a multi-year transition window. The hybrid signing specification is itself spec work and belongs in a future v0.2 LIP, not in this one.
The KT registry, with its SHA-384 commitments, is the bridge across this gap. Every v0.1.x document is committed to a hash-based audit trail at registration time. When a future LIP introduces hybrid ES256-plus-ML-DSA signing in v0.2, the historical record from v0.1.x carries forward intact: a consumer in 2040 evaluating a 2030 document verifies the 2030 ECDSA signature against the JWK whose SHA-384 thumbprint was committed to the registry’s signed snapshot in 2030. Whether or not the 2030 ECDSA signature is independently forgeable post-Shor at that future date, the registry attests that no other key was claimed as legitimate for that domain at that time, and that attestation rests entirely on hash-based primitives.
This LIP does not specify the v0.2 post-quantum LIP. It ensures only that the v0.1.x KT registry survives the transition when that LIP is authored and accepted.
4.7 Revisit criteria for the post-quantum signature deferral
The deferral in §4.6 is not open-ended. The editor MUST initiate work on the v0.2 post-quantum signature LIP when any one of the following gating signals is observed. The list is disjunctive: any single trigger is sufficient.
- IETF COSE standardization for ML-DSA reaches RFC status. Specifically, an RFC publishes the
algparameter values for ML-DSA-44, ML-DSA-65, and ML-DSA-87 (or whichever subset is appropriate), and IANA registers those values in the JOSE Algorithms registry. Until this trigger fires, adopting non-finalized identifiers risks a rename when the IETF draft converges. - Reference library support lands in
jose(or equivalent in the implementation’s dependency tree). A published version of thejosenpm package supporting ML-DSA signing and verification with a stable API. Alternatively, a viable replacement library with comparable maintenance posture. - NIST deprecation timeline approaches the 24-month horizon. NIST has published guidance deprecating 112-bit-security classical signature algorithms (including ECDSA P-256) by approximately 2030 and disallowing them by approximately 2035. When the deprecate-by date is within 24 months by the editor’s clock, the v0.2 LIP MUST begin regardless of the standardization or library state.
- Cryptographic emergency. A published cryptanalytic result that reduces the security margin of ES256, ES384, or EdDSA below the 100-bit level (classical or quantum) triggers immediate initiation of the v0.2 LIP. Editor MAY also issue an out-of-cycle advisory before the v0.2 LIP is complete.
In addition to the disjunctive triggers, the editor MUST conduct an annual review of this section starting on the anniversary of this LIP’s Final transition. Each review records its findings either as a comment on this LIP’s GitHub Discussion thread or as an editorial revision to this section, and confirms whether any trigger has fired in the prior twelve months. The review’s outcome is a binary recommendation: continue deferral, or initiate the v0.2 LIP. The outcome is recorded permanently in the LIP’s transitions log.
A backlog item tracking these criteria is maintained at infrastructure/BACKLOG.md in the llmo.org repository. The item is in scope until the v0.2 post-quantum LIP reaches Final status, at which point this section is amended to record the supersession and the backlog item moves to the COMPLETED section.
5. Backwards Compatibility
This LIP introduces a normative tier rule (X7) and a new piece of infrastructure (the registry). It is technically backwards-compatible with the v0.1.x schema (no schema changes are required by this LIP), but it changes consumer evaluation behavior: documents previously evaluated at Strict tier are evaluated at Standard tier after this LIP lands until their publishers register.
Mitigation:
- A grace period of 14 days from the LIP’s Final status to the date at which conforming consumers begin enforcing X7. During the grace period, the
kt_uninloggednote is surfaced advisorily. The original drafting of this LIP specified a 90-day grace period; the period was revised down to 14 days at the same time the LIP transitioned to Final, reflecting that pre-launch there are zero or very few existing publishers needing protection from the X7 transition. Future revisions of this LIP MAY extend the grace period if observed adoption produces a confused-publisher class that would benefit from a longer window. - A
llmo registerCLI subcommand (see Section 8) makes registration trivial for existing publishers. - The
/llmoClaude Code skill is updated to include a phase 10b (Section 8) that registers automatically as part of every publish operation.
No schema changes are required. The llmo.json document format is unchanged. The publisher’s JWKS is unchanged. The signing procedure is unchanged. The only new artifact is the KT registry entry, which is a separate JWS held by the registry rather than by the publisher.
6. Security Considerations
6.1 Threat model
The registry addresses the following threats:
- Domain takeover. Mitigated by cross-check of deployed JWKS thumbprint against registry thumbprint.
- Silent key substitution. Same.
- Historical document repudiation. A publisher who later denies having signed a document cannot retroactively remove the KT entry; the entry is in an append-only log with chained signed snapshots.
- Registry compromise altering history. Detected by snapshot comparison. A consumer who retained any prior snapshot can detect retroactive modification of any entry committed before that snapshot.
- Post-quantum signature forgery on historical documents. The registry’s hash-based commitment to the historical state of registered keys survives the migration of signatures. A consumer in 2040 evaluating a 2030 document can verify that the 2030 key was registered at the 2030 timestamp per the SHA-384 commitment, even if Shor’s algorithm has rendered the 2030 ECDSA signature forgeable.
The registry does NOT address the following threats:
- Compromise of a publisher’s private key at the time of registration. If an attacker controls the publisher’s private key, they can register their own key as legitimate. KT does not authenticate the publisher’s identity beyond their control of the signing key.
- Compromise of the registry’s signing key for snapshots. A registry whose signing key is compromised can produce snapshots that retroactively authenticate any state. Operational mitigation: the registry’s signing key custody is documented in ADR-0010, and snapshot signatures from federated peers (v0.2 roadmap) provide cross-witness.
- DNS-level attacks on the registry’s hostname. If an attacker can serve
llmo.orgtraffic, they can serve a forged registry. Operational mitigation: registry endpoints serve over HTTPS with standard PKI; the v0.2 federation roadmap distributes trust across multiple registry operators.
6.2 Append-only enforcement
The reference implementation maintains the log as a flat file under version control in a public Git repository. Append operations are commits; the commit history is the audit trail of appends. A registry implementation MAY use other mechanisms (database with append-only constraint, hash-chained entries, etc.) as long as the snapshot-based detection of historical modification per Section 3.8.3 remains effective.
Removal of entries is not supported. A publisher who wishes to revoke an entry MUST publish a new entry under a new key (rotation) or accept the kt_uninlogged downgrade.
6.3 Privacy
Registry entries are public by design. The contents (domain, kid, jwk_thumbprint, doc_url, doc_id, observed_at) are all metadata that the publisher has already chosen to publish at their own domain. The registry adds no PII beyond what the publisher’s /.well-known/llmo.json and /.well-known/llmo-keys.json already expose.
A separate concept, the public publisher directory at llmo.org/publishers/, is opt-in (Section 7.1). A publisher MAY register in the KT log without being listed in the public directory.
7. Open Questions
The following items are deliberately deferred for editor and community discussion during the LIP’s Draft window.
7.1 Public directory vs. auditable-only log
Two distinct functions:
- Auditable log. Any party with a domain in mind can query the registry for entries under that domain. This is necessary for X7’s machinery and is non-negotiable.
- Public directory. A browsable, indexed list at
llmo.org/publishers/enumerating all publishers with at least one entry in the log, suitable for human discovery.
Some publishers want the auditable-log property without the public directory listing. The LIP currently defaults to auditable-log only; the public directory is opt-in via a public_directory_listing: true flag in the registry entry’s payload. Alternative: make the directory automatic with an opt-out. Editor’s call.
7.2 Federation cadence for v0.2
Multiple log operators are anticipated for v0.2. The N-of-M inclusion model from CT is one option (entries must appear in at least N of M conforming logs to satisfy X7). The cross-witness model (each log signs the others’ roots periodically) is another. Out of scope for this LIP; named here so v0.2 work has a referent.
7.3 Snapshot signing cadence
The LIP specifies 24-hour snapshots as the reference implementation default. Hourly snapshots provide a fresher root; daily reduces registry signing infrastructure load. The cadence is a property of the registry, not the protocol; a registry MAY publish more frequently than 24 hours without breaking conformance. Editor’s call on whether to specify a minimum cadence as normative.
7.4 Registry signing key custody
The registry’s own signing key is a single point of trust. Custody, rotation cadence, and disaster recovery are operational decisions tracked in ADR-0010. The LIP does not normatively constrain custody, only behavior (the snapshot MUST verify against the registry’s published JWKS at the snapshot’s signing time).
8. Reference implementation (informative)
This section sketches the reference implementations of the publisher-side and consumer-side mechanics. None of the content in this section is normative; the normative requirements are in Section 3.
8.1 CLI subcommand: llmo register
$ llmo register \
--key ./llmo-private-<kid>.pem \
--kid <kid> \
--domain <primary_domain> \
--doc-url https://<primary_domain>/.well-known/llmo.json \
--doc-id <document_id> \
--registry https://llmo.org/kt/v1
→ POSTs the JWS-signed entry to the registry.
→ Captures the registry receipt.
→ Writes the receipt to ./llmo-kt-receipt-<observed_at>.json in the working directory.
The subcommand is non-interactive when all required flags are provided. With no flags, it prompts. The receipt file is preserved alongside the publisher’s other working-directory artifacts.
8.2 /llmo Claude Code skill: phase 10b
Between current phase 10 (Validate live) and the closing report, the skill performs:
Phase 10b: Register
1. Read the working-directory state (kid, key path, primary_domain, doc_url, doc_id).
2. Run `llmo register` with those values.
3. Verify the registry receipt.
4. Surface the registry entry_id and log_position to the publisher.
5. Append the receipt to the closing report.
The skill does not skip phase 10b silently. If registration fails (registry unreachable, network error, etc.), the skill surfaces the failure and offers the publisher the choice to proceed without registration (accepting Standard tier with kt_uninlogged) or retry.
8.3 Validator: X7 implementation
The reference validator at https://llmo.org/validator/ performs the X7 check as part of Strict-tier evaluation. The check is implemented as an additional collector in static/js/validator.js’s evaluateStrictTier() function. Failure modes from Section 3.8 are surfaced as distinct result codes.
The CLI llmo verify performs the same check by querying the configured registry endpoint (default https://llmo.org/kt/v1).
9. Test vectors
To be authored prior to Final status. Test vectors will demonstrate:
- A canonically formatted KT entry JWS.
- A registry receipt JWS.
- A registry snapshot JWS.
- A consumer-side X7 evaluation flow against a synthetic registry.
- Negative cases: signature/thumbprint mismatch; entry not retrievable; signature does not verify; registry serves modified history detectable via snapshot comparison.
Test vectors will be published at /spec/v0.1/test-vectors/kt/ alongside the existing v0.1.8 vectors.
10. Acknowledgments
The KT design is modeled on RFC 6962 (Certificate Transparency, Laurie, Langley, Käsper, 2013) and RFC 9162 (Certificate Transparency Version 2.0). Hash-based commitment to log state is a direct adaptation of the CT model. The choice to flatten rather than Merkle-ize at this scale is influenced by the experience of smaller transparency-log operators (sigsum.org, rekor) demonstrating the operational viability of flat logs for sub-CT-scale applications.
The choice of SHA-384 throughout is informed by NIST’s published guidance on post-quantum cryptography (FIPS 203, 204, 205, 206) and by the Grover-quadratic-speedup analysis in NISTIR 8413. SHA-384’s pre-quantum collision resistance margin gives a comfortable safety factor in the post-quantum regime.
11. Copyright
This LIP is licensed under the Creative Commons Attribution 4.0 International License (CC-BY-4.0).