@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,369 @@
1
+ /**
2
+ * Attachment Extension for Gordian Envelope
3
+ *
4
+ * Provides functionality for attaching vendor-specific metadata to envelopes.
5
+ * Attachments enable flexible, extensible data storage without modifying
6
+ * the core data model, facilitating interoperability and future compatibility.
7
+ *
8
+ * Each attachment has:
9
+ * - A payload (arbitrary data)
10
+ * - A required vendor identifier (typically a reverse domain name)
11
+ * - An optional conformsTo URI that indicates the format of the attachment
12
+ *
13
+ * See BCR-2023-006: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2023-006-envelope-attachment.md
14
+ */
15
+
16
+ import { Envelope } from "../base/envelope";
17
+ import { type Digest } from "../base/digest";
18
+ import { EnvelopeError } from "../base/error";
19
+ import type { EnvelopeEncodableValue } from "../base/envelope-encodable";
20
+
21
+ /**
22
+ * Known value for the 'attachment' predicate.
23
+ */
24
+ export const ATTACHMENT = "attachment";
25
+
26
+ /**
27
+ * Known value for the 'vendor' predicate.
28
+ */
29
+ export const VENDOR = "vendor";
30
+
31
+ /**
32
+ * Known value for the 'conformsTo' predicate.
33
+ */
34
+ export const CONFORMS_TO = "conformsTo";
35
+
36
+ /**
37
+ * A container for vendor-specific metadata attachments.
38
+ *
39
+ * Attachments provides a flexible mechanism for attaching arbitrary metadata
40
+ * to envelopes without modifying their core structure.
41
+ */
42
+ export class Attachments {
43
+ readonly #envelopes = new Map<string, Envelope>();
44
+
45
+ /**
46
+ * Creates a new empty attachments container.
47
+ */
48
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
49
+ constructor() {}
50
+
51
+ /**
52
+ * Adds a new attachment with the specified payload and metadata.
53
+ *
54
+ * @param payload - The data to attach
55
+ * @param vendor - A string identifying the entity that defined the attachment format
56
+ * @param conformsTo - Optional URI identifying the structure the payload conforms to
57
+ */
58
+ add(payload: EnvelopeEncodableValue, vendor: string, conformsTo?: string): void {
59
+ const attachment = Envelope.newAttachment(payload, vendor, conformsTo);
60
+ this.#envelopes.set(attachment.digest().hex(), attachment);
61
+ }
62
+
63
+ /**
64
+ * Retrieves an attachment by its digest.
65
+ *
66
+ * @param digest - The unique digest of the attachment to retrieve
67
+ * @returns The envelope if found, or undefined
68
+ */
69
+ get(digest: Digest): Envelope | undefined {
70
+ return this.#envelopes.get(digest.hex());
71
+ }
72
+
73
+ /**
74
+ * Removes an attachment by its digest.
75
+ *
76
+ * @param digest - The unique digest of the attachment to remove
77
+ * @returns The removed envelope if found, or undefined
78
+ */
79
+ remove(digest: Digest): Envelope | undefined {
80
+ const envelope = this.#envelopes.get(digest.hex());
81
+ this.#envelopes.delete(digest.hex());
82
+ return envelope;
83
+ }
84
+
85
+ /**
86
+ * Removes all attachments from the container.
87
+ */
88
+ clear(): void {
89
+ this.#envelopes.clear();
90
+ }
91
+
92
+ /**
93
+ * Returns whether the container has any attachments.
94
+ */
95
+ isEmpty(): boolean {
96
+ return this.#envelopes.size === 0;
97
+ }
98
+
99
+ /**
100
+ * Adds all attachments from this container to an envelope.
101
+ *
102
+ * @param envelope - The envelope to add attachments to
103
+ * @returns A new envelope with all attachments added as assertions
104
+ */
105
+ addToEnvelope(envelope: Envelope): Envelope {
106
+ let result = envelope;
107
+ for (const attachment of this.#envelopes.values()) {
108
+ result = result.addAssertion(ATTACHMENT, attachment);
109
+ }
110
+ return result;
111
+ }
112
+
113
+ /**
114
+ * Creates an Attachments container from an envelope's attachment assertions.
115
+ *
116
+ * @param envelope - The envelope to extract attachments from
117
+ * @returns A new Attachments container with the envelope's attachments
118
+ */
119
+ static fromEnvelope(envelope: Envelope): Attachments {
120
+ const attachments = new Attachments();
121
+ const attachmentEnvelopes = envelope.attachments();
122
+
123
+ for (const attachment of attachmentEnvelopes) {
124
+ attachments.#envelopes.set(attachment.digest().hex(), attachment);
125
+ }
126
+
127
+ return attachments;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Support for attachments in envelopes.
133
+ */
134
+ declare module "../base/envelope" {
135
+ interface Envelope {
136
+ /**
137
+ * Creates a new envelope with an attachment as its subject.
138
+ *
139
+ * The attachment consists of:
140
+ * - The predicate 'attachment'
141
+ * - An object that is a wrapped envelope containing:
142
+ * - The payload (as the subject)
143
+ * - A required 'vendor': String assertion
144
+ * - An optional 'conformsTo': String assertion
145
+ *
146
+ * @param payload - The content of the attachment
147
+ * @param vendor - A string identifying the entity (typically reverse domain name)
148
+ * @param conformsTo - Optional URI identifying the format of the attachment
149
+ * @returns A new attachment envelope
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const attachment = Envelope.newAttachment(
154
+ * "Custom data",
155
+ * "com.example",
156
+ * "https://example.com/format/v1"
157
+ * );
158
+ * ```
159
+ */
160
+ addAttachment(payload: EnvelopeEncodableValue, vendor: string, conformsTo?: string): Envelope;
161
+
162
+ /**
163
+ * Returns the payload of an attachment envelope.
164
+ *
165
+ * @returns The payload envelope
166
+ * @throws {EnvelopeError} If the envelope is not a valid attachment
167
+ */
168
+ attachmentPayload(): Envelope;
169
+
170
+ /**
171
+ * Returns the vendor identifier of an attachment envelope.
172
+ *
173
+ * @returns The vendor string
174
+ * @throws {EnvelopeError} If the envelope is not a valid attachment
175
+ */
176
+ attachmentVendor(): string;
177
+
178
+ /**
179
+ * Returns the optional conformsTo URI of an attachment envelope.
180
+ *
181
+ * @returns The conformsTo string if present, or undefined
182
+ * @throws {EnvelopeError} If the envelope is not a valid attachment
183
+ */
184
+ attachmentConformsTo(): string | undefined;
185
+
186
+ /**
187
+ * Returns all attachment assertions in this envelope.
188
+ *
189
+ * @returns Array of attachment envelopes
190
+ */
191
+ attachments(): Envelope[];
192
+
193
+ /**
194
+ * Searches for attachments matching the given vendor and conformsTo.
195
+ *
196
+ * @param vendor - Optional vendor identifier to match
197
+ * @param conformsTo - Optional conformsTo URI to match
198
+ * @returns Array of matching attachment envelopes
199
+ */
200
+ attachmentsWithVendorAndConformsTo(vendor?: string, conformsTo?: string): Envelope[];
201
+ }
202
+
203
+ namespace Envelope {
204
+ /**
205
+ * Creates a new attachment envelope.
206
+ *
207
+ * @param payload - The content of the attachment
208
+ * @param vendor - A string identifying the entity
209
+ * @param conformsTo - Optional URI identifying the format
210
+ * @returns A new attachment envelope
211
+ */
212
+ function newAttachment(
213
+ payload: EnvelopeEncodableValue,
214
+ vendor: string,
215
+ conformsTo?: string,
216
+ ): Envelope;
217
+ }
218
+ }
219
+
220
+ // Implementation
221
+
222
+ /**
223
+ * Creates a new attachment envelope.
224
+ */
225
+ Envelope.newAttachment = function (
226
+ payload: EnvelopeEncodableValue,
227
+ vendor: string,
228
+ conformsTo?: string,
229
+ ): Envelope {
230
+ // Create the payload envelope wrapped with vendor assertion
231
+ let attachmentObj = Envelope.new(payload).wrap().addAssertion(VENDOR, vendor);
232
+
233
+ // Add optional conformsTo
234
+ if (conformsTo !== undefined) {
235
+ attachmentObj = attachmentObj.addAssertion(CONFORMS_TO, conformsTo);
236
+ }
237
+
238
+ // Create an assertion with 'attachment' as predicate and the wrapped payload as object
239
+ // This returns an assertion envelope
240
+ const attachmentPredicate = Envelope.new(ATTACHMENT);
241
+ return attachmentPredicate.addAssertion(ATTACHMENT, attachmentObj).assertions()[0];
242
+ };
243
+
244
+ /**
245
+ * Adds an attachment to an envelope.
246
+ */
247
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
248
+ if (Envelope?.prototype) {
249
+ Envelope.prototype.addAttachment = function (
250
+ this: Envelope,
251
+ payload: EnvelopeEncodableValue,
252
+ vendor: string,
253
+ conformsTo?: string,
254
+ ): Envelope {
255
+ let attachmentObj = Envelope.new(payload).wrap().addAssertion(VENDOR, vendor);
256
+
257
+ if (conformsTo !== undefined) {
258
+ attachmentObj = attachmentObj.addAssertion(CONFORMS_TO, conformsTo);
259
+ }
260
+
261
+ return this.addAssertion(ATTACHMENT, attachmentObj);
262
+ };
263
+
264
+ /**
265
+ * Returns the payload of an attachment envelope.
266
+ */
267
+ Envelope.prototype.attachmentPayload = function (this: Envelope): Envelope {
268
+ const c = this.case();
269
+ if (c.type !== "assertion") {
270
+ throw EnvelopeError.general("Envelope is not an attachment assertion");
271
+ }
272
+
273
+ const obj = c.assertion.object();
274
+ return obj.unwrap();
275
+ };
276
+
277
+ /**
278
+ * Returns the vendor of an attachment envelope.
279
+ */
280
+ Envelope.prototype.attachmentVendor = function (this: Envelope): string {
281
+ const c = this.case();
282
+ if (c.type !== "assertion") {
283
+ throw EnvelopeError.general("Envelope is not an attachment assertion");
284
+ }
285
+
286
+ const obj = c.assertion.object();
287
+ const vendorEnv = obj.objectForPredicate(VENDOR);
288
+ const vendor = vendorEnv.asText();
289
+
290
+ if (vendor === undefined || vendor === "") {
291
+ throw EnvelopeError.general("Attachment has no vendor");
292
+ }
293
+
294
+ return vendor;
295
+ };
296
+
297
+ /**
298
+ * Returns the conformsTo of an attachment envelope.
299
+ */
300
+ Envelope.prototype.attachmentConformsTo = function (this: Envelope): string | undefined {
301
+ const c = this.case();
302
+ if (c.type !== "assertion") {
303
+ throw EnvelopeError.general("Envelope is not an attachment assertion");
304
+ }
305
+
306
+ const obj = c.assertion.object();
307
+ const conformsToEnv = obj.optionalObjectForPredicate(CONFORMS_TO);
308
+
309
+ if (conformsToEnv === undefined) {
310
+ return undefined;
311
+ }
312
+
313
+ return conformsToEnv.asText();
314
+ };
315
+
316
+ /**
317
+ * Returns all attachment assertions.
318
+ */
319
+ Envelope.prototype.attachments = function (this: Envelope): Envelope[] {
320
+ return this.assertionsWithPredicate(ATTACHMENT).map((a) => {
321
+ const c = a.case();
322
+ if (c.type === "assertion") {
323
+ return c.assertion.object();
324
+ }
325
+ throw EnvelopeError.general("Invalid attachment assertion");
326
+ });
327
+ };
328
+
329
+ /**
330
+ * Returns attachments matching vendor and/or conformsTo.
331
+ */
332
+ Envelope.prototype.attachmentsWithVendorAndConformsTo = function (
333
+ this: Envelope,
334
+ vendor?: string,
335
+ conformsTo?: string,
336
+ ): Envelope[] {
337
+ const allAttachments = this.attachments();
338
+
339
+ return allAttachments.filter((attachment) => {
340
+ try {
341
+ // The attachment is already a wrapped envelope with vendor/conformsTo assertions
342
+ // Check vendor if specified
343
+ if (vendor !== undefined) {
344
+ const vendorEnv = attachment.objectForPredicate(VENDOR);
345
+ const attachmentVendor = vendorEnv.asText();
346
+ if (attachmentVendor !== vendor) {
347
+ return false;
348
+ }
349
+ }
350
+
351
+ // Check conformsTo if specified
352
+ if (conformsTo !== undefined) {
353
+ const conformsToEnv = attachment.optionalObjectForPredicate(CONFORMS_TO);
354
+ if (conformsToEnv === undefined) {
355
+ return false;
356
+ }
357
+ const conformsToText = conformsToEnv.asText();
358
+ if (conformsToText !== conformsTo) {
359
+ return false;
360
+ }
361
+ }
362
+
363
+ return true;
364
+ } catch {
365
+ return false;
366
+ }
367
+ });
368
+ };
369
+ }
@@ -0,0 +1,293 @@
1
+ import { Envelope } from "../base/envelope";
2
+ import { EnvelopeError } from "../base/error";
3
+ import { type Digest } from "../base/digest";
4
+ import * as pako from "pako";
5
+ import { cborData, decodeCbor } from "@bcts/dcbor";
6
+
7
+ /// Extension for compressing and decompressing envelopes.
8
+ ///
9
+ /// This module provides functionality for compressing envelopes to reduce their
10
+ /// size while maintaining their digests. Unlike elision, which removes content,
11
+ /// compression preserves all the information in the envelope but represents it
12
+ /// more efficiently.
13
+ ///
14
+ /// Compression is implemented using the DEFLATE algorithm (via pako) and preserves
15
+ /// the envelope's digest, making it compatible with the envelope's hierarchical
16
+ /// digest tree structure.
17
+ ///
18
+ /// @example
19
+ /// ```typescript
20
+ /// // Create an envelope with some larger, compressible content
21
+ /// const lorem = "Lorem ipsum dolor sit amet...".repeat(10);
22
+ /// const envelope = Envelope.new(lorem);
23
+ ///
24
+ /// // Compress the envelope
25
+ /// const compressed = envelope.compress();
26
+ ///
27
+ /// // The compressed envelope has the same digest as the original
28
+ /// console.log(envelope.digest().equals(compressed.digest())); // true
29
+ ///
30
+ /// // But it takes up less space when serialized
31
+ /// console.log(compressed.cborBytes().length < envelope.cborBytes().length); // true
32
+ ///
33
+ /// // The envelope can be decompressed to recover the original content
34
+ /// const decompressed = compressed.decompress();
35
+ /// console.log(decompressed.asText() === lorem); // true
36
+ /// ```
37
+
38
+ /// Represents compressed data with optional digest
39
+ export class Compressed {
40
+ readonly #compressedData: Uint8Array;
41
+ readonly #digest?: Digest;
42
+
43
+ constructor(compressedData: Uint8Array, digest?: Digest) {
44
+ this.#compressedData = compressedData;
45
+ if (digest !== undefined) {
46
+ this.#digest = digest;
47
+ }
48
+ }
49
+
50
+ /// Creates a Compressed instance from decompressed data
51
+ static fromDecompressedData(decompressedData: Uint8Array, digest?: Digest): Compressed {
52
+ const compressed = pako.deflate(decompressedData);
53
+ return new Compressed(compressed, digest);
54
+ }
55
+
56
+ /// Returns the compressed data
57
+ compressedData(): Uint8Array {
58
+ return this.#compressedData;
59
+ }
60
+
61
+ /// Returns the optional digest
62
+ digestOpt(): Digest | undefined {
63
+ return this.#digest;
64
+ }
65
+
66
+ /// Decompresses the data
67
+ decompress(): Uint8Array {
68
+ return pako.inflate(this.#compressedData);
69
+ }
70
+ }
71
+
72
+ declare module "../base/envelope" {
73
+ interface Envelope {
74
+ /// Returns a compressed version of this envelope.
75
+ ///
76
+ /// This method compresses the envelope using the DEFLATE algorithm,
77
+ /// creating a more space-efficient representation while preserving the
78
+ /// envelope's digest and semantic content. The compressed envelope
79
+ /// maintains the same digest as the original, ensuring compatibility
80
+ /// with the envelope's digest tree structure.
81
+ ///
82
+ /// When an envelope is compressed, the entire envelope structure (including
83
+ /// its subject and assertions) is compressed as a single unit. The
84
+ /// compression preserves all the information but reduces the size of
85
+ /// the serialized envelope.
86
+ ///
87
+ /// @returns The compressed envelope
88
+ /// @throws {EnvelopeError} If the envelope is already encrypted or elided
89
+ ///
90
+ /// @example
91
+ /// ```typescript
92
+ /// // Create an envelope with some content
93
+ /// const text = "This is a fairly long text that will benefit from compression.";
94
+ /// const envelope = Envelope.new(text);
95
+ ///
96
+ /// // Compress the envelope
97
+ /// const compressed = envelope.compress();
98
+ ///
99
+ /// // Check that the compressed version has the same digest
100
+ /// console.log(envelope.digest().equals(compressed.digest())); // true
101
+ ///
102
+ /// // Verify that the compressed version takes less space
103
+ /// console.log(compressed.cborBytes().length < envelope.cborBytes().length);
104
+ /// ```
105
+ compress(): Envelope;
106
+
107
+ /// Returns the decompressed variant of this envelope.
108
+ ///
109
+ /// This method reverses the compression process, restoring the envelope to
110
+ /// its original decompressed form. The decompressed envelope will have
111
+ /// the same digest as the compressed version.
112
+ ///
113
+ /// @returns The decompressed envelope
114
+ /// @throws {EnvelopeError} If the envelope is not compressed, missing digest, or has invalid digest
115
+ ///
116
+ /// @example
117
+ /// ```typescript
118
+ /// // Create and compress an envelope
119
+ /// const original = Envelope.new("Hello, world!");
120
+ /// const compressed = original.compress();
121
+ ///
122
+ /// // Decompress it
123
+ /// const decompressed = compressed.decompress();
124
+ ///
125
+ /// // The decompressed envelope should match the original
126
+ /// console.log(decompressed.asText() === "Hello, world!"); // true
127
+ /// console.log(decompressed.digest().equals(original.digest())); // true
128
+ /// ```
129
+ decompress(): Envelope;
130
+
131
+ /// Returns this envelope with its subject compressed.
132
+ ///
133
+ /// Unlike `compress()` which compresses the entire envelope, this method
134
+ /// only compresses the subject of the envelope, leaving the assertions
135
+ /// decompressed. This is useful when you want to compress a large
136
+ /// subject while keeping the assertions readable and accessible.
137
+ ///
138
+ /// @returns A new envelope with a compressed subject
139
+ ///
140
+ /// @example
141
+ /// ```typescript
142
+ /// // Create an envelope with a large subject and some assertions
143
+ /// const lorem = "Lorem ipsum dolor sit amet...";
144
+ /// const envelope = Envelope.new(lorem)
145
+ /// .addAssertion("note", "This is a metadata note");
146
+ ///
147
+ /// // Compress just the subject
148
+ /// const subjectCompressed = envelope.compressSubject();
149
+ ///
150
+ /// // The envelope's digest is preserved
151
+ /// console.log(envelope.digest().equals(subjectCompressed.digest())); // true
152
+ ///
153
+ /// // The subject is now compressed
154
+ /// console.log(subjectCompressed.subject().isCompressed()); // true
155
+ /// ```
156
+ compressSubject(): Envelope;
157
+
158
+ /// Returns this envelope with its subject decompressed.
159
+ ///
160
+ /// This method reverses the effect of `compressSubject()`, decompressing
161
+ /// the subject of the envelope while leaving the rest of the envelope
162
+ /// unchanged.
163
+ ///
164
+ /// @returns A new envelope with a decompressed subject
165
+ ///
166
+ /// @example
167
+ /// ```typescript
168
+ /// // Create an envelope and compress its subject
169
+ /// const original = Envelope.new("Hello, world!")
170
+ /// .addAssertion("note", "Test note");
171
+ /// const compressed = original.compressSubject();
172
+ ///
173
+ /// // Verify the subject is compressed
174
+ /// console.log(compressed.subject().isCompressed()); // true
175
+ ///
176
+ /// // Decompress the subject
177
+ /// const decompressed = compressed.decompressSubject();
178
+ ///
179
+ /// // Verify the subject is now decompressed
180
+ /// console.log(!decompressed.subject().isCompressed()); // true
181
+ /// ```
182
+ decompressSubject(): Envelope;
183
+
184
+ /// Checks if this envelope is compressed
185
+ isCompressed(): boolean;
186
+ }
187
+ }
188
+
189
+ /// Register compression extension methods on Envelope prototype
190
+ /// This function is exported and called during module initialization
191
+ /// to ensure Envelope is fully defined before attaching methods.
192
+ export function registerCompressExtension(): void {
193
+ if (Envelope?.prototype === undefined) {
194
+ return;
195
+ }
196
+
197
+ // Skip if already registered
198
+ if (typeof Envelope.prototype.compress === "function") {
199
+ return;
200
+ }
201
+
202
+ Envelope.prototype.compress = function (this: Envelope): Envelope {
203
+ const c = this.case();
204
+
205
+ // If already compressed, return as-is
206
+ if (c.type === "compressed") {
207
+ return this;
208
+ }
209
+
210
+ // Can't compress encrypted or elided envelopes
211
+ if (c.type === "encrypted") {
212
+ throw EnvelopeError.general("Cannot compress encrypted envelope");
213
+ }
214
+ if (c.type === "elided") {
215
+ throw EnvelopeError.general("Cannot compress elided envelope");
216
+ }
217
+
218
+ // Compress the entire envelope
219
+ const cbor = this.taggedCbor();
220
+
221
+ const decompressedData = cborData(cbor);
222
+
223
+ const compressed = Compressed.fromDecompressedData(decompressedData, this.digest());
224
+
225
+ // Create a compressed envelope case
226
+ return Envelope.fromCase({ type: "compressed", value: compressed });
227
+ };
228
+
229
+ /// Implementation of decompress()
230
+ Envelope.prototype.decompress = function (this: Envelope): Envelope {
231
+ const c = this.case();
232
+
233
+ if (c.type !== "compressed") {
234
+ throw EnvelopeError.general("Envelope is not compressed");
235
+ }
236
+
237
+ const compressed = c.value;
238
+ const digest = compressed.digestOpt();
239
+
240
+ if (digest === undefined) {
241
+ throw EnvelopeError.general("Missing digest in compressed envelope");
242
+ }
243
+
244
+ // Verify the digest matches
245
+ if (!digest.equals(this.digest())) {
246
+ throw EnvelopeError.general("Invalid digest in compressed envelope");
247
+ }
248
+
249
+ // Decompress the data
250
+ const decompressedData = compressed.decompress();
251
+
252
+ // Parse back to envelope
253
+
254
+ const cbor = decodeCbor(decompressedData);
255
+ const envelope = Envelope.fromTaggedCbor(cbor);
256
+
257
+ // Verify the decompressed envelope has the correct digest
258
+ if (!envelope.digest().equals(digest)) {
259
+ throw EnvelopeError.general("Invalid digest after decompression");
260
+ }
261
+
262
+ return envelope;
263
+ };
264
+
265
+ /// Implementation of compressSubject()
266
+ Envelope.prototype.compressSubject = function (this: Envelope): Envelope {
267
+ if (this.subject().isCompressed()) {
268
+ return this;
269
+ }
270
+
271
+ const subject = this.subject().compress();
272
+ return this.replaceSubject(subject);
273
+ };
274
+
275
+ /// Implementation of decompressSubject()
276
+ Envelope.prototype.decompressSubject = function (this: Envelope): Envelope {
277
+ if (this.subject().isCompressed()) {
278
+ const subject = this.subject().decompress();
279
+ return this.replaceSubject(subject);
280
+ }
281
+
282
+ return this;
283
+ };
284
+
285
+ /// Implementation of isCompressed()
286
+ Envelope.prototype.isCompressed = function (this: Envelope): boolean {
287
+ return this.case().type === "compressed";
288
+ };
289
+ }
290
+
291
+ // Auto-register on module load - will be called again from index.ts
292
+ // to ensure proper ordering after all modules are loaded
293
+ registerCompressExtension();