-
Notifications
You must be signed in to change notification settings - Fork 14
/
IonRequest.ts
349 lines (286 loc) · 12.2 KB
/
IonRequest.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
import * as URI from 'uri-js';
import ErrorCode from './ErrorCode';
import ISigner from './interfaces/ISigner';
import InputValidator from './InputValidator';
import IonCreateRequestModel from './models/IonCreateRequestModel';
import IonDeactivateRequestModel from './models/IonDeactivateRequestModel';
import IonDocumentModel from './models/IonDocumentModel';
import IonError from './IonError';
import IonPublicKeyModel from './models/IonPublicKeyModel';
import IonRecoverRequestModel from './models/IonRecoverRequestModel';
import IonSdkConfig from './IonSdkConfig';
import IonServiceModel from './models/IonServiceModel';
import IonUpdateRequestModel from './models/IonUpdateRequestModel';
import JsonCanonicalizer from './JsonCanonicalizer';
import JwkEs256k from './models/JwkEs256k';
import Multihash from './Multihash';
import OperationKeyType from './enums/OperationKeyType';
import OperationType from './enums/OperationType';
import PatchAction from './enums/PatchAction';
/**
* Class containing operations related to ION requests.
*/
export default class IonRequest {
/**
* Creates an ION DID create request.
* @param input.document The initial state to be associate with the ION DID to be created using a `replace` document patch action.
*/
public static async createCreateRequest (input: {
recoveryKey: JwkEs256k;
updateKey: JwkEs256k;
document: IonDocumentModel;
}): Promise<IonCreateRequestModel> {
const recoveryKey = input.recoveryKey;
const updateKey = input.updateKey;
const didDocumentKeys = input.document.publicKeys;
const services = input.document.services;
// Validate recovery and update public keys.
InputValidator.validateEs256kOperationKey(recoveryKey, OperationKeyType.Public);
InputValidator.validateEs256kOperationKey(updateKey, OperationKeyType.Public);
// Validate all given DID Document keys.
IonRequest.validateDidDocumentKeys(didDocumentKeys);
// Validate all given service.
IonRequest.validateServices(services);
const hashAlgorithmInMultihashCode = IonSdkConfig.hashAlgorithmInMultihashCode;
const patches = [{
action: PatchAction.Replace,
document: input.document
}];
const delta = {
updateCommitment: await Multihash.canonicalizeThenDoubleHashThenEncode(updateKey, hashAlgorithmInMultihashCode),
patches
};
IonRequest.validateDeltaSize(delta);
const deltaHash = await Multihash.canonicalizeThenHashThenEncode(delta, hashAlgorithmInMultihashCode);
const suffixData = {
deltaHash,
recoveryCommitment: await Multihash.canonicalizeThenDoubleHashThenEncode(recoveryKey, hashAlgorithmInMultihashCode)
};
const operationRequest = {
type: OperationType.Create,
suffixData: suffixData,
delta: delta
};
return operationRequest;
}
public static async createDeactivateRequest (input: {
didSuffix: string,
recoveryPublicKey: JwkEs256k,
signer: ISigner
}): Promise<IonDeactivateRequestModel> {
// Validate DID suffix
IonRequest.validateDidSuffix(input.didSuffix);
// Validates recovery public key
InputValidator.validateEs256kOperationKey(input.recoveryPublicKey, OperationKeyType.Public);
const hashAlgorithmInMultihashCode = IonSdkConfig.hashAlgorithmInMultihashCode;
const revealValue = await Multihash.canonicalizeThenHashThenEncode(input.recoveryPublicKey, hashAlgorithmInMultihashCode);
const dataToBeSigned = {
didSuffix: input.didSuffix,
recoveryKey: input.recoveryPublicKey
};
const compactJws = await input.signer.sign({ alg: 'ES256K' }, dataToBeSigned);
return {
type: OperationType.Deactivate,
didSuffix: input.didSuffix,
revealValue: revealValue,
signedData: compactJws
};
}
public static async createRecoverRequest (input: {
didSuffix: string,
recoveryPublicKey: JwkEs256k,
nextRecoveryPublicKey: JwkEs256k,
nextUpdatePublicKey: JwkEs256k,
document: IonDocumentModel,
signer: ISigner
}): Promise<IonRecoverRequestModel> {
// Validate DID suffix
IonRequest.validateDidSuffix(input.didSuffix);
// Validate recovery public key
InputValidator.validateEs256kOperationKey(input.recoveryPublicKey, OperationKeyType.Public);
// Validate next recovery public key
InputValidator.validateEs256kOperationKey(input.nextRecoveryPublicKey, OperationKeyType.Public);
// Validate next update public key
InputValidator.validateEs256kOperationKey(input.nextUpdatePublicKey, OperationKeyType.Public);
// Validate all given DID Document keys.
IonRequest.validateDidDocumentKeys(input.document.publicKeys);
// Validate all given service.
IonRequest.validateServices(input.document.services);
const hashAlgorithmInMultihashCode = IonSdkConfig.hashAlgorithmInMultihashCode;
const revealValue = await Multihash.canonicalizeThenHashThenEncode(input.recoveryPublicKey, hashAlgorithmInMultihashCode);
const patches = [{
action: PatchAction.Replace,
document: input.document
}];
const nextUpdateCommitmentHash = await Multihash.canonicalizeThenDoubleHashThenEncode(input.nextUpdatePublicKey, hashAlgorithmInMultihashCode);
const delta = {
patches,
updateCommitment: nextUpdateCommitmentHash
};
const deltaHash = await Multihash.canonicalizeThenHashThenEncode(delta, hashAlgorithmInMultihashCode);
const nextRecoveryCommitmentHash = await Multihash.canonicalizeThenDoubleHashThenEncode(input.nextRecoveryPublicKey, hashAlgorithmInMultihashCode);
const dataToBeSigned = {
recoveryCommitment: nextRecoveryCommitmentHash,
recoveryKey: input.recoveryPublicKey,
deltaHash: deltaHash
};
const compactJws = await input.signer.sign({ alg: 'ES256K' }, dataToBeSigned);
return {
type: OperationType.Recover,
didSuffix: input.didSuffix,
revealValue: revealValue,
delta: delta,
signedData: compactJws
};
}
public static async createUpdateRequest (input: {
didSuffix: string;
updatePublicKey: JwkEs256k;
nextUpdatePublicKey: JwkEs256k;
signer: ISigner;
servicesToAdd?: IonServiceModel[];
idsOfServicesToRemove?: string[];
publicKeysToAdd?: IonPublicKeyModel[];
idsOfPublicKeysToRemove?: string[];
}): Promise<IonUpdateRequestModel> {
// Validate DID suffix
IonRequest.validateDidSuffix(input.didSuffix);
// Validate update public key
InputValidator.validateEs256kOperationKey(input.updatePublicKey, OperationKeyType.Public);
// Validate next update public key
InputValidator.validateEs256kOperationKey(input.nextUpdatePublicKey, OperationKeyType.Public);
// Validate all given service.
IonRequest.validateServices(input.servicesToAdd);
// Validate all given DID Document keys.
IonRequest.validateDidDocumentKeys(input.publicKeysToAdd);
// Validate all given service id to remove.
if (input.idsOfServicesToRemove !== undefined) {
for (const id of input.idsOfServicesToRemove) {
InputValidator.validateId(id);
}
}
// Validate all given public key id to remove.
if (input.idsOfPublicKeysToRemove !== undefined) {
for (const id of input.idsOfPublicKeysToRemove) {
InputValidator.validateId(id);
}
}
const patches = [];
// Create patches for add services
const servicesToAdd = input.servicesToAdd;
if (servicesToAdd !== undefined && servicesToAdd.length > 0) {
const patch = {
action: PatchAction.AddServices,
services: servicesToAdd
};
patches.push(patch);
}
// Create patches for remove services
const idsOfServicesToRemove = input.idsOfServicesToRemove;
if (idsOfServicesToRemove !== undefined && idsOfServicesToRemove.length > 0) {
const patch = {
action: PatchAction.RemoveServices,
ids: idsOfServicesToRemove
};
patches.push(patch);
}
// Create patches for adding public keys
const publicKeysToAdd = input.publicKeysToAdd;
if (publicKeysToAdd !== undefined && publicKeysToAdd.length > 0) {
const patch = {
action: PatchAction.AddPublicKeys,
publicKeys: publicKeysToAdd
};
patches.push(patch);
}
// Create patch for removing public keys
const idsOfPublicKeysToRemove = input.idsOfPublicKeysToRemove;
if (idsOfPublicKeysToRemove !== undefined && idsOfPublicKeysToRemove.length > 0) {
const patch = {
action: PatchAction.RemovePublicKeys,
ids: idsOfPublicKeysToRemove
};
patches.push(patch);
}
const hashAlgorithmInMultihashCode = IonSdkConfig.hashAlgorithmInMultihashCode;
const revealValue = await Multihash.canonicalizeThenHashThenEncode(input.updatePublicKey, hashAlgorithmInMultihashCode);
const nextUpdateCommitmentHash = await Multihash.canonicalizeThenDoubleHashThenEncode(input.nextUpdatePublicKey, hashAlgorithmInMultihashCode);
const delta = {
patches,
updateCommitment: nextUpdateCommitmentHash
};
const deltaHash = await Multihash.canonicalizeThenHashThenEncode(delta, hashAlgorithmInMultihashCode);
const dataToBeSigned = {
updateKey: input.updatePublicKey,
deltaHash
};
const compactJws = await input.signer.sign({ alg: 'ES256K' }, dataToBeSigned);
return {
type: OperationType.Update,
didSuffix: input.didSuffix,
revealValue,
delta,
signedData: compactJws
};
}
private static validateDidSuffix (didSuffix: string) {
Multihash.validateEncodedHashComputedUsingSupportedHashAlgorithm(didSuffix, 'didSuffix');
}
private static validateDidDocumentKeys (publicKeys?: IonPublicKeyModel[]) {
if (publicKeys === undefined) {
return;
}
// Validate each public key.
const publicKeyIdSet: Set<string> = new Set();
for (const publicKey of publicKeys) {
if (Array.isArray(publicKey.publicKeyJwk)) {
throw new IonError(ErrorCode.DidDocumentPublicKeyMissingOrIncorrectType, `DID Document key 'publicKeyJwk' property is not a non-array object.`);
}
InputValidator.validateId(publicKey.id);
// 'id' must be unique across all given keys.
if (publicKeyIdSet.has(publicKey.id)) {
throw new IonError(ErrorCode.DidDocumentPublicKeyIdDuplicated, `DID Document key with ID '${publicKey.id}' already exists.`);
}
publicKeyIdSet.add(publicKey.id);
InputValidator.validatePublicKeyPurposes(publicKey.purposes);
}
}
private static validateServices (services?: IonServiceModel[]) {
if (services !== undefined && services.length !== 0) {
const serviceIdSet: Set<string> = new Set();
for (const service of services) {
IonRequest.validateService(service);
if (serviceIdSet.has(service.id)) {
throw new IonError(ErrorCode.DidDocumentServiceIdDuplicated, 'Service id has to be unique');
}
serviceIdSet.add(service.id);
}
}
}
private static validateService (service: IonServiceModel) {
InputValidator.validateId(service.id);
const maxTypeLength = 30;
if (service.type.length > maxTypeLength) {
const errorMessage = `Service endpoint type length ${service.type.length} exceeds max allowed length of ${maxTypeLength}.`;
throw new IonError(ErrorCode.ServiceTypeTooLong, errorMessage);
}
// Throw error if `serviceEndpoint` is an array.
if (Array.isArray(service.serviceEndpoint)) {
const errorMessage = 'Service endpoint value cannot be an array.';
throw new IonError(ErrorCode.ServiceEndpointCannotBeAnArray, errorMessage);
}
if (typeof service.serviceEndpoint === 'string') {
const uri = URI.parse(service.serviceEndpoint);
if (uri.error !== undefined) {
throw new IonError(ErrorCode.ServiceEndpointStringNotValidUri, `Service endpoint string '${service.serviceEndpoint}' is not a URI.`);
}
}
}
private static validateDeltaSize (delta: object) {
const deltaBytes = JsonCanonicalizer.canonicalizeAsBytes(delta);
if (deltaBytes.length > IonSdkConfig.maxCanonicalizedDeltaSizeInBytes) {
const errorMessage = `Delta of ${deltaBytes.length} bytes exceeded limit of ${IonSdkConfig.maxCanonicalizedDeltaSizeInBytes} bytes.`;
throw new IonError(ErrorCode.DeltaExceedsMaximumSize, errorMessage);
}
}
}