Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
field_utils.test.cpp
Go to the documentation of this file.
4#include <gtest/gtest.h>
5
6using namespace bb;
7
8namespace {
9template <typename Builder> class FieldUtilsTests : public ::testing::Test {
10 public:
12 using native = typename field_t::native;
13};
14
15using CircuitTypes = ::testing::Types<bb::UltraCircuitBuilder, bb::MegaCircuitBuilder>;
16} // namespace
17
19
25TYPED_TEST(FieldUtilsTests, ValidateSplitRejectsModulus)
26{
27 using Builder = TypeParam;
28 using field_t = typename TestFixture::field_t;
29 using native = typename TestFixture::native;
30
32 constexpr size_t lo_bits = 128;
33
34 // Construct a value equal to the bn254 scalar field modulus
35 uint256_t modulus = native::modulus;
36 uint256_t lo_val = modulus.slice(0, lo_bits);
37 uint256_t hi_val = modulus.slice(lo_bits, 254);
38
39 // Create field elements from these values
40 auto lo = field_t::from_witness(&builder, native(lo_val));
41 auto hi = field_t::from_witness(&builder, native(hi_val));
42
43 // Verify the reconstruction equals the modulus
44 uint256_t reconstructed = uint256_t(lo.get_value()) + (uint256_t(hi.get_value()) << lo_bits);
45 EXPECT_EQ(reconstructed, modulus);
46
47 // Call validate_split_in_field_unsafe with the modulus itself
48 // This should create constraints that fail
49 stdlib::validate_split_in_field_unsafe(lo, hi, lo_bits, modulus);
50
51 // The circuit should fail because value == modulus is invalid
52 EXPECT_FALSE(CircuitChecker::check(builder));
53}
54
59TYPED_TEST(FieldUtilsTests, ValidateSplitAcceptsModulusMinusOne)
60{
61 using Builder = TypeParam;
62 using field_t = typename TestFixture::field_t;
63 using native = typename TestFixture::native;
64
66 constexpr size_t lo_bits = 128;
67
68 // Construct a value equal to the bn254 scalar field modulus - 1
69 uint256_t modulus = native::modulus;
70 uint256_t value = modulus - 1;
71 uint256_t lo_val = value.slice(0, lo_bits);
72 uint256_t hi_val = value.slice(lo_bits, 254);
73
74 // Create field elements from these values
75 auto lo = field_t::from_witness(&builder, native(lo_val));
76 auto hi = field_t::from_witness(&builder, native(hi_val));
77
78 // Verify the reconstruction equals modulus - 1
79 uint256_t reconstructed = uint256_t(lo.get_value()) + (uint256_t(hi.get_value()) << lo_bits);
80 EXPECT_EQ(reconstructed, value);
81
82 // Call validate_split_in_field_unsafe
83 // This should succeed because value < modulus
84 stdlib::validate_split_in_field_unsafe(lo, hi, lo_bits, modulus);
85
86 // The circuit should be valid
87 EXPECT_FALSE(builder.failed());
88 EXPECT_TRUE(CircuitChecker::check(builder));
89}
90
94TYPED_TEST(FieldUtilsTests, SplitUniqueRejectsModulus)
95{
96 using Builder = TypeParam;
97 using field_t = typename TestFixture::field_t;
98 using native = typename TestFixture::native;
99
101 constexpr size_t lo_bits = 128;
102
103 // Create a field element that represents 0 (which is equivalent to modulus in field arithmetic)
104 // In the native field, we can't directly create a witness with value == modulus
105 // because it gets reduced to 0. So we test the edge case by using 0.
106 auto field = field_t::from_witness(&builder, native(0));
107
108 // Split it
109 auto [lo, hi] = stdlib::split_unique(field, lo_bits);
110
111 // Both lo and hi should be 0 for value 0
112 EXPECT_EQ(uint256_t(lo.get_value()), uint256_t(0));
113 EXPECT_EQ(uint256_t(hi.get_value()), uint256_t(0));
114
115 // The circuit should be valid for 0 (the canonical representation)
116 EXPECT_FALSE(builder.failed());
117 EXPECT_TRUE(CircuitChecker::check(builder));
118}
119
123TYPED_TEST(FieldUtilsTests, SplitUniqueMaxValue)
124{
125 using Builder = TypeParam;
126 using field_t = typename TestFixture::field_t;
127 using native = typename TestFixture::native;
128
130 constexpr size_t lo_bits = 128;
131
132 // Create a field element with the maximum value (modulus - 1)
133 // This is represented as -1 in the field
134 auto field = field_t::from_witness(&builder, -native(1));
135
136 // Split it
137 auto [lo, hi] = stdlib::split_unique(field, lo_bits);
138
139 // Verify reconstruction
140 uint256_t lo_val = uint256_t(lo.get_value());
141 uint256_t hi_val = uint256_t(hi.get_value());
142 uint256_t reconstructed = lo_val + (hi_val << lo_bits);
143 uint256_t expected = uint256_t(native::modulus) - 1;
144
145 EXPECT_EQ(reconstructed, expected);
146
147 // The circuit should be valid
148 EXPECT_FALSE(builder.failed());
149 EXPECT_TRUE(CircuitChecker::check(builder));
150}
151
159TYPED_TEST(FieldUtilsTests, ValidateSplitConstantLoWitnessHiRejectsModulus)
160{
161 using Builder = TypeParam;
162 using field_t = typename TestFixture::field_t;
163 using native = typename TestFixture::native;
164
166 constexpr size_t lo_bits = 128;
167
168 // Use value == modulus (should be rejected)
169 uint256_t modulus = native::modulus;
170 uint256_t lo_val = modulus.slice(0, lo_bits);
171 uint256_t hi_val = modulus.slice(lo_bits, 254);
172
173 // Create constant lo and witness hi
174 auto lo = field_t(native(lo_val)); // constant
175 auto hi = field_t::from_witness(&builder, native(hi_val)); // witness
176
177 // Verify the setup
178 EXPECT_TRUE(lo.is_constant());
179 EXPECT_FALSE(hi.is_constant());
180
181 // Call validate_split_in_field_unsafe with value == modulus
182 stdlib::validate_split_in_field_unsafe(lo, hi, lo_bits, modulus);
183
184 // The circuit should FAIL because value == modulus is invalid
185 EXPECT_FALSE(CircuitChecker::check(builder));
186}
187
192TYPED_TEST(FieldUtilsTests, ValidateSplitWitnessLoConstantHiRejectsModulus)
193{
194 using Builder = TypeParam;
195 using field_t = typename TestFixture::field_t;
196 using native = typename TestFixture::native;
197
199 constexpr size_t lo_bits = 128;
200
201 // Use value == modulus (should be rejected)
202 uint256_t modulus = native::modulus;
203 uint256_t lo_val = modulus.slice(0, lo_bits);
204 uint256_t hi_val = modulus.slice(lo_bits, 254);
205
206 // Create witness lo and constant hi
207 auto lo = field_t::from_witness(&builder, native(lo_val)); // witness
208 auto hi = field_t(native(hi_val)); // constant
209
210 // Verify the setup
211 EXPECT_FALSE(lo.is_constant());
212 EXPECT_TRUE(hi.is_constant());
213
214 // Call validate_split_in_field_unsafe with value == modulus
215 stdlib::validate_split_in_field_unsafe(lo, hi, lo_bits, modulus);
216
217 // The circuit should FAIL because value == modulus is invalid
218 EXPECT_FALSE(CircuitChecker::check(builder));
219}
220
234TYPED_TEST(FieldUtilsTests, ValidateSplitRejectsModulusWithCorruptedBorrowZero)
235{
236 using Builder = TypeParam;
237 using field_t = typename TestFixture::field_t;
238 using native = typename TestFixture::native;
239
241 constexpr size_t lo_bits = 128;
242 const size_t hi_bits = native::modulus.get_msb() + 1 - lo_bits;
243
244 // Use value == modulus
245 uint256_t modulus = native::modulus;
246 uint256_t r_lo = modulus.slice(0, lo_bits);
247 uint256_t r_hi = modulus.slice(lo_bits, native::modulus.get_msb() + 1);
248
249 // Create witnesses for lo = r_lo, hi = r_hi (value == modulus)
250 auto lo = field_t::from_witness(&builder, native(r_lo));
251 auto hi = field_t::from_witness(&builder, native(r_hi));
252
253 // Malicious prover sets borrow = 0 (trying to bypass the check)
254 auto borrow = field_t::from_witness(&builder, native(0));
255 builder.create_small_range_constraint(borrow.get_witness_index(), 1, "borrow");
256
257 // Build the constraints manually (matching the fixed implementation)
258 // hi_diff = r_hi - hi - borrow
259 // lo_diff = (r_lo - 1) - lo + borrow * 2^lo_bits
260 field_t hi_diff = (-hi + r_hi) - borrow;
261 field_t lo_diff = (-lo + (r_lo - 1)) + (borrow * (uint256_t(1) << lo_bits));
262
263 hi_diff.create_range_constraint(hi_bits);
264 lo_diff.create_range_constraint(lo_bits);
265
266 // The circuit should FAIL because with borrow=0 and lo=r_lo:
267 // lo_diff = (r_lo - 1) - r_lo = -1, which underflows and fails the range check
268 EXPECT_FALSE(CircuitChecker::check(builder));
269}
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
constexpr uint256_t slice(uint64_t start, uint64_t end) const
void create_range_constraint(size_t num_bits, std::string const &msg="field_t::range_constraint") const
Let x = *this.normalize(), constrain x.v < 2^{num_bits}.
Definition field.cpp:919
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:466
AluTraceBuilder builder
Definition alu.test.cpp:124
stdlib::field_t< Builder > field_t
constexpr T get_msb(const T in)
Definition get_msb.hpp:49
std::pair< field_t< Builder >, field_t< Builder > > split_unique(const field_t< Builder > &field, const size_t lo_bits, const bool skip_range_constraints)
Split a bn254 scalar field element into unique lo and hi limbs.
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(CommitmentKeyTest, Curves)
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
testing::Types< bb::MegaCircuitBuilder, bb::UltraCircuitBuilder > CircuitTypes
General class for prime fields see Prime field documentation["field documentation"] for general imple...