Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
pairing_points.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Planned, auditors: [Khashayar], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
15#include <type_traits>
16
17namespace bb::stdlib::recursion {
18
19// Combined limbs for default pairing points: lo = limb0 + limb1 * 2^68, hi = limb2 + limb3 * 2^68
20// These are the source of truth, used in set_default_to_public() to avoid expensive bigfield operations.
21static constexpr bb::fr DEFAULT_PP_P0_X_LO =
22 bb::fr("0x000000000000000000000000000000b75c020998797da7842ab5d6d1986846cf");
23static constexpr bb::fr DEFAULT_PP_P0_X_HI =
24 bb::fr("0x0000000000000000000000000000000000031e97a575e9d05a107acb64952eca");
25static constexpr bb::fr DEFAULT_PP_P0_Y_LO =
26 bb::fr("0x000000000000000000000000000000c410db10a01750aebb5666547acf8bd5a4");
27static constexpr bb::fr DEFAULT_PP_P0_Y_HI =
28 bb::fr("0x0000000000000000000000000000000000178cbf4206471d722669117f9758a4");
29static constexpr bb::fr DEFAULT_PP_P1_X_LO =
30 bb::fr("0x0000000000000000000000000000007fd51009034b3357f0e91b8a11e7842c38");
31static constexpr bb::fr DEFAULT_PP_P1_X_HI =
32 bb::fr("0x00000000000000000000000000000000000f94656a2ca489889939f81e9c7402");
33static constexpr bb::fr DEFAULT_PP_P1_Y_LO =
34 bb::fr("0x00000000000000000000000000000093fe27776f50224bd6fb128b46c1ddb67f");
35static constexpr bb::fr DEFAULT_PP_P1_Y_HI =
36 bb::fr("0x00000000000000000000000000000000001b52c2020d7464a0c80c0da527a081");
37
38// TODO(https://github.com/AztecProtocol/barretenberg/issues/911): These are pairing points extracted from a
39// valid proof. This is a workaround because we can't represent the point at infinity in biggroup yet.
40// Derived from the combined limbs above: fq = lo + hi * 2^136
41static constexpr bb::fq DEFAULT_PAIRING_POINT_P0_X =
42 bb::fq(uint256_t(DEFAULT_PP_P0_X_LO) + (uint256_t(DEFAULT_PP_P0_X_HI) << 136));
43static constexpr bb::fq DEFAULT_PAIRING_POINT_P0_Y =
44 bb::fq(uint256_t(DEFAULT_PP_P0_Y_LO) + (uint256_t(DEFAULT_PP_P0_Y_HI) << 136));
45static constexpr bb::fq DEFAULT_PAIRING_POINT_P1_X =
46 bb::fq(uint256_t(DEFAULT_PP_P1_X_LO) + (uint256_t(DEFAULT_PP_P1_X_HI) << 136));
47static constexpr bb::fq DEFAULT_PAIRING_POINT_P1_Y =
48 bb::fq(uint256_t(DEFAULT_PP_P1_Y_LO) + (uint256_t(DEFAULT_PP_P1_Y_HI) << 136));
49
58template <typename Curve> struct PairingPoints {
59 using Builder = typename Curve::Builder;
63
64 // Number of bb::fr field elements used to represent pairing points in public inputs
65 static constexpr size_t PUBLIC_INPUTS_SIZE = PAIRING_POINTS_SIZE;
66
67 // Array-like interface for Codec compatibility
69 static constexpr size_t SIZE = 2;
70
71 std::array<Group, 2> _points;
72
73 bool has_data = false;
74 uint32_t tag_index = 0; // Index of the tag for tracking pairing point aggregation
75
76 Group& P0() { return _points[0]; }
77 Group& P1() { return _points[1]; }
78 const Group& P0() const { return _points[0]; }
79 const Group& P1() const { return _points[1]; }
80
81 PairingPoints() = default;
82
83 PairingPoints(const Group& p0, const Group& p1)
84 : _points{ p0, p1 }
85 , has_data(true)
86 {
87 Builder* builder = validate_context<Builder>(_points);
88 if (builder != nullptr) {
89 tag_index = builder->pairing_points_tagging.create_pairing_point_tag();
90 }
91
92#ifndef NDEBUG
93 bb::PairingPoints<typename Curve::NativeCurve> native_pp(P0().get_value(), P1().get_value());
94 info("Are Pairing Points with tag ", tag_index, " valid? ", native_pp.check() ? "true" : "false");
95#endif
96 }
97
98 // Array access (delegates to _points)
99 auto& operator[](size_t idx) { return _points[idx]; }
100 const auto& operator[](size_t idx) const { return _points[idx]; }
101
102 // Iterator support for range-based for (required by Codec deserialization)
103 // Non-const begin() sets has_data since Codec uses `for (auto& x : val)` pattern
104 auto begin()
105 {
106 has_data = true;
107 return _points.begin();
108 }
109 auto end() { return _points.end(); }
110 auto begin() const { return _points.begin(); }
111 auto end() const { return _points.end(); }
112 static constexpr size_t size() { return SIZE; }
113
114 typename Curve::bool_ct operator==(PairingPoints const& other) const
115 {
116 return P0() == other.P0() && P1() == other.P1();
117 }
118
136 static PairingPoints aggregate_multiple(std::vector<PairingPoints>& pairing_points, bool handle_edge_cases = true)
137 {
138 size_t num_points = pairing_points.size();
139 BB_ASSERT_GT(num_points, 1UL, "This method should be used only with more than one pairing point.");
140
141 std::vector<Group> first_components;
142 first_components.reserve(num_points);
143 std::vector<Group> second_components;
144 second_components.reserve(num_points);
145 for (const auto& points : pairing_points) {
146 first_components.emplace_back(points.P0());
147 second_components.emplace_back(points.P1());
148 }
149
150 // Fiat-Shamir: hash all points for binding, but only need n-1 challenges
151 StdlibTranscript<Builder> transcript{};
152 std::vector<std::string> labels;
153 labels.reserve(num_points - 1); // Only need n-1 challenges
154 for (size_t idx = 0; auto [first, second] : zip_view(first_components, second_components)) {
155 transcript.add_to_hash_buffer("first_component_" + std::to_string(idx), first);
156 transcript.add_to_hash_buffer("second_component_" + std::to_string(idx), second);
157 // Generate challenges for points 1..n-1 (skip the first point)
158 if (idx > 0) {
159 labels.emplace_back("pp_aggregation_challenge_" + std::to_string(idx));
160 }
161 idx++;
162 }
163
164 std::vector<Fr> challenges = transcript.template get_challenges<Fr>(labels);
165
166 // Aggregate: P_agg = P₀ + r₁·P₁ + r₂·P₂ + ... + rₙ₋₁·Pₙ₋₁
167 Group P0, P1;
168
169 // For MegaCircuitBuilder (Goblin): batch_mul optimizes constant scalar 1 (uses add instead of mul)
170 // so we can include all points in a single batch_mul with scalar [1, r₁, r₂, ..., rₙ₋₁]
171 // For UltraCircuitBuilder: no optimization for witness point × constant(1), so keep first point separate
173 // Single batch_mul for all points (efficient for Goblin with constant scalar 1)
174 std::vector<Fr> scalars;
175 scalars.reserve(num_points);
176 scalars.push_back(Fr(1)); // Optimized by Goblin: add instead of mul
177 scalars.insert(scalars.end(), challenges.begin(), challenges.end());
178
179 P0 = Group::batch_mul(first_components, scalars, 128, handle_edge_cases);
180 P1 = Group::batch_mul(second_components, scalars, 128, handle_edge_cases);
181 } else {
182 // Use first point as base, then batch_mul remaining points
183 std::vector<Group> remaining_first(first_components.begin() + 1, first_components.end());
184 std::vector<Group> remaining_second(second_components.begin() + 1, second_components.end());
185
186 P0 = first_components[0];
187 P1 = second_components[0];
188
189 P0 += Group::batch_mul(remaining_first, challenges, 128, handle_edge_cases);
190 P1 += Group::batch_mul(remaining_second, challenges, 128, handle_edge_cases);
191 }
192
193 PairingPoints aggregated_points(P0, P1);
194
195 // Merge tags
196 Builder* builder = P0.get_context();
197 if (builder != nullptr) {
198 for (const auto& points : pairing_points) {
199 builder->pairing_points_tagging.merge_pairing_point_tags(aggregated_points.tag_index, points.tag_index);
200 }
201 }
202
203 return aggregated_points;
204 }
205
213 void aggregate(PairingPoints const& other)
214 {
215 BB_ASSERT(other.has_data, "Cannot aggregate null pairing points.");
216
217 // If LHS is empty, simply set it equal to the incoming pairing points
218 if (!this->has_data && other.has_data) {
219 *this = other;
220 return;
221 }
222 // We use a Transcript because it provides us an easy way to hash to get a "random" separator.
223 StdlibTranscript<Builder> transcript{};
224 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1375): Sometimes unnecesarily hashing constants
225 transcript.add_to_hash_buffer("Accumulator_P0", P0());
226 transcript.add_to_hash_buffer("Accumulator_P1", P1());
227 transcript.add_to_hash_buffer("Aggregated_P0", other.P0());
228 transcript.add_to_hash_buffer("Aggregated_P1", other.P1());
229 auto recursion_separator =
230 transcript.template get_challenge<typename Curve::ScalarField>("recursion_separator");
231 // If Mega Builder is in use, the EC operations are deferred via Goblin
233 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1385): Can we improve efficiency here?
234 P0() = Group::batch_mul({ P0(), other.P0() }, { 1, recursion_separator });
235 P1() = Group::batch_mul({ P1(), other.P1() }, { 1, recursion_separator });
236 } else {
237 // Save gates using short scalars.
238 Group point_to_aggregate = other.P0().scalar_mul(recursion_separator, 128);
239 P0() += point_to_aggregate;
240 point_to_aggregate = other.P1().scalar_mul(recursion_separator, 128);
241 P1() += point_to_aggregate;
242 }
243
244 // Merge the tags in the builder
245 Builder* builder = P0().get_context();
246 if (builder != nullptr) {
247 builder->pairing_points_tagging.merge_pairing_point_tags(this->tag_index, other.tag_index);
248 }
249
250#ifndef NDEBUG
251 bb::PairingPoints<typename Curve::NativeCurve> native_pp(P0().get_value(), P1().get_value());
252 info("Are aggregated Pairing Points with tag ", tag_index, " valid? ", native_pp.check() ? "true" : "false");
253#endif
254 }
255
260 uint32_t set_public()
261 {
262 BB_ASSERT(this->has_data, "Calling set_public on empty pairing points.");
263 uint32_t start_idx = P0().set_public();
264 P1().set_public();
265 return start_idx;
266 }
267
272 {
273 BB_ASSERT(this->has_data, "Calling fix_witness on empty pairing points.");
274 P0().fix_witness();
275 P1().fix_witness();
276 }
277
282 bool check() const
283 {
284 BB_ASSERT(this->has_data, "Calling check on empty pairing points.");
285 bb::PairingPoints<typename Curve::NativeCurve> native_pp(P0().get_value(), P1().get_value());
286 return native_pp.check();
287 }
288
298 {
299 // Directly add precomputed combined limbs as public inputs, bypassing bigfield's self_reduce.
300 // These values encode the default pairing points in the format used by bigfield::set_public().
301 // Order: P0.x (lo, hi), P0.y (lo, hi), P1.x (lo, hi), P1.y (lo, hi)
302 auto add_fixed_public = [&](const bb::fr& value) {
303 uint32_t idx = builder->add_public_variable(value);
304 builder->fix_witness(idx, value);
305 return idx;
306 };
307
308 uint32_t start_idx = add_fixed_public(DEFAULT_PP_P0_X_LO);
309 add_fixed_public(DEFAULT_PP_P0_X_HI);
310 add_fixed_public(DEFAULT_PP_P0_Y_LO);
311 add_fixed_public(DEFAULT_PP_P0_Y_HI);
312 add_fixed_public(DEFAULT_PP_P1_X_LO);
313 add_fixed_public(DEFAULT_PP_P1_X_HI);
314 add_fixed_public(DEFAULT_PP_P1_Y_LO);
315 add_fixed_public(DEFAULT_PP_P1_Y_HI);
316
317 return start_idx;
318 }
319
324 {
325 Fq P0_x(DEFAULT_PAIRING_POINT_P0_X);
326 Fq P0_y(DEFAULT_PAIRING_POINT_P0_Y);
327 Fq P1_x(DEFAULT_PAIRING_POINT_P1_X);
328 Fq P1_y(DEFAULT_PAIRING_POINT_P1_Y);
329
330 // These are known, valid points, so we can skip the curve checks.
331 Group P0(P0_x, P0_y, /*assert_on_curve=*/false);
332 Group P1(P1_x, P1_y, /*assert_on_curve=*/false);
333
334 return PairingPoints(P0, P1);
335 }
336};
337
338template <typename NCT> std::ostream& operator<<(std::ostream& os, PairingPoints<NCT> const& as)
339{
340 return os << "P0: " << as.P0() << "\n"
341 << "P1: " << as.P1() << "\n"
342 << "has_data: " << as.has_data << "\n"
343 << "tag_index: " << as.tag_index << "\n";
344}
345
346} // namespace bb::stdlib::recursion
347
348// Enable std::tuple_size for Codec compatibility (array-like deserialization)
349namespace std {
350template <typename Curve>
351struct tuple_size<bb::stdlib::recursion::PairingPoints<Curve>> : std::integral_constant<size_t, 2> {};
352} // namespace std
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:113
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.
An object storing two EC points that represent the inputs to a pairing check.
bool check() const
Perform the pairing check.
typename grumpkin::g1 Group
Definition grumpkin.hpp:61
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
std::ostream & operator<<(std::ostream &os, PairingPoints< NCT > const &as)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
field< Bn254FqParams > fq
Definition fq.hpp:169
field< Bn254FrParams > fr
Definition fr.hpp:174
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
StdlibCodec for in-circuit (recursive) verification transcript handling.
An object storing two EC points that represent the inputs to a pairing check.
static constexpr size_t PUBLIC_INPUTS_SIZE
Curve::bool_ct operator==(PairingPoints const &other) const
bool check() const
Perform native pairing check on the witness values.
const auto & operator[](size_t idx) const
static uint32_t set_default_to_public(Builder *builder)
Set the witness indices for the default limbs of the 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)
Compute a linear combination of the present pairing points with an input set of pairing points.
uint32_t set_public()
Set the witness indices for the pairing points to public.
static PairingPoints construct_default()
Construct default pairing points.
void fix_witness()
Record the witness values of pairing points' coordinates in the selectors.
PairingPoints(const Group &p0, const Group &p1)