From fadaba9eb6c348c3fdc523e649dfd2b59467cbfa Mon Sep 17 00:00:00 2001 From: Konstantin Fastov Date: Wed, 27 Nov 2024 21:54:47 +0700 Subject: [PATCH] feat: draft impl of semaphore verifier contract --- contracts/verifier/src/datatypes.rs | 36 ++++++ contracts/verifier/src/interface.rs | 35 ++++++ contracts/verifier/src/lib.rs | 167 +++++++++++++++++++++++++++- 3 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 contracts/verifier/src/datatypes.rs create mode 100644 contracts/verifier/src/interface.rs diff --git a/contracts/verifier/src/datatypes.rs b/contracts/verifier/src/datatypes.rs new file mode 100644 index 0000000..7791f95 --- /dev/null +++ b/contracts/verifier/src/datatypes.rs @@ -0,0 +1,36 @@ +use soroban_sdk::{contracttype, contracterror, U256, BytesN}; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum VerifierError { + InvalidProofFormat = 1, + InvalidPublicSignals = 2, + InvalidMerkleTreeDepth = 3, + VerificationFailed = 4, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Proof { + pub p_a: BytesN<96>, // G1 point (2 * 48 bytes) + pub p_b: BytesN<192>, // G2 point (4 * 48 bytes) + pub p_c: BytesN<96>, // G1 point (2 * 48 bytes) +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PublicSignals { + pub root: BytesN<32>, // Merkle tree root + pub nullifier_hash: BytesN<32>, // Nullifier hash + pub signal_hash: BytesN<32>, // Signal hash + pub external_nullifier_hash: BytesN<32>, // External nullifier hash +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DataKey { + VerificationKey(u32), // maps tree_depth -> verification key + AllowedTreeDepths, // Store allowed Merkle tree depths + VerifierConfig(u32), // Any additional config needed per tree depth +} \ No newline at end of file diff --git a/contracts/verifier/src/interface.rs b/contracts/verifier/src/interface.rs new file mode 100644 index 0000000..be0e5ce --- /dev/null +++ b/contracts/verifier/src/interface.rs @@ -0,0 +1,35 @@ +use soroban_sdk::{contracttype, contracterror, U256, BytesN}; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum VerifierError { + InvalidProofFormat = 1, + InvalidPublicSignals = 2, + InvalidMerkleTreeDepth = 3, + VerificationFailed = 4, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Proof { + pub p_a: Vec, // Point A coordinates [x, y] + pub p_b: Vec>, // Point B coordinates [[x1, x2], [y1, y2]] + pub p_c: Vec, // Point C coordinates [x, y] +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PublicSignals { + pub root: BytesN<32>, // Merkle tree root + pub nullifier_hash: BytesN<32>, // Nullifier hash + pub signal_hash: BytesN<32>, // Signal hash + pub external_nullifier_hash: BytesN<32>, // External nullifier hash +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DataKey { + AllowedTreeDepths, // Store allowed Merkle tree depths + VerifierConfig(u32), // Any additional config needed per tree depth +} \ No newline at end of file diff --git a/contracts/verifier/src/lib.rs b/contracts/verifier/src/lib.rs index fab515d..90c1200 100644 --- a/contracts/verifier/src/lib.rs +++ b/contracts/verifier/src/lib.rs @@ -1,14 +1,169 @@ -pub fn example() -> &'static str { - "Hello, Verifier!" +#![no_std] +use soroban_sdk::{contract, contractimpl, BytesN, Env, U256, Vec}; +use crate::datatypes::{Proof, PublicSignals, VerifierError}; + +use soroban_sdk::crypto::bls12_381::{ + Bls12_381, Fr, G1Affine, G2Affine +}; + +pub const DST_G1: &[u8] = b"BLS12381G1_XMD:SHA-256_SSWU_RO_"; + +// Constants for verification key points +pub const ALPHA_X: &[u8; 32] = /* bytes for 16428432848801857252194528405604668803277877773566238944394625302971855135431 */; +pub const ALPHA_Y: &[u8; 32] = /* bytes for 16846502678714586896801519656441059708016666274385668027902869494772365009666 */; +// TODO: add other constants for beta, gamma points + +// Constant for scalar field size (TODO) +pub const SCALAR_FIELD_SIZE: U256 = U256::from_be_bytes([/* r value in bytes */]); + +#[contract] +pub struct SemaphoreVerifier; + +#[contractimpl] +impl SemaphoreVerifier { + /// Verifies a zero-knowledge proof for Semaphore + /// # Arguments + /// * `env` - The Soroban environment + /// * `proof` - The zero-knowledge proof components + /// * `public_signals` - The public signals for verification + /// * `merkle_tree_depth` - Depth of the Merkle tree + pub fn verify_proof( + env: &Env, + proof: Proof, + public_signals: PublicSignals, + merkle_tree_depth: u32, + ) -> Result { + // Validate public inputs + Self::validate_field_element(env, &external_nullifier)?; + Self::validate_field_element(env, &nullifier_hash)?; + Self::validate_field_element(env, &signal_hash)?; + Self::validate_field_element(env, &root)?; + + // Get BLS12-381 context + let bls = env.crypto().bls12_381(); + + // Convert proof components to G1/G2 points + let p_a = G1Affine::from_bytes(proof.p_a) + .map_err(|_| VerifierError::InvalidProofPoint)?; + let p_b = G2Affine::from_bytes(proof.p_b) + .map_err(|_| VerifierError::InvalidProofPoint)?; + let p_c = G1Affine::from_bytes(proof.p_c) + .map_err(|_| VerifierError::InvalidProofPoint)?; + + // Convert public signals to Fr elements + let external_nullifier = Fr::from_bytes(public_signals.external_nullifier) + .map_err(|_| VerifierError::InvalidPublicSignal)?; + let nullifier_hash = Fr::from_bytes(public_signals.nullifier_hash) + .map_err(|_| VerifierError::InvalidPublicSignal)?; + let signal_hash = Fr::from_bytes(public_signals.signal_hash) + .map_err(|_| VerifierError::InvalidPublicSignal)?; + let root = Fr::from_bytes(public_signals.root) + .map_err(|_| VerifierError::InvalidPublicSignal)?; + + // Create vectors for pairing check + let mut g1_points = Vec::new(env); + let mut g2_points = Vec::new(env); + + // Convert public signals to array of Fr elements + let public_signals_arr = [ + external_nullifier, + nullifier_hash, + signal_hash, + root + ]; + + // Get verification key points based on merkle_tree_depth + let vk_points = Self::get_verification_key_points(env, merkle_tree_depth); + + // Compute the linear combination (vk_x) + let vk_x = Self::compute_linear_combination(env, &vk_points, &public_signals_arr); + + // Set up the pairing check inputs + // -A (negative of proof.p_a) + g1_points.push_back(Self::negate(env, &p_a)); + g2_points.push_back(p_b); + + // alpha + g1_points.push_back(alpha); + g2_points.push_back(beta); + + // vk_x (the linear combination we computed) + g1_points.push_back(vk_x); + g2_points.push_back(gamma); + + // C (proof.p_c) + g1_points.push_back(p_c); + g2_points.push_back(delta); + + // Perform the pairing check + Ok(bls.pairing_check(g1_points, g2_points)) + } + + /// Helper function to convert bytes to field elements + fn hash_to_field(env: &Env, input: &BytesN<32>) -> Fr { + let bls = env.crypto().bls12_381(); + let dst: &[u8; 32] = DST_G1.try_into().expect("DST_G1 should be 32 bytes long"); + let dst_bytesn = BytesN::from_array(env, dst); + let input_bytes = input.clone().into(); + let dst_bytes = dst_bytesn.into(); + let point = bls.hash_to_g1(&input_bytes, &dst_bytes); + let point_bytes = point.to_bytes(); + let point_bytes_array = point_bytes.to_array(); + let bytes32_array: [u8; 32] = point_bytes_array[0..32].try_into().unwrap(); + + let bytes32 = BytesN::from_array(env, &bytes32_array); + Fr::from_bytes(bytes32) + } + + /// Helper function to perform scalar multiplication + fn scalar_mul(env: &Env, point: &G1Affine, scalar: &Fr) -> G1Affine { + env.crypto().bls12_381().g1_mul(point, scalar) + } + + /// Helper function to perform point addition + fn point_add(env: &Env, point1: &G1Affine, point2: &G1Affine) -> G1Affine { + env.crypto().bls12_381().g1_add(point1, point2) + } + + fn validate_field_element(env: &Env, element: &Fr) -> Result<(), VerifierError> { + let bytes = element.to_bytes(); + let value = U256::from_be_bytes(env, &bytes.to_array()); + if value >= SCALAR_FIELD_SIZE { + return Err(VerifierError::InvalidFieldElement); + } + Ok(()) + } + + fn compute_linear_combination( + env: &Env, + vk_points: &[G1Affine], + public_signals: &[Fr], + ) -> G1Affine { + let mut result = vk_points[0].clone(); + + for i in 0..public_signals.len() { + let term = Self::scalar_mul(env, &vk_points[i + 1], &public_signals[i]); + result = Self::point_add(env, &result, &term); + } + + result + } } #[cfg(test)] -mod tests { +mod test { use super::*; #[test] - fn test_example_verifier() { - let result = example(); - assert_eq!(result, "Hello, Verifier!"); + fn test_verify_valid_proof() { + // Test implementation + } + + #[test] + fn test_verify_invalid_proof() { + // Test implementation } } + +mod datatypes; +mod interface; \ No newline at end of file