@bcts/envelope 1.0.0-alpha.10

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 +782 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +782 -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 +84 -0
  14. package/src/base/assertion.ts +179 -0
  15. package/src/base/assertions.ts +153 -0
  16. package/src/base/cbor.ts +122 -0
  17. package/src/base/digest.ts +204 -0
  18. package/src/base/elide.ts +390 -0
  19. package/src/base/envelope-decodable.ts +186 -0
  20. package/src/base/envelope-encodable.ts +71 -0
  21. package/src/base/envelope.ts +988 -0
  22. package/src/base/error.ts +421 -0
  23. package/src/base/index.ts +56 -0
  24. package/src/base/leaf.ts +147 -0
  25. package/src/base/queries.ts +244 -0
  26. package/src/base/walk.ts +215 -0
  27. package/src/base/wrap.ts +26 -0
  28. package/src/extension/attachment.ts +280 -0
  29. package/src/extension/compress.ts +176 -0
  30. package/src/extension/encrypt.ts +297 -0
  31. package/src/extension/expression.ts +404 -0
  32. package/src/extension/index.ts +72 -0
  33. package/src/extension/proof.ts +227 -0
  34. package/src/extension/recipient.ts +440 -0
  35. package/src/extension/salt.ts +114 -0
  36. package/src/extension/signature.ts +398 -0
  37. package/src/extension/types.ts +92 -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,988 @@
