-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce UIntGadget and ComparisonGadget #163
Introduce UIntGadget and ComparisonGadget #163
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work overall !
A small note my side:
Sometimes, especially when enforcing constraints involving Vec, we often write constraints involving explicitly huge LCs instead of Field Elements read from such LCs using the FromBits gadget. While this allows to save some constraints (not much in the end, maybe a couple) it dramatically increase circuit densities, and since our focus, now, is on densities more than number of constraints, this over complication might be unnecessary. Let's discuss this.
@@ -631,6 +631,57 @@ impl Boolean { | |||
} | |||
} | |||
|
|||
/// Enforce that at least one operand is true, given that bits.len() is less than the size of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last modifications are ok.
I would modify other parts of the file if it is possible.
- In the function kary_and there should be given a short description of what this function is and what is its purpose.
- For the functions enforce_smaller_or_equal_than_le it could be good a more detailed description in in-line comments about how it works.
- Also conditional_enforce_equal and conditional_enforce_not_equal some inline comments should be given.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
/// In addition, the function also returns a flag which specifies if there are no "real" variables | ||
/// in the linear combination, that is if the sequence of Booleans comprises all constant values. | ||
/// Assumes that `bits` can be packed in a single field element (i.e., bits.len() <= ConstraintF::Params::CAPACITY). | ||
pub fn bits_to_linear_combination<'a, ConstraintF:Field, CS: ConstraintSystemAbstract<ConstraintF>>(_cs: CS, bits: impl Iterator<Item=&'a Boolean>) -> (LinearCombination<ConstraintF>, Option<ConstraintF>, bool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Split the definition of the function in multiple lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will also run cargo fmt
to automatically fix these formatting issues
@@ -808,7 +892,16 @@ impl<ConstraintF: Field> EqGadget<ConstraintF> for Boolean { | |||
// 1 - 1 = 0 - 0 = 0 | |||
(Constant(true), Constant(true)) | (Constant(false), Constant(false)) => return Ok(()), | |||
// false != true | |||
(Constant(_), Constant(_)) => return Err(SynthesisError::AssignmentMissing), | |||
(Constant(_), Constant(_)) => { | |||
if should_enforce.is_constant() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the utility of this if?
Why is it not present in conditional_enforce_not_equal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a bugfix for a bug I found out when running unit tests I wrote for UIntGadget
. In the previous implementation, if self
and other
were constants with different values, conditional_enforce_equal
would simply return an error, independently from the value of should_enforce
flag, which was wrong. Therefore, I modified the code to handle all the possible cases depending on the should_enforce
variable.
The bug was triggered in my unit tests only for conditional_enforce_equal
function, but I forgot to check if the same bug exists also for conditional_enforce_not_equal
, which explains why the code differs between the 2 functions. Thanks to your comment, I found out that the same bug should exist also for conditional_enforce_not_equal
, so I will modify the code accordingly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While reviewing the code of conditional_enforce_not_equal
to fix the bug for the case when self
and other
are both constants with the same value, I found out that the constraint enforced at the end of the function was wrong. Therefore I had to modify the constraint, which also required to add the eval_lc
function the ConstraintSystemAbstract
trait to compute the value of a linear combination of variables in the constraint system. I have also modified the unit test for conditional_enforce_equal
to test also the conditional_enforce_not_equal
function, which was not tested before, explaining why we missed the incorrect constraint previously enforced in such function
let mut value: Option<$native_type> = Some(0); | ||
for (i, el) in bits_iter.enumerate() { | ||
bits.push(*el); | ||
value = match el.get_value() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What "value" should be?
Should it not be the the integer corresponding to the sequence of bits? It does not seem from the definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is the integer corresponding to the sequence of bits interpreted in little-endian. I will update the description of the function to specify that the bits are considered as the little-endian representation of the integer being constructed
r1cs/gadgets/std/src/bits/macros.rs
Outdated
// max_overflow_bits are the maximum number of non-zero bits a `Self` element | ||
// can have to be multiplied to another `Self` without overflowing the field | ||
let max_overflow_bits = field_bits - $bit_size; | ||
// given a base b = 2^m, where m=2^max_overflow_bits, the `other` operand is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m = max_overflow_bits
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for pointing it out
r1cs/gadgets/std/src/bits/macros.rs
Outdated
fn mulmany<CS>(mut cs: CS, operands: &[Self]) -> Result<Self, SynthesisError> | ||
where CS: ConstraintSystemAbstract<ConstraintF> { | ||
let num_operands = operands.len(); | ||
let field_bits = (ConstraintF::Params::CAPACITY) as usize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to call it capacity. field_bits reminds more MODULUS_BITS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will rename it, also in other functions
r1cs/gadgets/std/src/bits/macros.rs
Outdated
test_uint_gadget_value(result_value, &result_var, "result correctness"); | ||
assert!(cs.is_satisfied()); | ||
|
||
// negative test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it precisely mean? Can you provide a description of what are you testing here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add more details to the comment, thanks
r1cs/gadgets/std/src/bits/macros.rs
Outdated
assert!(cs.is_satisfied()); | ||
|
||
|
||
if MAX_NUM_OPERANDS >= 2 { // negative tests are skipped if if double and add must be used because the field is too small |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add more details to the comment, thanks
let mut is_overflowing = false; | ||
let result_value: $native_type = operand_values.iter().cloned().reduce(|a,b| { | ||
let (updated_sum, overflow) = a.overflowing_add(b); | ||
is_overflowing |= overflow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why don't you define directly
let (updated_sum, is_overflowing) = a.overflowing_add(b); ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With your proposal I think that is_overflowing
will be true only if the last addition overflows. However, the addition of all the operands does not overflow only if none of the additions between the accumulator and each operand overflows. That's why I compute is_overflowing
as the OR of all the overflow flags of each addition. Does it look sound to you?
const NUM_OPERANDS: usize = MAX_NUM_OPERANDS*2+5; | ||
|
||
// we want to test a case when the operands must be split in multiple chunks | ||
assert!(NUM_OPERANDS > MAX_NUM_OPERANDS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the need for this assert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It ensures that the test is run with a number of operands that will force the mulmany
operation to process the operands by splitting them in multiple chunks, as otherwise the result would overflow the field size. Although the assertion may seem redundant given that it is performed over constant values, I added it to avoid that possible future modifications of the parameters of the test (e.g., the field being used) will no longer ensure that operands are split in multiple chunks
r1cs/gadgets/std/src/eq.rs
Outdated
let len = self.len(); | ||
if field_bits < len { | ||
// In this case we cannot split in chunks here, as it's not true that if two bit vectors | ||
// are not equal, then they are not equal chunkwise too. Therefore, we |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand this. The "chunks" are just a partition of the original bit of vectors. How is it possible that the chunks are equal but the original sequence of bits is different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sorry, probably the comment wasn't clear enough. I meant that there may be corresponding chunks which are equal even if the bit vectors differ (i.e., self
and other
may differ in only one chunk). I will modify the comment as follows to make it clearer:
// In this case we cannot split `self` and `other` in chunks here and enforce that each
// pair of chunks differ, as it's not true that if two bit vectors
// are not equal, then all their corresponding chunks differ.`
…force_eq for Boolean
@@ -73,15 +78,17 @@ where | |||
cur = sha256_compression_function(cs.ns(|| format!("block {}", i)), block, &cur)?; | |||
} | |||
|
|||
Ok(cur.into_iter().flat_map(|e| e.into_bits_be()).collect()) | |||
//Ok(cur.into_iter().enumerate().map(|(i,e)| e.to_bits(cs.ns(|| format!("unpack word {} of sha256 digest", i)))).collect::<Result<Vec<_>, SynthesisError>>()?.into_iter().flat_map(|e| e).collect()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove commented code
7a8abe2
to
81811bc
Compare
This PR addresses issues #149 and #150.
It defines a new trait
UIntGadget
which defines the functions available for small unsigned integers (up to 128 bits). In particular, theUIntGadget
comprises most common gadgets (e.g,EqGadget
,AllocGadget
) and specific functions like bitiwise and arithmetic operations. This PR introduces also a macro, namedimpl_uint_gadget
, which allows to define aUIntGadget
for an unsigned integer given a bit_size and an underlying Rust native type. The macro contains also unit tests that verifies the correctness of the functions implemented for theUIntGadget
being defined with the macro. The macro is employed to defineUIntGadget
for all the Rust native types for unsigned integers, starting fromu8
up tou128
. This macro mostly addresses issue #149.Furthermore, this PR introduces also a
ComparisonGadget
trait, which defines the operations that enable the comparison between gadgets implementing this trait. Two implementations of theComparisonGadget
are provided in this PR:UIntGadget
trait. In particular, the implementation of theComparisonGadget
trait is part of theimpl_uint_gadget
macro. This implementation addresses issue Optimized arithmetic and comparisons for UInt gadgets #150.FpGadget
elements, integrating the comparison functions already introduced inrc/feat/comp_gadget
branch; the implementation found in this PR is enriched with additional functions that allow to compare arbitrary field elements, as opposed to the implementation inrc/feat/comp_gadget
which requires the field elements to be smaller than (p-1)/2, where p is the modulus of the prime field. Remarkably, the functions added in this PR to deal with arbitrary field elements require only few constraints more than the corresponding ones requiring field elements smaller than (p-1)/2; however, the latter functions are retained in the implementation of theComparisonGadget
because they are still useful in case it is known that the field elements are smaller than (p-1)/2Lastly, an optimize implementation of the
EqGadget
is added in this PR for vectors of Booleans. This optimization allows to implement equality/inequality constraints on vectors of Booleans with about 4*len/field_bits constraints over a field for vectors with len bits, as opposed to the existing implementation that requires approximately 2 constraints per bit.