@metalabel/dfos-protocol 0.50.0 → 0.52.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.
@@ -79,6 +79,68 @@ var normalizedId = (prefix, id) => {
79
79
  return `${prefixLowered}_${idLowered}`;
80
80
  };
81
81
 
82
+ // src/crypto/json-scan.ts
83
+ var assertCanonicalJsonText = (text) => {
84
+ const frames = [];
85
+ let i = 0;
86
+ while (i < text.length) {
87
+ const ch = text[i];
88
+ if (ch === '"') {
89
+ const end = scanStringToken(text, i);
90
+ if (end < 0) return;
91
+ const value = decodeStringToken(text.slice(i, end));
92
+ if (value === null) return;
93
+ if (LONE_SURROGATE_RE.test(value)) {
94
+ throw new Error("string with an unpaired surrogate is not canonicalizable");
95
+ }
96
+ const frame = frames[frames.length - 1];
97
+ if (frame && frame.isObject && frame.awaitingKey) {
98
+ if (frame.keys.has(value)) {
99
+ throw new Error(`duplicate JSON key is malformed: ${JSON.stringify(value)}`);
100
+ }
101
+ frame.keys.add(value);
102
+ frame.awaitingKey = false;
103
+ }
104
+ i = end;
105
+ continue;
106
+ }
107
+ if (ch === "{") {
108
+ frames.push({ isObject: true, keys: /* @__PURE__ */ new Set(), awaitingKey: true });
109
+ } else if (ch === "[") {
110
+ frames.push({ isObject: false, keys: /* @__PURE__ */ new Set(), awaitingKey: false });
111
+ } else if (ch === "}" || ch === "]") {
112
+ frames.pop();
113
+ } else if (ch === ",") {
114
+ const frame = frames[frames.length - 1];
115
+ if (frame && frame.isObject) frame.awaitingKey = true;
116
+ }
117
+ i++;
118
+ }
119
+ };
120
+ var scanStringToken = (text, start) => {
121
+ let i = start + 1;
122
+ while (i < text.length) {
123
+ const ch = text[i];
124
+ if (ch === "\\") {
125
+ i += 2;
126
+ continue;
127
+ }
128
+ if (ch === '"') return i + 1;
129
+ i++;
130
+ }
131
+ return -1;
132
+ };
133
+ var decodeStringToken = (raw) => {
134
+ if (!raw.includes("\\")) return raw.slice(1, -1);
135
+ try {
136
+ const value = JSON.parse(raw);
137
+ return typeof value === "string" ? value : null;
138
+ } catch {
139
+ return null;
140
+ }
141
+ };
142
+ var LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
143
+
82
144
  // src/crypto/jws-profile.ts
