@enbox/dids 0.0.1

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.
Files changed (104) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1 -0
  3. package/dist/browser.js +77 -0
  4. package/dist/browser.js.map +7 -0
  5. package/dist/browser.mjs +77 -0
  6. package/dist/browser.mjs.map +7 -0
  7. package/dist/cjs/index.js +6303 -0
  8. package/dist/cjs/index.js.map +7 -0
  9. package/dist/cjs/package.json +1 -0
  10. package/dist/cjs/utils.js +245 -0
  11. package/dist/cjs/utils.js.map +7 -0
  12. package/dist/esm/bearer-did.js +201 -0
  13. package/dist/esm/bearer-did.js.map +1 -0
  14. package/dist/esm/did-error.js +62 -0
  15. package/dist/esm/did-error.js.map +1 -0
  16. package/dist/esm/did.js +114 -0
  17. package/dist/esm/did.js.map +1 -0
  18. package/dist/esm/index.js +16 -0
  19. package/dist/esm/index.js.map +1 -0
  20. package/dist/esm/methods/did-dht.js +1241 -0
  21. package/dist/esm/methods/did-dht.js.map +1 -0
  22. package/dist/esm/methods/did-ion.js +570 -0
  23. package/dist/esm/methods/did-ion.js.map +1 -0
  24. package/dist/esm/methods/did-jwk.js +298 -0
  25. package/dist/esm/methods/did-jwk.js.map +1 -0
  26. package/dist/esm/methods/did-key.js +983 -0
  27. package/dist/esm/methods/did-key.js.map +1 -0
  28. package/dist/esm/methods/did-method.js +53 -0
  29. package/dist/esm/methods/did-method.js.map +1 -0
  30. package/dist/esm/methods/did-web.js +83 -0
  31. package/dist/esm/methods/did-web.js.map +1 -0
  32. package/dist/esm/resolver/resolver-cache-level.js +101 -0
  33. package/dist/esm/resolver/resolver-cache-level.js.map +1 -0
  34. package/dist/esm/resolver/resolver-cache-noop.js +24 -0
  35. package/dist/esm/resolver/resolver-cache-noop.js.map +1 -0
  36. package/dist/esm/resolver/universal-resolver.js +187 -0
  37. package/dist/esm/resolver/universal-resolver.js.map +1 -0
  38. package/dist/esm/types/did-core.js +51 -0
  39. package/dist/esm/types/did-core.js.map +1 -0
  40. package/dist/esm/types/did-resolution.js +12 -0
  41. package/dist/esm/types/did-resolution.js.map +1 -0
  42. package/dist/esm/types/multibase.js +2 -0
  43. package/dist/esm/types/multibase.js.map +1 -0
  44. package/dist/esm/types/portable-did.js +2 -0
  45. package/dist/esm/types/portable-did.js.map +1 -0
  46. package/dist/esm/utils.js +458 -0
  47. package/dist/esm/utils.js.map +1 -0
  48. package/dist/types/bearer-did.d.ts +143 -0
  49. package/dist/types/bearer-did.d.ts.map +1 -0
  50. package/dist/types/did-error.d.ts +50 -0
  51. package/dist/types/did-error.d.ts.map +1 -0
  52. package/dist/types/did.d.ts +125 -0
  53. package/dist/types/did.d.ts.map +1 -0
  54. package/dist/types/index.d.ts +18 -0
  55. package/dist/types/index.d.ts.map +1 -0
  56. package/dist/types/methods/did-dht.d.ts +682 -0
  57. package/dist/types/methods/did-dht.d.ts.map +1 -0
  58. package/dist/types/methods/did-ion.d.ts +492 -0
  59. package/dist/types/methods/did-ion.d.ts.map +1 -0
  60. package/dist/types/methods/did-jwk.d.ts +236 -0
  61. package/dist/types/methods/did-jwk.d.ts.map +1 -0
  62. package/dist/types/methods/did-key.d.ts +499 -0
  63. package/dist/types/methods/did-key.d.ts.map +1 -0
  64. package/dist/types/methods/did-method.d.ts +238 -0
  65. package/dist/types/methods/did-method.d.ts.map +1 -0
  66. package/dist/types/methods/did-web.d.ts +37 -0
  67. package/dist/types/methods/did-web.d.ts.map +1 -0
  68. package/dist/types/resolver/resolver-cache-level.d.ts +86 -0
  69. package/dist/types/resolver/resolver-cache-level.d.ts.map +1 -0
  70. package/dist/types/resolver/resolver-cache-noop.d.ts +9 -0
  71. package/dist/types/resolver/resolver-cache-noop.d.ts.map +1 -0
  72. package/dist/types/resolver/universal-resolver.d.ts +109 -0
  73. package/dist/types/resolver/universal-resolver.d.ts.map +1 -0
  74. package/dist/types/types/did-core.d.ts +523 -0
  75. package/dist/types/types/did-core.d.ts.map +1 -0
  76. package/dist/types/types/did-resolution.d.ts +85 -0
  77. package/dist/types/types/did-resolution.d.ts.map +1 -0
  78. package/dist/types/types/multibase.d.ts +28 -0
  79. package/dist/types/types/multibase.d.ts.map +1 -0
  80. package/dist/types/types/portable-did.d.ts +59 -0
  81. package/dist/types/types/portable-did.d.ts.map +1 -0
  82. package/dist/types/utils.d.ts +378 -0
  83. package/dist/types/utils.d.ts.map +1 -0
  84. package/dist/utils.js +28 -0
  85. package/dist/utils.js.map +7 -0
  86. package/package.json +116 -0
  87. package/src/bearer-did.ts +287 -0
  88. package/src/did-error.ts +75 -0
  89. package/src/did.ts +186 -0
  90. package/src/index.ts +21 -0
  91. package/src/methods/did-dht.ts +1637 -0
  92. package/src/methods/did-ion.ts +887 -0
  93. package/src/methods/did-jwk.ts +410 -0
  94. package/src/methods/did-key.ts +1248 -0
  95. package/src/methods/did-method.ts +276 -0
  96. package/src/methods/did-web.ts +96 -0
  97. package/src/resolver/resolver-cache-level.ts +163 -0
  98. package/src/resolver/resolver-cache-noop.ts +26 -0
  99. package/src/resolver/universal-resolver.ts +238 -0
  100. package/src/types/did-core.ts +580 -0
  101. package/src/types/did-resolution.ts +93 -0
  102. package/src/types/multibase.ts +29 -0
  103. package/src/types/portable-did.ts +64 -0
  104. package/src/utils.ts +532 -0
