@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,276 @@
1
+ import { Envelope } from "../base/envelope";
2
+ import { type Digest } from "../base/digest";
3
+
4
+ /// Extension for envelope inclusion proofs.
5
+ ///
6
+ /// Inclusion proofs allow a holder of an envelope to prove that specific
7
+ /// elements exist within the envelope without revealing the entire contents.
8
+ /// This is particularly useful for selective disclosure of information in
9
+ /// privacy-preserving scenarios.
10
+ ///
11
+ /// ## How Inclusion Proofs Work
12
+ ///
13
+ /// The inclusion proof mechanism leverages the Merkle-like digest tree
14
+ /// structure of envelopes:
15
+ /// - The holder creates a minimal structure containing only the digests
16
+ /// necessary to validate the proof
17
+ /// - A verifier with a trusted root digest can confirm that the specific
18
+ /// elements exist in the original envelope
19
+ /// - All other content can remain elided, preserving privacy
20
+ ///
21
+ /// For enhanced privacy, elements can be salted to prevent correlation attacks.
22
+ ///
23
+ /// @example
24
+ /// ```typescript
25
+ /// // Create an envelope with multiple assertions
26
+ /// const aliceFriends = Envelope.new('Alice')
27
+ /// .addAssertion('knows', 'Bob')
28
+ /// .addAssertion('knows', 'Carol')
29
+ /// .addAssertion('knows', 'Dan');
30
+ ///
31
+ /// // Create a representation of just the root digest
32
+ /// const aliceFriendsRoot = aliceFriends.elideRevealingSet(new Set());
33
+ ///
34
+ /// // Create the target we want to prove exists
35
+ /// const knowsBobAssertion = Envelope.newAssertion('knows', 'Bob');
36
+ ///
37
+ /// // Generate a proof that Alice knows Bob
38
+ /// const aliceKnowsBobProof = aliceFriends.proofContainsTarget(knowsBobAssertion);
39
+ ///
40
+ /// // A third party can verify the proof against the trusted root
41
+ /// if (aliceKnowsBobProof) {
42
+ /// const isValid = aliceFriendsRoot.confirmContainsTarget(
43
+ /// knowsBobAssertion,
44
+ /// aliceKnowsBobProof
45
+ /// );
46
+ /// console.log('Proof is valid:', isValid);
47
+ /// }
48
+ /// ```
49
+
50
+ declare module "../base/envelope" {
51
+ interface Envelope {
52
+ /// Creates a proof that this envelope includes every element in the target set.
53
+ ///
54
+ /// An inclusion proof is a specially constructed envelope that:
55
+ /// - Has the same digest as the original envelope (or an elided version of it)
56
+ /// - Contains the minimal structure needed to prove the existence of target elements
57
+ /// - Keeps all other content elided to preserve privacy
58
+ ///
59
+ /// @param target - The set of digests representing elements that the proof must include
60
+ /// @returns A proof envelope if all targets can be proven to exist, undefined otherwise
61
+ proofContainsSet(target: Set<Digest>): Envelope | undefined;
62
+
63
+ /// Creates a proof that this envelope includes the single target element.
64
+ ///
65
+ /// This is a convenience method that wraps `proofContainsSet()` for the
66
+ /// common case of proving the existence of just one element.
67
+ ///
68
+ /// @param target - The element that the proof must demonstrate exists in this envelope
69
+ /// @returns A proof envelope if the target can be proven to exist, undefined otherwise
70
+ proofContainsTarget(target: Envelope): Envelope | undefined;
71
+
72
+ /// Verifies whether this envelope contains all elements in the target set
73
+ /// using the given inclusion proof.
74
+ ///
75
+ /// This method is used by a verifier to check if a proof demonstrates the
76
+ /// existence of all target elements within this envelope. The verification
77
+ /// succeeds only if:
78
+ /// 1. The proof's digest matches this envelope's digest
79
+ /// 2. The proof contains all the target elements
80
+ ///
81
+ /// @param target - The set of digests representing elements that need to be proven to exist
82
+ /// @param proof - The inclusion proof envelope to verify
83
+ /// @returns true if all target elements are proven to exist in this envelope by the proof
84
+ confirmContainsSet(target: Set<Digest>, proof: Envelope): boolean;
85
+
86
+ /// Verifies whether this envelope contains the single target element using
87
+ /// the given inclusion proof.
88
+ ///
89
+ /// This is a convenience method that wraps `confirmContainsSet()` for the
90
+ /// common case of verifying just one element.
91
+ ///
92
+ /// @param target - The element that needs to be proven to exist in this envelope
93
+ /// @param proof - The inclusion proof envelope to verify
94
+ /// @returns true if the target element is proven to exist in this envelope by the proof
95
+ confirmContainsTarget(target: Envelope, proof: Envelope): boolean;
96
+ }
97
+ }
98
+
99
+ /// Implementation of proof methods on Envelope prototype
100
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
101
+ if (Envelope?.prototype) {
102
+ Envelope.prototype.proofContainsSet = function (target: Set<Digest>): Envelope | undefined {
103
+ const revealSet = revealSetOfSet(this, target);
104
+
105
+ // Check if all targets can be revealed
106
+ if (!isSubset(target, revealSet)) {
107
+ return undefined;
108
+ }
109
+
110
+ // Create a proof by revealing only what's necessary, then eliding the targets
111
+ const revealed = this.elideRevealingSet(revealSet);
112
+ return revealed.elideRemovingSet(target);
113
+ };
114
+
115
+ Envelope.prototype.proofContainsTarget = function (target: Envelope): Envelope | undefined {
116
+ const targetSet = new Set<Digest>([target.digest()]);
117
+ return this.proofContainsSet(targetSet);
118
+ };
119
+
120
+ Envelope.prototype.confirmContainsSet = function (target: Set<Digest>, proof: Envelope): boolean {
121
+ // Verify the proof has the same digest as this envelope
122
+ if (this.digest().hex() !== proof.digest().hex()) {
123
+ return false;
124
+ }
125
+
126
+ // Verify the proof contains all target elements
127
+ return containsAll(proof, target);
128
+ };
129
+
130
+ Envelope.prototype.confirmContainsTarget = function (target: Envelope, proof: Envelope): boolean {
131
+ const targetSet = new Set<Digest>([target.digest()]);
132
+ return this.confirmContainsSet(targetSet, proof);
133
+ };
134
+ }
135
+
136
+ /// Internal helper functions
137
+
138
+ /// Builds a set of all digests needed to reveal the target set.
139
+ ///
140
+ /// This collects all digests in the path from the envelope's root to each
141
+ /// target element.
142
+ function revealSetOfSet(envelope: Envelope, target: Set<Digest>): Set<Digest> {
143
+ const result = new Set<Digest>();
144
+ revealSets(envelope, target, new Set<Digest>(), result);
145
+ return result;
146
+ }
147
+
148
+ /// Recursively traverses the envelope to collect all digests needed to
149
+ /// reveal the target set.
150
+ ///
151
+ /// Builds the set of digests forming the path from the root to each target element.
152
+ function revealSets(
153
+ envelope: Envelope,
154
+ target: Set<Digest>,
155
+ current: Set<Digest>,
156
+ result: Set<Digest>,
157
+ ): void {
158
+ // Add current envelope's digest to the path
159
+ const newCurrent = new Set(current);
160
+ newCurrent.add(envelope.digest());
161
+
162
+ // If this is a target, add the entire path to the result
163
+ if (containsDigest(target, envelope.digest())) {
164
+ for (const digest of newCurrent) {
165
+ result.add(digest);
166
+ }
167
+ }
168
+
169
+ // Traverse the envelope structure
170
+ const envelopeCase = envelope.case();
171
+
172
+ if (envelopeCase.type === "node") {
173
+ // Traverse subject
174
+ revealSets(envelopeCase.subject, target, newCurrent, result);
175
+
176
+ // Traverse all assertions
177
+ for (const assertion of envelopeCase.assertions) {
178
+ revealSets(assertion, target, newCurrent, result);
179
+ }
180
+ } else if (envelopeCase.type === "wrapped") {
181
+ // Traverse wrapped envelope
182
+ revealSets(envelopeCase.envelope, target, newCurrent, result);
183
+ } else if (envelopeCase.type === "assertion") {
184
+ // Traverse predicate and object
185
+ const predicate = envelopeCase.assertion.predicate();
186
+ const object = envelopeCase.assertion.object();
187
+ revealSets(predicate, target, newCurrent, result);
188
+ revealSets(object, target, newCurrent, result);
189
+ }
190
+ // For leaf envelopes (elided, encrypted, compressed, leaf), no further traversal needed
191
+ }
192
+
193
+ /// Checks if this envelope contains all elements in the target set.
194
+ ///
195
+ /// Used during proof verification to confirm all target elements exist in the proof.
196
+ function containsAll(envelope: Envelope, target: Set<Digest>): boolean {
197
+ const targetCopy = new Set(target);
198
+ removeAllFound(envelope, targetCopy);
199
+ return targetCopy.size === 0;
200
+ }
201
+
202
+ /// Recursively traverses the envelope and removes found target elements from the set.
203
+ ///
204
+ /// Used during proof verification to confirm all target elements are present.
205
+ function removeAllFound(envelope: Envelope, target: Set<Digest>): void {
206
+ // Check if this envelope's digest is in the target set
207
+ if (containsDigest(target, envelope.digest())) {
208
+ removeDigest(target, envelope.digest());
209
+ }
210
+
211
+ // Early exit if all targets found
212
+ if (target.size === 0) {
213
+ return;
214
+ }
215
+
216
+ // Traverse the envelope structure
217
+ const envelopeCase = envelope.case();
218
+
219
+ if (envelopeCase.type === "node") {
220
+ // Traverse subject
221
+ removeAllFound(envelopeCase.subject, target);
222
+
223
+ // Traverse all assertions
224
+ for (const assertion of envelopeCase.assertions) {
225
+ removeAllFound(assertion, target);
226
+ if (target.size === 0) break;
227
+ }
228
+ } else if (envelopeCase.type === "wrapped") {
229
+ // Traverse wrapped envelope
230
+ removeAllFound(envelopeCase.envelope, target);
231
+ } else if (envelopeCase.type === "assertion") {
232
+ // Traverse predicate and object
233
+ const predicate = envelopeCase.assertion.predicate();
234
+ const object = envelopeCase.assertion.object();
235
+ removeAllFound(predicate, target);
236
+ if (target.size > 0) {
237
+ removeAllFound(object, target);
238
+ }
239
+ }
240
+ // For leaf envelopes (elided, encrypted, compressed, leaf), no further traversal needed
241
+ }
242
+
243
+ /// Helper function to check if a set contains a digest (by hex comparison)
244
+ function containsDigest(set: Set<Digest>, digest: Digest): boolean {
245
+ const hexToFind = digest.hex();
246
+ for (const d of set) {
247
+ if (d.hex() === hexToFind) {
248
+ return true;
249
+ }
250
+ }
251
+ return false;
252
+ }
253
+
254
+ /// Helper function to remove a digest from a set (by hex comparison)
255
+ function removeDigest(set: Set<Digest>, digest: Digest): void {
256
+ const hexToFind = digest.hex();
257
+ for (const d of set) {
258
+ if (d.hex() === hexToFind) {
259
+ set.delete(d);
260
+ return;
261
+ }
262
+ }
263
+ }
264
+
265
+ /// Helper function to check if one set is a subset of another (by hex comparison)
266
+ function isSubset(subset: Set<Digest>, superset: Set<Digest>): boolean {
267
+ for (const digest of subset) {
268
+ if (!containsDigest(superset, digest)) {
269
+ return false;
270
+ }
271
+ }
272
+ return true;
273
+ }
274
+
275
+ // Export empty object to make this a module
276
+ export {};