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
49 // Check that lo and hi reconstruct to the original value
50 uint256_t lo_val = uint256_t(scalar.lo().get_value());
51 uint256_t hi_val = uint256_t(scalar.hi().get_value());
52 uint256_t reconstructed = lo_val + (hi_val << cycle_scalar::LO_BITS);
53
54 EXPECT_EQ(ScalarField(reconstructed), scalar_val);
55
56 check_circuit_and_gate_count(builder, 2761);
57}
58
62TYPED_TEST(CycleScalarTest, TestBigScalarFieldConstructor)
63{
64 using cycle_scalar = typename TestFixture::cycle_scalar;
65 using ScalarField = typename TestFixture::ScalarField;
66 using BigScalarField = typename cycle_scalar::BigScalarField;
67
68 // Test with a witness BigScalarField
69 {
70 TypeParam builder;
71
72 auto value = ScalarField::random_element(&engine);
73 auto big_scalar = BigScalarField::from_witness(&builder, value);
74 cycle_scalar scalar(big_scalar);
75
76 EXPECT_EQ(scalar.get_value(), value);
77 EXPECT_FALSE(scalar.is_constant());
78
79 // Verify lo/hi decomposition matches
80 uint256_t lo_val = uint256_t(scalar.lo().get_value());
81 uint256_t hi_val = uint256_t(scalar.hi().get_value());
82 uint256_t reconstructed = lo_val + (hi_val << cycle_scalar::LO_BITS);
83 EXPECT_EQ(ScalarField(reconstructed), value);
84
85 check_circuit_and_gate_count(builder, 3523);
86 }
87
88 // Test with constant BigScalarField
89 {
90 TypeParam builder;
91
92 uint256_t value(0x123456789ABCDEF);
93 BigScalarField big_scalar(&builder, value);
94 cycle_scalar scalar(big_scalar);
95
96 EXPECT_EQ(scalar.get_value(), ScalarField(value));
97 EXPECT_TRUE(scalar.is_constant());
98
99 // Verify lo/hi decomposition matches
100 uint256_t lo_val = uint256_t(scalar.lo().get_value());
101 uint256_t hi_val = uint256_t(scalar.hi().get_value());
102 uint256_t reconstructed = lo_val + (hi_val << cycle_scalar::LO_BITS);
103 EXPECT_EQ(ScalarField(reconstructed), value);
104
105 check_circuit_and_gate_count(builder, 0);
106 }
107}
108
117TYPED_TEST(CycleScalarTest, TestScalarFieldValidationFailureBetweenModuli)
118{
119 using cycle_scalar = typename TestFixture::cycle_scalar;
120 using field_t = typename TestFixture::field_t;
121 using NativeField = typename TestFixture::NativeField;
122
123 // Create a value that is between BN254::fr modulus and BN254::fq modulus
124 // BN254::fr modulus = 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001
125 // BN254::fq modulus = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD47
126 uint256_t bn254_fr_modulus = bb::fr::modulus;
127 uint256_t bn254_fq_modulus = bb::fq::modulus;
128 uint256_t moduli_diff = bn254_fq_modulus - bn254_fr_modulus;
129 uint256_t value_between_moduli = bn254_fr_modulus + moduli_diff / 2;
130
131 // Split the value into lo and hi components at the LO_BITS boundary
132 uint256_t lo_val = value_between_moduli.slice(0, cycle_scalar::LO_BITS);
133 uint256_t hi_val = value_between_moduli.slice(cycle_scalar::LO_BITS, 256);
134
135 // Test 1: Validate with Grumpkin scalar field (larger modulus) - should pass
136 {
137 TypeParam builder;
138
139 // Create lo and hi field elements
140 auto lo = field_t::from_witness(&builder, NativeField(lo_val));
141 auto hi = field_t::from_witness(&builder, NativeField(hi_val));
142
143 // Construct cycle_scalar directly which will validate against Grumpkin scalar field
144 // This should pass because value < BN254::fq modulus (Grumpkin scalar field)
145 auto scalar = cycle_scalar(lo, hi);
146
147 // The builder should NOT have failed
148 EXPECT_FALSE(builder.failed());
149 check_circuit_and_gate_count(builder, 2761);
150 }
151
152 // Test 2: Validate with BN254 scalar field (smaller modulus)
153 // We'll test the underlying validate_split_in_field directly with BN254::fr modulus
154 {
155 TypeParam builder;
156
157 // Create lo and hi field elements
158 auto lo = field_t::from_witness(&builder, NativeField(lo_val));
159 auto hi = field_t::from_witness(&builder, NativeField(hi_val));
160
161 // Construct cycle_scalar directly (validation against Grumpkin scalar field will pass)
162 auto scalar = cycle_scalar(lo, hi);
163
164 // Verify the reconstructed value matches what we expect
165 uint256_t reconstructed =
166 uint256_t(scalar.lo().get_value()) + (uint256_t(scalar.hi().get_value()) << cycle_scalar::LO_BITS);
167 EXPECT_EQ(reconstructed, value_between_moduli);
168
169 // Now directly call validate_split_in_field_unsafe with BN254::fr modulus
170 // This should create unsatisfied constraints because value > BN254::fr modulus
171 bb::stdlib::validate_split_in_field_unsafe(lo, hi, cycle_scalar::LO_BITS, bn254_fr_modulus);
172
173 // The builder should have failed
174 EXPECT_TRUE(builder.failed());
175 }
176}
177
182TYPED_TEST(CycleScalarTest, TestBigScalarFieldConstructorEdgeCases)
183{
184 using cycle_scalar = typename TestFixture::cycle_scalar;
185 using ScalarField = typename TestFixture::ScalarField;
186 using BigScalarField = typename cycle_scalar::BigScalarField;
187
188 // Test case 1: BigScalarField with zero value
189 {
190 TypeParam builder;
191 BigScalarField zero_scalar = BigScalarField::from_witness(&builder, typename BigScalarField::native(0));
192 cycle_scalar scalar(zero_scalar);
193
194 EXPECT_EQ(scalar.get_value(), ScalarField(0));
195 EXPECT_EQ(scalar.lo().get_value(), 0);
196 EXPECT_EQ(scalar.hi().get_value(), 0);
197
198 check_circuit_and_gate_count(builder, 3523);
199 }
200
201 // Test case 2: BigScalarField with only first limb set (value < 2^68)
202 {
203 TypeParam builder;
204 uint256_t small_value = uint256_t(0x12345678);
205 BigScalarField small_scalar =
206 BigScalarField::from_witness(&builder, typename BigScalarField::native(small_value));
207 cycle_scalar scalar(small_scalar);
208
209 EXPECT_EQ(scalar.get_value(), ScalarField(small_value));
210 EXPECT_EQ(scalar.lo().get_value(), small_value);
211 EXPECT_EQ(scalar.hi().get_value(), 0);
212
213 check_circuit_and_gate_count(builder, 3523);
214 }
215
216 // Test case 3: BigScalarField with value exactly at first limb boundary (2^68)
217 {
218 TypeParam builder;
219 uint256_t limb_boundary = uint256_t(1) << 68;
220 BigScalarField boundary_scalar =
221 BigScalarField::from_witness(&builder, typename BigScalarField::native(limb_boundary));
222 cycle_scalar scalar(boundary_scalar);
223
224 EXPECT_EQ(scalar.get_value(), ScalarField(limb_boundary));
225
226 check_circuit_and_gate_count(builder, 3523);
227 }
228
229 // Test case 4: BigScalarField with value that puts zero in limb1
230 // Value in range [2^68, 2^68 + 2^67] will have limb0 full and limb1 = 0
231 {
232 TypeParam builder;
233 uint256_t limb0_full = (uint256_t(1) << 68) - 1; // Max value for limb0
234 BigScalarField limb0_full_scalar =
235 BigScalarField::from_witness(&builder, typename BigScalarField::native(limb0_full));
236 cycle_scalar scalar(limb0_full_scalar);
237
238 EXPECT_EQ(scalar.get_value(), ScalarField(limb0_full));
239
240 check_circuit_and_gate_count(builder, 3523);
241 }
242
243 // Test case 5: BigScalarField with value exactly 2^136 (limb0=0, limb1=0, limb2=1)
244 {
245 TypeParam builder;
246 uint256_t val_136 = uint256_t(1) << 136; // limb0=0, limb1=0, limb2=1
247 BigScalarField val_136_scalar =
248 BigScalarField::from_witness(&builder, typename BigScalarField::native(val_136));
249 cycle_scalar scalar(val_136_scalar);
250
251 EXPECT_EQ(scalar.get_value(), ScalarField(val_136));
252
253 check_circuit_and_gate_count(builder, 3523);
254 }
255
256 // Test case 6: BigScalarField with value that genuinely has limb1 = 0
257 // Value = 2^136 + small_value (so limb0=small, limb1=0, limb2=1)
258 {
259 TypeParam builder;
260 uint256_t special_value = (uint256_t(1) << 136) + 0x42;
261 BigScalarField special_scalar =
262 BigScalarField::from_witness(&builder, typename BigScalarField::native(special_value));
263 cycle_scalar scalar(special_scalar);
264
265 EXPECT_EQ(scalar.get_value(), ScalarField(special_value));
266
267 check_circuit_and_gate_count(builder, 3523);
268 }
269
270 // Test case 7: BigScalarField where limb0 exceeds NUM_LIMB_BITS after addition
271 // This triggers the overflow handling path in the constructor
272 {
273 TypeParam builder;
274 // Create two BigScalarFields that when added will cause limb0.maximum_value > DEFAULT_MAXIMUM_LIMB
275 uint256_t val1 = (uint256_t(1) << 67) - 1; // Almost full first limb
276 uint256_t val2 = (uint256_t(1) << 67) - 1; // Another almost full first limb
277
278 BigScalarField scalar1 = BigScalarField::from_witness(&builder, typename BigScalarField::native(val1));
279 BigScalarField scalar2 = BigScalarField::from_witness(&builder, typename BigScalarField::native(val2));
280
281 // Add them together - this will make limb0.maximum_value = 2 * ((2^67) - 1) > 2^68 - 1
282 BigScalarField sum = scalar1 + scalar2;
283
284 // Verify that limb0's maximum_value exceeds the default maximum
285 EXPECT_GT(sum.binary_basis_limbs[0].maximum_value, BigScalarField::DEFAULT_MAXIMUM_LIMB);
286
287 // Now construct a cycle_scalar from this sum - this should trigger the overflow handling
288 cycle_scalar scalar(sum);
289
290 // Verify the result is correct
291 uint256_t expected = val1 + val2;
292 EXPECT_EQ(scalar.get_value(), ScalarField(expected));
293
294 // Extra gates due to a self reduction of the bigfield input
295 check_circuit_and_gate_count(builder, 3575);
296 }
297}
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:454
AluTraceBuilder builder
Definition alu.test.cpp:124
numeric::RNG & engine
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_unsafe(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 (assuming range constraints on lo and hi)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
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
testing::Types< bb::MegaCircuitBuilder, bb::UltraCircuitBuilder > CircuitTypes
static constexpr uint256_t modulus