@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.
- package/LICENSE +201 -0
- package/README.md +1 -0
- package/dist/browser.js +77 -0
- package/dist/browser.js.map +7 -0
- package/dist/browser.mjs +77 -0
- package/dist/browser.mjs.map +7 -0
- package/dist/cjs/index.js +6303 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/utils.js +245 -0
- package/dist/cjs/utils.js.map +7 -0
- package/dist/esm/bearer-did.js +201 -0
- package/dist/esm/bearer-did.js.map +1 -0
- package/dist/esm/did-error.js +62 -0
- package/dist/esm/did-error.js.map +1 -0
- package/dist/esm/did.js +114 -0
- package/dist/esm/did.js.map +1 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/methods/did-dht.js +1241 -0
- package/dist/esm/methods/did-dht.js.map +1 -0
- package/dist/esm/methods/did-ion.js +570 -0
- package/dist/esm/methods/did-ion.js.map +1 -0
- package/dist/esm/methods/did-jwk.js +298 -0
- package/dist/esm/methods/did-jwk.js.map +1 -0
- package/dist/esm/methods/did-key.js +983 -0
- package/dist/esm/methods/did-key.js.map +1 -0
- package/dist/esm/methods/did-method.js +53 -0
- package/dist/esm/methods/did-method.js.map +1 -0
- package/dist/esm/methods/did-web.js +83 -0
- package/dist/esm/methods/did-web.js.map +1 -0
- package/dist/esm/resolver/resolver-cache-level.js +101 -0
- package/dist/esm/resolver/resolver-cache-level.js.map +1 -0
- package/dist/esm/resolver/resolver-cache-noop.js +24 -0
- package/dist/esm/resolver/resolver-cache-noop.js.map +1 -0
- package/dist/esm/resolver/universal-resolver.js +187 -0
- package/dist/esm/resolver/universal-resolver.js.map +1 -0
- package/dist/esm/types/did-core.js +51 -0
- package/dist/esm/types/did-core.js.map +1 -0
- package/dist/esm/types/did-resolution.js +12 -0
- package/dist/esm/types/did-resolution.js.map +1 -0
- package/dist/esm/types/multibase.js +2 -0
- package/dist/esm/types/multibase.js.map +1 -0
- package/dist/esm/types/portable-did.js +2 -0
- package/dist/esm/types/portable-did.js.map +1 -0
- package/dist/esm/utils.js +458 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/types/bearer-did.d.ts +143 -0
- package/dist/types/bearer-did.d.ts.map +1 -0
- package/dist/types/did-error.d.ts +50 -0
- package/dist/types/did-error.d.ts.map +1 -0
- package/dist/types/did.d.ts +125 -0
- package/dist/types/did.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/methods/did-dht.d.ts +682 -0
- package/dist/types/methods/did-dht.d.ts.map +1 -0
- package/dist/types/methods/did-ion.d.ts +492 -0
- package/dist/types/methods/did-ion.d.ts.map +1 -0
- package/dist/types/methods/did-jwk.d.ts +236 -0
- package/dist/types/methods/did-jwk.d.ts.map +1 -0
- package/dist/types/methods/did-key.d.ts +499 -0
- package/dist/types/methods/did-key.d.ts.map +1 -0
- package/dist/types/methods/did-method.d.ts +238 -0
- package/dist/types/methods/did-method.d.ts.map +1 -0
- package/dist/types/methods/did-web.d.ts +37 -0
- package/dist/types/methods/did-web.d.ts.map +1 -0
- package/dist/types/resolver/resolver-cache-level.d.ts +86 -0
- package/dist/types/resolver/resolver-cache-level.d.ts.map +1 -0
- package/dist/types/resolver/resolver-cache-noop.d.ts +9 -0
- package/dist/types/resolver/resolver-cache-noop.d.ts.map +1 -0
- package/dist/types/resolver/universal-resolver.d.ts +109 -0
- package/dist/types/resolver/universal-resolver.d.ts.map +1 -0
- package/dist/types/types/did-core.d.ts +523 -0
- package/dist/types/types/did-core.d.ts.map +1 -0
- package/dist/types/types/did-resolution.d.ts +85 -0
- package/dist/types/types/did-resolution.d.ts.map +1 -0
- package/dist/types/types/multibase.d.ts +28 -0
- package/dist/types/types/multibase.d.ts.map +1 -0
- package/dist/types/types/portable-did.d.ts +59 -0
- package/dist/types/types/portable-did.d.ts.map +1 -0
- package/dist/types/utils.d.ts +378 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/utils.js +28 -0
- package/dist/utils.js.map +7 -0
- package/package.json +116 -0
- package/src/bearer-did.ts +287 -0
- package/src/did-error.ts +75 -0
- package/src/did.ts +186 -0
- package/src/index.ts +21 -0
- package/src/methods/did-dht.ts +1637 -0
- package/src/methods/did-ion.ts +887 -0
- package/src/methods/did-jwk.ts +410 -0
- package/src/methods/did-key.ts +1248 -0
- package/src/methods/did-method.ts +276 -0
- package/src/methods/did-web.ts +96 -0
- package/src/resolver/resolver-cache-level.ts +163 -0
- package/src/resolver/resolver-cache-noop.ts +26 -0
- package/src/resolver/universal-resolver.ts +238 -0
- package/src/types/did-core.ts +580 -0
- package/src/types/did-resolution.ts +93 -0
- package/src/types/multibase.ts +29 -0
- package/src/types/portable-did.ts +64 -0
- package/src/utils.ts +532 -0
|
@@ -0,0 +1,1248 @@
|
|
|
1
|
+
import type { MulticodecCode, MulticodecDefinition, RequireOnly } from '@enbox/common';
|
|
2
|
+
import type {
|
|
3
|
+
Jwk,
|
|
4
|
+
CryptoApi,
|
|
5
|
+
KeyCompressor,
|
|
6
|
+
KeyIdentifier,
|
|
7
|
+
KmsExportKeyParams,
|
|
8
|
+
KmsImportKeyParams,
|
|
9
|
+
KeyImporterExporter,
|
|
10
|
+
AsymmetricKeyConverter,
|
|
11
|
+
InferKeyGeneratorAlgorithm,
|
|
12
|
+
} from '@enbox/crypto';
|
|
13
|
+
|
|
14
|
+
import { Multicodec, universalTypeOf } from '@enbox/common';
|
|
15
|
+
import {
|
|
16
|
+
X25519,
|
|
17
|
+
Ed25519,
|
|
18
|
+
Secp256k1,
|
|
19
|
+
Secp256r1,
|
|
20
|
+
LocalKeyManager,
|
|
21
|
+
} from '@enbox/crypto';
|
|
22
|
+
|
|
23
|
+
import type { PortableDid } from '../types/portable-did.js';
|
|
24
|
+
import type { DidCreateOptions, DidCreateVerificationMethod } from './did-method.js';
|
|
25
|
+
import type {
|
|
26
|
+
DidDocument,
|
|
27
|
+
DidResolutionOptions,
|
|
28
|
+
DidResolutionResult,
|
|
29
|
+
DidVerificationMethod,
|
|
30
|
+
} from '../types/did-core.js';
|
|
31
|
+
|
|
32
|
+
import { Did } from '../did.js';
|
|
33
|
+
import { DidMethod } from './did-method.js';
|
|
34
|
+
import { BearerDid } from '../bearer-did.js';
|
|
35
|
+
import { DidError, DidErrorCode } from '../did-error.js';
|
|
36
|
+
import { KeyWithMulticodec } from '../types/multibase.js';
|
|
37
|
+
import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
|
|
38
|
+
import { getVerificationMethodTypes, keyBytesToMultibaseId, multibaseIdToKeyBytes } from '../utils.js';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Defines the set of options available when creating a new Decentralized Identifier (DID) with the
|
|
42
|
+
* 'did:key' method.
|
|
43
|
+
*
|
|
44
|
+
* Either the `algorithm` or `verificationMethods` option can be specified, but not both.
|
|
45
|
+
* - A new key will be generated using the algorithm identifier specified in either the `algorithm`
|
|
46
|
+
* property or the `verificationMethods` object's `algorithm` property.
|
|
47
|
+
* - If `verificationMethods` is given, it must contain exactly one entry since DID Key only
|
|
48
|
+
* supports a single verification method.
|
|
49
|
+
* - If neither is given, the default is to generate a new Ed25519 key.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* // By default, when no options are given, a new Ed25519 key will be generated.
|
|
54
|
+
* const did = await DidKey.create();
|
|
55
|
+
*
|
|
56
|
+
* // The algorithm to use for key generation can be specified as a top-level option.
|
|
57
|
+
* const did = await DidKey.create({
|
|
58
|
+
* options: { algorithm = 'secp256k1' }
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* // Or, alternatively as a property of the verification method.
|
|
62
|
+
* const did = await DidKey.create({
|
|
63
|
+
* options: {
|
|
64
|
+
* verificationMethods: [{ algorithm = 'secp256k1' }]
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // DID Creation with a KMS
|
|
69
|
+
* const keyManager = new LocalKeyManager();
|
|
70
|
+
* const did = await DidKey.create({ keyManager });
|
|
71
|
+
*
|
|
72
|
+
* // DID Resolution
|
|
73
|
+
* const resolutionResult = await DidKey.resolve({ did: did.uri });
|
|
74
|
+
*
|
|
75
|
+
* // Signature Operations
|
|
76
|
+
* const signer = await did.getSigner();
|
|
77
|
+
* const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
|
|
78
|
+
* const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
|
|
79
|
+
*
|
|
80
|
+
* // Import / Export
|
|
81
|
+
*
|
|
82
|
+
* // Export a BearerDid object to the PortableDid format.
|
|
83
|
+
* const portableDid = await did.export();
|
|
84
|
+
*
|
|
85
|
+
* // Reconstruct a BearerDid object from a PortableDid
|
|
86
|
+
* const did = await DidKey.import(portableDid);
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export interface DidKeyCreateOptions<TKms> extends DidCreateOptions<TKms> {
|
|
90
|
+
/**
|
|
91
|
+
* Optionally specify the algorithm to be used for key generation.
|
|
92
|
+
*/
|
|
93
|
+
algorithm?: TKms extends CryptoApi
|
|
94
|
+
? InferKeyGeneratorAlgorithm<TKms>
|
|
95
|
+
: InferKeyGeneratorAlgorithm<LocalKeyManager>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Optionally specify an array of JSON-LD context links for the @context property of the DID
|
|
99
|
+
* document.
|
|
100
|
+
*
|
|
101
|
+
* The @context property provides a JSON-LD processor with the information necessary to interpret
|
|
102
|
+
* the DID document JSON. The default context URL is 'https://www.w3.org/ns/did/v1'.
|
|
103
|
+
*/
|
|
104
|
+
defaultContext?: string;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Optionally enable encryption key derivation during DID creation.
|
|
108
|
+
*
|
|
109
|
+
* By default, this option is set to `false`, which means encryption key derivation is not
|
|
110
|
+
* performed unless explicitly enabled.
|
|
111
|
+
*
|
|
112
|
+
* When set to `true`, an `X25519` key will be derived from the `Ed25519` public key used to
|
|
113
|
+
* create the DID. This feature enables the same DID to be used for encrypted communication, in
|
|
114
|
+
* addition to signature verification.
|
|
115
|
+
*
|
|
116
|
+
* Notes:
|
|
117
|
+
* - This option is ONLY applicable when the `algorithm` of the DID's public key is `Ed25519`.
|
|
118
|
+
* - Enabling this introduces specific cryptographic considerations that should be understood
|
|
119
|
+
* before using the same key pair for digital signatures and encrypted communication. See the following for more information:
|
|
120
|
+
*/
|
|
121
|
+
enableEncryptionKeyDerivation?: boolean;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Optionally enable experimental public key types during DID creation.
|
|
125
|
+
* By default, this option is set to `false`, which means experimental public key types are not
|
|
126
|
+
* supported.
|
|
127
|
+
*
|
|
128
|
+
* Note: This implementation of the DID Key method does not support any experimental public key
|
|
129
|
+
* types.
|
|
130
|
+
*/
|
|
131
|
+
enableExperimentalPublicKeyTypes?: boolean;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Optionally specify the format of the public key to be used for DID creation.
|
|
135
|
+
*/
|
|
136
|
+
publicKeyFormat?: keyof typeof DidKeyVerificationMethodType;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Alternatively, specify the algorithm to be used for key generation of the single verification
|
|
140
|
+
* method in the DID Document.
|
|
141
|
+
*/
|
|
142
|
+
verificationMethods?: DidCreateVerificationMethod<TKms>[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Enumerates the types of keys that can be used in a DID Key document.
|
|
147
|
+
*
|
|
148
|
+
* The DID Key method supports various cryptographic key types. These key types are essential for
|
|
149
|
+
* the creation and management of DIDs and their associated cryptographic operations like signing
|
|
150
|
+
* and encryption.
|
|
151
|
+
*/
|
|
152
|
+
export enum DidKeyRegisteredKeyType {
|
|
153
|
+
/**
|
|
154
|
+
* Ed25519: A public-key signature system using the EdDSA (Edwards-curve Digital Signature
|
|
155
|
+
* Algorithm) and Curve25519.
|
|
156
|
+
*/
|
|
157
|
+
Ed25519 = 'Ed25519',
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* secp256k1: A cryptographic curve used for digital signatures in a range of decentralized
|
|
161
|
+
* systems.
|
|
162
|
+
*/
|
|
163
|
+
secp256k1 = 'secp256k1',
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* secp256r1: Also known as P-256 or prime256v1, this curve is used for cryptographic operations
|
|
167
|
+
* and is widely supported in various cryptographic libraries and standards.
|
|
168
|
+
*/
|
|
169
|
+
secp256r1 = 'secp256r1',
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* X25519: A Diffie-Hellman key exchange algorithm using Curve25519.
|
|
173
|
+
*/
|
|
174
|
+
X25519 = 'X25519'
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Enumerates the verification method types supported by the DID Key method.
|
|
179
|
+
*
|
|
180
|
+
* This enum defines the URIs associated with common verification methods used in DID Documents.
|
|
181
|
+
* These URIs represent cryptographic suites or key types standardized for use across decentralized
|
|
182
|
+
* identifiers (DIDs).
|
|
183
|
+
*/
|
|
184
|
+
export const DidKeyVerificationMethodType = {
|
|
185
|
+
/** Represents an Ed25519 public key used for digital signatures. */
|
|
186
|
+
Ed25519VerificationKey2020: 'https://w3id.org/security/suites/ed25519-2020/v1',
|
|
187
|
+
|
|
188
|
+
/** Represents a JSON Web Key (JWK) used for digital signatures and key agreement protocols. */
|
|
189
|
+
JsonWebKey2020: 'https://w3id.org/security/suites/jws-2020/v1',
|
|
190
|
+
|
|
191
|
+
/** Represents an X25519 public key used for key agreement protocols. */
|
|
192
|
+
X25519KeyAgreementKey2020: 'https://w3id.org/security/suites/x25519-2020/v1',
|
|
193
|
+
} as const;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Private helper that maps algorithm identifiers to their corresponding DID Key
|
|
197
|
+
* {@link DidKeyRegisteredKeyType | registered key type}.
|
|
198
|
+
*/
|
|
199
|
+
const AlgorithmToKeyTypeMap = {
|
|
200
|
+
Ed25519 : DidKeyRegisteredKeyType.Ed25519,
|
|
201
|
+
ES256K : DidKeyRegisteredKeyType.secp256k1,
|
|
202
|
+
ES256 : DidKeyRegisteredKeyType.secp256r1,
|
|
203
|
+
'P-256' : DidKeyRegisteredKeyType.secp256r1,
|
|
204
|
+
secp256k1 : DidKeyRegisteredKeyType.secp256k1,
|
|
205
|
+
secp256r1 : DidKeyRegisteredKeyType.secp256r1,
|
|
206
|
+
X25519 : DidKeyRegisteredKeyType.X25519
|
|
207
|
+
} as const;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The `DidKey` class provides an implementation of the 'did:key' DID method.
|
|
211
|
+
*
|
|
212
|
+
* Features:
|
|
213
|
+
* - DID Creation: Create new `did:key` DIDs.
|
|
214
|
+
* - DID Key Management: Instantiate a DID object from an existing verification method key set or
|
|
215
|
+
* or a key in a Key Management System (KMS). If supported by the KMS, a DID's
|
|
216
|
+
* key can be exported to a portable DID format.
|
|
217
|
+
* - DID Resolution: Resolve a `did:key` to its corresponding DID Document.
|
|
218
|
+
* - Signature Operations: Sign and verify messages using keys associated with a DID.
|
|
219
|
+
*
|
|
220
|
+
* @remarks
|
|
221
|
+
* The `did:key` DID method uses a single public key to generate a DID and does not rely
|
|
222
|
+
* on any external system such as a blockchain or centralized database. This characteristic makes
|
|
223
|
+
* it suitable for use cases where a assertions about a DID Subject can be self-verifiable by
|
|
224
|
+
* third parties.
|
|
225
|
+
*
|
|
226
|
+
* The method-specific identifier is formed by
|
|
227
|
+
* {@link https://datatracker.ietf.org/doc/html/draft-multiformats-multibase#name-base-58-bitcoin-encoding | Multibase base58-btc}
|
|
228
|
+
* encoding the concatenation of the
|
|
229
|
+
* {@link https://github.com/multiformats/multicodec/blob/master/README.md | Multicodec} identifier
|
|
230
|
+
* for the public key type and the raw public key bytes. To form the DID URI, the method-specific
|
|
231
|
+
* identifier is prefixed with the string 'did:key:'.
|
|
232
|
+
*
|
|
233
|
+
* This method can optionally derive an encryption key from the public key used to create the DID
|
|
234
|
+
* if and only if the public key algorithm is `Ed25519`. This feature enables the same DID to be
|
|
235
|
+
* used for encrypted communication, in addition to signature verification. To enable this
|
|
236
|
+
* feature when calling {@link DidKey.create | `DidKey.create()`}, first specify an `algorithm` of
|
|
237
|
+
* `Ed25519` or provide a `keySet` referencing an `Ed25519` key and then set the
|
|
238
|
+
* `enableEncryptionKeyDerivation` option to `true`.
|
|
239
|
+
*
|
|
240
|
+
* Note:
|
|
241
|
+
* - The authors of the DID Key specification have indicated that use of this method for long-lived
|
|
242
|
+
* use cases is only recommended when accompanied with high confidence that private keys are
|
|
243
|
+
* securely protected by software or hardware isolation.
|
|
244
|
+
*
|
|
245
|
+
* @see {@link https://w3c-ccg.github.io/did-method-key/ | DID Key Specification}
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```ts
|
|
249
|
+
* // DID Creation
|
|
250
|
+
* const did = await DidKey.create();
|
|
251
|
+
*
|
|
252
|
+
* // DID Creation with a KMS
|
|
253
|
+
* const keyManager = new LocalKeyManager();
|
|
254
|
+
* const did = await DidKey.create({ keyManager });
|
|
255
|
+
*
|
|
256
|
+
* // DID Resolution
|
|
257
|
+
* const resolutionResult = await DidKey.resolve({ did: did.uri });
|
|
258
|
+
*
|
|
259
|
+
* // Signature Operations
|
|
260
|
+
* const signer = await did.getSigner();
|
|
261
|
+
* const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
|
|
262
|
+
* const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
|
|
263
|
+
*
|
|
264
|
+
* // Key Management
|
|
265
|
+
*
|
|
266
|
+
* // Instantiate a DID object from an existing key in a KMS
|
|
267
|
+
* const did = await DidKey.fromKeyManager({
|
|
268
|
+
* didUri: 'did:key:z6MkpUzNmYVTGpqhStxK8yRKXWCRNm1bGYz8geAg2zmjYHKX',
|
|
269
|
+
* keyManager
|
|
270
|
+
* });
|
|
271
|
+
*
|
|
272
|
+
* // Instantiate a DID object from an existing verification method key
|
|
273
|
+
* const did = await DidKey.fromKeys({
|
|
274
|
+
* verificationMethods: [{
|
|
275
|
+
* publicKeyJwk: {
|
|
276
|
+
* kty: 'OKP',
|
|
277
|
+
* crv: 'Ed25519',
|
|
278
|
+
* x: 'cHs7YMLQ3gCWjkacMURBsnEJBcEsvlsE5DfnsfTNDP4'
|
|
279
|
+
* },
|
|
280
|
+
* privateKeyJwk: {
|
|
281
|
+
* kty: 'OKP',
|
|
282
|
+
* crv: 'Ed25519',
|
|
283
|
+
* x: 'cHs7YMLQ3gCWjkacMURBsnEJBcEsvlsE5DfnsfTNDP4',
|
|
284
|
+
* d: 'bdcGE4KzEaekOwoa-ee3gAm1a991WvNj_Eq3WKyqTnE'
|
|
285
|
+
* }
|
|
286
|
+
* }]
|
|
287
|
+
* });
|
|
288
|
+
*
|
|
289
|
+
* // Convert a DID object to a portable format
|
|
290
|
+
* const portableDid = await DidKey.toKeys({ did });
|
|
291
|
+
*
|
|
292
|
+
* // Reconstruct a DID object from a portable format
|
|
293
|
+
* const did = await DidKey.fromKeys(portableDid);
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
export class DidKey extends DidMethod {
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Name of the DID method, as defined in the DID Key specification.
|
|
300
|
+
*/
|
|
301
|
+
public static methodName = 'key';
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Creates a new DID using the `did:key` method formed from a newly generated key.
|
|
305
|
+
*
|
|
306
|
+
* @remarks
|
|
307
|
+
* The DID URI is formed by
|
|
308
|
+
* {@link https://datatracker.ietf.org/doc/html/draft-multiformats-multibase#name-base-58-bitcoin-encoding | Multibase base58-btc}
|
|
309
|
+
* encoding the
|
|
310
|
+
* {@link https://github.com/multiformats/multicodec/blob/master/README.md | Multicodec}-encoded
|
|
311
|
+
* public key and prefixing with `did:key:`.
|
|
312
|
+
*
|
|
313
|
+
* This method can optionally derive an encryption key from the public key used to create the DID
|
|
314
|
+
* if and only if the public key algorithm is `Ed25519`. This feature enables the same DID to be
|
|
315
|
+
* used for encrypted communication, in addition to signature verification. To enable this
|
|
316
|
+
* feature, specify an `algorithm` of `Ed25519` as either a top-level option or in a
|
|
317
|
+
* `verificationMethod` and set the `enableEncryptionKeyDerivation` option to `true`.
|
|
318
|
+
*
|
|
319
|
+
* Notes:
|
|
320
|
+
* - If no `options` are given, by default a new Ed25519 key will be generated.
|
|
321
|
+
* - The `algorithm` and `verificationMethods` options are mutually exclusive. If both are given,
|
|
322
|
+
* an error will be thrown.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* // DID Creation
|
|
327
|
+
* const did = await DidKey.create();
|
|
328
|
+
*
|
|
329
|
+
* // DID Creation with a KMS
|
|
330
|
+
* const keyManager = new LocalKeyManager();
|
|
331
|
+
* const did = await DidKey.create({ keyManager });
|
|
332
|
+
* ```
|
|
333
|
+
*
|
|
334
|
+
* @param params - The parameters for the create operation.
|
|
335
|
+
* @param params.keyManager - Key Management System (KMS) used to generate keys and sign data.
|
|
336
|
+
* @param params.options - Optional parameters that can be specified when creating a new DID.
|
|
337
|
+
* @returns A Promise resolving to a {@link BearerDid} object representing the new DID.
|
|
338
|
+
*/
|
|
339
|
+
public static async create<TKms extends CryptoApi | undefined = undefined>({
|
|
340
|
+
keyManager = new LocalKeyManager(),
|
|
341
|
+
options = {}
|
|
342
|
+
}: {
|
|
343
|
+
keyManager?: TKms;
|
|
344
|
+
options?: DidKeyCreateOptions<TKms>;
|
|
345
|
+
} = {}): Promise<BearerDid> {
|
|
346
|
+
// Before processing the create operation, validate DID-method-specific requirements to prevent
|
|
347
|
+
// keys from being generated unnecessarily.
|
|
348
|
+
|
|
349
|
+
// Check 1: Validate that `algorithm` or `verificationMethods` options are not both given.
|
|
350
|
+
if (options.algorithm && options.verificationMethods) {
|
|
351
|
+
throw new Error(`The 'algorithm' and 'verificationMethods' options are mutually exclusive`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check 2: If `verificationMethods` is given, it must contain exactly one entry since DID Key
|
|
355
|
+
// only supports a single verification method.
|
|
356
|
+
if (options.verificationMethods && options.verificationMethods.length !== 1) {
|
|
357
|
+
throw new Error(`The 'verificationMethods' option must contain exactly one entry`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Default to Ed25519 key generation if an algorithm is not given.
|
|
361
|
+
const algorithm = options.algorithm ?? options.verificationMethods?.[0]?.algorithm ?? 'Ed25519';
|
|
362
|
+
|
|
363
|
+
// Generate a new key using the specified `algorithm`.
|
|
364
|
+
const keyUri = await keyManager.generateKey({ algorithm });
|
|
365
|
+
const publicKey = await keyManager.getPublicKey({ keyUri });
|
|
366
|
+
|
|
367
|
+
// Compute the DID identifier from the public key by converting the JWK to a multibase-encoded
|
|
368
|
+
// multicodec value.
|
|
369
|
+
const identifier = await DidKeyUtils.publicKeyToMultibaseId({ publicKey });
|
|
370
|
+
|
|
371
|
+
// Attach the prefix `did:key` to form the complete DID URI.
|
|
372
|
+
const didUri = `did:${DidKey.methodName}:${identifier}`;
|
|
373
|
+
|
|
374
|
+
// Expand the DID URI string to a DID document.
|
|
375
|
+
const didResolutionResult = await DidKey.resolve(didUri, options);
|
|
376
|
+
const document = didResolutionResult.didDocument as DidDocument;
|
|
377
|
+
|
|
378
|
+
// Create the BearerDid object from the generated key material.
|
|
379
|
+
const did = new BearerDid({
|
|
380
|
+
uri : didUri,
|
|
381
|
+
document,
|
|
382
|
+
metadata : {},
|
|
383
|
+
keyManager
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
return did;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Given the W3C DID Document of a `did:key` DID, return the verification method that will be used
|
|
391
|
+
* for signing messages and credentials. With DID Key, the first verification method in the
|
|
392
|
+
* authentication property in the DID Document is used.
|
|
393
|
+
*
|
|
394
|
+
* Note that for DID Key, only one verification method intended for signing can exist so
|
|
395
|
+
* specifying `methodId` could be considered redundant or unnecessary. The option is provided for
|
|
396
|
+
* consistency with other DID method implementations.
|
|
397
|
+
*
|
|
398
|
+
* @param params - The parameters for the `getSigningMethod` operation.
|
|
399
|
+
* @param params.didDocument - DID Document to get the verification method from.
|
|
400
|
+
* @param params.methodId - ID of the verification method to use for signing.
|
|
401
|
+
* @returns Verification method to use for signing.
|
|
402
|
+
*/
|
|
403
|
+
public static async getSigningMethod({ didDocument }: {
|
|
404
|
+
didDocument: DidDocument;
|
|
405
|
+
methodId?: string;
|
|
406
|
+
}): Promise<DidVerificationMethod> {
|
|
407
|
+
// Verify the DID method is supported.
|
|
408
|
+
const parsedDid = Did.parse(didDocument.id);
|
|
409
|
+
if (parsedDid && parsedDid.method !== this.methodName) {
|
|
410
|
+
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Attempt to ge the first verification method intended for signing claims.
|
|
414
|
+
const [ methodId ] = didDocument.assertionMethod || [];
|
|
415
|
+
const verificationMethod = didDocument.verificationMethod?.find(vm => vm.id === methodId);
|
|
416
|
+
|
|
417
|
+
if (!(verificationMethod && verificationMethod.publicKeyJwk)) {
|
|
418
|
+
throw new DidError(DidErrorCode.InternalError, 'A verification method intended for signing could not be determined from the DID Document');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return verificationMethod;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Instantiates a {@link BearerDid} object for the DID Key method from a given {@link PortableDid}.
|
|
426
|
+
*
|
|
427
|
+
* This method allows for the creation of a `BearerDid` object using a previously created DID's
|
|
428
|
+
* key material, DID document, and metadata.
|
|
429
|
+
*
|
|
430
|
+
* @remarks
|
|
431
|
+
* The `verificationMethod` array of the DID document must contain exactly one key since the
|
|
432
|
+
* `did:key` method only supports a single verification method.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```ts
|
|
436
|
+
* // Export an existing BearerDid to PortableDid format.
|
|
437
|
+
* const portableDid = await did.export();
|
|
438
|
+
* // Reconstruct a BearerDid object from the PortableDid.
|
|
439
|
+
* const did = await DidKey.import({ portableDid });
|
|
440
|
+
* ```
|
|
441
|
+
*
|
|
442
|
+
* @param params - The parameters for the import operation.
|
|
443
|
+
* @param params.portableDid - The PortableDid object to import.
|
|
444
|
+
* @param params.keyManager - Optionally specify an external Key Management System (KMS) used to
|
|
445
|
+
* generate keys and sign data. If not given, a new
|
|
446
|
+
* {@link LocalKeyManager} instance will be created and
|
|
447
|
+
* used.
|
|
448
|
+
* @returns A Promise resolving to a `BearerDid` object representing the DID formed from the provided keys.
|
|
449
|
+
* @throws An error if the DID document does not contain exactly one verification method.
|
|
450
|
+
*/
|
|
451
|
+
public static async import({ portableDid, keyManager = new LocalKeyManager() }: {
|
|
452
|
+
keyManager?: CryptoApi & KeyImporterExporter<KmsImportKeyParams, KeyIdentifier, KmsExportKeyParams>;
|
|
453
|
+
portableDid: PortableDid;
|
|
454
|
+
}): Promise<BearerDid> {
|
|
455
|
+
// Verify the DID method is supported.
|
|
456
|
+
const parsedDid = Did.parse(portableDid.uri);
|
|
457
|
+
if (parsedDid?.method !== DidKey.methodName) {
|
|
458
|
+
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Use the given PortableDid to construct the BearerDid object.
|
|
462
|
+
const did = await BearerDid.import({ portableDid, keyManager });
|
|
463
|
+
|
|
464
|
+
// Validate that the given DID document contains exactly one verification method.
|
|
465
|
+
// Note: The non-undefined assertion is necessary because the type system cannot infer that
|
|
466
|
+
// the `verificationMethod` property is defined -- which is checked by `BearerDid.import()`.
|
|
467
|
+
if (did.document.verificationMethod!.length !== 1) {
|
|
468
|
+
throw new DidError(DidErrorCode.InvalidDidDocument, `DID document must contain exactly one verification method`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return did;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Resolves a `did:key` identifier to a DID Document.
|
|
476
|
+
*
|
|
477
|
+
* @param didUri - The DID to be resolved.
|
|
478
|
+
* @param options - Optional parameters for resolving the DID.
|
|
479
|
+
* @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of the resolution.
|
|
480
|
+
*/
|
|
481
|
+
public static async resolve(didUri: string, options?: DidResolutionOptions): Promise<DidResolutionResult> {
|
|
482
|
+
try {
|
|
483
|
+
// Attempt to expand the DID URI string to a DID document.
|
|
484
|
+
const didDocument = await DidKey.createDocument({ didUri, options });
|
|
485
|
+
|
|
486
|
+
// If the DID document was created successfully, return it.
|
|
487
|
+
return {
|
|
488
|
+
...EMPTY_DID_RESOLUTION_RESULT,
|
|
489
|
+
didDocument,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
} catch (error: any) {
|
|
493
|
+
// Rethrow any unexpected errors that are not a `DidError`.
|
|
494
|
+
if (!(error instanceof DidError)) throw new Error(error);
|
|
495
|
+
|
|
496
|
+
// Return a DID Resolution Result with the appropriate error code.
|
|
497
|
+
return {
|
|
498
|
+
...EMPTY_DID_RESOLUTION_RESULT,
|
|
499
|
+
didResolutionMetadata: {
|
|
500
|
+
error: error.code,
|
|
501
|
+
...error.message && { errorMessage: error.message }
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Expands a did:key identifier to a DID Document.
|
|
509
|
+
*
|
|
510
|
+
* Reference: https://w3c-ccg.github.io/did-method-key/#document-creation-algorithm
|
|
511
|
+
*
|
|
512
|
+
* @param options
|
|
513
|
+
* @returns - A DID dodcument.
|
|
514
|
+
*/
|
|
515
|
+
private static async createDocument({ didUri, options = {}}: {
|
|
516
|
+
didUri: string;
|
|
517
|
+
options?: Exclude<DidKeyCreateOptions<CryptoApi>, 'algorithm' | 'verificationMethods'> | DidResolutionOptions;
|
|
518
|
+
}): Promise<DidDocument> {
|
|
519
|
+
const {
|
|
520
|
+
defaultContext = 'https://www.w3.org/ns/did/v1',
|
|
521
|
+
enableEncryptionKeyDerivation = false,
|
|
522
|
+
enableExperimentalPublicKeyTypes = false,
|
|
523
|
+
publicKeyFormat = 'JsonWebKey2020'
|
|
524
|
+
} = options;
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* 1. Initialize document to an empty object.
|
|
528
|
+
*/
|
|
529
|
+
const didDocument: DidDocument = { id: '' };
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* 2. Using a colon (:) as the delimiter, split the identifier into its
|
|
533
|
+
* components: a scheme, a method, a version, and a multibaseValue.
|
|
534
|
+
* If there are only three components set the version to the string
|
|
535
|
+
* value 1 and use the last value as the multibaseValue.
|
|
536
|
+
*/
|
|
537
|
+
const parsedDid = Did.parse(didUri);
|
|
538
|
+
if (!parsedDid) {
|
|
539
|
+
throw new DidError(DidErrorCode.InvalidDid, `Invalid DID URI: ${didUri}`);
|
|
540
|
+
}
|
|
541
|
+
const multibaseValue = parsedDid.id;
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* 3. Check the validity of the input identifier.
|
|
545
|
+
* The scheme MUST be the value did. The method MUST be the value key.
|
|
546
|
+
* The version MUST be convertible to a positive integer value. The
|
|
547
|
+
* multibaseValue MUST be a string and begin with the letter z. If any
|
|
548
|
+
* of these requirements fail, an invalidDid error MUST be raised.
|
|
549
|
+
*/
|
|
550
|
+
if (parsedDid.method !== DidKey.methodName) {
|
|
551
|
+
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
|
|
552
|
+
}
|
|
553
|
+
if (!DidKey.validateIdentifier(parsedDid)) {
|
|
554
|
+
throw new DidError(DidErrorCode.InvalidDid, `Invalid DID URI: ${didUri}`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* 4. Initialize the signatureVerificationMethod to the result of passing
|
|
559
|
+
* identifier, multibaseValue, and options to a
|
|
560
|
+
* {@link https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm | Signature Method Creation Algorithm}.
|
|
561
|
+
*/
|
|
562
|
+
const signatureVerificationMethod = await DidKey.createSignatureMethod({
|
|
563
|
+
didUri,
|
|
564
|
+
multibaseValue,
|
|
565
|
+
options: { enableExperimentalPublicKeyTypes, publicKeyFormat }
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* 5. Set document.id to identifier. If document.id is not a valid DID,
|
|
570
|
+
* an invalidDid error MUST be raised.
|
|
571
|
+
*
|
|
572
|
+
* Note: Identifier was already confirmed to be valid in Step 3, so
|
|
573
|
+
* skipping the redundant validation.
|
|
574
|
+
*/
|
|
575
|
+
didDocument.id = parsedDid.uri;
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* 6. Initialize the verificationMethod property in document to an array
|
|
579
|
+
* where the first value is the signatureVerificationMethod.
|
|
580
|
+
*/
|
|
581
|
+
didDocument.verificationMethod = [signatureVerificationMethod];
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* 7. Initialize the authentication, assertionMethod, capabilityInvocation,
|
|
585
|
+
* and the capabilityDelegation properties in document to an array where
|
|
586
|
+
* the first item is the value of the id property in
|
|
587
|
+
* signatureVerificationMethod.
|
|
588
|
+
*/
|
|
589
|
+
didDocument.authentication = [signatureVerificationMethod.id];
|
|
590
|
+
didDocument.assertionMethod = [signatureVerificationMethod.id];
|
|
591
|
+
didDocument.capabilityInvocation = [signatureVerificationMethod.id];
|
|
592
|
+
didDocument.capabilityDelegation = [signatureVerificationMethod.id];
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* 8. If options.enableEncryptionKeyDerivation is set to true:
|
|
596
|
+
* Add the encryptionVerificationMethod value to the verificationMethod
|
|
597
|
+
* array. Initialize the keyAgreement property in document to an array
|
|
598
|
+
* where the first item is the value of the id property in
|
|
599
|
+
* encryptionVerificationMethod.
|
|
600
|
+
*/
|
|
601
|
+
if (enableEncryptionKeyDerivation === true) {
|
|
602
|
+
/**
|
|
603
|
+
* Although not covered by the did:key method specification, a sensible
|
|
604
|
+
* default will be taken to use the 'X25519KeyAgreementKey2020'
|
|
605
|
+
* verification method type if the given publicKeyFormat is
|
|
606
|
+
* 'Ed25519VerificationKey2020' and 'JsonWebKey2020' otherwise.
|
|
607
|
+
*/
|
|
608
|
+
const encryptionPublicKeyFormat =
|
|
609
|
+
(publicKeyFormat === 'Ed25519VerificationKey2020')
|
|
610
|
+
? 'X25519KeyAgreementKey2020'
|
|
611
|
+
: 'JsonWebKey2020';
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* 8.1 Initialize the encryptionVerificationMethod to the result of
|
|
615
|
+
* passing identifier, multibaseValue, and options to an
|
|
616
|
+
* {@link https://w3c-ccg.github.io/did-method-key/#encryption-method-creation-algorithm | Encryption Method Creation Algorithm}.
|
|
617
|
+
*/
|
|
618
|
+
const encryptionVerificationMethod = await this.createEncryptionMethod({
|
|
619
|
+
didUri,
|
|
620
|
+
multibaseValue,
|
|
621
|
+
options: { enableExperimentalPublicKeyTypes, publicKeyFormat: encryptionPublicKeyFormat }
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* 8.2 Add the encryptionVerificationMethod value to the
|
|
626
|
+
* verificationMethod array.
|
|
627
|
+
*/
|
|
628
|
+
didDocument.verificationMethod.push(encryptionVerificationMethod);
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* 8.3. Initialize the keyAgreement property in document to an array
|
|
632
|
+
* where the first item is the value of the id property in
|
|
633
|
+
* encryptionVerificationMethod.
|
|
634
|
+
*/
|
|
635
|
+
didDocument.keyAgreement = [encryptionVerificationMethod.id];
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* 9. Initialize the @context property in document to the result of passing document and options to the Context
|
|
640
|
+
* Creation algorithm.
|
|
641
|
+
*/
|
|
642
|
+
// Set contextArray to an array that is initialized to options.defaultContext.
|
|
643
|
+
const contextArray = [ defaultContext ];
|
|
644
|
+
|
|
645
|
+
// For every object in every verification relationship listed in document,
|
|
646
|
+
// add a string value to the contextArray based on the object type value,
|
|
647
|
+
// if it doesn't already exist, according to the following table:
|
|
648
|
+
// {@link https://w3c-ccg.github.io/did-method-key/#context-creation-algorithm | Context Type URL}
|
|
649
|
+
const verificationMethodTypes = getVerificationMethodTypes({ didDocument });
|
|
650
|
+
verificationMethodTypes.forEach((typeName: string) => {
|
|
651
|
+
const typeUrl = DidKeyVerificationMethodType[typeName as keyof typeof DidKeyVerificationMethodType];
|
|
652
|
+
contextArray.push(typeUrl);
|
|
653
|
+
});
|
|
654
|
+
didDocument['@context'] = contextArray;
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* 10. Return document.
|
|
658
|
+
*/
|
|
659
|
+
return didDocument;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Decoding a multibase-encoded multicodec value into a verification method
|
|
664
|
+
* that is suitable for verifying that encrypted information will be
|
|
665
|
+
* received by the intended recipient.
|
|
666
|
+
*/
|
|
667
|
+
private static async createEncryptionMethod({ didUri, multibaseValue, options }: {
|
|
668
|
+
didUri: string;
|
|
669
|
+
multibaseValue: string;
|
|
670
|
+
options: Required<Pick<DidKeyCreateOptions<CryptoApi>, 'enableExperimentalPublicKeyTypes' | 'publicKeyFormat'>>;
|
|
671
|
+
}): Promise<DidVerificationMethod> {
|
|
672
|
+
const { enableExperimentalPublicKeyTypes, publicKeyFormat } = options;
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* 1. Initialize verificationMethod to an empty object.
|
|
676
|
+
*/
|
|
677
|
+
const verificationMethod: DidVerificationMethod = { id: '', type: '', controller: '' };
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* 2. Set multicodecValue and raw publicKeyBytes to the result of passing multibaseValue and
|
|
681
|
+
* options to a Derive Encryption Key algorithm.
|
|
682
|
+
*/
|
|
683
|
+
const {
|
|
684
|
+
keyBytes: publicKeyBytes,
|
|
685
|
+
multicodecCode: multicodecValue,
|
|
686
|
+
} = await DidKey.deriveEncryptionKey({ multibaseValue });
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* 3. Ensure the proper key length of raw publicKeyBytes based on the multicodecValue table
|
|
690
|
+
* provided below:
|
|
691
|
+
*
|
|
692
|
+
* Multicodec hexadecimal value: 0xec
|
|
693
|
+
*
|
|
694
|
+
* If the byte length of raw publicKeyBytes does not match the expected public key length for
|
|
695
|
+
* the associated multicodecValue, an invalidPublicKeyLength error MUST be raised.
|
|
696
|
+
*/
|
|
697
|
+
const actualLength = publicKeyBytes.byteLength;
|
|
698
|
+
const expectedLength = DidKeyUtils.MULTICODEC_PUBLIC_KEY_LENGTH[multicodecValue];
|
|
699
|
+
if (actualLength !== expectedLength) {
|
|
700
|
+
throw new DidError(DidErrorCode.InvalidPublicKeyLength, `Expected ${actualLength} bytes. Actual: ${expectedLength}`);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* 4. Create the multibaseValue by concatenating the letter 'z' and the
|
|
705
|
+
* base58-btc encoding of the concatenation of the multicodecValue and
|
|
706
|
+
* the raw publicKeyBytes.
|
|
707
|
+
*/
|
|
708
|
+
const kemMultibaseValue = keyBytesToMultibaseId({
|
|
709
|
+
keyBytes : publicKeyBytes,
|
|
710
|
+
multicodecCode : multicodecValue
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* 5. Set the verificationMethod.id value by concatenating identifier,
|
|
715
|
+
* a hash character (#), and the multibaseValue. If verificationMethod.id
|
|
716
|
+
* is not a valid DID URL, an invalidDidUrl error MUST be raised.
|
|
717
|
+
*/
|
|
718
|
+
verificationMethod.id = `${didUri}#${kemMultibaseValue}`;
|
|
719
|
+
try {
|
|
720
|
+
new URL(verificationMethod.id);
|
|
721
|
+
} catch (error: any) {
|
|
722
|
+
throw new DidError(DidErrorCode.InvalidDidUrl, 'Verification Method ID is not a valid DID URL.');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* 6. Set the publicKeyFormat value to the options.publicKeyFormat value.
|
|
727
|
+
* 7. If publicKeyFormat is not known to the implementation, an
|
|
728
|
+
* unsupportedPublicKeyType error MUST be raised.
|
|
729
|
+
*/
|
|
730
|
+
if (!(publicKeyFormat in DidKeyVerificationMethodType)) {
|
|
731
|
+
throw new DidError(DidErrorCode.UnsupportedPublicKeyType, `Unsupported format: ${publicKeyFormat}`);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* 8. If options.enableExperimentalPublicKeyTypes is set to false and publicKeyFormat is not
|
|
736
|
+
* Multikey, JsonWebKey2020, or X25519KeyAgreementKey2020, an invalidPublicKeyType error MUST be
|
|
737
|
+
* raised.
|
|
738
|
+
*/
|
|
739
|
+
const StandardPublicKeyTypes = ['Multikey', 'JsonWebKey2020', 'X25519KeyAgreementKey2020'];
|
|
740
|
+
if (enableExperimentalPublicKeyTypes === false
|
|
741
|
+
&& !(StandardPublicKeyTypes.includes(publicKeyFormat))) {
|
|
742
|
+
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Specified '${publicKeyFormat}' without setting enableExperimentalPublicKeyTypes to true.`);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* 9. Set verificationMethod.type to the publicKeyFormat value.
|
|
747
|
+
*/
|
|
748
|
+
verificationMethod.type = publicKeyFormat;
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* 10. Set verificationMethod.controller to the identifier value.
|
|
752
|
+
*/
|
|
753
|
+
verificationMethod.controller = didUri;
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* 11. If publicKeyFormat is Multikey or X25519KeyAgreementKey2020, set the verificationMethod.publicKeyMultibase
|
|
757
|
+
* value to multibaseValue.
|
|
758
|
+
*
|
|
759
|
+
* Note: This implementation does not currently support the Multikey
|
|
760
|
+
* format.
|
|
761
|
+
*/
|
|
762
|
+
if (publicKeyFormat === 'X25519KeyAgreementKey2020') {
|
|
763
|
+
verificationMethod.publicKeyMultibase = kemMultibaseValue;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* 12. If publicKeyFormat is JsonWebKey2020, set the verificationMethod.publicKeyJwk value to
|
|
768
|
+
* the result of passing multicodecValue and rawPublicKeyBytes to a JWK encoding algorithm.
|
|
769
|
+
*/
|
|
770
|
+
if (publicKeyFormat === 'JsonWebKey2020') {
|
|
771
|
+
const { crv } = await DidKeyUtils.multicodecToJwk({ code: multicodecValue });
|
|
772
|
+
verificationMethod.publicKeyJwk = await DidKeyUtils.keyConverter(crv!).bytesToPublicKey({ publicKeyBytes });
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* 13. Return verificationMethod.
|
|
777
|
+
*/
|
|
778
|
+
return verificationMethod;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Decodes a multibase-encoded multicodec value into a verification method
|
|
783
|
+
* that is suitable for verifying digital signatures.
|
|
784
|
+
* @param options - Signature method creation algorithm inputs.
|
|
785
|
+
* @returns - A verification method.
|
|
786
|
+
*/
|
|
787
|
+
private static async createSignatureMethod({ didUri, multibaseValue, options }: {
|
|
788
|
+
didUri: string;
|
|
789
|
+
multibaseValue: string;
|
|
790
|
+
options: Required<Pick<DidKeyCreateOptions<CryptoApi>, 'enableExperimentalPublicKeyTypes' | 'publicKeyFormat'>>
|
|
791
|
+
}): Promise<DidVerificationMethod> {
|
|
792
|
+
const { enableExperimentalPublicKeyTypes, publicKeyFormat } = options;
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* 1. Initialize verificationMethod to an empty object.
|
|
796
|
+
*/
|
|
797
|
+
const verificationMethod: DidVerificationMethod = { id: '', type: '', controller: '' };
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* 2. Set multicodecValue and publicKeyBytes to the result of passing
|
|
801
|
+
* multibaseValue and options to a Decode Public Key algorithm.
|
|
802
|
+
*/
|
|
803
|
+
const {
|
|
804
|
+
keyBytes: publicKeyBytes,
|
|
805
|
+
multicodecCode: multicodecValue,
|
|
806
|
+
multicodecName
|
|
807
|
+
} = multibaseIdToKeyBytes({ multibaseKeyId: multibaseValue });
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* 3. Ensure the proper key length of publicKeyBytes based on the multicodecValue
|
|
811
|
+
* {@link https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm | table provided}.
|
|
812
|
+
* If the byte length of rawPublicKeyBytes does not match the expected public key length for the
|
|
813
|
+
* associated multicodecValue, an invalidPublicKeyLength error MUST be raised.
|
|
814
|
+
*/
|
|
815
|
+
const actualLength = publicKeyBytes.byteLength;
|
|
816
|
+
const expectedLength = DidKeyUtils.MULTICODEC_PUBLIC_KEY_LENGTH[multicodecValue];
|
|
817
|
+
if (actualLength !== expectedLength) {
|
|
818
|
+
throw new DidError(DidErrorCode.InvalidPublicKeyLength, `Expected ${actualLength} bytes. Actual: ${expectedLength}`);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* 4. Ensure the publicKeyBytes are a proper encoding of the public key type as specified by
|
|
823
|
+
* the multicodecValue. If an invalid public key value is detected, an invalidPublicKey error
|
|
824
|
+
* MUST be raised.
|
|
825
|
+
*/
|
|
826
|
+
let isValid = false;
|
|
827
|
+
switch (multicodecName) {
|
|
828
|
+
case 'secp256k1-pub':
|
|
829
|
+
isValid = await Secp256k1.validatePublicKey({ publicKeyBytes });
|
|
830
|
+
break;
|
|
831
|
+
case 'ed25519-pub':
|
|
832
|
+
isValid = await Ed25519.validatePublicKey({ publicKeyBytes });
|
|
833
|
+
break;
|
|
834
|
+
case 'x25519-pub':
|
|
835
|
+
// TODO: Validate key once/if X25519.validatePublicKey() is implemented.
|
|
836
|
+
// isValid = X25519.validatePublicKey({ key: rawPublicKeyBytes})
|
|
837
|
+
isValid = true;
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
if (!isValid) {
|
|
841
|
+
throw new DidError(DidErrorCode.InvalidPublicKey, 'Invalid public key detected.');
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* 5. Set the verificationMethod.id value by concatenating identifier, a hash character (#), and
|
|
846
|
+
* the multibaseValue. If verificationMethod.id is not a valid DID URL, an invalidDidUrl error
|
|
847
|
+
* MUST be raised.
|
|
848
|
+
*/
|
|
849
|
+
verificationMethod.id = `${didUri}#${multibaseValue}`;
|
|
850
|
+
try {
|
|
851
|
+
new URL(verificationMethod.id);
|
|
852
|
+
} catch (error: any) {
|
|
853
|
+
throw new DidError(DidErrorCode.InvalidDidUrl, 'Verification Method ID is not a valid DID URL.');
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* 6. Set the publicKeyFormat value to the options.publicKeyFormat value.
|
|
858
|
+
* 7. If publicKeyFormat is not known to the implementation, an unsupportedPublicKeyType error
|
|
859
|
+
* MUST be raised.
|
|
860
|
+
*/
|
|
861
|
+
if (!(publicKeyFormat in DidKeyVerificationMethodType)) {
|
|
862
|
+
throw new DidError(DidErrorCode.UnsupportedPublicKeyType, `Unsupported format: ${publicKeyFormat}`);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* 8. If options.enableExperimentalPublicKeyTypes is set to false and publicKeyFormat is not
|
|
867
|
+
* Multikey, JsonWebKey2020, or Ed25519VerificationKey2020, an invalidPublicKeyType error MUST
|
|
868
|
+
* be raised.
|
|
869
|
+
*/
|
|
870
|
+
const StandardPublicKeyTypes = ['Multikey', 'JsonWebKey2020', 'Ed25519VerificationKey2020'];
|
|
871
|
+
if (enableExperimentalPublicKeyTypes === false
|
|
872
|
+
&& !(StandardPublicKeyTypes.includes(publicKeyFormat))) {
|
|
873
|
+
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Specified '${publicKeyFormat}' without setting enableExperimentalPublicKeyTypes to true.`);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* 9. Set verificationMethod.type to the publicKeyFormat value.
|
|
878
|
+
*/
|
|
879
|
+
verificationMethod.type = publicKeyFormat;
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* 10. Set verificationMethod.controller to the identifier value.
|
|
883
|
+
*/
|
|
884
|
+
verificationMethod.controller = didUri;
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* 11. If publicKeyFormat is Multikey or Ed25519VerificationKey2020,
|
|
888
|
+
* set the verificationMethod.publicKeyMultibase value to multibaseValue.
|
|
889
|
+
*
|
|
890
|
+
* Note: This implementation does not currently support the Multikey
|
|
891
|
+
* format.
|
|
892
|
+
*/
|
|
893
|
+
if (publicKeyFormat === 'Ed25519VerificationKey2020') {
|
|
894
|
+
verificationMethod.publicKeyMultibase = multibaseValue;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* 12. If publicKeyFormat is JsonWebKey2020, set the verificationMethod.publicKeyJwk value to
|
|
899
|
+
* the result of passing multicodecValue and rawPublicKeyBytes to a JWK encoding algorithm.
|
|
900
|
+
*/
|
|
901
|
+
if (publicKeyFormat === 'JsonWebKey2020') {
|
|
902
|
+
const { crv } = await DidKeyUtils.multicodecToJwk({ code: multicodecValue });
|
|
903
|
+
verificationMethod.publicKeyJwk = await DidKeyUtils.keyConverter(crv!).bytesToPublicKey({ publicKeyBytes});
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* 13. Return verificationMethod.
|
|
908
|
+
*/
|
|
909
|
+
return verificationMethod;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Transform a multibase-encoded multicodec value to public encryption key
|
|
915
|
+
* components that are suitable for encrypting messages to a receiver. A
|
|
916
|
+
* mathematical proof elaborating on the safety of performing this operation
|
|
917
|
+
* is available in:
|
|
918
|
+
* {@link https://eprint.iacr.org/2021/509.pdf | On using the same key pair for Ed25519 and an X25519 based KEM}
|
|
919
|
+
*/
|
|
920
|
+
private static async deriveEncryptionKey({ multibaseValue }: {
|
|
921
|
+
multibaseValue: string
|
|
922
|
+
}): Promise<RequireOnly<KeyWithMulticodec, 'keyBytes' | 'multicodecCode'>> {
|
|
923
|
+
/**
|
|
924
|
+
* 1. Set publicEncryptionKey to an empty object.
|
|
925
|
+
*/
|
|
926
|
+
let publicEncryptionKey: RequireOnly<KeyWithMulticodec, 'keyBytes' | 'multicodecCode'> = {
|
|
927
|
+
keyBytes : new Uint8Array(),
|
|
928
|
+
multicodecCode : 0
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* 2. Decode multibaseValue using the base58-btc multibase alphabet and
|
|
933
|
+
* set multicodecValue to the multicodec header for the decoded value.
|
|
934
|
+
* Implementers are cautioned to ensure that the multicodecValue is set
|
|
935
|
+
* to the result after performing varint decoding.
|
|
936
|
+
*
|
|
937
|
+
* 3. Set the rawPublicKeyBytes to the bytes remaining after the multicodec
|
|
938
|
+
* header.
|
|
939
|
+
*/
|
|
940
|
+
const {
|
|
941
|
+
keyBytes: publicKeyBytes,
|
|
942
|
+
multicodecCode: multicodecValue
|
|
943
|
+
} = multibaseIdToKeyBytes({ multibaseKeyId: multibaseValue });
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* 4. If the multicodecValue is 0xed (Ed25519 public key), derive a public X25519 encryption key
|
|
947
|
+
* by using the raw publicKeyBytes and the algorithm defined in
|
|
948
|
+
* {@link https://datatracker.ietf.org/doc/html/draft-ietf-core-oscore-groupcomm | Group OSCORE - Secure Group Communication for CoAP}
|
|
949
|
+
* for Curve25519 in Section 2.4.2: ECDH with Montgomery Coordinates and set
|
|
950
|
+
* generatedPublicEncryptionKeyBytes to the result.
|
|
951
|
+
*/
|
|
952
|
+
if (multicodecValue === 0xed) {
|
|
953
|
+
const ed25519PublicKey = await DidKeyUtils.keyConverter('Ed25519').bytesToPublicKey({
|
|
954
|
+
publicKeyBytes
|
|
955
|
+
});
|
|
956
|
+
const generatedPublicEncryptionKey = await Ed25519.convertPublicKeyToX25519({
|
|
957
|
+
publicKey: ed25519PublicKey
|
|
958
|
+
});
|
|
959
|
+
const generatedPublicEncryptionKeyBytes = await DidKeyUtils.keyConverter('Ed25519').publicKeyToBytes({
|
|
960
|
+
publicKey: generatedPublicEncryptionKey
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* 5. Set multicodecValue to 0xec.
|
|
965
|
+
* 6. Set raw public keyBytes to generatedPublicEncryptionKeyBytes.
|
|
966
|
+
*/
|
|
967
|
+
publicEncryptionKey = {
|
|
968
|
+
keyBytes : generatedPublicEncryptionKeyBytes,
|
|
969
|
+
multicodecCode : 0xec
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* 7. Return publicEncryptionKey.
|
|
975
|
+
*/
|
|
976
|
+
return publicEncryptionKey;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Validates the structure and components of a DID URI against the `did:key` method specification.
|
|
981
|
+
*
|
|
982
|
+
* @param parsedDid - An object representing the parsed components of a DID URI, including the
|
|
983
|
+
* scheme, method, and method-specific identifier.
|
|
984
|
+
* @returns `true` if the DID URI meets the `did:key` method's structural requirements, `false` otherwise.
|
|
985
|
+
*
|
|
986
|
+
*/
|
|
987
|
+
private static validateIdentifier(parsedDid: Did): boolean {
|
|
988
|
+
const { method, id: multibaseValue } = parsedDid;
|
|
989
|
+
const [ scheme ] = parsedDid.uri.split(':', 1);
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Note: The W3C DID specification makes no mention of a version value being part of the DID
|
|
993
|
+
* syntax. Additionally, there does not appear to be any real-world usage of the version
|
|
994
|
+
* number. Consequently, this implementation will ignore the version related guidance in
|
|
995
|
+
* the did:key specification.
|
|
996
|
+
*/
|
|
997
|
+
const version = '1';
|
|
998
|
+
|
|
999
|
+
return (
|
|
1000
|
+
scheme === 'did' &&
|
|
1001
|
+
method === 'key' &&
|
|
1002
|
+
Number(version) > 0 &&
|
|
1003
|
+
universalTypeOf(multibaseValue) === 'String' &&
|
|
1004
|
+
multibaseValue.startsWith('z')
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* The `DidKeyUtils` class provides utility functions to support operations in the DID Key method.
|
|
1011
|
+
*/
|
|
1012
|
+
export class DidKeyUtils {
|
|
1013
|
+
/**
|
|
1014
|
+
* A mapping from JSON Web Key (JWK) property descriptors to multicodec names.
|
|
1015
|
+
*
|
|
1016
|
+
* This mapping is used to convert keys in JWK (JSON Web Key) format to multicodec format.
|
|
1017
|
+
*
|
|
1018
|
+
* @remarks
|
|
1019
|
+
* The keys of this object are strings that describe the JOSE key type and usage,
|
|
1020
|
+
* such as 'Ed25519:public', 'Ed25519:private', etc. The values are the corresponding multicodec
|
|
1021
|
+
* names used to represent these key types.
|
|
1022
|
+
*
|
|
1023
|
+
* @example
|
|
1024
|
+
* ```ts
|
|
1025
|
+
* const multicodecName = JWK_TO_MULTICODEC['Ed25519:public'];
|
|
1026
|
+
* // Returns 'ed25519-pub', the multicodec name for an Ed25519 public key
|
|
1027
|
+
* ```
|
|
1028
|
+
*/
|
|
1029
|
+
private static JWK_TO_MULTICODEC: { [key: string]: string } = {
|
|
1030
|
+
'Ed25519:public' : 'ed25519-pub',
|
|
1031
|
+
'Ed25519:private' : 'ed25519-priv',
|
|
1032
|
+
'secp256k1:public' : 'secp256k1-pub',
|
|
1033
|
+
'secp256k1:private' : 'secp256k1-priv',
|
|
1034
|
+
'X25519:public' : 'x25519-pub',
|
|
1035
|
+
'X25519:private' : 'x25519-priv',
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Defines the expected byte lengths for public keys associated with different cryptographic
|
|
1040
|
+
* algorithms, indexed by their multicodec code values.
|
|
1041
|
+
*/
|
|
1042
|
+
public static MULTICODEC_PUBLIC_KEY_LENGTH: Record<number, number> = {
|
|
1043
|
+
// secp256k1-pub - Secp256k1 public key (compressed) - 33 bytes
|
|
1044
|
+
0xe7: 33,
|
|
1045
|
+
|
|
1046
|
+
// x25519-pub - Curve25519 public key - 32 bytes
|
|
1047
|
+
0xec: 32,
|
|
1048
|
+
|
|
1049
|
+
// ed25519-pub - Ed25519 public key - 32 bytes
|
|
1050
|
+
0xed: 32
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* A mapping from multicodec names to their corresponding JOSE (JSON Object Signing and Encryption)
|
|
1055
|
+
* representations. This mapping facilitates the conversion of multicodec key formats to
|
|
1056
|
+
* JWK (JSON Web Key) formats.
|
|
1057
|
+
*
|
|
1058
|
+
* @remarks
|
|
1059
|
+
* The keys of this object are multicodec names, such as 'ed25519-pub', 'ed25519-priv', etc.
|
|
1060
|
+
* The values are objects representing the corresponding JWK properties for that key type.
|
|
1061
|
+
*
|
|
1062
|
+
* @example
|
|
1063
|
+
* ```ts
|
|
1064
|
+
* const joseKey = MULTICODEC_TO_JWK['ed25519-pub'];
|
|
1065
|
+
* // Returns a partial JWK for an Ed25519 public key
|
|
1066
|
+
* ```
|
|
1067
|
+
*/
|
|
1068
|
+
private static MULTICODEC_TO_JWK: { [key: string]: Jwk } = {
|
|
1069
|
+
'ed25519-pub' : { crv: 'Ed25519', kty: 'OKP', x: '' },
|
|
1070
|
+
'ed25519-priv' : { crv: 'Ed25519', kty: 'OKP', x: '', d: '' },
|
|
1071
|
+
'secp256k1-pub' : { crv: 'secp256k1', kty: 'EC', x: '', y: ''},
|
|
1072
|
+
'secp256k1-priv' : { crv: 'secp256k1', kty: 'EC', x: '', y: '', d: '' },
|
|
1073
|
+
'x25519-pub' : { crv: 'X25519', kty: 'OKP', x: '' },
|
|
1074
|
+
'x25519-priv' : { crv: 'X25519', kty: 'OKP', x: '', d: '' },
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Converts a JWK (JSON Web Key) to a Multicodec code and name.
|
|
1079
|
+
*
|
|
1080
|
+
* @example
|
|
1081
|
+
* ```ts
|
|
1082
|
+
* const jwk: Jwk = { crv: 'Ed25519', kty: 'OKP', x: '...' };
|
|
1083
|
+
* const { code, name } = await DidKeyUtils.jwkToMulticodec({ jwk });
|
|
1084
|
+
* ```
|
|
1085
|
+
*
|
|
1086
|
+
* @param params - The parameters for the conversion.
|
|
1087
|
+
* @param params.jwk - The JSON Web Key to be converted.
|
|
1088
|
+
* @returns A promise that resolves to a Multicodec definition.
|
|
1089
|
+
*/
|
|
1090
|
+
public static async jwkToMulticodec({ jwk }: {
|
|
1091
|
+
jwk: Jwk
|
|
1092
|
+
}): Promise<MulticodecDefinition<MulticodecCode>> {
|
|
1093
|
+
const params: string[] = [];
|
|
1094
|
+
|
|
1095
|
+
if (jwk.crv) {
|
|
1096
|
+
params.push(jwk.crv);
|
|
1097
|
+
if (jwk.d) {
|
|
1098
|
+
params.push('private');
|
|
1099
|
+
} else {
|
|
1100
|
+
params.push('public');
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const lookupKey = params.join(':');
|
|
1105
|
+
const name = DidKeyUtils.JWK_TO_MULTICODEC[lookupKey];
|
|
1106
|
+
|
|
1107
|
+
if (name === undefined) {
|
|
1108
|
+
throw new Error(`Unsupported JWK to Multicodec conversion: '${lookupKey}'`);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const code = Multicodec.getCodeFromName({ name });
|
|
1112
|
+
|
|
1113
|
+
return { code, name };
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Returns the appropriate public key compressor for the specified cryptographic curve.
|
|
1118
|
+
*
|
|
1119
|
+
* @param curve - The cryptographic curve to use for the key conversion.
|
|
1120
|
+
* @returns A public key compressor for the specified curve.
|
|
1121
|
+
*/
|
|
1122
|
+
public static keyCompressor(
|
|
1123
|
+
curve: string
|
|
1124
|
+
): KeyCompressor['compressPublicKey'] {
|
|
1125
|
+
// ): ({ publicKeyBytes }: { publicKeyBytes: Uint8Array }) => Promise<Uint8Array> {
|
|
1126
|
+
const compressors = {
|
|
1127
|
+
'P-256' : Secp256r1.compressPublicKey,
|
|
1128
|
+
'secp256k1' : Secp256k1.compressPublicKey
|
|
1129
|
+
} as Record<string, KeyCompressor['compressPublicKey']>;
|
|
1130
|
+
|
|
1131
|
+
const compressor = compressors[curve];
|
|
1132
|
+
|
|
1133
|
+
if (!compressor) throw new DidError(DidErrorCode.InvalidPublicKeyType, `Unsupported curve: ${curve}`);
|
|
1134
|
+
|
|
1135
|
+
return compressor;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Returns the appropriate key converter for the specified cryptographic curve.
|
|
1140
|
+
*
|
|
1141
|
+
* @param curve - The cryptographic curve to use for the key conversion.
|
|
1142
|
+
* @returns An `AsymmetricKeyConverter` for the specified curve.
|
|
1143
|
+
*/
|
|
1144
|
+
public static keyConverter(curve: string): AsymmetricKeyConverter {
|
|
1145
|
+
const converters: Record<string, AsymmetricKeyConverter> = {
|
|
1146
|
+
'Ed25519' : Ed25519,
|
|
1147
|
+
'P-256' : Secp256r1,
|
|
1148
|
+
'secp256k1' : Secp256k1,
|
|
1149
|
+
'X25519' : X25519
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
const converter = converters[curve];
|
|
1153
|
+
|
|
1154
|
+
if (!converter) throw new DidError(DidErrorCode.InvalidPublicKeyType, `Unsupported curve: ${curve}`);
|
|
1155
|
+
|
|
1156
|
+
return converter;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Converts a Multicodec code or name to parial JWK (JSON Web Key).
|
|
1161
|
+
*
|
|
1162
|
+
* @example
|
|
1163
|
+
* ```ts
|
|
1164
|
+
* const partialJwk = await DidKeyUtils.multicodecToJwk({ name: 'ed25519-pub' });
|
|
1165
|
+
* ```
|
|
1166
|
+
*
|
|
1167
|
+
* @param params - The parameters for the conversion.
|
|
1168
|
+
* @param params.code - Optional Multicodec code to convert.
|
|
1169
|
+
* @param params.name - Optional Multicodec name to convert.
|
|
1170
|
+
* @returns A promise that resolves to a JOSE format key.
|
|
1171
|
+
*/
|
|
1172
|
+
public static async multicodecToJwk({ code, name }: {
|
|
1173
|
+
code?: MulticodecCode,
|
|
1174
|
+
name?: string
|
|
1175
|
+
}): Promise<Jwk> {
|
|
1176
|
+
// Either code or name must be specified, but not both.
|
|
1177
|
+
if (!(name ? !code : code)) {
|
|
1178
|
+
throw new Error(`Either 'name' or 'code' must be defined, but not both.`);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// If name is undefined, lookup by code.
|
|
1182
|
+
name = (name === undefined ) ? Multicodec.getNameFromCode({ code: code! }) : name;
|
|
1183
|
+
|
|
1184
|
+
const lookupKey = name;
|
|
1185
|
+
const jose = DidKeyUtils.MULTICODEC_TO_JWK[lookupKey];
|
|
1186
|
+
|
|
1187
|
+
if (jose === undefined) {
|
|
1188
|
+
throw new Error(`Unsupported Multicodec to JWK conversion`);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
return { ...jose };
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Converts a public key in JWK (JSON Web Key) format to a multibase identifier.
|
|
1196
|
+
*
|
|
1197
|
+
* @remarks
|
|
1198
|
+
* Note: All secp public keys are converted to compressed point encoding
|
|
1199
|
+
* before the multibase identifier is computed.
|
|
1200
|
+
*
|
|
1201
|
+
* Per {@link https://github.com/multiformats/multicodec/blob/master/table.csv | Multicodec table}:
|
|
1202
|
+
* Public keys for Elliptic Curve cryptography algorithms (e.g., secp256k1,
|
|
1203
|
+
* secp256k1r1, secp384r1, etc.) are always represented with compressed point
|
|
1204
|
+
* encoding (e.g., secp256k1-pub, p256-pub, p384-pub, etc.).
|
|
1205
|
+
*
|
|
1206
|
+
* Per {@link https://datatracker.ietf.org/doc/html/rfc8812#name-jose-and-cose-secp256k1-cur | RFC 8812}:
|
|
1207
|
+
* "As a compressed point encoding representation is not defined for JWK
|
|
1208
|
+
* elliptic curve points, the uncompressed point encoding defined there
|
|
1209
|
+
* MUST be used. The x and y values represented MUST both be exactly
|
|
1210
|
+
* 256 bits, with any leading zeros preserved."
|
|
1211
|
+
*
|
|
1212
|
+
* @example
|
|
1213
|
+
* ```ts
|
|
1214
|
+
* const publicKey = { crv: 'Ed25519', kty: 'OKP', x: '...' };
|
|
1215
|
+
* const multibaseId = await DidKeyUtils.publicKeyToMultibaseId({ publicKey });
|
|
1216
|
+
* ```
|
|
1217
|
+
*
|
|
1218
|
+
* @param params - The parameters for the conversion.
|
|
1219
|
+
* @param params.publicKey - The public key in JWK format.
|
|
1220
|
+
* @returns A promise that resolves to the multibase identifier.
|
|
1221
|
+
*/
|
|
1222
|
+
public static async publicKeyToMultibaseId({ publicKey }: {
|
|
1223
|
+
publicKey: Jwk
|
|
1224
|
+
}): Promise<string> {
|
|
1225
|
+
if (!(publicKey?.crv && publicKey.crv in AlgorithmToKeyTypeMap)) {
|
|
1226
|
+
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Public key contains an unsupported key type: ${publicKey?.crv ?? 'undefined'}`);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Convert the public key from JWK format to a byte array.
|
|
1230
|
+
let publicKeyBytes = await DidKeyUtils.keyConverter(publicKey.crv).publicKeyToBytes({ publicKey });
|
|
1231
|
+
|
|
1232
|
+
// Compress the public key if it is an elliptic curve key.
|
|
1233
|
+
if (/^(secp256k1|P-256|P-384|P-521)$/.test(publicKey.crv)) {
|
|
1234
|
+
publicKeyBytes = await DidKeyUtils.keyCompressor(publicKey.crv)({ publicKeyBytes });
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Convert the JSON Web Key (JWK) parameters to a Multicodec name.
|
|
1238
|
+
const { name: multicodecName } = await DidKeyUtils.jwkToMulticodec({ jwk: publicKey });
|
|
1239
|
+
|
|
1240
|
+
// Compute the multibase identifier based on the provided key.
|
|
1241
|
+
const multibaseId = keyBytesToMultibaseId({
|
|
1242
|
+
keyBytes: publicKeyBytes,
|
|
1243
|
+
multicodecName
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
return multibaseId;
|
|
1247
|
+
}
|
|
1248
|
+
}
|