@intyga/verify 0.0.0-bootstrap.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,803 @@
1
+ /**
2
+ * A DIV Proof Envelope — a verifiable proof of what the human approved, returned once a challenge is
3
+ * APPROVED. `canonicalPayload` is the exact signed bytes (the DIV Intent Payload); the remaining
4
+ * fields are the signature metadata needed to verify it, extended beyond DIV §4.4's raw-ES256 shape
5
+ * with the WebAuthn assertion components so a passkey approval — the primary approver path — is
6
+ * representable.
7
+ */
8
+ export interface ApprovalReceipt {
9
+ canonicalPayload: string;
10
+ target?: string | null;
11
+ actionType?: string | null;
12
+ actionDescription: string;
13
+ params: Record<string, unknown>;
14
+ /**
15
+ * EVERY witness signature over `canonicalPayload`. A quorum receipt carries one entry per approver;
16
+ * a single-signer receipt carries one. Emitting only the first approval made an M-of-N approval
17
+ * indistinguishable from a 1-of-1 one, so the quorum could not be verified offline at all.
18
+ */
19
+ signatures?: ApprovalWitness[] | null;
20
+ signerDid?: string | null;
21
+ signerPublicKey?: string | null;
22
+ signature?: string | null;
23
+ sigAlg?: string | null;
24
+ authenticatorData?: string | null;
25
+ clientDataJSON?: string | null;
26
+ requester?: RequesterIdentity | null;
27
+ verificationCode: string;
28
+ }
29
+ /** One approver's signature over the canonical payload. */
30
+ export interface ApprovalWitness {
31
+ signerDid: string;
32
+ signerPublicKey: string;
33
+ signature: string;
34
+ sigAlg?: string | null;
35
+ authenticatorData?: string | null;
36
+ clientDataJSON?: string | null;
37
+ }
38
+ /** Thrown when a value outside the JSON data model is handed to the canonicalizer. */
39
+ export declare class NonCanonicalValue extends Error {
40
+ }
41
+ /**
42
+ * Deterministic JSON with recursively sorted keys — identical to mcp-schemas.stableStringify.
43
+ *
44
+ * STRICT: RFC 8785 defines a mapping over JSON data, so anything outside that domain is REJECTED
45
+ * rather than coerced. The permissive version silently collapsed distinct runtime values onto one
46
+ * canonical string — `new Date(0)`, `{}`, `new Map()` and any class instance all serialized to `{}`,
47
+ * and a `toJSON` method was emitted as a `null`-valued key instead of being honoured. For a function
48
+ * whose entire job is "these bytes are exactly what was signed", quietly mapping several inputs onto
49
+ * one output is the wrong failure mode: the relying party's `expected.params` come from its own live
50
+ * runtime objects, so it is the caller most likely to hand us a Date.
51
+ *
52
+ * Exported so the shared golden vectors can pin THIS copy directly (vectors.test.ts) — every other
53
+ * port pins its canonicalizer against the committed file; the shipped relying-party verifier must
54
+ * not be the one implementation pinned only transitively.
55
+ */
56
+ export declare function stableStringify(value: unknown): string;
57
+ /** The requesting workload's identity, as bound into a DIV Intent Payload. Mirrors mcp-schemas. */
58
+ export interface RequesterIdentity {
59
+ did: string;
60
+ attestation: {
61
+ method: string;
62
+ issuer: string;
63
+ subject: string;
64
+ } | null;
65
+ }
66
+ /** Agent-only DIV v1 claims. Configuration and execution truth are checked by the RP's PEP. */
67
+ export interface AgentIntentContext {
68
+ action: {
69
+ reversibility: "reversible" | "irreversible";
70
+ amount: {
71
+ amount: string;
72
+ currency: string;
73
+ } | null;
74
+ };
75
+ agent: {
76
+ label: string;
77
+ configDigest: string;
78
+ delegatedBy: string | null;
79
+ };
80
+ session: {
81
+ id: string;
82
+ seq: string;
83
+ prev: string | null;
84
+ aggregate: {
85
+ amount: string;
86
+ currency: string;
87
+ } | null;
88
+ };
89
+ nbf: string;
90
+ }
91
+ export declare function validateAgentIntentContext(context: AgentIntentContext, expiresAt: string): void;
92
+ /** DIV protocol version and type discriminator — identical to mcp-schemas. */
93
+ export declare const DIV_VERSION = 1;
94
+ export declare const DIV_INTENT_TYPE = "div-intent-verification";
95
+ /**
96
+ * OFFLINE APPROVAL (docs/DIV.md §5a.2): a normal approval, signed by real humans through the normal
97
+ * quorum, but collected OUT OF BAND at incident time because the gateway is unreachable. The relying
98
+ * party builds the challenge itself, the humans review and sign it on a disconnected device, and the
99
+ * result is verified by the ordinary §5 procedure.
100
+ *
101
+ * This deliberately replaces the older pre-signed "sealed break-glass" token. Pre-signing puts a
102
+ * bearer capability on disk and captures a human judgment about a HYPOTHETICAL; moving the ceremony
103
+ * off the network instead keeps the human in the loop for the ACTUAL incident and leaves nothing at
104
+ * rest to steal. See DIV §5a.1.
105
+ *
106
+ * The distinct `type` is the single most important guardrail in the whole mechanism. It sits inside
107
+ * the signed bytes, so:
108
+ * - an offline proof can NEVER verify as a normal approval, and
109
+ * - a normal approval can NEVER be replayed as an offline one.
110
+ * Neither direction is possible even with a byte-identical action, because the reconstructed payload
111
+ * differs and the signature comparison fails. Do not "simplify" this into a flag outside the payload.
112
+ */
113
+ export declare const DIV_OFFLINE_INTENT_TYPE = "div-offline-intent";
114
+ /**
115
+ * DELEGATION (docs/DIV.md §5a.5): signed in advance by the ordinary quorum, it transfers the
116
+ * AUTHORITY TO APPROVE one pre-declared action to a named set of local operators.
117
+ *
118
+ * A delegation authorizes NOTHING by itself. `verifyApprovalReceipt` refuses this type outright and
119
+ * there is deliberately no opt-in flag that would let it through — see `verifyDelegation`, which is a
120
+ * separate operation for exactly that reason. A delegation that could authorize its own action would
121
+ * be the pre-signed bearer capability DIV §5a.1 rejects.
122
+ */
123
+ export declare const DIV_DELEGATION_TYPE = "div-delegation";
124
+ /**
125
+ * Agent authority (DIV §5b): a quorum-signed statement of STANDING SCOPE for one agent. Authorizes
126
+ * no action on its own; `verifyApprovalReceipt` refuses it outright, and `verifyAgentAuthority` is
127
+ * the only door. Mirrors mcp-schemas.
128
+ */
129
+ export declare const DIV_AGENT_AUTHORITY_TYPE = "div-agent-authority";
130
+ /**
131
+ * Platform hash-only intent (DIV §5c): an integrating platform's subject signs the DIGEST of the
132
+ * platform's own canonical payload. `verifyApprovalReceipt` refuses it outright;
133
+ * `verifyPlatformReceipt` is the only door. Mirrors mcp-schemas.
134
+ */
135
+ export declare const DIV_PLATFORM_INTENT_TYPE = "div-platform-intent";
136
+ /**
137
+ * Hard ceiling on an offline proof's validity window, enforced at verification and not only at mint.
138
+ * An offline proof is created and redeemed within one incident, so the window is minutes — it exists
139
+ * to bound a proof whose `expiresAt` was minted over-long, which is otherwise indistinguishable at
140
+ * verification time from a correct one (DIV §5a.3).
141
+ */
142
+ export declare const MAX_OFFLINE_WINDOW_MINUTES = 60;
143
+ /**
144
+ * Ceiling on the witness list this verifier will process. A DIV quorum is single digits — this is a
145
+ * denial-of-service bound, not a policy limit, because verification runs in the relying party's own
146
+ * process on an attacker-supplied receipt immediately before an irreversible action.
147
+ */
148
+ export declare const MAX_WITNESSES = 64;
149
+ /**
150
+ * Hard ceiling on a delegation's validity window. Hours, not the 30 days the old sealed token
151
+ * allowed: a delegation cannot be revoked at an offline relying party, so the short window IS the
152
+ * revocation story (DIV §5a.6).
153
+ */
154
+ export declare const MAX_DELEGATION_WINDOW_HOURS = 72;
155
+ /**
156
+ * The approval policy in force for a challenge, frozen at creation and SIGNED as part of the payload.
157
+ *
158
+ * This exists because the gateway enforces a rich requirement (quorum, four-eyes, hardware class) that
159
+ * used to appear nowhere in the signed bytes. A receipt from a 3-of-3, hardware-key-pinned challenge
160
+ * was byte-for-byte indistinguishable from a 1-of-1, no-hardware one — so a relying party doing
161
+ * "offline verification" still had to take the gateway's word for the entire policy, which is the
162
+ * exact class of trust the offline verifier exists to remove. Binding it into the payload also means
163
+ * the APPROVER sees and attests to the policy their signature is being counted toward.
164
+ *
165
+ * What a verifier can check offline, and what it cannot:
166
+ * - `requiredApprovals` — fully checkable. Counts distinct trusted approver signatures.
167
+ * - `requesterCannotApprove` — fully checkable. The requester DID is in the same signed payload.
168
+ * - `requireHardwareKey` — PARTIALLY checkable. A verifier can confirm the witness is a WebAuthn
169
+ * assertion rather than a bare P-256 key, and it refuses one whose signed authenticatorData says
170
+ * Backup Eligible or Backup State (a synced passkey announcing itself). An assertion carries no
171
+ * attestation, though, so BE=0 is the authenticator's claim, not proof of a discrete security key.
172
+ * - `allowedAaguids` — NOT checkable offline. The AAGUID lives in attestedCredentialData, which is
173
+ * present at REGISTRATION, not in an assertion. Only the gateway (which stored it at enrollment)
174
+ * can enforce the model. What a verifier CAN do is refuse what obviously cannot satisfy it: a
175
+ * non-empty allowlist is treated exactly like `requireHardwareKey` — a bare-key witness is refused
176
+ * and an offline proof is refused outright (`requiresHardwareCredential`).
177
+ * - `signerClass` — PARTIALLY checkable, and differently per witness kind. For a WEBAUTHN witness
178
+ * the UV flag (already required by `verifyWebAuthnSignature`) is cryptographic evidence a
179
+ * user-verification ceremony — a human gesture — happened at signing. An ES256 witness carries no
180
+ * signer-class evidence at all: there the class rests on the issuing deployment's signing-time
181
+ * enforcement, or, for an offline proof, on the delegation ceremony that named the operators.
182
+ * The verifier's own obligation is narrower and absolute: REFUSE any value it does not
183
+ * recognize (only "human" is defined today), so a future signer class can never verify as
184
+ * human-approved by default. It deliberately does NOT reject ES256 witnesses under
185
+ * `signerClass: "human"` — humans legitimately sign with raw P-256 keys (offline break-glass);
186
+ * a deployment wanting cryptographic proof of the ceremony pins `requireHardwareKey`.
187
+ */
188
+ /**
189
+ * Whether a signed requirement can only be met by a hardware-backed WebAuthn credential: an explicit
190
+ * `requireHardwareKey`, OR a non-empty `allowedAaguids` model allowlist. The two are the same class of
191
+ * policy for every check a verifier can make — a bare key satisfies neither, and neither can be met
192
+ * offline — so they are refused together. Treating the allowlist as "not checkable, so not checked"
193
+ * let a bare software key satisfy a YubiKey-only rule on every offline path.
194
+ */
195
+ export declare function requiresHardwareCredential(requirement: {
196
+ requireHardwareKey?: unknown;
197
+ allowedAaguids?: unknown;
198
+ }): boolean;
199
+ export interface ApprovalRequirementAttestation {
200
+ requiredApprovals: number;
201
+ requireHardwareKey: boolean;
202
+ allowedAaguids: string[];
203
+ requesterCannotApprove: boolean;
204
+ /** Required signer class — `"human"` is the only value defined today. Unrecognized values are refused. */
205
+ signerClass: string;
206
+ }
207
+ /**
208
+ * The one signer class defined by DIV today (docs/DIV.md §4.3.2). Mirrors mcp-schemas, like
209
+ * DIV_VERSION — this package deliberately imports nothing from it.
210
+ */
211
+ export declare const SIGNER_CLASS_HUMAN = "human";
212
+ /**
213
+ * Canonical DIV Intent Payload (docs/DIV.md v1) — byte-identical to
214
+ * mcp-schemas.canonicalIntentPayload. Strict RFC 8785 JCS: the whole object is serialized with every
215
+ * key sorted recursively by UTF-16 code unit via `stableStringify`. Do NOT hand-order keys.
216
+ */
217
+ export declare function canonicalIntentPayload(input: {
218
+ target: string;
219
+ actionType: string;
220
+ display: string;
221
+ params: Record<string, unknown>;
222
+ requester: RequesterIdentity;
223
+ requirement: ApprovalRequirementAttestation;
224
+ nonce: string;
225
+ expiresAt: string;
226
+ agentContext?: AgentIntentContext;
227
+ }): string;
228
+ /**
229
+ * Canonical OFFLINE INTENT payload (DIV §5a.2). Deliberately a separate function rather than a `type`
230
+ * parameter on `canonicalIntentPayload`.
231
+ *
232
+ * A parameter would mean every existing call site could silently produce the wrong kind by passing
233
+ * the wrong argument, and the normal approval path — which is the overwhelmingly common one — would
234
+ * carry a footgun for the sake of a rare one. Two functions cannot be confused: you either called the
235
+ * offline builder or you did not.
236
+ *
237
+ * `challengedAt` is the only extra field, and it exists so the verifier can bound the validity
238
+ * WINDOW. Without it, a payload minted with a 10-year `expiresAt` would be indistinguishable from a
239
+ * correctly minted one at verification time.
240
+ */
241
+ export declare function canonicalOfflineIntentPayload(input: {
242
+ target: string;
243
+ actionType: string;
244
+ display: string;
245
+ params: Record<string, unknown>;
246
+ requester: RequesterIdentity;
247
+ requirement: ApprovalRequirementAttestation;
248
+ nonce: string;
249
+ challengedAt: string;
250
+ expiresAt: string;
251
+ }): string;
252
+ /**
253
+ * Canonical DELEGATION payload (DIV §5a.5) — a signed statement about WHO MAY APPROVE, not about
254
+ * what may run.
255
+ *
256
+ * `delegatedTo` is sorted because it is a SET: the same three operators in a different order must
257
+ * produce the same bytes, exactly as for `allowedAaguids`. `requirement` here describes the quorum
258
+ * that signed this delegation, while `delegatedQuorum` is how many of `delegatedTo` must sign at
259
+ * incident time — two different quorums, which is why both are in the signed bytes.
260
+ */
261
+ export declare function canonicalDelegationPayload(input: {
262
+ target: string;
263
+ actionType: string;
264
+ display: string;
265
+ params: Record<string, unknown>;
266
+ requester: RequesterIdentity;
267
+ requirement: ApprovalRequirementAttestation;
268
+ delegatedTo: string[];
269
+ delegatedQuorum: number;
270
+ nonce: string;
271
+ sealedAt: string;
272
+ expiresAt: string;
273
+ }): string;
274
+ /**
275
+ * Canonical AGENT AUTHORITY payload (DIV §5b) — byte-identical to
276
+ * mcp-schemas.canonicalAgentAuthorityPayload; parity is enforced by `canonical-parity.test.ts`.
277
+ *
278
+ * Not a delegation: `div-delegation` deliberately covers exactly one action, forbids wildcards and
279
+ * caps its window at 72 hours, because it pre-authorizes WHO MAY APPROVE at incident time. An
280
+ * authority is governance enforced online — it may carry a scope (patterns) and a long validity
281
+ * precisely because it authorizes nothing offline. `actionPatterns` is sorted because it is a SET,
282
+ * exactly as `allowedAaguids` is.
283
+ */
284
+ export declare function canonicalAgentAuthorityPayload(input: {
285
+ target: string;
286
+ actionPatterns: string[];
287
+ display: string;
288
+ agent: {
289
+ did: string;
290
+ };
291
+ parentReceiptHash?: string | null;
292
+ requester: RequesterIdentity;
293
+ requirement: ApprovalRequirementAttestation;
294
+ nonce: string;
295
+ sealedAt: string;
296
+ expiresAt: string;
297
+ }): string;
298
+ /**
299
+ * Canonical PLATFORM HASH-ONLY INTENT payload (DIV §5c.2) — byte-parity with mcp-schemas' copy is
300
+ * enforced by canonical-parity.test.ts. This mirror REPRODUCES bytes and does not validate the
301
+ * digest grammar (producers normalize, verifiers reproduce); `verifyPlatformReceipt` enforces the
302
+ * lowercase-hex rule on the RELYING PARTY's expected value instead.
303
+ */
304
+ export declare function canonicalPlatformIntentPayload(input: {
305
+ payloadHash: string;
306
+ rpId: string;
307
+ subjectExternalId: string;
308
+ signedAt: string;
309
+ expiresAt: string;
310
+ nonce: string;
311
+ }): string;
312
+ /** Short verification code (first 8 hex of SHA-256 of the canonical payload), grouped XXXX-XXXX. */
313
+ export declare function verificationCode(canonical: string): string;
314
+ /** Verify a raw ECDSA P-256 signature (base64, DER or IEEE-P1363) over `payload` against an SPKI key. */
315
+ export declare function verifyEcdsaP256(publicKeyB64: string, payload: string, signatureB64: string): boolean;
316
+ /** Default clock-skew tolerance for expiry validation (DIV §6.2 RECOMMENDED ±30s). */
317
+ export declare const DEFAULT_CLOCK_SKEW_SECONDS = 30;
318
+ /**
319
+ * The MINIMUM approval requirement YOUR policy demands for this action (DIV §5 step 3d).
320
+ *
321
+ * The signed `requirement` is authored by whoever composed the bytes the approvers signed — the
322
+ * issuing gateway, or any one approver composing their own payload. Its signature protects it
323
+ * against third parties, NOT against the signers the quorum constrains: an approver who is also
324
+ * the requester can sign `{ requiredApprovals: 1, requesterCannotApprove: false }` alone and, without
325
+ * a floor, that receipt verifies. Supplying the floor makes the verifier refuse any signed
326
+ * requirement weaker than it, before a single signature is counted.
327
+ *
328
+ * Without a floor the verifier proves only the signers' OWN stated quorum. Supply one whenever you
329
+ * hold the approval rule (a trust bundle, a pinned policy, your own configuration).
330
+ *
331
+ * Only strictly weaker values are refused: a signed requirement at least as strict passes, and the
332
+ * signed value is what is then enforced. `allowedAaguids` is not floored — express a model
333
+ * restriction as `requireHardwareKey`, which a verifier can at least partially check.
334
+ */
335
+ export interface RequirementFloor {
336
+ /** Integer ≥ 1. The signed `requiredApprovals` must be at least this. */
337
+ requiredApprovals: number;
338
+ /** When true, the signed requirement must also forbid the requester approving. Defaults to false. */
339
+ requesterCannotApprove?: boolean;
340
+ /** When true, the signed requirement must also demand a hardware key. Defaults to false. */
341
+ requireHardwareKey?: boolean;
342
+ }
343
+ /** The reason stem every port uses when a signed requirement is below the caller's floor. */
344
+ export declare const WEAKER_REQUIREMENT_REASON = "signed requirement is weaker than the relying party's policy";
345
+ /**
346
+ * The approver identities/keys YOU trust, resolved from your own key-management policy.
347
+ *
348
+ * This is the single most important input to verification. Without it, `verifyApprovalReceipt` would
349
+ * verify a signature using the public key carried INSIDE the receipt — which proves only that the
350
+ * receipt is internally consistent, i.e. nothing. Anyone able to hand you a receipt (per the DIV
351
+ * threat model, that includes the untrusted agent itself) could mint a keypair, sign a payload over
352
+ * the nonce you issued and the params you are about to run, put any string in `signerDid`, and be
353
+ * told the action was approved by a human. DIV §3 Invariant 3 and §5 step 3 require the Approver key
354
+ * to be resolved from deployment key-management policy; this type is that step.
355
+ *
356
+ * - `{ publicKeys }` — a direct allowlist of base64 SPKI / COSE keys.
357
+ * - `{ dids, resolveKey }` — a DID allowlist plus your own resolver (directory lookup, pinned
358
+ * enrollment record, etc). Return `null` for an unknown DID to reject it.
359
+ *
360
+ * PREFER DID MODE where you can. In `publicKeys` mode the receipt's `signerDid` is an unverified
361
+ * string, so quorum has to count distinct KEYS instead of distinct approvers — and a delegation
362
+ * (DIV §5a.6), which names identities, cannot be enforced at all.
363
+ *
364
+ * `resolveKey` may return SEVERAL keys for one DID. An approver commonly holds a software key plus
365
+ * one or more registered authenticators, and any of them is legitimately theirs; returning them all
366
+ * keeps the identity intact instead of forcing callers to flatten everything into `publicKeys` mode
367
+ * and lose the DID binding. Every key returned for a DID counts as that ONE approver.
368
+ *
369
+ * SELF-CERTIFYING DIDs need no resolver. A pinned DID of the form `did:intyga:key:<fingerprint>`
370
+ * (see {@link SELF_CERTIFYING_DID_PREFIX}) is itself a commitment to the enrolled public key, so the
371
+ * witness-carried key can be validated against the DID by hashing — no key distribution at all.
372
+ * `resolveKey` is therefore optional: it is required only for pinned DIDs that are NOT
373
+ * self-certifying, and verification fails closed with an explicit reason when such a DID is
374
+ * encountered without one.
375
+ *
376
+ * PRECEDENCE: when `resolveKey` DOES return keys for a self-certifying DID, those keys are the
377
+ * anchor and the hash commitment is not consulted. That is what lets a deployment widen the DID to
378
+ * the person's later-enrolled credentials, and — the security-relevant direction — NARROW it: a
379
+ * compromised credential is dropped by mapping the DID to the remaining keys, which a
380
+ * commitment-always-wins rule would silently keep trusting.
381
+ */
382
+ export type ApproverTrustAnchor = {
383
+ publicKeys: string[];
384
+ dids?: undefined;
385
+ resolveKey?: undefined;
386
+ } | {
387
+ dids: string[];
388
+ resolveKey?: (did: string) => string | string[] | null;
389
+ publicKeys?: undefined;
390
+ };
391
+ /**
392
+ * Prefix of a self-certifying Intyga DID: `did:intyga:key:<base64url(sha256(publicKey bytes))>`.
393
+ * The identifier IS a commitment to the enrolled public key (the issuing gateway's derivation), so a
394
+ * DID of this form can serve as a complete trust anchor entry on its own. The commitment is to the
395
+ * EXACT enrolled key bytes — an approver signing with a different credential (say, a browser passkey
396
+ * registered later) does not match it, and needs a `resolveKey` mapping under a stable DID instead.
397
+ */
398
+ export declare const SELF_CERTIFYING_DID_PREFIX = "did:intyga:key:";
399
+ /**
400
+ * Derive the self-certifying DID for a public key (base64; the DECODED bytes are hashed, so padded
401
+ * and unpadded encodings of the same key derive the same DID). Mirrors the gateway's derivation.
402
+ */
403
+ export declare function selfCertifyingDid(publicKeyB64: string): string;
404
+ /** What you assert the receipt must say. `target`, `nonce` and `approvers` are required. */
405
+ export interface ReceiptExpectation {
406
+ /**
407
+ * REQUIRED. The approvers you trust — see ApproverTrustAnchor. There is deliberately no default:
408
+ * a receipt cannot be permitted to vouch for its own signer.
409
+ */
410
+ approvers: ApproverTrustAnchor;
411
+ /**
412
+ * YOUR target identifier — the Relying Party / execution environment this approval must be bound to
413
+ * (DIV Target Isolation). Required and asserted from your own identity, never read from the
414
+ * receipt: this is what rejects an approval minted for a different service (cross-service replay).
415
+ */
416
+ target: string;
417
+ actionType: string;
418
+ params: Record<string, unknown>;
419
+ /**
420
+ * The challenge nonce YOU issued and are redeeming. Required: it is what ties this receipt to one
421
+ * specific request you are tracking. See the replay note on verifyApprovalReceipt.
422
+ */
423
+ nonce: string;
424
+ /** Optionally assert WHICH workload the approval was granted to. */
425
+ requesterDid?: string;
426
+ /** Independent PEP state for an AI_AGENT receipt. Never copy this from the receipt. */
427
+ agentContext?: AgentIntentContext;
428
+ /**
429
+ * STRONGLY RECOMMENDED. The minimum requirement YOUR approval rule demands for this action — see
430
+ * RequirementFloor. Without it this verifier enforces only the quorum the signers themselves
431
+ * stated, which one approver (possibly the requester) can set to 1-of-1. Under a delegation, pass
432
+ * the ORDINARY rule: the delegated quorum must already be at least as strict (DIV §5a.5).
433
+ */
434
+ requirement?: RequirementFloor;
435
+ }
436
+ /** Verification options. The WebAuthn expectations are mandatory for a WEBAUTHN receipt. */
437
+ export interface VerifyReceiptOptions {
438
+ allowAutoApproved?: boolean;
439
+ /** A complete root-to-leaf, independently trusted chain is mandatory for delegated agent receipts. */
440
+ agentAuthorityChain?: Array<{
441
+ receipt: ApprovalReceipt;
442
+ expected: {
443
+ approvers: ApproverTrustAnchor;
444
+ target: string;
445
+ agentDid: string;
446
+ requirement?: RequirementFloor;
447
+ };
448
+ }>;
449
+ /**
450
+ * Accept an OFFLINE APPROVAL (`type: "div-offline-intent"`). Defaults to FALSE — an offline proof is
451
+ * refused on every ordinary call site, exactly like `allowAutoApproved`.
452
+ *
453
+ * Pass this at the SPECIFIC call that is allowed to run under an offline approval, never globally. A
454
+ * process-wide default would mean every gated action in the service silently accepts an
455
+ * out-of-band approval, which is the difference between an emergency mechanism and a hole.
456
+ *
457
+ * Setting it does not weaken any other check: the quorum, four-eyes and target binding signed into
458
+ * the payload are still enforced, the window is capped at MAX_OFFLINE_WINDOW_MINUTES, and a proof
459
+ * whose signed policy demands a hardware key is REFUSED (DIV §5a.3 step 4) because that requirement
460
+ * cannot be satisfied offline.
461
+ */
462
+ allowOffline?: boolean;
463
+ /**
464
+ * A delegation that has ALREADY been verified by `verifyDelegation`, substituting the eligible
465
+ * approver set and the quorum for this one verification (DIV §5a.6).
466
+ *
467
+ * Only meaningful together with `allowOffline`. This narrows rather than widens: the delegation's
468
+ * target/actionType/params must equal what you are executing, and the offline payload's signed
469
+ * `requiredApprovals` must equal the delegation's `delegatedQuorum`, so the operators still sign the
470
+ * policy their signatures are counted toward. Its expiry is rechecked at this verification's
471
+ * evaluation time even if the successful seal verification was cached.
472
+ */
473
+ delegation?: VerifiedDelegation;
474
+ /** Exact `origin` the assertion must carry, e.g. "https://app.example.com". Required for WEBAUTHN. */
475
+ expectedOrigin?: string;
476
+ /** RP ID the authenticatorData must hash to, e.g. "app.example.com". Required for WEBAUTHN. */
477
+ expectedRpId?: string;
478
+ /** Demand the User-Verified flag (biometric/PIN, not mere possession). Defaults to true. */
479
+ requireUserVerification?: boolean;
480
+ /**
481
+ * Accept an assertion produced inside a cross-origin frame. Defaults to FALSE (refuse).
482
+ *
483
+ * `origin` alone cannot detect this: inside a cross-origin iframe the browser reports the FRAME's
484
+ * origin — which for an embedded RP page is the RP's own origin — and rpIdHash matches too. So with
485
+ * `publickey-credentials-get` delegated, a third-party embedder can drive a high-risk approval
486
+ * ceremony while every other check here passes. `crossOrigin` is the only signal that distinguishes
487
+ * the two (W3C WebAuthn L3 §7.2 step 9).
488
+ */
489
+ allowCrossOrigin?: boolean;
490
+ /**
491
+ * Expiry handling (DIV §5 step 8 / §6.2). By DEFAULT this verifier is fail-closed on `expiresAt`: a proof
492
+ * whose expiry is in the past (beyond the skew tolerance) is rejected — the correct behaviour for a
493
+ * pre-execution check. Set `allowExpired: true` ONLY for post-hoc audit/forensic re-verification,
494
+ * where you deliberately want to confirm a signature that was valid at the time even though it has
495
+ * since expired. `asOf` overrides "now" for deterministic/replayed checks.
496
+ */
497
+ allowExpired?: boolean;
498
+ /**
499
+ * Wall-clock instant to evaluate time against. Defaults to `new Date()`.
500
+ *
501
+ * Also the reference point for the forward-dating rule (DIV §5a.3 rule 3), which — unlike expiry —
502
+ * `allowExpired` does NOT waive: that option re-examines a proof that was valid and has lapsed,
503
+ * which says nothing about accepting one dated in the future.
504
+ */
505
+ asOf?: Date;
506
+ /** Clock-skew tolerance in seconds for expiry and forward-dating. Defaults to DEFAULT_CLOCK_SKEW_SECONDS. */
507
+ clockSkewSeconds?: number;
508
+ }
509
+ /** One WebAuthn assertion and the exact payload it claims to sign — a single stored witness row. */
510
+ export interface WebAuthnWitness {
511
+ /** The string whose UTF-8 bytes were the assertion challenge (the canonical payload), as signed. */
512
+ signedPayload: string;
513
+ /**
514
+ * The credential's P-256 public key, base64 or base64url: a COSE_Key (as recorded at registration)
515
+ * or DER SubjectPublicKeyInfo. Supply it from YOUR record of the credential — never from the thing
516
+ * being verified — or any key the presenter chose will do.
517
+ */
518
+ publicKey: string;
519
+ /** base64 or base64url, as the browser returned them. */
520
+ authenticatorData: string;
521
+ clientDataJSON: string;
522
+ /** The DER ECDSA assertion signature. */
523
+ signature: string;
524
+ }
525
+ export interface WebAuthnWitnessExpectation {
526
+ /** The origin(s) the assertion may carry, e.g. "https://app.example.com". Required. */
527
+ expectedOrigin: string | readonly string[];
528
+ /** The RP ID the authenticatorData must hash to, e.g. "app.example.com". Required. */
529
+ expectedRpId: string;
530
+ /** Demand the User-Verified flag. Defaults to TRUE; User-Present is always required. */
531
+ requireUserVerification?: boolean;
532
+ /** Accept `clientDataJSON.crossOrigin: true`. Defaults to FALSE (DIV §4.4.5 rule 5). */
533
+ allowCrossOrigin?: boolean;
534
+ }
535
+ /**
536
+ * Verify ONE WebAuthn assertion on its own — the §4.4.5 checks every receipt verifier here applies
537
+ * to each WEBAUTHN witness, without a receipt around it: one approver of a quorum, a console step-up,
538
+ * a login approval, a row read back out of an audit ledger. Same implementation, not a copy.
539
+ *
540
+ * It answers only "did the holder of THIS key sign THIS payload, at THIS relying party, with the
541
+ * user present". It knows nothing of quorum, validity windows, payload type, the signed requirement
542
+ * or who the key belongs to: a caller verifying an approval must use verifyApprovalReceipt (or the
543
+ * delegation/agent-authority/platform verifiers), which also enforce those. Never throws.
544
+ */
545
+ export declare function verifyWebAuthnWitness(witness: WebAuthnWitness, expectation: WebAuthnWitnessExpectation): {
546
+ ok: true;
547
+ } | {
548
+ ok: false;
549
+ reason: string;
550
+ };
551
+ /**
552
+ * Independently verify an approval receipt against the instruction you are ABOUT to execute. Recomputes
553
+ * the canonical payload from your params, confirms it byte-matches what was signed, and verifies the
554
+ * human's P-256 or WebAuthn signature — with no Intyga secret.
555
+ *
556
+ * WHAT THIS PROVES: that enough APPROVERS YOU ALREADY TRUST (`expected.approvers`) signed exactly this
557
+ * action, with exactly these params, for exactly the target and nonce you pass in `expected`; that the
558
+ * number of distinct valid signatures meets the quorum recorded in the signed payload; that the
559
+ * requester did not self-approve when the signed policy forbids it; and that the proof has not expired.
560
+ *
561
+ * THE SIGNED QUORUM IS THE SIGNERS' OWN STATEMENT. The signed `requirement` is authored by whoever
562
+ * composed the bytes — so one approver (possibly the requester) can sign a 1-of-1 payload alone. Pass
563
+ * `expected.requirement` (your own rule, see RequirementFloor) and a weaker signed requirement is
564
+ * refused (DIV §5 step 3d). Without it, "quorum met" means only "the quorum the signers stated".
565
+ *
566
+ * THE TRUST ANCHOR IS NOT OPTIONAL. Verification uses the key you resolve for an approver, never the
567
+ * `signerPublicKey` carried in the receipt. A receipt verified against its own embedded key proves
568
+ * only internal consistency — anyone who can hand you a receipt could have minted the keypair.
569
+ *
570
+ * EXPIRY: the signed `expiresAt` is enforced fail-closed by default (±30s skew) — a lapsed proof is
571
+ * rejected. Pass `{ allowExpired: true }` ONLY for post-hoc audit/forensic re-verification, where
572
+ * confirming a signature that was valid AT THE TIME is the point.
573
+ *
574
+ * WHAT THIS DOES NOT PROVE: that the approval has not ALREADY BEEN USED within its validity window.
575
+ * Expiry bounds how long a proof is valid, but single-use enforcement is separate and lives in the
576
+ * gateway's /authorize/verify (which atomically marks the challenge CONSUMED) — this function is a
577
+ * defense-in-depth companion to that call, not a replacement for it. If you verify offline and skip
578
+ * the consume step, YOU must record redeemed nonces yourself; requiring `expected.nonce` here is what
579
+ * makes that possible, since you cannot call this without having tracked the nonce you issued.
580
+ *
581
+ * Returns `{ ok: false, reason }` on any mismatch.
582
+ */
583
+ export declare function verifyApprovalReceipt(receipt: ApprovalReceipt, expected: ReceiptExpectation, opts?: VerifyReceiptOptions): {
584
+ ok: boolean;
585
+ reason?: string;
586
+ autoApproved?: boolean;
587
+ signers?: string[];
588
+ };
589
+ /** A platform hash-only receipt (DIV §5c) as issued by the gateway's platform plane. */
590
+ export interface PlatformReceipt {
591
+ /** The exact bytes the subject's passkey signed (the §5c.2 payload). */
592
+ canonicalPayload: string;
593
+ /** Display copies only — verification reconstructs from the RELYING PARTY's own values. */
594
+ payloadHash?: string | null;
595
+ rpId?: string | null;
596
+ subject?: {
597
+ externalId?: string | null;
598
+ } | null;
599
+ signedAt?: string | null;
600
+ expiresAt?: string | null;
601
+ nonce?: string | null;
602
+ signatures?: ApprovalWitness[] | null;
603
+ signerDid?: string | null;
604
+ signerPublicKey?: string | null;
605
+ signature?: string | null;
606
+ sigAlg?: string | null;
607
+ authenticatorData?: string | null;
608
+ clientDataJSON?: string | null;
609
+ verificationCode?: string;
610
+ }
611
+ export interface PlatformReceiptExpectation {
612
+ /** REQUIRED. The subject keys you trust — same modes as ReceiptExpectation.approvers. */
613
+ approvers: ApproverTrustAnchor;
614
+ /**
615
+ * REQUIRED. The SHA-256 (lowercase hex) YOU recompute from your own copy of the canonical
616
+ * payload — never read from the receipt. This is the §5c data-minimization anchor: the payload
617
+ * itself never traveled, so this digest is the entire content binding.
618
+ */
619
+ payloadHash: string;
620
+ /** REQUIRED. YOUR registered WebAuthn RP ID — used for the signed-bytes binding AND as the
621
+ * assertion's expected rpIdHash. Asserted from your own configuration, never the receipt. */
622
+ rpId: string;
623
+ /** REQUIRED. The signing challenge nonce you are redeeming. */
624
+ nonce: string;
625
+ /** Optionally assert WHICH of your subjects signed. */
626
+ subjectExternalId?: string;
627
+ }
628
+ /**
629
+ * Verify a PLATFORM HASH-ONLY receipt (DIV §5c.3). Deliberately a separate function:
630
+ * `verifyApprovalReceipt` refuses the `div-platform-intent` type outright, and this function
631
+ * refuses every other type, so neither proof kind can ever pass through the other's door.
632
+ *
633
+ * Every witness must be a WebAuthn assertion (this plane's subjects only ever sign with enrolled
634
+ * passkeys on the platform's registered origin), so `opts.expectedOrigin` is REQUIRED and the RP ID
635
+ * expectation comes from `expected.rpId`. `AUTO_APPROVED` is refused with no override — policy
636
+ * pre-approval does not exist on this plane.
637
+ */
638
+ export declare function verifyPlatformReceipt(receipt: PlatformReceipt, expected: PlatformReceiptExpectation, opts?: VerifyReceiptOptions): {
639
+ ok: boolean;
640
+ reason?: string;
641
+ signers?: string[];
642
+ };
643
+ /** A delegation whose own signature, quorum and window have been verified by `verifyDelegation`. */
644
+ export interface VerifiedDelegation {
645
+ /** Identities permitted to approve at incident time. Enforced against the witness DIDs. */
646
+ delegatedTo: string[];
647
+ /** How many distinct members of `delegatedTo` must sign. */
648
+ delegatedQuorum: number;
649
+ /** The single action this delegation covers. All three must equal what is being executed. */
650
+ target: string;
651
+ actionType: string;
652
+ params: Record<string, unknown>;
653
+ /** The delegation's OWN nonce — for the audit trail, never for authorization. */
654
+ nonce: string;
655
+ /** Who signed the delegation itself. */
656
+ signers: string[];
657
+ expiresAt: string;
658
+ }
659
+ /**
660
+ * Verify a DELEGATION (DIV §5a.6 step 1) — a statement, signed in advance by the ordinary quorum, that
661
+ * names local operators who may approve one pre-declared action while the gateway is unreachable.
662
+ *
663
+ * Deliberately a SEPARATE function from `verifyApprovalReceipt`, which refuses this payload type
664
+ * outright. A delegation authorizes nothing, and the only way to keep that true structurally is to
665
+ * make it impossible to hand one to the approval verifier and get an `ok: true` back. What you get here
666
+ * is a `VerifiedDelegation` — an input to a later approval check, never a substitute for one.
667
+ *
668
+ * `approvers` MUST be the ORDINARY approver set (from your trust bundle), not the delegated operators:
669
+ * the point of the check is that the people entitled to approve this action are the ones who signed
670
+ * away that entitlement.
671
+ */
672
+ export declare function verifyDelegation(receipt: ApprovalReceipt, expected: {
673
+ /** The ORDINARY approvers entitled to delegate. Resolved from your own trust policy. */
674
+ approvers: ApproverTrustAnchor;
675
+ /** YOUR target identifier, asserted independently of the delegation (DIV Target Isolation). */
676
+ target: string;
677
+ actionType: string;
678
+ params: Record<string, unknown>;
679
+ /**
680
+ * STRONGLY RECOMMENDED. The ORDINARY approval rule for the delegated action: the delegation's
681
+ * sealing requirement must be at least this strict (DIV §5a.5, §5 step 3d). Without it only the
682
+ * sealers' own stated quorum is enforced.
683
+ */
684
+ requirement?: RequirementFloor;
685
+ }, opts?: VerifyReceiptOptions): {
686
+ ok: boolean;
687
+ reason?: string;
688
+ delegation?: VerifiedDelegation;
689
+ };
690
+ /** An agent authority whose sealing signatures, quorum and window verified (`verifyAgentAuthority`). */
691
+ export interface VerifiedAgentAuthority {
692
+ /** The agent the authority is ABOUT — from the signed bytes, asserted by the caller. */
693
+ agentDid: string;
694
+ target: string;
695
+ /**
696
+ * The signed scope, deduplicated. Case-insensitive substring patterns over the machine
697
+ * `actionType` ONLY — deliberately NOT the human-readable description, which is authored by the
698
+ * agent being bounded and would let an out-of-scope request cover itself by quoting a pattern
699
+ * (DIV §5b.2). "*" = all.
700
+ */
701
+ actionPatterns: string[];
702
+ parentReceiptHash: string | null;
703
+ /** The authority's OWN nonce — for the audit trail, never for authorization. */
704
+ nonce: string;
705
+ /** Who sealed it. */
706
+ signers: string[];
707
+ sealedAt: string;
708
+ expiresAt: string;
709
+ }
710
+ /**
711
+ * Verify an AGENT AUTHORITY (DIV §5b) — a statement, sealed by a human quorum, of the standing scope
712
+ * one agent may operate under.
713
+ *
714
+ * Deliberately a SEPARATE function from `verifyApprovalReceipt`, which refuses this payload type
715
+ * outright — the same structural rule as delegations. What you get back is governance EVIDENCE:
716
+ * "these named humans granted this agent this scope, and the grant was live at `asOf`". It is never
717
+ * an approval; executing an action still requires an ordinary receipt.
718
+ *
719
+ * Two things the caller asserts and never reads from the artifact (DIV Invariant 3):
720
+ * `expected.approvers` (the sealing quorum's keys, from your own trust policy) and
721
+ * `expected.target` / `expected.agentDid` (what YOU are checking authority over). Revocation is
722
+ * authoritative online only — an offline verifier sees validity, not revocation; treat a seal like
723
+ * a certificate, not a bearer token.
724
+ */
725
+ export declare function verifyAgentAuthority(receipt: ApprovalReceipt, expected: {
726
+ /** The sealing approvers entitled to grant. Resolved from your own trust policy. */
727
+ approvers: ApproverTrustAnchor;
728
+ /** YOUR target identifier, asserted independently of the artifact (DIV Target Isolation). */
729
+ target: string;
730
+ /** The agent whose authority you are checking, asserted independently of the artifact. */
731
+ agentDid: string;
732
+ /**
733
+ * STRONGLY RECOMMENDED. YOUR sealing policy for agent authority: the signed sealing requirement
734
+ * must be at least this strict (DIV §5b.3, §5 step 3d). Without it only the sealers' own stated
735
+ * quorum is enforced.
736
+ */
737
+ requirement?: RequirementFloor;
738
+ }, opts?: VerifyReceiptOptions): {
739
+ ok: boolean;
740
+ reason?: string;
741
+ authority?: VerifiedAgentAuthority;
742
+ };
743
+ /** Hash the COMPLETE proof, including every witness. Hashing only the intent would permit a
744
+ * never-approved pending intent to be used as a parent receipt. */
745
+ export declare function agentReceiptDigest(receipt: ApprovalReceipt): string;
746
+ /** Verify a root-to-leaf chain of human-sealed agent scopes. Each child commits the COMPLETE
747
+ * parent receipt. A child's substring pattern denotes a subset only when it contains one of the
748
+ * parent's patterns; this deliberately rejects scopes whose inclusion cannot be proved. */
749
+ export declare function verifyAgentDelegationChain(chain: Array<{
750
+ receipt: ApprovalReceipt;
751
+ expected: {
752
+ approvers: ApproverTrustAnchor;
753
+ target: string;
754
+ agentDid: string;
755
+ requirement?: RequirementFloor;
756
+ };
757
+ }>, action: {
758
+ target: string;
759
+ actionType: string;
760
+ agentDid: string;
761
+ delegatedBy: string;
762
+ }, opts?: VerifyReceiptOptions): {
763
+ ok: boolean;
764
+ reason?: string;
765
+ };
766
+ /** RP-side commitment to the exact model/tool/prompt configuration handed to the agent runtime.
767
+ * This is an RP assertion, not an integrity attestation. The PEP must recalculate it immediately
768
+ * before execution and refuse any mismatch with the signed intent. Raw prompt text is never logged. */
769
+ export declare function agentConfigDigest(config: {
770
+ model: {
771
+ provider: string;
772
+ version: string;
773
+ };
774
+ tools: Array<{
775
+ id: string;
776
+ version: string;
777
+ schemaDigest: string;
778
+ }>;
779
+ systemPrompt: string;
780
+ }): string;
781
+ /** Verify a complete ordered session bundle against a head pinned OUTSIDE the bundle. An
782
+ * unanchored single branch cannot prove another branch was not withheld. */
783
+ export declare function verifyAgentSessionChain(entries: Array<{
784
+ receipt: ApprovalReceipt;
785
+ expected: ReceiptExpectation;
786
+ }>, trustedHead: string, opts?: VerifyReceiptOptions): {
787
+ ok: boolean;
788
+ reason?: string;
789
+ aggregate?: {
790
+ amount: string;
791
+ currency: string;
792
+ } | null;
793
+ };
794
+ export { ALGORITHM_REGISTRY, type AlgorithmRegistry, AUDIT_PROFILE, BUNDLE_KIND, type BundleVerification, type CheckResult, DEWP_PROTOCOL, DEWP_VERSION, deriveVerificationLevel, leafCountMismatch, type ProofBundle, type TrustedCheckpoint, type VerificationLevel, type VerificationProperties, verifyBundle, verifyEmbeddedSignature, type VerifyOptions, } from "./ledger-bundle.js";
795
+ export { EVIDENCE_BUNDLE_KIND, type EvidenceAnchorSet, type EvidenceBundle, type EvidenceEntry, type EvidenceVerification, type EvidenceVerifyOptions, type RedactionRecord, verifyEvidenceBundle, } from "./ledger-evidence.js";
796
+ export { ANCHOR_ALGORITHMS, ANCHOR_CLOCK_SKEW_SECONDS, type AnchorInput, type AnchorKeyResolver, type AnchorPolicy, type AnchorQuorumResult, DEFAULT_MAX_ANCHOR_LAG_SECONDS, type ExpectedCheckpoint, type ExternalAnchorKeys, anchorDigest, anchorDigestHex, anchorPreimage, isWellFormedAnchor, parseAnchorTimestampMs, signAnchor, type SignedAnchor, verifyAnchorQuorum, verifyAnchorSignature, } from "./ledger-anchor.js";
797
+ export { type ChainInput, type ChainVerification, chainHash, chainPreimage, GENESIS_PREV_CHAIN_HASH, type RootsChainEntry, verifyRootsChain, } from "./ledger-chain.js";
798
+ export { parseRekorEvidence, type RekorEvidence, type RekorVerification, rekorPayloadHashFor, verifyRekorAnchor, } from "./ledger-rekor.js";
799
+ export { type AuditLeaf, canonicalPreimage, leafHash } from "./ledger-leaf.js";
800
+ export { hashLeaf, hashPair, merkleProof, merkleRoot, type ProofStep, sha256Hex, verifyMerkleProof, } from "./ledger-merkle.js";
801
+ export { type InclusionProof, verifyInclusionProof } from "./ledger-proof.js";
802
+ export { verifyRfc3161Anchor, verifyRfc3161Timestamp, verifyRfc3161TimestampAsync, type Rfc3161Trust, type Rfc3161Verification, } from "./ledger-rfc3161.js";
803
+ export { verifyAuditSignature, type AuditSignaturePolicy, type AuditSignatureCheck, } from "./ledger-signature.js";