@bcts/xid 1.0.0-alpha.11 → 1.0.0-alpha.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bcts/xid",
3
- "version": "1.0.0-alpha.11",
3
+ "version": "1.0.0-alpha.13",
4
4
  "type": "module",
5
5
  "description": "Blockchain Commons XID for TypeScript",
6
6
  "license": "BSD-2-Clause-Patent",
@@ -47,8 +47,8 @@
47
47
  "dev": "tsdown --watch",
48
48
  "test": "vitest run",
49
49
  "test:watch": "vitest",
50
- "lint": "eslint 'src/**/*.ts'",
51
- "lint:fix": "eslint 'src/**/*.ts' --fix",
50
+ "lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
51
+ "lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
52
52
  "typecheck": "tsc --noEmit",
53
53
  "clean": "rm -rf dist",
54
54
  "docs": "typedoc",
@@ -69,6 +69,7 @@
69
69
  },
70
70
  "devDependencies": {
71
71
  "@bcts/eslint": "^0.1.0",
72
+ "@bcts/rand": "^1.0.0-alpha.13",
72
73
  "@bcts/tsconfig": "^0.1.0",
73
74
  "@eslint/js": "^9.39.2",
74
75
  "@typescript-eslint/eslint-plugin": "^8.50.1",
@@ -81,12 +82,10 @@
81
82
  "vitest": "^4.0.16"
82
83
  },
83
84
  "dependencies": {
84
- "@bcts/components": "^1.0.0-alpha.11",
85
- "@bcts/dcbor": "^1.0.0-alpha.11",
86
- "@bcts/envelope": "^1.0.0-alpha.11",
87
- "@bcts/known-values": "^1.0.0-alpha.11",
88
- "@bcts/provenance-mark": "^1.0.0-alpha.11",
89
- "@bcts/rand": "^1.0.0-alpha.11",
90
- "@bcts/uniform-resources": "^1.0.0-alpha.11"
85
+ "@bcts/components": "^1.0.0-alpha.13",
86
+ "@bcts/dcbor": "^1.0.0-alpha.13",
87
+ "@bcts/envelope": "^1.0.0-alpha.13",
88
+ "@bcts/known-values": "^1.0.0-alpha.13",
89
+ "@bcts/provenance-mark": "^1.0.0-alpha.13"
91
90
  }
92
91
  }
package/src/index.ts CHANGED
@@ -7,6 +7,9 @@
7
7
  * Ported from bc-xid-rust
8
8
  */
9
9
 
10
+ // Re-export XID from components for convenience
11
+ export { XID } from "@bcts/components";
12
+
10
13
  // Error handling
11
14
  export { XIDError, XIDErrorCode, type XIDResult } from "./error";
12
15
 
package/src/key.ts CHANGED
@@ -7,13 +7,21 @@
7
7
  * Ported from bc-xid-rust/src/key.rs
8
8
  */
9
9
 
10
- import { Envelope, PrivateKeyBase, PublicKeyBase, type EnvelopeEncodable } from "@bcts/envelope";
10
+ import { Envelope, type EnvelopeEncodable } from "@bcts/envelope";
11
11
  import { ENDPOINT, NICKNAME, PRIVATE_KEY, SALT, type KnownValue } from "@bcts/known-values";
12
12
  import type { EnvelopeEncodableValue } from "@bcts/envelope";
13
13
 
14
14
  // Helper to convert KnownValue to EnvelopeEncodableValue
15
15
  const kv = (v: KnownValue): EnvelopeEncodableValue => v as unknown as EnvelopeEncodableValue;
16
- import { Salt, Reference } from "@bcts/components";
16
+ import {
17
+ Salt,
18
+ type Reference,
19
+ PublicKeys,
20
+ PrivateKeys,
21
+ type PrivateKeyBase,
22
+ type Verifier,
23
+ type Signature,
24
+ } from "@bcts/components";
17
25
  import { Permissions, type HasPermissions } from "./permissions";
18
26
  import { type Privilege } from "./privilege";
19
27
  import { type HasNickname } from "./name";
@@ -54,28 +62,28 @@ export type XIDPrivateKeyOptionsValue =
54
62
  * Private key data that can be either decrypted or encrypted.
55
63
  */
56
64
  export type PrivateKeyData =
57
- | { type: "decrypted"; privateKeyBase: PrivateKeyBase }
65
+ | { type: "decrypted"; privateKeys: PrivateKeys }
58
66
  | { type: "encrypted"; envelope: Envelope };
