TypeScript: @dnsid-ai/log-c2sp-tlog
DNSid binding for C2SP tiled logs, including verified reads and split-signature prepared writes.
Breaking pre-1.0 logical-chain correction
Section titled “Breaking pre-1.0 logical-chain correction”This implementation follows DNSid method revision
d5a65d06f76eff4db81e50f8767a600d2ca7fc2a (exported as
DNSID_C2SP_METHOD_REVISION). The method, envelope v:1, and bundle @v1
labels are unchanged. Every scope requires signed binding context and a logical
predecessor chain. C2spChain.previousEventId replaces previousIndex and
previousLeafHash; the corresponding wire field is prev_event_id.
prepared.eventId is SHA-256 of "dnsid-c2sp-event-v1", one zero byte, and
canonical signed payload bytes excluding top-level sigs. State hashing is
unchanged. Old index/leaf chain fields and signed event_id are prohibited.
The old unchained option no longer bypasses verification.
Deploy corrected readers, writers, monitors and bundle producers together. Preserve conforming histories (including conforming public ISSUANCE-only streams) and terminal state. Inventory nonconforming histories and outstanding submissions; retain old evidence and use fresh streams where necessary. Never rewrite bytes that may already have been submitted, even when the logical event ID is unchanged. Historical cross-SDK matrix results are not corrected-contract release evidence.
The package root contains the Node.js checkpoint, Merkle, and signed-note
verification implementation. Browser-safe consumers that only prepare, inspect,
sign, or serialize registry-provided events can import the portable
@dnsid-ai/log-c2sp-tlog/writer subpath. Immutable binding-version
metadata is available from @dnsid-ai/log-c2sp-tlog/version.
import { createC2spTlogVerificationRegistry } from '@dnsid-ai/log-c2sp-tlog';
const registry = await createC2spTlogVerificationRegistry({ // This URL is explicit, independently trusted application configuration. policyUrl: 'https://policy.example/dnsid-policy', // Local freshness policy used by verifyNonRevocation. checkpointMaxAge: 5 * 60 * 1000, allowedClockSkew: 30 * 1000,});The factory fetches the policy with SSRF-safe destination checks, rejects redirects, requires HTTP 200, bounds decoded responses during reading, and uses the same bounded resource fetcher for log evidence. The example above supplies no bundle keys, so it uses a complete-log scan. With an independently distributed trustProfile, or policyDocument/policyUrl plus direct bundleVerifierKeys, set maxBundleLifetimeMs to enable bundle verification (and checkpointMaxAge for non-revocation); the default reader then prefers {lr log-prefix}/streams/{fqdn}?format=bundle and shares its verified lifecycle snapshot across binding, continuity, and key-age checks. A missing or temporarily unavailable endpoint falls back to the bounded complete-log scan. A valid newer bundle also falls back when no consistency-proof source is available, allowing the complete scan to verify both roots independently. Malformed, expired, invalid, rollback, or conflicting bundle evidence never falls back. Set requireStreamBundle to disable fallback.
For an explicit application decision to trust DNSid-managed DNSid logs, use the separately named managed factory:
import { createDnsidManagedVerificationRegistry } from '@dnsid-ai/log-c2sp-tlog';
const registry = await createDnsidManagedVerificationRegistry();It selects reviewed trust bundled with the SDK only for exact canonical public references to https://log.dev.dnsid.ai or https://log.dnsid.ai. Both development and production use bundle-first verification with bounded raw-scan fallback when bundle evidence is unavailable or consistency evidence requires a complete scan. Unknown scopes and prefixes fail closed. The generic factory never selects managed roots when trust is omitted.
Set checkpointMaxAge when using verifyNonRevocation; omitting it makes that operation fail closed. Non-revocation always refreshes evidence instead of relying on the lifecycle snapshot retained by VerifiedDomain. allowedClockSkew defaults to zero. The factory installs an in-memory trusted-checkpoint store by default; that protects against rollback only for the process lifetime. Inject a durable trustedCheckpointStore when protection must survive restarts. A custom resourceFetcher must implement the bounded fetch contract and explicitly report all required security guarantees.
Checkpoint-store load/compareAndSwap and consistency-source
fetchConsistencyProof receive the verification signal as their last argument.
Custom stores must check cancellation at the atomic commit boundary and prevent
pending writes after cancellation; racing an uncancelable write is insufficient.
Checkpoint advancement shares the caller’s cancellation and has a 30-second
standalone default (timeoutMs/signal options).
Never derive policyUrl from an unverified identity record, its lr, or a log prefix. Advanced deployments can compose parseC2spPolicyFile, ScanStreamSource, registerC2spTlog, and a custom checkpoint store directly for private transports, mirrors, archives, or portable bundles.
Public verification needs a trusted local policy and complete stream evidence. A single inclusion proof proves historical inclusion only; it is not current lifecycle state or non-revocation evidence.
For self-managed new identity instances, generate and persist a stream ID with
generateC2spTlogStreamId() (128 random bits, 22 unpadded base64url characters).
For registry-managed issuance, use the registry-provided bound log reference;
do not generate a second ID or reuse a bare FQDN as the stream ID.
For writes, construct C2spTlogBinding with a bound reference, call
prepareEvent, and pass the prepared event between signer processes.
Each process calls parsePreparedEvent before signPreparedEvent; existing
signatures and the provider’s public key are checked before another signature is
added. The operational side of split ISSUANCE supplies its locally expected
FQDN, governance ID, entity key, and operational key as verification context;
the SDK rejects missing or mismatched expectations before adding the operational
countersignature. entryBytes requires the same ISSUANCE context and verifies
every role signature.
writePreparedEvent passes the exact canonical entry bytes and optional
idempotency key to the deployment adapter. Events after genesis in every scope also
require a validateChain callback backed by authoritative prior stream state;
the SDK will not append them based only on caller-supplied chain hashes.
Draft-01 ISSUANCE entries contain the recorded entity and operational public keys and carry sigs.ae and sigs.op. KEY_ROTATION entries carry both the previous-key authorization in sigs.prev_op and the new-key proof of possession in sigs.new_op. Other supported lifecycle entries carry sigs.ae. The draft-01 behavior supports ISSUANCE, KEY_ROTATION, REVOCATION, RETIREMENT, MIGRATION, and DELEGATION.
Candidate selection deduplicates exact signed payloads, not complete entry bytes.
It accepts valid ES256 high-S/low-S variants and independently re-signed copies
without changing first-applied order, sequence or key age. Every role signature
must verify before a new payload can create a contradiction. Authority comes
from its verified predecessor, including superseded keys; signed forks and new
post-terminal events fail closed. Bundle summaries count logical events, but
every supplied inclusion proof verifies exact complete bytes, even for ignored
copies. readEvent independently authenticates every role of the requested
occurrence; an invalid-signature replay is not valid standalone evidence.
Inbound migration requires verifyMigration to return the verified prior-log
history through finalEntryRef, plus the entity and active operational keys it
establishes. To return LoggedStateEvidence, it also supplies the aligned
priorHistoryReferences; these preserve prior-log bounds without deriving them
from destination indexes. rebuildHistory validates imported state and returns
one stitched history with the migration event exactly once. A missing, terminal,
key-inconsistent, or boundary-incomplete prior history fails closed. Migration
callbacks receive a second { signal } argument and must propagate that same
budget through recursive predecessor verification. The callback owns exact-cutoff
proof verification and cumulative recursion/history/response limits.
Read operations and standalone stream verifiers have a 30-second overall default.
Factory/reader signal settings and per-call stream-verifier options propagate
cancellation; injected sources must honor it. Raw scans default to one million
entries and 256 MiB aggregate entry bytes; lifecycle replay additionally caps
retained history at 10,000 logical events. Copies still consume input limits.
Reuse registries/transports/checkpoint stores, monitor fallback cost, and lower
scan limits or require bundles for predictable cost.
Lifecycle and non-revocation verification require a timestamped witness quorum. checkpointMaxAge is required for non-revocation; bundle reads also enforce checkpoint freshness (using checkpointMaxAge when set, otherwise maxBundleLifetimeMs). Raw-scan historical binding and key-continuity checks accept older authenticated checkpoints. Text policies in the C2SP tlog-policy format, including nested groups, can be loaded with parseC2spPolicyFile.
This package currently supports C2SP signed-note Ed25519 log signatures (type 0x01) and timestamped Ed25519 witness cosignatures (type 0x04).
verifyC2spStreamBundle verifies canonical
dnsid-c2sp-stream-bundle@v1 bytes against exact independently accepted policy
bytes, a trusted bundle-signing key, C2SP inclusion proofs, lifecycle signatures,
checkpoint freshness, trusted-index completeness, and the origin-scoped checkpoint store. It does not resolve DNS or validate the identity record/status; callers must independently supply trusted policy, bundle keys, and entity key, plus a checkpoint store and positive bundle byte, event-count, and lifetime limits. The exported
C2SP_TLOG_SPECIFICATIONS object identifies the exact external C2SP revisions
implemented by this package.
C2SP tlog-backed lifecycle log support for DNSid.
Implements the c2sp-tlog log method: reading, writing, and verifying DNSid
lifecycle transparency logs built on the C2SP specifications
(tlog-checkpoint, tlog-tiles, signed-note, tlog-cosignature, and related
documents; see C2SP_TLOG_SPECIFICATIONS). A stream in such a log
records an agent’s identity-record lifecycle events (ISSUANCE, KEY_ROTATION,
REVOCATION, …) as canonical JSON entries in a Merkle tree, authenticated
by witnessed checkpoints under a local trust policy.
Key entry points: createC2spTlogVerificationRegistry for generic caller-supplied trust, createDnsidManagedVerificationRegistry for the reviewed DNSid-managed trust catalog, registerC2spTlog / C2spTlogReader for lower-level composition, C2spTlogBinding and the prepared-event writer API for appending events, and verifyC2spStreamBundle for offline bundles.
Type Aliases
Section titled “Type Aliases”C2spJsonEvent
Section titled “C2spJsonEvent”type C2spJsonEvent = Record<string, unknown>;Defined in: packages/log-c2sp-tlog/src/event-codec.ts:20
Raw JSON envelope of a DNSid lifecycle event as stored in a c2sp-tlog entry.
C2spSignerRole
Section titled “C2spSignerRole”type C2spSignerRole = | "Entity" | "OperationalCountersignature" | "PreviousOperational" | "NewOperational";Defined in: packages/log-c2sp-tlog/src/writer.ts:29
Role a signature is produced under when signing a prepared lifecycle event.
C2spTlogQuorumRule
Section titled “C2spTlogQuorumRule”type C2spTlogQuorumRule = | { kind: "none";} | { key: SignedNoteKey | string; kind: "witness";} | { kind: "threshold"; members: C2spTlogQuorumRule[]; threshold: number;};Defined in: packages/log-c2sp-tlog/src/policy.ts:9
Witness-quorum requirement for accepting a checkpoint: no witnesses, a single named witness, or a threshold over nested member rules.
C2spTlogScope
Section titled “C2spTlogScope”type C2spTlogScope = "public" | "testnet" | `private-${string}`;Defined in: packages/log-c2sp-tlog/src/lr.ts:5
Deployment scope of a c2sp-tlog reference; public additionally requires HTTPS and a non-zero witness quorum. All scopes require chaining; lifecycle reads require witnessed checkpoints.
Variables
Section titled “Variables”C2SP_ENTRIES_PER_BUNDLE
Section titled “C2SP_ENTRIES_PER_BUNDLE”const C2SP_ENTRIES_PER_BUNDLE: 256 = 256;Defined in: packages/log-c2sp-tlog/src/stream-source.ts:57
C2SP tlog-tiles fixes full entry bundles at 256 entries.
C2SP_TLOG_PROFILE_VERSION
Section titled “C2SP_TLOG_PROFILE_VERSION”const C2SP_TLOG_PROFILE_VERSION: 1;Defined in: packages/log-c2sp-tlog/src/version.ts:2
Exact external specifications implemented by this c2sp-tlog package.
C2SP_TLOG_SPECIFICATIONS
Section titled “C2SP_TLOG_SPECIFICATIONS”const C2SP_TLOG_SPECIFICATIONS: Readonly<{ signed-note: "https://c2sp.org/signed-note@v1.0.0"; tlog-checkpoint: "https://c2sp.org/tlog-checkpoint@v1.0.0"; tlog-cosignature: "https://c2sp.org/tlog-cosignature@v1.0.1"; tlog-mirror: "d0fe789122c75b903bfc1680b0b8b8dc570f0db3"; tlog-policy: "1896a5aea5559b3203d275d0206d872f59348cf5"; tlog-proof: "ab17a74116563005f908b9167e6421cc929a5c2b"; tlog-tiles: "https://c2sp.org/tlog-tiles@v0.1.0"; tlog-witness: "https://c2sp.org/tlog-witness@v1.0.0";}>;Defined in: packages/log-c2sp-tlog/src/version.ts:8
Pinned versions (URL or commit) of each C2SP specification this package implements.
DEFAULT_C2SP_MAX_CHECKPOINT_BYTES
Section titled “DEFAULT_C2SP_MAX_CHECKPOINT_BYTES”const DEFAULT_C2SP_MAX_CHECKPOINT_BYTES: 1048576 = 1_048_576;Defined in: packages/log-c2sp-tlog/src/stream-source.ts:59
DEFAULT_C2SP_MAX_ENTRY_BUNDLE_BYTES
Section titled “DEFAULT_C2SP_MAX_ENTRY_BUNDLE_BYTES”const DEFAULT_C2SP_MAX_ENTRY_BUNDLE_BYTES: 16777472 = 16_777_472;Defined in: packages/log-c2sp-tlog/src/stream-source.ts:61
256 * (2-byte prefix + 65,535-byte entry).
DEFAULT_C2SP_MAX_STREAM_BUNDLE_BYTES
Section titled “DEFAULT_C2SP_MAX_STREAM_BUNDLE_BYTES”const DEFAULT_C2SP_MAX_STREAM_BUNDLE_BYTES: number;Defined in: packages/log-c2sp-tlog/src/stream-bundle.ts:21
DEFAULT_C2SP_MAX_STREAM_BUNDLE_EVENTS
Section titled “DEFAULT_C2SP_MAX_STREAM_BUNDLE_EVENTS”const DEFAULT_C2SP_MAX_STREAM_BUNDLE_EVENTS: 10000 = 10_000;Defined in: packages/log-c2sp-tlog/src/stream-bundle.ts:22
DEFAULT_C2SP_MAX_TOTAL_ENTRY_BYTES
Section titled “DEFAULT_C2SP_MAX_TOTAL_ENTRY_BYTES”const DEFAULT_C2SP_MAX_TOTAL_ENTRY_BYTES: 268435456 = 268_435_456;Defined in: packages/log-c2sp-tlog/src/stream-source.ts:62
DEFAULT_C2SP_MAX_TREE_SIZE
Section titled “DEFAULT_C2SP_MAX_TREE_SIZE”const DEFAULT_C2SP_MAX_TREE_SIZE: 1000000 = 1_000_000;Defined in: packages/log-c2sp-tlog/src/stream-source.ts:58
DEFAULT_C2SP_REQUEST_TIMEOUT_MS
Section titled “DEFAULT_C2SP_REQUEST_TIMEOUT_MS”const DEFAULT_C2SP_REQUEST_TIMEOUT_MS: 10000 = 10_000;Defined in: packages/log-c2sp-tlog/src/stream-source.ts:63
DNSID_C2SP_METHOD_REVISION
Section titled “DNSID_C2SP_METHOD_REVISION”const DNSID_C2SP_METHOD_REVISION: "d5a65d06f76eff4db81e50f8767a600d2ca7fc2a";Defined in: packages/log-c2sp-tlog/src/version.ts:5
DNSid method contract with signature-independent logical event identity.
Classes
Section titled “Classes”Documented on Transparency log: classes:
- C2spTlogBinding
C2spTlogClient- C2spTlogError
- C2spTlogParseError
- C2spTlogReader
- C2spTlogVerificationError
- InMemoryTrustedC2spCheckpointStore
- ScanStreamSource
Interfaces
Section titled “Interfaces”Documented on Transparency log: interfaces:
- C2spBoundedResourceFetcher
- C2spChain
- C2spConsistencyProofSource
- C2spEventContext
- C2spResourceFetchGuarantees
- C2spResourceFetchOptions
- C2spScanLimits
- C2spSignatures
- C2spSignatureValue
- C2spStreamBundle
- C2spStreamBundleEvent
- C2spStreamBundleReaderOptions
- C2spStreamBundleSignature
- C2spStreamBundleState
- C2spTlogAppendOptions
- C2spTlogOriginPolicy
- C2spTlogPolicy
- C2spTlogReaderOptions
- C2spTlogTrustProfile
- C2spTlogVerificationOptions
- Checkpoint
- CheckpointPolicyResult
- DnsidManagedVerificationOptions
- IndexedEntry
- MigrationVerificationResult
- NoteSignature
- ParseC2spStreamBundleOptions
- ParsedC2spTlogLr
- PreparedC2spTlogEvent
- PreparedC2spVerificationContext
- ScanStreamSourceOptions
- SignedNoteKey
- SignPreparedC2spOptions
- StreamEvidence
- StreamSource
- StreamVerifierOptions
- TlogProofV1
- TrustedC2spCheckpoint
- TrustedC2spCheckpointStore
- VerifiedC2spStreamBundle
- VerifiedLifecycleEvent
- VerifyC2spStreamBundleOptions
Functions
Section titled “Functions”Documented on Transparency log: functions:
- advanceTrustedC2spCheckpoint()
- assertCanonicalJsonBytes()
- c2spEnvelopeToEvent()
- c2spEventId()
- c2spTlogEntryBytes()
- canonicalBytes()
- canonicalizeC2spEvent()
- canonicalJson()
- canonicalLogPrefix()
- checkpointOrigin()
- checkpointPath()
- createC2spTlogVerificationRegistry()
- createDefaultC2spBoundedResourceFetcher()
- createDnsidManagedVerificationRegistry()
- createFetchBackedC2spResourceFetcher()
- encodeEntryBundle()
- enforceCheckpointPolicy()
- entryBundlePath()
- eventToC2spEnvelope()
- generateC2spTlogStreamId()
- inclusionRoot()
- leafHash()
- merkleRootFromEntries()
- nodeHash()
- normalizedOriginPolicy()
- parseC2spEventEntry()
- parseC2spPolicyFile()
- parseC2spSignatures()
- parseC2spStreamBundle()
- parseC2spTlogLr()
- parseC2spTlogTrustProfile()
- parseCheckpoint()
- parseEntryBundle()
- parseJsonNoDuplicateMembers()
- parseNoteSignature()
- parsePreparedC2spTlogEvent()
- parseSignedNoteVerifierKey()
- parseTlogProofV1()
- prepareC2spTlogEvent()
- prepareC2spTlogEventForSigning()
- registerC2spTlog()
- requiredC2spResourceFetchGuarantees()
- requiredC2spSignatureNames()
- signedC2spEntryBytes()
- signedC2spEventBytes()
- signPreparedC2spTlogEvent()
- stateHash()
- stitchVerifiedMigrationHistory()
- tilePath()
- validateC2spResourceFetcher()
- verifiedCosignatureTimestamp()
- verifyC2spConsistencyProof()
- verifyC2spStreamBundle()
- verifyC2spTlogProof()
- verifyCheckpointSignature()
- verifyInclusion()
- verifyLifecycle()
- verifyLoggedEventSignature()
- verifyNoteSignature()
- verifyStreamLifecycle()
- writePreparedEvent()