@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,204 @@
1
+ import { sha256 } from "@bcts/crypto";
2
+
3
+ /// A cryptographic digest used to uniquely identify digital objects.
4
+ ///
5
+ /// Digests in Gordian Envelope are always SHA-256 hashes (32 bytes).
6
+ /// This is a fundamental building block for the Merkle-like digest tree
7
+ /// that enables privacy features while maintaining integrity.
8
+ ///
9
+ /// Based on BCR-2021-002: Digests for Digital Objects
10
+ /// @see https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2021-002-digest.md
11
+ export class Digest {
12
+ readonly #data: Uint8Array;
13
+
14
+ /// Creates a new Digest from raw bytes.
15
+ ///
16
+ /// @param data - The 32-byte digest data
17
+ /// @throws {Error} If data is not exactly 32 bytes
18
+ constructor(data: Uint8Array) {
19
+ if (data.length !== 32) {
20
+ throw new Error(`Digest must be exactly 32 bytes, got ${data.length} bytes`);
21
+ }
22
+ this.#data = data;
23
+ }
24
+
25
+ /// Returns the raw digest bytes.
26
+ ///
27
+ /// @returns A Uint8Array containing the 32-byte digest
28
+ data(): Uint8Array {
29
+ return this.#data;
30
+ }
31
+
32
+ /// Creates a digest from an image (arbitrary byte array).
33
+ ///
34
+ /// This is the primary way to create a digest from data. The data is
35
+ /// hashed using SHA-256 to produce a 32-byte digest.
36
+ ///
37
+ /// @param image - The data to hash
38
+ /// @returns A new Digest instance
39
+ ///
40
+ /// @example
41
+ /// ```typescript
42
+ /// const digest = Digest.fromImage(new TextEncoder().encode("Hello, world!"));
43
+ /// ```
44
+ static fromImage(image: Uint8Array): Digest {
45
+ const hash = sha256(image);
46
+ return new Digest(hash);
47
+ }
48
+
49
+ /// Creates a digest from multiple digests.
50
+ ///
51
+ /// This is used to combine digests in the Merkle-like tree structure.
52
+ /// The digests are concatenated and then hashed.
53
+ ///
54
+ /// @param digests - An array of digests to combine
55
+ /// @returns A new Digest instance representing the combined digests
56
+ ///
57
+ /// @example
58
+ /// ```typescript
59
+ /// const digest1 = Digest.fromImage(data1);
60
+ /// const digest2 = Digest.fromImage(data2);
61
+ /// const combined = Digest.fromDigests([digest1, digest2]);
62
+ /// ```
63
+ static fromDigests(digests: Digest[]): Digest {
64
+ const totalLength = digests.length * 32;
65
+ const combined = new Uint8Array(totalLength);
66
+ let offset = 0;
67
+ for (const digest of digests) {
68
+ combined.set(digest.data(), offset);
69
+ offset += 32;
70
+ }
71
+ return Digest.fromImage(combined);
72
+ }
73
+
74
+ /// Returns the hexadecimal string representation of the digest.
75
+ ///
76
+ /// @returns A 64-character hexadecimal string
77
+ ///
78
+ /// @example
79
+ /// ```typescript
80
+ /// const digest = Digest.fromImage(data);
81
+ /// console.log(digest.hex()); // "5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9"
82
+ /// ```
83
+ hex(): string {
84
+ return Array.from(this.#data)
85
+ .map((b) => b.toString(16).padStart(2, "0"))
86
+ .join("");
87
+ }
88
+
89
+ /// Returns an abbreviated hexadecimal representation for visual comparison.
90
+ ///
91
+ /// Following Blockchain Commons conventions, this returns the first 7
92
+ /// hexadecimal digits of the digest, which provides sufficient entropy
93
+ /// for human visual comparison while being easy to read.
94
+ ///
95
+ /// @returns A 7-character hexadecimal string
96
+ ///
97
+ /// @example
98
+ /// ```typescript
99
+ /// const digest = Digest.fromImage(data);
100
+ /// console.log(digest.short()); // "5feceb6"
101
+ /// ```
102
+ short(): string {
103
+ return this.hex().substring(0, 7);
104
+ }
105
+
106
+ /// Creates a digest from a hexadecimal string.
107
+ ///
108
+ /// @param hex - A 64-character hexadecimal string
109
+ /// @returns A new Digest instance
110
+ /// @throws {Error} If the hex string is not exactly 64 characters
111
+ ///
112
+ /// @example
113
+ /// ```typescript
114
+ /// const digest = Digest.fromHex("5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9");
115
+ /// ```
116
+ static fromHex(hex: string): Digest {
117
+ if (hex.length !== 64) {
118
+ throw new Error(`Hex string must be exactly 64 characters, got ${hex.length}`);
119
+ }
120
+ const data = new Uint8Array(32);
121
+ for (let i = 0; i < 32; i++) {
122
+ data[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
123
+ }
124
+ return new Digest(data);
125
+ }
126
+
127
+ /// Checks if two digests are equal.
128
+ ///
129
+ /// @param other - The other digest to compare with
130
+ /// @returns `true` if the digests are equal, `false` otherwise
131
+ equals(other: Digest): boolean {
132
+ if (this.#data.length !== other.#data.length) {
133
+ return false;
134
+ }
135
+ for (let i = 0; i < this.#data.length; i++) {
136
+ if (this.#data[i] !== other.#data[i]) {
137
+ return false;
138
+ }
139
+ }
140
+ return true;
141
+ }
142
+
143
+ /// Returns a string representation of the digest (short form).
144
+ ///
145
+ /// @returns The short hexadecimal representation
146
+ toString(): string {
147
+ return this.short();
148
+ }
149
+
150
+ /// Creates a deep copy of the digest.
151
+ ///
152
+ /// @returns A new Digest instance with the same data
153
+ clone(): Digest {
154
+ return new Digest(new Uint8Array(this.#data));
155
+ }
156
+ }
157
+
158
+ /// Trait for types that can provide a digest.
159
+ ///
160
+ /// This is equivalent to Rust's `DigestProvider` trait. Types that
161
+ /// implement this interface can be used in contexts where a digest
162
+ /// is needed for identity or integrity verification.
163
+ export interface DigestProvider {
164
+ /// Returns the digest of this object.
165
+ ///
166
+ /// The digest uniquely identifies the semantic content of the object,
167
+ /// regardless of whether parts of it are elided, encrypted, or compressed.
168
+ digest(): Digest;
169
+ }
170
+
171
+ /// Helper function to create a digest from a string.
172
+ ///
173
+ /// This is a convenience function for creating digests from text strings,
174
+ /// which are encoded as UTF-8 before hashing.
175
+ ///
176
+ /// @param text - The text to hash
177
+ /// @returns A new Digest instance
178
+ ///
179
+ /// @example
180
+ /// ```typescript
181
+ /// const digest = digestFromString("Hello, world!");
182
+ /// ```
183
+ export function digestFromString(text: string): Digest {
184
+ const encoder = new TextEncoder();
185
+ return Digest.fromImage(encoder.encode(text));
186
+ }
187
+
188
+ /// Helper function to create a digest from a number.
189
+ ///
190
+ /// The number is converted to a big-endian byte representation before hashing.
191
+ ///
192
+ /// @param num - The number to hash
193
+ /// @returns A new Digest instance
194
+ ///
195
+ /// @example
196
+ /// ```typescript
197
+ /// const digest = digestFromNumber(42);
198
+ /// ```
199
+ export function digestFromNumber(num: number): Digest {
200
+ const buffer = new ArrayBuffer(8);
201
+ const view = new DataView(buffer);
202
+ view.setFloat64(0, num, false); // big-endian
203
+ return Digest.fromImage(new Uint8Array(buffer));
204
+ }
@@ -0,0 +1,390 @@
1
+ import { type Digest, type DigestProvider } from "./digest";
2
+ import { Envelope } from "./envelope";
3
+ import { Assertion } from "./assertion";
4
+ import { EnvelopeError } from "./error";
5
+
6
+ /// Types of obscuration that can be applied to envelope elements.
7
+ ///
8
+ /// This enum identifies the different ways an envelope element can be obscured.
9
+ export enum ObscureType {
10
+ /// The element has been elided, showing only its digest.
11
+ Elided = "elided",
12
+
13
+ /// The element has been encrypted using symmetric encryption.
14
+ /// TODO: Implement when encrypt feature is added
15
+ Encrypted = "encrypted",
16
+
17
+ /// The element has been compressed to reduce its size.
18
+ /// TODO: Implement when compress feature is added
19
+ Compressed = "compressed",
20
+ }
21
+
22
+ /// Actions that can be performed on parts of an envelope to obscure them.
23
+ ///
24
+ /// Gordian Envelope supports several ways to obscure parts of an envelope while
25
+ /// maintaining its semantic integrity and digest tree.
26
+ export type ObscureAction =
27
+ | { type: "elide" }
28
+ | { type: "encrypt"; key: unknown } // TODO: SymmetricKey type
29
+ | { type: "compress" };
30
+
31
+ /// Helper to create elide action
32
+ export function elideAction(): ObscureAction {
33
+ return { type: "elide" };
34
+ }
35
+
36
+ /// Implementation of elide()
37
+ Envelope.prototype.elide = function (this: Envelope): Envelope {
38
+ const c = this.case();
39
+ if (c.type === "elided") {
40
+ return this;
41
+ }
42
+ return Envelope.newElided(this.digest());
43
+ };
44
+
45
+ /// Core elision logic
46
+ function elideSetWithAction(
47
+ envelope: Envelope,
48
+ target: Set<Digest>,
49
+ isRevealing: boolean,
50
+ action: ObscureAction,
51
+ ): Envelope {
52
+ const selfDigest = envelope.digest();
53
+ const targetContainsSelf = Array.from(target).some((d) => d.equals(selfDigest));
54
+
55
+ // Target Matches isRevealing elide
56
+ // false false false
57
+ // false true true
58
+ // true false true
59
+ // true true false
60
+
61
+ if (targetContainsSelf !== isRevealing) {
62
+ // Should obscure this envelope
63
+ if (action.type === "elide") {
64
+ return envelope.elide();
65
+ } else if (action.type === "encrypt") {
66
+ // TODO: Implement encryption
67
+ throw new Error("Encryption not yet implemented");
68
+ } else if (action.type === "compress") {
69
+ // TODO: Implement compression
70
+ throw new Error("Compression not yet implemented");
71
+ }
72
+ }
73
+
74
+ const c = envelope.case();
75
+
76
+ // Recursively process structure
77
+ if (c.type === "assertion") {
78
+ const predicate = elideSetWithAction(c.assertion.predicate(), target, isRevealing, action);
79
+ const object = elideSetWithAction(c.assertion.object(), target, isRevealing, action);
80
+ const elidedAssertion = new Assertion(predicate, object);
81
+ return Envelope.newWithAssertion(elidedAssertion);
82
+ } else if (c.type === "node") {
83
+ const elidedSubject = elideSetWithAction(c.subject, target, isRevealing, action);
84
+ const elidedAssertions = c.assertions.map((a) =>
85
+ elideSetWithAction(a, target, isRevealing, action),
86
+ );
87
+ return Envelope.newWithUncheckedAssertions(elidedSubject, elidedAssertions);
88
+ } else if (c.type === "wrapped") {
89
+ const elidedEnvelope = elideSetWithAction(c.envelope, target, isRevealing, action);
90
+ return Envelope.newWrapped(elidedEnvelope);
91
+ }
92
+
93
+ return envelope;
94
+ }
95
+
96
+ /// Implementation of elideRemovingSetWithAction
97
+ Envelope.prototype.elideRemovingSetWithAction = function (
98
+ this: Envelope,
99
+ target: Set<Digest>,
100
+ action: ObscureAction,
101
+ ): Envelope {
102
+ return elideSetWithAction(this, target, false, action);
103
+ };
104
+
105
+ /// Implementation of elideRemovingSet
106
+ Envelope.prototype.elideRemovingSet = function (this: Envelope, target: Set<Digest>): Envelope {
107
+ return elideSetWithAction(this, target, false, elideAction());
108
+ };
109
+
110
+ /// Implementation of elideRemovingArrayWithAction
111
+ Envelope.prototype.elideRemovingArrayWithAction = function (
112
+ this: Envelope,
113
+ target: DigestProvider[],
114
+ action: ObscureAction,
115
+ ): Envelope {
116
+ const targetSet = new Set(target.map((p) => p.digest()));
117
+ return elideSetWithAction(this, targetSet, false, action);
118
+ };
119
+
120
+ /// Implementation of elideRemovingArray
121
+ Envelope.prototype.elideRemovingArray = function (
122
+ this: Envelope,
123
+ target: DigestProvider[],
124
+ ): Envelope {
125
+ const targetSet = new Set(target.map((p) => p.digest()));
126
+ return elideSetWithAction(this, targetSet, false, elideAction());
127
+ };
128
+
129
+ /// Implementation of elideRemovingTargetWithAction
130
+ Envelope.prototype.elideRemovingTargetWithAction = function (
131
+ this: Envelope,
132
+ target: DigestProvider,
133
+ action: ObscureAction,
134
+ ): Envelope {
135
+ return this.elideRemovingArrayWithAction([target], action);
136
+ };
137
+
138
+ /// Implementation of elideRemovingTarget
139
+ Envelope.prototype.elideRemovingTarget = function (
140
+ this: Envelope,
141
+ target: DigestProvider,
142
+ ): Envelope {
143
+ return this.elideRemovingArray([target]);
144
+ };
145
+
146
+ /// Implementation of elideRevealingSetWithAction
147
+ Envelope.prototype.elideRevealingSetWithAction = function (
148
+ this: Envelope,
149
+ target: Set<Digest>,
150
+ action: ObscureAction,
151
+ ): Envelope {
152
+ return elideSetWithAction(this, target, true, action);
153
+ };
154
+
155
+ /// Implementation of elideRevealingSet
156
+ Envelope.prototype.elideRevealingSet = function (this: Envelope, target: Set<Digest>): Envelope {
157
+ return elideSetWithAction(this, target, true, elideAction());
158
+ };
159
+
160
+ /// Implementation of elideRevealingArrayWithAction
161
+ Envelope.prototype.elideRevealingArrayWithAction = function (
162
+ this: Envelope,
163
+ target: DigestProvider[],
164
+ action: ObscureAction,
165
+ ): Envelope {
166
+ const targetSet = new Set(target.map((p) => p.digest()));
167
+ return elideSetWithAction(this, targetSet, true, action);
168
+ };
169
+
170
+ /// Implementation of elideRevealingArray
171
+ Envelope.prototype.elideRevealingArray = function (
172
+ this: Envelope,
173
+ target: DigestProvider[],
174
+ ): Envelope {
175
+ const targetSet = new Set(target.map((p) => p.digest()));
176
+ return elideSetWithAction(this, targetSet, true, elideAction());
177
+ };
178
+
179
+ /// Implementation of elideRevealingTargetWithAction
180
+ Envelope.prototype.elideRevealingTargetWithAction = function (
181
+ this: Envelope,
182
+ target: DigestProvider,
183
+ action: ObscureAction,
184
+ ): Envelope {
185
+ return this.elideRevealingArrayWithAction([target], action);
186
+ };
187
+
188
+ /// Implementation of elideRevealingTarget
189
+ Envelope.prototype.elideRevealingTarget = function (
190
+ this: Envelope,
191
+ target: DigestProvider,
192
+ ): Envelope {
193
+ return this.elideRevealingArray([target]);
194
+ };
195
+
196
+ /// Implementation of unelide
197
+ Envelope.prototype.unelide = function (this: Envelope, envelope: Envelope): Envelope {
198
+ if (this.digest().equals(envelope.digest())) {
199
+ return envelope;
200
+ }
201
+ throw EnvelopeError.invalidDigest();
202
+ };
203
+
204
+ /// Implementation of nodesMatching
205
+ Envelope.prototype.nodesMatching = function (
206
+ this: Envelope,
207
+ targetDigests: Set<Digest> | undefined,
208
+ obscureTypes: ObscureType[],
209
+ ): Set<Digest> {
210
+ const result = new Set<Digest>();
211
+
212
+ const visitor = (envelope: Envelope): void => {
213
+ // Check if this node matches the target digests
214
+ const digestMatches =
215
+ targetDigests === undefined ||
216
+ Array.from(targetDigests).some((d) => d.equals(envelope.digest()));
217
+
218
+ if (!digestMatches) {
219
+ return;
220
+ }
221
+
222
+ // If no obscure types specified, include all nodes
223
+ if (obscureTypes.length === 0) {
224
+ result.add(envelope.digest());
225
+ return;
226
+ }
227
+
228
+ // Check if this node matches any of the specified obscure types
229
+ const c = envelope.case();
230
+ const typeMatches = obscureTypes.some((obscureType) => {
231
+ if (obscureType === ObscureType.Elided && c.type === "elided") {
232
+ return true;
233
+ }
234
+ if (obscureType === ObscureType.Encrypted && c.type === "encrypted") {
235
+ return true;
236
+ }
237
+ if (obscureType === ObscureType.Compressed && c.type === "compressed") {
238
+ return true;
239
+ }
240
+ return false;
241
+ });
242
+
243
+ if (typeMatches) {
244
+ result.add(envelope.digest());
245
+ }
246
+ };
247
+
248
+ // Walk the envelope tree
249
+ walkEnvelope(this, visitor);
250
+
251
+ return result;
252
+ };
253
+
254
+ /// Helper to walk envelope tree
255
+ function walkEnvelope(envelope: Envelope, visitor: (e: Envelope) => void): void {
256
+ visitor(envelope);
257
+
258
+ const c = envelope.case();
259
+ if (c.type === "node") {
260
+ walkEnvelope(c.subject, visitor);
261
+ for (const assertion of c.assertions) {
262
+ walkEnvelope(assertion, visitor);
263
+ }
264
+ } else if (c.type === "assertion") {
265
+ walkEnvelope(c.assertion.predicate(), visitor);
266
+ walkEnvelope(c.assertion.object(), visitor);
267
+ } else if (c.type === "wrapped") {
268
+ walkEnvelope(c.envelope, visitor);
269
+ }
270
+ }
271
+
272
+ /// Implementation of walkUnelide
273
+ Envelope.prototype.walkUnelide = function (this: Envelope, envelopes: Envelope[]): Envelope {
274
+ // Build a lookup map of digest -> envelope
275
+ const envelopeMap = new Map<string, Envelope>();
276
+ for (const env of envelopes) {
277
+ envelopeMap.set(env.digest().hex(), env);
278
+ }
279
+
280
+ return walkUnelideWithMap(this, envelopeMap);
281
+ };
282
+
283
+ /// Helper for walkUnelide with map
284
+ function walkUnelideWithMap(envelope: Envelope, envelopeMap: Map<string, Envelope>): Envelope {
285
+ const c = envelope.case();
286
+
287
+ if (c.type === "elided") {
288
+ // Try to find a matching envelope to restore
289
+ const replacement = envelopeMap.get(envelope.digest().hex());
290
+ return replacement ?? envelope;
291
+ }
292
+
293
+ if (c.type === "node") {
294
+ const newSubject = walkUnelideWithMap(c.subject, envelopeMap);
295
+ const newAssertions = c.assertions.map((a) => walkUnelideWithMap(a, envelopeMap));
296
+
297
+ if (
298
+ newSubject.isIdenticalTo(c.subject) &&
299
+ newAssertions.every((a, i) => a.isIdenticalTo(c.assertions[i]))
300
+ ) {
301
+ return envelope;
302
+ }
303
+
304
+ return Envelope.newWithUncheckedAssertions(newSubject, newAssertions);
305
+ }
306
+
307
+ if (c.type === "wrapped") {
308
+ const newEnvelope = walkUnelideWithMap(c.envelope, envelopeMap);
309
+ if (newEnvelope.isIdenticalTo(c.envelope)) {
310
+ return envelope;
311
+ }
312
+ return Envelope.newWrapped(newEnvelope);
313
+ }
314
+
315
+ if (c.type === "assertion") {
316
+ const newPredicate = walkUnelideWithMap(c.assertion.predicate(), envelopeMap);
317
+ const newObject = walkUnelideWithMap(c.assertion.object(), envelopeMap);
318
+
319
+ if (
320
+ newPredicate.isIdenticalTo(c.assertion.predicate()) &&
321
+ newObject.isIdenticalTo(c.assertion.object())
322
+ ) {
323
+ return envelope;
324
+ }
325
+
326
+ return Envelope.newAssertion(newPredicate, newObject);
327
+ }
328
+
329
+ return envelope;
330
+ }
331
+
332
+ /// Implementation of walkReplace
333
+ Envelope.prototype.walkReplace = function (
334
+ this: Envelope,
335
+ target: Set<Digest>,
336
+ replacement: Envelope,
337
+ ): Envelope {
338
+ // Check if this node matches the target
339
+ if (Array.from(target).some((d) => d.equals(this.digest()))) {
340
+ return replacement;
341
+ }
342
+
343
+ const c = this.case();
344
+
345
+ if (c.type === "node") {
346
+ const newSubject = c.subject.walkReplace(target, replacement);
347
+ const newAssertions = c.assertions.map((a) => a.walkReplace(target, replacement));
348
+
349
+ if (
350
+ newSubject.isIdenticalTo(c.subject) &&
351
+ newAssertions.every((a, i) => a.isIdenticalTo(c.assertions[i]))
352
+ ) {
353
+ return this;
354
+ }
355
+
356
+ // Validate that all assertions are either assertions or obscured
357
+ return Envelope.newWithAssertions(newSubject, newAssertions);
358
+ }
359
+
360
+ if (c.type === "wrapped") {
361
+ const newEnvelope = c.envelope.walkReplace(target, replacement);
362
+ if (newEnvelope.isIdenticalTo(c.envelope)) {
363
+ return this;
364
+ }
365
+ return Envelope.newWrapped(newEnvelope);
366
+ }
367
+
368
+ if (c.type === "assertion") {
369
+ const newPredicate = c.assertion.predicate().walkReplace(target, replacement);
370
+ const newObject = c.assertion.object().walkReplace(target, replacement);
371
+
372
+ if (
373
+ newPredicate.isIdenticalTo(c.assertion.predicate()) &&
374
+ newObject.isIdenticalTo(c.assertion.object())
375
+ ) {
376
+ return this;
377
+ }
378
+
379
+ return Envelope.newAssertion(newPredicate, newObject);
380
+ }
381
+
382
+ return this;
383
+ };
384
+
385
+ /// Implementation of isIdenticalTo
386
+ Envelope.prototype.isIdenticalTo = function (this: Envelope, other: Envelope): boolean {
387
+ // Two envelopes are identical if they have the same digest
388
+ // and the same case type (to handle wrapped vs unwrapped with same content)
389
+ return this.digest().equals(other.digest()) && this.case().type === other.case().type;
390
+ };