Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(avm): range check gadget #7967

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions barretenberg/cpp/pil/avm/alu.pil
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include "gadgets/range_check.pil";
namespace alu(256);

// ========= Table ALU-TR =================================================
Expand Down
190 changes: 190 additions & 0 deletions barretenberg/cpp/pil/avm/gadgets/range_check.pil
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
include "../main.pil";
include "../fixed/powers.pil";

namespace range_check(256);
pol commit clk;
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved

// Range check selector
pol commit sel_rng_chk;
sel_rng_chk * (1 - sel_rng_chk) = 0;

// Witnesses
// Value to range check
pol commit value;
// Number of bits to check against
pol commit rng_chk_bits;
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved

// Bit Size Columns
// It is enforced (further down) that the selected column is the lowest multiple of 16 that is greater than rng_chk_bits
Copy link
Contributor

@jeanmon jeanmon Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IlyasRidhuan " ... the lowest multiple of 16 that is greater than rng_chk_bits " I think you meant "greater or equal".
A 128-bit range check is allowed and in this case is_lte_u128 would be 1.

// e.g., rng_chk_bits = 10 ===> is_lte_u16, rng_chk_bits = 100 ==> is_lte_u112
// If rng_chk_bits is a multiple of 16, a prover is able to choose either is_lte_xx or is_lte_xx(+16), since the dynamic register will prove 0
// This isn't a concern and only costs the prover additional compute.
// TODO: Maybe we can get rid of is_lte_u128 since it's implicit if we have sel_rng_chk and no other is_lte_x
pol commit is_lte_u16;
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved
pol commit is_lte_u32;
pol commit is_lte_u48;
pol commit is_lte_u64;
pol commit is_lte_u80;
pol commit is_lte_u96;
pol commit is_lte_u112;
pol commit is_lte_u128;
is_lte_u16 * (1 - is_lte_u16) = 0;
is_lte_u32 * (1 - is_lte_u32) = 0;
is_lte_u48 * (1 - is_lte_u48) = 0;
is_lte_u64 * (1 - is_lte_u64) = 0;
is_lte_u80 * (1 - is_lte_u80) = 0;
is_lte_u96 * (1 - is_lte_u96) = 0;
is_lte_u112 * (1 - is_lte_u112) = 0;
is_lte_u128 * (1 - is_lte_u128) = 0;

// Mutual Exclusivity condition
is_lte_u16 + is_lte_u32 + is_lte_u48 + is_lte_u64 + is_lte_u80 + is_lte_u96 + is_lte_u112 + is_lte_u128 = sel_rng_chk;

// Eight 16-bit slice registers
pol commit u16_r0;
pol commit u16_r1;
pol commit u16_r2;
pol commit u16_r3;
pol commit u16_r4;
pol commit u16_r5;
pol commit u16_r6;
// This register has a (more expensive) set of constraint that enables dynamic range check of bit values between 0 and 16 bits
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved
pol commit u16_r7;

// In each of these relations, the u16_r7 register contains the most significant 16 bits of value.
Copy link
Contributor

@jeanmon jeanmon Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IlyasRidhuan "the most significant 16 bits of value." You meant after zero padding on the higher bits to express value with a number of bits which is defined by is_lte_XXX

pol X_0 = is_lte_u16 * u16_r7;
pol X_1 = is_lte_u32 * (u16_r0 + u16_r7 * 2**16);
pol X_2 = is_lte_u48 * (u16_r0 + u16_r1 * 2**16 + u16_r7 * 2**32);
pol X_3 = is_lte_u64 * (u16_r0 + u16_r1 * 2**16 + u16_r2 * 2**32 + u16_r7 * 2**48);
pol X_4 = is_lte_u80 * (u16_r0 + u16_r1 * 2**16 + u16_r2 * 2**32 + u16_r3 * 2**48 + u16_r7 * 2**64);
pol X_5 = is_lte_u96 * (u16_r0 + u16_r1 * 2**16 + u16_r2 * 2**32 + u16_r3 * 2**48 + u16_r4 * 2**64 + u16_r7 * 2**80);
pol X_6 = is_lte_u112 * (u16_r0 + u16_r1 * 2**16 + u16_r2 * 2**32 + u16_r3 * 2**48 + u16_r4 * 2**64 + u16_r5 * 2**80 + u16_r7 * 2**96);
pol X_7 = is_lte_u128 * (u16_r0 + u16_r1 * 2**16 + u16_r2 * 2**32 + u16_r3 * 2**48 + u16_r4 * 2**64 + u16_r5 * 2**80 + u16_r6 * 2**96 + u16_r7 * 2**112);

