npm package: @nori-zk/proof-conversion
Description: Verifying zkVM proofs inside o1js circuits, to generate Mina compatible proof
Latest version: 0.8.17

# 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

```bash
npm install -g @nori-zk/proof-conversion
```

### Quick start

**List available commands:**
```bash
nori-proof-converter
```

**Get detailed help for a specific command:**
```bash
nori-proof-converter describe <command>
# OR run the command without args:
nori-proof-converter <command>
```

**Convert a proof (object mode - single JSON file):**
```bash
nori-proof-converter sp1Plonk ./example-proofs/sp1_plonk_obj_v5.json
```

**Convert a proof (args mode - multiple JSON files):**
```bash
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.json
```

### Input 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
```bash
nori-proof-converter sp1Plonk path/to/sp1_proof.json
```

**2. 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
```bash
nori-proof-converter snarkjsGroth16 path/to/proof.json path/to/vk.json path/to/public_inputs.json
```

### Available `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:**
```bash
nori-proof-converter sp1Plonk ./example-proofs/sp1_plonk_obj_v5.json
```

#### sp1Groth16

Convert SP1 Groth16 proofs into verifiable o1js proofs for the **Mina Protocol**.

- **Args mode:** Not supported
- **Object mode:** Supported

**Object mode example:**
```bash
nori-proof-converter sp1Groth16 ./example-proofs/sp1_groth16_obj_v5.json
```

#### risc0Groth16

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:**
```bash
nori-proof-converter risc0Groth16 ./example-proofs/risc_zero_groth16_args_proof.json ./example-proofs/risc_zero_groth16_args_vk.json
```

**Object mode example:**
```bash
nori-proof-converter risc0Groth16 ./example-proofs/risc_zero_groth16_obj.json
```

#### snarkjsGroth16

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:**
```bash
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.json
```

**Object mode example:**
```bash
nori-proof-converter snarkjsGroth16 ./example-proofs/snarkjs_groth16_obj.json
```

### The `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:

```bash
nori-proof-converter describe snarkjsGroth16
# OR
nori-proof-converter snarkjsGroth16
```

**Example 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.json
```

### Automatic schema validation

The CLI validates all inputs against type-safe schemas before conversion:
```bash
$ 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 continue
```

### Parallel processing

You can change the number of child processes it spawns by setting the `MAX_PROCESSES` environment variable:
```bash
export MAX_PROCESSES=16
nori-proof-converter sp1Plonk path/to/proof.json
```

Default 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:
```bash
$ nori-proof-converter sp1Plonk path/to/my_proof.json
# Creates: path/to/my_proof.sp1Plonk.json
```

### Troubleshooting

#### Updating the CLI

**Local reinstallation**:
```bash
npm run relink
```

**Global reinstallation**:
```bash
npm uninstall -g @nori-zk/proof-conversion
npm install -g @nori-zk/proof-conversion
```

If 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

```bash
npm ci @nori-zk/proof-conversion --save
```

**Ensure you have o1js as a peer dependency.** Currently supported version **2.12.0**.

### Available conversion "plans"

```typescript
import { performSp1Plonk, performRisc0Groth16, performSnarkjsGroth16, performSp1Groth16 } from '@nori-zk/proof-conversion';
```

### Usage example in and ESM Node.js project

```typescript
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 execution
  - `2`: 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).

```typescript
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.

```typescript
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 (see `VkDataOutput`)
- `proofData`: Proof in o1js format with public I/O (see `ProofDataOutput`)

```typescript
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.

```typescript
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. 

```typescript
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.

```typescript
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 throws `ValidationError` with 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](https://www.npmjs.com/package/@nori-zk/proof-conversion-utils) for detailed type documentation (curve points, field extensions, pairings, proof structures, etc.)

**Example usage with real proof schemas:**

```typescript
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 `.schema` property)
- Enables runtime validation as shown in the [usage example](#usage-example-in-and-esm-nodejs-project) 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:**

```typescript
import {
  performSp1Plonk,
  performRisc0Groth16,
  performSnarkjsGroth16,
  performSp1Groth16,
  ComputationalPlanExecutor,
} from '@nori-zk/proof-conversion';
```

- `performSp1Plonk` - Convert SP1 PLONK proofs into verifiable o1js proofs for the **Mina Protocol**
- `performSp1Groth16` - Convert SP1 Groth16 proofs into verifiable o1js proofs for the **Mina Protocol**
- `performRisc0Groth16` - Convert RISC Zero Groth16 proofs into verifiable o1js proofs for the **Mina Protocol**
- `performSnarkjsGroth16` - Convert snarkjs Groth16 proofs into verifiable o1js proofs for the **Mina Protocol**
- `ComputationalPlanExecutor` - 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

```sh
sudo apt install numactl
```

Installing `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 performance
```

--------------------------------------------------------------------------------------------

## Overview of o1js-blobstream by Geometry Research

Refer to the **[Gitbook documentation](https://o1js-blobstream.gitbook.io/o1js-blobstream)** 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`

```sh
sudo apt install parallel
```

Depending 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:

- **[Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)** ([`LICENSE-APACHE`](LICENSE-APACHE))
- **[MIT License](https://opensource.org/licenses/MIT)** ([`LICENSE-MIT`](LICENSE-MIT))

at your option.

The **[SPDX](https://spdx.dev)** license identifier for this project is:  
`MIT OR Apache-2.0`.