From a970a0a99d171acc6dcd520c8be790f4b7f0083b Mon Sep 17 00:00:00 2001 From: Sergey Timoshin Date: Fri, 23 Feb 2024 19:34:47 +0000 Subject: [PATCH] Test rust client for gnark prover --- Cargo.lock | 49 +++++++- .../src/merkle-tree/MerkleTreeProof.circom | 8 +- circuit-lib/circuitlib-rs/Cargo.toml | 8 ++ circuit-lib/circuitlib-rs/scripts/prover.sh | 1 + circuit-lib/circuitlib-rs/src/helpers.rs | 9 ++ .../circuitlib-rs/src/merkle_proof_inputs.rs | 1 + .../circuitlib-rs/tests/gnark/constants.rs | 5 + .../circuitlib-rs/tests/gnark/helpers.rs | 114 ++++++++++++++++++ circuit-lib/circuitlib-rs/tests/gnark/main.rs | 11 ++ .../circuitlib-rs/tests/gnark/prove.rs | 26 ++++ .../circuitlib-rs/tests/merkle/main.rs | 9 +- 11 files changed, 227 insertions(+), 14 deletions(-) create mode 120000 circuit-lib/circuitlib-rs/scripts/prover.sh create mode 100644 circuit-lib/circuitlib-rs/tests/gnark/constants.rs create mode 100644 circuit-lib/circuitlib-rs/tests/gnark/helpers.rs create mode 100644 circuit-lib/circuitlib-rs/tests/gnark/main.rs create mode 100644 circuit-lib/circuitlib-rs/tests/gnark/prove.rs diff --git a/Cargo.lock b/Cargo.lock index f9fffed0a5..8eea68308e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1531,15 +1531,21 @@ dependencies = [ "ark-serialize 0.4.2", "ark-std 0.4.0", "color-eyre", + "duct", "env_logger 0.10.2", "groth16-solana 0.0.2 (git+https://github.com/Lightprotocol/groth16-solana.git)", "light-hasher", "light-merkle-tree-reference", "log", "num-bigint 0.4.4", + "num-traits", "once_cell", + "reqwest", + "serde", + "serde_json", "solana-program", "thiserror", + "tokio", "zeroize", ] @@ -2166,6 +2172,18 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "duct" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c" +dependencies = [ + "libc", + "once_cell", + "os_pipe", + "shared_child", +] + [[package]] name = "eager" version = "0.1.0" @@ -3944,6 +3962,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "os_pipe" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "os_str_bytes" version = "6.5.1" @@ -4592,9 +4620,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "async-compression", "base64 0.21.4", @@ -4621,6 +4649,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -5074,6 +5103,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared_child" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "shell-words" version = "1.1.0" @@ -6673,6 +6712,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" diff --git a/circuit-lib/circuit-lib.circom/src/merkle-tree/MerkleTreeProof.circom b/circuit-lib/circuit-lib.circom/src/merkle-tree/MerkleTreeProof.circom index a018e34684..cd1783f321 100644 --- a/circuit-lib/circuit-lib.circom/src/merkle-tree/MerkleTreeProof.circom +++ b/circuit-lib/circuit-lib.circom/src/merkle-tree/MerkleTreeProof.circom @@ -7,15 +7,15 @@ include "bitify.circom"; template MerkleTreeProof(levels, numberOfUTXOs) { signal input root[numberOfUTXOs]; signal input leaf[numberOfUTXOs]; - signal input inPathElements[numberOfUTXOs][levels]; - signal input inPathIndices[numberOfUTXOs]; + signal input in_path_elements[numberOfUTXOs][levels]; + signal input in_path_indices[numberOfUTXOs]; component inTree[numberOfUTXOs]; for (var i = 0; i < numberOfUTXOs; i++) { inTree[i] = MerkleProof(levels); inTree[i].leaf <== leaf[i]; - inTree[i].pathIndices <== inPathIndices[i]; - inTree[i].pathElements <== inPathElements[i]; + inTree[i].pathIndices <== in_path_indices[i]; + inTree[i].pathElements <== in_path_elements[i]; inTree[i].root === root[i]; } } diff --git a/circuit-lib/circuitlib-rs/Cargo.toml b/circuit-lib/circuitlib-rs/Cargo.toml index a32209cd9d..fa8d49d2f0 100644 --- a/circuit-lib/circuitlib-rs/Cargo.toml +++ b/circuit-lib/circuitlib-rs/Cargo.toml @@ -33,3 +33,11 @@ log = "0.4" env_logger = "0.10.2" # 1.3.0 required by package `aes-gcm-siv v0.10.3` zeroize = "=1.3.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.60" +num-traits = "0.2.18" + +[dev-dependencies] +tokio = { version = "1.36.0", features = ["rt", "macros"] } +reqwest = { version = "0.11.24", features = ["json", "rustls-tls"] } +duct = "0.13.7" \ No newline at end of file diff --git a/circuit-lib/circuitlib-rs/scripts/prover.sh b/circuit-lib/circuitlib-rs/scripts/prover.sh new file mode 120000 index 0000000000..fb24787cf4 --- /dev/null +++ b/circuit-lib/circuitlib-rs/scripts/prover.sh @@ -0,0 +1 @@ +../../circuit-lib.js/scripts/prover.sh \ No newline at end of file diff --git a/circuit-lib/circuitlib-rs/src/helpers.rs b/circuit-lib/circuitlib-rs/src/helpers.rs index 7b085084de..1c4f85f35f 100644 --- a/circuit-lib/circuitlib-rs/src/helpers.rs +++ b/circuit-lib/circuitlib-rs/src/helpers.rs @@ -1,3 +1,6 @@ +use env_logger::Builder; +use log::LevelFilter; + pub fn change_endianness(bytes: &[u8]) -> Vec { let mut vec = Vec::new(); for b in bytes.chunks(32) { @@ -13,3 +16,9 @@ pub fn convert_endianness_128(bytes: &[u8]) -> Vec { .flat_map(|b| b.iter().copied().rev().collect::>()) .collect::>() } + +pub fn init_logger() { + let _ = Builder::new() + .filter_module("circuitlib_rs", LevelFilter::Info) + .try_init(); +} diff --git a/circuit-lib/circuitlib-rs/src/merkle_proof_inputs.rs b/circuit-lib/circuitlib-rs/src/merkle_proof_inputs.rs index 339341d2c3..0ed41cc437 100644 --- a/circuit-lib/circuitlib-rs/src/merkle_proof_inputs.rs +++ b/circuit-lib/circuitlib-rs/src/merkle_proof_inputs.rs @@ -64,6 +64,7 @@ pub fn public_inputs(merkle_proof_inputs: &[MerkleTreeProofInput]) -> Vec<[u8; 3 } pub struct ProofInputs<'a>(pub &'a [MerkleTreeProofInput]); + impl<'a> TryInto> for ProofInputs<'a> { type Error = std::io::Error; diff --git a/circuit-lib/circuitlib-rs/tests/gnark/constants.rs b/circuit-lib/circuitlib-rs/tests/gnark/constants.rs new file mode 100644 index 0000000000..cd6843844c --- /dev/null +++ b/circuit-lib/circuitlib-rs/tests/gnark/constants.rs @@ -0,0 +1,5 @@ +pub const SERVER_ADDRESS: &str = "http://localhost:3001"; +pub const HEALTH_CHECK: &str = "/health"; +pub const PROVE: &str = "/prove"; + +const CONTENT_TYPE: &str = "text/plain; charset=utf-8"; diff --git a/circuit-lib/circuitlib-rs/tests/gnark/helpers.rs b/circuit-lib/circuitlib-rs/tests/gnark/helpers.rs new file mode 100644 index 0000000000..26790f3fd5 --- /dev/null +++ b/circuit-lib/circuitlib-rs/tests/gnark/helpers.rs @@ -0,0 +1,114 @@ +use std::{ + process::{Child, Command}, + thread, + time::Duration, +}; + +use circuitlib_rs::{init_merkle_tree::merkle_tree_inputs, merkle_proof_inputs::MerkleTreeInfo}; +use num_bigint::BigInt; +use num_traits::ToPrimitive; +use serde::Serialize; +use serde_json::json; + +use crate::constants::{HEALTH_CHECK, SERVER_ADDRESS}; + +#[allow(non_snake_case)] +#[derive(Serialize)] +pub struct JsonStruct { + root: Vec, + leaf: Vec, + inPathIndices: Vec, + inPathElements: Vec>, +} + +impl JsonStruct { + fn new(number_of_utxos: usize) -> Self { + let merkle_inputs = merkle_tree_inputs(MerkleTreeInfo::H26); + let roots = create_vec_of_string(number_of_utxos, &merkle_inputs.root); + let leafs = create_vec_of_string(number_of_utxos, &merkle_inputs.leaf); + let in_path_indices = create_vec_of_u32(number_of_utxos, &merkle_inputs.in_path_indices); + let in_path_elements = + create_vec_of_vec_of_string(number_of_utxos, &merkle_inputs.in_path_elements); + Self { + root: roots, + leaf: leafs, + inPathIndices: in_path_indices, + inPathElements: in_path_elements, + } + } +} +pub fn prepare_inputs(number_of_utxos: usize) -> String { + let json_struct = JsonStruct::new(number_of_utxos); + create_json_from_struct(&json_struct) +} + +pub fn spawn_gnark_server() -> Child { + let server_process = Command::new("sh") + .arg("-c") + .arg("scripts/prover.sh") + .spawn() + .expect("Failed to start server process"); + + // Wait for the server to launch before proceeding. + thread::sleep(Duration::from_secs(5)); + + server_process +} + +pub fn kill_gnark_server(gnark: &mut Child) { + println!("kill gnark!"); + Command::new("sh") + .arg("-c") + .arg("killall light-prover") + .spawn() + .unwrap(); + gnark.kill().unwrap(); + println!("gnark killed!"); +} + +pub async fn health_check() { + const MAX_RETRIES: usize = 20; + const TIMEOUT: usize = 5; + + let client = reqwest::Client::new(); + + for _ in 0..MAX_RETRIES { + match client + .get(&format!("{}{}", SERVER_ADDRESS, HEALTH_CHECK)) + .send() + .await + { + Ok(_) => break, + Err(_) => { + tokio::time::sleep(Duration::from_secs(TIMEOUT as u64)).await; + } + } + } +} + +pub fn create_vec_of_string(number_of_utxos: usize, element: &BigInt) -> Vec { + vec![format!("0x{}", element.to_str_radix(16)); number_of_utxos] +} + +pub fn create_vec_of_u32(number_of_utxos: usize, element: &BigInt) -> Vec { + vec![element.to_u32().unwrap(); number_of_utxos] +} + +pub fn create_vec_of_vec_of_string( + number_of_utxos: usize, + elements: &[BigInt], +) -> Vec> { + let vec: Vec = elements + .iter() + .map(|e| format!("0x{}", e.to_str_radix(16))) + .collect(); + vec![vec; number_of_utxos] +} + +pub fn create_json_from_struct(json_struct: &JsonStruct) -> String { + let json = json!(json_struct); + match serde_json::to_string_pretty(&json) { + Ok(json) => json, + Err(_) => panic!("Merkle tree data invalid"), + } +} diff --git a/circuit-lib/circuitlib-rs/tests/gnark/main.rs b/circuit-lib/circuitlib-rs/tests/gnark/main.rs new file mode 100644 index 0000000000..29e6dfd4fa --- /dev/null +++ b/circuit-lib/circuitlib-rs/tests/gnark/main.rs @@ -0,0 +1,11 @@ +mod constants; +mod helpers; +mod prove; + +use std::{ + process::{Child, Command}, + thread, + time::Duration, +}; + +use circuitlib_rs::helpers::init_logger; diff --git a/circuit-lib/circuitlib-rs/tests/gnark/prove.rs b/circuit-lib/circuitlib-rs/tests/gnark/prove.rs new file mode 100644 index 0000000000..8f88dcfa34 --- /dev/null +++ b/circuit-lib/circuitlib-rs/tests/gnark/prove.rs @@ -0,0 +1,26 @@ +use circuitlib_rs::helpers::init_logger; +use reqwest::header::CONTENT_TYPE; + +use crate::{ + constants::{PROVE, SERVER_ADDRESS}, + helpers::{health_check, kill_gnark_server, prepare_inputs, spawn_gnark_server}, +}; + +#[tokio::test] +async fn prove_inclusion() { + let number_of_utxos = 1; + init_logger(); + let mut gnark = spawn_gnark_server(); + health_check().await; + let inputs = prepare_inputs(number_of_utxos); + let client = reqwest::Client::new(); + let response_result = client + .post(&format!("{}{}", SERVER_ADDRESS, PROVE)) + .header("Content-Type", CONTENT_TYPE) + .body(inputs) + .send() + .await + .expect("Failed to execute request."); + assert!(response_result.status().is_success()); + kill_gnark_server(&mut gnark); +} diff --git a/circuit-lib/circuitlib-rs/tests/merkle/main.rs b/circuit-lib/circuitlib-rs/tests/merkle/main.rs index e662306f7f..760862bfc8 100644 --- a/circuit-lib/circuitlib-rs/tests/merkle/main.rs +++ b/circuit-lib/circuitlib-rs/tests/merkle/main.rs @@ -1,11 +1,10 @@ use circuitlib_rs::{ groth16_solana_verifier::{groth16_solana_verify, merkle_inclusion_proof}, + helpers::init_logger, init_merkle_tree::merkle_tree_inputs, merkle_proof_inputs::{MerkleTreeInfo, MerkleTreeProofInput}, verifying_keys::vk, }; -use env_logger::Builder; -use log::LevelFilter; macro_rules! test_and_prove { ($fn_name:ident, $mt_height:expr, $nr_inputs:expr) => { @@ -33,9 +32,3 @@ test_and_prove!(test_and_prove_26_2, MerkleTreeInfo::H26, 2); test_and_prove!(test_and_prove_26_3, MerkleTreeInfo::H26, 3); test_and_prove!(test_and_prove_26_4, MerkleTreeInfo::H26, 4); test_and_prove!(test_and_prove_26_8, MerkleTreeInfo::H26, 8); - -fn init_logger() { - let _ = Builder::new() - .filter_module("circuitlib_rs", LevelFilter::Info) - .try_init(); -}