From 8902b2bc0933eea022282615ee3369ecfd4ed17d Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Wed, 16 Oct 2024 19:22:42 -0700 Subject: [PATCH] fix: docs (#9) --- .gitignore | 1 + Cargo.lock | 2 + example/program/src/lib.rs | 13 +- example/script/src/main.rs | 9 +- verifier/Cargo.toml | 2 + verifier/src/lib.rs | 315 ++++----------------------- verifier/src/test.rs | 18 +- verifier/src/utils.rs | 279 ++++++++++++++++++++++++ verifier/vk/v1.2.0/groth16_vk.bin | Bin 520 -> 0 bytes verifier/vk/v3.0.0rc4/groth16_vk.bin | Bin 0 -> 396 bytes 10 files changed, 363 insertions(+), 276 deletions(-) create mode 100644 verifier/src/utils.rs delete mode 100644 verifier/vk/v1.2.0/groth16_vk.bin create mode 100644 verifier/vk/v3.0.0rc4/groth16_vk.bin diff --git a/.gitignore b/.gitignore index 3994d02..77d5979 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ target/ test_serialized_fixture.bin +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 25e7389..8469907 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8624,6 +8624,8 @@ dependencies = [ "ark-serialize 0.4.2", "borsh 1.5.1", "groth16-solana", + "hex", + "hex-literal 0.3.4", "num-bigint 0.4.6", "num-traits", "sha2 0.10.8", diff --git a/example/program/src/lib.rs b/example/program/src/lib.rs index e4af36c..f713e0e 100644 --- a/example/program/src/lib.rs +++ b/example/program/src/lib.rs @@ -11,9 +11,16 @@ use solana_program::entrypoint; #[cfg(not(feature = "no-entrypoint"))] entrypoint!(process_instruction); -// Derived by running `vk.bytes32()` on the program's vkey. -const FIBONACCI_VKEY_HASH: [u8; 32] = - hex_literal::hex!("0083e8e370d7f0d1c463337f76c9a60b62ad7cc54c89329107c92c1e62097872"); +#[cfg(not(doctest))] +/// Derived as follows: +/// +/// ``` +/// let client = sp1_sdk::ProverClient::new(); +/// let (pk, vk) = client.setup(YOUR_ELF_HERE); +/// let vkey_hash = vk.bytes32(); +/// ``` +const FIBONACCI_VKEY_HASH: &str = + "0x0083e8e370d7f0d1c463337f76c9a60b62ad7cc54c89329107c92c1e62097872"; /// The instruction data for the program. #[derive(BorshDeserialize, BorshSerialize)] diff --git a/example/script/src/main.rs b/example/script/src/main.rs index 956827a..2882b65 100644 --- a/example/script/src/main.rs +++ b/example/script/src/main.rs @@ -21,8 +21,10 @@ struct Cli { prove: bool, } +/// The ELF binary of the SP1 program. const ELF: &[u8] = include_bytes!("../../sp1-program/elf/riscv32im-succinct-zkvm-elf"); +/// Invokes the solana program using Solana Program Test. async fn run_verify_instruction(groth16_proof: SP1Groth16Proof) { let program_id = Pubkey::new_unique(); @@ -62,7 +64,12 @@ async fn main() { if args.prove { // Initialize the prover client let client = ProverClient::new(); - let (pk, _vk) = client.setup(ELF); + let (pk, vk) = client.setup(ELF); + + println!( + "Program Verification Key Bytes {:?}", + sp1_sdk::HashableKey::bytes32(&vk) + ); // In our SP1 program, compute the 20th fibonacci number. let mut stdin = SP1Stdin::new(); diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index 772d64f..25a09f3 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -20,7 +20,9 @@ ark-serialize = "0.4.2" ark-ff = "0.4.2" groth16-solana = { git = "https://github.com/sp1-patches/groth16-solana", branch = "patch-v0.0.3" } thiserror = "1.0.63" +hex = "0.4.3" [dev-dependencies] sp1-sdk = { version = "2.0.0", default-features = false} +hex-literal = "0.3.1" num-traits = { version = "0.2.19" } diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index a415b26..6c29142 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -1,255 +1,39 @@ -use ark_bn254::{Fq, G1Affine}; -use ark_ff::PrimeField; -use ark_serialize::CanonicalSerialize; -use borsh::BorshSerialize; +//! # Verifier +//! +//! This crate contains utilities for verifying SP1 Groth16 proofs on Solana. +//! +//! # Example +//! ``` +//! use sp1_sdk::proof::SP1ProofWithPublicValues; +//! use sp1_solana::{verify_proof, GROTH16_VK_2_0_0_BYTES}; +//! +//! // Load the sp1_proof_with_public_values from a file. +//! let sp1_proof_with_public_values_file = "../proofs/fibonacci_proof.bin"; +//! let sp1_proof_with_public_values = +//! SP1ProofWithPublicValues::load(&sp1_proof_with_public_values_file).unwrap(); +//! +//! // Fetch the proof and public inputs from the SP1ProofWithPublicValues. +//! let proof_bytes = sp1_proof_with_public_values.bytes(); +//! let sp1_public_inputs = sp1_proof_with_public_values.public_values.to_vec(); +//! +//! // Typically, the vkey hash is computed from `vk.bytes32()` on the SP1 program's vkey. +//! let vkey_hash = "0x0083e8e370d7f0d1c463337f76c9a60b62ad7cc54c89329107c92c1e62097872"; +//! +//! verify_proof(&proof_bytes, &sp1_public_inputs, &vkey_hash, &GROTH16_VK_2_0_0_BYTES).unwrap(); +//! ``` + use groth16_solana::groth16::Groth16Verifyingkey; use sha2::{Digest, Sha256}; -use thiserror::Error; - -/// Groth16 verification keys for different SP1 versions. -pub const GROTH16_VK_1_2_0_BYTES: &[u8] = include_bytes!("../vk/v1.2.0/groth16_vk.bin"); -pub const GROTH16_VK_2_0_0_BYTES: &[u8] = include_bytes!("../vk/v2.0.0/groth16_vk.bin"); #[cfg(test)] mod test; -#[derive(Error, Debug)] -pub enum Error { - #[error("G1 compression error")] - G1CompressionError, - #[error("G2 compression error")] - G2CompressionError, - #[error("Verification error")] - VerificationError, - #[error("Invalid public input")] - InvalidPublicInput, - #[error("Serialization error")] - SerializationError, - #[error("Deserialization error")] - DeserializationError, - #[error("Invalid instruction data")] - InvalidInstructionData, - #[error("Arithmetic error")] - ArithmeticError, - #[error("Pairing error")] - PairingError, - #[error("Invalid input")] - InvalidInput, - #[error("Borsh serialization error")] - BorshSerializeError, - #[error("Borsh deserialization error")] - BorshDeserializeError, - #[error("IO error")] - IoError, - #[error("Groth16 vkey hash mismatch")] - Groth16VkeyHashMismatch, -} - -const SCALAR_LEN: usize = 32; -const G1_LEN: usize = 64; -const G2_LEN: usize = 128; - -/// Everything needed to verify a Groth16 proof. -#[allow(dead_code)] -pub struct Verifier<'a, const N_PUBLIC: usize> { - /// The proof to verify. - proof: &'a Proof, - /// The public inputs to the proof. - public: &'a PublicInputs, - /// The verification key. - vk: &'a VerificationKey, -} - -/// A Groth16 proof. -/// -/// All Group elements are represented in uncompressed form. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Proof { - pub pi_a: [u8; 64], - pub pi_b: [u8; 128], - pub pi_c: [u8; 64], -} - -/// A generic Groth16 verification key over BN254. -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize)] -pub struct VerificationKey { - pub nr_pubinputs: u32, - pub vk_alpha_g1: [u8; G1_LEN], - pub vk_beta_g2: [u8; G2_LEN], - pub vk_gamma_g2: [u8; G2_LEN], - pub vk_delta_g2: [u8; G2_LEN], - pub vk_ic: Vec<[u8; G1_LEN]>, -} - -/// The public inputs for a Groth16 proof. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PublicInputs { - pub inputs: [[u8; SCALAR_LEN]; N], -} - -/// Convert the endianness of a byte array, chunk by chunk. -/// -/// Taken from https://github.com/anza-xyz/agave/blob/c54d840/curves/bn254/src/compression.rs#L176-L189 -fn convert_endianness( - bytes: &[u8; ARRAY_SIZE], -) -> [u8; ARRAY_SIZE] { - let reversed: [_; ARRAY_SIZE] = bytes - .chunks_exact(CHUNK_SIZE) - .flat_map(|chunk| chunk.iter().rev().copied()) - .enumerate() - .fold([0u8; ARRAY_SIZE], |mut acc, (i, v)| { - acc[i] = v; - acc - }); - reversed -} - -fn decompress_g1(g1_bytes: &[u8; 32]) -> Result<[u8; 64], Error> { - let g1_bytes = gnark_compressed_x_to_ark_compressed_x(g1_bytes)?; - let g1_bytes = convert_endianness::<32, 32>(&g1_bytes.as_slice().try_into().unwrap()); - groth16_solana::decompression::decompress_g1(&g1_bytes).map_err(|_| Error::G1CompressionError) -} - -fn decompress_g2(g2_bytes: &[u8; 64]) -> Result<[u8; 128], Error> { - let g2_bytes = gnark_compressed_x_to_ark_compressed_x(g2_bytes)?; - let g2_bytes = convert_endianness::<64, 64>(&g2_bytes.as_slice().try_into().unwrap()); - groth16_solana::decompression::decompress_g2(&g2_bytes).map_err(|_| Error::G2CompressionError) -} - -const GNARK_MASK: u8 = 0b11 << 6; -const GNARK_COMPRESSED_POSTIVE: u8 = 0b10 << 6; -const GNARK_COMPRESSED_NEGATIVE: u8 = 0b11 << 6; -const GNARK_COMPRESSED_INFINITY: u8 = 0b01 << 6; - -const ARK_MASK: u8 = 0b11 << 6; -const ARK_COMPRESSED_POSTIVE: u8 = 0b00 << 6; -const ARK_COMPRESSED_NEGATIVE: u8 = 0b10 << 6; -const ARK_COMPRESSED_INFINITY: u8 = 0b01 << 6; - -fn gnark_flag_to_ark_flag(msb: u8) -> Result { - let gnark_flag = msb & GNARK_MASK; - - let ark_flag = match gnark_flag { - GNARK_COMPRESSED_POSTIVE => ARK_COMPRESSED_POSTIVE, - GNARK_COMPRESSED_NEGATIVE => ARK_COMPRESSED_NEGATIVE, - GNARK_COMPRESSED_INFINITY => ARK_COMPRESSED_INFINITY, - _ => { - return Err(Error::InvalidInput); - } - }; - - Ok(msb & !ARK_MASK | ark_flag) -} - -fn gnark_compressed_x_to_ark_compressed_x(x: &[u8]) -> Result, Error> { - if x.len() != 32 && x.len() != 64 { - return Err(Error::InvalidInput); - } - let mut x_copy = x.to_owned(); - - let msb = gnark_flag_to_ark_flag(x_copy[0])?; - x_copy[0] = msb; - - x_copy.reverse(); - Ok(x_copy) -} - -fn uncompressed_bytes_to_g1_point(buf: &[u8]) -> Result { - if buf.len() != 64 { - return Err(Error::InvalidInput); - }; - - let (x_bytes, y_bytes) = buf.split_at(32); - - let x = Fq::from_be_bytes_mod_order(x_bytes); - let y = Fq::from_be_bytes_mod_order(y_bytes); - - Ok(G1Affine::new_unchecked(x, y)) -} - -fn negate_g1(g1_bytes: &[u8; 64]) -> Result<[u8; 64], Error> { - let g1 = -uncompressed_bytes_to_g1_point(g1_bytes)?; - let mut g1_bytes = [0u8; 64]; - g1.serialize_uncompressed(&mut g1_bytes[..]) - .map_err(|_| Error::G1CompressionError)?; - Ok(convert_endianness::<32, 64>( - &g1_bytes.as_slice().try_into().unwrap(), - )) -} - -fn load_proof_from_bytes(buffer: &[u8]) -> Result { - Ok(Proof { - pi_a: negate_g1( - &buffer[..64] - .try_into() - .map_err(|_| Error::G1CompressionError)?, - )?, - pi_b: buffer[64..192] - .try_into() - .map_err(|_| Error::G2CompressionError)?, - pi_c: buffer[192..256] - .try_into() - .map_err(|_| Error::G1CompressionError)?, - }) -} - -fn load_groth16_verifying_key_from_bytes(buffer: &[u8]) -> Result { - // Note that g1_beta and g1_delta are not used in the verification process. - let g1_alpha = decompress_g1(buffer[..32].try_into().unwrap())?; - let g2_beta = decompress_g2(buffer[64..128].try_into().unwrap())?; - let g2_gamma = decompress_g2(buffer[128..192].try_into().unwrap())?; - let g2_delta = decompress_g2(buffer[224..288].try_into().unwrap())?; - - let num_k = u32::from_be_bytes([buffer[288], buffer[289], buffer[290], buffer[291]]); - let mut k = Vec::new(); - let mut offset = 292; - for _ in 0..num_k { - let point = decompress_g1(&buffer[offset..offset + 32].try_into().unwrap())?; - k.push(point); - offset += 32; - } +mod utils; +use utils::*; - let num_of_array_of_public_and_commitment_committed = u32::from_be_bytes([ - buffer[offset], - buffer[offset + 1], - buffer[offset + 2], - buffer[offset + 3], - ]); - offset += 4; - for _ in 0..num_of_array_of_public_and_commitment_committed { - let num = u32::from_be_bytes([ - buffer[offset], - buffer[offset + 1], - buffer[offset + 2], - buffer[offset + 3], - ]); - offset += 4; - for _ in 0..num { - offset += 4; - } - } - - Ok(VerificationKey { - vk_alpha_g1: g1_alpha, - vk_beta_g2: g2_beta, - vk_gamma_g2: g2_gamma, - vk_delta_g2: g2_delta, - vk_ic: k.clone(), - nr_pubinputs: num_of_array_of_public_and_commitment_committed, - }) -} - -fn load_public_inputs_from_bytes(buffer: &[u8]) -> Result, Error> { - let mut bytes = [0u8; 64]; - bytes[1..].copy_from_slice(buffer); // vkey_hash is 31 bytes - - Ok(PublicInputs::<2> { - inputs: [ - bytes[..32].try_into().map_err(|_| Error::InvalidInput)?, // vkey_hash - bytes[32..].try_into().map_err(|_| Error::InvalidInput)?, // committed_values_digest - ], - }) -} +/// Groth16 verification keys for different SP1 versions. +pub const GROTH16_VK_3_0_0_RC4_BYTES: &[u8] = include_bytes!("../vk/v3.0.0rc4/groth16_vk.bin"); +pub const GROTH16_VK_2_0_0_BYTES: &[u8] = include_bytes!("../vk/v2.0.0/groth16_vk.bin"); /// Verifies a proof using raw bytes, without any checks. /// @@ -288,48 +72,39 @@ pub fn verify_proof_raw(proof: &[u8], public_inputs: &[u8], vk: &[u8]) -> Result } } -/// Hashes the public inputs in the same format as the Groth16 verifier. -pub fn hash_public_inputs(public_inputs: &[u8]) -> [u8; 32] { - let mut result = Sha256::digest(public_inputs); - - // Zero out the first 3 bits. - result[0] = result[0] & 0x1F; - - result.into() -} - -/// Formats the sp1 vkey hash and public inputs for use in the Groth16 verifier. -pub fn groth16_public_values(sp1_vkey_hash: &[u8; 32], sp1_public_inputs: &[u8]) -> Vec { - let committed_values_digest = hash_public_inputs(sp1_public_inputs); - [ - sp1_vkey_hash[1..].to_vec(), - committed_values_digest.to_vec(), - ] - .concat() -} - -/// Verifies a proof generated by [`SP1ProofWithPublicValues.bytes()`]. +/// Verifies a proof generated by [`SP1ProofWithPublicValues`]. /// -/// Checks the Groth16 vkey hash embedded in the `proof` against the provided groth16 vkey. +/// The proof is expected to be from this method on `SP1ProofWithPublicValues`: +/// https://docs.rs/sp1-sdk/latest/sp1_sdk/proof/struct.SP1ProofWithPublicValues.html#method.bytes +/// The public inputs are directly taken from the `SP1PublicValues`. +/// https://docs.rs/sp1-sdk/latest/sp1_sdk/struct.SP1PublicValues.html#method.as_slice +/// The vkey hash is derived from running `vk.bytes32()` on the program's vkey. +/// https://docs.rs/sp1-sdk/latest/sp1_sdk/trait.HashableKey.html#method.bytes32 #[inline] pub fn verify_proof( proof: &[u8], sp1_public_inputs: &[u8], - sp1_vkey_hash: &[u8; 32], + sp1_vkey_hash: &str, groth16_vk: &[u8], ) -> Result<(), Error> { // Hash the vk and get the first 4 bytes. let groth16_vk_hash: [u8; 4] = Sha256::digest(groth16_vk)[..4].try_into().unwrap(); - // Compare against the groth16_proof's groth16 vkey hash. + // Check to make sure that this proof was generated by the groth16 proving key corresponding to + // the given groth16_vk. + // + // SP1 prepends the raw Groth16 proof with the first 4 bytes of the groth16 vkey to + // faciliate this check. if groth16_vk_hash != proof[..4] { return Err(Error::Groth16VkeyHashMismatch); } + let sp1_vkey_hash = decode_sp1_vkey_hash(sp1_vkey_hash)?; + // Verify the proof. verify_proof_raw( &proof[4..], - &groth16_public_values(sp1_vkey_hash, sp1_public_inputs), + &groth16_public_values(&sp1_vkey_hash, sp1_public_inputs), groth16_vk, ) } diff --git a/verifier/src/test.rs b/verifier/src/test.rs index eab55c1..057ae51 100644 --- a/verifier/src/test.rs +++ b/verifier/src/test.rs @@ -29,10 +29,12 @@ fn test_verify_from_sp1() { padded_vkey_hash.extend_from_slice(&vkey_hash); let vkey_hash = padded_vkey_hash; + let sp1_vkey_hash = format!("0x{}", hex::encode(vkey_hash)); + assert!(verify_proof( &proof_bytes, &sp1_public_inputs, - &vkey_hash[..32].try_into().unwrap(), + &sp1_vkey_hash, &GROTH16_VK_2_0_0_BYTES ) .is_ok()); @@ -40,7 +42,7 @@ fn test_verify_from_sp1() { #[test] fn test_hash_public_inputs_() { - use crate::hash_public_inputs; + use crate::utils::hash_public_inputs; // Read the serialized SP1ProofWithPublicValues from the file. let sp1_proof_with_public_values_file = "../proofs/fibonacci_proof.bin"; @@ -61,3 +63,15 @@ fn test_hash_public_inputs_() { hash_public_inputs(&sp1_proof_with_public_values.public_values.to_vec()) ); } + +#[test] +fn test_decode_sp1_vkey_hash() { + use crate::utils::decode_sp1_vkey_hash; + + let sp1_vkey_hash = "0x0054c0e58911dd8b993c6d8f249aa50a2e523114ec4b7ef9dd355c5f6bfbf3ce"; + let decoded_sp1_vkey_hash = decode_sp1_vkey_hash(sp1_vkey_hash).unwrap(); + assert_eq!( + decoded_sp1_vkey_hash, + hex_literal::hex!("0054c0e58911dd8b993c6d8f249aa50a2e523114ec4b7ef9dd355c5f6bfbf3ce") + ); +} diff --git a/verifier/src/utils.rs b/verifier/src/utils.rs new file mode 100644 index 0000000..80a707c --- /dev/null +++ b/verifier/src/utils.rs @@ -0,0 +1,279 @@ +//! Utility functions for the SP1 Groth16 Solana verifier. +//! +//! This module contains functions for decompressing G1 and G2 points, as well as +//! for loading proofs into a form appropriate for verification. This is necessary to coerce +//! SP1 Groth16 proofs into the form expected by the `groth16_solana` crate. + +use ark_bn254::{Fq, G1Affine}; +use ark_ff::PrimeField; +use ark_serialize::CanonicalSerialize; +use sha2::{Digest, Sha256}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("G1 compression error")] + G1CompressionError, + #[error("G2 compression error")] + G2CompressionError, + #[error("Verification error")] + VerificationError, + #[error("Invalid public input")] + InvalidPublicInput, + #[error("Serialization error")] + SerializationError, + #[error("Deserialization error")] + DeserializationError, + #[error("Invalid instruction data")] + InvalidInstructionData, + #[error("Arithmetic error")] + ArithmeticError, + #[error("Pairing error")] + PairingError, + #[error("Invalid input")] + InvalidInput, + #[error("Borsh serialization error")] + BorshSerializeError, + #[error("Borsh deserialization error")] + BorshDeserializeError, + #[error("IO error")] + IoError, + #[error("Groth16 vkey hash mismatch")] + Groth16VkeyHashMismatch, + #[error("Invalid program vkey hash")] + InvalidProgramVkeyHash, +} + +const SCALAR_LEN: usize = 32; +const G1_LEN: usize = 64; +const G2_LEN: usize = 128; + +/// Everything needed to verify a Groth16 proof. +#[allow(dead_code)] +pub struct Verifier<'a, const N_PUBLIC: usize> { + /// The proof to verify. + proof: &'a Proof, + /// The public inputs to the proof. + public: &'a PublicInputs, + /// The verification key. + vk: &'a VerificationKey, +} + +/// A Groth16 proof. +/// +/// All Group elements are represented in uncompressed form. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Proof { + pub pi_a: [u8; 64], + pub pi_b: [u8; 128], + pub pi_c: [u8; 64], +} + +/// A generic Groth16 verification key over BN254. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VerificationKey { + pub nr_pubinputs: u32, + pub vk_alpha_g1: [u8; G1_LEN], + pub vk_beta_g2: [u8; G2_LEN], + pub vk_gamma_g2: [u8; G2_LEN], + pub vk_delta_g2: [u8; G2_LEN], + pub vk_ic: Vec<[u8; G1_LEN]>, +} + +/// The public inputs for a Groth16 proof. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublicInputs { + pub inputs: [[u8; SCALAR_LEN]; N], +} + +/// Convert the endianness of a byte array, chunk by chunk. +/// +/// Taken from https://github.com/anza-xyz/agave/blob/c54d840/curves/bn254/src/compression.rs#L176-L189 +fn convert_endianness( + bytes: &[u8; ARRAY_SIZE], +) -> [u8; ARRAY_SIZE] { + let reversed: [_; ARRAY_SIZE] = bytes + .chunks_exact(CHUNK_SIZE) + .flat_map(|chunk| chunk.iter().rev().copied()) + .enumerate() + .fold([0u8; ARRAY_SIZE], |mut acc, (i, v)| { + acc[i] = v; + acc + }); + reversed +} + +fn decompress_g1(g1_bytes: &[u8; 32]) -> Result<[u8; 64], Error> { + let g1_bytes = gnark_compressed_x_to_ark_compressed_x(g1_bytes)?; + let g1_bytes = convert_endianness::<32, 32>(&g1_bytes.as_slice().try_into().unwrap()); + groth16_solana::decompression::decompress_g1(&g1_bytes).map_err(|_| Error::G1CompressionError) +} + +fn decompress_g2(g2_bytes: &[u8; 64]) -> Result<[u8; 128], Error> { + let g2_bytes = gnark_compressed_x_to_ark_compressed_x(g2_bytes)?; + let g2_bytes = convert_endianness::<64, 64>(&g2_bytes.as_slice().try_into().unwrap()); + groth16_solana::decompression::decompress_g2(&g2_bytes).map_err(|_| Error::G2CompressionError) +} + +const GNARK_MASK: u8 = 0b11 << 6; +const GNARK_COMPRESSED_POSTIVE: u8 = 0b10 << 6; +const GNARK_COMPRESSED_NEGATIVE: u8 = 0b11 << 6; +const GNARK_COMPRESSED_INFINITY: u8 = 0b01 << 6; + +const ARK_MASK: u8 = 0b11 << 6; +const ARK_COMPRESSED_POSTIVE: u8 = 0b00 << 6; +const ARK_COMPRESSED_NEGATIVE: u8 = 0b10 << 6; +const ARK_COMPRESSED_INFINITY: u8 = 0b01 << 6; + +fn gnark_flag_to_ark_flag(msb: u8) -> Result { + let gnark_flag = msb & GNARK_MASK; + + let ark_flag = match gnark_flag { + GNARK_COMPRESSED_POSTIVE => ARK_COMPRESSED_POSTIVE, + GNARK_COMPRESSED_NEGATIVE => ARK_COMPRESSED_NEGATIVE, + GNARK_COMPRESSED_INFINITY => ARK_COMPRESSED_INFINITY, + _ => { + return Err(Error::InvalidInput); + } + }; + + Ok(msb & !ARK_MASK | ark_flag) +} + +fn gnark_compressed_x_to_ark_compressed_x(x: &[u8]) -> Result, Error> { + if x.len() != 32 && x.len() != 64 { + return Err(Error::InvalidInput); + } + let mut x_copy = x.to_owned(); + + let msb = gnark_flag_to_ark_flag(x_copy[0])?; + x_copy[0] = msb; + + x_copy.reverse(); + Ok(x_copy) +} + +fn uncompressed_bytes_to_g1_point(buf: &[u8]) -> Result { + if buf.len() != 64 { + return Err(Error::InvalidInput); + }; + + let (x_bytes, y_bytes) = buf.split_at(32); + + let x = Fq::from_be_bytes_mod_order(x_bytes); + let y = Fq::from_be_bytes_mod_order(y_bytes); + + Ok(G1Affine::new_unchecked(x, y)) +} + +fn negate_g1(g1_bytes: &[u8; 64]) -> Result<[u8; 64], Error> { + let g1 = -uncompressed_bytes_to_g1_point(g1_bytes)?; + let mut g1_bytes = [0u8; 64]; + g1.serialize_uncompressed(&mut g1_bytes[..]) + .map_err(|_| Error::G1CompressionError)?; + Ok(convert_endianness::<32, 64>( + &g1_bytes.as_slice().try_into().unwrap(), + )) +} + +pub(crate) fn load_proof_from_bytes(buffer: &[u8]) -> Result { + Ok(Proof { + pi_a: negate_g1( + &buffer[..64] + .try_into() + .map_err(|_| Error::G1CompressionError)?, + )?, + pi_b: buffer[64..192] + .try_into() + .map_err(|_| Error::G2CompressionError)?, + pi_c: buffer[192..256] + .try_into() + .map_err(|_| Error::G1CompressionError)?, + }) +} +pub(crate) fn load_groth16_verifying_key_from_bytes( + buffer: &[u8], +) -> Result { + // Note that g1_beta and g1_delta are not used in the verification process. + let g1_alpha = decompress_g1(buffer[..32].try_into().unwrap())?; + let g2_beta = decompress_g2(buffer[64..128].try_into().unwrap())?; + let g2_gamma = decompress_g2(buffer[128..192].try_into().unwrap())?; + let g2_delta = decompress_g2(buffer[224..288].try_into().unwrap())?; + + let num_k = u32::from_be_bytes([buffer[288], buffer[289], buffer[290], buffer[291]]); + let mut k = Vec::new(); + let mut offset = 292; + for _ in 0..num_k { + let point = decompress_g1(&buffer[offset..offset + 32].try_into().unwrap())?; + k.push(point); + offset += 32; + } + + let num_of_array_of_public_and_commitment_committed = u32::from_be_bytes([ + buffer[offset], + buffer[offset + 1], + buffer[offset + 2], + buffer[offset + 3], + ]); + offset += 4; + for _ in 0..num_of_array_of_public_and_commitment_committed { + let num = u32::from_be_bytes([ + buffer[offset], + buffer[offset + 1], + buffer[offset + 2], + buffer[offset + 3], + ]); + offset += 4; + for _ in 0..num { + offset += 4; + } + } + + Ok(VerificationKey { + vk_alpha_g1: g1_alpha, + vk_beta_g2: g2_beta, + vk_gamma_g2: g2_gamma, + vk_delta_g2: g2_delta, + vk_ic: k.clone(), + nr_pubinputs: num_of_array_of_public_and_commitment_committed, + }) +} + +pub(crate) fn load_public_inputs_from_bytes(buffer: &[u8]) -> Result, Error> { + let mut bytes = [0u8; 64]; + bytes[1..].copy_from_slice(buffer); // vkey_hash is 31 bytes + + Ok(PublicInputs::<2> { + inputs: [ + bytes[..32].try_into().map_err(|_| Error::InvalidInput)?, // vkey_hash + bytes[32..].try_into().map_err(|_| Error::InvalidInput)?, // committed_values_digest + ], + }) +} + +/// Hashes the public inputs in the same format as the Groth16 verifier. +pub fn hash_public_inputs(public_inputs: &[u8]) -> [u8; 32] { + let mut result = Sha256::digest(public_inputs); + + // The Groth16 verifier operates over a 254 bit field (BN254), so we need to zero + // out the first 3 bits. The same logic happens in the SP1 Ethereum verifier contract. + result[0] = result[0] & 0x1F; + + result.into() +} + +/// Formats the sp1 vkey hash and public inputs for use in the Groth16 verifier. +pub fn groth16_public_values(sp1_vkey_hash: &[u8; 32], sp1_public_inputs: &[u8]) -> Vec { + let committed_values_digest = hash_public_inputs(sp1_public_inputs); + [ + sp1_vkey_hash[1..].to_vec(), + committed_values_digest.to_vec(), + ] + .concat() +} + +/// Decodes the sp1 vkey hash from the string from 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) +} diff --git a/verifier/vk/v1.2.0/groth16_vk.bin b/verifier/vk/v1.2.0/groth16_vk.bin deleted file mode 100644 index fc0f1a1d577cfaff82330cd657c4b26c715e05e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 520 zcmV+j0{8vpGI&n8Ga8F}&gc$alph5S{k{52C}({Et(f!d>(M@z?kmEr5V&$BxN#I* zsul429EBCC&oi%<{1fEy4ayiM_ie8zV7*@zn1yl-#X=+`I zJ!qF-uZs6VC~NWwD8mMT1WPXHpfP%so*bmnaAHtPFsy~@%QL2T?H!Qqj|j?n0VX)g zYT62*vC-Q(M|EFL@=hH_+K*;AzfySZV12$An`7|~$+-To7KRY64n%<`IA?sCkl5`H zX$F-88ATIv5b+n3bJUJ_`%7*_U{fs`onzK9x^xNV(AV4m000BhUxv8wP;v+;GKP+y zG)~-ula^1jcPgugw3(q-YG|R9Qx{#a3UOQOB$GdOEju~q%e;tDqVSpF>|xqYxYk7 diff --git a/verifier/vk/v3.0.0rc4/groth16_vk.bin b/verifier/vk/v3.0.0rc4/groth16_vk.bin new file mode 100644 index 0000000000000000000000000000000000000000..f2634ecebd9e3b6a44a4afe72ce49602c2f7875f GIT binary patch literal 396 zcmV;70dxMFh69@$94^+d2h_o-EQ>}#zVk-mywDv^@=;%ts1`V$V=vP?CDkZCw+02$ zk~WecHhxXw zcj2gQ6A0!20KbkX3BTVW#@#5=8o^3vP=vTwZA9eVr?oR2m{U|jd@6q3%7d#4$&Eu5 z#WejDo^L1NOAXLljYMwI?XV+9vPI6ue#8qR0KAaZV(}4XwF~bo1?35biZIE3H