@devtion/actions 0.0.0-09f6b45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/index.mjs +2593 -0
  4. package/dist/index.node.js +2699 -0
  5. package/dist/types/hardhat.config.d.ts +6 -0
  6. package/dist/types/hardhat.config.d.ts.map +1 -0
  7. package/dist/types/src/helpers/authentication.d.ts +21 -0
  8. package/dist/types/src/helpers/authentication.d.ts.map +1 -0
  9. package/dist/types/src/helpers/constants.d.ts +200 -0
  10. package/dist/types/src/helpers/constants.d.ts.map +1 -0
  11. package/dist/types/src/helpers/contracts.d.ts +57 -0
  12. package/dist/types/src/helpers/contracts.d.ts.map +1 -0
  13. package/dist/types/src/helpers/crypto.d.ts +27 -0
  14. package/dist/types/src/helpers/crypto.d.ts.map +1 -0
  15. package/dist/types/src/helpers/database.d.ts +105 -0
  16. package/dist/types/src/helpers/database.d.ts.map +1 -0
  17. package/dist/types/src/helpers/functions.d.ts +145 -0
  18. package/dist/types/src/helpers/functions.d.ts.map +1 -0
  19. package/dist/types/src/helpers/security.d.ts +10 -0
  20. package/dist/types/src/helpers/security.d.ts.map +1 -0
  21. package/dist/types/src/helpers/services.d.ts +38 -0
  22. package/dist/types/src/helpers/services.d.ts.map +1 -0
  23. package/dist/types/src/helpers/storage.d.ts +124 -0
  24. package/dist/types/src/helpers/storage.d.ts.map +1 -0
  25. package/dist/types/src/helpers/tasks.d.ts +2 -0
  26. package/dist/types/src/helpers/tasks.d.ts.map +1 -0
  27. package/dist/types/src/helpers/utils.d.ts +139 -0
  28. package/dist/types/src/helpers/utils.d.ts.map +1 -0
  29. package/dist/types/src/helpers/verification.d.ts +95 -0
  30. package/dist/types/src/helpers/verification.d.ts.map +1 -0
  31. package/dist/types/src/helpers/vm.d.ts +112 -0
  32. package/dist/types/src/helpers/vm.d.ts.map +1 -0
  33. package/dist/types/src/index.d.ts +15 -0
  34. package/dist/types/src/index.d.ts.map +1 -0
  35. package/dist/types/src/types/enums.d.ts +133 -0
  36. package/dist/types/src/types/enums.d.ts.map +1 -0
  37. package/dist/types/src/types/index.d.ts +603 -0
  38. package/dist/types/src/types/index.d.ts.map +1 -0
  39. package/package.json +83 -0
  40. package/src/helpers/authentication.ts +37 -0
  41. package/src/helpers/constants.ts +318 -0
  42. package/src/helpers/contracts.ts +268 -0
  43. package/src/helpers/crypto.ts +55 -0
  44. package/src/helpers/database.ts +221 -0
  45. package/src/helpers/functions.ts +438 -0
  46. package/src/helpers/security.ts +67 -0
  47. package/src/helpers/services.ts +83 -0
  48. package/src/helpers/storage.ts +341 -0
  49. package/src/helpers/tasks.ts +56 -0
  50. package/src/helpers/utils.ts +737 -0
  51. package/src/helpers/verification.ts +354 -0
  52. package/src/helpers/vm.ts +398 -0
  53. package/src/index.ts +162 -0
  54. package/src/types/enums.ts +141 -0
  55. package/src/types/index.ts +654 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,2593 @@
