Skip to content

TypeScript: @dnsid-ai/protocol

Protocol core for DNSid TypeScript packages.

Use this package when you want the DNSid protocol engine and contracts without runtime-specific defaults.

  • IdentityManager
  • DNSid TXT record parsing, validation, serialization, and canonicalization
  • JWKS validation and JWK thumbprints
  • Protocol data types and strict protocol status validation
  • DNS, fetch, cache, key-provider, and log contracts
  • Common DNSid errors and verification primitives

This package intentionally does not include Node DNS/HTTPS transport, local filesystem key storage, environment-variable loading, registry-specific lifecycle behavior, or profile-specific conveniences.

Terminal window
npm install @dnsid-ai/protocol
import { IdentityManager } from '@dnsid-ai/protocol';
// config is data only; runtime objects are injected as dependencies.
const idm = new IdentityManager(
{
identity, // omit for a verification-only manager
verification: { dnssecMode: 'required', trustedEntities: [{ governanceId: 'agent.example' }] },
},
{ keyProvider, entityKeyProvider, logRegistry, dnsResolver, cache, fetchJson },
);

verification.trustedEntities is an optional counterparty allowlist checked on every verifyDomain return (including cache hits) after protocol verification. Entries match the verified record’s governance ID exactly; optional entityKeyThumbprints pin the current record-signing key. Omit it to make no acceptance decision; [] denies all. A denial raises VerificationError with code CounterpartyNotAccepted carrying only the observed verifiedGovernanceId/verifiedEntityKeyThumbprint.

The core has no default DNS or HTTPS implementation, so config.transport settings are rejected here; use @dnsid-ai/sdk/node, which consumes them for its defaults.

For ergonomic Node.js defaults, use @dnsid-ai/sdk/node with @dnsid-ai/transport.

verifyDomain(domain, peerCert?, { timeoutMs?, signal? }) has a 30-second whole-operation default. Each coalesced caller retains its deadline; shared identity work also has a 30-second cap and is canceled when its last caller leaves. DNS, HTTPS redirects, and lifecycle evidence share the remaining budget. Injected resolvers, fetchers, and log factories must honor the supplied signal, keep trust configuration immutable, and bound their responses before allocation. Native system DNS work may finish in the background under the platform’s finite resolver retry limits; canceled verification does not proceed or populate caches. TXT input is limited to 65,535 characters/1,024 chunks; JWKS and status fetches request 256 KiB and 16 KiB limits respectively.

Injected caches are namespaced per manager, never shared by domain alone. Configuration and log-method selection are snapshotted at construction; use a new manager for changed trust policy. DNS expiry starts at lookup acquisition, not verification completion. Status refresh never extends it, zero-TTL answers are never reused, and DNS/TLS/key-age bounds are checked before returning.

verifyDomain() validates the bilateral ISSUANCE binding for every identity. If the verified record advertises fl=logchk, the application must additionally perform a current non-revocation check before relying on the identity for an operation it classifies as high value or irreversible:

const verified = await idm.verifyDomain('agent.example');
if (verified.requiresLogCheck()) {
const evidence = await verified.verifyNonRevocation();
console.log(evidence.logReference, evidence.freshnessTime);
}

Operation classification remains application policy. Call verifyNonRevocation() regardless of the advertised flag when local policy requires it. The returned evidence identifies the verified history bounds, completeness mechanism, checkpoint, and freshness boundary; it does not contain the events. Log unavailability or stale evidence fails the check closed.

Protocol core for the DNSid TypeScript monorepo.

@dnsid-ai/protocol provides the DNSid protocol engine and contracts: TXT identity record parsing/validation/canonicalization, JWKS validation and thumbprints, the IdentityManager verification and lifecycle engine, transparency log contracts, strict agent status validation, and common DNSid error types.

The package is runtime-neutral: DNS resolution, JSON fetching, key storage/signing, and caching are injected through the DNSResolver, JsonFetcher, KeyProvider, and IdentityCache interfaces. Node conveniences live in @dnsid-ai/sdk/node, registry workflows in @dnsid-ai/registry.

