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: complete, auditors: [Luke], 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
26template <typename Curve> struct PairingPoints {
27 using Builder = typename Curve::Builder;
31
32 // Number of bb::fr field elements used to represent pairing points in public inputs
33 static constexpr size_t PUBLIC_INPUTS_SIZE = PAIRING_POINTS_SIZE;
34
35 uint32_t tag_index = 0; // Index of the tag for tracking pairing point aggregation
36
37 const Group& P0() const { return _points[0]; }
38 const Group& P1() const { return _points[1]; }
39
40 bool is_populated() const { return has_data_; }
41 bool is_default() const { return is_default_; }
42
43 PairingPoints() = default;
44
45 PairingPoints(const Group& p0, const Group& p1)
46 : _points{ p0, p1 }
47 , has_data_(true)
48 {
49 Builder* builder = validate_context<Builder>(p0.get_context(), p1.get_context());
50 if (builder != nullptr) {
51 tag_index = builder->pairing_points_tagging.create_pairing_point_tag();
52 }
53
54#ifndef NDEBUG
55 bb::PairingPoints<typename Curve::NativeCurve> native_pp(P0().get_value(), P1().get_value());
56 info("Are Pairing Points with tag ", tag_index, " valid? ", native_pp.check() ? "true" : "false");
57#endif
58 }
59
64 const std::span<const stdlib::field_t<Builder>, PUBLIC_INPUTS_SIZE>& limbs)
65 {
67 constexpr size_t GROUP_SIZE = Codec::template calc_num_fields<Group>();
68 Group p0 = Codec::template deserialize_from_fields<Group>(limbs.template subspan<0, GROUP_SIZE>());
69 Group p1 = Codec::template deserialize_from_fields<Group>(limbs.template subspan<GROUP_SIZE, GROUP_SIZE>());
70 return PairingPoints(p0, p1);
71 }
72
73 // Iterator support (used by validate_context to extract Builder* from the contained group elements)
74 auto begin() { return _points.begin(); }
75 auto end() { return _points.end(); }
76 auto begin() const { return _points.begin(); }
77 auto end() const { return _points.end(); }
78
94 static PairingPoints aggregate_multiple(std::vector<PairingPoints>& pairing_points, bool handle_edge_cases = true)
95 {
96 size_t num_points = pairing_points.size();
97 BB_ASSERT_GT(num_points, 0UL, "Must provide at least one PairingPoints for aggregation");
98 for (const auto& points : pairing_points) {
99 BB_ASSERT(points.has_data_, "Cannot aggregate null pairing points.");
100 }
101 if (num_points == 1) {
102 return pairing_points[0];
103 }
104
105 std::vector<Group> first_components;
106 first_components.reserve(num_points);
107 std::vector<Group> second_components;
108 second_components.reserve(num_points);
109 for (const auto& points : pairing_points) {
110 first_components.emplace_back(points.P0());
111 second_components.emplace_back(points.P1());
112 }
113
114 // Fiat-Shamir: hash all points for binding, but only need n-1 challenges
115 StdlibTranscript<Builder> transcript{};
116 std::vector<std::string> labels;
117 labels.reserve(num_points - 1); // Only need n-1 challenges
118 for (size_t idx = 0; auto [first, second] : zip_view(first_components, second_components)) {
119 transcript.add_to_hash_buffer("first_component_" + std::to_string(idx), first);
120 transcript.add_to_hash_buffer("second_component_" + std::to_string(idx), second);
121 // Generate challenges for points 1..n-1 (skip the first point)
122 if (idx > 0) {
123 labels.emplace_back("pp_aggregation_challenge_" + std::to_string(idx));
124 }
125 idx++;
126 }
127
128 std::vector<Fr> challenges = transcript.template get_short_challenges<Fr>(labels);
129
130 // Aggregate: P_agg = P₀ + r₁·P₁ + r₂·P₂ + ... + rₙ₋₁·Pₙ₋₁
131 Group P0;
132 Group P1;
133
134 // For MegaCircuitBuilder (Goblin): batch_mul optimizes constant scalar 1 (uses add instead of mul)
135 // so we can include all points in a single batch_mul with scalar [1, r₁, r₂, ..., rₙ₋₁]
136 // For UltraCircuitBuilder: no optimization for witness point × constant(1), so keep first point separate
138 // Single batch_mul for all points (efficient for Goblin with constant scalar 1)
139 std::vector<Fr> scalars;
140 scalars.reserve(num_points);
141 scalars.push_back(Fr(1)); // Optimized by Goblin: add instead of mul
142 scalars.insert(scalars.end(), challenges.begin(), challenges.end());
143
144 P0 = Group::batch_mul(first_components, scalars);
145 P1 = Group::batch_mul(second_components, scalars);
146 } else {
147 // Use first point as base, then batch_mul remaining points
148 std::vector<Group> remaining_first(first_components.begin() + 1, first_components.end());
149 std::vector<Group> remaining_second(second_components.begin() + 1, second_components.end());
150
151 P0 = first_components[0];
152 P1 = second_components[0];
153
154 P0 += Group::batch_mul(remaining_first, challenges, 128, handle_edge_cases);
155 P1 += Group::batch_mul(remaining_second, challenges, 128, handle_edge_cases);
156 }
157
158 PairingPoints aggregated_points(P0, P1);
159
160 // Merge tags
161 Builder* builder = P0.get_context();
162 if (builder != nullptr) {
163 for (const auto& points : pairing_points) {
164 builder->pairing_points_tagging.merge_pairing_point_tags(aggregated_points.tag_index, points.tag_index);
165 }
166 }
167
168 return aggregated_points;
169 }
170
178 void aggregate(PairingPoints const& other)
179 {
180 BB_ASSERT(other.has_data_, "Cannot aggregate null pairing points.");
181
182 // If LHS is empty, simply set it equal to the incoming pairing points
183 if (!this->has_data_ && other.has_data_) {
184 *this = other;
185 return;
186 }
187 // Use transcript to hash all four points to derive a binding challenge
188 StdlibTranscript<Builder> transcript{};
189 transcript.add_to_hash_buffer("Accumulator_P0", P0());
190 transcript.add_to_hash_buffer("Accumulator_P1", P1());
191 transcript.add_to_hash_buffer("Aggregated_P0", other.P0());
192 transcript.add_to_hash_buffer("Aggregated_P1", other.P1());
193 // Short challenge: scales `other`'s points in a (Goblin / 128-bit) batch_mul below.
194 auto recursion_separator =
195 transcript.template get_short_challenge<typename Curve::ScalarField>("recursion_separator");
196 is_default_ = false; // After aggregation, points are no longer default
197 // If Mega Builder is in use, the EC operations are deferred via Goblin.
198 // batch_mul with constant scalar 1 is optimal here (Goblin uses add instead of mul).
200 // Goblin: batch_mul with constant scalar 1 uses add instead of mul
201 _points[0] = Group::batch_mul({ P0(), other.P0() }, { 1, recursion_separator });
202 _points[1] = Group::batch_mul({ P1(), other.P1() }, { 1, recursion_separator });
203 } else {
204 // Ultra: 128-bit scalar mul to save gates
205 Group point_to_aggregate = other.P0().scalar_mul(recursion_separator, 128);
206 _points[0] += point_to_aggregate;
207 point_to_aggregate = other.P1().scalar_mul(recursion_separator, 128);
208 _points[1] += point_to_aggregate;
209 }
210
211 // Merge the tags in the builder
212 Builder* builder = P0().get_context();
213 if (builder != nullptr) {
214 builder->pairing_points_tagging.merge_pairing_point_tags(this->tag_index, other.tag_index);
215 }
216
217#ifndef NDEBUG
218 bb::PairingPoints<typename Curve::NativeCurve> native_pp(P0().get_value(), P1().get_value());
219 info("Are aggregated Pairing Points with tag ", tag_index, " valid? ", native_pp.check() ? "true" : "false");
220#endif
221 }
222
230 uint32_t set_public(Builder* ctx = nullptr)
231 {
232 BB_ASSERT(this->has_data_, "Calling set_public on empty pairing points.");
233 if (is_default_) {
234 Builder* builder = validate_context<Builder>(ctx, P0().get_context(), P1().get_context());
235 BB_ASSERT(builder != nullptr, "set_public on default pairing points requires a builder context.");
237 }
238 Builder* builder = validate_context<Builder>(ctx, P0().get_context(), P1().get_context());
239 BB_ASSERT(builder != nullptr, "set_public on pairing points requires a builder context.");
240 builder->pairing_points_tagging.set_public_pairing_points();
241 uint32_t start_idx = P0().set_public();
242 P1().set_public();
243 return start_idx;
244 }
245
250 {
251 BB_ASSERT(this->has_data_, "Calling fix_witness on empty pairing points.");
252 _points[0].fix_witness();
253 _points[1].fix_witness();
254 }
255
260 bool check() const
261 {
262 BB_ASSERT(this->has_data_, "Calling check on empty pairing points.");
263 bb::PairingPoints<typename Curve::NativeCurve> native_pp(P0().get_value(), P1().get_value());
264 return native_pp.check();
265 }
266
275 {
276 builder->pairing_points_tagging.set_public_pairing_points();
277 // Infinity is represented as (0,0) in biggroup. Directly add zero limbs as public inputs, bypassing bigfield's
278 // self_reduce.
279 uint32_t start_idx = static_cast<uint32_t>(builder->num_public_inputs());
280 for (size_t i = 0; i < PUBLIC_INPUTS_SIZE; i++) {
281 uint32_t idx = builder->add_public_variable(bb::fr(0));
282 builder->fix_witness(idx, bb::fr(0));
283 }
284 return start_idx;
285 }
286
292 {
293 Group P0(Fq(0), Fq(0), /*assert_on_curve=*/false);
294 Group P1(Fq(0), Fq(0), /*assert_on_curve=*/false);
295 PairingPoints pp(P0, P1);
296 pp.is_default_ = true;
297 return pp;
298 }
299
300 private:
301 std::array<Group, 2> _points;
302 bool has_data_ = false;
303 bool is_default_ = false; // True for default (infinity) pairing points from construct_default()
304};
305
306template <typename NCT> std::ostream& operator<<(std::ostream& os, PairingPoints<NCT> const& as)
307{
308 return os << "P0: " << as.P0() << "\n"
309 << "P1: " << as.P1() << "\n"
310 << "is_populated: " << as.is_populated() << "\n"
311 << "tag_index: " << as.tag_index << "\n";
312}
313
314} // namespace bb::stdlib::recursion
#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
Verify the pairing equation e(P0, [1]₂) · e(P1, [x]₂) = 1.
typename grumpkin::g1 Group
Definition grumpkin.hpp:62
#define info(...)
Definition log.hpp:93
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)
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
bool check() const
Perform native pairing check on the witness values.
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.
static PairingPoints reconstruct_from_public(const std::span< const stdlib::field_t< Builder >, PUBLIC_INPUTS_SIZE > &limbs)
Reconstruct PairingPoints from public input limbs.
void aggregate(PairingPoints const &other)
Aggregate another PairingPoints into this one via random linear combination.
static PairingPoints construct_default()
Construct default pairing points (both at infinity).
void fix_witness()
Record the witness values of pairing points' coordinates in the selectors.
uint32_t set_public(Builder *ctx=nullptr)
Set the witness indices for the pairing points to public.
PairingPoints(const Group &p0, const Group &p1)