Skip to content

Commit

Permalink
feat: added converter
Browse files Browse the repository at this point in the history
  • Loading branch information
NikitaMasych committed Jan 27, 2025
1 parent 66cd35b commit 27b8107
Show file tree
Hide file tree
Showing 12 changed files with 582 additions and 79 deletions.
314 changes: 252 additions & 62 deletions Cargo.lock

Large diffs are not rendered by default.

20 changes: 18 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,31 @@ resolver = "2"

[dependencies]
plonky2 = { git = "https://github.com/distributed-lab/plonky2", branch = "stable", default-features = false}

snafu = { version = "0.8.5", default-features = false }
serde = { version = "1.0.217", default-features = false, features = ["derive"] }
serde_with = { version = "3.12.0", default-features = false, features = ["macros", "hex"] }

anyhow = { version = "1.0.95", default-features = false, optional = true }
serde_json = { version = "1.0.137", default-features = false, optional = true, features = ["std"] }
bincode = { version = "1.3.3", default-features = false, optional = true}
clap = { version = "4.5.27", default-features = false, optional = true, features = ["std", "derive", "help"] }
log = { version = "0.4.25", default-features = false, optional = true}
env_logger = { version = "0.11.6", default-features = false, optional = true }
hex = { version = "0.4.3", default-features = false, optional = true, features = ["std"] }

[dev-dependencies]
rstest = "0.23.0"
rstest = "0.24.0"
serde_json = "1.0.137"

[[bin]]
name = "plonky2-converter"
path = "src/bin/converter.rs"
required-features = ["converter"]

[features]
default = ["std"]
std = [
"plonky2/std",
"snafu/std",
]
converter = ["anyhow", "serde_json", "bincode", "clap", "log", "env_logger", "hex"]
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,58 @@
This rust crate provides functionality to deserialize and verify proof, public inputs and verification key.

## plonky2-converter
`Plonky2` has a certain number of generics for its constraint system, such as used field, hasher etc.
Since we are limited by the nature of passing them in `zkVerify`, we use a custom format of [Vk](./src/vk.rs).

For that reason, this crate also provides a binary to convert verification keys into acceptable by `zkVerify` format.

### Install:

```bash
cargo install --features converter --path .
```

### Usage:
```bash
plonky2-converter vk --help
Serialize VerifierCircuitData into zkVerify format

Usage:

Arguments:
<INPUT>

[OUTPUT]

Options:
-i, --in-fmt <IN_FMT>
[default: bytes]

Possible values:
- bytes: Raw binary bytes
- hex: Hex-encoded string (with optional "0x" prefix)

-o, --out-fmt <OUT_FMT>
[default: json]

Possible values:
- json: JSON format (pretty-printed)
- bytes: Raw binary bytes
- hex: Hex-encoded string

-c, --config <CONFIG>
[default: poseidon]

Possible values:
- keccak: Preset Keccak over Goldilocks config available in `plonky2`
- poseidon: Preset Poseidon over Goldilocks config available in `plonky2`

-h, --help
Print help (see a summary with '-h')

```

## License

