Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hybrid method for random access/conditional selection from 2^k values #119

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
39c111e
merge_imports rustfmt option is deprecated
mmagician May 2, 2023
e5291a0
fmt
mmagician May 2, 2023
c0e6ca2
Add an extra test for conditionally selecting an FpVar
mmagician May 2, 2023
6c93fb6
move the Repeated Selection method logic to its own fn
mmagician May 2, 2023
3e817f2
start the implementation of SumOfConditions method
mmagician May 2, 2023
e7db1c0
update trait doc: we allow for selecting between many values, not just 2
mmagician May 2, 2023
e60df9d
Implement sum of conditions helper method
mmagician May 3, 2023
d9dd651
override `condionally_select_power_of_two_vector` for `FpVar`
mmagician May 3, 2023
f5617fa
make `count_ones` a local function inside `sum_of_conditions`
mmagician May 3, 2023
80dfddf
Make `conditionally_select_power_of_two_vector` generic
mmagician May 3, 2023
aff5fe0
replace usage of `FpVar<F>` with `Self` in FpVar's `allocate_vars` fn
mmagician May 4, 2023
bd5eb43
replace unwraps with error propagation
mmagician May 4, 2023
0952ab0
place the bulk of the `allocat_vars` logic in a standalone generic fn
mmagician May 4, 2023
995463f
temp: add bench for cond_select for FpVar
mmagician May 4, 2023
db7b97d
format links
mmagician May 4, 2023
f7e8abc
simplify `add_lc` for `FpVar`
mmagician May 4, 2023
d5f3a34
rename fn to `allocate_with_value`
mmagician May 4, 2023
6939d3f
remove dead code
mmagician May 4, 2023
1ea679d
combine type-specific logic from two new trait methods into one
mmagician May 8, 2023
760b045
Add some initial docs for hybrid selection method
mmagician May 8, 2023
3ca576b
implement hybrid selection for `AllocatedBool` and `Boolean`
mmagician May 8, 2023
b5f88b8
implement `hybrid_selection` for `AllocatedFp`
mmagician May 8, 2023
777da9c
Implement hybrid selection for ProjectiveVar for SW
mmagician May 8, 2023
6c4e56f
Refactor: inline `hybrid_selection` into `conditionally_select._power…
mmagician May 9, 2023
74ce483
update the docs for the default and implementors of improved method
mmagician May 9, 2023
f165d68
Add bench macro to encompass both Boolean and FpVar select methods
mmagician May 9, 2023
2f79c48
Add further support for ProjectiveVar
mmagician May 9, 2023
7ce86de
Merge branch 'master' into hybrid-method
mmagician May 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 87 additions & 3 deletions benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use ark_ff::PrimeField;
use ark_ec::short_weierstrass::Projective;
use ark_ff::{PrimeField, UniformRand};
use ark_r1cs_std::{
alloc::AllocVar,
eq::EqGadget,
fields::{nonnative::NonNativeFieldVar, FieldVar},
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
groups::{curves::short_weierstrass::ProjectiveVar, CurveVar},
prelude::Boolean,
select::CondSelectGadget,
};
use ark_relations::{
ns,
r1cs::{ConstraintSystem, ConstraintSystemRef, OptimizationGoal},
};
use ark_std::rand::RngCore;
use ark_std::rand::{Rng, RngCore};

const NUM_REPETITIONS: usize = 1;

Expand Down Expand Up @@ -162,6 +166,64 @@ fn inverse<TargetField: PrimeField, BaseField: PrimeField, R: RngCore>(
);
}

macro_rules! cond_select_bench_individual {
($bench_name:ident, $bench_base_field:ty, $var:ty, $native_type:ty, $constant:ident) => {
let rng = &mut ark_std::test_rng();
let mut num_constraints = 0;
let mut num_nonzeros = 0;
for _ in 0..NUM_REPETITIONS {
let cs_sys = ConstraintSystem::<$bench_base_field>::new();
let cs = ConstraintSystemRef::new(cs_sys);
cs.set_optimization_goal(OptimizationGoal::Constraints);

let (cur_constraints, cur_nonzeros) = {
// value array
let values: Vec<$native_type> =
(0..128).map(|_| <$native_type>::rand(rng)).collect();
let values_const: Vec<$var> =
values.iter().map(|x| <$var>::$constant(*x)).collect();

// index array
let position: Vec<bool> = (0..7).map(|_| rng.gen()).collect();
let position_var: Vec<Boolean<$bench_base_field>> = position
.iter()
.map(|b| {
Boolean::new_witness(ark_relations::ns!(cs, "index_arr_element"), || Ok(*b))
.unwrap()
})
.collect();

let constraints_before = cs.num_constraints();
let nonzeros_before = get_density(&cs);

let _ =
<$var>::conditionally_select_power_of_two_vector(&position_var, &values_const);

let constraints_after = cs.num_constraints();
let nonzeros_after = get_density(&cs);

(
constraints_after - constraints_before,
nonzeros_after - nonzeros_before,
)
};

num_constraints += cur_constraints;
num_nonzeros += cur_nonzeros;

assert!(cs.is_satisfied().unwrap());
}
let average_constraints = num_constraints / NUM_REPETITIONS;
let average_nonzeros = num_nonzeros / NUM_REPETITIONS;
println!(
"{} takes: {} constraints, {} non-zeros",
stringify!($bench_name),
average_constraints,
average_nonzeros,
);
};
}

macro_rules! nonnative_bench_individual {
($bench_method:ident, $bench_name:ident, $bench_target_field:ty, $bench_base_field:ty) => {
let rng = &mut ark_std::test_rng();
Expand Down Expand Up @@ -235,4 +297,26 @@ fn main() {
nonnative_bench!(BLS12MNT4Small, ark_bls12_381::Fr, ark_mnt4_298::Fr);
nonnative_bench!(BLS12, ark_bls12_381::Fq, ark_bls12_381::Fr);
nonnative_bench!(MNT6BigMNT4Small, ark_mnt6_753::Fr, ark_mnt4_298::Fr);
cond_select_bench_individual!(
FpVar_select,
ark_mnt6_753::Fr,
FpVar<ark_mnt6_753::Fr>,
ark_mnt6_753::Fr,
Constant
);
cond_select_bench_individual!(
Boolean_select,
ark_mnt6_753::Fr,
Boolean<ark_mnt6_753::Fr>,
bool,
Constant
);
pub type FBaseVar = FpVar<ark_mnt6_753::Fq>;
cond_select_bench_individual!(
SWProjective_select,
ark_mnt6_753::Fq,
ProjectiveVar<ark_mnt6_753::g1::Config, FBaseVar>,
Projective<ark_mnt6_753::g1::Config>,
constant
);
}
2 changes: 1 addition & 1 deletion rustfmt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ match_block_trailing_comma = true
use_field_init_shorthand = true
edition = "2018"
condense_wildcard_suffixes = true
merge_imports = true
imports_granularity="Crate"
140 changes: 140 additions & 0 deletions src/bits/boolean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,56 @@ impl<F: Field> CondSelectGadget<F> for AllocatedBool<F> {
_ => unreachable!("Impossible"),
}
}

/// Using the hybrid method 5.3 from <https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf>.
fn conditionally_select_power_of_two_vector(
position: &[Boolean<F>],
values: &[Self],
) -> Result<Self, SynthesisError> {
let cs = position[0].cs();
let n = position.len();

// split n into l and m, where l + m = n
// total cost is 2^m + 2^l - l - 2, so we'd rather maximize l than m
let m = n / 2;
let l = n - m;

let two_to_l = 1 << l;
let two_to_m = 1 << m;

// we only need the lower L bits
let lower_bits = &mut position[m..].to_vec();
let sub_tree = sum_of_conditions(lower_bits)?;

// index for the chunk
let mut index = 0;
for x in lower_bits {
index *= 2;
index += if x.value()? { 1 } else { 0 };
}
let chunk_size = 1 << l;
let root_vals: Vec<Self> = values
.chunks(chunk_size)
.map(|chunk| chunk[index].clone())
.collect();

let upper_elems = {
for i in 0..two_to_m {
let mut x = LinearCombination::zero();
for j in 0..two_to_l {
let v = values[i * two_to_l + j].value()?;
x = &x + sub_tree[j].clone() * v.into();
}
cs.enforce_constraint(x, lc!() + Variable::One, lc!() + root_vals[i].variable)?;
}

Ok(root_vals)
}?;

// apply the repeated selection method, to select one of 2^m subtree results
let upper_bits = &mut position[..m].to_vec();
repeated_selection(upper_bits, upper_elems)
}
}

/// Represents a boolean value in the constraint system which is guaranteed
Expand Down Expand Up @@ -950,6 +1000,56 @@ impl<F: Field> CondSelectGadget<F> for Boolean<F> {
},
}
}

/// Using the hybrid method 5.3 from <https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf>.
fn conditionally_select_power_of_two_vector(
position: &[Boolean<F>],
values: &[Self],
) -> Result<Self, SynthesisError> {
let cs = position[0].cs();
let n = position.len();

// split n into l and m, where l + m = n
// total cost is 2^m + 2^l - l - 2, so we'd rather maximize l than m
let m = n / 2;
let l = n - m;

let two_to_l = 1 << l;
let two_to_m = 1 << m;

// we only need the lower L bits
let lower_bits = &mut position[m..].to_vec();
let sub_tree = sum_of_conditions(lower_bits)?;

// index for the chunk
let mut index = 0;
for x in lower_bits {
index *= 2;
index += if x.value()? { 1 } else { 0 };
}
let chunk_size = 1 << l;
let root_vals: Vec<Self> = values
.chunks(chunk_size)
.map(|chunk| chunk[index].clone())
.collect();

let upper_elems = {
for i in 0..two_to_m {
let mut x = LinearCombination::zero();
for j in 0..two_to_l {
let v = values[i * two_to_l + j].value()?;
x = &x + sub_tree[j].clone() * v.into();
}
cs.enforce_constraint(x, lc!() + Variable::One, root_vals[i].lc())?;
}

Ok(root_vals)
}?;

// apply the repeated selection method, to select one of 2^m subtree results
let upper_bits = &mut position[..m].to_vec();
repeated_selection(upper_bits, upper_elems)
}
}

#[cfg(test)]
Expand All @@ -958,6 +1058,7 @@ mod test {
use crate::prelude::*;
use ark_ff::{BitIteratorBE, BitIteratorLE, Field, One, PrimeField, UniformRand, Zero};
use ark_relations::r1cs::{ConstraintSystem, Namespace, SynthesisError};
use ark_std::rand::Rng;
use ark_test_curves::bls12_381::Fr;

#[test]
Expand Down Expand Up @@ -1818,4 +1919,43 @@ mod test {

Ok(())
}

#[test]
fn test_bool_random_access() {
let mut rng = ark_std::test_rng();

for _ in 0..100 {
let cs = ConstraintSystem::<Fr>::new_ref();

// value array
let values: Vec<bool> = (0..128).map(|_| rng.gen()).collect();
let values_const: Vec<Boolean<Fr>> =
values.iter().map(|x| Boolean::Constant(*x)).collect();

// index array
let position: Vec<bool> = (0..7).map(|_| rng.gen()).collect();
let position_var: Vec<Boolean<Fr>> = position
.iter()
.map(|b| {
Boolean::new_witness(ark_relations::ns!(cs, "index_arr_element"), || Ok(*b))
.unwrap()
})
.collect();

// index
let mut index = 0;
for x in position {
index *= 2;
index += if x { 1 } else { 0 };
}

assert_eq!(
Boolean::conditionally_select_power_of_two_vector(&position_var, &values_const)
.unwrap()
.value()
.unwrap(),
values[index]
)
}
}
}
Loading