@bcts/xid 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.
@@ -0,0 +1,160 @@
1
+ /**
2
+ * XID Privileges
3
+ *
4
+ * Defines the various privileges that can be granted to keys and delegates
5
+ * in an XID document.
6
+ *
7
+ * Ported from bc-xid-rust/src/privilege.rs
8
+ */
9
+
10
+ import {
11
+ type KnownValue,
12
+ PRIVILEGE_ALL,
13
+ PRIVILEGE_AUTH,
14
+ PRIVILEGE_SIGN,
15
+ PRIVILEGE_ENCRYPT,
16
+ PRIVILEGE_ELIDE,
17
+ PRIVILEGE_ISSUE,
18
+ PRIVILEGE_ACCESS,
19
+ PRIVILEGE_DELEGATE,
20
+ PRIVILEGE_VERIFY,
21
+ PRIVILEGE_UPDATE,
22
+ PRIVILEGE_TRANSFER,
23
+ PRIVILEGE_ELECT,
24
+ PRIVILEGE_BURN,
25
+ PRIVILEGE_REVOKE,
26
+ } from "@bcts/known-values";
27
+ import { Envelope } from "@bcts/envelope";
28
+ import { XIDError } from "./error";
29
+
30
+ /**
31
+ * Enum representing XID privileges.
32
+ */
33
+ export enum Privilege {
34
+ // Operational Functions
35
+ /** Allow all applicable XID operations */
36
+ All = "All",
37
+ /** Authenticate as the subject (e.g., log into services) */
38
+ Auth = "Auth",
39
+ /** Sign digital communications as the subject */
40
+ Sign = "Sign",
41
+ /** Encrypt messages from the subject */
42
+ Encrypt = "Encrypt",
43
+ /** Elide data under the subject's control */
44
+ Elide = "Elide",
45
+ /** Issue or revoke verifiable credentials on the subject's authority */
46
+ Issue = "Issue",
47
+ /** Access resources under the subject's control */
48
+ Access = "Access",
49
+
50
+ // Management Functions
51
+ /** Delegate privileges to third parties */
52
+ Delegate = "Delegate",
53
+ /** Verify (update) the XID document */
54
+ Verify = "Verify",
55
+ /** Update service endpoints */
56
+ Update = "Update",
57
+ /** Remove the inception key from the XID document */
58
+ Transfer = "Transfer",
59
+ /** Add or remove other verifiers (rotate keys) */
60
+ Elect = "Elect",
61
+ /** Transition to a new provenance mark chain */
62
+ Burn = "Burn",
63
+ /** Revoke the XID entirely */
64
+ Revoke = "Revoke",
65
+ }
66
+
67
+ /**
68
+ * Convert a Privilege to its corresponding KnownValue.
69
+ */
70
+ export function privilegeToKnownValue(privilege: Privilege): KnownValue {
71
+ switch (privilege) {
72
+ case Privilege.All:
73
+ return PRIVILEGE_ALL;
74
+ case Privilege.Auth:
75
+ return PRIVILEGE_AUTH;
76
+ case Privilege.Sign:
77
+ return PRIVILEGE_SIGN;
78
+ case Privilege.Encrypt:
79
+ return PRIVILEGE_ENCRYPT;
80
+ case Privilege.Elide:
81
+ return PRIVILEGE_ELIDE;
82
+ case Privilege.Issue:
83
+ return PRIVILEGE_ISSUE;
84
+ case Privilege.Access:
85
+ return PRIVILEGE_ACCESS;
86
+ case Privilege.Delegate:
87
+ return PRIVILEGE_DELEGATE;
88
+ case Privilege.Verify:
89
+ return PRIVILEGE_VERIFY;
90
+ case Privilege.Update:
91
+ return PRIVILEGE_UPDATE;
92
+ case Privilege.Transfer:
93
+ return PRIVILEGE_TRANSFER;
94
+ case Privilege.Elect:
95
+ return PRIVILEGE_ELECT;
96
+ case Privilege.Burn:
97
+ return PRIVILEGE_BURN;
98
+ case Privilege.Revoke:
99
+ return PRIVILEGE_REVOKE;
100
+ default:
101
+ throw XIDError.unknownPrivilege();
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Convert a KnownValue to its corresponding Privilege.
107
+ */
108
+ export function privilegeFromKnownValue(knownValue: KnownValue): Privilege {
109
+ const value = knownValue.value();
110
+ switch (value) {
111
+ case PRIVILEGE_ALL.value():
112
+ return Privilege.All;
113
+ case PRIVILEGE_AUTH.value():
114
+ return Privilege.Auth;
115
+ case PRIVILEGE_SIGN.value():
116
+ return Privilege.Sign;
117
+ case PRIVILEGE_ENCRYPT.value():
118
+ return Privilege.Encrypt;
119
+ case PRIVILEGE_ELIDE.value():
120
+ return Privilege.Elide;
121
+ case PRIVILEGE_ISSUE.value():
122
+ return Privilege.Issue;
123
+ case PRIVILEGE_ACCESS.value():
124
+ return Privilege.Access;
125
+ case PRIVILEGE_DELEGATE.value():
126
+ return Privilege.Delegate;
127
+ case PRIVILEGE_VERIFY.value():
128
+ return Privilege.Verify;
129
+ case PRIVILEGE_UPDATE.value():
130
+ return Privilege.Update;
131
+ case PRIVILEGE_TRANSFER.value():
132
+ return Privilege.Transfer;
133
+ case PRIVILEGE_ELECT.value():
134
+ return Privilege.Elect;
135
+ case PRIVILEGE_BURN.value():
136
+ return Privilege.Burn;
137
+ case PRIVILEGE_REVOKE.value():
138
+ return Privilege.Revoke;
139
+ default:
140
+ throw XIDError.unknownPrivilege();
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Convert a Privilege to an Envelope.
146
+ */
147
+ export function privilegeToEnvelope(privilege: Privilege): Envelope {
148
+ return Envelope.newWithKnownValue(privilegeToKnownValue(privilege));
149
+ }
150
+
151
+ /**
152
+ * Convert an Envelope to a Privilege.
153
+ */
154
+ export function privilegeFromEnvelope(envelope: Envelope): Privilege {
155
+ const envelopeCase = envelope.case();
156
+ if (envelopeCase.type !== "knownValue") {
157
+ throw XIDError.unknownPrivilege();
158
+ }
159
+ return privilegeFromKnownValue(envelopeCase.value);
160
+ }
@@ -0,0 +1,374 @@
1
+ /**
2
+ * XID Provenance
3
+ *
4
+ * Represents provenance information in an XID document, containing a provenance mark
5
+ * and optional generator.
6
+ *
7
+ * Ported from bc-xid-rust/src/provenance.rs
8
+ */
9
+
10
+ import { Envelope, type EnvelopeEncodable, type EnvelopeEncodableValue } from "@bcts/envelope";
11
+ import { PROVENANCE_GENERATOR, SALT, type KnownValue } from "@bcts/known-values";
12
+ import { Salt } from "@bcts/components";
13
+
14
+ // Helper to convert KnownValue to EnvelopeEncodableValue
15
+ const kv = (v: KnownValue): EnvelopeEncodableValue => v as unknown as EnvelopeEncodableValue;
16
+ import { ProvenanceMark, ProvenanceMarkGenerator } from "@bcts/provenance-mark";
17
+ import { cborData, decodeCbor } from "@bcts/dcbor";
18
+ import { XIDError } from "./error";
19
+
20
+ /**
21
+ * Options for handling generators in envelopes.
22
+ */
23
+ export enum XIDGeneratorOptions {
24
+ /** Omit the generator from the envelope (default). */
25
+ Omit = "Omit",
26
+ /** Include the generator in plaintext (with salt for decorrelation). */
27
+ Include = "Include",
28
+ /** Include the generator assertion but elide it (maintains digest tree). */
29
+ Elide = "Elide",
30
+ /** Include the generator encrypted with a password. */
31
+ Encrypt = "Encrypt",
32
+ }
33
+
34
+ /**
35
+ * Configuration for encrypting generators.
36
+ */
37
+ export interface XIDGeneratorEncryptConfig {
38
+ type: XIDGeneratorOptions.Encrypt;
39
+ password: Uint8Array;
40
+ }
41
+
42
+ /**
43
+ * Union type for all generator options.
44
+ */
45
+ export type XIDGeneratorOptionsValue =
46
+ | XIDGeneratorOptions.Omit
47
+ | XIDGeneratorOptions.Include
48
+ | XIDGeneratorOptions.Elide
49
+ | XIDGeneratorEncryptConfig;
50
+
51
+ /**
52
+ * Generator data that can be either decrypted or encrypted.
53
+ */
54
+ export type GeneratorData =
55
+ | { type: "decrypted"; generator: ProvenanceMarkGenerator }
56
+ | { type: "encrypted"; envelope: Envelope };
57
+
58
+ /**
59
+ * Represents provenance information in an XID document.
60
+ */
61
+ export class Provenance implements EnvelopeEncodable {
62
+ private _mark: ProvenanceMark;
63
+ private _generator: { data: GeneratorData; salt: Salt } | undefined;
64
+
65
+ private constructor(mark: ProvenanceMark, generator?: { data: GeneratorData; salt: Salt }) {
66
+ this._mark = mark;
67
+ this._generator = generator;
68
+ }
69
+
70
+ /**
71
+ * Create a new Provenance with just a mark.
72
+ */
73
+ static new(mark: ProvenanceMark): Provenance {
74
+ return new Provenance(mark);
75
+ }
76
+
77
+ /**
78
+ * Create a new Provenance with a generator and mark.
79
+ */
80
+ static newWithGenerator(generator: ProvenanceMarkGenerator, mark: ProvenanceMark): Provenance {
81
+ const salt = Salt.random(32);
82
+ return new Provenance(mark, { data: { type: "decrypted", generator }, salt });
83
+ }
84
+
85
+ /**
86
+ * Get the provenance mark.
87
+ */
88
+ mark(): ProvenanceMark {
89
+ return this._mark;
90
+ }
91
+
92
+ /**
93
+ * Get the generator, if available and decrypted.
94
+ */
95
+ generator(): ProvenanceMarkGenerator | undefined {
96
+ if (this._generator === undefined) return undefined;
97
+ if (this._generator.data.type === "decrypted") {
98
+ return this._generator.data.generator;
99
+ }
100
+ return undefined;
101
+ }
102
+
103
+ /**
104
+ * Check if this provenance has a decrypted generator.
105
+ */
106
+ hasGenerator(): boolean {
107
+ return this._generator?.data.type === "decrypted";
108
+ }
109
+
110
+ /**
111
+ * Check if this provenance has an encrypted generator.
112
+ */
113
+ hasEncryptedGenerator(): boolean {
114
+ return this._generator?.data.type === "encrypted";
115
+ }
116
+
117
+ /**
118
+ * Get the salt used for generator decorrelation.
119
+ */
120
+ generatorSalt(): Salt | undefined {
121
+ return this._generator?.salt;
122
+ }
123
+
124
+ /**
125
+ * Update the provenance mark.
126
+ */
127
+ setMark(mark: ProvenanceMark): void {
128
+ this._mark = mark;
129
+ }
130
+
131
+ /**
132
+ * Set or replace the generator.
133
+ */
134
+ setGenerator(generator: ProvenanceMarkGenerator): void {
135
+ const salt = Salt.random(32);
136
+ this._generator = { data: { type: "decrypted", generator }, salt };
137
+ }
138
+
139
+ /**
140
+ * Take and remove the generator.
141
+ */
142
+ takeGenerator(): { data: GeneratorData; salt: Salt } | undefined {
143
+ const gen = this._generator;
144
+ this._generator = undefined;
145
+ return gen;
146
+ }
147
+
148
+ /**
149
+ * Get a mutable reference to the generator, decrypting if necessary.
150
+ */
151
+ generatorMut(password?: Uint8Array): ProvenanceMarkGenerator | undefined {
152
+ type EnvelopeExt = Envelope & {
153
+ decryptSubject(p: Uint8Array): Envelope;
154
+ tryUnwrap(): Envelope;
155
+ asByteString(): Uint8Array | undefined;
156
+ };
157
+
158
+ if (this._generator === undefined) return undefined;
159
+
160
+ if (this._generator.data.type === "decrypted") {
161
+ return this._generator.data.generator;
162
+ }
163
+
164
+ // Try to decrypt
165
+ if (password !== undefined) {
166
+ const encryptedEnvelope = this._generator.data.envelope as EnvelopeExt;
167
+ try {
168
+ const decrypted = encryptedEnvelope.decryptSubject(password) as EnvelopeExt;
169
+ const unwrapped = decrypted.tryUnwrap() as EnvelopeExt;
170
+ // Extract generator from unwrapped envelope
171
+ const generatorData = unwrapped.asByteString();
172
+ if (generatorData !== undefined) {
173
+ const json = decodeCbor(generatorData) as unknown as Record<string, unknown>;
174
+ const generator = ProvenanceMarkGenerator.fromJSON(json);
175
+ // Replace encrypted with decrypted
176
+ this._generator = {
177
+ data: { type: "decrypted", generator },
178
+ salt: this._generator.salt,
179
+ };
180
+ return generator;
181
+ }
182
+ } catch {
183
+ throw XIDError.invalidPassword();
184
+ }
185
+ }
186
+
187
+ throw XIDError.invalidPassword();
188
+ }
189
+
190
+ /**
191
+ * Convert to envelope with specified options.
192
+ */
193
+ intoEnvelopeOpt(generatorOptions: XIDGeneratorOptionsValue = XIDGeneratorOptions.Omit): Envelope {
194
+ type EnvelopeExt = Envelope & {
195
+ elide(): Envelope;
196
+ wrap(): Envelope;
197
+ encryptSubject(p: Uint8Array): Envelope;
198
+ };
199
+
200
+ // Create envelope with the mark as subject
201
+ let envelope = Envelope.new(this._mark.toCborData());
202
+
203
+ // Handle generator
204
+ if (this._generator !== undefined) {
205
+ const { data, salt } = this._generator;
206
+
207
+ if (data.type === "encrypted") {
208
+ // Always preserve encrypted generators
209
+ envelope = envelope.addAssertion(kv(PROVENANCE_GENERATOR), data.envelope);
210
+ envelope = envelope.addAssertion(kv(SALT), salt.toData());
211
+ } else if (data.type === "decrypted") {
212
+ // Handle decrypted generators based on options
213
+ const option =
214
+ typeof generatorOptions === "object" ? generatorOptions.type : generatorOptions;
215
+
216
+ switch (option) {
217
+ case XIDGeneratorOptions.Include: {
218
+ const generatorBytes = cborData(data.generator.toJSON());
219
+ envelope = envelope.addAssertion(kv(PROVENANCE_GENERATOR), generatorBytes);
220
+ envelope = envelope.addAssertion(kv(SALT), salt.toData());
221
+ break;
222
+ }
223
+ case XIDGeneratorOptions.Elide: {
224
+ const generatorBytes2 = cborData(data.generator.toJSON());
225
+ const baseAssertion = Envelope.newAssertion(kv(PROVENANCE_GENERATOR), generatorBytes2);
226
+ const elidedAssertion = (baseAssertion as EnvelopeExt).elide();
227
+ envelope = envelope.addAssertionEnvelope(elidedAssertion);
228
+ envelope = envelope.addAssertion(kv(SALT), salt.toData());
229
+ break;
230
+ }
231
+ case XIDGeneratorOptions.Encrypt: {
232
+ if (typeof generatorOptions === "object") {
233
+ const generatorBytes3 = cborData(data.generator.toJSON());
234
+ const generatorEnvelope = Envelope.new(generatorBytes3) as EnvelopeExt;
235
+ const wrapped = generatorEnvelope.wrap() as EnvelopeExt;
236
+ const encrypted = wrapped.encryptSubject(generatorOptions.password);
237
+ envelope = envelope.addAssertion(kv(PROVENANCE_GENERATOR), encrypted);
238
+ envelope = envelope.addAssertion(kv(SALT), salt.toData());
239
+ }
240
+ break;
241
+ }
242
+ case XIDGeneratorOptions.Omit:
243
+ default:
244
+ // Do nothing - omit generator
245
+ break;
246
+ }
247
+ }
248
+ }
249
+
250
+ return envelope;
251
+ }
252
+
253
+ // EnvelopeEncodable implementation
254
+ intoEnvelope(): Envelope {
255
+ return this.intoEnvelopeOpt(XIDGeneratorOptions.Omit);
256
+ }
257
+
258
+ /**
259
+ * Try to extract a Provenance from an envelope, optionally with password for decryption.
260
+ */
261
+ static tryFromEnvelope(envelope: Envelope, password?: Uint8Array): Provenance {
262
+ type EnvelopeExt = Envelope & {
263
+ asByteString(): Uint8Array | undefined;
264
+ assertionsWithPredicate(p: unknown): Envelope[];
265
+ decryptSubject(p: Uint8Array): Envelope;
266
+ tryUnwrap(): Envelope;
267
+ };
268
+ const env = envelope as EnvelopeExt;
269
+
270
+ // Extract mark from subject
271
+ // The envelope may be a node (with assertions) or a leaf
272
+ const envCase = env.case();
273
+ const subject =
274
+ envCase.type === "node" ? (env as unknown as { subject(): Envelope }).subject() : envelope;
275
+ const markData = (subject as EnvelopeExt).asByteString();
276
+ if (markData === undefined) {
277
+ throw XIDError.provenanceMark(new Error("Could not extract mark from envelope"));
278
+ }
279
+ const mark = ProvenanceMark.fromCborData(markData);
280
+
281
+ // Extract optional generator
282
+ let generator: { data: GeneratorData; salt: Salt } | undefined;
283
+
284
+ // Extract salt from top level (if present)
285
+ let salt: Salt = Salt.random(32);
286
+ const saltAssertions = env.assertionsWithPredicate(SALT);
287
+ if (saltAssertions.length > 0) {
288
+ const saltAssertion = saltAssertions[0];
289
+ const saltCase = saltAssertion.case();
290
+ if (saltCase.type === "assertion") {
291
+ const saltObj = saltCase.assertion.object() as EnvelopeExt;
292
+ const saltData = saltObj.asByteString();
293
+ if (saltData !== undefined) {
294
+ salt = Salt.from(saltData);
295
+ }
296
+ }
297
+ }
298
+
299
+ const generatorAssertions = env.assertionsWithPredicate(PROVENANCE_GENERATOR);
300
+ if (generatorAssertions.length > 0) {
301
+ const generatorAssertion = generatorAssertions[0] as EnvelopeExt;
302
+ const assertionCase = generatorAssertion.case();
303
+
304
+ if (assertionCase.type === "assertion") {
305
+ const generatorObject = assertionCase.assertion.object() as EnvelopeExt;
306
+
307
+ // Check if encrypted
308
+ const objCase = generatorObject.case();
309
+ if (objCase.type === "encrypted") {
310
+ if (password !== undefined) {
311
+ try {
312
+ const decrypted = generatorObject.decryptSubject(password) as EnvelopeExt;
313
+ const unwrapped = decrypted.tryUnwrap() as EnvelopeExt;
314
+ const generatorData = unwrapped.asByteString();
315
+ if (generatorData !== undefined) {
316
+ const json = decodeCbor(generatorData) as unknown as Record<string, unknown>;
317
+ const gen = ProvenanceMarkGenerator.fromJSON(json);
318
+ generator = {
319
+ data: { type: "decrypted", generator: gen },
320
+ salt,
321
+ };
322
+ }
323
+ } catch {
324
+ // Wrong password - store as encrypted
325
+ generator = {
326
+ data: { type: "encrypted", envelope: generatorObject },
327
+ salt,
328
+ };
329
+ }
330
+ } else {
331
+ // No password - store as encrypted
332
+ generator = {
333
+ data: { type: "encrypted", envelope: generatorObject },
334
+ salt,
335
+ };
336
+ }
337
+ } else {
338
+ // Plain text generator
339
+ const generatorData = generatorObject.asByteString();
340
+ if (generatorData !== undefined) {
341
+ const json2 = decodeCbor(generatorData) as unknown as Record<string, unknown>;
342
+ const gen = ProvenanceMarkGenerator.fromJSON(json2);
343
+ generator = {
344
+ data: { type: "decrypted", generator: gen },
345
+ salt,
346
+ };
347
+ }
348
+ }
349
+ }
350
+ }
351
+
352
+ return new Provenance(mark, generator);
353
+ }
354
+
355
+ /**
356
+ * Check equality with another Provenance.
357
+ */
358
+ equals(other: Provenance): boolean {
359
+ return this._mark.equals(other._mark);
360
+ }
361
+
362
+ /**
363
+ * Clone this Provenance.
364
+ * Note: ProvenanceMark is immutable so we can use the same instance.
365
+ */
366
+ clone(): Provenance {
367
+ return new Provenance(
368
+ this._mark,
369
+ this._generator !== undefined
370
+ ? { data: this._generator.data, salt: this._generator.salt }
371
+ : undefined,
372
+ );
373
+ }
374
+ }