@enbox/dids 0.0.8 → 0.0.9

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": "@enbox/dids",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "TBD DIDs library",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/index.js",
@@ -80,8 +80,8 @@
80
80
  "dependencies": {
81
81
  "@decentralized-identity/ion-sdk": "1.0.4",
82
82
  "@dnsquery/dns-packet": "6.1.1",
83
- "@enbox/common": "0.0.6",
84
- "@enbox/crypto": "0.0.7",
83
+ "@enbox/common": "0.0.7",
84
+ "@enbox/crypto": "0.0.8",
85
85
  "abstract-level": "1.0.4",
86
86
  "bencode": "4.0.0",
87
87
  "level": "8.0.1",
@@ -1,8 +1,30 @@
1
- import type { DidDocument, DidResolutionOptions, DidResolutionResult } from '../types/did-core.js';
1
+ import type {
2
+ InferKeyGeneratorAlgorithm,
3
+ KeyIdentifier,
4
+ KeyImporterExporter,
5
+ KeyManager,
6
+ KmsExportKeyParams,
7
+ KmsImportKeyParams,
8
+ } from '@enbox/crypto';
2
9
 
10
+ import { LocalKeyManager } from '@enbox/crypto';
11
+
12
+ import type { PortableDid } from '../types/portable-did.js';
13
+ import type { DidCreateOptions, DidCreateVerificationMethod } from './did-method.js';
14
+ import type {
15
+ DidDocument,
16
+ DidResolutionOptions,
17
+ DidResolutionResult,
18
+ DidService,
19
+ DidVerificationMethod,
20
+ } from '../types/did-core.js';
21
+
22
+ import { BearerDid } from '../bearer-did.js';
3
23
  import { Did } from '../did.js';
4
24
  import { DidMethod } from './did-method.js';
5
25
  import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
26
+ import { extractDidFragment } from '../utils.js';
27
+ import { DidError, DidErrorCode } from '../did-error.js';
6
28
 
7
29
  /** Default fetch timeout for DID document retrieval (30 seconds). */
8
30
  const FETCH_TIMEOUT_MS = 30_000;
@@ -44,11 +66,75 @@ function isPrivateHostname(hostname: string): boolean {
44
66
  return false;
45
67
  }
46
68
 
69
+ /**
70
+ * Defines the set of options available when creating a new Decentralized Identifier (DID) with the
71
+ * 'did:web' method.
72
+ *
73
+ * Unlike self-certifying DID methods (e.g., `did:jwk`, `did:dht`), `did:web` requires external
74
+ * information — a domain and an optional path — to form the DID URI. The `create()` method
75
+ * generates keys locally and constructs a DID document, but does NOT publish it. Registration
76
+ * with a `did:web` hosting server is the caller's responsibility.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * // Create a did:web DID for enbox.id/users/alice
81
+ * const did = await DidWeb.create({
82
+ * options: {
83
+ * domain: 'enbox.id',
84
+ * path: 'users:alice',
85
+ * }
86
+ * });
87
+ * // did.uri === 'did:web:enbox.id:users:alice'
88
+ * ```
89
+ */
90
+ export interface DidWebCreateOptions<TKms> extends DidCreateOptions<TKms> {
91
+ /**
92
+ * The domain (and optional port) that will host the DID document.
93
+ *
94
+ * Ports are percent-encoded per the did:web spec (e.g., `localhost%3A3000`).
95
+ * This value forms the first segment of the method-specific identifier.
96
+ *
97
+ * @example 'enbox.id'
98
+ * @example 'localhost%3A3000'
99
+ */
100
+ domain: string;
101
+
102
+ /**
103
+ * Optional colon-separated path segments appended after the domain.
104
+ *
105
+ * When present the DID document is served at `https://<domain>/<path>/did.json`.
106
+ * When absent the DID document is served at `https://<domain>/.well-known/did.json`.
107
+ *
108
+ * @example 'users:alice' // → did:web:enbox.id:users:alice
109
+ * @example 'orgs:acme' // → did:web:enbox.id:orgs:acme
110
+ */
111
+ path?: string;
112
+
113
+ /**
114
+ * Optionally specify the algorithm to be used for key generation of the primary
115
+ * verification method. Defaults to Ed25519. Mutually exclusive with
116
+ * `verificationMethods`.
117
+ */
118
+ algorithm?: TKms extends KeyManager
119
+ ? InferKeyGeneratorAlgorithm<TKms>
120
+ : InferKeyGeneratorAlgorithm<LocalKeyManager>;
121
+
122
+ /**
123
+ * Optional services to include in the DID document (e.g., DWN endpoints).
124
+ */
125
+ services?: DidService[];
126
+ }
127
+
47
128
  /**
48
129
  * The `DidWeb` class provides an implementation of the `did:web` DID method.
49
130
  *
50
131
  * Features:
132
+ * - DID Creation: Create new `did:web` DIDs with local key generation.
133
+ * - DID Key Management: Instantiate a DID object from an existing verification method key set or
134
+ * a key in a Key Management System (KMS). If supported by the KMS, a DID's
135
+ * key can be exported to a portable DID format.
51
136
  * - DID Resolution: Resolve a `did:web` to its corresponding DID Document.
137
+ * - Signature Operations: Sign and verify messages using keys associated with a DID.
52
138
  *
53
139
  * @remarks
54
140
  * The `did:web` method uses a web domain's existing reputation and aims to integrate decentralized
@@ -56,12 +142,30 @@ function isPrivateHostname(hostname: string): boolean {
56
142
  * security models and domain ownership to provide accessible, interoperable digital identity
57
143
  * management.
58
144
  *
145
+ * Unlike self-certifying methods, `did:web` creation generates keys and a DID document locally
146
+ * but does NOT publish them. The caller is responsible for registering the DID document with
147
+ * the hosting server.
148
+ *
59
149
  * @see {@link https://w3c-ccg.github.io/did-method-web/ | DID Web Specification}
60
150
  *
61
151
  * @example
62
152
  * ```ts
153
+ * // DID Creation
154
+ * const did = await DidWeb.create({
155
+ * options: { domain: 'enbox.id', path: 'users:alice' }
156
+ * });
157
+ *
63
158
  * // DID Resolution
64
- * const resolutionResult = await DidWeb.resolve({ did: did.uri });
159
+ * const resolutionResult = await DidWeb.resolve('did:web:enbox.id:users:alice');
160
+ *
161
+ * // Signature Operations
162
+ * const signer = await did.getSigner();
163
+ * const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
164
+ * const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
165
+ *
166
+ * // Import / Export
167
+ * const portableDid = await did.export();
168
+ * const imported = await DidWeb.import({ portableDid });
65
169
  * ```
66
170
  */
67
171
  export class DidWeb extends DidMethod {
@@ -71,6 +175,214 @@ export class DidWeb extends DidMethod {
71
175
  */
72
176
  public static methodName = 'web';
73
177
 
178
+ /**
179
+ * Creates a new DID using the `did:web` method formed from a newly generated key.
180
+ *
181
+ * @remarks
182
+ * The DID URI is formed from the given `domain` and optional `path`, prefixed with `did:web:`.
183
+ * Keys are generated locally and a DID document is constructed, but the document is NOT
184
+ * published to any server. Registration with a `did:web` hosting server is the caller's
185
+ * responsibility.
186
+ *
187
+ * Notes:
188
+ * - If no `options.algorithm` or `options.verificationMethods` are given, a new Ed25519 key is
189
+ * generated by default.
190
+ * - The `algorithm` and `verificationMethods` options are mutually exclusive.
191
+ * - The `domain` option is required.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * // DID Creation
196
+ * const did = await DidWeb.create({
197
+ * options: { domain: 'enbox.id', path: 'users:alice' }
198
+ * });
199
+ * // did.uri === 'did:web:enbox.id:users:alice'
200
+ *
201
+ * // DID Creation with a KMS
202
+ * const keyManager = new LocalKeyManager();
203
+ * const did = await DidWeb.create({
204
+ * keyManager,
205
+ * options: { domain: 'enbox.id', path: 'users:alice' }
206
+ * });
207
+ * ```
208
+ *
209
+ * @param params - The parameters for the create operation.
210
+ * @param params.keyManager - Optionally specify a Key Management System (KMS) used to generate
211
+ * keys and sign data.
212
+ * @param params.options - Parameters that specify the domain, path, algorithm, verification
213
+ * methods, and services for the new DID.
214
+ * @returns A Promise resolving to a {@link BearerDid} object representing the new DID.
215
+ */
216
+ public static async create<TKms extends KeyManager | undefined = undefined>({
217
+ keyManager = new LocalKeyManager(),
218
+ options,
219
+ }: {
220
+ keyManager?: TKms;
221
+ options: DidWebCreateOptions<TKms>;
222
+ }): Promise<BearerDid> {
223
+ // Validate required options.
224
+ if (!options.domain) {
225
+ throw new DidError(DidErrorCode.InvalidDid, `The 'domain' option is required for did:web creation`);
226
+ }
227
+
228
+ // Validate that `algorithm` and `verificationMethods` are not both given.
229
+ if (options.algorithm && options.verificationMethods) {
230
+ throw new Error(`The 'algorithm' and 'verificationMethods' options are mutually exclusive`);
231
+ }
232
+
233
+ // Build the DID URI from the domain and optional path.
234
+ const identifier = options.path
235
+ ? `${options.domain}:${options.path}`
236
+ : options.domain;
237
+ const didUri = `did:${DidWeb.methodName}:${identifier}`;
238
+
239
+ // Validate the constructed DID URI.
240
+ const parsedDid = Did.parse(didUri);
241
+ if (!parsedDid) {
242
+ throw new DidError(DidErrorCode.InvalidDid, `The constructed DID URI is invalid: ${didUri}`);
243
+ }
244
+
245
+ // Determine verification methods to create.
246
+ const verificationMethodsToAdd: DidCreateVerificationMethod<TKms>[] =
247
+ options.verificationMethods ?? [{
248
+ algorithm : (options.algorithm ?? 'Ed25519') as DidCreateVerificationMethod<TKms>['algorithm'],
249
+ purposes : ['authentication', 'assertionMethod', 'capabilityDelegation', 'capabilityInvocation'],
250
+ }];
251
+
252
+ // Begin constructing the DID Document.
253
+ const document: DidDocument = {
254
+ '@context' : ['https://www.w3.org/ns/did/v1'],
255
+ id : didUri,
256
+ };
257
+
258
+ // Generate key material for each verification method and add to the document.
259
+ for (let i = 0; i < verificationMethodsToAdd.length; i++) {
260
+ const vmOptions = verificationMethodsToAdd[i];
261
+
262
+ // Generate a random key.
263
+ const keyUri = await keyManager.generateKey({ algorithm: vmOptions.algorithm });
264
+ const publicKey = await keyManager.getPublicKey({ keyUri });
265
+
266
+ // Use the given ID or default to `key-N`.
267
+ const fragment = vmOptions.id?.split('#').pop() ?? `key-${i}`;
268
+ const methodId = `${didUri}#${fragment}`;
269
+
270
+ // Initialize the `verificationMethod` array if needed.
271
+ document.verificationMethod ??= [];
272
+
273
+ // Add the verification method to the DID document.
274
+ document.verificationMethod.push({
275
+ id : methodId,
276
+ type : 'JsonWebKey',
277
+ controller : vmOptions.controller ?? didUri,
278
+ publicKeyJwk : publicKey,
279
+ });
280
+
281
+ // Add the verification method to the specified purpose properties.
282
+ for (const purpose of vmOptions.purposes ?? []) {
283
+ document[purpose] ??= [];
284
+ document[purpose]!.push(methodId);
285
+ }
286
+ }
287
+
288
+ // Add services, if any.
289
+ if (options.services) {
290
+ document.service = options.services.map(service => ({
291
+ ...service,
292
+ id: `${didUri}#${service.id.split('#').pop()}`,
293
+ }));
294
+ }
295
+
296
+ // Create the BearerDid object. Metadata indicates unpublished state.
297
+ const did = new BearerDid({
298
+ uri : didUri,
299
+ document,
300
+ metadata : { published: false },
301
+ keyManager,
302
+ });
303
+
304
+ return did;
305
+ }
306
+
307
+ /**
308
+ * Given the W3C DID Document of a `did:web` DID, return the verification method that will be
309
+ * used for signing messages and credentials. If given, the `methodId` parameter is used to
310
+ * select the verification method. If not given, the first verification method referenced in
311
+ * `assertionMethod` is used, falling back to the first verification method in the document.
312
+ *
313
+ * @param params - The parameters for the `getSigningMethod` operation.
314
+ * @param params.didDocument - DID Document to get the verification method from.
315
+ * @param params.methodId - ID of the verification method to use for signing.
316
+ * @returns Verification method to use for signing.
317
+ */
318
+ public static async getSigningMethod({ didDocument, methodId }: {
319
+ didDocument: DidDocument;
320
+ methodId?: string;
321
+ }): Promise<DidVerificationMethod> {
322
+ // Verify the DID method is supported.
323
+ const parsedDid = Did.parse(didDocument.id);
324
+ if (parsedDid && parsedDid.method !== this.methodName) {
325
+ throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
326
+ }
327
+
328
+ // Attempt to find a verification method that matches the given method ID, or if not given,
329
+ // find the first verification method intended for signing claims.
330
+ const targetFragment = extractDidFragment(methodId)
331
+ ?? extractDidFragment(didDocument.assertionMethod?.[0]);
332
+
333
+ const verificationMethod = targetFragment
334
+ ? didDocument.verificationMethod?.find(vm => extractDidFragment(vm.id) === targetFragment)
335
+ : didDocument.verificationMethod?.[0];
336
+
337
+ if (!(verificationMethod && verificationMethod.publicKeyJwk)) {
338
+ throw new DidError(
339
+ DidErrorCode.InternalError,
340
+ 'A verification method intended for signing could not be determined from the DID Document'
341
+ );
342
+ }
343
+
344
+ return verificationMethod;
345
+ }
346
+
347
+ /**
348
+ * Instantiates a {@link BearerDid} object for the DID Web method from a given
349
+ * {@link PortableDid}.
350
+ *
351
+ * This method allows for the creation of a `BearerDid` object using a previously created DID's
352
+ * key material, DID document, and metadata.
353
+ *
354
+ * @example
355
+ * ```ts
356
+ * // Export an existing BearerDid to PortableDid format.
357
+ * const portableDid = await did.export();
358
+ * // Reconstruct a BearerDid object from the PortableDid.
359
+ * const did = await DidWeb.import({ portableDid });
360
+ * ```
361
+ *
362
+ * @param params - The parameters for the import operation.
363
+ * @param params.portableDid - The PortableDid object to import.
364
+ * @param params.keyManager - Optionally specify an external Key Management System (KMS) used to
365
+ * generate keys and sign data. If not given, a new
366
+ * {@link LocalKeyManager} instance will be created and used.
367
+ * @returns A Promise resolving to a `BearerDid` object representing the DID formed from the
368
+ * provided keys.
369
+ */
370
+ public static async import({ portableDid, keyManager = new LocalKeyManager() }: {
371
+ keyManager?: KeyManager & KeyImporterExporter<KmsImportKeyParams, KeyIdentifier, KmsExportKeyParams>;
372
+ portableDid: PortableDid;
373
+ }): Promise<BearerDid> {
374
+ // Verify the DID method is supported.
375
+ const parsedDid = Did.parse(portableDid.uri);
376
+ if (parsedDid?.method !== DidWeb.methodName) {
377
+ throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported`);
378
+ }
379
+
380
+ // Use the given PortableDid to construct the BearerDid object.
381
+ const did = await BearerDid.import({ portableDid, keyManager });
382
+
383
+ return did;
384
+ }
385
+
74
386
  /**
75
387
  * Resolves a `did:web` identifier to a DID Document.
76
388
  *