@@ -0,0 +1,410 @@
1
+ import type {
2
+ Jwk,
3
+ CryptoApi,
4
+ KeyIdentifier,
5
+ KmsExportKeyParams,
6
+ KmsImportKeyParams,
7
+ KeyImporterExporter,
8
+ InferKeyGeneratorAlgorithm,
9
+ } from '@enbox/crypto';
10
+
11
+ import { Convert } from '@enbox/common';
12
+ import { LocalKeyManager } from '@enbox/crypto';
13
+
14
+ import type { PortableDid } from '../types/portable-did.js';
15
+ import type { DidCreateOptions, DidCreateVerificationMethod } from './did-method.js';
16
+ import type { DidDocument, DidResolutionOptions, DidResolutionResult, DidVerificationMethod } from '../types/did-core.js';
17
+
18
+ import { Did } from '../did.js';
19
+ import { DidMethod } from './did-method.js';
20
+ import { BearerDid } from '../bearer-did.js';
21
+ import { DidError, DidErrorCode } from '../did-error.js';
22
+ import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
23
+
24
+ /**
25
+ * Defines the set of options available when creating a new Decentralized Identifier (DID) with the
26
+ * 'did:jwk' method.
27
+ *
28
+ * Either the `algorithm` or `verificationMethods` option can be specified, but not both.
29
+ * - A new key will be generated using the algorithm identifier specified in either the `algorithm`
30
+ * property or the `verificationMethods` object's `algorithm` property.
31
+ * - If `verificationMethods` is given, it must contain exactly one entry since DID JWK only
32
+ * supports a single verification method.
33
+ * - If neither is given, the default is to generate a new Ed25519 key.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // DID Creation
38
+ *
39
+ * // By default, when no options are given, a new Ed25519 key will be generated.
40
+ * const did = await DidJwk.create();
41
+ *
42
+ * // The algorithm to use for key generation can be specified as a top-level option.
43
+ * const did = await DidJwk.create({
44
+ * options: { algorithm = 'ES256K' }
45
+ * });
46
+ *
47
+ * // Or, alternatively as a property of the verification method.
48
+ * const did = await DidJwk.create({
49
+ * options: {
50
+ * verificationMethods: [{ algorithm = 'ES256K' }]
51
+ * }
52
+ * });
53
+ *
54
+ * // DID Creation with a KMS
55
+ * const keyManager = new LocalKeyManager();
56
+ * const did = await DidJwk.create({ keyManager });
57
+ *
58
+ * // DID Resolution
59
+ * const resolutionResult = await DidJwk.resolve({ did: did.uri });
60
+ *
61
+ * // Signature Operations
62
+ * const signer = await did.getSigner();
63
+ * const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
64
+ * const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
65
+ *
66
+ * // Import / Export
67
+ *
68
+ * // Export a BearerDid object to the PortableDid format.
69
+ * const portableDid = await did.export();
70
+ *
71
+ * // Reconstruct a BearerDid object from a PortableDid
72
+ * const did = await DidJwk.import(portableDid);
73
+ * ```
74
+ */
75
+ export interface DidJwkCreateOptions<TKms> extends DidCreateOptions<TKms> {
76
+ /**
77
+ * Optionally specify the algorithm to be used for key generation.
78
+ */
79
+ algorithm?: TKms extends CryptoApi
80
+ ? InferKeyGeneratorAlgorithm<TKms>
81
+ : InferKeyGeneratorAlgorithm<LocalKeyManager>;
82
+
83
+ /**
84
+ * Alternatively, specify the algorithm to be used for key generation of the single verification
85
+ * method in the DID Document.
86
+ */
87
+ verificationMethods?: DidCreateVerificationMethod<TKms>[];
88
+ }
89
+
90
+ /**
91
+ * The `DidJwk` class provides an implementation of the `did:jwk` DID method.
92
+ *
93
+ * Features:
94
+ * - DID Creation: Create new `did:jwk` DIDs.
95
+ * - DID Key Management: Instantiate a DID object from an existing verification method key set or
96
+ * or a key in a Key Management System (KMS). If supported by the KMS, a DID's
97
+ * key can be exported to a portable DID format.
98
+ * - DID Resolution: Resolve a `did:jwk` to its corresponding DID Document.
99
+ * - Signature Operations: Sign and verify messages using keys associated with a DID.
100
+ *
101
+ * @remarks
102
+ * The `did:jwk` DID method uses a single JSON Web Key (JWK) to generate a DID and does not rely
103
+ * on any external system such as a blockchain or centralized database. This characteristic makes
104
+ * it suitable for use cases where a assertions about a DID Subject can be self-verifiable by
105
+ * third parties.
106
+ *
107
+ * The DID URI is formed by Base64URL-encoding the JWK and prefixing with `did:jwk:`. The DID
108
+ * Document of a `did:jwk` DID contains a single verification method, which is the JWK used
109
+ * to generate the DID. The verification method is identified by the key ID `#0`.
110
+ *
111
+ * @see {@link https://github.com/quartzjer/did-jwk/blob/main/spec.md | DID JWK Specification}
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * // DID Creation
116
+ * const did = await DidJwk.create();
117
+ *
118
+ * // DID Creation with a KMS
119
+ * const keyManager = new LocalKeyManager();
120
+ * const did = await DidJwk.create({ keyManager });
121
+ *
122
+ * // DID Resolution
123
+ * const resolutionResult = await DidJwk.resolve({ did: did.uri });
124
+ *
125
+ * // Signature Operations
126
+ * const signer = await did.getSigner();
127
+ * const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
128
+ * const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
129
+ *
130
+ * // Key Management
131
+ *
132
+ * // Instantiate a DID object from an existing key in a KMS
133
+ * const did = await DidJwk.fromKeyManager({
134
+ * didUri: 'did:jwk:eyJrIjoiT0tQIiwidCI6IkV1c2UyNTYifQ',
135
+ * keyManager
136
+ * });
137
+ *
138
+ * // Instantiate a DID object from an existing verification method key
139
+ * const did = await DidJwk.fromKeys({
140
+ * verificationMethods: [{
141
+ * publicKeyJwk: {
142
+ * kty: 'OKP',
143
+ * crv: 'Ed25519',
144
+ * x: 'cHs7YMLQ3gCWjkacMURBsnEJBcEsvlsE5DfnsfTNDP4'
145
+ * },
146
+ * privateKeyJwk: {
147
+ * kty: 'OKP',
148
+ * crv: 'Ed25519',
149
+ * x: 'cHs7YMLQ3gCWjkacMURBsnEJBcEsvlsE5DfnsfTNDP4',
150
+ * d: 'bdcGE4KzEaekOwoa-ee3gAm1a991WvNj_Eq3WKyqTnE'
151
+ * }
152
+ * }]
153
+ * });
154
+ *
155
+ * // Convert a DID object to a portable format
156
+ * const portableDid = await DidJwk.toKeys({ did });
157
+ *
158
+ * // Reconstruct a DID object from a portable format
159
+ * const did = await DidJwk.fromKeys(portableDid);
160
+ * ```
161
+ */
162
+ export class DidJwk extends DidMethod {
163
+
164
+ /**
165
+ * Name of the DID method, as defined in the DID JWK specification.
166
+ */
167
+ public static methodName = 'jwk';
168
+
169
+ /**
170
+ * Creates a new DID using the `did:jwk` method formed from a newly generated key.
171
+ *
172
+ * @remarks
173
+ * The DID URI is formed by Base64URL-encoding the JWK and prefixing with `did:jwk:`.
174
+ *
175
+ * Notes:
176
+ * - If no `options` are given, by default a new Ed25519 key will be generated.
177
+ * - The `algorithm` and `verificationMethods` options are mutually exclusive. If both are given,
178
+ * an error will be thrown.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * // DID Creation
183
+ * const did = await DidJwk.create();
184
+ *
185
+ * // DID Creation with a KMS
186
+ * const keyManager = new LocalKeyManager();
187
+ * const did = await DidJwk.create({ keyManager });
188
+ * ```
189
+ *
190
+ * @param params - The parameters for the create operation.
191
+ * @param params.keyManager - Optionally specify a Key Management System (KMS) used to generate
192
+ * keys and sign data.
193
+ * @param params.options - Optional parameters that can be specified when creating a new DID.
194
+ * @returns A Promise resolving to a {@link BearerDid} object representing the new DID.
195
+ */
196
+ public static async create<TKms extends CryptoApi | undefined = undefined>({
197
+ keyManager = new LocalKeyManager(),
198
+ options = {}
199
+ }: {
200
+ keyManager?: TKms;
201
+ options?: DidJwkCreateOptions<TKms>;
202
+ } = {}): Promise<BearerDid> {
203
+ // Before processing the create operation, validate DID-method-specific requirements to prevent
204
+ // keys from being generated unnecessarily.
205
+
206
+ // Check 1: Validate that `algorithm` or `verificationMethods` options are not both given.
207
+ if (options.algorithm && options.verificationMethods) {
208
+ throw new Error(`The 'algorithm' and 'verificationMethods' options are mutually exclusive`);
209
+ }
210
+
211
+ // Check 2: If `verificationMethods` is given, it must contain exactly one entry since DID JWK
212
+ // only supports a single verification method.
213
+ if (options.verificationMethods && options.verificationMethods.length !== 1) {
214
+ throw new Error(`The 'verificationMethods' option must contain exactly one entry`);
215
+ }
216
+
217
+ // Default to Ed25519 key generation if an algorithm is not given.
218
+ const algorithm = options.algorithm ?? options.verificationMethods?.[0]?.algorithm ?? 'Ed25519';
219
+
220
+ // Generate a new key using the specified `algorithm`.
221
+ const keyUri = await keyManager.generateKey({ algorithm });
222
+ const publicKey = await keyManager.getPublicKey({ keyUri });
223
+
224
+ // Compute the DID identifier from the public key by serializing the JWK to a UTF-8 string and
225
+ // encoding in Base64URL format.
226
+ const identifier = Convert.object(publicKey).toBase64Url();
227
+
228
+ // Attach the prefix `did:jwk` to form the complete DID URI.
229
+ const didUri = `did:${DidJwk.methodName}:${identifier}`;
230
+
231
+ // Expand the DID URI string to a DID document.
232
+ const didResolutionResult = await DidJwk.resolve(didUri);
233
+ const document = didResolutionResult.didDocument as DidDocument;
234
+
235
+ // Create the BearerDid object from the generated key material.
236
+ const did = new BearerDid({
237
+ uri : didUri,
238
+ document,
239
+ metadata : {},
240
+ keyManager
241
+ });
242
+
243
+ return did;
244
+ }
245
+
246
+ /**
247
+ * Given the W3C DID Document of a `did:jwk` DID, return the verification method that will be used
248
+ * for signing messages and credentials. If given, the `methodId` parameter is used to select the
249
+ * verification method. If not given, the first verification method in the DID Document is used.
250
+ *
251
+ * Note that for DID JWK, only one verification method can exist so specifying `methodId` could be
252
+ * considered redundant or unnecessary. The option is provided for consistency with other DID
253
+ * method implementations.
254
+ *
255
+ * @param params - The parameters for the `getSigningMethod` operation.
256
+ * @param params.didDocument - DID Document to get the verification method from.
257
+ * @param params.methodId - ID of the verification method to use for signing.
258
+ * @returns Verification method to use for signing.
259
+ */
260
+ public static async getSigningMethod({ didDocument }: {
261
+ didDocument: DidDocument;
262
+ methodId?: string;
263
+ }): Promise<DidVerificationMethod> {
264
+ // Verify the DID method is supported.
265
+ const parsedDid = Did.parse(didDocument.id);
266
+ if (parsedDid && parsedDid.method !== this.methodName) {
267
+ throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
268
+ }
269
+
270
+ // Attempt to find the verification method in the DID Document.
271
+ const [ verificationMethod ] = didDocument.verificationMethod ?? [];
272
+
273
+ if (!(verificationMethod && verificationMethod.publicKeyJwk)) {
274
+ throw new DidError(DidErrorCode.InternalError, 'A verification method intended for signing could not be determined from the DID Document');
275
+ }
276
+
277
+ return verificationMethod;
278
+ }
279
+
280
+ /**
281
+ * Instantiates a {@link BearerDid} object for the DID JWK method from a given {@link PortableDid}.
282
+ *
283
+ * This method allows for the creation of a `BearerDid` object using a previously created DID's
284
+ * key material, DID document, and metadata.
285
+ *
286
+ * @remarks
287
+ * The `verificationMethod` array of the DID document must contain exactly one key since the
288
+ * `did:jwk` method only supports a single verification method.
289
+ *
290
+ * @example
291
+ * ```ts
292
+ * // Export an existing BearerDid to PortableDid format.
293
+ * const portableDid = await did.export();
294
+ * // Reconstruct a BearerDid object from the PortableDid.
295
+ * const did = await DidJwk.import({ portableDid });
296
+ * ```
297
+ *
298
+ * @param params - The parameters for the import operation.
299
+ * @param params.portableDid - The PortableDid object to import.
300
+ * @param params.keyManager - Optionally specify an external Key Management System (KMS) used to
301
+ * generate keys and sign data. If not given, a new
302
+ * {@link LocalKeyManager} instance will be created and
303
+ * used.
304
+ * @returns A Promise resolving to a `BearerDid` object representing the DID formed from the provided keys.
305
+ * @throws An error if the DID document does not contain exactly one verification method.
306
+ */
307
+ public static async import({ portableDid, keyManager = new LocalKeyManager() }: {
308
+ keyManager?: CryptoApi & KeyImporterExporter<KmsImportKeyParams, KeyIdentifier, KmsExportKeyParams>;
309
+ portableDid: PortableDid;
310
+ }): Promise<BearerDid> {
311
+ // Verify the DID method is supported.
312
+ const parsedDid = Did.parse(portableDid.uri);
313
+ if (parsedDid?.method !== DidJwk.methodName) {
314
+ throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported`);
315
+ }
316
+
317
+ // Use the given PortableDid to construct the BearerDid object.
318
+ const did = await BearerDid.import({ portableDid, keyManager });
319
+
320
+ // Validate that the given DID document contains exactly one verification method.
321
+ // Note: The non-undefined assertion is necessary because the type system cannot infer that
322
+ // the `verificationMethod` property is defined -- which is checked by `BearerDid.import()`.
323
+ if (did.document.verificationMethod!.length !== 1) {
324
+ throw new DidError(DidErrorCode.InvalidDidDocument, `DID document must contain exactly one verification method`);
325
+ }
326
+
327
+ return did;
328
+ }
329
+
330
+ /**
331
+ * Resolves a `did:jwk` identifier to a DID Document.
332
+ *
333
+ * @param didUri - The DID to be resolved.
334
+ * @param _options - Optional parameters for resolving the DID. Unused by this DID method.
335
+ * @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of the resolution.
336
+ */
337
+ public static async resolve(didUri: string, _options?: DidResolutionOptions): Promise<DidResolutionResult> {
338
+ // Attempt to parse the DID URI.
339
+ const parsedDid = Did.parse(didUri);
340
+
341
+ // Attempt to decode the Base64URL-encoded JWK.
342
+ let publicKey: Jwk | undefined;
343
+ try {
344
+ publicKey = Convert.base64Url(parsedDid!.id).toObject() as Jwk;
345
+ } catch { /* Consume the error so that a DID resolution error can be returned later. */ }
346
+
347
+ // If parsing or decoding failed, the DID is invalid.
348
+ if (!parsedDid || !publicKey) {
349
+ return {
350
+ ...EMPTY_DID_RESOLUTION_RESULT,
351
+ didResolutionMetadata: { error: 'invalidDid' }
352
+ };
353
+ }
354
+
355
+ // If the DID method is not "jwk", return an error.
356
+ if (parsedDid.method !== DidJwk.methodName) {
357
+ return {
358
+ ...EMPTY_DID_RESOLUTION_RESULT,
359
+ didResolutionMetadata: { error: 'methodNotSupported' }
360
+ };
361
+ }
362
+
363
+ const didDocument: DidDocument = {
364
+ '@context': [
365
+ 'https://www.w3.org/ns/did/v1'
366
+ ],
367
+ id: parsedDid.uri
368
+ };
369
+
370
+ const keyUri = `${didDocument.id}#0`;
371
+
372
+ // Set the Verification Method property.
373
+ didDocument.verificationMethod = [{
374
+ id : keyUri,
375
+ type : 'JsonWebKey',
376
+ controller : didDocument.id,
377
+ publicKeyJwk : publicKey
378
+ }];
379
+
380
+ // Set the Verification Relationship properties.
381
+ didDocument.authentication = [keyUri];
382
+ didDocument.assertionMethod = [keyUri];
383
+ didDocument.capabilityInvocation = [keyUri];
384
+ didDocument.capabilityDelegation = [keyUri];
385
+ didDocument.keyAgreement = [keyUri];
386
+
387
+ // If the JWK contains a `use` property with the value "sig" then the `keyAgreement` property
388
+ // is not included in the DID Document. If the `use` value is "enc" then only the `keyAgreement`
389
+ // property is included in the DID Document.
390
+ switch (publicKey.use) {
391
+ case 'sig': {
392
+ delete didDocument.keyAgreement;
393
+ break;
394
+ }
395
+
396
+ case 'enc': {
397
+ delete didDocument.authentication;
398
+ delete didDocument.assertionMethod;
399
+ delete didDocument.capabilityInvocation;
400
+ delete didDocument.capabilityDelegation;
401
+ break;
402
+ }
403
+ }
404
+
405
+ return {
406
+ ...EMPTY_DID_RESOLUTION_RESULT,
407
+ didDocument,
408
+ };
409
+ }
410
+ }