@bcts/envelope 1.0.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 (44) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +23 -0
  3. package/dist/index.cjs +2646 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +978 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +978 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.iife.js +2644 -0
  10. package/dist/index.iife.js.map +1 -0
  11. package/dist/index.mjs +2552 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +85 -0
  14. package/src/base/assertion.ts +179 -0
  15. package/src/base/assertions.ts +304 -0
  16. package/src/base/cbor.ts +122 -0
  17. package/src/base/digest.ts +204 -0
  18. package/src/base/elide.ts +526 -0
  19. package/src/base/envelope-decodable.ts +229 -0
  20. package/src/base/envelope-encodable.ts +71 -0
  21. package/src/base/envelope.ts +790 -0
  22. package/src/base/error.ts +421 -0
  23. package/src/base/index.ts +56 -0
  24. package/src/base/leaf.ts +226 -0
  25. package/src/base/queries.ts +374 -0
  26. package/src/base/walk.ts +241 -0
  27. package/src/base/wrap.ts +72 -0
  28. package/src/extension/attachment.ts +369 -0
  29. package/src/extension/compress.ts +293 -0
  30. package/src/extension/encrypt.ts +379 -0
  31. package/src/extension/expression.ts +404 -0
  32. package/src/extension/index.ts +72 -0
  33. package/src/extension/proof.ts +276 -0
  34. package/src/extension/recipient.ts +557 -0
  35. package/src/extension/salt.ts +223 -0
  36. package/src/extension/signature.ts +463 -0
  37. package/src/extension/types.ts +222 -0
  38. package/src/format/diagnostic.ts +116 -0
  39. package/src/format/hex.ts +25 -0
  40. package/src/format/index.ts +13 -0
  41. package/src/format/tree.ts +168 -0
  42. package/src/index.ts +32 -0
  43. package/src/utils/index.ts +8 -0
  44. package/src/utils/string.ts +48 -0
