Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
triple_ipa.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8
18
19#include <array>
20#include <cstddef>
21#include <span>
22#include <string>
23#include <utility>
24#include <vector>
25
26namespace bb {
27
28template <typename Curve_, size_t log_poly_length> class TripleIPA {
29 public:
30 using Curve = Curve_;
31 using Fr = typename Curve::ScalarField;
32 using GroupElement = typename Curve::Element;
38
39 // The native deferred accumulator and its discharge (verify_accumulator / batch_verify_accumulators) are generic
40 // IPA functionality and live in the base IPA; the TripleIPA verifier only *produces* the accumulator (via the
41 // TripleIPA b_0 fold) and delegates the SRS-MSM discharge. Recursive verification likewise reuses the IPA's
42 // in-circuit VerifierAccumulator (IpaAccumulator), deferred up the recursion.
46
47 static constexpr size_t poly_length = 1UL << log_poly_length;
48 static_assert(log_poly_length >= 1, "log_poly_length must be at least 1");
49
50 // The opening combines three tensor families into one IPA claim: eq (F), shifted-eq (F'), pow (P).
51 static constexpr size_t NUM_TENSORS = 3;
52 // One cross-sum per distinct unordered pair of tensors — the off-diagonal terms of <A, b>: (F,sh), (F,P), (sh,P).
53 static constexpr size_t NUM_CROSS_SUMS = NUM_TENSORS * (NUM_TENSORS - 1) / 2;
54
55 private:
57 std::vector<Fr> multilinear_challenge;
62
63 // Expand the symbolic combined tensor into the explicit IPA b-vector
64 // b = eq_weight*eq + shifted_eq_weight*b_sh + powers_weight*pow.
73
74 // Contract the combined tensor b = eq_weight*eq + shifted_eq_weight*b_sh + powers_weight*pow against the IPA
75 // s-vector, reusing each tensor family's own folded evaluation.
76 Fr evaluate_folded(std::span<const Fr> ipa_round_challenges_inv) const
77 {
80 return eq_weight * ShiftedEq::evaluate_eq_folded(point, ipa_round_challenges_inv) +
81 shifted_eq_weight * ShiftedEq::evaluate_folded(point, ipa_round_challenges_inv) +
82 powers_weight * evaluate_pow_folded(univariate_challenge, ipa_round_challenges_inv);
83 }
84
85 private:
86 static Fr evaluate_pow_folded(const Fr& challenge, std::span<const Fr> ipa_round_challenges_inv)
87 {
88 return IPAProtocol::evaluate_challenge_poly(
89 std::vector<Fr>(ipa_round_challenges_inv.begin(), ipa_round_challenges_inv.end()), challenge);
90 }
91 };
92
93 public:
94 // The claim data structs live in `triple_ipa_claim.hpp` (constructed identically by the ECCVM prover and
95 // verifier via `TripleIpaClaimData::create`). Aliased here so external references keep the `TripleIPA::...`
96 // spelling.
100
101 private:
107
108 // The combined claim's evaluation v = <A, b> = sum_k zeta_k^2 * diagonal_k + sum_{j<k} zeta_j zeta_k * cross_{jk},
109 // i.e. the inner product of the zeta-combined witness and tensor reassembled from the per-tensor diagonal
110 // evaluations and the cross-sums.
111 static Fr combined_inner_product(const std::array<Fr, NUM_TENSORS>& diagonal_evaluations,
112 const std::array<Fr, NUM_TENSORS>& zeta,
113 const std::array<Fr, NUM_CROSS_SUMS>& cross_sums)
114 {
115 const std::array<Fr, NUM_TENSORS + NUM_CROSS_SUMS> weights{ zeta[0].sqr(), zeta[1].sqr(),
116 zeta[2].sqr(), zeta[0] * zeta[1],
117 zeta[0] * zeta[2], zeta[1] * zeta[2] };
118 const std::array<Fr, NUM_TENSORS + NUM_CROSS_SUMS> values{ diagonal_evaluations[0], diagonal_evaluations[1],
119 diagonal_evaluations[2], cross_sums[0],
120 cross_sums[1], cross_sums[2] };
121 if constexpr (Curve::is_stdlib_type) {
122 return Fr::mult_madd(
123 std::vector<Fr>(weights.begin(), weights.end()), std::vector<Fr>(values.begin(), values.end()), {});
124 }
125 Fr result = Fr::zero();
126 for (size_t idx = 0; idx < weights.size(); ++idx) {
127 result += weights[idx] * values[idx];
128 }
129 return result;
130 }
131
140 const Polynomial& unshifted_witness,
141 const Polynomial& shifted_witness,
142 const Polynomial& eq_tensor)
143 {
144 const auto& multilinear_challenge = input.claim_data.unshifted.multilinear_challenge;
145 const auto& univariate_challenge = input.claim_data.univariate.opening_pair.challenge;
146
147 const Fr unshifted_against_shift = // ⟨F, b_sh⟩
149 const Fr unshifted_against_pow = unshifted_witness.evaluate(univariate_challenge); // ⟨F, b_pow⟩ = F(x)
150
151 const auto& shifted = input.claim_data.shifted;
152 Fr shifted_against_eq =
153 Fr::zero(); // ⟨F', b_eq⟩ = F'(u) = Σ_j ρ^j · v_{s_j}, reusing the sources' unshifted evals
154 for (size_t idx = 0; idx < shifted.source_unshifted_evaluations.size(); ++idx) {
155 shifted_against_eq += shifted.rho_powers[idx] * shifted.source_unshifted_evaluations[idx];
156 }
157
158 const Fr shifted_against_pow = shifted_witness.evaluate(univariate_challenge); // ⟨F', b_pow⟩ = F'(x)
159 const Fr univariate_against_eq = // ⟨P, b_eq⟩ = P(u)
160 input.univariate_polynomial.evaluate_mle(std::span<const Fr>(multilinear_challenge));
161 const Fr univariate_against_shift = // ⟨P, b_sh⟩
163
164 return { unshifted_against_shift + shifted_against_eq, // c_{F,sh} = ⟨F, b_sh⟩ + ⟨F', b_eq⟩
165 unshifted_against_pow + univariate_against_eq, // c_{F,P} = ⟨F, b_pow⟩ + ⟨P, b_eq⟩
166 shifted_against_pow + univariate_against_shift }; // c_{sh,P} = ⟨F', b_pow⟩ + ⟨P, b_sh⟩
167 }
168
170 const std::array<Fr, NUM_TENSORS>& zeta,
171 const std::array<Fr, NUM_CROSS_SUMS>& cross_sums)
172 {
174 claim.shifted_commitment,
175 claim.univariate.commitment };
176 const std::array<Fr, NUM_TENSORS> diagonal_evaluations{ claim.unshifted_evaluation,
177 claim.shifted_evaluation,
178 claim.univariate.opening_pair.evaluation };
179 return { batch_commitments<Curve>(std::span<const Commitment>(commitments), std::span<const Fr>(zeta)),
180 { .multilinear_challenge = claim.multilinear_challenge,
181 .univariate_challenge = claim.univariate.opening_pair.challenge,
182 .eq_weight = zeta[0],
183 .shifted_eq_weight = zeta[1],
184 .powers_weight = zeta[2] },
185 combined_inner_product(diagonal_evaluations, zeta, cross_sums) };
186 }
187
188 template <typename Transcript>
190 const std::shared_ptr<Transcript>& transcript)
191 {
192 add_claim_to_hash_buffer(claim, transcript);
193
194 const std::array<Fr, NUM_CROSS_SUMS> cross_sums{
195 transcript->template receive_from_prover<Fr>("TripleIPA:cross_F_shift"),
196 transcript->template receive_from_prover<Fr>("TripleIPA:cross_F_P"),
197 transcript->template receive_from_prover<Fr>("TripleIPA:cross_shift_P")
198 };
199 const auto zeta = transcript->template get_challenges<Fr>(
200 std::array<std::string, NUM_TENSORS>{ "TripleIPA:zeta_F", "TripleIPA:zeta_shift", "TripleIPA:zeta_P" });
201 const IpaVerifierClaim combined_claim = combine_into_ipa_claim(claim, zeta, cross_sums);
202 add_claim_to_hash_buffer(combined_claim, transcript);
203 return combined_claim;
204 }
205
206 public:
207 template <typename Transcript>
208 static void compute_opening_proof(const CK& ck,
209 const TripleIpaInput& input,
210 const std::shared_ptr<Transcript>& transcript)
211 {
212 // Stage-1 rho-batching of the commitments and evaluations is shared with the verifier via
213 // `TripleIpaClaimData::batch`, so the values absorbed below (`TripleIPA:F`/`TripleIPA:F_shift`/`TripleIPA:P`)
214 // are byte-identical to the verifier's by construction. The prover additionally batches the witness
215 // polynomials.
216 const TripleIpaClaim claim = input.claim_data.batch();
217
218 // Build the batched unshifted/shifted witnesses F, F' once. They are reused by both the cross-sum
219 // inner products and the final combined witness, so neither stage re-expands over every original source
220 // polynomial.
221 Polynomial unshifted_witness(poly_length);
223 unshifted_witness, input.unshifted_polynomials, std::span<const Fr>(input.claim_data.unshifted.rho_powers));
224
226 shifted_sources.reserve(input.shifted_polynomials.size());
227 for (const auto& polynomial : input.shifted_polynomials) {
228 shifted_sources.push_back(static_cast<PolynomialSpan<const Fr>>(polynomial));
229 }
230 Polynomial shifted_witness(poly_length);
231 add_scaled_batch(shifted_witness,
232 std::span<const PolynomialSpan<const Fr>>(shifted_sources),
233 std::span<const Fr>(input.claim_data.shifted.rho_powers));
234
235 add_claim_to_hash_buffer(claim, transcript);
236
237 // eq(u) is built once and reused by both the cross-sum inner products and the combined b-vector below.
238 const Polynomial eq_tensor =
240
241 const auto cross_sums = compute_cross_sums(input, unshifted_witness, shifted_witness, eq_tensor);
242 transcript->send_to_verifier("TripleIPA:cross_F_shift", cross_sums[0]);
243 transcript->send_to_verifier("TripleIPA:cross_F_P", cross_sums[1]);
244 transcript->send_to_verifier("TripleIPA:cross_shift_P", cross_sums[2]);
245
246 const auto zeta = transcript->template get_challenges<Fr>(
247 std::array<std::string, NUM_TENSORS>{ "TripleIPA:zeta_F", "TripleIPA:zeta_shift", "TripleIPA:zeta_P" });
248 const std::array<PolynomialSpan<const Fr>, NUM_TENSORS> combined_witness_sources{
249 static_cast<PolynomialSpan<const Fr>>(unshifted_witness),
250 static_cast<PolynomialSpan<const Fr>>(shifted_witness),
252 };
253 Polynomial combined_witness(poly_length);
254 add_scaled_batch(combined_witness,
255 std::span<const PolynomialSpan<const Fr>>(combined_witness_sources),
256 std::span<const Fr>(zeta));
257
258 const IpaVerifierClaim combined_claim = combine_into_ipa_claim(claim, zeta, cross_sums);
259 add_claim_to_hash_buffer(combined_claim, transcript);
260 auto b_poly = combined_claim.opening_vector.construct_combined_tensor(eq_tensor);
261 IPAProtocol::compute_inner_product_proof_internal(ck, combined_witness, std::move(b_poly), transcript);
262 }
263
264 // Verification consumes the compact `TripleIpaClaim`; the prover-only `TripleIpaInput` (with witness
265 // polynomials) is for `compute_opening_proof`. Native verification has two entry points: `reduce_to_accumulator`
266 // produces a deferrable accumulator (no SRS-MSM) discharged later by `verify_accumulator` /
267 // `batch_verify_accumulators`, and `reduce_verify` bundles both for the single-proof case. Recursive verification
268 // has one entry point — `reduce_verify` returns an in-circuit accumulator deferred up the recursion.
269 template <typename Transcript>
270 static bool reduce_verify(const VK& vk, const TripleIpaClaim& claim, const std::shared_ptr<Transcript>& transcript)
271 requires(!Curve::is_stdlib_type)
272 {
273 return verify_accumulator(vk, reduce_to_accumulator(claim, transcript));
274 }
275
276 static VerifierAccumulator reduce_verify(const TripleIpaClaim& claim, const auto& transcript)
277 requires(Curve::is_stdlib_type)
278 {
279 const auto combined_claim = compute_ipa_verifier_claim(claim, transcript);
280 return IPAProtocol::reduce_verify_inner_product_recursive(
281 combined_claim.commitment,
282 combined_claim.evaluation,
283 [&](std::span<const Fr> round_challenges_inv) {
284 return combined_claim.opening_vector.evaluate_folded(round_challenges_inv);
285 },
286 transcript);
287 }
288
289 // Native reduction to a deferrable accumulator (no SRS-MSM). Discharge later via verify_accumulator (single)
290 // or batch_verify_accumulators (one combined MSM across many proofs).
291 static NativeAccumulator reduce_to_accumulator(const TripleIpaClaim& claim, const auto& transcript)
292 requires(!Curve::is_stdlib_type)
293 {
294 return reduce_to_accumulator_internal(compute_ipa_verifier_claim(claim, transcript), transcript);
295 }
296
297 // The accumulator discharge (single / batched SRS-MSM) is generic IPA functionality; these thin wrappers keep the
298 // TripleIPA verifier's native lifecycle (reduce_to_accumulator -> discharge) reachable under one type.
299 static bool verify_accumulator(const VK& vk, const NativeAccumulator& accumulator)
300 requires(!Curve::is_stdlib_type)
301 {
302 return IPAProtocol::verify_accumulator(vk, accumulator);
303 }
304
306 requires(!Curve::is_stdlib_type)
307 {
308 return IPAProtocol::batch_verify_accumulators(vk, accumulators);
309 }
310
311 private:
312 template <typename Transcript>
313 static void add_claim_to_hash_buffer(const TripleIpaClaim& claim, const std::shared_ptr<Transcript>& transcript)
314 {
315 transcript->add_to_hash_buffer("TripleIPA:F:commitment", claim.unshifted_commitment);
316 transcript->add_to_hash_buffer("TripleIPA:F:evaluation", claim.unshifted_evaluation);
317 transcript->add_to_hash_buffer("TripleIPA:F_shift:commitment", claim.shifted_commitment);
318 transcript->add_to_hash_buffer("TripleIPA:F_shift:evaluation", claim.shifted_evaluation);
319 transcript->add_to_hash_buffer("TripleIPA:P:commitment", claim.univariate.commitment);
320 transcript->add_to_hash_buffer("TripleIPA:P:evaluation", claim.univariate.opening_pair.evaluation);
321 transcript->add_to_hash_buffer("TripleIPA:P:x", claim.univariate.opening_pair.challenge);
322
323 // u is the shared multilinear point for both the F (eq) and F' (shift) claims; absorb it once.
324 for (size_t coordinate_idx = 0; coordinate_idx < log_poly_length; ++coordinate_idx) {
325 transcript->add_to_hash_buffer("TripleIPA:u_" + std::to_string(coordinate_idx),
326 claim.multilinear_challenge[coordinate_idx]);
327 }
328 }
329
330 template <typename Transcript>
331 static void add_claim_to_hash_buffer(const IpaVerifierClaim& opening_claim,
332 const std::shared_ptr<Transcript>& transcript)
333 {
334 transcript->add_to_hash_buffer("TripleIPA:combined:commitment", opening_claim.commitment);
335 transcript->add_to_hash_buffer("TripleIPA:combined:evaluation", opening_claim.evaluation);
336 }
337
344 const auto& transcript)
345 requires(!Curve::is_stdlib_type)
346 {
347 BB_BENCH_NAME("TripleIPA::reduce_to_accumulator");
348 auto data = IPAProtocol::read_inner_product_transcript_data(
349 opening_claim.commitment,
350 opening_claim.evaluation,
351 [&](std::span<const Fr> round_challenges_inv) {
352 return opening_claim.opening_vector.evaluate_folded(round_challenges_inv);
353 },
354 transcript);
355
356 // IPA group relation using the prover-claimed G_0 (cheap; the SRS-MSM that certifies G_0 is deferred).
357 const Commitment aux_generator = Commitment::one() * data.gen_challenge;
358 const GroupElement right_hand_side =
359 data.G_zero_from_prover * data.a_zero + aux_generator * data.a_zero * data.b_zero;
360 const bool relation_succeeded = data.C_zero.normalize() == right_hand_side.normalize();
361
362 return { std::move(data.round_challenges_inv), data.G_zero_from_prover, relation_succeeded };
363 }
364};
365
366} // namespace bb
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
CommitmentKey object over a pairing group 𝔾₁.
IPA (inner product argument) commitment scheme class.
Definition ipa.hpp:87
static bb::Polynomial< Fr > powers_tensor(const Fr &r)
The power tensor (1, r, r^2, ..., r^{poly_length-1}), the IPA b-vector for a univariate opening.
Definition ipa.hpp:135
Fr evaluate(const Fr &z) const
Fr evaluate_mle(std::span< const Fr > evaluation_points, bool shift=false) const
evaluate multi-linear extension p(X_0,…,X_{n-1}) = \sum_i a_i*L_i(X_0,…,X_{n-1}) at u = (u_0,...
static Polynomial< FF > construct(std::span< const FF > challenges, size_t log_num_monomials)
Construct eq(X, r) coefficient table over Boolean hypercube {0,1}^d.
static FF evaluate_from_eq(const Polynomial &eq, const Polynomial &witness)
static void add_scaled(Polynomial &result, const Polynomial &eq, const FF &scaling_factor)
typename IPAProtocol::NativeAccumulator NativeAccumulator
static NativeAccumulator reduce_to_accumulator(const TripleIpaClaim &claim, const auto &transcript)
typename Curve::Element GroupElement
typename Curve::ScalarField Fr
static bool batch_verify_accumulators(const VK &vk, std::span< const NativeAccumulator > accumulators)
static constexpr size_t NUM_TENSORS
static bool verify_accumulator(const VK &vk, const NativeAccumulator &accumulator)
static void add_claim_to_hash_buffer(const TripleIpaClaim &claim, const std::shared_ptr< Transcript > &transcript)
static bool reduce_verify(const VK &vk, const TripleIpaClaim &claim, const std::shared_ptr< Transcript > &transcript)
static std::array< Fr, NUM_CROSS_SUMS > compute_cross_sums(const TripleIpaInput &input, const Polynomial &unshifted_witness, const Polynomial &shifted_witness, const Polynomial &eq_tensor)
Compute the three cross-sums from already-built batched witnesses F and F'.
typename Curve::AffineElement Commitment
static constexpr size_t NUM_CROSS_SUMS
static NativeAccumulator reduce_to_accumulator_internal(const IpaVerifierClaim &opening_claim, const auto &transcript)
Native reduction to a deferrable accumulator: checks the cheap IPA group relation against the prover-...
static void compute_opening_proof(const CK &ck, const TripleIpaInput &input, const std::shared_ptr< Transcript > &transcript)
static void add_claim_to_hash_buffer(const IpaVerifierClaim &opening_claim, const std::shared_ptr< Transcript > &transcript)
std::conditional_t< Curve::is_stdlib_type, typename IPAProtocol::VerifierAccumulator, NativeAccumulator > VerifierAccumulator
static VerifierAccumulator reduce_verify(const TripleIpaClaim &claim, const auto &transcript)
static Fr combined_inner_product(const std::array< Fr, NUM_TENSORS > &diagonal_evaluations, const std::array< Fr, NUM_TENSORS > &zeta, const std::array< Fr, NUM_CROSS_SUMS > &cross_sums)
static IpaVerifierClaim combine_into_ipa_claim(const TripleIpaClaim &claim, const std::array< Fr, NUM_TENSORS > &zeta, const std::array< Fr, NUM_CROSS_SUMS > &cross_sums)
static constexpr size_t poly_length
static IpaVerifierClaim compute_ipa_verifier_claim(const TripleIpaClaim &claim, const std::shared_ptr< Transcript > &transcript)
Representation of the Grumpkin Verifier Commitment Key inside a bn254 circuit.
typename Group::element Element
Definition grumpkin.hpp:63
static constexpr bool is_stdlib_type
Definition grumpkin.hpp:67
typename Group::affine_element AffineElement
Definition grumpkin.hpp:64
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
void add_scaled_batch(Polynomial< Fr > &dst, std::span< const PolynomialSpan< const Fr > > sources, std::span< const Fr > scalars)
Fused parallel batched add: dst += sum_i scalars[i] * sources[i].
CommitmentKey< Curve > ck
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
std::byte * data
Deferred native IPA verification state — the native counterpart of VerifierAccumulator.
Definition ipa.hpp:108
Fr evaluate_folded(std::span< const Fr > ipa_round_challenges_inv) const
static Fr evaluate_pow_folded(const Fr &challenge, std::span< const Fr > ipa_round_challenges_inv)
std::vector< Fr > multilinear_challenge
Polynomial construct_combined_tensor(const Polynomial &eq) const
Pre-batch verifier-side claim data: per-polynomial commitments/evaluations before Stage-1 rho-batchin...
The compact TripleIPA opening claim: the statement that crosses verifier boundaries.
std::vector< Fr > multilinear_challenge
Commitment unshifted_commitment
OpeningClaim< Curve > univariate
Prover-side TripleIPA input: claim data plus the witness polynomials needed to build the proof.
std::vector< Polynomial > shifted_polynomials
std::vector< Polynomial > unshifted_polynomials
TripleIpaClaimData< Curve > claim_data
static constexpr field zero()
VectorField result