Skip to content

Commit 0c875d9

Browse files
author
AztecBot
committed
Merge branch 'next' into ad/chore/ci-release-pr-canary
2 parents f98f1e2 + 6b85873 commit 0c875d9

File tree

87 files changed

+1190
-4960
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+1190
-4960
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
name: stdlib-point-at-infinity
3+
description: Guidelines for handling point-at-infinity in stdlib circuit types. Use when working on serialization, public inputs, or cycle_group/biggroup code.
4+
---
5+
6+
# Stdlib Point-at-Infinity Handling
7+
8+
## Two stdlib element types for bn254
9+
10+
- **`element_default::element`** (Ultra arithmetization): Separate `_is_infinity` bool_ct flag. Coordinates can be arbitrary when infinity is set. Has `is_point_at_infinity()` returning `bool_ct`.
11+
- **`goblin_element`** (Mega arithmetization): Represents infinity as `(0,0)` by construction. No separate infinity flag. No `is_point_at_infinity()` method on the circuit type. ECCVM enforces the `(0,0)` convention.
12+
- The alias `element<Builder, Fq, Fr, G>` resolves to one or the other via `IsGoblinBigGroup`.
13+
- Both types have `get_value()` returning a native `affine_element` with `is_point_at_infinity()`.
14+
15+
## cycle_group (Grumpkin) infinity handling
16+
17+
`cycle_group` has `_is_infinity` bool computed from `x^2 + 5*y^2 == 0` (which has no non-trivial solutions in the field, since `p == 2 mod 5`). For `(0,0)` this gives `0 == 0`, so infinity is auto-detected.
18+
19+
**Non-canonical infinity**: `cycle_group` operations (`operator+`, `operator-`, `dbl`, `batch_mul`) can produce points at infinity with non-canonical coordinates (`_is_infinity=true` but `x,y != 0,0`). This happens because the arithmetic uses `conditional_assign` to avoid division by zero -- the "real" coordinates are garbage when the infinity flag is set.
20+
21+
**Observation boundaries**: Canonicalization to `(0,0)` is deferred to these boundaries:
22+
- `StdlibCodec::serialize_to_fields` -- canonicalizes `grumpkin_commitment` via `conditional_assign`
23+
- `cycle_group::set_public` -- canonicalizes before exposing as public inputs
24+
- `cycle_group::operator==` and `assert_equal` -- handles infinity comparison
25+
26+
## StdlibCodec::serialize_to_fields (field_conversion.hpp)
27+
28+
- **grumpkin_commitment**: Canonicalizes infinity to `(0,0)` via `conditional_assign`. This IS needed because the `IPA::accumulate` -> `full_verify_recursive` path computes `G_zero_1 + G_zero_2 * alpha` using `cycle_group` arithmetic, which can produce non-canonical infinity when a malicious prover sends both `G_zero` values as `(0,0)`.
29+
- **bn254_commitment**: Allows canonical `(0,0)` infinity; asserts (`BB_ASSERT`) that infinity points have zero coordinates. All existing code paths (public inputs, transcript) produce canonical `(0,0)` infinity, so the assert is a safety guard against misuse. No canonicalization is performed (unlike `grumpkin_commitment`), since there are no available code paths that produce non-canonical bn254 infinity.
30+
31+
## Analyzing whether canonicalization is needed
32+
33+
Trace whether the value comes from:
34+
35+
1. **Deserialization** (from public inputs via `reconstruct_from_public`, or from transcript via `receive_from_prover`): Coordinates are already canonical `(0,0)` for infinity. Canonicalization is a no-op.
36+
2. **cycle_group arithmetic** (`operator+`, `operator-`, `dbl`, `batch_mul`): Coordinates may be non-canonical when `_is_infinity` is true. Canonicalization IS needed.
37+
38+
Key production paths for grumpkin commitments through `serialize_to_fields`:
39+
- **IPA `add_claim_to_hash_buffer`** (verifier side): Commitment is deserialized from public inputs -> already canonical.
40+
- **IPA `accumulate` hashing of `G_zero`**: `G_zero` is deserialized from transcript -> already canonical.
41+
- **IPA `full_verify_recursive`**: Accumulated commitment is the result of cycle_group arithmetic (`G_zero_1 + G_zero_2 * alpha`) -> may be non-canonical if infinity -> canonicalization needed.
42+
- **VK hashing** (`flavor.hpp` `to_field_elements`/`hash_with_origin_tagging`): Commitments are deserialized from fields -> already canonical.
43+
44+
## Recursive verification and malicious provers
45+
46+
For recursive verifier circuits, the circuit must be **constructible** even with malicious witness values (it just won't be satisfiable). This means:
47+
- Do NOT use `BB_ASSERT` on values a malicious prover can control -- it would crash circuit construction.
48+
- Use `conditional_assign` canonicalization instead, which produces correct circuit constraints regardless of witness values.
49+
- `BB_ASSERT` is appropriate for invariants that hold across all existing code paths (e.g., `bn254_commitment` in `serialize_to_fields` asserts canonical `(0,0)` form for infinity, since all paths that can reach it produce canonical infinity).
50+
51+
## Common bug patterns to watch for
52+
53+
These patterns have caused repeated bugs across biggroup, cycle_group, ECCVM, AVM, and ECDSA:
54+
55+
### 1. Representation mismatch: internal sentinel vs `(0,0)`
56+
BB's native `affine_element` uses an internal sentinel for infinity (MSB set on the x coordinate's raw representation, not a valid field element). Noir, AVM, and the transcript convention use `(0,0)`. Any code that reads raw coordinates without checking `is_point_at_infinity()` (or without calling `get_standard_form()` on stdlib types) will see sentinel values, not `(0,0)`. Always use the standard-form convention `(0,0)` at component boundaries.
57+
58+
### 2. Forgetting to propagate the infinity flag through conditional operations
59+
When doing `conditional_assign` or `conditional_select` on points, both the coordinates AND the `_is_infinity` flag must be selected. Multiple ECDSA and biggroup bugs came from selecting x/y but leaving `_is_infinity` unchanged.
60+
61+
### 3. Incomplete addition formulas crash on infinity
62+
Performance-optimized ECC (chain_add, Montgomery ladder) assumes inputs are never infinity and never equal. When infinity appears as an intermediate value, these formulas divide by zero or produce wrong results. If a code path can encounter infinity mid-computation, use complete addition (`operator+`) instead of `chain_add_start`/`chain_add`/`chain_add_end`. This costs ~2% more gates but is correct.
63+
64+
### 4. Constructor and validation bypasses
65+
Constructors that accept a direct `is_infinity` flag can bypass on-curve validation (a point with `is_infinity=true` but `x,y != 0` passes `validate_on_curve` because the check is skipped for infinity). The 4-argument biggroup constructor with explicit infinity flag is now private. Prefer the 2-argument `(x, y)` constructor which auto-detects infinity from `x == 0 && y == 0`.
66+
67+
### 5. Forgetting to canonicalize before comparison or hashing
68+
`cycle_group::operator==` and `assert_equal` handle infinity correctly, but raw coordinate comparison does not. Before comparing or hashing point coordinates, ensure infinity points have canonical `(0,0)` coordinates. The observation boundary pattern (serialize_to_fields, set_public, operator==) exists for this reason.

