Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
yuwen01 committed Oct 24, 2024
1 parent 6d35735 commit b794704
Show file tree
Hide file tree
Showing 14 changed files with 60 additions and 45 deletions.
2 changes: 1 addition & 1 deletion crates/verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
lazy_static = { version = "1.5.0", default-features = false }

[dev-dependencies]
sp1-sdk = { path = "../../crates/sdk" }
sp1-sdk = { path = "../sdk" }
num-bigint = "0.4.6"
num-traits = "0.2.19"

Expand Down
5 changes: 0 additions & 5 deletions crates/verifier/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ Groth16 proof verification is supported in completely no-std environments.
Plonk proof verification requires randomness, so it requires a `getrandom` implementation in the
runtime. Environments like wasm and the sp1 zkvm are capable of providing this.

TODO:
* Add some sample snippets demonstrating how to use the verifier in different environments.
* Should you really be verifying SP1 plonk proofs within sp1? If SP1's randomness is from fiat shamir,
I guess it should be fine ... but this seems like a subtle, bug-prone use case.

# Acknowledgements

Adapted from [@Bisht13's](https://github.com/Bisht13/gnark-bn254-verifier) `gnark-bn254-verifier` crate.
Expand Down
3 changes: 3 additions & 0 deletions crates/verifier/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/// Gnark (and arkworks) use the 2 most significant bits to encode the flag for a compressed point.
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;
Expand Down
30 changes: 25 additions & 5 deletions crates/verifier/src/converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,32 @@ use crate::{
error::Error,
};

pub(crate) fn is_zeroed(first_byte: u8, buf: &[u8]) -> Result<bool, Error> {
/// Checks if the first byte is zero and the rest of the bytes are zero.
pub(crate) fn is_zeroed(first_byte: u8, buf: &[u8]) -> bool {
if first_byte != 0 {
return Ok(false);
return false;
}
for &b in buf {
if b != 0 {
return Ok(false);
return false;
}
}

Ok(true)
true
}

/// 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) {
if !is_zeroed(buf[0] & !MASK, &buf[1..32]).map_err(|_| Error::InvalidPoint)? {
if !is_zeroed(buf[0] & !MASK, &buf[1..32]) {
return Err(Error::InvalidPoint);
}
Ok((Fq::zero(), CompressedPointFlag::Infinity))
Expand All @@ -42,6 +47,10 @@ pub(crate) fn deserialize_with_flags(buf: &[u8]) -> Result<(Fq, CompressedPointF
}
}

/// 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.
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)?;
Expand All @@ -58,6 +67,9 @@ pub(crate) fn unchecked_compressed_x_to_g1_point(buf: &[u8]) -> Result<AffineG1,
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);
Expand All @@ -70,6 +82,11 @@ pub(crate) fn uncompressed_bytes_to_g1_point(buf: &[u8]) -> Result<AffineG1, Err
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.
pub(crate) fn unchecked_compressed_x_to_g2_point(buf: &[u8]) -> Result<AffineG2, Error> {
if buf.len() != 64 {
return Err(Error::InvalidXLength);
Expand All @@ -92,6 +109,9 @@ pub(crate) fn unchecked_compressed_x_to_g2_point(buf: &[u8]) -> Result<AffineG2,
}
}

/// 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);
Expand Down
4 changes: 1 addition & 3 deletions crates/verifier/src/groth16/converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ pub(crate) fn load_groth16_verifying_key_from_bytes(
buffer: &[u8],
) -> Result<Groth16VerifyingKey, Groth16Error> {
let g1_alpha = unchecked_compressed_x_to_g1_point(&buffer[..32])?;
let g1_beta = unchecked_compressed_x_to_g1_point(&buffer[32..64])?;
let g2_beta = unchecked_compressed_x_to_g2_point(&buffer[64..128])?;
let g2_gamma = unchecked_compressed_x_to_g2_point(&buffer[128..192])?;
let g1_delta = unchecked_compressed_x_to_g1_point(&buffer[192..224])?;
let g2_delta = unchecked_compressed_x_to_g2_point(&buffer[224..288])?;

let num_k = u32::from_be_bytes([buffer[288], buffer[289], buffer[290], buffer[291]]);
Expand All @@ -38,7 +36,7 @@ pub(crate) fn load_groth16_verifying_key_from_bytes(
}

Ok(Groth16VerifyingKey {
g1: Groth16G1 { alpha: g1_alpha, beta: -g1_beta, delta: g1_delta, k },
g1: Groth16G1 { alpha: g1_alpha, k },
g2: Groth16G2 { beta: -g2_beta, gamma: g2_gamma, delta: g2_delta },
})
}
4 changes: 2 additions & 2 deletions crates/verifier/src/groth16/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl Groth16Verifier {
/// * `sp1_vkey_hash` - The SP1 vkey hash.
/// This is generated in the following manner:
///
/// ```no_run
/// ```ignore
/// use sp1_sdk::ProverClient;
/// let client = ProverClient::new();
/// let (pk, vk) = client.setup(ELF);
Expand Down Expand Up @@ -61,6 +61,6 @@ impl Groth16Verifier {
let proof = load_groth16_proof_from_bytes(&proof[4..]).unwrap();
let groth16_vk = load_groth16_verifying_key_from_bytes(groth16_vk).unwrap();

verify_groth16(&groth16_vk, &proof, &public_inputs)
verify_groth16_raw(&groth16_vk, &proof, &public_inputs)
}
}
24 changes: 11 additions & 13 deletions crates/verifier/src/groth16/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,27 @@ use super::error::Groth16Error;

