Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
range_constraint.test.cpp
Go to the documentation of this file.
4#include "ultra_honk.test.hpp"
5
6using namespace bb;
7
8#ifdef STARKNET_GARAGA_FLAVORS
9using FlavorTypes = testing::Types<UltraFlavor,
13 UltraStarknetFlavor,
14 UltraStarknetZKFlavor>;
15#else
16using FlavorTypes = testing::Types<UltraFlavor, UltraZKFlavor, UltraKeccakFlavor, UltraKeccakZKFlavor>;
17#endif
18template <typename T> using RangeTests = UltraHonkTests<T>;
20
21/***************************************************************************************************
22 * enforce_small_deltas tests
23 * These test the low-level delta constraint: consecutive values must differ by at most 3.
24 ***************************************************************************************************/
25
26// Basic test: sorted sequence [1,2,3,4] has deltas of 1, which is valid
27TYPED_TEST(RangeTests, EnforceSmallDeltasBasic)
28{
30 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4 });
31 builder.enforce_small_deltas(idx);
32
33 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
34 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
35}
36
37// Sequence with max delta of 3 and duplicates: all deltas in {0,1,2,3}
38TYPED_TEST(RangeTests, EnforceSmallDeltasWithDuplicatesAndMaxDelta)
39{
41 // Deltas: 2, 1, 3, 0, 1, 3, 3, 1, 0, 3, 1, 2, 0, 3, 1, 1, 1, 3, 2
42 auto idx = TestFixture::add_variables(builder,
43 { 1, 3, 4, 7, 7, 8, 11, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 });
44 builder.enforce_small_deltas(idx);
45
46 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
47 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
48}
49
50// FAILURE: delta of 5 (from 3 to 8) exceeds maximum of 3
51TYPED_TEST(RangeTests, EnforceSmallDeltasFailsDeltaTooLarge)
52{
54 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 8 }); // delta from 3 to 8 is 5
55 builder.enforce_small_deltas(idx);
56
57 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
58 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
59}
60
61// FAILURE: sequence not sorted (16 comes before 14)
62TYPED_TEST(RangeTests, EnforceSmallDeltasFailsNotSorted)
63{
65 // 16 appears before 14, causing a negative delta (wraps to large positive in field)
66 auto idx = TestFixture::add_variables(builder,
67 { 1, 3, 4, 7, 7, 8, 16, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 });
68 builder.enforce_small_deltas(idx);
69
70 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
71 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
72}
73
74/***************************************************************************************************
75 * create_sort_constraint_with_edges tests
76 * These test delta constraints with explicit start and end boundary checks.
77 ***************************************************************************************************/
78
79// Basic test: sequence [1..8] with start=1, end=8
80TYPED_TEST(RangeTests, SortConstraintWithEdgesBasic)
81{
83 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
84 builder.create_sort_constraint_with_edges(idx, /*start=*/1, /*end=*/8);
85
86 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
87 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
88}
89
90// Complex sequence with duplicates and varying deltas, all within bounds
91TYPED_TEST(RangeTests, SortConstraintWithEdgesComplex)
92{
94 auto idx = TestFixture::add_variables(builder, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25,
95 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 });
96 builder.create_sort_constraint_with_edges(idx, /*start=*/1, /*end=*/45);
97
98 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
99 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
100}
101
102// FAILURE: end constraint not satisfied (actual end is 8, but we claim end=7)
103TYPED_TEST(RangeTests, SortConstraintWithEdgesFailsWrongEnd)
104{
106 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
107 builder.create_sort_constraint_with_edges(idx, /*start=*/1, /*end=*/7); // actual end is 8
108
109 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
110 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
111}
112
113// FAILURE: start constraint not satisfied (actual start is 1, but we claim start=2)
114TYPED_TEST(RangeTests, SortConstraintWithEdgesFailsWrongStart)
115{
117 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
118 builder.create_sort_constraint_with_edges(idx, /*start=*/2, /*end=*/8); // actual start is 1
119
120 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
121 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
122}
123
124// FAILURE: delta too large (15 appears where small delta expected)
125TYPED_TEST(RangeTests, SortConstraintWithEdgesFailsDeltaTooLarge)
126{
128 auto idx = TestFixture::add_variables(builder, { 1, 15, 3, 4, 5, 6, 7, 8 }); // 1 to 15 is delta of 14
129 builder.create_sort_constraint_with_edges(idx, /*start=*/2, /*end=*/8);
130
131 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
132 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
133}
134
135/***************************************************************************************************
136 * create_small_range_constraint tests
137 * Range is [0, target_range] (inclusive).
138 ***************************************************************************************************/
139
140// Basic test: values [1..8] all in range [0, 8]
141TYPED_TEST(RangeTests, SmallRangeConstraintBasic)
142{
144 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
145 for (auto i : idx) {
146 builder.create_small_range_constraint(i, /*target_range=*/8);
147 }
148 builder.create_unconstrained_gates(idx);
149
150 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
151 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
152}
153
154// Single value at exact boundary: 3 in range [0, 3]
155TYPED_TEST(RangeTests, SmallRangeConstraintAtBoundary)
156{
158 auto idx = TestFixture::add_variables(builder, { 3 });
159 builder.create_small_range_constraint(idx[0], /*target_range=*/3);
160 builder.create_unconstrained_gates(idx);
161
162 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
163 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
164}
165
166// Multiple values with various ranges, all valid
167TYPED_TEST(RangeTests, SmallRangeConstraintMultipleValues)
168{
170 auto idx =
171 TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 10, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 19, 51 });
172 for (auto i : idx) {
173 builder.create_small_range_constraint(i, /*target_range=*/128);
174 }
175 builder.create_unconstrained_gates(idx);
176
177 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
178 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
179}
180
181// FAILURE: value 25 exceeds range [0, 8]
182TYPED_TEST(RangeTests, SmallRangeConstraintFailsValueTooLarge)
183{
185 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 8, 25 }); // 25 > 8
186 for (auto i : idx) {
187 builder.create_small_range_constraint(i, /*target_range=*/8);
188 }
189 builder.enforce_small_deltas(idx);
190
191 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
192 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
193}
194
195// FAILURE: value 80 exceeds range [0, 79]
196TYPED_TEST(RangeTests, SmallRangeConstraintFailsValueJustOverBoundary)
197{
199 auto idx = TestFixture::add_variables(
200 builder, { 1, 2, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 }); // 80 > 79
201 for (auto i : idx) {
202 builder.create_small_range_constraint(i, /*target_range=*/79);
203 }
204 builder.create_unconstrained_gates(idx);
205
206 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
207 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
208}
209
210// FAILURE: orphan variable (not in any gate) causes GPA failure. This is a quirk of `create_small_range_constraint`.
211TYPED_TEST(RangeTests, SmallRangeConstraintFailsOrphanVariable)
212{
214 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
215 for (auto i : idx) {
216 builder.create_small_range_constraint(i, /*target_range=*/8);
217 }
218 // NOT calling create_unconstrained_gates - variables are orphans
219 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
220 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
221}
222
223/***************************************************************************************************
224 * Range constraints combined with arithmetic gates
225 ***************************************************************************************************/
226
227// Range constraints work alongside arithmetic gates
228TYPED_TEST(RangeTests, RangeConstraintWithArithmeticGates)
229{
231 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
232 for (auto i : idx) {
233 builder.create_small_range_constraint(i, /*target_range=*/8);
234 }
235
236 // Add arithmetic constraints: 1+2=3, 3+4=7, 5+6=11, 7+8=15
237 builder.create_add_gate({ idx[0], idx[1], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -3 });
238 builder.create_add_gate({ idx[2], idx[3], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -7 });
239 builder.create_add_gate({ idx[4], idx[5], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -11 });
240 builder.create_add_gate({ idx[6], idx[7], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -15 });
241
242 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
243 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
244}
245
246// Non-power-of-two range works correctly
247TYPED_TEST(RangeTests, RangeConstraintNonPowerOfTwo)
248{
250 auto idx = TestFixture::add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
251 for (auto i : idx) {
252 builder.create_small_range_constraint(i, /*target_range=*/12); // not a power of 2
253 }
254
255 builder.create_add_gate({ idx[0], idx[1], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -3 });
256 builder.create_add_gate({ idx[2], idx[3], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -7 });
257 builder.create_add_gate({ idx[4], idx[5], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -11 });
258 builder.create_add_gate({ idx[6], idx[7], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -15 });
259
260 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
261 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
262}
263
267TYPED_TEST(RangeTests, MultipleRangeConstraintsOnSmallWitness)
268{
270
271 uint32_t witness_idx = builder.add_variable(fr(5));
272
273 // Apply multiple range constraints with different target ranges
274 builder.create_small_range_constraint(witness_idx, /*target_range=*/8);
275 builder.create_small_range_constraint(witness_idx, /*target_range=*/6);
276 builder.create_small_range_constraint(witness_idx, /*target_range=*/10);
277 builder.create_small_range_constraint(witness_idx, /*target_range=*/100);
278
279 // Add unconstrained gate to prevent orphan variable failure
280 builder.create_unconstrained_gates({ witness_idx });
281
282 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
283 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
284}
285/***************************************************************************************************
286 * create_limbed_range_constraint tests
287 * For large ranges (> 14 bits), decompose into smaller limbs.
288 ***************************************************************************************************/
289
290// Boundary case: exactly 14 bits (single limb, at DEFAULT_PLOOKUP_RANGE_BITNUM)
291TYPED_TEST(RangeTests, LimbedRangeConstraint14Bits)
292{
294
295 // Create a value that fits in exactly 14 bits (max value = 16383 = 2^14 - 1)
296 auto value = fr(16383);
297
298 auto idx = builder.add_variable(value);
299 // NOTE: we do not need to create an auxiliary arithmetic gate; the `create_limbed_range_constraint` functionality
300 // prevents `idx` from being orphaned.
301 builder.create_limbed_range_constraint(idx, /*num_bits=*/14);
302
303 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
304 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
305}
306
307// Large range constraint using limb decomposition (133 bits)
308TYPED_TEST(RangeTests, LimbedRangeConstraint133Bits)
309{
311
312 // Create a random value that fits in 133 bits
313 auto random_field = fr::random_element();
314 auto truncated = uint256_t(random_field).slice(0, 133);
315 auto value = fr(truncated);
316
317 auto idx = builder.add_variable(value);
318 builder.create_add_gate({ idx, builder.zero_idx(), builder.zero_idx(), 1, 0, 0, -value });
319 builder.create_limbed_range_constraint(idx, /*num_bits=*/133);
320
321 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
322 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
323}
324
325// Edge-case range constraint using limb decomposition. Here, `253 == MAX_NUM_BITS_RANGE_CONSTRAINT`.
326TYPED_TEST(RangeTests, LimbedRangeConstraint253Bits)
327{
329
330 // Create a random value that fits in 253 bits
331 auto random_field = fr::random_element();
332 auto truncated = uint256_t(random_field).slice(0, 253);
333 auto value = fr(truncated);
334
335 auto idx = builder.add_variable(value);
336 builder.create_limbed_range_constraint(idx, /*num_bits=*/253);
337
338 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
339 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
340}
341
342/***************************************************************************************************
343 * create_dyadic_range_constraint tests
344 * Main entry point that handles orphan variables by adding dummy arithmetic gates.
345 ***************************************************************************************************/
346
352TYPED_TEST(RangeTests, DyadicRangeConstraintOnOrphanVariable)
353{
355
356 // Create a variable that will ONLY be range-constrained, not used in any other gate
357 auto orphan_idx = builder.add_variable(fr(100));
358 builder.create_dyadic_range_constraint(orphan_idx, /*num_bits=*/8, "orphan range constraint");
359
360 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
361 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
362}
363
364/***************************************************************************************************
365 * Copy constraint interaction tests
366 * Tests that (multiple) range constraints work correctly copy constraints.
367 ***************************************************************************************************/
368
374TYPED_TEST(RangeTests, RangeConstraintsOnDuplicateVariables)
375{
377
378 uint32_t a = builder.add_variable(fr(100));
379 uint32_t b = builder.add_variable(fr(100));
380 uint32_t c = builder.add_variable(fr(100));
381 uint32_t d = builder.add_variable(fr(100));
382
383 // Link all variables together via copy constraints
384 builder.assert_equal(a, b);
385 builder.assert_equal(a, c);
386 builder.assert_equal(a, d);
387
388 // Apply different range constraints to each (tightest is 999)
389 builder.create_small_range_constraint(a, /*target_range=*/1000);
390 builder.create_small_range_constraint(b, /*target_range=*/1001);
391 builder.create_small_range_constraint(c, /*target_range=*/999);
392 builder.create_small_range_constraint(d, /*target_range=*/1000);
393
394 builder.create_unconstrained_gates({ a, b, c, d });
395
396 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
397 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
398}
Child class of UltraFlavor that runs with ZK Sumcheck.
constexpr uint256_t slice(uint64_t start, uint64_t end) const
AluTraceBuilder builder
Definition alu.test.cpp:124
FF a
FF b
testing::Types< UltraFlavor, UltraKeccakFlavor, MegaFlavor > FlavorTypes
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:155
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
static constexpr field one()
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()