Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: draft impl of semaphore verifier contract #24

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions contracts/verifier/src/datatypes.rs
Original file line number Diff line number Diff line change
@@ -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
}
35 changes: 35 additions & 0 deletions contracts/verifier/src/interface.rs
Original file line number Diff line number Diff line change
@@ -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<U256>, // Point A coordinates [x, y]
pub p_b: Vec<Vec<U256>>, // Point B coordinates [[x1, x2], [y1, y2]]
pub p_c: Vec<U256>, // 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
}
167 changes: 161 additions & 6 deletions contracts/verifier/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<bool, VerifierError> {
// 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;