diff --git a/Cargo.toml b/Cargo.toml index eb72ebb2..b384feab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "expander-rs" version = "0.1.0" edition = "2021" -default-run = "expander-rs" # default +default-run = "expander-rs" # default [dependencies] arith = { path = "arith" } @@ -11,13 +11,16 @@ clap = { version = "4.1", features = ["derive"] } log = "0.4" rand = "0.8.5" sha2 = "0.10.8" - -halo2curves = { git = "https://github.com/zhenfeizhang/halo2curves", default-features = false, features = [ "bits" ] } +halo2curves = { git = "https://github.com/PolyhedraZK/halo2curves", default-features = false, features = [ + "bits", +] } +# for the server +warp = "0.3.7" +tokio = { version = "1.38.0", features = ["full"] } +bytes = "1.6.0" [workspace] -members = [ - "arith" -] +members = ["arith"] [[bin]] name = "expander-exec" diff --git a/arith/Cargo.toml b/arith/Cargo.toml index e8c971b6..c9a16b9b 100644 --- a/arith/Cargo.toml +++ b/arith/Cargo.toml @@ -9,6 +9,6 @@ log = "0.4" rand = "0.8.5" sha2 = "0.10.8" -halo2curves = { git = "https://github.com/zhenfeizhang/halo2curves", default-features = false, features = [ "bits" ] } +halo2curves = { git = "https://github.com/PolyhedraZK/halo2curves", default-features = false, features = [ "bits" ] } [features] \ No newline at end of file diff --git a/readme.md b/readme.md index e8bb5fcd..b95d0b31 100644 --- a/readme.md +++ b/readme.md @@ -57,6 +57,7 @@ Usage: ```sh RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- prove RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- verify +RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- serve ``` Example: @@ -64,6 +65,12 @@ Example: ```sh RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- prove ./data/compiler_out/circuit.txt ./data/compiler_out/witness.txt ./data/compiler_out/out.bin RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- verify ./data/compiler_out/circuit.txt ./data/compiler_out/witness.txt ./data/compiler_out/out.bin +RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- serve ./data/compiler_out/circuit.txt 127.0.0.1 3030 +``` + +To test the service started by `expander-exec serve`, you can use the following command: +```sh +python ./scripts/test_http.py # need "requests" package ``` ## How to contribute? diff --git a/scripts/test_http.py b/scripts/test_http.py new file mode 100644 index 00000000..7be95a24 --- /dev/null +++ b/scripts/test_http.py @@ -0,0 +1,45 @@ +import requests + + +if __name__ == '__main__': + # prove + with open('data/compiler_out/witness.txt', 'rb') as f: + witness = f.read() + url = 'http://127.0.0.1:3030' + prove_headers = { + 'Content-Type': 'application/octet-stream', + 'Content-Length': str(len(witness)), + } + response = requests.post(url+"/prove", headers=prove_headers, data=witness) + proof = response.content + print(response) + print("Proof generated successfully, length:", len(proof)) + with open('data/compiler_out/proof_http.bin', 'wb') as f: + f.write(proof) + + # verify + # add u64 length of witness and proof to the beginning of the file + witness_len = len(witness).to_bytes(8, byteorder='little') + proof_len = len(proof).to_bytes(8, byteorder='little') + verifier_input = witness_len + proof_len + witness + proof + verify_headers = { + 'Content-Type': 'application/octet-stream', + 'Content-Length': str(len(proof)), + } + response = requests.post(url+"/verify", headers=verify_headers, data=verifier_input) + print(response) + # check 200 + assert response.text == "success", f"Failed to verify proof: {response.text}" + print("Proof verified successfully") + + # try tempered proof + import random + # flip a random bit + random_byte_index = random.randint(0, len(proof) - 1) + random_bit_index = random.randint(0, 7) + tempered_proof = proof[:random_byte_index] + bytes([proof[random_byte_index] ^ (1 << random_bit_index)]) + proof[random_byte_index+1:] + tempered_input = witness_len + proof_len + witness + tempered_proof + response = requests.post(url+"/verify", headers=verify_headers, data=tempered_input) + # check 400 + assert response.text != "success", f"Failed to detect tempered proof: {response.text}" + print("Tempered proof detected successfully") diff --git a/src/circuit.rs b/src/circuit.rs index 415ea133..7832c2a6 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -210,9 +210,12 @@ fn read_f_u32_val(file_bytes: &[u8]) -> u32 { } impl Circuit { - pub fn load_witness(&mut self, filename: &str) { + pub fn load_witness_file(&mut self, filename: &str) { // note that, for data parallel, one should load multiple witnesses into different slot in the vectorized F let file_bytes = fs::read(filename).unwrap(); + self.load_witness_bytes(&file_bytes); + } + pub fn load_witness_bytes(&mut self, file_bytes: &[u8]) { log::trace!("witness file size: {} bytes", file_bytes.len()); log::trace!("expecting: {} bytes", 32 * (1 << self.log_input_size())); let mut cur = 0; diff --git a/src/exec.rs b/src/exec.rs index 6da61d38..3b35cd82 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,7 +1,12 @@ -use std::{fs, vec}; +use std::{ + fs, + sync::{Arc, Mutex}, + vec, +}; use arith::{Field, FieldSerde, VectorizedM31}; use expander_rs::{Circuit, Config, Proof, Prover, Verifier}; +use warp::Filter; type F = VectorizedM31; @@ -40,10 +45,12 @@ fn load_proof_and_claimed_v(bytes: &[u8]) -> (Proof, Vec) { (proof, claimed_v) } -fn main() { +#[tokio::main] +async fn main() { // examples: // expander-exec prove // expander-exec verify + // expander-exec serve let args = std::env::args().collect::>(); if args.len() < 4 { println!( @@ -52,17 +59,18 @@ fn main() { println!( "Usage: expander-exec verify " ); + println!("Usage: expander-exec serve "); return; } let command = &args[1]; let circuit_file = &args[2]; - let witness_file = &args[3]; - let output_file = &args[4]; match command.as_str() { "prove" => { + let witness_file = &args[3]; + let output_file = &args[4]; let config = Config::m31_config(); let mut circuit = Circuit::::load_circuit(circuit_file); - circuit.load_witness(witness_file); + circuit.load_witness_file(witness_file); circuit.evaluate(); let mut prover = Prover::new(&config); prover.prepare_mem(&circuit); @@ -71,14 +79,76 @@ fn main() { fs::write(output_file, bytes).expect("Unable to write proof to file."); } "verify" => { + let witness_file = &args[3]; + let output_file = &args[4]; let config = Config::m31_config(); let mut circuit = Circuit::::load_circuit(circuit_file); - circuit.load_witness(witness_file); + circuit.load_witness_file(witness_file); let bytes = fs::read(output_file).expect("Unable to read proof from file."); let (proof, claimed_v) = load_proof_and_claimed_v(&bytes); let verifier = Verifier::new(&config); assert!(verifier.verify(&circuit, &claimed_v, &proof)); - println!("success") + println!("success"); + } + "serve" => { + let host: [u8; 4] = args[3] + .split('.') + .map(|s| s.parse().unwrap()) + .collect::>() + .try_into() + .unwrap(); + let port = args[4].parse().unwrap(); + let config = Config::m31_config(); + let circuit = Circuit::::load_circuit(circuit_file); + let mut prover = Prover::new(&config); + prover.prepare_mem(&circuit); + let verifier = Verifier::new(&config); + let circuit = Arc::new(Mutex::new(circuit)); + let circuit_clone_for_verifier = circuit.clone(); + let prover = Arc::new(Mutex::new(prover)); + let verifier = Arc::new(Mutex::new(verifier)); + + let prove = + warp::path("prove") + .and(warp::body::bytes()) + .map(move |bytes: bytes::Bytes| { + let witness_bytes: Vec = bytes.to_vec(); + let mut circuit = circuit.lock().unwrap(); + let mut prover = prover.lock().unwrap(); + circuit.load_witness_bytes(&witness_bytes); + circuit.evaluate(); + let (claimed_v, proof) = prover.prove(&circuit); + dump_proof_and_claimed_v(&proof, &claimed_v) + }); + let verify = + warp::path("verify") + .and(warp::body::bytes()) + .map(move |bytes: bytes::Bytes| { + let witness_and_proof_bytes: Vec = bytes.to_vec(); + let length_of_witness_bytes = + u64::from_le_bytes(witness_and_proof_bytes[0..8].try_into().unwrap()) + as usize; + let length_of_proof_bytes = + u64::from_le_bytes(witness_and_proof_bytes[8..16].try_into().unwrap()) + as usize; + let witness_bytes = + &witness_and_proof_bytes[16..16 + length_of_witness_bytes]; + let proof_bytes = &witness_and_proof_bytes[16 + length_of_witness_bytes + ..16 + length_of_witness_bytes + length_of_proof_bytes]; + + let mut circuit = circuit_clone_for_verifier.lock().unwrap(); + let verifier = verifier.lock().unwrap(); + circuit.load_witness_bytes(witness_bytes); + let (proof, claimed_v) = load_proof_and_claimed_v(proof_bytes); + if verifier.verify(&circuit, &claimed_v, &proof) { + "success".to_string() + } else { + "failure".to_string() + } + }); + warp::serve(warp::post().and(prove.or(verify))) + .run((host, port)) + .await; } _ => { println!("Invalid command."); diff --git a/tests/compiler_integration.rs b/tests/compiler_integration.rs index 0b685683..f074258f 100644 --- a/tests/compiler_integration.rs +++ b/tests/compiler_integration.rs @@ -16,7 +16,7 @@ fn test_compiler_format_integration() { println!("Config created."); let mut circuit = Circuit::::load_circuit(FILENAME_CIRCUIT); println!("Circuit loaded."); - circuit.load_witness(FILENAME_WITNESS); + circuit.load_witness_file(FILENAME_WITNESS); println!("Witness loaded."); circuit.evaluate(); println!("Circuit evaluated.");