-
Notifications
You must be signed in to change notification settings - Fork 240
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
feat: 20-30% cost reduction in recursive ipa algorithm #9420
Changes from 3 commits
1b549f9
2309c28
377c704
4f956c6
b9dd785
03ae38b
a981a14
a54d242
efb3f72
17b3b2a
6ec18ff
5f24fd4
51ea9c8
bba2c68
4148dbc
a9e0edd
eb95746
521461e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -678,23 +678,110 @@ typename cycle_group<Builder>::cycle_scalar cycle_group<Builder>::cycle_scalar:: | |
template <typename Builder> cycle_group<Builder>::cycle_scalar::cycle_scalar(BigScalarField& scalar) | ||
{ | ||
auto* ctx = get_context() ? get_context() : scalar.get_context(); | ||
const uint256_t value((scalar.get_value() % uint512_t(ScalarField::modulus)).lo); | ||
const uint256_t value_lo = value.slice(0, LO_BITS); | ||
const uint256_t value_hi = value.slice(LO_BITS, HI_BITS); | ||
|
||
if (scalar.is_constant()) { | ||
const uint256_t value((scalar.get_value() % uint512_t(ScalarField::modulus)).lo); | ||
const uint256_t value_lo = value.slice(0, LO_BITS); | ||
const uint256_t value_hi = value.slice(LO_BITS, HI_BITS); | ||
|
||
lo = value_lo; | ||
hi = value_hi; | ||
// N.B. to be able to call assert equal, these cannot be constants | ||
} else { | ||
lo = witness_t(ctx, value_lo); | ||
hi = witness_t(ctx, value_hi); | ||
field_t zero = field_t(0); | ||
zero.convert_constant_to_fixed_witness(ctx); | ||
BigScalarField lo_big(lo, zero); | ||
BigScalarField hi_big(hi, zero); | ||
BigScalarField res = lo_big + hi_big * BigScalarField((uint256_t(1) << LO_BITS)); | ||
scalar.assert_equal(res); | ||
validate_scalar_is_in_field(); | ||
// To efficiently convert a bigfield into a cycle scalar, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for taking the time to add all the docs :) |
||
// we are going to explicitly rely on the fact that `scalar.lo` and `scalar.hi` | ||
// are implicitly range-constrained to be 128 bits when they are converted into 4-bit lookup window slices | ||
|
||
// First check: can the scalar actually fit into LO_BITS + HI_BITS? | ||
// If it can, we can tolerate the scalar being > ScalarField::modulus, because performing a scalar mul | ||
// implicilty performs a modular reduction | ||
// If not, call `self_reduce` to cut enougn modulus multiples until the above condition is met | ||
if (scalar.get_maximum_value() >= (uint512_t(1) << (LO_BITS + HI_BITS))) { | ||
scalar.self_reduce(); | ||
} | ||
|
||
field_t limb0 = scalar.binary_basis_limbs[0].element; | ||
field_t limb1 = scalar.binary_basis_limbs[1].element; | ||
field_t limb2 = scalar.binary_basis_limbs[2].element; | ||
field_t limb3 = scalar.binary_basis_limbs[3].element; | ||
|
||
// The general plan is as follows: | ||
// 1. ensure limb0 contains no more than BigScalarField::NUM_LIMB_BITS | ||
// 2. define limb1_lo = limb1.slice(0, LO_BITS - BigScalarField::NUM_LIMB_BITS) | ||
// 3. define limb1_hi = limb1.slice(LO_BITS - BigScalarField::NUM_LIMB_BITS, <whatever maximum bound of limb1 | ||
// is>) | ||
// 4. construct *this.lo out of limb0 and limb1_lo | ||
// 5. construct *this.hi out of limb1_hi, limb2 and limb3 | ||
// This is a lot of logic, but very cheap on constraints. | ||
// For fresh bignums that have come out of a MUL operation, | ||
// the only "expensive" part is a size (LO_BITS - BigScalarField::NUM_LIMB_BITS) range check | ||
|
||
// to convert into a cycle_scalar, we need to convert 4*68 bit limbs into 2*128 bit limbs | ||
// we also need to ensure that the number of bits in cycle_scalar is < LO_BITS + HI_BITS | ||
// note: we do not need to validate that the scalar is within the field modulus | ||
// because performing a scalar multiplication implicitly performs a modular reduction (ecc group is | ||
// multiplicative modulo BigField::modulus) | ||
|
||
uint256_t limb1_max = scalar.binary_basis_limbs[1].maximum_value; | ||
|
||
// Ensure that limb0 only contains at most NUM_LIMB_BITS. If it exceeds this value, slice of the excess and add | ||
// it into limb1 | ||
if (scalar.binary_basis_limbs[0].maximum_value > BigScalarField::DEFAULT_MAXIMUM_LIMB) { | ||
const uint256_t limb = limb0.get_value(); | ||
const uint256_t lo_v = limb.slice(0, BigScalarField::NUM_LIMB_BITS); | ||
const uint256_t hi_v = limb >> BigScalarField::NUM_LIMB_BITS; | ||
field_t lo = field_t::from_witness(ctx, lo_v); | ||
field_t hi = field_t::from_witness(ctx, hi_v); | ||
Comment on lines
+733
to
+734
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't this two lines create an unconstrained witness, shouldn't we create a constant and then call convert_constant_to_fixed_witness? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those aren’t constants. They vary depending on the value of limb. We constrain them to be correct via the lines 738-740 If we called |
||
|
||
uint256_t hi_max = (scalar.binary_basis_limbs[0].maximum_value >> BigScalarField::NUM_LIMB_BITS); | ||
const size_t hi_bits = hi_max.get_msb() + 1; | ||
lucasxia01 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
lo.create_range_constraint(BigScalarField::NUM_LIMB_BITS); | ||
hi.create_range_constraint(hi_bits); | ||
limb0.assert_equal(lo + hi * BigScalarField::shift_1); | ||
|
||
limb1 += hi; | ||
limb1_max += hi_max; | ||
limb0 = lo; | ||
} | ||
|
||
// sanity check that limb[1] is the limb that contributs both to *this.lo and *this.hi | ||
ASSERT((BigScalarField::NUM_LIMB_BITS * 2 > LO_BITS) && (BigScalarField::NUM_LIMB_BITS < LO_BITS)); | ||
|
||
// limb1 is the tricky one as it contributs to both *this.lo and *this.hi | ||
// By this point, we know that limb1 fits in the range `1 << BigScalarField::NUM_LIMB_BITS to (1 << | ||
// BigScalarField::NUM_LIMB_BITS) + limb1_max.get_maximum_value() we need to slice this limb into 2. The first | ||
// is LO_BITS - BigScalarField::NUM_LIMB_BITS (which reprsents its contribution to *this.lo) and the second | ||
// represents the limbs contribution to *this.hi Step 1: compute the max bit sizes of both slices | ||
const size_t lo_bits_in_limb_1 = LO_BITS - BigScalarField::NUM_LIMB_BITS; | ||
const size_t hi_bits_in_limb_1 = (limb1_max.get_msb() + 1) - lo_bits_in_limb_1; | ||
|
||
// Step 2: compute the witness values of both slices | ||
const uint256_t limb_1 = limb1.get_value(); | ||
const uint256_t limb_1_hi_multiplicand = (uint256_t(1) << lo_bits_in_limb_1); | ||
const uint256_t limb_1_hi_v = limb_1 >> lo_bits_in_limb_1; | ||
const uint256_t limb_1_lo_v = limb_1 - (limb_1_hi_v << lo_bits_in_limb_1); | ||
|
||
// Step 3: instantiate both slices as witnesses and validate their sum equals limb1 | ||
field_t limb_1_lo = field_t::from_witness(ctx, limb_1_lo_v); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question here |
||
field_t limb_1_hi = field_t::from_witness(ctx, limb_1_hi_v); | ||
limb1.assert_equal(limb_1_hi * limb_1_hi_multiplicand + limb_1_lo); | ||
|
||
// Step 4: apply range constraints to validate both slices represent the expected contributions to *this.lo and | ||
// *this,hi | ||
limb_1_lo.create_range_constraint(lo_bits_in_limb_1); | ||
limb_1_hi.create_range_constraint(hi_bits_in_limb_1); | ||
|
||
// construct *this.lo out of: | ||
// a. `limb0` (the first NUM_LIMB_BITS bits of scalar) | ||
// b. `limb_1_lo` (the first LO_BITS - NUM_LIMB_BITS) of limb1 | ||
lo = limb0 + (limb_1_lo * BigScalarField::shift_1); | ||
|
||
const uint256_t limb_2_shift = uint256_t(1) << (BigScalarField::NUM_LIMB_BITS - lo_bits_in_limb_1); | ||
const uint256_t limb_3_shift = | ||
uint256_t(1) << ((BigScalarField::NUM_LIMB_BITS - lo_bits_in_limb_1) + BigScalarField::NUM_LIMB_BITS); | ||
|
||
// construct *this.hi out of limb2, limb3 and the remaining term from limb1 not contributing to `lo` | ||
hi = limb_1_hi.add_two(limb2 * limb_2_shift, limb3 * limb_3_shift); | ||
} | ||
}; | ||
|
||
|
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'd be nice to add more comments in this section
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.
can add more myself later