@bcts/xid 1.0.0-alpha.12 → 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/dist/index.cjs +108 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -34
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +49 -34
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +109 -93
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +105 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -10
- package/src/index.ts +3 -0
- package/src/key.ts +91 -60
- package/src/xid-document.ts +64 -70
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bcts/xid",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
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.
|
|
85
|
-
"@bcts/dcbor": "^1.0.0-alpha.
|
|
86
|
-
"@bcts/envelope": "^1.0.0-alpha.
|
|
87
|
-
"@bcts/known-values": "^1.0.0-alpha.
|
|
88
|
-
"@bcts/provenance-mark": "^1.0.0-alpha.
|
|
89
|
-
"@bcts/rand": "^1.0.0-alpha.12",
|
|
90
|
-
"@bcts/uniform-resources": "^1.0.0-alpha.12"
|
|
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
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,
|
|
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 {
|
|
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";
|
|
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
|
|
65
|
-
private readonly
|
|
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
|
-
|
|
72
|
-
|
|
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.
|
|
78
|
-
this.
|
|
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(
|
|
88
|
-
return new Key(
|
|
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(
|
|
95
|
-
return new Key(
|
|
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
|
|
107
|
+
* Create a new Key with private keys.
|
|
100
108
|
*/
|
|
101
|
-
static
|
|
109
|
+
static newWithPrivateKeys(privateKeys: PrivateKeys, publicKeys: PublicKeys): Key {
|
|
102
110
|
const salt = Salt.random(32);
|
|
103
111
|
return new Key(
|
|
104
|
-
|
|
105
|
-
{ data: { type: "decrypted",
|
|
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
|
-
*
|
|
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
|
-
|
|
116
|
-
return this.
|
|
132
|
+
publicKeys(): PublicKeys {
|
|
133
|
+
return this._publicKeys;
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
/**
|
|
120
|
-
* Get the private
|
|
137
|
+
* Get the private keys, if available and decrypted.
|
|
121
138
|
*/
|
|
122
|
-
|
|
123
|
-
if (this.
|
|
124
|
-
if (this.
|
|
125
|
-
return this.
|
|
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.
|
|
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.
|
|
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.
|
|
165
|
+
return this._privateKeyData?.salt;
|
|
149
166
|
}
|
|
150
167
|
|
|
151
168
|
/**
|
|
152
|
-
* Get the reference for this key (based on public
|
|
169
|
+
* Get the reference for this key (based on public keys tagged CBOR).
|
|
153
170
|
*/
|
|
154
171
|
reference(): Reference {
|
|
155
|
-
return
|
|
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
|
-
|
|
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.
|
|
214
|
-
const { data, salt } = this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
298
|
-
if (
|
|
299
|
-
throw XIDError.component(new Error("Could not extract public
|
|
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
|
|
331
|
+
const publicKeys = PublicKeys.fromTaggedCborData(publicKeysData);
|
|
302
332
|
|
|
303
333
|
// Extract optional private key
|
|
304
|
-
let
|
|
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
|
-
|
|
338
|
-
privateKeys =
|
|
339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
360
|
-
if (
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
data: { type: "decrypted",
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
423
|
-
this.
|
|
424
|
-
? { data: this.
|
|
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),
|