Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
cycle_scalar.test.cpp
Go to the documentation of this file.
9#include <gtest/gtest.h>
10
11using namespace bb;
12
13namespace {
15}
16
17template <class Builder> class CycleScalarTest : public ::testing::Test {
18 public:
22 using Curve = typename Builder::EmbeddedCurve;
24 using NativeField = typename Builder::FF;
25};
26
27using CircuitTypes = ::testing::Types<bb::UltraCircuitBuilder, bb::MegaCircuitBuilder>;
29
31
33
37TYPED_TEST(CycleScalarTest, TestFromWitness)
38{
39 using cycle_scalar = typename TestFixture::cycle_scalar;
40 using ScalarField = typename TestFixture::ScalarField;
41
42 TypeParam builder;
43 auto scalar_val = ScalarField::random_element(&engine);
44 auto scalar = cycle_scalar::from_witness(&builder, scalar_val);
45
46 EXPECT_EQ(scalar.get_value(), scalar_val);
47 EXPECT_FALSE(scalar.is_constant());
48 EXPECT_EQ(scalar.num_bits(), cycle_scalar::NUM_BITS);
49
50 // Check that lo and hi reconstruct to the original value
51 uint256_t lo_val = uint256_t(scalar.lo.get_value());
52 uint256_t hi_val = uint256_t(scalar.hi.get_value());
53 uint256_t reconstructed = lo_val + (hi_val << cycle_scalar::LO_BITS);
54
55 EXPECT_EQ(ScalarField(reconstructed), scalar_val);
56
57 check_circuit_and_gate_count(builder, 0);
58}
59
63TYPED_TEST(CycleScalarTest, TestFromU256Witness)
64{
65 using cycle_scalar = typename TestFixture::cycle_scalar;
66 using ScalarField = typename TestFixture::ScalarField;
67
68 TypeParam builder;
69 uint256_t value(123456789);
70 auto scalar = cycle_scalar::from_u256_witness(&builder, value);
71
72 EXPECT_EQ(scalar.get_value(), ScalarField(value));
73 EXPECT_FALSE(scalar.is_constant());
74 EXPECT_EQ(scalar.num_bits(), 256);
75
76 // Check that lo and hi reconstruct to the original value
77 uint256_t lo_val = uint256_t(scalar.lo.get_value());
78 uint256_t hi_val = uint256_t(scalar.hi.get_value());
79 uint256_t reconstructed = lo_val + (hi_val << cycle_scalar::LO_BITS);
80
81 EXPECT_EQ(reconstructed, value);
82
83 check_circuit_and_gate_count(builder, 0);
84}
85
89TYPED_TEST(CycleScalarTest, TestCreateFromBn254Scalar)
90{
91 using cycle_scalar = typename TestFixture::cycle_scalar;
92 using ScalarField = typename TestFixture::ScalarField;
93 using field_t = typename TestFixture::field_t;
94 using NativeField = typename TestFixture::NativeField;
95
96 TypeParam builder;
97 auto native_val = NativeField::random_element(&engine);
98 auto field_val = field_t::from_witness(&builder, native_val);
99
100 auto scalar = cycle_scalar::create_from_bn254_scalar(field_val);
101
102 EXPECT_EQ(scalar.get_value(), ScalarField(uint256_t(native_val)));
103 EXPECT_FALSE(scalar.is_constant());
104 EXPECT_TRUE(scalar.use_bn254_scalar_field_for_primality_test());
105
106 // Check that lo and hi reconstruct to the original value
107 uint256_t lo_val = uint256_t(scalar.lo.get_value());
108 uint256_t hi_val = uint256_t(scalar.hi.get_value());
109 uint256_t reconstructed = lo_val + (hi_val << cycle_scalar::LO_BITS);
110
111 EXPECT_EQ(NativeField(reconstructed), field_val.get_value());
112
113 check_circuit_and_gate_count(builder, 2762);
114}
115
119TYPED_TEST(CycleScalarTest, TestBigScalarFieldConstructor)
120{
121 using cycle_scalar = typename TestFixture::cycle_scalar;
122 using ScalarField = typename TestFixture::ScalarField;
123 using BigScalarField = typename cycle_scalar::BigScalarField;
124
125 // Test with a witness BigScalarField
126 {
127 TypeParam builder;
128
129 auto value = ScalarField::random_element(&engine);
130 auto big_scalar = BigScalarField::from_witness(&builder, value);
131 cycle_scalar scalar(big_scalar);
132
133 EXPECT_EQ(scalar.get_value(), value);
134 EXPECT_FALSE(scalar.is_constant());
135
136 // Verify lo/hi decomposition matches
137 uint256_t lo_val = uint256_t(scalar.lo.get_value());
138 uint256_t hi_val = uint256_t(scalar.hi.get_value());
139 uint256_t reconstructed = lo_val + (hi_val << cycle_scalar::LO_BITS);
140 EXPECT_EQ(ScalarField(reconstructed), value);
141
142 check_circuit_and_gate_count(builder, 3498);
143 }
144
145 // Test with constant BigScalarField
146 {
147 TypeParam builder;
148
149 uint256_t value(0x123456789ABCDEF);
150 BigScalarField big_scalar(&builder, value);
151 cycle_scalar scalar(big_scalar);
152
153 EXPECT_EQ(scalar.get_value(), ScalarField(value));
154 EXPECT_TRUE(scalar.is_constant());
155
156 // Verify lo/hi decomposition matches
157 uint256_t lo_val = uint256_t(scalar.lo.get_value());
158 uint256_t hi_val = uint256_t(scalar.hi.get_value());
159 uint256_t reconstructed = lo_val + (hi_val << cycle_scalar::LO_BITS);
160 EXPECT_EQ(ScalarField(reconstructed), value);
161
162 check_circuit_and_gate_count(builder, 0);
163 }
164}
165
169TYPED_TEST(CycleScalarTest, TestScalarFieldValidation)
170{
171 using cycle_scalar = typename TestFixture::cycle_scalar;
172 using ScalarField = typename TestFixture::ScalarField;
173
174 TypeParam builder;
175
176 // Test with a valid scalar
177 auto valid_scalar = ScalarField::random_element(&engine);
178 auto scalar = cycle_scalar::from_witness(&builder, valid_scalar);
179 scalar.validate_scalar_is_in_field();
180 EXPECT_FALSE(builder.failed());
181
182 check_circuit_and_gate_count(builder, 2761);
183}
184
193TYPED_TEST(CycleScalarTest, TestScalarFieldValidationFailureBetweenModuli)
194{
195 using cycle_scalar = typename TestFixture::cycle_scalar;
196 using field_t = typename TestFixture::field_t;
197 using NativeField = typename TestFixture::NativeField;
198
199 // Create a value that is between BN254::fr modulus and BN254::fq modulus
200 // BN254::fr modulus = 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001
201 // BN254::fq modulus = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD47
202 uint256_t bn254_fr_modulus = bb::fr::modulus;
203 uint256_t bn254_fq_modulus = bb::fq::modulus;
204 uint256_t moduli_diff = bn254_fq_modulus - bn254_fr_modulus;
205 uint256_t value_between_moduli = bn254_fr_modulus + moduli_diff / 2;
206
207 // Split the value into lo and hi components at the LO_BITS boundary
208 uint256_t lo_val = value_between_moduli.slice(0, cycle_scalar::LO_BITS);
209 uint256_t hi_val = value_between_moduli.slice(cycle_scalar::LO_BITS, 256);
210
211 // Test 1: Validate with Grumpkin scalar field (larger modulus) - should pass
212 {
213 TypeParam builder;
214
215 // Create lo and hi field elements
216 auto lo = field_t::from_witness(&builder, NativeField(lo_val));
217 auto hi = field_t::from_witness(&builder, NativeField(hi_val));
218
219 // Construct cycle_scalar directly WITHOUT BN254 scalar field validation flag
220 auto scalar = cycle_scalar(lo, hi);
221
222 // This should NOT use BN254 scalar field for primality test
223 EXPECT_FALSE(scalar.use_bn254_scalar_field_for_primality_test());
224
225 // Validate - this should pass because value < BN254::fq modulus (Grumpkin scalar field)
226 scalar.validate_scalar_is_in_field();
227
228 // The builder should NOT have failed
229 EXPECT_FALSE(builder.failed());
230 check_circuit_and_gate_count(builder, 2761);
231 }
232
233 // Test 2: Validate with BN254 scalar field (smaller modulus)
234 // Note: Since we can't set the use_bn254_scalar_field_for_primality_test flag directly with the public constructor,
235 // we'll test the underlying validate_split_in_field directly to achieve what would be achieved internally
236 {
237 TypeParam builder;
238
239 // Create lo and hi field elements
240 auto lo = field_t::from_witness(&builder, NativeField(lo_val));
241 auto hi = field_t::from_witness(&builder, NativeField(hi_val));
242
243 // Construct cycle_scalar with the public constructor
244 auto scalar = cycle_scalar(lo, hi);
245
246 // Verify the reconstructed value matches what we expect
247 uint256_t reconstructed =
248 uint256_t(scalar.lo.get_value()) + (uint256_t(scalar.hi.get_value()) << cycle_scalar::LO_BITS);
249 EXPECT_EQ(reconstructed, value_between_moduli);
250
251 // Now directly call validate_split_in_field with BN254::fr modulus
252 // This should create unsatisfied constraints because value > BN254::fr modulus
253 bb::stdlib::validate_split_in_field(lo, hi, cycle_scalar::LO_BITS, bn254_fr_modulus);
254
255 // The builder should have failed
256 EXPECT_TRUE(builder.failed());
257 }
258}
259
264TYPED_TEST(CycleScalarTest, TestBigScalarFieldConstructorEdgeCases)
265{
266 using cycle_scalar = typename TestFixture::cycle_scalar;
267 using ScalarField = typename TestFixture::ScalarField;
268 using BigScalarField = typename cycle_scalar::BigScalarField;
269
270 // Test case 1: BigScalarField with zero value
271 {
272 TypeParam builder;
273 BigScalarField zero_scalar = BigScalarField::from_witness(&builder, uint256_t(0));
274 cycle_scalar scalar(zero_scalar);
275
276 EXPECT_EQ(scalar.get_value(), ScalarField(0));
277 EXPECT_EQ(scalar.lo.get_value(), 0);
278 EXPECT_EQ(scalar.hi.get_value(), 0);
279
280 check_circuit_and_gate_count(builder, 3498);
281 }
282
283 // Test case 2: BigScalarField with only first limb set (value < 2^68)
284 {
285 TypeParam builder;
286 uint256_t small_value = uint256_t(0x12345678);
287 BigScalarField small_scalar = BigScalarField::from_witness(&builder, small_value);
288 cycle_scalar scalar(small_scalar);
289
290 EXPECT_EQ(scalar.get_value(), ScalarField(small_value));
291 EXPECT_EQ(scalar.lo.get_value(), small_value);
292 EXPECT_EQ(scalar.hi.get_value(), 0);
293
294 check_circuit_and_gate_count(builder, 3498);
295 }
296
297 // Test case 3: BigScalarField with value exactly at first limb boundary (2^68)
298 {
299 TypeParam builder;
300 uint256_t limb_boundary = uint256_t(1) << 68;
301 BigScalarField boundary_scalar = BigScalarField::from_witness(&builder, limb_boundary);
302 cycle_scalar scalar(boundary_scalar);
303
304 EXPECT_EQ(scalar.get_value(), ScalarField(limb_boundary));
305
306 check_circuit_and_gate_count(builder, 3498);
307 }
308
309 // Test case 4: BigScalarField with value that puts zero in limb1
310 // Value in range [2^68, 2^68 + 2^67] will have limb0 full and limb1 = 0
311 {
312 TypeParam builder;
313 uint256_t limb0_full = (uint256_t(1) << 68) - 1; // Max value for limb0
314 BigScalarField limb0_full_scalar = BigScalarField::from_witness(&builder, limb0_full);
315 cycle_scalar scalar(limb0_full_scalar);
316
317 EXPECT_EQ(scalar.get_value(), ScalarField(limb0_full));
318
319 check_circuit_and_gate_count(builder, 3498);
320 }
321
322 // Test case 5: BigScalarField with value exactly 2^136 (limb0=0, limb1=0, limb2=1)
323 {
324 TypeParam builder;
325 uint256_t val_136 = uint256_t(1) << 136; // limb0=0, limb1=0, limb2=1
326 BigScalarField val_136_scalar = BigScalarField::from_witness(&builder, val_136);
327 cycle_scalar scalar(val_136_scalar);
328
329 EXPECT_EQ(scalar.get_value(), ScalarField(val_136));
330
331 check_circuit_and_gate_count(builder, 3498);
332 }
333
334 // Test case 6: BigScalarField with value that genuinely has limb1 = 0
335 // Value = 2^136 + small_value (so limb0=small, limb1=0, limb2=1)
336 {
337 TypeParam builder;
338 uint256_t special_value = (uint256_t(1) << 136) + 0x42;
339 BigScalarField special_scalar = BigScalarField::from_witness(&builder, special_value);
340 cycle_scalar scalar(special_scalar);
341
342 EXPECT_EQ(scalar.get_value(), ScalarField(special_value));
343
344 check_circuit_and_gate_count(builder, 3498);
345 }
346
347 // Test case 7: BigScalarField where limb0 exceeds NUM_LIMB_BITS after addition
348 // This triggers the overflow handling path in the constructor
349 {
350 TypeParam builder;
351 // Create two BigScalarFields that when added will cause limb0.maximum_value > DEFAULT_MAXIMUM_LIMB
352 uint256_t val1 = (uint256_t(1) << 67) - 1; // Almost full first limb
353 uint256_t val2 = (uint256_t(1) << 67) - 1; // Another almost full first limb
354
355 BigScalarField scalar1 = BigScalarField::from_witness(&builder, val1);
356 BigScalarField scalar2 = BigScalarField::from_witness(&builder, val2);
357
358 // Add them together - this will make limb0.maximum_value = 2 * ((2^67) - 1) > 2^68 - 1
359 BigScalarField sum = scalar1 + scalar2;
360
361 // Verify that limb0's maximum_value exceeds the default maximum
362 EXPECT_GT(sum.binary_basis_limbs[0].maximum_value, BigScalarField::DEFAULT_MAXIMUM_LIMB);
363
364 // Now construct a cycle_scalar from this sum - this should trigger the overflow handling
365 cycle_scalar scalar(sum);
366
367 // Verify the result is correct
368 uint256_t expected = val1 + val2;
369 EXPECT_EQ(scalar.get_value(), ScalarField(expected));
370
371 // Extra gates due to a self reduction of the bigfield input
372 check_circuit_and_gate_count(builder, 3555);
373 }
374}
typename Builder::FF NativeField
typename Builder::EmbeddedCurve Curve
typename Curve::ScalarField ScalarField
bb::fr ScalarField
Definition bn254.hpp:18
constexpr uint256_t slice(uint64_t start, uint64_t end) const
Represents a member of the Grumpkin curve scalar field (i.e. BN254 base field).
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:432
AluTraceBuilder builder
Definition alu.test.cpp:123
testing::Types< bb::UltraCircuitBuilder, bb::MegaCircuitBuilder > CircuitTypes
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:190
void check_circuit_and_gate_count(Builder &builder, uint32_t expected_gates_without_base)
Utility function for gate count checking and circuit verification.
void validate_split_in_field(const field_t< Builder > &lo, const field_t< Builder > &hi, const size_t lo_bits, const uint256_t &field_modulus)
Validates that lo + hi * 2^lo_bits < field_modulus.
Entry point for Barretenberg command-line interface.
TYPED_TEST_SUITE(ShpleminiTest, TestSettings)
Inner sum(Cont< Inner, Args... > const &in)
Definition container.hpp:70
TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit p...
#define STANDARD_TESTING_TAGS
static constexpr uint256_t modulus