59
67
 
60
68
  /**
61
69
  * Represents a key in an XID document.
62
70
  */
63
- export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
64
- private readonly _publicKeyBase: PublicKeyBase;
65
- private readonly _privateKeys: { data: PrivateKeyData; salt: Salt } | undefined;
71
+ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable, Verifier {
72
+ private readonly _publicKeys: PublicKeys;
73
+ private readonly _privateKeyData: { data: PrivateKeyData; salt: Salt } | undefined;
66
74
  private _nickname: string;
67
75
  private readonly _endpoints: Set<string>;
68
76
  private readonly _permissions: Permissions;
69
77
 
70
78
  private constructor(
71
- publicKeyBase: PublicKeyBase,
72
- privateKeys?: { data: PrivateKeyData; salt: Salt },
79
+ publicKeys: PublicKeys,
80
+ privateKeyData?: { data: PrivateKeyData; salt: Salt },
73
81
  nickname = "",
74
82
  endpoints = new Set<string>(),
75
83
  permissions = Permissions.new(),
76
84
  ) {
77
- this._publicKeyBase = publicKeyBase;
78
- this._privateKeys = privateKeys;
85
+ this._publicKeys = publicKeys;
86
+ this._privateKeyData = privateKeyData;
79
87
  this._nickname = nickname;
80
88
  this._endpoints = endpoints;
81
89
  this._permissions = permissions;
@@ -84,25 +92,25 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
84
92
  /**
85
93
  * Create a new Key with only public keys.
86
94
  */
87
- static new(publicKeyBase: PublicKeyBase): Key {
88
- return new Key(publicKeyBase);
95
+ static new(publicKeys: PublicKeys): Key {
96
+ return new Key(publicKeys);
89
97
  }
90
98
 
91
99
  /**
92
100
  * Create a new Key with public keys and allow-all permissions.
93
101
  */
94
- static newAllowAll(publicKeyBase: PublicKeyBase): Key {
95
- return new Key(publicKeyBase, undefined, "", new Set(), Permissions.newAllowAll());
102
+ static newAllowAll(publicKeys: PublicKeys): Key {
103
+ return new Key(publicKeys, undefined, "", new Set(), Permissions.newAllowAll());
96
104
  }
97
105
 
98
106
  /**
99
- * Create a new Key with private key base.
107
+ * Create a new Key with private keys.
100
108
  */
101
- static newWithPrivateKeyBase(privateKeyBase: PrivateKeyBase): Key {
109
+ static newWithPrivateKeys(privateKeys: PrivateKeys, publicKeys: PublicKeys): Key {
102
110
  const salt = Salt.random(32);
103
111
  return new Key(
104
- privateKeyBase.publicKeys(),
105
- { data: { type: "decrypted", privateKeyBase }, salt },
112
+ publicKeys,
113
+ { data: { type: "decrypted", privateKeys }, salt },
106
114
  "",
107
115
  new Set(),
108
116
  Permissions.newAllowAll(),
@@ -110,19 +118,28 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
110
118
  }
111
119
 
112
120
  /**
113
- * Get the public key base.
121
+ * Create a new Key with private key base (derives keys from it).
122
+ */
123
+ static newWithPrivateKeyBase(privateKeyBase: PrivateKeyBase): Key {
124
+ const privateKeys = privateKeyBase.ed25519PrivateKeys();
125
+ const publicKeys = privateKeyBase.ed25519PublicKeys();
126
+ return Key.newWithPrivateKeys(privateKeys, publicKeys);
127
+ }
128
+
129
+ /**
130
+ * Get the public keys.
114
131
  */
115
- publicKeyBase(): PublicKeyBase {
116
- return this._publicKeyBase;
132
+ publicKeys(): PublicKeys {
133
+ return this._publicKeys;
117
134
  }
118
135
 
119
136
  /**
120
- * Get the private key base, if available and decrypted.
137
+ * Get the private keys, if available and decrypted.
121
138
  */
122
- privateKeyBase(): PrivateKeyBase | undefined {
123
- if (this._privateKeys === undefined) return undefined;
124
- if (this._privateKeys.data.type === "decrypted") {
125
- return this._privateKeys.data.privateKeyBase;
139
+ privateKeys(): PrivateKeys | undefined {
140
+ if (this._privateKeyData === undefined) return undefined;
141
+ if (this._privateKeyData.data.type === "decrypted") {
142
+ return this._privateKeyData.data.privateKeys;
126
143
  }
127
144
  return undefined;
128
145
  }
@@ -131,28 +148,39 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
131
148
  * Check if this key has decrypted private keys.
132
149
  */
133
150
  hasPrivateKeys(): boolean {
134
- return this._privateKeys?.data.type === "decrypted";
151
+ return this._privateKeyData?.data.type === "decrypted";
135
152
  }
136
153
 
137
154
  /**
138
155
  * Check if this key has encrypted private keys.
139
156
  */
140
157
  hasEncryptedPrivateKeys(): boolean {
141
- return this._privateKeys?.data.type === "encrypted";
158
+ return this._privateKeyData?.data.type === "encrypted";
142
159
  }
143
160
 
144
161
  /**
145
162
  * Get the salt used for private key decorrelation.
146
163
  */
147
164
  privateKeySalt(): Salt | undefined {
148
- return this._privateKeys?.salt;
165
+ return this._privateKeyData?.salt;
149
166
  }
150
167
 
151
168
  /**
152
- * Get the reference for this key (based on public key).
169
+ * Get the reference for this key (based on public keys tagged CBOR).
153
170
  */
154
171
  reference(): Reference {
155
- return Reference.hash(this._publicKeyBase.data());
172
+ return this._publicKeys.reference();
173
+ }
174
+
175
+ // ============================================================================
176
+ // Verifier Interface
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Verify a signature against a message.
181
+ */
182
+ verify(signature: Signature, message: Uint8Array): boolean {
183
+ return this._publicKeys.verify(signature, message);
156
184
  }
157
185
 
158
186
  /**
@@ -207,11 +235,12 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
207
235
  intoEnvelopeOpt(
208
236
  privateKeyOptions: XIDPrivateKeyOptionsValue = XIDPrivateKeyOptions.Omit,
209
237
  ): Envelope {
210
- let envelope = Envelope.new(this._publicKeyBase.data());
238
+ // Use tagged CBOR representation of PublicKeys as subject
239
+ let envelope = Envelope.new(this._publicKeys.taggedCborData());
211
240
 
212
241
  // Handle private keys
213
- if (this._privateKeys !== undefined) {
214
- const { data, salt } = this._privateKeys;
242
+ if (this._privateKeyData !== undefined) {
243
+ const { data, salt } = this._privateKeyData;
215
244
 
216
245
  if (data.type === "encrypted") {
217
246
  // Always preserve encrypted keys
@@ -224,14 +253,15 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
224
253
 
225
254
  switch (option) {
226
255
  case XIDPrivateKeyOptions.Include: {
227
- envelope = envelope.addAssertion(kv(PRIVATE_KEY), data.privateKeyBase.data());
256
+ // Store PrivateKeys as tagged CBOR
257
+ envelope = envelope.addAssertion(kv(PRIVATE_KEY), data.privateKeys.taggedCborData());
228
258
  envelope = envelope.addAssertion(kv(SALT), salt.toData());
229
259
  break;
230
260
  }
231
261
  case XIDPrivateKeyOptions.Elide: {
232
262
  const baseAssertion = Envelope.newAssertion(
233
263
  kv(PRIVATE_KEY),
234
- data.privateKeyBase.data(),
264
+ data.privateKeys.taggedCborData(),
235
265
  );
236
266
  const elidedAssertion = (baseAssertion as unknown as { elide(): Envelope }).elide();
237
267
  envelope = envelope.addAssertionEnvelope(elidedAssertion);
@@ -240,7 +270,7 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
240
270
  }
241
271
  case XIDPrivateKeyOptions.Encrypt: {
242
272
  if (typeof privateKeyOptions === "object") {
243
- const privateKeysEnvelope = Envelope.new(data.privateKeyBase.data());
273
+ const privateKeysEnvelope = Envelope.new(data.privateKeys.taggedCborData());
244
274
  const encrypted = (
245
275
  privateKeysEnvelope as unknown as { encryptSubject(p: Uint8Array): Envelope }
246
276
  ).encryptSubject(privateKeyOptions.password);
@@ -290,18 +320,18 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
290
320
  };
291
321
  const env = envelope as EnvelopeExt;
292
322
 
293
- // Extract public key base from subject
323
+ // Extract PublicKeys from subject (stored as tagged CBOR)
294
324
  // The envelope may be a node (with assertions) or a leaf
295
325
  const envCase = env.case();
296
326
  const subject = envCase.type === "node" ? env.subject() : env;
297
- const publicKeyData = (subject as EnvelopeExt).asByteString();
298
- if (publicKeyData === undefined) {
299
- throw XIDError.component(new Error("Could not extract public key from envelope"));
327
+ const publicKeysData = (subject as EnvelopeExt).asByteString();
328
+ if (publicKeysData === undefined) {
329
+ throw XIDError.component(new Error("Could not extract public keys from envelope"));
300
330
  }
301
- const publicKeyBase = new PublicKeyBase(publicKeyData);
331
+ const publicKeys = PublicKeys.fromTaggedCborData(publicKeysData);
302
332
 
303
333
  // Extract optional private key
304
- let privateKeys: { data: PrivateKeyData; salt: Salt } | undefined;
334
+ let privateKeyData: { data: PrivateKeyData; salt: Salt } | undefined;
305
335
 
306
336
  // Extract salt from top level (if present)
307
337
  let salt: Salt = Salt.random(32);
@@ -334,33 +364,34 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
334
364
  const decrypted = privateKeyObject.decryptSubject(password) as EnvelopeExt;
335
365
  const decryptedData = decrypted.asByteString();
336
366
  if (decryptedData !== undefined) {
337
- const privateKeyBase = PrivateKeyBase.fromBytes(decryptedData, publicKeyData);
338
- privateKeys = {
339
- data: { type: "decrypted", privateKeyBase },
367
+ // Parse PrivateKeys from tagged CBOR
368
+ const privateKeys = PrivateKeys.fromTaggedCborData(decryptedData);
369
+ privateKeyData = {
370
+ data: { type: "decrypted", privateKeys },
340
371
  salt,
341
372
  };
342
373
  }
343
374
  } catch {
344
375
  // Wrong password - store as encrypted
345
- privateKeys = {
376
+ privateKeyData = {
346
377
  data: { type: "encrypted", envelope: privateKeyObject },
347
378
  salt,
348
379
  };
349
380
  }
350
381
  } else {
351
382
  // No password - store as encrypted
352
- privateKeys = {
383
+ privateKeyData = {
353
384
  data: { type: "encrypted", envelope: privateKeyObject },
354
385
  salt,
355
386
  };
356
387
  }
357
388
  } else {
358
- // Plain text private key
359
- const privateKeyData = privateKeyObject.asByteString();
360
- if (privateKeyData !== undefined) {
361
- const privateKeyBase = PrivateKeyBase.fromBytes(privateKeyData, publicKeyData);
362
- privateKeys = {
363
- data: { type: "decrypted", privateKeyBase },
389
+ // Plain text private key - stored as tagged CBOR
390
+ const privateKeysBytes = privateKeyObject.asByteString();
391
+ if (privateKeysBytes !== undefined) {
392
+ const privateKeys = PrivateKeys.fromTaggedCborData(privateKeysBytes);
393
+ privateKeyData = {
394
+ data: { type: "decrypted", privateKeys },
364
395
  salt,
365
396
  };
366
397
  }
@@ -397,21 +428,21 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
397
428
  // Extract permissions
398
429
  const permissions = Permissions.tryFromEnvelope(envelope);
399
430
 
400
- return new Key(publicKeyBase, privateKeys, nickname, endpoints, permissions);
431
+ return new Key(publicKeys, privateKeyData, nickname, endpoints, permissions);
401
432
  }
402
433
 
403
434
  /**
404
435
  * Check equality with another Key.
405
436
  */
406
437
  equals(other: Key): boolean {
407
- return this._publicKeyBase.hex() === other._publicKeyBase.hex();
438
+ return this._publicKeys.equals(other._publicKeys);
408
439
  }
409
440
 
410
441
  /**
411
442
  * Get a hash key for use in Sets/Maps.
412
443
  */
413
444
  hashKey(): string {
414
- return this._publicKeyBase.hex();
445
+ return this._publicKeys.reference().toHex();
415
446
  }
416
447
 
417
448
  /**
@@ -419,9 +450,9 @@ export class Key implements HasNickname, HasPermissions, EnvelopeEncodable {
419
450
  */
420
451
  clone(): Key {
421
452
  return new Key(
422
- this._publicKeyBase,
423
- this._privateKeys !== undefined
424
- ? { data: this._privateKeys.data, salt: this._privateKeys.salt }
453
+ this._publicKeys,
454
+ this._privateKeyData !== undefined
455
+ ? { data: this._privateKeyData.data, salt: this._privateKeyData.salt }
425
456
  : undefined,
426
457
  this._nickname,
427
458
  new Set(this._endpoints),