@agentcontextdistributionprotocol/acdp 0.3.0 → 0.5.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.
Files changed (2) hide show
  1. package/index.d.ts +243 -8
  2. package/package.json +5 -5
package/index.d.ts CHANGED
@@ -19,6 +19,25 @@ export interface ResolvedDidKey {
19
19
  */
20
20
  publicKeyB64: string
21
21
  }
22
+ /**
23
+ * [`ResolvedDidKey`] plus the RFC-ACDP-0010 §9 lifecycle signal for
24
+ * registry receipt keys.
25
+ */
26
+ export interface ResolvedReceiptKey {
27
+ /** Verification-method id (full DID URL with `#fragment`). */
28
+ keyId: string
29
+ /** `ed25519` or `ecdsa-p256`. */
30
+ algorithm: string
31
+ /** Standard base64 of the raw key bytes (see [`ResolvedDidKey`]). */
32
+ publicKeyB64: string
33
+ /**
34
+ * `true` when the key is retained in `verificationMethod` but no
35
+ * longer referenced by `assertionMethod` — a retired receipt key.
36
+ * Verify the receipt, but report it with the distinguishable
37
+ * *historically authorized* status (RFC-ACDP-0010 §9).
38
+ */
39
+ historical: boolean
40
+ }
22
41
  /**
23
42
  * Options for `buildPublishRequest`. Field names map directly to the
24
43
  * PublishRequest wire schema (camelCase on the JS side).
@@ -80,6 +99,25 @@ export interface PublishOpts {
80
99
  * only — rejected on first-version publishes.
81
100
  */
82
101
  expectedLineageId?: string
102
+ /**
103
+ * Explicit `acdp_version` string for the emitted request.
104
+ *
105
+ * **SDK default (since 0.2): `acdp_version` is emitted explicitly,
106
+ * set to the library's current ACDP protocol version
107
+ * (`acdp::ACDP_VERSION` — `"0.2.0"` as of this release).** Per
108
+ * RFC-ACDP-0001 §6 consumers treat an absent field as `"0.1.0"`,
109
+ * but the omitted and explicit forms are *different JCS preimages*
110
+ * and therefore hash differently — pick one form per lineage and
111
+ * never switch mid-lineage.
112
+ */
113
+ acdpVersion?: string
114
+ /**
115
+ * When `true`, omit `acdp_version` entirely (the 0.1.x SDK default
116
+ * form). Use this only to reproduce hashes signed under the
117
+ * omitted form (e.g. the sig-001 golden vector); takes precedence
118
+ * over `acdpVersion`.
119
+ */
120
+ omitAcdpVersion?: boolean
83
121
  }
84
122
  /**
85
123
  * Options for `buildSupersedeRequest`. Any field omitted is carried
@@ -103,6 +141,18 @@ export interface SupersedeOpts {
103
141
  dataPeriod?: string
104
142
  /** Self-verifying `lin:sha256:<hex>` lineage id (v2+). */
105
143
  expectedLineageId?: string
144
+ /**
145
+ * Explicit `acdp_version` string. **By default (since 0.2) the
146
+ * library's current ACDP protocol version (`acdp::ACDP_VERSION` —
147
+ * `"0.2.0"` as of this release) is emitted explicitly** — see
148
+ * `PublishOpts.acdpVersion`. Do not switch the form mid-lineage.
149
+ */
150
+ acdpVersion?: string
151
+ /**
152
+ * When `true`, omit `acdp_version` entirely (the 0.1.x form);
153
+ * takes precedence over `acdpVersion`.
154
+ */
155
+ omitAcdpVersion?: boolean
106
156
  }
107
157
  /** Stateless did:web string helpers. All methods are static. */
