@ouro.bot/friends 0.1.0-alpha.4 → 0.1.0-alpha.5

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 (36) hide show
  1. package/README.md +66 -6
  2. package/changelog.json +6 -0
  3. package/dist/a2a-client/a2a-message.d.ts +39 -0
  4. package/dist/a2a-client/a2a-message.js +54 -0
  5. package/dist/a2a-client/adapter.d.ts +97 -0
  6. package/dist/a2a-client/adapter.js +114 -0
  7. package/dist/a2a-client/agent-card.d.ts +50 -0
  8. package/dist/a2a-client/agent-card.js +32 -0
  9. package/dist/a2a-client/did-key.d.ts +38 -0
  10. package/dist/a2a-client/did-key.js +120 -0
  11. package/dist/a2a-client/did-verifier.d.ts +109 -0
  12. package/dist/a2a-client/did-verifier.js +163 -0
  13. package/dist/a2a-client/did-web.d.ts +26 -0
  14. package/dist/a2a-client/did-web.js +140 -0
  15. package/dist/a2a-client/index.d.ts +23 -0
  16. package/dist/a2a-client/index.js +72 -0
  17. package/dist/a2a-client/jcs.d.ts +5 -0
  18. package/dist/a2a-client/jcs.js +84 -0
  19. package/dist/a2a-client/reachability.d.ts +22 -0
  20. package/dist/a2a-client/reachability.js +17 -0
  21. package/dist/a2a-client/seal.d.ts +47 -0
  22. package/dist/a2a-client/seal.js +95 -0
  23. package/dist/a2a-client/sealed-envelope.d.ts +55 -0
  24. package/dist/a2a-client/sealed-envelope.js +94 -0
  25. package/dist/a2a-client/sign.d.ts +42 -0
  26. package/dist/a2a-client/sign.js +87 -0
  27. package/dist/a2a-client/sodium.d.ts +5 -0
  28. package/dist/a2a-client/sodium.js +19 -0
  29. package/dist/agent-peer.js +5 -1
  30. package/dist/{a2a → mailbox}/index.js +10 -3
  31. package/dist/mcp/bin.js +0 -0
  32. package/dist/store-file.d.ts +6 -2
  33. package/dist/store-file.js +28 -5
  34. package/dist/types.d.ts +22 -7
  35. package/package.json +15 -6
  36. /package/dist/{a2a → mailbox}/index.d.ts +0 -0
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.base58btcEncode = base58btcEncode;
4
+ exports.base58btcDecode = base58btcDecode;
5
+ exports.parseDidKey = parseDidKey;
6
+ exports.ed25519PubToDidKey = ed25519PubToDidKey;
7
+ exports.keyAgreementFromDidKey = keyAgreementFromDidKey;
8
+ exports.didKeyIdentityFromEd25519 = didKeyIdentityFromEd25519;
9
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
10
+ // Reverse lookup: char → value, or `undefined` for a char not in the alphabet
11
+ // (base58btcDecode checks `=== undefined`).
12
+ const BASE58_MAP = (() => {
13
+ const m = {};
14
+ for (let i = 0; i < BASE58_ALPHABET.length; i++)
15
+ m[BASE58_ALPHABET[i]] = i;
16
+ return m;
17
+ })();
18
+ // The ed25519-pub multicodec, varint-encoded: 0xed → [0xed, 0x01].
19
+ const ED25519_MULTICODEC = Uint8Array.from([0xed, 0x01]);
20
+ const ED25519_PUB_LEN = 32;
21
+ /** Encode bytes as base58btc (Bitcoin alphabet). */
22
+ function base58btcEncode(bytes) {
23
+ if (bytes.length === 0)
24
+ return "";
25
+ // Count leading zero bytes → leading '1's.
26
+ let zeros = 0;
27
+ while (zeros < bytes.length && bytes[zeros] === 0)
28
+ zeros++;
29
+ // Big-endian base-256 → base-58 via repeated division.
30
+ const digits = [0];
31
+ for (let i = zeros; i < bytes.length; i++) {
32
+ let carry = bytes[i];
33
+ for (let j = 0; j < digits.length; j++) {
34
+ carry += digits[j] << 8;
35
+ digits[j] = carry % 58;
36
+ carry = (carry / 58) | 0;
37
+ }
38
+ while (carry > 0) {
39
+ digits.push(carry % 58);
40
+ carry = (carry / 58) | 0;
41
+ }
42
+ }
43
+ let out = "1".repeat(zeros);
44
+ for (let k = digits.length - 1; k >= 0; k--)
45
+ out += BASE58_ALPHABET[digits[k]];
46
+ return out;
47
+ }
48
+ /** Decode a base58btc string. Returns null on an invalid character. */
49
+ function base58btcDecode(str) {
50
+ if (str.length === 0)
51
+ return new Uint8Array(0);
52
+ let zeros = 0;
53
+ while (zeros < str.length && str[zeros] === "1")
54
+ zeros++;
55
+ const bytes = [0];
56
+ for (let i = zeros; i < str.length; i++) {
57
+ const val = BASE58_MAP[str[i]];
58
+ if (val === undefined)
59
+ return null;
60
+ let carry = val;
61
+ for (let j = 0; j < bytes.length; j++) {
62
+ carry += bytes[j] * 58;
63
+ bytes[j] = carry & 0xff;
64
+ carry >>= 8;
65
+ }
66
+ while (carry > 0) {
67
+ bytes.push(carry & 0xff);
68
+ carry >>= 8;
69
+ }
70
+ }
71
+ const out = new Uint8Array(zeros + bytes.length);
72
+ // leading zeros already 0; fill the rest big-endian.
73
+ for (let k = 0; k < bytes.length; k++)
74
+ out[zeros + k] = bytes[bytes.length - 1 - k];
75
+ return out;
76
+ }
77
+ /** Parse a `did:key:z…` (Ed25519) into its 32-byte public key. Returns null on:
78
+ * wrong scheme, missing `z` multibase prefix, bad base58, wrong multicodec, or
79
+ * wrong key length. */
80
+ function parseDidKey(did) {
81
+ if (typeof did !== "string" || !did.startsWith("did:key:"))
82
+ return null;
83
+ const mb = did.slice("did:key:".length);
84
+ if (!mb.startsWith("z"))
85
+ return null; // only base58btc multibase supported
86
+ const decoded = base58btcDecode(mb.slice(1));
87
+ if (!decoded)
88
+ return null;
89
+ if (decoded.length !== ED25519_MULTICODEC.length + ED25519_PUB_LEN)
90
+ return null;
91
+ if (decoded[0] !== ED25519_MULTICODEC[0] || decoded[1] !== ED25519_MULTICODEC[1])
92
+ return null;
93
+ return { ed25519Pub: decoded.slice(ED25519_MULTICODEC.length) };
94
+ }
95
+ /** Encode an Ed25519 public key as a `did:key:z…`. Throws on a wrong-length key
96
+ * (a guard — callers pass real 32-byte keys). */
97
+ function ed25519PubToDidKey(pub) {
98
+ if (pub.length !== ED25519_PUB_LEN) {
99
+ throw new Error(`did:key: expected a ${ED25519_PUB_LEN}-byte Ed25519 public key, got ${pub.length}`);
100
+ }
101
+ const prefixed = new Uint8Array(ED25519_MULTICODEC.length + pub.length);
102
+ prefixed.set(ED25519_MULTICODEC, 0);
103
+ prefixed.set(pub, ED25519_MULTICODEC.length);
104
+ return `did:key:z${base58btcEncode(prefixed)}`;
105
+ }
106
+ /** Derive the X25519 keyAgreement PUBLIC key from an Ed25519 public key. */
107
+ function keyAgreementFromDidKey(input) {
108
+ return input.sodium.crypto_sign_ed25519_pk_to_curve25519(input.ed25519Pub);
109
+ }
110
+ /** Build a did:key identity from an Ed25519 keypair (the signing + the derived
111
+ * X25519 keyAgreement keys). */
112
+ function didKeyIdentityFromEd25519(input) {
113
+ const { sodium, ed25519Pub, ed25519Priv } = input;
114
+ const did = ed25519PubToDidKey(ed25519Pub);
115
+ const x25519Pub = sodium.crypto_sign_ed25519_pk_to_curve25519(ed25519Pub);
116
+ const x25519Priv = sodium.crypto_sign_ed25519_sk_to_curve25519(ed25519Priv);
117
+ // The did:key fragment repeats the multibase body (did:key is self-describing).
118
+ const zBase = did.slice("did:key:".length);
119
+ return { did, ed25519Pub, ed25519Priv, x25519Pub, x25519Priv, keyId: `${did}#${zBase}` };
120
+ }
@@ -0,0 +1,109 @@
1
+ import type { AgentVerifier } from "../verifier";
2
+ import type { TrustLevel } from "../types";
3
+ import type { Sodium } from "./sodium";
4
+ /** The pinned identity record for a peer (persisted by the HOST onto
5
+ * `AgentMeta.a2a.did` + a pinned-key field; injectable so tests use a map). */
6
+ export interface PinnedDid {
7
+ did: string;
8
+ ed25519Pub: Uint8Array;
9
+ }
10
+ /** A pin store the host implements (in-memory map in tests; persisted on the
11
+ * agent record in production — a2a-client never touches fs itself). */
12
+ export interface PinStore {
13
+ get(fromAgentId: string): PinnedDid | undefined;
14
+ set(fromAgentId: string, pinned: PinnedDid): void;
15
+ }
16
+ /** A simple in-memory PinStore (used by tests + as a host convenience). */
17
+ export declare class MemoryPinStore implements PinStore {
18
+ private readonly map;
19
+ get(fromAgentId: string): PinnedDid | undefined;
20
+ set(fromAgentId: string, pinned: PinnedDid): void;
21
+ }
22
+ export interface DidVerifierInput {
23
+ sodium: Sodium;
24
+ /** The already-resolved+pinned Ed25519 public key for the inbound message's
25
+ * sender (the key the signature is verified against). */
26
+ pinnedEd25519Pub: Uint8Array;
27
+ /** The DID the pinned key belongs to (== the expected `fromAgentId`). */
28
+ pinnedDid: string;
29
+ /** The concrete inbound plaintext envelope this verifier is bound to. The
30
+ * signature is over `jcsBytes(envelope without proof)`, so the verifier must
31
+ * hold the envelope to perform a REAL crypto check inside the sync `verify`.
32
+ * The adapter (U8) constructs a fresh DidVerifier per inbound message. */
33
+ envelope: unknown;
34
+ }
35
+ /** The sync verifier handed to a core importer. Bound to one inbound envelope;
36
+ * `verify` confirms the agentId===did binding AND the Ed25519 signature over that
37
+ * envelope against the PINNED key. Pure sync, no I/O — the async DID resolve + pin
38
+ * happened in the adapter BEFORE construction. */
39
+ export declare class DidVerifier implements AgentVerifier {
40
+ private readonly sodium;
41
+ private readonly pinnedEd25519Pub;
42
+ private readonly pinnedDid;
43
+ private readonly envelope;
44
+ constructor(input: DidVerifierInput);
45
+ /** Sync, no I/O. False on any binding or signature failure. */
46
+ verify(fromAgentId: string, proof?: string): boolean;
47
+ }
48
+ export interface CardDidBindingInput {
49
+ /** The agent card (carries a `did`; for did:web also a back-reference). */
50
+ card: {
51
+ did?: unknown;
52
+ url?: unknown;
53
+ };
54
+ /** The resolved DID document (did:web). For did:key pass `null` — the binding is
55
+ * "card.did === the did:key string" only (did:key is self-contained). */
56
+ didDoc: {
57
+ id: string;
58
+ cardServiceUrl?: string;
59
+ } | null;
60
+ /** The DID the card claims (the agent's identity). */
61
+ did: string;
62
+ }
63
+ /** Verify the card and DID agree BOTH directions. For did:web: card.did === did
64
+ * === didDoc.id AND the doc's `service` endpoint === the card URL. For did:key
65
+ * (didDoc null): card.did === did only. */
66
+ export declare function verifyCardDidBinding(input: CardDidBindingInput): boolean;
67
+ /** First contact: accept + pin the (did, key). Idempotent re-pin to the same key
68
+ * is fine; a DIFFERENT key for an existing pin must go through `evaluateRotation`,
69
+ * not this. Returns the pinned record. */
70
+ export declare function pinOnFirstContact(input: {
71
+ pinStore: PinStore;
72
+ fromAgentId: string;
73
+ did: string;
74
+ ed25519Pub: Uint8Array;
75
+ }): PinnedDid;
76
+ /** Whether a peer is already pinned. */
77
+ export declare function isPinned(pinStore: PinStore, fromAgentId: string): boolean;
78
+ /** The pinned record for a peer, or undefined. */
79
+ export declare function getPinned(pinStore: PinStore, fromAgentId: string): PinnedDid | undefined;
80
+ export type RotationDecision = {
81
+ decision: "unchanged";
82
+ } | {
83
+ decision: "accepted";
84
+ } | {
85
+ decision: "rejected";
86
+ reason: "bad_rotation_proof" | "rotation_requires_reconfirm" | "not_pinned";
87
+ };
88
+ /** Mint a rotation proof: the OLD private key signs `{successor:newDid, newKey}`.
89
+ * Returns the base64 detached signature. (Test/host helper.) */
90
+ export declare function signSuccessor(input: {
91
+ sodium: Sodium;
92
+ oldEd25519Priv: Uint8Array;
93
+ newDid: string;
94
+ newEd25519Pub: Uint8Array;
95
+ }): string;
96
+ export interface EvaluateRotationInput {
97
+ sodium: Sodium;
98
+ pinStore: PinStore;
99
+ fromAgentId: string;
100
+ trustOfSource: TrustLevel;
101
+ newDid: string;
102
+ newEd25519Pub: Uint8Array;
103
+ /** The base64 signature from `signSuccessor`, if presented. */
104
+ rotationProof?: string;
105
+ }
106
+ /** Evaluate a presented key against the pin (Fork 11). family/friend auto-accept a
107
+ * VALID signed successor proof (re-pin); acquaintance/stranger reject regardless;
108
+ * an unchanged key is `unchanged`; an unpinned peer is `not_pinned` (use TOFU). */
109
+ export declare function evaluateRotation(input: EvaluateRotationInput): RotationDecision;
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DidVerifier = exports.MemoryPinStore = void 0;
4
+ exports.verifyCardDidBinding = verifyCardDidBinding;
5
+ exports.pinOnFirstContact = pinOnFirstContact;
6
+ exports.isPinned = isPinned;
7
+ exports.getPinned = getPinned;
8
+ exports.signSuccessor = signSuccessor;
9
+ exports.evaluateRotation = evaluateRotation;
10
+ const jcs_1 = require("./jcs");
11
+ const sign_1 = require("./sign");
12
+ /** A simple in-memory PinStore (used by tests + as a host convenience). */
13
+ class MemoryPinStore {
14
+ map = new Map();
15
+ get(fromAgentId) {
16
+ return this.map.get(fromAgentId);
17
+ }
18
+ set(fromAgentId, pinned) {
19
+ this.map.set(fromAgentId, pinned);
20
+ }
21
+ }
22
+ exports.MemoryPinStore = MemoryPinStore;
23
+ /** Constant-time-ish byte equality (length-checked). The keys are public, so this
24
+ * is correctness, not timing-secrecy — but keep it total. */
25
+ function bytesEqual(a, b) {
26
+ if (a.length !== b.length)
27
+ return false;
28
+ let diff = 0;
29
+ for (let i = 0; i < a.length; i++)
30
+ diff |= a[i] ^ b[i];
31
+ return diff === 0;
32
+ }
33
+ /** The sync verifier handed to a core importer. Bound to one inbound envelope;
34
+ * `verify` confirms the agentId===did binding AND the Ed25519 signature over that
35
+ * envelope against the PINNED key. Pure sync, no I/O — the async DID resolve + pin
36
+ * happened in the adapter BEFORE construction. */
37
+ class DidVerifier {
38
+ sodium;
39
+ pinnedEd25519Pub;
40
+ pinnedDid;
41
+ envelope;
42
+ constructor(input) {
43
+ this.sodium = input.sodium;
44
+ this.pinnedEd25519Pub = input.pinnedEd25519Pub;
45
+ this.pinnedDid = input.pinnedDid;
46
+ this.envelope = input.envelope;
47
+ }
48
+ /** Sync, no I/O. False on any binding or signature failure. */
49
+ verify(fromAgentId, proof) {
50
+ if (proof === undefined)
51
+ return false;
52
+ const parsed = (0, sign_1.parseProof)(proof);
53
+ if (!parsed)
54
+ return false;
55
+ // agentId === did binding (Fork 10): the proof's signer DID must equal the
56
+ // arriving agentId AND the pinned DID. A spoof where agentId ≠ signerDid, or a
57
+ // proof claiming a different DID than the pinned one, is rejected here before
58
+ // any crypto.
59
+ if (parsed.signerDid !== fromAgentId)
60
+ return false;
61
+ if (parsed.signerDid !== this.pinnedDid)
62
+ return false;
63
+ // Real cryptographic gate: the pinned Ed25519 key must have signed this exact
64
+ // (proof-stripped) envelope.
65
+ return (0, sign_1.verifyEnvelopeSignature)({
66
+ sodium: this.sodium,
67
+ envelope: this.envelope,
68
+ proof: parsed,
69
+ signerEd25519Pub: this.pinnedEd25519Pub,
70
+ });
71
+ }
72
+ }
73
+ exports.DidVerifier = DidVerifier;
74
+ /** Verify the card and DID agree BOTH directions. For did:web: card.did === did
75
+ * === didDoc.id AND the doc's `service` endpoint === the card URL. For did:key
76
+ * (didDoc null): card.did === did only. */
77
+ function verifyCardDidBinding(input) {
78
+ const cardDid = typeof input.card.did === "string" ? input.card.did : undefined;
79
+ if (cardDid !== input.did)
80
+ return false; // card → DID
81
+ if (input.didDoc === null) {
82
+ // did:key: self-contained; the card.did === did check above is sufficient.
83
+ return true;
84
+ }
85
+ // did:web: DID → card. The doc must reference the card URL via a service entry.
86
+ if (input.didDoc.id !== input.did)
87
+ return false;
88
+ const cardUrl = typeof input.card.url === "string" ? input.card.url : undefined;
89
+ if (!cardUrl)
90
+ return false;
91
+ return input.didDoc.cardServiceUrl === cardUrl;
92
+ }
93
+ // ── TOFU pin ──────────────────────────────────────────────────────────────────
94
+ /** First contact: accept + pin the (did, key). Idempotent re-pin to the same key
95
+ * is fine; a DIFFERENT key for an existing pin must go through `evaluateRotation`,
96
+ * not this. Returns the pinned record. */
97
+ function pinOnFirstContact(input) {
98
+ const pinned = { did: input.did, ed25519Pub: input.ed25519Pub };
99
+ input.pinStore.set(input.fromAgentId, pinned);
100
+ return pinned;
101
+ }
102
+ /** Whether a peer is already pinned. */
103
+ function isPinned(pinStore, fromAgentId) {
104
+ return pinStore.get(fromAgentId) !== undefined;
105
+ }
106
+ /** The pinned record for a peer, or undefined. */
107
+ function getPinned(pinStore, fromAgentId) {
108
+ return pinStore.get(fromAgentId);
109
+ }
110
+ /** The canonical successor statement the OLD key signs to authorize a rotation. */
111
+ function successorMessage(newDid, newEd25519Pub, b64) {
112
+ return (0, jcs_1.jcsBytes)({ statement: "key-successor", successor: newDid, newKey: b64(newEd25519Pub) });
113
+ }
114
+ /** Mint a rotation proof: the OLD private key signs `{successor:newDid, newKey}`.
115
+ * Returns the base64 detached signature. (Test/host helper.) */
116
+ function signSuccessor(input) {
117
+ const { sodium } = input;
118
+ const b64 = (b) => sodium.to_base64(b, sodium.base64_variants.ORIGINAL);
119
+ const msg = successorMessage(input.newDid, input.newEd25519Pub, b64);
120
+ return b64(sodium.crypto_sign_detached(msg, input.oldEd25519Priv));
121
+ }
122
+ /** Evaluate a presented key against the pin (Fork 11). family/friend auto-accept a
123
+ * VALID signed successor proof (re-pin); acquaintance/stranger reject regardless;
124
+ * an unchanged key is `unchanged`; an unpinned peer is `not_pinned` (use TOFU). */
125
+ function evaluateRotation(input) {
126
+ const { sodium, pinStore, fromAgentId, trustOfSource, newDid, newEd25519Pub } = input;
127
+ const current = pinStore.get(fromAgentId);
128
+ if (!current)
129
+ return { decision: "rejected", reason: "not_pinned" };
130
+ // Unchanged key (same bytes) → nothing to rotate.
131
+ if (current.did === newDid && bytesEqual(current.ed25519Pub, newEd25519Pub)) {
132
+ return { decision: "unchanged" };
133
+ }
134
+ // acquaintance / stranger: never auto-accept a rotation, even with a valid proof.
135
+ if (trustOfSource === "acquaintance" || trustOfSource === "stranger") {
136
+ return { decision: "rejected", reason: "rotation_requires_reconfirm" };
137
+ }
138
+ // family / friend: require a VALID signed successor proof from the OLD pinned key.
139
+ if (input.rotationProof === undefined) {
140
+ return { decision: "rejected", reason: "bad_rotation_proof" };
141
+ }
142
+ const b64 = (b) => sodium.to_base64(b, sodium.base64_variants.ORIGINAL);
143
+ const msg = successorMessage(newDid, newEd25519Pub, b64);
144
+ let sig;
145
+ try {
146
+ sig = sodium.from_base64(input.rotationProof, sodium.base64_variants.ORIGINAL);
147
+ }
148
+ catch {
149
+ return { decision: "rejected", reason: "bad_rotation_proof" };
150
+ }
151
+ let ok = false;
152
+ try {
153
+ ok = sodium.crypto_sign_verify_detached(sig, msg, current.ed25519Pub);
154
+ }
155
+ catch {
156
+ ok = false;
157
+ }
158
+ if (!ok)
159
+ return { decision: "rejected", reason: "bad_rotation_proof" };
160
+ // Valid: re-pin to the new key.
161
+ pinStore.set(fromAgentId, { did: newDid, ed25519Pub: newEd25519Pub });
162
+ return { decision: "accepted" };
163
+ }
@@ -0,0 +1,26 @@
1
+ /** The injectable resolver: given the resolved DID-doc URL, return the document.
2
+ * The host wires real `fetch`; tests inject a `(url) => Promise<fixture>` map. */
3
+ export type DidDocResolver = (didDocUrl: string) => Promise<unknown>;
4
+ /** A parsed did:web document (only the fields the overlay needs). */
5
+ export interface DidDocument {
6
+ id: string;
7
+ ed25519Pub: Uint8Array;
8
+ ed25519KeyId: string;
9
+ x25519Pub: Uint8Array;
10
+ x25519KeyId: string;
11
+ /** The agent-card URL declared in a `service` entry, if present (U6 binding). */
12
+ cardServiceUrl?: string;
13
+ }
14
+ /** `did:web:…` → the DID-doc URL. Returns null on a malformed DID. */
15
+ export declare function didWebToUrl(did: string): string | null;
16
+ /** Parse a DID document. Returns null/typed-failure on: not an object, id
17
+ * mismatch, missing assertionMethod (Ed25519), missing keyAgreement (X25519),
18
+ * unsupported key encoding, or a key-length mismatch. */
19
+ export declare function parseDidDocument(doc: unknown, did: string): DidDocument | null;
20
+ export interface ResolveDidWebInput {
21
+ did: string;
22
+ resolver: DidDocResolver;
23
+ }
24
+ /** Resolve a did:web via the injected resolver and parse the document. Returns
25
+ * null (never throws) on a malformed DID, a resolver error, or a parse failure. */
26
+ export declare function resolveDidWeb(input: ResolveDidWebInput): Promise<DidDocument | null>;
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.didWebToUrl = didWebToUrl;
4
+ exports.parseDidDocument = parseDidDocument;
5
+ exports.resolveDidWeb = resolveDidWeb;
6
+ // did:web — DID resolution behind an INJECTABLE resolver hook. did:key needs no
7
+ // network (U4); did:web does — so the resolver is the only network seam, and it
8
+ // is injected (the host supplies real `fetch`; tests inject a fixture map). NO
9
+ // real HTTP lives here. The a2a-client stays transport-injectable end to end.
10
+ //
11
+ // did:web → URL: `did:web:host` → https://host/.well-known/did.json;
12
+ // `did:web:host:a:b` → https://host/a/b/did.json
13
+ // Each colon-separated segment is percent-decoded (the host may carry an encoded
14
+ // port `%3A`). The DID-doc parse extracts the Ed25519 assertion key + the X25519
15
+ // keyAgreement key + the card service back-reference (for the U6 binding check).
16
+ const did_key_1 = require("./did-key");
17
+ // Multicodec prefixes (varint) for publicKeyMultibase decoding.
18
+ const ED25519_MULTICODEC = [0xed, 0x01];
19
+ const X25519_MULTICODEC = [0xec, 0x01];
20
+ const KEY_LEN = 32;
21
+ /** `did:web:…` → the DID-doc URL. Returns null on a malformed DID. */
22
+ function didWebToUrl(did) {
23
+ if (typeof did !== "string" || !did.startsWith("did:web:"))
24
+ return null;
25
+ const rest = did.slice("did:web:".length);
26
+ if (rest.length === 0)
27
+ return null;
28
+ const rawSegments = rest.split(":");
29
+ if (rawSegments.some((s) => s.length === 0))
30
+ return null;
31
+ let segments;
32
+ try {
33
+ segments = rawSegments.map((s) => decodeURIComponent(s));
34
+ }
35
+ catch {
36
+ return null; // a malformed percent-escape
37
+ }
38
+ // segments[0] (the host) is guaranteed non-empty: the `some(s.length === 0)`
39
+ // guard above rejects any empty segment, and decodeURIComponent of a non-empty
40
+ // string can't yield empty.
41
+ const host = segments[0];
42
+ if (segments.length === 1) {
43
+ return `https://${host}/.well-known/did.json`;
44
+ }
45
+ const path = segments.slice(1).join("/");
46
+ return `https://${host}/${path}/did.json`;
47
+ }
48
+ /** Decode a `publicKeyMultibase` (`z…` base58btc) and verify the multicodec +
49
+ * length. Returns the 32-byte key or null. */
50
+ function decodeMultibaseKey(value, multicodec) {
51
+ if (typeof value !== "string" || !value.startsWith("z"))
52
+ return null;
53
+ const decoded = (0, did_key_1.base58btcDecode)(value.slice(1));
54
+ if (!decoded)
55
+ return null;
56
+ if (decoded.length !== multicodec.length + KEY_LEN)
57
+ return null;
58
+ if (decoded[0] !== multicodec[0] || decoded[1] !== multicodec[1])
59
+ return null;
60
+ return decoded.slice(multicodec.length);
61
+ }
62
+ /** A verification method may be inline (an object) or a string reference. We only
63
+ * support inline methods here (string refs into the same doc are resolved by
64
+ * matching `id`). Returns the inline method object or null. */
65
+ function asMethodObject(m) {
66
+ if (!m || typeof m !== "object" || Array.isArray(m))
67
+ return null;
68
+ return m;
69
+ }
70
+ /** Find the first verification method in `methods` that decodes to a key of the
71
+ * given multicodec, returning the key + its id. */
72
+ function extractKey(methods, multicodec) {
73
+ if (!Array.isArray(methods))
74
+ return null;
75
+ for (const m of methods) {
76
+ const obj = asMethodObject(m);
77
+ if (!obj)
78
+ continue;
79
+ const pub = decodeMultibaseKey(obj.publicKeyMultibase, multicodec);
80
+ if (pub && typeof obj.id === "string") {
81
+ return { pub, keyId: obj.id };
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ /** Extract the agent-card service URL: a `service` entry whose endpoint is a
87
+ * string. Returns undefined when no usable service is present. */
88
+ function extractCardServiceUrl(doc) {
89
+ const services = doc.service;
90
+ if (!Array.isArray(services))
91
+ return undefined;
92
+ for (const s of services) {
93
+ if (!s || typeof s !== "object" || Array.isArray(s))
94
+ continue;
95
+ const endpoint = s.serviceEndpoint;
96
+ if (typeof endpoint === "string")
97
+ return endpoint;
98
+ }
99
+ return undefined;
100
+ }
101
+ /** Parse a DID document. Returns null/typed-failure on: not an object, id
102
+ * mismatch, missing assertionMethod (Ed25519), missing keyAgreement (X25519),
103
+ * unsupported key encoding, or a key-length mismatch. */
104
+ function parseDidDocument(doc, did) {
105
+ if (!doc || typeof doc !== "object" || Array.isArray(doc))
106
+ return null;
107
+ const d = doc;
108
+ if (typeof d.id !== "string" || d.id !== did)
109
+ return null;
110
+ // assertionMethod is the signing relationship; fall back to authentication.
111
+ const ed = extractKey(d.assertionMethod, ED25519_MULTICODEC) ?? extractKey(d.authentication, ED25519_MULTICODEC);
112
+ if (!ed)
113
+ return null;
114
+ const x = extractKey(d.keyAgreement, X25519_MULTICODEC);
115
+ if (!x)
116
+ return null;
117
+ return {
118
+ id: did,
119
+ ed25519Pub: ed.pub,
120
+ ed25519KeyId: ed.keyId,
121
+ x25519Pub: x.pub,
122
+ x25519KeyId: x.keyId,
123
+ cardServiceUrl: extractCardServiceUrl(d),
124
+ };
125
+ }
126
+ /** Resolve a did:web via the injected resolver and parse the document. Returns
127
+ * null (never throws) on a malformed DID, a resolver error, or a parse failure. */
128
+ async function resolveDidWeb(input) {
129
+ const url = didWebToUrl(input.did);
130
+ if (!url)
131
+ return null;
132
+ let doc;
133
+ try {
134
+ doc = await input.resolver(url);
135
+ }
136
+ catch {
137
+ return null; // network error / resolver throw — never propagates
138
+ }
139
+ return parseDidDocument(doc, input.did);
140
+ }
@@ -0,0 +1,23 @@
1
+ export { ready } from "./sodium";
2
+ export type { Sodium } from "./sodium";
3
+ export { jcsBytes, jcsString } from "./jcs";
4
+ export { openSealed, sealTo, SealOpenError } from "./seal";
5
+ export type { SealedBlob, OpenSealedInput, SealToInput } from "./seal";
6
+ export { parseProof, serializeProof, signEnvelope, verifyEnvelopeSignature } from "./sign";
7
+ export type { StructuredProof } from "./sign";
8
+ export { base58btcDecode, base58btcEncode, didKeyIdentityFromEd25519, ed25519PubToDidKey, keyAgreementFromDidKey, parseDidKey, } from "./did-key";
9
+ export type { DidKeyIdentity } from "./did-key";
10
+ export { didWebToUrl, parseDidDocument, resolveDidWeb } from "./did-web";
11
+ export type { DidDocResolver, DidDocument, ResolveDidWebInput } from "./did-web";
12
+ export { DidVerifier, evaluateRotation, getPinned, isPinned, MemoryPinStore, pinOnFirstContact, signSuccessor, verifyCardDidBinding, } from "./did-verifier";
13
+ export type { PinnedDid, PinStore, RotationDecision } from "./did-verifier";
14
+ export { openSealedEnvelope, sealEnvelope } from "./sealed-envelope";
15
+ export type { FriendsKind, FromIdentity, OpenSealedEnvelopeResult, RecipientIdentity, SealedEnvelope, } from "./sealed-envelope";
16
+ export { unwrapDataPart, wrapInDataPart } from "./a2a-message";
17
+ export type { A2ADataPart, A2AMessage, FriendsDataPartPayload } from "./a2a-message";
18
+ export { buildFriendsAgentCard } from "./agent-card";
19
+ export type { A2ACapabilities, A2ASkill, FriendsAgentCard } from "./agent-card";
20
+ export { resolveReachability } from "./reachability";
21
+ export type { ReachabilityPlan } from "./reachability";
22
+ export { receiveShare, sendShare } from "./adapter";
23
+ export type { A2ATransport, DidResolution, ReceiveShareInput, ReceiveShareResult, SeenLedgerLike, SendShareInput, SendShareResult, } from "./adapter";
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ // @ouro.bot/friends/a2a-client — the public host-side A2A adapter + the friends
3
+ // E2E security overlay (sign-then-seal + DID identity). This is the ONLY directory
4
+ // permitted to import libsodium / A2A / DID; the dependency-direction lint enforces
5
+ // core ⊥ a2a-client and a2a-client ⊥ mcp.
6
+ //
7
+ // The security model in one line: friends agents speak REAL A2A (`message/send`,
8
+ // DataPart) while every envelope is SIGNED by the sender (Ed25519) and SEALED to
9
+ // the recipient (XChaCha20-Poly1305 AEAD over ephemeral X25519 ECDH, with the
10
+ // recipient DID bound into the AEAD AD), so a relay carries CIPHERTEXT ONLY — it
11
+ // can never read, forge, tamper, re-target, replay-to-effect, or escalate.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.sendShare = exports.receiveShare = exports.resolveReachability = exports.buildFriendsAgentCard = exports.wrapInDataPart = exports.unwrapDataPart = exports.sealEnvelope = exports.openSealedEnvelope = exports.verifyCardDidBinding = exports.signSuccessor = exports.pinOnFirstContact = exports.MemoryPinStore = exports.isPinned = exports.getPinned = exports.evaluateRotation = exports.DidVerifier = exports.resolveDidWeb = exports.parseDidDocument = exports.didWebToUrl = exports.parseDidKey = exports.keyAgreementFromDidKey = exports.ed25519PubToDidKey = exports.didKeyIdentityFromEd25519 = exports.base58btcEncode = exports.base58btcDecode = exports.verifyEnvelopeSignature = exports.signEnvelope = exports.serializeProof = exports.parseProof = exports.SealOpenError = exports.sealTo = exports.openSealed = exports.jcsString = exports.jcsBytes = exports.ready = void 0;
14
+ // ── init seam ──
15
+ var sodium_1 = require("./sodium");
16
+ Object.defineProperty(exports, "ready", { enumerable: true, get: function () { return sodium_1.ready; } });
17
+ // ── canonicalization (RFC 8785 JCS) ──
18
+ var jcs_1 = require("./jcs");
19
+ Object.defineProperty(exports, "jcsBytes", { enumerable: true, get: function () { return jcs_1.jcsBytes; } });
20
+ Object.defineProperty(exports, "jcsString", { enumerable: true, get: function () { return jcs_1.jcsString; } });
21
+ // ── seal / open primitives ──
22
+ var seal_1 = require("./seal");
23
+ Object.defineProperty(exports, "openSealed", { enumerable: true, get: function () { return seal_1.openSealed; } });
24
+ Object.defineProperty(exports, "sealTo", { enumerable: true, get: function () { return seal_1.sealTo; } });
25
+ Object.defineProperty(exports, "SealOpenError", { enumerable: true, get: function () { return seal_1.SealOpenError; } });
26
+ // ── sign / verify + structured proof ──
27
+ var sign_1 = require("./sign");
28
+ Object.defineProperty(exports, "parseProof", { enumerable: true, get: function () { return sign_1.parseProof; } });
29
+ Object.defineProperty(exports, "serializeProof", { enumerable: true, get: function () { return sign_1.serializeProof; } });
30
+ Object.defineProperty(exports, "signEnvelope", { enumerable: true, get: function () { return sign_1.signEnvelope; } });
31
+ Object.defineProperty(exports, "verifyEnvelopeSignature", { enumerable: true, get: function () { return sign_1.verifyEnvelopeSignature; } });
32
+ // ── did:key (both keys from one DID) ──
33
+ var did_key_1 = require("./did-key");
34
+ Object.defineProperty(exports, "base58btcDecode", { enumerable: true, get: function () { return did_key_1.base58btcDecode; } });
35
+ Object.defineProperty(exports, "base58btcEncode", { enumerable: true, get: function () { return did_key_1.base58btcEncode; } });
36
+ Object.defineProperty(exports, "didKeyIdentityFromEd25519", { enumerable: true, get: function () { return did_key_1.didKeyIdentityFromEd25519; } });
37
+ Object.defineProperty(exports, "ed25519PubToDidKey", { enumerable: true, get: function () { return did_key_1.ed25519PubToDidKey; } });
38
+ Object.defineProperty(exports, "keyAgreementFromDidKey", { enumerable: true, get: function () { return did_key_1.keyAgreementFromDidKey; } });
39
+ Object.defineProperty(exports, "parseDidKey", { enumerable: true, get: function () { return did_key_1.parseDidKey; } });
40
+ // ── did:web (behind an injectable resolver) ──
41
+ var did_web_1 = require("./did-web");
42
+ Object.defineProperty(exports, "didWebToUrl", { enumerable: true, get: function () { return did_web_1.didWebToUrl; } });
43
+ Object.defineProperty(exports, "parseDidDocument", { enumerable: true, get: function () { return did_web_1.parseDidDocument; } });
44
+ Object.defineProperty(exports, "resolveDidWeb", { enumerable: true, get: function () { return did_web_1.resolveDidWeb; } });
45
+ // ── DidVerifier — binding, TOFU pin, trust-tiered rotation ──
46
+ var did_verifier_1 = require("./did-verifier");
47
+ Object.defineProperty(exports, "DidVerifier", { enumerable: true, get: function () { return did_verifier_1.DidVerifier; } });
48
+ Object.defineProperty(exports, "evaluateRotation", { enumerable: true, get: function () { return did_verifier_1.evaluateRotation; } });
49
+ Object.defineProperty(exports, "getPinned", { enumerable: true, get: function () { return did_verifier_1.getPinned; } });
50
+ Object.defineProperty(exports, "isPinned", { enumerable: true, get: function () { return did_verifier_1.isPinned; } });
51
+ Object.defineProperty(exports, "MemoryPinStore", { enumerable: true, get: function () { return did_verifier_1.MemoryPinStore; } });
52
+ Object.defineProperty(exports, "pinOnFirstContact", { enumerable: true, get: function () { return did_verifier_1.pinOnFirstContact; } });
53
+ Object.defineProperty(exports, "signSuccessor", { enumerable: true, get: function () { return did_verifier_1.signSuccessor; } });
54
+ Object.defineProperty(exports, "verifyCardDidBinding", { enumerable: true, get: function () { return did_verifier_1.verifyCardDidBinding; } });
55
+ // ── SealedEnvelope sign-then-seal compose ──
56
+ var sealed_envelope_1 = require("./sealed-envelope");
57
+ Object.defineProperty(exports, "openSealedEnvelope", { enumerable: true, get: function () { return sealed_envelope_1.openSealedEnvelope; } });
58
+ Object.defineProperty(exports, "sealEnvelope", { enumerable: true, get: function () { return sealed_envelope_1.sealEnvelope; } });
59
+ // ── A2A DataPart mapping (relay-blind) ──
60
+ var a2a_message_1 = require("./a2a-message");
61
+ Object.defineProperty(exports, "unwrapDataPart", { enumerable: true, get: function () { return a2a_message_1.unwrapDataPart; } });
62
+ Object.defineProperty(exports, "wrapInDataPart", { enumerable: true, get: function () { return a2a_message_1.wrapInDataPart; } });
63
+ // ── friends agent card ──
64
+ var agent_card_1 = require("./agent-card");
65
+ Object.defineProperty(exports, "buildFriendsAgentCard", { enumerable: true, get: function () { return agent_card_1.buildFriendsAgentCard; } });
66
+ // ── reachability ladder ──
67
+ var reachability_1 = require("./reachability");
68
+ Object.defineProperty(exports, "resolveReachability", { enumerable: true, get: function () { return reachability_1.resolveReachability; } });
69
+ // ── send / receive adapter ──
70
+ var adapter_1 = require("./adapter");
71
+ Object.defineProperty(exports, "receiveShare", { enumerable: true, get: function () { return adapter_1.receiveShare; } });
72
+ Object.defineProperty(exports, "sendShare", { enumerable: true, get: function () { return adapter_1.sendShare; } });
@@ -0,0 +1,5 @@
1
+ /** The JCS canonical JSON string for `value` (RFC 8785). */
2
+ export declare function jcsString(value: unknown): string;
3
+ /** The UTF-8 bytes of the JCS canonical JSON string — the message fed to
4
+ * Ed25519 signing and used as AEAD associated-data. */
5
+ export declare function jcsBytes(value: unknown): Uint8Array;