83
145
  var assertJwsProfile = (header, makeError) => {
84
146
  if (header.alg !== "EdDSA") {
@@ -96,6 +158,30 @@ var assertJwsProfile = (header, makeError) => {
96
158
  };
97
159
 
98
160
  // src/crypto/jws.ts
161
+ var decodeJwsSegment = (segmentB64) => {
162
+ let text;
163
+ try {
164
+ text = new TextDecoder("utf-8", { fatal: true }).decode(base64urlDecode(segmentB64));
165
+ } catch {
166
+ return null;
167
+ }
168
+ assertCanonicalJsonText(text);
169
+ let value;
170
+ try {
171
+ value = JSON.parse(text);
172
+ } catch {
173
+ return null;
174
+ }
175
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
176
+ return value;
177
+ };
178
+ var asJwsHeader = (raw) => {
179
+ if (typeof raw["alg"] !== "string") return null;
180
+ if (typeof raw["typ"] !== "string") return null;
181
+ if (typeof raw["kid"] !== "string") return null;
182
+ if ("cid" in raw && typeof raw["cid"] !== "string") return null;
183
+ return raw;
184
+ };
99
185
  var createJws = async (options) => {
100
186
  const headerB64 = base64urlEncode(JSON.stringify(options.header));
101
187
  const payloadB64 = base64urlEncode(JSON.stringify(options.payload));
@@ -111,18 +197,22 @@ var verifyJws = (options) => {
111
197
  throw new JwsVerificationError("Invalid token format");
112
198
  }
113
199
  const [headerB64, payloadB64, signatureB64] = parts;
114
- let header;
200
+ let rawHeader;
115
201
  let payload;
116
202
  try {
117
- header = JSON.parse(new TextDecoder().decode(base64urlDecode(headerB64)));
118
- payload = JSON.parse(new TextDecoder().decode(base64urlDecode(payloadB64)));
119
- } catch {
203
+ rawHeader = decodeJwsSegment(headerB64);
204
+ payload = decodeJwsSegment(payloadB64);
205
+ } catch (e) {
206
+ throw new JwsVerificationError(e.message);
207
+ }
208
+ if (!rawHeader || !payload) {
120
209
  throw new JwsVerificationError("Failed to decode token");
121
210
  }
122
- assertJwsProfile(
123
- header,
124
- (m) => new JwsVerificationError(m)
125
- );
211
+ assertJwsProfile(rawHeader, (m) => new JwsVerificationError(m));
212
+ const header = asJwsHeader(rawHeader);
213
+ if (!header) {
214
+ throw new JwsVerificationError("Invalid protected header");
215
+ }
126
216
  const signingInput = `${headerB64}.${payloadB64}`;
127
217
  const signingInputBytes = new TextEncoder().encode(signingInput);
128
218
  const signatureBytes = base64urlDecode(signatureB64);
@@ -135,14 +225,19 @@ var verifyJws = (options) => {
135
225
  var decodeJwsUnsafe = (token) => {
136
226
  const parts = token.split(".");
137
227
  if (parts.length !== 3) return null;
228
+ const [headerB64, payloadB64] = parts;
229
+ let rawHeader;
230
+ let payload;
138
231
  try {
139
- const [headerB64, payloadB64] = parts;
140
- const header = JSON.parse(new TextDecoder().decode(base64urlDecode(headerB64)));
141
- const payload = JSON.parse(new TextDecoder().decode(base64urlDecode(payloadB64)));
142
- return { header, payload };
232
+ rawHeader = decodeJwsSegment(headerB64);
233
+ payload = decodeJwsSegment(payloadB64);
143
234
  } catch {
144
235
  return null;
145
236
  }
237
+ if (!rawHeader || !payload) return null;
238
+ const header = asJwsHeader(rawHeader);
239
+ if (!header) return null;
240
+ return { header, payload };
146
241
  };
147
242
  var JwsVerificationError = class extends Error {
148
243
  constructor(message) {
@@ -203,7 +298,7 @@ var verifyJwt = (options) => {
203
298
  const isValid = isValidEd25519Signature(signingInputBytes, signatureBytes, options.publicKey);
204
299
  if (!isValid) throw new JwtVerificationError("Invalid signature");
205
300
  const currentTime = options.currentTime ?? Math.floor(Date.now() / 1e3);
206
- if (payload.exp <= currentTime) {
301
+ if (!Number.isSafeInteger(payload.exp) || payload.exp <= currentTime) {
207
302
  throw new JwtVerificationError("Token expired");
208
303
  }
209
304
  if (options.issuer !== void 0 && payload.iss !== options.issuer) {
@@ -231,20 +326,25 @@ import * as Block from "multiformats/block";
231
326
  import { CID } from "multiformats/cid";
232
327
  import { sha256 as sha2562 } from "multiformats/hashes/sha2";
233
328
  var dagCborCanonicalEncode = async (value) => {
234
- assertCanonicalNumbers(value);
329
+ assertCanonicalValue(value);
235
330
  const serialized = JSON.parse(JSON.stringify(value));
236
- assertCanonicalNumbers(serialized);
237
- return await Block.encode({
238
- // removes any undefineds or other non-serializable values (and normalizes
239
- // -0 to 0)
240
- value: serialized,
241
- codec: dagCborCodec,
242
- hasher: sha2562
243
- });
331
+ assertCanonicalValue(serialized);
332
+ try {
333
+ return await Block.encode({
334
+ // removes any undefineds or other non-serializable values (and normalizes
335
+ // -0 to 0)
336
+ value: serialized,
337
+ codec: dagCborCodec,
338
+ hasher: sha2562
339
+ });
340
+ } catch (e) {
341
+ throw new Error(`value is not canonically encodable: ${e.message}`);
342
+ }
244
343
  };
245
344
  var MAX_SAFE_CANONICAL_INTEGER = 9007199254740991;
246
345
  var MAX_CANONICAL_DEPTH = 1024;
247
- var assertCanonicalNumbers = (value, depth = 0) => {
346
+ var LONE_SURROGATE_RE2 = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
347
+ var assertCanonicalValue = (value, depth = 0) => {
248
348
  if (depth > MAX_CANONICAL_DEPTH) {
249
349
  throw new Error(`value nesting exceeds max depth ${MAX_CANONICAL_DEPTH}`);
250
350
  }
@@ -264,12 +364,26 @@ var assertCanonicalNumbers = (value, depth = 0) => {
264
364
  }
265
365
  return;
266
366
  }
367
+ if (typeof value === "string") {
368
+ if (LONE_SURROGATE_RE2.test(value)) {
369
+ throw new Error("string with an unpaired surrogate is not canonicalizable");
370
+ }
371
+ return;
372
+ }
267
373
  if (Array.isArray(value)) {
268
- for (const entry of value) assertCanonicalNumbers(entry, depth + 1);
374
+ for (const entry of value) assertCanonicalValue(entry, depth + 1);
269
375
  return;
270
376
  }
271
377
  if (value !== null && typeof value === "object") {
272
- for (const entry of Object.values(value)) assertCanonicalNumbers(entry, depth + 1);
378
+ if ("/" in value && "bytes" in value) {
379
+ throw new Error('object carrying both "/" and "bytes" members is not canonicalizable');
380
+ }
381
+ for (const [key, entry] of Object.entries(value)) {
382
+ if (LONE_SURROGATE_RE2.test(key)) {
383
+ throw new Error("string with an unpaired surrogate is not canonicalizable");
384
+ }
385
+ assertCanonicalValue(entry, depth + 1);
386
+ }
273
387
  }
274
388
  };
275
389
  var parseDagCborCID = (cid) => {
@@ -293,6 +407,7 @@ export {
293
407
  generateId,
294
408
  isValidId,
295
409
  normalizedId,
410
+ assertCanonicalJsonText,
296
411
  assertJwsProfile,
297
412
  createJws,
298
413
  verifyJws,
@@ -1,15 +1,16 @@
1
- export { b as Attenuation, e as CredentialVerificationError, D as DFOSCredentialPayload, i as MAX_CREDENTIAL_SIZE, R as RevocationChecker, V as VerifiedDFOSCredential, v as VerifiedDelegationChain, y as createDFOSCredential, z as decodeDFOSCredentialUnsafe, B as isAttenuated, E as matchesResource, G as verifyDFOSCredential, H as verifyDelegationChain } from '../dfos-credential-BtiYPqBT.js';
1
+ export { b as Attenuation, e as CredentialVerificationError, D as DFOSCredentialPayload, i as MAX_CREDENTIAL_SIZE, R as ResolvedIdentity, r as RevocationChecker, V as VerifiedDFOSCredential, w as VerifiedDelegationChain, z as createDFOSCredential, B as decodeDFOSCredentialUnsafe, E as isAttenuated, F as matchesResource, H as verifyDFOSCredential, J as verifyDelegationChain } from '../dfos-credential-j-kHtFvG.js';
2
2
  import 'zod';
3
3
 
4
4
  /**
5
- * The normative JWS header `typ` for a request proof (API-AUTH.md). Signers MUST
6
- * set it; the request-proof verifier rejects anything else — it is also what lets
7
- * typ-routing dispatchers tell a proof apart from credentials and chain ops.
5
+ * The normative JWS header `typ` for a request proof (INTEGRATIONS.md, The
6
+ * request proof). Signers MUST set it; the request-proof verifier rejects
7
+ * anything else — it is also what lets typ-routing dispatchers tell a proof
8
+ * apart from credentials and chain ops.
8
9
  */
9
10
  declare const REQUEST_PROOF_JWS_TYP = "did:dfos:request-proof";
10
11
  /**
11
- * The normative JWS header `typ` for an identity proof (API-AUTH.md) — the
12
- * request proof's credential-less sibling.
12
+ * The normative JWS header `typ` for an identity proof (INTEGRATIONS.md, The
13
+ * identity proof) — the request proof's credential-less sibling.
13
14
  *
14
15
  * THE TYP GATE IS ABSOLUTE, IN BOTH DIRECTIONS. "Possession of a grant's
15
16
  * audience key" and "possession of a bare identity's key" are different claims,
@@ -84,10 +85,10 @@ interface IdentityProofPayload {
84
85
  /**
85
86
  * ADDITIVE MEMBERS, appended AFTER the canonical order.
86
87
  *
87
- * API-AUTH.md's growth rule is additive members on this envelope, never a new
88
- * envelope: "additional members register additively, appended to the canonical
89
- * order". `jti` — the per-request uniqueness member a write-gating deployment
90
- * requires — is the named one.
88
+ * INTEGRATIONS.md, One envelope, optional credential's growth rule is additive
89
+ * members on this envelope, never a new envelope: "additional members register
90
+ * additively, appended to the canonical order". `jti` — the per-request
91
+ * uniqueness member a write-gating deployment requires — is the named one.
91
92
  *
92
93
  * TWO RULES MAKE THIS A BYTE-TWIN. (1) Extra members are emitted AFTER every
93
94
  * canonical member, so a verifier that ignores them still reconstructs the same
@@ -173,7 +174,7 @@ interface SignApiRequestInput {
173
174
  /**
174
175
  * ADDITIVE members, appended after the canonical order in lexicographic name
175
176
  * order. `{ jti }` is the registered one — required by a deployment that gates
176
- * WRITES with this envelope (API-AUTH.md, Security Considerations).
177
+ * WRITES with this envelope (INTEGRATIONS.md, API security notes).
177
178
  */
178
179
  extraMembers?: ProofExtraMembers;
179
180
  }
@@ -211,7 +212,7 @@ interface SignApiIdentityRequestInput {
211
212
  /**
212
213
  * ADDITIVE members, appended after the canonical order in lexicographic name
213
214
  * order. `{ jti }` is the registered one, and a WRITE-SHAPED surface — relay
214
- * ingestion, blob upload — REQUIRES it (WEB-RELAY.md, Authentication).
215
+ * ingestion, blob upload — REQUIRES it (INTEGRATIONS.md, API security notes).
215
216
  */
216
217
  extraMembers?: ProofExtraMembers;
217
218
  }
@@ -247,8 +248,8 @@ declare const buildApiAuthHeaders: (input: {
247
248
  * On an `api:<host>` surface an accompanying `X-Credential` is MALFORMED: the
248
249
  * two headers would assert two different claims at once. A relay content-plane
249
250
  * read is NOT that case — there the identity proof is the AuthN half and a DFOS
250
- * credential presentation is a separate authorization artifact (WEB-RELAY.md,
251
- * Authentication) — so that refusal belongs to the middleware of the surface
251
+ * credential presentation is a separate authorization artifact (RELAY.md,
252
+ * Access) — so that refusal belongs to the middleware of the surface
252
253
  * being served, never to this builder.
253
254
  */
254
255
  declare const buildApiIdentityHeaders: (input: {
@@ -317,7 +318,7 @@ interface ProofPresenterState {
317
318
  type ResolveProofPresenter = (did: string) => Promise<ProofPresenterState | null>;
318
319
  /**
319
320
  * What the PROOF PHASE reads — the subset of a verifier's inputs that
320
- * API-AUTH.md steps 1–7 touch.
321
+ * INTEGRATIONS.md, Verification algorithm steps 1–7 touch.
321
322
  */
322
323
  interface ProofEnvelopeInput {
323
324
  /** The proof JWS — the `Authorization: DFOS <token>` token, scheme stripped. */
@@ -346,8 +347,8 @@ interface ProofEnvelopeInput {
346
347
  now?: () => number;
347
348
  }
348
349
  /**
349
- * API-AUTH.md step 4's CONFIG half, hoisted so a caller can run it BEFORE any
350
- * request-dependent gate.
350
+ * INTEGRATIONS.md, Verification algorithm step 4's freshness configuration,
351
+ * hoisted so a caller can run it BEFORE any request-dependent gate.
351
352
  *
352
353
  * ORDER IS LOAD-BEARING. A deployment whose freshness span is out of bounds must
353
354
  * never verify anything, and its misconfiguration must never be REPORTED as a
@@ -399,9 +400,9 @@ interface VerifiedProofEnvelope {
399
400
  */
400
401
  declare const verifyIdentityProofEnvelope: (input: ProofEnvelopeInput, resolvePresenter: ResolveProofPresenter) => Promise<VerifiedProofEnvelope>;
401
402
  /**
402
- * Verify a REQUEST proof's envelope — API-AUTH.md steps 1–7 with the request
403
- * `typ`. The caller then performs steps 8–11 (the credential walk), for which a
404
- * verified proof signature is the gate.
403
+ * Verify a REQUEST proof's envelope — INTEGRATIONS.md, Verification algorithm
404
+ * steps 1–7 with the request `typ`. The caller then performs steps 8–11 (the
405
+ * credential walk), for which a verified proof signature is the gate.
405
406
  */
406
407
  declare const verifyRequestProofEnvelope: (input: ProofEnvelopeInput, resolvePresenter: ResolveProofPresenter) => Promise<VerifiedProofEnvelope>;
407
408
 
@@ -34,9 +34,9 @@ import {
34
34
  verifyDelegationChain,
35
35
  verifyIdentityProofEnvelope,
36
36
  verifyRequestProofEnvelope
37
- } from "../chunk-MHSBBYDO.js";
38
- import "../chunk-IDVYITX7.js";
39
- import "../chunk-4LG2GEB2.js";
37
+ } from "../chunk-IV3TIYKP.js";
38
+ import "../chunk-F7RHI2CK.js";
39
+ import "../chunk-QTJZGXMH.js";
40
40
  export {
41
41
  ApiRequestVerifyError,
42
42
  Attenuation,
@@ -128,6 +128,28 @@ declare class JwsVerificationError extends Error {
128
128
  constructor(message: string);
129
129
  }
130
130
 
131
+ /**
132
+ * Reject a signed JSON document whose raw text carries something its decoded
133
+ * value can no longer show:
134
+ *
135
+ * 1. DUPLICATE KEYS — PROTOCOL: "A payload containing duplicate keys is
136
+ * malformed: the signature commits to the raw payload bytes while the CID
137
+ * derives from the decoded value." Every JSON parser silently resolves a
138
+ * duplicate (last wins), so the only place to see one is the text.
139
+ * 2. LONE SURROGATE ESCAPES — a `\uD800`-class escape with no pair. TypeScript
140
+ * keeps it; Go's decoder replaces it with U+FFFD. Two verifiers would derive
141
+ * two different CIDs from the same signed bytes, so refusing the escape is
142
+ * the one verdict both can reach.
143
+ *
144
+ * This is a tokenizer, not a parser: it does not validate the grammar — the
145
+ * caller's `JSON.parse` is the grammar judge, and this walk simply stops at the
146
+ * first thing it cannot read. It tracks only enough structure to know which
147
+ * strings are member names of which object.
148
+ *
149
+ * MUST match the Go reference (AssertCanonicalJSONText in json_scan.go).
150
+ */
151
+ declare const assertCanonicalJsonText: (text: string) => void;
152
+
131
153
  /**
132
154
  * Apply the DFOS signature verification profile to a decoded protected header.
133
155
  *
@@ -214,4 +236,4 @@ declare const parseDagCborCID: (cid: string) => CID<unknown, number, number, mul
214
236
  */
215
237
  declare const isCanonicallyEqual: (data1: unknown, data2: unknown) => Promise<boolean>;
216
238
 
217
- export { type JwsHeader, JwsVerificationError, type JwtClaims, type JwtCreateOptions, type JwtHeader, JwtVerificationError, type JwtVerifyOptions, type PrefixedID, assertJwsProfile, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, sha256, signPayloadEd25519, verifyJws, verifyJwt };
239
+ export { type JwsHeader, JwsVerificationError, type JwtClaims, type JwtCreateOptions, type JwtHeader, JwtVerificationError, type JwtVerifyOptions, type PrefixedID, assertCanonicalJsonText, assertJwsProfile, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, sha256, signPayloadEd25519, verifyJws, verifyJwt };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  JwsVerificationError,
3
3
  JwtVerificationError,
4
+ assertCanonicalJsonText,
4
5
  assertJwsProfile,
5
6
  base64urlDecode,
6
7
  base64urlEncode,
@@ -22,10 +23,11 @@ import {
22
23
  signPayloadEd25519,
23
24
  verifyJws,
24
25
  verifyJwt
25
- } from "../chunk-4LG2GEB2.js";
26
+ } from "../chunk-QTJZGXMH.js";
26
27
  export {
27
28
  JwsVerificationError,
28
29
  JwtVerificationError,
30
+ assertCanonicalJsonText,
29
31
  assertJwsProfile,
30
32
  base64urlDecode,
31
33
  base64urlEncode,
@@ -294,6 +294,11 @@ declare const VerifiedIdentity: z.ZodObject<{
294
294
  publicKeyMultibase: z.ZodString;
295
295
  }, z.core.$loose>>;
296
296
  }, z.core.$strict>>;
297
+ seenKeys: z.ZodOptional<z.ZodArray<z.ZodObject<{
298
+ id: z.ZodString;
299
+ type: z.ZodLiteral<"Multikey">;
300
+ publicKeyMultibase: z.ZodString;
301
+ }, z.core.$loose>>>;
297
302
  }, z.core.$strict>;
298
303
  type VerifiedIdentity = z.infer<typeof VerifiedIdentity>;
299
304
  declare const ContentOperation: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -377,7 +382,7 @@ declare const MAX_CREDIT_CLAIM_SIZE = 4096;
377
382
  * Credit claim: a claimant's signed assertion that it holds a named role on a
378
383
  * content chain. A document-plane artifact — it is NOT gossiped and relays are not
379
384
  * credit-claim aware; a claim travels inside the document bytes that embed it (see
380
- * `specs/CREDITS.md`).
385
+ * `specs/CONTENT-MODEL.md`).
381
386
  *
382
387
  * `contentId` is the binder — the STABLE 31-char content chain id, never a
383
388
  * documentCID or a chain head CID. Binding to the chain (not a document) is what
@@ -435,7 +440,8 @@ declare const MAX_SIGN_REQUEST_PAYLOAD_SIZE = 4096;
435
440
  *
436
441
  * Unknown envelope fields are preserved-and-ignored, matching every other wire
437
442
  * payload in this file. The signer-side target-payload check is deliberately
438
- * strict instead: a signer refuses fields it cannot render (see SIGNING.md).
443
+ * strict instead: a signer refuses fields it cannot render (see RELAY.md,
444
+ * Signer obligations).
439
445
  */
440
446
  declare const SignRequestPayload: z.ZodObject<{
441
447
  version: z.ZodLiteral<1>;
@@ -500,6 +506,24 @@ interface VerifiedDFOSCredential {
500
506
  /** kid from the JWS header */
501
507
  signerKeyId: string;
502
508
  }
509
+ /**
510
+ * The identity a resolver answers with, plus the one fact about that answer only
511
+ * the resolver knows: whether it is FINAL for the basis it was asked about.
512
+ *
513
+ * A resolver whose copy of the chain runs past the basis answers determinately,
514
+ * and a key missing from that state is a verdict. A resolver answering from a
515
+ * copy that ends at or before the basis does not: an operation dated at or
516
+ * before the basis can still arrive and add the key, so the miss is a dependency
517
+ * miss the caller retries. Absent is the retryable reading, which is the safe
518
+ * default: only a resolver that can say its answer is final gets a verdict.
519
+ */
520
+ type ResolvedIdentity = VerifiedIdentity & {
521
+ /**
522
+ * True when no operation dated at or before the basis can still arrive and
523
+ * change this key state.
524
+ */
525
+ basisDeterminate?: boolean;
526
+ };
503
527
  interface VerifiedDelegationChain {
504
528
  /** The leaf credential */
505
529
  credential: VerifiedDFOSCredential;
@@ -512,8 +536,8 @@ interface VerifiedDelegationChain {
512
536
  * Check whether a credential (leaf or any parent) has been revoked.
513
537
  *
514
538
  * `asOfUnix` selects WHICH question is being asked, and the two are different
515
- * decisions: **acceptance is a freshness decision; verification of committed
516
- * history is a validity decision.**
539
+ * decisions: **acceptance is a freshness decision; verification at a basis is a
540
+ * validity decision.**
517
541
  *
518
542
  * - **Omitted, or `<= 0` (timeless)** — "is this credential revoked as far as you
519
543
  * know right now?". The freshness question. Used by acceptance gates: relay
@@ -524,10 +548,10 @@ interface VerifiedDelegationChain {
524
548
  * 1970) therefore gets the stricter answer in both languages.
525
549
  * - **Positive (as-of)** — "was this credential already revoked at `asOfUnix`?".
526
550
  * The validity question. Return true only if a revocation exists AND its
527
- * signed `createdAt` is ≤ `asOfUnix`. Used when verifying operations already
528
- * committed to a chain, where `asOfUnix` is the operation's own `createdAt`.
529
- * A revocation signed AFTER an operation does not invalidate it — see
530
- * CREDENTIALS.md "Revocation Scope".
551
+ * signed `createdAt` is ≤ `asOfUnix`. Used when verifying an artifact against
552
+ * a basis, where `asOfUnix` is the basis in integer Unix seconds. A revocation
553
+ * signed AFTER the basis does not invalidate the artifact — see CREDENTIALS.md
554
+ * "Revocation against the basis".
531
555
  *
532
556
  * An implementation that ignores `asOfUnix` degrades to the timeless answer,
533
557
  * which is always the stricter (safe) direction — it can only reject history
@@ -559,13 +583,35 @@ declare const createDFOSCredential: (options: {
559
583
  /**
560
584
  * Verify a DFOS credential — signature, schema, expiry, CID integrity
561
585
  *
586
+ * Every check runs against ONE basis time (PROTOCOL, Time basis): the issuer's
587
+ * key must be effective in the identity's state as of the basis, and `exp` must
588
+ * be strictly greater than the basis in integer Unix seconds. `iat` is
589
+ * informational and gates nothing.
590
+ *
562
591
  * Does NOT verify the delegation chain. Use `verifyDelegationChain` for full
563
592
  * chain verification including attenuation enforcement.
564
593
  */
565
594
  declare const verifyDFOSCredential: (jwsToken: string, options: {
566
- resolveIdentity: (did: string) => Promise<VerifiedIdentity | undefined>;
567
- /** Current time in seconds (defaults to Date.now() / 1000) */
568
- now?: number;
595
+ /**
596
+ * Resolve a DID to its verified identity state AS OF `basis`. Called with
597
+ * the basis this verification runs at, or with none when the presentation is
598
+ * ephemeral and the answer is head state. A resolver that knows its answer
599
+ * is final for the basis says so on the result (`ResolvedIdentity`).
600
+ */
601
+ resolveIdentity: (did: string, basis?: string) => Promise<ResolvedIdentity | undefined>;
602
+ /**
603
+ * The basis time, in the `createdAt` grammar — the operation's own
604
+ * `createdAt` for a credential carried inline in a committed operation.
605
+ * Omitted for an ephemeral presentation, where the basis is now.
606
+ */
607
+ basis?: string;
608
+ /**
609
+ * The ephemeral clock in integer Unix seconds, for a caller that carries its
610
+ * own. It reaches `exp` and nothing else: key resolution on the ephemeral
611
+ * path is head state, which is what "the basis is now" means. Ignored when
612
+ * `basis` is present, and defaults to `floor(Date.now() / 1000)`.
613
+ */
614
+ nowUnix?: number;
569
615
  }) => Promise<VerifiedDFOSCredential>;
570
616
  /**
571
617
  * Verify a full delegation chain — walk `prf`, confirm monotonic attenuation,
@@ -578,23 +624,29 @@ declare const verifyDFOSCredential: (jwsToken: string, options: {
578
624
  *
579
625
  * The chain terminates when a credential has `prf: []` (root credential). The
580
626
  * root credential's `iss` must equal `rootDID`.
627
+ *
628
+ * ONE BASIS FOR THE WHOLE WALK. Every hop — each parent's signing key, each
629
+ * `exp`, each revocation — resolves against the same basis the leaf did, so a
630
+ * chain either held at that instant or it did not.
581
631
  */
582
632
  declare const verifyDelegationChain: (credential: VerifiedDFOSCredential, options: {
583
- resolveIdentity: (did: string) => Promise<VerifiedIdentity | undefined>;
633
+ /** Resolve a DID to its verified identity state as of `basis`. */
634
+ resolveIdentity: (did: string, basis?: string) => Promise<ResolvedIdentity | undefined>;
584
635
  /** The expected root authority DID (e.g., content chain creator) */
585
636
  rootDID: string;
586
- /** Current time in seconds (defaults to Date.now() / 1000) */
587
- now?: number;
588
637
  /** Check if a credential has been revoked (checked at every level of the chain) */
589
638
  isRevoked?: RevocationChecker;
590
639
  /**
591
- * As-of basis for the revocation check, unix seconds. Kept SEPARATE from
592
- * `now` (the expiry basis) on purpose: expiry and revocation are two
593
- * different decisions, and a caller evaluating expiry against a deterministic
594
- * basis does not automatically want history-relative revocation. Omitted =
595
- * timeless revocation (current knowledge). See `RevocationChecker`.
640
+ * The basis time, in the `createdAt` grammar. Omitted for an ephemeral
641
+ * presentation: `exp` runs against the wall clock and revocation is asked
642
+ * timelessly, which is the stricter direction (see `RevocationChecker`).
643
+ */
644
+ basis?: string;
645
+ /**
646
+ * The ephemeral clock in integer Unix seconds, threaded to every hop's `exp`
647
+ * and nothing else. Ignored when `basis` is present.
596
648
  */
597
- asOfUnix?: number;
649
+ nowUnix?: number;
598
650
  }) => Promise<VerifiedDelegationChain>;
599
651
  /**
600
652
  * Check if `childAtt` is a valid attenuation of `parentAtt`
@@ -639,4 +691,4 @@ declare class CredentialVerificationError extends Error {
639
691
  constructor(message: string);
640
692
  }
641
693
 
642
- export { ARTIFACT_CID_ANCHOR_RE as A, isAttenuated as B, CONTENT_ID_ANCHOR_RE as C, DFOSCredentialPayload as D, matchesResource as E, parseProtocolTimestampUnix as F, verifyDFOSCredential as G, verifyDelegationChain as H, IdentityOperation as I, MAX_ARTIFACT_PAYLOAD_SIZE as M, type RevocationChecker as R, ServiceEntry as S, type VerifiedDFOSCredential as V, ArtifactPayload as a, Attenuation as b, ContentOperation as c, CountersignPayload as d, CredentialVerificationError as e, CreditClaimPayload as f, DeclaredKeyState as g, Iso8601 as h, MAX_CREDENTIAL_SIZE as i, MAX_CREDIT_CLAIM_SIZE as j, MAX_KEY_PROOFS as k, MAX_OPERATION_SIZE as l, MAX_SERVICES_ENTRIES as m, MAX_SERVICES_PAYLOAD_SIZE as n, MAX_SIGN_REQUEST_PAYLOAD_SIZE as o, MAX_SIGN_REQUEST_SIZE as p, MultikeyPublicKey as q, RevocationPayload as r, ServicesArray as s, SignRequestPayload as t, type Signer as u, type VerifiedDelegationChain as v, VerifiedIdentity as w, VoidKeyMembership as x, createDFOSCredential as y, decodeDFOSCredentialUnsafe as z };
694
+ export { ARTIFACT_CID_ANCHOR_RE as A, decodeDFOSCredentialUnsafe as B, CONTENT_ID_ANCHOR_RE as C, DFOSCredentialPayload as D, isAttenuated as E, matchesResource as F, parseProtocolTimestampUnix as G, verifyDFOSCredential as H, IdentityOperation as I, verifyDelegationChain as J, MAX_ARTIFACT_PAYLOAD_SIZE as M, type ResolvedIdentity as R, ServiceEntry as S, type VerifiedDFOSCredential as V, ArtifactPayload as a, Attenuation as b, ContentOperation as c, CountersignPayload as d, CredentialVerificationError as e, CreditClaimPayload as f, DeclaredKeyState as g, Iso8601 as h, MAX_CREDENTIAL_SIZE as i, MAX_CREDIT_CLAIM_SIZE as j, MAX_KEY_PROOFS as k, MAX_OPERATION_SIZE as l, MAX_SERVICES_ENTRIES as m, MAX_SERVICES_PAYLOAD_SIZE as n, MAX_SIGN_REQUEST_PAYLOAD_SIZE as o, MAX_SIGN_REQUEST_SIZE as p, MultikeyPublicKey as q, type RevocationChecker as r, RevocationPayload as s, ServicesArray as t, SignRequestPayload as u, type Signer as v, type VerifiedDelegationChain as w, VerifiedIdentity as x, VoidKeyMembership as y, createDFOSCredential as z };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, assertJwsProfile, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, sha256, signPayloadEd25519, verifyJws, verifyJwt } from './crypto/index.js';
2
- export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, C as CONTENT_ID_ANCHOR_RE, c as ContentOperation, d as CountersignPayload, e as CredentialVerificationError, f as CreditClaimPayload, D as DFOSCredentialPayload, g as DeclaredKeyState, I as IdentityOperation, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, i as MAX_CREDENTIAL_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, R as RevocationChecker, r as RevocationPayload, S as ServiceEntry, s as ServicesArray, t as SignRequestPayload, u as Signer, V as VerifiedDFOSCredential, v as VerifiedDelegationChain, w as VerifiedIdentity, x as VoidKeyMembership, y as createDFOSCredential, z as decodeDFOSCredentialUnsafe, B as isAttenuated, E as matchesResource, F as parseProtocolTimestampUnix, G as verifyDFOSCredential, H as verifyDelegationChain } from './dfos-credential-BtiYPqBT.js';
3
- export { AnchorKind, CreditClaimFailureReason, CreditClaimVerifyError, CreditEntry, CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, RECOGNIZED_SERVICE_TYPES, SignRequestFailureReason, SignRequestVerifyError, VerifiedArtifact, VerifiedContentChain, VerifiedCountersignature, VerifiedCreditClaim, VerifiedCreditEntry, VerifiedRevocation, VerifiedSignRequest, anchorsByLabel, assertCanonicalSignRequestPayload, assertServicesWithinCap, buildSignRequest, classifyAnchor, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation, verifySignRequest } from './chain/index.js';
1
+ export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, assertCanonicalJsonText, assertJwsProfile, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, sha256, signPayloadEd25519, verifyJws, verifyJwt } from './crypto/index.js';
2
+ export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, b as Attenuation, C as CONTENT_ID_ANCHOR_RE, c as ContentOperation, d as CountersignPayload, e as CredentialVerificationError, f as CreditClaimPayload, D as DFOSCredentialPayload, g as DeclaredKeyState, I as IdentityOperation, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, i as MAX_CREDENTIAL_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, R as ResolvedIdentity, r as RevocationChecker, s as RevocationPayload, S as ServiceEntry, t as ServicesArray, u as SignRequestPayload, v as Signer, V as VerifiedDFOSCredential, w as VerifiedDelegationChain, x as VerifiedIdentity, y as VoidKeyMembership, z as createDFOSCredential, B as decodeDFOSCredentialUnsafe, E as isAttenuated, F as matchesResource, G as parseProtocolTimestampUnix, H as verifyDFOSCredential, J as verifyDelegationChain } from './dfos-credential-j-kHtFvG.js';
3
+ export { AnchorKind, CreditClaimFailureReason, CreditClaimVerifyError, CreditEntry, CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, RECOGNIZED_SERVICE_TYPES, SignRequestFailureReason, SignRequestVerifyError, VerifiedArtifact, VerifiedContentChain, VerifiedCountersignature, VerifiedCreditClaim, VerifiedCreditEntry, VerifiedRevocation, VerifiedSignRequest, anchorsByLabel, assertCanonicalSignRequestPayload, assertServicesWithinCap, buildSignRequest, classifyAnchor, decodeEd25519PublicMultikey, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation, verifySignRequest } from './chain/index.js';
4
4
  export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, IdentityProofPayload, MAX_BODY_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, ParsedProofPayload, ProofEnvelopeInput, ProofExtraMembers, ProofPresenterState, REQUEST_PROOF_JWS_TYP, RequestProofFailurePhase, RequestProofFailureReason, RequestProofPayload, ResolveProofPresenter, SignApiIdentityRequestInput, SignApiRequestInput, VerifiedProofEnvelope, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, canonicalExtraMembers, invalidProof, misconfiguredProof, parseDfosAuthorization, sha256BodyHash, signApiIdentityRequest, signApiRequest, unverifiableProof, verifyIdentityProofEnvelope, verifyRequestProofEnvelope } from './credentials/index.js';
5
5
  export { DEFAULT_KEY_PROOF_SKEW_SECONDS, KEY_ADD_JWS_TYP, KEY_ROLES, KeyProofFailureReason, KeyProofPayload, KeyProofVerifyError, KeyRole, MAX_KEY_PROOF_SIZE, SignKeyProofInput, VerifiedKeyProof, VerifyChainKeyProofOptions, VerifyKeyProofOptions, isCanonicalRoleSet, keyProofSigningInput, keyWordFingerprint, parseRoleSet, roleSetCovers, serializeRoleSet, signKeyProof, unsafeKeyProofSubject, verifyChainKeyProof, verifyKeyProof } from './key-proof/index.js';
6
6
  export { FoldOperation, INDEX_V1_SCHEMA, IndexDelta, IndexDocument, IndexEntry, LwwDelta, OrderKey, byteCompare, compareHeadPreference, compareLinear, foldIndexV1, foldLwwMap, linearize } from './fold/index.js';
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ import {
52
52
  verifyIdentityExtensionFromTrustedState,
53
53
  verifyRevocation,
54
54
  verifySignRequest
55
- } from "./chunk-JGFFADPR.js";
55
+ } from "./chunk-OU2DPHCN.js";
56
56
  import {
57
57
  ApiRequestVerifyError,
58
58
  Attenuation,
@@ -92,7 +92,7 @@ import {
92
92
  verifyDelegationChain,
93
93
  verifyIdentityProofEnvelope,
94
94
  verifyRequestProofEnvelope
95
- } from "./chunk-MHSBBYDO.js";
95
+ } from "./chunk-IV3TIYKP.js";
96
96
  import {
97
97
  DEFAULT_KEY_PROOF_SKEW_SECONDS,
98
98
  KEY_ADD_JWS_TYP,
@@ -109,16 +109,18 @@ import {
109
109
  unsafeKeyProofSubject,
110
110
  verifyChainKeyProof,
111
111
  verifyKeyProof
112
- } from "./chunk-JVSC67DC.js";
112
+ } from "./chunk-QRCOMLAP.js";
113
113
  import {
114
114
  ED25519_PRIV_MULTICODEC,
115
115
  ED25519_PUB_MULTICODEC,
116
+ decodeEd25519PublicMultikey,
116
117
  decodeMultikey,
117
118
  encodeEd25519Multikey
118
- } from "./chunk-IDVYITX7.js";
119
+ } from "./chunk-F7RHI2CK.js";
119
120
  import {
120
121
  JwsVerificationError,
121
122
  JwtVerificationError,
123
+ assertCanonicalJsonText,
122
124
  assertJwsProfile,
123
125
  base64urlDecode,
124
126
  base64urlEncode,
@@ -140,7 +142,7 @@ import {
140
142
  signPayloadEd25519,
141
143
  verifyJws,
142
144
  verifyJwt
143
- } from "./chunk-4LG2GEB2.js";
145
+ } from "./chunk-QTJZGXMH.js";
144
146
  import {
145
147
  INDEX_V1_SCHEMA,
146
148
  byteCompare,
@@ -205,6 +207,7 @@ export {
205
207
  anchorsByLabel,
206
208
  apiIdentitySigningInput,
207
209
  apiRequestSigningInput,
210
+ assertCanonicalJsonText,
208
211
  assertCanonicalSignRequestPayload,
209
212
  assertJwsProfile,
210
213
  assertProofVerifierConfig,
@@ -226,6 +229,7 @@ export {
226
229
  createNewEd25519Keypair,
227
230
  dagCborCanonicalEncode,
228
231
  decodeDFOSCredentialUnsafe,
232
+ decodeEd25519PublicMultikey,
229
233
  decodeJwsUnsafe,
230
234
  decodeJwtUnsafe,
231
235
  decodeMultikey,