This code is released under the GPL 3.0 license.
76 changes: 76 additions & 0 deletions src/bin/converter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
mod formats;

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use plonky2_verifier::Plonky2Config;
use std::fs::File;
use std::io::{self, Write};
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[command(
name = "plonky2-converter",
about = "Converts plonky2 formats",
version
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
/// Serialize VerifierCircuitData into zkVerify format.
Vk(VkArgs),
}

#[derive(Debug, Parser)]
struct VkArgs {
#[arg(short, long, value_enum, default_value_t = formats::VerifierCircuitDataFormat::default())]
in_fmt: formats::VerifierCircuitDataFormat,

#[arg(short, long, value_enum, default_value_t = formats::Format::default())]
out_fmt: formats::Format,

input: PathBuf,
output: Option<PathBuf>,

#[arg(short, long, value_enum, default_value_t = Plonky2Config::default())]
config: Plonky2Config,
}

fn main() -> Result<()> {
env_logger::init();
let cli = Cli::parse();
match cli.command {
Commands::Vk(args) => handle_vk(args),
}
}

fn handle_vk(args: VkArgs) -> Result<()> {
log::info!("Processing VK command with args: {:?}", args);

let vk_bytes = std::fs::read(&args.input).with_context(|| {
format!(
"Could not read file {:?}. Ensure the file exists and is accessible.",
&args.input
)
})?;
let vk = args.in_fmt.decode(vk_bytes, args.config)?;
let mut out = out_file(args.output.as_ref())?;
args.out_fmt.write_vk(&vk, &mut out)?;

log::info!("Successfully wrote output");
Ok(())
}

fn out_file(output: Option<&PathBuf>) -> Result<Box<dyn Write>> {
match output {
Some(path) => {
Ok(Box::new(File::create(path).with_context(|| {
format!("Failed to create output file {:?}", path)
})?))
}
None => Ok(Box::new(io::stdout())),
}
}
70 changes: 70 additions & 0 deletions src/bin/formats/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use anyhow::{Context, Result};
use clap::ValueEnum;
use plonky2_verifier::Plonky2Config;
use plonky2_verifier::Vk;
use std::io;

/// Supported formats for input verifier circuit data.
#[derive(Copy, Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Hash)]
pub enum VerifierCircuitDataFormat {
/// Raw binary bytes.
#[default]
Bytes,
/// Hex-encoded string (with optional "0x" prefix).
Hex,
}

impl VerifierCircuitDataFormat {
/// Decodes the verifier circuit data from the specified format.
pub fn decode(&self, vk_bytes: Vec<u8>, config: Plonky2Config) -> Result<Vk> {
let vk_bytes = match self {
VerifierCircuitDataFormat::Bytes => vk_bytes,
VerifierCircuitDataFormat::Hex => {
let hex_str = vk_bytes.strip_prefix(b"0x").unwrap_or(&vk_bytes);
hex::decode(hex_str).with_context(|| {
format!(
"Failed to decode hex string: {:?}",
String::from_utf8_lossy(hex_str)
)
})?
}
};

Ok(Vk {
vk_bytes: bytes,
config,
})
}
}

/// Formats for outputting serialized verifier circuit data.
#[derive(Copy, Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Hash)]
pub enum Format {
/// JSON format (pretty-printed).
#[default]
Json,
/// Raw binary bytes.
Bytes,
/// Hex-encoded string.
Hex,
}

impl Format {
/// Writes the verifier circuit data (`Vk`) to the specified output in the selected format.
pub fn write_vk(&self, vk: &Vk, out: &mut dyn io::Write) -> Result<()> {
match self {
Format::Json => {
serde_json::to_writer_pretty(out, vk).context("Failed to serialize Vk as JSON")?;
}
Format::Bytes => {
out.write_all(&vk.as_bytes())
.context("Failed to write Vk as raw bytes")?;
}
Format::Hex => {
out.write_all(vk.as_hex().as_bytes())
.context("Failed to write Vk as a hex string")?;
}
}
Ok(())
}
}
16 changes: 16 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Configuration for `plonky2` verifier.
use serde::{Deserialize, Serialize};

#[cfg(feature = "converter")]
use clap::ValueEnum;

/// Config for `Plonky2` proving system with options, acceptable by `zkVerify`.
#[derive(Copy, Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "converter", derive(ValueEnum))]
pub enum Plonky2Config {
/// Preset Keccak over Goldilocks config available in `plonky2`
Keccak,
/// Preset Poseidon over Goldilocks config available in `plonky2`
#[default]
Poseidon,
}
25 changes: 20 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
#[cfg(not(feature = "std"))]
extern crate alloc;

mod config;
mod deserializer;
pub mod validate;
mod vk;

use deserializer::{deserialize_proof_with_pubs, deserialize_vk};

Expand All @@ -15,7 +17,9 @@ use plonky2::hash::hash_types::RichField;
use plonky2::plonk::config::{GenericConfig, KeccakGoldilocksConfig, PoseidonGoldilocksConfig};
use snafu::Snafu;

pub use config::Plonky2Config;
pub use deserializer::{custom::ZKVerifyGateSerializer, DeserializeError};
pub use vk::Vk;

/// Verification error.
#[derive(Debug, Snafu)]
Expand All @@ -38,9 +42,12 @@ impl From<DeserializeError> for VerifyError {
}
}

