@devtion/devcli 1.0.5-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/index.js +3206 -0
- package/dist/types/commands/auth.d.ts +25 -0
- package/dist/types/commands/clean.d.ts +6 -0
- package/dist/types/commands/contribute.d.ts +139 -0
- package/dist/types/commands/finalize.d.ts +51 -0
- package/dist/types/commands/index.d.ts +9 -0
- package/dist/types/commands/listCeremonies.d.ts +5 -0
- package/dist/types/commands/logout.d.ts +6 -0
- package/dist/types/commands/observe.d.ts +22 -0
- package/dist/types/commands/setup.d.ts +86 -0
- package/dist/types/commands/validate.d.ts +8 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/errors.d.ts +60 -0
- package/dist/types/lib/files.d.ts +64 -0
- package/dist/types/lib/localConfigs.d.ts +110 -0
- package/dist/types/lib/prompts.d.ts +104 -0
- package/dist/types/lib/services.d.ts +31 -0
- package/dist/types/lib/theme.d.ts +42 -0
- package/dist/types/lib/utils.d.ts +158 -0
- package/dist/types/types/index.d.ts +65 -0
- package/package.json +99 -0
- package/src/commands/auth.ts +194 -0
- package/src/commands/clean.ts +49 -0
- package/src/commands/contribute.ts +1090 -0
- package/src/commands/finalize.ts +382 -0
- package/src/commands/index.ts +9 -0
- package/src/commands/listCeremonies.ts +32 -0
- package/src/commands/logout.ts +67 -0
- package/src/commands/observe.ts +193 -0
- package/src/commands/setup.ts +901 -0
- package/src/commands/validate.ts +29 -0
- package/src/index.ts +66 -0
- package/src/lib/errors.ts +77 -0
- package/src/lib/files.ts +102 -0
- package/src/lib/localConfigs.ts +186 -0
- package/src/lib/prompts.ts +748 -0
- package/src/lib/services.ts +191 -0
- package/src/lib/theme.ts +45 -0
- package/src/lib/utils.ts +778 -0
- package/src/types/conf.d.ts +16 -0
- package/src/types/index.ts +70 -0
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { zKey } from "snarkjs"
|
|
4
|
+
import boxen from "boxen"
|
|
5
|
+
import { createWriteStream, Dirent, renameSync } from "fs"
|
|
6
|
+
import { pipeline } from "node:stream"
|
|
7
|
+
import { promisify } from "node:util"
|
|
8
|
+
import fetch from "node-fetch"
|
|
9
|
+
import { Functions } from "firebase/functions"
|
|
10
|
+
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"
|
|
11
|
+
import {
|
|
12
|
+
CeremonyTimeoutType,
|
|
13
|
+
CircomCompilerData,
|
|
14
|
+
CircuitInputData,
|
|
15
|
+
extractPrefix,
|
|
16
|
+
getR1CSInfo,
|
|
17
|
+
commonTerms,
|
|
18
|
+
convertToDoubleDigits,
|
|
19
|
+
CeremonyInputData,
|
|
20
|
+
CircuitDocument,
|
|
21
|
+
extractPoTFromFilename,
|
|
22
|
+
potFileDownloadMainUrl,
|
|
23
|
+
potFilenameTemplate,
|
|
24
|
+
getBucketName,
|
|
25
|
+
createS3Bucket,
|
|
26
|
+
multiPartUpload,
|
|
27
|
+
isCoordinator,
|
|
28
|
+
genesisZkeyIndex,
|
|
29
|
+
getR1csStorageFilePath,
|
|
30
|
+
getWasmStorageFilePath,
|
|
31
|
+
getPotStorageFilePath,
|
|
32
|
+
getZkeyStorageFilePath,
|
|
33
|
+
checkIfObjectExist,
|
|
34
|
+
blake512FromPath,
|
|
35
|
+
CircuitArtifacts,
|
|
36
|
+
CircuitTimings,
|
|
37
|
+
setupCeremony,
|
|
38
|
+
parseCeremonyFile,
|
|
39
|
+
CircuitContributionVerificationMechanism
|
|
40
|
+
} from "@p0tion/actions"
|
|
41
|
+
import { customSpinner, simpleLoader, sleep, terminate } from "../lib/utils.js"
|
|
42
|
+
import {
|
|
43
|
+
promptCeremonyInputData,
|
|
44
|
+
promptCircomCompiler,
|
|
45
|
+
promptCircuitInputData,
|
|
46
|
+
askForConfirmation,
|
|
47
|
+
promptCircuitSelector,
|
|
48
|
+
promptSameCircomCompiler,
|
|
49
|
+
promptCircuitAddition,
|
|
50
|
+
promptPreComputedZkey,
|
|
51
|
+
promptPreComputedZkeySelector,
|
|
52
|
+
promptNeededPowersForCircuit,
|
|
53
|
+
promptPotSelector
|
|
54
|
+
} from "../lib/prompts.js"
|
|
55
|
+
import { COMMAND_ERRORS, showError } from "../lib/errors.js"
|
|
56
|
+
import { authWithToken, bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
|
|
57
|
+
import { getCWDFilePath, getPotLocalFilePath, getZkeyLocalFilePath, localPaths } from "../lib/localConfigs.js"
|
|
58
|
+
import theme from "../lib/theme.js"
|
|
59
|
+
import {
|
|
60
|
+
filterDirectoryFilesByExtension,
|
|
61
|
+
cleanDir,
|
|
62
|
+
getDirFilesSubPaths,
|
|
63
|
+
getFileStats,
|
|
64
|
+
checkAndMakeNewDirectoryIfNonexistent
|
|
65
|
+
} from "../lib/files.js"
|
|
66
|
+
import { Readable } from "stream"
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Handle whatever is needed to obtain the input data for a circuit that the coordinator would like to add to the ceremony.
|
|
70
|
+
* @param choosenCircuitFilename <string> - the name of the circuit to add.
|
|
71
|
+
* @param matchingWasmFilename <string> - the name of the circuit wasm file.
|
|
72
|
+
* @param ceremonyTimeoutMechanismType <CeremonyTimeoutType> - the type of ceremony timeout mechanism.
|
|
73
|
+
* @param sameCircomCompiler <boolean> - true, if this circuit shares with the others the <CircomCompilerData>; otherwise false.
|
|
74
|
+
* @param circuitSequencePosition <number> - the position of the circuit in the contribution queue.
|
|
75
|
+
* @param sharedCircomCompilerData <string> - version and commit hash of the Circom compiler used to compile the ceremony circuits.
|
|
76
|
+
* @returns <Promise<CircuitInputData>> - the input data of the circuit to add to the ceremony.
|
|
77
|
+
*/
|
|
78
|
+
export const getInputDataToAddCircuitToCeremony = async (
|
|
79
|
+
choosenCircuitFilename: string,
|
|
80
|
+
matchingWasmFilename: string,
|
|
81
|
+
ceremonyTimeoutMechanismType: CeremonyTimeoutType,
|
|
82
|
+
sameCircomCompiler: boolean,
|
|
83
|
+
circuitSequencePosition: number,
|
|
84
|
+
sharedCircomCompilerData: CircomCompilerData
|
|
85
|
+
): Promise<CircuitInputData> => {
|
|
86
|
+
// Extract name and prefix.
|
|
87
|
+
const circuitName = choosenCircuitFilename.substring(0, choosenCircuitFilename.indexOf("."))
|
|
88
|
+
const circuitPrefix = extractPrefix(circuitName)
|
|
89
|
+
|
|
90
|
+
// R1CS file path.
|
|
91
|
+
const r1csCWDFilePath = getCWDFilePath(process.cwd(), choosenCircuitFilename)
|
|
92
|
+
|
|
93
|
+
const spinner = customSpinner(`Looking for circuit metadata...`, "clock")
|
|
94
|
+
spinner.start()
|
|
95
|
+
|
|
96
|
+
// Read R1CS and store metadata locally.
|
|
97
|
+
const metadata = getR1CSInfo(r1csCWDFilePath)
|
|
98
|
+
|
|
99
|
+
await sleep(2000) // Sleep 2s to avoid unexpected termination (file descriptor close).
|
|
100
|
+
|
|
101
|
+
spinner.succeed(`Circuit metadata read and saved correctly`)
|
|
102
|
+
|
|
103
|
+
// Prompt for circuit input data.
|
|
104
|
+
const circuitInputData = await promptCircuitInputData(
|
|
105
|
+
metadata.constraints,
|
|
106
|
+
ceremonyTimeoutMechanismType,
|
|
107
|
+
sameCircomCompiler,
|
|
108
|
+
!(metadata.constraints <= 1000000) // nb. we assume after our dry-runs that CF works fine for up to one million circuit constraints.
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
process.stdout.write("\n")
|
|
112
|
+
|
|
113
|
+
// Return updated data.
|
|
114
|
+
return {
|
|
115
|
+
...circuitInputData,
|
|
116
|
+
metadata,
|
|
117
|
+
compiler: {
|
|
118
|
+
commitHash:
|
|
119
|
+
!circuitInputData.compiler.commitHash && sameCircomCompiler
|
|
120
|
+
? sharedCircomCompilerData.commitHash
|
|
121
|
+
: circuitInputData.compiler.commitHash,
|
|
122
|
+
version:
|
|
123
|
+
!circuitInputData.compiler.version && sameCircomCompiler
|
|
124
|
+
? sharedCircomCompilerData.version
|
|
125
|
+
: circuitInputData.compiler.version
|
|
126
|
+
},
|
|
127
|
+
compilationArtifacts: {
|
|
128
|
+
r1csFilename: choosenCircuitFilename,
|
|
129
|
+
wasmFilename: matchingWasmFilename
|
|
130
|
+
},
|
|
131
|
+
name: circuitName,
|
|
132
|
+
prefix: circuitPrefix,
|
|
133
|
+
sequencePosition: circuitSequencePosition
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Handle the addition of one or more circuits to the ceremony.
|
|
139
|
+
* @param options <Array<string>> - list of possible circuits that can be added to the ceremony.
|
|
140
|
+
* @param ceremonyTimeoutMechanismType <CeremonyTimeoutType> - the type of ceremony timeout mechanism.
|
|
141
|
+
* @returns <Promise<Array<CircuitInputData>>> - the input data for each circuit that has been added to the ceremony.
|
|
142
|
+
*/
|
|
143
|
+
export const handleAdditionOfCircuitsToCeremony = async (
|
|
144
|
+
r1csOptions: Array<string>,
|
|
145
|
+
wasmOptions: Array<string>,
|
|
146
|
+
ceremonyTimeoutMechanismType: CeremonyTimeoutType
|
|
147
|
+
): Promise<Array<CircuitInputData>> => {
|
|
148
|
+
// Prepare data.
|
|
149
|
+
const inputDataForCircuits: Array<CircuitInputData> = [] // All circuits interactive data.
|
|
150
|
+
let circuitSequencePosition = 1 // The circuit's position for contribution.
|
|
151
|
+
let readyToSummarizeCeremony = false // Boolean flag to check whether the coordinator has finished to add circuits to the ceremony.
|
|
152
|
+
let wannaAddAnotherCircuit = true // Loop flag.
|
|
153
|
+
const sharedCircomCompilerData: CircomCompilerData = { version: "", commitHash: "" }
|
|
154
|
+
|
|
155
|
+
// Prompt if the circuits to be added were compiled with the same version of Circom.
|
|
156
|
+
// nb. CIRCOM compiler version/commit-hash is a declaration useful for later verifiability and avoid bugs.
|
|
157
|
+
const sameCircomCompiler = await promptSameCircomCompiler()
|
|
158
|
+
|
|
159
|
+
if (sameCircomCompiler) {
|
|
160
|
+
// Prompt for Circom compiler.
|
|
161
|
+
const { version, commitHash } = await promptCircomCompiler()
|
|
162
|
+
|
|
163
|
+
sharedCircomCompilerData.version = version
|
|
164
|
+
sharedCircomCompilerData.commitHash = commitHash
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
while (wannaAddAnotherCircuit) {
|
|
168
|
+
// Gather information about the ceremony circuits.
|
|
169
|
+
console.log(theme.text.bold(`\n- Circuit # ${theme.colors.magenta(`${circuitSequencePosition}`)}\n`))
|
|
170
|
+
|
|
171
|
+
// Select one circuit among cwd circuits identified by R1CS files.
|
|
172
|
+
const choosenCircuitFilename = await promptCircuitSelector(r1csOptions)
|
|
173
|
+
|
|
174
|
+
// Update list of possible options for next selection (if, any).
|
|
175
|
+
r1csOptions = r1csOptions.filter((circuitFilename: string) => circuitFilename !== choosenCircuitFilename)
|
|
176
|
+
|
|
177
|
+
// Select the wasm file accordingly to circuit R1CS filename.
|
|
178
|
+
const matchingWasms = wasmOptions.filter(
|
|
179
|
+
(wasmFilename: string) =>
|
|
180
|
+
choosenCircuitFilename.split(`.r1cs`)[0] ===
|
|
181
|
+
wasmFilename.split(`.${commonTerms.foldersAndPathsTerms.wasm}`)[0]
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if (matchingWasms.length !== 1) showError(COMMAND_ERRORS.COMMAND_SETUP_MISMATCH_R1CS_WASM, true)
|
|
185
|
+
|
|
186
|
+
// Get input data for choosen circuit.
|
|
187
|
+
const circuitInputData = await getInputDataToAddCircuitToCeremony(
|
|
188
|
+
choosenCircuitFilename,
|
|
189
|
+
matchingWasms[0],
|
|
190
|
+
ceremonyTimeoutMechanismType,
|
|
191
|
+
sameCircomCompiler,
|
|
192
|
+
circuitSequencePosition,
|
|
193
|
+
sharedCircomCompilerData
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
// Store circuit data.
|
|
197
|
+
inputDataForCircuits.push(circuitInputData)
|
|
198
|
+
|
|
199
|
+
// Check if any circuit is left for potentially addition to ceremony.
|
|
200
|
+
if (r1csOptions.length !== 0) {
|
|
201
|
+
// Prompt for selection.
|
|
202
|
+
const wannaAddNewCircuit = await promptCircuitAddition()
|
|
203
|
+
|
|
204
|
+
if (wannaAddNewCircuit === false) readyToSummarizeCeremony = true // Terminate circuit addition.
|
|
205
|
+
else circuitSequencePosition += 1 // Continue with next one.
|
|
206
|
+
} else readyToSummarizeCeremony = true // No more circuit to add.
|
|
207
|
+
|
|
208
|
+
// Summarize the ceremony.
|
|
209
|
+
if (readyToSummarizeCeremony) wannaAddAnotherCircuit = false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return inputDataForCircuits
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Print ceremony and related circuits information.
|
|
217
|
+
* @param ceremonyInputData <CeremonyInputData> - the input data of the ceremony.
|
|
218
|
+
* @param circuits <Array<CircuitDocument>> - the circuit documents associated to the circuits of the ceremony.
|
|
219
|
+
*/
|
|
220
|
+
export const displayCeremonySummary = (ceremonyInputData: CeremonyInputData, circuits: Array<CircuitDocument>) => {
|
|
221
|
+
// Prepare ceremony summary.
|
|
222
|
+
let summary = `${`${theme.text.bold(ceremonyInputData.title)}\n${theme.text.italic(ceremonyInputData.description)}`}
|
|
223
|
+
\n${`Opening: ${theme.text.bold(
|
|
224
|
+
theme.text.underlined(new Date(ceremonyInputData.startDate).toUTCString().replace("GMT", "UTC"))
|
|
225
|
+
)}\nEnding: ${theme.text.bold(
|
|
226
|
+
theme.text.underlined(new Date(ceremonyInputData.endDate).toUTCString().replace("GMT", "UTC"))
|
|
227
|
+
)}`}
|
|
228
|
+
\n${theme.text.bold(
|
|
229
|
+
ceremonyInputData.timeoutMechanismType === CeremonyTimeoutType.DYNAMIC ? `Dynamic` : `Fixed`
|
|
230
|
+
)} Timeout / ${theme.text.bold(ceremonyInputData.penalty)}m Penalty`
|
|
231
|
+
|
|
232
|
+
for (const circuit of circuits) {
|
|
233
|
+
// Append circuit summary.
|
|
234
|
+
summary += `\n\n${theme.text.bold(
|
|
235
|
+
`- CIRCUIT # ${theme.text.bold(theme.colors.magenta(`${circuit.sequencePosition}`))}`
|
|
236
|
+
)}
|
|
237
|
+
\n${`${theme.text.bold(circuit.name)}\n${theme.text.italic(circuit.description)}
|
|
238
|
+
\nCurve: ${theme.text.bold(circuit.metadata?.curve)}\nCompiler: ${theme.text.bold(
|
|
239
|
+
`${circuit.compiler.version}`
|
|
240
|
+
)} (${theme.text.bold(circuit.compiler.commitHash.slice(0, 7))})\nVerification: ${theme.text.bold(
|
|
241
|
+
`${circuit.verification.cfOrVm}`
|
|
242
|
+
)} ${theme.text.bold(
|
|
243
|
+
circuit.verification.cfOrVm === CircuitContributionVerificationMechanism.VM
|
|
244
|
+
? `(${circuit.verification.vm.vmConfigurationType} / ${circuit.verification.vm.vmDiskType} volume)`
|
|
245
|
+
: ""
|
|
246
|
+
)}\nSource: ${theme.text.bold(circuit.template.source.split(`/`).at(-1))}(${theme.text.bold(
|
|
247
|
+
circuit.template.paramsConfiguration
|
|
248
|
+
)})\n${
|
|
249
|
+
ceremonyInputData.timeoutMechanismType === CeremonyTimeoutType.DYNAMIC
|
|
250
|
+
? `Threshold: ${theme.text.bold(circuit.dynamicThreshold)}%`
|
|
251
|
+
: `Max Contribution Time: ${theme.text.bold(circuit.fixedTimeWindow)}m`
|
|
252
|
+
}
|
|
253
|
+
\n# Wires: ${theme.text.bold(circuit.metadata?.wires)}\n# Constraints: ${theme.text.bold(
|
|
254
|
+
circuit.metadata?.constraints
|
|
255
|
+
)}\n# Private Inputs: ${theme.text.bold(circuit.metadata?.privateInputs)}\n# Public Inputs: ${theme.text.bold(
|
|
256
|
+
circuit.metadata?.publicInputs
|
|
257
|
+
)}\n# Labels: ${theme.text.bold(circuit.metadata?.labels)}\n# Outputs: ${theme.text.bold(
|
|
258
|
+
circuit.metadata?.outputs
|
|
259
|
+
)}\n# PoT: ${theme.text.bold(circuit.metadata?.pot)}`}`
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Display complete summary.
|
|
263
|
+
console.log(
|
|
264
|
+
boxen(summary, {
|
|
265
|
+
title: theme.colors.magenta(`CEREMONY SUMMARY`),
|
|
266
|
+
titleAlignment: "center",
|
|
267
|
+
textAlignment: "left",
|
|
268
|
+
margin: 1,
|
|
269
|
+
padding: 1
|
|
270
|
+
})
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check if the smallest Powers of Tau has already been downloaded/stored in the correspondent local path
|
|
276
|
+
* @dev we are downloading the Powers of Tau file from Hermez Cryptography Phase 1 Trusted Setup.
|
|
277
|
+
* @param powers <string> - the smallest amount of powers needed for the given circuit (should be in a 'XY' stringified form).
|
|
278
|
+
* @param ptauCompleteFilename <string> - the complete file name of the powers of tau file to be downloaded.
|
|
279
|
+
* @returns <Promise<void>>
|
|
280
|
+
*/
|
|
281
|
+
export const checkAndDownloadSmallestPowersOfTau = async (
|
|
282
|
+
powers: string,
|
|
283
|
+
ptauCompleteFilename: string
|
|
284
|
+
): Promise<void> => {
|
|
285
|
+
// Get already downloaded ptau files.
|
|
286
|
+
const alreadyDownloadedPtauFiles = await getDirFilesSubPaths(localPaths.pot)
|
|
287
|
+
|
|
288
|
+
// Get the required smallest ptau file.
|
|
289
|
+
const smallestPtauFileForGivenPowers: Array<string> = alreadyDownloadedPtauFiles
|
|
290
|
+
.filter((dirent: Dirent) => extractPoTFromFilename(dirent.name) === Number(powers))
|
|
291
|
+
.map((dirent: Dirent) => dirent.name)
|
|
292
|
+
|
|
293
|
+
// Check if already downloaded or not.
|
|
294
|
+
if (smallestPtauFileForGivenPowers.length === 0) {
|
|
295
|
+
const spinner = customSpinner(
|
|
296
|
+
`Downloading the ${theme.text.bold(
|
|
297
|
+
`#${powers}`
|
|
298
|
+
)} smallest PoT file needed from the Hermez Cryptography Phase 1 Trusted Setup...`,
|
|
299
|
+
`clock`
|
|
300
|
+
)
|
|
301
|
+
spinner.start()
|
|
302
|
+
|
|
303
|
+
// Download smallest Powers of Tau file from remote server.
|
|
304
|
+
const streamPipeline = promisify(pipeline)
|
|
305
|
+
|
|
306
|
+
// Make the call.
|
|
307
|
+
const response = await fetch(`${potFileDownloadMainUrl}${ptauCompleteFilename}`)
|
|
308
|
+
|
|
309
|
+
// Handle errors.
|
|
310
|
+
if (!response.ok && response.status !== 200) showError(COMMAND_ERRORS.COMMAND_SETUP_DOWNLOAD_PTAU, true)
|
|
311
|
+
// Write the file locally
|
|
312
|
+
else await streamPipeline(response.body!, createWriteStream(getPotLocalFilePath(ptauCompleteFilename)))
|
|
313
|
+
|
|
314
|
+
spinner.succeed(`Powers of tau ${theme.text.bold(`#${powers}`)} downloaded successfully`)
|
|
315
|
+
} else
|
|
316
|
+
console.log(
|
|
317
|
+
`${theme.symbols.success} Smallest Powers of Tau ${theme.text.bold(`#${powers}`)} already downloaded`
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Handle the needs in terms of Powers of Tau for the selected pre-computed zKey.
|
|
323
|
+
* @notice in case there are no Powers of Tau file suitable for the pre-computed zKey (i.e., having a
|
|
324
|
+
* number of powers greater than or equal to the powers needed by the zKey), the coordinator will be asked
|
|
325
|
+
* to provide a number of powers manually, ranging from the smallest possible to the largest.
|
|
326
|
+
* @param neededPowers <number> - the smallest amount of powers needed by the zKey.
|
|
327
|
+
* @returns Promise<string, string> - the information about the choosen Powers of Tau file for the pre-computed zKey
|
|
328
|
+
* along with related powers.
|
|
329
|
+
*/
|
|
330
|
+
export const handlePreComputedZkeyPowersOfTauSelection = async (
|
|
331
|
+
neededPowers: number
|
|
332
|
+
): Promise<{
|
|
333
|
+
doubleDigitsPowers: string
|
|
334
|
+
potCompleteFilename: string
|
|
335
|
+
usePreDownloadedPoT: boolean
|
|
336
|
+
}> => {
|
|
337
|
+
let doubleDigitsPowers: string = "" // The amount of stringified powers in a double-digits format (XY).
|
|
338
|
+
let potCompleteFilename: string = "" // The complete filename of the Powers of Tau file selected for the pre-computed zKey.
|
|
339
|
+
let usePreDownloadedPoT = false // Boolean flag to check if the coordinator is going to use a pre-downloaded PoT file or not.
|
|
340
|
+
|
|
341
|
+
// Check for PoT file associated to selected pre-computed zKey.
|
|
342
|
+
const spinner = customSpinner("Looking for Powers of Tau files...", "clock")
|
|
343
|
+
spinner.start()
|
|
344
|
+
|
|
345
|
+
// Get local `.ptau` files.
|
|
346
|
+
const potFilePaths = await filterDirectoryFilesByExtension(process.cwd(), `.ptau`)
|
|
347
|
+
|
|
348
|
+
// Filter based on suitable amount of powers.
|
|
349
|
+
const potOptions: Array<string> = potFilePaths
|
|
350
|
+
.filter((dirent: Dirent) => extractPoTFromFilename(dirent.name) >= neededPowers)
|
|
351
|
+
.map((dirent: Dirent) => dirent.name)
|
|
352
|
+
|
|
353
|
+
if (potOptions.length <= 0) {
|
|
354
|
+
spinner.warn(`There is no already downloaded Powers of Tau file suitable for this zKey`)
|
|
355
|
+
|
|
356
|
+
// Ask coordinator to input the amount of powers.
|
|
357
|
+
const choosenPowers = await promptNeededPowersForCircuit(neededPowers)
|
|
358
|
+
|
|
359
|
+
// Convert to double digits powers (e.g., 9 -> 09).
|
|
360
|
+
doubleDigitsPowers = convertToDoubleDigits(choosenPowers)
|
|
361
|
+
potCompleteFilename = `${potFilenameTemplate}${doubleDigitsPowers}.ptau`
|
|
362
|
+
} else {
|
|
363
|
+
spinner.stop()
|
|
364
|
+
|
|
365
|
+
// Prompt for Powers of Tau selection among already downloaded ones.
|
|
366
|
+
potCompleteFilename = await promptPotSelector(potOptions)
|
|
367
|
+
|
|
368
|
+
// Convert to double digits powers (e.g., 9 -> 09).
|
|
369
|
+
doubleDigitsPowers = convertToDoubleDigits(extractPoTFromFilename(potCompleteFilename))
|
|
370
|
+
|
|
371
|
+
usePreDownloadedPoT = true
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
doubleDigitsPowers,
|
|
376
|
+
potCompleteFilename,
|
|
377
|
+
usePreDownloadedPoT
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Generate a brand new zKey from scratch.
|
|
383
|
+
* @param r1csLocalPathAndFileName <string> - the local complete path of the R1CS selected file.
|
|
384
|
+
* @param potLocalPathAndFileName <string> - the local complete path of the PoT selected file.
|
|
385
|
+
* @param zkeyLocalPathAndFileName <string> - the local complete path of the pre-computed zKey selected file.
|
|
386
|
+
*/
|
|
387
|
+
export const handleNewZkeyGeneration = async (
|
|
388
|
+
r1csLocalPathAndFileName: string,
|
|
389
|
+
potLocalPathAndFileName: string,
|
|
390
|
+
zkeyLocalPathAndFileName: string
|
|
391
|
+
) => {
|
|
392
|
+
console.log(
|
|
393
|
+
`${theme.symbols.info} The computation of your brand new zKey is starting soon.\n${theme.text.bold(
|
|
394
|
+
`${theme.symbols.warning} Be careful, stopping the process will result in the loss of all progress achieved so far.`
|
|
395
|
+
)}`
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
// Generate zKey.
|
|
399
|
+
await zKey.newZKey(r1csLocalPathAndFileName, potLocalPathAndFileName, zkeyLocalPathAndFileName, console)
|
|
400
|
+
|
|
401
|
+
console.log(`\n${theme.symbols.success} Generation of genesis zKey completed successfully`)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Manage the creation of a ceremony file storage bucket.
|
|
406
|
+
* @param firebaseFunctions <Functions> - the Firebase Cloud Functions instance connected to the current application.
|
|
407
|
+
* @param ceremonyPrefix <string> - the prefix of the ceremony.
|
|
408
|
+
* @returns <Promise<string>> - the ceremony bucket name.
|
|
409
|
+
*/
|
|
410
|
+
export const handleCeremonyBucketCreation = async (
|
|
411
|
+
firebaseFunctions: Functions,
|
|
412
|
+
ceremonyPrefix: string
|
|
413
|
+
): Promise<string> => {
|
|
414
|
+
// Compose bucket name using the ceremony prefix.
|
|
415
|
+
const bucketName = getBucketName(ceremonyPrefix, process.env.CONFIG_CEREMONY_BUCKET_POSTFIX!)
|
|
416
|
+
|
|
417
|
+
const spinner = customSpinner(`Getting ready for ceremony files and data storage...`, `clock`)
|
|
418
|
+
spinner.start()
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
// Make the call to create the bucket.
|
|
422
|
+
await createS3Bucket(firebaseFunctions, bucketName)
|
|
423
|
+
} catch (error: any) {
|
|
424
|
+
const errorBody = JSON.parse(JSON.stringify(error))
|
|
425
|
+
showError(`[${errorBody.code}] ${error.message} ${!errorBody.details ? "" : `\n${errorBody.details}`}`, true)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
spinner.succeed(`Ceremony bucket has been successfully created`)
|
|
429
|
+
|
|
430
|
+
return bucketName
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Upload a circuit artifact (r1cs, WASM, ptau) to the ceremony storage.
|
|
435
|
+
* @dev this method uses a multi part upload to upload the file in chunks.
|
|
436
|
+
* @param firebaseFunctions <Functions> - the Firebase Cloud Functions instance connected to the current application.
|
|
437
|
+
* @param bucketName <string> - the ceremony bucket name.
|
|
438
|
+
* @param storageFilePath <string> - the storage (bucket) path where the file should be uploaded.
|
|
439
|
+
* @param localPathAndFileName <string> - the local file path where is located.
|
|
440
|
+
* @param completeFilename <string> - the complete filename.
|
|
441
|
+
*/
|
|
442
|
+
export const handleCircuitArtifactUploadToStorage = async (
|
|
443
|
+
firebaseFunctions: Functions,
|
|
444
|
+
bucketName: string,
|
|
445
|
+
storageFilePath: string,
|
|
446
|
+
localPathAndFileName: string,
|
|
447
|
+
completeFilename: string
|
|
448
|
+
) => {
|
|
449
|
+
const spinner = customSpinner(`Uploading ${theme.text.bold(completeFilename)} file to ceremony storage...`, `clock`)
|
|
450
|
+
spinner.start()
|
|
451
|
+
|
|
452
|
+
await multiPartUpload(
|
|
453
|
+
firebaseFunctions,
|
|
454
|
+
bucketName,
|
|
455
|
+
storageFilePath,
|
|
456
|
+
localPathAndFileName,
|
|
457
|
+
Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB)
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
spinner.succeed(`Upload of (${theme.text.bold(completeFilename)}) file completed successfully`)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Setup command.
|
|
465
|
+
* @notice The setup command allows the coordinator of the ceremony to prepare the next ceremony by interacting with the CLI.
|
|
466
|
+
* @dev For proper execution, the command must be run in a folder containing the R1CS files related to the circuits
|
|
467
|
+
* for which the coordinator wants to create the ceremony. The command will download the necessary Tau powers
|
|
468
|
+
* from Hermez's ceremony Phase 1 Reliable Setup Ceremony.
|
|
469
|
+
* @param cmd? <any> - the path to the ceremony setup file.
|
|
470
|
+
*/
|
|
471
|
+
const setup = async (cmd: { template?: string, auth?: string}) => {
|
|
472
|
+
// Setup command state.
|
|
473
|
+
const circuits: Array<CircuitDocument> = [] // Circuits.
|
|
474
|
+
let ceremonyId: string = "" // The unique identifier of the ceremony.
|
|
475
|
+
|
|
476
|
+
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices()
|
|
477
|
+
|
|
478
|
+
// Check for authentication.
|
|
479
|
+
const { user, providerUserId } = cmd.auth ? await authWithToken(firebaseApp, cmd.auth) : await checkAuth(firebaseApp)
|
|
480
|
+
|
|
481
|
+
// Preserve command execution only for coordinators.
|
|
482
|
+
if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true)
|
|
483
|
+
|
|
484
|
+
// Get current working directory.
|
|
485
|
+
const cwd = process.cwd()
|
|
486
|
+
|
|
487
|
+
console.log(
|
|
488
|
+
`${theme.symbols.warning} To setup a zkSNARK Groth16 Phase 2 Trusted Setup ceremony you need to have the Rank-1 Constraint System (R1CS) file for each circuit in your working directory`
|
|
489
|
+
)
|
|
490
|
+
console.log(
|
|
491
|
+
`\n${theme.symbols.info} Your current working directory is ${theme.text.bold(
|
|
492
|
+
theme.text.underlined(process.cwd())
|
|
493
|
+
)}\n`
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
// Prepare local directories.
|
|
497
|
+
checkAndMakeNewDirectoryIfNonexistent(localPaths.output)
|
|
498
|
+
cleanDir(localPaths.setup)
|
|
499
|
+
cleanDir(localPaths.pot)
|
|
500
|
+
cleanDir(localPaths.zkeys)
|
|
501
|
+
cleanDir(localPaths.wasm)
|
|
502
|
+
|
|
503
|
+
// if there is the file option, then set up the non interactively
|
|
504
|
+
if (cmd.template) {
|
|
505
|
+
// 1. parse the file
|
|
506
|
+
// tmp data - do not cleanup files as we need them
|
|
507
|
+
const spinner = customSpinner(`Parsing ${theme.text.bold(cmd.template!)} setup configuration file...`, `clock`)
|
|
508
|
+
spinner.start()
|
|
509
|
+
const setupCeremonyData = await parseCeremonyFile(cmd.template!)
|
|
510
|
+
spinner.succeed(`Parsing of ${theme.text.bold(cmd.template!)} setup configuration file completed successfully`)
|
|
511
|
+
|
|
512
|
+
// final setup data
|
|
513
|
+
const ceremonySetupData = setupCeremonyData
|
|
514
|
+
|
|
515
|
+
// create a new bucket
|
|
516
|
+
const bucketName = await handleCeremonyBucketCreation(firebaseFunctions, ceremonySetupData.ceremonyPrefix)
|
|
517
|
+
console.log(`\n${theme.symbols.success} Ceremony bucket name: ${theme.text.bold(bucketName)}`)
|
|
518
|
+
|
|
519
|
+
// create S3 clienbt
|
|
520
|
+
const s3 = new S3Client({region: 'us-east-1'})
|
|
521
|
+
|
|
522
|
+
// loop through each circuit
|
|
523
|
+
for await (const circuit of setupCeremonyData.circuits) {
|
|
524
|
+
// Local paths.
|
|
525
|
+
const index = ceremonySetupData.circuits.indexOf(circuit)
|
|
526
|
+
const r1csLocalPathAndFileName = `./${circuit.name}.r1cs`
|
|
527
|
+
const wasmLocalPathAndFileName = `./${circuit.name}.wasm`
|
|
528
|
+
const potLocalPathAndFileName = getPotLocalFilePath(circuit.files.potFilename)
|
|
529
|
+
const zkeyLocalPathAndFileName = getZkeyLocalFilePath(circuit.files.initialZkeyFilename)
|
|
530
|
+
|
|
531
|
+
// 2. download the pot and wasm files
|
|
532
|
+
const streamPipeline = promisify(pipeline)
|
|
533
|
+
await checkAndDownloadSmallestPowersOfTau(convertToDoubleDigits(circuit.metadata?.pot!), circuit.files.potFilename)
|
|
534
|
+
|
|
535
|
+
// download the wasm to calculate the hash
|
|
536
|
+
const spinner = customSpinner(
|
|
537
|
+
`Downloading the ${theme.text.bold(
|
|
538
|
+
`#${circuit.name}`
|
|
539
|
+
)} WASM file from the project's bucket...`,
|
|
540
|
+
`clock`
|
|
541
|
+
)
|
|
542
|
+
spinner.start()
|
|
543
|
+
const command = new GetObjectCommand({ Bucket: ceremonySetupData.circuitArtifacts[index].artifacts.bucket, Key: ceremonySetupData.circuitArtifacts[index].artifacts.wasmStoragePath })
|
|
544
|
+
|
|
545
|
+
const response = await s3.send(command)
|
|
546
|
+
|
|
547
|
+
if (response.$metadata.httpStatusCode !== 200) {
|
|
548
|
+
throw new Error("There was an error while trying to download the wasm file. Please check that the file has the correct permissions (public) set.")
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (response.Body instanceof Readable)
|
|
552
|
+
await streamPipeline(response.Body, createWriteStream(wasmLocalPathAndFileName))
|
|
553
|
+
|
|
554
|
+
spinner.stop()
|
|
555
|
+
// 3. generate the zKey
|
|
556
|
+
await zKey.newZKey(r1csLocalPathAndFileName, getPotLocalFilePath(circuit.files.potFilename), zkeyLocalPathAndFileName, undefined)
|
|
557
|
+
|
|
558
|
+
// 4. calculate the hashes
|
|
559
|
+
const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName)
|
|
560
|
+
const potBlake2bHash = await blake512FromPath(getPotLocalFilePath(circuit.files.potFilename))
|
|
561
|
+
const initialZkeyBlake2bHash = await blake512FromPath(zkeyLocalPathAndFileName)
|
|
562
|
+
|
|
563
|
+
// 5. upload the artifacts
|
|
564
|
+
|
|
565
|
+
// Upload zKey to Storage.
|
|
566
|
+
await handleCircuitArtifactUploadToStorage(
|
|
567
|
+
firebaseFunctions,
|
|
568
|
+
bucketName,
|
|
569
|
+
circuit.files.initialZkeyStoragePath,
|
|
570
|
+
zkeyLocalPathAndFileName,
|
|
571
|
+
circuit.files.initialZkeyFilename
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
// Check if PoT file has been already uploaded to storage.
|
|
575
|
+
const alreadyUploadedPot = await checkIfObjectExist(
|
|
576
|
+
firebaseFunctions,
|
|
577
|
+
bucketName,
|
|
578
|
+
circuit.files.potStoragePath
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
// If it wasn't uploaded yet, upload it.
|
|
582
|
+
if (!alreadyUploadedPot) {
|
|
583
|
+
// Upload PoT to Storage.
|
|
584
|
+
await handleCircuitArtifactUploadToStorage(
|
|
585
|
+
firebaseFunctions,
|
|
586
|
+
bucketName,
|
|
587
|
+
circuit.files.potStoragePath,
|
|
588
|
+
potLocalPathAndFileName,
|
|
589
|
+
circuit.files.potFilename
|
|
590
|
+
)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Upload r1cs to Storage.
|
|
594
|
+
await handleCircuitArtifactUploadToStorage(
|
|
595
|
+
firebaseFunctions,
|
|
596
|
+
bucketName,
|
|
597
|
+
circuit.files.r1csStoragePath,
|
|
598
|
+
r1csLocalPathAndFileName,
|
|
599
|
+
circuit.files.r1csFilename
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
// Upload wasm to Storage.
|
|
603
|
+
await handleCircuitArtifactUploadToStorage(
|
|
604
|
+
firebaseFunctions,
|
|
605
|
+
bucketName,
|
|
606
|
+
circuit.files.wasmStoragePath,
|
|
607
|
+
r1csLocalPathAndFileName,
|
|
608
|
+
circuit.files.wasmFilename
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
// 6 update the setup data object
|
|
612
|
+
ceremonySetupData.circuits[index].files = {
|
|
613
|
+
...circuit.files,
|
|
614
|
+
potBlake2bHash: potBlake2bHash,
|
|
615
|
+
wasmBlake2bHash: wasmBlake2bHash,
|
|
616
|
+
initialZkeyBlake2bHash: initialZkeyBlake2bHash
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
ceremonySetupData.circuits[index].zKeySizeInBytes = getFileStats(zkeyLocalPathAndFileName).size
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
// 7. setup the ceremony
|
|
624
|
+
const ceremonyId = await setupCeremony(firebaseFunctions, ceremonySetupData.ceremonyInputData, ceremonySetupData.ceremonyPrefix, ceremonySetupData.circuits)
|
|
625
|
+
console.log( `Congratulations, the setup of ceremony ${theme.text.bold(
|
|
626
|
+
ceremonySetupData.ceremonyInputData.title
|
|
627
|
+
)} (${`UID: ${theme.text.bold(ceremonyId)}`}) has been successfully completed ${
|
|
628
|
+
theme.emojis.tada
|
|
629
|
+
}. You will be able to find all the files and info respectively in the ceremony bucket and database document.`)
|
|
630
|
+
|
|
631
|
+
terminate(providerUserId)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Look for R1CS files.
|
|
635
|
+
const r1csFilePaths = await filterDirectoryFilesByExtension(cwd, `.r1cs`)
|
|
636
|
+
// Look for WASM files.
|
|
637
|
+
const wasmFilePaths = await filterDirectoryFilesByExtension(cwd, `.wasm`)
|
|
638
|
+
// Look for pre-computed zKeys references (if any).
|
|
639
|
+
const localPreComputedZkeysFilenames = await filterDirectoryFilesByExtension(cwd, `.zkey`)
|
|
640
|
+
|
|
641
|
+
if (!r1csFilePaths.length) showError(COMMAND_ERRORS.COMMAND_SETUP_NO_R1CS, true)
|
|
642
|
+
if (!wasmFilePaths.length) showError(COMMAND_ERRORS.COMMAND_SETUP_NO_WASM, true)
|
|
643
|
+
if (wasmFilePaths.length !== r1csFilePaths.length) showError(COMMAND_ERRORS.COMMAND_SETUP_MISMATCH_R1CS_WASM, true)
|
|
644
|
+
|
|
645
|
+
// Prompt the coordinator for gather ceremony input data.
|
|
646
|
+
const ceremonyInputData = await promptCeremonyInputData(firestoreDatabase)
|
|
647
|
+
const ceremonyPrefix = extractPrefix(ceremonyInputData.title)
|
|
648
|
+
|
|
649
|
+
// Add circuits to ceremony.
|
|
650
|
+
const circuitsInputData: Array<CircuitInputData> = await handleAdditionOfCircuitsToCeremony(
|
|
651
|
+
r1csFilePaths.map((dirent: Dirent) => dirent.name),
|
|
652
|
+
wasmFilePaths.map((dirent: Dirent) => dirent.name),
|
|
653
|
+
ceremonyInputData.timeoutMechanismType
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
// Move input data to circuits.
|
|
657
|
+
circuitsInputData.forEach((data: CircuitInputData) => circuits.push(data))
|
|
658
|
+
|
|
659
|
+
// Display ceremony summary.
|
|
660
|
+
displayCeremonySummary(ceremonyInputData, circuits)
|
|
661
|
+
|
|
662
|
+
// Prepare data.
|
|
663
|
+
let wannaGenerateNewZkey = true // New zKey generation flag.
|
|
664
|
+
let wannaUsePreDownloadedPoT = false // Local PoT file usage flag.
|
|
665
|
+
let bucketName: string = "" // The name of the bucket.
|
|
666
|
+
|
|
667
|
+
// Ask for confirmation.
|
|
668
|
+
const { confirmation } = await askForConfirmation("Do you want to continue with the ceremony setup?", "Yes", "No")
|
|
669
|
+
|
|
670
|
+
if (confirmation) {
|
|
671
|
+
await simpleLoader(`Looking for any pre-computed zkey file...`, `clock`, 1000)
|
|
672
|
+
|
|
673
|
+
// Simulate pre-computed zkeys search.
|
|
674
|
+
let leftPreComputedZkeys = localPreComputedZkeysFilenames
|
|
675
|
+
|
|
676
|
+
/** Circuit-based setup */
|
|
677
|
+
for (let i = 0; i < circuits.length; i += 1) {
|
|
678
|
+
const circuit = circuits[i]
|
|
679
|
+
|
|
680
|
+
console.log(
|
|
681
|
+
theme.text.bold(`\n- Setup for Circuit # ${theme.colors.magenta(`${circuit.sequencePosition}`)}\n`)
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
// Convert to double digits powers (e.g., 9 -> 09).
|
|
685
|
+
let doubleDigitsPowers = convertToDoubleDigits(circuit.metadata?.pot!)
|
|
686
|
+
let smallestPowersOfTauCompleteFilenameForCircuit = `${potFilenameTemplate}${doubleDigitsPowers}.ptau`
|
|
687
|
+
|
|
688
|
+
// Rename R1Cs and zKey based on circuit name and prefix.
|
|
689
|
+
const r1csCompleteFilename = `${circuit.name}.r1cs`
|
|
690
|
+
const wasmCompleteFilename = `${circuit.name}.wasm`
|
|
691
|
+
const firstZkeyCompleteFilename = `${circuit.prefix}_${genesisZkeyIndex}.zkey`
|
|
692
|
+
let preComputedZkeyCompleteFilename = ``
|
|
693
|
+
|
|
694
|
+
// Local paths.
|
|
695
|
+
const r1csLocalPathAndFileName = getCWDFilePath(cwd, r1csCompleteFilename)
|
|
696
|
+
const wasmLocalPathAndFileName = getCWDFilePath(cwd, wasmCompleteFilename)
|
|
697
|
+
let potLocalPathAndFileName = getPotLocalFilePath(smallestPowersOfTauCompleteFilenameForCircuit)
|
|
698
|
+
let zkeyLocalPathAndFileName = getZkeyLocalFilePath(firstZkeyCompleteFilename)
|
|
699
|
+
|
|
700
|
+
// Storage paths.
|
|
701
|
+
const r1csStorageFilePath = getR1csStorageFilePath(circuit.prefix!, r1csCompleteFilename)
|
|
702
|
+
const wasmStorageFilePath = getWasmStorageFilePath(circuit.prefix!, wasmCompleteFilename)
|
|
703
|
+
let potStorageFilePath = getPotStorageFilePath(smallestPowersOfTauCompleteFilenameForCircuit)
|
|
704
|
+
const zkeyStorageFilePath = getZkeyStorageFilePath(circuit.prefix!, firstZkeyCompleteFilename)
|
|
705
|
+
|
|
706
|
+
if (leftPreComputedZkeys.length <= 0)
|
|
707
|
+
console.log(
|
|
708
|
+
`${theme.symbols.warning} No pre-computed zKey was found. Therefore, a new zKey from scratch will be generated.`
|
|
709
|
+
)
|
|
710
|
+
else {
|
|
711
|
+
// Prompt if coordinator wanna use a pre-computed zKey for the circuit.
|
|
712
|
+
const wannaUsePreComputedZkey = await promptPreComputedZkey()
|
|
713
|
+
|
|
714
|
+
if (wannaUsePreComputedZkey) {
|
|
715
|
+
// Prompt for pre-computed zKey selection.
|
|
716
|
+
const preComputedZkeyOptions = leftPreComputedZkeys.map((dirent: Dirent) => dirent.name)
|
|
717
|
+
preComputedZkeyCompleteFilename = await promptPreComputedZkeySelector(preComputedZkeyOptions)
|
|
718
|
+
|
|
719
|
+
// Switch to pre-computed zkey path.
|
|
720
|
+
zkeyLocalPathAndFileName = getCWDFilePath(cwd, preComputedZkeyCompleteFilename)
|
|
721
|
+
|
|
722
|
+
// Handle the selection for the PoT file to associate w/ the selected pre-computed zKey.
|
|
723
|
+
const {
|
|
724
|
+
doubleDigitsPowers: selectedDoubleDigitsPowers,
|
|
725
|
+
potCompleteFilename: selectedPotCompleteFilename,
|
|
726
|
+
usePreDownloadedPoT
|
|
727
|
+
} = await handlePreComputedZkeyPowersOfTauSelection(circuit.metadata?.pot!)
|
|
728
|
+
|
|
729
|
+
// Update state.
|
|
730
|
+
doubleDigitsPowers = selectedDoubleDigitsPowers
|
|
731
|
+
smallestPowersOfTauCompleteFilenameForCircuit = selectedPotCompleteFilename
|
|
732
|
+
wannaUsePreDownloadedPoT = usePreDownloadedPoT
|
|
733
|
+
|
|
734
|
+
// Update paths.
|
|
735
|
+
potLocalPathAndFileName = getPotLocalFilePath(smallestPowersOfTauCompleteFilenameForCircuit)
|
|
736
|
+
potStorageFilePath = getPotStorageFilePath(smallestPowersOfTauCompleteFilenameForCircuit)
|
|
737
|
+
|
|
738
|
+
// Check (and download) the smallest Powers of Tau for circuit.
|
|
739
|
+
if (!wannaUsePreDownloadedPoT)
|
|
740
|
+
await checkAndDownloadSmallestPowersOfTau(
|
|
741
|
+
doubleDigitsPowers,
|
|
742
|
+
smallestPowersOfTauCompleteFilenameForCircuit
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
// Update flag for zKey generation accordingly.
|
|
746
|
+
wannaGenerateNewZkey = false
|
|
747
|
+
|
|
748
|
+
// Update paths.
|
|
749
|
+
renameSync(getCWDFilePath(cwd, preComputedZkeyCompleteFilename), firstZkeyCompleteFilename) // the pre-computed zKey become the new first (genesis) zKey.
|
|
750
|
+
zkeyLocalPathAndFileName = getCWDFilePath(cwd, firstZkeyCompleteFilename)
|
|
751
|
+
|
|
752
|
+
// Remove the pre-computed zKey from the list of possible pre-computed options.
|
|
753
|
+
leftPreComputedZkeys = leftPreComputedZkeys.filter(
|
|
754
|
+
(dirent: Dirent) => dirent.name !== preComputedZkeyCompleteFilename
|
|
755
|
+
)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Check (and download) the smallest Powers of Tau for circuit.
|
|
760
|
+
if (!wannaUsePreDownloadedPoT)
|
|
761
|
+
await checkAndDownloadSmallestPowersOfTau(
|
|
762
|
+
doubleDigitsPowers,
|
|
763
|
+
smallestPowersOfTauCompleteFilenameForCircuit
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
if (wannaGenerateNewZkey)
|
|
767
|
+
await handleNewZkeyGeneration(
|
|
768
|
+
r1csLocalPathAndFileName,
|
|
769
|
+
potLocalPathAndFileName,
|
|
770
|
+
zkeyLocalPathAndFileName
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
// Create a bucket for ceremony if it has not yet been created.
|
|
774
|
+
if (!bucketName) bucketName = await handleCeremonyBucketCreation(firebaseFunctions, ceremonyPrefix)
|
|
775
|
+
|
|
776
|
+
// Upload zKey to Storage.
|
|
777
|
+
await handleCircuitArtifactUploadToStorage(
|
|
778
|
+
firebaseFunctions,
|
|
779
|
+
bucketName,
|
|
780
|
+
zkeyStorageFilePath,
|
|
781
|
+
zkeyLocalPathAndFileName,
|
|
782
|
+
firstZkeyCompleteFilename
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
// Check if PoT file has been already uploaded to storage.
|
|
786
|
+
const alreadyUploadedPot = await checkIfObjectExist(
|
|
787
|
+
firebaseFunctions,
|
|
788
|
+
bucketName,
|
|
789
|
+
getPotStorageFilePath(smallestPowersOfTauCompleteFilenameForCircuit)
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
if (!alreadyUploadedPot) {
|
|
793
|
+
// Upload PoT to Storage.
|
|
794
|
+
await handleCircuitArtifactUploadToStorage(
|
|
795
|
+
firebaseFunctions,
|
|
796
|
+
bucketName,
|
|
797
|
+
potStorageFilePath,
|
|
798
|
+
potLocalPathAndFileName,
|
|
799
|
+
smallestPowersOfTauCompleteFilenameForCircuit
|
|
800
|
+
)
|
|
801
|
+
} else
|
|
802
|
+
console.log(
|
|
803
|
+
`${theme.symbols.success} The Powers of Tau (${theme.text.bold(
|
|
804
|
+
smallestPowersOfTauCompleteFilenameForCircuit
|
|
805
|
+
)}) file is already saved in the storage`
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
// Upload R1CS to Storage.
|
|
809
|
+
await handleCircuitArtifactUploadToStorage(
|
|
810
|
+
firebaseFunctions,
|
|
811
|
+
bucketName,
|
|
812
|
+
r1csStorageFilePath,
|
|
813
|
+
r1csLocalPathAndFileName,
|
|
814
|
+
r1csCompleteFilename
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
// Upload WASM to Storage.
|
|
818
|
+
await handleCircuitArtifactUploadToStorage(
|
|
819
|
+
firebaseFunctions,
|
|
820
|
+
bucketName,
|
|
821
|
+
wasmStorageFilePath,
|
|
822
|
+
wasmLocalPathAndFileName,
|
|
823
|
+
wasmCompleteFilename
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
process.stdout.write(`\n`)
|
|
827
|
+
|
|
828
|
+
const spinner = customSpinner(`Preparing the ceremony data (this may take a while)...`, `clock`)
|
|
829
|
+
spinner.start()
|
|
830
|
+
|
|
831
|
+
// Computing file hash (this may take a while).
|
|
832
|
+
const r1csBlake2bHash = await blake512FromPath(r1csLocalPathAndFileName)
|
|
833
|
+
const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName)
|
|
834
|
+
const potBlake2bHash = await blake512FromPath(potLocalPathAndFileName)
|
|
835
|
+
const initialZkeyBlake2bHash = await blake512FromPath(zkeyLocalPathAndFileName)
|
|
836
|
+
|
|
837
|
+
spinner.stop()
|
|
838
|
+
|
|
839
|
+
// Prepare circuit data for writing to the DB.
|
|
840
|
+
const circuitFiles: CircuitArtifacts = {
|
|
841
|
+
r1csFilename: r1csCompleteFilename,
|
|
842
|
+
wasmFilename: wasmCompleteFilename,
|
|
843
|
+
potFilename: smallestPowersOfTauCompleteFilenameForCircuit,
|
|
844
|
+
initialZkeyFilename: firstZkeyCompleteFilename,
|
|
845
|
+
r1csStoragePath: r1csStorageFilePath,
|
|
846
|
+
wasmStoragePath: wasmStorageFilePath,
|
|
847
|
+
potStoragePath: potStorageFilePath,
|
|
848
|
+
initialZkeyStoragePath: zkeyStorageFilePath,
|
|
849
|
+
r1csBlake2bHash,
|
|
850
|
+
wasmBlake2bHash,
|
|
851
|
+
potBlake2bHash,
|
|
852
|
+
initialZkeyBlake2bHash
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// nb. these will be populated after the first contribution.
|
|
856
|
+
const circuitTimings: CircuitTimings = {
|
|
857
|
+
contributionComputation: 0,
|
|
858
|
+
fullContribution: 0,
|
|
859
|
+
verifyCloudFunction: 0
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
circuits[i] = {
|
|
863
|
+
...circuit,
|
|
864
|
+
files: circuitFiles,
|
|
865
|
+
avgTimings: circuitTimings,
|
|
866
|
+
zKeySizeInBytes: getFileStats(zkeyLocalPathAndFileName).size
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Reset flags.
|
|
870
|
+
wannaGenerateNewZkey = true
|
|
871
|
+
wannaUsePreDownloadedPoT = false
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const spinner = customSpinner(`Writing ceremony data...`, `clock`)
|
|
875
|
+
spinner.start()
|
|
876
|
+
|
|
877
|
+
try {
|
|
878
|
+
// Call the Cloud Function for writing ceremony data on Firestore DB.
|
|
879
|
+
ceremonyId = await setupCeremony(firebaseFunctions, ceremonyInputData, ceremonyPrefix, circuits)
|
|
880
|
+
} catch (error: any) {
|
|
881
|
+
const errorBody = JSON.parse(JSON.stringify(error))
|
|
882
|
+
showError(
|
|
883
|
+
`[${errorBody.code}] ${error.message} ${!errorBody.details ? "" : `\n${errorBody.details}`}`,
|
|
884
|
+
true
|
|
885
|
+
)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
await sleep(5000) // Cloud function unexpected termination workaround.
|
|
889
|
+
|
|
890
|
+
spinner.succeed(
|
|
891
|
+
`Congratulations, the setup of ceremony ${theme.text.bold(
|
|
892
|
+
ceremonyInputData.title
|
|
893
|
+
)} (${`UID: ${theme.text.bold(ceremonyId)}`}) has been successfully completed ${
|
|
894
|
+
theme.emojis.tada
|
|
895
|
+
}. You will be able to find all the files and info respectively in the ceremony bucket and database document.`
|
|
896
|
+
)
|
|
897
|
+
}
|
|
898
|
+
terminate(providerUserId)
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
export default setup
|