1
+ import { Digest, type DigestProvider } from "./digest";
2
+ import { Assertion } from "./assertion";
3
+ import { EnvelopeError } from "./error";
4
+ import type { EnvelopeEncodableValue } from "./envelope-encodable";
5
+ import { KnownValue } from "@bcts/known-values";
6
+ import type { Cbor, CborMap } from "@bcts/dcbor";
7
+ import {
8
+ cbor,
9
+ cborData,
10
+ toTaggedValue,
11
+ TAG_ENCODED_CBOR,
12
+ MajorType,
13
+ asByteString,
14
+ asCborArray,
15
+ asCborMap,
16
+ asTaggedValue,
17
+ tryExpectedTaggedValue,
18
+ } from "@bcts/dcbor";
19
+ import { ENVELOPE, LEAF, ENCRYPTED, COMPRESSED } from "@bcts/components";
20
+
21
+ // Type imports for extension method declarations
22
+ // These are imported as types only to avoid circular dependencies at runtime
23
+ import type { ObscureAction, ObscureType } from "./elide";
24
+ import type { Visitor } from "./walk";
25
+ import type {
26
+ SymmetricKey,
27
+ SealedMessage,
28
+ PublicKeyBase,
29
+ PrivateKeyBase,
30
+ Signer,
31
+ Verifier,
32
+ SignatureMetadata,
33
+ } from "../extension";
34
+
35
+ /// Import tag values from the tags registry
36
+ /// These match the Rust reference implementation in bc-tags-rust
37
+ const TAG_ENVELOPE = ENVELOPE.value;
38
+ const TAG_LEAF = LEAF.value;
39
+ const TAG_ENCRYPTED = ENCRYPTED.value;
40
+ const TAG_COMPRESSED = COMPRESSED.value;
41
+
42
+ /// The core structural variants of a Gordian Envelope.
43
+ ///
44
+ /// Each variant represents a different structural form that an
45
+ /// envelope can take, as defined in the Gordian Envelope IETF Internet Draft.
46
+ /// The different cases provide different capabilities and serve different
47
+ /// purposes in the envelope ecosystem.
48
+ ///
49
+ /// The `EnvelopeCase` is the internal representation of an envelope's
50
+ /// structure. While each case has unique properties, they all maintain a digest
51
+ /// that ensures the integrity of the envelope.
52
+ ///
53
+ /// It is advised to use the other Envelope APIs for most uses. Please see the
54
+ /// queries module for more information on how to interact with envelopes.
55
+ export type EnvelopeCase =
56
+ | {
57
+ type: "node";
58
+ /// The subject of the node
59
+ subject: Envelope;
60
+ /// The assertions attached to the subject
61
+ assertions: Envelope[];
62
+ /// The digest of the node
63
+ digest: Digest;
64
+ }
65
+ | {
66
+ type: "leaf";
67
+ /// The CBOR value contained in the leaf
68
+ cbor: Cbor;
69
+ /// The digest of the leaf
70
+ digest: Digest;
71
+ }
72
+ | {
73
+ type: "wrapped";
74
+ /// The envelope being wrapped
75
+ envelope: Envelope;
76
+ /// The digest of the wrapped envelope
77
+ digest: Digest;
78
+ }
79
+ | {
80
+ type: "assertion";
81
+ /// The assertion
82
+ assertion: Assertion;
83
+ }
84
+ | {
85
+ type: "elided";
86
+ /// The digest of the elided content
87
+ digest: Digest;
88
+ }
89
+ | {
90
+ type: "knownValue";
91
+ /// The known value instance
92
+ value: KnownValue;
93
+ /// The digest of the known value
94
+ digest: Digest;
95
+ }
96
+ | {
97
+ type: "encrypted";
98
+ /// The encrypted message
99
+ message: EncryptedMessage;
100
+ }
101
+ | {
102
+ type: "compressed";
103
+ /// The compressed data
104
+ value: Compressed;
105
+ };
106
+
107
+ // Import types from extension modules (will be available at runtime)
108
+ import { Compressed } from "../extension/compress";
109
+ import { EncryptedMessage } from "../extension/encrypt";
110
+
111
+ /// A flexible container for structured data with built-in integrity
112
+ /// verification.
113
+ ///
114
+ /// Gordian Envelope is the primary data structure of this library. It provides a
115
+ /// way to encapsulate and organize data with cryptographic integrity, privacy
116
+ /// features, and selective disclosure capabilities.
117
+ ///
118
+ /// Key characteristics of envelopes:
119
+ ///
120
+ /// - **Immutability**: Envelopes are immutable. Operations that appear to
121
+ /// "modify" an envelope actually create a new envelope. This immutability is
122
+ /// fundamental to maintaining the integrity of the envelope's digest tree.
123
+ ///
124
+ /// - **Efficient Cloning**: Envelopes use shallow copying for efficient O(1)
125
+ /// cloning. Since they're immutable, clones share the same underlying data.
126
+ ///
127
+ /// - **Semantic Structure**: Envelopes can represent various semantic
128
+ /// relationships through subjects, predicates, and objects (similar to RDF
129
+ /// triples).
130
+ ///
131
+ /// - **Digest Tree**: Each envelope maintains a Merkle-like digest tree that
132
+ /// ensures the integrity of its contents and enables verification of
133
+ /// individual parts.
134
+ ///
135
+ /// - **Privacy Features**: Envelopes support selective disclosure through
136
+ /// elision, encryption, and compression of specific parts, while maintaining
137
+ /// the overall integrity of the structure.
138
+ ///
139
+ /// - **Deterministic Representation**: Envelopes use deterministic CBOR
140
+ /// encoding to ensure consistent serialization across platforms.
141
+ ///
142
+ /// The Gordian Envelope specification is defined in an IETF Internet Draft, and
143
+ /// this implementation closely follows that specification.
144
+ ///
145
+ /// @example
146
+ /// ```typescript
147
+ /// // Create an envelope representing a person
148
+ /// const person = Envelope.new("person")
149
+ /// .addAssertion("name", "Alice")
150
+ /// .addAssertion("age", 30)
151
+ /// .addAssertion("email", "alice@example.com");
152
+ ///
153
+ /// // Create a partially redacted version by eliding the email
154
+ /// const redacted = person.elideRemovingTarget(
155
+ /// person.assertionWithPredicate("email")
156
+ /// );
157
+ ///
158
+ /// // The digest of both envelopes remains the same
159
+ /// assert(person.digest().equals(redacted.digest()));
160
+ /// ```
161
+ export class Envelope implements DigestProvider {
162
+ readonly #case: EnvelopeCase;
163
+
164
+ /// Private constructor. Use static factory methods to create envelopes.
165
+ ///
166
+ /// @param envelopeCase - The envelope case variant
167
+ private constructor(envelopeCase: EnvelopeCase) {
168
+ this.#case = envelopeCase;
169
+ }
170
+
171
+ /// Returns a reference to the underlying envelope case.
172
+ ///
173
+ /// The `EnvelopeCase` enum represents the specific structural variant of
174
+ /// this envelope. This method provides access to that underlying
175
+ /// variant for operations that need to differentiate between the
176
+ /// different envelope types.
177
+ ///
178
+ /// @returns The `EnvelopeCase` that defines this envelope's structure.
179
+ case(): EnvelopeCase {
180
+ return this.#case;
181
+ }
182
+
183
+ /// Creates an envelope with a subject, which can be any value that
184
+ /// can be encoded as an envelope.
185
+ ///
186
+ /// @param subject - The subject value
187
+ /// @returns A new envelope containing the subject
188
+ ///
189
+ /// @example
190
+ /// ```typescript
191
+ /// const envelope = Envelope.new("Hello, world!");
192
+ /// const numberEnvelope = Envelope.new(42);
193
+ /// const binaryEnvelope = Envelope.new(new Uint8Array([1, 2, 3]));
194
+ /// ```
195
+ static new(subject: EnvelopeEncodableValue): Envelope {
196
+ // Convert the subject to an envelope
197
+ if (subject instanceof Envelope) {
198
+ return subject;
199
+ }
200
+
201
+ // Handle primitives and create leaf envelopes
202
+ return Envelope.newLeaf(subject);
203
+ }
204
+
205
+ /// Creates an envelope with a subject, or null if subject is undefined.
206
+ ///
207
+ /// @param subject - The optional subject value
208
+ /// @returns A new envelope or null envelope
209
+ static newOrNull(subject: EnvelopeEncodableValue | undefined): Envelope {
210
+ if (subject === undefined || subject === null) {
211
+ return Envelope.null();
212
+ }
213
+ return Envelope.new(subject);
214
+ }
215
+
216
+ /// Creates an envelope with a subject, or undefined if subject is undefined.
217
+ ///
218
+ /// @param subject - The optional subject value
219
+ /// @returns A new envelope or undefined
220
+ static newOrNone(subject: EnvelopeEncodableValue | undefined): Envelope | undefined {
221
+ if (subject === undefined || subject === null) {
222
+ return undefined;
223
+ }
224
+ return Envelope.new(subject);
225
+ }
226
+
227
+ /// Creates an envelope from an EnvelopeCase.
228
+ ///
229
+ /// This is an internal method used by extensions to create envelopes
230
+ /// from custom case types like compressed or encrypted.
231
+ ///
232
+ /// @param envelopeCase - The envelope case to wrap
233
+ /// @returns A new envelope with the given case
234
+ static fromCase(envelopeCase: EnvelopeCase): Envelope {
235
+ return new Envelope(envelopeCase);
236
+ }
237
+
238
+ /// Creates an assertion envelope with a predicate and object.
239
+ ///
240
+ /// @param predicate - The predicate of the assertion
241
+ /// @param object - The object of the assertion
242
+ /// @returns A new assertion envelope
243
+ ///
244
+ /// @example
245
+ /// ```typescript
246
+ /// const assertion = Envelope.newAssertion("name", "Alice");
247
+ /// ```
248
+ static newAssertion(predicate: EnvelopeEncodableValue, object: EnvelopeEncodableValue): Envelope {
249
+ const predicateEnv = predicate instanceof Envelope ? predicate : Envelope.new(predicate);
250
+ const objectEnv = object instanceof Envelope ? object : Envelope.new(object);
251
+ return Envelope.newWithAssertion(new Assertion(predicateEnv, objectEnv));
252
+ }
253
+
254
+ /// Creates a null envelope (containing CBOR null).
255
+ ///
256
+ /// @returns A null envelope
257
+ static null(): Envelope {
258
+ return Envelope.newLeaf(null);
259
+ }
260
+
261
+ //
262
+ // Internal constructors
263
+ //
264
+
265
+ /// Creates an envelope with a subject and unchecked assertions.
266
+ ///
267
+ /// The assertions are sorted by digest and the envelope's digest is calculated.
268
+ ///
269
+ /// @param subject - The subject envelope
270
+ /// @param uncheckedAssertions - The assertions to attach
271
+ /// @returns A new node envelope
272
+ static newWithUncheckedAssertions(subject: Envelope, uncheckedAssertions: Envelope[]): Envelope {
273
+ if (uncheckedAssertions.length === 0) {
274
+ throw new Error("Assertions array cannot be empty");
275
+ }
276
+
277
+ // Sort assertions by digest
278
+ const sortedAssertions = [...uncheckedAssertions].sort((a, b) => {
279
+ const aHex = a.digest().hex();
280
+ const bHex = b.digest().hex();
281
+ return aHex.localeCompare(bHex);
282
+ });
283
+
284
+ // Calculate digest from subject and all assertions
285
+ const digests = [subject.digest(), ...sortedAssertions.map((a) => a.digest())];
286
+ const digest = Digest.fromDigests(digests);
287
+
288
+ return new Envelope({
289
+ type: "node",
290
+ subject,
291
+ assertions: sortedAssertions,
292
+ digest,
293
+ });
294
+ }
295
+
296
+ /// Creates an envelope with a subject and validated assertions.
297
+ ///
298
+ /// All assertions must be assertion or obscured envelopes.
299
+ ///
300
+ /// @param subject - The subject envelope
301
+ /// @param assertions - The assertions to attach
302
+ /// @returns A new node envelope
303
+ /// @throws {EnvelopeError} If any assertion is not valid
304
+ static newWithAssertions(subject: Envelope, assertions: Envelope[]): Envelope {
305
+ // Validate that all assertions are assertion or obscured envelopes
306
+ for (const assertion of assertions) {
307
+ if (!assertion.isSubjectAssertion() && !assertion.isSubjectObscured()) {
308
+ throw EnvelopeError.invalidFormat();
309
+ }
310
+ }
311
+
312
+ return Envelope.newWithUncheckedAssertions(subject, assertions);
313
+ }
314
+
315
+ /// Creates an envelope with an assertion as its subject.
316
+ ///
317
+ /// @param assertion - The assertion
318
+ /// @returns A new assertion envelope
319
+ static newWithAssertion(assertion: Assertion): Envelope {
320
+ return new Envelope({
321
+ type: "assertion",
322
+ assertion,
323
+ });
324
+ }
325
+
326
+ /// Creates an envelope with a known value.
327
+ ///
328
+ /// @param value - The known value (can be a KnownValue instance or a number/bigint)
329
+ /// @returns A new known value envelope
330
+ static newWithKnownValue(value: KnownValue | number | bigint): Envelope {
331
+ const knownValue = value instanceof KnownValue ? value : new KnownValue(value);
332
+ // Calculate digest from CBOR encoding of the known value
333
+ const digest = Digest.fromImage(knownValue.toCborData());
334
+ return new Envelope({
335
+ type: "knownValue",
336
+ value: knownValue,
337
+ digest,
338
+ });
339
+ }
340
+
341
+ /// Creates an envelope with encrypted content.
342
+ ///
343
+ /// @param encryptedMessage - The encrypted message
344
+ /// @returns A new encrypted envelope
345
+ /// @throws {EnvelopeError} If the encrypted message doesn't have a digest
346
+ static newWithEncrypted(encryptedMessage: EncryptedMessage): Envelope {
347
+ // TODO: Validate that encrypted message has digest
348
+ // if (!encryptedMessage.hasDigest()) {
349
+ // throw EnvelopeError.missingDigest();
350
+ // }
351
+ return new Envelope({
352
+ type: "encrypted",
353
+ message: encryptedMessage,
354
+ });
355
+ }
356
+
357
+ /// Creates an envelope with compressed content.
358
+ ///
359
+ /// @param compressed - The compressed data
360
+ /// @returns A new compressed envelope
361
+ /// @throws {EnvelopeError} If the compressed data doesn't have a digest
362
+ static newWithCompressed(compressed: Compressed): Envelope {
363
+ // TODO: Validate that compressed has digest
364
+ // if (!compressed.hasDigest()) {
365
+ // throw EnvelopeError.missingDigest();
366
+ // }
367
+ return new Envelope({
368
+ type: "compressed",
369
+ value: compressed,
370
+ });
371
+ }
372
+
373
+ /// Creates an elided envelope containing only a digest.
374
+ ///
375
+ /// @param digest - The digest of the elided content
376
+ /// @returns A new elided envelope
377
+ static newElided(digest: Digest): Envelope {
378
+ return new Envelope({
379
+ type: "elided",
380
+ digest,
381
+ });
382
+ }
383
+
384
+ /// Creates a leaf envelope containing a CBOR value.
385
+ ///
386
+ /// @param value - The value to encode as CBOR
387
+ /// @returns A new leaf envelope
388
+ static newLeaf(value: unknown): Envelope {
389
+ // Convert value to CBOR
390
+ const cbor = Envelope.valueToCbor(value);
391
+
392
+ // Calculate digest from CBOR bytes
393
+ const cborBytes = Envelope.cborToBytes(cbor);
394
+ const digest = Digest.fromImage(cborBytes);
395
+
396
+ return new Envelope({
397
+ type: "leaf",
398
+ cbor,
399
+ digest,
400
+ });
401
+ }
402
+
403
+ /// Creates a wrapped envelope.
404
+ ///
405
+ /// @param envelope - The envelope to wrap
406
+ /// @returns A new wrapped envelope
407
+ static newWrapped(envelope: Envelope): Envelope {
408
+ const digest = Digest.fromDigests([envelope.digest()]);
409
+ return new Envelope({
410
+ type: "wrapped",
411
+ envelope,
412
+ digest,
413
+ });
414
+ }
415
+
416
+ /// Returns the digest of this envelope.
417
+ ///
418
+ /// Implementation of DigestProvider interface.
419
+ ///
420
+ /// @returns The envelope's digest
421
+ digest(): Digest {
422
+ const c = this.#case;
423
+ switch (c.type) {
424
+ case "node":
425
+ case "leaf":
426
+ case "wrapped":
427
+ case "elided":
428
+ case "knownValue":
429
+ return c.digest;
430
+ case "assertion":
431
+ return c.assertion.digest();
432
+ case "encrypted": {
433
+ // Get digest from encrypted message (AAD)
434
+ const digest = c.message.aadDigest();
435
+ if (digest === undefined) {
436
+ throw new Error("Encrypted envelope missing digest");
437
+ }
438
+ return digest;
439
+ }
440
+ case "compressed": {
441
+ // Get digest from compressed value
442
+ const digest = c.value.digestOpt();
443
+ if (digest === undefined) {
444
+ throw new Error("Compressed envelope missing digest");
445
+ }
446
+ return digest;
447
+ }
448
+ }
449
+ }
450
+
451
+ /// Returns the subject of this envelope.
452
+ ///
453
+ /// For different envelope cases:
454
+ /// - Node: Returns the subject envelope
455
+ /// - Other cases: Returns the envelope itself
456
+ ///
457
+ /// @returns The subject envelope
458
+ subject(): Envelope {
459
+ const c = this.#case;
460
+ switch (c.type) {
461
+ case "node":
462
+ return c.subject;
463
+ case "leaf":
464
+ case "wrapped":
465
+ case "assertion":
466
+ case "elided":
467
+ case "knownValue":
468
+ case "encrypted":
469
+ case "compressed":
470
+ return this;
471
+ }
472
+ }
473
+
474
+ /// Checks if the envelope's subject is an assertion.
475
+ ///
476
+ /// @returns `true` if the subject is an assertion, `false` otherwise
477
+ isSubjectAssertion(): boolean {
478
+ return this.#case.type === "assertion";
479
+ }
480
+
481
+ /// Checks if the envelope's subject is obscured (elided, encrypted, or compressed).
482
+ ///
483
+ /// @returns `true` if the subject is obscured, `false` otherwise
484
+ isSubjectObscured(): boolean {
485
+ const t = this.#case.type;
486
+ return t === "elided" || t === "encrypted" || t === "compressed";
487
+ }
488
+
489
+ //
490
+ // CBOR conversion helpers
491
+ //
492
+
493
+ /// Converts a value to CBOR.
494
+ ///
495
+ /// @param value - The value to convert
496
+ /// @returns A CBOR representation
497
+ private static valueToCbor(value: unknown): Cbor {
498
+ // Import cbor function at runtime to avoid circular dependencies
499
+
500
+ return cbor(value as Parameters<typeof cbor>[0]);
501
+ }
502
+
503
+ /// Converts CBOR to bytes.
504
+ ///
505
+ /// @param cbor - The CBOR value
506
+ /// @returns Byte representation
507
+ private static cborToBytes(cbor: Cbor): Uint8Array {
508
+ // Import cborData function at runtime to avoid circular dependencies
509
+
510
+ return cborData(cbor);
511
+ }
512
+
513
+ /// Returns the untagged CBOR representation of this envelope.
514
+ ///
515
+ /// @returns The untagged CBOR
516
+ untaggedCbor(): Cbor {
517
+ const c = this.#case;
518
+ switch (c.type) {
519
+ case "node": {
520
+ // Array with subject followed by assertions
521
+ const result = [c.subject.untaggedCbor()];
522
+ for (const assertion of c.assertions) {
523
+ result.push(assertion.untaggedCbor());
524
+ }
525
+ return Envelope.valueToCbor(result);
526
+ }
527
+ case "leaf":
528
+ // Tagged with TAG_LEAF (204)
529
+ return toTaggedValue(TAG_LEAF, c.cbor);
530
+ case "wrapped":
531
+ // Wrapped envelopes are tagged with TAG_ENVELOPE
532
+ return c.envelope.taggedCbor();
533
+ case "assertion":
534
+ // Assertions convert to CBOR maps
535
+ return c.assertion.toCbor();
536
+ case "elided":
537
+ // Elided is just the digest bytes
538
+ return Envelope.valueToCbor(c.digest.data());
539
+ case "knownValue":
540
+ // TODO: Implement known value encoding
541
+ throw new Error("Known value encoding not yet implemented");
542
+ case "encrypted": {
543
+ // Encrypted is tagged with TAG_ENCRYPTED (40002)
544
+ // Contains: [ciphertext, nonce, auth, optional_aad_digest]
545
+ // Per BCR-2023-004 and BCR-2022-001
546
+ const message = c.message;
547
+ const digest = message.aadDigest();
548
+ const arr =
549
+ digest !== undefined
550
+ ? [message.ciphertext(), message.nonce(), message.authTag(), digest.data()]
551
+ : [message.ciphertext(), message.nonce(), message.authTag()];
552
+ return toTaggedValue(TAG_ENCRYPTED, Envelope.valueToCbor(arr));
553
+ }
554
+ case "compressed": {
555
+ // Compressed is tagged with TAG_COMPRESSED (40003)
556
+ // and contains an array: [compressed_data, optional_digest]
557
+ const digest = c.value.digestOpt();
558
+ const data = c.value.compressedData();
559
+ const arr = digest !== undefined ? [data, digest.data()] : [data];
560
+ return toTaggedValue(TAG_COMPRESSED, Envelope.valueToCbor(arr));
561
+ }
562
+ }
563
+ }
564
+
565
+ /// Returns the tagged CBOR representation of this envelope.
566
+ ///
567
+ /// All envelopes are tagged with TAG_ENVELOPE (200).
568
+ ///
569
+ /// @returns The tagged CBOR
570
+ taggedCbor(): Cbor {
571
+ return toTaggedValue(TAG_ENVELOPE, this.untaggedCbor());
572
+ }
573
+
574
+ /// Creates an envelope from untagged CBOR.
575
+ ///
576
+ /// @param cbor - The untagged CBOR value
577
+ /// @returns A new envelope
578
+ static fromUntaggedCbor(cbor: Cbor): Envelope {
579
+ // Check if it's a tagged value
580
+ const tagged = asTaggedValue(cbor);
581
+ if (tagged !== undefined) {
582
+ const [tag, item] = tagged;
583
+ switch (tag.value) {
584
+ case TAG_LEAF:
585
+ case TAG_ENCODED_CBOR:
586
+ // Leaf envelope
587
+ return Envelope.newLeaf(item);
588
+ case TAG_ENVELOPE: {
589
+ // Wrapped envelope
590
+ const envelope = Envelope.fromUntaggedCbor(item);
591
+ return Envelope.newWrapped(envelope);
592
+ }
593
+ case TAG_COMPRESSED: {
594
+ // Compressed envelope: array with [compressed_data, optional_digest]
595
+ const arr = asCborArray(item);
596
+ if (arr === undefined || arr.length < 1 || arr.length > 2) {
597
+ throw EnvelopeError.cbor("compressed envelope must have 1 or 2 elements");
598
+ }
599
+ // We've already checked arr.length >= 1 above
600
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
601
+ const compressedData = asByteString(arr.get(0)!);
602
+ if (compressedData === undefined) {
603
+ throw EnvelopeError.cbor("compressed data must be byte string");
604
+ }
605
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
606
+ const digestBytes = arr.length === 2 ? asByteString(arr.get(1)!) : undefined;
607
+ if (arr.length === 2 && digestBytes === undefined) {
608
+ throw EnvelopeError.cbor("digest must be byte string");
609
+ }
610
+ const digest = digestBytes !== undefined ? new Digest(digestBytes) : undefined;
611
+
612
+ // Import Compressed class at runtime to avoid circular dependency
613
+
614
+ const compressed = new Compressed(compressedData, digest);
615
+ return Envelope.fromCase({ type: "compressed", value: compressed });
616
+ }
617
+ case TAG_ENCRYPTED: {
618
+ // Encrypted envelope: array with [ciphertext, nonce, auth, optional_aad_digest]
619
+ // Per BCR-2023-004 and BCR-2022-001
620
+ const arr = asCborArray(item);
621
+ if (arr === undefined || arr.length < 3 || arr.length > 4) {
622
+ throw EnvelopeError.cbor("encrypted envelope must have 3 or 4 elements");
623
+ }
624
+ // We've already checked arr.length >= 3 above
625
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
626
+ const ciphertext = asByteString(arr.get(0)!);
627
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
628
+ const nonce = asByteString(arr.get(1)!);
629
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
630
+ const authTag = asByteString(arr.get(2)!);
631
+ if (ciphertext === undefined || nonce === undefined || authTag === undefined) {
632
+ throw EnvelopeError.cbor("ciphertext, nonce, and auth must be byte strings");
633
+ }
634
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
635
+ const digestBytes = arr.length === 4 ? asByteString(arr.get(3)!) : undefined;
636
+ if (arr.length === 4 && digestBytes === undefined) {
637
+ throw EnvelopeError.cbor("aad digest must be byte string");
638
+ }
639
+ const digest = digestBytes !== undefined ? new Digest(digestBytes) : undefined;
640
+
641
+ const message = new EncryptedMessage(ciphertext, nonce, authTag, digest);
642
+ return Envelope.fromCase({ type: "encrypted", message });
643
+ }
644
+ default:
645
+ throw EnvelopeError.cbor(`unknown envelope tag: ${tag.value}`);
646
+ }
647
+ }
648
+
649
+ // Check if it's a byte string (elided)
650
+ const bytes = asByteString(cbor);
651
+ if (bytes !== undefined) {
652
+ if (bytes.length !== 32) {
653
+ throw EnvelopeError.cbor("elided digest must be 32 bytes");
654
+ }
655
+ return Envelope.newElided(new Digest(bytes));
656
+ }
657
+
658
+ // Check if it's an array (node)
659
+ const array = asCborArray(cbor);
660
+ if (array !== undefined) {
661
+ if (array.length < 2) {
662
+ throw EnvelopeError.cbor("node must have at least two elements");
663
+ }
664
+ const subjectCbor = array.get(0);
665
+ if (subjectCbor === undefined) {
666
+ throw EnvelopeError.cbor("node subject is missing");
667
+ }
668
+ const subject = Envelope.fromUntaggedCbor(subjectCbor);
669
+ const assertions: Envelope[] = [];
670
+ for (let i = 1; i < array.length; i++) {
671
+ const assertionCbor = array.get(i);
672
+ if (assertionCbor === undefined) {
673
+ throw EnvelopeError.cbor(`node assertion at index ${i} is missing`);
674
+ }
675
+ assertions.push(Envelope.fromUntaggedCbor(assertionCbor));
676
+ }
677
+ return Envelope.newWithAssertions(subject, assertions);
678
+ }
679
+
680
+ // Check if it's a map (assertion)
681
+ const map = asCborMap(cbor);
682
+ if (map !== undefined) {
683
+ const assertion = Assertion.fromCborMap(map);
684
+ return Envelope.newWithAssertion(assertion);
685
+ }
686
+
687
+ // Handle known values (unsigned integers)
688
+ if (cbor.type === MajorType.Unsigned) {
689
+ const knownValue = new KnownValue(cbor.value as number | bigint);
690
+ return Envelope.newWithKnownValue(knownValue);
691
+ }
692
+
693
+ throw EnvelopeError.cbor("invalid envelope format");
694
+ }
695
+
696
+ /// Creates an envelope from tagged CBOR.
697
+ ///
698
+ /// @param cbor - The tagged CBOR value (should have TAG_ENVELOPE)
699
+ /// @returns A new envelope
700
+ static fromTaggedCbor(cbor: Cbor): Envelope {
701
+ try {
702
+ const untagged = tryExpectedTaggedValue(cbor, TAG_ENVELOPE);
703
+ return Envelope.fromUntaggedCbor(untagged);
704
+ } catch (error) {
705
+ throw EnvelopeError.cbor(
706
+ `expected TAG_ENVELOPE (${TAG_ENVELOPE})`,
707
+ error instanceof Error ? error : undefined,
708
+ );
709
+ }
710
+ }
711
+
712
+ /// Adds an assertion to this envelope.
713
+ ///
714
+ /// @param predicate - The assertion predicate
715
+ /// @param object - The assertion object
716
+ /// @returns A new envelope with the assertion added
717
+ ///
718
+ /// @example
719
+ /// ```typescript
720
+ /// const person = Envelope.new("Alice")
721
+ /// .addAssertion("age", 30)
722
+ /// .addAssertion("city", "Boston");
723
+ /// ```
724
+ addAssertion(predicate: EnvelopeEncodableValue, object: EnvelopeEncodableValue): Envelope {
725
+ const assertion = Envelope.newAssertion(predicate, object);
726
+ return this.addAssertionEnvelope(assertion);
727
+ }
728
+
729
+ /// Adds an assertion envelope to this envelope.
730
+ ///
731
+ /// @param assertion - The assertion envelope
732
+ /// @returns A new envelope with the assertion added
733
+ addAssertionEnvelope(assertion: Envelope): Envelope {
734
+ const c = this.#case;
735
+
736
+ // If this is already a node, add to existing assertions
737
+ if (c.type === "node") {
738
+ return Envelope.newWithAssertions(c.subject, [...c.assertions, assertion]);
739
+ }
740
+
741
+ // Otherwise, create a new node with this envelope as subject
742
+ return Envelope.newWithAssertions(this, [assertion]);
743
+ }
744
+
745
+ /// Creates a string representation of this envelope.
746
+ ///
747
+ /// @returns A string representation
748
+ toString(): string {
749
+ return `Envelope(${this.#case.type})`;
750
+ }
751
+
752
+ /// Creates a shallow copy of this envelope.
753
+ ///
754
+ /// Since envelopes are immutable, this returns the same instance.
755
+ ///
756
+ /// @returns This envelope
757
+ clone(): Envelope {
758
+ return this;
759
+ }
760
+
761
+ //
762
+ // Format methods (implemented via prototype extension in format module)
763
+ //
764
+
765
+ /// Returns a tree-formatted string representation of the envelope.
766
+ ///
767
+ /// The tree format displays the hierarchical structure of the envelope,
768
+ /// showing subjects, assertions, and their relationships.
769
+ ///
770
+ /// @param options - Optional formatting options
771
+ /// @returns A tree-formatted string
772
+ declare treeFormat: (options?: {
773
+ hideNodes?: boolean;
774
+ highlightDigests?: Set<string>;
775
+ digestDisplay?: "short" | "full";
776
+ }) => string;
777
+
778
+ /// Returns a short identifier for this envelope based on its digest.
779
+ ///
780
+ /// @param format - Format for the digest ('short' or 'full')
781
+ /// @returns A digest identifier string
782
+ declare shortId: (format?: "short" | "full") => string;
783
+
784
+ /// Returns a summary string for this envelope.
785
+ ///
786
+ /// @param maxLength - Maximum length of the summary
787
+ /// @returns A summary string
788
+ declare summary: (maxLength?: number) => string;
789
+
790
+ /// Returns a hex representation of the envelope's CBOR encoding.
791
+ ///
792
+ /// @returns A hex string
793
+ declare hex: () => string;
794
+
795
+ /// Returns the CBOR-encoded bytes of the envelope.
796
+ ///
797
+ /// @returns The CBOR bytes
798
+ declare cborBytes: () => Uint8Array;
799
+
800
+ /// Returns a CBOR diagnostic notation string for the envelope.
801
+ ///
802
+ /// @returns A diagnostic string
803
+ declare diagnostic: () => string;
804
+
805
+ //
806
+ // Extension methods (implemented via prototype extension in extension modules)
807
+ // These declarations ensure TypeScript recognizes the methods when consuming the package
808
+ //
809
+
810
+ // From assertions.ts
811
+ declare addAssertionEnvelopes: (assertions: Envelope[]) => Envelope;
812
+ declare addOptionalAssertionEnvelope: (assertion: Envelope | undefined) => Envelope;
813
+ declare addOptionalAssertion: (
814
+ predicate: EnvelopeEncodableValue,
815
+ object: EnvelopeEncodableValue | undefined,
816
+ ) => Envelope;
817
+ declare addNonemptyStringAssertion: (predicate: EnvelopeEncodableValue, str: string) => Envelope;
818
+ declare addAssertions: (envelopes: Envelope[]) => Envelope;
819
+ declare addAssertionIf: (
820
+ condition: boolean,
821
+ predicate: EnvelopeEncodableValue,
822
+ object: EnvelopeEncodableValue,
823
+ ) => Envelope;
824
+ declare addAssertionEnvelopeIf: (condition: boolean, assertionEnvelope: Envelope) => Envelope;
825
+ declare removeAssertion: (target: Envelope) => Envelope;
826
+ declare replaceAssertion: (assertion: Envelope, newAssertion: Envelope) => Envelope;
827
+ declare replaceSubject: (subject: Envelope) => Envelope;
828
+
829
+ // From elide.ts
830
+ declare elide: () => Envelope;
831
+ declare elideRemovingSetWithAction: (target: Set<Digest>, action: ObscureAction) => Envelope;
832
+ declare elideRemovingSet: (target: Set<Digest>) => Envelope;
833
+ declare elideRemovingArrayWithAction: (
834
+ target: DigestProvider[],
835
+ action: ObscureAction,
836
+ ) => Envelope;
837
+ declare elideRemovingArray: (target: DigestProvider[]) => Envelope;
838
+ declare elideRemovingTargetWithAction: (
839
+ target: DigestProvider,
840
+ action: ObscureAction,
841
+ ) => Envelope;
842
+ declare elideRemovingTarget: (target: DigestProvider) => Envelope;
843
+ declare elideRevealingSetWithAction: (target: Set<Digest>, action: ObscureAction) => Envelope;
844
+ declare elideRevealingSet: (target: Set<Digest>) => Envelope;
845
+ declare elideRevealingArrayWithAction: (
846
+ target: DigestProvider[],
847
+ action: ObscureAction,
848
+ ) => Envelope;
849
+ declare elideRevealingArray: (target: DigestProvider[]) => Envelope;
850
+ declare elideRevealingTargetWithAction: (
851
+ target: DigestProvider,
852
+ action: ObscureAction,
853
+ ) => Envelope;
854
+ declare elideRevealingTarget: (target: DigestProvider) => Envelope;
855
+ declare unelide: (envelope: Envelope) => Envelope;
856
+ declare nodesMatching: (
857
+ targetDigests: Set<Digest> | undefined,
858
+ obscureTypes: ObscureType[],
859
+ ) => Set<Digest>;
860
+ declare walkUnelide: (envelopes: Envelope[]) => Envelope;
861
+ declare walkReplace: (target: Set<Digest>, replacement: Envelope) => Envelope;
862
+ declare isIdenticalTo: (other: Envelope) => boolean;
863
+
864
+ // From leaf.ts
865
+ declare tryLeaf: () => Cbor;
866
+ declare extractString: () => string;
867
+ declare extractNumber: () => number;
868
+ declare extractBoolean: () => boolean;
869
+ declare extractBytes: () => Uint8Array;
870
+ declare extractNull: () => null;
871
+
872
+ // From queries.ts
873
+ declare isFalse: () => boolean;
874
+ declare isTrue: () => boolean;
875
+ declare isBool: () => boolean;
876
+ declare isNumber: () => boolean;
877
+ declare isSubjectNumber: () => boolean;
878
+ declare isNaN: () => boolean;
879
+ declare isSubjectNaN: () => boolean;
880
+ declare isNull: () => boolean;
881
+ declare tryByteString: () => Uint8Array;
882
+ declare asByteString: () => Uint8Array | undefined;
883
+ declare asArray: () => readonly Cbor[] | undefined;
884
+ declare asMap: () => CborMap | undefined;
885
+ declare asText: () => string | undefined;
886
+ declare asLeaf: () => Cbor | undefined;
887
+ declare hasAssertions: () => boolean;
888
+ declare asAssertion: () => Envelope | undefined;
889
+ declare tryAssertion: () => Envelope;
890
+ declare asPredicate: () => Envelope | undefined;
891
+ declare tryPredicate: () => Envelope;
892
+ declare asObject: () => Envelope | undefined;
893
+ declare tryObject: () => Envelope;
894
+ declare isAssertion: () => boolean;
895
+ declare isElided: () => boolean;
896
+ declare isLeaf: () => boolean;
897
+ declare isNode: () => boolean;
898
+ declare isWrapped: () => boolean;
899
+ declare isInternal: () => boolean;
900
+ declare isObscured: () => boolean;
901
+ declare assertions: () => Envelope[];
902
+ declare assertionsWithPredicate: (predicate: EnvelopeEncodableValue) => Envelope[];
903
+ declare assertionWithPredicate: (predicate: EnvelopeEncodableValue) => Envelope;
904
+ declare optionalAssertionWithPredicate: (
905
+ predicate: EnvelopeEncodableValue,
906
+ ) => Envelope | undefined;
907
+ declare objectForPredicate: (predicate: EnvelopeEncodableValue) => Envelope;
908
+ declare optionalObjectForPredicate: (predicate: EnvelopeEncodableValue) => Envelope | undefined;
909
+ declare objectsForPredicate: (predicate: EnvelopeEncodableValue) => Envelope[];
910
+ declare elementsCount: () => number;
911
+
912
+ // From walk.ts
913
+ declare walk: <State>(hideNodes: boolean, state: State, visit: Visitor<State>) => void;
914
+
915
+ // From wrap.ts
916
+ declare wrap: () => Envelope;
917
+ declare tryUnwrap: () => Envelope;
918
+ declare unwrap: () => Envelope;
919
+
920
+ // From attachment.ts
921
+ declare addAttachment: (
922
+ payload: EnvelopeEncodableValue,
923
+ vendor: string,
924
+ conformsTo?: string,
925
+ ) => Envelope;
926
+ declare attachmentPayload: () => Envelope;
927
+ declare attachmentVendor: () => string;
928
+ declare attachmentConformsTo: () => string | undefined;
929
+ declare attachments: () => Envelope[];
930
+ declare attachmentsWithVendorAndConformsTo: (vendor?: string, conformsTo?: string) => Envelope[];
931
+
932
+ // From compress.ts
933
+ declare compress: () => Envelope;
934
+ declare decompress: () => Envelope;
935
+ declare compressSubject: () => Envelope;
936
+ declare decompressSubject: () => Envelope;
937
+ declare isCompressed: () => boolean;
938
+
939
+ // From encrypt.ts
940
+ declare encryptSubject: (key: SymmetricKey) => Envelope;
941
+ declare decryptSubject: (key: SymmetricKey) => Envelope;
942
+ declare encrypt: (key: SymmetricKey) => Envelope;
943
+ declare decrypt: (key: SymmetricKey) => Envelope;
944
+ declare isEncrypted: () => boolean;
945
+
946
+ // From proof.ts
947
+ declare proofContainsSet: (target: Set<Digest>) => Envelope | undefined;
948
+ declare proofContainsTarget: (target: Envelope) => Envelope | undefined;
949
+ declare confirmContainsSet: (target: Set<Digest>, proof: Envelope) => boolean;
950
+ declare confirmContainsTarget: (target: Envelope, proof: Envelope) => boolean;
951
+
952
+ // From recipient.ts
953
+ declare encryptSubjectToRecipient: (recipientPublicKey: PublicKeyBase) => Envelope;
954
+ declare encryptSubjectToRecipients: (recipients: PublicKeyBase[]) => Envelope;
955
+ declare addRecipient: (recipientPublicKey: PublicKeyBase, contentKey: SymmetricKey) => Envelope;
956
+ declare decryptSubjectToRecipient: (recipientPrivateKey: PrivateKeyBase) => Envelope;
957
+ declare decryptToRecipient: (recipientPrivateKey: PrivateKeyBase) => Envelope;
958
+ declare encryptToRecipients: (recipients: PublicKeyBase[]) => Envelope;
959
+ declare recipients: () => SealedMessage[];
960
+
961
+ // From salt.ts
962
+ declare addSalt: () => Envelope;
963
+ declare addSaltWithLength: (count: number) => Envelope;
964
+ declare addSaltBytes: (saltBytes: Uint8Array) => Envelope;
965
+ declare addSaltInRange: (min: number, max: number) => Envelope;
966
+
967
+ // From signature.ts
968
+ declare addSignature: (signer: Signer) => Envelope;
969
+ declare addSignatureWithMetadata: (signer: Signer, metadata?: SignatureMetadata) => Envelope;
970
+ declare addSignatures: (signers: Signer[]) => Envelope;
971
+ declare hasSignatureFrom: (verifier: Verifier) => boolean;
972
+ declare verifySignatureFrom: (verifier: Verifier) => Envelope;
973
+ declare signatures: () => Envelope[];
974
+
975
+ // From types.ts
976
+ declare addType: (object: EnvelopeEncodableValue) => Envelope;
977
+ declare types: () => Envelope[];
978
+ declare getType: () => Envelope;
979
+ declare hasType: (t: EnvelopeEncodableValue) => boolean;
980
+ declare checkType: (t: EnvelopeEncodableValue) => void;
981
+
982
+ // Static methods from extensions
983
+ declare static newAttachment: (
984
+ payload: EnvelopeEncodableValue,
985
+ vendor: string,
986
+ conformsTo?: string,
987
+ ) => Envelope;
988
+ }