barretenberg/cpp/cmake/module.cmake

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ function(barretenberg_module_with_sources MODULE_NAME)
111111
endif()
112112
list(APPEND lib_targets ${MODULE_NAME})
113113

114+
set(MODULE_LINK_NAME ${MODULE_NAME})
115+
elseif(MODULE_DEPENDENCIES AND NOT BENCH_SOURCE_FILES AND NOT FUZZERS_SOURCE_FILES)
116+
# Header-only module with dependencies: create an INTERFACE library
117+
# so dependents can still reference this module by name.
118+
add_library(${MODULE_NAME} INTERFACE)
119+
target_link_libraries(${MODULE_NAME} INTERFACE ${MODULE_DEPENDENCIES})
114120
set(MODULE_LINK_NAME ${MODULE_NAME})
115121
endif()
116122

barretenberg/cpp/cmake/threading.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ if(ENABLE_PAR_ALGOS)
3030
find_package(TBB QUIET OPTIONAL_COMPONENTS tbb)
3131
if(${TBB_FOUND})
3232
message(STATUS "std::execution parallel algorithms are enabled.")
33+
link_libraries(TBB::tbb)
3334
else()
3435
message(STATUS "Could not locate Intel TBB, disabling std::execution parallel algorithms.")
3536
add_definitions(-DNO_PAR_ALGOS)

