@agentcontextdistributionprotocol/acdp 0.3.0 → 0.4.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 +204 -8
  2. package/package.json +5 -5
package/index.d.ts CHANGED
@@ -80,6 +80,25 @@ export interface PublishOpts {
80
80
  * only — rejected on first-version publishes.
81
81
  */
82
82
  expectedLineageId?: string
83
+ /**
84
+ * Explicit `acdp_version` string for the emitted request.
85
+ *
86
+ * **SDK default (since 0.2): `acdp_version` is emitted explicitly,
87
+ * set to the library's current ACDP protocol version
88
+ * (`acdp::ACDP_VERSION` — `"0.2.0"` as of this release).** Per
89
+ * RFC-ACDP-0001 §6 consumers treat an absent field as `"0.1.0"`,
90
+ * but the omitted and explicit forms are *different JCS preimages*
91
+ * and therefore hash differently — pick one form per lineage and
92
+ * never switch mid-lineage.
93
+ */
94
+ acdpVersion?: string
95
+ /**
96
+ * When `true`, omit `acdp_version` entirely (the 0.1.x SDK default
97
+ * form). Use this only to reproduce hashes signed under the
98
+ * omitted form (e.g. the sig-001 golden vector); takes precedence
99
+ * over `acdpVersion`.
100
+ */
101
+ omitAcdpVersion?: boolean
83
102
  }
84
103
  /**
85
104
  * Options for `buildSupersedeRequest`. Any field omitted is carried
@@ -103,6 +122,18 @@ export interface SupersedeOpts {
103
122
  dataPeriod?: string
104
123
  /** Self-verifying `lin:sha256:<hex>` lineage id (v2+). */
105
124
  expectedLineageId?: string
125
+ /**
126
+ * Explicit `acdp_version` string. **By default (since 0.2) the
127
+ * library's current ACDP protocol version (`acdp::ACDP_VERSION` —
128
+ * `"0.2.0"` as of this release) is emitted explicitly** — see
129
+ * `PublishOpts.acdpVersion`. Do not switch the form mid-lineage.
130
+ */
131
+ acdpVersion?: string
132
+ /**
133
+ * When `true`, omit `acdp_version` entirely (the 0.1.x form);
134
+ * takes precedence over `acdpVersion`.
135
+ */
136
+ omitAcdpVersion?: boolean
106
137
  }
107
138
  /** Stateless did:web string helpers. All methods are static. */
