diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a527e55 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/decanus/go-rln + +go 1.16 diff --git a/lib/librln.d b/lib/librln.d new file mode 100644 index 0000000..b115406 --- /dev/null +++ b/lib/librln.d @@ -0,0 +1 @@ +/Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/target/release/librln.rlib: /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/circuit/mod.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/circuit/polynomial.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/circuit/poseidon.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/circuit/rln.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/ffi.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/lib.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/merkle.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/poseidon.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/public.rs /Users/deaneigenmann/go/src/github.com/decanus/go-rln/lib/rln/src/utils.rs diff --git a/lib/librln.rlib b/lib/librln.rlib new file mode 100644 index 0000000..6b0b650 Binary files /dev/null and b/lib/librln.rlib differ diff --git a/lib/rln/.gitignore b/lib/rln/.gitignore new file mode 100644 index 0000000..d9d206e --- /dev/null +++ b/lib/rln/.gitignore @@ -0,0 +1,8 @@ +/target +/pkg +/examples/www +node_modules +*.key +Cargo.lock +.cargo +tmp_wasm \ No newline at end of file diff --git a/lib/rln/Cargo.toml b/lib/rln/Cargo.toml new file mode 100644 index 0000000..01a8185 --- /dev/null +++ b/lib/rln/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "rln" +version = "0.1.0" +authors = ["Onur Kılıç "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +multicore = ["sapling-crypto/multicore", "bellman/multicore"] +wasm = ["sapling-crypto/wasm", "bellman/wasm", "bellman/nolog"] +bench = [] + +[dependencies] +rand = "0.4" +blake2 = "0.8.1" +sapling-crypto = { package = "sapling-crypto_ce", version = "0.1.3", default-features = false } +# sapling-crypto = {package = "sapling-crypto_ce", path = "../sapling-crypto", default-features = false } +bellman = { package = "bellman_ce", version = "0.3.4", default-features = false } +# bellman = {package = "bellman_ce", path = "../bellman", default-features = false } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +hex = "0.4" +console_error_panic_hook = { version = "0.1.1" } +wasm-bindgen = "=0.2.60" +# wee_alloc = "0.4.5" +web-sys = {version = "0.3", features = ["console", "Performance", "Window"]} +js-sys = "0.3.37" + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-test = "0.3" + +[profile.release] +opt-level = 3 +lto = "thin" +incremental = true + +# build all our deps in release mode +[profile.dev.package."*"] +opt-level = 3 + +[profile.bench] +opt-level = 3 +debug = false +rpath = false +lto = "thin" +incremental = true +debug-assertions = false + + +[profile.test] +opt-level = 3 +incremental = true +debug-assertions = true +debug = true + + diff --git a/lib/rln/README.md b/lib/rln/README.md new file mode 100644 index 0000000..6a9e75b --- /dev/null +++ b/lib/rln/README.md @@ -0,0 +1,41 @@ +# RLN + +This is the development repo of rate limit nullifier zkSNARK circuits. + +For details, see work in progress document [here](https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view) + +## Test + +``` +cargo test --release --features multicore rln_32 -- --nocapture +``` + +## Generate Test Keys + +``` +cargo run --release --example export_test_keys +``` + +## Wasm Support + +### Build + +``` +wasm-pack build --release --target=nodejs --scope=rln --out-name=$PACKAGE --out-dir=$PACKAGE_DIR -- --features wasm +``` + +### Test + +With wasm-pack: + +``` +wasm-pack test --release --node -- --features wasm +``` + +With cargo: + +Follow the steps [here](https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/usage.html#appendix-using-wasm-bindgen-test-without-wasm-pack) before running the test, then run: + +``` +cargo test --release --target wasm32-unknown-unknown --features wasm +``` \ No newline at end of file diff --git a/lib/rln/examples/export_test_keys/main.rs b/lib/rln/examples/export_test_keys/main.rs new file mode 100644 index 0000000..14d352d --- /dev/null +++ b/lib/rln/examples/export_test_keys/main.rs @@ -0,0 +1,38 @@ +#[cfg(not(target_arch = "wasm32"))] +fn main() { + use sapling_crypto::bellman::pairing::bn256::Bn256; + let merkle_depth = 32usize; + test_keys::export::(merkle_depth); +} + +#[cfg(target_arch = "wasm32")] +fn main() { + panic!("should not be run in wasm"); +} + +#[cfg(not(target_arch = "wasm32"))] +mod test_keys { + use sapling_crypto::bellman::pairing::Engine; + pub fn export(merkle_depth: usize) { + use rand::{SeedableRng, XorShiftRng}; + use rln::circuit::poseidon::PoseidonCircuit; + use rln::circuit::rln::{RLNCircuit, RLNInputs}; + use rln::poseidon::PoseidonParams; + use sapling_crypto::bellman::groth16::generate_random_parameters; + use std::fs::File; + + let poseidon_params = PoseidonParams::::new(8, 55, 3, None, None, None); + let mut rng = XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let hasher = PoseidonCircuit::new(poseidon_params.clone()); + let circuit = RLNCircuit:: { + inputs: RLNInputs::::empty(merkle_depth), + hasher: hasher.clone(), + }; + let parameters = generate_random_parameters(circuit, &mut rng).unwrap(); + let mut file_vk = File::create("verifier.key").unwrap(); + let vk = parameters.vk.clone(); + vk.write(&mut file_vk).unwrap(); + let mut file_paramaters = File::create("parameters.key").unwrap(); + parameters.write(&mut file_paramaters).unwrap(); + } +} diff --git a/lib/rln/src/circuit/bench.rs b/lib/rln/src/circuit/bench.rs new file mode 100644 index 0000000..099c8e6 --- /dev/null +++ b/lib/rln/src/circuit/bench.rs @@ -0,0 +1,198 @@ +use crate::circuit::poseidon::PoseidonCircuit; +use crate::circuit::rln::{RLNCircuit, RLNInputs}; +use crate::merkle::MerkleTree; +use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams}; +use rand::{Rand, SeedableRng, XorShiftRng}; +use sapling_crypto::bellman::groth16::*; +use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use sapling_crypto::bellman::pairing::Engine; +use sapling_crypto::bellman::Circuit; +use sapling_crypto::circuit::test::TestConstraintSystem; +use std::error::Error; +use std::io::{self, ErrorKind, Read, Write}; +use std::thread::sleep; +use std::time::{Duration, Instant}; + +use crate::public::RLN; + +pub struct ProverBenchResult { + pub prover_key_size: usize, + pub prover_time: f64, +} + +impl ProverBenchResult { + pub fn new() -> ProverBenchResult { + ProverBenchResult { + prover_key_size: 0, + prover_time: 0f64, + } + } +} + +pub fn run_rln_prover_bench( + merkle_depth: usize, + poseidon_params: PoseidonParams, +) -> ProverBenchResult { + RLNTest::new(merkle_depth, Some(poseidon_params)).run_prover_bench() +} + +pub struct RLNTest +where + E: Engine, +{ + rln: RLN, + merkle_depth: usize, +} + +impl RLNTest +where + E: Engine, +{ + fn rng() -> XorShiftRng { + XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]) + } + + fn empty_inputs(merkle_depth: usize) -> RLNInputs { + RLNInputs:: { + share_x: None, + share_y: None, + epoch: None, + nullifier: None, + root: None, + id_key: None, + auth_path: vec![None; merkle_depth], + } + } + + pub fn new(merkle_depth: usize, poseidon_params: Option>) -> RLNTest { + RLNTest { + rln: RLN::new(merkle_depth, poseidon_params), + merkle_depth, + } + } + + pub fn hasher(&self) -> PoseidonHasher { + self.rln.hasher() + } + + pub fn valid_inputs(&self) -> RLNInputs { + let mut rng = Self::rng(); + let mut hasher = self.rln.hasher(); + + // Initialize empty merkle tree + let merkle_depth = self.merkle_depth; + let mut membership_tree = MerkleTree::empty(hasher.clone(), merkle_depth); + + // A. setup an identity + + let id_key = E::Fr::rand(&mut rng); + let id_comm: E::Fr = hasher.hash(vec![id_key.clone()]); + + // B. insert to the membership tree + + let id_index = 6; // any number below 2^depth will work + membership_tree.update(id_index, id_comm); + + // C.1 get membership witness + + let auth_path = membership_tree.witness(id_index); + assert!(membership_tree.check_inclusion(auth_path.clone(), id_index, id_key.clone())); + + // C.2 prepare sss + + // get current epoch + let epoch = E::Fr::rand(&mut rng); + + let signal_hash = E::Fr::rand(&mut rng); + // evaluation point is the signal_hash + let share_x = signal_hash.clone(); + + // calculate current line equation + let a_0 = id_key.clone(); + let a_1: E::Fr = hasher.hash(vec![a_0, epoch]); + + // evaluate line equation + let mut share_y = a_1.clone(); + share_y.mul_assign(&share_x); + share_y.add_assign(&a_0); + + // calculate nullfier + let nullifier = hasher.hash(vec![a_1]); + + // compose the circuit + + let inputs = RLNInputs:: { + share_x: Some(share_x), + share_y: Some(share_y), + epoch: Some(epoch), + nullifier: Some(nullifier), + root: Some(membership_tree.root()), + id_key: Some(id_key), + auth_path: auth_path.into_iter().map(|w| Some(w)).collect(), + }; + + inputs + } + + pub fn synthesize(&self) -> usize { + let hasher = PoseidonCircuit::new(self.rln.poseidon_params()); + let inputs = self.valid_inputs(); + let circuit = RLNCircuit:: { + inputs: inputs.clone(), + hasher: hasher.clone(), + }; + + let mut cs = TestConstraintSystem::::new(); + + let circuit = circuit.clone(); + circuit.synthesize(&mut cs).unwrap(); + let unsatisfied = cs.which_is_unsatisfied(); + if unsatisfied.is_some() { + panic!("unsatisfied\n{}", unsatisfied.unwrap()); + } + let unconstrained = cs.find_unconstrained(); + if !unconstrained.is_empty() { + panic!("unconstrained\n{}", unconstrained); + } + assert!(cs.is_satisfied()); + cs.num_constraints() + } + + pub fn run_prover_bench(&self) -> ProverBenchResult { + let mut raw_inputs: Vec = Vec::new(); + let inputs = self.valid_inputs(); + inputs.write(&mut raw_inputs).unwrap(); + + let mut proof: Vec = Vec::new(); + let now = Instant::now(); + self.rln + .generate_proof(raw_inputs.as_slice(), &mut proof) + .unwrap(); + let prover_time = now.elapsed().as_millis() as f64 / 1000.0; + + let mut raw_public_inputs: Vec = Vec::new(); + inputs.write_public_inputs(&mut raw_public_inputs).unwrap(); + + assert!( + self.rln + .verify(proof.as_slice(), raw_public_inputs.as_slice()) + .unwrap(), + true + ); + + let mut circuit_parameters: Vec = Vec::new(); + self.rln + .export_circuit_parameters(&mut circuit_parameters) + .unwrap(); + let prover_key_size = circuit_parameters.len(); + + ProverBenchResult { + prover_time, + prover_key_size, + } + } + + pub fn export_circuit_parameters(&self, w: W) -> io::Result<()> { + self.rln.export_circuit_parameters(w) + } +} diff --git a/lib/rln/src/circuit/mod.rs b/lib/rln/src/circuit/mod.rs new file mode 100644 index 0000000..7779873 --- /dev/null +++ b/lib/rln/src/circuit/mod.rs @@ -0,0 +1,6 @@ +mod polynomial; +pub mod poseidon; +pub mod rln; + +#[cfg(any(test, feature = "bench"))] +pub mod bench; diff --git a/lib/rln/src/circuit/polynomial.rs b/lib/rln/src/circuit/polynomial.rs new file mode 100644 index 0000000..d49801c --- /dev/null +++ b/lib/rln/src/circuit/polynomial.rs @@ -0,0 +1,46 @@ +use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use sapling_crypto::bellman::pairing::Engine; +use sapling_crypto::bellman::{Circuit, ConstraintSystem, SynthesisError, Variable}; +use sapling_crypto::circuit::{boolean, ecc, num, Assignment}; + +// helper for horner evaluation methods +// b = a_0 + a_1 * x +pub fn allocate_add_with_coeff( + mut cs: CS, + a1: &num::AllocatedNum, + x: &num::AllocatedNum, + a0: &num::AllocatedNum, +) -> Result, SynthesisError> +where + E: Engine, + CS: ConstraintSystem, +{ + let ax = num::AllocatedNum::alloc(cs.namespace(|| "a1x"), || { + let mut ax_val = *a1.get_value().get()?; + let x_val = *x.get_value().get()?; + ax_val.mul_assign(&x_val); + Ok(ax_val) + })?; + + cs.enforce( + || "a1*x", + |lc| lc + a1.get_variable(), + |lc| lc + x.get_variable(), + |lc| lc + ax.get_variable(), + ); + + let y = num::AllocatedNum::alloc(cs.namespace(|| "y"), || { + let ax_val = *ax.get_value().get()?; + let mut y_val = *a0.get_value().get()?; + y_val.add_assign(&ax_val); + Ok(y_val) + })?; + + cs.enforce( + || "enforce y", + |lc| lc + ax.get_variable() + a0.get_variable(), + |lc| lc + CS::one(), + |lc| lc + y.get_variable(), + ); + Ok(y) +} diff --git a/lib/rln/src/circuit/poseidon.rs b/lib/rln/src/circuit/poseidon.rs new file mode 100644 index 0000000..c86cbfd --- /dev/null +++ b/lib/rln/src/circuit/poseidon.rs @@ -0,0 +1,403 @@ +use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams}; +use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use sapling_crypto::bellman::pairing::Engine; +use sapling_crypto::bellman::{Circuit, ConstraintSystem, LinearCombination, SynthesisError}; +use sapling_crypto::circuit::{boolean, ecc, num, Assignment}; + +#[derive(Clone)] +struct Element +where + E: Engine, +{ + an: Option>, + nu: Option>, +} + +enum RoundType { + Full, + Partial, + Exhausted, +} + +struct RoundCtx<'a, E> +where + E: Engine, +{ + number: usize, + params: &'a PoseidonParams, +} + +struct State +where + E: Engine, +{ + elements: Vec>, +} + +#[derive(Clone)] +pub struct PoseidonCircuit +where + E: Engine, +{ + params: PoseidonParams, +} + +impl Element +where + E: Engine, +{ + pub fn new_from_alloc(an: num::AllocatedNum) -> Self { + return Element { + an: Some(an), + nu: None, + }; + } + + pub fn new_from_num(nu: num::Num) -> Self { + return Element { + an: None, + nu: Some(nu), + }; + } + + pub fn is_allocated(&self) -> bool { + return self.an.is_some(); + } + + pub fn is_number(&self) -> bool { + return self.nu.is_some(); + } + + pub fn update_with_allocated(&mut self, an: num::AllocatedNum) { + self.an = Some(an); + self.nu = None; + } + + pub fn update_with_num(&mut self, nu: num::Num) { + self.nu = Some(nu); + self.an = None; + } + + pub fn num(&self) -> num::Num { + if let Some(nu) = self.nu.clone() { + nu + } else { + match self.an.clone() { + Some(an) => num::Num::from(an), + None => panic!("element not exist"), + } + } + } + + pub fn allocate>( + &self, + mut cs: CS, + ) -> Result, SynthesisError> { + match self.nu.clone() { + Some(nu) => { + let v = num::AllocatedNum::alloc(cs.namespace(|| "allocate num"), || { + nu.get_value() + .ok_or_else(|| SynthesisError::AssignmentMissing) + })?; + cs.enforce( + || format!("enforce allocated"), + |_| nu.lc(E::Fr::one()), + |lc| lc + CS::one(), + |lc| lc + v.get_variable(), + ); + Ok(v) + } + None => panic!(""), + } + } + + pub fn allocated(&self) -> Option> { + self.an.clone() + } +} + +impl<'a, E> RoundCtx<'a, E> +where + E: Engine, +{ + pub fn new(params: &'a PoseidonParams) -> Self { + RoundCtx { + params, + number: 0usize, + } + } + + pub fn width(&self) -> usize { + self.params.width() + } + + pub fn round_number(&self) -> usize { + self.number + } + + pub fn is_full_round(&self) -> bool { + match self.round_type() { + RoundType::Full => true, + _ => false, + } + } + + pub fn is_exhausted(&self) -> bool { + match self.round_type() { + RoundType::Exhausted => true, + _ => false, + } + } + + pub fn is_last_round(&self) -> bool { + self.number == self.params.total_rounds() - 1 + } + + pub fn in_transition(&self) -> bool { + let a1 = self.params.full_round_half_len(); + let a2 = a1 + self.params.partial_round_len(); + self.number == a1 - 1 || self.number == a2 - 1 + } + + pub fn round_constant(&self) -> E::Fr { + self.params.round_constant(self.number) + } + + pub fn mds_matrix_row(&self, i: usize) -> Vec { + let w = self.width(); + let matrix = self.params.mds_matrix(); + matrix[i * w..(i + 1) * w].to_vec() + } + + pub fn round_type(&self) -> RoundType { + let a1 = self.params.full_round_half_len(); + let (a2, a3) = ( + a1 + self.params.partial_round_len(), + self.params.total_rounds(), + ); + if self.number < a1 { + RoundType::Full + } else if self.number >= a1 && self.number < a2 { + RoundType::Partial + } else if self.number >= a2 && self.number < a3 { + RoundType::Full + } else { + RoundType::Exhausted + } + } + + pub fn round_end(&mut self) { + self.number += 1; + } +} + +impl State +where + E: Engine, +{ + pub fn new(elements: Vec>) -> Self { + Self { elements } + } + + pub fn first_allocated>( + &mut self, + mut cs: CS, + ) -> Result, SynthesisError> { + let el = match self.elements[0].allocated() { + Some(an) => an, + None => self.elements[0].allocate(cs.namespace(|| format!("alloc first")))?, + }; + Ok(el) + } + + fn sbox>( + &mut self, + mut cs: CS, + ctx: &mut RoundCtx, + ) -> Result<(), SynthesisError> { + assert_eq!(ctx.width(), self.elements.len()); + + for i in 0..if ctx.is_full_round() { ctx.width() } else { 1 } { + let round_constant = ctx.round_constant(); + let si = { + match self.elements[i].allocated() { + Some(an) => an, + None => self.elements[i] + .allocate(cs.namespace(|| format!("alloc sbox input {}", i)))?, + } + }; + let si2 = num::AllocatedNum::alloc( + cs.namespace(|| format!("square with round constant {}", i)), + || { + let mut val = *si.get_value().get()?; + val.add_assign(&round_constant); + val.square(); + Ok(val) + }, + )?; + cs.enforce( + || format!("constraint square with round constant {}", i), + |lc| lc + si.get_variable() + (round_constant, CS::one()), + |lc| lc + si.get_variable() + (round_constant, CS::one()), + |lc| lc + si2.get_variable(), + ); + let si4 = si2.square(cs.namespace(|| format!("si^4 {}", i)))?; + let si5 = num::AllocatedNum::alloc(cs.namespace(|| format!("si^5 {}", i)), || { + let mut val = *si4.get_value().get()?; + let mut si_val = *si.get_value().get()?; + si_val.add_assign(&round_constant); + val.mul_assign(&si_val); + Ok(val) + })?; + cs.enforce( + || format!("constraint sbox result {}", i), + |lc| lc + si.get_variable() + (round_constant, CS::one()), + |lc| lc + si4.get_variable(), + |lc| lc + si5.get_variable(), + ); + self.elements[i].update_with_allocated(si5); + } + + Ok(()) + } + + fn mul_mds_matrix>( + &mut self, + ctx: &mut RoundCtx, + ) -> Result<(), SynthesisError> { + assert_eq!(ctx.width(), self.elements.len()); + + if !ctx.is_last_round() { + // skip mds multiplication in last round + + let mut new_state: Vec> = Vec::new(); + let w = ctx.width(); + + for i in 0..w { + let row = ctx.mds_matrix_row(i); + let mut acc = num::Num::::zero(); + for j in 0..w { + let mut r = self.elements[j].num(); + r.scale(row[j]); + acc.add_assign(&r); + } + new_state.push(acc); + } + + // round ends here + let is_full_round = ctx.is_full_round(); + let in_transition = ctx.in_transition(); + ctx.round_end(); + + // add round constants just after mds if + // first full round has just ended + // or in partial rounds expect the last one. + if in_transition == is_full_round { + // add round constants for elements in {1, t} + let round_constant = ctx.round_constant(); + for i in 1..w { + let mut constant_as_num = num::Num::::zero(); + constant_as_num = constant_as_num.add_bool_with_coeff( + CS::one(), + &boolean::Boolean::Constant(true), + round_constant, + ); + new_state[i].add_assign(&constant_as_num); + } + } + + for (s0, s1) in self.elements.iter_mut().zip(new_state) { + s0.update_with_num(s1); + } + } else { + // terminates hades + ctx.round_end(); + } + Ok(()) + } +} + +impl PoseidonCircuit +where + E: Engine, +{ + pub fn new(params: PoseidonParams) -> Self { + Self { params: params } + } + + pub fn width(&self) -> usize { + self.params.width() + } + + pub fn alloc>( + &self, + mut cs: CS, + input: Vec>, + ) -> Result, SynthesisError> { + assert!(input.len() < self.params.width()); + + let mut elements: Vec> = input + .iter() + .map(|el| Element::new_from_alloc(el.clone())) + .collect(); + elements.resize(self.width(), Element::new_from_num(num::Num::zero())); + + let mut state = State::new(elements); + let mut ctx = RoundCtx::new(&self.params); + loop { + match ctx.round_type() { + RoundType::Exhausted => { + break; + } + _ => { + let round_number = ctx.round_number(); + state.sbox(cs.namespace(|| format!("sbox {}", round_number)), &mut ctx)?; + state.mul_mds_matrix::(&mut ctx)?; + } + } + } + state.first_allocated(cs.namespace(|| format!("allocate result"))) + } +} + +#[test] +fn test_poseidon_circuit() { + use sapling_crypto::bellman::pairing::bn256::{Bn256, Fr}; + use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; + use sapling_crypto::circuit::test::TestConstraintSystem; + + let mut cs = TestConstraintSystem::::new(); + let params = PoseidonParams::new(8, 55, 3, None, None, None); + + let inputs: Vec = ["0", "0"] + .iter() + .map(|e| Fr::from_str(e).unwrap()) + .collect(); + let allocated_inputs = inputs + .clone() + .into_iter() + .enumerate() + .map(|(i, e)| { + let a = num::AllocatedNum::alloc(cs.namespace(|| format!("input {}", i)), || Ok(e)); + a.unwrap() + }) + .collect(); + + let circuit = PoseidonCircuit::::new(params.clone()); + let res_allocated = circuit + .alloc(cs.namespace(|| "hash alloc"), allocated_inputs) + .unwrap(); + let result = res_allocated.get_value().unwrap(); + let mut poseidon = PoseidonHasher::new(params.clone()); + let expected = poseidon.hash(inputs); + + assert_eq!(result, expected); + assert!(cs.is_satisfied()); + println!( + "number of constraints for (t: {}, rf: {}, rp: {}), {}", + params.width(), + params.full_round_half_len() * 2, + params.partial_round_len(), + cs.num_constraints() + ); +} diff --git a/lib/rln/src/circuit/rln.rs b/lib/rln/src/circuit/rln.rs new file mode 100644 index 0000000..0067e05 --- /dev/null +++ b/lib/rln/src/circuit/rln.rs @@ -0,0 +1,480 @@ +use crate::circuit::polynomial::allocate_add_with_coeff; +use crate::circuit::poseidon::PoseidonCircuit; +use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams}; +use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use sapling_crypto::bellman::pairing::Engine; +use sapling_crypto::bellman::{Circuit, ConstraintSystem, SynthesisError, Variable}; +use sapling_crypto::circuit::{boolean, ecc, num, Assignment}; +use sapling_crypto::jubjub::{JubjubEngine, JubjubParams, PrimeOrder}; + +use std::io::{self, Read, Write}; + +// Rate Limit Nullifier + +#[derive(Clone)] +pub struct RLNInputs +where + E: Engine, +{ + // Public inputs + + // share, (x, y), + // where x should be hash of the signal + // and y is the evaluation + pub share_x: Option, + pub share_y: Option, + + // epoch is the external nullifier + // we derive the line equation and the nullifier from epoch + pub epoch: Option, + + // nullifier + pub nullifier: Option, + + // root is the current state of membership set + pub root: Option, + + // Private inputs + + // id_key must be a preimage of a leaf in membership tree. + // id_key also together with epoch will be used to construct + // a secret line equation together with the epoch + pub id_key: Option, + + // authentication path of the member + pub auth_path: Vec>, +} + +impl RLNInputs +where + E: Engine, +{ + pub fn public_inputs(&self) -> Vec { + vec![ + self.root.unwrap(), + self.epoch.unwrap(), + self.share_x.unwrap(), + self.share_y.unwrap(), + self.nullifier.unwrap(), + ] + } + + pub fn merkle_depth(&self) -> usize { + self.auth_path.len() + } + + pub fn empty(merkle_depth: usize) -> RLNInputs { + RLNInputs:: { + share_x: None, + share_y: None, + epoch: None, + nullifier: None, + root: None, + id_key: None, + auth_path: vec![None; merkle_depth], + } + } + + pub fn read(mut reader: R) -> io::Result> { + let mut buf = ::Repr::default(); + + buf.read_le(&mut reader)?; + let share_x = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + + buf.read_le(&mut reader)?; + let share_y = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + buf.read_le(&mut reader)?; + let epoch = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + buf.read_le(&mut reader)?; + let nullifier = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + buf.read_le(&mut reader)?; + let root = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + buf.read_le(&mut reader)?; + let id_key = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let auth_path = Self::decode_auth_path(&mut reader)?; + Ok(RLNInputs { + share_x: Some(share_x), + share_y: Some(share_y), + epoch: Some(epoch), + nullifier: Some(nullifier), + root: Some(root), + id_key: Some(id_key), + auth_path, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + self.share_x + .unwrap() + .into_repr() + .write_le(&mut writer) + .unwrap(); + self.share_y + .unwrap() + .into_repr() + .write_le(&mut writer) + .unwrap(); + self.epoch + .unwrap() + .into_repr() + .write_le(&mut writer) + .unwrap(); + self.nullifier + .unwrap() + .into_repr() + .write_le(&mut writer) + .unwrap(); + self.root + .unwrap() + .into_repr() + .write_le(&mut writer) + .unwrap(); + self.id_key + .unwrap() + .into_repr() + .write_le(&mut writer) + .unwrap(); + Self::encode_auth_path(&mut writer, self.auth_path.clone()).unwrap(); + Ok(()) + } + + pub fn read_public_inputs(mut reader: R) -> io::Result> { + let mut buf = ::Repr::default(); + buf.read_le(&mut reader)?; + let root = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + buf.read_le(&mut reader)?; + let epoch = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + buf.read_le(&mut reader)?; + let share_x = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + buf.read_le(&mut reader)?; + let share_y = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + buf.read_le(&mut reader)?; + let nullifier = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(vec![root, epoch, share_x, share_y, nullifier]) + } + + pub fn write_public_inputs(&self, mut writer: W) -> io::Result<()> { + self.root.unwrap().into_repr().write_le(&mut writer)?; + self.epoch.unwrap().into_repr().write_le(&mut writer)?; + self.share_x.unwrap().into_repr().write_le(&mut writer)?; + self.share_y.unwrap().into_repr().write_le(&mut writer)?; + self.nullifier.unwrap().into_repr().write_le(&mut writer)?; + Ok(()) + } + + pub fn encode_auth_path( + mut writer: W, + auth_path: Vec>, + ) -> io::Result<()> { + let path_len = auth_path.len() as u8; + writer.write(&[path_len])?; + for el in auth_path.iter() { + let c = el.unwrap(); + if c.1 { + writer.write(&[1])?; + } else { + writer.write(&[0])?; + } + c.0.into_repr().write_le(&mut writer).unwrap(); + } + Ok(()) + } + + pub fn decode_auth_path(mut reader: R) -> io::Result>> { + let mut byte_buf = vec![0u8; 1]; + let mut el_buf = ::Repr::default(); + let mut auth_path: Vec> = vec![]; + reader.read_exact(&mut byte_buf)?; + let path_len = byte_buf[0]; + if path_len < 2 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid path length", + )); + } + for _ in 0..path_len { + reader.read_exact(&mut byte_buf)?; + let path_dir = match byte_buf[0] { + 0u8 => false, + 1u8 => true, + _ => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid path direction", + )) + } + }; + el_buf.read_le(&mut reader)?; + let node = E::Fr::from_repr(el_buf) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + auth_path.push(Some((node, path_dir))); + } + Ok(auth_path) + } +} + +#[derive(Clone)] +pub struct RLNCircuit +where + E: Engine, +{ + pub inputs: RLNInputs, + pub hasher: PoseidonCircuit, +} + +impl Circuit for RLNCircuit +where + E: Engine, +{ + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { + // 1. Part + // Membership constraints + // root == merkle_proof(auth_path, preimage_of_leaf) + + let root = num::AllocatedNum::alloc(cs.namespace(|| "root"), || { + let value = self.inputs.root.clone(); + Ok(*value.get()?) + })?; + root.inputize(cs.namespace(|| "root is public"))?; + + let preimage = num::AllocatedNum::alloc(cs.namespace(|| "preimage"), || { + let value = self.inputs.id_key; + Ok(*value.get()?) + })?; + + // identity is a leaf of membership tree + + let identity = self + .hasher + .alloc(cs.namespace(|| "identity"), vec![preimage.clone()])?; + + // accumulator up to the root + + let mut acc = identity.clone(); + + // ascend the tree + + let auth_path_witness = self.inputs.auth_path.clone(); + for (i, e) in auth_path_witness.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("auth path {}", i)); + let position = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "position bit"), + e.map(|e| e.1), + )?); + let path_element = + num::AllocatedNum::alloc(cs.namespace(|| "path element"), || Ok(e.get()?.0))?; + + let (xr, xl) = num::AllocatedNum::conditionally_reverse( + cs.namespace(|| "conditional reversal of preimage"), + &acc, + &path_element, + &position, + )?; + + acc = self + .hasher + .alloc(cs.namespace(|| "hash couple"), vec![xl, xr])?; + } + + // see if it is a member + + cs.enforce( + || "enforce membership", + |lc| lc + acc.get_variable(), + |lc| lc + CS::one(), + |lc| lc + root.get_variable(), + ); + + // 2. Part + // Line Equation Constaints + // a_1 = hash(a_0, epoch) + // share_y == a_0 + a_1 * share_x + + let epoch = num::AllocatedNum::alloc(cs.namespace(|| "epoch"), || { + let value = self.inputs.epoch.clone(); + Ok(*value.get()?) + })?; + epoch.inputize(cs.namespace(|| "epoch is public"))?; + + let a_0 = preimage.clone(); + + // a_1 == h(a_0, epoch) + + let a_1 = self + .hasher + .alloc(cs.namespace(|| "a_1"), vec![a_0.clone(), epoch])?; + + let share_x = num::AllocatedNum::alloc(cs.namespace(|| "share x"), || { + let value = self.inputs.share_x.clone(); + Ok(*value.get()?) + })?; + share_x.inputize(cs.namespace(|| "share x is public"))?; + + // constaint the evaluation the line equation + + let eval = allocate_add_with_coeff(cs.namespace(|| "eval"), &a_1, &share_x, &a_0)?; + + let share_y = num::AllocatedNum::alloc(cs.namespace(|| "share y"), || { + let value = self.inputs.share_y.clone(); + Ok(*value.get()?) + })?; + share_y.inputize(cs.namespace(|| "share y is public"))?; + + // see if share satisfies the line equation + + cs.enforce( + || "enforce lookup", + |lc| lc + share_y.get_variable(), + |lc| lc + CS::one(), + |lc| lc + eval.get_variable(), + ); + + // 3. Part + // Nullifier constraints + + // hashing secret twice with epoch ingredient + // a_1 == hash(a_0, epoch) is already constrained + + // nullifier == hash(a_1) + + let nullifier_calculated = self + .hasher + .alloc(cs.namespace(|| "calculated nullifier"), vec![a_1.clone()])?; + + let nullifier = num::AllocatedNum::alloc(cs.namespace(|| "nullifier"), || { + let value = self.inputs.nullifier.clone(); + Ok(*value.get()?) + })?; + nullifier.inputize(cs.namespace(|| "nullifier is public"))?; + + // check if correct nullifier supplied + + cs.enforce( + || "enforce nullifier", + |lc| lc + nullifier_calculated.get_variable(), + |lc| lc + CS::one(), + |lc| lc + nullifier.get_variable(), + ); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use super::RLNInputs; + use crate::circuit::bench; + use crate::poseidon::PoseidonParams; + use sapling_crypto::bellman::pairing::bls12_381::Bls12; + use sapling_crypto::bellman::pairing::bn256::Bn256; + use sapling_crypto::bellman::pairing::Engine; + + struct TestSuite { + merkle_depth: usize, + poseidon_parameters: PoseidonParams, + } + + fn cases() -> Vec> { + vec![ + TestSuite { + merkle_depth: 3, + poseidon_parameters: PoseidonParams::new(8, 55, 3, None, None, None), + }, + TestSuite { + merkle_depth: 24, + poseidon_parameters: PoseidonParams::new(8, 55, 3, None, None, None), + }, + TestSuite { + merkle_depth: 32, + poseidon_parameters: PoseidonParams::new(8, 55, 3, None, None, None), + }, + TestSuite { + merkle_depth: 16, + poseidon_parameters: PoseidonParams::new(8, 33, 3, None, None, None), + }, + TestSuite { + merkle_depth: 24, + poseidon_parameters: PoseidonParams::new(8, 33, 3, None, None, None), + }, + TestSuite { + merkle_depth: 32, + poseidon_parameters: PoseidonParams::new(8, 33, 3, None, None, None), + }, + ] + } + + #[test] + fn test_rln_bn() { + use sapling_crypto::bellman::pairing::bn256::Bn256; + let cases = cases::(); + for case in cases.iter() { + let rln_test = bench::RLNTest::::new( + case.merkle_depth, + Some(case.poseidon_parameters.clone()), + ); + let num_constraints = rln_test.synthesize(); + let result = rln_test.run_prover_bench(); + println!( + "bn256, t: {}, rf: {}, rp: {}, merkle depth: {}", + case.poseidon_parameters.width(), + case.poseidon_parameters.full_round_half_len() * 2, + case.poseidon_parameters.partial_round_len(), + case.merkle_depth, + ); + println!("number of constatins:\t{}", num_constraints); + println!("prover key size:\t{}", result.prover_key_size); + println!("prover time:\t{}", result.prover_time); + } + } + + #[test] + fn test_input_serialization() { + use sapling_crypto::bellman::pairing::bn256::{Bn256, Fr}; + use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; + let share_x = Fr::from_str("1").unwrap(); + let share_y = Fr::from_str("2").unwrap(); + let epoch = Fr::from_str("3").unwrap(); + let nullifier = Fr::from_str("4").unwrap(); + let root = Fr::from_str("5").unwrap(); + let id_key = Fr::from_str("6").unwrap(); + let auth_path = vec![ + Some((Fr::from_str("20").unwrap(), false)), + Some((Fr::from_str("21").unwrap(), true)), + Some((Fr::from_str("22").unwrap(), true)), + Some((Fr::from_str("23").unwrap(), false)), + ]; + let input0 = RLNInputs:: { + share_x: Some(share_x), + share_y: Some(share_y), + epoch: Some(epoch), + nullifier: Some(nullifier), + root: Some(root), + id_key: Some(id_key), + auth_path, + }; + let mut raw_inputs: Vec = Vec::new(); + input0.write(&mut raw_inputs).unwrap(); + let mut reader = raw_inputs.as_slice(); + let input1 = RLNInputs::::read(&mut reader).unwrap(); + assert_eq!(input0.share_x, input1.share_x); + assert_eq!(input0.share_y, input1.share_y); + assert_eq!(input0.epoch, input1.epoch); + assert_eq!(input0.nullifier, input1.nullifier); + assert_eq!(input0.root, input1.root); + assert_eq!(input0.id_key, input1.id_key); + assert_eq!(input0.auth_path, input1.auth_path); + } +} diff --git a/lib/rln/src/ffi.rs b/lib/rln/src/ffi.rs new file mode 100644 index 0000000..5a22a32 --- /dev/null +++ b/lib/rln/src/ffi.rs @@ -0,0 +1,273 @@ +use crate::{circuit::rln, public::RLN}; +use bellman::pairing::bn256::Bn256; +use std::slice; + +/// Buffer struct is taken from +/// https://github.com/celo-org/celo-threshold-bls-rs/blob/master/crates/threshold-bls-ffi/src/ffi.rs + +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +pub struct Buffer { + pub ptr: *const u8, + pub len: usize, +} + +impl From<&[u8]> for Buffer { + fn from(src: &[u8]) -> Self { + Self { + ptr: &src[0] as *const u8, + len: src.len(), + } + } +} + +impl<'a> From<&Buffer> for &'a [u8] { + fn from(src: &Buffer) -> &'a [u8] { + unsafe { slice::from_raw_parts(src.ptr, src.len) } + } +} + +#[no_mangle] +pub extern "C" fn new_circuit_from_params( + merkle_depth: usize, + parameters_buffer: *const Buffer, + ctx: *mut *mut RLN, +) -> bool { + let buffer = <&[u8]>::from(unsafe { &*parameters_buffer }); + let rln = match RLN::::new_with_raw_params(merkle_depth, buffer, None) { + Ok(rln) => rln, + Err(_) => return false, + }; + unsafe { *ctx = Box::into_raw(Box::new(rln)) }; + true +} + +#[no_mangle] +pub extern "C" fn generate_proof( + ctx: *const RLN, + input_buffer: *const Buffer, + output_buffer: *mut Buffer, +) -> bool { + let rln = unsafe { &*ctx }; + let input_data = <&[u8]>::from(unsafe { &*input_buffer }); + let mut output_data: Vec = Vec::new(); + match rln.generate_proof(input_data, &mut output_data) { + Ok(proof_data) => proof_data, + Err(_) => return false, + }; + unsafe { *output_buffer = Buffer::from(&output_data[..]) }; + std::mem::forget(output_data); + true +} + +#[no_mangle] +pub extern "C" fn verify( + ctx: *const RLN, + proof_buffer: *const Buffer, + public_inputs_buffer: *const Buffer, + result_ptr: *mut u32, +) -> bool { + let rln = unsafe { &*ctx }; + let proof_data = <&[u8]>::from(unsafe { &*proof_buffer }); + let public_inputs_data = <&[u8]>::from(unsafe { &*public_inputs_buffer }); + if match rln.verify(proof_data, public_inputs_data) { + Ok(verified) => verified, + Err(_) => return false, + } { + unsafe { *result_ptr = 0 }; + } else { + unsafe { *result_ptr = 1 }; + }; + true +} + +#[no_mangle] +pub extern "C" fn hash( + ctx: *const RLN, + inputs_buffer: *const Buffer, + input_len: *const usize, + output_buffer: *mut Buffer, +) -> bool { + let rln = unsafe { &*ctx }; + let input_data = <&[u8]>::from(unsafe { &*inputs_buffer }); + let n: usize = unsafe { *input_len }; + let mut output_data: Vec = Vec::new(); + match rln.hash(input_data, n, &mut output_data) { + Ok(output_data) => output_data, + Err(_) => return false, + }; + unsafe { *output_buffer = Buffer::from(&output_data[..]) }; + std::mem::forget(output_data); + true +} + +#[no_mangle] +pub extern "C" fn key_gen(ctx: *const RLN, keypair_buffer: *mut Buffer) -> bool { + let rln = unsafe { &*ctx }; + let mut output_data: Vec = Vec::new(); + match rln.key_gen(&mut output_data) { + Ok(_) => (), + Err(_) => return false, + } + unsafe { *keypair_buffer = Buffer::from(&output_data[..]) }; + std::mem::forget(output_data); + true +} + +use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use sapling_crypto::bellman::pairing::Engine; +use std::io::{self, Read, Write}; + +#[cfg(test)] +mod tests { + use crate::circuit::bench; + use crate::poseidon::PoseidonParams; + use bellman::pairing::bn256::{Bn256, Fr}; + + use super::*; + use std::mem::MaybeUninit; + + fn merkle_depth() -> usize { + 3usize + } + + fn rln_test() -> bench::RLNTest { + let merkle_depth = merkle_depth(); + let poseidon_params = PoseidonParams::::new(8, 55, 3, None, None, None); + let rln_test = bench::RLNTest::::new(merkle_depth, Some(poseidon_params)); + rln_test + } + + fn rln_pointer(circuit_parameters: Vec) -> MaybeUninit<*mut RLN> { + // restore this new curcuit with bindings + let merkle_depth = merkle_depth(); + let circuit_parameters_buffer = &Buffer::from(circuit_parameters.as_ref()); + let mut rln_pointer = MaybeUninit::<*mut RLN>::uninit(); + unsafe { + new_circuit_from_params( + merkle_depth, + circuit_parameters_buffer, + rln_pointer.as_mut_ptr(), + ) + }; + + rln_pointer + } + + #[test] + fn test_proof_ffi() { + let rln_test = rln_test(); + + let mut circuit_parameters: Vec = Vec::new(); + rln_test + .export_circuit_parameters(&mut circuit_parameters) + .unwrap(); + + let rln_pointer = rln_pointer(circuit_parameters); + let rln_pointer = unsafe { &*rln_pointer.assume_init() }; + + let mut inputs_data: Vec = Vec::new(); + let inputs = rln_test.valid_inputs(); + inputs.write(&mut inputs_data).unwrap(); + let inputs_buffer = &Buffer::from(inputs_data.as_ref()); + + let mut proof_buffer = MaybeUninit::::uninit(); + + let success = + unsafe { generate_proof(rln_pointer, inputs_buffer, proof_buffer.as_mut_ptr()) }; + assert!(success, "proof generation failed"); + + let proof_buffer = unsafe { proof_buffer.assume_init() }; + + let mut public_inputs_data: Vec = Vec::new(); + inputs.write_public_inputs(&mut public_inputs_data).unwrap(); + let public_inputs_buffer = &Buffer::from(public_inputs_data.as_ref()); + + let mut result = 0u32; + let result_ptr = &mut result as *mut u32; + + let success = + unsafe { verify(rln_pointer, &proof_buffer, public_inputs_buffer, result_ptr) }; + assert!(success, "verification operation failed"); + assert_eq!(0, result); + } + + #[test] + fn test_hash_ffi() { + let rln_test = rln_test(); + + let mut circuit_parameters: Vec = Vec::new(); + rln_test + .export_circuit_parameters(&mut circuit_parameters) + .unwrap(); + let mut hasher = rln_test.hasher(); + + let rln_pointer = rln_pointer(circuit_parameters); + let rln_pointer = unsafe { &*rln_pointer.assume_init() }; + + let mut input_data: Vec = Vec::new(); + + let inputs: Vec = ["1", "2"] + .iter() + .map(|e| Fr::from_str(e).unwrap()) + .collect(); + inputs.iter().for_each(|e| { + e.into_repr().write_le(&mut input_data).unwrap(); + }); + let input_buffer = &Buffer::from(input_data.as_ref()); + + let input_len: usize = 2; + let input_len_pointer = &input_len as *const usize; + + let expected = hasher.hash(inputs); + let mut expected_data: Vec = Vec::new(); + expected.into_repr().write_le(&mut expected_data).unwrap(); + + let mut result_buffer = MaybeUninit::::uninit(); + + let success = unsafe { + hash( + rln_pointer, + input_buffer, + input_len_pointer, + result_buffer.as_mut_ptr(), + ) + }; + assert!(success, "hash ffi call failed"); + + let result_buffer = unsafe { result_buffer.assume_init() }; + let result_data = <&[u8]>::from(&result_buffer); + assert_eq!(expected_data.as_slice(), result_data); + } + + #[test] + fn test_keygen_ffi() { + let rln_test = rln_test(); + + let mut circuit_parameters: Vec = Vec::new(); + rln_test + .export_circuit_parameters(&mut circuit_parameters) + .unwrap(); + let mut hasher = rln_test.hasher(); + + let rln_pointer = rln_pointer(circuit_parameters); + let rln_pointer = unsafe { &*rln_pointer.assume_init() }; + + let mut keypair_buffer = MaybeUninit::::uninit(); + + let success = unsafe { key_gen(rln_pointer, keypair_buffer.as_mut_ptr()) }; + assert!(success, "proof generation failed"); + + let keypair_buffer = unsafe { keypair_buffer.assume_init() }; + let mut keypair_data = <&[u8]>::from(&keypair_buffer); + + let mut buf = ::Repr::default(); + buf.read_le(&mut keypair_data).unwrap(); + let secret = Fr::from_repr(buf).unwrap(); + buf.read_le(&mut keypair_data).unwrap(); + let public = Fr::from_repr(buf).unwrap(); + let expected_public: Fr = hasher.hash(vec![secret]); + + assert_eq!(public, expected_public); + } +} diff --git a/lib/rln/src/lib.rs b/lib/rln/src/lib.rs new file mode 100644 index 0000000..266691e --- /dev/null +++ b/lib/rln/src/lib.rs @@ -0,0 +1,14 @@ +#![allow(dead_code)] +#![allow(unused_imports)] + +pub mod circuit; +pub mod merkle; +pub mod poseidon; +pub mod public; +mod utils; + +#[cfg(not(target_arch = "wasm32"))] +pub mod ffi; + +#[cfg(target_arch = "wasm32")] +mod wasm; diff --git a/lib/rln/src/merkle.rs b/lib/rln/src/merkle.rs new file mode 100644 index 0000000..9964511 --- /dev/null +++ b/lib/rln/src/merkle.rs @@ -0,0 +1,154 @@ +use crate::poseidon::{Poseidon as Hasher, PoseidonParams}; +use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use sapling_crypto::bellman::pairing::Engine; +use std::collections::HashMap; + +pub struct MerkleTree +where + E: Engine, +{ + pub hasher: Hasher, + zero: Vec, + depth: usize, + nodes: HashMap<(usize, usize), E::Fr>, +} + +impl MerkleTree +where + E: Engine, +{ + pub fn empty(mut hasher: Hasher, depth: usize) -> Self { + let mut zero: Vec = Vec::with_capacity(depth + 1); + zero.push(E::Fr::from_str("0").unwrap()); + for i in 0..depth { + zero.push(hasher.hash([zero[i]; 2].to_vec())); + } + zero.reverse(); + MerkleTree { + hasher: hasher, + zero: zero.clone(), + depth: depth, + nodes: HashMap::new(), + } + } + + fn get_node(&self, depth: usize, index: usize) -> E::Fr { + *self + .nodes + .get(&(depth, index)) + .unwrap_or_else(|| &self.zero[depth]) + } + + fn hash_couple(&mut self, depth: usize, index: usize) -> E::Fr { + let b = index & !1; + self.hasher + .hash([self.get_node(depth, b), self.get_node(depth, b + 1)].to_vec()) + } + + fn recalculate_from(&mut self, leaf_index: usize) { + let mut i = leaf_index; + let mut depth = self.depth; + loop { + let h = self.hash_couple(depth, i); + i >>= 1; + depth -= 1; + self.nodes.insert((depth, i), h); + if depth == 0 { + break; + } + } + assert_eq!(depth, 0); + assert_eq!(i, 0); + } + + pub fn insert(&mut self, leaf_index: usize, new: E::Fr, old: Option) { + let d = self.depth; + { + if old.is_some() { + let old = old.unwrap(); + let t = self.get_node(d, leaf_index); + if t.is_zero() { + assert!(old.is_zero()); + } else { + assert!(t.eq(&self.hasher.hash(vec![old]))); + } + } + }; + let leaf = self.hasher.hash(vec![new]); + self.update(leaf_index, leaf); + } + + pub fn update(&mut self, leaf_index: usize, leaf: E::Fr) { + self.nodes.insert((self.depth, leaf_index), leaf); + self.recalculate_from(leaf_index); + } + + pub fn root(&self) -> E::Fr { + return self.get_node(0, 0); + } + + pub fn witness(&mut self, leaf_index: usize) -> Vec<(E::Fr, bool)> { + let mut witness = Vec::<(E::Fr, bool)>::with_capacity(self.depth); + let mut i = leaf_index; + let mut depth = self.depth; + loop { + i ^= 1; + witness.push((self.get_node(depth, i), (i & 1 == 1))); + i >>= 1; + depth -= 1; + if depth == 0 { + break; + } + } + assert_eq!(i, 0); + witness + } + + pub fn check_inclusion( + &mut self, + witness: Vec<(E::Fr, bool)>, + leaf_index: usize, + data: E::Fr, + ) -> bool { + let mut acc = self.hasher.hash(vec![data]); + { + assert!(self.get_node(self.depth, leaf_index).eq(&acc)); + } + for w in witness.into_iter() { + if w.1 { + acc = self.hasher.hash(vec![acc, w.0]); + } else { + acc = self.hasher.hash(vec![w.0, acc]); + } + } + acc.eq(&self.root()) + } +} + +#[test] +fn test_merkle_set() { + let zero = Some(Fr::zero()); + let data: Vec = (0..8) + .map(|s| Fr::from_str(&format!("{}", s)).unwrap()) + .collect(); + use sapling_crypto::bellman::pairing::bn256::{Bn256, Fr, FrRepr}; + let params = PoseidonParams::::new(8, 55, 3, None, None, None); + let hasher = Hasher::new(params); + let mut set = MerkleTree::empty(hasher, 3); + let leaf_index = 6; + set.insert(leaf_index, data[0], zero); + let witness = set.witness(leaf_index); + assert!(set.check_inclusion(witness, leaf_index, data[0])); +} + +#[test] +fn test_merkle_zeros() { + use sapling_crypto::bellman::pairing::bn256::{Bn256, Fr, FrRepr}; + let params = PoseidonParams::::new(8, 55, 3, None, None, None); + let hasher = Hasher::new(params); + let mut set = MerkleTree::empty(hasher, 32); + set.insert(5, Fr::from_str("1").unwrap(), Some(Fr::zero())); + println!("{}", set.root()); + set.insert(6, Fr::from_str("2").unwrap(), Some(Fr::zero())); + println!("{}", set.root()); +} diff --git a/lib/rln/src/poseidon.rs b/lib/rln/src/poseidon.rs new file mode 100644 index 0000000..dadd1ee --- /dev/null +++ b/lib/rln/src/poseidon.rs @@ -0,0 +1,245 @@ +use blake2::{Blake2s, Digest}; + +use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use sapling_crypto::bellman::pairing::Engine; + +#[derive(Clone)] +pub struct PoseidonParams { + rf: usize, + rp: usize, + t: usize, + round_constants: Vec, + mds_matrix: Vec, +} + +#[derive(Clone)] +pub struct Poseidon { + state: Vec, + round: usize, + params: PoseidonParams, +} + +impl PoseidonParams { + pub fn new( + rf: usize, + rp: usize, + t: usize, + round_constants: Option>, + mds_matrix: Option>, + seed: Option>, + ) -> PoseidonParams { + let seed = match seed { + Some(seed) => seed, + None => b"".to_vec(), + }; + + let _round_constants = match round_constants { + Some(round_constants) => round_constants, + None => PoseidonParams::::generate_constants(b"drlnhdsc", seed.clone(), rf + rp), + }; + assert_eq!(rf + rp, _round_constants.len()); + + let _mds_matrix = match mds_matrix { + Some(mds_matrix) => mds_matrix, + None => PoseidonParams::::generate_mds_matrix(b"drlnhdsm", seed.clone(), t), + }; + PoseidonParams { + rf, + rp, + t, + round_constants: _round_constants, + mds_matrix: _mds_matrix, + } + } + + pub fn width(&self) -> usize { + return self.t; + } + + pub fn partial_round_len(&self) -> usize { + return self.rp; + } + + pub fn full_round_half_len(&self) -> usize { + return self.rf / 2; + } + + pub fn total_rounds(&self) -> usize { + return self.rf + self.rp; + } + + pub fn round_constant(&self, round: usize) -> E::Fr { + return self.round_constants[round]; + } + + pub fn mds_matrix_row(&self, i: usize) -> Vec { + let w = self.width(); + self.mds_matrix[i * w..(i + 1) * w].to_vec() + } + + pub fn mds_matrix(&self) -> Vec { + self.mds_matrix.clone() + } + + pub fn generate_mds_matrix(persona: &[u8; 8], seed: Vec, t: usize) -> Vec { + let v: Vec = PoseidonParams::::generate_constants(persona, seed, t * 2); + let mut matrix: Vec = Vec::with_capacity(t * t); + for i in 0..t { + for j in 0..t { + let mut tmp = v[i]; + tmp.add_assign(&v[t + j]); + let entry = tmp.inverse().unwrap(); + matrix.insert((i * t) + j, entry); + } + } + matrix + } + + pub fn generate_constants(persona: &[u8; 8], seed: Vec, len: usize) -> Vec { + let mut constants: Vec = Vec::new(); + let mut source = seed.clone(); + loop { + let mut hasher = Blake2s::new(); + hasher.input(persona); + hasher.input(source); + source = hasher.result().to_vec(); + let mut candidate_repr = ::Repr::default(); + candidate_repr.read_le(&source[..]).unwrap(); + if let Ok(candidate) = E::Fr::from_repr(candidate_repr) { + constants.push(candidate); + if constants.len() == len { + break; + } + } + } + constants + } +} + +impl Poseidon { + pub fn new(params: PoseidonParams) -> Poseidon { + Poseidon { + round: 0, + state: Vec::new(), + params, + } + } + + fn new_state(&mut self, inputs: Vec) { + let t = self.t(); + self.state = inputs.clone(); + self.state.resize(t, E::Fr::zero()); + } + + fn clear(&mut self) { + self.round = 0; + } + + fn t(&self) -> usize { + self.params.t + } + + fn result(&self) -> E::Fr { + self.state[0] + } + + pub fn hash(&mut self, inputs: Vec) -> E::Fr { + self.new_state(inputs); + loop { + self.round(self.round); + self.round += 1; + if self.round == self.params.total_rounds() { + break; + } + } + let r = self.result(); + self.clear(); + r + } + + fn round(&mut self, round: usize) { + let a1 = self.params.full_round_half_len(); + let a2 = a1 + self.params.partial_round_len(); + let a3 = self.params.total_rounds(); + if round < a1 { + self.full_round(round); + } else if round >= a1 && round < a2 { + self.partial_round(round); + } else if round >= a2 && round < a3 { + if round == a3 - 1 { + self.full_round_last(); + } else { + self.full_round(round); + } + } else { + panic!("should not be here") + } + } + + fn full_round(&mut self, round: usize) { + self.add_round_constants(round); + self.apply_quintic_sbox(true); + self.mul_mds_matrix(); + } + + fn full_round_last(&mut self) { + let last_round = self.params.total_rounds() - 1; + self.add_round_constants(last_round); + self.apply_quintic_sbox(true); + } + + fn partial_round(&mut self, round: usize) { + self.add_round_constants(round); + self.apply_quintic_sbox(false); + self.mul_mds_matrix(); + } + + fn add_round_constants(&mut self, round: usize) { + for (_, b) in self.state.iter_mut().enumerate() { + let c = self.params.round_constants[round]; + b.add_assign(&c); + } + } + + fn apply_quintic_sbox(&mut self, full: bool) { + for s in self.state.iter_mut() { + let mut b = s.clone(); + b.square(); + b.square(); + s.mul_assign(&b); + if !full { + break; + } + } + } + + fn mul_mds_matrix(&mut self) { + let w = self.params.t; + let mut new_state = vec![E::Fr::zero(); w]; + for (i, ns) in new_state.iter_mut().enumerate() { + for (j, s) in self.state.iter().enumerate() { + let mut tmp = s.clone(); + tmp.mul_assign(&self.params.mds_matrix[i * w + j]); + ns.add_assign(&tmp); + } + } + self.state = new_state; + } +} + +#[test] +fn test_poseidon_hash() { + use sapling_crypto::bellman::pairing::bn256; + use sapling_crypto::bellman::pairing::bn256::{Bn256, Fr}; + let params = PoseidonParams::::new(8, 55, 3, None, None, None); + let mut hasher = Poseidon::::new(params); + let input1: Vec = ["0"].iter().map(|e| Fr::from_str(e).unwrap()).collect(); + let r1: Fr = hasher.hash(input1.to_vec()); + let input2: Vec = ["0", "0"] + .iter() + .map(|e| Fr::from_str(e).unwrap()) + .collect(); + let r2: Fr = hasher.hash(input2.to_vec()); + // println!("{:?}", r1); + assert_eq!(r1, r2, "just to see if internal state resets"); +} diff --git a/lib/rln/src/public.rs b/lib/rln/src/public.rs new file mode 100644 index 0000000..c7232f7 --- /dev/null +++ b/lib/rln/src/public.rs @@ -0,0 +1,140 @@ +use crate::circuit::poseidon::PoseidonCircuit; +use crate::circuit::rln::{RLNCircuit, RLNInputs}; +use crate::merkle::MerkleTree; +use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams}; +use crate::utils::{read_inputs, read_uncompressed_proof, write_uncompressed_proof}; +use bellman::groth16::generate_random_parameters; +use bellman::groth16::{create_proof, prepare_verifying_key, verify_proof}; +use bellman::groth16::{create_random_proof, Parameters, Proof}; +use bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use bellman::pairing::{CurveAffine, EncodedPoint, Engine}; +use bellman::{Circuit, ConstraintSystem, SynthesisError}; +use rand::{Rand, SeedableRng, XorShiftRng}; +use std::io::{self, Error, ErrorKind, Read, Write}; + +pub struct RLN +where + E: Engine, +{ + circuit_parameters: Parameters, + poseidon_params: PoseidonParams, + merkle_depth: usize, +} + +impl RLN +where + E: Engine, +{ + fn default_poseidon_params() -> PoseidonParams { + PoseidonParams::::new(8, 55, 3, None, None, None) + } + + fn new_circuit(merkle_depth: usize, poseidon_params: PoseidonParams) -> Parameters { + let mut rng = XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let inputs = RLNInputs::::empty(merkle_depth); + let circuit = RLNCircuit:: { + inputs, + hasher: PoseidonCircuit::new(poseidon_params.clone()), + }; + generate_random_parameters(circuit, &mut rng).unwrap() + } + + fn new_with_params( + merkle_depth: usize, + circuit_parameters: Parameters, + poseidon_params: PoseidonParams, + ) -> RLN { + RLN { + circuit_parameters, + poseidon_params, + merkle_depth, + } + } + + pub fn poseidon_params(&self) -> PoseidonParams { + self.poseidon_params.clone() + } + + pub fn new(merkle_depth: usize, poseidon_params: Option>) -> RLN { + let poseidon_params = match poseidon_params { + Some(params) => params, + None => Self::default_poseidon_params(), + }; + let circuit_parameters = Self::new_circuit(merkle_depth, poseidon_params.clone()); + Self::new_with_params(merkle_depth, circuit_parameters, poseidon_params) + } + + pub fn new_with_raw_params( + merkle_depth: usize, + raw_circuit_parameters: R, + poseidon_params: Option>, + ) -> io::Result> { + let circuit_parameters = Parameters::::read(raw_circuit_parameters, true)?; + let poseidon_params = match poseidon_params { + Some(params) => params, + None => Self::default_poseidon_params(), + }; + Ok(Self::new_with_params( + merkle_depth, + circuit_parameters, + poseidon_params, + )) + } + + pub fn hasher(&self) -> PoseidonHasher { + PoseidonHasher::new(self.poseidon_params.clone()) + } + + pub fn hash(&self, input: R, n: usize, mut output: W) -> io::Result<()> { + let mut hasher = self.hasher(); + let input: Vec = read_inputs::(input, n)?; + let result = hasher.hash(input); + // let mut output_data: Vec = Vec::new(); + result.into_repr().write_le(&mut output)?; + Ok(()) + } + + pub fn generate_proof(&self, input: R, mut output: W) -> io::Result<()> { + use rand::chacha::ChaChaRng; + use rand::SeedableRng; + let mut rng = ChaChaRng::new_unseeded(); + let inputs = RLNInputs::::read(input)?; + assert_eq!(self.merkle_depth, inputs.merkle_depth()); + let circuit_hasher = PoseidonCircuit::new(self.poseidon_params.clone()); + let circuit = RLNCircuit { + inputs: inputs.clone(), + hasher: circuit_hasher.clone(), + }; + let proof = create_random_proof(circuit, &self.circuit_parameters, &mut rng).unwrap(); + write_uncompressed_proof(proof, &mut output)?; + // proof.write(&mut w).unwrap(); + Ok(()) + } + + pub fn verify(&self, uncompresed_proof: R, raw_public_inputs: R) -> io::Result { + let proof = read_uncompressed_proof(uncompresed_proof)?; + // let proof = Proof::read(uncompresed_proof).unwrap(); + let public_inputs = RLNInputs::::read_public_inputs(raw_public_inputs)?; + let verifing_key = prepare_verifying_key(&self.circuit_parameters.vk); + let success = verify_proof(&verifing_key, &proof, &public_inputs).unwrap(); + Ok(success) + } + + pub fn key_gen(&self, mut w: W) -> io::Result<()> { + let mut rng = XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let mut hasher = self.hasher(); + let secret = E::Fr::rand(&mut rng); + let public: E::Fr = hasher.hash(vec![secret.clone()]); + secret.into_repr().write_le(&mut w)?; + public.into_repr().write_le(&mut w)?; + Ok(()) + } + + pub fn export_verifier_key(&self, w: W) -> io::Result<()> { + self.circuit_parameters.vk.write(w) + } + + pub fn export_circuit_parameters(&self, w: W) -> io::Result<()> { + self.circuit_parameters.write(w) + } +} diff --git a/lib/rln/src/utils.rs b/lib/rln/src/utils.rs new file mode 100644 index 0000000..7f64ebe --- /dev/null +++ b/lib/rln/src/utils.rs @@ -0,0 +1,80 @@ +use bellman::groth16::Proof; +use bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; +use bellman::pairing::{CurveAffine, EncodedPoint, Engine}; + +use rand::{Rand, SeedableRng, XorShiftRng}; +use std::io::{self, Error, ErrorKind, Read, Write}; + +pub fn read_inputs(mut reader: R, n: usize) -> io::Result> { + let mut out: Vec = Vec::new(); + let mut buf = ::Repr::default(); + for _ in 0..n { + buf.read_le(&mut reader)?; + let input = + E::Fr::from_repr(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + out.push(input); + } + Ok(out) +} + +pub fn write_uncompressed_proof( + proof: Proof, + mut writer: W, +) -> io::Result<()> { + writer.write_all(proof.a.into_uncompressed().as_ref())?; + writer.write_all(proof.b.into_uncompressed().as_ref())?; + writer.write_all(proof.c.into_uncompressed().as_ref())?; + Ok(()) +} + +pub fn read_uncompressed_proof(mut reader: R) -> io::Result> { + let mut g1_repr = ::Uncompressed::empty(); + let mut g2_repr = ::Uncompressed::empty(); + + reader.read_exact(g1_repr.as_mut())?; + let a = g1_repr + .into_affine() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + .and_then(|e| { + if e.is_zero() { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )) + } else { + Ok(e) + } + })?; + + reader.read_exact(g2_repr.as_mut())?; + let b = g2_repr + .into_affine() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + .and_then(|e| { + if e.is_zero() { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )) + } else { + Ok(e) + } + })?; + + reader.read_exact(g1_repr.as_mut())?; + let c = g1_repr + .into_affine() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + .and_then(|e| { + if e.is_zero() { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )) + } else { + Ok(e) + } + })?; + + Ok(Proof { a, b, c }) +} diff --git a/lib/rln/src/wasm.rs b/lib/rln/src/wasm.rs new file mode 100644 index 0000000..4123d97 --- /dev/null +++ b/lib/rln/src/wasm.rs @@ -0,0 +1,132 @@ +use crate::public::RLN; + +use std::io::{self, Error, ErrorKind, Read, Write}; +use wasm_bindgen::prelude::*; + +use js_sys::Array; +use sapling_crypto::bellman::pairing::bn256::{Bn256, Fr}; + +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + // #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} + +#[wasm_bindgen] +pub struct RLNWasm { + api: RLN, +} + +#[wasm_bindgen] +impl RLNWasm { + #[wasm_bindgen] + pub fn new(merkle_depth: usize) -> RLNWasm { + set_panic_hook(); + RLNWasm { + api: RLN::::new(merkle_depth, None), + } + } + + #[wasm_bindgen] + pub fn new_with_raw_params( + merkle_depth: usize, + raw_circuit_parameters: &[u8], + ) -> Result { + set_panic_hook(); + let api = match RLN::new_with_raw_params(merkle_depth, raw_circuit_parameters, None) { + Ok(api) => api, + Err(e) => return Err(e.to_string().into()), + }; + Ok(RLNWasm { api }) + } + + #[wasm_bindgen] + pub fn generate_proof(&self, input: &[u8]) -> Result, JsValue> { + let proof = match self.api.generate_proof(input) { + Ok(proof) => proof, + Err(e) => return Err(e.to_string().into()), + }; + Ok(proof) + } + + #[wasm_bindgen] + pub fn verify( + &self, + uncompresed_proof: &[u8], + raw_public_inputs: &[u8], + ) -> Result { + let success = match self.api.verify(uncompresed_proof, raw_public_inputs) { + Ok(success) => success, + Err(e) => return Err(e.to_string().into()), + }; + Ok(success) + } + + #[wasm_bindgen] + pub fn export_verifier_key(&self) -> Result, JsValue> { + let mut output: Vec = Vec::new(); + match self.api.export_verifier_key(&mut output) { + Ok(_) => (), + Err(e) => return Err(e.to_string().into()), + }; + Ok(output) + } + + #[wasm_bindgen] + pub fn export_circuit_parameters(&self) -> Result, JsValue> { + let mut output: Vec = Vec::new(); + match self.api.export_circuit_parameters(&mut output) { + Ok(_) => (), + Err(e) => return Err(e.to_string().into()), + }; + Ok(output) + } +} + +#[cfg(test)] +mod test { + + use crate::circuit::bench; + use wasm_bindgen_test::*; + + use crate::circuit::poseidon::PoseidonCircuit; + use crate::circuit::rln::{RLNCircuit, RLNInputs}; + use crate::merkle::MerkleTree; + use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams}; + use bellman::groth16::{generate_random_parameters, Parameters, Proof}; + use bellman::pairing::bn256::{Bn256, Fr}; + use bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; + use rand::{Rand, SeedableRng, XorShiftRng}; + + #[wasm_bindgen_test] + fn test_rln_wasm() { + let merkle_depth = 3usize; + let poseidon_params = PoseidonParams::::new(8, 55, 3, None, None, None); + let rln_test = bench::RLNTest::::new(merkle_depth, Some(poseidon_params)); + + let rln_wasm = super::RLNWasm::new(merkle_depth); + + let mut raw_inputs: Vec = Vec::new(); + let inputs = rln_test.valid_inputs(); + inputs.write(&mut raw_inputs); + + // let now = Instant::now(); + let proof = rln_wasm.generate_proof(raw_inputs.as_slice()).unwrap(); + // let prover_time = now.elapsed().as_millis() as f64 / 1000.0; + + let mut raw_public_inputs: Vec = Vec::new(); + inputs.write_public_inputs(&mut raw_public_inputs); + + assert_eq!( + rln_wasm + .verify(proof.as_slice(), raw_public_inputs.as_slice()) + .unwrap(), + true + ); + } +}