3030
3131namespace bb {
3232
33+ namespace detail {
34+ template <typename > struct ref_array_extent ;
35+ template <typename T, std::size_t N> struct ref_array_extent <RefArray<T, N>> {
36+ static constexpr std::size_t value = N;
37+ };
38+ } // namespace detail
39+
3340class TranslatorFlavor {
3441
3542 public:
@@ -586,17 +593,23 @@ class TranslatorFlavor {
586593
587594 /* *
588595 * @brief All unshifted polynomials for PCS (excludes computable precomputed, includes concatenated).
589- * @details masking(1) + ordered_extra(1) + op(1) + ordered(5) + z_perm(1) + concat(5) = 14
596+ * @details masking(1) + ordered_extra(1) + op(1) + op_queue_tbs(3) + ordered(5) + z_perm(1) + concat(5) = 17
597+ *
598+ * The op-queue to-be-shifted wires (x_lo_y_hi, x_hi_z_1, y_lo_z_2) appear here in addition to
599+ * get_pcs_to_be_shifted because the decomposition relation reads them both unshifted (e.g. `x_lo`)
600+ * and shift-by-1 (e.g. `y_hi`) at the same row. Without registering the unshifted opening, the
601+ * unshifted MLE evaluations at the sumcheck point would be unconstrained.
590602 */
591603 auto get_pcs_unshifted ()
592604 {
593605 return concatenate (
594606 MaskingEntities<DataType>::get_all (), // gemini_masking_poly
595607 RefArray<DataType, 1 >{ this ->ordered_extra_range_constraints_numerator }, // non-computable precomputed
596608 WireNonshiftedEntities<DataType>::get_all (), // op (from merge protocol)
597- OrderedRangeConstraints<DataType>::get_all (), // ordered_0..4
598- DerivedWitnessEntities<DataType>::get_all (), // z_perm
599- ConcatenatedPolynomials<DataType>::get_all ()); // concat_0..4
609+ OpQueueWiresToBeShiftedEntities<DataType>::get_all (), // x_lo_y_hi, x_hi_z_1, y_lo_z_2
610+ OrderedRangeConstraints<DataType>::get_all (), // ordered_0..4
611+ DerivedWitnessEntities<DataType>::get_all (), // z_perm
612+ ConcatenatedPolynomials<DataType>::get_all ()); // concat_0..4
600613 }
601614
602615 /* *
@@ -711,9 +724,11 @@ class TranslatorFlavor {
711724 "Range constraint wires must fill exactly 4 concatenation groups");
712725
713726 // PCS batch sizes
714- static constexpr size_t NUM_UNSHIFTED_WITNESSES_WITHOUT_CONCATENATED = WireNonshiftedEntities<FF>::_members_size +
715- OrderedRangeConstraints<FF>::_members_size +
716- DerivedWitnessEntities<FF>::_members_size;
727+ // Note: op-queue to-be-shifted wires (x_lo_y_hi, x_hi_z_1, y_lo_z_2) are registered in BOTH the
728+ // unshifted and shifted PCS batches because the decomposition relation reads them in both forms.
729+ static constexpr size_t NUM_UNSHIFTED_WITNESSES_WITHOUT_CONCATENATED =
730+ WireNonshiftedEntities<FF>::_members_size + OpQueueWiresToBeShiftedEntities<FF>::_members_size +
731+ OrderedRangeConstraints<FF>::_members_size + DerivedWitnessEntities<FF>::_members_size;
717732 static constexpr size_t NUM_TO_BE_SHIFTED = OpQueueWiresToBeShiftedEntities<FF>::_members_size +
718733 OrderedRangeConstraints<FF>::_members_size +
719734 DerivedWitnessEntities<FF>::_members_size;
@@ -731,17 +746,20 @@ class TranslatorFlavor {
731746
732747 // A container to be fed to ShpleminiVerifier to avoid redundant scalar muls.
733748 // Identifies commitments that appear in both the unshifted and shifted batches:
734- // Unshifted batch: masking(1) + ordered_extra(1) + op(1) + ordered(5) + z_perm(1) + concat(5) = 14
749+ // Unshifted batch: masking(1) + ordered_extra(1) + op(1) + op_queue_tbs(3) + ordered(5) + z_perm(1) + concat(5)
750+ // = 17
735751 // Shifted batch: op_queue(3) + ordered(5) + z_perm(1) + concat(5) = 14
736- // Range 1: ordered(5) + z_perm(1) — stored indices 2..7 (unshifted) ↔ 16..21 (shifted)
737- // Range 2: concatenated(5) — stored indices 8..12 (unshifted) ↔ 22..26 (shifted)
752+ // Range 1: op_queue_tbs(3) + ordered(5) + z_perm(1) = 9 (contiguous in both batches)
753+ // stored indices 2..10 (unshifted) ↔ 16..24 (shifted)
754+ // Range 2: concatenated(5) — stored indices 11..15 (unshifted) ↔ 25..29 (shifted)
738755 // (Stored indices are 0-based after ZK offset; offset=2 accounts for Q_commitment + gemini_masking_poly)
756+ static constexpr size_t NUM_OP_QUEUE_TO_BE_SHIFTED = OpQueueWiresToBeShiftedEntities<FF>::_members_size;
739757 static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS =
740758 RepeatedCommitmentsData (2 ,
741759 2 + NUM_PCS_TO_BE_SHIFTED,
742- NUM_ORDERED_RANGE + 1 ,
743- 2 + NUM_ORDERED_RANGE + 1 ,
744- 2 + NUM_PCS_TO_BE_SHIFTED + NUM_ORDERED_RANGE + 1 ,
760+ NUM_OP_QUEUE_TO_BE_SHIFTED + NUM_ORDERED_RANGE + 1 ,
761+ 2 + NUM_OP_QUEUE_TO_BE_SHIFTED + NUM_ORDERED_RANGE + 1 ,
762+ 2 + NUM_PCS_TO_BE_SHIFTED + NUM_OP_QUEUE_TO_BE_SHIFTED + NUM_ORDERED_RANGE + 1 ,
745763 NUM_CONCATENATED_POLYS);
746764
747765 static constexpr size_t PROOF_LENGTH =
@@ -1249,4 +1267,17 @@ class TranslatorFlavor {
12491267 using VerifierCommitments = VerifierCommitments_<Commitment, VerificationKey>;
12501268};
12511269
1270+ // Guard against drift between the runtime PCS entity lists and their compile-time counts;
1271+ // REPEATED_COMMITMENTS and PROOF_LENGTH depend on these counts and desync silently otherwise.
1272+ static_assert (detail::ref_array_extent<decltype (std::declval<TranslatorFlavor::AllEntities<TranslatorFlavor::FF>&>()
1273+ .get_pcs_unshifted())>::value ==
1274+ TranslatorFlavor::NUM_PCS_UNSHIFTED,
1275+ " get_pcs_unshifted() entity count must equal NUM_PCS_UNSHIFTED. If you added a witness entity, "
1276+ " update both the runtime list and NUM_UNSHIFTED_WITNESSES_WITHOUT_CONCATENATED." );
1277+ static_assert (detail::ref_array_extent<decltype (std::declval<TranslatorFlavor::AllEntities<TranslatorFlavor::FF>&>()
1278+ .get_pcs_to_be_shifted())>::value ==
1279+ TranslatorFlavor::NUM_PCS_TO_BE_SHIFTED,
1280+ " get_pcs_to_be_shifted() entity count must equal NUM_PCS_TO_BE_SHIFTED. If you added a to-be-shifted "
1281+ " entity, update both the runtime list and NUM_TO_BE_SHIFTED." );
1282+
12521283} // namespace bb
0 commit comments