@agentcontextdistributionprotocol/acdp 0.4.0 → 0.6.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.
- package/index.d.ts +262 -0
- package/index.js +2 -1
- 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).
|
|
@@ -193,6 +212,26 @@ export declare class AcdpDidDocument {
|
|
|
193
212
|
* is the full DID URL from the signature's `key_id`.
|
|
194
213
|
*/
|
|
195
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
|
|
196
235
|
}
|
|
197
236
|
/** RFC 8785 canonicalization utilities. All methods are static. */
|
|
198
237
|
export declare class AcdpCanonicalizer {
|
|
@@ -219,6 +258,33 @@ export declare class AcdpCanonicalizer {
|
|
|
219
258
|
*/
|
|
220
259
|
static contentHash(jsonStr: string): string
|
|
221
260
|
}
|
|
261
|
+
/** RFC-ACDP-0012 §5 Merkle helpers. All methods are static. */
|
|
262
|
+
export declare class AcdpMerkle {
|
|
263
|
+
/**
|
|
264
|
+
* The §5.1 leaf hash of a transparency-log leaf:
|
|
265
|
+
* `SHA-256(0x00 ‖ JCS(leaf))`, returned in the wire form
|
|
266
|
+
* `"sha256:<64-hex>"`.
|
|
267
|
+
*
|
|
268
|
+
* * `leafJson` — the closed leaf object (e.g. the output of
|
|
269
|
+
* `AcdpVerifier.buildLogLeaf`). Shape-validated first; a
|
|
270
|
+
* malformed leaf throws (`.code === "invalid_log_proof"`)
|
|
271
|
+
* rather than hashing bytes no conformant log ever committed.
|
|
272
|
+
*/
|
|
273
|
+
static leafHash(leafJson: string): string
|
|
274
|
+
/**
|
|
275
|
+
* The §5.1 interior-node hash `SHA-256(0x01 ‖ left ‖ right)` over
|
|
276
|
+
* the raw digests the two wire-form (`"sha256:<hex>"`) arguments
|
|
277
|
+
* encode. The 0x00/0x01 domain-separation prefixes are what stop
|
|
278
|
+
* leaf/node second-preimage forgeries — never hash without them.
|
|
279
|
+
*/
|
|
280
|
+
static nodeHash(leftHash: string, rightHash: string): string
|
|
281
|
+
/**
|
|
282
|
+
* The §5.2 RFC 6962 Merkle tree hash `MTH(D[n])` over an ordered
|
|
283
|
+
* JSON array of wire-form leaf hashes (`'["sha256:...", ...]'`).
|
|
284
|
+
* An empty array yields the empty-tree root, `SHA-256("")`.
|
|
285
|
+
*/
|
|
286
|
+
static rootHash(leafHashesJson: string): string
|
|
287
|
+
}
|
|
222
288
|
/**
|
|
223
289
|
* An ACDP producer: an Ed25519 signing key and its DID identity
|
|
224
290
|
* (`did:web`, or `did:key` via the `*DidKey` factories — ACDP 0.2).
|
|
@@ -595,4 +661,200 @@ export declare class AcdpVerifier {
|
|
|
595
661
|
* message otherwise.
|
|
596
662
|
*/
|
|
597
663
|
static verifyReceipt(receiptJson: string, registryPublicKeyB64: string, expectedCtxId: string, recomputedBodyHash: string, producerKeyFingerprint: string): boolean
|
|
664
|
+
/**
|
|
665
|
+
* Verify a lineage-head receipt offline per RFC-ACDP-0011 §7:
|
|
666
|
+
* closed parse, registry/lineage/head bindings, `as_of` clock
|
|
667
|
+
* skew, and the registry signature over the RAW wire preimage —
|
|
668
|
+
* against the registry key extracted from the caller-supplied DID
|
|
669
|
+
* document (RFC-ACDP-0010 §9 receipt-key lifecycle: retired keys
|
|
670
|
+
* verify with `historical: true`; fully removed keys fail closed).
|
|
671
|
+
*
|
|
672
|
+
* * `receiptJson` — the `lineage_head_receipt` object as received
|
|
673
|
+
* on the wire.
|
|
674
|
+
* * `expectedJson` — the consumer's own expectations:
|
|
675
|
+
* `{"authority" and/or "registry_did", "lineage_id",
|
|
676
|
+
* "head_ctx_id", "head_version", "head_status",
|
|
677
|
+
* "on_current_endpoint"?}`. `authority` is the authority the
|
|
678
|
+
* response was *actually fetched from* (compare your HTTP
|
|
679
|
+
* client's URL, not any response field); `registry_did` is
|
|
680
|
+
* `capabilities.registry_did`; either derives the other.
|
|
681
|
+
* `on_current_endpoint` defaults to `true` (`GET /current` — §7
|
|
682
|
+
* step 5 byte-match); pass `false` for a full retrieval, where
|
|
683
|
+
* the §7 step 5b stale-consistency rule applies.
|
|
684
|
+
* * `registryDidDocJson` — the registry's resolved DID document
|
|
685
|
+
* (resolution stays in JS land: `AcdpDid.webToUrl` + `fetch`).
|
|
686
|
+
* * `nowRfc3339` — the consumer clock (defaults to now).
|
|
687
|
+
* * `maxSkewSecs` — §7 step 6 allowance (default 120).
|
|
688
|
+
* * `maxAgeSecs` — §6 freshness policy (default 300).
|
|
689
|
+
*
|
|
690
|
+
* Returns a JSON verdict: `{"valid": true, "stale": bool,
|
|
691
|
+
* "age_secs": int, "historical": bool}` — staleness is policy,
|
|
692
|
+
* not verification failure — or `{"valid": false, "code":
|
|
693
|
+
* "invalid_receipt"|..., "error": ...}`. Throws only on malformed
|
|
694
|
+
* host input.
|
|
695
|
+
*/
|
|
696
|
+
static verifyLineageHeadReceipt(receiptJson: string, expectedJson: string, registryDidDocJson: string, nowRfc3339?: string | undefined | null, maxSkewSecs?: number | undefined | null, maxAgeSecs?: number | undefined | null): string
|
|
697
|
+
/**
|
|
698
|
+
* Verify a transparency-log checkpoint (signed tree head) offline
|
|
699
|
+
* per RFC-ACDP-0012 §9.3: closed parse, optional `logId` pin
|
|
700
|
+
* (§7.4 — a new `log_id` is an explicit history reset), timestamp
|
|
701
|
+
* form + clock skew, and the registry signature over the RAW wire
|
|
702
|
+
* preimage against the receipt key from the caller-supplied DID
|
|
703
|
+
* document (retired keys verify with `historical: true`).
|
|
704
|
+
*
|
|
705
|
+
* The HOST still owns the §9.3 step 3 serving-authority half:
|
|
706
|
+
* confirm the `log_id`'s registry DID matches the authority the
|
|
707
|
+
* checkpoint was actually fetched from and
|
|
708
|
+
* `capabilities.registry_did`.
|
|
709
|
+
*
|
|
710
|
+
* Returns `{"valid": true, "log_id", "tree_size", "root_hash",
|
|
711
|
+
* "age_secs", "historical"}` (retain `tree_size`/`root_hash` for
|
|
712
|
+
* future §9.2 consistency checks) or `{"valid": false, "code":
|
|
713
|
+
* "invalid_log_proof", "error": ...}`.
|
|
714
|
+
*/
|
|
715
|
+
static verifyLogCheckpoint(checkpointJson: string, registryDidDocJson: string, expectedLogId?: string | undefined | null, nowRfc3339?: string | undefined | null, maxSkewSecs?: number | undefined | null): string
|
|
716
|
+
/**
|
|
717
|
+
* Verify a transparency-log inclusion proof offline —
|
|
718
|
+
* RFC-ACDP-0012 §9.1 steps 2 and 4–6: hash the RECONSTRUCTED
|
|
719
|
+
* leaf, check the proof ↔ checkpoint bindings, fold the audit
|
|
720
|
+
* path, compare against the checkpoint root.
|
|
721
|
+
*
|
|
722
|
+
* * `inclusionJson` — the proof (`log_id`, `leaf_index`,
|
|
723
|
+
* `tree_size`, `inclusion_path`, optionally an embedded
|
|
724
|
+
* `log_checkpoint`).
|
|
725
|
+
* * `checkpointJson` — the checkpoint the proof verifies against.
|
|
726
|
+
* Inserted when the proof carries none; when the proof embeds
|
|
727
|
+
* one, the two MUST be byte-equal (a proof quietly carrying a
|
|
728
|
+
* different checkpoint is the substitution §9.1 step 3 exists
|
|
729
|
+
* to stop). Verify its signature separately with
|
|
730
|
+
* `verifyLogCheckpoint` — the verdicts are independent.
|
|
731
|
+
* * `reconstructedLeafJson` — the leaf built from *verified* body
|
|
732
|
+
* + receipt material via `buildLogLeaf` (§9.1 step 1). NEVER
|
|
733
|
+
* pass a leaf echoed by the registry — the whole point is that
|
|
734
|
+
* the verifier vouches for the leaf bytes itself.
|
|
735
|
+
*
|
|
736
|
+
* Returns `{"valid": true, "leaf_hash": "sha256:..."}` or
|
|
737
|
+
* `{"valid": false, "code": "invalid_log_proof", "error": ...}`.
|
|
738
|
+
*/
|
|
739
|
+
static verifyLogInclusion(inclusionJson: string, checkpointJson: string, reconstructedLeafJson: string): string
|
|
740
|
+
/**
|
|
741
|
+
* Verify a transparency-log consistency proof offline —
|
|
742
|
+
* RFC-ACDP-0012 §9.2, the history-rewrite detector: prove the
|
|
743
|
+
* tree the verifier RETAINED a root for (`firstRootHash`, at
|
|
744
|
+
* `first_tree_size`) is a prefix of the checkpointed later tree.
|
|
745
|
+
*
|
|
746
|
+
* * `consistencyJson` — the proof (`log_id`, `first_tree_size`,
|
|
747
|
+
* `second_tree_size`, `consistency_path`, optionally an
|
|
748
|
+
* embedded `log_checkpoint`).
|
|
749
|
+
* * `checkpointJson` — the later checkpoint (merged/byte-checked
|
|
750
|
+
* exactly as in `verifyLogInclusion`; verify its signature
|
|
751
|
+
* separately with `verifyLogCheckpoint`).
|
|
752
|
+
* * `firstRootHash` — the verifier's own retained root
|
|
753
|
+
* (`"sha256:<hex>"`) — retaining it is the whole point.
|
|
754
|
+
*
|
|
755
|
+
* Returns `{"valid": true}` or `{"valid": false, "code":
|
|
756
|
+
* "invalid_log_proof", "error": ...}`. A fold failure between two
|
|
757
|
+
* signature-valid checkpoints of one `log_id` is cryptographic
|
|
758
|
+
* evidence of a logged-history rewrite — retain both checkpoints
|
|
759
|
+
* and the failing path (§9.2, §15).
|
|
760
|
+
*/
|
|
761
|
+
static verifyLogConsistency(consistencyJson: string, checkpointJson: string, firstRootHash: string): string
|
|
762
|
+
/**
|
|
763
|
+
* Build the canonical RFC-ACDP-0012 §4 log leaf from a VERIFIED
|
|
764
|
+
* RFC-ACDP-0010 receipt (§9.1 step 1) — every leaf field other
|
|
765
|
+
* than `receipt_hash` duplicates a receipt field, and
|
|
766
|
+
* `receipt_hash` is the receipt's §5 preimage hash, computed here
|
|
767
|
+
* over the RAW wire JSON as received. Returns the leaf as a JSON
|
|
768
|
+
* string, ready for `verifyLogInclusion` / `AcdpMerkle.leafHash`.
|
|
769
|
+
*
|
|
770
|
+
* Run `verifyReceipt` on the receipt FIRST: a leaf reconstructed
|
|
771
|
+
* from an unverified receipt proves membership of a claim nobody
|
|
772
|
+
* has checked. Throws on a malformed receipt
|
|
773
|
+
* (`.code === "invalid_receipt"`).
|
|
774
|
+
*/
|
|
775
|
+
static buildLogLeaf(receiptJson: string): string
|
|
776
|
+
/**
|
|
777
|
+
* Verify one `registry_state.lifecycle_events` entry offline per
|
|
778
|
+
* RFC-ACDP-0013 §5: closed §4 parse, binding to `expectedCtxId`
|
|
779
|
+
* (a signed event cannot be replayed against another context),
|
|
780
|
+
* the §5 actor binding (`signature.key_id` DID = `actor`), and
|
|
781
|
+
* the signature over the RAW wire preimage.
|
|
782
|
+
*
|
|
783
|
+
* * `eventJson` — the event object as received.
|
|
784
|
+
* * `actorDidDocJson` — the ACTOR's resolved DID document, or
|
|
785
|
+
* `null` for a `did:key` actor (self-certifying — verified
|
|
786
|
+
* natively with no document). For `did:web` actors the key must
|
|
787
|
+
* pass the `assertionMethod` gate, like a body signature.
|
|
788
|
+
* * `expectedCtxId` — the ctx_id of the context whose registry
|
|
789
|
+
* state carries the event.
|
|
790
|
+
*
|
|
791
|
+
* The HOST still owns the §4/§12 authorization check that `actor`
|
|
792
|
+
* equals the context's `body.agent_id` (producer-initiated) or
|
|
793
|
+
* the registry's `capabilities.registry_did` (registry-initiated)
|
|
794
|
+
* — this binding sees neither document. Retraction state itself
|
|
795
|
+
* is derived from array order, last `retracted`/`republished`
|
|
796
|
+
* event wins; unknown event types are inert (§7.1, §7.3).
|
|
797
|
+
*
|
|
798
|
+
* Returns `{"valid": true, "event_id", "event_type", "actor"}` or
|
|
799
|
+
* `{"valid": false, "code": ..., "error": ...}` (an unsigned
|
|
800
|
+
* event fails — producer-initiated events MUST be signed).
|
|
801
|
+
*/
|
|
802
|
+
static verifyLifecycleEvent(eventJson: string, actorDidDocJson: string | undefined | null, expectedCtxId: string): string
|
|
803
|
+
/**
|
|
804
|
+
* Parse and shape-validate a `key-revocation` context body
|
|
805
|
+
* (RFC-ACDP-0014 §4) and derive its §5/§6 trust class. Returns
|
|
806
|
+
* the typed revocation as JSON: `{"revoked_key_fingerprint",
|
|
807
|
+
* "compromised_since", "reason"?, "revoked_key_id"?,
|
|
808
|
+
* "revoked_key_controller", "publisher", "trust_class":
|
|
809
|
+
* "producer_signed"|"registry_attested"}`. The fingerprint is
|
|
810
|
+
* authoritative; `compromised_since` is the compromise boundary
|
|
811
|
+
* T. Never collapse the two trust classes when reporting (§6).
|
|
812
|
+
*
|
|
813
|
+
* * `bodyJson` — the retrieved context `body` (the §5.7 layout
|
|
814
|
+
* including registry-assigned fields).
|
|
815
|
+
* * `signerFingerprint` — the RFC-ACDP-0010 §6 fingerprint of the
|
|
816
|
+
* RESOLVED key that signed the body, for the §5 step 2
|
|
817
|
+
* not-self-signed rule. For `did:key` signers the check runs
|
|
818
|
+
* natively from the body itself; for `did:web` signers resolve
|
|
819
|
+
* the key in JS land (`AcdpDidDocument.keyForAlgorithm` +
|
|
820
|
+
* `fingerprintEd25519B64`) and pass its fingerprint here — a
|
|
821
|
+
* revocation signed by the very key it revokes proves only
|
|
822
|
+
* possession of the attacker-held key and throws
|
|
823
|
+
* (`.code === "key_not_authorized"`).
|
|
824
|
+
*
|
|
825
|
+
* Parsing does NOT verify the body: run the ordinary hash +
|
|
826
|
+
* signature pipeline (`verifyContentHash` + `verifySignature`, or
|
|
827
|
+
* `verifyBodyOffline` for did:key) before trusting the result.
|
|
828
|
+
* Throws with `.code === "schema_violation"` on §4 shape
|
|
829
|
+
* violations.
|
|
830
|
+
*/
|
|
831
|
+
static parseKeyRevocation(bodyJson: string, signerFingerprint?: string | undefined | null): string
|
|
832
|
+
/**
|
|
833
|
+
* Apply the RFC-ACDP-0014 §7 compromise-boundary rule — the
|
|
834
|
+
* fail-closed classification the Rust client uses, over the
|
|
835
|
+
* earliest `compromised_since` among the supplied revocations
|
|
836
|
+
* naming the key (§4 monotonicity: a superseding revocation can
|
|
837
|
+
* widen, never quietly shrink, the window — feed the whole
|
|
838
|
+
* lineage through, superseded revocations included).
|
|
839
|
+
*
|
|
840
|
+
* * `revocationsJson` — JSON array of VERIFIED revocations (the
|
|
841
|
+
* shapes `parseKeyRevocation` returns). Which trust classes to
|
|
842
|
+
* act on is the caller's §6 policy.
|
|
843
|
+
* * `signerFingerprint` — fingerprint of the key that signed the
|
|
844
|
+
* context under verification.
|
|
845
|
+
* * `receiptCreatedAtRfc3339` — `created_at` from a registry
|
|
846
|
+
* receipt VERIFIED per RFC-ACDP-0010 §8, or `null` when there
|
|
847
|
+
* is no verified receipt. NEVER the bare body `created_at` — it
|
|
848
|
+
* is registry-assigned, producer-unsigned, and
|
|
849
|
+
* attacker-backdatable (§7 step 1).
|
|
850
|
+
*
|
|
851
|
+
* Returns `{"authorization": "none"}` (no revocation names the
|
|
852
|
+
* key — ordinary rules apply), `{"authorization":
|
|
853
|
+
* "historically_authorized_pre_compromise", "boundary": ...}` (§7
|
|
854
|
+
* step 2 — still verify the signature itself, under the
|
|
855
|
+
* RFC-ACDP-0010 §10 historical rule), or `{"authorization":
|
|
856
|
+
* "none", "boundary": ..., "error": ...}` — fail closed (§7
|
|
857
|
+
* steps 3–4).
|
|
858
|
+
*/
|
|
859
|
+
static classifyUnderRevocation(revocationsJson: string, signerFingerprint: string, receiptCreatedAtRfc3339?: string | undefined | null): string
|
|
598
860
|
}
|
package/index.js
CHANGED
|
@@ -310,11 +310,12 @@ if (!nativeBinding) {
|
|
|
310
310
|
throw new Error(`Failed to load native binding`)
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
const { AcdpDid, AcdpDidDocument, AcdpCanonicalizer, AcdpProducer, AcdpP256Producer, AcdpSsrfPolicy, AcdpVerifier } = nativeBinding
|
|
313
|
+
const { AcdpDid, AcdpDidDocument, AcdpCanonicalizer, AcdpMerkle, AcdpProducer, AcdpP256Producer, AcdpSsrfPolicy, AcdpVerifier } = nativeBinding
|
|
314
314
|
|
|
315
315
|
module.exports.AcdpDid = AcdpDid
|
|
316
316
|
module.exports.AcdpDidDocument = AcdpDidDocument
|
|
317
317
|
module.exports.AcdpCanonicalizer = AcdpCanonicalizer
|
|
318
|
+
module.exports.AcdpMerkle = AcdpMerkle
|
|
318
319
|
module.exports.AcdpProducer = AcdpProducer
|
|
319
320
|
module.exports.AcdpP256Producer = AcdpP256Producer
|
|
320
321
|
module.exports.AcdpSsrfPolicy = AcdpSsrfPolicy
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcontextdistributionprotocol/acdp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.
|
|
51
|
-
"@agentcontextdistributionprotocol/acdp-darwin-arm64": "0.
|
|
52
|
-
"@agentcontextdistributionprotocol/acdp-linux-x64-gnu": "0.
|
|
53
|
-
"@agentcontextdistributionprotocol/acdp-linux-arm64-gnu": "0.
|
|
50
|
+
"@agentcontextdistributionprotocol/acdp-darwin-x64": "0.6.0",
|
|
51
|
+
"@agentcontextdistributionprotocol/acdp-darwin-arm64": "0.6.0",
|
|
52
|
+
"@agentcontextdistributionprotocol/acdp-linux-x64-gnu": "0.6.0",
|
|
53
|
+
"@agentcontextdistributionprotocol/acdp-linux-arm64-gnu": "0.6.0"
|
|
54
54
|
}
|
|
55
55
|
}
|