Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ constexpr std::tuple<size_t, size_t> HONK_RECURSION_CONSTANTS(
// ========================================

// Gate count for Chonk recursive verification (Ultra with RollupIO)
inline constexpr size_t CHONK_RECURSION_GATES = 1684865;
inline constexpr size_t CHONK_RECURSION_GATES = 1685107;

// ========================================
// Hypernova Recursion Constants
Expand Down Expand Up @@ -147,7 +147,7 @@ inline constexpr size_t HIDING_KERNEL_ULTRA_OPS = 124;
// ========================================

// Gate count for ECCVM recursive verifier (Ultra-arithmetized)
inline constexpr size_t ECCVM_RECURSIVE_VERIFIER_GATE_COUNT = 224458;
inline constexpr size_t ECCVM_RECURSIVE_VERIFIER_GATE_COUNT = 224702;

// ========================================
// Goblin AVM Recursive Verifier Constants
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ RelationParameters<FF> compute_full_relation_params(ProverPolynomials& polynomia
return params;
}

/**
* @brief Find the first transcript no-op row: all selectors zero, not first/last row.
*/
size_t find_transcript_noop_row(const ProverPolynomials& polynomials)
{
const size_t num_rows = polynomials.get_polynomial_size();
for (size_t i = 2; i < num_rows - 1; i++) {
if (polynomials.transcript_add[i] == FF(0) && polynomials.transcript_mul[i] == FF(0) &&
polynomials.transcript_eq[i] == FF(0) && polynomials.transcript_reset_accumulator[i] == FF(0) &&
polynomials.lagrange_first[i] == FF(0) && polynomials.lagrange_last[i] == FF(0)) {
return i;
}
}
return 0;
}

} // anonymous namespace

class ECCVMRelationCorruptionTests : public ::testing::Test {
Expand Down Expand Up @@ -330,3 +346,34 @@ TEST_F(ECCVMRelationCorruptionTests, MSMRelationFailsOnShiftedMSMTable)
polynomials, full_params, "ECCVMLookupRelation");
EXPECT_TRUE(lookup_failures.empty()) << "ECCVMLookupRelation should still pass (inverse computed post-shift)";
}

/**
* @brief On a transcript no-op row, setting accumulator_not_empty=1 must be caught by subrelation 22.
*
* @details The `accumulator_infinity_from_noop` term in subrelation 22 forces
* is_accumulator_empty_shift = 1 whenever all selectors are zero. This test corrupts
* the shifted value (i.e. accumulator_not_empty at row+1) to 1 and verifies detection.
*/
TEST_F(ECCVMRelationCorruptionTests, TranscriptNoOpRowRejectsAccumulatorNotEmpty)
{
auto polynomials = build_valid_eccvm_msm_state();
RelationParameters<FF> params{};

auto baseline =
RelationChecker<void>::check<ECCVMTranscriptRelation<FF>>(polynomials, params, "ECCVMTranscriptRelation");
EXPECT_TRUE(baseline.empty()) << "Baseline transcript relation should pass";

size_t noop_row = find_transcript_noop_row(polynomials);
ASSERT_NE(noop_row, 0) << "Should find a transcript no-op row";

// The no-op constraint at row `noop_row` constrains is_accumulator_empty_shift,
// which reads from accumulator_not_empty at row `noop_row + 1`.
polynomials.transcript_accumulator_not_empty.at(noop_row + 1) = FF(1);
polynomials.set_shifted();

auto failures =
RelationChecker<void>::check<ECCVMTranscriptRelation<FF>>(polynomials, params, "ECCVMTranscriptRelation");
EXPECT_FALSE(failures.empty()) << "Transcript relation should fail after corrupting accumulator_not_empty on "
"the row following a no-op";
EXPECT_TRUE(failures.contains(22)) << "Subrelation 22 (accumulator_infinity) should catch the corruption";
}
Original file line number Diff line number Diff line change
Expand Up @@ -479,11 +479,20 @@ void ECCVMTranscriptRelationImpl<FF>::accumulate(ContainerOverSubrelations& accu
}

/**
* @brief Validate `is_accumulator_empty` is updated correctly
* An add operation can produce a point at infinity
* Resetting the accumulator produces a point at infinity
* If we are not adding, performing an msm or resetting the accumulator (or doing a no-op),
* is_accumulator_empty should not update
* @brief Validate `is_accumulator_empty` is updated correctly.
*
* The relation is a sum of four mutually exclusive terms, each constraining `is_accumulator_empty_shift`
* for a specific case:
* (A) accumulator_infinity_preserve: active when propagate_transcript_accumulator != 0
* (i.e. q_mul without msm_transition, or q_eq without q_reset). Preserves the emptiness flag.
* (B) accumulator_infinity_q_reset: active when q_reset_accumulator = 1. Forces empty.
* (B) accumulator_infinity_from_add: active when any_add_is_active != 0 (q_add or msm_transition).
* Sets emptiness from the add/msm result.
* (C) accumulator_infinity_from_noop: active when opcode_is_zero != 0 (all selectors off). Forces empty.
*
* These are mutually exclusive because opcode_is_zero requires all selectors = 0 (which zeros out A and B),
* while A and B each require at least one selector to be non-zero (which zeros out C).
* Within A and B, exclusivity follows from the opcode exclusion constraint (subrelation 8).
*/
auto accumulator_infinity_preserve_flag = propagate_transcript_accumulator; // degree 1
auto accumulator_infinity_preserve = accumulator_infinity_preserve_flag *
Expand All @@ -492,10 +501,19 @@ void ECCVMTranscriptRelationImpl<FF>::accumulate(ContainerOverSubrelations& accu
auto accumulator_infinity_q_reset = q_reset_accumulator * (-is_accumulator_empty_shift + 1); // degree 2
auto accumulator_infinity_from_add =
any_add_is_active * (result_is_infinity - is_accumulator_empty_shift); // degree 3
// When opcode_is_zero (no-op row), the accumulator output is forced to (0,0) by subrelations 15 and 16.
// We must also force is_accumulator_empty_shift = 1 so that the emptiness flag is consistent
// with the (0,0) accumulator coordinates. Without this, a malicious prover could set
// accumulator_not_empty = 1 on the next row while the accumulator is (0,0), creating an
// inconsistency that bypasses on-curve checks (which are only performed on input coordinates).
auto opcode_is_zero =
(is_not_first_row) * (-q_add + 1) * (-q_mul + 1) * (-q_reset_accumulator + 1) * (-q_eq + 1); // degree 5
auto accumulator_infinity_from_noop = opcode_is_zero * (-is_accumulator_empty_shift + 1); // degree 6
auto accumulator_infinity_relation =
accumulator_infinity_preserve +
(accumulator_infinity_q_reset + accumulator_infinity_from_add) * is_not_first_row; // degree 4
std::get<22>(accumulator) += accumulator_infinity_relation * scaling_factor; // degree 4
(accumulator_infinity_q_reset + accumulator_infinity_from_add) * is_not_first_row +
accumulator_infinity_from_noop; // degree 6
std::get<22>(accumulator) += accumulator_infinity_relation * scaling_factor; // degree 6

/**
* @brief Validate `transcript_add_x_equal` is well-formed
Expand Down
Loading