diff --git a/Dockerfile b/Dockerfile index a65e973..b046a02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ ENV NODE_ENV=development ARG GID=1000 ARG UID=1000 RUN if [ "$GID" != "1000" ]; then \ + if getent group $GID; then groupdel $(getent group $GID | cut -d: -f1); fi && \ groupmod -g $GID node && \ (find / -group 1000 -exec chgrp -h $GID {} \; || true) \ ; fi @@ -14,6 +15,7 @@ RUN if [ "$UID" != "1000" ]; then \ ; fi RUN apt-get update +RUN apt-get install --yes git USER node diff --git a/compose.yaml b/compose.yaml index 06d5f49..b58462c 100644 --- a/compose.yaml +++ b/compose.yaml @@ -16,6 +16,7 @@ services: restart: unless-stopped volumes: - ./:/sindri/ + - ~/.gitconfig:/home/node/.gitconfig - yarn-cache:/home/node/.cache/yarn/ volumes: diff --git a/package.json b/package.json index dc4311e..c0537a0 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,7 @@ "name": "sindri", "version": "0.0.0", "description": "The Sindri Labs JavaScript SDK and CLI tool.", - "files": [ - "dist/", - "src/", - "sindri-manifest.json" - ], + "files": ["dist/", "sindri-manifest.json", "src/", "templates/"], "main": "dist/lib/index.js", "module": "dist/lib/index.mjs", "bin": { @@ -55,6 +51,7 @@ "ignore-walk": "^6.0.4", "jsonschema": "^1.4.1", "lodash": "^4.17.21", + "nunjucks": "^3.2.4", "pino": "^8.16.2", "pino-pretty": "^10.2.3", "rc": "^1.2.8", @@ -67,6 +64,7 @@ "@types/ignore-walk": "^4.0.3", "@types/lodash": "^4.14.202", "@types/node": "^20.9.1", + "@types/nunjucks": "^3.2.6", "@types/tar": "^6.1.10", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", diff --git a/src/cli/deploy.ts b/src/cli/deploy.ts index eb4dd4e..04d848c 100644 --- a/src/cli/deploy.ts +++ b/src/cli/deploy.ts @@ -115,6 +115,7 @@ export const deployCommand = new Command() // Upload the tarball. let circuitId: string | undefined; try { + logger.info("Circuit compilation initiated."); const response = await CircuitsService.circuitCreate(formData); circuitId = response.circuit_id; logger.debug("/api/v1/circuit/create/ response:"); diff --git a/src/cli/index.ts b/src/cli/index.ts index 9870231..a123d5d 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -4,6 +4,7 @@ import { argv, exit } from "process"; import { Command } from "@commander-js/extra-typings"; import { Config, configCommand } from "cli/config"; +import { initCommand } from "cli/init"; import { deployCommand } from "cli/deploy"; import { lintCommand } from "cli/lint"; import { logger } from "cli/logging"; @@ -23,6 +24,7 @@ export const program = new Command() false, ) .addCommand(configCommand) + .addCommand(initCommand) .addCommand(deployCommand) .addCommand(lintCommand) .addCommand(loginCommand) diff --git a/src/cli/init.ts b/src/cli/init.ts new file mode 100644 index 0000000..b6f4ef1 --- /dev/null +++ b/src/cli/init.ts @@ -0,0 +1,193 @@ +import { execSync } from "child_process"; +import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from "fs"; +import path from "path"; +import process from "process"; + +import { Command } from "@commander-js/extra-typings"; +import { confirm, input, select } from "@inquirer/prompts"; + +import { logger } from "cli/logging"; +import { scaffoldDirectory } from "cli/utils"; + +export const initCommand = new Command() + .name("init") + .description("Initialize a new Sindri project.") + .argument( + "[directory]", + "The directory where the new project should be initialized.", + ".", + ) + .action(async (directory) => { + // Prepare the directory paths. + const directoryPath = path.resolve(directory); + const directoryName = path.basename(directoryPath); + + // Ensure that the directory exists. + if (!existsSync(directoryPath)) { + mkdirSync(directoryPath, { recursive: true }); + } else if (!statSync(directoryPath).isDirectory()) { + logger.warn( + `File "${directoryPath}" exists and is not a directory, aborting.`, + ); + return process.exit(1); + } + + // Check that the directory is empty. + const existingFiles = readdirSync(directoryPath); + if (existingFiles.length > 0) { + const proceed = await confirm({ + message: + `The "${directoryPath}" directory already exists and contains files. Continuing will ` + + "overwrite your existing files. Are you *SURE* you would like to proceed?", + default: false, + }); + if (!proceed) { + logger.info("Aborting."); + return process.exit(1); + } + } + + // Collect common fields. + const circuitName = await input({ + message: "Circuit Name:", + default: directoryName.replace(/[^-a-zA-Z0-9_]/g, "-"), + validate: (input): boolean | string => { + if (input.length === 0) { + return "You must specify a circuit name."; + } + if (!/^[-a-zA-Z0-9_]+$/.test(input)) { + return "Only alphanumeric characters, hyphens, and underscores are allowed."; + } + return true; + }, + }); + const circuitType: "circom" | "gnark" | "halo2" | "noir" = await select({ + message: "Proving Framework:", + default: "gnark", + choices: [ + { name: "Circom", value: "circom" }, + { name: "Gnark", value: "gnark" }, + { name: "Halo2", value: "halo2" }, + { name: "Noir", value: "noir" }, + ], + }); + const context: object = { circuitName, circuitType }; + + // Handle individual circuit types. + // Gnark. + if (circuitType === "gnark") { + const packageName = await input({ + message: "Go Package Name:", + default: circuitName + .replace(/[^a-zA-Z0-9]/g, "") + .replace(/^[^a-z]*/, ""), + validate: (input): boolean | string => { + if (input.length === 0) { + return "You must specify a package name."; + } + if (!/^[a-z][a-z0-9]*$/.test(input)) { + return ( + "Package names must begin with a lowercase letter and only be followed by " + + "alphanumeric characters." + ); + } + return true; + }, + }); + const provingScheme: "groth16" = await select({ + message: "Proving Scheme:", + default: "groth16", + choices: [{ name: "Groth16", value: "groth16" }], + }); + const curveName: + | "bn254" + | "bls12-377" + | "bls12-381" + | "bls24-315" + | "bw6-633" + | "bw6-761" = await select({ + message: "Curve Name:", + default: "bn254", + choices: [ + { name: "BN254", value: "bn254" }, + { name: "BLS12-377", value: "bls12-377" }, + { name: "BLS12-381", value: "bls12-381" }, + { name: "BLS24-315", value: "bls24-315" }, + { name: "BW6-633", value: "bw6-633" }, + { name: "BW6-761", value: "bw6-761" }, + ], + }); + const gnarkCurveName = curveName.toUpperCase().replace("-", "_"); + Object.assign(context, { + curveName, + gnarkCurveName, + packageName, + provingScheme, + }); + } else { + logger.fatal(`Sorry, ${circuitType} is not yet supported.`); + return process.exit(1); + } + + // Perform the scaffolding. + logger.info( + `Proceeding to generate scaffolded project in "${directoryPath}".`, + ); + await scaffoldDirectory("common", directoryPath, context); + await scaffoldDirectory(circuitType, directoryPath, context); + // We use this in `common` right now to keep the directory tracked, we can remove this once we + // add files there. + const gitKeepFile = path.join(directoryPath, ".gitkeep"); + if (existsSync(gitKeepFile)) { + rmSync(gitKeepFile); + } + logger.info("Project scaffolding successful."); + + // Optionally, initialize a git repository. + let gitInstalled: boolean = false; + try { + execSync("git --version"); + gitInstalled = true; + } catch { + logger.debug( + "Git is not installed, skipping git initialization questions.", + ); + } + const gitAlreadyInitialized = existsSync(path.join(directoryPath, ".git")); + if (gitInstalled && !gitAlreadyInitialized) { + const initializeGit = await confirm({ + message: `Would you like to initialize a git repository in "${directoryPath}"?`, + default: true, + }); + if (initializeGit) { + logger.info(`Initializing git repository in "${directoryPath}".`); + try { + execSync("git init .", { cwd: directoryPath }); + execSync("git add .", { cwd: directoryPath }); + execSync("git commit -m 'Initial commit.'", { cwd: directoryPath }); + logger.info("Successfully initialized git repository."); + } catch (error) { + logger.error("Error occurred while initializing the git repository."); + // Node.js doesn't seem to have a typed version of this error, so we assert it as + // something that's at least in the right ballpark. + const execError = error as NodeJS.ErrnoException & { + output: Buffer | string; + stderr: Buffer | string; + stdout: Buffer | string; + }; + // The output is a really long list of numbers because it's a buffer, so truncate it. + const noisyKeys: Array<"output" | "stderr" | "stdout"> = [ + "output", + "stderr", + "stdout", + ]; + noisyKeys.forEach((key) => { + if (key in execError) { + execError[key] = ""; + } + }); + logger.error(execError); + } + } + } + }); diff --git a/src/cli/utils.ts b/src/cli/utils.ts index d26c534..edf5b88 100644 --- a/src/cli/utils.ts +++ b/src/cli/utils.ts @@ -1,13 +1,32 @@ -import { readdirSync, readFileSync } from "fs"; +import { constants as fsConstants, readdirSync, readFileSync } from "fs"; +import { access, mkdir, readdir, readFile, stat, writeFile } from "fs/promises"; import path from "path"; import { fileURLToPath } from "url"; import type { Schema } from "jsonschema"; +import nunjucks from "nunjucks"; import type { PackageJson } from "type-fest"; +import { logger } from "cli/logging"; + const currentFilePath = fileURLToPath(import.meta.url); const currentDirectoryPath = path.dirname(currentFilePath); +/** + * Checks whether or not a file (including directories) exists. + * + * @param filePath - The path of the file to check. + * @returns A boolean value indicating whether the file path exists. + */ +export async function fileExists(filePath: string): Promise { + try { + await access(filePath, fsConstants.F_OK); + return true; + } catch { + return false; + } +} + /** * Recursively searches for a file in the given directory and its parent directories. * @@ -86,3 +105,91 @@ export function locatePackageJson(): string { } return packageJsonPath; } + +/** + * Recursively copies and populates the contents of a template directory into an output directory. + * + * @param templateDirectory - The path to the template directory. Can be an absolute path or a + * subdirectory of the `templates/` directory in the project root. + * @param outputDirectory - The path to the output directory where the populated templates will be + * written. + * @param context - The nunjucks template context. + */ +export async function scaffoldDirectory( + templateDirectory: string, + outputDirectory: string, + context: object, +): Promise { + // Normalize the paths and create the output directory if necessary. + const fullOutputDirectory = path.resolve(outputDirectory); + if (!(await fileExists(fullOutputDirectory))) { + await mkdir(fullOutputDirectory, { recursive: true }); + } + const rootTemplateDirectory = findFileUpwards("templates"); + if (!rootTemplateDirectory) { + throw new Error("Root template directory not found."); + } + const fullTemplateDirectory = path.isAbsolute(templateDirectory) + ? templateDirectory + : path.resolve(rootTemplateDirectory, templateDirectory); + if (!(await fileExists(fullTemplateDirectory))) { + throw new Error(`The "${fullTemplateDirectory}" directory does not exist.`); + } + + // Render a template using two syntaxes: + // * hacky `templateVARIABLENAME` syntax. + // * `nunjucks` template syntax. + const render = (content: string, context: object): string => { + let newContent = content; + // Poor man's templating with `templateVARIABLENAME`: + Object.entries(context).forEach(([key, value]) => { + if (typeof value !== "string") return; + newContent = newContent.replace( + new RegExp(`template${key.toUpperCase()}`, "gi"), + value, + ); + }); + // Real templating: + return nunjucks.renderString(newContent, context); + }; + + // Process the template directory recursively. + const processPath = async ( + inputPath: string, + outputPath: string, + ): Promise => { + // Handle directories. + if ((await stat(inputPath)).isDirectory()) { + // Ensure the output directory exists. + if (!(await fileExists(outputPath))) { + await mkdir(outputPath, { recursive: true }); + logger.debug(`Created directory: "${outputPath}"`); + } + if (!(await stat(outputPath)).isDirectory()) { + throw new Error(`"File ${outputPath} exists and is not a directory.`); + } + + // Process all files in the directory. + const files = await readdir(inputPath); + await Promise.all( + files.map(async (file) => { + // Render the filename so that `outputPath` always corresponds to the true output path. + // This handles situations like `{{ circuitName }}.go` where there's a variable in the name. + const populatedFile = render(file, context); + await processPath( + path.join(inputPath, file), + path.join(outputPath, populatedFile), + ); + }), + ); + return; + } + + // Handle files, rendering them and writing them out. + const template = await readFile(inputPath, { encoding: "utf-8" }); + const renderedTemplate = render(template, context); + await writeFile(outputPath, renderedTemplate, { encoding: "utf-8" }); + logger.debug(`Rendered "${inputPath}" template to "${outputPath}".`); + }; + await processPath(fullTemplateDirectory, fullOutputDirectory); +} diff --git a/templates/README.md b/templates/README.md new file mode 100644 index 0000000..06dc94b --- /dev/null +++ b/templates/README.md @@ -0,0 +1,12 @@ +# Project Scaffolding Templates + +Each directory here corresponds to a project scaffold where the directory name corresponds to the `circuitName` variable collected when using the `sindri init` command. +Any file under the `{{ circuitType }}` directory will be copied into the destination directory as a rendered template. +The templating rendering includes file names, directory names, and the contents of each file. +There are two templating mechanisms that are applied. + +1. Any [`nunjucks`](https://mozilla.github.io/nunjucks/) templating will be applied, again in both filenames and file contents. + This includes variable substitution as well as more sophisticated features like conditional logic or loops. +2. Any `templateVariableName` string in a case-insensitive search will be replaced if the `context` variable is a string. + This pass exists so that variables can be used without breaking code formatting, LSP completion, and other helpful editor features that don't play nicely with `{{ variableName }}` syntax. + It should only be used if you run into issues with the nunjucks templates, and camelCase names like `templateCircuitName` should be preferred unless there's a reason to do otherwise. diff --git a/templates/common/.gitkeep b/templates/common/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/common/README.md b/templates/common/README.md new file mode 100644 index 0000000..13648e8 --- /dev/null +++ b/templates/common/README.md @@ -0,0 +1,5 @@ +## {{ circuitName }} Circuit + +This circuit project contains all the necessary ingredients to upload to Sindri's API and start requesting proofs. + +The circuit language is written in {{ circuitType }} and will produce proofs that two variables x and y (public) are equal. diff --git a/templates/gnark/example-input.json b/templates/gnark/example-input.json new file mode 100644 index 0000000..b6911b9 --- /dev/null +++ b/templates/gnark/example-input.json @@ -0,0 +1,4 @@ +{ + "X": "1", + "Y": "1" +} \ No newline at end of file diff --git a/templates/gnark/go.mod b/templates/gnark/go.mod new file mode 100644 index 0000000..d8c9f06 --- /dev/null +++ b/templates/gnark/go.mod @@ -0,0 +1,25 @@ +module templatePackageName + +go 1.19 + +require ( + github.com/consensys/gnark v0.9.0 + github.com/consensys/gnark-crypto v0.11.2 +) + +require ( + github.com/bits-and-blooms/bitset v1.8.0 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/zerolog v1.30.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/sys v0.11.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) diff --git a/templates/gnark/go.sum b/templates/gnark/go.sum new file mode 100644 index 0000000..20c37e5 --- /dev/null +++ b/templates/gnark/go.sum @@ -0,0 +1,54 @@ +github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= +github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark v0.9.0 h1:OoOr0Q771mQINVdP3s1AF2Rs1y8gtXhWVkadz/9KmZc= +github.com/consensys/gnark v0.9.0/go.mod h1:Sy9jJjIaGJFfNeupyNOR9Ei2IbAB6cfCO78DfG27YvM= +github.com/consensys/gnark-crypto v0.11.2 h1:GJjjtWJ+db1xGao7vTsOgAOGgjfPe7eRGPL+xxMX0qE= +github.com/consensys/gnark-crypto v0.11.2/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= +github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/templates/gnark/sindri.json b/templates/gnark/sindri.json new file mode 100644 index 0000000..cae4ba5 --- /dev/null +++ b/templates/gnark/sindri.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://forge.sindri.app/api/v1/sindri-manifest-schema.json", + "name": "{{ circuitName }}", + "circuitStructName": "Circuit", + "circuitType": "gnark", + "curve": "{{ curveName }}", + "gnarkVersion": "v0.9.0", + "packageName": "{{ packageName }}", + "provingScheme": "{{ provingScheme }}" +} diff --git a/templates/gnark/templatePackageName.go b/templates/gnark/templatePackageName.go new file mode 100644 index 0000000..db2768e --- /dev/null +++ b/templates/gnark/templatePackageName.go @@ -0,0 +1,70 @@ +package templatePackageName + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/frontend" +) + +type Circuit struct { + // Your circuit inputs go here. + X frontend.Variable + Y frontend.Variable `gnark:",public"` +} + +func (circuit *Circuit) Define(api frontend.API) error { + // Your circuit logic goes here. + api.AssertIsEqual(circuit.X, circuit.Y) + return nil +} + +// Common utility for reading JSON in from a file. +func ReadFromInputPath(pathInput string) (map[string]interface{}, error) { + + absPath, err := filepath.Abs(pathInput) + if err != nil { + fmt.Println("Error constructing absolute path:", err) + return nil, err + } + + file, err := os.Open(absPath) + if err != nil { + panic(err) + } + defer file.Close() + + var data map[string]interface{} + err = json.NewDecoder(file).Decode(&data) + if err != nil { + panic(err) + } + + return data, nil +} + +// Construct a witness from input data in a JSON file. +func FromJson(pathInput string) witness.Witness { + + data, err := ReadFromInputPath(pathInput) + if err != nil { + panic(err) + } + + // Your witness construction logic goes here. + X := frontend.Variable(data["X"]) + Y := frontend.Variable(data["Y"]) + assignment := Circuit{ + X: X, + Y: Y, + } + w, err := frontend.NewWitness(&assignment, ecc.templateGnarkCurveName.ScalarField()) + if err != nil { + panic(err) + } + return w +} diff --git a/yarn.lock b/yarn.lock index 48a2414..2f952fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -468,6 +468,11 @@ dependencies: undici-types "~5.26.4" +"@types/nunjucks@^3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@types/nunjucks/-/nunjucks-3.2.6.tgz#6d6e0363719545df8b9a024279902edf68b2caa9" + integrity sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w== + "@types/semver@^7.5.0": version "7.5.5" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.5.tgz#deed5ab7019756c9c90ea86139106b0346223f35" @@ -576,6 +581,11 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +a-sync-waterfall@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz#75b6b6aa72598b497a125e7a2770f14f4c8a1fa7" + integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -645,6 +655,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +asap@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -832,6 +847,11 @@ commander@^4.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1755,6 +1775,15 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +nunjucks@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/nunjucks/-/nunjucks-3.2.4.tgz#f0878eef528ce7b0aa35d67cc6898635fd74649e" + integrity sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ== + dependencies: + a-sync-waterfall "^1.0.0" + asap "^2.0.3" + commander "^5.1.0" + object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"