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