diff --git a/benches/bench.rs b/benches/bench.rs index 1a14d1f4..248516af 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -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; @@ -162,6 +166,64 @@ fn inverse( ); } +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 = (0..7).map(|_| rng.gen()).collect(); + let position_var: Vec> = 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(); @@ -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, + Constant + ); + cond_select_bench_individual!( + Boolean_select, + ark_mnt6_753::Fr, + Boolean, + bool, + Constant + ); + pub type FBaseVar = FpVar; + cond_select_bench_individual!( + SWProjective_select, + ark_mnt6_753::Fq, + ProjectiveVar, + Projective, + constant + ); } diff --git a/rustfmt.toml b/rustfmt.toml index 71712138..6a424ff4 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -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" diff --git a/src/bits/boolean.rs b/src/bits/boolean.rs index ff9e79ad..595d153c 100644 --- a/src/bits/boolean.rs +++ b/src/bits/boolean.rs @@ -216,6 +216,56 @@ impl CondSelectGadget for AllocatedBool { _ => unreachable!("Impossible"), } } + + /// Using the hybrid method 5.3 from . + fn conditionally_select_power_of_two_vector( + position: &[Boolean], + values: &[Self], + ) -> Result { + 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 = 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 @@ -950,6 +1000,56 @@ impl CondSelectGadget for Boolean { }, } } + + /// Using the hybrid method 5.3 from . + fn conditionally_select_power_of_two_vector( + position: &[Boolean], + values: &[Self], + ) -> Result { + 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 = 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)] @@ -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] @@ -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::::new_ref(); + + // value array + let values: Vec = (0..128).map(|_| rng.gen()).collect(); + let values_const: Vec> = + values.iter().map(|x| Boolean::Constant(*x)).collect(); + + // index array + let position: Vec = (0..7).map(|_| rng.gen()).collect(); + let position_var: Vec> = 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] + ) + } + } } diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 8241aa60..d0736da7 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -425,8 +425,9 @@ impl AllocatedFp { // The high level logic is as follows: // We want to check that self - other != 0. We do this by checking that // (self - other).inverse() exists. In more detail, we check the following: - // If `should_enforce == true`, then we set `multiplier = (self - other).inverse()`, - // and check that (self - other) * multiplier == 1. (i.e., that the inverse exists) + // If `should_enforce == true`, then we set `multiplier = (self - + // other).inverse()`, and check that (self - other) * multiplier == 1. + // (i.e., that the inverse exists) // // If `should_enforce == false`, then we set `multiplier == 0`, and check that // (self - other) * 0 == 0, which is always satisfied. @@ -577,6 +578,58 @@ impl CondSelectGadget for AllocatedFp { }, } } + + /// Using the hybrid method 5.3 from . + fn conditionally_select_power_of_two_vector( + position: &[Boolean], + values: &[Self], + ) -> Result { + 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 = values + .chunks(chunk_size) + .map(|chunk| chunk[index].clone()) + .collect(); + + let upper_elems = { + // get the linear combination for leaves of the upper tree + for i in 0..two_to_m { + let mut lc = LinearCombination::zero(); + for j in 0..two_to_l { + let v = values[i * two_to_l + j].value()?; + lc = &lc + sub_tree[j].clone() * v; + } + + cs.enforce_constraint(lc, 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) + } } /// Uses two bits to perform a lookup into a table @@ -978,6 +1031,71 @@ impl CondSelectGadget for FpVar { }, } } + + /// Using the hybrid method 5.3 from . + fn conditionally_select_power_of_two_vector( + position: &[Boolean], + values: &[Self], + ) -> Result { + 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 = values + .chunks(chunk_size) + .map(|chunk| chunk[index].clone()) + .collect(); + + let upper_elems = { + let mut upper_leaves = Vec::with_capacity(two_to_m); + + // get the linear combination for leaves of the upper tree: sum of conditions + 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; + } + upper_leaves.push(x); + } + + // allocate a new FpVar, forcing value from `root_vals` to equal the linear + // combination obtained above + let allocated_vars: Result, _> = root_vals + .iter() + .zip(upper_leaves) + .map(|(val, lc)| { + let var = cs.new_lc(lc)?; + let v = val.value()?; + Ok(AllocatedFp::new(Some(v), var, cs.clone()).into()) + }) + .collect::, _>>(); + + allocated_vars + }?; + + // 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) + } } /// Uses two bits to perform a lookup into a table @@ -1067,12 +1185,14 @@ impl<'a, F: PrimeField> Sum<&'a FpVar> for FpVar { mod test { use crate::{ alloc::{AllocVar, AllocationMode}, + boolean::Boolean, eq::EqGadget, fields::fp::FpVar, + select::CondSelectGadget, R1CSVar, }; use ark_relations::r1cs::ConstraintSystem; - use ark_std::{UniformRand, Zero}; + use ark_std::{rand::Rng, UniformRand, Zero}; use ark_test_curves::bls12_381::Fr; #[test] @@ -1105,4 +1225,42 @@ mod test { assert!(cs.is_satisfied().unwrap()); assert_eq!(sum.value().unwrap(), sum_expected); } + + #[test] + fn test_fpvar_random_access() { + let mut rng = ark_std::test_rng(); + + for _ in 0..100 { + let cs = ConstraintSystem::::new_ref(); + + // value array + let values: Vec = (0..128).map(|_| rng.gen()).collect(); + let values_const: Vec> = values.iter().map(|x| FpVar::Constant(*x)).collect(); + + // index array + let position: Vec = (0..7).map(|_| rng.gen()).collect(); + let position_var: Vec> = 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!( + FpVar::conditionally_select_power_of_two_vector(&position_var, &values_const) + .unwrap() + .value() + .unwrap(), + values[index] + ) + } + } } diff --git a/src/fields/mod.rs b/src/fields/mod.rs index 0c342998..76e0c69d 100644 --- a/src/fields/mod.rs +++ b/src/fields/mod.rs @@ -21,7 +21,8 @@ pub mod quadratic_extension; pub mod fp; /// This module contains a generic implementation of "nonnative" prime field -/// variables. It emulates `Fp` arithmetic using `Fq` operations, where `p != q`. +/// variables. It emulates `Fp` arithmetic using `Fq` operations, where `p != +/// q`. pub mod nonnative; /// This module contains a generic implementation of the degree-12 tower diff --git a/src/fields/nonnative/allocated_field_var.rs b/src/fields/nonnative/allocated_field_var.rs index aadbe1a3..2a512c0b 100644 --- a/src/fields/nonnative/allocated_field_var.rs +++ b/src/fields/nonnative/allocated_field_var.rs @@ -634,10 +634,10 @@ impl } /// Allocates a new non-native field witness with value given by the - /// function `f`. Enforces that the field element has value in `[0, modulus)`, - /// and returns the bits of its binary representation. - /// The bits are in little-endian (i.e., the bit at index 0 is the LSB) and the - /// bit-vector is empty in non-witness allocation modes. + /// function `f`. Enforces that the field element has value in `[0, + /// modulus)`, and returns the bits of its binary representation. + /// The bits are in little-endian (i.e., the bit at index 0 is the LSB) and + /// the bit-vector is empty in non-witness allocation modes. pub fn new_witness_with_le_bits>( cs: impl Into>, f: impl FnOnce() -> Result, diff --git a/src/groups/curves/short_weierstrass/mod.rs b/src/groups/curves/short_weierstrass/mod.rs index b41d959a..73d66495 100644 --- a/src/groups/curves/short_weierstrass/mod.rs +++ b/src/groups/curves/short_weierstrass/mod.rs @@ -720,6 +720,22 @@ where Ok(Self::new(x, y, z)) } + + fn conditionally_select_power_of_two_vector( + position: &[Boolean<::BasePrimeField>], + values: &[Self], + ) -> Result { + let x_values = values.iter().map(|v| v.x.clone()).collect::>(); + let x = F::conditionally_select_power_of_two_vector(&position, &x_values)?; + + let y_values = values.iter().map(|v| v.y.clone()).collect::>(); + let y = F::conditionally_select_power_of_two_vector(&position, &y_values)?; + + let z_values = values.iter().map(|v| v.z.clone()).collect::>(); + let z = F::conditionally_select_power_of_two_vector(&position, &z_values)?; + + Ok(Self::new(x, y, z)) + } } impl EqGadget<::BasePrimeField> for ProjectiveVar @@ -965,3 +981,57 @@ where Ok(bytes) } } + +#[cfg(test)] +mod test { + + use crate::{fields::fp::FpVar, groups::curves::short_weierstrass::ProjectiveVar, prelude::*}; + use ark_ec::short_weierstrass::Projective; + use ark_relations::r1cs::ConstraintSystem; + use ark_std::rand::Rng; + use ark_test_curves::bls12_381::{g1::Config, Fq}; + + #[test] + fn test_projective_random_access() { + pub type FBaseVar = FpVar; + + let mut rng = ark_std::test_rng(); + + for _ in 0..100 { + let cs = ConstraintSystem::::new_ref(); + + // value array + let values: Vec> = (0..128).map(|_| rng.gen()).collect(); + let values_const: Vec> = + values.iter().map(|x| ProjectiveVar::constant(*x)).collect(); + + // index array + let position: Vec = (0..7).map(|_| rng.gen()).collect(); + let position_var: Vec> = 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!( + ProjectiveVar::conditionally_select_power_of_two_vector( + &position_var, + &values_const + ) + .unwrap() + .value() + .unwrap(), + values[index] + ) + } + } +} diff --git a/src/pairing/mod.rs b/src/pairing/mod.rs index 958134e1..55cc1391 100644 --- a/src/pairing/mod.rs +++ b/src/pairing/mod.rs @@ -1,6 +1,5 @@ use crate::prelude::*; -use ark_ec::pairing::Pairing; -use ark_ec::CurveGroup; +use ark_ec::{pairing::Pairing, CurveGroup}; use ark_ff::Field; use ark_relations::r1cs::SynthesisError; use core::fmt::Debug; diff --git a/src/select.rs b/src/select.rs index 5d128a67..7923d6b8 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,8 +1,8 @@ use crate::prelude::*; use ark_ff::Field; -use ark_relations::r1cs::SynthesisError; +use ark_relations::r1cs::{LinearCombination, SynthesisError}; use ark_std::vec::Vec; -/// Generates constraints for selecting between one of two values. +/// Generates constraints for selecting between one of many values. pub trait CondSelectGadget where Self: Sized, @@ -22,7 +22,7 @@ where /// Returns an element of `values` whose index in represented by `position`. /// `position` is an array of boolean that represents an unsigned integer in - /// big endian order. + /// big endian order. The default is the repeated selection method 5.1 from . /// /// # Example /// To get the 6th element of `values`, convert unsigned integer 6 (`0b110`) @@ -32,39 +32,128 @@ where position: &[Boolean], values: &[Self], ) -> Result { - let m = values.len(); - let n = position.len(); - - // Assert m is a power of 2, and n = log(m) - assert!(m.is_power_of_two()); - assert_eq!(1 << n, m); - - let mut cur_mux_values = values.to_vec(); - - // Traverse the evaluation tree from bottom to top in level order traversal. - // This is method 5.1 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf - // TODO: Add method 5.2/5.3 - for i in 0..n { - // Size of current layer. - let cur_size = 1 << (n - i); - assert_eq!(cur_mux_values.len(), cur_size); - - let mut next_mux_values = Vec::new(); - for j in (0..cur_size).step_by(2) { - let cur = Self::conditionally_select( - &position[n - 1 - i], - // true case - &cur_mux_values[j + 1], - // false case - &cur_mux_values[j], - )?; - next_mux_values.push(cur); + repeated_selection(position, values.to_vec()) + } +} + +/// Sum of conditions method 5.2 from +/// Use this to generate the selector conditions. +pub fn sum_of_conditions( + position: &[Boolean], +) -> Result>, SynthesisError> { + let n = position.len(); + let m = 1 << n; + + let mut selectors: Vec> = vec![Boolean::constant(true); m]; + // let's construct the table of selectors. + // for a bit-decomposition (b_{n-1}, b_{n-2}, ..., b_0) of `power`: + // [ + // (b_{n-1} * b_{n-2} * ... * b_1 * b_0), + // (b_{n-1} * b_{n-2} * ... * b_1), + // (b_{n-1} * b_{n-2} * ... * b_0), + // ... + // (b_1 * b_0), + // b_1, + // b_0, + // 1, + // ] + // + // the element of the selector table at index i is a product of `bits` + // e.g. for i = 5 == (101)_binary + // `selectors[5]` <== b_2 * b_0` + // we can construct the first `max_bits_in_power - 1` elements without products, + // directly from `bits`: + // e.g.: + // `selectors[0] <== 1` + // `selectors[1] <== b_0` + // `selectors[2] <== b_1` + // `selectors[4] <== b_2` + // `selectors[8] <== b_3` + + // First element is 1==true, but we've already initialized the vector with + // `true`. + for i in 0..n { + selectors[1 << i] = position[n - i - 1].clone(); + for j in (1 << i) + 1..(1 << (i + 1)) { + selectors[j] = selectors[1 << i].and(&selectors[j - (1 << i)])?; + } + } + + fn count_ones(x: usize) -> usize { + // count the number of 1s in the binary representation of x + let mut count = 0; + let mut y = x; + while y > 0 { + count += y & 1; + y >>= 1; + } + count + } + + // Selector sums for each leaf node + // E.g. for n = 2, m = 4 we have: + // `selectors[0] <== 1` + // `selectors[1] <== b_0` + // `selectors[2] <== b_1` + // `selectors[3] <== b_1 * b_0` + // Then the selector_sums for i = 0, 1, 2, 3 are: + // i = 0 = (00) = (1-b_1)*(1-b_0) = 1 - b_0 - b_1 + b_0*b_1 = selectors[0] - + // selectors[1] - selectors[2] + selectors[3] i = 1 = (01) = (1-b_1)*b_0 = + // b_0 - b_0*b_1 = selectors[1] - + // selectors[3] i = 2 = (10) = b_1*(1-b_0) = b_1 - b_0*b_1 = + // selectors[2] - selectors[3] i = 3 = (11) = b_1*b_0 + // = selectors[3] + let mut selector_sums: Vec> = vec![LinearCombination::zero(); m]; + for i in 0..m { + for j in 0..m { + if i | j == j { + let counts = count_ones(j - i); + if counts % 2 == 0 { + selector_sums[i] = &selector_sums[i] + &selectors[j].lc(); + } else { + selector_sums[i] = &selector_sums[i] - &selectors[j].lc(); + }; } - cur_mux_values = next_mux_values; } + } + Ok(selector_sums) +} + +/// Repeated selection method 5.1 from +pub fn repeated_selection>( + position: &[Boolean], + values: Vec, +) -> Result { + let m = values.len(); + let n = position.len(); + + // Assert m is a power of 2, and n = log(m) + assert!(m.is_power_of_two()); + assert_eq!(1 << n, m); - Ok(cur_mux_values[0].clone()) + let mut cur_mux_values = values; + + // Traverse the evaluation tree from bottom to top in level order traversal. + for i in 0..n { + // Size of current layer. + let cur_size = 1 << (n - i); + assert_eq!(cur_mux_values.len(), cur_size); + + let mut next_mux_values = Vec::new(); + for j in (0..cur_size).step_by(2) { + let cur = CondG::conditionally_select( + &position[n - 1 - i], + // true case + &cur_mux_values[j + 1], + // false case + &cur_mux_values[j], + )?; + next_mux_values.push(cur); + } + cur_mux_values = next_mux_values; } + + Ok(cur_mux_values[0].clone()) } /// Performs a lookup in a 4-element table using two bits.