@@ -0,0 +1,557 @@
1
+ import { Envelope } from "../base/envelope";
2
+ import { EnvelopeError } from "../base/error";
3
+ import { SymmetricKey } from "./encrypt";
4
+ import {
5
+ x25519NewPrivateKeyUsing,
6
+ x25519PublicKeyFromPrivateKey,
7
+ x25519SharedKey,
8
+ aeadChaCha20Poly1305EncryptWithAad,
9
+ aeadChaCha20Poly1305DecryptWithAad,
10
+ hkdfHmacSha256,
11
+ X25519_PUBLIC_KEY_SIZE,
12
+ X25519_PRIVATE_KEY_SIZE,
13
+ SYMMETRIC_NONCE_SIZE,
14
+ SYMMETRIC_AUTH_SIZE,
15
+ } from "@bcts/crypto";
16
+ import { SecureRandomNumberGenerator, rngRandomData } from "@bcts/rand";
17
+
18
+ /// Extension for public-key encryption to specific recipients.
19
+ ///
20
+ /// This module implements multi-recipient public key encryption using the
21
+ /// Gordian Envelope sealed message pattern. Each recipient gets a sealed
22
+ /// message containing an encrypted content key, allowing multiple recipients
23
+ /// to decrypt the same envelope using their private keys.
24
+ ///
25
+ /// ## How it works:
26
+ ///
27
+ /// 1. A random symmetric content key is generated
28
+ /// 2. The envelope's subject is encrypted with the content key
29
+ /// 3. The content key is encrypted to each recipient's public key using X25519 key agreement
30
+ /// 4. Each encrypted content key is added as a `hasRecipient` assertion
31
+ ///
32
+ /// ## Sealed Box Security:
33
+ ///
34
+ /// Sealed boxes use ephemeral X25519 key pairs. The ephemeral private key
35
+ /// is discarded after encryption, ensuring that even the sender cannot
36
+ /// decrypt the message later. Recipients must try each sealed message
37
+ /// until one decrypts successfully.
38
+ ///
39
+ /// Uses @bcts/crypto functions for X25519 and ChaCha20-Poly1305.
40
+ ///
41
+ /// @example
42
+ /// ```typescript
43
+ /// // Generate recipient keys
44
+ /// const alice = PrivateKeyBase.generate();
45
+ /// const bob = PrivateKeyBase.generate();
46
+ ///
47
+ /// // Encrypt to multiple recipients
48
+ /// const envelope = Envelope.new("Secret message")
49
+ /// .encryptSubjectToRecipients([alice.publicKeys(), bob.publicKeys()]);
50
+ ///
51
+ /// // Alice decrypts
52
+ /// const aliceDecrypted = envelope.decryptSubjectToRecipient(alice);
53
+ ///
54
+ /// // Bob decrypts
55
+ /// const bobDecrypted = envelope.decryptSubjectToRecipient(bob);
56
+ /// ```
57
+
58
+ /// Predicate constant for recipient assertions
59
+ export const HAS_RECIPIENT = "hasRecipient";
60
+
61
+ /// Represents an X25519 public key for encryption
62
+ export class PublicKeyBase {
63
+ readonly #publicKey: Uint8Array;
64
+
65
+ constructor(publicKey: Uint8Array) {
66
+ if (publicKey.length !== X25519_PUBLIC_KEY_SIZE) {
67
+ throw new Error(`Public key must be ${X25519_PUBLIC_KEY_SIZE} bytes`);
68
+ }
69
+ this.#publicKey = publicKey;
70
+ }
71
+
72
+ /// Returns the raw public key bytes
73
+ data(): Uint8Array {
74
+ return this.#publicKey;
75
+ }
76
+
77
+ /// Returns the public key as a hex string
78
+ hex(): string {
79
+ return Array.from(this.#publicKey)
80
+ .map((b) => b.toString(16).padStart(2, "0"))
81
+ .join("");
82
+ }
83
+
84
+ /// Creates a public key from hex string
85
+ static fromHex(hex: string): PublicKeyBase {
86
+ if (hex.length !== 64) {
87
+ throw new Error("Hex string must be 64 characters (32 bytes)");
88
+ }
89
+ const bytes = new Uint8Array(32);
90
+ for (let i = 0; i < 32; i++) {
91
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
92
+ }
93
+ return new PublicKeyBase(bytes);
94
+ }
95
+ }
96
+
97
+ /// Represents an X25519 private key for decryption
98
+ export class PrivateKeyBase {
99
+ readonly #privateKey: Uint8Array;
100
+ readonly #publicKey: PublicKeyBase;
101
+
102
+ private constructor(privateKey: Uint8Array, publicKey: Uint8Array) {
103
+ this.#privateKey = privateKey;
104
+ this.#publicKey = new PublicKeyBase(publicKey);
105
+ }
106
+
107
+ /// Generates a new random X25519 key pair
108
+ static generate(): PrivateKeyBase {
109
+ const rng = new SecureRandomNumberGenerator();
110
+ const privateKey = x25519NewPrivateKeyUsing(rng);
111
+ const publicKey = x25519PublicKeyFromPrivateKey(privateKey);
112
+ return new PrivateKeyBase(privateKey, publicKey);
113
+ }
114
+
115
+ /// Creates a private key from existing bytes
116
+ static fromBytes(privateKey: Uint8Array, publicKey: Uint8Array): PrivateKeyBase {
117
+ if (privateKey.length !== X25519_PRIVATE_KEY_SIZE) {
118
+ throw new Error(`Private key must be ${X25519_PRIVATE_KEY_SIZE} bytes`);
119
+ }
120
+ if (publicKey.length !== X25519_PUBLIC_KEY_SIZE) {
121
+ throw new Error(`Public key must be ${X25519_PUBLIC_KEY_SIZE} bytes`);
122
+ }
123
+ return new PrivateKeyBase(privateKey, publicKey);
124
+ }
125
+
126
+ /// Creates a private key from hex strings
127
+ static fromHex(privateHex: string, publicHex: string): PrivateKeyBase {
128
+ const privateBytes = new Uint8Array(32);
129
+ const publicBytes = new Uint8Array(32);
130
+
131
+ for (let i = 0; i < 32; i++) {
132
+ privateBytes[i] = parseInt(privateHex.substr(i * 2, 2), 16);
133
+ publicBytes[i] = parseInt(publicHex.substr(i * 2, 2), 16);
134
+ }
135
+
136
+ return new PrivateKeyBase(privateBytes, publicBytes);
137
+ }
138
+
139
+ /// Returns the public key
140
+ publicKeys(): PublicKeyBase {
141
+ return this.#publicKey;
142
+ }
143
+
144
+ /// Returns the raw private key bytes
145
+ data(): Uint8Array {
146
+ return this.#privateKey;
147
+ }
148
+
149
+ /// Returns the private key as hex string
150
+ hex(): string {
151
+ return Array.from(this.#privateKey)
152
+ .map((b) => b.toString(16).padStart(2, "0"))
153
+ .join("");
154
+ }
155
+
156
+ /// Decrypts a sealed message to get the content key
157
+ unseal(sealedMessage: SealedMessage): SymmetricKey {
158
+ try {
159
+ const decrypted = sealedMessage.decrypt(this.#privateKey, this.#publicKey.data());
160
+ return SymmetricKey.from(decrypted);
161
+ } catch (_error) {
162
+ throw EnvelopeError.general("Failed to unseal message: not a recipient");
163
+ }
164
+ }
165
+ }
166
+
167
+ /// Represents a sealed message - encrypted content key for a recipient
168
+ /// Uses X25519 key agreement + ChaCha20-Poly1305 AEAD
169
+ ///
170
+ /// Format: ephemeral_public_key (32 bytes) || nonce (12 bytes) || ciphertext || auth_tag (16 bytes)
171
+ export class SealedMessage {
172
+ readonly #data: Uint8Array;
173
+
174
+ constructor(data: Uint8Array) {
175
+ this.#data = data;
176
+ }
177
+
178
+ /// Creates a sealed message by encrypting a symmetric key to a recipient's public key
179
+ /// Uses X25519 ECDH for key agreement and ChaCha20-Poly1305 for encryption
180
+ static seal(contentKey: SymmetricKey, recipientPublicKey: PublicKeyBase): SealedMessage {
181
+ const rng = new SecureRandomNumberGenerator();
182
+
183
+ // Generate ephemeral key pair
184
+ const ephemeralPrivate = x25519NewPrivateKeyUsing(rng);
185
+ const ephemeralPublic = x25519PublicKeyFromPrivateKey(ephemeralPrivate);
186
+
187
+ // Compute shared secret using X25519 ECDH
188
+ const sharedSecret = x25519SharedKey(ephemeralPrivate, recipientPublicKey.data());
189
+
190
+ // Derive encryption key from shared secret using HKDF
191
+ const salt = new TextEncoder().encode("sealed_message");
192
+ const encryptionKey = hkdfHmacSha256(sharedSecret, salt, 32);
193
+
194
+ // Generate random nonce
195
+ const nonce = rngRandomData(rng, SYMMETRIC_NONCE_SIZE);
196
+
197
+ // Encrypt content key using ChaCha20-Poly1305
198
+ const plaintext = contentKey.data();
199
+ const [ciphertext, authTag] = aeadChaCha20Poly1305EncryptWithAad(
200
+ plaintext,
201
+ encryptionKey,
202
+ nonce,
203
+ new Uint8Array(0), // No AAD for sealed box
204
+ );
205
+
206
+ // Format: ephemeral_public_key || nonce || ciphertext || auth_tag
207
+ const totalLength = ephemeralPublic.length + nonce.length + ciphertext.length + authTag.length;
208
+ const sealed = new Uint8Array(totalLength);
209
+ let offset = 0;
210
+
211
+ sealed.set(ephemeralPublic, offset);
212
+ offset += ephemeralPublic.length;
213
+
214
+ sealed.set(nonce, offset);
215
+ offset += nonce.length;
216
+
217
+ sealed.set(ciphertext, offset);
218
+ offset += ciphertext.length;
219
+
220
+ sealed.set(authTag, offset);
221
+
222
+ return new SealedMessage(sealed);
223
+ }
224
+
225
+ /// Decrypts this sealed message using recipient's private key
226
+ decrypt(recipientPrivate: Uint8Array, _recipientPublic: Uint8Array): Uint8Array {
227
+ // Parse sealed message format: ephemeral_public_key || nonce || ciphertext || auth_tag
228
+ const minLength = X25519_PUBLIC_KEY_SIZE + SYMMETRIC_NONCE_SIZE + SYMMETRIC_AUTH_SIZE;
229
+ if (this.#data.length < minLength) {
230
+ throw new Error("Sealed message too short");
231
+ }
232
+
233
+ let offset = 0;
234
+
235
+ // Extract ephemeral public key
236
+ const ephemeralPublic = this.#data.slice(offset, offset + X25519_PUBLIC_KEY_SIZE);
237
+ offset += X25519_PUBLIC_KEY_SIZE;
238
+
239
+ // Extract nonce
240
+ const nonce = this.#data.slice(offset, offset + SYMMETRIC_NONCE_SIZE);
241
+ offset += SYMMETRIC_NONCE_SIZE;
242
+
243
+ // Extract ciphertext and auth tag
244
+ const ciphertextAndTag = this.#data.slice(offset);
245
+ const ciphertext = ciphertextAndTag.slice(0, -SYMMETRIC_AUTH_SIZE);
246
+ const authTag = ciphertextAndTag.slice(-SYMMETRIC_AUTH_SIZE);
247
+
248
+ // Compute shared secret using X25519 ECDH
249
+ const sharedSecret = x25519SharedKey(recipientPrivate, ephemeralPublic);
250
+
251
+ // Derive decryption key from shared secret using HKDF
252
+ const salt = new TextEncoder().encode("sealed_message");
253
+ const decryptionKey = hkdfHmacSha256(sharedSecret, salt, 32);
254
+
255
+ // Decrypt using ChaCha20-Poly1305
256
+ const plaintext = aeadChaCha20Poly1305DecryptWithAad(
257
+ ciphertext,
258
+ decryptionKey,
259
+ nonce,
260
+ new Uint8Array(0), // No AAD for sealed box
261
+ authTag,
262
+ );
263
+
264
+ return plaintext;
265
+ }
266
+
267
+ /// Returns the raw sealed message bytes
268
+ data(): Uint8Array {
269
+ return this.#data;
270
+ }
271
+
272
+ /// Returns the sealed message as hex string
273
+ hex(): string {
274
+ return Array.from(this.#data)
275
+ .map((b) => b.toString(16).padStart(2, "0"))
276
+ .join("");
277
+ }
278
+
279
+ /// Creates a sealed message from hex string
280
+ static fromHex(hex: string): SealedMessage {
281
+ const bytes = new Uint8Array(hex.length / 2);
282
+ for (let i = 0; i < bytes.length; i++) {
283
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
284
+ }
285
+ return new SealedMessage(bytes);
286
+ }
287
+ }
288
+
289
+ declare module "../base/envelope" {
290
+ interface Envelope {
291
+ /// Encrypts the envelope's subject to a single recipient.
292
+ ///
293
+ /// Generates a random content key, encrypts the subject with it,
294
+ /// and adds a sealed message containing the content key encrypted
295
+ /// to the recipient's public key.
296
+ ///
297
+ /// @param recipientPublicKey - The recipient's public key
298
+ /// @returns A new envelope with encrypted subject and recipient assertion
299
+ ///
300
+ /// @example
301
+ /// ```typescript
302
+ /// const bob = PrivateKeyBase.generate();
303
+ /// const encrypted = envelope.encryptSubjectToRecipient(bob.publicKeys());
304
+ /// ```
305
+ encryptSubjectToRecipient(recipientPublicKey: PublicKeyBase): Envelope;
306
+
307
+ /// Encrypts the envelope's subject to multiple recipients.
308
+ ///
309
+ /// Each recipient gets their own sealed message containing the same
310
+ /// content key encrypted to their public key. Recipients must try
311
+ /// each sealed message until one decrypts successfully.
312
+ ///
313
+ /// @param recipients - Array of recipient public keys
314
+ /// @returns A new envelope with encrypted subject and multiple recipient assertions
315
+ ///
316
+ /// @example
317
+ /// ```typescript
318
+ /// const encrypted = envelope.encryptSubjectToRecipients([
319
+ /// alice.publicKeys(),
320
+ /// bob.publicKeys(),
321
+ /// charlie.publicKeys()
322
+ /// ]);
323
+ /// ```
324
+ encryptSubjectToRecipients(recipients: PublicKeyBase[]): Envelope;
325
+
326
+ /// Adds a recipient to an already encrypted envelope.
327
+ ///
328
+ /// The envelope must already be encrypted with a content key.
329
+ /// This method adds another recipient who can decrypt using the
330
+ /// same content key.
331
+ ///
332
+ /// @param recipientPublicKey - The new recipient's public key
333
+ /// @param contentKey - The symmetric key used to encrypt the subject
334
+ /// @returns A new envelope with an additional recipient assertion
335
+ ///
336
+ /// @example
337
+ /// ```typescript
338
+ /// const dave = PrivateKeyBase.generate();
339
+ /// const withDave = encrypted.addRecipient(dave.publicKeys(), contentKey);
340
+ /// ```
341
+ addRecipient(recipientPublicKey: PublicKeyBase, contentKey: SymmetricKey): Envelope;
342
+
343
+ /// Decrypts an envelope's subject using a recipient's private key.
344
+ ///
345
+ /// Tries each `hasRecipient` assertion until one unseals successfully,
346
+ /// then uses the recovered content key to decrypt the subject.
347
+ ///
348
+ /// @param recipientPrivateKey - The recipient's private key
349
+ /// @returns A new envelope with decrypted subject
350
+ /// @throws {EnvelopeError} If not a valid recipient or subject not encrypted
351
+ ///
352
+ /// @example
353
+ /// ```typescript
354
+ /// const decrypted = encrypted.decryptSubjectToRecipient(bob);
355
+ /// ```
356
+ decryptSubjectToRecipient(recipientPrivateKey: PrivateKeyBase): Envelope;
357
+
358
+ /// Convenience method to decrypt and unwrap an envelope.
359
+ ///
360
+ /// Useful when the entire envelope was wrapped and encrypted.
361
+ /// Decrypts the subject and then unwraps it.
362
+ ///
363
+ /// @param recipientPrivateKey - The recipient's private key
364
+ /// @returns The original decrypted and unwrapped envelope
365
+ /// @throws {EnvelopeError} If not a valid recipient, subject not encrypted, or cannot unwrap
366
+ ///
367
+ /// @example
368
+ /// ```typescript
369
+ /// const original = wrappedEncrypted.decryptToRecipient(alice);
370
+ /// ```
371
+ decryptToRecipient(recipientPrivateKey: PrivateKeyBase): Envelope;
372
+
373
+ /// Convenience method to encrypt an entire envelope to recipients.
374
+ ///
375
+ /// Wraps the envelope first, then encrypts it to the recipients.
376
+ /// This encrypts both the subject and all assertions.
377
+ ///
378
+ /// @param recipients - Array of recipient public keys
379
+ /// @returns A new encrypted envelope
380
+ ///
381
+ /// @example
382
+ /// ```typescript
383
+ /// const encrypted = envelope.encryptToRecipients([
384
+ /// alice.publicKeys(),
385
+ /// bob.publicKeys()
386
+ /// ]);
387
+ /// ```
388
+ encryptToRecipients(recipients: PublicKeyBase[]): Envelope;
389
+
390
+ /// Returns all sealed messages (recipient assertions) in the envelope.
391
+ ///
392
+ /// Each sealed message represents one recipient who can decrypt
393
+ /// the envelope's subject.
394
+ ///
395
+ /// @returns Array of sealed messages
396
+ ///
397
+ /// @example
398
+ /// ```typescript
399
+ /// const recipients = envelope.recipients();
400
+ /// console.log(`Envelope has ${recipients.length} recipients`);
401
+ /// ```
402
+ recipients(): SealedMessage[];
403
+ }
404
+ }
405
+
406
+ /// Implementation of encryptSubjectToRecipient()
407
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
408
+ if (Envelope?.prototype) {
409
+ Envelope.prototype.encryptSubjectToRecipient = function (
410
+ this: Envelope,
411
+ recipientPublicKey: PublicKeyBase,
412
+ ): Envelope {
413
+ // Generate a random content key
414
+ const contentKey = SymmetricKey.generate();
415
+
416
+ // Encrypt the subject with the content key
417
+ const encrypted = this.encryptSubject(contentKey);
418
+
419
+ // Add the recipient
420
+ return encrypted.addRecipient(recipientPublicKey, contentKey);
421
+ };
422
+
423
+ /// Implementation of encryptSubjectToRecipients()
424
+ Envelope.prototype.encryptSubjectToRecipients = function (
425
+ this: Envelope,
426
+ recipients: PublicKeyBase[],
427
+ ): Envelope {
428
+ if (recipients.length === 0) {
429
+ throw EnvelopeError.general("Must provide at least one recipient");
430
+ }
431
+
432
+ // Generate a random content key
433
+ const contentKey = SymmetricKey.generate();
434
+
435
+ // Encrypt the subject with the content key
436
+ let result = this.encryptSubject(contentKey);
437
+
438
+ // Add each recipient
439
+ for (const recipient of recipients) {
440
+ result = result.addRecipient(recipient, contentKey);
441
+ }
442
+
443
+ return result;
444
+ };
445
+
446
+ /// Implementation of addRecipient()
447
+ Envelope.prototype.addRecipient = function (
448
+ this: Envelope,
449
+ recipientPublicKey: PublicKeyBase,
450
+ contentKey: SymmetricKey,
451
+ ): Envelope {
452
+ // Create a sealed message with the content key
453
+ const sealedMessage = SealedMessage.seal(contentKey, recipientPublicKey);
454
+
455
+ // Store the sealed message as bytes in the assertion
456
+ return this.addAssertion(HAS_RECIPIENT, sealedMessage.data());
457
+ };
458
+
459
+ /// Implementation of decryptSubjectToRecipient()
460
+ Envelope.prototype.decryptSubjectToRecipient = function (
461
+ this: Envelope,
462
+ recipientPrivateKey: PrivateKeyBase,
463
+ ): Envelope {
464
+ // Check that the subject is encrypted
465
+ const subjectCase = this.subject().case();
466
+ if (subjectCase.type !== "encrypted") {
467
+ throw EnvelopeError.general("Subject is not encrypted");
468
+ }
469
+
470
+ // Get all recipient assertions
471
+ const recipientAssertions = this.assertions().filter((assertion) => {
472
+ try {
473
+ const predicate = assertion.subject().asPredicate();
474
+ if (predicate === undefined) return false;
475
+ return predicate.asText() === HAS_RECIPIENT;
476
+ } catch {
477
+ return false;
478
+ }
479
+ });
480
+
481
+ if (recipientAssertions.length === 0) {
482
+ throw EnvelopeError.general("No recipients found");
483
+ }
484
+
485
+ // Try each recipient assertion until one unseals successfully
486
+ let contentKey: SymmetricKey | null = null;
487
+
488
+ for (const assertion of recipientAssertions) {
489
+ try {
490
+ const obj = assertion.subject().asObject();
491
+ if (obj === undefined) continue;
492
+ const sealedData = obj.asByteString();
493
+ if (sealedData === undefined) continue;
494
+ const sealedMessage = new SealedMessage(sealedData);
495
+
496
+ // Try to unseal with our private key
497
+ contentKey = recipientPrivateKey.unseal(sealedMessage);
498
+ break; // Success!
499
+ } catch {
500
+ // Not for us, try next one
501
+ continue;
502
+ }
503
+ }
504
+
505
+ if (contentKey === null) {
506
+ throw EnvelopeError.general("Not a valid recipient");
507
+ }
508
+
509
+ // Decrypt the subject using the content key
510
+ return this.decryptSubject(contentKey);
511
+ };
512
+
513
+ /// Implementation of decryptToRecipient()
514
+ Envelope.prototype.decryptToRecipient = function (
515
+ this: Envelope,
516
+ recipientPrivateKey: PrivateKeyBase,
517
+ ): Envelope {
518
+ const decrypted = this.decryptSubjectToRecipient(recipientPrivateKey);
519
+ return decrypted.unwrap();
520
+ };
521
+
522
+ /// Implementation of encryptToRecipients()
523
+ Envelope.prototype.encryptToRecipients = function (
524
+ this: Envelope,
525
+ recipients: PublicKeyBase[],
526
+ ): Envelope {
527
+ return this.wrap().encryptSubjectToRecipients(recipients);
528
+ };
529
+
530
+ /// Implementation of recipients()
531
+ Envelope.prototype.recipients = function (this: Envelope): SealedMessage[] {
532
+ const recipientAssertions = this.assertions().filter((assertion) => {
533
+ try {
534
+ const predicate = assertion.subject().asPredicate();
535
+ if (predicate === undefined) return false;
536
+ return predicate.asText() === HAS_RECIPIENT;
537
+ } catch {
538
+ return false;
539
+ }
540
+ });
541
+
542
+ return recipientAssertions.map((assertion) => {
543
+ const obj = assertion.subject().asObject();
544
+ if (obj === undefined) {
545
+ throw EnvelopeError.general("Invalid recipient assertion");
546
+ }
547
+ const sealedData = obj.asByteString();
548
+ if (sealedData === undefined) {
549
+ throw EnvelopeError.general("Invalid recipient data");
550
+ }
551
+ return new SealedMessage(sealedData);
552
+ });
553
+ };
554
+ }
555
+
556
+ // Import side-effect to register prototype extensions
557
+ export {};