@nori-zk/proof-conversion
v0.8.17
Published
Verifying zkVM proofs inside o1js circuits, to generate Mina compatible proof
Readme
Proof-Conversion
Description
This repository enables verification of PLONK and Groth16 proofs generated by SP1, RISC Zero zkVMs and Snarkjs inside o1js circuits, making them compatible with zkApps built on the the Mina Protocol. The codebase employs a parallel and recursive architecture to efficiently verify non-native proofs in o1js.
The infrastructure provides format-specific conversion pipelines for each supported proof system. SP1 proofs (both PLONK and Groth16) use gnark's compressed serialization format internally, which the conversion process decompresses and transforms. All supported systems (SP1, RISC Zero, snarkjs) generate BN254 curve-based proofs, enabling a common cryptographic foundation. While each proof system requires dedicated handling due to format differences, the modular architecture separates proof decompression, verification key processing, and o1js compatible serialization into reusable components.
The repository handles Groth16 proofs from multiple sources: SP1's gnark-based implementation, RISC Zero's format, and snarkjs proofs (commonly used with circom circuits). Each format undergoes conversion to normalize curve point representations and compute the necessary pairing precomputations, producing proofs verifiable in o1js circuits on the Mina Protocol.
CLI tool
Installation
npm install -g @nori-zk/proof-conversionQuick start
List available commands:
nori-proof-converterGet detailed help for a specific command:
nori-proof-converter describe <command>
# OR run the command without args:
nori-proof-converter <command>Convert a proof (object mode - single JSON file):
nori-proof-converter sp1Plonk ./example-proofs/sp1_plonk_obj_v5.jsonConvert a proof (args mode - multiple JSON files):
nori-proof-converter snarkjsGroth16 ./example-proofs/snarkjs_groth16_args_proof.json ./example-proofs/snarkjs_groth16_args_vk.json ./example-proofs/snarkjs_groth16_args_public_inputs.jsonInput modes
The CLI supports two input modes depending on the command:
1. Object mode (always supported)
- Provide a single JSON file containing all required fields
- The file structure is validated against the command's schema
nori-proof-converter sp1Plonk path/to/sp1_proof.json2. Args mode (command-dependent)
- Provide multiple JSON files as separate arguments in a specific order
- Each file is validated against its corresponding schema
- Run
describe <command>to see if a command supports args mode and the required file order
nori-proof-converter snarkjsGroth16 path/to/proof.json path/to/vk.json path/to/public_inputs.jsonAvailable conversion commands
sp1Plonk
Convert SP1 PLONK proofs into verifiable o1js proofs for the Mina Protocol.
- Args mode: Not supported
- Object mode: Supported
Object mode example:
nori-proof-converter sp1Plonk ./example-proofs/sp1_plonk_obj_v5.jsonsp1Groth16
Convert SP1 Groth16 proofs into verifiable o1js proofs for the Mina Protocol.
- Args mode: Not supported
- Object mode: Supported
Object mode example:
nori-proof-converter sp1Groth16 ./example-proofs/sp1_groth16_obj_v5.jsonrisc0Groth16
Convert RISC Zero Groth16 proofs into verifiable o1js proofs for the Mina Protocol.
- Args mode: Supported (files:
proof.json,vk.json) - Object mode: Supported
Args mode example:
nori-proof-converter risc0Groth16 ./example-proofs/risc_zero_groth16_args_proof.json ./example-proofs/risc_zero_groth16_args_vk.jsonObject mode example:
nori-proof-converter risc0Groth16 ./example-proofs/risc_zero_groth16_obj.jsonsnarkjsGroth16
Convert snarkjs/circom Groth16 proofs into verifiable o1js proofs for the Mina Protocol.
- Args mode: Supported (files:
proof.json,vk.json,public_inputs.json) - Object mode: Supported
Args mode example:
nori-proof-converter snarkjsGroth16 ./example-proofs/snarkjs_groth16_args_proof.json ./example-proofs/snarkjs_groth16_args_vk.json ./example-proofs/snarkjs_groth16_args_public_inputs.jsonObject mode example:
nori-proof-converter snarkjsGroth16 ./example-proofs/snarkjs_groth16_obj.jsonThe describe command
Get detailed information about any command including schemas, supported modes, and examples. You can use the describe command or run the command without arguments:
nori-proof-converter describe snarkjsGroth16
# OR
nori-proof-converter snarkjsGroth16Example output:
=== snarkjsGroth16 ===
Object-mode schema:
Provide one JSON file path arguments
Expected schemas for the file:
{
"proof": {
"protocol": "groth16",
"curve": "bn128",
"pi_a": "ProjectivePoint",
"pi_b": "ComplexProjectivePoint",
"pi_c": "ProjectivePoint"
},
"vk": {
"protocol": "groth16",
"curve": "bn128",
"nPublic": "BoundedNumberUnion(0..6)",
"vk_alpha_1": "ProjectivePoint",
"vk_beta_2": "ComplexProjectivePoint",
"vk_gamma_2": "ComplexProjectivePoint",
"vk_delta_2": "ComplexProjectivePoint",
"vk_alphabeta_12": "ArrayOfLength[2]<ComplexProjectivePoint>",
"IC": "ArrayOfBoundedLength[0..7]<ProjectivePoint>"
},
"publicInputs": "ArrayOfBoundedLength[0..6]<String>"
}
Object-mode usage:
$ nori-proof-converter snarkjsGroth16 path/to/snarkjs_groth16_input.json
Args-mode (file-per-key) schemas:
Provide '3' JSON file path arguments, in order: proof, vk, publicInputs
Expected schemas for each file:
proof.json:
{
"protocol": "groth16",
"curve": "bn128",
"pi_a": "ProjectivePoint",
"pi_b": "ComplexProjectivePoint",
"pi_c": "ProjectivePoint"
}
vk.json:
{
"protocol": "groth16",
"curve": "bn128",
"nPublic": "BoundedNumberUnion(0..6)",
"vk_alpha_1": "ProjectivePoint",
"vk_beta_2": "ComplexProjectivePoint",
"vk_gamma_2": "ComplexProjectivePoint",
"vk_delta_2": "ComplexProjectivePoint",
"vk_alphabeta_12": "ArrayOfLength[2]<ComplexProjectivePoint>",
"IC": "ArrayOfBoundedLength[0..7]<ProjectivePoint>"
}
publicInputs.json:
"ArrayOfBoundedLength[0..6]<String>"
Args-mode usage:
$ nori-proof-converter snarkjsGroth16 path/to/proof.json path/to/vk.json path/to/public_inputs.jsonAutomatic schema validation
The CLI validates all inputs against type-safe schemas before conversion:
$ nori-proof-converter snarkjsGroth16 path/to/invalid_proof.json path/to/invalid_vk.json path/to/invalid_public_inputs.json
Args-mode validation failed for 'snarkjsGroth16'.
Provide '3' JSON file path arguments, in order: proof, vk, publicInputs
Expected schemas for each file:
path/to/invalid_proof.json (proof.json):
{
"protocol": "groth16",
"curve": "bn128",
"pi_a": "ProjectivePoint",
"pi_b": "ComplexProjectivePoint",
"pi_c": "ProjectivePoint"
}
path/to/invalid_vk.json (vk.json):
{
"protocol": "groth16",
"curve": "bn128",
"nPublic": "BoundedNumberUnion(0..6)",
"vk_alpha_1": "ProjectivePoint",
"vk_beta_2": "ComplexProjectivePoint",
"vk_gamma_2": "ComplexProjectivePoint",
"vk_delta_2": "ComplexProjectivePoint",
"vk_alphabeta_12": "ArrayOfLength[2]<ComplexProjectivePoint>",
"IC": "ArrayOfBoundedLength[0..7]<ProjectivePoint>"
}
path/to/invalid_public_inputs.json (publicInputs.json):
"ArrayOfBoundedLength[0..6]<String>"
Validation errors:
path/to/invalid_proof.json (proof.json):
proof["protocol"]: must be exactly "groth16", got "groth6"
proof["pi_b"]: expected ComplexProjectivePoint, got [[String, String], [String, String], String, [String, String]]
proof["extra_key"]: unexpected extra key
path/to/invalid_vk.json (vk.json):
vk["nPublic"]: expected BoundedNumberUnion(0..6), got 10 which exceeds maximum 6
vk["vk_gamma_2"]: expected ComplexProjectivePoint, got [[String, String], [String, String], [String, Number]]
vk["vk_delta_2"]: expected ComplexProjectivePoint, got [[String, String], [String, String], [String, String], [String, String]]
vk["vk_alphabeta_12"] should have type ArrayOfLength[2]<ComplexProjectivePoint>
vk["vk_alphabeta_12"][0]: expected ComplexProjectivePoint, got [[String, String], [String, String], [String, String], Number]
vk["IC"]: expected ArrayOfBoundedLength[0..7]<ProjectivePoint>, got array of length 9, exceeding maximum 7
path/to/invalid_public_inputs.json (publicInputs.json):
publicInputs: expected ArrayOfBoundedLength[0..6]<String>, got object
Due to the validation issues running 'snarkjsGroth16' in 'args' mode cannot continueParallel processing
You can change the number of child processes it spawns by setting the MAX_PROCESSES environment variable:
export MAX_PROCESSES=16
nori-proof-converter sp1Plonk path/to/proof.jsonDefault is 1. Beyond MAX_PROCESSES=32 no performance gains can be expected.
Output file naming convention
Converted proofs are automatically saved with the command name appended before the extension:
$ nori-proof-converter sp1Plonk path/to/my_proof.json
# Creates: path/to/my_proof.sp1Plonk.jsonTroubleshooting
Updating the CLI
Local reinstallation:
npm run relinkGlobal reinstallation:
npm uninstall -g @nori-zk/proof-conversion
npm install -g @nori-zk/proof-conversionIf getting a permission denied error, check npm's awareness of linked modules npm ls -g --depth=0 --link=true, remove symlinks manually if necessary, and run npm run relink.
Typescript API
This is a TypeScript-first API that incorporates Rust components via WebAssembly for performance-critical cryptographic operations.
Installation
npm ci @nori-zk/proof-conversion --saveEnsure you have o1js as a peer dependency. Currently supported version 2.12.0.
Available conversion "plans"
import { performSp1Plonk, performRisc0Groth16, performSnarkjsGroth16, performSp1Groth16 } from '@nori-zk/proof-conversion';Usage example in and ESM Node.js project
import {
ComputationalPlanExecutor,
performSp1Plonk,
type SP1ProofWithPublicValuesPlonkNoTee,
} from '@nori-zk/proof-conversion';
import { assertExactStructure } from '@nori-zk/proof-conversion/validation';
import { readFileSync } from 'fs';
// Optionally include this package, to see internal progress of the executor during proof conversion
import { LogPrinter } from 'esm-iso-logger';
async function main() {
new LogPrinter('NoriProofConverter');
const maxProcesses = 10;
const executor = new ComputationalPlanExecutor(maxProcesses);
const sp1ProofStr = readFileSync('./example-proofs/v5.json', 'utf8');
const sp1Proof: unknown = JSON.parse(sp1ProofStr);
// If sp1Proof is invalid ValidationError('SP1ProofWithPublicValuesPlonkNoTee validation failed: <specificInformation>') will be thrown!
assertExactStructure(
sp1Proof,
performSp1Plonk.schema,
'SP1ProofWithPublicValuesPlonkNoTee'
);
// sp1Proof is now fully typed!
const validatedSP1Proof: SP1ProofWithPublicValuesPlonkNoTee = sp1Proof;
const result = await performSp1Plonk(executor, validatedSP1Proof);
console.log('Finished conversion', result);
}
main().catch(console.error);Converted proof types
ProofDataOutput
Proof data in o1js format.
Contains a serialized o1js proof along with its public inputs and outputs, plus metadata about recursive proof composition.
maxProofsVerified: Number of proofs this circuit verifies recursively. This is a structural property of the proof's circuit, not a verification mode:0: Circuit does not verify any other proofs (base case/leaf proof)1: Circuit verifies exactly 1 other proof inside its execution2: Circuit verifies exactly 2 other proofs inside its execution (binary tree recursion)
proof: Base64-encoded o1js proof in Pickles format. This is the native proof representation used by o1js, containing all the cryptographic witness data and commitments required for verification.publicInput: Array of public input field elements as decimal strings. Each string represents a field element in the Pallas base field (255-bit modulus).publicOutput: Array of public output field elements as decimal strings. Each string represents a field element in the Pallas base field (255-bit modulus).
interface ProofDataOutput {
publicInput: string[];
publicOutput: string[];
maxProofsVerified: 0 | 1 | 2;
proof: string;
}VkDataOutput
Verification key data in o1js format.
Contains a serialized o1js verification key with its cryptographic hash.
data: Base64-encoded o1js verification key in Pickles format. Contains all the cryptographic parameters needed to verify proofs: constraint system commitments, setup parameters, and domain configuration.hash: Cryptographic hash of the verification key for integrity checking and identification.
interface VkDataOutput {
data: string;
hash: string;
}ConversionOutput
Complete conversion output in o1js format.
Bundles together the verification key and proof data for o1js verification. This is the return type of all proof conversion functions (performSp1Plonk, performRisc0Groth16, performSnarkjsGroth16, performSp1Groth16).
vkData: Verification key in o1js format (seeVkDataOutput)proofData: Proof in o1js format with public I/O (seeProofDataOutput)
interface ConversionOutput {
vkData: VkDataOutput;
proofData: ProofDataOutput;
}Minimal export
The package exports via the /min path, useful types and some limited utilities omitting anything which relies on a Node.js dependancy. This is largely to support frontend applications. Note none of the proof conversion "plans" e.g. performSp1Plonk, the ComputationalPlanExecutor nor the wasm based utilities are exported.
import {
parsePlonkPublicInputsProvable,
wordToBytes,
NodeProofLeft,
FrC,
DeferredPromise
} from '@nori-zk/proof-conversion/min'; // These utilities are also available in '@nori-zk/proof-conversion'
import type {
ConversionOutput,
Risc0Groth16Vk,
Risc0Groth16PairedVk,
Risc0Groth16Proof,
Risc0Groth16Input,
SnarkjsGroth16Proof,
SnarkjsGroth16VK,
SnarkjsGroth16Input,
SP1ProofWithPublicValues,
SP1ProofWithPublicValuesGroth16NoTee,
SP1ProofWithPublicValuesPlonkNoTee,
} from '@nori-zk/proof-conversion/min'; // These types are also available in '@nori-zk/proof-conversion'Wasm utilities
The package exports via the /wasm-utils path, useful wasm based utilities are included to assist proof conversions from one format to another - omitting anything which relies on a Node.js dependancy.
import {
computeAuxWitness,
convertSp1Groth16ToO1js,
convertSnarkjsGroth16ToO1js,
computeRisc0Groth16Pairing,
} from '@nori-zk/proof-conversion/wasm-utils'; // These types are also available in '@nori-zk/proof-conversion'Validation utilities
The package exports runtime validation utilities via the /validation path. These are platform-agnostic (work in both Node.js and browser) and provide type-safe schema validation with detailed error messages.
import {
assertExactStructure,
ValidationError,
isString,
isNumber,
isArray,
isStringArray,
isAffinePoint2d,
isComplexAffinePoint2d,
// ... many more validators
} from '@nori-zk/proof-conversion/validation';Key exports:
assertExactStructure(obj, schema, context)- Validates an object against a schema definition and throwsValidationErrorwith detailed diagnostics on failure. Uses TypeScript assertion signatures to narrow types on success.ValidationError- Error class thrown when validation fails, containing path-aware error messages.Type guard validators:
- Primitives:
isString,isNumber,isBoolean,isNull,isUndefined - Arrays:
isArray(validator),isStringArray,isNumberArray,isArrayOfLength(n),isArrayOfBoundedLength(min, max) - Crypto types:
isAffinePoint2d,isComplexAffinePoint2d,isProjectivePoint,isComplexProjectivePoint- See @nori-zk/proof-conversion-utils for detailed type documentation (curve points, field extensions, pairings, proof structures, etc.)
- Primitives:
Example usage with real proof schemas:
import {
assertExactStructure,
isString,
isProjectivePoint,
isComplexProjectivePoint,
isArrayOfBoundedLength,
isBoundedNumberUnion,
} from '@nori-zk/proof-conversion/validation';
// Define schema for snarkjs Groth16 verification key
const snarkjsGroth16VKSchema = {
protocol: 'groth16' as const, // Literal value - must be exactly "groth16"
curve: 'bn128' as const, // Literal value - must be exactly "bn128"
nPublic: isBoundedNumberUnion({ min: 0, max: 6 }), // Number between 0-6
vk_alpha_1: isProjectivePoint, // [x, y, z] array of field element strings
vk_beta_2: isComplexProjectivePoint, // [[x0,x1], [y0,y1], [z0,z1]] nested arrays
vk_gamma_2: isComplexProjectivePoint,
vk_delta_2: isComplexProjectivePoint,
IC: isArrayOfBoundedLength(isProjectivePoint, { minLength: 0, maxLength: 7 }),
};
// Validate untrusted input
const untrustedVK: unknown = JSON.parse(vkJsonString);
// This throws ValidationError with detailed path-aware messages if invalid
assertExactStructure(untrustedVK, snarkjsGroth16VKSchema, "Snarkjs Groth16 VK");
// After validation, TypeScript knows the exact type
// untrustedVK is now: { protocol: "groth16", curve: "bn128", nPublic: number, ... }Integration with API methods:
All proof conversion functions (performSp1Plonk, performRisc0Groth16, etc.) are decorated with the @ApiMethod decorator, which:
- Attaches the validation schema to the function (accessible via
.schemaproperty) - Enables runtime validation as shown in the usage example above:
performSp1Plonk.schema - Provides CLI support by converting positional arguments to typed objects
- Ensures type safety across the entire conversion pipeline
See src/api/*/schema.ts files for the actual schemas used by each conversion method.
Full package (Node.js only)
The main package export @nori-zk/proof-conversion includes everything from /min plus Node.js-specific functionality for running proof conversion plans. These plans spawn child processes to perform parallel proof conversion operations.
Additional Node.js-only exports:
import {
performSp1Plonk,
performRisc0Groth16,
performSnarkjsGroth16,
performSp1Groth16,
ComputationalPlanExecutor,
} from '@nori-zk/proof-conversion';performSp1Plonk- Convert SP1 PLONK proofs into verifiable o1js proofs for the Mina ProtocolperformSp1Groth16- Convert SP1 Groth16 proofs into verifiable o1js proofs for the Mina ProtocolperformRisc0Groth16- Convert RISC Zero Groth16 proofs into verifiable o1js proofs for the Mina ProtocolperformSnarkjsGroth16- Convert snarkjs Groth16 proofs into verifiable o1js proofs for the Mina ProtocolComputationalPlanExecutor- Executor for running conversion plans with parallel processing
These require Node.js because they spawn child processes and perform filesystem operations for intermediate proof data.
Numa
sudo apt install numactlInstalling numactl if your system supports it is highly recommended. Note without it, it is expected that you will see crashes on x86/x86_64 environments. If it is installed, this library will automatically use it.
Optional Kernel Tuning
sudo sysctl -w vm.zone_reclaim_mode=0
sudo sysctl -w vm.overcommit_memory=1
sudo sysctl -w vm.swappiness=10
sudo cpupower frequency-set -g performanceOverview of o1js-blobstream by Geometry Research
Refer to the Gitbook documentation for details on o1js-blobstream.
Low level tinkering
If you wish to do additional research with this library, bash scripts exist which allow you interact with the lower level programs
of the library. When doing this ensure you setup numactl and in addition you will need parallel.
Install parallel
sudo apt install parallelDepending on the CPU model, specificaly NUMA nodes setup, you may need to adjust values in
scrips/plonk_tree.sh
License
This project is licensed under either:
at your option.
The SPDX license identifier for this project is:MIT OR Apache-2.0.