Defined in: packages/protocol/src/types.ts:8

auto: "auto";

Defined in: packages/protocol/src/types.ts:9

required: "required";

Defined in: packages/protocol/src/types.ts:11

validated: "validated";

Defined in: packages/protocol/src/types.ts:10


Defined in: packages/protocol/src/types.ts:1

FAILED: "FAILED";

Defined in: packages/protocol/src/types.ts:4

UNKNOWN: "UNKNOWN";

Defined in: packages/protocol/src/types.ts:5

UNSIGNED: "UNSIGNED";

Defined in: packages/protocol/src/types.ts:2

VALID: "VALID";

Defined in: packages/protocol/src/types.ts:3


Defined in: packages/protocol/src/errors.ts:2

Machine-readable classification of a DNSid verification failure, carried on VerificationError.

CounterpartyNotAccepted: "CounterpartyNotAccepted";

Defined in: packages/protocol/src/errors.ts:22

Configured trustedEntities policy denied a protocol-valid counterparty. Always permanent.

DNSResolution: "DNSResolution";

Defined in: packages/protocol/src/errors.ts:4

DNS lookup of the _dnsid TXT record failed or returned no identity record. Absence alone is not classified as transient.

DNSSECFailed: "DNSSECFailed";

Defined in: packages/protocol/src/errors.ts:6

DNSSEC validation failed, or the zone is unsigned when the configured DNSSEC mode requires signing.

KeyAgeExceeded: "KeyAgeExceeded";

Defined in: packages/protocol/src/errors.ts:14

The operational key is older than the identity record’s ka maximum key age.

LogError: "LogError";

Defined in: packages/protocol/src/errors.ts:20

A transparency log read, entry check, or consistency verification failed.

RecordInvalid: "RecordInvalid";

Defined in: packages/protocol/src/errors.ts:8

The identity record or a fetched JWKS is malformed or fails protocol validation.

SignatureInvalid: "SignatureInvalid";

Defined in: packages/protocol/src/errors.ts:10

The identity record signature (or a bilateral binding signature) does not verify against the entity key.

StatusNotActive: "StatusNotActive";

Defined in: packages/protocol/src/errors.ts:18

The agent status document reports a state other than active (e.g. revoked or retired).

StatusUnavailable: "StatusUnavailable";

Defined in: packages/protocol/src/errors.ts:16

The agent status endpoint is unreachable or returned an unusable response.

TLSError: "TLSError";

Defined in: packages/protocol/src/errors.ts:12

An HTTPS fetch of the JWKS or status endpoint failed at the transport/TLS layer.

type AgentStatusState =
| "PENDING"
| "PROVISIONING"
| "VERIFYING"
| "ACTIVE"
| "RETIRED"
| "REVOKED";

Defined in: packages/protocol/src/types.ts:14


type JsonFetcher = (url, opts?) => Promise<FetchResult>;

Defined in: packages/protocol/src/identity-manager.ts:81

string

JsonFetchOptions

Promise<FetchResult>


type LifecycleErrorCategory =
| "GENESIS_REQUIRED"
| "DUPLICATE_ISSUANCE"
| "INVALID_ISSUANCE"
| "TERMINAL_STATE"
| "DOMAIN_MISMATCH"
| "KEY_CONTINUITY"
| "INVALID_REVOCATION_REASON"
| "INVALID_MIGRATION"
| "SNAPSHOT_EMPTY"
| "SNAPSHOT_NON_PREFIX"
| "UNSUPPORTED_EVENT"
| "CHAIN_CONTINUITY"
| "INVALID_EVIDENCE"
| "INCOMPLETE_STREAM";

Defined in: packages/protocol/src/errors.ts:26

Stable, language-neutral categories used by lifecycle conformance vectors.


type LogEvent =
| IssuanceEvent
| KeyRotationEvent
| RevocationEvent
| RetirementEvent
| MigrationEvent
| DelegationEvent;

