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
14#include <type_traits>
15
16namespace bb::stdlib::recursion {
17
18static constexpr bb::fq DEFAULT_PAIRING_POINTS_P0_X(
19 "0x031e97a575e9d05a107acb64952ecab75c020998797da7842ab5d6d1986846cf");
20static constexpr bb::fq DEFAULT_PAIRING_POINTS_P0_Y(
21 "0x178cbf4206471d722669117f9758a4c410db10a01750aebb5666547acf8bd5a4");
22static constexpr bb::fq DEFAULT_PAIRING_POINTS_P1_X(
23 "0x0f94656a2ca489889939f81e9c74027fd51009034b3357f0e91b8a11e7842c38");
24static constexpr bb::fq DEFAULT_PAIRING_POINTS_P1_Y(
25 "0x1b52c2020d7464a0c80c0da527a08193fe27776f50224bd6fb128b46c1ddb67f");
26
36template <typename Curve> struct PairingPoints {
37 using Builder = typename Curve::Builder;
43
44 bool has_data = false;
45 uint32_t tag_index = 0; // Index of the tag for tracking pairing point aggregation
46
47 // Number of bb::fr field elements used to represent a goblin element in the public inputs
48 static constexpr size_t PUBLIC_INPUTS_SIZE = PAIRING_POINTS_SIZE;
49
50 PairingPoints() = default;
51
52 PairingPoints(const Group& P0, const Group& P1)
53 : P0(P0)
54 , P1(P1)
55 , has_data(true)
56 {
57 // Get the builder from the group elements and assign a new tag
58 Builder* builder = P0.get_context();
59 if (builder != nullptr) {
60 tag_index = builder->pairing_points_tagging.create_pairing_point_tag();
61 }
62
63#ifndef NDEBUG
64 bb::PairingPoints<typename Curve::NativeCurve> native_pp(P0.get_value(), P1.get_value());
65 info("Are Pairing Points with tag ", tag_index, " valid? ", native_pp.check() ? "true" : "false");
66#endif
67 }
68
70 : PairingPoints(points[0], points[1])
71 {}
72
73 Group& operator[](size_t idx)
74 {
75 BB_ASSERT(idx < 2, "Index out of bounds");
76 return idx == 0 ? P0 : P1;
77 }
78
79 const Group& operator[](size_t idx) const
80 {
81 BB_ASSERT(idx < 2, "Index out of bounds");
82 return idx == 0 ? P0 : P1;
83 }
84
85 typename Curve::bool_ct operator==(PairingPoints const& other) const { return P0 == other.P0 && P1 == other.P1; };
86
104 static PairingPoints aggregate_multiple(std::vector<PairingPoints>& pairing_points, bool handle_edge_cases = true)
105 {
106 size_t num_points = pairing_points.size();
107 BB_ASSERT_GT(num_points, 1UL, "This method should be used only with more than one pairing point.");
108
109 std::vector<Group> first_components;
110 first_components.reserve(num_points);
111 std::vector<Group> second_components;
112 second_components.reserve(num_points);
113 for (const auto& points : pairing_points) {
114 first_components.emplace_back(points.P0);
115 second_components.emplace_back(points.P1);
116 }
117
118 // Fiat-Shamir: hash all points for binding, but only need n-1 challenges
119 StdlibTranscript<Builder> transcript{};
120 std::vector<std::string> labels;
121 labels.reserve(num_points - 1); // Only need n-1 challenges
122 for (size_t idx = 0; auto [first, second] : zip_view(first_components, second_components)) {
123 transcript.add_to_hash_buffer("first_component_" + std::to_string(idx), first);
124 transcript.add_to_hash_buffer("second_component_" + std::to_string(idx), second);
125 // Generate challenges for points 1..n-1 (skip the first point)
126 if (idx > 0) {
127 labels.emplace_back("pp_aggregation_challenge_" + std::to_string(idx));
128 }
129 idx++;
130 }
131
132 std::vector<Fr> challenges = transcript.template get_challenges<Fr>(labels);
133
134 // Aggregate: P_agg = P₀ + r₁·P₁ + r₂·P₂ + ... + rₙ₋₁·Pₙ₋₁
135 Group P0, P1;
136
137 // For MegaCircuitBuilder (Goblin): batch_mul optimizes constant scalar 1 (uses add instead of mul)
138 // so we can include all points in a single batch_mul with scalar [1, r₁, r₂, ..., rₙ₋₁]
139 // For UltraCircuitBuilder: no optimization for witness point × constant(1), so keep first point separate
141 // Single batch_mul for all points (efficient for Goblin with constant scalar 1)
142 std::vector<Fr> scalars;
143 scalars.reserve(num_points);
144 scalars.push_back(Fr(1)); // Optimized by Goblin: add instead of mul
145 scalars.insert(scalars.end(), challenges.begin(), challenges.end());
146
147 P0 = Group::batch_mul(first_components, scalars, 128, handle_edge_cases);
148 P1 = Group::batch_mul(second_components, scalars, 128, handle_edge_cases);
149 } else {
150 // Use first point as base, then batch_mul remaining points
151 std::vector<Group> remaining_first(first_components.begin() + 1, first_components.end());
152 std::vector<Group> remaining_second(second_components.begin() + 1, second_components.end());
153
154 P0 = first_components[0];
155 P1 = second_components[0];
156
157 P0 += Group::batch_mul(remaining_first, challenges, 128, handle_edge_cases);
158 P1 += Group::batch_mul(remaining_second, challenges, 128, handle_edge_cases);
159 }
160
161 PairingPoints aggregated_points(P0, P1);
162
163 // Merge tags
164 Builder* builder = P0.get_context();
165 if (builder != nullptr) {
166 for (const auto& points : pairing_points) {
167 builder->pairing_points_tagging.merge_pairing_point_tags(aggregated_points.tag_index, points.tag_index);
168 }
169 }
170
171 return aggregated_points;
172 }
173
181 void aggregate(PairingPoints const& other)
182 {
183 BB_ASSERT(other.has_data, "Cannot aggregate null pairing points.");
184
185 // If LHS is empty, simply set it equal to the incoming pairing points
186 if (!this->has_data && other.has_data) {
187 *this = other;
188 return;
189 }
190 // We use a Transcript because it provides us an easy way to hash to get a "random" separator.
191 StdlibTranscript<Builder> transcript{};
192 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1375): Sometimes unnecesarily hashing constants
193 transcript.add_to_hash_buffer("Accumulator_P0", P0);
194 transcript.add_to_hash_buffer("Accumulator_P1", P1);
195 transcript.add_to_hash_buffer("Aggregated_P0", other.P0);
196 transcript.add_to_hash_buffer("Aggregated_P1", other.P1);
197 auto recursion_separator =
198 transcript.template get_challenge<typename Curve::ScalarField>("recursion_separator");
199 // If Mega Builder is in use, the EC operations are deferred via Goblin
201 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1385): Can we improve efficiency here?
202 P0 = Group::batch_mul({ P0, other.P0 }, { 1, recursion_separator });
203 P1 = Group::batch_mul({ P1, other.P1 }, { 1, recursion_separator });
204 } else {
205 // Save gates using short scalars.
206 Group point_to_aggregate = other.P0.scalar_mul(recursion_separator, 128);
207 P0 += point_to_aggregate;
208 point_to_aggregate = other.P1.scalar_mul(recursion_separator, 128);
209 P1 += point_to_aggregate;
210 }
211
212 // Merge the tags in the builder
213 Builder* builder = P0.get_context();
214 if (builder != nullptr) {
215 builder->pairing_points_tagging.merge_pairing_point_tags(this->tag_index, other.tag_index);
216 }
217
218#ifndef NDEBUG
219 bb::PairingPoints<typename Curve::NativeCurve> native_pp(P0.get_value(), P1.get_value());
220 info("Are aggregated Pairing Points with tag ", tag_index, " valid? ", native_pp.check() ? "true" : "false");
221#endif
222 }
223
229 uint32_t set_public()
230 {
231 BB_ASSERT(this->has_data, "Calling set_public on empty pairing points.");
232 uint32_t start_idx = P0.set_public();
233 P1.set_public();
234
235 return start_idx;
236 }
237
244 {
245 uint32_t start_idx = 0;
246 for (size_t idx = 0; auto const& coordinate : { DEFAULT_PAIRING_POINTS_P0_X,
247 DEFAULT_PAIRING_POINTS_P0_Y,
248 DEFAULT_PAIRING_POINTS_P1_X,
249 DEFAULT_PAIRING_POINTS_P1_Y }) {
250 bigfield<Builder, bb::Bn254FqParams> bigfield_coordinate(coordinate);
251 bigfield_coordinate.convert_constant_to_fixed_witness(builder);
252 uint32_t index = bigfield_coordinate.set_public();
253 start_idx = idx == 0 ? index : start_idx;
254 idx++;
255 }
256
257 return start_idx;
258 }
259
267 {
268 const size_t FRS_PER_POINT = Group::PUBLIC_INPUTS_SIZE;
269 std::span<const Fr, FRS_PER_POINT> P0_limbs{ limbs.data(), FRS_PER_POINT };
270 std::span<const Fr, FRS_PER_POINT> P1_limbs{ limbs.data() + FRS_PER_POINT, FRS_PER_POINT };
271 Group P0 = Group::reconstruct_from_public(P0_limbs);
272 Group P1 = Group::reconstruct_from_public(P1_limbs);
273 return { P0, P1 };
274 }
275
282 {
283 // TODO(https://github.com/AztecProtocol/barretenberg/issues/911): These are pairing points extracted from a
284 // valid proof. This is a workaround because we can't represent the point at infinity in biggroup yet.
285 Fq x0(DEFAULT_PAIRING_POINTS_P0_X);
286 Fq y0(DEFAULT_PAIRING_POINTS_P0_Y);
287 Fq x1(DEFAULT_PAIRING_POINTS_P1_X);
288 Fq y1(DEFAULT_PAIRING_POINTS_P1_Y);
289
290 // These are known, valid points, so we can skip the curve checks.
291 Group P0(x0, y0, /*assert_on_curve=*/false);
292 Group P1(x1, y1, /*assert_on_curve=*/false);
293
294 return { P0, P1 };
295 }
296};
297
298template <typename NCT> std::ostream& operator<<(std::ostream& os, PairingPoints<NCT> const& as)
299{
300 return os << "P0: " << as.P0 << "\n"
301 << "P1: " << as.P1 << "\n"
302 << "has_data: " << as.has_data << "\n"
303 << "tag_index: " << as.tag_index << "\n";
304}
305
306} // namespace bb::stdlib::recursion
#define BB_ASSERT(expression,...)
Definition assert.hpp:80
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:123
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
void convert_constant_to_fixed_witness(Builder *builder)
Definition bigfield.hpp:661
uint32_t set_public() const
Set the witness indices of the binary basis limbs to public.
Definition bigfield.hpp:737
void info(Args... args)
Definition log.hpp:89
AluTraceBuilder builder
Definition alu.test.cpp:124
std::ostream & operator<<(std::ostream &os, PairingPoints< NCT > const &as)
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 constexpr size_t PUBLIC_INPUTS_SIZE
Curve::bool_ct operator==(PairingPoints const &other) 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 limbs of the pairing points to public.
static PairingPoints< Curve > reconstruct_from_public(const std::span< const Fr, PUBLIC_INPUTS_SIZE > &limbs)
Reconstruct an PairingPoints from its representation as limbs (generally stored in the public inputs)
static PairingPoints construct_default()
Construct default pairing points.
PairingPoints(std::array< Group, 2 > const &points)
const Group & operator[](size_t idx) const
PairingPoints(const Group &P0, const Group &P1)