diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index f13810d..21d662c 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -1 +1,3 @@ +mod polynomial; mod poseidon; +mod rln; diff --git a/src/circuit/polynomial.rs b/src/circuit/polynomial.rs new file mode 100644 index 0000000..65b17c6 --- /dev/null +++ b/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/src/circuit/rln.rs b/src/circuit/rln.rs new file mode 100644 index 0000000..99910f0 --- /dev/null +++ b/src/circuit/rln.rs @@ -0,0 +1,372 @@ +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::Engine; +use sapling_crypto::bellman::{Circuit, ConstraintSystem, SynthesisError, Variable}; +use sapling_crypto::circuit::{boolean, ecc, num, Assignment}; +use sapling_crypto::jubjub::{JubjubEngine, JubjubParams, PrimeOrder}; + +// 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, +{ + fn public_inputs(self) -> Vec { + vec![ + self.root.unwrap(), + self.epoch.unwrap(), + self.share_x.unwrap(), + self.share_y.unwrap(), + self.nullifier.unwrap(), + ] + } +} + +#[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::{RLNCircuit, RLNInputs}; + use crate::circuit::poseidon::PoseidonCircuit; + use crate::merkle::MerkleTree; + use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams}; + use rand::{Rand, SeedableRng, XorShiftRng}; + use sapling_crypto::bellman::groth16::{ + create_random_proof, generate_random_parameters, prepare_verifying_key, verify_proof, + }; + 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::thread::sleep; + use std::time::{Duration, Instant}; + + struct RLNTest + where + E: Engine, + { + // cs: TestConstraintSystem, + merkle_depth: usize, + poseidon_params: PoseidonParams, + } + + impl RLNTest + where + E: Engine, + { + pub fn new(poseidon_params: PoseidonParams, merkle_depth: usize) -> RLNTest { + // let cs = TestConstraintSystem::::new(); + RLNTest:: { + poseidon_params, + merkle_depth, + } + } + + fn inputs(&self) -> RLNInputs { + let mut rng = XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let mut hasher = PoseidonHasher::new(self.poseidon_params.clone()); + // 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 = 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 = 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 + } + + fn empty_inputs(&self) -> RLNInputs { + RLNInputs:: { + share_x: None, + share_y: None, + epoch: None, + nullifier: None, + root: None, + id_key: None, + auth_path: vec![None; self.merkle_depth], + } + } + + pub fn run(&self) { + let mut rng = XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let hasher = PoseidonCircuit::new(self.poseidon_params.clone()); + let inputs = self.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() { + println!("unsatisfied\n{}", unsatisfied.unwrap()); + // panic!("unsatisfied\n{}", unsatisfied.unwrap()); + } + let unconstrained = cs.find_unconstrained(); + if !unconstrained.is_empty() { + // panic!("unconstrained\n{}", unconstrained); + println!("unconstrained\n{}", unconstrained); + } + // assert!(cs.is_satisfied()); + println!("{}", cs.is_satisfied()); + println!("number of constaints {}", cs.num_constraints()); + } + + { + let parameters = { + let inputs = self.empty_inputs(); + let circuit = RLNCircuit:: { + inputs, + hasher: hasher.clone(), + }; + let parameters = generate_random_parameters(circuit, &mut rng).unwrap(); + parameters + }; + + let mut key_size = 0; + let point_size = (((E::Fr::NUM_BITS / 64) + 1) * 8 * 2) as usize; + key_size += parameters.a.len() * point_size; + key_size += parameters.l.len() * point_size; + key_size += parameters.h.len() * point_size; + key_size += parameters.b_g1.len() * point_size; + key_size += parameters.b_g2.len() * point_size * 2; + + let now = Instant::now(); + let proof = create_random_proof(circuit, ¶meters, &mut rng).unwrap(); + println!("prover time {}", now.elapsed().as_millis() as f64 / 1000.0); + + let verifing_key = prepare_verifying_key(¶meters.vk); + assert!(verify_proof(&verifing_key, &proof, &inputs.public_inputs()).unwrap()); + } + } + } + + #[test] + fn test_rln() { + use sapling_crypto::bellman::pairing::bn256::Bn256; + let poseidon_params = PoseidonParams::::default(); + let rln_test = RLNTest::new(poseidon_params, 32); + rln_test.run(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 47fe6bb..e2a8f6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,3 @@ mod circuit; mod merkle; mod poseidon; -