#[derive(Clone, PartialEq)]
pub(crate) struct Groth16G1 {
pub alpha: AffineG1,
pub beta: AffineG1,
pub delta: AffineG1,
pub k: Vec<AffineG1>,
pub(crate) alpha: AffineG1,
pub(crate) k: Vec<AffineG1>,
}

#[derive(Clone, PartialEq)]
pub(crate) struct Groth16G2 {
pub beta: AffineG2,
pub delta: AffineG2,
pub gamma: AffineG2,
pub(crate) beta: AffineG2,
pub(crate) delta: AffineG2,
pub(crate) gamma: AffineG2,
}

#[derive(Clone, PartialEq)]
pub(crate) struct Groth16VerifyingKey {
pub g1: Groth16G1,
pub g2: Groth16G2,
pub(crate) g1: Groth16G1,
pub(crate) g2: Groth16G2,
}

pub(crate) struct Groth16Proof {
pub ar: AffineG1,
pub krs: AffineG1,
pub bs: AffineG2,
pub(crate) ar: AffineG1,
pub(crate) krs: AffineG1,
pub(crate) bs: AffineG2,
}

// Prepare the inputs for the Groth16 verification by combining the public inputs with the corresponding elements of the verification key.
Expand All @@ -43,7 +41,7 @@ fn prepare_inputs(vk: Groth16VerifyingKey, public_inputs: &[Fr]) -> Result<G1, G
.into())
}