// Since the is_lte_x are mutually exclusive, only one of the Xs will be non-zero
pol RESULT = X_0 + X_1 + X_2 + X_3 + X_4 + X_5 + X_6 + X_7;

#[CHECK_RECOMPOSITION]
sel_rng_chk * (RESULT - value) = 0;

// ===== Dynamic Check Constraints =====

// The number of bits that form the dynamic range check is depending on the claimed lte value and the witness rng_chk_bits
// claimed is_lte_x | dyn_rng_chk_bits
// -----------------|-----------------
// is_lte_u16 | rng_chk_bits
// is_lte_u32 | rng_chk_bits - 16
// is_lte_u48 | rng_chk_bits - 32
// is_lte_u64 | rng_chk_bits - 48
// is_lte_u80 | rng_chk_bits - 64
// is_lte_u96 | rng_chk_bits - 80
// is_lte_u112 | rng_chk_bits - 96
// is_lte_u128 | rng_chk_bits - 112

// [CALCULATION STEPS]
// 1) Calculate dyn_rng_chk_bits from the table above
// 2) Calculate dyn_rng_chk_pow_2 = 2^dyn_rng_chk_bits
// 3) Calculate dyn_diff = dyn_rng_chk_pow_2 - u16_r7 - 1

// [ASSERTIONS]
// 1) Assert 0 <= dyn_rng_chk_bits <= 16 (i.e. dyn_rng_chk_bits supports up to a 16-bit number)
// 2) Assert dyn_diff > 0 (i.e. dyn_diff did not underflow)

// === Ensuring dyn_rng_chk_bits is in the range [0,16] ===
// 1) We perform an 8-bit lookup to get dyn_rng_chk_pow_2 - note this is an 8-bit lookup so only constrains dyn_rng_chk_bits to be [0, 255]
// 2) This value is used in dyn_diff = dyn_rng_chk_pow_2 - u16_r7 - 1
// (a) A 16-bit lookup is performed on dyn_diff to check it hasn't underflowed - this constrains it to be between [0, 2^16 - 1]
// (b) u16_r7 is constrained by a 16-bit lookup table [0, 2^16 - 1]
// 3) If the value of dyn_rng_chk_pow_2 > 2^16, i.e. dyn_rng_chk_bits is > 16, the condition (2a) will not hold
// (a) [0, 2^16 - 1] = dyn_rng_chk_pow_2 - [0, 2^16 - 1] - 1
// (b) from above, dyn_rng_check_pow_2 must be [0, 2^16]

// Some counter-examples
// Assume a range check that the value 3 fits into 100 bits
// [A Valid Proof]
// 1) value = 3, rng_chk_bits = 100, is_lte_u112 = 1
// 2) u16_r0 = 3, while all other registers including u16_r7 (the dynamic one) are set to zero - passing #[CHECK_RECOMPOSITION]
// 3) dyn_rng_chk_bits = 100 - 96 = 4, as per the table above - this passes #[LOOKUP_RNG_CHK_POW_2]
// 4) dyn_rng_chk_pow_2 = 2^4 = 16
// 5) dyn_diff = dyn_rng_chk_pow_2 - u16_r7 - 1 = 16 - 0 - 1 = 15 - passing the range check #[LOOKUP_RNG_CHK_DIFF]

// [An Invalid Proof where dyn_rng_chk_bits > 16]
// 1) value = 3, rng_chk_bits = 100, is_lte_u16 = 1 -- a prover tries to claim the value is between 0 and u16 (which it is but isnt what the range check attesting)
// 2) u16_r7 = 3, this still passes #[CHECK_RECOMPOSITION]
// 3) dyn_rng_chk_bits = 100, as per the table - this still passes #[LOOKUP_RNG_CHK_POW_2]
// 4) dyn_rng_check_pow_2 = 2^100
// 5) dyn_diff = dyn_rng_chk_pow_2 - u16_r7 - 1 = 2^100 - 3 - 1 = 2^100 - 4 - this would fail the 16-bit range check #[LOOKUP_RNG_CHK_DIFF]

// [An Invalid Proof where dyn_rng_chk_bits < 0]
// 1) value = 3, rng_chk_bits = 100, is_lte_u128 = 1 -- a prover claims it fits within u112 and u128.
// 2) u16_r0 = 3, while all other registers including u16_r7 (the dynamic one) are set to zero - passing #[CHECK_RECOMPOSITION]
// 3) dyn_rng_chk_bits = 100 - 112 = -12, as per the table above - this fails #[LOOKUP_RNG_CHK_POW_2]