barretenberg/cpp/src/CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,10 @@ set(BARRETENBERG_TARGET_OBJECTS
165165
$<TARGET_OBJECTS:common_objects>
166166
$<TARGET_OBJECTS:crypto_aes128_objects>
167167
$<TARGET_OBJECTS:crypto_blake2s_objects>
168-
$<TARGET_OBJECTS:crypto_blake3s_objects>
169-
$<TARGET_OBJECTS:crypto_ecdsa_objects>
170168
$<TARGET_OBJECTS:crypto_keccak_objects>
171169
$<TARGET_OBJECTS:crypto_pedersen_commitment_objects>
172170
$<TARGET_OBJECTS:crypto_pedersen_hash_objects>
173171
$<TARGET_OBJECTS:crypto_poseidon2_objects>
174-
$<TARGET_OBJECTS:crypto_schnorr_objects>
175172
$<TARGET_OBJECTS:crypto_sha256_objects>
176173
$<TARGET_OBJECTS:dsl_objects>
177174
$<TARGET_OBJECTS:ecc_objects>

barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description.test.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ TEST(boomerang_ultra_circuit_constructor, test_graph_for_elliptic_add_gate)
130130
uint32_t x3 = circuit_constructor.add_variable(p3.x);
131131
uint32_t y3 = circuit_constructor.add_variable(p3.y);
132132

133-
circuit_constructor.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, 1 });
133+
circuit_constructor.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, /*is_addition=*/true });
134134

135135
StaticAnalyzer graph = StaticAnalyzer(circuit_constructor);
136136
auto connected_components = graph.find_connected_components();
@@ -197,7 +197,7 @@ TEST(boomerang_ultra_circuit_constructor, test_graph_for_elliptic_together)
197197
uint32_t x3 = circuit_constructor.add_variable(p3.x);
198198
uint32_t y3 = circuit_constructor.add_variable(p3.y);
199199

200-
circuit_constructor.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, 1 });
200+
circuit_constructor.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, /*is_addition=*/true });
201201
affine_element p4(element(p3).dbl());
202202
uint32_t x4 = circuit_constructor.add_variable(p4.x);
203203
uint32_t y4 = circuit_constructor.add_variable(p4.y);
@@ -214,7 +214,7 @@ TEST(boomerang_ultra_circuit_constructor, test_graph_for_elliptic_together)
214214
uint32_t x7 = circuit_constructor.add_variable(p7.x);
215215
uint32_t y7 = circuit_constructor.add_variable(p7.y);
216216

217-
circuit_constructor.create_ecc_add_gate({ x5, y5, x6, y6, x7, y7, 1 });
217+
circuit_constructor.create_ecc_add_gate({ x5, y5, x6, y6, x7, y7, /*is_addition=*/true });
218218
affine_element p8(element(p7).dbl());
219219
uint32_t x8 = circuit_constructor.add_variable(p8.x);
220220
uint32_t y8 = circuit_constructor.add_variable(p8.y);
@@ -572,7 +572,7 @@ TEST(boomerang_ultra_circuit_constructor, test_variables_gates_counts_for_ecc_ad
572572
uint32_t x3 = circuit_constructor.add_variable(p3.x);
573573
uint32_t y3 = circuit_constructor.add_variable(p3.y);
574574

575-
circuit_constructor.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, 1 });
575+
circuit_constructor.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, /*is_addition=*/true });
576576

