Skip to content

Commit

Permalink
feat: test rust client for gnark prover (#484)
Browse files Browse the repository at this point in the history
* Test rust client for gnark prover

* Update README with Git LFS and submodules instructions

* Refactor gnark testing and remove unused constants

Removed unused constant CONTENT_TYPE and moved it inline in the test module. Refactored the prove_inclusion test to iterate over several UTXO counts for better coverage. Unnecessary logging and imports were also removed for cleaner code.

* Add newline at end of 'prove' module
  • Loading branch information
sergeytimoshin authored Feb 27, 2024
1 parent 44a1b32 commit 38c0c7a
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 12 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
uses: actions/checkout@v4
with:
lfs: true
submodules: true

- name: Cache .local directory
uses: buildjet/cache@v3
Expand All @@ -45,6 +46,15 @@ jobs:
./scripts/install.sh
source ./scripts/devenv.sh
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version-file: './gnark-prover/go.mod'
- name: Build
run: |
cd gnark-prover
go build
- name: Build and test
run: |
source ./scripts/devenv.sh
Expand Down
49 changes: 47 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ Developers can use Light to build applications such as
- on-chain games with encrypted game state
- zk-identity



## ZK Anchor

ZK Anchor is what we call the collection of developer tools for writing Private Solana Programs (PSPs).
Expand All @@ -34,6 +32,20 @@ To get started, a good PSP reference implementation is available [here](https://

Otherwise, to work with this Monorepo, read below:

## Git Large File Storage (LFS)
This project uses Git LFS to manage and version large files. Before you can clone the project and fetch all the necessary data, you need to install and configure Git LFS on your machine.

If you already have Git installed, run the following command:
```
git lfs install
```

To verify that Git LFS is properly configured, use:
```
git lfs env
```

The output should show that Git LFS is installed and configured correctly. After setting up Git LFS, you can proceed to clone the project.

## Development environment

Expand Down Expand Up @@ -96,6 +108,10 @@ If you still want to setup dependencies manually, these are the requirements:
* [NodeJS](https://nodejs.org/) [(16.16 LTS)](https://nodejs.org/en/blog/release/v16.16.0)
* [Anchor](https://www.anchor-lang.com/) [(0.26.0)](https://crates.io/crates/anchor-cli/0.26.0)

## Initializing Submodules
Before building, make sure to initialize the submodules:
```git submodule update --init```

## Building

To build the project, use the following commands:
Expand Down
8 changes: 8 additions & 0 deletions circuit-lib/circuitlib-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
1 change: 1 addition & 0 deletions circuit-lib/circuitlib-rs/scripts/prover.sh
9 changes: 9 additions & 0 deletions circuit-lib/circuitlib-rs/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use env_logger::Builder;
use log::LevelFilter;

pub fn change_endianness(bytes: &[u8]) -> Vec<u8> {
let mut vec = Vec::new();
for b in bytes.chunks(32) {
Expand All @@ -13,3 +16,9 @@ pub fn convert_endianness_128(bytes: &[u8]) -> Vec<u8> {
.flat_map(|b| b.iter().copied().rev().collect::<Vec<u8>>())
.collect::<Vec<u8>>()
}

pub fn init_logger() {
let _ = Builder::new()
.filter_module("circuitlib_rs", LevelFilter::Info)
.try_init();
}
1 change: 1 addition & 0 deletions circuit-lib/circuitlib-rs/src/merkle_proof_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HashMap<String, Inputs>> for ProofInputs<'a> {
type Error = std::io::Error;

Expand Down
3 changes: 3 additions & 0 deletions circuit-lib/circuitlib-rs/tests/gnark/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub const SERVER_ADDRESS: &str = "http://localhost:3001";
pub const HEALTH_CHECK: &str = "/health";
pub const PROVE: &str = "/prove";
112 changes: 112 additions & 0 deletions circuit-lib/circuitlib-rs/tests/gnark/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
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<String>,
leaf: Vec<String>,
inPathIndices: Vec<u32>,
inPathElements: Vec<Vec<String>>,
}

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) {
Command::new("sh")
.arg("-c")
.arg("killall light-prover")
.spawn()
.unwrap();
gnark.kill().unwrap();
}

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<String> {
vec![format!("0x{}", element.to_str_radix(16)); number_of_utxos]
}

pub fn create_vec_of_u32(number_of_utxos: usize, element: &BigInt) -> Vec<u32> {
vec![element.to_u32().unwrap(); number_of_utxos]
}

pub fn create_vec_of_vec_of_string(
number_of_utxos: usize,
elements: &[BigInt],
) -> Vec<Vec<String>> {
let vec: Vec<String> = 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"),
}
}
3 changes: 3 additions & 0 deletions circuit-lib/circuitlib-rs/tests/gnark/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod constants;
mod helpers;
mod prove;
27 changes: 27 additions & 0 deletions circuit-lib/circuitlib-rs/tests/gnark/prove.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use circuitlib_rs::helpers::init_logger;

use crate::{
constants::{PROVE, SERVER_ADDRESS},
helpers::{health_check, kill_gnark_server, prepare_inputs, spawn_gnark_server},
};

#[tokio::test]
async fn prove_inclusion() {
init_logger();
let mut gnark = spawn_gnark_server();
health_check().await;
let client = reqwest::Client::new();
for number_of_utxos in &[1, 2, 3, 4, 8] {
let inputs = prepare_inputs(*number_of_utxos as usize);
let response_result = client
.post(&format!("{}{}", SERVER_ADDRESS, PROVE))
.header("Content-Type", "text/plain; charset=utf-8")
.body(inputs)
.send()
.await
.expect("Failed to execute request.");
assert!(response_result.status().is_success());
}

kill_gnark_server(&mut gnark);
}
9 changes: 1 addition & 8 deletions circuit-lib/circuitlib-rs/tests/merkle/main.rs
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down Expand Up @@ -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();
}

0 comments on commit 38c0c7a

Please sign in to comment.