From a7739c7d907b5cf10b027b5efe321d6a3811d183 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Thu, 10 Oct 2024 21:13:04 -0700 Subject: [PATCH] added hashing the public values --- Cargo.lock | 2 + Cargo.toml | 16 +++++-- example/program/Cargo.toml | 4 +- example/program/src/lib.rs | 9 +++- example/script/src/main.rs | 2 +- proof-fixtures/fibonacci_fixture.bin | Bin 323 -> 340 bytes verifier/Cargo.toml | 2 +- verifier/src/fixture.rs | 60 +++++++++++++++++---------- verifier/src/lib.rs | 5 ++- 9 files changed, 67 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfc8194..77cb013 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2408,9 +2408,11 @@ name = "example-solana-contract" version = "0.1.0" dependencies = [ "borsh 1.5.1", + "hex", "hex-literal 0.3.4", "num-bigint 0.4.6", "num-traits", + "sha2 0.10.8", "solana-program", "sp1-solana", ] diff --git a/Cargo.toml b/Cargo.toml index f93754c..f3e1922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,15 +4,23 @@ exclude = ["example/sp1-program"] resolver = "2" [workspace.dependencies] -borsh = "1.5.1" -solana-program = { git = "https://github.com/anza-xyz/agave", rev = "6e62af0f0de6a40e4e22628cbbcf63b1a6da560e" } + +# workspace sp1-solana = { path = "verifier" } -num-bigint = { version = "0.4.6" } example-solana-contract = { path = "example/program" } + +# solana +solana-program = { git = "https://github.com/anza-xyz/agave", rev = "6e62af0f0de6a40e4e22628cbbcf63b1a6da560e" } solana-program-test = { git = "https://github.com/anza-xyz/agave", rev = "6e62af0f0de6a40e4e22628cbbcf63b1a6da560e" } solana-sdk = { git = "https://github.com/anza-xyz/agave", rev = "6e62af0f0de6a40e4e22628cbbcf63b1a6da560e" } -solana-inline-spl = { git = "https://github.com/anza-xyz/agave", rev = "6e62af0f0de6a40e4e22628cbbcf63b1a6da560e" } +borsh = "1.5.1" + +# misc +num-bigint = "0.4.6" +num-traits = "0.2.19" tokio = "1.40.0" +sha2 = "0.10.8" + [patch.crates-io] # See https://github.com/anza-xyz/agave/blob/master/Cargo.toml#L551-L586 diff --git a/example/program/Cargo.toml b/example/program/Cargo.toml index 5fc0f60..b83d4da 100644 --- a/example/program/Cargo.toml +++ b/example/program/Cargo.toml @@ -16,7 +16,9 @@ default = [] borsh.workspace = true solana-program.workspace = true sp1-solana.workspace = true +num-bigint.workspace = true +sha2.workspace = true +hex = "0.4.3" hex-literal = "0.3.1" num-traits = "0.2.19" -num-bigint.workspace = true diff --git a/example/program/src/lib.rs b/example/program/src/lib.rs index da50a53..d231076 100644 --- a/example/program/src/lib.rs +++ b/example/program/src/lib.rs @@ -1,6 +1,6 @@ use borsh::BorshDeserialize; use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; -use sp1_solana::{verify_proof_fixture, SP1ProofFixture}; +use sp1_solana::{hash_public_inputs, verify_proof_fixture, SP1ProofFixture}; #[cfg(not(feature = "no-entrypoint"))] use solana_program::entrypoint; @@ -29,7 +29,12 @@ pub fn process_instruction( assert!(result.is_ok()); // Make sure that we're verifying a fibonacci program. - assert_eq!(fixture.vkey_hash(), FIBONACCI_VKEY_HASH); + assert_eq!(&fixture.vkey_hash(), FIBONACCI_VKEY_HASH); + + if let Some(sp1_public_inputs) = &fixture.sp1_public_inputs { + let digest = hash_public_inputs(sp1_public_inputs); + assert_eq!(digest, fixture.commited_values_digest()); + } Ok(()) } diff --git a/example/script/src/main.rs b/example/script/src/main.rs index 92fc999..170db29 100644 --- a/example/script/src/main.rs +++ b/example/script/src/main.rs @@ -83,7 +83,7 @@ async fn main() { // Load the proof from the file, and convert it to a fixture. let sp1_proof_with_public_values = SP1ProofWithPublicValues::load(&proof_file).unwrap(); - let fixture = SP1ProofFixture::from(sp1_proof_with_public_values); + let fixture = SP1ProofFixture::from_sp1(sp1_proof_with_public_values, true); let fixture_file = "../../proof-fixtures/fibonacci_fixture.bin"; fixture.save(&fixture_file).unwrap(); diff --git a/proof-fixtures/fibonacci_fixture.bin b/proof-fixtures/fibonacci_fixture.bin index 24c536b7b38a7e200e9b346eb5ace3ccb4064f8c..053129d556460ec61608587d81c2a8c76edca281 100644 GIT binary patch delta 25 fcmX@ibcJbyGov6Q4+8^(2oUE=F)&=_W?%pSM9Bmv delta 7 Ocmcb@beL&_Ga~>DO#+Yr diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index 73f8e89..455f964 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -13,11 +13,11 @@ categories = ["cryptography"] [dependencies] borsh.workspace = true num-bigint.workspace = true +sha2.workspace = true ark-bn254 = "0.4.0" ark-serialize = "0.4.2" ark-ff = "0.4.2" -sha2 = { version = "0.10.8"} # groth16-solana = { git = "https://github.com/sp1-patches/groth16-solana", branch = "patch-v0.0.3" } groth16-solana = { path = "../../groth16-solana-patch" } thiserror = "1.0.63" diff --git a/verifier/src/fixture.rs b/verifier/src/fixture.rs index 7f1234a..9d32d71 100644 --- a/verifier/src/fixture.rs +++ b/verifier/src/fixture.rs @@ -6,7 +6,6 @@ use std::{ use crate::{verify_proof_raw, Error}; use borsh::{BorshDeserialize, BorshSerialize}; -use num_bigint::BigUint; use sha2::{Digest, Sha256}; /// The necessary information for a solana program to verify an SP1 Groth16 proof. @@ -18,6 +17,8 @@ pub struct SP1ProofFixture { pub public_inputs: [u8; 63], /// The first 4 bytes of the Groth16 vkey hash. pub groth16_vkey_hash: [u8; 4], + /// The public inputs of the underlying SP1 program. + pub sp1_public_inputs: Option>, } impl SP1ProofFixture { @@ -40,17 +41,8 @@ impl SP1ProofFixture { } /// Retrieves the SP1 commited values digest from the public inputs. - pub fn commited_values_digest(&self) -> String { - // The committed values digest is the second half of the public inputs - let digest_bytes = &self.public_inputs[31..63]; - - // Convert the digest bytes to a BigUint - let digest_biguint = BigUint::from_bytes_be(digest_bytes); - - // Convert the BigUint to a decimal string - let digest_string = digest_biguint.to_str_radix(10); - - digest_string + pub fn commited_values_digest(&self) -> [u8; 32] { + self.public_inputs[31..63].try_into().unwrap() } /// Retrieves the SP1 vkey hash from the public inputs. @@ -67,6 +59,16 @@ impl SP1ProofFixture { } } +/// Hashes the public inputs in order to match the groth16 verifier's format +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() +} + /// Verify a proof using a [`SP1ProofFixture`]. /// /// Checks the Groth16 vkey hash in the fixture against the provided vk. @@ -92,8 +94,11 @@ mod sp1_serialize { use super::SP1ProofFixture; /// Convert a SP1ProofWithPublicValues to a SP1ProofFixture. - impl From for SP1ProofFixture { - fn from(sp1_proof_with_public_values: SP1ProofWithPublicValues) -> Self { + impl SP1ProofFixture { + pub fn from_sp1( + sp1_proof_with_public_values: SP1ProofWithPublicValues, + use_public_values: bool, + ) -> Self { let proof = sp1_proof_with_public_values .proof .try_as_groth_16() @@ -101,20 +106,28 @@ mod sp1_serialize { let raw_proof = hex::decode(proof.raw_proof).unwrap(); - // Convert public inputs to byte representations. + // Convert public inputs and vkey hash to bytes. let vkey_hash = BigUint::from_str_radix(&proof.public_inputs[0], 10) .unwrap() .to_bytes_be(); + let committed_values_digest = BigUint::from_str_radix(&proof.public_inputs[1], 10) .unwrap() .to_bytes_be(); let public_inputs = [vkey_hash.to_vec(), committed_values_digest.to_vec()].concat(); + let raw_public_values = if use_public_values { + Some(sp1_proof_with_public_values.public_values.to_vec()) // TODO: get rid of this clone + } else { + None + }; + SP1ProofFixture { proof: raw_proof[..256].try_into().unwrap(), public_inputs: public_inputs.try_into().unwrap(), groth16_vkey_hash: proof.groth16_vkey_hash[..4].try_into().unwrap(), + sp1_public_inputs: raw_public_values, } } } @@ -122,9 +135,10 @@ mod sp1_serialize { #[cfg(feature = "sp1-serialize")] #[test] fn test_public_inputs() { - // Read the serialized SP1ProofWithPublicValues from the file. - + use crate::hash_public_inputs; use std::str::FromStr; + + // Read the serialized SP1ProofWithPublicValues from the 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(); @@ -135,23 +149,25 @@ mod sp1_serialize { .try_as_groth_16() .unwrap(); + // Convert vkey_hash from base 10 to hex using the hex crate. let vkey_hash = &groth16_proof.public_inputs[0]; - // Convert vkey_hash from base 10 to hex using the hex crate let vkey_hash_biguint = BigUint::from_str(vkey_hash).unwrap(); let mut vkey_hash_bytes = vec![0]; vkey_hash_bytes.extend_from_slice(&vkey_hash_biguint.to_bytes_be()); let vkey_hash_hex = hex::encode(vkey_hash_bytes); - let commited_values_digest = &groth16_proof.public_inputs[1]; + // let commited_values_digest: &String = &groth16_proof.public_inputs[1]; // Convert the SP1ProofWithPublicValues to a SP1ProofFixture. - let fixture = SP1ProofFixture::from(sp1_proof_with_public_values); + let fixture = SP1ProofFixture::from_sp1(sp1_proof_with_public_values, false); // Verify the public inputs. assert_eq!( - fixture.commited_values_digest(), - commited_values_digest.to_string() + &fixture.commited_values_digest(), + &hash_public_inputs(fixture.sp1_public_inputs.as_ref().unwrap()) ); + + // Verify the vkey hash. assert_eq!(fixture.vkey_hash(), vkey_hash_hex); } } diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index 50dbaff..8b59b0d 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -6,9 +6,12 @@ use groth16_solana::groth16::Groth16Verifyingkey; use thiserror::Error; mod fixture; +pub use fixture::hash_public_inputs; pub use fixture::verify_proof_fixture; pub use fixture::SP1ProofFixture; +pub const GROTH16_VK_BYTES: &[u8] = include_bytes!("../vk/groth16_vk.bin"); + /// 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 @@ -26,8 +29,6 @@ fn convert_endianness( reversed } -pub const GROTH16_VK_BYTES: &[u8] = include_bytes!("../vk/groth16_vk.bin"); - #[derive(Error, Debug)] pub enum Error { #[error("G1 compression error")]