/** * Envelope shapes for the deepidv chain layer. * * Mirrors `shared-deps/chain/lib/types.ts`, which is the source of * truth. Any drift between this file and ARCHITECTURE.md §5 is a bug * — the architecture doc wins. * * The envelope is what a tenant verifies with: it carries the claim * hash, the issuer id, the subject pseudonym, the dual-TSA timestamps, * and a record-type tag. Signatures live in the proof bundle's * `signature.bin`, NOT in the envelope (per ARCHITECTURE.md §5 the * envelope IS what's signed; embedding the signature would create a * circular hash preimage). */ /** * Envelope format version. * * v = 1 current. Plain envelope with `claim_hash` as a SHA-256 * digest of the canonical claim body stored by the tenant. * v = 2 reserved for a future schema evolution. * v = 3 TripleLock (Phase 2). Three nested AES-256-GCM layers; the * outer key is the subject's, so not even deepidv can decrypt * without the subject's explicit unlock. * * `v` is included in the hash preimage, so v1 envelopes verified * today remain verifiable indefinitely regardless of what v2+ * envelopes look like. */ type EnvelopeVersion = 1 | 2 | 3; /** * Signing algorithm identifier — used by STH preimages, not by the v1 * envelope itself (envelope algorithm is implicit in `v = 1`). * * v1 launches with ECDSA over NIST P-256 with SHA-256 (AWS KMS spec * `ECC_NIST_P256`, signing algorithm `ECDSA_SHA_256`). When v2+ ships * with a different algorithm, this union widens. */ type SigningAlg = "ECDSA_P256_SHA256"; /** * Record type for the v1 envelope's `t` field. * * - `IDV` is the only record type emitted by the v1 mint Lambda. * - `BIO`, `DOC`, and `ADDR` are reserved for Phase 2 (per-capability * event emission) and are included in the union as a forward- * compatibility marker. Bundles tagged `BIO`, `DOC`, or `ADDR` will * not appear in v1; do not author them client-side. * - `WIT` is reserved for Phase 3 — Witness attestation (DIDV-481/483). * - `AGT` is reserved for Phase 3 — Agent identity (DIDV-488/489). * * `RSK | AML | AGR | ACT` are deliberately NOT in this union. Adding * them later is a semver-minor bump in the SDK, semver-major in the * envelope schema. */ type RecordType = "IDV" | "BIO" | "DOC" | "ADDR" | "WIT" | "AGT"; /** * Label commitment as it appears in the envelope. * * `commit` is `"sha256:"` of * `SHA-256(name || ":" || value || ":" || salt_16_hex)`. * * `public_value` appears only when the issuer marked the label * non-sensitive at mint time. Sensitive labels omit it; the subject * can reveal the salt later through a proof bundle. * * Salts are NOT in the envelope — they live server-side on the * chain-log row and ship to the bundle only when the bundle was * built with an explicit reveal-set (see `RevealedLabel` in * `bundle.ts`). */ interface LabelCommit { name: string; commit: string; public_value?: boolean | string | number; } /** * Canonical v1 envelope. Matches ARCHITECTURE.md §5 exactly. * * When hashing (see `envelopeHash` in `../crypto`), the envelope is * JCS-canonicalized (RFC 8785) then SHA-256'd. */ interface EnvelopeV1 { v: 1; t: RecordType; /** ULID with `attest_` prefix — e.g. `"attest_01JAXK9F7W..."`. */ id: string; /** Opaque tenant id; the Organizations-{env} table's id column. */ tenant: string; /** Human-readable issuer id — e.g. `"iss_acme_prod"`. */ issuer: string; /** `"sha256:"` — salted pseudonym, never the raw identifier. */ subject: string; /** `"sha256:"` of the canonical claim body (tenant-side). */ claim_hash: string; labels?: LabelCommit[]; /** * Dual RFC 3161 timestamps. At least one MUST be present at mint * time. Values are `"b64:"`. */ rts: { digicert?: string; sectigo?: string; }; /** ISO 8601 — derived from the upstream Session's `verifiedAt`. */ minted_at: string; /** ULID tying this envelope to the originating SNS message. */ correlation_id: string; } /** * Narrow alias — extend to a discriminated union when v2/v3 * envelopes ship. */ type Envelope = EnvelopeV1; /** * Public registry API response types — `api.proof.deepidv.com/v1`. * * Mirrors the live shapes confirmed against the staging deploy at * 2026-05-01 (cross-checked with `proof-deepidv/src/lib/api/types.ts`, * the M06b reference). Where the M06.md spec disagreed with the * deployed surface, the deployed surface wins (e.g. the singular * `/v1/attestation/:id` route). * * All endpoints are unauthenticated — the registry is a public * transparency log. Authenticated routes (`/governance/*`, * `/admin/*`) are not part of this SDK; they belong to the * Governance Console (M10). * * Conventions: * - Hex fields (root, leafHash, txHash) are lowercase with no `0x` * prefix unless explicitly typed as `0x${string}`. * - Timestamps are ISO 8601 UTC with `Z` suffix. * - Cursor pagination — `nextCursor: string | null`. `null` means * "no more pages". */ /** * Network short name for the on-chain anchor. * * The registry API returns `"base"` for mainnet (NOT `"base-mainnet"`, * which is what `OnchainProofJson` in the bundle uses). The bundle * shape and the API shape diverge here intentionally — bundles need a * canonical name that won't drift, while the registry leans on the * network's vernacular. */ type AnchorNetwork = "base-sepolia" | "base"; interface ChainAnchor { network: AnchorNetwork; txHash: `0x${string}`; blockNumber?: number; treeRootHex: string; timestamp: string; } interface RegistryLabelView { name: string; /** * `true` when the registry has a `public_value` for this label * (issuer marked it non-sensitive at mint time). Subjects can * still later reveal hidden labels via a proof bundle. */ revealed?: boolean; /** Present iff `revealed === true`. */ value?: string; } interface RegistryRow { id: string; recordType: RecordType; issuerId: string; mintedAt: string; segment: number; leafIndex: number; labels: RegistryLabelView[]; anchored: boolean; envelopeHash?: string; } interface RegistryPage { items: RegistryRow[]; nextCursor: string | null; } interface InclusionView { treeSize: number; leafIndex: number; auditPath: string[]; rootHex: string; } interface SthView { version: string; treeSize: number; rootHex: string; timestamp: string; checkpoint: boolean; closure?: boolean; masterSigB64?: string; keyId?: string; alg?: string; anchor?: ChainAnchor; } interface AttestationDetail { id: string; recordType: RecordType; segment: number; leafIndex: number; envelopeHash: string; issuerId: string; mintedAt: string; labels: RegistryLabelView[]; envelope: Envelope; inclusion?: InclusionView; sth?: SthView; anchor?: ChainAnchor; } interface SthListResponse { segment: number; items: SthView[]; } interface SegmentDetail { segment: number; treeSize: number; closed: boolean; sparkline: Array<{ ts: string; treeSize: number; }>; sths: SthView[]; anchors?: ChainAnchor[]; } interface IssuerDetail { id: string; status: "active" | "rotating" | "revoked"; publicKeyPem: string; rotations: Array<{ at: string; reason?: string; previousKeyFingerprint?: string; }>; recentAttestations: RegistryRow[]; } /** * Consistency proof between two STHs in the same segment. * * The `proof` array is ordered as RFC 6962 §2.1.2 specifies (consumed * by `verifyConsistency` in `../crypto`). Verifiers check that * `oldRoot` and `newRoot` match the `rootHex` fields of the matching * STHs in the registry. */ interface ConsistencyProofResponse { segment: number; fromTreeSize: number; toTreeSize: number; oldRootHex: string; newRootHex: string; proof: string[]; } /** * Live attestation stream event over Server-Sent Events. * * The registry API exposes `/v1/stream` as a long-lived `text/event- * stream` that emits one JSON-encoded `StreamEvent` per `data:` * frame. The SDK's `streamAttestations()` AsyncIterable handles * reconnection with exponential backoff and surfaces these events * unchanged. * * Additional event `type` values may be added in minor versions; the * union is intentionally open at the SDK type level — consumers * should switch on `type` and ignore unknown variants rather than * exhaustiveness-check. */ type StreamEvent = { type: "attestation.minted"; payload: { id: string; recordType: RecordType; issuerId: string; mintedAt: string; segment: number; leafIndex: number; envelopeHash?: string; anchored?: boolean; }; } | { type: "sth.signed"; payload: { segment: number; treeSize: number; rootHex: string; timestamp: string; checkpoint: boolean; }; } | { type: "anchor.confirmed"; payload: { segment: number; network: AnchorNetwork; txHash: `0x${string}`; treeRootHex: string; }; }; /** Filters accepted by `listRegistry`. */ interface RegistryListFilters { type?: RecordType; issuer?: string; q?: string; } export type { AttestationDetail as A, ConsistencyProofResponse as C, Envelope as E, IssuerDetail as I, LabelCommit as L, RegistryListFilters as R, SegmentDetail as S, RegistryPage as a, SthListResponse as b, StreamEvent as c, AnchorNetwork as d, ChainAnchor as e, EnvelopeV1 as f, EnvelopeVersion as g, InclusionView as h, RecordType as i, RegistryLabelView as j, RegistryRow as k, SigningAlg as l, SthView as m };