Skip to content

Commit 57280d7

Browse files
authored
chore: minor fixes pt. 3 (#22181)
triaging relations findings
1 parent da8cd88 commit 57280d7

13 files changed

Lines changed: 156 additions & 66 deletions

File tree

barretenberg/cpp/src/barretenberg/bbapi/bbapi.test.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#include "barretenberg/bbapi/bbapi.hpp"
2+
#include "barretenberg/api/file_io.hpp"
3+
#include "barretenberg/bbapi/bbapi_shared.hpp"
24
#include "barretenberg/common/serialize.hpp"
35
#include "barretenberg/common/utils.hpp"
46
#include "barretenberg/serialize/test_helper.hpp"
@@ -42,3 +44,72 @@ TYPED_TEST(BBApiMsgpack, DefaultConstructorRoundtrip)
4244
EXPECT_EQ(actual_response, expected_response);
4345
std::cout << msgpack_schema_to_string(command) << " " << msgpack_schema_to_string(response) << std::endl;
4446
}
47+
48+
// Regression tests for input validation at API boundaries.
49+
// These ensure non-canonical field encodings and trailing bytes are rejected.
50+
51+
TEST(BBApiInputValidation, NonCanonicalPublicInputRejected)
52+
{
53+
using Flavor = UltraFlavor;
54+
// A value >= BN254 scalar field modulus should be rejected
55+
uint256_t non_canonical = fr::modulus + 1;
56+
std::vector<uint256_t> bad_public_inputs = { non_canonical };
57+
std::vector<uint256_t> proof = { uint256_t(0) };
58+
59+
EXPECT_THROW(bbapi::concatenate_proof<Flavor>(bad_public_inputs, proof), std::runtime_error);
60+
}
61+
62+
TEST(BBApiInputValidation, NonCanonicalProofElementRejected)
63+
{
64+
using Flavor = UltraFlavor;
65+
// The modulus itself is non-canonical (valid range is [0, modulus))
66+
uint256_t non_canonical = fr::modulus;
67+
std::vector<uint256_t> public_inputs = { uint256_t(42) };
68+
std::vector<uint256_t> bad_proof = { non_canonical };
69+
70+
EXPECT_THROW(bbapi::concatenate_proof<Flavor>(public_inputs, bad_proof), std::runtime_error);
71+
}
72+
73+
TEST(BBApiInputValidation, CanonicalValuesAccepted)
74+
{
75+
using Flavor = UltraFlavor;
76+
// modulus - 1 is the largest canonical value
77+
uint256_t max_canonical = fr::modulus - 1;
78+
std::vector<uint256_t> public_inputs = { uint256_t(0), max_canonical };
79+
std::vector<uint256_t> proof = { uint256_t(1) };
80+
81+
EXPECT_NO_THROW(bbapi::concatenate_proof<Flavor>(public_inputs, proof));
82+
}
83+
84+
TEST(BBApiInputValidation, TrailingBytesInBinaryInputRejected)
85+
{
86+
// A buffer that is not a multiple of 32 bytes should be rejected
87+
std::vector<uint8_t> buf(32 + 1, 0); // 33 bytes = 1 field element + 1 trailing byte
88+
EXPECT_THROW(many_from_buffer_exact<uint256_t>(buf, "test input"), std::runtime_error);
89+
}
90+
91+
TEST(BBApiInputValidation, ExactBinaryInputAccepted)
92+
{
93+
// A buffer that is exactly 2 field elements should parse fine
94+
std::vector<uint8_t> buf(64, 0);
95+
EXPECT_NO_THROW(many_from_buffer_exact<uint256_t>(buf, "test input"));
96+
auto result = many_from_buffer_exact<uint256_t>(buf, "test input");
97+
EXPECT_EQ(result.size(), 2UL);
98+
}
99+
100+
TEST(BBApiInputValidation, VkWithTrailingBytesRejectedOnProveSide)
101+
{
102+
using VK = UltraFlavor::VerificationKey;
103+
const size_t expected_size = VK::calc_num_data_types() * sizeof(bb::fr);
104+
// One extra byte beyond the expected VK size
105+
std::vector<uint8_t> bad_vk(expected_size + 1, 0);
106+
EXPECT_THROW(bbapi::validate_vk_size<VK>(bad_vk), std::runtime_error);
107+
}
108+
109+
TEST(BBApiInputValidation, VkWithCorrectSizeAccepted)
110+
{
111+
using VK = UltraFlavor::VerificationKey;
112+
const size_t expected_size = VK::calc_num_data_types() * sizeof(bb::fr);
113+
std::vector<uint8_t> good_vk(expected_size, 0);
114+
EXPECT_NO_THROW(bbapi::validate_vk_size<VK>(good_vk));
115+
}

