Skip to content

Commit 800c834

Browse files
authored
chore: clean up proof lengths and IPA (#11020)
Closes AztecProtocol/barretenberg#1184. Closes AztecProtocol/barretenberg#1168. Cleans up some ugliness by deduplication and refactoring. Also adds new UltraRollupHonk tests and a new test for checking proof lengths.
1 parent bf10a5c commit 800c834

11 files changed

Lines changed: 194 additions & 117 deletions

File tree

barretenberg/cpp/src/barretenberg/bb/main.cpp

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,10 @@ void prove_tube(const std::string& output_path)
228228
// circuit
229229
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1048): INSECURE - make this tube proof actually use
230230
// these public inputs by turning proof into witnesses and calling set_public on each witness
231-
auto num_public_inputs = static_cast<uint32_t>(static_cast<uint256_t>(proof.mega_proof[1]));
232-
num_public_inputs -= bb::PAIRING_POINT_ACCUMULATOR_SIZE; // don't add the agg object
231+
auto num_inner_public_inputs = static_cast<uint32_t>(static_cast<uint256_t>(proof.mega_proof[1]));
232+
num_inner_public_inputs -= bb::PAIRING_POINT_ACCUMULATOR_SIZE; // don't add the agg object
233233

234-
for (size_t i = 0; i < num_public_inputs; i++) {
234+
for (size_t i = 0; i < num_inner_public_inputs; i++) {
235235
auto offset = bb::HONK_PROOF_PUBLIC_INPUT_OFFSET;
236236
builder->add_public_variable(proof.mega_proof[i + offset]);
237237
}
@@ -278,13 +278,13 @@ void prove_tube(const std::string& output_path)
278278
Verifier tube_verifier(tube_verification_key, ipa_verification_key);
279279

280280
// Break up the tube proof into the honk portion and the ipa portion
281-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1168): Add formula to flavor
282-
const size_t HONK_PROOF_LENGTH = 469;
281+
const size_t HONK_PROOF_LENGTH_WITHOUT_INNER_PUB_INPUTS =
282+
UltraRollupFlavor::PROOF_LENGTH_WITHOUT_PUB_INPUTS + PAIRING_POINT_ACCUMULATOR_SIZE + IPA_CLAIM_SIZE;
283283
// The extra calculation is for the IPA proof length.
284-
ASSERT(tube_proof.size() == HONK_PROOF_LENGTH + 1 + 4 * (CONST_ECCVM_LOG_N) + 2 + 2 + num_public_inputs);
284+
ASSERT(tube_proof.size() == HONK_PROOF_LENGTH_WITHOUT_INNER_PUB_INPUTS + num_inner_public_inputs);
285285
// split out the ipa proof
286-
const std::ptrdiff_t honk_proof_with_pub_inputs_length =
287-
static_cast<std::ptrdiff_t>(HONK_PROOF_LENGTH + num_public_inputs);
286+
const std::ptrdiff_t honk_proof_with_pub_inputs_length = static_cast<std::ptrdiff_t>(
287+
HONK_PROOF_LENGTH_WITHOUT_INNER_PUB_INPUTS - IPA_PROOF_LENGTH + num_inner_public_inputs);
288288
auto ipa_proof = HonkProof(tube_proof.begin() + honk_proof_with_pub_inputs_length, tube_proof.end());
289289
auto tube_honk_proof = HonkProof(tube_proof.begin(), tube_proof.end() + honk_proof_with_pub_inputs_length);
290290
bool verified = tube_verifier.verify_proof(tube_honk_proof, ipa_proof);
@@ -895,15 +895,13 @@ template <IsUltraFlavor Flavor> bool verify_honk(const std::string& proof_path,
895895
bool verified;
896896
if constexpr (HasIPAAccumulator<Flavor>) {
897897
// Break up the tube proof into the honk portion and the ipa portion
898-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1168): Add formula to flavor
899-
const size_t HONK_PROOF_LENGTH = 469;
900-
const size_t num_public_inputs =
901-
static_cast<size_t>(uint64_t(proof[1])) - PAIRING_POINT_ACCUMULATOR_SIZE - IPA_CLAIM_SIZE;
898+
const size_t HONK_PROOF_LENGTH = Flavor::PROOF_LENGTH_WITHOUT_PUB_INPUTS - IPA_PROOF_LENGTH;
899+
const size_t num_public_inputs = static_cast<size_t>(uint64_t(proof[1]));
902900
// The extra calculation is for the IPA proof length.
903901
debug("proof size: ", proof.size());
904902
debug("num public inputs: ", num_public_inputs);
905903
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1182): Move to ProofSurgeon.
906-
ASSERT(proof.size() == HONK_PROOF_LENGTH + 1 + 4 * (CONST_ECCVM_LOG_N) + 2 + 2 + num_public_inputs);
904+
ASSERT(proof.size() == HONK_PROOF_LENGTH + IPA_PROOF_LENGTH + num_public_inputs);
907905
// split out the ipa proof
908906
const std::ptrdiff_t honk_proof_with_pub_inputs_length =
909907
static_cast<std::ptrdiff_t>(HONK_PROOF_LENGTH + num_public_inputs);

barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
#include "barretenberg/common/container.hpp"
77
#include "barretenberg/common/thread.hpp"
88
#include "barretenberg/common/throw_or_abort.hpp"
9+
#include "barretenberg/constants.hpp"
910
#include "barretenberg/ecc/scalar_multiplication/scalar_multiplication.hpp"
1011
#include "barretenberg/stdlib/hash/poseidon2/poseidon2.hpp"
1112
#include "barretenberg/stdlib/honk_verifier/ipa_accumulator.hpp"
13+
#include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp"
1214
#include "barretenberg/stdlib/transcript/transcript.hpp"
1315
#include "barretenberg/transcript/transcript.hpp"
1416
#include <cstddef>
@@ -20,6 +22,8 @@
2022
namespace bb {
2123
// clang-format off
2224

25+
constexpr size_t IPA_PROOF_LENGTH = 1 + 4 * CONST_ECCVM_LOG_N + 2 + 2;
26+
2327
/**
2428
* @brief IPA (inner product argument) commitment scheme class.
2529
*
@@ -949,6 +953,32 @@ template <typename Curve_> class IPA {
949953
output_claim.opening_pair.evaluation.self_reduce();
950954
return {output_claim, prover_transcript->proof_data};
951955
}
956+
957+
static std::pair<OpeningClaim<Curve>, HonkProof> create_fake_ipa_claim_and_proof(UltraCircuitBuilder& builder)
958+
requires Curve::is_stdlib_type {
959+
using NativeCurve = curve::Grumpkin;
960+
using Builder = typename Curve::Builder;
961+
using Curve = stdlib::grumpkin<Builder>;
962+
auto ipa_transcript = std::make_shared<NativeTranscript>();
963+
auto ipa_commitment_key = std::make_shared<CommitmentKey<NativeCurve>>(1 << CONST_ECCVM_LOG_N);
964+
size_t n = 4;
965+
auto poly = Polynomial<fq>(n);
966+
for (size_t i = 0; i < n; i++) {
967+
poly.at(i) = fq::random_element();
968+
}
969+
fq x = fq::random_element();
970+
fq eval = poly.evaluate(x);
971+
auto commitment = ipa_commitment_key->commit(poly);
972+
const OpeningPair<NativeCurve> opening_pair = { x, eval };
973+
IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
974+
975+
auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
976+
auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
977+
auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
978+
OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
979+
980+
return {stdlib_opening_claim, ipa_transcript->export_proof()};
981+
}
952982
};
953983

954984
} // namespace bb

barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -439,31 +439,13 @@ HonkRecursionConstraintsOutput<Builder> process_honk_recursion_constraints(
439439
} else if (nested_ipa_claims.size() > 2) {
440440
throw_or_abort("Too many nested IPA claims to accumulate");
441441
} else {
442-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1184): Move to IPA class.
443442
if (honk_recursion == 2) {
444443
info("Proving with UltraRollupHonk but no IPA claims exist.");
445-
// just create some fake IPA claim and proof
446-
using NativeCurve = curve::Grumpkin;
447-
using Curve = stdlib::grumpkin<Builder>;
448-
auto ipa_transcript = std::make_shared<NativeTranscript>();
449-
auto ipa_commitment_key = std::make_shared<CommitmentKey<NativeCurve>>(1 << CONST_ECCVM_LOG_N);
450-
size_t n = 4;
451-
auto poly = Polynomial<fq>(n);
452-
for (size_t i = 0; i < n; i++) {
453-
poly.at(i) = fq::random_element();
454-
}
455-
fq x = fq::random_element();
456-
fq eval = poly.evaluate(x);
457-
auto commitment = ipa_commitment_key->commit(poly);
458-
const OpeningPair<NativeCurve> opening_pair = { x, eval };
459-
IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
460-
461-
auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
462-
auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
463-
auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
464-
OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
444+
auto [stdlib_opening_claim, ipa_proof] =
445+
IPA<stdlib::grumpkin<Builder>>::create_fake_ipa_claim_and_proof(builder);
446+
465447
output.ipa_claim = stdlib_opening_claim;
466-
output.ipa_proof = ipa_transcript->export_proof();
448+
output.ipa_proof = ipa_proof;
467449
}
468450
}
469451
output.agg_obj_indices = current_aggregation_object;

barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,9 @@ void create_dummy_vkey_and_proof(Builder& builder,
4242
const std::vector<field_ct>& proof_fields)
4343
{
4444
// Set vkey->circuit_size correctly based on the proof size
45-
size_t num_frs_comm = bb::field_conversion::calc_num_bn254_frs<typename Flavor::Commitment>();
46-
size_t num_frs_fr = bb::field_conversion::calc_num_bn254_frs<typename Flavor::FF>();
47-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1168): Add formula to flavor
48-
assert((proof_size - bb::HONK_PROOF_PUBLIC_INPUT_OFFSET - Flavor::NUM_WITNESS_ENTITIES * num_frs_comm -
49-
Flavor::NUM_ALL_ENTITIES * num_frs_fr - num_frs_comm) %
50-
(num_frs_comm + num_frs_fr * (Flavor::BATCHED_RELATION_PARTIAL_LENGTH + 1)) ==
51-
0);
45+
ASSERT(proof_size == Flavor::PROOF_LENGTH_WITHOUT_PUB_INPUTS);
5246
// Note: this computation should always result in log_circuit_size = CONST_PROOF_SIZE_LOG_N
53-
auto log_circuit_size =
54-
(proof_size - bb::HONK_PROOF_PUBLIC_INPUT_OFFSET - Flavor::NUM_WITNESS_ENTITIES * num_frs_comm -
55-
Flavor::NUM_ALL_ENTITIES * num_frs_fr - num_frs_comm) /
56-
(num_frs_comm + num_frs_fr * (Flavor::BATCHED_RELATION_PARTIAL_LENGTH + 1));
47+
auto log_circuit_size = CONST_PROOF_SIZE_LOG_N;
5748
// First key field is circuit size
5849
builder.assert_equal(builder.add_variable(1 << log_circuit_size), key_fields[0].witness_index);
5950
// Second key field is number of public inputs

barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,14 @@ UltraRecursiveVerifier_<Flavor>::Output UltraRecursiveVerifier_<Flavor>::verify_
4949
Output output;
5050
StdlibProof<Builder> honk_proof;
5151
if constexpr (HasIPAAccumulator<Flavor>) {
52-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1168): Add formula to flavor
53-
const size_t HONK_PROOF_LENGTH = 469;
52+
const size_t HONK_PROOF_LENGTH = Flavor::NativeFlavor::PROOF_LENGTH_WITHOUT_PUB_INPUTS - IPA_PROOF_LENGTH;
5453
const size_t num_public_inputs = static_cast<uint32_t>(proof[1].get_value());
5554
// The extra calculation is for the IPA proof length.
5655
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1182): Handle in ProofSurgeon.
57-
ASSERT(proof.size() == HONK_PROOF_LENGTH + (1 + 4 * (CONST_ECCVM_LOG_N) + 2 + 2) + num_public_inputs -
58-
(PAIRING_POINT_ACCUMULATOR_SIZE + IPA_CLAIM_SIZE));
56+
ASSERT(proof.size() == HONK_PROOF_LENGTH + IPA_PROOF_LENGTH + num_public_inputs);
5957
// split out the ipa proof
60-
const std::ptrdiff_t honk_proof_with_pub_inputs_length = static_cast<std::ptrdiff_t>(
61-
HONK_PROOF_LENGTH + num_public_inputs - (PAIRING_POINT_ACCUMULATOR_SIZE + IPA_CLAIM_SIZE));
58+
const std::ptrdiff_t honk_proof_with_pub_inputs_length =
59+
static_cast<std::ptrdiff_t>(HONK_PROOF_LENGTH + num_public_inputs);
6260
output.ipa_proof = StdlibProof<Builder>(proof.begin() + honk_proof_with_pub_inputs_length, proof.end());
6361
honk_proof = StdlibProof<Builder>(proof.begin(), proof.end() + honk_proof_with_pub_inputs_length);
6462
} else {

barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,29 +77,11 @@ template <typename RecursiveFlavor> class RecursiveVerifierTest : public testing
7777
PairingPointAccumulatorIndices agg_obj_indices = stdlib::recursion::init_default_agg_obj_indices(builder);
7878
builder.add_pairing_point_accumulator(agg_obj_indices);
7979

80-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1184): Move to IPA class.
8180
if constexpr (HasIPAAccumulator<RecursiveFlavor>) {
82-
using NativeCurve = curve::Grumpkin;
83-
using Curve = stdlib::grumpkin<InnerBuilder>;
84-
auto ipa_transcript = std::make_shared<NativeTranscript>();
85-
auto ipa_commitment_key = std::make_shared<CommitmentKey<NativeCurve>>(1 << CONST_ECCVM_LOG_N);
86-
size_t n = 4;
87-
auto poly = Polynomial<fq>(n);
88-
for (size_t i = 0; i < n; i++) {
89-
poly.at(i) = fq::random_element();
90-
}
91-
fq x = fq::random_element();
92-
fq eval = poly.evaluate(x);
93-
auto commitment = ipa_commitment_key->commit(poly);
94-
const OpeningPair<NativeCurve> opening_pair = { x, eval };
95-
IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
96-
97-
auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
98-
auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
99-
auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
100-
OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
81+
auto [stdlib_opening_claim, ipa_proof] =
82+
IPA<grumpkin<InnerBuilder>>::create_fake_ipa_claim_and_proof(builder);
10183
builder.add_ipa_claim(stdlib_opening_claim.get_witness_indices());
102-
builder.ipa_proof = ipa_transcript->export_proof();
84+
builder.ipa_proof = ipa_proof;
10385
}
10486
return builder;
10587
};

barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_flavor.hpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ class UltraFlavor {
6767
// Note: made generic for use in MegaRecursive.
6868
template <typename FF>
6969

70-
// List of relations reflecting the Ultra arithmetisation. WARNING: As UltraKeccak flavor inherits from Ultra flavor
71-
// any change of ordering in this tuple needs to be reflected in the smart contract, otherwise relation accumulation
72-
// will not match.
70+
// List of relations reflecting the Ultra arithmetisation. WARNING: As UltraKeccak flavor inherits from
71+
// Ultra flavor any change of ordering in this tuple needs to be reflected in the smart contract, otherwise
72+
// relation accumulation will not match.
7373
using Relations_ = std::tuple<bb::UltraArithmeticRelation<FF>,
7474
bb::UltraPermutationRelation<FF>,
7575
bb::LogDerivLookupRelation<FF>,
@@ -97,6 +97,22 @@ class UltraFlavor {
9797
static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = MAX_PARTIAL_RELATION_LENGTH + 1;
9898
static constexpr size_t NUM_RELATIONS = std::tuple_size_v<Relations>;
9999

100+
// Proof length formula:
101+
// 1. HONK_PROOF_PUBLIC_INPUT_OFFSET are the circuit_size, num_public_inputs, pub_inputs_offset
102+
// 2. PAIRING_POINT_ACCUMULATOR_SIZE public inputs for pairing point accumulator
103+
// 3. NUM_WITNESS_ENTITIES commitments
104+
// 4. CONST_PROOF_SIZE_LOG_N sumcheck univariates
105+
// 5. NUM_ALL_ENTITIES sumcheck evaluations
106+
// 6. CONST_PROOF_SIZE_LOG_N Gemini Fold commitments
107+
// 7. CONST_PROOF_SIZE_LOG_N Gemini a evaluations
108+
// 8. KZG W commitment
109+
static constexpr size_t num_frs_comm = bb::field_conversion::calc_num_bn254_frs<Commitment>();
110+
static constexpr size_t num_frs_fr = bb::field_conversion::calc_num_bn254_frs<FF>();
111+
static constexpr size_t PROOF_LENGTH_WITHOUT_PUB_INPUTS =
112+
HONK_PROOF_PUBLIC_INPUT_OFFSET + NUM_WITNESS_ENTITIES * num_frs_comm +
113+
CONST_PROOF_SIZE_LOG_N * BATCHED_RELATION_PARTIAL_LENGTH * num_frs_fr + NUM_ALL_ENTITIES * num_frs_fr +
114+
CONST_PROOF_SIZE_LOG_N * num_frs_comm + CONST_PROOF_SIZE_LOG_N * num_frs_fr + num_frs_comm;
115+
100116
template <size_t NUM_KEYS>
101117
using ProtogalaxyTupleOfTuplesOfUnivariatesNoOptimisticSkipping =
102118
decltype(create_protogalaxy_tuple_of_tuples_of_univariates<Relations, NUM_KEYS>());
@@ -537,7 +553,6 @@ class UltraFlavor {
537553
* @brief A container for storing the partially evaluated multivariates produced by sumcheck.
538554
*/
539555
class PartiallyEvaluatedMultivariates : public AllEntities<Polynomial> {
540-
541556
public:
542557
PartiallyEvaluatedMultivariates() = default;
543558
PartiallyEvaluatedMultivariates(const size_t circuit_size)
@@ -675,7 +690,7 @@ class UltraFlavor {
675690
this->z_perm = commitments.z_perm;
676691
}
677692
}
678-
};
693+
}; // namespace bb
679694
// Specialize for Ultra (general case used in UltraRecursive).
680695
using VerifierCommitments = VerifierCommitments_<Commitment, VerificationKey>;
681696

barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_rollup_flavor.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
#pragma once
2+
#include "barretenberg/commitment_schemes/ipa/ipa.hpp"
23
#include "barretenberg/stdlib_circuit_builders/ultra_flavor.hpp"
34

45
namespace bb {
56

67
class UltraRollupFlavor : public bb::UltraFlavor {
78
public:
9+
// Proof length formula:
10+
// 1. HONK_PROOF_PUBLIC_INPUT_OFFSET are the circuit_size, num_public_inputs, pub_inputs_offset
11+
// 2. PAIRING_POINT_ACCUMULATOR_SIZE public inputs for pairing point accumulator
12+
// 3. IPA_CLAIM_SIZE public inputs for IPA claim
13+
// 4. NUM_WITNESS_ENTITIES commitments
14+
// 5. CONST_PROOF_SIZE_LOG_N sumcheck univariates
15+
// 6. NUM_ALL_ENTITIES sumcheck evaluations
16+
// 7. CONST_PROOF_SIZE_LOG_N Gemini Fold commitments
17+
// 8. CONST_PROOF_SIZE_LOG_N Gemini a evaluations
18+
// 9. KZG W commitment
19+
static constexpr size_t num_frs_comm = bb::field_conversion::calc_num_bn254_frs<Commitment>();
20+
static constexpr size_t num_frs_fr = bb::field_conversion::calc_num_bn254_frs<FF>();
21+
static constexpr size_t PROOF_LENGTH_WITHOUT_PUB_INPUTS =
22+
UltraFlavor::PROOF_LENGTH_WITHOUT_PUB_INPUTS + IPA_PROOF_LENGTH;
23+
824
using UltraFlavor::UltraFlavor;
925
class ProvingKey : public UltraFlavor::ProvingKey {
1026
public:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
barretenberg_module(ultra_honk sumcheck)
1+
barretenberg_module(ultra_honk sumcheck stdlib_primitives)

barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ template <IsUltraFlavor Flavor> HonkProof UltraProver_<Flavor>::export_proof()
4747
// Add the IPA proof
4848
if constexpr (HasIPAAccumulator<Flavor>) {
4949
// The extra calculation is for the IPA proof length.
50-
ASSERT(proving_key->proving_key.ipa_proof.size() == 1 + 4 * (CONST_ECCVM_LOG_N) + 2 + 2);
50+
ASSERT(proving_key->proving_key.ipa_proof.size() == IPA_PROOF_LENGTH);
5151
proof.insert(proof.end(), proving_key->proving_key.ipa_proof.begin(), proving_key->proving_key.ipa_proof.end());
5252
}
5353
return proof;

0 commit comments

Comments
 (0)