@youam/sdk 0.1.0 → 0.2.1
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/LICENSE +190 -0
- package/README.md +149 -7
- package/dist/cli/index.cjs +3345 -0
- package/dist/index.cjs +2974 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1095 -0
- package/dist/index.d.ts +1095 -0
- package/dist/index.js +2946 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -14
- package/index.js +0 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1095 @@
|
|
|
1
|
+
declare class SDKConfig {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly relayUrl: string;
|
|
4
|
+
readonly relayWsUrl: string;
|
|
5
|
+
readonly keyDir: string;
|
|
6
|
+
readonly dataDir: string;
|
|
7
|
+
readonly displayName: string;
|
|
8
|
+
readonly transportType: string;
|
|
9
|
+
readonly trustPolicy: string;
|
|
10
|
+
relayDomain: string;
|
|
11
|
+
/** The domain the caller chose, if any; null when only derived. */
|
|
12
|
+
readonly explicitRelayDomain: string | null;
|
|
13
|
+
constructor(options: {
|
|
14
|
+
name: string;
|
|
15
|
+
relayUrl?: string | null;
|
|
16
|
+
relayWsUrl?: string | null;
|
|
17
|
+
keyDir?: string | null;
|
|
18
|
+
dataDir?: string | null;
|
|
19
|
+
displayName?: string | null;
|
|
20
|
+
transportType?: string;
|
|
21
|
+
trustPolicy?: string;
|
|
22
|
+
relayDomain?: string;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Core types, constants, and utility functions for UAM protocol.
|
|
28
|
+
*/
|
|
29
|
+
/** Protocol version. */
|
|
30
|
+
declare const UAM_VERSION = "0.1";
|
|
31
|
+
/** Maximum envelope size in bytes (64 KB). */
|
|
32
|
+
declare const MAX_ENVELOPE_SIZE = 65536;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum allowed skew, in seconds, between an inbound envelope's signed
|
|
35
|
+
* timestamp and the receiver's clock (either direction). Also the TTL of
|
|
36
|
+
* the inbound replay cache. Matches Python MAX_ENVELOPE_AGE.
|
|
37
|
+
*/
|
|
38
|
+
declare const MAX_ENVELOPE_AGE = 300;
|
|
39
|
+
/** All UAM message types. */
|
|
40
|
+
declare enum MessageType {
|
|
41
|
+
MESSAGE = "message",
|
|
42
|
+
HANDSHAKE_REQUEST = "handshake.request",
|
|
43
|
+
HANDSHAKE_ACCEPT = "handshake.accept",
|
|
44
|
+
HANDSHAKE_DENY = "handshake.deny",
|
|
45
|
+
RECEIPT_DELIVERED = "receipt.delivered",
|
|
46
|
+
RECEIPT_READ = "receipt.read",
|
|
47
|
+
RECEIPT_FAILED = "receipt.failed",
|
|
48
|
+
SESSION_REQUEST = "session.request",
|
|
49
|
+
SESSION_ACCEPT = "session.accept",
|
|
50
|
+
SESSION_DECLINE = "session.decline",
|
|
51
|
+
SESSION_END = "session.end"
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* URL-safe base64 encode, stripping padding.
|
|
55
|
+
*/
|
|
56
|
+
declare function b64Encode(data: Uint8Array): string;
|
|
57
|
+
/**
|
|
58
|
+
* URL-safe base64 decode, tolerating missing padding.
|
|
59
|
+
*/
|
|
60
|
+
declare function b64Decode(s: string): Uint8Array;
|
|
61
|
+
/**
|
|
62
|
+
* Return a canonical UTC timestamp: YYYY-MM-DDTHH:MM:SS.mmmZ
|
|
63
|
+
*/
|
|
64
|
+
declare function utcTimestamp(): string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* UAM exception hierarchy.
|
|
68
|
+
*
|
|
69
|
+
* All protocol-specific errors inherit from UAMError.
|
|
70
|
+
*/
|
|
71
|
+
/** Base error for all UAM protocol errors. */
|
|
72
|
+
declare class UAMError extends Error {
|
|
73
|
+
constructor(message?: string);
|
|
74
|
+
}
|
|
75
|
+
/** Raised when an address string fails validation. */
|
|
76
|
+
declare class InvalidAddressError extends UAMError {
|
|
77
|
+
constructor(message?: string);
|
|
78
|
+
}
|
|
79
|
+
/** Raised when an envelope fails schema validation. */
|
|
80
|
+
declare class InvalidEnvelopeError extends UAMError {
|
|
81
|
+
constructor(message?: string);
|
|
82
|
+
}
|
|
83
|
+
/** Raised when an envelope exceeds the maximum allowed size. */
|
|
84
|
+
declare class EnvelopeTooLargeError extends InvalidEnvelopeError {
|
|
85
|
+
constructor(message?: string);
|
|
86
|
+
}
|
|
87
|
+
/** Raised on signing failures. */
|
|
88
|
+
declare class SignatureError extends UAMError {
|
|
89
|
+
constructor(message?: string);
|
|
90
|
+
}
|
|
91
|
+
/** Raised when a cryptographic signature cannot be verified. */
|
|
92
|
+
declare class SignatureVerificationError extends SignatureError {
|
|
93
|
+
constructor(message?: string);
|
|
94
|
+
}
|
|
95
|
+
/** Raised on encryption failures. */
|
|
96
|
+
declare class EncryptionError extends UAMError {
|
|
97
|
+
constructor(message?: string);
|
|
98
|
+
}
|
|
99
|
+
/** Raised on decryption failures. */
|
|
100
|
+
declare class DecryptionError extends EncryptionError {
|
|
101
|
+
constructor(message?: string);
|
|
102
|
+
}
|
|
103
|
+
/** Raised when a contact card fails validation. */
|
|
104
|
+
declare class InvalidContactCardError extends UAMError {
|
|
105
|
+
constructor(message?: string);
|
|
106
|
+
}
|
|
107
|
+
/** Raised when a pinned contact's public key doesn't match the resolved key. */
|
|
108
|
+
declare class KeyPinningError extends UAMError {
|
|
109
|
+
constructor(message?: string);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* UAM address parsing and validation.
|
|
114
|
+
*
|
|
115
|
+
* A UAM address has the form `agent::domain` (e.g. `alice::youam.network`).
|
|
116
|
+
*/
|
|
117
|
+
/**
|
|
118
|
+
* A parsed UAM address (always lowercase).
|
|
119
|
+
*/
|
|
120
|
+
interface Address {
|
|
121
|
+
readonly agent: string;
|
|
122
|
+
readonly domain: string;
|
|
123
|
+
readonly full: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse and validate a UAM address string.
|
|
127
|
+
*
|
|
128
|
+
* Strips whitespace, lowercases, and validates format.
|
|
129
|
+
* Enforces max lengths: agent <= 64 chars, full address <= 128 chars.
|
|
130
|
+
*
|
|
131
|
+
* @throws {InvalidAddressError} If `raw` is not a valid `agent::domain` address.
|
|
132
|
+
*/
|
|
133
|
+
declare function parseAddress(raw: string): Address;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Cryptographic primitives for UAM protocol.
|
|
137
|
+
*
|
|
138
|
+
* Wraps libsodium-wrappers for Ed25519 signing, Curve25519 key exchange,
|
|
139
|
+
* NaCl Box (authenticated encryption), and NaCl SealedBox (anonymous encryption).
|
|
140
|
+
*
|
|
141
|
+
* This module never hand-rolls crypto -- every operation delegates to libsodium.
|
|
142
|
+
*/
|
|
143
|
+
/**
|
|
144
|
+
* Promise that resolves when libsodium is ready.
|
|
145
|
+
* Callers must await this before first use of any crypto function.
|
|
146
|
+
*/
|
|
147
|
+
declare const sodiumReady: Promise<void>;
|
|
148
|
+
/** 64-byte Ed25519 secret key (seed + public). */
|
|
149
|
+
type SigningKey = Uint8Array;
|
|
150
|
+
/** 32-byte Ed25519 public key. */
|
|
151
|
+
type VerifyKey = Uint8Array;
|
|
152
|
+
/** 32-byte seed. */
|
|
153
|
+
type Seed = Uint8Array;
|
|
154
|
+
interface Keypair {
|
|
155
|
+
signingKey: Uint8Array;
|
|
156
|
+
verifyKey: Uint8Array;
|
|
157
|
+
seed: Uint8Array;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Generate an Ed25519 keypair.
|
|
161
|
+
*/
|
|
162
|
+
declare function generateKeypair(): Keypair;
|
|
163
|
+
/**
|
|
164
|
+
* Serialize a signing key to URL-safe base64 (32-byte seed).
|
|
165
|
+
*
|
|
166
|
+
* Matches Python: PyNaCl's SigningKey.encode() returns the 32-byte seed.
|
|
167
|
+
*/
|
|
168
|
+
declare function serializeSigningKey(seed: Uint8Array): string;
|
|
169
|
+
/**
|
|
170
|
+
* Restore a signing key from its base64-encoded seed.
|
|
171
|
+
*/
|
|
172
|
+
declare function deserializeSigningKey(s: string): Keypair;
|
|
173
|
+
/**
|
|
174
|
+
* Serialize a verify (public) key to URL-safe base64.
|
|
175
|
+
*/
|
|
176
|
+
declare function serializeVerifyKey(key: Uint8Array): string;
|
|
177
|
+
/**
|
|
178
|
+
* Restore a verify key from its base64 encoding.
|
|
179
|
+
*/
|
|
180
|
+
declare function deserializeVerifyKey(s: string): Uint8Array;
|
|
181
|
+
/**
|
|
182
|
+
* Return the SHA-256 hex digest of the verify key bytes.
|
|
183
|
+
*
|
|
184
|
+
* This 64-character string serves as the agent's identity fingerprint.
|
|
185
|
+
*/
|
|
186
|
+
declare function publicKeyFingerprint(verifyKey: Uint8Array): string;
|
|
187
|
+
/**
|
|
188
|
+
* Produce deterministic JSON bytes for signing.
|
|
189
|
+
*
|
|
190
|
+
* - Excludes the "signature" key.
|
|
191
|
+
* - Excludes keys whose value is null or undefined.
|
|
192
|
+
* - Sorts keys, uses compact separators, ensures ASCII encoding.
|
|
193
|
+
*/
|
|
194
|
+
declare function canonicalize(data: Record<string, unknown>): Uint8Array;
|
|
195
|
+
/**
|
|
196
|
+
* Sign data with the Ed25519 signing key.
|
|
197
|
+
*
|
|
198
|
+
* @returns The 64-byte signature as a URL-safe base64 string.
|
|
199
|
+
*/
|
|
200
|
+
declare function signMessage(data: Uint8Array, signingKey: Uint8Array): string;
|
|
201
|
+
/**
|
|
202
|
+
* Verify an Ed25519 signature.
|
|
203
|
+
*
|
|
204
|
+
* @throws {SignatureVerificationError} If the signature is invalid.
|
|
205
|
+
*/
|
|
206
|
+
declare function verifySignature(data: Uint8Array, signatureB64: string, verifyKey: Uint8Array): void;
|
|
207
|
+
/**
|
|
208
|
+
* Generate 24 cryptographically random bytes, returned as base64.
|
|
209
|
+
*/
|
|
210
|
+
declare function generateNonce(): string;
|
|
211
|
+
/**
|
|
212
|
+
* Encrypt plaintext using NaCl Box (authenticated encryption).
|
|
213
|
+
*
|
|
214
|
+
* Both parties are known; the sender signs implicitly via key exchange.
|
|
215
|
+
* Format: nonce (24 bytes) || ciphertext, matching PyNaCl Box.encrypt() output.
|
|
216
|
+
*
|
|
217
|
+
* @returns Base64-encoded ciphertext (nonce prepended).
|
|
218
|
+
* @throws {EncryptionError} On any libsodium error.
|
|
219
|
+
*/
|
|
220
|
+
declare function encryptPayload(plaintext: Uint8Array, senderSigningKey: Uint8Array, recipientVerifyKey: Uint8Array): string;
|
|
221
|
+
/**
|
|
222
|
+
* Decrypt NaCl Box ciphertext.
|
|
223
|
+
*
|
|
224
|
+
* @returns The original plaintext bytes.
|
|
225
|
+
* @throws {DecryptionError} On any libsodium error (wrong keys, tampered data).
|
|
226
|
+
*/
|
|
227
|
+
declare function decryptPayload(ciphertextB64: string, recipientSigningKey: Uint8Array, senderVerifyKey: Uint8Array): Uint8Array;
|
|
228
|
+
/**
|
|
229
|
+
* Encrypt plaintext using NaCl SealedBox (anonymous sender).
|
|
230
|
+
*
|
|
231
|
+
* Only the recipient's public key is required; no sender authentication.
|
|
232
|
+
*
|
|
233
|
+
* @returns Base64-encoded ciphertext.
|
|
234
|
+
*/
|
|
235
|
+
declare function encryptPayloadAnonymous(plaintext: Uint8Array, recipientVerifyKey: Uint8Array): string;
|
|
236
|
+
/**
|
|
237
|
+
* Decrypt NaCl SealedBox ciphertext.
|
|
238
|
+
*
|
|
239
|
+
* @returns The original plaintext bytes.
|
|
240
|
+
* @throws {DecryptionError} On any libsodium error.
|
|
241
|
+
*/
|
|
242
|
+
declare function decryptPayloadAnonymous(ciphertextB64: string, recipientSigningKey: Uint8Array): Uint8Array;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* UAM message envelope -- creation, signing, verification, wire format.
|
|
246
|
+
*
|
|
247
|
+
* An envelope wraps every UAM message with cryptographic signatures and
|
|
248
|
+
* encrypted payloads, using fromAddress/toAddress naming internally
|
|
249
|
+
* ("from"/"to" on the wire).
|
|
250
|
+
*/
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* A signed, encrypted UAM message envelope.
|
|
254
|
+
*/
|
|
255
|
+
interface MessageEnvelope {
|
|
256
|
+
readonly uamVersion: string;
|
|
257
|
+
readonly messageId: string;
|
|
258
|
+
readonly fromAddress: string;
|
|
259
|
+
readonly toAddress: string;
|
|
260
|
+
readonly timestamp: string;
|
|
261
|
+
readonly type: string;
|
|
262
|
+
readonly nonce: string;
|
|
263
|
+
readonly payload: string;
|
|
264
|
+
readonly signature: string;
|
|
265
|
+
readonly threadId?: string;
|
|
266
|
+
readonly replyTo?: string;
|
|
267
|
+
readonly expires?: string;
|
|
268
|
+
readonly mediaType?: string;
|
|
269
|
+
readonly metadata?: Record<string, unknown>;
|
|
270
|
+
readonly attachments?: Array<Record<string, unknown>>;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Convert an envelope to a wire-format dict.
|
|
274
|
+
*
|
|
275
|
+
* Maps fromAddress -> "from" and toAddress -> "to".
|
|
276
|
+
* Excludes undefined-valued optional fields.
|
|
277
|
+
*/
|
|
278
|
+
declare function toWireDict(envelope: MessageEnvelope): Record<string, unknown>;
|
|
279
|
+
/**
|
|
280
|
+
* Restore an envelope from a wire-format dict.
|
|
281
|
+
*
|
|
282
|
+
* @throws {InvalidEnvelopeError} If any required field is missing.
|
|
283
|
+
*/
|
|
284
|
+
declare function fromWireDict(d: Record<string, unknown>): MessageEnvelope;
|
|
285
|
+
/**
|
|
286
|
+
* Check that the serialized envelope does not exceed MAX_ENVELOPE_SIZE.
|
|
287
|
+
*
|
|
288
|
+
* @throws {EnvelopeTooLargeError} If the wire JSON exceeds 64 KB.
|
|
289
|
+
*/
|
|
290
|
+
declare function validateEnvelopeSize(envelope: MessageEnvelope): void;
|
|
291
|
+
/**
|
|
292
|
+
* Create a signed, encrypted message envelope.
|
|
293
|
+
*
|
|
294
|
+
* @throws {InvalidAddressError} If either address is invalid.
|
|
295
|
+
* @throws {EnvelopeTooLargeError} If the serialized envelope exceeds 64 KB.
|
|
296
|
+
*/
|
|
297
|
+
declare function createEnvelope(fromAddress: string, toAddress: string, messageType: MessageType | string, payloadPlaintext: Uint8Array, signingKey: Uint8Array, recipientVerifyKey: Uint8Array, options?: {
|
|
298
|
+
threadId?: string;
|
|
299
|
+
replyTo?: string;
|
|
300
|
+
expires?: string;
|
|
301
|
+
mediaType?: string;
|
|
302
|
+
metadata?: Record<string, unknown>;
|
|
303
|
+
attachments?: Array<Record<string, unknown>>;
|
|
304
|
+
}): Promise<MessageEnvelope>;
|
|
305
|
+
/**
|
|
306
|
+
* Verify the cryptographic signature on an envelope.
|
|
307
|
+
*
|
|
308
|
+
* @throws {SignatureVerificationError} If the signature is invalid.
|
|
309
|
+
*/
|
|
310
|
+
declare function verifyEnvelope(envelope: MessageEnvelope, senderVerifyKey: Uint8Array): void;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* UAM contact cards -- self-signed agent identity documents.
|
|
314
|
+
*
|
|
315
|
+
* A contact card advertises an agent's address, public key, relay endpoint,
|
|
316
|
+
* and optional metadata. The card is signed by the agent's own signing key
|
|
317
|
+
* so that any recipient can verify authenticity using the embedded public key.
|
|
318
|
+
*/
|
|
319
|
+
/**
|
|
320
|
+
* A self-signed agent identity card.
|
|
321
|
+
*/
|
|
322
|
+
interface ContactCard {
|
|
323
|
+
readonly version: string;
|
|
324
|
+
readonly address: string;
|
|
325
|
+
readonly displayName: string;
|
|
326
|
+
readonly description?: string;
|
|
327
|
+
readonly system?: string;
|
|
328
|
+
readonly connectionEndpoint?: string;
|
|
329
|
+
readonly relay: string;
|
|
330
|
+
readonly publicKey: string;
|
|
331
|
+
readonly signature: string;
|
|
332
|
+
readonly verifiedDomain?: string;
|
|
333
|
+
readonly payloadFormats?: string[];
|
|
334
|
+
readonly fingerprint?: string;
|
|
335
|
+
/** CARD-04: Multi-relay support. Outside signature scope. */
|
|
336
|
+
readonly relays?: string[];
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Serialize a contact card to a plain dict.
|
|
340
|
+
*
|
|
341
|
+
* Uses snake_case names on the wire, excludes undefined-valued optional fields.
|
|
342
|
+
*/
|
|
343
|
+
declare function contactCardToDict(card: ContactCard): Record<string, unknown>;
|
|
344
|
+
/**
|
|
345
|
+
* Deserialize a contact card from a dict.
|
|
346
|
+
*
|
|
347
|
+
* When verify is true (the default), the card's signature is checked
|
|
348
|
+
* immediately after deserialization.
|
|
349
|
+
*
|
|
350
|
+
* @throws {InvalidContactCardError} If required fields are missing.
|
|
351
|
+
* @throws {SignatureVerificationError} If verify is true and the signature is invalid.
|
|
352
|
+
*/
|
|
353
|
+
declare function contactCardFromDict(d: Record<string, unknown>, options?: {
|
|
354
|
+
verify?: boolean;
|
|
355
|
+
}): ContactCard;
|
|
356
|
+
/**
|
|
357
|
+
* Create a self-signed contact card.
|
|
358
|
+
*
|
|
359
|
+
* payloadFormats defaults to ["text/plain", "text/markdown"] when not specified.
|
|
360
|
+
* The fingerprint is always auto-computed as the SHA-256 hex digest of the
|
|
361
|
+
* Ed25519 public key bytes.
|
|
362
|
+
*
|
|
363
|
+
* @throws {InvalidAddressError} If address is not valid.
|
|
364
|
+
*/
|
|
365
|
+
declare function createContactCard(address: string, displayName: string, relay: string, signingKey: Uint8Array, options?: {
|
|
366
|
+
description?: string;
|
|
367
|
+
system?: string;
|
|
368
|
+
connectionEndpoint?: string;
|
|
369
|
+
verifiedDomain?: string;
|
|
370
|
+
payloadFormats?: string[];
|
|
371
|
+
relays?: string[];
|
|
372
|
+
}): Promise<ContactCard>;
|
|
373
|
+
/**
|
|
374
|
+
* Verify a contact card's signature using its embedded public key.
|
|
375
|
+
*
|
|
376
|
+
* @throws {InvalidContactCardError} If the address is invalid.
|
|
377
|
+
* @throws {SignatureVerificationError} If the signature is invalid.
|
|
378
|
+
*/
|
|
379
|
+
declare function verifyContactCard(card: ContactCard): void;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* UAM Protocol -- Universal Agent Messaging protocol library.
|
|
383
|
+
*
|
|
384
|
+
* Public API re-exports for the protocol layer.
|
|
385
|
+
*/
|
|
386
|
+
|
|
387
|
+
type index_Address = Address;
|
|
388
|
+
type index_ContactCard = ContactCard;
|
|
389
|
+
type index_DecryptionError = DecryptionError;
|
|
390
|
+
declare const index_DecryptionError: typeof DecryptionError;
|
|
391
|
+
type index_EncryptionError = EncryptionError;
|
|
392
|
+
declare const index_EncryptionError: typeof EncryptionError;
|
|
393
|
+
type index_EnvelopeTooLargeError = EnvelopeTooLargeError;
|
|
394
|
+
declare const index_EnvelopeTooLargeError: typeof EnvelopeTooLargeError;
|
|
395
|
+
type index_InvalidAddressError = InvalidAddressError;
|
|
396
|
+
declare const index_InvalidAddressError: typeof InvalidAddressError;
|
|
397
|
+
type index_InvalidContactCardError = InvalidContactCardError;
|
|
398
|
+
declare const index_InvalidContactCardError: typeof InvalidContactCardError;
|
|
399
|
+
type index_InvalidEnvelopeError = InvalidEnvelopeError;
|
|
400
|
+
declare const index_InvalidEnvelopeError: typeof InvalidEnvelopeError;
|
|
401
|
+
type index_KeyPinningError = KeyPinningError;
|
|
402
|
+
declare const index_KeyPinningError: typeof KeyPinningError;
|
|
403
|
+
type index_Keypair = Keypair;
|
|
404
|
+
declare const index_MAX_ENVELOPE_AGE: typeof MAX_ENVELOPE_AGE;
|
|
405
|
+
declare const index_MAX_ENVELOPE_SIZE: typeof MAX_ENVELOPE_SIZE;
|
|
406
|
+
type index_MessageEnvelope = MessageEnvelope;
|
|
407
|
+
type index_MessageType = MessageType;
|
|
408
|
+
declare const index_MessageType: typeof MessageType;
|
|
409
|
+
type index_Seed = Seed;
|
|
410
|
+
type index_SignatureError = SignatureError;
|
|
411
|
+
declare const index_SignatureError: typeof SignatureError;
|
|
412
|
+
type index_SignatureVerificationError = SignatureVerificationError;
|
|
413
|
+
declare const index_SignatureVerificationError: typeof SignatureVerificationError;
|
|
414
|
+
type index_SigningKey = SigningKey;
|
|
415
|
+
type index_UAMError = UAMError;
|
|
416
|
+
declare const index_UAMError: typeof UAMError;
|
|
417
|
+
declare const index_UAM_VERSION: typeof UAM_VERSION;
|
|
418
|
+
type index_VerifyKey = VerifyKey;
|
|
419
|
+
declare const index_b64Decode: typeof b64Decode;
|
|
420
|
+
declare const index_b64Encode: typeof b64Encode;
|
|
421
|
+
declare const index_canonicalize: typeof canonicalize;
|
|
422
|
+
declare const index_contactCardFromDict: typeof contactCardFromDict;
|
|
423
|
+
declare const index_contactCardToDict: typeof contactCardToDict;
|
|
424
|
+
declare const index_createContactCard: typeof createContactCard;
|
|
425
|
+
declare const index_createEnvelope: typeof createEnvelope;
|
|
426
|
+
declare const index_decryptPayload: typeof decryptPayload;
|
|
427
|
+
declare const index_decryptPayloadAnonymous: typeof decryptPayloadAnonymous;
|
|
428
|
+
declare const index_deserializeSigningKey: typeof deserializeSigningKey;
|
|
429
|
+
declare const index_deserializeVerifyKey: typeof deserializeVerifyKey;
|
|
430
|
+
declare const index_encryptPayload: typeof encryptPayload;
|
|
431
|
+
declare const index_encryptPayloadAnonymous: typeof encryptPayloadAnonymous;
|
|
432
|
+
declare const index_fromWireDict: typeof fromWireDict;
|
|
433
|
+
declare const index_generateKeypair: typeof generateKeypair;
|
|
434
|
+
declare const index_generateNonce: typeof generateNonce;
|
|
435
|
+
declare const index_parseAddress: typeof parseAddress;
|
|
436
|
+
declare const index_publicKeyFingerprint: typeof publicKeyFingerprint;
|
|
437
|
+
declare const index_serializeSigningKey: typeof serializeSigningKey;
|
|
438
|
+
declare const index_serializeVerifyKey: typeof serializeVerifyKey;
|
|
439
|
+
declare const index_signMessage: typeof signMessage;
|
|
440
|
+
declare const index_sodiumReady: typeof sodiumReady;
|
|
441
|
+
declare const index_toWireDict: typeof toWireDict;
|
|
442
|
+
declare const index_utcTimestamp: typeof utcTimestamp;
|
|
443
|
+
declare const index_validateEnvelopeSize: typeof validateEnvelopeSize;
|
|
444
|
+
declare const index_verifyContactCard: typeof verifyContactCard;
|
|
445
|
+
declare const index_verifyEnvelope: typeof verifyEnvelope;
|
|
446
|
+
declare const index_verifySignature: typeof verifySignature;
|
|
447
|
+
declare namespace index {
|
|
448
|
+
export { type index_Address as Address, type index_ContactCard as ContactCard, index_DecryptionError as DecryptionError, index_EncryptionError as EncryptionError, index_EnvelopeTooLargeError as EnvelopeTooLargeError, index_InvalidAddressError as InvalidAddressError, index_InvalidContactCardError as InvalidContactCardError, index_InvalidEnvelopeError as InvalidEnvelopeError, index_KeyPinningError as KeyPinningError, type index_Keypair as Keypair, index_MAX_ENVELOPE_AGE as MAX_ENVELOPE_AGE, index_MAX_ENVELOPE_SIZE as MAX_ENVELOPE_SIZE, type index_MessageEnvelope as MessageEnvelope, index_MessageType as MessageType, type index_Seed as Seed, index_SignatureError as SignatureError, index_SignatureVerificationError as SignatureVerificationError, type index_SigningKey as SigningKey, index_UAMError as UAMError, index_UAM_VERSION as UAM_VERSION, type index_VerifyKey as VerifyKey, index_b64Decode as b64Decode, index_b64Encode as b64Encode, index_canonicalize as canonicalize, index_contactCardFromDict as contactCardFromDict, index_contactCardToDict as contactCardToDict, index_createContactCard as createContactCard, index_createEnvelope as createEnvelope, index_decryptPayload as decryptPayload, index_decryptPayloadAnonymous as decryptPayloadAnonymous, index_deserializeSigningKey as deserializeSigningKey, index_deserializeVerifyKey as deserializeVerifyKey, index_encryptPayload as encryptPayload, index_encryptPayloadAnonymous as encryptPayloadAnonymous, index_fromWireDict as fromWireDict, index_generateKeypair as generateKeypair, index_generateNonce as generateNonce, index_parseAddress as parseAddress, index_publicKeyFingerprint as publicKeyFingerprint, index_serializeSigningKey as serializeSigningKey, index_serializeVerifyKey as serializeVerifyKey, index_signMessage as signMessage, index_sodiumReady as sodiumReady, index_toWireDict as toWireDict, index_utcTimestamp as utcTimestamp, index_validateEnvelopeSize as validateEnvelopeSize, index_verifyContactCard as verifyContactCard, index_verifyEnvelope as verifyEnvelope, index_verifySignature as verifySignature };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Ed25519 key generation, storage, loading, and token persistence.
|
|
453
|
+
*
|
|
454
|
+
* Matches Python key_manager.py: stores 32-byte seed (not 64-byte secret key),
|
|
455
|
+
* uses same file format so keys are cross-language compatible.
|
|
456
|
+
*/
|
|
457
|
+
|
|
458
|
+
declare class KeyManager {
|
|
459
|
+
private _keyDir;
|
|
460
|
+
private _keypair;
|
|
461
|
+
constructor(keyDir?: string | null);
|
|
462
|
+
/** The 64-byte Ed25519 signing key. Throws if not loaded. */
|
|
463
|
+
get signingKey(): SigningKey;
|
|
464
|
+
/** The 32-byte Ed25519 verify (public) key. Throws if not loaded. */
|
|
465
|
+
get verifyKey(): VerifyKey;
|
|
466
|
+
/** The 32-byte seed. Throws if not loaded. */
|
|
467
|
+
get seed(): Seed;
|
|
468
|
+
/**
|
|
469
|
+
* Load existing keypair or generate a new one.
|
|
470
|
+
*
|
|
471
|
+
* Checks UAM_SIGNING_KEY env var first (base64-encoded Ed25519 seed).
|
|
472
|
+
* Falls back to file-based storage at {keyDir}/{name}.key.
|
|
473
|
+
* Generates a new keypair if neither source exists.
|
|
474
|
+
*/
|
|
475
|
+
loadOrGenerate(name: string): void;
|
|
476
|
+
/**
|
|
477
|
+
* Store the relay token alongside the keypair.
|
|
478
|
+
*/
|
|
479
|
+
saveToken(name: string, token: string): void;
|
|
480
|
+
/**
|
|
481
|
+
* Store the address `relayUrl` gave this agent.
|
|
482
|
+
*/
|
|
483
|
+
saveAddress(name: string, address: string, relayUrl: string): void;
|
|
484
|
+
/**
|
|
485
|
+
* Load the address saved for `relayUrl`.
|
|
486
|
+
*
|
|
487
|
+
* Returns null if there is none: the file is missing, unreadable or
|
|
488
|
+
* malformed, or it belongs to another relay.
|
|
489
|
+
*/
|
|
490
|
+
loadAddress(name: string, relayUrl: string): string | null;
|
|
491
|
+
/**
|
|
492
|
+
* Load a previously saved token, or return null.
|
|
493
|
+
*
|
|
494
|
+
* Checks UAM_TOKEN env var first, then file-based storage,
|
|
495
|
+
* then legacy .api_key files for backward compatibility. With `relayUrl`,
|
|
496
|
+
* a saved token that belongs to another relay counts as none, so it is
|
|
497
|
+
* only offered to the relay that issued it.
|
|
498
|
+
*/
|
|
499
|
+
loadToken(name: string, relayUrl?: string): string | null;
|
|
500
|
+
/**
|
|
501
|
+
* True if the saved address says this agent's files are another relay's.
|
|
502
|
+
* A missing or damaged file says nothing.
|
|
503
|
+
*/
|
|
504
|
+
private _savedForAnotherRelay;
|
|
505
|
+
/** Set file permissions to 0o600 (owner read/write only). */
|
|
506
|
+
private _setPermissions;
|
|
507
|
+
/** Warn if key file permissions are too permissive. */
|
|
508
|
+
private _checkPermissions;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* ReceivedMessage -- frozen data object for inbound messages.
|
|
513
|
+
*
|
|
514
|
+
* Matches Python message.py: a readonly interface for decrypted messages.
|
|
515
|
+
* The toString() omission is deliberate prompt-injection isolation (SDK-10).
|
|
516
|
+
*/
|
|
517
|
+
/**
|
|
518
|
+
* A decrypted, verified inbound message.
|
|
519
|
+
*
|
|
520
|
+
* This is a DATA OBJECT. Framework integrations must explicitly
|
|
521
|
+
* extract .content to use in prompts. The SDK never concatenates
|
|
522
|
+
* message content into LLM context automatically.
|
|
523
|
+
*/
|
|
524
|
+
interface ReceivedMessage {
|
|
525
|
+
readonly messageId: string;
|
|
526
|
+
readonly fromAddress: string;
|
|
527
|
+
readonly toAddress: string;
|
|
528
|
+
readonly content: string;
|
|
529
|
+
readonly timestamp: string;
|
|
530
|
+
readonly type: string;
|
|
531
|
+
readonly threadId?: string;
|
|
532
|
+
readonly replyTo?: string;
|
|
533
|
+
readonly mediaType?: string;
|
|
534
|
+
readonly verified: boolean;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* SQLite-backed local contact storage (HAND-03).
|
|
539
|
+
*
|
|
540
|
+
* Stores known contacts with their public keys and trust state.
|
|
541
|
+
* Provides fast in-memory caches for isKnown() and isBlocked() checks.
|
|
542
|
+
*
|
|
543
|
+
* Uses better-sqlite3 for synchronous SQLite (simpler than Python's aiosqlite).
|
|
544
|
+
*/
|
|
545
|
+
/**
|
|
546
|
+
* A row returned from listContacts().
|
|
547
|
+
*/
|
|
548
|
+
interface ContactRow {
|
|
549
|
+
address: string;
|
|
550
|
+
displayName: string | null;
|
|
551
|
+
trustState: string;
|
|
552
|
+
firstSeen: string;
|
|
553
|
+
lastSeen: string;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* A pending handshake entry.
|
|
557
|
+
*/
|
|
558
|
+
interface PendingEntry {
|
|
559
|
+
address: string;
|
|
560
|
+
contactCard: string;
|
|
561
|
+
receivedAt: string;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* A blocked pattern entry.
|
|
565
|
+
*/
|
|
566
|
+
interface BlockedEntry {
|
|
567
|
+
pattern: string;
|
|
568
|
+
blockedAt: string;
|
|
569
|
+
}
|
|
570
|
+
declare class ContactBook {
|
|
571
|
+
private _dbPath;
|
|
572
|
+
private _db;
|
|
573
|
+
private _knownAddresses;
|
|
574
|
+
private _blockedExact;
|
|
575
|
+
private _blockedDomains;
|
|
576
|
+
constructor(dataDir: string);
|
|
577
|
+
/**
|
|
578
|
+
* Open the database, create tables, run migrations, load caches.
|
|
579
|
+
*/
|
|
580
|
+
open(): void;
|
|
581
|
+
/**
|
|
582
|
+
* Run schema migrations using PRAGMA user_version.
|
|
583
|
+
*/
|
|
584
|
+
private _migrate;
|
|
585
|
+
/**
|
|
586
|
+
* Close the database connection.
|
|
587
|
+
*/
|
|
588
|
+
close(): void;
|
|
589
|
+
/**
|
|
590
|
+
* Check if an address is in the contact book (in-memory, no I/O).
|
|
591
|
+
*/
|
|
592
|
+
isKnown(address: string): boolean;
|
|
593
|
+
/**
|
|
594
|
+
* Check if an address matches any blocked pattern (O(1) set lookup).
|
|
595
|
+
*/
|
|
596
|
+
isBlocked(address: string): boolean;
|
|
597
|
+
/**
|
|
598
|
+
* Look up the public key for a known contact.
|
|
599
|
+
*/
|
|
600
|
+
getPublicKey(address: string): string | null;
|
|
601
|
+
/**
|
|
602
|
+
* Return the relay URLs for a known contact (CARD-04).
|
|
603
|
+
*/
|
|
604
|
+
getRelayUrls(address: string): string[] | null;
|
|
605
|
+
/**
|
|
606
|
+
* Add or update a contact (upsert).
|
|
607
|
+
*
|
|
608
|
+
* Refuses to replace the key of a contact whose trust_state is "pinned"
|
|
609
|
+
* or "verified" with a different key, unless `force: true` is passed (for
|
|
610
|
+
* a deliberate key rotation, which is logged). Matches the Python SDK's
|
|
611
|
+
* ContactBook.add_contact.
|
|
612
|
+
*
|
|
613
|
+
* @throws {KeyPinningError} If a pinned/verified contact's key would be
|
|
614
|
+
* replaced by a different key without `force`.
|
|
615
|
+
*/
|
|
616
|
+
addContact(address: string, publicKey: string, options?: {
|
|
617
|
+
displayName?: string | null;
|
|
618
|
+
trustState?: string;
|
|
619
|
+
trustSource?: string | null;
|
|
620
|
+
relay?: string | null;
|
|
621
|
+
relays?: string[] | null;
|
|
622
|
+
force?: boolean;
|
|
623
|
+
}): void;
|
|
624
|
+
/**
|
|
625
|
+
* Return all contacts sorted by last_seen DESC.
|
|
626
|
+
*/
|
|
627
|
+
listContacts(): ContactRow[];
|
|
628
|
+
/**
|
|
629
|
+
* Store a pending handshake request.
|
|
630
|
+
*/
|
|
631
|
+
addPending(address: string, contactCardJson: string): void;
|
|
632
|
+
/**
|
|
633
|
+
* Retrieve all pending handshake requests.
|
|
634
|
+
*/
|
|
635
|
+
getPending(): PendingEntry[];
|
|
636
|
+
/**
|
|
637
|
+
* Remove a pending handshake request.
|
|
638
|
+
*/
|
|
639
|
+
removePending(address: string): void;
|
|
640
|
+
/**
|
|
641
|
+
* Return pending handshakes older than N days.
|
|
642
|
+
*/
|
|
643
|
+
getExpiredPending(days?: number): PendingEntry[];
|
|
644
|
+
/**
|
|
645
|
+
* Return the trust_state for an address, or null if unknown.
|
|
646
|
+
*/
|
|
647
|
+
getTrustState(address: string): string | null;
|
|
648
|
+
/**
|
|
649
|
+
* Set the pinned_at timestamp and trust_state='pinned' for a contact (TOFU).
|
|
650
|
+
*/
|
|
651
|
+
setPinnedAt(address: string): void;
|
|
652
|
+
/**
|
|
653
|
+
* Remove a contact by address. Returns true if a contact was deleted.
|
|
654
|
+
*/
|
|
655
|
+
removeContact(address: string): boolean;
|
|
656
|
+
/**
|
|
657
|
+
* Return true if the address has trust_state 'trusted', 'verified', or 'pinned'.
|
|
658
|
+
*
|
|
659
|
+
* Convenience helper for inbound message filtering (CARD-05, TOFU-02).
|
|
660
|
+
*/
|
|
661
|
+
isTrustedOrVerified(address: string): boolean;
|
|
662
|
+
/**
|
|
663
|
+
* Block an address or domain pattern (e.g., '*::evil.com').
|
|
664
|
+
*/
|
|
665
|
+
addBlock(pattern: string): void;
|
|
666
|
+
/**
|
|
667
|
+
* Remove a block pattern.
|
|
668
|
+
*/
|
|
669
|
+
removeBlock(pattern: string): void;
|
|
670
|
+
/**
|
|
671
|
+
* Return all blocked patterns with their timestamps.
|
|
672
|
+
*/
|
|
673
|
+
listBlocked(): BlockedEntry[];
|
|
674
|
+
private _cacheBlockPattern;
|
|
675
|
+
private _uncacheBlockPattern;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* First-contact handshake flow manager (HAND-01 through HAND-04).
|
|
680
|
+
*
|
|
681
|
+
* Handles the three-phase handshake:
|
|
682
|
+
* 1. HANDSHAKE_REQUEST: sender sends contact card (encrypted with SealedBox)
|
|
683
|
+
* 2. HANDSHAKE_ACCEPT: recipient stores contact and sends accept back
|
|
684
|
+
* 3. HANDSHAKE_DENY: recipient rejects the handshake
|
|
685
|
+
*
|
|
686
|
+
* The HandshakeManager works with the ContactBook to persist trust decisions.
|
|
687
|
+
*/
|
|
688
|
+
|
|
689
|
+
interface AgentLike {
|
|
690
|
+
readonly address: string;
|
|
691
|
+
readonly _config: {
|
|
692
|
+
readonly displayName: string;
|
|
693
|
+
readonly relayWsUrl: string;
|
|
694
|
+
};
|
|
695
|
+
readonly _keyManager: {
|
|
696
|
+
readonly signingKey: Uint8Array;
|
|
697
|
+
};
|
|
698
|
+
readonly _transport: {
|
|
699
|
+
send(envelope: Record<string, unknown>): Promise<void>;
|
|
700
|
+
} | null;
|
|
701
|
+
}
|
|
702
|
+
declare class HandshakeManager {
|
|
703
|
+
private _contactBook;
|
|
704
|
+
readonly _trustPolicy: string;
|
|
705
|
+
constructor(contactBook: ContactBook, trustPolicy: string);
|
|
706
|
+
/**
|
|
707
|
+
* Create a handshake request envelope with embedded contact card.
|
|
708
|
+
*
|
|
709
|
+
* The payload is the JSON-serialized contact card, encrypted with
|
|
710
|
+
* SealedBox (anonymous encryption) to the recipient.
|
|
711
|
+
*
|
|
712
|
+
* Returns wire-format dict ready to send via transport.
|
|
713
|
+
*/
|
|
714
|
+
createHandshakeRequest(agent: AgentLike, toAddress: string, recipientVk: VerifyKey): Promise<Record<string, unknown>>;
|
|
715
|
+
/**
|
|
716
|
+
* Handle an inbound handshake message.
|
|
717
|
+
*
|
|
718
|
+
* Returns null -- handshake messages are not user-visible.
|
|
719
|
+
*/
|
|
720
|
+
handleInbound(agent: AgentLike, envelope: MessageEnvelope, senderVk: VerifyKey): Promise<ReceivedMessage | null>;
|
|
721
|
+
/**
|
|
722
|
+
* Process a handshake.request: decrypt contact card, apply trust policy.
|
|
723
|
+
*/
|
|
724
|
+
private _handleRequest;
|
|
725
|
+
/**
|
|
726
|
+
* Process a handshake.accept: store the sender as pinned (TOFU).
|
|
727
|
+
*
|
|
728
|
+
* Only honoured when:
|
|
729
|
+
* 1. we previously sent this address a handshake.request
|
|
730
|
+
* (trust_state === "handshake-sent"); anything else is an unsolicited
|
|
731
|
+
* accept and is dropped. This also means an accept can never
|
|
732
|
+
* downgrade a trusted/verified contact to pinned;
|
|
733
|
+
* 2. the payload decrypts with our key and the sender's key, and parses
|
|
734
|
+
* as JSON. Otherwise it is dropped without pinning;
|
|
735
|
+
* 3. an embedded contact_card, if present, is well-formed, correctly
|
|
736
|
+
* self-signed, and bound to the envelope sender (throws otherwise).
|
|
737
|
+
*/
|
|
738
|
+
private _handleAccept;
|
|
739
|
+
/**
|
|
740
|
+
* Send a handshake.accept envelope back to the requester.
|
|
741
|
+
*/
|
|
742
|
+
_sendAccept(agent: AgentLike, toAddress: string, recipientVk: VerifyKey): Promise<void>;
|
|
743
|
+
/**
|
|
744
|
+
* Send a handshake.deny envelope to the requester.
|
|
745
|
+
*/
|
|
746
|
+
_sendDeny(agent: AgentLike, toAddress: string, recipientVk: VerifyKey): Promise<void>;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Abstract transport interface for relay communication.
|
|
751
|
+
*
|
|
752
|
+
* Two implementations:
|
|
753
|
+
* - HTTPTransport: stateless polling via REST API
|
|
754
|
+
* - WebSocketTransport: persistent real-time connection
|
|
755
|
+
*/
|
|
756
|
+
declare abstract class TransportBase {
|
|
757
|
+
/** Establish connection to the relay. */
|
|
758
|
+
abstract connect(): Promise<void>;
|
|
759
|
+
/** Close the connection. */
|
|
760
|
+
abstract disconnect(): Promise<void>;
|
|
761
|
+
/** Send a message envelope to the relay. */
|
|
762
|
+
abstract send(envelope: Record<string, unknown>): Promise<void>;
|
|
763
|
+
/** Retrieve pending messages from the relay. */
|
|
764
|
+
abstract receive(limit?: number): Promise<Record<string, unknown>[]>;
|
|
765
|
+
/**
|
|
766
|
+
* Start listening for real-time messages.
|
|
767
|
+
*
|
|
768
|
+
* For WebSocket: registers a callback for push delivery.
|
|
769
|
+
* For HTTP: not supported (throws Error).
|
|
770
|
+
*/
|
|
771
|
+
abstract listen(callback: (msg: Record<string, unknown>) => Promise<void>): Promise<void>;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Inbound (from address, message id) pairs kept in a SQLite file.
|
|
776
|
+
*
|
|
777
|
+
* Opens itself on first use, so an Agent driven without connect() still has
|
|
778
|
+
* replay protection. Safe to share between processes: the insert is the
|
|
779
|
+
* check (INSERT OR IGNORE), in WAL mode.
|
|
780
|
+
*/
|
|
781
|
+
declare class PersistentReplayCache {
|
|
782
|
+
readonly capacity: number;
|
|
783
|
+
private readonly _path;
|
|
784
|
+
private readonly _now;
|
|
785
|
+
private _db;
|
|
786
|
+
private _opening;
|
|
787
|
+
private _count;
|
|
788
|
+
private _lastPurge;
|
|
789
|
+
private _createdAt;
|
|
790
|
+
private readonly _overflow;
|
|
791
|
+
/**
|
|
792
|
+
* @param path - The SQLite file
|
|
793
|
+
* @param options.now - The clock, in ms since the epoch
|
|
794
|
+
*/
|
|
795
|
+
constructor(path: string, options?: {
|
|
796
|
+
capacity?: number;
|
|
797
|
+
now?: () => number;
|
|
798
|
+
});
|
|
799
|
+
/** Open the file now, rather than on first use. */
|
|
800
|
+
open(): Promise<void>;
|
|
801
|
+
/**
|
|
802
|
+
* Return true if the envelope must be dropped as a replay. Otherwise record
|
|
803
|
+
* the pair and return false.
|
|
804
|
+
*
|
|
805
|
+
* @param signedAt - The envelope's signed time, in ms since the epoch. It
|
|
806
|
+
* matters only when the cache is full.
|
|
807
|
+
*/
|
|
808
|
+
seenOrRecord(fromAddress: string, messageId: string, signedAt: number): Promise<boolean>;
|
|
809
|
+
/** When this cache file was first created, in ms since the epoch. */
|
|
810
|
+
createdAt(): Promise<number>;
|
|
811
|
+
/** True if an envelope signed at `signedAt` (ms) predates this cache. */
|
|
812
|
+
signedBeforeCache(signedAt: number): Promise<boolean>;
|
|
813
|
+
/** Number of ids currently held on disk. */
|
|
814
|
+
count(): Promise<number>;
|
|
815
|
+
/** Close the file; the next call opens it again. */
|
|
816
|
+
close(): Promise<void>;
|
|
817
|
+
private _open;
|
|
818
|
+
private _connect;
|
|
819
|
+
private _purge;
|
|
820
|
+
private _countRows;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Agent class -- the primary SDK interface (SDK-01).
|
|
825
|
+
*
|
|
826
|
+
* Provides send(), inbox(), handshake flow, contact management,
|
|
827
|
+
* trust policies, approve/deny/block/unblock.
|
|
828
|
+
*
|
|
829
|
+
* Matches Python agent.py API surface.
|
|
830
|
+
*/
|
|
831
|
+
|
|
832
|
+
declare class Agent {
|
|
833
|
+
/** @internal */
|
|
834
|
+
readonly _config: SDKConfig;
|
|
835
|
+
/** @internal */
|
|
836
|
+
readonly _keyManager: KeyManager;
|
|
837
|
+
/** @internal */
|
|
838
|
+
_transport: TransportBase | null;
|
|
839
|
+
private _resolver;
|
|
840
|
+
private _address;
|
|
841
|
+
private _token;
|
|
842
|
+
private _connected;
|
|
843
|
+
private _autoRegister;
|
|
844
|
+
/** @internal */
|
|
845
|
+
readonly _contactBook: ContactBook;
|
|
846
|
+
/** @internal */
|
|
847
|
+
readonly _handshake: HandshakeManager;
|
|
848
|
+
/**
|
|
849
|
+
* @internal Wall clock in ms since the epoch, used by the inbound
|
|
850
|
+
* freshness/expiry checks and the replay cache. Replaceable in tests.
|
|
851
|
+
*/
|
|
852
|
+
_clock: () => number;
|
|
853
|
+
/**
|
|
854
|
+
* @internal Inbound replay cache, keyed by (from address, message id).
|
|
855
|
+
* Kept on disk under the data dir, so it outlives restarts and is shared by
|
|
856
|
+
* every process using it; capacity from UAM_REPLAY_CACHE_CAPACITY.
|
|
857
|
+
*/
|
|
858
|
+
readonly _replayCache: PersistentReplayCache;
|
|
859
|
+
/** Whether our relay has said it writes relay_meta. */
|
|
860
|
+
private _relayStamps;
|
|
861
|
+
/** Until when, by _monotonic, to take the relay's last no as still true. */
|
|
862
|
+
private _relayNoUntil;
|
|
863
|
+
/** @internal Monotonic clock in ms. Replaceable in tests. */
|
|
864
|
+
_monotonic: () => number;
|
|
865
|
+
/**
|
|
866
|
+
* Create an Agent. No I/O happens here -- call connect() to initialize.
|
|
867
|
+
*/
|
|
868
|
+
constructor(name: string, options?: {
|
|
869
|
+
relay?: string | null;
|
|
870
|
+
domain?: string | null;
|
|
871
|
+
keyDir?: string | null;
|
|
872
|
+
autoRegister?: boolean;
|
|
873
|
+
displayName?: string | null;
|
|
874
|
+
transport?: string;
|
|
875
|
+
trustPolicy?: string;
|
|
876
|
+
});
|
|
877
|
+
/** The agent's full UAM address (e.g., 'myagent::youam.network'). */
|
|
878
|
+
get address(): string;
|
|
879
|
+
/** UAM SDK version. */
|
|
880
|
+
get version(): string;
|
|
881
|
+
/** The agent's public key (base64-encoded Ed25519 verify key). */
|
|
882
|
+
get publicKey(): string;
|
|
883
|
+
/** Whether the agent has completed connection setup. */
|
|
884
|
+
get isConnected(): boolean;
|
|
885
|
+
/**
|
|
886
|
+
* Generate and return a signed contact card for this agent.
|
|
887
|
+
*/
|
|
888
|
+
contactCard(): Promise<Record<string, unknown>>;
|
|
889
|
+
/**
|
|
890
|
+
* Initialize the agent: load/generate keys, register, connect transport.
|
|
891
|
+
*
|
|
892
|
+
* Idempotent -- calling twice is safe.
|
|
893
|
+
*/
|
|
894
|
+
connect(): Promise<void>;
|
|
895
|
+
/**
|
|
896
|
+
* Disconnect the transport and clean up resources.
|
|
897
|
+
*/
|
|
898
|
+
close(): Promise<void>;
|
|
899
|
+
/**
|
|
900
|
+
* Send an encrypted, signed message. Returns the message_id.
|
|
901
|
+
*/
|
|
902
|
+
send(toAddress: string, message: string, options?: {
|
|
903
|
+
threadId?: string;
|
|
904
|
+
attachments?: Array<Record<string, unknown>>;
|
|
905
|
+
}): Promise<string>;
|
|
906
|
+
/**
|
|
907
|
+
* Retrieve, decrypt, and verify pending messages.
|
|
908
|
+
*
|
|
909
|
+
* Returns a list of ReceivedMessage data objects.
|
|
910
|
+
* Drops messages with invalid signatures or decryption errors. A message
|
|
911
|
+
* that fails processing for any other reason (malformed envelope, bad
|
|
912
|
+
* handshake payload, ...) is logged and dropped; it never aborts the
|
|
913
|
+
* rest of the batch.
|
|
914
|
+
*/
|
|
915
|
+
inbox(limit?: number): Promise<ReceivedMessage[]>;
|
|
916
|
+
/** List pending handshake requests. */
|
|
917
|
+
pending(): Promise<Array<{
|
|
918
|
+
address: string;
|
|
919
|
+
contactCard: string;
|
|
920
|
+
receivedAt: string;
|
|
921
|
+
}>>;
|
|
922
|
+
/**
|
|
923
|
+
* Approve a pending handshake request.
|
|
924
|
+
*
|
|
925
|
+
* Adds the sender to contacts with trust_source='explicit-approval'
|
|
926
|
+
* and sends handshake.accept back.
|
|
927
|
+
*/
|
|
928
|
+
approve(address: string): Promise<void>;
|
|
929
|
+
/**
|
|
930
|
+
* Deny a pending handshake request.
|
|
931
|
+
*
|
|
932
|
+
* Removes the entry from pending and sends handshake.deny.
|
|
933
|
+
*/
|
|
934
|
+
deny(address: string): Promise<void>;
|
|
935
|
+
/**
|
|
936
|
+
* Block an address or domain pattern (HAND-04).
|
|
937
|
+
*/
|
|
938
|
+
block(pattern: string): Promise<void>;
|
|
939
|
+
/**
|
|
940
|
+
* Remove a block pattern (HAND-04).
|
|
941
|
+
*/
|
|
942
|
+
unblock(pattern: string): Promise<void>;
|
|
943
|
+
/**
|
|
944
|
+
* Verify domain ownership via relay endpoint (DNS-02).
|
|
945
|
+
*
|
|
946
|
+
* Polls `POST /api/v1/verify-domain` until the relay confirms
|
|
947
|
+
* DNS TXT (or HTTPS fallback) verification succeeds, or timeout
|
|
948
|
+
* elapses.
|
|
949
|
+
*
|
|
950
|
+
* Returns `true` if verified, `false` on timeout.
|
|
951
|
+
*/
|
|
952
|
+
verifyDomain(domain: string, options?: {
|
|
953
|
+
timeout?: number;
|
|
954
|
+
pollInterval?: number;
|
|
955
|
+
}): Promise<boolean>;
|
|
956
|
+
/** Multi-relay failover (CARD-06). */
|
|
957
|
+
private _getRelayUrls;
|
|
958
|
+
/**
|
|
959
|
+
* Try sending wire envelope to each relay URL in order (CARD-06).
|
|
960
|
+
*
|
|
961
|
+
* Uses transient fetch POST requests so the envelope can be delivered
|
|
962
|
+
* to any relay that hosts the recipient.
|
|
963
|
+
*/
|
|
964
|
+
private _trySendWithFailover;
|
|
965
|
+
private _ensureConnected;
|
|
966
|
+
/**
|
|
967
|
+
* Resolve a recipient's public key, checking contact book first.
|
|
968
|
+
*
|
|
969
|
+
* Pure lookup: never writes to the contact book. The contact row is
|
|
970
|
+
* created by _initiateHandshake (as "handshake-sent") once the request
|
|
971
|
+
* has gone out, giving the lifecycle no row -> handshake-sent -> pinned.
|
|
972
|
+
*
|
|
973
|
+
* TOFU-01: Known contacts always use stored key (zero network I/O).
|
|
974
|
+
* TOFU-03: Race-condition double-check after network resolve.
|
|
975
|
+
* Raises KeyPinningError if a pinned contact's key doesn't match.
|
|
976
|
+
*/
|
|
977
|
+
private _resolvePublicKey;
|
|
978
|
+
private _initiateHandshake;
|
|
979
|
+
/**
|
|
980
|
+
* Process a single inbound envelope: verify, decrypt, handle handshakes.
|
|
981
|
+
*
|
|
982
|
+
* Returns null for invalid messages or handshake protocol messages.
|
|
983
|
+
*/
|
|
984
|
+
private _processInbound;
|
|
985
|
+
/**
|
|
986
|
+
* Recipient, freshness, expiry and replay checks for a signature-verified
|
|
987
|
+
* inbound envelope. Returns false (and logs) when it must be dropped.
|
|
988
|
+
* Matches the Python SDK's _process_inbound checks.
|
|
989
|
+
*/
|
|
990
|
+
private _passesInboundChecks;
|
|
991
|
+
/** True if a message too old to be live is stored mail, not a replay. */
|
|
992
|
+
private _wasStored;
|
|
993
|
+
/**
|
|
994
|
+
* True if our relay writes relay_meta, so a sender can't have.
|
|
995
|
+
*
|
|
996
|
+
* Asked only when a stale message carries a stamp. A yes is remembered for
|
|
997
|
+
* good, a no for RELAY_NO_MEMORY_MS: a relay can be upgraded while this
|
|
998
|
+
* agent runs. A failed lookup isn't remembered, because the relay has
|
|
999
|
+
* already marked this mail delivered and the next message should get a
|
|
1000
|
+
* real answer.
|
|
1001
|
+
*/
|
|
1002
|
+
private _relayStampsMail;
|
|
1003
|
+
/** The relay's features; a body that doesn't list any is an empty list. */
|
|
1004
|
+
private _fetchRelayFeatures;
|
|
1005
|
+
/**
|
|
1006
|
+
* Sweep expired pending handshakes and send receipt.failed (HAND-03).
|
|
1007
|
+
*/
|
|
1008
|
+
private _sweepExpiredHandshakes;
|
|
1009
|
+
/**
|
|
1010
|
+
* Send receipt.read back to the sender (RCPT-01).
|
|
1011
|
+
*
|
|
1012
|
+
* Fire-and-forget: errors are never propagated.
|
|
1013
|
+
* Anti-loop guard: receipts, handshakes, and session messages are skipped.
|
|
1014
|
+
*/
|
|
1015
|
+
private _sendReadReceipt;
|
|
1016
|
+
/**
|
|
1017
|
+
* Register with the relay server.
|
|
1018
|
+
*
|
|
1019
|
+
* Calls POST /api/v1/register with agent name and public key. If the
|
|
1020
|
+
* address exists (409), repeats it with a proof that this agent holds the
|
|
1021
|
+
* key. Stores the returned API key on disk.
|
|
1022
|
+
*/
|
|
1023
|
+
private _registerWithRelay;
|
|
1024
|
+
/** This agent's address on the relay, for signing a proof. */
|
|
1025
|
+
private _addressOnRelay;
|
|
1026
|
+
/**
|
|
1027
|
+
* True if the relay's URL fits `domain`, so a proof can be signed for it.
|
|
1028
|
+
*
|
|
1029
|
+
* A proof names an address on one relay, so it is only signed for a domain
|
|
1030
|
+
* this URL belongs to. A relay on this machine may use any domain.
|
|
1031
|
+
*/
|
|
1032
|
+
private _relayMayUse;
|
|
1033
|
+
private _postRegistration;
|
|
1034
|
+
/**
|
|
1035
|
+
* Sign a statement that this agent holds the key for `address`.
|
|
1036
|
+
*
|
|
1037
|
+
* The signed string must stay byte-identical to the Python SDK's.
|
|
1038
|
+
*/
|
|
1039
|
+
private _registrationProof;
|
|
1040
|
+
private _ownershipNotProven;
|
|
1041
|
+
/** Remember `address` for this relay, if the key folder allows it. */
|
|
1042
|
+
private _saveAddress;
|
|
1043
|
+
/** Saving is best effort: a key folder we can't write doesn't stop us. */
|
|
1044
|
+
private _tryToSave;
|
|
1045
|
+
/** Take `address` as ours; its domain is this relay's real domain. */
|
|
1046
|
+
private _useAddress;
|
|
1047
|
+
/**
|
|
1048
|
+
* The address the stored token belongs to.
|
|
1049
|
+
*
|
|
1050
|
+
* An explicit domain settles it. Otherwise the relay chooses the domain,
|
|
1051
|
+
* and it need not be the relay's hostname: use the address saved for this
|
|
1052
|
+
* relay, or ask the relay once and keep the answer.
|
|
1053
|
+
*/
|
|
1054
|
+
private _storedAddress;
|
|
1055
|
+
/** Ask the relay for its domain; null if it cannot say. */
|
|
1056
|
+
private _fetchRelayDomain;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Receiver-side webhook signature verification (HOOK-03).
|
|
1061
|
+
*
|
|
1062
|
+
* Small utility for webhook receivers to verify that incoming webhook
|
|
1063
|
+
* payloads were signed by the UAM relay. Uses HMAC-SHA256 with the
|
|
1064
|
+
* agent's webhook secret, which the relay returns when the webhook is set
|
|
1065
|
+
* (PUT /api/v1/agents/{address}/webhook). Setting the same URL again
|
|
1066
|
+
* returns it again.
|
|
1067
|
+
*
|
|
1068
|
+
* Matches Python webhook_verify.py API surface.
|
|
1069
|
+
*
|
|
1070
|
+
* Usage:
|
|
1071
|
+
*
|
|
1072
|
+
* import { verifyWebhookSignature } from "@youam/sdk";
|
|
1073
|
+
*
|
|
1074
|
+
* const payload = Buffer.from(requestBody);
|
|
1075
|
+
* const signature = request.headers["x-uam-signature"];
|
|
1076
|
+
* const secret = "your-webhook-secret";
|
|
1077
|
+
*
|
|
1078
|
+
* if (verifyWebhookSignature(payload, signature, secret)) {
|
|
1079
|
+
* // payload is authentic
|
|
1080
|
+
* }
|
|
1081
|
+
*/
|
|
1082
|
+
/**
|
|
1083
|
+
* Verify an HMAC-SHA256 webhook signature.
|
|
1084
|
+
*
|
|
1085
|
+
* @param payload - Raw request body bytes
|
|
1086
|
+
* @param signatureHeader - "sha256=<hex>" format (X-UAM-Signature header)
|
|
1087
|
+
* @param secret - The agent's webhook secret
|
|
1088
|
+
* @returns `true` if valid, `false` otherwise.
|
|
1089
|
+
*
|
|
1090
|
+
* Uses `crypto.timingSafeEqual()` for constant-time comparison
|
|
1091
|
+
* to prevent timing attacks.
|
|
1092
|
+
*/
|
|
1093
|
+
declare function verifyWebhookSignature(payload: Buffer | Uint8Array, signatureHeader: string, secret: string): boolean;
|
|
1094
|
+
|
|
1095
|
+
export { Agent, ContactBook, type ReceivedMessage, index as protocol, verifyWebhookSignature };
|