From 244357f37243cf94c62765c1ed60ce637fbc4764 Mon Sep 17 00:00:00 2001 From: ledwards2225 Date: Fri, 8 May 2026 18:50:06 +0000 Subject: [PATCH 1/2] in circuit assert prohibiting inf in unconditional add/sub --- .../stdlib/primitives/group/cycle_group.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp index 11b3280f80ad..4ae819cf5c1a 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp @@ -370,11 +370,12 @@ cycle_group cycle_group::_unconditional_add_or_subtract(const bool is_addition, const std::optional hint) const { - // This method should not be called on known points at infinity - BB_ASSERT(!this->is_constant_point_at_infinity(), - "cycle_group::_unconditional_add_or_subtract called on constant point at infinity"); - BB_ASSERT(!other.is_constant_point_at_infinity(), - "cycle_group::_unconditional_add_or_subtract called on constant point at infinity"); + // Reject point-at-infinity operands: the incomplete ecc_add_gate is degenerate at (0, 0) (the + // canonical witness representation of infinity) and admits forged outputs there. + this->is_point_at_infinity().assert_equal( + false, "cycle_group::_unconditional_add_or_subtract called on point at infinity"); + other.is_point_at_infinity().assert_equal( + false, "cycle_group::_unconditional_add_or_subtract called on point at infinity"); auto context = get_context(other); From 7d85633fad9a87e50d6293872b250f5873d14a70 Mon Sep 17 00:00:00 2001 From: ledwards2225 Date: Fri, 8 May 2026 21:11:31 +0000 Subject: [PATCH 2/2] defense in depth assert and comments about security model re offset generator --- .../stdlib/primitives/group/cycle_group.cpp | 10 ++++++---- .../stdlib/primitives/group/straus_lookup_table.cpp | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp index 4ae819cf5c1a..3ee10a0229cc 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp @@ -830,6 +830,8 @@ typename cycle_group::batch_mul_internal_output cycle_group::_ // Execute Straus algorithm in-circuit using the precomputed hints. // If unconditional_add == false, accumulate x-coordinate differences to batch-validate no collisions. + // If unconditional_add == true (constant inputs), no per-slice check is added; soundness relies on the + // offset-generator domain separator keeping per-slice operands distinct. field_t coordinate_check_product = 1; for (size_t i = 0; i < num_rounds; ++i) { // Double the accumulator ROM_TABLE_BITS times (except in first round) @@ -1081,8 +1083,8 @@ typename cycle_group::batch_mul_internal_output cycle_group::_ for (size_t j = 0; j < num_points; ++j) { const field_t scalar_slice = scalar_slices[j][num_rounds - i - 1]; const cycle_group point = point_tables[j].read(scalar_slice); - // Safe to use unconditional_add: all base points are constants hence linearly independent of offset - // generators + // No per-slice x-collision check; soundness relies on the offset-generator domain separator + // keeping `accumulator` distinct from any plookup table entry across the loop. accumulator = accumulator.unconditional_add(point, *hint_ptr); hint_ptr++; } @@ -1171,8 +1173,8 @@ cycle_group cycle_group::fixed_batch_mul(const std::vector::straus_lookup_table(Builder* context, // Case 1: if the input point is constant, it is cheaper to fix the point as a witness and then derive the // table, than it is to derive the table and fix its witnesses to be constant! (due to group additions = 1 gate, // and fixing x/y coords to be constant = 2 gates) + + // base_point == offset_generator collapses the first table add into a degenerate G + G ecc_add gate; + // the non-doubling relation is identically zero when operands match, so is satisfied by any result coordinates. + BB_ASSERT(base_point.get_value() != offset_generator.get_value(), + "straus_lookup_table case-1: base_point must not coincide with offset_generator"); + modded_base_point = cycle_group::from_constant_witness(_context, modded_base_point.get_value()); point_table[0] = cycle_group::from_constant_witness(_context, offset_generator.get_value()); for (size_t i = 1; i < table_size; ++i) { - // Safe to use unconditional_add without collision checking due to constant points and inclusion of offset - // generator in table entries point_table[i] = point_table[i - 1].unconditional_add(modded_base_point, get_hint(i - 1)); } } else {