Skip to content

Commit d032f37

Browse files
committed
feat(setup): non interactive setup with artifacts download
1 parent 331d2cb commit d032f37

File tree

8 files changed

+103
-34
lines changed

8 files changed

+103
-34
lines changed

packages/actions/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"dependencies": {
4343
"@adobe/node-fetch-retry": "^2.2.0",
4444
"@aws-sdk/client-ec2": "^3.358.0",
45+
"@aws-sdk/client-s3": "^3.329.0",
4546
"@aws-sdk/client-ssm": "^3.358.0",
4647
"@octokit/auth-oauth-device": "^4.0.4",
4748
"@types/winston": "^2.4.4",

packages/actions/src/helpers/utils.ts

+52-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Firestore } from "firebase/firestore"
22
import fs, { ReadPosition } from "fs"
33
import { utils as ffUtils } from "ffjavascript"
44
import winston, { Logger } from "winston"
5+
import { S3Client, GetObjectCommand, HeadObjectCommand } from "@aws-sdk/client-s3"
56
import {
67
CircuitMetadata,
78
Contribution,
@@ -27,14 +28,17 @@ import {
2728
getWasmStorageFilePath,
2829
getZkeyStorageFilePath
2930
} from "./storage"
31+
import { blake512FromPath } from "./crypto"
32+
import { Readable } from "stream"
3033

3134
/**
3235
* Parse and validate that the ceremony configuration is correct
3336
* @notice this does not upload any files to storage
3437
* @param path <string> - the path to the configuration file
38+
* @param cleanup <boolean> - whether to delete the r1cs file after parsing
3539
* @returns any - the data to pass to the cloud function for setup and the circuit artifacts
3640
*/
37-
export const parseCeremonyFile = (path: string): SetupCeremonyData => {
41+
export const parseCeremonyFile = async (path: string, cleanup: boolean = false): Promise<SetupCeremonyData> => {
3842
// check that the path exists
3943
if (!fs.existsSync(path)) throw new Error("The provided path to the configuration file does not exist. Please provide an absolute path and try again.")
4044

@@ -82,15 +86,48 @@ export const parseCeremonyFile = (path: string): SetupCeremonyData => {
8286
circuitArtifacts.push({
8387
artifacts: artifacts
8488
})
85-
const r1csPath = artifacts.r1csLocalFilePath
86-
const wasmPath = artifacts.wasmLocalFilePath
89+
const r1csPath = artifacts.r1csStoragePath
90+
const wasmPath = artifacts.wasmStoragePath
91+
92+
// where we storing the r1cs downloaded
93+
const localR1csPath = `./${circuitData.name}.r1cs`
94+
95+
// check that the artifacts exist in S3
96+
const s3 = new S3Client({region: 'us-east-1'})
97+
98+
try {
99+
await s3.send(new HeadObjectCommand({
100+
Bucket: circuitData.artifacts.bucket,
101+
Key: r1csPath
102+
}))
103+
} catch (error: any) {
104+
throw new Error("The r1cs file seems to not exist. Please ensure this is correct and that the object is publicly available.")
105+
}
106+
107+
try {
108+
await s3.send(new HeadObjectCommand({
109+
Bucket: artifacts.bucket,
110+
Key: wasmPath
111+
}))
112+
} catch (error: any) {
113+
throw new Error("The wasm file seems to not exist. Please ensure this is correct and that the object is publicly available.")
114+
}
87115

88-
// ensure that the artifact exist locally
89-
if (!fs.existsSync(r1csPath)) throw new Error("The path to the r1cs file does not exist. Please ensure this is correct and that an absolute path is provided.")
90-
if (!fs.existsSync(wasmPath)) throw new Error("The path to the wasm file does not exist. Please ensure this is correct and that an absolute path is provided.")
116+
// download the r1cs to extract the metadata
117+
const command = new GetObjectCommand({ Bucket: artifacts.bucket, Key: artifacts.r1csStoragePath })
118+
const response = await s3.send(command)
119+
120+
const fileStream = fs.createWriteStream(localR1csPath)
121+
if (response.$metadata.httpStatusCode !== 200) {
122+
throw new Error("There was an error while trying to download the r1cs file. Please check that the file has the correct permissions (public) set.")
123+
}
124+
// const streamPipeline = promisify(pipeline)
125+
if (response.Body instanceof Readable) {
126+
response.Body.pipe(fileStream)
127+
}
91128

92129
// extract the metadata from the r1cs
93-
const metadata = getR1CSInfo(r1csPath)
130+
const metadata = getR1CSInfo(localR1csPath)
94131

95132
// validate that the circuit hash and template links are valid
96133
const template = circuitData.template
@@ -101,6 +138,9 @@ export const parseCeremonyFile = (path: string): SetupCeremonyData => {
101138
const hashMatch = template.commitHash.match(commitHashPattern)
102139
if (!hashMatch || hashMatch.length === 0 || hashMatch.length > 1) throw new Error("You should provide a valid commit hash of the circuit templates.")
103140

141+
// calculate the hash of the r1cs file
142+
const r1csBlake2bHash = await blake512FromPath(localR1csPath)
143+
104144
const circuitPrefix = extractPrefix(circuitData.name)
105145

106146
// filenames
@@ -124,7 +164,8 @@ export const parseCeremonyFile = (path: string): SetupCeremonyData => {
124164
potStoragePath: potStorageFilePath,
125165
r1csStoragePath: r1csStorageFilePath,
126166
wasmStoragePath: wasmStorageFilePath,
127-
initialZkeyStoragePath: zkeyStorageFilePath
167+
initialZkeyStoragePath: zkeyStorageFilePath,
168+
r1csBlake2bHash: r1csBlake2bHash
128169
}
129170

130171
// validate that the compiler hash is a valid hash
@@ -177,6 +218,9 @@ export const parseCeremonyFile = (path: string): SetupCeremonyData => {
177218
}
178219

179220
circuits.push(circuit)
221+
222+
// remove the local r1cs download (if used for verifying the config only vs setup)
223+
if (cleanup) fs.unlinkSync(localR1csPath)
180224
}
181225

182226
const setupData: SetupCeremonyData = {

packages/actions/src/types/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -623,8 +623,9 @@ export type SetupCeremonyData = {
623623

624624
export type CeremonySetupTemplateCircuitArtifacts = {
625625
artifacts: {
626-
r1csLocalFilePath: string
627-
wasmLocalFilePath: string
626+
bucket: string
627+
r1csStoragePath: string
628+
wasmStoragePath: string
628629
}
629630
}
630631

packages/actions/test/data/artifacts/ceremonySetup.json

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"title": "Example Ceremony",
2+
"title": "Test dev ceremony",
33
"description": "This is an example ceremony",
4-
"startDate": "2023-07-07T00:00:00",
5-
"endDate": "2023-07-10T00:00:00",
4+
"startDate": "2023-08-07T00:00:00",
5+
"endDate": "2023-09-10T00:00:00",
66
"timeoutMechanismType": "FIXED",
77
"penalty": 10,
88
"circuits": [
99
{
10-
"description": "description",
10+
"description": "description1",
1111
"compiler": {
1212
"version": "1.0",
1313
"commitHash": "b7ad01b11f9b4195e38ecc772291251260ab2c67"
@@ -21,8 +21,9 @@
2121
"cfOrVm": "CF"
2222
},
2323
"artifacts": {
24-
"r1csLocalFilePath": "./circuit.r1cs",
25-
"wasmLocalFilePath": "./circuit.wasm"
24+
"bucket": "test-qfi-p0tion-development-environment",
25+
"r1csStoragePath": "circuits/circuit/circuit.r1cs",
26+
"wasmStoragePath": "circuits/circuit/circuit.wasm"
2627
},
2728
"name": "circuit",
2829
"dynamicThreshold": 0,

packages/backend/src/lib/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ export const deleteObject = async (bucketName: string, objectKey: string) => {
300300

301301
// Prepare command.
302302
const command = new DeleteObjectCommand({ Bucket: bucketName, Key: objectKey })
303-
303+
304304
// Execute command.
305305
const data = await client.send(command)
306306

packages/phase2cli/src/commands/setup.ts

+35-14
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import { zKey } from "snarkjs"
44
import boxen from "boxen"
5-
import { createWriteStream, Dirent, renameSync } from "fs"
5+
import { createWriteStream, Dirent, renameSync, createReadStream } from "fs"
66
import { pipeline } from "node:stream"
77
import { promisify } from "node:util"
88
import fetch from "node-fetch"
99
import { Functions } from "firebase/functions"
10+
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"
11+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
1012
import {
1113
CeremonyTimeoutType,
1214
CircomCompilerData,
@@ -62,6 +64,7 @@ import {
6264
getFileStats,
6365
checkAndMakeNewDirectoryIfNonexistent
6466
} from "../lib/files.js"
67+
import { Readable } from "stream"
6568

6669
/**
6770
* Handle whatever is needed to obtain the input data for a circuit that the coordinator would like to add to the ceremony.
@@ -501,38 +504,54 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
501504
// if there is the file option, then set up the non interactively
502505
if (cmd.template) {
503506
// 1. parse the file
504-
// tmp data
505-
const setupCeremonyData = parseCeremonyFile(cmd.template!)
507+
// tmp data - do not cleanup files as we need them
508+
const setupCeremonyData = await parseCeremonyFile(cmd.template!)
506509
// final setup data
507510
const ceremonySetupData = setupCeremonyData
508511

509512
// create a new bucket
510513
const bucketName = await handleCeremonyBucketCreation(firebaseFunctions, ceremonySetupData.ceremonyPrefix)
511514
console.log(`\n${theme.symbols.success} Ceremony bucket name: ${theme.text.bold(bucketName)}`)
512515

516+
// create S3 clienbt
517+
const s3 = new S3Client({region: 'us-east-1'})
518+
513519
// loop through each circuit
514-
for (const circuit of setupCeremonyData.circuits) {
520+
for await (const circuit of setupCeremonyData.circuits) {
515521
// Local paths.
516522
const index = ceremonySetupData.circuits.indexOf(circuit)
517-
const r1csLocalPathAndFileName = setupCeremonyData.circuitArtifacts[index].artifacts.r1csLocalFilePath
518-
const wasmLocalPathAndFileName = setupCeremonyData.circuitArtifacts[index].artifacts.wasmLocalFilePath
523+
const r1csLocalPathAndFileName = `./${circuit.name}.r1cs`
524+
const wasmLocalPathAndFileName = `./${circuit.name}.wasm`
519525
const potLocalPathAndFileName = getPotLocalFilePath(circuit.files.potFilename)
520526
const zkeyLocalPathAndFileName = getZkeyLocalFilePath(circuit.files.initialZkeyFilename)
521527

522-
// 2. download the pot
528+
// 2. download the pot and wasm files
523529
const streamPipeline = promisify(pipeline)
524-
const response = await fetch(`${potFileDownloadMainUrl}${circuit.files.potFilename}`)
530+
const potResponse = await fetch(`${potFileDownloadMainUrl}${circuit.files.potFilename}`)
525531

526532
// Handle errors.
527-
if (!response.ok && response.status !== 200) showError("Error while setting up the ceremony. Could not download the powers of tau file.", true)
533+
if (!potResponse.ok && potResponse.status !== 200) showError("Error while setting up the ceremony. Could not download the powers of tau file.", true)
528534

529-
await streamPipeline(response.body!, createWriteStream(potLocalPathAndFileName))
535+
await streamPipeline(potResponse.body!, createWriteStream(potLocalPathAndFileName))
536+
537+
// download the wasm to calculate the hash
538+
const command = new GetObjectCommand({ Bucket: ceremonySetupData.circuitArtifacts[index].artifacts.bucket, Key: ceremonySetupData.circuitArtifacts[index].artifacts.wasmStoragePath })
539+
540+
const response = await s3.send(command)
541+
542+
const fileStream = createWriteStream(wasmLocalPathAndFileName)
543+
if (response.$metadata.httpStatusCode !== 200) {
544+
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.")
545+
}
546+
// const streamPipeline = promisify(pipeline)
547+
if (response.Body instanceof Readable) {
548+
response.Body.pipe(fileStream)
549+
}
530550

531551
// 3. generate the zKey
532552
await zKey.newZKey(r1csLocalPathAndFileName, potLocalPathAndFileName, zkeyLocalPathAndFileName, undefined)
533553

534554
// 4. calculate the hashes
535-
const r1csBlake2bHash = await blake512FromPath(r1csLocalPathAndFileName)
536555
const wasmBlake2bHash = await blake512FromPath(wasmLocalPathAndFileName)
537556
const potBlake2bHash = await blake512FromPath(potLocalPathAndFileName)
538557
const initialZkeyBlake2bHash = await blake512FromPath(zkeyLocalPathAndFileName)
@@ -557,7 +576,7 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
557576
circuit.files.potFilename
558577
)
559578

560-
// Upload R1CS to Storage.
579+
// Move r1cs between buckets
561580
await handleCircuitArtifactUploadToStorage(
562581
firebaseFunctions,
563582
bucketName,
@@ -566,7 +585,7 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
566585
circuit.files.r1csFilename
567586
)
568587

569-
// Upload WASM to Storage.
588+
// Move wasm between buckets.
570589
await handleCircuitArtifactUploadToStorage(
571590
firebaseFunctions,
572591
bucketName,
@@ -575,11 +594,13 @@ const setup = async (cmd: { template?: string, auth?: string}) => {
575594
circuit.files.wasmFilename
576595
)
577596

597+
// @todo move the artifacts from the origin bucket to the new bucket
598+
// instead of uploading r1cs and wasm
599+
578600
// 6 update the setup data object
579601
ceremonySetupData.circuits[index].files = {
580602
...circuit.files,
581603
potBlake2bHash: potBlake2bHash,
582-
r1csBlake2bHash: r1csBlake2bHash,
583604
wasmBlake2bHash: wasmBlake2bHash,
584605
initialZkeyBlake2bHash: initialZkeyBlake2bHash
585606
}

packages/phase2cli/src/commands/validate.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { showError } from "../lib/errors.js"
66
*/
77
const validate = async (cmd: { template: string, constraints?: number }) => {
88
try {
9-
// parse the file
10-
const parsedFile = parseCeremonyFile(cmd.template)
9+
// parse the file and cleanup after
10+
const parsedFile = await parseCeremonyFile(cmd.template, true)
1111
// check whether we have a constraints option otherwise default to 1M
1212
const constraints = cmd.constraints || 1000000
13-
for (const circuit of parsedFile.circuits) {
13+
for await (const circuit of parsedFile.circuits) {
1414
if (circuit.metadata.constraints > constraints) {
1515
console.log(false)
1616
process.exit(0)

yarn.lock

+1
Original file line numberDiff line numberDiff line change
@@ -6146,6 +6146,7 @@ __metadata:
61466146
dependencies:
61476147
"@adobe/node-fetch-retry": ^2.2.0
61486148
"@aws-sdk/client-ec2": ^3.358.0
6149+
"@aws-sdk/client-s3": ^3.329.0
61496150
"@aws-sdk/client-ssm": ^3.358.0
61506151
"@ethersproject/providers": ^5.7.2
61516152
"@nomicfoundation/hardhat-chai-matchers": ^1.0.6

0 commit comments

Comments
 (0)