From 39c111e0e5c4118373972ca6e5f251cea5a7481b Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 1 May 2023 21:42:51 -0600 Subject: [PATCH 01/27] merge_imports rustfmt option is deprecated --- rustfmt.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From e5291a0b80c5b568a5904dbeb93e88b06901984c Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 1 May 2023 21:43:04 -0600 Subject: [PATCH 02/27] fmt --- src/fields/fp/mod.rs | 5 +++-- src/fields/mod.rs | 3 ++- src/fields/nonnative/allocated_field_var.rs | 8 ++++---- src/pairing/mod.rs | 3 +-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 8241aa60..ed88fe8c 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. 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/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; From c0e6ca27e60e5c3df08cc5bbf1e982430af0d034 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 1 May 2023 21:58:17 -0600 Subject: [PATCH 03/27] Add an extra test for conditionally selecting an FpVar --- src/fields/fp/mod.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index ed88fe8c..c570b738 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -1068,12 +1068,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] @@ -1106,4 +1108,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] + ) + } + } } From 6c93fb6aa1b5911ac5088587825b1e3d539714fc Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 1 May 2023 22:14:12 -0600 Subject: [PATCH 04/27] move the Repeated Selection method logic to its own fn --- src/select.rs | 62 ++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/select.rs b/src/select.rs index 5d128a67..b0687d7f 100644 --- a/src/select.rs +++ b/src/select.rs @@ -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. This is hybrid method 5.3 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf. /// /// # Example /// To get the 6th element of `values`, convert unsigned integer 6 (`0b110`) @@ -32,39 +32,45 @@ where position: &[Boolean], values: &[Self], ) -> Result { - let m = values.len(); - let n = position.len(); + repeated_selection(position, values) + } +} - // Assert m is a power of 2, and n = log(m) - assert!(m.is_power_of_two()); - assert_eq!(1 << n, m); +/// Repeated selection method 5.1 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf +fn repeated_selection>( + position: &[Boolean], + values: &[CondG], +) -> Result { + let m = values.len(); + let n = position.len(); - let mut cur_mux_values = values.to_vec(); + // Assert m is a power of 2, and n = log(m) + assert!(m.is_power_of_two()); + assert_eq!(1 << n, m); - // 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 cur_mux_values = values.to_vec(); - 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); - } - cur_mux_values = next_mux_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); - Ok(cur_mux_values[0].clone()) + 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. From 3e817f20c57cef34c9defd9deb856c26ea13b626 Mon Sep 17 00:00:00 2001 From: mmagician Date: Tue, 2 May 2023 10:35:25 -0600 Subject: [PATCH 05/27] start the implementation of SumOfConditions method --- src/select.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/src/select.rs b/src/select.rs index b0687d7f..cc8910a5 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,6 +1,6 @@ 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. pub trait CondSelectGadget @@ -32,10 +32,102 @@ where position: &[Boolean], values: &[Self], ) -> Result { + let _ = sum_of_conditions(position, values); repeated_selection(position, values) } } +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 +} + +/// Sum of conditions method 5.2 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf +fn sum_of_conditions>( + position: &[Boolean], + values: &[CondG], +) -> 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 selectors: Vec> = Vec::with_capacity(m); + + // fill the selectors vec with Boolean true entries + for _ in 0..m { + selectors.push(Boolean::constant(true).lc()); + } + + // 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, + // ] + // signal selectors[leafCount]; + // + // 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. for + // `selectors[1] <== b_0` + // `selectors[2] <== b_1` + // `selectors[4] <== b_2` + // `selectors[8] <== b_3` + + // First element is true, but we've already filled it in. + // selectors[0] = Boolean::constant(true); + for i in 0..n { + selectors[1 << i] = position[i].lc(); + for j in (1 << i) + 1..(1 << (i + 1)) { + selectors[j] = &selectors[1 << i] + &selectors[j - (1 << i)]; + } + } + + let mut selector_sums: Vec> = Vec::with_capacity(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]; + } else { + selector_sums[i] = &selector_sums[i] - &selectors[j]; + }; + } + } + } + + let root: LinearCombination = LinearCombination::zero(); + // var x = 0; + for i in 0..m { + root = &root + (values[i], selector_sums[i]); + } + // for (var i = 0; i < nextPow; i++) { + // x += leaves[i] * selector_sums[i]; + // } + // root <== x; + + unimplemented!() +} + /// Repeated selection method 5.1 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf fn repeated_selection>( position: &[Boolean], From e7db1c0d7cf7c0b3e1e6a968f4d413aa6801c849 Mon Sep 17 00:00:00 2001 From: mmagician Date: Tue, 2 May 2023 12:07:37 -0600 Subject: [PATCH 06/27] update trait doc: we allow for selecting between many values, not just 2 --- src/select.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/select.rs b/src/select.rs index cc8910a5..1ba28172 100644 --- a/src/select.rs +++ b/src/select.rs @@ -2,7 +2,7 @@ use crate::prelude::*; use ark_ff::Field; 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, From e60df9d458dc9c88fc383f6e06b838caa2a64387 Mon Sep 17 00:00:00 2001 From: mmagician Date: Tue, 2 May 2023 22:24:25 -0600 Subject: [PATCH 07/27] Implement sum of conditions helper method --- src/select.rs | 61 +++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/src/select.rs b/src/select.rs index 1ba28172..e185d4aa 100644 --- a/src/select.rs +++ b/src/select.rs @@ -49,24 +49,14 @@ fn count_ones(x: usize) -> usize { } /// Sum of conditions method 5.2 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf -fn sum_of_conditions>( +/// Use this to generate the selector sums. +pub fn sum_of_conditions( position: &[Boolean], - values: &[CondG], -) -> Result { - let m = values.len(); +) -> Result>, SynthesisError> { let n = position.len(); + let m = 1 << n; - // Assert m is a power of 2, and n = log(m) - assert!(m.is_power_of_two()); - assert_eq!(1 << n, m); - - let mut selectors: Vec> = Vec::with_capacity(m); - - // fill the selectors vec with Boolean true entries - for _ in 0..m { - selectors.push(Boolean::constant(true).lc()); - } - + 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`: // [ @@ -79,53 +69,52 @@ fn sum_of_conditions>( // b_0, // 1, // ] - // signal selectors[leafCount]; // // 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. for + // e.g.: + // `selectors[0] <== 1` // `selectors[1] <== b_0` // `selectors[2] <== b_1` // `selectors[4] <== b_2` // `selectors[8] <== b_3` - // First element is true, but we've already filled it in. - // selectors[0] = Boolean::constant(true); + // First element is 1==true, but we've already initialized the vector with `true`. for i in 0..n { - selectors[1 << i] = position[i].lc(); + selectors[1 << i] = position[n - i - 1].clone(); for j in (1 << i) + 1..(1 << (i + 1)) { - selectors[j] = &selectors[1 << i] + &selectors[j - (1 << i)]; + selectors[j] = selectors[1 << i].and(&selectors[j - (1 << i)])?; } } - let mut selector_sums: Vec> = Vec::with_capacity(m); + // 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]; + selector_sums[i] = &selector_sums[i] + &selectors[j].lc(); } else { - selector_sums[i] = &selector_sums[i] - &selectors[j]; + selector_sums[i] = &selector_sums[i] - &selectors[j].lc(); }; } } } - - let root: LinearCombination = LinearCombination::zero(); - // var x = 0; - for i in 0..m { - root = &root + (values[i], selector_sums[i]); - } - // for (var i = 0; i < nextPow; i++) { - // x += leaves[i] * selector_sums[i]; - // } - // root <== x; - - unimplemented!() + Ok(selector_sums) } /// Repeated selection method 5.1 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf From d9dd651e432ea3fdcee8057881095420331d464a Mon Sep 17 00:00:00 2001 From: mmagician Date: Tue, 2 May 2023 22:28:54 -0600 Subject: [PATCH 08/27] override `condionally_select_power_of_two_vector` for `FpVar` --- src/fields/fp/mod.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index c570b738..a95fd7af 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -979,6 +979,34 @@ impl CondSelectGadget for FpVar { }, } } + + #[tracing::instrument(target = "r1cs")] + fn conditionally_select_power_of_two_vector( + position: &[Boolean], + values: &[Self], + ) -> Result { + let m = values.len(); + + let selector_sums = sum_of_conditions(position)?; + + let cs = position.cs(); + + let mut root: LinearCombination = LinearCombination::zero(); + for i in 0..m { + let v = values[i].value().unwrap(); + root = &root + (v, &selector_sums[i]); + } + let result = cs.new_lc(root.clone())?; + + // index for the witness + let mut index = 0; + for x in position { + index *= 2; + index += if x.value()? { 1 } else { 0 }; + } + + Ok(AllocatedFp::new(Some(values[index].value().unwrap()), result, cs.clone()).into()) + } } /// Uses two bits to perform a lookup into a table From f5617fa9a34a3cedf2dba8a30f6a16243620776b Mon Sep 17 00:00:00 2001 From: mmagician Date: Tue, 2 May 2023 22:31:31 -0600 Subject: [PATCH 09/27] make `count_ones` a local function inside `sum_of_conditions` --- src/select.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/select.rs b/src/select.rs index e185d4aa..c5c3cd2a 100644 --- a/src/select.rs +++ b/src/select.rs @@ -37,17 +37,6 @@ where } } -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 -} - /// Sum of conditions method 5.2 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf /// Use this to generate the selector sums. pub fn sum_of_conditions( @@ -90,6 +79,17 @@ pub fn sum_of_conditions( } } + 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` From 80dfddf4ad6d9ae141f9b584e7e6b65d34dc9f59 Mon Sep 17 00:00:00 2001 From: mmagician Date: Wed, 3 May 2023 11:36:53 -0600 Subject: [PATCH 10/27] Make `conditionally_select_power_of_two_vector` generic at the cost of introducing two extra methods on the implementers --- src/fields/fp/mod.rs | 47 +++++++++++++++++++++++-------------- src/select.rs | 55 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index a95fd7af..55c162e1 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -980,32 +980,45 @@ impl CondSelectGadget for FpVar { } } - #[tracing::instrument(target = "r1cs")] - fn conditionally_select_power_of_two_vector( - position: &[Boolean], - values: &[Self], - ) -> Result { - let m = values.len(); - - let selector_sums = sum_of_conditions(position)?; + fn add_lc( + val: &Self, + lc: &LinearCombination, + ) -> Result, SynthesisError> { + let root: LinearCombination = LinearCombination::zero(); + let v = val.value().unwrap(); + Ok(root + (v, lc)) + } + fn allocate_vars( + values: &[Self], + position: &[Boolean], + lc: Vec>, + ) -> Result, SynthesisError> { let cs = position.cs(); - let mut root: LinearCombination = LinearCombination::zero(); - for i in 0..m { - let v = values[i].value().unwrap(); - root = &root + (v, &selector_sums[i]); - } - let result = cs.new_lc(root.clone())?; - - // index for the witness + // index for the chunk let mut index = 0; for x in position { index *= 2; index += if x.value()? { 1 } else { 0 }; } + let chunk_size = 1 << position.len(); + let root_vals: Vec> = values + .chunks(chunk_size) + .map(|chunk| chunk[index].clone()) + .collect(); - Ok(AllocatedFp::new(Some(values[index].value().unwrap()), result, cs.clone()).into()) + let allocated_vars: Vec> = root_vals + .iter() + .zip(lc) + .map(|(val, lc)| { + let v = val.value().unwrap(); + let var = cs.new_lc(lc).unwrap(); + AllocatedFp::new(Some(v), var, cs.clone()).into() + }) + .collect::>>(); + + Ok(allocated_vars) } } diff --git a/src/select.rs b/src/select.rs index c5c3cd2a..2b50eaf9 100644 --- a/src/select.rs +++ b/src/select.rs @@ -20,6 +20,26 @@ where false_value: &Self, ) -> Result; + // fn sum_of_lc( + // lc: Vec>, + // values: &[Self], + // ) -> Result; + + fn add_lc( + _val: &Self, + _lc: &LinearCombination, + ) -> Result, SynthesisError> { + unimplemented!() + } + + fn allocate_vars( + _values: &[Self], + _position: &[Boolean], + _lc: Vec>, + ) -> Result, SynthesisError> { + unimplemented!() + } + /// 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. This is hybrid method 5.3 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf. @@ -32,8 +52,35 @@ where position: &[Boolean], values: &[Self], ) -> Result { - let _ = sum_of_conditions(position, values); - repeated_selection(position, values) + // let num_leaves = values.len(); + 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)?; + + let mut upper_leaves = Vec::with_capacity(two_to_m); + + for i in 0..two_to_m { + let mut x = LinearCombination::zero(); + for j in 0..two_to_l { + x = &x + Self::add_lc(&values[i * two_to_l + j], &sub_tree[j].clone())?; + } + upper_leaves.push(x); + } + + let upper_elems = Self::allocate_vars(values, lower_bits, upper_leaves)?; + + let upper_bits = &mut position[..m].to_vec(); + repeated_selection(upper_bits, upper_elems) } } @@ -120,7 +167,7 @@ pub fn sum_of_conditions( /// Repeated selection method 5.1 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf fn repeated_selection>( position: &[Boolean], - values: &[CondG], + values: Vec, ) -> Result { let m = values.len(); let n = position.len(); @@ -129,7 +176,7 @@ fn repeated_selection>( assert!(m.is_power_of_two()); assert_eq!(1 << n, m); - let mut cur_mux_values = values.to_vec(); + let mut cur_mux_values = values; // Traverse the evaluation tree from bottom to top in level order traversal. for i in 0..n { From aff5fe07b749ec29bfc215a52c51fbdb5b67b04c Mon Sep 17 00:00:00 2001 From: mmagician Date: Thu, 4 May 2023 10:04:36 -0600 Subject: [PATCH 11/27] replace usage of `FpVar` with `Self` in FpVar's `allocate_vars` fn --- src/fields/fp/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 55c162e1..48360a62 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -1003,12 +1003,12 @@ impl CondSelectGadget for FpVar { index += if x.value()? { 1 } else { 0 }; } let chunk_size = 1 << position.len(); - let root_vals: Vec> = values + let root_vals: Vec = values .chunks(chunk_size) .map(|chunk| chunk[index].clone()) .collect(); - let allocated_vars: Vec> = root_vals + let allocated_vars: Vec = root_vals .iter() .zip(lc) .map(|(val, lc)| { @@ -1016,7 +1016,7 @@ impl CondSelectGadget for FpVar { let var = cs.new_lc(lc).unwrap(); AllocatedFp::new(Some(v), var, cs.clone()).into() }) - .collect::>>(); + .collect::>(); Ok(allocated_vars) } From bd5eb43a87a64675f84bbb4b9cf245e60e1fc58e Mon Sep 17 00:00:00 2001 From: mmagician Date: Thu, 4 May 2023 10:09:18 -0600 Subject: [PATCH 12/27] replace unwraps with error propagation --- src/fields/fp/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 48360a62..d5f369f7 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -985,7 +985,7 @@ impl CondSelectGadget for FpVar { lc: &LinearCombination, ) -> Result, SynthesisError> { let root: LinearCombination = LinearCombination::zero(); - let v = val.value().unwrap(); + let v = val.value()?; Ok(root + (v, lc)) } @@ -1008,17 +1008,17 @@ impl CondSelectGadget for FpVar { .map(|chunk| chunk[index].clone()) .collect(); - let allocated_vars: Vec = root_vals + let allocated_vars: Result, _> = root_vals .iter() .zip(lc) .map(|(val, lc)| { - let v = val.value().unwrap(); - let var = cs.new_lc(lc).unwrap(); - AllocatedFp::new(Some(v), var, cs.clone()).into() + let v = val.value()?; + let var = cs.new_lc(lc)?; + Ok(AllocatedFp::new(Some(v), var, cs.clone()).into()) }) - .collect::>(); + .collect::, _>>(); - Ok(allocated_vars) + allocated_vars } } From 0952ab0f0b39e364b22268039984df0bec9fc0e4 Mon Sep 17 00:00:00 2001 From: mmagician Date: Thu, 4 May 2023 10:24:53 -0600 Subject: [PATCH 13/27] place the bulk of the `allocat_vars` logic in a standalone generic fn --- src/fields/fp/mod.rs | 37 +++++++--------------------------- src/select.rs | 48 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index d5f369f7..80129376 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -989,36 +989,13 @@ impl CondSelectGadget for FpVar { Ok(root + (v, lc)) } - fn allocate_vars( - values: &[Self], - position: &[Boolean], - lc: Vec>, - ) -> Result, SynthesisError> { - let cs = position.cs(); - - // index for the chunk - let mut index = 0; - for x in position { - index *= 2; - index += if x.value()? { 1 } else { 0 }; - } - let chunk_size = 1 << position.len(); - let root_vals: Vec = values - .chunks(chunk_size) - .map(|chunk| chunk[index].clone()) - .collect(); - - let allocated_vars: Result, _> = root_vals - .iter() - .zip(lc) - .map(|(val, lc)| { - let v = val.value()?; - let var = cs.new_lc(lc)?; - Ok(AllocatedFp::new(Some(v), var, cs.clone()).into()) - }) - .collect::, _>>(); - - allocated_vars + fn allocate_to_lc( + var: Variable, + val: &Self, + cs: &ConstraintSystemRef, + ) -> Result { + let v = val.value()?; + Ok(AllocatedFp::new(Some(v), var, cs.clone()).into()) } } diff --git a/src/select.rs b/src/select.rs index 2b50eaf9..a9b4edd8 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use ark_ff::Field; -use ark_relations::r1cs::{LinearCombination, SynthesisError}; +use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, SynthesisError, Variable}; use ark_std::vec::Vec; /// Generates constraints for selecting between one of many values. pub trait CondSelectGadget @@ -32,11 +32,11 @@ where unimplemented!() } - fn allocate_vars( - _values: &[Self], - _position: &[Boolean], - _lc: Vec>, - ) -> Result, SynthesisError> { + fn allocate_to_lc( + _var: Variable, + _val: &Self, + _cs: &ConstraintSystemRef, + ) -> Result { unimplemented!() } @@ -52,7 +52,6 @@ where position: &[Boolean], values: &[Self], ) -> Result { - // let num_leaves = values.len(); let n = position.len(); // split n into l and m, where l + m = n @@ -77,7 +76,7 @@ where upper_leaves.push(x); } - let upper_elems = Self::allocate_vars(values, lower_bits, upper_leaves)?; + let upper_elems = allocate_vars(values, lower_bits, upper_leaves)?; let upper_bits = &mut position[..m].to_vec(); repeated_selection(upper_bits, upper_elems) @@ -86,7 +85,7 @@ where /// Sum of conditions method 5.2 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf /// Use this to generate the selector sums. -pub fn sum_of_conditions( +fn sum_of_conditions( position: &[Boolean], ) -> Result>, SynthesisError> { let n = position.len(); @@ -164,6 +163,37 @@ pub fn sum_of_conditions( Ok(selector_sums) } +fn allocate_vars>( + values: &[CondG], + position: &[Boolean], + lc: Vec>, +) -> Result, SynthesisError> { + let cs = position.cs(); + + // index for the chunk + let mut index = 0; + for x in position { + index *= 2; + index += if x.value()? { 1 } else { 0 }; + } + let chunk_size = 1 << position.len(); + let root_vals: Vec = values + .chunks(chunk_size) + .map(|chunk| chunk[index].clone()) + .collect(); + + let allocated_vars: Result, _> = root_vals + .iter() + .zip(lc) + .map(|(val, lc)| { + let var = cs.new_lc(lc)?; + CondG::allocate_to_lc(var, val, &cs) + }) + .collect::, _>>(); + + allocated_vars +} + /// Repeated selection method 5.1 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf fn repeated_selection>( position: &[Boolean], From 995463ff2cb5233ce5f847d9d65c833c5313664a Mon Sep 17 00:00:00 2001 From: mmagician Date: Thu, 4 May 2023 11:52:31 -0600 Subject: [PATCH 14/27] temp: add bench for cond_select for FpVar --- benches/bench.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 1a14d1f4..30422c97 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -2,13 +2,15 @@ use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::AllocVar, eq::EqGadget, - fields::{nonnative::NonNativeFieldVar, FieldVar}, + fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar}, + 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 +164,64 @@ fn inverse( ); } +fn cond_select( + cs: ConstraintSystemRef, + rng: &mut R, +) -> (usize, usize) { + // value array + let values: Vec = (0..128).map(|_| BaseField::rand(rng)).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(); + + let constraints_before = cs.num_constraints(); + let nonzeros_before = get_density(&cs); + + let _ = FpVar::conditionally_select_power_of_two_vector(&position_var, &values_const); + + let constraints_after = cs.num_constraints(); + let nonzeros_after = get_density(&cs); + + return ( + constraints_after - constraints_before, + nonzeros_after - nonzeros_before, + ); +} + +macro_rules! cond_select_bench_individual { + ($bench_base_field:ty) => { + 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) = + cond_select::<$bench_base_field, _>(cs.clone(), rng); + + 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!( + "cond_select takes: {} constraints, {} non-zeros", + 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 +295,5 @@ 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!(ark_mnt6_753::Fr); } From db7b97dc5b81f7f68acbef3d7fb88ef068793a25 Mon Sep 17 00:00:00 2001 From: mmagician Date: Thu, 4 May 2023 12:14:22 -0600 Subject: [PATCH 15/27] format links --- src/select.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/select.rs b/src/select.rs index a9b4edd8..23e2dcfe 100644 --- a/src/select.rs +++ b/src/select.rs @@ -42,7 +42,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. This is hybrid method 5.3 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf. + /// big endian order. This is hybrid method 5.3 from . /// /// # Example /// To get the 6th element of `values`, convert unsigned integer 6 (`0b110`) @@ -83,7 +83,7 @@ where } } -/// Sum of conditions method 5.2 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf +/// Sum of conditions method 5.2 from /// Use this to generate the selector sums. fn sum_of_conditions( position: &[Boolean], @@ -117,7 +117,8 @@ fn sum_of_conditions( // `selectors[4] <== b_2` // `selectors[8] <== b_3` - // First element is 1==true, but we've already initialized the vector with `true`. + // 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)) { @@ -143,10 +144,12 @@ fn sum_of_conditions( // `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] + // 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 { @@ -194,7 +197,7 @@ fn allocate_vars>( allocated_vars } -/// Repeated selection method 5.1 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf +/// Repeated selection method 5.1 from fn repeated_selection>( position: &[Boolean], values: Vec, From f7e8abc407db1b53398a58ce62ad9c358a599519 Mon Sep 17 00:00:00 2001 From: mmagician Date: Thu, 4 May 2023 16:07:47 -0600 Subject: [PATCH 16/27] simplify `add_lc` for `FpVar` --- src/fields/fp/mod.rs | 5 ++--- src/select.rs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 80129376..62b5bdaa 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -982,11 +982,10 @@ impl CondSelectGadget for FpVar { fn add_lc( val: &Self, - lc: &LinearCombination, + lc: LinearCombination, ) -> Result, SynthesisError> { - let root: LinearCombination = LinearCombination::zero(); let v = val.value()?; - Ok(root + (v, lc)) + Ok(lc * v) } fn allocate_to_lc( diff --git a/src/select.rs b/src/select.rs index 23e2dcfe..28d38ba6 100644 --- a/src/select.rs +++ b/src/select.rs @@ -27,7 +27,7 @@ where fn add_lc( _val: &Self, - _lc: &LinearCombination, + _lc: LinearCombination, ) -> Result, SynthesisError> { unimplemented!() } @@ -71,7 +71,7 @@ where for i in 0..two_to_m { let mut x = LinearCombination::zero(); for j in 0..two_to_l { - x = &x + Self::add_lc(&values[i * two_to_l + j], &sub_tree[j].clone())?; + x = &x + Self::add_lc(&values[i * two_to_l + j], sub_tree[j].clone())?; } upper_leaves.push(x); } From d5f3a34aa5eabdc1a9dd24e950aeeecf361fb8da Mon Sep 17 00:00:00 2001 From: mmagician Date: Thu, 4 May 2023 16:08:05 -0600 Subject: [PATCH 17/27] rename fn to `allocate_with_value` --- src/fields/fp/mod.rs | 2 +- src/select.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 62b5bdaa..34080447 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -988,7 +988,7 @@ impl CondSelectGadget for FpVar { Ok(lc * v) } - fn allocate_to_lc( + fn allocate_with_value( var: Variable, val: &Self, cs: &ConstraintSystemRef, diff --git a/src/select.rs b/src/select.rs index 28d38ba6..767fc4db 100644 --- a/src/select.rs +++ b/src/select.rs @@ -32,7 +32,7 @@ where unimplemented!() } - fn allocate_to_lc( + fn allocate_with_value( _var: Variable, _val: &Self, _cs: &ConstraintSystemRef, @@ -190,7 +190,7 @@ fn allocate_vars>( .zip(lc) .map(|(val, lc)| { let var = cs.new_lc(lc)?; - CondG::allocate_to_lc(var, val, &cs) + CondG::allocate_with_value(var, val, &cs) }) .collect::, _>>(); From 6939d3f212f78806cecc823dbf006ed9004589aa Mon Sep 17 00:00:00 2001 From: mmagician Date: Thu, 4 May 2023 16:08:13 -0600 Subject: [PATCH 18/27] remove dead code --- src/select.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/select.rs b/src/select.rs index 767fc4db..0b6dd946 100644 --- a/src/select.rs +++ b/src/select.rs @@ -20,11 +20,6 @@ where false_value: &Self, ) -> Result; - // fn sum_of_lc( - // lc: Vec>, - // values: &[Self], - // ) -> Result; - fn add_lc( _val: &Self, _lc: LinearCombination, From 1ea679d503f9d8caa0df89a3fa6de4c5ef54452f Mon Sep 17 00:00:00 2001 From: mmagician Date: Sun, 7 May 2023 18:47:31 -0600 Subject: [PATCH 19/27] combine type-specific logic from two new trait methods into one call it `hybrid_selection` --- src/fields/fp/mod.rs | 43 +++++++++++++++++-------- src/select.rs | 76 ++++++++++++++------------------------------ 2 files changed, 52 insertions(+), 67 deletions(-) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 34080447..993c3069 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -980,21 +980,36 @@ impl CondSelectGadget for FpVar { } } - fn add_lc( - val: &Self, - lc: LinearCombination, - ) -> Result, SynthesisError> { - let v = val.value()?; - Ok(lc * v) - } + fn hybrid_selection( + values: &[Self], + root_vals: Vec, + two_to_l: usize, + two_to_m: usize, + sub_tree: Vec>, + cs: ConstraintSystemRef, + ) -> Result, SynthesisError> { + let mut upper_leaves = Vec::with_capacity(two_to_m); + + 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); + } - fn allocate_with_value( - var: Variable, - val: &Self, - cs: &ConstraintSystemRef, - ) -> Result { - let v = val.value()?; - Ok(AllocatedFp::new(Some(v), var, cs.clone()).into()) + 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 } } diff --git a/src/select.rs b/src/select.rs index 0b6dd946..bc64d8b3 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use ark_ff::Field; -use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, SynthesisError, Variable}; +use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, SynthesisError}; use ark_std::vec::Vec; /// Generates constraints for selecting between one of many values. pub trait CondSelectGadget @@ -20,18 +20,15 @@ where false_value: &Self, ) -> Result; - fn add_lc( - _val: &Self, - _lc: LinearCombination, - ) -> Result, SynthesisError> { - unimplemented!() - } - - fn allocate_with_value( - _var: Variable, - _val: &Self, - _cs: &ConstraintSystemRef, - ) -> Result { + #[allow(unused_variables)] + fn hybrid_selection( + values: &[Self], + root_vals: Vec, + two_to_l: usize, + two_to_m: usize, + sub_tree: Vec>, + cs: ConstraintSystemRef, + ) -> Result, SynthesisError> { unimplemented!() } @@ -47,6 +44,7 @@ where position: &[Boolean], values: &[Self], ) -> Result { + let cs = position[0].cs(); let n = position.len(); // split n into l and m, where l + m = n @@ -61,17 +59,20 @@ where let lower_bits = &mut position[m..].to_vec(); let sub_tree = sum_of_conditions(lower_bits)?; - let mut upper_leaves = Vec::with_capacity(two_to_m); - - for i in 0..two_to_m { - let mut x = LinearCombination::zero(); - for j in 0..two_to_l { - x = &x + Self::add_lc(&values[i * two_to_l + j], sub_tree[j].clone())?; - } - upper_leaves.push(x); + // 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 = allocate_vars(values, lower_bits, upper_leaves)?; + let upper_elems = + Self::hybrid_selection(values, root_vals, two_to_l, two_to_m, sub_tree, cs)?; let upper_bits = &mut position[..m].to_vec(); repeated_selection(upper_bits, upper_elems) @@ -161,37 +162,6 @@ fn sum_of_conditions( Ok(selector_sums) } -fn allocate_vars>( - values: &[CondG], - position: &[Boolean], - lc: Vec>, -) -> Result, SynthesisError> { - let cs = position.cs(); - - // index for the chunk - let mut index = 0; - for x in position { - index *= 2; - index += if x.value()? { 1 } else { 0 }; - } - let chunk_size = 1 << position.len(); - let root_vals: Vec = values - .chunks(chunk_size) - .map(|chunk| chunk[index].clone()) - .collect(); - - let allocated_vars: Result, _> = root_vals - .iter() - .zip(lc) - .map(|(val, lc)| { - let var = cs.new_lc(lc)?; - CondG::allocate_with_value(var, val, &cs) - }) - .collect::, _>>(); - - allocated_vars -} - /// Repeated selection method 5.1 from fn repeated_selection>( position: &[Boolean], From 760b045fe1508d34cdfa7dcfec50d667092389f9 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 8 May 2023 17:25:13 -0600 Subject: [PATCH 20/27] Add some initial docs for hybrid selection method fmt update docs --- src/fields/fp/mod.rs | 2 ++ src/select.rs | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 993c3069..c254ce80 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -990,6 +990,7 @@ impl CondSelectGadget for FpVar { ) -> Result, SynthesisError> { 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 { @@ -999,6 +1000,7 @@ impl CondSelectGadget for FpVar { 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) diff --git a/src/select.rs b/src/select.rs index bc64d8b3..11adc3c1 100644 --- a/src/select.rs +++ b/src/select.rs @@ -21,6 +21,14 @@ where ) -> Result; #[allow(unused_variables)] + /// Given a: + /// - vector of leaf values `values` + /// - vector of values `root_vals`, where each element is the expected value + /// of a root of a subtree corresponding to the lowest `l` bits + /// - list of linear combination `sub_tree` for bottom tree of `2^l` leaves, + /// represnting the sum-of-condidtions of elements. + /// Return: + /// - a vector of allocated values of `Self`, TODO fn hybrid_selection( values: &[Self], root_vals: Vec, @@ -74,6 +82,7 @@ where let upper_elems = Self::hybrid_selection(values, root_vals, two_to_l, two_to_m, sub_tree, cs)?; + // 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) } From 3ca576bbbeb449c077dbbe7ed8339c5c43d55086 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 8 May 2023 15:40:15 -0600 Subject: [PATCH 21/27] implement hybrid selection for `AllocatedBool` and `Boolean` --- src/bits/boolean.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/bits/boolean.rs b/src/bits/boolean.rs index ff9e79ad..c503cc5b 100644 --- a/src/bits/boolean.rs +++ b/src/bits/boolean.rs @@ -216,6 +216,26 @@ impl CondSelectGadget for AllocatedBool { _ => unreachable!("Impossible"), } } + + fn hybrid_selection( + values: &[Self], + root_vals: Vec, + two_to_l: usize, + two_to_m: usize, + sub_tree: Vec>, + cs: ConstraintSystemRef, + ) -> Result, SynthesisError> { + 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) + } } /// Represents a boolean value in the constraint system which is guaranteed @@ -950,6 +970,26 @@ impl CondSelectGadget for Boolean { }, } } + + fn hybrid_selection( + values: &[Self], + root_vals: Vec, + two_to_l: usize, + two_to_m: usize, + sub_tree: Vec>, + cs: ConstraintSystemRef, + ) -> Result, SynthesisError> { + 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) + } } #[cfg(test)] @@ -958,6 +998,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 +1859,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] + ) + } + } } From b5f88b83d4830ae9fe8d7a29621fe1dca1c0aa02 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 8 May 2023 15:49:28 -0600 Subject: [PATCH 22/27] implement `hybrid_selection` for `AllocatedFp` --- src/fields/fp/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index c254ce80..8addf65c 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -578,6 +578,28 @@ impl CondSelectGadget for AllocatedFp { }, } } + + fn hybrid_selection( + values: &[Self], + root_vals: Vec, + two_to_l: usize, + two_to_m: usize, + sub_tree: Vec>, + cs: ConstraintSystemRef, + ) -> Result, SynthesisError> { + // 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) + } } /// Uses two bits to perform a lookup into a table From 777da9c31edf8d9ff4f8c2570f1655f65e7d26f9 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 8 May 2023 17:01:37 -0600 Subject: [PATCH 23/27] Implement hybrid selection for ProjectiveVar for SW --- src/groups/curves/short_weierstrass/mod.rs | 105 ++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/src/groups/curves/short_weierstrass/mod.rs b/src/groups/curves/short_weierstrass/mod.rs index b41d959a..2d3eba75 100644 --- a/src/groups/curves/short_weierstrass/mod.rs +++ b/src/groups/curves/short_weierstrass/mod.rs @@ -5,7 +5,7 @@ use ark_ec::{ AffineRepr, CurveGroup, }; use ark_ff::{BigInteger, BitIteratorBE, Field, One, PrimeField, Zero}; -use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData, ops::Mul}; use non_zero_affine::NonZeroAffineVar; @@ -720,6 +720,54 @@ where Ok(Self::new(x, y, z)) } + + fn hybrid_selection( + values: &[Self], + root_vals: Vec, + two_to_l: usize, + two_to_m: usize, + sub_tree: Vec::BasePrimeField>>, + cs: ConstraintSystemRef<::BasePrimeField>, + ) -> Result, SynthesisError> { + let xs = values.iter().map(|v| v.x.clone()).collect::>(); + let root_xs = root_vals.iter().map(|v| v.x.clone()).collect::>(); + + let x = F::hybrid_selection( + &xs, + root_xs, + two_to_l, + two_to_m, + sub_tree.clone(), + cs.clone(), + )?; + + let ys = values.iter().map(|v| v.y.clone()).collect::>(); + let root_ys = root_vals.iter().map(|v| v.y.clone()).collect::>(); + + let y = F::hybrid_selection( + &ys, + root_ys, + two_to_l, + two_to_m, + sub_tree.clone(), + cs.clone(), + )?; + + let zs = values.iter().map(|v| v.z.clone()).collect::>(); + let root_zs = root_vals.iter().map(|v| v.z.clone()).collect::>(); + + let z = F::hybrid_selection(&zs, root_zs, two_to_l, two_to_m, sub_tree, cs)?; + + // zip x, y, z and make Vec of Self + let root_vals = x + .iter() + .zip(y.iter()) + .zip(z.iter()) + .map(|((x, y), z)| Self::new(x.clone(), y.clone(), z.clone())) + .collect::>(); + + Ok(root_vals) + } } impl EqGadget<::BasePrimeField> for ProjectiveVar @@ -965,3 +1013,58 @@ where Ok(bytes) } } + +#[cfg(test)] +mod test { + + use crate::groups::curves::short_weierstrass::ProjectiveVar; + use crate::{fields::fp::FpVar, 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] + ) + } + } +} From 6c4e56ffc14510e0ff279cf2b3bf6f703c093da3 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 8 May 2023 19:01:28 -0600 Subject: [PATCH 24/27] Refactor: inline `hybrid_selection` into `conditionally_select._powers_of_two` --- src/bits/boolean.rs | 118 +++++++++++----- src/fields/fp/mod.rs | 149 ++++++++++++++------- src/groups/curves/short_weierstrass/mod.rs | 63 +++------ src/select.rs | 60 +-------- 4 files changed, 211 insertions(+), 179 deletions(-) diff --git a/src/bits/boolean.rs b/src/bits/boolean.rs index c503cc5b..bcc8e432 100644 --- a/src/bits/boolean.rs +++ b/src/bits/boolean.rs @@ -217,24 +217,53 @@ impl CondSelectGadget for AllocatedBool { } } - fn hybrid_selection( + fn conditionally_select_power_of_two_vector( + position: &[Boolean], values: &[Self], - root_vals: Vec, - two_to_l: usize, - two_to_m: usize, - sub_tree: Vec>, - cs: ConstraintSystemRef, - ) -> Result, SynthesisError> { - 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)?; + ) -> 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) + }?; - 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) } } @@ -971,24 +1000,53 @@ impl CondSelectGadget for Boolean { } } - fn hybrid_selection( + fn conditionally_select_power_of_two_vector( + position: &[Boolean], values: &[Self], - root_vals: Vec, - two_to_l: usize, - two_to_m: usize, - sub_tree: Vec>, - cs: ConstraintSystemRef, - ) -> Result, SynthesisError> { - 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())?; + ) -> 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) + }?; - 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) } } diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index 8addf65c..a34eed3d 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -579,26 +579,55 @@ impl CondSelectGadget for AllocatedFp { } } - fn hybrid_selection( + fn conditionally_select_power_of_two_vector( + position: &[Boolean], values: &[Self], - root_vals: Vec, - two_to_l: usize, - two_to_m: usize, - sub_tree: Vec>, - cs: ConstraintSystemRef, - ) -> Result, SynthesisError> { - // 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; + ) -> 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)?; } - cs.enforce_constraint(lc, lc!() + Variable::One, lc!() + root_vals[i].variable)?; - } + Ok(root_vals) + }?; - 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) } } @@ -1002,38 +1031,68 @@ impl CondSelectGadget for FpVar { } } - fn hybrid_selection( + fn conditionally_select_power_of_two_vector( + position: &[Boolean], values: &[Self], - root_vals: Vec, - two_to_l: usize, - two_to_m: usize, - sub_tree: Vec>, - cs: ConstraintSystemRef, - ) -> Result, SynthesisError> { - 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); + ) -> 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 + }?; - // 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) } } diff --git a/src/groups/curves/short_weierstrass/mod.rs b/src/groups/curves/short_weierstrass/mod.rs index 2d3eba75..73d66495 100644 --- a/src/groups/curves/short_weierstrass/mod.rs +++ b/src/groups/curves/short_weierstrass/mod.rs @@ -5,7 +5,7 @@ use ark_ec::{ AffineRepr, CurveGroup, }; use ark_ff::{BigInteger, BitIteratorBE, Field, One, PrimeField, Zero}; -use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, Namespace, SynthesisError}; +use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData, ops::Mul}; use non_zero_affine::NonZeroAffineVar; @@ -721,52 +721,20 @@ where Ok(Self::new(x, y, z)) } - fn hybrid_selection( + fn conditionally_select_power_of_two_vector( + position: &[Boolean<::BasePrimeField>], values: &[Self], - root_vals: Vec, - two_to_l: usize, - two_to_m: usize, - sub_tree: Vec::BasePrimeField>>, - cs: ConstraintSystemRef<::BasePrimeField>, - ) -> Result, SynthesisError> { - let xs = values.iter().map(|v| v.x.clone()).collect::>(); - let root_xs = root_vals.iter().map(|v| v.x.clone()).collect::>(); - - let x = F::hybrid_selection( - &xs, - root_xs, - two_to_l, - two_to_m, - sub_tree.clone(), - cs.clone(), - )?; - - let ys = values.iter().map(|v| v.y.clone()).collect::>(); - let root_ys = root_vals.iter().map(|v| v.y.clone()).collect::>(); - - let y = F::hybrid_selection( - &ys, - root_ys, - two_to_l, - two_to_m, - sub_tree.clone(), - cs.clone(), - )?; - - let zs = values.iter().map(|v| v.z.clone()).collect::>(); - let root_zs = root_vals.iter().map(|v| v.z.clone()).collect::>(); - - let z = F::hybrid_selection(&zs, root_zs, two_to_l, two_to_m, sub_tree, cs)?; - - // zip x, y, z and make Vec of Self - let root_vals = x - .iter() - .zip(y.iter()) - .zip(z.iter()) - .map(|((x, y), z)| Self::new(x.clone(), y.clone(), z.clone())) - .collect::>(); - - Ok(root_vals) + ) -> 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)) } } @@ -1017,8 +985,7 @@ where #[cfg(test)] mod test { - use crate::groups::curves::short_weierstrass::ProjectiveVar; - use crate::{fields::fp::FpVar, prelude::*}; + 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; diff --git a/src/select.rs b/src/select.rs index 11adc3c1..42129319 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use ark_ff::Field; -use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, SynthesisError}; +use ark_relations::r1cs::{LinearCombination, SynthesisError}; use ark_std::vec::Vec; /// Generates constraints for selecting between one of many values. pub trait CondSelectGadget @@ -20,26 +20,6 @@ where false_value: &Self, ) -> Result; - #[allow(unused_variables)] - /// Given a: - /// - vector of leaf values `values` - /// - vector of values `root_vals`, where each element is the expected value - /// of a root of a subtree corresponding to the lowest `l` bits - /// - list of linear combination `sub_tree` for bottom tree of `2^l` leaves, - /// represnting the sum-of-condidtions of elements. - /// Return: - /// - a vector of allocated values of `Self`, TODO - fn hybrid_selection( - values: &[Self], - root_vals: Vec, - two_to_l: usize, - two_to_m: usize, - sub_tree: Vec>, - cs: ConstraintSystemRef, - ) -> Result, SynthesisError> { - unimplemented!() - } - /// 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. This is hybrid method 5.3 from . @@ -52,45 +32,13 @@ where 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 = - Self::hybrid_selection(values, root_vals, two_to_l, two_to_m, sub_tree, cs)?; - - // 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) + repeated_selection(position, values.to_vec()) } } /// Sum of conditions method 5.2 from /// Use this to generate the selector sums. -fn sum_of_conditions( +pub fn sum_of_conditions( position: &[Boolean], ) -> Result>, SynthesisError> { let n = position.len(); @@ -172,7 +120,7 @@ fn sum_of_conditions( } /// Repeated selection method 5.1 from -fn repeated_selection>( +pub fn repeated_selection>( position: &[Boolean], values: Vec, ) -> Result { From 74ce483c5f81c07a8a6f31245044f03ff19ea649 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 8 May 2023 19:04:50 -0600 Subject: [PATCH 25/27] update the docs for the default and implementors of improved method --- src/bits/boolean.rs | 2 ++ src/fields/fp/mod.rs | 2 ++ src/select.rs | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bits/boolean.rs b/src/bits/boolean.rs index bcc8e432..595d153c 100644 --- a/src/bits/boolean.rs +++ b/src/bits/boolean.rs @@ -217,6 +217,7 @@ impl CondSelectGadget for AllocatedBool { } } + /// Using the hybrid method 5.3 from . fn conditionally_select_power_of_two_vector( position: &[Boolean], values: &[Self], @@ -1000,6 +1001,7 @@ impl CondSelectGadget for Boolean { } } + /// Using the hybrid method 5.3 from . fn conditionally_select_power_of_two_vector( position: &[Boolean], values: &[Self], diff --git a/src/fields/fp/mod.rs b/src/fields/fp/mod.rs index a34eed3d..d0736da7 100644 --- a/src/fields/fp/mod.rs +++ b/src/fields/fp/mod.rs @@ -579,6 +579,7 @@ impl CondSelectGadget for AllocatedFp { } } + /// Using the hybrid method 5.3 from . fn conditionally_select_power_of_two_vector( position: &[Boolean], values: &[Self], @@ -1031,6 +1032,7 @@ impl CondSelectGadget for FpVar { } } + /// Using the hybrid method 5.3 from . fn conditionally_select_power_of_two_vector( position: &[Boolean], values: &[Self], diff --git a/src/select.rs b/src/select.rs index 42129319..7923d6b8 100644 --- a/src/select.rs +++ b/src/select.rs @@ -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. This is hybrid method 5.3 from . + /// 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`) @@ -37,7 +37,7 @@ where } /// Sum of conditions method 5.2 from -/// Use this to generate the selector sums. +/// Use this to generate the selector conditions. pub fn sum_of_conditions( position: &[Boolean], ) -> Result>, SynthesisError> { From f165d68acff692153c73cdffffb631b4892f2145 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 8 May 2023 19:37:36 -0600 Subject: [PATCH 26/27] Add bench macro to encompass both Boolean and FpVar select methods --- benches/bench.rs | 86 +++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 30422c97..a336ddc8 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,4 +1,4 @@ -use ark_ff::PrimeField; +use ark_ff::{PrimeField, UniformRand}; use ark_r1cs_std::{ alloc::AllocVar, eq::EqGadget, @@ -164,39 +164,8 @@ fn inverse( ); } -fn cond_select( - cs: ConstraintSystemRef, - rng: &mut R, -) -> (usize, usize) { - // value array - let values: Vec = (0..128).map(|_| BaseField::rand(rng)).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(); - - let constraints_before = cs.num_constraints(); - let nonzeros_before = get_density(&cs); - - let _ = FpVar::conditionally_select_power_of_two_vector(&position_var, &values_const); - - let constraints_after = cs.num_constraints(); - let nonzeros_after = get_density(&cs); - - return ( - constraints_after - constraints_before, - nonzeros_after - nonzeros_before, - ); -} - macro_rules! cond_select_bench_individual { - ($bench_base_field:ty) => { + ($bench_name:ident, $bench_base_field:ty, $var:ty, $native_type:ty) => { let rng = &mut ark_std::test_rng(); let mut num_constraints = 0; let mut num_nonzeros = 0; @@ -205,8 +174,36 @@ macro_rules! cond_select_bench_individual { let cs = ConstraintSystemRef::new(cs_sys); cs.set_optimization_goal(OptimizationGoal::Constraints); - let (cur_constraints, cur_nonzeros) = - cond_select::<$bench_base_field, _>(cs.clone(), rng); + 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; @@ -216,8 +213,10 @@ macro_rules! cond_select_bench_individual { let average_constraints = num_constraints / NUM_REPETITIONS; let average_nonzeros = num_nonzeros / NUM_REPETITIONS; println!( - "cond_select takes: {} constraints, {} non-zeros", - average_constraints, average_nonzeros, + "{} takes: {} constraints, {} non-zeros", + stringify!($bench_name), + average_constraints, + average_nonzeros, ); }; } @@ -295,5 +294,16 @@ 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!(ark_mnt6_753::Fr); + cond_select_bench_individual!( + FpVar_select, + ark_mnt6_753::Fr, + FpVar, + ark_mnt6_753::Fr + ); + cond_select_bench_individual!( + Boolean_select, + ark_mnt6_753::Fr, + Boolean, + bool + ); } From 2f79c48fc17ba1979c2153e3e7b054518f211fc0 Mon Sep 17 00:00:00 2001 From: mmagician Date: Mon, 8 May 2023 19:47:15 -0600 Subject: [PATCH 27/27] Add further support for ProjectiveVar --- benches/bench.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index a336ddc8..248516af 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,8 +1,10 @@ +use ark_ec::short_weierstrass::Projective; use ark_ff::{PrimeField, UniformRand}; use ark_r1cs_std::{ alloc::AllocVar, eq::EqGadget, fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar}, + groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, prelude::Boolean, select::CondSelectGadget, }; @@ -165,7 +167,7 @@ fn inverse( } macro_rules! cond_select_bench_individual { - ($bench_name:ident, $bench_base_field:ty, $var:ty, $native_type:ty) => { + ($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; @@ -178,7 +180,8 @@ macro_rules! cond_select_bench_individual { // 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(); + let values_const: Vec<$var> = + values.iter().map(|x| <$var>::$constant(*x)).collect(); // index array let position: Vec = (0..7).map(|_| rng.gen()).collect(); @@ -298,12 +301,22 @@ fn main() { 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, - bool + bool, + Constant + ); + pub type FBaseVar = FpVar; + cond_select_bench_individual!( + SWProjective_select, + ark_mnt6_753::Fq, + ProjectiveVar, + Projective, + constant ); }