1
+ /**
2
+ * @module @p0tion/actions
3
+ * @version 1.1.0
4
+ * @file A set of actions and helpers for CLI commands
5
+ * @copyright Ethereum Foundation 2022
6
+ * @license MIT
7
+ * @see [Github]{@link https://github.com/privacy-scaling-explorations/p0tion}
8
+ */
9
+ import mime from 'mime-types';
10
+ import fs, { createWriteStream } from 'fs';
11
+ import fetch from '@adobe/node-fetch-retry';
12
+ import https from 'https';
13
+ import { httpsCallable, httpsCallableFromURL, getFunctions } from 'firebase/functions';
14
+ import { onSnapshot, query, collection, getDocs, doc, getDoc, where, Timestamp, getFirestore } from 'firebase/firestore';
15
+ import { zKey, groth16 } from 'snarkjs';
16
+ import crypto from 'crypto';
17
+ import blake from 'blakejs';
18
+ import { utils } from 'ffjavascript';
19
+ import winston from 'winston';
20
+ import { pipeline } from 'stream';
21
+ import { promisify } from 'util';
22
+ import { initializeApp } from 'firebase/app';
23
+ import { signInWithCredential, initializeAuth, getAuth } from 'firebase/auth';
24
+ import { ContractFactory } from 'ethers';
25
+ import solc from 'solc';
26
+ import { EC2Client, RunInstancesCommand, DescribeInstanceStatusCommand, StartInstancesCommand, StopInstancesCommand, TerminateInstancesCommand } from '@aws-sdk/client-ec2';
27
+ import { SSMClient, SendCommandCommand, GetCommandInvocationCommand } from '@aws-sdk/client-ssm';
28
+ import dotenv from 'dotenv';
29
+
30
+ // Main part for the Hermez Phase 1 Trusted Setup URLs to download PoT files.
31
+ const potFileDownloadMainUrl = `https://hermez.s3-eu-west-1.amazonaws.com/`;
32
+ // Main part for the Hermez Phase 1 Trusted Setup PoT files to be downloaded.
33
+ const potFilenameTemplate = `powersOfTau28_hez_final_`;
34
+ // The genesis zKey index.
35
+ const genesisZkeyIndex = `00000`;
36
+ // The number of exponential iterations to be executed by SnarkJS when finalizing the ceremony.
37
+ const numExpIterations = 10;
38
+ // The Solidity version of the Verifier Smart Contract generated with SnarkJS when finalizing the ceremony.
39
+ const solidityVersion = "0.8.0";
40
+ // The index of the final zKey.
41
+ const finalContributionIndex = "final";
42
+ // The acronym for verification key.
43
+ const verificationKeyAcronym = "vkey";
44
+ // The acronym for Verifier smart contract.
45
+ const verifierSmartContractAcronym = "verifier";
46
+ // The tag for ec2 instances.
47
+ const ec2InstanceTag = "p0tionec2instance";
48
+ // The name of the VM startup script file.
49
+ const vmBootstrapScriptFilename = "bootstrap.sh";
50
+ /**
51
+ * Define the supported VM configuration types.
52
+ * @dev the VM configurations can be retrieved at https://aws.amazon.com/ec2/instance-types/
53
+ * The on-demand prices for the configurations can be retrieved at https://aws.amazon.com/ec2/pricing/on-demand/.
54
+ * @notice the price has to be intended as on-demand hourly billing usage for Linux OS
55
+ * VMs located in the us-east-1 region expressed in USD.
56
+ */
57
+ const vmConfigurationTypes = {
58
+ t3_large: {
59
+ type: "t3.large",
60
+ ram: 8,
61
+ vcpu: 2,
62
+ pricePerHour: 0.08352
63
+ },
64
+ t3_2xlarge: {
65
+ type: "t3.2xlarge",
66
+ ram: 32,
67
+ vcpu: 8,
68
+ pricePerHour: 0.3328
69
+ },
70
+ c5_9xlarge: {
71
+ type: "c5.9xlarge",
72
+ ram: 72,
73
+ vcpu: 36,
74
+ pricePerHour: 1.53
75
+ },
76
+ c5_18xlarge: {
77
+ type: "c5.18xlarge",
78
+ ram: 144,
79
+ vcpu: 72,
80
+ pricePerHour: 3.06
81
+ },
82
+ c5a_8xlarge: {
83
+ type: "c5a.8xlarge",
84
+ ram: 64,
85
+ vcpu: 32,
86
+ pricePerHour: 1.232
87
+ },
88
+ c6id_32xlarge: {
89
+ type: "c6id.32xlarge",
90
+ ram: 256,
91
+ vcpu: 128,
92
+ pricePerHour: 6.4512
93
+ },
94
+ m6a_32xlarge: {
95
+ type: "m6a.32xlarge",
96
+ ram: 512,
97
+ vcpu: 128,
98
+ pricePerHour: 5.5296
99
+ }
100
+ };
101
+ /**
102
+ * Define the PPoT Trusted Setup ceremony output powers of tau files size (in GB).
103
+ * @dev the powers of tau files can be retrieved at https://github.com/weijiekoh/perpetualpowersoftau
104
+ */
105
+ const powersOfTauFiles = [
106
+ {
107
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_01.ptau",
108
+ size: 0.000084
109
+ },
110
+ {
111
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_02.ptau",
112
+ size: 0.000086
113
+ },
114
+ {
115
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_03.ptau",
116
+ size: 0.000091
117
+ },
118
+ {
119
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_04.ptau",
120
+ size: 0.0001
121
+ },
122
+ {
123
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_05.ptau",
124
+ size: 0.000117
125
+ },
126
+ {
127
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_06.ptau",
128
+ size: 0.000153
129
+ },
130
+ {
131
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_07.ptau",
132
+ size: 0.000225
133
+ },
134
+ {
135
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_08.ptau",
136
+ size: 0.0004
137
+ },
138
+ {
139
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_09.ptau",
140
+ size: 0.000658
141
+ },
142
+ {
143
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_10.ptau",
144
+ size: 0.0013
145
+ },
146
+ {
147
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_11.ptau",
148
+ size: 0.0023
149
+ },
150
+ {
151
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_12.ptau",
152
+ size: 0.0046
153
+ },
154
+ {
155
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_13.ptau",
156
+ size: 0.0091
157
+ },
158
+ {
159
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_14.ptau",
160
+ size: 0.0181
161
+ },
162
+ {
163
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_15.ptau",
164
+ size: 0.0361
165
+ },
166
+ {
167
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_16.ptau",
168
+ size: 0.0721
169
+ },
170
+ {
171
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_17.ptau",
172
+ size: 0.144
173
+ },
174
+ {
175
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_18.ptau",
176
+ size: 0.288
177
+ },
178
+ {
179
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_19.ptau",
180
+ size: 0.576
181
+ },
182
+ {
183
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_20.ptau",
184
+ size: 1.1
185
+ },
186
+ {
187
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_21.ptau",
188
+ size: 2.3
189
+ },
190
+ {
191
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_22.ptau",
192
+ size: 4.5
193
+ },
194
+ {
195
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_23.ptau",
196
+ size: 9.0
197
+ },
198
+ {
199
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_24.ptau",
200
+ size: 18.0
201
+ },
202
+ {
203
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_25.ptau",
204
+ size: 36.0
205
+ },
206
+ {
207
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_26.ptau",
208
+ size: 72.0
209
+ },
210
+ {
211
+ ref: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_27.ptau",
212
+ size: 144.0
213
+ }
214
+ ];
215
+ /**
216
+ * Commonly used terms.
217
+ * @dev useful for creating paths, references to collections and queries, object properties, folder names, and so on.
218
+ */
219
+ const commonTerms = {
220
+ collections: {
221
+ users: {
222
+ name: "users",
223
+ fields: {
224
+ creationTime: "creationTime",
225
+ displayName: "displayName",
226
+ email: "email",
227
+ emailVerified: "emailVerified",
228
+ lastSignInTime: "lastSignInTime",
229
+ lastUpdated: "lastUpdated",
230
+ name: "name",
231
+ photoURL: "photoURL"
232
+ }
233
+ },
234
+ participants: {
235
+ name: "participants",
236
+ fields: {
237
+ contributionProgress: "contributionProgress",
238
+ contributionStartedAt: "contributionStartedAt",
239
+ contributionStep: "contributionStep",
240
+ contributions: "contributions",
241
+ lastUpdated: "lastUpdated",
242
+ status: "status",
243
+ verificationStartedAt: "verificationStartedAt"
244
+ }
245
+ },
246
+ avatars: {
247
+ name: "avatars",
248
+ fields: {
249
+ avatarUrl: "avatarUrl"
250
+ }
251
+ },
252
+ ceremonies: {
253
+ name: "ceremonies",
254
+ fields: {
255
+ coordinatorId: "coordinatorId",
256
+ description: "description",
257
+ endDate: "endDate",
258
+ lastUpdated: "lastUpdated",
259
+ penalty: "penalty",
260
+ prefix: "prefix",
261
+ startDate: "startDate",
262
+ state: "state",
263
+ timeoutType: "timeoutType",
264
+ title: "title",
265
+ type: "type"
266
+ }
267
+ },
268
+ circuits: {
269
+ name: "circuits",
270
+ fields: {
271
+ avgTimings: "avgTimings",
272
+ compiler: "compiler",
273
+ description: "description",
274
+ files: "files",
275
+ lastUpdated: "lastUpdated",
276
+ metadata: "metadata",
277
+ name: "name",
278
+ prefix: "prefix",
279
+ sequencePosition: "sequencePosition",
280
+ template: "template",
281
+ timeoutMaxContributionWaitingTime: "timeoutMaxContributionWaitingTime",
282
+ waitingQueue: "waitingQueue",
283
+ zKeySizeInBytes: "zKeySizeInBytes",
284
+ verification: "verification"
285
+ }
286
+ },
287
+ contributions: {
288
+ name: "contributions",
289
+ fields: {
290
+ contributionComputationTime: "contributionComputationTime",
291
+ files: "files",
292
+ lastUpdated: "lastUpdated",
293
+ participantId: "participantId",
294
+ valid: "valid",
295
+ verificationComputationTime: "verificationComputationTime",
296
+ zkeyIndex: "zKeyIndex"
297
+ }
298
+ },
299
+ timeouts: {
300
+ name: "timeouts",
301
+ fields: {
302
+ type: "type",
303
+ startDate: "startDate",
304
+ endDate: "endDate"
305
+ }
306
+ }
307
+ },
308
+ foldersAndPathsTerms: {
309
+ output: `output`,
310
+ setup: `setup`,
311
+ contribute: `contribute`,
312
+ finalize: `finalize`,
313
+ pot: `pot`,
314
+ zkeys: `zkeys`,
315
+ wasm: `wasm`,
316
+ vkeys: `vkeys`,
317
+ metadata: `metadata`,
318
+ transcripts: `transcripts`,
319
+ attestation: `attestation`,
320
+ verifiers: `verifiers`
321
+ },
322
+ cloudFunctionsNames: {
323
+ setupCeremony: "setupCeremony",
324
+ checkParticipantForCeremony: "checkParticipantForCeremony",
325
+ progressToNextCircuitForContribution: "progressToNextCircuitForContribution",
326
+ resumeContributionAfterTimeoutExpiration: "resumeContributionAfterTimeoutExpiration",
327
+ createBucket: "createBucket",
328
+ generateGetObjectPreSignedUrl: "generateGetObjectPreSignedUrl",
329
+ progressToNextContributionStep: "progressToNextContributionStep",
330
+ permanentlyStoreCurrentContributionTimeAndHash: "permanentlyStoreCurrentContributionTimeAndHash",
331
+ startMultiPartUpload: "startMultiPartUpload",
332
+ temporaryStoreCurrentContributionMultiPartUploadId: "temporaryStoreCurrentContributionMultiPartUploadId",
333
+ temporaryStoreCurrentContributionUploadedChunkData: "temporaryStoreCurrentContributionUploadedChunkData",
334
+ generatePreSignedUrlsParts: "generatePreSignedUrlsParts",
335
+ completeMultiPartUpload: "completeMultiPartUpload",
336
+ checkIfObjectExist: "checkIfObjectExist",
337
+ verifyContribution: "verifycontribution",
338
+ checkAndPrepareCoordinatorForFinalization: "checkAndPrepareCoordinatorForFinalization",
339
+ finalizeCircuit: "finalizeCircuit",
340
+ finalizeCeremony: "finalizeCeremony",
341
+ downloadCircuitArtifacts: "downloadCircuitArtifacts",
342
+ transferObject: "transferObject"
343
+ }
344
+ };
345
+
346
+ /**
347
+ * Setup a new ceremony by calling the related cloud function.
348
+ * @param functions <Functions> - the Firebase cloud functions object instance.
349
+ * @param ceremonyInputData <CeremonyInputData> - the input data of the ceremony.
350
+ * @param ceremonyPrefix <string> - the prefix of the ceremony.
351
+ * @param circuits <Circuit[]> - the circuits data.
352
+ * @returns Promise<string> - the unique identifier of the created ceremony.
353
+ */
354
+ const setupCeremony = async (functions, ceremonyInputData, ceremonyPrefix, circuits) => {
355
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.setupCeremony);
356
+ const { data: ceremonyId } = await cf({
357
+ ceremonyInputData,
358
+ ceremonyPrefix,
359
+ circuits
360
+ });
361
+ return String(ceremonyId);
362
+ };
363
+ /**
364
+ * Check the user's current participant status for the ceremony
365
+ * @param functions <Functions> - the Firebase cloud functions object instance.
366
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
367
+ * @returns <boolean> - true when participant is able to contribute; otherwise false.
368
+ */
369
+ const checkParticipantForCeremony = async (functions, ceremonyId) => {
370
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.checkParticipantForCeremony);
371
+ const { data } = await cf({ ceremonyId });
372
+ return data;
373
+ };
374
+ /**
375
+ * Progress the participant to the next circuit preparing for the next contribution.
376
+ * @param functions <Functions> - the Firebase cloud functions object instance.
377
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
378
+ */
379
+ const progressToNextCircuitForContribution = async (functions, ceremonyId) => {
380
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.progressToNextCircuitForContribution);
381
+ await cf({
382
+ ceremonyId
383
+ });
384
+ };
385
+ /**
386
+ * Resume the contributor circuit contribution from scratch after the timeout expiration.
387
+ * @param functions <Functions> - the Firebase cloud functions object instance.
388
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
389
+ */
390
+ const resumeContributionAfterTimeoutExpiration = async (functions, ceremonyId) => {
391
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.resumeContributionAfterTimeoutExpiration);
392
+ await cf({
393
+ ceremonyId
394
+ });
395
+ };
396
+ /**
397
+ * Make a request to create a new AWS S3 bucket for a ceremony.
398
+ * @param functions <Functions> - the Firebase cloud functions object instance.
399
+ * @param bucketName <string> - the name of the ceremony bucket.
400
+ */
401
+ const createS3Bucket = async (functions, bucketName) => {
402
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.createBucket);
403
+ await cf({ bucketName });
404
+ };
405
+ /**
406
+ * Return a pre-signed url for a given object contained inside the provided AWS S3 bucket in order to perform a GET request.
407
+ * @param functions <Functions> - the Firebase cloud functions object instance.
408
+ * @param bucketName <string> - the name of the ceremony bucket.
409
+ * @param objectKey <string> - the storage path that locates the artifact to be downloaded in the bucket.
410
+ * @returns <Promise<string>> - the pre-signed url w/ GET request permissions for specified object key.
411
+ */
412
+ const generateGetObjectPreSignedUrl = async (functions, bucketName, objectKey) => {
413
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.generateGetObjectPreSignedUrl);
414
+ const { data: getPreSignedUrl } = await cf({
415
+ bucketName,
416
+ objectKey
417
+ });
418
+ return String(getPreSignedUrl);
419
+ };
420
+ /**
421
+ * Progress the participant to the next circuit preparing for the next contribution.
422
+ * @param functions <Functions> - the Firebase cloud functions object instance.
423
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
424
+ */
425
+ const progressToNextContributionStep = async (functions, ceremonyId) => {
426
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.progressToNextContributionStep);
427
+ await cf({
428
+ ceremonyId
429
+ });
430
+ };
431
+ /**
432
+ * Write the information about current contribution hash and computation time for the current contributor.
433
+ * @param functions <Functions> - the Firebase cloud functions object instance.
434
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
435
+ * @param contributionComputationTime <number> - the time when it was computed
436
+ * @param contributingHash <string> - the hash of the contribution
437
+ */
438
+ const permanentlyStoreCurrentContributionTimeAndHash = async (functions, ceremonyId, contributionComputationTime, contributionHash) => {
439
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.permanentlyStoreCurrentContributionTimeAndHash);
440
+ await cf({
441
+ ceremonyId,
442
+ contributionComputationTime,
443
+ contributionHash
444
+ });
445
+ };
446
+ /**
447
+ * Start a new multi-part upload for a specific object in the given AWS S3 bucket.
448
+ * @param functions <Functions> - the Firebase cloud functions object instance.
449
+ * @param bucketName <string> - the name of the ceremony bucket.
450
+ * @param objectKey <string> - the storage path that locates the artifact to be downloaded in the bucket.
451
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
452
+ * @returns Promise<string> - the multi-part upload id.
453
+ */
454
+ const openMultiPartUpload = async (functions, bucketName, objectKey, ceremonyId) => {
455
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.startMultiPartUpload);
456
+ const { data: uploadId } = await cf({
457
+ bucketName,
458
+ objectKey,
459
+ ceremonyId
460
+ });
461
+ return String(uploadId);
462
+ };
463
+ /**
464
+ * Write temporary information about the unique identifier about the opened multi-part upload to eventually resume the contribution.
465
+ * @param functions <Functions> - the Firebase cloud functions object instance.
466
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
467
+ * @param uploadId <string> - the unique identifier of the multi-part upload.
468
+ */
469
+ const temporaryStoreCurrentContributionMultiPartUploadId = async (functions, ceremonyId, uploadId) => {
470
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.temporaryStoreCurrentContributionMultiPartUploadId);
471
+ await cf({
472
+ ceremonyId,
473
+ uploadId
474
+ });
475
+ };
476
+ /**
477
+ * Write temporary information about the etags and part numbers for each uploaded chunk in order to make the upload resumable from last chunk.
478
+ * @param functions <Functions> - the Firebase cloud functions object instance.
479
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
480
+ * @param chunk <ETagWithPartNumber> - the information about the already uploaded chunk.
481
+ */
482
+ const temporaryStoreCurrentContributionUploadedChunkData = async (functions, ceremonyId, chunk) => {
483
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.temporaryStoreCurrentContributionUploadedChunkData);
484
+ await cf({
485
+ ceremonyId,
486
+ chunk
487
+ });
488
+ };
489
+ /**
490
+ * Generate a new pre-signed url for each chunk related to a started multi-part upload.
491
+ * @param functions <Functions> - the Firebase cloud functions object instance.
492
+ * @param bucketName <string> - the name of the ceremony bucket.
493
+ * @param objectKey <string> - the storage path that locates the artifact to be downloaded in the bucket.
494
+ * @param uploadId <string> - the unique identifier of the multi-part upload.
495
+ * @param numberOfChunks <number> - the number of pre-signed urls to be generated.
496
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
497
+ * @returns Promise<Array<string>> - the set of pre-signed urls (one for each chunk).
498
+ */
499
+ const generatePreSignedUrlsParts = async (functions, bucketName, objectKey, uploadId, numberOfParts, ceremonyId) => {
500
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.generatePreSignedUrlsParts);
501
+ const { data: chunksUrls } = await cf({
502
+ bucketName,
503
+ objectKey,
504
+ uploadId,
505
+ numberOfParts,
506
+ ceremonyId
507
+ });
508
+ return chunksUrls;
509
+ };
510
+ /**
511
+ * Complete a multi-part upload for a specific object in the given AWS S3 bucket.
512
+ * @param functions <Functions> - the Firebase cloud functions object instance.
513
+ * @param bucketName <string> - the name of the ceremony bucket.
514
+ * @param objectKey <string> - the storage path that locates the artifact to be downloaded in the bucket.
515
+ * @param uploadId <string> - the unique identifier of the multi-part upload.
516
+ * @param parts Array<ETagWithPartNumber> - the completed .
517
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
518
+ * @returns Promise<string> - the location of the uploaded ceremony artifact.
519
+ */
520
+ const completeMultiPartUpload = async (functions, bucketName, objectKey, uploadId, parts, ceremonyId) => {
521
+ // Call completeMultiPartUpload() Cloud Function.
522
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.completeMultiPartUpload);
523
+ const { data: location } = await cf({
524
+ bucketName,
525
+ objectKey,
526
+ uploadId,
527
+ parts,
528
+ ceremonyId
529
+ });
530
+ return String(location);
531
+ };
532
+ /**
533
+ * Check if a specified object exist in a given AWS S3 bucket.
534
+ * @param functions <Functions> - the Firebase cloud functions object instance.
535
+ * @param bucketName <string> - the name of the ceremony bucket.
536
+ * @param objectKey <string> - the storage path that locates the artifact to be downloaded in the bucket.
537
+ * @returns <Promise<string>> - true if and only if the object exists, otherwise false.
538
+ */
539
+ const checkIfObjectExist = async (functions, bucketName, objectKey) => {
540
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.checkIfObjectExist);
541
+ const { data: doesObjectExist } = await cf({
542
+ bucketName,
543
+ objectKey
544
+ });
545
+ return doesObjectExist;
546
+ };
547
+ /**
548
+ * Request to verify the newest contribution for the circuit.
549
+ * @param functions <Functions> - the Firebase cloud functions object instance.
550
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
551
+ * @param circuit <FirebaseDocumentInfo> - the document info about the circuit.
552
+ * @param bucketName <string> - the name of the ceremony bucket.
553
+ * @param contributorOrCoordinatorIdentifier <string> - the identifier of the contributor or coordinator (only when finalizing).
554
+ * @param verifyContributionCloudFunctionEndpoint <string> - the endpoint (direct url) necessary to call the V2 Cloud Function.
555
+ * @returns <Promise<void>> -
556
+ */
557
+ const verifyContribution = async (functions, ceremonyId, circuit, // any just to avoid breaking the tests.
558
+ bucketName, contributorOrCoordinatorIdentifier, verifyContributionCloudFunctionEndpoint) => {
559
+ const cf = httpsCallableFromURL(functions, verifyContributionCloudFunctionEndpoint, {
560
+ timeout: 3600000 // max timeout 60 minutes.
561
+ });
562
+ /**
563
+ * @dev Force a race condition to fix #57.
564
+ * TL;DR if the cloud function does not return despite having finished its execution, we use
565
+ * a listener on the circuit, we check and retrieve the info about the correct execution and
566
+ * return it manually. In other cases, it will be the function that returns either a timeout in case it
567
+ * remains in execution for too long.
568
+ */
569
+ await Promise.race([
570
+ cf({
571
+ ceremonyId,
572
+ circuitId: circuit.id,
573
+ contributorOrCoordinatorIdentifier,
574
+ bucketName
575
+ }),
576
+ new Promise((resolve) => {
577
+ setTimeout(() => {
578
+ const unsubscribeToCeremonyCircuitListener = onSnapshot(circuit.ref, async (changedCircuit) => {
579
+ // Check data.
580
+ if (!circuit.data || !changedCircuit.data())
581
+ throw Error(`Unable to retrieve circuit data from the ceremony.`);
582
+ // Extract data.
583
+ const { avgTimings: changedAvgTimings, waitingQueue: changedWaitingQueue } = changedCircuit.data();
584
+ const { contributionComputation: changedContributionComputation, fullContribution: changedFullContribution, verifyCloudFunction: changedVerifyCloudFunction } = changedAvgTimings;
585
+ const { failedContributions: changedFailedContributions, completedContributions: changedCompletedContributions } = changedWaitingQueue;
586
+ const { avgTimings: prevAvgTimings, waitingQueue: prevWaitingQueue } = changedCircuit.data();
587
+ const { contributionComputation: prevContributionComputation, fullContribution: prevFullContribution, verifyCloudFunction: prevVerifyCloudFunction } = prevAvgTimings;
588
+ const { failedContributions: prevFailedContributions, completedContributions: prevCompletedContributions } = prevWaitingQueue;
589
+ // Pre-conditions.
590
+ const invalidContribution = prevFailedContributions === changedFailedContributions - 1;
591
+ const validContribution = prevCompletedContributions === changedCompletedContributions - 1;
592
+ const avgTimeUpdates = prevContributionComputation !== changedContributionComputation &&
593
+ prevFullContribution !== changedFullContribution &&
594
+ prevVerifyCloudFunction !== changedVerifyCloudFunction;
595
+ if ((invalidContribution || validContribution) && avgTimeUpdates) {
596
+ resolve({});
597
+ }
598
+ });
599
+ // Unsubscribe from listener.
600
+ unsubscribeToCeremonyCircuitListener();
601
+ }, 3600000 - 1000); // 59:59 throws 1s before max time for CF execution.
602
+ })
603
+ ]);
604
+ };
605
+ /**
606
+ * Prepare the coordinator for the finalization of the ceremony.
607
+ * @param functions <Functions> - the Firebase cloud functions object instance.
608
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
609
+ * @returns <Promise<boolean>> - true when the coordinator is ready for finalization; otherwise false.
610
+ */
611
+ const checkAndPrepareCoordinatorForFinalization = async (functions, ceremonyId) => {
612
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.checkAndPrepareCoordinatorForFinalization);
613
+ const { data: isCoordinatorReadyForCeremonyFinalization } = await cf({
614
+ ceremonyId
615
+ });
616
+ return isCoordinatorReadyForCeremonyFinalization;
617
+ };
618
+ /**
619
+ * Finalize the ceremony circuit.
620
+ * @param functions <Functions> - the Firebase cloud functions object instance.
621
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
622
+ * @param circuitId <string> - the unique identifier of the circuit.
623
+ * @param bucketName <string> - the name of the ceremony bucket.
624
+ * @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
625
+ */
626
+ const finalizeCircuit = async (functions, ceremonyId, circuitId, bucketName, beacon) => {
627
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.finalizeCircuit);
628
+ await cf({
629
+ ceremonyId,
630
+ circuitId,
631
+ bucketName,
632
+ beacon
633
+ });
634
+ };
635
+ /**
636
+ * Conclude the finalization of the ceremony.
637
+ * @param functions <Functions> - the Firebase cloud functions object instance.
638
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
639
+ */
640
+ const finalizeCeremony = async (functions, ceremonyId) => {
641
+ const cf = httpsCallable(functions, commonTerms.cloudFunctionsNames.finalizeCeremony);
642
+ await cf({
643
+ ceremonyId
644
+ });
645
+ };
646
+
647
+ /**
648
+ * Return the bucket name based on ceremony prefix.
649
+ * @param ceremonyPrefix <string> - the ceremony prefix.
650
+ * @param ceremonyPostfix <string> - the ceremony postfix.
651
+ * @returns <string>
652
+ */
653
+ const getBucketName = (ceremonyPrefix, ceremonyPostfix) => `${ceremonyPrefix}${ceremonyPostfix}`;
654
+ /**
655
+ * Get chunks and signed urls related to an object that must be uploaded using a multi-part upload.
656
+ * @param cloudFunctions <Functions> - the Firebase Cloud Functions service instance.
657
+ * @param bucketName <string> - the name of the ceremony artifacts bucket (AWS S3).
658
+ * @param objectKey <string> - the unique key to identify the object inside the given AWS S3 bucket.
659
+ * @param localFilePath <string> - the local path where the artifact will be downloaded.
660
+ * @param uploadId <string> - the unique identifier of the multi-part upload.
661
+ * @param configStreamChunkSize <number> - size of each chunk into which the artifact is going to be splitted (nb. will be converted in MB).
662
+ * @param [ceremonyId] <string> - the unique identifier of the ceremony.
663
+ * @returns Promise<Array<ChunkWithUrl>> - the chunks with related pre-signed url.
664
+ */
665
+ const getChunksAndPreSignedUrls = async (cloudFunctions, bucketName, objectKey, localFilePath, uploadId, configStreamChunkSize, ceremonyId) => {
666
+ // Prepare a new stream to read the file.
667
+ const stream = fs.createReadStream(localFilePath, {
668
+ highWaterMark: configStreamChunkSize * 1024 * 1024 // convert to MB.
669
+ });
670
+ // Split in chunks.
671
+ const chunks = [];
672
+ for await (const chunk of stream)
673
+ chunks.push(chunk);
674
+ // Check if the file is not empty.
675
+ if (!chunks.length)
676
+ throw new Error("Unable to split an empty file into chunks.");
677
+ // Request pre-signed url generation for each chunk.
678
+ const preSignedUrls = await generatePreSignedUrlsParts(cloudFunctions, bucketName, objectKey, uploadId, chunks.length, ceremonyId);
679
+ // Map pre-signed urls with corresponding chunks.
680
+ return chunks.map((val1, index) => ({
681
+ partNumber: index + 1,
682
+ chunk: val1,
683
+ preSignedUrl: preSignedUrls[index]
684
+ }));
685
+ };
686
+ /**
687
+ * Forward the request to upload each single chunk of the related ceremony artifact.
688
+ * @param chunksWithUrls <Array<ChunkWithUrl>> - the array containing each chunk mapped with the corresponding pre-signed urls.
689
+ * @param contentType <string | false> - the content type of the ceremony artifact.
690
+ * @param cloudFunctions <Functions> - the Firebase Cloud Functions service instance.
691
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
692
+ * @param alreadyUploadedChunks Array<ETagWithPartNumber> - the temporary information about the already uploaded chunks.
693
+ * @param logger <GenericBar> - an optional logger to show progress.
694
+ * @returns <Promise<Array<ETagWithPartNumber>>> - the completed (uploaded) chunks information.
695
+ */
696
+ const uploadParts = async (chunksWithUrls, contentType, cloudFunctions, ceremonyId, alreadyUploadedChunks, logger) => {
697
+ // Keep track of uploaded chunks.
698
+ const uploadedChunks = alreadyUploadedChunks || [];
699
+ // if we were passed a logger, start it
700
+ if (logger)
701
+ logger.start(chunksWithUrls.length, 0);
702
+ // Loop through remaining chunks.
703
+ for (let i = alreadyUploadedChunks ? alreadyUploadedChunks.length : 0; i < chunksWithUrls.length; i += 1) {
704
+ // Consume the pre-signed url to upload the chunk.
705
+ // @ts-ignore
706
+ const response = await fetch(chunksWithUrls[i].preSignedUrl, {
707
+ retryOptions: {
708
+ retryInitialDelay: 500,
709
+ socketTimeout: 60000,
710
+ retryMaxDuration: 300000 // 5 minutes.
711
+ },
712
+ method: "PUT",
713
+ body: chunksWithUrls[i].chunk,
714
+ headers: {
715
+ "Content-Type": contentType.toString(),
716
+ "Content-Length": chunksWithUrls[i].chunk.length.toString()
717
+ },
718
+ agent: new https.Agent({ keepAlive: true })
719
+ });
720
+ // Verify the response.
721
+ if (response.status !== 200 || !response.ok)
722
+ throw new Error(`Unable to upload chunk number ${i}. Please, terminate the current session and retry to resume from the latest uploaded chunk.`);
723
+ // Extract uploaded chunk data.
724
+ const chunk = {
725
+ ETag: response.headers.get("etag") || undefined,
726
+ PartNumber: chunksWithUrls[i].partNumber
727
+ };
728
+ uploadedChunks.push(chunk);
729
+ // Temporary store uploaded chunk data to enable later resumable contribution.
730
+ // nb. this must be done only when contributing (not finalizing).
731
+ if (!!ceremonyId && !!cloudFunctions)
732
+ await temporaryStoreCurrentContributionUploadedChunkData(cloudFunctions, ceremonyId, chunk);
733
+ // increment the count on the logger
734
+ if (logger)
735
+ logger.increment();
736
+ }
737
+ return uploadedChunks;
738
+ };
739
+ /**
740
+ * Upload a ceremony artifact to the corresponding bucket.
741
+ * @notice this method implements the multi-part upload using pre-signed urls, optimal for large files.
742
+ * Steps:
743
+ * 0) Check if current contributor could resume a multi-part upload.
744
+ * 0.A) If yes, continue from last uploaded chunk using the already opened multi-part upload.
745
+ * 0.B) Otherwise, start creating a new multi-part upload.
746
+ * 1) Generate a pre-signed url for each (remaining) chunk of the ceremony artifact.
747
+ * 2) Consume the pre-signed urls to upload chunks.
748
+ * 3) Complete the multi-part upload.
749
+ * @param cloudFunctions <Functions> - the Firebase Cloud Functions service instance.
750
+ * @param bucketName <string> - the name of the ceremony artifacts bucket (AWS S3).
751
+ * @param objectKey <string> - the unique key to identify the object inside the given AWS S3 bucket.
752
+ * @param localPath <string> - the local path where the artifact will be downloaded.
753
+ * @param configStreamChunkSize <number> - size of each chunk into which the artifact is going to be splitted (nb. will be converted in MB).
754
+ * @param [ceremonyId] <string> - the unique identifier of the ceremony (used as a double-edge sword - as identifier and as a check if current contributor is the coordinator finalizing the ceremony).
755
+ * @param [temporaryDataToResumeMultiPartUpload] <TemporaryParticipantContributionData> - the temporary information necessary to resume an already started multi-part upload.
756
+ * @param logger <GenericBar> - an optional logger to show progress.
757
+ */
758
+ const multiPartUpload = async (cloudFunctions, bucketName, objectKey, localFilePath, configStreamChunkSize, ceremonyId, temporaryDataToResumeMultiPartUpload, logger) => {
759
+ // The unique identifier of the multi-part upload.
760
+ let multiPartUploadId = "";
761
+ // The list of already uploaded chunks.
762
+ let alreadyUploadedChunks = [];
763
+ // Step (0).
764
+ if (temporaryDataToResumeMultiPartUpload && !!temporaryDataToResumeMultiPartUpload.uploadId) {
765
+ // Step (0.A).
766
+ multiPartUploadId = temporaryDataToResumeMultiPartUpload.uploadId;
767
+ alreadyUploadedChunks = temporaryDataToResumeMultiPartUpload.chunks;
768
+ }
769
+ else {
770
+ // Step (0.B).
771
+ // Open a new multi-part upload for the ceremony artifact.
772
+ multiPartUploadId = await openMultiPartUpload(cloudFunctions, bucketName, objectKey, ceremonyId);
773
+ // Store multi-part upload identifier on document collection.
774
+ if (ceremonyId)
775
+ // Store Multi-Part Upload ID after generation.
776
+ await temporaryStoreCurrentContributionMultiPartUploadId(cloudFunctions, ceremonyId, multiPartUploadId);
777
+ }
778
+ // Step (1).
779
+ const chunksWithUrlsZkey = await getChunksAndPreSignedUrls(cloudFunctions, bucketName, objectKey, localFilePath, multiPartUploadId, configStreamChunkSize, ceremonyId);
780
+ // Step (2).
781
+ const partNumbersAndETagsZkey = await uploadParts(chunksWithUrlsZkey, mime.lookup(localFilePath), // content-type.
782
+ cloudFunctions, ceremonyId, alreadyUploadedChunks, logger);
783
+ // Step (3).
784
+ await completeMultiPartUpload(cloudFunctions, bucketName, objectKey, multiPartUploadId, partNumbersAndETagsZkey, ceremonyId);
785
+ };
786
+ /**
787
+ * Download an artifact from S3 (only for authorized users)
788
+ * @param cloudFunctions <Functions> Firebase cloud functions instance.
789
+ * @param bucketName <string> Name of the bucket where the artifact is stored.
790
+ * @param storagePath <string> Path to the artifact in the bucket.
791
+ * @param localPath <string> Path to the local file where the artifact will be saved.
792
+ */
793
+ const downloadCeremonyArtifact = async (cloudFunctions, bucketName, storagePath, localPath) => {
794
+ // Request pre-signed url to make GET download request.
795
+ const getPreSignedUrl = await generateGetObjectPreSignedUrl(cloudFunctions, bucketName, storagePath);
796
+ // Make fetch to get info about the artifact.
797
+ // @ts-ignore
798
+ const response = await fetch(getPreSignedUrl);
799
+ if (response.status !== 200 && !response.ok)
800
+ throw new Error(`There was an erorr while downloading the object ${storagePath} from the bucket ${bucketName}. Please check the function inputs and try again.`);
801
+ const content = response.body;
802
+ // Prepare stream.
803
+ const writeStream = createWriteStream(localPath);
804
+ // Write chunk by chunk.
805
+ for await (const chunk of content) {
806
+ // Write chunk.
807
+ writeStream.write(chunk);
808
+ }
809
+ };
810
+ /**
811
+ * Get R1CS file path tied to a particular circuit of a ceremony in the storage.
812
+ * @notice each R1CS file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeR1csFilename>`.
813
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
814
+ * @param circuitPrefix <string> - the prefix of the circuit.
815
+ * @param completeR1csFilename <string> - the complete R1CS filename (name + ext).
816
+ * @returns <string> - the storage path of the R1CS file.
817
+ */
818
+ const getR1csStorageFilePath = (circuitPrefix, completeR1csFilename) => `${commonTerms.collections.circuits.name}/${circuitPrefix}/${completeR1csFilename}`;
819
+ /**
820
+ * Get WASM file path tied to a particular circuit of a ceremony in the storage.
821
+ * @notice each WASM file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeWasmFilename>`.
822
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
823
+ * @param circuitPrefix <string> - the prefix of the circuit.
824
+ * @param completeWasmFilename <string> - the complete WASM filename (name + ext).
825
+ * @returns <string> - the storage path of the WASM file.
826
+ */
827
+ const getWasmStorageFilePath = (circuitPrefix, completeWasmFilename) => `${commonTerms.collections.circuits.name}/${circuitPrefix}/${completeWasmFilename}`;
828
+ /**
829
+ * Get PoT file path in the storage.
830
+ * @notice each PoT file in the storage must be stored in the following path: `pot/<completePotFilename>`.
831
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
832
+ * @param completePotFilename <string> - the complete PoT filename (name + ext).
833
+ * @returns <string> - the storage path of the PoT file.
834
+ */
835
+ const getPotStorageFilePath = (completePotFilename) => `${commonTerms.foldersAndPathsTerms.pot}/${completePotFilename}`;
836
+ /**
837
+ * Get zKey file path tied to a particular circuit of a ceremony in the storage.
838
+ * @notice each zKey file in the storage must be stored in the following path: `circuits/<circuitPrefix>/contributions/<completeZkeyFilename>`.
839
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
840
+ * @param circuitPrefix <string> - the prefix of the circuit.
841
+ * @param completeZkeyFilename <string> - the complete zKey filename (name + ext).
842
+ * @returns <string> - the storage path of the zKey file.
843
+ */
844
+ const getZkeyStorageFilePath = (circuitPrefix, completeZkeyFilename) => `${commonTerms.collections.circuits.name}/${circuitPrefix}/${commonTerms.collections.contributions.name}/${completeZkeyFilename}`;
845
+ /**
846
+ * Get verification key file path tied to a particular circuit of a ceremony in the storage.
847
+ * @notice each verification key file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeVerificationKeyFilename>`.
848
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
849
+ * @param circuitPrefix <string> - the prefix of the circuit.
850
+ * @param completeVerificationKeyFilename <string> - the complete verification key filename (name + ext).
851
+ * @returns <string> - the storage path of the verification key file.
852
+ */
853
+ const getVerificationKeyStorageFilePath = (circuitPrefix, completeVerificationKeyFilename) => `${commonTerms.collections.circuits.name}/${circuitPrefix}/${completeVerificationKeyFilename}`;
854
+ /**
855
+ * Get verifier contract file path tied to a particular circuit of a ceremony in the storage.
856
+ * @notice each verifier contract file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeVerificationKeyFilename>`.
857
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
858
+ * @param circuitPrefix <string> - the prefix of the circuit.
859
+ * @param completeVerifierContractFilename <string> - the complete verifier contract filename (name + ext).
860
+ * @returns <string> - the storage path of the verifier contract file.
861
+ */
862
+ const getVerifierContractStorageFilePath = (circuitPrefix, completeVerifierContractFilename) => `${commonTerms.collections.circuits.name}/${circuitPrefix}/${completeVerifierContractFilename}`;
863
+ /**
864
+ * Get transcript file path tied to a particular circuit of a ceremony in the storage.
865
+ * @notice each R1CS file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeTranscriptFilename>`.
866
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
867
+ * @param circuitPrefix <string> - the prefix of the circuit.
868
+ * @param completeTranscriptFilename <string> - the complete transcript filename (name + ext).
869
+ * @returns <string> - the storage path of the transcript file.
870
+ */
871
+ const getTranscriptStorageFilePath = (circuitPrefix, completeTranscriptFilename) => `${commonTerms.collections.circuits.name}/${circuitPrefix}/${commonTerms.foldersAndPathsTerms.transcripts}/${completeTranscriptFilename}`;
872
+
873
+ /**
874
+ * Get participants collection path for database reference.
875
+ * @notice all participants related documents are store under `ceremonies/<ceremonyId>/participants` collection path.
876
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
877
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
878
+ * @returns <string> - the participants collection path.
879
+ */
880
+ const getParticipantsCollectionPath = (ceremonyId) => `${commonTerms.collections.ceremonies.name}/${ceremonyId}/${commonTerms.collections.participants.name}`;
881
+ /**
882
+ * Get circuits collection path for database reference.
883
+ * @notice all circuits related documents are store under `ceremonies/<ceremonyId>/circuits` collection path.
884
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
885
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
886
+ * @returns <string> - the participants collection path.
887
+ */
888
+ const getCircuitsCollectionPath = (ceremonyId) => `${commonTerms.collections.ceremonies.name}/${ceremonyId}/${commonTerms.collections.circuits.name}`;
889
+ /**
890
+ * Get contributions collection path for database reference.
891
+ * @notice all contributions related documents are store under `ceremonies/<ceremonyId>/circuits/<circuitId>/contributions` collection path.
892
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
893
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
894
+ * @param circuitId <string> - the unique identifier of the circuit.
895
+ * @returns <string> - the contributions collection path.
896
+ */
897
+ const getContributionsCollectionPath = (ceremonyId, circuitId) => `${getCircuitsCollectionPath(ceremonyId)}/${circuitId}/${commonTerms.collections.contributions.name}`;
898
+ /**
899
+ * Get timeouts collection path for database reference.
900
+ * @notice all timeouts related documents are store under `ceremonies/<ceremonyId>/participants/<participantId>/timeouts` collection path.
901
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
902
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
903
+ * @param participantId <string> - the unique identifier of the participant.
904
+ * @returns <string> - the timeouts collection path.
905
+ */
906
+ const getTimeoutsCollectionPath = (ceremonyId, participantId) => `${getParticipantsCollectionPath(ceremonyId)}/${participantId}/${commonTerms.collections.timeouts.name}`;
907
+ /**
908
+ * Helper for query a collection based on certain constraints.
909
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
910
+ * @param collection <string> - the name of the collection.
911
+ * @param queryConstraints <Array<QueryConstraint>> - a sequence of where conditions.
912
+ * @returns <Promise<QuerySnapshot<DocumentData>>> - return the matching documents (if any).
913
+ */
914
+ const queryCollection = async (firestoreDatabase, collection$1, queryConstraints) => {
915
+ // Make a query.
916
+ const q = query(collection(firestoreDatabase, collection$1), ...queryConstraints);
917
+ // Get docs.
918
+ const snap = await getDocs(q);
919
+ return snap;
920
+ };
921
+ /**
922
+ * Helper for obtaining uid and data for query document snapshots.
923
+ * @param queryDocSnap <Array<QueryDocumentSnapshot>> - the array of query document snapshot to be converted.
924
+ * @returns Array<FirebaseDocumentInfo>
925
+ */
926
+ const fromQueryToFirebaseDocumentInfo = (queryDocSnap) => queryDocSnap.map((document) => ({
927
+ id: document.id,
928
+ ref: document.ref,
929
+ data: document.data()
930
+ }));
931
+ /**
932
+ * Fetch for all documents in a collection.
933
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
934
+ * @param collection <string> - the name of the collection.
935
+ * @returns <Promise<Array<QueryDocumentSnapshot<DocumentData>>>> - return all documents (if any).
936
+ */
937
+ const getAllCollectionDocs = async (firestoreDatabase, collection$1) => (await getDocs(collection(firestoreDatabase, collection$1))).docs;
938
+ /**
939
+ * Get a specific document from database.
940
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
941
+ * @param collection <string> - the name of the collection.
942
+ * @param documentId <string> - the unique identifier of the document in the collection.
943
+ * @returns <Promise<DocumentSnapshot<DocumentData>>> - return the document from Firestore.
944
+ */
945
+ const getDocumentById = async (firestoreDatabase, collection, documentId) => {
946
+ const docRef = doc(firestoreDatabase, collection, documentId);
947
+ return getDoc(docRef);
948
+ };
949
+ /**
950
+ * Query for opened ceremonies.
951
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
952
+ * @returns <Promise<Array<FirebaseDocumentInfo>>>
953
+ */
954
+ const getOpenedCeremonies = async (firestoreDatabase) => {
955
+ const runningStateCeremoniesQuerySnap = await queryCollection(firestoreDatabase, commonTerms.collections.ceremonies.name, [
956
+ where(commonTerms.collections.ceremonies.fields.state, "==", "OPENED" /* CeremonyState.OPENED */),
957
+ where(commonTerms.collections.ceremonies.fields.endDate, ">=", Date.now())
958
+ ]);
959
+ return fromQueryToFirebaseDocumentInfo(runningStateCeremoniesQuerySnap.docs);
960
+ };
961
+ /**
962
+ * Query for ceremony circuits.
963
+ * @notice the order by sequence position is fundamental to maintain parallelism among contributions for different circuits.
964
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
965
+ * @param ceremonyId <string> - the ceremony unique identifier.
966
+ * @returns Promise<Array<FirebaseDocumentInfo>> - the ceremony' circuits documents ordered by sequence position.
967
+ */
968
+ const getCeremonyCircuits = async (firestoreDatabase, ceremonyId) => fromQueryToFirebaseDocumentInfo(await getAllCollectionDocs(firestoreDatabase, getCircuitsCollectionPath(ceremonyId))).sort((a, b) => a.data.sequencePosition - b.data.sequencePosition);
969
+ /**
970
+ * Query for a specific ceremony' circuit contribution from a given contributor (if any).
971
+ * @notice if the caller is a coordinator, there could be more than one contribution (= the one from finalization applies to this criteria).
972
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
973
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
974
+ * @param circuitId <string> - the unique identifier of the circuit.
975
+ * @param participantId <string> - the unique identifier of the participant.
976
+ * @returns <Promise<Array<FirebaseDocumentInfo>>> - the document info about the circuit contributions from contributor.
977
+ */
978
+ const getCircuitContributionsFromContributor = async (firestoreDatabase, ceremonyId, circuitId, participantId) => {
979
+ const participantContributionsQuerySnap = await queryCollection(firestoreDatabase, getContributionsCollectionPath(ceremonyId, circuitId), [where(commonTerms.collections.contributions.fields.participantId, "==", participantId)]);
980
+ return fromQueryToFirebaseDocumentInfo(participantContributionsQuerySnap.docs);
981
+ };
982
+ /**
983
+ * Query for the active timeout from given participant for a given ceremony (if any).
984
+ * @param ceremonyId <string> - the identifier of the ceremony.
985
+ * @param participantId <string> - the identifier of the participant.
986
+ * @returns <Promise<Array<FirebaseDocumentInfo>>> - the document info about the current active participant timeout.
987
+ */
988
+ const getCurrentActiveParticipantTimeout = async (firestoreDatabase, ceremonyId, participantId) => {
989
+ const participantTimeoutQuerySnap = await queryCollection(firestoreDatabase, getTimeoutsCollectionPath(ceremonyId, participantId), [where(commonTerms.collections.timeouts.fields.endDate, ">=", Timestamp.now().toMillis())]);
990
+ return fromQueryToFirebaseDocumentInfo(participantTimeoutQuerySnap.docs);
991
+ };
992
+ /**
993
+ * Query for the closed ceremonies.
994
+ * @notice a ceremony is closed when the period for receiving new contributions has ended.
995
+ * @dev when the ceremony is closed it becomes ready for finalization.
996
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
997
+ * @returns <Promise<Array<FirebaseDocumentInfo>>> - the list of closed ceremonies.
998
+ */
999
+ const getClosedCeremonies = async (firestoreDatabase) => {
1000
+ const closedCeremoniesQuerySnap = await queryCollection(firestoreDatabase, commonTerms.collections.ceremonies.name, [
1001
+ where(commonTerms.collections.ceremonies.fields.state, "==", "CLOSED" /* CeremonyState.CLOSED */),
1002
+ where(commonTerms.collections.ceremonies.fields.endDate, "<=", Date.now())
1003
+ ]);
1004
+ return fromQueryToFirebaseDocumentInfo(closedCeremoniesQuerySnap.docs);
1005
+ };
1006
+
1007
+ /**
1008
+ * @hidden
1009
+ */
1010
+ const toHexByte = (byte) => (byte < 0x10 ? `0${byte.toString(16)}` : byte.toString(16));
1011
+ /**
1012
+ * Converts Uint8Array to hexadecimal string.
1013
+ * @param buffer arbritrary length of data
1014
+ * @returns hexadecimal string
1015
+ */
1016
+ const toHex = (buffer) => Array.from(buffer).map(toHexByte).join("");
1017
+ /**
1018
+ * Get 512 bit blake hash of the contents of given path.
1019
+ * @param data buffer or hexadecimal string
1020
+ * @returns 64 byte hexadecimal string
1021
+ */
1022
+ const blake512FromPath = async (path) => {
1023
+ const context = blake.blake2bInit(64, undefined);
1024
+ const hash = await new Promise((resolve) => {
1025
+ fs.createReadStream(path)
1026
+ .on("data", (chunk) => {
1027
+ blake.blake2bUpdate(context, chunk);
1028
+ })
1029
+ .on("end", () => {
1030
+ resolve(toHex(blake.blake2bFinal(context)));
1031
+ });
1032
+ });
1033
+ return hash;
1034
+ };
1035
+ /**
1036
+ * Return the SHA256 hash (HEX format) of a given value
1037
+ * @param value <string> - the value to be hashed.
1038
+ * @returns <string> - the HEX format of the SHA256 hash of the given value
1039
+ */
1040
+ const computeSHA256ToHex = (value) => crypto.createHash("sha256").update(value).digest("hex");
1041
+ /**
1042
+ * Helper function that can be used to compare whether two files' hashes are equal or not.
1043
+ * @param path1 <string> Path to the first file.
1044
+ * @param path2 <string> Path to the second file.
1045
+ * @returns <Promise<boolean>> Whether the files are equal or not.
1046
+ */
1047
+ const compareHashes = async (path1, path2) => {
1048
+ const hash1 = await blake512FromPath(path1);
1049
+ const hash2 = await blake512FromPath(path2);
1050
+ return hash1 === hash2;
1051
+ };
1052
+
1053
+ /**
1054
+ * Return a string with double digits if the provided input is one digit only.
1055
+ * @param in <number> - the input number to be converted.
1056
+ * @returns <string> - the two digits stringified number derived from the conversion.
1057
+ */
1058
+ const convertToDoubleDigits = (amount) => (amount < 10 ? `0${amount}` : amount.toString());
1059
+ /**
1060
+ * Extract a prefix consisting of alphanumeric and underscore characters from a string with arbitrary characters.
1061
+ * @dev replaces all special symbols and whitespaces with an underscore char ('_'). Convert all uppercase chars to lowercase.
1062
+ * @notice example: str = 'Multiplier-2!2.4.zkey'; output prefix = 'multiplier_2_2_4.zkey'.
1063
+ * NB. Prefix extraction is a key process that conditions the name of the ceremony artifacts, download/upload from/to storage, collections paths.
1064
+ * @param str <string> - the arbitrary string from which to extract the prefix.
1065
+ * @returns <string> - the resulting prefix.
1066
+ */
1067
+ const extractPrefix = (str) =>
1068
+ // eslint-disable-next-line no-useless-escape
1069
+ str.replace(/[`\s~!@#$%^&*()|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "-").toLowerCase();
1070
+ /**
1071
+ * Extract data from a R1CS metadata file generated with a custom file-based logger.
1072
+ * @notice useful for extracting metadata circuits contained in the generated file using a logger
1073
+ * on the `r1cs.info()` method of snarkjs.
1074
+ * @param fullFilePath <string> - the full path of the file.
1075
+ * @param keyRgx <RegExp> - the regular expression linked to the key from which you want to extract the value.
1076
+ * @returns <string> - the stringified extracted value.
1077
+ */
1078
+ const extractR1CSInfoValueForGivenKey = (fullFilePath, keyRgx) => {
1079
+ // Read the logger file.
1080
+ const fileContents = fs.readFileSync(fullFilePath, "utf-8");
1081
+ // Check for the matching value.
1082
+ const matchingValue = fileContents.match(keyRgx);
1083
+ if (!matchingValue)
1084
+ throw new Error(`Unable to retrieve circuit metadata. Possible causes may involve an error while using the logger. Please, check whether the corresponding \`.log\` file is present in your local \`output/setup/metadata\` folder. In any case, we kindly ask you to terminate the current session and repeat the process.`);
1085
+ // Elaborate spaces and special characters to extract the value.
1086
+ // nb. this is a manual process which follows this custom arbitrary extraction rule
1087
+ // accordingly to the output produced by the `r1cs.info()` method from snarkjs library.
1088
+ return matchingValue?.at(0)?.split(":")[1].replace(" ", "").split("#")[0].replace("\n", "");
1089
+ };
1090
+ /**
1091
+ * Calculate the smallest amount of Powers of Tau needed for a circuit with a constraint size.
1092
+ * @param constraints <number> - the number of circuit constraints (extracted from metadata).
1093
+ * @param outputs <number> - the number of circuit outputs (extracted from metadata)
1094
+ * @returns <number> - the smallest amount of Powers of Tau for the given constraint size.
1095
+ */
1096
+ const computeSmallestPowersOfTauForCircuit = (constraints, outputs) => {
1097
+ let power = 2;
1098
+ let tau = 2 ** power;
1099
+ while (constraints + outputs > tau) {
1100
+ power += 1;
1101
+ tau = 2 ** power;
1102
+ }
1103
+ return power;
1104
+ };
1105
+ /**
1106
+ * Transform a number in a zKey index format.
1107
+ * @dev this method is aligned with the number of characters of the genesis zKey index (which is a constant).
1108
+ * @param progress <number> - the progression in zKey index.
1109
+ * @returns <string> - the progression in a zKey index format (`XYZAB`).
1110
+ */
1111
+ const formatZkeyIndex = (progress) => {
1112
+ let index = progress.toString();
1113
+ // Pad with zeros if the progression has less digits.
1114
+ while (index.length < genesisZkeyIndex.length) {
1115
+ index = `0${index}`;
1116
+ }
1117
+ return index;
1118
+ };
1119
+ /**
1120
+ * Extract the amount of powers from Powers of Tau file name.
1121
+ * @dev the PoT files must follow these convention (i_am_a_pot_file_09.ptau) where the numbers before '.ptau' are the powers.
1122
+ * @param potCompleteFilename <string> - the complete filename of the Powers of Tau file.
1123
+ * @returns <number> - the amount of powers.
1124
+ */
1125
+ const extractPoTFromFilename = (potCompleteFilename) => Number(potCompleteFilename.split("_").pop()?.split(".").at(0));
1126
+ /**
1127
+ * Automate the generation of an entropy for a contribution.
1128
+ * @dev Took inspiration from here https://github.com/glamperd/setup-mpc-ui/blob/master/client/src/state/Compute.tsx#L112.
1129
+ * @todo we need to improve the entropy generation (too naive).
1130
+ * @returns <string> - the auto-generated entropy.
1131
+ */
1132
+ const autoGenerateEntropy = () => new Uint8Array(256).map(() => Math.random() * 256).toString();
1133
+ /**
1134
+ * Check and return the circuit document based on its sequence position among a set of circuits (if any).
1135
+ * @dev there should be only one circuit with a provided sequence position. This method checks and return an
1136
+ * error if none is found.
1137
+ * @param circuits <Array<FirebaseDocumentInfo>> - the set of ceremony circuits documents.
1138
+ * @param sequencePosition <number> - the sequence position (index) of the circuit to be found and returned.
1139
+ * @returns <FirebaseDocumentInfo> - the document of the circuit in the set of circuits that has the provided sequence position.
1140
+ */
1141
+ const getCircuitBySequencePosition = (circuits, sequencePosition) => {
1142
+ // Filter by sequence position.
1143
+ const matchedCircuits = circuits.filter((circuitDocument) => circuitDocument.data.sequencePosition === sequencePosition);
1144
+ if (matchedCircuits.length !== 1)
1145
+ throw new Error(`Unable to find the circuit having position ${sequencePosition}. Run the command again and, if this error persists please contact the coordinator.`);
1146
+ return matchedCircuits.at(0);
1147
+ };
1148
+ /**
1149
+ * Convert bytes or chilobytes into gigabytes with customizable precision.
1150
+ * @param bytesOrKb <number> - the amount of bytes or chilobytes to be converted.
1151
+ * @param isBytes <boolean> - true when the amount to be converted is in bytes; otherwise false (= Chilobytes).
1152
+ * @returns <number> - the converted amount in GBs.
1153
+ */
1154
+ const convertBytesOrKbToGb = (bytesOrKb, isBytes) => Number(bytesOrKb / 1024 ** (isBytes ? 3 : 2));
1155
+ /**
1156
+ * Get the validity of contributors' contributions for each circuit of the given ceremony (if any).
1157
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
1158
+ * @param circuits <Array<FirebaseDocumentInfo>> - the array of ceremony circuits documents.
1159
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
1160
+ * @param participantId <string> - the unique identifier of the contributor.
1161
+ * @param isFinalizing <boolean> - flag to discriminate between ceremony finalization (true) and contribution (false).
1162
+ * @returns <Promise<Array<ContributionValidity>>> - a list of contributor contributions together with contribution validity (based on coordinator verification).
1163
+ */
1164
+ const getContributionsValidityForContributor = async (firestoreDatabase, circuits, ceremonyId, participantId, isFinalizing) => {
1165
+ const contributionsValidity = [];
1166
+ for await (const circuit of circuits) {
1167
+ // Get circuit contribution from contributor.
1168
+ const circuitContributionsFromContributor = await getCircuitContributionsFromContributor(firestoreDatabase, ceremonyId, circuit.id, participantId);
1169
+ // Check for ceremony finalization (= there could be more than one contribution).
1170
+ const contribution = isFinalizing
1171
+ ? circuitContributionsFromContributor
1172
+ .filter((contributionDocument) => contributionDocument.data.zkeyIndex === finalContributionIndex)
1173
+ .at(0)
1174
+ : circuitContributionsFromContributor.at(0);
1175
+ if (!contribution)
1176
+ throw new Error("Unable to retrieve contributions for the participant. There may have occurred a database-side error. Please, we kindly ask you to terminate the current session and repeat the process");
1177
+ contributionsValidity.push({
1178
+ contributionId: contribution?.id,
1179
+ circuitId: circuit.id,
1180
+ valid: contribution?.data.valid
1181
+ });
1182
+ }
1183
+ return contributionsValidity;
1184
+ };
1185
+ /**
1186
+ * Return the public attestation preamble for given contributor.
1187
+ * @param contributorIdentifier <string> - the identifier of the contributor (handle, name, uid).
1188
+ * @param ceremonyName <string> - the name of the ceremony.
1189
+ * @param isFinalizing <boolean> - true when the coordinator is finalizing the ceremony, otherwise false.
1190
+ * @returns <string> - the public attestation preamble.
1191
+ */
1192
+ const getPublicAttestationPreambleForContributor = (contributorIdentifier, ceremonyName, isFinalizing) => `Hey, I'm ${contributorIdentifier} and I have ${isFinalizing ? "finalized" : "contributed to"} the ${ceremonyName}${ceremonyName.toLowerCase().includes("trusted setup") || ceremonyName.toLowerCase().includes("ceremony")
1193
+ ? "."
1194
+ : " MPC Phase2 Trusted Setup ceremony."}\nThe following are my contribution signatures:`;
1195
+ /**
1196
+ * Check and prepare public attestation for the contributor made only of its valid contributions.
1197
+ * @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
1198
+ * @param circuits <Array<FirebaseDocumentInfo>> - the array of ceremony circuits documents.
1199
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
1200
+ * @param participantId <string> - the unique identifier of the contributor.
1201
+ * @param participantContributions <Array<Co> - the document data of the participant.
1202
+ * @param contributorIdentifier <string> - the identifier of the contributor (handle, name, uid).
1203
+ * @param ceremonyName <string> - the name of the ceremony.
1204
+ * @param isFinalizing <boolean> - true when the coordinator is finalizing the ceremony, otherwise false.
1205
+ * @returns <Promise<string>> - the public attestation for the contributor.
1206
+ */
1207
+ const generateValidContributionsAttestation = async (firestoreDatabase, circuits, ceremonyId, participantId, participantContributions, contributorIdentifier, ceremonyName, isFinalizing) => {
1208
+ // Generate the attestation preamble for the contributor.
1209
+ let publicAttestation = getPublicAttestationPreambleForContributor(contributorIdentifier, ceremonyName, isFinalizing);
1210
+ // Get contributors' contributions validity.
1211
+ const contributionsWithValidity = await getContributionsValidityForContributor(firestoreDatabase, circuits, ceremonyId, participantId, isFinalizing);
1212
+ for await (const contributionWithValidity of contributionsWithValidity) {
1213
+ // Filter for the related contribution document info.
1214
+ const matchedContributions = participantContributions.filter((contribution) => contribution.doc === contributionWithValidity.contributionId);
1215
+ if (matchedContributions.length === 0)
1216
+ throw new Error(`Unable to retrieve given circuit contribution information. This could happen due to some errors while writing the information on the database.`);
1217
+ if (matchedContributions.length > 1)
1218
+ throw new Error(`Duplicated circuit contribution information. Please, contact the coordinator.`);
1219
+ const participantContribution = matchedContributions.at(0);
1220
+ // Get circuit document (the one for which the contribution was calculated).
1221
+ const circuitDocument = await getDocumentById(firestoreDatabase, getCircuitsCollectionPath(ceremonyId), contributionWithValidity.circuitId);
1222
+ const contributionDocument = await getDocumentById(firestoreDatabase, getContributionsCollectionPath(ceremonyId, contributionWithValidity.circuitId), participantContribution.doc);
1223
+ if (!contributionDocument.data() || !circuitDocument.data())
1224
+ throw new Error(`Something went wrong when retrieving the data from the database`);
1225
+ // Extract data.
1226
+ const { sequencePosition, prefix } = circuitDocument.data();
1227
+ const { zkeyIndex } = contributionDocument.data();
1228
+ // Update public attestation.
1229
+ publicAttestation = `${publicAttestation}\n\nCircuit # ${sequencePosition} (${prefix})\nContributor # ${zkeyIndex > 0 ? Number(zkeyIndex) : zkeyIndex}\n${participantContribution.hash}`;
1230
+ }
1231
+ return publicAttestation;
1232
+ };
1233
+ /**
1234
+ * Create a custom logger to write logs on a local file.
1235
+ * @param filename <string> - the name of the output file (where the logs are going to be written).
1236
+ * @param level <winston.LoggerOptions["level"]> - the option for the logger level (e.g., info, error).
1237
+ * @returns <Logger> - a customized winston logger for files.
1238
+ */
1239
+ const createCustomLoggerForFile = (filename, level = "info") => winston.createLogger({
1240
+ level,
1241
+ transports: new winston.transports.File({
1242
+ filename,
1243
+ format: winston.format.printf((log) => log.message),
1244
+ level
1245
+ })
1246
+ });
1247
+ /**
1248
+ * Return an amount of bytes read from a file to a particular location in the form of a buffer.
1249
+ * @param localFilePath <string> - the local path where the artifact will be downloaded.
1250
+ * @param offset <number> - the index of the line to be read (0 from the start).
1251
+ * @param length <number> - the length of the line to be read.
1252
+ * @param position <ReadPosition> - the position inside the file.
1253
+ * @returns <Buffer> - the buffer w/ the read bytes.
1254
+ */
1255
+ const readBytesFromFile = (localFilePath, offset, length, position) => {
1256
+ // Open the file (read mode).
1257
+ const fileDescriptor = fs.openSync(localFilePath, "r");
1258
+ // Prepare buffer.
1259
+ const buffer = Buffer.alloc(length);
1260
+ // Read bytes.
1261
+ fs.readSync(fileDescriptor, buffer, offset, length, position);
1262
+ // Return the read bytes.
1263
+ return buffer;
1264
+ };
1265
+ /**
1266
+ * Return the info about the R1CS file.ù
1267
+ * @dev this method was built taking inspiration from
1268
+ * https://github.com/weijiekoh/circom-helper/blob/master/ts/read_num_inputs.ts#L5.
1269
+ * You can find the specs of R1CS file here
1270
+ * https://github.com/iden3/r1csfile/blob/master/doc/r1cs_bin_format.md
1271
+ * @param localR1CSFilePath <string> - the local path to the R1CS file.
1272
+ * @returns <CircuitMetadata> - the info about the R1CS file.
1273
+ */
1274
+ const getR1CSInfo = (localR1CSFilePath) => {
1275
+ /**
1276
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1277
+ * ┃ 4 │ 72 31 63 73 ┃ Magic "r1cs"
1278
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1279
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1280
+ * ┃ 4 │ 01 00 00 00 ┃ Version 1
1281
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1282
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1283
+ * ┃ 4 │ 03 00 00 00 ┃ Number of Sections
1284
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1285
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
1286
+ * ┃ 4 │ sectionType ┃ 8 │ SectionSize ┃
1287
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛
1288
+ * ┏━━━━━━━━━━━━━━━━━━━━━┓
1289
+ * ┃ ┃
1290
+ * ┃ ┃
1291
+ * ┃ ┃
1292
+ * ┃ Section Content ┃
1293
+ * ┃ ┃
1294
+ * ┃ ┃
1295
+ * ┃ ┃
1296
+ * ┗━━━━━━━━━━━━━━━━━━━━━┛
1297
+ *
1298
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
1299
+ * ┃ 4 │ sectionType ┃ 8 │ SectionSize ┃
1300
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛
1301
+ * ┏━━━━━━━━━━━━━━━━━━━━━┓
1302
+ * ┃ ┃
1303
+ * ┃ ┃
1304
+ * ┃ ┃
1305
+ * ┃ Section Content ┃
1306
+ * ┃ ┃
1307
+ * ┃ ┃
1308
+ * ┃ ┃
1309
+ * ┗━━━━━━━━━━━━━━━━━━━━━┛
1310
+ *
1311
+ * ...
1312
+ * ...
1313
+ * ...
1314
+ */
1315
+ // Prepare state.
1316
+ let pointer = 0; // selector to particular file data position in order to read data.
1317
+ let wires = 0;
1318
+ let publicOutputs = 0;
1319
+ let publicInputs = 0;
1320
+ let privateInputs = 0;
1321
+ let labels = 0;
1322
+ let constraints = 0;
1323
+ try {
1324
+ // Get 'number of section' (jump magic r1cs and version1 data).
1325
+ const numberOfSections = utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 4, 8));
1326
+ // Jump to first section.
1327
+ pointer = 12;
1328
+ // For each section
1329
+ for (let i = 0; i < numberOfSections; i++) {
1330
+ // Read section type.
1331
+ const sectionType = utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 4, pointer));
1332
+ // Jump to section size.
1333
+ pointer += 4;
1334
+ // Read section size
1335
+ const sectionSize = Number(utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 8, pointer)));
1336
+ // If at header section (0x00000001 : Header Section).
1337
+ if (sectionType === BigInt(1)) {
1338
+ // Read info from header section.
1339
+ /**
1340
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1341
+ * ┃ 4 │ 20 00 00 00 ┃ Field Size in bytes (fs)
1342
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1343
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
1344
+ * ┃ fs │ 010000f0 93f5e143 9170b979 48e83328 5d588181 b64550b8 29a031e1 724e6430 ┃ Prime size
1345
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
1346
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1347
+ * ┃ 32 │ 01 00 00 00 ┃ nWires
1348
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1349
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1350
+ * ┃ 32 │ 01 00 00 00 ┃ nPubOut
1351
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1352
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1353
+ * ┃ 32 │ 01 00 00 00 ┃ nPubIn
1354
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1355
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1356
+ * ┃ 32 │ 01 00 00 00 ┃ nPrvIn
1357
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1358
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
1359
+ * ┃ 64 │ 01 00 00 00 00 00 00 00 ┃ nLabels
1360
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
1361
+ * ┏━━━━┳━━━━━━━━━━━━━━━━━┓
1362
+ * ┃ 32 │ 01 00 00 00 ┃ mConstraints
1363
+ * ┗━━━━┻━━━━━━━━━━━━━━━━━┛
1364
+ */
1365
+ pointer += sectionSize - 20;
1366
+ // Read R1CS info.
1367
+ wires = Number(utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 4, pointer)));
1368
+ pointer += 4;
1369
+ publicOutputs = Number(utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 4, pointer)));
1370
+ pointer += 4;
1371
+ publicInputs = Number(utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 4, pointer)));
1372
+ pointer += 4;
1373
+ privateInputs = Number(utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 4, pointer)));
1374
+ pointer += 4;
1375
+ labels = Number(utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 8, pointer)));
1376
+ pointer += 8;
1377
+ constraints = Number(utils.leBuff2int(readBytesFromFile(localR1CSFilePath, 0, 4, pointer)));
1378
+ }
1379
+ pointer += 8 + Number(sectionSize);
1380
+ }
1381
+ return {
1382
+ curve: "bn-128",
1383
+ wires,
1384
+ constraints,
1385
+ privateInputs,
1386
+ publicInputs,
1387
+ labels,
1388
+ outputs: publicOutputs,
1389
+ pot: computeSmallestPowersOfTauForCircuit(constraints, publicOutputs)
1390
+ };
1391
+ }
1392
+ catch (err) {
1393
+ throw new Error(`The R1CS file you provided would not appear to be correct. Please, check that you have provided a valid R1CS file and repeat the process.`);
1394
+ }
1395
+ };
1396
+ /**
1397
+ * Parse and validate that the ceremony configuration is correct
1398
+ * @notice this does not upload any files to storage
1399
+ * @param path <string> - the path to the configuration file
1400
+ * @param cleanup <boolean> - whether to delete the r1cs file after parsing
1401
+ * @returns any - the data to pass to the cloud function for setup and the circuit artifacts
1402
+ */
1403
+ const parseCeremonyFile = async (path, cleanup = false) => {
1404
+ // check that the path exists
1405
+ if (!fs.existsSync(path))
1406
+ throw new Error("The provided path to the configuration file does not exist. Please provide an absolute path and try again.");
1407
+ try {
1408
+ // read the data
1409
+ const data = JSON.parse(fs.readFileSync(path).toString());
1410
+ // verify that the data is correct
1411
+ if (data.timeoutMechanismType !== "DYNAMIC" /* CeremonyTimeoutType.DYNAMIC */ &&
1412
+ data.timeoutMechanismType !== "FIXED" /* CeremonyTimeoutType.FIXED */)
1413
+ throw new Error("Invalid timeout type. Please choose between DYNAMIC and FIXED.");
1414
+ // validate that we have at least 1 circuit input data
1415
+ if (!data.circuits || data.circuits.length === 0)
1416
+ throw new Error("You need to provide the data for at least 1 circuit.");
1417
+ // validate that the end date is in the future
1418
+ let endDate;
1419
+ let startDate;
1420
+ try {
1421
+ endDate = new Date(data.endDate);
1422
+ startDate = new Date(data.startDate);
1423
+ }
1424
+ catch (error) {
1425
+ throw new Error("The dates should follow this format: 2023-07-04T00:00:00.");
1426
+ }
1427
+ if (endDate <= startDate)
1428
+ throw new Error("The end date should be greater than the start date.");
1429
+ const currentDate = new Date();
1430
+ if (endDate <= currentDate || startDate <= currentDate)
1431
+ throw new Error("The start and end dates should be in the future.");
1432
+ // validate penalty
1433
+ if (data.penalty <= 0)
1434
+ throw new Error("The penalty should be greater than zero.");
1435
+ const circuits = [];
1436
+ const urlPattern = /(https?:\/\/[^\s]+)/g;
1437
+ const commitHashPattern = /^[a-f0-9]{40}$/i;
1438
+ const circuitArtifacts = [];
1439
+ for (let i = 0; i < data.circuits.length; i++) {
1440
+ const circuitData = data.circuits[i];
1441
+ const { artifacts } = circuitData;
1442
+ circuitArtifacts.push({
1443
+ artifacts
1444
+ });
1445
+ // where we storing the r1cs downloaded
1446
+ const localR1csPath = `./${circuitData.name}.r1cs`;
1447
+ // where we storing the wasm downloaded
1448
+ const localWasmPath = `./${circuitData.name}.wasm`;
1449
+ // download the r1cs to extract the metadata
1450
+ const streamPipeline = promisify(pipeline);
1451
+ // Make the call.
1452
+ const responseR1CS = await fetch(artifacts.r1csStoragePath);
1453
+ // Handle errors.
1454
+ if (!responseR1CS.ok && responseR1CS.status !== 200)
1455
+ throw new Error(`There was an error while trying to download the r1cs file for circuit ${circuitData.name}. Please check that the file has the correct permissions (public) set.`);
1456
+ await streamPipeline(responseR1CS.body, createWriteStream(localR1csPath));
1457
+ // Write the file locally
1458
+ // extract the metadata from the r1cs
1459
+ const metadata = getR1CSInfo(localR1csPath);
1460
+ // download wasm too to ensure it's available
1461
+ const responseWASM = await fetch(artifacts.wasmStoragePath);
1462
+ if (!responseWASM.ok && responseWASM.status !== 200)
1463
+ throw new Error(`There was an error while trying to download the WASM file for circuit ${circuitData.name}. Please check that the file has the correct permissions (public) set.`);
1464
+ await streamPipeline(responseWASM.body, createWriteStream(localWasmPath));
1465
+ // validate that the circuit hash and template links are valid
1466
+ const { template } = circuitData;
1467
+ const URLMatch = template.source.match(urlPattern);
1468
+ if (!URLMatch || URLMatch.length === 0 || URLMatch.length > 1)
1469
+ throw new Error("You should provide the URL to the circuits templates on GitHub.");
1470
+ const hashMatch = template.commitHash.match(commitHashPattern);
1471
+ if (!hashMatch || hashMatch.length === 0 || hashMatch.length > 1)
1472
+ throw new Error("You should provide a valid commit hash of the circuit templates.");
1473
+ // calculate the hash of the r1cs file
1474
+ const r1csBlake2bHash = await blake512FromPath(localR1csPath);
1475
+ const circuitPrefix = extractPrefix(circuitData.name);
1476
+ // filenames
1477
+ const doubleDigitsPowers = convertToDoubleDigits(metadata.pot);
1478
+ const r1csCompleteFilename = `${circuitData.name}.r1cs`;
1479
+ const wasmCompleteFilename = `${circuitData.name}.wasm`;
1480
+ const smallestPowersOfTauCompleteFilenameForCircuit = `${potFilenameTemplate}${doubleDigitsPowers}.ptau`;
1481
+ const firstZkeyCompleteFilename = `${circuitPrefix}_${genesisZkeyIndex}.zkey`;
1482
+ // storage paths
1483
+ const r1csStorageFilePath = getR1csStorageFilePath(circuitPrefix, r1csCompleteFilename);
1484
+ const wasmStorageFilePath = getWasmStorageFilePath(circuitPrefix, wasmCompleteFilename);
1485
+ const potStorageFilePath = getPotStorageFilePath(smallestPowersOfTauCompleteFilenameForCircuit);
1486
+ const zkeyStorageFilePath = getZkeyStorageFilePath(circuitPrefix, firstZkeyCompleteFilename);
1487
+ const files = {
1488
+ potFilename: smallestPowersOfTauCompleteFilenameForCircuit,
1489
+ r1csFilename: r1csCompleteFilename,
1490
+ wasmFilename: wasmCompleteFilename,
1491
+ initialZkeyFilename: firstZkeyCompleteFilename,
1492
+ potStoragePath: potStorageFilePath,
1493
+ r1csStoragePath: r1csStorageFilePath,
1494
+ wasmStoragePath: wasmStorageFilePath,
1495
+ initialZkeyStoragePath: zkeyStorageFilePath,
1496
+ r1csBlake2bHash
1497
+ };
1498
+ // validate that the compiler hash is a valid hash
1499
+ const { compiler } = circuitData;
1500
+ const compilerHashMatch = compiler.commitHash.match(commitHashPattern);
1501
+ if (!compilerHashMatch || compilerHashMatch.length === 0 || compilerHashMatch.length > 1)
1502
+ throw new Error("You should provide a valid commit hash of the circuit compiler.");
1503
+ // validate that the verification options are valid
1504
+ const { verification } = circuitData;
1505
+ if (verification.cfOrVm !== "CF" && verification.cfOrVm !== "VM")
1506
+ throw new Error("Please enter a valid verification mechanism: either CF or VM");
1507
+ // @todo VM parameters verification
1508
+ // if (verification['cfOrVM'] === "VM") {}
1509
+ // check that the timeout is provided for the correct configuration
1510
+ let dynamicThreshold;
1511
+ let fixedTimeWindow;
1512
+ let circuit = {};
1513
+ if (data.timeoutMechanismType === "DYNAMIC" /* CeremonyTimeoutType.DYNAMIC */) {
1514
+ if (circuitData.dynamicThreshold <= 0)
1515
+ throw new Error("The dynamic threshold should be > 0.");
1516
+ dynamicThreshold = circuitData.dynamicThreshold;
1517
+ // the Circuit data for the ceremony setup
1518
+ circuit = {
1519
+ name: circuitData.name,
1520
+ description: circuitData.description,
1521
+ prefix: circuitPrefix,
1522
+ sequencePosition: i + 1,
1523
+ metadata,
1524
+ files,
1525
+ template,
1526
+ compiler,
1527
+ verification,
1528
+ dynamicThreshold,
1529
+ avgTimings: {
1530
+ contributionComputation: 0,
1531
+ fullContribution: 0,
1532
+ verifyCloudFunction: 0
1533
+ }
1534
+ };
1535
+ }
1536
+ if (data.timeoutMechanismType === "FIXED" /* CeremonyTimeoutType.FIXED */) {
1537
+ if (circuitData.fixedTimeWindow <= 0)
1538
+ throw new Error("The fixed time window threshold should be > 0.");
1539
+ fixedTimeWindow = circuitData.fixedTimeWindow;
1540
+ // the Circuit data for the ceremony setup
1541
+ circuit = {
1542
+ name: circuitData.name,
1543
+ description: circuitData.description,
1544
+ prefix: circuitPrefix,
1545
+ sequencePosition: i + 1,
1546
+ metadata,
1547
+ files,
1548
+ template,
1549
+ compiler,
1550
+ verification,
1551
+ fixedTimeWindow,
1552
+ avgTimings: {
1553
+ contributionComputation: 0,
1554
+ fullContribution: 0,
1555
+ verifyCloudFunction: 0
1556
+ }
1557
+ };
1558
+ }
1559
+ circuits.push(circuit);
1560
+ // remove the local r1cs and wasm downloads (if used for verifying the config only vs setup)
1561
+ if (cleanup)
1562
+ fs.unlinkSync(localR1csPath);
1563
+ fs.unlinkSync(localWasmPath);
1564
+ }
1565
+ const setupData = {
1566
+ ceremonyInputData: {
1567
+ title: data.title,
1568
+ description: data.description,
1569
+ startDate: startDate.valueOf(),
1570
+ endDate: endDate.valueOf(),
1571
+ timeoutMechanismType: data.timeoutMechanismType,
1572
+ penalty: data.penalty
1573
+ },
1574
+ ceremonyPrefix: extractPrefix(data.title),
1575
+ circuits,
1576
+ circuitArtifacts
1577
+ };
1578
+ return setupData;
1579
+ }
1580
+ catch (error) {
1581
+ throw new Error(`Error while parsing up the ceremony setup file. ${error.message}`);
1582
+ }
1583
+ };
1584
+
1585
+ /**
1586
+ * Verify that a zKey is valid
1587
+ * @param r1csLocalFilePath <string> path to the r1cs file
1588
+ * @param zkeyLocalPath <string> path to the zKey file
1589
+ * @param potLocalFilePath <string> path to the PoT file
1590
+ * @param logger <any> logger instance
1591
+ * @returns <boolean> true if the zKey is valid, false otherwise
1592
+ */
1593
+ const verifyZKey = async (r1csLocalFilePath, zkeyLocalPath, potLocalFilePath, logger) => {
1594
+ if (!fs.existsSync(r1csLocalFilePath))
1595
+ throw new Error(`R1CS file not found at ${r1csLocalFilePath}`);
1596
+ if (!fs.existsSync(zkeyLocalPath))
1597
+ throw new Error(`zKey file not found at ${zkeyLocalPath}`);
1598
+ if (!fs.existsSync(potLocalFilePath))
1599
+ throw new Error(`PoT file not found at ${potLocalFilePath}`);
1600
+ const res = await zKey.verifyFromR1cs(r1csLocalFilePath, potLocalFilePath, zkeyLocalPath, logger);
1601
+ return res;
1602
+ };
1603
+ /**
1604
+ * Generates a GROTH16 proof
1605
+ * @param circuitInput <object> Input to the circuit
1606
+ * @param zkeyFilePath <string> Path to the proving key
1607
+ * @param wasmFilePath <string> Path to the compiled circuit
1608
+ * @param logger <any> Optional logger
1609
+ * @returns <Promise<object>> The proof
1610
+ */
1611
+ const generateGROTH16Proof = async (circuitInput, zkeyFilePath, wasmFilePath, logger) => {
1612
+ try {
1613
+ const { proof, publicSignals } = await groth16.fullProve(circuitInput, wasmFilePath, zkeyFilePath, logger);
1614
+ return {
1615
+ proof,
1616
+ publicSignals
1617
+ };
1618
+ }
1619
+ catch (error) {
1620
+ throw new Error("There was an error while generating a proof. Please check that the input is correct, as well as the required system paths; and please try again.");
1621
+ }
1622
+ };
1623
+ /**
1624
+ * Verifies a GROTH16 proof
1625
+ * @param verificationKeyPath <string> Path to the verification key
1626
+ * @param publicSignals <object> Public signals
1627
+ * @param proof <object> Proof
1628
+ * @returns <Promise<boolean>> Whether the proof is valid or not
1629
+ */
1630
+ const verifyGROTH16Proof = async (verificationKeyPath, publicSignals, proof) => {
1631
+ const verificationKey = JSON.parse(fs.readFileSync(verificationKeyPath).toString());
1632
+ const success = await groth16.verify(verificationKey, publicSignals, proof);
1633
+ return success;
1634
+ };
1635
+ /**
1636
+ * Helper method to extract the Solidity verifier
1637
+ * from a final zKey file and save it to a local file.
1638
+ * @param finalZkeyPath <string> The path to the zKey file.
1639
+ * @return <any> The Solidity verifier code.
1640
+ */
1641
+ const exportVerifierContract = async (finalZkeyPath, templatePath) => {
1642
+ // Extract verifier.
1643
+ let verifierCode = await zKey.exportSolidityVerifier(finalZkeyPath, {
1644
+ groth16: fs.readFileSync(templatePath).toString()
1645
+ }, console);
1646
+ // Update solidity version.
1647
+ verifierCode = verifierCode.replace(/pragma solidity \^\d+\.\d+\.\d+/, `pragma solidity ^${solidityVersion}`);
1648
+ return verifierCode;
1649
+ };
1650
+ /**
1651
+ * Helpers method to extract the vKey from a final zKey file
1652
+ * @param finalZkeyPath <string> The path to the zKey file.
1653
+ * @return <any> The vKey.
1654
+ */
1655
+ const exportVkey = async (finalZkeyPath) => {
1656
+ const verificationKeyJSONData = await zKey.exportVerificationKey(finalZkeyPath);
1657
+ return verificationKeyJSONData;
1658
+ };
1659
+ /**
1660
+ * Helper method to extract the Solidity verifier and the Verification key
1661
+ * from a final zKey file and save them to local files.
1662
+ * @param finalZkeyPath <string> The path to the zKey file.
1663
+ * @param verifierLocalPath <string> The path to the local file where the verifier will be saved.
1664
+ * @param vKeyLocalPath <string> The path to the local file where the vKey will be saved.
1665
+ * @param templatePath <string> The path to the template file.
1666
+ */
1667
+ const exportVerifierAndVKey = async (finalZkeyPath, verifierLocalPath, vKeyLocalPath, templatePath) => {
1668
+ const verifierCode = await exportVerifierContract(finalZkeyPath, templatePath);
1669
+ fs.writeFileSync(verifierLocalPath, verifierCode);
1670
+ const verificationKeyJSONData = await exportVkey(finalZkeyPath);
1671
+ fs.writeFileSync(vKeyLocalPath, JSON.stringify(verificationKeyJSONData));
1672
+ };
1673
+ /**
1674
+ * Generate a zKey from scratch (useful to compute either the genesis or final zKey)
1675
+ * @param isFinalizing <boolean> Whether the ceremony is finalizing or not
1676
+ * @param r1csLocalPath <string> The path to the local r1cs file
1677
+ * @param potLocalPath <string> The path to the local pot file
1678
+ * @param zkeyLocalPath <string> The path to save the generated zKey
1679
+ * @param logger <any> The logger instance
1680
+ * @param finalContributionZKeyLocalPath <string> The path to the local zkey file of the final contribution (only for final zKey)
1681
+ * @param coordinatorIdentifier <string> The identifier of the coordinator (only for final zKey)
1682
+ * @param beacon <string> The beacon value for the last contribution (only for final zKey)
1683
+ */
1684
+ const generateZkeyFromScratch = async (isFinalizing, r1csLocalPath, potLocalPath, zkeyLocalPath, logger, finalContributionZKeyLocalPath, coordinatorIdentifier, beacon) => {
1685
+ if (!fs.existsSync(r1csLocalPath) || !fs.existsSync(potLocalPath))
1686
+ throw new Error("There was an error while opening the local files. Please make sure that you provided the right paths and try again.");
1687
+ if (isFinalizing) {
1688
+ if (!fs.existsSync(finalContributionZKeyLocalPath))
1689
+ throw new Error("There was an error while opening the last zKey generated by a contributor. Please make sure that you provided the right path and try again.");
1690
+ await zKey.beacon(finalContributionZKeyLocalPath, zkeyLocalPath, coordinatorIdentifier, beacon, numExpIterations, logger);
1691
+ }
1692
+ else
1693
+ await zKey.newZKey(r1csLocalPath, potLocalPath, zkeyLocalPath, logger);
1694
+ };
1695
+ /**
1696
+ * Helper function used to compare two ceremony artifacts
1697
+ * @param firebaseFunctions <Functions> Firebase functions object
1698
+ * @param localPath1 <string> Local path to store the first artifact
1699
+ * @param localPath2 <string> Local path to store the second artifact
1700
+ * @param storagePath1 <string> Storage path to the first artifact
1701
+ * @param storagePath2 <string> Storage path to the second artifact
1702
+ * @param bucketName1 <string> Bucket name of the first artifact
1703
+ * @param bucketName2 <string> Bucket name of the second artifact
1704
+ * @param cleanup <boolean> Whether to delete the downloaded files or not
1705
+ * @returns <Promise<boolean>> true if the hashes match, false otherwise
1706
+ */
1707
+ const compareCeremonyArtifacts = async (firebaseFunctions, localPath1, localPath2, storagePath1, storagePath2, bucketName1, bucketName2, cleanup) => {
1708
+ // 1. download files
1709
+ await downloadCeremonyArtifact(firebaseFunctions, bucketName1, storagePath1, localPath1);
1710
+ await downloadCeremonyArtifact(firebaseFunctions, bucketName2, storagePath2, localPath2);
1711
+ // 2. compare hashes
1712
+ const res = await compareHashes(localPath1, localPath2);
1713
+ // 3. cleanup
1714
+ if (cleanup) {
1715
+ fs.unlinkSync(localPath1);
1716
+ fs.unlinkSync(localPath2);
1717
+ }
1718
+ // 4. return result
1719
+ return res;
1720
+ };
1721
+ /**
1722
+ * Given a ceremony prefix, download all the ceremony artifacts
1723
+ * @param functions <Functions> firebase functions instance
1724
+ * @param firestore <Firestore> firebase firestore instance
1725
+ * @param ceremonyPrefix <string> ceremony prefix
1726
+ * @param outputDirectory <string> output directory where to
1727
+ * @returns <Promise<CeremonyArtifacts[]>> array of ceremony artifacts
1728
+ */
1729
+ const downloadAllCeremonyArtifacts = async (functions, firestore, ceremonyPrefix, outputDirectory) => {
1730
+ // mkdir if not exists
1731
+ if (!fs.existsSync(outputDirectory)) {
1732
+ fs.mkdirSync(outputDirectory);
1733
+ }
1734
+ if (!process.env.CONFIG_CEREMONY_BUCKET_POSTFIX)
1735
+ throw new Error("CONFIG_CEREMONY_BUCKET_POSTFIX not set. Please review your env file and try again.");
1736
+ const ceremonyArtifacts = [];
1737
+ // find the ceremony given the prefix
1738
+ const ceremonyQuery = await queryCollection(firestore, commonTerms.collections.ceremonies.name, [
1739
+ where(commonTerms.collections.ceremonies.fields.prefix, "==", ceremonyPrefix)
1740
+ ]);
1741
+ // get the data
1742
+ const ceremonyData = fromQueryToFirebaseDocumentInfo(ceremonyQuery.docs);
1743
+ if (ceremonyData.length === 0)
1744
+ throw new Error("Ceremony not found. Please review your ceremony prefix and try again.");
1745
+ const ceremony = ceremonyData.at(0);
1746
+ // reconstruct the bucket name
1747
+ const bucketName = getBucketName(ceremonyPrefix, process.env.CONFIG_CEREMONY_BUCKET_POSTFIX);
1748
+ const circuits = await getCeremonyCircuits(firestore, ceremony.id);
1749
+ if (circuits.length === 0)
1750
+ throw new Error("No circuits found for this ceremony. Please review your ceremony prefix and try again.");
1751
+ // for each circuit we have to download artifacts
1752
+ for (const circuit of circuits) {
1753
+ // make a directory for storing the circuit artifacts
1754
+ const circuitDir = `${outputDirectory}/${ceremony.data.prefix}/${circuit.data.prefix}`;
1755
+ fs.mkdirSync(circuitDir, { recursive: true });
1756
+ // get all required file names in storage and for local storage
1757
+ const { potStoragePath } = circuit.data.files;
1758
+ const potLocalPath = `${circuitDir}/${circuit.data.files.potFilename}`;
1759
+ const { r1csStoragePath } = circuit.data.files;
1760
+ const r1csLocalPath = `${circuitDir}/${circuit.data.files.r1csFilename}`;
1761
+ const contributions = circuit.data.waitingQueue.completedContributions;
1762
+ const zkeyIndex = formatZkeyIndex(contributions);
1763
+ const lastZKeyStoragePath = getZkeyStorageFilePath(circuit.data.prefix, `${circuit.data.prefix}_${zkeyIndex}.zkey`);
1764
+ const lastZKeyLocalPath = `${circuitDir}/${circuit.data.prefix}_${zkeyIndex}.zkey`;
1765
+ const finalZKeyName = `${circuit.data.prefix}_${finalContributionIndex}.zkey`;
1766
+ const finalZkeyStoragePath = getZkeyStorageFilePath(circuit.data.prefix, finalZKeyName);
1767
+ const finalZKeyLocalPath = `${circuitDir}/${finalZKeyName}`;
1768
+ const verifierStoragePath = getVerifierContractStorageFilePath(circuit.data.prefix, `${verifierSmartContractAcronym}.sol`);
1769
+ const verifierLocalPath = `${circuitDir}/${circuit.data.prefix}_${verifierSmartContractAcronym}.sol`;
1770
+ const vKeyStoragePath = getVerificationKeyStorageFilePath(circuit.data.prefix, `${verificationKeyAcronym}.json`);
1771
+ const vKeyLocalPath = `${circuitDir}/${circuit.data.prefix}_${verificationKeyAcronym}.json`;
1772
+ const wasmStoragePath = getWasmStorageFilePath(circuit.data.prefix, `${circuit.data.prefix}.wasm`);
1773
+ const wasmLocalPath = `${circuitDir}/${circuit.data.prefix}.wasm`;
1774
+ // download everything
1775
+ await downloadCeremonyArtifact(functions, bucketName, potStoragePath, potLocalPath);
1776
+ await downloadCeremonyArtifact(functions, bucketName, r1csStoragePath, r1csLocalPath);
1777
+ await downloadCeremonyArtifact(functions, bucketName, lastZKeyStoragePath, lastZKeyLocalPath);
1778
+ await downloadCeremonyArtifact(functions, bucketName, finalZkeyStoragePath, finalZKeyLocalPath);
1779
+ await downloadCeremonyArtifact(functions, bucketName, verifierStoragePath, verifierLocalPath);
1780
+ await downloadCeremonyArtifact(functions, bucketName, vKeyStoragePath, vKeyLocalPath);
1781
+ await downloadCeremonyArtifact(functions, bucketName, wasmStoragePath, wasmLocalPath);
1782
+ ceremonyArtifacts.push({
1783
+ circuitPrefix: circuit.data.prefix,
1784
+ circuitId: circuit.id,
1785
+ directoryRoot: circuitDir,
1786
+ potLocalFilePath: potLocalPath,
1787
+ r1csLocalFilePath: r1csLocalPath,
1788
+ finalZkeyLocalFilePath: finalZKeyLocalPath,
1789
+ lastZkeyLocalFilePath: lastZKeyLocalPath,
1790
+ verifierLocalFilePath: verifierLocalPath,
1791
+ verificationKeyLocalFilePath: vKeyLocalPath,
1792
+ wasmLocalFilePath: wasmLocalPath
1793
+ });
1794
+ }
1795
+ return ceremonyArtifacts;
1796
+ };
1797
+ /**
1798
+ * Fetch the final contribution beacon from Firestore
1799
+ * @param firestore <Firestore> firebase firestore instance
1800
+ * @param ceremonyId <string> ceremony id
1801
+ * @param circuitId <string> circuit id
1802
+ * @param participantId <string> participant id
1803
+ * @returns <Promise<string>> final contribution beacon
1804
+ */
1805
+ const getFinalContributionBeacon = async (firestore, ceremonyId, circuitId, participantId) => {
1806
+ const contributions = await getCircuitContributionsFromContributor(firestore, ceremonyId, circuitId, participantId);
1807
+ const filtered = contributions
1808
+ .filter((contributionDocument) => contributionDocument.data.zkeyIndex === finalContributionIndex)
1809
+ .at(0);
1810
+ if (!filtered)
1811
+ throw new Error("Final contribution not found. Please check that you provided the correct input data and try again.");
1812
+ return filtered.data.beacon.value;
1813
+ };
1814
+
1815
+ /**
1816
+ * This method initialize a Firebase app if no other app has already been initialized.
1817
+ * @param options <FirebaseOptions> - an object w/ every necessary Firebase option to init app.
1818
+ * @returns <FirebaseApp> - the initialized Firebase app object.
1819
+ */
1820
+ const initializeFirebaseApp = (options) => initializeApp(options);
1821
+ /**
1822
+ * This method returns the Firestore database instance associated to the given Firebase application.
1823
+ * @param app <FirebaseApp> - the Firebase application.
1824
+ * @returns <Firestore> - the Firebase Firestore associated to the application.
1825
+ */
1826
+ const getFirestoreDatabase = (app) => getFirestore(app);
1827
+ /**
1828
+ * This method returns the Cloud Functions instance associated to the given Firebase application.
1829
+ * @param app <FirebaseApp> - the Firebase application.
1830
+ * @returns <Functions> - the Cloud Functions associated to the application.
1831
+ */
1832
+ const getFirebaseFunctions = (app) => getFunctions(app, "europe-west1");
1833
+ /**
1834
+ * Retrieve the configuration variables for the AWS services (S3, EC2).
1835
+ * @returns <AWSVariables> - the values of the AWS services configuration variables.
1836
+ */
1837
+ const getAWSVariables = () => {
1838
+ if (!process.env.AWS_ACCESS_KEY_ID ||
1839
+ !process.env.AWS_SECRET_ACCESS_KEY ||
1840
+ !process.env.AWS_REGION ||
1841
+ !process.env.AWS_INSTANCE_PROFILE_ARN ||
1842
+ !process.env.AWS_AMI_ID)
1843
+ throw new Error("Could not retrieve the AWS environment variables. Please, verify your environment configuration and retry");
1844
+ return {
1845
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
1846
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
1847
+ region: process.env.AWS_REGION || "us-east-1",
1848
+ instanceProfileArn: process.env.AWS_INSTANCE_PROFILE_ARN,
1849
+ amiId: process.env.AWS_AMI_ID
1850
+ };
1851
+ };
1852
+ /**
1853
+ * Return the core Firebase services instances (App, Database, Functions).
1854
+ * @param apiKey <string> - the API key specified in the application config.
1855
+ * @param authDomain <string> - the authDomain string specified in the application config.
1856
+ * @param projectId <string> - the projectId specified in the application config.
1857
+ * @param messagingSenderId <string> - the messagingSenderId specified in the application config.
1858
+ * @param appId <string> - the appId specified in the application config.
1859
+ * @returns <Promise<FirebaseServices>>
1860
+ */
1861
+ const initializeFirebaseCoreServices = async (apiKey, authDomain, projectId, messagingSenderId, appId) => {
1862
+ const firebaseApp = initializeFirebaseApp({
1863
+ apiKey,
1864
+ authDomain,
1865
+ projectId,
1866
+ messagingSenderId,
1867
+ appId
1868
+ });
1869
+ const firestoreDatabase = getFirestoreDatabase(firebaseApp);
1870
+ const firebaseFunctions = getFirebaseFunctions(firebaseApp);
1871
+ return {
1872
+ firebaseApp,
1873
+ firestoreDatabase,
1874
+ firebaseFunctions
1875
+ };
1876
+ };
1877
+
1878
+ /**
1879
+ * Sign in w/ OAuth 2.0 token.
1880
+ * @param firebaseApp <FirebaseApp> - the configured instance of the Firebase App in use.
1881
+ * @param credentials <OAuthCredential> - the OAuth credential generated from token exchange.
1882
+ */
1883
+ const signInToFirebaseWithCredentials = async (firebaseApp, credentials) => signInWithCredential(initializeAuth(firebaseApp), credentials);
1884
+ /**
1885
+ * Return the current authenticated user in the given Firebase Application.
1886
+ * @param firebaseApp <FirebaseApp> - the configured instance of the Firebase App in use.
1887
+ * @returns <User> - the object containing the data about the current authenticated user in the given Firebase application.
1888
+ */
1889
+ const getCurrentFirebaseAuthUser = (firebaseApp) => {
1890
+ const user = getAuth(firebaseApp).currentUser;
1891
+ if (!user)
1892
+ throw new Error(`Unable to find the user currently authenticated with Firebase. Verify that the Firebase application is properly configured and repeat user authentication before trying again.`);
1893
+ return user;
1894
+ };
1895
+ /**
1896
+ * Check if the user can claim to be a coordinator.
1897
+ * @param user <User> - the user to be checked.
1898
+ * @returns Promise<boolean> - true if the user is a coordinator, false otherwise.
1899
+ */
1900
+ const isCoordinator = async (user) => {
1901
+ const userTokenAndClaims = await user.getIdTokenResult();
1902
+ return !!userTokenAndClaims.claims.coordinator;
1903
+ };
1904
+
1905
+ /**
1906
+ * Formats part of a GROTH16 SNARK proof
1907
+ * @link adapted from SNARKJS p256 function
1908
+ * @param proofPart <any> a part of a proof to be formatted
1909
+ * @returns <string> the formatted proof part
1910
+ */
1911
+ const p256 = (proofPart) => {
1912
+ let nProofPart = proofPart.toString(16);
1913
+ while (nProofPart.length < 64)
1914
+ nProofPart = `0${nProofPart}`;
1915
+ nProofPart = `0x${nProofPart}`;
1916
+ return nProofPart;
1917
+ };
1918
+ /**
1919
+ * This function formats the calldata for Solidity
1920
+ * @link adapted from SNARKJS formatSolidityCalldata function
1921
+ * @dev this function is supposed to be called with
1922
+ * @dev the output of generateGROTH16Proof
1923
+ * @param circuitInput <string[]> Inputs to the circuit
1924
+ * @param _proof <object> Proof
1925
+ * @returns <SolidityCalldata> The calldata formatted for Solidity
1926
+ */
1927
+ const formatSolidityCalldata = (circuitInput, _proof) => {
1928
+ try {
1929
+ const proof = utils.unstringifyBigInts(_proof);
1930
+ // format the public inputs to the circuit
1931
+ const formattedCircuitInput = [];
1932
+ for (const cInput of circuitInput) {
1933
+ formattedCircuitInput.push(p256(utils.unstringifyBigInts(cInput)));
1934
+ }
1935
+ // construct calldata
1936
+ const calldata = {
1937
+ arg1: [p256(proof.pi_a[0]), p256(proof.pi_a[1])],
1938
+ arg2: [
1939
+ [p256(proof.pi_b[0][1]), p256(proof.pi_b[0][0])],
1940
+ [p256(proof.pi_b[1][1]), p256(proof.pi_b[1][0])]
1941
+ ],
1942
+ arg3: [p256(proof.pi_c[0]), p256(proof.pi_c[1])],
1943
+ arg4: formattedCircuitInput
1944
+ };
1945
+ return calldata;
1946
+ }
1947
+ catch (error) {
1948
+ throw new Error("There was an error while formatting the calldata. Please make sure that you are calling this function with the output of the generateGROTH16Proof function, and then please try again.");
1949
+ }
1950
+ };
1951
+ /**
1952
+ * Verify a GROTH16 SNARK proof on chain
1953
+ * @param contract <Contract> The contract instance
1954
+ * @param proof <SolidityCalldata> The calldata formatted for Solidity
1955
+ * @returns <Promise<boolean>> Whether the proof is valid or not
1956
+ */
1957
+ const verifyGROTH16ProofOnChain = async (contract, proof) => {
1958
+ const res = await contract.verifyProof(proof.arg1, proof.arg2, proof.arg3, proof.arg4);
1959
+ return res;
1960
+ };
1961
+ /**
1962
+ * Compiles a contract given a path
1963
+ * @param contractPath <string> path to the verifier contract
1964
+ * @returns <Promise<any>> the compiled contract
1965
+ */
1966
+ const compileContract = async (contractPath) => {
1967
+ if (!fs.existsSync(contractPath))
1968
+ throw new Error("The contract path does not exist. Please make sure that you are passing a valid path to the contract and try again.");
1969
+ const data = fs.readFileSync(contractPath).toString();
1970
+ const input = {
1971
+ language: "Solidity",
1972
+ sources: {
1973
+ Verifier: { content: data }
1974
+ },
1975
+ settings: {
1976
+ outputSelection: {
1977
+ "*": {
1978
+ "*": ["*"]
1979
+ }
1980
+ }
1981
+ }
1982
+ };
1983
+ try {
1984
+ const compiled = JSON.parse(solc.compile(JSON.stringify(input), { import: { contents: "" } }));
1985
+ return compiled.contracts.Verifier.Verifier;
1986
+ }
1987
+ catch (error) {
1988
+ throw new Error("There was an error while compiling the smart contract. Please check that the file is not corrupted and try again.");
1989
+ }
1990
+ };
1991
+ /**
1992
+ * Deploy the verifier contract
1993
+ * @param contractFactory <ContractFactory> The contract factory
1994
+ * @returns <Promise<Contract>> The contract instance
1995
+ */
1996
+ const deployVerifierContract = async (contractPath, signer) => {
1997
+ const compiledContract = await compileContract(contractPath);
1998
+ // connect to hardhat node running locally
1999
+ const contractFactory = new ContractFactory(compiledContract.abi, compiledContract.evm.bytecode.object, signer);
2000
+ const contract = await contractFactory.deploy();
2001
+ await contract.deployed();
2002
+ return contract;
2003
+ };
2004
+ /**
2005
+ * Verify a ceremony validity
2006
+ * 1. Download all artifacts
2007
+ * 2. Verify that the zkeys are valid
2008
+ * 3. Extract the verifier and the vKey
2009
+ * 4. Generate a proof and verify it locally
2010
+ * 5. Deploy Verifier contract and verify the proof on-chain
2011
+ * @param functions <Functions> firebase functions instance
2012
+ * @param firestore <Firestore> firebase firestore instance
2013
+ * @param ceremonyPrefix <string> ceremony prefix
2014
+ * @param outputDirectory <string> output directory where to store the ceremony artifacts
2015
+ * @param circuitInputsPath <string> path to the circuit inputs file
2016
+ * @param verifierTemplatePath <string> path to the verifier template file
2017
+ * @param signer <Signer> signer for contract interaction
2018
+ * @param logger <any> logger for printing snarkjs output
2019
+ */
2020
+ const verifyCeremony = async (functions, firestore, ceremonyPrefix, outputDirectory, circuitInputsPath, verifierTemplatePath, signer, logger) => {
2021
+ // 1. download all ceremony artifacts
2022
+ const ceremonyArtifacts = await downloadAllCeremonyArtifacts(functions, firestore, ceremonyPrefix, outputDirectory);
2023
+ // if there are no ceremony artifacts, we throw an error
2024
+ if (ceremonyArtifacts.length === 0)
2025
+ throw new Error("There was an error while downloading all ceremony artifacts. Please review your ceremony prefix and try again.");
2026
+ // extract the circuit inputs
2027
+ if (!fs.existsSync(circuitInputsPath))
2028
+ throw new Error("The circuit inputs file does not exist. Please check the path and try again.");
2029
+ const circuitsInputs = JSON.parse(fs.readFileSync(circuitInputsPath).toString());
2030
+ // find the ceremony given the prefix
2031
+ const ceremonyQuery = await queryCollection(firestore, commonTerms.collections.ceremonies.name, [
2032
+ where(commonTerms.collections.ceremonies.fields.prefix, "==", ceremonyPrefix)
2033
+ ]);
2034
+ // get the ceremony data - no need to do an existence check as
2035
+ // we already checked that the ceremony exists in downloafAllCeremonyArtifacts
2036
+ const ceremonyData = fromQueryToFirebaseDocumentInfo(ceremonyQuery.docs);
2037
+ const ceremony = ceremonyData.at(0);
2038
+ // this is required to re-generate the final zKey
2039
+ const { coordinatorId } = ceremony.data;
2040
+ const ceremonyId = ceremony.id;
2041
+ // we verify each circuit separately
2042
+ for (const ceremonyArtifact of ceremonyArtifacts) {
2043
+ // get the index of the circuit in the list of circuits
2044
+ const inputIndex = ceremonyArtifacts.indexOf(ceremonyArtifact);
2045
+ // 2. verify the final zKey
2046
+ const isValid = await verifyZKey(ceremonyArtifact.r1csLocalFilePath, ceremonyArtifact.finalZkeyLocalFilePath, ceremonyArtifact.potLocalFilePath, logger);
2047
+ if (!isValid)
2048
+ throw new Error(`The zkey for Circuit ${ceremonyArtifact.circuitPrefix} is not valid. Please check that the artifact is correct. If not, you might have to re run the final contribution to compute a valid final zKey.`);
2049
+ // 3. get the final contribution beacon
2050
+ const contributionBeacon = await getFinalContributionBeacon(firestore, ceremonyId, ceremonyArtifact.circuitId, coordinatorId);
2051
+ const generatedFinalZkeyPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${finalContributionIndex}_verification.zkey`;
2052
+ // 4. re generate the zkey using the beacon and check hashes
2053
+ await generateZkeyFromScratch(true, ceremonyArtifact.r1csLocalFilePath, ceremonyArtifact.potLocalFilePath, generatedFinalZkeyPath, logger, ceremonyArtifact.lastZkeyLocalFilePath, coordinatorId, contributionBeacon);
2054
+ const zKeysMatching = await compareHashes(generatedFinalZkeyPath, ceremonyArtifact.finalZkeyLocalFilePath);
2055
+ if (!zKeysMatching)
2056
+ throw new Error(`The final zkey for the Circuit ${ceremonyArtifact.circuitPrefix} does not match the one generated from the beacon. Please confirm manually by downloading from the S3 bucket.`);
2057
+ // 5. extract the verifier and the vKey
2058
+ const verifierLocalPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${verifierSmartContractAcronym}_verification.sol`;
2059
+ const vKeyLocalPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${verificationKeyAcronym}_verification.json`;
2060
+ await exportVerifierAndVKey(ceremonyArtifact.finalZkeyLocalFilePath, verifierLocalPath, vKeyLocalPath, verifierTemplatePath);
2061
+ // 6. verify that the generated verifier and vkey match the ones downloaded from S3
2062
+ const verifierMatching = await compareHashes(verifierLocalPath, ceremonyArtifact.verifierLocalFilePath);
2063
+ if (!verifierMatching)
2064
+ throw new Error(`The verifier contract for the Contract ${ceremonyArtifact.circuitPrefix} does not match the one downloaded from S3. Please confirm manually by downloading from the S3 bucket.`);
2065
+ const vKeyMatching = await compareHashes(vKeyLocalPath, ceremonyArtifact.verificationKeyLocalFilePath);
2066
+ if (!vKeyMatching)
2067
+ throw new Error(`The verification key for the Contract ${ceremonyArtifact.circuitPrefix} does not match the one downloaded from S3. Please confirm manually by downloading from the S3 bucket.`);
2068
+ // 7. generate a proof and verify it locally (use either of the downloaded or generated as the hashes will have matched at this point)
2069
+ const { proof, publicSignals } = await generateGROTH16Proof(circuitsInputs[inputIndex], ceremonyArtifact.finalZkeyLocalFilePath, ceremonyArtifact.wasmLocalFilePath, logger);
2070
+ const isProofValid = await verifyGROTH16Proof(vKeyLocalPath, publicSignals, proof);
2071
+ if (!isProofValid)
2072
+ throw new Error(`Could not verify the proof for Circuit ${ceremonyArtifact.circuitPrefix}. Please check that the artifacts are correct as well as the inputs to the circuit, and try again.`);
2073
+ // 8. deploy Verifier contract and verify the proof on-chain
2074
+ const verifierContract = await deployVerifierContract(verifierLocalPath, signer);
2075
+ const formattedProof = await formatSolidityCalldata(publicSignals, proof);
2076
+ const isProofValidOnChain = await verifyGROTH16ProofOnChain(verifierContract, formattedProof);
2077
+ if (!isProofValidOnChain)
2078
+ throw new Error(`Could not verify the proof on-chain for Circuit ${ceremonyArtifact.circuitPrefix}. Please check that the artifacts are correct as well as the inputs to the circuit, and try again.`);
2079
+ }
2080
+ };
2081
+
2082
+ /**
2083
+ * This function queries the GitHub API to fetch users statistics
2084
+ * @param user {string} the user uid
2085
+ * @returns {any} the stats from the GitHub API
2086
+ */
2087
+ const getGitHubStats = async (user) => {
2088
+ const response = await fetch(`https://api.github.com/user/${user}`, {
2089
+ method: "GET",
2090
+ headers: {
2091
+ Authorization: `token ${process.env.GITHUB_ACCESS_TOKEN}`
2092
+ }
2093
+ });
2094
+ if (response.status !== 200)
2095
+ throw new Error("It was not possible to retrieve the user's statistic. Please try again.");
2096
+ const jsonData = await response.json();
2097
+ const data = {
2098
+ following: jsonData.following,
2099
+ followers: jsonData.followers,
2100
+ publicRepos: jsonData.public_repos,
2101
+ avatarUrl: jsonData.avatar_url,
2102
+ age: jsonData.created_at
2103
+ };
2104
+ return data;
2105
+ };
2106
+ /**
2107
+ * This function will check if the user is reputable enough to be able to use the app
2108
+ * @param userLogin <string> The username of the user
2109
+ * @param minimumAmountOfFollowing <number> The minimum amount of following the user should have
2110
+ * @param minimumAmountOfFollowers <number> The minimum amount of followers the user should have
2111
+ * @param minimumAmountOfPublicRepos <number> The minimum amount of public repos the user should have
2112
+ * @returns <any> Return the avatar URL of the user if the user is reputable, false otherwise
2113
+ */
2114
+ const githubReputation = async (userLogin, minimumAmountOfFollowing, minimumAmountOfFollowers, minimumAmountOfPublicRepos, minimumAge) => {
2115
+ if (!process.env.GITHUB_ACCESS_TOKEN)
2116
+ throw new Error("The GitHub access token is missing. Please insert a valid token to be used for anti-sybil checks on user registation, and then try again.");
2117
+ const { following, followers, publicRepos, avatarUrl, age } = await getGitHubStats(userLogin);
2118
+ if (following < minimumAmountOfFollowing ||
2119
+ publicRepos < minimumAmountOfPublicRepos ||
2120
+ followers < minimumAmountOfFollowers ||
2121
+ new Date(age) > new Date(Date.now() - minimumAge))
2122
+ return {
2123
+ reputable: false,
2124
+ avatarUrl: ""
2125
+ };
2126
+ return {
2127
+ reputable: true,
2128
+ avatarUrl
2129
+ };
2130
+ };
2131
+
2132
+ /**
2133
+ * Define different states of a ceremony.
2134
+ * @enum {string}
2135
+ * - SCHEDULED: when the ceremony setup has been properly completed but the contribution period has not yet started.
2136
+ * - OPENED: when the contribution period has started.
2137
+ * - PAUSED: When the coordinator has manually paused the ceremony (NB. currently not possible because the relevant functionality has not yet been implemented).
2138
+ * - CLOSED: when the contribution period has finished.
2139
+ * - FINALIZED: when the ceremony finalization has been properly completed.
2140
+ */
2141
+ var CeremonyState;
2142
+ (function (CeremonyState) {
2143
+ CeremonyState["SCHEDULED"] = "SCHEDULED";
2144
+ CeremonyState["OPENED"] = "OPENED";
2145
+ CeremonyState["PAUSED"] = "PAUSED";
2146
+ CeremonyState["CLOSED"] = "CLOSED";
2147
+ CeremonyState["FINALIZED"] = "FINALIZED";
2148
+ })(CeremonyState || (CeremonyState = {}));
2149
+ /**
2150
+ * Define the type of Trusted Setup ceremony (Phase 1 or Phase 2).
2151
+ * @enum {string}
2152
+ * - PHASE1: when the ceremony is a Phase 1 Trusted Setup ceremony.
2153
+ * - PHASE2: when the ceremony is a Phase 2 Trusted Setup ceremony.
2154
+ */
2155
+ var CeremonyType;
2156
+ (function (CeremonyType) {
2157
+ CeremonyType["PHASE1"] = "PHASE1";
2158
+ CeremonyType["PHASE2"] = "PHASE2";
2159
+ })(CeremonyType || (CeremonyType = {}));
2160
+ /**
2161
+ * Define different status of a participant.
2162
+ * @enum {string}
2163
+ * - CREATED: when the participant document has been created in the database.
2164
+ * - WAITING: when the participant is waiting for a contribution (i.e., is currently queued or is waiting for its status to be checked after a timeout expiration).
2165
+ * - READY: when the participant is ready for a contribution.
2166
+ * - CONTRIBUTING: when the participant is currently contributing (i.e., not queued anymore, but the current contributor at this time).
2167
+ * - CONTRIBUTED: when the participant has completed successfully the contribution for all circuits in a ceremony. The participant may need to wait for the latest contribution verification while having this status.
2168
+ * - DONE: when the participant has completed contributions and verifications from coordinator.
2169
+ * - FINALIZING: when the coordinator is currently finalizing the ceremony.
2170
+ * - FINALIZED: when the coordinator has successfully finalized the ceremony.
2171
+ * - TIMEDOUT: when the participant has been timedout while contributing. This may happen due to network or memory issues, un/intentional crash, or contributions lasting for too long.
2172
+ * - EXHUMED: when the participant is ready to resume the contribution after a timeout expiration.
2173
+ */
2174
+ var ParticipantStatus;
2175
+ (function (ParticipantStatus) {
2176
+ ParticipantStatus["CREATED"] = "CREATED";
2177
+ ParticipantStatus["WAITING"] = "WAITING";
2178
+ ParticipantStatus["READY"] = "READY";
2179
+ ParticipantStatus["CONTRIBUTING"] = "CONTRIBUTING";
2180
+ ParticipantStatus["CONTRIBUTED"] = "CONTRIBUTED";
2181
+ ParticipantStatus["DONE"] = "DONE";
2182
+ ParticipantStatus["FINALIZING"] = "FINALIZING";
2183
+ ParticipantStatus["FINALIZED"] = "FINALIZED";
2184
+ ParticipantStatus["TIMEDOUT"] = "TIMEDOUT";
2185
+ ParticipantStatus["EXHUMED"] = "EXHUMED";
2186
+ })(ParticipantStatus || (ParticipantStatus = {}));
2187
+ /**
2188
+ * Define different steps during which the participant may be during the contribution.
2189
+ * @enum {string}
2190
+ * - DOWNLOADING: when the participant is doing the download of the last contribution (from previous participant).
2191
+ * - COMPUTING: when the participant is actively computing the contribution.
2192
+ * - UPLOADING: when the participant is uploading the computed contribution.
2193
+ * - VERIFYING: when the participant is waiting from verification results from the coordinator.
2194
+ * - COMPLETED: when the participant has received the verification results from the coordinator and completed the contribution steps.
2195
+ */
2196
+ var ParticipantContributionStep;
2197
+ (function (ParticipantContributionStep) {
2198
+ ParticipantContributionStep["DOWNLOADING"] = "DOWNLOADING";
2199
+ ParticipantContributionStep["COMPUTING"] = "COMPUTING";
2200
+ ParticipantContributionStep["UPLOADING"] = "UPLOADING";
2201
+ ParticipantContributionStep["VERIFYING"] = "VERIFYING";
2202
+ ParticipantContributionStep["COMPLETED"] = "COMPLETED";
2203
+ })(ParticipantContributionStep || (ParticipantContributionStep = {}));
2204
+ /**
2205
+ * Define what type of timeout was performed.
2206
+ * @enum {string}
2207
+ * - BLOCKING_CONTRIBUTION: when the current contributor was blocking the waiting queue.
2208
+ * - BLOCKING_CLOUD_FUNCTION: when the contribution verification has gone beyond the time limit.
2209
+ */
2210
+ var TimeoutType;
2211
+ (function (TimeoutType) {
2212
+ TimeoutType["BLOCKING_CONTRIBUTION"] = "BLOCKING_CONTRIBUTION";
2213
+ TimeoutType["BLOCKING_CLOUD_FUNCTION"] = "BLOCKING_CLOUD_FUNCTION";
2214
+ })(TimeoutType || (TimeoutType = {}));
2215
+ /**
2216
+ * Define what type of timeout mechanism is currently adopted for a ceremony.
2217
+ * @enum {string}
2218
+ * - DYNAMIC: self-update approach based on latest contribution time.
2219
+ * - FIXED: approach based on a fixed amount of time.
2220
+ */
2221
+ var CeremonyTimeoutType;
2222
+ (function (CeremonyTimeoutType) {
2223
+ CeremonyTimeoutType["DYNAMIC"] = "DYNAMIC";
2224
+ CeremonyTimeoutType["FIXED"] = "FIXED";
2225
+ })(CeremonyTimeoutType || (CeremonyTimeoutType = {}));
2226
+ /**
2227
+ * Define request type for pre-signed urls.
2228
+ */
2229
+ var RequestType;
2230
+ (function (RequestType) {
2231
+ RequestType["PUT"] = "PUT";
2232
+ RequestType["GET"] = "GET";
2233
+ })(RequestType || (RequestType = {}));
2234
+ /**
2235
+ * Define the environment in use when testing.
2236
+ * @enum {string}
2237
+ * - DEVELOPMENT: tests are performed on the local Firebase emulator instance.
2238
+ * - PRODUCTION: tests are performed on the remote (deployed) Firebase application.
2239
+ */
2240
+ var TestingEnvironment;
2241
+ (function (TestingEnvironment) {
2242
+ TestingEnvironment["DEVELOPMENT"] = "DEVELOPMENT";
2243
+ TestingEnvironment["PRODUCTION"] = "PRODUCTION";
2244
+ })(TestingEnvironment || (TestingEnvironment = {}));
2245
+ /**
2246
+ * Define what type of contribution verification mechanism is currently adopted for a circuit.
2247
+ * @enum {string}
2248
+ * - CF: Cloud Functions.
2249
+ * - VM: Virtual Machine.
2250
+ */
2251
+ var CircuitContributionVerificationMechanism;
2252
+ (function (CircuitContributionVerificationMechanism) {
2253
+ CircuitContributionVerificationMechanism["CF"] = "CF";
2254
+ CircuitContributionVerificationMechanism["VM"] = "VM";
2255
+ })(CircuitContributionVerificationMechanism || (CircuitContributionVerificationMechanism = {}));
2256
+ /**
2257
+ * Define the supported VM volume types.
2258
+ * @dev the VM volume types can be retrieved at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html
2259
+ * @enum {string}
2260
+ * - GP2: General Purpose SSD version 2.
2261
+ * - GP3: General Purpose SSD version 3.
2262
+ * - IO1: Provisioned IOPS SSD volumes version 1.
2263
+ * - ST1: Throughput Optimized HDD volumes.
2264
+ * - SC1: Cold HDD volumes.
2265
+ */
2266
+ var DiskTypeForVM;
2267
+ (function (DiskTypeForVM) {
2268
+ DiskTypeForVM["GP2"] = "gp2";
2269
+ DiskTypeForVM["GP3"] = "gp3";
2270
+ DiskTypeForVM["IO1"] = "io1";
2271
+ DiskTypeForVM["ST1"] = "st1";
2272
+ DiskTypeForVM["SC1"] = "sc1";
2273
+ })(DiskTypeForVM || (DiskTypeForVM = {}));
2274
+
2275
+ dotenv.config();
2276
+ /**
2277
+ * Create a new AWS EC2 client.
2278
+ * @returns <Promise<EC2Client>> - the EC2 client instance.
2279
+ */
2280
+ const createEC2Client = async () => {
2281
+ // Get the AWS variables.
2282
+ const { accessKeyId, secretAccessKey, region } = getAWSVariables();
2283
+ // Instantiate the new client.
2284
+ return new EC2Client({
2285
+ credentials: {
2286
+ accessKeyId,
2287
+ secretAccessKey
2288
+ },
2289
+ region
2290
+ });
2291
+ };
2292
+ /**
2293
+ * Create a new AWS SSM client.
2294
+ * @returns <Promise<SSMClient>> - the SSM client instance.
2295
+ */
2296
+ const createSSMClient = async () => {
2297
+ // Get the AWS variables.
2298
+ const { accessKeyId, secretAccessKey, region } = getAWSVariables();
2299
+ // Instantiate the new client.
2300
+ return new SSMClient({
2301
+ credentials: {
2302
+ accessKeyId,
2303
+ secretAccessKey
2304
+ },
2305
+ region
2306
+ });
2307
+ };
2308
+ /**
2309
+ * Return the list of bootstrap commands to be executed.
2310
+ * @dev the startup commands must be suitable for a shell script.
2311
+ * @param bucketName <string> - the name of the AWS S3 bucket.
2312
+ * @returns <Array<string>> - the list of startup commands to be executed.
2313
+ */
2314
+ const vmBootstrapCommand = (bucketName) => [
2315
+ "#!/bin/bash",
2316
+ `aws s3 cp s3://${bucketName}/${vmBootstrapScriptFilename} ${vmBootstrapScriptFilename}`,
2317
+ `chmod +x ${vmBootstrapScriptFilename} && bash ${vmBootstrapScriptFilename}` // grant permission and execute.
2318
+ ];
2319
+ /**
2320
+ * Return the list of Node environment (and packages) installation plus artifact caching for contribution verification.
2321
+ * @param zKeyPath <string> - the path to zKey artifact inside AWS S3 bucket.
2322
+ * @param potPath <string> - the path to ptau artifact inside AWS S3 bucket.
2323
+ * @param snsTopic <string> - the SNS topic ARN.
2324
+ * @param region <string> - the AWS region.
2325
+ * @returns <Array<string>> - the array of commands to be run by the EC2 instance.
2326
+ */
2327
+ const vmDependenciesAndCacheArtifactsCommand = (zKeyPath, potPath, snsTopic, region) => [
2328
+ "#!/bin/bash",
2329
+ 'MARKER_FILE="/var/run/my_script_ran"',
2330
+ // eslint-disable-next-line no-template-curly-in-string
2331
+ "if [ -e ${MARKER_FILE} ]; then",
2332
+ "exit 0",
2333
+ "else",
2334
+ // eslint-disable-next-line no-template-curly-in-string
2335
+ "touch ${MARKER_FILE}",
2336
+ "sudo yum update -y",
2337
+ "curl -O https://nodejs.org/dist/v16.13.0/node-v16.13.0-linux-x64.tar.xz",
2338
+ "tar -xf node-v16.13.0-linux-x64.tar.xz",
2339
+ "mv node-v16.13.0-linux-x64 nodejs",
2340
+ "sudo mv nodejs /opt/",
2341
+ "echo 'export NODEJS_HOME=/opt/nodejs' >> /etc/profile",
2342
+ "echo 'export PATH=$NODEJS_HOME/bin:$PATH' >> /etc/profile",
2343
+ "source /etc/profile",
2344
+ "npm install -g snarkjs",
2345
+ `aws s3 cp s3://${zKeyPath} /var/tmp/genesisZkey.zkey`,
2346
+ `aws s3 cp s3://${potPath} /var/tmp/pot.ptau`,
2347
+ "wget https://github.com/BLAKE3-team/BLAKE3/releases/download/1.4.0/b3sum_linux_x64_bin -O /var/tmp/blake3.bin",
2348
+ "chmod +x /var/tmp/blake3.bin",
2349
+ "INSTANCE_ID=$(ec2-metadata -i | awk '{print $2}')",
2350
+ `aws sns publish --topic-arn ${snsTopic} --message "$INSTANCE_ID" --region ${region}`,
2351
+ "fi"
2352
+ ];
2353
+ /**
2354
+ * Return the list of commands for contribution verification.
2355
+ * @dev this method generates the verification transcript as well.
2356
+ * @param bucketName <string> - the name of the AWS S3 bucket.
2357
+ * @param lastZkeyStoragePath <string> - the last zKey storage path.
2358
+ * @param verificationTranscriptStoragePathAndFilename <string> - the verification transcript storage path.
2359
+ * @returns Array<string> - the list of commands for contribution verification.
2360
+ */
2361
+ const vmContributionVerificationCommand = (bucketName, lastZkeyStoragePath, verificationTranscriptStoragePathAndFilename) => [
2362
+ `source /etc/profile`,
2363
+ `aws s3 cp s3://${bucketName}/${lastZkeyStoragePath} /var/tmp/lastZKey.zkey > /var/tmp/log.txt`,
2364
+ `snarkjs zkvi /var/tmp/genesisZkey.zkey /var/tmp/pot.ptau /var/tmp/lastZKey.zkey > /var/tmp/verification_transcript.log`,
2365
+ `aws s3 cp /var/tmp/verification_transcript.log s3://${bucketName}/${verificationTranscriptStoragePathAndFilename} &>/dev/null`,
2366
+ `/var/tmp/blake3.bin /var/tmp/verification_transcript.log | awk '{print $1}'`,
2367
+ `rm /var/tmp/lastZKey.zkey /var/tmp/verification_transcript.log /var/tmp/log.txt &>/dev/null`
2368
+ ];
2369
+ /**
2370
+ * Compute the VM disk size.
2371
+ * @dev the disk size is computed using the zKey size in bytes taking into consideration
2372
+ * the verification task (2 * zKeySize) + ptauSize + OS/VM (~8GB).
2373
+ * @param zKeySizeInBytes <number> the size of the zKey in bytes.
2374
+ * @param pot <number> the amount of powers needed for the circuit (index of the PPoT file).
2375
+ * @return <number> the configuration of the VM disk size in GB.
2376
+ */
2377
+ const computeDiskSizeForVM = (zKeySizeInBytes, pot) => Math.ceil(2 * convertBytesOrKbToGb(zKeySizeInBytes, true) + powersOfTauFiles[pot - 1].size) + 8;
2378
+ /**
2379
+ * Creates a new EC2 instance
2380
+ * @param ec2 <EC2Client> - the instance of the EC2 client.
2381
+ * @param commands <Array<string>> - the list of commands to be run on the EC2 instance.
2382
+ * @param instanceType <string> - the type of the EC2 VM instance.
2383
+ * @param diskSize <number> - the size of the disk (volume) of the VM.
2384
+ * @param diskType <DiskTypeForVM> - the type of the disk (volume) of the VM.
2385
+ * @returns <Promise<P0tionEC2Instance>> the instance that was created
2386
+ */
2387
+ const createEC2Instance = async (ec2, commands, instanceType, volumeSize, diskType) => {
2388
+ // Get the AWS variables.
2389
+ const { amiId, instanceProfileArn } = getAWSVariables();
2390
+ // Parametrize the VM EC2 instance.
2391
+ const params = {
2392
+ ImageId: amiId,
2393
+ InstanceType: instanceType,
2394
+ MaxCount: 1,
2395
+ MinCount: 1,
2396
+ // nb. to find this: iam -> roles -> role_name.
2397
+ IamInstanceProfile: {
2398
+ Arn: instanceProfileArn
2399
+ },
2400
+ // nb. for running commands at the startup.
2401
+ UserData: Buffer.from(commands.join("\n")).toString("base64"),
2402
+ BlockDeviceMappings: [
2403
+ {
2404
+ DeviceName: "/dev/xvda",
2405
+ Ebs: {
2406
+ DeleteOnTermination: true,
2407
+ VolumeSize: volumeSize,
2408
+ VolumeType: diskType
2409
+ }
2410
+ }
2411
+ ],
2412
+ // tag the resource
2413
+ TagSpecifications: [
2414
+ {
2415
+ ResourceType: "instance",
2416
+ Tags: [
2417
+ {
2418
+ Key: "Name",
2419
+ Value: ec2InstanceTag
2420
+ },
2421
+ {
2422
+ Key: "Initialized",
2423
+ Value: "false"
2424
+ }
2425
+ ]
2426
+ }
2427
+ ]
2428
+ };
2429
+ try {
2430
+ // Create a new command instance.
2431
+ const command = new RunInstancesCommand(params);
2432
+ // Send the command for execution.
2433
+ const response = await ec2.send(command);
2434
+ if (response.$metadata.httpStatusCode !== 200)
2435
+ throw new Error(`Something went wrong when creating the EC2 instance. More details ${response}`);
2436
+ // Create a new EC2 VM instance.
2437
+ return {
2438
+ instanceId: response.Instances[0].InstanceId,
2439
+ imageId: response.Instances[0].ImageId,
2440
+ instanceType: response.Instances[0].InstanceType,
2441
+ keyName: response.Instances[0].KeyName,
2442
+ launchTime: response.Instances[0].LaunchTime.toISOString()
2443
+ };
2444
+ }
2445
+ catch (error) {
2446
+ throw new Error(`Something went wrong when creating the EC2 instance. More details ${error}`);
2447
+ }
2448
+ };
2449
+ /**
2450
+ * Check if the current VM EC2 instance is running by querying the status.
2451
+ * @param ec2 <EC2Client> - the instance of the EC2 client.
2452
+ * @param instanceId <string> - the unique identifier of the EC2 VM instance.
2453
+ * @returns <Promise<boolean>> - true if the current status of the EC2 VM instance is 'running'; otherwise false.
2454
+ */
2455
+ const checkIfRunning = async (ec2Client, instanceId) => {
2456
+ // Generate a new describe status command.
2457
+ const command = new DescribeInstanceStatusCommand({
2458
+ InstanceIds: [instanceId]
2459
+ });
2460
+ // Run the command.
2461
+ const response = await ec2Client.send(command);
2462
+ if (response.$metadata.httpStatusCode !== 200)
2463
+ throw new Error(`Something went wrong when retrieving the EC2 instance (${instanceId}) status. More details ${response}`);
2464
+ return response.InstanceStatuses[0].InstanceState.Name === "running";
2465
+ };
2466
+ /**
2467
+ * Start an EC2 VM instance.
2468
+ * @dev the instance must have been created previously.
2469
+ * @param ec2 <EC2Client> - the instance of the EC2 client.
2470
+ * @param instanceId <string> - the unique identifier of the EC2 VM instance.
2471
+ */
2472
+ const startEC2Instance = async (ec2, instanceId) => {
2473
+ // Generate a new start instance command.
2474
+ const command = new StartInstancesCommand({
2475
+ InstanceIds: [instanceId],
2476
+ DryRun: false
2477
+ });
2478
+ // Run the command.
2479
+ const response = await ec2.send(command);
2480
+ if (response.$metadata.httpStatusCode !== 200)
2481
+ throw new Error(`Something went wrong when starting the EC2 instance (${instanceId}). More details ${response}`);
2482
+ };
2483
+ /**
2484
+ * Stop an EC2 VM instance.
2485
+ * @dev the instance must have been in a running status.
2486
+ * @param ec2 <EC2Client> - the instance of the EC2 client.
2487
+ * @param instanceId <string> - the unique identifier of the EC2 VM instance.
2488
+ */
2489
+ const stopEC2Instance = async (ec2, instanceId) => {
2490
+ // Generate a new stop instance command.
2491
+ const command = new StopInstancesCommand({
2492
+ InstanceIds: [instanceId],
2493
+ DryRun: false
2494
+ });
2495
+ // Run the command.
2496
+ const response = await ec2.send(command);
2497
+ if (response.$metadata.httpStatusCode !== 200)
2498
+ throw new Error(`Something went wrong when stopping the EC2 instance (${instanceId}). More details ${response}`);
2499
+ };
2500
+ /**
2501
+ * Terminate an EC2 VM instance.
2502
+ * @param ec2 <EC2Client> - the instance of the EC2 client.
2503
+ * @param instanceId <string> - the unique identifier of the EC2 VM instance.
2504
+ */
2505
+ const terminateEC2Instance = async (ec2, instanceId) => {
2506
+ // Generate a new terminate instance command.
2507
+ const command = new TerminateInstancesCommand({
2508
+ InstanceIds: [instanceId],
2509
+ DryRun: false
2510
+ });
2511
+ // Run the command.
2512
+ const response = await ec2.send(command);
2513
+ if (response.$metadata.httpStatusCode !== 200)
2514
+ throw new Error(`Something went wrong when terminating the EC2 instance (${instanceId}). More details ${response}`);
2515
+ };
2516
+ /**
2517
+ * Run a command on an EC2 VM instance by using SSM.
2518
+ * @dev this method returns the command identifier for checking the status and retrieve
2519
+ * the output of the command execution later on.
2520
+ * @param ssm <SSMClient> - the instance of the sSM client.
2521
+ * @param instanceId <string> - the unique identifier of the EC2 VM instance.
2522
+ * @param commands <Array<string>> - the list of commands.
2523
+ * @return <Promise<string>> - the unique identifier of the command.
2524
+ */
2525
+ const runCommandUsingSSM = async (ssm, instanceId, commands) => {
2526
+ // Generate a new send command input command.
2527
+ const params = {
2528
+ DocumentName: "AWS-RunShellScript",
2529
+ InstanceIds: [instanceId],
2530
+ Parameters: {
2531
+ commands
2532
+ },
2533
+ TimeoutSeconds: 1200
2534
+ };
2535
+ try {
2536
+ // Run the command.
2537
+ const response = await ssm.send(new SendCommandCommand(params));
2538
+ // if (response.$metadata.httpStatusCode !== 200)
2539
+ // throw new Error(
2540
+ // `Something went wrong when trying to run a command on the EC2 instance (${instanceId}). More details ${response}`
2541
+ // )
2542
+ return response.Command.CommandId;
2543
+ }
2544
+ catch (error) {
2545
+ throw new Error(`Something went wrong when trying to run a command on the EC2 instance. More details ${error}`);
2546
+ }
2547
+ };
2548
+ /**
2549
+ * Get the output of an SSM command executed on an EC2 VM instance.
2550
+ * @param ssm <SSMClient> - the instance of the sSM client.
2551
+ * @param instanceId <string> - the unique identifier of the EC2 VM instance.
2552
+ * @param commandId <string> - the unique identifier of the command.
2553
+ * @return <Promise<string>> - the command output.
2554
+ */
2555
+ const retrieveCommandOutput = async (ssm, instanceId, commandId) => {
2556
+ // Generate a new get command invocation command.
2557
+ const command = new GetCommandInvocationCommand({
2558
+ CommandId: commandId,
2559
+ InstanceId: instanceId
2560
+ });
2561
+ try {
2562
+ // Run the command.
2563
+ const response = await ssm.send(command);
2564
+ return response.StandardOutputContent;
2565
+ }
2566
+ catch (error) {
2567
+ throw new Error(`Something went wrong when trying to retrieve the command ${commandId} output on the EC2 instance (${instanceId}). More details ${error}`);
2568
+ }
2569
+ };
2570
+ /**
2571
+ * Get the status of an SSM command executed on an EC2 VM instance.
2572
+ * @param ssm <SSMClient> - the instance of the sSM client.
2573
+ * @param instanceId <string> - the unique identifier of the EC2 VM instance.
2574
+ * @param commandId <string> - the unique identifier of the command.
2575
+ * @return <Promise<string>> - the command status.
2576
+ */
2577
+ const retrieveCommandStatus = async (ssm, instanceId, commandId) => {
2578
+ // Generate a new get command invocation command.
2579
+ const command = new GetCommandInvocationCommand({
2580
+ CommandId: commandId,
2581
+ InstanceId: instanceId
2582
+ });
2583
+ try {
2584
+ // Run the command.
2585
+ const response = await ssm.send(command);
2586
+ return response.Status;
2587
+ }
2588
+ catch (error) {
2589
+ throw new Error(`Something went wrong when trying to retrieve the command ${commandId} status on the EC2 instance (${instanceId}). More details ${error}`);
2590
+ }
2591
+ };
2592
+
2593
+ export { CeremonyState, CeremonyTimeoutType, CeremonyType, CircuitContributionVerificationMechanism, DiskTypeForVM, ParticipantContributionStep, ParticipantStatus, RequestType, TestingEnvironment, TimeoutType, autoGenerateEntropy, blake512FromPath, checkAndPrepareCoordinatorForFinalization, checkIfObjectExist, checkIfRunning, checkParticipantForCeremony, commonTerms, compareCeremonyArtifacts, compareHashes, compileContract, completeMultiPartUpload, computeDiskSizeForVM, computeSHA256ToHex, computeSmallestPowersOfTauForCircuit, convertBytesOrKbToGb, convertToDoubleDigits, createCustomLoggerForFile, createEC2Client, createEC2Instance, createS3Bucket, createSSMClient, downloadAllCeremonyArtifacts, downloadCeremonyArtifact, ec2InstanceTag, exportVerifierAndVKey, exportVerifierContract, exportVkey, extractPoTFromFilename, extractPrefix, extractR1CSInfoValueForGivenKey, finalContributionIndex, finalizeCeremony, finalizeCircuit, formatSolidityCalldata, formatZkeyIndex, fromQueryToFirebaseDocumentInfo, generateGROTH16Proof, generateGetObjectPreSignedUrl, generatePreSignedUrlsParts, generateValidContributionsAttestation, generateZkeyFromScratch, genesisZkeyIndex, getAllCollectionDocs, getBucketName, getCeremonyCircuits, getCircuitBySequencePosition, getCircuitContributionsFromContributor, getCircuitsCollectionPath, getClosedCeremonies, getContributionsCollectionPath, getContributionsValidityForContributor, getCurrentActiveParticipantTimeout, getCurrentFirebaseAuthUser, getDocumentById, getOpenedCeremonies, getParticipantsCollectionPath, getPotStorageFilePath, getPublicAttestationPreambleForContributor, getR1CSInfo, getR1csStorageFilePath, getTimeoutsCollectionPath, getTranscriptStorageFilePath, getVerificationKeyStorageFilePath, getVerifierContractStorageFilePath, getWasmStorageFilePath, getZkeyStorageFilePath, githubReputation, initializeFirebaseCoreServices, isCoordinator, multiPartUpload, numExpIterations, p256, parseCeremonyFile, permanentlyStoreCurrentContributionTimeAndHash, potFileDownloadMainUrl, potFilenameTemplate, powersOfTauFiles, progressToNextCircuitForContribution, progressToNextContributionStep, queryCollection, resumeContributionAfterTimeoutExpiration, retrieveCommandOutput, retrieveCommandStatus, runCommandUsingSSM, setupCeremony, signInToFirebaseWithCredentials, solidityVersion, startEC2Instance, stopEC2Instance, temporaryStoreCurrentContributionMultiPartUploadId, temporaryStoreCurrentContributionUploadedChunkData, terminateEC2Instance, toHex, verificationKeyAcronym, verifierSmartContractAcronym, verifyCeremony, verifyContribution, verifyGROTH16Proof, verifyGROTH16ProofOnChain, verifyZKey, vmBootstrapCommand, vmBootstrapScriptFilename, vmConfigurationTypes, vmContributionVerificationCommand, vmDependenciesAndCacheArtifactsCommand };