Defined in: packages/protocol/src/log-events.ts:122


type LogEventType = LogEvent["type"];

Defined in: packages/protocol/src/log-events.ts:130


type LogRef = string;

Defined in: packages/protocol/src/types.ts:55


type LogSignerRole =
| "Entity"
| "Operational"
| "OperationalCountersignature"
| "PreviousOperational"
| "NewOperational";

Defined in: packages/protocol/src/log.ts:4


type MaxKeyAge = "24h" | "7d" | "30d" | "90d";

Defined in: packages/protocol/src/types.ts:28


type RevocationReason =
| "keyCompromise"
| "policyViolation"
| "superseded"
| "cessationOfOperation";

Defined in: packages/protocol/src/types.ts:22

const DEFAULT_PUBLISH_PROFILE: "dnsid-draft-01" = DNSID_DRAFT01_VERSION;

Defined in: packages/protocol/src/txt-record.ts:8


const DNSID_DRAFT01_VERSION: "dnsid-draft-01" = 'dnsid-draft-01';

Defined in: packages/protocol/src/txt-record.ts:7

Immutable selector for submitted draft-ihsanullah-dnsid-01.


const DNSID_VERSION: "DNSid1" = 'DNSid1';

Defined in: packages/protocol/src/txt-record.ts:5

Pre-RFC moving verification selector. Never published while version 1 is a draft.


const JWKS_MAX_RESPONSE_BYTES: number;

Defined in: packages/protocol/src/identity-manager.ts:71


const sha256Bytes: TRet<CHash<_SHA256>>;

Defined in: node_modules/@noble/hashes/sha2.d.ts:113

SHA2-256 hash function from RFC 4634. In JS it’s the fastest: even faster than Blake3. Some info:

  • Trying 2^128 hashes would get 50% chance of collision, using birthday attack.
  • BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
  • Each sha256 hash is executing 2^18 bit operations.
  • Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.

msg

message bytes to hash

opts

Reserved hash options.

Digest bytes.

Hash a message with SHA2-256.

sha256(new Uint8Array([97, 98, 99]));

const SIGNING_ALGS: Set<string>;

Defined in: packages/protocol/src/jwks.ts:6


const STATUS_MAX_RESPONSE_BYTES: number;

Defined in: packages/protocol/src/identity-manager.ts:72


const SUPPORTED_PUBLISH_PROFILES: readonly ["dnsid-draft-01"];

Defined in: packages/protocol/src/txt-record.ts:9


const SUPPORTED_VALIDATION_PROFILES: readonly ["dnsid-draft-01", "DNSid1"];

Defined in: packages/protocol/src/txt-record.ts:10

function activeStatusDocument(lastTransitionAt?): AgentStatus;

Defined in: packages/protocol/src/agent-status.ts:16

Builds a simple ACTIVE status document for demos/tests that do not model lifecycle state.

Date = ...

AgentStatus


function canonicalIssuanceBinding(event): Uint8Array;

Defined in: packages/protocol/src/identity-manager.ts:1082

Canonical bytes covered by BOTH the entity signature and the operational countersignature of a draft-01 bilateral ISSUANCE event. Both signatures MUST cover identical content, so this is the single source of that content.

ponytail: minimal, fixed-order line encoding — no JSON key-ordering traps. Replace with the spec’s canonicalization/test vectors once they land.

IssuanceEvent

Uint8Array


function fromBase64Url(b64): Uint8Array;

Defined in: packages/protocol/src/utils.ts:150

Decodes a base64url string (accepts both padded and unpadded forms) to Uint8Array.

string

Uint8Array


function isDomainName(value): boolean;

Defined in: packages/protocol/src/utils.ts:90

Checks whether a string is a valid domain name (for gi consistency checks). Returns true if the value looks like a domain name (as opposed to a URI or other identifier).

string

boolean


function isTransientVerificationError(error): boolean;

Defined in: packages/protocol/src/retry.ts:26

