-
Notifications
You must be signed in to change notification settings - Fork 361
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
1,774 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
[package] | ||
name = "sp1-verifier" | ||
description = "Verifier for SP1 Groth16 and Plonk proofs." | ||
readme = "README.md" | ||
version = { workspace = true } | ||
edition = { workspace = true } | ||
license = { workspace = true } | ||
repository = { workspace = true } | ||
keywords = { workspace = true } | ||
categories = { workspace = true } | ||
|
||
[dependencies] | ||
bn = { git = "https://github.com/sp1-patches/bn", version = "0.6.0", tag = "substrate_bn-v0.6.0-patch-v2", package = "substrate-bn" } | ||
sha2 = { version = "0.10.8", default-features = false } | ||
thiserror-no-std = "2.0.2" | ||
hex = { version = "0.4.3", default-features = false, features = ["alloc"] } | ||
lazy_static = { version = "1.5.0", default-features = false } | ||
|
||
[dev-dependencies] | ||
sp1-sdk = { workspace = true } | ||
num-bigint = "0.4.6" | ||
num-traits = "0.2.19" | ||
|
||
[features] | ||
default = ["std"] | ||
std = ["thiserror-no-std/std"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# SP1 Verifier | ||
|
||
This crate provides verifiers for SP1 Groth16 and Plonk zero-knowledge proofs. These proofs are expected | ||
to be generated using the [SP1 SDK](../sdk). | ||
|
||
## Features | ||
|
||
Groth16 and Plonk proof verification are supported in `no-std` environments. Verification in the | ||
SP1 ZKVM context is patched, in order to make use of the | ||
[bn254 precompiles](https://blog.succinct.xyz/succinctshipsprecompiles/). | ||
|
||
### Pre-generated verification keys | ||
|
||
Verification keys for Groth16 and Plonk are stored in the [`bn254-vk`](./bn254-vk/) directory. These | ||
vkeys are used to verify all SP1 proofs. | ||
|
||
These vkeys are the same as those found locally in | ||
`~/.sp1/circuits/<circuit_name>/<version>/<circuit_name>_vk.bin`, and should be automatically | ||
updated after every release. | ||
|
||
## Tests | ||
|
||
Run tests with the following command: | ||
|
||
```sh | ||
cargo test --package sp1-verifier | ||
``` | ||
|
||
These tests verify the proofs in the [`test_binaries`](./test_binaries) directory. These test binaries | ||
were generated from the fibonacci [groth16](../../examples/fibonacci/script/bin/groth16_bn254.rs) and | ||
[plonk](../../examples/fibonacci/script/bin/plonk_bn254.rs) examples. You can reproduce these proofs | ||
from the examples by running `cargo run --bin groth16_bn254` and `cargo run --bin plonk_bn254` from the | ||
[`examples/fibonacci`](../../examples/fibonacci/) directory. | ||
|
||
## Acknowledgements | ||
|
||
Adapted from [@Bisht13's](https://github.com/Bisht13/gnark-bn254-verifier) `gnark-bn254-verifier` crate. |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/// Gnark (and arkworks) use the 2 most significant bits to encode the flag for a compressed | ||
/// G1 point. | ||
/// https://github.com/Consensys/gnark-crypto/blob/a7d721497f2a98b1f292886bb685fd3c5a90f930/ecc/bn254/marshal.go#L32-L42 | ||
pub(crate) const MASK: u8 = 0b11 << 6; | ||
|
||
/// The flags for a positive, negative, or infinity compressed point. | ||
pub(crate) const COMPRESSED_POSITIVE: u8 = 0b10 << 6; | ||
pub(crate) const COMPRESSED_NEGATIVE: u8 = 0b11 << 6; | ||
pub(crate) const COMPRESSED_INFINITY: u8 = 0b01 << 6; | ||
|
||
#[derive(Debug, PartialEq, Eq)] | ||
pub(crate) enum CompressedPointFlag { | ||
Positive = COMPRESSED_POSITIVE as isize, | ||
Negative = COMPRESSED_NEGATIVE as isize, | ||
Infinity = COMPRESSED_INFINITY as isize, | ||
} | ||
|
||
impl From<u8> for CompressedPointFlag { | ||
fn from(val: u8) -> Self { | ||
match val { | ||
COMPRESSED_POSITIVE => CompressedPointFlag::Positive, | ||
COMPRESSED_NEGATIVE => CompressedPointFlag::Negative, | ||
COMPRESSED_INFINITY => CompressedPointFlag::Infinity, | ||
_ => panic!("Invalid compressed point flag"), | ||
} | ||
} | ||
} | ||
|
||
impl From<CompressedPointFlag> for u8 { | ||
fn from(value: CompressedPointFlag) -> Self { | ||
value as u8 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
use core::cmp::Ordering; | ||
|
||
use bn::{AffineG1, AffineG2, Fq, Fq2}; | ||
|
||
use crate::{ | ||
constants::{CompressedPointFlag, MASK}, | ||
error::Error, | ||
}; | ||
|
||
/// Deserializes an Fq element from a buffer. | ||
/// | ||
/// If this Fq element is part of a compressed point, the flag that indicates the sign of the | ||
/// y coordinate is also returned. | ||
pub(crate) fn deserialize_with_flags(buf: &[u8]) -> Result<(Fq, CompressedPointFlag), Error> { | ||
if buf.len() != 32 { | ||
return Err(Error::InvalidXLength); | ||
}; | ||
|
||
let m_data = buf[0] & MASK; | ||
if m_data == u8::from(CompressedPointFlag::Infinity) { | ||
// Checks if the first byte is zero after masking AND the rest of the bytes are zero. | ||
if buf[0] & !MASK == 0 && buf[1..].iter().all(|&b| b == 0) { | ||
return Err(Error::InvalidPoint); | ||
} | ||
Ok((Fq::zero(), CompressedPointFlag::Infinity)) | ||
} else { | ||
let mut x_bytes: [u8; 32] = [0u8; 32]; | ||
x_bytes.copy_from_slice(buf); | ||
x_bytes[0] &= !MASK; | ||
|
||
let x = Fq::from_be_bytes_mod_order(&x_bytes).expect("Failed to convert x bytes to Fq"); | ||
|
||
Ok((x, m_data.into())) | ||
} | ||
} | ||
|
||
/// Converts a compressed G1 point to an AffineG1 point. | ||
/// | ||
/// Asserts that the compressed point is represented as a single fq element: the x coordinate | ||
/// of the point. The y coordinate is then computed from the x coordinate. The final point | ||
/// is not checked to be on the curve for efficiency. | ||
pub(crate) fn unchecked_compressed_x_to_g1_point(buf: &[u8]) -> Result<AffineG1, Error> { | ||
let (x, m_data) = deserialize_with_flags(buf)?; | ||
let (y, neg_y) = AffineG1::get_ys_from_x_unchecked(x).ok_or(Error::InvalidPoint)?; | ||
|
||
let mut final_y = y; | ||
if y.cmp(&neg_y) == Ordering::Greater { | ||
if m_data == CompressedPointFlag::Positive { | ||
final_y = -y; | ||
} | ||
} else if m_data == CompressedPointFlag::Negative { | ||
final_y = -y; | ||
} | ||
|
||
Ok(AffineG1::new_unchecked(x, final_y)) | ||
} | ||
|
||
/// Converts an uncompressed G1 point to an AffineG1 point. | ||
/// | ||
/// Asserts that the affine point is represented as two fq elements. | ||
pub(crate) fn uncompressed_bytes_to_g1_point(buf: &[u8]) -> Result<AffineG1, Error> { | ||
if buf.len() != 64 { | ||
return Err(Error::InvalidXLength); | ||
}; | ||
|
||
let (x_bytes, y_bytes) = buf.split_at(32); | ||
|
||
let x = Fq::from_slice(x_bytes).map_err(Error::Field)?; | ||
let y = Fq::from_slice(y_bytes).map_err(Error::Field)?; | ||
AffineG1::new(x, y).map_err(Error::Group) | ||
} | ||
|
||
/// Converts a compressed G2 point to an AffineG2 point. | ||
/// | ||
/// Asserts that the compressed point is represented as a single fq2 element: the x coordinate | ||
/// of the point. | ||
/// Then, gets the y coordinate from the x coordinate. | ||
/// For efficiency, this function does not check that the final point is on the curve. | ||
pub(crate) fn unchecked_compressed_x_to_g2_point(buf: &[u8]) -> Result<AffineG2, Error> { | ||
if buf.len() != 64 { | ||
return Err(Error::InvalidXLength); | ||
}; | ||
|
||
let (x1, flag) = deserialize_with_flags(&buf[..32])?; | ||
let x0 = Fq::from_be_bytes_mod_order(&buf[32..64]).map_err(Error::Field)?; | ||
let x = Fq2::new(x0, x1); | ||
|
||
if flag == CompressedPointFlag::Infinity { | ||
return Ok(AffineG2::one()); | ||
} | ||
|
||
let (y, neg_y) = AffineG2::get_ys_from_x_unchecked(x).ok_or(Error::InvalidPoint)?; | ||
|
||
match flag { | ||
CompressedPointFlag::Positive => Ok(AffineG2::new_unchecked(x, y)), | ||
CompressedPointFlag::Negative => Ok(AffineG2::new_unchecked(x, neg_y)), | ||
_ => Err(Error::InvalidPoint), | ||
} | ||
} | ||
|
||
/// Converts an uncompressed G2 point to an AffineG2 point. | ||
/// | ||
/// Asserts that the affine point is represented as two fq2 elements. | ||
pub(crate) fn uncompressed_bytes_to_g2_point(buf: &[u8]) -> Result<AffineG2, Error> { | ||
if buf.len() != 128 { | ||
return Err(Error::InvalidXLength); | ||
} | ||
|
||
let (x_bytes, y_bytes) = buf.split_at(64); | ||
let (x1_bytes, x0_bytes) = x_bytes.split_at(32); | ||
let (y1_bytes, y0_bytes) = y_bytes.split_at(32); | ||
|
||
let x1 = Fq::from_slice(x1_bytes).map_err(Error::Field)?; | ||
let x0 = Fq::from_slice(x0_bytes).map_err(Error::Field)?; | ||
let y1 = Fq::from_slice(y1_bytes).map_err(Error::Field)?; | ||
let y0 = Fq::from_slice(y0_bytes).map_err(Error::Field)?; | ||
|
||
let x = Fq2::new(x0, x1); | ||
let y = Fq2::new(y0, y1); | ||
|
||
AffineG2::new(x, y).map_err(Error::Group) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use bn::{CurveError, FieldError, GroupError}; | ||
use thiserror_no_std::Error; | ||
|
||
#[derive(Error, Debug)] | ||
pub enum Error { | ||
// Input Errors | ||
#[error("Invalid witness")] | ||
InvalidWitness, | ||
#[error("Invalid x length")] | ||
InvalidXLength, | ||
#[error("Invalid data")] | ||
InvalidData, | ||
#[error("Invalid point in subgroup check")] | ||
InvalidPoint, | ||
|
||
// Conversion Errors | ||
#[error("Failed to get Fr from random bytes")] | ||
FailedToGetFrFromRandomBytes, | ||
|
||
// External Library Errors | ||
#[error("BN254 Field Error")] | ||
Field(FieldError), | ||
#[error("BN254 Group Error")] | ||
Group(GroupError), | ||
#[error("BN254 Curve Error")] | ||
Curve(CurveError), | ||
|
||
// SP1 Errors | ||
#[error("Invalid program vkey hash")] | ||
InvalidProgramVkeyHash, | ||
} |
Oops, something went wrong.