577577
StaticAnalyzer graph = StaticAnalyzer(circuit_constructor);
578578
auto variables_gate_counts = graph.get_variables_gate_counts();

barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_merge_recursive_verifier.test.cpp

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,10 @@ template <class RecursiveBuilder> class BoomerangRecursiveMergeVerifierTest : pu
4141

4242
static void analyze_circuit(RecursiveBuilder& outer_circuit)
4343
{
44-
// AUDITTODO: The 8 under-constrained variables are the _is_infinity boolean flags from the 8
45-
// commitments created via goblin_element::from_witness (4 t_commitments + 4 T_prev_commitments).
46-
// Each boolean is only constrained by a single bool gate (x * (x - 1) = 0) and is not
47-
// connected to the point coordinates. This may be a security issue if the infinity flag is not
48-
// properly bound to the coordinates via Fiat-Shamir - a malicious prover could potentially
49-
// set the flag independently of the actual point value.
50-
constexpr size_t EXPECTED_UNCONSTRAINED_INFINITY_FLAGS = 4;
51-
52-
if constexpr (IsMegaBuilder<RecursiveBuilder>) {
53-
MegaStaticAnalyzer tool = MegaStaticAnalyzer(outer_circuit);
54-
auto result = tool.analyze_circuit();
55-
EXPECT_EQ(result.first.size(), 1);
56-
EXPECT_EQ(result.second.size(), EXPECTED_UNCONSTRAINED_INFINITY_FLAGS);
57-
}
58-
if constexpr (IsUltraBuilder<RecursiveBuilder>) {
59-
StaticAnalyzer tool = StaticAnalyzer(outer_circuit);
60-
auto result = tool.analyze_circuit();
61-
EXPECT_EQ(result.first.size(), 1);
62-
EXPECT_EQ(result.second.size(), EXPECTED_UNCONSTRAINED_INFINITY_FLAGS);
63-
}
44+
auto tool = StaticAnalyzer_<bb::fr, RecursiveBuilder>(outer_circuit);
45+
auto result = tool.analyze_circuit();
46+
EXPECT_EQ(result.first.size(), 1);
47+
EXPECT_EQ(result.second.size(), 0);
6448
}
6549

6650
static void prove_and_verify_merge(const std::shared_ptr<ECCOpQueue>& op_queue,
@@ -96,9 +80,15 @@ template <class RecursiveBuilder> class BoomerangRecursiveMergeVerifierTest : pu
9680
auto merge_transcript = std::make_shared<StdlibTranscript<RecursiveBuilder>>();
9781
RecursiveMergeVerifier verifier{ settings, merge_transcript };
9882
const stdlib::Proof<RecursiveBuilder> stdlib_merge_proof(outer_circuit, merge_proof);
99-
[[maybe_unused]] auto [pairing_points, merged_commitments, reduction_succeeded] =
83+
auto [pairing_points, merged_commitments, reduction_succeeded] =
10084
verifier.reduce_to_pairing_check(stdlib_merge_proof, recursive_merge_commitments);
10185

86+
// The pairing points are public outputs from the recursive verifier that will be verified externally via a
87+
// pairing check. Their output coordinate limbs (from goblin batch_mul's queue_ecc_eq) may only appear in a
88+
// single ECC op gate. Calling fix_witness() adds explicit constraints on these values so the StaticAnalyzer
89+
// does not flag them as under-constrained.
90+
pairing_points.fix_witness();
91+
10292
// Check for a failure flag in the recursive verifier circuit
10393
EXPECT_FALSE(outer_circuit.failed());
10494
if (run_analyzer) {

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ TEST_F(UltraCircuitBuilderElliptic, Addition)
6565
auto points = create_add_points(1, 2, true);
6666

6767
auto [x1, y1, x2, y2, x3, y3] = add_add_gate_variables(builder, points);
68-
builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, 1 });
68+
builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, /*is_addition=*/true });
6969

