Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ec_operations.test.cpp
Go to the documentation of this file.
1#include "ec_operations.hpp"
2#include "acir_format.hpp"
3
7
8#include <gtest/gtest.h>
9#include <vector>
10
11using namespace ::acir_format;
12
13enum class InputConstancy : uint8_t { None, Input1, Input2, Both };
14
24template <typename Builder_, InputConstancy Constancy> class EcOperationsTestingFunctions {
25 public:
26 using Builder = Builder_;
27 using AcirConstraint = EcAdd;
30 using FF = bb::fr;
31
33 public:
34 enum class Target : uint8_t {
35 None,
36 Input1, // Invalidate first input point
37 Input2, // Invalidate second input point
38 Result // Invalidate result output
39 };
40
45
46 static std::vector<std::string> get_labels() { return { "None", "Input1", "Input2", "Result" }; }
47 };
48
49 static ProgramMetadata generate_metadata() { return ProgramMetadata{}; }
50
51 static void generate_constraints(AcirConstraint& ec_add_constraint, WitnessVector& witness_values)
52 {
53 // Generate points on Grumpkin
56 GrumpkinPoint result = input1 + input2;
57 BB_ASSERT(result != GrumpkinPoint::one()); // Ensure that tampering works correctly
58
59 // Helper to add a point: either as witness or constant
60 auto construct_point = [&](const GrumpkinPoint& point, bool as_constant) -> std::vector<WitnessOrConstant<FF>> {
61 if (as_constant) {
62 // Point is constant
63 return { WitnessOrConstant<FF>::from_constant(point.x),
64 WitnessOrConstant<FF>::from_constant(point.y),
65 WitnessOrConstant<FF>::from_constant(point.is_point_at_infinity() ? FF(1) : FF(0)) };
66 }
67 // Point is witness
68 std::vector<uint32_t> point_indices = add_to_witness_and_track_indices(witness_values, point);
69 return { WitnessOrConstant<FF>::from_index(point_indices[0]),
70 WitnessOrConstant<FF>::from_index(point_indices[1]),
71 WitnessOrConstant<FF>::from_index(point_indices[2]) };
72 };
73
74 // Determine which inputs are constants based on the Constancy template parameter
75 constexpr bool input1_is_constant = (Constancy == InputConstancy::Input1 || Constancy == InputConstancy::Both);
76 constexpr bool input2_is_constant = (Constancy == InputConstancy::Input2 || Constancy == InputConstancy::Both);
77
78 // Add inputs according to constancy template parameter
79 auto input1_fields = construct_point(input1, input1_is_constant);
80 auto input2_fields = construct_point(input2, input2_is_constant);
81
82 // Construct result and predicate as witnesses
83 std::vector<uint32_t> result_indices = add_to_witness_and_track_indices(witness_values, result);
84 uint32_t predicate_index = add_to_witness_and_track_indices(witness_values, FF(1));
85
86 // Build the constraint
87 ec_add_constraint = EcAdd{
88 .input1_x = input1_fields[0],
89 .input1_y = input1_fields[1],
90 .input1_infinite = input1_fields[2],
91 .input2_x = input2_fields[0],
92 .input2_y = input2_fields[1],
93 .input2_infinite = input2_fields[2],
94 .predicate = WitnessOrConstant<FF>::from_index(predicate_index),
95 .result_x = result_indices[0],
96 .result_y = result_indices[1],
97 .result_infinite = result_indices[2],
98 };
99 }
100
102 AcirConstraint constraint, WitnessVector witness_values, const InvalidWitness::Target& invalid_witness_target)
103 {
104 switch (invalid_witness_target) {
106 // Invalidate the first input by adding 1 to x coordinate
107 if constexpr (Constancy == InputConstancy::None || Constancy == InputConstancy::Input2) {
108 witness_values[constraint.input1_x.index] += bb::fr(1);
109 } else {
110 constraint.input1_x = WitnessOrConstant<FF>::from_constant(constraint.input1_x.value + bb::fr(1));
111 }
112 break;
113 }
115 // Invalidate the second input by adding 1 to x coordinate
116 if constexpr (Constancy == InputConstancy::None || Constancy == InputConstancy::Input1) {
117 witness_values[constraint.input2_x.index] += bb::fr(1);
118 } else {
119 constraint.input2_x = WitnessOrConstant<FF>::from_constant(constraint.input2_x.value + bb::fr(1));
120 }
121 break;
122 }
124 // Tamper with the result (always a witness) by setting it to the generator point
125 witness_values[constraint.result_x] = GrumpkinPoint::one().x;
126 witness_values[constraint.result_y] = GrumpkinPoint::one().y;
127 witness_values[constraint.result_infinite] = FF::zero();
128 break;
129 }
131 default:
132 break;
133 }
134
135 return { constraint, witness_values };
136 };
137};
138
139template <typename Builder>
141 : public ::testing::Test,
142 public TestClassWithPredicate<EcOperationsTestingFunctions<Builder, InputConstancy::None>> {
143 protected:
145};
146
147template <typename Builder>
149 : public ::testing::Test,
150 public TestClassWithPredicate<EcOperationsTestingFunctions<Builder, InputConstancy::Input1>> {
151 protected:
153};
154
155template <typename Builder>
157 : public ::testing::Test,
158 public TestClassWithPredicate<EcOperationsTestingFunctions<Builder, InputConstancy::Input2>> {
159 protected:
161};
162
163template <typename Builder>
165 : public ::testing::Test,
166 public TestClassWithPredicate<EcOperationsTestingFunctions<Builder, InputConstancy::Both>> {
167 protected:
169};
170
171using BuilderTypes = testing::Types<UltraCircuitBuilder, MegaCircuitBuilder>;
172
177
178TYPED_TEST(EcOperationsTestsNoneConstant, GenerateVKFromConstraints)
179{
181 TestFixture::template test_vk_independence<Flavor>();
182}
183
185{
187 TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::Result);
188}
189
191{
193 TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::Result);
194}
195
197{
199 TestFixture::test_witness_false_slow();
200}
201
203{
205 [[maybe_unused]] std::vector<std::string> _ = TestFixture::test_invalid_witnesses();
206}
207
209{
211 TestFixture::template test_vk_independence<Flavor>();
212}
213
215{
217 TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::Result);
218}
219
221{
223 TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::Result);
224}
225
227{
229 TestFixture::test_witness_false_slow();
230}
231
233{
235 [[maybe_unused]] std::vector<std::string> _ = TestFixture::test_invalid_witnesses();
236}
237
239{
241 TestFixture::template test_vk_independence<Flavor>();
242}
243
245{
247 TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::Result);
248}
249
251{
253 TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::Result);
254}
255
257{
259 TestFixture::test_witness_false_slow();
260}
261
263{
265 [[maybe_unused]] std::vector<std::string> _ = TestFixture::test_invalid_witnesses();
266}
267
268TYPED_TEST(EcOperationsTestsBothConstant, GenerateVKFromConstraints)
269{
271 TestFixture::template test_vk_independence<Flavor>();
272}
273
275{
277 TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::Result);
278}
279
281{
283 TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::Result);
284}
285
287{
289 TestFixture::test_witness_false_slow();
290}
291
293{
295 [[maybe_unused]] std::vector<std::string> _ = TestFixture::test_invalid_witnesses();
296}
297
298// ============================================================
299// Infinity flag tests
300// ============================================================
302using FF = bb::fr;
303
304struct AcirPoint {
307 {
308 return { p.x, p.y, p.is_point_at_infinity() ? FF(1) : FF(0) };
309 }
310 static AcirPoint infinity() { return { FF(0), FF(0), FF(1) }; }
311};
312
313template <typename Builder> class EcOperationsInfinityTests : public ::testing::Test {
314 protected:
316
317 // Push an AcirPoint to the witness vector and return the [x, y, inf] indices.
318 static std::array<uint32_t, 3> push_point(WitnessVector& witness, const AcirPoint& pt)
319 {
320 uint32_t xi = static_cast<uint32_t>(witness.size());
321 witness.emplace_back(pt.x);
322 uint32_t yi = static_cast<uint32_t>(witness.size());
323 witness.emplace_back(pt.y);
324 uint32_t ii = static_cast<uint32_t>(witness.size());
325 witness.emplace_back(pt.inf);
326 return { xi, yi, ii };
327 }
328
329 // Build an EcAdd constraint (predicate=1) from three ACIR points.
330 // Returns the constraint and the populated witness vector.
332 {
333 WitnessVector witness;
334 auto i1 = push_point(witness, p1);
335 auto i2 = push_point(witness, p2);
336 auto r = push_point(witness, result);
337 uint32_t pred_idx = static_cast<uint32_t>(witness.size());
338 witness.emplace_back(FF(1));
339
340 EcAdd c{
341 .input1_x = WitnessOrConstant<FF>::from_index(i1[0]),
342 .input1_y = WitnessOrConstant<FF>::from_index(i1[1]),
343 .input1_infinite = WitnessOrConstant<FF>::from_index(i1[2]),
344 .input2_x = WitnessOrConstant<FF>::from_index(i2[0]),
345 .input2_y = WitnessOrConstant<FF>::from_index(i2[1]),
346 .input2_infinite = WitnessOrConstant<FF>::from_index(i2[2]),
347 .predicate = WitnessOrConstant<FF>::from_index(pred_idx),
348 .result_x = r[0],
349 .result_y = r[1],
350 .result_infinite = r[2],
351 };
352 return { c, witness };
353 }
354
355 // Run the circuit and return (satisfied, error_string).
356 static std::pair<bool, std::string> run_circuit(EcAdd constraint, WitnessVector witness)
357 {
358 AcirFormat cs = constraint_to_acir_format(constraint);
359 AcirProgram program{ cs, witness };
360 auto builder = create_circuit<Builder>(program, ProgramMetadata{});
361 bool ok = CircuitChecker::check(builder) && !builder.failed();
362 return { ok, builder.err() };
363 }
364};
365
367
368// P + (-P) = point_at_infinity: valid proof with result_infinite=1, result_x=0, result_y=0.
370{
373 auto [constraint, witness] =
374 TestFixture::make_ec_add(AcirPoint::from_native(p), AcirPoint::from_native(-p), AcirPoint::infinity());
375 auto [ok, err] = TestFixture::run_circuit(constraint, witness);
376 EXPECT_TRUE(ok) << "P + (-P) = infinity should produce a valid circuit";
377}
378
379// A finite result with result_infinite=1 (forged) must fail.
380TYPED_TEST(EcOperationsInfinityTests, ForgedInfinityFlagOnFiniteResultFails)
381{
385 ASSERT_FALSE((p + q).is_point_at_infinity());
386 auto [constraint, witness] =
387 TestFixture::make_ec_add(AcirPoint::from_native(p), AcirPoint::from_native(q), AcirPoint::from_native(p + q));
388 witness[constraint.result_infinite] = FF(1); // forge: finite result claimed as infinite
389
390 auto [ok, err] = TestFixture::run_circuit(constraint, witness);
391 EXPECT_TRUE(!ok || err.find("assert_eq") != std::string::npos)
392 << "Forged infinity flag on finite result should fail";
393}
394
395// An infinity result with result_infinite=0 (forged) must fail.
396TYPED_TEST(EcOperationsInfinityTests, ForgedFiniteFlagOnInfinityResultFails)
397{
400 // Forge result: (0,0) coordinates but is_infinite=0 (should be 1)
401 auto [constraint, witness] = TestFixture::make_ec_add(
403
404 auto [ok, err] = TestFixture::run_circuit(constraint, witness);
405 EXPECT_TRUE(!ok || err.find("assert_eq") != std::string::npos)
406 << "Forged finite flag on infinity result should fail";
407}
408
409// Input point at infinity (∞ + P = P): should produce a valid circuit.
411{
414 auto [constraint, witness] =
415 TestFixture::make_ec_add(AcirPoint::infinity(), AcirPoint::from_native(p), AcirPoint::from_native(p));
416
417 auto [ok, err] = TestFixture::run_circuit(constraint, witness);
418 EXPECT_TRUE(ok) << "infinity + P = P should produce a valid circuit";
419}
420
421// Input point with forged input_infinite=1 (non-zero coordinates) must fail.
422TYPED_TEST(EcOperationsInfinityTests, ForgedInputInfinityFlagFails)
423{
427 auto [constraint, witness] =
428 TestFixture::make_ec_add(AcirPoint::from_native(p), AcirPoint::from_native(q), AcirPoint::from_native(p + q));
429 witness[constraint.input1_infinite.index] = FF(1); // forge: finite input1 claimed as infinite
430
431 auto [ok, err] = TestFixture::run_circuit(constraint, witness);
432 EXPECT_TRUE(!ok || err.find("assert_eq") != std::string::npos) << "Forged input infinity flag should fail";
433}
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
static std::array< uint32_t, 3 > push_point(WitnessVector &witness, const AcirPoint &pt)
static std::pair< bool, std::string > run_circuit(EcAdd constraint, WitnessVector witness)
static std::pair< EcAdd, WitnessVector > make_ec_add(AcirPoint p1, AcirPoint p2, AcirPoint result)
static std::vector< std::string > get_labels()
Testing functions to generate the EcOperationTest test suite. Constancy specifies which inputs to the...
static ProgramMetadata generate_metadata()
static void generate_constraints(AcirConstraint &ec_add_constraint, WitnessVector &witness_values)
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
constexpr bool is_point_at_infinity() const noexcept
static affine_element random_element(numeric::RNG *engine=nullptr) noexcept
Samples a random point on the curve.
static constexpr affine_element one() noexcept
group class. Represents an elliptic curve group element. Group is parametrised by Fq and Fr
Definition group.hpp:38
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:44
AluTraceBuilder builder
Definition alu.test.cpp:124
bb::fr FF
TYPED_TEST_SUITE(EcOperationsTestsNoneConstant, BuilderTypes)
TYPED_TEST(EcOperationsTestsNoneConstant, GenerateVKFromConstraints)
bb::group< bb::fr, bb::fq, G1Params > g1
Definition grumpkin.hpp:46
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
::testing::Types< UltraCircuitBuilder, MegaCircuitBuilder > BuilderTypes
static AcirPoint from_native(const GrumpkinPoint &p)
static AcirPoint infinity()
static constexpr field zero()