@verii/metadata-registration 1.0.0-pre.1752076816
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 +202 -0
- package/NOTICE +1 -0
- package/README.md +47 -0
- package/index.js +25 -0
- package/package.json +40 -0
- package/src/constants.js +24 -0
- package/src/contracts/metadata-registry.json +38094 -0
- package/src/contracts/revocation-registry.json +19812 -0
- package/src/contracts/verification-coupon.json +27451 -0
- package/src/metadata-registry.js +495 -0
- package/src/revocation-registry.js +185 -0
- package/src/verification-coupon.js +103 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2023 Velocity Team
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { mapWithIndex } = require('@verii/common-functions');
|
|
18
|
+
const {
|
|
19
|
+
initContractClient,
|
|
20
|
+
initContractWithTransactingClient,
|
|
21
|
+
} = require('@verii/base-contract-io');
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
encrypt,
|
|
25
|
+
decrypt,
|
|
26
|
+
get2BytesHash,
|
|
27
|
+
deriveEncryptionSecretFromPassword,
|
|
28
|
+
} = require('@verii/crypto');
|
|
29
|
+
const { jwkFromSecp256k1Key, hexFromJwk } = require('@verii/jwt');
|
|
30
|
+
const { find, flow, isEmpty, last, map, partition } = require('lodash/fp');
|
|
31
|
+
|
|
32
|
+
const contractAbi = require('./contracts/metadata-registry.json');
|
|
33
|
+
const { RESOLUTION_METADATA_ERROR } = require('./constants');
|
|
34
|
+
|
|
35
|
+
const VERSION = '1';
|
|
36
|
+
const ALG_TYPE = 'aes-256-gcm';
|
|
37
|
+
|
|
38
|
+
const initMetadataRegistry = async (
|
|
39
|
+
{ privateKey, contractAddress, rpcProvider },
|
|
40
|
+
context
|
|
41
|
+
) => {
|
|
42
|
+
const { log } = context;
|
|
43
|
+
log.info({ contractAddress }, 'initMetadataRegistry');
|
|
44
|
+
|
|
45
|
+
const { contractClient, pullEvents } = await initContractClient(
|
|
46
|
+
{
|
|
47
|
+
privateKey,
|
|
48
|
+
contractAddress,
|
|
49
|
+
rpcProvider,
|
|
50
|
+
contractAbi,
|
|
51
|
+
},
|
|
52
|
+
context
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// TODO this check should NOT require a contractClient call. All free types should be cached on startup, and reloaded every X (configurable) hours
|
|
56
|
+
const isExistMetadataList = (id, accountId) => {
|
|
57
|
+
log.info({ id, accountId }, 'isExistMetadataList');
|
|
58
|
+
return contractClient.isExistMetadataList(accountId, id);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// TODO this check should NOT require a contractClient call. All free types should be cached on startup, and reloaded every X (configurable) hours
|
|
62
|
+
const isFreeCredentialType = (credentialType) => {
|
|
63
|
+
log.info({ credentialType }, 'isFreeCredentialType');
|
|
64
|
+
return contractClient.isFreeCredentialType(get2BytesHash(credentialType));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const isFreeCredentialTypeList = async (freeCredentialTypesList) => {
|
|
68
|
+
log.info({ freeCredentialTypesList }, 'isFreeCredentialTypeList');
|
|
69
|
+
const checkList = await Promise.all(
|
|
70
|
+
freeCredentialTypesList.map(isFreeCredentialType)
|
|
71
|
+
);
|
|
72
|
+
return checkList.every((check) => check);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const setEntrySigned = async (
|
|
76
|
+
credentialType,
|
|
77
|
+
encryptedPK,
|
|
78
|
+
listId,
|
|
79
|
+
index,
|
|
80
|
+
caoDid
|
|
81
|
+
) => {
|
|
82
|
+
log.info(
|
|
83
|
+
{ credentialType, encryptedPK, listId, index, caoDid },
|
|
84
|
+
'setEntrySigned'
|
|
85
|
+
);
|
|
86
|
+
const { transactingClient, signature } =
|
|
87
|
+
await initContractWithTransactingClient(
|
|
88
|
+
{
|
|
89
|
+
privateKey,
|
|
90
|
+
contractAddress,
|
|
91
|
+
rpcProvider,
|
|
92
|
+
contractAbi,
|
|
93
|
+
},
|
|
94
|
+
context
|
|
95
|
+
);
|
|
96
|
+
const tx = await transactingClient.contractClient.setEntrySigned(
|
|
97
|
+
credentialType,
|
|
98
|
+
encryptedPK,
|
|
99
|
+
listId,
|
|
100
|
+
index,
|
|
101
|
+
context.traceId,
|
|
102
|
+
caoDid,
|
|
103
|
+
signature
|
|
104
|
+
);
|
|
105
|
+
const txResult = await tx.wait();
|
|
106
|
+
log.info({ events: txResult.logs }, 'setEntrySigned Complete');
|
|
107
|
+
return last(txResult.logs).args;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const getFreeEntries = async (indexEntries) => {
|
|
111
|
+
log.info({ indexEntries }, 'getFreeEntries');
|
|
112
|
+
return contractClient.getFreeEntries(indexEntries);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const getPaidEntriesSigned = async (
|
|
116
|
+
indexEntries,
|
|
117
|
+
traceId,
|
|
118
|
+
caoDid,
|
|
119
|
+
burnerDid
|
|
120
|
+
) => {
|
|
121
|
+
log.info(
|
|
122
|
+
{ indexEntries, traceId, caoDid, burnerDid },
|
|
123
|
+
'getPaidEntriesSigned'
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const { transactingClient, signature } =
|
|
127
|
+
await initContractWithTransactingClient(
|
|
128
|
+
{
|
|
129
|
+
privateKey,
|
|
130
|
+
contractAddress,
|
|
131
|
+
rpcProvider,
|
|
132
|
+
contractAbi,
|
|
133
|
+
},
|
|
134
|
+
context
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
await transactingClient.contractClient.getPaidEntriesSigned.staticCall(
|
|
138
|
+
indexEntries,
|
|
139
|
+
traceId,
|
|
140
|
+
caoDid,
|
|
141
|
+
burnerDid,
|
|
142
|
+
signature
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const tx = await transactingClient.contractClient.getPaidEntriesSigned(
|
|
146
|
+
indexEntries,
|
|
147
|
+
traceId,
|
|
148
|
+
caoDid,
|
|
149
|
+
burnerDid,
|
|
150
|
+
signature
|
|
151
|
+
);
|
|
152
|
+
const txResult = await tx.wait();
|
|
153
|
+
return last(txResult.logs)?.args?.credentialMetadataList;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const createCredentialMetadataList = async (
|
|
157
|
+
accountId,
|
|
158
|
+
listId,
|
|
159
|
+
issuerVC,
|
|
160
|
+
caoDid,
|
|
161
|
+
algType = ALG_TYPE,
|
|
162
|
+
version = VERSION
|
|
163
|
+
) => {
|
|
164
|
+
log.info(
|
|
165
|
+
{ listId, issuerVC, caoDid, algType, version },
|
|
166
|
+
'createCredentialMetadataList'
|
|
167
|
+
);
|
|
168
|
+
const { transactingClient, signature } =
|
|
169
|
+
await initContractWithTransactingClient(
|
|
170
|
+
{
|
|
171
|
+
privateKey,
|
|
172
|
+
contractAddress,
|
|
173
|
+
rpcProvider,
|
|
174
|
+
contractAbi,
|
|
175
|
+
},
|
|
176
|
+
context
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const tx = await transactingClient.contractClient.newMetadataListSigned(
|
|
181
|
+
listId,
|
|
182
|
+
get2BytesHash(algType),
|
|
183
|
+
get2BytesHash(version),
|
|
184
|
+
`0x${Buffer.from(issuerVC).toString('hex')}`,
|
|
185
|
+
context.traceId,
|
|
186
|
+
caoDid,
|
|
187
|
+
signature
|
|
188
|
+
);
|
|
189
|
+
await tx.wait();
|
|
190
|
+
return true;
|
|
191
|
+
} catch (creationError) {
|
|
192
|
+
if (!(await isExistMetadataList(listId, accountId))) {
|
|
193
|
+
throw creationError;
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const addCredentialMetadataEntry = async (
|
|
200
|
+
{ listId, index, credentialTypeEncoded, publicKey },
|
|
201
|
+
password,
|
|
202
|
+
caoDid
|
|
203
|
+
) => {
|
|
204
|
+
log.info(
|
|
205
|
+
{ listId, index, credentialTypeEncoded, caoDid, publicKey },
|
|
206
|
+
'addCredentialMetadataEntry'
|
|
207
|
+
);
|
|
208
|
+
const secret = await deriveEncryptionSecretFromPassword(password);
|
|
209
|
+
const encryptedPK = `0x${Buffer.from(
|
|
210
|
+
encrypt(hexFromJwk(publicKey, false), secret),
|
|
211
|
+
'base64'
|
|
212
|
+
).toString('hex')}`;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
await setEntrySigned(
|
|
216
|
+
credentialTypeEncoded,
|
|
217
|
+
encryptedPK,
|
|
218
|
+
listId,
|
|
219
|
+
index,
|
|
220
|
+
caoDid
|
|
221
|
+
);
|
|
222
|
+
return true;
|
|
223
|
+
} catch (e) {
|
|
224
|
+
throw modifySetEntrySignedError(e);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const modifySetEntrySignedError = (e) => {
|
|
229
|
+
let errorCode;
|
|
230
|
+
switch (e.reason) {
|
|
231
|
+
case 'Permissions: primary of operator lacks credential:issue permission':
|
|
232
|
+
errorCode = 'career_issuing_not_permitted';
|
|
233
|
+
break;
|
|
234
|
+
case 'Permissions: primary of operator lacks credential:identityissue permission':
|
|
235
|
+
errorCode = 'identity_issuing_not_permitted';
|
|
236
|
+
break;
|
|
237
|
+
case 'Permissions: primary of operator lacks credential:contactissue permission':
|
|
238
|
+
errorCode = 'contact_issuing_not_permitted';
|
|
239
|
+
break;
|
|
240
|
+
default:
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
// eslint-disable-next-line better-mutation/no-mutation
|
|
244
|
+
e.errorCode = errorCode;
|
|
245
|
+
return e;
|
|
246
|
+
};
|
|
247
|
+
const parseVelocityV2Did = (did) => {
|
|
248
|
+
log.info({ did }, 'parseVelocityV2Did');
|
|
249
|
+
if (!did.startsWith('did:velocity:v2:')) {
|
|
250
|
+
throw new Error(`Wrong did ${did}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const multiToken = ':multi:';
|
|
254
|
+
if (did.indexOf(multiToken) === -1) {
|
|
255
|
+
const [, , , accountId, listId, index, contentHash] = did.split(':');
|
|
256
|
+
return [{ accountId, listId, index, contentHash }];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const [, entriesPart] = did.split(multiToken);
|
|
260
|
+
return map((entryString) => {
|
|
261
|
+
const [accountId, listId, index, contentHash] = entryString.split(':');
|
|
262
|
+
return { accountId, listId, index, contentHash };
|
|
263
|
+
}, entriesPart.split(';'));
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const resolvePublicKey = ({ id, entry, secret }) => {
|
|
267
|
+
log.info({ id, entry, secret }, 'resolvePublicKey');
|
|
268
|
+
const { algType, version } = entry;
|
|
269
|
+
if (
|
|
270
|
+
version !== get2BytesHash(VERSION) ||
|
|
271
|
+
algType !== get2BytesHash(ALG_TYPE)
|
|
272
|
+
) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Unsupported encryption algorithm "${ALG_TYPE}" or version "${VERSION}"`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const encryptedPublicKey = Buffer.from(
|
|
279
|
+
entry.encryptedPublicKey.slice(2),
|
|
280
|
+
'hex'
|
|
281
|
+
).toString('base64');
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const publicKeyJwk = jwkFromSecp256k1Key(
|
|
285
|
+
decrypt(encryptedPublicKey, secret),
|
|
286
|
+
false
|
|
287
|
+
);
|
|
288
|
+
return {
|
|
289
|
+
id: `${id}#key-1`,
|
|
290
|
+
publicKeyJwk,
|
|
291
|
+
};
|
|
292
|
+
} catch (e) {
|
|
293
|
+
log.error({ err: e }, 'resolvePublicKey: DECRYPTION FAIL');
|
|
294
|
+
return {
|
|
295
|
+
id,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const resolveService = ({ id, entry, credentialType }) => {
|
|
301
|
+
log.info({ id, entry, credentialType }, 'resolveService');
|
|
302
|
+
if (
|
|
303
|
+
!credentialType ||
|
|
304
|
+
entry.credentialType !== get2BytesHash(credentialType) // TODO - looks like credential types are stored incorrectly
|
|
305
|
+
) {
|
|
306
|
+
throw new Error(`Invalid hash credentialType "${credentialType}"`);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
id: `${id}#service`,
|
|
310
|
+
credentialType,
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const resolveIssuerVc = ({ id, entry }) => {
|
|
315
|
+
log.info({ id, entry }, 'resolveIssuerVc');
|
|
316
|
+
return {
|
|
317
|
+
id,
|
|
318
|
+
format: 'jwt_vc',
|
|
319
|
+
vc: Buffer.from(entry.issuerVc.slice(2), 'hex').toString(),
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const resolveContractEntries = async ({
|
|
324
|
+
credentials,
|
|
325
|
+
indexEntries,
|
|
326
|
+
traceId,
|
|
327
|
+
caoDid,
|
|
328
|
+
burnerDid,
|
|
329
|
+
}) => {
|
|
330
|
+
log.info(
|
|
331
|
+
{ credentials, indexEntries, traceId, caoDid, burnerDid },
|
|
332
|
+
'resolveContractEntries'
|
|
333
|
+
);
|
|
334
|
+
const isFree = await flow(
|
|
335
|
+
map(({ id, credentialType }) => {
|
|
336
|
+
if (!credentialType) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Could not resolve credential type from VC with ${id}`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
return credentialType;
|
|
342
|
+
}),
|
|
343
|
+
isFreeCredentialTypeList
|
|
344
|
+
)(credentials);
|
|
345
|
+
if (isEmpty(indexEntries)) {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
if (isFree) {
|
|
349
|
+
return getFreeEntries(indexEntries);
|
|
350
|
+
}
|
|
351
|
+
return getPaidEntriesSigned(indexEntries, traceId, caoDid, burnerDid);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const resolveDidDocument = async ({
|
|
355
|
+
did,
|
|
356
|
+
credentials,
|
|
357
|
+
burnerDid,
|
|
358
|
+
caoDid,
|
|
359
|
+
}) => {
|
|
360
|
+
log.info({ did, credentials, burnerDid, caoDid }, 'resolveDidDocument');
|
|
361
|
+
const { traceId } = context;
|
|
362
|
+
const indexEntries = parseVelocityV2Did(did);
|
|
363
|
+
const entries = await resolveContractEntries({
|
|
364
|
+
credentials,
|
|
365
|
+
indexEntries,
|
|
366
|
+
traceId,
|
|
367
|
+
caoDid,
|
|
368
|
+
burnerDid,
|
|
369
|
+
});
|
|
370
|
+
if (isEmpty(entries)) {
|
|
371
|
+
throw new Error(`Entries were not retrieved for ${indexEntries}.`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const credentialEntries = await Promise.all(
|
|
375
|
+
mapWithIndex(async (entry, i) => {
|
|
376
|
+
const id = toDID(indexEntries[i]).toLowerCase();
|
|
377
|
+
const credential = find((c) => c.id.toLowerCase() === id, credentials);
|
|
378
|
+
const secret =
|
|
379
|
+
indexEntries[i].contentHash != null
|
|
380
|
+
? await deriveEncryptionSecretFromPassword(
|
|
381
|
+
indexEntries[i].contentHash
|
|
382
|
+
)
|
|
383
|
+
: await deriveEncryptionSecret(credential);
|
|
384
|
+
return {
|
|
385
|
+
entry,
|
|
386
|
+
id,
|
|
387
|
+
credentialType: credential.credentialType,
|
|
388
|
+
secret,
|
|
389
|
+
};
|
|
390
|
+
}, entries)
|
|
391
|
+
);
|
|
392
|
+
log.info({ credentialEntries }, 'resolveDidDocument 1');
|
|
393
|
+
|
|
394
|
+
const [resolvedPublicKeys, unresolvedPublicKeys] = flow(
|
|
395
|
+
map(resolvePublicKey),
|
|
396
|
+
partition(({ publicKeyJwk }) => !!publicKeyJwk)
|
|
397
|
+
)(credentialEntries);
|
|
398
|
+
|
|
399
|
+
const service = map(resolveService, credentialEntries);
|
|
400
|
+
|
|
401
|
+
const didDocument = {
|
|
402
|
+
id: did,
|
|
403
|
+
publicKey: resolvedPublicKeys,
|
|
404
|
+
service,
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const didDocumentMetadata = {
|
|
408
|
+
boundIssuerVcs: map(resolveIssuerVc, credentialEntries),
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
log.info(
|
|
412
|
+
{ didDocument, didDocumentMetadata, unresolvedPublicKeys },
|
|
413
|
+
'resolveDidDocument 3'
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
if (isEmpty(unresolvedPublicKeys)) {
|
|
417
|
+
return {
|
|
418
|
+
didDocument,
|
|
419
|
+
didDocumentMetadata,
|
|
420
|
+
didResolutionMetadata: {},
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const didResolutionMetadata = {
|
|
425
|
+
error: RESOLUTION_METADATA_ERROR.UNRESOLVED_MULTI_DID_ENTRIES,
|
|
426
|
+
unresolvedMultiDidEntries: map(
|
|
427
|
+
({ id }) => ({
|
|
428
|
+
id,
|
|
429
|
+
error: RESOLUTION_METADATA_ERROR.DATA_INTEGRITY_ERROR,
|
|
430
|
+
}),
|
|
431
|
+
unresolvedPublicKeys
|
|
432
|
+
),
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
log.error({ didResolutionMetadata }, 'Unable to resolve did entries');
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
didDocument,
|
|
439
|
+
didDocumentMetadata,
|
|
440
|
+
didResolutionMetadata,
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const setPermissionsAddress = async (permissionsContractAddress) => {
|
|
445
|
+
const tx = await contractClient.setPermissionsAddress(
|
|
446
|
+
permissionsContractAddress
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
return tx.wait();
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const toDID = ({ accountId, listId, index, contentHash }) => {
|
|
453
|
+
if (contentHash != null) {
|
|
454
|
+
return `did:velocity:v2:${accountId}:${listId}:${index}:${contentHash}`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return `did:velocity:v2:${accountId}:${listId}:${index}`;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const pullCreatedMetadataListEvents = pullEvents('CreatedMetadataList');
|
|
461
|
+
|
|
462
|
+
const pullAddedCredentialMetadataEvents = pullEvents(
|
|
463
|
+
'AddedCredentialMetadata'
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
createCredentialMetadataList,
|
|
468
|
+
addCredentialMetadataEntry,
|
|
469
|
+
contractClient,
|
|
470
|
+
isFreeCredentialTypeList,
|
|
471
|
+
isFreeCredentialType,
|
|
472
|
+
isExistMetadataList,
|
|
473
|
+
getPaidEntriesSigned,
|
|
474
|
+
getFreeEntries,
|
|
475
|
+
setEntrySigned,
|
|
476
|
+
resolveContractEntries,
|
|
477
|
+
resolveDidDocument,
|
|
478
|
+
setPermissionsAddress,
|
|
479
|
+
pullCreatedMetadataListEvents,
|
|
480
|
+
pullAddedCredentialMetadataEvents,
|
|
481
|
+
parseVelocityV2Did,
|
|
482
|
+
};
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const deriveEncryptionSecret = async (credential) => {
|
|
486
|
+
const contentHash = credential?.contentHash;
|
|
487
|
+
if (!contentHash) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Could not resolve content hash from VC with ${credential.id}`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
return deriveEncryptionSecretFromPassword(contentHash);
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
module.exports = initMetadataRegistry;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2023 Velocity Team
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/* eslint-disable camelcase */
|
|
18
|
+
const ethUrlParser = require('eth-url-parser');
|
|
19
|
+
const {
|
|
20
|
+
initContractClient,
|
|
21
|
+
initContractWithTransactingClient,
|
|
22
|
+
} = require('@verii/base-contract-io');
|
|
23
|
+
|
|
24
|
+
const contractAbi = require('./contracts/revocation-registry.json');
|
|
25
|
+
|
|
26
|
+
const initRevocationRegistry = async (
|
|
27
|
+
{ privateKey, contractAddress, rpcProvider },
|
|
28
|
+
context
|
|
29
|
+
) => {
|
|
30
|
+
const { log } = context;
|
|
31
|
+
log.info({ privateKey, contractAddress }, 'initRevocationRegistry');
|
|
32
|
+
|
|
33
|
+
const { contractClient, pullEvents } = await initContractClient(
|
|
34
|
+
{
|
|
35
|
+
privateKey,
|
|
36
|
+
contractAddress,
|
|
37
|
+
rpcProvider,
|
|
38
|
+
contractAbi,
|
|
39
|
+
},
|
|
40
|
+
context
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const addWalletToRegistrySigned = async ({ caoDid }) => {
|
|
44
|
+
log.info({ caoDid }, 'addWalletToRegistrySigned');
|
|
45
|
+
const { traceId } = context;
|
|
46
|
+
const { transactingClient, signature } =
|
|
47
|
+
await initContractWithTransactingClient(
|
|
48
|
+
{
|
|
49
|
+
privateKey,
|
|
50
|
+
contractAddress,
|
|
51
|
+
rpcProvider,
|
|
52
|
+
contractAbi,
|
|
53
|
+
},
|
|
54
|
+
context
|
|
55
|
+
);
|
|
56
|
+
const tx = await transactingClient.contractClient.addWalletSigned(
|
|
57
|
+
traceId,
|
|
58
|
+
caoDid,
|
|
59
|
+
signature
|
|
60
|
+
);
|
|
61
|
+
const txResult = await tx.wait();
|
|
62
|
+
return { txResult };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const addRevocationListSigned = async (listId, caoDid) => {
|
|
66
|
+
log.info({ listId, caoDid }, 'addRevocationListSigned');
|
|
67
|
+
const { traceId } = context;
|
|
68
|
+
const { transactingClient, signature } =
|
|
69
|
+
await initContractWithTransactingClient(
|
|
70
|
+
{
|
|
71
|
+
privateKey,
|
|
72
|
+
contractAddress,
|
|
73
|
+
rpcProvider,
|
|
74
|
+
contractAbi,
|
|
75
|
+
},
|
|
76
|
+
context
|
|
77
|
+
);
|
|
78
|
+
const tx = await transactingClient.contractClient.addRevocationListSigned(
|
|
79
|
+
listId,
|
|
80
|
+
traceId,
|
|
81
|
+
caoDid,
|
|
82
|
+
signature
|
|
83
|
+
);
|
|
84
|
+
const txResult = await tx.wait();
|
|
85
|
+
return { txResult };
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getRevokeUrl = (accountId, listId, index) => {
|
|
89
|
+
return ethUrlParser.build({
|
|
90
|
+
scheme: 'ethereum',
|
|
91
|
+
target_address: contractAddress,
|
|
92
|
+
function_name: 'getRevokedStatus',
|
|
93
|
+
parameters: {
|
|
94
|
+
address: accountId,
|
|
95
|
+
listId,
|
|
96
|
+
index,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const setRevokedStatusSigned = async ({
|
|
102
|
+
accountId,
|
|
103
|
+
listId,
|
|
104
|
+
index,
|
|
105
|
+
caoDid,
|
|
106
|
+
}) => {
|
|
107
|
+
log.info({ listId, index }, 'setRevokedStatusSigned');
|
|
108
|
+
|
|
109
|
+
const { traceId } = context;
|
|
110
|
+
const { transactingClient, signature } =
|
|
111
|
+
await initContractWithTransactingClient(
|
|
112
|
+
{
|
|
113
|
+
privateKey,
|
|
114
|
+
contractAddress,
|
|
115
|
+
rpcProvider,
|
|
116
|
+
contractAbi,
|
|
117
|
+
},
|
|
118
|
+
context
|
|
119
|
+
);
|
|
120
|
+
const tx = await transactingClient.contractClient.setRevokedStatusSigned(
|
|
121
|
+
listId,
|
|
122
|
+
index,
|
|
123
|
+
traceId,
|
|
124
|
+
caoDid,
|
|
125
|
+
signature
|
|
126
|
+
);
|
|
127
|
+
const txResult = await tx.wait();
|
|
128
|
+
return {
|
|
129
|
+
url: getRevokeUrl(accountId, listId, index),
|
|
130
|
+
txResult,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const getRevokedStatus = (url) => {
|
|
135
|
+
log.info({ url }, 'getRevokedStatus');
|
|
136
|
+
|
|
137
|
+
const {
|
|
138
|
+
scheme,
|
|
139
|
+
target_address,
|
|
140
|
+
function_name,
|
|
141
|
+
parameters: { address, listId, index },
|
|
142
|
+
} = ethUrlParser.parse(url);
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
target_address !== contractAddress ||
|
|
146
|
+
function_name !== 'getRevokedStatus' ||
|
|
147
|
+
scheme !== 'ethereum'
|
|
148
|
+
) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
'Wrong url, please check the params: scheme, target_address, function_name'
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return contractClient.getRevokedStatus(address, listId, index);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const setPermissionsAddress = async (permissionsContractAddress) => {
|
|
158
|
+
const tx = await contractClient.setPermissionsAddress(
|
|
159
|
+
permissionsContractAddress
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return tx.wait();
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const pullWalletAddedEvents = pullEvents('WalletAdded');
|
|
166
|
+
|
|
167
|
+
const pullRevocationListCreateEvents = pullEvents('RevocationListCreate');
|
|
168
|
+
|
|
169
|
+
const pullRevokedStatusUpdateEvents = pullEvents('RevokedStatusUpdate');
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
contractClient,
|
|
173
|
+
addWalletToRegistrySigned,
|
|
174
|
+
addRevocationListSigned,
|
|
175
|
+
setRevokedStatusSigned,
|
|
176
|
+
getRevokedStatus,
|
|
177
|
+
getRevokeUrl,
|
|
178
|
+
setPermissionsAddress,
|
|
179
|
+
pullWalletAddedEvents,
|
|
180
|
+
pullRevocationListCreateEvents,
|
|
181
|
+
pullRevokedStatusUpdateEvents,
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
module.exports = initRevocationRegistry;
|