7070
EXPECT_TRUE(CircuitChecker::check(builder));
7171
}
@@ -78,7 +78,7 @@ TEST_F(UltraCircuitBuilderElliptic, AdditionFailure)
7878
auto points = create_add_points(1, 2, true);
7979
modify_points(points);
8080
auto [x1, y1, x2, y2, x3, y3] = add_add_gate_variables(builder, points);
81-
builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, 1 });
81+
builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, /*is_addition=*/true });
8282
EXPECT_FALSE(CircuitChecker::check(builder));
8383
};
8484

@@ -96,7 +96,7 @@ TEST_F(UltraCircuitBuilderElliptic, Subtraction)
9696
UltraCircuitBuilder builder;
9797
auto points = create_add_points(1, 2, false); // false = subtraction
9898
auto [x1, y1, x2, y2, x3, y3] = add_add_gate_variables(builder, points);
99-
builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, -1 });
99+
builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, /*is_addition=*/false });
100100
EXPECT_TRUE(CircuitChecker::check(builder));
101101
}
102102

@@ -108,7 +108,7 @@ TEST_F(UltraCircuitBuilderElliptic, SubtractionFailure)
108108
auto points = create_add_points(1, 2, /*is_addition=*/false);
109109
modify_points(points);
110110
auto [x1, y1, x2, y2, x3, y3] = add_add_gate_variables(builder, points);
111-
builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, /*sign_coefficient=*/-1 });
111+
builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, /*is_addition=*/false });
112112
EXPECT_FALSE(CircuitChecker::check(builder));
113113
};
114114

@@ -165,8 +165,8 @@ TEST_F(UltraCircuitBuilderElliptic, MultipleOperationsUnchained)
165165
auto [sub_x1, sub_y1, sub_x2, sub_y2, sub_x3, sub_y3] = add_add_gate_variables(builder, sub_points);
166166
auto [dbl_x1, dbl_y1, dbl_x3, dbl_y3] = add_dbl_gate_variables(builder, dbl_points);
167167

168-
builder.create_ecc_add_gate({ add_x1, add_y1, add_x2, add_y2, add_x3, add_y3, /*sign_coefficient=*/1 });
169-
builder.create_ecc_add_gate({ sub_x1, sub_y1, sub_x2, sub_y2, sub_x3, sub_y3, /*sign_coefficient=*/-1 });
168+
builder.create_ecc_add_gate({ add_x1, add_y1, add_x2, add_y2, add_x3, add_y3, /*is_addition=*/true });
169+
builder.create_ecc_add_gate({ sub_x1, sub_y1, sub_x2, sub_y2, sub_x3, sub_y3, /*is_addition=*/false });
170170
builder.create_ecc_dbl_gate({ dbl_x1, dbl_y1, dbl_x3, dbl_y3 });
171171

172172
EXPECT_EQ(builder.blocks.elliptic.size(), 6UL); // 3 unchained operations, 2 gates each
@@ -194,8 +194,8 @@ TEST_F(UltraCircuitBuilderElliptic, ChainedOperations)
194194
uint32_t x_result = builder.add_variable(result.x);
195195
uint32_t y_result = builder.add_variable(result.y);
196196

197-
builder.create_ecc_add_gate({ x1, y1, x2, y2, x_temp, y_temp, /*sign_coefficient=*/1 });
198-
builder.create_ecc_add_gate({ x_temp, y_temp, x3, y3, x_result, y_result, /*sign_coefficient=*/1 });
197+
builder.create_ecc_add_gate({ x1, y1, x2, y2, x_temp, y_temp, /*is_addition=*/true });
198+
builder.create_ecc_add_gate({ x_temp, y_temp, x3, y3, x_result, y_result, /*is_addition=*/true });
199199

200200
EXPECT_EQ(builder.blocks.elliptic.size(), 3UL); // 2 chained operations = 2 + (2 - 1) gates
201201
EXPECT_TRUE(CircuitChecker::check(builder));
@@ -230,9 +230,9 @@ TEST_F(UltraCircuitBuilderElliptic, ChainedOperationsWithDouble)
230230
uint32_t x_result = builder.add_variable(result.x);
231231
uint32_t y_result = builder.add_variable(result.y);
232232