pub fn verify_groth16(
pub(crate) fn verify_groth16_raw(
vk: &Groth16VerifyingKey,
proof: &Groth16Proof,
public_inputs: &[Fr],
Expand Down
2 changes: 1 addition & 1 deletion crates/verifier/src/plonk/hash_to_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl WrappedHashToField {
Ok(res[0].clone())
}

pub fn hash(msg: Vec<u8>, dst: Vec<u8>, count: usize) -> Result<Vec<Vec<u8>>, Error> {
pub(crate) fn hash(msg: Vec<u8>, dst: Vec<u8>, count: usize) -> Result<Vec<Vec<u8>>, Error> {
let bytes = 32;
let l = 16 + bytes;

Expand Down
8 changes: 4 additions & 4 deletions crates/verifier/src/plonk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ pub(crate) mod error;

pub(crate) use converter::{load_plonk_proof_from_bytes, load_plonk_verifying_key_from_bytes};
pub(crate) use proof::PlonkProof;
use sha2::{Digest, Sha256};
pub(crate) use verify::verify_plonk;
pub(crate) use verify::verify_plonk_raw;

use error::PlonkError;
use sha2::{Digest, Sha256};

use crate::{bn254_public_values, decode_sp1_vkey_hash};
/// A verifier for Plonk zero-knowledge proofs.
Expand All @@ -32,7 +32,7 @@ impl PlonkVerifier {
/// * `sp1_vkey_hash` - The SP1 vkey hash.
/// This is generated in the following manner:
///
/// ```no_run
/// ```ignore
/// use sp1_sdk::ProverClient;
/// let client = ProverClient::new();
/// let (pk, vk) = client.setup(ELF);
Expand Down Expand Up @@ -69,6 +69,6 @@ impl PlonkVerifier {
let proof = load_plonk_proof_from_bytes(&proof[4..]).unwrap();
let plonk_vk = load_plonk_verifying_key_from_bytes(plonk_vk).unwrap();

verify_plonk(&plonk_vk, &proof, &public_inputs)
verify_plonk_raw(&plonk_vk, &proof, &public_inputs)
}
}
2 changes: 1 addition & 1 deletion crates/verifier/src/plonk/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub(crate) struct PlonkVerifyingKey {
/// # Returns
///
/// * `Result<bool, PlonkError>` - Returns true if the proof is valid, or an error if verification fails
pub fn verify_plonk(
pub(crate) fn verify_plonk_raw(
vk: &PlonkVerifyingKey,
proof: &PlonkProof,
public_inputs: &[Fr],
Expand Down
13 changes: 7 additions & 6 deletions crates/verifier/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::{Groth16Verifier, PlonkVerifier};
use sp1_sdk::SP1ProofWithPublicValues;

extern crate std;
Expand All @@ -7,7 +6,7 @@ extern crate std;
fn test_verify_groth16() {
// Location of the serialized SP1ProofWithPublicValues. This proof was generated with the
// fibonacci example in `examples/fibonacci/program`.
let proof_file = "test_binaries/fib_groth_300.bin";
let proof_file = "test_binaries/fibonacci_groth16.bin";

// Load the saved proof and extract the proof and public inputs.
let sp1_proof_with_public_values = SP1ProofWithPublicValues::load(proof_file).unwrap();
Expand All @@ -19,19 +18,20 @@ fn test_verify_groth16() {
let vkey_hash = "0x0051835c0ba4b1ce3e6c5f4c5ab88a41e3eb1bc725d383f12255028ed76bd9a7";

let is_valid =
Groth16Verifier::verify(&proof, &public_inputs, vkey_hash, &crate::GROTH16_VK_BYTES)
crate::Groth16Verifier::verify(&proof, &public_inputs, vkey_hash, &crate::GROTH16_VK_BYTES)
.expect("Groth16 proof is invalid");

if !is_valid {
panic!("Groth16 proof is invalid");
}
}

#[cfg(feature = "getrandom")]
#[test]
fn test_verify_plonk() {
// Location of the serialized SP1ProofWithPublicValues. This proof was generated with the
// fibonacci example in `examples/fibonacci/program`.
let proof_file = "test_binaries/fib_plonk_300.bin";
let proof_file = "test_binaries/fibonacci_plonk.bin";

// Load the saved proof and extract the proof and public inputs.
let sp1_proof_with_public_values = SP1ProofWithPublicValues::load(proof_file).unwrap();
Expand All @@ -42,8 +42,9 @@ fn test_verify_plonk() {
// This vkey hash was derived by calling `vk.bytes32()` on the verifying key.
let vkey_hash = "0x0051835c0ba4b1ce3e6c5f4c5ab88a41e3eb1bc725d383f12255028ed76bd9a7";

let is_valid = PlonkVerifier::verify(&proof, &public_inputs, vkey_hash, &crate::PLONK_VK_BYTES)
.expect("Plonk proof is invalid");
let is_valid =
crate::PlonkVerifier::verify(&proof, &public_inputs, vkey_hash, &crate::PLONK_VK_BYTES)
.expect("Plonk proof is invalid");

if !is_valid {
panic!("Plonk proof is invalid");
Expand Down
8 changes: 4 additions & 4 deletions crates/verifier/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@ use sha2::{Digest, Sha256};

use crate::error::Error;

/// Hashes the public inputs in the same format as the BN254 verifier.
/// Hashes the public inputs in the same format as the Plonk and Groth16 verifiers.
pub fn hash_public_inputs(public_inputs: &[u8]) -> [u8; 32] {
let mut result = Sha256::digest(public_inputs);

// The BN254 verifier operate over a 254 bit field, so we need to zero
// The Plonk and Groth16 verifiers operate over a 254 bit field, so we need to zero
// out the first 3 bits. The same logic happens in the SP1 Ethereum verifier contract.
result[0] &= 0x1F;

result.into()
}

/// Formats the sp1 vkey hash and public inputs for use in the BN254 verifier.
/// Formats the sp1 vkey hash and public inputs for use in either the Plonk or Groth16 verifier.
pub fn bn254_public_values(sp1_vkey_hash: &[u8; 32], sp1_public_inputs: &[u8]) -> [Fr; 2] {
let committed_values_digest = hash_public_inputs(sp1_public_inputs);
let vkey_hash = Fr::from_slice(&sp1_vkey_hash[1..]).unwrap();
let committed_values_digest = Fr::from_slice(&committed_values_digest).unwrap();
[vkey_hash, committed_values_digest]
}

/// Decodes the sp1 vkey hash from the string from bytes32.
/// Decodes the sp1 vkey hash from the string from a call to `vk.bytes32`.
pub fn decode_sp1_vkey_hash(sp1_vkey_hash: &str) -> Result<[u8; 32], Error> {
let bytes = hex::decode(&sp1_vkey_hash[2..]).map_err(|_| Error::InvalidProgramVkeyHash)?;
bytes.try_into().map_err(|_| Error::InvalidProgramVkeyHash)
Expand Down

0 comments on commit b794704

Please sign in to comment.