// The number of bits that need to be dynamically range checked.
pol commit dyn_rng_chk_bits;
// Valid values for dyn_rng_chk_bits are in the range [0, 16]
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved
dyn_rng_chk_bits - (rng_chk_bits - (is_lte_u32 * 16) - (is_lte_u48 * 32) - (is_lte_u64 * 48) - (is_lte_u80 * 64) - (is_lte_u96 * 80) - (is_lte_u112 * 96) - (is_lte_u128 * 112)) = 0;

// To perform the dynamic range check we also need the value of 2^dyn_rng_chk_bits
pol commit dyn_rng_chk_pow_2;

// This lookup does 2 things (1) Indirectly range checks dyn_rng_chk_bits to not have underflowed and (2) Simplified calculation of 2^dyn_rng_chk_bits
#[LOOKUP_RNG_CHK_POW_2]
sel_rng_chk {dyn_rng_chk_bits, dyn_rng_chk_pow_2} in main.sel_rng_8 {main.clk, powers.power_of_2};


// Now we need to perform the dynamic range check itself
// We check that u16_r7 < dyn_rng_chk_pow_2 ==> dyn_rng_chk_pow_2 - u16_r7 - 1 >= 0
pol commit dyn_diff;
sel_rng_chk * (dyn_diff - (dyn_rng_chk_pow_2 - u16_r7 - 1)) = 0;
// The value of dyn_diff has to be between [0, 2^16)
// To check we did not underflow we just range check it
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved
#[LOOKUP_RNG_CHK_DIFF]
sel_rng_chk { dyn_diff } in main.sel_rng_16 { main.clk };


// Lookup relations.
// We only need these relations while we do not support pol in the LHS selector
pol commit sel_lookup_0;
pol commit sel_lookup_1;
pol commit sel_lookup_2;
pol commit sel_lookup_3;
pol commit sel_lookup_4;
pol commit sel_lookup_5;
pol commit sel_lookup_6;

// The lookups are cumulative - i.e. every value greater than 16 bits involve sel_lookup_0
// Note that the lookup for the u16_r7 is always active (dynamic range check)
sel_lookup_0 - (is_lte_u32 + is_lte_u48 + is_lte_u64 + is_lte_u80 + is_lte_u96 + is_lte_u112 + is_lte_u128) = 0;
sel_lookup_1 - (is_lte_u48 + is_lte_u64 + is_lte_u80 + is_lte_u96 + is_lte_u112 + is_lte_u128) = 0;
sel_lookup_2 - (is_lte_u64 + is_lte_u80 + is_lte_u96 + is_lte_u112 + is_lte_u128) = 0;
sel_lookup_3 - (is_lte_u80 + is_lte_u96 + is_lte_u112 + is_lte_u128) = 0;
sel_lookup_4 - (is_lte_u96 + is_lte_u112 + is_lte_u128) = 0;
sel_lookup_5 - (is_lte_u112 + is_lte_u128) = 0;
sel_lookup_6 - is_lte_u128 = 0;

#[LOOKUP_RNG_CHK_0]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll replace all of these with the ones in main when integration happens

sel_lookup_0 { u16_r0 } in main.sel_rng_16 { main.clk };

#[LOOKUP_RNG_CHK_1]
sel_lookup_1 { u16_r1 } in main.sel_rng_16 { main.clk };

#[LOOKUP_RNG_CHK_2]
sel_lookup_2 { u16_r2 } in main.sel_rng_16 { main.clk };

#[LOOKUP_RNG_CHK_3]
sel_lookup_3 { u16_r3 } in main.sel_rng_16 { main.clk };

#[LOOKUP_RNG_CHK_4]
sel_lookup_4 { u16_r4 } in main.sel_rng_16 { main.clk };

#[LOOKUP_RNG_CHK_5]
sel_lookup_5 { u16_r5 } in main.sel_rng_16 { main.clk };

#[LOOKUP_RNG_CHK_6]
sel_lookup_6 { u16_r6 } in main.sel_rng_16 { main.clk };

