@devtion/actions 0.0.0-7e983e3

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 +2608 -0
  4. package/dist/index.node.js +2714 -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 +194 -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 +121 -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 +87 -0
  40. package/src/helpers/authentication.ts +37 -0
  41. package/src/helpers/constants.ts +312 -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 +86 -0
  47. package/src/helpers/services.ts +83 -0
  48. package/src/helpers/storage.ts +329 -0
  49. package/src/helpers/tasks.ts +56 -0
  50. package/src/helpers/utils.ts +743 -0
  51. package/src/helpers/verification.ts +354 -0
  52. package/src/helpers/vm.ts +392 -0
  53. package/src/index.ts +162 -0
  54. package/src/types/enums.ts +141 -0
  55. package/src/types/index.ts +650 -0
@@ -0,0 +1,329 @@
1
+ import { Functions } from "firebase/functions"
2
+ import mime from "mime-types"
3
+ import fs, { createWriteStream } from "fs"
4
+ import fetch from "@adobe/node-fetch-retry"
5
+ import https from "https"
6
+ import { ETagWithPartNumber, ChunkWithUrl, TemporaryParticipantContributionData } from "../types/index"
7
+ import { commonTerms } from "./constants"
8
+ import {
9
+ completeMultiPartUpload,
10
+ generateGetObjectPreSignedUrl,
11
+ generatePreSignedUrlsParts,
12
+ openMultiPartUpload,
13
+ temporaryStoreCurrentContributionMultiPartUploadId,
14
+ temporaryStoreCurrentContributionUploadedChunkData
15
+ } from "./functions"
16
+
17
+ /**
18
+ * Return the bucket name based on ceremony prefix.
19
+ * @param ceremonyPrefix <string> - the ceremony prefix.
20
+ * @param ceremonyPostfix <string> - the ceremony postfix.
21
+ * @returns <string>
22
+ */
23
+ export const getBucketName = (ceremonyPrefix: string, ceremonyPostfix: string): string =>
24
+ `${ceremonyPrefix}${ceremonyPostfix}`
25
+
26
+ /**
27
+ * Get chunks and signed urls related to an object that must be uploaded using a multi-part upload.
28
+ * @param cloudFunctions <Functions> - the Firebase Cloud Functions service instance.
29
+ * @param bucketName <string> - the name of the ceremony artifacts bucket (AWS S3).
30
+ * @param objectKey <string> - the unique key to identify the object inside the given AWS S3 bucket.
31
+ * @param localFilePath <string> - the local path where the artifact will be downloaded.
32
+ * @param uploadId <string> - the unique identifier of the multi-part upload.
33
+ * @param configStreamChunkSize <number> - size of each chunk into which the artifact is going to be splitted (nb. will be converted in MB).
34
+ * @param [ceremonyId] <string> - the unique identifier of the ceremony.
35
+ * @returns Promise<Array<ChunkWithUrl>> - the chunks with related pre-signed url.
36
+ */
37
+ export const getChunksAndPreSignedUrls = async (
38
+ cloudFunctions: Functions,
39
+ bucketName: string,
40
+ objectKey: string,
41
+ localFilePath: string,
42
+ uploadId: string,
43
+ configStreamChunkSize: number,
44
+ ceremonyId?: string
45
+ ): Promise<Array<ChunkWithUrl>> => {
46
+ // Prepare a new stream to read the file.
47
+ const stream = fs.createReadStream(localFilePath, {
48
+ highWaterMark: configStreamChunkSize * 1024 * 1024 // convert to MB.
49
+ })
50
+
51
+ // Split in chunks.
52
+ const chunks = []
53
+ for await (const chunk of stream) chunks.push(chunk)
54
+
55
+ // Check if the file is not empty.
56
+ if (!chunks.length) throw new Error("Unable to split an empty file into chunks.")
57
+
58
+ // Request pre-signed url generation for each chunk.
59
+ const preSignedUrls: Array<string> = await generatePreSignedUrlsParts(
60
+ cloudFunctions,
61
+ bucketName,
62
+ objectKey,
63
+ uploadId,
64
+ chunks.length,
65
+ ceremonyId
66
+ )
67
+
68
+ // Map pre-signed urls with corresponding chunks.
69
+ return chunks.map((val1, index) => ({
70
+ partNumber: index + 1,
71
+ chunk: val1,
72
+ preSignedUrl: preSignedUrls[index]
73
+ }))
74
+ }
75
+
76
+ /**
77
+ * Forward the request to upload each single chunk of the related ceremony artifact.
78
+ * @param chunksWithUrls <Array<ChunkWithUrl>> - the array containing each chunk mapped with the corresponding pre-signed urls.
79
+ * @param contentType <string | false> - the content type of the ceremony artifact.
80
+ * @param cloudFunctions <Functions> - the Firebase Cloud Functions service instance.
81
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
82
+ * @param alreadyUploadedChunks Array<ETagWithPartNumber> - the temporary information about the already uploaded chunks.
83
+ * @returns <Promise<Array<ETagWithPartNumber>>> - the completed (uploaded) chunks information.
84
+ */
85
+ export const uploadParts = async (
86
+ chunksWithUrls: Array<ChunkWithUrl>,
87
+ contentType: string | false,
88
+ cloudFunctions?: Functions,
89
+ ceremonyId?: string,
90
+ alreadyUploadedChunks?: Array<ETagWithPartNumber>
91
+ ): Promise<Array<ETagWithPartNumber>> => {
92
+ // Keep track of uploaded chunks.
93
+ const uploadedChunks: Array<ETagWithPartNumber> = alreadyUploadedChunks || []
94
+
95
+ // Loop through remaining chunks.
96
+ for (let i = alreadyUploadedChunks ? alreadyUploadedChunks.length : 0; i < chunksWithUrls.length; i += 1) {
97
+ // Consume the pre-signed url to upload the chunk.
98
+ // @ts-ignore
99
+ const response = await fetch(chunksWithUrls[i].preSignedUrl, {
100
+ retryOptions: {
101
+ retryInitialDelay: 500, // 500 ms.
102
+ socketTimeout: 60000, // 60 seconds.
103
+ retryMaxDuration: 300000 // 5 minutes.
104
+ },
105
+ method: "PUT",
106
+ body: chunksWithUrls[i].chunk,
107
+ headers: {
108
+ "Content-Type": contentType.toString(),
109
+ "Content-Length": chunksWithUrls[i].chunk.length.toString()
110
+ },
111
+ agent: new https.Agent({ keepAlive: true })
112
+ })
113
+
114
+ // Verify the response.
115
+ if (response.status !== 200 || !response.ok)
116
+ throw new Error(
117
+ `Unable to upload chunk number ${i}. Please, terminate the current session and retry to resume from the latest uploaded chunk.`
118
+ )
119
+
120
+ // Extract uploaded chunk data.
121
+ const chunk = {
122
+ ETag: response.headers.get("etag") || undefined,
123
+ PartNumber: chunksWithUrls[i].partNumber
124
+ }
125
+ uploadedChunks.push(chunk)
126
+
127
+ // Temporary store uploaded chunk data to enable later resumable contribution.
128
+ // nb. this must be done only when contributing (not finalizing).
129
+ if (!!ceremonyId && !!cloudFunctions)
130
+ await temporaryStoreCurrentContributionUploadedChunkData(cloudFunctions, ceremonyId, chunk)
131
+ }
132
+
133
+ return uploadedChunks
134
+ }
135
+
136
+ /**
137
+ * Upload a ceremony artifact to the corresponding bucket.
138
+ * @notice this method implements the multi-part upload using pre-signed urls, optimal for large files.
139
+ * Steps:
140
+ * 0) Check if current contributor could resume a multi-part upload.
141
+ * 0.A) If yes, continue from last uploaded chunk using the already opened multi-part upload.
142
+ * 0.B) Otherwise, start creating a new multi-part upload.
143
+ * 1) Generate a pre-signed url for each (remaining) chunk of the ceremony artifact.
144
+ * 2) Consume the pre-signed urls to upload chunks.
145
+ * 3) Complete the multi-part upload.
146
+ * @param cloudFunctions <Functions> - the Firebase Cloud Functions service instance.
147
+ * @param bucketName <string> - the name of the ceremony artifacts bucket (AWS S3).
148
+ * @param objectKey <string> - the unique key to identify the object inside the given AWS S3 bucket.
149
+ * @param localPath <string> - the local path where the artifact will be downloaded.
150
+ * @param configStreamChunkSize <number> - size of each chunk into which the artifact is going to be splitted (nb. will be converted in MB).
151
+ * @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).
152
+ * @param [temporaryDataToResumeMultiPartUpload] <TemporaryParticipantContributionData> - the temporary information necessary to resume an already started multi-part upload.
153
+ */
154
+ export const multiPartUpload = async (
155
+ cloudFunctions: Functions,
156
+ bucketName: string,
157
+ objectKey: string,
158
+ localFilePath: string,
159
+ configStreamChunkSize: number,
160
+ ceremonyId?: string,
161
+ temporaryDataToResumeMultiPartUpload?: TemporaryParticipantContributionData
162
+ ) => {
163
+ // The unique identifier of the multi-part upload.
164
+ let multiPartUploadId: string = ""
165
+ // The list of already uploaded chunks.
166
+ let alreadyUploadedChunks: Array<ETagWithPartNumber> = []
167
+
168
+ // Step (0).
169
+ if (temporaryDataToResumeMultiPartUpload && !!temporaryDataToResumeMultiPartUpload.uploadId) {
170
+ // Step (0.A).
171
+ multiPartUploadId = temporaryDataToResumeMultiPartUpload.uploadId
172
+ alreadyUploadedChunks = temporaryDataToResumeMultiPartUpload.chunks
173
+ } else {
174
+ // Step (0.B).
175
+ // Open a new multi-part upload for the ceremony artifact.
176
+ multiPartUploadId = await openMultiPartUpload(cloudFunctions, bucketName, objectKey, ceremonyId)
177
+
178
+ // Store multi-part upload identifier on document collection.
179
+ if (ceremonyId)
180
+ // Store Multi-Part Upload ID after generation.
181
+ await temporaryStoreCurrentContributionMultiPartUploadId(cloudFunctions, ceremonyId!, multiPartUploadId)
182
+ }
183
+
184
+ // Step (1).
185
+ const chunksWithUrlsZkey = await getChunksAndPreSignedUrls(
186
+ cloudFunctions,
187
+ bucketName,
188
+ objectKey,
189
+ localFilePath,
190
+ multiPartUploadId,
191
+ configStreamChunkSize,
192
+ ceremonyId
193
+ )
194
+
195
+ // Step (2).
196
+ const partNumbersAndETagsZkey = await uploadParts(
197
+ chunksWithUrlsZkey,
198
+ mime.lookup(localFilePath), // content-type.
199
+ cloudFunctions,
200
+ ceremonyId,
201
+ alreadyUploadedChunks
202
+ )
203
+
204
+ // Step (3).
205
+ await completeMultiPartUpload(
206
+ cloudFunctions,
207
+ bucketName,
208
+ objectKey,
209
+ multiPartUploadId,
210
+ partNumbersAndETagsZkey,
211
+ ceremonyId
212
+ )
213
+ }
214
+
215
+ /**
216
+ * Download an artifact from S3 (only for authorized users)
217
+ * @param cloudFunctions <Functions> Firebase cloud functions instance.
218
+ * @param bucketName <string> Name of the bucket where the artifact is stored.
219
+ * @param storagePath <string> Path to the artifact in the bucket.
220
+ * @param localPath <string> Path to the local file where the artifact will be saved.
221
+ */
222
+ export const downloadCeremonyArtifact = async (
223
+ cloudFunctions: Functions,
224
+ bucketName: string,
225
+ storagePath: string,
226
+ localPath: string
227
+ ) => {
228
+ // Request pre-signed url to make GET download request.
229
+ const getPreSignedUrl = await generateGetObjectPreSignedUrl(cloudFunctions, bucketName, storagePath)
230
+
231
+ // Make fetch to get info about the artifact.
232
+ // @ts-ignore
233
+ const response = await fetch(getPreSignedUrl)
234
+
235
+ if (response.status !== 200 && !response.ok)
236
+ throw new Error(
237
+ `There was an erorr while downloading the object ${storagePath} from the bucket ${bucketName}. Please check the function inputs and try again.`
238
+ )
239
+
240
+ const content: any = response.body
241
+ // Prepare stream.
242
+ const writeStream = createWriteStream(localPath)
243
+
244
+ // Write chunk by chunk.
245
+ for await (const chunk of content) {
246
+ // Write chunk.
247
+ writeStream.write(chunk)
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Get R1CS file path tied to a particular circuit of a ceremony in the storage.
253
+ * @notice each R1CS file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeR1csFilename>`.
254
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
255
+ * @param circuitPrefix <string> - the prefix of the circuit.
256
+ * @param completeR1csFilename <string> - the complete R1CS filename (name + ext).
257
+ * @returns <string> - the storage path of the R1CS file.
258
+ */
259
+ export const getR1csStorageFilePath = (circuitPrefix: string, completeR1csFilename: string): string =>
260
+ `${commonTerms.collections.circuits.name}/${circuitPrefix}/${completeR1csFilename}`
261
+
262
+ /**
263
+ * Get WASM file path tied to a particular circuit of a ceremony in the storage.
264
+ * @notice each WASM file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeWasmFilename>`.
265
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
266
+ * @param circuitPrefix <string> - the prefix of the circuit.
267
+ * @param completeWasmFilename <string> - the complete WASM filename (name + ext).
268
+ * @returns <string> - the storage path of the WASM file.
269
+ */
270
+ export const getWasmStorageFilePath = (circuitPrefix: string, completeWasmFilename: string): string =>
271
+ `${commonTerms.collections.circuits.name}/${circuitPrefix}/${completeWasmFilename}`
272
+
273
+ /**
274
+ * Get PoT file path in the storage.
275
+ * @notice each PoT file in the storage must be stored in the following path: `pot/<completePotFilename>`.
276
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
277
+ * @param completePotFilename <string> - the complete PoT filename (name + ext).
278
+ * @returns <string> - the storage path of the PoT file.
279
+ */
280
+ export const getPotStorageFilePath = (completePotFilename: string): string =>
281
+ `${commonTerms.foldersAndPathsTerms.pot}/${completePotFilename}`
282
+
283
+ /**
284
+ * Get zKey file path tied to a particular circuit of a ceremony in the storage.
285
+ * @notice each zKey file in the storage must be stored in the following path: `circuits/<circuitPrefix>/contributions/<completeZkeyFilename>`.
286
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
287
+ * @param circuitPrefix <string> - the prefix of the circuit.
288
+ * @param completeZkeyFilename <string> - the complete zKey filename (name + ext).
289
+ * @returns <string> - the storage path of the zKey file.
290
+ */
291
+ export const getZkeyStorageFilePath = (circuitPrefix: string, completeZkeyFilename: string): string =>
292
+ `${commonTerms.collections.circuits.name}/${circuitPrefix}/${commonTerms.collections.contributions.name}/${completeZkeyFilename}`
293
+
294
+ /**
295
+ * Get verification key file path tied to a particular circuit of a ceremony in the storage.
296
+ * @notice each verification key file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeVerificationKeyFilename>`.
297
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
298
+ * @param circuitPrefix <string> - the prefix of the circuit.
299
+ * @param completeVerificationKeyFilename <string> - the complete verification key filename (name + ext).
300
+ * @returns <string> - the storage path of the verification key file.
301
+ */
302
+ export const getVerificationKeyStorageFilePath = (
303
+ circuitPrefix: string,
304
+ completeVerificationKeyFilename: string
305
+ ): string => `${commonTerms.collections.circuits.name}/${circuitPrefix}/${completeVerificationKeyFilename}`
306
+
307
+ /**
308
+ * Get verifier contract file path tied to a particular circuit of a ceremony in the storage.
309
+ * @notice each verifier contract file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeVerificationKeyFilename>`.
310
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
311
+ * @param circuitPrefix <string> - the prefix of the circuit.
312
+ * @param completeVerifierContractFilename <string> - the complete verifier contract filename (name + ext).
313
+ * @returns <string> - the storage path of the verifier contract file.
314
+ */
315
+ export const getVerifierContractStorageFilePath = (
316
+ circuitPrefix: string,
317
+ completeVerifierContractFilename: string
318
+ ): string => `${commonTerms.collections.circuits.name}/${circuitPrefix}/${completeVerifierContractFilename}`
319
+
320
+ /**
321
+ * Get transcript file path tied to a particular circuit of a ceremony in the storage.
322
+ * @notice each R1CS file in the storage must be stored in the following path: `circuits/<circuitPrefix>/<completeTranscriptFilename>`.
323
+ * nb. This is a rule that must be satisfied. This is NOT an optional convention.
324
+ * @param circuitPrefix <string> - the prefix of the circuit.
325
+ * @param completeTranscriptFilename <string> - the complete transcript filename (name + ext).
326
+ * @returns <string> - the storage path of the transcript file.
327
+ */
328
+ export const getTranscriptStorageFilePath = (circuitPrefix: string, completeTranscriptFilename: string): string =>
329
+ `${commonTerms.collections.circuits.name}/${circuitPrefix}/${commonTerms.foldersAndPathsTerms.transcripts}/${completeTranscriptFilename}`
@@ -0,0 +1,56 @@
1
+ import { task } from "hardhat/config"
2
+ import { cwd } from "process"
3
+ import { verifyCeremony } from "./contracts"
4
+ import {
5
+ createMockUser,
6
+ deleteAdminApp,
7
+ generatePseudoRandomStringOfNumbers,
8
+ initializeAdminServices,
9
+ initializeUserServices
10
+ } from "../../test/utils/index"
11
+
12
+ task("verifyCeremony", "A task that can be used to verify a ceremony finalization validity")
13
+ .addPositionalParam("ceremonyPrefix")
14
+ .addPositionalParam("circuitInputsPath")
15
+ .setAction(async (taskArgs: any, hre: any) => {
16
+ // get a signer
17
+ const [deployer] = await hre.ethers.getSigners()
18
+
19
+ // init user and admin app
20
+ const { adminAuth, adminFirestore } = initializeAdminServices()
21
+ const { userApp, userFirestore, userFunctions } = initializeUserServices()
22
+
23
+ // this is where we are saving the artifacts
24
+ const outputDirectory = `${cwd()}/test/data/artifacts/verification/`
25
+ const verifierTemplatePath = `${cwd()}/../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
26
+
27
+ // create user
28
+ const coordinatorEmail = "coordinator@email.com"
29
+ const coordinatorPassword = generatePseudoRandomStringOfNumbers(20)
30
+ const coordinatorUID = await createMockUser(userApp, coordinatorEmail, coordinatorPassword, true, adminAuth)
31
+
32
+ try {
33
+ // verify ceremony
34
+ await verifyCeremony(
35
+ userFunctions,
36
+ userFirestore,
37
+ taskArgs.ceremonyPrefix,
38
+ outputDirectory,
39
+ taskArgs.circuitInputsPath,
40
+ verifierTemplatePath,
41
+ deployer
42
+ )
43
+
44
+ // if we are here it is because it didn't throw so we can safely assume that it all was veriifer
45
+ console.log(`\n[+] The artifacts generated by the ceremony ${taskArgs.ceremonyPrefix} are valid\n`)
46
+ } catch (err: any) {
47
+ console.log(`\n[-] Could not verify the ceremony validity. ${err.toString()}\n`)
48
+ } finally {
49
+ // Clean ceremony and user from DB.
50
+ await adminFirestore.collection("users").doc(coordinatorUID).delete()
51
+ // Remove Auth user.
52
+ await adminAuth.deleteUser(coordinatorUID)
53
+ // Delete admin app.
54
+ await deleteAdminApp()
55
+ }
56
+ })