Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
pairing_points.test.cpp
Go to the documentation of this file.
9#include <gtest/gtest.h>
10
11namespace bb::stdlib::recursion {
12
13template <typename Builder> class PairingPointsTests : public testing::Test {
14 public:
16
22 template <typename Curve>
23 static std::vector<PairingPoints<Curve>> create_valid_pairing_points(typename Curve::Builder* builder, size_t count)
24 {
25 using Group = typename Curve::Group;
26
28 auto srs = ck.get_monomial_points();
29
31 result.reserve(count);
32 for (size_t i = 1; i <= count; i++) {
33 result.emplace_back(Group::from_witness(builder, srs[i]), Group::from_witness(builder, -srs[i - 1]));
34 }
35 return result;
36 }
37};
38
39using Curves = testing::Types<stdlib::bn254<UltraCircuitBuilder>, stdlib::bn254<MegaCircuitBuilder>>;
41
42// Precondition asserts fire on unpopulated PairingPoints
43TYPED_TEST(PairingPointsTests, EmptyPairingPointsProtection)
44{
45 using Curve = TypeParam;
46 using Builder = typename Curve::Builder;
47 using PP = PairingPoints<Curve>;
48
50 auto pps = TestFixture::template create_valid_pairing_points<Curve>(&builder, 1);
51
52 PP empty;
53 EXPECT_THROW_WITH_MESSAGE(pps[0].aggregate(empty), "Cannot aggregate null pairing points.");
54 EXPECT_THROW_WITH_MESSAGE(empty.set_public(), "Calling set_public on empty pairing points.");
55 EXPECT_THROW_WITH_MESSAGE(empty.check(), "Calling check on empty pairing points.");
56}
57
58// Gate count pinning for set_default_to_public
60{
61 using Builder = typename TypeParam::Builder;
62 static constexpr size_t NUM_GATES_ADDED = 8;
63
65
66 size_t num_gates = builder.num_gates();
68 EXPECT_EQ(NUM_GATES_ADDED, builder.num_gates() - num_gates)
69 << "There has been a change in the number of gates required to set default PairingPoints as public inputs.";
70
71 EXPECT_TRUE(CircuitChecker::check(builder));
72}
73
74// Default pairing points satisfy the pairing equation
75TYPED_TEST(PairingPointsTests, DefaultPointsAreValid)
76{
77 using Builder = TypeParam::Builder;
78 using PP = PairingPoints<TypeParam>;
79
81
82 auto pp = PP::construct_default();
83 EXPECT_TRUE(pp.is_default());
84 pp.set_public(&builder);
85 EXPECT_TRUE(CircuitChecker::check(builder));
86
87 // Validate default PairingPoints via native pairing check
88 bb::PairingPoints<curve::BN254> native_pp(pp.P0().get_value(), pp.P1().get_value());
89 EXPECT_TRUE(native_pp.check()) << "Default PairingPoints are not valid pairing points.";
90}
91
92// Pairwise aggregate computes this = this + r * other with correct Fiat-Shamir challenge
94{
95 using Curve = TypeParam;
96 using Builder = typename Curve::Builder;
97 using Group = typename Curve::Group;
98 using Fr = typename Curve::ScalarField;
99
101 auto pps = TestFixture::template create_valid_pairing_points<Curve>(&builder, 2);
102 EXPECT_FALSE(pps[0].is_default());
103 EXPECT_FALSE(pps[1].is_default());
104
105 // Save the original values before aggregation mutates pps[0]
106 auto orig_P0 = pps[0].P0();
107 auto orig_P1 = pps[0].P1();
108
109 pps[0].aggregate(pps[1]);
110 EXPECT_FALSE(pps[0].is_default());
111
112 // Replicate the transcript to derive the same separator
114 transcript.add_to_hash_buffer("Accumulator_P0", orig_P0);
115 transcript.add_to_hash_buffer("Accumulator_P1", orig_P1);
116 transcript.add_to_hash_buffer("Aggregated_P0", pps[1].P0());
117 transcript.add_to_hash_buffer("Aggregated_P1", pps[1].P1());
118 auto r = transcript.template get_challenge<Fr>("recursion_separator");
119
120 // Expected: orig + r * other
121 Group expected_P0 = orig_P0 + pps[1].P0() * r;
122 Group expected_P1 = orig_P1 + pps[1].P1() * r;
123
124 EXPECT_EQ(pps[0].P0().get_value(), expected_P0.get_value());
125 EXPECT_EQ(pps[0].P1().get_value(), expected_P1.get_value());
126
127 EXPECT_TRUE(CircuitChecker::check(builder));
128}
129
130// Aggregating into an unpopulated LHS simply copies the RHS
131TYPED_TEST(PairingPointsTests, AggregateIntoEmpty)
132{
133 using Curve = TypeParam;
134 using Builder = typename Curve::Builder;
135 using PP = PairingPoints<Curve>;
136
138 auto pps = TestFixture::template create_valid_pairing_points<Curve>(&builder, 1);
139
140 // Default-constructed PairingPoints has no data
141 PP empty;
142 EXPECT_FALSE(empty.is_populated());
143 EXPECT_FALSE(empty.is_default());
144
145 // Aggregating into empty should just copy the other (including is_default flag)
146 empty.aggregate(pps[0]);
147 EXPECT_TRUE(empty.is_populated());
148 EXPECT_FALSE(empty.is_default());
149 EXPECT_EQ(empty.P0().get_value(), pps[0].P0().get_value());
150 EXPECT_EQ(empty.P1().get_value(), pps[0].P1().get_value());
151}
152
153// N-way aggregate computes P₀ + r₁·P₁ + r₂·P₂ with correct Fiat-Shamir challenges
155{
156 using Curve = TypeParam;
157 using Builder = typename Curve::Builder;
158 using PP = PairingPoints<Curve>;
159 using Group = typename Curve::Group;
160 using Fr = typename Curve::ScalarField;
161
163 auto pps = TestFixture::template create_valid_pairing_points<Curve>(&builder, 3);
164
165 // Replicate the transcript to derive the same challenges
167 for (size_t idx = 0; idx < 3; ++idx) {
168 transcript.add_to_hash_buffer("first_component_" + std::to_string(idx), pps[idx].P0());
169 transcript.add_to_hash_buffer("second_component_" + std::to_string(idx), pps[idx].P1());
170 }
171 std::vector<std::string> labels = { "pp_aggregation_challenge_1", "pp_aggregation_challenge_2" };
172 auto challenges = transcript.template get_challenges<Fr>(labels);
173
174 // Expected: P₀ + r₁·P₁ + r₂·P₂
175 Group expected_P0 = pps[0].P0() + pps[1].P0() * challenges[0] + pps[2].P0() * challenges[1];
176 Group expected_P1 = pps[0].P1() + pps[1].P1() * challenges[0] + pps[2].P1() * challenges[1];
177
178 std::vector<PP> pp_vec = { pps[0], pps[1], pps[2] };
179 PP aggregated = PP::aggregate_multiple(pp_vec);
180
181 EXPECT_FALSE(aggregated.is_default());
182 EXPECT_EQ(aggregated.P0().get_value(), expected_P0.get_value());
183 EXPECT_EQ(aggregated.P1().get_value(), expected_P1.get_value());
184 EXPECT_TRUE(CircuitChecker::check(builder));
185}
186
187// set_public then reconstruct_from_public recovers the same point values
188TYPED_TEST(PairingPointsTests, PublicInputSerdeRoundTrip)
189{
190 using Curve = TypeParam;
191 using Builder = typename Curve::Builder;
192 using PP = PairingPoints<Curve>;
193 using Fr = Curve::ScalarField;
194 using PublicPP = stdlib::PublicInputComponent<PP>;
195
197
198 // Create default (infinity) pairing points
199 PP original = PP::construct_default();
200 EXPECT_TRUE(original.is_populated());
201
202 // Serialize to public inputs (set_public detects default and uses set_default_to_public)
203 uint32_t start_idx = original.set_public(&builder);
204
205 // Extract the public inputs as field_t elements
206 std::vector<Fr> public_inputs;
207 for (uint32_t var_idx : builder.public_inputs()) {
208 public_inputs.emplace_back(Fr::from_witness_index(&builder, var_idx));
209 }
210
211 // Reconstruct via PublicInputComponent (exercises reconstruct_from_public)
212 PP reconstructed = PublicPP::reconstruct(public_inputs, PublicComponentKey{ start_idx });
213 EXPECT_TRUE(reconstructed.is_populated());
214
215 // Verify the round-tripped values match
216 EXPECT_EQ(reconstructed.P0().get_value(), original.P0().get_value());
217 EXPECT_EQ(reconstructed.P1().get_value(), original.P1().get_value());
218
219 EXPECT_TRUE(CircuitChecker::check(builder));
220}
221
222// =============================================================================
223// Tagging mechanism tests (tag tracking, merge, ProverInstance enforcement)
224// =============================================================================
225
226template <typename Builder> class PairingPointsTaggingTests : public testing::Test {
227 public:
229};
230
232
233// Tags are assigned sequentially and unified by aggregate / aggregate_multiple
235{
236 using Curve = TypeParam;
237 using Builder = typename Curve::Builder;
239 using Group = PairingPoints::Group;
240 using Fr = PairingPoints::Fr;
241 using NativeFr = typename Curve::ScalarFieldNative;
242
244
245 Fr scalar_one = Fr::from_witness(&builder, NativeFr::random_element());
246 Fr scalar_two = Fr::from_witness(&builder, NativeFr::random_element());
247 Group P0 = Group::batch_mul({ Group::one(&builder) }, { scalar_one });
248 Group P1 = Group::batch_mul({ Group::one(&builder) }, { scalar_two });
249
250 // Check that no pairing points exist
251 EXPECT_TRUE(builder.pairing_points_tagging.has_single_pairing_point_tag());
252
253 PairingPoints pp_one = { P0, P1 };
254 PairingPoints pp_two = { P0, P1 };
255
256 // Check the tags
257 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_one.tag_index), 0U);
258 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_two.tag_index), 1U);
259
260 // Check that there are two different pairing points in the builder
261 EXPECT_FALSE(builder.pairing_points_tagging.has_single_pairing_point_tag());
262
263 // Merge the tags
264 pp_one.aggregate(pp_two);
265
266 // Check that the tags have been merged
267 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_two.tag_index), 0U);
268 EXPECT_TRUE(builder.pairing_points_tagging.has_single_pairing_point_tag());
269
270 // Create two new pairing points and aggregate with aggregate_multiple
271 PairingPoints pp_three = { P0, P1 };
272 PairingPoints pp_four = { P0, P1 };
273
274 // Check the tags
275 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_three.tag_index), 2U);
276 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_four.tag_index), 3U);
277
278 // Check that there are two different pairing points in the builder
279 EXPECT_FALSE(builder.pairing_points_tagging.has_single_pairing_point_tag());
280
281 // Merge the tags
282 std::vector<PairingPoints> pp_to_be_aggregated = { pp_one, pp_three, pp_four };
283 PairingPoints aggregated_pp = PairingPoints::aggregate_multiple(pp_to_be_aggregated);
284
285 // Check that the tags have been merged
286 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_one.tag_index), 4U);
287 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_two.tag_index), 4U);
288 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_three.tag_index), 4U);
289 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_four.tag_index), 4U);
290 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(aggregated_pp.tag_index), 4U);
291 EXPECT_TRUE(builder.pairing_points_tagging.has_single_pairing_point_tag());
292}
293
294// ProverInstance rejects unaggregated or non-public pairing points
295TYPED_TEST(PairingPointsTaggingTests, ProverInstanceRejectsUnmergedTags)
296{
297 using Curve = TypeParam;
298 using Builder = typename Curve::Builder;
300 using Group = PairingPoints::Group;
301 using Fr = PairingPoints::Fr;
302 using NativeFr = typename Curve::ScalarFieldNative;
305
307
308 Fr scalar_one = Fr::from_witness(&builder, NativeFr::random_element());
309 Fr scalar_two = Fr::from_witness(&builder, NativeFr::random_element());
310 Group P0 = Group::batch_mul({ Group::one(&builder) }, { scalar_one });
311 Group P1 = Group::batch_mul({ Group::one(&builder) }, { scalar_two });
312
313 PairingPoints pp_one = { P0, P1 };
314 PairingPoints pp_two = { P0, P1 };
315 PairingPoints pp_three = { P0, P1 };
316
317 // Check the tags
318 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_one.tag_index), 0U);
319 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_two.tag_index), 1U);
320 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_three.tag_index), 2U);
321
322 // Check that there are different pairing points in the builder
323 EXPECT_FALSE(builder.pairing_points_tagging.has_single_pairing_point_tag());
324
325 // Merge the tags
326 pp_one.aggregate(pp_two);
327
328 // Check that the tags have not been merged
329 EXPECT_FALSE(builder.pairing_points_tagging.has_single_pairing_point_tag());
330
331 // Create a ProverInstance, expect failure because pairing points have not been aggregated
333 ProverInstance prover_instance(builder),
334 "Pairing points must all be aggregated together. Either no pairing points should be created, or "
335 "all created pairing points must be aggregated into a single pairing point. Found 2 different "
336 "pairing points");
337
338 // Aggregate pairing points
339 pp_one.aggregate(pp_three);
340
341 // Create a ProverInstance, expect failure because pairing points have not been set to public
343 ProverInstance prover_instance(builder),
344 "Pairing points must be set to public in the circuit before constructing the ProverInstance.");
345
347 inputs.pairing_inputs = pp_one;
349
350 // Construct Prover instance successfully
351 ProverInstance prover_instance(builder);
352}
353
354// Copying a PairingPoints shares the same tag (no new equivalence class)
356{
357 using Curve = TypeParam;
358 using Builder = typename Curve::Builder;
359
361 using Group = PairingPoints::Group;
362 using Fr = Curve::ScalarField;
363 using NativeFr = Curve::ScalarFieldNative;
364
366
367 Fr scalar_one = Fr::from_witness(&builder, NativeFr::random_element());
368 Fr scalar_two = Fr::from_witness(&builder, NativeFr::random_element());
369 Group P0 = Group::batch_mul({ Group::one(&builder) }, { scalar_one });
370 Group P1 = Group::batch_mul({ Group::one(&builder) }, { scalar_two });
371
372 PairingPoints pp_original = { P0, P1 };
373 PairingPoints pp_copy(pp_original);
374
375 // Check that there is only one tag
376 EXPECT_TRUE(builder.pairing_points_tagging.has_single_pairing_point_tag());
377
378 // Check that the tags are the same
379 BB_ASSERT_EQ(builder.pairing_points_tagging.get_tag(pp_original.tag_index),
380 builder.pairing_points_tagging.get_tag(pp_copy.tag_index));
381}
382
383// After set_public, no further merges or set_public calls are allowed
385{
386 using Curve = TypeParam;
387 using Builder = typename Curve::Builder;
389 using Group = PairingPoints::Group;
390 using Fr = PairingPoints::Fr;
391 using NativeFr = typename Curve::ScalarFieldNative;
392
394
395 Fr scalar_one = Fr::from_witness(&builder, NativeFr::random_element());
396 Fr scalar_two = Fr::from_witness(&builder, NativeFr::random_element());
397 Group P0 = Group::batch_mul({ Group::one(&builder) }, { scalar_one });
398 Group P1 = Group::batch_mul({ Group::one(&builder) }, { scalar_two });
399
400 PairingPoints pp_one = { P0, P1 };
401 PairingPoints pp_two = { P0, P1 };
402
403 pp_one.aggregate(pp_two);
405 inputs.pairing_inputs = pp_one;
407
408 // Cannot merge after set_public
409 PairingPoints pp_three = { P0, P1 };
410 EXPECT_THROW_WITH_MESSAGE(pp_one.aggregate(pp_three),
411 "Cannot merge pairing point tags after pairing points have been set to public.");
412
413 // Cannot set_public twice
415 builder.pairing_points_tagging.set_public_pairing_points(),
416 "Trying to set pairing points to public for a circuit that already has public pairing points.");
417}
418
419} // namespace bb::stdlib::recursion
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
Common transcript class for both parties. Stores the data for the current round, as well as the manif...
void add_to_hash_buffer(const std::string &label, const T &element)
Adds an element to the transcript.
CommitmentKey object over a pairing group 𝔾₁.
An object storing two EC points that represent the inputs to a pairing check.
bool check() const
Verify the pairing equation e(P0, [1]₂) · e(P1, [x]₂) = 1.
Contains all the information required by a Honk prover to create a proof, constructed from a finalize...
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
typename grumpkin::g1 Group
Definition grumpkin.hpp:63
A wrapper class for serializing objects to and from the public inputs of a circuit.
static std::vector< PairingPoints< Curve > > create_valid_pairing_points(typename Curve::Builder *builder, size_t count)
Create N distinct valid pairing points as circuit witnesses from SRS points.
Manages the data that is propagated on the public inputs of an application/function circuit.
AluTraceBuilder builder
Definition alu.test.cpp:124
AvmProvingInputs inputs
ProverInstance_< UltraKeccakFlavor > ProverInstance
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
TYPED_TEST(PairingPointsTests, EmptyPairingPointsProtection)
TYPED_TEST_SUITE(PairingPointsTests, Curves)
testing::Types< stdlib::bn254< UltraCircuitBuilder >, stdlib::bn254< MegaCircuitBuilder > > Curves
CommitmentKey< Curve > ck
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
An object storing two EC points that represent the inputs to a pairing check.
static uint32_t set_default_to_public(Builder *builder)
Set the witness indices for the default (infinity) pairing points to public.
static PairingPoints aggregate_multiple(std::vector< PairingPoints > &pairing_points, bool handle_edge_cases=true)
Aggregate multiple PairingPoints using random linear combination.
void aggregate(PairingPoints const &other)
Aggregate another PairingPoints into this one via random linear combination.
uint32_t set_public(Builder *ctx=nullptr)
Set the witness indices for the pairing points to public.