@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.
- package/LICENSE +48 -0
- package/README.md +23 -0
- package/dist/index.cjs +2646 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +978 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +978 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +2644 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +2552 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +85 -0
- package/src/base/assertion.ts +179 -0
- package/src/base/assertions.ts +304 -0
- package/src/base/cbor.ts +122 -0
- package/src/base/digest.ts +204 -0
- package/src/base/elide.ts +526 -0
- package/src/base/envelope-decodable.ts +229 -0
- package/src/base/envelope-encodable.ts +71 -0
- package/src/base/envelope.ts +790 -0
- package/src/base/error.ts +421 -0
- package/src/base/index.ts +56 -0
- package/src/base/leaf.ts +226 -0
- package/src/base/queries.ts +374 -0
- package/src/base/walk.ts +241 -0
- package/src/base/wrap.ts +72 -0
- package/src/extension/attachment.ts +369 -0
- package/src/extension/compress.ts +293 -0
- package/src/extension/encrypt.ts +379 -0
- package/src/extension/expression.ts +404 -0
- package/src/extension/index.ts +72 -0
- package/src/extension/proof.ts +276 -0
- package/src/extension/recipient.ts +557 -0
- package/src/extension/salt.ts +223 -0
- package/src/extension/signature.ts +463 -0
- package/src/extension/types.ts +222 -0
- package/src/format/diagnostic.ts +116 -0
- package/src/format/hex.ts +25 -0
- package/src/format/index.ts +13 -0
- package/src/format/tree.ts +168 -0
- package/src/index.ts +32 -0
- package/src/utils/index.ts +8 -0
- 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 {};
|