Returns true when the error is a transient DNSid verification failure.

unknown

boolean


function jwkSignatureAlg(key): string;

Defined in: packages/protocol/src/jwks.ts:37

DnsIdJWK

string


function jwkThumbprint(key): Promise<string>;

Defined in: packages/protocol/src/jwks.ts:218

Computes the RFC 7638 JWK thumbprint of a key. Returns unpadded base64url (RFC 7515 §2).

Lifecycle log bindings MUST use thumbprints, not kid values, as the durable key identifier.

DnsIdJWK

Promise<string>


function keySetsShareKeyMaterial(a, b): Promise<boolean>;

Defined in: packages/protocol/src/jwks.ts:247

Returns true if any key in a shares an RFC 7638 JWK thumbprint with any key in b.

draft-01 §Two-Key Separation requires the ek and ku JWK Sets to be pairwise thumbprint-disjoint, even for self-accounted DNSids — a verifier MUST NOT infer self-accounting from key equality, so this check does not special-case any relationship between the two sets.

Throws a normalized ValidationError if any key in either set is too malformed to thumbprint.

JWKS

JWKS

Promise<boolean>


function matchesDnsName(san, fqdn): boolean;

Defined in: packages/protocol/src/utils.ts:208

RFC 9525 §4 dNSName SAN matching. Returns true when at least one SAN entry matches the given FQDN. Supports case-insensitive comparison, trailing-dot normalisation, and wildcard labels (only leftmost *. matching one or more labels at depth > 0).

string[]

string

boolean


function normalizeFQDN(name, agentFQDN?): string;

Defined in: packages/protocol/src/utils.ts:18

Converts IDNA U-labels to A-label punycode, lowercases ASCII, strips one trailing root dot, and validates DNS label constraints.

string

boolean = false

string

ValidationError if the name is empty, contains empty labels, has any label over 63 octets, exceeds the 253-octet DNS limit, or (when agentFQDN=true) exceeds the 246-octet DNSid agent limit.


function normalizePrivateAddressHost(entry): string;

Defined in: packages/protocol/src/identity-manager.ts:171

Validates one TransportConfig.privateAddressHosts entry and returns it normalized: lowercase, no trailing dot, leading dot preserved for suffix entries such as .test. IP literals, ports, schemes, paths, credentials, and empty strings are rejected with ArgumentError.

string

string


function parseCompactJose(token): object;

Defined in: packages/protocol/src/strict-json.ts:31

Standalone JOSE limits: 1 MiB compact token, 16 KiB encoded header.

string

object

header: Record<string, unknown>;
parts: [string, string, string];
payload: Uint8Array;
signature: Uint8Array;

function parseJoseObject(bytes): Record<string, unknown>;

Defined in: packages/protocol/src/strict-json.ts:52

Uint8Array

Record<string, unknown>


function parseJsonNoDuplicateMembers(bytes): unknown;

Defined in: packages/protocol/src/strict-json.ts:5

UTF-8 JSON with duplicate member rejection, including escaped member names.

Uint8Array

unknown


function parseKaDuration(ka): number;

Defined in: packages/protocol/src/utils.ts:166

Parses a duration string (as used in the ka tag) to milliseconds. Valid values: “24h”, “7d”, “30d”, “90d”.

string

number


function parseKeyId(keyId): object;

Defined in: packages/protocol/src/utils.ts:110

Parses the DNSid SDK’s cross-profile compound key ID convention: “{domain}#{kid}”.

This is an SDK/profile convention used by packages such as @dnsid-ai/jose and @dnsid-ai/http-signatures to bind a profile-level key reference to a DNSid agent FQDN plus a JWKS “kid”. It is not a DNSid protocol wire-format requirement; the protocol itself only requires JWKS keys to carry “kid” values.

Splits on the first ’#’, normalizes the domain side with normalizeFQDN(), and rejects if either side is empty or the kid side contains another ’#’.

string

object

domain: string;
kid: string;

ArgumentError if the key ID is malformed.