#[LOOKUP_RNG_CHK_7]
sel_rng_chk { u16_r7 } in main.sel_rng_16 { main.clk };

Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,36 @@ AvmCircuitBuilder::ProverPolynomials AvmCircuitBuilder::compute_polynomials() co
polys.poseidon2_mem_addr_write_d[i] = rows[i].poseidon2_mem_addr_write_d;
polys.poseidon2_output_addr[i] = rows[i].poseidon2_output_addr;
polys.poseidon2_sel_poseidon_perm[i] = rows[i].poseidon2_sel_poseidon_perm;
polys.range_check_clk[i] = rows[i].range_check_clk;
polys.range_check_dyn_diff[i] = rows[i].range_check_dyn_diff;
polys.range_check_dyn_rng_chk_bits[i] = rows[i].range_check_dyn_rng_chk_bits;
polys.range_check_dyn_rng_chk_pow_2[i] = rows[i].range_check_dyn_rng_chk_pow_2;
polys.range_check_is_lte_u112[i] = rows[i].range_check_is_lte_u112;
polys.range_check_is_lte_u128[i] = rows[i].range_check_is_lte_u128;
polys.range_check_is_lte_u16[i] = rows[i].range_check_is_lte_u16;
polys.range_check_is_lte_u32[i] = rows[i].range_check_is_lte_u32;
polys.range_check_is_lte_u48[i] = rows[i].range_check_is_lte_u48;
polys.range_check_is_lte_u64[i] = rows[i].range_check_is_lte_u64;
polys.range_check_is_lte_u80[i] = rows[i].range_check_is_lte_u80;
polys.range_check_is_lte_u96[i] = rows[i].range_check_is_lte_u96;
polys.range_check_rng_chk_bits[i] = rows[i].range_check_rng_chk_bits;
polys.range_check_sel_lookup_0[i] = rows[i].range_check_sel_lookup_0;
polys.range_check_sel_lookup_1[i] = rows[i].range_check_sel_lookup_1;
polys.range_check_sel_lookup_2[i] = rows[i].range_check_sel_lookup_2;
polys.range_check_sel_lookup_3[i] = rows[i].range_check_sel_lookup_3;
polys.range_check_sel_lookup_4[i] = rows[i].range_check_sel_lookup_4;
polys.range_check_sel_lookup_5[i] = rows[i].range_check_sel_lookup_5;
polys.range_check_sel_lookup_6[i] = rows[i].range_check_sel_lookup_6;
polys.range_check_sel_rng_chk[i] = rows[i].range_check_sel_rng_chk;
polys.range_check_u16_r0[i] = rows[i].range_check_u16_r0;
polys.range_check_u16_r1[i] = rows[i].range_check_u16_r1;
polys.range_check_u16_r2[i] = rows[i].range_check_u16_r2;
polys.range_check_u16_r3[i] = rows[i].range_check_u16_r3;
polys.range_check_u16_r4[i] = rows[i].range_check_u16_r4;
polys.range_check_u16_r5[i] = rows[i].range_check_u16_r5;
polys.range_check_u16_r6[i] = rows[i].range_check_u16_r6;
polys.range_check_u16_r7[i] = rows[i].range_check_u16_r7;
polys.range_check_value[i] = rows[i].range_check_value;
polys.sha256_clk[i] = rows[i].sha256_clk;
polys.sha256_input[i] = rows[i].sha256_input;
polys.sha256_output[i] = rows[i].sha256_output;
Expand All @@ -627,6 +657,16 @@ AvmCircuitBuilder::ProverPolynomials AvmCircuitBuilder::compute_polynomials() co
polys.slice_sel_start[i] = rows[i].slice_sel_start;
polys.slice_space_id[i] = rows[i].slice_space_id;
polys.slice_val[i] = rows[i].slice_val;
polys.lookup_rng_chk_pow_2_counts[i] = rows[i].lookup_rng_chk_pow_2_counts;
polys.lookup_rng_chk_diff_counts[i] = rows[i].lookup_rng_chk_diff_counts;
polys.lookup_rng_chk_0_counts[i] = rows[i].lookup_rng_chk_0_counts;
polys.lookup_rng_chk_1_counts[i] = rows[i].lookup_rng_chk_1_counts;
polys.lookup_rng_chk_2_counts[i] = rows[i].lookup_rng_chk_2_counts;
polys.lookup_rng_chk_3_counts[i] = rows[i].lookup_rng_chk_3_counts;
polys.lookup_rng_chk_4_counts[i] = rows[i].lookup_rng_chk_4_counts;
polys.lookup_rng_chk_5_counts[i] = rows[i].lookup_rng_chk_5_counts;
polys.lookup_rng_chk_6_counts[i] = rows[i].lookup_rng_chk_6_counts;
polys.lookup_rng_chk_7_counts[i] = rows[i].lookup_rng_chk_7_counts;
polys.lookup_pow_2_0_counts[i] = rows[i].lookup_pow_2_0_counts;
polys.lookup_pow_2_1_counts[i] = rows[i].lookup_pow_2_1_counts;
polys.lookup_u8_0_counts[i] = rows[i].lookup_u8_0_counts;
Expand Down
Loading
Loading