233-
builder.create_ecc_add_gate({ x1, y1, x2, y2, x_temp1, y_temp1, /*sign_coefficient=*/1 });
233+
builder.create_ecc_add_gate({ x1, y1, x2, y2, x_temp1, y_temp1, /*is_addition=*/true });
234234
builder.create_ecc_dbl_gate({ x_temp1, y_temp1, x_temp2, y_temp2 });
235-
builder.create_ecc_add_gate({ x_temp2, y_temp2, x3, y3, x_result, y_result, /*sign_coefficient=*/1 });
235+
builder.create_ecc_add_gate({ x_temp2, y_temp2, x3, y3, x_result, y_result, /*is_addition=*/true });
236236

237237
EXPECT_EQ(builder.blocks.elliptic.size(), 4UL); // 3 chained operations, 2 + (2 - 1) + (2 - 1) gates
238238
EXPECT_TRUE(CircuitChecker::check(builder));
@@ -268,9 +268,9 @@ TEST_F(UltraCircuitBuilderElliptic, ChainedOperationsDoubleFailure)
268268
uint32_t x_result = builder.add_variable(result.x);
269269
uint32_t y_result = builder.add_variable(result.y);
270270

271-
builder.create_ecc_add_gate({ x1, y1, x2, y2, x_temp1, y_temp1, /*sign_coefficient=*/1 });
271+
builder.create_ecc_add_gate({ x1, y1, x2, y2, x_temp1, y_temp1, /*is_addition=*/true });
272272
builder.create_ecc_dbl_gate({ x_temp1, y_temp1, x_temp2, y_temp2 });
273-
builder.create_ecc_add_gate({ x_temp2, y_temp2, x3, y3, x_result, y_result, /*sign_coefficient=*/1 });
273+
builder.create_ecc_add_gate({ x_temp2, y_temp2, x3, y3, x_result, y_result, /*is_addition=*/true });
274274

275275
EXPECT_EQ(builder.blocks.elliptic.size(), 4UL); // 3 chained operations, 2 + (2 - 1) + (2 - 1) gates
276276
// Should fail because the middle operation (doubling) has an invalid result

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,14 @@ template <typename Curve_, size_t log_poly_length = CONST_ECCVM_LOG_N> class IPA
529529
// Receive a_zero from the prover
530530
const auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
531531

532+
// OriginTag false positive: G_zero and a_zero are fully determined once all round challenges are fixed - the
533+
// prover must send the correct values or the final relation check fails.
534+
if constexpr (Curve::is_stdlib_type) {
535+
const auto last_round_tag = round_challenges.back().get_origin_tag();
536+
G_zero.set_origin_tag(last_round_tag);
537+
const_cast<Fr&>(a_zero).set_origin_tag(last_round_tag);
538+
}
539+
532540
// Step 7.
533541
// Compute R = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j - G₀ * a₀ - (f(\beta) - a₀ * b₀) ⋅ U
534542
// If everything is correct, then R == -C, as C':= C + f(\beta) ⋅ U

barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ template <typename Curve_> class KZG {
136136
// This challenge is used to compute offset generators in the batch_mul call below
137137
const Fr masking_challenge = transcript->template get_challenge<Fr>("KZG:masking_challenge");
138138

139+
// OriginTag false positive: The quotient commitment is PCS-bound - the prover cannot
140+
// choose it freely because it must satisfy the batched opening equation.
141+
if constexpr (Curve::is_stdlib_type) {
142+
const auto challenge_tag = masking_challenge.get_origin_tag();
143+
quotient_commitment.set_origin_tag(challenge_tag);
144+
}
145+
139146
// The pairing check can be expressed as
140147
// e(C + [W]₁ ⋅ z, [1]₂) * e(−[W]₁, [X]₂) = 1, where C = ∑ commitmentsᵢ ⋅ scalarsᵢ.
141148
GroupElement P_0;

0 commit comments

Comments
 (0)