UNPKG

sindri

Version:

The Sindri Labs JavaScript SDK and CLI tool.

196 lines (182 loc) 6.61 kB
import fs from "fs"; import path from "path"; import process from "process"; import { Command } from "@commander-js/extra-typings"; import { collectMetaWithLogger, findFileUpwards } from "cli/utils"; import sindri from "lib"; import { ApiError } from "lib/api"; import { print } from "lib/logging"; import { getDefaultMeta } from "lib/utils"; const readStdin = async (): Promise<string> => { let inputData = ""; return new Promise((resolve) => { process.stdin.on("data", (chunk) => (inputData += chunk)); process.stdin.on("end", () => resolve(inputData)); }); }; const proofCreateCommand = new Command() .name("create") .description("Create a proof for the circuit.") .option( "-i, --input <input>", "Input file for the proof (defaults to stdin if non-TTY; " + "`input.json`, `example-input.json`, or `Prover.toml` otherwise).", ) .option( "-m, --meta <key=value>", "Metadata key/value to attach to the proof.", collectMetaWithLogger(sindri.logger), getDefaultMeta({ logger: sindri.logger, raiseExceptions: false }), ) .option("-t, --tag <tag>", "Tag to generate the proof from.", "latest") .option( "-v, --verify", "Perform verification of the proof after creating it.", ) .action(async ({ input, meta, tag, verify }) => { // Check that the API client is authorized. if (!sindri.apiKey || !sindri.baseUrl) { sindri.logger.warn("You must login first with `sindri login`."); return process.exit(1); } // Find `sindri.json` and move into the root of the project directory. const currentDirectoryPath = path.resolve("."); if (!fs.existsSync(currentDirectoryPath)) { sindri.logger.error( `The "${currentDirectoryPath}" directory does not exist. Aborting.`, ); return process.exit(1); } const sindriJsonPath = findFileUpwards( /^sindri.json$/i, currentDirectoryPath, ); if (!sindriJsonPath) { sindri.logger.error( `No "sindri.json" file was found in or above "${currentDirectoryPath}". Aborting.`, ); return process.exit(1); } sindri.logger.debug(`Found "sindri.json" at "${sindriJsonPath}".`); const rootDirectory = path.dirname(sindriJsonPath); sindri.logger.debug(`Changing current directory to "${rootDirectory}".`); process.chdir(rootDirectory); // Load `sindri.json` and find the circuit name. let sindriJson: object = {}; try { const sindriJsonContent = fs.readFileSync(sindriJsonPath, { encoding: "utf-8", }); sindriJson = JSON.parse(sindriJsonContent); sindri.logger.debug( `Successfully loaded "sindri.json" from "${sindriJsonPath}":`, ); sindri.logger.debug(sindriJson); } catch (error) { sindri.logger.fatal( `Error loading "${sindriJsonPath}", perhaps it is not valid JSON?`, ); sindri.logger.error(error); return process.exit(1); } if (!("name" in sindriJson)) { sindri.logger.error('No "name" field found in "sindri.json". Aborting.'); return process.exit(1); } const circuitName = sindriJson.name; // Reed in the proof input. let proofInput: string | undefined; if (input && fs.existsSync(input)) { // Read from the specified input file. proofInput = fs.readFileSync(input, "utf-8"); } else if (!process.stdin.isTTY || input === "-") { // Read from stdin in a non-TTY context. proofInput = await readStdin(); } if (!proofInput || !proofInput.trim()) { // Try to load from common input filenames. const defaultInputFiles = [ "input.json", "example-input.json", "Prover.toml", ]; for (const file of defaultInputFiles) { const qualifiedFile = path.join(rootDirectory, file); if (fs.existsSync(file)) { proofInput = fs.readFileSync(qualifiedFile, "utf-8"); break; } } if (!proofInput) { console.error( "No input file specified, none of the default files found, and not in a non-TTY context.", ); process.exit(1); } } // Only Circom supports smart contract calldata right now, so we only enable it for that circuit // type. We'll need to update this as we add support for more circuit types. const includeSmartContractCalldata = "circuitType" in sindriJson && typeof sindriJson.circuitType === "string" && ["circom"].includes(sindriJson.circuitType); const circuitIdentifier = `${circuitName}:${tag}`; try { // Poll for proof generation to complete. const startTime = Date.now(); const response = await sindri.proveCircuit( circuitIdentifier, proofInput, !!verify, includeSmartContractCalldata, meta, ); const elapsedSeconds = ((Date.now() - startTime) / 1000).toFixed(1); // Check that the status is "Ready" or log an error. if (response.status === "Ready") { sindri.logger.info( `Proof generated successfully after ${elapsedSeconds} seconds.`, ); } else if (response.status === "Failed") { sindri.logger.error( `Proof generation failed after ${elapsedSeconds} seconds: ` + (response.error ?? "Unknown error."), ); return process.exit(1); } else { sindri.logger.fatal(`Unexpected response status: ${response.status}`); return process.exit(1); } // Print out the formatted proof response. print( JSON.stringify( { proofId: response.proof_id, meta: response.meta, proof: response.proof, public: response.public, // TODO: We need to figure out if this is the format we want to expose. // smart_contract_calldata: response.smart_contract_calldata, verification_key: response.verification_key, }, null, 2, ), ); } catch (error) { // TODO: Better error handling. if (error instanceof ApiError && error.status === 404) { sindri.logger.error( `No circuit found with the name "${circuitName}" and tag "${tag}".`, ); } else { sindri.logger.fatal("An unknown error occurred."); sindri.logger.error(error); } return process.exit(1); } }); export const proofCommand = new Command() .name("proof") .description("Commands related to proofs for the current circuit.") .addCommand(proofCreateCommand);