diff --git a/algebraic-gpu/Cargo.toml b/algebraic-gpu/Cargo.toml new file mode 100644 index 00000000..6e66017a --- /dev/null +++ b/algebraic-gpu/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "algebraic-gpu" +version = "0.0.1" +edition = "2021" +license = "Apache-2.0" +description = "Eigen Algebraic based on R1CS" +documentation = "eigen.market" +homepage = "eigen.market" + +[lib] +crate-type = ["cdylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hex-literal = "0.2.1" +itertools = "0.8.1" +log = "0.4.11" +num-bigint = "0.3.3" +num-traits = "0.2.8" +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0", features = ["arbitrary_precision"] } +hex = "*" +wasmer = { version = "4.3.2", default-features = false } +thiserror = "1.0" +anyhow = "1.0.79" +fnv = { version = "1.0.3", default-features = false } +num = { version = "0.4.0" } +byteorder = "1" + +ff = { version = "0.13.0", features = ["derive"] } +pairing = "0.23.0" +bellperson = { version = "0.26", default-features = false, features = ["groth16"] } + +[dev-dependencies] +env_logger = "0.10" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +rand = "0.4" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { version = "0.2.51", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.1" +rand = { version="0.6.5", features = ["wasm-bindgen"] } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-test = "0.3" + +[features] +default = ["bellperson/default", "wasmer/singlepass"] +wasm = ["wasmer/js-default"] +cuda = ["bellperson/cuda", "wasmer/singlepass"] +opencl = ["bellperson/opencl", "wasmer/singlepass"] \ No newline at end of file diff --git a/algebraic-gpu/src/circom_circuit.rs b/algebraic-gpu/src/circom_circuit.rs new file mode 100644 index 00000000..9c183aa7 --- /dev/null +++ b/algebraic-gpu/src/circom_circuit.rs @@ -0,0 +1,169 @@ +// copy from https://github.com/poma/zkutil/blob/master/src/circom_circuit.rs partially +#![allow(clippy::needless_range_loop)] +extern crate rand; + +use itertools::Itertools; +use std::collections::BTreeMap; +use std::str; + +use crate::bellperson::{ + Circuit, ConstraintSystem, Index, LinearCombination, SynthesisError, Variable, +}; +use ff::PrimeField; +pub use num_bigint::BigUint; +use num_traits::Num; + +pub fn repr_to_big(r: T) -> String { + let hex_str = format!("{:?}", r); + let trim_quotes = hex_str + .trim_start_matches("Scalar(0x") + .trim_end_matches(')'); + let clean_hex = trim_quotes.trim_matches('"').trim_start_matches("0x"); + BigUint::from_str_radix(clean_hex, 16) + .map(|bigint: BigUint| bigint.to_str_radix(10)) + .unwrap() +} + +#[derive(Serialize, Deserialize)] +pub struct CircuitJson { + pub constraints: Vec>>, + #[serde(rename = "nPubInputs")] + pub num_inputs: usize, + #[serde(rename = "nOutputs")] + pub num_outputs: usize, + #[serde(rename = "nVars")] + pub num_variables: usize, +} + +pub type Constraint = (Vec<(usize, E)>, Vec<(usize, E)>, Vec<(usize, E)>); + +// R1CSfile's CustomGates +#[derive(Debug, Default, Clone)] +pub struct CustomGates { + pub template_name: String, + pub parameters: Vec, +} + +// R1CSfile's CustomGatesUses +#[derive(Debug, Default, Clone)] +pub struct CustomGatesUses { + pub id: u64, + pub signals: Vec, +} + +/// R1CS spec: https://www.sikoba.com/docs/SKOR_GD_R1CS_Format.pdf +#[derive(Clone, Debug)] +pub struct R1CS { + pub num_inputs: usize, + pub num_aux: usize, + pub num_variables: usize, + pub num_outputs: usize, + pub constraints: Vec>, + pub custom_gates: Vec>, + pub custom_gates_uses: Vec, +} + +#[derive(Clone, Debug)] +pub struct CircomCircuit { + pub r1cs: R1CS, + pub witness: Option>, + pub wire_mapping: Option>, + pub aux_offset: usize, + // debug symbols +} + +impl CircomCircuit { + pub fn get_public_inputs(&self) -> Option> { + match &self.witness { + None => None, + Some(w) => match &self.wire_mapping { + None => Some(w[1..self.r1cs.num_inputs].to_vec()), + Some(m) => Some( + m[1..self.r1cs.num_inputs] + .iter() + .map(|i| w[*i]) + .collect_vec(), + ), + }, + } + } + + pub fn get_public_inputs_json(&self) -> String { + let inputs = self.get_public_inputs(); + let inputs = match inputs { + None => return String::from("[]"), + Some(inp) => inp.iter().map(repr_to_big).collect_vec(), + }; + serde_json::to_string_pretty(&inputs).unwrap() + } +} + +/// Our demo circuit implements this `Circuit` trait which +/// is used during paramgen and proving in order to +/// synthesize the constraint system. +impl Circuit for CircomCircuit { + //noinspection RsBorrowChecker + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { + let witness = &self.witness; + let wire_mapping = &self.wire_mapping; + for i in 1..self.r1cs.num_inputs { + cs.alloc_input( + || format!("variable {}", i), + || { + Ok(match witness { + None => E::from_str_vartime(&format!("alloc input {} error", i)).unwrap(), + Some(w) => match wire_mapping { + None => w[i], + Some(m) => w[m[i]], + }, + }) + }, + )?; + } + for i in 0..self.r1cs.num_aux { + cs.alloc( + || format!("aux {}", i + self.aux_offset), + || { + Ok(match witness { + None => { + E::from_str_vartime(&format!("alloc aux {} error", i + self.aux_offset)) + .unwrap() + } + Some(w) => match wire_mapping { + None => w[i + self.r1cs.num_inputs], + Some(m) => w[m[i + self.r1cs.num_inputs]], + }, + }) + }, + )?; + } + + let make_index = |index| { + if index < self.r1cs.num_inputs { + Index::Input(index) + } else { + Index::Aux(index - self.r1cs.num_inputs + self.aux_offset) + } + }; + let make_lc = |lc_data: Vec<(usize, E)>| { + lc_data.iter().fold( + LinearCombination::::zero(), + |lc: LinearCombination, (index, coeff)| { + lc + (*coeff, Variable::new_unchecked(make_index(*index))) + }, + ) + }; + for (i, constraint) in self.r1cs.constraints.iter().enumerate() { + // 0 * LC = 0 must be ignored + if !((constraint.0.is_empty() || constraint.1.is_empty()) && constraint.2.is_empty()) { + cs.enforce( + || format!("{}", i), + |_| make_lc(constraint.0.clone()), + |_| make_lc(constraint.1.clone()), + |_| make_lc(constraint.2.clone()), + ); + } + } + Ok(()) + } +} diff --git a/algebraic-gpu/src/lib.rs b/algebraic-gpu/src/lib.rs new file mode 100644 index 00000000..79157213 --- /dev/null +++ b/algebraic-gpu/src/lib.rs @@ -0,0 +1,58 @@ +#![allow(clippy::unit_arg)] + +#[macro_use] +extern crate serde; +#[macro_use] +extern crate hex_literal; +extern crate bellperson; +extern crate byteorder; +extern crate ff; +extern crate itertools; +extern crate num_bigint; +extern crate num_traits; +extern crate pairing; +extern crate rand; + +pub mod circom_circuit; +pub mod r1cs_file; +pub mod reader; +pub mod witness; + +pub use bellperson::groth16::*; +pub use ff::*; + +#[cfg(target_arch = "wasm32")] +extern crate wasm_bindgen; + +#[cfg(all(test, target_arch = "wasm32"))] +extern crate wasm_bindgen_test; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + + // The `console.log` is quite polymorphic, so we can bind it with multiple + // signatures. Note that we need to use `js_name` to ensure we always call + // `log` in JS. + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_u32(a: u32); + + // Multiple arguments too! + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_many(a: &str, b: &str); +} + +#[cfg(target_arch = "wasm32")] +#[macro_export] +macro_rules! console_log { + // Note that this is using the `log` function imported above during + // `bare_bones` + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} diff --git a/algebraic-gpu/src/r1cs_file.rs b/algebraic-gpu/src/r1cs_file.rs new file mode 100644 index 00000000..90e633b1 --- /dev/null +++ b/algebraic-gpu/src/r1cs_file.rs @@ -0,0 +1,315 @@ +// some codes borrowed from https://github.com/poma/zkutil/blob/master/src/r1cs_reader.rs +// Implement of https://github.com/iden3/r1csfile/blob/master/doc/r1cs_bin_format.md +#![allow(unused_variables, dead_code, non_snake_case)] +use crate::circom_circuit::{Constraint, CustomGates, CustomGatesUses}; +use byteorder::{LittleEndian, ReadBytesExt}; +use ff::PrimeField; +use std::{ + collections::BTreeMap, + io::{Error, ErrorKind, Read, Result, Seek, SeekFrom}, +}; + +// R1CSFile's header +#[derive(Debug, Default)] +pub struct Header { + pub field_size: u32, + pub prime_size: Vec, + pub n_wires: u32, + pub n_pub_out: u32, + pub n_pub_in: u32, + pub n_prv_in: u32, + pub n_labels: u64, + pub n_constraints: u32, + pub use_custom_gates: bool, +} + +// R1CSFile parse result +#[derive(Debug, Default)] +pub struct R1CSFile { + pub version: u32, + pub header: Header, + pub constraints: Vec>, + pub wire_mapping: Vec, + pub custom_gates: Vec>, + pub custom_gates_uses: Vec, +} + +fn read_field(mut reader: R) -> Result { + let mut repr = E::default().to_repr(); + let repr_slice = repr.as_mut(); + reader.read_exact(repr_slice)?; + let maybe_field = E::from_repr(repr); + if maybe_field.is_some().unwrap_u8() == 1 { + Ok(maybe_field.unwrap()) + } else { + Err(Error::new( + ErrorKind::InvalidData, + "Invalid field representation", + )) + } +} + +const HEADER_TYPE: u32 = 1; +const CONSTRAINT_TYPE: u32 = 2; +const WIRE2LABEL_TYPE: u32 = 3; +const CUSTOM_GATES_LIST: u32 = 4; +const CUSTOM_GATES_USE: u32 = 5; + +fn read_header(mut reader: R, size: u64) -> Result
{ + let field_size = reader.read_u32::()?; + let mut prime_size = vec![0u8; field_size as usize]; + reader.read_exact(&mut prime_size)?; + if size != 32 + field_size as u64 { + return Err(Error::new( + ErrorKind::InvalidData, + "Invalid header section size", + )); + } + + Ok(Header { + field_size, + prime_size, + n_wires: reader.read_u32::()?, + n_pub_out: reader.read_u32::()?, + n_pub_in: reader.read_u32::()?, + n_prv_in: reader.read_u32::()?, + n_labels: reader.read_u64::()?, + n_constraints: reader.read_u32::()?, + use_custom_gates: false, + }) +} + +fn read_constraint_vec( + mut reader: R, + header: &Header, +) -> Result> { + let n_vec = reader.read_u32::()? as usize; + let mut vec = Vec::with_capacity(n_vec); + for _ in 0..n_vec { + vec.push(( + reader.read_u32::()? as usize, + read_field::<&mut R, E>(&mut reader)?, + )); + // sort by key + vec.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + } + Ok(vec) +} + +fn read_constraints( + mut reader: R, + size: u64, + header: &Header, +) -> Result>> { + // todo check section size + let mut vec = Vec::with_capacity(header.n_constraints as usize); + for _ in 0..header.n_constraints { + vec.push(( + read_constraint_vec::<&mut R, E>(&mut reader, header)?, + read_constraint_vec::<&mut R, E>(&mut reader, header)?, + read_constraint_vec::<&mut R, E>(&mut reader, header)?, + )); + } + Ok(vec) +} + +fn read_map(mut reader: R, size: u64, header: &Header) -> Result> { + if size != header.n_wires as u64 * 8 { + return Err(Error::new( + ErrorKind::InvalidData, + "Invalid map section size", + )); + } + let mut vec = Vec::with_capacity(header.n_wires as usize); + for _ in 0..header.n_wires { + vec.push(reader.read_u64::()?); + } + if vec[0] != 0 { + return Err(Error::new( + ErrorKind::InvalidData, + "Wire 0 should always be mapped to 0", + )); + } + Ok(vec) +} + +// TODO: why does the `read_to_end` not work? +fn read_to_string(mut reader: R) -> String { + let mut name_buf = vec![1u8; 1]; + let mut buf = vec![]; + loop { + let name_size_res = reader.read_exact(&mut name_buf); + if name_buf[0] != 0 { + buf.push(name_buf[0]); + } else { + break; + } + } + String::from_utf8_lossy(&buf).to_string() +} + +fn read_custom_gates_list( + mut reader: R, + size: u64, + header: &Header, +) -> Result>> { + let num = reader.read_u32::()?; + let mut custom_gates: Vec> = vec![]; + for i in 0..num { + let mut custom_gate = CustomGates:: { + template_name: read_to_string(&mut reader), + parameters: vec![], + }; + let num_parameters = reader.read_u32::()?; + for _i in 0..num_parameters { + custom_gate + .parameters + .push(read_field::<&mut R, E>(&mut reader)?); + } + custom_gates.push(custom_gate); + } + Ok(custom_gates) +} + +fn read_custom_gates_uses_list( + mut reader: R, + size: u64, + header: &Header, +) -> Result> { + let mut custom_gates_uses: Vec = vec![]; + + let sz = size as usize / 4; + let mut b_r1cs32 = Vec::with_capacity(sz); + for _ in 0..sz { + b_r1cs32.push(reader.read_u32::()?); + } + + let n_custom_gate_uses = b_r1cs32[0]; + let mut b_r1cs_pos = 1; + for i in 0..n_custom_gate_uses { + let mut c = CustomGatesUses { + id: b_r1cs32[b_r1cs_pos] as u64, + ..Default::default() + }; + b_r1cs_pos += 1; + let num_signals = b_r1cs32[b_r1cs_pos]; + b_r1cs_pos += 1; + for j in 0..num_signals { + let LSB = b_r1cs32[b_r1cs_pos] as u64; + b_r1cs_pos += 1; + let MSB = b_r1cs32[b_r1cs_pos] as u64; + b_r1cs_pos += 1; + c.signals.push(MSB * 0x100000000u64 + LSB); + } + custom_gates_uses.push(c); + } + Ok(custom_gates_uses) +} + +pub fn from_reader(mut reader: R) -> Result> { + let mut magic = [0u8; 4]; + reader.read_exact(&mut magic)?; + if magic != [0x72, 0x31, 0x63, 0x73] { + // magic = "r1cs" + return Err(Error::new(ErrorKind::InvalidData, "Invalid magic number")); + } + + let version = reader.read_u32::()?; + if version != 1 { + return Err(Error::new(ErrorKind::InvalidData, "Unsupported version")); + } + + let num_sections = reader.read_u32::()?; + + // section type -> file offset + let mut section_offsets = BTreeMap::::new(); + let mut section_sizes = BTreeMap::::new(); + + // get file offset of each section, we donot support custom gate yet, so ignore the + // last two sections. + for i in 0..(num_sections) { + let section_type = reader.read_u32::()?; + let section_size = reader.read_u64::()?; + let offset = reader.stream_position()?; + section_offsets.insert(section_type, offset); + section_sizes.insert(section_type, section_size); + reader.seek(SeekFrom::Current(section_size as i64))?; + } + + reader.seek(SeekFrom::Start(*section_offsets.get(&HEADER_TYPE).unwrap()))?; + let mut header = read_header(&mut reader, *section_sizes.get(&HEADER_TYPE).unwrap())?; + if section_offsets.contains_key(&CUSTOM_GATES_USE) + && section_offsets.contains_key(&CUSTOM_GATES_LIST) + { + header.use_custom_gates = true; + } + if !(header.field_size == 32 || header.field_size == 8) { + return Err(Error::new( + ErrorKind::InvalidData, + "This parser only supports 32-bytes or 8-bytes fields", + )); + } + if header.field_size != (E::NUM_BITS + 7) / 8 { + return Err(Error::new(ErrorKind::InvalidData, "Different prime")); + } + if !(header.prime_size + == hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") + || header.prime_size + == hex!("01000000fffffffffe5bfeff02a4bd5305d8a10908d83933487d9d2953a7ed73") + || header.prime_size == hex!("01000000ffffffff")) + { + return Err(Error::new( + ErrorKind::InvalidData, + "This parser only supports bn256 or GL", + )); + } + reader.seek(SeekFrom::Start( + *section_offsets.get(&CONSTRAINT_TYPE).unwrap(), + ))?; + let constraints = read_constraints::<&mut R, E>( + &mut reader, + *section_sizes.get(&CONSTRAINT_TYPE).unwrap(), + &header, + )?; + + reader.seek(SeekFrom::Start( + *section_offsets.get(&WIRE2LABEL_TYPE).unwrap(), + ))?; + let wire_mapping = read_map( + &mut reader, + *section_sizes.get(&WIRE2LABEL_TYPE).unwrap(), + &header, + )?; + let mut custom_gates: Vec> = vec![]; + if section_offsets.contains_key(&CUSTOM_GATES_LIST) { + reader.seek(SeekFrom::Start( + *section_offsets.get(&CUSTOM_GATES_LIST).unwrap(), + ))?; + custom_gates = read_custom_gates_list( + &mut reader, + *section_sizes.get(&CUSTOM_GATES_LIST).unwrap(), + &header, + )?; + } + + let mut custom_gates_uses: Vec = vec![]; + if section_offsets.contains_key(&CUSTOM_GATES_USE) { + reader.seek(SeekFrom::Start( + *section_offsets.get(&CUSTOM_GATES_USE).unwrap(), + ))?; + custom_gates_uses = read_custom_gates_uses_list( + &mut reader, + *section_sizes.get(&CUSTOM_GATES_USE).unwrap(), + &header, + )?; + } + + Ok(R1CSFile { + version, + header, + constraints, + wire_mapping, + custom_gates, + custom_gates_uses, + }) +} diff --git a/algebraic-gpu/src/reader.rs b/algebraic-gpu/src/reader.rs new file mode 100644 index 00000000..ff9ee093 --- /dev/null +++ b/algebraic-gpu/src/reader.rs @@ -0,0 +1,208 @@ +use anyhow::{bail, Result}; +use byteorder::{LittleEndian, ReadBytesExt}; +use itertools::Itertools; +use std::collections::BTreeMap; +use std::fs::{File, OpenOptions}; +use std::io::{BufReader, Read, Seek}; +use std::str; + +use ff::PrimeField; + +use crate::circom_circuit::{CircuitJson, R1CS}; + +/// get universal setup file by filename +#[allow(dead_code)] +fn get_universal_setup_file_buff_reader(setup_file_name: &str) -> Result> { + let setup_file = File::open(setup_file_name)?; + Ok(BufReader::with_capacity(1 << 29, setup_file)) +} + +/// load witness file by filename with autodetect encoding (bin or json). +pub fn load_witness_from_file(filename: &str) -> Vec { + if filename.ends_with("json") { + load_witness_from_json_file::(filename) + } else { + load_witness_from_bin_file::(filename) + } +} + +/// load witness from json file by filename +pub fn load_witness_from_json_file(filename: &str) -> Vec { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .unwrap_or_else(|_| panic!("Unable to open {}.", filename)); + load_witness_from_json::>(BufReader::new(reader)) +} + +/// load witness from json by a reader +fn load_witness_from_json(reader: R) -> Vec { + let witness: Vec = serde_json::from_reader(reader).expect("Unable to read."); + witness + .into_iter() + .map(|x| E::from_str_vartime(&x).unwrap()) + .collect::>() +} + +/// load witness from bin file by filename +pub fn load_witness_from_bin_file(filename: &str) -> Vec { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .unwrap_or_else(|_| panic!("Unable to open {}.", filename)); + load_witness_from_bin_reader::>(BufReader::new(reader)) + .expect("read witness failed") +} + +/// load witness from u8 array +pub fn load_witness_from_array(buffer: Vec) -> Result> { + load_witness_from_bin_reader::(buffer.as_slice()) +} + +/// load witness from u8 array by a reader +pub fn load_witness_from_bin_reader(mut reader: R) -> Result> { + let mut wtns_header = [0u8; 4]; + reader.read_exact(&mut wtns_header)?; + if wtns_header != [119, 116, 110, 115] { + // python -c 'print([ord(c) for c in "wtns"])' => [119, 116, 110, 115] + bail!("Invalid file header"); + } + let version = reader.read_u32::()?; + log::trace!("wtns version {}", version); + if version > 2 { + bail!("unsupported file version"); + } + let num_sections = reader.read_u32::()?; + if num_sections != 2 { + bail!("invalid num sections".to_string()); + } + // read the first section + let sec_type = reader.read_u32::()?; + if sec_type != 1 { + bail!("invalid section type".to_string()); + } + let sec_size = reader.read_u64::()?; + if sec_size != 4 + 32 + 4 { + bail!("invalid section len".to_string()); + } + let field_size = reader.read_u32::()?; + if field_size != 32 { + bail!("invalid field byte size".to_string()); + } + let mut prime = vec![0u8; field_size as usize]; + reader.read_exact(&mut prime)?; + if prime != hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") { + bail!("invalid curve prime".to_string()); + } + let witness_len = reader.read_u32::()?; + log::trace!("witness len {}", witness_len); + let sec_type = reader.read_u32::()?; + if sec_type != 2 { + bail!("invalid section type".to_string()); + } + let sec_size = reader.read_u64::()?; + if sec_size != (witness_len * field_size) as u64 { + bail!(format!("Invalid witness section size {}", sec_size)); + } + let mut result = Vec::with_capacity(witness_len as usize); + for _ in 0..witness_len { + let mut repr = E::default().to_repr(); + let repr_slice = repr.as_mut(); + if reader.read_exact(repr_slice).is_err() { + continue; + } + let maybe_field_elem = E::from_repr(repr); + if maybe_field_elem.is_some().unwrap_u8() == 1 { + result.push(maybe_field_elem.unwrap()); + } else { + continue; + } + // repr.read_le(&mut reader)?; + // result.push(E::Fr::from_repr(repr)?); + } + Ok(result) +} + +/// load r1cs file by filename with autodetect encoding (bin or json) +pub fn load_r1cs(filename: &str) -> R1CS { + if filename.ends_with("json") { + load_r1cs_from_json_file(filename) + } else { + let (r1cs, _wire_mapping) = load_r1cs_from_bin_file(filename); + r1cs + } +} + +/// load r1cs from json file by filename +fn load_r1cs_from_json_file(filename: &str) -> R1CS { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .unwrap_or_else(|_| panic!("Unable to open {}.", filename)); + load_r1cs_from_json(BufReader::new(reader)) +} + +/// load r1cs from json by a reader +fn load_r1cs_from_json(reader: R) -> R1CS { + let circuit_json: CircuitJson = serde_json::from_reader(reader).expect("Unable to read."); + + let num_inputs = circuit_json.num_inputs + circuit_json.num_outputs + 1; + let num_aux = circuit_json.num_variables - num_inputs; + + let convert_constraint = |lc: &BTreeMap| { + lc.iter() + .map(|(index, coeff)| (index.parse().unwrap(), E::from_str_vartime(coeff).unwrap())) + .collect_vec() + }; + + let constraints = circuit_json + .constraints + .iter() + .map(|c| { + ( + convert_constraint(&c[0]), + convert_constraint(&c[1]), + convert_constraint(&c[2]), + ) + }) + .collect_vec(); + + R1CS { + num_inputs, + num_aux, + num_variables: circuit_json.num_variables, + num_outputs: circuit_json.num_outputs, + constraints, + custom_gates: vec![], + custom_gates_uses: vec![], + } +} + +/// load r1cs from bin file by filename +fn load_r1cs_from_bin_file(filename: &str) -> (R1CS, Vec) { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .unwrap_or_else(|_| panic!("Unable to open {}.", filename)); + load_r1cs_from_bin(BufReader::new(reader)) +} + +/// load r1cs from bin by a reader +pub fn load_r1cs_from_bin(reader: R) -> (R1CS, Vec) { + let file = crate::r1cs_file::from_reader::(reader).expect("Unable to read."); + let num_inputs = (1 + file.header.n_pub_in + file.header.n_pub_out) as usize; + let num_variables = file.header.n_wires as usize; + let num_aux = num_variables - num_inputs; + ( + R1CS { + num_aux, + num_inputs, + num_variables, + num_outputs: file.header.n_pub_out as usize, + constraints: file.constraints, + custom_gates: file.custom_gates, + custom_gates_uses: file.custom_gates_uses, + }, + file.wire_mapping.iter().map(|e| *e as usize).collect_vec(), + ) +} diff --git a/algebraic-gpu/src/witness/circom.rs b/algebraic-gpu/src/witness/circom.rs new file mode 100644 index 00000000..98707285 --- /dev/null +++ b/algebraic-gpu/src/witness/circom.rs @@ -0,0 +1,133 @@ +// copied and modified by https://github.com/arkworks-rs/circom-compat/blob/master/src/witness/circom.rs +use anyhow::Result; +use wasmer::{Function, Instance, Store, Value}; + +#[derive(Clone, Debug)] +pub struct Wasm(Instance); + +impl Wasm { + pub(crate) fn get_field_num_len32(&self, store: &mut Store) -> Result { + self.get_u32(store, "getFieldNumLen32") + } + + pub(crate) fn get_raw_prime(&self, store: &mut Store) -> Result<()> { + let func = self.func("getRawPrime"); + func.call(store, &[])?; + Ok(()) + } + + pub(crate) fn read_shared_rw_memory(&self, store: &mut Store, i: u32) -> Result { + let func = self.func("readSharedRWMemory"); + let result = func.call(store, &[i.into()])?; + Ok(result[0].unwrap_i32() as u32) + } + + pub(crate) fn write_shared_rw_memory(&self, store: &mut Store, i: u32, v: u32) -> Result<()> { + let func = self.func("writeSharedRWMemory"); + func.call(store, &[i.into(), v.into()])?; + Ok(()) + } + + pub(crate) fn set_input_signal( + &self, + store: &mut Store, + hmsb: u32, + hlsb: u32, + pos: u32, + ) -> Result<()> { + let func = self.func("setInputSignal"); + func.call(store, &[hmsb.into(), hlsb.into(), pos.into()])?; + Ok(()) + } + + pub(crate) fn get_witness(&self, store: &mut Store, i: u32) -> Result<()> { + let func = self.func("getWitness"); + func.call(store, &[i.into()])?; + Ok(()) + } + + pub(crate) fn get_witness_size(&self, store: &mut Store) -> Result { + self.get_u32(store, "getWitnessSize") + } + + pub(crate) fn init(&self, store: &mut Store, sanity_check: bool) -> Result<()> { + let func = self.func("init"); + func.call(store, &[Value::I32(sanity_check as i32)])?; + Ok(()) + } + + // pub(crate) fn get_ptr_witness_buffer(&self, store: &mut Store) -> Result { + // self.get_u32(store, "getWitnessBuffer") + // } + + // pub(crate) fn get_ptr_witness(&self, store: &mut Store, w: u32) -> Result { + // let func = self.func( "getPWitness"); + // let res = func.call(store, &[w.into()])?; + // + // Ok(res[0].unwrap_i32() as u32) + // } + + // pub(crate) fn get_signal_offset32( + // &self, + // store: &mut Store, + // p_sig_offset: u32, + // component: u32, + // hash_msb: u32, + // hash_lsb: u32, + // ) -> Result<()> { + // let func = self.func( "getSignalOffset32"); + // func.call( + // store, + // &[ + // p_sig_offset.into(), + // component.into(), + // hash_msb.into(), + // hash_lsb.into(), + // ], + // )?; + // + // Ok(()) + // } + // + // pub(crate) fn set_signal( + // &self, + // store: &mut Store, + // c_idx: u32, + // component: u32, + // signal: u32, + // p_val: u32, + // ) -> Result<()> { + // let func = self.func( "setSignal"); + // func.call( + // store, + // &[c_idx.into(), component.into(), signal.into(), p_val.into()], + // )?; + // + // Ok(()) + // } + + // Default to version 1 if it isn't explicitly defined + pub(crate) fn get_version(&self, store: &mut Store) -> Result { + match self.0.exports.get_function("getVersion") { + Ok(func) => Ok(func.call(store, &[])?[0].unwrap_i32() as u32), + Err(_) => Ok(1), + } + } + + pub(crate) fn get_u32(&self, store: &mut Store, name: &str) -> Result { + let func = self.func(name); + let result = func.call(store, &[])?; + Ok(result[0].unwrap_i32() as u32) + } + + pub(crate) fn func(&self, name: &str) -> &Function { + self.0 + .exports + .get_function(name) + .unwrap_or_else(|_| panic!("function {} not found", name)) + } + + pub fn new(instance: Instance) -> Self { + Self(instance) + } +} diff --git a/algebraic-gpu/src/witness/memory.rs b/algebraic-gpu/src/witness/memory.rs new file mode 100644 index 00000000..f1ea578f --- /dev/null +++ b/algebraic-gpu/src/witness/memory.rs @@ -0,0 +1,42 @@ +//! Safe-ish interface for reading and writing specific types to the WASM runtime's memory, +//! modified from ark-circom +use num_bigint::BigInt; +use wasmer::Memory; + +#[derive(Clone, Debug)] +pub struct SafeMemory { + pub memory: Memory, + pub prime: BigInt, + // short_max: BigInt, + // short_min: BigInt, + // r_inv: BigInt, + // n32: usize, +} + +impl SafeMemory { + /// Creates a new SafeMemory + pub fn new(memory: Memory, _n32: usize, prime: BigInt) -> Self { + // TODO: Figure out a better way to calculate these + // let short_max = BigInt::from(0x8000_0000u64); + // let short_min = BigInt::from_biguint( + // num_bigint::Sign::NoSign, + // BigUint::from_str( + // "21888242871839275222246405745257275088548364400416034343698204186575808495617", + // ) + // .unwrap(), + // ) - &short_max; + // let r_inv = BigInt::from_str( + // "9915499612839321149637521777990102151350674507940716049588462388200839649614", + // ) + // .unwrap(); + + Self { + memory, + prime, + // short_max, + // short_min, + // r_inv, + // n32, + } + } +} diff --git a/algebraic-gpu/src/witness/mod.rs b/algebraic-gpu/src/witness/mod.rs new file mode 100644 index 00000000..86838d46 --- /dev/null +++ b/algebraic-gpu/src/witness/mod.rs @@ -0,0 +1,55 @@ +mod circom; +pub(crate) mod memory; +pub(super) mod witness_calculator; + +use fnv::FnvHasher; +use num_bigint::BigInt; +use num_traits::{One, Zero}; +use serde_json::Value; +use std::collections::HashMap; +use std::hash::Hasher; +use std::str::FromStr; +pub use witness_calculator::flat_array; +pub use witness_calculator::WitnessCalculator; + +pub(crate) fn fnv(inp: &str) -> (u32, u32) { + let mut hasher = FnvHasher::default(); + hasher.write(inp.as_bytes()); + let h = hasher.finish(); + + ((h >> 32) as u32, h as u32) +} + +pub fn load_input_for_witness(input_file: &str) -> HashMap> { + let inputs_str = std::fs::read_to_string(input_file) + .unwrap_or_else(|_| panic!("Unable to load {}", input_file)); + let inputs: std::collections::HashMap = + serde_json::from_str(&inputs_str).unwrap(); + + inputs + .iter() + .map(|(key, value)| { + let res = match value { + Value::String(inner) => { + vec![BigInt::from_str(inner).unwrap()] + } + Value::Bool(inner) => { + if *inner { + vec![BigInt::one()] + } else { + vec![BigInt::zero()] + } + } + Value::Number(inner) => { + vec![BigInt::from_str(&inner.to_string()).unwrap()] + //vec![BigInt::from(inner.as_u64().expect("not a u32"))] + } + //Value::Array(inner) => inner.iter().cloned().map(value_to_bigint).collect(), + Value::Array(inner) => flat_array(inner), + _ => panic!("{:?}", value), + }; + + (key.clone(), res) + }) + .collect::>() +} diff --git a/algebraic-gpu/src/witness/witness_calculator.rs b/algebraic-gpu/src/witness/witness_calculator.rs new file mode 100644 index 00000000..cf344cde --- /dev/null +++ b/algebraic-gpu/src/witness/witness_calculator.rs @@ -0,0 +1,484 @@ +// copied and modified by https://github.com/arkworks-rs/circom-compat/blob/master/src/witness/witness_calculator.rs +use crate::witness::{circom::Wasm, fnv, memory::SafeMemory}; +use anyhow::{bail, Result}; +use ff::PrimeField; +use num::ToPrimitive; +use num_bigint::BigInt; +use num_bigint::Sign; +use num_traits::{One, Zero}; +use serde_json::Value; +use std::str::FromStr; +use wasmer::{imports, Function, Instance, Memory, MemoryType, Module, Store}; + +#[cfg(not(feature = "wasm"))] +use std::fs::OpenOptions; +#[cfg(not(feature = "wasm"))] +use std::io::{BufWriter, Write}; + +use byteorder::{LittleEndian, WriteBytesExt}; + +pub struct WitnessCalculator { + pub instance: Wasm, + store: Store, + pub memory: SafeMemory, + pub n64: u32, + pub circom_version: u32, +} + +fn from_array32(arr: Vec) -> BigInt { + let mut res = BigInt::zero(); + let radix = BigInt::from(0x100000000u64); + for &val in arr.iter() { + res = res * &radix + BigInt::from(val); + } + res +} + +fn to_array32(s: &BigInt, size: usize) -> Vec { + let mut res = vec![0; size]; + let mut rem = s.clone(); + let radix = BigInt::from(0x100000000u64); + let mut c = size; + while !rem.is_zero() { + c -= 1; + res[c] = (&rem % &radix).to_u32().unwrap(); + rem /= &radix; + } + + res +} + +impl WitnessCalculator { + pub fn from_file(path: impl AsRef) -> Result { + let mut store = Store::default(); + let module = Module::from_file(&store, path)?; + let mut wtns = Self::from_module(&mut store, module)?; + wtns.store = store; + Ok(wtns) + } + + pub fn from_module(store: &mut Store, module: Module) -> Result { + // Set up the memory + let memory = Memory::new(store, MemoryType::new(2000, None, false))?; + let import_object = imports! { + "env" => { + "memory" => memory.clone(), + }, + // Host function callbacks from the WASM + "runtime" => { + "error" => runtime::error(store), + "logSetSignal" => runtime::log_signal(store), + "logGetSignal" => runtime::log_signal(store), + "logFinishComponent" => runtime::log_component(store), + "logStartComponent" => runtime::log_component(store), + "log" => runtime::log_component(store), + "exceptionHandler" => runtime::exception_handler(store), + "showSharedRWMemory" => runtime::show_memory(store), + "printErrorMessage" => runtime::print_error_message(store), + "writeBufferMessage" => runtime::write_buffer_message(store), + } + }; + let instance = Wasm::new(Instance::new(store, &module, &import_object)?); + + // Circom 2 feature flag with version 2 + fn new_circom( + store: &mut Store, + instance: Wasm, + memory: Memory, + ) -> Result { + let version = instance.get_version(store).unwrap_or(1); + + let n32 = instance.get_field_num_len32(store)?; + let mut safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero()); + instance.get_raw_prime(store)?; + let mut arr = vec![0; n32 as usize]; + for i in 0..n32 { + let res = instance.read_shared_rw_memory(store, i)?; + arr[(n32 as usize) - (i as usize) - 1] = res; + } + let prime = from_array32(arr); + + let n64 = ((prime.bits() - 1) / 64 + 1) as u32; + safe_memory.prime = prime; + + Ok(WitnessCalculator { + instance, + store: Store::default(), + memory: safe_memory, + n64, + circom_version: version, + }) + } + + new_circom(store, instance, memory) + } + + pub fn calculate_witness)>>( + &mut self, + // store: &mut Store, + inputs: I, + sanity_check: bool, + ) -> Result> { + self.instance.init(&mut self.store, sanity_check)?; + let wtns_u32 = self.calculate_witness_circom(inputs, sanity_check)?; + let n32 = self.instance.get_field_num_len32(&mut self.store)?; + + let mut wo = Vec::new(); + let witness_size = self.instance.get_witness_size(&mut self.store)?; + for i in 0..witness_size { + let mut arr = vec![0u32; n32 as usize]; + for j in 0..n32 { + arr[(n32 - 1 - j) as usize] = wtns_u32[(i * n32 + j) as usize]; + } + wo.push(from_array32(arr)); + } + Ok(wo) + } + + pub fn calculate_witness_bin)>>( + &mut self, + inputs: I, + sanity_check: bool, + ) -> Result> { + self.instance.init(&mut self.store, sanity_check)?; + self.calculate_witness_circom(inputs, sanity_check) + } + + // Circom 2 feature flag with version 2 + fn calculate_witness_circom)>>( + &mut self, + inputs: I, + sanity_check: bool, + ) -> Result> { + self.instance.init(&mut self.store, sanity_check)?; + + let n32 = self.instance.get_field_num_len32(&mut self.store)?; + + // allocate the inputs + for (name, values) in inputs.into_iter() { + let (msb, lsb) = fnv(&name); + + for (i, value) in values.into_iter().enumerate() { + let f_arr = to_array32(&value, n32 as usize); + for j in 0..n32 { + self.instance.write_shared_rw_memory( + &mut self.store, + j, + f_arr[(n32 as usize) - 1 - (j as usize)], + )?; + } + self.instance + .set_input_signal(&mut self.store, msb, lsb, i as u32)?; + } + } + + let mut w = Vec::new(); + + let witness_size = self.instance.get_witness_size(&mut self.store)?; + for i in 0..witness_size { + self.instance.get_witness(&mut self.store, i)?; + for j in 0..n32 { + w.push(self.instance.read_shared_rw_memory(&mut self.store, j)?); + } + } + + Ok(w) + } + + #[cfg(not(feature = "wasm"))] + pub fn save_witness_to_bin_file( + &mut self, + filename: &str, + w: &Vec, + ) -> Result<()> { + let writer = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(filename)?; + + let writer = BufWriter::new(writer); + self.save_witness_from_bin_writer::(writer, w) + } + + pub fn save_witness_from_bin_writer( + &mut self, + mut writer: W, + wtns: &Vec, + ) -> Result<()> { + let n32 = self.instance.get_field_num_len32(&mut self.store)?; + let wtns_header = [119, 116, 110, 115]; + writer.write_all(&wtns_header)?; + + let version = self.circom_version; + writer.write_u32::(version)?; + let num_section = 2u32; + writer.write_u32::(num_section)?; + + // id section 1 + let id_section = 1u32; + writer.write_u32::(id_section)?; + + let sec_size: u64 = (n32 * 4 + 8) as u64; + writer.write_u64::(sec_size)?; + + let field_size: u32 = n32 * 4; + writer.write_u32::(field_size)?; + + // write prime + let (sign, prime_buf) = self.memory.prime.to_bytes_le(); + if sign != Sign::Plus { + bail!(format!( + "Invalid prime: {}, must be positive", + self.memory.prime + )); + } + if prime_buf.len() as u32 != field_size { + bail!(format!( + "Invalid prime: {}, len must be of {}", + self.memory.prime, + prime_buf.len() + )); + } + writer.write_all(&prime_buf)?; + + // write witness size + let wtns_size = wtns.len() as u32 / n32; + writer.write_u32::(wtns_size)?; + // sec type + writer.write_u32::(2)?; + // sec size + writer.write_u64::((wtns_size * field_size) as u64)?; + + for w in wtns { + writer.write_u32::(*w)?; + } + Ok(()) + } +} + +#[allow(dead_code)] +pub fn value_to_bigint(v: Value) -> BigInt { + match v { + Value::String(inner) => BigInt::from_str(&inner).unwrap(), + Value::Number(inner) => BigInt::from( + inner + .as_u64() + .unwrap_or_else(|| panic!("{} not a u32", inner)), + ), + _ => panic!("unsupported type {:?}", v), + } +} + +pub fn flat_array(v: &[Value]) -> Vec { + let mut result = Vec::new(); + fn fill_array(out: &mut Vec, value: &Value) { + match value { + Value::Array(inner) => { + for v2 in inner.iter() { + fill_array(out, v2); + } + } + Value::Bool(inner) => { + if *inner { + out.push(BigInt::one()); + } else { + out.push(BigInt::zero()); + } + } + Value::String(inner) => { + out.push(BigInt::from_str(inner).unwrap()); + } + Value::Number(inner) => { + out.push(BigInt::from_str(&inner.to_string()).unwrap()); + } + _ => panic!(), + } + } + + for v2 in v.iter() { + fill_array(&mut result, v2); + } + result +} + +// callback hooks for debugging +mod runtime { + use super::*; + + pub fn error(store: &mut Store) -> Function { + #[allow(unused)] + #[allow(clippy::many_single_char_names)] + fn func( + a: i32, + b: i32, + c: i32, + d: i32, + e: i32, + f: i32, + ) -> std::result::Result<(), wasmer::RuntimeError> { + // NOTE: We can also get more information why it is failing, see p2str etc here: + // https://github.com/iden3/circom_runtime/blob/master/js/witness_calculator.js#L52-L64 + log::trace!("runtime error, exiting early: {a} {b} {c} {d} {e} {f}",); + Err(wasmer::RuntimeError::new("1")) + } + Function::new_typed(store, func) + } + + // Circom 2.0 + pub fn exception_handler(store: &mut Store) -> Function { + #[allow(unused)] + fn func(a: i32) {} + Function::new_typed(store, func) + } + + // Circom 2.0 + pub fn show_memory(store: &mut Store) -> Function { + #[allow(unused)] + fn func() {} + Function::new_typed(store, func) + } + + // Circom 2.0 + pub fn print_error_message(store: &mut Store) -> Function { + #[allow(unused)] + fn func() {} + Function::new_typed(store, func) + } + + // Circom 2.0 + pub fn write_buffer_message(store: &mut Store) -> Function { + #[allow(unused)] + fn func() {} + Function::new_typed(store, func) + } + + pub fn log_signal(store: &mut Store) -> Function { + #[allow(unused)] + fn func(a: i32, b: i32) {} + Function::new_typed(store, func) + } + + pub fn log_component(store: &mut Store) -> Function { + #[allow(unused)] + fn func(a: i32) {} + Function::new_typed(store, func) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::{json, Value}; + use std::{collections::HashMap, path::PathBuf}; + + struct TestCase<'a> { + circuit_path: &'a str, + inputs: HashMap, + n64: u32, + witness: &'a [&'a str], + } + + pub fn root_path(p: &str) -> String { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push(p); + path.to_string_lossy().to_string() + } + + #[test] + fn multiplier_1() { + let inputs = HashMap::from([("a".to_string(), json!(3)), ("b".to_string(), json!(11))]); + + run_test(TestCase { + circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + inputs, + n64: 4, + witness: &["1", "33", "3", "11"], + }); + } + + #[test] + fn multiplier_2() { + let inputs = HashMap::from([ + ( + "a".to_string(), + json!( + "21888242871839275222246405745257275088548364400416034343698204186575796149939" + ), + ), + ("b".to_string(), json!(11)), + ]); + + run_test(TestCase { + circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + inputs, + n64: 4, + witness: &[ + "1", + "21888242871839275222246405745257275088548364400416034343698204186575672693159", + "21888242871839275222246405745257275088548364400416034343698204186575796149939", + "11", + ], + }); + } + + #[test] + fn multiplier_3() { + let inputs = HashMap::from([ + ( + "a".to_string(), + json!( + "10944121435919637611123202872628637544274182200208017171849102093287904246808" + ), + ), + ("b".to_string(), json!(2)), + ]); + + run_test(TestCase { + circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + inputs, + n64: 4, + witness: &[ + "1", + "21888242871839275222246405745257275088548364400416034343698204186575808493616", + "10944121435919637611123202872628637544274182200208017171849102093287904246808", + "2", + ], + }); + } + + // TODO: test complex samples + + fn run_test(case: TestCase) { + let mut wtns = WitnessCalculator::from_file(case.circuit_path).unwrap(); + assert_eq!( + wtns.memory.prime.to_str_radix(16), + "30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001".to_lowercase() + ); + assert_eq!({ wtns.n64 }, case.n64); + + let inputs = case + .inputs + .iter() + .map(|(key, value)| { + let res = match value { + Value::String(inner) => { + vec![BigInt::from_str(inner).unwrap()] + } + Value::Number(inner) => { + vec![BigInt::from(inner.as_u64().expect("not a u32"))] + } + Value::Array(inner) => inner.iter().cloned().map(value_to_bigint).collect(), + _ => panic!(), + }; + + (key.clone(), res) + }) + .collect::>(); + + let res = wtns.calculate_witness(inputs, true).unwrap(); + for (r, w) in res.iter().zip(case.witness) { + assert_eq!(r, &BigInt::from_str(w).unwrap()); + } + } +} diff --git a/algebraic-gpu/test-vectors/mycircuit.circom b/algebraic-gpu/test-vectors/mycircuit.circom new file mode 100644 index 00000000..5f57f054 --- /dev/null +++ b/algebraic-gpu/test-vectors/mycircuit.circom @@ -0,0 +1,11 @@ +pragma circom 2.0.1; +template Multiplier() { + signal input a; + signal input b; + signal output c; + + c <== a*b; +} + +component main = Multiplier(); + diff --git a/algebraic-gpu/test-vectors/mycircuit.wasm b/algebraic-gpu/test-vectors/mycircuit.wasm new file mode 100644 index 00000000..ed838ce5 Binary files /dev/null and b/algebraic-gpu/test-vectors/mycircuit.wasm differ diff --git a/algebraic/Cargo.toml b/algebraic/Cargo.toml index 771b5896..396d1701 100644 --- a/algebraic/Cargo.toml +++ b/algebraic/Cargo.toml @@ -3,9 +3,9 @@ name = "algebraic" version = "0.0.1" edition = "2021" license = "Apache-2.0" -description = "Eigen Algbraic based on R1CS" -documentation = "eigen.cash" -homepage = "eigen.cash" +description = "Eigen Algebraic based on R1CS" +documentation = "eigen.market" +homepage = "eigen.market" [lib] crate-type = ["cdylib", "rlib"] diff --git a/algebraic/src/witness/witness_calculator.rs b/algebraic/src/witness/witness_calculator.rs index fc1146af..25703c62 100644 --- a/algebraic/src/witness/witness_calculator.rs +++ b/algebraic/src/witness/witness_calculator.rs @@ -369,11 +369,12 @@ mod runtime { #[cfg(test)] mod tests { use super::*; + use serde_json::{json, Value}; use std::{collections::HashMap, path::PathBuf}; struct TestCase<'a> { circuit_path: &'a str, - inputs_path: &'a str, + inputs: HashMap, n64: u32, witness: &'a [&'a str], } @@ -386,9 +387,11 @@ mod tests { #[test] fn multiplier_1() { + let inputs = HashMap::from([("a".to_string(), json!(3)), ("b".to_string(), json!(11))]); + run_test(TestCase { circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), - inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(), + inputs, n64: 4, witness: &["1", "33", "3", "11"], }); @@ -396,9 +399,19 @@ mod tests { #[test] fn multiplier_2() { + let inputs = HashMap::from([ + ( + "a".to_string(), + json!( + "21888242871839275222246405745257275088548364400416034343698204186575796149939" + ), + ), + ("b".to_string(), json!(11)), + ]); + run_test(TestCase { circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), - inputs_path: root_path("test-vectors/mycircuit-input2.json").as_str(), + inputs, n64: 4, witness: &[ "1", @@ -411,9 +424,19 @@ mod tests { #[test] fn multiplier_3() { + let inputs = HashMap::from([ + ( + "a".to_string(), + json!( + "10944121435919637611123202872628637544274182200208017171849102093287904246808" + ), + ), + ("b".to_string(), json!(2)), + ]); + run_test(TestCase { circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), - inputs_path: root_path("test-vectors/mycircuit-input3.json").as_str(), + inputs, n64: 4, witness: &[ "1", @@ -434,11 +457,8 @@ mod tests { ); assert_eq!({ wtns.n64 }, case.n64); - let inputs_str = std::fs::read_to_string(case.inputs_path).unwrap(); - let inputs: std::collections::HashMap = - serde_json::from_str(&inputs_str).unwrap(); - - let inputs = inputs + let inputs = case + .inputs .iter() .map(|(key, value)| { let res = match value { diff --git a/algebraic/test-vectors/mycircuit-input1.json b/algebraic/test-vectors/mycircuit-input1.json deleted file mode 100644 index 3b53ebe6..00000000 --- a/algebraic/test-vectors/mycircuit-input1.json +++ /dev/null @@ -1,2 +0,0 @@ -{"a": 3, "b": 11} - diff --git a/algebraic/test-vectors/mycircuit-input2.json b/algebraic/test-vectors/mycircuit-input2.json deleted file mode 100644 index 866a2eb9..00000000 --- a/algebraic/test-vectors/mycircuit-input2.json +++ /dev/null @@ -1 +0,0 @@ -{"a": "21888242871839275222246405745257275088548364400416034343698204186575796149939", "b": 11} diff --git a/algebraic/test-vectors/mycircuit-input3.json b/algebraic/test-vectors/mycircuit-input3.json deleted file mode 100644 index a6f0bdd0..00000000 --- a/algebraic/test-vectors/mycircuit-input3.json +++ /dev/null @@ -1 +0,0 @@ -{"a": "10944121435919637611123202872628637544274182200208017171849102093287904246808", "b": 2} diff --git a/groth16/Cargo.toml b/groth16/Cargo.toml index e83b663d..76059ef0 100644 --- a/groth16/Cargo.toml +++ b/groth16/Cargo.toml @@ -3,35 +3,44 @@ name = "groth16" version = "0.0.1" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] byteorder = "1" -franklin-crypto = { git = "https://github.com/matter-labs/franklin-crypto", branch = "beta", features = [ "plonk" ], version = "0.0.5"} -#franklin-crypto = { path = "../../franklin-crypto", features = [ "plonk" ], version = "0.0.5"} - hex-literal = "0.2.1" itertools = "0.8.1" log = "0.4.11" num-bigint = "0.3.3" num-traits = "0.2.8" -serde = { version = "1.0", features = [ "derive" ] } -serde_json = { version = "1.0", features = [ "arbitrary_precision" ] } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0", features = ["arbitrary_precision"] } hex = "*" -thiserror="1.0" +thiserror = "1.0" fnv = { version = "1.0.3", default-features = false } num = { version = "0.4.0" } -algebraic = { path = "../algebraic", default-features=false } -rand = "0.4" anyhow = "1.0.79" regex = "0.2" - -profiler_macro = {git = "https://github.com/ChengYueJia/profiler-rs", rev = "6d6a77f8"} +profiler_macro = { git = "https://github.com/ChengYueJia/profiler-rs", rev = "6d6a77f8" } ark-std = { version = "0.4.0", optional = true } +algebraic = { path = "../algebraic", default-features = false, optional = true } + +# GPU-specific dependencies (enabled only for cuda and opencl features) +algebraic-gpu = { path = "../algebraic-gpu", default-features = false, optional = true } +ff = { version = "0.13.0", features = ["derive"], optional = true } +pairing = { version = "0.23.0", optional = true } +group = { version = "0.13.0", optional = true } +blstrs = { version = "0.7.0", features = ["__private_bench"], optional = true } +bellperson = { version = "0.26", default-features = false, features = ["groth16"], optional = true } +rand_new = { package = "rand", version = "0.8", features = ["getrandom"], optional = true } +rand_core = { version = "0.6", optional = true } + +# CPU-only dependencies +franklin-crypto = { git = "https://github.com/matter-labs/franklin-crypto", branch = "beta", features = ["plonk"], version = "0.0.5", optional = true } +rand_old = { package = "rand", version = "0.4", optional = true } [dev-dependencies] env_logger = "0.10" [features] -default = ["franklin-crypto/multicore", "algebraic/default"] -profiler = ["ark-std/print-trace"] \ No newline at end of file +default = ["rand_old", "franklin-crypto/multicore", "algebraic/default"] +profiler = ["rand_old", "ark-std/print-trace"] +cuda = ["ff", "pairing", "group", "blstrs", "bellperson/cuda", "algebraic-gpu/cuda", "rand_new", "rand_core"] +opencl = ["ff", "pairing", "group", "blstrs", "bellperson/opencl", "algebraic-gpu/opencl", "rand_new", "rand_core"] \ No newline at end of file diff --git a/groth16/README.md b/groth16/README.md new file mode 100644 index 00000000..99e31519 --- /dev/null +++ b/groth16/README.md @@ -0,0 +1,46 @@ +# Groth16 Performance Analysis +Below, we provide a detailed description of the test circuit constraints used in our evaluations, as well as the proving times when utilizing CPU and GPU resources. + +Circuit Constraints + + • Template Instances: 519 + • Non-linear Constraints: 8,644,880 + • Linear Constraints: 0 + • Public Inputs: 0 + • Private Inputs: 29,370 (29,366 belong to the witness) + • Public Outputs: 1 + • Wires: 8,616,649 + • Labels: 11,692,711 + +## Groth16 Proving Time Analysis +Machine 1 + +CPU Environment (Machine 1) + + • Processor: 13th Gen Intel(R) Core(TM) i7-13700, 16 cores, base clock 2700 MHz + • Memory: 32 GB DDR4 + • OS: Ubuntu 22.04.4 LTS (Jammy Jellyfish) + • Proving Time: 138 seconds + +GPU Environment (Machine 1) + + • GPU Model: NVIDIA GeForce RTX 4060 + • Memory: 8 GB GDDR6 (8188 MiB) + • Proving Time: 121 seconds + +Machine 2 + +CPU Environment (Machine 2) + + • Processor: AMD EPYC 9354 32-Core Processor, 64 cores, base clock 3249.998 MHz + • Memory: 487 GB DDR4 + • OS: Ubuntu 22.04.4 LTS (Jammy Jellyfish) + • Proving Time: 179 seconds + +GPU Environment (Machine 2) + + • GPU Model: 4 x NVIDIA GeForce RTX 4090 + • Memory: 24 GB GDDR6X (24564 MiB) + • Proving Time: 157 seconds + +The speedup achieved by using the GPU over the CPU for both machines is approximately 1.14. \ No newline at end of file diff --git a/groth16/src/api.rs b/groth16/src/api.rs index 0f60e03f..80fa3119 100644 --- a/groth16/src/api.rs +++ b/groth16/src/api.rs @@ -1,15 +1,13 @@ -use crate::{ - bellman_ce::{ - groth16::{Parameters, Proof, VerifyingKey}, - pairing::{ - bls12_381::{Bls12, Fr as Fr_bls12381}, - bn256::{Bn256, Fr}, - }, +#[cfg(not(any(feature = "cuda", feature = "opencl")))] +use crate::bellman_ce::{ + groth16::{Parameters, Proof, VerifyingKey}, + pairing::{ + bls12_381::{Bls12, Fr as Fr_bls12381}, + bn256::{Bn256, Fr}, }, - groth16::Groth16, - json_utils::*, - template, }; +use crate::{groth16::Groth16, json_utils::*, template::CONTRACT_TEMPLATE}; +#[cfg(not(any(feature = "cuda", feature = "opencl")))] use algebraic::{ bellman_ce::Engine, circom_circuit::CircomCircuit, @@ -17,11 +15,30 @@ use algebraic::{ witness::{load_input_for_witness, WitnessCalculator}, Field, PrimeField, }; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use algebraic_gpu::{ + circom_circuit::CircomCircuit, + reader::load_r1cs, + witness::{load_input_for_witness, WitnessCalculator}, + Field, PrimeField, +}; use anyhow::{anyhow, bail, Result}; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use bellperson::{gpu, groth16::*}; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use blstrs::{Bls12, Scalar}; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use group::WnafGroup; use num_traits::Zero; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use pairing::{Engine, MultiMillerLoop}; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use rand_new as rand; +#[cfg(not(any(feature = "cuda", feature = "opencl")))] +use rand_old as rand; use regex::Regex; -use template::CONTRACT_TEMPLATE; +#[cfg(not(any(feature = "cuda", feature = "opencl")))] pub fn groth16_setup( curve_type: &str, circuit_file: &str, @@ -48,6 +65,30 @@ pub fn groth16_setup( Ok(()) } +#[cfg(any(feature = "cuda", feature = "opencl"))] +pub fn groth16_setup( + curve_type: &str, + circuit_file: &str, + pk_file: &str, + vk_file: &str, + to_hex: bool, +) -> Result<()> { + let mut rng = rand::thread_rng(); + match curve_type { + "BLS12381" => { + let circuit = create_circuit_from_file::(circuit_file, None); + let (pk, vk): (Parameters, VerifyingKey) = + Groth16::circuit_specific_setup(circuit, &mut rng)?; + write_pk_vk_to_files(curve_type, pk, vk, pk_file, vk_file, to_hex)? + } + _ => { + bail!(format!("Unknown curve type: {}", curve_type)) + } + }; + Ok(()) +} + +#[cfg(not(any(feature = "cuda", feature = "opencl")))] #[allow(clippy::too_many_arguments)] pub fn groth16_prove( curve_type: &str, @@ -107,10 +148,56 @@ pub fn groth16_prove( bail!(format!("Unknown curve type: {}", curve_type)) } }; + Ok(()) +} + +#[cfg(any(feature = "cuda", feature = "opencl"))] +#[allow(clippy::too_many_arguments)] +pub fn groth16_prove( + curve_type: &str, + circuit_file: &str, + wtns_file: &str, + pk_file: &str, + input_file: &str, + public_input_file: &str, + proof_file: &str, + to_hex: bool, +) -> Result<()> { + let mut rng = rand::thread_rng(); + + let mut wtns = WitnessCalculator::from_file(wtns_file)?; + let inputs = load_input_for_witness(input_file); + let w = wtns.calculate_witness(inputs, false)?; + match curve_type { + "BLS12381" => { + let pk: Parameters = read_pk_from_file(pk_file, false)?; + let w = w + .iter() + .map(|wi| { + if wi.is_zero() { + Scalar::ZERO + } else { + Scalar::from_str_vartime(&wi.to_string()).unwrap() + } + }) + .collect::>(); + let circuit: CircomCircuit = + create_circuit_from_file::(circuit_file, Some(w)); + let proof = Groth16::prove(&pk, circuit.clone(), &mut rng)?; + let proof_json = serialize_proof(&proof, curve_type, to_hex)?; + std::fs::write(proof_file, proof_json)?; + let input_json = circuit.get_public_inputs_json(); + std::fs::write(public_input_file, input_json)?; + } + _ => { + bail!(format!("Unknown curve type: {}", curve_type)) + } + }; Ok(()) } +#[cfg(not(any(feature = "cuda", feature = "opencl")))] pub fn groth16_verify( curve_type: &str, vk_file: &str, @@ -152,6 +239,34 @@ pub fn groth16_verify( Ok(()) } +#[cfg(any(feature = "cuda", feature = "opencl"))] +pub fn groth16_verify( + curve_type: &str, + vk_file: &str, + public_input_file: &str, + proof_file: &str, +) -> Result<()> { + match curve_type { + "BLS12381" => { + let vk: VerifyingKey = read_vk_from_file(vk_file)?; + let inputs: Vec = read_public_input_from_file(public_input_file)?; + let proof = read_proof_from_file(proof_file)?; + let verification_result = + Groth16::<_, CircomCircuit>::verify_with_processed_vk(&vk, &inputs, &proof); + + if verification_result.is_err() || !verification_result.unwrap() { + bail!("verify failed"); + } + } + + _ => { + bail!(format!("Unknown curve type: {}", curve_type)) + } + } + + Ok(()) +} + // Acknowledgement: The Solidity verifier template was modified from ZoKrates implementation. pub fn generate_verifier(vk_file_path: &str, sol_file_path: &str) -> Result<()> { let json_data = std::fs::read_to_string(vk_file_path)?; @@ -264,6 +379,7 @@ pub fn generate_verifier(vk_file_path: &str, sol_file_path: &str) -> Result<()> Ok(()) } +#[cfg(not(any(feature = "cuda", feature = "opencl")))] fn create_circuit_from_file( circuit_file: &str, witness: Option>, @@ -276,6 +392,20 @@ fn create_circuit_from_file( } } +#[cfg(any(feature = "cuda", feature = "opencl"))] +fn create_circuit_from_file( + circuit_file: &str, + witness: Option>, +) -> CircomCircuit { + CircomCircuit { + r1cs: load_r1cs(circuit_file), + witness, + wire_mapping: None, + aux_offset: 0, + } +} + +#[cfg(not(any(feature = "cuda", feature = "opencl")))] fn read_pk_from_file(file_path: &str, checked: bool) -> Result> { let file = std::fs::File::open(file_path).map_err(|e| anyhow!("Open {}, {:?}", file_path, e))?; @@ -283,16 +413,36 @@ fn read_pk_from_file(file_path: &str, checked: bool) -> Result::read(&mut reader, checked)?) } +#[cfg(any(feature = "cuda", feature = "opencl"))] +fn read_pk_from_file(file_path: &str, checked: bool) -> Result> +where + E: MultiMillerLoop, + E::G1: WnafGroup, + E::G2: WnafGroup, + E::Fr: gpu::GpuName, +{ + let file = + std::fs::File::open(file_path).map_err(|e| anyhow!("Open {}, {:?}", file_path, e))?; + let mut reader = std::io::BufReader::new(file); + Ok(Parameters::::read(&mut reader, checked)?) +} + fn read_vk_from_file(file_path: &str) -> Result> { let json_data = std::fs::read_to_string(file_path)?; Ok(to_verification_key::

(&json_data)) } - +#[cfg(not(any(feature = "cuda", feature = "opencl")))] fn read_public_input_from_file(file_path: &str) -> Result> { let json_data = std::fs::read_to_string(file_path)?; Ok(to_public_input::(&json_data)) } +#[cfg(any(feature = "cuda", feature = "opencl"))] +fn read_public_input_from_file(file_path: &str) -> Result> { + let json_data = std::fs::read_to_string(file_path)?; + Ok(to_public_input(&json_data)) +} + fn read_proof_from_file(file_path: &str) -> Result> { let json_data = std::fs::read_to_string(file_path)?; Ok(to_proof::

(&json_data)) diff --git a/groth16/src/groth16.rs b/groth16/src/groth16.rs index 1dd0b75c..e5a2287d 100644 --- a/groth16/src/groth16.rs +++ b/groth16/src/groth16.rs @@ -1,14 +1,79 @@ +#[cfg(not(any(feature = "cuda", feature = "opencl")))] use crate::bellman_ce::{groth16::*, Circuit}; use anyhow::Result; +#[cfg(not(any(feature = "cuda", feature = "opencl")))] use franklin_crypto::bellman::pairing::Engine; -use rand::Rng; +#[allow(unused_imports)] +#[cfg(not(any(feature = "cuda", feature = "opencl")))] +use rand_old::{self as rand, Rng}; + +#[cfg(any(feature = "cuda", feature = "opencl"))] +use bellperson::{gpu, groth16::*, Circuit}; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use group::WnafGroup; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use pairing::{Engine, MultiMillerLoop}; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use rand_core::RngCore; use std::marker::PhantomData; -pub struct Groth16> { +#[cfg(any(feature = "cuda", feature = "opencl"))] +pub struct Groth16> { _engine: PhantomData, _circuit: PhantomData, } +#[cfg(any(feature = "cuda", feature = "opencl"))] +impl Groth16 +where + E: MultiMillerLoop, + E::G1: WnafGroup, + E::G2: WnafGroup, + C: Circuit + std::marker::Send, + E::Fr: gpu::GpuName, +{ + pub fn circuit_specific_setup( + circuit: C, + rng: &mut R, + ) -> Result<(Parameters, VerifyingKey)> { + let pk: Parameters = generate_random_parameters::(circuit, rng)?; + let vk = pk.vk.clone(); + + Ok((pk, vk)) + } + + pub fn prove( + circuit_pk: &Parameters, + input_and_witness: C, + rng: &mut R, + ) -> Result> + where + E::G1Affine: gpu::GpuName, + E::G2Affine: gpu::GpuName, + { + let result = create_random_proof::(input_and_witness, circuit_pk, rng)?; + + Ok(result) + } + + pub fn verify_with_processed_vk( + circuit_vk: &VerifyingKey, + public_input: &[E::Fr], + proof: &Proof, + ) -> Result { + let circuit_pvk = prepare_verifying_key(circuit_vk); + let result = verify_proof(&circuit_pvk, proof, public_input)?; + + Ok(result) + } +} + +#[cfg(not(any(feature = "cuda", feature = "opencl")))] +pub struct Groth16> { + _engine: PhantomData, + _circuit: PhantomData, +} +#[cfg(not(any(feature = "cuda", feature = "opencl")))] impl> Groth16 { pub fn circuit_specific_setup( circuit: C, @@ -43,6 +108,7 @@ impl> Groth16 { } #[cfg(test)] +#[cfg(not(any(feature = "cuda", feature = "opencl")))] mod tests { use franklin_crypto::bellman::{Field, PrimeField}; use num_traits::Zero; @@ -177,3 +243,80 @@ mod tests { Ok(()) } } + +#[cfg(test)] +#[cfg(any(feature = "cuda", feature = "opencl"))] +mod tests { + use super::*; + use algebraic::witness::{load_input_for_witness, WitnessCalculator}; + use algebraic_gpu::circom_circuit::CircomCircuit; + use algebraic_gpu::reader; + use blstrs::{Bls12, Scalar}; + use ff::{Field, PrimeField}; + use num_traits::Zero; + use rand_new::rngs::OsRng; + const INPUT_FILE: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../test/multiplier.input.json"); + const CIRCUIT_FILE_BLS12: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/test-vectors/mycircuit_bls12381.r1cs" + ); + const WASM_FILE_BLS12: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/test-vectors/mycircuit_bls12381.wasm" + ); + + #[test] + fn groth16_proof() -> Result<()> { + //1. SRS + let t = std::time::Instant::now(); + let circuit: CircomCircuit = CircomCircuit { + r1cs: reader::load_r1cs(CIRCUIT_FILE_BLS12), + witness: None, + wire_mapping: None, + aux_offset: 0, + }; + let params = Groth16::circuit_specific_setup(circuit, &mut OsRng)?; + let elapsed = t.elapsed().as_secs_f64(); + println!("1-groth16-bls12381 setup run time: {} secs", elapsed); + + //2. Prove + let t1 = std::time::Instant::now(); + let mut wtns = WitnessCalculator::from_file(WASM_FILE_BLS12)?; + let inputs = load_input_for_witness(INPUT_FILE); + let w = wtns.calculate_witness(inputs, false).unwrap(); + let w = w + .iter() + .map(|wi| { + if wi.is_zero() { + ::Fr::ZERO + } else { + // println!("wi: {}", wi); + ::Fr::from_str_vartime(&wi.to_string()).unwrap() + } + }) + .collect::>(); + let circuit1: CircomCircuit = CircomCircuit { + r1cs: reader::load_r1cs(CIRCUIT_FILE_BLS12), + witness: Some(w), + wire_mapping: None, + aux_offset: 0, + }; + let inputs = circuit1.get_public_inputs().unwrap(); + let proof: bellperson::groth16::Proof = + Groth16::prove(¶ms.0, circuit1, &mut OsRng)?; + let elapsed1 = t1.elapsed().as_secs_f64(); + println!("2-groth16-bls12381 prove run time: {} secs", elapsed1); + + //3. Verify + let t2 = std::time::Instant::now(); + let verified = Groth16::<_, CircomCircuit>::verify_with_processed_vk( + ¶ms.1, &inputs, &proof, + )?; + let elapsed2 = t2.elapsed().as_secs_f64(); + println!("3-groth16-bls12381 verify run time: {} secs", elapsed2); + + assert!(verified); + + Ok(()) + } +} diff --git a/groth16/src/json_utils.rs b/groth16/src/json_utils.rs index 10fcca05..eb0eccb8 100644 --- a/groth16/src/json_utils.rs +++ b/groth16/src/json_utils.rs @@ -1,6 +1,17 @@ +#[cfg(not(any(feature = "cuda", feature = "opencl")))] use crate::bellman_ce::pairing::{bls12_381::Bls12, bn256::Bn256}; -use algebraic::{utils::repr_to_big, PrimeField}; +#[cfg(not(any(feature = "cuda", feature = "opencl")))] +use algebraic::utils::repr_to_big; +#[cfg(not(any(feature = "cuda", feature = "opencl")))] +use algebraic::PrimeField; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use algebraic_gpu::circom_circuit::repr_to_big; use anyhow::Result; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use bellperson::groth16::*; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use blstrs::{Bls12, Fp, Fp2, G1Affine, G2Affine, Scalar}; +#[cfg(not(any(feature = "cuda", feature = "opencl")))] use franklin_crypto::bellman::{ bls12_381::{ Fq2 as Fq2_bls12381, G1Affine as G1Affine_bls12381, G2Affine as G2Affine_bls12381, @@ -11,6 +22,8 @@ use franklin_crypto::bellman::{ }; use num_bigint::BigUint; use num_traits::Num; +#[cfg(any(feature = "cuda", feature = "opencl"))] +use pairing::MultiMillerLoop; use serde::{Deserialize, Serialize}; use serde_json::to_string; use std::fmt; @@ -78,6 +91,7 @@ pub struct ProofFile { pub curve: String, } +#[cfg(not(any(feature = "cuda", feature = "opencl")))] pub trait Parser: franklin_crypto::bellman::pairing::Engine { fn parse_g1(e: &Self::G1Affine, to_hex: bool) -> (String, String); fn parse_g2(e: &Self::G2Affine, to_hex: bool) -> (String, String, String, String); @@ -99,6 +113,29 @@ pub trait Parser: franklin_crypto::bellman::pairing::Engine { fn to_g2(x0: &str, x1: &str, y0: &str, y1: &str) -> Self::G2Affine; } +#[cfg(any(feature = "cuda", feature = "opencl"))] +pub trait Parser: MultiMillerLoop { + fn parse_g1(e: &Self::G1Affine, to_hex: bool) -> (String, String); + fn parse_g2(e: &Self::G2Affine, to_hex: bool) -> (String, String, String, String); + fn parse_g1_json(e: &Self::G1Affine, to_hex: bool) -> G1 { + let parsed = Self::parse_g1(e, to_hex); + G1 { + x: parsed.0, + y: parsed.1, + } + } + fn parse_g2_json(e: &Self::G2Affine, to_hex: bool) -> G2 { + let parsed = Self::parse_g2(e, to_hex); + G2 { + x: (parsed.0, parsed.1).into(), + y: (parsed.2, parsed.3).into(), + } + } + fn to_g1(x: &str, y: &str) -> Self::G1Affine; + fn to_g2(x0: &str, x1: &str, y0: &str, y1: &str) -> Self::G2Affine; +} + +#[cfg(not(any(feature = "cuda", feature = "opencl")))] pub fn render_scalar_to_str(el: &F, to_hex: bool) -> String { let repr = el.into_repr(); if to_hex { @@ -108,6 +145,7 @@ pub fn render_scalar_to_str(el: &F, to_hex: bool) -> String { } } +#[cfg(not(any(feature = "cuda", feature = "opencl")))] pub fn render_str_to_scalar(value: &str) -> F { let value = match value.starts_with("0x") { true => BigUint::from_str_radix(&value[2..], 16) @@ -118,6 +156,7 @@ pub fn render_str_to_scalar(value: &str) -> F { F::from_str(&value).unwrap() } +#[cfg(not(any(feature = "cuda", feature = "opencl")))] pub fn to_public_input(s: &str) -> Vec { let input: Vec = serde_json::from_str(s).unwrap(); input @@ -125,7 +164,7 @@ pub fn to_public_input(s: &str) -> Vec { .map(|hex_str| render_str_to_scalar::(hex_str)) .collect() } - +#[cfg(not(any(feature = "cuda", feature = "opencl")))] impl Parser for Bn256 { fn parse_g1(e: &Self::G1Affine, to_hex: bool) -> (String, String) { let (x, y) = e.into_xy_unchecked(); @@ -162,6 +201,7 @@ impl Parser for Bn256 { } } +#[cfg(not(any(feature = "cuda", feature = "opencl")))] impl Parser for Bls12 { fn parse_g1(e: &Self::G1Affine, to_hex: bool) -> (String, String) { let (x, y) = e.into_xy_unchecked(); @@ -198,6 +238,100 @@ impl Parser for Bls12 { } } +#[cfg(any(feature = "cuda", feature = "opencl"))] +pub fn render_fp_to_str(fp: &Fp, to_hex: bool) -> String { + let be_bytes = fp.to_bytes_be(); + let mut hex_string = String::new(); + hex_string.push_str("0x"); + for &b in be_bytes.iter() { + hex_string.push_str(&format!("{:02x}", b)); + } + if to_hex { + hex_string + } else { + repr_to_big(hex_string) + } +} + +#[cfg(any(feature = "cuda", feature = "opencl"))] +pub fn render_str_to_fp(value: &str) -> Fp { + let mut be_bytes = [0u8; 48]; + let hex_str; + if value.starts_with("0x") { + hex_str = value[2..].to_string(); + } else { + let big_uint = BigUint::from_str_radix(value, 10).unwrap(); + hex_str = big_uint.to_str_radix(16); + } + let final_hex_str = if hex_str.len() % 2 != 0 { + format!("0{}", hex_str) + } else { + hex_str + }; + let bytes = hex::decode(final_hex_str).expect("Invalid hex string"); + let start = 48 - bytes.len(); + be_bytes[start..].copy_from_slice(&bytes); + Fp::from_bytes_be(&be_bytes).unwrap() +} + +#[cfg(any(feature = "cuda", feature = "opencl"))] +pub fn render_str_to_scalar(value: &str) -> Scalar { + let mut be_bytes = [0u8; 32]; + let hex_str; + if value.starts_with("0x") { + hex_str = value[2..].to_string(); + } else { + let big_uint = BigUint::from_str_radix(value, 10).unwrap(); + hex_str = big_uint.to_str_radix(16); + } + let final_hex_str = if hex_str.len() % 2 != 0 { + format!("0{}", hex_str) + } else { + hex_str + }; + let bytes = hex::decode(final_hex_str).expect("Invalid hex string"); + let start = 32 - bytes.len(); + be_bytes[start..].copy_from_slice(&bytes); + Scalar::from_bytes_be(&be_bytes).unwrap() +} + +#[cfg(any(feature = "cuda", feature = "opencl"))] +pub fn to_public_input(s: &str) -> Vec { + let input: Vec = serde_json::from_str(s).unwrap(); + input + .iter() + .map(|hex_str| render_str_to_scalar(hex_str)) + .collect() +} + +#[cfg(any(feature = "cuda", feature = "opencl"))] +impl Parser for Bls12 { + fn parse_g1(e: &Self::G1Affine, to_hex: bool) -> (String, String) { + let (x, y) = (e.x(), e.y()); + (render_fp_to_str(&x, to_hex), render_fp_to_str(&y, to_hex)) + } + + fn parse_g2(e: &Self::G2Affine, to_hex: bool) -> (String, String, String, String) { + let (x, y) = (e.x(), e.y()); + ( + render_fp_to_str(&x.c0(), to_hex), + render_fp_to_str(&x.c1(), to_hex), + render_fp_to_str(&y.c0(), to_hex), + render_fp_to_str(&y.c1(), to_hex), + ) + } + + fn to_g1(x: &str, y: &str) -> Self::G1Affine { + G1Affine::from_raw_unchecked(render_str_to_fp(x), render_str_to_fp(y), false) + } + + fn to_g2(x0: &str, x1: &str, y0: &str, y1: &str) -> Self::G2Affine { + let x = Fp2::new(render_str_to_fp(x0), render_str_to_fp(x1)); + let y = Fp2::new(render_str_to_fp(y0), render_str_to_fp(y1)); + G2Affine::from_raw_unchecked(x, y, false) + } +} + pub fn serialize_vk( vk: &VerifyingKey

, curve_type: &str, @@ -267,6 +401,7 @@ pub fn to_proof(s: &str) -> Proof

{ } #[cfg(test)] +#[cfg(not(any(feature = "cuda", feature = "opencl")))] mod tests { use super::*; @@ -325,3 +460,28 @@ mod tests { assert_eq!(proof_from_bin.a, proof_from_json.a, "Proofs are not equal"); } } + +#[cfg(test)] +#[cfg(any(feature = "cuda", feature = "opencl"))] +mod tests { + use super::*; + + #[test] + fn test_serialize_vk_bls12381() { + let mut reader = std::io::BufReader::with_capacity( + 1 << 24, + std::fs::File::open("./test-vectors/verification_key_bls12381.bin").unwrap(), + ); + let vk_from_bin = VerifyingKey::::read(&mut reader).unwrap(); + let result = serialize_vk(&vk_from_bin, "BLS12381", false).unwrap(); + std::fs::write("./test-vectors/verification_key_bls12381.json", result) + .expect("Unable to write data to file"); + let json_data = std::fs::read_to_string("./test-vectors/verification_key_bls12381.json") + .expect("Unable to read the JSON file"); + let verifying_key_from_json = to_verification_key::(&json_data); + assert_eq!( + vk_from_bin.alpha_g1, verifying_key_from_json.alpha_g1, + "VerificationKey are not equal" + ); + } +} diff --git a/groth16/src/lib.rs b/groth16/src/lib.rs index 42e7433f..d8e5def1 100644 --- a/groth16/src/lib.rs +++ b/groth16/src/lib.rs @@ -3,6 +3,12 @@ pub mod groth16; pub mod json_utils; mod template; -pub use bellman_ce::pairing::ff; -pub use ff::*; -pub use franklin_crypto::bellman as bellman_ce; +#[cfg(not(any(feature = "cuda", feature = "opencl")))] +mod non_gpu_specific { + pub use bellman_ce::pairing::ff; + pub use ff::*; + pub use franklin_crypto::bellman as bellman_ce; +} + +#[cfg(not(any(feature = "cuda", feature = "opencl")))] +pub use non_gpu_specific::*; diff --git a/plonky/Cargo.toml b/plonky/Cargo.toml index bcd98c1a..56cffd94 100644 --- a/plonky/Cargo.toml +++ b/plonky/Cargo.toml @@ -4,8 +4,8 @@ version = "0.0.2" edition = "2021" license = "Apache-2.0" description = "Eigen Plonk" -documentation = "eigen.cash" -homepage = "eigen.cash" +documentation = "eigen.market" +homepage = "eigen.market" [lib] crate-type = ["cdylib", "rlib"] diff --git a/recursion/Cargo.toml b/recursion/Cargo.toml index 428edcb2..08e4dc6f 100644 --- a/recursion/Cargo.toml +++ b/recursion/Cargo.toml @@ -25,12 +25,9 @@ anyhow = "1.0.79" fnv = { version = "1.0.3", default-features = false } num = { version = "0.4.0" } byteorder = "1" -franklin-crypto = { git = "https://github.com/matter-labs/franklin-crypto", branch = "beta", features = [ "plonk" ], version = "0.0.5"} -#franklin-crypto = { path = "../../franklin-crypto", features = [ "plonk" ], version = "0.0.5"} ff = {package="ff_ce" , version="0.12", features = ["derive"]} fields = { path = "../fields", default-features = false } starky = { path = "../starky", default-features = false } -plonky = { path = "../plonky", default-features = false } algebraic = { path = "../algebraic", default-features = false } powdr = { git = "https://github.com/0xEigenLabs/powdr", branch = "binary-mux2", default-features = false } @@ -52,6 +49,6 @@ rand = { version="0.6.5", features = ["wasm-bindgen"] } wasm-bindgen-test = "0.3" [features] -default = ["franklin-crypto/multicore", "wasmer/singlepass", "starky/default", "plonky/default"] +default = ["wasmer/singlepass", "starky/default"] wasm = ["wasmer/js-default"] -avx512 = ["fields/avx512", "plonky/avx512", "starky/avx512", "powdr/starky-avx512"] +avx512 = ["fields/avx512", "starky/avx512", "powdr/starky-avx512"] \ No newline at end of file diff --git a/recursion/src/compressor12/plonk_setup.rs b/recursion/src/compressor12/plonk_setup.rs index 16160afe..402b31c7 100644 --- a/recursion/src/compressor12/plonk_setup.rs +++ b/recursion/src/compressor12/plonk_setup.rs @@ -5,10 +5,10 @@ use super::{ }; use crate::pilcom::compile_pil_from_str; use crate::r1cs2plonk::{r1cs2plonk, PlonkAdd, PlonkGate}; +use algebraic::circom_circuit::R1CS; use array_tool::vec::Shift; -use plonky::circom_circuit::R1CS; -use plonky::field_gl::Fr as FGL; -use plonky::field_gl::GL; +use fields::field_gl::Fr as FGL; +use fields::field_gl::GL; use starky::helper; use starky::polsarray::{PolKind, PolsArray}; use starky::types::PIL; diff --git a/test/stark_aggregation.sh b/test/stark_aggregation.sh index fe969e28..9e4f37c8 100755 --- a/test/stark_aggregation.sh +++ b/test/stark_aggregation.sh @@ -51,6 +51,9 @@ elif [ "x${USE_AVX512}" = "xyes" ]; then # build with avx512 feature RUSTFLAGS='-C target-feature=+avx512f,+avx512bw,+avx512cd,+avx512dq,+avx512vl' \ cargo build --release --features profiler --features avx512 +elif [ "x${USE_CUDA}" = "xyes" ]; then + # build with cuda feature + cargo build --release --features profiler --features cuda else cargo build --release --features profiler fi diff --git a/zkit/Cargo.toml b/zkit/Cargo.toml index 88c971df..28f98845 100644 --- a/zkit/Cargo.toml +++ b/zkit/Cargo.toml @@ -4,8 +4,8 @@ version = "0.0.1" edition = "2021" license = "Apache-2.0" description = "Eigen ZKit command tools" -documentation = "https://eigen.cash" -homepage = "https://eigen.cash" +documentation = "https://eigen.market" +homepage = "https://eigen.market" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -29,3 +29,5 @@ anyhow = { version = "1.0", default-features = false } default = [] profiler = ["plonky/profiler", "starky/profiler", "groth16/profiler"] avx512 = ["starky/avx512", "recursion/avx512"] +cuda = ["groth16/cuda"] +opencl = ["groth16/opencl"]