/// Verify the given proof `proof` and public inputs `pubs` using verification key `vk`.
/// Use the given verification key `vk` to verify the proof `proof` against the public inputs `pubs`.
pub fn verify<F, C, const D: usize>(vk: &[u8], proof: &[u8], pubs: &[u8]) -> Result<(), VerifyError>
/// Verify the given `proof` and public inputs `pubs` using verification key `vk`.
pub fn verify_inner<F, C, const D: usize>(
vk: &[u8],
proof: &[u8],
pubs: &[u8],
) -> Result<(), VerifyError>
where
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
Expand All @@ -57,7 +64,7 @@ pub fn verify_default_poseidon(vk: &[u8], proof: &[u8], pubs: &[u8]) -> Result<(
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;

verify::<F, C, D>(vk, proof, pubs)
verify_inner::<F, C, D>(vk, proof, pubs)
}

/// Verification with preset Keccak over Goldilocks config available in `plonky2`.
Expand All @@ -66,5 +73,13 @@ pub fn verify_default_keccak(vk: &[u8], proof: &[u8], pubs: &[u8]) -> Result<(),
type C = KeccakGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;

verify::<F, C, D>(vk, proof, pubs)
verify_inner::<F, C, D>(vk, proof, pubs)
}

/// Verify `proof` with `pubs` depending on `vk` plonky2 configuration.
pub fn verify(vk: &Vk, proof: &[u8], pubs: &[u8]) -> Result<(), VerifyError> {
match vk.config {
Plonky2Config::Keccak => verify_default_keccak(&vk.bytes, proof, pubs),
Plonky2Config::Poseidon => verify_default_poseidon(&vk.bytes, proof, pubs),
}
}
25 changes: 18 additions & 7 deletions src/validate.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Validation crate centered for plonky2-verifier.
use crate::deserializer::deserialize_vk;
use crate::DeserializeError;
use crate::{DeserializeError, Plonky2Config, Vk};
use plonky2::plonk::config::{GenericConfig, KeccakGoldilocksConfig, PoseidonGoldilocksConfig};
use snafu::Snafu;

Expand All @@ -23,24 +23,35 @@ impl From<DeserializeError> for ValidateError {
}
}

/// Validation result.
type ValidateResult = Result<(), ValidateError>;

/// Validate `Vk`.
pub fn validate_vk(vk: &Vk) -> ValidateResult {
match vk.config {
Plonky2Config::Keccak => validate_vk_default_keccak(&vk.bytes),
Plonky2Config::Poseidon => validate_vk_default_poseidon(&vk.bytes),
}
}

/// Validate vk with preset Poseidon over Goldilocks config available in `plonky2`.
pub fn validate_vk_default_poseidon(vk: &[u8]) -> Result<(), ValidateError> {
pub fn validate_vk_default_poseidon(vk: &[u8]) -> ValidateResult {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;

deserialize_vk::<F, C, D>(vk)
.map(|_| ()) // Discard `Ok` value, map it to `()`
.map_err(ValidateError::from) // Convert `DeserializeError` to `ValidateError`
.map(|_| ())
.map_err(ValidateError::from)
}

/// Validate vk with preset Keccak over Goldilocks config available in `plonky2`.
pub fn validate_vk_default_keccak(vk: &[u8]) -> Result<(), ValidateError> {
pub fn validate_vk_default_keccak(vk: &[u8]) -> ValidateResult {
const D: usize = 2;
type C = KeccakGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;

deserialize_vk::<F, C, D>(vk)
.map(|_| ()) // Discard `Ok` value, map it to `()`
.map_err(ValidateError::from) // Convert `DeserializeError` to `ValidateError`
.map(|_| ())
.map_err(ValidateError::from)
}
Loading

0 comments on commit 27b8107

Please sign in to comment.