function requireLocalDomain(manager): string;

Defined in: packages/protocol/src/identity-manager.ts:105

Returns config.identity.domain or throws ArgumentError for verification-only managers.

SigningIdentityManager

string


function retryTransientVerification<T>(operation, options?): Promise<T>;

Defined in: packages/protocol/src/retry.ts:35

Retries an operation using exponential backoff, but only for transient VerificationError failures by default. Integrity and policy failures are never retried unless callers explicitly override shouldRetry.

T

() => Promise<T>

RetryBackoffOptions = {}

Promise<T>


function toArrayBuffer(bytes): Uint8Array<ArrayBuffer>;

Defined in: packages/protocol/src/utils.ts:181

Returns a Uint8Array view over the same memory — no copy. Required because WebCrypto’s BufferSource only accepts ArrayBuffer-backed views, not the default Uint8Array that TypeScript infers.

Uint8Array

Uint8Array<ArrayBuffer>


function toBase64Url(bytes): string;

Defined in: packages/protocol/src/utils.ts:138

Encodes a Uint8Array to unpadded base64url (RFC 7515 §2).

Uint8Array

string


function validateAgentStatus(data): AgentStatus;

Defined in: packages/protocol/src/agent-status.ts:23

Validates the DNSid JSON status profile returned by the su endpoint.

unknown

AgentStatus


function validateDnsidConfig(config?): DnsidConfig;

Defined in: packages/protocol/src/identity-manager.ts:296

Validates and snapshots a DnsidConfig. Shared by every constructor and loader so all initialization paths apply identical defaults and rejections.

unknown = {}

DnsidConfig


function verifyBilateralBinding(
event,
record,
currentEntityKey
): Promise<{
initialOperationalThumbprint: string;
}>;

Defined in: packages/protocol/src/identity-manager.ts:1119

draft-01 step-5 bilateral binding check. ISSUANCE is bilateral: it is only valid when BOTH the accountable-entity record-signing key (ek) and the initial operational key (ku) signed the same canonical binding, and that binding corresponds to the TXT record and current key material.

Verifies, against key material recorded IN THE EVENT:

  1. Each slot’s thumbprint equals jwkThumbprint(jwk) (the canonical binding only commits to the thumbprint, so the embedded JWK must be pinned to it).
  2. entitySig under entityKey, and operationalSig under operationalKey.
  3. Same DNSid FQDN and same gi as the record.
  4. The ISSUANCE-recorded entity key is the key currently at ek.

It does NOT reject a rotated ku: the current operational key may be the initial key OR a key linked to it by KEY_ROTATION continuity. That linkage (and entity KEY_ROTATION linkage) is verified separately by LogReader.verifyOperationalContinuity, which runs unconditionally in the draft-01 path of verifyDomain. The recorded initial operational thumbprint is returned so the caller can hand it to that continuity check.

IssuanceEvent

DnsIdTxtRecord

DnsIdJWK

Promise<{ initialOperationalThumbprint: string; }>


function verifyWithKey(
signingInput,
signature,
key,
expectedAlg?
): Promise<boolean>;

Defined in: packages/protocol/src/utils.ts:228

Verifies a signing input against a raw signature using a public JWK. Returns true if the signature is valid, false otherwise.

string | Uint8Array<ArrayBufferLike>

Uint8Array

DnsIdJWK

string

Promise<boolean>


function waitForVerification<T>(operation, signal): Promise<T>;

Defined in: packages/protocol/src/verification-budget.ts:43

Races cooperative work against cancellation, including already-aborted invocations.

T

(signal) => Promise<T>

AbortSignal

Promise<T>


function withVerificationBudget<T>(operation, options?): Promise<T>;

Defined in: packages/protocol/src/verification-budget.ts:10

Runs an invocation with a shared cancellation signal; child operations must not restart its budget.

T

(signal) => Promise<T>

VerificationOptions = {}

Promise<T>

Documented on Protocol core: classes:

Documented on Protocol core: interfaces: