Skip to content

Commit

Permalink
add http interface (#21)
Browse files Browse the repository at this point in the history
* add http interface

* minor description fix

* redirect fork

---------

Co-authored-by: Tiancheng Xie <[email protected]>
  • Loading branch information
sixbigsquare and niconiconi committed Jun 23, 2024
1 parent 53bae44 commit 2f0a120
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 16 deletions.
15 changes: 9 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion arith/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
7 changes: 7 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,20 @@ Usage:
```sh
RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- prove <input:circuit_file> <input:witness_file> <output:proof>
RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- verify <input:circuit_file> <input:witness_file> <input:proof>
RUSTFLAGS="-C target-cpu=native" cargo run --bin expander-exec --release -- serve <input:circuit_file> <input:ip> <input:port>
```
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?
Expand Down
45 changes: 45 additions & 0 deletions scripts/test_http.py
Original file line number Diff line number Diff line change
@@ -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")
5 changes: 4 additions & 1 deletion src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,12 @@ fn read_f_u32_val(file_bytes: &[u8]) -> u32 {
}

impl<F: Field> Circuit<F> {
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;
Expand Down
84 changes: 77 additions & 7 deletions src/exec.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -40,10 +45,12 @@ fn load_proof_and_claimed_v(bytes: &[u8]) -> (Proof, Vec<F>) {
(proof, claimed_v)
}

fn main() {
#[tokio::main]
async fn main() {
// examples:
// expander-exec prove <input:circuit_file> <input:witness_file> <output:proof>
// expander-exec verify <input:circuit_file> <input:witness_file> <input:proof>
// expander-exec serve <input:circuit_file> <input:ip> <input:port>
let args = std::env::args().collect::<Vec<String>>();
if args.len() < 4 {
println!(
Expand All @@ -52,17 +59,18 @@ fn main() {
println!(
"Usage: expander-exec verify <input:circuit_file> <input:witness_file> <input:proof>"
);
println!("Usage: expander-exec serve <input:circuit_file> <input:host> <input:port>");
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::<F>::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);
Expand All @@ -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::<F>::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::<Vec<u8>>()
.try_into()
.unwrap();
let port = args[4].parse().unwrap();
let config = Config::m31_config();
let circuit = Circuit::<F>::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<u8> = 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<u8> = 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.");
Expand Down
2 changes: 1 addition & 1 deletion tests/compiler_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn test_compiler_format_integration() {
println!("Config created.");
let mut circuit = Circuit::<F>::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.");
Expand Down

0 comments on commit 2f0a120

Please sign in to comment.