108
139
  export declare class AcdpDid {
@@ -189,7 +220,8 @@ export declare class AcdpCanonicalizer {
189
220
  static contentHash(jsonStr: string): string
190
221
  }
191
222
  /**
192
- * An ACDP producer: an Ed25519 signing key and its did:web identity.
223
+ * An ACDP producer: an Ed25519 signing key and its DID identity
224
+ * (`did:web`, or `did:key` via the `*DidKey` factories — ACDP 0.2).
193
225
  *
194
226
  * All methods return wire-ready JSON strings the caller sends via its
195
227
  * own HTTP client. No HTTP calls are made inside this class.
@@ -197,11 +229,38 @@ export declare class AcdpCanonicalizer {
197
229
  export declare class AcdpProducer {
198
230
  /** Generate a producer with a fresh random Ed25519 key (OsRng). */
199
231
  static generate(agentDid: string, keyId: string): AcdpProducer
232
+ /**
233
+ * Generate a producer whose identity **is** its fresh Ed25519 key
234
+ * (`did:key`, ACDP 0.2). The `agentDid` and `keyId` are derived
235
+ * from the public key — no domain, no DID-document hosting.
236
+ * Consumers verify did:key contexts offline
237
+ * (`AcdpVerifier.verifyBodyOffline`), with no dependency on the
238
+ * producer's infrastructure remaining online.
239
+ *
240
+ * Tradeoff: did:key cannot rotate — a new key is a new identity,
241
+ * and `supersedes` requires the same `agent_id`, so lineage
242
+ * continuity ends with the key. Use `did:web` for long-lived
243
+ * organizational anchors; use did:key for ephemeral or
244
+ * archival-critical producers.
245
+ */
246
+ static generateDidKey(): AcdpProducer
247
+ /**
248
+ * Construct a `did:key` producer from a 32-byte Ed25519 seed.
249
+ *
250
+ * Deterministic — the same seed always derives the same
251
+ * `agentDid` / `keyId`. The seed is the private key — protect it
252
+ * as such. See [`AcdpProducer::generate_did_key`] for the did:key
253
+ * rotation tradeoff.
254
+ */
255
+ static fromSeedDidKey(seed: Buffer): AcdpProducer
200
256
  /** Construct from a 32-byte Ed25519 seed (deterministic). */
201
257
  static fromSeed(seed: Buffer, agentDid: string, keyId: string): AcdpProducer
202
- /** The producer's DID (`did:web:…`). */
258
+ /** The producer's DID (`did:web:…` or `did:key:…`). */
203
259
  get agentDid(): string
204
- /** The producer's signing-key DID URL (`did:web:…#key-1`). */
260
+ /**
261
+ * The producer's signing-key DID URL (`did:web:…#key-1`, or the
262
+ * `did:key:z…#z…` self-fragment form).
263
+ */
205
264
  get keyId(): string
206
265
  /**
207
266
  * Raw Ed25519 public key as standard base64 (44 chars with padding).
@@ -216,12 +275,25 @@ export declare class AcdpProducer {
216
275
  /**
217
276
  * Build and sign a first-version PublishRequest. Returns the
218
277
  * wire JSON string.
278
+ *
279
+ * **By default (since 0.2) `acdp_version` is emitted explicitly,
280
+ * set to the library's current ACDP protocol version
281
+ * (`acdp::ACDP_VERSION` — `"0.2.0"` as of this release).** Pass
282
+ * `omitAcdpVersion: true` to reproduce the 0.1.x omitted form (a
283
+ * distinct JCS preimage, so a distinct `content_hash`), or
284
+ * `acdpVersion` to pin another string.
219
285
  */
220
286
  buildPublishRequest(opts: PublishOpts): string
221
287
  /**
222
288
  * Build and sign a supersession PublishRequest from a previous
223
289
  * version's `Body` JSON. Version is propagated automatically
224
290
  * (`previous.version + 1`) and `lineage_id` is carried forward.
291
+ *
292
+ * **By default (since 0.2) `acdp_version` is emitted explicitly,
293
+ * set to the library's current ACDP protocol version
294
+ * (`acdp::ACDP_VERSION` — `"0.2.0"` as of this release)** — see
295
+ * `buildPublishRequest`. Do not switch between the omitted and
296
+ * explicit forms mid-lineage.
225
297
  */
226
298
  buildSupersedeRequest(previousBodyJson: string, opts: SupersedeOpts): string
227
299
  /**
@@ -247,14 +319,30 @@ export declare class AcdpProducer {
247
319
  export declare class AcdpP256Producer {
248
320
  /** Generate a producer with a fresh random P-256 key (OsRng). */
249
321
  static generate(agentDid: string, keyId: string): AcdpP256Producer
322
+ /**
323
+ * Generate a producer whose identity **is** its fresh P-256 key
324
+ * (`did:key`, ACDP 0.2). See [`AcdpProducer::generate_did_key`]
325
+ * for the did:key rotation tradeoff.
326
+ */
327
+ static generateDidKey(): AcdpP256Producer
328
+ /**
329
+ * Construct a `did:key` producer from a 32-byte P-256 private
330
+ * scalar (big-endian). Deterministic — the same seed always
331
+ * derives the same `agentDid` / `keyId`. Throws if the bytes are
332
+ * not exactly 32 or are not a valid scalar.
333
+ */
334
+ static fromSeedDidKey(seed: Buffer): AcdpP256Producer
250
335
  /**
251
336
  * Construct from a 32-byte P-256 private scalar (deterministic).
252
337
  * Throws if the bytes are not exactly 32 or are not a valid scalar.
253
338
  */
254
339
  static fromSeed(seed: Buffer, agentDid: string, keyId: string): AcdpP256Producer
255
- /** The producer's DID (`did:web:…`). */
340
+ /** The producer's DID (`did:web:…` or `did:key:…`). */
256
341
  get agentDid(): string
257
- /** The producer's signing-key DID URL (`did:web:…#key-1`). */
342
+ /**
343
+ * The producer's signing-key DID URL (`did:web:…#key-1`, or the
344
+ * `did:key:z…#z…` self-fragment form).
345
+ */
258
346
  get keyId(): string
259
347
  /**
260
348
  * SEC1-uncompressed public key (`0x04 || x || y`, 65 bytes) as
@@ -289,14 +377,19 @@ export declare class AcdpP256Producer {
289
377
  seedBytes(): Buffer
290
378
  /**
291
379
  * 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.
380
+ * JSON string. Same surface as [`AcdpProducer::build_publish_request`]
381
+ * including the explicit `acdp_version` default (the library's
382
+ * current ACDP protocol version, `"0.2.0"` as of this release) and
383
+ * the `acdpVersion` / `omitAcdpVersion` controls; only the
384
+ * signature algorithm differs.
294
385
  */
295
386
  buildPublishRequest(opts: PublishOpts): string
296
387
  /**
297
388
  * Build and sign a supersession PublishRequest from a previous
298
389
  * version's `Body` JSON. Same semantics as
299
- * [`AcdpProducer::build_supersede_request`].
390
+ * [`AcdpProducer::build_supersede_request`], including the
391
+ * explicit `acdp_version` default (the library's current ACDP
392
+ * protocol version, `"0.2.0"` as of this release).
300
393
  */
301
394
  buildSupersedeRequest(previousBodyJson: string, opts: SupersedeOpts): string
302
395
  /**
@@ -399,4 +492,107 @@ export declare class AcdpVerifier {
399
492
  * Returns `true` on success; throws on failure.
400
493
  */
401
494
  static verifySignatureP256(pubKeySec1B64: string, sigB64: string, contentHash: string): boolean
495
+ /**
496
+ * Full offline verification of a retrieved `Body` from a `did:key`
497
+ * producer (ACDP 0.2) — structural validation, `content_hash`
498
+ * recomputation, key_id/agent_id binding, and signature check, all
499
+ * with no network.
500
+ *
501
+ * * `bodyJson` — the `body` object from a `FullContext` retrieval
502
+ * (the registry-assigned fields must be present).
503
+ *
504
+ * Throws for `did:web` bodies — those need DID-document
505
+ * resolution, which stays in JS land (resolve via `AcdpDid.webToUrl`
506
+ * + `fetch`, then use `verifySignature`).
507
+ *
508
+ * Returns `true` on success; throws on any failure.
509
+ */
510
+ static verifyBodyOffline(bodyJson: string): boolean
511
+ /**
512
+ * Offline verification of a `PublishRequest` from a `did:key`
513
+ * producer (ACDP 0.2): recomputes the `content_hash` over the
514
+ * request's producer-controlled fields, then verifies the
515
+ * signature against the key embedded in the `did:key` itself.
516
+ *
517
+ * * `requestJson` — the full PublishRequest wire JSON (e.g. the
518
+ * string `buildPublishRequest` returned).
519
+ *
520
+ * Throws for non-`did:key` requests — `did:web` verification needs
521
+ * DID resolution, which stays in JS land by design.
522
+ *
523
+ * Returns `true` on success; throws on any failure.
524
+ */
525
+ static verifyPublishRequestOffline(requestJson: string): boolean
526
+ /**
527
+ * Diagnose a `content_hash` mismatch (ACDP 0.2 divergence
528
+ * tooling). Probes the known cross-implementation divergence
529
+ * patterns — `acdp_version` omitted vs explicit, null-vs-absent
530
+ * optionals, sub-millisecond timestamps — and returns a
531
+ * human-readable report naming the matching pattern (or the
532
+ * recomputed hash and preimage when none matches).
533
+ *
534
+ * Never use this to *accept* a body; it is producer/SDK-author
535
+ * tooling for chasing "the hash that won't reproduce".
536
+ */
537
+ static explainHashMismatch(bodyJson: string, expectedHash: string): string
538
+ /**
539
+ * The exact JCS canonical preimage hashed for `content_hash`,
540
+ * returned as a UTF-8 string. When two SDKs disagree on a hash,
541
+ * diffing their canonical preimages localizes the divergence in a
542
+ * way two opaque digests never can.
543
+ */
544
+ static canonicalPreimage(bodyJson: string): string
545
+ /**
546
+ * SHA-256 fingerprint (`"sha256:<64-hex>"`) of a raw Ed25519
547
+ * public key, as carried in a registry receipt's
548
+ * `key_fingerprint` (ACDP 0.2).
549
+ *
550
+ * * `publicKeyB64` — standard base64 of the 32-byte raw key (the
551
+ * same shape as `AcdpProducer.publicKeyB64` /
552
+ * `ResolvedDidKey.publicKeyB64`).
553
+ */
554
+ static fingerprintEd25519B64(publicKeyB64: string): string
555
+ /**
556
+ * Verify a registry receipt (ACDP 0.2, RFC-ACDP-0010): the
557
+ * canonical `created_at` byte-form check, the offline cross-checks
558
+ * (`ctx_id`, recomputed body hash, producer key fingerprint,
559
+ * ms-truncated `created_at`, `registry_did`/`origin_registry`
560
+ * consistency), then the Ed25519 signature check against the
561
+ * registry's receipt key — with the signature preimage hashed over
562
+ * the **raw wire JSON** of the receipt as received (minus
563
+ * `signature`), never a re-serialization of the parsed struct.
564
+ *
565
+ * Resolving `registry_did` to that key is the caller's job — DID
566
+ * resolution stays in JS land by design (resolve the registry's
567
+ * DID document via `AcdpDid.webToUrl` + `fetch`, extract the key
568
+ * with `AcdpDidDocument.keyForAlgorithm`).
569
+ *
570
+ * **Two checks remain the HOST's obligation** — this binding makes
571
+ * no HTTP calls and never sees the accompanying body:
572
+ *
573
+ * 1. **Serving-authority binding** — `receipt.registry_did` MUST
574
+ * equal `"did:web:" + <authority>` where `<authority>` is the
575
+ * authority the response was *actually fetched from*, not
576
+ * whatever the receipt claims.
577
+ * 2. **Body bindings** — the receipt's `lineage_id`,
578
+ * `origin_registry`, and `created_at` MUST equal the
579
+ * accompanying body's fields, and the `recomputedBodyHash`
580
+ * argument MUST be independently recomputed from that body
581
+ * (run `AcdpVerifier.verifyContentHash` first) — never the
582
+ * body's echoed `content_hash` field.
583
+ *
584
+ * * `receiptJson` — the `registry_receipt` object from a
585
+ * `FullContext` retrieval.
586
+ * * `registryPublicKeyB64` — standard base64 of the registry's
587
+ * 32-byte raw Ed25519 receipt key.
588
+ * * `expectedCtxId` — the ctx_id the caller actually requested.
589
+ * * `recomputedBodyHash` — the *independently recomputed* body
590
+ * hash, never the body's echoed `content_hash` field.
591
+ * * `producerKeyFingerprint` — fingerprint of the resolved
592
+ * producer key (see `fingerprintEd25519B64`).
593
+ *
594
+ * Returns `true` on success; throws with the failing check's
595
+ * message otherwise.
596
+ */
597
+ static verifyReceipt(receiptJson: string, registryPublicKeyB64: string, expectedCtxId: string, recomputedBodyHash: string, producerKeyFingerprint: string): boolean
402
598
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcontextdistributionprotocol/acdp",
3
- "version": "0.3.0",
3
+ "version": "0.4.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.4.0",
51
+ "@agentcontextdistributionprotocol/acdp-darwin-arm64": "0.4.0",
52
+ "@agentcontextdistributionprotocol/acdp-linux-x64-gnu": "0.4.0",
53
+ "@agentcontextdistributionprotocol/acdp-linux-arm64-gnu": "0.4.0"
54
54
  }
55
55
  }