barretenberg/cpp/src/barretenberg/bbapi/bbapi_ultra_honk.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ CircuitProve::Response _prove(std::vector<uint8_t>&& bytecode,
7272
info("WARNING: computing verification key while proving. Pass in a precomputed vk for better performance.");
7373
vk = std::make_shared<VerificationKey>(prover_instance->get_precomputed());
7474
} else {
75+
validate_vk_size<VerificationKey>(vk_bytes);
7576
vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vk_bytes));
7677
}
7778

barretenberg/cpp/src/barretenberg/chonk/chonk.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace bb {
2525
Chonk::Chonk(size_t num_circuits)
2626
: num_circuits(num_circuits)
2727
{
28-
BB_ASSERT_GT(num_circuits, 0UL, "Number of circuits must be specified and greater than 0.");
28+
BB_ASSERT_GTE(num_circuits, 4UL, "Number of circuits must be at least 4 (get_queue_type uses num_circuits - 3).");
2929
}
3030

3131
/**

barretenberg/cpp/src/barretenberg/chonk/chonk_proof.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ ChonkProof_<IsRecursive> ChonkProof_<IsRecursive>::from_field_elements(const std
4343

4444
// MegaZK Oink proof size = total - all other fixed-size components.
4545
// This correctly accounts for any ACIR public inputs prepended to the oink portion.
46-
const size_t mega_zk_oink_length = fields.size() - merge_size - eccvm_size - ipa_size - joint_size;
46+
constexpr size_t fixed_total = merge_size + eccvm_size + ipa_size + joint_size;
47+
BB_ASSERT_GTE(fields.size(), fixed_total, "ChonkProof::from_field_elements: proof too short");
48+
const size_t mega_zk_oink_length = fields.size() - fixed_total;
4749

4850
auto it = fields.begin();
4951

barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_builder_elliptic.test.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,30 @@ TEST_F(UltraCircuitBuilderElliptic, ChainedOperationsDoubleFailure)
276276
// Should fail because the middle operation (doubling) has an invalid result
277277
EXPECT_FALSE(CircuitChecker::check(builder));
278278
}
279+
280+
// Demonstrates that a doubling gate with off-curve input (0,0) does not constrain the output.
281+
// The elliptic relation substitutes x1^3 = y1^2 - b, which is invalid for off-curve points.
282+
// When (x1,y1) = (0,0), both the x- and y-coordinate subrelations become 0 = 0 for any output.
283+
// Production code (cycle_group::dbl) prevents this by substituting y1 = 1 for points at infinity.
284+
// See CycleGroupTest.TestDblWitnessPoints (cycle_group.test.cpp) for the complementary test
285+
// that verifies the mitigation works correctly.
286+
TEST_F(UltraCircuitBuilderElliptic, DoubleOffCurveOriginUnconstrainedOutput)
287+
{
288+
// (0,0) is not on grumpkin (y^2 = x^3 - 17), so this is an off-curve input
289+
auto x1_val = bb::fr(0);
290+
auto y1_val = bb::fr(0);
291+
// Claim an arbitrary output point
292+
auto x3_val = bb::fr::random_element();
293+
auto y3_val = bb::fr::random_element();
294+
295+
UltraCircuitBuilder builder;
296+
auto x1 = builder.add_variable(x1_val);
297+
auto y1 = builder.add_variable(y1_val);
298+
auto x3 = builder.add_variable(x3_val);
299+
auto y3 = builder.add_variable(y3_val);
300+
builder.create_ecc_dbl_gate({ x1, y1, x3, y3 });
301+
302+
// The circuit checker passes because the relation is trivially satisfied for (0,0) input.
303+
// This is NOT a bug — production code never lets (0,0) reach a doubling gate.
304+
EXPECT_TRUE(CircuitChecker::check(builder));
305+
}

barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_checker.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ MegaCircuitBuilder_<bb::fr> UltraCircuitChecker::prepare_circuit<MegaCircuitBuil
4848

4949
template <typename Builder> bool UltraCircuitChecker::check(const Builder& builder_in)
5050
{
51+
if (builder_in.failed()) {
52+
info("CircuitChecker: circuit contains invalid witnesses: ", builder_in.err());
53+
}
54+
5155
Builder builder = UltraCircuitChecker::prepare_circuit(builder_in);
5256

5357
// Construct a hash table for lookup table entries to efficiently determine if a lookup gate is valid

barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -199,24 +199,6 @@ class MegaFlavor {
199199
auto get_to_be_shifted() { return RefArray{ z_perm }; };
200200
};
201201

202-
/**
203-
* @brief ZK-specific entities (only used when HasZK = true)
204-
* @details Contains the Gemini masking polynomial used for zero-knowledge
205-
*/
206-
template <typename DataType, bool HasZK_ = false> class MaskingEntities {
207-
public:
208-
// When ZK is disabled, this class is empty
209-
auto get_all() { return RefArray<DataType, 0>{}; }
210-
auto get_all() const { return RefArray<const DataType, 0>{}; }
211-
static auto get_labels() { return std::vector<std::string>{}; }
212-
};
213-
214-
// Specialization for when ZK is enabled
215-
template <typename DataType> class MaskingEntities<DataType, true> {
216-
public:
217-
DEFINE_FLAVOR_MEMBERS(DataType, gemini_masking_poly)
218-
};
219-
220202
/**
221203
* @brief Container for all witness polynomials used/constructed by the prover.
222204
* @details Shifts are not included here since they do not occupy their own memory.
@@ -324,24 +306,19 @@ class MegaFlavor {
324306
* @details Used to build containers for: the prover's polynomial during sumcheck; the sumcheck's folded
325307
* polynomials; the univariates constructed during sumcheck; the evaluations produced by sumcheck.
326308
*
327-
* Symbolically we have: AllEntities = MaskingEntities + PrecomputedEntities + WitnessEntities + ShiftedEntities.
309+
* Symbolically we have: AllEntities = PrecomputedEntities + WitnessEntities + ShiftedEntities.
310+
* Note: Mega has no MaskingEntities — ZK masking is provided by the Translator in Chonk.
328311
*/
329-
template <typename DataType, bool HasZK_ = HasZK>
330-
class AllEntities_ : public MaskingEntities<DataType, HasZK_>,
331-
public PrecomputedEntities<DataType>,
312+
template <typename DataType>
313+
class AllEntities_ : public PrecomputedEntities<DataType>,
332314
public WitnessEntities_<DataType>,
333315
public ShiftedEntities<DataType> {
334316
public:
335-
DEFINE_COMPOUND_GET_ALL(MaskingEntities<DataType, HasZK_>,
336-
PrecomputedEntities<DataType>,
337-
WitnessEntities_<DataType>,
338-
ShiftedEntities<DataType>)
317+
DEFINE_COMPOUND_GET_ALL(PrecomputedEntities<DataType>, WitnessEntities_<DataType>, ShiftedEntities<DataType>)
339318

340319
auto get_unshifted()
341320
{
342-
return concatenate(MaskingEntities<DataType, HasZK_>::get_all(),
343-
PrecomputedEntities<DataType>::get_all(),
344-
WitnessEntities_<DataType>::get_all());
321+
return concatenate(PrecomputedEntities<DataType>::get_all(), WitnessEntities_<DataType>::get_all());
345322
};
346323
auto get_precomputed() { return PrecomputedEntities<DataType>::get_all(); }
347324
auto get_witness() { return WitnessEntities_<DataType>::get_all(); };
@@ -350,8 +327,7 @@ class MegaFlavor {
350327
auto get_shifted() const { return ShiftedEntities<DataType>::get_all(); };
351328
};
352329

353-
// Default AllEntities alias (no ZK)
354-
template <typename DataType> using AllEntities = AllEntities_<DataType, HasZK>;
330+
template <typename DataType> using AllEntities = AllEntities_<DataType>;
355331

356332
// Derive entity counts from the actual struct definitions
357333
static constexpr size_t NUM_PRECOMPUTED_ENTITIES = PrecomputedEntities<FF>::_members_size;
@@ -375,21 +351,12 @@ class MegaFlavor {
375351
* @brief A field element for each entity of the flavor. These entities represent the prover polynomials evaluated
376352
* at one point.
377353
*/
378-
template <bool HasZK_ = HasZK> class AllValues_ : public AllEntities_<FF, HasZK_> {
379-
public:
380-
using Base = AllEntities_<FF, HasZK_>;
381-
using Base::Base;
382-
};
383-
384-
using AllValues = AllValues_<HasZK>;
354+
using AllValues = AllEntities_<FF>;
385355

386356
/**
387357
* @brief A container for the prover polynomials handles.
388358
*/
389-
template <bool HasZK_ = HasZK>
390-
using ProverPolynomials_ = ProverPolynomialsBase<AllEntities_<Polynomial, HasZK_>, AllValues_<HasZK_>, Polynomial>;
391-
392-
using ProverPolynomials = ProverPolynomials_<HasZK>;
359+
using ProverPolynomials = ProverPolynomialsBase<AllEntities_<Polynomial>, AllValues, Polynomial>;
393360

394361
using PrecomputedData = PrecomputedData_<Polynomial, NUM_PRECOMPUTED_ENTITIES>;
395362

@@ -404,11 +371,8 @@ class MegaFlavor {
404371
/**
405372
* @brief A container for storing the partially evaluated multivariates produced by sumcheck.
406373
*/
407-
template <bool HasZK_ = HasZK>
408-
using PartiallyEvaluatedMultivariates_ =
409-
PartiallyEvaluatedMultivariatesBase<AllEntities_<Polynomial, HasZK_>, ProverPolynomials_<HasZK_>, Polynomial>;
410-
411-
using PartiallyEvaluatedMultivariates = PartiallyEvaluatedMultivariates_<HasZK>;
374+
using PartiallyEvaluatedMultivariates =
375+
PartiallyEvaluatedMultivariatesBase<AllEntities_<Polynomial>, ProverPolynomials, Polynomial>;
412376

413377
/**
414378
* @brief A container for univariates used in sumcheck.
@@ -497,8 +461,8 @@ class MegaFlavor {
497461
/**
498462
* Note: Made generic for use in MegaRecursive.
499463
**/
500-
template <typename Commitment, typename VerificationKey, bool HasZK_ = HasZK>
501-
class VerifierCommitments_ : public AllEntities_<Commitment, HasZK_> {
464+
template <typename Commitment, typename VerificationKey>
465+
class VerifierCommitments_ : public AllEntities_<Commitment> {
502466
public:
503467
VerifierCommitments_(const std::shared_ptr<VerificationKey>& verification_key,
504468
const std::optional<WitnessEntities<Commitment>>& witness_commitments = std::nullopt)
@@ -525,7 +489,7 @@ class MegaFlavor {
525489
}
526490
};
527491
// Specialize for Mega (general case used in MegaRecursive).
528-
using VerifierCommitments = VerifierCommitments_<Commitment, VerificationKey, HasZK>;
492+
using VerifierCommitments = VerifierCommitments_<Commitment, VerificationKey>;
529493
};
530494

531495
} // namespace bb

barretenberg/cpp/src/barretenberg/honk/composer/composer_lib.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ void construct_lookup_table_polynomials(const RefArray<typename Flavor::Polynomi
3434
offset++;
3535
}
3636
}
37+
BB_ASSERT(offset <= table_polynomials[0].end_index(),
38+
"construct_lookup_table_polynomials: total lookup table entries exceed polynomial size");
3739
}
3840

3941
/**

barretenberg/cpp/src/barretenberg/honk/proof_length.hpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,11 @@ template <typename Flavor> struct Honk {
109109
* @param proof_size Total proof size in field elements
110110
* @param log_n Log of circuit size (VIRTUAL_LOG_N for padded, vk->log_circuit_size for non-padded)
111111
*/
112-
static constexpr size_t derive_num_public_inputs(size_t proof_size, size_t log_n)
112+
static size_t derive_num_public_inputs(size_t proof_size, size_t log_n)
113113
{
114-
return proof_size - LENGTH_WITHOUT_PUB_INPUTS(log_n);
114+
const size_t overhead = LENGTH_WITHOUT_PUB_INPUTS(log_n);
115+
BB_ASSERT_GTE(proof_size, overhead, "Honk proof too short to derive num_public_inputs");
116+
return proof_size - overhead;
115117
}
116118

117119
/**
@@ -141,9 +143,11 @@ template <typename Flavor> struct HypernovaInstanceToAccum {
141143
return Oink<Flavor>::LENGTH_WITHOUT_PUB_INPUTS + Sumcheck<Flavor>::LENGTH(log_n);
142144
}
143145

144-
static constexpr size_t derive_num_public_inputs(size_t proof_size, size_t log_n)
146+
static size_t derive_num_public_inputs(size_t proof_size, size_t log_n)
145147
{
146-
return proof_size - LENGTH_WITHOUT_PUB_INPUTS(log_n);
148+
const size_t overhead = LENGTH_WITHOUT_PUB_INPUTS(log_n);
149+
BB_ASSERT_GTE(proof_size, overhead, "HypernovaInstanceToAccum proof too short to derive num_public_inputs");
150+
return proof_size - overhead;
147151
}
148152
};
149153

@@ -179,9 +183,11 @@ template <typename Flavor, typename BatchingFlavor> struct HypernovaFolding {
179183
MultilinearBatching<BatchingFlavor>::LENGTH;
180184
}
181185

182-
static constexpr size_t derive_num_public_inputs(size_t proof_size, size_t log_n)
186+
static size_t derive_num_public_inputs(size_t proof_size, size_t log_n)
183187
{
184-
return proof_size - LENGTH_WITHOUT_PUB_INPUTS(log_n);
188+
const size_t overhead = LENGTH_WITHOUT_PUB_INPUTS(log_n);
189+
BB_ASSERT_GTE(proof_size, overhead, "HypernovaFolding proof too short to derive num_public_inputs");
190+
return proof_size - overhead;
185191
}
186192
};
187193

barretenberg/cpp/src/barretenberg/relations/logderiv_lookup_relation.hpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,14 @@ namespace bb {
7575
* \text{read_tag} \cdot \text{read_tag} - \text{read_tag} = 0
7676
* \f]
7777
*
78-
* Further constraining of read_tags and read_counts is not required, since by tampering read_tags a malicious
79-
* prover can only skip a table_term. This is disadvantageous for the cheating prover as it reduces the size of the
80-
* lookup table. Hence, a malicious prover cannot abuse this to prove an incorrect lookup.
78+
* Further constraining of read_tags and read_counts is not required:
79+
* - read_tags: tampering can only skip a table_term, which is disadvantageous for the cheating prover as it
80+
* reduces the size of the lookup table.
81+
* - read_counts: soundness follows from the partial fraction decomposition of the lookup identity. After
82+
* substituting random challenges (β, γ), each table_term and lookup_term evaluates to a distinct field element
83+
* (Schwartz-Zippel). A lookup of a value not in the table creates a pole in the LHS (Σ 1/lookup_term) that no
84+
* choice of read_counts can cancel on the RHS (Σ read_count/table_term), since the denominators are distinct.
85+
* See Haböck (https://eprint.iacr.org/2022/1530.pdf), Section 4.
8186
*
8287
* @note Subrelation (2) is "linearly dependent" in the sense that it establishes that a sum across all rows of the
8388
* execution trace is zero, rather than that some expression holds independently at each row. Accordingly, this

0 commit comments

Comments
 (0)