108
158
  export declare class AcdpDid {
@@ -162,6 +212,26 @@ export declare class AcdpDidDocument {
162
212
  * is the full DID URL from the signature's `key_id`.
163
213
  */
164
214
  keyForAlgorithm(requestedKeyId: string, requestedAlg: string): ResolvedDidKey
215
+ /**
216
+ * Resolve a **registry receipt** signing key, applying the
217
+ * RFC-ACDP-0010 §9 lifecycle instead of the `assertionMethod` gate:
218
+ * retired receipt keys MUST remain in `verificationMethod`
219
+ * indefinitely and MUST still verify historical receipts even after
220
+ * rotation removes them from `assertionMethod`. A key absent from
221
+ * `verificationMethod` entirely still fails
222
+ * (`.code === "key_not_found"`) — full removal is the registry's
223
+ * compromise-revocation signal.
224
+ *
225
+ * `historical` on the result is `true` when the key is no longer in
226
+ * `assertionMethod`: verify the receipt, but report it with the
227
+ * distinguishable *historically authorized* status. The
228
+ * algorithm-downgrade defense (RFC-ACDP-0008 §3.9) and key decoding
229
+ * are enforced identically to `keyForAlgorithm`.
230
+ *
231
+ * Use `keyForAlgorithm` for producer keys and auth challenges —
232
+ * publish-time authorization still requires `assertionMethod`.
233
+ */
234
+ receiptKeyForAlgorithm(requestedKeyId: string, requestedAlg: string): ResolvedReceiptKey
165
235
  }
166
236
  /** RFC 8785 canonicalization utilities. All methods are static. */
167
237
  export declare class AcdpCanonicalizer {
@@ -189,7 +259,8 @@ export declare class AcdpCanonicalizer {
189
259
  static contentHash(jsonStr: string): string
190
260
  }
191
261
  /**
192
- * An ACDP producer: an Ed25519 signing key and its did:web identity.
262
+ * An ACDP producer: an Ed25519 signing key and its DID identity
263
+ * (`did:web`, or `did:key` via the `*DidKey` factories — ACDP 0.2).
193
264
  *
194
265
  * All methods return wire-ready JSON strings the caller sends via its
195
266
  * own HTTP client. No HTTP calls are made inside this class.
@@ -197,11 +268,38 @@ export declare class AcdpCanonicalizer {
197
268
  export declare class AcdpProducer {
198
269
  /** Generate a producer with a fresh random Ed25519 key (OsRng). */
199
270
  static generate(agentDid: string, keyId: string): AcdpProducer
271
+ /**
272
+ * Generate a producer whose identity **is** its fresh Ed25519 key
273
+ * (`did:key`, ACDP 0.2). The `agentDid` and `keyId` are derived
274
+ * from the public key — no domain, no DID-document hosting.
275
+ * Consumers verify did:key contexts offline
276
+ * (`AcdpVerifier.verifyBodyOffline`), with no dependency on the
277
+ * producer's infrastructure remaining online.
278
+ *
279
+ * Tradeoff: did:key cannot rotate — a new key is a new identity,
280
+ * and `supersedes` requires the same `agent_id`, so lineage
281
+ * continuity ends with the key. Use `did:web` for long-lived
282
+ * organizational anchors; use did:key for ephemeral or
283
+ * archival-critical producers.
284
+ */
285
+ static generateDidKey(): AcdpProducer
286
+ /**
287
+ * Construct a `did:key` producer from a 32-byte Ed25519 seed.
288
+ *
289
+ * Deterministic — the same seed always derives the same
290
+ * `agentDid` / `keyId`. The seed is the private key — protect it
291
+ * as such. See [`AcdpProducer::generate_did_key`] for the did:key
292
+ * rotation tradeoff.
293
+ */
294
+ static fromSeedDidKey(seed: Buffer): AcdpProducer
200
295
  /** Construct from a 32-byte Ed25519 seed (deterministic). */
201
296
  static fromSeed(seed: Buffer, agentDid: string, keyId: string): AcdpProducer
202
- /** The producer's DID (`did:web:…`). */
297
+ /** The producer's DID (`did:web:…` or `did:key:…`). */
203
298
  get agentDid(): string
204
- /** The producer's signing-key DID URL (`did:web:…#key-1`). */
299
+ /**
300
+ * The producer's signing-key DID URL (`did:web:…#key-1`, or the
301
+ * `did:key:z…#z…` self-fragment form).
302
+ */
205
303
  get keyId(): string
206
304
  /**
207
305
  * Raw Ed25519 public key as standard base64 (44 chars with padding).
@@ -216,12 +314,25 @@ export declare class AcdpProducer {
216
314
  /**
217
315
  * Build and sign a first-version PublishRequest. Returns the
218
316
  * wire JSON string.
317
+ *
318
+ * **By default (since 0.2) `acdp_version` is emitted explicitly,
319
+ * set to the library's current ACDP protocol version
320
+ * (`acdp::ACDP_VERSION` — `"0.2.0"` as of this release).** Pass
321
+ * `omitAcdpVersion: true` to reproduce the 0.1.x omitted form (a
322
+ * distinct JCS preimage, so a distinct `content_hash`), or
323
+ * `acdpVersion` to pin another string.
219
324
  */
220
325
  buildPublishRequest(opts: PublishOpts): string
221
326
  /**
222
327
  * Build and sign a supersession PublishRequest from a previous
223
328
  * version's `Body` JSON. Version is propagated automatically
224
329
  * (`previous.version + 1`) and `lineage_id` is carried forward.
330
+ *
331
+ * **By default (since 0.2) `acdp_version` is emitted explicitly,
332
+ * set to the library's current ACDP protocol version
333
+ * (`acdp::ACDP_VERSION` — `"0.2.0"` as of this release)** — see
334
+ * `buildPublishRequest`. Do not switch between the omitted and
335
+ * explicit forms mid-lineage.
225
336
  */
226
337
  buildSupersedeRequest(previousBodyJson: string, opts: SupersedeOpts): string
227
338
  /**
@@ -247,14 +358,30 @@ export declare class AcdpProducer {
247
358
  export declare class AcdpP256Producer {
248
359
  /** Generate a producer with a fresh random P-256 key (OsRng). */
249
360
  static generate(agentDid: string, keyId: string): AcdpP256Producer
361
+ /**
362
+ * Generate a producer whose identity **is** its fresh P-256 key
363
+ * (`did:key`, ACDP 0.2). See [`AcdpProducer::generate_did_key`]
364
+ * for the did:key rotation tradeoff.
365
+ */
366
+ static generateDidKey(): AcdpP256Producer
367
+ /**
368
+ * Construct a `did:key` producer from a 32-byte P-256 private
369
+ * scalar (big-endian). Deterministic — the same seed always
370
+ * derives the same `agentDid` / `keyId`. Throws if the bytes are
371
+ * not exactly 32 or are not a valid scalar.
372
+ */
373
+ static fromSeedDidKey(seed: Buffer): AcdpP256Producer
250
374
  /**
251
375
  * Construct from a 32-byte P-256 private scalar (deterministic).
252
376
  * Throws if the bytes are not exactly 32 or are not a valid scalar.
253
377
  */
254
378
  static fromSeed(seed: Buffer, agentDid: string, keyId: string): AcdpP256Producer
255
- /** The producer's DID (`did:web:…`). */
379
+ /** The producer's DID (`did:web:…` or `did:key:…`). */
256
380
  get agentDid(): string
257
- /** The producer's signing-key DID URL (`did:web:…#key-1`). */
381
+ /**
382
+ * The producer's signing-key DID URL (`did:web:…#key-1`, or the
383
+ * `did:key:z…#z…` self-fragment form).
384
+ */
258
385
  get keyId(): string
259
386
  /**
260
387
  * SEC1-uncompressed public key (`0x04 || x || y`, 65 bytes) as
@@ -289,14 +416,19 @@ export declare class AcdpP256Producer {
289
416
  seedBytes(): Buffer
290
417
  /**
291
418
  * Build and sign a first-version PublishRequest. Returns the wire
292
- * JSON string. Same surface as [`AcdpProducer::build_publish_request`];
293
- * only the signature algorithm differs.
419
+ * JSON string. Same surface as [`AcdpProducer::build_publish_request`]
420
+ * including the explicit `acdp_version` default (the library's
421
+ * current ACDP protocol version, `"0.2.0"` as of this release) and
422
+ * the `acdpVersion` / `omitAcdpVersion` controls; only the
423
+ * signature algorithm differs.
294
424
  */
295
425
  buildPublishRequest(opts: PublishOpts): string
296
426
  /**
297
427
  * Build and sign a supersession PublishRequest from a previous
298
428
  * version's `Body` JSON. Same semantics as
299
- * [`AcdpProducer::build_supersede_request`].
429
+ * [`AcdpProducer::build_supersede_request`], including the
430
+ * explicit `acdp_version` default (the library's current ACDP
431
+ * protocol version, `"0.2.0"` as of this release).
300
432
  */
301
433
  buildSupersedeRequest(previousBodyJson: string, opts: SupersedeOpts): string
302
434
  /**
@@ -399,4 +531,107 @@ export declare class AcdpVerifier {
399
531
  * Returns `true` on success; throws on failure.
400
532
  */
401
533
  static verifySignatureP256(pubKeySec1B64: string, sigB64: string, contentHash: string): boolean
534
+ /**
535
+ * Full offline verification of a retrieved `Body` from a `did:key`
536
+ * producer (ACDP 0.2) — structural validation, `content_hash`
537
+ * recomputation, key_id/agent_id binding, and signature check, all
538
+ * with no network.
539
+ *
540
+ * * `bodyJson` — the `body` object from a `FullContext` retrieval
541
+ * (the registry-assigned fields must be present).
542
+ *
543
+ * Throws for `did:web` bodies — those need DID-document
544
+ * resolution, which stays in JS land (resolve via `AcdpDid.webToUrl`
545
+ * + `fetch`, then use `verifySignature`).
546
+ *
547
+ * Returns `true` on success; throws on any failure.
548
+ */
549
+ static verifyBodyOffline(bodyJson: string): boolean
550
+ /**
551
+ * Offline verification of a `PublishRequest` from a `did:key`
552
+ * producer (ACDP 0.2): recomputes the `content_hash` over the
553
+ * request's producer-controlled fields, then verifies the
554
+ * signature against the key embedded in the `did:key` itself.
555
+ *
556
+ * * `requestJson` — the full PublishRequest wire JSON (e.g. the
557
+ * string `buildPublishRequest` returned).
558
+ *
559
+ * Throws for non-`did:key` requests — `did:web` verification needs
560
+ * DID resolution, which stays in JS land by design.
561
+ *
562
+ * Returns `true` on success; throws on any failure.
563
+ */
564
+ static verifyPublishRequestOffline(requestJson: string): boolean
565
+ /**
566
+ * Diagnose a `content_hash` mismatch (ACDP 0.2 divergence
567
+ * tooling). Probes the known cross-implementation divergence
568
+ * patterns — `acdp_version` omitted vs explicit, null-vs-absent
569
+ * optionals, sub-millisecond timestamps — and returns a
570
+ * human-readable report naming the matching pattern (or the
571
+ * recomputed hash and preimage when none matches).
572
+ *
573
+ * Never use this to *accept* a body; it is producer/SDK-author
574
+ * tooling for chasing "the hash that won't reproduce".
575
+ */
576
+ static explainHashMismatch(bodyJson: string, expectedHash: string): string
577
+ /**
578
+ * The exact JCS canonical preimage hashed for `content_hash`,
579
+ * returned as a UTF-8 string. When two SDKs disagree on a hash,
580
+ * diffing their canonical preimages localizes the divergence in a
581
+ * way two opaque digests never can.
582
+ */
583
+ static canonicalPreimage(bodyJson: string): string
584
+ /**
585
+ * SHA-256 fingerprint (`"sha256:<64-hex>"`) of a raw Ed25519
586
+ * public key, as carried in a registry receipt's
587
+ * `key_fingerprint` (ACDP 0.2).
588
+ *
589
+ * * `publicKeyB64` — standard base64 of the 32-byte raw key (the
590
+ * same shape as `AcdpProducer.publicKeyB64` /
591
+ * `ResolvedDidKey.publicKeyB64`).
592
+ */
593
+ static fingerprintEd25519B64(publicKeyB64: string): string
594
+ /**
595
+ * Verify a registry receipt (ACDP 0.2, RFC-ACDP-0010): the
596
+ * canonical `created_at` byte-form check, the offline cross-checks
597
+ * (`ctx_id`, recomputed body hash, producer key fingerprint,
598
+ * ms-truncated `created_at`, `registry_did`/`origin_registry`
599
+ * consistency), then the Ed25519 signature check against the
600
+ * registry's receipt key — with the signature preimage hashed over
601
+ * the **raw wire JSON** of the receipt as received (minus
602
+ * `signature`), never a re-serialization of the parsed struct.
603
+ *
604
+ * Resolving `registry_did` to that key is the caller's job — DID
605
+ * resolution stays in JS land by design (resolve the registry's
606
+ * DID document via `AcdpDid.webToUrl` + `fetch`, extract the key
607
+ * with `AcdpDidDocument.keyForAlgorithm`).
608
+ *
609
+ * **Two checks remain the HOST's obligation** — this binding makes
610
+ * no HTTP calls and never sees the accompanying body:
611
+ *
612
+ * 1. **Serving-authority binding** — `receipt.registry_did` MUST
613
+ * equal `"did:web:" + <authority>` where `<authority>` is the
614
+ * authority the response was *actually fetched from*, not
615
+ * whatever the receipt claims.
616
+ * 2. **Body bindings** — the receipt's `lineage_id`,
617
+ * `origin_registry`, and `created_at` MUST equal the
618
+ * accompanying body's fields, and the `recomputedBodyHash`
619
+ * argument MUST be independently recomputed from that body
620
+ * (run `AcdpVerifier.verifyContentHash` first) — never the
621
+ * body's echoed `content_hash` field.
622
+ *
623
+ * * `receiptJson` — the `registry_receipt` object from a
624
+ * `FullContext` retrieval.
625
+ * * `registryPublicKeyB64` — standard base64 of the registry's
626
+ * 32-byte raw Ed25519 receipt key.
627
+ * * `expectedCtxId` — the ctx_id the caller actually requested.
628
+ * * `recomputedBodyHash` — the *independently recomputed* body
629
+ * hash, never the body's echoed `content_hash` field.
630
+ * * `producerKeyFingerprint` — fingerprint of the resolved
631
+ * producer key (see `fingerprintEd25519B64`).
632
+ *
633
+ * Returns `true` on success; throws with the failing check's
634
+ * message otherwise.
635
+ */
636
+ static verifyReceipt(receiptJson: string, registryPublicKeyB64: string, expectedCtxId: string, recomputedBodyHash: string, producerKeyFingerprint: string): boolean
402
637
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcontextdistributionprotocol/acdp",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Agent Context Distribution Protocol — Node.js SDK",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
@@ -47,9 +47,9 @@
47
47
  "registry": "https://registry.npmjs.org/"
48
48
  },
49
49
  "optionalDependencies": {
50
- "@agentcontextdistributionprotocol/acdp-darwin-x64": "0.3.0",
51
- "@agentcontextdistributionprotocol/acdp-darwin-arm64": "0.3.0",
52
- "@agentcontextdistributionprotocol/acdp-linux-x64-gnu": "0.3.0",
53
- "@agentcontextdistributionprotocol/acdp-linux-arm64-gnu": "0.3.0"
50
+ "@agentcontextdistributionprotocol/acdp-darwin-x64": "0.5.0",
51
+ "@agentcontextdistributionprotocol/acdp-darwin-arm64": "0.5.0",
52
+ "@agentcontextdistributionprotocol/acdp-linux-x64-gnu": "0.5.0",
53
+ "@agentcontextdistributionprotocol/acdp-linux-arm64-gnu": "0.5.0"
54
54
  }
55
55
  }