@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,1637 @@
|
|
|
1
|
+
import type { Packet, StringAnswer, TxtAnswer, TxtData } from '@dnsquery/dns-packet';
|
|
2
|
+
import type {
|
|
3
|
+
Jwk,
|
|
4
|
+
Signer,
|
|
5
|
+
CryptoApi,
|
|
6
|
+
KeyIdentifier,
|
|
7
|
+
KmsExportKeyParams,
|
|
8
|
+
KmsImportKeyParams,
|
|
9
|
+
KeyImporterExporter,
|
|
10
|
+
AsymmetricKeyConverter,
|
|
11
|
+
} from '@enbox/crypto';
|
|
12
|
+
|
|
13
|
+
import bencode from 'bencode';
|
|
14
|
+
import { Convert } from '@enbox/common';
|
|
15
|
+
import { computeJwkThumbprint, Ed25519, LocalKeyManager, Secp256k1, Secp256r1, X25519 } from '@enbox/crypto';
|
|
16
|
+
import { AUTHORITATIVE_ANSWER, decode as dnsPacketDecode, encode as dnsPacketEncode } from '@dnsquery/dns-packet';
|
|
17
|
+
|
|
18
|
+
import type { DidMetadata, PortableDid } from '../types/portable-did.js';
|
|
19
|
+
import type { DidCreateOptions, DidCreateVerificationMethod, DidRegistrationResult } from './did-method.js';
|
|
20
|
+
import type {
|
|
21
|
+
DidService,
|
|
22
|
+
DidDocument,
|
|
23
|
+
DidResolutionResult,
|
|
24
|
+
DidResolutionOptions,
|
|
25
|
+
DidVerificationMethod,
|
|
26
|
+
} from '../types/did-core.js';
|
|
27
|
+
|
|
28
|
+
import { Did } from '../did.js';
|
|
29
|
+
import { DidMethod } from './did-method.js';
|
|
30
|
+
import { BearerDid } from '../bearer-did.js';
|
|
31
|
+
import { extractDidFragment } from '../utils.js';
|
|
32
|
+
import { DidError, DidErrorCode } from '../did-error.js';
|
|
33
|
+
import { DidVerificationRelationship } from '../types/did-core.js';
|
|
34
|
+
import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Represents a BEP44 message, which is used for storing and retrieving data in the Mainline DHT
|
|
38
|
+
* network.
|
|
39
|
+
*
|
|
40
|
+
* A BEP44 message is used primarily in the context of the DID DHT method for publishing and
|
|
41
|
+
* resolving DID documents in the DHT network. This type encapsulates the data structure required
|
|
42
|
+
* for such operations in accordance with BEP44.
|
|
43
|
+
*
|
|
44
|
+
* @see {@link https://www.bittorrent.org/beps/bep_0044.html | BEP44}
|
|
45
|
+
*/
|
|
46
|
+
export interface Bep44Message {
|
|
47
|
+
/**
|
|
48
|
+
* The public key bytes of the Identity Key, which serves as the identifier in the DHT network for
|
|
49
|
+
* the corresponding BEP44 message.
|
|
50
|
+
*/
|
|
51
|
+
k: Uint8Array;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The sequence number of the message, used to ensure the latest version of the data is retrieved
|
|
55
|
+
* and updated. It's a monotonically increasing number.
|
|
56
|
+
*/
|
|
57
|
+
seq: number;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The signature of the message, ensuring the authenticity and integrity of the data. It's
|
|
61
|
+
* computed over the bencoded sequence number and value.
|
|
62
|
+
*/
|
|
63
|
+
sig: Uint8Array;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The actual data being stored or retrieved from the DHT network, typically encoded in a format
|
|
67
|
+
* suitable for DNS packet representation of a DID Document.
|
|
68
|
+
*/
|
|
69
|
+
v: Uint8Array;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Options for creating a Decentralized Identifier (DID) using the DID DHT method.
|
|
74
|
+
*/
|
|
75
|
+
export interface DidDhtCreateOptions<TKms> extends DidCreateOptions<TKms> {
|
|
76
|
+
/**
|
|
77
|
+
* Optionally specify that the DID Subject is also identified by one or more other DIDs or URIs.
|
|
78
|
+
*
|
|
79
|
+
* A DID subject can have multiple identifiers for different purposes, or at different times.
|
|
80
|
+
* The assertion that two or more DIDs (or other types of URI) refer to the same DID subject can
|
|
81
|
+
* be made using the `alsoKnownAs` property.
|
|
82
|
+
*
|
|
83
|
+
* @see {@link https://www.w3.org/TR/did-core/#also-known-as | DID Core Specification, § Also Known As}
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const did = await DidDht.create({
|
|
88
|
+
* options: {
|
|
89
|
+
* alsoKnownAs: 'did:example:123'
|
|
90
|
+
* };
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
alsoKnownAs?: string[];
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Optionally specify which DID (or DIDs) is authorized to make changes to the DID document.
|
|
97
|
+
*
|
|
98
|
+
* A DID controller is an entity that is authorized to make changes to a DID document. Typically,
|
|
99
|
+
* only the DID Subject (i.e., the value of `id` property in the DID document) is authoritative.
|
|
100
|
+
* However, another DID (or DIDs) can be specified as the DID controller, and when doing so, any
|
|
101
|
+
* verification methods contained in the DID document for the other DID should be accepted as
|
|
102
|
+
* authoritative. In other words, proofs created by the controller DID should be considered
|
|
103
|
+
* equivalent to proofs created by the DID Subject.
|
|
104
|
+
*
|
|
105
|
+
* @see {@link https://www.w3.org/TR/did-core/#did-controller | DID Core Specification, § DID Controller}
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const did = await DidDht.create({
|
|
110
|
+
* options: {
|
|
111
|
+
* controller: 'did:example:123'
|
|
112
|
+
* };
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
controllers?: string | string[];
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Optional. The URI of a server involved in executing DID method operations. In the context of
|
|
119
|
+
* DID creation, the endpoint is expected to be a DID DHT Gateway or Pkarr relay. If not
|
|
120
|
+
* specified, a default gateway node is used.
|
|
121
|
+
*/
|
|
122
|
+
gatewayUri?: string;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Optional. Determines whether the created DID should be published to the DHT network.
|
|
126
|
+
*
|
|
127
|
+
* If set to `true` or omitted, the DID is publicly discoverable. If `false`, the DID is not
|
|
128
|
+
* published and cannot be resolved by others. By default, newly created DIDs are published.
|
|
129
|
+
*
|
|
130
|
+
* @see {@link https://did-dht.com | DID DHT Method Specification}
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* const did = await DidDht.create({
|
|
135
|
+
* options: {
|
|
136
|
+
* publish: false
|
|
137
|
+
* };
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
publish?: boolean;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Optional. An array of service endpoints associated with the DID.
|
|
144
|
+
*
|
|
145
|
+
* Services are used in DID documents to express ways of communicating with the DID subject or
|
|
146
|
+
* associated entities. A service can be any type of service the DID subject wants to advertise,
|
|
147
|
+
* including decentralized identity management services for further discovery, authentication,
|
|
148
|
+
* authorization, or interaction.
|
|
149
|
+
*
|
|
150
|
+
* @see {@link https://www.w3.org/TR/did-core/#services | DID Core Specification, § Services}
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* const did = await DidDht.create({
|
|
155
|
+
* options: {
|
|
156
|
+
* services: [
|
|
157
|
+
* {
|
|
158
|
+
* id: 'did:dht:i9xkp8ddcbcg8jwq54ox699wuzxyifsqx4jru45zodqu453ksz6y#dwn',
|
|
159
|
+
* type: 'DecentralizedWebNode',
|
|
160
|
+
* serviceEndpoint: ['https://example.com/dwn1', 'https://example/dwn2']
|
|
161
|
+
* }
|
|
162
|
+
* ]
|
|
163
|
+
* };
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
services?: DidService[];
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Optionally specify one or more registered DID DHT types to make the DID discovereable.
|
|
170
|
+
*
|
|
171
|
+
* Type indexing is an OPTIONAL feature that enables DIDs to become discoverable. DIDs that wish
|
|
172
|
+
* to be discoverable and resolveable by type can include one or more types when publishing their
|
|
173
|
+
* DID document to a DID DHT Gateway.
|
|
174
|
+
*
|
|
175
|
+
* The registered DID types are published in the {@link https://did-dht.com/registry/index.html#indexed-types | DID DHT Registry}.
|
|
176
|
+
*/
|
|
177
|
+
types?: (DidDhtRegisteredDidType | keyof typeof DidDhtRegisteredDidType)[];
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Optional. An array of verification methods to be included in the DID document.
|
|
181
|
+
*
|
|
182
|
+
* By default, a newly created DID DHT document will contain a single Ed25519 verification method,
|
|
183
|
+
* also known as the {@link https://did-dht.com/#term:identity-key | Identity Key}. Additional
|
|
184
|
+
* verification methods can be added to the DID document using the `verificationMethods` property.
|
|
185
|
+
*
|
|
186
|
+
* @see {@link https://www.w3.org/TR/did-core/#verification-methods | DID Core Specification, § Verification Methods}
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* const did = await DidDht.create({
|
|
191
|
+
* options: {
|
|
192
|
+
* verificationMethods: [
|
|
193
|
+
* {
|
|
194
|
+
* algorithm: 'Ed25519',
|
|
195
|
+
* purposes: ['authentication', 'assertionMethod']
|
|
196
|
+
* },
|
|
197
|
+
* {
|
|
198
|
+
* algorithm: 'Ed25519',
|
|
199
|
+
* id: 'dwn-sig',
|
|
200
|
+
* purposes: ['authentication', 'assertionMethod']
|
|
201
|
+
* }
|
|
202
|
+
* ]
|
|
203
|
+
* };
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
verificationMethods?: DidCreateVerificationMethod<TKms>[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Proof to used to construct the `_prv._did.` DNS record as described in https://did-dht.com/#rotation to link a DID to a previous DID.
|
|
211
|
+
*/
|
|
212
|
+
export type PreviousDidProof = {
|
|
213
|
+
/** The previous DID. */
|
|
214
|
+
previousDid: string;
|
|
215
|
+
|
|
216
|
+
/** The signature signed using the private Identity Key of the previous DID in Base64URL format. */
|
|
217
|
+
signature: string;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The default DID DHT Gateway or Pkarr Relay server to use when publishing and resolving DID
|
|
222
|
+
* documents.
|
|
223
|
+
*/
|
|
224
|
+
const DEFAULT_GATEWAY_URI = 'https://did-dht-production.up.railway.app';
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The version of the DID DHT specification that is implemented by this library.
|
|
228
|
+
*
|
|
229
|
+
* When a DID DHT document is published to the DHT network, the version of the specification that
|
|
230
|
+
* was used to create the document is included in the DNS TXT record for the root record. This
|
|
231
|
+
* allows clients to determine whether the DID DHT document is compatible with the client's
|
|
232
|
+
* implementation of the DID DHT specification. The version number is not present in the
|
|
233
|
+
* corresponding DID document.
|
|
234
|
+
*
|
|
235
|
+
* @see {@link https://did-dht.com | DID DHT Method Specification}
|
|
236
|
+
*/
|
|
237
|
+
const DID_DHT_SPECIFICATION_VERSION = 0;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The default TTL for DNS records published to the DHT network.
|
|
241
|
+
*
|
|
242
|
+
* The recommended TTL value is 7200 seconds (2 hours) since it matches the default TTL for
|
|
243
|
+
* Mainline DHT records.
|
|
244
|
+
*/
|
|
245
|
+
const DNS_RECORD_TTL = 7200;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Character used to separate distinct elements or entries in the DNS packet representation
|
|
249
|
+
* of a DID Document.
|
|
250
|
+
*
|
|
251
|
+
* For example, verification methods, verification relationships, and services are separated by
|
|
252
|
+
* semicolons (`;`) in the root record:
|
|
253
|
+
* ```
|
|
254
|
+
* vm=k1;auth=k1;asm=k2;inv=k3;del=k3;srv=s1
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
const PROPERTY_SEPARATOR = ';';
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Character used to separate distinct values within a single element or entry in the DNS packet
|
|
261
|
+
* representation of a DID Document.
|
|
262
|
+
*
|
|
263
|
+
* For example, multiple key references for the `authentication` verification relationships are
|
|
264
|
+
* separated by commas (`,`):
|
|
265
|
+
* ```
|
|
266
|
+
* auth=0,1,2
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
const VALUE_SEPARATOR = ',';
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Represents an optional extension to a DID Document’s DNS packet representation exposed as a
|
|
273
|
+
* type index.
|
|
274
|
+
*
|
|
275
|
+
* Type indexing is an OPTIONAL feature that enables DIDs to become discoverable. DIDs that wish to
|
|
276
|
+
* be discoverable and resolveable by type can include one or more types when publishing their DID
|
|
277
|
+
* document to a DID DHT Gateway.
|
|
278
|
+
*
|
|
279
|
+
* The registered DID types are published in the {@link https://did-dht.com/registry/index.html#indexed-types | DID DHT Registry}.
|
|
280
|
+
*/
|
|
281
|
+
export enum DidDhtRegisteredDidType {
|
|
282
|
+
/**
|
|
283
|
+
* Type 0 is reserved for DIDs that do not wish to associate themselves with a specific type but
|
|
284
|
+
* wish to make themselves discoverable.
|
|
285
|
+
*/
|
|
286
|
+
Discoverable = 0,
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Organization
|
|
290
|
+
* @see {@link https://schema.org/Organization | schema definition}
|
|
291
|
+
*/
|
|
292
|
+
Organization = 1,
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Government Organization
|
|
296
|
+
* @see {@link https://schema.org/GovernmentOrganization | schema definition}
|
|
297
|
+
*/
|
|
298
|
+
Government = 2,
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Corporation
|
|
302
|
+
* @see {@link https://schema.org/Corporation | schema definition}
|
|
303
|
+
*/
|
|
304
|
+
Corporation = 3,
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Corporation
|
|
308
|
+
* @see {@link https://schema.org/Corporation | schema definition}
|
|
309
|
+
*/
|
|
310
|
+
LocalBusiness = 4,
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Software Package
|
|
314
|
+
* @see {@link https://schema.org/SoftwareSourceCode | schema definition}
|
|
315
|
+
*/
|
|
316
|
+
SoftwarePackage = 5,
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Web App
|
|
320
|
+
* @see {@link https://schema.org/WebApplication | schema definition}
|
|
321
|
+
*/
|
|
322
|
+
WebApp = 6,
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Financial Institution
|
|
326
|
+
* @see {@link https://schema.org/FinancialService | schema definition}
|
|
327
|
+
*/
|
|
328
|
+
FinancialInstitution = 7
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Enumerates the types of keys that can be used in a DID DHT document.
|
|
333
|
+
*
|
|
334
|
+
* The DID DHT method supports various cryptographic key types. These key types are essential for
|
|
335
|
+
* the creation and management of DIDs and their associated cryptographic operations like signing
|
|
336
|
+
* and encryption. The registered key types are published in the DID DHT Registry and each is
|
|
337
|
+
* assigned a unique numerical value for use by client and gateway implementations.
|
|
338
|
+
*
|
|
339
|
+
* The registered key types are published in the {@link https://did-dht.com/registry/index.html#key-type-index | DID DHT Registry}.
|
|
340
|
+
*/
|
|
341
|
+
export enum DidDhtRegisteredKeyType {
|
|
342
|
+
/**
|
|
343
|
+
* Ed25519: A public-key signature system using the EdDSA (Edwards-curve Digital Signature
|
|
344
|
+
* Algorithm) and Curve25519.
|
|
345
|
+
*/
|
|
346
|
+
Ed25519 = 0,
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* secp256k1: A cryptographic curve used for digital signatures in a range of decentralized
|
|
350
|
+
* systems.
|
|
351
|
+
*/
|
|
352
|
+
secp256k1 = 1,
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* secp256r1: Also known as P-256 or prime256v1, this curve is used for cryptographic operations
|
|
356
|
+
* and is widely supported in various cryptographic libraries and standards.
|
|
357
|
+
*/
|
|
358
|
+
secp256r1 = 2,
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* X25519: A public key used for Diffie-Hellman key exchange using Curve25519.
|
|
362
|
+
*/
|
|
363
|
+
X25519 = 3,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Maps {@link https://www.w3.org/TR/did-core/#verification-relationships | DID Core Verification Relationship}
|
|
368
|
+
* values to the corresponding record name in the DNS packet representation of a DHT DID document.
|
|
369
|
+
*/
|
|
370
|
+
export enum DidDhtVerificationRelationship {
|
|
371
|
+
/**
|
|
372
|
+
* Specifies how the DID subject is expected to be authenticated.
|
|
373
|
+
*/
|
|
374
|
+
authentication = 'auth',
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Specifies how the DID subject is expected to express claims, such as for issuing Verifiable
|
|
378
|
+
* Credentials.
|
|
379
|
+
*/
|
|
380
|
+
assertionMethod = 'asm',
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Specifies a mechanism used by the DID subject to delegate a cryptographic capability to another
|
|
384
|
+
* party
|
|
385
|
+
*/
|
|
386
|
+
capabilityDelegation = 'del',
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Specifies a verification method used by the DID subject to invoke a cryptographic capability.
|
|
390
|
+
*/
|
|
391
|
+
capabilityInvocation = 'inv',
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Specifies how an entity can generate encryption material to communicate confidentially with the
|
|
395
|
+
* DID subject.
|
|
396
|
+
*/
|
|
397
|
+
keyAgreement = 'agm'
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Private helper that maps algorithm identifiers to their corresponding DID DHT
|
|
402
|
+
* {@link DidDhtRegisteredKeyType | registered key type}.
|
|
403
|
+
*/
|
|
404
|
+
const AlgorithmToKeyTypeMap = {
|
|
405
|
+
Ed25519 : DidDhtRegisteredKeyType.Ed25519,
|
|
406
|
+
ES256K : DidDhtRegisteredKeyType.secp256k1,
|
|
407
|
+
ES256 : DidDhtRegisteredKeyType.secp256r1,
|
|
408
|
+
'P-256' : DidDhtRegisteredKeyType.secp256r1,
|
|
409
|
+
secp256k1 : DidDhtRegisteredKeyType.secp256k1,
|
|
410
|
+
secp256r1 : DidDhtRegisteredKeyType.secp256r1,
|
|
411
|
+
X25519 : DidDhtRegisteredKeyType.X25519,
|
|
412
|
+
} as const;
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Private helper that maps did dht registered key types to their corresponding default algorithm identifiers.
|
|
416
|
+
*/
|
|
417
|
+
const KeyTypeToDefaultAlgorithmMap = {
|
|
418
|
+
[DidDhtRegisteredKeyType.Ed25519] : 'EdDSA',
|
|
419
|
+
[DidDhtRegisteredKeyType.secp256k1] : 'ES256K',
|
|
420
|
+
[DidDhtRegisteredKeyType.secp256r1] : 'ES256',
|
|
421
|
+
[DidDhtRegisteredKeyType.X25519] : 'ECDH-ES+A256KW',
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* The `DidDht` class provides an implementation of the `did:dht` DID method.
|
|
426
|
+
*
|
|
427
|
+
* Features:
|
|
428
|
+
* - DID Creation: Create new `did:dht` DIDs.
|
|
429
|
+
* - DID Key Management: Instantiate a DID object from an existing verification method keys or
|
|
430
|
+
* or a key in a Key Management System (KMS). If supported by the KMS, a DID's
|
|
431
|
+
* key can be exported to a portable DID format.
|
|
432
|
+
* - DID Resolution: Resolve a `did:dht` to its corresponding DID Document stored in the DHT network.
|
|
433
|
+
* - Signature Operations: Sign and verify messages using keys associated with a DID.
|
|
434
|
+
*
|
|
435
|
+
* @remarks
|
|
436
|
+
* The `did:dht` method leverages the distributed nature of the Mainline DHT network for
|
|
437
|
+
* decentralized identity management. This method allows DIDs to be resolved without relying on
|
|
438
|
+
* centralized registries or ledgers, enhancing privacy and control for users. The DID Document is
|
|
439
|
+
* stored and retrieved from the DHT network, and the method includes optional mechanisms for
|
|
440
|
+
* discovering DIDs by type.
|
|
441
|
+
*
|
|
442
|
+
* The DID URI in the `did:dht` method includes a method-specific identifier called the Identity Key
|
|
443
|
+
* which corresponds to the DID's entry in the DHT network. The Identity Key required to make
|
|
444
|
+
* changes to the DID Document since Mainline DHT nodes validate the signature of each message
|
|
445
|
+
* before storing the value in the DHT.
|
|
446
|
+
*
|
|
447
|
+
* @see {@link https://did-dht.com | DID DHT Method Specification}
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* ```ts
|
|
451
|
+
* // DID Creation
|
|
452
|
+
* const did = await DidDht.create();
|
|
453
|
+
*
|
|
454
|
+
* // DID Creation with a KMS
|
|
455
|
+
* const keyManager = new LocalKeyManager();
|
|
456
|
+
* const did = await DidDht.create({ keyManager });
|
|
457
|
+
*
|
|
458
|
+
* // DID Resolution
|
|
459
|
+
* const resolutionResult = await DidDht.resolve({ did: did.uri });
|
|
460
|
+
*
|
|
461
|
+
* // Signature Operations
|
|
462
|
+
* const signer = await did.getSigner();
|
|
463
|
+
* const signature = await signer.sign({ data: new TextEncoder().encode('Message') });
|
|
464
|
+
* const isValid = await signer.verify({ data: new TextEncoder().encode('Message'), signature });
|
|
465
|
+
*
|
|
466
|
+
* // Import / Export
|
|
467
|
+
*
|
|
468
|
+
* // Export a BearerDid object to the PortableDid format.
|
|
469
|
+
* const portableDid = await did.export();
|
|
470
|
+
*
|
|
471
|
+
* // Reconstruct a BearerDid object from a PortableDid
|
|
472
|
+
* const did = await DidDht.import(portableDid);
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
export class DidDht extends DidMethod {
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Name of the DID method, as defined in the DID DHT specification.
|
|
479
|
+
*/
|
|
480
|
+
public static methodName = 'dht';
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Creates a new DID using the `did:dht` method formed from a newly generated key.
|
|
484
|
+
*
|
|
485
|
+
* @remarks
|
|
486
|
+
* The DID URI is formed by z-base-32 encoding the Identity Key public key and prefixing with
|
|
487
|
+
* `did:dht:`.
|
|
488
|
+
*
|
|
489
|
+
* Notes:
|
|
490
|
+
* - If no `options` are given, by default a new Ed25519 key will be generated which serves as the
|
|
491
|
+
* Identity Key.
|
|
492
|
+
*
|
|
493
|
+
* @example
|
|
494
|
+
* ```ts
|
|
495
|
+
* // DID Creation
|
|
496
|
+
* const did = await DidDht.create();
|
|
497
|
+
*
|
|
498
|
+
* // DID Creation with a KMS
|
|
499
|
+
* const keyManager = new LocalKeyManager();
|
|
500
|
+
* const did = await DidDht.create({ keyManager });
|
|
501
|
+
* ```
|
|
502
|
+
*
|
|
503
|
+
* @param params - The parameters for the create operation.
|
|
504
|
+
* @param params.keyManager - Optionally specify a Key Management System (KMS) used to generate
|
|
505
|
+
* keys and sign data.
|
|
506
|
+
* @param params.options - Optional parameters that can be specified when creating a new DID.
|
|
507
|
+
* @returns A Promise resolving to a {@link BearerDid} object representing the new DID.
|
|
508
|
+
*/
|
|
509
|
+
public static async create<TKms extends CryptoApi | undefined = undefined>({
|
|
510
|
+
keyManager = new LocalKeyManager(),
|
|
511
|
+
options = {}
|
|
512
|
+
}: {
|
|
513
|
+
keyManager?: TKms;
|
|
514
|
+
options?: DidDhtCreateOptions<TKms>;
|
|
515
|
+
} = {}): Promise<BearerDid> {
|
|
516
|
+
// Before processing the create operation, validate DID-method-specific requirements to prevent
|
|
517
|
+
// keys from being generated unnecessarily.
|
|
518
|
+
|
|
519
|
+
// Check 1: Validate that the algorithm for any given verification method is supported by the
|
|
520
|
+
// DID DHT specification.
|
|
521
|
+
if (options.verificationMethods?.some(vm => !(vm.algorithm in AlgorithmToKeyTypeMap))) {
|
|
522
|
+
throw new Error('One or more verification method algorithms are not supported');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Check 2: Validate that the ID for any given verification method is unique.
|
|
526
|
+
const methodIds = options.verificationMethods?.filter(vm => 'id' in vm).map(vm => vm.id);
|
|
527
|
+
if (methodIds && methodIds.length !== new Set(methodIds).size) {
|
|
528
|
+
throw new Error('One or more verification method IDs are not unique');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Check 3: Validate that the required properties for any given services are present.
|
|
532
|
+
if (options.services?.some(s => !s.id || !s.type || !s.serviceEndpoint)) {
|
|
533
|
+
throw new Error('One or more services are missing required properties');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Generate random key material for the Identity Key.
|
|
537
|
+
const identityKeyUri = await keyManager.generateKey({ algorithm: 'Ed25519' });
|
|
538
|
+
const identityKey = await keyManager.getPublicKey({ keyUri: identityKeyUri });
|
|
539
|
+
|
|
540
|
+
// Compute the DID URI from the Identity Key.
|
|
541
|
+
const didUri = await DidDhtUtils.identityKeyToIdentifier({ identityKey });
|
|
542
|
+
|
|
543
|
+
// Begin constructing the DID Document.
|
|
544
|
+
const document: DidDocument = {
|
|
545
|
+
id: didUri,
|
|
546
|
+
...options.alsoKnownAs && { alsoKnownAs: options.alsoKnownAs },
|
|
547
|
+
...options.controllers && { controller: options.controllers }
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// If the given verification methods do not contain an Identity Key, add one.
|
|
551
|
+
const verificationMethodsToAdd = [...options.verificationMethods ?? []];
|
|
552
|
+
if (!verificationMethodsToAdd?.some(vm => vm.id?.split('#').pop() === '0')) {
|
|
553
|
+
// Add the Identity Key to the beginning of the key set.
|
|
554
|
+
verificationMethodsToAdd.unshift({
|
|
555
|
+
algorithm : 'Ed25519' as any,
|
|
556
|
+
id : '0',
|
|
557
|
+
purposes : ['authentication', 'assertionMethod', 'capabilityDelegation', 'capabilityInvocation']
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Generate random key material for the Identity Key and any additional verification methods.
|
|
562
|
+
// Add verification methods to the DID document.
|
|
563
|
+
for (const verificationMethod of verificationMethodsToAdd) {
|
|
564
|
+
// Generate a random key for the verification method, or if its the Identity Key's
|
|
565
|
+
// verification method (`id` is 0) use the key previously generated.
|
|
566
|
+
const keyUri = (verificationMethod.id && verificationMethod.id.split('#').pop() === '0')
|
|
567
|
+
? identityKeyUri
|
|
568
|
+
: await keyManager.generateKey({ algorithm: verificationMethod.algorithm });
|
|
569
|
+
|
|
570
|
+
const publicKey = await keyManager.getPublicKey({ keyUri });
|
|
571
|
+
|
|
572
|
+
// Use the given ID, the key's ID, or the key's thumbprint as the verification method ID.
|
|
573
|
+
let methodId = verificationMethod.id ?? publicKey.kid ?? await computeJwkThumbprint({ jwk: publicKey });
|
|
574
|
+
methodId = `${didUri}#${extractDidFragment(methodId)}`; // Remove fragment prefix, if any.
|
|
575
|
+
|
|
576
|
+
// Initialize the `verificationMethod` array if it does not already exist.
|
|
577
|
+
document.verificationMethod ??= [];
|
|
578
|
+
|
|
579
|
+
// Add the verification method to the DID document.
|
|
580
|
+
document.verificationMethod.push({
|
|
581
|
+
id : methodId,
|
|
582
|
+
type : 'JsonWebKey',
|
|
583
|
+
controller : verificationMethod.controller ?? didUri,
|
|
584
|
+
publicKeyJwk : publicKey,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Add the verification method to the specified purpose properties of the DID document.
|
|
588
|
+
for (const purpose of verificationMethod.purposes ?? []) {
|
|
589
|
+
// Initialize the purpose property if it does not already exist.
|
|
590
|
+
if (!document[purpose]) document[purpose] = [];
|
|
591
|
+
// Add the verification method to the purpose property.
|
|
592
|
+
document[purpose]!.push(methodId);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Add services, if any, to the DID document.
|
|
597
|
+
options.services?.forEach(service => {
|
|
598
|
+
document.service ??= [];
|
|
599
|
+
service.id = `${didUri}#${service.id.split('#').pop()}`; // Remove fragment prefix, if any.
|
|
600
|
+
document.service.push(service);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// Create the BearerDid object, including the registered DID types (if any), and specify that
|
|
604
|
+
// the DID has not yet been published.
|
|
605
|
+
const did = new BearerDid({
|
|
606
|
+
uri : didUri,
|
|
607
|
+
document,
|
|
608
|
+
metadata : {
|
|
609
|
+
published: false,
|
|
610
|
+
...options.types && { types: options.types }
|
|
611
|
+
},
|
|
612
|
+
keyManager
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// By default, publish the DID document to a DHT Gateway unless explicitly disabled.
|
|
616
|
+
if (options.publish ?? true) {
|
|
617
|
+
const registrationResult = await DidDht.publish({ did, gatewayUri: options.gatewayUri });
|
|
618
|
+
did.metadata = registrationResult.didDocumentMetadata;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return did;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Instantiates a {@link BearerDid} object for the DID DHT method from a given {@link PortableDid}.
|
|
626
|
+
*
|
|
627
|
+
* This method allows for the creation of a `BearerDid` object using a previously created DID's
|
|
628
|
+
* key material, DID document, and metadata.
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
* ```ts
|
|
632
|
+
* // Export an existing BearerDid to PortableDid format.
|
|
633
|
+
* const portableDid = await did.export();
|
|
634
|
+
* // Reconstruct a BearerDid object from the PortableDid.
|
|
635
|
+
* const did = await DidDht.import({ portableDid });
|
|
636
|
+
* ```
|
|
637
|
+
*
|
|
638
|
+
* @param params - The parameters for the import operation.
|
|
639
|
+
* @param params.portableDid - The PortableDid object to import.
|
|
640
|
+
* @param params.keyManager - Optionally specify an external Key Management System (KMS) used to
|
|
641
|
+
* generate keys and sign data. If not given, a new
|
|
642
|
+
* {@link LocalKeyManager} instance will be created and
|
|
643
|
+
* used.
|
|
644
|
+
* @returns A Promise resolving to a `BearerDid` object representing the DID formed from the
|
|
645
|
+
* provided PortableDid.
|
|
646
|
+
* @throws An error if the PortableDid document does not contain any verification methods, lacks
|
|
647
|
+
* an Identity Key, or the keys for any verification method are missing in the key
|
|
648
|
+
* manager.
|
|
649
|
+
*/
|
|
650
|
+
public static async import({ portableDid, keyManager = new LocalKeyManager() }: {
|
|
651
|
+
keyManager?: CryptoApi & KeyImporterExporter<KmsImportKeyParams, KeyIdentifier, KmsExportKeyParams>;
|
|
652
|
+
portableDid: PortableDid;
|
|
653
|
+
}): Promise<BearerDid> {
|
|
654
|
+
// Verify the DID method is supported.
|
|
655
|
+
const parsedDid = Did.parse(portableDid.uri);
|
|
656
|
+
if (parsedDid?.method !== DidDht.methodName) {
|
|
657
|
+
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported`);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const did = await BearerDid.import({ portableDid, keyManager });
|
|
661
|
+
|
|
662
|
+
// Validate that the given verification methods contain an Identity Key.
|
|
663
|
+
if (!did.document.verificationMethod?.some(vm => vm.id?.split('#').pop() === '0')) {
|
|
664
|
+
throw new DidError(DidErrorCode.InvalidDidDocument, `DID document must contain an Identity Key`);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return did;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Given the W3C DID Document of a `did:dht` DID, return the verification method that will be used
|
|
672
|
+
* for signing messages and credentials. If given, the `methodId` parameter is used to select the
|
|
673
|
+
* verification method. If not given, the Identity Key's verification method with an ID fragment
|
|
674
|
+
* of '#0' is used.
|
|
675
|
+
*
|
|
676
|
+
* @param params - The parameters for the `getSigningMethod` operation.
|
|
677
|
+
* @param params.didDocument - DID Document to get the verification method from.
|
|
678
|
+
* @param params.methodId - ID of the verification method to use for signing.
|
|
679
|
+
* @returns Verification method to use for signing.
|
|
680
|
+
*/
|
|
681
|
+
public static async getSigningMethod({ didDocument, methodId = '#0' }: {
|
|
682
|
+
didDocument: DidDocument;
|
|
683
|
+
methodId?: string;
|
|
684
|
+
}): Promise<DidVerificationMethod> {
|
|
685
|
+
// Verify the DID method is supported.
|
|
686
|
+
const parsedDid = Did.parse(didDocument.id);
|
|
687
|
+
if (parsedDid && parsedDid.method !== this.methodName) {
|
|
688
|
+
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Attempt to find a verification method that matches the given method ID, or if not given,
|
|
692
|
+
// find the first verification method intended for signing claims.
|
|
693
|
+
const verificationMethod = didDocument.verificationMethod?.find(
|
|
694
|
+
vm => extractDidFragment(vm.id) === (extractDidFragment(methodId) ?? extractDidFragment(didDocument.assertionMethod?.[0]))
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
if (!(verificationMethod && verificationMethod.publicKeyJwk)) {
|
|
698
|
+
throw new DidError(DidErrorCode.InternalError, 'A verification method intended for signing could not be determined from the DID Document');
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return verificationMethod;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Publishes a DID to the DHT, making it publicly discoverable and resolvable.
|
|
706
|
+
*
|
|
707
|
+
* This method handles the publication of a DID Document associated with a `did:dht` DID to the
|
|
708
|
+
* Mainline DHT network. The publication process involves storing the DID Document in Mainline DHT
|
|
709
|
+
* via a Pkarr relay server.
|
|
710
|
+
*
|
|
711
|
+
* @remarks
|
|
712
|
+
* - This method is typically invoked automatically during the creation of a new DID unless the
|
|
713
|
+
* `publish` option is set to `false`.
|
|
714
|
+
* - For existing, unpublished DIDs, it can be used to publish the DID Document to Mainline DHT.
|
|
715
|
+
* - The method relies on the specified Pkarr relay server to interface with the DHT network.
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```ts
|
|
719
|
+
* // Generate a new DID and keys but explicitly disable publishing.
|
|
720
|
+
* const did = await DidDht.create({ options: { publish: false } });
|
|
721
|
+
* // Publish the DID to the DHT.
|
|
722
|
+
* const registrationResult = await DidDht.publish({ did });
|
|
723
|
+
* // `registrationResult.didDocumentMetadata.published` is true if the DID was successfully published.
|
|
724
|
+
* ```
|
|
725
|
+
*
|
|
726
|
+
* @param params - The parameters for the `publish` operation.
|
|
727
|
+
* @param params.did - The `BearerDid` object representing the DID to be published.
|
|
728
|
+
* @param params.gatewayUri - Optional. The URI of a server involved in executing DID method
|
|
729
|
+
* operations. In the context of publishing, the endpoint is expected
|
|
730
|
+
* to be a DID DHT Gateway or Pkarr Relay. If not specified, a default
|
|
731
|
+
* gateway node is used.
|
|
732
|
+
* @returns A promise that resolves to a {@link DidRegistrationResult} object that contains
|
|
733
|
+
* the result of registering the DID with a DID DHT Gateway or Pkarr relay.
|
|
734
|
+
*/
|
|
735
|
+
public static async publish({ did, gatewayUri = DEFAULT_GATEWAY_URI }: {
|
|
736
|
+
did: BearerDid;
|
|
737
|
+
gatewayUri?: string;
|
|
738
|
+
}): Promise<DidRegistrationResult> {
|
|
739
|
+
const registrationResult = await DidDhtDocument.put({ did, gatewayUri });
|
|
740
|
+
|
|
741
|
+
return registrationResult;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Resolves a `did:dht` identifier to its corresponding DID document.
|
|
746
|
+
*
|
|
747
|
+
* This method performs the resolution of a `did:dht` DID, retrieving its DID Document from the
|
|
748
|
+
* Mainline DHT network. The process involves querying the DHT network via a Pkarr relay server to
|
|
749
|
+
* retrieve the DID Document that corresponds to the given DID identifier.
|
|
750
|
+
*
|
|
751
|
+
* @remarks
|
|
752
|
+
* - If a `gatewayUri` option is not specified, a default Pkarr relay is used to access the DHT
|
|
753
|
+
* network.
|
|
754
|
+
* - It decodes the DID identifier and retrieves the associated DID Document and metadata.
|
|
755
|
+
* - In case of resolution failure, appropriate error information is returned.
|
|
756
|
+
*
|
|
757
|
+
* @example
|
|
758
|
+
* ```ts
|
|
759
|
+
* const resolutionResult = await DidDht.resolve('did:dht:example');
|
|
760
|
+
* ```
|
|
761
|
+
*
|
|
762
|
+
* @param didUri - The DID to be resolved.
|
|
763
|
+
* @param options - Optional parameters for resolving the DID. Unused by this DID method.
|
|
764
|
+
* @returns A Promise resolving to a {@link DidResolutionResult} object representing the result of
|
|
765
|
+
* the resolution.
|
|
766
|
+
*/
|
|
767
|
+
public static async resolve(didUri: string, options: DidResolutionOptions = {}): Promise<DidResolutionResult> {
|
|
768
|
+
// To execute the read method operation, use the given gateway URI or a default.
|
|
769
|
+
const gatewayUri = options?.gatewayUri ?? DEFAULT_GATEWAY_URI;
|
|
770
|
+
|
|
771
|
+
try {
|
|
772
|
+
// Attempt to decode the z-base-32-encoded identifier.
|
|
773
|
+
await DidDhtUtils.identifierToIdentityKey({ didUri });
|
|
774
|
+
|
|
775
|
+
// Attempt to retrieve the DID document and metadata from the DHT network.
|
|
776
|
+
const { didDocument, didDocumentMetadata } = await DidDhtDocument.get({ didUri, gatewayUri });
|
|
777
|
+
|
|
778
|
+
// If the DID document was retrieved successfully, return it.
|
|
779
|
+
return {
|
|
780
|
+
...EMPTY_DID_RESOLUTION_RESULT,
|
|
781
|
+
didDocument,
|
|
782
|
+
didDocumentMetadata
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
} catch (error: any) {
|
|
786
|
+
// Rethrow any unexpected errors that are not a `DidError`.
|
|
787
|
+
if (!(error instanceof DidError)) throw new Error(error);
|
|
788
|
+
|
|
789
|
+
// Return a DID Resolution Result with the appropriate error code.
|
|
790
|
+
return {
|
|
791
|
+
...EMPTY_DID_RESOLUTION_RESULT,
|
|
792
|
+
didResolutionMetadata: {
|
|
793
|
+
error: error.code,
|
|
794
|
+
...error.message && { errorMessage: error.message }
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* The `DidDhtDocument` class provides functionality for interacting with the DID document stored in
|
|
803
|
+
* Mainline DHT in support of DID DHT method create, resolve, update, and deactivate operations.
|
|
804
|
+
*
|
|
805
|
+
* This class includes methods for retrieving and publishing DID documents to and from the DHT,
|
|
806
|
+
* using DNS packet encoding and DID DHT Gateway or Pkarr Relay servers.
|
|
807
|
+
*/
|
|
808
|
+
export class DidDhtDocument {
|
|
809
|
+
/**
|
|
810
|
+
* Retrieves a DID document and its metadata from the DHT network.
|
|
811
|
+
*
|
|
812
|
+
* @param params - The parameters for the get operation.
|
|
813
|
+
* @param params.didUri - The DID URI containing the Identity Key.
|
|
814
|
+
* @param params.gatewayUri - The DID DHT Gateway or Pkarr Relay URI.
|
|
815
|
+
* @returns A Promise resolving to a {@link DidResolutionResult} object containing the DID
|
|
816
|
+
* document and its metadata.
|
|
817
|
+
*/
|
|
818
|
+
public static async get({ didUri, gatewayUri }: {
|
|
819
|
+
didUri: string;
|
|
820
|
+
gatewayUri: string;
|
|
821
|
+
}): Promise<DidResolutionResult> {
|
|
822
|
+
// Decode the z-base-32 DID identifier to public key as a byte array.
|
|
823
|
+
const publicKeyBytes = DidDhtUtils.identifierToIdentityKeyBytes({ didUri });
|
|
824
|
+
|
|
825
|
+
// Retrieve the signed BEP44 message from a DID DHT Gateway or Pkarr relay.
|
|
826
|
+
const bep44Message = await DidDhtDocument.pkarrGet({ gatewayUri, publicKeyBytes });
|
|
827
|
+
|
|
828
|
+
// Verify the signature of the BEP44 message and parse the value to a DNS packet.
|
|
829
|
+
const dnsPacket = await DidDhtUtils.parseBep44GetMessage({ bep44Message });
|
|
830
|
+
|
|
831
|
+
// Convert the DNS packet to a DID document and metadata.
|
|
832
|
+
const resolutionResult = await DidDhtDocument.fromDnsPacket({ didUri, dnsPacket });
|
|
833
|
+
|
|
834
|
+
// Set the version ID of the DID document metadata to the sequence number of the BEP44 message.
|
|
835
|
+
resolutionResult.didDocumentMetadata.versionId = bep44Message.seq.toString();
|
|
836
|
+
|
|
837
|
+
return resolutionResult;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Publishes a DID document to the DHT network.
|
|
842
|
+
*
|
|
843
|
+
* @param params - The parameters to use when publishing the DID document to the DHT network.
|
|
844
|
+
* @param params.did - The DID object whose DID document will be published.
|
|
845
|
+
* @param params.gatewayUri - The DID DHT Gateway or Pkarr Relay URI.
|
|
846
|
+
* @returns A promise that resolves to a {@link DidRegistrationResult} object that contains
|
|
847
|
+
* the result of registering the DID with a DID DHT Gateway or Pkarr relay.
|
|
848
|
+
*/
|
|
849
|
+
public static async put({ did, gatewayUri }: {
|
|
850
|
+
did: BearerDid;
|
|
851
|
+
gatewayUri: string;
|
|
852
|
+
}): Promise<DidRegistrationResult> {
|
|
853
|
+
// Convert the DID document and DID metadata (such as DID types) to a DNS packet.
|
|
854
|
+
const dnsPacket = await DidDhtDocument.toDnsPacket({
|
|
855
|
+
didDocument : did.document,
|
|
856
|
+
didMetadata : did.metadata,
|
|
857
|
+
authoritativeGatewayUris : [gatewayUri]
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// Create a signed BEP44 put message from the DNS packet.
|
|
861
|
+
const bep44Message = await DidDhtUtils.createBep44PutMessage({
|
|
862
|
+
dnsPacket,
|
|
863
|
+
publicKeyBytes : DidDhtUtils.identifierToIdentityKeyBytes({ didUri: did.uri }),
|
|
864
|
+
signer : await did.getSigner({ methodId: '0' })
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// Publish the DNS packet to the DHT network.
|
|
868
|
+
const putResult = await DidDhtDocument.pkarrPut({ gatewayUri, bep44Message });
|
|
869
|
+
|
|
870
|
+
// Return the result of processing the PUT operation, including the updated DID metadata with
|
|
871
|
+
// the version ID and the publishing result.
|
|
872
|
+
return {
|
|
873
|
+
didDocument : did.document,
|
|
874
|
+
didDocumentMetadata : {
|
|
875
|
+
...did.metadata,
|
|
876
|
+
published : putResult,
|
|
877
|
+
versionId : bep44Message.seq.toString()
|
|
878
|
+
},
|
|
879
|
+
didRegistrationMetadata: {}
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Retrieves a signed BEP44 message from a DID DHT Gateway or Pkarr Relay server.
|
|
885
|
+
*
|
|
886
|
+
* @see {@link https://github.com/Nuhvi/pkarr/blob/main/design/relays.md | Pkarr Relay design}
|
|
887
|
+
*
|
|
888
|
+
* @param params
|
|
889
|
+
* @param params.gatewayUri - The DID DHT Gateway or Pkarr Relay URI.
|
|
890
|
+
* @param params.publicKeyBytes - The public key bytes of the Identity Key, z-base-32 encoded.
|
|
891
|
+
* @returns A promise resolving to a BEP44 message containing the signed DNS packet.
|
|
892
|
+
*/
|
|
893
|
+
private static async pkarrGet({ gatewayUri, publicKeyBytes }: {
|
|
894
|
+
publicKeyBytes: Uint8Array;
|
|
895
|
+
gatewayUri: string;
|
|
896
|
+
}): Promise<Bep44Message> {
|
|
897
|
+
// The identifier (key in the DHT) is the z-base-32 encoding of the Identity Key.
|
|
898
|
+
const identifier = Convert.uint8Array(publicKeyBytes).toBase32Z();
|
|
899
|
+
|
|
900
|
+
// Concatenate the gateway URI with the identifier to form the full URL.
|
|
901
|
+
const url = new URL(identifier, gatewayUri).href;
|
|
902
|
+
|
|
903
|
+
// Transmit the Get request to the DID DHT Gateway or Pkarr Relay and get the response.
|
|
904
|
+
let response: Response;
|
|
905
|
+
try {
|
|
906
|
+
response = await fetch(url, { method: 'GET' });
|
|
907
|
+
|
|
908
|
+
if (!response.ok) {
|
|
909
|
+
throw new DidError(DidErrorCode.NotFound, `Pkarr record not found for: ${identifier}`);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
} catch (error: any) {
|
|
913
|
+
if (error instanceof DidError) throw error;
|
|
914
|
+
throw new DidError(DidErrorCode.InternalError, `Failed to fetch Pkarr record: ${error.message}`);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Read the Fetch Response stream into a byte array.
|
|
918
|
+
const messageBytes = await response.arrayBuffer();
|
|
919
|
+
|
|
920
|
+
if(!messageBytes) {
|
|
921
|
+
throw new DidError(DidErrorCode.NotFound, `Pkarr record not found for: ${identifier}`);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (messageBytes.byteLength < 72) {
|
|
925
|
+
throw new DidError(DidErrorCode.InvalidDidDocumentLength, `Pkarr response must be at least 72 bytes but got: ${messageBytes.byteLength}`);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (messageBytes.byteLength > 1072) {
|
|
929
|
+
throw new DidError(DidErrorCode.InvalidDidDocumentLength, `Pkarr response exceeds 1000 byte limit: ${messageBytes.byteLength}`);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Decode the BEP44 message from the byte array.
|
|
933
|
+
const bep44Message: Bep44Message = {
|
|
934
|
+
k : publicKeyBytes,
|
|
935
|
+
seq : Number(new DataView(messageBytes).getBigUint64(64)),
|
|
936
|
+
sig : new Uint8Array(messageBytes, 0, 64),
|
|
937
|
+
v : new Uint8Array(messageBytes, 72)
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
return bep44Message;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Publishes a signed BEP44 message to a DID DHT Gateway or Pkarr Relay server.
|
|
945
|
+
*
|
|
946
|
+
* @see {@link https://github.com/Nuhvi/pkarr/blob/main/design/relays.md | Pkarr Relay design}
|
|
947
|
+
*
|
|
948
|
+
* @param params - The parameters to use when publishing a signed BEP44 message to a Pkarr relay server.
|
|
949
|
+
* @param params.gatewayUri - The DID DHT Gateway or Pkarr Relay URI.
|
|
950
|
+
* @param params.bep44Message - The BEP44 message to be published, containing the signed DNS packet.
|
|
951
|
+
* @returns A promise resolving to `true` if the message was successfully published, otherwise `false`.
|
|
952
|
+
*/
|
|
953
|
+
private static async pkarrPut({ gatewayUri, bep44Message }: {
|
|
954
|
+
bep44Message: Bep44Message;
|
|
955
|
+
gatewayUri: string;
|
|
956
|
+
}): Promise<boolean> {
|
|
957
|
+
// The identifier (key in the DHT) is the z-base-32 encoding of the Identity Key.
|
|
958
|
+
const identifier = Convert.uint8Array(bep44Message.k).toBase32Z();
|
|
959
|
+
|
|
960
|
+
// Concatenate the gateway URI with the identifier to form the full URL.
|
|
961
|
+
const url = new URL(identifier, gatewayUri).href;
|
|
962
|
+
|
|
963
|
+
// Construct the body of the request according to the Pkarr relay specification.
|
|
964
|
+
const body = new Uint8Array(bep44Message.v.length + 72);
|
|
965
|
+
body.set(bep44Message.sig, 0);
|
|
966
|
+
new DataView(body.buffer).setBigUint64(bep44Message.sig.length, BigInt(bep44Message.seq));
|
|
967
|
+
body.set(bep44Message.v, bep44Message.sig.length + 8);
|
|
968
|
+
|
|
969
|
+
// Transmit the Put request to the Pkarr relay and get the response.
|
|
970
|
+
let response: Response;
|
|
971
|
+
try {
|
|
972
|
+
response = await fetch(url, {
|
|
973
|
+
method : 'PUT',
|
|
974
|
+
headers : { 'Content-Type': 'application/octet-stream' },
|
|
975
|
+
body
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
} catch (error: any) {
|
|
979
|
+
throw new DidError(DidErrorCode.InternalError, `Failed to put Pkarr record for identifier ${identifier}: ${error.message}`);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Return `true` if the DHT request was successful, otherwise return `false`.
|
|
983
|
+
return response.ok;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Converts a DNS packet to a DID document according to the DID DHT specification.
|
|
988
|
+
*
|
|
989
|
+
* @see {@link https://did-dht.com/#dids-as-dns-records | DID DHT Specification, § DIDs as DNS Records}
|
|
990
|
+
*
|
|
991
|
+
* @param params - The parameters to use when converting a DNS packet to a DID document.
|
|
992
|
+
* @param params.didUri - The DID URI of the DID document.
|
|
993
|
+
* @param params.dnsPacket - The DNS packet to convert to a DID document.
|
|
994
|
+
* @returns A Promise resolving to a {@link DidResolutionResult} object containing the DID
|
|
995
|
+
* document and its metadata.
|
|
996
|
+
*/
|
|
997
|
+
public static async fromDnsPacket({ didUri, dnsPacket }: {
|
|
998
|
+
didUri: string;
|
|
999
|
+
dnsPacket: Packet;
|
|
1000
|
+
}): Promise<DidResolutionResult> {
|
|
1001
|
+
// Begin constructing the DID Document.
|
|
1002
|
+
const didDocument: DidDocument = { id: didUri };
|
|
1003
|
+
|
|
1004
|
+
// Since the DID document is being retrieved from the DHT, it is considered published.
|
|
1005
|
+
const didDocumentMetadata: DidMetadata = {
|
|
1006
|
+
published: true
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
const idLookup = new Map<string, string>();
|
|
1010
|
+
|
|
1011
|
+
for (const answer of dnsPacket?.answers ?? []) {
|
|
1012
|
+
// DID DHT properties are ONLY present in DNS TXT records.
|
|
1013
|
+
if (answer.type !== 'TXT') continue;
|
|
1014
|
+
|
|
1015
|
+
// Get the DID DHT record identifier (e.g., k0, aka, did, etc.) from the DNS resource name.
|
|
1016
|
+
const dnsRecordId = answer.name.split('.')[0].substring(1);
|
|
1017
|
+
|
|
1018
|
+
switch (true) {
|
|
1019
|
+
// Process an also known as record.
|
|
1020
|
+
case dnsRecordId.startsWith('aka'): {
|
|
1021
|
+
// Decode the DNS TXT record data value to a string.
|
|
1022
|
+
const data = DidDhtUtils.parseTxtDataToString(answer.data);
|
|
1023
|
+
|
|
1024
|
+
// Add the 'alsoKnownAs' property to the DID document.
|
|
1025
|
+
didDocument.alsoKnownAs = data.split(VALUE_SEPARATOR);
|
|
1026
|
+
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Process a controller record.
|
|
1031
|
+
case dnsRecordId.startsWith('cnt'): {
|
|
1032
|
+
// Decode the DNS TXT record data value to a string.
|
|
1033
|
+
const data = DidDhtUtils.parseTxtDataToString(answer.data);
|
|
1034
|
+
|
|
1035
|
+
// Add the 'controller' property to the DID document.
|
|
1036
|
+
didDocument.controller = data.includes(VALUE_SEPARATOR) ? data.split(VALUE_SEPARATOR) : data;
|
|
1037
|
+
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Process verification methods.
|
|
1042
|
+
case dnsRecordId.startsWith('k'): {
|
|
1043
|
+
// Get the key type (t), Base64URL-encoded public key (k), algorithm (a), and
|
|
1044
|
+
// optionally, controller (c) or Verification Method ID (id) from the decoded TXT record data.
|
|
1045
|
+
const { id, t, k, c, a: parsedAlg } = DidDhtUtils.parseTxtDataToObject(answer.data);
|
|
1046
|
+
|
|
1047
|
+
// Convert the public key from Base64URL format to a byte array.
|
|
1048
|
+
const publicKeyBytes = Convert.base64Url(k).toUint8Array();
|
|
1049
|
+
|
|
1050
|
+
// Use the key type integer to look up the cryptographic curve name.
|
|
1051
|
+
const namedCurve = DidDhtRegisteredKeyType[Number(t)];
|
|
1052
|
+
|
|
1053
|
+
// Convert the public key from a byte array to JWK format.
|
|
1054
|
+
let publicKey = await DidDhtUtils.keyConverter(namedCurve).bytesToPublicKey({ publicKeyBytes });
|
|
1055
|
+
|
|
1056
|
+
publicKey.alg = parsedAlg || KeyTypeToDefaultAlgorithmMap[Number(t) as DidDhtRegisteredKeyType];
|
|
1057
|
+
|
|
1058
|
+
// TOOD: when this is complete https://github.com/TBD54566975/web5-js/issues/638 then we can add this back and
|
|
1059
|
+
// update the test vectors kid back to '0'
|
|
1060
|
+
// if(dnsRecordId === 'k0') {
|
|
1061
|
+
// publicKey.kid = '0';
|
|
1062
|
+
// }
|
|
1063
|
+
|
|
1064
|
+
// Determine the Verification Method ID: '0' for the identity key,
|
|
1065
|
+
// the id from the TXT Data Object, or the JWK thumbprint if an explicity Verification Method ID not defined.
|
|
1066
|
+
const vmId = dnsRecordId === 'k0' ? '0' : id !== undefined ? id : await computeJwkThumbprint({ jwk: publicKey });
|
|
1067
|
+
|
|
1068
|
+
// Initialize the `verificationMethod` array if it does not already exist.
|
|
1069
|
+
didDocument.verificationMethod ??= [];
|
|
1070
|
+
|
|
1071
|
+
// Prepend the DID URI to the ID fragment to form the full verification method ID.
|
|
1072
|
+
const methodId = `${didUri}#${vmId}`;
|
|
1073
|
+
|
|
1074
|
+
// Add the verification method to the DID document.
|
|
1075
|
+
didDocument.verificationMethod.push({
|
|
1076
|
+
id : methodId,
|
|
1077
|
+
type : 'JsonWebKey',
|
|
1078
|
+
controller : c ?? didUri,
|
|
1079
|
+
publicKeyJwk : publicKey,
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
// Add a mapping from the DNS record ID (e.g., 'k0', 'k1', etc.) to the verification
|
|
1083
|
+
// method ID (e.g., 'did:dht:...#0', etc.).
|
|
1084
|
+
idLookup.set(dnsRecordId, methodId);
|
|
1085
|
+
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Process services.
|
|
1090
|
+
case dnsRecordId.startsWith('s'): {
|
|
1091
|
+
// Get the service ID fragment (id), type (t), service endpoint (se), and optionally,
|
|
1092
|
+
// other properties from the decoded TXT record data.
|
|
1093
|
+
const { id, t, se, ...customProperties } = DidDhtUtils.parseTxtDataToObject(answer.data);
|
|
1094
|
+
|
|
1095
|
+
// if multi-values: 'a,b,c' -> ['a', 'b', 'c'], if single-value: 'a' -> ['a']
|
|
1096
|
+
// NOTE: The service endpoint technically can either be a string or an array of strings,
|
|
1097
|
+
// we enforce an array for single-value to simplify verification of vector 3 in the spec: https://did-dht.com/#vector-3
|
|
1098
|
+
const serviceEndpoint = se.includes(VALUE_SEPARATOR) ? se.split(VALUE_SEPARATOR) : [se];
|
|
1099
|
+
|
|
1100
|
+
// Convert custom property values to either a string or an array of strings.
|
|
1101
|
+
const serviceProperties = Object.fromEntries(Object.entries(customProperties).map(
|
|
1102
|
+
([k, v]) => [k, v.includes(VALUE_SEPARATOR) ? v.split(VALUE_SEPARATOR) : v]
|
|
1103
|
+
));
|
|
1104
|
+
|
|
1105
|
+
// Initialize the `service` array if it does not already exist.
|
|
1106
|
+
didDocument.service ??= [];
|
|
1107
|
+
|
|
1108
|
+
didDocument.service.push({
|
|
1109
|
+
...serviceProperties,
|
|
1110
|
+
id : `${didUri}#${id}`,
|
|
1111
|
+
type : t,
|
|
1112
|
+
serviceEndpoint
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Process DID DHT types.
|
|
1119
|
+
case dnsRecordId.startsWith('typ'): {
|
|
1120
|
+
// Decode the DNS TXT record data value to an object.
|
|
1121
|
+
const { id: types } = DidDhtUtils.parseTxtDataToObject(answer.data);
|
|
1122
|
+
|
|
1123
|
+
// Add the DID DHT Registered DID Types represented as numbers to DID metadata.
|
|
1124
|
+
didDocumentMetadata.types = types.split(VALUE_SEPARATOR).map(typeInteger => Number(typeInteger));
|
|
1125
|
+
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Process root record.
|
|
1130
|
+
case dnsRecordId.startsWith('did'): {
|
|
1131
|
+
// Helper function that maps verification relationship values to verification method IDs.
|
|
1132
|
+
const recordIdsToMethodIds = (data: string): string[] => data
|
|
1133
|
+
.split(VALUE_SEPARATOR)
|
|
1134
|
+
.map(dnsRecordId => idLookup.get(dnsRecordId))
|
|
1135
|
+
.filter((id): id is string => typeof id === 'string');
|
|
1136
|
+
|
|
1137
|
+
// Decode the DNS TXT record data and destructure verification relationship properties.
|
|
1138
|
+
const { auth, asm, del, inv, agm } = DidDhtUtils.parseTxtDataToObject(answer.data);
|
|
1139
|
+
|
|
1140
|
+
// Add the verification relationships, if any, to the DID document.
|
|
1141
|
+
if (auth) didDocument.authentication = recordIdsToMethodIds(auth);
|
|
1142
|
+
if (asm) didDocument.assertionMethod = recordIdsToMethodIds(asm);
|
|
1143
|
+
if (del) didDocument.capabilityDelegation = recordIdsToMethodIds(del);
|
|
1144
|
+
if (inv) didDocument.capabilityInvocation = recordIdsToMethodIds(inv);
|
|
1145
|
+
if (agm) didDocument.keyAgreement = recordIdsToMethodIds(agm);
|
|
1146
|
+
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
return { didDocument, didDocumentMetadata, didResolutionMetadata: {} };
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Converts a DID document to a DNS packet according to the DID DHT specification.
|
|
1157
|
+
*
|
|
1158
|
+
* @see {@link https://did-dht.com/#dids-as-dns-records | DID DHT Specification, § DIDs as DNS Records}
|
|
1159
|
+
*
|
|
1160
|
+
* @param params - The parameters to use when converting a DID document to a DNS packet.
|
|
1161
|
+
* @param params.didDocument - The DID document to convert to a DNS packet.
|
|
1162
|
+
* @param params.didMetadata - The DID metadata to include in the DNS packet.
|
|
1163
|
+
* @param params.authoritativeGatewayUris - The URIs of the Authoritative Gateways to generate NS records from.
|
|
1164
|
+
* @param params.previousDidProof - The signature proof that this DID is linked to the given previous DID.
|
|
1165
|
+
* @returns A promise that resolves to a DNS packet.
|
|
1166
|
+
*/
|
|
1167
|
+
public static async toDnsPacket({ didDocument, didMetadata, authoritativeGatewayUris, previousDidProof }: {
|
|
1168
|
+
didDocument: DidDocument;
|
|
1169
|
+
didMetadata: DidMetadata;
|
|
1170
|
+
authoritativeGatewayUris?: string[];
|
|
1171
|
+
previousDidProof?: PreviousDidProof;
|
|
1172
|
+
}): Promise<Packet> {
|
|
1173
|
+
const txtRecords: TxtAnswer[] = [];
|
|
1174
|
+
const nsRecords: StringAnswer[] = [];
|
|
1175
|
+
const idLookup = new Map<string, string>();
|
|
1176
|
+
const serviceIds: string[] = [];
|
|
1177
|
+
const verificationMethodIds: string[] = [];
|
|
1178
|
+
|
|
1179
|
+
// Add `_prv._did.` TXT record if previous DID proof is provided and valid.
|
|
1180
|
+
if (previousDidProof !== undefined) {
|
|
1181
|
+
const { signature, previousDid } = previousDidProof;
|
|
1182
|
+
|
|
1183
|
+
await DidDhtUtils.validatePreviousDidProof({
|
|
1184
|
+
newDid: didDocument.id,
|
|
1185
|
+
previousDidProof
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
txtRecords.push({
|
|
1189
|
+
type : 'TXT',
|
|
1190
|
+
name : '_prv._did.',
|
|
1191
|
+
ttl : DNS_RECORD_TTL,
|
|
1192
|
+
data : `id=${previousDid};s=${signature}`
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// Add DNS TXT records if the DID document contains an `alsoKnownAs` property.
|
|
1197
|
+
if (didDocument.alsoKnownAs) {
|
|
1198
|
+
txtRecords.push({
|
|
1199
|
+
type : 'TXT',
|
|
1200
|
+
name : '_aka._did.',
|
|
1201
|
+
ttl : DNS_RECORD_TTL,
|
|
1202
|
+
data : didDocument.alsoKnownAs.join(VALUE_SEPARATOR)
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Add DNS TXT records if the DID document contains a `controller` property.
|
|
1207
|
+
if (didDocument.controller) {
|
|
1208
|
+
const controller = Array.isArray(didDocument.controller)
|
|
1209
|
+
? didDocument.controller.join(VALUE_SEPARATOR)
|
|
1210
|
+
: didDocument.controller;
|
|
1211
|
+
txtRecords.push({
|
|
1212
|
+
type : 'TXT',
|
|
1213
|
+
name : '_cnt._did.',
|
|
1214
|
+
ttl : DNS_RECORD_TTL,
|
|
1215
|
+
data : controller
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// Add DNS TXT records for each verification method.
|
|
1220
|
+
for (const [index, verificationMethod] of didDocument.verificationMethod?.entries() ?? []) {
|
|
1221
|
+
const dnsRecordId = `k${index}`;
|
|
1222
|
+
verificationMethodIds.push(dnsRecordId);
|
|
1223
|
+
let methodId = verificationMethod.id.split('#').pop()!; // Remove fragment prefix, if any.
|
|
1224
|
+
idLookup.set(methodId, dnsRecordId);
|
|
1225
|
+
|
|
1226
|
+
const publicKey = verificationMethod.publicKeyJwk;
|
|
1227
|
+
|
|
1228
|
+
if (!(publicKey?.crv && publicKey.crv in AlgorithmToKeyTypeMap)) {
|
|
1229
|
+
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Verification method '${verificationMethod.id}' contains an unsupported key type: ${publicKey?.crv ?? 'undefined'}`);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Use the public key's `crv` property to get the DID DHT key type.
|
|
1233
|
+
const keyType = DidDhtRegisteredKeyType[publicKey.crv as keyof typeof DidDhtRegisteredKeyType];
|
|
1234
|
+
|
|
1235
|
+
// Convert the public key from JWK format to a byte array.
|
|
1236
|
+
const publicKeyBytes = await DidDhtUtils.keyConverter(publicKey.crv).publicKeyToBytes({ publicKey });
|
|
1237
|
+
|
|
1238
|
+
// Convert the public key from a byte array to Base64URL format.
|
|
1239
|
+
const publicKeyBase64Url = Convert.uint8Array(publicKeyBytes).toBase64Url();
|
|
1240
|
+
|
|
1241
|
+
// Define the data for the DNS TXT record.
|
|
1242
|
+
const txtData = [`t=${keyType}`, `k=${publicKeyBase64Url}`];
|
|
1243
|
+
// if the methodId is not the identity key or a thumbprint, explicity define the id within the DNS TXT record.
|
|
1244
|
+
// otherwise the id can be inferred from the thumbprint.
|
|
1245
|
+
if (methodId !== '0' && await computeJwkThumbprint({ jwk: publicKey }) !== methodId) {
|
|
1246
|
+
txtData.unshift(`id=${methodId}`);
|
|
1247
|
+
}
|
|
1248
|
+
// Only set the algorithm property (`a`) if it differs from the default algorithm for the key type.
|
|
1249
|
+
if(publicKey.alg !== KeyTypeToDefaultAlgorithmMap[keyType]) {
|
|
1250
|
+
txtData.push(`a=${publicKey.alg}`);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Add the controller property, if set to a value other than the Identity Key (DID Subject).
|
|
1254
|
+
if (verificationMethod.controller !== didDocument.id) txtData.push(`c=${verificationMethod.controller}`);
|
|
1255
|
+
|
|
1256
|
+
// Add a TXT record for the verification method.
|
|
1257
|
+
txtRecords.push({
|
|
1258
|
+
type : 'TXT',
|
|
1259
|
+
name : `_${dnsRecordId}._did.`,
|
|
1260
|
+
ttl : DNS_RECORD_TTL,
|
|
1261
|
+
data : txtData.join(PROPERTY_SEPARATOR)
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Add DNS TXT records for each service.
|
|
1266
|
+
didDocument.service?.forEach((service, index) => {
|
|
1267
|
+
const dnsRecordId = `s${index}`;
|
|
1268
|
+
serviceIds.push(dnsRecordId);
|
|
1269
|
+
let { id, type: t, serviceEndpoint: se, ...customProperties } = service;
|
|
1270
|
+
id = extractDidFragment(id)!;
|
|
1271
|
+
se = Array.isArray(se) ? se.join(',') : se;
|
|
1272
|
+
|
|
1273
|
+
// Define the data for the DNS TXT record.
|
|
1274
|
+
const txtData = Object.entries({ id, t, se, ...customProperties }).map(
|
|
1275
|
+
([key, value]) => `${key}=${value}`
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
const txtDataString = txtData.join(PROPERTY_SEPARATOR);
|
|
1279
|
+
const data = DidDhtUtils.chunkDataIfNeeded(txtDataString);
|
|
1280
|
+
|
|
1281
|
+
// Add a TXT record for the verification method.
|
|
1282
|
+
txtRecords.push({
|
|
1283
|
+
type : 'TXT',
|
|
1284
|
+
name : `_${dnsRecordId}._did.`,
|
|
1285
|
+
ttl : DNS_RECORD_TTL,
|
|
1286
|
+
data
|
|
1287
|
+
});
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
// Initialize the root DNS TXT record with the DID DHT specification version.
|
|
1291
|
+
const rootRecord: string[] = [`v=${DID_DHT_SPECIFICATION_VERSION}`];
|
|
1292
|
+
|
|
1293
|
+
// Add verification methods to the root record.
|
|
1294
|
+
if (verificationMethodIds.length) {
|
|
1295
|
+
rootRecord.push(`vm=${verificationMethodIds.join(VALUE_SEPARATOR)}`);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Add verification relationships to the root record.
|
|
1299
|
+
Object.keys(DidVerificationRelationship).forEach(relationship => {
|
|
1300
|
+
// Collect the verification method IDs for the given relationship.
|
|
1301
|
+
const dnsRecordIds = (didDocument[relationship as keyof DidDocument] as any[])
|
|
1302
|
+
?.map(id => idLookup.get(id.split('#').pop()));
|
|
1303
|
+
|
|
1304
|
+
// If the relationship includes verification methods, add them to the root record.
|
|
1305
|
+
if (dnsRecordIds) {
|
|
1306
|
+
const recordName = DidDhtVerificationRelationship[relationship as keyof typeof DidDhtVerificationRelationship];
|
|
1307
|
+
rootRecord.push(`${recordName}=${dnsRecordIds.join(VALUE_SEPARATOR)}`);
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
// Add services to the root record.
|
|
1312
|
+
if (serviceIds.length) {
|
|
1313
|
+
rootRecord.push(`svc=${serviceIds.join(VALUE_SEPARATOR)}`);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// If defined, add a DNS TXT record for each registered DID type.
|
|
1317
|
+
if (didMetadata.types?.length) {
|
|
1318
|
+
// DID types can be specified as either a string or a number, so we need to normalize the
|
|
1319
|
+
// values to integers.
|
|
1320
|
+
const types = didMetadata.types as (DidDhtRegisteredDidType | keyof typeof DidDhtRegisteredDidType)[];
|
|
1321
|
+
const typeIntegers = types.map(type => typeof type === 'string' ? DidDhtRegisteredDidType[type] : type);
|
|
1322
|
+
|
|
1323
|
+
txtRecords.push({
|
|
1324
|
+
type : 'TXT',
|
|
1325
|
+
name : '_typ._did.',
|
|
1326
|
+
ttl : DNS_RECORD_TTL,
|
|
1327
|
+
data : `id=${typeIntegers.join(VALUE_SEPARATOR)}`
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// Add a DNS TXT record for the root record.
|
|
1332
|
+
txtRecords.push({
|
|
1333
|
+
type : 'TXT',
|
|
1334
|
+
name : '_did.' + DidDhtDocument.getUniqueDidSuffix(didDocument.id) + '.', // name of a Root Record MUST end in `<ID>.`
|
|
1335
|
+
ttl : DNS_RECORD_TTL,
|
|
1336
|
+
data : rootRecord.join(PROPERTY_SEPARATOR)
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
// Add an NS record for each authoritative gateway URI.
|
|
1340
|
+
for (const gatewayUri of authoritativeGatewayUris || []) {
|
|
1341
|
+
nsRecords.push({
|
|
1342
|
+
type : 'NS',
|
|
1343
|
+
name : '_did.' + DidDhtDocument.getUniqueDidSuffix(didDocument.id) + '.', // name of an NS record a authoritative gateway MUST end in `<ID>.`
|
|
1344
|
+
ttl : DNS_RECORD_TTL,
|
|
1345
|
+
data : gatewayUri + '.'
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Create a DNS response packet with the authoritative answer flag set.
|
|
1350
|
+
const dnsPacket: Packet = {
|
|
1351
|
+
id : 0,
|
|
1352
|
+
type : 'response',
|
|
1353
|
+
flags : AUTHORITATIVE_ANSWER,
|
|
1354
|
+
answers : [...txtRecords, ...nsRecords]
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
return dnsPacket;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Gets the unique portion of the DID identifier after the last `:` character.
|
|
1362
|
+
* e.g. `did:dht:example` -> `example`
|
|
1363
|
+
*
|
|
1364
|
+
* @param did - The DID to extract the unique suffix from.
|
|
1365
|
+
*/
|
|
1366
|
+
private static getUniqueDidSuffix(did: string ): string {
|
|
1367
|
+
return did.split(':')[2];
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/**
|
|
1372
|
+
* The `DidDhtUtils` class provides utility functions to support operations in the DID DHT method.
|
|
1373
|
+
* This includes functions for creating and parsing BEP44 messages, handling identity keys, and
|
|
1374
|
+
* converting between different formats and representations.
|
|
1375
|
+
*/
|
|
1376
|
+
export class DidDhtUtils {
|
|
1377
|
+
/**
|
|
1378
|
+
* Creates a BEP44 put message, which is used to publish a DID document to the DHT network.
|
|
1379
|
+
*
|
|
1380
|
+
* @param params - The parameters to use when creating the BEP44 put message
|
|
1381
|
+
* @param params.dnsPacket - The DNS packet to encode in the BEP44 message.
|
|
1382
|
+
* @param params.publicKeyBytes - The public key bytes of the Identity Key.
|
|
1383
|
+
* @param params.signer - Signer that can sign and verify data using the Identity Key.
|
|
1384
|
+
* @returns A promise that resolves to a BEP44 put message.
|
|
1385
|
+
*/
|
|
1386
|
+
public static async createBep44PutMessage({ dnsPacket, publicKeyBytes, signer }: {
|
|
1387
|
+
dnsPacket: Packet;
|
|
1388
|
+
publicKeyBytes: Uint8Array;
|
|
1389
|
+
signer: Signer;
|
|
1390
|
+
}): Promise<Bep44Message> {
|
|
1391
|
+
// BEP44 requires that the sequence number be a monotoically increasing integer, so we use the
|
|
1392
|
+
// current time in seconds since Unix epoch as a simple solution. Higher precision is not
|
|
1393
|
+
// recommended since DID DHT documents are not expected to change frequently and there are
|
|
1394
|
+
// small differences in system clocks that can cause issues if multiple clients are publishing
|
|
1395
|
+
// updates to the same DID document.
|
|
1396
|
+
const sequenceNumber = Math.ceil(Date.now() / 1000);
|
|
1397
|
+
|
|
1398
|
+
// Encode the DNS packet into a byte array containing a UDP payload.
|
|
1399
|
+
const encodedDnsPacket = dnsPacketEncode(dnsPacket);
|
|
1400
|
+
|
|
1401
|
+
// Encode the sequence and DNS byte array to bencode format.
|
|
1402
|
+
const bencodedData = bencode.encode({ seq: sequenceNumber, v: encodedDnsPacket }).subarray(1, -1);
|
|
1403
|
+
|
|
1404
|
+
if (bencodedData.length > 1000) {
|
|
1405
|
+
throw new DidError(DidErrorCode.InvalidDidDocumentLength, `DNS packet exceeds the 1000 byte maximum size: ${bencodedData.length} bytes`);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Sign the BEP44 message.
|
|
1409
|
+
const signature = await signer.sign({ data: bencodedData });
|
|
1410
|
+
|
|
1411
|
+
return { k: publicKeyBytes, seq: sequenceNumber, sig: signature, v: encodedDnsPacket };
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Converts a DID URI to a JSON Web Key (JWK) representing the Identity Key.
|
|
1416
|
+
*
|
|
1417
|
+
* @param params - The parameters to use for the conversion.
|
|
1418
|
+
* @param params.didUri - The DID URI containing the Identity Key.
|
|
1419
|
+
* @returns A promise that resolves to a JWK representing the Identity Key.
|
|
1420
|
+
*/
|
|
1421
|
+
public static async identifierToIdentityKey({ didUri }: {
|
|
1422
|
+
didUri: string
|
|
1423
|
+
}): Promise<Jwk> {
|
|
1424
|
+
// Decode the method-specific identifier from z-base-32 to a byte array.
|
|
1425
|
+
let identityKeyBytes = DidDhtUtils.identifierToIdentityKeyBytes({ didUri });
|
|
1426
|
+
|
|
1427
|
+
// Convert the byte array to a JWK.
|
|
1428
|
+
const identityKey = await Ed25519.bytesToPublicKey({ publicKeyBytes: identityKeyBytes });
|
|
1429
|
+
|
|
1430
|
+
return identityKey;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* Converts a DID URI to the byte array representation of the Identity Key.
|
|
1435
|
+
*
|
|
1436
|
+
* @param params - The parameters to use for the conversion.
|
|
1437
|
+
* @param params.didUri - The DID URI containing the Identity Key.
|
|
1438
|
+
* @returns A byte array representation of the Identity Key.
|
|
1439
|
+
*/
|
|
1440
|
+
public static identifierToIdentityKeyBytes({ didUri }: {
|
|
1441
|
+
didUri: string
|
|
1442
|
+
}): Uint8Array {
|
|
1443
|
+
// Parse the DID URI.
|
|
1444
|
+
const parsedDid = Did.parse(didUri);
|
|
1445
|
+
|
|
1446
|
+
// Verify that the DID URI is valid.
|
|
1447
|
+
if (!parsedDid) {
|
|
1448
|
+
throw new DidError(DidErrorCode.InvalidDid, `Invalid DID URI: ${didUri}`);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// Verify the DID method is supported.
|
|
1452
|
+
if (parsedDid.method !== DidDht.methodName) {
|
|
1453
|
+
throw new DidError(DidErrorCode.MethodNotSupported, `Method not supported: ${parsedDid.method}`);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// Decode the method-specific identifier from z-base-32 to a byte array.
|
|
1457
|
+
let identityKeyBytes: Uint8Array | undefined;
|
|
1458
|
+
try {
|
|
1459
|
+
identityKeyBytes = Convert.base32Z(parsedDid.id).toUint8Array();
|
|
1460
|
+
} catch {
|
|
1461
|
+
throw new DidError(DidErrorCode.InvalidPublicKey, `Failed to decode method-specific identifier`);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
if (identityKeyBytes.length !== 32) {
|
|
1465
|
+
throw new DidError(DidErrorCode.InvalidPublicKeyLength, `Invalid public key length: ${identityKeyBytes.length}`);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
return identityKeyBytes;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* Encodes a DID DHT Identity Key into a DID identifier.
|
|
1473
|
+
*
|
|
1474
|
+
* This method first z-base-32 encodes the Identity Key. The resulting string is prefixed with
|
|
1475
|
+
* `did:dht:` to form the DID identifier.
|
|
1476
|
+
*
|
|
1477
|
+
* @param params - The parameters to use for the conversion.
|
|
1478
|
+
* @param params.identityKey The Identity Key from which the DID identifier is computed.
|
|
1479
|
+
* @returns A promise that resolves to a string containing the DID identifier.
|
|
1480
|
+
*/
|
|
1481
|
+
public static async identityKeyToIdentifier({ identityKey }: {
|
|
1482
|
+
identityKey: Jwk
|
|
1483
|
+
}): Promise<string> {
|
|
1484
|
+
// Convert the key from JWK format to a byte array.
|
|
1485
|
+
const publicKeyBytes = await Ed25519.publicKeyToBytes({ publicKey: identityKey });
|
|
1486
|
+
|
|
1487
|
+
// Encode the byte array as a z-base-32 string.
|
|
1488
|
+
const identifier = Convert.uint8Array(publicKeyBytes).toBase32Z();
|
|
1489
|
+
|
|
1490
|
+
return `did:${DidDht.methodName}:${identifier}`;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Returns the appropriate key converter for the specified cryptographic curve.
|
|
1495
|
+
*
|
|
1496
|
+
* @param curve - The cryptographic curve to use for the key conversion.
|
|
1497
|
+
* @returns An `AsymmetricKeyConverter` for the specified curve.
|
|
1498
|
+
*/
|
|
1499
|
+
public static keyConverter(curve: string): AsymmetricKeyConverter {
|
|
1500
|
+
const converters: Record<string, AsymmetricKeyConverter> = {
|
|
1501
|
+
'Ed25519' : Ed25519,
|
|
1502
|
+
'P-256' : {
|
|
1503
|
+
// Wrap the key converter which produces uncompressed public key bytes to produce compressed key bytes as required by the DID DHT spec.
|
|
1504
|
+
// See https://did-dht.com/#representing-keys for more info.
|
|
1505
|
+
publicKeyToBytes: async ({ publicKey }: { publicKey: Jwk }): Promise<Uint8Array> => {
|
|
1506
|
+
const publicKeyBytes = await Secp256r1.publicKeyToBytes({ publicKey });
|
|
1507
|
+
const compressedPublicKey = await Secp256r1.compressPublicKey({ publicKeyBytes });
|
|
1508
|
+
return compressedPublicKey;
|
|
1509
|
+
},
|
|
1510
|
+
bytesToPublicKey : Secp256r1.bytesToPublicKey,
|
|
1511
|
+
privateKeyToBytes : Secp256r1.privateKeyToBytes,
|
|
1512
|
+
bytesToPrivateKey : Secp256r1.bytesToPrivateKey,
|
|
1513
|
+
},
|
|
1514
|
+
'secp256k1': {
|
|
1515
|
+
// Wrap the key converter which produces uncompressed public key bytes to produce compressed key bytes as required by the DID DHT spec.
|
|
1516
|
+
// See https://did-dht.com/#representing-keys for more info.
|
|
1517
|
+
publicKeyToBytes: async ({ publicKey }: { publicKey: Jwk }): Promise<Uint8Array> => {
|
|
1518
|
+
const publicKeyBytes = await Secp256k1.publicKeyToBytes({ publicKey });
|
|
1519
|
+
const compressedPublicKey = await Secp256k1.compressPublicKey({ publicKeyBytes });
|
|
1520
|
+
return compressedPublicKey;
|
|
1521
|
+
},
|
|
1522
|
+
bytesToPublicKey : Secp256k1.bytesToPublicKey,
|
|
1523
|
+
privateKeyToBytes : Secp256k1.privateKeyToBytes,
|
|
1524
|
+
bytesToPrivateKey : Secp256k1.bytesToPrivateKey,
|
|
1525
|
+
},
|
|
1526
|
+
X25519: X25519,
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
const converter = converters[curve];
|
|
1530
|
+
|
|
1531
|
+
if (!converter) throw new DidError(DidErrorCode.InvalidPublicKeyType, `Unsupported curve: ${curve}`);
|
|
1532
|
+
|
|
1533
|
+
return converter;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Parses and verifies a BEP44 Get message, converting it to a DNS packet.
|
|
1538
|
+
*
|
|
1539
|
+
* @param params - The parameters to use when verifying and parsing the BEP44 Get response message.
|
|
1540
|
+
* @param params.bep44Message - The BEP44 message to verify and parse.
|
|
1541
|
+
* @returns A promise that resolves to a DNS packet.
|
|
1542
|
+
*/
|
|
1543
|
+
public static async parseBep44GetMessage({ bep44Message }: {
|
|
1544
|
+
bep44Message: Bep44Message;
|
|
1545
|
+
}): Promise<Packet> {
|
|
1546
|
+
// Convert the public key byte array to JWK format.
|
|
1547
|
+
const publicKey = await Ed25519.bytesToPublicKey({ publicKeyBytes: bep44Message.k });
|
|
1548
|
+
|
|
1549
|
+
// Encode the sequence and DNS byte array to bencode format.
|
|
1550
|
+
const bencodedData = bencode.encode({ seq: bep44Message.seq, v: bep44Message.v }).subarray(1, -1);
|
|
1551
|
+
|
|
1552
|
+
// Verify the signature of the BEP44 message.
|
|
1553
|
+
const isValid = await Ed25519.verify({
|
|
1554
|
+
key : publicKey,
|
|
1555
|
+
signature : bep44Message.sig,
|
|
1556
|
+
data : bencodedData
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
if (!isValid) {
|
|
1560
|
+
throw new DidError(DidErrorCode.InvalidSignature, `Invalid signature for DHT BEP44 message`);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
return dnsPacketDecode(bep44Message.v);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* Decodes and parses the data value of a DNS TXT record into a key-value object.
|
|
1568
|
+
*
|
|
1569
|
+
* @param txtData - The data value of a DNS TXT record.
|
|
1570
|
+
* @returns An object containing the key/value pairs of the TXT record data.
|
|
1571
|
+
*/
|
|
1572
|
+
public static parseTxtDataToObject(txtData: TxtData): Record<string, string> {
|
|
1573
|
+
return this.parseTxtDataToString(txtData).split(PROPERTY_SEPARATOR).reduce((acc, pair) => {
|
|
1574
|
+
const [key, value] = pair.split('=');
|
|
1575
|
+
acc[key] = value;
|
|
1576
|
+
return acc;
|
|
1577
|
+
}, {} as Record<string, string>);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* Decodes and parses the data value of a DNS TXT record into a string.
|
|
1582
|
+
*
|
|
1583
|
+
* @param txtData - The data value of a DNS TXT record.
|
|
1584
|
+
* @returns A string representation of the TXT record data.
|
|
1585
|
+
*/
|
|
1586
|
+
public static parseTxtDataToString(txtData: TxtData): string {
|
|
1587
|
+
if (typeof txtData === 'string') {
|
|
1588
|
+
return txtData;
|
|
1589
|
+
} else if (txtData instanceof Uint8Array) {
|
|
1590
|
+
return Convert.uint8Array(txtData).toString();
|
|
1591
|
+
} else if (Array.isArray(txtData)) {
|
|
1592
|
+
return txtData.map(item => this.parseTxtDataToString(item)).join('');
|
|
1593
|
+
} else {
|
|
1594
|
+
throw new DidError(DidErrorCode.InternalError, 'Pkarr returned DNS TXT record with invalid data type');
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/**
|
|
1599
|
+
* Validates the proof of previous DID given.
|
|
1600
|
+
*
|
|
1601
|
+
* @param params - The parameters to validate the previous DID proof.
|
|
1602
|
+
* @param params.newDid - The new DID that the previous DID is linking to.
|
|
1603
|
+
* @param params.previousDidProof - The proof of the previous DID, containing the previous DID and signature signed by the previous DID.
|
|
1604
|
+
*/
|
|
1605
|
+
public static async validatePreviousDidProof({ newDid, previousDidProof }: {
|
|
1606
|
+
newDid: string,
|
|
1607
|
+
previousDidProof: PreviousDidProof,
|
|
1608
|
+
}): Promise<void> {
|
|
1609
|
+
const key = await DidDhtUtils.identifierToIdentityKey({ didUri: previousDidProof.previousDid });
|
|
1610
|
+
const data = DidDhtUtils.identifierToIdentityKeyBytes({ didUri: newDid });
|
|
1611
|
+
const signature = Convert.base64Url(previousDidProof.signature).toUint8Array();
|
|
1612
|
+
const isValid = await Ed25519.verify({ key, data, signature });
|
|
1613
|
+
|
|
1614
|
+
if (!isValid) {
|
|
1615
|
+
throw new DidError(DidErrorCode.InvalidPreviousDidProof, 'The previous DID proof is invalid.');
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* Splits a string into chunks of length 255 if the string exceeds length 255.
|
|
1621
|
+
* @param data - The string to split into chunks.
|
|
1622
|
+
* @returns The original string if its length is less than or equal to 255, otherwise an array of chunked strings.
|
|
1623
|
+
*/
|
|
1624
|
+
public static chunkDataIfNeeded(data: string): string | string[] {
|
|
1625
|
+
if (data.length <= 255) {
|
|
1626
|
+
return data;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// Split the data into chunks of 255 characters.
|
|
1630
|
+
const chunks: string[] = [];
|
|
1631
|
+
for (let i = 0; i < data.length; i += 255) {
|
|
1632
|
+
chunks.push(data.slice(i, i + 255)); // end index is ignored if it exceeds the length of the string
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
return chunks;
|
|
1636
|
+
}
|
|
1637
|
+
}
|