From 4f272f041ce54a49a01265b7ebda85525a5502a4 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 28 Jun 2026 17:59:15 +0100 Subject: [PATCH 01/72] Mark PCS fixed windows Permanent in verifier memory planner What: Change the memory lifetime of the four PCS fixed windows (rot_points, x1_powers, q_com, q_eval_set) in VerifierMemoryLayout::new from MemoryLifetime::Phase(MemoryPhase::PcsFixed) to MemoryLifetime::Permanent, and remove the now-unused MemoryPhase::PcsFixed enum variant. Why: These windows are not transient scratch. They are written during PCS preparation and then read across several *later* phases: x1_powers feeds both the rolled q_eval fold (PcsQEvalSourceTable) and the fused final MSM (PcsFinalMsm), while rot_points/q_eval_set feed the f_eval interpolation. Because MemoryLifetime::intersects treats two distinct Phase values as never co-live, tagging these long-lived windows with their own phase made MemoryMap::validate() blind to any overlap between them and the PCS scratch that consumes them. The planner's central non-overlap invariant therefore silently did not cover these windows. They were safe in practice only because their fixed theta-relative addresses (words 52-201) happen to sit below the commitment region while all consuming scratch is allocated above it, a property of the hard-coded address map rather than something the validator guaranteed. How: Nothing ever reuses these byte ranges, so the correct lifetime is Permanent, which forces validate() to reject any future overlap against them. With the four registrations switched over, MemoryPhase::PcsFixed has no remaining users and is deleted. An explanatory comment records the lifetime invariant at the allocation site. No address, size, or rendered output changes; all 28 layout unit tests and the full crate build pass. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/layout/memory.rs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 55bc5acb2..4d8164d05 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -132,8 +132,6 @@ pub(crate) enum MemoryPhase { LagrangeBatchInvert, /// Compact quotient VM temps and stack. QuotientVm, - /// Historical fixed PCS windows rooted at `ROT_POINTS_MPTR`. - PcsFixed, /// Source-address table used by the rolled q_eval fold. PcsQEvalSourceTable, /// Optional trace-only q_com MSM materialization. @@ -695,29 +693,41 @@ impl VerifierMemoryLayout { + meta.num_lookups + meta.num_trashcans; let committed_g1s = non_quotient_g1s + meta.num_quotients; + // The rot_points / x1_powers / q_com / q_eval_set windows are NOT + // transient scratch even though they sit in the theta scratch band. + // They are written during PCS preparation and then read across several + // *later* phases: `x1_powers` feeds both the rolled q_eval fold + // (`PcsQEvalSourceTable`) and the fused final MSM (`PcsFinalMsm`), + // while `rot_points`/`q_eval_set` feed the f_eval interpolation. Because + // `MemoryLifetime::intersects` treats two different `Phase`s as never + // co-live, tagging these as a dedicated phase would make + // `validate()` blind to any overlap between them and the PCS scratch + // that consumes them. Nothing ever reuses these byte ranges, so they + // are `Permanent`: the planner must guarantee they never overlap any + // other live region. let rot_points_mptr = Ptr::memory(arena.alloc_fixed( "rot_points", at_theta(theta_windows.rot_points_word), config.pcs.rot_points_words * WORD_BYTES, - MemoryLifetime::Phase(MemoryPhase::PcsFixed), + MemoryLifetime::Permanent, )); let x1_powers_mptr = Ptr::memory(arena.alloc_fixed( "x1_powers", at_theta(theta_windows.x1_powers_word), config.pcs.x1_powers_words * WORD_BYTES, - MemoryLifetime::Phase(MemoryPhase::PcsFixed), + MemoryLifetime::Permanent, )); let q_com_mptr = Ptr::memory(arena.alloc_fixed( "q_com_fixed_window", at_theta(theta_windows.q_com_word), config.pcs.q_com_words * WORD_BYTES, - MemoryLifetime::Phase(MemoryPhase::PcsFixed), + MemoryLifetime::Permanent, )); let q_eval_set_mptr = Ptr::memory(arena.alloc_fixed( "q_eval_set", at_theta(theta_windows.q_eval_set_word), config.pcs.q_eval_set_words * WORD_BYTES, - MemoryLifetime::Phase(MemoryPhase::PcsFixed), + MemoryLifetime::Permanent, )); let q_eval_cptr_mptr = Ptr::memory(arena.alloc_fixed( "q_eval_cptr_slot", From bb6f46331fd41bb33312d04429f42218615f1c1e Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 28 Jun 2026 18:21:55 +0100 Subject: [PATCH 02/72] Model selector accumulator phase span What: add an inclusive phase-span memory lifetime and tag selector_accumulators as live from QuotientVm through PcsFinalMsm. Why: selector accumulators are written during quotient evaluation and read by later PCS phases, so a single PcsFinalMsm phase let validation miss unsafe overlaps. How: teach MemoryLifetime::intersects about phase spans, apply the span to selector_accumulators, and cover the behavior with memory-layout regression tests. --- .../src/lowering/layout/memory.rs | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 4d8164d05..68955229d 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -11,7 +11,7 @@ //! historical addresses in the generated verifier, gives each range a name and //! lifetime, and rejects accidental overlap when two live ranges can coexist. //! Intentional scratch reuse is modeled by assigning the same byte range to -//! disjoint `MemoryPhase`s. +//! disjoint `MemoryPhase`s or non-overlapping phase spans. //! //! This is not a packing allocator yet. The first version is deliberately //! conservative: it names the old layout, validates it, and centralizes all @@ -160,6 +160,11 @@ pub(crate) enum MemoryLifetime { /// Region is live only during the named phase. Regions in different phases /// may reuse the same byte range. Phase(MemoryPhase), + /// Region is written in one phase and read through a later phase. + PhaseSpan { + start: MemoryPhase, + end: MemoryPhase, + }, } impl MemoryLifetime { @@ -168,6 +173,20 @@ impl MemoryLifetime { match (self, other) { (Self::Permanent, _) | (_, Self::Permanent) => true, (Self::Phase(lhs), Self::Phase(rhs)) => lhs == rhs, + (Self::Phase(phase), Self::PhaseSpan { start, end }) + | (Self::PhaseSpan { start, end }, Self::Phase(phase)) => { + start <= phase && phase <= end + } + ( + Self::PhaseSpan { + start: lhs_start, + end: lhs_end, + }, + Self::PhaseSpan { + start: rhs_start, + end: rhs_end, + }, + ) => lhs_start <= rhs_end && rhs_start <= lhs_end, } } } @@ -479,7 +498,7 @@ pub(crate) struct VerifierMemoryLayout { pub(crate) trashcan_comms_mptr_base: Ptr, pub(crate) quotient_limb_comms_mptr_base: Ptr, /// First byte after all decompressed proof commitments. Selector - /// accumulators are live here during final linearization/final MSM. + /// accumulators are written by quotient evaluation and read by PCS MSMs. pub(crate) selector_acc_mptr: usize, /// Reuses selector-accumulator bytes during the earlier Lagrange batch /// inversion phase. @@ -779,7 +798,10 @@ impl VerifierMemoryLayout { comms_mptr_base.value().as_usize(), commitments_len, selector_len, - MemoryLifetime::Phase(MemoryPhase::PcsFinalMsm), + MemoryLifetime::PhaseSpan { + start: MemoryPhase::QuotientVm, + end: MemoryPhase::PcsFinalMsm, + }, ); let batch_invert_scratch_mptr = { let mut scratch = arena.scratch_allocator(selector_acc_mptr); @@ -1111,6 +1133,25 @@ mod tests { map.validate().expect("disjoint scratch lifetimes"); } + #[test] + fn phase_spans_cover_each_phase_in_their_range() { + let lifetime = MemoryLifetime::PhaseSpan { + start: MemoryPhase::QuotientVm, + end: MemoryPhase::PcsFinalMsm, + }; + + for phase in [ + MemoryPhase::QuotientVm, + MemoryPhase::PcsQEvalSourceTable, + MemoryPhase::PcsQComTrace, + MemoryPhase::PcsFinalMsm, + ] { + assert!(lifetime.intersects(&MemoryLifetime::Phase(phase))); + } + assert!(!lifetime.intersects(&MemoryLifetime::Phase(MemoryPhase::LagrangeBatchInvert))); + assert!(!lifetime.intersects(&MemoryLifetime::Phase(MemoryPhase::PcsPairing))); + } + #[test] fn unaligned_regions_fail() { let mut map = MemoryMap::default(); @@ -1423,6 +1464,35 @@ mod tests { assert_eq!(region.len, 4 * G1_MSM_PAIR_BYTES); } + #[test] + fn selector_accumulators_are_live_from_quotient_to_final_msm() { + let meta = ConstraintSystemMeta { + num_simple_selectors: 1, + ..ConstraintSystemMeta::default() + }; + let vk = synthetic_vk(); + let layout = VerifierMemoryLayout::new( + &meta, + &vk, + Ptr::memory(0x1000), + VerifierMemoryLayoutConfig::default(), + ); + let selector = layout + .map + .region("selector_accumulators") + .expect("selector accumulators registered"); + + assert_eq!( + selector.lifetime, + MemoryLifetime::PhaseSpan { + start: MemoryPhase::QuotientVm, + end: MemoryPhase::PcsFinalMsm, + } + ); + assert_eq!(layout.batch_invert_scratch_mptr, layout.selector_acc_mptr); + layout.validate().expect("earlier batch inversion may reuse selector bytes"); + } + #[test] fn trace_log_word_is_registered_after_scratch_regions() { let meta = ConstraintSystemMeta { From 4352a7116214f8816a3e8d4b7bd1c39ac6bea7dc Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 28 Jun 2026 18:22:51 +0100 Subject: [PATCH 03/72] Use pairing batch hash size constant What: size the accumulator pairing batch region with PAIRING_BATCH_HASH_BYTES. Why: the old expression only matched the hash frame because current G2 and G1 encodings have a coincidental ratio. How: import the named accumulator hash-frame constant and keep the existing memory-region alias pointed at it. --- .../src/lowering/layout/memory.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 68955229d..25584983a 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -21,13 +21,13 @@ use std::collections::BTreeMap; pub(crate) use crate::lowering::layout::{ - ACC_MSM_MIN_SCRATCH_BYTES, G1ADD_INPUT_BYTES, G1_BYTES, G1_MSM_PAIR_BYTES, G1_WORDS, - LOW_MEMORY_SCRATCH_START, MODEXP_FRAME_BYTES, MODEXP_SCRATCH_BYTES, - PAIRING_STATIC_WORKING_WORDS, PAIRING_TWO_PAIR_BYTES, PCS_PAIRING_SCRATCH_START, - PCS_STATIC_WORKING_WORDS, QUOTIENT_RETURN_BUFFER_START, SOLIDITY_FREE_MEMORY_POINTER_SLOT, - SOLIDITY_RESERVED_MEMORY_BYTES, SOLIDITY_SCRATCH_SPACE_BYTES, SOLIDITY_ZERO_SLOT, - TRANSCRIPT_BUFFER_START, VERIFIER_RETURN_BUFFER_START, VK_CONSTRUCTOR_PAYLOAD_START, - WORD_BYTES, + accumulator::PAIRING_BATCH_HASH_BYTES, ACC_MSM_MIN_SCRATCH_BYTES, G1ADD_INPUT_BYTES, G1_BYTES, + G1_MSM_PAIR_BYTES, G1_WORDS, LOW_MEMORY_SCRATCH_START, MODEXP_FRAME_BYTES, + MODEXP_SCRATCH_BYTES, PAIRING_STATIC_WORKING_WORDS, PAIRING_TWO_PAIR_BYTES, + PCS_PAIRING_SCRATCH_START, PCS_STATIC_WORKING_WORDS, QUOTIENT_RETURN_BUFFER_START, + SOLIDITY_FREE_MEMORY_POINTER_SLOT, SOLIDITY_RESERVED_MEMORY_BYTES, + SOLIDITY_SCRATCH_SPACE_BYTES, SOLIDITY_ZERO_SLOT, TRANSCRIPT_BUFFER_START, + VERIFIER_RETURN_BUFFER_START, VK_CONSTRUCTOR_PAYLOAD_START, WORD_BYTES, }; use crate::lowering::{ encoding::{ConstraintSystemMeta, Ptr}, @@ -39,8 +39,7 @@ use crate::lowering::{ /// The template starts this frame at `0x100`, writes a one-word domain tag, /// then four G1 points: KZG rhs/lhs and accumulator rhs/lhs. The last copy ends /// at `0x320`, so the registered range is `[0x100, 0x320)`. -const ACCUMULATOR_PAIRING_BATCH_BYTES: usize = - PAIRING_TWO_PAIR_BYTES - G1ADD_INPUT_BYTES + WORD_BYTES; +const ACCUMULATOR_PAIRING_BATCH_BYTES: usize = PAIRING_BATCH_HASH_BYTES; // Fixed word offsets from `THETA_MPTR`. // From bafd8d7f1cf2d677a9cf35eedff44108cd4c3436 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 28 Jun 2026 18:24:02 +0100 Subject: [PATCH 04/72] Account theta windows in trace scratch bound What: include rot_points, x1_powers, q_com, q_eval_set, and q_eval_cptr ends when placing trace_u256_log_word. Why: the trace scratch bound should stay correct if theta-window caps move beyond today's g1_identity guard. How: add each theta-window end to the max-bound list and cover the placement with an oversized-window regression test. --- .../src/lowering/layout/memory.rs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 25584983a..437311cb4 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -875,6 +875,11 @@ impl VerifierMemoryLayout { vk_start + vk.len(), challenge_start + meta.challenge_indices.len() * WORD_BYTES, theta_start + theta_windows.rot_points_word * WORD_BYTES, + rot_points_mptr.value().as_usize() + config.pcs.rot_points_words * WORD_BYTES, + x1_powers_mptr.value().as_usize() + config.pcs.x1_powers_words * WORD_BYTES, + q_com_mptr.value().as_usize() + config.pcs.q_com_words * WORD_BYTES, + q_eval_set_mptr.value().as_usize() + config.pcs.q_eval_set_words * WORD_BYTES, + q_eval_cptr_mptr.value().as_usize() + WORD_BYTES, g1_identity_mptr.value().as_usize() + G1_BYTES, reversed_evals_mptr.value().as_usize() + meta.num_evals * WORD_BYTES, comms_mptr_base.value().as_usize() + commitments_len, @@ -1522,6 +1527,52 @@ mod tests { layout.validate().expect("trace log word must not overlap"); } + #[test] + fn trace_log_word_accounts_for_theta_window_region_ends() { + let meta = ConstraintSystemMeta::default(); + let vk = synthetic_vk(); + let window_words = 4096; + let config = VerifierMemoryLayoutConfig { + pcs: PcsMemoryRequirements { + rot_points_words: window_words, + x1_powers_words: window_words, + q_com_words: window_words, + q_eval_set_words: window_words, + ..PcsMemoryRequirements::default() + }, + ..VerifierMemoryLayoutConfig::default() + }; + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + + for (name, end) in [ + ( + "rot_points", + layout.rot_points_mptr.value().as_usize() + window_words * WORD_BYTES, + ), + ( + "x1_powers", + layout.x1_powers_mptr.value().as_usize() + window_words * WORD_BYTES, + ), + ( + "q_com_fixed_window", + layout.q_com_mptr.value().as_usize() + window_words * WORD_BYTES, + ), + ( + "q_eval_set", + layout.q_eval_set_mptr.value().as_usize() + window_words * WORD_BYTES, + ), + ( + "q_eval_cptr_slot", + layout.q_eval_cptr_mptr.value().as_usize() + WORD_BYTES, + ), + ] { + assert!( + layout.trace_u256_mptr >= end, + "trace log word should be after {name}" + ); + } + } + #[test] fn batch_invert_scratch_region_tracks_instance_shape() { let meta = ConstraintSystemMeta { From de3bd7ea4fce8490578a0c5361d3e58f53a58054 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 28 Jun 2026 18:32:35 +0100 Subject: [PATCH 05/72] Reserve fused add-product constants early What: reserve constant slots for product-add fused opcodes before emitting the base expression. Why: emitting the base can add enough constants to push the product scalar past the u8 slot checked earlier, causing codegen to panic. How: insert the fused product scalar with const_slot before base emission and add a regression with 256 base constants followed by a fused mem*mem*const add. --- .../src/lowering/quotient_numerator/vm/mod.rs | 12 +++++++ .../solidity-verifier/src/lowering/tests.rs | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index affd899a2..babf24f0e 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -1593,6 +1593,7 @@ impl QuotientProgramBuilder { return false; }; + self.reserve_product_add_consts(product); self.emit_expr(base); self.emit_product_add(product); true @@ -1641,6 +1642,17 @@ impl QuotientProgramBuilder { } } + /// Reserve coefficient slots before another expression can grow the table. + fn reserve_product_add_consts(&mut self, product: QuotientProductAdd) { + match product { + QuotientProductAdd::MemMemConstU8 { scalar, .. } + | QuotientProductAdd::ConstU8Mem { scalar, .. } => { + self.const_slot(scalar); + } + QuotientProductAdd::MemMem { .. } => {} + } + } + /// Emit one fused add-mul accumulator operation. fn emit_product_add(&mut self, product: QuotientProductAdd) { match product { diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index d3ea64048..b55384bbd 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -2439,6 +2439,41 @@ fn quotient_vm_pow5_rejects_near_miss_product_shapes() { ); } +#[test] +fn quotient_vm_add_product_reserves_scalar_before_base_constants() { + let lhs = 0xaa0; + let rhs = 0xac0; + let mut values = HashMap::new(); + values.insert(lhs, Fq::from(149u64)); + values.insert(rhs, Fq::from(157u64)); + + let mut base = QuotientExpr::Const(U256::ZERO); + for value in 1..=255u64 { + base = quotient_add_expr(base, QuotientExpr::Const(U256::from(value))); + } + let product = quotient_mul_expr( + quotient_mul_expr( + QuotientExpr::Mem(QuotientMem::Literal(lhs)), + QuotientExpr::Mem(QuotientMem::Literal(rhs)), + ), + QuotientExpr::Const(U256::from(300u64)), + ); + let expr = quotient_add_expr(base, product); + + let expected = eval_quotient_expr_for_test(&expr, &values); + let mut builder = QuotientProgramBuilder::default(); + builder.emit_expr(&expr); + + assert!( + builder.bytes.contains(&Q_OP_ADD_MUL_MEM_MEM_CONST_U8), + "product should stay on the fused add-mul path" + ); + assert_eq!( + eval_quotient_vm_for_test(&builder.bytes, &builder.consts, &values), + expected + ); +} + #[test] fn quotient_vm_limb_subshape_matches_direct_expr_eval() { let mut values = HashMap::new(); From 7b498f75fd2f1c4fa65c452206763b3661a3a3b9 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 28 Jun 2026 18:34:49 +0100 Subject: [PATCH 06/72] Reserve limb decomposition constants early What: reserve limb-shape coefficient slots before emitting the extracted residue expression. Why: residue emission can add enough constants to invalidate the earlier u8-slot preflight and panic in emit_limb_shape. How: call const_slot for each recognized limb coefficient before residue emission and add a regression with a LIN7 subshape plus 256 residue constants. --- .../src/lowering/quotient_numerator/vm/mod.rs | 17 +++++++ .../solidity-verifier/src/lowering/tests.rs | 47 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index babf24f0e..eac35dfbd 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -1363,6 +1363,7 @@ impl QuotientProgramBuilder { if !self.limb_shape_has_u8_const_slots(&shape) { return false; } + self.reserve_limb_shape_consts(&shape); self.emit_expr(&residue); self.emit_limb_shape(shape); self.op_binary(Q_OP_ADD); @@ -1428,6 +1429,22 @@ impl QuotientProgramBuilder { self.peek_u8_const_slots(&coeffs).is_some() } + /// Reserve coefficient slots before residue emission can grow the table. + fn reserve_limb_shape_consts(&mut self, shape: &QuotientLimbShape) { + match shape { + QuotientLimbShape::Lin7 { terms } | QuotientLimbShape::Bilin7Row { terms, .. } => { + for (coeff, _) in terms { + self.const_slot(*coeff); + } + } + QuotientLimbShape::Bilin7Pairwise { coeffs, .. } => { + for coeff in coeffs { + self.const_slot(*coeff); + } + } + } + } + /// Emit the byte-level representation of a pre-validated limb shape. fn emit_limb_shape(&mut self, shape: QuotientLimbShape) { match shape { diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index b55384bbd..dfa3c71ed 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -2521,6 +2521,53 @@ fn quotient_vm_limb_subshape_matches_direct_expr_eval() { ); } +#[test] +fn quotient_vm_limb_decomposition_reserves_shape_coeffs_before_residue() { + let mut values = HashMap::new(); + let mut expr = QuotientExpr::Const(U256::ZERO); + + for i in 0..7u32 { + let ptr = 0xb00 + i * WORD_BYTES as u32; + values.insert(ptr, Fq::from(151 + i as u64)); + expr = quotient_add_expr( + expr, + quotient_scale_expr( + Fq::from(19 + i as u64), + QuotientExpr::Mem(QuotientMem::Literal(ptr)), + ), + ); + } + + for i in 0..256u32 { + let ptr = 0x2000 + i * 0x40; + values.insert(ptr, Fq::from(401 + i as u64)); + expr = quotient_add_expr( + expr, + quotient_scale_expr( + Fq::from(1000 + i as u64), + QuotientExpr::Mem(QuotientMem::Literal(ptr)), + ), + ); + } + + let expected = eval_quotient_expr_for_test(&expr, &values); + let mut builder = QuotientProgramBuilder::with_limb_vm_ops(true); + builder.emit_expr(&expr); + + assert!( + builder.consts.len() > u8::MAX as usize, + "residue should grow the constant table past u8" + ); + assert!( + builder.bytes.contains(&Q_OP_LIN7), + "larger affine sums should still extract LIN7 subshapes" + ); + assert_eq!( + eval_quotient_vm_for_test(&builder.bytes, &builder.consts, &values), + expected + ); +} + #[test] fn quotient_vm_limb_subshape_inside_conditional_product_matches_direct_expr_eval() { let mut values = HashMap::new(); From 5d7afe9b9f94f8ca7882341bb2c8cba423a1e2e6 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 28 Jun 2026 19:04:48 +0100 Subject: [PATCH 07/72] Guard rotated public instance queries in Solidity codegen What: add regression coverage that the Solidity generator rejects rotated non-committed instance queries, and clarify the code comments around the single local public-instance evaluation. How: keep the existing constructor-time Rotation::cur guard, document why non-committed instance_eval is a single word, add a debug assertion in quotient lowering, and replace two trace/layout magic literals with named constants. Why: the lowering path only reconstructs the non-committed public-input polynomial at the current rotation, so rotated instance queries must fail at codegen time instead of producing a verifier that disagrees with native Midfall. --- proofs/solidity-verifier/src/builder/api.rs | 3 + .../src/lowering/encoding/mod.rs | 9 +-- .../solidity-verifier/src/lowering/kzg/mod.rs | 6 +- .../src/lowering/layout/mod.rs | 2 + .../src/lowering/quotient_numerator/vm/mod.rs | 7 +++ .../solidity-verifier/src/lowering/tests.rs | 59 ++++++++++++++++++- 6 files changed, 78 insertions(+), 8 deletions(-) diff --git a/proofs/solidity-verifier/src/builder/api.rs b/proofs/solidity-verifier/src/builder/api.rs index 497a59f81..52ae43f99 100644 --- a/proofs/solidity-verifier/src/builder/api.rs +++ b/proofs/solidity-verifier/src/builder/api.rs @@ -49,6 +49,9 @@ impl<'a> SolidityGenerator<'a> { if let Some(acc_encoding) = config.accumulator { acc_encoding.validate_for_num_instances(config.num_instances)?; } + // Non-committed instance evaluations are reconstructed once from the + // public-input polynomial at the current rotation. Reject rotated + // instance queries until that path is keyed by `(column, rotation)`. if let Some((column, rotation)) = vk .cs() .instance_queries() diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index 5ddf9bcdf..6fe3e0b22 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -249,7 +249,9 @@ pub(crate) struct Data { /// User challenge words. pub(crate) challenges: Vec, - /// Locally-computed non-committed instance evaluation. + /// Locally-computed non-committed instance evaluation at `Rotation::cur()`. + /// `SolidityGenerator::try_new` rejects rotated instance queries, so this + /// stays a single word instead of a `(column, rotation)` map. pub(crate) instance_eval: Word, /// Per-(committed-instance-column, rotation): the calldata word for /// that committed instance evaluation. Empty when @@ -273,8 +275,7 @@ pub(crate) struct Data { pub(crate) computed_quotient_eval: Word, /// Word offset (in the verifier's static memory map) of the start of - /// the per-category EIP-2537-padded commitment region. See the - /// `KNOWN BUG` block in `Data::new` for the layout convention. + /// the per-category EIP-2537-padded commitment region. pub(crate) comms_mptr_base: Ptr, /// Memory base of the decoded-evals buffer (Optimisation H3). The /// transcript-side `evaluations` loop spills the decoded scalar value to @@ -795,7 +796,7 @@ impl EcPoint { /// Infinite iterator of consecutive padded G1 points. pub(crate) fn range(base: impl Into) -> impl Iterator { let base = base.into().base; - (0..).map(move |idx| EcPoint::new(base + 4 * idx)) + (0..).map(move |idx| EcPoint::new(base + G1_WORDS * idx)) } /// Return the pointer to the first word. diff --git a/proofs/solidity-verifier/src/lowering/kzg/mod.rs b/proofs/solidity-verifier/src/lowering/kzg/mod.rs index 0fb662402..b49763380 100644 --- a/proofs/solidity-verifier/src/lowering/kzg/mod.rs +++ b/proofs/solidity-verifier/src/lowering/kzg/mod.rs @@ -1189,7 +1189,7 @@ pub(crate) fn computations( )); lines.push(format!( "trace_point({}, {trace_scratch:#x})", - 40000 + set_idx + trace::PCS_Q_COM_BASE + set_idx as u64 )); continue; } @@ -1272,11 +1272,11 @@ pub(crate) fn computations( )); lines.push(format!( "if iszero(q_com_trace_ok_{set_idx}) {{ mstore(TRACE_U256_MPTR, {}) revert(TRACE_U256_MPTR, {WORD_BYTES:#x}) }}", - 40000 + set_idx + trace::PCS_Q_COM_BASE + set_idx as u64 )); lines.push(format!( "trace_point({}, {trace_scratch:#x})", - 40000 + set_idx + trace::PCS_Q_COM_BASE + set_idx as u64 )); } blocks.push(lines); diff --git a/proofs/solidity-verifier/src/lowering/layout/mod.rs b/proofs/solidity-verifier/src/lowering/layout/mod.rs index 1c885ea67..8612fc319 100644 --- a/proofs/solidity-verifier/src/lowering/layout/mod.rs +++ b/proofs/solidity-verifier/src/lowering/layout/mod.rs @@ -591,6 +591,7 @@ pub(crate) mod trace { pub(crate) const PROOF_COMMIT_BASE: usize = 10_000; // Proof G1 reads. pub(crate) const PROOF_EVAL_BASE: usize = 20_000; // Proof scalar eval reads. pub(crate) const QUOTIENT_IDENTITY_BASE: u64 = 30_000; // Quotient identities. + pub(crate) const PCS_Q_COM_BASE: u64 = 40_000; // PCS q_com points. pub(crate) const PCS_SERIALIZED_POINT_SET_BASE: u64 = 41_000; // PCS point sets. pub(crate) const SELECTOR_FOLD_BASE: usize = 60_000; // Selector accumulators. } @@ -680,6 +681,7 @@ mod tests { assert_eq!(trace::PROOF_COMMIT_BASE, 10_000); assert_eq!(trace::PROOF_EVAL_BASE, 20_000); assert_eq!(trace::QUOTIENT_IDENTITY_BASE, 30_000); + assert_eq!(trace::PCS_Q_COM_BASE, 40_000); assert_eq!(trace::PCS_SERIALIZED_POINT_SET_BASE, 41_000); assert_eq!(trace::SELECTOR_FOLD_BASE, 60_000); } diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index eac35dfbd..426633b7a 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -2435,6 +2435,13 @@ impl QuotientExpressionEnv for DataQuotientExpressionEnv<'_> { .expect("committed instance eval present"), ) } else { + // The builder rejects rotated instance queries before lowering, so + // the direct public-input column always uses the one local + // Lagrange evaluation computed for `Rotation::cur()`. + debug_assert_eq!( + rotation, 0, + "rotated public instance query reached lowering" + ); word_to_quotient_expr(self.data.instance_eval) } } diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index dfa3c71ed..81982270a 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -291,6 +291,60 @@ fn lowering_plan_test_vk() -> ( (params, vk) } +#[derive(Clone, Debug)] +struct RotatedPublicInstanceCircuit; + +impl Circuit for RotatedPublicInstanceCircuit { + type Config = (); + type FloorPlanner = SimpleFloorPlanner; + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advice = meta.advice_column(); + let committed_instance = meta.instance_column(); + let public_instance = meta.instance_column(); + + meta.create_gate("rotated public instance", |meta| { + let advice = meta.query_advice(advice, Rotation::cur()); + let committed = meta.query_instance(committed_instance, Rotation::cur()); + let public_next = meta.query_instance(public_instance, Rotation::next()); + Constraints::without_selector(vec![( + "rotated public instance", + advice + committed + public_next, + )]) + }); + } + + fn synthesize( + &self, + _config: Self::Config, + _layouter: impl Layouter, + ) -> Result<(), PlonkError> { + Ok(()) + } +} + +#[test] +fn generator_rejects_rotated_non_committed_instance_queries() { + let mut rng = ChaCha8Rng::seed_from_u64(8); + let params = ParamsKZG::::unsafe_setup(4, &mut rng); + let circuit = RotatedPublicInstanceCircuit; + let vk = keygen_vk_with_k::, _>(¶ms, &circuit, 4) + .expect("test circuit VK should build"); + + assert!(matches!( + SolidityGenerator::try_new(¶ms, &vk, GeneratorConfig::new(1, 1)), + Err(GeneratorError::RotatedInstanceQuery { + column: 1, + rotation: 1, + }) + )); +} + #[test] fn scalar_le_to_be_word_reverses_exactly_one_word() { let mut le = [0u8; 32]; @@ -1432,7 +1486,10 @@ fn differential_trace_hooks_cover_expected_categories() { "serialized PCS point sets", "trace::PCS_SERIALIZED_POINT_SET_BASE + set_idx as u64", ), - ("PCS q_com commitments", "40000 + set_idx"), + ( + "PCS q_com commitments", + "trace::PCS_Q_COM_BASE + set_idx as u64", + ), ] { assert!( pcs_source.contains(needle), From a01b1ea16afe88fec71040e834e65efec9d5c91b Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 17 Jul 2026 22:05:25 +0100 Subject: [PATCH 08/72] Fix transcript buffer sizing and quotient VM const-table overflow Size the pre-first-squeeze transcript run from every advice phase up to and including the first challenge-bearing phase, not just phase 0, so the valid "advice in an early phase, challenge in a later phase" shape no longer under-sizes the buffer and overruns VK_MPTR. In the quotient VM limb-decomposition and product-add peepholes, re-check that fused opcode coefficients still fit their one-byte constant slots after intervening residue/base emission, falling back to generic ops instead of panicking in the u8::try_from(...).expect(...). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/lowering/abi/proof.rs | 71 +++++++++++++++-- .../src/lowering/quotient_numerator/vm/mod.rs | 77 ++++++++++++++++--- .../solidity-verifier/src/lowering/tests.rs | 40 ++++++++++ proofs/solidity-verifier/src/lowering/vk.rs | 11 ++- 4 files changed, 182 insertions(+), 17 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/abi/proof.rs b/proofs/solidity-verifier/src/lowering/abi/proof.rs index 3e085d1cb..82c32efb8 100644 --- a/proofs/solidity-verifier/src/lowering/abi/proof.rs +++ b/proofs/solidity-verifier/src/lowering/abi/proof.rs @@ -307,18 +307,44 @@ pub(crate) struct TranscriptBufferLayout { impl TranscriptBufferLayout { /// Derive transcript-buffer bounds from proof calldata shape and instances. - pub(crate) fn from_proof_layout(proof: &ProofCalldataLayout, num_instances: usize) -> Self { + /// + /// `phase_challenge_counts[i]` is the number of Fiat-Shamir challenges the + /// native schedule squeezes for user phase `i` (i.e. `num_user_challenges`). + /// It selects which advice commitments share the pre-first-squeeze run: a + /// phase that owns no challenge does not trigger a squeeze, so its advices + /// accumulate into the same run as the following phase's. An empty slice is + /// treated as "no phase owns a challenge", which sums every advice phase as + /// a safe upper bound. + pub(crate) fn from_proof_layout( + proof: &ProofCalldataLayout, + num_instances: usize, + phase_challenge_counts: &[usize], + ) -> Self { let word_absorb = layout::transcript::WORD_ABSORB_BYTES; let g1_absorb = layout::transcript::G1_ABSORB_BYTES; let squeeze_cushion = layout::transcript::POST_SQUEEZE_CUSHION_WORDS * WORD_BYTES; - let phase_1_advices = - proof.advice_phases.first().map(|section| section.item_count).unwrap_or(0); + // Advices absorbed before the first challenge squeeze. The native + // verifier squeezes a challenge only for phases that own one, and the + // unconditional `theta` squeeze follows every user phase, so all advice + // phases up to and INCLUDING the first challenge-bearing phase are + // absorbed into a single streaming run before the first squeeze. + // Counting only the first phase under-sized the buffer for the valid + // "advice in an early phase, challenge in a later phase" shape + // (e.g. a SecondPhase RLC column) and let the G1 absorb loop overrun + // `VK_MPTR`; see `plan_allows_challenge_phase_beyond_advice_phases`. + let mut pre_squeeze_advices = 0usize; + for (phase, section) in proof.advice_phases.iter().enumerate() { + pre_squeeze_advices += section.item_count; + if phase_challenge_counts.get(phase).copied().unwrap_or(0) > 0 { + break; + } + } let initial_run_bytes = word_absorb + g1_absorb + word_absorb + num_instances * word_absorb - + phase_1_advices * g1_absorb + + pre_squeeze_advices * g1_absorb + squeeze_cushion; let eval_run_bytes = proof.quotient_limbs.byte_len @@ -464,7 +490,7 @@ mod tests { fn transcript_layout_matches_current_conservative_bound() { let protocol = protocol_shape(vec![64], vec![], 0, 0, 2); let proof = ProofCalldataLayout::from_protocol(&protocol, 0, 10, 3); - let transcript = TranscriptBufferLayout::from_proof_layout(&proof, 0); + let transcript = TranscriptBufferLayout::from_proof_layout(&proof, 0, &[1]); let first_phase_run = 32 + 128 + 32 + 64 * 128 + 32 * 32; assert!(transcript.words * WORD_BYTES >= first_phase_run); @@ -473,4 +499,39 @@ mod tests { 2 * G1_BYTES + (10 + 3) * WORD_BYTES + 32 * WORD_BYTES ); } + + #[test] + fn transcript_layout_covers_multi_phase_advice_before_first_squeeze() { + // Valid shape (see `plan_allows_challenge_phase_beyond_advice_phases`): + // phase 0 has advice but no challenge, so both phases' advices land in + // the same pre-first-squeeze run alongside a large public-input block. + let protocol = protocol_shape(vec![1, 9], vec![], 0, 0, 1); + let proof = ProofCalldataLayout::from_protocol(&protocol, 0, 12, 2); + let num_instances = 86; + let transcript = + TranscriptBufferLayout::from_proof_layout(&proof, num_instances, &[0, 1]); + + let word = layout::transcript::WORD_ABSORB_BYTES; + let g1 = layout::transcript::G1_ABSORB_BYTES; + let cushion = layout::transcript::POST_SQUEEZE_CUSHION_WORDS * WORD_BYTES; + + // Bytes actually absorbed before the first squeeze: vk_digest, + // committed_pi, num_instances length word, instance words, then BOTH + // phases' advices (phase 0 owns no challenge). + let true_initial_run = + word + g1 + word + num_instances * word + (1 + 9) * g1; + assert!( + transcript.words * WORD_BYTES >= true_initial_run, + "reserved {} bytes < true pre-squeeze run {}", + transcript.words * WORD_BYTES, + true_initial_run + ); + assert_eq!(transcript.initial_run_bytes, true_initial_run + cushion); + + // The old bound counted only phase 0's advice and therefore under-sized + // the buffer below the true run — confirm the fix was load-bearing. + let phase0_only_run = word + g1 + word + num_instances * word + 1 * g1; + assert!(phase0_only_run < true_initial_run); + assert!(phase0_only_run + cushion < true_initial_run); + } } diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index 426633b7a..aeb060bee 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -1357,7 +1357,7 @@ impl QuotientProgramBuilder { if !self.limb_vm_ops { return false; } - let Some((shape, residue)) = quotient_limb_subshape(expr) else { + let Some((shape, residue, matched)) = quotient_limb_subshape(expr) else { return false; }; if !self.limb_shape_has_u8_const_slots(&shape) { @@ -1365,11 +1365,39 @@ impl QuotientProgramBuilder { } self.reserve_limb_shape_consts(&shape); self.emit_expr(&residue); - self.emit_limb_shape(shape); + // `limb_shape_has_u8_const_slots` was checked against the constant table + // before `emit_expr(&residue)`. Emitting the residue can insert new + // constants and push a shape coefficient past a one-byte constant slot, + // which would panic in `emit_limb_shape`'s `u8::try_from(...).expect(...)`. + // Re-check against the post-residue table: keep the fused limb opcode + // only while every coefficient still fits, otherwise emit the matched + // terms through the generic path (their sum equals the shape's value). + if self.limb_shape_has_u8_const_slots(&shape) { + self.emit_limb_shape(shape); + } else { + self.emit_affine_terms(&matched); + } self.op_binary(Q_OP_ADD); true } + /// Emit `Σ terms[i]` with generic stack ops, leaving one value on the stack. + /// + /// Each entry is one recognized affine/bilinear term (a scaled memory load + /// or product), so emitting it cannot re-enter the limb-decomposition + /// peephole, and its coefficients use `emit_const`'s u16-capable slots. This + /// is the panic-free fallback for a recognized limb shape whose coefficients + /// no longer fit one-byte constant slots after intervening emission. The + /// slice is always non-empty (a recognized subshape uses at least one term). + fn emit_affine_terms(&mut self, terms: &[QuotientExpr]) { + for (idx, term) in terms.iter().enumerate() { + self.emit_expr(term); + if idx > 0 { + self.op_binary(Q_OP_ADD); + } + } + } + /// Try to replace a full expression with one limb-specialized opcode. /// /// The const-slot preflight is part of the ABI justification: limb opcodes @@ -1606,16 +1634,39 @@ impl QuotientProgramBuilder { if !collect_product_leaves(product, &mut leaves) { return false; } - let Some(product) = self.product_add_macro(&leaves) else { + let Some(fused) = self.product_add_macro(&leaves) else { return false; }; - self.reserve_product_add_consts(product); + self.reserve_product_add_consts(fused); self.emit_expr(base); - self.emit_product_add(product); + // `product_add_macro` checked the fused scalar against the constant + // table as it stood *before* `emit_expr(base)`. Emitting `base` can + // insert new constants and push that scalar past a one-byte constant + // slot, which would panic in `emit_product_add`'s + // `u8::try_from(...).expect(...)`. Re-check against the post-`base` + // table: keep the fused opcode only while the scalar still fits, + // otherwise add the product through the generic path (its lone scalar + // goes through `emit_const`, which falls back to a u16 slot). + if self.product_add_fits_u8_slot(&fused) { + self.emit_product_add(fused); + } else { + self.emit_expr(product); + self.op_binary(Q_OP_ADD); + } true } + /// Whether the fused product-add scalar (if any) still lands in a one-byte + /// constant slot given the current constant table. + fn product_add_fits_u8_slot(&self, product: &QuotientProductAdd) -> bool { + match *product { + QuotientProductAdd::MemMemConstU8 { scalar, .. } + | QuotientProductAdd::ConstU8Mem { scalar, .. } => self.const_fits_u8_slot(scalar), + QuotientProductAdd::MemMem { .. } => true, + } + } + /// Recognize product leaves that can be encoded as one fused add-mul op. /// /// The fused forms require literal `u16` memory pointers and, where a @@ -2690,7 +2741,7 @@ pub(crate) fn quotient_pow5_base(expr: &QuotientExpr) -> Option<&QuotientExpr> { /// Extract one limb shape from a larger affine sum and return the residue. pub(crate) fn quotient_limb_subshape( expr: &QuotientExpr, -) -> Option<(QuotientLimbShape, QuotientExpr)> { +) -> Option<(QuotientLimbShape, QuotientExpr, Vec)> { let mut terms = Vec::new(); let mut constant = Fq::ZERO; if !collect_quotient_affine_terms(expr, Fq::ONE, &mut terms, &mut constant) { @@ -2708,15 +2759,23 @@ pub(crate) fn quotient_limb_subshape( return None; } + // Split the affine terms into the residue (unused terms plus the constant) + // and the matched terms that reconstruct `shape` as a plain sum. The matched + // terms are the panic-free fallback for `emit_limb_shape`: their sum equals + // the fused opcode's value, but each is a single scaled load/product that + // uses u16-capable constant loads. let used = used.into_iter().collect::>(); let mut residue = QuotientExpr::Const(quotient_fq_to_u256(constant)); + let mut matched = Vec::with_capacity(used.len()); for (idx, (coeff, term)) in terms.into_iter().enumerate() { + let scaled = quotient_scaled_term_expr(coeff, (*term).clone()); if used.contains(&idx) { - continue; + matched.push(scaled); + } else { + residue = quotient_sum_expr(residue, scaled); } - residue = quotient_sum_expr(residue, quotient_scaled_term_expr(coeff, (*term).clone())); } - Some((shape, residue)) + Some((shape, residue, matched)) } /// Recognize a whole affine foreign-field/ECC identity that can be evaluated diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index 81982270a..7d5203944 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -2445,6 +2445,46 @@ fn quotient_vm_bilin7_pairwise_matches_direct_expr_eval() { ); } +#[test] +fn quotient_vm_limb_decomposition_survives_const_table_overflow() { + // A large affine sum over consecutive limb pointers is emitted as a chain of + // LIN7 opcodes whose coefficients land in the one-byte constant table. With + // more than 256 distinct coefficients the table overflows a `u8` slot + // partway through emission. Before the fix, `emit_limb_shape`'s + // `u8::try_from(slot).expect(...)` panicked once the shape being emitted was + // preceded by enough residue constants; the decomposition path now re-checks + // the post-residue table and falls back to generic ops for the overflowing + // shape. This exercises that fallback and confirms it still evaluates the + // expression correctly. + let term_count = 300u32; + let mut values = HashMap::new(); + let mut expr = QuotientExpr::Const(U256::ZERO); + for k in 0..term_count { + let ptr = 0x1000 + k * 0x20; + values.insert(ptr, Fq::from(17 + k as u64)); + expr = quotient_add_expr( + expr, + // Coefficient `k + 2` keeps every term scaled (coeff 1 would drop the + // constant) and distinct, so the table grows one slot per term. + quotient_scale_expr(Fq::from(k as u64 + 2), QuotientExpr::Mem(QuotientMem::Literal(ptr))), + ); + } + + let expected = eval_quotient_expr_for_test(&expr, &values); + let mut builder = QuotientProgramBuilder::with_limb_vm_ops(true); + // The pre-fix builder panics inside this call for this input. + builder.emit_expr(&expr); + + assert!( + builder.consts.len() > u8::MAX as usize, + "test must overflow the one-byte constant table to exercise the fallback" + ); + assert_eq!( + eval_quotient_vm_for_test(&builder.bytes, &builder.consts, &values), + expected + ); +} + #[test] fn quotient_vm_pow5_matches_direct_expr_eval() { let ptr = 0xa20; diff --git a/proofs/solidity-verifier/src/lowering/vk.rs b/proofs/solidity-verifier/src/lowering/vk.rs index 480b68c37..44fd9e730 100644 --- a/proofs/solidity-verifier/src/lowering/vk.rs +++ b/proofs/solidity-verifier/src/lowering/vk.rs @@ -414,8 +414,9 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { // midnight-proofs verifiers the dominating run is whichever of the // following is largest: // (a) initial absorbs (vk_digest + committed_pi + num_instances - // + all instance scalars + all phase-1 advices) before the - // first user-phase challenge squeeze (`theta`), or + // + all instance scalars + every advice commitment up to and + // including the first challenge-bearing phase) before the first + // user-phase challenge squeeze (`theta` at the latest), or // (b) the evaluation block (all `num_evals` scalars) absorbed // after the `y` squeeze and before the next squeeze. // @@ -438,6 +439,10 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { meta.num_evals, meta.num_point_sets, ); - TranscriptBufferLayout::from_proof_layout(&proof_layout, num_instances) + TranscriptBufferLayout::from_proof_layout( + &proof_layout, + num_instances, + &meta.protocol.num_user_challenges, + ) } } From adc589fce1bb5e359eb5e38dd557a905c6475c7c Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 22:22:14 +0100 Subject: [PATCH 09/72] Bound the Lagrange batch-invert input run in the memory planner The generated Lagrange block writes num_instances + num_neg_lagranges + 1 denominator words in place starting at X_N_MPTR (theta word 26) and batch-inverts them, but the memory planner modeled only the prefix-product scratch half of that call: the input run was never registered as a region or bounded, batch_invert_scratch_bytes sized only the modexp scratch, and validate() capped just the PCS fixed windows. Nothing caps num_instances, so for large enough circuits the run silently overwrote state that is already live at Lagrange time: Q_EVAL_CPTR_MPTR (theta word 201, stored by the proof parser and read by the PCS q_eval fold) at num_instances + num_neg_lagranges >= 175, the virgin-zero G1_IDENTITY_MPTR (word 209, relied on as the committed-instance MSM base) at >= 183, and the decoded proof evaluations (word 220) at >= 194. Codegen succeeded but the deployed verifier rejected every honest proof, a silent memory-safety/completeness break. Extract batch_invert_input_words as the shared source of truth for the run size, record it on VerifierMemoryLayout, and make validate() reject any layout whose run reaches min(Q_EVAL_CPTR_WORD, G1_IDENTITY_WORD, REVERSED_EVALS_WORD), failing codegen with a clear error before any Solidity is rendered. A capacity check is used instead of an arena region because the run intentionally overlays the theta band and PCS fixed windows that are dead at Lagrange time but modeled Permanent, so registering it would produce fake overlaps; the existing benign spill into ROT_POINTS stays allowed. Add a boundary regression test covering the exact pass/fail instance counts. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/layout/memory.rs | 93 ++++++++++++++++--- 1 file changed, 82 insertions(+), 11 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 437311cb4..632e7ac20 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -502,6 +502,9 @@ pub(crate) struct VerifierMemoryLayout { /// Reuses selector-accumulator bytes during the earlier Lagrange batch /// inversion phase. pub(crate) batch_invert_scratch_mptr: usize, + /// Number of Fr words the generated Lagrange block writes in place at + /// `X_N_MPTR` as the batch-inversion input run. + pub(crate) batch_invert_input_words: usize, /// First quotient VM temporary. Also the canonical PCS scratch base once /// selector accumulators are accounted for. pub(crate) quotient_tmp_mptr: usize, @@ -620,6 +623,7 @@ impl VerifierMemoryLayout { .max() .expect("constructor G1MSM smoke bounds are non-empty"); let batch_invert_len = batch_invert_scratch_bytes(meta, config.num_instances); + let batch_invert_input_words = batch_invert_input_words(meta, config.num_instances); let quotient_return_len = (2 + meta.num_simple_selectors) * WORD_BYTES; let mut arena = MemoryArena::default(); @@ -961,6 +965,7 @@ impl VerifierMemoryLayout { quotient_limb_comms_mptr_base, selector_acc_mptr, batch_invert_scratch_mptr, + batch_invert_input_words, quotient_tmp_mptr, quotient_stack_mptr, pcs_q_eval_source_table_mptr, @@ -1056,6 +1061,29 @@ impl VerifierMemoryLayout { )); } + // The Lagrange block writes its batch-inversion input run in place at + // `X_N_MPTR` (theta word 26). The run intentionally spills past the + // theta band into the PCS fixed windows, which are written only after + // the Lagrange phase, so it cannot be registered as a region without + // fake overlaps; it is capacity-checked here instead. The first bytes + // that are already live at Lagrange time are the q_eval calldata + // cursor (written by the proof parser), the G1 identity slot (must + // stay virgin zero memory), and the decoded proof evaluations. + let lagrange_run_end = ThetaSlot::XN.word() + self.batch_invert_input_words; + let lagrange_run_cap = windows + .q_eval_cptr_word + .min(windows.g1_identity_word) + .min(windows.reversed_evals_word); + if lagrange_run_end > lagrange_run_cap { + return Err(format!( + "Lagrange batch-inversion input run overflows live verifier memory: \ + [X_N_MPTR, X_N_MPTR + {} word(s)) ends at theta word {lagrange_run_end}, \ + but Q_EVAL_CPTR_MPTR/G1_IDENTITY_MPTR/decoded evaluations are live from \ + theta word {lagrange_run_cap}; reduce the public instance count", + self.batch_invert_input_words + )); + } + self.map.validate()?; Ok(()) @@ -1073,25 +1101,31 @@ pub(crate) fn commitment_g1_count(meta: &ConstraintSystemMeta) -> usize { + meta.num_quotients } +/// Number of Fr words the generated Lagrange block writes in place starting at +/// `X_N_MPTR` as the batch-inversion input run. +/// +/// The input range covers: +/// - num_instances public Lagrange denominators, or one fallback word when +/// there are no public instances; +/// - `abs(rotation_last)` negative-row denominators; +/// - x_n - 1. +fn batch_invert_input_words(meta: &ConstraintSystemMeta, num_instances: usize) -> usize { + if num_instances == 0 { + meta.rotation_last.unsigned_abs() as usize + 2 + } else { + num_instances + meta.rotation_last.unsigned_abs() as usize + 1 + } +} + /// Scratch size required by the batched scalar-inversion helper. fn batch_invert_scratch_bytes(meta: &ConstraintSystemMeta, num_instances: usize) -> usize { // The template calls: // batch_invert(X_N_MPTR, mptr_end + WORD_BYTES, scratch, r) // - // The input range covers: - // - num_instances public Lagrange denominators, or one fallback word when - // there are no public instances; - // - `abs(rotation_last)` negative-row denominators; - // - x_n - 1. - // // For N inputs, the batched inversion stores N-2 prefix products and then // overlays one modexp frame at the current prefix pointer. Singletons use // only the frame. - let input_words = if num_instances == 0 { - meta.rotation_last.unsigned_abs() as usize + 2 - } else { - num_instances + meta.rotation_last.unsigned_abs() as usize + 1 - }; + let input_words = batch_invert_input_words(meta, num_instances); MODEXP_FRAME_BYTES + input_words.saturating_sub(2) * WORD_BYTES } @@ -1595,4 +1629,41 @@ mod tests { MODEXP_FRAME_BYTES + (5 + 3 + 1 - 2) * WORD_BYTES ); } + + #[test] + fn lagrange_batch_invert_input_run_is_bounded_by_live_memory() { + let meta = ConstraintSystemMeta { + rotation_last: -3, + ..ConstraintSystemMeta::default() + }; + let vk = synthetic_vk(); + let windows = ThetaWindowLayout::compatibility(); + let cap_words = windows + .q_eval_cptr_word + .min(windows.g1_identity_word) + .min(windows.reversed_evals_word) + - ThetaSlot::XN.word(); + + // Largest instance count whose denominator run plus `x_n - 1` still + // fits below the q_eval calldata cursor: num + |rotation_last| + 1. + let max_instances = cap_words - 3 - 1; + let config = VerifierMemoryLayoutConfig { + num_instances: max_instances, + ..VerifierMemoryLayoutConfig::default() + }; + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + assert_eq!(layout.batch_invert_input_words, cap_words); + layout.validate().expect("run ending below Q_EVAL_CPTR_MPTR is valid"); + + let config = VerifierMemoryLayoutConfig { + num_instances: max_instances + 1, + ..VerifierMemoryLayoutConfig::default() + }; + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let err = layout.validate().unwrap_err(); + assert!( + err.contains("Lagrange batch-inversion input run"), + "unexpected validation error: {err}" + ); + } } From b24be9e2b7e74b3de120e644945af90c42554974 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 22:41:49 +0100 Subject: [PATCH 10/72] Reject off-curve points in EIP-2537 G1/G2 encoders g1_to_u256s and g2_to_u256s returned the all-zero EIP-2537 word array whenever coordinates() yielded None. In midnight-curves, coordinates() returns None not only for the point at infinity but for any off-curve point, and the all-zero words are exactly the EIP-2537 encoding of the identity. An off-curve/corrupted point (e.g. an s_g2 from an unchecked deserialization path) was therefore silently baked into the generated verifier as the identity: NEG_S_G2 collapsing to infinity degenerates the e(w, -s*G2) KZG pairing term, and a malformed VK commitment becomes an identity commitment (a different circuit), both with no build error. Distinguish the genuine identity (is_identity()) from an invalid point and assert on the latter, so codegen fails loudly instead of emitting a soundness-broken constant. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/encoding/mod.rs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index 6fe3e0b22..9397f8f2a 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -13,6 +13,7 @@ use std::{ }; use ff::PrimeField; +use group::prime::PrimeCurveAffine; use itertools::{izip, Itertools}; use midnight_curves::{Coordinates, CurveAffine, Fq, G1Affine, G2Affine}; use midnight_proofs::plonk::{Any, Column, ConstraintSystem}; @@ -833,8 +834,18 @@ fn fp48_be_to_hi_lo(be: &[u8]) -> (U256, U256) { /// big-endian and split (hi=top 16 bytes padded into u256, lo=bottom 32 /// bytes). pub(crate) fn g1_to_u256s(ec_point: impl Borrow) -> [U256; 4] { - let Some(coords) = Option::>::from(ec_point.borrow().coordinates()) - else { + let point = ec_point.borrow(); + let Some(coords) = Option::>::from(point.coordinates()) else { + // `coordinates()` returns None for the identity *and* for any off-curve + // point. The all-zero words below are the EIP-2537 encoding of the point + // at infinity, so silently returning them for an off-curve input would + // bake an identity commitment into the verifier. Only the identity may + // take this path; anything else is a malformed/corrupted point and must + // fail codegen rather than degrade a soundness-critical constant. + assert!( + bool::from(point.is_identity()), + "refusing to EIP-2537 encode an off-curve G1 point as the identity" + ); return [U256::ZERO; 4]; }; let mut x_be = [0u8; BLS_FP_BYTES]; @@ -855,8 +866,16 @@ pub(crate) fn g1_to_u256s(ec_point: impl Borrow) -> [U256; 4] { /// in big-endian per coord. The midnight-curves convention matches: /// each `Fp` coordinate read via `to_repr()` returns LE bytes. pub(crate) fn g2_to_u256s(ec_point: impl Borrow) -> [U256; 8] { - let Some(coords) = Option::>::from(ec_point.borrow().coordinates()) - else { + let point = ec_point.borrow(); + let Some(coords) = Option::>::from(point.coordinates()) else { + // See `g1_to_u256s`: `coordinates()` also returns None for off-curve G2 + // points, whose all-zero encoding would collapse to the identity (e.g. + // a corrupted `s_g2` degenerating the `e(w, -s*G2)` pairing term). Only + // the genuine identity may be encoded as zeros. + assert!( + bool::from(point.is_identity()), + "refusing to EIP-2537 encode an off-curve G2 point as the identity" + ); return [U256::ZERO; 8]; }; From fc42518db31635f8cc9218d4014c7c43e75b1860 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 22:42:00 +0100 Subject: [PATCH 11/72] Validate the SRS G1 base equals the canonical generator generate_base_vk emits G1Affine::generator() as the VK header G1_BASE while taking g2/s_g2 from the deployer-supplied params, with nothing asserting params.g[0] == G. midnight-proofs' ParamsKZG accepts arbitrary g vectors, so a params file whose SRS base is c*G (c != 1) built without error. The generated verifier then computes the -v*G term of the KZG combination from G while every proof/VK commitment is over c*G, so the on-chain pairing check enforces a different (scaled) statement than the native Rust verifier: honest proofs are rejected, and accept/reject can diverge from the reference verifier, all silently at build time. g[0] is crate-private in midnight-proofs, but the commitment of the constant-1 polynomial equals g[0], and in the Lagrange basis that commitment is sum(g_lagrange). Assert sum(g_lagrange) == generator at codegen time so a mismatched SRS fails the build instead of producing a verifier that silently diverges from the reference verifier. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/vk.rs | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/vk.rs b/proofs/solidity-verifier/src/lowering/vk.rs index 44fd9e730..33e173465 100644 --- a/proofs/solidity-verifier/src/lowering/vk.rs +++ b/proofs/solidity-verifier/src/lowering/vk.rs @@ -6,7 +6,7 @@ //! generated verifier contracts. use ff::Field; -use group::{prime::PrimeCurveAffine, Curve}; +use group::{prime::PrimeCurveAffine, Curve, Group}; use itertools::chain; use midnight_curves::{Fq, G1Affine, G1Projective, G2Affine}; use ruint::aliases::U256; @@ -69,6 +69,27 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { // (4 / 8 u256 words respectively). We cannot read `params.g[0]` // directly (the field is crate-private in midnight-proofs), so // we use the canonical BLS12-381 generator. + // + // The generated verifier subtracts `v * G1_BASE` in the final KZG + // linearization while every commitment in the proof/VK is over the + // SRS base `g[0]`. If a deployer-supplied `params` had `g[0] != G`, + // the contract would enforce a different pairing equation than the + // native verifier (bricking honest proofs, or accepting evaluation + // claims for a scaled statement). Validate consistency at build + // time: the commitment of the constant-1 polynomial equals `g[0]`, + // and in the Lagrange basis that commitment is `sum(g_lagrange)`. + let g_lagrange = self.params.g_lagrange(); + let srs_g1_base = g_lagrange + .iter() + .copied() + .fold(G1Projective::identity(), |acc, g| acc + g) + .to_affine(); + assert_eq!( + srs_g1_base, + G1Affine::generator(), + "SRS G1 base (sum of g_lagrange) is not the canonical BLS12-381 \ + generator; the emitted G1_BASE would diverge from the commitment base" + ); let g1_pt: G1Affine = G1Affine::generator(); let g2_pt: G2Affine = self.params.g2().to_affine(); let neg_s_g2_pt: G2Affine = (-self.params.s_g2()).to_affine(); From c80cbc823f1b42b38773678841600765274c66ba Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 22:42:10 +0100 Subject: [PATCH 12/72] Invalidate reassigned variables in the limb7 chain rewriter record_yul_const_assignment recorded 'let name := literal' bindings for limb7 coefficient recognition but ignored non-let reassignments, because yul_let_assignment returns None for 'x := ...'. A stale literal therefore survived a later runtime reassignment, so a block that binds a variable to a limb7 coefficient literal and then reassigns it to a runtime value could be falsely matched: specialize_limb7_chains would replace the chain with a q_limb7 call baking in the literal while the original code multiplied by the reassigned runtime value, silently miscompiling the quotient identity (no codegen or Yul error). This is unreachable today only because gate-identity lines are SSA, but nothing asserts that and the Evaluator already emits non-let reassignment lines on other paths. Track constants via yul_assignment (let or not) and forget any variable reassigned to a non-constant value, so a stale coefficient can never be matched. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/quotient.rs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient.rs b/proofs/solidity-verifier/src/lowering/quotient.rs index d77a5e504..1fee3f8c1 100644 --- a/proofs/solidity-verifier/src/lowering/quotient.rs +++ b/proofs/solidity-verifier/src/lowering/quotient.rs @@ -1262,15 +1262,26 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { yul_const_value(value, const_vars).as_deref() == Some(expected_coeff) } - /// Record `let name := const` bindings for later limb-chain matching. + /// Track constant variable bindings for later limb-chain matching. + /// + /// Records `name := const` (whether or not introduced with `let`), and, + /// crucially, forgets any variable that is reassigned to a non-constant + /// value. Ignoring non-`let` reassignments would leave a stale literal in + /// `const_vars`, so a later `mulmod(name, limb, r)` could be mis-recognized + /// as a fused limb7 coefficient and bake the wrong constant into the + /// generated quotient identity. fn record_yul_const_assignment(line: &str, const_vars: &mut HashMap) { - let Some((dst, rhs)) = yul_let_assignment(line) else { - return; - }; - let Some(value) = yul_const_value(&rhs, const_vars) else { + let Some(assignment) = yul_assignment(line) else { return; }; - const_vars.insert(dst, value); + match yul_const_value(&assignment.expr, const_vars) { + Some(value) => { + const_vars.insert(assignment.dst, value); + } + None => { + const_vars.remove(&assignment.dst); + } + } } /// Trace, advance, and accumulate one main quotient identity value. From 1fa3b41a86629ca84fd0afa65a9a3d4eb38429b3 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 22:42:19 +0100 Subject: [PATCH 13/72] Content-check embedded VK quotient program against the plan build The VK payload embeds the quotient const table and packed bytecode compiled inside generate_vk, but LoweringPlan recompiles the program from scratch and renders the interpreter (program length, opcode gating, native-callback ordering, stack sizing) from that second build. The only cross-checks were length/reservation inequalities; nothing compared the bytes actually embedded in the VK payload with the second build. Parity rested solely on compile determinism over HashMap/HashSet-backed structures, so an ordering-dependent compile could ship a pinned VK whose bytecode disagrees with the rendered interpreter, permanently rejecting every valid proof (fail-closed on-chain DoS) invisibly at codegen time. Compare vk.constants[quotient_const_offset..] and [quotient_program_offset..] word-for-word against the plan build's consts and PackedProgramCodec::encode_words(bytes) in validate_generator_invariants, failing codegen on any divergence. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/plan.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/plan.rs b/proofs/solidity-verifier/src/lowering/plan.rs index e1682f287..b98e7290b 100644 --- a/proofs/solidity-verifier/src/lowering/plan.rs +++ b/proofs/solidity-verifier/src/lowering/plan.rs @@ -279,6 +279,37 @@ impl LoweringPlan { self.vk.quotient_program_words )); } + // The VK payload embeds the const table and packed bytecode compiled + // inside `generate_vk`, but the interpreter is rendered from this + // independently recompiled `self.quotient.build`. Length checks alone + // let a nondeterministic/order-dependent compile ship a pinned VK whose + // bytecode disagrees with the rendered VM. Compare the embedded words + // against the plan build word-for-word so any divergence fails codegen. + if let Some(const_offset) = self.vk.quotient_const_offset_words { + let build_consts = &self.quotient.build.consts; + let embedded = &self.vk.constants[const_offset..const_offset + build_consts.len()]; + if embedded.iter().map(|(_, value)| value).ne(build_consts.iter()) { + return Err( + "quotient const table embedded in the VK payload does not match the \ + plan-rebuilt const table" + .to_string(), + ); + } + } + if let Some(program_offset) = self.vk.quotient_program_offset_words { + let build_words = layout::vk_payload::PackedProgramCodec::encode_words( + &self.quotient.build.bytes, + ); + let embedded = + &self.vk.constants[program_offset..program_offset + build_words.len()]; + if embedded.iter().map(|(_, value)| value).ne(build_words.iter()) { + return Err( + "quotient program bytecode embedded in the VK payload does not match the \ + plan-rebuilt bytecode" + .to_string(), + ); + } + } if self.quotient.program.stack_mptr != self.quotient.stack_mptr { return Err(format!( "quotient stack pointer drifted: model={:#x} planned={:#x}", From 8db58be1f59bc3e1a80a69268d55d266e55a6f32 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 22:42:31 +0100 Subject: [PATCH 14/72] Bounds-check quotient VM const-table slots in the offline validator validate_quotient_program proved stack effects, instruction lengths, and mem-token bytes, but never checked that decoded const-table indices fall inside the emitted table. An encoder/planner regression emitting an out-of-range const slot (this class already produced one real bug, the quotient VM const-table overflow) would ship a verifier that loads an arbitrary trailing VK word as a gate coefficient, deterministically flipping accept/reject with nothing failing at build or runtime. Add validate_quotient_const_slots, which walks the finalized byte stream and bounds-checks every constant slot across all const-bearing opcodes (push/add/mul const and const_u8, the add-mul const_u8 forms and their runs, LIN7/BILIN7 rows and pairwise coeffs, and the dynamic AFFINE_SUM and MODARITH7 layouts). It runs after validate_quotient_program in QuotientProgramBuilder::finish, whose length validation guarantees the walked layout is in bounds. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/quotient_numerator/vm/mod.rs | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index aeb060bee..439f70c66 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -1210,6 +1210,8 @@ impl QuotientProgramBuilder { let bytes = compact_quotient_runs(&self.bytes); let validated_max_stack = validate_quotient_program(&bytes) .unwrap_or_else(|err| panic!("invalid finalized quotient VM program: {err}")); + validate_quotient_const_slots(&bytes, self.consts.len()) + .unwrap_or_else(|err| panic!("invalid finalized quotient VM program: {err}")); assert_eq!( validated_max_stack, self.max_stack, "quotient VM physical program stack depth diverged from builder accounting" @@ -2049,6 +2051,139 @@ pub(crate) fn validate_quotient_program(bytes: &[u8]) -> Result { Ok(max_stack) } +/// Bounds-check every constant-table slot referenced by a finalized program. +/// +/// `validate_quotient_program` proves structural and stack safety but never +/// checks that decoded const-table indices fall inside the emitted table. An +/// encoder/planner regression that emits an out-of-range slot (this class has +/// already produced one real bug) would otherwise make the deployed verifier +/// load an arbitrary trailing VK word as a gate coefficient, silently flipping +/// accept/reject. Run this after `validate_quotient_program`, whose byte-length +/// validation guarantees the layout walked here is already in bounds. +pub(crate) fn validate_quotient_const_slots(bytes: &[u8], const_len: usize) -> Result<(), String> { + let check = |slot: usize, idx: usize| -> Result<(), String> { + if slot >= const_len { + return Err(format!( + "quotient VM const slot {slot} at byte {idx} is outside the {const_len}-entry constant table" + )); + } + Ok(()) + }; + let limb_stride = 1 + QUOTIENT_VM_BYTE_U16_BYTES; + + for (idx, op, _len) in quotient_bytecode_ops(bytes) { + match op { + Q_OP_PUSH_CONST | Q_OP_ADD_CONST | Q_OP_MUL_CONST => { + check(read_u16(bytes, idx + 1) as usize, idx)?; + } + Q_OP_PUSH_CONST_U8 | Q_OP_ADD_CONST_U8 | Q_OP_MUL_CONST_U8 => { + check(bytes[idx + 1] as usize, idx)?; + } + Q_OP_ADD_MUL_CONST_U8_MEM_U16 => { + check(bytes[idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + } + Q_OP_ADD_MUL_MEM_MEM_CONST_U8 => { + check(bytes[idx + 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + } + Q_OP_RUN_ADD_MUL_CONST_U8_MEM_U16 => { + let count = read_u16(bytes, idx + 1) as usize; + let base = idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES; + let stride = QUOTIENT_VM_BYTE_U16_BYTES + 1; + for k in 0..count { + check(bytes[base + k * stride + QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + } + } + Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8 => { + let count = read_u16(bytes, idx + 1) as usize; + let base = idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES; + let stride = 2 * QUOTIENT_VM_BYTE_U16_BYTES + 1; + for k in 0..count { + check(bytes[base + k * stride + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + } + } + Q_OP_LIN7 => { + for k in 0..QUOTIENT_VM_LIMBS { + check(bytes[idx + 1 + k * limb_stride] as usize, idx)?; + } + } + Q_OP_BILIN7_ROW => { + let base = idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES; + for k in 0..QUOTIENT_VM_LIMBS { + check(bytes[base + k * limb_stride] as usize, idx)?; + } + } + Q_OP_BILIN7_PAIRWISE => { + let base = idx + 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES; + for k in 0..QUOTIENT_VM_PAIRWISE_COEFFS { + check(bytes[base + k] as usize, idx)?; + } + } + Q_OP_AFFINE_SUM => { + let lin_count = read_u16(bytes, idx + 1) as usize; + let product_count = read_u16(bytes, idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES) as usize; + let mut cursor = idx + 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES; + for _ in 0..lin_count { + check(bytes[cursor + QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + cursor += QUOTIENT_VM_BYTE_U16_BYTES + 1; + } + for _ in 0..product_count { + check(bytes[cursor + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + cursor += 2 * QUOTIENT_VM_BYTE_U16_BYTES + 1; + } + } + Q_OP_MODARITH7 => { + let mut cursor = idx + 1; + let flags = bytes[cursor]; + cursor += 1; + if flags & Q_MODARITH7_FLAG_COND != 0 { + cursor += QUOTIENT_VM_BYTE_U16_BYTES; + } + if flags & Q_MODARITH7_FLAG_CONST != 0 { + check(bytes[cursor] as usize, idx)?; + cursor += 1; + } + let lin_count = bytes[cursor] as usize; + let row_count = bytes[cursor + 1] as usize; + let pairwise_count = bytes[cursor + 2] as usize; + let mem_count = bytes[cursor + 3] as usize; + let product_count = bytes[cursor + 4] as usize; + cursor += 5; + for _ in 0..lin_count { + for _ in 0..QUOTIENT_VM_LIMBS { + check(bytes[cursor] as usize, idx)?; + cursor += limb_stride; + } + } + for _ in 0..row_count { + cursor += QUOTIENT_VM_BYTE_U16_BYTES; + for _ in 0..QUOTIENT_VM_LIMBS { + check(bytes[cursor] as usize, idx)?; + cursor += limb_stride; + } + } + for _ in 0..pairwise_count { + cursor += 2 * QUOTIENT_VM_BYTE_U16_BYTES; + for _ in 0..QUOTIENT_VM_PAIRWISE_COEFFS { + check(bytes[cursor] as usize, idx)?; + cursor += 1; + } + } + for _ in 0..mem_count { + check(bytes[cursor] as usize, idx)?; + cursor += limb_stride; + } + for _ in 0..product_count { + check(bytes[cursor] as usize, idx)?; + cursor += 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES; + } + } + _ => {} + } + } + + Ok(()) +} + /// Decode one instruction and validate token operands. fn decode_byte_quotient_instruction(bytes: &[u8], idx: usize) -> Result<(u8, usize), String> { require_quotient_bytes(bytes, idx, 1, "opcode")?; From 700af6d38ff3acd07601cdd487844511d906d6c8 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 22:55:52 +0100 Subject: [PATCH 15/72] Fail closed on zero KZG point sets in the PCS emitter What: make kzg::memory_requirements and kzg::computations panic instead of returning a default/empty result when the intermediate-set construction yields zero point sets. How: replace the two `if n_sets == 0 { return ... }` early returns with an assert!(n_sets != 0, ...) carrying the soundness rationale. Why: with zero point sets, computations() emits no Block 6, so PAIRING_LHS_MPTR/PAIRING_RHS_MPTR are never written. Zero-initialized EVM memory is the EIP-2537 encoding of the BLS12-381 point at infinity, so FinalPairing.yul's ec_pairing computes e(inf, G2) * e(inf, -sG2) = 1 and the verifier accepts ANY transcript-parseable proof with no cryptographic checking of the openings. This is unreachable today only because ProtocolPlan::validate requires the schedule to end with the Linearization query (n_sets >= 1); the KZG emitter is the last line of defense for the pairing check and must fail closed rather than silently emit an accept-all verifier. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../solidity-verifier/src/lowering/kzg/mod.rs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/kzg/mod.rs b/proofs/solidity-verifier/src/lowering/kzg/mod.rs index b49763380..e8fca397c 100644 --- a/proofs/solidity-verifier/src/lowering/kzg/mod.rs +++ b/proofs/solidity-verifier/src/lowering/kzg/mod.rs @@ -744,9 +744,17 @@ pub(crate) fn memory_requirements( ) -> PcsMemoryRequirements { let sets = intermediate_sets(meta, data); let n_sets = sets.point_sets.len(); - if n_sets == 0 { - return PcsMemoryRequirements::default(); - } + // Fail closed: zero point sets means the plan carries no PCS queries, which + // would size (and, in `computations`, emit) a verifier with no final + // pairing check. Since zero-initialized PAIRING_{LHS,RHS}_MPTR encode the + // point at infinity, such a verifier accepts any transcript-parseable proof. + // `ProtocolPlan::validate` guarantees at least the Linearization query, so + // this is unreachable; assert it rather than silently return a default. + assert!( + n_sets != 0, + "KZG intermediate-set construction produced zero point sets; refusing to \ + size a verifier with no PCS/pairing check (would be accept-all)" + ); let by_set = commitments_by_set(&sets, n_sets); let commitments_per_set = by_set.iter().map(Vec::len); @@ -819,9 +827,17 @@ pub(crate) fn computations( const TRUNC_MASK_128: &str = "0xffffffffffffffffffffffffffffffff"; let sets = intermediate_sets(meta, data); let n_sets = sets.point_sets.len(); - if n_sets == 0 { - return Vec::new(); - } + // Fail closed: an empty point-set list would emit a verifier with no Block 6, + // so PAIRING_{LHS,RHS}_MPTR stay zero-initialized. Zero memory is the EIP-2537 + // encoding of the BLS12-381 point at infinity, so the final pairing evaluates + // to 1 and the verifier accepts ANY proof with no cryptographic checking. + // `ProtocolPlan::validate` requires the query schedule to end with the + // Linearization query (n_sets >= 1), so reaching here is a generator bug. + assert!( + n_sets != 0, + "KZG intermediate-set construction produced zero point sets; refusing to \ + emit a verifier with no PCS/pairing check (would be accept-all)" + ); // The emitted blocks below adapt the Rust `multi_prepare` flow: // construct/sort point sets, fold q_eval vectors, interpolate at x3, From 6e14bfe13c7d0ead7e1de789cf4993ce532f498c Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 22:57:50 +0100 Subject: [PATCH 16/72] Bound the unrolled PCS rotation-point walk at codegen What: assert the rotation-point walk in kzg::computations does not unroll more than MAX_ROTATION_WALK_STEPS (4096) mulmod steps. How: compute the walk length as |max_rot| + |min_rot| and assert it against a generous cap before emitting the forward/backward omega walks. Why: the block emits one `mulmod` line per unit step across the whole rotation span, so the emitted Yul grows with rotation magnitude, not with the number of distinct rotations (which is separately capped at ROT_POINTS_CAP_WORDS=28). A legitimate circuit using a large rotation (e.g. Rotation(50_000)) would pass every existing capacity check yet emit enough code to exceed the EIP-170 24KB runtime-size limit, producing an undeployable verifier with no diagnostic. The cap fails closed with a clear message well above any realistic circuit's rotation range; the documented remedy is to roll the walk into a Yul loop. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../solidity-verifier/src/lowering/kzg/mod.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/kzg/mod.rs b/proofs/solidity-verifier/src/lowering/kzg/mod.rs index e8fca397c..4462eec5d 100644 --- a/proofs/solidity-verifier/src/lowering/kzg/mod.rs +++ b/proofs/solidity-verifier/src/lowering/kzg/mod.rs @@ -887,6 +887,26 @@ pub(crate) fn computations( let max_rot = *distinct_rotations.iter().max().unwrap_or(&0); let min_rot = *distinct_rotations.iter().min().unwrap_or(&0); + // Fail closed on pathological rotation magnitudes. This block unrolls one + // `mulmod` per unit step across the entire rotation span (forward to + // max_rot, backward to min_rot), so the emitted line count scales with + // |max_rot| + |min_rot|, NOT with the (separately capped) number of + // distinct rotations. A circuit using a very large rotation would emit + // enough Yul to exceed the EIP-170 24KB runtime-code limit and produce an + // undeployable verifier with no diagnostic from our own validation. Bound + // the walk here; if this ever fires for a legitimate circuit, roll the + // walk into a Yul loop (as Block 2 does for x1 powers) rather than raising + // the cap. The cap is far above any realistic circuit's rotation range. + const MAX_ROTATION_WALK_STEPS: i64 = 4096; + let walk_steps = i64::from(max_rot).max(0) + (-i64::from(min_rot)).max(0); + assert!( + walk_steps <= MAX_ROTATION_WALK_STEPS, + "PCS rotation-point walk would unroll {walk_steps} mulmod steps \ + (max_rot={max_rot}, min_rot={min_rot}), exceeding the \ + {MAX_ROTATION_WALK_STEPS}-step cap; such a verifier would blow the \ + EIP-170 runtime code-size limit (roll the walk into a Yul loop instead)" + ); + let store_rot = |rot: i32| -> Option { distinct_rotations.iter().position(|r| *r == rot).map(|idx| { format!( From 36fc616a7537824f80b6b3f936db8637a6988ebd Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:00:57 +0100 Subject: [PATCH 17/72] Correct the KZG batch-inversion non-zero soundness comment What: rewrite the misleading soundness note above the Montgomery batch inverse in kzg::computations to state the real lbasis_j != 0 invariant and the actual failure mode. How: comment-only. Document that lbasis_j is non-zero only because distinct rotation *values* map to distinct rotation *points* (which holds because the domain order n=2^k exceeds the rotation span for every supported circuit), and that a degenerate tiny-domain circuit aliasing two rotations fails closed (a zero lbasis product makes scalar_inv revert) rather than producing a wrong f_eval. Add a TODO to thread the domain order in for a codegen-time assert. Why: the previous comment claimed lbasis_j is non-zero simply because construct_intermediate_sets de-duplicates rotations, but that de-dups i32 rotation values, not the points x*omega^rot; two distinct rotations alias when rot_i == rot_j (mod n). The comment also contradicted a sibling finding on scalar_inv: the batch path reverts on a zero product, so the true behavior is fail-closed, not a silent wrong evaluation. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../solidity-verifier/src/lowering/kzg/mod.rs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/kzg/mod.rs b/proofs/solidity-verifier/src/lowering/kzg/mod.rs index 4462eec5d..3b69f28e7 100644 --- a/proofs/solidity-verifier/src/lowering/kzg/mod.rs +++ b/proofs/solidity-verifier/src/lowering/kzg/mod.rs @@ -1435,11 +1435,27 @@ pub(crate) fn computations( // Soundness: requires every input to be non-zero. dx_j is // non-zero by Fiat-Shamir (x3 is uniform random; the // probability that x3 = p_j for a structured rotation point - // is ~2^-256). lbasis_j is non-zero because the points in a - // set are distinct by construction (`construct_intermediate_sets` - // de-duplicates rotations within each set). Defensive note: - // a malicious prover cannot influence either, so we don't - // need an explicit zero check. + // is ~2^-256). lbasis_j = prod_{k != j} (p_j - p_k) is non-zero + // as long as the rotation points p = x*omega^rot are pairwise + // distinct within the set. `construct_intermediate_sets` + // de-duplicates rotation *values* (i32) per set, and distinct + // values map to distinct points only because the domain order + // n = 2^k exceeds the rotation span for every supported circuit + // (rotations are bounded by the gate/lookup structure, k is + // large). A malicious prover cannot influence either value, so + // no explicit codegen zero check is added here. + // + // Note the failure mode is fail-closed, not silent: if a + // degenerate tiny-domain circuit ever aliased two rotations + // (rot_i == rot_j mod n), the corresponding p_j - p_k would be + // zero, so some lbasis_j and hence the batched product bp_{n-1} + // would be zero, and `scalar_inv` reverts on a zero input + // (AssemblyHelpers.yul). Such a verifier rejects all proofs + // rather than computing a wrong f_eval. + // + // TODO: to surface that misconfiguration at codegen time instead + // of at proof time, thread the domain order n into this emitter + // and assert every point set's rotation span is < n. // // The reference computes lagrange interpolation directly via // full polynomial construction; here we collapse the From 66c32f9526054e0f37ec5938267386a0d5cbf625 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:01:49 +0100 Subject: [PATCH 18/72] Fix inverted pairing-orientation comment in the KZG emitter What: correct the comment claiming PAIRING_LHS = pi is 'paired against G2_BASE' in kzg::computations Block 6. How: comment-only. pi (PAIRING_LHS) is actually paired against NEG_S_G2_BASE: FinalPairing.yul calls ec_pairing(success, PAIRING_RHS_MPTR, PAIRING_LHS_MPTR) with the slots swapped, and ec_pairing pairs arg0 against G2_BASE and arg1 against NEG_S_G2_BASE. The comment now states this and ties it to the KZG identity e(final_com - v*G + x3*pi, [1]_2) = e(pi, [s]_2). Why: the executed code is correct, but the contradictory comment invited a maintainer to 'un-swap' the ec_pairing call (or swap the mcopy destinations) to make code match comment, which would flip the equation to e(pi,[1]) = e(RHS,[s]) and reject all honest proofs (fail-closed completeness break). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/kzg/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/kzg/mod.rs b/proofs/solidity-verifier/src/lowering/kzg/mod.rs index 3b69f28e7..d8a447ce1 100644 --- a/proofs/solidity-verifier/src/lowering/kzg/mod.rs +++ b/proofs/solidity-verifier/src/lowering/kzg/mod.rs @@ -1760,7 +1760,12 @@ pub(crate) fn computations( lines.push("// Scale z*pi - vG before the final pairing check".to_string()); lines.push("// pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi)".to_string()); - // PAIRING_LHS = pi (paired against G2_BASE). + // PAIRING_LHS = pi. FinalPairing.yul calls + // ec_pairing(success, PAIRING_RHS_MPTR, PAIRING_LHS_MPTR) with the + // slots swapped, and ec_pairing pairs its first argument against + // G2_BASE and its second against NEG_S_G2_BASE, so pi (PAIRING_LHS) + // is paired against NEG_S_G2_BASE (the [s]_2 side), matching the KZG + // identity e(final_com - v*G + x3*pi, [1]_2) = e(pi, [s]_2). lines.push(format!("mcopy(PAIRING_LHS_MPTR, PI_MPTR, {G1_BYTES:#x})")); // tmp = (-v) * G => load G into planned scratch, scale by (r - v). From 688efdeee2896bdf35264fc738e047e9eca65a75 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:02:51 +0100 Subject: [PATCH 19/72] Document the pointer-identity KZG query-grouping invariant What: add a TODO(structural) comment in construct_intermediate_sets_impl explaining that KZG query grouping and the duplicate-query eval-consistency assert compare EcPoint/Word handles by memory pointer, not by runtime value. How: comment-only. Records that this diverges from the midnight-proofs prover (groups by polynomial identity) and verifier (groups by commitment value), that it is safe today only because SolidityGenerator supports a single committed-instance column and all consumers depend only on first-appearance order, and that with >= 2 committed-instance columns sharing G1_IDENTITY_MPTR at one rotation the assert would panic at codegen. A value-based grouping is the real fix but is structural and out of scope here. Why: the auditor flagged both a codegen DoS (assert panics for >= 2 shared committed-instance columns) and a prover/verifier grouping divergence rooted in pointer identity; the fix is structural, so the invariant is documented to prevent a future refactor from silently dropping the eval-consistency check. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../solidity-verifier/src/lowering/kzg/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/kzg/mod.rs b/proofs/solidity-verifier/src/lowering/kzg/mod.rs index d8a447ce1..2a607b6d5 100644 --- a/proofs/solidity-verifier/src/lowering/kzg/mod.rs +++ b/proofs/solidity-verifier/src/lowering/kzg/mod.rs @@ -320,6 +320,23 @@ fn construct_intermediate_sets_impl(queries: &[Query]) -> IntermediateSets { // when their evals also agree; otherwise the proof claims the // same polynomial opens to two different values, which is a // protocol bug and must be rejected. + // + // TODO(structural): this dedup and the whole intermediate-set + // grouping compare EcPoint/Word handles by *memory pointer* (derived + // PartialEq), not by runtime value, whereas the midnight-proofs + // prover groups queries by polynomial identity and the verifier by + // commitment value. They agree today only because (a) + // SolidityGenerator supports a single committed-instance column (see + // validate_instance_column_shape in builder/api.rs), and (b) every + // downstream consumer depends only on first-appearance order and + // per-commitment point sets. The assert below also compares eval + // Words by pointer: with >= 2 committed-instance columns sharing + // G1_IDENTITY_MPTR at the same rotation it would panic at codegen + // (their eval Words are distinct memory handles) instead of + // collapsing them, and compute_dummy_queries would silently `skip` + // the same pair. Grouping by runtime value would lift both + // restrictions but is a structural change; the invariant is + // documented here rather than fixed. if let Some(existing_pos) = slot.1.iter().position(|pi| *pi == point_idx) { assert_eq!( slot.2[existing_pos], query.eval, From 098f6a1d542664970a976c489dc35032fc55615b Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:03:45 +0100 Subject: [PATCH 20/72] Harden rotated-instance guard in the quotient VM lowerer What: promote the debug_assert_eq!(rotation, 0) in DataQuotientExpressionEnv::instance to a hard assert_eq!. How: replace debug_assert_eq! with assert_eq! and document why it must fail closed even in release builds. Why: the non-committed public-instance column is only interpolated at Rotation::cur(), so a rotated query must never reach this arm. debug_assert compiles out in release, so a rotated query slipping past the far-away constructor guard (builder/api.rs) would silently substitute instance(x) for instance(x*omega^k), generating a verifier whose quotient identity differs from the circuit (it could reject valid proofs and accept ones violating the intended constraint). A hard assert matches the fail-closed style of word_to_quotient_expr and ptr_to_quotient_mem in this file. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/quotient_numerator/vm/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index 439f70c66..6ecb0fc2e 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -2624,7 +2624,15 @@ impl QuotientExpressionEnv for DataQuotientExpressionEnv<'_> { // The builder rejects rotated instance queries before lowering, so // the direct public-input column always uses the one local // Lagrange evaluation computed for `Rotation::cur()`. - debug_assert_eq!( + // + // Hard assert (not debug_assert): `debug_assert` compiles out in + // release, and this is the last line of defense far from the + // constructor guard (builder/api.rs). Substituting the Rotation::cur + // eval for a rotated query would silently emit a verifier that + // evaluates the gate with instance(x) instead of instance(x*w^k), + // enforcing a different quotient identity than the circuit. Fail + // closed, matching word_to_quotient_expr / ptr_to_quotient_mem. + assert_eq!( rotation, 0, "rotated public instance query reached lowering" ); From 16f43b56ee84a6fabd7c8b9fe27e096ecfee75d7 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:04:19 +0100 Subject: [PATCH 21/72] Use checked u32 conversion for quotient VM memory pointers What: replace the two `offset as u32` casts in ptr_to_quotient_mem with u32::try_from(...).expect(...). How: convert the isize Value offset via u32::try_from, which rejects both negative and > u32::MAX offsets, dropping the now-redundant assert!(offset >= 0) guards. Why: Value::Integer/Identifier carry isize offsets, so `offset as u32` silently wraps a planner offset above u32::MAX, and the generated VM bytecode would mload a wrapped, unrelated address, producing a verifier that computes the quotient numerator from the wrong memory word with no build-time diagnostic. Every other narrowing in this file already uses a checked conversion; this was the one silent cast. Practically unreachable today (EVM gas keeps frames far below 4GiB) but now fails loudly at generation time. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/quotient_numerator/vm/mod.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index 6ecb0fc2e..c653cfb48 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -2665,17 +2665,26 @@ pub(crate) fn ptr_to_quotient_mem(ptr: Ptr) -> QuotientMem { ); match ptr.value() { Value::Integer(offset) => { - assert!(offset >= 0, "negative quotient memory pointer"); - QuotientMem::Literal(offset as u32) + // Checked conversion: Value offsets are isize, so `offset as u32` + // would silently wrap for a negative or > u32::MAX offset and make + // the VM mload an unrelated address (a verifier computing the + // quotient numerator from the wrong memory word, with no build-time + // diagnostic). Fail loudly instead, like the other narrowing casts + // in this file (u16::try_from, u8::try_from). + let offset = u32::try_from(offset) + .expect("quotient memory pointer must be a non-negative offset that fits in u32"); + QuotientMem::Literal(offset) } Value::Identifier(name, offset) => { - assert!(offset >= 0, "negative quotient memory token offset"); + let offset = u32::try_from(offset).expect( + "quotient memory token offset must be a non-negative offset that fits in u32", + ); let token = quotient_mem_token_from_name(name) .unwrap_or_else(|| panic!("unsupported quotient memory token: {name}")); if offset == 0 { QuotientMem::Token(token) } else { - QuotientMem::TokenOffset(token, offset as u32) + QuotientMem::TokenOffset(token, offset) } } } From 56f20be1957387e4626d1f56d672e3b068547f1d Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:10:35 +0100 Subject: [PATCH 22/72] Validate quotient VM memory pointers are word-aligned at construction What: assert every quotient memory pointer/token-offset is 32-byte word aligned in ptr_to_quotient_mem. How: at the single QuotientMem construction choke point, assert offset % WORD_BYTES == 0 for both the absolute Literal address and the TokenOffset offset. Why: the offline bytecode safety pass proves structural/stack safety and now bounds-checks const-table slots (validate_quotient_const_slots), but nothing validated the embedded memory operands. Every address the quotient VM reads is a word slot, so a non-word-aligned pointer indicates a truncated/mis-encoded address that would make the interpreter mload a straddling window and fold the wrong value into the numerator, flipping accept/reject with no build-time diagnostic. Guarding at ptr_to_quotient_mem covers all memory pointers in one place; all 151 lib tests pass, confirming the invariant. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/quotient_numerator/vm/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index c653cfb48..4c3600660 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -2673,12 +2673,26 @@ pub(crate) fn ptr_to_quotient_mem(ptr: Ptr) -> QuotientMem { // in this file (u16::try_from, u8::try_from). let offset = u32::try_from(offset) .expect("quotient memory pointer must be a non-negative offset that fits in u32"); + // Every address the quotient VM reads is a 32-byte word slot (the + // layout allocates in WORD_BYTES units and all eval/challenge/VK/ + // scratch handles are word multiples). A non-word-aligned literal + // pointer signals a truncated/mis-encoded address that would make + // the VM mload a straddling window; reject it at the single + // construction choke point rather than emit a corrupt verifier. + assert!( + offset as usize % WORD_BYTES == 0, + "quotient memory pointer {offset:#x} is not 32-byte word aligned" + ); QuotientMem::Literal(offset) } Value::Identifier(name, offset) => { let offset = u32::try_from(offset).expect( "quotient memory token offset must be a non-negative offset that fits in u32", ); + assert!( + offset as usize % WORD_BYTES == 0, + "quotient memory token offset {offset:#x} is not 32-byte word aligned" + ); let token = quotient_mem_token_from_name(name) .unwrap_or_else(|| panic!("unsupported quotient memory token: {name}")); if offset == 0 { From 2286dff78dadd48d5e9e6c4ee16b621ddba310a7 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:12:26 +0100 Subject: [PATCH 23/72] Guard rotated non-committed instance queries in the Yul quotient emitter What: fail closed on a rotated non-committed instance query in instance_eval_at and expression_memory_ptr. How: hard assert_eq!(rotation, 0) in instance_eval_at's non-committed branch (the value resolver), and return None for rotation != 0 in expression_memory_ptr's Instance arm (which only detects consecutive direct memory pointers, so declining is safe). Why: both helpers resolve ANY rotation of a non-committed instance column to the single Rotation::cur() INSTANCE_EVAL word. Commit 5d7afe9 added the guard to the parallel-lookup path in quotient_numerator/vm/mod.rs but not to this file, so the only protection was the constructor-time guard in builder/api.rs. Any future entry point or refactor that lowers a circuit querying the public instance column at rotation +/-1 without going through SolidityGenerator::try_new would silently evaluate instance[rot] as instance[cur], generating a verifier that checks a different polynomial identity than the native Midfall verifier. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../lowering/quotient_numerator/yul_emit.rs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs index bc8242de0..11b94950d 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs @@ -788,6 +788,14 @@ impl<'a> Evaluator<'a> { .get(&(column_index, query.rotation().0)) .copied()? } else { + // The non-committed public-input column only has its local + // Rotation::cur() interpolation at INSTANCE_EVAL_MPTR. + // Decline to treat a rotated query as a direct memory + // pointer; the constructor rejects rotated instance queries + // and instance_eval_at hard-asserts rotation == 0. + if query.rotation().0 != 0 { + return None; + } self.data.instance_eval } } @@ -1103,8 +1111,18 @@ impl<'a> Evaluator<'a> { .to_string() } else { // The current public API supports one non-committed instance - // column, whose Lagrange-combined evaluation is computed by - // the template prologue and stored at INSTANCE_EVAL_MPTR. + // column, whose Lagrange-combined evaluation is computed by the + // template prologue and stored at INSTANCE_EVAL_MPTR for + // Rotation::cur() only. Hard-assert (not debug_assert) so a + // rotated query that ever bypasses the far-away constructor guard + // (builder/api.rs) fails closed in release builds too, instead of + // silently evaluating instance(x) in place of instance(x*omega^k) + // and generating a verifier that checks a different identity than + // the native Midfall verifier. + assert_eq!( + rotation, 0, + "rotated public instance query reached Yul quotient emission" + ); self.data.instance_eval.to_string() } } From d149f96363bb0c3048d6f4a6a95b2fbe29115aa1 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:12:44 +0100 Subject: [PATCH 24/72] Use unsigned_abs in column_eval_var to avoid i32::MIN overflow What: replace rotation.abs() with rotation.unsigned_abs() in column_eval_var. How: one-line change; unsigned_abs returns u32 and is total over all i32. Why: rotation.abs() overflows for rotation == i32::MIN, panicking in debug and wrapping to a colliding/garbage variable name in release. This is only reachable if the constraint system supplied to codegen contains a query at Rotation(i32::MIN), so it is a build-time DoS on codegen inputs, not an on-chain issue, but unsigned_abs makes the conversion total. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/quotient_numerator/yul_emit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs index 11b94950d..4ca61f5d9 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs @@ -1159,7 +1159,7 @@ fn u256_string(value: U256) -> String { /// Stable variable name for a column evaluation and rotation. fn column_eval_var(prefix: &'static str, column_index: usize, rotation: i32) -> String { match rotation.cmp(&0) { - Ordering::Less => format!("{prefix}_{column_index}_prev_{}", rotation.abs()), + Ordering::Less => format!("{prefix}_{column_index}_prev_{}", rotation.unsigned_abs()), Ordering::Equal => format!("{prefix}_{column_index}"), Ordering::Greater => format!("{prefix}_{column_index}_next_{rotation}"), } From e5f77f8ab3a91b5fd76ef2be22db395d94f32ba7 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:14:25 +0100 Subject: [PATCH 25/72] Emit h_eval, not 0, for an empty lookup input chunk in the Yul emitter What: change the k == 0 empty-input-chunk branch of lookup_computations to emit the helper eval (h_eval) as the identity value instead of a hard-coded 0. How: replace the `let zero := 0` stub with out.push((lines, h_eval.to_string())). Why: the reference verifier (plonk/logup.rs) computes an empty chunk's helper constraint as helper_eval * (empty product = 1) - (empty sum = 0) = helper_eval, which enforces h == 0. Emitting 0 drops that binding while the accumulator constraint still folds the chunk's h_eval into sum_h, so a prover could set the unconstrained h freely and shift selector*sum_h to forge lookup balance. The branch is dead today (chunks are never empty), but emitting the reference-faithful value fails safe if a future chunking refactor ever reaches it. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/quotient_numerator/yul_emit.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs index 4ca61f5d9..97b758889 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs @@ -479,10 +479,17 @@ impl<'a> Evaluator<'a> { let k = f_plus_beta_vars.len(); if k == 0 { - // Empty chunk shouldn't happen but emit a no-op. - let zero = self.fresh_var(); - lines.push(format!("let {zero} := 0")); - out.push((lines, zero)); + // Unreachable today (BatchedArgument::new requires >= 1 + // parallel lookup and slice::chunks never yields an empty + // chunk), but emit the reference-faithful value rather than + // 0 so a future chunking change cannot silently drop the + // constraint. For an empty chunk the native verifier + // (plonk/logup.rs) computes helper_eval * (empty product = 1) + // - (empty sum = 0) = helper_eval, enforcing h == 0. Emitting + // 0 would leave h unconstrained while the accumulator still + // folds this h_eval into sum_h, letting a prover forge lookup + // balance. + out.push((lines, h_eval.to_string())); continue; } From 33e183e71044d83feccf569f851d65b1cf0357d0 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:14:45 +0100 Subject: [PATCH 26/72] Guard against a lookup with zero helper chunks in the Yul emitter What: make lookup_computations' accumulator block tolerate an empty h_evals vector instead of indexing h_evals[0]. How: replace `let sum_h := h_evals[0]` + slice with a split_first(), emitting `let sum_h := 0` when there are no helper evals. Why: a LogUp argument with no input expressions produces zero helper chunks and an empty h_evals vector; the native verifier folds sum_helpers over the empty set to F::ZERO, but this emitter panicked with an index-out-of-bounds during code generation. This makes the Solidity generator match the native handling (and the structured native path in quotient.rs) instead of crashing at build time on a degenerate but legal circuit. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/quotient_numerator/yul_emit.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs index 97b758889..752387719 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs @@ -578,11 +578,18 @@ impl<'a> Evaluator<'a> { let beta = self.fresh_var(); lines.push(format!("let {beta} := mload(BETA_MPTR)")); - // Σ_h h_eval[c] + // Σ_h h_eval[c]. Empty for a lookup with no input expressions + // (zero helper chunks); the native verifier's sum_helpers folds + // over an empty set to 0, so mirror that instead of indexing + // h_evals[0] out of bounds and panicking at codegen. let sum_h = self.fresh_var(); - lines.push(format!("let {sum_h} := {}", h_evals[0])); - for h in &h_evals[1..] { - lines.push(format!("{sum_h} := addmod({sum_h}, {h}, r)")); + if let Some((first, rest)) = h_evals.split_first() { + lines.push(format!("let {sum_h} := {first}")); + for h in rest { + lines.push(format!("{sum_h} := addmod({sum_h}, {h}, r)")); + } + } else { + lines.push(format!("let {sum_h} := 0")); } // selector eval (full Expression; not necessarily a From 0b561862e729559ac8c7def150c4d46732dc461b Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:15:52 +0100 Subject: [PATCH 27/72] Floor quotient stack scratch at one word to cover the eval-scratch write What: make quotient_stack_words_for_build return at least one word. How: add .max(1) to the max_stack/native-scratch computation and document why. Why: every inline/native direct_quotient_block writes one eval-scratch word at eval_scratch_slot == quotient_stack_mptr, but the function could return 0 for a degenerate-but-valid VK whose gates all fit the inline prefix with no permutation sets, lookups, or VM items. That write is only in-bounds today because layout/memory.rs independently sizes the region as max(quotient_stack_len, MODEXP_FRAME_BYTES) for modexp-frame sharing, which is documented as a modexp concern, not as covering the quotient eval scratch. If that clamp is ever refactored away, the inline write would land one word past the registered region. Flooring here keeps the invariant with the code that emits the write; the modexp clamp still dominates real layouts, so this changes no current output (151 lib tests still pass). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/quotient.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient.rs b/proofs/solidity-verifier/src/lowering/quotient.rs index 1fee3f8c1..b3fbfd5fa 100644 --- a/proofs/solidity-verifier/src/lowering/quotient.rs +++ b/proofs/solidity-verifier/src/lowering/quotient.rs @@ -253,7 +253,17 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { // `build.max_stack` only describes the interpreted operand stack. Some // native callbacks share `quotient_stack_mptr` as a scratch base, so // the registered memory region must cover both possible users. - build.max_stack.max(native_callback_scratch_words) + // + // Floor at one word: every inline/native direct_quotient_block writes + // one eval-scratch word at eval_scratch_slot == quotient_stack_mptr + // (see direct_quotient_block and compact_quotient_computation_blocks), + // so the region is always written even when the interpreted stack and + // native scratch are both empty (a degenerate-but-valid VK whose gates + // all fit the inline prefix with no permutation sets, lookups, or VM + // items). Accounting that word here keeps the in-bounds invariant with + // the code that emits the write, rather than relying on the unrelated + // MODEXP-frame clamp in layout/memory.rs. + build.max_stack.max(native_callback_scratch_words).max(1) } /// Number of persistent VM temp words needed for state plus selector From d67a2e925cba1d79fbc1f6d2617edf8648a0ff14 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:17:02 +0100 Subject: [PATCH 28/72] Emit h_eval, not 0, for an empty lookup chunk in the structured path What: change the k == 0 empty-input-chunk branch of structured_lookup_loop_block to fold h_eval instead of 0 into q_lookup_eval. How: replace `let q_lookup_eval := 0` with `let q_lookup_eval := `, mirroring the Yul emitter fix. Why: same latent constraint-drop as in quotient_numerator/yul_emit.rs. The native verifier computes helper_eval * 1 - 0 = helper_eval for an empty chunk, enforcing h == 0; emitting 0 leaves h unconstrained while the accumulator still folds this h_eval into sum_h, which would let a prover forge lookup balance if a future chunking refactor ever produced an empty chunk. Dead code today but now fails safe in both lookup emission paths. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/quotient.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/quotient.rs b/proofs/solidity-verifier/src/lowering/quotient.rs index b3fbfd5fa..42804784b 100644 --- a/proofs/solidity-verifier/src/lowering/quotient.rs +++ b/proofs/solidity-verifier/src/lowering/quotient.rs @@ -1617,7 +1617,15 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { block.push("{".to_string()); if k == 0 { - block.push("let q_lookup_eval := 0".to_string()); + // Unreachable today (chunks are never empty), but emit the + // reference-faithful value (h_eval) rather than 0: the + // native verifier (plonk/logup.rs) computes + // helper_eval * (empty product = 1) - (empty sum = 0) + // = helper_eval for an empty chunk, enforcing h == 0. + // Emitting 0 leaves h unconstrained while the accumulator + // still folds this h_eval into sum_h. Mirrors the fix in + // quotient_numerator/yul_emit.rs. + block.push(format!("let q_lookup_eval := {}", h_eval)); Self::push_structured_main_fold( &mut block, "q_lookup_eval", From 63be0298063c6e01a0418c5aa5faea7c3d46743b Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:18:00 +0100 Subject: [PATCH 29/72] Assert lookup chunk/helper-eval count parity at both zip sites What: add assert_eq!(input_chunks.len(), h_evals.len()) before the chunk/helper-eval zip in both structured_lookup_loop_block (quotient.rs) and lookup_computations (quotient_numerator/yul_emit.rs). How: assert the two counts match immediately before zipping them. Why: both paths fold one helper identity per element of input_expression_chunks().zip(h_evals). If data.lookup_evals ever carried fewer helper evals than chunk_by_degree produces chunks (e.g. an EvalRead-schedule or protocol drift after a midnight_proofs update), zip would silently drop the excess chunks, removing helper constraints from the y-batched numerator and producing a verifier that accepts proofs with unconstrained h values (forged lookup membership). This is only guarded transitively today via the protocol lookup count check; a direct assert at each zip site fails loudly at codegen. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/quotient.rs | 12 ++++++++++++ .../src/lowering/quotient_numerator/yul_emit.rs | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/quotient.rs b/proofs/solidity-verifier/src/lowering/quotient.rs index 42804784b..8552031c0 100644 --- a/proofs/solidity-verifier/src/lowering/quotient.rs +++ b/proofs/solidity-verifier/src/lowering/quotient.rs @@ -1610,6 +1610,18 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { Self::push_structured_main_fold(&mut block, "q_lookup_eval", state_slots, trace); block.push("}".to_string()); + // Fail closed on a chunk/helper-eval count mismatch: zip would + // otherwise silently drop the excess chunks, removing helper + // constraints from the y-batched numerator. Guarded indirectly today + // by the protocol lookup count check, but assert it directly at the + // zip site. + assert_eq!( + chunked.input_expression_chunks().len(), + h_evals.len(), + "lookup {lookup_idx}: input chunk count {} != helper eval count {}", + chunked.input_expression_chunks().len(), + h_evals.len(), + ); for (input_chunk, h_eval) in chunked.input_expression_chunks().iter().zip(h_evals.iter()) { diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs index 752387719..1b68231be 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs @@ -456,6 +456,16 @@ impl<'a> Evaluator<'a> { // `(Vec, String)` entries. let selector_expr = chunked.selector_expression(); + // Fail closed on a chunk/helper-eval count mismatch: zip would + // otherwise silently drop the excess chunks, removing helper + // constraints from the numerator. + assert_eq!( + chunked.input_expression_chunks().len(), + h_evals.len(), + "lookup {lookup_idx}: input chunk count {} != helper eval count {}", + chunked.input_expression_chunks().len(), + h_evals.len(), + ); for (input_chunk, h_eval) in chunked.input_expression_chunks().iter().zip(h_evals.iter()) { From fd3842c4f7864d3dc206f3ee185afd6c7cb90a42 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:20:49 +0100 Subject: [PATCH 30/72] Document the proxy selector_gap in native_identity_estimate_block What: add a doc comment explaining why native_identity_estimate_block uses a hard-coded selector_gap of Some(1) that differs from the real selector_fold.gap_for(identity) used at emission. How: documentation only; no behavior change. Why: an auditor flagged the gap mismatch between the estimate (Some(1)) and the real native emission in compact_quotient_computation_blocks (gap_for). The estimate feeds only the native-vs-VM gate selection heuristic and runs during native_gate_candidates, before the selector_fold plan exists (the plan is derived from the selection outcome), so the real gap is genuinely unavailable here and the proxy is intentional. The divergence can only shift which gates are promoted to native callbacks, never the correctness of the emitted verifier. Documenting this prevents a future maintainer from mistaking it for a bug or wiring in gap_for where the plan does not yet exist. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/quotient.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/quotient.rs b/proofs/solidity-verifier/src/lowering/quotient.rs index 8552031c0..f1b38498b 100644 --- a/proofs/solidity-verifier/src/lowering/quotient.rs +++ b/proofs/solidity-verifier/src/lowering/quotient.rs @@ -604,12 +604,27 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { } /// Estimate the generated native callback block for one identity. + /// + /// This is a size/gas PROXY consumed only by the native-vs-VM gate + /// selection heuristic (native_gate_candidates); it is never emitted. The + /// real native blocks are produced by compact_quotient_computation_blocks + /// with `selector_fold.gap_for(identity)`, but that fold plan does not exist + /// yet at selection time (it is derived from the selection outcome), so we + /// use a fixed proxy gap: Some(1) for selector targets, None otherwise. + /// + /// Any divergence between this proxy and the eventual `gap_for` only + /// perturbs which gates get promoted to native callbacks; it can never + /// change the correctness of the generated verifier, because actual emission + /// always uses the real gap. Do not "fix" this to call `gap_for` here: the + /// plan is intentionally unavailable at this point. fn native_identity_estimate_block(identity: &QuotientIdentity) -> Vec { let state_slots = QuotientStateSlots { eval_numer_mptr: 0x2000, trace_id_mptr: 0x2020, selector_power_mptr: 0x2040, }; + // Proxy gap only; see the doc comment above. This deliberately differs + // from selector_fold.gap_for(identity) used at emission time. let selector_gap = matches!(identity.target, QuotientTarget::Selector(_)).then_some(1); Self::direct_quotient_block( &identity.lines, From b8b4c39749075822ec2213beccf288deccbb7e97 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:23:44 +0100 Subject: [PATCH 31/72] Fail closed on negative offset in Value::as_usize What: assert the offset is non-negative in Value::as_usize before casting to usize. How: replace the bare `*int as usize` with an assert!(*int >= 0) guard, then the cast. Why: Value::Integer is stored as isize purely so BLS pointer arithmetic can form negative ptr_end sentinels for lt(ptr_end, ptr) style loops, where any value below the smallest visited address is acceptable. as_usize is a different accessor that yields a concrete memory address; if it were ever called on a negative offset, `*int as usize` would wrap to a near-2^word value and silently corrupt every mload/mstore derived from it. Asserting turns that latent misuse into a loud build-time failure. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../solidity-verifier/src/lowering/encoding/mod.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index 9397f8f2a..1dfdd82cb 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -596,7 +596,19 @@ impl Value { /// Return the concrete offset, panicking for symbolic identifiers. pub(crate) fn as_usize(&self) -> usize { match self { - Value::Integer(int) => *int as usize, + Value::Integer(int) => { + // `Integer` is signed only so BLS pointer math can produce + // negative `ptr_end` sentinels for `lt(ptr_end, ptr)` loops. A + // concrete memory address is never negative; `*int as usize` on + // a negative value would wrap to a near-2^word offset and + // silently corrupt every derived mload/mstore, so fail closed. + assert!( + *int >= 0, + "Value::as_usize on negative offset {int}: signed offsets are \ + only valid as lt()-loop sentinels, not concrete addresses" + ); + *int as usize + } Value::Identifier(..) => unreachable!(), } } From a9032f85cabbe9298b03c47066adbc8bb87161fd Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:24:08 +0100 Subject: [PATCH 32/72] Assert VK fixed-commitment count matches meta in Data::new What: add assert_eq!(vk.fixed_comms.len(), meta.num_fixeds) before deriving the permutation commitment base. How: one assertion between the fixed_comm_mptr and permutation_comm_mptr computations. Why: permutation_comm_mptr is computed as fixed_comm_mptr + G1_WORDS * vk.fixed_comms.len(), but the fixed-commitment region is actually consumed as meta.num_fixeds slots. These two counts are assumed equal but never checked; if a VK ever carried a fixed_comms vector of a different length than the constraint system's fixed-column count, the fixed and permutation commitment memory regions would silently overlap or leave a gap, mapping proof commitments to the wrong memory and producing a verifier that reads garbage points. The assert makes that inconsistency fail loudly at generation time (151 lib tests still pass). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/encoding/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index 1dfdd82cb..41ed78f43 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -308,6 +308,16 @@ impl Data { // BLS12-381 G1 commitments occupy 4 words (EIP-2537 padded), so the // stride between consecutive points is 4 instead of the BN254-era 2. let fixed_comm_mptr = memory.vk_mptr + vk.constants.len(); + // The fixed-commitment region is consumed as meta.num_fixeds slots + // (EcPoint::range(fixed_comm_mptr).take(meta.num_fixeds) below), but the + // permutation base is advanced past vk.fixed_comms.len() of them. If the + // two counts ever diverge, the fixed and permutation commitment regions + // would silently overlap or leave a gap, so require them equal here. + assert_eq!( + vk.fixed_comms.len(), + meta.num_fixeds, + "VK fixed commitment count must match constraint-system fixed count" + ); let permutation_comm_mptr = fixed_comm_mptr + G1_WORDS * vk.fixed_comms.len(); let challenge_mptr = memory.challenge_mptr; let theta_mptr = memory.theta_mptr; From 822980e3b2d0c2c0c93c39c41e00bef5b861c4eb Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:24:23 +0100 Subject: [PATCH 33/72] Correct dummy_eval_words address formula in the field doc What: fix the Data::dummy_eval_words doc comment to reference num_main_evals instead of num_evals. How: documentation only; no behavior change. Why: the field doc said dummy_eval_words[i] points at REVERSED_EVALS_MPTR + (num_evals + i) * 0x20, but set_dummy_eval_words offsets by num_main_evals (the eval count before dummies). Since meta.num_evals already includes the dummy evals on the second protocol build, the old wording double-counts them and mis-describes where the dummy slots live. Aligning the doc with the code (and with set_dummy_eval_words' own doc) prevents a future reader from computing the wrong slot address. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/encoding/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index 41ed78f43..d99515f00 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -287,7 +287,9 @@ pub(crate) struct Data { /// fewer-point-sets path. Empty when the feature is disabled. The /// dummy buffer is laid out immediately after the main reversed- /// evals buffer; `dummy_eval_words[i]` points at - /// `REVERSED_EVALS_MPTR + (num_evals + i) * 0x20`. The transcript + /// `REVERSED_EVALS_MPTR + (num_main_evals + i) * 0x20`, where + /// `num_main_evals` is the eval count *before* dummies are appended + /// (not `meta.num_evals`, which already includes them). The transcript /// loop reads `num_dummy_evals` extra Fr scalars after the main /// eval block and spills them into this buffer the same way the /// main loop does. From 69889bece29501ead200ef31da1c0c3dab162537 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:27:23 +0100 Subject: [PATCH 34/72] Reject VK runtime exceeding the EIP-170 code-size limit at codegen What: add an EIP-170 (24576-byte) runtime-size check to Halo2VerifyingKey::validate_payload_layout, plus an EIP_170_MAX_RUNTIME_BYTES constant. How: compute runtime_len() and return an Err (which the generator turns into a panic) when it exceeds 0x6000 bytes, before building the constructor memory layout. Why: the VK is shipped as its own data contract whose runtime is [INVALID, ...payload]. Unlike the main verifier (which solc size-checks), this hand-rolled runtime blob had no size guard, so a circuit with enough constants/commitments to push the payload past 24576 bytes would only fail at on-chain deployment with a cryptic max-code-size revert. Checking at generation turns that into an actionable build-time error. All 151 lib fixtures stay well under the limit, so no current output changes. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/render/models.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/render/models.rs b/proofs/solidity-verifier/src/lowering/render/models.rs index 0ff85f228..399d08667 100644 --- a/proofs/solidity-verifier/src/lowering/render/models.rs +++ b/proofs/solidity-verifier/src/lowering/render/models.rs @@ -257,6 +257,10 @@ pub(crate) struct Halo2VerifyingKey { pub(crate) const VK_RUNTIME_PREFIX: u8 = 0xfe; /// Number of bytes skipped before copying the separate VK payload. pub(crate) const VK_RUNTIME_PREFIX_LEN: usize = 1; +/// EIP-170 deployed-contract runtime code-size limit (24576 bytes). The VK is +/// shipped as its own data contract whose runtime is `[INVALID, ...payload]`, +/// so it must fit under this bound to be deployable on EIP-170 chains. +pub(crate) const EIP_170_MAX_RUNTIME_BYTES: usize = 0x6000; impl Halo2VerifyingKey { /// Reconstruct and validate the typed VK payload layout. @@ -306,7 +310,15 @@ impl Halo2VerifyingKey { self.len() )); } - let constructor_memory = VkConstructorMemoryLayout::new(self.runtime_len()); + let runtime_len = self.runtime_len(); + if runtime_len > EIP_170_MAX_RUNTIME_BYTES { + return Err(format!( + "VK runtime code size {runtime_len} bytes exceeds the EIP-170 limit of \ + {EIP_170_MAX_RUNTIME_BYTES} bytes; the VK data contract would revert at \ + deployment. Reduce the circuit's constant/commitment count." + )); + } + let constructor_memory = VkConstructorMemoryLayout::new(runtime_len); constructor_memory.validate()?; if self.constructor_payload_mptr != constructor_memory.payload_mptr { return Err(format!( From 01f45494af255544540a70a983e0f2ee8ad63cc2 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:27:58 +0100 Subject: [PATCH 35/72] Harden accumulator fixed-base/scalar count check to a release assert What: change the debug_assert_eq! guarding acc_fixed_bases.len() == fixed_scalar_count to a hard assert_eq!. How: one keyword change; message and operands unchanged. Why: acc_fixed_bases supplies the (base, negate) pairs for the accumulator's fixed-base MSM, which must line up one-to-one with the fixed_scalar_count scalars the generated verifier reads. If those counts ever diverged (e.g. an accumulator-encoding change altering fixed_scalar_count without updating the base construction), a debug_assert compiles out in release, so a release-built generator would silently emit an accumulator pairing check over mismatched base/scalar sets. A hard assert fails closed in every build. 151 lib tests pass. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- proofs/solidity-verifier/src/lowering/artifacts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/artifacts.rs b/proofs/solidity-verifier/src/lowering/artifacts.rs index 4dbf611ed..495795ba1 100644 --- a/proofs/solidity-verifier/src/lowering/artifacts.rs +++ b/proofs/solidity-verifier/src/lowering/artifacts.rs @@ -175,7 +175,7 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { })) .collect::>() }; - debug_assert_eq!( + assert_eq!( bases.len(), fixed_scalar_count, "accumulator fixed-base scalar tail must match generated bases" From b2e99e8c0b81dac63ac81dfcd3f5fb9760821a83 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 18 Jul 2026 23:29:41 +0100 Subject: [PATCH 36/72] Use checked arithmetic when reserving VK payload sections What: guard the word-cursor addition in reserve and the commitments*G1_WORDS multiplication in reserve_g1 with checked_add/checked_mul that return a layout error on overflow. How: replace `self.cursor_words += word_len` with checked_add and `commitments * G1_WORDS` with checked_mul, surfacing descriptive Err strings. Why: both reservations do unchecked usize arithmetic on externally-derived counts (constant/program/commitment sizes). An absurdly large count would wrap in release, producing a smaller-than-real cursor and a section map whose offsets overlap prior sections, i.e. a VK payload whose extcodecopy ranges silently alias. Overflow is unreachable for realistic circuits, but reserve/reserve_g1 already return Result, so failing closed here is free and keeps the section map monotonic by construction. 151 lib tests pass. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../solidity-verifier/src/lowering/layout/vk_payload.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/vk_payload.rs b/proofs/solidity-verifier/src/lowering/layout/vk_payload.rs index c7c21f047..a416d3576 100644 --- a/proofs/solidity-verifier/src/lowering/layout/vk_payload.rs +++ b/proofs/solidity-verifier/src/lowering/layout/vk_payload.rs @@ -111,7 +111,9 @@ impl VkPayloadLayout { word_offset: self.cursor_words, word_len, }; - self.cursor_words += word_len; + self.cursor_words = self.cursor_words.checked_add(word_len).ok_or_else(|| { + format!("VK payload word cursor overflow reserving {kind:?} ({word_len} words)") + })?; self.sections.push(section); Ok(section) } @@ -122,7 +124,10 @@ impl VkPayloadLayout { kind: PayloadSectionKind, commitments: usize, ) -> Result { - self.reserve(kind, commitments * G1_WORDS) + let word_len = commitments.checked_mul(G1_WORDS).ok_or_else(|| { + format!("VK payload G1 word overflow for {kind:?}: {commitments} commitments") + })?; + self.reserve(kind, word_len) } /// Return the section for `kind`, if it has been reserved. From 68adb9070fe8b57446492a8230cd6ed308b68309 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:37:13 +0100 Subject: [PATCH 37/72] Range-check accumulator scalars where they are read `validate_public_accumulator` reads the carried LHS/RHS scalars and every fixed-base tail scalar straight from calldata and feeds them to the EIP-2537 G1MSM precompile without any `< r` check of its own. Two consequences: * G1MSM reduces scalars mod r implicitly, so `s` and `s + r` are indistinguishable inside the helper. * For negated fixed bases the helper computes `mod(sub(r, s), r)`, which for `s >= r` evaluates to `(2^256 + r - s) mod r` -- not `-s mod r`, but that value shifted by the nonzero constant `2^256 mod r`. Nothing in this file rejected such a scalar. Canonicality was inherited from the instance-absorption loop in TranscriptProofParser.yul, which reverts on any instance word `>= r` and provably covers the accumulator region. But that loop runs *later*: validate_public_accumulator is called from VkLoading.yul before the transcript starts, so the invariant was an unstated cross-template ordering dependency. Reordering, weakening, or exempting the accumulator region from that loop would have silently turned the wrapped negation into a forgery vector. Fold `lt(scalar, r)` into the helper's own `out` flag at each of the three read sites, so the negation's precondition is enforced locally by the code that depends on it. Add a template-corpus test pinning the checks, matching how the other accumulator invariants in this file are covered (no fixture currently renders the accumulator branch). Co-Authored-By: Claude Opus 4.8 --- .../solidity-verifier/src/lowering/tests.rs | 20 +++++++++++++++++++ .../partials/verifier/AccumulatorHelpers.yul | 9 +++++++++ 2 files changed, 29 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index 7d5203944..39551dfb1 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -1273,6 +1273,26 @@ fn accumulator_points_are_prevalidated_before_transcript_work() { } } +#[test] +fn accumulator_scalars_are_range_checked_where_they_are_read() { + let verifier_template = verifier_template_corpus(); + + // `validate_public_accumulator` runs before the transcript instance loop + // that rejects non-canonical instance words, and EIP-2537 G1MSM reduces + // scalars mod r implicitly. Canonicality must therefore be enforced at the + // read sites in this helper rather than inherited from a later template. + for required in [ + "out := and(out, lt(lhs_scalar, r))", + "out := and(out, lt(rhs_scalar, r))", + "out := and(out, lt(fixed_scalar_{{ loop.index0 }}, r))", + ] { + assert!( + verifier_template.contains(required), + "accumulator scalars must be range-checked against r where they are read: {required}" + ); + } +} + #[test] fn accumulator_decoder_rejects_noncanonical_infinity() { let verifier_template = verifier_template_corpus(); diff --git a/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul index 3a5d5eb77..b0f088d47 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul @@ -259,6 +259,11 @@ // Carried-scalar layout: the circuit exposes the scalar // that multiplies the carried LHS point. let lhs_scalar := calldataload(lhs_scalar_ptr) + // Canonicality is enforced here rather than relying on the + // later instance-absorption loop: G1MSM reduces scalars + // mod r implicitly, so s and s+r would be indistinguishable + // inside this helper. + out := and(out, lt(lhs_scalar, r)) {%- else %} // Already-collapsed point-pair layout: carried scalars are // implicit one. @@ -319,6 +324,7 @@ {%- if self.expected_acc_has_carried_scalars %} // Explicit carried RHS scalar. let rhs_scalar := calldataload(rhs_scalar_ptr) + out := and(out, lt(rhs_scalar, r)) {%- else %} // Implicit unit scalar for already-collapsed point pairs. let rhs_scalar := 1 @@ -347,6 +353,9 @@ // corresponding base point is embedded in verifier memory at // {{ base_mptr|hex() }}. let fixed_scalar_{{ loop.index0 }} := calldataload(fixed_scalar_ptr) + // Reject non-canonical tail scalars before the negation below: + // for s >= r, `mod(sub(r, s), r)` is not -s mod r. + out := and(out, lt(fixed_scalar_{{ loop.index0 }}, r)) {%- if negate_scalar %} // Some accumulator bases are represented with a negated scalar // so the MSM can reuse the generated positive base point. From 2e49d03f1a87525fb72de1e46c29c1db3a6f0bdd Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:37:41 +0100 Subject: [PATCH 38/72] Leave batch_invert immediately on a failed modexp On a failed modexp staticcall (or a returndatasize mismatch), batch_invert set `ret := 0` and then kept going: `all_inv := mload(gp_mptr)` read back the stale frame header (0x20, the base-length word -- a failed staticcall writes no output), and the backward pass overwrote every word of memory[mptr_start, mptr_end) with garbage products derived from it before finally returning ret = 0. The current sole caller in Lagrange.yul folds `ret` into `success` and reverts at the section boundary, so the clobbered range is never consumed. But the intermediate loops between the call and that revert already compute and persist L_LAST/L_BLIND/L_0/INSTANCE_EVAL from the clobbered denominators, and any future caller -- or a reordering that moves the success check later -- would read meaningless values while believing memory was left intact. Bail out with `leave` as soon as `ret` is zero, mirroring the early-exit structure the single-element fast path already uses, so a failed inverse leaves the input range untouched. Co-Authored-By: Claude Opus 4.8 --- .../templates/partials/verifier/AssemblyHelpers.yul | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul index f3ddbfc73..2f2e4208a 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul @@ -188,6 +188,12 @@ mstore(add(gp_mptr, {{ template_constants.modexp.mod_offset|hex() }}), r) ret := staticcall(gas(), {{ template_constants.modexp.address|hex() }}, gp_mptr, {{ template_constants.modexp.frame_bytes|hex() }}, gp_mptr, {{ template_constants.modexp.output_bytes|hex() }}) ret := and(ret, eq(returndatasize(), {{ template_constants.modexp.output_bytes|hex() }})) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total From 636c7ecefc17a35bc63d25aae5e895e611530d3e Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:38:32 +0100 Subject: [PATCH 39/72] Make the verifier's accept path fail closed locally The generated verifier's terminal code was `mstore(RETURN_MPTR, 1); return(RETURN_MPTR, 0x20)` and never consulted `success`. That was safe only because of a two-link chain across three files: FinalPairing.yul reverts if `success` is zero before calling ec_pairing, and ec_pairing reverts internally on any pairing or precompile failure. The weak link was ec_pairing's own entry guard, `ret := success; if iszero(ret) { leave }` -- the one exit in that function that returns a zero flag instead of reverting. Every other helper in this codebase is written in success-plumbing style (batch_invert, validate_public_ accumulator return booleans the caller folds in), and the upstream PSE verifier this derives from returns the pairing flag for the caller to fold. So a plausible future refactor -- dropping FinalPairing's pre-revert, or making ec_pairing return its flag "like everything else" -- would have made the verifier return true for any calldata, since FinalPairing assigns the result into `success` and nothing reads it again. Close both ends: * ec_pairing reverts on a zero input flag, so it has no path that returns control with the proof unverified. * TraceReturn.yul reverts on `iszero(success)` before returning true. Redundant today, but it makes acceptance a local property of the file that performs it. Co-Authored-By: Claude Opus 4.8 --- .../templates/partials/verifier/AssemblyHelpers.yul | 7 ++++++- .../templates/partials/verifier/TraceReturn.yul | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul index 2f2e4208a..d019b60a2 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul @@ -218,7 +218,12 @@ // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to diff --git a/proofs/solidity-verifier/templates/partials/verifier/TraceReturn.yul b/proofs/solidity-verifier/templates/partials/verifier/TraceReturn.yul index 5f040942c..7d3f27f40 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/TraceReturn.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/TraceReturn.yul @@ -54,5 +54,11 @@ // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) From d870dd15a933211f7cc40cf5312e2508deda0cf8 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:39:07 +0100 Subject: [PATCH 40/72] Reject non-reduced inputs in the Fr inversion helpers `scalar_inv` and `batch_invert`'s single-element fast path rejected only the literal word 0. Both then call modexp(x, r-2, r), which returns 0 for *any* x congruent to 0 mod r -- x = r being the obvious case. Such an input would be handed back as if it were a valid inverse, and the downstream mulmod chains that reconstruct f_eval and the Lagrange values would silently absorb the zero rather than failing. Every emitted call site today feeds these helpers addmod/mulmod output, which is always < r, so this is not reachable: the barycentric chains in the generated PCS blocks and the Lagrange denominators are all reduced by construction. But that precondition was only implicit, and it is exactly the kind of thing a future emitter passing a raw calldata scalar would violate without any signal. Check the full canonical range at both sites. scalar_inv reverts (its existing failure mode); the batch_invert fast path returns ret = 0, which its caller already folds into `success`. Co-Authored-By: Claude Opus 4.8 --- .../partials/verifier/AssemblyHelpers.yul | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul index d019b60a2..43c77cfe7 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul @@ -5,7 +5,13 @@ // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } let p := {{ memory.scalar_inv_scratch_mptr|hex() }} // EIP-198 modexp frame: @@ -142,6 +148,13 @@ // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave From b680bf2ffed805d10c0a3ffa560c495d28487bdf Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:39:35 +0100 Subject: [PATCH 41/72] Check spilled-stack balance in the quotient VM epilogue The interpreter's fail-closed epilogue checked that the program counter landed exactly on q_end and that no cached top was left live, but never that q_sp had returned to the base of the operand stack. Those two checks miss precisely one state: a FOLD executed while more than one operand is live consumes only the cached top, leaving abandoned spill words below q_sp with q_has_top clear. Both existing checks pass, the VM reports success, and nu_y(x) has been computed from a truncated expression -- one operand of the identity silently dropped. The result is a verifier that enforces a different polynomial identity than the circuit. This is unreachable today: the offline validator (validate_quotient_ program / apply_quotient_stack_effect) requires depth exactly 1 at every fold and 0 at the end, and the bytecode is VK-pinned. But these runtime checks exist specifically as a backstop for malformed generator output, and per the comment above them they are meant to catch a final opcode that "over-reads operands or leaves a partial expression live". A stack-effect table regression in a future encoder or compactor is exactly what would produce the one state they could not see, and it would produce a wrong-but-accepted verifier rather than a reverting one. Add the q_sp equality check next to the other two; it costs three opcodes once per verification. Co-Authored-By: Claude Opus 4.8 --- .../partials/quotient_numerator/QuotientNumeratorBlock.yul | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul b/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul index e206aed4e..92c63217c 100644 --- a/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul +++ b/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul @@ -1190,6 +1190,12 @@ // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, {{ program.stack_mptr|hex() }})) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled From 3f95c05d462f2afd70b53bdd943aa5f64c8d278c Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:40:12 +0100 Subject: [PATCH 42/72] Initialize the y^0 slot in the selector power table The precompute loop wrote y^i for i in 1..=selector_max_power, leaving word 0 of the table (already reserved -- selector_power_words allocates max_power + 1 words) never written. Nothing read it, but only because of two conventions maintained in a different file: FOLD_SELECTOR guards its read on a nonzero gap, and selector_tail_updates filters out zero tails. The template's tail block has no such guard -- it multiplies the selector bucket by mload(selector_power_mptr + power_offset) unconditionally. If either filter regressed (a tail of 0 emitted for a selector whose last identity is the final one in the stream), the bucket would be scaled by an uninitialized word, corrupting that selector's linearization scalar and flipping the final PCS check on whatever happened to be in memory from an earlier phase. Store 1 into slot 0 during the precompute. One mstore, and the table is then correct for every index a reader could compute. Co-Authored-By: Claude Opus 4.8 --- .../quotient_numerator/QuotientNumeratorBlock.yul | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul b/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul index 92c63217c..f4de01c7b 100644 --- a/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul +++ b/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul @@ -126,7 +126,14 @@ { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore({{ program.selector_power_mptr|hex() }}, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, {{ program.selector_max_power + 1 }}) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) From 14cb772757a811a8f2968abbef15b91a04534ad2 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:40:43 +0100 Subject: [PATCH 43/72] Count quotient limbs in absorb bytes in eval_run_bytes `eval_run_bytes` is a bound on transcript absorb bytes, but it summed `proof.quotient_limbs.byte_len` -- a *calldata* length -- while its two sibling bounds, initial_run_bytes and total_run_bytes, correctly use `layout::transcript::G1_ABSORB_BYTES`. The mixed units are invisible only because G1_ABSORB_BYTES == G1_BYTES == 128 right now. The term is also provably dead: total_run_bytes - eval_run_bytes reduces to word_absorb + (total_g1_count - num_quotients) * 128 + squeeze_cushion, which is non-negative, so eval_run_bytes can never be the max that sets `words`. That makes the wrong units silent -- no test can currently distinguish them. These two facts compound badly. If the transcript G1 absorb encoding ever diverges from the calldata encoding again -- as it did before the fix that stopped absorbing compressed 48/49-byte G1s against padded calldata -- then any refactor that starts trusting eval_run_bytes (using it for a per-run assertion, or dropping the conservative total_run_bytes) inherits a silently undersized keccak buffer, and the absorb loop overruns VK_MPTR, corrupting K_MPTR/OMEGA_MPTR. Count the limbs as `item_count * g1_absorb` so the bound is expressed in the units it claims. Value is unchanged today; the term stays as the per-run documentation of the eval phase. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/lowering/abi/proof.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/abi/proof.rs b/proofs/solidity-verifier/src/lowering/abi/proof.rs index 82c32efb8..e998770ca 100644 --- a/proofs/solidity-verifier/src/lowering/abi/proof.rs +++ b/proofs/solidity-verifier/src/lowering/abi/proof.rs @@ -347,7 +347,13 @@ impl TranscriptBufferLayout { + pre_squeeze_advices * g1_absorb + squeeze_cushion; - let eval_run_bytes = proof.quotient_limbs.byte_len + // Absorb bytes, so the quotient limbs are counted at the transcript's + // G1 absorb width -- NOT `quotient_limbs.byte_len`, which is a + // calldata length. The two happen to be equal today, but they have + // diverged before (a compressed 48/49-byte transcript encoding against + // padded calldata), and that divergence is what overruns the keccak + // buffer into `VK_MPTR`. + let eval_run_bytes = proof.quotient_limbs.item_count * g1_absorb + proof.evals.byte_len + proof.q_evals.byte_len + squeeze_cushion; From 79dd945701009f4bfd154f198ac6ef122db6db33 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:42:11 +0100 Subject: [PATCH 44/72] Assert VK permutation commitment count matches meta in Data::new Companion to the existing fixed-commitment assert. `permutation_comms` is built by zipping `meta.permutation_columns` against `EcPoint::range(permutation_comm_mptr)` with `izip!`, which silently truncates to the shorter side. Nothing checked that the VK actually carries one commitment per permutation column. Both counts derive from the same VerifyingKey and are equal by halo2 construction, so this is not reachable through the public API. But midnight-proofs' `VerifyingKey::read` takes column counts from file-controlled u32s without cross-checking the constraint system, so a crafted or corrupted VK could under-report them. The truncating zip would then quietly drop permutation columns from the generated verifier's permutation argument -- a verifier enforcing a weaker statement than the circuit -- instead of failing at codegen. Assert equality next to the fixed-commitment check that guards the same region's base pointer. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/lowering/encoding/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index d99515f00..5a98f1f97 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -320,6 +320,17 @@ impl Data { meta.num_fixeds, "VK fixed commitment count must match constraint-system fixed count" ); + // Same argument for the permutation region: `permutation_comms` below + // zips `meta.permutation_columns` against `EcPoint::range(...)`, and + // `izip!` silently truncates to the shorter side. A VK carrying fewer + // commitments than the constraint system has permutation columns would + // therefore drop columns from the permutation argument rather than + // fail. + assert_eq!( + vk.permutation_comms.len(), + meta.permutation_columns.len(), + "VK permutation commitment count must match constraint-system permutation column count" + ); let permutation_comm_mptr = fixed_comm_mptr + G1_WORDS * vk.fixed_comms.len(); let challenge_mptr = memory.challenge_mptr; let theta_mptr = memory.theta_mptr; From 9d7081bf091958d48f5305425cc985e2e94fa65b Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:44:00 +0100 Subject: [PATCH 45/72] Panic instead of rendering a negative pointer as sub(0, N) `Value::Integer` holds a signed byte offset, and `Display` rendered a negative one as `sub(0, N)`. The doc comment justified this by claiming the result "only ever appears as `ptr_end` in `lt(ptr_end, ptr)` style loops where any value strictly less than the smallest visited address is acceptable". That justification is backwards. `sub(0, N)` in EVM arithmetic is `2^256 - N`, and `lt` is unsigned -- so the value is *larger* than every representable address, not smaller. A descending loop written exactly as the comment describes, for { ptr := start } lt(ptr_end, ptr) { ptr := sub(ptr, 0x20) } { ... } would evaluate `lt(2^256 - N, start)` as false on the first check and run zero iterations, silently skipping whatever proof reads, absorption, or validation the body performs. That is the classic shape of a fail-open bug, and the comment invited a future emitter to rely on it. No emitter produces a negative offset today (no rendered template contains `sub(0,`), so this is latent. Make it stay that way: panic on a negative concrete offset at render time and correct the comment to say why no rendering is possible. The `Sub` impls stay -- they are not dead as the report assumed. The Askama template Constants.sol uses `proof_cptr - 1`, which is exactly the kind of caller the panic now protects. Co-Authored-By: Claude Opus 4.8 --- .../src/lowering/encoding/mod.rs | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index 5a98f1f97..ec7e2f086 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -604,11 +604,17 @@ pub(crate) enum Location { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum Value { - /// Byte offset stored as signed so that the BLS code-gen can compute - /// `ptr - N` even when `N` exceeds the original offset (the result - /// only ever appears as `ptr_end` in `lt(ptr_end, ptr)` style loops - /// where any value strictly less than the smallest visited address is - /// acceptable). + /// Byte offset. Stored as signed for historical reasons; concrete + /// offsets must be non-negative and `Display` panics otherwise. + /// + /// A negative offset has no correct rendering here. It used to be + /// emitted as `sub(0, N)`, which in EVM unsigned arithmetic is + /// `2^256 - N` -- the *largest* representable word. The previous + /// comment claimed such a value was safe because it "only ever appears + /// as `ptr_end` in `lt(ptr_end, ptr)` style loops where any value + /// strictly less than the smallest visited address is acceptable", but + /// `lt` is unsigned, so it is larger than every address and such a loop + /// runs zero iterations. Integer(isize), /// A symbolic Yul identifier `name`, with an optional byte-offset that /// will be rendered as `add(name, 0xNN)` (or just `name` when zero). @@ -673,7 +679,9 @@ impl Display for Value { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Value::Integer(int) if *int >= 0 => write!(f, "{}", fmt_hex(*int)), - Value::Integer(int) => write!(f, "sub(0, {})", fmt_hex(-*int)), + Value::Integer(int) => { + panic!("negative pointer offset {int} has no correct Yul rendering") + } Value::Identifier(ident, 0) => write!(f, "{ident}"), Value::Identifier(ident, off) if *off > 0 => { write!(f, "add({ident}, {})", fmt_hex(*off)) @@ -685,29 +693,29 @@ impl Display for Value { } } -impl Add for Value { - /// Pointer-expression value after word-wise addition. +impl Sub for Value { + /// Pointer-expression value after word-wise subtraction. type Output = Value; - /// Advance by `rhs` EVM words. - fn add(self, rhs: usize) -> Self::Output { + /// Move backward by `rhs` EVM words. + fn sub(self, rhs: usize) -> Self::Output { match self { - Value::Integer(int) => Value::Integer(int + (rhs as isize) * WORD_BYTES as isize), + Value::Integer(int) => Value::Integer(int - (rhs as isize) * WORD_BYTES as isize), Value::Identifier(name, off) => { - Value::Identifier(name, off + (rhs as isize) * WORD_BYTES as isize) + Value::Identifier(name, off - (rhs as isize) * WORD_BYTES as isize) } } } } -impl Sub for Value { - /// Pointer-expression value after word-wise subtraction. +impl Add for Value { + /// Pointer-expression value after word-wise addition. type Output = Value; - /// Move backward by `rhs` EVM words. - fn sub(self, rhs: usize) -> Self::Output { + /// Advance by `rhs` EVM words. + fn add(self, rhs: usize) -> Self::Output { match self { - Value::Integer(int) => Value::Integer(int - (rhs as isize) * WORD_BYTES as isize), + Value::Integer(int) => Value::Integer(int + (rhs as isize) * WORD_BYTES as isize), Value::Identifier(name, off) => { - Value::Identifier(name, off - (rhs as isize) * WORD_BYTES as isize) + Value::Identifier(name, off + (rhs as isize) * WORD_BYTES as isize) } } } @@ -756,16 +764,6 @@ impl Display for Ptr { } } -impl Add for Ptr { - /// Pointer with the same location and advanced value. - type Output = Ptr; - /// Advance by `rhs` EVM words while preserving location. - fn add(mut self, rhs: usize) -> Self::Output { - self.value = self.value + rhs; - self - } -} - impl Sub for Ptr { /// Pointer with the same location and rewound value. type Output = Ptr; @@ -776,6 +774,16 @@ impl Sub for Ptr { } } +impl Add for Ptr { + /// Pointer with the same location and advanced value. + type Output = Ptr; + /// Advance by `rhs` EVM words while preserving location. + fn add(mut self, rhs: usize) -> Self::Output { + self.value = self.value + rhs; + self + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct Word(Ptr); From b4319da6d2c6340e6917e6d61e91064cd0fe7744 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:45:32 +0100 Subject: [PATCH 46/72] Reject non-canonical scalars in the proof repacking shim `repack_proof` validated G1 commitments (rejecting anything that fails subgroup-checked decompression with InvalidCompressedG1) but passed every eval and q_eval word straight through `scalar_le_to_be_word`, which only reverses byte order and never checks the value is < r. A caller feeding a corrupted or hostile native proof whose LE scalar bytes decode to a value >= r therefore got `Ok(calldata)` back from the shim -- calldata that the generated verifier is guaranteed to reject, since TranscriptProofParser.yul reverts on any eval or q_eval that is not a canonical Fr element. The caller pays gas for a doomed transaction and gets an opaque on-chain revert instead of a descriptive host-side error. This is fail-closed, not a soundness gap: the on-chain verifier range-checks every scalar and instance itself, so no forged acceptance was possible through the shim. It is purely about reporting the failure where the diagnostic is useful. Validate each scalar with `Fq::from_repr` -- the same canonicality test the native verifier applies -- and add a RepackError::NonCanonicalScalar variant carrying the byte offset and the offending word, mirroring the existing G1 error. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/api.rs | 12 ++++++++ .../src/lowering/calldata.rs | 28 +++++++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/proofs/solidity-verifier/src/api.rs b/proofs/solidity-verifier/src/api.rs index bbe20325a..e4e6671a2 100644 --- a/proofs/solidity-verifier/src/api.rs +++ b/proofs/solidity-verifier/src/api.rs @@ -389,6 +389,13 @@ pub enum RepackError { /// Hex encoding of the failing 48-byte compressed G1. bytes_hex: String, }, + /// One proof scalar is not a canonical Fr element (`>= r`). + NonCanonicalScalar { + /// Byte offset of the failing scalar in the native proof. + offset: usize, + /// Big-endian hex encoding of the failing 32-byte scalar. + bytes_hex: String, + }, } impl fmt::Display for RepackError { @@ -410,6 +417,11 @@ impl fmt::Display for RepackError { "invalid compressed G1 at compressed[{offset}..{}]: bytes = 0x{bytes_hex}", offset + layout::G1_COMPRESSED_BYTES ), + Self::NonCanonicalScalar { offset, bytes_hex } => write!( + f, + "non-canonical Fr scalar at compressed[{offset}..{}]: bytes = 0x{bytes_hex}", + offset + layout::WORD_BYTES + ), } } } diff --git a/proofs/solidity-verifier/src/lowering/calldata.rs b/proofs/solidity-verifier/src/lowering/calldata.rs index 0a3a462bc..3bafa14af 100644 --- a/proofs/solidity-verifier/src/lowering/calldata.rs +++ b/proofs/solidity-verifier/src/lowering/calldata.rs @@ -6,6 +6,7 @@ //! big-endian scalar words. This module performs that deterministic boundary //! conversion. +use ff::PrimeField; use group::GroupEncoding; use midnight_curves::{Fq, G1Affine}; @@ -84,11 +85,26 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { out.extend_from_slice(&y_be[16..48]); Ok(()) }; - let push_scalar_be = |cursor: &mut usize, out: &mut Vec| { - out.extend_from_slice(&scalar_le_to_be_word( - &compressed[*cursor..*cursor + layout::WORD_BYTES], - )); + let push_scalar_be = |cursor: &mut usize, out: &mut Vec| -> Result<(), RepackError> { + let le = &compressed[*cursor..*cursor + layout::WORD_BYTES]; + // The generated verifier reverts on any eval or q_eval word that + // is not a canonical Fr element, so reject it here rather than + // handing the caller calldata that is guaranteed to revert + // on-chain. Mirrors the G1 validation in `push_g1`. + let mut arr = [0u8; layout::WORD_BYTES]; + arr.copy_from_slice(le); + let repr = ::Repr::from(arr); + if Option::::from(Fq::from_repr(repr)).is_none() { + let mut be = arr; + be.reverse(); + return Err(RepackError::NonCanonicalScalar { + offset: *cursor, + bytes_hex: hex::encode(be), + }); + } + out.extend_from_slice(&scalar_le_to_be_word(le)); *cursor += layout::WORD_BYTES; + Ok(()) }; for &n in &plan.g1_groups { for _ in 0..n { @@ -98,13 +114,13 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { // evals (Fr 32-byte LE in native proof) -> BE calldata words // (incl. dummy slots). for _ in 0..plan.num_evals { - push_scalar_be(&mut cursor, &mut out); + push_scalar_be(&mut cursor, &mut out)?; } // f_com push_g1(&mut cursor, &mut out)?; // q_evals (Fr 32-byte LE in native proof) -> BE calldata words. for _ in 0..plan.num_point_sets { - push_scalar_be(&mut cursor, &mut out); + push_scalar_be(&mut cursor, &mut out)?; } // pi push_g1(&mut cursor, &mut out)?; From 55d548b8c074a951ddf6839a2ebe0c1c26ccc959 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:46:09 +0100 Subject: [PATCH 47/72] Correct the pairing-batch domain tag comment The constant was documented as ASCII "pairing-batch-acc-kzg" right-padded to one EVM word. It is not. The literal is 29 bytes -- 21 ASCII bytes plus 8 trailing zeros -- and FinalPairing.yul writes it with `mstore`, which left-pads numeric literals. The word actually hashed into alpha is 00 00 00 || "pairing-batch-acc-kzg" || 00 * 8 Domain separation is unaffected, since the value is still a fixed unique constant, and no code reads the tag by reconstructing it from the ASCII. But the documented form and the implemented form differ, so any external tool, differential fixture, or future reimplementation that derives alpha from the right-padded spec would compute a different challenge than the deployed verifier and diverge on accept/reject when reproducing a verification. Document the actual hashed word rather than changing the constant -- changing it would alter every derived alpha for no benefit. Co-Authored-By: Claude Opus 4.8 --- .../src/lowering/layout/mod.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/mod.rs b/proofs/solidity-verifier/src/lowering/layout/mod.rs index 8612fc319..6beb93b2d 100644 --- a/proofs/solidity-verifier/src/lowering/layout/mod.rs +++ b/proofs/solidity-verifier/src/lowering/layout/mod.rs @@ -142,7 +142,22 @@ pub(crate) mod accumulator { /// Low-memory hash frame for batching the accumulator pairing with KZG: /// domain tag word, KZG rhs/lhs G1s, then accumulator rhs/lhs G1s. pub(crate) const PAIRING_BATCH_PTR: usize = 0x100; - /// ASCII `"pairing-batch-acc-kzg"` right-padded to one EVM word. + /// Domain-separation word for the accumulator pairing batch. + /// + /// This is a 29-byte numeric literal: ASCII `"pairing-batch-acc-kzg"` + /// (21 bytes) followed by 8 zero bytes. FinalPairing.yul stores it with + /// `mstore`, which left-pads numeric literals to a full word, so the word + /// actually hashed into alpha is + /// + /// ```text + /// 00 00 00 || "pairing-batch-acc-kzg" || 00 * 8 + /// ``` + /// + /// i.e. NOT right-padded ASCII, as this comment previously claimed. The + /// value is still a fixed unique constant, so domain separation is + /// unaffected -- but any reimplementation or differential fixture that + /// derives alpha from the right-padded form will disagree with the + /// deployed verifier on accept/reject. pub(crate) const PAIRING_BATCH_DOMAIN_TAG_HEX: &str = "0x70616972696e672d62617463682d6163632d6b7a670000000000000000"; /// KZG pairing RHS point offset inside the batch hash frame. From a1bb2c4d9505fce97488234d726edb7a3f4bf967 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:58:12 +0100 Subject: [PATCH 48/72] Move final pairing scratch past the accumulator batch frame The accumulator pairing-batch hash frame occupies [PAIRING_BATCH_PTR, PAIRING_BATCH_PTR + PAIRING_BATCH_HASH_BYTES) = [0x100, 0x320), while final_pairing_scratch started at FINAL_PAIRING_SCRATCH_START = PAIRING_TWO_PAIR_BYTES = 0x300. The two regions therefore shared the word [0x300, 0x320) -- the last word of the ACC_LHS copy inside the alpha keccak preimage was physically the same memory as the first word of ec_pairing's input frame. Nothing caught this. The regions carry different MemoryPhases (AccumulatorPairingBatch vs FinalPairing), and MemoryLifetime::intersects returns false for any two distinct phases, so the arena's overlap validation is structurally incapable of reporting it. It was correct only by execution order within a single straight-line file: FinalPairing.yul computes the batch keccak, and makes its last read of the frame, before ec_pairing writes its scratch. Any future change that writes the pairing scratch earlier -- pre-staging the G2 pairs to save an mcopy, or emitting a debug re-hash after the pairing -- would corrupt the y_lo word of ACC_LHS inside the alpha preimage while validate() still passed. A partially-unbound alpha weakens the Fiat-Shamir binding of the randomized batching. Derive FINAL_PAIRING_SCRATCH_START from the batch frame's end so the two cannot overlap by construction (0x300 -> 0x320), and assert disjointness in fixed_low_memory_regions_are_planner_registered, since that assertion is now the only check that can catch a regression. Verified end-to-end: rendered scratch moves to 0x0320 and the fixture verifiers still compile under solc and verify real proofs on revm. Co-Authored-By: Claude Opus 4.8 --- .../src/lowering/layout/memory.rs | 23 +++++++++++++++++++ .../src/lowering/layout/mod.rs | 11 ++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 632e7ac20..808af34ce 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -1453,6 +1453,29 @@ mod tests { meta.num_simple_selectors * G1_MSM_PAIR_BYTES ); + // These two regions carry different phases, and `MemoryLifetime:: + // intersects` treats distinct phases as never co-live -- so the arena's + // own overlap validation is structurally blind to an overlap here and + // this assertion is the only thing that catches one. + let batch = layout + .map + .region("accumulator_pairing_batch") + .expect("accumulator pairing batch registered"); + let final_pairing = layout + .map + .region("final_pairing_scratch") + .expect("final pairing scratch registered"); + assert!( + batch.start + batch.len <= final_pairing.start, + "accumulator pairing batch [{:#x}, {:#x}) must not overlap final pairing scratch \ + [{:#x}, {:#x}): the last word of the hashed ACC_LHS copy would share bytes with \ + ec_pairing's input frame", + batch.start, + batch.start + batch.len, + final_pairing.start, + final_pairing.start + final_pairing.len, + ); + let scalar_inv = layout .map .region("scalar_inv_scratch") diff --git a/proofs/solidity-verifier/src/lowering/layout/mod.rs b/proofs/solidity-verifier/src/lowering/layout/mod.rs index 6beb93b2d..59bc19f1e 100644 --- a/proofs/solidity-verifier/src/lowering/layout/mod.rs +++ b/proofs/solidity-verifier/src/lowering/layout/mod.rs @@ -89,7 +89,16 @@ pub(crate) const PCS_STATIC_WORKING_WORDS: usize = 32; /// Static two-pair KZG pairing scratch plus one return word. pub(crate) const PAIRING_STATIC_WORKING_WORDS: usize = PAIRING_TWO_PAIR_BYTES / WORD_BYTES + 1; /// Low-memory frame used by the final two-pair KZG pairing helper. -pub(crate) const FINAL_PAIRING_SCRATCH_START: usize = PAIRING_TWO_PAIR_BYTES; +/// +/// Placed past the end of the accumulator pairing-batch hash frame, which +/// occupies `[PAIRING_BATCH_PTR, PAIRING_BATCH_PTR + PAIRING_BATCH_HASH_BYTES)` +/// = `[0x100, 0x320)`. Starting at `PAIRING_TWO_PAIR_BYTES` (0x300) instead +/// would put the last word of the hashed ACC_LHS copy inside this scratch. +/// The two regions carry different `MemoryPhase`s, and `MemoryLifetime:: +/// intersects` treats distinct phases as never co-live, so the planner cannot +/// catch that overlap -- it has to be avoided by construction here. +pub(crate) const FINAL_PAIRING_SCRATCH_START: usize = + accumulator::PAIRING_BATCH_PTR + accumulator::PAIRING_BATCH_HASH_BYTES; pub(crate) mod precompile { //! EVM precompile addresses used by the generated verifier. These values From 914c61c6630054c5e1a4898857d155a22c36d14e Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 00:59:56 +0100 Subject: [PATCH 49/72] Order MemoryPhase to match generated-verifier execution order MemoryLifetime::intersects decides whether two memory regions may share bytes by comparing MemoryPhase values with <=, so the enum's declaration order is trusted as the runtime execution order. Two variants were in the wrong place: * AccumulatorMsm was declared after PcsPairing, but its only user -- validate_public_accumulator, the sole consumer of acc_msm_scratch -- is called from VkLoading.yul immediately after the VK payload loads, before the transcript has started. * ScalarInv was declared between Transcript and LagrangeBatchInvert, but scalar_inv is called from the PCS f_eval interpolation. In the rendered verifier its call sites sit after the q_eval source-table fold and before the fused final MSM, nowhere near transcript absorption. No current region pair turns this into corruption -- the arena validates to the correct verdict, but for the wrong reason. The hazard is a future region: a PhaseSpan { start: QuotientVm, end: PcsFinalMsm }, exactly the span the selector accumulators already use, overlapping scalar_inv's scratch at [vk_mptr - 0x100, vk_mptr - 0x40) would have been accepted by validate() (ScalarInv sorted below QuotientVm) while scalar_inv's modexp frames were in fact written in the middle of that span -- corrupting the region and flipping the verification result with no codegen-time error. Move both variants to their true positions and document that this order is load-bearing and must track the include order in Halo2Verifier.sol. The full EVM gate still passes, which confirms the reorder does not expose a real overlap; it only makes the validator ask the right question. Co-Authored-By: Claude Opus 4.8 --- .../src/lowering/layout/memory.rs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 808af34ce..59615e34b 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -117,16 +117,26 @@ impl ThetaWindowLayout { } } +/// Verifier execution phases, in the order the generated code runs them. +/// +/// The derived `Ord` is load-bearing: `MemoryLifetime::intersects` compares +/// phases with `<=` to decide whether a `PhaseSpan` covers a `Phase`, so a +/// variant declared out of runtime order makes the arena's overlap validation +/// answer the wrong question. Keep this list in sync with the include order in +/// `templates/contracts/Halo2Verifier.sol` and the call sites it renders. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub(crate) enum MemoryPhase { /// Generated verifying-key constructor return payload. VkConstructorPayload, /// Constructor-only precompile smoke tests. ConstructorSmoke, + /// Public-accumulator MSM input buffer. + /// + /// `validate_public_accumulator` runs from VkLoading.yul, immediately + /// after the VK payload is loaded and *before* the transcript starts. + AccumulatorMsm, /// Streaming Fiat-Shamir buffer before generated VK memory is live. Transcript, - /// Single scalar inversion scratch used by the modexp wrapper. - ScalarInv, /// Batch inversion for Lagrange denominator terms. LagrangeBatchInvert, /// Compact quotient VM temps and stack. @@ -135,12 +145,16 @@ pub(crate) enum MemoryPhase { PcsQEvalSourceTable, /// Optional trace-only q_com MSM materialization. PcsQComTrace, + /// Single scalar inversion scratch used by the modexp wrapper. + /// + /// `scalar_inv` is called from the PCS f_eval interpolation, i.e. after + /// the q_eval source-table fold and before the fused final MSM -- not + /// during transcript absorption. + ScalarInv, /// Fused final PCS MSM input buffer. PcsFinalMsm, /// Low-memory PCS pairing input helpers. PcsPairing, - /// Public-accumulator MSM input buffer. - AccumulatorMsm, /// Public-accumulator pairing-batch hash and two G1 add/MSM frames. AccumulatorPairingBatch, /// Final two-pair KZG pairing frame. From ed547aed7889fb3fa8a4c5ca063ef882a501e679 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:02:32 +0100 Subject: [PATCH 50/72] Register the trace-only linearization MSM scratch with the arena In trace renders, QuotientAndLinearization.yul expands the linearization terms into a G1MSM frame at `add(SELECTOR_ACC_MPTR, selector_len)` and writes (num_quotients + num_simple_selectors) * 0xa0 bytes there. That byte range was never registered in the MemoryArena: lin_trace_len was computed only to size the constructor's G1MSM smoke maximum, which is allocated at a different base. The write band was therefore in bounds by accident. Its safety rested on an unvalidated coincidence -- that the PCS scratch trio allocated from the same base (quotient_tmp_base) happens to be at least as long, because final_msm.input_bytes covers (num_quotients + nsel + 1) * 0xa0 -- and on the trace log word being placed past the PCS scratch ends. Neither memory.validate() nor validate_layout() could see the region at all, so a future layout change that shrank or relocated the PCS band (moving pcs_final_msm_scratch to a dedicated base, or a scheme change reducing final-MSM terms) would let the trace MSM silently overwrite whatever followed -- first casualty trace_u256_log_word, then unregistered memory -- with no codegen-time diagnostic. Register it as "linearization_trace_msm" under a new LinearizationTrace phase (it runs after the quotient VM and before the PCS blocks reuse the band), feed its end into the trace-log-word placement, and assert its base equals the address the template computes so the two cannot drift. Trace builds are not the production verifier, so there was no path to accepting a bad proof; this closes a blind spot in the planner rather than an exploitable bug. Co-Authored-By: Claude Opus 4.8 --- .../src/lowering/layout/memory.rs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 59615e34b..fe3efd00c 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -141,6 +141,9 @@ pub(crate) enum MemoryPhase { LagrangeBatchInvert, /// Compact quotient VM temps and stack. QuotientVm, + /// Trace-only linearization-commitment MSM, run after the quotient VM and + /// before the PCS blocks reuse the same scratch band. + LinearizationTrace, /// Source-address table used by the rolled q_eval fold. PcsQEvalSourceTable, /// Optional trace-only q_com MSM materialization. @@ -843,6 +846,29 @@ impl VerifierMemoryLayout { ); (quotient_tmp_mptr, quotient_stack_mptr) }; + // Trace renders expand the linearization terms into their own G1MSM + // frame at `SELECTOR_ACC_MPTR + selector_len`, i.e. starting exactly at + // `quotient_tmp_base`. Register it so the write band is visible to the + // arena and to the trace-log-word placement below; without this the + // only thing keeping it in bounds is the incidental fact that the PCS + // scratch allocated from the same base happens to be at least as long. + let linearization_trace_msm_mptr = { + let mut scratch = arena.scratch_allocator(quotient_tmp_base); + scratch.alloc_phase_scratch( + "linearization_trace_msm", + lin_trace_len, + MemoryPhase::LinearizationTrace, + ) + }; + // QuotientAndLinearization.yul derives the frame base as + // `add(SELECTOR_ACC_MPTR, selector_len)`. Pin the equality so the + // registered region cannot drift away from the address the template + // actually writes. + assert_eq!( + linearization_trace_msm_mptr, + selector_acc_mptr + selector_len, + "linearization trace MSM region must start where the template computes lin_scratch" + ); let pcs_scratch_mptr = quotient_tmp_mptr; let ( pcs_q_eval_source_table_mptr, @@ -904,6 +930,7 @@ impl VerifierMemoryLayout { selector_acc_mptr + selector_len, batch_invert_scratch_mptr + batch_invert_len, quotient_stack_mptr + quotient_stack_len.max(MODEXP_FRAME_BYTES), + linearization_trace_msm_mptr + lin_trace_len, pcs_q_eval_source_table_mptr + q_eval_source_len, pcs_q_com_trace_scratch_mptr + q_com_trace_len, pcs_final_msm_scratch_mptr + final_msm_len, From 591206148e2c4432fb30cd4bd8ac51a03b6fa252 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:09:48 +0100 Subject: [PATCH 51/72] Compile the accumulator render arm in the default EVM gate No production fixture enables `with_accumulator`, so the entire `{%- if self.expected_has_accumulator %}` branch of AccumulatorHelpers.yul -- the packed-limb coordinate decoder, the pre-transcript public-accumulator MSM, and the fixed-base scalar tail -- was never handed to solc by the default test gate. The only coverage was the opt-in ivc_keccak_solidity bench, which needs k = 20, a release build, and external SRS assets, so in practice a Yul syntax error or a solc stack-depth regression in that branch could reach a release unnoticed. Rendered-template assertions in lowering/tests.rs pin the source text but never compile it. Render three accumulator shapes against the existing Poseidon VK -- fully collapsed, partially collapsed with a fixed-base scalar tail sized to the VK's own commitments (-G + fixed + permutation), and the point-pair form with implicit unit scalars -- and compile each verifier and VK under the pinned solc. Also pin the accumulator scalar canonicality checks, and assert the point-pair form reads no explicit scalar at all. Verified the coverage is real: injecting a Yul syntax error into the accumulator-only branch fails this test, and it passes again once reverted. This still does not execute the accumulator logic against a real proof. That needs a decider circuit carrying a genuine accumulator in its public inputs, which remains the ivc_keccak_solidity bench's job. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/test.rs | 120 ++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 3 deletions(-) diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index 0c585e179..6caf62614 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -45,9 +45,9 @@ use ruint::aliases::U256; use sha3::Digest; use crate::{ - compile_solidity, encode_calldata, pinned_solc_available, CallOutcome, Evm, GeneratorConfig, - RenderDiagnostics, RenderOptions, RenderQuotient, RenderVk, SolidityGenerator, - FN_SIG_VERIFY_PROOF, + compile_solidity, encode_calldata, pinned_solc_available, AccumulatorEncoding, CallOutcome, + Evm, GeneratorConfig, RenderDiagnostics, RenderOptions, RenderQuotient, RenderVk, + SolidityGenerator, FN_SIG_VERIFY_PROOF, }; /// Scalar field used by the BLS12-381 Poseidon fixtures. @@ -1485,6 +1485,120 @@ fn compile_solidity_is_deterministic_for_same_source() { assert_eq!(bytecode_a, bytecode_b); } +/// Compile the accumulator render arm. +/// +/// No production fixture enables `with_accumulator`, so before this test the +/// whole `{%- if self.expected_has_accumulator %}` branch of +/// AccumulatorHelpers.yul -- the limb decoder, the pre-transcript +/// public-accumulator MSM, and the fixed-base scalar tail -- was never handed +/// to solc by the default gate. Only the opt-in `ivc_keccak_solidity` bench +/// (k = 20, release, external SRS assets) rendered it, so a Yul syntax error +/// or a solc stack-depth regression in that branch could reach a release +/// unnoticed. +/// +/// This does not execute the accumulator logic against a real recursive proof +/// -- that still needs a decider circuit carrying a genuine accumulator in its +/// public inputs. It does guarantee the branch compiles, and it pins the +/// canonicality checks on the scalars the helper feeds to G1MSM. +#[test] +fn accumulator_verifier_variants_compile_with_pinned_solc() { + if !poseidon_inputs_available_for_evm() { + return; + } + + let srs_dir = srs_dir(); + env::set_var("SRS_DIR", &srs_dir); + let relation = PoseidonExample; + let srs = srs_for_test(&relation, Some(POSEIDON_K)); + let vk = setup_vk(&srs, &relation); + + // A fully collapsed accumulator occupies FULLY_COLLAPSED_PUBLIC_INPUT_WORDS + // public inputs and has no fixed-base scalar tail. + let collapsed_words = AccumulatorEncoding::FULLY_COLLAPSED_PUBLIC_INPUT_WORDS; + // The partially collapsed form appends one scalar per generated base: + // `-G`, then every fixed commitment, then every permutation commitment. + let num_fixed_comms = vk.vk().fixed_commitments().len(); + let num_perm_comms = vk.vk().permutation().commitments().len(); + let tail_words = 1 + num_fixed_comms + num_perm_comms; + + let variants = [ + ( + "fully collapsed accumulator", + collapsed_words, + AccumulatorEncoding::new(0, 7, 56), + true, + ), + ( + "accumulator with fixed-base scalar tail", + collapsed_words + tail_words, + AccumulatorEncoding::new(0, 7, 56), + true, + ), + // Point-pair encodings carry no explicit scalars -- both are implicit + // one -- so no calldata scalar is read and none is range-checked. + ( + "point-pair accumulator", + AccumulatorEncoding::POINT_PAIR_PUBLIC_INPUT_WORDS, + AccumulatorEncoding::point_pair(0, 7, 56), + false, + ), + ]; + + for (name, num_instances, acc, has_carried_scalars) in variants { + let generator = SolidityGenerator::new( + &srs, + vk.vk(), + GeneratorConfig::new(num_instances, 1).with_accumulator(acc), + ); + let artifacts = generator + .render(RenderOptions { + vk: RenderVk::Separate, + ..RenderOptions::default() + }) + .unwrap_or_else(|err| panic!("{name} should render: {err}")); + + let verifier = artifacts.verifier; + assert!( + verifier.contains("function validate_public_accumulator"), + "{name} should render the accumulator helper" + ); + // The helper runs before the transcript loop that rejects + // non-canonical instance words, and EIP-2537 G1MSM reduces scalars mod + // r implicitly, so it must reject `s >= r` itself. + if has_carried_scalars { + for required in ["lt(lhs_scalar, r)", "lt(rhs_scalar, r)"] { + assert!( + verifier.contains(required), + "{name} should range-check accumulator scalars: {required}" + ); + } + } else { + assert!( + !verifier.contains("lt(lhs_scalar, r)"), + "{name} carries no explicit scalars, so none should be read or checked" + ); + } + + for (label, source) in [ + (name, verifier.as_str()), + ( + "accumulator VK", + artifacts + .verifying_key + .as_deref() + .expect("separate render includes VK"), + ), + ] { + let bytecode = std::panic::catch_unwind(AssertUnwindSafe(|| compile_solidity(source))) + .unwrap_or_else(|_| panic!("{label} should compile under the pinned solc")); + assert!( + !bytecode.is_empty(), + "{label} should compile to non-empty bytecode" + ); + } + } +} + #[test] fn poseidon_verifier_variants_compile_with_pinned_solc() { if !poseidon_inputs_available_for_evm() { From 48a6f0cf84b110378f54412bf356396d8765194d Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:10:39 +0100 Subject: [PATCH 52/72] Regenerate tracked verifier fixture artifacts The committed fixture dumps under target/*-fixture-dump/ no longer matched what codegen produces. Two independent causes: * They were already stale before this branch's verifier changes. The quotient constant table in the VK dumps is ordered differently because of "Reserve fused add-product constants early" and "Reserve limb decomposition constants early", which legitimately change the order constants are interned -- but the artifacts were not regenerated at the time. Confirmed by regenerating at the parent commit and diffing against the committed file. * This branch's verifier-side hardening (Fr inversion range checks, batch_invert early leave, the ec_pairing/TraceReturn fail-closed guards, the quotient VM stack-balance check, the y^0 selector slot, and the final-pairing scratch move to 0x320) changes the rendered Yul. Codegen itself is deterministic -- regenerating twice on the same tree produces byte-identical output, and that output matches a regeneration at the parent commit -- so this is purely a refresh, not a fix for nondeterminism. Note: target/ivc-keccak-solidity-dump/ is also tracked and is the only dump covering the accumulator render path, but regenerating it requires the opt-in ivc_keccak_solidity bench (k = 20, release build, external SRS assets), so it is left untouched here and remains stale. Co-Authored-By: Claude Opus 4.8 --- .../hybrid-mt-fixture-dump/Halo2Verifier.sol | 53 +++++++++++++++++-- .../Halo2VerifyingKey.sol | 40 +++++++------- .../poseidon-fixture-dump/Halo2Verifier.sol | 51 ++++++++++++++++-- .../Halo2Verifier.sol | 51 ++++++++++++++++-- .../Halo2Verifier.sol | 53 +++++++++++++++++-- .../Halo2VerifyingKey.sol | 32 +++++------ 6 files changed, 226 insertions(+), 54 deletions(-) diff --git a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol index c0bd92500..3168b1c84 100644 --- a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol @@ -43,7 +43,7 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 8640; uint256 internal constant EXPECTED_VK_LENGTH = 8641; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0xaa09cafce41491f57d16b422d0224fa9090c5313065c1edccaf639ecd603e0fa; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0xd85df9a6825e0edb1a2ed376a8a317078c3f98f663d45355679082083fbbd1ad; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -321,7 +321,13 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } let p := 0x1ce0 // EIP-198 modexp frame: @@ -458,6 +464,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -504,6 +517,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -528,14 +547,19 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x0320 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) @@ -1401,7 +1425,14 @@ contract Halo2Verifier { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0x7100, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 42) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) @@ -2353,6 +2384,12 @@ contract Halo2Verifier { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0x7640)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -3223,6 +3260,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol index 17578c207..986773183 100644 --- a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol @@ -94,8 +94,8 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x03a0), 0x00000000000000000000000000000000006d57f79a18220d1e5ef04bd519e995) // neg_s_g2_y_c1_hi mstore(add(payload, 0x03c0), 0x9a9cc71553bb761b5422a6b6971b75c8d3695bfa07b861c4b1c958da426efc45) // neg_s_g2_y_c1_lo mstore(add(payload, 0x03e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // quotient_const - mstore(add(payload, 0x0400), 0x0000000000000000000000000000000000000000000000000000040000000000) // quotient_const - mstore(add(payload, 0x0420), 0x0000000000000000000000000000000000000000000000000000000000100000) // quotient_const + mstore(add(payload, 0x0400), 0x0000000000000000000000000000000000000000000000000000000000100000) // quotient_const + mstore(add(payload, 0x0420), 0x0000000000000000000000000000000000000000000000000000040000000000) // quotient_const mstore(add(payload, 0x0440), 0x0000000000000000000000000000000000000000000000000000000000000002) // quotient_const mstore(add(payload, 0x0460), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffefff00001) // quotient_const mstore(add(payload, 0x0480), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffe00001) // quotient_const @@ -119,19 +119,19 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x06c0), 0x0000000000000000000000000000000000000000000000000000000000040011) // quotient_const mstore(add(payload, 0x06e0), 0x0000000000000000000000000000000000000000000000000000001100000000) // quotient_const mstore(add(payload, 0x0700), 0x0000000000000000000000000000000000000000000000000000000044000000) // quotient_const - mstore(add(payload, 0x0720), 0x0000000000000000000000000000000000000000000000000000000000200000) // quotient_const - mstore(add(payload, 0x0740), 0x0000000000000000000000000000000000000000000000000000000000000400) // quotient_const - mstore(add(payload, 0x0760), 0x0000000000000000000000000000000000000000000000000000000000400000) // quotient_const + mstore(add(payload, 0x0720), 0x0000000000000000000000000000000000000000000000000000000000000400) // quotient_const + mstore(add(payload, 0x0740), 0x0000000000000000000000000000000000000000000000000000000000200000) // quotient_const + mstore(add(payload, 0x0760), 0x0000000000000000000000000000000000000000000000000000000000000004) // quotient_const mstore(add(payload, 0x0780), 0x0000000000000000000000000000000000000000000000000000000000002000) // quotient_const - mstore(add(payload, 0x07a0), 0x0000000000000000000000000000000000000000000000000000000000000004) // quotient_const - mstore(add(payload, 0x07c0), 0x0000000000000000000000000000000000000000000000000000100000000000) // quotient_const + mstore(add(payload, 0x07a0), 0x0000000000000000000000000000000000000000000000000000000000400000) // quotient_const + mstore(add(payload, 0x07c0), 0x0000000000000000000000000000000000000000000000000000000000000010) // quotient_const mstore(add(payload, 0x07e0), 0x0000000000000000000000000000000000000000000000000000000004000000) // quotient_const - mstore(add(payload, 0x0800), 0x0000000000000000000000000000000000000000000000000000000000000010) // quotient_const - mstore(add(payload, 0x0820), 0x0000000000000000000000000000000000000000000000000000000002000000) // quotient_const + mstore(add(payload, 0x0800), 0x0000000000000000000000000000000000000000000000000000100000000000) // quotient_const + mstore(add(payload, 0x0820), 0x0000000000000000000000000000000000000000000000000000000000000040) // quotient_const mstore(add(payload, 0x0840), 0x0000000000000000000000000000000000000000000000000000000000000800) // quotient_const - mstore(add(payload, 0x0860), 0x0000000000000000000000000000000000000000000000000000000000000040) // quotient_const - mstore(add(payload, 0x0880), 0x0000000000000000000000000000000000000000000000000004000000000000) // quotient_const - mstore(add(payload, 0x08a0), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x0860), 0x0000000000000000000000000000000000000000000000000000000002000000) // quotient_const + mstore(add(payload, 0x0880), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x08a0), 0x0000000000000000000000000000000000000000000000000004000000000000) // quotient_const mstore(add(payload, 0x08c0), 0x0000000000000000000000000000000000000000000000000000000000040000) // quotient_const mstore(add(payload, 0x08e0), 0x0000000000000000000000000000000000000000000000000000000000000080) // quotient_const mstore(add(payload, 0x0900), 0x0000000000000000000000000000000000000000000000000000000000000008) // quotient_const @@ -141,18 +141,18 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x0980), 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140) // quotient_const mstore(add(payload, 0x09a0), 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a) // quotient_const mstore(add(payload, 0x09c0), 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db) // quotient_const - mstore(add(payload, 0x09e0), 0x055b80105ea0055c2008060d000b020001055ca0105cc0105ba00901115d0013) // quotient_program - mstore(add(payload, 0x0a00), 0x5b6002105c000901115ce0135b4002105be00d030608060d000b030000055ca0) // quotient_program - mstore(add(payload, 0x0a20), 0x105cc00901115d00135b6002105c000901115ce0135b4002105be00d03060806) // quotient_program + mstore(add(payload, 0x09e0), 0x055b80105ea0055c2008060d000b020001055ca0105cc0105ba00902115d0013) // quotient_program + mstore(add(payload, 0x0a00), 0x5b6001105c000902115ce0135b4001105be00d030608060d000b030000055ca0) // quotient_program + mstore(add(payload, 0x0a20), 0x105cc00902115d00135b6001105c000902115ce0135b4001105be00d03060806) // quotient_program mstore(add(payload, 0x0a40), 0x0d000b040000055b80105ba0055bc008060d000b0400011b00001b0001210001) // quotient_program mstore(add(payload, 0x0a60), 0x00000700045b40055b60065b80075ba0085bc0095be00a5c000b5c200c5ca00d) // quotient_program mstore(add(payload, 0x0a80), 0x5cc00e5ce00f5d00105d20115d400b07000021000100000700045b40055b6012) // quotient_program mstore(add(payload, 0x0aa0), 0x5b80135ba0145bc0095be00a5c00155c20165ca0175cc00e5ce00f5d00185d20) // quotient_program - mstore(add(payload, 0x0ac0), 0x195d400b080000091a115d60135c601b105d80055da008060d000b090000091c) // quotient_program - mstore(add(payload, 0x0ae0), 0x115d60135dc01d135c601e105c80055da008060d000b0a0000091f115ce0135d) // quotient_program - mstore(add(payload, 0x0b00), 0x0020135b4021105b60055b8008060d000b0a00010922115d60135dc01d135c60) // quotient_program - mstore(add(payload, 0x0b20), 0x23135c8024105d80055da008060d000b0b00000925115ce0135d0020135b401c) // quotient_program - mstore(add(payload, 0x0b40), 0x135b6026105be0055b8008060d000b0b00011c275c40285c60295c80025d601b) // quotient_program + mstore(add(payload, 0x0ac0), 0x195d400b080000091b115d60135c601a105d80055da008060d000b090000091e) // quotient_program + mstore(add(payload, 0x0ae0), 0x115d60135dc01d135c601c105c80055da008060d000b0a00000921115ce0135d) // quotient_program + mstore(add(payload, 0x0b00), 0x0020135b401f105b60055b8008060d000b0a00010924115d60135dc01d135c60) // quotient_program + mstore(add(payload, 0x0b20), 0x23135c8022105d80055da008060d000b0b00000926115ce0135d0020135b401e) // quotient_program + mstore(add(payload, 0x0b40), 0x135b6025105be0055b8008060d000b0b00011c275c40285c60295c80015d601a) // quotient_program mstore(add(payload, 0x0b60), 0x5dc02a5de02b5e00105d80055da008060d000b0c0000090008105de0115de00d) // quotient_program mstore(add(payload, 0x0b80), 0x000b0c0001090008105c40115c400d000b0c0001090008105e00115e000d000b) // quotient_program mstore(add(payload, 0x0ba0), 0x0c00011c005ba0005bc0005c20005ca0005cc0005d20005d40055da0135e202c) // quotient_program diff --git a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol index 3660c9092..e5f1b4029 100644 --- a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol @@ -321,7 +321,13 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } let p := 0x1880 // EIP-198 modexp frame: @@ -458,6 +464,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -504,6 +517,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -528,14 +547,19 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x0320 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) @@ -1385,7 +1409,14 @@ contract Halo2Verifier { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0x5720, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 22) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) @@ -1943,6 +1974,12 @@ contract Halo2Verifier { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0x59e0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -2648,6 +2685,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol index 41513989e..d0caa0941 100644 --- a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol @@ -321,7 +321,13 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } let p := 0x15e0 // EIP-198 modexp frame: @@ -458,6 +464,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -504,6 +517,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -528,14 +547,19 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x0320 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) @@ -1357,7 +1381,14 @@ contract Halo2Verifier { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0x5060, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 15) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) @@ -1761,6 +1792,12 @@ contract Halo2Verifier { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0x5240)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -2251,6 +2288,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol index 2e9d4419e..9a8fd44af 100644 --- a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol @@ -43,7 +43,7 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 8032; uint256 internal constant EXPECTED_VK_LENGTH = 8033; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x6200af6d8b2f00ac28582e3c159c74e3e43daf38a03210efc438aa723d3e98bc; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x327e3a0642d1a157521eb891596256086b59006348710f0cd960b89ed35e5854; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -321,7 +321,13 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } let p := 0x1c20 // EIP-198 modexp frame: @@ -458,6 +464,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -504,6 +517,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -528,14 +547,19 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x0320 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) @@ -1373,7 +1397,14 @@ contract Halo2Verifier { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0x6d00, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 35) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) @@ -2323,6 +2354,12 @@ contract Halo2Verifier { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0x7160)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -2987,6 +3024,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol index 0ee83d389..7b15639cf 100644 --- a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol @@ -116,19 +116,19 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x0660), 0x0000000000000000000000000000000000000000000000000000000000040011) // quotient_const mstore(add(payload, 0x0680), 0x0000000000000000000000000000000000000000000000000000001100000000) // quotient_const mstore(add(payload, 0x06a0), 0x0000000000000000000000000000000000000000000000000000000044000000) // quotient_const - mstore(add(payload, 0x06c0), 0x0000000000000000000000000000000000000000000000000000000000200000) // quotient_const - mstore(add(payload, 0x06e0), 0x0000000000000000000000000000000000000000000000000000000000000400) // quotient_const - mstore(add(payload, 0x0700), 0x0000000000000000000000000000000000000000000000000000000000400000) // quotient_const + mstore(add(payload, 0x06c0), 0x0000000000000000000000000000000000000000000000000000000000000400) // quotient_const + mstore(add(payload, 0x06e0), 0x0000000000000000000000000000000000000000000000000000000000200000) // quotient_const + mstore(add(payload, 0x0700), 0x0000000000000000000000000000000000000000000000000000000000000004) // quotient_const mstore(add(payload, 0x0720), 0x0000000000000000000000000000000000000000000000000000000000002000) // quotient_const - mstore(add(payload, 0x0740), 0x0000000000000000000000000000000000000000000000000000000000000004) // quotient_const - mstore(add(payload, 0x0760), 0x0000000000000000000000000000000000000000000000000000100000000000) // quotient_const + mstore(add(payload, 0x0740), 0x0000000000000000000000000000000000000000000000000000000000400000) // quotient_const + mstore(add(payload, 0x0760), 0x0000000000000000000000000000000000000000000000000000000000000010) // quotient_const mstore(add(payload, 0x0780), 0x0000000000000000000000000000000000000000000000000000000004000000) // quotient_const - mstore(add(payload, 0x07a0), 0x0000000000000000000000000000000000000000000000000000000000000010) // quotient_const - mstore(add(payload, 0x07c0), 0x0000000000000000000000000000000000000000000000000000000002000000) // quotient_const + mstore(add(payload, 0x07a0), 0x0000000000000000000000000000000000000000000000000000100000000000) // quotient_const + mstore(add(payload, 0x07c0), 0x0000000000000000000000000000000000000000000000000000000000000040) // quotient_const mstore(add(payload, 0x07e0), 0x0000000000000000000000000000000000000000000000000000000000000800) // quotient_const - mstore(add(payload, 0x0800), 0x0000000000000000000000000000000000000000000000000000000000000040) // quotient_const - mstore(add(payload, 0x0820), 0x0000000000000000000000000000000000000000000000000004000000000000) // quotient_const - mstore(add(payload, 0x0840), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x0800), 0x0000000000000000000000000000000000000000000000000000000002000000) // quotient_const + mstore(add(payload, 0x0820), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x0840), 0x0000000000000000000000000000000000000000000000000004000000000000) // quotient_const mstore(add(payload, 0x0860), 0x0000000000000000000000000000000000000000000000000000000000040000) // quotient_const mstore(add(payload, 0x0880), 0x0000000000000000000000000000000000000000000000000000000000000080) // quotient_const mstore(add(payload, 0x08a0), 0x0000000000000000000000000000000000000000000000000000000000000008) // quotient_const @@ -140,12 +140,12 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x0960), 0x08060d000b0400011b00021b0003210001000007000158200258400358600458) // quotient_program mstore(add(payload, 0x0980), 0x800558a00658c00758e00859000959800a59a00b59c00c59e00d5a000e5a200b) // quotient_program mstore(add(payload, 0x09a0), 0x070000210001000007000158200258400f58601058801158a00658c00758e012) // quotient_program - mstore(add(payload, 0x09c0), 0x59001359801459a00b59c00c59e0155a00165a200b0800000917115a40135940) // quotient_program - mstore(add(payload, 0x09e0), 0x18105a60055a8008060d000b0900000919115a40135aa01a1359401b10596005) // quotient_program - mstore(add(payload, 0x0a00), 0x5a8008060d000b0a0000091c1159c01359e01d1358201e10584005586008060d) // quotient_program - mstore(add(payload, 0x0a20), 0x000b0a0001091f115a40135aa01a1359402013596021105a60055a8008060d00) // quotient_program - mstore(add(payload, 0x0a40), 0x0b0b000009221159c01359e01d13582019135840231058c005586008060d000b) // quotient_program - mstore(add(payload, 0x0a60), 0x0b00011c245920255940265960275a40185aa0285ac0295ae0105a60055a8008) // quotient_program + mstore(add(payload, 0x09c0), 0x59001359801459a00b59c00c59e0155a00165a200b0800000918115a40135940) // quotient_program + mstore(add(payload, 0x09e0), 0x17105a60055a8008060d000b090000091b115a40135aa01a1359401910596005) // quotient_program + mstore(add(payload, 0x0a00), 0x5a8008060d000b0a0000091e1159c01359e01d1358201c10584005586008060d) // quotient_program + mstore(add(payload, 0x0a20), 0x000b0a00010921115a40135aa01a135940201359601f105a60055a8008060d00) // quotient_program + mstore(add(payload, 0x0a40), 0x0b0b000009231159c01359e01d1358201b135840221058c005586008060d000b) // quotient_program + mstore(add(payload, 0x0a60), 0x0b00011c245920255940265960275a40175aa0285ac0295ae0105a60055a8008) // quotient_program mstore(add(payload, 0x0a80), 0x060d000b0c0000090008105ac0115ac00d000b0c00010900081059201159200d) // quotient_program mstore(add(payload, 0x0aa0), 0x000b0c0001090008105ae0115ae00d000b0c00011c0058800058a00059000059) // quotient_program mstore(add(payload, 0x0ac0), 0x800059a0005a00005a20055a80135b002a08060d000b0d0000191f0000000000) // quotient_program From 54907b59709e04326a52837e54776901672df027 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:22:13 +0100 Subject: [PATCH 53/72] Reject an accumulator fixed-base tail the VK cannot supply bases for The accumulator's fixed-base scalar tail is derived from the *instance* vector: fixed_scalar_count = num_instances - (offset + fixed_payload). The bases those scalars multiply are generated from the *verifying key*, as fixed_comm_mptr + i * 0x80 for i in 0..num_fixed_bases, where num_fixed_bases = fixed_scalar_count - (1 + num_permutation_comms) Nothing bounded num_fixed_bases against the VK's actual fixed-commitment count. A tail longer than the VK can supply bases for rendered cleanly and emitted base pointers past the end of the fixed-commitment region, aliasing permutation commitments -- and past those, arbitrary VK payload words -- as accumulator G1 bases. The generated verifier then checks a different accumulator equation than the native verifier, so honest proofs fail on-chain and the drift is discovered only at proof time. The existing assert did not catch this: `bases.len() == fixed_scalar_count` is a tautology, since `bases` is built as `1 + num_fixed_bases + num_perm_bases` with num_fixed_bases defined by subtracting those same terms from fixed_scalar_count. It can never fire. (An earlier commit promoted it from debug_assert to assert, which changed when it runs but not what it can detect.) Reproduced against the Poseidon VK: num_instances chosen for a 32-scalar tail against a VK with 19 fixed and 8 permutation commitments rendered successfully with 23 fixed bases, i.e. 4 pointers into the permutation region. Validate in SolidityGenerator::try_new, where both the encoding and the VK are in scope and a typed error is already the contract. Two tail lengths are rejected: longer than `-G` + permutations + every fixed commitment (the out-of-region case above), and shorter than `-G` + permutations (which underflows the base count in the artifact emitter). A tail inside that range is accepted: it covers a prefix of the fixed commitments and keeps every pointer in region. Nothing documents whether a prefix is meaningful, but it is memory-safe, and rejecting it would invent a stricter contract than the emitter implies. Add GeneratorError::AccumulatorFixedBaseTailMismatch carrying the supported range and both commitment counts, so a misconfigured num_instances reports the values it should have had, and replace the tautological assert in artifacts.rs with a bound that actually constrains num_fixed_bases. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/api.rs | 33 ++++++++ proofs/solidity-verifier/src/builder/api.rs | 29 +++++++ .../src/lowering/artifacts.rs | 16 ++++ proofs/solidity-verifier/src/test.rs | 79 +++++++++++++++++++ 4 files changed, 157 insertions(+) diff --git a/proofs/solidity-verifier/src/api.rs b/proofs/solidity-verifier/src/api.rs index e4e6671a2..0cf0be6e1 100644 --- a/proofs/solidity-verifier/src/api.rs +++ b/proofs/solidity-verifier/src/api.rs @@ -458,6 +458,25 @@ pub enum GeneratorError { num_instances: usize, reason: &'static str, }, + /// The accumulator's fixed-base scalar tail asks for more bases than this + /// verifying key can supply. + /// + /// The tail must be empty (fully collapsed accumulator), or cover `-G` plus + /// every permutation commitment plus at most one scalar per fixed + /// commitment. + AccumulatorFixedBaseTailMismatch { + /// Tail scalars implied by `num_instances` and the encoding. + fixed_scalar_count: usize, + /// Smallest non-empty tail this verifying key supports (`-G` plus every + /// permutation commitment). + min_fixed_scalar_count: usize, + /// Largest tail this verifying key supports. + max_fixed_scalar_count: usize, + /// Fixed commitments in the verifying key. + num_fixed_comms: usize, + /// Permutation commitments in the verifying key. + num_permutation_comms: usize, + }, /// Internal render/layout planning failed before Solidity was emitted. Planning { /// Planning stage. @@ -510,6 +529,20 @@ impl fmt::Display for GeneratorError { f, "unsupported accumulator encoding: offset={offset}, num_limbs={num_limbs}, num_limb_bits={num_limb_bits}, num_instances={num_instances}; {reason}" ), + Self::AccumulatorFixedBaseTailMismatch { + fixed_scalar_count, + min_fixed_scalar_count, + max_fixed_scalar_count, + num_fixed_comms, + num_permutation_comms, + } => write!( + f, + "accumulator fixed-base scalar tail of {fixed_scalar_count} is not supported by \ + this verifying key ({num_fixed_comms} fixed, {num_permutation_comms} permutation \ + commitments): expected 0 (fully collapsed) or \ + {min_fixed_scalar_count}..={max_fixed_scalar_count}; adjust num_instances or the \ + accumulator offset" + ), Self::Planning { stage, message } => { write!(f, "generator planning failed during {stage}: {message}") } diff --git a/proofs/solidity-verifier/src/builder/api.rs b/proofs/solidity-verifier/src/builder/api.rs index 52ae43f99..0f0869580 100644 --- a/proofs/solidity-verifier/src/builder/api.rs +++ b/proofs/solidity-verifier/src/builder/api.rs @@ -48,6 +48,35 @@ impl<'a> SolidityGenerator<'a> { )?; if let Some(acc_encoding) = config.accumulator { acc_encoding.validate_for_num_instances(config.num_instances)?; + // The fixed-base scalar tail is derived from the *instance* length, + // but the bases it multiplies come from this verifying key: `-G`, + // then `fixed_comm_mptr + i * G1_BYTES` per fixed base, then the + // permutation commitments. Two tail lengths break that mapping: + // + // * Too long: the generated fixed-base pointers run past the end + // of the fixed-commitment region, silently aliasing permutation + // commitments and then arbitrary VK payload words as G1 bases. + // * Shorter than `-G` plus the permutation commitments: the base + // count underflows in the artifact emitter. + // + // A tail inside the range is left alone -- it covers a prefix of + // the fixed commitments, which keeps every pointer in region. + let fixed_scalar_count = acc_encoding.fixed_scalar_count(config.num_instances)?; + let num_fixed_comms = vk.fixed_commitments().len(); + let num_permutation_comms = vk.permutation().commitments().len(); + let min_fixed_scalar_count = 1 + num_permutation_comms; + let max_fixed_scalar_count = min_fixed_scalar_count + num_fixed_comms; + if fixed_scalar_count != 0 + && !(min_fixed_scalar_count..=max_fixed_scalar_count).contains(&fixed_scalar_count) + { + return Err(GeneratorError::AccumulatorFixedBaseTailMismatch { + fixed_scalar_count, + min_fixed_scalar_count, + max_fixed_scalar_count, + num_fixed_comms, + num_permutation_comms, + }); + } } // Non-committed instance evaluations are reconstructed once from the // public-input polynomial at the current rotation. Reject rotated diff --git a/proofs/solidity-verifier/src/lowering/artifacts.rs b/proofs/solidity-verifier/src/lowering/artifacts.rs index 495795ba1..fa61fabf8 100644 --- a/proofs/solidity-verifier/src/lowering/artifacts.rs +++ b/proofs/solidity-verifier/src/lowering/artifacts.rs @@ -157,6 +157,19 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { let num_fixed_bases = fixed_scalar_count .checked_sub(1 + num_perm_bases) .expect("accumulator fixed scalar count is smaller than -G + permutations"); + // Each generated base below is `fixed_comm_mptr + i * 0x80`, + // so more bases than the VK has fixed commitments would + // point past the region into the permutation commitments + // and then the rest of the VK payload. SolidityGenerator:: + // try_new rejects this shape with a typed error; fail + // closed here too, since this is where the out-of-region + // pointers would actually be emitted. + assert!( + num_fixed_bases <= plan.vk.fixed_comms.len(), + "accumulator fixed-base count {num_fixed_bases} exceeds the VK \ + fixed-commitment region ({} commitments)", + plan.vk.fixed_comms.len() + ); std::iter::once(("-G".to_string(), g1_base_mptr_byte, true)) .chain((0..num_fixed_bases).map(|i| { @@ -175,6 +188,9 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { })) .collect::>() }; + // Holds by construction of `bases` above; kept as a cheap + // guard on the one-to-one correspondence with the scalars the + // generated verifier reads from calldata. assert_eq!( bases.len(), fixed_scalar_count, diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index 6caf62614..5e492d48e 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -1485,6 +1485,85 @@ fn compile_solidity_is_deterministic_for_same_source() { assert_eq!(bytecode_a, bytecode_b); } +/// The accumulator fixed-base scalar tail must match the verifying key. +/// +/// `fixed_scalar_count` is derived from `num_instances`, but the bases those +/// scalars multiply are generated as `fixed_comm_mptr + i * 0x80` from the VK. +/// A tail longer than the VK's fixed-commitment count used to render fine and +/// silently emit base pointers past that region, aliasing permutation +/// commitments -- and beyond them arbitrary VK payload words -- as accumulator +/// G1 bases. +#[test] +fn accumulator_fixed_base_tail_must_match_verifying_key() { + if !poseidon_inputs_available_for_evm() { + return; + } + + let srs_dir = srs_dir(); + env::set_var("SRS_DIR", &srs_dir); + let relation = PoseidonExample; + let srs = srs_for_test(&relation, Some(POSEIDON_K)); + let vk = setup_vk(&srs, &relation); + + let num_fixed_comms = vk.vk().fixed_commitments().len(); + let num_permutation_comms = vk.vk().permutation().commitments().len(); + let collapsed = AccumulatorEncoding::FULLY_COLLAPSED_PUBLIC_INPUT_WORDS; + // `-G` plus every permutation commitment, then up to one scalar per fixed + // commitment. + let min_tail = 1 + num_permutation_comms; + let max_tail = min_tail + num_fixed_comms; + + // A tail three scalars longer than the VK can supply bases for. Before the + // guard this rendered a verifier whose last three "fixed bases" were + // permutation commitments. + let err = SolidityGenerator::try_new( + &srs, + vk.vk(), + GeneratorConfig::new(collapsed + max_tail + 3, 1) + .with_accumulator(AccumulatorEncoding::new(0, 7, 56)), + ) + .expect_err("oversized accumulator fixed-base tail should be rejected"); + assert!( + matches!( + err, + crate::GeneratorError::AccumulatorFixedBaseTailMismatch { + fixed_scalar_count, + max_fixed_scalar_count, + .. + } if fixed_scalar_count == max_tail + 3 && max_fixed_scalar_count == max_tail + ), + "unexpected error for oversized tail: {err}" + ); + + // A tail too short to cover `-G` plus the permutation commitments would + // underflow the base count in the artifact emitter. + SolidityGenerator::try_new( + &srs, + vk.vk(), + GeneratorConfig::new(collapsed + min_tail - 1, 1) + .with_accumulator(AccumulatorEncoding::new(0, 7, 56)), + ) + .expect_err("undersized accumulator fixed-base tail should be rejected"); + + // Supported shapes still build: no tail at all, the full base set, and a + // prefix of the fixed commitments in between (every pointer stays inside + // the fixed-commitment region). + for num_instances in [ + collapsed, + collapsed + min_tail, + collapsed + max_tail, + collapsed + (min_tail + max_tail) / 2, + ] { + SolidityGenerator::try_new( + &srs, + vk.vk(), + GeneratorConfig::new(num_instances, 1) + .with_accumulator(AccumulatorEncoding::new(0, 7, 56)), + ) + .unwrap_or_else(|err| panic!("supported accumulator tail should build: {err}")); + } +} + /// Compile the accumulator render arm. /// /// No production fixture enables `with_accumulator`, so before this test the From bea362e787f4d65e90dfb873f36c535874e0e1eb Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:35:10 +0100 Subject: [PATCH 54/72] Fail loudly when the EVM gate is requested but cannot run Setting HALO2_SOLIDITY_RUN_EVM_TESTS=1 without the test SRS or the pinned solc used to print a "skipping ..." line and return, so the run reported every test green while compiling no Solidity and executing no proof. That is the worst possible outcome: the operator explicitly asked for the gate and got a silent no-op that is indistinguishable from a pass. This is not hypothetical. It is how the rendered fixture artifacts under target/*-fixture-dump/ drifted out of date across several commits with no test noticing, and how a round of verifier-template changes in this branch was initially reported as "validated by the full suite" when in fact 28 of the 34 end-to-end tests had returned early. Split the two cases that were conflated: * Not opting in stays a quiet skip. A fresh checkout without the 96 MB SRS still runs `cargo test` cleanly, which is why the skips exist. * Opting in and lacking a prerequisite is now a panic, with the fetch command for the SRS and the SOLC / allow-unpinned escape hatches for the compiler, so the message says how to fix it. Applied to both the in-crate gate helpers and the four fixture integration tests, which each carried their own copy of the skip logic. Verified all four paths: opted out passes quietly (189 tests, no EVM work); opted in with SRS and solc present passes for real; opted in with SRS_DIR pointing nowhere fails with fetch instructions; opted in with a bogus SOLC fails naming the pinned version. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/test.rs | 63 +++++++++++-------- .../tests/hybrid_mt_fixture.rs | 29 +++++---- .../tests/poseidon_fixture.rs | 30 +++++---- .../tests/rsa_signature_fixture.rs | 29 +++++---- .../tests/sha_preimage_fixture.rs | 29 +++++---- 5 files changed, 110 insertions(+), 70 deletions(-) diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index 5e492d48e..f3b022548 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -62,6 +62,9 @@ type PoseidonVerifierParams = const POSEIDON_K: u32 = 6; /// Environment flag that opts into expensive EVM/Solidity integration tests. const RUN_EVM_TESTS_ENV: &str = "HALO2_SOLIDITY_RUN_EVM_TESTS"; +/// Source for the test SRS, as documented by `zk_stdlib`'s own loader error. +const SRS_DOWNLOAD_URL: &str = + "https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19"; /// Minimal caller used to exercise the production verifier under STATICCALL. const STATICCALL_VERIFIER_HARNESS: &str = r#" // SPDX-License-Identifier: CC0-1.0 @@ -972,10 +975,9 @@ fn shape_fuzz_inputs_available_for_evm() -> bool { eprintln!("skipping supported-shape circuit fuzz: set {RUN_EVM_TESTS_ENV}=1 to run it"); return false; } - if !solc_available() { - eprintln!("skipping supported-shape circuit fuzz: solc not found"); - return false; - } + // Requested but unusable is a failure, not a skip. See + // `poseidon_inputs_available_for_evm`. + solc_available(); true } @@ -4131,13 +4133,13 @@ fn poseidon_inputs_available_for_evm() -> bool { eprintln!("skipping Poseidon Solidity property test: set {RUN_EVM_TESTS_ENV}=1 to run it"); return false; } - if !poseidon_srs_available() { - return false; - } - if !solc_available() { - eprintln!("skipping Poseidon Solidity property test: solc not found"); - return false; - } + // Past this point the gate was explicitly requested, so a missing + // prerequisite is a failure rather than a skip. Returning `false` here + // would report a green run that compiled no Solidity and executed no + // proof -- the failure mode that let the rendered fixture artifacts drift + // out of date across several commits without any test noticing. + poseidon_srs_available(); + solc_available(); true } @@ -4153,25 +4155,36 @@ fn env_flag_enabled(name: &str) -> bool { .unwrap_or(false) } -/// Return whether the Poseidon test SRS can be found on disk. -fn poseidon_srs_available() -> bool { +/// Require the Poseidon test SRS on disk. +/// +/// Only called once the EVM gate has been explicitly requested, so a missing +/// asset panics with fetch instructions instead of silently skipping. +fn poseidon_srs_available() { let srs_dir = PathBuf::from(srs_dir()); let exact_srs_path = srs_dir.join(format!("bls_filecoin_2p{POSEIDON_K}")); let fallback_srs_path = srs_dir.join("bls_filecoin_2p19"); - if !exact_srs_path.exists() && !fallback_srs_path.exists() { - eprintln!( - "skipping Poseidon Solidity property test: SRS not found at {} or {}", - exact_srs_path.display(), - fallback_srs_path.display() - ); - return false; - } - true + assert!( + exact_srs_path.exists() || fallback_srs_path.exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {} or {}.\n\ + Fetch it with:\n curl -L -o {} {SRS_DOWNLOAD_URL}\n\ + or point SRS_DIR at an existing copy.", + exact_srs_path.display(), + fallback_srs_path.display(), + fallback_srs_path.display(), + ); } -/// Return whether the configured pinned solc is available. -fn solc_available() -> bool { - pinned_solc_available() +/// Require the pinned solc. +/// +/// Same contract as [`poseidon_srs_available`]: loud once the gate is on. +fn solc_available() { + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires solc {}, which was not found or did not match.\n\ + Install it, point SOLC at the binary, or set {}=1 to accept another version.", + crate::PINNED_SOLC_VERSION, + crate::ALLOW_UNPINNED_SOLC_ENV, + ); } /// Resolve the SRS directory used by fixture setup. diff --git a/proofs/solidity-verifier/tests/hybrid_mt_fixture.rs b/proofs/solidity-verifier/tests/hybrid_mt_fixture.rs index 4a94bbdd7..baaa8da6d 100644 --- a/proofs/solidity-verifier/tests/hybrid_mt_fixture.rs +++ b/proofs/solidity-verifier/tests/hybrid_mt_fixture.rs @@ -193,13 +193,17 @@ fn hybrid_mt_renders_compiles_and_verifies() { let srs_dir = srs_dir(); let srs_path = format!("{srs_dir}/bls_filecoin_2p{K}"); let fallback_srs_path = format!("{srs_dir}/bls_filecoin_2p19"); - if !Path::new(&srs_path).exists() && !Path::new(&fallback_srs_path).exists() { - eprintln!( - "skipping hybrid MT Solidity smoke: SRS not found at {srs_path} or \ - {fallback_srs_path}. Set SRS_DIR or fetch the asset under midfall/zk_stdlib." - ); - return; - } + // The gate was explicitly requested, so a missing asset fails rather than + // silently reporting a pass that rendered and compiled nothing. + assert!( + Path::new(&srs_path).exists() || Path::new(&fallback_srs_path).exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {srs_path} or \ + {fallback_srs_path}. +Fetch it with: + curl -L -o {fallback_srs_path} \ + https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19 +or point SRS_DIR at an existing copy." + ); env::set_var("SRS_DIR", &srs_dir); let relation = HybridMtCircuit; @@ -263,10 +267,13 @@ fn hybrid_mt_renders_compiles_and_verifies() { verifier_solidity.len() ); - if !pinned_solc_available() { - eprintln!("skipping hybrid MT EVM smoke: pinned solc not available"); - return; - } + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires the pinned solc, which was not found or did not match. +\ + Install it, point SOLC at the binary, or set \ + HALO2_SOLIDITY_ALLOW_UNPINNED_SOLC=1 to accept another version." + ); let vk_creation_code = compile_solidity(&vk_solidity); let verifier_creation_code = compile_solidity(&verifier_solidity); diff --git a/proofs/solidity-verifier/tests/poseidon_fixture.rs b/proofs/solidity-verifier/tests/poseidon_fixture.rs index 58bfe73a3..36092af7f 100644 --- a/proofs/solidity-verifier/tests/poseidon_fixture.rs +++ b/proofs/solidity-verifier/tests/poseidon_fixture.rs @@ -125,13 +125,17 @@ fn poseidon_renders_compiles_and_verifies() { let srs_dir = srs_dir(); let srs_path = format!("{srs_dir}/bls_filecoin_2p{K}"); let fallback_srs_path = format!("{srs_dir}/bls_filecoin_2p19"); - if !Path::new(&srs_path).exists() && !Path::new(&fallback_srs_path).exists() { - eprintln!( - "skipping poseidon end-to-end smoke: SRS not found at {srs_path} or {fallback_srs_path}. \ - Set SRS_DIR or fetch the asset under midfall/zk_stdlib." - ); - return; - } + // The gate was explicitly requested, so a missing asset fails rather than + // silently reporting a pass that rendered and compiled nothing. + assert!( + Path::new(&srs_path).exists() || Path::new(&fallback_srs_path).exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {srs_path} or \ + {fallback_srs_path}. +Fetch it with: + curl -L -o {fallback_srs_path} \ + https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19 +or point SRS_DIR at an existing copy." + ); env::set_var("SRS_DIR", &srs_dir); let relation = PoseidonExample; @@ -200,11 +204,13 @@ fn poseidon_renders_compiles_and_verifies() { verifier_solidity.len() ); - // Skip the EVM portion if the pinned solc is not available. - if !pinned_solc_available() { - eprintln!("skipping poseidon end-to-end smoke: pinned solc not available"); - return; - } + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires the pinned solc, which was not found or did not match. +\ + Install it, point SOLC at the binary, or set \ + HALO2_SOLIDITY_ALLOW_UNPINNED_SOLC=1 to accept another version." + ); let vk_creation_code = compile_solidity(&vk_solidity); let verifier_creation_code = compile_solidity(&verifier_solidity); diff --git a/proofs/solidity-verifier/tests/rsa_signature_fixture.rs b/proofs/solidity-verifier/tests/rsa_signature_fixture.rs index 132d1e4c0..f37cce3bc 100644 --- a/proofs/solidity-verifier/tests/rsa_signature_fixture.rs +++ b/proofs/solidity-verifier/tests/rsa_signature_fixture.rs @@ -103,13 +103,17 @@ fn rsa_signature_renders_compiles_and_verifies() { let srs_dir = srs_dir(); let srs_path = format!("{srs_dir}/bls_filecoin_2p{K}"); let fallback_srs_path = format!("{srs_dir}/bls_filecoin_2p19"); - if !Path::new(&srs_path).exists() && !Path::new(&fallback_srs_path).exists() { - eprintln!( - "skipping RSA signature Solidity smoke: SRS not found at {srs_path} or \ - {fallback_srs_path}. Set SRS_DIR or fetch the asset under midfall/zk_stdlib." - ); - return; - } + // The gate was explicitly requested, so a missing asset fails rather than + // silently reporting a pass that rendered and compiled nothing. + assert!( + Path::new(&srs_path).exists() || Path::new(&fallback_srs_path).exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {srs_path} or \ + {fallback_srs_path}. +Fetch it with: + curl -L -o {fallback_srs_path} \ + https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19 +or point SRS_DIR at an existing copy." + ); env::set_var("SRS_DIR", &srs_dir); let relation = RsaSignatureCircuit; @@ -172,10 +176,13 @@ fn rsa_signature_renders_compiles_and_verifies() { verifier_solidity.len() ); - if !pinned_solc_available() { - eprintln!("skipping RSA signature EVM smoke: pinned solc not available"); - return; - } + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires the pinned solc, which was not found or did not match. +\ + Install it, point SOLC at the binary, or set \ + HALO2_SOLIDITY_ALLOW_UNPINNED_SOLC=1 to accept another version." + ); let vk_creation_code = compile_solidity(&vk_solidity); let verifier_creation_code = compile_solidity(&verifier_solidity); diff --git a/proofs/solidity-verifier/tests/sha_preimage_fixture.rs b/proofs/solidity-verifier/tests/sha_preimage_fixture.rs index 573038809..3ef8529f2 100644 --- a/proofs/solidity-verifier/tests/sha_preimage_fixture.rs +++ b/proofs/solidity-verifier/tests/sha_preimage_fixture.rs @@ -82,13 +82,17 @@ fn sha_preimage_renders_compiles_and_verifies() { let srs_dir = srs_dir(); let srs_path = format!("{srs_dir}/bls_filecoin_2p{K}"); let fallback_srs_path = format!("{srs_dir}/bls_filecoin_2p19"); - if !Path::new(&srs_path).exists() && !Path::new(&fallback_srs_path).exists() { - eprintln!( - "skipping SHA preimage Solidity smoke: SRS not found at {srs_path} or \ - {fallback_srs_path}. Set SRS_DIR or fetch the asset under midfall/zk_stdlib." - ); - return; - } + // The gate was explicitly requested, so a missing asset fails rather than + // silently reporting a pass that rendered and compiled nothing. + assert!( + Path::new(&srs_path).exists() || Path::new(&fallback_srs_path).exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {srs_path} or \ + {fallback_srs_path}. +Fetch it with: + curl -L -o {fallback_srs_path} \ + https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19 +or point SRS_DIR at an existing copy." + ); env::set_var("SRS_DIR", &srs_dir); let relation = ShaPreimageCircuit; @@ -152,10 +156,13 @@ fn sha_preimage_renders_compiles_and_verifies() { verifier_solidity.len() ); - if !pinned_solc_available() { - eprintln!("skipping SHA preimage EVM smoke: pinned solc not available"); - return; - } + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires the pinned solc, which was not found or did not match. +\ + Install it, point SOLC at the binary, or set \ + HALO2_SOLIDITY_ALLOW_UNPINNED_SOLC=1 to accept another version." + ); let vk_creation_code = compile_solidity(&vk_solidity); let verifier_creation_code = compile_solidity(&verifier_solidity); From 9e714b7c61736d4bbce5879d53c028c54cea20a9 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:49:58 +0100 Subject: [PATCH 55/72] Require the pairing precompile to return exactly one `ec_pairing` folded the precompile result into its success flag with `ret := and(ret, mload(scratch))`. Because `ret` is already reduced to 0 or 1 by the preceding `returndatasize` check, the bitwise `and` against the full 32-byte result word only tests that word's low bit -- so any odd return value was accepted as a successful pairing, not just the canonical 1. A conformant EIP-2537 pairing precompile returns exactly 0 or 1, so this is hardening rather than a live break. It is worth closing anyway: the threat model explicitly warns that deployers on L2s and alt-EVMs must run their own conformance tests, and the codebase was already inconsistent with itself -- the constructor smoke test in PrecompileSmoke.sol uses the strict `eq(mload(scratch), 1)` form. Compare against 1 in the helper too, and update the template assertion in `production_verifier_documents_revert_or_true_policy`, which pinned the old expression verbatim. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/lowering/tests.rs | 2 +- .../templates/partials/verifier/AssemblyHelpers.yul | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index 39551dfb1..942280109 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -1793,7 +1793,7 @@ fn production_verifier_documents_revert_or_true_policy() { && verifier_template .contains("ret := success\n if iszero(ret) { leave }") && verifier_template.contains( - "ret := and(ret, mload(scratch))\n if iszero(ret) { revert(0, 0) }\n ret := 1", + "ret := and(ret, eq(mload(scratch), 1))\n if iszero(ret) { revert(0, 0) }\n ret := 1", ), "final pairing helper must revert on pairing failure and normalize success to one" ); diff --git a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul index 43c77cfe7..589fe1273 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul @@ -250,7 +250,11 @@ mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), {{ template_constants.eip2537.pairing_address|hex() }}, scratch, {{ template_constants.pairing_two_pair_bytes|hex() }}, scratch, {{ template_constants.word_bytes|hex() }}) ret := and(ret, eq(returndatasize(), {{ template_constants.word_bytes|hex() }})) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } From 72bc1b2d02df1f43874b72721a6f263ca921f37e Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:50:08 +0100 Subject: [PATCH 56/72] Fix an always-false guard in the accumulator limb decoder `load_acc_coord_shifted` stripped the identity flag from the first packed word under the guard: if and(iszero(div(i, limbs_per_word)), first_adjust) Yul's `and` is bitwise, not logical. `iszero(...)` yields 0 or 1, while `first_adjust` is either 0 or a radix base -- 2^56 for the current BLS12-381 self-emulation parameters. `1 & 2^56` is 0, so the guard was false on every call and `packed := sub(packed, first_adjust)` never executed. The parameter was entirely inert. The visible consequence is that the identity probe in `load_acc_coord` never removed the flag before testing for p-1, so `is_id` was always 0 and the `if is_id` branch in `load_acc_point` was dead. Infinity was still accepted, via the separate canonical constant comparison in `is_acc_encoded_identity`, and a flag-carrying x that did not match that quadruple decoded to (p-1) + 2^56 and failed the base-field range check. So the decoder was fail-closed throughout and no forgery was reachable. What was broken is the canonicality barrier the comment above `is_acc_encoded_identity` claims to enforce -- a barrier that exists so the on-chain verifier need not rely on circuit-side reasoning it cannot itself check. Gate on the word index alone. Subtracting `first_adjust` is already a no-op when it is zero, so the second conjunct was never needed. Co-Authored-By: Claude Opus 4.8 --- .../templates/partials/verifier/AccumulatorHelpers.yul | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul index b0f088d47..448df6991 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul @@ -25,7 +25,13 @@ // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div From 58824e8b30a17d6eec8310eb7e957655ac1607d5 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:55:31 +0100 Subject: [PATCH 57/72] Report unsupported constraint systems from try_new instead of panicking `SolidityGenerator::try_new` documents itself as "returning a typed error when the supplied constraint system is outside the currently supported Midfall verifier shape", and exists precisely so callers can avoid `new`'s panic. It did not hold up its end. After its own checks, try_new called `ConstraintSystemMeta::new`, which reaches `ProtocolPlan::from_constraint_system` and ends in: plan.validate().unwrap_or_else(|err| panic!("invalid protocol plan: {err}")); `ProtocolPlan::validate` has roughly twenty `return Err(...)` arms, every one of which became a panic on the supposedly panic-free path. The most reachable is the absorbed-but-unopened advice check: `advice_indices` has an entry per declared advice column while `advice_queries` holds only the queried ones, so a circuit that calls `cs.advice_column()` without ever querying it -- an ordinary authoring mistake -- panicked inside try_new. The exposure is the opposite way round from what one might assume: `SolidityGenerator::new` has no non-test callers, so its deliberate panic is not on a production path, while try_new is. Split each layer into a fallible variant and a panicking wrapper: `ProtocolPlan::try_from_constraint_system`, `ConstraintSystemMeta::try_new`. try_new now maps the failure onto the existing `GeneratorError::Planning` variant with stage "constraint system"; `new` and the protocol-level tests keep the old panicking entry points, so no caller changes. Adds `generator_reports_unopened_advice_column_as_typed_error`, which builds a VK from a circuit declaring an advice column it never queries and asserts a typed error rather than a panic. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/builder/api.rs | 12 +++- .../src/lowering/abi/proof.rs | 6 +- .../src/lowering/encoding/mod.rs | 18 ++++- proofs/solidity-verifier/src/lowering/plan.rs | 8 +-- .../src/lowering/protocol/mod.rs | 22 ++++++- .../src/lowering/quotient_numerator/vm/mod.rs | 15 ++++- .../solidity-verifier/src/lowering/tests.rs | 66 ++++++++++++++++++- proofs/solidity-verifier/src/test.rs | 5 +- 8 files changed, 129 insertions(+), 23 deletions(-) diff --git a/proofs/solidity-verifier/src/builder/api.rs b/proofs/solidity-verifier/src/builder/api.rs index 0f0869580..6eee59cb2 100644 --- a/proofs/solidity-verifier/src/builder/api.rs +++ b/proofs/solidity-verifier/src/builder/api.rs @@ -93,7 +93,17 @@ impl<'a> SolidityGenerator<'a> { }); } - let meta = ConstraintSystemMeta::new(vk.cs(), config.num_committed_instances); + // Fallible: `ProtocolPlan::validate` rejects a range of unsupported + // constraint-system shapes -- an advice column that is absorbed but + // never opened by a PCS query being the most reachable authoring + // mistake. Panicking here would break this constructor's contract of + // reporting such shapes as a typed error. + let meta = ConstraintSystemMeta::try_new(vk.cs(), config.num_committed_instances).map_err( + |message| GeneratorError::Planning { + stage: "constraint system", + message, + }, + )?; Ok(Self { params, diff --git a/proofs/solidity-verifier/src/lowering/abi/proof.rs b/proofs/solidity-verifier/src/lowering/abi/proof.rs index e998770ca..4a8d061d4 100644 --- a/proofs/solidity-verifier/src/lowering/abi/proof.rs +++ b/proofs/solidity-verifier/src/lowering/abi/proof.rs @@ -514,8 +514,7 @@ mod tests { let protocol = protocol_shape(vec![1, 9], vec![], 0, 0, 1); let proof = ProofCalldataLayout::from_protocol(&protocol, 0, 12, 2); let num_instances = 86; - let transcript = - TranscriptBufferLayout::from_proof_layout(&proof, num_instances, &[0, 1]); + let transcript = TranscriptBufferLayout::from_proof_layout(&proof, num_instances, &[0, 1]); let word = layout::transcript::WORD_ABSORB_BYTES; let g1 = layout::transcript::G1_ABSORB_BYTES; @@ -524,8 +523,7 @@ mod tests { // Bytes actually absorbed before the first squeeze: vk_digest, // committed_pi, num_instances length word, instance words, then BOTH // phases' advices (phase 0 owns no challenge). - let true_initial_run = - word + g1 + word + num_instances * word + (1 + 9) * g1; + let true_initial_run = word + g1 + word + num_instances * word + (1 + 9) * g1; assert!( transcript.words * WORD_BYTES >= true_initial_run, "reserved {} bytes < true pre-squeeze run {}", diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index ec7e2f086..a2ec9c7f4 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -138,10 +138,22 @@ impl ConstraintSystemMeta { /// verifier *reads* from the proof transcript (vs. computes locally /// via Lagrange interpolation). For the poseidon example this is 0; /// for IVC-style fixtures with committed inputs it would be > 0. + /// + /// Panics if the constraint system is outside the supported verifier shape. + /// Use [`ConstraintSystemMeta::try_new`] on fallible paths. pub(crate) fn new(cs: &ConstraintSystem, nb_committed_instances: usize) -> Self { - let protocol = ProtocolPlan::from_constraint_system(cs, nb_committed_instances); + Self::try_new(cs, nb_committed_instances) + .unwrap_or_else(|err| panic!("invalid protocol plan: {err}")) + } - Self { + /// Fallible counterpart of [`ConstraintSystemMeta::new`]. + pub(crate) fn try_new( + cs: &ConstraintSystem, + nb_committed_instances: usize, + ) -> Result { + let protocol = ProtocolPlan::try_from_constraint_system(cs, nb_committed_instances)?; + + Ok(Self { protocol: protocol.clone(), num_fixeds: protocol.num_fixeds, permutation_columns: protocol.permutation_columns.clone(), @@ -164,7 +176,7 @@ impl ConstraintSystemMeta { advice_indices: protocol.advice_indices.clone(), challenge_indices: protocol.challenge_indices.clone(), rotation_last: protocol.rotation_last, - } + }) } /// Check legacy scalar fields against the typed protocol plan. diff --git a/proofs/solidity-verifier/src/lowering/plan.rs b/proofs/solidity-verifier/src/lowering/plan.rs index b98e7290b..f8aaea1ad 100644 --- a/proofs/solidity-verifier/src/lowering/plan.rs +++ b/proofs/solidity-verifier/src/lowering/plan.rs @@ -297,11 +297,9 @@ impl LoweringPlan { } } if let Some(program_offset) = self.vk.quotient_program_offset_words { - let build_words = layout::vk_payload::PackedProgramCodec::encode_words( - &self.quotient.build.bytes, - ); - let embedded = - &self.vk.constants[program_offset..program_offset + build_words.len()]; + let build_words = + layout::vk_payload::PackedProgramCodec::encode_words(&self.quotient.build.bytes); + let embedded = &self.vk.constants[program_offset..program_offset + build_words.len()]; if embedded.iter().map(|(_, value)| value).ne(build_words.iter()) { return Err( "quotient program bytecode embedded in the VK payload does not match the \ diff --git a/proofs/solidity-verifier/src/lowering/protocol/mod.rs b/proofs/solidity-verifier/src/lowering/protocol/mod.rs index a035abaf5..41f004e59 100644 --- a/proofs/solidity-verifier/src/lowering/protocol/mod.rs +++ b/proofs/solidity-verifier/src/lowering/protocol/mod.rs @@ -324,10 +324,28 @@ impl ProtocolPlan { /// passed to `partially_evaluate_identities` and KZG `multi_prepare`. /// The plan preserves that order so the Solidity transcript and proof /// cursors stay byte-compatible with the Rust verifier. + /// + /// Panics if the resulting plan fails [`ProtocolPlan::validate`]. Callers on + /// a fallible path — notably [`SolidityGenerator::try_new`], which promises + /// a typed error for unsupported constraint systems — must use + /// [`ProtocolPlan::try_from_constraint_system`] instead. pub(crate) fn from_constraint_system( cs: &ConstraintSystem, nb_committed_instances: usize, ) -> Self { + Self::try_from_constraint_system(cs, nb_committed_instances) + .unwrap_or_else(|err| panic!("invalid protocol plan: {err}")) + } + + /// Fallible counterpart of [`ProtocolPlan::from_constraint_system`]. + /// + /// Returns the validation failure rather than panicking, so constraint + /// systems outside the supported verifier shape can be surfaced as a typed + /// error at the public API boundary. + pub(crate) fn try_from_constraint_system( + cs: &ConstraintSystem, + nb_committed_instances: usize, + ) -> Result { let cs_degree = cs.degree(); let num_fixeds = cs.num_fixed_columns(); let permutation_columns = cs.permutation().get_columns(); @@ -610,8 +628,8 @@ impl ProtocolPlan { common_polys, quotient, }; - plan.validate().unwrap_or_else(|err| panic!("invalid protocol plan: {err}")); - plan + plan.validate()?; + Ok(plan) } /// Number of scalar evaluations in the proof's main eval block. diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index 4c3600660..3c26bee4b 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -2083,14 +2083,20 @@ pub(crate) fn validate_quotient_const_slots(bytes: &[u8], const_len: usize) -> R check(bytes[idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; } Q_OP_ADD_MUL_MEM_MEM_CONST_U8 => { - check(bytes[idx + 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + check( + bytes[idx + 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, + idx, + )?; } Q_OP_RUN_ADD_MUL_CONST_U8_MEM_U16 => { let count = read_u16(bytes, idx + 1) as usize; let base = idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES; let stride = QUOTIENT_VM_BYTE_U16_BYTES + 1; for k in 0..count { - check(bytes[base + k * stride + QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + check( + bytes[base + k * stride + QUOTIENT_VM_BYTE_U16_BYTES] as usize, + idx, + )?; } } Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8 => { @@ -2098,7 +2104,10 @@ pub(crate) fn validate_quotient_const_slots(bytes: &[u8], const_len: usize) -> R let base = idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES; let stride = 2 * QUOTIENT_VM_BYTE_U16_BYTES + 1; for k in 0..count { - check(bytes[base + k * stride + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + check( + bytes[base + k * stride + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, + idx, + )?; } } Q_OP_LIN7 => { diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index 942280109..b720000eb 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -345,6 +345,67 @@ fn generator_rejects_rotated_non_committed_instance_queries() { )); } +/// Declares two advice columns but queries only one, so the second is absorbed +/// into the transcript without ever being opened by a PCS query. +struct UnopenedAdviceColumnCircuit; + +impl Circuit for UnopenedAdviceColumnCircuit { + type Config = (); + type FloorPlanner = SimpleFloorPlanner; + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advice = meta.advice_column(); + // Declared and committed, but never queried: this is the shape + // `ProtocolPlan::validate` rejects. + let _unopened = meta.advice_column(); + let committed_instance = meta.instance_column(); + let public_instance = meta.instance_column(); + + meta.create_gate("unopened advice", |meta| { + let advice = meta.query_advice(advice, Rotation::cur()); + let committed = meta.query_instance(committed_instance, Rotation::cur()); + let public = meta.query_instance(public_instance, Rotation::cur()); + Constraints::without_selector(vec![("unopened advice", advice + committed + public)]) + }); + } + + fn synthesize( + &self, + _config: Self::Config, + _layouter: impl Layouter, + ) -> Result<(), PlonkError> { + Ok(()) + } +} + +/// `try_new` documents a typed error for unsupported constraint systems, so an +/// unopened advice column must not reach the `panic!` inside +/// `ProtocolPlan::from_constraint_system`. +#[test] +fn generator_reports_unopened_advice_column_as_typed_error() { + let mut rng = ChaCha8Rng::seed_from_u64(8); + let params = ParamsKZG::::unsafe_setup(4, &mut rng); + let circuit = UnopenedAdviceColumnCircuit; + let vk = keygen_vk_with_k::, _>(¶ms, &circuit, 4) + .expect("test circuit VK should build"); + + let err = SolidityGenerator::try_new(¶ms, &vk, GeneratorConfig::new(1, 1)) + .expect_err("unopened advice column is outside the supported verifier shape"); + let GeneratorError::Planning { stage, message } = err else { + panic!("expected a planning error, got {err:?}"); + }; + assert_eq!(stage, "constraint system"); + assert!( + message.contains("absorbed but never opened"), + "error should name the unopened advice column, got {message}" + ); +} + #[test] fn scalar_le_to_be_word_reverses_exactly_one_word() { let mut le = [0u8; 32]; @@ -2486,7 +2547,10 @@ fn quotient_vm_limb_decomposition_survives_const_table_overflow() { expr, // Coefficient `k + 2` keeps every term scaled (coeff 1 would drop the // constant) and distinct, so the table grows one slot per term. - quotient_scale_expr(Fq::from(k as u64 + 2), QuotientExpr::Mem(QuotientMem::Literal(ptr))), + quotient_scale_expr( + Fq::from(k as u64 + 2), + QuotientExpr::Mem(QuotientMem::Literal(ptr)), + ), ); } diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index f3b022548..a47e14ab6 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -1664,10 +1664,7 @@ fn accumulator_verifier_variants_compile_with_pinned_solc() { (name, verifier.as_str()), ( "accumulator VK", - artifacts - .verifying_key - .as_deref() - .expect("separate render includes VK"), + artifacts.verifying_key.as_deref().expect("separate render includes VK"), ), ] { let bytecode = std::panic::catch_unwind(AssertUnwindSafe(|| compile_solidity(source))) From 75f9f992317a86e54d3fcbcad6256ab465508e72 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 01:59:21 +0100 Subject: [PATCH 58/72] Bound the public instance count at generator construction The Lagrange block writes its batch-inversion input run in place from X_N_MPTR, and the run is num_instances + abs(rotation_last) + 1 words long. Nothing in `GeneratorConfig` or `SolidityGenerator::try_new` placed any upper bound on `config.num_instances`, so an oversized count was carried all the way into layout planning. The overrun itself was already caught -- `VerifierMemoryLayout::validate` has a dedicated cap check for this run against the first memory that is live when Lagrange executes (the q_eval calldata cursor, the G1 identity slot, and the decoded evaluations). So this was never a memory-safety hole. What was wrong is where the failure surfaced: a caller passing too many public inputs got an error phrased in theta-word offsets from deep inside layout planning, rather than a typed rejection from the constructor whose job is to validate the requested shape. The asymmetry made the gap easy to miss: `transcript_buffer_layout_for_meta` explicitly sizes the transcript buffer for arbitrary `num_instances`, so large public-input counts look supported, while the Lagrange window silently is not. Reject the count in `try_new` with a new `GeneratorError::TooManyInstances` carrying the requested count, the maximum, and the rotation contribution. The check must sit after `meta` is built, since `rotation_last` is only known then. To keep the early bound and the layout check from drifting apart, both now derive from one source: `batch_invert_input_words` for the run length and a new `theta_window::LAGRANGE_RUN_CAP_WORDS` for the cap. The existing layout test asserts the constant equals the window-derived value. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/api.rs | 24 +++++++++++++++ proofs/solidity-verifier/src/builder/api.rs | 18 ++++++++++++ .../src/lowering/layout/memory.rs | 6 +++- .../src/lowering/layout/mod.rs | 19 ++++++++++++ .../solidity-verifier/src/lowering/tests.rs | 29 +++++++++++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/proofs/solidity-verifier/src/api.rs b/proofs/solidity-verifier/src/api.rs index 0cf0be6e1..e2213b9eb 100644 --- a/proofs/solidity-verifier/src/api.rs +++ b/proofs/solidity-verifier/src/api.rs @@ -477,6 +477,19 @@ pub enum GeneratorError { /// Permutation commitments in the verifying key. num_permutation_comms: usize, }, + /// The public instance count pushes the Lagrange batch-inversion input run + /// past the verifier memory that is already live when Lagrange executes. + /// + /// The run is written in place from `X_N_MPTR` and is + /// `num_instances + abs(rotation_last) + 1` words long. + TooManyInstances { + /// Requested public instance count. + num_instances: usize, + /// Largest public instance count this constraint system can support. + max_num_instances: usize, + /// Negative-row denominators contributed by `rotation_last`. + rotation_last_words: usize, + }, /// Internal render/layout planning failed before Solidity was emitted. Planning { /// Planning stage. @@ -543,6 +556,17 @@ impl fmt::Display for GeneratorError { {min_fixed_scalar_count}..={max_fixed_scalar_count}; adjust num_instances or the \ accumulator offset" ), + Self::TooManyInstances { + num_instances, + max_num_instances, + rotation_last_words, + } => write!( + f, + "public instance count {num_instances} is too large: the Lagrange \ + batch-inversion run writes num_instances + {rotation_last_words} + 1 words from \ + X_N_MPTR and would overrun verifier memory that is live at that point; \ + the maximum for this constraint system is {max_num_instances}" + ), Self::Planning { stage, message } => { write!(f, "generator planning failed during {stage}: {message}") } diff --git a/proofs/solidity-verifier/src/builder/api.rs b/proofs/solidity-verifier/src/builder/api.rs index 6eee59cb2..7afc789f9 100644 --- a/proofs/solidity-verifier/src/builder/api.rs +++ b/proofs/solidity-verifier/src/builder/api.rs @@ -6,6 +6,7 @@ //! stable caller-facing knobs before lower-level generation starts. use super::*; +use crate::lowering::layout::{memory::batch_invert_input_words, theta_window}; impl<'a> SolidityGenerator<'a> { /// Number of committed instance columns supported by the generated ABI. @@ -105,6 +106,23 @@ impl<'a> SolidityGenerator<'a> { }, )?; + // Bound the public instance count here rather than leaving it to + // `VerifierMemoryLayout::validate`. The Lagrange block writes its + // batch-inversion input run in place from `X_N_MPTR`, and that run + // grows with `num_instances`; the layout does catch an overrun, but + // only much later and phrased in theta-word offsets. `rotation_last` + // is only known once `meta` is built, hence the position of this check. + let run_words = batch_invert_input_words(&meta, config.num_instances); + if run_words > theta_window::LAGRANGE_RUN_CAP_WORDS { + let rotation_last_words = meta.rotation_last.unsigned_abs() as usize; + return Err(GeneratorError::TooManyInstances { + num_instances: config.num_instances, + max_num_instances: theta_window::LAGRANGE_RUN_CAP_WORDS + .saturating_sub(rotation_last_words + 1), + rotation_last_words, + }); + } + Ok(Self { params, vk, diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index fe3efd00c..07cdcede6 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -1150,7 +1150,7 @@ pub(crate) fn commitment_g1_count(meta: &ConstraintSystemMeta) -> usize { /// there are no public instances; /// - `abs(rotation_last)` negative-row denominators; /// - x_n - 1. -fn batch_invert_input_words(meta: &ConstraintSystemMeta, num_instances: usize) -> usize { +pub(crate) fn batch_invert_input_words(meta: &ConstraintSystemMeta, num_instances: usize) -> usize { if num_instances == 0 { meta.rotation_last.unsigned_abs() as usize + 2 } else { @@ -1707,6 +1707,10 @@ mod tests { .min(windows.g1_identity_word) .min(windows.reversed_evals_word) - ThetaSlot::XN.word(); + // `SolidityGenerator::try_new` rejects oversized instance counts up + // front against the constant form of this cap. The two must agree, or + // the early bound and the layout check would disagree about what fits. + assert_eq!(cap_words, theta_window::LAGRANGE_RUN_CAP_WORDS); // Largest instance count whose denominator run plus `x_n - 1` still // fits below the q_eval calldata cursor: num + |rotation_last| + 1. diff --git a/proofs/solidity-verifier/src/lowering/layout/mod.rs b/proofs/solidity-verifier/src/lowering/layout/mod.rs index 59bc19f1e..a84bc8236 100644 --- a/proofs/solidity-verifier/src/lowering/layout/mod.rs +++ b/proofs/solidity-verifier/src/lowering/layout/mod.rs @@ -602,6 +602,25 @@ pub(crate) mod theta_window { /// Word offset where reversed proof evaluations begin. pub(crate) const REVERSED_EVALS_WORD: usize = G1_IDENTITY_WORD + G1_WORDS + G1_IDENTITY_PADDING_WORDS; + + /// Largest Lagrange batch-inversion input run that fits below live memory. + /// + /// The run starts in place at `X_N_MPTR` and deliberately spills past the + /// theta band into the rot_points / x1_powers / q_eval_set windows, which + /// are written only after the Lagrange phase. The first words that are + /// already live when Lagrange runs are the q_eval calldata cursor, the G1 + /// identity slot (which must stay virgin zero memory), and the decoded + /// proof evaluations, so the run must end at or before the lowest of those. + pub(crate) const LAGRANGE_RUN_CAP_WORDS: usize = { + let mut cap = Q_EVAL_CPTR_WORD; + if G1_IDENTITY_WORD < cap { + cap = G1_IDENTITY_WORD; + } + if REVERSED_EVALS_WORD < cap { + cap = REVERSED_EVALS_WORD; + } + cap - ThetaSlot::XN.word() + }; } pub(crate) mod trace { diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index b720000eb..8a9942630 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -448,6 +448,35 @@ fn external_quotient_output_uses_planned_return_buffer() { ); } +/// The Lagrange denominator run grows with `num_instances`, so an oversized +/// instance count must be rejected at construction with a typed error rather +/// than surfacing later as a memory-layout failure. +#[test] +fn generator_rejects_instance_counts_that_overrun_the_lagrange_run() { + let (params, vk) = lowering_plan_test_vk(); + let meta = ConstraintSystemMeta::new(vk.cs(), 1); + let rotation_last_words = meta.rotation_last.unsigned_abs() as usize; + let max_num_instances = + crate::lowering::layout::theta_window::LAGRANGE_RUN_CAP_WORDS - rotation_last_words - 1; + + SolidityGenerator::try_new(¶ms, &vk, GeneratorConfig::new(max_num_instances, 1)) + .expect("the largest fitting instance count must still be accepted"); + + let err = + SolidityGenerator::try_new(¶ms, &vk, GeneratorConfig::new(max_num_instances + 1, 1)) + .expect_err("one instance past the cap must be rejected"); + assert!( + matches!( + err, + GeneratorError::TooManyInstances { + max_num_instances: reported, + .. + } if reported == max_num_instances + ), + "unexpected error: {err:?}" + ); +} + #[test] fn lowering_plan_reuses_stable_layout_facts() { let (params, vk) = lowering_plan_test_vk(); From 06e46be52e1879ebea213a3bce05a1c904ccde7d Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 02:02:12 +0100 Subject: [PATCH 59/72] Pin the phase ordering the Lagrange denominator cap depends on The Lagrange batch-inversion input run is written in place from X_N_MPTR and is longer than the 26 words between X_N_MPTR and ROT_POINTS_MPTR -- in the shipped moonlight-wrap render it spans [0x6cc0, 0x7080), four words past ROT_POINTS_MPTR. That is intentional, not an overrun: the rot_points, x1_powers and q_eval_set windows are written only in later phases, so `VerifierMemoryLayout::validate` caps the run against the first memory that is genuinely live at Lagrange time -- the q_eval calldata cursor, the G1 identity slot, and the decoded evaluations. The run is deliberately not registered as a `MemoryRegion`: doing so would make the arena's overlap loop report overlaps that are correct by phase ordering. The cost is that nothing mechanically enforces the ordering the cap relies on. It rested entirely on a source comment, so moving a rot_points access earlier would silently either clobber the denominators mid-flight or read their leftovers -- with no codegen error and no failing test. Add `rot_points_window_is_written_after_the_lagrange_denominator_run`, which renders verifier source and asserts the first ROT_POINTS_MPTR access -- read or write -- occurs after the Lagrange batch_invert call. State the invariant explicitly at the cap check and point it at the test. No behaviour change; this closes the enforcement gap, not a live defect. Co-Authored-By: Claude Opus 4.8 --- .../src/lowering/layout/memory.rs | 8 ++++ .../solidity-verifier/src/lowering/tests.rs | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 07cdcede6..b91c70f1d 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -1110,6 +1110,14 @@ impl VerifierMemoryLayout { // that are already live at Lagrange time are the q_eval calldata // cursor (written by the proof parser), the G1 identity slot (must // stay virgin zero memory), and the decoded proof evaluations. + // + // INVARIANT: this cap is sound only while rot_points, x1_powers and + // q_eval_set are first touched *after* the Lagrange phase. The arena's + // overlap loop cannot enforce that, because the run is not a + // registered region -- registering it would report overlaps that are + // correct by phase ordering. The ordering is pinned instead by + // `rot_points_window_is_written_after_the_lagrange_denominator_run`, + // which asserts it against rendered verifier source. let lagrange_run_end = ThetaSlot::XN.word() + self.batch_invert_input_words; let lagrange_run_cap = windows .q_eval_cptr_word diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index 8a9942630..75290e373 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -448,6 +448,44 @@ fn external_quotient_output_uses_planned_return_buffer() { ); } +/// The Lagrange denominator run is deliberately allowed to spill out of the +/// theta band into the rot_points / x1_powers / q_eval_set windows, so +/// `VerifierMemoryLayout::validate` caps it against the *live* boundary +/// (`Q_EVAL_CPTR_MPTR`) rather than against `ROT_POINTS_MPTR`. That is only +/// sound while those three windows are written strictly after the Lagrange +/// phase and never read before being rewritten. +/// +/// Because the run is not registered as a `MemoryRegion` -- registering it +/// would report overlaps that are correct by phase ordering -- the arena's +/// overlap loop cannot enforce this. Pin the ordering here instead, so moving +/// a rot_points write earlier fails a test rather than silently corrupting the +/// denominators. +#[test] +fn rot_points_window_is_written_after_the_lagrange_denominator_run() { + let (params, vk) = lowering_plan_test_vk(); + let generator = SolidityGenerator::new(¶ms, &vk, GeneratorConfig::new(1, 1)); + let source = generator + .render(crate::RenderOptions::default()) + .expect("test verifier should render") + .verifier; + + let lagrange_end = source + .find("batch_invert(success, X_N_MPTR") + .expect("Lagrange block must call batch_invert over the denominator run"); + // Any access at all, read or write: the run leaves garbage in this window, + // so an early read is as wrong as an early write being clobbered. + let first_rot_points_access = source + .find("add(ROT_POINTS_MPTR") + .expect("verifier must access the rot_points window"); + + assert!( + first_rot_points_access > lagrange_end, + "ROT_POINTS_MPTR is accessed at byte {first_rot_points_access}, before the Lagrange \ + batch_invert at byte {lagrange_end}; the denominator run spills into that window, so \ + touching it earlier would either corrupt the denominators or read their leftovers" + ); +} + /// The Lagrange denominator run grows with `num_instances`, so an oversized /// instance count must be rejected at construction with a typed error rather /// than surfacing later as a memory-layout failure. From 896fd04efed9eb8c676be2facec91277e9be3c17 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 02:15:21 +0100 Subject: [PATCH 60/72] Add a known-answer vector to the EIP-2537 constructor smoke test Every EIP-2537 probe in `require_eip2537_precompiles` used the all-zero point-at-infinity encoding: G1ADD(identity, identity), a worst-case G1MSM with all-zero terms, and PAIRING_CHECK over identity pairs. The identity is the one input a precompile can answer correctly without performing any curve arithmetic, so an implementation that simply returns zeros satisfies the G1ADD and G1MSM probes. The soundness-relevant part is narrower and worse: the identity is also the one input on which an implementation that omits the EIP-2537 subgroup check still returns the right answer, and the production verifier leans on G1MSM precisely as its subgroup validator for absorbed commitments. A chain whose G1MSM is present and correctly sized but subgroup-check-less would deploy without complaint and then accept proofs carrying off-subgroup points. Add a G1ADD(G, G) == 2G probe, which no stub can satisfy by echoing its input or returning a constant. The vectors are computed from the curve library at render time rather than hardcoded, and emitted as literals so the deploy cost stays a handful of mstores. Note this narrows but does not close the gap: the pairing and MSM probes are still identity-only, so a subgroup-check-less G1MSM would still pass. Closing that needs an off-subgroup negative vector, which is a larger change. Verified against revm's real EIP-2537 implementation: the constructor deploys, and `verifier_constructor_rejects_missing_or_mismatched_eip2537_precompiles` still rejects misrouted precompiles. A full `--features evm` run shows no test regressing relative to 2c77051. `constructor_known_answer_vector_is_the_generator_and_its_double` pins the constants against the curve library, including that the expected output differs from the probe input -- a known-answer test whose answer equals its question tests nothing. Co-Authored-By: Claude Opus 4.8 --- .../src/lowering/render/models.rs | 21 +++++++++++ .../solidity-verifier/src/lowering/tests.rs | 36 +++++++++++++++++++ .../partials/verifier/PrecompileSmoke.sol | 33 +++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/proofs/solidity-verifier/src/lowering/render/models.rs b/proofs/solidity-verifier/src/lowering/render/models.rs index 399d08667..52e54c7c9 100644 --- a/proofs/solidity-verifier/src/lowering/render/models.rs +++ b/proofs/solidity-verifier/src/lowering/render/models.rs @@ -10,6 +10,8 @@ use std::fmt; use askama::{Error, Template}; +use group::{prime::PrimeCurveAffine, Curve, Group}; +use midnight_curves::{G1Affine, G1Projective}; use ruint::aliases::U256; use crate::lowering::{ @@ -54,6 +56,14 @@ pub(crate) struct Eip2537TemplateConstants { pub(crate) g1msm_address: usize, pub(crate) pairing_address: usize, pub(crate) smoke_scratch_bytes: usize, + /// BLS12-381 G1 generator in EIP-2537 padded encoding. + /// + /// Used with [`Self::g1_double_generator`] as a known-answer vector for the + /// constructor smoke test: identity-only probes are satisfied by + /// implementations that never do any real curve arithmetic. + pub(crate) g1_generator: G1Words, + /// Twice the BLS12-381 G1 generator, in EIP-2537 padded encoding. + pub(crate) g1_double_generator: G1Words, } /// EIP-198 modexp frame constants rendered into templates. @@ -142,6 +152,12 @@ pub(crate) struct QuotientVmTemplateConstants { pub(crate) limb_pairwise_coeffs: usize, } +/// Convert a G1 point into the tuple form the templates render. +fn g1_words(point: G1Affine) -> G1Words { + let [x_hi, x_lo, y_hi, y_lo] = crate::lowering::encoding::g1_to_u256s(point); + (x_hi, x_lo, y_hi, y_lo) +} + impl Default for TemplateConstants { /// Build template constants from the Rust-side layout and VM specs. fn default() -> Self { @@ -158,6 +174,11 @@ impl Default for TemplateConstants { g1msm_address: layout::precompile::G1MSM_ADDRESS, pairing_address: layout::precompile::PAIRING_ADDRESS, smoke_scratch_bytes: layout::PAIRING_TWO_PAIR_BYTES, + g1_generator: g1_words(G1Affine::generator()), + g1_double_generator: { + let g = G1Projective::generator(); + g1_words((g + g).to_affine()) + }, }, modexp: ModexpTemplateConstants { address: layout::precompile::MODEXP_ADDRESS, diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index 75290e373..e562978ab 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -1197,6 +1197,39 @@ fn failed_success_paths_do_not_enter_ec_precompiles() { ); } +/// The constructor's known-answer probe is only as good as its constants: a +/// wrong 2G would brick every deployment, and a 2G that happened to equal the +/// probe's own input would silently restore the identity-only weakness. Pin +/// both against the curve library rather than against hardcoded literals. +#[test] +fn constructor_known_answer_vector_is_the_generator_and_its_double() { + use group::{prime::PrimeCurveAffine, Curve, Group}; + use midnight_curves::{G1Affine, G1Projective}; + + let constants = crate::lowering::render::TemplateConstants::default().eip2537; + let expected_g = crate::lowering::encoding::g1_to_u256s(G1Affine::generator()); + let g = G1Projective::generator(); + let expected_2g = crate::lowering::encoding::g1_to_u256s((g + g).to_affine()); + + assert_eq!( + constants.g1_generator, + (expected_g[0], expected_g[1], expected_g[2], expected_g[3]) + ); + assert_eq!( + constants.g1_double_generator, + ( + expected_2g[0], + expected_2g[1], + expected_2g[2], + expected_2g[3] + ) + ); + assert_ne!( + constants.g1_generator, constants.g1_double_generator, + "a known-answer probe whose expected output equals its input tests nothing" + ); +} + #[test] fn verifier_constructor_smoke_tests_runtime_prerequisites() { let verifier_template = verifier_template_corpus(); @@ -1211,6 +1244,9 @@ fn verifier_constructor_smoke_tests_runtime_prerequisites() { "eq(mload(add(scratch, {{ template_constants.word_bytes|hex() }})), 0x1234)", "non-Cancun fork fails during deployment", "G1ADD(identity, identity) -> identity", + "Known-answer probe: G1ADD(G, G) == 2G", + "template_constants.eip2537.g1_generator", + "template_constants.eip2537.g1_double_generator", "Worst-case generated G1MSM with all identity/zero terms", "constructor_g1msm_smoke_input_bytes", "PAIRING_CHECK([(identity_g1, identity_g2), (identity_g1, identity_g2)])", diff --git a/proofs/solidity-verifier/templates/partials/verifier/PrecompileSmoke.sol b/proofs/solidity-verifier/templates/partials/verifier/PrecompileSmoke.sol index baa6e6180..5fed51133 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/PrecompileSmoke.sol +++ b/proofs/solidity-verifier/templates/partials/verifier/PrecompileSmoke.sol @@ -27,6 +27,39 @@ revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), {{ template_constants.eip2537.g1_generator.0|hex_padded(64) }}) + mstore(add(scratch, 0x20), {{ template_constants.eip2537.g1_generator.1|hex_padded(64) }}) + mstore(add(scratch, 0x40), {{ template_constants.eip2537.g1_generator.2|hex_padded(64) }}) + mstore(add(scratch, 0x60), {{ template_constants.eip2537.g1_generator.3|hex_padded(64) }}) + mcopy(add(scratch, {{ template_constants.g1_bytes|hex() }}), scratch, {{ template_constants.g1_bytes|hex() }}) + if iszero(staticcall(gas(), {{ template_constants.eip2537.g1add_address|hex() }}, scratch, {{ template_constants.g1add_input_bytes|hex() }}, scratch, {{ template_constants.g1_bytes|hex() }})) { revert(0, 0) } + if iszero(eq(returndatasize(), {{ template_constants.g1_bytes|hex() }})) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), {{ template_constants.eip2537.g1_double_generator.0|hex_padded(64) }}), + eq(mload(add(scratch, 0x20)), {{ template_constants.eip2537.g1_double_generator.1|hex_padded(64) }}) + ), + and( + eq(mload(add(scratch, 0x40)), {{ template_constants.eip2537.g1_double_generator.2|hex_padded(64) }}), + eq(mload(add(scratch, 0x60)), {{ template_constants.eip2537.g1_double_generator.3|hex_padded(64) }}) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, {{ template_constants.eip2537.smoke_scratch_bytes|hex() }}) { off := add(off, {{ template_constants.word_bytes|hex() }}) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair From 6333673effe28f9a690378c6b2e6e0b3d85cc435 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 02:38:36 +0100 Subject: [PATCH 61/72] Base the generated layout above solc's via-IR spill reservation The verifier body is wrapped in `assembly ("memory-safe")` while writing absolute addresses from 0x80 upward and never allocating through the free-memory pointer. The annotation is therefore false -- and it is also what enables solc's via-IR stack-to-memory mover, which reserves spill slots upward from 0x80 and records the top in the runtime's `mstore(0x40, ...)` prologue. Both parties then claimed the same bytes. The deployed moonlight-wrap runtime begins `6108e0604052` = mstore(0x40, 0x08e0): solc reserved [0x80, 0x8e0) for itself, while the generated layout put TRANSCRIPT_MPTR and RETURN_MPTR at 0x80. The optimized IR shows a real spill in that window -- `mstore(0x300, mload(0x6a00))` stores the y challenge and reads it back ~600 lines later. This has not misbehaved so far only because the spill placements and the verifier's writes happen not to collide destructively. Nothing enforced that. The reservation is a function of stack pressure, not a constant: 0x80 (ivc-keccak, none), 0xe0 (rsa), 0x3c0 (poseidon), 0x8e0 (moonlight-wrap). A different circuit, solc release, or optimizer schedule can place a live spill slot across a verifier write, corrupting a challenge or pairing input with no source change, no compile error, and no crash -- potentially a false accept produced by recompilation alone. Removing the annotation was tried first and does not work: the block then fails with `Cannot swap Variable usr$f_4 ... too deep in the stack by 1 slots`, and solc itself suggests re-adding it. It is load-bearing. So move the generated layout above the reservation instead. Rooting LOW_MEMORY_SCRATCH_START at 0x1000 makes solc's spill region and the verifier's memory disjoint in space, so their liveness stops mattering. VK_CONSTRUCTOR_PAYLOAD_START is decoupled and stays at 0x80: it belongs to Halo2VerifyingKey, whose assembly is not annotated memory-safe, so no reservation exists there. Being explicit about scope: this removes the consequence, not the false annotation. Making the annotation honest needs runtime mload(0x40)-based re-basing, which costs an ADD per access. Add `compiled_memoryguard_does_not_overlap_generated_layout`, which compiles each rendered variant and reads the free-memory-pointer prologue back out of real bytecode, failing if the reservation ever reaches the layout base. Verified to have teeth: with the base returned to 0x80 it fails with `solc reserved [0x80, 0x3c0) ... overlaps the generated verifier layout based at 0x80`. Size impact is immaterial -- the largest known verifier runtime is 21161 bytes against EIP-170's 24576, and the change only widens some PUSH1 address literals to PUSH2. Full gate green: 194 passed with HALO2_SOLIDITY_RUN_EVM_TESTS=1 and features evm,rust-verifier-trace, including the end-to-end proof verification tests under revm. Co-Authored-By: Claude Opus 4.8 --- .../docs/architecture/MEMORY_LAYOUT.md | 31 ++++++-- .../docs/architecture/MIGRATION.md | 4 ++ proofs/solidity-verifier/docs/audit/AUDIT.md | 27 ++++++- proofs/solidity-verifier/src/evm.rs | 72 +++++++++++++++++++ proofs/solidity-verifier/src/lib.rs | 5 +- .../src/lowering/layout/memory.rs | 26 +++---- .../src/lowering/layout/mod.rs | 32 ++++++++- .../src/lowering/render/models.rs | 8 +-- proofs/solidity-verifier/src/test.rs | 46 +++++++++++- 9 files changed, 220 insertions(+), 31 deletions(-) diff --git a/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md b/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md index 4550d1607..5ad79c2eb 100644 --- a/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md +++ b/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md @@ -27,10 +27,33 @@ Solidity reserves the first four words of memory for compiler conventions: The generated verifier intentionally does not follow Solidity allocation by reading and bumping `mload(0x40)`. Instead, every generated absolute memory -region is planned at or above `0x80`. The streaming transcript buffer, the main -verifier return word, the split quotient return frame, the VK constructor -payload buffer, and low-memory precompile scratch all start from named -Rust-side layout constants rooted at `SOLIDITY_ALLOCATABLE_MEMORY_START`. +region is planned from named Rust-side layout constants. + +Those constants are **not** rooted at `0x80`. The verifier body is wrapped in +`assembly ("memory-safe")`, which is load-bearing -- without it the block does +not compile under `--via-ir` (stack too deep) -- but also factually untrue, +since the block writes memory it never obtained from the free-memory pointer. +The annotation is what enables solc's stack-to-memory mover, which reserves +spill slots upward from `0x80` and records the top in the runtime's +`mstore(0x40, ...)` prologue. Observed reservations range from `0x80` (none) to +`0x8e0`, varying with the circuit, the solc release, and the optimizer +schedule. + +Basing the layout at `0x80` therefore put solc's spill slots and the verifier's +own transcript buffer in the same bytes, separated only by live ranges that +nothing enforced -- a recompilation could silently place a live spill across a +verifier write and corrupt a challenge or pairing input. So the streaming +transcript buffer, the main verifier return word, the split quotient return +frame, and low-memory precompile scratch are rooted at +`LOW_MEMORY_SCRATCH_START` (`0x1000`), above the largest observed reservation. +`compiled_memoryguard_does_not_overlap_generated_layout` compiles each rendered +variant and fails the build if a future circuit or compiler pushes the +reservation past that base. + +The VK constructor payload buffer is the exception: it lives in +`Halo2VerifyingKey`, whose assembly carries no `memory-safe` annotation, so +solc reserves nothing there and it stays at +`SOLIDITY_ALLOCATABLE_MEMORY_START`. The code generator treats `[0x00..0x80)` as off limits for generated writes: `VerifierMemoryLayout::validate()` rejects any registered region inside that diff --git a/proofs/solidity-verifier/docs/architecture/MIGRATION.md b/proofs/solidity-verifier/docs/architecture/MIGRATION.md index 5e314d953..237132060 100644 --- a/proofs/solidity-verifier/docs/architecture/MIGRATION.md +++ b/proofs/solidity-verifier/docs/architecture/MIGRATION.md @@ -343,6 +343,10 @@ The `cargo test --lib` suite now stands at 7/7 green: `assembly ("memory-safe") { ... }` to silence the legacy stack-too-deep error path; with `--via-ir` solc 0.8.30 now compiles the full ~117 kB output cleanly. + Note the annotation is not merely cosmetic: it enables solc's + stack-to-memory mover, which reserves spill slots upward from + `0x80`. See AUDIT.md TA-5 and `docs/architecture/MEMORY_LAYOUT.md` + for why the generated layout is based at `0x1000` rather than `0x80`. * `tests/poseidon_fixture.rs` — new integration test (gated behind `feature = "evm"` and currently `#[ignore]`d, see below) that: 1. Configures `SRS_DIR` to point at diff --git a/proofs/solidity-verifier/docs/audit/AUDIT.md b/proofs/solidity-verifier/docs/audit/AUDIT.md index b85c509aa..aba140bf0 100644 --- a/proofs/solidity-verifier/docs/audit/AUDIT.md +++ b/proofs/solidity-verifier/docs/audit/AUDIT.md @@ -2314,10 +2314,33 @@ memory-safety rules. If future edits add Solidity code after the block, or if the compiler reasons across the block in an unexpected way, this becomes a miscompilation risk. +**Status: addressed.** The risk was not hypothetical. `solc 0.8.30` with the +pinned flags emits `mstore(0x40, 0x08e0)` for the moonlight-wrap render, i.e. +it reserved `[0x80, 0x8e0)` for via-IR stack-to-memory spill slots -- directly +on top of a generated layout that started at `0x80`. Reservations observed +across renders: `0x80` (ivc-keccak, none), `0xe0` (rsa), `0x3c0` (poseidon), +`0x8e0` (moonlight-wrap). At least one spill (`mstore(0x300, mload(0x6a00))`, +the `y` challenge, re-read ~600 IR lines later) sits in that window. + +The recommendation below to remove the annotation was tested and does not work: +the block then fails to compile with `Cannot swap Variable usr$f_4 ... too deep +in the stack by 1 slots`, and solc itself suggests re-adding the annotation. +The annotation is load-bearing. + +The fix instead moves the generated layout above the reservation +(`LOW_MEMORY_SCRATCH_START = 0x1000`), making the two regions disjoint in space +so their liveness no longer matters, and adds +`compiled_memoryguard_does_not_overlap_generated_layout`, which compiles each +rendered variant and fails if the reservation ever grows past that base. Note +this removes the *consequence*, not the false annotation itself; making the +annotation honest would require runtime `mload(0x40)`-based re-basing at the +cost of an `ADD` per memory access. + Recommendation: -- Prefer removing `"memory-safe"` from the terminal verifier block unless there - is a compiler-specific proof that this pattern is accepted. +- ~~Prefer removing `"memory-safe"` from the terminal verifier block unless + there is a compiler-specific proof that this pattern is accepted.~~ + Superseded: removal does not compile. See status note above. - Pin the exact compiler and EVM version. The Renegade audit's recommendation to use fixed pragmas rather than floating `^0.8.x` is especially relevant for generated verifier code. diff --git a/proofs/solidity-verifier/src/evm.rs b/proofs/solidity-verifier/src/evm.rs index 5a3ae7fec..7f929275e 100644 --- a/proofs/solidity-verifier/src/evm.rs +++ b/proofs/solidity-verifier/src/evm.rs @@ -231,6 +231,78 @@ pub(crate) mod test { } } + /// Read the free-memory-pointer initializer from a runtime bytecode prefix. + /// + /// solc opens every contract by storing the initial free-memory pointer to + /// slot `0x40`. With `memoryguard` active -- which the verifier's + /// `assembly ("memory-safe")` annotation enables -- that value is raised + /// above `0x80` to reserve via-IR stack-to-memory spill slots, so the + /// returned value is the top of the region solc has claimed for itself. + /// + /// Recognises the two prologue encodings solc emits: + /// `PUSH1 v` / `PUSH2 v` followed by `PUSH1 0x40 MSTORE`, allowing an + /// optional `DUP1` between them (used when the value is reused). + /// Returns `None` if the prefix does not match, so callers can distinguish + /// "no reservation found" from "reservation is 0x80". + #[must_use] + pub fn runtime_free_memory_pointer_init(runtime: &[u8]) -> Option { + // PUSH1 v | PUSH2 v_hi v_lo + let (value, mut i) = match *runtime.first()? { + 0x60 => (usize::from(*runtime.get(1)?), 2), + 0x61 => ( + (usize::from(*runtime.get(1)?) << 8) | usize::from(*runtime.get(2)?), + 3, + ), + _ => return None, + }; + // Optional DUP1 when solc reuses the value. + if runtime.get(i) == Some(&0x80) { + i += 1; + } + // PUSH1 0x40 MSTORE + if runtime.get(i) == Some(&0x60) + && runtime.get(i + 1) == Some(&0x40) + && runtime.get(i + 2) == Some(&0x52) + { + return Some(value); + } + None + } + + /// Compile `solidity` and return the deployed runtime bytecode. + /// + /// # Panics + /// Panics under the same conditions as [`compile_solidity`]. + pub fn compile_solidity_runtime(solidity: impl AsRef<[u8]>) -> Vec { + let solc = require_pinned_solc(); + let mut process = Command::new(&solc) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("--bin-runtime") + .arg("--optimize") + .arg("--optimize-runs") + .arg(DEFAULT_OPTIMIZE_RUNS.to_string()) + .arg("--via-ir") + .arg("--evm-version") + .arg("cancun") + .arg("--no-cbor-metadata") + .arg("-") + .spawn() + .unwrap_or_else(|err| panic!("Failed to spawn process with command '{solc}':\n{err}")); + process.stdin.take().unwrap().write_all(solidity.as_ref()).unwrap(); + let output = process.wait_with_output().unwrap(); + let stdout = str::from_utf8(&output.stdout).unwrap(); + let marker = "Binary of the runtime part:"; + let start = stdout.find(marker).unwrap_or_else(|| { + panic!( + "Runtime compilation fails:\n{}", + str::from_utf8(&output.stderr).unwrap() + ) + }) + marker.len(); + hex::decode(stdout[start..].trim()).expect("solc runtime output should be hex") + } + /// Extract creation bytecode from solc's text `--bin` output. fn find_binary(stdout: &str) -> Option> { let start = stdout.find("Binary:")? + 8; diff --git a/proofs/solidity-verifier/src/lib.rs b/proofs/solidity-verifier/src/lib.rs index 011af0a58..6cf2f30cf 100644 --- a/proofs/solidity-verifier/src/lib.rs +++ b/proofs/solidity-verifier/src/lib.rs @@ -58,8 +58,9 @@ pub const OUTER_SINGLE_H_COMMITMENT_ENABLED: bool = cfg!(feature = "outer-single #[cfg(feature = "evm")] pub use evm::test::{ - compile_solidity, compile_solidity_with_runs, pinned_solc_available, revm, solc_version, - CallOutcome, Evm, ALLOW_UNPINNED_SOLC_ENV, DEFAULT_OPTIMIZE_RUNS, PINNED_SOLC_VERSION, + compile_solidity, compile_solidity_runtime, compile_solidity_with_runs, pinned_solc_available, + revm, runtime_free_memory_pointer_init, solc_version, CallOutcome, Evm, + ALLOW_UNPINNED_SOLC_ENV, DEFAULT_OPTIMIZE_RUNS, PINNED_SOLC_VERSION, }; /// Test-only helper that exposes the internal BLS12-381 G1 to EIP-2537 diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index b91c70f1d..fd91314d3 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -1374,7 +1374,7 @@ mod tests { let layout = VerifierMemoryLayout::new( &meta, &vk, - Ptr::memory(0x1000), + Ptr::memory(0x2000), VerifierMemoryLayoutConfig::default(), ); let theta = layout.theta_mptr.value().as_usize(); @@ -1429,7 +1429,7 @@ mod tests { let layout = VerifierMemoryLayout::new( &meta, &vk, - Ptr::memory(0x1000), + Ptr::memory(0x2000), VerifierMemoryLayoutConfig::default(), ); @@ -1531,7 +1531,7 @@ mod tests { .expect("scalar inversion scratch registered"); assert_eq!( layout.scalar_inv_scratch_mptr, - 0x1000 - MODEXP_SCRATCH_BYTES + 0x2000 - MODEXP_SCRATCH_BYTES ); assert_eq!(scalar_inv.start, layout.scalar_inv_scratch_mptr); assert_eq!(scalar_inv.len, MODEXP_FRAME_BYTES); @@ -1545,17 +1545,17 @@ mod tests { let windows = ThetaWindowLayout::compatibility(); let mut config = VerifierMemoryLayoutConfig::default(); config.pcs.rot_points_words = windows.rot_points_cap_words + 1; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); assert!(layout.validate().unwrap_err().contains("ROT_POINTS_MPTR")); let mut config = VerifierMemoryLayoutConfig::default(); config.pcs.x1_powers_words = windows.x1_powers_cap_words + 1; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); assert!(layout.validate().unwrap_err().contains("X1_POWERS_MPTR")); let mut config = VerifierMemoryLayoutConfig::default(); config.pcs.q_eval_set_words = windows.q_eval_set_cap_words + 1; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); assert!(layout.validate().unwrap_err().contains("Q_EVAL_SET_MPTR")); } @@ -1567,7 +1567,7 @@ mod tests { acc_msm_terms: 4, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); let region = layout.map.region("accumulator_msm").expect("accumulator MSM region registered"); @@ -1584,7 +1584,7 @@ mod tests { let layout = VerifierMemoryLayout::new( &meta, &vk, - Ptr::memory(0x1000), + Ptr::memory(0x2000), VerifierMemoryLayoutConfig::default(), ); let selector = layout @@ -1621,7 +1621,7 @@ mod tests { }, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); let region = layout.map.region("trace_u256_log_word").expect("trace_u256 region registered"); @@ -1648,7 +1648,7 @@ mod tests { }, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); for (name, end) in [ ( @@ -1690,7 +1690,7 @@ mod tests { num_instances: 5, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); let region = layout .map .region("batch_invert_scratch") @@ -1727,7 +1727,7 @@ mod tests { num_instances: max_instances, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); assert_eq!(layout.batch_invert_input_words, cap_words); layout.validate().expect("run ending below Q_EVAL_CPTR_MPTR is valid"); @@ -1735,7 +1735,7 @@ mod tests { num_instances: max_instances + 1, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); let err = layout.validate().unwrap_err(); assert!( err.contains("Lagrange batch-inversion input run"), diff --git a/proofs/solidity-verifier/src/lowering/layout/mod.rs b/proofs/solidity-verifier/src/lowering/layout/mod.rs index a84bc8236..76a08f82f 100644 --- a/proofs/solidity-verifier/src/lowering/layout/mod.rs +++ b/proofs/solidity-verifier/src/lowering/layout/mod.rs @@ -26,7 +26,22 @@ pub(crate) const SOLIDITY_ALLOCATABLE_MEMORY_START: usize = 0x80; /// The full reserved prefix: scratch, free-memory pointer, and zero slot. pub(crate) const SOLIDITY_RESERVED_MEMORY_BYTES: usize = SOLIDITY_ALLOCATABLE_MEMORY_START; /// Generated verifier transcript and low-memory precompile scratch base. -pub(crate) const LOW_MEMORY_SCRATCH_START: usize = SOLIDITY_ALLOCATABLE_MEMORY_START; +/// +/// Deliberately *above* [`SOLIDITY_ALLOCATABLE_MEMORY_START`]. The verifier +/// body is wrapped in `assembly ("memory-safe")`, which is what lets solc's +/// via-IR stack-to-memory mover run at all -- without it the block does not +/// compile (stack too deep). That mover reserves spill slots from `0x80` +/// upward and records the top in the runtime's `mstore(0x40, ...)` prologue. +/// Observed reservations run from `0x80` (none) to `0x8e0`, varying with the +/// circuit, the solc release, and the optimizer schedule. +/// +/// Basing the generated layout at `0x80` therefore put solc's spill slots and +/// the verifier's own transcript buffer in the same bytes, kept apart only by +/// live ranges that nothing enforced. Starting above the largest observed +/// reservation makes them disjoint by construction; +/// `compiled_memoryguard_does_not_overlap_generated_layout` fails the build if +/// a future circuit or compiler pushes the reservation past this base. +pub(crate) const LOW_MEMORY_SCRATCH_START: usize = 0x1000; /// Start of the Keccak transcript buffer used by the assembly helpers. pub(crate) const TRANSCRIPT_BUFFER_START: usize = LOW_MEMORY_SCRATCH_START; /// Shared low-memory scratch for PCS pairing serialization. @@ -36,7 +51,12 @@ pub(crate) const VERIFIER_RETURN_BUFFER_START: usize = LOW_MEMORY_SCRATCH_START; /// Return buffer used by split quotient evaluator calls. pub(crate) const QUOTIENT_RETURN_BUFFER_START: usize = LOW_MEMORY_SCRATCH_START; /// Constructor-time memory base for the separate VK runtime payload. -pub(crate) const VK_CONSTRUCTOR_PAYLOAD_START: usize = LOW_MEMORY_SCRATCH_START; +/// +/// Deliberately *not* derived from [`LOW_MEMORY_SCRATCH_START`]. This buffer +/// belongs to `Halo2VerifyingKey`, a separate contract whose assembly is not +/// annotated `memory-safe`, so solc reserves no via-IR spill window there and +/// the payload can sit at Solidity's normal allocatable start. +pub(crate) const VK_CONSTRUCTOR_PAYLOAD_START: usize = SOLIDITY_ALLOCATABLE_MEMORY_START; /// Number of EVM words in one Fr scalar. pub(crate) const FR_WORDS: usize = 1; /// EIP-2537 padded G1 encoding: x_hi, x_lo, y_hi, y_lo. @@ -662,7 +682,13 @@ mod tests { assert_eq!(super::SOLIDITY_FREE_MEMORY_POINTER_SLOT, 0x40); assert_eq!(super::SOLIDITY_ZERO_SLOT, 0x60); assert_eq!(super::SOLIDITY_RESERVED_MEMORY_BYTES, 0x80); - assert_eq!(super::TRANSCRIPT_BUFFER_START, 0x80); + assert_eq!( + super::TRANSCRIPT_BUFFER_START, + super::LOW_MEMORY_SCRATCH_START + ); + // Above Solidity's allocatable start on purpose: solc reserves + // via-IR spill slots upward from 0x80 in this contract. + assert!(super::LOW_MEMORY_SCRATCH_START > super::SOLIDITY_ALLOCATABLE_MEMORY_START); assert_eq!(super::VK_CONSTRUCTOR_PAYLOAD_START, 0x80); } diff --git a/proofs/solidity-verifier/src/lowering/render/models.rs b/proofs/solidity-verifier/src/lowering/render/models.rs index 52e54c7c9..58974681b 100644 --- a/proofs/solidity-verifier/src/lowering/render/models.rs +++ b/proofs/solidity-verifier/src/lowering/render/models.rs @@ -1065,7 +1065,7 @@ mod tests { let memory = VerifierMemoryLayout::new( &ConstraintSystemMeta::default(), &synthetic_vk(0, 0), - Ptr::memory(0x1000), + Ptr::memory(0x2000), VerifierMemoryLayoutConfig::default(), ); let mut proof = ProofReadPlan::default(); @@ -1119,9 +1119,9 @@ mod tests { }, memory, vk_header: Default::default(), - vk_mptr: Ptr::memory(0x1000), - challenge_mptr: Ptr::memory(0x1200), - theta_mptr: Ptr::memory(0x1300), + vk_mptr: Ptr::memory(0x2000), + challenge_mptr: Ptr::memory(0x2200), + theta_mptr: Ptr::memory(0x2300), proof_cptr: Ptr::calldata(proof_cptr), abi_selector_bytes: crate::lowering::layout::abi::SELECTOR_BYTES, abi_proof_head_offset: crate::lowering::layout::abi::VERIFY_PROOF_PROOF_HEAD_OFFSET, diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index a47e14ab6..9ef3ee5ae 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -45,9 +45,10 @@ use ruint::aliases::U256; use sha3::Digest; use crate::{ - compile_solidity, encode_calldata, pinned_solc_available, AccumulatorEncoding, CallOutcome, - Evm, GeneratorConfig, RenderDiagnostics, RenderOptions, RenderQuotient, RenderVk, - SolidityGenerator, FN_SIG_VERIFY_PROOF, + compile_solidity, compile_solidity_runtime, encode_calldata, pinned_solc_available, + runtime_free_memory_pointer_init, AccumulatorEncoding, CallOutcome, Evm, GeneratorConfig, + RenderDiagnostics, RenderOptions, RenderQuotient, RenderVk, SolidityGenerator, + FN_SIG_VERIFY_PROOF, }; /// Scalar field used by the BLS12-381 Poseidon fixtures. @@ -1357,6 +1358,45 @@ fn pinned_quotient_verifier_rejects_wrong_vk_and_quotient_contracts() { ); } +/// The verifier body is wrapped in `assembly ("memory-safe")` while writing +/// absolute addresses, which is a false promise: it never allocates through the +/// free-memory pointer. That annotation is nonetheless load-bearing -- without +/// it the block does not compile (stack too deep) -- and it is what lets solc's +/// via-IR stack-to-memory mover reserve spill slots upward from `0x80`. +/// +/// The generated layout is therefore based above that reservation rather than +/// at `0x80`, so solc's spill slots and the verifier's own memory are disjoint +/// by construction instead of by a liveness coincidence. The reservation size +/// is not fixed -- observed values range from `0x80` to `0x8e0` depending on +/// the circuit, the solc release, and the optimizer schedule -- so assert the +/// property against real compiled bytecode rather than assuming it holds. +#[test] +fn compiled_memoryguard_does_not_overlap_generated_layout() { + if !poseidon_inputs_available_for_evm() { + return; + } + + let fixture = create_property_poseidon_fixture(); + for (name, source) in [ + ("embedded", fixture.embedded_verifier_solidity.as_str()), + ("separate", fixture.separate_verifier_solidity.as_str()), + ("quotient", fixture.quotient_verifier_solidity.as_str()), + ] { + let runtime = compile_solidity_runtime(source); + let reserved_end = runtime_free_memory_pointer_init(&runtime).unwrap_or_else(|| { + panic!("{name}: could not read the free-memory-pointer prologue from runtime bytecode") + }); + assert!( + reserved_end <= crate::lowering::layout::LOW_MEMORY_SCRATCH_START, + "{name}: solc reserved [0x80, {reserved_end:#x}) for via-IR spill slots, which \ + overlaps the generated verifier layout based at {:#x}. A live spill slot can then \ + sit across a verifier write, silently corrupting a challenge or pairing input. \ + Raise LOW_MEMORY_SCRATCH_START above {reserved_end:#x}.", + crate::lowering::layout::LOW_MEMORY_SCRATCH_START + ); + } +} + #[test] fn verifier_constructor_rejects_missing_or_mismatched_eip2537_precompiles() { if !poseidon_inputs_available_for_evm() { From a30fef416406900ece595f320524feae6bd4e0bf Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 02:52:38 +0100 Subject: [PATCH 62/72] Enforce the EIP-170 runtime size limit on the verifier contracts `Halo2VerifyingKey` is size-checked at render time by `validate_payload_layout`, because a data contract's runtime length is known before compilation. The verifier's is not -- it only exists once solc has run -- so nothing bounded it. That gap was invisible rather than latent: the revm harness sets `cfg.limit_contract_code_size = Some(usize::MAX)` (evm.rs), deliberately, so that in-process tests can run oversized contracts. The consequence is that a verifier exceeding 24576 bytes would pass the entire suite, including every end-to-end proof verification, and then fail to deploy on mainnet or any other EIP-170 chain. The comment justifying the cap lift is also stale: it cites a ~25 kB verifier from before the inline `decompress_g1` calls were removed. Add `compiled_verifier_runtime_fits_the_eip170_limit`, which compiles the embedded, separate, quotient and VK renders and asserts each runtime is within the limit. Checking the compiled artifact is the only option here, since the size is a property of solc's output rather than of the plan. Current headroom is comfortable -- the embedded poseidon render is 16037 bytes and the largest known verifier, the deployed moonlight-wrap one, is 21161 -- but the layout rebase in the previous commit widens some address literals from PUSH1 to PUSH2, so the bound is now worth asserting rather than assuming. Verified the assertion measures real bytecode by temporarily inverting it and reading back the reported sizes. Co-Authored-By: Claude Opus 4.8 --- proofs/solidity-verifier/src/test.rs | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index 9ef3ee5ae..5eab7d99b 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -1397,6 +1397,37 @@ fn compiled_memoryguard_does_not_overlap_generated_layout() { } } +/// `Halo2VerifyingKey` is size-checked at render time by +/// `validate_payload_layout`, because a data contract's runtime length is known +/// before compilation. The verifier's is not -- it only exists once solc has +/// run -- so nothing bounded it, and the revm harness deliberately sets +/// `limit_contract_code_size = usize::MAX` (see `evm.rs`), meaning an oversized +/// verifier would pass the whole suite and then fail to deploy on any EIP-170 +/// chain. Check the compiled artifact directly. +#[test] +fn compiled_verifier_runtime_fits_the_eip170_limit() { + if !poseidon_inputs_available_for_evm() { + return; + } + + let limit = crate::lowering::render::EIP_170_MAX_RUNTIME_BYTES; + let fixture = create_property_poseidon_fixture(); + for (name, source) in [ + ("embedded", fixture.embedded_verifier_solidity.as_str()), + ("separate", fixture.separate_verifier_solidity.as_str()), + ("quotient", fixture.quotient_verifier_solidity.as_str()), + ("vk", fixture.vk_solidity.as_str()), + ] { + let runtime_len = compile_solidity_runtime(source).len(); + assert!( + runtime_len <= limit, + "{name}: compiled runtime is {runtime_len} bytes, over the EIP-170 limit of \ + {limit}; this contract cannot be deployed on mainnet or any \ + EIP-170 chain. Note the revm harness lifts this cap, so no other test catches it." + ); + } +} + #[test] fn verifier_constructor_rejects_missing_or_mismatched_eip2537_precompiles() { if !poseidon_inputs_available_for_evm() { From 2938a400d9d187fbd24fd3bb07fc3812329c49e9 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 03:09:18 +0100 Subject: [PATCH 63/72] Propagate the TranscriptInputBytes bound to LightAggregator::verify `midnight-aggregation` did not compile. `LightAggregator::verify` delegates to `plonk::prepare`, which gained the bound ::Input: TranscriptInputBytes in db26827 ("test(solidity): add native differential trace hooks"). The bound was never propagated to this caller, so the crate has been broken at HEAD -- and because it is a workspace member, `cargo check --workspace` fails before reaching anything else. Add the bound to `verify`'s where clause, matching how the other `prepare` callers declare it in `proofs/tests/plonk_api.rs:477,523`. This is a propagation gap rather than a design problem: `TranscriptInputBytes` is implemented for both real transcript input types, `Vec` and `Vec`, so every concrete transcript already satisfies it and no call site needs to change. `cargo check --workspace --lib --tests` is clean and the crate's two lib tests pass. Co-Authored-By: Claude Opus 4.8 --- aggregation/src/light_aggregator/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/aggregation/src/light_aggregator/mod.rs b/aggregation/src/light_aggregator/mod.rs index d78d35022..3ef582708 100644 --- a/aggregation/src/light_aggregator/mod.rs +++ b/aggregation/src/light_aggregator/mod.rs @@ -81,7 +81,9 @@ use midnight_proofs::{ }, EvaluationDomain, }, - transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}, + transcript::{ + CircuitTranscript, Hashable, Sampleable, Transcript, TranscriptHash, TranscriptInputBytes, + }, }; use rand::{CryptoRng, RngCore}; @@ -414,6 +416,11 @@ impl LightAggregator { C: Hashable, F: Sampleable + Hashable, u32: Hashable, + // Required by `plonk::prepare`, which this method delegates to. The + // bound is satisfied by both real transcript input types (`Vec` + // and `Vec`); propagating it here mirrors the other `prepare` + // callers in `proofs/tests/plonk_api.rs`. + ::Input: TranscriptInputBytes, { // Read the LHS of the acc from the transcript. let acc_lhs: Msm = { From 70dd1a8a5c69f6bcfb35a050e1a986c69aab9d32 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 03:59:02 +0100 Subject: [PATCH 64/72] Add a CI replay that adversarially exercises the accumulator decoder The public-accumulator decode path in AccumulatorHelpers.yul had no executing test coverage at all. The tests that appeared to cover it -- `accumulator_decoder_rejects_noncanonical_infinity` and neighbours in src/lowering/tests.rs -- are `verifier_template.contains("...")` string greps over the raw template: they assert the guard *text* is present and never render, compile, or run it. That is exactly how the always-false `and` guard fixed in a951f04 survived. Those greps passed for as long as the identity branch was dead code, and a regression that re-deadened it would pass them again. The only executing accumulator tests are in tests/ivc_keccak_solidity.rs, which proves a k=20 decider from scratch. It needs ~300 MB of SRS and three minutes, so it is gated behind HALO2_SOLIDITY_RUN_IVC_BENCH=1 and never runs in CI. Replace the orphaned fixtures/ivc contents with pre-rendered artifacts and replay those instead. The previous fixtures (vk.bin, proof.bin, instance.bin, two trace dumps) were referenced by no test, script, or example, had no writer anywhere in the repo, and were not even loadable: `VerifyingKey` serialization starts with VERSION = 0x03 while vk.bin starts with 0x12. Reading one would also require the decider circuit type from midnight-aggregation, and rendering a verifier from a VK needs the full SRS because `SolidityGenerator` consumes `params.g_lagrange()` -- which is what makes a VK-based replay unusable in CI no matter how fresh the fixture is. Shipping the rendered contracts plus matching calldata sidesteps all of that: the replay needs only solc and revm. It runs in ~2 s. The test verifies the fixture proof is accepted first, then asserts three classes of accumulator mutation are rejected: non-canonical limb packing, a malformed coordinate with a zeroed scalar, and substitution of the canonical encoded point at infinity. The accept baseline is what makes the rejections meaningful -- without it they could pass because the verifier rejects everything. Two things guard against the test passing vacuously: - The offset arithmetic is checked structurally. Four 56-bit limbs occupy the low 224 bits of a packed word, so every accumulator coordinate word must have four zero high bytes; random proof bytes would not. A miscomputed offset would otherwise still revert, for the wrong reason. - Each mutation class was individually neutered and confirmed to make the verifier accept, so all three assertions are load-bearing. The fixture describes itself: accumulator offset, limb count and the has_accumulator flag are parsed back out of the rendered VK payload, and the infinity encoding out of the verifier's own constants, so this test carries no duplicate constants that could drift from the artifact. Fixtures were regenerated at this branch's codegen and confirmed to carry the rebased memory layout (TRANSCRIPT_MPTR = 0x1000); the underlying bench verified the proof on-chain in 1,285,274 gas. fixtures/ivc/README.md records provenance and the regeneration command. Known limitation: a rendered fixture is a codegen snapshot. It stays self-consistent and keeps passing after a codegen change, so it silently stops testing current output. Detecting that needs re-rendering, which needs the SRS again, so it is tracked by a commit stamp rather than an assertion. Co-Authored-By: Claude Opus 4.8 --- .../fixtures/ivc/Halo2QuotientEvaluator.sol | 1593 ++++++++++++ .../fixtures/ivc/Halo2Verifier.sol | 2282 +++++++++++++++++ .../fixtures/ivc/Halo2VerifyingKey.sol | 693 +++++ .../solidity-verifier/fixtures/ivc/README.md | 48 + .../fixtures/ivc/calldata.bin | Bin 0 -> 8356 bytes .../fixtures/ivc/instance.bin | Bin 3528 -> 0 bytes .../solidity-verifier/fixtures/ivc/proof.bin | Bin 9824 -> 0 bytes .../fixtures/ivc/rust_trace.json | 1068 -------- .../fixtures/ivc/solidity_trace.json | 1 - proofs/solidity-verifier/fixtures/ivc/vk.bin | Bin 66731 -> 0 bytes .../tests/ivc_accumulator_replay.rs | 315 +++ 11 files changed, 4931 insertions(+), 1069 deletions(-) create mode 100644 proofs/solidity-verifier/fixtures/ivc/Halo2QuotientEvaluator.sol create mode 100644 proofs/solidity-verifier/fixtures/ivc/Halo2Verifier.sol create mode 100644 proofs/solidity-verifier/fixtures/ivc/Halo2VerifyingKey.sol create mode 100644 proofs/solidity-verifier/fixtures/ivc/README.md create mode 100644 proofs/solidity-verifier/fixtures/ivc/calldata.bin delete mode 100644 proofs/solidity-verifier/fixtures/ivc/instance.bin delete mode 100644 proofs/solidity-verifier/fixtures/ivc/proof.bin delete mode 100644 proofs/solidity-verifier/fixtures/ivc/rust_trace.json delete mode 100644 proofs/solidity-verifier/fixtures/ivc/solidity_trace.json delete mode 100644 proofs/solidity-verifier/fixtures/ivc/vk.bin create mode 100644 proofs/solidity-verifier/tests/ivc_accumulator_replay.rs diff --git a/proofs/solidity-verifier/fixtures/ivc/Halo2QuotientEvaluator.sol b/proofs/solidity-verifier/fixtures/ivc/Halo2QuotientEvaluator.sol new file mode 100644 index 000000000..c2bc6699d --- /dev/null +++ b/proofs/solidity-verifier/fixtures/ivc/Halo2QuotientEvaluator.sol @@ -0,0 +1,1593 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.24; + +/// @title Split Halo2 quotient numerator evaluator. +/// @notice Reconstructs the scalar side of the linearization query for a generated verifier. +/// @dev This is the split-out implementation of the expensive +/// `partially_evaluate_identities` / `compute_linearization_commitment` side +/// from the Midfall Rust verifier: +/// - `midfall/proofs/src/plonk/mod.rs::partially_evaluate_identities` +/// - `midfall/proofs/src/plonk/linearization/verifier.rs::compute_linearization_commitment` +/// - `midfall/proofs/src/plonk/{permutation,logup,trash}.rs` +/// @dev The main verifier has already parsed calldata, checked proof scalar +/// ranges, sampled Fiat-Shamir challenges, loaded the VK payload, and computed +/// local Lagrange/public-input values before making the staticcall. +/// +/// Instead of receiving structured Solidity arguments, the evaluator receives +/// the verifier's memory frame as raw calldata: +/// +/// calldata[0..QUOTIENT_FRAME_LEN) +/// == memory[QUOTIENT_FRAME_BASE..QUOTIENT_FRAME_BASE+QUOTIENT_FRAME_LEN) +/// +/// The fallback copies that frame back into the same generated memory +/// addresses. All constants below are therefore memory addresses inside that +/// copied frame, not ABI offsets. +/// +/// Output is a compact fixed frame consumed by Halo2Verifier: +/// +/// word 0: QUOTIENT_MAGIC, a generated version/magic guard +/// word 1: linearization_expected_eval +/// word 2..: simple-selector accumulator scalars +/// +/// This contract reconstructs the Rust verifier's y-batched identity numerator +/// nu_y(x) and returns the linearization expected scalar -nu_y(x). It does not +/// evaluate or trust a quotient scalar h(x). +/// +/// The quotient limb commitments are handled by Halo2Verifier on the commitment +/// side as (1 - x^n) * sum_i x_split^i * Q_i. That is why this scalar side is +/// -nu_y(x), not h(x) = nu_y(x) / (x^n - 1). +/// +/// See docs/QUOTIENT_NUMERATOR_EVALUATOR.md for the full Rust/Solidity mapping. +contract Halo2QuotientEvaluator { + // BLS12-381 scalar field modulus. All arithmetic in this contract is over + // Fr and uses addmod/mulmod with this modulus. + uint256 internal constant FR_MODULUS = + 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + + // Start of the copied verifier-key payload in memory. The VK payload also + // carries the compact quotient VM constant/program tables used by the + // included numerator block. + uint256 internal constant VK_MPTR = 0x3680; + + // Fiat-Shamir challenge slots. Halo2Verifier sampled these in transcript + // order before the external call. The evaluator only reads them. + uint256 internal constant CHALLENGE_MPTR = 0x7900; + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; + + // Common polynomial values at x. Halo2Verifier computes these once after + // sampling x and places them in the frame so the numerator block can share + // the exact Rust verifier inputs. + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; + + // Proof evaluation table. Values are already decoded as canonical Fr words + // by Halo2Verifier. The generated numerator code indexes this table by the + // same query order as the Rust verifier. + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; + + // Scratch/output region for simple-selector linearization accumulators. + // The numerator block writes one bucket per simple selector, then the + // fallback copies those buckets into the compact return frame. + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; + // Callee-local scratch for trace hooks. Trace-enabled verifier builds call + // this evaluator with CALL so quotient identity logs can be compared with + // the native Rust trace. Production verifier builds keep using STATICCALL + // and render this evaluator without trace hooks. + uint256 internal constant TRACE_U256_MPTR = 0x1000; + uint256 internal constant QUOTIENT_OUTPUT_MPTR = 0x1000; + + // External-call frame metadata. The main verifier calls this contract with + // exactly QUOTIENT_FRAME_LEN bytes starting at + // QUOTIENT_FRAME_BASE, then checks the return length and QUOTIENT_MAGIC. + uint256 internal constant QUOTIENT_FRAME_BASE = 0x3680; + uint256 internal constant QUOTIENT_FRAME_LEN = 0x6ac0; + uint256 internal constant QUOTIENT_OUTPUT_LEN = 0x0180; + uint256 internal constant QUOTIENT_MAGIC = 0x00000000000000000000000000000000000000000000000051554556414c0001; + + /// @notice Evaluate the generated quotient numerator block for one verifier memory frame. + /// @dev Calldata is exactly the raw frame, not ABI-encoded arguments. Returns `QUOTIENT_MAGIC`, the linearization expected eval, and selector buckets. + /// @dev This fallback also uses generated absolute memory addresses and + /// returns directly from assembly. Its compact return frame starts at + /// `0x80`, preserving Solidity's reserved memory words. + fallback() external { + assembly ("memory-safe") { + // Reject malformed calls. This contract is not a general-purpose + // ABI endpoint; accepting partial or shifted frames would make the + // generated memory addresses point at the wrong data. + if iszero(eq(calldatasize(), QUOTIENT_FRAME_LEN)) { revert(0, 0) } + + // Rehydrate the verifier memory image. From this point onward the + // generated Yul can use the same MPTR constants as the monolithic + // verifier path. + calldatacopy(QUOTIENT_FRAME_BASE, 0, QUOTIENT_FRAME_LEN) + + let r := FR_MODULUS + + // This included block is the main body of the evaluator. It: + // 1. evaluates gate/permutation/lookup/trash identities in the + // same order as Rust `partially_evaluate_identities`; + // 2. y-batches fully evaluated identities into + // quotient_eval_numer; + // 3. y-batches simple-selector identities into + // SELECTOR_ACC_MPTR buckets; + // 4. writes -quotient_eval_numer to QUOTIENT_EVAL_MPTR. + // + // Depending on codegen settings, some identities are native Yul + // callbacks and the rest are executed by the compact q_program VM + // stored in the copied VK payload. + // + // The upstream Rust comments call out that simple multiplicative + // selectors do not appear as normal proof eval scalars. The Yul + // block mirrors that rule by accumulating those identities into + // SELECTOR_ACC_MPTR buckets for later multiplication by fixed + // selector commitments, while fully evaluated identities contribute + // to the negated expected scalar. // Optional quotient helper functions. Each one is rendered only + // when the Rust lowering pass recognized the corresponding + // expression shape in this generated verifier. They are pure Fr + // helpers and share the same FR_MODULUS as the surrounding + // numerator block. + // VK-specialized identity helper for Poseidon S-box terms. + // + // Rust source shape: + // circuits/src/hash/poseidon/poseidon_chip.rs::sbox + // full_round_gate / partial_round_gate + // circuits/src/hash/poseidon/round_skips.rs::RoundId + // + // The Rust verifier only sees this as an Expression tree from + // `vk.cs.gates`; the generator emits q_pow5 after recognizing five + // equal multiplicative factors. It is a codegen shortcut for x^5, + // not a separate verifier rule. + function q_pow5(x) -> z { + let q_r := FR_MODULUS + let x2 := mulmod(x, x, q_r) + z := mulmod(x, mulmod(x2, x2, q_r), q_r) + } // =============================================================== + // Batched identity numerator / linearization target. + // + // This block does not evaluate the quotient polynomial h(x), and + // the proof does not provide an h(x) scalar to trust. Instead it: + // + // 1. Reconstructs the y-batched constraint numerator nu_y(x) + // from the alleged polynomial evaluations read after the + // transcript sampled x. + // 2. Stores -nu_y(x) as the expected opening scalar for the + // linearized commitment. + // + // The commitment side is built in the next block from the quotient + // limb commitments as (1 - x^n) * Σ_i x_split^i * Q_i, plus any + // simple-selector commitments. The PCS check later binds that + // linearized commitment to this expected scalar at x. + // + // Rust source-of-truth: + // - verifier.rs reads quotient commitments, samples x, then + // reads/computes all evaluations used below. + // - mod.rs::partially_evaluate_identities returns identities in + // gate, permutation, lookup, trash order. + // - linearization/verifier.rs::compute_linearization_commitment + // reverse-folds those identities by powers of y, sends + // simple-selector identities to selector commitment scalars, + // and subtracts fully-evaluated identities into expected_eval. + // + // This template is shared by the monolithic and external quotient + // paths. In the external path, Halo2QuotientEvaluator first copies + // the verifier memory frame into the same generated addresses. + // + // Runtime inputs expected to exist before this block starts: + // - `r` is the BLS12-381 scalar-field modulus. + // - Y_MPTR holds the quotient batching challenge y. + // - X_MPTR, L_*_MPTR, INSTANCE_EVAL_MPTR, and + // REVERSED_EVALS_MPTR hold values parsed or derived by the + // main verifier after the transcript sampled x. + // - VK_MPTR holds the pinned VK payload; in compact mode that + // payload includes the quotient constant table and bytecode. + // + // Runtime outputs written by this block: + // - QUOTIENT_EVAL_MPTR receives the scalar expected opening for + // the linearized commitment, namely -nu_y(x). + // - SELECTOR_ACC_MPTR[0..num_simple_selectors) receives one + // linearization scalar per generated simple selector. + // + // Line-by-line reading conventions used below: + // + // * Every runtime value is one canonical Fr element stored in a + // 256-bit EVM memory word. The small integer operands decoded + // from q_program are never field values; they are pointers, + // constant-table slots, selector indexes, offsets, or counts. + // + // * `mload(ptr)` is the only way the VM turns a small pointer + // operand into a real 255-bit field element. The value loaded + // from memory is then combined with `addmod(..., r)` or + // `mulmod(..., r)`, so every arithmetic line is reduced modulo + // the BLS12-381 scalar-field order. + // + // * `q_top` is the cached top of the VM operand stack. When an + // opcode needs to push while `q_top` is already live, the old + // value is written to `q_sp` and `q_sp` is advanced by one + // word. Binary `ADD`/`MUL` move `q_sp` back by one word and + // combine that spilled value with `q_top`. + // + // * Identity boundaries are explicit. Expression opcodes leave + // one value in `q_top`; `FOLD_MAIN` or `FOLD_SELECTOR` consumes + // it and advances the global y-batch position. Native callback + // opcodes are only emitted at empty-stack boundaries and run + // generated Yul that performs the same fold side effects. + // + // * The generated Solidity source intentionally emits comments + // before opcode cases. Those comments are documentation only: + // they do not affect bytecode, but they make rendered verifier + // assembly readable without jumping back to Rust codegen. + // =============================================================== + { + // Compact quotient-program mode. + // + // The largest identity expressions are not all emitted as + // unrolled Yul. Instead, most arithmetic is encoded as a small + // q_program bytecode stored in the VK payload. This block + // interprets that program, while selected heavy identities may + // still be emitted as native callbacks for gas. + // + // Compact mode is a code-size trade: short bytecode operands + // name already-planned memory slots, and the interpreter turns + // those names into Fr arithmetic. The opcode stream is fully + // generated and pinned by the VK/runtime codehash; no proof + // calldata can alter control flow. + // Load the quotient batching challenge used by every fold. + let y := mload(Y_MPTR) + + // q_const_mptr points to Fr constants used by the VM. + // q_program_mptr points to the bytecode stream. + // Constants are stored as consecutive 32-byte Fr words. + let q_const_mptr := 0x3a60 + // Program bytes are also stored in the VK payload, packed into + // 32-byte words by PackedProgramCodec. + let q_program_mptr := 0x50a0 + // Running Horner accumulator for fully evaluated identities. + // After all identities, this is nu_y(x) for the `None` + // identity group. + // Initialize A = 0 before scanning the identity stream. + mstore(0xb280, 0) + // Simple selectors are grouped into separate linearization + // buckets. They start at zero for every proof. + // q_sel_zero_off walks selector bucket byte offsets. + for { let q_sel_zero_off := 0 } lt(q_sel_zero_off, 0x0140) { q_sel_zero_off := add(q_sel_zero_off, 0x20) } { + // B_s = 0 for each simple selector bucket. + mstore(add(SELECTOR_ACC_MPTR, q_sel_zero_off), 0) + } + // Codegen knows the selector identity positions. Precompute + // the y^k powers needed for selector gap and tail updates, + // avoiding a runtime y^-1 modexp and per-identity selector + // scale maintenance. + { + // q_y_power holds y^i at the current loop index. + let q_y_power := 1 + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0xb2c0, 1) + // Start at i=1 because y^0 = 1 is written above. + for { let q_y_power_i := 1 } lt(q_y_power_i, 49) { q_y_power_i := add(q_y_power_i, 1) } { + // Advance from y^(i-1) to y^i modulo Fr. + q_y_power := mulmod(q_y_power, y, r) + // Store y^i at selector_power_mptr + 32*i. + mstore(add(0xb2c0, shl(5, q_y_power_i)), q_y_power) + } + } + + // Direct inline prefix. These identities are generated as Yul + // before entering the VM. They use the same fold snippets as + // VM/native identities, so they occupy the same y-batch order. + { + let var0 := 0x1 + let f_3 := mload(0x9ac0) + let f_4 := mload(0x99c0) + let a_0 := mload(0x94a0) + let var1 := mulmod(f_4, a_0, r) + let var2 := addmod(f_3, var1, r) + let f_5 := mload(0x99e0) + let a_1 := mload(0x94c0) + let var3 := mulmod(f_5, a_1, r) + let var4 := addmod(var2, var3, r) + let f_6 := mload(0x9a00) + let a_2 := mload(0x94e0) + let var5 := mulmod(f_6, a_2, r) + let var6 := addmod(var4, var5, r) + let f_7 := mload(0x9a20) + let a_3 := mload(0x9500) + let var7 := mulmod(f_7, a_3, r) + let var8 := addmod(var6, var7, r) + let f_8 := mload(0x9a40) + let a_4 := mload(0x9520) + let var9 := mulmod(f_8, a_4, r) + let var10 := addmod(var8, var9, r) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var11 := mulmod(f_0, a_0_next_1, r) + let var12 := addmod(var10, var11, r) + let f_1 := mload(0x9a80) + let var13 := mulmod(f_1, a_0, r) + let var14 := mulmod(var13, a_1, r) + let var15 := addmod(var12, var14, r) + let f_2 := mload(0x9aa0) + let var16 := mulmod(f_2, a_0, r) + let var17 := mulmod(var16, a_2, r) + let var18 := addmod(var15, var17, r) + let var19 := mulmod(var0, var18, r) + mstore(0xb8e0, var19) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_1 := mload(0x94c0) + let a_2 := mload(0x94e0) + let var1 := addmod(a_1, a_2, r) + let a_3 := mload(0x9500) + let var2 := addmod(0, sub(r, a_3), r) + let var3 := addmod(var1, var2, r) + let a_4 := mload(0x9520) + let var4 := addmod(0, sub(r, a_4), r) + let var5 := addmod(var3, var4, r) + let var6 := mulmod(var0, var5, r) + mstore(0xb8e0, var6) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let f_4 := mload(0x99c0) + let var1 := addmod(a_0, f_4, r) + let a_0_next_1 := mload(0x9540) + let var2 := addmod(0, sub(r, a_0_next_1), r) + let var3 := addmod(var1, var2, r) + let var4 := mulmod(var0, var3, r) + mstore(0xb8e0, var4) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_1 := mload(0x94c0) + let f_5 := mload(0x99e0) + let var1 := addmod(a_1, f_5, r) + let a_1_next_1 := mload(0x9560) + let var2 := addmod(0, sub(r, a_1_next_1), r) + let var3 := addmod(var1, var2, r) + let var4 := mulmod(var0, var3, r) + mstore(0xb8e0, var4) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + + // VM registers: + // q_pc current bytecode pointer + // q_end end of bytecode stream + // q_sp memory stack pointer for non-top stack values + // q_top cached top-of-stack value + // q_has_top whether q_top currently holds a stack value + // + // The cached top reduces memory traffic in the interpreter. + // q_sp's registered range must cover the interpreted operand + // stack plus any native callback scratch that reuses this base + // pointer. In particular, the native permutation callback + // writes a structured scratch table at program.stack_mptr. + // q_pc starts at the first encoded instruction. + let q_pc := q_program_mptr + // q_end is an exclusive byte pointer for the VM loop. + let q_end := add(q_program_mptr, 0x11cf) + // q_sp starts at the first free stack word. + let q_sp := 0xb8e0 + // q_top is meaningless until q_has_top is set. + let q_top := 0 + // q_has_top = 0 means the VM stack is empty. + let q_has_top := 0 + + // q_program opcode summary: + // 0x01/0x09 push const 0x02/0x05 push memory + // 0x03/0x04 push token ptr 0x06 add, 0x07 mul, 0x08 neg + // 0x0a fold main identity 0x0b fold selector identity + // 0x0c..0x11 add/mul const or memory into top + // 0x12..0x16 fused add-mul runs + // 0x17/0x18 reserved + // 0x19 native permutation 0x1b native heavy identity + // 0x1c LIN7 0x1d BILIN7_ROW + // 0x1e BILIN7_PAIRWISE 0x1f native lookup + // 0x20 POW5 0x21 MODARITH7 + // 0x22 AFFINE_SUM + // + // The default IVC verifier uses one physical encoding for the + // logical VM: compact byte-oriented opcodes with variable-width + // operands, dynamic runs, and limb-aware cases. + + // Byte-oriented encoding: opcodes are one byte followed by + // variable-width operand bytes. + for { } lt(q_pc, q_end) { } { + // The bytecode table is byte-addressed, but EVM memory + // loads whole words. `byte(0, mload(q_pc))` extracts the + // opcode at the current byte cursor; each case advances + // q_pc by exactly its operand width. + let q_op := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + + switch q_op + // VM 0x05 PUSH_MEM_U16 (bytes): next two bytes are a short memory pointer. + case 0x05 { + // Operand layout: u16 absolute memory pointer. The + // memory planner keeps the hot quotient frame below + // 64 KiB when this compact form is emitted. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + if q_has_top { + mstore(q_sp, q_top) + q_sp := add(q_sp, 0x20) + } + q_top := mload(q_ptr) + q_has_top := 1 + } + // VM 0x06 ADD: pop one spilled stack word and add it to q_top. + case 0x06 { + // The safety validator guarantees a spilled operand + // exists before ADD. q_top is the right operand. + q_sp := sub(q_sp, 0x20) + q_top := addmod(mload(q_sp), q_top, r) + } + // VM 0x08 NEG: replace q_top with its Fr negation. + case 0x08 { + // addmod(0, r - x, r) maps zero back to zero and every + // nonzero scalar to its canonical additive inverse. + q_top := addmod(0, sub(r, q_top), r) + } + // VM 0x0d MUL_CONST_U8: multiply q_top by a small constant-table slot. + case 0x0d { + // One-byte constant-index multiply, used by short + // affine chains after an initial PUSH. + let qconst := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + q_top := mulmod(q_top, mload(add(q_const_mptr, shl(5, qconst))), r) + } + // VM 0x10 ADD_MEM_U16: add a short memory load into q_top. + case 0x10 { + // Operand layout: u16 pointer. The pointed word is an + // already range-checked Fr scalar in verifier memory. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + q_top := addmod(q_top, mload(q_ptr), r) + } + // VM 0x11 MUL_MEM_U16: multiply q_top by a short memory load. + case 0x11 { + // In-place multiply by a planned memory word. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + q_top := mulmod(q_top, mload(q_ptr), r) + } + // Limb-aware opcodes are opt-in compact forms for + // structurally recognized non-SHA foreign-field shapes. + // Coefficients are indexes into q_const_mptr, which is + // generated from VK/program data, never from proof + // calldata. + // + // Rust source shape: + // proofs/src/plonk/mod.rs::partially_evaluate_identities + // circuits/src/field/foreign/util.rs::{sum_exprs,pair_wise_prod} + // circuits/src/field/foreign/params.rs::{base_powers,double_base_powers} + // + // "Foreign field" means the circuit represents elements + // modulo another modulus m as 7 limbs in base + // 2^LOG2_BASE. The verifier does not switch fields; it + // evaluates the lowered identity over BLS12-381 Fr, using + // Fr coefficients equal to base^i mod m or base^(i+j) mod m. + // VM 0x21 MODARITH7: byte-only fused affine 7-limb foreign-field/ECC identity. + case 0x21 { + // MODARITH7: + // maybe_cond * ( + // c + // + sum LIN7 blocks + // + sum BILIN7_ROW blocks + // + sum BILIN7_PAIRWISE blocks + // + sum coeff[k] * mload(ptr[k]) + // + sum coeff[k] * mload(lhs[k]) * mload(rhs[k]) + // ) + // It is a dispatch/operand-load optimization only; + // all coefficients still come from the generated + // quotient constant table. + // + // Flags: + // bit 0: multiply the final affine sum by a memory + // condition word. + // bit 1: seed q_acc from a constant-table word + // before reading the counted term blocks. + let q_flags := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + let q_cond_ptr := 0 + if and(q_flags, 0x01) { + // Optional condition pointer. When present, the + // whole identity is gated by mload(q_cond_ptr). + q_cond_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + } + + let q_acc := 0 + if and(q_flags, 0x02) { + // Optional constant seed for affine identities + // with a standalone constant term. + let qconst := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + q_acc := mload(add(q_const_mptr, shl(5, qconst))) + } + + // Five one-byte counters describe the blocks that + // follow. Each block has a fixed-width internal layout, + // so q_pc can advance without per-term tags. + let q_counts_word := mload(q_pc) + let q_lin_count := byte(0, q_counts_word) + let q_row_count := byte(1, q_counts_word) + let q_pairwise_count := byte(2, q_counts_word) + let q_mem_count := byte(3, q_counts_word) + let q_product_count := byte(4, q_counts_word) + q_pc := add(q_pc, 5) + + if q_has_top { + mstore(q_sp, q_top) + q_sp := add(q_sp, 0x20) + } + + // LIN7 blocks: q_acc += sum_i c_i * limb_i. + for { let q_lin_block := 0 } lt(q_lin_block, q_lin_count) { q_lin_block := add(q_lin_block, 1) } { + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_ptr := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod(mload(add(q_const_mptr, shl(5, qconst))), mload(q_ptr), r), + r + ) + } + } + + // BILIN7_ROW blocks: q_acc += lhs * sum_i c_i * rhs_i. + for { let q_row_block := 0 } lt(q_row_block, q_row_count) { q_row_block := add(q_row_block, 1) } { + let q_lhs := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + let q_lhs_value := mload(q_lhs) + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_rhs := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod( + mulmod(q_lhs_value, mload(q_rhs), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + } + + // BILIN7_PAIRWISE blocks: q_acc += weighted 7-by-7 + // product convolution. + for { let q_pair_block := 0 } lt(q_pair_block, q_pairwise_count) { q_pair_block := add(q_pair_block, 1) } { + let q_pair_word := mload(q_pc) + let q_lhs_base := shr(240, q_pair_word) + let q_rhs_base := and(shr(224, q_pair_word), 0xffff) + q_pc := add(q_pc, 0x04) + let q_coeff_pc := q_pc + q_pc := add(q_pc, 13) + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_lhs_value := mload(add(q_lhs_base, shl(5, q_i))) + for { let q_j := 0 } lt(q_j, 7) { q_j := add(q_j, 1) } { + let qconst := byte(0, mload(add(q_coeff_pc, add(q_i, q_j)))) + q_acc := addmod( + q_acc, + mulmod( + mulmod(q_lhs_value, mload(add(q_rhs_base, shl(5, q_j))), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + } + } + + // Extra linear memory terms outside the 7-limb shapes. + for { let q_mem_block := 0 } lt(q_mem_block, q_mem_count) { q_mem_block := add(q_mem_block, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_ptr := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod(mload(add(q_const_mptr, shl(5, qconst))), mload(q_ptr), r), + r + ) + } + + // Extra binary product terms outside the 7-limb shapes. + for { let q_product_block := 0 } lt(q_product_block, q_product_count) { q_product_block := add(q_product_block, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_lhs := and(shr(232, q_word), 0xffff) + let q_rhs := and(shr(216, q_word), 0xffff) + q_pc := add(q_pc, 5) + q_acc := addmod( + q_acc, + mulmod( + mulmod(mload(q_lhs), mload(q_rhs), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + + if and(q_flags, 0x01) { + // Apply the optional gate condition last so every + // subterm shares the same selector/condition. + q_acc := mulmod(mload(q_cond_ptr), q_acc, r) + } + // MODARITH7 pushes its fused identity value. + q_top := q_acc + q_has_top := 1 + } + // Native permutation callback. It evaluates the + // permutation identities from permutation.rs at this exact + // VM position, preserving the Rust identity order while + // avoiding a large interpreted product loop. + // VM 0x19 NATIVE_PERMUTATION: marker for the generated permutation callback. + case 0x19 { + // Native callbacks are identity-boundary opcodes. They + // must not inherit any partially evaluated VM stack + // state from the previous expression. + q_top := 0 + q_has_top := 0 + // The generated loop below uses program.stack_mptr as + // its scratch-table base, not as a conventional VM + // stack. The Rust memory planner must reserve enough + // words for structured_permutation_scratch_words(meta) + // whenever this opcode can appear. + q_sp := 0xb8e0 + // The generated lines below call the same fold snippets + // used by interpreted expressions, so trace IDs and + // y-batch positions remain contiguous. + { + let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 + let q_perm_vals := 0xb8e0 + let q_perm_sigmas := 0xbb20 + let q_perm_z_cur := 0xbd60 + let q_perm_z_next := 0xbe20 + let q_perm_z_last := 0xbee0 + let q_perm_delta_base_ptr := 0xbf80 + let q_perm_num_cols := 18 + let q_perm_num_sets := 6 + let q_perm_chunk_len := 3 + let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 + mstore(add(q_perm_vals, 0x0), mload(0x99a0)) + { + for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { + let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) + let q_perm_val_load_src_off := q_perm_val_load_dst_off + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x94a0, q_perm_val_load_src_off))) + } + } + mstore(add(q_perm_vals, 0xc0), mload(0x9480)) + mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) + { + for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 9) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { + let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) + let q_perm_val_load_src_off := q_perm_val_load_dst_off + mstore(add(add(q_perm_vals, 0x100), q_perm_val_load_dst_off), mload(add(0x95a0, q_perm_val_load_src_off))) + } + } + mstore(add(q_perm_vals, 0x220), mload(0x9980)) + { + for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 18) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { + let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) + let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x9bc0, q_perm_sigma_load_src_off))) + } + } + { + for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 6) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { + let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) + let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x9e00, q_perm_z_cur_load_src_off))) + } + } + { + for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 6) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { + let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) + let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x9e20, q_perm_z_next_load_src_off))) + } + } + { + for { let q_perm_z_last_load_i := 0 } lt(q_perm_z_last_load_i, 5) { q_perm_z_last_load_i := add(q_perm_z_last_load_i, 1) } { + let q_perm_z_last_load_dst_off := shl(5, q_perm_z_last_load_i) + let q_perm_z_last_load_src_off := mul(q_perm_z_last_load_i, 0x60) + mstore(add(add(q_perm_z_last, 0x0), q_perm_z_last_load_dst_off), mload(add(0x9e40, q_perm_z_last_load_src_off))) + } + } + let q_perm_eval := 0 + q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + let q_perm_zn := mload(add(q_perm_z_cur, 0xa0)) + q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + for { let q_perm_i := 1 } lt(q_perm_i, 6) { q_perm_i := add(q_perm_i, 1) } { + let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) + let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) + q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + } + mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) + for { let q_perm_set := 0 } lt(q_perm_set, 6) { q_perm_set := add(q_perm_set, 1) } { + let q_perm_start := mul(q_perm_set, q_perm_chunk_len) + let q_perm_end := add(q_perm_start, q_perm_chunk_len) + if gt(q_perm_end, q_perm_num_cols) { q_perm_end := q_perm_num_cols } + let q_perm_left := mload(add(q_perm_z_next, shl(5, q_perm_set))) + let q_perm_right := mload(add(q_perm_z_cur, shl(5, q_perm_set))) + let q_perm_delta_pow := mload(q_perm_delta_base_ptr) + for { let q_perm_j := q_perm_start } lt(q_perm_j, q_perm_end) { q_perm_j := add(q_perm_j, 1) } { + let q_perm_off := shl(5, q_perm_j) + let q_perm_v := mload(add(q_perm_vals, q_perm_off)) + let q_perm_s := mload(add(q_perm_sigmas, q_perm_off)) + q_perm_left := mulmod(q_perm_left, addmod(addmod(q_perm_v, mulmod(mload(BETA_MPTR), q_perm_s, r), r), mload(GAMMA_MPTR), r), r) + q_perm_right := mulmod(q_perm_right, addmod(addmod(q_perm_v, q_perm_delta_pow, r), mload(GAMMA_MPTR), r), r) + q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) + } + q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) + } + } + } + // Native lookup callback. This whole-family opcode + // evaluates the LogUp boundary, helper-chunk, and + // accumulator identities at this VM position, preserving + // the Rust y-batch order while avoiding many interpreted + // product-loop opcodes. + // VM 0x1f NATIVE_LOOKUP: marker for the generated LogUp lookup callback. + case 0x1f { + // Reset VM stack state before entering structured + // lookup Yul. Lookup callbacks own their scratch + // layout and perform all needed folds internally. + q_top := 0 + q_has_top := 0 + // The generated loop below uses program.stack_mptr as + // f+beta/prefix/suffix scratch rather than as a + // conventional VM stack. The Rust memory planner must + // reserve structured_lookup_scratch_words(meta). + q_sp := 0xb8e0 + // Generated LogUp code follows the same y-batch order + // as the Rust identity stream. + { + let q_lookup_f := 0xb8e0 + let q_lookup_prefix := 0xb960 + let q_lookup_suffix := 0xb9e0 + let q_lookup_l0 := mload(L_0_MPTR) + let q_lookup_llast := mload(L_LAST_MPTR) + let q_lookup_lblind := mload(L_BLIND_MPTR) + let q_lookup_lsum := addmod(q_lookup_l0, q_lookup_llast, r) + let q_lookup_active := addmod(1, sub(r, addmod(q_lookup_llast, q_lookup_lblind, r)), r) + let q_lookup_beta := mload(BETA_MPTR) + let q_lookup_theta := mload(THETA_MPTR) + { + { + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa060), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let f_10 := mload(0x9ae0) + let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) + let var1 := mulmod(var0, q_lookup_theta, r) + for { let q_lookup_shared_i := 0 } lt(q_lookup_shared_i, 4) { q_lookup_shared_i := add(q_lookup_shared_i, 1) } { + let q_lookup_shared_off := shl(5, q_lookup_shared_i) + let q_lookup_shared_tail := mload(add(0x94c0, q_lookup_shared_off)) + let q_lookup_shared_compressed := addmod(var1, q_lookup_shared_tail, r) + mstore(add(q_lookup_f, q_lookup_shared_off), addmod(q_lookup_shared_compressed, q_lookup_beta, r)) + } + let q_lookup_product := 1 + for { let q_lookup_prod_i := 0 } lt(q_lookup_prod_i, 4) { q_lookup_prod_i := add(q_lookup_prod_i, 1) } { + q_lookup_product := mulmod(q_lookup_product, mload(add(q_lookup_f, shl(5, q_lookup_prod_i))), r) + } + mstore(q_lookup_prefix, 1) + for { let q_lookup_pref_i := 1 } lt(q_lookup_pref_i, 4) { q_lookup_pref_i := add(q_lookup_pref_i, 1) } { + let q_lookup_pref_prev := sub(q_lookup_pref_i, 1) + mstore(add(q_lookup_prefix, shl(5, q_lookup_pref_i)), mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_pref_prev))), mload(add(q_lookup_f, shl(5, q_lookup_pref_prev))), r)) + } + mstore(add(q_lookup_suffix, 0x60), 1) + for { let q_lookup_suf_i := sub(4, 1) } gt(q_lookup_suf_i, 0) { q_lookup_suf_i := sub(q_lookup_suf_i, 1) } { + let q_lookup_suf_prev := sub(q_lookup_suf_i, 1) + mstore(add(q_lookup_suffix, shl(5, q_lookup_suf_prev)), mulmod(mload(add(q_lookup_suffix, shl(5, q_lookup_suf_i))), mload(add(q_lookup_f, shl(5, q_lookup_suf_i))), r)) + } + let q_lookup_sum := 0 + for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 4) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { + q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) + } + let q_lookup_eval := addmod(mulmod(mload(0xa040), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let q_lookup_sum_h := mload(0xa040) + let f_17 := mload(0x9b60) + let f_11 := mload(0x9b00) + let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) + let f_12 := mload(0x9b20) + let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) + let q_lookup_s_sum_h := mulmod(f_17, q_lookup_sum_h, r) + let q_lookup_diff := addmod(mload(0xa080), sub(r, addmod(mload(0xa060), q_lookup_s_sum_h, r)), r) + let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa020), r) + let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + } + { + { + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa0e0), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let a_14 := mload(0x9980) + let var0 := addmod(mulmod(0, q_lookup_theta, r), a_14, r) + let a_0 := mload(0x94a0) + let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_0, r) + let a_1 := mload(0x94c0) + let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_1, r) + let a_2 := mload(0x94e0) + let var3 := addmod(mulmod(var2, q_lookup_theta, r), a_2, r) + let a_3 := mload(0x9500) + let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_3, r) + let a_4 := mload(0x9520) + let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_4, r) + let a_5 := mload(0x95a0) + let var6 := addmod(mulmod(var5, q_lookup_theta, r), a_5, r) + let a_6 := mload(0x95c0) + let var7 := addmod(mulmod(var6, q_lookup_theta, r), a_6, r) + let a_7 := mload(0x95e0) + let var8 := addmod(mulmod(var7, q_lookup_theta, r), a_7, r) + let a_8 := mload(0x9600) + let var9 := addmod(mulmod(var8, q_lookup_theta, r), a_8, r) + let a_9 := mload(0x9620) + let var10 := addmod(mulmod(var9, q_lookup_theta, r), a_9, r) + let a_10 := mload(0x9640) + let var11 := addmod(mulmod(var10, q_lookup_theta, r), a_10, r) + let a_11 := mload(0x9660) + let var12 := addmod(mulmod(var11, q_lookup_theta, r), a_11, r) + let a_12 := mload(0x9680) + let var13 := addmod(mulmod(var12, q_lookup_theta, r), a_12, r) + let a_13 := mload(0x96a0) + let var14 := addmod(mulmod(var13, q_lookup_theta, r), a_13, r) + let f_13 := mload(0x9b40) + let var15 := addmod(mulmod(var14, q_lookup_theta, r), f_13, r) + let q_lookup_eval := addmod(mulmod(mload(0xa0c0), addmod(var15, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let q_lookup_sum_h := mload(0xa0c0) + let var0 := 0x1 + let f_26 := mload(0x9ba0) + let var1 := addmod(0, sub(r, f_26), r) + let var2 := addmod(var0, var1, r) + let a_14 := mload(0x9980) + let var3 := mulmod(var2, a_14, r) + let var4 := addmod(mulmod(0, q_lookup_theta, r), var3, r) + let a_0 := mload(0x94a0) + let var5 := mulmod(var2, a_0, r) + let var6 := addmod(mulmod(var4, q_lookup_theta, r), var5, r) + let a_1 := mload(0x94c0) + let var7 := mulmod(var2, a_1, r) + let var8 := addmod(mulmod(var6, q_lookup_theta, r), var7, r) + let a_2 := mload(0x94e0) + let var9 := mulmod(var2, a_2, r) + let var10 := addmod(mulmod(var8, q_lookup_theta, r), var9, r) + let a_3 := mload(0x9500) + let var11 := mulmod(var2, a_3, r) + let var12 := addmod(mulmod(var10, q_lookup_theta, r), var11, r) + let a_4 := mload(0x9520) + let var13 := mulmod(var2, a_4, r) + let var14 := addmod(mulmod(var12, q_lookup_theta, r), var13, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var2, a_5, r) + let var16 := addmod(mulmod(var14, q_lookup_theta, r), var15, r) + let a_6 := mload(0x95c0) + let var17 := mulmod(var2, a_6, r) + let var18 := addmod(mulmod(var16, q_lookup_theta, r), var17, r) + let a_7 := mload(0x95e0) + let var19 := mulmod(var2, a_7, r) + let var20 := addmod(mulmod(var18, q_lookup_theta, r), var19, r) + let a_8 := mload(0x9600) + let var21 := mulmod(var2, a_8, r) + let var22 := addmod(mulmod(var20, q_lookup_theta, r), var21, r) + let a_9 := mload(0x9620) + let var23 := mulmod(var2, a_9, r) + let var24 := addmod(mulmod(var22, q_lookup_theta, r), var23, r) + let a_10 := mload(0x9640) + let var25 := mulmod(var2, a_10, r) + let var26 := addmod(mulmod(var24, q_lookup_theta, r), var25, r) + let a_11 := mload(0x9660) + let var27 := mulmod(var2, a_11, r) + let var28 := addmod(mulmod(var26, q_lookup_theta, r), var27, r) + let a_12 := mload(0x9680) + let var29 := mulmod(var2, a_12, r) + let var30 := addmod(mulmod(var28, q_lookup_theta, r), var29, r) + let a_13 := mload(0x96a0) + let var31 := mulmod(var2, a_13, r) + let var32 := addmod(mulmod(var30, q_lookup_theta, r), var31, r) + let f_13 := mload(0x9b40) + let var33 := mulmod(var2, f_13, r) + let var34 := addmod(mulmod(var32, q_lookup_theta, r), var33, r) + let q_lookup_s_sum_h := mulmod(var0, q_lookup_sum_h, r) + let q_lookup_diff := addmod(mload(0xa100), sub(r, addmod(mload(0xa0e0), q_lookup_s_sum_h, r)), r) + let q_lookup_t_beta := addmod(var34, q_lookup_beta, r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa0a0), r) + let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + } + } + } + // Native callbacks are generated only for the heaviest + // recognized Midfall gate identities. All other gate and + // non-native identity arithmetic remains in + // the compact q_program VM above, preserving the Rust + // `partially_evaluate_identities` order. + // VM 0x1b NATIVE_IDENTITY: marker for generated heavy-gate callbacks. + case 0x1b { + // Operand layout: u16 native callback index. The + // manifest validates that callback indexes appear in + // generated order and target existing switch cases. + let q_native_idx := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + // Heavy identities are whole expressions, so clear the + // interpreter stack before dispatching. + q_top := 0 + q_has_top := 0 + q_sp := 0xb8e0 + // Native identity sub-cases are generated from selected heavy gate identities. + switch q_native_idx + case 0 { + { + let var0 := 0x1 + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var1 := addmod(0, sub(r, a_0_next_1), r) + let var2 := addmod(f_0, var1, r) + let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace + let a_0 := mload(0x94a0) + let var4 := mulmod(a_0, a_0, r) + let a_3 := mload(0x9500) + let var5 := mulmod(var4, a_3, r) + let var6 := mulmod(var3, var5, r) + let var7 := addmod(var2, var6, r) + let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 + let a_1 := mload(0x94c0) + let var9 := mulmod(a_1, a_1, r) + let a_4 := mload(0x9520) + let var10 := mulmod(var9, a_4, r) + let var11 := mulmod(var8, var10, r) + let var12 := addmod(var7, var11, r) + let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 + let a_2 := mload(0x94e0) + let var14 := mulmod(a_2, a_2, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var14, a_5, r) + let var16 := mulmod(var13, var15, r) + let var17 := addmod(var12, var16, r) + let var18 := mulmod(var0, var17, r) + mstore(0xb8e0, var18) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 1 { + { + let var0 := 0x1 + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) + let var1 := addmod(0, sub(r, a_1_next_1), r) + let var2 := addmod(f_1, var1, r) + let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 + let a_0 := mload(0x94a0) + let var4 := mulmod(a_0, a_0, r) + let a_3 := mload(0x9500) + let var5 := mulmod(var4, a_3, r) + let var6 := mulmod(var3, var5, r) + let var7 := addmod(var2, var6, r) + let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 + let a_1 := mload(0x94c0) + let var9 := mulmod(a_1, a_1, r) + let a_4 := mload(0x9520) + let var10 := mulmod(var9, a_4, r) + let var11 := mulmod(var8, var10, r) + let var12 := addmod(var7, var11, r) + let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f + let a_2 := mload(0x94e0) + let var14 := mulmod(a_2, a_2, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var14, a_5, r) + let var16 := mulmod(var13, var15, r) + let var17 := addmod(var12, var16, r) + let var18 := mulmod(var0, var17, r) + mstore(0xb8e0, var18) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 2 { + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let a_0_next_1 := mload(0x9540) + let var1 := mulmod(a_0, a_0_next_1, r) + let var2 := 0x100000000000000 + let a_1_next_1 := mload(0x9560) + let var3 := mulmod(a_0, a_1_next_1, r) + let var4 := mulmod(var2, var3, r) + let var5 := addmod(var1, var4, r) + let var6 := 0x10000000000000000000000000000 + let a_2_next_1 := mload(0x9580) + let var7 := mulmod(a_0, a_2_next_1, r) + let var8 := mulmod(var6, var7, r) + let var9 := addmod(var5, var8, r) + let a_1 := mload(0x94c0) + let var10 := mulmod(a_1, a_0_next_1, r) + let var11 := mulmod(var2, var10, r) + let var12 := addmod(var9, var11, r) + let var13 := mulmod(a_1, a_1_next_1, r) + let var14 := mulmod(var6, var13, r) + let var15 := addmod(var12, var14, r) + let var16 := 0x3212e00cde6d2002b119d800000347fcb8 + let a_6_next_1 := mload(0x9720) + let var17 := mulmod(a_1, a_6_next_1, r) + let var18 := mulmod(var16, var17, r) + let var19 := addmod(var15, var18, r) + let a_2 := mload(0x94e0) + let var20 := mulmod(a_2, a_0_next_1, r) + let var21 := mulmod(var6, var20, r) + let var22 := addmod(var19, var21, r) + let a_5_next_1 := mload(0x9700) + let var23 := mulmod(a_2, a_5_next_1, r) + let var24 := mulmod(var16, var23, r) + let var25 := addmod(var22, var24, r) + let var26 := 0x297784894e27525bc342b7fde37dba9366 + let var27 := mulmod(a_2, a_6_next_1, r) + let var28 := mulmod(var26, var27, r) + let var29 := addmod(var25, var28, r) + let a_3 := mload(0x9500) + let a_4_next_1 := mload(0x96e0) + let var30 := mulmod(a_3, a_4_next_1, r) + let var31 := mulmod(var16, var30, r) + let var32 := addmod(var29, var31, r) + let var33 := mulmod(a_3, a_5_next_1, r) + let var34 := mulmod(var26, var33, r) + let var35 := addmod(var32, var34, r) + let var36 := 0x340f2ebe380a0f5eff4360543988a61dc2 + let var37 := mulmod(a_3, a_6_next_1, r) + let var38 := mulmod(var36, var37, r) + let var39 := addmod(var35, var38, r) + let a_4 := mload(0x9520) + let a_3_next_1 := mload(0x96c0) + let var40 := mulmod(a_4, a_3_next_1, r) + let var41 := mulmod(var16, var40, r) + let var42 := addmod(var39, var41, r) + let var43 := mulmod(a_4, a_4_next_1, r) + let var44 := mulmod(var26, var43, r) + let var45 := addmod(var42, var44, r) + let var46 := mulmod(a_4, a_5_next_1, r) + let var47 := mulmod(var36, var46, r) + let var48 := addmod(var45, var47, r) + let var49 := 0x13af65741744bd7bb2c6872df2b800320 + let var50 := mulmod(a_4, a_6_next_1, r) + let var51 := mulmod(var49, var50, r) + let var52 := addmod(var48, var51, r) + let a_5 := mload(0x95a0) + let var53 := mulmod(a_5, a_2_next_1, r) + let var54 := mulmod(var16, var53, r) + let var55 := addmod(var52, var54, r) + let var56 := mulmod(a_5, a_3_next_1, r) + let var57 := mulmod(var26, var56, r) + let var58 := addmod(var55, var57, r) + let var59 := mulmod(a_5, a_4_next_1, r) + let var60 := mulmod(var36, var59, r) + let var61 := addmod(var58, var60, r) + let var62 := mulmod(a_5, a_5_next_1, r) + let var63 := mulmod(var49, var62, r) + let var64 := addmod(var61, var63, r) + let var65 := 0x2cb9b546d20373eaf85e8f53db883cb548 + let var66 := mulmod(a_5, a_6_next_1, r) + let var67 := mulmod(var65, var66, r) + let var68 := addmod(var64, var67, r) + let a_6 := mload(0x95c0) + let var69 := mulmod(a_6, a_1_next_1, r) + let var70 := mulmod(var16, var69, r) + let var71 := addmod(var68, var70, r) + let var72 := mulmod(a_6, a_2_next_1, r) + let var73 := mulmod(var26, var72, r) + let var74 := addmod(var71, var73, r) + let var75 := mulmod(a_6, a_3_next_1, r) + let var76 := mulmod(var36, var75, r) + let var77 := addmod(var74, var76, r) + let var78 := mulmod(a_6, a_4_next_1, r) + let var79 := mulmod(var49, var78, r) + let var80 := addmod(var77, var79, r) + let var81 := mulmod(a_6, a_5_next_1, r) + let var82 := mulmod(var65, var81, r) + let var83 := addmod(var80, var82, r) + let var84 := 0xc8557e86f90d0d89eed6eb5349a0f8820 + let var85 := mulmod(a_6, a_6_next_1, r) + let var86 := mulmod(var84, var85, r) + let var87 := addmod(var83, var86, r) + let var88 := mulmod(var2, a_1, r) + let var89 := addmod(a_0, var88, r) + let var90 := mulmod(var6, a_2, r) + let var91 := addmod(var89, var90, r) + let var92 := addmod(var87, var91, r) + let var93 := mulmod(var2, a_1_next_1, r) + let var94 := addmod(a_0_next_1, var93, r) + let var95 := mulmod(var6, a_2_next_1, r) + let var96 := addmod(var94, var95, r) + let var97 := addmod(var92, var96, r) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) + let var98 := mulmod(var2, a_8, r) + let var99 := addmod(a_7, var98, r) + let a_9 := mload(0x9620) + let var100 := mulmod(var6, a_9, r) + let var101 := addmod(var99, var100, r) + let var102 := addmod(0, sub(r, var101), r) + let var103 := addmod(var97, var102, r) + let a_7_next_1 := mload(0x9740) + let var104 := 0x241eabfffeb153ffffb9feffffffffaaab + let var105 := mulmod(a_7_next_1, var104, r) + let var106 := addmod(0, sub(r, var105), r) + let var107 := addmod(var103, var106, r) + let var108 := addmod(0, sub(r, var16), r) + let var109 := addmod(var107, var108, r) + let a_8_next_1 := mload(0x9760) + let var110 := 0x73eda753299d7d483339d80809a1d80553b9202d7ffe85d4800008bb20000001 + let var111 := addmod(a_8_next_1, var110, r) + let var112 := 0x4000000000000000000000000000000000 + let var113 := mulmod(var111, var112, r) + let var114 := addmod(0, sub(r, var113), r) + let var115 := addmod(var109, var114, r) + let var116 := mulmod(var0, var115, r) + mstore(0xb8e0, var116) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x80) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 3 { + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let var1 := 0x10000000000000000000000000000 + let var2 := addmod(a_0, var1, r) + let var3 := 0x100000000000000 + let a_1 := mload(0x94c0) + let var4 := addmod(a_1, var1, r) + let var5 := mulmod(var3, var4, r) + let var6 := addmod(var2, var5, r) + let a_2 := mload(0x94e0) + let var7 := addmod(a_2, var1, r) + let var8 := mulmod(var1, var7, r) + let var9 := addmod(var6, var8, r) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) + let var10 := mulmod(var3, a_8, r) + let var11 := addmod(a_7, var10, r) + let a_9 := mload(0x9620) + let var12 := mulmod(var1, a_9, r) + let var13 := addmod(var11, var12, r) + let var14 := addmod(0, sub(r, var13), r) + let var15 := addmod(var9, var14, r) + let var16 := addmod(0, sub(r, var1), r) + let var17 := addmod(var15, var16, r) + let a_7_next_1 := mload(0x9740) + let var18 := 0x241eabfffeb153ffffb9feffffffffaaab + let var19 := mulmod(a_7_next_1, var18, r) + let var20 := addmod(0, sub(r, var19), r) + let var21 := addmod(var17, var20, r) + let var22 := 0xd9d44a30b019261257667fde3844a8cd6 + let var23 := addmod(0, sub(r, var22), r) + let var24 := addmod(var21, var23, r) + let a_8_next_1 := mload(0x9760) + let var25 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5b6e855000003ab00002 + let var26 := addmod(a_8_next_1, var25, r) + let var27 := 0x4000000000000000000000000000000000 + let var28 := mulmod(var26, var27, r) + let var29 := addmod(0, sub(r, var28), r) + let var30 := addmod(var24, var29, r) + let var31 := mulmod(var0, var30, r) + mstore(0xb8e0, var31) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xa0) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + default { revert(0, 0) } + } + // VM 0x0b FOLD_SELECTOR: consume q_top into one simple-selector bucket. + case 0x0b { + // Operand layout packed into three bytes: + // high byte: selector bucket index; + // low u16 : y-power gap since this selector's + // previous contribution. + let q_selector_payload := shr(232, mload(q_pc)) + q_pc := add(q_pc, 3) + let q_sel_idx := shr(16, q_selector_payload) + let q_sel_gap := and(q_selector_payload, 0xffff) + let q_eval := q_top + q_has_top := 0 + // Simple-selector identity: keep the same y-batch + // position as main identities, then advance only this + // selector bucket by its codegen-known gap. + // + // The global fully-evaluated accumulator is still + // multiplied by y so later main identities land at the + // same y powers as Rust's reverse fold. + mstore(0xb280, mulmod(mload(0xb280), y, r)) + let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) + let q_sel_acc := mload(q_target_ptr) + if q_sel_gap { + // Selector buckets are sparse in the global + // identity stream. Precomputed y^gap advances only + // this selector's local accumulator. + q_sel_acc := mulmod(q_sel_acc, mload(add(0xb2c0, shl(5, q_sel_gap))), r) + } + mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) + } + // Invalid generated bytecode should fail closed. 0x1a intentionally lands here. + default { + revert(0, 0) + } + } + // The VK-pinned bytecode must end exactly at q_end and every + // identity must have been consumed by a fold/native callback. + // This catches malformed generator output whose final opcode + // over-reads operands or leaves a partial expression live. + if iszero(eq(q_pc, q_end)) { revert(0, 0) } + if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0xb8e0)) { revert(0, 0) } + + // Structured post-VM suffix. The current default uses this for + // regular trash constraints: it is smaller than fully unrolled + // Yul and cheaper than interpreting every trash operation. + // + // These generated blocks run after q_pc reaches q_end, but + // they still participate in the same identity order and write + // into the same numerator / selector accumulators. + { + let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) + { + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 + let var1 := mulmod(a_0_next_1, var0, r) + let var2 := addmod(f_0, var1, r) + let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 + let a_0 := mload(0x94a0) + let var4 := mulmod(var3, a_0, r) + let var5 := addmod(var2, var4, r) + let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f + let a_1 := mload(0x94c0) + let var7 := mulmod(var6, a_1, r) + let var8 := addmod(var5, var7, r) + let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 + let a_2 := mload(0x94e0) + let var10 := q_pow5(a_2) + let var11 := mulmod(var9, var10, r) + let var12 := addmod(var8, var11, r) + let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 + let a_3 := mload(0x9500) + let var14 := q_pow5(a_3) + let var15 := mulmod(var13, var14, r) + let var16 := addmod(var12, var15, r) + let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 + let a_4 := mload(0x9520) + let var18 := q_pow5(a_4) + let var19 := mulmod(var17, var18, r) + let var20 := addmod(var16, var19, r) + let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e + let a_5 := mload(0x95a0) + let var22 := q_pow5(a_5) + let var23 := mulmod(var21, var22, r) + let var24 := addmod(var20, var23, r) + let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 + let a_6 := mload(0x95c0) + let var26 := q_pow5(a_6) + let var27 := mulmod(var25, var26, r) + let var28 := addmod(var24, var27, r) + let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 + let a_7 := mload(0x95e0) + let var30 := q_pow5(a_7) + let var31 := mulmod(var29, var30, r) + let var32 := addmod(var28, var31, r) + let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) + let var34 := mulmod(a_1_next_1, var0, r) + let var35 := addmod(f_1, var34, r) + let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d + let var37 := mulmod(var36, a_0, r) + let var38 := addmod(var35, var37, r) + let var39 := 0x4d0ea7f9c3fda06d9535b0fdafd8338bd47c2200b284fa71a325ff41ac358028 + let var40 := mulmod(var39, a_1, r) + let var41 := addmod(var38, var40, r) + let var42 := 0x26cc223e16f47c20e17cc6069605fa5a8af05ea4f6eb36029a641d23b818eb10 + let var43 := mulmod(var42, var10, r) + let var44 := addmod(var41, var43, r) + let var45 := 0x31e823a45e567484c1544e310c0fa5cd66547a8f0dde659ac61698c30e838d25 + let var46 := mulmod(var45, var14, r) + let var47 := addmod(var44, var46, r) + let var48 := 0x275a20361ea91992193920270d3e2d1f6361880ac0a439c64bef815d4469ba85 + let var49 := mulmod(var48, var18, r) + let var50 := addmod(var47, var49, r) + let var51 := 0x5f3a15bab4ce4097b1edc3a25002694b92395ce355a8a12fe557459d9633f701 + let var52 := mulmod(var51, var22, r) + let var53 := addmod(var50, var52, r) + let var54 := 0x301cf56f9b4577112cc4241cddf6484aaadedbf1bbd0f2351adf2e41c2fb2ecd + let var55 := mulmod(var54, var26, r) + let var56 := addmod(var53, var55, r) + let var57 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f + let var58 := mulmod(var57, var30, r) + let var59 := addmod(var56, var58, r) + let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) + let f_2 := mload(0x9aa0) + let var61 := mulmod(a_3, var0, r) + let var62 := addmod(f_2, var61, r) + let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 + let var64 := mulmod(var63, a_0, r) + let var65 := addmod(var62, var64, r) + let var66 := 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a + let var67 := mulmod(var66, a_1, r) + let var68 := addmod(var65, var67, r) + let var69 := 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db + let var70 := mulmod(var69, var10, r) + let var71 := addmod(var68, var70, r) + let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) + let f_3 := mload(0x9ac0) + let var73 := mulmod(a_4, var0, r) + let var74 := addmod(f_3, var73, r) + let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 + let var76 := mulmod(var75, a_0, r) + let var77 := addmod(var74, var76, r) + let var78 := 0x26c2cc87f95726b28f33ca03409a460ec987cfe12adae32769e3565865d07191 + let var79 := mulmod(var78, a_1, r) + let var80 := addmod(var77, var79, r) + let var81 := 0x4382d0938a760120dd6cef8f3b90a0c38abae475e3d21e39365472b76d780272 + let var82 := mulmod(var81, var10, r) + let var83 := addmod(var80, var82, r) + let var84 := mulmod(var69, var14, r) + let var85 := addmod(var83, var84, r) + let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) + let f_4 := mload(0x99c0) + let var87 := mulmod(a_5, var0, r) + let var88 := addmod(f_4, var87, r) + let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b + let var90 := mulmod(var89, a_0, r) + let var91 := addmod(var88, var90, r) + let var92 := 0x24822e1af9aa2887c912c87eb0f20bd332330e7e55cd784de67cb407a9f05520 + let var93 := mulmod(var92, a_1, r) + let var94 := addmod(var91, var93, r) + let var95 := 0x4e5280109d8f96b8bfb543a6b1af25fb56a9db616af85a90eedc558e3eb1ea29 + let var96 := mulmod(var95, var10, r) + let var97 := addmod(var94, var96, r) + let var98 := mulmod(var81, var14, r) + let var99 := addmod(var97, var98, r) + let var100 := mulmod(var69, var18, r) + let var101 := addmod(var99, var100, r) + let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) + let f_5 := mload(0x99e0) + let var103 := mulmod(a_6, var0, r) + let var104 := addmod(f_5, var103, r) + let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 + let var106 := mulmod(var105, a_0, r) + let var107 := addmod(var104, var106, r) + let var108 := 0x23a6684b942d726a22e4d5b8d8ff83aeaa773f62600184efe5d033d7c7c6e827 + let var109 := mulmod(var108, a_1, r) + let var110 := addmod(var107, var109, r) + let var111 := 0x1981b4b33d6a9dab957b351d981d3323e65da39493af5bc01f7e8ffe17f98d4e + let var112 := mulmod(var111, var10, r) + let var113 := addmod(var110, var112, r) + let var114 := mulmod(var95, var14, r) + let var115 := addmod(var113, var114, r) + let var116 := mulmod(var81, var18, r) + let var117 := addmod(var115, var116, r) + let var118 := mulmod(var69, var22, r) + let var119 := addmod(var117, var118, r) + let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) + let f_6 := mload(0x9a00) + let var121 := mulmod(a_7, var0, r) + let var122 := addmod(f_6, var121, r) + let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a + let var124 := mulmod(var123, a_0, r) + let var125 := addmod(var122, var124, r) + let var126 := 0x27e7119226c42a6d19c1541904b99ae40685511ed2e078964b74594d38340849 + let var127 := mulmod(var126, a_1, r) + let var128 := addmod(var125, var127, r) + let var129 := 0xd94c46a8456352aa44d7a885ab59e3a36664e6fb25e826f8a4cd79822f0533 + let var130 := mulmod(var129, var10, r) + let var131 := addmod(var128, var130, r) + let var132 := mulmod(var111, var14, r) + let var133 := addmod(var131, var132, r) + let var134 := mulmod(var95, var18, r) + let var135 := addmod(var133, var134, r) + let var136 := mulmod(var81, var22, r) + let var137 := addmod(var135, var136, r) + let var138 := mulmod(var69, var26, r) + let var139 := addmod(var137, var138, r) + let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) + let f_7 := mload(0x9a20) + let a_2_next_1 := mload(0x9580) + let var141 := mulmod(a_2_next_1, var0, r) + let var142 := addmod(f_7, var141, r) + let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 + let var144 := mulmod(var143, a_0, r) + let var145 := addmod(var142, var144, r) + let var146 := 0x40fa389feb2522bb934881ac9ed749aee2296502af592418c6b5675c0f560261 + let var147 := mulmod(var146, a_1, r) + let var148 := addmod(var145, var147, r) + let var149 := 0x1f61345b652161410c5e29f51e301ae56342af824bc110649393d2b911c50d3e + let var150 := mulmod(var149, var10, r) + let var151 := addmod(var148, var150, r) + let var152 := mulmod(var129, var14, r) + let var153 := addmod(var151, var152, r) + let var154 := mulmod(var111, var18, r) + let var155 := addmod(var153, var154, r) + let var156 := mulmod(var95, var22, r) + let var157 := addmod(var155, var156, r) + let var158 := mulmod(var81, var26, r) + let var159 := addmod(var157, var158, r) + let var160 := mulmod(var69, var30, r) + let var161 := addmod(var159, var160, r) + let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) + let f_19 := mload(0x9b80) + let q_trash_one_minus_selector := addmod(1, sub(r, f_19), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0xa120), r) + let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_trash_eval, r)) + } + } + // Finish selector buckets by applying the codegen-known tail + // from each selector's last identity to the end of the global + // y-batch. + // + // After this step, every selector bucket is aligned with the + // final global y position and can be multiplied by its fixed + // selector commitment in the linearized MSM. + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0600)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x05e0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0580)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x04c0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0460)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0400)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x03a0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0340)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x02e0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0280)), r)) + } + + // Fully evaluated identities are the constant-polynomial side + // of the linearization query. Rust subtracts that grouped + // scalar into expected_eval, so Solidity stores -nu_y(x). + let linearization_expected_eval := addmod(0, sub(r, mload(0xb280)), r) + mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) + pop(y) + } + + // Return the compact output frame. Halo2Verifier checks the magic, + // stores word 1 as the linearization expected eval, then expands + // selector buckets into the fused final PCS MSM. + mstore(QUOTIENT_OUTPUT_MPTR, QUOTIENT_MAGIC) + mstore(add(QUOTIENT_OUTPUT_MPTR, 0x20), mload(QUOTIENT_EVAL_MPTR)) + // Copy selector buckets from the generated absolute memory region + // into the compact external-call return frame. + for { let q_i := 0 } lt(q_i, 10) { q_i := add(q_i, 1) } { + mstore(add(QUOTIENT_OUTPUT_MPTR, add(0x40, shl(5, q_i))), mload(add(SELECTOR_ACC_MPTR, shl(5, q_i)))) + } + return(QUOTIENT_OUTPUT_MPTR, QUOTIENT_OUTPUT_LEN) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/Halo2Verifier.sol b/proofs/solidity-verifier/fixtures/ivc/Halo2Verifier.sol new file mode 100644 index 000000000..6f66ad7c7 --- /dev/null +++ b/proofs/solidity-verifier/fixtures/ivc/Halo2Verifier.sol @@ -0,0 +1,2282 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.24; + +/// @title Halo2 BLS12-381 KZG verifier. +/// @notice Circuit-specialized verifier for Midfall/midnight-proofs Halo2 +/// proofs rendered by this repository's Rust generator. +/// @dev This contract ports the verifier flow from +/// `midfall/proofs/src/plonk/verifier.rs`, the Keccak transcript comments from +/// `midfall/proofs/src/transcript/implementors.rs`, and the KZG multi-open +/// comments from `midfall/proofs/src/poly/kzg/mod.rs`. +/// @dev It is not a generic verifier. The proof layout, VK payload, quotient +/// identity program, memory layout, and optional quotient evaluator are all +/// generated for one `VerifyingKey>`. +/// +/// Halo2 KZG verifier for the BLS12-381 curve, midnight-proofs flavour. +/// +/// Differences vs the original BN254 / halo2 v0.4 template: +// +/// - BLS12-381 base field Fp is 381 bits and does not fit in a uint256. +/// Each Fp coord is encoded EIP-2537 padded (16 zero bytes + 48 bytes). +/// A G1 point is 128 bytes (4 words); a G2 point is 256 bytes (8). +/// - Calldata carries G1 commitments in uncompressed EIP-2537 padded +/// form (4 words = 128 bytes per point: x_hi, x_lo, y_hi, y_lo). The +/// proof bytes produced by midnight-proofs prover are repacked off +/// chain (compressed -> uncompressed) before being passed to +/// `verifyProof`. The verifier hashes the uncompressed 128-byte form into +/// the transcript verbatim, matching `Hashable for G1Projective::to_input`; +/// see `common_uncompressed_g1`. +/// - Transcript `common` absorbs raw inputs in order. `squeeze` computes one +/// Keccak digest, resets the transcript buffer to that digest, then samples +/// by interpreting the digest as a big-endian integer modulo r. +/// - Scalar inversion uses modexp(scalar, r-2, r). +/// - Constructors run deployment-time smoke tests for MCOPY and the EIP-2537 +/// precompiles using identity inputs. Compile with Solidity >=0.8.24 and +/// deploy only on chains/forks that support MCOPY and EIP-2537. +contract Halo2Verifier { + + /// @notice Verifying-key contract address authorized for this verifier. + /// @dev The runtime length and codehash are pinned by generated constants and checked at construction time. + address public immutable AUTHORIZED_VK; + // Expected VK runtime metadata. The deployed VK runtime is + // INVALID || payload, hence EXPECTED_VK_LENGTH is one byte longer than + // EXPECTED_VK_PAYLOAD_LENGTH. + uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 17024; + uint256 internal constant EXPECTED_VK_LENGTH = 17025; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x67bac137fa7e479c25b63324812752e4b6e13d9841d5bf83c322170bf91c0f88; + bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); + /// @notice Quotient evaluator contract authorized for split quotient reconstruction. + /// @dev The evaluator returns the linearization expected scalar and selector buckets; its runtime may be pinned by generated constants. + address public immutable AUTHORIZED_QUOTIENT; + // Expected split evaluator runtime metadata. It is checked at deployment + // and again immediately before each external quotient reconstruction. + uint256 internal constant EXPECTED_QUOTIENT_LENGTH = 9552; + uint256 internal constant EXPECTED_QUOTIENT_CODEHASH_WORD = 0x6a7ec82a9b0683783c459f4b43e924f8ceba256731bf2b9782ab5747e801ea2c; + bytes32 internal constant EXPECTED_QUOTIENT_CODEHASH = bytes32(EXPECTED_QUOTIENT_CODEHASH_WORD); + + // Solidity ABI calldata cursors. The generated verifier accepts exactly + // verifyProof(bytes proof, uint256[] instances), then parses the `proof` + // bytes itself in the same order as the Rust verifier transcript. + uint256 internal constant PROOF_LEN_CPTR = 0x44; + uint256 internal constant PROOF_CPTR = 0x64; + uint256 internal constant NUM_INSTANCE_CPTR = 0x1ec4; + uint256 internal constant INSTANCE_CPTR = 0x1ee4; + // First general-purpose memory words reserved by the generated verifier. + // RETURN_MPTR is a single word set to 1 on success. + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; + + // ---------------------------------------------------------------------- + // Verifying-key memory map. The VK header lives at VK_MPTR, followed + // by the quotient VM payload and commitments. After the full VK + // runtime comes the challenge slots (challenge_mptr..) and the + // per-stage scratch (theta_mptr..). + // ---------------------------------------------------------------------- + uint256 internal constant VK_MPTR = 0x3680; + uint256 internal constant VK_DIGEST_MPTR = 0x3680; + uint256 internal constant NUM_INSTANCES_MPTR = 0x36a0; + uint256 internal constant K_MPTR = 0x36c0; + uint256 internal constant N_INV_MPTR = 0x36e0; + uint256 internal constant OMEGA_MPTR = 0x3700; + uint256 internal constant OMEGA_INV_MPTR = 0x3720; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x3740; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x3760; + uint256 internal constant ACC_OFFSET_MPTR = 0x3780; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x37a0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x37c0; + uint256 internal constant G1_BASE_MPTR = 0x37e0; + uint256 internal constant G2_BASE_MPTR = 0x3860; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x3960; + + uint256 internal constant CHALLENGE_MPTR = 0x7900; + + // Challenge layout. Squeeze order in midnight-proofs: + // user_phase challenges (variable count) + // theta -> beta, gamma -> trash_challenge -> y -> x -> + // x1, x2 -> x3 -> x4 + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; + + // Batch-open commitments live in 4-word EIP-2537 padded slots. + uint256 internal constant F_COM_MPTR = 0x7a40; + uint256 internal constant PI_MPTR = 0x7ac0; + + // Accumulator (KZG IVC). + uint256 internal constant ACC_LHS_MPTR = 0x7b40; + uint256 internal constant ACC_RHS_MPTR = 0x7bc0; + + // Lagrange / linearization scratch. + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; + // Legacy name: this is not h(x). It stores the expected opening + // scalar for the linearized commitment, i.e. the negated y-batched + // identity numerator reconstructed from the alleged evals at x. + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; + uint256 internal constant QUOTIENT_MPTR = 0x7d20; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x7dc0; + uint256 internal constant V_MPTR = 0x7de0; + uint256 internal constant FINAL_COM_MPTR = 0x7e00; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x7e80; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x7f00; // 4 words + + // Multi-prepare scratch (sized at codegen time). + uint256 internal constant ROT_POINTS_MPTR = 0x7f80; + uint256 internal constant X1_POWERS_MPTR = 0x8300; + // Q_COM materialization is currently fused into the final MSM scratch, + // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero + // reserved capacity until a future emitter starts writing Q_COM_MPTR. + uint256 internal constant Q_COM_MPTR = 0x8b20; + uint256 internal constant Q_EVAL_SET_MPTR = 0x8b20; + + // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals + // block of the proof; we keep it as a memory slot for symmetry. + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x9220; + + // Reserved 4-word slot for the G1 identity (point at infinity) in + // EIP-2537 padded form. EVM memory is zero-initialised, and we + // never write to this region, so the four `mload`s below produce + // 0,0,0,0 which is exactly the identity encoding the EIP-2537 + // ec_add / ec_mul precompiles accept. + uint256 internal constant G1_IDENTITY_MPTR = 0x9320; + + // Decoded polynomial-eval buffer (Optimisation H3). The off-chain + // Solidity proof shim rewrites proof scalars into canonical BE words, + // so `calldataload` gives the field element directly. The transcript- + // side `evaluations` loop range-checks and spills that value here so + // downstream eval references (gate evaluator + PCS q_eval Horner) + // become 3-gas `mload(...)` instead of calldata reads. + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0xb140; + uint256 internal constant TRACE_U256_MPTR = 0xe340; + + // ---------------------------------------------------------------------- + // Per-category bases for EIP-2537 padded G1 commitments. The proof + // calldata carries 128-byte uncompressed/padded G1s after the off-chain + // proof shim repacks midnight-proofs' native compressed stream; this + // region stores the 4-word slots used by PCS / quotient-fold sections. + // + // Cumulative offsets (in words from `comms_mptr_base`): + // ADVICE_COMMS_MPTR_BASE + 0 + // LOOKUP_M_COMMS_MPTR_BASE + 4*total_advices + // PERM_Z_COMMS_MPTR_BASE + 4*total_advices + 4*num_lookups + // LOOKUP_HELPER_COMMS_MPTR_BASE + ... + 4*num_permutation_zs + // LOOKUP_Z_COMMS_MPTR_BASE + ... + 4*lookup_helper_chunks_total + // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups + // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans + // ---------------------------------------------------------------------- + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0xa140; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0xa8c0; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0xa9c0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0xacc0; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0xadc0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0xaec0; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0xaf40; + + // BLS12-381 scalar-field modulus, used for transcript challenges and all + // Halo2 verifier arithmetic. + uint256 internal constant FR_MODULUS = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + + // BLS12-381 Fp modulus minus one, split like an EIP-2537 coordinate: + // high word = 16 zero bytes || top 16 coordinate bytes, low word = + // bottom 32 coordinate bytes. + uint256 internal constant BLS_P_HI = 0x000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd7; + uint256 internal constant BLS_P_MINUS_ONE_LO = 0x64774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa; + // Packed public-accumulator sentinels for the shifted coordinate codec. + // The `_WITH_ID_FLAG` variant is used only for the first x-coordinate word. + uint256 internal constant BLS_P_MINUS_ONE_PACKED_0 = 0x00000000f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa; + uint256 internal constant BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG = 0x00000000f38512bf6730d2a0f6b0f6241eabfffeb153ffffbafeffffffffaaaa; + uint256 internal constant BLS_P_MINUS_ONE_PACKED_1 = 0x0000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84; + + /// @notice Smoke-check the Cancun/EIP-2537 runtime features required by the verifier. + /// @dev Exercises MCOPY and identity EIP-2537 inputs to catch incompatible chain/fork configurations at deployment. + function require_eip2537_precompiles() private view { + assembly ("memory-safe") { + // Scratch is reused for every runtime-prerequisite probe. + let scratch := 0x1000 + + // MCOPY must be available because the verifier uses it for + // proof-time point/scratch staging. Execute the opcode here so a + // non-Cancun fork fails during deployment instead of later proofs. + mstore(scratch, 0x1234) + mcopy(add(scratch, 0x20), scratch, 0x20) + if iszero(eq(mload(add(scratch, 0x20)), 0x1234)) { revert(0, 0) } + + // Start the EIP-2537 probes with the identity encoding for G1/G2: + // all-zero padded words. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + + // G1ADD(identity, identity) -> identity, 128-byte return. + // This catches chains where the precompile is missing or returns a + // non-standard success shape. + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if or(or(mload(scratch), mload(add(scratch, 0x20))), or(mload(add(scratch, 0x40)), mload(add(scratch, 0x60)))) { + revert(0, 0) + } + + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + + // Worst-case generated G1MSM with all identity/zero terms -> + // identity, 128-byte return. This exercises the largest MSM input + // length rendered by this verifier instead of only a one-pair + // smoke call. + let msm_scratch := 0xb140 + for { let off := 0 } lt(off, 0x30c0) { off := add(off, 0x20) } { + mstore(add(msm_scratch, off), 0) + } + // The production verifier uses G1MSM both for commitments and as + // the subgroup validator for absorbed proof points. + if iszero(staticcall(gas(), 0x0c, msm_scratch, 0x30c0, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if or(or(mload(scratch), mload(add(scratch, 0x20))), or(mload(add(scratch, 0x40)), mload(add(scratch, 0x60)))) { + revert(0, 0) + } + + // PAIRING_CHECK([(identity_g1, identity_g2), (identity_g1, identity_g2)]) + // -> true, 32-byte return. This matches the runtime two-pair KZG + // pairing input size and catches absent pairing precompiles, + // short return data, and obviously incompatible semantics. + if iszero(staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x20)) { revert(0, 0) } + if iszero(eq(mload(scratch), 1)) { revert(0, 0) } + } + } + + + /// @notice Create a verifier pinned to a verifying key and quotient evaluator. + /// @dev Checks MCOPY/EIP-2537 availability and verifies both dependency runtimes before storing their addresses. + /// @param authorizedVk Address of the generated `Halo2VerifyingKey` runtime. + /// @param authorizedQuotient Address of the generated `Halo2QuotientEvaluator` runtime. + constructor(address authorizedVk, address authorizedQuotient) { + // Verifier correctness depends on chain support for MCOPY and the + // BLS12-381 precompiles; fail deployment before pinning dependencies. + require_eip2537_precompiles(); + // Pin the generated VK runtime exactly. The verifier later repeats the + // codehash/length check before copying the VK payload for a proof. + require( + authorizedVk.code.length == EXPECTED_VK_LENGTH + && authorizedVk.codehash == EXPECTED_VK_CODEHASH, + "invalid vk" + ); + // The split evaluator contains generated verifier logic, not a generic + // library. Pin it with the same strictness as the verifying key. + require( + authorizedQuotient.code.length == EXPECTED_QUOTIENT_LENGTH + && authorizedQuotient.codehash == EXPECTED_QUOTIENT_CODEHASH, + "invalid quotient" + ); + // Store the already-validated dependency addresses for proof-time + // memory loading and quotient reconstruction. + AUTHORIZED_VK = authorizedVk; + AUTHORIZED_QUOTIENT = authorizedQuotient; + } + + /// @notice Verify a Halo2/Midfall proof for the generated verifying key. + /// @dev This checks only that `proof` verifies for the supplied public + /// `instances` under this pinned VK/protocol. Application contracts must + /// bind the meaning of those instances separately: state roots, program + /// identifiers, expected IVC outputs, chain/domain separation, and any + /// protocol-specific authorization are outside this raw verifier ABI. + /// @dev Production renders are success-or-revert: accepted proofs return + /// `true`, while malformed calldata, invalid proof material, failed + /// precompiles, or mismatched pinned dependency code revert. Trace and gas + /// renders keep the same failure policy. + /// @dev The generated verifier uses absolute Yul memory addresses instead + /// of Solidity's free-memory pointer, but generated scratch starts at + /// `0x80` so Solidity's reserved memory prefix is preserved. The main + /// assembly block remains terminal: accepted proofs return from assembly + /// and all rejected inputs revert. Do not inline this body into Solidity + /// code that continues executing after verification without reviewing the + /// memory strategy; see `docs/MEMORY_LAYOUT.md`. + /// @param proof Solidity-facing proof bytes, with G1 elements repacked into EIP-2537 padded uncompressed form. + /// @param instances Public instance scalars encoded as canonical BLS12-381 scalar-field words. + /// @return Always `true` for accepted proofs; invalid proofs revert instead of returning `false`. + function verifyProof( + bytes calldata proof, + uint256[] calldata instances + ) external view returns (bool) { + // Cheap ABI-shape guard before any generated memory work: + // - proof head must point at the bytes payload; + // - instances head must point at the generated instance array. + // + // The verifier below is a hand-rolled calldata parser. Failing here + // keeps malformed dynamic-argument layouts from being interpreted as a + // valid Midfall proof stream. + assembly ("memory-safe") { + if iszero(and(eq(calldataload(0x04), 0x40), eq(calldataload(0x24), sub(NUM_INSTANCE_CPTR, 0x04)))) { + revert(0, 0) + } + } + // Non-embedded renders pin the VK by address and codehash. The Yul + // loader rechecks the runtime before every proof and copies the + // INVALID-prefixed payload into VK_MPTR. + address vk = AUTHORIZED_VK; + // Split quotient renders delegate the scalar-side identity numerator + // reconstruction to a separately deployed generated evaluator. + address quotientEvaluator = AUTHORIZED_QUOTIENT; + assembly ("memory-safe") { + // This block owns the call-frame memory and remains terminal. + // Generated scratch starts at TRANSCRIPT_MPTR (0x80), preserving + // Solidity's reserved scratch, free-memory-pointer, and zero-slot + // words. See docs/MEMORY_LAYOUT.md. + // =============================================================== + // Helpers: modexp, transcript, EIP-2537 calls + // =============================================================== + + // Inverse of a Fr scalar via modexp(x, r-2, r). The verifier + // calls this only after transcript absorption is complete, so it + // reuses the dead transcript buffer just below VK_MPTR instead of + // a fixed post-VK address that can collide with live PCS scratch + // when the VK payload becomes smaller. + function scalar_inv(x) -> inv { + // Zero has no multiplicative inverse in Fr; callers rely on a + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } + if iszero(x) { revert(0, 0) } + let p := 0x3580 + // EIP-198 modexp frame: + // [base_len, exp_len, mod_len, base, exponent, modulus] + mstore(add(p, 0x00), 0x20) // base len + mstore(add(p, 0x20), 0x20) // exp len + mstore(add(p, 0x40), 0x20) // mod len + mstore(add(p, 0x60), x) + mstore(add(p, 0x80), sub(FR_MODULUS, 2)) + mstore(add(p, 0xa0), FR_MODULUS) + if iszero(staticcall(gas(), 0x05, p, 0xc0, p, 0x20)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x20)) { revert(0, 0) } + inv := mload(p) + } + + // ---------- Streaming Keccak256 transcript helpers ---------- + // + // The transcript buffer lives at + // memory[TRANSCRIPT_MPTR..buf_len). On verifier entry it starts + // empty. Each common(input) appends raw bytes. squeeze_*(buf_len) + // computes one Keccak digest, reseeds the buffer with that + // 32-byte digest, and samples a Fq element as + // uint256(digest_be) mod r. + + function transcript_init() -> buf_len { + // Empty transcript buffer starts exactly at TRANSCRIPT_MPTR. + buf_len := TRANSCRIPT_MPTR + } + + // Append one 32-byte big-endian field/transcript word at the + // current end of the transcript buffer. + function common_word(buf_len, word) -> ret { + mstore(buf_len, word) + ret := add(buf_len, 32) + } + + // Absorb a BLS12-381 G1 point in EIP-2537 padded + // uncompressed form (4 calldata words = 128 bytes: + // x_hi || x_lo || y_hi || y_lo, each coord = 16 zero + // pad bytes + 48 big-endian field bytes) into the + // transcript buffer at `buf_len`. + // + // Matches the patched `Hashable for + // midnight_curves::G1Projective::to_input` in + // midnight-proofs, which now emits the same 128-byte form + // (`midfall/proofs/src/transcript/implementors.rs`). The + // previous emitter hashed the 48-byte ZCash compressed + // encoding instead and ran a 384-bit `lex(y) > lex(p − y)` + // ladder + identity flag fixup to derive the sign bit on + // the fly; switching to the uncompressed form drops that + // ladder entirely. + // + // Canonicality: reject non-zero bytes in the top 16 bytes + // of each `_hi` calldata word and reject coordinates + // outside Fp. Normalizing those bytes before hashing would + // make multiple calldata encodings share one transcript. + // + // This helper does not run an independent curve/subgroup + // check. Instead, ProtocolPlan::validate rejects generated + // plans where an absorbed proof commitment would not later be + // consumed by an EIP-2537 G1MSM or pairing path, and those + // precompiles perform the curve/subgroup validation. + // + // The point's uncompressed form remains in calldata; the + // call site is responsible for `calldatacopy`-ing it into + // memory afterwards if it needs the on-curve coordinates. + function common_uncompressed_g1(buf_len, cptr) -> ret { + let x_hi_word := calldataload(cptr) + let x_lo := calldataload(add(cptr, 0x20)) + let y_hi_word := calldataload(add(cptr, 0x40)) + let y_lo := calldataload(add(cptr, 0x60)) + if shr(128, x_hi_word) { revert(0, 0) } + if shr(128, y_hi_word) { revert(0, 0) } + + let x_hi := and(x_hi_word, 0xffffffffffffffffffffffffffffffff) + let y_hi := and(y_hi_word, 0xffffffffffffffffffffffffffffffff) + if iszero(or(lt(x_hi, BLS_P_HI), and(eq(x_hi, BLS_P_HI), iszero(gt(x_lo, BLS_P_MINUS_ONE_LO))))) { + revert(0, 0) + } + if iszero(or(lt(y_hi, BLS_P_HI), and(eq(y_hi, BLS_P_HI), iszero(gt(y_lo, BLS_P_MINUS_ONE_LO))))) { + revert(0, 0) + } + + // Memcpy the 4 calldata words (128 bytes) verbatim + // into the keccak buffer. + calldatacopy(buf_len, cptr, 0x80) + ret := add(buf_len, 0x80) + } + + // One Keccak finalization + reseed. Returns the new buffer + // cursor (= TRANSCRIPT_MPTR + 32) and stores the squeezed Fq at + // `mptr`. + function squeeze_to(buf_len, mptr) -> ret { + let h0 := keccak256(TRANSCRIPT_MPTR, sub(buf_len, TRANSCRIPT_MPTR)) + // Reseed: write the 32-byte digest at start of buffer. + mstore(TRANSCRIPT_MPTR, h0) + let r := FR_MODULUS + // Sample Fq as uint256(keccak_digest_be) mod r. + mstore(mptr, mod(h0, r)) + ret := add(TRANSCRIPT_MPTR, 32) + } + + // ---------- EC primitives (EIP-2537 wrappers) ---------- + // + // These mirror the BN254 helpers but operate on 4-word G1 + // points. They use planned memory windows above Solidity's + // reserved prefix; the streaming transcript buffer is no longer + // needed once all challenges are squeezed. + + // Invert a contiguous run of Fr words in-place using Montgomery's + // batch inversion trick: + // 1. write prefix products to scratch; + // 2. invert the total product once with modexp; + // 3. walk backward to recover each individual inverse. + // + // The function returns a boolean instead of reverting so callers + // can combine it with other `success` plumbing until a section + // boundary decides whether to fail closed. + function batch_invert(success, mptr_start, mptr_end, scratch_mptr, r) -> ret { + ret := success + if iszero(ret) { leave } + // Memory ranges must be forward and word-aligned by + // construction; a reversed range is always a codegen error. + if lt(mptr_end, mptr_start) { + ret := 0 + leave + } + + let count_bytes := sub(mptr_end, mptr_start) + // Empty batch is valid and leaves memory untouched. + if iszero(count_bytes) { leave } + + // Fast path for a single denominator: avoid prefix scratch and + // just run one modexp inverse in place. + if eq(count_bytes, 0x20) { + let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } + if iszero(x) { + ret := 0 + leave + } + + let single_scratch := scratch_mptr + mstore(add(single_scratch, 0x00), 0x20) + mstore(add(single_scratch, 0x20), 0x20) + mstore(add(single_scratch, 0x40), 0x20) + mstore(add(single_scratch, 0x60), x) + mstore(add(single_scratch, 0x80), sub(r, 2)) + mstore(add(single_scratch, 0xa0), r) + ret := staticcall(gas(), 0x05, single_scratch, 0xc0, single_scratch, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + if ret { mstore(mptr_start, mload(single_scratch)) } + leave + } + + // Forward pass: scratch stores prefix products up to, but not + // including, the final element. `gp` becomes the total product. + let gp_mptr := scratch_mptr + let gp := mload(mptr_start) + let mptr := add(mptr_start, 0x20) + for {} lt(mptr, sub(mptr_end, 0x20)) {} { + gp := mulmod(gp, mload(mptr), r) + mstore(gp_mptr, gp) + mptr := add(mptr, 0x20) + gp_mptr := add(gp_mptr, 0x20) + } + gp := mulmod(gp, mload(mptr), r) + // A zero total product means at least one denominator was + // zero, so no batch inverse exists. + if iszero(gp) { + ret := 0 + leave + } + + // Invert the total product once. + mstore(add(gp_mptr, 0x00), 0x20) + mstore(add(gp_mptr, 0x20), 0x20) + mstore(add(gp_mptr, 0x40), 0x20) + mstore(add(gp_mptr, 0x60), gp) + mstore(add(gp_mptr, 0x80), sub(r, 2)) + mstore(add(gp_mptr, 0xa0), r) + ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } + let all_inv := mload(gp_mptr) + + // Backward pass: derive each inverse from the inverted total + // product and the saved prefix products. + let first_mptr := mptr_start + let second_mptr := add(first_mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + for {} lt(second_mptr, mptr) {} { + let inv := mulmod(all_inv, mload(gp_mptr), r) + all_inv := mulmod(all_inv, mload(mptr), r) + mstore(mptr, inv) + mptr := sub(mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + } + let inv_first := mulmod(all_inv, mload(second_mptr), r) + let inv_second := mulmod(all_inv, mload(first_mptr), r) + mstore(first_mptr, inv_first) + mstore(second_mptr, inv_second) + } + + // Final EIP-2537 pairing wrapper. `lhs_mptr` and `rhs_mptr` are + // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. + function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { + ret := success + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } + // Lay out two (G1, G2) pairs at scratch..scratch+0x300: + // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] + // Cancun MCOPY (3 + 3·words gas) replaces what used to + // be a 4-step mstore chain for each G1 (~60 gas) and an + // 8-iter mstore loop for each G2 (~240 gas). Net saving + // here is ~500 gas per ec_pairing call. + let scratch := 0x0320 + mcopy(scratch, lhs_mptr, 0x80) + mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) + mcopy(add(scratch, 0x180), rhs_mptr, 0x80) + mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) + ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) + if iszero(ret) { revert(0, 0) } + ret := 1 + } + + // ---------- IVC accumulator public-input decoding ---------- + // + // `AssignedForeignPoint` exposes each base-field coordinate + // through `AssignedField::as_public_input`: seven radix-2^56 limbs of + // (coord - 1) are packed four-at-a-time into native field elements. + // The x coordinate's first packed word carries the identity flag by + // adding one raw radix base. Rebuild EIP-2537 padded + // (x_hi, x_lo, y_hi, y_lo) words from that encoding. + // + // Public-input layout for one coordinate: + // word 0: limb_0 | limb_1 << bits | ... up to limbs_per_word + // word 1: next limbs, if any + // + // The limbs are little-endian in the represented integer even + // though calldata words are loaded as big 256-bit values. The loop + // below extracts each limb by shifting inside the packed word and + // reconstructs the full coordinate into the two-word EIP-2537 + // representation expected by the BLS12-381 precompiles. + function load_acc_coord_shifted(src, bits, n, base, limbs_per_word, first_adjust) -> hi, lo { + // Mask for one radix limb, e.g. 2^56 - 1 for the current + // BLS12-381 self-emulation parameters. + let mask := sub(base, 1) + for { let i := 0 } lt(i, n) { i := add(i, 1) } { + // Limb words are little-endian packed inside each Fr + // public input. `first_adjust` removes the identity flag + // base from the first x word when present. + let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { + packed := sub(packed, first_adjust) + } + // Select limb i from its packed field word. The mod/div + // pair maps a limb index to an intra-word limb slot and + // the calldata word containing it. + let limb := and(shr(mul(mod(i, limbs_per_word), bits), packed), mask) + + let shift := mul(i, bits) + // Split the reconstructed 384-bit coordinate into the + // EIP-2537 high/low words expected by the precompiles. + if lt(shift, 256) { + lo := add(lo, shl(shift, limb)) + if gt(add(shift, bits), 256) { + // A limb can straddle the 256-bit low/high split. + // Move the overflow bits into hi. + hi := add(hi, shr(sub(256, shift), limb)) + } + } + if iszero(lt(shift, 256)) { + // Once shift >= 256 the whole limb belongs to hi. + hi := add(hi, shl(sub(shift, 256), limb)) + } + } + } + + // The shifted coordinate codec represents zero as p-1 before the + // final +1 below, so keep this sentinel explicit. + function is_bls_p_minus_one(hi, lo) -> yes { + yes := and(eq(hi, BLS_P_HI), eq(lo, BLS_P_MINUS_ONE_LO)) + } + + // Canonical encoded accumulator identity: + // x = p-1 plus the identity flag in the first packed word, + // y = p-1 with no identity flag. + // It decodes to the EIP-2537 point-at-infinity slot (all zeros). + // + // This fast path is deliberately stricter than "decodes to zero": + // the point at infinity has exactly one accepted public-input + // encoding. Non-canonical zero-like encodings are rejected later. + function is_acc_encoded_identity(src) -> yes { + yes := and( + and( + eq(calldataload(src), BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG), + eq(calldataload(add(src, 0x20)), BLS_P_MINUS_ONE_PACKED_1) + ), + and( + eq(calldataload(add(src, 0x40)), BLS_P_MINUS_ONE_PACKED_0), + eq(calldataload(add(src, 0x60)), BLS_P_MINUS_ONE_PACKED_1) + ) + ) + } + + // Reject unused high bits in the packed public-input words. This + // makes each accumulator point encoding canonical before it reaches + // the precompile-based curve/subgroup validation. + function check_acc_coord_packing(src, bits, n, limbs_per_word) -> ok { + ok := 1 + // Number of packed native-field public-input words occupied by + // one coordinate. + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + for { let word_idx := 0 } lt(word_idx, coord_words) { word_idx := add(word_idx, 1) } { + // The final word may contain fewer than limbs_per_word + // limbs. Any unused high bits must be zero, otherwise the + // same coordinate would have multiple calldata encodings. + let remaining := sub(n, mul(word_idx, limbs_per_word)) + let limbs_in_word := limbs_per_word + if lt(remaining, limbs_per_word) { + limbs_in_word := remaining + } + let used_bits := mul(limbs_in_word, bits) + if lt(used_bits, 256) { + // shl(used_bits, 1) == 2^used_bits. The packed word + // must be strictly less than that bound. + ok := and(ok, lt(calldataload(add(src, mul(word_idx, 0x20))), shl(used_bits, 1))) + } + } + } + + // Decode one shifted coordinate. `allow_id` is true only for x, + // because the identity flag lives in x's first packed word. + function load_acc_coord(src, allow_id, bits, n, base, limbs_per_word) -> ok, hi, lo, is_id { + ok := check_acc_coord_packing(src, bits, n, limbs_per_word) + if and(allow_id, iszero(lt(calldataload(src), base))) { + // Probe the x identity flag by removing one radix base and + // checking whether the adjusted coordinate is p-1. + // + // `calldataload(src) >= base` is a cheap prefilter: only x + // can carry this flag, and adding one radix base must make + // the first packed word at least base. + let adj_hi, adj_lo := load_acc_coord_shifted(src, bits, n, base, limbs_per_word, base) + is_id := is_bls_p_minus_one(adj_hi, adj_lo) + } + + // Decode again with the identity adjustment applied only when + // the canonical identity flag was actually detected. + hi, lo := load_acc_coord_shifted(src, bits, n, base, limbs_per_word, mul(is_id, base)) + ok := and( + ok, + // Coordinate must be in the BLS12-381 base field, i.e. + // <= p - 1 in split hi/lo form. + or(lt(hi, BLS_P_HI), and(eq(hi, BLS_P_HI), iszero(gt(lo, BLS_P_MINUS_ONE_LO)))) + ) + + let was_p_minus_one := is_bls_p_minus_one(hi, lo) + if was_p_minus_one { + // Shifted encoding maps p-1 back to zero. + hi := 0 + lo := 0 + } + if iszero(was_p_minus_one) { + // All other coordinates are encoded as coord - 1, so add + // one back with carry into the high word. + let next_lo := add(lo, 1) + hi := add(hi, lt(next_lo, lo)) + lo := next_lo + } + + // EIP-2537 pads each 48-byte Fp coordinate to 64 bytes, + // so the high word must fit in its low 128 bits. + // This also catches impossible reconstructions above 384 bits. + ok := and(ok, lt(hi, shl(128, 1))) + } + + // Decode a public accumulator point into an EIP-2537 4-word G1 + // slot. Non-identity points are curve/subgroup checked later by + // routing them through G1MSM. + function load_acc_point(dst, src, bits, n, base) -> ok, is_id { + // Prefer the canonical all-coordinate identity encoding before + // attempting coordinate-level shifted decoding. This accepts + // the point at infinity only in the exact form generated by the + // circuit's public-input codec. + is_id := is_acc_encoded_identity(src) + if is_id { + ok := 1 + // EIP-2537 encodes G1 identity as four zero words: + // x_hi = x_lo = y_hi = y_lo = 0. + mstore(dst, 0) + mstore(add(dst, 0x20), 0) + mstore(add(dst, 0x40), 0) + mstore(add(dst, 0x60), 0) + } + if iszero(is_id) { + // x occupies coord_words packed public-input words; y + // starts immediately after x. + let limbs_per_word := 4 + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + // Only x may carry the identity flag. y must decode as a + // normal shifted coordinate. + let x_ok, x_hi, x_lo, x_is_id := load_acc_coord(src, 1, bits, n, base, limbs_per_word) + let y_ok, y_hi, y_lo, y_id := load_acc_coord( + add(src, mul(coord_words, 0x20)), + 0, + bits, + n, + base, + limbs_per_word + ) + // y_id is always zero because allow_id was false, but the + // tuple shape is shared with x decoding. + pop(y_id) + ok := and(x_ok, y_ok) + is_id := x_is_id + + if is_id { + // If x carried the identity flag, both decoded + // coordinates must be zero after shifting. Any other y + // value would be a malformed infinity encoding. + ok := and(ok, iszero(or(or(x_hi, x_lo), or(y_hi, y_lo)))) + mstore(dst, 0) + mstore(add(dst, 0x20), 0) + mstore(add(dst, 0x40), 0) + mstore(add(dst, 0x60), 0) + } + if iszero(is_id) { + // The coordinate codec maps encoded p-1 to decoded + // zero. EIP-2537 reserves affine (0,0) for the point + // at infinity, so a decoded infinity is only valid + // when the canonical accumulator identity encoding + // was used above. + let decoded_zero := iszero(or(or(x_hi, x_lo), or(y_hi, y_lo))) + ok := and(ok, iszero(decoded_zero)) + // Store the affine point in the exact precompile input + // layout: x_hi, x_lo, y_hi, y_lo. + mstore(dst, x_hi) + mstore(add(dst, 0x20), x_lo) + mstore(add(dst, 0x40), y_hi) + mstore(add(dst, 0x60), y_lo) + } + } + } + // Validate and prepare the public accumulator equation before the + // main transcript starts. This fails malformed public inputs early + // and writes ACC_LHS_MPTR / ACC_RHS_MPTR for final pairing batching. + // + // The accumulator public input represents an equality of two G1 + // commitments used by the recursive KZG accumulator. This helper: + // 1. decodes carried public G1 points from shifted limbs; + // 2. forces every decoded point through EIP-2537 G1MSM so the + // precompile validates curve/subgroup membership; + // 3. folds the RHS carried point and fixed-base scalar tail into + // ACC_RHS_MPTR, leaving ACC_LHS_MPTR / ACC_RHS_MPTR ready for + // randomized batching in FinalPairing.yul. + function validate_public_accumulator(success, r) -> out { + out := success + let bits := 56 + let n := 7 + // The BLS12-381 self-emulation currently exposes Fp + // coordinates as 7 radix-2^56 limbs. + let limb_base := shl(bits, 1) + let limbs_per_word := 4 + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + // acc_offset is generated from the VK/protocol shape and + // points into the ABI `instances` array. + let acc_instance_ptr := add(INSTANCE_CPTR, 0x80) + + // LHS layout: point limbs (x,y), then either an explicit + // scalar word or an implicit unit scalar for already-collapsed + // point-pair public inputs. + // The scalar pointer is computed unconditionally; the rendered + // branch below decides whether to read it or use scalar 1. + let lhs_scalar_ptr := add(acc_instance_ptr, mul(mul(2, coord_words), 0x20)) + let lhs_ok, lhs_is_id := load_acc_point(ACC_LHS_MPTR, acc_instance_ptr, bits, n, limb_base) + out := and(out, lhs_ok) + // Shared scratch for one-pair LHS validation and the later + // variable-length RHS MSM. + let acc_scratch := 0xb140 + { + // Carried-scalar layout: the circuit exposes the scalar + // that multiplies the carried LHS point. + let lhs_scalar := calldataload(lhs_scalar_ptr) + // Canonicality is enforced here rather than relying on the + // later instance-absorption loop: G1MSM reduces scalars + // mod r implicitly, so s and s+r would be indistinguishable + // inside this helper. + out := and(out, lt(lhs_scalar, r)) + // Identity status is useful for decoding checks above, but + // validation still goes through G1MSM for all points. + pop(lhs_is_id) + // Always route the decoded carried point through G1MSM, + // even for identity points and zero/one scalars. The + // precompile is the on-curve/subgroup validator for this + // public-input point; skipping it would let a malformed + // non-identity point hide behind scalar 0. + mcopy(acc_scratch, ACC_LHS_MPTR, 0x80) + mstore(add(acc_scratch, 0x80), lhs_scalar) + if out { + // Single-pair MSM output overwrites ACC_LHS_MPTR with + // lhs_scalar * decoded_lhs. If lhs_scalar is one, this + // is also a curve/subgroup validation round-trip. + out := staticcall(gas(), 0x0c, acc_scratch, 0xa0, ACC_LHS_MPTR, 0x80) + out := and(out, eq(returndatasize(), 0x80)) + } + } + // RHS layout for this generated verifier is fully collapsed: + // point limbs (x,y), scalar. There is no fixed-base scalar + // tail; fixed-base contributions were already folded into + // ACC_RHS by the circuit/native accumulator construction. + let rhs_instance_ptr := add(lhs_scalar_ptr, 0x20) + // RHS scalar, when present, immediately follows the RHS point + // limbs. The fixed-base scalar tail starts after it. + let rhs_scalar_ptr := add(rhs_instance_ptr, mul(mul(2, coord_words), 0x20)) + let rhs_ok, rhs_is_id := load_acc_point(ACC_RHS_MPTR, rhs_instance_ptr, bits, n, limb_base) + out := and(out, rhs_ok) + // acc_pair_ptr appends (G1, scalar) pairs into acc_scratch for + // one final RHS MSM. + let acc_pair_ptr := acc_scratch + { + // Explicit carried RHS scalar. + let rhs_scalar := calldataload(rhs_scalar_ptr) + out := and(out, lt(rhs_scalar, r)) + pop(rhs_is_id) + // Keep the carried RHS point in the MSM input even when + // it is encoded as identity or has scalar 0/1, so EIP-2537 + // validates every decoded public accumulator point before + // it can affect, or be erased from, the pairing batch. + mcopy(acc_pair_ptr, ACC_RHS_MPTR, 0x80) + mstore(add(acc_pair_ptr, 0x80), rhs_scalar) + // Move to the next (G1, scalar) pair slot. + acc_pair_ptr := add(acc_pair_ptr, 0xa0) + } + // Total byte length of the appended RHS MSM input pairs. This + // is at least one pair because the carried RHS point is always + // appended; keep the guard for synthetic render configurations. + let acc_msm_len := sub(acc_pair_ptr, acc_scratch) + if acc_msm_len { + // Fold the carried RHS point and any generated fixed-base + // tail into ACC_RHS_MPTR. The later final pairing block + // randomizes this equation together with the KZG pairing. + if out { + // Output overwrites ACC_RHS_MPTR with: + // rhs_scalar * carried_rhs + // + sum_i fixed_scalar_i * fixed_base_i + // + // The precompile also validates every nonzero fixed + // base embedded by codegen and the carried RHS point. + out := staticcall( + gas(), + 0x0c, + acc_scratch, + acc_msm_len, + ACC_RHS_MPTR, + 0x80 + ) + out := and(out, eq(returndatasize(), 0x80)) + } + } + // The caller checks `out` and reverts before transcript work if + // any decode, canonicality, or precompile validation failed. + } + + + + let r := FR_MODULUS + let success := true + + + + // =============================================================== + // VK loading: either bake in the embedded VK bytes or fetch + // them from the linked AUTHORIZED_VK contract. + // + // This is the first verifier phase after helper definitions. Its + // job is to make the generated VK payload available at VK_MPTR in + // one canonical memory layout, regardless of whether this render + // embeds the VK directly or links a separate Halo2VerifyingKey + // contract. + // + // Later template partials treat VK_MPTR as already populated with: + // - header words: vk_digest, domain data, accumulator metadata; + // - BLS12-381 base points used by the final pairing; + // - compact quotient VM constants/program bytes, when enabled; + // - fixed and permutation commitments in 4-word G1 slots. + // =============================================================== + { + // Re-check the pinned VK dependency on every proof. The + // constructor check catches normal deployment mistakes, while + // this fresh check hardens forks or same-transaction edge + // cases where code at the authorized address could differ + // from the runtime originally pinned by this verifier. + // + // EXPECTED_VK_LENGTH includes the leading INVALID byte in the + // Halo2VerifyingKey runtime. EXPECTED_VK_CODEHASH_WORD is the + // full runtime hash, not only the payload hash. + if iszero(and( + eq(extcodesize(vk), EXPECTED_VK_LENGTH), + eq(extcodehash(vk), EXPECTED_VK_CODEHASH_WORD) + )) { revert(0, 0) } + // Runtime byte 0 is INVALID so direct calls cannot execute the + // payload. Copy from byte 1 into VK_MPTR to reconstruct the + // exact payload layout used by the embedded branch. + extcodecopy(vk, VK_MPTR, 0x01, EXPECTED_VK_PAYLOAD_LENGTH) + + // Cross-check loaded VK header words against the verifier + // constants used by later parser, domain, and accumulator + // paths. Codehash pinning protects the external VK address; + // these checks catch generator drift before calldata parsing + // chooses a stale schema. + success := and(success, eq(mload(NUM_INSTANCES_MPTR), 14)) + success := and(success, eq(mload(K_MPTR), 20)) + success := and(success, eq(mload(HAS_ACCUMULATOR_MPTR), 1)) + success := and(success, eq(mload(ACC_OFFSET_MPTR), 4)) + success := and(success, eq(mload(NUM_ACC_LIMBS_MPTR), 7)) + success := and(success, eq(mload(NUM_ACC_LIMB_BITS_MPTR), 56)) + if iszero(success) { revert(0, 0) } + // + // The checks below validate the dynamic ABI envelope before the + // transcript parser starts walking raw calldata: + // - proof bytes length equals the generated proof layout; + // - instance array length equals the generated public input + // count; + // - total calldata length has no missing or trailing words. + // + // `success` is folded through `and` for consistency with later + // sections, then immediately enforced at the end of this block. + // A failure here means the verifier is not looking at the proof + // shape it was generated to parse. + success := and(success, eq(0x1e60, calldataload(PROOF_LEN_CPTR))) + success := and(success, eq(14, calldataload(NUM_INSTANCE_CPTR))) + // Calldata must contain exactly the ABI selector, proof bytes, + // instance-array length, and generated number of instance + // words. Any trailing bytes fail closed. + success := and( + success, + eq(calldatasize(), add(INSTANCE_CPTR, 0x01c0)) + ) + // Stop before any transcript absorption if the ABI/proof shape + // is not exactly the generated one. + if iszero(success) { revert(0, 0) } + } + // Fail malformed accumulator public inputs before transcript, + // quotient, PCS, and final pairing work. The late accumulator block + // only batches these already-validated G1 outputs into the final + // pairing equation. + // + // Accumulator validation decodes shifted public-input limbs into + // EIP-2537 G1 slots, checks canonical encodings, and routes points + // through G1MSM for curve/subgroup validation. Doing it here means + // invalid accumulator public inputs cannot influence transcript + // challenge derivation or waste gas in later quotient/PCS work. + // validate_public_accumulator returns a boolean to share the same + // success-plumbing style as other helper calls; this boundary is + // where the verifier converts failure to a revert. + success := validate_public_accumulator(success, r) + if iszero(success) { revert(0, 0) } + + // =============================================================== + // Transcript: VK digest + instances + proof. + // + // This block is the Solidity mirror of the native Midfall verifier + // transcript schedule. It does three jobs at once: + // + // 1. Absorb public data and proof bytes into the streaming + // Keccak transcript in exactly the native order. + // 2. Decode/range-check proof scalars and canonical G1 calldata. + // 3. Copy proof commitments/evaluations into planned memory + // slots consumed by Lagrange, quotient, PCS, and pairing + // blocks later in the verifier. + // + // `buf_len` is a write cursor into the transcript buffer. The + // helper functions append bytes and return the new cursor; squeeze + // helpers hash memory[TRANSCRIPT_MPTR..buf_len), reseed the buffer + // with the digest, and write the sampled Fr challenge to memory. + // =============================================================== + let buf_len := transcript_init() + // VK_DIGEST_MPTR holds the digest as a BE 32-byte word (the + // VK contract stores it via `mstore`, which matches the + // Keccak Fq transcript input). + // + // This digest commits to the verifier key / constraint system + // before any proof material is read. + buf_len := common_word(buf_len, mload(VK_DIGEST_MPTR)) + + // Absorb committed_pi = G1Affine::identity() when the + // `committed-instances` feature is on in midnight-proofs. + // Under the patched `Hashable::to_input` (see + // `midfall/proofs/src/transcript/implementors.rs`), the + // identity hashes as 128 zero bytes (EIP-2537 (0,0) + // convention), NOT the 48-byte ZCash compressed form + // 0xc0||47*0x00 that the previous emitter produced. + // Native verifier absorbs this BEFORE the instance count. + { + // 128 zero bytes: zero out 4 consecutive 32-byte words + // at buf_len. + // This is a raw transcript absorb, not a memory slot kept for + // later elliptic-curve operations. + mstore(buf_len, 0) + mstore(add(buf_len, 0x20), 0) + mstore(add(buf_len, 0x40), 0) + mstore(add(buf_len, 0x60), 0) + buf_len := add(buf_len, 0x80) + } + + { + // Native verifier absorbs a length scalar before instance + // values; Keccak Fq transcript input is canonical BE. + // The ABI length was already checked against this generated + // constant in VkLoading.yul. + buf_len := common_word(buf_len, 14) + + let instance_cptr := INSTANCE_CPTR + for { let instance_cptr_end := add(instance_cptr, 0x01c0) } + lt(instance_cptr, instance_cptr_end) + { instance_cptr := add(instance_cptr, 0x20) } { + let inst_be := calldataload(instance_cptr) + // Public inputs are BLS12-381 scalar-field elements. They + // must be canonical before transcript absorption; accepting + // non-canonical encodings would admit transcript aliases. + success := and(success, lt(inst_be, r)) + // Instances are passed BE in calldata, matching the + // Keccak Fq transcript input. + buf_len := common_word(buf_len, inst_be) + } + if iszero(success) { revert(0, 0) } + } + + // =============================================================== + // Per-user-phase reads + challenge squeezes. + // + // Each proof G1 is already EIP-2537 padded in calldata. The + // verifier validates and absorbs that 128-byte form, then copies + // it into the corresponding per-category MPTR. The PCS / + // quotient-fold blocks below dereference those MPTRs. + // + // All G1 reads follow the same pattern: + // - common_uncompressed_g1 canonicalizes/range-checks the two Fp + // coordinates and appends the exact 128 calldata bytes; + // - calldatacopy stores the same 4-word G1 slot in planned + // memory for later EIP-2537 precompile calls; + // - proof_cptr advances by one G1 byte length. + // =============================================================== + // proof_cptr walks the raw proof bytes inside the ABI `bytes` + // payload. Every successful read advances it exactly once, and the + // final equality check below proves the parser consumed the whole + // generated proof layout. + let proof_cptr := PROOF_CPTR + // advice_walk mirrors proof commitment order into the contiguous + // G1 commitment memory region used by PCS and quotient folding. + let advice_walk := ADVICE_COMMS_MPTR_BASE + // ---- User phase 1 ---- + // Advice commitments for this phase are absorbed before the phase's + // challenge squeezes. The number of commitments and challenges is + // generated from the protocol plan. + for { let end := add(proof_cptr, 0x0780) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + // Store the commitment at its phase-ordered advice slot. + calldatacopy(advice_walk, proof_cptr, 0x80) + advice_walk := add(advice_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- theta ---- + // From this point onward the transcript alternates between + // squeezed challenges and proof commitments exactly as + // midnight-proofs does in `plonk/verifier.rs`. + // theta batches lookup input expressions. + buf_len := squeeze_to(buf_len, THETA_MPTR) + // ---- multiplicities (one G1 per lookup) ---- + // Lookup multiplicity commitments are absorbed after theta and + // copied into their own contiguous G1 region. + let lookup_m_walk := LOOKUP_M_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0100) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_m_walk, proof_cptr, 0x80) + lookup_m_walk := add(lookup_m_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- beta, gamma ---- + // beta and gamma are the permutation/lookup randomizers. They are + // squeezed after lookup multiplicities and before permutation + // product commitments, matching the native verifier schedule. + buf_len := squeeze_to(buf_len, BETA_MPTR) + buf_len := squeeze_to(buf_len, GAMMA_MPTR) + // ---- permutation Z products ---- + // Permutation product commitments are used by the permutation + // identities in the quotient numerator and later by PCS openings. + let perm_z_walk := PERM_Z_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0300) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(perm_z_walk, proof_cptr, 0x80) + perm_z_walk := add(perm_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // ---- lookup helpers + accumulators (per-lookup) ---- + // Each lookup contributes zero or more helper commitments followed + // by its lookup accumulator Z commitment. The generated layout keeps + // helper commitments and accumulator commitments in separate memory + // regions because the quotient/PCS schedules address them + // differently. + let lookup_helper_walk := LOOKUP_HELPER_COMMS_MPTR_BASE + let lookup_z_walk := LOOKUP_Z_COMMS_MPTR_BASE + // lookup 0: 1 helper(s) + 1 acc + // Helper commitments for lookup 0. + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_helper_walk, proof_cptr, 0x80) + lookup_helper_walk := add(lookup_helper_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // Accumulator commitment for lookup 0. This is + // always one G1 when the lookup section is present. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_z_walk, proof_cptr, 0x80) + lookup_z_walk := add(lookup_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + // lookup 1: 1 helper(s) + 1 acc + // Helper commitments for lookup 1. + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_helper_walk, proof_cptr, 0x80) + lookup_helper_walk := add(lookup_helper_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // Accumulator commitment for lookup 1. This is + // always one G1 when the lookup section is present. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_z_walk, proof_cptr, 0x80) + lookup_z_walk := add(lookup_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // ---- trash_challenge ---- + // Midnight squeezes this challenge unconditionally, even when the + // circuit has no trash arguments. + // Keeping this squeeze unconditional preserves transcript + // compatibility across circuits with and without trash columns. + buf_len := squeeze_to(buf_len, TRASH_CHALLENGE_MPTR) + // ---- trashcans ---- + // Trashcan commitments are optional, but when present they are + // absorbed before y so the quotient batching challenge binds them. + let trashcan_walk := TRASHCAN_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(trashcan_walk, proof_cptr, 0x80) + trashcan_walk := add(trashcan_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- y ---- + // y batches all quotient identities. Quotient commitments are read + // only after y is sampled, matching the Rust verifier flow. + buf_len := squeeze_to(buf_len, Y_MPTR) + + // ---- quotient commitment(s) ---- + // Each uncompressed quotient commitment is calldatacopied directly to + // QUOTIENT_LIMB_COMMS_MPTR_BASE; the Horner fold below reads + // them back from memory. common_uncompressed_g1 absorbs the + // 128-byte calldata form into the transcript verbatim. + // + // Multi-limb quotient mode reads several Q_i commitments; single-H + // mode renders this loop with one limb. + let quotient_walk := QUOTIENT_LIMB_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0200) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(quotient_walk, proof_cptr, 0x80) + quotient_walk := add(quotient_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- x ---- + // x is the main evaluation point. Values read after this point are + // alleged polynomial evaluations at x or derived PCS openings. + buf_len := squeeze_to(buf_len, X_MPTR) + + // ---- evaluations ---- + // Optimisation H3: the off-chain Solidity proof shim rewrites + // proof scalars into BE calldata words. Spill each decoded eval + // into REVERSED_EVALS_MPTR in the same iteration we range-check + // it, so downstream references can use cheap mload. + // + // The Rust verifier conceptually reads evaluations in query order. + // The lowering plan arranges REVERSED_EVALS_MPTR in the order used + // by the quotient VM/direct evaluator, hence the generated name. + { + let eval_buf := REVERSED_EVALS_MPTR + for { let end := add(proof_cptr, 0x0cc0) } + lt(proof_cptr, end) + {} { + let eval := calldataload(proof_cptr) + // Proof evaluation scalars must be canonical Fr elements + // before they are absorbed or made available to quotient + // reconstruction. + if iszero(lt(eval, r)) { revert(0, 0) } + // Spill for quotient numerator and PCS codegen. + mstore(eval_buf, eval) + eval_buf := add(eval_buf, 0x20) + // Absorb the exact BE field word used by the native + // Keccak transcript. + buf_len := common_word(buf_len, eval) + proof_cptr := add(proof_cptr, 0x20) + } + } + + // ---- x1, x2 ---- + // x1 and x2 batch the KZG multi-opening reduction. They are + // squeezed after all polynomial evaluations are absorbed. + buf_len := squeeze_to(buf_len, X1_MPTR) + buf_len := squeeze_to(buf_len, X2_MPTR) + + // ---- f_com (1 uncompressed G1) ---- + // f_com is the commitment to the batched polynomial used by the PCS + // multi-open protocol. It is both transcript material and later + // pairing/MSM input. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(F_COM_MPTR, proof_cptr, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // ---- x3 ---- + // x3 is the PCS evaluation point for f_com. + buf_len := squeeze_to(buf_len, X3_MPTR) + // truncated-challenges mirrors midnight-proofs + // proofs/src/poly/kzg/mod.rs: + // - x3 is the f_com evaluation point and is truncated + // immediately after squeeze. + // - x1 and x4 remain full squeezed Fr words, but later PCS + // batching stores truncate(x1^i) and truncate(x4^i) while + // keeping the internal power accumulators full precision. + // This direct x3 mask is therefore one part of the PCS truncation + // rule, not the only truncated value used by the verifier. + mstore(X3_MPTR, and(mload(X3_MPTR), 0xffffffffffffffffffffffffffffffff)) + + // ---- q_evals (one Fq per point set) ---- + // q_evals are not spilled into REVERSED_EVALS_MPTR because the PCS + // emitter reads them as a contiguous calldata range from the saved + // Q_EVAL_CPTR_MPTR cursor. + // + // Each q_eval is the claimed evaluation for one prepared point set + // in the KZG multi-open reduction. They are still transcript + // material and must be range-checked as Fr scalars. + mstore(Q_EVAL_CPTR_MPTR, proof_cptr) + for { let end := add(proof_cptr, 0xa0) } + lt(proof_cptr, end) + {} { + let eval := calldataload(proof_cptr) + // Canonical Fr check before transcript absorption. + if iszero(lt(eval, r)) { revert(0, 0) } + buf_len := common_word(buf_len, eval) + proof_cptr := add(proof_cptr, 0x20) + } + + // ---- x4 ---- + // x4 is the final PCS batching challenge, sampled after q_evals + // and before the opening proof point pi. + buf_len := squeeze_to(buf_len, X4_MPTR) + + // ---- pi (1 uncompressed G1) ---- + // pi is the KZG opening proof commitment. It is the last proof + // object absorbed into the transcript and later becomes one side of + // the final pairing check. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(PI_MPTR, proof_cptr, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // The hand-rolled proof parser must consume exactly the ABI + // `proof` bytes before the `instances` length word. This is + // redundant with the generated proof length today, but makes + // future proof-layout drift fail closed. + // + // NUM_INSTANCE_CPTR is the calldata word immediately after the + // dynamic proof bytes payload. If proof_cptr lands anywhere else, + // some section was under-read or over-read. + if iszero(eq(proof_cptr, NUM_INSTANCE_CPTR)) { revert(0, 0) } + + // `success` carries deferred canonicality failures from public + // instance reads. G1/proof scalar helpers revert immediately. + if iszero(success) { revert(0, 0) } + + // =============================================================== + // Lagrange & instance-evaluation block (pure Fr arithmetic). + // =============================================================== + { + let k := 20 + let x := mload(X_MPTR) + // Compute x^n by repeated squaring, with n = 2^k. + let x_n := x + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { + x_n := mulmod(x_n, x_n, r) + } + + let omega := mload(OMEGA_MPTR) + + // First pass writes denominators (x - omega_i) for every + // Lagrange value needed below, then appends x^n - 1. The + // batch inversion pass turns all of them into inverses in one + // modexp call. + let mptr := X_N_MPTR + let mptr_end := add(mptr, 0x0300) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } + lt(mptr, mptr_end) + { mptr := add(mptr, 0x20) } { + mstore(mptr, addmod(x, sub(r, pow_of_omega), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + let x_n_minus_1 := addmod(x_n, sub(r, 1), r) + mstore(mptr_end, x_n_minus_1) + success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20), BATCH_INV_SCRATCH_MPTR, r) + + // Convert inverted denominators into Lagrange evaluations: + // L_i(x) = (x^n - 1) * n^-1 * omega_i / (x - omega_i). + mptr := X_N_MPTR + let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } + lt(mptr, mptr_end) + { mptr := add(mptr, 0x20) } { + mstore(mptr, mulmod(l_i_common, mulmod(mload(mptr), pow_of_omega, r), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + + // l_blind is the sum of the negative-rotation Lagrange terms + // used by the midnight-proofs blinding identity. + let l_blind := mload(add(X_N_MPTR, 0x20)) + let l_i_cptr := add(X_N_MPTR, 0x40) + for { let l_i_cptr_end := add(X_N_MPTR, 0x0140) } + lt(l_i_cptr, l_i_cptr_end) + { l_i_cptr := add(l_i_cptr, 0x20) } { + l_blind := addmod(l_blind, mload(l_i_cptr), r) + } + + // Public instance polynomial evaluation at x. Instance words + // have already been range-checked and absorbed in transcript + // order; this loop only forms the linear combination. + let instance_eval := 0 + for { + let instance_cptr := INSTANCE_CPTR + let instance_cptr_end := add(instance_cptr, 0x01c0) + } + lt(instance_cptr, instance_cptr_end) + { instance_cptr := add(instance_cptr, 0x20) + l_i_cptr := add(l_i_cptr, 0x20) } { + instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r) + } + + // Persist the derived values into named memory slots consumed + // by quotient reconstruction and PCS preparation. + let x_n_minus_1_inv := mload(mptr_end) + let l_last := mload(X_N_MPTR) + let l_0 := mload(add(X_N_MPTR, 0x0140)) + + mstore(X_N_MPTR, x_n) + mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv) + mstore(L_LAST_MPTR, l_last) + mstore(L_BLIND_MPTR, l_blind) + mstore(L_0_MPTR, l_0) + mstore(INSTANCE_EVAL_MPTR, instance_eval) + } + + if iszero(success) { revert(0, 0) } + + + // =============================================================== + // External batched identity numerator reconstruction. + // + // The quotient evaluator receives the verifier memory image from + // QUOTIENT_FRAME_BASE..+QUOTIENT_FRAME_LEN, reconstructs the same + // y-batched numerator, and returns: + // word 0: magic/version + // word 1: linearization expected eval + // word 2..: simple-selector accumulators + // =============================================================== + { + let q_out := QUOTIENT_RETURN_MPTR + // The quotient evaluator is as correctness-critical as the VK: + // it reconstructs the y-batched identity numerator and + // selector buckets. Re-check the pinned runtime before every + // external call, mirroring the VK freshness guard above. + if iszero(and( + eq(extcodesize(quotientEvaluator), EXPECTED_QUOTIENT_LENGTH), + eq(extcodehash(quotientEvaluator), EXPECTED_QUOTIENT_CODEHASH_WORD) + )) { revert(0, 0) } + if iszero(staticcall(gas(), quotientEvaluator, 0x3680, 0x6ac0, q_out, 0x0180)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x0180)) { revert(0, 0) } + if iszero(eq(mload(q_out), 0x00000000000000000000000000000000000000000000000051554556414c0001)) { revert(0, 0) } + // Word 1 is the negated y-batched identity numerator, stored + // in the same memory slot used by the monolithic path. + mstore(QUOTIENT_EVAL_MPTR, mload(add(q_out, 0x20))) + // Remaining return words are selector linearization buckets. + // Copy them back into the canonical selector accumulator region + // so the PCS code path is identical for split and monolithic + // quotient renders. + for { let q_i := 0 } lt(q_i, 10) { q_i := add(q_i, 1) } { + mstore(add(SELECTOR_ACC_MPTR, shl(5, q_i)), mload(add(q_out, add(0x40, shl(5, q_i))))) + } + } + + // =============================================================== + // Prepare linearization scalars for the final PCS MSM. + // + // The linearized commitment is + // (1 - x^n) * Σ_i x_split^i * Q_i + // + Σ_j sel_acc_j * S_j_com, + // where x_split = x^(n-1). Instead of materializing that point + // with a standalone G1MSM here, PCS block 5 expands the + // linearized commitment into its quotient and selector + // pairs inside the already-fused final MSM. + // + // QUOTIENT_MPTR is no longer a G1 point in this path. Its first + // two words carry: + // word 0: x_split + // word 1: one_minus_x_n + // =============================================================== + { + let x := mload(X_MPTR) + let k := 20 + // Compute both x^n and x^(n-1) with the same squaring walk: + // x_pow_2i tracks x^(2^i), while x_pow_2i_minus1 tracks + // x^(2^i - 1). + let x_pow_2i := x + let x_pow_2i_minus1 := 1 + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { + x_pow_2i_minus1 := mulmod( + mulmod(x_pow_2i_minus1, x_pow_2i_minus1, r), + x, + r + ) + x_pow_2i := mulmod(x_pow_2i, x_pow_2i, r) + } + let x_split := x_pow_2i_minus1 + let one_minus_x_n := addmod(1, sub(r, x_pow_2i), r) + + // PCS block 5 interprets this 2-word payload as scalar + // metadata, not as a materialized G1 point. + mstore(QUOTIENT_MPTR, x_split) + mstore(add(QUOTIENT_MPTR, 0x20), one_minus_x_n) + } + + // =============================================================== + // PCS computation (multi-prepare emitter from Step 5). + // + // The Rust lowering stage has already expanded the KZG multi-open + // equation into a sequence of generated Yul sub-blocks. Those + // blocks populate: + // - F_EVAL_MPTR / V_MPTR scalar batching values; + // - FINAL_COM_MPTR for the fused commitment MSM; + // - PAIRING_LHS_MPTR and PAIRING_RHS_MPTR for the final pairing. + // =============================================================== + { + // Generated PCS sub-block 1. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // 4 distinct rotation(s) + let x := mload(X_MPTR) + let omega := mload(OMEGA_MPTR) + let omega_inv := mload(OMEGA_INV_MPTR) + let x_pow_of_omega := x + mstore(add(ROT_POINTS_MPTR, 0x40), x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega, r) + mstore(add(ROT_POINTS_MPTR, 0x60), x_pow_of_omega) + x_pow_of_omega := x + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + mstore(add(ROT_POINTS_MPTR, 0x20), x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + mstore(add(ROT_POINTS_MPTR, 0x0), x_pow_of_omega) + } + // Generated PCS sub-block 2. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // pre-compute 43 x1 power(s) + let x1 := mload(X1_MPTR) + mstore(X1_POWERS_MPTR, 1) + let acc := 1 + let p := X1_POWERS_MPTR + for { let i := 0 } lt(i, 0x2a) { i := add(i, 1) } { + p := add(p, 0x20) + acc := mulmod(acc, x1, r) + mstore(p, and(acc, 0xffffffffffffffffffffffffffffffff)) + } + } + // Generated PCS sub-block 3. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[0]: 43 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x9980) + mstore(0xb2a0, 0x9480) + mstore(0xb2c0, 0xa020) + mstore(0xb2e0, 0xa040) + mstore(0xb300, 0xa0a0) + mstore(0xb320, 0xa0c0) + mstore(0xb340, 0xa120) + mstore(0xb360, 0x99a0) + mstore(0xb380, 0x99c0) + mstore(0xb3a0, 0x99e0) + mstore(0xb3c0, 0x9a00) + mstore(0xb3e0, 0x9a20) + mstore(0xb400, 0x9a40) + mstore(0xb420, 0x9a60) + mstore(0xb440, 0x9a80) + mstore(0xb460, 0x9aa0) + mstore(0xb480, 0x9ac0) + mstore(0xb4a0, 0x9ae0) + mstore(0xb4c0, 0x9b00) + mstore(0xb4e0, 0x9b20) + mstore(0xb500, 0x9b40) + mstore(0xb520, 0x9b60) + mstore(0xb540, 0x9b80) + mstore(0xb560, 0x9ba0) + mstore(0xb580, 0x9bc0) + mstore(0xb5a0, 0x9be0) + mstore(0xb5c0, 0x9c00) + mstore(0xb5e0, 0x9c20) + mstore(0xb600, 0x9c40) + mstore(0xb620, 0x9c60) + mstore(0xb640, 0x9c80) + mstore(0xb660, 0x9ca0) + mstore(0xb680, 0x9cc0) + mstore(0xb6a0, 0x9ce0) + mstore(0xb6c0, 0x9d00) + mstore(0xb6e0, 0x9d20) + mstore(0xb700, 0x9d40) + mstore(0xb720, 0x9d60) + mstore(0xb740, 0x9d80) + mstore(0xb760, 0x9da0) + mstore(0xb780, 0x9dc0) + mstore(0xb7a0, 0x9de0) + mstore(0xb7c0, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x9980) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x20) + for { let i := 1 } lt(i, 0x2b) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x20) + } + mstore(add(Q_EVAL_SET_MPTR, 0x0), q_eval_set_0) + } + // Generated PCS sub-block 4. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[1]: 3 commitment(s) + let q_eval_set_0 := mload(0x9660) + let q_eval_set_1 := mload(0x9920) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x9680), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9940), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x96a0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9960), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) + } + // Generated PCS sub-block 5. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[2]: 3 commitment(s) + let q_eval_set_0 := mload(0x9fe0) + let q_eval_set_1 := mload(0xa000) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa060), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa080), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa0e0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa100), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) + } + // Generated PCS sub-block 6. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[3]: 11 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x94a0) + mstore(0xb2a0, 0x9540) + mstore(0xb2c0, 0x97c0) + mstore(0xb2e0, 0x94c0) + mstore(0xb300, 0x9560) + mstore(0xb320, 0x97e0) + mstore(0xb340, 0x94e0) + mstore(0xb360, 0x9580) + mstore(0xb380, 0x9800) + mstore(0xb3a0, 0x9500) + mstore(0xb3c0, 0x96c0) + mstore(0xb3e0, 0x9820) + mstore(0xb400, 0x9520) + mstore(0xb420, 0x96e0) + mstore(0xb440, 0x9840) + mstore(0xb460, 0x95a0) + mstore(0xb480, 0x9700) + mstore(0xb4a0, 0x9860) + mstore(0xb4c0, 0x95c0) + mstore(0xb4e0, 0x9720) + mstore(0xb500, 0x9880) + mstore(0xb520, 0x95e0) + mstore(0xb540, 0x9740) + mstore(0xb560, 0x98a0) + mstore(0xb580, 0x9600) + mstore(0xb5a0, 0x9760) + mstore(0xb5c0, 0x98c0) + mstore(0xb5e0, 0x9620) + mstore(0xb600, 0x9780) + mstore(0xb620, 0x98e0) + mstore(0xb640, 0x9640) + mstore(0xb660, 0x97a0) + mstore(0xb680, 0x9900) + let q_eval_set_0 := mload(0x94a0) + let q_eval_set_1 := mload(0x9540) + let q_eval_set_2 := mload(0x97c0) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x60) + for { let i := 1 } lt(i, 0xb) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(mload(add(eval_p, 0x20))), pow, r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(mload(add(eval_p, 0x40))), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x60) + } + mstore(add(Q_EVAL_SET_MPTR, 0xa0), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0xc0), q_eval_set_1) + mstore(add(Q_EVAL_SET_MPTR, 0xe0), q_eval_set_2) + } + // Generated PCS sub-block 7. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[4]: 5 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x9e00) + mstore(0xb2a0, 0x9e20) + mstore(0xb2c0, 0x9e40) + mstore(0xb2e0, 0x9e60) + mstore(0xb300, 0x9e80) + mstore(0xb320, 0x9ea0) + mstore(0xb340, 0x9ec0) + mstore(0xb360, 0x9ee0) + mstore(0xb380, 0x9f00) + mstore(0xb3a0, 0x9f20) + mstore(0xb3c0, 0x9f40) + mstore(0xb3e0, 0x9f60) + mstore(0xb400, 0x9f80) + mstore(0xb420, 0x9fa0) + mstore(0xb440, 0x9fc0) + let q_eval_set_0 := mload(0x9e00) + let q_eval_set_1 := mload(0x9e20) + let q_eval_set_2 := mload(0x9e40) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x60) + for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(mload(add(eval_p, 0x20))), pow, r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(mload(add(eval_p, 0x40))), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x60) + } + mstore(add(Q_EVAL_SET_MPTR, 0x100), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x120), q_eval_set_1) + mstore(add(Q_EVAL_SET_MPTR, 0x140), q_eval_set_2) + } + // Generated PCS sub-block 8. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // f_eval via Horner over 5 reversed set(s) + let x2 := mload(X2_MPTR) + let x3 := mload(X3_MPTR) + let f_eval := 0 + let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) + let rot_pt_0 := mload(add(ROT_POINTS_MPTR, 0x0)) + let rot_pt_1 := mload(add(ROT_POINTS_MPTR, 0x20)) + let rot_pt_2 := mload(add(ROT_POINTS_MPTR, 0x40)) + let rot_pt_3 := mload(add(ROT_POINTS_MPTR, 0x60)) + // --- set 4 (cardinality 3) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let dx_2 := addmod(x3, sub(r, rot_pt_0), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_0), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_0), r), r) + let lbasis_2 := 1 + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_0, sub(r, rot_pt_2), r), r) + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_0, sub(r, rot_pt_3), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, dx_2, r) + let bp_3 := mulmod(bp_2, lbasis_0, r) + let bp_4 := mulmod(bp_3, lbasis_1, r) + let bp_5 := mulmod(bp_4, lbasis_2, r) + let bq := scalar_inv(bp_5) + let lbasis_inv_2 := mulmod(bq, bp_4, r) + bq := mulmod(bq, lbasis_2, r) + let lbasis_inv_1 := mulmod(bq, bp_3, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_2 := mulmod(bq, bp_1, r) + bq := mulmod(bq, dx_2, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + den_inv := mulmod(den_inv, dx_inv_2, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x100)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x120)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + let term_2 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x140)), dx_inv_2, r), lbasis_inv_2, r) + eval := addmod(eval, sub(r, term_2), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 3 (cardinality 3) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let dx_2 := addmod(x3, sub(r, rot_pt_1), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_1), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_1), r), r) + let lbasis_2 := 1 + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_1, sub(r, rot_pt_2), r), r) + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_1, sub(r, rot_pt_3), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, dx_2, r) + let bp_3 := mulmod(bp_2, lbasis_0, r) + let bp_4 := mulmod(bp_3, lbasis_1, r) + let bp_5 := mulmod(bp_4, lbasis_2, r) + let bq := scalar_inv(bp_5) + let lbasis_inv_2 := mulmod(bq, bp_4, r) + bq := mulmod(bq, lbasis_2, r) + let lbasis_inv_1 := mulmod(bq, bp_3, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_2 := mulmod(bq, bp_1, r) + bq := mulmod(bq, dx_2, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + den_inv := mulmod(den_inv, dx_inv_2, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xa0)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xc0)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + let term_2 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xe0)), dx_inv_2, r), lbasis_inv_2, r) + eval := addmod(eval, sub(r, term_2), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 2 (cardinality 2) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, lbasis_0, r) + let bp_3 := mulmod(bp_2, lbasis_1, r) + let bq := scalar_inv(bp_3) + let lbasis_inv_1 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_1, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x60)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x80)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 1 (cardinality 2) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_1), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_1), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_1, sub(r, rot_pt_2), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, lbasis_0, r) + let bp_3 := mulmod(bp_2, lbasis_1, r) + let bq := scalar_inv(bp_3) + let lbasis_inv_1 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_1, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x20)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x40)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 0 (cardinality 1) --- + { + let dx0 := addmod(x3, sub(r, rot_pt_2), r) + let dx0_inv := scalar_inv(dx0) + let eval := mulmod(addmod(calldataload(add(Q_EVAL_CPTR, 0x0)), sub(r, mload(add(Q_EVAL_SET_MPTR, 0x0))), r), dx0_inv, r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + mstore(F_EVAL_MPTR, f_eval) + } + // Generated PCS sub-block 9. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // build final_com and v (KZG single-opening proof, fused MSM) + // final MSM input length from circuit/VK shape: 78 term(s) + let x4 := mload(X4_MPTR) + let lin_x_split := mload(QUOTIENT_MPTR) + let lin_one_minus_x_n := mload(add(QUOTIENT_MPTR, 0x20)) + let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) + let x4_pow_full := 1 + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_1 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_2 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_3 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_4 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_5 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + let v := calldataload(Q_EVAL_CPTR) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), x4_pow_4, r), r) + v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_5, r), r) + mcopy(0xb280, 0xa840, 0x80) + mstore(0xb300, 1) + mcopy(0xb320, 0xa8c0, 0x80) + mstore(0xb3a0, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0xb3c0, 0xacc0, 0x80) + mstore(0xb440, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0xb460, 0xa940, 0x80) + mstore(0xb4e0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0xb500, 0xad40, 0x80) + mstore(0xb580, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0xb5a0, 0xaec0, 0x80) + mstore(0xb620, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0xb640, 0x6700, 0x80) + mstore(0xb6c0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0xb6e0, 0x6480, 0x80) + mstore(0xb760, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0xb780, 0x6500, 0x80) + mstore(0xb800, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0xb820, 0x6580, 0x80) + mstore(0xb8a0, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0xb8c0, 0x6600, 0x80) + mstore(0xb940, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0xb960, 0x6680, 0x80) + mstore(0xb9e0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0xba00, 0x6280, 0x80) + mstore(0xba80, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0xbaa0, 0x6300, 0x80) + mstore(0xbb20, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0xbb40, 0x6380, 0x80) + mstore(0xbbc0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0xbbe0, 0x6400, 0x80) + mstore(0xbc60, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0xbc80, 0x6780, 0x80) + mstore(0xbd00, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0xbd20, 0x6800, 0x80) + mstore(0xbda0, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0xbdc0, 0x6880, 0x80) + mstore(0xbe40, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0xbe60, 0x6900, 0x80) + mstore(0xbee0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0xbf00, 0x6b00, 0x80) + mstore(0xbf80, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0xbfa0, 0x6c00, 0x80) + mstore(0xc020, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0xc040, 0x6f80, 0x80) + mstore(0xc0c0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0xc0e0, 0x7000, 0x80) + mstore(0xc160, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0xc180, 0x7080, 0x80) + mstore(0xc200, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0xc220, 0x7100, 0x80) + mstore(0xc2a0, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0xc2c0, 0x7180, 0x80) + mstore(0xc340, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0xc360, 0x7200, 0x80) + mstore(0xc3e0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0xc400, 0x7280, 0x80) + mstore(0xc480, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0xc4a0, 0x7300, 0x80) + mstore(0xc520, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0xc540, 0x7380, 0x80) + mstore(0xc5c0, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0xc5e0, 0x7400, 0x80) + mstore(0xc660, mload(add(X1_POWERS_MPTR, 0x400))) + mcopy(0xc680, 0x7480, 0x80) + mstore(0xc700, mload(add(X1_POWERS_MPTR, 0x420))) + mcopy(0xc720, 0x7500, 0x80) + mstore(0xc7a0, mload(add(X1_POWERS_MPTR, 0x440))) + mcopy(0xc7c0, 0x7580, 0x80) + mstore(0xc840, mload(add(X1_POWERS_MPTR, 0x460))) + mcopy(0xc860, 0x7600, 0x80) + mstore(0xc8e0, mload(add(X1_POWERS_MPTR, 0x480))) + mcopy(0xc900, 0x7680, 0x80) + mstore(0xc980, mload(add(X1_POWERS_MPTR, 0x4a0))) + mcopy(0xc9a0, 0x7700, 0x80) + mstore(0xca20, mload(add(X1_POWERS_MPTR, 0x4c0))) + mcopy(0xca40, 0x7780, 0x80) + mstore(0xcac0, mload(add(X1_POWERS_MPTR, 0x4e0))) + mcopy(0xcae0, 0x7800, 0x80) + mstore(0xcb60, mload(add(X1_POWERS_MPTR, 0x500))) + mcopy(0xcb80, 0x7880, 0x80) + mstore(0xcc00, mload(add(X1_POWERS_MPTR, 0x520))) + let lin_query_scalar_41 := mload(add(X1_POWERS_MPTR, 0x540)) + let lin_cur_scalar_41 := mulmod(lin_query_scalar_41, lin_one_minus_x_n, r) + mcopy(0xcc20, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0xcca0, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xccc0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0xcd40, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xcd60, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0xcde0, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xce00, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0xce80, lin_cur_scalar_41) + mcopy(0xcea0, 0x6980, 0x80) + mstore(0xcf20, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0xcf40, 0x6a00, 0x80) + mstore(0xcfc0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0xcfe0, 0x6a80, 0x80) + mstore(0xd060, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0xd080, 0x6b80, 0x80) + mstore(0xd100, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0xd120, 0x6c80, 0x80) + mstore(0xd1a0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0xd1c0, 0x6d00, 0x80) + mstore(0xd240, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0xd260, 0x6d80, 0x80) + mstore(0xd2e0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0xd300, 0x6e00, 0x80) + mstore(0xd380, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0xd3a0, 0x6e80, 0x80) + mstore(0xd420, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0xd440, 0x6f00, 0x80) + mstore(0xd4c0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0xd4e0, 0xa6c0, 0x80) + mstore(0xd560, x4_pow_1) + mcopy(0xd580, 0xa740, 0x80) + mstore(0xd600, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0xd620, 0xa7c0, 0x80) + mstore(0xd6a0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0xd6c0, 0xac40, 0x80) + mstore(0xd740, x4_pow_2) + mcopy(0xd760, 0xadc0, 0x80) + mstore(0xd7e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0xd800, 0xae40, 0x80) + mstore(0xd880, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0xd8a0, 0xa140, 0x80) + mstore(0xd920, x4_pow_3) + mcopy(0xd940, 0xa1c0, 0x80) + mstore(0xd9c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xd9e0, 0xa240, 0x80) + mstore(0xda60, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_3, r)) + mcopy(0xda80, 0xa2c0, 0x80) + mstore(0xdb00, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_3, r)) + mcopy(0xdb20, 0xa340, 0x80) + mstore(0xdba0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_3, r)) + mcopy(0xdbc0, 0xa3c0, 0x80) + mstore(0xdc40, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_3, r)) + mcopy(0xdc60, 0xa440, 0x80) + mstore(0xdce0, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_3, r)) + mcopy(0xdd00, 0xa4c0, 0x80) + mstore(0xdd80, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_3, r)) + mcopy(0xdda0, 0xa540, 0x80) + mstore(0xde20, mulmod(mload(add(X1_POWERS_MPTR, 0x100)), x4_pow_3, r)) + mcopy(0xde40, 0xa5c0, 0x80) + mstore(0xdec0, mulmod(mload(add(X1_POWERS_MPTR, 0x120)), x4_pow_3, r)) + mcopy(0xdee0, 0xa640, 0x80) + mstore(0xdf60, mulmod(mload(add(X1_POWERS_MPTR, 0x140)), x4_pow_3, r)) + mcopy(0xdf80, 0xa9c0, 0x80) + mstore(0xe000, x4_pow_4) + mcopy(0xe020, 0xaa40, 0x80) + mstore(0xe0a0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_4, r)) + mcopy(0xe0c0, 0xaac0, 0x80) + mstore(0xe140, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_4, r)) + mcopy(0xe160, 0xab40, 0x80) + mstore(0xe1e0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_4, r)) + mcopy(0xe200, 0xabc0, 0x80) + mstore(0xe280, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_4, r)) + mcopy(0xe2a0, F_COM_MPTR, 0x80) + mstore(0xe320, x4_pow_5) + if success { + success := staticcall(gas(), 0x0c, 0xb280, 0x30c0, FINAL_COM_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mstore(V_MPTR, v) + } + // Generated PCS sub-block 10. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // Scale z*pi - vG before the final pairing check + // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) + mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) + if success { + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(0x1080, FINAL_COM_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) + if success { + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + if success { + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) + } + } + + // Batch the prevalidated public IVC accumulator pairing equation + // into the final KZG pairing. + // + // We do not simply multiply the two pairing equations together: + // two bad equations could cancel. Instead, after all four G1 + // pairing inputs are fixed, derive a verifier-local randomizer + // alpha and check: + // + // e(kzg_rhs + alpha * acc_rhs, G2_BASE) + // * e(kzg_lhs + alpha * acc_lhs, NEG_S_G2_BASE) == 1 + // + // If either original equation is bad, this combined equation + // holds for at most one alpha in Fr. + { + let batch_ptr := 0x0100 + + // Domain || KZG rhs/lhs || accumulator rhs/lhs. + mstore(batch_ptr, 0x70616972696e672d62617463682d6163632d6b7a670000000000000000) + mcopy(add(batch_ptr, 0x20), PAIRING_RHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0xa0), PAIRING_LHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0x0120), ACC_RHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0x01a0), ACC_LHS_MPTR, 0x80) + // alpha is Fiat-Shamir over the fully materialized pairing + // inputs. Replace the negligible zero draw with one so the + // accumulator equation cannot be accidentally dropped. + let acc_pair_alpha := mod(keccak256(batch_ptr, 0x0220), r) + if iszero(acc_pair_alpha) { acc_pair_alpha := 1 } + + // PAIRING_RHS_MPTR += alpha * ACC_RHS_MPTR. + // First compute alpha * ACC_RHS with a one-pair G1MSM, then + // add it into the KZG RHS point. + mcopy(batch_ptr, ACC_RHS_MPTR, 0x80) + mstore(add(batch_ptr, 0x80), acc_pair_alpha) + if success { + success := staticcall(gas(), 0x0c, batch_ptr, 0xa0, batch_ptr, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(add(batch_ptr, 0x80), PAIRING_RHS_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, batch_ptr, 0x0100, PAIRING_RHS_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + + // PAIRING_LHS_MPTR += alpha * ACC_LHS_MPTR. + // Mirror the same randomized batching on the KZG LHS point. + mcopy(batch_ptr, ACC_LHS_MPTR, 0x80) + mstore(add(batch_ptr, 0x80), acc_pair_alpha) + if success { + success := staticcall(gas(), 0x0c, batch_ptr, 0xa0, batch_ptr, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(add(batch_ptr, 0x80), PAIRING_LHS_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, batch_ptr, 0x0100, PAIRING_LHS_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + } + + // The Yul `ec_pairing` helper checks + // e(arg0, G2_BASE) * e(arg1, NEG_S_G2_BASE) == 1 + // i.e. e(arg0, [1]_2) = e(arg1, [s]_2). + // + // The KZG pairing identity is + // e(final_com - v*G + x3*pi, [1]_2) = e(pi, [s]_2), + // so arg0 must be (final_com - v*G + x3*pi) and arg1 must be + // pi. The PAIRING_*_MPTR slots store + // PAIRING_LHS_MPTR := pi + // PAIRING_RHS_MPTR := final_com - v*G + x3*pi + // -- the historical "LHS"/"RHS" naming follows the dual MSM + // accumulator (left = pi, right = combined) and *not* the + // pairing argument order. Pass them swapped to ec_pairing. + if iszero(success) { revert(0, 0) } + success := ec_pairing(success, PAIRING_RHS_MPTR, PAIRING_LHS_MPTR) + + + + // Success path is terminal. Invalid inputs have already reverted, + // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } + mstore(RETURN_MPTR, 1) + return(RETURN_MPTR, 0x20) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/Halo2VerifyingKey.sol b/proofs/solidity-verifier/fixtures/ivc/Halo2VerifyingKey.sol new file mode 100644 index 000000000..f8c08652b --- /dev/null +++ b/proofs/solidity-verifier/fixtures/ivc/Halo2VerifyingKey.sol @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity ^0.8.24; + +/// @title Halo2 BLS12-381 verifying-key payload. +/// @notice Contract whose deployed runtime is `INVALID || generated verifier-key payload`. +/// @dev Byte 0 is an unconditional INVALID opcode so direct calls cannot execute payload bytes as code. The linked verifier pins the full runtime by length/codehash and copies the payload starting at byte 1. +/// @dev The layout follows the verifier inputs derived from +/// `midfall/proofs/src/plonk/mod.rs::VerifyingKey` and the transcript +/// `vk.hash_into` behavior used by `midfall/proofs/src/plonk/verifier.rs`. +/// +/// Layout (in 32-byte words, big-endian). The header slots are generated from +/// Rust's `VkHeaderLayout`; the byte offsets are absolute from the start of the +/// VK payload, not from byte 0 of the runtime. Runtime byte 0 is the INVALID +/// prefix; the verifier loads the payload via +/// `extcodecopy(vk, VK_MPTR, 0x01, vk_payload_len)` and then references each +/// slot by `VK_MPTR + i`. +/// +/// word 0 : vk_digest (Fq, transcript_repr of the CS) +/// word 1 : num_instances +/// word 2 : k (log2 of the domain size) +/// word 3 : n_inv (1/n in Fr) +/// word 4 : omega (n-th primitive root of unity) +/// word 5 : omega_inv +/// word 6 : omega_inv_to_l (omega_inv ^ |rotation_last|) +/// word 7 : has_accumulator (0 or 1) +/// word 8 : acc_offset (instance index of the accumulator) +/// word 9 : num_acc_limbs +/// word 10 : num_acc_limb_bits +/// word 11..14 : G1_BASE (4 words, EIP-2537 padded) +/// word 15..22 : G2_BASE (8 words, EIP-2537 padded) +/// word 23..30 : NEG_S_G2_BASE (8 words, EIP-2537 padded) +/// word 31..30 + Q_PAYLOAD : quotient VM constants + packed bytecode +/// word 31 + Q_PAYLOAD .. : fixed_comms (4 words each) +/// word 31 + Q_PAYLOAD + 4*N_FIXED .. +/// : permutation_comms (4 words each) +/// +/// Notes: +/// - `extcodehash` of this contract is pinned by the linked verifier via +/// `EXPECTED_VK_CODEHASH`, so any byte tweak is detected at deploy time. +/// - The quotient identity interpreter's static program is stored in this +/// pinned VK runtime. The verifier reads it from memory after `extcodecopy`, +/// avoiding verifier-side PUSH32/mstore immediates while keeping the program +/// covered by `EXPECTED_VK_CODEHASH`. +/// - The midnight-proofs migration bakes the per-lookup chunk counts, trashcan +/// structure, and `num_simple_selectors` into the generated verifier code. +contract Halo2VerifyingKey { + /// @notice Deploy the verifying-key payload as this contract's runtime bytecode. + /// @dev The constructor writes an INVALID byte followed by generated words into memory and returns that prefixed runtime. + /// @dev The transient construction buffer starts at `0x80`, preserving Solidity's reserved memory words. + constructor() { + assembly { + // Runtime layout: + // byte 0 : INVALID, so the payload cannot be executed + // byte 1..end : generated VK payload copied by Halo2Verifier + // + // `runtime` includes the INVALID prefix; `payload` points to word + // zero of the verifier-key data described in the contract NatSpec. + let runtime := 0x80 + let payload := add(runtime, 0x01) + mstore8(runtime, 0xfe) + // Header, base-point, and quotient-program words generated from + // VkPayloadLayout. The inline names on each mstore identify the + // exact slot in the rendered source. + mstore(add(payload, 0x0000), 0x04d431b03dc86a4ddf0aef1f258576d6df8e1b884a7aa7564fea2df1b04a0f86) // vk_digest + mstore(add(payload, 0x0020), 0x000000000000000000000000000000000000000000000000000000000000000e) // num_instances + mstore(add(payload, 0x0040), 0x0000000000000000000000000000000000000000000000000000000000000014) // k + mstore(add(payload, 0x0060), 0x73eda0144f284aae5b6554d46c21576b363d4ec725be2bff1a400fff00001001) // n_inv + mstore(add(payload, 0x0080), 0x03e1c54bcb947035a57a6e07cb98de4a2f69e02d265e09d9fece7e0e39898d4b) // omega + mstore(add(payload, 0x00a0), 0x6c39442eade0092768ac033fa6f608750624a1bb17dbc026ef97c3573a28fc8c) // omega_inv + mstore(add(payload, 0x00c0), 0x2a0ccbaa0613f093f2bb6e97859513f0b613d8587eaa92db9e5604b8d6b68d45) // omega_inv_to_l + mstore(add(payload, 0x00e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // has_accumulator + mstore(add(payload, 0x0100), 0x0000000000000000000000000000000000000000000000000000000000000004) // acc_offset + mstore(add(payload, 0x0120), 0x0000000000000000000000000000000000000000000000000000000000000007) // num_acc_limbs + mstore(add(payload, 0x0140), 0x0000000000000000000000000000000000000000000000000000000000000038) // num_acc_limb_bits + mstore(add(payload, 0x0160), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) // g1_x_hi + mstore(add(payload, 0x0180), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) // g1_x_lo + mstore(add(payload, 0x01a0), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) // g1_y_hi + mstore(add(payload, 0x01c0), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) // g1_y_lo + mstore(add(payload, 0x01e0), 0x00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051) // g2_x_c0_hi + mstore(add(payload, 0x0200), 0xc6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8) // g2_x_c0_lo + mstore(add(payload, 0x0220), 0x0000000000000000000000000000000013e02b6052719f607dacd3a088274f65) // g2_x_c1_hi + mstore(add(payload, 0x0240), 0x596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e) // g2_x_c1_lo + mstore(add(payload, 0x0260), 0x000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351a) // g2_y_c0_hi + mstore(add(payload, 0x0280), 0xadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801) // g2_y_c0_lo + mstore(add(payload, 0x02a0), 0x000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99) // g2_y_c1_hi + mstore(add(payload, 0x02c0), 0xcb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be) // g2_y_c1_lo + mstore(add(payload, 0x02e0), 0x0000000000000000000000000000000007acb569b3187c0fd1993980aa52a6e9) // neg_s_g2_x_c0_hi + mstore(add(payload, 0x0300), 0xe2080b9697fab96abd5c5f1c3b988256f2d99366f1bbccf13cf0e20702fee18c) // neg_s_g2_x_c0_lo + mstore(add(payload, 0x0320), 0x0000000000000000000000000000000004bbe1a24fcc4f988c6ef268d0c1160e) // neg_s_g2_x_c1_hi + mstore(add(payload, 0x0340), 0xac0a0c4f53d80bd74f3d2e4667be27408625a83825354e27c70859883102eb43) // neg_s_g2_x_c1_lo + mstore(add(payload, 0x0360), 0x00000000000000000000000000000000091f5fc856da557cdb852412d3fd2cef) // neg_s_g2_y_c0_hi + mstore(add(payload, 0x0380), 0x9034c9a66ce38bf356c49f6a012109440035923a9cd6c71ca0c8efa5b6badf52) // neg_s_g2_y_c0_lo + mstore(add(payload, 0x03a0), 0x000000000000000000000000000000000af8fa5434d3fdd8c90fe8e532246c49) // neg_s_g2_y_c1_hi + mstore(add(payload, 0x03c0), 0x9926d8d728ccbb4ac40381158e8da573b4895782bfdb788c8ff40ba22032eab3) // neg_s_g2_y_c1_lo + mstore(add(payload, 0x03e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // quotient_const + mstore(add(payload, 0x0400), 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140) // quotient_const + mstore(add(payload, 0x0420), 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a) // quotient_const + mstore(add(payload, 0x0440), 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db) // quotient_const + mstore(add(payload, 0x0460), 0x00bbe1fbe9ef1e2d62490b03a82bf9ef10f5e9b2323033669cf6c50481f63e05) // quotient_const + mstore(add(payload, 0x0480), 0x0000000000000000000000000000000000000000000000000100000000000000) // quotient_const + mstore(add(payload, 0x04a0), 0x0000000000000000000000000000000000010000000000000000000000000000) // quotient_const + mstore(add(payload, 0x04c0), 0x0000000000000000000000000000000000000000000000000000000400000000) // quotient_const + mstore(add(payload, 0x04e0), 0x0000000000000000000000000000000000000000040000000000000000000000) // quotient_const + mstore(add(payload, 0x0500), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x0520), 0x0000000000000000000000000000000000000000000000100000000000000000) // quotient_const + mstore(add(payload, 0x0540), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000) // quotient_const + mstore(add(payload, 0x0560), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefeffffff00000001) // quotient_const + mstore(add(payload, 0x0580), 0x73eda753299d7d483339d80809a1d80553bca402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x05a0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffb00000001) // quotient_const + mstore(add(payload, 0x05c0), 0x73eda753299d7d483339d80809a1d80553bda402fbfe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x05e0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffefffff001) // quotient_const + mstore(add(payload, 0x0600), 0x73eda753299d7d483339d80809a1d80553bda402fffe5beeffffffff00000001) // quotient_const + mstore(add(payload, 0x0620), 0x00000000000000000000000000000010ff0726c5de281020ad8016cf6f691213) // quotient_const + mstore(add(payload, 0x0640), 0x0000000000000000000000000000002c64068790f917282347187665718b04c8) // quotient_const + mstore(add(payload, 0x0660), 0x00000000000000000000000000000027241bb5338dce8a77499428839473bf3a) // quotient_const + mstore(add(payload, 0x0680), 0x0000000000000000000000000000002b7c4a26a1c7ae6fc4b499d04e4a463c4b) // quotient_const + mstore(add(payload, 0x06a0), 0x000000000000000000000000000000274bc40fcf526be95333a8c22c79465298) // quotient_const + mstore(add(payload, 0x06c0), 0x0000000000000000000000000000002a5ee6db49930276e2939d1c43ac82f744) // quotient_const + mstore(add(payload, 0x06e0), 0x73eda753299d7d483339d80809a1d7edd77e26c51c38afb5debf8afa00c15cc3) // quotient_const + mstore(add(payload, 0x0700), 0x73eda753299d7d483339d80809a1d7c553bda402fffe5bfeffffffff00000002) // quotient_const + mstore(add(payload, 0x0720), 0x73eda753299d7d483339d80809a1d80553bda402fffe53ebc627fffef6280001) // quotient_const + mstore(add(payload, 0x0740), 0x0000000000000000000001000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0760), 0x0000000100000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0780), 0x6bc66e553973f396854f5626172ba135587d41e37a68209402355093fdcaaf6c) // quotient_const + mstore(add(payload, 0x07a0), 0x63f31e3f446953960c9d6964474300df43ab29179970f642a28e39d6c883c74b) // quotient_const + mstore(add(payload, 0x07c0), 0x73eda753299d7d483339d70809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x07e0), 0x73eda752299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0800), 0x082738fdf02989b1adea81e1f27636cffb40621f85963b6afdcaaf6b02355095) // quotient_const + mstore(add(payload, 0x0820), 0x0ffa8913e53429b2269c6ea3c25ed72610127aeb668d65bc5d71c628377c38b6) // quotient_const + mstore(add(payload, 0x0840), 0x01ec1c0519185dfe86132d479c76d0786e1e037d0b05ca47648a1c5d29492c9b) // quotient_const + mstore(add(payload, 0x0860), 0x1e179025ca2470882b34e63940ccbd7ad9090bf414d43b696e093b5a8782528f) // quotient_const + mstore(add(payload, 0x0880), 0x4298bfee9a84c8ef8e83702075cb1abeb576f146636342e3db9ea6b0a4adf29d) // quotient_const + mstore(add(payload, 0x08a0), 0x3c83b078e9abed278d042acc8f3bd21e228716f04be96af8025da860e2d1bba9) // quotient_const + mstore(add(payload, 0x08c0), 0x427868260f487d1ef07edaadf37f5dbe705bd1318290f2577ae756b009c24f11) // quotient_const + mstore(add(payload, 0x08e0), 0x03020e6a35e595abd22838beeadc45cfcb0545d85ca0ab2c59d44c203fac84a7) // quotient_const + mstore(add(payload, 0x0900), 0x000000000000000000000000000000000000000000000000d201000000010000) // quotient_const + mstore(add(payload, 0x0920), 0x0000000100001b7c3f8d3fe3c5b448f1bdeb2ae34698b72d6ce966fc208c05ed) // quotient_const + mstore(add(payload, 0x0940), 0x73eda753299d7d483339d80809a1d7fd4057a4c12f26d1c1778e3360a6820001) // quotient_const + mstore(add(payload, 0x0960), 0x057797fa7060856f215654ff11006fe0acf6a437e9477bf6f782dfac86f2cf75) // quotient_const + mstore(add(payload, 0x0980), 0x0000000000000000000000000000000000000000000000000000000000000002) // quotient_const + mstore(add(payload, 0x09a0), 0x0000000000000000000000000000000000000000000000000200000000000000) // quotient_const + mstore(add(payload, 0x09c0), 0x0000000000000000000000000000000000020000000000000000000000000000) // quotient_const + mstore(add(payload, 0x09e0), 0x73eda753299d7d483339d80809a1d7e13511a4044eaa5bff4600ffff00005556) // quotient_const + mstore(add(payload, 0x0a00), 0x73eda753299d7d483339d80809a1d7c553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0a20), 0x73eda753299d7d483339d80809a1d7d340dd972492de594de627fffefcb80349) // quotient_const + mstore(add(payload, 0x0a40), 0x73eda753299d7d483339d80809a1d7dbdc391ab4d8ac003bbd48021b82456c9b) // quotient_const + mstore(add(payload, 0x0a60), 0x73eda753299d7d483339d80809a1d7d1448ee5caf5eefcffbc9fabc57759e23f) // quotient_const + mstore(add(payload, 0x0a80), 0x73eda753299d7d483339d80809a1d80418c74cc18bb28443d3978d1fd47ffce1) // quotient_const + mstore(add(payload, 0x0aa0), 0x0000000000000000000000000000006425c019bcda40056233b00000068ff970) // quotient_const + mstore(add(payload, 0x0ac0), 0x00000000000000000000000000000052ef09129c4ea4b786856ffbc6fb7526cc) // quotient_const + mstore(add(payload, 0x0ae0), 0x73eda753299d7d483339d80809a1d7d89a085d30fc8a7106a170ac2377c34ab9) // quotient_const + mstore(add(payload, 0x0b00), 0x73eda753299d7d483339d80809a1d7f8ce65bb936f2d836012914aca65f077e1) // quotient_const + mstore(add(payload, 0x0b20), 0x000000000000000000000000000000681e5d7c70141ebdfe86c0a873114c3b84) // quotient_const + mstore(add(payload, 0x0b40), 0x000000000000000000000000000000297784894e27525bc342b7fde37dba9366) // quotient_const + mstore(add(payload, 0x0b60), 0x0000000000000000000000000000000275ecae82e897af7658d0e5be57000640) // quotient_const + mstore(add(payload, 0x0b80), 0x000000000000000000000000000000013af65741744bd7bb2c6872df2b800320) // quotient_const + mstore(add(payload, 0x0ba0), 0x00000000000000000000000000000059736a8da406e7d5f0bd1ea7b710796a90) // quotient_const + mstore(add(payload, 0x0bc0), 0x0000000000000000000000000000000c8557e86f90d0d89eed6eb5349a0f8820) // quotient_const + mstore(add(payload, 0x0be0), 0x0453ae02a5f228d8f956b5eab4fc92bbeea5eb26b6ae4b42b4fdfcfdf026aa22) // quotient_const + mstore(add(payload, 0x0c00), 0x0000000000000000000000000000000000000000000000000000000800000000) // quotient_const + mstore(add(payload, 0x0c20), 0x0000000000000000000000000000000000000000080000000000000000000000) // quotient_const + mstore(add(payload, 0x0c40), 0x0000000000000000000000000000000000000000000000000000000000002000) // quotient_const + mstore(add(payload, 0x0c60), 0x0000000000000000000000000000000000000000000000200000000000000000) // quotient_const + mstore(add(payload, 0x0c80), 0x73eda753299d7d483339d80809a1d7f454b67d3d21d64bde527fe92f9096edee) // quotient_const + mstore(add(payload, 0x0ca0), 0x73eda753299d7d483339d80809a1d7d8efb71c7206e733dbb8e789998e74fb39) // quotient_const + mstore(add(payload, 0x0cc0), 0x73eda753299d7d483339d80809a1d7de2fa1eecf722fd187b66bd77b6b8c40c7) // quotient_const + mstore(add(payload, 0x0ce0), 0x73eda753299d7d483339d80809a1d7d9d7737d61384fec3a4b662fb0b5b9c3b6) // quotient_const + mstore(add(payload, 0x0d00), 0x73eda753299d7d483339d80809a1d7de07f99433ad9272abcc573dd286b9ad69) // quotient_const + mstore(add(payload, 0x0d20), 0x73eda753299d7d483339d80809a1d7daf4d6c8b96cfbe51c6c62e3bb537d08bd) // quotient_const + mstore(add(payload, 0x0d40), 0x00000000000000000000000000000021fe0e4d8bbc5020415b002d9eded22426) // quotient_const + mstore(add(payload, 0x0d60), 0x00000000000000000000000000000058c80d0f21f22e50468e30eccae3160990) // quotient_const + mstore(add(payload, 0x0d80), 0x0000000000000000000000000000004e48376a671b9d14ee9328510728e77e74) // quotient_const + mstore(add(payload, 0x0da0), 0x00000000000000000000000000000056f8944d438f5cdf896933a09c948c7896) // quotient_const + mstore(add(payload, 0x0dc0), 0x0000000000000000000000000000004e97881f9ea4d7d2a667518458f28ca530) // quotient_const + mstore(add(payload, 0x0de0), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb9194fffd) // quotient_const + mstore(add(payload, 0x0e00), 0x0000000000000000000002000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0e20), 0x0000000200000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0e40), 0x639f3557494a69e4d764d44424b56a655d3cdfc3f4d1e529046aa128fb955ed7) // quotient_const + mstore(add(payload, 0x0e60), 0x53f8952b5f3529e3e600fac084e429b93398ae2c32e39086451c73ae91078e95) // quotient_const + mstore(add(payload, 0x0e80), 0x72018b4e10851f49ad26aac06d2b078ce59fa085f4f891b79b75e3a1d6b6d366) // quotient_const + mstore(add(payload, 0x0ea0), 0x55d6172d5f790cc00804f1cec8d51a8a7ab4980eeb2a209591f6c4a4787dad72) // quotient_const + mstore(add(payload, 0x0ec0), 0x3154e7648f18b458a4b667e793d6bd469e46b2bc9c9b191b2461594e5b520d64) // quotient_const + mstore(add(payload, 0x0ee0), 0x3769f6da3ff19020a635ad3b7a6605e731368d12b414f106fda2579e1d2e4458) // quotient_const + mstore(add(payload, 0x0f00), 0x31753f2d1a55002942bafd5a16227a46e361d2d17d6d69a78518a94ef63db0f0) // quotient_const + mstore(add(payload, 0x0f20), 0x70eb98e8f3b7e79c61119f491ec5923588b85e2aa35db0d2a62bb3dec0537b5a) // quotient_const + mstore(add(payload, 0x0f40), 0x03d8380a3230bbfd0c265a8f38eda0f0dc3c06fa160b948ec91438ba52925936) // quotient_const + mstore(add(payload, 0x0f60), 0x3c2f204b9448e1105669cc7281997af5b21217e829a876d2dc1276b50f04a51e) // quotient_const + mstore(add(payload, 0x0f80), 0x1143d88a0b6c1496e9cd0838e1f45d7817303e89c6c829c8b73d4d62495be539) // quotient_const + mstore(add(payload, 0x0fa0), 0x0519b99ea9ba5d06e6ce7d9114d5cc36f15089dd97d479f104bb50c2c5a37751) // quotient_const + mstore(add(payload, 0x0fc0), 0x110328f8f4f37cf5adc3dd53dd5ce3778cf9fe60052388aff5cead6113849e21) // quotient_const + mstore(add(payload, 0x0fe0), 0x057797fa7060856f215655ff11006fee9a1697597c277945ddaadfac83aad2c0) // quotient_const + mstore(add(payload, 0x1000), 0x0000000000000000000000000000003212e00cde6d2002b119d800000347fcb8) // quotient_const + mstore(add(payload, 0x1020), 0x000000000000000000000000000000340f2ebe380a0f5eff4360543988a61dc2) // quotient_const + mstore(add(payload, 0x1040), 0x0000000000000000000000000000002cb9b546d20373eaf85e8f53db883cb548) // quotient_const + mstore(add(payload, 0x1060), 0x0453ae02a5f228d8f956b6eab50092aaff9ec460d8863b22077de62a80bd8812) // quotient_const + mstore(add(payload, 0x1080), 0x73eda753299d7d4833351088b4af7508df8b737010b26601ef73fcbb87bd0000) // quotient_const + mstore(add(payload, 0x10a0), 0x0aef2ff4e0c10ade42aca9fe2200e00159ed486fd28ef7edef05bf590de59ef3) // quotient_const + mstore(add(payload, 0x10c0), 0x0000000000000000000000000000000000000000000000000000000000000006) // quotient_const + mstore(add(payload, 0x10e0), 0x0000000000000000000000000000000000000000000000000600000000000000) // quotient_const + mstore(add(payload, 0x1100), 0x0000000000000000000000000000000000060000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1120), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffffff) // quotient_const + mstore(add(payload, 0x1140), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefdffffff00000001) // quotient_const + mstore(add(payload, 0x1160), 0x73eda753299d7d483339d80809a1d80553bba402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1180), 0x0000000000000000000000000000000000000000000000000000000000000003) // quotient_const + mstore(add(payload, 0x11a0), 0x0000000000000000000000000000000000030000000000000000000000000000) // quotient_const + mstore(add(payload, 0x11c0), 0x0000000000000000000000000000012c71404d368ec010269b10000013afec50) // quotient_const + mstore(add(payload, 0x11e0), 0x000000000000000000000000000000f8cd1b37d4ebee2693904ff354f25f7464) // quotient_const + mstore(add(payload, 0x1200), 0x000000000000000000000000000001385b1875503c5c39fb9441f95933e4b28c) // quotient_const + mstore(add(payload, 0x1220), 0x0000000000000000000000000000007c668d9bea75f71349c827f9aa792fba32) // quotient_const + mstore(add(payload, 0x1240), 0x0000000000000000000000000000000761c60b88b9c70e630a72b13b050012c0) // quotient_const + mstore(add(payload, 0x1260), 0x73eda753299d7d483339d80809a1d7a12dfd8a4625be569ccc4ffffef9700691) // quotient_const + mstore(add(payload, 0x1280), 0x73eda753299d7d483339d80809a1d7b264b49166b159a4787a900438048ad935) // quotient_const + mstore(add(payload, 0x12a0), 0x00000000000000000000000000000003b0e305c45ce387318539589d82800960) // quotient_const + mstore(add(payload, 0x12c0), 0x0000000000000000000000000000010c5a3fa8ec14b781d2375bf725316c3fb0) // quotient_const + mstore(add(payload, 0x12e0), 0x000000000000000000000000000000259007b94eb27289dcc84c1f9dce2e9860) // quotient_const + mstore(add(payload, 0x1300), 0x73eda753299d7d483339d80809a1d79d35602792ebdf9e00793f578beeb3c47d) // quotient_const + mstore(add(payload, 0x1320), 0x73eda753299d7d483339d80809a1d802ddd0f5801766ac88a72f1a40a8fff9c1) // quotient_const + mstore(add(payload, 0x1340), 0x73eda753299d7d483339d80809a1d7abe053165ef916860e42e15847ef869571) // quotient_const + mstore(add(payload, 0x1360), 0x73eda753299d7d483339d80809a1d7ec490dd323de5caac125229595cbe0efc1) // quotient_const + mstore(add(payload, 0x1380), 0x08a75c054be451b1f2ad6bd569f92577dd4bd64d6d5c968569fbf9fbe04d544d) // quotient_const + mstore(add(payload, 0x13a0), 0x0000000000000000000000000000000000000000000000000000001800000000) // quotient_const + mstore(add(payload, 0x13c0), 0x0000000000000000000000000000000000000000180000000000000000000000) // quotient_const + mstore(add(payload, 0x13e0), 0x0000000000000000000000000000000000000000000000000000000000006000) // quotient_const + mstore(add(payload, 0x1400), 0x0000000000000000000000000000000000000000000000600000000000000000) // quotient_const + mstore(add(payload, 0x1420), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffff700000001) // quotient_const + mstore(add(payload, 0x1440), 0x73eda753299d7d483339d80809a1d80553bda402f7fe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1460), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffe001) // quotient_const + mstore(add(payload, 0x1480), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bdeffffffff00000001) // quotient_const + mstore(add(payload, 0x14a0), 0x73eda753299d7d483339d80809a1d7e355af567743ae3bbda4ffd260212ddbdb) // quotient_const + mstore(add(payload, 0x14c0), 0x73eda753299d7d483339d80809a1d7ac8bb094e10dd00bb871cf13341ce9f671) // quotient_const + mstore(add(payload, 0x14e0), 0x73eda753299d7d483339d80809a1d7b70b86399be46147106cd7aef7d718818d) // quotient_const + mstore(add(payload, 0x1500), 0x73eda753299d7d483339d80809a1d7ae5b2956bf70a17c7596cc5f626b73876b) // quotient_const + mstore(add(payload, 0x1520), 0x73eda753299d7d483339d80809a1d7b6bc3584645b26895898ae7ba60d735ad1) // quotient_const + mstore(add(payload, 0x1540), 0x73eda753299d7d483339d80809a1d7b095efed6fd9f96e39d8c5c777a6fa1179) // quotient_const + mstore(add(payload, 0x1560), 0x00000000000000000000000000000065fa2ae8a334f060c4110088dc9c766c72) // quotient_const + mstore(add(payload, 0x1580), 0x00000000000000000000000000000000000000000c0000000000000000000000) // quotient_const + mstore(add(payload, 0x15a0), 0x0000000000000000000000000000010a58272d65d68af0d3aa92c660a9421cb0) // quotient_const + mstore(add(payload, 0x15c0), 0x0000000000000000000000000000000000000000000000300000000000000000) // quotient_const + mstore(add(payload, 0x15e0), 0x000000000000000000000000000000ead8a63f3552d73ecbb978f3157ab67b5c) // quotient_const + mstore(add(payload, 0x1600), 0x000000000000000000000000000000852c1396b2eb457869d549633054a10e58) // quotient_const + mstore(add(payload, 0x1620), 0x00000000000000000000000000000104e9bce7caae169e9c3b9ae1d5bda569c2) // quotient_const + mstore(add(payload, 0x1640), 0x0000000000000000000000000000008274de73e5570b4f4e1dcd70eaded2b4e1) // quotient_const + mstore(add(payload, 0x1660), 0x000000000000000000000000000000ebc6985edbee8777f335f48d0ad7a5ef90) // quotient_const + mstore(add(payload, 0x1680), 0x0000000000000000000000000000007f1cb491dcb90764a7bad754cb0588e5cc) // quotient_const + mstore(add(payload, 0x16a0), 0x73eda753299d7d48333049095fbd120c6b5942dd2166802b5297f978232a0002) // quotient_const + mstore(add(payload, 0x16c0), 0x0000000000000000000006000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x16e0), 0x0000000600000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1700), 0x4302515f88a4431e1fbaccbc5adc8f25703b5745de78f77d0d3fe37cf2c01c83) // quotient_const + mstore(add(payload, 0x1720), 0x140e70dbca64831b4b8f40317b68cd20f34ec27e98adf994cf555b0db316abbd) // quotient_const + mstore(add(payload, 0x1740), 0x73eda753299d7d483339d60809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1760), 0x73eda751299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1780), 0x104e71fbe05313635bd503c3e4ec6d9ff680c43f0b2c76d5fb955ed6046aa12a) // quotient_const + mstore(add(payload, 0x17a0), 0x1ff51227ca6853644d38dd4784bdae4c2024f5d6cd1acb78bae38c506ef8716c) // quotient_const + mstore(add(payload, 0x17c0), 0x70156f48f76cc14b27137d78d0b4371477819d08e9f2c77036ebc744ad6da6cb) // quotient_const + mstore(add(payload, 0x17e0), 0x37be870795549c37dcd00b9588085d0fa1ab8c1ad655e52c23ed8949f0fb5ae3) // quotient_const + mstore(add(payload, 0x1800), 0x62a9cec91e3168b1496ccfcf27ad7a8d3c8d65793936323648c2b29cb6a41ac8) // quotient_const + mstore(add(payload, 0x1820), 0x6ed3edb47fe320414c6b5a76f4cc0bce626d1a256829e20dfb44af3c3a5c88b0) // quotient_const + mstore(add(payload, 0x1840), 0x62ea7e5a34aa00528575fab42c44f48dc6c3a5a2fadad34f0a31529dec7b61e0) // quotient_const + mstore(add(payload, 0x1860), 0x6de98a7ebdd251f08ee9668a33e94c65bdb3185246bd05a64c5767be80a6f6b3) // quotient_const + mstore(add(payload, 0x1880), 0x0b88a81e969233f724730fadaac8e2d294b414ee4222bdac5b3caa2ef7b70ba2) // quotient_const + mstore(add(payload, 0x18a0), 0x0000000300000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x18c0), 0x409fb98f933d25e8d0038d4f7b2a98dbc278a3b57cfb0879943764202d0def59) // quotient_const + mstore(add(payload, 0x18e0), 0x43fe0c177a010031bf648c1cc285529323863340cc562ac9e7aaad86598b55df) // quotient_const + mstore(add(payload, 0x1900), 0x33cb899e22443dc4bd6718aaa5dd18684590bb9d54587d5a25b7e826dc13afab) // quotient_const + mstore(add(payload, 0x1920), 0x5a46b0715e6d5198819eb2abc26638708b1b23dc3e7cb23c4a1bb20f9686f7ad) // quotient_const + mstore(add(payload, 0x1940), 0x0f4d2cdbfd2f1714b46b78b33e8164a4d3f19d98c77d6dd30e31f24850ea65f3) // quotient_const + mstore(add(payload, 0x1960), 0x419d6a1793664a2e73d2a85da4119e5513d7a0cde3bde4e90718f923a87532fa) // quotient_const + mstore(add(payload, 0x1980), 0x33097aeadeda76e1094b97fb9816aa66a6edfb200f6a9a0fe16c08233a8dda63) // quotient_const + mstore(add(payload, 0x19a0), 0x09062b3ea1b0c1037678aa3cc094d16f610fd18915e201850d7ce460bf058df5) // quotient_const + mstore(add(payload, 0x19c0), 0x0456a29a706afacf2158850711006fe0acf6a437e9477bf6f782dfac86f2cf7b) // quotient_const + mstore(add(payload, 0x19e0), 0x0397cc06bc030aab970dabe70cd498bbeea8daaea65607bb6a872125fec74a10) // quotient_const + mstore(add(payload, 0x1a00), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb91950003) // quotient_const + mstore(add(payload, 0x1a20), 0x0594e0109a0005958008060d000b0200010594a01194a01194a005950008060d) // quotient_program + mstore(add(payload, 0x1a40), 0x000b0300000594c01194c01194c005952008060d000b0300010594e01194e011) // quotient_program + mstore(add(payload, 0x1a60), 0x94e00595a008060d000b0300011b00001b000105958008109aa00594a01194a0) // quotient_program + mstore(add(payload, 0x1a80), 0x1195000d01060594c01194c01195200d02060594e01194e01195a00d03060d00) // quotient_program + mstore(add(payload, 0x1aa0), 0x0b0300011b000221020403070002000094a00594c00694e00795000895200095) // quotient_program + mstore(add(payload, 0x1ac0), 0x400595600695800995a00a95c00b95e00c96000d96200e96400f966010968011) // quotient_program + mstore(add(payload, 0x1ae0), 0x96a00796c00896e00997000a972094a00095400595600695800796c00896e009) // quotient_program + mstore(add(payload, 0x1b00), 0x97000a972094c00595400695600795800896c00996e00a970012972094e00695) // quotient_program + mstore(add(payload, 0x1b20), 0x400795600895800996c00a96e012970013972095000795400895600995800a96) // quotient_program + mstore(add(payload, 0x1b40), 0xc01296e013970014972095200895400995600a95801296c01396e01497001597) // quotient_program + mstore(add(payload, 0x1b60), 0x2095a00995400a95601295801396c01496e015970016972095c00a9540129560) // quotient_program + mstore(add(payload, 0x1b80), 0x1395801496c01596e01697001797201897401997800b04000121021a03070001) // quotient_program + mstore(add(payload, 0x1ba0), 0x000094a00594c00694e01b95001c95200095400595600695801d95a01e95c00b) // quotient_program + mstore(add(payload, 0x1bc0), 0x95e00c96000d96201f96402096602196802296a01b96c01c96e01d97001e9720) // quotient_program + mstore(add(payload, 0x1be0), 0x94a00095400595600695801b96c01c96e01d97001e972094c00595400695601b) // quotient_program + mstore(add(payload, 0x1c00), 0x95801c96c01d96e01e970023972094e00695401b95601c95801d96c01e96e023) // quotient_program + mstore(add(payload, 0x1c20), 0x970024972095001b95401c95601d95801e96c02396e024970025972095201c95) // quotient_program + mstore(add(payload, 0x1c40), 0x401d95601e95802396c02496e025970026972095a01d95401e95602395802496) // quotient_program + mstore(add(payload, 0x1c60), 0xc02596e026970027972095c01e95402395602495802596c02696e02797002897) // quotient_program + mstore(add(payload, 0x1c80), 0x202997400b0400011b000321022a01000009000995a00a95c00b95e00c96000d) // quotient_program + mstore(add(payload, 0x1ca0), 0x96200e96400f96600094a00594c00694e00795000895201096801196a0189740) // quotient_program + mstore(add(payload, 0x1cc0), 0x1997800b05000121022b01000008001d95a01e95c00b95e00c96000d96201f96) // quotient_program + mstore(add(payload, 0x1ce0), 0x402096600094a00594c00694e01b95001c95202196802296a02997400b050001) // quotient_program + mstore(add(payload, 0x1d00), 0x210397a02c0000000b2b0b94a00c94c00d94e02d95402e95602f95800b95e00c) // quotient_program + mstore(add(payload, 0x1d20), 0x96000d96203097403197600b94a095e00c94a096000d94a096200c94c095e00d) // quotient_program + mstore(add(payload, 0x1d40), 0x94c096003294c096a00d94e095e03294e096803394e096a03295009660339500) // quotient_program + mstore(add(payload, 0x1d60), 0x968034950096a032952096403395209660349520968035952096a00095409540) // quotient_program + mstore(add(payload, 0x1d80), 0x2e954095602f9540958006956095603695609720369580970037958097203295) // quotient_program + mstore(add(payload, 0x1da0), 0xa096203395a096403495a096603595a096803895a096a03295c096003395c096) // quotient_program + mstore(add(payload, 0x1dc0), 0x203495c096403595c096603895c096803995c096a03696c096e03796c097003a) // quotient_program + mstore(add(payload, 0x1de0), 0x96c097203b96e096e03a96e097003c96e097203d970097003e970097203f9720) // quotient_program + mstore(add(payload, 0x1e00), 0x97200d000b060000210397a04003080002150b94a00c94c00d94e00e95000f95) // quotient_program + mstore(add(payload, 0x1e20), 0x202d95402e95602f95801095a01195c00b95e00c96000d96200e96400f966010) // quotient_program + mstore(add(payload, 0x1e40), 0x96801196a04196c04296e043970044972094a00b95e00c96000d96200e96400f) // quotient_program + mstore(add(payload, 0x1e60), 0x96601096801196a094c00c95e00d96000e96200f96401096601196804596a094) // quotient_program + mstore(add(payload, 0x1e80), 0xe00d95e00e96000f96201096401196604596804696a095000e95e00f96001096) // quotient_program + mstore(add(payload, 0x1ea0), 0x201196404596604696804796a095200f95e01096001196204596404696604796) // quotient_program + mstore(add(payload, 0x1ec0), 0x804896a095400095402e95602f95804196c04296e043970044972095a01095e0) // quotient_program + mstore(add(payload, 0x1ee0), 0x1196004596204696404796604896804996a095c01195e0459600469620479640) // quotient_program + mstore(add(payload, 0x1f00), 0x4896604996804a96a01897401997800695609560419560958042956096c04395) // quotient_program + mstore(add(payload, 0x1f20), 0x6096e044956097004b95609720089580958043958096c044958096e04b958097) // quotient_program + mstore(add(payload, 0x1f40), 0x004c958097200a96c096c04b96c096e04c96c097004d96c097201396e096e04d) // quotient_program + mstore(add(payload, 0x1f60), 0x96e097004e96e0972015970097004f9700972017972097200d000b0600012103) // quotient_program + mstore(add(payload, 0x1f80), 0x97a05003080001150b94a00c94c00d94e01f95002095202d95402e95602f9580) // quotient_program + mstore(add(payload, 0x1fa0), 0x2195a02295c00b95e00c96000d96201f96402096602196802296a05196c05296) // quotient_program + mstore(add(payload, 0x1fc0), 0xe053970054972094a00b95e00c96000d96201f96402096602196802296a094c0) // quotient_program + mstore(add(payload, 0x1fe0), 0x0c95e00d96001f96202096402196602296805596a094e00d95e01f9600209620) // quotient_program + mstore(add(payload, 0x2000), 0x2196402296605596805696a095001f95e0209600219620229640559660569680) // quotient_program + mstore(add(payload, 0x2020), 0x5796a095202095e02196002296205596405696605796805896a095400095402e) // quotient_program + mstore(add(payload, 0x2040), 0x95602f95805196c05296e053970054972095a02195e022960055962056964057) // quotient_program + mstore(add(payload, 0x2060), 0x96605896805996a095c02295e05596005696205796405896605996805a96a029) // quotient_program + mstore(add(payload, 0x2080), 0x97400695609560519560958052956096c053956096e054956097005b95609720) // quotient_program + mstore(add(payload, 0x20a0), 0x1c9580958053958096c054958096e05b958097005c958097201e96c096c05b96) // quotient_program + mstore(add(payload, 0x20c0), 0xc096e05c96c097005d96c097202496e096e05d96e097005e96e0972026970097) // quotient_program + mstore(add(payload, 0x20e0), 0x005f9700972028972097200d000b060001210397a0600000000c390b94a00c94) // quotient_program + mstore(add(payload, 0x2100), 0xc00d94e03097403197600097a00097c00597e00698000b98a00c98c00d98e000) // quotient_program + mstore(add(payload, 0x2120), 0x954097c005954097e006954098000b954098a00c954098c00d954098e0059560) // quotient_program + mstore(add(payload, 0x2140), 0x97c006956097e061956098800c956098a00d956098c0329560996006958097c0) // quotient_program + mstore(add(payload, 0x2160), 0x61958098603b958098800d958098a0329580994033958099600095e097a00596) // quotient_program + mstore(add(payload, 0x2180), 0x0097a006962097a06196c098403b96c098606296c098803296c099203396c099) // quotient_program + mstore(add(payload, 0x21a0), 0x403496c099606196e098203b96e098406296e098603d96e098803296e0990033) // quotient_program + mstore(add(payload, 0x21c0), 0x96e099203496e099403596e0996061970098003b9700982062970098403d9700) // quotient_program + mstore(add(payload, 0x21e0), 0x9860639700988032970098e03397009900349700992035970099403897009960) // quotient_program + mstore(add(payload, 0x2200), 0x61972097e03b9720980062972098203d9720984063972098603f972098803297) // quotient_program + mstore(add(payload, 0x2220), 0x2098c033972098e034972099003597209920389720994039972099600d000b07) // quotient_program + mstore(add(payload, 0x2240), 0x0000210397a064020f000a001997800097a00097c00597e00698000798200898) // quotient_program + mstore(add(payload, 0x2260), 0x400998600a98800b98a00c98c00d98e00e99000f992097a00095e00596000696) // quotient_program + mstore(add(payload, 0x2280), 0x200796400896600996800a96a097c00095400595600695800796c00896e00997) // quotient_program + mstore(add(payload, 0x22a0), 0x000a972097e00595400695600795800896c00996e00a97001297209800069540) // quotient_program + mstore(add(payload, 0x22c0), 0x0795600895800996c00a96e012970013972098200795400895600995800a96c0) // quotient_program + mstore(add(payload, 0x22e0), 0x1296e013970014972098400895400995600a95801296c01396e0149700159720) // quotient_program + mstore(add(payload, 0x2300), 0x98600995400a95601295801396c01496e015970016972098800a954012956013) // quotient_program + mstore(add(payload, 0x2320), 0x95801496c01596e016970017972095400b98a00c98c00d98e00e99000f992010) // quotient_program + mstore(add(payload, 0x2340), 0x994011996095600c98a00d98c00e98e00f990010992011994045996095800d98) // quotient_program + mstore(add(payload, 0x2360), 0xa00e98c00f98e010990011992045994046996096c00e98a00f98c01098e01199) // quotient_program + mstore(add(payload, 0x2380), 0x0045992046994047996096e00f98a01098c01198e04599004699204799404899) // quotient_program + mstore(add(payload, 0x23a0), 0x6097001098a01198c04598e046990047992048994049996097201198a04598c0) // quotient_program + mstore(add(payload, 0x23c0), 0x4698e04799004899204999404a99600b94a00c94c00d94e00e95000f95201095) // quotient_program + mstore(add(payload, 0x23e0), 0xa01195c01897401099401199600d000b070001210397a065020f0009000097a0) // quotient_program + mstore(add(payload, 0x2400), 0x0097c00597e00698001b98201c98401d98601e98800b98a00c98c00d98e01f99) // quotient_program + mstore(add(payload, 0x2420), 0x0020992021994097a00095e00596000696201b96401c96601d96801e96a097c0) // quotient_program + mstore(add(payload, 0x2440), 0x0095400595600695801b96c01c96e01d97001e972097e00595400695601b9580) // quotient_program + mstore(add(payload, 0x2460), 0x1c96c01d96e01e970023972098000695401b95601c95801d96c01e96e0239700) // quotient_program + mstore(add(payload, 0x2480), 0x24972098201b95401c95601d95801e96c02396e024970025972098401c95401d) // quotient_program + mstore(add(payload, 0x24a0), 0x95601e95802396c02496e025970026972098601d95401e95602395802496c025) // quotient_program + mstore(add(payload, 0x24c0), 0x96e026970027972098801e95402395602495802596c02696e027970028972095) // quotient_program + mstore(add(payload, 0x24e0), 0x400b98a00c98c00d98e01f990020992021994022996095600c98a00d98c01f98) // quotient_program + mstore(add(payload, 0x2500), 0xe020990021992022994055996095800d98a01f98c02098e02199002299205599) // quotient_program + mstore(add(payload, 0x2520), 0x4056996096c01f98a02098c02198e022990055992056994057996096e02098a0) // quotient_program + mstore(add(payload, 0x2540), 0x2198c02298e055990056992057994058996097002198a02298c05598e0569900) // quotient_program + mstore(add(payload, 0x2560), 0x57992058994059996097202298a05598c05698e05799005899205999405a9960) // quotient_program + mstore(add(payload, 0x2580), 0x0b94a00c94c00d94e01f95002095202195a02295c02997402299600d000b0700) // quotient_program + mstore(add(payload, 0x25a0), 0x01210397a0660000000b2b6794a06894c06994e06a95406b95606c95806a95e0) // quotient_program + mstore(add(payload, 0x25c0), 0x6b96006c96203097403197606d94a094a06894a094c06994a094e06e94c094c0) // quotient_program + mstore(add(payload, 0x25e0), 0x6f94c095c06f94e095a07094e095c06f9500952070950095a071950095c07295) // quotient_program + mstore(add(payload, 0x2600), 0x20952071952095a073952095c06a954095e06b954096006c954096206b956095) // quotient_program + mstore(add(payload, 0x2620), 0xe06c9560960074956096a06c958095e0749580968075958096a07695a095a077) // quotient_program + mstore(add(payload, 0x2640), 0x95a095c07895c095c074960097207496209700759620972074964096e0759640) // quotient_program + mstore(add(payload, 0x2660), 0x9700799640972074966096c075966096e079966097007a9660972075968096c0) // quotient_program + mstore(add(payload, 0x2680), 0x79968096e07a968097007b968097207996a096c07a96a096e07b96a097007c96) // quotient_program + mstore(add(payload, 0x26a0), 0xa097200d000b080000210397a07d03080002156794a06894c06994e07e95007f) // quotient_program + mstore(add(payload, 0x26c0), 0x95206a95406b95606c95808095a08195c06a95e06b96006c9620829640839660) // quotient_program + mstore(add(payload, 0x26e0), 0x8496808596a08296c08396e084970085972094a06d94a06894c06994e07e9500) // quotient_program + mstore(add(payload, 0x2700), 0x7f95208095a08195c095406a95e06b96006c96208296408396608496808596a0) // quotient_program + mstore(add(payload, 0x2720), 0x95606b95e06c96008296208396408496608596808696a095806c95e082960083) // quotient_program + mstore(add(payload, 0x2740), 0x96208496408596608696808796a096c08295e083960084962085964086966087) // quotient_program + mstore(add(payload, 0x2760), 0x96808896a096e08395e08496008596208696408796608896808996a097008495) // quotient_program + mstore(add(payload, 0x2780), 0xe08596008696208796408896608996808a96a097208595e08696008796208896) // quotient_program + mstore(add(payload, 0x27a0), 0x408996608a96808b96a01897401997806e94c094c07e94c094e07f94c0950080) // quotient_program + mstore(add(payload, 0x27c0), 0x94c095208194c095a08c94c095c08d94e094e08094e095008194e095208c94e0) // quotient_program + mstore(add(payload, 0x27e0), 0x95a08e94e095c08f950095008c950095208e950095a090950095c09195209520) // quotient_program + mstore(add(payload, 0x2800), 0x90952095a092952095c09395a095a09495a095c09595c095c00d000b08000121) // quotient_program + mstore(add(payload, 0x2820), 0x0397a09603080001156794a06894c06994e09795009895206a95406b95606c95) // quotient_program + mstore(add(payload, 0x2840), 0x809995a09a95c06a95e06b96006c96209b96409c96609d96809e96a09b96c09c) // quotient_program + mstore(add(payload, 0x2860), 0x96e09d97009e972094a06d94a06894c06994e09795009895209995a09a95c095) // quotient_program + mstore(add(payload, 0x2880), 0x406a95e06b96006c96209b96409c96609d96809e96a095606b95e06c96009b96) // quotient_program + mstore(add(payload, 0x28a0), 0x209c96409d96609e96809f96a095806c95e09b96009c96209d96409e96609f96) // quotient_program + mstore(add(payload, 0x28c0), 0x80a096a096c09b95e09c96009d96209e96409f9660a09680a196a096e09c95e0) // quotient_program + mstore(add(payload, 0x28e0), 0x9d96009e96209f9640a09660a19680a296a097009d95e09e96009f9620a09640) // quotient_program + mstore(add(payload, 0x2900), 0xa19660a29680a396a097209e95e09f9600a09620a19640a29660a39680a496a0) // quotient_program + mstore(add(payload, 0x2920), 0x2997406e94c094c09794c094e09894c095009994c095209a94c095a0a594c095) // quotient_program + mstore(add(payload, 0x2940), 0xc0a694e094e09994e095009a94e09520a594e095a0a794e095c0a895009500a5) // quotient_program + mstore(add(payload, 0x2960), 0x95009520a7950095a0a9950095c0aa95209520a9952095a0ab952095c0ac95a0) // quotient_program + mstore(add(payload, 0x2980), 0x95a0ad95a095c0ae95c095c00d000b080001210397a0af0000000e100094a005) // quotient_program + mstore(add(payload, 0x29a0), 0x94c00694e06a95406b95606c95800095e00596000696203097403197600097c0) // quotient_program + mstore(add(payload, 0x29c0), 0x0597e00698000b954095406b954095606c954095800d95609560749560972074) // quotient_program + mstore(add(payload, 0x29e0), 0x9580970075958097207496c096e07596c097007996c097203396e096e07996e0) // quotient_program + mstore(add(payload, 0x2a00), 0x97007a96e0972035970097007b9700972039972097200d000b090000210397a0) // quotient_program + mstore(add(payload, 0x2a20), 0xb004010002150094a00594c00694e00795000895206a95406b95606c95800995) // quotient_program + mstore(add(payload, 0x2a40), 0xa00a95c00095e00596000696200796400896600996800a96a08296c08396e084) // quotient_program + mstore(add(payload, 0x2a60), 0x97008597200097c00597e00698000798200898400998600a988095400b95406b) // quotient_program + mstore(add(payload, 0x2a80), 0x95606c95808296c08396e08497008597201897401997800d9560956082956095) // quotient_program + mstore(add(payload, 0x2aa0), 0x8083956096c084956096e0859560970086956097200f9580958084958096c085) // quotient_program + mstore(add(payload, 0x2ac0), 0x958096e0869580970087958097201196c096c08696c096e08796c097008896c0) // quotient_program + mstore(add(payload, 0x2ae0), 0x97204696e096e08896e097008996e0972048970097008a970097204a97209720) // quotient_program + mstore(add(payload, 0x2b00), 0x0d000b090001210397a0b104010001150094a00594c00694e01b95001c95206a) // quotient_program + mstore(add(payload, 0x2b20), 0x95406b95606c95801d95a01e95c00095e00596000696201b96401c96601d9680) // quotient_program + mstore(add(payload, 0x2b40), 0x1e96a09b96c09c96e09d97009e97200097c00597e00698001b98201c98401d98) // quotient_program + mstore(add(payload, 0x2b60), 0x601e988095400b95406b95606c95809b96c09c96e09d97009e97202997400d95) // quotient_program + mstore(add(payload, 0x2b80), 0x6095609b956095809c956096c09d956096e09e956097009f9560972020958095) // quotient_program + mstore(add(payload, 0x2ba0), 0x809d958096c09e958096e09f95809700a0958097202296c096c09f96c096e0a0) // quotient_program + mstore(add(payload, 0x2bc0), 0x96c09700a196c097205696e096e0a196e09700a296e097205897009700a39700) // quotient_program + mstore(add(payload, 0x2be0), 0x97205a972097200d000b090001191f0000000000000000000000000000000000) // quotient_program + // Fixed-column commitment 0, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2c00), 0x00000000000000000000000000000000055f7961345dce7ce57401dd993cc81a) // fixed_comms[0].x_hi + mstore(add(payload, 0x2c20), 0xb4a8bb072416d10d143dbceefaa489acea245ef19b9b96fef5bf433eb3a11715) // fixed_comms[0].x_lo + mstore(add(payload, 0x2c40), 0x00000000000000000000000000000000123e8a257be057ec25558c37e4b17ce9) // fixed_comms[0].y_hi + mstore(add(payload, 0x2c60), 0x8e3b8e47d06f0961e4194860938f70e8f8f29e6c09dc697cb5c3486879220eaf) // fixed_comms[0].y_lo + // Fixed-column commitment 1, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2c80), 0x000000000000000000000000000000000dc17ef381e9c195813396905a4c7619) // fixed_comms[1].x_hi + mstore(add(payload, 0x2ca0), 0x326e62da20d14e245cf995ae353e85749ad599d8859cdf7ff0996b64ad724553) // fixed_comms[1].x_lo + mstore(add(payload, 0x2cc0), 0x0000000000000000000000000000000004af0ea1ccdc1cd0a2a638aa09f6e2ae) // fixed_comms[1].y_hi + mstore(add(payload, 0x2ce0), 0xe2fe34ad7bd96fb709c73d2d9705c120c1c78c8702fc136bcecb58a6efc4353d) // fixed_comms[1].y_lo + // Fixed-column commitment 2, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2d00), 0x0000000000000000000000000000000018da87ffd53a1cdfc243a1f594c7db5f) // fixed_comms[2].x_hi + mstore(add(payload, 0x2d20), 0x6a20adfd78c0e9d2dd4a3377189dcaf31f886eecc6bcfd19d7263ff57b36c01e) // fixed_comms[2].x_lo + mstore(add(payload, 0x2d40), 0x0000000000000000000000000000000001921c576e8a2684cc7521fbf6ec96c3) // fixed_comms[2].y_hi + mstore(add(payload, 0x2d60), 0xde86625c4429546e64909b0f415e9b7bcff50f6e96df84f795a6bf7c3fd0e085) // fixed_comms[2].y_lo + // Fixed-column commitment 3, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2d80), 0x000000000000000000000000000000001390c4b48f0af2e2a9332b1851fbb5d1) // fixed_comms[3].x_hi + mstore(add(payload, 0x2da0), 0xe13c168e64b12da23a13fb7b449e2f8d38dd385220d9a9cc7af8c1cb5d7e7364) // fixed_comms[3].x_lo + mstore(add(payload, 0x2dc0), 0x0000000000000000000000000000000019b9ac80c33724396a9da36abc8884bc) // fixed_comms[3].y_hi + mstore(add(payload, 0x2de0), 0x9adb1d0bf586080efd6ffd8520aab78d8d205c0a11726db029aecc899111d9a9) // fixed_comms[3].y_lo + // Fixed-column commitment 4, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2e00), 0x0000000000000000000000000000000015bc72a82a34331b999a01881b5c4b3b) // fixed_comms[4].x_hi + mstore(add(payload, 0x2e20), 0xb138f213ddd19ae669c32f961811a4164e6eec8fac46a36f5a4e04870f11a3d1) // fixed_comms[4].x_lo + mstore(add(payload, 0x2e40), 0x00000000000000000000000000000000197b5b9237d51d93dc155332b6330653) // fixed_comms[4].y_hi + mstore(add(payload, 0x2e60), 0xef5fa3e86ca7b8acaf6cb32697d8ececfbdac8b2ffe2f53e47bdc8ba9e3993b6) // fixed_comms[4].y_lo + // Fixed-column commitment 5, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2e80), 0x0000000000000000000000000000000013eb9d933b5284bfef6caf1f9e08ca59) // fixed_comms[5].x_hi + mstore(add(payload, 0x2ea0), 0x283ea2dbd92fc9a748e3761fff443680718b50347a55be560c0229972f7e0a24) // fixed_comms[5].x_lo + mstore(add(payload, 0x2ec0), 0x000000000000000000000000000000001100235c0764123ef1a72a76e2abc95c) // fixed_comms[5].y_hi + mstore(add(payload, 0x2ee0), 0x8af88c99dda9239bf71fca4d07b5fee527d576ad79c679035b5836347686d74b) // fixed_comms[5].y_lo + // Fixed-column commitment 6, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2f00), 0x000000000000000000000000000000000563bb1e4b8d6ac080403aa94fe32b7d) // fixed_comms[6].x_hi + mstore(add(payload, 0x2f20), 0xe1921a07b4a8ade8f03fbf6079d44191250a74ad822758d3f3c76ac1dcf34844) // fixed_comms[6].x_lo + mstore(add(payload, 0x2f40), 0x000000000000000000000000000000001660e1d7dc487ef2074c1cbcc53f96c4) // fixed_comms[6].y_hi + mstore(add(payload, 0x2f60), 0x90a10debafc892aa61fc1f5deccbe9e504f860b20ab154069fc7d9db6abd4b8c) // fixed_comms[6].y_lo + // Fixed-column commitment 7, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2f80), 0x0000000000000000000000000000000011d8086a3a74772d6977655ec27ea545) // fixed_comms[7].x_hi + mstore(add(payload, 0x2fa0), 0xb8f93c43434e29d5987c92a14aff1f1eaaf7301c5a7e3089af5e92d1f848d0ca) // fixed_comms[7].x_lo + mstore(add(payload, 0x2fc0), 0x0000000000000000000000000000000005dad0d11cdc8ea706cfc3def7d4b133) // fixed_comms[7].y_hi + mstore(add(payload, 0x2fe0), 0xc3589649c58c36b9396e7ba08d2dcce7e9ffc79c05293e88ffdcd779031bd430) // fixed_comms[7].y_lo + // Fixed-column commitment 8, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3000), 0x000000000000000000000000000000000c93bd7351261d64a616e0136a9d422b) // fixed_comms[8].x_hi + mstore(add(payload, 0x3020), 0xb749e19c58376c59582349ad89c4cd137403a5708d9c57caa9aa60a61ebac5eb) // fixed_comms[8].x_lo + mstore(add(payload, 0x3040), 0x000000000000000000000000000000000cbfb4f76cc2e2dd1cb5c3d5102d3b9a) // fixed_comms[8].y_hi + mstore(add(payload, 0x3060), 0xd4a9cd56cbcb6df99c181927f6448f16319d629b8e456d7c82888b8ebff6605c) // fixed_comms[8].y_lo + // Fixed-column commitment 9, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3080), 0x0000000000000000000000000000000008727a8cb32cb038513549fb17b3ba8d) // fixed_comms[9].x_hi + mstore(add(payload, 0x30a0), 0xd9695316ca607544e71a5430e6910b8fee65f6ad1a50f524c684c5e957c1c73e) // fixed_comms[9].x_lo + mstore(add(payload, 0x30c0), 0x000000000000000000000000000000000108a6377aea32a5e3bbce056526f625) // fixed_comms[9].y_hi + mstore(add(payload, 0x30e0), 0x33a146b087d572d2cdda900fca8baea1863367075e4b606110b6325be4397d52) // fixed_comms[9].y_lo + // Fixed-column commitment 10, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3100), 0x0000000000000000000000000000000004757ce24e0add6ba492d720099e2fbd) // fixed_comms[10].x_hi + mstore(add(payload, 0x3120), 0xc54defd9e931ec5af33e4a92ad1867c2f4d790e30268a97b42e74f65d1d26feb) // fixed_comms[10].x_lo + mstore(add(payload, 0x3140), 0x0000000000000000000000000000000010f63d4681250b4d2f91725c42a7993b) // fixed_comms[10].y_hi + mstore(add(payload, 0x3160), 0xaa140dc9f52cc57f62aa46386e696d66b92a24406de6a8dfb053ebf589cd908b) // fixed_comms[10].y_lo + // Fixed-column commitment 11, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3180), 0x00000000000000000000000000000000085b6410030ecdc020942851047cf237) // fixed_comms[11].x_hi + mstore(add(payload, 0x31a0), 0x9c9c9bb218540241434381109f505f21842aadb79e1290bf15d09779052c830e) // fixed_comms[11].x_lo + mstore(add(payload, 0x31c0), 0x0000000000000000000000000000000001662c17e52c0576a1daf532fd9b5d44) // fixed_comms[11].y_hi + mstore(add(payload, 0x31e0), 0xa3b693810486d8cc2b3231928889a5901b11f8de1ea0a8d7da54719bb1bacb39) // fixed_comms[11].y_lo + // Fixed-column commitment 12, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3200), 0x000000000000000000000000000000001842a8cec63398e3cd72495091717037) // fixed_comms[12].x_hi + mstore(add(payload, 0x3220), 0xed2687fe07e288a66dbddadb866eab042e668795097a24350329af3a2faa15e8) // fixed_comms[12].x_lo + mstore(add(payload, 0x3240), 0x000000000000000000000000000000000cee59973fde1d885353d9f171f17c99) // fixed_comms[12].y_hi + mstore(add(payload, 0x3260), 0xfd88d67302f6cec5cbcd7d8430f17485f2bbaa4a092e725824499b3fc0cf01bd) // fixed_comms[12].y_lo + // Fixed-column commitment 13, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3280), 0x0000000000000000000000000000000019d8a32c4ce7586146ff1ba0ac184d1e) // fixed_comms[13].x_hi + mstore(add(payload, 0x32a0), 0x8263acc4da9e972d5b111879f60990df5070a38e99c93a8e9f52e94ffc5e889e) // fixed_comms[13].x_lo + mstore(add(payload, 0x32c0), 0x00000000000000000000000000000000027e571331ef494a3859d52330b48271) // fixed_comms[13].y_hi + mstore(add(payload, 0x32e0), 0x9386739b55372fd11694634dc2fe32fc5ed8a01fbe87cdbbcfacb4aed4903fc3) // fixed_comms[13].y_lo + // Fixed-column commitment 14, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3300), 0x00000000000000000000000000000000017d226d304f23f7f52ffe5adadcaca9) // fixed_comms[14].x_hi + mstore(add(payload, 0x3320), 0x54a9d37b66adcd661144899193c80d6d365b30bd133f0bd5a60c12da38b4685e) // fixed_comms[14].x_lo + mstore(add(payload, 0x3340), 0x000000000000000000000000000000000f485636ff7bd4a83beb91de87df7ed4) // fixed_comms[14].y_hi + mstore(add(payload, 0x3360), 0x4757ccb9b4c2999112e89a82d9c72579b1d0020cdda10f3e0b41bf57aec34686) // fixed_comms[14].y_lo + // Fixed-column commitment 15, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3380), 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].x_hi + mstore(add(payload, 0x33a0), 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].x_lo + mstore(add(payload, 0x33c0), 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].y_hi + mstore(add(payload, 0x33e0), 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].y_lo + // Fixed-column commitment 16, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3400), 0x0000000000000000000000000000000008fd1060bd58dfc0e828393d62e3aee0) // fixed_comms[16].x_hi + mstore(add(payload, 0x3420), 0xfe8983d71d04414402f77df4de23f2a73b3c5c48ee85fa51fcd64a3027e0167b) // fixed_comms[16].x_lo + mstore(add(payload, 0x3440), 0x000000000000000000000000000000001024eab25af80d874e553df0f690d9e3) // fixed_comms[16].y_hi + mstore(add(payload, 0x3460), 0x634a41f5518caaaeee1dbc993ced1264ade8431a680d202247d83eb8ef1ed620) // fixed_comms[16].y_lo + // Fixed-column commitment 17, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3480), 0x000000000000000000000000000000000b877a879e46bc947071a221c60797d8) // fixed_comms[17].x_hi + mstore(add(payload, 0x34a0), 0x0d338c66213b76689a66c79be8734280510aa45f8c79ce0eaaba3d7f2c201c99) // fixed_comms[17].x_lo + mstore(add(payload, 0x34c0), 0x000000000000000000000000000000000ffcad707a79c0b29c100d2ab1b60935) // fixed_comms[17].y_hi + mstore(add(payload, 0x34e0), 0x2a2c605009875c39b73bb6d3e2216b305369028923566fc6ef2d22c8b74ce2bf) // fixed_comms[17].y_lo + // Fixed-column commitment 18, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3500), 0x000000000000000000000000000000000d0d0b7c841523297c824431ffca522d) // fixed_comms[18].x_hi + mstore(add(payload, 0x3520), 0xb24daa6cbd8c5f783501a4681bf7367e7aa5d71fca25234cd1a8f31ff24b9871) // fixed_comms[18].x_lo + mstore(add(payload, 0x3540), 0x000000000000000000000000000000001396209d456313b44ec6d4a2fe5f434f) // fixed_comms[18].y_hi + mstore(add(payload, 0x3560), 0xd5bd61218a04eaee084c376ade74d9bceba17fb9e12c2341581e5279596d7c97) // fixed_comms[18].y_lo + // Fixed-column commitment 19, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3580), 0x00000000000000000000000000000000136cd0a2afc84eaecd680d24627d704f) // fixed_comms[19].x_hi + mstore(add(payload, 0x35a0), 0x7e67e5d7fa3094f1aac03b8600277d394817f19fd0b0810dd2087b359429bf11) // fixed_comms[19].x_lo + mstore(add(payload, 0x35c0), 0x0000000000000000000000000000000015cedd2e0ff3e776d58981915174cb65) // fixed_comms[19].y_hi + mstore(add(payload, 0x35e0), 0x13bbf7d0f959d6be24d858ac4ae78c6b9ba0c80d69183a84cd73ec4e47693a14) // fixed_comms[19].y_lo + // Fixed-column commitment 20, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3600), 0x0000000000000000000000000000000010e71146f473749481916e34fff2ba4a) // fixed_comms[20].x_hi + mstore(add(payload, 0x3620), 0xd3e26bb933c011913f506f7892a36f43f7ba4f88b3f1c632e707bbffede7feb9) // fixed_comms[20].x_lo + mstore(add(payload, 0x3640), 0x000000000000000000000000000000000982bd3e7a5ba58d468d0835936ad925) // fixed_comms[20].y_hi + mstore(add(payload, 0x3660), 0xa3d7160336e586296b5e3742251d002e79d8d8d409bf94bafd09719f3ff0b382) // fixed_comms[20].y_lo + // Fixed-column commitment 21, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3680), 0x00000000000000000000000000000000141c94b0741a3d6471b0dd59b3420d9e) // fixed_comms[21].x_hi + mstore(add(payload, 0x36a0), 0xa03be602a9ebe7f1567966db7e3ace5a14ae393194015380293d1d995596651c) // fixed_comms[21].x_lo + mstore(add(payload, 0x36c0), 0x000000000000000000000000000000000185758fd177d9c06fad9502b24ca417) // fixed_comms[21].y_hi + mstore(add(payload, 0x36e0), 0x9cbb2dd41d7ede5248fb78316c557683f183c083b731a261a7381d45cf930742) // fixed_comms[21].y_lo + // Fixed-column commitment 22, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3700), 0x0000000000000000000000000000000001e93db65a35232bfd9581766d6c5c59) // fixed_comms[22].x_hi + mstore(add(payload, 0x3720), 0xa90a6d1906f94fc5643024253805f7a17b6fc2428bbe864add9dd8124518153c) // fixed_comms[22].x_lo + mstore(add(payload, 0x3740), 0x000000000000000000000000000000001897a8f562cb20282c670eecf5f77249) // fixed_comms[22].y_hi + mstore(add(payload, 0x3760), 0x4df0f2823207b6a3832a1b0d7987b20827e8416b563efbbfe212f510acf289df) // fixed_comms[22].y_lo + // Fixed-column commitment 23, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3780), 0x000000000000000000000000000000001141339a865a8fc5477605fae30566b9) // fixed_comms[23].x_hi + mstore(add(payload, 0x37a0), 0x7bd7c27d8d218efa4eb28c80916844e8ec5d47c7dc2f871545707f004b7bde50) // fixed_comms[23].x_lo + mstore(add(payload, 0x37c0), 0x000000000000000000000000000000000a728fcbfa5a7a5bfae84b4ab18b83e6) // fixed_comms[23].y_hi + mstore(add(payload, 0x37e0), 0xb10f17f88be257afb9d981d6516cfd2c4b3e89f5fa5ca13434fd95b24bc0f5c1) // fixed_comms[23].y_lo + // Fixed-column commitment 24, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3800), 0x0000000000000000000000000000000002a788024d9035e7e40f601bb8082ff8) // fixed_comms[24].x_hi + mstore(add(payload, 0x3820), 0x53ec39d899cd245ad2ee9ad32549a184d0c5cb4d4d99940a1cda52c4aa50f79d) // fixed_comms[24].x_lo + mstore(add(payload, 0x3840), 0x0000000000000000000000000000000019a91d8fc2a4d3db45f22db05108197e) // fixed_comms[24].y_hi + mstore(add(payload, 0x3860), 0x7eac8129c7add70a75761fd1e18fa2f40ea6e687a3f43b41995a2e74bf31ff24) // fixed_comms[24].y_lo + // Fixed-column commitment 25, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3880), 0x0000000000000000000000000000000001442c60b19c670229debddb76233f20) // fixed_comms[25].x_hi + mstore(add(payload, 0x38a0), 0x6a6627da0dc462833c112cdb1784e1f5f8a9aec196cf07b4442d408dbdc8de34) // fixed_comms[25].x_lo + mstore(add(payload, 0x38c0), 0x0000000000000000000000000000000013f3b855645d02a97df448668efd5d7f) // fixed_comms[25].y_hi + mstore(add(payload, 0x38e0), 0x8d52d302c0337d44e292fff6ae85d5a6dc5a59067e2a4f0925f48080dc521d40) // fixed_comms[25].y_lo + // Fixed-column commitment 26, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3900), 0x00000000000000000000000000000000190429c7a977675dbb8d96b442fe3eb0) // fixed_comms[26].x_hi + mstore(add(payload, 0x3920), 0xb466636ed724932185b8820e3f115e95ce19cde31f721358e32beda77fa44c98) // fixed_comms[26].x_lo + mstore(add(payload, 0x3940), 0x00000000000000000000000000000000058d18d63ff3a3abf337b6edc6a7709a) // fixed_comms[26].y_hi + mstore(add(payload, 0x3960), 0x36a019565bfbe01076597121c5484c2f3c63a301ed971058d75bb5d61b50bf05) // fixed_comms[26].y_lo + // Permutation commitment 0, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3980), 0x00000000000000000000000000000000120ea7ffaddae135109dc68a0169e39e) // permutation_comms[0].x_hi + mstore(add(payload, 0x39a0), 0x17f95865ea6411611a36d6ee8affde13b3120990c5feff3a2dc32b1b71606512) // permutation_comms[0].x_lo + mstore(add(payload, 0x39c0), 0x000000000000000000000000000000000d8604c2a5312ba81e5c56ad904e3fd3) // permutation_comms[0].y_hi + mstore(add(payload, 0x39e0), 0xc73a292e67febcd994e3d882c050e50940bcd0364b49eaf0ea8c838123757e51) // permutation_comms[0].y_lo + // Permutation commitment 1, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3a00), 0x0000000000000000000000000000000012baec9d926370da0174d2da9125909f) // permutation_comms[1].x_hi + mstore(add(payload, 0x3a20), 0x51c030289c7267c9173faacb20c43e027ba889a257fc9da7685fe92a1d8e1159) // permutation_comms[1].x_lo + mstore(add(payload, 0x3a40), 0x00000000000000000000000000000000151c6bdabf6387e09e4ab927a972d0a5) // permutation_comms[1].y_hi + mstore(add(payload, 0x3a60), 0x93e74bf9513d1d9be31b2d8828eb248d8b76fd529ee3d3343629b1327f47b74c) // permutation_comms[1].y_lo + // Permutation commitment 2, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3a80), 0x000000000000000000000000000000000502f4f83331ce4fe3b1e618b27af0d4) // permutation_comms[2].x_hi + mstore(add(payload, 0x3aa0), 0xed1c9014937f4e0c989f42c2a22d0d54e395b1e6d35830f1681e7f7633bd5e48) // permutation_comms[2].x_lo + mstore(add(payload, 0x3ac0), 0x00000000000000000000000000000000158fff4ecfaf728c449c4f9955fe87b1) // permutation_comms[2].y_hi + mstore(add(payload, 0x3ae0), 0x8938d2b58f9f4325ad6c56ef82526e5d6bedff11dd4ef4730c55430e7ecc12b7) // permutation_comms[2].y_lo + // Permutation commitment 3, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3b00), 0x00000000000000000000000000000000157dbf7d9e1605bb29df570e4e4165d2) // permutation_comms[3].x_hi + mstore(add(payload, 0x3b20), 0xe3af202d72afd27c2516ad10f0ef973f33dddb0fb0e34df741b4385b713cd1a8) // permutation_comms[3].x_lo + mstore(add(payload, 0x3b40), 0x000000000000000000000000000000001227de928658870ba5ecaeae8dc6272e) // permutation_comms[3].y_hi + mstore(add(payload, 0x3b60), 0x3c0f65f9d0e0daf9bcae0c8d2f61f4dfe5603cd4f9c2efe24c8bb68df6091f8b) // permutation_comms[3].y_lo + // Permutation commitment 4, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3b80), 0x00000000000000000000000000000000095679f4757e1699305ce877a6cede75) // permutation_comms[4].x_hi + mstore(add(payload, 0x3ba0), 0xa7e634400e785e5c802142e6df51aa4b9d707d525cf7a80209699c36020b7971) // permutation_comms[4].x_lo + mstore(add(payload, 0x3bc0), 0x00000000000000000000000000000000044f9e79c7622ffb279d558f84e0e7ec) // permutation_comms[4].y_hi + mstore(add(payload, 0x3be0), 0x8355f52668f5832af0d5f80a018da5d9cf622b4ad414ef10eaad16f94ddc8def) // permutation_comms[4].y_lo + // Permutation commitment 5, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3c00), 0x000000000000000000000000000000000dc0d3a9a27cacb14bf99d6c879fc0f2) // permutation_comms[5].x_hi + mstore(add(payload, 0x3c20), 0x92f80d3f8becaa901863aa7ca5b046bbe0f61b5da880eb4b1bb5b40ecf735d54) // permutation_comms[5].x_lo + mstore(add(payload, 0x3c40), 0x00000000000000000000000000000000077a8a36b30f3ba3d444bc0427088640) // permutation_comms[5].y_hi + mstore(add(payload, 0x3c60), 0x8840d2e5d575557d7495f63d40abf4b7daa04fc2bd662f6e299fc27aca5de4c5) // permutation_comms[5].y_lo + // Permutation commitment 6, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3c80), 0x0000000000000000000000000000000000986f7215a9b5e4bc69608acaeb755c) // permutation_comms[6].x_hi + mstore(add(payload, 0x3ca0), 0xc0ed4dd7bd88976da07e9c47756f1c9b90ef494c5cc3031ac6c9b5570fe5c45d) // permutation_comms[6].x_lo + mstore(add(payload, 0x3cc0), 0x0000000000000000000000000000000003658180fe0ac3ab217301cc34d2f9aa) // permutation_comms[6].y_hi + mstore(add(payload, 0x3ce0), 0x04b19a3be7154ad43d1737fd1668783a7069648dfb24de83a6b13d7261001032) // permutation_comms[6].y_lo + // Permutation commitment 7, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3d00), 0x0000000000000000000000000000000001a53f321f3bf6d20af84c660efc018e) // permutation_comms[7].x_hi + mstore(add(payload, 0x3d20), 0xa56fe987917489d477e3f2ac4da73c5a1e032748de091337870f0d5405473cd7) // permutation_comms[7].x_lo + mstore(add(payload, 0x3d40), 0x000000000000000000000000000000000cc1da20073f989dd769a6d1003df07d) // permutation_comms[7].y_hi + mstore(add(payload, 0x3d60), 0x5f354c49e670c02fcd04d732bf1dcf1ea91bf6fda6ccaf7b3884498827925ad7) // permutation_comms[7].y_lo + // Permutation commitment 8, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3d80), 0x0000000000000000000000000000000006416291ebc77017412205eb4523f2c4) // permutation_comms[8].x_hi + mstore(add(payload, 0x3da0), 0xfd559ac96201738085b756438d69e28d22d12d42527f26d4d076768379361baf) // permutation_comms[8].x_lo + mstore(add(payload, 0x3dc0), 0x000000000000000000000000000000000d7c5e9e2123f1a9259e09115e8bcbde) // permutation_comms[8].y_hi + mstore(add(payload, 0x3de0), 0x993cb20b6226d0b408d9ba2ea78f60939c10a918d0b706c0069e74ba1eeb7587) // permutation_comms[8].y_lo + // Permutation commitment 9, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3e00), 0x00000000000000000000000000000000155b9dd46792d67982688c923afe6f78) // permutation_comms[9].x_hi + mstore(add(payload, 0x3e20), 0xf0a9671faf2fbd90216a37854e9cc8f38a4e4e2edfbec79eabb17473cb14233a) // permutation_comms[9].x_lo + mstore(add(payload, 0x3e40), 0x000000000000000000000000000000001136f0b77aaf0d619c1ab6b1f0dcee68) // permutation_comms[9].y_hi + mstore(add(payload, 0x3e60), 0x5a362fa96be37353acf9f0ad8061c2da4ea54d0a983b25c140fff64eb5814a43) // permutation_comms[9].y_lo + // Permutation commitment 10, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3e80), 0x00000000000000000000000000000000190c34b5f99861f6dfcdde40fbfd95d0) // permutation_comms[10].x_hi + mstore(add(payload, 0x3ea0), 0x95a6bd47b58b054fe90452e3b358cd4dba6dd76fa6d5877d1ad1a6dc5f2c2ad7) // permutation_comms[10].x_lo + mstore(add(payload, 0x3ec0), 0x00000000000000000000000000000000025a2fa63f92a2b0012325d053fb4dd7) // permutation_comms[10].y_hi + mstore(add(payload, 0x3ee0), 0xeea7e8f98e3d57f1d404d8c6266072c77e1bf610f1b2d7e66a24c4eb5c7fa858) // permutation_comms[10].y_lo + // Permutation commitment 11, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3f00), 0x0000000000000000000000000000000006dba7402f78627c2b84e3f197be1ff9) // permutation_comms[11].x_hi + mstore(add(payload, 0x3f20), 0xe3b778c0de9938ede71f40c70a82c4b21e7f7e17f5ae37d8f431d8c56cb0cb7d) // permutation_comms[11].x_lo + mstore(add(payload, 0x3f40), 0x0000000000000000000000000000000002f707f86413969433104530051e0e7f) // permutation_comms[11].y_hi + mstore(add(payload, 0x3f60), 0xef774edc804973bdd3807446ef32363e657593d67b0460a33e76fc23d9feb998) // permutation_comms[11].y_lo + // Permutation commitment 12, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3f80), 0x00000000000000000000000000000000116f8891da6e5ddfca44225df76baa02) // permutation_comms[12].x_hi + mstore(add(payload, 0x3fa0), 0x795f3f817116f937e2102b4196eb86112a0d3290a6811fa1e7ada37bdcd91129) // permutation_comms[12].x_lo + mstore(add(payload, 0x3fc0), 0x0000000000000000000000000000000012b7e92df964086ecd115e6b47daad77) // permutation_comms[12].y_hi + mstore(add(payload, 0x3fe0), 0xcf396451a41cf55fa02fe68aaea4416953ad112a6608797bcc1f4a06ec6a786f) // permutation_comms[12].y_lo + // Permutation commitment 13, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4000), 0x000000000000000000000000000000000c92a9011db64f5f7857340d5ecc0e76) // permutation_comms[13].x_hi + mstore(add(payload, 0x4020), 0x6d6a2f4362053c3253503813a1a4167c6848cdbae2c90b104fc2a05290a24f78) // permutation_comms[13].x_lo + mstore(add(payload, 0x4040), 0x00000000000000000000000000000000143212478a1a01c12e08520644d09291) // permutation_comms[13].y_hi + mstore(add(payload, 0x4060), 0x4b417957d5c80179f30c06b68b329aa111de3f613cc66c70fd4841ec26139999) // permutation_comms[13].y_lo + // Permutation commitment 14, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4080), 0x000000000000000000000000000000000a03f34520ffafc5b6a3f90f70e4e373) // permutation_comms[14].x_hi + mstore(add(payload, 0x40a0), 0x673b3105b9864666ae11b398378b0648279065fc693c5edc4d42da133102545c) // permutation_comms[14].x_lo + mstore(add(payload, 0x40c0), 0x00000000000000000000000000000000073205a58fda6d5adfd59e82867a548c) // permutation_comms[14].y_hi + mstore(add(payload, 0x40e0), 0x80b028b16770d9a79d949f623d043d9aeff1cac440bd1482d1c898d7fc7bbbce) // permutation_comms[14].y_lo + // Permutation commitment 15, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4100), 0x0000000000000000000000000000000008f70210c58006a3cb8b927287fa0bfb) // permutation_comms[15].x_hi + mstore(add(payload, 0x4120), 0x55da954a29534ecb479d764e8ca977440dee39b3d09ebbe6b3fbb9c5b92cfe8b) // permutation_comms[15].x_lo + mstore(add(payload, 0x4140), 0x0000000000000000000000000000000007285c63d0eadb56452c598285dfaa89) // permutation_comms[15].y_hi + mstore(add(payload, 0x4160), 0x7d569c04ddbe85aa5deb978df472a328eb6db26586db41278f2cddeee55568cf) // permutation_comms[15].y_lo + // Permutation commitment 16, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4180), 0x0000000000000000000000000000000019e4494289915c32cc82722cdf9cd7f2) // permutation_comms[16].x_hi + mstore(add(payload, 0x41a0), 0xdc0187db7a50b887b1b5b2003c62effcf00f99c2a920ec56cfee0462655ac108) // permutation_comms[16].x_lo + mstore(add(payload, 0x41c0), 0x000000000000000000000000000000001933cb285d7f70cc217fdf83c792d776) // permutation_comms[16].y_hi + mstore(add(payload, 0x41e0), 0x527c8109795e8922038ac6a98e67c92d0fcb6e3a700e7ec2c0fa98cc1df52702) // permutation_comms[16].y_lo + // Permutation commitment 17, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4200), 0x000000000000000000000000000000000485fd87bac44c42dc3c80fd042cc7d0) // permutation_comms[17].x_hi + mstore(add(payload, 0x4220), 0xbb99316753b744e151e09c7f07e670300181f2f8e79a8b37c9828ac6be7a9573) // permutation_comms[17].x_lo + mstore(add(payload, 0x4240), 0x000000000000000000000000000000000fd370bb45717fa6282230adcff6e4b6) // permutation_comms[17].y_hi + mstore(add(payload, 0x4260), 0xab0ead4db625a61209a55551a96a3b5618f2996e645f09bbfda12ca3881536ac) // permutation_comms[17].y_lo + + // Return exactly the INVALID prefix plus the generated payload. The + // linked verifier pins this byte length and the resulting codehash. + return(runtime, 0x4281) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/README.md b/proofs/solidity-verifier/fixtures/ivc/README.md new file mode 100644 index 000000000..350fbe00f --- /dev/null +++ b/proofs/solidity-verifier/fixtures/ivc/README.md @@ -0,0 +1,48 @@ +# IVC Public-Accumulator Replay Fixture + +Pre-rendered artifacts for `tests/ivc_accumulator_replay.rs`, which replays a +real IVC final proof and then mutates the accumulator public inputs to check the +decoder in `templates/partials/verifier/AccumulatorHelpers.yul` rejects them. + +These are *rendered* contracts plus matching calldata rather than a verifying +key and proof, so the replay needs only solc and revm -- no SRS, no proving run, +and no `midnight-aggregation`. A verifier cannot be rendered from a VK without +the full SRS, because `SolidityGenerator` consumes `params.g_lagrange()`; that +is what makes a vk.bin-based replay unusable in CI. + +## Provenance + +| Field | Value | +| --- | --- | +| Source commit | `f5b7466` | +| Rendered by | `tests/ivc_keccak_solidity.rs` (`ivc_final_keccak_solidity_e2e`) | +| Circuit | IVC k=19 leaves, k=20 decider | +| Accumulator | `AccumulatorEncoding::new(offset=4, num_limbs=7, num_limb_bits=56)` | +| Verified on-chain | yes, 1,285,274 gas under revm Prague | + +## Regenerating + +Requires `midnight-srs-2p19` and `midnight-srs-2p20` in `SRS_DIR` +(~300 MB, from ): + +```bash +HALO2_SOLIDITY_RUN_IVC_BENCH=1 \ + SRS_DIR=/path/to/midfall/zk_stdlib/examples/assets \ + cargo test --release \ + --features evm,truncated-challenges,in-circuit-fewer-point-sets \ + --test ivc_keccak_solidity -- --nocapture + +cp target/ivc-keccak-solidity-dump/{Halo2Verifier.sol,Halo2VerifyingKey.sol,\ +Halo2QuotientEvaluator.sol,calldata.bin} fixtures/ivc/ +``` + +Then update the source commit above. + +## Staleness + +This is a snapshot of the codegen that produced it. The artifacts are +self-consistent, so the replay keeps passing after a codegen change -- it just +stops exercising current output. Detecting that automatically would mean +re-rendering, which needs the SRS again, so it is tracked by the commit stamp +above rather than by an assertion. Regenerate after changes to the accumulator +templates or the memory layout. diff --git a/proofs/solidity-verifier/fixtures/ivc/calldata.bin b/proofs/solidity-verifier/fixtures/ivc/calldata.bin new file mode 100644 index 0000000000000000000000000000000000000000..9aed1ca8961ee78ba7fde25b9914805108918db6 GIT binary patch literal 8356 zcma*sWl$VJgEw$waSOV*v$(qxB)G%kZVB!X$l^hQy97&+-~@uZySqbhT|8*ut$RN` zwYj>x`P5zi`Av8A%v4u*Q^rx^!~M^f)c+h)cKt88Ic!b>heydwMYCnhNfx}XZC)J+C2}!NO zdAdXU!j$HZaM*)+G=Yp|z2}9|;Aw9Tk?V)`a8Iv8A-ea*tmHnfl5(KXTYy9+3YX1t zD{Up?$cLF`3;fiCbWMz{UOBX6+J^|=&x*E9!j)RFza-7Bk&rs# z;bRoH)nMQ^iRV*P z8Kb7_z2~mH-g>&>4&MyucY^hB2_D`Xj~1C%qD?cE!#{fVx7D^k@V^Ev*Z z_htu|HBwY^9}4t_yuZH|vyitSum|$)|dl z+Q)G!`|FGj>)()O(e#OVZX$3->G!y$3q({XN-dtK1%&hq3ko(s(>2ZLyv}EvO<7GjIzutcV#tS` za9n7xo@DOP*??A;vZ7)_A$cZ9-S?~yhX#AFMQK3#O?EbZ$hYv#&!&`+o2IX^P2sxr z5Ll1xb(7Y!)X6~UaE>CBRkVzuloMvAj1;oHTg!T{8E8GN*9ZDZwWT_R&RHHk6JP`D zaZXx*7F-HC76oqH!*gt3w;?yCocwZ~upUdu z9$uya(Gd{~%(X-(4z?GU_PA2mW4_8HFA@$8JBI^X)Ljq6?NeGx?wPzU^TK)(Z=nYE z;sw%qUBRWpBmUh;@GH@}3vnKg?%sR6mijzj=C~Y5LSJ zr`VRH;Dzi%SnXOu{L3CDSd=*}+E&k-(&_=f#=wC%#OZ|XP3z)^23U{k7&;v$kG5FF z6La+tI6r_jo}xj*K@X8@+Bul<>!dO@KO~juWR_*O(n!;iOj(2VXwtQzdbd64Uu?=! z5nFQ}Z$5n_Tb;?Qe5{724GhZ-n!aW>_!8;1@pXirGMGhm0M-*Zq&)cU+#=TV63!d#!OZ2r(!%~d6>AV$D?xHhO8{$l&A%m?7s1l$i# zl)I3GsMv{twC{Vv?qsN91S>b}$l#KeucRBp-VgTw%nyhI-b_XBSUG(y8Y#^~D=F`s zh(`%!hL~FEto=}C%;&DCt?w(*7V!&WW3qU~8LMF1N9Q2-LOy;OX-BVH_~p%4AURgq zyYCiss26M>Z1BS)pQ7z}O;7^s>aKs*pm7&<8P+2Uov1QNE5x1Jn79^WrZc{}{si?768OAL=^^s>kek+|1s zif_jyLqNfbjeo^?AccqZfP}0sFVeJPJCe-RCzQR_B&OCE4#up$b$9~6s zue0bQ8YiqLvegyzw-)`%7ujCPl}$^4q23`N@31W*;H5q4njFvtc{|7JQ?h_G9wneM z^o8f2`47XjmA!-|xoWj1X)jaaixzaqE1%(2+c*s^;#RUfc|A#P)*itQ8A z!y|0_Sl{KG^&{RtBpUiSk834SjpBF|iJv=B-UWSEDx3exFyNyv4X~y7LAj~;>^R=I z2S&%q(Ma@Rye=EqMCUvknNRIy%KgWU|f)Ctgv;&@zZx{$Fm@LJKDo~ zViHL=-!qh}_I7(waYA=ZNScJ_AF$BxXhW7ZTKAu%KW}v0bz;vFkKH#30v=gPU_Is> zs?}FVs&IZTpI~@PM+4~1_qoZTOw|HC-ffes?0h;Kd268PO9hAwdQd)dH3RF>S*J}Y zl?;y(8J^!45OE~VOlO~2W2+a*yAz7&&)qiWJ_E&`a;K8}b#B7Jwypn;UlxVqCLB{v zfS6!p`%i^jD}H06wf5bbLFpe6gQFRsPf_!M5=1x_NILDwoIzzCmI8j|?zkb8BNYrayRh zpY3ESc{St|taK5%W;f2JtoKpDbDFHOC(_(a?X6qC(@wNM;2*C()fWo|fJZ5FLLW8| zgObuv%$|>s$yNz8Yz<{I2z##=LLNU*0d34%YPQ(>QDHrPSJvj;Exiu!0F!2^j8fpH zoT^={>Xzppp0F3>r_jnL#dJDVJLVoE#CQRgwdp!o4_vuYW){fN6F~yTe5?CNk3*{v zcbr?#N2fwvpEt9068XbU&jC`gzRX|f1{GU=8EwBVioJdk!}E;RJ$fp8 zVLo{p4}P{(p%t3Ej80lwQMCGI>kaF%fRB6a{Q`?yR?vHp*}C?@H2|=;hwH&}kG)tf zUQaU%{~j!m9S@=CGKrYF$G(B}C~B(`wUWxoUIMzH0cLX7Llg%KS>IwDtU8;ryj4HK z_bEj*?9+HFx?$-bR%cBASw9dcWR>0T^yO8Sx&&Igx1socHoLBZ@_%#R?h`5IQJw=!QO1t6v4+tjRZF$e)rjM*ZUk(&Bt4C z%u{9kU&)G!nnUy9JXnulAG{60A{4mqsc&9jIaAcr6U%*CIWTg}HP+9NyoH}d*UmaI zJu4CFIn^t6Z|i{d#53^DlB}I^h_OKlNYz64T^oH#@X*Sdghuqd zCRRMddNf|&=s#y>*6mu7UJMI4JI}Pm32=2QG0p?{i1Fq(WeRwnm}Ih5w8xyImdZmWRE$-r9`8i@j@-BU_ zo=DQ5Q*&+2`EM}LKQ%ZMr*h3r$6^fi4F)Jk>5QyQ@@J9IBz1Di4fhFajTPXRJgg@i z-~4$~mP%Vyn_ljZP}9yFzCwv$R8WP5OHlqYi3Orq%y0d%uBj)@hJu}e^@a)7BQ7Ca z-91u;XZ~JmCUcPZTq?7(J>`2kTkFjm_y8V#gu|d)j_Eg2O|}bU;?CkDg7vU<-c)pm z-=(nslNlzurPP99H7ELrKuWtMlgFiuZhpBy!k6{(n%pR(&?wZchACaGKSlkd-8SJzcZ#pYD8l|>(IFN+w z6eVdRIKhJ`MTmvI`OAXsMr=JyBTj5rD-0b;Z|9%&r7f(DpIuAx+ZC(D+nf9?WPrFr zkt38C2O2918JIHujO<+@a6+mTmaeu@L76?8Kdr`um4<_-DSev!UxXBWLB@bMZ%}sn z6!`M2JJI=h=8x5;Ba#I38_bQ$uPzdAFt(lLQeFa>oR0&JvQHkNMB;{}Op;?%`9xLq z<}W~BKsZvhH-hbUisZ};j)jE`o8~pKiwCaZ{9oehd73K|3C26UjxqQm-$R7FWcucm z?2@Bv1hu+GD;(&Gl_3O5SQ&^5&3YIOcPJ5qKF6V5$v$eUzmJR1V(?&}$g)Z2ee0dZ zWNuz!)a9OYeOEZWLS26uor=sa9@dvHHByMU zU3EUx4rG~~uAG>!sxr$t!UuEAq@_xpU1yYO>nO4H)^1lOv0@PUbcFFUax z_}_)c-$YvO|5p4_M~a~aa&6QnK#R1sVNbZ~W1bh;G6fwP z19C+hoNV#MJ2JA+-_Bqf;Uy|A06Wvj?L0kJKhF)OE)6fFSBCkzjOiSWgT>cHk|EN6 zmBUm@+RmUQopcXm8yhL}4<~oYkFG6!ge^M>EIBQ{8NfQc0T7GB`23yCP;WkeepHwi zW%@oXC!s(Nqt;-O7GF-2^Iv5-Mbh!4w^Cwe6-0V9`KO7#YK|G<$H>^ zh}V}V!9A?C8)3!Jm&}zbh=_3a5WeG3IHE7bw?M29WS=YTqN?E#S5;?^!Cx;DM4;AI zgxcEW(ZA>(sRw^MW6uuAXcrX0_de2i{?!bMGbi@X8uut}-$(`QH&`^bk>^$&{kknK z(%*@Nq*?P5AQKhdx?d|OMff7qL0jU&mMIz9*@dM9PqY*7r#t>6IFMRc9^Ik@|Hb>_^UtU<@41QW$X|LT#Dyx#4@~ zaHVtV2EeMbFcMsRU^5O4;bHy>tN@WJVns)GXOV`-t}FCsr$mP)DR{fCYK&x86VKRj zdvOEO2vMq#qpB4O+i{h}1NtX9q!M;mL7 zsZ1AT$Y6l)_Q0M(9;mZmrD19OBMbXgaZhE9vS-P4Y=R&Wf9Qi);TYYv2X9nzCo4g~ zZv*2X9*TN^I}@+}W8*BerWl0H{xo2iV;XHqj7|Q!HKVjA)+tYpFnN?;DqeYa7YbIS%VcJ!s6RtxFUy`qZ#vv21=PMZlQ9gyZ7kosfZB8}z zr{xOhT)tnrm~Opnw0uIWY7{O}+WB@oT^ZM$XVQ;M$Nq9rfSw?w|JUqH`?Vx^Y^60aOCX9-%2a|Gn@1SvKAbe zFPs_bGUvzJQue-kDD)u^p7?E-q-mhOCG0o+p(e_`;prC~b4(jkv z=~pHwqpf1mEVVt1olk{}D=o5vvn^lh+-WHM*cOkOD^f8Q0s z)Q7ncB}3=8--&SKcvIdZWW}i={P<@27P}X_-Qh&i?et}{4_F(}GZdI%p%}#B0Hybw zzHxNc00+9!;}V*p{YEPVKbdbwD@HWoco_bkA5FM#v#sUcO>t}+8+XPes(4#L6wk7G zS1dTK#h#U>C(5JS{tbPo!1)H(;?d8Eg?zyWG3TyyrAZ~ZuHu=GfA5LpJd z9E#&qO3ygPu!;sb(lB+2)|@yCMI4`14f3_%?CLgf`ei&S?`aiW*=aGB#ogHj!CBO) z%HDyNGQAULH6Rwrpon0K{jfBph;kVjFDs(UkDG~Id#F-Y$Z1`;QZE8jLgEf)+oyr)QWy2Z-8g~vq&kWG&y6qklOcPMs*>D{2lpT{{fzF6}<%E z#_szNA3+zvQBdT7g!^2Z5!GPEM<;)VJjnX%NjK$+JWr<>3HNW?r<)%OE(AT}aBe77 z+!x94zBxoh)v|^70jRk){FsH=7?MYstWw^)2L4*s|MA347mb8}cV=)K7w z>aJRGe~!@|kP8NzG#Jehd(iErEb%g3Zs=~Q8tpi+$_aaou~4U2pjuAMCo3h#%^w^f zbW038&Vq!}-TwTJTI#>DtmwXD-+I|zE;w{j9EqwrG zO?m&4_BYiu#V3(;Zx4^(NQjKZC;O54A`iC0Ra^+FEsv}mDf7~=T90z>TWeCPEKXadicgv`mg5?$5%3jylb{OzSmGAVq`pMGG2(bLBilTr z%5}$AH_@2Wz#;n3hYWcun%P4D%k4}+pg{+Rf;9i0>^;>XKM%D2g4G{m*Hn!%f`X^# zubZinV6p>cy!czjelz0=oOe#9&z{3}ch0 zqSploMwReh12hF60dHBIpe+zP9#dSib7%`Aw9J}lo*?upWbn~iZ_Cy6a-|K*oMi?w}`C(op_Mu@R(zE`%Z z8yj)&-|H4BKdP^^l0 zKsH%XQ8D2PNaCQ9TQ71D0H-eeV(a5Cyt0i?nr!6}%=c80azoRrY9mp0|Q zg6y|@dmH9yq-fSpE>K9IIhPD3&*oVRgdi(pU_=yB?cJXa2ur3tnTyO~>Si`83oSW_ zsUa`q%h?au^$Id;|BOci;TDh6pE>m?bW?_^9AYXkl!5*EJkw|Py7o}nlgj>Px#rdKx&C1)GMN z@w=ixyWK@+^Ur=Jj2fW~cw!*Eq!QT}7w&$rQd)aUiLfhv^M1-t8R1(S6&1+JTMmob zh}sFT$c&(QxB0K4jIW@9$6KC+{0Q93g9|f4ZIdb6Mp=?7v1Kpgm)~Sjt1K5t-=^%^ zSS+anfNgaDoG+nVU$a$lh5~Rl?5}Jneym2@-3I8d zMrn^68dAcJ4+aqT>09Z7e9Ggfy4c^mz0wTJroWrxV`r$f=P&Vkhwp-jSi;y}KCf&Y zzL-th(ZhO}`ak=}FgZM-Z2Rhz7F_OFr?bQipy#fYxnLG3mDgXcdeFK?gY3}jQN14}zMUJTrYvtOP2=5s6CGd;+#{SdbkL5wlesu>@B0@X( z)@+!-6XvP^m6L+3rxu3rY}BKgY+Go~V!tao@R1IzkH?Elp8lstF7hwb<|94Jos|mL S-r{X~M&0oI&-}kB!~Z|Q2_ESH literal 0 HcmV?d00001 diff --git a/proofs/solidity-verifier/fixtures/ivc/instance.bin b/proofs/solidity-verifier/fixtures/ivc/instance.bin deleted file mode 100644 index 9247d55a50fd0ab415307885a5f1bc9985cc2e75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3528 zcmZ{lbx<4F7Kb57&=hwHK?}v9P^1)>qQy3Na1ByiOR)_StU!U{?ph#=wrJ5(Tv8;s zLver8-FY*8GyBdT=lnN<82-8iHVj12Bb~0~G{Hf; z!c9rNr$1@|!&@yXJeUXnJo8T{96lk5a=uA1J_fr?2R?+@60j8gB5#a^lkQ6I-qkt% zQM{IKFO}dc3^94R+becBKK;8-@qz9D{YH;?MD|Y^cKa?N=I@twzw>{|n7?L{B*EjA zfNl!x7SSG2--yq5dV5`uk5)sHXnd}IcW^gehL|TrWBGp?*1$`grw@$9rToR?t-^Fq zjT=>tF_^ZX8K8)2T_{5(`c=4wwH4@hAJYh1+2$&{(_}EIh*X&Id`(tp5%?XlD^{3$^=V(Xu98Hpbe_VDhm&5++o-Lc_Y{b41koGRL+PyS|B0aEL) zn&Ix3nLqnUXdVsNC&EaYu6mZQN#u79^mR;Mk*KDEtrey{40^}Yvwb6o6j64ypXcCr zl;<>wJF0I@vj-H25Al*dmS}vw9*6lXaIO!4T3 z42#(Z&l#b~UM@#GAgXuHxRa}Q@8bQla4f<-OhO;C%@Lf6w!`29liK}a1USnC;(L=N z#k{SK)4AZ_kHd4A&vHz&_Ojd#U%MUKo;uZ5u;4JoFi)~NCQ^raun$!O8E>wg7}VA| z&z?0pyH^EBaOZK2#u%p=Ha#PTs2hqL{$y>pY|Kva z>tSRG(7`n#jAnK6UOU%pC`Yu~yZe5aBr(eB^;J*RT=&Wx|3m!*GuxuUE#|bHY|2A< z6)ak_z09}g2wx4Xfse(yFK(t&g0j<1R0BMZLE@jsH*V==nax4Tz5e)`Aq4c}30`oB z{a@IT&ak@ghrYbqO0B}KxM3a?=W>mu5KKQgGZ(z8+y)O|Zw4WZ4X{;98rdg-EBxMf zi2T4f><(4#+&W7ECi4)BpfU)dlYyxGB^2zX^h&>DbWzly38k?;)qQZQEM8EtH830- zOcH>+S$d4tp*yXGDWe>)+qZuz?ot!mD@EJaN-9dZF>)CxSN25bmyckXyraie3mD_5 zI1OAV76$9A>(iLY)tpma3TgUU7$yF9^qfx z`xnNSl92u4uv#-x_&`f+ZY@Go09X*ZU>2*%7{A0~v){1HkjWqPYo~G!&1ma~uexY$ z>u%V>HCd1|dPEu*R~c``G+qB^=UOHKdTaLarH-9$yQF!+D{xi%1;0p$vWG)`R-}%4fzyF`1an5W7 zHnL6&9b_I&oO$0X9HEJp8&V|w&`k614(TNk55LP)x#PWS3k=ecR42U({ly#jRj?t+ z!@;n!&U>d|Wi%@$JGh{?Km`MiJNAqJx5#-GHi2ir7aYBb!)e_x)T;J*3%j@JN0Ztm|Catn-%Vm@u8wdHHnuaZVw$vmS7mIrE)lEQl*fRK#sYv%j$W z+%Jxnxrjytf4Vve~aVFg#XK&tQ7=aL4AkG5sPfh;Wru;E4Qf7rpM#o&)4n*%)QLb5 z2k0{C6UDfwa^;2i$1*W@f7a?u+u+H~092LZ(VEp@BH!RYxAdS%6;f}_^5n!QPig?2 z>X%yZV6?m%XljE{p#_(Bidb46X z#8PVhF+ioq_Qot&ig0&$j+J>ygLh!5g?i?-Q3k^c|8FC;h{oJJ>6E&R^QAc3np@@4 zmzZ_hMIBtulTC|yS01;$$fR<;l1CiK^BSw=G<>|TX zUWYY1`GW{so}THLt1ao!URg(??QwpciyGDvU)r|5*Zfv%WD}qWs~8orHL6#ASfSU_mzq5PnRKYMp&2mF*)>QeL&* zM^#FBgWVSrKS={{(wFgjEav_aC|ciEFkm@*O45^Rc|z)s3lAIk`4#DzVz4DYI}Q1wOhe zeXW%_UrQnq9{mA_Tt5{mc03{IJQ2heb>TiHglLz3flgb7#Xi^D&0#$!3?uETo76*a zb4KJ08aXo%X#^Q|Nv_{8`1W9h(q|DuW+eJ7=u8HC2&5bKmt;bL<^~mNP>K$gGqHsu zy&?{pKo1mcGD%MHngz%c&X^;wpm9_q3%3@IT;>$sFhrRIQDz%WHTHIv?didrsW=jK zx!1)o1LKx$f(3vCe?lqAplbc&K1~cGb!Q|G=6-o`Pi|~dW0T`ln&&1fW39om^=`0S zQ4JCATk&dTvyfc$I$iPCMv!60P6>>o0LHdh1TesL3s1QC043zlG3J+B`c^@`FZ)ey zazhC;B*W98=Ed2^b@5c7qmn;gDs-qIm^(^=qCfhqg3kytiv;>%2O;w;U{}L#UQSzT zv{QD0;5f@3D49H@+uk^7lg#gp7%Gz!6Gg~`Auhuu->mNav7BeDBinTT9k}?Mmc zXH}@R5{4ukL+TrfMs;(_^{;Wsvvu-F3z%@8_bt4n?wd#T(6ix`2+-Q`T}sVwMJx0e z(^4u7c9aoO_*0cCbta$LrR0zdvl=_rQ};O8f|Cc#HYl2S55x6>ShGy3p3$*=;b-R7 z;Egv}6emlR;|Hw`#kII(=K|VJfOfY^)&ip3Q`ru!gr@#O8mC)`$dpf7GP$VCjO{*j Qv#xewZ=xlnO_ciXKNgEPzyJUM diff --git a/proofs/solidity-verifier/fixtures/ivc/proof.bin b/proofs/solidity-verifier/fixtures/ivc/proof.bin deleted file mode 100644 index 910bd9d65092fc823312c99eca1732f391ffb29a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9824 zcmV-mCZE~0qwN{AXqIhJdO}A0Ej|D5_LI-9CJwx#!Ub@!J&?O+fsY?4{9f%RZP_u< zsP{yHa$Qe?Yt-Sg)Jb!v%KD@4Xk%W1w^o>L9)6r!sEzJh6E0SD#XPIQrezIJcOsl|30qDSbdPK9b* z;f}pJ!y%~YCpJ_O5jrfC#p4(?8tO6Pl6#qs*Q#mvtCZ@`>?N9zlut^TC+&kt!0DkN ztJeyYvc4AB0o!<_j=vj@gDeud*0DpKBuSz&`*<|T?tm(s7rB~;@>=^$R-L0)GmKh| zZ*Py9aMSKgOm-10nUy2`lE)Irj*gl899V!xPSFLf#a+Mq6BoT04Jpi?QD@PF=n|KLs_Ibu%Ql8sK6W!L{fJo3j`>MmA+~ zJ&(S6D$+&e6FhfI-*I3;bsN-%BU&9Gct`PtrbPk~VT;;v;J6(R9co zkj<1>@;FPW)3y4xWL?dsmZ@8m3(7H=0taI}5MD(#@xj@|f&r7TOVkQO{Y4IJU_&ZGbnRD=s7* z(WCZ?_`H^i$|_F@C(fUQr6m6Xq!daT;Qe^9y0yzXbMf^>0Q%R>Sm%J8W5c z`O$-ePRq2qD5pW?r);1n;Z4l70grsOC%}8ouABR-H9rPNAh6$XQD60jk3?yX(y(M$&fWfM(eL#@DAJ9Fu05S<(KdG^Kz=A~Pp@Kn7ad zUol0;0r+s^_@I3B0jvbdSBKlWBSN+l{*iKb%Zb&>)EZtPyRRsfR zYl9co*`Mx1_mhz68(zIIJn+W9GVp4x+EJ;k1}G}(_|fv!=jZ?1!|0y}NZW@2SI;Y> z-hflrF~S4@9Drk^wBe2kfvvERYvaUj1evB^>%3BI3h~s(@YXP|A9nu*^V)|>->Hv- z*s0J+sY?dD&2jPAHx44HW6-F14D1@p)ECut@I4roJD1S7X`kMK2SjnJU-(OFBsJvT zHHJp=_{;6m2Z6bbRw|{87wc+U>elfKV#<@<<|r4}vB=YXVC%b%|DjuIR2N-=^rzh>jFodLkdhv=^KFdaca;7cnWZt|`KgkBt2bXy5@tSt_dH zX?9|)laGLP1bXZrqI*h}jj~jsQ-HdY?31ydT==NI9YA>JOB)&raM=g-&SVG{{~Z;r zaHRc^aLcx$-I_2YhT;{E%V*vp>sij|U^ABJY4c2d!|Pk$oI~=_Xf?(b>PkaOC>zj-zmbe}yPNwId&|D2{5nJ@~9i(lQGT zEsh(%mSvJImMiiEad^4`i`83fWR%69ZdKcW&hQD{F3RH@r-Va9uvR(}gd_3T+c6*} zG^|sj^T+Nz*VQ12Tki>;XZWTs@NkM_i)$VhQAt1G0*Gx5S=dCZ3q@>|qLNe!ZG|u> z8BEbve&;bA@=cDqjo;}=L1a5Gu0mshSQ{X!7`oDOCYLjyh+j%9Wp;LEppD^aD}+zG?Ct(spC*=2Xod)M7oDN8vfE9} zDsFQ-r>O=yUNdM#DDXibS0^YiXMpp4?=b;bow8&DU0@40y>vbuafm4BcPjL<*OQPc zck2*tEEs@s0RJpn!?C_hLrq7{x&dkFwQ>;GTDsBwya%QON|stEh`0_>zcX%i+|{nG z@DOb!V!8-aCane$rBwe2wQ%6IRUaK@3lqF>-1G0K-T*=aoVtdEcD4%}ksOC-{{d$U zv_l9MyA%zQ^YAh|9vZAj-{F;v`tnT`#2Z({*LC}TYM(#5DznyUgBP}MvIy+ZMOiLF ztY!qsb%L;F*$eiWAtV|1Y?J$v=XY#f5NA!i7?Q6;XelIQs+eYuEab#?rRZBXCWohY zzKPdc(U75a$N7_UX7P8JJxa@rXmEuvE5lT7S%eQjLU{WD+N1I5prN3gQOqy%(jbNOAO;+LRT8_R+eRxlgX40bWYoF^fKsq&px7_Kb`Y!UIGr&ucl<*U z7G0p)K0>0!vAX*iLX&FFUy>|ZLAZMFT!nv7QSif5w}nszI_Xp_F;h2y`HwgouQEc! zx#tqRh&4d@OuMYK2>-Wds*Jtu5Uyl5RVMLIb+k!ITP9Qu4Nay@SGy|6$*b+o*v44I z$aRa_>s3JRy`*BAJ;zgl3)V9p($fYjb1s*xR|w3nlj&%2NHzV<#n4km5vNB!&{hJ~ z(i|dqOt~*qBx<4!<{pTdZ(R6w-9MKauWOLU~idJUhc*WE#3ezb5 zGjCaRWPV`Rkv%AQg;dA>BCRnh$!l@0li$B;~ZUS|kW@0v-K70I8 z&nJ_4Ta#hiv^y)uQ1F(4*(8x?NLeS`v~_%kZD|@*V0QbdMa4w&z|`rH>VQ)(C^_<} zmShQeLrFhJ4$R|UQ13-oBcUgAbxT`p$8 zmY)C|{^{VioFN?tJl}*_&MOzG%sw7cu>@tvuXoR;i9l`r8<3Hf9)}&lFh5C7wGBt3 zI>Yg)C!UcK>e6RB{8xoLPk?*5l*3x?qVnTAYH3zwch}$`#o-b8O_9LSt(3lEFM)Dn z3)x0_UX7aJqJ|Ap#SiIq>1SqIHtKabdTCtem5%$Zml;MIQ4w?fov_cAdl{JKh*?CQ zY^b85YR9X)0>ah9nVSKC&nAXP8K`l7R-GH-rc^-g<5U~+cB0%}yy)@+L9G;K^u0$1 z;jTb4t1e0;*zCbzb|jNW!?9*m7W5_=`)ymv&!*%ZVK`APT%!|7W2tIg@DN>lxA)!0 z(ta`}@)RU39&eRX2q}6Da*7=mNZ_HniuBx!dMx)8?Hv5^(za??W+uJ^H%(apBM$-9 zxsERDn3S0~5B9noPQBeG2fS?@T{$G@3BpZ{)&Wg4@RK?xVI?TfwP7A3KR8jQYjQW7 zSq-13yan=b+6Z{T@ew#SqgM}d;WVTCRP=viwlrS)A*|C5lWyb^ ztb&W3(Du?Ly2PKe0U|!xY>6EoHx@6E7r!B8R=-(XJTSfRA9Btc@;sySG$x*z0k1RTC{x)y%Hdnv5DX{0kt zqZIO<-3}agto1kGa-Ns-S3}vzldtwr;4G_<#v9T?#P$rCjgL1L%U)#;HSnbpX)QP! zr$NUoV_!cw8;=CR0G*)9ckZGp)0cXAea|QY3`gw(0b~x-C z7C$!&D0#|KC7v%*uH851RXP7s^yQfTPB|Pi*n3Y((!mXETox(wf?nbO@q+jeSxap2 zyj%Clm`BV+uu*WXUu;pK;(oQa))%jQE+qQWSb!sboWVZ8F#N@oUZ)j3gXBF(uN1@4 zONts2J+i-%#2k`ac&09%PRnN0$4#w+)Epq^?~Jyx0n(Ub;?Qpt^1dOXukoBgwk9j; z%YOb0V(=Im{69eoz3CL!Tw)$1fE|O{V%fB7KvI-L4;5$L87m52kmR8>O@PrZ-k;2M z2Z3aRvrCI;nk}zPL87IGv!WSnRsOYij)0h+ehe~{7A>fi;oK3AgP*&5gRe2zwJAp* zt=>YU(2>R|$licodSb9|bbFBsS%GBKVt}wI`;)1EmttN zD|mg0*2M`sSN~*lij4%V7b9&$uSLH1jO&Tk<%>HH@Snzufl%mm(u-@=$^R8H z!4zMI%B%?p8(f8<1rT^_5zI6oTkNhJl_j*f^qL9Q^u-8)0*?i=-zS9#$wupCUN6Qu zR_6SlyO>i?@(aO9A=(LR+K2FdwF2I#o^eOHnnabrtjz$BX82QLsXHY1Zcj3STMUBY zvi{u?IH#!7;$9NjBs{aHu}93=-G-;>X*^I6X&$}}e7etecD?$?d_E8yXIA#R|93uV zo_`s!szp1cc|djL4v_DR`*iTw~*I+rvvCr=^`P#Bb9)0YWb&#?z->?ebi^Lc{5xy zVt2_llMWuiHH$Jfc6cg!Ei%E;@iUjJli&VdT`v?Z)4?#8`pg-9->DB7*#CJ(mG8?T z;)gtta73#w(9|9Bv1AX9Ix$msx~~Z3jccZG6~vXd+bOt>ZUv5W{t*NF#Li3IJ+^)p z^Tmp&>EG@Wc~}`6LP@|^)>ckO=$2AJ7tJ;<|E%5^&MUmI)f9TaGcbOwxXJ}i->|%} zIwk}~apA`021~AJJ3~n_SF-l=+Oz)LxtWw&h=J8d)y*Wsy{vsEh;n_DRXGmOa)G9qhMf za3WXt=ASB=9y#-P-|+`Vt_>CtKb&u){8;hTpDPXo-Zn~eXvT!i*=aw=T`o!dlFLLh zJCS0^AO0KmomYwhotOVi`E%+gDx64N;6s%mN1+_7z-OX86G|XCY#fBj!MilyZIk*n zJv`9{8q87_I~EFq(@MKTS@*AgJzoPvQbDeH=+N(d9edccmU9-<^fn?YsZ5;IaVgEj zzTxsCz<~Bz9d1NgG_m#g5?@0C_JmFO%v=cp)Ie_QUL8I0IXOXuW9(ClQU{`vFZ*Mg z$>ca;ourbUm~F?e80WokIuO7LcICXxjOn%pbv?xxT@h+O=TFUsC{lkvOvRIyjs)lk z>fM2z8!>Y`Gxd_4Oy?1sNfcj>R~lc(Bor49qAx-bUAt~jiR>EYztTSCXJ<<#qZzgt zzv!tg!yHxog?00!LIqpaJkiGJnjtntX5TrxdmeAnD0VVMC|sJm`9{vK`%%=e;9d-x zofSqx=#vC+iKr@4)hWJ4uTnH{m_n4GIGGZ#;eTmn=VUyYtI`}Ji$3P-UFaPBxX%A~ zrJ6f1#<6)4%0(Xz`vy3}$r)B(Ua2($_<}L!5Z+xyzFM*ArFVUM`?CaNVRya4Sj9}t z|Js6Sh1c}d6$_m=hEd#Rz_*xp<&$+MFeh3TG?r*^q^quoJPf`%_jeo+WQ8K3(Z^-w z1o-Pwuf`6?l3O+W9%Bl82`QUT;a+1+)Rh5hM}YfAU(3!@{l1$A10P1G+V)ee2~${& zu(@Ba^pC7^`Jbk~htg{3fnV@*cho8*myJ=RMQu}yYWjW)kh75Bv)#4y3%9XZLPa8C zA0}5cNa+_}J5FNSjj7S^)V~5CnhWvp6ktEe^3B+$$$AAy(!?nEw5}q4G}Tz&ON|oz ze^^K6jxxUB>PCHAEnp_DSf6Y?U$7B!&=qw9WjfZMe;F6o$p;5)VI@AxD;qe!bfo8P za}ls>-wxQ{Vk@5PaA1==`+KF5cWWAs)}d_9mN4r~O+*{RF35vJGmiwd3K4?BQDjt+ zgNzD|B=15j?5pqy$(xy-CCdC{Y4P+DU`ifFGf zN~|lD5Y^x1J%Y7!W4c)qnNu9@KRmrb!Xs8EA}>J)mYM8hl#}rtn)WFUOG$V|qj?Wd zN0nr<^=SCs$97bHBGFn>EbVjUU+mp$7SNT|e5wSzebm%kF_Yc)EtCZqM? zwI^QDlm}c!dDi{r&T|Gi?A{u>crUhx=!&exY{;lU)R!#vPth#-k+*Vnq{0QNT}J2y z+1d{!Ej=~0#3g=RNzBgGg82Gc}dg7OaZ@SVs%^gUTHW&}hW z+O-is{s{7LGH8yY_{Sjiy78XXf`|ue@I9836d)qaep)YWQbN)*Cvq;&cbangVovMUn9LH3L}5F-q2O$A zY4p^MI@)4S+~b`m9u@=?cCRez0c#=Nur}h_S=246Jq?IR1tOqkN>eSN9QGU7z)xf< z-DewCD*~CGiJbC{aNv-D-|$peWMXWJ15|gN`@e2*mswUAb)fLx6Gkypb1^?&ko@Kd zR6{mQf+W%<^FwS_fxLb=6N}7WKl!l!kd|xeUVoQ^U58`MEw7!RO zKKv9XmI=MN2R?<4dDNTc`OzTm7Ls;SoKdy;%Yf6&OX*G$U zh2>gt8oEU=-|J$)qRk4nI3xAe!0{#<>6qkM=|uHVjzLa;)Cwqpj`S2Dz0DR=!q?`v z!=I!-u;*2pPanA4i1!_uYx8g8=KhY9M7}vtal_D z2H1QO$@YH`Hc=$)6sk;tr21>%VAnm+n^Mfezt_+rKMvcvH-uR3H+2K269%!l*6unG zYc(g{D-=Y8h(hTY>bt6J6R`I=&AkQ^Acj8j<&x@QwkCBGONQa0;GO2^ZA)ZqU`D47 zwKz!wWAHGSWo*uJFU;HT>UL`>XeoALrc9>$!^Wme7D6q0c_bu_^@`xQW-|z;o>3lEM!Q_nw z{ER4~n1KL>6!q6!>leLYpz>mZ1O>t$mlujS)jSzdf#cD-dZ>pDof0LdICQyPge!WyD8>JC>9N~3k#`*kW zb2&q&6U&xav>ky30QeQhs)h9o@RTcO%V;snXi|1K=bbg&+@jevpTD4lqsFrLKgv5k zT4)NiY51W6uyRlITyheP3dTyCAN)c%9sp z)MhYuFc%~e+SvS9)O!mHpt~9QF@Xt-`L(2Qp@?ZQ>Vw_n-oy|qB-^yPK@Bq`%n_WO z#<&05Uy0rgmhsf8M3LB2P0bNWsXde_C4@g=lASDy`eyWhqRz!m=V;=1HGI|)isD~T z1F}I8tNX2`Qo=*VSDsjDDveCI2cRxdXtw!{6-D92+nDLbxdRas4eSafHPEiIlcB>C zyv{g4=C!8MO8J6|;JnIl2qlM6)BP4rk9g9Z4$(R~yNhmv3?T4j!gI#w%P5A@FL_fn zhU-#93c3VLBr$UE9t^O^HQxN%Ht{&0`mKzXRG?A;l}iLkvh%|ZK7TX&(y_Ji9zJMz zt=Ca=#AltsK>=PE6&FTv@Gkk^$jE50X@hYa50BS;Ji$ji-TWS4BUg*)U}!qxp~7Qt zmOn(90cuQ??=oa?@!E1h)tYgS(Rc6r|B|{}xJa6QeS?P*e zUZPMt{K;c12oz~A^4H=fA4<}K%I+E6= z(QfG~F+BRD*wat@{bag=DUt+*1vD0=oazca*tf~_9AZ@`%o+d$oHq;qn}u&&aeZBn zzt`=`mEJKp{J%7G)R%wpGS}YRIoOZ|(qEz2J3bjapTVh0=A(_T)O>^gk4NVOO$#fJQ| z&I3~DD?lxF%GBZWbhj_A^y!ARhLsq$ysUs%>bve)Kg@q2j8D)oFSWEn!5~5u89$^O zDx{YxGx!9Nmues_D!Q%!tE9ntpl56>l5!vYZp}I9QoF>Jqn}-3v$G4X_yHXE*yjVL z1i1{cM}{}iSdV~FBnV|8yT+K^UzDJ6SXTXa~3 z{7Ot3U*p|XJOM6`KI>uy#m%`(eWfXTV$7_Dv|1$8I&1MZ)Xy=|x>wD)&)>=~u58vI zM`Upu$56cGWzqrHP*nEFERg_vR1)HKcypTY&IMC+FSX4NJ zUF4c?Gv7{nq}n^P)ju`la`p3HVeEMPom+0%ywFIa%g*Ya_KEq2i;6RWYnezGo@a!o zD{+Rev3u(mSF|M019q{oi&eL2Ec__|+Lj1f%ed?IP4Cd!-^p(Vex8LLgas2-KYU@5 zQ{Zw{w+|Ea>0(b-szPfu;Xmjw<=_Jq2gUP;qu}p%?Wd{_;K@mdbrfszg#TTR3Vx}0 z_!>Z{uTQ^IcGArg>py)Y_Re7QKFm6Lwi7u^+M~HAdSaU5-!hieaEfO{+O~2Q@icH&wB}0GSZ@RcfQS8~LswejM*O zP$5I@GRulh&tg~-co6%hky`8uFc@DZ5X-sAGWESwOrME=5g#0~jhJ)3;OuyQd&!+{ z;*I?+*h~)DEa9?ZLEzcHS$c9!F-I8xojebIDgjGZPJd8+P`*(z!cCF)N`DM$6<3KI$4rKq)-1(P>h60HE*u5AOw)^M*HRO|%qDXAV|%8~-ip5UVQJx8bQYtX7eSVNm1K_nrYo?Jxs`A!oW6OyeTRz&`IR^x8l7WT$f3IzE8 z&4O0HV#R9|Q!%THTB3%$owA(s%P4dh7c$5H=s}XapC?S0T&&L+RY@V^JnhEYQ=~r* zSazv9CYidub8L6OB4A*nmYxZ}1`22{_NXTPx;x=wDZsZ1T!yv{bs&bvg*+RwpYwJA zL>b8Y;izw1?B~-83jttDt72R;h=lb#--u4z@UyLf|M@Bu2g4p zjJrrpGbka;I2J~hVoBOQ6sRpvm7~VxS{#rXp{vrd;UiaMNBSg^4wz5{ixVagmi{6VA937H=8{3(ZbAMf+OhLN~%ju&wG zvRSu&qkr(j5gL85U~p&-xs7ME&kE5(kqh)kx&~GIat&5^s-=exAfBDDYwHlJ7=}*} zs$|Ap;}80D`a45u!ya-oQ5&Ae-E1VVxzsFpe56KC3iJgDq80sj928ueLpZU@4TS)0 zf}ReYkRGH!z);qGyGC;|(;9hISVB@7oy$c#w5rIf`X0 z`T1%Za;iwd_xCTu*C`5ON@aqINz?*8i%3vX zMG-_MtAsp@CJV^;5rF(#YtAGA!q^ykIgruq`y$K0X G&vsxZbJ7z4 diff --git a/proofs/solidity-verifier/fixtures/ivc/rust_trace.json b/proofs/solidity-verifier/fixtures/ivc/rust_trace.json deleted file mode 100644 index 3f5fe0189..000000000 --- a/proofs/solidity-verifier/fixtures/ivc/rust_trace.json +++ /dev/null @@ -1,1068 +0,0 @@ -{ - "entries": [ - { - "kind": "Intermediate", - "data": { - "tag": "vk_repr", - "fe_be_hex": "51a3ad301c5292dcc208abbe14bae00feebc636408a4b2c54d5edd07ffc492a2" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[0]", - "eip2537_hex": "9324a75f7803c2fb7be84f8f8f92397fc38f438472414438972d85137fa1e884f7fde28c1f4b1d3555b721915e60a4d7" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[1]", - "eip2537_hex": "afabdc807327c5de8fc13495028900d83ac62f2a9533b780582fdd18bd68e5e20287c861c82923e6576330099dec584b" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[2]", - "eip2537_hex": "ab042a4727ee1f333d73706db25bd1f229cb608765128af1b9377b3e2b4795e85503deb7f086fdb781a3c4d190205a40" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[3]", - "eip2537_hex": "986d652bf616055fcb845f79e496bb94a6dcdc7e332a53d2c1d2370a637b0d2a4f2636551e681cf1eb70c561defa62c7" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[4]", - "eip2537_hex": "a24481617df262dbd06b5fddf658f9c32e49c6403bff43252ea10fb35c37d987af93346ecee6db2b794f8ea6b2b60286" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[5]", - "eip2537_hex": "955ddbc47fea8bf6280024d6b455ebb60462d79a19f49187443bcdcec9b16a84f41f41367805521eb89f876c13cc2880" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[6]", - "eip2537_hex": "872ba4345eb8cc6bc2fca678eae0455ed17ceca164901368a453b3826fde369e8cf63db2a87b14fc54073a369ccabfe2" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[7]", - "eip2537_hex": "8e14088f20db449b8f49695c3c84d8d873faedfdde1ebbe87c39c81765d149380c313b4242278b752177fd7b661d0554" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[8]", - "eip2537_hex": "b5e8f709e287c8a92bcfae306d20a15034f8f04e0cd8dee49059feaec3e99e002d4268f36f613598b68b55cc3b6ffaf6" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[9]", - "eip2537_hex": "93c8f3366eb85800f928116e3a177bb36a5baa5b698e70afc838e2e9eac2b35a5a0895da21f21f86b4dd5833447118ac" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[10]", - "eip2537_hex": "a23d93bcefea6c36837e348911ea299e8f478c5097f803c74c9aa0752a09c812749102e3a3321193452d710a95ed97fa" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[11]", - "eip2537_hex": "8c509b6d23069f785cdcac3319040d8b5dd155bac5e4a3b815891e6a571dedf99a2d46b4670dfdaa7c89cf43fc4f7df9" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[12]", - "eip2537_hex": "b642bfcd07db3fb367d968a923c87f6c0db808f812cd9014a01f36d8b1a33b20de6409ebd7db009104cdf07ce9ddd872" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[13]", - "eip2537_hex": "8980d60b9cfe2b6d5327ced4400e8ac611d292991c0abd3a2c7e692ff63b866ebf4c1a54f15c38603e60ec9247578c24" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[14]", - "eip2537_hex": "b946984d70cd450a0f9def10b8f8694e58f74b5cfc521963170307df92baa7e8237fe25d9fe81c0421d909ad0f5a6b76" - } - }, - { - "kind": "Challenge", - "data": { - "name": "theta", - "fe_be_hex": "3ff9db99be64b81421248a42da80d4cd7ba4aa6bb94e8ca2dc7c04b5feaabccf" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_mult", - "eip2537_hex": "883447beb33d2cd632274e5745542ba9f827e3de62e2218a3139751cb8997abcbd90a1ca2dd9be5f6c861b06c2c9ebbc" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_mult", - "eip2537_hex": "a06266251c32ea2ac0f43fb5dcee056bb7e8002fd9ea8480c384085f1e788e542965d48ab07e31d0140987c4b566eca4" - } - }, - { - "kind": "Challenge", - "data": { - "name": "beta", - "fe_be_hex": "5e1004307780e019454167777748c63f1ceb0bfd8137c218f46a29d9fcd8997b" - } - }, - { - "kind": "Challenge", - "data": { - "name": "gamma", - "fe_be_hex": "0058d4bedb482ef6ec68cc4102d79d26a3e68c67a660554306766a08d0b4bae4" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "b992ef1d42c2f3db9b82a33465d87623c31772480224aceaa7bf46ab89cca003b1973ef7cb161a373cb00d968286aafd" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "b124921f1914ac76be4886f24155f31c4ec67e4bfd34b400eb9d1a282310d7c4e7577f80297f48c5e46e17247c9cc03c" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "9215d3b97960403604292c0e49b5560bda50f7de776039dd4792ddf1afcf4485e4036d4c6f3d84c6a730805f433432c8" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "89d2652251e8c6df970de2fc4b5090d192110de217fb3da3b11db3886c472d7b9d4f9b4eac8f599e90071394dc7efe0e" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "86302bed0abdb7528321e4170aaef2962c67c8926908dc990923971736f90f2173ea372b7110afe8d9e393cb73da0677" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "b36a8b2ee086077c3673c953aa45c68f947735789e2c386fe2c7bc768251bff3eea2ac6681d1ba9dbf24a774ffb41662" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_helper", - "eip2537_hex": "88451ae997148f4b0b042066e35fd50c3a39a24507945b0ea61d3a5557d39e45f01740cc16d469f688730cda358e55fa" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_acc", - "eip2537_hex": "b14d249dd6b8c44b4007503b116d26e684be9ff9d1e76c66b9a0182f4d4a5998bb0bbea3c807f2294543b0651afaaede" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_helper", - "eip2537_hex": "a2d3ee8eea5e7e755b69bb79d8fc18ca688ff7487afb894c3077ab6c68cb8d7151e96009cc5bea912ad2fcca23c63f26" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_acc", - "eip2537_hex": "84189193c82ea16ce40c6c3a7670c782d9ef22343531180d8f15aef0e3ac7714c6b4d2c3eb58485630260e95a094e50d" - } - }, - { - "kind": "Challenge", - "data": { - "name": "trash_challenge", - "fe_be_hex": "0de9348372df1d1e7432fcb24ef5ecbf1358ae5dbe88097c7df40bfcb8d7889d" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "trashcan", - "eip2537_hex": "a846009a66f994210618221e9d4c3b7dd2830290c5a316a1befb1cb3fa2fc5ef84b679e5e5766914673284cbf6a1f3f8" - } - }, - { - "kind": "Challenge", - "data": { - "name": "y", - "fe_be_hex": "717b58f00b4d3d2793757513329c2c1d50deb5b93956dfd2048548a4d7837d9f" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "quotient_limb", - "eip2537_hex": "b4a018c05d3d9fa4b36bf3f2ae2feca08a658412829a26fe41aec4134158fba7739358e125cea1a5914f72cf9b525e72" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "quotient_limb", - "eip2537_hex": "815a896114a233d654689dafd891428d126e7e620e30dee5a5f1c0d9ceabc0581fa23b37dbd0b7775aeabd9fccb1d3b1" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "quotient_limb", - "eip2537_hex": "b6b9678337e622f2d663b2293748d82b7102ac83213590b5813c5099481fa123f81cf582d5f7b4e81dd3ce024d79097d" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "quotient_limb", - "eip2537_hex": "a2c5f0060ffd5c4f8741b884ee6834a04366739fecb2bbef7f27b8e2d08065b4e979ede81b398a1ac6cad46664035ead" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x", - "fe_be_hex": "25a95a70b527cd00441e66fc7716f297fce60d998bcc0158e816488a393e408c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "committed_instance_eval", - "fe_be_hex": "0000000000000000000000000000000000000000000000000000000000000000" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[0]", - "fe_be_hex": "2aff2f04fce943e11f15b05b610d1006f7276184a3eb52764a9b162ef19b774b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[1]", - "fe_be_hex": "613ce970681657d1997019c85dd9e468b607ebeb81c81bf5b3e373afcf25e417" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[2]", - "fe_be_hex": "5ad602afd794d4ba4fa04d59b442983c446b7a1924f36a1c869b6fc2718f1b33" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[3]", - "fe_be_hex": "1b19d3101f528b398c5636f9af64ceb203941f5a69906344f6018dcdd93ec9f8" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[4]", - "fe_be_hex": "63f62d35bef1451fe363e8ae48f478aa78d3775c1df49470ed9b08acc8a582f1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[5]", - "fe_be_hex": "6120b59c0ef4b1422d808ba3bf08c4319da59fe04f3ed69e30cca0e30438fc02" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[6]", - "fe_be_hex": "04924d8799758ac570dbce5be1bccde9de9b44f64ba93506721bc8fd9b0bbaed" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[7]", - "fe_be_hex": "2cb4f7a76de56ef73523af97075a0498d7789853d23aad7fc12c7b7f7092a018" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[8]", - "fe_be_hex": "62b0bdacb806ca168215731c666c8d1b3d35241023674f4e8866119719dbfc3f" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[9]", - "fe_be_hex": "5d9ea3fb49590e6dbc8a42f662741fec9a7322411f948f8d3f54bd70191a6f3b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[10]", - "fe_be_hex": "28c0c553847c9c7c586fa0c979a308b2d4c0c87e3c7061d200f7aef29776f159" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[11]", - "fe_be_hex": "48a364fdc95646a925fe21aaf84ed1fc70e97483af6ce14452c251c4fd20ab8c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[12]", - "fe_be_hex": "4e4e184924e843a78524088430f840c2734476144dd5adf53bb35071bb7b2d0e" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[13]", - "fe_be_hex": "1061413a5fd1787a3f0d54dad842be30a6097a89c52db89360ecd2bbdc98dca9" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[14]", - "fe_be_hex": "64da1115a5c5107dc2aa9304d6dc5b3d83195eb097c28fc05cd57a218fbef7c9" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[15]", - "fe_be_hex": "33ffd290974cedb43eb4e5e4cf9ae0a8c6b1c45b45654b5e960ab34df74efb2a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[16]", - "fe_be_hex": "5cfeb68e3f0b568eadbc559b3466c6d236161a3c8a040c0b4514b8145ce79238" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[17]", - "fe_be_hex": "18e676097fd27a394eecbfc4fc9e77b93bcec22fa31c8cb5df47148a24d61f5a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[18]", - "fe_be_hex": "188899add7daab3c574a1ea69dc2462b98f4784965414b67fe968533a5046841" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[19]", - "fe_be_hex": "678d9102966f8980e4ab679b0e8d89062a8a4b4287350de27b073897526af3d3" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[20]", - "fe_be_hex": "2c69fff1c50460ad0cb18d96216a250fdca786373030ea394cf3e08493085c3b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[21]", - "fe_be_hex": "69e0c4daf6c37e6c659ee977e0f911703782d74a535f30c757b45b83fc865da6" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[22]", - "fe_be_hex": "728344ed27b3ebaa17612c3ffb57a14efb120656258b47fa20c25ee225ef2171" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[23]", - "fe_be_hex": "4806c4286bf892869328dc209bc46314b2759765bdcbcb99744fa0f348545a75" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[24]", - "fe_be_hex": "0af784a7a802b57c08ffaea2b29653005cac85e3332f5cee2b754ab02b5e0bb3" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[25]", - "fe_be_hex": "15d0be507d1046854082c0ae8b1d4ff812a5b0f2899cccfb3d7877f355d5b83b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[26]", - "fe_be_hex": "27f5830c86931649356219918d1fe8ed97eb4600aa3e46b4b173778d7671d50c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[27]", - "fe_be_hex": "6e3f6903d3dc1e0e195f673938a9edfdd6dd456dcdcd560ad4d9dbfaf85f39cc" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[28]", - "fe_be_hex": "22ca4cfa850bf7487c8d7eb1cf2ec2c86a633768d9b9e2f16919b0830d3fb844" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[29]", - "fe_be_hex": "5a9e3590a4f9be93687d68104c9b6cb2cc9ed410fc07d6162dbc9f64decd7ce1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[30]", - "fe_be_hex": "4b7d002e918dddcd4be4f0623d8f9c0a9533b24af17e5dc634df24e500729dba" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[31]", - "fe_be_hex": "5d3073c3a96253693c8df7ce25e52b38b8c78afd7950a60e21cda35c3e48b78b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[32]", - "fe_be_hex": "2f1400afb1e213b69d45580bf9a0054f90e54c4dfaf340510358368d7eb037fc" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[33]", - "fe_be_hex": "54f6ff91933cf536ecfe439e01b2103b02aff564ad64cc03322803101fba3812" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[34]", - "fe_be_hex": "6bc15393109fc72220322b2e100a293d9b4d7f43828783f18f226701900cb91c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[35]", - "fe_be_hex": "1071a9bbef3816d70ebf4b92b9755e176e2a8558a3bbb3d16ebe2570ffb34f1b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[36]", - "fe_be_hex": "700c1d9c6e073b01b5c81986fb4df82a1ec87043972046372890ca3c7b5b0928" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[37]", - "fe_be_hex": "64a7cee5b982c3b7eecc70e2a7a96e53be8effb11d1040c9a73a9ebf334b1194" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[38]", - "fe_be_hex": "0175ebff5c51ed1bc0b0ac14b7193dafdf64b1c2cec0c23de2e781fa8394362e" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[39]", - "fe_be_hex": "6955a4c86380c065bd0c80036c3ab85fbdb52dcced432ecd68c48d9a1b274509" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[0]", - "fe_be_hex": "094662684cbadaf67132f94dfe2511cc8aee0f2519f2540b17314664295d2d49" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[1]", - "fe_be_hex": "6249297a890a17fbc31920e6f5b33e284b17b738d945b93af9ac63581f39e926" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[2]", - "fe_be_hex": "533aa4f8f4c2d59ee7d3ef836d846c08608c2c2a0f9322f6f6b269284401b569" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[3]", - "fe_be_hex": "0a7069537b5283f647e1a5fb4510e338d37915aadcab9cee9ac42474f027159e" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[4]", - "fe_be_hex": "036721bd95b1adffb069c6f37846d0949a2541fddded7dbef574c926ea067125" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[5]", - "fe_be_hex": "151dd83de8726f4907cad27fe7c01b6a38b780c9e36a92f2b14b334c6d43d323" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[6]", - "fe_be_hex": "0d8f561572fb44d01ce808d9b57f3dea1151b1c70af6170efd615597953aab90" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[7]", - "fe_be_hex": "388498775fc06d0cede26ab654db23b72fd6d2c693ee2fcebb0c3c8943544618" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[8]", - "fe_be_hex": "3f4cd7d8912aa56d301ccaabfff5b239f7d0b375dc7e59e70ac83d1df74cea7f" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[9]", - "fe_be_hex": "0d28bbe4bb3217c71d9f55cf4e629b6fcbb9cbaf3db566064f6c14d650c3518b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[10]", - "fe_be_hex": "6ee5a51f25ab89cecb00e84084a45fc4f644603406662d4ee5e8f8c93ab72c8e" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[11]", - "fe_be_hex": "6aabae683a739b717d4284d4ce090ca897bf16439d18a537381223febcacf0d1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[12]", - "fe_be_hex": "734189390c0049b73dd5622affb5e10138229f67c4fa0f70f3acc7ac5492da03" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[13]", - "fe_be_hex": "1be1180e1778d1802e467100378415b572888ae535042e01cb3539dd1059352f" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[14]", - "fe_be_hex": "5a8998c3bdc57c8c6bb4b209ce16f2e401655f094e065e08a1b42d14091d6c1b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[15]", - "fe_be_hex": "497165498e6280b15e3e3b2b3bb1be8fafb4880013a2860237039c2872fb5a1d" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[16]", - "fe_be_hex": "3969731b9c485b627553a8d5c91fee63f06759560d441480d36ab2144c382294" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[0]", - "fe_be_hex": "17e99dd0f4b34d2795f0c82eed3b8081bb499b675d2ef634a8b2b399adf374c3" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[1]", - "fe_be_hex": "434e96f8f1801a73f63d1faf599d28f3c7c9eaa3112979564c2a59a955dd3cd2" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[2]", - "fe_be_hex": "6b5e6841b8101eaa53ccb8eefb7fa79e95275028679301c335ff66f216f41b93" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[3]", - "fe_be_hex": "43752a03cf17d3d0d64777af9a39d283e7859eaac4a2da400baf3af7359a127c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[4]", - "fe_be_hex": "4ee605378c3ccaf4e48e82fa9f5c6ef2c9bd3a44f482ef00cb170f7901cc2b8b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[5]", - "fe_be_hex": "0fc1c5e02cfbf5d9115b7137074f6f0f6f294d7777c54d75f5b7c6cca84c03f1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[6]", - "fe_be_hex": "5492e7d49f48fdb68ab634eaf30059712aa662ac23bc8d7bb269afee222f3d93" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[7]", - "fe_be_hex": "09ede7d5f304404ce3602f10ac6c48423349268c98a5e7512c42a85e54a4a8b5" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[8]", - "fe_be_hex": "25bae27aa445934670e7660234eb483b0855de75f8e0e5c25a8859d5b5f1697a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[9]", - "fe_be_hex": "10a09e888fe4fa67cc79bcce1fb2965b6e166238799d9772d47a07b947236493" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[10]", - "fe_be_hex": "22b7aea68284a218fc8a1c85c16f2674c1f9b76494e29be3d5d1d25879b2bfe4" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[11]", - "fe_be_hex": "421d73a8c0aaf6b421264b6ac308d0ba2dd267d3f3134b006b8e440d08e2a0cd" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[12]", - "fe_be_hex": "2fcb6e506ea4f6d5a73ff93256c62ef6c08ce5cb4dfa0834f80fcebabbcd98c0" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[13]", - "fe_be_hex": "2a8865c0d658cdc019c2bad26c10b388ab8138c474013d2618a4a7a9fd78a151" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[14]", - "fe_be_hex": "256e955fd4aca98fc22078c6b2ddfa4535dde657f14193bc91a10e81c6ac8637" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[15]", - "fe_be_hex": "3b36739c2dab05e8731283b6a33ae8a8f899a4dce36067d38876c5603e6d4dfa" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[16]", - "fe_be_hex": "71de8b575f2b8d2f97d31a3877fcaec1603135e3648dc8fa91ed9a159dc72026" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[17]", - "fe_be_hex": "2693d914e4ec4b414043dd7cd62baad35ef74a798d63641cbb89b9adaa44b2e5" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "6f5d99221a6baa6fdd4a86dccdaecda185b900e389abb4aebe6a78dae9986dd6" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "09e25e6e94f2d464bc6dfbdb00dec371465c9e63b351f030faa16f2fc9089729" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "6d7e57bcc8a142e541bd48302d7d5f09ced322a30f7f46f5ebd3f068934ff3c5" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "09e6e9d001c1722bef8caaa1defa9016313d4ad09c6ab2a025c873fdf217a88a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "3c4fadc803f4d688529db1ab405f4b7c87d62b8312d06b82580e09f83509eaf8" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "35a66b9743844178d52f221e577f21da84afa99357bd796ab5ea58a44000abaa" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "0a1d4b022e51379ad4fb5e3dd2ef2d85782787f64cdc57b69e4eefdea908c5be" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "04411e7377b1c6c426bfc47ff792dc95b65276cb44d3e3742944dadd2356ecd7" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "3a8947962b0f02550c0eb3ca374c7dad62e6a93f6beac0b717f0ecd53ddc08db" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "530b29213aa759519d799bcfced039200fdc999b52b0a3a805fe5f17a87ec4f1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "610e49e355e2d7b519059f2a05cee82ba7af81ccc8521c00c05a299b43afad72" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "4c4ae9b7ff00533557189e0d7e3d92968a22b735511793c9b1cfd24ddb481426" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "360e366879475eaf65c181ee1ec4f7cb7505e72c2cbbf909ba9501657943c7e6" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "6793d5eb4cab328ec774e8790d4fa5f7e2eb2fcb326aaad5da8d7902bfbb0cbb" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "61bef43e11955c61a073955b26c7a5100c70f40cbc557d72efd31d37de08eb99" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "4f8352e3683141c181c8b3f2c36fb3c87d307f2ce83460476b2abc3e00e367ba" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "6a3393d0a58d7eff8d70efe2ca1651abea2810066637f38d47d3fe505e6a66b1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_m_eval", - "fe_be_hex": "5db5037d269fe3b3163aee34efa1700b945b64eea07a169e05c1beec055879b3" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_helper_eval", - "fe_be_hex": "739afd93c5d8099da5c1a1a38c3b931c0b9d78ad5ac1b0cc8a5a02fd0b9b8290" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_acc_eval", - "fe_be_hex": "3c654f49671abd7b7d5e4a2dc444a8b59ba8be9a5422bfc776344bcda00f2575" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_acc_next_eval", - "fe_be_hex": "5c73faba39c84ccb0f0a9be5f7afad39553d992f22d65e43370137f7f1748ea8" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_m_eval", - "fe_be_hex": "37b288ce777b7ad710ca063faeca1c7e830994011132bee36592dab105910e30" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_helper_eval", - "fe_be_hex": "0b10469cb107cf10a1323b9a3488bcb5070d70e57cec1012a0d832cea8f50d5a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_acc_eval", - "fe_be_hex": "1f5f07b0f2b3157e0b593ffe0cd4c03602ebba3bce94a97d1416c12c0c04cb13" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_acc_next_eval", - "fe_be_hex": "19aea3331455c4d6a7d7e0396441b078d62d74d9b13021572a4bd8f9ac98353d" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "trash_eval", - "fe_be_hex": "2c5b5194dadc6558b5486ac297257d2f731ab83bf4a6de7f1adb44611ad985be" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x1", - "fe_be_hex": "13f8c24186a7be1a397c7b2859ff227b2d0920d32d545885da542bbed0ae254f" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x2", - "fe_be_hex": "29175d62cd5f3b273588fab38705600a7614b67768c291d31130891f1199a011" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "f_com", - "eip2537_hex": "a6812bc568c0e0aa3aafc20385685e504c2f36778180e78ca44ab76a64b6be3b77c87f80da055bbd519aba9d5122e459" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x3", - "fe_be_hex": "58a0feaa98295060f65934374141b1b8720018a665345b485d2fc89de8704cde" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "15874d911e497a01d83006b38e4b7fd697d1cbc335f9119f085739db694a5a85" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "34d56361507e3fe5e2faf8f8289d4fb0b5a83072b4fe6b898b1fab754381561d" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "4928e1876a849e1c27ee7536482186bf9359d26c1d347cde0fcf83aa529f147a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "48ac5842c24471f2d12739e9db0f841e972ee94549e5a16531e6b0972273c8a2" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "4ec3466b72d2b0ea1ab18978b23d2da7d01b8b040443598bbe13c6b04d404983" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x4", - "fe_be_hex": "0406be097ef26b66a1727792c8300aca3e81715664c38d1284b4d40ea47edc67" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "pi", - "eip2537_hex": "a9f4920b7a8364d6c85cfc71346be70f46dd9ecd7fd527f2593c83cb55359ae395f091c8c26289f663cbcc7bc945b923" - } - } - ] -} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/solidity_trace.json b/proofs/solidity-verifier/fixtures/ivc/solidity_trace.json deleted file mode 100644 index 287b943bc..000000000 --- a/proofs/solidity-verifier/fixtures/ivc/solidity_trace.json +++ /dev/null @@ -1 +0,0 @@ -[{"kind":"ReadPoint","tag":"advice","eip2537_hex":"b5a3ed19b468966d517a4246fc2d3dffeff693cfae260ebca3c20570b03d90bb67818f1f29fc5eed276dd931d0a8f744"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"81725d4f826bd4e1b2d44973a7cafaa3ef68635e81b756986e1e7e9c5aa88dee5b132e5675c53cabc1a6650d4f7724b5"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"8405678eca7b3899fe8a5151c2f120af233db43316370079b635b0a1b586a11f88640a51e8fbc3b828a9c5761aa247e8"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"a94e856a5ce18ebd3ac321a8e927365411113a2c94c5e318351aea31e2927b998ed7aa69f7ab94eacfec259a90944f4a"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"9927ed8349c0e9a120abd70a94b2be16d801db78a48ebf1b8e832c12bad6b1439d2449a232fb7834c9ee802a9c17b99a"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"87f25afb4c569da357338c5a8d6f6f8f9a70d3ee4c4c76112ce557120a6c6675e7a050cf6e31a3bf9810ceb691fb5939"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"a901dbf17bda36068a654324f80bbb5cb662cb3b6b6c8de00c092be6d339f7089b92885504c313ce6942de14bb465b5c"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"895a3b7f738bb1df304e5dbe3d3f053598e3eb2e421980ad161aabc942edefac85393611ec42fd40d0374bbf924f405e"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"9662fc7620753324b71ae06218c1b5e8089bb31839463665723d8fbe7a2ad245e5133c774bdf716041751bd486235a32"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"8024ab67e2e9b10695f9d9de724f8150fbe2ae651c15270b46ea58edab90cd9458f2384ba9d3b5fab6645dcda696a95b"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"940bca31980207633c105e4723789eb5f4127c41f6325b855daf2cf3180eeab52b72f734549b72b57f0a27f787a5ca07"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"8cfa3c33b295c80d24437052ff753e93461752b8ab0170a48208d445658e53f6fb76bba84de7344233644bb68650a7d8"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"a7390d9b294df6d8c18c35c3ea28883c1c4cd48fbc12e8d82928d1e2dc340cd64defac67dca106a37967fab7fa8d3cb0"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"acc0d4d4feab2c840d1eecde2bc9f96bc6d2cda78ff298ec6716da118151cf598938b6c9346d8037a02b2e241dd1a3f6"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"8af8bc94ee0178b5f139926856976d0253940d5cb5391b39b9b4cf2c06f3ba7289bd798230be9a834976ae152441886b"},{"kind":"Challenge","tag":"theta","fe_be_hex":"27e8037bbfbfd47ffd3c2ce862e3acf6b81a0a6619471248726ed6576227807f"},{"kind":"ReadPoint","tag":"lookup_mult","eip2537_hex":"8309bd912ca8cf399279d7dbc8f453e873317ebcbc5e510acb13e62904711a628ff30b61dbbf04f66ef56f464056c3f4"},{"kind":"ReadPoint","tag":"lookup_mult","eip2537_hex":"873b6c597af9d183834ecbb4ba28a741e5a76ca027e14dccb5018f7cb527c07bceae9bfbab353f064720b0df70515ff5"},{"kind":"Challenge","tag":"beta","fe_be_hex":"12bc4f0d2f0d0c27bdaf7b29adc5b364ef4eb3550618960d1e6e7699f4b71760"},{"kind":"Challenge","tag":"gamma","fe_be_hex":"32539d02a7dc77ccee137a40521fe9a0adca56d16214e383a1458d03ea2ddd15"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"868f44698ed2b1174d655ade22a1bcabde75534c1af29d4c646df25add46d276e58066d8ffc6d7a7221c93669959d1fe"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"a734a580452233277b40065adb5f3145c701f870e3f8a07cf401ac04ca5787e44b551f11f521813b76db5ab41ce56b83"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"84da1d462aee0e1f8e2b08ceea88ac0b7f60842a6ecd97428bafa652e50b722f646dcc6d5a3b41ad5a5ead0178a9775e"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"88677162fef7cc540a46813c82a43da84b476a8726e6e376faaabe15d30e379aa0a902b5c962cfcad26f0b6b7046df38"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"94ade48a9857fdd229d84e556f6e483c512cdd54dd550503686b8317d6d99fee43f79390e91b5ebd303cf0c6bf32f06a"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"adda51a9ad06282aeaf8d1f2d5e7e7ffdbc3e89f0748db870157cf2ba3de8053d731c204001c8063a3b4e18e0981adb0"},{"kind":"ReadPoint","tag":"lookup","eip2537_hex":"906be3c46e0499a65febbc526c0af1d4c7f0d630af1f76ff05f3da874adfa98f83d8a9d048a94b06bccd71f1d8370e22"},{"kind":"ReadPoint","tag":"lookup","eip2537_hex":"a963d0a8790cec1acad417d575f03d18963b97d0b8699fde81074471ab5ff84b6b2435e4de358646f2f8cbedd20781b9"},{"kind":"ReadPoint","tag":"lookup","eip2537_hex":"8d562aa58c17eb6a5bead6f10b62ca93dde62817d7b1c8d37d60ebc7699f07bdd4c1df103a6308381b6ce1ef62758956"},{"kind":"ReadPoint","tag":"lookup","eip2537_hex":"82aee8ae26dfa6e8cd888e145c7a221fc4b4179bfc7aadccfd173129b1ae29c28f8f8cfd0b68e00142592aaae2697662"},{"kind":"Challenge","tag":"trash_challenge","fe_be_hex":"1f4aa81eea4602bcd9a4d8c413c83d27a205f49d074b3e58d25d8202feae2b60"},{"kind":"ReadPoint","tag":"trashcan","eip2537_hex":"ac938f8075047aec1fa27b4a958db254a15380ba93ec93b19f5cf8a8be1d4078e74b1b1a0a70d907f5ce640816ff1d15"},{"kind":"Challenge","tag":"y","fe_be_hex":"4b77a617a03318cfb24402ba260a8d4b6bb38e03827db22b36d6196840061025"},{"kind":"ReadPoint","tag":"quotient_limb","eip2537_hex":"ad70a4fd9070cbb6a2dd9a302486e2158fcb67de21eb59cee8603396e869f34c7dc3eb5bdfe3cb6a99629ee6524662d0"},{"kind":"ReadPoint","tag":"quotient_limb","eip2537_hex":"a71a43c4506df681ca8112d94d5f57cdeee6c1be7c875f0cfac40e8291bb52dbb147c0655b7c15aef823e5778f764ae7"},{"kind":"ReadPoint","tag":"quotient_limb","eip2537_hex":"b1fe3bf128c226686b753e4bae0a9f7633e3ff0d3905e0dd90e926578fd1d9bfc270e4faec8ea370827f85283fb5231f"},{"kind":"ReadPoint","tag":"quotient_limb","eip2537_hex":"af288e6aba3df8ac49d2320b0d2d8e1bbf9665922e962bf2047178ba018bd55b6c6494c59f6e55db80cef009dd2ecae3"},{"kind":"Challenge","tag":"x","fe_be_hex":"480c85d8d1c587dac3891e48a29ade9ecf1674679a0b808e8d2cb5bb3491f2ea"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"09ef5b8920d5d73deec7f3a353ac34262031dbd8f12384123a56b0444384a71b"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"5492a2956c450bac44d8590d6d8802e03f4951161e6b8b638a70f02fa6f8679e"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"201b58816342ae2f3b644148e9df8dba8e4df21d31e77e57d14c192830856d0a"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"3e8973b8bec854146133131923f26a622f86d10f3729e5a033972672d2ba18aa"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"08476b87f42a92ea6faee7430cf19849167bee80d0903e2b94ff68d20d067e16"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"4a5f88276bae0a6dc5737b3acc98ef978311a886979287c51213f09e50700dbc"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"4ddbb2b1a19d17740886685096269f5cfeedecbb4f842b69e18da0667676652c"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"64b29d580131ef7df38067302827572041f0284568335e3a06a9a73b736e2acc"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"2cff007180182c6e10eb772a9093d7b2f42a77e82888711d3e74bd370b605d03"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"0eb888285a964a03a607bcfdd1ba5ad71072b5e96901bace474d434cbeb1c35a"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"1d1f55b5e070b508ff54a51106ad265408ba62256d10f0aeaed5dc756e33bf51"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"0843b40b6701ff67871c911b0bb6768586ba9c034200dea8eff3dc70bc130b66"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"3f9f6a7efb75d7c4571bc4154df2fa8c95e1df48ac1a1e3b32f0f3920d14bb16"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"19242199f60bd966b08275c90466ac422e5945d0ec08b26fb6178369d6b32abb"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"5be8a576c4e42c8e6698aa6424296843af9218bc4d67105d6c77e792fb936cf7"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"54c32b308570688ccb4a3d9877f1667393f9c775a190d15bd789be77a7872637"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"65dc496a78bb27bea7e832fb2fcc519ca0a1a0e9f1a3da01fb7842400f84596e"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"2fd8a069b0528005bad464a072e383372b46dba2bb12557d1c0620f48520d260"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"2c925fce6a934219fbbab1c5a2423edaa05d161143fc7731319d38ebab1076c0"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"4232af1b388ff9803753312c54e93a055085b754c3f051507f855cef7ab8415a"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"49b4754ff126553764ae10edbd8caa67b7ff08b4acbb4cf9403588bc12e7b9c4"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"62a4bdee4055ebda8b75c8c458c6d8ceedabc9c82abb574ca64d0d0d54265b49"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"53d0c5cdfd35487168e993afcc0857ac972e732b06d3d21e33d60b8153c73d9a"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"2c3fba7975d6454c64a7806254b583692fb94c78221cd2d50256d03e47a71146"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"596f33fe28d30a2cd4c578e366568a4fd773643713d11c317a74d1c324e7fd23"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"049d1ebe2161d109d340ae7152df2f4831ad22fec7548578283d91d7607e6474"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"6627866c47eba59bd8337e7ef01a0067277a257293e77d9cdb1f317de54c7074"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"34f29783304a2e64bdced10de519a3550478203d00d69574dad8df6020cdcd30"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"48362875e1a6d36ba286b3a5a8a538d437762d7364b33c3c3b742b2aa58913e3"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"61935b799327cf51fc7b3e9d3662669d75026e5bd83f8bb50da9c277a5903dfc"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"44c545a9fb7660541a696d877c75b4dc275948679124d98196f050c72b3bb4db"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"27a1235745ef505fe3cc0e473f494379096496a9f239282f5380ea91e9d4c0f2"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"3197b3329a543c53400fa92f0ef912fe3f12265a7a9ebfd1fad1c96c5b4b7573"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"0c6b7da40f51cefedc5e562d996225571e96da7ee9afce17a551f343f6893a21"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"1d219cb7e0e9fe1c009f965566fbfa71aa4567bcd937963627640f6eaa6cc77c"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"1d871e9691901bfd6d4089a6cf77afc86504b1521e3ecca9172bce5984df3c07"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"5ac394b97b804f3b8557fc3b67d2ea12919e27a9f1c33aa3470db54e493f30c1"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"46d90b6372812f62be94add1c0914df911e1c520e0d7776556696a3be3f2a2ee"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"461997adfb8e95e75c697a3975ea365a6667e975e90fc5520d86a2e19a8d5e79"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"019b99c3d5c202bbabc76aa2a2a86c9e445988e698197b96cfb09dfd7311511b"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"14ad4103f2e8bc5ddca276f21b54e3ee4054a6e21b9d567e71a819478626cf80"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"5b6dfb1926f4165466b1c34793247660c1ecd8244a2eab3340aee10747bdf466"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"2414f225327ed2c7ddf7b77b5d10f05d6aa9634913a35c2e5138611de4a6cfc9"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"6ab6d2f1fc1ced14f72c7a8cdcf48abba1e048161d8a720c7a290853956f1e2d"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"6dbc0726ddbd4e1cbaf60f38999498eb2e8eb9d5010f2300594d3703be266658"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"37726ba651383f231e61b5cf282561273a93f0344d01d68d4dc209e724395d1c"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"3e68bc805df454fca334e1720f57a3363811f1c17808da70f205bca79f0d599c"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"3b40f386c432f27daf4e49a626e983cc4a8bb5501f0fef370a463cfed9d63aff"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"5e34b61c1b3db7f63cfd45d3e261d300e858536fd81612be8f50451447b519a2"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"2f16371f1d896cd93e2201b39fc4ba25d2f6d09d8b82ac12e46e930ed3ac21fa"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"0ea3f215bfe32c3166b8abbf36962f3ece721ff0bd303c5c59bf566521bf1791"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"004f497fb64bacf49f0e7273047e805a2c1af696595c259f8a73a49bdb41799c"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"3e06211806b4e7b4674d4a2dd409484190ecb8158387b9d9be9940e35fdf9b1e"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"1db7203289fa5f6db1e2b150057157b1da3fc8b87a54b62f42f3521edaeade54"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"01bbc3918a1edda73c72ef9c0ccc91b584fd1915e469bb9355897c37515e0075"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"1134aa786c389ecc8afb97cc1f80c6d17fe4b0a282371404e99daf7e22748a0a"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"44154629726a79952b69531e24d1c476a100c387864071ce9873ca0bd4fb8259"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"6c9e183607a90e4d710b847ed3082cb90e11430e161e180fb4cf6dff5e60f6fe"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"712d02268b3d16ffd37948ddbf54e8e0476873c1a561ed9a50983d8c3af196a6"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"5839aa24628fdcc4ee441615e0f60859084e903ca95b43bd89aee00ff1accc76"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"37f5ac771c0edd9ef214a34b33a469bbac297bc17e16ba72be1c046fe41fbaae"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"655ecb16378f8d990cf6c442d21bc690ab2ce050f6af93c8d94357f3979e72e0"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"3241d62aa2ee77caa09d00c1048f1b383f5f632cc741a71a382d6912a5f0350e"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"373f161bec38765b8579fae5f9057301be67013bb38928b5776909ea08337edb"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"0dc1d24a4f7bd8331c394efe98e5f452ff3955e737ddae522f9e2552ca79280b"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"516c5fae7051b044cc4798c9f75bbcf06c4b5910f882f1ffe15e82f329165c6c"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"3de4833d15a75e94c5fc30c03ec19c7e238058d2fa242e7caf17d6b7b57ee2a1"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"201cd483ad4dc7d466cb4e9e2ea6785b921cc491bfb23d121a8a4bd1c314af48"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"18f0620cfe7ecbea2b26b6419cf1afa321bef2146fd0e26398d201b2b68cefe7"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"0a2b19de67150f439452406bb4d962da831d80241e625cd714e9bd0a413ffc1a"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"19a2b386a5a2414caf2d9a688b4bb38364810775cc9fde2ed1804d34a1e4905e"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"4729b5d831af837bbb9f838f11dce195a82d1694320c7e9e98808e77b5fe556c"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"6307e640d82663d46481590a917b746fb0627a5f80dec829c691d0a442dead1f"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"64ff573b09c5d6897d782bb730572d1f05f73ae3c9ab8b7dcef6a8312df95c23"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"6b8bd275e850818bc69ff00f3b8be5d689eb8cf7be45af436d2317ad048d8a73"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"69f0d9446331009d2881069dc939974374d361c058e4419736d747d753d8e4d5"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"5f14c13215ffc96f43762265ec6be1523aa467caaa8dbbed4fb5250f6f5fd26b"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"08c5f4d6099af4b9b425951caeec5b2034cc116c781005a1855c1b0809acca87"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"09da2149c10bf24f5398bb9ffce65639c62f5e65eb46c9088527dfb3058f0281"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"324f6ef7243ba96253f8669000cdacc095449ab947719ea8de02b57ef087da6b"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"503c69e9a786ddd9cc47b1a7b33c24d9125ee2d3a8a73812ddfeb2e2820c5b81"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"3b45aab1197f9e693e77ffbbf656671c103e7cc7fabd7676cfba7c0dbe1e6910"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"68038ff1509fd4631d2d8ca7a62939990052c92827ae6692656bd3c2e18ae3a5"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"52f75632c182416b0ec8eae43b99ba8f4cb07f305d4a23e7328433ea7cdcccaf"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"5c3379b067d47d07f0baee88a7f96a71809523bc2122e94ce803a77bd790b7d1"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"142f5d5ffedf93ab9733f1d1c1322d7a2a787636328b35c11e0e9336c9776233"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"1dd4d02fab4470903c87e221cbef954679ffd8190fa9df7d19ccfa9730c1d32d"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"0311fe738e056e8db829dbb795c41570a66b8de508afba7753313a8e0f64b1f2"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"5296e8474e56d657c049421b19587912eedfe9a88ac5f3167eb63ddd4bcec4fb"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"04263ab0bcb0df4e05cab8ad7e3033bf7a14d5b0bc2bce18deacff2e36cd1741"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"24cdd547d581885a9499b9dcfeb3daf3f6b2573249433b68ae4b06e5c6e17145"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"066b9b55e4b6942a9bd6a7d37d970a88f38772d00e3955947d7288267dacbdc3"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"68ee03441d1c0f093ed8fabea2015e3444c1d9ae728a6fe6da42fb84a98a8e73"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"18425279cb0d2ea5a3fe6a9008003baf88d8609655e501bb056d6fac2b3a8154"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"08f9d736bb32f4fc2fd22f40ce8d737e27c02aef5ecadd2117e182ccfec4907d"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"52cf1481bbb3ab0dcb051cff44bc54d8719152970ff508290418e0c36d59ac9c"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"160dae4607f1df78f3391e992a9fe6f757227067b7ec1d0d02ec14c1d9bee8db"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"44cb92fc492e5dc73f69d9cd84c668734a36de040e2b9fd5f158fca36f9c3f10"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"1ca147219543e05d489c2a27ea73f94cff979d018a579df61bfe1fc962913b33"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"0a163b1652cc1a06d13c3d35fa936ddf34bbc1ca841c6c39204a133da267c0ac"},{"kind":"ReadScalar","tag":"trash_eval","fe_be_hex":"2236f4d3167396b4d87b1d7defd0e879ae415244035f3d7eaff75943bb4ad383"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4d84f602435f12f8f5b1345a446e1d5af680c023f2e1bec4cd2971d49c4ca92a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"38e4c99b63fb2f92a207528b53ec6384413939f13d1d5eeb6e40d401095cccf9"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6a115d18c53d7506b6e98cccbce5760ac0103a70bde718afc76d989e92a49d61"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"11e74c9d92f5333b73311b9d81ddea08e8048e9693c54c3f7f522886cd4fe73f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4b6767e53ed2bfe61aec89506ebb5d11422fa20f171424c75f1a578e5f14499b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"39df664536219ae8c6d13cd65b0542a4f37585fc551cc32da9e8bf19b619a325"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0493e84246159d9a0c5ee0b0d451fbafce46f9bb9a5c2845327628d26f1e7bbb"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1cd2ab993c64e766697fe1b0129938a0944298703452af46be29d5522aa88970"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5619c9c33806fb0e1f45ca1279b1c6303b9aa577ffceb8fd1ce85debe63e8b23"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4cc558c2bd77616304b3fb7b7d77a5e9b15abe455dde10e63182f80335a95e5f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"689634165a2730277593e57798b7c066dc5186379d0b15d4f4d7856982daffcc"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"355b92c70ec6af51ebf804e565c7d1a1228564101c77f73abe0c3c88aeaba470"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"461f03079bbefd53cecb5f46fb80476a0195d44d635ee14f9b29097c0a631efc"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2ad47773f05f81e86ad287bea69ff972ac8ff4af5fb9b08d585309ad53f6daa7"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"57261f6222454259b1b70bf4b5ddb3e090b3900b7efa6a8b536d45a4518d9724"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"057ac9a6d8cdf2c93f6014f0f10b9a2002bfd4efd1a98dda624e3b5f17e94834"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"26602d5b7d46eae0be328ee647587ffc128d4bdf58d5347e22aeb4f828c4d248"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"381b2bcb3e25616c0707c9d717197f9fd63a65037515d07211b05f3d6c9f58ae"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6ca1d68e1a6b7792a57bfb3b936070ec9e2b62dfd80edf6bb011736de7a474bf"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"42ef248d0a8c8391546451c282110ab5048f334383c82ec31b444d4ceb3096ce"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"55e21e3da2832df2f41d6a6f43a020f3f4bd2ab7bece259d999bc908f0abec2c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1c53991259ba6373b5823de5dfd510952bac4a31af688a42635a506aa3d1799f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0f79a34578494b0e29f69a1df1939463ec999607412f22275623c241bd3c3fee"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"04aa7cd595d0166bddec5fe573ed2c525ad1227e5476c7def868f5b264954750"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"18676fbe593dcd108833fc5da5423a9f916fa9a30bd080fbdd93315cd4d47dbc"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"131071e42b01b53cc5a69662719d931d7d7b9990e703a53ae84096d048f9ae3c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"01675aadf0c0d8681f8d27bc981bd2d2111820e15c7fdf04009c6e0af1f455c5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5e27b5e1f5a3260b0700251b06342a148fff1c9ac5ad4432f0d95c3c658c56fa"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2c97d440a8c86cc5ac8ae887b62f78ba1adeec390673cee6fdd679465c0794d1"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1824e3e45ab7c6b5353d2d250fdad905e8465daa05c2a47572b791f92cd14ff5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0f80a9f7bc25fe7c2b512bfd60b4823141eef42adf188c14242f8bf78f6c9fbd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6940b7e93ea186db8c9089f0730eabea3b14a6f2bcc4d5f640e79a3df0e9629f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"17fd958f28b995d154f198bc0da303f14ca5f808705a77dfd144ed6a94d9249e"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5f0ae1389418a52788093ba4a333c9fe08f8ad8625d3d0f126ff9593deee4a98"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"48ffcedfc3681408df77ecf6e1b6b1010e76eedecb534506435060091ecd2625"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"44046630593df43fc89df0f40ef2825e44d3060718c5236f3766852e595e1d65"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"22201493963df06b078882d59ef1baf520c7f8a38e683270f208fe3f11b5da1c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"01bad9a7a03e41cd6bd172f3fc4e62fa729a77cf2e722733d242526d2f5a7ecd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"12cc9895e0990ea45556a45312c508ae9fe8f8573484156ef20db108976a284b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"216b01ea2caf761404161e279de3dc4f62da3a8dd4f469706ce0a1bb3b61448b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1b67dd2a644fc0d81bf61ca12d534a66a0220548880d3daa2dd459dae236b0de"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"565997706ebffb9d7754038a6c62645854f0df8090e0708df29c899e99022b56"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2b6581101163f525d224824c36435408e6fc905e3f317353314613def0a07518"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"68e20f87934b626a2d5f7c209d869401839a4a118b5bb8b178ce3090d20e8487"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"21d1f9e69bd479903c92a4303b730a30323e07b9bd09962714fc3e7287beb409"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"45ba1a715ae585a0893569cf924a6b7489ae3f09e258430d2b339b8ccde77007"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"280ad47f4e418e50f544e959e498e91b26f1c0d6f52338b60acda2c062ebdf30"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6ff36b9a1df788dcb81f4f9a55e7b03fa49fc3b7e6d7c25316cdbd2014f48e81"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"04dbf5bc21710d87e2f53f794c449b940c3dbf98d0c3715039be44948efee6e3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4cce456370593b4b2c12338ba432d7d2dc8d2b954fc27ad7839953b388c7fa40"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"245136107ff6c9127cd806b4c4d551c870e803b2d6bcac2a4b8488326f5dde50"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"037537ee588437badb0e3f22d0d7bfc2cc529bd03dd760e06bfaa4814caa14ed"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"06bdcd39f7b0136caabbea18e942888444142bde27356b103aeed6b9b10613a7"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4938b50ea746606c644b6de8e69de0a1e1864b137526b661ea92e5f23e862012"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2d42164ca6c6c3fba64ca662762968296b76eaefdbcc2f72ce6c659730f06303"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2cf5ed389a2b092dde54c11fc598b5296f3f9ea6083366b8e08af58d2424797a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5e2e4955bb1026354983477b6391a75eef3ec35abc89ca0ad9b2836bbe052efd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"23b0a43901f778461e3e19850f0a644639f5b098404462b28f9eb11dcf5c943d"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"42c426784977ee16629b7b136ffd514988b085edb2b2b5732ff6ddef06212b65"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"510c8c528c8aaefc6e7ee6636a68fe5c0d6676244c76e739f6db4350657ee7db"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1f5fa161bd17eb5cd7f51486008198a2288cfc068de4c1d52f7ce9165dbd1a32"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"723089e8c78223ff6bcf157f5d7795048d0e5f342272e157c99fca5221b91c9f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3819ad665aa0f4079328407614b2763c879aa0688462dd1d2a054a692fd625db"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"65714decc38fc058c4c9647ba3bda9deaa1a66c396f0a2ff9882db4e4bb6eed5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"11a51b2754bd7e318250260b9e937f8097aa6f5c456ac5ba7b108dec1f4e94b6"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2b94f00cf585aac715f80005811db45996cb13a743397362fcf9c66575e11c79"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0a685a3e3bca3ff7b2c6a384a0bf9f35d9a2dcdc359de738765268cb3168cb67"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"44ea929ce6e2d770840cd5e995b12643282cae1749c019df2a74fa328152b2d1"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"45c2845b5844106df6f399279ac4df7145f86caaabafdc21285923a6a74179b3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4d41d079b8ccb0700381f9cd3445420ce2f7216919d2476d167e16ce020ba1d3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4050fc9352e2344291a6150266d4ee85b3f348bf9e048d5b959cb418244e9d1c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"241730773066d495dc9d78b9ae20bb88625f7db54fe8922d10cf63d3f1faeafe"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"10c4dee4dd83ea316988a170a4b5f98b098131f919bba00b0b7bd458fcd8da12"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"34fe8bc397563d8317fbe40dde895fdbffb7c69d9c11cc24330d41b9b4db242b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1186fce670c53ea565b4cc9bedd44983fa02f3ed77004f78630248955dd8fcbf"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2c9d92613f842529943da94911cd4d53d89144aad4f196d65f9282d774ebe3ef"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"43c252a5adfbab1141b203505fe28a11d67c3578e268e74ec5cea27ff466fa8a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0aec0d131103b9c6e998dbc5e145158df9b668512ea007b84c8d2a69589e57c6"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"16fdd35187250871cabce08b82f94ad2a6b5e64038cebc13c3a193b2aed03525"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"52eb863553792fd28628cbe7c673c265f0200c836e8bbb3a3ad10e9dd2788f4d"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"044b950152a054968cadfa9f38f136dafcde35c8b00c1ef07231244c04ba0a44"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"461715185e0141c19d67c47351d7ad78683e1ef1b5b1d2fb337f3e0ec3f3b249"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3a6860e88b5723601efcdd3c47c13c7cd78f0f1b718369b068c8c8dff92ef071"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6f180c1c3141f5d42fa4cdb774114b9a7032ef944c6a0199443f966f63c2a1e2"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"63c9fc3b50a25e5a8ae95997e46f3c582543251ad166556ac863ac987e6bc10f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"30d882588ee8f6959b437bccc3d6ed9d2deeca82d24a1f26e2d7f22f6914082c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"04922982ba64fdfb4fd3d8a4fa3c312ae96ed18a2070d900179d47cf710cba27"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5d7d715c6f859bff0b379c04001acc2755621cf4c9b7d83d0aea9ca516340585"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"193e3bd8a15fd20590d839dcded732f17f97d47434bffc3831de95caedd7bf8f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"152104cd9b83bc618b92993d360b4d04e7478fff837cd4af8da3e64aa9c19f3c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"447f5f3b822ad2157c6cd978078607526759f0f312beb0cf8753ae893f7bc736"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"35a49b4552bc2897670723e74f0e687ee1cab24d67a67711f030e305d8852c51"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1c23439d4cf76c447fb8afd031d562c07f75d0e5e5c91950c0e4528ed7b3110c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5aa38b685836bb01905355f1faaa3eb0bf44091f957fedb8866431b50ca21ddb"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2fb774f3e1d4ca762d402be85203ceff34ddeab9e654270864c40042cf693ae6"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4220c142b4b52f30d04f8c217fcc3f59eebbea5780acbcb6189586b586e9f4ae"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1f72922c6c67a07ac1a4ab00aeba2a2d206a979104f8332a97a42a1ba43f1914"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"378647b10cb904a603e7d8f71c01f8ae0bb3b3625d9fa395c4bb52e839cd6efd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"67487522f6a74fa7cef58cbb07683df5491680ef85085fa407036639950ae5d1"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"19937dfeefd2158ca0bc9e683b1d325bb5c90053db94f517c6ceb9af4eb40c86"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"58745b577b25297b5690da2a6f756362e2886372b5728c9179f4009b92597fb5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"245ab486accc627b29a57d4bb9cdc50562eb3e8f2e013c55dde35f1a4c4afc85"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"01d265e5bc50c71b71644720d66cae2fcadfcfb9cd57bad131cfd436f16b3ad4"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3cae55f63a45e8ff3ecc07611f0de9d5091af977e5a9e37b00912cc8f65450d7"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4562d249ab3ab25956c3de8943594e55aecd3e5c3415883a06ee8194a04ecd77"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3178d11e65909267934d86293bb734ac15e64585f69abfc3e8a96ac96efd5443"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6626afb1b7f2e317e5ee2112695b983b77158e0047d819aaf887ff0c6fd64422"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"353fd5b33bdaa47a4edf336f9ae45d83385458c9e6d038b44090bc38a29b05de"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"48996b81338a8b87f989f69eeacecba348d0bcd96e5b9dfc78ec615ff3f572e4"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0896da0029fc2c69b7558bb1b17603cf24b45717eb7bb1af86712ba784679e18"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"130fb75572e05392617c3f551305841d859e7e066fc9dfdad0ef4df6ebb8cb5a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"49c9e00faaa7ed77efe0a387f3c5071503e0e530e83fe1356b42aa564f62e9f4"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"60cef6247d3feb13cdd27653bf4fafa8401af878a97e0a8e5dff84f26b147588"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"07e3202eb6b6e74418ee84c8323af991b2667a27b9a3da4b3913b67a3acc3ef3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3935077739ab4d47a0618aef4f8e5d30b10b37f9339175d9eb1b636be268b2fd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5862cf4d8acb32ed43215038ef1c7e21aef91bb8a36a55f8109900bfb155375b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"73988db21c1f117f899f4c54bdf532c9b9cb10265f18300aec5a91a6fb107812"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1947314d727a59bfd9e04162b2e12cd90e4cd82dfd8de26e9dc97b7e78ece0be"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0ac408ecdb6ad3b5cf54c15edbfaac8e6e201db89f94a4746d3b13175cf8960d"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"48f98be7724694687e4fc7e16870109ba6aacfc31aecca40ef927bdae0c339db"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"16bbe36a02d0e3e8e9f01fb110230fbdacdb0707e98ee0a54a8eacd0d907051a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"37ab84231048f467c2b936aa5d14e2104c0d35e1ddc0abcb9e39891eaa640372"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2d354479ac28e801fc69ddbd171916a382bb4d9fe19b7d7e5e47b3993d775c29"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5289eb6c95e57d1ad45e356ff215b710b00977e6b878bac2f3666c337eff957c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3c2940471aea3e0ce4cb460fa12b31443bb9d2bcf6761b7e0d13334357c973b5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"04ec541aaf70d69c8653191b4536b5d179005c975df40ffcefa000785269d1b0"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4c0c5a5528bebc895414052346322a486f3c13ea28ab92d8f3e6895440212bee"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"457d318a5dfcd57055dfe70341eb983de6d319e2865f580cd83838c1a35d0273"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"72f512e290a77f23ffb69963813bfc2889a0708c02478b163cf1ee0589c2c059"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"289bd7dd96c36c5a2cd05941aad06beda7473df7a2e09ecc0292ca3f4191ea7a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2124419ef77feab36d68c55819258cd114a1ee496fdb8e247bb9002e403cb392"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5682cd00f9040a55c6f616db6ee35678fe44561fad92131a134ef9f6445c9e9b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4c279fbc9241e8ffc73217187428cbf39cb29dbc86a25a8bab3153146bc562bf"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"606022c0776c73bdba99263ba976580e3fa453dbc6ed3ce321495518cfac5c97"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1b3c85c78620750db6865c0ab7c02962e13bbafd26a8f62e680a06bf099e96a3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4e88df3cf58488335c62ab4b60010b0ad3e7ec5c6fa8e1fbc819440076f39fb2"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2128334d48bb8c736754aec52e94822a683250edbca9c64bc71763b41cb5f0db"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2efa47645723e1b1d2aba11a901c5ae5c6a3954f2da8143eda496296461638cb"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0dedea303cf72dd88c5dd088f6b64a11ff0ad7e03808cc329077cbce08ccdcb0"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"70178e70b89186bff3ef1f778729fcf11e9909d1a341fc391cca61ae7503bea9"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5506ba47f40b9142d10acfb4678db90e687060b17d1a11c3f07fa37eb759b2f9"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"433bfa74fa0fe35dc664aa104f8618ab10eb6bb09d9e200e87a5aa78560d72fb"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5c141c77fd15a20905f40a4f46a47c782cd4b9b0246cddc79e1b5133721ec369"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"50425855791ad3327346bb7dd650c040a41e909d0e9e826d00850dcab138439b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0ab7145a5f012039efbd8abc425d1adc640625fe6ad438845382d418af87d0cc"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"52e36072d5fc448de5583f545c97443b9722b6fd380a0abf76bd27d629252b35"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4513c98e9becd4d01b9d9734382c0116b59583474a487c2087da3c3498c1ffd9"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"37d2f2356a2f0eee375547232c36c6756249547d8c867acc93ec1963aa3c6d31"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3c3fabeb32f89129ad9027840b32aa28686e0627135772c7b6b9510848947a00"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"48aa721a6af9f92c658a39617590780619dbefec5e31794a1358907354d5ff2c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5cc0ab5e75c80c54d6001fb4e90f302e3269c87afe4166f62fde2965e7fdf7c1"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6abc22d42dfa2da89411aa00d98aac6a68d325dc34455a6c3cc98ceab56c5836"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"55f48be93ffd19a77df336ecf30080e84669468f92216bf063ed907addb560f5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1123b770b00b6e62a0f3c01c42d570127854425c70a66e004b6b7ebdeea69817"},{"kind":"Challenge","tag":"x1","fe_be_hex":"1c098ae6b043e3086251d5a69213433f867bce3584401f06632376bf4cc0d51f"},{"kind":"Challenge","tag":"x2","fe_be_hex":"450e371d88aee670e1855a0118a6ef70867a135a675e6283dc8a18cc1384add8"},{"kind":"ReadPoint","tag":"f_com","eip2537_hex":"909974aed62735634516ad3d04fb1c75814b1f04420e7ba5153e3f8ea730a425fd8cc7f4b9c06bc994f9b9fb18153ccd"},{"kind":"Challenge","tag":"x3","fe_be_hex":"2f336b860308ad449b601a9a51554f543475b39cd5647fee3fe190cf524b9693"},{"kind":"ReadScalar","tag":"q_eval","fe_be_hex":"3c84ab264411455250488b3d02d4498a82654a620a29d7f86a53d62c3b7cdd10"},{"kind":"Challenge","tag":"x4","fe_be_hex":"52e5456496a5abf0c1067996c62310f623e7d6b4972e9ae2746606965965c719"},{"kind":"ReadPoint","tag":"pi","eip2537_hex":"8b260bc8f81180fc5b6bce2401c2d81895ef4e29d8ac2300fad888e667cdbd286708865ad384a4aa27bd090951cf7660"}] \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/vk.bin b/proofs/solidity-verifier/fixtures/ivc/vk.bin deleted file mode 100644 index 670ff28e0213779ab0a81c3866bb6041da154306..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66731 zcmds=1zc23`~G)nM8W_B5eWko6BAKUQ3+dYu~6(zOav6W1-k>g6-C7k6uYn$yRa4W zcb#F+#@TzB^?iLF-~WF{QOu~my<{R6u-z1y*E#QBZs(>G4Fd+O(KqM<|m$sJ#ApR!0MJj~d? zx$v(wo_X;!V4uija=aSjnGH{KJU#Htfv51j@D#q!iKhvkMtF+p3~@|8@^taLetAa2 zm3iCme0AxH_i_(;>1`u|UMIc?3Ht14?J(B2YV*ZM&n*hr7?y}9Au z$I0gpo!L@qN?gh3=(-aVNTV&!SEf+wN&F>x##yXD;r3<8p>~UKwTgqJN8;m+WS2o$#$h zDQWq+2F1qibDU|F-r>UWGsEKR$}VjxbnaS@xM9uj`uDDN?n=<;?a5!y9(-^7?3~e{ z-GSw$>5bisWe*?LYE8@P<2LMi-DU3bJvq8P-fQ)vLa`MdUAE6nkGWj>;q%~|Zd*Q2 zJUOjmyI^Vh+{J6v?$GS8>89Ij3dhS!?yCG`b3nJ-r`ENLt#bQTS)XsK8!tQ1e`<$9 zvww8VbN24sCerkFhYqevobzP4LB0H+YE-+wCs)kZ?^miuKCfNRzYX3pCy*Sa(7;4x`>8=D%k_7_ZkxZBJQ+BDTZW{p#k2a#3xet(^-O3R&1%JrB3 zIMd~jP10Feo4e~)J-kpwn%E4yCb@g*OD$iA znwvRPtLGqdpS@-IlX-bl+XRf3rq5P)*UfeBs$Bi%*r}44=aSt$isY(3;d8>)XqV^S zE`1hxF7_{zVY$ty6?Qd&$`nh{l&!jdrI|AOYeVS&)Sdg zOXeQ>F8{&(&WkL1xAZQsXm_8vt?asa6xK+LH}WsztTz1kxaYI{S9+JvMQ)U>7_~Jp zx==y0W>GT-`ab^f?0Tq8qtUzlliv76e(90VOPW4+*k|W29|~{n6WTnc)03WkI`=F% zx6|~*H?MC+#CFR)#P<5AHzU6Ns9xsyr5;K7%u)|X)0^hMeJJ-+tAkCQ0w?!!t$kzM zK(~aY$)ye_$xUinzO7e&kC|7m#~nR_inKI8w5QmMfztGr7YYvO?>4>Yk|~ouS%g=r zx2X8ClF1*2T(Es(e}7?_XRSXZTGrpR;eAW1VqMRznK?H_n%?ZH)v_1W(+szA>Ki{KmHNi}REtKaS-O>dR<&AeLYX#-B) z^w>M2>X47$KW?eh`rhqYA>LDKb%?vzy-sVl$j^nE95H`3_fmzSUA6iTi(#kaVfSp$ zehp|duX5O{Ya`D3%j!fFk80#ne0EZr!?@M;Q?vQyA3N;n%9zb_L$|$>mTzFwW(?Y8i}9WEcYmX*EMsAE{plnS#)ti3gC%G%(ss~xsge5sXw`ioQj z7MV@nnPK^EP|5*UZ?nFML8+5wK8Q&;~03T`kZ2gPf z`sR~RBA;(tqw*P^OQPbh*Qi`!#JZx9C11#jB{)AC|6|9cW8*G2_j>fabFH$bTIt7z zPS|k7uVK-<#d|F7GWAaR6%Ix_ef&Mn&%Aei_{!9aZ+q9+^KEl_;8DVG8R$5=nkg#iw`t(d1aJ#_Fk|R%i{3Lt3%VOcl{b_@$+%MYg$gYG7{#&m) zj9K6ATZgL^7gh_ErnkCeHY;Q7f%Np+0c9qeR!D7r+OtDd%v!oIH=v$*_{FsD?H zfT>>b{wBfRQv#}dm!>z{vGb!v+$D$B`~2el3dk?sZZJ5Jx!7gQ6 zqWhV7hDU#{7_;VAsSIg)BhU41x-VN(Evn@3ihE4DTiri==4SO#+58IsoVaOEvyv&R zT>Dt}jpA@q)OtQRO_xP0GEuW4+X! z2}7SBtYoyHxWjCxP4Rw71}CNI?W;~Zv|;hWt7$%4f>)W@F8}OSXxXJ3+aD|}*)_wd zt8GGP-|>5Eg93i^(HN?ElqEcdSIIA&ym?qy(^vTT#avGcN{!cmUD}mH|ocF zpUou)?}~1c^G8X$kq`Hl-TrBo-6bJr)Oh4+A6s^1Nbm5Mt;~my8PoCbr>)1D`SpL@ z#`wM6hmQ+8*YJ5Z>)P~{@nzeYMn~Gt*ttR@G2Y~E^Q>RE0;XaE+t+{3Yqu0 z?9b`0-a&mgHA}J$UX}mBw4yGf4|~3Iimo%XVuNdAr0K0p+@IucQ2Bb!0x53!uWlap zq3P+2Gj;NKpYF9Yrhi&N->^a}I{0iDR&P{??v6v{0jbjTrn?fOtSjA%DACyU?TaR3 zD<7$P+|e-h^v#Y=U2R;NADa`KdM9%1k0C+!DQylLml@Vonm+scdONQENVdBtOHAIe zYVeU9-+yeFIB$KA*mj+RR-S57|8mW)&jVddJ(mvXKl?*?jf>Lsx!+}ku75Ya?YT#F zJ1N0%(mEba_M-GLjUw$7SRdM~iDX%u1?|P-jd1-pfj;(So zjb3B?bi{y7TS|Sj?i2p{Y0&7had#)=4{kaoplNZ+n*kj(E_vgBNtLSl|>SG(f+65BaYu&Z74Xyg9Mg5)z4Ge-S?$7>Vf!6h7 z-D=x`)OzEhF16b6KHGs=soC2Vn-gqszfRZ6o95n)zSyD0>K)Om&gAj2iR|_E`{?bzee1uu*a*{F5_% z;MBvEo;Fvd=`Cv<+*fwsn6ZszSMM#WHoHY_|6iAzRc!XEWmxmGgLgiUz9RSbJ{Mx_ zo-bwi-Sr6#i%Zj+Rx=5E6ZCNC*@bO)1y}vvJl;6&+vuK-ZjYvow9a?_h-vk7pXHw; z`*}DgJGI<5BW0m9z2)G8OX``9KRdLwU!xbdr+ppYbX#(8r^xJ2y|1V2Kc2IN)s*O- zWl#Hj>bCGsU*kd>Jf!LKEWBS~<%d(j`yPHAcDG>t@X<~)<~@9CefsCd^2s&7L^$>N z=(}v5XP0AJQ{TjfOsKm|nm$LhnqDP-mb92~)z)F%ouxUO?0U1h%sshlaA<0-Rik1) z>|K%Mzwzp}GkMGo4_-AkW1=*@X}G+8m5V19N67Bp+}Yx1`;kFg!{;?k2wodntU%!G z;@90Ko^h&FtazH!#q=vbp57GZ@){3gpIXZ&y&H0-cc`~)qs@$64ZF9FonNHgfalL^ zzMfU|l3kmm@I}w2cy+s)+H;8Kml$DAukpy`Iof>4<8x=0ma~35b!_+fHCmmnEdQSP zyyL`Fw}f6V*4Dc{dGeMQj{=ge^i6&%?7U+N9Mf zvn5~u-e1y_8dkoX*2UJhpY5K>PrF~W^!etwQ=@Rak-w}`yXhUrSJ&IvuDN`{wO{vE zl^>gZb@}AoYI?@>dXWQe8!zyinXqPV!t)j*y4%EclBSo zgRkmc+dn?8*V0i7>kR7Sv|xIAK;1S+3>}Z3+S%Ot(a{iTdc)3RB7SB&vfg!o{7l)4 z8S9L;&aeE$Ht=#4`wBnox(@a3+pW`-Zw2l|t=?Lte+QXO8EJZx_TNJ)H+g$j?p}Iz z_D}5>^lcMfIC;_f=K1gCy|JNb)WH|m1`K#FB5F3`{1@Yci`Fl9`QVf=b3szWH3xsb+1vN$q)IJD^^U65+wWsBX?mNeyMfD_ zU!S)3)bbI73Kfay*{yTTl?BU}M_;`-C@y|&zi9^;MN8Evm;h@yS96Py)=D} z3K{MC$U~2>cJKFg@2I5CNBTCJdh^B6Hck^m{VvFFwH)HQ`=Vv`mvibjTo4^RVM4ex zy`^K1!iEJrminc{w8#-sx_#8PFR|;aT|2~@k1Za#&Hq!+higx!fAJsPXwZkCv2Sut zk*2rqF}zm6AMOsKhT@k1D zBO)$eywEyOn%>y7LP~Ur*Q1YrFMX`k+I+2DT?^E!?cKTS$41*KHV$%(xbe8YW34xF zt*Te**YSAj;+tCO*RS02aZ`?)^*!dbp3^X}rb}wMje{D`Dfqo|tJ0HfiXL=c8(O?V zhudSP`a1krQYcqGY5JTS`XBn`aVBoi;*+Jkzos{JJiM&-^j3DBm&UlvcMWb(zg%wT zQ^jB1t34**QKO@er=63gw_Os}f5*Pb1?S3cZ)_0#$lB!A(w~F;hvfY>GkojjB1G>9zg4T=VVdTx(v} zeLG&~HjL{Sw5+4y5VQO_T33{&xAU51dUffjIkO*o&Umx+TJ=5$JOYQUve;g5f09>N zt9PzeZ}MF>|2lZ^l4i#b8m)-Xn!lM{KACe#*^Uz$mK>SdVTfCvH@)SPFZU^wFV&!j z_c6@uVyACe*JxIq;VB2#G&o%6p4R%INtNhzMShi?ACl|dpmoiw^%}qHeL~vA$KG8_ zv>jAw^AG0}si$^#etNsI`|RYKotA#JlGfkKIn|}ms%2m9^=LBxU1g*6EnzpRTrqMk zIwZT@%-S(a4n6%D{$xbJ`U71m-%onfGP~CN%i>6J@r>Xm$vyAtlq40~$w zB-ipy2g_W0USrI`3a$x7hx)at-EGykFLz$g*IK{IbI#p)rmTAVfjwr=?R&P~#1i%e zD>i(VcUr=6_q1+fr*};b`8Dy@bRT!yhWBl<)w%miT7Q!xD@@;2t!j62c8}i6<~#`8 zF}wKUmF2IEIz4#zwwa~RPAc(59`|+HrR_Vn4@(&<+u=SDVrmpUu#ZQ_}?K5rhF8vMLJS(@G`@&57#XBy0%-1}YE z^M~xLHe}1*pviTMYYnQDsNLm2Vc&5D<0}>{SHJKHvzFsa8@#9~O>c95o9lyBGxGI` zjat#C{^%!{YmB)wZrQjqHa)${f1LL6p7Y>dM{|#?nC4L4rd5F7>6X&;)@JsxM(dv* zinKUzAxB}~DW@Cu- zug~EV21CamXq1}XE#HuRyTUi$2s=^DKI3)83ac6x_&m8^`7(pngbcdgV}1DNI+5$7 z>2rOp7ntq(*0XtfUoo+t;96x~C3llvw>R7P?yL53No1R$cL%<^7To1QTkED@mRc_@ zC9Gd%!&A5pgx_u0;3=$$=El<&Pdhw?F(wb5LaZzFSL&?5i)n;((x;G*_!ROJpF+Ok zQ^;R@3gw7Tp*+R&W|&-NC6}4YWpV?V%*|CUGcb_Jg;zrZnStK9pzv2p=jk$A_r5@FRR^rXVvb>*6ZQJ0|y$F?-y8g!C$q)8@hT z)f2A_^lh?t@sJLA?o>D3>3CLAh@jNXRaWJFiMmHmm$+yZ;yPjBM=$$9^Rg$utZTj_ ztzZ9K-w$33Rg^CH0lJ#;(YwRKI^8%>FeW@HcXs`CRGt4Zirj*^Z^!}?uifW-& zpqT*X9W={)>$#d%8hfFB&7y(VdR5zGl02|<&>ZV0Td#eTr<9K0K2Wn-7M>^j*64J6 z|CoIptrj<~7c}76w$g*cYn1%_)y{BEvw*jn)v~@DR&Q0qmS0=V8h^aHVMstPi=7Tp zHT`!yi2pu0bhKlBs#-XWa9-9nUzGzXt6gi>d=OaYLz@u8(s3K*yIKv%mJxDn)byh> zd^M}p<66mu-tKFsv!S6?&*dr_1YRyE=Y6Zj{Tj$+w>G0$) z5o?Wu(xaSz?O%9hYVFj-1_@I$7Tvq4Rju5tG-Ee3S^6RTB2aiR&O|sPS`QIXe8EUl zl2j040#1A@>%YLVqi@{ch>oS>Po$m=O&)oXui3UxTPUHzP7m;U? zA<+}+7jE48NR8b;(nju`6~_FfN+Wm$fc&pkg-bw!dn&53V$Rb-u&6 zzzk%1mX3ZWhkm7lr0PkO4w9;;-1}4W>H~wWC3<%4f45kKp$jf;z3VAlH2T#OB&CR` zCrC;WQBRPRBF{ZLYhN@R@cc{LX-#g9_u1)>?niG$=5$LY+7d`gJD~_9rJYa&lG08n z0!eAlKDPPOKGV-%U;L`~&a(5Z$Kz~*6j~iFe4P*RddB6cR~KU7=8F7;9SZbAm7j@T z`I)l(gouHDsPePWE5Gb+uEO9egasOp0?zAy{oLB**RMT4f8nuiy&HOb$wR9z5OT7U z4S2PtNs&b({k*(4l*m3)U<=7T(bFPTIkpv4;z<_5q9VLbjz9R4~&tjDk`;OjHE_rLOV#R zUsKN$$AWf{S-GU99VF!vp&caUl4mEAnCThz9`1GW4DQ@-lF><>W4U@g73Uq}E)_`- z+Cl1cNtLmn9b{H6sc8pExkP9ONx4L52T8e9xIo^WUQ^Ca91<|wBWm`5ByXKD2caFK zzD}1kwS&ycB{l6JDVGTCASss!?I0ljoXYM-%YuA5Rn&3`@)RI7*9p+aX49d*;xnNtU) z%2m*rOE;yaGxwj5PgT{>K2qqk)KNE0omM(1RkngoYu%KZP8%I@v;Bx_Bdeqx+YsRD zJILr))fH8VxY;IM62}N}v%PlQym!ZNn`TSK1rEM>@ol??4qA<|nh=?*KmOggS+4R; z?MNeTrV$0Liko{J@7=W0fcLRu8#F6qUu;>q79*>t4eRPM+n`*-SwE6D_hcmNQ$pW6 zSM%%EBsTk^Zk_7Xl-;elzKH$2zMpC=pIYha$*2<;pKu2Mg}9j(#^O=2=cN&)b}??| zYZv2YzIHKgmcMi|cE}U*b6l?CbrudjKeV^A;YbVPgzJ-wBv!DgWuK;4Xu%ObMfjeD>uGdq!BlBv?I!7F7dU4WG?ZwgJdr8wS#0X@wJ0w zF7dU4WG?wcZ5jG({j0)LjEbI_R{5f{Ta4YCpl98`7=*;Pe{dmjttxKjYe$sHT;gj7 z$z0-V2gzLGYX`|(;%f)VT;gj7nc0?xb}eM>KhpWls2dyKj}AG|xAle6k<;HbANHi# z7W2ama;V~FzIH^J%q6~dkjy2%c96^^zIKqzCBAl$nQe)y9VBzf(7>vDxkq!>U-YPW z;Q6gu=S~~fy545#de4@Z>$+6i5WVJKh@0u_7?*lxY(#&p?BayDS&wE8N{`e0kH*ck z5aHaq3To)Dl{$sEnXglboB2Bb*|?b&Vjxq*&GgqwokHBq*D1u!e4Wa;S!`KJ=#L(Kyb5%@ z^Pru_e%o<{2YkGndEw~@G(HwiE^gt~VRe+=frgy{+Zm^QF4aE4+QJ|GUB4Uh6DB%p zMQ9$1=Y(-=nxpwW-ie6f3r4bOjuxr-KumynlTfx^K@Ui(bgeA{EKhoYPApbgPi!5e zN+;@ASp#eJwZ~6h)qBs^;CpiCsa!&6oI6)6N*4m+6hG(TN+7jVBNJNogk(fuyt( zeF;cG`yXDu5V|Iv53zqh$DmgzVt}qrXJ(2ojQ;aokNKOJw?1i?5N&QY(xpkbWvLh) z%OPuyt+4Y+eO7bW>h*?+KdWn1$yZuOVOAz=d;A-5rnD~V?-kSQzp|I$uf)F)uh5Lx zn;N&7JL;1|7E@E>wz`iv7FJ=9CpB)1q}nLA=N)qtYSu+)2dUE~Rknh5kXgB;rX3{Z5}_R=Umo(cvW=J|+($o$zE0@%?gQQ#{w1cEvBD8~~Tq3lCq+BAl0)o`(lBRad zy0UUfO*=@+B|JviO>#`a)}u0KxXBVns%(6P%aVLK~gRe+Cfq-5!yjgE)m*6QZ5nNK~gRe<2*>g zB{-mJIg%r>9~ZQ1t(Fi&9By5e3Z(arspT>;Rk`=QdQ_j;D@%#=f!Vr!ljDe2C# z_6(_oZrn^arQV$X^K~|Tc0U;9@IUTab5M+B>m2jHBit z#082RC36rl7qyIW$nq5DAT**#z(_U+;cvPS*V*{nD*l~yHd+`abSjri?I4*; z{4L}lB`(pzP)U_bG@{fFlDWj!4wAXV*A9}o#Mcgzxy08FlDWj+J`YlKiK88BO{^{P zwS#0X@wJ0wF7dU4WG?ZwgJdr8wS#0X@oyaf$z0-V$66C}iLV_bbBV7VBy)+c9VBy! zuN@?FiLV`GW?SOkHUW~k#Mh3sCgu`fJ4ogdUpq+V5??z=<`Q2!$jr9H)ee%m#J^<( zq~MYemi?I4*; zeC;5aOMLAhnM?dW52WZ4M?2Pi?I4*;eC;5aOMLAhnM-`_Ael@2{vITA ziLV`NP0S^}c96^^zIKqzCBAl$%q6~dkjy2%c95BEi96PTWG?ZwW37p~#Mcgzxy08F zlDWj!4wAXV*A6nXEpfGjWG?Z?d60rja6r+mXupV8qM+SX_t(m9Npqb|Os$zv(9H6p zzgFh;hi@f>PTdc&D!;LBOf54pwc1BG{9CucYSu#gow%P(>t=`D=O=7i^nPvUS0OvU z@0$^;-^~uf>X<$5cF^O72f45+2k?(JH=+INeu!u0H_oe2Nf(_tsxl<+#}mI+q&JY^ z`Vuk}{%d6VA*L{pIqG$V~mI&uTQi_Orf}|7?^#n;NBDOMv)Kd|r#8gWr z6oI6)6N*4m+6hGPeIilBy@sq(SOgPh6x_TOynXNhu=g36fGo)DtA7h`97X>Zu5q9@UZwMIb5dgd&iX zc0v(IN;{zlB&D71Oa$%dOa7z(96CqmXI)oET_3mCt*J|s5lxF%6M7}$KZjP`Cb8!C zZj<<%0ji?QBVYdJzJos=IFkw$A1Z|Ym5m2NV{*}1IdPm326$I(O8i>!4GZYCDb*(m zq3%>(pfq<_6wpDbvJ-Y#6x2*(4l*m3)U<=7Tq1VGfuvp1x}XuSQ4>m46_wgSQj-8eJ4l@_sWKL{ zgUre$HSHiNmk8}3DVK;Hfgov@RFzbmcf^%CUD7=7Aa%N=%2v=0GAozVw1cEvBD8~~ zTq3lCq+B9)LxR-llBRYdoo&{!3n9)?>y)ajBcW5qz9l}l>c zktgL6(fC2qE>QzL?UJgJ3hfvcb-JXf9i&c|RM{#9`m9`1(~dkTmk8}3DVK-~A0+LP zs*(!r80>Vqq^TXGPM1{KDhB$jTvF4HJSmq5?I05`^) zkXgB;rX3{Z5}_R=5`^)40c($q^2DtgtwiW_m>u-jN}?=2kCqSzvzIH{r$rEbLjY*NM7 z4w6l(`1(O+HT6`s*#B;a2Q7@{DNa3UM5!GSJ#&e#9VBy!uN@?FiNDPcB<+%<)@J4ogd zUpq+V5`SweNahk>J7Q|)5??z=<`Q2!Nahk>J4ogdUpq+V5??#W%(lec)(euk#Mh4K zo4Lf-4wAXV*A9}o#Mcgzxy08FGP5mlwS#0X@wYUC6kHM_Hw<_G_^u}Us%B-2lPVff zc6z8Kn^f@|KS(yI;x_=0Sxr4P2YPDiNkp$079~?pS{NJX#i=KaD77O`<`Q2!Nahk> zJ4ogdzwv{lU6RzA4fGP1B#2TGhDFgO8c`>iOMLCflexs#4wAXV*A9}o#J})CN?f9a zv4LK6iAI##F)T8d_}W1-m-yO2GMD(;K{A*4+Cegx_?;d|(It*{40g;VzIKqzCBAl$ z%q6~dkjy2%c96^^zIKqzC4PqwlDWj!j=_$(#Mcgzxy08FlDWj!4wAXV*A9}o#Mcfo zvn_E4JCMvJzIF_D%q6~dkjy2%c96^^zIKqzCBAl$nQe)y9VBx}uYq20i7gyveIrZE z0sU`e(dRA}Lq$KP^98qi>VAmb!*AS>;)eY2qt6a)aB|IcCPhueVij*$js_$tNq`K6 zvNIQJG-YY!36d(E*!c|7T)#X8GYOK&6QmhI61!?aQl%4l;+|9^{qh7!l}_x`1*vE0 zxYyHAztTZc^(0CMN!63sCk#^0dg5+Nsx1-DgQOG@^#n;NBI*f}Qbg=$2C1ha+!;x= zWI_>0N;{zlB&D5D1d`HDC;~}oCw6Fq6tuhQ?nT54-&OM}*M5h~=a1gOhv|}bA!2P5 zub5Wo)diV~n?v>bZ!0iSVz=~a6|dE`fn>MzYLVLYRNPRkMQY`V2?$j>(G7tVZ;jW? zRx3}0>*9YPpiUBbf@HS|t7AAOnm9bv@#V+SiCC*q!#8Bc0@ z4sU*G+{b#&O^1As(CU4CCCynT{^^hZ$yildS@rjd4Mu#%4l*m3)U<=7Tq3lCq+B947J;N)QdLr+9rGNzErHb34pOH}s%!=AAhU8w zO*=@+B|5`^)tQhHZ zNmDz>tXxvl4w7<-&<>JviO>#`a*5Cml5&X%0YK_>NmDykp|WyGO*=@+B|{{b+%H&;N)y4oQ1VI;x>{6CNrO(O-`s-F0jG67I}MU zMUp!Ie8~U$TAgH^Q(S>UK+iPNUn?miQ2ad|!XJv^?`bf1b9KWsMwrQ{`T@z2SRKYu z^B5v%L5`AnjF^jN<}6Qf9z!Ea-N=*8WBA%ZvUv<&Kgg_Rdujn2)~l%59-$qi#3fo7 zFqKO*qSOwOxy08FlDWj!4wAXV-_HV)c1coe$kB31f+!V%6kVbbb&|CuzINovT;gj7 z$z0-V2gzLG@8tn0afud&N~&C<5v6vJ%q6~dkjy2%c96^^zIKqzCBAl$%q9LlCXk{_ z9PL=IVpkkrJ4ogdUpq+V5??z=<`Q2!Nahk>J4ogde-9T(<`Q2!)~lFHeC;5aOMLAh znM-`_Ael>i?I4*;eC;4J+Y)#G8c606Upv;Tm`i-^Ael>i?I4*;eC;5aOMLAhGuskZ zJ4ogdfA1bh!6jEWMX#ds<*%;Q(Vc-1>ADIProUE55u#juVj^q3Vj>&ij@v)FM@G$Y zS_kIaddd95e_Bq^Da>Tl>Zf@q)|PS9JVxupFj6v)5p&VZn~~x?hDH<#7|G@_{O(X( ztK)YG|IS(+EsU+Ji7wHIQoFcT$JZ{d)$z59YjynY@V{BBqlK|`HL)$xh*CS&z?e&X z?I2lO;%f)V+7e$oNH!i?I4*; zeC;5aOZ;IFr05byJJze1OMLAhnM-`_Ael>i?I4*;eC;5aOMLAhnM?c-03>sXuN~`E z%q6~dkjy2%c96^^zIKqzCBAl$%q6~dkeO|X8~%V~F7dTvy^6WS*A9}o#Mcgzxy08F zlDWj!4l=VXakYbFF7ZP@kb+C%TAkLN8(V+abgnX~$>|%3Ju|}Hi|K#ohJzk|e+2X4 zKmB(&6rCgSB6cW3^zN$tz4*1VcQt@3Xc*e0;uA4}&?NN94}Xb=z%;XmA%8TiWV83k}93pgbPy7(lJP|zgWpH9VAsxqI8f{ zJ&7&AAoZ*#x@oE{5nGi(Qi_Orf}|7?^#n;NBDN}n)Kd|i@@D{0Kit=E5Ju#wh9{k`JX?tkn5Vpp$<=CRmC{>C7vnStgzh0edSgZxhq&YH?J z-zjwdm0e+4SAk~LWU@aUG)VJTf3Gm&uk0K9OTi4}D^w$pqB>#b4?`4Au>gtcgw>tI zv0~>K)d`EF+KvL0P7OdJsf^P8K+vVk69)?I2)d}DijkVyLF#l#m9b*ySXM5nX-A%v zOT=!9|2jBSRZ*!O!67xE6WT%QbV-%5V&_;^E~#lpo|H?3b{#=1v6BR3mTd`jRh)MO zoKy%+Xa}j&B~9%hvvNsIJ4nhULOV#xB|k# zm{AV@@QC=#PD5p zT?t3R|NFuu&|fQH;s1Qm=U1S=^MAK$)J4ogdUpq+V5??z=<`VzcpCFk_eC-IfnM-`_Ael>i z?I4*;eC;5aOMLAhnM-`_AT!$%_gB9lnM-`_2+NsEeC;5aOMLAhnM-`_Ael>i?I1JT z5?4D&=8|5&_7+^qoM>qM(tG#$3ELLEU)%Xr$jQ@n=picO{X z2zzyLgtulgnYm2(ow9{YmR%;Zl*z1QGHYQaP*xZzfys>IvK+ru#}>jdKFcn=3K$Wg-2C_OY`=$F{2reD_i)SK!>xV~xBflc=67&8_T&f+Sh(VZr_lCs z6&Dsh6Ru{(E4wc6-azr5U1507t}48mDLyw>yoy(6=2eX?LW<8V6|Yu`S8K(q4SPjp zgjaU;%VoKQV{|0)d2$(oP-7QYSxbwR28JafU*r$7dJ`9WtZQhkRmSyGdu^Ip+`YWl z(3pOxu$!#$=LrutABo66Fo#Wv2OmZpn-e&&e!t{XQwKedE!uSHnm(B`^>XI7OQx+m zXl5GKbo5Je>#e7QikZs``ZV=$Dd3ea|NVGq&HQmwytoP5JG`nz)PDJ>`NI8O`}-|y z{;qG8@8dh2+jy{7i_ve5mYzTK4m0=6AEXkFkO--&d(L$));(e6urjUj-jCe~?Nz%1qVd$E)&}r|<1yyy}8=>XM(q&Gx?R-aV#?!=kqP zkNC_Bw}R`LKS-5%qkvGB$(gMuu8;P~xqSb_#}iMyeY&OWn7y{wzD%1xdO?)U?OtWE z13vQysWP|23#qCZAH6#)tkaDH1!KaKf~W3~jrMA9JHyPPYD(EnOYgrrg#AI8KS(7U zu^PJK1PLbxv9AeEPV4-KJ1(D`IO%i90>|Dr*3_EuvOvmwmpM6IeO`L|SUZNWlT*)X zO~#QQOMA^Jx8=v?>t2&Ck8qQ1i~ibgMZsUyHz`!K@g{)poyvBqCp zO?=aK<);_r4d!>s=bn(~g$;djN$Z0+eRA4)+wR?Q);DhJ zt0T)B8gvVqS*cB0)A(g2A2qMFDAwz3ooSe4#svJmJ?OL?H4`&WPC(qTb>8ik4p06PvDP>!J<9pl{)Jbj z)=o`qkT5l4(Y>4G$r0L$?2ue&az?0*TZyPAMosR%cGzL??CX@Do&hmi%AP;q^PseEw%-Mxky^e3w$zwuCM#ld68+CgB@?MK#@6Drf+6`&6 zhDmo{-8E=-@&4W29$rbf{wr$Jx*^p%wwFhzKRWMq?Zok?g)^l)jM=fhO7}(U=L|2G zZ*D#>_s1bCX3yH(`e6Q1(|+1#OsP*x=Nm>z=Nm>y*TXQPBW8w$%7sxN8w~4ZywaiN z$4YCyFFqJoVp-bFUEjOhZgk^)^#-F8x6C+EIFK1u_(_hLg^m{OWIUnYJRV)8lUipBDPEG9^5+q_G!m z7~f{fFuu){VSJkj!}PFO7_ac7K5{XeF$SR;rm(s1^>=H$R@dunz2VFPr=o+q&+$vF z_I8=`6Q3QG>ZDf;gE#0`e7{y)@}i(y;w=9$8x~&+*z}-CXM@cx3*dS1*5l;p2oN%Vzb-^|zkdDtgr_hNc;Jao{KC1v}ZF)0;hrNXpSnNAlAgaxGPW$0qn^Tm9Zl_%6m a%1iSU_Fk)}6(r}v(@_BN5cLSJ^8W+(ed+W7 diff --git a/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs b/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs new file mode 100644 index 000000000..0fba70215 --- /dev/null +++ b/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: CC0-1.0 +//! CI-runnable adversarial replay of the public-accumulator decode path. +//! +//! The accumulator decoder in `templates/partials/verifier/AccumulatorHelpers.yul` +//! had no executing test coverage. The tests that looked like they covered it +//! -- `accumulator_decoder_rejects_noncanonical_infinity` and friends in +//! `src/lowering/tests.rs` -- are `verifier_template.contains("...")` string +//! greps over the raw template. They assert the guard *text* exists and never +//! render, compile, or run it. That is why the always-false `and` guard in +//! `load_acc_coord_shifted` survived: those greps passed for as long as the +//! identity branch was dead code. +//! +//! The only executing accumulator tests live in `tests/ivc_keccak_solidity.rs`, +//! which proves a k=20 decider from scratch and needs ~300 MB of SRS, so it is +//! gated behind `HALO2_SOLIDITY_RUN_IVC_BENCH=1` and never runs in CI. +//! +//! This test closes that gap by replaying *pre-rendered* artifacts. Because it +//! ships the generated Solidity and the matching calldata rather than a +//! verifying key, it needs neither SRS nor a proving run nor +//! `midnight-aggregation` -- only solc and revm. A verifier cannot be rendered +//! from a VK without the full SRS (`SolidityGenerator` consumes +//! `params.g_lagrange()`), which is what rules out a vk.bin-based replay. +//! +//! Regenerate the fixtures with: +//! +//! ```text +//! HALO2_SOLIDITY_RUN_IVC_BENCH=1 \ +//! SRS_DIR=/path/to/midfall/zk_stdlib/examples/assets \ +//! cargo test --release \ +//! --features evm,truncated-challenges,in-circuit-fewer-point-sets \ +//! --test ivc_keccak_solidity -- --nocapture +//! ``` +//! +//! then copy `target/ivc-keccak-solidity-dump/{Halo2Verifier.sol, +//! Halo2VerifyingKey.sol,Halo2QuotientEvaluator.sol,calldata.bin}` into +//! `fixtures/ivc/` and update the commit stamp in `fixtures/ivc/README.md`. +//! +//! The fixture describes its own accumulator placement: the offset, limb count +//! and `has_accumulator` flag are parsed back out of the rendered verifying-key +//! payload, and the infinity encoding out of the verifier's own constants, so +//! this file carries no second copy that could drift. +//! +//! Staleness caveat: these are a snapshot of the codegen that produced them. +//! The fixture is self-consistent, so it keeps passing after a codegen change +//! -- it just stops testing current output. `fixtures/ivc/README.md` records +//! the source commit so drift is auditable; detecting it automatically would +//! require re-rendering, which needs the SRS again. + +#![cfg(feature = "evm")] + +use std::path::PathBuf; + +use halo2_solidity_verifier::{compile_solidity_with_runs, CallOutcome, Evm}; + +/// The IVC verifier is large, so it is rendered and benched at `runs = 1`. +const SOLC_OPTIMIZE_RUNS: u32 = 1; +/// Generous cap so an unexpected loop reports OutOfGas rather than masquerading +/// as a revert. +const GAS_CAP: u64 = 5_000_000_000; +/// ABI prologue: selector, proof head, instances head, then the proof length +/// word. The proof payload starts immediately after. +const PROOF_PAYLOAD_START: usize = 4 + 0x40 + 0x20; + +fn fixture_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/ivc") +} + +fn read_fixture(name: &str) -> Vec { + let path = fixture_dir().join(name); + std::fs::read(&path) + .unwrap_or_else(|err| panic!("missing IVC fixture {}: {err}", path.display())) +} + +fn read_fixture_string(name: &str) -> String { + String::from_utf8(read_fixture(name)).expect("fixture should be UTF-8") +} + +/// Read a labelled VK payload word out of the rendered verifying-key source. +/// +/// The generator emits each header word as +/// `mstore(add(payload, 0x...), 0x...) // name`, so the fixture describes its +/// own accumulator placement and this test does not have to carry a second +/// copy that could drift out of sync with the artifact. +fn vk_payload_word(vk_solidity: &str, name: &str) -> u64 { + let suffix = format!("// {name}"); + let line = vk_solidity + .lines() + .map(str::trim) + .find(|line| line.starts_with("mstore(") && line.ends_with(&suffix)) + .unwrap_or_else(|| panic!("verifying-key source has no `{name}` payload word")); + let value = line + .rsplit_once("0x") + .expect("payload word should be hex") + .1 + .split_whitespace() + .next() + .expect("payload word should have a value") + .trim_end_matches(')'); + u64::from_str_radix(value.trim_start_matches('0'), 16).unwrap_or(0) +} + +fn read_u256_word(calldata: &[u8], offset: usize) -> u64 { + let word = &calldata[offset..offset + 0x20]; + assert!( + word[..24].iter().all(|b| *b == 0), + "word at {offset:#x} does not fit in u64" + ); + u64::from_be_bytes(word[24..].try_into().unwrap()) +} + +fn assert_reverts(outcome: CallOutcome, case: &str) { + match outcome { + CallOutcome::Revert { .. } => {} + CallOutcome::Success { output, .. } => panic!( + "{case}: verifier accepted a proof it must reject (output = 0x{})", + hex::encode(output) + ), + CallOutcome::Halt { reason, .. } => { + panic!("{case}: expected a revert but the call halted ({reason})") + } + } +} + +/// Replay the shipped IVC accumulator fixture, then mutate the accumulator +/// public inputs and assert every mutation is rejected. +/// +/// The accept baseline is what gives the rejections meaning: without it a +/// "rejects" assertion could pass because the verifier rejects everything. +#[test] +fn ivc_accumulator_decoder_rejects_malformed_public_accumulator() { + let verifier_solidity = read_fixture_string("Halo2Verifier.sol"); + let vk_solidity = read_fixture_string("Halo2VerifyingKey.sol"); + let quotient_solidity = read_fixture_string("Halo2QuotientEvaluator.sol"); + let calldata = read_fixture("calldata.bin"); + + assert_eq!( + vk_payload_word(&vk_solidity, "has_accumulator"), + 1, + "fixture must be a public-accumulator render, otherwise this test \ + exercises none of AccumulatorHelpers.yul" + ); + let final_acc_offset = vk_payload_word(&vk_solidity, "acc_offset") as usize; + let num_acc_limbs = vk_payload_word(&vk_solidity, "num_acc_limbs"); + + // Derive the accumulator's calldata position from the ABI layout; the + // fixture describes its own instance-space offset via the VK payload. + let proof_len = read_u256_word(&calldata, PROOF_PAYLOAD_START - 0x20) as usize; + let instances_len_word = PROOF_PAYLOAD_START + proof_len; + let first_acc_word = instances_len_word + 0x20 + final_acc_offset * 0x20; + // 7 limbs of 56 bits pack 4 to a field element, so each coordinate takes 2 + // words and each point 4, with one scalar word following. + assert_eq!( + num_acc_limbs, 7, + "limb count changed; the word arithmetic below no longer holds" + ); + let lhs_scalar_word = first_acc_word + 4 * 0x20; + let rhs_first_word = lhs_scalar_word + 0x20; + let rhs_scalar_word = rhs_first_word + 4 * 0x20; + assert!( + rhs_scalar_word + 0x20 <= calldata.len(), + "accumulator words run past the fixture calldata; the fixture is inconsistent" + ); + + // Guard the offset arithmetic above. Without this, a miscomputed + // `first_acc_word` would still make every mutation below revert -- for the + // wrong reason -- and the test would pass while exercising nothing. + // + // Four 56-bit limbs occupy the low 224 bits of a packed word, so the top + // four bytes of every accumulator coordinate word must be zero. Random + // proof or instance bytes would not satisfy this. + for (index, word_start) in [first_acc_word, rhs_first_word] + .into_iter() + .flat_map(|point| (0..4).map(move |w| point + w * 0x20)) + .enumerate() + { + assert_eq!( + &calldata[word_start..word_start + 4], + &[0u8; 4], + "accumulator coordinate word {index} at {word_start:#x} has non-zero high bytes; \ + the computed accumulator offset does not point at packed limbs" + ); + } + + let mut evm = Evm::default(); + let vk_address = evm.create(compile_solidity_with_runs(&vk_solidity, SOLC_OPTIMIZE_RUNS)); + let quotient_address = evm.create(compile_solidity_with_runs( + "ient_solidity, + SOLC_OPTIMIZE_RUNS, + )); + let verifier_address = evm.create_with_two_address_args( + compile_solidity_with_runs(&verifier_solidity, SOLC_OPTIMIZE_RUNS), + vk_address, + quotient_address, + ); + + match evm.try_call_with_gas(verifier_address, calldata.clone(), GAS_CAP) { + CallOutcome::Success { output, .. } => { + let expected: Vec = [vec![0u8; 31], vec![1]].concat(); + assert_eq!( + output, expected, + "fixture proof should verify; the fixture and calldata may be out of sync" + ); + } + CallOutcome::Revert { gas_used, output } => panic!( + "fixture proof was rejected (gas_used = {gas_used}, output = 0x{}); \ + regenerate fixtures/ivc -- see this file's header", + hex::encode(output) + ), + CallOutcome::Halt { gas_used, reason } => { + panic!("fixture proof halted (gas_used = {gas_used}, reason = {reason})") + } + } + + // Limbs are 56 bits packed 4 to a word, so the first word carries 224 + // significant bits. Byte 3 is the lowest unused high byte in the + // big-endian word: setting it keeps the value below the Fr modulus, so + // only `check_acc_coord_packing` can catch it. + let mut bad_packing = calldata.clone(); + bad_packing[first_acc_word + 3] ^= 0x01; + assert_reverts( + evm.try_call_with_gas(verifier_address, bad_packing, GAS_CAP), + "non-canonical accumulator limb packing", + ); + + // Perturb a coordinate and zero its scalar, so the term cannot be + // dismissed as a no-op multiply and has to fail the point decode. + for (case, point_word, scalar_word) in [ + ( + "malformed LHS accumulator point", + first_acc_word, + lhs_scalar_word, + ), + ( + "malformed RHS accumulator point", + rhs_first_word, + rhs_scalar_word, + ), + ] { + let mut malformed = calldata.clone(); + malformed[point_word + 31] ^= 0x01; + malformed[scalar_word..scalar_word + 0x20].fill(0); + assert_reverts( + evm.try_call_with_gas(verifier_address, malformed, GAS_CAP), + case, + ); + } + + // Substituting the canonical point at infinity for a real accumulator term + // must not verify. This is the encoding `is_acc_encoded_identity` accepts, + // so it exercises the identity path rather than the range check. + for (case, point_word) in [ + ( + "LHS accumulator replaced with encoded infinity", + first_acc_word, + ), + ( + "RHS accumulator replaced with encoded infinity", + rhs_first_word, + ), + ] { + let mut identity = calldata.clone(); + write_encoded_identity(&mut identity, point_word, &verifier_solidity); + assert_reverts( + evm.try_call_with_gas(verifier_address, identity, GAS_CAP), + case, + ); + } +} + +/// Read a rendered `uint256 internal constant NAME = 0x...;` out of the +/// fixture's verifier source. +/// +/// Parsing the constants back out of the artifact under test keeps this file +/// from carrying a second copy of them: if codegen ever changes the encoding, +/// the vector below follows automatically instead of silently testing a stale +/// literal. +fn solidity_constant(source: &str, name: &str) -> [u8; 0x20] { + let needle = format!("constant {name} "); + let line = source + .lines() + .map(str::trim) + .find(|line| line.starts_with("uint256") && line.contains(&needle)) + .unwrap_or_else(|| panic!("verifier source has no constant `{name}`")); + let hex_value = line + .split_once("0x") + .expect("constant should be hex") + .1 + .trim_end_matches(';') + .trim(); + let bytes = hex::decode(format!("{hex_value:0>64}")) + .unwrap_or_else(|err| panic!("constant `{name}` is not hex: {err}")); + bytes.try_into().expect("constant should be one EVM word") +} + +/// Overwrite the four accumulator coordinate words at `point_word` with the +/// canonical encoded point at infinity. +/// +/// This is the exact quadruple `is_acc_encoded_identity` accepts: `p - 1` in +/// every packed word, with the identity flag (one radix base) folded into the +/// first word of `x`. +fn write_encoded_identity(calldata: &mut [u8], point_word: usize, verifier_solidity: &str) { + for (index, name) in [ + "BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG", + "BLS_P_MINUS_ONE_PACKED_1", + "BLS_P_MINUS_ONE_PACKED_0", + "BLS_P_MINUS_ONE_PACKED_1", + ] + .into_iter() + .enumerate() + { + let word = solidity_constant(verifier_solidity, name); + let at = point_word + index * 0x20; + calldata[at..at + 0x20].copy_from_slice(&word); + } +} From 6f786863b1642cb465348c41592d682bb0f23786 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 04:43:55 +0100 Subject: [PATCH 65/72] Cover point_pair accumulators and port calldata attacks to the replays Two gaps remained after the IVC replay landed. The `point_pair` accumulator encoding had no executing coverage of any kind. `accumulator_verifier_variants_compile_with_pinned_solc` renders it against the poseidon VK and checks it compiles, but the template's `expected_acc_has_carried_scalars = false` arms were never run against a proof -- and that is the encoding the deployed Moonlight wrap verifier uses. It cannot be synthesized from the IVC fixture either: point_pair occupies 8 public-input words against point-and-scalar's 10, so dropping the scalars changes the instance count, hence the Lagrange evaluation and the transcript. Public inputs are bound into the proof. So generate a real one. `fixtures/moonlight-wrap` holds the rendered wrap decider and its calldata, produced by Moonlight's wrap bench on `origin/codex/wrap-bench-cherry-picks` against this branch's codegen. That run also confirms the layout rebase against a second accumulator shape: proof accepted on-chain in 1,277,823 gas with 244 native/Solidity trace points matched. The replay is now parameterized over both fixtures and recovers the encoding kind from the payload width rather than being told, so a fixture still describes itself. Split renders deploy a quotient evaluator; single-contract renders like the wrap one deploy against the verifying key alone. Second gap: the ivc replay only attacked the accumulator words. Port the calldata-level dimensions that poseidon covers and a static fixture can carry -- framing (trailing bytes, truncation, selector, ABI heads, proof length, instance count), every proof commitment against off-curve, base-modulus and non-canonical-padding G1 vectors, and every evaluation scalar and non-accumulator public input set to the Fr modulus. That is 32 commitments x 3 vectors, 115 eval words and 14 instances for the IVC fixture, ~230 EVM calls in about 2 s. The poseidon suite is left intact. Much of it cannot move to a static fixture -- wrong-VK and VK-mutation cases need a second verifying key, cross-wiring needs four circuits on one SRS, the forced-domain-root case needs prover-side control, and the pbt_* family draws fresh proptest seeds per run. Deleting those would have removed dimensions no fixture replay can reproduce, so this is additive: the accumulator configs gain the portable attacks, poseidon keeps the rest. The commitment run length is discovered from the EIP-2537 padding signature rather than hardcoded, so a regenerated fixture with a different commitment count stays covered. Every mutation class was individually neutered and confirmed to make the verifier accept, including on the point_pair fixture -- so its accumulator is genuinely consumed rather than ignored, and no assertion passes vacuously. Co-Authored-By: Claude Opus 4.8 --- .../fixtures/moonlight-wrap/Halo2Verifier.sol | 3659 +++++++++++++++++ .../moonlight-wrap/Halo2VerifyingKey.sol | 693 ++++ .../fixtures/moonlight-wrap/README.md | 69 + .../fixtures/moonlight-wrap/calldata.bin | Bin 0 -> 8516 bytes .../tests/ivc_accumulator_replay.rs | 302 +- 5 files changed, 4674 insertions(+), 49 deletions(-) create mode 100644 proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2Verifier.sol create mode 100644 proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2VerifyingKey.sol create mode 100644 proofs/solidity-verifier/fixtures/moonlight-wrap/README.md create mode 100644 proofs/solidity-verifier/fixtures/moonlight-wrap/calldata.bin diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2Verifier.sol b/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2Verifier.sol new file mode 100644 index 000000000..026c4e8a6 --- /dev/null +++ b/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2Verifier.sol @@ -0,0 +1,3659 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.24; + +/// @title Halo2 BLS12-381 KZG verifier. +/// @notice Circuit-specialized verifier for Midfall/midnight-proofs Halo2 +/// proofs rendered by this repository's Rust generator. +/// @dev This contract ports the verifier flow from +/// `midfall/proofs/src/plonk/verifier.rs`, the Keccak transcript comments from +/// `midfall/proofs/src/transcript/implementors.rs`, and the KZG multi-open +/// comments from `midfall/proofs/src/poly/kzg/mod.rs`. +/// @dev It is not a generic verifier. The proof layout, VK payload, quotient +/// identity program, memory layout, and optional quotient evaluator are all +/// generated for one `VerifyingKey>`. +/// +/// Halo2 KZG verifier for the BLS12-381 curve, midnight-proofs flavour. +/// +/// Differences vs the original BN254 / halo2 v0.4 template: +// +/// - BLS12-381 base field Fp is 381 bits and does not fit in a uint256. +/// Each Fp coord is encoded EIP-2537 padded (16 zero bytes + 48 bytes). +/// A G1 point is 128 bytes (4 words); a G2 point is 256 bytes (8). +/// - Calldata carries G1 commitments in uncompressed EIP-2537 padded +/// form (4 words = 128 bytes per point: x_hi, x_lo, y_hi, y_lo). The +/// proof bytes produced by midnight-proofs prover are repacked off +/// chain (compressed -> uncompressed) before being passed to +/// `verifyProof`. The verifier hashes the uncompressed 128-byte form into +/// the transcript verbatim, matching `Hashable for G1Projective::to_input`; +/// see `common_uncompressed_g1`. +/// - Transcript `common` absorbs raw inputs in order. `squeeze` computes one +/// Keccak digest, resets the transcript buffer to that digest, then samples +/// by interpreting the digest as a big-endian integer modulo r. +/// - Scalar inversion uses modexp(scalar, r-2, r). +/// - Constructors run deployment-time smoke tests for MCOPY and the EIP-2537 +/// precompiles using identity inputs. Compile with Solidity >=0.8.24 and +/// deploy only on chains/forks that support MCOPY and EIP-2537. +contract Halo2Verifier { + + /// @notice Verifying-key contract address authorized for this verifier. + /// @dev The runtime length and codehash are pinned by generated constants and checked at construction time. + address public immutable AUTHORIZED_VK; + // Expected VK runtime metadata. The deployed VK runtime is + // INVALID || payload, hence EXPECTED_VK_LENGTH is one byte longer than + // EXPECTED_VK_PAYLOAD_LENGTH. + uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 17024; + uint256 internal constant EXPECTED_VK_LENGTH = 17025; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0xe68d89362065c8b7107055774f1e045b69bc3bffde4704541c5bc5c91c94cf52; + bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); + + // Solidity ABI calldata cursors. The generated verifier accepts exactly + // verifyProof(bytes proof, uint256[] instances), then parses the `proof` + // bytes itself in the same order as the Rust verifier transcript. + uint256 internal constant PROOF_LEN_CPTR = 0x44; + uint256 internal constant PROOF_CPTR = 0x64; + uint256 internal constant NUM_INSTANCE_CPTR = 0x1ec4; + uint256 internal constant INSTANCE_CPTR = 0x1ee4; + // First general-purpose memory words reserved by the generated verifier. + // RETURN_MPTR is a single word set to 1 on success. + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; + + // ---------------------------------------------------------------------- + // Verifying-key memory map. The VK header lives at VK_MPTR, followed + // by the quotient VM payload and commitments. After the full VK + // runtime comes the challenge slots (challenge_mptr..) and the + // per-stage scratch (theta_mptr..). + // ---------------------------------------------------------------------- + uint256 internal constant VK_MPTR = 0x3680; + uint256 internal constant VK_DIGEST_MPTR = 0x3680; + uint256 internal constant NUM_INSTANCES_MPTR = 0x36a0; + uint256 internal constant K_MPTR = 0x36c0; + uint256 internal constant N_INV_MPTR = 0x36e0; + uint256 internal constant OMEGA_MPTR = 0x3700; + uint256 internal constant OMEGA_INV_MPTR = 0x3720; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x3740; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x3760; + uint256 internal constant ACC_OFFSET_MPTR = 0x3780; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x37a0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x37c0; + uint256 internal constant G1_BASE_MPTR = 0x37e0; + uint256 internal constant G2_BASE_MPTR = 0x3860; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x3960; + + uint256 internal constant CHALLENGE_MPTR = 0x7900; + + // Challenge layout. Squeeze order in midnight-proofs: + // user_phase challenges (variable count) + // theta -> beta, gamma -> trash_challenge -> y -> x -> + // x1, x2 -> x3 -> x4 + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; + + // Batch-open commitments live in 4-word EIP-2537 padded slots. + uint256 internal constant F_COM_MPTR = 0x7a40; + uint256 internal constant PI_MPTR = 0x7ac0; + + // Accumulator (KZG IVC). + uint256 internal constant ACC_LHS_MPTR = 0x7b40; + uint256 internal constant ACC_RHS_MPTR = 0x7bc0; + + // Lagrange / linearization scratch. + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; + // Legacy name: this is not h(x). It stores the expected opening + // scalar for the linearized commitment, i.e. the negated y-batched + // identity numerator reconstructed from the alleged evals at x. + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; + uint256 internal constant QUOTIENT_MPTR = 0x7d20; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x7dc0; + uint256 internal constant V_MPTR = 0x7de0; + uint256 internal constant FINAL_COM_MPTR = 0x7e00; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x7e80; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x7f00; // 4 words + + // Multi-prepare scratch (sized at codegen time). + uint256 internal constant ROT_POINTS_MPTR = 0x7f80; + uint256 internal constant X1_POWERS_MPTR = 0x8300; + // Q_COM materialization is currently fused into the final MSM scratch, + // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero + // reserved capacity until a future emitter starts writing Q_COM_MPTR. + uint256 internal constant Q_COM_MPTR = 0x8b20; + uint256 internal constant Q_EVAL_SET_MPTR = 0x8b20; + + // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals + // block of the proof; we keep it as a memory slot for symmetry. + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x9220; + + // Reserved 4-word slot for the G1 identity (point at infinity) in + // EIP-2537 padded form. EVM memory is zero-initialised, and we + // never write to this region, so the four `mload`s below produce + // 0,0,0,0 which is exactly the identity encoding the EIP-2537 + // ec_add / ec_mul precompiles accept. + uint256 internal constant G1_IDENTITY_MPTR = 0x9320; + + // Decoded polynomial-eval buffer (Optimisation H3). The off-chain + // Solidity proof shim rewrites proof scalars into canonical BE words, + // so `calldataload` gives the field element directly. The transcript- + // side `evaluations` loop range-checks and spills that value here so + // downstream eval references (gate evaluator + PCS q_eval Horner) + // become 3-gas `mload(...)` instead of calldata reads. + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0xb140; + uint256 internal constant TRACE_U256_MPTR = 0xe340; + + // ---------------------------------------------------------------------- + // Per-category bases for EIP-2537 padded G1 commitments. The proof + // calldata carries 128-byte uncompressed/padded G1s after the off-chain + // proof shim repacks midnight-proofs' native compressed stream; this + // region stores the 4-word slots used by PCS / quotient-fold sections. + // + // Cumulative offsets (in words from `comms_mptr_base`): + // ADVICE_COMMS_MPTR_BASE + 0 + // LOOKUP_M_COMMS_MPTR_BASE + 4*total_advices + // PERM_Z_COMMS_MPTR_BASE + 4*total_advices + 4*num_lookups + // LOOKUP_HELPER_COMMS_MPTR_BASE + ... + 4*num_permutation_zs + // LOOKUP_Z_COMMS_MPTR_BASE + ... + 4*lookup_helper_chunks_total + // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups + // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans + // ---------------------------------------------------------------------- + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0xa140; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0xa8c0; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0xa9c0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0xacc0; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0xadc0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0xaec0; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0xaf40; + + // BLS12-381 scalar-field modulus, used for transcript challenges and all + // Halo2 verifier arithmetic. + uint256 internal constant FR_MODULUS = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + + // BLS12-381 Fp modulus minus one, split like an EIP-2537 coordinate: + // high word = 16 zero bytes || top 16 coordinate bytes, low word = + // bottom 32 coordinate bytes. + uint256 internal constant BLS_P_HI = 0x000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd7; + uint256 internal constant BLS_P_MINUS_ONE_LO = 0x64774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa; + // Packed public-accumulator sentinels for the shifted coordinate codec. + // The `_WITH_ID_FLAG` variant is used only for the first x-coordinate word. + uint256 internal constant BLS_P_MINUS_ONE_PACKED_0 = 0x00000000f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa; + uint256 internal constant BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG = 0x00000000f38512bf6730d2a0f6b0f6241eabfffeb153ffffbafeffffffffaaaa; + uint256 internal constant BLS_P_MINUS_ONE_PACKED_1 = 0x0000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84; + + /// @notice Smoke-check the Cancun/EIP-2537 runtime features required by the verifier. + /// @dev Exercises MCOPY and identity EIP-2537 inputs to catch incompatible chain/fork configurations at deployment. + function require_eip2537_precompiles() private view { + assembly ("memory-safe") { + // Scratch is reused for every runtime-prerequisite probe. + let scratch := 0x1000 + + // MCOPY must be available because the verifier uses it for + // proof-time point/scratch staging. Execute the opcode here so a + // non-Cancun fork fails during deployment instead of later proofs. + mstore(scratch, 0x1234) + mcopy(add(scratch, 0x20), scratch, 0x20) + if iszero(eq(mload(add(scratch, 0x20)), 0x1234)) { revert(0, 0) } + + // Start the EIP-2537 probes with the identity encoding for G1/G2: + // all-zero padded words. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + + // G1ADD(identity, identity) -> identity, 128-byte return. + // This catches chains where the precompile is missing or returns a + // non-standard success shape. + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if or(or(mload(scratch), mload(add(scratch, 0x20))), or(mload(add(scratch, 0x40)), mload(add(scratch, 0x60)))) { + revert(0, 0) + } + + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + + // Worst-case generated G1MSM with all identity/zero terms -> + // identity, 128-byte return. This exercises the largest MSM input + // length rendered by this verifier instead of only a one-pair + // smoke call. + let msm_scratch := 0xb140 + for { let off := 0 } lt(off, 0x30c0) { off := add(off, 0x20) } { + mstore(add(msm_scratch, off), 0) + } + // The production verifier uses G1MSM both for commitments and as + // the subgroup validator for absorbed proof points. + if iszero(staticcall(gas(), 0x0c, msm_scratch, 0x30c0, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if or(or(mload(scratch), mload(add(scratch, 0x20))), or(mload(add(scratch, 0x40)), mload(add(scratch, 0x60)))) { + revert(0, 0) + } + + // PAIRING_CHECK([(identity_g1, identity_g2), (identity_g1, identity_g2)]) + // -> true, 32-byte return. This matches the runtime two-pair KZG + // pairing input size and catches absent pairing precompiles, + // short return data, and obviously incompatible semantics. + if iszero(staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x20)) { revert(0, 0) } + if iszero(eq(mload(scratch), 1)) { revert(0, 0) } + } + } + + + /// @notice Create a verifier pinned to a generated verifying key. + /// @dev Checks MCOPY/EIP-2537 availability and verifies the VK runtime before storing its address. + /// @param authorizedVk Address of the generated `Halo2VerifyingKey` runtime. + constructor(address authorizedVk) { + // Embedded quotient path: only the external VK runtime needs to be + // pinned, but the runtime opcode/precompile prerequisites are still + // mandatory. + require_eip2537_precompiles(); + require( + authorizedVk.code.length == EXPECTED_VK_LENGTH + && authorizedVk.codehash == EXPECTED_VK_CODEHASH, + "invalid vk" + ); + AUTHORIZED_VK = authorizedVk; + } + + /// @notice Verify a Halo2/Midfall proof for the generated verifying key. + /// @dev This checks only that `proof` verifies for the supplied public + /// `instances` under this pinned VK/protocol. Application contracts must + /// bind the meaning of those instances separately: state roots, program + /// identifiers, expected IVC outputs, chain/domain separation, and any + /// protocol-specific authorization are outside this raw verifier ABI. + /// @dev Production renders are success-or-revert: accepted proofs return + /// `true`, while malformed calldata, invalid proof material, failed + /// precompiles, or mismatched pinned dependency code revert. Trace and gas + /// renders keep the same failure policy. + /// @dev The generated verifier uses absolute Yul memory addresses instead + /// of Solidity's free-memory pointer, but generated scratch starts at + /// `0x80` so Solidity's reserved memory prefix is preserved. The main + /// assembly block remains terminal: accepted proofs return from assembly + /// and all rejected inputs revert. Do not inline this body into Solidity + /// code that continues executing after verification without reviewing the + /// memory strategy; see `docs/MEMORY_LAYOUT.md`. + /// @param proof Solidity-facing proof bytes, with G1 elements repacked into EIP-2537 padded uncompressed form. + /// @param instances Public instance scalars encoded as canonical BLS12-381 scalar-field words. + /// @return Always `true` for accepted proofs; invalid proofs revert instead of returning `false`. + function verifyProof( + bytes calldata proof, + uint256[] calldata instances + ) external view returns (bool) { + // Cheap ABI-shape guard before any generated memory work: + // - proof head must point at the bytes payload; + // - instances head must point at the generated instance array. + // + // The verifier below is a hand-rolled calldata parser. Failing here + // keeps malformed dynamic-argument layouts from being interpreted as a + // valid Midfall proof stream. + assembly ("memory-safe") { + if iszero(and(eq(calldataload(0x04), 0x40), eq(calldataload(0x24), sub(NUM_INSTANCE_CPTR, 0x04)))) { + revert(0, 0) + } + } + // Non-embedded renders pin the VK by address and codehash. The Yul + // loader rechecks the runtime before every proof and copies the + // INVALID-prefixed payload into VK_MPTR. + address vk = AUTHORIZED_VK; + assembly ("memory-safe") { + // This block owns the call-frame memory and remains terminal. + // Generated scratch starts at TRANSCRIPT_MPTR (0x80), preserving + // Solidity's reserved scratch, free-memory-pointer, and zero-slot + // words. See docs/MEMORY_LAYOUT.md. + // =============================================================== + // Helpers: modexp, transcript, EIP-2537 calls + // =============================================================== + + // Inverse of a Fr scalar via modexp(x, r-2, r). The verifier + // calls this only after transcript absorption is complete, so it + // reuses the dead transcript buffer just below VK_MPTR instead of + // a fixed post-VK address that can collide with live PCS scratch + // when the VK payload becomes smaller. + function scalar_inv(x) -> inv { + // Zero has no multiplicative inverse in Fr; callers rely on a + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } + if iszero(x) { revert(0, 0) } + let p := 0x3580 + // EIP-198 modexp frame: + // [base_len, exp_len, mod_len, base, exponent, modulus] + mstore(add(p, 0x00), 0x20) // base len + mstore(add(p, 0x20), 0x20) // exp len + mstore(add(p, 0x40), 0x20) // mod len + mstore(add(p, 0x60), x) + mstore(add(p, 0x80), sub(FR_MODULUS, 2)) + mstore(add(p, 0xa0), FR_MODULUS) + if iszero(staticcall(gas(), 0x05, p, 0xc0, p, 0x20)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x20)) { revert(0, 0) } + inv := mload(p) + } + + // ---------- Streaming Keccak256 transcript helpers ---------- + // + // The transcript buffer lives at + // memory[TRANSCRIPT_MPTR..buf_len). On verifier entry it starts + // empty. Each common(input) appends raw bytes. squeeze_*(buf_len) + // computes one Keccak digest, reseeds the buffer with that + // 32-byte digest, and samples a Fq element as + // uint256(digest_be) mod r. + + function transcript_init() -> buf_len { + // Empty transcript buffer starts exactly at TRANSCRIPT_MPTR. + buf_len := TRANSCRIPT_MPTR + } + + // Append one 32-byte big-endian field/transcript word at the + // current end of the transcript buffer. + function common_word(buf_len, word) -> ret { + mstore(buf_len, word) + ret := add(buf_len, 32) + } + + // Absorb a BLS12-381 G1 point in EIP-2537 padded + // uncompressed form (4 calldata words = 128 bytes: + // x_hi || x_lo || y_hi || y_lo, each coord = 16 zero + // pad bytes + 48 big-endian field bytes) into the + // transcript buffer at `buf_len`. + // + // Matches the patched `Hashable for + // midnight_curves::G1Projective::to_input` in + // midnight-proofs, which now emits the same 128-byte form + // (`midfall/proofs/src/transcript/implementors.rs`). The + // previous emitter hashed the 48-byte ZCash compressed + // encoding instead and ran a 384-bit `lex(y) > lex(p − y)` + // ladder + identity flag fixup to derive the sign bit on + // the fly; switching to the uncompressed form drops that + // ladder entirely. + // + // Canonicality: reject non-zero bytes in the top 16 bytes + // of each `_hi` calldata word and reject coordinates + // outside Fp. Normalizing those bytes before hashing would + // make multiple calldata encodings share one transcript. + // + // This helper does not run an independent curve/subgroup + // check. Instead, ProtocolPlan::validate rejects generated + // plans where an absorbed proof commitment would not later be + // consumed by an EIP-2537 G1MSM or pairing path, and those + // precompiles perform the curve/subgroup validation. + // + // The point's uncompressed form remains in calldata; the + // call site is responsible for `calldatacopy`-ing it into + // memory afterwards if it needs the on-curve coordinates. + function common_uncompressed_g1(buf_len, cptr) -> ret { + let x_hi_word := calldataload(cptr) + let x_lo := calldataload(add(cptr, 0x20)) + let y_hi_word := calldataload(add(cptr, 0x40)) + let y_lo := calldataload(add(cptr, 0x60)) + if shr(128, x_hi_word) { revert(0, 0) } + if shr(128, y_hi_word) { revert(0, 0) } + + let x_hi := and(x_hi_word, 0xffffffffffffffffffffffffffffffff) + let y_hi := and(y_hi_word, 0xffffffffffffffffffffffffffffffff) + if iszero(or(lt(x_hi, BLS_P_HI), and(eq(x_hi, BLS_P_HI), iszero(gt(x_lo, BLS_P_MINUS_ONE_LO))))) { + revert(0, 0) + } + if iszero(or(lt(y_hi, BLS_P_HI), and(eq(y_hi, BLS_P_HI), iszero(gt(y_lo, BLS_P_MINUS_ONE_LO))))) { + revert(0, 0) + } + + // Memcpy the 4 calldata words (128 bytes) verbatim + // into the keccak buffer. + calldatacopy(buf_len, cptr, 0x80) + ret := add(buf_len, 0x80) + } + + // One Keccak finalization + reseed. Returns the new buffer + // cursor (= TRANSCRIPT_MPTR + 32) and stores the squeezed Fq at + // `mptr`. + function squeeze_to(buf_len, mptr) -> ret { + let h0 := keccak256(TRANSCRIPT_MPTR, sub(buf_len, TRANSCRIPT_MPTR)) + // Reseed: write the 32-byte digest at start of buffer. + mstore(TRANSCRIPT_MPTR, h0) + let r := FR_MODULUS + // Sample Fq as uint256(keccak_digest_be) mod r. + mstore(mptr, mod(h0, r)) + ret := add(TRANSCRIPT_MPTR, 32) + } + + // ---------- EC primitives (EIP-2537 wrappers) ---------- + // + // These mirror the BN254 helpers but operate on 4-word G1 + // points. They use planned memory windows above Solidity's + // reserved prefix; the streaming transcript buffer is no longer + // needed once all challenges are squeezed. + + // Invert a contiguous run of Fr words in-place using Montgomery's + // batch inversion trick: + // 1. write prefix products to scratch; + // 2. invert the total product once with modexp; + // 3. walk backward to recover each individual inverse. + // + // The function returns a boolean instead of reverting so callers + // can combine it with other `success` plumbing until a section + // boundary decides whether to fail closed. + function batch_invert(success, mptr_start, mptr_end, scratch_mptr, r) -> ret { + ret := success + if iszero(ret) { leave } + // Memory ranges must be forward and word-aligned by + // construction; a reversed range is always a codegen error. + if lt(mptr_end, mptr_start) { + ret := 0 + leave + } + + let count_bytes := sub(mptr_end, mptr_start) + // Empty batch is valid and leaves memory untouched. + if iszero(count_bytes) { leave } + + // Fast path for a single denominator: avoid prefix scratch and + // just run one modexp inverse in place. + if eq(count_bytes, 0x20) { + let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } + if iszero(x) { + ret := 0 + leave + } + + let single_scratch := scratch_mptr + mstore(add(single_scratch, 0x00), 0x20) + mstore(add(single_scratch, 0x20), 0x20) + mstore(add(single_scratch, 0x40), 0x20) + mstore(add(single_scratch, 0x60), x) + mstore(add(single_scratch, 0x80), sub(r, 2)) + mstore(add(single_scratch, 0xa0), r) + ret := staticcall(gas(), 0x05, single_scratch, 0xc0, single_scratch, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + if ret { mstore(mptr_start, mload(single_scratch)) } + leave + } + + // Forward pass: scratch stores prefix products up to, but not + // including, the final element. `gp` becomes the total product. + let gp_mptr := scratch_mptr + let gp := mload(mptr_start) + let mptr := add(mptr_start, 0x20) + for {} lt(mptr, sub(mptr_end, 0x20)) {} { + gp := mulmod(gp, mload(mptr), r) + mstore(gp_mptr, gp) + mptr := add(mptr, 0x20) + gp_mptr := add(gp_mptr, 0x20) + } + gp := mulmod(gp, mload(mptr), r) + // A zero total product means at least one denominator was + // zero, so no batch inverse exists. + if iszero(gp) { + ret := 0 + leave + } + + // Invert the total product once. + mstore(add(gp_mptr, 0x00), 0x20) + mstore(add(gp_mptr, 0x20), 0x20) + mstore(add(gp_mptr, 0x40), 0x20) + mstore(add(gp_mptr, 0x60), gp) + mstore(add(gp_mptr, 0x80), sub(r, 2)) + mstore(add(gp_mptr, 0xa0), r) + ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } + let all_inv := mload(gp_mptr) + + // Backward pass: derive each inverse from the inverted total + // product and the saved prefix products. + let first_mptr := mptr_start + let second_mptr := add(first_mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + for {} lt(second_mptr, mptr) {} { + let inv := mulmod(all_inv, mload(gp_mptr), r) + all_inv := mulmod(all_inv, mload(mptr), r) + mstore(mptr, inv) + mptr := sub(mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + } + let inv_first := mulmod(all_inv, mload(second_mptr), r) + let inv_second := mulmod(all_inv, mload(first_mptr), r) + mstore(first_mptr, inv_first) + mstore(second_mptr, inv_second) + } + + // Final EIP-2537 pairing wrapper. `lhs_mptr` and `rhs_mptr` are + // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. + function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { + ret := success + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } + // Lay out two (G1, G2) pairs at scratch..scratch+0x300: + // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] + // Cancun MCOPY (3 + 3·words gas) replaces what used to + // be a 4-step mstore chain for each G1 (~60 gas) and an + // 8-iter mstore loop for each G2 (~240 gas). Net saving + // here is ~500 gas per ec_pairing call. + let scratch := 0x0320 + mcopy(scratch, lhs_mptr, 0x80) + mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) + mcopy(add(scratch, 0x180), rhs_mptr, 0x80) + mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) + ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) + if iszero(ret) { revert(0, 0) } + ret := 1 + } + + // ---------- IVC accumulator public-input decoding ---------- + // + // `AssignedForeignPoint` exposes each base-field coordinate + // through `AssignedField::as_public_input`: seven radix-2^56 limbs of + // (coord - 1) are packed four-at-a-time into native field elements. + // The x coordinate's first packed word carries the identity flag by + // adding one raw radix base. Rebuild EIP-2537 padded + // (x_hi, x_lo, y_hi, y_lo) words from that encoding. + // + // Public-input layout for one coordinate: + // word 0: limb_0 | limb_1 << bits | ... up to limbs_per_word + // word 1: next limbs, if any + // + // The limbs are little-endian in the represented integer even + // though calldata words are loaded as big 256-bit values. The loop + // below extracts each limb by shifting inside the packed word and + // reconstructs the full coordinate into the two-word EIP-2537 + // representation expected by the BLS12-381 precompiles. + function load_acc_coord_shifted(src, bits, n, base, limbs_per_word, first_adjust) -> hi, lo { + // Mask for one radix limb, e.g. 2^56 - 1 for the current + // BLS12-381 self-emulation parameters. + let mask := sub(base, 1) + for { let i := 0 } lt(i, n) { i := add(i, 1) } { + // Limb words are little-endian packed inside each Fr + // public input. `first_adjust` removes the identity flag + // base from the first x word when present. + let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { + packed := sub(packed, first_adjust) + } + // Select limb i from its packed field word. The mod/div + // pair maps a limb index to an intra-word limb slot and + // the calldata word containing it. + let limb := and(shr(mul(mod(i, limbs_per_word), bits), packed), mask) + + let shift := mul(i, bits) + // Split the reconstructed 384-bit coordinate into the + // EIP-2537 high/low words expected by the precompiles. + if lt(shift, 256) { + lo := add(lo, shl(shift, limb)) + if gt(add(shift, bits), 256) { + // A limb can straddle the 256-bit low/high split. + // Move the overflow bits into hi. + hi := add(hi, shr(sub(256, shift), limb)) + } + } + if iszero(lt(shift, 256)) { + // Once shift >= 256 the whole limb belongs to hi. + hi := add(hi, shl(sub(shift, 256), limb)) + } + } + } + + // The shifted coordinate codec represents zero as p-1 before the + // final +1 below, so keep this sentinel explicit. + function is_bls_p_minus_one(hi, lo) -> yes { + yes := and(eq(hi, BLS_P_HI), eq(lo, BLS_P_MINUS_ONE_LO)) + } + + // Canonical encoded accumulator identity: + // x = p-1 plus the identity flag in the first packed word, + // y = p-1 with no identity flag. + // It decodes to the EIP-2537 point-at-infinity slot (all zeros). + // + // This fast path is deliberately stricter than "decodes to zero": + // the point at infinity has exactly one accepted public-input + // encoding. Non-canonical zero-like encodings are rejected later. + function is_acc_encoded_identity(src) -> yes { + yes := and( + and( + eq(calldataload(src), BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG), + eq(calldataload(add(src, 0x20)), BLS_P_MINUS_ONE_PACKED_1) + ), + and( + eq(calldataload(add(src, 0x40)), BLS_P_MINUS_ONE_PACKED_0), + eq(calldataload(add(src, 0x60)), BLS_P_MINUS_ONE_PACKED_1) + ) + ) + } + + // Reject unused high bits in the packed public-input words. This + // makes each accumulator point encoding canonical before it reaches + // the precompile-based curve/subgroup validation. + function check_acc_coord_packing(src, bits, n, limbs_per_word) -> ok { + ok := 1 + // Number of packed native-field public-input words occupied by + // one coordinate. + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + for { let word_idx := 0 } lt(word_idx, coord_words) { word_idx := add(word_idx, 1) } { + // The final word may contain fewer than limbs_per_word + // limbs. Any unused high bits must be zero, otherwise the + // same coordinate would have multiple calldata encodings. + let remaining := sub(n, mul(word_idx, limbs_per_word)) + let limbs_in_word := limbs_per_word + if lt(remaining, limbs_per_word) { + limbs_in_word := remaining + } + let used_bits := mul(limbs_in_word, bits) + if lt(used_bits, 256) { + // shl(used_bits, 1) == 2^used_bits. The packed word + // must be strictly less than that bound. + ok := and(ok, lt(calldataload(add(src, mul(word_idx, 0x20))), shl(used_bits, 1))) + } + } + } + + // Decode one shifted coordinate. `allow_id` is true only for x, + // because the identity flag lives in x's first packed word. + function load_acc_coord(src, allow_id, bits, n, base, limbs_per_word) -> ok, hi, lo, is_id { + ok := check_acc_coord_packing(src, bits, n, limbs_per_word) + if and(allow_id, iszero(lt(calldataload(src), base))) { + // Probe the x identity flag by removing one radix base and + // checking whether the adjusted coordinate is p-1. + // + // `calldataload(src) >= base` is a cheap prefilter: only x + // can carry this flag, and adding one radix base must make + // the first packed word at least base. + let adj_hi, adj_lo := load_acc_coord_shifted(src, bits, n, base, limbs_per_word, base) + is_id := is_bls_p_minus_one(adj_hi, adj_lo) + } + + // Decode again with the identity adjustment applied only when + // the canonical identity flag was actually detected. + hi, lo := load_acc_coord_shifted(src, bits, n, base, limbs_per_word, mul(is_id, base)) + ok := and( + ok, + // Coordinate must be in the BLS12-381 base field, i.e. + // <= p - 1 in split hi/lo form. + or(lt(hi, BLS_P_HI), and(eq(hi, BLS_P_HI), iszero(gt(lo, BLS_P_MINUS_ONE_LO)))) + ) + + let was_p_minus_one := is_bls_p_minus_one(hi, lo) + if was_p_minus_one { + // Shifted encoding maps p-1 back to zero. + hi := 0 + lo := 0 + } + if iszero(was_p_minus_one) { + // All other coordinates are encoded as coord - 1, so add + // one back with carry into the high word. + let next_lo := add(lo, 1) + hi := add(hi, lt(next_lo, lo)) + lo := next_lo + } + + // EIP-2537 pads each 48-byte Fp coordinate to 64 bytes, + // so the high word must fit in its low 128 bits. + // This also catches impossible reconstructions above 384 bits. + ok := and(ok, lt(hi, shl(128, 1))) + } + + // Decode a public accumulator point into an EIP-2537 4-word G1 + // slot. Non-identity points are curve/subgroup checked later by + // routing them through G1MSM. + function load_acc_point(dst, src, bits, n, base) -> ok, is_id { + // Prefer the canonical all-coordinate identity encoding before + // attempting coordinate-level shifted decoding. This accepts + // the point at infinity only in the exact form generated by the + // circuit's public-input codec. + is_id := is_acc_encoded_identity(src) + if is_id { + ok := 1 + // EIP-2537 encodes G1 identity as four zero words: + // x_hi = x_lo = y_hi = y_lo = 0. + mstore(dst, 0) + mstore(add(dst, 0x20), 0) + mstore(add(dst, 0x40), 0) + mstore(add(dst, 0x60), 0) + } + if iszero(is_id) { + // x occupies coord_words packed public-input words; y + // starts immediately after x. + let limbs_per_word := 4 + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + // Only x may carry the identity flag. y must decode as a + // normal shifted coordinate. + let x_ok, x_hi, x_lo, x_is_id := load_acc_coord(src, 1, bits, n, base, limbs_per_word) + let y_ok, y_hi, y_lo, y_id := load_acc_coord( + add(src, mul(coord_words, 0x20)), + 0, + bits, + n, + base, + limbs_per_word + ) + // y_id is always zero because allow_id was false, but the + // tuple shape is shared with x decoding. + pop(y_id) + ok := and(x_ok, y_ok) + is_id := x_is_id + + if is_id { + // If x carried the identity flag, both decoded + // coordinates must be zero after shifting. Any other y + // value would be a malformed infinity encoding. + ok := and(ok, iszero(or(or(x_hi, x_lo), or(y_hi, y_lo)))) + mstore(dst, 0) + mstore(add(dst, 0x20), 0) + mstore(add(dst, 0x40), 0) + mstore(add(dst, 0x60), 0) + } + if iszero(is_id) { + // The coordinate codec maps encoded p-1 to decoded + // zero. EIP-2537 reserves affine (0,0) for the point + // at infinity, so a decoded infinity is only valid + // when the canonical accumulator identity encoding + // was used above. + let decoded_zero := iszero(or(or(x_hi, x_lo), or(y_hi, y_lo))) + ok := and(ok, iszero(decoded_zero)) + // Store the affine point in the exact precompile input + // layout: x_hi, x_lo, y_hi, y_lo. + mstore(dst, x_hi) + mstore(add(dst, 0x20), x_lo) + mstore(add(dst, 0x40), y_hi) + mstore(add(dst, 0x60), y_lo) + } + } + } + // Validate and prepare the public accumulator equation before the + // main transcript starts. This fails malformed public inputs early + // and writes ACC_LHS_MPTR / ACC_RHS_MPTR for final pairing batching. + // + // The accumulator public input represents an equality of two G1 + // commitments used by the recursive KZG accumulator. This helper: + // 1. decodes carried public G1 points from shifted limbs; + // 2. forces every decoded point through EIP-2537 G1MSM so the + // precompile validates curve/subgroup membership; + // 3. folds the RHS carried point and fixed-base scalar tail into + // ACC_RHS_MPTR, leaving ACC_LHS_MPTR / ACC_RHS_MPTR ready for + // randomized batching in FinalPairing.yul. + function validate_public_accumulator(success, r) -> out { + out := success + let bits := 56 + let n := 7 + // The BLS12-381 self-emulation currently exposes Fp + // coordinates as 7 radix-2^56 limbs. + let limb_base := shl(bits, 1) + let limbs_per_word := 4 + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + // acc_offset is generated from the VK/protocol shape and + // points into the ABI `instances` array. + let acc_instance_ptr := add(INSTANCE_CPTR, 0x0160) + + // LHS layout: point limbs (x,y), then either an explicit + // scalar word or an implicit unit scalar for already-collapsed + // point-pair public inputs. + // The scalar pointer is computed unconditionally; the rendered + // branch below decides whether to read it or use scalar 1. + let lhs_scalar_ptr := add(acc_instance_ptr, mul(mul(2, coord_words), 0x20)) + let lhs_ok, lhs_is_id := load_acc_point(ACC_LHS_MPTR, acc_instance_ptr, bits, n, limb_base) + out := and(out, lhs_ok) + // Shared scratch for one-pair LHS validation and the later + // variable-length RHS MSM. + let acc_scratch := 0xb140 + { + // Already-collapsed point-pair layout: carried scalars are + // implicit one. + let lhs_scalar := 1 + // Identity status is useful for decoding checks above, but + // validation still goes through G1MSM for all points. + pop(lhs_is_id) + // Always route the decoded carried point through G1MSM, + // even for identity points and zero/one scalars. The + // precompile is the on-curve/subgroup validator for this + // public-input point; skipping it would let a malformed + // non-identity point hide behind scalar 0. + mcopy(acc_scratch, ACC_LHS_MPTR, 0x80) + mstore(add(acc_scratch, 0x80), lhs_scalar) + if out { + // Single-pair MSM output overwrites ACC_LHS_MPTR with + // lhs_scalar * decoded_lhs. If lhs_scalar is one, this + // is also a curve/subgroup validation round-trip. + out := staticcall(gas(), 0x0c, acc_scratch, 0xa0, ACC_LHS_MPTR, 0x80) + out := and(out, eq(returndatasize(), 0x80)) + } + } + // RHS layout for this generated verifier is an already + // collapsed point pair: lhs point, rhs point. Both carried + // scalars are implicit one, and there is no fixed-base scalar + // tail. + let rhs_instance_ptr := lhs_scalar_ptr + // RHS scalar, when present, immediately follows the RHS point + // limbs. The fixed-base scalar tail starts after it. + let rhs_scalar_ptr := add(rhs_instance_ptr, mul(mul(2, coord_words), 0x20)) + let rhs_ok, rhs_is_id := load_acc_point(ACC_RHS_MPTR, rhs_instance_ptr, bits, n, limb_base) + out := and(out, rhs_ok) + // acc_pair_ptr appends (G1, scalar) pairs into acc_scratch for + // one final RHS MSM. + let acc_pair_ptr := acc_scratch + { + // Implicit unit scalar for already-collapsed point pairs. + let rhs_scalar := 1 + pop(rhs_is_id) + // Keep the carried RHS point in the MSM input even when + // it is encoded as identity or has scalar 0/1, so EIP-2537 + // validates every decoded public accumulator point before + // it can affect, or be erased from, the pairing batch. + mcopy(acc_pair_ptr, ACC_RHS_MPTR, 0x80) + mstore(add(acc_pair_ptr, 0x80), rhs_scalar) + // Move to the next (G1, scalar) pair slot. + acc_pair_ptr := add(acc_pair_ptr, 0xa0) + } + // Total byte length of the appended RHS MSM input pairs. This + // is at least one pair because the carried RHS point is always + // appended; keep the guard for synthetic render configurations. + let acc_msm_len := sub(acc_pair_ptr, acc_scratch) + if acc_msm_len { + // Fold the carried RHS point and any generated fixed-base + // tail into ACC_RHS_MPTR. The later final pairing block + // randomizes this equation together with the KZG pairing. + if out { + // Output overwrites ACC_RHS_MPTR with: + // rhs_scalar * carried_rhs + // + sum_i fixed_scalar_i * fixed_base_i + // + // The precompile also validates every nonzero fixed + // base embedded by codegen and the carried RHS point. + out := staticcall( + gas(), + 0x0c, + acc_scratch, + acc_msm_len, + ACC_RHS_MPTR, + 0x80 + ) + out := and(out, eq(returndatasize(), 0x80)) + } + } + // The caller checks `out` and reverts before transcript work if + // any decode, canonicality, or precompile validation failed. + } + + + + let r := FR_MODULUS + let success := true + + + + // =============================================================== + // VK loading: either bake in the embedded VK bytes or fetch + // them from the linked AUTHORIZED_VK contract. + // + // This is the first verifier phase after helper definitions. Its + // job is to make the generated VK payload available at VK_MPTR in + // one canonical memory layout, regardless of whether this render + // embeds the VK directly or links a separate Halo2VerifyingKey + // contract. + // + // Later template partials treat VK_MPTR as already populated with: + // - header words: vk_digest, domain data, accumulator metadata; + // - BLS12-381 base points used by the final pairing; + // - compact quotient VM constants/program bytes, when enabled; + // - fixed and permutation commitments in 4-word G1 slots. + // =============================================================== + { + // Re-check the pinned VK dependency on every proof. The + // constructor check catches normal deployment mistakes, while + // this fresh check hardens forks or same-transaction edge + // cases where code at the authorized address could differ + // from the runtime originally pinned by this verifier. + // + // EXPECTED_VK_LENGTH includes the leading INVALID byte in the + // Halo2VerifyingKey runtime. EXPECTED_VK_CODEHASH_WORD is the + // full runtime hash, not only the payload hash. + if iszero(and( + eq(extcodesize(vk), EXPECTED_VK_LENGTH), + eq(extcodehash(vk), EXPECTED_VK_CODEHASH_WORD) + )) { revert(0, 0) } + // Runtime byte 0 is INVALID so direct calls cannot execute the + // payload. Copy from byte 1 into VK_MPTR to reconstruct the + // exact payload layout used by the embedded branch. + extcodecopy(vk, VK_MPTR, 0x01, EXPECTED_VK_PAYLOAD_LENGTH) + + // Cross-check loaded VK header words against the verifier + // constants used by later parser, domain, and accumulator + // paths. Codehash pinning protects the external VK address; + // these checks catch generator drift before calldata parsing + // chooses a stale schema. + success := and(success, eq(mload(NUM_INSTANCES_MPTR), 19)) + success := and(success, eq(mload(K_MPTR), 20)) + success := and(success, eq(mload(HAS_ACCUMULATOR_MPTR), 1)) + success := and(success, eq(mload(ACC_OFFSET_MPTR), 11)) + success := and(success, eq(mload(NUM_ACC_LIMBS_MPTR), 7)) + success := and(success, eq(mload(NUM_ACC_LIMB_BITS_MPTR), 56)) + if iszero(success) { revert(0, 0) } + // + // The checks below validate the dynamic ABI envelope before the + // transcript parser starts walking raw calldata: + // - proof bytes length equals the generated proof layout; + // - instance array length equals the generated public input + // count; + // - total calldata length has no missing or trailing words. + // + // `success` is folded through `and` for consistency with later + // sections, then immediately enforced at the end of this block. + // A failure here means the verifier is not looking at the proof + // shape it was generated to parse. + success := and(success, eq(0x1e60, calldataload(PROOF_LEN_CPTR))) + success := and(success, eq(19, calldataload(NUM_INSTANCE_CPTR))) + // Calldata must contain exactly the ABI selector, proof bytes, + // instance-array length, and generated number of instance + // words. Any trailing bytes fail closed. + success := and( + success, + eq(calldatasize(), add(INSTANCE_CPTR, 0x0260)) + ) + // Stop before any transcript absorption if the ABI/proof shape + // is not exactly the generated one. + if iszero(success) { revert(0, 0) } + } + // Fail malformed accumulator public inputs before transcript, + // quotient, PCS, and final pairing work. The late accumulator block + // only batches these already-validated G1 outputs into the final + // pairing equation. + // + // Accumulator validation decodes shifted public-input limbs into + // EIP-2537 G1 slots, checks canonical encodings, and routes points + // through G1MSM for curve/subgroup validation. Doing it here means + // invalid accumulator public inputs cannot influence transcript + // challenge derivation or waste gas in later quotient/PCS work. + // validate_public_accumulator returns a boolean to share the same + // success-plumbing style as other helper calls; this boundary is + // where the verifier converts failure to a revert. + success := validate_public_accumulator(success, r) + if iszero(success) { revert(0, 0) } + + // =============================================================== + // Transcript: VK digest + instances + proof. + // + // This block is the Solidity mirror of the native Midfall verifier + // transcript schedule. It does three jobs at once: + // + // 1. Absorb public data and proof bytes into the streaming + // Keccak transcript in exactly the native order. + // 2. Decode/range-check proof scalars and canonical G1 calldata. + // 3. Copy proof commitments/evaluations into planned memory + // slots consumed by Lagrange, quotient, PCS, and pairing + // blocks later in the verifier. + // + // `buf_len` is a write cursor into the transcript buffer. The + // helper functions append bytes and return the new cursor; squeeze + // helpers hash memory[TRANSCRIPT_MPTR..buf_len), reseed the buffer + // with the digest, and write the sampled Fr challenge to memory. + // =============================================================== + let buf_len := transcript_init() + // VK_DIGEST_MPTR holds the digest as a BE 32-byte word (the + // VK contract stores it via `mstore`, which matches the + // Keccak Fq transcript input). + // + // This digest commits to the verifier key / constraint system + // before any proof material is read. + buf_len := common_word(buf_len, mload(VK_DIGEST_MPTR)) + + // Absorb committed_pi = G1Affine::identity() when the + // `committed-instances` feature is on in midnight-proofs. + // Under the patched `Hashable::to_input` (see + // `midfall/proofs/src/transcript/implementors.rs`), the + // identity hashes as 128 zero bytes (EIP-2537 (0,0) + // convention), NOT the 48-byte ZCash compressed form + // 0xc0||47*0x00 that the previous emitter produced. + // Native verifier absorbs this BEFORE the instance count. + { + // 128 zero bytes: zero out 4 consecutive 32-byte words + // at buf_len. + // This is a raw transcript absorb, not a memory slot kept for + // later elliptic-curve operations. + mstore(buf_len, 0) + mstore(add(buf_len, 0x20), 0) + mstore(add(buf_len, 0x40), 0) + mstore(add(buf_len, 0x60), 0) + buf_len := add(buf_len, 0x80) + } + + { + // Native verifier absorbs a length scalar before instance + // values; Keccak Fq transcript input is canonical BE. + // The ABI length was already checked against this generated + // constant in VkLoading.yul. + buf_len := common_word(buf_len, 19) + + let instance_cptr := INSTANCE_CPTR + for { let instance_cptr_end := add(instance_cptr, 0x0260) } + lt(instance_cptr, instance_cptr_end) + { instance_cptr := add(instance_cptr, 0x20) } { + let inst_be := calldataload(instance_cptr) + // Public inputs are BLS12-381 scalar-field elements. They + // must be canonical before transcript absorption; accepting + // non-canonical encodings would admit transcript aliases. + success := and(success, lt(inst_be, r)) + // Instances are passed BE in calldata, matching the + // Keccak Fq transcript input. + buf_len := common_word(buf_len, inst_be) + } + if iszero(success) { revert(0, 0) } + } + + // =============================================================== + // Per-user-phase reads + challenge squeezes. + // + // Each proof G1 is already EIP-2537 padded in calldata. The + // verifier validates and absorbs that 128-byte form, then copies + // it into the corresponding per-category MPTR. The PCS / + // quotient-fold blocks below dereference those MPTRs. + // + // All G1 reads follow the same pattern: + // - common_uncompressed_g1 canonicalizes/range-checks the two Fp + // coordinates and appends the exact 128 calldata bytes; + // - calldatacopy stores the same 4-word G1 slot in planned + // memory for later EIP-2537 precompile calls; + // - proof_cptr advances by one G1 byte length. + // =============================================================== + // proof_cptr walks the raw proof bytes inside the ABI `bytes` + // payload. Every successful read advances it exactly once, and the + // final equality check below proves the parser consumed the whole + // generated proof layout. + let proof_cptr := PROOF_CPTR + // advice_walk mirrors proof commitment order into the contiguous + // G1 commitment memory region used by PCS and quotient folding. + let advice_walk := ADVICE_COMMS_MPTR_BASE + // ---- User phase 1 ---- + // Advice commitments for this phase are absorbed before the phase's + // challenge squeezes. The number of commitments and challenges is + // generated from the protocol plan. + for { let end := add(proof_cptr, 0x0780) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + // Store the commitment at its phase-ordered advice slot. + calldatacopy(advice_walk, proof_cptr, 0x80) + advice_walk := add(advice_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- theta ---- + // From this point onward the transcript alternates between + // squeezed challenges and proof commitments exactly as + // midnight-proofs does in `plonk/verifier.rs`. + // theta batches lookup input expressions. + buf_len := squeeze_to(buf_len, THETA_MPTR) + // ---- multiplicities (one G1 per lookup) ---- + // Lookup multiplicity commitments are absorbed after theta and + // copied into their own contiguous G1 region. + let lookup_m_walk := LOOKUP_M_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0100) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_m_walk, proof_cptr, 0x80) + lookup_m_walk := add(lookup_m_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- beta, gamma ---- + // beta and gamma are the permutation/lookup randomizers. They are + // squeezed after lookup multiplicities and before permutation + // product commitments, matching the native verifier schedule. + buf_len := squeeze_to(buf_len, BETA_MPTR) + buf_len := squeeze_to(buf_len, GAMMA_MPTR) + // ---- permutation Z products ---- + // Permutation product commitments are used by the permutation + // identities in the quotient numerator and later by PCS openings. + let perm_z_walk := PERM_Z_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0300) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(perm_z_walk, proof_cptr, 0x80) + perm_z_walk := add(perm_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // ---- lookup helpers + accumulators (per-lookup) ---- + // Each lookup contributes zero or more helper commitments followed + // by its lookup accumulator Z commitment. The generated layout keeps + // helper commitments and accumulator commitments in separate memory + // regions because the quotient/PCS schedules address them + // differently. + let lookup_helper_walk := LOOKUP_HELPER_COMMS_MPTR_BASE + let lookup_z_walk := LOOKUP_Z_COMMS_MPTR_BASE + // lookup 0: 1 helper(s) + 1 acc + // Helper commitments for lookup 0. + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_helper_walk, proof_cptr, 0x80) + lookup_helper_walk := add(lookup_helper_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // Accumulator commitment for lookup 0. This is + // always one G1 when the lookup section is present. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_z_walk, proof_cptr, 0x80) + lookup_z_walk := add(lookup_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + // lookup 1: 1 helper(s) + 1 acc + // Helper commitments for lookup 1. + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_helper_walk, proof_cptr, 0x80) + lookup_helper_walk := add(lookup_helper_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // Accumulator commitment for lookup 1. This is + // always one G1 when the lookup section is present. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_z_walk, proof_cptr, 0x80) + lookup_z_walk := add(lookup_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // ---- trash_challenge ---- + // Midnight squeezes this challenge unconditionally, even when the + // circuit has no trash arguments. + // Keeping this squeeze unconditional preserves transcript + // compatibility across circuits with and without trash columns. + buf_len := squeeze_to(buf_len, TRASH_CHALLENGE_MPTR) + // ---- trashcans ---- + // Trashcan commitments are optional, but when present they are + // absorbed before y so the quotient batching challenge binds them. + let trashcan_walk := TRASHCAN_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(trashcan_walk, proof_cptr, 0x80) + trashcan_walk := add(trashcan_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- y ---- + // y batches all quotient identities. Quotient commitments are read + // only after y is sampled, matching the Rust verifier flow. + buf_len := squeeze_to(buf_len, Y_MPTR) + + // ---- quotient commitment(s) ---- + // Each uncompressed quotient commitment is calldatacopied directly to + // QUOTIENT_LIMB_COMMS_MPTR_BASE; the Horner fold below reads + // them back from memory. common_uncompressed_g1 absorbs the + // 128-byte calldata form into the transcript verbatim. + // + // Multi-limb quotient mode reads several Q_i commitments; single-H + // mode renders this loop with one limb. + let quotient_walk := QUOTIENT_LIMB_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0200) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(quotient_walk, proof_cptr, 0x80) + quotient_walk := add(quotient_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- x ---- + // x is the main evaluation point. Values read after this point are + // alleged polynomial evaluations at x or derived PCS openings. + buf_len := squeeze_to(buf_len, X_MPTR) + + // ---- evaluations ---- + // Optimisation H3: the off-chain Solidity proof shim rewrites + // proof scalars into BE calldata words. Spill each decoded eval + // into REVERSED_EVALS_MPTR in the same iteration we range-check + // it, so downstream references can use cheap mload. + // + // The Rust verifier conceptually reads evaluations in query order. + // The lowering plan arranges REVERSED_EVALS_MPTR in the order used + // by the quotient VM/direct evaluator, hence the generated name. + { + let eval_buf := REVERSED_EVALS_MPTR + for { let end := add(proof_cptr, 0x0cc0) } + lt(proof_cptr, end) + {} { + let eval := calldataload(proof_cptr) + // Proof evaluation scalars must be canonical Fr elements + // before they are absorbed or made available to quotient + // reconstruction. + if iszero(lt(eval, r)) { revert(0, 0) } + // Spill for quotient numerator and PCS codegen. + mstore(eval_buf, eval) + eval_buf := add(eval_buf, 0x20) + // Absorb the exact BE field word used by the native + // Keccak transcript. + buf_len := common_word(buf_len, eval) + proof_cptr := add(proof_cptr, 0x20) + } + } + + // ---- x1, x2 ---- + // x1 and x2 batch the KZG multi-opening reduction. They are + // squeezed after all polynomial evaluations are absorbed. + buf_len := squeeze_to(buf_len, X1_MPTR) + buf_len := squeeze_to(buf_len, X2_MPTR) + + // ---- f_com (1 uncompressed G1) ---- + // f_com is the commitment to the batched polynomial used by the PCS + // multi-open protocol. It is both transcript material and later + // pairing/MSM input. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(F_COM_MPTR, proof_cptr, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // ---- x3 ---- + // x3 is the PCS evaluation point for f_com. + buf_len := squeeze_to(buf_len, X3_MPTR) + // truncated-challenges mirrors midnight-proofs + // proofs/src/poly/kzg/mod.rs: + // - x3 is the f_com evaluation point and is truncated + // immediately after squeeze. + // - x1 and x4 remain full squeezed Fr words, but later PCS + // batching stores truncate(x1^i) and truncate(x4^i) while + // keeping the internal power accumulators full precision. + // This direct x3 mask is therefore one part of the PCS truncation + // rule, not the only truncated value used by the verifier. + mstore(X3_MPTR, and(mload(X3_MPTR), 0xffffffffffffffffffffffffffffffff)) + + // ---- q_evals (one Fq per point set) ---- + // q_evals are not spilled into REVERSED_EVALS_MPTR because the PCS + // emitter reads them as a contiguous calldata range from the saved + // Q_EVAL_CPTR_MPTR cursor. + // + // Each q_eval is the claimed evaluation for one prepared point set + // in the KZG multi-open reduction. They are still transcript + // material and must be range-checked as Fr scalars. + mstore(Q_EVAL_CPTR_MPTR, proof_cptr) + for { let end := add(proof_cptr, 0xa0) } + lt(proof_cptr, end) + {} { + let eval := calldataload(proof_cptr) + // Canonical Fr check before transcript absorption. + if iszero(lt(eval, r)) { revert(0, 0) } + buf_len := common_word(buf_len, eval) + proof_cptr := add(proof_cptr, 0x20) + } + + // ---- x4 ---- + // x4 is the final PCS batching challenge, sampled after q_evals + // and before the opening proof point pi. + buf_len := squeeze_to(buf_len, X4_MPTR) + + // ---- pi (1 uncompressed G1) ---- + // pi is the KZG opening proof commitment. It is the last proof + // object absorbed into the transcript and later becomes one side of + // the final pairing check. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(PI_MPTR, proof_cptr, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // The hand-rolled proof parser must consume exactly the ABI + // `proof` bytes before the `instances` length word. This is + // redundant with the generated proof length today, but makes + // future proof-layout drift fail closed. + // + // NUM_INSTANCE_CPTR is the calldata word immediately after the + // dynamic proof bytes payload. If proof_cptr lands anywhere else, + // some section was under-read or over-read. + if iszero(eq(proof_cptr, NUM_INSTANCE_CPTR)) { revert(0, 0) } + + // `success` carries deferred canonicality failures from public + // instance reads. G1/proof scalar helpers revert immediately. + if iszero(success) { revert(0, 0) } + + // =============================================================== + // Lagrange & instance-evaluation block (pure Fr arithmetic). + // =============================================================== + { + let k := 20 + let x := mload(X_MPTR) + // Compute x^n by repeated squaring, with n = 2^k. + let x_n := x + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { + x_n := mulmod(x_n, x_n, r) + } + + let omega := mload(OMEGA_MPTR) + + // First pass writes denominators (x - omega_i) for every + // Lagrange value needed below, then appends x^n - 1. The + // batch inversion pass turns all of them into inverses in one + // modexp call. + let mptr := X_N_MPTR + let mptr_end := add(mptr, 0x03a0) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } + lt(mptr, mptr_end) + { mptr := add(mptr, 0x20) } { + mstore(mptr, addmod(x, sub(r, pow_of_omega), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + let x_n_minus_1 := addmod(x_n, sub(r, 1), r) + mstore(mptr_end, x_n_minus_1) + success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20), BATCH_INV_SCRATCH_MPTR, r) + + // Convert inverted denominators into Lagrange evaluations: + // L_i(x) = (x^n - 1) * n^-1 * omega_i / (x - omega_i). + mptr := X_N_MPTR + let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } + lt(mptr, mptr_end) + { mptr := add(mptr, 0x20) } { + mstore(mptr, mulmod(l_i_common, mulmod(mload(mptr), pow_of_omega, r), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + + // l_blind is the sum of the negative-rotation Lagrange terms + // used by the midnight-proofs blinding identity. + let l_blind := mload(add(X_N_MPTR, 0x20)) + let l_i_cptr := add(X_N_MPTR, 0x40) + for { let l_i_cptr_end := add(X_N_MPTR, 0x0140) } + lt(l_i_cptr, l_i_cptr_end) + { l_i_cptr := add(l_i_cptr, 0x20) } { + l_blind := addmod(l_blind, mload(l_i_cptr), r) + } + + // Public instance polynomial evaluation at x. Instance words + // have already been range-checked and absorbed in transcript + // order; this loop only forms the linear combination. + let instance_eval := 0 + for { + let instance_cptr := INSTANCE_CPTR + let instance_cptr_end := add(instance_cptr, 0x0260) + } + lt(instance_cptr, instance_cptr_end) + { instance_cptr := add(instance_cptr, 0x20) + l_i_cptr := add(l_i_cptr, 0x20) } { + instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r) + } + + // Persist the derived values into named memory slots consumed + // by quotient reconstruction and PCS preparation. + let x_n_minus_1_inv := mload(mptr_end) + let l_last := mload(X_N_MPTR) + let l_0 := mload(add(X_N_MPTR, 0x0140)) + + mstore(X_N_MPTR, x_n) + mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv) + mstore(L_LAST_MPTR, l_last) + mstore(L_BLIND_MPTR, l_blind) + mstore(L_0_MPTR, l_0) + mstore(INSTANCE_EVAL_MPTR, instance_eval) + } + + if iszero(success) { revert(0, 0) } + + // Optional quotient helper functions. Each one is rendered only + // when the Rust lowering pass recognized the corresponding + // expression shape in this generated verifier. They are pure Fr + // helpers and share the same FR_MODULUS as the surrounding + // numerator block. + // VK-specialized identity helper for Poseidon S-box terms. + // + // Rust source shape: + // circuits/src/hash/poseidon/poseidon_chip.rs::sbox + // full_round_gate / partial_round_gate + // circuits/src/hash/poseidon/round_skips.rs::RoundId + // + // The Rust verifier only sees this as an Expression tree from + // `vk.cs.gates`; the generator emits q_pow5 after recognizing five + // equal multiplicative factors. It is a codegen shortcut for x^5, + // not a separate verifier rule. + function q_pow5(x) -> z { + let q_r := FR_MODULUS + let x2 := mulmod(x, x, q_r) + z := mulmod(x, mulmod(x2, x2, q_r), q_r) + } // =============================================================== + // Batched identity numerator / linearization target. + // + // This block does not evaluate the quotient polynomial h(x), and + // the proof does not provide an h(x) scalar to trust. Instead it: + // + // 1. Reconstructs the y-batched constraint numerator nu_y(x) + // from the alleged polynomial evaluations read after the + // transcript sampled x. + // 2. Stores -nu_y(x) as the expected opening scalar for the + // linearized commitment. + // + // The commitment side is built in the next block from the quotient + // limb commitments as (1 - x^n) * Σ_i x_split^i * Q_i, plus any + // simple-selector commitments. The PCS check later binds that + // linearized commitment to this expected scalar at x. + // + // Rust source-of-truth: + // - verifier.rs reads quotient commitments, samples x, then + // reads/computes all evaluations used below. + // - mod.rs::partially_evaluate_identities returns identities in + // gate, permutation, lookup, trash order. + // - linearization/verifier.rs::compute_linearization_commitment + // reverse-folds those identities by powers of y, sends + // simple-selector identities to selector commitment scalars, + // and subtracts fully-evaluated identities into expected_eval. + // + // This template is shared by the monolithic and external quotient + // paths. In the external path, Halo2QuotientEvaluator first copies + // the verifier memory frame into the same generated addresses. + // + // Runtime inputs expected to exist before this block starts: + // - `r` is the BLS12-381 scalar-field modulus. + // - Y_MPTR holds the quotient batching challenge y. + // - X_MPTR, L_*_MPTR, INSTANCE_EVAL_MPTR, and + // REVERSED_EVALS_MPTR hold values parsed or derived by the + // main verifier after the transcript sampled x. + // - VK_MPTR holds the pinned VK payload; in compact mode that + // payload includes the quotient constant table and bytecode. + // + // Runtime outputs written by this block: + // - QUOTIENT_EVAL_MPTR receives the scalar expected opening for + // the linearized commitment, namely -nu_y(x). + // - SELECTOR_ACC_MPTR[0..num_simple_selectors) receives one + // linearization scalar per generated simple selector. + // + // Line-by-line reading conventions used below: + // + // * Every runtime value is one canonical Fr element stored in a + // 256-bit EVM memory word. The small integer operands decoded + // from q_program are never field values; they are pointers, + // constant-table slots, selector indexes, offsets, or counts. + // + // * `mload(ptr)` is the only way the VM turns a small pointer + // operand into a real 255-bit field element. The value loaded + // from memory is then combined with `addmod(..., r)` or + // `mulmod(..., r)`, so every arithmetic line is reduced modulo + // the BLS12-381 scalar-field order. + // + // * `q_top` is the cached top of the VM operand stack. When an + // opcode needs to push while `q_top` is already live, the old + // value is written to `q_sp` and `q_sp` is advanced by one + // word. Binary `ADD`/`MUL` move `q_sp` back by one word and + // combine that spilled value with `q_top`. + // + // * Identity boundaries are explicit. Expression opcodes leave + // one value in `q_top`; `FOLD_MAIN` or `FOLD_SELECTOR` consumes + // it and advances the global y-batch position. Native callback + // opcodes are only emitted at empty-stack boundaries and run + // generated Yul that performs the same fold side effects. + // + // * The generated Solidity source intentionally emits comments + // before opcode cases. Those comments are documentation only: + // they do not affect bytecode, but they make rendered verifier + // assembly readable without jumping back to Rust codegen. + // =============================================================== + { + // Compact quotient-program mode. + // + // The largest identity expressions are not all emitted as + // unrolled Yul. Instead, most arithmetic is encoded as a small + // q_program bytecode stored in the VK payload. This block + // interprets that program, while selected heavy identities may + // still be emitted as native callbacks for gas. + // + // Compact mode is a code-size trade: short bytecode operands + // name already-planned memory slots, and the interpreter turns + // those names into Fr arithmetic. The opcode stream is fully + // generated and pinned by the VK/runtime codehash; no proof + // calldata can alter control flow. + // Load the quotient batching challenge used by every fold. + let y := mload(Y_MPTR) + + // q_const_mptr points to Fr constants used by the VM. + // q_program_mptr points to the bytecode stream. + // Constants are stored as consecutive 32-byte Fr words. + let q_const_mptr := 0x3a60 + // Program bytes are also stored in the VK payload, packed into + // 32-byte words by PackedProgramCodec. + let q_program_mptr := 0x50a0 + // Running Horner accumulator for fully evaluated identities. + // After all identities, this is nu_y(x) for the `None` + // identity group. + // Initialize A = 0 before scanning the identity stream. + mstore(0xb280, 0) + // Simple selectors are grouped into separate linearization + // buckets. They start at zero for every proof. + // q_sel_zero_off walks selector bucket byte offsets. + for { let q_sel_zero_off := 0 } lt(q_sel_zero_off, 0x0140) { q_sel_zero_off := add(q_sel_zero_off, 0x20) } { + // B_s = 0 for each simple selector bucket. + mstore(add(SELECTOR_ACC_MPTR, q_sel_zero_off), 0) + } + // Codegen knows the selector identity positions. Precompute + // the y^k powers needed for selector gap and tail updates, + // avoiding a runtime y^-1 modexp and per-identity selector + // scale maintenance. + { + // q_y_power holds y^i at the current loop index. + let q_y_power := 1 + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0xb2c0, 1) + // Start at i=1 because y^0 = 1 is written above. + for { let q_y_power_i := 1 } lt(q_y_power_i, 49) { q_y_power_i := add(q_y_power_i, 1) } { + // Advance from y^(i-1) to y^i modulo Fr. + q_y_power := mulmod(q_y_power, y, r) + // Store y^i at selector_power_mptr + 32*i. + mstore(add(0xb2c0, shl(5, q_y_power_i)), q_y_power) + } + } + + // Direct inline prefix. These identities are generated as Yul + // before entering the VM. They use the same fold snippets as + // VM/native identities, so they occupy the same y-batch order. + { + let var0 := 0x1 + let f_3 := mload(0x9ac0) + let f_4 := mload(0x99c0) + let a_0 := mload(0x94a0) + let var1 := mulmod(f_4, a_0, r) + let var2 := addmod(f_3, var1, r) + let f_5 := mload(0x99e0) + let a_1 := mload(0x94c0) + let var3 := mulmod(f_5, a_1, r) + let var4 := addmod(var2, var3, r) + let f_6 := mload(0x9a00) + let a_2 := mload(0x94e0) + let var5 := mulmod(f_6, a_2, r) + let var6 := addmod(var4, var5, r) + let f_7 := mload(0x9a20) + let a_3 := mload(0x9500) + let var7 := mulmod(f_7, a_3, r) + let var8 := addmod(var6, var7, r) + let f_8 := mload(0x9a40) + let a_4 := mload(0x9520) + let var9 := mulmod(f_8, a_4, r) + let var10 := addmod(var8, var9, r) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var11 := mulmod(f_0, a_0_next_1, r) + let var12 := addmod(var10, var11, r) + let f_1 := mload(0x9a80) + let var13 := mulmod(f_1, a_0, r) + let var14 := mulmod(var13, a_1, r) + let var15 := addmod(var12, var14, r) + let f_2 := mload(0x9aa0) + let var16 := mulmod(f_2, a_0, r) + let var17 := mulmod(var16, a_2, r) + let var18 := addmod(var15, var17, r) + let var19 := mulmod(var0, var18, r) + mstore(0xb8e0, var19) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_1 := mload(0x94c0) + let a_2 := mload(0x94e0) + let var1 := addmod(a_1, a_2, r) + let a_3 := mload(0x9500) + let var2 := addmod(0, sub(r, a_3), r) + let var3 := addmod(var1, var2, r) + let a_4 := mload(0x9520) + let var4 := addmod(0, sub(r, a_4), r) + let var5 := addmod(var3, var4, r) + let var6 := mulmod(var0, var5, r) + mstore(0xb8e0, var6) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let f_4 := mload(0x99c0) + let var1 := addmod(a_0, f_4, r) + let a_0_next_1 := mload(0x9540) + let var2 := addmod(0, sub(r, a_0_next_1), r) + let var3 := addmod(var1, var2, r) + let var4 := mulmod(var0, var3, r) + mstore(0xb8e0, var4) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_1 := mload(0x94c0) + let f_5 := mload(0x99e0) + let var1 := addmod(a_1, f_5, r) + let a_1_next_1 := mload(0x9560) + let var2 := addmod(0, sub(r, a_1_next_1), r) + let var3 := addmod(var1, var2, r) + let var4 := mulmod(var0, var3, r) + mstore(0xb8e0, var4) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + + // VM registers: + // q_pc current bytecode pointer + // q_end end of bytecode stream + // q_sp memory stack pointer for non-top stack values + // q_top cached top-of-stack value + // q_has_top whether q_top currently holds a stack value + // + // The cached top reduces memory traffic in the interpreter. + // q_sp's registered range must cover the interpreted operand + // stack plus any native callback scratch that reuses this base + // pointer. In particular, the native permutation callback + // writes a structured scratch table at program.stack_mptr. + // q_pc starts at the first encoded instruction. + let q_pc := q_program_mptr + // q_end is an exclusive byte pointer for the VM loop. + let q_end := add(q_program_mptr, 0x11cf) + // q_sp starts at the first free stack word. + let q_sp := 0xb8e0 + // q_top is meaningless until q_has_top is set. + let q_top := 0 + // q_has_top = 0 means the VM stack is empty. + let q_has_top := 0 + + // q_program opcode summary: + // 0x01/0x09 push const 0x02/0x05 push memory + // 0x03/0x04 push token ptr 0x06 add, 0x07 mul, 0x08 neg + // 0x0a fold main identity 0x0b fold selector identity + // 0x0c..0x11 add/mul const or memory into top + // 0x12..0x16 fused add-mul runs + // 0x17/0x18 reserved + // 0x19 native permutation 0x1b native heavy identity + // 0x1c LIN7 0x1d BILIN7_ROW + // 0x1e BILIN7_PAIRWISE 0x1f native lookup + // 0x20 POW5 0x21 MODARITH7 + // 0x22 AFFINE_SUM + // + // The default IVC verifier uses one physical encoding for the + // logical VM: compact byte-oriented opcodes with variable-width + // operands, dynamic runs, and limb-aware cases. + + // Byte-oriented encoding: opcodes are one byte followed by + // variable-width operand bytes. + for { } lt(q_pc, q_end) { } { + // The bytecode table is byte-addressed, but EVM memory + // loads whole words. `byte(0, mload(q_pc))` extracts the + // opcode at the current byte cursor; each case advances + // q_pc by exactly its operand width. + let q_op := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + + switch q_op + // VM 0x05 PUSH_MEM_U16 (bytes): next two bytes are a short memory pointer. + case 0x05 { + // Operand layout: u16 absolute memory pointer. The + // memory planner keeps the hot quotient frame below + // 64 KiB when this compact form is emitted. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + if q_has_top { + mstore(q_sp, q_top) + q_sp := add(q_sp, 0x20) + } + q_top := mload(q_ptr) + q_has_top := 1 + } + // VM 0x06 ADD: pop one spilled stack word and add it to q_top. + case 0x06 { + // The safety validator guarantees a spilled operand + // exists before ADD. q_top is the right operand. + q_sp := sub(q_sp, 0x20) + q_top := addmod(mload(q_sp), q_top, r) + } + // VM 0x08 NEG: replace q_top with its Fr negation. + case 0x08 { + // addmod(0, r - x, r) maps zero back to zero and every + // nonzero scalar to its canonical additive inverse. + q_top := addmod(0, sub(r, q_top), r) + } + // VM 0x0d MUL_CONST_U8: multiply q_top by a small constant-table slot. + case 0x0d { + // One-byte constant-index multiply, used by short + // affine chains after an initial PUSH. + let qconst := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + q_top := mulmod(q_top, mload(add(q_const_mptr, shl(5, qconst))), r) + } + // VM 0x10 ADD_MEM_U16: add a short memory load into q_top. + case 0x10 { + // Operand layout: u16 pointer. The pointed word is an + // already range-checked Fr scalar in verifier memory. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + q_top := addmod(q_top, mload(q_ptr), r) + } + // VM 0x11 MUL_MEM_U16: multiply q_top by a short memory load. + case 0x11 { + // In-place multiply by a planned memory word. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + q_top := mulmod(q_top, mload(q_ptr), r) + } + // Limb-aware opcodes are opt-in compact forms for + // structurally recognized non-SHA foreign-field shapes. + // Coefficients are indexes into q_const_mptr, which is + // generated from VK/program data, never from proof + // calldata. + // + // Rust source shape: + // proofs/src/plonk/mod.rs::partially_evaluate_identities + // circuits/src/field/foreign/util.rs::{sum_exprs,pair_wise_prod} + // circuits/src/field/foreign/params.rs::{base_powers,double_base_powers} + // + // "Foreign field" means the circuit represents elements + // modulo another modulus m as 7 limbs in base + // 2^LOG2_BASE. The verifier does not switch fields; it + // evaluates the lowered identity over BLS12-381 Fr, using + // Fr coefficients equal to base^i mod m or base^(i+j) mod m. + // VM 0x21 MODARITH7: byte-only fused affine 7-limb foreign-field/ECC identity. + case 0x21 { + // MODARITH7: + // maybe_cond * ( + // c + // + sum LIN7 blocks + // + sum BILIN7_ROW blocks + // + sum BILIN7_PAIRWISE blocks + // + sum coeff[k] * mload(ptr[k]) + // + sum coeff[k] * mload(lhs[k]) * mload(rhs[k]) + // ) + // It is a dispatch/operand-load optimization only; + // all coefficients still come from the generated + // quotient constant table. + // + // Flags: + // bit 0: multiply the final affine sum by a memory + // condition word. + // bit 1: seed q_acc from a constant-table word + // before reading the counted term blocks. + let q_flags := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + let q_cond_ptr := 0 + if and(q_flags, 0x01) { + // Optional condition pointer. When present, the + // whole identity is gated by mload(q_cond_ptr). + q_cond_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + } + + let q_acc := 0 + if and(q_flags, 0x02) { + // Optional constant seed for affine identities + // with a standalone constant term. + let qconst := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + q_acc := mload(add(q_const_mptr, shl(5, qconst))) + } + + // Five one-byte counters describe the blocks that + // follow. Each block has a fixed-width internal layout, + // so q_pc can advance without per-term tags. + let q_counts_word := mload(q_pc) + let q_lin_count := byte(0, q_counts_word) + let q_row_count := byte(1, q_counts_word) + let q_pairwise_count := byte(2, q_counts_word) + let q_mem_count := byte(3, q_counts_word) + let q_product_count := byte(4, q_counts_word) + q_pc := add(q_pc, 5) + + if q_has_top { + mstore(q_sp, q_top) + q_sp := add(q_sp, 0x20) + } + + // LIN7 blocks: q_acc += sum_i c_i * limb_i. + for { let q_lin_block := 0 } lt(q_lin_block, q_lin_count) { q_lin_block := add(q_lin_block, 1) } { + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_ptr := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod(mload(add(q_const_mptr, shl(5, qconst))), mload(q_ptr), r), + r + ) + } + } + + // BILIN7_ROW blocks: q_acc += lhs * sum_i c_i * rhs_i. + for { let q_row_block := 0 } lt(q_row_block, q_row_count) { q_row_block := add(q_row_block, 1) } { + let q_lhs := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + let q_lhs_value := mload(q_lhs) + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_rhs := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod( + mulmod(q_lhs_value, mload(q_rhs), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + } + + // BILIN7_PAIRWISE blocks: q_acc += weighted 7-by-7 + // product convolution. + for { let q_pair_block := 0 } lt(q_pair_block, q_pairwise_count) { q_pair_block := add(q_pair_block, 1) } { + let q_pair_word := mload(q_pc) + let q_lhs_base := shr(240, q_pair_word) + let q_rhs_base := and(shr(224, q_pair_word), 0xffff) + q_pc := add(q_pc, 0x04) + let q_coeff_pc := q_pc + q_pc := add(q_pc, 13) + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_lhs_value := mload(add(q_lhs_base, shl(5, q_i))) + for { let q_j := 0 } lt(q_j, 7) { q_j := add(q_j, 1) } { + let qconst := byte(0, mload(add(q_coeff_pc, add(q_i, q_j)))) + q_acc := addmod( + q_acc, + mulmod( + mulmod(q_lhs_value, mload(add(q_rhs_base, shl(5, q_j))), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + } + } + + // Extra linear memory terms outside the 7-limb shapes. + for { let q_mem_block := 0 } lt(q_mem_block, q_mem_count) { q_mem_block := add(q_mem_block, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_ptr := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod(mload(add(q_const_mptr, shl(5, qconst))), mload(q_ptr), r), + r + ) + } + + // Extra binary product terms outside the 7-limb shapes. + for { let q_product_block := 0 } lt(q_product_block, q_product_count) { q_product_block := add(q_product_block, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_lhs := and(shr(232, q_word), 0xffff) + let q_rhs := and(shr(216, q_word), 0xffff) + q_pc := add(q_pc, 5) + q_acc := addmod( + q_acc, + mulmod( + mulmod(mload(q_lhs), mload(q_rhs), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + + if and(q_flags, 0x01) { + // Apply the optional gate condition last so every + // subterm shares the same selector/condition. + q_acc := mulmod(mload(q_cond_ptr), q_acc, r) + } + // MODARITH7 pushes its fused identity value. + q_top := q_acc + q_has_top := 1 + } + // Native permutation callback. It evaluates the + // permutation identities from permutation.rs at this exact + // VM position, preserving the Rust identity order while + // avoiding a large interpreted product loop. + // VM 0x19 NATIVE_PERMUTATION: marker for the generated permutation callback. + case 0x19 { + // Native callbacks are identity-boundary opcodes. They + // must not inherit any partially evaluated VM stack + // state from the previous expression. + q_top := 0 + q_has_top := 0 + // The generated loop below uses program.stack_mptr as + // its scratch-table base, not as a conventional VM + // stack. The Rust memory planner must reserve enough + // words for structured_permutation_scratch_words(meta) + // whenever this opcode can appear. + q_sp := 0xb8e0 + // The generated lines below call the same fold snippets + // used by interpreted expressions, so trace IDs and + // y-batch positions remain contiguous. + { + let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 + let q_perm_vals := 0xb8e0 + let q_perm_sigmas := 0xbb20 + let q_perm_z_cur := 0xbd60 + let q_perm_z_next := 0xbe20 + let q_perm_z_last := 0xbee0 + let q_perm_delta_base_ptr := 0xbf80 + let q_perm_num_cols := 18 + let q_perm_num_sets := 6 + let q_perm_chunk_len := 3 + let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 + mstore(add(q_perm_vals, 0x0), mload(0x99a0)) + { + for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { + let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) + let q_perm_val_load_src_off := q_perm_val_load_dst_off + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x94a0, q_perm_val_load_src_off))) + } + } + mstore(add(q_perm_vals, 0xc0), mload(0x9480)) + mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) + { + for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 9) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { + let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) + let q_perm_val_load_src_off := q_perm_val_load_dst_off + mstore(add(add(q_perm_vals, 0x100), q_perm_val_load_dst_off), mload(add(0x95a0, q_perm_val_load_src_off))) + } + } + mstore(add(q_perm_vals, 0x220), mload(0x9980)) + { + for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 18) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { + let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) + let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x9bc0, q_perm_sigma_load_src_off))) + } + } + { + for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 6) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { + let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) + let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x9e00, q_perm_z_cur_load_src_off))) + } + } + { + for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 6) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { + let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) + let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x9e20, q_perm_z_next_load_src_off))) + } + } + { + for { let q_perm_z_last_load_i := 0 } lt(q_perm_z_last_load_i, 5) { q_perm_z_last_load_i := add(q_perm_z_last_load_i, 1) } { + let q_perm_z_last_load_dst_off := shl(5, q_perm_z_last_load_i) + let q_perm_z_last_load_src_off := mul(q_perm_z_last_load_i, 0x60) + mstore(add(add(q_perm_z_last, 0x0), q_perm_z_last_load_dst_off), mload(add(0x9e40, q_perm_z_last_load_src_off))) + } + } + let q_perm_eval := 0 + q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + let q_perm_zn := mload(add(q_perm_z_cur, 0xa0)) + q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + for { let q_perm_i := 1 } lt(q_perm_i, 6) { q_perm_i := add(q_perm_i, 1) } { + let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) + let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) + q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + } + mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) + for { let q_perm_set := 0 } lt(q_perm_set, 6) { q_perm_set := add(q_perm_set, 1) } { + let q_perm_start := mul(q_perm_set, q_perm_chunk_len) + let q_perm_end := add(q_perm_start, q_perm_chunk_len) + if gt(q_perm_end, q_perm_num_cols) { q_perm_end := q_perm_num_cols } + let q_perm_left := mload(add(q_perm_z_next, shl(5, q_perm_set))) + let q_perm_right := mload(add(q_perm_z_cur, shl(5, q_perm_set))) + let q_perm_delta_pow := mload(q_perm_delta_base_ptr) + for { let q_perm_j := q_perm_start } lt(q_perm_j, q_perm_end) { q_perm_j := add(q_perm_j, 1) } { + let q_perm_off := shl(5, q_perm_j) + let q_perm_v := mload(add(q_perm_vals, q_perm_off)) + let q_perm_s := mload(add(q_perm_sigmas, q_perm_off)) + q_perm_left := mulmod(q_perm_left, addmod(addmod(q_perm_v, mulmod(mload(BETA_MPTR), q_perm_s, r), r), mload(GAMMA_MPTR), r), r) + q_perm_right := mulmod(q_perm_right, addmod(addmod(q_perm_v, q_perm_delta_pow, r), mload(GAMMA_MPTR), r), r) + q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) + } + q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) + } + } + } + // Native lookup callback. This whole-family opcode + // evaluates the LogUp boundary, helper-chunk, and + // accumulator identities at this VM position, preserving + // the Rust y-batch order while avoiding many interpreted + // product-loop opcodes. + // VM 0x1f NATIVE_LOOKUP: marker for the generated LogUp lookup callback. + case 0x1f { + // Reset VM stack state before entering structured + // lookup Yul. Lookup callbacks own their scratch + // layout and perform all needed folds internally. + q_top := 0 + q_has_top := 0 + // The generated loop below uses program.stack_mptr as + // f+beta/prefix/suffix scratch rather than as a + // conventional VM stack. The Rust memory planner must + // reserve structured_lookup_scratch_words(meta). + q_sp := 0xb8e0 + // Generated LogUp code follows the same y-batch order + // as the Rust identity stream. + { + let q_lookup_f := 0xb8e0 + let q_lookup_prefix := 0xb960 + let q_lookup_suffix := 0xb9e0 + let q_lookup_l0 := mload(L_0_MPTR) + let q_lookup_llast := mload(L_LAST_MPTR) + let q_lookup_lblind := mload(L_BLIND_MPTR) + let q_lookup_lsum := addmod(q_lookup_l0, q_lookup_llast, r) + let q_lookup_active := addmod(1, sub(r, addmod(q_lookup_llast, q_lookup_lblind, r)), r) + let q_lookup_beta := mload(BETA_MPTR) + let q_lookup_theta := mload(THETA_MPTR) + { + { + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa060), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let f_10 := mload(0x9ae0) + let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) + let var1 := mulmod(var0, q_lookup_theta, r) + for { let q_lookup_shared_i := 0 } lt(q_lookup_shared_i, 4) { q_lookup_shared_i := add(q_lookup_shared_i, 1) } { + let q_lookup_shared_off := shl(5, q_lookup_shared_i) + let q_lookup_shared_tail := mload(add(0x94c0, q_lookup_shared_off)) + let q_lookup_shared_compressed := addmod(var1, q_lookup_shared_tail, r) + mstore(add(q_lookup_f, q_lookup_shared_off), addmod(q_lookup_shared_compressed, q_lookup_beta, r)) + } + let q_lookup_product := 1 + for { let q_lookup_prod_i := 0 } lt(q_lookup_prod_i, 4) { q_lookup_prod_i := add(q_lookup_prod_i, 1) } { + q_lookup_product := mulmod(q_lookup_product, mload(add(q_lookup_f, shl(5, q_lookup_prod_i))), r) + } + mstore(q_lookup_prefix, 1) + for { let q_lookup_pref_i := 1 } lt(q_lookup_pref_i, 4) { q_lookup_pref_i := add(q_lookup_pref_i, 1) } { + let q_lookup_pref_prev := sub(q_lookup_pref_i, 1) + mstore(add(q_lookup_prefix, shl(5, q_lookup_pref_i)), mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_pref_prev))), mload(add(q_lookup_f, shl(5, q_lookup_pref_prev))), r)) + } + mstore(add(q_lookup_suffix, 0x60), 1) + for { let q_lookup_suf_i := sub(4, 1) } gt(q_lookup_suf_i, 0) { q_lookup_suf_i := sub(q_lookup_suf_i, 1) } { + let q_lookup_suf_prev := sub(q_lookup_suf_i, 1) + mstore(add(q_lookup_suffix, shl(5, q_lookup_suf_prev)), mulmod(mload(add(q_lookup_suffix, shl(5, q_lookup_suf_i))), mload(add(q_lookup_f, shl(5, q_lookup_suf_i))), r)) + } + let q_lookup_sum := 0 + for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 4) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { + q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) + } + let q_lookup_eval := addmod(mulmod(mload(0xa040), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let q_lookup_sum_h := mload(0xa040) + let f_17 := mload(0x9b60) + let f_11 := mload(0x9b00) + let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) + let f_12 := mload(0x9b20) + let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) + let q_lookup_s_sum_h := mulmod(f_17, q_lookup_sum_h, r) + let q_lookup_diff := addmod(mload(0xa080), sub(r, addmod(mload(0xa060), q_lookup_s_sum_h, r)), r) + let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa020), r) + let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + } + { + { + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa0e0), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let a_14 := mload(0x9980) + let var0 := addmod(mulmod(0, q_lookup_theta, r), a_14, r) + let a_0 := mload(0x94a0) + let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_0, r) + let a_1 := mload(0x94c0) + let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_1, r) + let a_2 := mload(0x94e0) + let var3 := addmod(mulmod(var2, q_lookup_theta, r), a_2, r) + let a_3 := mload(0x9500) + let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_3, r) + let a_4 := mload(0x9520) + let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_4, r) + let a_5 := mload(0x95a0) + let var6 := addmod(mulmod(var5, q_lookup_theta, r), a_5, r) + let a_6 := mload(0x95c0) + let var7 := addmod(mulmod(var6, q_lookup_theta, r), a_6, r) + let a_7 := mload(0x95e0) + let var8 := addmod(mulmod(var7, q_lookup_theta, r), a_7, r) + let a_8 := mload(0x9600) + let var9 := addmod(mulmod(var8, q_lookup_theta, r), a_8, r) + let a_9 := mload(0x9620) + let var10 := addmod(mulmod(var9, q_lookup_theta, r), a_9, r) + let a_10 := mload(0x9640) + let var11 := addmod(mulmod(var10, q_lookup_theta, r), a_10, r) + let a_11 := mload(0x9660) + let var12 := addmod(mulmod(var11, q_lookup_theta, r), a_11, r) + let a_12 := mload(0x9680) + let var13 := addmod(mulmod(var12, q_lookup_theta, r), a_12, r) + let a_13 := mload(0x96a0) + let var14 := addmod(mulmod(var13, q_lookup_theta, r), a_13, r) + let f_13 := mload(0x9b40) + let var15 := addmod(mulmod(var14, q_lookup_theta, r), f_13, r) + let q_lookup_eval := addmod(mulmod(mload(0xa0c0), addmod(var15, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let q_lookup_sum_h := mload(0xa0c0) + let var0 := 0x1 + let f_24 := mload(0x9b80) + let var1 := addmod(0, sub(r, f_24), r) + let var2 := addmod(var0, var1, r) + let a_14 := mload(0x9980) + let var3 := mulmod(var2, a_14, r) + let var4 := addmod(mulmod(0, q_lookup_theta, r), var3, r) + let a_0 := mload(0x94a0) + let var5 := mulmod(var2, a_0, r) + let var6 := addmod(mulmod(var4, q_lookup_theta, r), var5, r) + let a_1 := mload(0x94c0) + let var7 := mulmod(var2, a_1, r) + let var8 := addmod(mulmod(var6, q_lookup_theta, r), var7, r) + let a_2 := mload(0x94e0) + let var9 := mulmod(var2, a_2, r) + let var10 := addmod(mulmod(var8, q_lookup_theta, r), var9, r) + let a_3 := mload(0x9500) + let var11 := mulmod(var2, a_3, r) + let var12 := addmod(mulmod(var10, q_lookup_theta, r), var11, r) + let a_4 := mload(0x9520) + let var13 := mulmod(var2, a_4, r) + let var14 := addmod(mulmod(var12, q_lookup_theta, r), var13, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var2, a_5, r) + let var16 := addmod(mulmod(var14, q_lookup_theta, r), var15, r) + let a_6 := mload(0x95c0) + let var17 := mulmod(var2, a_6, r) + let var18 := addmod(mulmod(var16, q_lookup_theta, r), var17, r) + let a_7 := mload(0x95e0) + let var19 := mulmod(var2, a_7, r) + let var20 := addmod(mulmod(var18, q_lookup_theta, r), var19, r) + let a_8 := mload(0x9600) + let var21 := mulmod(var2, a_8, r) + let var22 := addmod(mulmod(var20, q_lookup_theta, r), var21, r) + let a_9 := mload(0x9620) + let var23 := mulmod(var2, a_9, r) + let var24 := addmod(mulmod(var22, q_lookup_theta, r), var23, r) + let a_10 := mload(0x9640) + let var25 := mulmod(var2, a_10, r) + let var26 := addmod(mulmod(var24, q_lookup_theta, r), var25, r) + let a_11 := mload(0x9660) + let var27 := mulmod(var2, a_11, r) + let var28 := addmod(mulmod(var26, q_lookup_theta, r), var27, r) + let a_12 := mload(0x9680) + let var29 := mulmod(var2, a_12, r) + let var30 := addmod(mulmod(var28, q_lookup_theta, r), var29, r) + let a_13 := mload(0x96a0) + let var31 := mulmod(var2, a_13, r) + let var32 := addmod(mulmod(var30, q_lookup_theta, r), var31, r) + let f_13 := mload(0x9b40) + let var33 := mulmod(var2, f_13, r) + let var34 := addmod(mulmod(var32, q_lookup_theta, r), var33, r) + let q_lookup_s_sum_h := mulmod(var0, q_lookup_sum_h, r) + let q_lookup_diff := addmod(mload(0xa100), sub(r, addmod(mload(0xa0e0), q_lookup_s_sum_h, r)), r) + let q_lookup_t_beta := addmod(var34, q_lookup_beta, r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa0a0), r) + let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + } + } + } + // Native callbacks are generated only for the heaviest + // recognized Midfall gate identities. All other gate and + // non-native identity arithmetic remains in + // the compact q_program VM above, preserving the Rust + // `partially_evaluate_identities` order. + // VM 0x1b NATIVE_IDENTITY: marker for generated heavy-gate callbacks. + case 0x1b { + // Operand layout: u16 native callback index. The + // manifest validates that callback indexes appear in + // generated order and target existing switch cases. + let q_native_idx := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + // Heavy identities are whole expressions, so clear the + // interpreter stack before dispatching. + q_top := 0 + q_has_top := 0 + q_sp := 0xb8e0 + // Native identity sub-cases are generated from selected heavy gate identities. + switch q_native_idx + case 0 { + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let a_0_next_1 := mload(0x9540) + let var1 := mulmod(a_0, a_0_next_1, r) + let var2 := 0x100000000000000 + let a_1_next_1 := mload(0x9560) + let var3 := mulmod(a_0, a_1_next_1, r) + let var4 := mulmod(var2, var3, r) + let var5 := addmod(var1, var4, r) + let var6 := 0x10000000000000000000000000000 + let a_2_next_1 := mload(0x9580) + let var7 := mulmod(a_0, a_2_next_1, r) + let var8 := mulmod(var6, var7, r) + let var9 := addmod(var5, var8, r) + let a_1 := mload(0x94c0) + let var10 := mulmod(a_1, a_0_next_1, r) + let var11 := mulmod(var2, var10, r) + let var12 := addmod(var9, var11, r) + let var13 := mulmod(a_1, a_1_next_1, r) + let var14 := mulmod(var6, var13, r) + let var15 := addmod(var12, var14, r) + let var16 := 0x3212e00cde6d2002b119d800000347fcb8 + let a_6_next_1 := mload(0x9720) + let var17 := mulmod(a_1, a_6_next_1, r) + let var18 := mulmod(var16, var17, r) + let var19 := addmod(var15, var18, r) + let a_2 := mload(0x94e0) + let var20 := mulmod(a_2, a_0_next_1, r) + let var21 := mulmod(var6, var20, r) + let var22 := addmod(var19, var21, r) + let a_5_next_1 := mload(0x9700) + let var23 := mulmod(a_2, a_5_next_1, r) + let var24 := mulmod(var16, var23, r) + let var25 := addmod(var22, var24, r) + let var26 := 0x297784894e27525bc342b7fde37dba9366 + let var27 := mulmod(a_2, a_6_next_1, r) + let var28 := mulmod(var26, var27, r) + let var29 := addmod(var25, var28, r) + let a_3 := mload(0x9500) + let a_4_next_1 := mload(0x96e0) + let var30 := mulmod(a_3, a_4_next_1, r) + let var31 := mulmod(var16, var30, r) + let var32 := addmod(var29, var31, r) + let var33 := mulmod(a_3, a_5_next_1, r) + let var34 := mulmod(var26, var33, r) + let var35 := addmod(var32, var34, r) + let var36 := 0x340f2ebe380a0f5eff4360543988a61dc2 + let var37 := mulmod(a_3, a_6_next_1, r) + let var38 := mulmod(var36, var37, r) + let var39 := addmod(var35, var38, r) + let a_4 := mload(0x9520) + let a_3_next_1 := mload(0x96c0) + let var40 := mulmod(a_4, a_3_next_1, r) + let var41 := mulmod(var16, var40, r) + let var42 := addmod(var39, var41, r) + let var43 := mulmod(a_4, a_4_next_1, r) + let var44 := mulmod(var26, var43, r) + let var45 := addmod(var42, var44, r) + let var46 := mulmod(a_4, a_5_next_1, r) + let var47 := mulmod(var36, var46, r) + let var48 := addmod(var45, var47, r) + let var49 := 0x13af65741744bd7bb2c6872df2b800320 + let var50 := mulmod(a_4, a_6_next_1, r) + let var51 := mulmod(var49, var50, r) + let var52 := addmod(var48, var51, r) + let a_5 := mload(0x95a0) + let var53 := mulmod(a_5, a_2_next_1, r) + let var54 := mulmod(var16, var53, r) + let var55 := addmod(var52, var54, r) + let var56 := mulmod(a_5, a_3_next_1, r) + let var57 := mulmod(var26, var56, r) + let var58 := addmod(var55, var57, r) + let var59 := mulmod(a_5, a_4_next_1, r) + let var60 := mulmod(var36, var59, r) + let var61 := addmod(var58, var60, r) + let var62 := mulmod(a_5, a_5_next_1, r) + let var63 := mulmod(var49, var62, r) + let var64 := addmod(var61, var63, r) + let var65 := 0x2cb9b546d20373eaf85e8f53db883cb548 + let var66 := mulmod(a_5, a_6_next_1, r) + let var67 := mulmod(var65, var66, r) + let var68 := addmod(var64, var67, r) + let a_6 := mload(0x95c0) + let var69 := mulmod(a_6, a_1_next_1, r) + let var70 := mulmod(var16, var69, r) + let var71 := addmod(var68, var70, r) + let var72 := mulmod(a_6, a_2_next_1, r) + let var73 := mulmod(var26, var72, r) + let var74 := addmod(var71, var73, r) + let var75 := mulmod(a_6, a_3_next_1, r) + let var76 := mulmod(var36, var75, r) + let var77 := addmod(var74, var76, r) + let var78 := mulmod(a_6, a_4_next_1, r) + let var79 := mulmod(var49, var78, r) + let var80 := addmod(var77, var79, r) + let var81 := mulmod(a_6, a_5_next_1, r) + let var82 := mulmod(var65, var81, r) + let var83 := addmod(var80, var82, r) + let var84 := 0xc8557e86f90d0d89eed6eb5349a0f8820 + let var85 := mulmod(a_6, a_6_next_1, r) + let var86 := mulmod(var84, var85, r) + let var87 := addmod(var83, var86, r) + let var88 := mulmod(var2, a_1, r) + let var89 := addmod(a_0, var88, r) + let var90 := mulmod(var6, a_2, r) + let var91 := addmod(var89, var90, r) + let var92 := addmod(var87, var91, r) + let var93 := mulmod(var2, a_1_next_1, r) + let var94 := addmod(a_0_next_1, var93, r) + let var95 := mulmod(var6, a_2_next_1, r) + let var96 := addmod(var94, var95, r) + let var97 := addmod(var92, var96, r) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) + let var98 := mulmod(var2, a_8, r) + let var99 := addmod(a_7, var98, r) + let a_9 := mload(0x9620) + let var100 := mulmod(var6, a_9, r) + let var101 := addmod(var99, var100, r) + let var102 := addmod(0, sub(r, var101), r) + let var103 := addmod(var97, var102, r) + let a_7_next_1 := mload(0x9740) + let var104 := 0x241eabfffeb153ffffb9feffffffffaaab + let var105 := mulmod(a_7_next_1, var104, r) + let var106 := addmod(0, sub(r, var105), r) + let var107 := addmod(var103, var106, r) + let var108 := addmod(0, sub(r, var16), r) + let var109 := addmod(var107, var108, r) + let a_8_next_1 := mload(0x9760) + let var110 := 0x73eda753299d7d483339d80809a1d80553b9202d7ffe85d4800008bb20000001 + let var111 := addmod(a_8_next_1, var110, r) + let var112 := 0x4000000000000000000000000000000000 + let var113 := mulmod(var111, var112, r) + let var114 := addmod(0, sub(r, var113), r) + let var115 := addmod(var109, var114, r) + let var116 := mulmod(var0, var115, r) + mstore(0xb8e0, var116) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 1 { + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let var1 := 0x10000000000000000000000000000 + let var2 := addmod(a_0, var1, r) + let var3 := 0x100000000000000 + let a_1 := mload(0x94c0) + let var4 := addmod(a_1, var1, r) + let var5 := mulmod(var3, var4, r) + let var6 := addmod(var2, var5, r) + let a_2 := mload(0x94e0) + let var7 := addmod(a_2, var1, r) + let var8 := mulmod(var1, var7, r) + let var9 := addmod(var6, var8, r) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) + let var10 := mulmod(var3, a_8, r) + let var11 := addmod(a_7, var10, r) + let a_9 := mload(0x9620) + let var12 := mulmod(var1, a_9, r) + let var13 := addmod(var11, var12, r) + let var14 := addmod(0, sub(r, var13), r) + let var15 := addmod(var9, var14, r) + let var16 := addmod(0, sub(r, var1), r) + let var17 := addmod(var15, var16, r) + let a_7_next_1 := mload(0x9740) + let var18 := 0x241eabfffeb153ffffb9feffffffffaaab + let var19 := mulmod(a_7_next_1, var18, r) + let var20 := addmod(0, sub(r, var19), r) + let var21 := addmod(var17, var20, r) + let var22 := 0xd9d44a30b019261257667fde3844a8cd6 + let var23 := addmod(0, sub(r, var22), r) + let var24 := addmod(var21, var23, r) + let a_8_next_1 := mload(0x9760) + let var25 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5b6e855000003ab00002 + let var26 := addmod(a_8_next_1, var25, r) + let var27 := 0x4000000000000000000000000000000000 + let var28 := mulmod(var26, var27, r) + let var29 := addmod(0, sub(r, var28), r) + let var30 := addmod(var24, var29, r) + let var31 := mulmod(var0, var30, r) + mstore(0xb8e0, var31) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x80) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 2 { + { + let var0 := 0x1 + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var1 := addmod(0, sub(r, a_0_next_1), r) + let var2 := addmod(f_0, var1, r) + let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace + let a_0 := mload(0x94a0) + let var4 := mulmod(a_0, a_0, r) + let a_3 := mload(0x9500) + let var5 := mulmod(var4, a_3, r) + let var6 := mulmod(var3, var5, r) + let var7 := addmod(var2, var6, r) + let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 + let a_1 := mload(0x94c0) + let var9 := mulmod(a_1, a_1, r) + let a_4 := mload(0x9520) + let var10 := mulmod(var9, a_4, r) + let var11 := mulmod(var8, var10, r) + let var12 := addmod(var7, var11, r) + let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 + let a_2 := mload(0x94e0) + let var14 := mulmod(a_2, a_2, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var14, a_5, r) + let var16 := mulmod(var13, var15, r) + let var17 := addmod(var12, var16, r) + let var18 := mulmod(var0, var17, r) + mstore(0xb8e0, var18) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x120) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 3 { + { + let var0 := 0x1 + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) + let var1 := addmod(0, sub(r, a_1_next_1), r) + let var2 := addmod(f_1, var1, r) + let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 + let a_0 := mload(0x94a0) + let var4 := mulmod(a_0, a_0, r) + let a_3 := mload(0x9500) + let var5 := mulmod(var4, a_3, r) + let var6 := mulmod(var3, var5, r) + let var7 := addmod(var2, var6, r) + let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 + let a_1 := mload(0x94c0) + let var9 := mulmod(a_1, a_1, r) + let a_4 := mload(0x9520) + let var10 := mulmod(var9, a_4, r) + let var11 := mulmod(var8, var10, r) + let var12 := addmod(var7, var11, r) + let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f + let a_2 := mload(0x94e0) + let var14 := mulmod(a_2, a_2, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var14, a_5, r) + let var16 := mulmod(var13, var15, r) + let var17 := addmod(var12, var16, r) + let var18 := mulmod(var0, var17, r) + mstore(0xb8e0, var18) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x120) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + default { revert(0, 0) } + } + // VM 0x0b FOLD_SELECTOR: consume q_top into one simple-selector bucket. + case 0x0b { + // Operand layout packed into three bytes: + // high byte: selector bucket index; + // low u16 : y-power gap since this selector's + // previous contribution. + let q_selector_payload := shr(232, mload(q_pc)) + q_pc := add(q_pc, 3) + let q_sel_idx := shr(16, q_selector_payload) + let q_sel_gap := and(q_selector_payload, 0xffff) + let q_eval := q_top + q_has_top := 0 + // Simple-selector identity: keep the same y-batch + // position as main identities, then advance only this + // selector bucket by its codegen-known gap. + // + // The global fully-evaluated accumulator is still + // multiplied by y so later main identities land at the + // same y powers as Rust's reverse fold. + mstore(0xb280, mulmod(mload(0xb280), y, r)) + let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) + let q_sel_acc := mload(q_target_ptr) + if q_sel_gap { + // Selector buckets are sparse in the global + // identity stream. Precomputed y^gap advances only + // this selector's local accumulator. + q_sel_acc := mulmod(q_sel_acc, mload(add(0xb2c0, shl(5, q_sel_gap))), r) + } + mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) + } + // Invalid generated bytecode should fail closed. 0x1a intentionally lands here. + default { + revert(0, 0) + } + } + // The VK-pinned bytecode must end exactly at q_end and every + // identity must have been consumed by a fold/native callback. + // This catches malformed generator output whose final opcode + // over-reads operands or leaves a partial expression live. + if iszero(eq(q_pc, q_end)) { revert(0, 0) } + if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0xb8e0)) { revert(0, 0) } + + // Structured post-VM suffix. The current default uses this for + // regular trash constraints: it is smaller than fully unrolled + // Yul and cheaper than interpreting every trash operation. + // + // These generated blocks run after q_pc reaches q_end, but + // they still participate in the same identity order and write + // into the same numerator / selector accumulators. + { + let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) + { + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 + let var1 := mulmod(a_0_next_1, var0, r) + let var2 := addmod(f_0, var1, r) + let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 + let a_0 := mload(0x94a0) + let var4 := mulmod(var3, a_0, r) + let var5 := addmod(var2, var4, r) + let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f + let a_1 := mload(0x94c0) + let var7 := mulmod(var6, a_1, r) + let var8 := addmod(var5, var7, r) + let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 + let a_2 := mload(0x94e0) + let var10 := q_pow5(a_2) + let var11 := mulmod(var9, var10, r) + let var12 := addmod(var8, var11, r) + let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 + let a_3 := mload(0x9500) + let var14 := q_pow5(a_3) + let var15 := mulmod(var13, var14, r) + let var16 := addmod(var12, var15, r) + let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 + let a_4 := mload(0x9520) + let var18 := q_pow5(a_4) + let var19 := mulmod(var17, var18, r) + let var20 := addmod(var16, var19, r) + let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e + let a_5 := mload(0x95a0) + let var22 := q_pow5(a_5) + let var23 := mulmod(var21, var22, r) + let var24 := addmod(var20, var23, r) + let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 + let a_6 := mload(0x95c0) + let var26 := q_pow5(a_6) + let var27 := mulmod(var25, var26, r) + let var28 := addmod(var24, var27, r) + let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 + let a_7 := mload(0x95e0) + let var30 := q_pow5(a_7) + let var31 := mulmod(var29, var30, r) + let var32 := addmod(var28, var31, r) + let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) + let var34 := mulmod(a_1_next_1, var0, r) + let var35 := addmod(f_1, var34, r) + let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d + let var37 := mulmod(var36, a_0, r) + let var38 := addmod(var35, var37, r) + let var39 := 0x4d0ea7f9c3fda06d9535b0fdafd8338bd47c2200b284fa71a325ff41ac358028 + let var40 := mulmod(var39, a_1, r) + let var41 := addmod(var38, var40, r) + let var42 := 0x26cc223e16f47c20e17cc6069605fa5a8af05ea4f6eb36029a641d23b818eb10 + let var43 := mulmod(var42, var10, r) + let var44 := addmod(var41, var43, r) + let var45 := 0x31e823a45e567484c1544e310c0fa5cd66547a8f0dde659ac61698c30e838d25 + let var46 := mulmod(var45, var14, r) + let var47 := addmod(var44, var46, r) + let var48 := 0x275a20361ea91992193920270d3e2d1f6361880ac0a439c64bef815d4469ba85 + let var49 := mulmod(var48, var18, r) + let var50 := addmod(var47, var49, r) + let var51 := 0x5f3a15bab4ce4097b1edc3a25002694b92395ce355a8a12fe557459d9633f701 + let var52 := mulmod(var51, var22, r) + let var53 := addmod(var50, var52, r) + let var54 := 0x301cf56f9b4577112cc4241cddf6484aaadedbf1bbd0f2351adf2e41c2fb2ecd + let var55 := mulmod(var54, var26, r) + let var56 := addmod(var53, var55, r) + let var57 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f + let var58 := mulmod(var57, var30, r) + let var59 := addmod(var56, var58, r) + let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) + let f_2 := mload(0x9aa0) + let var61 := mulmod(a_3, var0, r) + let var62 := addmod(f_2, var61, r) + let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 + let var64 := mulmod(var63, a_0, r) + let var65 := addmod(var62, var64, r) + let var66 := 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a + let var67 := mulmod(var66, a_1, r) + let var68 := addmod(var65, var67, r) + let var69 := 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db + let var70 := mulmod(var69, var10, r) + let var71 := addmod(var68, var70, r) + let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) + let f_3 := mload(0x9ac0) + let var73 := mulmod(a_4, var0, r) + let var74 := addmod(f_3, var73, r) + let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 + let var76 := mulmod(var75, a_0, r) + let var77 := addmod(var74, var76, r) + let var78 := 0x26c2cc87f95726b28f33ca03409a460ec987cfe12adae32769e3565865d07191 + let var79 := mulmod(var78, a_1, r) + let var80 := addmod(var77, var79, r) + let var81 := 0x4382d0938a760120dd6cef8f3b90a0c38abae475e3d21e39365472b76d780272 + let var82 := mulmod(var81, var10, r) + let var83 := addmod(var80, var82, r) + let var84 := mulmod(var69, var14, r) + let var85 := addmod(var83, var84, r) + let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) + let f_4 := mload(0x99c0) + let var87 := mulmod(a_5, var0, r) + let var88 := addmod(f_4, var87, r) + let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b + let var90 := mulmod(var89, a_0, r) + let var91 := addmod(var88, var90, r) + let var92 := 0x24822e1af9aa2887c912c87eb0f20bd332330e7e55cd784de67cb407a9f05520 + let var93 := mulmod(var92, a_1, r) + let var94 := addmod(var91, var93, r) + let var95 := 0x4e5280109d8f96b8bfb543a6b1af25fb56a9db616af85a90eedc558e3eb1ea29 + let var96 := mulmod(var95, var10, r) + let var97 := addmod(var94, var96, r) + let var98 := mulmod(var81, var14, r) + let var99 := addmod(var97, var98, r) + let var100 := mulmod(var69, var18, r) + let var101 := addmod(var99, var100, r) + let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) + let f_5 := mload(0x99e0) + let var103 := mulmod(a_6, var0, r) + let var104 := addmod(f_5, var103, r) + let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 + let var106 := mulmod(var105, a_0, r) + let var107 := addmod(var104, var106, r) + let var108 := 0x23a6684b942d726a22e4d5b8d8ff83aeaa773f62600184efe5d033d7c7c6e827 + let var109 := mulmod(var108, a_1, r) + let var110 := addmod(var107, var109, r) + let var111 := 0x1981b4b33d6a9dab957b351d981d3323e65da39493af5bc01f7e8ffe17f98d4e + let var112 := mulmod(var111, var10, r) + let var113 := addmod(var110, var112, r) + let var114 := mulmod(var95, var14, r) + let var115 := addmod(var113, var114, r) + let var116 := mulmod(var81, var18, r) + let var117 := addmod(var115, var116, r) + let var118 := mulmod(var69, var22, r) + let var119 := addmod(var117, var118, r) + let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) + let f_6 := mload(0x9a00) + let var121 := mulmod(a_7, var0, r) + let var122 := addmod(f_6, var121, r) + let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a + let var124 := mulmod(var123, a_0, r) + let var125 := addmod(var122, var124, r) + let var126 := 0x27e7119226c42a6d19c1541904b99ae40685511ed2e078964b74594d38340849 + let var127 := mulmod(var126, a_1, r) + let var128 := addmod(var125, var127, r) + let var129 := 0xd94c46a8456352aa44d7a885ab59e3a36664e6fb25e826f8a4cd79822f0533 + let var130 := mulmod(var129, var10, r) + let var131 := addmod(var128, var130, r) + let var132 := mulmod(var111, var14, r) + let var133 := addmod(var131, var132, r) + let var134 := mulmod(var95, var18, r) + let var135 := addmod(var133, var134, r) + let var136 := mulmod(var81, var22, r) + let var137 := addmod(var135, var136, r) + let var138 := mulmod(var69, var26, r) + let var139 := addmod(var137, var138, r) + let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) + let f_7 := mload(0x9a20) + let a_2_next_1 := mload(0x9580) + let var141 := mulmod(a_2_next_1, var0, r) + let var142 := addmod(f_7, var141, r) + let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 + let var144 := mulmod(var143, a_0, r) + let var145 := addmod(var142, var144, r) + let var146 := 0x40fa389feb2522bb934881ac9ed749aee2296502af592418c6b5675c0f560261 + let var147 := mulmod(var146, a_1, r) + let var148 := addmod(var145, var147, r) + let var149 := 0x1f61345b652161410c5e29f51e301ae56342af824bc110649393d2b911c50d3e + let var150 := mulmod(var149, var10, r) + let var151 := addmod(var148, var150, r) + let var152 := mulmod(var129, var14, r) + let var153 := addmod(var151, var152, r) + let var154 := mulmod(var111, var18, r) + let var155 := addmod(var153, var154, r) + let var156 := mulmod(var95, var22, r) + let var157 := addmod(var155, var156, r) + let var158 := mulmod(var81, var26, r) + let var159 := addmod(var157, var158, r) + let var160 := mulmod(var69, var30, r) + let var161 := addmod(var159, var160, r) + let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) + let f_26 := mload(0x9ba0) + let q_trash_one_minus_selector := addmod(1, sub(r, f_26), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0xa120), r) + let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_trash_eval, r)) + } + } + // Finish selector buckets by applying the codegen-known tail + // from each selector's last identity to the end of the global + // y-batch. + // + // After this step, every selector bucket is aligned with the + // final global y position and can be multiplied by its fixed + // selector commitment in the linearized MSM. + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0600)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x05e0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0580)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0520)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x04c0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0460)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0400)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x03a0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0340)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0280)), r)) + } + + // Fully evaluated identities are the constant-polynomial side + // of the linearization query. Rust subtracts that grouped + // scalar into expected_eval, so Solidity stores -nu_y(x). + let linearization_expected_eval := addmod(0, sub(r, mload(0xb280)), r) + mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) + pop(y) + } + + // =============================================================== + // Prepare linearization scalars for the final PCS MSM. + // + // The linearized commitment is + // (1 - x^n) * Σ_i x_split^i * Q_i + // + Σ_j sel_acc_j * S_j_com, + // where x_split = x^(n-1). Instead of materializing that point + // with a standalone G1MSM here, PCS block 5 expands the + // linearized commitment into its quotient and selector + // pairs inside the already-fused final MSM. + // + // QUOTIENT_MPTR is no longer a G1 point in this path. Its first + // two words carry: + // word 0: x_split + // word 1: one_minus_x_n + // =============================================================== + { + let x := mload(X_MPTR) + let k := 20 + // Compute both x^n and x^(n-1) with the same squaring walk: + // x_pow_2i tracks x^(2^i), while x_pow_2i_minus1 tracks + // x^(2^i - 1). + let x_pow_2i := x + let x_pow_2i_minus1 := 1 + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { + x_pow_2i_minus1 := mulmod( + mulmod(x_pow_2i_minus1, x_pow_2i_minus1, r), + x, + r + ) + x_pow_2i := mulmod(x_pow_2i, x_pow_2i, r) + } + let x_split := x_pow_2i_minus1 + let one_minus_x_n := addmod(1, sub(r, x_pow_2i), r) + + // PCS block 5 interprets this 2-word payload as scalar + // metadata, not as a materialized G1 point. + mstore(QUOTIENT_MPTR, x_split) + mstore(add(QUOTIENT_MPTR, 0x20), one_minus_x_n) + } + + // =============================================================== + // PCS computation (multi-prepare emitter from Step 5). + // + // The Rust lowering stage has already expanded the KZG multi-open + // equation into a sequence of generated Yul sub-blocks. Those + // blocks populate: + // - F_EVAL_MPTR / V_MPTR scalar batching values; + // - FINAL_COM_MPTR for the fused commitment MSM; + // - PAIRING_LHS_MPTR and PAIRING_RHS_MPTR for the final pairing. + // =============================================================== + { + // Generated PCS sub-block 1. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // 4 distinct rotation(s) + let x := mload(X_MPTR) + let omega := mload(OMEGA_MPTR) + let omega_inv := mload(OMEGA_INV_MPTR) + let x_pow_of_omega := x + mstore(add(ROT_POINTS_MPTR, 0x40), x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega, r) + mstore(add(ROT_POINTS_MPTR, 0x60), x_pow_of_omega) + x_pow_of_omega := x + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + mstore(add(ROT_POINTS_MPTR, 0x20), x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + mstore(add(ROT_POINTS_MPTR, 0x0), x_pow_of_omega) + } + // Generated PCS sub-block 2. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // pre-compute 43 x1 power(s) + let x1 := mload(X1_MPTR) + mstore(X1_POWERS_MPTR, 1) + let acc := 1 + let p := X1_POWERS_MPTR + for { let i := 0 } lt(i, 0x2a) { i := add(i, 1) } { + p := add(p, 0x20) + acc := mulmod(acc, x1, r) + mstore(p, and(acc, 0xffffffffffffffffffffffffffffffff)) + } + } + // Generated PCS sub-block 3. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[0]: 43 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x9980) + mstore(0xb2a0, 0x9480) + mstore(0xb2c0, 0xa020) + mstore(0xb2e0, 0xa040) + mstore(0xb300, 0xa0a0) + mstore(0xb320, 0xa0c0) + mstore(0xb340, 0xa120) + mstore(0xb360, 0x99a0) + mstore(0xb380, 0x99c0) + mstore(0xb3a0, 0x99e0) + mstore(0xb3c0, 0x9a00) + mstore(0xb3e0, 0x9a20) + mstore(0xb400, 0x9a40) + mstore(0xb420, 0x9a60) + mstore(0xb440, 0x9a80) + mstore(0xb460, 0x9aa0) + mstore(0xb480, 0x9ac0) + mstore(0xb4a0, 0x9ae0) + mstore(0xb4c0, 0x9b00) + mstore(0xb4e0, 0x9b20) + mstore(0xb500, 0x9b40) + mstore(0xb520, 0x9b60) + mstore(0xb540, 0x9b80) + mstore(0xb560, 0x9ba0) + mstore(0xb580, 0x9bc0) + mstore(0xb5a0, 0x9be0) + mstore(0xb5c0, 0x9c00) + mstore(0xb5e0, 0x9c20) + mstore(0xb600, 0x9c40) + mstore(0xb620, 0x9c60) + mstore(0xb640, 0x9c80) + mstore(0xb660, 0x9ca0) + mstore(0xb680, 0x9cc0) + mstore(0xb6a0, 0x9ce0) + mstore(0xb6c0, 0x9d00) + mstore(0xb6e0, 0x9d20) + mstore(0xb700, 0x9d40) + mstore(0xb720, 0x9d60) + mstore(0xb740, 0x9d80) + mstore(0xb760, 0x9da0) + mstore(0xb780, 0x9dc0) + mstore(0xb7a0, 0x9de0) + mstore(0xb7c0, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x9980) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x20) + for { let i := 1 } lt(i, 0x2b) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x20) + } + mstore(add(Q_EVAL_SET_MPTR, 0x0), q_eval_set_0) + } + // Generated PCS sub-block 4. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[1]: 3 commitment(s) + let q_eval_set_0 := mload(0x9660) + let q_eval_set_1 := mload(0x9920) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x9680), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9940), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x96a0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9960), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) + } + // Generated PCS sub-block 5. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[2]: 3 commitment(s) + let q_eval_set_0 := mload(0x9fe0) + let q_eval_set_1 := mload(0xa000) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa060), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa080), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa0e0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa100), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) + } + // Generated PCS sub-block 6. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[3]: 11 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x94a0) + mstore(0xb2a0, 0x9540) + mstore(0xb2c0, 0x97c0) + mstore(0xb2e0, 0x94c0) + mstore(0xb300, 0x9560) + mstore(0xb320, 0x97e0) + mstore(0xb340, 0x94e0) + mstore(0xb360, 0x9580) + mstore(0xb380, 0x9800) + mstore(0xb3a0, 0x9500) + mstore(0xb3c0, 0x96c0) + mstore(0xb3e0, 0x9820) + mstore(0xb400, 0x9520) + mstore(0xb420, 0x96e0) + mstore(0xb440, 0x9840) + mstore(0xb460, 0x95a0) + mstore(0xb480, 0x9700) + mstore(0xb4a0, 0x9860) + mstore(0xb4c0, 0x95c0) + mstore(0xb4e0, 0x9720) + mstore(0xb500, 0x9880) + mstore(0xb520, 0x95e0) + mstore(0xb540, 0x9740) + mstore(0xb560, 0x98a0) + mstore(0xb580, 0x9600) + mstore(0xb5a0, 0x9760) + mstore(0xb5c0, 0x98c0) + mstore(0xb5e0, 0x9620) + mstore(0xb600, 0x9780) + mstore(0xb620, 0x98e0) + mstore(0xb640, 0x9640) + mstore(0xb660, 0x97a0) + mstore(0xb680, 0x9900) + let q_eval_set_0 := mload(0x94a0) + let q_eval_set_1 := mload(0x9540) + let q_eval_set_2 := mload(0x97c0) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x60) + for { let i := 1 } lt(i, 0xb) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(mload(add(eval_p, 0x20))), pow, r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(mload(add(eval_p, 0x40))), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x60) + } + mstore(add(Q_EVAL_SET_MPTR, 0xa0), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0xc0), q_eval_set_1) + mstore(add(Q_EVAL_SET_MPTR, 0xe0), q_eval_set_2) + } + // Generated PCS sub-block 7. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[4]: 5 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x9e00) + mstore(0xb2a0, 0x9e20) + mstore(0xb2c0, 0x9e40) + mstore(0xb2e0, 0x9e60) + mstore(0xb300, 0x9e80) + mstore(0xb320, 0x9ea0) + mstore(0xb340, 0x9ec0) + mstore(0xb360, 0x9ee0) + mstore(0xb380, 0x9f00) + mstore(0xb3a0, 0x9f20) + mstore(0xb3c0, 0x9f40) + mstore(0xb3e0, 0x9f60) + mstore(0xb400, 0x9f80) + mstore(0xb420, 0x9fa0) + mstore(0xb440, 0x9fc0) + let q_eval_set_0 := mload(0x9e00) + let q_eval_set_1 := mload(0x9e20) + let q_eval_set_2 := mload(0x9e40) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x60) + for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(mload(add(eval_p, 0x20))), pow, r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(mload(add(eval_p, 0x40))), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x60) + } + mstore(add(Q_EVAL_SET_MPTR, 0x100), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x120), q_eval_set_1) + mstore(add(Q_EVAL_SET_MPTR, 0x140), q_eval_set_2) + } + // Generated PCS sub-block 8. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // f_eval via Horner over 5 reversed set(s) + let x2 := mload(X2_MPTR) + let x3 := mload(X3_MPTR) + let f_eval := 0 + let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) + let rot_pt_0 := mload(add(ROT_POINTS_MPTR, 0x0)) + let rot_pt_1 := mload(add(ROT_POINTS_MPTR, 0x20)) + let rot_pt_2 := mload(add(ROT_POINTS_MPTR, 0x40)) + let rot_pt_3 := mload(add(ROT_POINTS_MPTR, 0x60)) + // --- set 4 (cardinality 3) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let dx_2 := addmod(x3, sub(r, rot_pt_0), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_0), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_0), r), r) + let lbasis_2 := 1 + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_0, sub(r, rot_pt_2), r), r) + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_0, sub(r, rot_pt_3), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, dx_2, r) + let bp_3 := mulmod(bp_2, lbasis_0, r) + let bp_4 := mulmod(bp_3, lbasis_1, r) + let bp_5 := mulmod(bp_4, lbasis_2, r) + let bq := scalar_inv(bp_5) + let lbasis_inv_2 := mulmod(bq, bp_4, r) + bq := mulmod(bq, lbasis_2, r) + let lbasis_inv_1 := mulmod(bq, bp_3, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_2 := mulmod(bq, bp_1, r) + bq := mulmod(bq, dx_2, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + den_inv := mulmod(den_inv, dx_inv_2, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x100)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x120)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + let term_2 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x140)), dx_inv_2, r), lbasis_inv_2, r) + eval := addmod(eval, sub(r, term_2), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 3 (cardinality 3) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let dx_2 := addmod(x3, sub(r, rot_pt_1), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_1), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_1), r), r) + let lbasis_2 := 1 + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_1, sub(r, rot_pt_2), r), r) + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_1, sub(r, rot_pt_3), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, dx_2, r) + let bp_3 := mulmod(bp_2, lbasis_0, r) + let bp_4 := mulmod(bp_3, lbasis_1, r) + let bp_5 := mulmod(bp_4, lbasis_2, r) + let bq := scalar_inv(bp_5) + let lbasis_inv_2 := mulmod(bq, bp_4, r) + bq := mulmod(bq, lbasis_2, r) + let lbasis_inv_1 := mulmod(bq, bp_3, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_2 := mulmod(bq, bp_1, r) + bq := mulmod(bq, dx_2, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + den_inv := mulmod(den_inv, dx_inv_2, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xa0)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xc0)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + let term_2 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xe0)), dx_inv_2, r), lbasis_inv_2, r) + eval := addmod(eval, sub(r, term_2), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 2 (cardinality 2) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, lbasis_0, r) + let bp_3 := mulmod(bp_2, lbasis_1, r) + let bq := scalar_inv(bp_3) + let lbasis_inv_1 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_1, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x60)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x80)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 1 (cardinality 2) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_1), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_1), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_1, sub(r, rot_pt_2), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, lbasis_0, r) + let bp_3 := mulmod(bp_2, lbasis_1, r) + let bq := scalar_inv(bp_3) + let lbasis_inv_1 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_1, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x20)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x40)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 0 (cardinality 1) --- + { + let dx0 := addmod(x3, sub(r, rot_pt_2), r) + let dx0_inv := scalar_inv(dx0) + let eval := mulmod(addmod(calldataload(add(Q_EVAL_CPTR, 0x0)), sub(r, mload(add(Q_EVAL_SET_MPTR, 0x0))), r), dx0_inv, r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + mstore(F_EVAL_MPTR, f_eval) + } + // Generated PCS sub-block 9. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // build final_com and v (KZG single-opening proof, fused MSM) + // final MSM input length from circuit/VK shape: 78 term(s) + let x4 := mload(X4_MPTR) + let lin_x_split := mload(QUOTIENT_MPTR) + let lin_one_minus_x_n := mload(add(QUOTIENT_MPTR, 0x20)) + let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) + let x4_pow_full := 1 + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_1 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_2 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_3 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_4 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_5 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + let v := calldataload(Q_EVAL_CPTR) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), x4_pow_4, r), r) + v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_5, r), r) + mcopy(0xb280, 0xa840, 0x80) + mstore(0xb300, 1) + mcopy(0xb320, 0xa8c0, 0x80) + mstore(0xb3a0, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0xb3c0, 0xacc0, 0x80) + mstore(0xb440, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0xb460, 0xa940, 0x80) + mstore(0xb4e0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0xb500, 0xad40, 0x80) + mstore(0xb580, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0xb5a0, 0xaec0, 0x80) + mstore(0xb620, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0xb640, 0x6700, 0x80) + mstore(0xb6c0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0xb6e0, 0x6480, 0x80) + mstore(0xb760, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0xb780, 0x6500, 0x80) + mstore(0xb800, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0xb820, 0x6580, 0x80) + mstore(0xb8a0, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0xb8c0, 0x6600, 0x80) + mstore(0xb940, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0xb960, 0x6680, 0x80) + mstore(0xb9e0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0xba00, 0x6280, 0x80) + mstore(0xba80, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0xbaa0, 0x6300, 0x80) + mstore(0xbb20, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0xbb40, 0x6380, 0x80) + mstore(0xbbc0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0xbbe0, 0x6400, 0x80) + mstore(0xbc60, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0xbc80, 0x6780, 0x80) + mstore(0xbd00, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0xbd20, 0x6800, 0x80) + mstore(0xbda0, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0xbdc0, 0x6880, 0x80) + mstore(0xbe40, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0xbe60, 0x6900, 0x80) + mstore(0xbee0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0xbf00, 0x6b00, 0x80) + mstore(0xbf80, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0xbfa0, 0x6e80, 0x80) + mstore(0xc020, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0xc040, 0x6f80, 0x80) + mstore(0xc0c0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0xc0e0, 0x7000, 0x80) + mstore(0xc160, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0xc180, 0x7080, 0x80) + mstore(0xc200, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0xc220, 0x7100, 0x80) + mstore(0xc2a0, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0xc2c0, 0x7180, 0x80) + mstore(0xc340, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0xc360, 0x7200, 0x80) + mstore(0xc3e0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0xc400, 0x7280, 0x80) + mstore(0xc480, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0xc4a0, 0x7300, 0x80) + mstore(0xc520, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0xc540, 0x7380, 0x80) + mstore(0xc5c0, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0xc5e0, 0x7400, 0x80) + mstore(0xc660, mload(add(X1_POWERS_MPTR, 0x400))) + mcopy(0xc680, 0x7480, 0x80) + mstore(0xc700, mload(add(X1_POWERS_MPTR, 0x420))) + mcopy(0xc720, 0x7500, 0x80) + mstore(0xc7a0, mload(add(X1_POWERS_MPTR, 0x440))) + mcopy(0xc7c0, 0x7580, 0x80) + mstore(0xc840, mload(add(X1_POWERS_MPTR, 0x460))) + mcopy(0xc860, 0x7600, 0x80) + mstore(0xc8e0, mload(add(X1_POWERS_MPTR, 0x480))) + mcopy(0xc900, 0x7680, 0x80) + mstore(0xc980, mload(add(X1_POWERS_MPTR, 0x4a0))) + mcopy(0xc9a0, 0x7700, 0x80) + mstore(0xca20, mload(add(X1_POWERS_MPTR, 0x4c0))) + mcopy(0xca40, 0x7780, 0x80) + mstore(0xcac0, mload(add(X1_POWERS_MPTR, 0x4e0))) + mcopy(0xcae0, 0x7800, 0x80) + mstore(0xcb60, mload(add(X1_POWERS_MPTR, 0x500))) + mcopy(0xcb80, 0x7880, 0x80) + mstore(0xcc00, mload(add(X1_POWERS_MPTR, 0x520))) + let lin_query_scalar_41 := mload(add(X1_POWERS_MPTR, 0x540)) + let lin_cur_scalar_41 := mulmod(lin_query_scalar_41, lin_one_minus_x_n, r) + mcopy(0xcc20, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0xcca0, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xccc0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0xcd40, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xcd60, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0xcde0, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xce00, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0xce80, lin_cur_scalar_41) + mcopy(0xcea0, 0x6980, 0x80) + mstore(0xcf20, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0xcf40, 0x6a00, 0x80) + mstore(0xcfc0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0xcfe0, 0x6a80, 0x80) + mstore(0xd060, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0xd080, 0x6b80, 0x80) + mstore(0xd100, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0xd120, 0x6c00, 0x80) + mstore(0xd1a0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0xd1c0, 0x6c80, 0x80) + mstore(0xd240, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0xd260, 0x6d00, 0x80) + mstore(0xd2e0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0xd300, 0x6d80, 0x80) + mstore(0xd380, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0xd3a0, 0x6e00, 0x80) + mstore(0xd420, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0xd440, 0x6f00, 0x80) + mstore(0xd4c0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0xd4e0, 0xa6c0, 0x80) + mstore(0xd560, x4_pow_1) + mcopy(0xd580, 0xa740, 0x80) + mstore(0xd600, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0xd620, 0xa7c0, 0x80) + mstore(0xd6a0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0xd6c0, 0xac40, 0x80) + mstore(0xd740, x4_pow_2) + mcopy(0xd760, 0xadc0, 0x80) + mstore(0xd7e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0xd800, 0xae40, 0x80) + mstore(0xd880, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0xd8a0, 0xa140, 0x80) + mstore(0xd920, x4_pow_3) + mcopy(0xd940, 0xa1c0, 0x80) + mstore(0xd9c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xd9e0, 0xa240, 0x80) + mstore(0xda60, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_3, r)) + mcopy(0xda80, 0xa2c0, 0x80) + mstore(0xdb00, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_3, r)) + mcopy(0xdb20, 0xa340, 0x80) + mstore(0xdba0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_3, r)) + mcopy(0xdbc0, 0xa3c0, 0x80) + mstore(0xdc40, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_3, r)) + mcopy(0xdc60, 0xa440, 0x80) + mstore(0xdce0, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_3, r)) + mcopy(0xdd00, 0xa4c0, 0x80) + mstore(0xdd80, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_3, r)) + mcopy(0xdda0, 0xa540, 0x80) + mstore(0xde20, mulmod(mload(add(X1_POWERS_MPTR, 0x100)), x4_pow_3, r)) + mcopy(0xde40, 0xa5c0, 0x80) + mstore(0xdec0, mulmod(mload(add(X1_POWERS_MPTR, 0x120)), x4_pow_3, r)) + mcopy(0xdee0, 0xa640, 0x80) + mstore(0xdf60, mulmod(mload(add(X1_POWERS_MPTR, 0x140)), x4_pow_3, r)) + mcopy(0xdf80, 0xa9c0, 0x80) + mstore(0xe000, x4_pow_4) + mcopy(0xe020, 0xaa40, 0x80) + mstore(0xe0a0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_4, r)) + mcopy(0xe0c0, 0xaac0, 0x80) + mstore(0xe140, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_4, r)) + mcopy(0xe160, 0xab40, 0x80) + mstore(0xe1e0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_4, r)) + mcopy(0xe200, 0xabc0, 0x80) + mstore(0xe280, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_4, r)) + mcopy(0xe2a0, F_COM_MPTR, 0x80) + mstore(0xe320, x4_pow_5) + if success { + success := staticcall(gas(), 0x0c, 0xb280, 0x30c0, FINAL_COM_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mstore(V_MPTR, v) + } + // Generated PCS sub-block 10. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // Scale z*pi - vG before the final pairing check + // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) + mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) + if success { + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(0x1080, FINAL_COM_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) + if success { + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + if success { + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) + } + } + + // Batch the prevalidated public IVC accumulator pairing equation + // into the final KZG pairing. + // + // We do not simply multiply the two pairing equations together: + // two bad equations could cancel. Instead, after all four G1 + // pairing inputs are fixed, derive a verifier-local randomizer + // alpha and check: + // + // e(kzg_rhs + alpha * acc_rhs, G2_BASE) + // * e(kzg_lhs + alpha * acc_lhs, NEG_S_G2_BASE) == 1 + // + // If either original equation is bad, this combined equation + // holds for at most one alpha in Fr. + { + let batch_ptr := 0x0100 + + // Domain || KZG rhs/lhs || accumulator rhs/lhs. + mstore(batch_ptr, 0x70616972696e672d62617463682d6163632d6b7a670000000000000000) + mcopy(add(batch_ptr, 0x20), PAIRING_RHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0xa0), PAIRING_LHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0x0120), ACC_RHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0x01a0), ACC_LHS_MPTR, 0x80) + // alpha is Fiat-Shamir over the fully materialized pairing + // inputs. Replace the negligible zero draw with one so the + // accumulator equation cannot be accidentally dropped. + let acc_pair_alpha := mod(keccak256(batch_ptr, 0x0220), r) + if iszero(acc_pair_alpha) { acc_pair_alpha := 1 } + + // PAIRING_RHS_MPTR += alpha * ACC_RHS_MPTR. + // First compute alpha * ACC_RHS with a one-pair G1MSM, then + // add it into the KZG RHS point. + mcopy(batch_ptr, ACC_RHS_MPTR, 0x80) + mstore(add(batch_ptr, 0x80), acc_pair_alpha) + if success { + success := staticcall(gas(), 0x0c, batch_ptr, 0xa0, batch_ptr, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(add(batch_ptr, 0x80), PAIRING_RHS_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, batch_ptr, 0x0100, PAIRING_RHS_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + + // PAIRING_LHS_MPTR += alpha * ACC_LHS_MPTR. + // Mirror the same randomized batching on the KZG LHS point. + mcopy(batch_ptr, ACC_LHS_MPTR, 0x80) + mstore(add(batch_ptr, 0x80), acc_pair_alpha) + if success { + success := staticcall(gas(), 0x0c, batch_ptr, 0xa0, batch_ptr, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(add(batch_ptr, 0x80), PAIRING_LHS_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, batch_ptr, 0x0100, PAIRING_LHS_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + } + + // The Yul `ec_pairing` helper checks + // e(arg0, G2_BASE) * e(arg1, NEG_S_G2_BASE) == 1 + // i.e. e(arg0, [1]_2) = e(arg1, [s]_2). + // + // The KZG pairing identity is + // e(final_com - v*G + x3*pi, [1]_2) = e(pi, [s]_2), + // so arg0 must be (final_com - v*G + x3*pi) and arg1 must be + // pi. The PAIRING_*_MPTR slots store + // PAIRING_LHS_MPTR := pi + // PAIRING_RHS_MPTR := final_com - v*G + x3*pi + // -- the historical "LHS"/"RHS" naming follows the dual MSM + // accumulator (left = pi, right = combined) and *not* the + // pairing argument order. Pass them swapped to ec_pairing. + if iszero(success) { revert(0, 0) } + success := ec_pairing(success, PAIRING_RHS_MPTR, PAIRING_LHS_MPTR) + + + + // Success path is terminal. Invalid inputs have already reverted, + // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } + mstore(RETURN_MPTR, 1) + return(RETURN_MPTR, 0x20) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2VerifyingKey.sol b/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2VerifyingKey.sol new file mode 100644 index 000000000..8f0b378f8 --- /dev/null +++ b/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2VerifyingKey.sol @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity ^0.8.24; + +/// @title Halo2 BLS12-381 verifying-key payload. +/// @notice Contract whose deployed runtime is `INVALID || generated verifier-key payload`. +/// @dev Byte 0 is an unconditional INVALID opcode so direct calls cannot execute payload bytes as code. The linked verifier pins the full runtime by length/codehash and copies the payload starting at byte 1. +/// @dev The layout follows the verifier inputs derived from +/// `midfall/proofs/src/plonk/mod.rs::VerifyingKey` and the transcript +/// `vk.hash_into` behavior used by `midfall/proofs/src/plonk/verifier.rs`. +/// +/// Layout (in 32-byte words, big-endian). The header slots are generated from +/// Rust's `VkHeaderLayout`; the byte offsets are absolute from the start of the +/// VK payload, not from byte 0 of the runtime. Runtime byte 0 is the INVALID +/// prefix; the verifier loads the payload via +/// `extcodecopy(vk, VK_MPTR, 0x01, vk_payload_len)` and then references each +/// slot by `VK_MPTR + i`. +/// +/// word 0 : vk_digest (Fq, transcript_repr of the CS) +/// word 1 : num_instances +/// word 2 : k (log2 of the domain size) +/// word 3 : n_inv (1/n in Fr) +/// word 4 : omega (n-th primitive root of unity) +/// word 5 : omega_inv +/// word 6 : omega_inv_to_l (omega_inv ^ |rotation_last|) +/// word 7 : has_accumulator (0 or 1) +/// word 8 : acc_offset (instance index of the accumulator) +/// word 9 : num_acc_limbs +/// word 10 : num_acc_limb_bits +/// word 11..14 : G1_BASE (4 words, EIP-2537 padded) +/// word 15..22 : G2_BASE (8 words, EIP-2537 padded) +/// word 23..30 : NEG_S_G2_BASE (8 words, EIP-2537 padded) +/// word 31..30 + Q_PAYLOAD : quotient VM constants + packed bytecode +/// word 31 + Q_PAYLOAD .. : fixed_comms (4 words each) +/// word 31 + Q_PAYLOAD + 4*N_FIXED .. +/// : permutation_comms (4 words each) +/// +/// Notes: +/// - `extcodehash` of this contract is pinned by the linked verifier via +/// `EXPECTED_VK_CODEHASH`, so any byte tweak is detected at deploy time. +/// - The quotient identity interpreter's static program is stored in this +/// pinned VK runtime. The verifier reads it from memory after `extcodecopy`, +/// avoiding verifier-side PUSH32/mstore immediates while keeping the program +/// covered by `EXPECTED_VK_CODEHASH`. +/// - The midnight-proofs migration bakes the per-lookup chunk counts, trashcan +/// structure, and `num_simple_selectors` into the generated verifier code. +contract Halo2VerifyingKey { + /// @notice Deploy the verifying-key payload as this contract's runtime bytecode. + /// @dev The constructor writes an INVALID byte followed by generated words into memory and returns that prefixed runtime. + /// @dev The transient construction buffer starts at `0x80`, preserving Solidity's reserved memory words. + constructor() { + assembly { + // Runtime layout: + // byte 0 : INVALID, so the payload cannot be executed + // byte 1..end : generated VK payload copied by Halo2Verifier + // + // `runtime` includes the INVALID prefix; `payload` points to word + // zero of the verifier-key data described in the contract NatSpec. + let runtime := 0x80 + let payload := add(runtime, 0x01) + mstore8(runtime, 0xfe) + // Header, base-point, and quotient-program words generated from + // VkPayloadLayout. The inline names on each mstore identify the + // exact slot in the rendered source. + mstore(add(payload, 0x0000), 0x56c0824fcff237dd8dc7b15f527346d9e1647d191815acb142500b0293e84f66) // vk_digest + mstore(add(payload, 0x0020), 0x0000000000000000000000000000000000000000000000000000000000000013) // num_instances + mstore(add(payload, 0x0040), 0x0000000000000000000000000000000000000000000000000000000000000014) // k + mstore(add(payload, 0x0060), 0x73eda0144f284aae5b6554d46c21576b363d4ec725be2bff1a400fff00001001) // n_inv + mstore(add(payload, 0x0080), 0x03e1c54bcb947035a57a6e07cb98de4a2f69e02d265e09d9fece7e0e39898d4b) // omega + mstore(add(payload, 0x00a0), 0x6c39442eade0092768ac033fa6f608750624a1bb17dbc026ef97c3573a28fc8c) // omega_inv + mstore(add(payload, 0x00c0), 0x2a0ccbaa0613f093f2bb6e97859513f0b613d8587eaa92db9e5604b8d6b68d45) // omega_inv_to_l + mstore(add(payload, 0x00e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // has_accumulator + mstore(add(payload, 0x0100), 0x000000000000000000000000000000000000000000000000000000000000000b) // acc_offset + mstore(add(payload, 0x0120), 0x0000000000000000000000000000000000000000000000000000000000000007) // num_acc_limbs + mstore(add(payload, 0x0140), 0x0000000000000000000000000000000000000000000000000000000000000038) // num_acc_limb_bits + mstore(add(payload, 0x0160), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) // g1_x_hi + mstore(add(payload, 0x0180), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) // g1_x_lo + mstore(add(payload, 0x01a0), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) // g1_y_hi + mstore(add(payload, 0x01c0), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) // g1_y_lo + mstore(add(payload, 0x01e0), 0x00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051) // g2_x_c0_hi + mstore(add(payload, 0x0200), 0xc6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8) // g2_x_c0_lo + mstore(add(payload, 0x0220), 0x0000000000000000000000000000000013e02b6052719f607dacd3a088274f65) // g2_x_c1_hi + mstore(add(payload, 0x0240), 0x596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e) // g2_x_c1_lo + mstore(add(payload, 0x0260), 0x000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351a) // g2_y_c0_hi + mstore(add(payload, 0x0280), 0xadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801) // g2_y_c0_lo + mstore(add(payload, 0x02a0), 0x000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99) // g2_y_c1_hi + mstore(add(payload, 0x02c0), 0xcb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be) // g2_y_c1_lo + mstore(add(payload, 0x02e0), 0x000000000000000000000000000000000632aaf712568f19c297802268a7ad9d) // neg_s_g2_x_c0_hi + mstore(add(payload, 0x0300), 0xceea6ef7ab6f75a7d26781c8e90c7432bc5e99dcc219ba64010f3052123983ab) // neg_s_g2_x_c0_lo + mstore(add(payload, 0x0320), 0x00000000000000000000000000000000191ff4920e077a2f8cb3969ba8f05bc2) // neg_s_g2_x_c1_hi + mstore(add(payload, 0x0340), 0xaa9da8c95d640b1e051be7cf344ee7f01996df2568bf0e7ccd9eb70978820045) // neg_s_g2_x_c1_lo + mstore(add(payload, 0x0360), 0x0000000000000000000000000000000005f434ebf45460a864ad5b17497c7903) // neg_s_g2_y_c0_hi + mstore(add(payload, 0x0380), 0x71820c70c83aa186029536d22dff54373251152c28bc43269f95281eba1b012e) // neg_s_g2_y_c0_lo + mstore(add(payload, 0x03a0), 0x0000000000000000000000000000000004d1c747141bcac15e77e3e1d3853254) // neg_s_g2_y_c1_hi + mstore(add(payload, 0x03c0), 0xc8687afdad35345a04f79d9c2759007f6640676eb44aee7011ce5ad80744bb23) // neg_s_g2_y_c1_lo + mstore(add(payload, 0x03e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // quotient_const + mstore(add(payload, 0x0400), 0x00bbe1fbe9ef1e2d62490b03a82bf9ef10f5e9b2323033669cf6c50481f63e05) // quotient_const + mstore(add(payload, 0x0420), 0x0000000000000000000000000000000000000000000000000100000000000000) // quotient_const + mstore(add(payload, 0x0440), 0x0000000000000000000000000000000000010000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0460), 0x0000000000000000000000000000000000000000000000000000000400000000) // quotient_const + mstore(add(payload, 0x0480), 0x0000000000000000000000000000000000000000040000000000000000000000) // quotient_const + mstore(add(payload, 0x04a0), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x04c0), 0x0000000000000000000000000000000000000000000000100000000000000000) // quotient_const + mstore(add(payload, 0x04e0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000) // quotient_const + mstore(add(payload, 0x0500), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefeffffff00000001) // quotient_const + mstore(add(payload, 0x0520), 0x73eda753299d7d483339d80809a1d80553bca402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0540), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffb00000001) // quotient_const + mstore(add(payload, 0x0560), 0x73eda753299d7d483339d80809a1d80553bda402fbfe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0580), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffefffff001) // quotient_const + mstore(add(payload, 0x05a0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5beeffffffff00000001) // quotient_const + mstore(add(payload, 0x05c0), 0x00000000000000000000000000000010ff0726c5de281020ad8016cf6f691213) // quotient_const + mstore(add(payload, 0x05e0), 0x0000000000000000000000000000002c64068790f917282347187665718b04c8) // quotient_const + mstore(add(payload, 0x0600), 0x00000000000000000000000000000027241bb5338dce8a77499428839473bf3a) // quotient_const + mstore(add(payload, 0x0620), 0x0000000000000000000000000000002b7c4a26a1c7ae6fc4b499d04e4a463c4b) // quotient_const + mstore(add(payload, 0x0640), 0x000000000000000000000000000000274bc40fcf526be95333a8c22c79465298) // quotient_const + mstore(add(payload, 0x0660), 0x0000000000000000000000000000002a5ee6db49930276e2939d1c43ac82f744) // quotient_const + mstore(add(payload, 0x0680), 0x73eda753299d7d483339d80809a1d7edd77e26c51c38afb5debf8afa00c15cc3) // quotient_const + mstore(add(payload, 0x06a0), 0x73eda753299d7d483339d80809a1d7c553bda402fffe5bfeffffffff00000002) // quotient_const + mstore(add(payload, 0x06c0), 0x73eda753299d7d483339d80809a1d80553bda402fffe53ebc627fffef6280001) // quotient_const + mstore(add(payload, 0x06e0), 0x0000000000000000000001000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0700), 0x0000000100000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0720), 0x6bc66e553973f396854f5626172ba135587d41e37a68209402355093fdcaaf6c) // quotient_const + mstore(add(payload, 0x0740), 0x63f31e3f446953960c9d6964474300df43ab29179970f642a28e39d6c883c74b) // quotient_const + mstore(add(payload, 0x0760), 0x73eda753299d7d483339d70809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0780), 0x73eda752299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x07a0), 0x082738fdf02989b1adea81e1f27636cffb40621f85963b6afdcaaf6b02355095) // quotient_const + mstore(add(payload, 0x07c0), 0x0ffa8913e53429b2269c6ea3c25ed72610127aeb668d65bc5d71c628377c38b6) // quotient_const + mstore(add(payload, 0x07e0), 0x01ec1c0519185dfe86132d479c76d0786e1e037d0b05ca47648a1c5d29492c9b) // quotient_const + mstore(add(payload, 0x0800), 0x1e179025ca2470882b34e63940ccbd7ad9090bf414d43b696e093b5a8782528f) // quotient_const + mstore(add(payload, 0x0820), 0x4298bfee9a84c8ef8e83702075cb1abeb576f146636342e3db9ea6b0a4adf29d) // quotient_const + mstore(add(payload, 0x0840), 0x3c83b078e9abed278d042acc8f3bd21e228716f04be96af8025da860e2d1bba9) // quotient_const + mstore(add(payload, 0x0860), 0x427868260f487d1ef07edaadf37f5dbe705bd1318290f2577ae756b009c24f11) // quotient_const + mstore(add(payload, 0x0880), 0x03020e6a35e595abd22838beeadc45cfcb0545d85ca0ab2c59d44c203fac84a7) // quotient_const + mstore(add(payload, 0x08a0), 0x000000000000000000000000000000000000000000000000d201000000010000) // quotient_const + mstore(add(payload, 0x08c0), 0x0000000100001b7c3f8d3fe3c5b448f1bdeb2ae34698b72d6ce966fc208c05ed) // quotient_const + mstore(add(payload, 0x08e0), 0x73eda753299d7d483339d80809a1d7fd4057a4c12f26d1c1778e3360a6820001) // quotient_const + mstore(add(payload, 0x0900), 0x057797fa7060856f215654ff11006fe0acf6a437e9477bf6f782dfac86f2cf75) // quotient_const + mstore(add(payload, 0x0920), 0x0000000000000000000000000000000000000000000000000000000000000002) // quotient_const + mstore(add(payload, 0x0940), 0x0000000000000000000000000000000000000000000000000200000000000000) // quotient_const + mstore(add(payload, 0x0960), 0x0000000000000000000000000000000000020000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0980), 0x73eda753299d7d483339d80809a1d7e13511a4044eaa5bff4600ffff00005556) // quotient_const + mstore(add(payload, 0x09a0), 0x73eda753299d7d483339d80809a1d7c553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x09c0), 0x73eda753299d7d483339d80809a1d7d340dd972492de594de627fffefcb80349) // quotient_const + mstore(add(payload, 0x09e0), 0x73eda753299d7d483339d80809a1d7dbdc391ab4d8ac003bbd48021b82456c9b) // quotient_const + mstore(add(payload, 0x0a00), 0x73eda753299d7d483339d80809a1d7d1448ee5caf5eefcffbc9fabc57759e23f) // quotient_const + mstore(add(payload, 0x0a20), 0x73eda753299d7d483339d80809a1d80418c74cc18bb28443d3978d1fd47ffce1) // quotient_const + mstore(add(payload, 0x0a40), 0x0000000000000000000000000000006425c019bcda40056233b00000068ff970) // quotient_const + mstore(add(payload, 0x0a60), 0x00000000000000000000000000000052ef09129c4ea4b786856ffbc6fb7526cc) // quotient_const + mstore(add(payload, 0x0a80), 0x73eda753299d7d483339d80809a1d7d89a085d30fc8a7106a170ac2377c34ab9) // quotient_const + mstore(add(payload, 0x0aa0), 0x73eda753299d7d483339d80809a1d7f8ce65bb936f2d836012914aca65f077e1) // quotient_const + mstore(add(payload, 0x0ac0), 0x000000000000000000000000000000681e5d7c70141ebdfe86c0a873114c3b84) // quotient_const + mstore(add(payload, 0x0ae0), 0x000000000000000000000000000000297784894e27525bc342b7fde37dba9366) // quotient_const + mstore(add(payload, 0x0b00), 0x0000000000000000000000000000000275ecae82e897af7658d0e5be57000640) // quotient_const + mstore(add(payload, 0x0b20), 0x000000000000000000000000000000013af65741744bd7bb2c6872df2b800320) // quotient_const + mstore(add(payload, 0x0b40), 0x00000000000000000000000000000059736a8da406e7d5f0bd1ea7b710796a90) // quotient_const + mstore(add(payload, 0x0b60), 0x0000000000000000000000000000000c8557e86f90d0d89eed6eb5349a0f8820) // quotient_const + mstore(add(payload, 0x0b80), 0x0453ae02a5f228d8f956b5eab4fc92bbeea5eb26b6ae4b42b4fdfcfdf026aa22) // quotient_const + mstore(add(payload, 0x0ba0), 0x0000000000000000000000000000000000000000000000000000000800000000) // quotient_const + mstore(add(payload, 0x0bc0), 0x0000000000000000000000000000000000000000080000000000000000000000) // quotient_const + mstore(add(payload, 0x0be0), 0x0000000000000000000000000000000000000000000000000000000000002000) // quotient_const + mstore(add(payload, 0x0c00), 0x0000000000000000000000000000000000000000000000200000000000000000) // quotient_const + mstore(add(payload, 0x0c20), 0x73eda753299d7d483339d80809a1d7f454b67d3d21d64bde527fe92f9096edee) // quotient_const + mstore(add(payload, 0x0c40), 0x73eda753299d7d483339d80809a1d7d8efb71c7206e733dbb8e789998e74fb39) // quotient_const + mstore(add(payload, 0x0c60), 0x73eda753299d7d483339d80809a1d7de2fa1eecf722fd187b66bd77b6b8c40c7) // quotient_const + mstore(add(payload, 0x0c80), 0x73eda753299d7d483339d80809a1d7d9d7737d61384fec3a4b662fb0b5b9c3b6) // quotient_const + mstore(add(payload, 0x0ca0), 0x73eda753299d7d483339d80809a1d7de07f99433ad9272abcc573dd286b9ad69) // quotient_const + mstore(add(payload, 0x0cc0), 0x73eda753299d7d483339d80809a1d7daf4d6c8b96cfbe51c6c62e3bb537d08bd) // quotient_const + mstore(add(payload, 0x0ce0), 0x00000000000000000000000000000021fe0e4d8bbc5020415b002d9eded22426) // quotient_const + mstore(add(payload, 0x0d00), 0x00000000000000000000000000000058c80d0f21f22e50468e30eccae3160990) // quotient_const + mstore(add(payload, 0x0d20), 0x0000000000000000000000000000004e48376a671b9d14ee9328510728e77e74) // quotient_const + mstore(add(payload, 0x0d40), 0x00000000000000000000000000000056f8944d438f5cdf896933a09c948c7896) // quotient_const + mstore(add(payload, 0x0d60), 0x0000000000000000000000000000004e97881f9ea4d7d2a667518458f28ca530) // quotient_const + mstore(add(payload, 0x0d80), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb9194fffd) // quotient_const + mstore(add(payload, 0x0da0), 0x0000000000000000000002000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0dc0), 0x0000000200000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0de0), 0x639f3557494a69e4d764d44424b56a655d3cdfc3f4d1e529046aa128fb955ed7) // quotient_const + mstore(add(payload, 0x0e00), 0x53f8952b5f3529e3e600fac084e429b93398ae2c32e39086451c73ae91078e95) // quotient_const + mstore(add(payload, 0x0e20), 0x72018b4e10851f49ad26aac06d2b078ce59fa085f4f891b79b75e3a1d6b6d366) // quotient_const + mstore(add(payload, 0x0e40), 0x55d6172d5f790cc00804f1cec8d51a8a7ab4980eeb2a209591f6c4a4787dad72) // quotient_const + mstore(add(payload, 0x0e60), 0x3154e7648f18b458a4b667e793d6bd469e46b2bc9c9b191b2461594e5b520d64) // quotient_const + mstore(add(payload, 0x0e80), 0x3769f6da3ff19020a635ad3b7a6605e731368d12b414f106fda2579e1d2e4458) // quotient_const + mstore(add(payload, 0x0ea0), 0x31753f2d1a55002942bafd5a16227a46e361d2d17d6d69a78518a94ef63db0f0) // quotient_const + mstore(add(payload, 0x0ec0), 0x70eb98e8f3b7e79c61119f491ec5923588b85e2aa35db0d2a62bb3dec0537b5a) // quotient_const + mstore(add(payload, 0x0ee0), 0x03d8380a3230bbfd0c265a8f38eda0f0dc3c06fa160b948ec91438ba52925936) // quotient_const + mstore(add(payload, 0x0f00), 0x3c2f204b9448e1105669cc7281997af5b21217e829a876d2dc1276b50f04a51e) // quotient_const + mstore(add(payload, 0x0f20), 0x1143d88a0b6c1496e9cd0838e1f45d7817303e89c6c829c8b73d4d62495be539) // quotient_const + mstore(add(payload, 0x0f40), 0x0519b99ea9ba5d06e6ce7d9114d5cc36f15089dd97d479f104bb50c2c5a37751) // quotient_const + mstore(add(payload, 0x0f60), 0x110328f8f4f37cf5adc3dd53dd5ce3778cf9fe60052388aff5cead6113849e21) // quotient_const + mstore(add(payload, 0x0f80), 0x057797fa7060856f215655ff11006fee9a1697597c277945ddaadfac83aad2c0) // quotient_const + mstore(add(payload, 0x0fa0), 0x0000000000000000000000000000003212e00cde6d2002b119d800000347fcb8) // quotient_const + mstore(add(payload, 0x0fc0), 0x000000000000000000000000000000340f2ebe380a0f5eff4360543988a61dc2) // quotient_const + mstore(add(payload, 0x0fe0), 0x0000000000000000000000000000002cb9b546d20373eaf85e8f53db883cb548) // quotient_const + mstore(add(payload, 0x1000), 0x0453ae02a5f228d8f956b6eab50092aaff9ec460d8863b22077de62a80bd8812) // quotient_const + mstore(add(payload, 0x1020), 0x73eda753299d7d4833351088b4af7508df8b737010b26601ef73fcbb87bd0000) // quotient_const + mstore(add(payload, 0x1040), 0x0aef2ff4e0c10ade42aca9fe2200e00159ed486fd28ef7edef05bf590de59ef3) // quotient_const + mstore(add(payload, 0x1060), 0x0000000000000000000000000000000000000000000000000000000000000006) // quotient_const + mstore(add(payload, 0x1080), 0x0000000000000000000000000000000000000000000000000600000000000000) // quotient_const + mstore(add(payload, 0x10a0), 0x0000000000000000000000000000000000060000000000000000000000000000) // quotient_const + mstore(add(payload, 0x10c0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffffff) // quotient_const + mstore(add(payload, 0x10e0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefdffffff00000001) // quotient_const + mstore(add(payload, 0x1100), 0x73eda753299d7d483339d80809a1d80553bba402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1120), 0x0000000000000000000000000000000000000000000000000000000000000003) // quotient_const + mstore(add(payload, 0x1140), 0x0000000000000000000000000000000000030000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1160), 0x0000000000000000000000000000012c71404d368ec010269b10000013afec50) // quotient_const + mstore(add(payload, 0x1180), 0x000000000000000000000000000000f8cd1b37d4ebee2693904ff354f25f7464) // quotient_const + mstore(add(payload, 0x11a0), 0x000000000000000000000000000001385b1875503c5c39fb9441f95933e4b28c) // quotient_const + mstore(add(payload, 0x11c0), 0x0000000000000000000000000000007c668d9bea75f71349c827f9aa792fba32) // quotient_const + mstore(add(payload, 0x11e0), 0x0000000000000000000000000000000761c60b88b9c70e630a72b13b050012c0) // quotient_const + mstore(add(payload, 0x1200), 0x73eda753299d7d483339d80809a1d7a12dfd8a4625be569ccc4ffffef9700691) // quotient_const + mstore(add(payload, 0x1220), 0x73eda753299d7d483339d80809a1d7b264b49166b159a4787a900438048ad935) // quotient_const + mstore(add(payload, 0x1240), 0x00000000000000000000000000000003b0e305c45ce387318539589d82800960) // quotient_const + mstore(add(payload, 0x1260), 0x0000000000000000000000000000010c5a3fa8ec14b781d2375bf725316c3fb0) // quotient_const + mstore(add(payload, 0x1280), 0x000000000000000000000000000000259007b94eb27289dcc84c1f9dce2e9860) // quotient_const + mstore(add(payload, 0x12a0), 0x73eda753299d7d483339d80809a1d79d35602792ebdf9e00793f578beeb3c47d) // quotient_const + mstore(add(payload, 0x12c0), 0x73eda753299d7d483339d80809a1d802ddd0f5801766ac88a72f1a40a8fff9c1) // quotient_const + mstore(add(payload, 0x12e0), 0x73eda753299d7d483339d80809a1d7abe053165ef916860e42e15847ef869571) // quotient_const + mstore(add(payload, 0x1300), 0x73eda753299d7d483339d80809a1d7ec490dd323de5caac125229595cbe0efc1) // quotient_const + mstore(add(payload, 0x1320), 0x08a75c054be451b1f2ad6bd569f92577dd4bd64d6d5c968569fbf9fbe04d544d) // quotient_const + mstore(add(payload, 0x1340), 0x0000000000000000000000000000000000000000000000000000001800000000) // quotient_const + mstore(add(payload, 0x1360), 0x0000000000000000000000000000000000000000180000000000000000000000) // quotient_const + mstore(add(payload, 0x1380), 0x0000000000000000000000000000000000000000000000000000000000006000) // quotient_const + mstore(add(payload, 0x13a0), 0x0000000000000000000000000000000000000000000000600000000000000000) // quotient_const + mstore(add(payload, 0x13c0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffff700000001) // quotient_const + mstore(add(payload, 0x13e0), 0x73eda753299d7d483339d80809a1d80553bda402f7fe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1400), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffe001) // quotient_const + mstore(add(payload, 0x1420), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bdeffffffff00000001) // quotient_const + mstore(add(payload, 0x1440), 0x73eda753299d7d483339d80809a1d7e355af567743ae3bbda4ffd260212ddbdb) // quotient_const + mstore(add(payload, 0x1460), 0x73eda753299d7d483339d80809a1d7ac8bb094e10dd00bb871cf13341ce9f671) // quotient_const + mstore(add(payload, 0x1480), 0x73eda753299d7d483339d80809a1d7b70b86399be46147106cd7aef7d718818d) // quotient_const + mstore(add(payload, 0x14a0), 0x73eda753299d7d483339d80809a1d7ae5b2956bf70a17c7596cc5f626b73876b) // quotient_const + mstore(add(payload, 0x14c0), 0x73eda753299d7d483339d80809a1d7b6bc3584645b26895898ae7ba60d735ad1) // quotient_const + mstore(add(payload, 0x14e0), 0x73eda753299d7d483339d80809a1d7b095efed6fd9f96e39d8c5c777a6fa1179) // quotient_const + mstore(add(payload, 0x1500), 0x00000000000000000000000000000065fa2ae8a334f060c4110088dc9c766c72) // quotient_const + mstore(add(payload, 0x1520), 0x00000000000000000000000000000000000000000c0000000000000000000000) // quotient_const + mstore(add(payload, 0x1540), 0x0000000000000000000000000000010a58272d65d68af0d3aa92c660a9421cb0) // quotient_const + mstore(add(payload, 0x1560), 0x0000000000000000000000000000000000000000000000300000000000000000) // quotient_const + mstore(add(payload, 0x1580), 0x000000000000000000000000000000ead8a63f3552d73ecbb978f3157ab67b5c) // quotient_const + mstore(add(payload, 0x15a0), 0x000000000000000000000000000000852c1396b2eb457869d549633054a10e58) // quotient_const + mstore(add(payload, 0x15c0), 0x00000000000000000000000000000104e9bce7caae169e9c3b9ae1d5bda569c2) // quotient_const + mstore(add(payload, 0x15e0), 0x0000000000000000000000000000008274de73e5570b4f4e1dcd70eaded2b4e1) // quotient_const + mstore(add(payload, 0x1600), 0x000000000000000000000000000000ebc6985edbee8777f335f48d0ad7a5ef90) // quotient_const + mstore(add(payload, 0x1620), 0x0000000000000000000000000000007f1cb491dcb90764a7bad754cb0588e5cc) // quotient_const + mstore(add(payload, 0x1640), 0x73eda753299d7d48333049095fbd120c6b5942dd2166802b5297f978232a0002) // quotient_const + mstore(add(payload, 0x1660), 0x0000000000000000000006000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1680), 0x0000000600000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x16a0), 0x4302515f88a4431e1fbaccbc5adc8f25703b5745de78f77d0d3fe37cf2c01c83) // quotient_const + mstore(add(payload, 0x16c0), 0x140e70dbca64831b4b8f40317b68cd20f34ec27e98adf994cf555b0db316abbd) // quotient_const + mstore(add(payload, 0x16e0), 0x73eda753299d7d483339d60809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1700), 0x73eda751299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1720), 0x104e71fbe05313635bd503c3e4ec6d9ff680c43f0b2c76d5fb955ed6046aa12a) // quotient_const + mstore(add(payload, 0x1740), 0x1ff51227ca6853644d38dd4784bdae4c2024f5d6cd1acb78bae38c506ef8716c) // quotient_const + mstore(add(payload, 0x1760), 0x70156f48f76cc14b27137d78d0b4371477819d08e9f2c77036ebc744ad6da6cb) // quotient_const + mstore(add(payload, 0x1780), 0x37be870795549c37dcd00b9588085d0fa1ab8c1ad655e52c23ed8949f0fb5ae3) // quotient_const + mstore(add(payload, 0x17a0), 0x62a9cec91e3168b1496ccfcf27ad7a8d3c8d65793936323648c2b29cb6a41ac8) // quotient_const + mstore(add(payload, 0x17c0), 0x6ed3edb47fe320414c6b5a76f4cc0bce626d1a256829e20dfb44af3c3a5c88b0) // quotient_const + mstore(add(payload, 0x17e0), 0x62ea7e5a34aa00528575fab42c44f48dc6c3a5a2fadad34f0a31529dec7b61e0) // quotient_const + mstore(add(payload, 0x1800), 0x6de98a7ebdd251f08ee9668a33e94c65bdb3185246bd05a64c5767be80a6f6b3) // quotient_const + mstore(add(payload, 0x1820), 0x0b88a81e969233f724730fadaac8e2d294b414ee4222bdac5b3caa2ef7b70ba2) // quotient_const + mstore(add(payload, 0x1840), 0x0000000300000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1860), 0x409fb98f933d25e8d0038d4f7b2a98dbc278a3b57cfb0879943764202d0def59) // quotient_const + mstore(add(payload, 0x1880), 0x43fe0c177a010031bf648c1cc285529323863340cc562ac9e7aaad86598b55df) // quotient_const + mstore(add(payload, 0x18a0), 0x33cb899e22443dc4bd6718aaa5dd18684590bb9d54587d5a25b7e826dc13afab) // quotient_const + mstore(add(payload, 0x18c0), 0x5a46b0715e6d5198819eb2abc26638708b1b23dc3e7cb23c4a1bb20f9686f7ad) // quotient_const + mstore(add(payload, 0x18e0), 0x0f4d2cdbfd2f1714b46b78b33e8164a4d3f19d98c77d6dd30e31f24850ea65f3) // quotient_const + mstore(add(payload, 0x1900), 0x419d6a1793664a2e73d2a85da4119e5513d7a0cde3bde4e90718f923a87532fa) // quotient_const + mstore(add(payload, 0x1920), 0x33097aeadeda76e1094b97fb9816aa66a6edfb200f6a9a0fe16c08233a8dda63) // quotient_const + mstore(add(payload, 0x1940), 0x09062b3ea1b0c1037678aa3cc094d16f610fd18915e201850d7ce460bf058df5) // quotient_const + mstore(add(payload, 0x1960), 0x0456a29a706afacf2158850711006fe0acf6a437e9477bf6f782dfac86f2cf7b) // quotient_const + mstore(add(payload, 0x1980), 0x0397cc06bc030aab970dabe70cd498bbeea8daaea65607bb6a872125fec74a10) // quotient_const + mstore(add(payload, 0x19a0), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb91950003) // quotient_const + mstore(add(payload, 0x19c0), 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140) // quotient_const + mstore(add(payload, 0x19e0), 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a) // quotient_const + mstore(add(payload, 0x1a00), 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db) // quotient_const + mstore(add(payload, 0x1a20), 0x0594e0109a0005958008060d000b0200011b000021020103070002000094a002) // quotient_program + mstore(add(payload, 0x1a40), 0x94c00394e00495000595200095400295600395800695a00795c00895e0099600) // quotient_program + mstore(add(payload, 0x1a60), 0x0a96200b96400c96600d96800e96a00496c00596e006970007972094a0009540) // quotient_program + mstore(add(payload, 0x1a80), 0x0295600395800496c00596e006970007972094c00295400395600495800596c0) // quotient_program + mstore(add(payload, 0x1aa0), 0x0696e00797000f972094e00395400495600595800696c00796e00f9700109720) // quotient_program + mstore(add(payload, 0x1ac0), 0x95000495400595600695800796c00f96e0109700119720952005954006956007) // quotient_program + mstore(add(payload, 0x1ae0), 0x95800f96c01096e011970012972095a00695400795600f95801096c01196e012) // quotient_program + mstore(add(payload, 0x1b00), 0x970013972095c00795400f95601095801196c01296e013970014972015974016) // quotient_program + mstore(add(payload, 0x1b20), 0x97800b03000121021703070001000094a00294c00394e0189500199520009540) // quotient_program + mstore(add(payload, 0x1b40), 0x0295600395801a95a01b95c00895e00996000a96201c96401d96601e96801f96) // quotient_program + mstore(add(payload, 0x1b60), 0xa01896c01996e01a97001b972094a00095400295600395801896c01996e01a97) // quotient_program + mstore(add(payload, 0x1b80), 0x001b972094c00295400395601895801996c01a96e01b970020972094e0039540) // quotient_program + mstore(add(payload, 0x1ba0), 0x1895601995801a96c01b96e020970021972095001895401995601a95801b96c0) // quotient_program + mstore(add(payload, 0x1bc0), 0x2096e021970022972095201995401a95601b95802096c02196e0229700239720) // quotient_program + mstore(add(payload, 0x1be0), 0x95a01a95401b95602095802196c02296e023970024972095c01b954020956021) // quotient_program + mstore(add(payload, 0x1c00), 0x95802296c02396e02497002597202697400b0300011b00012102270100000900) // quotient_program + mstore(add(payload, 0x1c20), 0x0695a00795c00895e00996000a96200b96400c96600094a00294c00394e00495) // quotient_program + mstore(add(payload, 0x1c40), 0x000595200d96800e96a01597401697800b04000121022801000008001a95a01b) // quotient_program + mstore(add(payload, 0x1c60), 0x95c00895e00996000a96201c96401d96600094a00294c00394e0189500199520) // quotient_program + mstore(add(payload, 0x1c80), 0x1e96801f96a02697400b040001210397a0290000000b2b0894a00994c00a94e0) // quotient_program + mstore(add(payload, 0x1ca0), 0x2a95402b95602c95800895e00996000a96202d97402e97600894a095e00994a0) // quotient_program + mstore(add(payload, 0x1cc0), 0x96000a94a096200994c095e00a94c096002f94c096a00a94e095e02f94e09680) // quotient_program + mstore(add(payload, 0x1ce0), 0x3094e096a02f95009660309500968031950096a02f9520964030952096603195) // quotient_program + mstore(add(payload, 0x1d00), 0x20968032952096a000954095402b954095602c95409580039560956033956097) // quotient_program + mstore(add(payload, 0x1d20), 0x20339580970034958097202f95a096203095a096403195a096603295a0968035) // quotient_program + mstore(add(payload, 0x1d40), 0x95a096a02f95c096003095c096203195c096403295c096603595c096803695c0) // quotient_program + mstore(add(payload, 0x1d60), 0x96a03396c096e03496c097003796c097203896e096e03796e097003996e09720) // quotient_program + mstore(add(payload, 0x1d80), 0x3a970097003b970097203c972097200d000b050000210397a03d030800021508) // quotient_program + mstore(add(payload, 0x1da0), 0x94a00994c00a94e00b95000c95202a95402b95602c95800d95a00e95c00895e0) // quotient_program + mstore(add(payload, 0x1dc0), 0x0996000a96200b96400c96600d96800e96a03e96c03f96e040970041972094a0) // quotient_program + mstore(add(payload, 0x1de0), 0x0895e00996000a96200b96400c96600d96800e96a094c00995e00a96000b9620) // quotient_program + mstore(add(payload, 0x1e00), 0x0c96400d96600e96804296a094e00a95e00b96000c96200d96400e9660429680) // quotient_program + mstore(add(payload, 0x1e20), 0x4396a095000b95e00c96000d96200e96404296604396804496a095200c95e00d) // quotient_program + mstore(add(payload, 0x1e40), 0x96000e96204296404396604496804596a095400095402b95602c95803e96c03f) // quotient_program + mstore(add(payload, 0x1e60), 0x96e040970041972095a00d95e00e96004296204396404496604596804696a095) // quotient_program + mstore(add(payload, 0x1e80), 0xc00e95e04296004396204496404596604696804796a015974016978003956095) // quotient_program + mstore(add(payload, 0x1ea0), 0x603e956095803f956096c040956096e041956097004895609720059580958040) // quotient_program + mstore(add(payload, 0x1ec0), 0x958096c041958096e0489580970049958097200796c096c04896c096e04996c0) // quotient_program + mstore(add(payload, 0x1ee0), 0x97004a96c097201096e096e04a96e097004b96e0972012970097004c97009720) // quotient_program + mstore(add(payload, 0x1f00), 0x14972097200d000b050001210397a04d03080001150894a00994c00a94e01c95) // quotient_program + mstore(add(payload, 0x1f20), 0x001d95202a95402b95602c95801e95a01f95c00895e00996000a96201c96401d) // quotient_program + mstore(add(payload, 0x1f40), 0x96601e96801f96a04e96c04f96e050970051972094a00895e00996000a96201c) // quotient_program + mstore(add(payload, 0x1f60), 0x96401d96601e96801f96a094c00995e00a96001c96201d96401e96601f968052) // quotient_program + mstore(add(payload, 0x1f80), 0x96a094e00a95e01c96001d96201e96401f96605296805396a095001c95e01d96) // quotient_program + mstore(add(payload, 0x1fa0), 0x001e96201f96405296605396805496a095201d95e01e96001f96205296405396) // quotient_program + mstore(add(payload, 0x1fc0), 0x605496805596a095400095402b95602c95804e96c04f96e050970051972095a0) // quotient_program + mstore(add(payload, 0x1fe0), 0x1e95e01f96005296205396405496605596805696a095c01f95e0529600539620) // quotient_program + mstore(add(payload, 0x2000), 0x5496405596605696805796a026974003956095604e956095804f956096c05095) // quotient_program + mstore(add(payload, 0x2020), 0x6096e051956097005895609720199580958050958096c051958096e058958097) // quotient_program + mstore(add(payload, 0x2040), 0x0059958097201b96c096c05896c096e05996c097005a96c097202196e096e05a) // quotient_program + mstore(add(payload, 0x2060), 0x96e097005b96e0972023970097005c9700972025972097200d000b0500012103) // quotient_program + mstore(add(payload, 0x2080), 0x97a05d0000000c390894a00994c00a94e02d97402e97600097a00097c00297e0) // quotient_program + mstore(add(payload, 0x20a0), 0x0398000898a00998c00a98e000954097c002954097e0039540980008954098a0) // quotient_program + mstore(add(payload, 0x20c0), 0x09954098c00a954098e002956097c003956097e05e9560988009956098a00a95) // quotient_program + mstore(add(payload, 0x20e0), 0x6098c02f9560996003958097c05e9580986038958098800a958098a02f958099) // quotient_program + mstore(add(payload, 0x2100), 0x4030958099600095e097a002960097a003962097a05e96c098403896c098605f) // quotient_program + mstore(add(payload, 0x2120), 0x96c098802f96c099203096c099403196c099605e96e098203896e098405f96e0) // quotient_program + mstore(add(payload, 0x2140), 0x98603a96e098802f96e099003096e099203196e099403296e099605e97009800) // quotient_program + mstore(add(payload, 0x2160), 0x38970098205f970098403a9700986060970098802f970098e030970099003197) // quotient_program + mstore(add(payload, 0x2180), 0x009920329700994035970099605e972097e038972098005f972098203a972098) // quotient_program + mstore(add(payload, 0x21a0), 0x4060972098603c972098802f972098c030972098e03197209900329720992035) // quotient_program + mstore(add(payload, 0x21c0), 0x9720994036972099600d000b060000210397a061020f000a001697800097a000) // quotient_program + mstore(add(payload, 0x21e0), 0x97c00297e00398000498200598400698600798800898a00998c00a98e00b9900) // quotient_program + mstore(add(payload, 0x2200), 0x0c992097a00095e00296000396200496400596600696800796a097c000954002) // quotient_program + mstore(add(payload, 0x2220), 0x95600395800496c00596e006970007972097e00295400395600495800596c006) // quotient_program + mstore(add(payload, 0x2240), 0x96e00797000f972098000395400495600595800696c00796e00f970010972098) // quotient_program + mstore(add(payload, 0x2260), 0x200495400595600695800796c00f96e010970011972098400595400695600795) // quotient_program + mstore(add(payload, 0x2280), 0x800f96c01096e011970012972098600695400795600f95801096c01196e01297) // quotient_program + mstore(add(payload, 0x22a0), 0x0013972098800795400f95601095801196c01296e013970014972095400898a0) // quotient_program + mstore(add(payload, 0x22c0), 0x0998c00a98e00b99000c99200d99400e996095600998a00a98c00b98e00c9900) // quotient_program + mstore(add(payload, 0x22e0), 0x0d99200e994042996095800a98a00b98c00c98e00d99000e9920429940439960) // quotient_program + mstore(add(payload, 0x2300), 0x96c00b98a00c98c00d98e00e990042992043994044996096e00c98a00d98c00e) // quotient_program + mstore(add(payload, 0x2320), 0x98e042990043992044994045996097000d98a00e98c04298e043990044992045) // quotient_program + mstore(add(payload, 0x2340), 0x994046996097200e98a04298c04398e04499004599204699404799600894a009) // quotient_program + mstore(add(payload, 0x2360), 0x94c00a94e00b95000c95200d95a00e95c01597400d99400e99600d000b060001) // quotient_program + mstore(add(payload, 0x2380), 0x210397a062020f0009000097a00097c00297e00398001898201998401a98601b) // quotient_program + mstore(add(payload, 0x23a0), 0x98800898a00998c00a98e01c99001d99201e994097a00095e002960003962018) // quotient_program + mstore(add(payload, 0x23c0), 0x96401996601a96801b96a097c00095400295600395801896c01996e01a97001b) // quotient_program + mstore(add(payload, 0x23e0), 0x972097e00295400395601895801996c01a96e01b970020972098000395401895) // quotient_program + mstore(add(payload, 0x2400), 0x601995801a96c01b96e020970021972098201895401995601a95801b96c02096) // quotient_program + mstore(add(payload, 0x2420), 0xe021970022972098401995401a95601b95802096c02196e02297002397209860) // quotient_program + mstore(add(payload, 0x2440), 0x1a95401b95602095802196c02296e023970024972098801b9540209560219580) // quotient_program + mstore(add(payload, 0x2460), 0x2296c02396e024970025972095400898a00998c00a98e01c99001d99201e9940) // quotient_program + mstore(add(payload, 0x2480), 0x1f996095600998a00a98c01c98e01d99001e99201f994052996095800a98a01c) // quotient_program + mstore(add(payload, 0x24a0), 0x98c01d98e01e99001f992052994053996096c01c98a01d98c01e98e01f990052) // quotient_program + mstore(add(payload, 0x24c0), 0x992053994054996096e01d98a01e98c01f98e052990053992054994055996097) // quotient_program + mstore(add(payload, 0x24e0), 0x001e98a01f98c05298e053990054992055994056996097201f98a05298c05398) // quotient_program + mstore(add(payload, 0x2500), 0xe05499005599205699405799600894a00994c00a94e01c95001d95201e95a01f) // quotient_program + mstore(add(payload, 0x2520), 0x95c02697401f99600d000b060001210397a0630000000b2b6494a06594c06694) // quotient_program + mstore(add(payload, 0x2540), 0xe06795406895606995806795e06896006996202d97402e97606a94a094a06594) // quotient_program + mstore(add(payload, 0x2560), 0xa094c06694a094e06b94c094c06c94c095c06c94e095a06d94e095c06c950095) // quotient_program + mstore(add(payload, 0x2580), 0x206d950095a06e950095c06f952095206e952095a070952095c067954095e068) // quotient_program + mstore(add(payload, 0x25a0), 0x95409600699540962068956095e0699560960071956096a069958095e0719580) // quotient_program + mstore(add(payload, 0x25c0), 0x968072958096a07395a095a07495a095c07595c095c071960097207196209700) // quotient_program + mstore(add(payload, 0x25e0), 0x729620972071964096e07296409700769640972071966096c072966096e07696) // quotient_program + mstore(add(payload, 0x2600), 0x609700779660972072968096c076968096e0779680970078968097207696a096) // quotient_program + mstore(add(payload, 0x2620), 0xc07796a096e07896a097007996a097200d000b070000210397a07a0308000215) // quotient_program + mstore(add(payload, 0x2640), 0x6494a06594c06694e07b95007c95206795406895606995807d95a07e95c06795) // quotient_program + mstore(add(payload, 0x2660), 0xe06896006996207f96408096608196808296a07f96c08096e081970082972094) // quotient_program + mstore(add(payload, 0x2680), 0xa06a94a06594c06694e07b95007c95207d95a07e95c095406795e06896006996) // quotient_program + mstore(add(payload, 0x26a0), 0x207f96408096608196808296a095606895e06996007f96208096408196608296) // quotient_program + mstore(add(payload, 0x26c0), 0x808396a095806995e07f96008096208196408296608396808496a096c07f95e0) // quotient_program + mstore(add(payload, 0x26e0), 0x8096008196208296408396608496808596a096e08095e0819600829620839640) // quotient_program + mstore(add(payload, 0x2700), 0x8496608596808696a097008195e08296008396208496408596608696808796a0) // quotient_program + mstore(add(payload, 0x2720), 0x97208295e08396008496208596408696608796808896a01597401697806b94c0) // quotient_program + mstore(add(payload, 0x2740), 0x94c07b94c094e07c94c095007d94c095207e94c095a08994c095c08a94e094e0) // quotient_program + mstore(add(payload, 0x2760), 0x7d94e095007e94e095208994e095a08b94e095c08c9500950089950095208b95) // quotient_program + mstore(add(payload, 0x2780), 0x0095a08d950095c08e952095208d952095a08f952095c09095a095a09195a095) // quotient_program + mstore(add(payload, 0x27a0), 0xc09295c095c00d000b070001210397a09303080001156494a06594c06694e094) // quotient_program + mstore(add(payload, 0x27c0), 0x95009595206795406895606995809695a09795c06795e0689600699620989640) // quotient_program + mstore(add(payload, 0x27e0), 0x9996609a96809b96a09896c09996e09a97009b972094a06a94a06594c06694e0) // quotient_program + mstore(add(payload, 0x2800), 0x9495009595209695a09795c095406795e06896006996209896409996609a9680) // quotient_program + mstore(add(payload, 0x2820), 0x9b96a095606895e06996009896209996409a96609b96809c96a095806995e098) // quotient_program + mstore(add(payload, 0x2840), 0x96009996209a96409b96609c96809d96a096c09895e09996009a96209b96409c) // quotient_program + mstore(add(payload, 0x2860), 0x96609d96809e96a096e09995e09a96009b96209c96409d96609e96809f96a097) // quotient_program + mstore(add(payload, 0x2880), 0x009a95e09b96009c96209d96409e96609f9680a096a097209b95e09c96009d96) // quotient_program + mstore(add(payload, 0x28a0), 0x209e96409f9660a09680a196a02697406b94c094c09494c094e09594c0950096) // quotient_program + mstore(add(payload, 0x28c0), 0x94c095209794c095a0a294c095c0a394e094e09694e095009794e09520a294e0) // quotient_program + mstore(add(payload, 0x28e0), 0x95a0a494e095c0a595009500a295009520a4950095a0a6950095c0a795209520) // quotient_program + mstore(add(payload, 0x2900), 0xa6952095a0a8952095c0a995a095a0aa95a095c0ab95c095c00d000b07000121) // quotient_program + mstore(add(payload, 0x2920), 0x0397a0ac0000000e100094a00294c00394e06795406895606995800095e00296) // quotient_program + mstore(add(payload, 0x2940), 0x000396202d97402e97600097c00297e003980008954095406895409560699540) // quotient_program + mstore(add(payload, 0x2960), 0x95800a956095607195609720719580970072958097207196c096e07296c09700) // quotient_program + mstore(add(payload, 0x2980), 0x7696c097203096e096e07696e097007796e09720329700970078970097203697) // quotient_program + mstore(add(payload, 0x29a0), 0x2097200d000b080000210397a0ad04010002150094a00294c00394e004950005) // quotient_program + mstore(add(payload, 0x29c0), 0x95206795406895606995800695a00795c00095e0029600039620049640059660) // quotient_program + mstore(add(payload, 0x29e0), 0x0696800796a07f96c08096e08197008297200097c00297e00398000498200598) // quotient_program + mstore(add(payload, 0x2a00), 0x4006986007988095400895406895606995807f96c08096e08197008297201597) // quotient_program + mstore(add(payload, 0x2a20), 0x401697800a956095607f9560958080956096c081956096e08295609700839560) // quotient_program + mstore(add(payload, 0x2a40), 0x97200c9580958081958096c082958096e0839580970084958097200e96c096c0) // quotient_program + mstore(add(payload, 0x2a60), 0x8396c096e08496c097008596c097204396e096e08596e097008696e097204597) // quotient_program + mstore(add(payload, 0x2a80), 0x009700879700972047972097200d000b080001210397a0ae04010001150094a0) // quotient_program + mstore(add(payload, 0x2aa0), 0x0294c00394e01895001995206795406895606995801a95a01b95c00095e00296) // quotient_program + mstore(add(payload, 0x2ac0), 0x000396201896401996601a96801b96a09896c09996e09a97009b97200097c002) // quotient_program + mstore(add(payload, 0x2ae0), 0x97e00398001898201998401a98601b988095400895406895606995809896c099) // quotient_program + mstore(add(payload, 0x2b00), 0x96e09a97009b97202697400a95609560989560958099956096c09a956096e09b) // quotient_program + mstore(add(payload, 0x2b20), 0x956097009c956097201d958095809a958096c09b958096e09c958097009d9580) // quotient_program + mstore(add(payload, 0x2b40), 0x97201f96c096c09c96c096e09d96c097009e96c097205396e096e09e96e09700) // quotient_program + mstore(add(payload, 0x2b60), 0x9f96e097205597009700a09700972057972097200d000b0800010594a01194a0) // quotient_program + mstore(add(payload, 0x2b80), 0x1194a005950008060d000b0900000594c01194c01194c005952008060d000b09) // quotient_program + mstore(add(payload, 0x2ba0), 0x00010594e01194e01194e00595a008060d000b0900011b00021b000305958008) // quotient_program + mstore(add(payload, 0x2bc0), 0x109aa00594a01194a01195000daf060594c01194c01195200db0060594e01194) // quotient_program + mstore(add(payload, 0x2be0), 0xe01195a00db1060d000b090001191f0000000000000000000000000000000000) // quotient_program + // Fixed-column commitment 0, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2c00), 0x0000000000000000000000000000000016e98a681dca730dcbe651aec5493976) // fixed_comms[0].x_hi + mstore(add(payload, 0x2c20), 0x11944be61932e7d19a63786871335939b0eefa7e32507b68b4ad6fd89a5c1028) // fixed_comms[0].x_lo + mstore(add(payload, 0x2c40), 0x0000000000000000000000000000000010e6ae8d3becb251e60db7d788be7029) // fixed_comms[0].y_hi + mstore(add(payload, 0x2c60), 0x8dbcdfdbb394da2ca9725e07485d6b630569a66ec867aa0d605573ae82d550df) // fixed_comms[0].y_lo + // Fixed-column commitment 1, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2c80), 0x000000000000000000000000000000000fb2ef3076aa1a8068bec7ef35167c1c) // fixed_comms[1].x_hi + mstore(add(payload, 0x2ca0), 0xf1f26a24abaa3b4dc8c6d218021c08429e4c175654b5996eb4de74d17a5c188d) // fixed_comms[1].x_lo + mstore(add(payload, 0x2cc0), 0x00000000000000000000000000000000000f94fa3dcc144ffe6a4ace1de5f956) // fixed_comms[1].y_hi + mstore(add(payload, 0x2ce0), 0x76ad02ffc32c0345767b466f064aec3f1101ecd9eaf91ba5d4c145f792d1285a) // fixed_comms[1].y_lo + // Fixed-column commitment 2, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2d00), 0x0000000000000000000000000000000016909a26057dec4152b5bd207c37f9a8) // fixed_comms[2].x_hi + mstore(add(payload, 0x2d20), 0xc3af2c8cfa63dfd83e34704255392445fae3c47d749e435e0291232659a8caeb) // fixed_comms[2].x_lo + mstore(add(payload, 0x2d40), 0x0000000000000000000000000000000000b9edb176686ec391a3684928dc0842) // fixed_comms[2].y_hi + mstore(add(payload, 0x2d60), 0x80c60eca091b8a3925e31ef1032793f2ef4e31225ab560bf721f17525dd476c8) // fixed_comms[2].y_lo + // Fixed-column commitment 3, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2d80), 0x0000000000000000000000000000000014861e20b064f637d60cc250ff3b8804) // fixed_comms[3].x_hi + mstore(add(payload, 0x2da0), 0x9b9697b5245d031c784398bdf950e99d7968c86e92c4ec1cb3821a69686dfb09) // fixed_comms[3].x_lo + mstore(add(payload, 0x2dc0), 0x000000000000000000000000000000000c117b524f84a06b024a40003e1c3aab) // fixed_comms[3].y_hi + mstore(add(payload, 0x2de0), 0x25253a3cf5b53d2f77d1f9328a9c0b32bea4f591eb470dbb599b5fbdfa7df46b) // fixed_comms[3].y_lo + // Fixed-column commitment 4, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2e00), 0x000000000000000000000000000000001420bb2d8c182dda6c982eb820e035f6) // fixed_comms[4].x_hi + mstore(add(payload, 0x2e20), 0xa4a5c6c69376dfda17ac5588d34a0b313800b8d18ecd818f3e4e2671589ec691) // fixed_comms[4].x_lo + mstore(add(payload, 0x2e40), 0x0000000000000000000000000000000000855eb6333071d5bb90a0351dbf958c) // fixed_comms[4].y_hi + mstore(add(payload, 0x2e60), 0x13e5648bcafb55776c78e46031e4620cfda02728e26bb97f067a9a0cdf72aa9f) // fixed_comms[4].y_lo + // Fixed-column commitment 5, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2e80), 0x000000000000000000000000000000000707698990b767903c9aaf028c98b46a) // fixed_comms[5].x_hi + mstore(add(payload, 0x2ea0), 0xc4925e4e30db42105aade1a5cd7dd9b3d2222bd7085071b5c2212709cfcc2d46) // fixed_comms[5].x_lo + mstore(add(payload, 0x2ec0), 0x000000000000000000000000000000000a88a7d4f1148bd3efffaedb5be62777) // fixed_comms[5].y_hi + mstore(add(payload, 0x2ee0), 0x27fad92f1005d28d12ef861b5dcf8b580a1322048d469da5f84b26fa7f8d2e4d) // fixed_comms[5].y_lo + // Fixed-column commitment 6, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2f00), 0x000000000000000000000000000000001495975f8f8c0cb6c54e1163fe045732) // fixed_comms[6].x_hi + mstore(add(payload, 0x2f20), 0x2be3eb4af919cb0eb6126028ebeab81d075dff85aa522989166bf6e01880c1be) // fixed_comms[6].x_lo + mstore(add(payload, 0x2f40), 0x0000000000000000000000000000000018bdd4eef361f4a6d23a6511b48e20fb) // fixed_comms[6].y_hi + mstore(add(payload, 0x2f60), 0x4080898637660f3f3371adfb48585dd2ad89c29cc260d604ce9cd68a31ff427d) // fixed_comms[6].y_lo + // Fixed-column commitment 7, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2f80), 0x00000000000000000000000000000000166c1cd47b07ac0aee553f0b4277cf37) // fixed_comms[7].x_hi + mstore(add(payload, 0x2fa0), 0xb4822422a23ea90486c60a5b3b9052dff113041d51718304e781a5a7bd93bdc2) // fixed_comms[7].x_lo + mstore(add(payload, 0x2fc0), 0x00000000000000000000000000000000016c91bfb3ce38ba23df779d69abeaa4) // fixed_comms[7].y_hi + mstore(add(payload, 0x2fe0), 0x8aaa142a59667ce4eabfaf36c5453af4aaec6e596ad923693a2a7d1483c2fd20) // fixed_comms[7].y_lo + // Fixed-column commitment 8, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3000), 0x000000000000000000000000000000001262dde7fc4aeca256572897f5d601ef) // fixed_comms[8].x_hi + mstore(add(payload, 0x3020), 0x5b8b5586b8593d43cbcd11d2fd80fe2a2214269e5479b5d976cf507d5262c273) // fixed_comms[8].x_lo + mstore(add(payload, 0x3040), 0x0000000000000000000000000000000012bd810c9b8eaaf899f161e2d8c26762) // fixed_comms[8].y_hi + mstore(add(payload, 0x3060), 0xc3f299919bfa2b2809a027cc9bcac1799ef2d48b11403ce47a5341f7307d2cd4) // fixed_comms[8].y_lo + // Fixed-column commitment 9, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3080), 0x00000000000000000000000000000000096d06362268ed80162468c5e664e2bb) // fixed_comms[9].x_hi + mstore(add(payload, 0x30a0), 0x1a1a3b60a6c419cb6edb470b646e9ed4e021686e93f82d0c3ee2448368fe81be) // fixed_comms[9].x_lo + mstore(add(payload, 0x30c0), 0x0000000000000000000000000000000015ceb98a36a3576ad6db1163a0a98d2a) // fixed_comms[9].y_hi + mstore(add(payload, 0x30e0), 0x0535a28ca330834bb07c6682d32b20cd8171f4c0d9f063d768b6e3185b6ae44e) // fixed_comms[9].y_lo + // Fixed-column commitment 10, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3100), 0x0000000000000000000000000000000019731a2f803950b978082f57fdfffb38) // fixed_comms[10].x_hi + mstore(add(payload, 0x3120), 0x0de3c6bef55492c30e409d77ac9acb523958dea2856b12cb4e87608584ab499a) // fixed_comms[10].x_lo + mstore(add(payload, 0x3140), 0x0000000000000000000000000000000005f057d8c91984630d36a0df6c2ebb07) // fixed_comms[10].y_hi + mstore(add(payload, 0x3160), 0x3881713eb294d8cdf7bb0374cb722b7bb5080101a54638216ab4886f0276a475) // fixed_comms[10].y_lo + // Fixed-column commitment 11, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3180), 0x0000000000000000000000000000000004d63c6ffcf22c5a45cbc67e299c2b3d) // fixed_comms[11].x_hi + mstore(add(payload, 0x31a0), 0xcfd98199c525785d127006aca0f82774876a94e120b9bde61b9227ee96dc6841) // fixed_comms[11].x_lo + mstore(add(payload, 0x31c0), 0x0000000000000000000000000000000002385e4a7b9ab571a73f07d9574152e5) // fixed_comms[11].y_hi + mstore(add(payload, 0x31e0), 0xa45de038325c587523413d98b5feb6d2071c293abbeab53bf941d76b264f3369) // fixed_comms[11].y_lo + // Fixed-column commitment 12, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3200), 0x000000000000000000000000000000000eea9ee53f693242e50678f2ac4a5053) // fixed_comms[12].x_hi + mstore(add(payload, 0x3220), 0x6bc4aa60deae574c46e3cf56a8d14af17d603f8c222728fb09300eee5c5b326b) // fixed_comms[12].x_lo + mstore(add(payload, 0x3240), 0x0000000000000000000000000000000014aa3a043bb1340472428a1756ad82bb) // fixed_comms[12].y_hi + mstore(add(payload, 0x3260), 0x49fa2fee5ff945799006e851969947efb6351c7642b2683deb31537ea6209643) // fixed_comms[12].y_lo + // Fixed-column commitment 13, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3280), 0x000000000000000000000000000000000b7028c202ee5b6a74d6ccfe5990dc98) // fixed_comms[13].x_hi + mstore(add(payload, 0x32a0), 0x4381d45aa22ccb019d848829dd72d16143e5af158899fcc009d5ca17c97c3414) // fixed_comms[13].x_lo + mstore(add(payload, 0x32c0), 0x0000000000000000000000000000000006c6ca7119058dbb54d5302f125f9cef) // fixed_comms[13].y_hi + mstore(add(payload, 0x32e0), 0xdc6b68957c7b970427a4b5a3bd0aa7ff5fa033687afdc2a4e150a408b0a5f4d2) // fixed_comms[13].y_lo + // Fixed-column commitment 14, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3300), 0x0000000000000000000000000000000008a540ee790774da565637ec30ced988) // fixed_comms[14].x_hi + mstore(add(payload, 0x3320), 0x563c90d314d01996465fa3f893d5fb010b617a4edcabc5c8f8141584792a0067) // fixed_comms[14].x_lo + mstore(add(payload, 0x3340), 0x00000000000000000000000000000000071e81991f7c8f62be7df37cd485cb5e) // fixed_comms[14].y_hi + mstore(add(payload, 0x3360), 0x1f941d942cc15f814350867bcdf1521ef6e7e76b6928827688b0d3d14a6cf1a3) // fixed_comms[14].y_lo + // Fixed-column commitment 15, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3380), 0x0000000000000000000000000000000005a4ed142d6c05e535a91ff8244ca5cb) // fixed_comms[15].x_hi + mstore(add(payload, 0x33a0), 0xdda66355e156c19ea759a1f5aecd1225e16cb3f18397a66b055a2918a2a74d54) // fixed_comms[15].x_lo + mstore(add(payload, 0x33c0), 0x000000000000000000000000000000000ce5eb9724134cce6feb1c0ff1b66ab3) // fixed_comms[15].y_hi + mstore(add(payload, 0x33e0), 0x5e79b2b4087200f1d9a76ee4a2b3b5424266da9d518653e9391e9a94196f1d7f) // fixed_comms[15].y_lo + // Fixed-column commitment 16, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3400), 0x0000000000000000000000000000000009b07c1df13d348c1ed7cb560fcdd682) // fixed_comms[16].x_hi + mstore(add(payload, 0x3420), 0x55221aadb75410fa4526857c95cea6bf3a0efa98e31898de56ea7ff30f7c514a) // fixed_comms[16].x_lo + mstore(add(payload, 0x3440), 0x0000000000000000000000000000000011b2d20f8b12527aba78064c809753e2) // fixed_comms[16].y_hi + mstore(add(payload, 0x3460), 0x21cebde9bb41aa67abae156e3a179655388e51211ba5614fb696a7f0cc212560) // fixed_comms[16].y_lo + // Fixed-column commitment 17, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3480), 0x0000000000000000000000000000000010ce280498c90ca7501427caf6bcd6d9) // fixed_comms[17].x_hi + mstore(add(payload, 0x34a0), 0xb4987380f8d9e8ef710b81034a460c8da32362568e1e9fd6b2cb5297805c98ad) // fixed_comms[17].x_lo + mstore(add(payload, 0x34c0), 0x000000000000000000000000000000000292967ae2e91be4d3555cddb84538dd) // fixed_comms[17].y_hi + mstore(add(payload, 0x34e0), 0x0edcb077ce0626f751bee2673ff6b24c182fd2ae99a0cd83881a81a3facfcb6e) // fixed_comms[17].y_lo + // Fixed-column commitment 18, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3500), 0x0000000000000000000000000000000017cf661d855e771f61cab52918bc03e5) // fixed_comms[18].x_hi + mstore(add(payload, 0x3520), 0x329761bebc6704041c0430c64bc2d589210a4250f57e9dec51a807d4d6926f31) // fixed_comms[18].x_lo + mstore(add(payload, 0x3540), 0x000000000000000000000000000000000f50cacb58bf9101f3d7926b2e26675d) // fixed_comms[18].y_hi + mstore(add(payload, 0x3560), 0x036c7cf34f335946dfc1d1a6e148de3da13cabfe8e7507f10baca11e07231413) // fixed_comms[18].y_lo + // Fixed-column commitment 19, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3580), 0x0000000000000000000000000000000016caf868eda0aeb753fd8ec3bee96cbe) // fixed_comms[19].x_hi + mstore(add(payload, 0x35a0), 0x3217eade0bae8e908b6cfe1f144518cbaec5bbbbc6e6641d82702b3dd5997985) // fixed_comms[19].x_lo + mstore(add(payload, 0x35c0), 0x000000000000000000000000000000000a35da0b540c40574d639e726b9c7e6d) // fixed_comms[19].y_hi + mstore(add(payload, 0x35e0), 0xb45e6cbefb614f14a1721bdbca2dfcb20aaeb64926032522ba3de23420dc1d87) // fixed_comms[19].y_lo + // Fixed-column commitment 20, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3600), 0x000000000000000000000000000000000b7f1691be78a38c579c2ac6bef18d54) // fixed_comms[20].x_hi + mstore(add(payload, 0x3620), 0xf2d63e634590931fda91cce7af80f7c589cc37a98870e127c93e059ff020df07) // fixed_comms[20].x_lo + mstore(add(payload, 0x3640), 0x0000000000000000000000000000000012582699af0164d4335252425d0251c4) // fixed_comms[20].y_hi + mstore(add(payload, 0x3660), 0xa1af4e259a3f65f0065b2b05127538a8434de1326c4920ee45da346bbbc7d293) // fixed_comms[20].y_lo + // Fixed-column commitment 21, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3680), 0x000000000000000000000000000000000e004abc0ac243147c480dca32186a34) // fixed_comms[21].x_hi + mstore(add(payload, 0x36a0), 0x78642aaafe922adde54e02ae93479fa0ae45b75ed0229c2f358a0473d17c09a5) // fixed_comms[21].x_lo + mstore(add(payload, 0x36c0), 0x0000000000000000000000000000000013e0dac8358fff676678a7b7d854aee3) // fixed_comms[21].y_hi + mstore(add(payload, 0x36e0), 0x3fda118cfb39d426f3418966ed06283adbd804fbe8cf51fd89c2f5c410919f48) // fixed_comms[21].y_lo + // Fixed-column commitment 22, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3700), 0x00000000000000000000000000000000168c0d6883d0d5b67d3a5944ee0616a3) // fixed_comms[22].x_hi + mstore(add(payload, 0x3720), 0xce7ac537685a26b678bab6b1b50115c32a5f90b6ca56cb8560a40964840893ce) // fixed_comms[22].x_lo + mstore(add(payload, 0x3740), 0x00000000000000000000000000000000197d467f656fc49a1c5119d7cd826cc2) // fixed_comms[22].y_hi + mstore(add(payload, 0x3760), 0xbe6345c67f108e7e44f8ebfcebea5366fdee85d69947c8f74f623165dc1029b5) // fixed_comms[22].y_lo + // Fixed-column commitment 23, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3780), 0x00000000000000000000000000000000133f78d1d5ad537d358f421d115bdb2f) // fixed_comms[23].x_hi + mstore(add(payload, 0x37a0), 0x44eff93e623017fb4a06006877bd8504b8b614c6c877d73de7cdc2c2e69a8998) // fixed_comms[23].x_lo + mstore(add(payload, 0x37c0), 0x00000000000000000000000000000000067b406b38e3895daddca2f64de3ee21) // fixed_comms[23].y_hi + mstore(add(payload, 0x37e0), 0x5c7961f1d2f1904f7e2aa60bc20559610c868810183505527e28eea6089d6bf8) // fixed_comms[23].y_lo + // Fixed-column commitment 24, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3800), 0x000000000000000000000000000000000bd679e559d6bf9f498928a3074a0709) // fixed_comms[24].x_hi + mstore(add(payload, 0x3820), 0x23c4cf1d5823b59316852fd6653fe003a4a2da9ba443e4640993524c79a6f56d) // fixed_comms[24].x_lo + mstore(add(payload, 0x3840), 0x000000000000000000000000000000000821077b092335b9f70fb79a424cca13) // fixed_comms[24].y_hi + mstore(add(payload, 0x3860), 0x4e823d96a4fdeaf4154ab7c5ef023844695a10e9bfdf314847ccaa40205f980a) // fixed_comms[24].y_lo + // Fixed-column commitment 25, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3880), 0x00000000000000000000000000000000041f7c88095a1bbf5759d88a7d109853) // fixed_comms[25].x_hi + mstore(add(payload, 0x38a0), 0x4b1661f3b1b9012866e17c7550a5b52861ac915beba38767c7aeb04c1df330ac) // fixed_comms[25].x_lo + mstore(add(payload, 0x38c0), 0x000000000000000000000000000000000f2e8cc95c27bf0235efa88fa70bb3eb) // fixed_comms[25].y_hi + mstore(add(payload, 0x38e0), 0x59a5ef2602a9b70c5af7b0db755f10a8e0ffc380b61a580c3300fda8d6077854) // fixed_comms[25].y_lo + // Fixed-column commitment 26, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3900), 0x000000000000000000000000000000001983933b9a69c960c9eda1f5942af11a) // fixed_comms[26].x_hi + mstore(add(payload, 0x3920), 0x22de694e7c8b2a5f8d920b50d6c90aca30d149ace5ff9feebf84a136abdfa9f4) // fixed_comms[26].x_lo + mstore(add(payload, 0x3940), 0x00000000000000000000000000000000162f68d07437f5dd432392f6a244044d) // fixed_comms[26].y_hi + mstore(add(payload, 0x3960), 0x4605b4457384fb65577df046c667a744f851fbdb0e8751aaeeb334bf32fbd3e0) // fixed_comms[26].y_lo + // Permutation commitment 0, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3980), 0x0000000000000000000000000000000014669fe860d72f984d085b33bfffa399) // permutation_comms[0].x_hi + mstore(add(payload, 0x39a0), 0x12a9b0d0acb1ff8fd119eee9d0870eab9324293b88df67b9e1ebd180db262c72) // permutation_comms[0].x_lo + mstore(add(payload, 0x39c0), 0x000000000000000000000000000000000f3292f0f7b5707fe0874c56fdae2e88) // permutation_comms[0].y_hi + mstore(add(payload, 0x39e0), 0x2a2ba44cc1a14f736d255905838f8438fb8dbfd65ad84bcbca748a749c58955e) // permutation_comms[0].y_lo + // Permutation commitment 1, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3a00), 0x000000000000000000000000000000000ee7e9d684b203f28dd5424f3a511695) // permutation_comms[1].x_hi + mstore(add(payload, 0x3a20), 0xff88a9d696976adca8c54262e0f553756ec45b8e103453f8c18a07c3abcef5fd) // permutation_comms[1].x_lo + mstore(add(payload, 0x3a40), 0x00000000000000000000000000000000177029152e262c258c987bfbeeee14c8) // permutation_comms[1].y_hi + mstore(add(payload, 0x3a60), 0xe945c114efada4abdeff8610bea0cf199ef3f375cf15f6b437de67f3379c5801) // permutation_comms[1].y_lo + // Permutation commitment 2, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3a80), 0x000000000000000000000000000000000e2ead1ec1acc411940e7bea5d0f2114) // permutation_comms[2].x_hi + mstore(add(payload, 0x3aa0), 0x6b39327e36ebab84013dd8f5c2b3f8b576a125db88ff28db45a79a28df414b66) // permutation_comms[2].x_lo + mstore(add(payload, 0x3ac0), 0x00000000000000000000000000000000199b4ea033c01e1d83627c054ba6f2dc) // permutation_comms[2].y_hi + mstore(add(payload, 0x3ae0), 0x2a61b1277d07469ad65c413a1970c9cf179bedfc4cf2f61c1ee8ca5e4a01ab90) // permutation_comms[2].y_lo + // Permutation commitment 3, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3b00), 0x0000000000000000000000000000000011af8f4e99c419588e05414f664ae8f6) // permutation_comms[3].x_hi + mstore(add(payload, 0x3b20), 0xdded731799d69d8f5a72c124a5c6df33153e15767000ef54648a8dd23687140a) // permutation_comms[3].x_lo + mstore(add(payload, 0x3b40), 0x0000000000000000000000000000000017ba99d9323e45fe54ec6ac4daaad8d3) // permutation_comms[3].y_hi + mstore(add(payload, 0x3b60), 0x0c0dbc53d3f5f54c05667eb0e4f28da8973657bf62915fcdfc551698f56aa574) // permutation_comms[3].y_lo + // Permutation commitment 4, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3b80), 0x000000000000000000000000000000000ed68fa806cd3171cb061afebf65799d) // permutation_comms[4].x_hi + mstore(add(payload, 0x3ba0), 0xf9014492a08de2420f1234158d2f1d214b5354b7ad8cfc753d2712e6bb9b20a5) // permutation_comms[4].x_lo + mstore(add(payload, 0x3bc0), 0x0000000000000000000000000000000013f05c6e0393baeec023451082007353) // permutation_comms[4].y_hi + mstore(add(payload, 0x3be0), 0x55211ac5dd3c2267dcf706d18263fa7b7916e44ab1d5309430542f75717187c4) // permutation_comms[4].y_lo + // Permutation commitment 5, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3c00), 0x000000000000000000000000000000000a925e4ad4c2d71f096de0e53baea720) // permutation_comms[5].x_hi + mstore(add(payload, 0x3c20), 0x7a90226e678437d20f2ec9ae630075a12ae88a186bcfba1bc0444e5bc40245e7) // permutation_comms[5].x_lo + mstore(add(payload, 0x3c40), 0x0000000000000000000000000000000006ff1ce2b5e6c2c3cedd7a8465c5ed3a) // permutation_comms[5].y_hi + mstore(add(payload, 0x3c60), 0x72e4496eead09ba2cd0ef56fdd94942ed738b42b71caea3d1bd78b0ae3ec3f96) // permutation_comms[5].y_lo + // Permutation commitment 6, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3c80), 0x000000000000000000000000000000000190af4fe845524352a9f624463ae7ca) // permutation_comms[6].x_hi + mstore(add(payload, 0x3ca0), 0x406fa14f4319031fe5a7630ab2bd66c107d6cb05fa65b98ee0c5f0234c29d189) // permutation_comms[6].x_lo + mstore(add(payload, 0x3cc0), 0x0000000000000000000000000000000013036a5f7dca90b530955bf735da2920) // permutation_comms[6].y_hi + mstore(add(payload, 0x3ce0), 0x29b8759dc69e53d6c830a3bee081165d4d59442dacb50c2a06aded3193e88387) // permutation_comms[6].y_lo + // Permutation commitment 7, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3d00), 0x0000000000000000000000000000000012b6e22b800c1a2dfde554f3312f4b50) // permutation_comms[7].x_hi + mstore(add(payload, 0x3d20), 0x1b75400de7c8c476dcc9fd72dcef80d9384da4e4af8f928a07e4a9fa485da8bc) // permutation_comms[7].x_lo + mstore(add(payload, 0x3d40), 0x00000000000000000000000000000000053da19ebda674bbc95d3d10ad04cb96) // permutation_comms[7].y_hi + mstore(add(payload, 0x3d60), 0xa410aecead2c7059df1e12a1a56117a830f0008a91551cec88f1ad548a379dfe) // permutation_comms[7].y_lo + // Permutation commitment 8, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3d80), 0x0000000000000000000000000000000016038223f354e7d7c75614116c58b1c9) // permutation_comms[8].x_hi + mstore(add(payload, 0x3da0), 0x866a8a39d6b30ea560b263c68da7e78210b2f2a7f9b19564388f2f2370d7cd6a) // permutation_comms[8].x_lo + mstore(add(payload, 0x3dc0), 0x000000000000000000000000000000000bdb1d40f473070bfa087ff87bc9c86d) // permutation_comms[8].y_hi + mstore(add(payload, 0x3de0), 0x0d57398e71f45845ebcd0aa24a9ea829718dfce700af80fab46b51ad73fb58f6) // permutation_comms[8].y_lo + // Permutation commitment 9, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3e00), 0x00000000000000000000000000000000085c579504429306bbf0ec28b07f91a6) // permutation_comms[9].x_hi + mstore(add(payload, 0x3e20), 0x7326fabf9ec866a45ef4d294f390a7c497c7b990a62b602f5999fcaee0e7e9bc) // permutation_comms[9].x_lo + mstore(add(payload, 0x3e40), 0x000000000000000000000000000000000b042dab28398e8c0670ee6f04cca93a) // permutation_comms[9].y_hi + mstore(add(payload, 0x3e60), 0xa41ecc7205c8216513d027b4c1213d0e452c16898d7b2c811027598cf2715dc3) // permutation_comms[9].y_lo + // Permutation commitment 10, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3e80), 0x00000000000000000000000000000000093b6ec41dd5c2455b9d2dca635c472d) // permutation_comms[10].x_hi + mstore(add(payload, 0x3ea0), 0x386496c219c45db9f1a5d96c179d1028694626c4a1845aef384efaaa8053284b) // permutation_comms[10].x_lo + mstore(add(payload, 0x3ec0), 0x000000000000000000000000000000000386d1128886a4a879e8a93aa84b6525) // permutation_comms[10].y_hi + mstore(add(payload, 0x3ee0), 0x1061026f4dae08952816961f9d6fc0b8153784a5961f831b68f1e2ddc567efb7) // permutation_comms[10].y_lo + // Permutation commitment 11, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3f00), 0x0000000000000000000000000000000000b6ed6814a099c4e435e42fc8ee4250) // permutation_comms[11].x_hi + mstore(add(payload, 0x3f20), 0x0e16b88c78069cebe20e4d647c8bb44acd6b9c6712d53a3ebd014e54a1a75e89) // permutation_comms[11].x_lo + mstore(add(payload, 0x3f40), 0x0000000000000000000000000000000001ae3805b59b2f2d53a59e2c19001e97) // permutation_comms[11].y_hi + mstore(add(payload, 0x3f60), 0xaace57ac2ba8b58550a740e897ba6278e409291e5c87466a01edd7cbe3b99f88) // permutation_comms[11].y_lo + // Permutation commitment 12, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3f80), 0x000000000000000000000000000000000ca3af5a65ec51141f694db29736dd57) // permutation_comms[12].x_hi + mstore(add(payload, 0x3fa0), 0xa1cdbfa79f45aa80a6cdda8de3d51c4b16c95361b412c6f47e132eae7b69f7f7) // permutation_comms[12].x_lo + mstore(add(payload, 0x3fc0), 0x0000000000000000000000000000000018dc16284e57fccd755058b259b913c7) // permutation_comms[12].y_hi + mstore(add(payload, 0x3fe0), 0x7a54da0d0e206b7906878fd3e994ad04c66f48e19cd982d5651f99b48b992064) // permutation_comms[12].y_lo + // Permutation commitment 13, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4000), 0x00000000000000000000000000000000197560c82bf47486bee750469d626ce4) // permutation_comms[13].x_hi + mstore(add(payload, 0x4020), 0x2d40ce7489ff61a64a0eb55e6672926a40f06348ab575f926fbe4d87b672fcd7) // permutation_comms[13].x_lo + mstore(add(payload, 0x4040), 0x0000000000000000000000000000000007b4f39a579a01d4f01f4ab9bc841a7d) // permutation_comms[13].y_hi + mstore(add(payload, 0x4060), 0xe70541d6b79945e9569fd59388739f5b4ce57794044d287d7b0b58e35198caab) // permutation_comms[13].y_lo + // Permutation commitment 14, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4080), 0x0000000000000000000000000000000004e1b539a3a123291eaa8c85be3395e7) // permutation_comms[14].x_hi + mstore(add(payload, 0x40a0), 0x9c6845178d995313daf42680692210e1ec79278ec8b6e2da1d6f289609100404) // permutation_comms[14].x_lo + mstore(add(payload, 0x40c0), 0x0000000000000000000000000000000010d56b62afe35890b4f98b946daa8936) // permutation_comms[14].y_hi + mstore(add(payload, 0x40e0), 0xd83e014595585e2892e21bfc5e86f84d404b435dc1a689539d74773aba55dad4) // permutation_comms[14].y_lo + // Permutation commitment 15, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4100), 0x000000000000000000000000000000000f227bcaef796cabf0a93289fb8321e9) // permutation_comms[15].x_hi + mstore(add(payload, 0x4120), 0x1d43880fa5fcebab73c77c6bb406cc5e9386a81774635b5a6144b75ef10cd994) // permutation_comms[15].x_lo + mstore(add(payload, 0x4140), 0x000000000000000000000000000000000a849d95d37e501cc6272f325f88d677) // permutation_comms[15].y_hi + mstore(add(payload, 0x4160), 0xb25be83ce57eb9c32d382613930bbc0ef7ec349d44ac74a9383c111efcb73783) // permutation_comms[15].y_lo + // Permutation commitment 16, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4180), 0x000000000000000000000000000000000b77ada25b279742ec75d2014583ad79) // permutation_comms[16].x_hi + mstore(add(payload, 0x41a0), 0x427bfa99ab8a941648e1a11084fec2a6fdb8192f498cf3e272a5e786597273a1) // permutation_comms[16].x_lo + mstore(add(payload, 0x41c0), 0x00000000000000000000000000000000056788c108fd40583888b8771a70d3f6) // permutation_comms[16].y_hi + mstore(add(payload, 0x41e0), 0x364290f978d1fc201c0a76561a4c1cdde7643f7d76448dad2586c3eac2e34104) // permutation_comms[16].y_lo + // Permutation commitment 17, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4200), 0x0000000000000000000000000000000002ecf71916494d46153fdd513ead1917) // permutation_comms[17].x_hi + mstore(add(payload, 0x4220), 0x06401b48839713727c920f4591fe09ac3e8f4140ef1cd9e39b0d0bbe0b5b87d4) // permutation_comms[17].x_lo + mstore(add(payload, 0x4240), 0x000000000000000000000000000000000e7bab8cc3c714f06826c8f3695dc63c) // permutation_comms[17].y_hi + mstore(add(payload, 0x4260), 0xe7e931ff0dbe009cbb84fef0b99124905d90cdd6f5356a7ee7673548f6f0692c) // permutation_comms[17].y_lo + + // Return exactly the INVALID prefix plus the generated payload. The + // linked verifier pins this byte length and the resulting codehash. + return(runtime, 0x4281) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md b/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md new file mode 100644 index 000000000..07efdfa66 --- /dev/null +++ b/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md @@ -0,0 +1,69 @@ +# Moonlight Wrap point_pair Replay Fixture + +Pre-rendered artifacts for the `point_pair` accumulator arm of +`tests/ivc_accumulator_replay.rs`. The IVC fixture next door covers +`AccumulatorEncoding::new` (explicit lhs/rhs scalars); this one covers +`AccumulatorEncoding::point_pair`, whose +`expected_acc_has_carried_scalars = false` template arms were otherwise only +ever compiled, never executed against a real proof. + +This is a single-contract render, so there is no `Halo2QuotientEvaluator.sol`; +the replay deploys the verifier with the verifying key alone. + +## Provenance + +| Field | Value | +| --- | --- | +| Source commit | `5209a9c` | +| Rendered by | Moonlight `wrap_circuit_composes_two_fold_children_from_four_dummy_fold_proofs` | +| Moonlight revision | `origin/codex/wrap-bench-cherry-picks` | +| Accumulator | `AccumulatorEncoding::point_pair(offset=11, num_limbs=7, num_limb_bits=56)` | +| Public inputs | 19 (accumulator occupies the trailing 8 words) | +| Verified on-chain | yes, 1,277,823 gas under revm Prague | +| Native/Solidity trace | 244 trace points matched | + +## Regenerating + +Needs a Moonlight checkout on `origin/codex/wrap-bench-cherry-picks` placed as +a **sibling of this repository** -- its `aggregation/Cargo.toml` refers to +`../../midfall/proofs/solidity-verifier` by relative path, so the bench renders +with local codegen. + +That branch pins the Midfall crates to a fixed git revision, which would link a +second copy of `midnight-proofs` alongside the path-dependency one. Add a patch +to the Moonlight workspace `Cargo.toml` to unify them (do not commit it): + +```toml +[patch."https://github.com/EYBlockchain/midfall.git"] +midnight-circuits = { path = "/path/to/midfall/circuits" } +midnight-curves = { path = "/path/to/midfall/curves" } +midnight-proofs = { path = "/path/to/midfall/proofs" } +midnight-zk-stdlib = { path = "/path/to/midfall/zk_stdlib" } +blake2b_halo2 = { path = "/path/to/midfall/third_party/blake2b_halo2" } +``` + +Then, from the Moonlight checkout (needs `midnight-srs-2p19`/`2p20` in +`SRS_DIR`; takes several minutes): + +```bash +MOONLIGHT_RUN_WRAP_SOLIDITY_BENCH=1 \ +MOONLIGHT_RUN_WRAP_SOLIDITY_TRACE=1 \ +MOONLIGHT_WRAP_SOLIDITY_DUMP_DIR=/path/to/midfall/proofs/solidity-verifier/target/moonlight-wrap-solidity-dump \ +SRS_DIR=/path/to/midfall/zk_stdlib/examples/assets \ + cargo test --release --lib \ + wrap_circuit_composes_two_fold_children_from_four_dummy_fold_proofs \ + -- --ignored --nocapture + +cp target/moonlight-wrap-solidity-dump/{Halo2Verifier.sol,Halo2VerifyingKey.sol,\ +calldata.bin} fixtures/moonlight-wrap/ +``` + +Then update the source commit above. + +## Staleness + +A snapshot of the codegen that produced it. The artifacts are self-consistent, +so the replay keeps passing after a codegen change -- it just stops exercising +current output. Tracked by the commit stamp rather than an assertion, because +detecting drift means re-rendering, which needs the SRS and the Moonlight +checkout again. diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/calldata.bin b/proofs/solidity-verifier/fixtures/moonlight-wrap/calldata.bin new file mode 100644 index 0000000000000000000000000000000000000000..f19116b45b7a7b4a91f8cfd6990d9cb2af063bbd GIT binary patch literal 8516 zcmajkWl$VZmoQ)kXK)>y;O+!>x50x24+M7!?(S|05}e==+=IIl+}+(}zujNoJE_|3 zf45FQeY(5q-rIc+X)Gx=)c<~n{_kPZuKy!$1R00J`#pp73q?=<;R6#(>GU@~kOt8) z2NFOxxuM_ea-dk52u>~dt2DQ6I;&boOXfAC2PK3y(uybI-Wyi>OZKvJBxSYvEkCL{ zG=s+XRZz#+3uEB;x5(BVFO*FyU2<>eAU*u3!XCFAugD46J>v<2wCL^WxnP3Mv8tZ0 z*>a&quv*UCb=c|M{)qLD7lzBxN*qW}AO-j1D95oV?OL-z=O;#O2tY;?6q{#eaIH}o zuS^6$c3Jd~HZ;;ZB;4kjvs4R#^vEBZN?8q^I!TR~!21=b!a1$F@PCuP6-smfCL~ zny;5HVcbVcnzYNElueyEimZg2&UtOSV`Qe4(e}}6hkAL#7)mp)(X27nN-Ql= z8WtRE&91-mkN*szW z;*U7p0_oxC8W`M7*F>nb*{kCtOA^#h^wC%XH2eVm0wMIH>H3Oy;BXuYl#n7?h3`>> z4ZDyY_oY7|BY(#lX&VS1J~bQyOKbEAAT|E5vmmbi;(_vRL+9TnL3|_Jq*j*H6l=){ z>7fHsUjgM9Y$m>q+r1>%nywjT!_WG0#+S43?h)#iK#Rs#vGDXM;|dgRV)o+_0Z0#% zEwqsq3!{>4WX^!hmiTl$Q667I0sBRrs zExx~c{5N-aCvrX*QLv~llNy=ucXE;>e1que>VOjV@LL6iMFy3L=Fw9xq{oT(c#D~^ z2iv6zpN|_gzTc?zFRm=wx-wilv1K7#*i11)U*58bfpK4+o9i-0|9SsV-rkrUnRBmN z+9*R-8{f50j6TX33u{7uKhNs#*?k`Ea*(`4<9yAzub)%TjrmLfnI9_WGa^D88rQZ5 z(jd`y3Vm&?+XIAmqVI9J^Pft%C~?=nToq;eej=8~$79~^*1d)F2yVYc5nxKN(@>JX zjr$VQ@JhOhCrP_25co48!i&I>WYg$=r=9F4jC&{~Z4ZL`CqKd)`c1ZZiUAcaje+vf ziBKK_z+%N@saKHjZ|b*_MtGPTcn>ejgO~jJU8Tb`RYJ)8xcRD*2kZQ_+2q_+Rr0ck zYz|%|NOIn@T+QV;+GM#_ub01N7L67{kzCgF{6&X0Aw7oUGvbizvS=trVD$U;UARsMEoY!OtG@I-djvj373hWS<2_ zs|af7l*lVEbB!n`85&VSdJJZi(xXT}5^s7WPV1~HxnA)V%Iu(}J-0ekK46?>;K_MP zau_{%a~%JM&wNhU4@i&HT68h;k|$DudGt)kVN~*W2~W;Twf2R~_D3R&>m%}&=#^ONoxRs`RHL-#_Pb~C5)fcf1(lM((5%5cRBDQh{B_d zDCKti9Wx#OnLi-zp;LlN0&lyzMTH_Aw~dmA-+#I~h>veDG0X47D7WU0x=>5!gwOt_ zNaMhd{Z)X>4|2Kv>W8*FRq)aETl&hF%9*|j^1Gn}oY%n7?xh_WriQDqn2i4YCSW^> z{>Uep8`5JHZPq?VB8gRP3niKiDy7+_n=lGm$(=WJqyg1r8zvZ%LSvs$VH45toSKcG z%*Frl7qRGj!ftZJEV3tBVR0=^#3A>DgBCu^u#w)e6bjR(ZG1mcz8h21M8VPixG-af z^vG9K>9DOr)9f5H8yOK=H-(R#TXWv^Hfw8ztO8PhG@Fzwo!~wFBWl3aLR)7S^N=1k z7+9q(+N$=V0DX$#tHle2yjih#6fFhk*<>P>-7}Dfz^~dudnumAV_!qs&vy zu9;_sZjPptI(mEr2HuRE;us)Aqw^9$ZKzU8Tj-5DUDUbGInP9QO5_)DLVEb}S>w}I z(-X5|k75h@nNz58$x(cf<#$&l!`dYU{k|ya^gkr!O#(fLyNgG>$%>F3Q`5%bRZGl% zDB6QQ_O}H41DkLF-HKcBlQk~O4s+$cLuk*DhGgVI7$LB6M5YP=>EV6v7E0eo%#_IA z=!hQPeou9PY&?H{v-5%}Q0gA0!cPexLnMfhIYIhB8xcwEB?{?L1bOTuR!y__c3=W_ z#IUZiY`PcvOwf%WU@ffxX}I?HcHTw7UWGX5Im%uMHTTm9`7Cy*W#QM|EhkEHPJ+0_3V1GbBv zKrPg)r)sSJ5TSExi?b0hpi3dYL!uja9j%ATKi>}N0hfwL)W&t0T9M1DU|D0?kE)3} zwVttwg>rh5XcsH%^y{? zdA6RvKD=6WmRHkNOrhCbKPh)_Lg6!m^k`1Kdh4(!1d2nbeyoLMvE7?OL>F^@&KULJ z147_?CYf#vG#_%O<#-?B%p!Vl7NmzFO-OxYGt-Nr{w2>?vxcK4{ekNTR@(x$5Bwf_ zy;H*tg7+qs+UDL0Nx6I|kRAonBgRiylXInF3yl^sCb?OQZgyoT{NcN8eLK!Kr=P}~ zeciGJS21}FIu7?N?IO+3L3;cY9Yt-ASnOs8;zp0cha&DDaDuaRD6DiA&!lS}eH13v~3$eLnC<+Jh#X_Q6*XQ}_ zpZyd6s(Hde@hRZ_Z62>jI2Hc-Nj3NM!o%bqf0|8!y;i}jyzg(t-0*S|02R|4j^#UK zexQtW>J7eN`!xQ@oYvbmU$j_?30xx&-}N(|zU*p^Jql(DJ8rL`E4Oc{LG=3=2c!p4 z+qk5-Hm>933iUZt(-W69S}jq>EgPCX@I5sQTCx6-^$vbL$ji8$5h3KdeH4N8_(KHN z0B)P2JwS?3zfdoBwM4{l&-s;!56-8ZSaN2x)E}`?tHYYmBua_COf4P%?4Q`eG=)QH z=KiH4Yx+d{pH|mm)GlTt`Jc#wngv_R;mzH0S8^Uk`fl5F2-Hea3uqzp!_$3-1#YDf z7bG^0tpnj4YI111m`I|;AL&h(o=ULGy;-eAKL>gTjTTD^LHd8jPKyo$4vk`x zH-!l<;yzLUu1f(+knNO5Le288i0lU*hNq{EZPP){L(JcP$>42sdH6d?el79iwO`6x z>6tj#4C2lRECqb;TpP$$`Z76jbeH&B7=JnIk3(5Bd8Yx+`W-AXD^%{?J;7uA))`sE)8!&h_&2@3d7ZWt7)GmULz`K6h!wg3rO852?VqB{71GU&&hPZPjPQQye_=lVu$QY^{BQvm8Hwc zhfOLC?#Ui&8}N%jYT`j=AVUBTDgW&DvTi%V>RYt55j3sHHFrS-CT72ao3s ztm>V*izvPLIA50xAj@W9E0P$^sA->6g!9`P){c8f{my#DJ-=eY4IRC_KD-Er!j$OR zcU&#v6p1U6J}v*C)}nWM>R0St9UD;)R{i}wHLur*b6`NW!sRU zWM7H{za;P-W$klaqmM;{-YO-=QT$t!6@zs(s+AH@2S++k@5O$ovRS9TtfbEui=k;p zPh0I(q?5fa7JDNKtJz251Q`>&@sJw19z#fns{bjXB}Cwd1#i}vF{_NPTF8Bc=<;bbqu~ppj~P?UsH`9$#CMIS2q^@X6G`R zC|rQa0@r}HY_d<^G>*htz`1x3D%cWs&SlU7F}(7$ILM)hpM|w z36{p1vQ)+p^`49+4=}SF_DDo|fdj2q2x|0{CQQKxqV=mbb1}2fxPesCgXAi1E2De8 zqD@6AQzahu3?`^4L+=`I9Kg^5l+Z2rhPNL}a&5+wXh>TK$yYGeSNuMBjON!RsF3AF z2J~fkwkx(xMT7_PH}&c*2mY<(oQC;8IKkfm)BPuhSADI1=))}fL`60{LN(7S2IyMA zpOdp%%yaI!=+LJ3VAUi1-QGzYG4_2sbllO(p(Ud*!*_coJFtr#wh60!FH+Dj2=Gfb zTc%fJ!OW{KEV8l#<8AAM+4(xEwP!Jm)-M09wF13Xc%$7!eCJe2R@vD{&b&f3#YvMw z5+%xwfo3i;o8$b$HS9kpS!4W0!qV|^2J8A%^5F%GBsjInRx8+%w-#)IgKgDrg+CgD z57Dn=i_BE~>|7*zs5v+(Xcs>@n$5N(_VDxhRK7ReD_-=c#xC3veQx@u6xjaj?+`py zr~>bqR;RuHXk+~zD=bdR;ko@-Kh06^MY=%Uv*Cjg_n>0-5Yk$w%zzLMZ*6T*6p2Y_ zm<4p%d%84?8+!~0f$>xr|3EMAJTneKaZS;Xy3N@)RKH30BQT9Rt5bu)qVVN#5fnHq zWs)&Fi*6__?bBL!A=y*U!!1K8W5&G>z+@)$P429&sX1B5qKKoHLfdB|r*-Qt*Ub*b zLuyjG72>60{xDgJlmQCTdwx78Klk$X#rG?jZHV*<5N1sm6~QaeSPJ+VN=V!tih}$B zQ(HD^6HXW#=rjUDPQZt*XCiWf0l6q$8`?lslxHa!*}pP@KHCf8Q; zP7|P**Uj-RG}n^F61r1mI6O9%WX`O~GvcU;9&l2xgYuF>L_)=OAve%fed|y6{7Mbe z&E^!J3uT-=*Qz;InHS7)$ z0|ndcHW484m;j7l@$c2gA0J4l2c`W>kIl&aur|B~dU(djV)qVWF-)cE(HLy+R4*3<5lGvjwk2Y??}0_aQzc=2!Zd?fb~8 zNQyTqq%!O`r_8)-f9To#;IzQpESHMxq1v2iqc3cth)ZX0LP1&J{_Ju?TzS|AJDRO} z`QazL+}6RJI*-ydB0L=yUm$txxB9&5-nNyvZBCMpwG$;ac`c_P3%@B)BR@*<8@;M- z4t3)ZkJRK>(^uaEs+Nz(u2b++L4v2xzYIG0KKp#&qZUP!i*FU2QpPe9b{?6g7a?RzBXPV-C78?g$J4Pup=l7 zcVePY>}mTrbOxnC;QB*PLl^#*ONCJNrBR8B~3k^z~ zfIJOL41~@+i&`Fk$Dm9UidjgReK~Y`I3;jAI@$WGw9yHwqB$!Fi>nVZ(mJxKws>BT zqL>g$Kwj0DJA*aUvBa}qLFE-m$xPg!MZUD$8BC&8bx*LviL`ej+yNR3wChkQBmpbW zXq-qhgjQR8LS$?YP2T#7w8~Tc#ZWQ9rwqKY_QN! zvU>>a9vakKO}2JL=8b_1!;J!K%)*N%%PcT9@q$M?g<9akbVgHEkE9z!!6RgBKt8#n zjFN+(h|T1`nQ`n))o&NC4DP}@$zeH9xap;atPJ)%y>{+PAO5^k0YW|Q83D7f&s#<-~MvyB#V#%@< z^HHc@RxY<^(MlvAC-pf*WfWKt7-0}A4iPuy(n#?R`e`og7iG6E7;|JD4MmS6nY5df zRALY44?X4#RG2Ts1MhLtxTr2W)0ArF8k(cN$Gpz5hr_9OMC~-vfh3#_B?mkD!#-QC zbCJ&xbtpc`-O*Zx9WwzeSZ9*BgQe9du>DB4WH9k}^0K-A782-H-Ld-A{*nFc@5qGc z{@2yP#?vzDoBln$Ynr$Afp6Yf^`-8DKTnBcp=>91sE995%TsfDEe7qTje zv`fL#%ojtA5adVQuQ$+!WA>Zkc$Nv6Ekk{>ww$uzpF{{m?kev2?w^Yg%|5q4KL*D! zww2P9mbZ;v1PC_^kK2>D&Lde`Q+G~84C8!pgb&Ee3NvG#2y5Cn-$RM=uo0P_HKE^@ z8t)pA0d3DbTlu7&rol)C|IP-N#CC{}2XHcc_M+n;Ab@WRHO!cf*IJ(xJiV!KhCXI3 zB11{&%jKn-P?lxTb_ySRh6$$+js9`Fx%+AF;}5;MdV@Ms93LICRHT3a5S-a@;tvTN z#l+k+LRT@e0(pzv0gKgMV3^$oTVrbu5G~-mRHUbdV18X)y~o(C8_0`EE9bX9A~#j7gW;H>IkMSAMYLGgC3a8#biz0X15OPVhfY!bG0_SFF81U+V^ ztJWdgvE*VvekSpubJ5!t#mYS-Z{w>>7|GwEC&IRtxm@Olt`ST$ep!Y`QjniSDBY0r zqpi6Yxyn2V4aqJ~QKD|r!A+grAc?UzRtC4DiobOjyovArH$;MycLY;~Uy^Ikms{bt zYj~HFJ33O13&n#}z`{GM98QVaSeXa0`uOCuz-qu74k~4~x}-^fHhrl{F4=>Z8!kO> zBze^Jo&!B{{c}9VuH;o`mmp~~`jR_J?meC!PU7g3ngvb^9fjW zt5IsXel!;-H<d){_7|@z`C^^Xk;JK9DzA5% z^ELm}f9j{`T==z9$M59e5di~ODEL(Bw@nq^)aM-c4@i4i#)eN4Dcx~T9AP75EP2se zKG`Xd|2x(;3G_VG1MrHh7@93;K{yQ*{9S+xB{*jNK| z*q^uWB-?@w(T0rYh{;KwW}>5HGX5mR^Q04=;J3r6Do+vwq{e_{Z#N{+Ta(PxI>rD~ zy?e_GzXzP?tff#dDJYJ1bkKwKV@zj#6(Zcw+tJU^gN`I$>*>`k6K z9#Mky5aa)=zx_{t2Eqd&#*o8*>T?h${L?>%h~wKFR0PIjCQDAXL&NiPX-T3Qv$zrJ z=$pXkpycY+)aMHu4oysBQu`R68*ux1Qsoq_OiebUFnuvEFQLxrvj3;P0&#r~@!k?e zPIzxrzji5J^yLo17MD^vw2`7>@x6NNjSPlGtaBV#0e<4hcvs*5r#@8(y;QeuRvUD5 zuSqK4Nl5IPMXD6PSX(ZU_^0UqzaQ*+o#Kj%mA%U4IfJLM#EtFY)IJC0{OU zH^U~TXRaTNql;1XP|VkKMlsO(kNPucKg{b5>a1g-Ue|Iw&gxnD1N!oRd+-2&tcudx zWOp-pb*qu-`K}4vI^al$S$3rQAN^~TjJXg%eD+iLuAo%n_`v#!knjJ@Zx3RN#5;HW wCe1R>k@#_Y^R%=CCf#+C`hGo?|L9*}d3-BmdhHJtYer6rsNY>C1K8aC7isx+d;kCd literal 0 HcmV?d00001 diff --git a/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs b/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs index 0fba70215..a8f000c4f 100644 --- a/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs +++ b/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs @@ -21,30 +21,27 @@ //! from a VK without the full SRS (`SolidityGenerator` consumes //! `params.g_lagrange()`), which is what rules out a vk.bin-based replay. //! -//! Regenerate the fixtures with: +//! Two fixtures are replayed, covering both accumulator encodings: //! -//! ```text -//! HALO2_SOLIDITY_RUN_IVC_BENCH=1 \ -//! SRS_DIR=/path/to/midfall/zk_stdlib/examples/assets \ -//! cargo test --release \ -//! --features evm,truncated-challenges,in-circuit-fewer-point-sets \ -//! --test ivc_keccak_solidity -- --nocapture -//! ``` +//! - `fixtures/ivc` -- the IVC Keccak decider, `AccumulatorEncoding::new`, +//! which carries explicit lhs/rhs scalars. +//! - `fixtures/moonlight-wrap` -- the Moonlight wrap decider, `point_pair`, +//! which does not. Its `expected_acc_has_carried_scalars = false` arms were +//! previously only ever compiled, never executed against a proof. //! -//! then copy `target/ivc-keccak-solidity-dump/{Halo2Verifier.sol, -//! Halo2VerifyingKey.sol,Halo2QuotientEvaluator.sol,calldata.bin}` into -//! `fixtures/ivc/` and update the commit stamp in `fixtures/ivc/README.md`. +//! Each fixture's README records its provenance and regeneration command. //! -//! The fixture describes its own accumulator placement: the offset, limb count -//! and `has_accumulator` flag are parsed back out of the rendered verifying-key -//! payload, and the infinity encoding out of the verifier's own constants, so -//! this file carries no second copy that could drift. +//! A fixture describes itself: the accumulator offset, limb count and +//! `has_accumulator` flag are parsed back out of the rendered verifying-key +//! payload, the encoding kind is recovered from the payload width, and the +//! infinity encoding comes from the verifier's own constants. So this file +//! carries no per-fixture constants that could drift from the artifacts. //! -//! Staleness caveat: these are a snapshot of the codegen that produced them. -//! The fixture is self-consistent, so it keeps passing after a codegen change -//! -- it just stops testing current output. `fixtures/ivc/README.md` records -//! the source commit so drift is auditable; detecting it automatically would -//! require re-rendering, which needs the SRS again. +//! Staleness caveat: these are snapshots of the codegen that produced them. +//! A fixture is self-consistent, so the replay keeps passing after a codegen +//! change -- it just stops testing current output. The commit stamp in each +//! README makes drift auditable; detecting it automatically would require +//! re-rendering, which needs the SRS again. #![cfg(feature = "evm")] @@ -61,18 +58,24 @@ const GAS_CAP: u64 = 5_000_000_000; /// word. The proof payload starts immediately after. const PROOF_PAYLOAD_START: usize = 4 + 0x40 + 0x20; -fn fixture_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/ivc") +fn fixture_dir(fixture: &str) -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures").join(fixture) } -fn read_fixture(name: &str) -> Vec { - let path = fixture_dir().join(name); - std::fs::read(&path) - .unwrap_or_else(|err| panic!("missing IVC fixture {}: {err}", path.display())) +fn read_fixture(fixture: &str, name: &str) -> Vec { + let path = fixture_dir(fixture).join(name); + std::fs::read(&path).unwrap_or_else(|err| panic!("missing fixture {}: {err}", path.display())) } -fn read_fixture_string(name: &str) -> String { - String::from_utf8(read_fixture(name)).expect("fixture should be UTF-8") +fn read_fixture_string(fixture: &str, name: &str) -> String { + String::from_utf8(read_fixture(fixture, name)).expect("fixture should be UTF-8") +} + +/// Read an optional fixture file. Split renders ship a quotient evaluator; +/// single-contract renders do not. +fn read_optional_fixture_string(fixture: &str, name: &str) -> Option { + let path = fixture_dir(fixture).join(name); + std::fs::read_to_string(path).ok() } /// Read a labelled VK payload word out of the rendered verifying-key source. @@ -121,17 +124,30 @@ fn assert_reverts(outcome: CallOutcome, case: &str) { } } -/// Replay the shipped IVC accumulator fixture, then mutate the accumulator -/// public inputs and assert every mutation is rejected. +/// The IVC decider carries explicit lhs/rhs scalars +/// (`AccumulatorEncoding::new`). +#[test] +fn ivc_accumulator_decoder_rejects_malformed_public_accumulator() { + replay_accumulator_fixture("ivc"); +} + +/// The Moonlight wrap decider uses the scalar-free `point_pair` encoding, whose +/// `expected_acc_has_carried_scalars = false` arms were previously only ever +/// compiled, never executed against a proof. +#[test] +fn wrap_point_pair_decoder_rejects_malformed_public_accumulator() { + replay_accumulator_fixture("moonlight-wrap"); +} + +/// Replay a rendered accumulator fixture, then mutate the proof and public +/// inputs and assert every mutation is rejected. /// /// The accept baseline is what gives the rejections meaning: without it a /// "rejects" assertion could pass because the verifier rejects everything. -#[test] -fn ivc_accumulator_decoder_rejects_malformed_public_accumulator() { - let verifier_solidity = read_fixture_string("Halo2Verifier.sol"); - let vk_solidity = read_fixture_string("Halo2VerifyingKey.sol"); - let quotient_solidity = read_fixture_string("Halo2QuotientEvaluator.sol"); - let calldata = read_fixture("calldata.bin"); +fn replay_accumulator_fixture(fixture: &str) { + let verifier_solidity = read_fixture_string(fixture, "Halo2Verifier.sol"); + let vk_solidity = read_fixture_string(fixture, "Halo2VerifyingKey.sol"); + let calldata = read_fixture(fixture, "calldata.bin"); assert_eq!( vk_payload_word(&vk_solidity, "has_accumulator"), @@ -146,18 +162,34 @@ fn ivc_accumulator_decoder_rejects_malformed_public_accumulator() { // fixture describes its own instance-space offset via the VK payload. let proof_len = read_u256_word(&calldata, PROOF_PAYLOAD_START - 0x20) as usize; let instances_len_word = PROOF_PAYLOAD_START + proof_len; + let instance_count = read_u256_word(&calldata, instances_len_word); let first_acc_word = instances_len_word + 0x20 + final_acc_offset * 0x20; // 7 limbs of 56 bits pack 4 to a field element, so each coordinate takes 2 - // words and each point 4, with one scalar word following. + // words and each point 4. assert_eq!( num_acc_limbs, 7, "limb count changed; the word arithmetic below no longer holds" ); + + // Recover the encoding kind from the payload width rather than hardcoding + // it per fixture: the accumulator occupies the instance tail, so eight + // words is `point_pair` and ten is the scalar-carrying encoding. + let acc_words = instance_count as usize - final_acc_offset; + let has_carried_scalars = match acc_words { + 8 => false, + 10 => true, + other => panic!( + "unexpected accumulator payload of {other} words; expected 8 (point_pair) \ + or 10 (point-and-scalar)" + ), + }; + let scalar_stride = if has_carried_scalars { 0x20 } else { 0 }; let lhs_scalar_word = first_acc_word + 4 * 0x20; - let rhs_first_word = lhs_scalar_word + 0x20; + let rhs_first_word = lhs_scalar_word + scalar_stride; let rhs_scalar_word = rhs_first_word + 4 * 0x20; + let acc_end_word = rhs_scalar_word + scalar_stride; assert!( - rhs_scalar_word + 0x20 <= calldata.len(), + acc_end_word <= calldata.len(), "accumulator words run past the fixture calldata; the fixture is inconsistent" ); @@ -183,15 +215,20 @@ fn ivc_accumulator_decoder_rejects_malformed_public_accumulator() { let mut evm = Evm::default(); let vk_address = evm.create(compile_solidity_with_runs(&vk_solidity, SOLC_OPTIMIZE_RUNS)); - let quotient_address = evm.create(compile_solidity_with_runs( - "ient_solidity, - SOLC_OPTIMIZE_RUNS, - )); - let verifier_address = evm.create_with_two_address_args( - compile_solidity_with_runs(&verifier_solidity, SOLC_OPTIMIZE_RUNS), - vk_address, - quotient_address, - ); + let verifier_code = compile_solidity_with_runs(&verifier_solidity, SOLC_OPTIMIZE_RUNS); + // Split renders pin a separately deployed quotient evaluator; single + // contract renders take the verifying key alone. + let verifier_address = match read_optional_fixture_string(fixture, "Halo2QuotientEvaluator.sol") + { + Some(quotient_solidity) => { + let quotient_address = evm.create(compile_solidity_with_runs( + "ient_solidity, + SOLC_OPTIMIZE_RUNS, + )); + evm.create_with_two_address_args(verifier_code, vk_address, quotient_address) + } + None => evm.create_with_address_arg(verifier_code, vk_address), + }; match evm.try_call_with_gas(verifier_address, calldata.clone(), GAS_CAP) { CallOutcome::Success { output, .. } => { @@ -238,7 +275,9 @@ fn ivc_accumulator_decoder_rejects_malformed_public_accumulator() { ] { let mut malformed = calldata.clone(); malformed[point_word + 31] ^= 0x01; - malformed[scalar_word..scalar_word + 0x20].fill(0); + if has_carried_scalars { + malformed[scalar_word..scalar_word + 0x20].fill(0); + } assert_reverts( evm.try_call_with_gas(verifier_address, malformed, GAS_CAP), case, @@ -265,6 +304,171 @@ fn ivc_accumulator_decoder_rejects_malformed_public_accumulator() { case, ); } + + // --------------------------------------------------------------------- + // Calldata framing. These attacks need no layout knowledge at all. + // --------------------------------------------------------------------- + let instance_count = read_u256_word(&calldata, instances_len_word); + let mut framing_cases: Vec<(String, Vec)> = Vec::new(); + + let mut trailing = calldata.clone(); + trailing.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]); + framing_cases.push(("extra trailing calldata".into(), trailing)); + + let mut truncated = calldata.clone(); + truncated.pop(); + framing_cases.push(("truncated calldata".into(), truncated)); + + let mut wrong_selector = calldata.clone(); + wrong_selector[3] ^= 0x01; + framing_cases.push(("wrong function selector".into(), wrong_selector)); + + for (case, offset, value) in [ + ("wrong proof ABI head", 0x04, 0x60), + ("wrong instances ABI head", 0x24, 0x20), + ( + "short proof length", + PROOF_PAYLOAD_START - 0x20, + proof_len as u64 - 0x20, + ), + ( + "long proof length", + PROOF_PAYLOAD_START - 0x20, + proof_len as u64 + 0x20, + ), + ( + "wrong instance array length", + instances_len_word, + instance_count + 1, + ), + ] { + let mut mutated = calldata.clone(); + write_u256_word(&mut mutated, offset, value); + framing_cases.push((case.into(), mutated)); + } + + for (case, mutated) in framing_cases { + assert_reverts( + evm.try_call_with_gas(verifier_address, mutated, GAS_CAP), + &case, + ); + } + + // --------------------------------------------------------------------- + // Curve-level attacks on every proof commitment. + // + // The repacked proof opens with a run of EIP-2537 padded G1 points, so the + // run length is discovered from the padding signature rather than + // hardcoded: a regenerated fixture with a different commitment count stays + // covered. + // --------------------------------------------------------------------- + let g1_count = padded_g1_block_count(&calldata, PROOF_PAYLOAD_START, proof_len); + assert!( + g1_count >= 8, + "expected a run of padded G1 commitments at the proof head, found {g1_count}; \ + the fixture proof layout changed" + ); + + let p_hi = solidity_constant(&verifier_solidity, "BLS_P_HI"); + let mut p_lo = solidity_constant(&verifier_solidity, "BLS_P_MINUS_ONE_LO"); + // p - 1 ends in ...aaaa, so incrementing cannot carry out of the low byte. + p_lo[31] += 1; + + for index in 0..g1_count { + let at = PROOF_PAYLOAD_START + index * G1_PADDED_BYTES; + + // (0, 1) is field-canonical but off the curve: y^2 = x^3 + 4 gives + // 1 != 4, so the G1 precompiles must reject it. + let mut off_curve = calldata.clone(); + off_curve[at..at + G1_PADDED_BYTES].fill(0); + off_curve[at + G1_PADDED_BYTES - 1] = 1; + + // x = p exactly: one past the largest canonical coordinate. + let mut base_modulus = calldata.clone(); + base_modulus[at..at + 0x20].copy_from_slice(&p_hi); + base_modulus[at + 0x20..at + 0x40].copy_from_slice(&p_lo); + + // EIP-2537 pads each 48-byte coordinate with 16 leading zero bytes. + // Setting one is a non-canonical encoding of an otherwise valid point. + let mut bad_padding = calldata.clone(); + bad_padding[at] ^= 0x01; + + for (label, mutated) in [ + ("off-curve", off_curve), + ("base-modulus", base_modulus), + ("non-canonical padding", bad_padding), + ] { + assert_reverts( + evm.try_call_with_gas(verifier_address, mutated, GAS_CAP), + &format!("{label} G1 at proof commitment {index}"), + ); + } + } + + // --------------------------------------------------------------------- + // Scalar canonicality across the evaluation block that follows the + // commitments, plus the non-accumulator public inputs. + // --------------------------------------------------------------------- + let fr_modulus = solidity_constant(&verifier_solidity, "FR_MODULUS"); + let evals_start = PROOF_PAYLOAD_START + g1_count * G1_PADDED_BYTES; + let evals_end = PROOF_PAYLOAD_START + proof_len; + assert!( + evals_start < evals_end && (evals_end - evals_start) % 0x20 == 0, + "evaluation block is not a whole number of words" + ); + + for (index, at) in (evals_start..evals_end).step_by(0x20).enumerate() { + let mut noncanonical = calldata.clone(); + noncanonical[at..at + 0x20].copy_from_slice(&fr_modulus); + assert_reverts( + evm.try_call_with_gas(verifier_address, noncanonical, GAS_CAP), + &format!("proof scalar {index} set to the Fr modulus"), + ); + } + + // The accumulator words are covered above; sweep the remaining public + // inputs for scalar canonicality. + let first_instance_word = instances_len_word + 0x20; + for index in 0..instance_count as usize { + let at = first_instance_word + index * 0x20; + if (first_acc_word..rhs_scalar_word + 0x20).contains(&at) { + continue; + } + let mut noncanonical = calldata.clone(); + noncanonical[at..at + 0x20].copy_from_slice(&fr_modulus); + assert_reverts( + evm.try_call_with_gas(verifier_address, noncanonical, GAS_CAP), + &format!("public input {index} set to the Fr modulus"), + ); + } +} + +/// EIP-2537 padded G1: `x_hi, x_lo, y_hi, y_lo`. +const G1_PADDED_BYTES: usize = 4 * 0x20; + +/// Count the leading run of EIP-2537 padded G1 points in the repacked proof. +/// +/// Each coordinate is a 48-byte field element left-padded to 64 bytes, so the +/// high word of `x` and of `y` both start with sixteen zero bytes. Evaluation +/// scalars do not share that signature, which is what ends the run. +fn padded_g1_block_count(calldata: &[u8], proof_start: usize, proof_len: usize) -> usize { + let mut count = 0; + while (count + 1) * G1_PADDED_BYTES <= proof_len { + let at = proof_start + count * G1_PADDED_BYTES; + let x_pad_zero = calldata[at..at + 16].iter().all(|b| *b == 0); + let y_pad_zero = calldata[at + 0x40..at + 0x50].iter().all(|b| *b == 0); + if !(x_pad_zero && y_pad_zero) { + break; + } + count += 1; + } + count +} + +/// Write `value` as a big-endian EVM word at `offset`. +fn write_u256_word(calldata: &mut [u8], offset: usize, value: u64) { + calldata[offset..offset + 0x20].fill(0); + calldata[offset + 0x18..offset + 0x20].copy_from_slice(&value.to_be_bytes()); } /// Read a rendered `uint256 internal constant NAME = 0x...;` out of the From 29b4eb91da47ddb87d33c5fdcd6545cce1850046 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 05:12:24 +0100 Subject: [PATCH 66/72] Record deployment status and bytecode verification for moonlight-wrap The Sepolia deployment record documented what was deployed but not how to check it, nor that the generator has since diverged from it. Add both. Deliberately does not regenerate the artifacts. This directory is a record of a live deployment -- addresses, transaction hashes and the runtime codehashes of contracts that exist on Sepolia. Re-rendering Halo2Verifier.sol at current codegen while leaving those fields would assert that 0x5CfEd44D16F994fc17f681A83FEdFEB9c3348c17 has source it does not have. The sources and bytecode stay exactly as deployed. Verification. The tracked source does reproduce the on-chain runtime: compiled with the documented flags at optimizer-runs 200 it yields 21,161 bytes, matching the recorded runtimeBytes. Its codehash (0x79432a36...) is *not* the recorded runtimeCodeHash, and should not be: a byte-level comparison against the fetched on-chain code shows exactly 40 differing bytes, in two 20-byte runs at offsets 0x51 and 0x125. Those are the placeholder slots for `address public immutable AUTHORIZED_VK`, which the constructor fills at deployment. Every other byte matches. Both the command and the figures were re-run from inside the deployment directory before being written down. Divergence. Re-rendering the same circuit at e5300d4 gives a 212,419-byte source and a 21,203-byte runtime against the deployed 206,619 / 21,161, with TRANSCRIPT_MPTR moving from 0x80 to 0x1000. The verifying key differs too, in its quotient_program section. The circuit is unchanged (acc_offset = 11, 19 public inputs, point_pair). The record now names the fixes the deployed code does not carry: the memory layout rebase (its layout still sits inside solc's via-IR spill window -- AUDIT.md TA-5), the always-false accumulator identity guard, the pairing check accepting any odd result word, and the identity-only EIP-2537 smoke vectors. None is a demonstrated forgery path, but a reader of this directory should not have to reconstruct that list from the git log. Redeployment needs a funded keystore and an RPC endpoint and is left to a deliberate operator action. Co-Authored-By: Claude Opus 4.8 --- .../sepolia/moonlight-wrap/README.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/proofs/solidity-verifier/deployments/sepolia/moonlight-wrap/README.md b/proofs/solidity-verifier/deployments/sepolia/moonlight-wrap/README.md index 0a2c41b79..130a695cf 100644 --- a/proofs/solidity-verifier/deployments/sepolia/moonlight-wrap/README.md +++ b/proofs/solidity-verifier/deployments/sepolia/moonlight-wrap/README.md @@ -4,6 +4,64 @@ This directory contains the generated Moonlight wrap verifier contracts that were deployed to Sepolia, plus the exact runtime bytecode fetched back from the chain. +## Status: deployed code predates current codegen + +**The contracts at the addresses below are still the ones described here, but +the generator has since moved on. They are not reproducible from current +`main`.** Nothing in this directory has been regenerated -- the sources and +bytecode remain exactly what was deployed, because they are the record of what +is on chain. + +Re-rendering the same circuit at `e5300d4` produces a different verifier: + +| | Deployed | Current codegen | +| --- | ---: | ---: | +| `Halo2Verifier.sol` | 206,619 bytes | 212,419 bytes | +| Verifier runtime | 21,161 bytes | 21,203 bytes | +| `TRANSCRIPT_MPTR` | `0x80` | `0x1000` | + +The verifying key also differs, in its `quotient_program` section; the circuit +itself is unchanged (`acc_offset = 11`, 19 public inputs, `point_pair`). + +Fixes made after this deployment that it therefore does **not** carry: + +- **Memory layout rebase.** The deployed verifier bases its layout at `0x80`, + inside the `[0x80, 0x8e0)` window solc reserves for via-IR stack-to-memory + spill slots -- see AUDIT.md TA-5. It has not misbehaved, but the separation + rests on spill placement rather than on anything enforced. +- **Accumulator identity guard.** `load_acc_coord_shifted` used a bitwise `and` + against a radix base, making the guard false on every call; the identity + branch was dead and the canonicality barrier ineffective. Fail-closed, not a + forgery path. +- **Pairing result check.** `ec_pairing` folded the precompile result with a + bitwise `and`, accepting any odd return word rather than exactly `1`. +- **Constructor smoke test.** Probes used identity-only EIP-2537 vectors, which + a non-conformant precompile can satisfy without doing curve arithmetic. + +Redeploying is a deliberate on-chain action requiring a funded keystore and an +RPC endpoint; see "Recreate The Deployment" below. Until then this record stays +as-is and accurate. + +## Verifying the deployed bytecode + +The tracked source reproduces the on-chain runtime exactly, apart from the +immutable address slots that are substituted at deployment: + +```bash +solc --bin-runtime --optimize --optimize-runs 200 --via-ir \ + --evm-version cancun --no-cbor-metadata Halo2Verifier.sol +``` + +That yields 21,161 bytes -- matching `runtimeBytes` -- with codehash +`0x79432a36a98570db8c04b9c5cc23994477089eabd2501cfe0b0a78ae5f3c38f8`. + +It does **not** equal the recorded `runtimeCodeHash`, and should not: comparing +compiled output byte-for-byte against on-chain code shows exactly 40 differing +bytes, in two 20-byte runs at offsets `0x51` and `0x125`. Those are the two +placeholder slots for `address public immutable AUTHORIZED_VK`, filled in by the +constructor. Every other byte is identical. Verified at `e5300d4` with +`solc 0.8.30+commit.73712a01`. + ## Addresses | Contract | Address | From 9bf8c51e1d1cc29afeac5d64f9d676aa8d470369 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 05:14:22 +0100 Subject: [PATCH 67/72] Regenerate tracked verifier fixture artifacts The rendered artifacts under proofs/solidity-verifier/target are committed and gated in CI, which fails the build when they drift from the generator. Refresh them for the codegen changes on this branch. Every difference is accounted for: - TRANSCRIPT_MPTR / RETURN_MPTR move from 0x80 to 0x1000, the layout rebase above solc's via-IR spill reservation. - ec_pairing compares the precompile result against 1 rather than folding it with a bitwise and. - The accumulator limb decoder's identity guard is gated on the word index alone, replacing the always-false bitwise and. - The constructor smoke test gains the G1ADD(G, G) == 2G known-answer vector. - Halo2VerifyingKey's quotient_program changes because the VM encodes absolute memory pointers: each shifts by exactly 0xf80, which is 0x1000 - 0x80, matching the layout move (0x4740 -> 0x56c0, 0x48c0 -> 0x5840, 0x47e0 -> 0x5760). Co-Authored-By: Claude Opus 4.8 --- .../hybrid-mt-fixture-dump/Halo2Verifier.sol | 1029 +++++++++-------- .../Halo2VerifyingKey.sol | 38 +- .../Halo2QuotientEvaluator.sol | 513 ++++---- .../Halo2Verifier.sol | 822 +++++++------ .../Halo2VerifyingKey.sol | 286 ++--- .../poseidon-fixture-dump/Halo2Verifier.sol | 811 +++++++------ .../Halo2VerifyingKey.sol | 6 +- .../Halo2Verifier.sol | 665 ++++++----- .../Halo2Verifier.sol | 963 +++++++-------- .../Halo2VerifyingKey.sol | 26 +- 10 files changed, 2719 insertions(+), 2440 deletions(-) diff --git a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol index 3168b1c84..6c05c2567 100644 --- a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol @@ -43,7 +43,7 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 8640; uint256 internal constant EXPECTED_VK_LENGTH = 8641; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0xd85df9a6825e0edb1a2ed376a8a317078c3f98f663d45355679082083fbbd1ad; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x055be1a34918fad6a0d2545bab0bac0a28bbc5c616f3a9d284639391b7b99b3e; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -55,8 +55,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0x15c4; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -64,84 +64,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x1de0; - uint256 internal constant VK_DIGEST_MPTR = 0x1de0; - uint256 internal constant NUM_INSTANCES_MPTR = 0x1e00; - uint256 internal constant K_MPTR = 0x1e20; - uint256 internal constant N_INV_MPTR = 0x1e40; - uint256 internal constant OMEGA_MPTR = 0x1e60; - uint256 internal constant OMEGA_INV_MPTR = 0x1e80; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x1ea0; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x1ec0; - uint256 internal constant ACC_OFFSET_MPTR = 0x1ee0; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x1f00; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x1f20; - uint256 internal constant G1_BASE_MPTR = 0x1f40; - uint256 internal constant G2_BASE_MPTR = 0x1fc0; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x20c0; - - uint256 internal constant CHALLENGE_MPTR = 0x3fa0; + uint256 internal constant VK_MPTR = 0x2d60; + uint256 internal constant VK_DIGEST_MPTR = 0x2d60; + uint256 internal constant NUM_INSTANCES_MPTR = 0x2d80; + uint256 internal constant K_MPTR = 0x2da0; + uint256 internal constant N_INV_MPTR = 0x2dc0; + uint256 internal constant OMEGA_MPTR = 0x2de0; + uint256 internal constant OMEGA_INV_MPTR = 0x2e00; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x2e20; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x2e40; + uint256 internal constant ACC_OFFSET_MPTR = 0x2e60; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2e80; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x2ea0; + uint256 internal constant G1_BASE_MPTR = 0x2ec0; + uint256 internal constant G2_BASE_MPTR = 0x2f40; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x3040; + + uint256 internal constant CHALLENGE_MPTR = 0x4f20; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x3fa0; - uint256 internal constant BETA_MPTR = 0x3fc0; - uint256 internal constant GAMMA_MPTR = 0x3fe0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x4000; - uint256 internal constant Y_MPTR = 0x4020; - uint256 internal constant X_MPTR = 0x4040; - uint256 internal constant X1_MPTR = 0x4060; - uint256 internal constant X2_MPTR = 0x4080; - uint256 internal constant X3_MPTR = 0x40a0; - uint256 internal constant X4_MPTR = 0x40c0; + uint256 internal constant THETA_MPTR = 0x4f20; + uint256 internal constant BETA_MPTR = 0x4f40; + uint256 internal constant GAMMA_MPTR = 0x4f60; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x4f80; + uint256 internal constant Y_MPTR = 0x4fa0; + uint256 internal constant X_MPTR = 0x4fc0; + uint256 internal constant X1_MPTR = 0x4fe0; + uint256 internal constant X2_MPTR = 0x5000; + uint256 internal constant X3_MPTR = 0x5020; + uint256 internal constant X4_MPTR = 0x5040; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x40e0; - uint256 internal constant PI_MPTR = 0x4160; + uint256 internal constant F_COM_MPTR = 0x5060; + uint256 internal constant PI_MPTR = 0x50e0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x41e0; - uint256 internal constant ACC_RHS_MPTR = 0x4260; + uint256 internal constant ACC_LHS_MPTR = 0x5160; + uint256 internal constant ACC_RHS_MPTR = 0x51e0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x42e0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x4300; - uint256 internal constant L_LAST_MPTR = 0x4320; - uint256 internal constant L_BLIND_MPTR = 0x4340; - uint256 internal constant L_0_MPTR = 0x4360; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x4380; + uint256 internal constant X_N_MPTR = 0x5260; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x5280; + uint256 internal constant L_LAST_MPTR = 0x52a0; + uint256 internal constant L_BLIND_MPTR = 0x52c0; + uint256 internal constant L_0_MPTR = 0x52e0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x5300; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x43a0; - uint256 internal constant QUOTIENT_MPTR = 0x43c0; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x4460; - uint256 internal constant V_MPTR = 0x4480; - uint256 internal constant FINAL_COM_MPTR = 0x44a0; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x4520; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x45a0; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x5320; + uint256 internal constant QUOTIENT_MPTR = 0x5340; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x53e0; + uint256 internal constant V_MPTR = 0x5400; + uint256 internal constant FINAL_COM_MPTR = 0x5420; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x54a0; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x5520; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x4620; - uint256 internal constant X1_POWERS_MPTR = 0x49a0; + uint256 internal constant ROT_POINTS_MPTR = 0x55a0; + uint256 internal constant X1_POWERS_MPTR = 0x5920; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x51c0; - uint256 internal constant Q_EVAL_SET_MPTR = 0x51c0; + uint256 internal constant Q_COM_MPTR = 0x6140; + uint256 internal constant Q_EVAL_SET_MPTR = 0x6140; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x58c0; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x6840; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x59c0; + uint256 internal constant G1_IDENTITY_MPTR = 0x6940; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -149,11 +149,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x5b20; - uint256 internal constant SELECTOR_ACC_MPTR = 0x6ee0; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x6ee0; - uint256 internal constant TRACE_U256_MPTR = 0x9a00; + uint256 internal constant REVERSED_EVALS_MPTR = 0x6aa0; + uint256 internal constant SELECTOR_ACC_MPTR = 0x7e60; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x7e60; + uint256 internal constant TRACE_U256_MPTR = 0xa980; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -170,13 +170,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x63e0; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x67e0; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x68e0; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x6a60; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x6b60; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x6c60; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x6ce0; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x7360; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x7760; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x7860; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x79e0; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x7ae0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x7be0; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x7c60; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -198,7 +198,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -222,11 +222,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0x6ee0 + let msm_scratch := 0x7e60 for { let off := 0 } lt(off, 0x2940) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -329,7 +362,7 @@ contract Halo2Verifier { // guards against a future emitter passing a raw scalar. if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x1ce0 + let p := 0x2c60 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -566,7 +599,11 @@ contract Halo2Verifier { mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -598,7 +635,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -1402,15 +1445,15 @@ contract Halo2Verifier { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x21c0 + let q_const_mptr := 0x3140 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x27c0 + let q_program_mptr := 0x3740 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0x70c0, 0) + mstore(0x8040, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -1431,13 +1474,13 @@ contract Halo2Verifier { // block multiplies by mload(selector_power_mptr + offset) // unconditionally -- so initialize the slot rather than // leaving correctness to two filters in another file. - mstore(0x7100, 1) + mstore(0x8080, 1) // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 42) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0x7100, shl(5, q_y_power_i)), q_y_power) + mstore(add(0x8080, shl(5, q_y_power_i)), q_y_power) } } @@ -1446,102 +1489,102 @@ contract Halo2Verifier { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x5f60) - let f_4 := mload(0x5e60) - let a_0 := mload(0x5b40) + let f_3 := mload(0x6ee0) + let f_4 := mload(0x6de0) + let a_0 := mload(0x6ac0) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x5e80) - let a_1 := mload(0x5b60) + let f_5 := mload(0x6e00) + let a_1 := mload(0x6ae0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x5ea0) - let a_2 := mload(0x5b80) + let f_6 := mload(0x6e20) + let a_2 := mload(0x6b00) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x5ec0) - let a_3 := mload(0x5ba0) + let f_7 := mload(0x6e40) + let a_3 := mload(0x6b20) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x5ee0) - let a_4 := mload(0x5bc0) + let f_8 := mload(0x6e60) + let a_4 := mload(0x6b40) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x5f00) - let a_0_next_1 := mload(0x5be0) + let f_0 := mload(0x6e80) + let a_0_next_1 := mload(0x6b60) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x5f20) + let f_1 := mload(0x6ea0) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x5f40) + let f_2 := mload(0x6ec0) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0x7640, var19) + mstore(0x85c0, var19) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } { let var0 := 0x1 - let a_1 := mload(0x5b60) - let a_2 := mload(0x5b80) + let a_1 := mload(0x6ae0) + let a_2 := mload(0x6b00) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0x7640, var6) + mstore(0x85c0, var6) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } { let var0 := 0x1 - let a_0 := mload(0x5b40) - let f_4 := mload(0x5e60) + let a_0 := mload(0x6ac0) + let f_4 := mload(0x6de0) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x5be0) + let a_0_next_1 := mload(0x6b60) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x7640, var4) + mstore(0x85c0, var4) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } { let var0 := 0x1 - let a_1 := mload(0x5b60) - let f_5 := mload(0x5e80) + let a_1 := mload(0x6ae0) + let f_5 := mload(0x6e00) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x5c00) + let a_1_next_1 := mload(0x6b80) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x7640, var4) + mstore(0x85c0, var4) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x7100, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x8080, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } // VM registers: @@ -1561,7 +1604,7 @@ contract Halo2Verifier { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x025d) // q_sp starts at the first free stack word. - let q_sp := 0x7640 + let q_sp := 0x85c0 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -1892,70 +1935,70 @@ contract Halo2Verifier { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0x7640 + q_sp := 0x85c0 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0x7640 - let q_perm_sigmas := 0x7760 - let q_perm_z_cur := 0x7880 - let q_perm_z_next := 0x78e0 - let q_perm_z_last := 0x7940 - let q_perm_delta_base_ptr := 0x7980 + let q_perm_vals := 0x85c0 + let q_perm_sigmas := 0x86e0 + let q_perm_z_cur := 0x8800 + let q_perm_z_next := 0x8860 + let q_perm_z_last := 0x88c0 + let q_perm_delta_base_ptr := 0x8900 let q_perm_num_cols := 9 let q_perm_num_sets := 3 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x5e40)) + mstore(add(q_perm_vals, 0x0), mload(0x6dc0)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x5b40, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x6ac0, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x5b20)) + mstore(add(q_perm_vals, 0xc0), mload(0x6aa0)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) - mstore(add(q_perm_vals, 0x100), mload(0x5c40)) + mstore(add(q_perm_vals, 0x100), mload(0x6bc0)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 9) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x60a0, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x7020, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 3) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x61c0, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x7140, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 3) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x61e0, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x7160, q_perm_z_next_load_src_off))) } } - mstore(add(q_perm_z_last, 0x0), mload(0x6200)) - mstore(add(q_perm_z_last, 0x20), mload(0x6260)) + mstore(add(q_perm_z_last, 0x0), mload(0x7180)) + mstore(add(q_perm_z_last, 0x20), mload(0x71e0)) let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_perm_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0x40)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_perm_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 3) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_perm_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 3) { q_perm_set := add(q_perm_set, 1) } { @@ -1974,8 +2017,8 @@ contract Halo2Verifier { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_perm_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -1996,13 +2039,13 @@ contract Halo2Verifier { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0x7640 + q_sp := 0x85c0 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0x7640 - let q_lookup_prefix := 0x7680 - let q_lookup_suffix := 0x76c0 + let q_lookup_f := 0x85c0 + let q_lookup_prefix := 0x8600 + let q_lookup_suffix := 0x8640 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -2012,54 +2055,54 @@ contract Halo2Verifier { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6300), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x7280), r) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } { - let f_10 := mload(0x5f80) + let f_10 := mload(0x6f00) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_1, r) - let q_lookup_eval := addmod(mulmod(mload(0x62e0), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x7260), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x62e0) - let f_19 := mload(0x6040) - let f_11 := mload(0x5fa0) + let q_lookup_sum_h := mload(0x7260) + let f_19 := mload(0x6fc0) + let f_11 := mload(0x6f20) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x5fc0) + let f_12 := mload(0x6f40) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_19, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x6320), sub(r, addmod(mload(0x6300), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x72a0), sub(r, addmod(mload(0x7280), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x62c0), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x7240), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } } { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6380), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x7300), r) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } { - let f_0 := mload(0x5f00) + let f_0 := mload(0x6e80) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_0, r) - let a_6 := mload(0x5c60) + let a_6 := mload(0x6be0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_6, r) - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_0, r) mstore(add(q_lookup_f, 0x0), addmod(var2, q_lookup_beta, r)) - let f_1 := mload(0x5f20) + let f_1 := mload(0x6ea0) let var3 := addmod(mulmod(0, q_lookup_theta, r), f_1, r) - let a_7 := mload(0x5c80) + let a_7 := mload(0x6c00) let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_7, r) - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_1, r) mstore(add(q_lookup_f, 0x20), addmod(var5, q_lookup_beta, r)) let q_lookup_product := 1 @@ -2080,26 +2123,26 @@ contract Halo2Verifier { for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 2) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) } - let q_lookup_eval := addmod(mulmod(mload(0x6360), q_lookup_product, r), sub(r, q_lookup_sum), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x72e0), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x6360) - let f_20 := mload(0x6060) - let f_13 := mload(0x5fe0) + let q_lookup_sum_h := mload(0x72e0) + let f_20 := mload(0x6fe0) + let f_13 := mload(0x6f60) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_13, r) - let f_14 := mload(0x6000) + let f_14 := mload(0x6f80) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_14, r) - let f_15 := mload(0x6020) + let f_15 := mload(0x6fa0) let var2 := addmod(mulmod(var1, q_lookup_theta, r), f_15, r) let q_lookup_s_sum_h := mulmod(f_20, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x63a0), sub(r, addmod(mload(0x6380), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x7320), sub(r, addmod(mload(0x7300), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var2, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x6340), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x72c0), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } } } @@ -2120,24 +2163,24 @@ contract Halo2Verifier { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0x7640 + q_sp := 0x85c0 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 let var1 := 0x1000000000000000 - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var2 := mulmod(var1, a_4, r) let var3 := 0x10000000000 - let a_3_prev_1 := mload(0x5ca0) + let a_3_prev_1 := mload(0x6c20) let var4 := mulmod(var3, a_3_prev_1, r) let var5 := addmod(var2, var4, r) let var6 := 0x400000 - let a_4_prev_1 := mload(0x5cc0) + let a_4_prev_1 := mload(0x6c40) let var7 := mulmod(var6, a_4_prev_1, r) let var8 := addmod(var5, var7, r) - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var9 := addmod(var8, a_3, r) let var10 := 0x40000000000 let var11 := mulmod(var10, a_3, r) @@ -2159,54 +2202,54 @@ contract Halo2Verifier { let var27 := addmod(var24, var26, r) let var28 := addmod(var27, a_3_prev_1, r) let var29 := addmod(var19, var28, r) - let a_0_prev_1 := mload(0x5ce0) + let a_0_prev_1 := mload(0x6c60) let var30 := mulmod(var10, a_0_prev_1, r) - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var31 := mulmod(var25, a_0, r) let var32 := addmod(var30, var31, r) - let a_0_next_1 := mload(0x5be0) + let a_0_next_1 := mload(0x6b60) let var33 := addmod(var32, a_0_next_1, r) let var34 := 0x2 - let a_1_prev_1 := mload(0x5d00) + let a_1_prev_1 := mload(0x6c80) let var35 := mulmod(var10, a_1_prev_1, r) - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var36 := mulmod(var25, a_1, r) let var37 := addmod(var35, var36, r) - let a_1_next_1 := mload(0x5c00) + let a_1_next_1 := mload(0x6b80) let var38 := addmod(var37, a_1_next_1, r) let var39 := mulmod(var34, var38, r) let var40 := addmod(var33, var39, r) let var41 := addmod(0, sub(r, var40), r) let var42 := addmod(var29, var41, r) let var43 := mulmod(var0, var42, r) - mstore(0x7640, var43) + mstore(0x85c0, var43) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xa0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } } case 1 { { let var0 := 0x1 let var1 := 0x10000000000000 - let a_3_next_1 := mload(0x5d20) + let a_3_next_1 := mload(0x6ca0) let var2 := mulmod(var1, a_3_next_1, r) let var3 := 0x4000000000 - let a_3_prev_1 := mload(0x5ca0) + let a_3_prev_1 := mload(0x6c20) let var4 := mulmod(var3, a_3_prev_1, r) let var5 := addmod(var2, var4, r) let var6 := 0x4000 - let a_4_prev_1 := mload(0x5cc0) + let a_4_prev_1 := mload(0x6c40) let var7 := mulmod(var6, a_4_prev_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x400 - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var10 := mulmod(var9, a_3, r) let var11 := addmod(var8, var10, r) - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var12 := addmod(var11, a_4, r) let var13 := 0x40000000000000 let var14 := mulmod(var13, a_4, r) @@ -2233,112 +2276,112 @@ contract Halo2Verifier { let var35 := addmod(var33, var34, r) let var36 := addmod(var35, a_3_prev_1, r) let var37 := addmod(var25, var36, r) - let a_0_prev_1 := mload(0x5ce0) + let a_0_prev_1 := mload(0x6c60) let var38 := mulmod(var15, a_0_prev_1, r) let var39 := 0x100000 - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var40 := mulmod(var39, a_0, r) let var41 := addmod(var38, var40, r) - let a_0_next_1 := mload(0x5be0) + let a_0_next_1 := mload(0x6b60) let var42 := addmod(var41, a_0_next_1, r) let var43 := 0x2 - let a_1_prev_1 := mload(0x5d00) + let a_1_prev_1 := mload(0x6c80) let var44 := mulmod(var15, a_1_prev_1, r) - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var45 := mulmod(var39, a_1, r) let var46 := addmod(var44, var45, r) - let a_1_next_1 := mload(0x5c00) + let a_1_next_1 := mload(0x6b80) let var47 := addmod(var46, a_1_next_1, r) let var48 := mulmod(var43, var47, r) let var49 := addmod(var42, var48, r) let var50 := addmod(0, sub(r, var49), r) let var51 := addmod(var37, var50, r) let var52 := mulmod(var0, var51, r) - mstore(0x7640, var52) + mstore(0x85c0, var52) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xc0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } } case 2 { { let var0 := 0x1 - let f_0 := mload(0x5f00) - let a_0_next_1 := mload(0x5be0) + let f_0 := mload(0x6e80) + let a_0_next_1 := mload(0x6b60) let var1 := addmod(0, sub(r, a_0_next_1), r) let var2 := addmod(f_0, var1, r) let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_2 := mload(0x5b80) + let a_2 := mload(0x6b00) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x5c40) + let a_5 := mload(0x6bc0) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x7640, var18) + mstore(0x85c0, var18) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x1c0) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x7100, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x8080, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } } case 3 { { let var0 := 0x1 - let f_1 := mload(0x5f20) - let a_1_next_1 := mload(0x5c00) + let f_1 := mload(0x6ea0) + let a_1_next_1 := mload(0x6b80) let var1 := addmod(0, sub(r, a_1_next_1), r) let var2 := addmod(f_1, var1, r) let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f - let a_2 := mload(0x5b80) + let a_2 := mload(0x6b00) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x5c40) + let a_5 := mload(0x6bc0) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x7640, var18) + mstore(0x85c0, var18) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x1c0) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x7100, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x8080, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } } default { revert(0, 0) } @@ -2362,14 +2405,14 @@ contract Halo2Verifier { // The global fully-evaluated accumulator is still // multiplied by y so later main identities land at the // same y powers as Rust's reverse fold. - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) let q_sel_acc := mload(q_target_ptr) if q_sel_gap { // Selector buckets are sparse in the global // identity stream. Precomputed y^gap advances only // this selector's local accumulator. - q_sel_acc := mulmod(q_sel_acc, mload(add(0x7100, shl(5, q_sel_gap))), r) + q_sel_acc := mulmod(q_sel_acc, mload(add(0x8080, shl(5, q_sel_gap))), r) } mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) } @@ -2389,7 +2432,7 @@ contract Halo2Verifier { // top, leaving abandoned words below q_sp with q_has_top // clear -- so both checks above pass while an operand of the // identity has been silently dropped from nu_y(x). - if iszero(eq(q_sp, 0x7640)) { revert(0, 0) } + if iszero(eq(q_sp, 0x85c0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -2401,52 +2444,52 @@ contract Halo2Verifier { { let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) { - let f_0 := mload(0x5f00) - let a_0_next_1 := mload(0x5be0) + let f_0 := mload(0x6e80) + let a_0_next_1 := mload(0x6b60) let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 let var1 := mulmod(a_0_next_1, var0, r) let var2 := addmod(f_0, var1, r) let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var4 := mulmod(var3, a_0, r) let var5 := addmod(var2, var4, r) let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var7 := mulmod(var6, a_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 - let a_2 := mload(0x5b80) + let a_2 := mload(0x6b00) let var10 := q_pow5(a_2) let var11 := mulmod(var9, var10, r) let var12 := addmod(var8, var11, r) let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var14 := q_pow5(a_3) let var15 := mulmod(var13, var14, r) let var16 := addmod(var12, var15, r) let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var18 := q_pow5(a_4) let var19 := mulmod(var17, var18, r) let var20 := addmod(var16, var19, r) let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e - let a_5 := mload(0x5c40) + let a_5 := mload(0x6bc0) let var22 := q_pow5(a_5) let var23 := mulmod(var21, var22, r) let var24 := addmod(var20, var23, r) let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 - let a_6 := mload(0x5c60) + let a_6 := mload(0x6be0) let var26 := q_pow5(a_6) let var27 := mulmod(var25, var26, r) let var28 := addmod(var24, var27, r) let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_7 := mload(0x5c80) + let a_7 := mload(0x6c00) let var30 := q_pow5(a_7) let var31 := mulmod(var29, var30, r) let var32 := addmod(var28, var31, r) let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) - let f_1 := mload(0x5f20) - let a_1_next_1 := mload(0x5c00) + let f_1 := mload(0x6ea0) + let a_1_next_1 := mload(0x6b80) let var34 := mulmod(a_1_next_1, var0, r) let var35 := addmod(f_1, var34, r) let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d @@ -2474,7 +2517,7 @@ contract Halo2Verifier { let var58 := mulmod(var57, var30, r) let var59 := addmod(var56, var58, r) let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) - let f_2 := mload(0x5f40) + let f_2 := mload(0x6ec0) let var61 := mulmod(a_3, var0, r) let var62 := addmod(f_2, var61, r) let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 @@ -2487,7 +2530,7 @@ contract Halo2Verifier { let var70 := mulmod(var69, var10, r) let var71 := addmod(var68, var70, r) let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) - let f_3 := mload(0x5f60) + let f_3 := mload(0x6ee0) let var73 := mulmod(a_4, var0, r) let var74 := addmod(f_3, var73, r) let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 @@ -2502,7 +2545,7 @@ contract Halo2Verifier { let var84 := mulmod(var69, var14, r) let var85 := addmod(var83, var84, r) let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) - let f_4 := mload(0x5e60) + let f_4 := mload(0x6de0) let var87 := mulmod(a_5, var0, r) let var88 := addmod(f_4, var87, r) let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b @@ -2519,7 +2562,7 @@ contract Halo2Verifier { let var100 := mulmod(var69, var18, r) let var101 := addmod(var99, var100, r) let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) - let f_5 := mload(0x5e80) + let f_5 := mload(0x6e00) let var103 := mulmod(a_6, var0, r) let var104 := addmod(f_5, var103, r) let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 @@ -2538,7 +2581,7 @@ contract Halo2Verifier { let var118 := mulmod(var69, var22, r) let var119 := addmod(var117, var118, r) let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) - let f_6 := mload(0x5ea0) + let f_6 := mload(0x6e20) let var121 := mulmod(a_7, var0, r) let var122 := addmod(f_6, var121, r) let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a @@ -2559,8 +2602,8 @@ contract Halo2Verifier { let var138 := mulmod(var69, var26, r) let var139 := addmod(var137, var138, r) let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) - let f_7 := mload(0x5ec0) - let a_2_next_1 := mload(0x5c20) + let f_7 := mload(0x6e40) + let a_2_next_1 := mload(0x6ba0) let var141 := mulmod(a_2_next_1, var0, r) let var142 := addmod(f_7, var141, r) let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 @@ -2583,12 +2626,12 @@ contract Halo2Verifier { let var160 := mulmod(var69, var30, r) let var161 := addmod(var159, var160, r) let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) - let f_33 := mload(0x6080) + let f_33 := mload(0x7000) let q_trash_one_minus_selector := addmod(1, sub(r, f_33), r) - let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x63c0), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x7340), r) let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_trash_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_trash_eval, r)) } } // Finish selector buckets by applying the codegen-known tail @@ -2600,69 +2643,69 @@ contract Halo2Verifier { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0520)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0520)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0500)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0500)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x04a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x04a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0480)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0480)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0440)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0440)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0420)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0420)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0400)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0400)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x03e0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x03e0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x03c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x03c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x03a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x03a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0140) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0360)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0360)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0160) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0320)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0320)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0180) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x02a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x02a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x01a0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0280)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0280)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x01c0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x01c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x01c0)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0x70c0)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0x8040)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } @@ -2766,44 +2809,44 @@ contract Halo2Verifier { { // q_eval_set[0]: 35 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x70c0, 0x5b20) - mstore(0x70e0, 0x62c0) - mstore(0x7100, 0x62e0) - mstore(0x7120, 0x6340) - mstore(0x7140, 0x6360) - mstore(0x7160, 0x63c0) - mstore(0x7180, 0x5e40) - mstore(0x71a0, 0x5e60) - mstore(0x71c0, 0x5e80) - mstore(0x71e0, 0x5ea0) - mstore(0x7200, 0x5ec0) - mstore(0x7220, 0x5ee0) - mstore(0x7240, 0x5f00) - mstore(0x7260, 0x5f20) - mstore(0x7280, 0x5f40) - mstore(0x72a0, 0x5f60) - mstore(0x72c0, 0x5f80) - mstore(0x72e0, 0x5fa0) - mstore(0x7300, 0x5fc0) - mstore(0x7320, 0x5fe0) - mstore(0x7340, 0x6000) - mstore(0x7360, 0x6020) - mstore(0x7380, 0x6040) - mstore(0x73a0, 0x6060) - mstore(0x73c0, 0x6080) - mstore(0x73e0, 0x60a0) - mstore(0x7400, 0x60c0) - mstore(0x7420, 0x60e0) - mstore(0x7440, 0x6100) - mstore(0x7460, 0x6120) - mstore(0x7480, 0x6140) - mstore(0x74a0, 0x6160) - mstore(0x74c0, 0x6180) - mstore(0x74e0, 0x61a0) - mstore(0x7500, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x5b20) + mstore(0x8040, 0x6aa0) + mstore(0x8060, 0x7240) + mstore(0x8080, 0x7260) + mstore(0x80a0, 0x72c0) + mstore(0x80c0, 0x72e0) + mstore(0x80e0, 0x7340) + mstore(0x8100, 0x6dc0) + mstore(0x8120, 0x6de0) + mstore(0x8140, 0x6e00) + mstore(0x8160, 0x6e20) + mstore(0x8180, 0x6e40) + mstore(0x81a0, 0x6e60) + mstore(0x81c0, 0x6e80) + mstore(0x81e0, 0x6ea0) + mstore(0x8200, 0x6ec0) + mstore(0x8220, 0x6ee0) + mstore(0x8240, 0x6f00) + mstore(0x8260, 0x6f20) + mstore(0x8280, 0x6f40) + mstore(0x82a0, 0x6f60) + mstore(0x82c0, 0x6f80) + mstore(0x82e0, 0x6fa0) + mstore(0x8300, 0x6fc0) + mstore(0x8320, 0x6fe0) + mstore(0x8340, 0x7000) + mstore(0x8360, 0x7020) + mstore(0x8380, 0x7040) + mstore(0x83a0, 0x7060) + mstore(0x83c0, 0x7080) + mstore(0x83e0, 0x70a0) + mstore(0x8400, 0x70c0) + mstore(0x8420, 0x70e0) + mstore(0x8440, 0x7100) + mstore(0x8460, 0x7120) + mstore(0x8480, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x6aa0) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x70c0, 0x20) + let eval_p := add(0x8040, 0x20) for { let i := 1 } lt(i, 0x23) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2817,12 +2860,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[1]: 3 commitment(s) - let q_eval_set_0 := mload(0x6280) - let q_eval_set_1 := mload(0x62a0) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6300), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6320), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6380), mload(add(X1_POWERS_MPTR, 0x40)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x63a0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + let q_eval_set_0 := mload(0x7200) + let q_eval_set_1 := mload(0x7220) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x7280), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x72a0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x7300), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x7320), mload(add(X1_POWERS_MPTR, 0x40)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) } @@ -2832,35 +2875,35 @@ contract Halo2Verifier { { // q_eval_set[2]: 8 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x70c0, 0x5b40) - mstore(0x70e0, 0x5be0) - mstore(0x7100, 0x5ce0) - mstore(0x7120, 0x5b60) - mstore(0x7140, 0x5c00) - mstore(0x7160, 0x5d00) - mstore(0x7180, 0x5b80) - mstore(0x71a0, 0x5c20) - mstore(0x71c0, 0x5da0) - mstore(0x71e0, 0x5ba0) - mstore(0x7200, 0x5d20) - mstore(0x7220, 0x5ca0) - mstore(0x7240, 0x5bc0) - mstore(0x7260, 0x5d40) - mstore(0x7280, 0x5cc0) - mstore(0x72a0, 0x5c40) - mstore(0x72c0, 0x5e00) - mstore(0x72e0, 0x5de0) - mstore(0x7300, 0x5c60) - mstore(0x7320, 0x5d80) - mstore(0x7340, 0x5d60) - mstore(0x7360, 0x5c80) - mstore(0x7380, 0x5e20) - mstore(0x73a0, 0x5dc0) - let q_eval_set_0 := mload(0x5b40) - let q_eval_set_1 := mload(0x5be0) - let q_eval_set_2 := mload(0x5ce0) + mstore(0x8040, 0x6ac0) + mstore(0x8060, 0x6b60) + mstore(0x8080, 0x6c60) + mstore(0x80a0, 0x6ae0) + mstore(0x80c0, 0x6b80) + mstore(0x80e0, 0x6c80) + mstore(0x8100, 0x6b00) + mstore(0x8120, 0x6ba0) + mstore(0x8140, 0x6d20) + mstore(0x8160, 0x6b20) + mstore(0x8180, 0x6ca0) + mstore(0x81a0, 0x6c20) + mstore(0x81c0, 0x6b40) + mstore(0x81e0, 0x6cc0) + mstore(0x8200, 0x6c40) + mstore(0x8220, 0x6bc0) + mstore(0x8240, 0x6d80) + mstore(0x8260, 0x6d60) + mstore(0x8280, 0x6be0) + mstore(0x82a0, 0x6d00) + mstore(0x82c0, 0x6ce0) + mstore(0x82e0, 0x6c00) + mstore(0x8300, 0x6da0) + mstore(0x8320, 0x6d40) + let q_eval_set_0 := mload(0x6ac0) + let q_eval_set_1 := mload(0x6b60) + let q_eval_set_2 := mload(0x6c60) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x70c0, 0x60) + let eval_p := add(0x8040, 0x60) for { let i := 1 } lt(i, 0x8) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2878,12 +2921,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[3]: 2 commitment(s) - let q_eval_set_0 := mload(0x61c0) - let q_eval_set_1 := mload(0x61e0) - let q_eval_set_2 := mload(0x6200) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6220), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6240), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x6260), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + let q_eval_set_0 := mload(0x7140) + let q_eval_set_1 := mload(0x7160) + let q_eval_set_2 := mload(0x7180) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x71a0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x71c0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x71e0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0xc0), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0xe0), q_eval_set_1) mstore(add(Q_EVAL_SET_MPTR, 0x100), q_eval_set_2) @@ -3051,145 +3094,145 @@ contract Halo2Verifier { v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_4, r), r) - mcopy(0x70c0, 0x67e0, 0x80) - mstore(0x7140, mload(add(X1_POWERS_MPTR, 0x20))) - mcopy(0x7160, 0x6a60, 0x80) - mstore(0x71e0, mload(add(X1_POWERS_MPTR, 0x40))) - mcopy(0x7200, 0x6860, 0x80) - mstore(0x7280, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0x72a0, 0x6ae0, 0x80) - mstore(0x7320, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0x7340, 0x6c60, 0x80) - mstore(0x73c0, mload(add(X1_POWERS_MPTR, 0xa0))) - mcopy(0x73e0, 0x2ea0, 0x80) - mstore(0x7460, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0x7480, 0x2c20, 0x80) - mstore(0x7500, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0x7520, 0x2ca0, 0x80) - mstore(0x75a0, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0x75c0, 0x2d20, 0x80) - mstore(0x7640, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0x7660, 0x2da0, 0x80) - mstore(0x76e0, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0x7700, 0x2e20, 0x80) - mstore(0x7780, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0x77a0, 0x2a20, 0x80) - mstore(0x7820, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0x7840, 0x2aa0, 0x80) - mstore(0x78c0, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0x78e0, 0x2b20, 0x80) - mstore(0x7960, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0x7980, 0x2ba0, 0x80) - mstore(0x7a00, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0x7a20, 0x2f20, 0x80) - mstore(0x7aa0, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0x7ac0, 0x2fa0, 0x80) - mstore(0x7b40, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0x7b60, 0x3020, 0x80) - mstore(0x7be0, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0x7c00, 0x30a0, 0x80) - mstore(0x7c80, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0x7ca0, 0x3120, 0x80) - mstore(0x7d20, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0x7d40, 0x31a0, 0x80) - mstore(0x7dc0, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0x7de0, 0x33a0, 0x80) - mstore(0x7e60, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0x7e80, 0x3420, 0x80) - mstore(0x7f00, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0x7f20, 0x3aa0, 0x80) - mstore(0x7fa0, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0x7fc0, 0x3b20, 0x80) - mstore(0x8040, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0x8060, 0x3ba0, 0x80) - mstore(0x80e0, mload(add(X1_POWERS_MPTR, 0x340))) - mcopy(0x8100, 0x3c20, 0x80) - mstore(0x8180, mload(add(X1_POWERS_MPTR, 0x360))) - mcopy(0x81a0, 0x3ca0, 0x80) - mstore(0x8220, mload(add(X1_POWERS_MPTR, 0x380))) - mcopy(0x8240, 0x3d20, 0x80) - mstore(0x82c0, mload(add(X1_POWERS_MPTR, 0x3a0))) - mcopy(0x82e0, 0x3da0, 0x80) - mstore(0x8360, mload(add(X1_POWERS_MPTR, 0x3c0))) - mcopy(0x8380, 0x3e20, 0x80) - mstore(0x8400, mload(add(X1_POWERS_MPTR, 0x3e0))) - mcopy(0x8420, 0x3ea0, 0x80) - mstore(0x84a0, mload(add(X1_POWERS_MPTR, 0x400))) - mcopy(0x84c0, 0x3f20, 0x80) - mstore(0x8540, mload(add(X1_POWERS_MPTR, 0x420))) + mcopy(0x8040, 0x7760, 0x80) + mstore(0x80c0, mload(add(X1_POWERS_MPTR, 0x20))) + mcopy(0x80e0, 0x79e0, 0x80) + mstore(0x8160, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0x8180, 0x77e0, 0x80) + mstore(0x8200, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0x8220, 0x7a60, 0x80) + mstore(0x82a0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0x82c0, 0x7be0, 0x80) + mstore(0x8340, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0x8360, 0x3e20, 0x80) + mstore(0x83e0, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0x8400, 0x3ba0, 0x80) + mstore(0x8480, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0x84a0, 0x3c20, 0x80) + mstore(0x8520, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0x8540, 0x3ca0, 0x80) + mstore(0x85c0, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0x85e0, 0x3d20, 0x80) + mstore(0x8660, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0x8680, 0x3da0, 0x80) + mstore(0x8700, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0x8720, 0x39a0, 0x80) + mstore(0x87a0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0x87c0, 0x3a20, 0x80) + mstore(0x8840, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0x8860, 0x3aa0, 0x80) + mstore(0x88e0, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0x8900, 0x3b20, 0x80) + mstore(0x8980, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0x89a0, 0x3ea0, 0x80) + mstore(0x8a20, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0x8a40, 0x3f20, 0x80) + mstore(0x8ac0, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0x8ae0, 0x3fa0, 0x80) + mstore(0x8b60, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0x8b80, 0x4020, 0x80) + mstore(0x8c00, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0x8c20, 0x40a0, 0x80) + mstore(0x8ca0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0x8cc0, 0x4120, 0x80) + mstore(0x8d40, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0x8d60, 0x4320, 0x80) + mstore(0x8de0, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0x8e00, 0x43a0, 0x80) + mstore(0x8e80, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0x8ea0, 0x4a20, 0x80) + mstore(0x8f20, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0x8f40, 0x4aa0, 0x80) + mstore(0x8fc0, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0x8fe0, 0x4b20, 0x80) + mstore(0x9060, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0x9080, 0x4ba0, 0x80) + mstore(0x9100, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0x9120, 0x4c20, 0x80) + mstore(0x91a0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0x91c0, 0x4ca0, 0x80) + mstore(0x9240, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0x9260, 0x4d20, 0x80) + mstore(0x92e0, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0x9300, 0x4da0, 0x80) + mstore(0x9380, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0x93a0, 0x4e20, 0x80) + mstore(0x9420, mload(add(X1_POWERS_MPTR, 0x400))) + mcopy(0x9440, 0x4ea0, 0x80) + mstore(0x94c0, mload(add(X1_POWERS_MPTR, 0x420))) let lin_query_scalar_33 := mload(add(X1_POWERS_MPTR, 0x440)) let lin_cur_scalar_33 := mulmod(lin_query_scalar_33, lin_one_minus_x_n, r) - mcopy(0x8560, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0x85e0, lin_cur_scalar_33) + mcopy(0x94e0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0x9560, lin_cur_scalar_33) lin_cur_scalar_33 := mulmod(lin_cur_scalar_33, lin_x_split, r) - mcopy(0x8600, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0x8680, lin_cur_scalar_33) + mcopy(0x9580, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0x9600, lin_cur_scalar_33) lin_cur_scalar_33 := mulmod(lin_cur_scalar_33, lin_x_split, r) - mcopy(0x86a0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0x8720, lin_cur_scalar_33) + mcopy(0x9620, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0x96a0, lin_cur_scalar_33) lin_cur_scalar_33 := mulmod(lin_cur_scalar_33, lin_x_split, r) - mcopy(0x8740, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0x87c0, lin_cur_scalar_33) - mcopy(0x87e0, 0x3220, 0x80) - mstore(0x8860, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0x8880, 0x32a0, 0x80) - mstore(0x8900, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0x8920, 0x3320, 0x80) - mstore(0x89a0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0x89c0, 0x34a0, 0x80) - mstore(0x8a40, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) - mcopy(0x8a60, 0x3520, 0x80) - mstore(0x8ae0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) - mcopy(0x8b00, 0x35a0, 0x80) - mstore(0x8b80, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) - mcopy(0x8ba0, 0x3620, 0x80) - mstore(0x8c20, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) - mcopy(0x8c40, 0x36a0, 0x80) - mstore(0x8cc0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) - mcopy(0x8ce0, 0x3720, 0x80) - mstore(0x8d60, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) - mcopy(0x8d80, 0x37a0, 0x80) - mstore(0x8e00, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) - mcopy(0x8e20, 0x3820, 0x80) - mstore(0x8ea0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x140)), r)) - mcopy(0x8ec0, 0x38a0, 0x80) - mstore(0x8f40, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x160)), r)) - mcopy(0x8f60, 0x3920, 0x80) - mstore(0x8fe0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x180)), r)) - mcopy(0x9000, 0x39a0, 0x80) - mstore(0x9080, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x1a0)), r)) - mcopy(0x90a0, 0x3a20, 0x80) - mstore(0x9120, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x1c0)), r)) - mcopy(0x9140, 0x69e0, 0x80) - mstore(0x91c0, x4_pow_1) - mcopy(0x91e0, 0x6b60, 0x80) - mstore(0x9260, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0x9280, 0x6be0, 0x80) - mstore(0x9300, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0x9320, 0x63e0, 0x80) - mstore(0x93a0, x4_pow_2) - mcopy(0x93c0, 0x6460, 0x80) - mstore(0x9440, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0x9460, 0x64e0, 0x80) - mstore(0x94e0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) - mcopy(0x9500, 0x6560, 0x80) - mstore(0x9580, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_2, r)) - mcopy(0x95a0, 0x65e0, 0x80) - mstore(0x9620, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_2, r)) - mcopy(0x9640, 0x6660, 0x80) - mstore(0x96c0, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_2, r)) - mcopy(0x96e0, 0x66e0, 0x80) - mstore(0x9760, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_2, r)) - mcopy(0x9780, 0x6760, 0x80) - mstore(0x9800, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_2, r)) - mcopy(0x9820, 0x68e0, 0x80) - mstore(0x98a0, x4_pow_3) - mcopy(0x98c0, 0x6960, 0x80) - mstore(0x9940, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) - mcopy(0x9960, F_COM_MPTR, 0x80) - mstore(0x99e0, x4_pow_4) + mcopy(0x96c0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0x9740, lin_cur_scalar_33) + mcopy(0x9760, 0x41a0, 0x80) + mstore(0x97e0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0x9800, 0x4220, 0x80) + mstore(0x9880, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0x98a0, 0x42a0, 0x80) + mstore(0x9920, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0x9940, 0x4420, 0x80) + mstore(0x99c0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0x99e0, 0x44a0, 0x80) + mstore(0x9a60, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0x9a80, 0x4520, 0x80) + mstore(0x9b00, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0x9b20, 0x45a0, 0x80) + mstore(0x9ba0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0x9bc0, 0x4620, 0x80) + mstore(0x9c40, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0x9c60, 0x46a0, 0x80) + mstore(0x9ce0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0x9d00, 0x4720, 0x80) + mstore(0x9d80, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0x9da0, 0x47a0, 0x80) + mstore(0x9e20, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x140)), r)) + mcopy(0x9e40, 0x4820, 0x80) + mstore(0x9ec0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x160)), r)) + mcopy(0x9ee0, 0x48a0, 0x80) + mstore(0x9f60, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x180)), r)) + mcopy(0x9f80, 0x4920, 0x80) + mstore(0xa000, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x1a0)), r)) + mcopy(0xa020, 0x49a0, 0x80) + mstore(0xa0a0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x1c0)), r)) + mcopy(0xa0c0, 0x7960, 0x80) + mstore(0xa140, x4_pow_1) + mcopy(0xa160, 0x7ae0, 0x80) + mstore(0xa1e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0xa200, 0x7b60, 0x80) + mstore(0xa280, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0xa2a0, 0x7360, 0x80) + mstore(0xa320, x4_pow_2) + mcopy(0xa340, 0x73e0, 0x80) + mstore(0xa3c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0xa3e0, 0x7460, 0x80) + mstore(0xa460, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0xa480, 0x74e0, 0x80) + mstore(0xa500, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_2, r)) + mcopy(0xa520, 0x7560, 0x80) + mstore(0xa5a0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_2, r)) + mcopy(0xa5c0, 0x75e0, 0x80) + mstore(0xa640, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_2, r)) + mcopy(0xa660, 0x7660, 0x80) + mstore(0xa6e0, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_2, r)) + mcopy(0xa700, 0x76e0, 0x80) + mstore(0xa780, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_2, r)) + mcopy(0xa7a0, 0x7860, 0x80) + mstore(0xa820, x4_pow_3) + mcopy(0xa840, 0x78e0, 0x80) + mstore(0xa8c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xa8e0, F_COM_MPTR, 0x80) + mstore(0xa960, x4_pow_4) if success { - success := staticcall(gas(), 0x0c, 0x70c0, 0x2940, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0x8040, 0x2940, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -3201,28 +3244,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } diff --git a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol index 986773183..fcfb39dad 100644 --- a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol @@ -141,25 +141,25 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x0980), 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140) // quotient_const mstore(add(payload, 0x09a0), 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a) // quotient_const mstore(add(payload, 0x09c0), 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db) // quotient_const - mstore(add(payload, 0x09e0), 0x055b80105ea0055c2008060d000b020001055ca0105cc0105ba00902115d0013) // quotient_program - mstore(add(payload, 0x0a00), 0x5b6001105c000902115ce0135b4001105be00d030608060d000b030000055ca0) // quotient_program - mstore(add(payload, 0x0a20), 0x105cc00902115d00135b6001105c000902115ce0135b4001105be00d03060806) // quotient_program - mstore(add(payload, 0x0a40), 0x0d000b040000055b80105ba0055bc008060d000b0400011b00001b0001210001) // quotient_program - mstore(add(payload, 0x0a60), 0x00000700045b40055b60065b80075ba0085bc0095be00a5c000b5c200c5ca00d) // quotient_program - mstore(add(payload, 0x0a80), 0x5cc00e5ce00f5d00105d20115d400b07000021000100000700045b40055b6012) // quotient_program - mstore(add(payload, 0x0aa0), 0x5b80135ba0145bc0095be00a5c00155c20165ca0175cc00e5ce00f5d00185d20) // quotient_program - mstore(add(payload, 0x0ac0), 0x195d400b080000091b115d60135c601a105d80055da008060d000b090000091e) // quotient_program - mstore(add(payload, 0x0ae0), 0x115d60135dc01d135c601c105c80055da008060d000b0a00000921115ce0135d) // quotient_program - mstore(add(payload, 0x0b00), 0x0020135b401f105b60055b8008060d000b0a00010924115d60135dc01d135c60) // quotient_program - mstore(add(payload, 0x0b20), 0x23135c8022105d80055da008060d000b0b00000926115ce0135d0020135b401e) // quotient_program - mstore(add(payload, 0x0b40), 0x135b6025105be0055b8008060d000b0b00011c275c40285c60295c80015d601a) // quotient_program - mstore(add(payload, 0x0b60), 0x5dc02a5de02b5e00105d80055da008060d000b0c0000090008105de0115de00d) // quotient_program - mstore(add(payload, 0x0b80), 0x000b0c0001090008105c40115c400d000b0c0001090008105e00115e000d000b) // quotient_program - mstore(add(payload, 0x0ba0), 0x0c00011c005ba0005bc0005c20005ca0005cc0005d20005d40055da0135e202c) // quotient_program - mstore(add(payload, 0x0bc0), 0x08060d000b0d0000055b40115b40115b40055ba008060d000b0e0000055b6011) // quotient_program - mstore(add(payload, 0x0be0), 0x5b60115b60055bc008060d000b0e0001055b80115b80115b80055c4008060d00) // quotient_program - mstore(add(payload, 0x0c00), 0x0b0e00011b00021b0003055c2008105f40055b40115b40115ba00d2d06055b60) // quotient_program - mstore(add(payload, 0x0c20), 0x115b60115bc00d2e06055b80115b80115c400d2f060d000b0e0001191f000000) // quotient_program + mstore(add(payload, 0x09e0), 0x056b00106e20056ba008060d000b020001056c20106c40106b200902116c8013) // quotient_program + mstore(add(payload, 0x0a00), 0x6ae001106b800902116c60136ac001106b600d030608060d000b030000056c20) // quotient_program + mstore(add(payload, 0x0a20), 0x106c400902116c80136ae001106b800902116c60136ac001106b600d03060806) // quotient_program + mstore(add(payload, 0x0a40), 0x0d000b040000056b00106b20056b4008060d000b0400011b00001b0001210001) // quotient_program + mstore(add(payload, 0x0a60), 0x00000700046ac0056ae0066b00076b20086b40096b600a6b800b6ba00c6c200d) // quotient_program + mstore(add(payload, 0x0a80), 0x6c400e6c600f6c80106ca0116cc00b07000021000100000700046ac0056ae012) // quotient_program + mstore(add(payload, 0x0aa0), 0x6b00136b20146b40096b600a6b80156ba0166c20176c400e6c600f6c80186ca0) // quotient_program + mstore(add(payload, 0x0ac0), 0x196cc00b080000091b116ce0136be01a106d00056d2008060d000b090000091e) // quotient_program + mstore(add(payload, 0x0ae0), 0x116ce0136d401d136be01c106c00056d2008060d000b0a00000921116c60136c) // quotient_program + mstore(add(payload, 0x0b00), 0x8020136ac01f106ae0056b0008060d000b0a00010924116ce0136d401d136be0) // quotient_program + mstore(add(payload, 0x0b20), 0x23136c0022106d00056d2008060d000b0b00000926116c60136c8020136ac01e) // quotient_program + mstore(add(payload, 0x0b40), 0x136ae025106b60056b0008060d000b0b00011c276bc0286be0296c00016ce01a) // quotient_program + mstore(add(payload, 0x0b60), 0x6d402a6d602b6d80106d00056d2008060d000b0c0000090008106d60116d600d) // quotient_program + mstore(add(payload, 0x0b80), 0x000b0c0001090008106bc0116bc00d000b0c0001090008106d80116d800d000b) // quotient_program + mstore(add(payload, 0x0ba0), 0x0c00011c006b20006b40006ba0006c20006c40006ca0006cc0056d20136da02c) // quotient_program + mstore(add(payload, 0x0bc0), 0x08060d000b0d0000056ac0116ac0116ac0056b2008060d000b0e0000056ae011) // quotient_program + mstore(add(payload, 0x0be0), 0x6ae0116ae0056b4008060d000b0e0001056b00116b00116b00056bc008060d00) // quotient_program + mstore(add(payload, 0x0c00), 0x0b0e00011b00021b0003056ba008106ec0056ac0116ac0116b200d2d06056ae0) // quotient_program + mstore(add(payload, 0x0c20), 0x116ae0116b400d2e06056b00116b00116bc00d2f060d000b0e0001191f000000) // quotient_program // Fixed-column commitment 0, stored as one // EIP-2537 padded uncompressed G1 slot. mstore(add(payload, 0x0c40), 0x0000000000000000000000000000000002586b66bd923976cc5f5c9ff774e3c2) // fixed_comms[0].x_hi diff --git a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2QuotientEvaluator.sol b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2QuotientEvaluator.sol index 6e74608d2..c2bc6699d 100644 --- a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2QuotientEvaluator.sol +++ b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2QuotientEvaluator.sol @@ -47,53 +47,53 @@ contract Halo2QuotientEvaluator { // Start of the copied verifier-key payload in memory. The VK payload also // carries the compact quotient VM constant/program tables used by the // included numerator block. - uint256 internal constant VK_MPTR = 0x2700; + uint256 internal constant VK_MPTR = 0x3680; // Fiat-Shamir challenge slots. Halo2Verifier sampled these in transcript // order before the external call. The evaluator only reads them. - uint256 internal constant CHALLENGE_MPTR = 0x6980; - uint256 internal constant THETA_MPTR = 0x6980; - uint256 internal constant BETA_MPTR = 0x69a0; - uint256 internal constant GAMMA_MPTR = 0x69c0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x69e0; - uint256 internal constant Y_MPTR = 0x6a00; - uint256 internal constant X_MPTR = 0x6a20; - uint256 internal constant X1_MPTR = 0x6a40; - uint256 internal constant X2_MPTR = 0x6a60; - uint256 internal constant X3_MPTR = 0x6a80; - uint256 internal constant X4_MPTR = 0x6aa0; + uint256 internal constant CHALLENGE_MPTR = 0x7900; + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; // Common polynomial values at x. Halo2Verifier computes these once after // sampling x and places them in the frame so the numerator block can share // the exact Rust verifier inputs. - uint256 internal constant X_N_MPTR = 0x6cc0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x6ce0; - uint256 internal constant L_LAST_MPTR = 0x6d00; - uint256 internal constant L_BLIND_MPTR = 0x6d20; - uint256 internal constant L_0_MPTR = 0x6d40; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x6d60; - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x6d80; + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; // Proof evaluation table. Values are already decoded as canonical Fr words // by Halo2Verifier. The generated numerator code indexes this table by the // same query order as the Rust verifier. - uint256 internal constant REVERSED_EVALS_MPTR = 0x8500; + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; // Scratch/output region for simple-selector linearization accumulators. // The numerator block writes one bucket per simple selector, then the // fallback copies those buckets into the compact return frame. - uint256 internal constant SELECTOR_ACC_MPTR = 0xa1c0; + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; // Callee-local scratch for trace hooks. Trace-enabled verifier builds call // this evaluator with CALL so quotient identity logs can be compared with // the native Rust trace. Production verifier builds keep using STATICCALL // and render this evaluator without trace hooks. - uint256 internal constant TRACE_U256_MPTR = 0x80; - uint256 internal constant QUOTIENT_OUTPUT_MPTR = 0x80; + uint256 internal constant TRACE_U256_MPTR = 0x1000; + uint256 internal constant QUOTIENT_OUTPUT_MPTR = 0x1000; // External-call frame metadata. The main verifier calls this contract with // exactly QUOTIENT_FRAME_LEN bytes starting at // QUOTIENT_FRAME_BASE, then checks the return length and QUOTIENT_MAGIC. - uint256 internal constant QUOTIENT_FRAME_BASE = 0x2700; + uint256 internal constant QUOTIENT_FRAME_BASE = 0x3680; uint256 internal constant QUOTIENT_FRAME_LEN = 0x6ac0; uint256 internal constant QUOTIENT_OUTPUT_LEN = 0x0180; uint256 internal constant QUOTIENT_MAGIC = 0x00000000000000000000000000000000000000000000000051554556414c0001; @@ -251,15 +251,15 @@ contract Halo2QuotientEvaluator { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x2ae0 + let q_const_mptr := 0x3a60 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x4120 + let q_program_mptr := 0x50a0 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0xa300, 0) + mstore(0xb280, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -274,12 +274,19 @@ contract Halo2QuotientEvaluator { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0xb2c0, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 49) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0xa340, shl(5, q_y_power_i)), q_y_power) + mstore(add(0xb2c0, shl(5, q_y_power_i)), q_y_power) } } @@ -288,102 +295,102 @@ contract Halo2QuotientEvaluator { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x8b40) - let f_4 := mload(0x8a40) - let a_0 := mload(0x8520) + let f_3 := mload(0x9ac0) + let f_4 := mload(0x99c0) + let a_0 := mload(0x94a0) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x8a60) - let a_1 := mload(0x8540) + let f_5 := mload(0x99e0) + let a_1 := mload(0x94c0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x8a80) - let a_2 := mload(0x8560) + let f_6 := mload(0x9a00) + let a_2 := mload(0x94e0) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x8aa0) - let a_3 := mload(0x8580) + let f_7 := mload(0x9a20) + let a_3 := mload(0x9500) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x8ac0) - let a_4 := mload(0x85a0) + let f_8 := mload(0x9a40) + let a_4 := mload(0x9520) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x8ae0) - let a_0_next_1 := mload(0x85c0) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x8b00) + let f_1 := mload(0x9a80) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x8b20) + let f_2 := mload(0x9aa0) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0xa960, var19) + mstore(0xb8e0, var19) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } { let var0 := 0x1 - let a_1 := mload(0x8540) - let a_2 := mload(0x8560) + let a_1 := mload(0x94c0) + let a_2 := mload(0x94e0) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0xa960, var6) + mstore(0xb8e0, var6) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } { let var0 := 0x1 - let a_0 := mload(0x8520) - let f_4 := mload(0x8a40) + let a_0 := mload(0x94a0) + let f_4 := mload(0x99c0) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x85c0) + let a_0_next_1 := mload(0x9540) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0xa960, var4) + mstore(0xb8e0, var4) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } { let var0 := 0x1 - let a_1 := mload(0x8540) - let f_5 := mload(0x8a60) + let a_1 := mload(0x94c0) + let f_5 := mload(0x99e0) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x85e0) + let a_1_next_1 := mload(0x9560) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0xa960, var4) + mstore(0xb8e0, var4) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0xa340, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } // VM registers: @@ -403,7 +410,7 @@ contract Halo2QuotientEvaluator { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x11cf) // q_sp starts at the first free stack word. - let q_sp := 0xa960 + let q_sp := 0xb8e0 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -677,82 +684,82 @@ contract Halo2QuotientEvaluator { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0xa960 + q_sp := 0xb8e0 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0xa960 - let q_perm_sigmas := 0xaba0 - let q_perm_z_cur := 0xade0 - let q_perm_z_next := 0xaea0 - let q_perm_z_last := 0xaf60 - let q_perm_delta_base_ptr := 0xb000 + let q_perm_vals := 0xb8e0 + let q_perm_sigmas := 0xbb20 + let q_perm_z_cur := 0xbd60 + let q_perm_z_next := 0xbe20 + let q_perm_z_last := 0xbee0 + let q_perm_delta_base_ptr := 0xbf80 let q_perm_num_cols := 18 let q_perm_num_sets := 6 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x8a20)) + mstore(add(q_perm_vals, 0x0), mload(0x99a0)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x8520, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x94a0, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x8500)) + mstore(add(q_perm_vals, 0xc0), mload(0x9480)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 9) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x100), q_perm_val_load_dst_off), mload(add(0x8620, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x100), q_perm_val_load_dst_off), mload(add(0x95a0, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0x220), mload(0x8a00)) + mstore(add(q_perm_vals, 0x220), mload(0x9980)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 18) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x8c40, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x9bc0, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 6) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x8e80, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x9e00, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 6) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x8ea0, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x9e20, q_perm_z_next_load_src_off))) } } { for { let q_perm_z_last_load_i := 0 } lt(q_perm_z_last_load_i, 5) { q_perm_z_last_load_i := add(q_perm_z_last_load_i, 1) } { let q_perm_z_last_load_dst_off := shl(5, q_perm_z_last_load_i) let q_perm_z_last_load_src_off := mul(q_perm_z_last_load_i, 0x60) - mstore(add(add(q_perm_z_last, 0x0), q_perm_z_last_load_dst_off), mload(add(0x8ec0, q_perm_z_last_load_src_off))) + mstore(add(add(q_perm_z_last, 0x0), q_perm_z_last_load_dst_off), mload(add(0x9e40, q_perm_z_last_load_src_off))) } } let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_perm_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0xa0)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_perm_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 6) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_perm_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 6) { q_perm_set := add(q_perm_set, 1) } { @@ -771,8 +778,8 @@ contract Halo2QuotientEvaluator { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_perm_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -793,13 +800,13 @@ contract Halo2QuotientEvaluator { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0xa960 + q_sp := 0xb8e0 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0xa960 - let q_lookup_prefix := 0xa9e0 - let q_lookup_suffix := 0xaa60 + let q_lookup_f := 0xb8e0 + let q_lookup_prefix := 0xb960 + let q_lookup_suffix := 0xb9e0 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -809,17 +816,17 @@ contract Halo2QuotientEvaluator { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x90e0), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa060), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } { - let f_10 := mload(0x8b60) + let f_10 := mload(0x9ae0) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) let var1 := mulmod(var0, q_lookup_theta, r) for { let q_lookup_shared_i := 0 } lt(q_lookup_shared_i, 4) { q_lookup_shared_i := add(q_lookup_shared_i, 1) } { let q_lookup_shared_off := shl(5, q_lookup_shared_i) - let q_lookup_shared_tail := mload(add(0x8540, q_lookup_shared_off)) + let q_lookup_shared_tail := mload(add(0x94c0, q_lookup_shared_off)) let q_lookup_shared_compressed := addmod(var1, q_lookup_shared_tail, r) mstore(add(q_lookup_f, q_lookup_shared_off), addmod(q_lookup_shared_compressed, q_lookup_beta, r)) } @@ -841,130 +848,130 @@ contract Halo2QuotientEvaluator { for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 4) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) } - let q_lookup_eval := addmod(mulmod(mload(0x90c0), q_lookup_product, r), sub(r, q_lookup_sum), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0xa040), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x90c0) - let f_17 := mload(0x8be0) - let f_11 := mload(0x8b80) + let q_lookup_sum_h := mload(0xa040) + let f_17 := mload(0x9b60) + let f_11 := mload(0x9b00) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x8ba0) + let f_12 := mload(0x9b20) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_17, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x9100), sub(r, addmod(mload(0x90e0), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0xa080), sub(r, addmod(mload(0xa060), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x90a0), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa020), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } } { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x9160), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa0e0), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } { - let a_14 := mload(0x8a00) + let a_14 := mload(0x9980) let var0 := addmod(mulmod(0, q_lookup_theta, r), a_14, r) - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_0, r) - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_1, r) - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var3 := addmod(mulmod(var2, q_lookup_theta, r), a_2, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_3, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_4, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var6 := addmod(mulmod(var5, q_lookup_theta, r), a_5, r) - let a_6 := mload(0x8640) + let a_6 := mload(0x95c0) let var7 := addmod(mulmod(var6, q_lookup_theta, r), a_6, r) - let a_7 := mload(0x8660) + let a_7 := mload(0x95e0) let var8 := addmod(mulmod(var7, q_lookup_theta, r), a_7, r) - let a_8 := mload(0x8680) + let a_8 := mload(0x9600) let var9 := addmod(mulmod(var8, q_lookup_theta, r), a_8, r) - let a_9 := mload(0x86a0) + let a_9 := mload(0x9620) let var10 := addmod(mulmod(var9, q_lookup_theta, r), a_9, r) - let a_10 := mload(0x86c0) + let a_10 := mload(0x9640) let var11 := addmod(mulmod(var10, q_lookup_theta, r), a_10, r) - let a_11 := mload(0x86e0) + let a_11 := mload(0x9660) let var12 := addmod(mulmod(var11, q_lookup_theta, r), a_11, r) - let a_12 := mload(0x8700) + let a_12 := mload(0x9680) let var13 := addmod(mulmod(var12, q_lookup_theta, r), a_12, r) - let a_13 := mload(0x8720) + let a_13 := mload(0x96a0) let var14 := addmod(mulmod(var13, q_lookup_theta, r), a_13, r) - let f_13 := mload(0x8bc0) + let f_13 := mload(0x9b40) let var15 := addmod(mulmod(var14, q_lookup_theta, r), f_13, r) - let q_lookup_eval := addmod(mulmod(mload(0x9140), addmod(var15, q_lookup_beta, r), r), sub(r, 1), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0xa0c0), addmod(var15, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x9140) + let q_lookup_sum_h := mload(0xa0c0) let var0 := 0x1 - let f_26 := mload(0x8c20) + let f_26 := mload(0x9ba0) let var1 := addmod(0, sub(r, f_26), r) let var2 := addmod(var0, var1, r) - let a_14 := mload(0x8a00) + let a_14 := mload(0x9980) let var3 := mulmod(var2, a_14, r) let var4 := addmod(mulmod(0, q_lookup_theta, r), var3, r) - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var5 := mulmod(var2, a_0, r) let var6 := addmod(mulmod(var4, q_lookup_theta, r), var5, r) - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var7 := mulmod(var2, a_1, r) let var8 := addmod(mulmod(var6, q_lookup_theta, r), var7, r) - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var9 := mulmod(var2, a_2, r) let var10 := addmod(mulmod(var8, q_lookup_theta, r), var9, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var11 := mulmod(var2, a_3, r) let var12 := addmod(mulmod(var10, q_lookup_theta, r), var11, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var13 := mulmod(var2, a_4, r) let var14 := addmod(mulmod(var12, q_lookup_theta, r), var13, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var15 := mulmod(var2, a_5, r) let var16 := addmod(mulmod(var14, q_lookup_theta, r), var15, r) - let a_6 := mload(0x8640) + let a_6 := mload(0x95c0) let var17 := mulmod(var2, a_6, r) let var18 := addmod(mulmod(var16, q_lookup_theta, r), var17, r) - let a_7 := mload(0x8660) + let a_7 := mload(0x95e0) let var19 := mulmod(var2, a_7, r) let var20 := addmod(mulmod(var18, q_lookup_theta, r), var19, r) - let a_8 := mload(0x8680) + let a_8 := mload(0x9600) let var21 := mulmod(var2, a_8, r) let var22 := addmod(mulmod(var20, q_lookup_theta, r), var21, r) - let a_9 := mload(0x86a0) + let a_9 := mload(0x9620) let var23 := mulmod(var2, a_9, r) let var24 := addmod(mulmod(var22, q_lookup_theta, r), var23, r) - let a_10 := mload(0x86c0) + let a_10 := mload(0x9640) let var25 := mulmod(var2, a_10, r) let var26 := addmod(mulmod(var24, q_lookup_theta, r), var25, r) - let a_11 := mload(0x86e0) + let a_11 := mload(0x9660) let var27 := mulmod(var2, a_11, r) let var28 := addmod(mulmod(var26, q_lookup_theta, r), var27, r) - let a_12 := mload(0x8700) + let a_12 := mload(0x9680) let var29 := mulmod(var2, a_12, r) let var30 := addmod(mulmod(var28, q_lookup_theta, r), var29, r) - let a_13 := mload(0x8720) + let a_13 := mload(0x96a0) let var31 := mulmod(var2, a_13, r) let var32 := addmod(mulmod(var30, q_lookup_theta, r), var31, r) - let f_13 := mload(0x8bc0) + let f_13 := mload(0x9b40) let var33 := mulmod(var2, f_13, r) let var34 := addmod(mulmod(var32, q_lookup_theta, r), var33, r) let q_lookup_s_sum_h := mulmod(var0, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x9180), sub(r, addmod(mload(0x9160), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0xa100), sub(r, addmod(mload(0xa0e0), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var34, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x9120), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa0a0), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } } } @@ -985,104 +992,104 @@ contract Halo2QuotientEvaluator { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0xa960 + q_sp := 0xb8e0 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 - let f_0 := mload(0x8ae0) - let a_0_next_1 := mload(0x85c0) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) let var1 := addmod(0, sub(r, a_0_next_1), r) let var2 := addmod(f_0, var1, r) let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0xa960, var18) + mstore(0xb8e0, var18) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0xa340, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } } case 1 { { let var0 := 0x1 - let f_1 := mload(0x8b00) - let a_1_next_1 := mload(0x85e0) + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) let var1 := addmod(0, sub(r, a_1_next_1), r) let var2 := addmod(f_1, var1, r) let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0xa960, var18) + mstore(0xb8e0, var18) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0xa340, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } } case 2 { { let var0 := 0x1 - let a_0 := mload(0x8520) - let a_0_next_1 := mload(0x85c0) + let a_0 := mload(0x94a0) + let a_0_next_1 := mload(0x9540) let var1 := mulmod(a_0, a_0_next_1, r) let var2 := 0x100000000000000 - let a_1_next_1 := mload(0x85e0) + let a_1_next_1 := mload(0x9560) let var3 := mulmod(a_0, a_1_next_1, r) let var4 := mulmod(var2, var3, r) let var5 := addmod(var1, var4, r) let var6 := 0x10000000000000000000000000000 - let a_2_next_1 := mload(0x8600) + let a_2_next_1 := mload(0x9580) let var7 := mulmod(a_0, a_2_next_1, r) let var8 := mulmod(var6, var7, r) let var9 := addmod(var5, var8, r) - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var10 := mulmod(a_1, a_0_next_1, r) let var11 := mulmod(var2, var10, r) let var12 := addmod(var9, var11, r) @@ -1090,15 +1097,15 @@ contract Halo2QuotientEvaluator { let var14 := mulmod(var6, var13, r) let var15 := addmod(var12, var14, r) let var16 := 0x3212e00cde6d2002b119d800000347fcb8 - let a_6_next_1 := mload(0x87a0) + let a_6_next_1 := mload(0x9720) let var17 := mulmod(a_1, a_6_next_1, r) let var18 := mulmod(var16, var17, r) let var19 := addmod(var15, var18, r) - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var20 := mulmod(a_2, a_0_next_1, r) let var21 := mulmod(var6, var20, r) let var22 := addmod(var19, var21, r) - let a_5_next_1 := mload(0x8780) + let a_5_next_1 := mload(0x9700) let var23 := mulmod(a_2, a_5_next_1, r) let var24 := mulmod(var16, var23, r) let var25 := addmod(var22, var24, r) @@ -1106,8 +1113,8 @@ contract Halo2QuotientEvaluator { let var27 := mulmod(a_2, a_6_next_1, r) let var28 := mulmod(var26, var27, r) let var29 := addmod(var25, var28, r) - let a_3 := mload(0x8580) - let a_4_next_1 := mload(0x8760) + let a_3 := mload(0x9500) + let a_4_next_1 := mload(0x96e0) let var30 := mulmod(a_3, a_4_next_1, r) let var31 := mulmod(var16, var30, r) let var32 := addmod(var29, var31, r) @@ -1118,8 +1125,8 @@ contract Halo2QuotientEvaluator { let var37 := mulmod(a_3, a_6_next_1, r) let var38 := mulmod(var36, var37, r) let var39 := addmod(var35, var38, r) - let a_4 := mload(0x85a0) - let a_3_next_1 := mload(0x8740) + let a_4 := mload(0x9520) + let a_3_next_1 := mload(0x96c0) let var40 := mulmod(a_4, a_3_next_1, r) let var41 := mulmod(var16, var40, r) let var42 := addmod(var39, var41, r) @@ -1133,7 +1140,7 @@ contract Halo2QuotientEvaluator { let var50 := mulmod(a_4, a_6_next_1, r) let var51 := mulmod(var49, var50, r) let var52 := addmod(var48, var51, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var53 := mulmod(a_5, a_2_next_1, r) let var54 := mulmod(var16, var53, r) let var55 := addmod(var52, var54, r) @@ -1150,7 +1157,7 @@ contract Halo2QuotientEvaluator { let var66 := mulmod(a_5, a_6_next_1, r) let var67 := mulmod(var65, var66, r) let var68 := addmod(var64, var67, r) - let a_6 := mload(0x8640) + let a_6 := mload(0x95c0) let var69 := mulmod(a_6, a_1_next_1, r) let var70 := mulmod(var16, var69, r) let var71 := addmod(var68, var70, r) @@ -1180,23 +1187,23 @@ contract Halo2QuotientEvaluator { let var95 := mulmod(var6, a_2_next_1, r) let var96 := addmod(var94, var95, r) let var97 := addmod(var92, var96, r) - let a_7 := mload(0x8660) - let a_8 := mload(0x8680) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) let var98 := mulmod(var2, a_8, r) let var99 := addmod(a_7, var98, r) - let a_9 := mload(0x86a0) + let a_9 := mload(0x9620) let var100 := mulmod(var6, a_9, r) let var101 := addmod(var99, var100, r) let var102 := addmod(0, sub(r, var101), r) let var103 := addmod(var97, var102, r) - let a_7_next_1 := mload(0x87c0) + let a_7_next_1 := mload(0x9740) let var104 := 0x241eabfffeb153ffffb9feffffffffaaab let var105 := mulmod(a_7_next_1, var104, r) let var106 := addmod(0, sub(r, var105), r) let var107 := addmod(var103, var106, r) let var108 := addmod(0, sub(r, var16), r) let var109 := addmod(var107, var108, r) - let a_8_next_1 := mload(0x87e0) + let a_8_next_1 := mload(0x9760) let var110 := 0x73eda753299d7d483339d80809a1d80553b9202d7ffe85d4800008bb20000001 let var111 := addmod(a_8_next_1, var110, r) let var112 := 0x4000000000000000000000000000000000 @@ -1204,42 +1211,42 @@ contract Halo2QuotientEvaluator { let var114 := addmod(0, sub(r, var113), r) let var115 := addmod(var109, var114, r) let var116 := mulmod(var0, var115, r) - mstore(0xa960, var116) + mstore(0xb8e0, var116) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x80) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } } case 3 { { let var0 := 0x1 - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var1 := 0x10000000000000000000000000000 let var2 := addmod(a_0, var1, r) let var3 := 0x100000000000000 - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var4 := addmod(a_1, var1, r) let var5 := mulmod(var3, var4, r) let var6 := addmod(var2, var5, r) - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var7 := addmod(a_2, var1, r) let var8 := mulmod(var1, var7, r) let var9 := addmod(var6, var8, r) - let a_7 := mload(0x8660) - let a_8 := mload(0x8680) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) let var10 := mulmod(var3, a_8, r) let var11 := addmod(a_7, var10, r) - let a_9 := mload(0x86a0) + let a_9 := mload(0x9620) let var12 := mulmod(var1, a_9, r) let var13 := addmod(var11, var12, r) let var14 := addmod(0, sub(r, var13), r) let var15 := addmod(var9, var14, r) let var16 := addmod(0, sub(r, var1), r) let var17 := addmod(var15, var16, r) - let a_7_next_1 := mload(0x87c0) + let a_7_next_1 := mload(0x9740) let var18 := 0x241eabfffeb153ffffb9feffffffffaaab let var19 := mulmod(a_7_next_1, var18, r) let var20 := addmod(0, sub(r, var19), r) @@ -1247,7 +1254,7 @@ contract Halo2QuotientEvaluator { let var22 := 0xd9d44a30b019261257667fde3844a8cd6 let var23 := addmod(0, sub(r, var22), r) let var24 := addmod(var21, var23, r) - let a_8_next_1 := mload(0x87e0) + let a_8_next_1 := mload(0x9760) let var25 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5b6e855000003ab00002 let var26 := addmod(a_8_next_1, var25, r) let var27 := 0x4000000000000000000000000000000000 @@ -1255,13 +1262,13 @@ contract Halo2QuotientEvaluator { let var29 := addmod(0, sub(r, var28), r) let var30 := addmod(var24, var29, r) let var31 := mulmod(var0, var30, r) - mstore(0xa960, var31) + mstore(0xb8e0, var31) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xa0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } } default { revert(0, 0) } @@ -1285,14 +1292,14 @@ contract Halo2QuotientEvaluator { // The global fully-evaluated accumulator is still // multiplied by y so later main identities land at the // same y powers as Rust's reverse fold. - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) let q_sel_acc := mload(q_target_ptr) if q_sel_gap { // Selector buckets are sparse in the global // identity stream. Precomputed y^gap advances only // this selector's local accumulator. - q_sel_acc := mulmod(q_sel_acc, mload(add(0xa340, shl(5, q_sel_gap))), r) + q_sel_acc := mulmod(q_sel_acc, mload(add(0xb2c0, shl(5, q_sel_gap))), r) } mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) } @@ -1307,6 +1314,12 @@ contract Halo2QuotientEvaluator { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0xb8e0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -1318,52 +1331,52 @@ contract Halo2QuotientEvaluator { { let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) { - let f_0 := mload(0x8ae0) - let a_0_next_1 := mload(0x85c0) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 let var1 := mulmod(a_0_next_1, var0, r) let var2 := addmod(f_0, var1, r) let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var4 := mulmod(var3, a_0, r) let var5 := addmod(var2, var4, r) let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var7 := mulmod(var6, a_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var10 := q_pow5(a_2) let var11 := mulmod(var9, var10, r) let var12 := addmod(var8, var11, r) let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var14 := q_pow5(a_3) let var15 := mulmod(var13, var14, r) let var16 := addmod(var12, var15, r) let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var18 := q_pow5(a_4) let var19 := mulmod(var17, var18, r) let var20 := addmod(var16, var19, r) let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var22 := q_pow5(a_5) let var23 := mulmod(var21, var22, r) let var24 := addmod(var20, var23, r) let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 - let a_6 := mload(0x8640) + let a_6 := mload(0x95c0) let var26 := q_pow5(a_6) let var27 := mulmod(var25, var26, r) let var28 := addmod(var24, var27, r) let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_7 := mload(0x8660) + let a_7 := mload(0x95e0) let var30 := q_pow5(a_7) let var31 := mulmod(var29, var30, r) let var32 := addmod(var28, var31, r) let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) - let f_1 := mload(0x8b00) - let a_1_next_1 := mload(0x85e0) + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) let var34 := mulmod(a_1_next_1, var0, r) let var35 := addmod(f_1, var34, r) let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d @@ -1391,7 +1404,7 @@ contract Halo2QuotientEvaluator { let var58 := mulmod(var57, var30, r) let var59 := addmod(var56, var58, r) let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) - let f_2 := mload(0x8b20) + let f_2 := mload(0x9aa0) let var61 := mulmod(a_3, var0, r) let var62 := addmod(f_2, var61, r) let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 @@ -1404,7 +1417,7 @@ contract Halo2QuotientEvaluator { let var70 := mulmod(var69, var10, r) let var71 := addmod(var68, var70, r) let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) - let f_3 := mload(0x8b40) + let f_3 := mload(0x9ac0) let var73 := mulmod(a_4, var0, r) let var74 := addmod(f_3, var73, r) let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 @@ -1419,7 +1432,7 @@ contract Halo2QuotientEvaluator { let var84 := mulmod(var69, var14, r) let var85 := addmod(var83, var84, r) let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) - let f_4 := mload(0x8a40) + let f_4 := mload(0x99c0) let var87 := mulmod(a_5, var0, r) let var88 := addmod(f_4, var87, r) let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b @@ -1436,7 +1449,7 @@ contract Halo2QuotientEvaluator { let var100 := mulmod(var69, var18, r) let var101 := addmod(var99, var100, r) let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) - let f_5 := mload(0x8a60) + let f_5 := mload(0x99e0) let var103 := mulmod(a_6, var0, r) let var104 := addmod(f_5, var103, r) let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 @@ -1455,7 +1468,7 @@ contract Halo2QuotientEvaluator { let var118 := mulmod(var69, var22, r) let var119 := addmod(var117, var118, r) let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) - let f_6 := mload(0x8a80) + let f_6 := mload(0x9a00) let var121 := mulmod(a_7, var0, r) let var122 := addmod(f_6, var121, r) let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a @@ -1476,8 +1489,8 @@ contract Halo2QuotientEvaluator { let var138 := mulmod(var69, var26, r) let var139 := addmod(var137, var138, r) let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) - let f_7 := mload(0x8aa0) - let a_2_next_1 := mload(0x8600) + let f_7 := mload(0x9a20) + let a_2_next_1 := mload(0x9580) let var141 := mulmod(a_2_next_1, var0, r) let var142 := addmod(f_7, var141, r) let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 @@ -1500,12 +1513,12 @@ contract Halo2QuotientEvaluator { let var160 := mulmod(var69, var30, r) let var161 := addmod(var159, var160, r) let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) - let f_19 := mload(0x8c00) + let f_19 := mload(0x9b80) let q_trash_one_minus_selector := addmod(1, sub(r, f_19), r) - let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x91a0), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0xa120), r) let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_trash_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_trash_eval, r)) } } // Finish selector buckets by applying the codegen-known tail @@ -1517,49 +1530,49 @@ contract Halo2QuotientEvaluator { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0600)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0600)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x05e0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x05e0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0580)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0580)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x04c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x04c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0460)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0460)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0400)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0400)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x03a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x03a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0340)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0340)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x02e0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x02e0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0280)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0280)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0xa300)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0xb280)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } diff --git a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2Verifier.sol index 8dd0fcf29..6f66ad7c7 100644 --- a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2Verifier.sol @@ -43,15 +43,15 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 17024; uint256 internal constant EXPECTED_VK_LENGTH = 17025; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x0a489a96da94a3dde90d99d69bd4464d5d5731a89f202154559e68a17fac1c21; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x67bac137fa7e479c25b63324812752e4b6e13d9841d5bf83c322170bf91c0f88; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); /// @notice Quotient evaluator contract authorized for split quotient reconstruction. /// @dev The evaluator returns the linearization expected scalar and selector buckets; its runtime may be pinned by generated constants. address public immutable AUTHORIZED_QUOTIENT; // Expected split evaluator runtime metadata. It is checked at deployment // and again immediately before each external quotient reconstruction. - uint256 internal constant EXPECTED_QUOTIENT_LENGTH = 9531; - uint256 internal constant EXPECTED_QUOTIENT_CODEHASH_WORD = 0x02f1ea00260a78e72dc51376f5e3053ae6c83deae7219acb89c6a0bca5dc8f1c; + uint256 internal constant EXPECTED_QUOTIENT_LENGTH = 9552; + uint256 internal constant EXPECTED_QUOTIENT_CODEHASH_WORD = 0x6a7ec82a9b0683783c459f4b43e924f8ceba256731bf2b9782ab5747e801ea2c; bytes32 internal constant EXPECTED_QUOTIENT_CODEHASH = bytes32(EXPECTED_QUOTIENT_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -63,8 +63,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0x1ee4; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -72,84 +72,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x2700; - uint256 internal constant VK_DIGEST_MPTR = 0x2700; - uint256 internal constant NUM_INSTANCES_MPTR = 0x2720; - uint256 internal constant K_MPTR = 0x2740; - uint256 internal constant N_INV_MPTR = 0x2760; - uint256 internal constant OMEGA_MPTR = 0x2780; - uint256 internal constant OMEGA_INV_MPTR = 0x27a0; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x27c0; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x27e0; - uint256 internal constant ACC_OFFSET_MPTR = 0x2800; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2820; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x2840; - uint256 internal constant G1_BASE_MPTR = 0x2860; - uint256 internal constant G2_BASE_MPTR = 0x28e0; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x29e0; - - uint256 internal constant CHALLENGE_MPTR = 0x6980; + uint256 internal constant VK_MPTR = 0x3680; + uint256 internal constant VK_DIGEST_MPTR = 0x3680; + uint256 internal constant NUM_INSTANCES_MPTR = 0x36a0; + uint256 internal constant K_MPTR = 0x36c0; + uint256 internal constant N_INV_MPTR = 0x36e0; + uint256 internal constant OMEGA_MPTR = 0x3700; + uint256 internal constant OMEGA_INV_MPTR = 0x3720; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x3740; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x3760; + uint256 internal constant ACC_OFFSET_MPTR = 0x3780; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x37a0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x37c0; + uint256 internal constant G1_BASE_MPTR = 0x37e0; + uint256 internal constant G2_BASE_MPTR = 0x3860; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x3960; + + uint256 internal constant CHALLENGE_MPTR = 0x7900; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x6980; - uint256 internal constant BETA_MPTR = 0x69a0; - uint256 internal constant GAMMA_MPTR = 0x69c0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x69e0; - uint256 internal constant Y_MPTR = 0x6a00; - uint256 internal constant X_MPTR = 0x6a20; - uint256 internal constant X1_MPTR = 0x6a40; - uint256 internal constant X2_MPTR = 0x6a60; - uint256 internal constant X3_MPTR = 0x6a80; - uint256 internal constant X4_MPTR = 0x6aa0; + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x6ac0; - uint256 internal constant PI_MPTR = 0x6b40; + uint256 internal constant F_COM_MPTR = 0x7a40; + uint256 internal constant PI_MPTR = 0x7ac0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x6bc0; - uint256 internal constant ACC_RHS_MPTR = 0x6c40; + uint256 internal constant ACC_LHS_MPTR = 0x7b40; + uint256 internal constant ACC_RHS_MPTR = 0x7bc0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x6cc0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x6ce0; - uint256 internal constant L_LAST_MPTR = 0x6d00; - uint256 internal constant L_BLIND_MPTR = 0x6d20; - uint256 internal constant L_0_MPTR = 0x6d40; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x6d60; + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x6d80; - uint256 internal constant QUOTIENT_MPTR = 0x6da0; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x6e40; - uint256 internal constant V_MPTR = 0x6e60; - uint256 internal constant FINAL_COM_MPTR = 0x6e80; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x6f00; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x6f80; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; + uint256 internal constant QUOTIENT_MPTR = 0x7d20; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x7dc0; + uint256 internal constant V_MPTR = 0x7de0; + uint256 internal constant FINAL_COM_MPTR = 0x7e00; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x7e80; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x7f00; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x7000; - uint256 internal constant X1_POWERS_MPTR = 0x7380; + uint256 internal constant ROT_POINTS_MPTR = 0x7f80; + uint256 internal constant X1_POWERS_MPTR = 0x8300; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x7ba0; - uint256 internal constant Q_EVAL_SET_MPTR = 0x7ba0; + uint256 internal constant Q_COM_MPTR = 0x8b20; + uint256 internal constant Q_EVAL_SET_MPTR = 0x8b20; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x82a0; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x9220; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x83a0; + uint256 internal constant G1_IDENTITY_MPTR = 0x9320; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -157,11 +157,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x8500; - uint256 internal constant SELECTOR_ACC_MPTR = 0xa1c0; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0xa1c0; - uint256 internal constant TRACE_U256_MPTR = 0xd3c0; + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0xb140; + uint256 internal constant TRACE_U256_MPTR = 0xe340; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -178,13 +178,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x91c0; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x9940; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x9a40; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x9d40; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x9e40; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x9f40; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x9fc0; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0xa140; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0xa8c0; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0xa9c0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0xacc0; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0xadc0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0xaec0; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0xaf40; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -206,7 +206,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -230,11 +230,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0xa1c0 + let msm_scratch := 0xb140 for { let off := 0 } lt(off, 0x30c0) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -344,9 +377,15 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x2600 + let p := 0x3580 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -481,6 +520,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -527,6 +573,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -551,21 +603,30 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x0320 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -597,7 +658,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -823,11 +890,16 @@ contract Halo2Verifier { out := and(out, lhs_ok) // Shared scratch for one-pair LHS validation and the later // variable-length RHS MSM. - let acc_scratch := 0xa1c0 + let acc_scratch := 0xb140 { // Carried-scalar layout: the circuit exposes the scalar // that multiplies the carried LHS point. let lhs_scalar := calldataload(lhs_scalar_ptr) + // Canonicality is enforced here rather than relying on the + // later instance-absorption loop: G1MSM reduces scalars + // mod r implicitly, so s and s+r would be indistinguishable + // inside this helper. + out := and(out, lt(lhs_scalar, r)) // Identity status is useful for decoding checks above, but // validation still goes through G1MSM for all points. pop(lhs_is_id) @@ -862,6 +934,7 @@ contract Halo2Verifier { { // Explicit carried RHS scalar. let rhs_scalar := calldataload(rhs_scalar_ptr) + out := and(out, lt(rhs_scalar, r)) pop(rhs_is_id) // Keep the carried RHS point in the MSM input even when // it is encoded as identity or has scalar 0/1, so EIP-2537 @@ -1275,6 +1348,16 @@ contract Halo2Verifier { // ---- x3 ---- // x3 is the PCS evaluation point for f_com. buf_len := squeeze_to(buf_len, X3_MPTR) + // truncated-challenges mirrors midnight-proofs + // proofs/src/poly/kzg/mod.rs: + // - x3 is the f_com evaluation point and is truncated + // immediately after squeeze. + // - x1 and x4 remain full squeezed Fr words, but later PCS + // batching stores truncate(x1^i) and truncate(x4^i) while + // keeping the internal power accumulators full precision. + // This direct x3 mask is therefore one part of the PCS truncation + // rule, not the only truncated value used by the verifier. + mstore(X3_MPTR, and(mload(X3_MPTR), 0xffffffffffffffffffffffffffffffff)) // ---- q_evals (one Fq per point set) ---- // q_evals are not spilled into REVERSED_EVALS_MPTR because the PCS @@ -1424,7 +1507,7 @@ contract Halo2Verifier { eq(extcodesize(quotientEvaluator), EXPECTED_QUOTIENT_LENGTH), eq(extcodehash(quotientEvaluator), EXPECTED_QUOTIENT_CODEHASH_WORD) )) { revert(0, 0) } - if iszero(staticcall(gas(), quotientEvaluator, 0x2700, 0x6ac0, q_out, 0x0180)) { revert(0, 0) } + if iszero(staticcall(gas(), quotientEvaluator, 0x3680, 0x6ac0, q_out, 0x0180)) { revert(0, 0) } if iszero(eq(returndatasize(), 0x0180)) { revert(0, 0) } if iszero(eq(mload(q_out), 0x00000000000000000000000000000000000000000000000051554556414c0001)) { revert(0, 0) } // Word 1 is the negated y-batched identity numerator, stored @@ -1529,7 +1612,7 @@ contract Halo2Verifier { for { let i := 0 } lt(i, 0x2a) { i := add(i, 1) } { p := add(p, 0x20) acc := mulmod(acc, x1, r) - mstore(p, acc) + mstore(p, and(acc, 0xffffffffffffffffffffffffffffffff)) } } // Generated PCS sub-block 3. These lines are @@ -1538,52 +1621,52 @@ contract Halo2Verifier { { // q_eval_set[0]: 43 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0xa300, 0x8a00) - mstore(0xa320, 0x8500) - mstore(0xa340, 0x90a0) - mstore(0xa360, 0x90c0) - mstore(0xa380, 0x9120) - mstore(0xa3a0, 0x9140) - mstore(0xa3c0, 0x91a0) - mstore(0xa3e0, 0x8a20) - mstore(0xa400, 0x8a40) - mstore(0xa420, 0x8a60) - mstore(0xa440, 0x8a80) - mstore(0xa460, 0x8aa0) - mstore(0xa480, 0x8ac0) - mstore(0xa4a0, 0x8ae0) - mstore(0xa4c0, 0x8b00) - mstore(0xa4e0, 0x8b20) - mstore(0xa500, 0x8b40) - mstore(0xa520, 0x8b60) - mstore(0xa540, 0x8b80) - mstore(0xa560, 0x8ba0) - mstore(0xa580, 0x8bc0) - mstore(0xa5a0, 0x8be0) - mstore(0xa5c0, 0x8c00) - mstore(0xa5e0, 0x8c20) - mstore(0xa600, 0x8c40) - mstore(0xa620, 0x8c60) - mstore(0xa640, 0x8c80) - mstore(0xa660, 0x8ca0) - mstore(0xa680, 0x8cc0) - mstore(0xa6a0, 0x8ce0) - mstore(0xa6c0, 0x8d00) - mstore(0xa6e0, 0x8d20) - mstore(0xa700, 0x8d40) - mstore(0xa720, 0x8d60) - mstore(0xa740, 0x8d80) - mstore(0xa760, 0x8da0) - mstore(0xa780, 0x8dc0) - mstore(0xa7a0, 0x8de0) - mstore(0xa7c0, 0x8e00) - mstore(0xa7e0, 0x8e20) - mstore(0xa800, 0x8e40) - mstore(0xa820, 0x8e60) - mstore(0xa840, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x8a00) + mstore(0xb280, 0x9980) + mstore(0xb2a0, 0x9480) + mstore(0xb2c0, 0xa020) + mstore(0xb2e0, 0xa040) + mstore(0xb300, 0xa0a0) + mstore(0xb320, 0xa0c0) + mstore(0xb340, 0xa120) + mstore(0xb360, 0x99a0) + mstore(0xb380, 0x99c0) + mstore(0xb3a0, 0x99e0) + mstore(0xb3c0, 0x9a00) + mstore(0xb3e0, 0x9a20) + mstore(0xb400, 0x9a40) + mstore(0xb420, 0x9a60) + mstore(0xb440, 0x9a80) + mstore(0xb460, 0x9aa0) + mstore(0xb480, 0x9ac0) + mstore(0xb4a0, 0x9ae0) + mstore(0xb4c0, 0x9b00) + mstore(0xb4e0, 0x9b20) + mstore(0xb500, 0x9b40) + mstore(0xb520, 0x9b60) + mstore(0xb540, 0x9b80) + mstore(0xb560, 0x9ba0) + mstore(0xb580, 0x9bc0) + mstore(0xb5a0, 0x9be0) + mstore(0xb5c0, 0x9c00) + mstore(0xb5e0, 0x9c20) + mstore(0xb600, 0x9c40) + mstore(0xb620, 0x9c60) + mstore(0xb640, 0x9c80) + mstore(0xb660, 0x9ca0) + mstore(0xb680, 0x9cc0) + mstore(0xb6a0, 0x9ce0) + mstore(0xb6c0, 0x9d00) + mstore(0xb6e0, 0x9d20) + mstore(0xb700, 0x9d40) + mstore(0xb720, 0x9d60) + mstore(0xb740, 0x9d80) + mstore(0xb760, 0x9da0) + mstore(0xb780, 0x9dc0) + mstore(0xb7a0, 0x9de0) + mstore(0xb7c0, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x9980) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0xa300, 0x20) + let eval_p := add(0xb280, 0x20) for { let i := 1 } lt(i, 0x2b) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1597,12 +1680,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[1]: 3 commitment(s) - let q_eval_set_0 := mload(0x86e0) - let q_eval_set_1 := mload(0x89a0) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x8700), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x89c0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x8720), mload(add(X1_POWERS_MPTR, 0x40)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x89e0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + let q_eval_set_0 := mload(0x9660) + let q_eval_set_1 := mload(0x9920) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x9680), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9940), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x96a0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9960), mload(add(X1_POWERS_MPTR, 0x40)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) } @@ -1611,12 +1694,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[2]: 3 commitment(s) - let q_eval_set_0 := mload(0x9060) - let q_eval_set_1 := mload(0x9080) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x90e0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9100), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x9160), mload(add(X1_POWERS_MPTR, 0x40)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9180), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + let q_eval_set_0 := mload(0x9fe0) + let q_eval_set_1 := mload(0xa000) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa060), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa080), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa0e0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa100), mload(add(X1_POWERS_MPTR, 0x40)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) } @@ -1626,44 +1709,44 @@ contract Halo2Verifier { { // q_eval_set[3]: 11 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0xa300, 0x8520) - mstore(0xa320, 0x85c0) - mstore(0xa340, 0x8840) - mstore(0xa360, 0x8540) - mstore(0xa380, 0x85e0) - mstore(0xa3a0, 0x8860) - mstore(0xa3c0, 0x8560) - mstore(0xa3e0, 0x8600) - mstore(0xa400, 0x8880) - mstore(0xa420, 0x8580) - mstore(0xa440, 0x8740) - mstore(0xa460, 0x88a0) - mstore(0xa480, 0x85a0) - mstore(0xa4a0, 0x8760) - mstore(0xa4c0, 0x88c0) - mstore(0xa4e0, 0x8620) - mstore(0xa500, 0x8780) - mstore(0xa520, 0x88e0) - mstore(0xa540, 0x8640) - mstore(0xa560, 0x87a0) - mstore(0xa580, 0x8900) - mstore(0xa5a0, 0x8660) - mstore(0xa5c0, 0x87c0) - mstore(0xa5e0, 0x8920) - mstore(0xa600, 0x8680) - mstore(0xa620, 0x87e0) - mstore(0xa640, 0x8940) - mstore(0xa660, 0x86a0) - mstore(0xa680, 0x8800) - mstore(0xa6a0, 0x8960) - mstore(0xa6c0, 0x86c0) - mstore(0xa6e0, 0x8820) - mstore(0xa700, 0x8980) - let q_eval_set_0 := mload(0x8520) - let q_eval_set_1 := mload(0x85c0) - let q_eval_set_2 := mload(0x8840) + mstore(0xb280, 0x94a0) + mstore(0xb2a0, 0x9540) + mstore(0xb2c0, 0x97c0) + mstore(0xb2e0, 0x94c0) + mstore(0xb300, 0x9560) + mstore(0xb320, 0x97e0) + mstore(0xb340, 0x94e0) + mstore(0xb360, 0x9580) + mstore(0xb380, 0x9800) + mstore(0xb3a0, 0x9500) + mstore(0xb3c0, 0x96c0) + mstore(0xb3e0, 0x9820) + mstore(0xb400, 0x9520) + mstore(0xb420, 0x96e0) + mstore(0xb440, 0x9840) + mstore(0xb460, 0x95a0) + mstore(0xb480, 0x9700) + mstore(0xb4a0, 0x9860) + mstore(0xb4c0, 0x95c0) + mstore(0xb4e0, 0x9720) + mstore(0xb500, 0x9880) + mstore(0xb520, 0x95e0) + mstore(0xb540, 0x9740) + mstore(0xb560, 0x98a0) + mstore(0xb580, 0x9600) + mstore(0xb5a0, 0x9760) + mstore(0xb5c0, 0x98c0) + mstore(0xb5e0, 0x9620) + mstore(0xb600, 0x9780) + mstore(0xb620, 0x98e0) + mstore(0xb640, 0x9640) + mstore(0xb660, 0x97a0) + mstore(0xb680, 0x9900) + let q_eval_set_0 := mload(0x94a0) + let q_eval_set_1 := mload(0x9540) + let q_eval_set_2 := mload(0x97c0) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0xa300, 0x60) + let eval_p := add(0xb280, 0x60) for { let i := 1 } lt(i, 0xb) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1682,26 +1765,26 @@ contract Halo2Verifier { { // q_eval_set[4]: 5 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0xa300, 0x8e80) - mstore(0xa320, 0x8ea0) - mstore(0xa340, 0x8ec0) - mstore(0xa360, 0x8ee0) - mstore(0xa380, 0x8f00) - mstore(0xa3a0, 0x8f20) - mstore(0xa3c0, 0x8f40) - mstore(0xa3e0, 0x8f60) - mstore(0xa400, 0x8f80) - mstore(0xa420, 0x8fa0) - mstore(0xa440, 0x8fc0) - mstore(0xa460, 0x8fe0) - mstore(0xa480, 0x9000) - mstore(0xa4a0, 0x9020) - mstore(0xa4c0, 0x9040) - let q_eval_set_0 := mload(0x8e80) - let q_eval_set_1 := mload(0x8ea0) - let q_eval_set_2 := mload(0x8ec0) + mstore(0xb280, 0x9e00) + mstore(0xb2a0, 0x9e20) + mstore(0xb2c0, 0x9e40) + mstore(0xb2e0, 0x9e60) + mstore(0xb300, 0x9e80) + mstore(0xb320, 0x9ea0) + mstore(0xb340, 0x9ec0) + mstore(0xb360, 0x9ee0) + mstore(0xb380, 0x9f00) + mstore(0xb3a0, 0x9f20) + mstore(0xb3c0, 0x9f40) + mstore(0xb3e0, 0x9f60) + mstore(0xb400, 0x9f80) + mstore(0xb420, 0x9fa0) + mstore(0xb440, 0x9fc0) + let q_eval_set_0 := mload(0x9e00) + let q_eval_set_1 := mload(0x9e20) + let q_eval_set_2 := mload(0x9e40) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0xa300, 0x60) + let eval_p := add(0xb280, 0x60) for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1892,181 +1975,186 @@ contract Halo2Verifier { let lin_x_split := mload(QUOTIENT_MPTR) let lin_one_minus_x_n := mload(add(QUOTIENT_MPTR, 0x20)) let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) - let x4_pow_0 := 1 - let x4_pow_1 := mulmod(x4_pow_0, x4, r) - let x4_pow_2 := mulmod(x4_pow_1, x4, r) - let x4_pow_3 := mulmod(x4_pow_2, x4, r) - let x4_pow_4 := mulmod(x4_pow_3, x4, r) - let x4_pow_5 := mulmod(x4_pow_4, x4, r) + let x4_pow_full := 1 + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_1 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_2 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_3 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_4 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_5 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) let v := calldataload(Q_EVAL_CPTR) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), x4_pow_4, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_5, r), r) - mcopy(0xa300, 0x98c0, 0x80) - mstore(0xa380, 1) - mcopy(0xa3a0, 0x9940, 0x80) - mstore(0xa420, mload(add(X1_POWERS_MPTR, 0x40))) - mcopy(0xa440, 0x9d40, 0x80) - mstore(0xa4c0, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0xa4e0, 0x99c0, 0x80) - mstore(0xa560, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0xa580, 0x9dc0, 0x80) - mstore(0xa600, mload(add(X1_POWERS_MPTR, 0xa0))) - mcopy(0xa620, 0x9f40, 0x80) - mstore(0xa6a0, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0xa6c0, 0x5780, 0x80) - mstore(0xa740, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0xa760, 0x5500, 0x80) - mstore(0xa7e0, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0xa800, 0x5580, 0x80) - mstore(0xa880, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0xa8a0, 0x5600, 0x80) - mstore(0xa920, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0xa940, 0x5680, 0x80) - mstore(0xa9c0, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0xa9e0, 0x5700, 0x80) - mstore(0xaa60, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0xaa80, 0x5300, 0x80) - mstore(0xab00, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0xab20, 0x5380, 0x80) - mstore(0xaba0, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0xabc0, 0x5400, 0x80) - mstore(0xac40, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0xac60, 0x5480, 0x80) - mstore(0xace0, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0xad00, 0x5800, 0x80) - mstore(0xad80, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0xada0, 0x5880, 0x80) - mstore(0xae20, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0xae40, 0x5900, 0x80) - mstore(0xaec0, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0xaee0, 0x5980, 0x80) - mstore(0xaf60, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0xaf80, 0x5b80, 0x80) - mstore(0xb000, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0xb020, 0x5c80, 0x80) - mstore(0xb0a0, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0xb0c0, 0x6000, 0x80) - mstore(0xb140, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0xb160, 0x6080, 0x80) - mstore(0xb1e0, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0xb200, 0x6100, 0x80) - mstore(0xb280, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0xb2a0, 0x6180, 0x80) - mstore(0xb320, mload(add(X1_POWERS_MPTR, 0x340))) - mcopy(0xb340, 0x6200, 0x80) - mstore(0xb3c0, mload(add(X1_POWERS_MPTR, 0x360))) - mcopy(0xb3e0, 0x6280, 0x80) - mstore(0xb460, mload(add(X1_POWERS_MPTR, 0x380))) - mcopy(0xb480, 0x6300, 0x80) - mstore(0xb500, mload(add(X1_POWERS_MPTR, 0x3a0))) - mcopy(0xb520, 0x6380, 0x80) - mstore(0xb5a0, mload(add(X1_POWERS_MPTR, 0x3c0))) - mcopy(0xb5c0, 0x6400, 0x80) - mstore(0xb640, mload(add(X1_POWERS_MPTR, 0x3e0))) - mcopy(0xb660, 0x6480, 0x80) - mstore(0xb6e0, mload(add(X1_POWERS_MPTR, 0x400))) - mcopy(0xb700, 0x6500, 0x80) - mstore(0xb780, mload(add(X1_POWERS_MPTR, 0x420))) - mcopy(0xb7a0, 0x6580, 0x80) - mstore(0xb820, mload(add(X1_POWERS_MPTR, 0x440))) - mcopy(0xb840, 0x6600, 0x80) - mstore(0xb8c0, mload(add(X1_POWERS_MPTR, 0x460))) - mcopy(0xb8e0, 0x6680, 0x80) - mstore(0xb960, mload(add(X1_POWERS_MPTR, 0x480))) - mcopy(0xb980, 0x6700, 0x80) - mstore(0xba00, mload(add(X1_POWERS_MPTR, 0x4a0))) - mcopy(0xba20, 0x6780, 0x80) - mstore(0xbaa0, mload(add(X1_POWERS_MPTR, 0x4c0))) - mcopy(0xbac0, 0x6800, 0x80) - mstore(0xbb40, mload(add(X1_POWERS_MPTR, 0x4e0))) - mcopy(0xbb60, 0x6880, 0x80) - mstore(0xbbe0, mload(add(X1_POWERS_MPTR, 0x500))) - mcopy(0xbc00, 0x6900, 0x80) - mstore(0xbc80, mload(add(X1_POWERS_MPTR, 0x520))) + mcopy(0xb280, 0xa840, 0x80) + mstore(0xb300, 1) + mcopy(0xb320, 0xa8c0, 0x80) + mstore(0xb3a0, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0xb3c0, 0xacc0, 0x80) + mstore(0xb440, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0xb460, 0xa940, 0x80) + mstore(0xb4e0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0xb500, 0xad40, 0x80) + mstore(0xb580, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0xb5a0, 0xaec0, 0x80) + mstore(0xb620, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0xb640, 0x6700, 0x80) + mstore(0xb6c0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0xb6e0, 0x6480, 0x80) + mstore(0xb760, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0xb780, 0x6500, 0x80) + mstore(0xb800, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0xb820, 0x6580, 0x80) + mstore(0xb8a0, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0xb8c0, 0x6600, 0x80) + mstore(0xb940, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0xb960, 0x6680, 0x80) + mstore(0xb9e0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0xba00, 0x6280, 0x80) + mstore(0xba80, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0xbaa0, 0x6300, 0x80) + mstore(0xbb20, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0xbb40, 0x6380, 0x80) + mstore(0xbbc0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0xbbe0, 0x6400, 0x80) + mstore(0xbc60, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0xbc80, 0x6780, 0x80) + mstore(0xbd00, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0xbd20, 0x6800, 0x80) + mstore(0xbda0, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0xbdc0, 0x6880, 0x80) + mstore(0xbe40, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0xbe60, 0x6900, 0x80) + mstore(0xbee0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0xbf00, 0x6b00, 0x80) + mstore(0xbf80, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0xbfa0, 0x6c00, 0x80) + mstore(0xc020, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0xc040, 0x6f80, 0x80) + mstore(0xc0c0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0xc0e0, 0x7000, 0x80) + mstore(0xc160, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0xc180, 0x7080, 0x80) + mstore(0xc200, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0xc220, 0x7100, 0x80) + mstore(0xc2a0, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0xc2c0, 0x7180, 0x80) + mstore(0xc340, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0xc360, 0x7200, 0x80) + mstore(0xc3e0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0xc400, 0x7280, 0x80) + mstore(0xc480, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0xc4a0, 0x7300, 0x80) + mstore(0xc520, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0xc540, 0x7380, 0x80) + mstore(0xc5c0, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0xc5e0, 0x7400, 0x80) + mstore(0xc660, mload(add(X1_POWERS_MPTR, 0x400))) + mcopy(0xc680, 0x7480, 0x80) + mstore(0xc700, mload(add(X1_POWERS_MPTR, 0x420))) + mcopy(0xc720, 0x7500, 0x80) + mstore(0xc7a0, mload(add(X1_POWERS_MPTR, 0x440))) + mcopy(0xc7c0, 0x7580, 0x80) + mstore(0xc840, mload(add(X1_POWERS_MPTR, 0x460))) + mcopy(0xc860, 0x7600, 0x80) + mstore(0xc8e0, mload(add(X1_POWERS_MPTR, 0x480))) + mcopy(0xc900, 0x7680, 0x80) + mstore(0xc980, mload(add(X1_POWERS_MPTR, 0x4a0))) + mcopy(0xc9a0, 0x7700, 0x80) + mstore(0xca20, mload(add(X1_POWERS_MPTR, 0x4c0))) + mcopy(0xca40, 0x7780, 0x80) + mstore(0xcac0, mload(add(X1_POWERS_MPTR, 0x4e0))) + mcopy(0xcae0, 0x7800, 0x80) + mstore(0xcb60, mload(add(X1_POWERS_MPTR, 0x500))) + mcopy(0xcb80, 0x7880, 0x80) + mstore(0xcc00, mload(add(X1_POWERS_MPTR, 0x520))) let lin_query_scalar_41 := mload(add(X1_POWERS_MPTR, 0x540)) let lin_cur_scalar_41 := mulmod(lin_query_scalar_41, lin_one_minus_x_n, r) - mcopy(0xbca0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0xbd20, lin_cur_scalar_41) + mcopy(0xcc20, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0xcca0, lin_cur_scalar_41) lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) - mcopy(0xbd40, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0xbdc0, lin_cur_scalar_41) + mcopy(0xccc0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0xcd40, lin_cur_scalar_41) lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) - mcopy(0xbde0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0xbe60, lin_cur_scalar_41) + mcopy(0xcd60, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0xcde0, lin_cur_scalar_41) lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) - mcopy(0xbe80, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0xbf00, lin_cur_scalar_41) - mcopy(0xbf20, 0x5a00, 0x80) - mstore(0xbfa0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0xbfc0, 0x5a80, 0x80) - mstore(0xc040, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0xc060, 0x5b00, 0x80) - mstore(0xc0e0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0xc100, 0x5c00, 0x80) - mstore(0xc180, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) - mcopy(0xc1a0, 0x5d00, 0x80) - mstore(0xc220, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) - mcopy(0xc240, 0x5d80, 0x80) - mstore(0xc2c0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) - mcopy(0xc2e0, 0x5e00, 0x80) - mstore(0xc360, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) - mcopy(0xc380, 0x5e80, 0x80) - mstore(0xc400, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) - mcopy(0xc420, 0x5f00, 0x80) - mstore(0xc4a0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) - mcopy(0xc4c0, 0x5f80, 0x80) - mstore(0xc540, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) - mcopy(0xc560, 0x9740, 0x80) - mstore(0xc5e0, x4_pow_1) - mcopy(0xc600, 0x97c0, 0x80) - mstore(0xc680, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0xc6a0, 0x9840, 0x80) - mstore(0xc720, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0xc740, 0x9cc0, 0x80) - mstore(0xc7c0, x4_pow_2) - mcopy(0xc7e0, 0x9e40, 0x80) - mstore(0xc860, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0xc880, 0x9ec0, 0x80) - mstore(0xc900, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) - mcopy(0xc920, 0x91c0, 0x80) - mstore(0xc9a0, x4_pow_3) - mcopy(0xc9c0, 0x9240, 0x80) - mstore(0xca40, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) - mcopy(0xca60, 0x92c0, 0x80) - mstore(0xcae0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_3, r)) - mcopy(0xcb00, 0x9340, 0x80) - mstore(0xcb80, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_3, r)) - mcopy(0xcba0, 0x93c0, 0x80) - mstore(0xcc20, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_3, r)) - mcopy(0xcc40, 0x9440, 0x80) - mstore(0xccc0, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_3, r)) - mcopy(0xcce0, 0x94c0, 0x80) - mstore(0xcd60, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_3, r)) - mcopy(0xcd80, 0x9540, 0x80) - mstore(0xce00, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_3, r)) - mcopy(0xce20, 0x95c0, 0x80) - mstore(0xcea0, mulmod(mload(add(X1_POWERS_MPTR, 0x100)), x4_pow_3, r)) - mcopy(0xcec0, 0x9640, 0x80) - mstore(0xcf40, mulmod(mload(add(X1_POWERS_MPTR, 0x120)), x4_pow_3, r)) - mcopy(0xcf60, 0x96c0, 0x80) - mstore(0xcfe0, mulmod(mload(add(X1_POWERS_MPTR, 0x140)), x4_pow_3, r)) - mcopy(0xd000, 0x9a40, 0x80) - mstore(0xd080, x4_pow_4) - mcopy(0xd0a0, 0x9ac0, 0x80) - mstore(0xd120, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_4, r)) - mcopy(0xd140, 0x9b40, 0x80) - mstore(0xd1c0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_4, r)) - mcopy(0xd1e0, 0x9bc0, 0x80) - mstore(0xd260, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_4, r)) - mcopy(0xd280, 0x9c40, 0x80) - mstore(0xd300, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_4, r)) - mcopy(0xd320, F_COM_MPTR, 0x80) - mstore(0xd3a0, x4_pow_5) + mcopy(0xce00, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0xce80, lin_cur_scalar_41) + mcopy(0xcea0, 0x6980, 0x80) + mstore(0xcf20, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0xcf40, 0x6a00, 0x80) + mstore(0xcfc0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0xcfe0, 0x6a80, 0x80) + mstore(0xd060, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0xd080, 0x6b80, 0x80) + mstore(0xd100, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0xd120, 0x6c80, 0x80) + mstore(0xd1a0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0xd1c0, 0x6d00, 0x80) + mstore(0xd240, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0xd260, 0x6d80, 0x80) + mstore(0xd2e0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0xd300, 0x6e00, 0x80) + mstore(0xd380, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0xd3a0, 0x6e80, 0x80) + mstore(0xd420, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0xd440, 0x6f00, 0x80) + mstore(0xd4c0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0xd4e0, 0xa6c0, 0x80) + mstore(0xd560, x4_pow_1) + mcopy(0xd580, 0xa740, 0x80) + mstore(0xd600, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0xd620, 0xa7c0, 0x80) + mstore(0xd6a0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0xd6c0, 0xac40, 0x80) + mstore(0xd740, x4_pow_2) + mcopy(0xd760, 0xadc0, 0x80) + mstore(0xd7e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0xd800, 0xae40, 0x80) + mstore(0xd880, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0xd8a0, 0xa140, 0x80) + mstore(0xd920, x4_pow_3) + mcopy(0xd940, 0xa1c0, 0x80) + mstore(0xd9c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xd9e0, 0xa240, 0x80) + mstore(0xda60, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_3, r)) + mcopy(0xda80, 0xa2c0, 0x80) + mstore(0xdb00, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_3, r)) + mcopy(0xdb20, 0xa340, 0x80) + mstore(0xdba0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_3, r)) + mcopy(0xdbc0, 0xa3c0, 0x80) + mstore(0xdc40, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_3, r)) + mcopy(0xdc60, 0xa440, 0x80) + mstore(0xdce0, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_3, r)) + mcopy(0xdd00, 0xa4c0, 0x80) + mstore(0xdd80, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_3, r)) + mcopy(0xdda0, 0xa540, 0x80) + mstore(0xde20, mulmod(mload(add(X1_POWERS_MPTR, 0x100)), x4_pow_3, r)) + mcopy(0xde40, 0xa5c0, 0x80) + mstore(0xdec0, mulmod(mload(add(X1_POWERS_MPTR, 0x120)), x4_pow_3, r)) + mcopy(0xdee0, 0xa640, 0x80) + mstore(0xdf60, mulmod(mload(add(X1_POWERS_MPTR, 0x140)), x4_pow_3, r)) + mcopy(0xdf80, 0xa9c0, 0x80) + mstore(0xe000, x4_pow_4) + mcopy(0xe020, 0xaa40, 0x80) + mstore(0xe0a0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_4, r)) + mcopy(0xe0c0, 0xaac0, 0x80) + mstore(0xe140, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_4, r)) + mcopy(0xe160, 0xab40, 0x80) + mstore(0xe1e0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_4, r)) + mcopy(0xe200, 0xabc0, 0x80) + mstore(0xe280, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_4, r)) + mcopy(0xe2a0, F_COM_MPTR, 0x80) + mstore(0xe320, x4_pow_5) if success { - success := staticcall(gas(), 0x0c, 0xa300, 0x30c0, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0xb280, 0x30c0, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -2078,28 +2166,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } @@ -2181,6 +2269,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2VerifyingKey.sol index 7aac985fc..f8c08652b 100644 --- a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2VerifyingKey.sol @@ -271,149 +271,149 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x19c0), 0x0456a29a706afacf2158850711006fe0acf6a437e9477bf6f782dfac86f2cf7b) // quotient_const mstore(add(payload, 0x19e0), 0x0397cc06bc030aab970dabe70cd498bbeea8daaea65607bb6a872125fec74a10) // quotient_const mstore(add(payload, 0x1a00), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb91950003) // quotient_const - mstore(add(payload, 0x1a20), 0x058560108a8005860008060d000b02000105852011852011852005858008060d) // quotient_program - mstore(add(payload, 0x1a40), 0x000b0300000585401185401185400585a008060d000b03000105856011856011) // quotient_program - mstore(add(payload, 0x1a60), 0x856005862008060d000b0300011b00001b000105860008108b20058520118520) // quotient_program - mstore(add(payload, 0x1a80), 0x1185800d01060585401185401185a00d02060585601185601186200d03060d00) // quotient_program - mstore(add(payload, 0x1aa0), 0x0b0300011b000221020403070002000085200585400685600785800885a00085) // quotient_program - mstore(add(payload, 0x1ac0), 0xc00585e00686000986200a86400b86600c86800d86a00e86c00f86e010870011) // quotient_program - mstore(add(payload, 0x1ae0), 0x87200787400887600987800a87a085200085c00585e006860007874008876009) // quotient_program - mstore(add(payload, 0x1b00), 0x87800a87a085400585c00685e00786000887400987600a87801287a085600685) // quotient_program - mstore(add(payload, 0x1b20), 0xc00785e00886000987400a87601287801387a085800785c00885e00986000a87) // quotient_program - mstore(add(payload, 0x1b40), 0x401287601387801487a085a00885c00985e00a86001287401387601487801587) // quotient_program - mstore(add(payload, 0x1b60), 0xa086200985c00a85e01286001387401487601587801687a086400a85c01285e0) // quotient_program - mstore(add(payload, 0x1b80), 0x1386001487401587601687801787a01887c01988000b04000121021a03070001) // quotient_program - mstore(add(payload, 0x1ba0), 0x000085200585400685601b85801c85a00085c00585e00686001d86201e86400b) // quotient_program - mstore(add(payload, 0x1bc0), 0x86600c86800d86a01f86c02086e02187002287201b87401c87601d87801e87a0) // quotient_program - mstore(add(payload, 0x1be0), 0x85200085c00585e00686001b87401c87601d87801e87a085400585c00685e01b) // quotient_program - mstore(add(payload, 0x1c00), 0x86001c87401d87601e87802387a085600685c01b85e01c86001d87401e876023) // quotient_program - mstore(add(payload, 0x1c20), 0x87802487a085801b85c01c85e01d86001e87402387602487802587a085a01c85) // quotient_program - mstore(add(payload, 0x1c40), 0xc01d85e01e86002387402487602587802687a086201d85c01e85e02386002487) // quotient_program - mstore(add(payload, 0x1c60), 0x402587602687802787a086401e85c02385e02486002587402687602787802887) // quotient_program - mstore(add(payload, 0x1c80), 0xa02987c00b0400011b000321022a01000009000986200a86400b86600c86800d) // quotient_program - mstore(add(payload, 0x1ca0), 0x86a00e86c00f86e00085200585400685600785800885a01087001187201887c0) // quotient_program - mstore(add(payload, 0x1cc0), 0x1988000b05000121022b01000008001d86201e86400b86600c86800d86a01f86) // quotient_program - mstore(add(payload, 0x1ce0), 0xc02086e00085200585400685601b85801c85a02187002287202987c00b050001) // quotient_program - mstore(add(payload, 0x1d00), 0x210388202c0000000b2b0b85200c85400d85602d85c02e85e02f86000b86600c) // quotient_program - mstore(add(payload, 0x1d20), 0x86800d86a03087c03187e00b852086600c852086800d852086a00c854086600d) // quotient_program - mstore(add(payload, 0x1d40), 0x8540868032854087200d856086603285608700338560872032858086e0338580) // quotient_program - mstore(add(payload, 0x1d60), 0x870034858087203285a086c03385a086e03485a087003585a087200085c085c0) // quotient_program - mstore(add(payload, 0x1d80), 0x2e85c085e02f85c086000685e085e03685e087a0368600878037860087a03286) // quotient_program - mstore(add(payload, 0x1da0), 0x2086a033862086c034862086e035862087003886208720328640868033864086) // quotient_program - mstore(add(payload, 0x1dc0), 0xa034864086c035864086e038864087003986408720368740876037874087803a) // quotient_program - mstore(add(payload, 0x1de0), 0x874087a03b876087603a876087803c876087a03d878087803e878087a03f87a0) // quotient_program - mstore(add(payload, 0x1e00), 0x87a00d000b060000210388204003080002150b85200c85400d85600e85800f85) // quotient_program - mstore(add(payload, 0x1e20), 0xa02d85c02e85e02f86001086201186400b86600c86800d86a00e86c00f86e010) // quotient_program - mstore(add(payload, 0x1e40), 0x87001187204187404287604387804487a085200b86600c86800d86a00e86c00f) // quotient_program - mstore(add(payload, 0x1e60), 0x86e010870011872085400c86600d86800e86a00f86c01086e011870045872085) // quotient_program - mstore(add(payload, 0x1e80), 0x600d86600e86800f86a01086c01186e045870046872085800e86600f86801086) // quotient_program - mstore(add(payload, 0x1ea0), 0xa01186c04586e046870047872085a00f86601086801186a04586c04686e04787) // quotient_program - mstore(add(payload, 0x1ec0), 0x0048872085c00085c02e85e02f86004187404287604387804487a08620108660) // quotient_program - mstore(add(payload, 0x1ee0), 0x1186804586a04686c04786e048870049872086401186604586804686a04786c0) // quotient_program - mstore(add(payload, 0x1f00), 0x4886e04987004a87201887c01988000685e085e04185e086004285e087404385) // quotient_program - mstore(add(payload, 0x1f20), 0xe087604485e087804b85e087a00886008600438600874044860087604b860087) // quotient_program - mstore(add(payload, 0x1f40), 0x804c860087a00a874087404b874087604c874087804d874087a013876087604d) // quotient_program - mstore(add(payload, 0x1f60), 0x876087804e876087a015878087804f878087a01787a087a00d000b0600012103) // quotient_program - mstore(add(payload, 0x1f80), 0x88205003080001150b85200c85400d85601f85802085a02d85c02e85e02f8600) // quotient_program - mstore(add(payload, 0x1fa0), 0x2186202286400b86600c86800d86a01f86c02086e02187002287205187405287) // quotient_program - mstore(add(payload, 0x1fc0), 0x605387805487a085200b86600c86800d86a01f86c02086e02187002287208540) // quotient_program - mstore(add(payload, 0x1fe0), 0x0c86600d86801f86a02086c02186e022870055872085600d86601f86802086a0) // quotient_program - mstore(add(payload, 0x2000), 0x2186c02286e055870056872085801f86602086802186a02286c05586e0568700) // quotient_program - mstore(add(payload, 0x2020), 0x57872085a02086602186802286a05586c05686e057870058872085c00085c02e) // quotient_program - mstore(add(payload, 0x2040), 0x85e02f86005187405287605387805487a086202186602286805586a05686c057) // quotient_program - mstore(add(payload, 0x2060), 0x86e058870059872086402286605586805686a05786c05886e05987005a872029) // quotient_program - mstore(add(payload, 0x2080), 0x87c00685e085e05185e086005285e087405385e087605485e087805b85e087a0) // quotient_program - mstore(add(payload, 0x20a0), 0x1c86008600538600874054860087605b860087805c860087a01e874087405b87) // quotient_program - mstore(add(payload, 0x20c0), 0x4087605c874087805d874087a024876087605d876087805e876087a026878087) // quotient_program - mstore(add(payload, 0x20e0), 0x805f878087a02887a087a00d000b06000121038820600000000c390b85200c85) // quotient_program - mstore(add(payload, 0x2100), 0x400d85603087c03187e00088200088400588600688800b89200c89400d896000) // quotient_program - mstore(add(payload, 0x2120), 0x85c088400585c088600685c088800b85c089200c85c089400d85c089600585e0) // quotient_program - mstore(add(payload, 0x2140), 0x88400685e088606185e089000c85e089200d85e089403285e089e00686008840) // quotient_program - mstore(add(payload, 0x2160), 0x61860088e03b860089000d8600892032860089c033860089e000866088200586) // quotient_program - mstore(add(payload, 0x2180), 0x8088200686a0882061874088c03b874088e0628740890032874089a033874089) // quotient_program - mstore(add(payload, 0x21a0), 0xc034874089e061876088a03b876088c062876088e03d87608900328760898033) // quotient_program - mstore(add(payload, 0x21c0), 0x876089a034876089c035876089e061878088803b878088a062878088c03d8780) // quotient_program - mstore(add(payload, 0x21e0), 0x88e063878089003287808960338780898034878089a035878089c038878089e0) // quotient_program - mstore(add(payload, 0x2200), 0x6187a088603b87a088806287a088a03d87a088c06387a088e03f87a089003287) // quotient_program - mstore(add(payload, 0x2220), 0xa089403387a089603487a089803587a089a03887a089c03987a089e00d000b07) // quotient_program - mstore(add(payload, 0x2240), 0x00002103882064020f000a001988000088200088400588600688800788a00888) // quotient_program - mstore(add(payload, 0x2260), 0xc00988e00a89000b89200c89400d89600e89800f89a088200086600586800686) // quotient_program - mstore(add(payload, 0x2280), 0xa00786c00886e00987000a872088400085c00585e00686000787400887600987) // quotient_program - mstore(add(payload, 0x22a0), 0x800a87a088600585c00685e00786000887400987600a87801287a088800685c0) // quotient_program - mstore(add(payload, 0x22c0), 0x0785e00886000987400a87601287801387a088a00785c00885e00986000a8740) // quotient_program - mstore(add(payload, 0x22e0), 0x1287601387801487a088c00885c00985e00a86001287401387601487801587a0) // quotient_program - mstore(add(payload, 0x2300), 0x88e00985c00a85e01286001387401487601587801687a089000a85c01285e013) // quotient_program - mstore(add(payload, 0x2320), 0x86001487401587601687801787a085c00b89200c89400d89600e89800f89a010) // quotient_program - mstore(add(payload, 0x2340), 0x89c01189e085e00c89200d89400e89600f89801089a01189c04589e086000d89) // quotient_program - mstore(add(payload, 0x2360), 0x200e89400f89601089801189a04589c04689e087400e89200f89401089601189) // quotient_program - mstore(add(payload, 0x2380), 0x804589a04689c04789e087600f89201089401189604589804689a04789c04889) // quotient_program - mstore(add(payload, 0x23a0), 0xe087801089201189404589604689804789a04889c04989e087a0118920458940) // quotient_program - mstore(add(payload, 0x23c0), 0x4689604789804889a04989c04a89e00b85200c85400d85600e85800f85a01086) // quotient_program - mstore(add(payload, 0x23e0), 0x201186401887c01089c01189e00d000b0700012103882065020f000900008820) // quotient_program - mstore(add(payload, 0x2400), 0x0088400588600688801b88a01c88c01d88e01e89000b89200c89400d89601f89) // quotient_program - mstore(add(payload, 0x2420), 0x802089a02189c088200086600586800686a01b86c01c86e01d87001e87208840) // quotient_program - mstore(add(payload, 0x2440), 0x0085c00585e00686001b87401c87601d87801e87a088600585c00685e01b8600) // quotient_program - mstore(add(payload, 0x2460), 0x1c87401d87601e87802387a088800685c01b85e01c86001d87401e8760238780) // quotient_program - mstore(add(payload, 0x2480), 0x2487a088a01b85c01c85e01d86001e87402387602487802587a088c01c85c01d) // quotient_program - mstore(add(payload, 0x24a0), 0x85e01e86002387402487602587802687a088e01d85c01e85e023860024874025) // quotient_program - mstore(add(payload, 0x24c0), 0x87602687802787a089001e85c02385e02486002587402687602787802887a085) // quotient_program - mstore(add(payload, 0x24e0), 0xc00b89200c89400d89601f89802089a02189c02289e085e00c89200d89401f89) // quotient_program - mstore(add(payload, 0x2500), 0x602089802189a02289c05589e086000d89201f89402089602189802289a05589) // quotient_program - mstore(add(payload, 0x2520), 0xc05689e087401f89202089402189602289805589a05689c05789e08760208920) // quotient_program - mstore(add(payload, 0x2540), 0x2189402289605589805689a05789c05889e08780218920228940558960568980) // quotient_program - mstore(add(payload, 0x2560), 0x5789a05889c05989e087a02289205589405689605789805889a05989c05a89e0) // quotient_program - mstore(add(payload, 0x2580), 0x0b85200c85400d85601f85802085a02186202286402987c02289e00d000b0700) // quotient_program - mstore(add(payload, 0x25a0), 0x0121038820660000000b2b6785206885406985606a85c06b85e06c86006a8660) // quotient_program - mstore(add(payload, 0x25c0), 0x6b86806c86a03087c03187e06d85208520688520854069852085606e85408540) // quotient_program - mstore(add(payload, 0x25e0), 0x6f854086406f8560862070856086406f858085a0708580862071858086407285) // quotient_program - mstore(add(payload, 0x2600), 0xa085a07185a086207385a086406a85c086606b85c086806c85c086a06b85e086) // quotient_program - mstore(add(payload, 0x2620), 0x606c85e086807485e087206c8600866074860087007586008720768620862077) // quotient_program - mstore(add(payload, 0x2640), 0x86208640788640864074868087a07486a087807586a087a07486c087607586c0) // quotient_program - mstore(add(payload, 0x2660), 0x87807986c087a07486e087407586e087607986e087807a86e087a07587008740) // quotient_program - mstore(add(payload, 0x2680), 0x79870087607a870087807b870087a079872087407a872087607b872087807c87) // quotient_program - mstore(add(payload, 0x26a0), 0x2087a00d000b080000210388207d03080002156785206885406985607e85807f) // quotient_program - mstore(add(payload, 0x26c0), 0x85a06a85c06b85e06c86008086208186406a86606b86806c86a08286c08386e0) // quotient_program - mstore(add(payload, 0x26e0), 0x8487008587208287408387608487808587a085206d85206885406985607e8580) // quotient_program - mstore(add(payload, 0x2700), 0x7f85a080862081864085c06a86606b86806c86a08286c08386e0848700858720) // quotient_program - mstore(add(payload, 0x2720), 0x85e06b86606c86808286a08386c08486e085870086872086006c866082868083) // quotient_program - mstore(add(payload, 0x2740), 0x86a08486c08586e086870087872087408286608386808486a08586c08686e087) // quotient_program - mstore(add(payload, 0x2760), 0x870088872087608386608486808586a08686c08786e088870089872087808486) // quotient_program - mstore(add(payload, 0x2780), 0x608586808686a08786c08886e08987008a872087a08586608686808786a08886) // quotient_program - mstore(add(payload, 0x27a0), 0xc08986e08a87008b87201887c01988006e854085407e854085607f8540858080) // quotient_program - mstore(add(payload, 0x27c0), 0x854085a081854086208c854086408d85608560808560858081856085a08c8560) // quotient_program - mstore(add(payload, 0x27e0), 0x86208e856086408f858085808c858085a08e8580862090858086409185a085a0) // quotient_program - mstore(add(payload, 0x2800), 0x9085a086209285a086409386208620948620864095864086400d000b08000121) // quotient_program - mstore(add(payload, 0x2820), 0x0388209603080001156785206885406985609785809885a06a85c06b85e06c86) // quotient_program - mstore(add(payload, 0x2840), 0x009986209a86406a86606b86806c86a09b86c09c86e09d87009e87209b87409c) // quotient_program - mstore(add(payload, 0x2860), 0x87609d87809e87a085206d85206885406985609785809885a09986209a864085) // quotient_program - mstore(add(payload, 0x2880), 0xc06a86606b86806c86a09b86c09c86e09d87009e872085e06b86606c86809b86) // quotient_program - mstore(add(payload, 0x28a0), 0xa09c86c09d86e09e87009f872086006c86609b86809c86a09d86c09e86e09f87) // quotient_program - mstore(add(payload, 0x28c0), 0x00a0872087409b86609c86809d86a09e86c09f86e0a08700a1872087609c8660) // quotient_program - mstore(add(payload, 0x28e0), 0x9d86809e86a09f86c0a086e0a18700a2872087809d86609e86809f86a0a086c0) // quotient_program - mstore(add(payload, 0x2900), 0xa186e0a28700a3872087a09e86609f8680a086a0a186c0a286e0a38700a48720) // quotient_program - mstore(add(payload, 0x2920), 0x2987c06e854085409785408560988540858099854085a09a85408620a5854086) // quotient_program - mstore(add(payload, 0x2940), 0x40a68560856099856085809a856085a0a585608620a785608640a885808580a5) // quotient_program - mstore(add(payload, 0x2960), 0x858085a0a785808620a985808640aa85a085a0a985a08620ab85a08640ac8620) // quotient_program - mstore(add(payload, 0x2980), 0x8620ad86208640ae864086400d000b08000121038820af0000000e1000852005) // quotient_program - mstore(add(payload, 0x29a0), 0x85400685606a85c06b85e06c86000086600586800686a03087c03187e0008840) // quotient_program - mstore(add(payload, 0x29c0), 0x0588600688800b85c085c06b85c085e06c85c086000d85e085e07485e087a074) // quotient_program - mstore(add(payload, 0x29e0), 0x8600878075860087a07487408760758740878079874087a03387608760798760) // quotient_program - mstore(add(payload, 0x2a00), 0x87807a876087a035878087807b878087a03987a087a00d000b09000021038820) // quotient_program - mstore(add(payload, 0x2a20), 0xb004010002150085200585400685600785800885a06a85c06b85e06c86000986) // quotient_program - mstore(add(payload, 0x2a40), 0x200a86400086600586800686a00786c00886e00987000a872082874083876084) // quotient_program - mstore(add(payload, 0x2a60), 0x87808587a00088400588600688800788a00888c00988e00a890085c00b85c06b) // quotient_program - mstore(add(payload, 0x2a80), 0x85e06c86008287408387608487808587a01887c01988000d85e085e08285e086) // quotient_program - mstore(add(payload, 0x2aa0), 0x008385e087408485e087608585e087808685e087a00f86008600848600874085) // quotient_program - mstore(add(payload, 0x2ac0), 0x86008760868600878087860087a0118740874086874087608787408780888740) // quotient_program - mstore(add(payload, 0x2ae0), 0x87a04687608760888760878089876087a048878087808a878087a04a87a087a0) // quotient_program - mstore(add(payload, 0x2b00), 0x0d000b09000121038820b104010001150085200585400685601b85801c85a06a) // quotient_program - mstore(add(payload, 0x2b20), 0x85c06b85e06c86001d86201e86400086600586800686a01b86c01c86e01d8700) // quotient_program - mstore(add(payload, 0x2b40), 0x1e87209b87409c87609d87809e87a00088400588600688801b88a01c88c01d88) // quotient_program - mstore(add(payload, 0x2b60), 0xe01e890085c00b85c06b85e06c86009b87409c87609d87809e87a02987c00d85) // quotient_program - mstore(add(payload, 0x2b80), 0xe085e09b85e086009c85e087409d85e087609e85e087809f85e087a020860086) // quotient_program - mstore(add(payload, 0x2ba0), 0x009d860087409e860087609f86008780a0860087a022874087409f87408760a0) // quotient_program - mstore(add(payload, 0x2bc0), 0x87408780a1874087a05687608760a187608780a2876087a05887808780a38780) // quotient_program - mstore(add(payload, 0x2be0), 0x87a05a87a087a00d000b090001191f0000000000000000000000000000000000) // quotient_program + mstore(add(payload, 0x1a20), 0x0594e0109a0005958008060d000b0200010594a01194a01194a005950008060d) // quotient_program + mstore(add(payload, 0x1a40), 0x000b0300000594c01194c01194c005952008060d000b0300010594e01194e011) // quotient_program + mstore(add(payload, 0x1a60), 0x94e00595a008060d000b0300011b00001b000105958008109aa00594a01194a0) // quotient_program + mstore(add(payload, 0x1a80), 0x1195000d01060594c01194c01195200d02060594e01194e01195a00d03060d00) // quotient_program + mstore(add(payload, 0x1aa0), 0x0b0300011b000221020403070002000094a00594c00694e00795000895200095) // quotient_program + mstore(add(payload, 0x1ac0), 0x400595600695800995a00a95c00b95e00c96000d96200e96400f966010968011) // quotient_program + mstore(add(payload, 0x1ae0), 0x96a00796c00896e00997000a972094a00095400595600695800796c00896e009) // quotient_program + mstore(add(payload, 0x1b00), 0x97000a972094c00595400695600795800896c00996e00a970012972094e00695) // quotient_program + mstore(add(payload, 0x1b20), 0x400795600895800996c00a96e012970013972095000795400895600995800a96) // quotient_program + mstore(add(payload, 0x1b40), 0xc01296e013970014972095200895400995600a95801296c01396e01497001597) // quotient_program + mstore(add(payload, 0x1b60), 0x2095a00995400a95601295801396c01496e015970016972095c00a9540129560) // quotient_program + mstore(add(payload, 0x1b80), 0x1395801496c01596e01697001797201897401997800b04000121021a03070001) // quotient_program + mstore(add(payload, 0x1ba0), 0x000094a00594c00694e01b95001c95200095400595600695801d95a01e95c00b) // quotient_program + mstore(add(payload, 0x1bc0), 0x95e00c96000d96201f96402096602196802296a01b96c01c96e01d97001e9720) // quotient_program + mstore(add(payload, 0x1be0), 0x94a00095400595600695801b96c01c96e01d97001e972094c00595400695601b) // quotient_program + mstore(add(payload, 0x1c00), 0x95801c96c01d96e01e970023972094e00695401b95601c95801d96c01e96e023) // quotient_program + mstore(add(payload, 0x1c20), 0x970024972095001b95401c95601d95801e96c02396e024970025972095201c95) // quotient_program + mstore(add(payload, 0x1c40), 0x401d95601e95802396c02496e025970026972095a01d95401e95602395802496) // quotient_program + mstore(add(payload, 0x1c60), 0xc02596e026970027972095c01e95402395602495802596c02696e02797002897) // quotient_program + mstore(add(payload, 0x1c80), 0x202997400b0400011b000321022a01000009000995a00a95c00b95e00c96000d) // quotient_program + mstore(add(payload, 0x1ca0), 0x96200e96400f96600094a00594c00694e00795000895201096801196a0189740) // quotient_program + mstore(add(payload, 0x1cc0), 0x1997800b05000121022b01000008001d95a01e95c00b95e00c96000d96201f96) // quotient_program + mstore(add(payload, 0x1ce0), 0x402096600094a00594c00694e01b95001c95202196802296a02997400b050001) // quotient_program + mstore(add(payload, 0x1d00), 0x210397a02c0000000b2b0b94a00c94c00d94e02d95402e95602f95800b95e00c) // quotient_program + mstore(add(payload, 0x1d20), 0x96000d96203097403197600b94a095e00c94a096000d94a096200c94c095e00d) // quotient_program + mstore(add(payload, 0x1d40), 0x94c096003294c096a00d94e095e03294e096803394e096a03295009660339500) // quotient_program + mstore(add(payload, 0x1d60), 0x968034950096a032952096403395209660349520968035952096a00095409540) // quotient_program + mstore(add(payload, 0x1d80), 0x2e954095602f9540958006956095603695609720369580970037958097203295) // quotient_program + mstore(add(payload, 0x1da0), 0xa096203395a096403495a096603595a096803895a096a03295c096003395c096) // quotient_program + mstore(add(payload, 0x1dc0), 0x203495c096403595c096603895c096803995c096a03696c096e03796c097003a) // quotient_program + mstore(add(payload, 0x1de0), 0x96c097203b96e096e03a96e097003c96e097203d970097003e970097203f9720) // quotient_program + mstore(add(payload, 0x1e00), 0x97200d000b060000210397a04003080002150b94a00c94c00d94e00e95000f95) // quotient_program + mstore(add(payload, 0x1e20), 0x202d95402e95602f95801095a01195c00b95e00c96000d96200e96400f966010) // quotient_program + mstore(add(payload, 0x1e40), 0x96801196a04196c04296e043970044972094a00b95e00c96000d96200e96400f) // quotient_program + mstore(add(payload, 0x1e60), 0x96601096801196a094c00c95e00d96000e96200f96401096601196804596a094) // quotient_program + mstore(add(payload, 0x1e80), 0xe00d95e00e96000f96201096401196604596804696a095000e95e00f96001096) // quotient_program + mstore(add(payload, 0x1ea0), 0x201196404596604696804796a095200f95e01096001196204596404696604796) // quotient_program + mstore(add(payload, 0x1ec0), 0x804896a095400095402e95602f95804196c04296e043970044972095a01095e0) // quotient_program + mstore(add(payload, 0x1ee0), 0x1196004596204696404796604896804996a095c01195e0459600469620479640) // quotient_program + mstore(add(payload, 0x1f00), 0x4896604996804a96a01897401997800695609560419560958042956096c04395) // quotient_program + mstore(add(payload, 0x1f20), 0x6096e044956097004b95609720089580958043958096c044958096e04b958097) // quotient_program + mstore(add(payload, 0x1f40), 0x004c958097200a96c096c04b96c096e04c96c097004d96c097201396e096e04d) // quotient_program + mstore(add(payload, 0x1f60), 0x96e097004e96e0972015970097004f9700972017972097200d000b0600012103) // quotient_program + mstore(add(payload, 0x1f80), 0x97a05003080001150b94a00c94c00d94e01f95002095202d95402e95602f9580) // quotient_program + mstore(add(payload, 0x1fa0), 0x2195a02295c00b95e00c96000d96201f96402096602196802296a05196c05296) // quotient_program + mstore(add(payload, 0x1fc0), 0xe053970054972094a00b95e00c96000d96201f96402096602196802296a094c0) // quotient_program + mstore(add(payload, 0x1fe0), 0x0c95e00d96001f96202096402196602296805596a094e00d95e01f9600209620) // quotient_program + mstore(add(payload, 0x2000), 0x2196402296605596805696a095001f95e0209600219620229640559660569680) // quotient_program + mstore(add(payload, 0x2020), 0x5796a095202095e02196002296205596405696605796805896a095400095402e) // quotient_program + mstore(add(payload, 0x2040), 0x95602f95805196c05296e053970054972095a02195e022960055962056964057) // quotient_program + mstore(add(payload, 0x2060), 0x96605896805996a095c02295e05596005696205796405896605996805a96a029) // quotient_program + mstore(add(payload, 0x2080), 0x97400695609560519560958052956096c053956096e054956097005b95609720) // quotient_program + mstore(add(payload, 0x20a0), 0x1c9580958053958096c054958096e05b958097005c958097201e96c096c05b96) // quotient_program + mstore(add(payload, 0x20c0), 0xc096e05c96c097005d96c097202496e096e05d96e097005e96e0972026970097) // quotient_program + mstore(add(payload, 0x20e0), 0x005f9700972028972097200d000b060001210397a0600000000c390b94a00c94) // quotient_program + mstore(add(payload, 0x2100), 0xc00d94e03097403197600097a00097c00597e00698000b98a00c98c00d98e000) // quotient_program + mstore(add(payload, 0x2120), 0x954097c005954097e006954098000b954098a00c954098c00d954098e0059560) // quotient_program + mstore(add(payload, 0x2140), 0x97c006956097e061956098800c956098a00d956098c0329560996006958097c0) // quotient_program + mstore(add(payload, 0x2160), 0x61958098603b958098800d958098a0329580994033958099600095e097a00596) // quotient_program + mstore(add(payload, 0x2180), 0x0097a006962097a06196c098403b96c098606296c098803296c099203396c099) // quotient_program + mstore(add(payload, 0x21a0), 0x403496c099606196e098203b96e098406296e098603d96e098803296e0990033) // quotient_program + mstore(add(payload, 0x21c0), 0x96e099203496e099403596e0996061970098003b9700982062970098403d9700) // quotient_program + mstore(add(payload, 0x21e0), 0x9860639700988032970098e03397009900349700992035970099403897009960) // quotient_program + mstore(add(payload, 0x2200), 0x61972097e03b9720980062972098203d9720984063972098603f972098803297) // quotient_program + mstore(add(payload, 0x2220), 0x2098c033972098e034972099003597209920389720994039972099600d000b07) // quotient_program + mstore(add(payload, 0x2240), 0x0000210397a064020f000a001997800097a00097c00597e00698000798200898) // quotient_program + mstore(add(payload, 0x2260), 0x400998600a98800b98a00c98c00d98e00e99000f992097a00095e00596000696) // quotient_program + mstore(add(payload, 0x2280), 0x200796400896600996800a96a097c00095400595600695800796c00896e00997) // quotient_program + mstore(add(payload, 0x22a0), 0x000a972097e00595400695600795800896c00996e00a97001297209800069540) // quotient_program + mstore(add(payload, 0x22c0), 0x0795600895800996c00a96e012970013972098200795400895600995800a96c0) // quotient_program + mstore(add(payload, 0x22e0), 0x1296e013970014972098400895400995600a95801296c01396e0149700159720) // quotient_program + mstore(add(payload, 0x2300), 0x98600995400a95601295801396c01496e015970016972098800a954012956013) // quotient_program + mstore(add(payload, 0x2320), 0x95801496c01596e016970017972095400b98a00c98c00d98e00e99000f992010) // quotient_program + mstore(add(payload, 0x2340), 0x994011996095600c98a00d98c00e98e00f990010992011994045996095800d98) // quotient_program + mstore(add(payload, 0x2360), 0xa00e98c00f98e010990011992045994046996096c00e98a00f98c01098e01199) // quotient_program + mstore(add(payload, 0x2380), 0x0045992046994047996096e00f98a01098c01198e04599004699204799404899) // quotient_program + mstore(add(payload, 0x23a0), 0x6097001098a01198c04598e046990047992048994049996097201198a04598c0) // quotient_program + mstore(add(payload, 0x23c0), 0x4698e04799004899204999404a99600b94a00c94c00d94e00e95000f95201095) // quotient_program + mstore(add(payload, 0x23e0), 0xa01195c01897401099401199600d000b070001210397a065020f0009000097a0) // quotient_program + mstore(add(payload, 0x2400), 0x0097c00597e00698001b98201c98401d98601e98800b98a00c98c00d98e01f99) // quotient_program + mstore(add(payload, 0x2420), 0x0020992021994097a00095e00596000696201b96401c96601d96801e96a097c0) // quotient_program + mstore(add(payload, 0x2440), 0x0095400595600695801b96c01c96e01d97001e972097e00595400695601b9580) // quotient_program + mstore(add(payload, 0x2460), 0x1c96c01d96e01e970023972098000695401b95601c95801d96c01e96e0239700) // quotient_program + mstore(add(payload, 0x2480), 0x24972098201b95401c95601d95801e96c02396e024970025972098401c95401d) // quotient_program + mstore(add(payload, 0x24a0), 0x95601e95802396c02496e025970026972098601d95401e95602395802496c025) // quotient_program + mstore(add(payload, 0x24c0), 0x96e026970027972098801e95402395602495802596c02696e027970028972095) // quotient_program + mstore(add(payload, 0x24e0), 0x400b98a00c98c00d98e01f990020992021994022996095600c98a00d98c01f98) // quotient_program + mstore(add(payload, 0x2500), 0xe020990021992022994055996095800d98a01f98c02098e02199002299205599) // quotient_program + mstore(add(payload, 0x2520), 0x4056996096c01f98a02098c02198e022990055992056994057996096e02098a0) // quotient_program + mstore(add(payload, 0x2540), 0x2198c02298e055990056992057994058996097002198a02298c05598e0569900) // quotient_program + mstore(add(payload, 0x2560), 0x57992058994059996097202298a05598c05698e05799005899205999405a9960) // quotient_program + mstore(add(payload, 0x2580), 0x0b94a00c94c00d94e01f95002095202195a02295c02997402299600d000b0700) // quotient_program + mstore(add(payload, 0x25a0), 0x01210397a0660000000b2b6794a06894c06994e06a95406b95606c95806a95e0) // quotient_program + mstore(add(payload, 0x25c0), 0x6b96006c96203097403197606d94a094a06894a094c06994a094e06e94c094c0) // quotient_program + mstore(add(payload, 0x25e0), 0x6f94c095c06f94e095a07094e095c06f9500952070950095a071950095c07295) // quotient_program + mstore(add(payload, 0x2600), 0x20952071952095a073952095c06a954095e06b954096006c954096206b956095) // quotient_program + mstore(add(payload, 0x2620), 0xe06c9560960074956096a06c958095e0749580968075958096a07695a095a077) // quotient_program + mstore(add(payload, 0x2640), 0x95a095c07895c095c074960097207496209700759620972074964096e0759640) // quotient_program + mstore(add(payload, 0x2660), 0x9700799640972074966096c075966096e079966097007a9660972075968096c0) // quotient_program + mstore(add(payload, 0x2680), 0x79968096e07a968097007b968097207996a096c07a96a096e07b96a097007c96) // quotient_program + mstore(add(payload, 0x26a0), 0xa097200d000b080000210397a07d03080002156794a06894c06994e07e95007f) // quotient_program + mstore(add(payload, 0x26c0), 0x95206a95406b95606c95808095a08195c06a95e06b96006c9620829640839660) // quotient_program + mstore(add(payload, 0x26e0), 0x8496808596a08296c08396e084970085972094a06d94a06894c06994e07e9500) // quotient_program + mstore(add(payload, 0x2700), 0x7f95208095a08195c095406a95e06b96006c96208296408396608496808596a0) // quotient_program + mstore(add(payload, 0x2720), 0x95606b95e06c96008296208396408496608596808696a095806c95e082960083) // quotient_program + mstore(add(payload, 0x2740), 0x96208496408596608696808796a096c08295e083960084962085964086966087) // quotient_program + mstore(add(payload, 0x2760), 0x96808896a096e08395e08496008596208696408796608896808996a097008495) // quotient_program + mstore(add(payload, 0x2780), 0xe08596008696208796408896608996808a96a097208595e08696008796208896) // quotient_program + mstore(add(payload, 0x27a0), 0x408996608a96808b96a01897401997806e94c094c07e94c094e07f94c0950080) // quotient_program + mstore(add(payload, 0x27c0), 0x94c095208194c095a08c94c095c08d94e094e08094e095008194e095208c94e0) // quotient_program + mstore(add(payload, 0x27e0), 0x95a08e94e095c08f950095008c950095208e950095a090950095c09195209520) // quotient_program + mstore(add(payload, 0x2800), 0x90952095a092952095c09395a095a09495a095c09595c095c00d000b08000121) // quotient_program + mstore(add(payload, 0x2820), 0x0397a09603080001156794a06894c06994e09795009895206a95406b95606c95) // quotient_program + mstore(add(payload, 0x2840), 0x809995a09a95c06a95e06b96006c96209b96409c96609d96809e96a09b96c09c) // quotient_program + mstore(add(payload, 0x2860), 0x96e09d97009e972094a06d94a06894c06994e09795009895209995a09a95c095) // quotient_program + mstore(add(payload, 0x2880), 0x406a95e06b96006c96209b96409c96609d96809e96a095606b95e06c96009b96) // quotient_program + mstore(add(payload, 0x28a0), 0x209c96409d96609e96809f96a095806c95e09b96009c96209d96409e96609f96) // quotient_program + mstore(add(payload, 0x28c0), 0x80a096a096c09b95e09c96009d96209e96409f9660a09680a196a096e09c95e0) // quotient_program + mstore(add(payload, 0x28e0), 0x9d96009e96209f9640a09660a19680a296a097009d95e09e96009f9620a09640) // quotient_program + mstore(add(payload, 0x2900), 0xa19660a29680a396a097209e95e09f9600a09620a19640a29660a39680a496a0) // quotient_program + mstore(add(payload, 0x2920), 0x2997406e94c094c09794c094e09894c095009994c095209a94c095a0a594c095) // quotient_program + mstore(add(payload, 0x2940), 0xc0a694e094e09994e095009a94e09520a594e095a0a794e095c0a895009500a5) // quotient_program + mstore(add(payload, 0x2960), 0x95009520a7950095a0a9950095c0aa95209520a9952095a0ab952095c0ac95a0) // quotient_program + mstore(add(payload, 0x2980), 0x95a0ad95a095c0ae95c095c00d000b080001210397a0af0000000e100094a005) // quotient_program + mstore(add(payload, 0x29a0), 0x94c00694e06a95406b95606c95800095e00596000696203097403197600097c0) // quotient_program + mstore(add(payload, 0x29c0), 0x0597e00698000b954095406b954095606c954095800d95609560749560972074) // quotient_program + mstore(add(payload, 0x29e0), 0x9580970075958097207496c096e07596c097007996c097203396e096e07996e0) // quotient_program + mstore(add(payload, 0x2a00), 0x97007a96e0972035970097007b9700972039972097200d000b090000210397a0) // quotient_program + mstore(add(payload, 0x2a20), 0xb004010002150094a00594c00694e00795000895206a95406b95606c95800995) // quotient_program + mstore(add(payload, 0x2a40), 0xa00a95c00095e00596000696200796400896600996800a96a08296c08396e084) // quotient_program + mstore(add(payload, 0x2a60), 0x97008597200097c00597e00698000798200898400998600a988095400b95406b) // quotient_program + mstore(add(payload, 0x2a80), 0x95606c95808296c08396e08497008597201897401997800d9560956082956095) // quotient_program + mstore(add(payload, 0x2aa0), 0x8083956096c084956096e0859560970086956097200f9580958084958096c085) // quotient_program + mstore(add(payload, 0x2ac0), 0x958096e0869580970087958097201196c096c08696c096e08796c097008896c0) // quotient_program + mstore(add(payload, 0x2ae0), 0x97204696e096e08896e097008996e0972048970097008a970097204a97209720) // quotient_program + mstore(add(payload, 0x2b00), 0x0d000b090001210397a0b104010001150094a00594c00694e01b95001c95206a) // quotient_program + mstore(add(payload, 0x2b20), 0x95406b95606c95801d95a01e95c00095e00596000696201b96401c96601d9680) // quotient_program + mstore(add(payload, 0x2b40), 0x1e96a09b96c09c96e09d97009e97200097c00597e00698001b98201c98401d98) // quotient_program + mstore(add(payload, 0x2b60), 0x601e988095400b95406b95606c95809b96c09c96e09d97009e97202997400d95) // quotient_program + mstore(add(payload, 0x2b80), 0x6095609b956095809c956096c09d956096e09e956097009f9560972020958095) // quotient_program + mstore(add(payload, 0x2ba0), 0x809d958096c09e958096e09f95809700a0958097202296c096c09f96c096e0a0) // quotient_program + mstore(add(payload, 0x2bc0), 0x96c09700a196c097205696e096e0a196e09700a296e097205897009700a39700) // quotient_program + mstore(add(payload, 0x2be0), 0x97205a972097200d000b090001191f0000000000000000000000000000000000) // quotient_program // Fixed-column commitment 0, stored as one // EIP-2537 padded uncompressed G1 slot. mstore(add(payload, 0x2c00), 0x00000000000000000000000000000000055f7961345dce7ce57401dd993cc81a) // fixed_comms[0].x_hi diff --git a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol index e5f1b4029..8a2e5f8cf 100644 --- a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol @@ -43,7 +43,7 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 4576; uint256 internal constant EXPECTED_VK_LENGTH = 4577; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0xf2c10777bfd922a3700fb18f2e9a2894dfa51dd920f6261bba71ed8b5363e495; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x233a1a8063ef1a9bfd30983d3034d3d90ab75ff6ac95c0e72d770bf08dcff51b; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -55,8 +55,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0x1164; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -64,84 +64,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x1980; - uint256 internal constant VK_DIGEST_MPTR = 0x1980; - uint256 internal constant NUM_INSTANCES_MPTR = 0x19a0; - uint256 internal constant K_MPTR = 0x19c0; - uint256 internal constant N_INV_MPTR = 0x19e0; - uint256 internal constant OMEGA_MPTR = 0x1a00; - uint256 internal constant OMEGA_INV_MPTR = 0x1a20; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x1a40; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x1a60; - uint256 internal constant ACC_OFFSET_MPTR = 0x1a80; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x1aa0; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x1ac0; - uint256 internal constant G1_BASE_MPTR = 0x1ae0; - uint256 internal constant G2_BASE_MPTR = 0x1b60; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x1c60; - - uint256 internal constant CHALLENGE_MPTR = 0x2b60; + uint256 internal constant VK_MPTR = 0x2900; + uint256 internal constant VK_DIGEST_MPTR = 0x2900; + uint256 internal constant NUM_INSTANCES_MPTR = 0x2920; + uint256 internal constant K_MPTR = 0x2940; + uint256 internal constant N_INV_MPTR = 0x2960; + uint256 internal constant OMEGA_MPTR = 0x2980; + uint256 internal constant OMEGA_INV_MPTR = 0x29a0; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x29c0; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x29e0; + uint256 internal constant ACC_OFFSET_MPTR = 0x2a00; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2a20; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x2a40; + uint256 internal constant G1_BASE_MPTR = 0x2a60; + uint256 internal constant G2_BASE_MPTR = 0x2ae0; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x2be0; + + uint256 internal constant CHALLENGE_MPTR = 0x3ae0; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x2b60; - uint256 internal constant BETA_MPTR = 0x2b80; - uint256 internal constant GAMMA_MPTR = 0x2ba0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x2bc0; - uint256 internal constant Y_MPTR = 0x2be0; - uint256 internal constant X_MPTR = 0x2c00; - uint256 internal constant X1_MPTR = 0x2c20; - uint256 internal constant X2_MPTR = 0x2c40; - uint256 internal constant X3_MPTR = 0x2c60; - uint256 internal constant X4_MPTR = 0x2c80; + uint256 internal constant THETA_MPTR = 0x3ae0; + uint256 internal constant BETA_MPTR = 0x3b00; + uint256 internal constant GAMMA_MPTR = 0x3b20; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x3b40; + uint256 internal constant Y_MPTR = 0x3b60; + uint256 internal constant X_MPTR = 0x3b80; + uint256 internal constant X1_MPTR = 0x3ba0; + uint256 internal constant X2_MPTR = 0x3bc0; + uint256 internal constant X3_MPTR = 0x3be0; + uint256 internal constant X4_MPTR = 0x3c00; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x2ca0; - uint256 internal constant PI_MPTR = 0x2d20; + uint256 internal constant F_COM_MPTR = 0x3c20; + uint256 internal constant PI_MPTR = 0x3ca0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x2da0; - uint256 internal constant ACC_RHS_MPTR = 0x2e20; + uint256 internal constant ACC_LHS_MPTR = 0x3d20; + uint256 internal constant ACC_RHS_MPTR = 0x3da0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x2ea0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x2ec0; - uint256 internal constant L_LAST_MPTR = 0x2ee0; - uint256 internal constant L_BLIND_MPTR = 0x2f00; - uint256 internal constant L_0_MPTR = 0x2f20; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x2f40; + uint256 internal constant X_N_MPTR = 0x3e20; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x3e40; + uint256 internal constant L_LAST_MPTR = 0x3e60; + uint256 internal constant L_BLIND_MPTR = 0x3e80; + uint256 internal constant L_0_MPTR = 0x3ea0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x3ec0; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x2f60; - uint256 internal constant QUOTIENT_MPTR = 0x2f80; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x3020; - uint256 internal constant V_MPTR = 0x3040; - uint256 internal constant FINAL_COM_MPTR = 0x3060; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x30e0; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x3160; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x3ee0; + uint256 internal constant QUOTIENT_MPTR = 0x3f00; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x3fa0; + uint256 internal constant V_MPTR = 0x3fc0; + uint256 internal constant FINAL_COM_MPTR = 0x3fe0; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x4060; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x40e0; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x31e0; - uint256 internal constant X1_POWERS_MPTR = 0x3560; + uint256 internal constant ROT_POINTS_MPTR = 0x4160; + uint256 internal constant X1_POWERS_MPTR = 0x44e0; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x3d80; - uint256 internal constant Q_EVAL_SET_MPTR = 0x3d80; + uint256 internal constant Q_COM_MPTR = 0x4d00; + uint256 internal constant Q_EVAL_SET_MPTR = 0x4d00; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x4480; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x5400; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x4580; + uint256 internal constant G1_IDENTITY_MPTR = 0x5500; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -149,11 +149,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x46e0; - uint256 internal constant SELECTOR_ACC_MPTR = 0x5660; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x5660; - uint256 internal constant TRACE_U256_MPTR = 0x7440; + uint256 internal constant REVERSED_EVALS_MPTR = 0x5660; + uint256 internal constant SELECTOR_ACC_MPTR = 0x65e0; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x65e0; + uint256 internal constant TRACE_U256_MPTR = 0x83c0; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -170,13 +170,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x4ce0; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x50e0; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x5160; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x52e0; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x5360; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x53e0; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x5460; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x5c60; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x6060; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x60e0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x6260; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x62e0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x6360; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x63e0; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -198,7 +198,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -222,11 +222,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0x5660 + let msm_scratch := 0x65e0 for { let off := 0 } lt(off, 0x1d60) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -329,7 +362,7 @@ contract Halo2Verifier { // guards against a future emitter passing a raw scalar. if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x1880 + let p := 0x2800 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -566,7 +599,11 @@ contract Halo2Verifier { mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -598,7 +635,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -1386,15 +1429,15 @@ contract Halo2Verifier { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x1d60 + let q_const_mptr := 0x2ce0 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x1d80 + let q_program_mptr := 0x2d00 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0x56e0, 0) + mstore(0x6660, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -1415,13 +1458,13 @@ contract Halo2Verifier { // block multiplies by mload(selector_power_mptr + offset) // unconditionally -- so initialize the slot rather than // leaving correctness to two filters in another file. - mstore(0x5720, 1) + mstore(0x66a0, 1) // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 22) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0x5720, shl(5, q_y_power_i)), q_y_power) + mstore(add(0x66a0, shl(5, q_y_power_i)), q_y_power) } } @@ -1430,102 +1473,102 @@ contract Halo2Verifier { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x4980) - let f_4 := mload(0x4880) - let a_0 := mload(0x4700) + let f_3 := mload(0x5900) + let f_4 := mload(0x5800) + let a_0 := mload(0x5680) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x48a0) - let a_1 := mload(0x4720) + let f_5 := mload(0x5820) + let a_1 := mload(0x56a0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x48c0) - let a_2 := mload(0x4740) + let f_6 := mload(0x5840) + let a_2 := mload(0x56c0) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x48e0) - let a_3 := mload(0x4760) + let f_7 := mload(0x5860) + let a_3 := mload(0x56e0) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x4900) - let a_4 := mload(0x4780) + let f_8 := mload(0x5880) + let a_4 := mload(0x5700) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x4920) - let a_0_next_1 := mload(0x47a0) + let f_0 := mload(0x58a0) + let a_0_next_1 := mload(0x5720) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x4940) + let f_1 := mload(0x58c0) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x4960) + let f_2 := mload(0x58e0) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0x59e0, var19) + mstore(0x6960, var19) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } { let var0 := 0x1 - let a_1 := mload(0x4720) - let a_2 := mload(0x4740) + let a_1 := mload(0x56a0) + let a_2 := mload(0x56c0) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0x59e0, var6) + mstore(0x6960, var6) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } { let var0 := 0x1 - let a_0 := mload(0x4700) - let f_4 := mload(0x4880) + let a_0 := mload(0x5680) + let f_4 := mload(0x5800) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x47a0) + let a_0_next_1 := mload(0x5720) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x59e0, var4) + mstore(0x6960, var4) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } { let var0 := 0x1 - let a_1 := mload(0x4720) - let f_5 := mload(0x48a0) + let a_1 := mload(0x56a0) + let f_5 := mload(0x5820) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x47c0) + let a_1_next_1 := mload(0x5740) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x59e0, var4) + mstore(0x6960, var4) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5720, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x66a0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } // VM registers: @@ -1545,7 +1588,7 @@ contract Halo2Verifier { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x58) // q_sp starts at the first free stack word. - let q_sp := 0x59e0 + let q_sp := 0x6960 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -1645,69 +1688,69 @@ contract Halo2Verifier { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0x59e0 + q_sp := 0x6960 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0x59e0 - let q_perm_sigmas := 0x5ae0 - let q_perm_z_cur := 0x5be0 - let q_perm_z_next := 0x5c40 - let q_perm_z_last := 0x5ca0 - let q_perm_delta_base_ptr := 0x5ce0 + let q_perm_vals := 0x6960 + let q_perm_sigmas := 0x6a60 + let q_perm_z_cur := 0x6b60 + let q_perm_z_next := 0x6bc0 + let q_perm_z_last := 0x6c20 + let q_perm_delta_base_ptr := 0x6c60 let q_perm_num_cols := 8 let q_perm_num_sets := 3 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x4860)) + mstore(add(q_perm_vals, 0x0), mload(0x57e0)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x4700, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x5680, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x46e0)) + mstore(add(q_perm_vals, 0xc0), mload(0x5660)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 8) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x4a40, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x59c0, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 3) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x4b40, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x5ac0, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 3) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x4b60, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x5ae0, q_perm_z_next_load_src_off))) } } - mstore(add(q_perm_z_last, 0x0), mload(0x4b80)) - mstore(add(q_perm_z_last, 0x20), mload(0x4be0)) + mstore(add(q_perm_z_last, 0x0), mload(0x5b00)) + mstore(add(q_perm_z_last, 0x20), mload(0x5b60)) let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_perm_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0x40)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_perm_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 3) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_perm_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 3) { q_perm_set := add(q_perm_set, 1) } { @@ -1726,8 +1769,8 @@ contract Halo2Verifier { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_perm_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -1748,13 +1791,13 @@ contract Halo2Verifier { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0x59e0 + q_sp := 0x6960 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0x59e0 - let q_lookup_prefix := 0x5a00 - let q_lookup_suffix := 0x5a20 + let q_lookup_f := 0x6960 + let q_lookup_prefix := 0x6980 + let q_lookup_suffix := 0x69a0 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -1764,33 +1807,33 @@ contract Halo2Verifier { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x4c80), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x5c00), r) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_lookup_eval, r)) } { - let f_10 := mload(0x49a0) + let f_10 := mload(0x5920) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_1, r) - let q_lookup_eval := addmod(mulmod(mload(0x4c60), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x5be0), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x4c60) - let f_16 := mload(0x4a00) - let f_11 := mload(0x49c0) + let q_lookup_sum_h := mload(0x5be0) + let f_16 := mload(0x5980) + let f_11 := mload(0x5940) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x49e0) + let f_12 := mload(0x5960) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_16, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x4ca0), sub(r, addmod(mload(0x4c80), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x5c20), sub(r, addmod(mload(0x5c00), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x4c40), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x5bc0), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_lookup_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_lookup_eval, r)) } } } @@ -1811,124 +1854,124 @@ contract Halo2Verifier { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0x59e0 + q_sp := 0x6960 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 - let f_0 := mload(0x4920) - let a_0_next_1 := mload(0x47a0) + let f_0 := mload(0x58a0) + let a_0_next_1 := mload(0x5720) let var1 := addmod(0, sub(r, a_0_next_1), r) let var2 := addmod(f_0, var1, r) let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace - let a_0 := mload(0x4700) + let a_0 := mload(0x5680) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_2 := mload(0x4740) + let a_2 := mload(0x56c0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x4800) + let a_5 := mload(0x5780) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x59e0, var18) + mstore(0x6960, var18) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5720, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x66a0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } } case 1 { { let var0 := 0x1 - let f_1 := mload(0x4940) - let a_1_next_1 := mload(0x47c0) + let f_1 := mload(0x58c0) + let a_1_next_1 := mload(0x5740) let var1 := addmod(0, sub(r, a_1_next_1), r) let var2 := addmod(f_1, var1, r) let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 - let a_0 := mload(0x4700) + let a_0 := mload(0x5680) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f - let a_2 := mload(0x4740) + let a_2 := mload(0x56c0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x4800) + let a_5 := mload(0x5780) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x59e0, var18) + mstore(0x6960, var18) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5720, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x66a0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } } case 2 { { let var0 := 0x1 - let f_2 := mload(0x4960) - let a_2_next_1 := mload(0x47e0) + let f_2 := mload(0x58e0) + let a_2_next_1 := mload(0x5760) let var1 := addmod(0, sub(r, a_2_next_1), r) let var2 := addmod(f_2, var1, r) let var3 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 - let a_0 := mload(0x4700) + let a_0 := mload(0x5680) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db - let a_2 := mload(0x4740) + let a_2 := mload(0x56c0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x4800) + let a_5 := mload(0x5780) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x59e0, var18) + mstore(0x6960, var18) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5720, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x66a0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } } default { revert(0, 0) } @@ -1952,14 +1995,14 @@ contract Halo2Verifier { // The global fully-evaluated accumulator is still // multiplied by y so later main identities land at the // same y powers as Rust's reverse fold. - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) let q_sel_acc := mload(q_target_ptr) if q_sel_gap { // Selector buckets are sparse in the global // identity stream. Precomputed y^gap advances only // this selector's local accumulator. - q_sel_acc := mulmod(q_sel_acc, mload(add(0x5720, shl(5, q_sel_gap))), r) + q_sel_acc := mulmod(q_sel_acc, mload(add(0x66a0, shl(5, q_sel_gap))), r) } mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) } @@ -1979,7 +2022,7 @@ contract Halo2Verifier { // top, leaving abandoned words below q_sp with q_has_top // clear -- so both checks above pass while an operand of the // identity has been silently dropped from nu_y(x). - if iszero(eq(q_sp, 0x59e0)) { revert(0, 0) } + if iszero(eq(q_sp, 0x6960)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -1991,52 +2034,52 @@ contract Halo2Verifier { { let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) { - let f_0 := mload(0x4920) - let a_0_next_1 := mload(0x47a0) + let f_0 := mload(0x58a0) + let a_0_next_1 := mload(0x5720) let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 let var1 := mulmod(a_0_next_1, var0, r) let var2 := addmod(f_0, var1, r) let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 - let a_0 := mload(0x4700) + let a_0 := mload(0x5680) let var4 := mulmod(var3, a_0, r) let var5 := addmod(var2, var4, r) let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var7 := mulmod(var6, a_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 - let a_2 := mload(0x4740) + let a_2 := mload(0x56c0) let var10 := q_pow5(a_2) let var11 := mulmod(var9, var10, r) let var12 := addmod(var8, var11, r) let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var14 := q_pow5(a_3) let var15 := mulmod(var13, var14, r) let var16 := addmod(var12, var15, r) let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var18 := q_pow5(a_4) let var19 := mulmod(var17, var18, r) let var20 := addmod(var16, var19, r) let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e - let a_5 := mload(0x4800) + let a_5 := mload(0x5780) let var22 := q_pow5(a_5) let var23 := mulmod(var21, var22, r) let var24 := addmod(var20, var23, r) let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 - let a_6 := mload(0x4820) + let a_6 := mload(0x57a0) let var26 := q_pow5(a_6) let var27 := mulmod(var25, var26, r) let var28 := addmod(var24, var27, r) let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_7 := mload(0x4840) + let a_7 := mload(0x57c0) let var30 := q_pow5(a_7) let var31 := mulmod(var29, var30, r) let var32 := addmod(var28, var31, r) let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) - let f_1 := mload(0x4940) - let a_1_next_1 := mload(0x47c0) + let f_1 := mload(0x58c0) + let a_1_next_1 := mload(0x5740) let var34 := mulmod(a_1_next_1, var0, r) let var35 := addmod(f_1, var34, r) let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d @@ -2064,7 +2107,7 @@ contract Halo2Verifier { let var58 := mulmod(var57, var30, r) let var59 := addmod(var56, var58, r) let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) - let f_2 := mload(0x4960) + let f_2 := mload(0x58e0) let var61 := mulmod(a_3, var0, r) let var62 := addmod(f_2, var61, r) let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 @@ -2077,7 +2120,7 @@ contract Halo2Verifier { let var70 := mulmod(var69, var10, r) let var71 := addmod(var68, var70, r) let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) - let f_3 := mload(0x4980) + let f_3 := mload(0x5900) let var73 := mulmod(a_4, var0, r) let var74 := addmod(f_3, var73, r) let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 @@ -2092,7 +2135,7 @@ contract Halo2Verifier { let var84 := mulmod(var69, var14, r) let var85 := addmod(var83, var84, r) let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) - let f_4 := mload(0x4880) + let f_4 := mload(0x5800) let var87 := mulmod(a_5, var0, r) let var88 := addmod(f_4, var87, r) let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b @@ -2109,7 +2152,7 @@ contract Halo2Verifier { let var100 := mulmod(var69, var18, r) let var101 := addmod(var99, var100, r) let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) - let f_5 := mload(0x48a0) + let f_5 := mload(0x5820) let var103 := mulmod(a_6, var0, r) let var104 := addmod(f_5, var103, r) let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 @@ -2128,7 +2171,7 @@ contract Halo2Verifier { let var118 := mulmod(var69, var22, r) let var119 := addmod(var117, var118, r) let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) - let f_6 := mload(0x48c0) + let f_6 := mload(0x5840) let var121 := mulmod(a_7, var0, r) let var122 := addmod(f_6, var121, r) let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a @@ -2149,8 +2192,8 @@ contract Halo2Verifier { let var138 := mulmod(var69, var26, r) let var139 := addmod(var137, var138, r) let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) - let f_7 := mload(0x48e0) - let a_2_next_1 := mload(0x47e0) + let f_7 := mload(0x5860) + let a_2_next_1 := mload(0x5760) let var141 := mulmod(a_2_next_1, var0, r) let var142 := addmod(f_7, var141, r) let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 @@ -2173,12 +2216,12 @@ contract Halo2Verifier { let var160 := mulmod(var69, var30, r) let var161 := addmod(var159, var160, r) let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) - let f_18 := mload(0x4a20) + let f_18 := mload(0x59a0) let q_trash_one_minus_selector := addmod(1, sub(r, f_18), r) - let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x4cc0), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x5c40), r) let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_trash_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_trash_eval, r)) } } // Finish selector buckets by applying the codegen-known tail @@ -2190,25 +2233,25 @@ contract Halo2Verifier { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5720, 0x02a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x66a0, 0x02a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5720, 0x0280)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x66a0, 0x0280)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5720, 0x0220)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x66a0, 0x0220)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5720, 0x0160)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x66a0, 0x0160)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0x56e0)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0x6660)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } @@ -2310,42 +2353,42 @@ contract Halo2Verifier { { // q_eval_set[0]: 33 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x56e0, 0x4760) - mstore(0x5700, 0x4780) - mstore(0x5720, 0x4800) - mstore(0x5740, 0x4820) - mstore(0x5760, 0x4840) - mstore(0x5780, 0x46e0) - mstore(0x57a0, 0x4c40) - mstore(0x57c0, 0x4c60) - mstore(0x57e0, 0x4cc0) - mstore(0x5800, 0x4860) - mstore(0x5820, 0x4880) - mstore(0x5840, 0x48a0) - mstore(0x5860, 0x48c0) - mstore(0x5880, 0x48e0) - mstore(0x58a0, 0x4900) - mstore(0x58c0, 0x4920) - mstore(0x58e0, 0x4940) - mstore(0x5900, 0x4960) - mstore(0x5920, 0x4980) - mstore(0x5940, 0x49a0) - mstore(0x5960, 0x49c0) - mstore(0x5980, 0x49e0) - mstore(0x59a0, 0x4a00) - mstore(0x59c0, 0x4a20) - mstore(0x59e0, 0x4a40) - mstore(0x5a00, 0x4a60) - mstore(0x5a20, 0x4a80) - mstore(0x5a40, 0x4aa0) - mstore(0x5a60, 0x4ac0) - mstore(0x5a80, 0x4ae0) - mstore(0x5aa0, 0x4b00) - mstore(0x5ac0, 0x4b20) - mstore(0x5ae0, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x4760) + mstore(0x6660, 0x56e0) + mstore(0x6680, 0x5700) + mstore(0x66a0, 0x5780) + mstore(0x66c0, 0x57a0) + mstore(0x66e0, 0x57c0) + mstore(0x6700, 0x5660) + mstore(0x6720, 0x5bc0) + mstore(0x6740, 0x5be0) + mstore(0x6760, 0x5c40) + mstore(0x6780, 0x57e0) + mstore(0x67a0, 0x5800) + mstore(0x67c0, 0x5820) + mstore(0x67e0, 0x5840) + mstore(0x6800, 0x5860) + mstore(0x6820, 0x5880) + mstore(0x6840, 0x58a0) + mstore(0x6860, 0x58c0) + mstore(0x6880, 0x58e0) + mstore(0x68a0, 0x5900) + mstore(0x68c0, 0x5920) + mstore(0x68e0, 0x5940) + mstore(0x6900, 0x5960) + mstore(0x6920, 0x5980) + mstore(0x6940, 0x59a0) + mstore(0x6960, 0x59c0) + mstore(0x6980, 0x59e0) + mstore(0x69a0, 0x5a00) + mstore(0x69c0, 0x5a20) + mstore(0x69e0, 0x5a40) + mstore(0x6a00, 0x5a60) + mstore(0x6a20, 0x5a80) + mstore(0x6a40, 0x5aa0) + mstore(0x6a60, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x56e0) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x56e0, 0x20) + let eval_p := add(0x6660, 0x20) for { let i := 1 } lt(i, 0x21) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2360,20 +2403,20 @@ contract Halo2Verifier { { // q_eval_set[1]: 5 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x56e0, 0x4700) - mstore(0x5700, 0x47a0) - mstore(0x5720, 0x4720) - mstore(0x5740, 0x47c0) - mstore(0x5760, 0x4740) - mstore(0x5780, 0x47e0) - mstore(0x57a0, 0x4c00) - mstore(0x57c0, 0x4c20) - mstore(0x57e0, 0x4c80) - mstore(0x5800, 0x4ca0) - let q_eval_set_0 := mload(0x4700) - let q_eval_set_1 := mload(0x47a0) + mstore(0x6660, 0x5680) + mstore(0x6680, 0x5720) + mstore(0x66a0, 0x56a0) + mstore(0x66c0, 0x5740) + mstore(0x66e0, 0x56c0) + mstore(0x6700, 0x5760) + mstore(0x6720, 0x5b80) + mstore(0x6740, 0x5ba0) + mstore(0x6760, 0x5c00) + mstore(0x6780, 0x5c20) + let q_eval_set_0 := mload(0x5680) + let q_eval_set_1 := mload(0x5720) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x56e0, 0x40) + let eval_p := add(0x6660, 0x40) for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2389,12 +2432,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[2]: 2 commitment(s) - let q_eval_set_0 := mload(0x4b40) - let q_eval_set_1 := mload(0x4b60) - let q_eval_set_2 := mload(0x4b80) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x4ba0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x4bc0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x4be0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + let q_eval_set_0 := mload(0x5ac0) + let q_eval_set_1 := mload(0x5ae0) + let q_eval_set_2 := mload(0x5b00) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x5b20), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x5b40), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x5b60), mload(add(X1_POWERS_MPTR, 0x20)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) mstore(add(Q_EVAL_SET_MPTR, 0xa0), q_eval_set_2) @@ -2514,107 +2557,107 @@ contract Halo2Verifier { v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_3, r), r) - mcopy(0x56e0, 0x4e60, 0x80) - mstore(0x5760, 1) - mcopy(0x5780, 0x4ee0, 0x80) - mstore(0x5800, mload(add(X1_POWERS_MPTR, 0x20))) - mcopy(0x5820, 0x4f60, 0x80) - mstore(0x58a0, mload(add(X1_POWERS_MPTR, 0x40))) - mcopy(0x58c0, 0x4fe0, 0x80) - mstore(0x5940, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0x5960, 0x5060, 0x80) - mstore(0x59e0, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0x5a00, 0x50e0, 0x80) - mstore(0x5a80, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0x5aa0, 0x52e0, 0x80) - mstore(0x5b20, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0x5b40, 0x53e0, 0x80) - mstore(0x5bc0, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0x5be0, 0x2260, 0x80) - mstore(0x5c60, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0x5c80, 0x1fe0, 0x80) - mstore(0x5d00, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0x5d20, 0x2060, 0x80) - mstore(0x5da0, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0x5dc0, 0x20e0, 0x80) - mstore(0x5e40, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0x5e60, 0x2160, 0x80) - mstore(0x5ee0, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0x5f00, 0x21e0, 0x80) - mstore(0x5f80, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0x5fa0, 0x1de0, 0x80) - mstore(0x6020, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0x6040, 0x1e60, 0x80) - mstore(0x60c0, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0x60e0, 0x1ee0, 0x80) - mstore(0x6160, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0x6180, 0x1f60, 0x80) - mstore(0x6200, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0x6220, 0x22e0, 0x80) - mstore(0x62a0, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0x62c0, 0x2360, 0x80) - mstore(0x6340, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0x6360, 0x23e0, 0x80) - mstore(0x63e0, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0x6400, 0x25e0, 0x80) - mstore(0x6480, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0x64a0, 0x26e0, 0x80) - mstore(0x6520, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0x6540, 0x2760, 0x80) - mstore(0x65c0, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0x65e0, 0x27e0, 0x80) - mstore(0x6660, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0x6680, 0x2860, 0x80) - mstore(0x6700, mload(add(X1_POWERS_MPTR, 0x340))) - mcopy(0x6720, 0x28e0, 0x80) - mstore(0x67a0, mload(add(X1_POWERS_MPTR, 0x360))) - mcopy(0x67c0, 0x2960, 0x80) - mstore(0x6840, mload(add(X1_POWERS_MPTR, 0x380))) - mcopy(0x6860, 0x29e0, 0x80) - mstore(0x68e0, mload(add(X1_POWERS_MPTR, 0x3a0))) - mcopy(0x6900, 0x2a60, 0x80) - mstore(0x6980, mload(add(X1_POWERS_MPTR, 0x3c0))) - mcopy(0x69a0, 0x2ae0, 0x80) - mstore(0x6a20, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0x6660, 0x5de0, 0x80) + mstore(0x66e0, 1) + mcopy(0x6700, 0x5e60, 0x80) + mstore(0x6780, mload(add(X1_POWERS_MPTR, 0x20))) + mcopy(0x67a0, 0x5ee0, 0x80) + mstore(0x6820, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0x6840, 0x5f60, 0x80) + mstore(0x68c0, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0x68e0, 0x5fe0, 0x80) + mstore(0x6960, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0x6980, 0x6060, 0x80) + mstore(0x6a00, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0x6a20, 0x6260, 0x80) + mstore(0x6aa0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0x6ac0, 0x6360, 0x80) + mstore(0x6b40, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0x6b60, 0x31e0, 0x80) + mstore(0x6be0, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0x6c00, 0x2f60, 0x80) + mstore(0x6c80, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0x6ca0, 0x2fe0, 0x80) + mstore(0x6d20, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0x6d40, 0x3060, 0x80) + mstore(0x6dc0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0x6de0, 0x30e0, 0x80) + mstore(0x6e60, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0x6e80, 0x3160, 0x80) + mstore(0x6f00, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0x6f20, 0x2d60, 0x80) + mstore(0x6fa0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0x6fc0, 0x2de0, 0x80) + mstore(0x7040, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0x7060, 0x2e60, 0x80) + mstore(0x70e0, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0x7100, 0x2ee0, 0x80) + mstore(0x7180, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0x71a0, 0x3260, 0x80) + mstore(0x7220, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0x7240, 0x32e0, 0x80) + mstore(0x72c0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0x72e0, 0x3360, 0x80) + mstore(0x7360, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0x7380, 0x3560, 0x80) + mstore(0x7400, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0x7420, 0x3660, 0x80) + mstore(0x74a0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0x74c0, 0x36e0, 0x80) + mstore(0x7540, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0x7560, 0x3760, 0x80) + mstore(0x75e0, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0x7600, 0x37e0, 0x80) + mstore(0x7680, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0x76a0, 0x3860, 0x80) + mstore(0x7720, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0x7740, 0x38e0, 0x80) + mstore(0x77c0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0x77e0, 0x3960, 0x80) + mstore(0x7860, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0x7880, 0x39e0, 0x80) + mstore(0x7900, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0x7920, 0x3a60, 0x80) + mstore(0x79a0, mload(add(X1_POWERS_MPTR, 0x3e0))) let lin_query_scalar_31 := mload(add(X1_POWERS_MPTR, 0x400)) let lin_cur_scalar_31 := mulmod(lin_query_scalar_31, lin_one_minus_x_n, r) - mcopy(0x6a40, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0x6ac0, lin_cur_scalar_31) + mcopy(0x79c0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0x7a40, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x6ae0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0x6b60, lin_cur_scalar_31) + mcopy(0x7a60, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0x7ae0, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x6b80, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0x6c00, lin_cur_scalar_31) + mcopy(0x7b00, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0x7b80, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x6c20, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0x6ca0, lin_cur_scalar_31) - mcopy(0x6cc0, 0x2460, 0x80) - mstore(0x6d40, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0x6d60, 0x24e0, 0x80) - mstore(0x6de0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0x6e00, 0x2560, 0x80) - mstore(0x6e80, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0x6ea0, 0x2660, 0x80) - mstore(0x6f20, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) - mcopy(0x6f40, 0x4ce0, 0x80) - mstore(0x6fc0, x4_pow_1) - mcopy(0x6fe0, 0x4d60, 0x80) - mstore(0x7060, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0x7080, 0x4de0, 0x80) - mstore(0x7100, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0x7120, 0x5260, 0x80) - mstore(0x71a0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_1, r)) - mcopy(0x71c0, 0x5360, 0x80) - mstore(0x7240, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_1, r)) - mcopy(0x7260, 0x5160, 0x80) - mstore(0x72e0, x4_pow_2) - mcopy(0x7300, 0x51e0, 0x80) - mstore(0x7380, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0x73a0, F_COM_MPTR, 0x80) - mstore(0x7420, x4_pow_3) + mcopy(0x7ba0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0x7c20, lin_cur_scalar_31) + mcopy(0x7c40, 0x33e0, 0x80) + mstore(0x7cc0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0x7ce0, 0x3460, 0x80) + mstore(0x7d60, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0x7d80, 0x34e0, 0x80) + mstore(0x7e00, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0x7e20, 0x35e0, 0x80) + mstore(0x7ea0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0x7ec0, 0x5c60, 0x80) + mstore(0x7f40, x4_pow_1) + mcopy(0x7f60, 0x5ce0, 0x80) + mstore(0x7fe0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0x8000, 0x5d60, 0x80) + mstore(0x8080, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0x80a0, 0x61e0, 0x80) + mstore(0x8120, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_1, r)) + mcopy(0x8140, 0x62e0, 0x80) + mstore(0x81c0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_1, r)) + mcopy(0x81e0, 0x60e0, 0x80) + mstore(0x8260, x4_pow_2) + mcopy(0x8280, 0x6160, 0x80) + mstore(0x8300, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0x8320, F_COM_MPTR, 0x80) + mstore(0x83a0, x4_pow_3) if success { - success := staticcall(gas(), 0x0c, 0x56e0, 0x1d60, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0x6660, 0x1d60, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -2626,28 +2669,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } diff --git a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2VerifyingKey.sol index 895f2a4d2..1ee722abf 100644 --- a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2VerifyingKey.sol @@ -94,9 +94,9 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x03a0), 0x00000000000000000000000000000000006d57f79a18220d1e5ef04bd519e995) // neg_s_g2_y_c1_hi mstore(add(payload, 0x03c0), 0x9a9cc71553bb761b5422a6b6971b75c8d3695bfa07b861c4b1c958da426efc45) // neg_s_g2_y_c1_lo mstore(add(payload, 0x03e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // quotient_const - mstore(add(payload, 0x0400), 0x0547401048c00547e008060d000b02000105470011470011470005476008060d) // quotient_program - mstore(add(payload, 0x0420), 0x000b03000005472011472011472005478008060d000b03000105474011474011) // quotient_program - mstore(add(payload, 0x0440), 0x474005480008060d000b0300011b00001b00011b0002191f0000000000000000) // quotient_program + mstore(add(payload, 0x0400), 0x0556c010584005576008060d000b0200010556801156801156800556e008060d) // quotient_program + mstore(add(payload, 0x0420), 0x000b0300000556a01156a01156a005570008060d000b0300010556c01156c011) // quotient_program + mstore(add(payload, 0x0440), 0x56c005578008060d000b0300011b00001b00011b0002191f0000000000000000) // quotient_program // Fixed-column commitment 0, stored as one // EIP-2537 padded uncompressed G1 slot. mstore(add(payload, 0x0460), 0x0000000000000000000000000000000016742a8c4f331d1be5bc8622ba92b271) // fixed_comms[0].x_hi diff --git a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol index d0caa0941..dea9593f8 100644 --- a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol @@ -55,8 +55,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0xec4; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -64,84 +64,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x16e0; - uint256 internal constant VK_DIGEST_MPTR = 0x16e0; - uint256 internal constant NUM_INSTANCES_MPTR = 0x1700; - uint256 internal constant K_MPTR = 0x1720; - uint256 internal constant N_INV_MPTR = 0x1740; - uint256 internal constant OMEGA_MPTR = 0x1760; - uint256 internal constant OMEGA_INV_MPTR = 0x1780; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x17a0; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x17c0; - uint256 internal constant ACC_OFFSET_MPTR = 0x17e0; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x1800; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x1820; - uint256 internal constant G1_BASE_MPTR = 0x1840; - uint256 internal constant G2_BASE_MPTR = 0x18c0; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x19c0; - - uint256 internal constant CHALLENGE_MPTR = 0x2760; + uint256 internal constant VK_MPTR = 0x2660; + uint256 internal constant VK_DIGEST_MPTR = 0x2660; + uint256 internal constant NUM_INSTANCES_MPTR = 0x2680; + uint256 internal constant K_MPTR = 0x26a0; + uint256 internal constant N_INV_MPTR = 0x26c0; + uint256 internal constant OMEGA_MPTR = 0x26e0; + uint256 internal constant OMEGA_INV_MPTR = 0x2700; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x2720; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x2740; + uint256 internal constant ACC_OFFSET_MPTR = 0x2760; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2780; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x27a0; + uint256 internal constant G1_BASE_MPTR = 0x27c0; + uint256 internal constant G2_BASE_MPTR = 0x2840; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x2940; + + uint256 internal constant CHALLENGE_MPTR = 0x36e0; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x2760; - uint256 internal constant BETA_MPTR = 0x2780; - uint256 internal constant GAMMA_MPTR = 0x27a0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x27c0; - uint256 internal constant Y_MPTR = 0x27e0; - uint256 internal constant X_MPTR = 0x2800; - uint256 internal constant X1_MPTR = 0x2820; - uint256 internal constant X2_MPTR = 0x2840; - uint256 internal constant X3_MPTR = 0x2860; - uint256 internal constant X4_MPTR = 0x2880; + uint256 internal constant THETA_MPTR = 0x36e0; + uint256 internal constant BETA_MPTR = 0x3700; + uint256 internal constant GAMMA_MPTR = 0x3720; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x3740; + uint256 internal constant Y_MPTR = 0x3760; + uint256 internal constant X_MPTR = 0x3780; + uint256 internal constant X1_MPTR = 0x37a0; + uint256 internal constant X2_MPTR = 0x37c0; + uint256 internal constant X3_MPTR = 0x37e0; + uint256 internal constant X4_MPTR = 0x3800; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x28a0; - uint256 internal constant PI_MPTR = 0x2920; + uint256 internal constant F_COM_MPTR = 0x3820; + uint256 internal constant PI_MPTR = 0x38a0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x29a0; - uint256 internal constant ACC_RHS_MPTR = 0x2a20; + uint256 internal constant ACC_LHS_MPTR = 0x3920; + uint256 internal constant ACC_RHS_MPTR = 0x39a0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x2aa0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x2ac0; - uint256 internal constant L_LAST_MPTR = 0x2ae0; - uint256 internal constant L_BLIND_MPTR = 0x2b00; - uint256 internal constant L_0_MPTR = 0x2b20; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x2b40; + uint256 internal constant X_N_MPTR = 0x3a20; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x3a40; + uint256 internal constant L_LAST_MPTR = 0x3a60; + uint256 internal constant L_BLIND_MPTR = 0x3a80; + uint256 internal constant L_0_MPTR = 0x3aa0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x3ac0; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x2b60; - uint256 internal constant QUOTIENT_MPTR = 0x2b80; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x2c20; - uint256 internal constant V_MPTR = 0x2c40; - uint256 internal constant FINAL_COM_MPTR = 0x2c60; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x2ce0; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x2d60; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x3ae0; + uint256 internal constant QUOTIENT_MPTR = 0x3b00; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x3ba0; + uint256 internal constant V_MPTR = 0x3bc0; + uint256 internal constant FINAL_COM_MPTR = 0x3be0; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x3c60; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x3ce0; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x2de0; - uint256 internal constant X1_POWERS_MPTR = 0x3160; + uint256 internal constant ROT_POINTS_MPTR = 0x3d60; + uint256 internal constant X1_POWERS_MPTR = 0x40e0; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x3980; - uint256 internal constant Q_EVAL_SET_MPTR = 0x3980; + uint256 internal constant Q_COM_MPTR = 0x4900; + uint256 internal constant Q_EVAL_SET_MPTR = 0x4900; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x4080; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x5000; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x4180; + uint256 internal constant G1_IDENTITY_MPTR = 0x5100; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -149,11 +149,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x42e0; - uint256 internal constant SELECTOR_ACC_MPTR = 0x4fc0; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x4fc0; - uint256 internal constant TRACE_U256_MPTR = 0x7000; + uint256 internal constant REVERSED_EVALS_MPTR = 0x5260; + uint256 internal constant SELECTOR_ACC_MPTR = 0x5f40; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x5f40; + uint256 internal constant TRACE_U256_MPTR = 0x7940; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -170,13 +170,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x4840; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x4ac0; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x4b40; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x4cc0; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x4d40; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x4dc0; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x4dc0; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x57c0; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x5a40; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x5ac0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x5c40; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x5cc0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x5d40; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x5d40; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -198,7 +198,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -222,11 +222,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0x4fc0 + let msm_scratch := 0x5f40 for { let off := 0 } lt(off, 0x19a0) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -329,7 +362,7 @@ contract Halo2Verifier { // guards against a future emitter passing a raw scalar. if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x15e0 + let p := 0x2560 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -566,7 +599,11 @@ contract Halo2Verifier { mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -598,7 +635,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -1358,15 +1401,15 @@ contract Halo2Verifier { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x1ac0 + let q_const_mptr := 0x2a40 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x1ac0 + let q_program_mptr := 0x2a40 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0x5020, 0) + mstore(0x5fa0, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -1387,13 +1430,13 @@ contract Halo2Verifier { // block multiplies by mload(selector_power_mptr + offset) // unconditionally -- so initialize the slot rather than // leaving correctness to two filters in another file. - mstore(0x5060, 1) + mstore(0x5fe0, 1) // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 15) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0x5060, shl(5, q_y_power_i)), q_y_power) + mstore(add(0x5fe0, shl(5, q_y_power_i)), q_y_power) } } @@ -1402,102 +1445,102 @@ contract Halo2Verifier { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x4520) - let f_4 := mload(0x4420) - let a_0 := mload(0x4300) + let f_3 := mload(0x54a0) + let f_4 := mload(0x53a0) + let a_0 := mload(0x5280) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x4440) - let a_1 := mload(0x4320) + let f_5 := mload(0x53c0) + let a_1 := mload(0x52a0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x4460) - let a_2 := mload(0x4340) + let f_6 := mload(0x53e0) + let a_2 := mload(0x52c0) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x4480) - let a_3 := mload(0x4360) + let f_7 := mload(0x5400) + let a_3 := mload(0x52e0) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x44a0) - let a_4 := mload(0x4380) + let f_8 := mload(0x5420) + let a_4 := mload(0x5300) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x44c0) - let a_0_next_1 := mload(0x43a0) + let f_0 := mload(0x5440) + let a_0_next_1 := mload(0x5320) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x44e0) + let f_1 := mload(0x5460) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x4500) + let f_2 := mload(0x5480) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0x5240, var19) + mstore(0x61c0, var19) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } { let var0 := 0x1 - let a_1 := mload(0x4320) - let a_2 := mload(0x4340) + let a_1 := mload(0x52a0) + let a_2 := mload(0x52c0) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x4360) + let a_3 := mload(0x52e0) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x4380) + let a_4 := mload(0x5300) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0x5240, var6) + mstore(0x61c0, var6) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } { let var0 := 0x1 - let a_0 := mload(0x4300) - let f_4 := mload(0x4420) + let a_0 := mload(0x5280) + let f_4 := mload(0x53a0) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x43a0) + let a_0_next_1 := mload(0x5320) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x5240, var4) + mstore(0x61c0, var4) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } { let var0 := 0x1 - let a_1 := mload(0x4320) - let f_5 := mload(0x4440) + let a_1 := mload(0x52a0) + let f_5 := mload(0x53c0) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x43c0) + let a_1_next_1 := mload(0x5340) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x5240, var4) + mstore(0x61c0, var4) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5060, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x5fe0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } // VM registers: @@ -1517,7 +1560,7 @@ contract Halo2Verifier { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x05) // q_sp starts at the first free stack word. - let q_sp := 0x5240 + let q_sp := 0x61c0 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -1567,69 +1610,69 @@ contract Halo2Verifier { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0x5240 + q_sp := 0x61c0 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0x5240 - let q_perm_sigmas := 0x5340 - let q_perm_z_cur := 0x5440 - let q_perm_z_next := 0x54a0 - let q_perm_z_last := 0x5500 - let q_perm_delta_base_ptr := 0x5540 + let q_perm_vals := 0x61c0 + let q_perm_sigmas := 0x62c0 + let q_perm_z_cur := 0x63c0 + let q_perm_z_next := 0x6420 + let q_perm_z_last := 0x6480 + let q_perm_delta_base_ptr := 0x64c0 let q_perm_num_cols := 8 let q_perm_num_sets := 3 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x4400)) + mstore(add(q_perm_vals, 0x0), mload(0x5380)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x4300, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x5280, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x42e0)) + mstore(add(q_perm_vals, 0xc0), mload(0x5260)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 8) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x45c0, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x5540, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 3) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x46c0, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x5640, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 3) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x46e0, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x5660, q_perm_z_next_load_src_off))) } } - mstore(add(q_perm_z_last, 0x0), mload(0x4700)) - mstore(add(q_perm_z_last, 0x20), mload(0x4760)) + mstore(add(q_perm_z_last, 0x0), mload(0x5680)) + mstore(add(q_perm_z_last, 0x20), mload(0x56e0)) let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_perm_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0x40)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_perm_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 3) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_perm_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 3) { q_perm_set := add(q_perm_set, 1) } { @@ -1648,8 +1691,8 @@ contract Halo2Verifier { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_perm_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -1670,13 +1713,13 @@ contract Halo2Verifier { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0x5240 + q_sp := 0x61c0 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0x5240 - let q_lookup_prefix := 0x52c0 - let q_lookup_suffix := 0x5340 + let q_lookup_f := 0x61c0 + let q_lookup_prefix := 0x6240 + let q_lookup_suffix := 0x62c0 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -1686,17 +1729,17 @@ contract Halo2Verifier { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x4800), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x5780), r) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_lookup_eval, r)) } { - let f_10 := mload(0x4540) + let f_10 := mload(0x54c0) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) let var1 := mulmod(var0, q_lookup_theta, r) for { let q_lookup_shared_i := 0 } lt(q_lookup_shared_i, 4) { q_lookup_shared_i := add(q_lookup_shared_i, 1) } { let q_lookup_shared_off := shl(5, q_lookup_shared_i) - let q_lookup_shared_tail := mload(add(0x4320, q_lookup_shared_off)) + let q_lookup_shared_tail := mload(add(0x52a0, q_lookup_shared_off)) let q_lookup_shared_compressed := addmod(var1, q_lookup_shared_tail, r) mstore(add(q_lookup_f, q_lookup_shared_off), addmod(q_lookup_shared_compressed, q_lookup_beta, r)) } @@ -1718,24 +1761,24 @@ contract Halo2Verifier { for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 4) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) } - let q_lookup_eval := addmod(mulmod(mload(0x47e0), q_lookup_product, r), sub(r, q_lookup_sum), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x5760), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x47e0) - let f_16 := mload(0x45a0) - let f_11 := mload(0x4560) + let q_lookup_sum_h := mload(0x5760) + let f_16 := mload(0x5520) + let f_11 := mload(0x54e0) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x4580) + let f_12 := mload(0x5500) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_16, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x4820), sub(r, addmod(mload(0x4800), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x57a0), sub(r, addmod(mload(0x5780), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x47c0), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x5740), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_lookup_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_lookup_eval, r)) } } } @@ -1756,27 +1799,27 @@ contract Halo2Verifier { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0x5240 + q_sp := 0x61c0 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 - let a_2 := mload(0x4340) - let f_6 := mload(0x4460) + let a_2 := mload(0x52c0) + let f_6 := mload(0x53e0) let var1 := addmod(a_2, f_6, r) - let a_2_next_1 := mload(0x43e0) + let a_2_next_1 := mload(0x5360) let var2 := addmod(0, sub(r, a_2_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x5240, var4) + mstore(0x61c0, var4) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5060, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x5fe0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } } default { revert(0, 0) } @@ -1797,7 +1840,7 @@ contract Halo2Verifier { // top, leaving abandoned words below q_sp with q_has_top // clear -- so both checks above pass while an operand of the // identity has been silently dropped from nu_y(x). - if iszero(eq(q_sp, 0x5240)) { revert(0, 0) } + if iszero(eq(q_sp, 0x61c0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -1815,21 +1858,21 @@ contract Halo2Verifier { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5060, 0x01c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5fe0, 0x01c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5060, 0x01a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5fe0, 0x01a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5060, 0x0140)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5fe0, 0x0140)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0x5020)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0x5fa0)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } @@ -1930,37 +1973,37 @@ contract Halo2Verifier { { // q_eval_set[0]: 28 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x5020, 0x4360) - mstore(0x5040, 0x4380) - mstore(0x5060, 0x42e0) - mstore(0x5080, 0x47c0) - mstore(0x50a0, 0x47e0) - mstore(0x50c0, 0x4400) - mstore(0x50e0, 0x4420) - mstore(0x5100, 0x4440) - mstore(0x5120, 0x4460) - mstore(0x5140, 0x4480) - mstore(0x5160, 0x44a0) - mstore(0x5180, 0x44c0) - mstore(0x51a0, 0x44e0) - mstore(0x51c0, 0x4500) - mstore(0x51e0, 0x4520) - mstore(0x5200, 0x4540) - mstore(0x5220, 0x4560) - mstore(0x5240, 0x4580) - mstore(0x5260, 0x45a0) - mstore(0x5280, 0x45c0) - mstore(0x52a0, 0x45e0) - mstore(0x52c0, 0x4600) - mstore(0x52e0, 0x4620) - mstore(0x5300, 0x4640) - mstore(0x5320, 0x4660) - mstore(0x5340, 0x4680) - mstore(0x5360, 0x46a0) - mstore(0x5380, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x4360) + mstore(0x5fa0, 0x52e0) + mstore(0x5fc0, 0x5300) + mstore(0x5fe0, 0x5260) + mstore(0x6000, 0x5740) + mstore(0x6020, 0x5760) + mstore(0x6040, 0x5380) + mstore(0x6060, 0x53a0) + mstore(0x6080, 0x53c0) + mstore(0x60a0, 0x53e0) + mstore(0x60c0, 0x5400) + mstore(0x60e0, 0x5420) + mstore(0x6100, 0x5440) + mstore(0x6120, 0x5460) + mstore(0x6140, 0x5480) + mstore(0x6160, 0x54a0) + mstore(0x6180, 0x54c0) + mstore(0x61a0, 0x54e0) + mstore(0x61c0, 0x5500) + mstore(0x61e0, 0x5520) + mstore(0x6200, 0x5540) + mstore(0x6220, 0x5560) + mstore(0x6240, 0x5580) + mstore(0x6260, 0x55a0) + mstore(0x6280, 0x55c0) + mstore(0x62a0, 0x55e0) + mstore(0x62c0, 0x5600) + mstore(0x62e0, 0x5620) + mstore(0x6300, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x52e0) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x5020, 0x20) + let eval_p := add(0x5fa0, 0x20) for { let i := 1 } lt(i, 0x1c) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1975,20 +2018,20 @@ contract Halo2Verifier { { // q_eval_set[1]: 5 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x5020, 0x4300) - mstore(0x5040, 0x43a0) - mstore(0x5060, 0x4320) - mstore(0x5080, 0x43c0) - mstore(0x50a0, 0x4340) - mstore(0x50c0, 0x43e0) - mstore(0x50e0, 0x4780) - mstore(0x5100, 0x47a0) - mstore(0x5120, 0x4800) - mstore(0x5140, 0x4820) - let q_eval_set_0 := mload(0x4300) - let q_eval_set_1 := mload(0x43a0) + mstore(0x5fa0, 0x5280) + mstore(0x5fc0, 0x5320) + mstore(0x5fe0, 0x52a0) + mstore(0x6000, 0x5340) + mstore(0x6020, 0x52c0) + mstore(0x6040, 0x5360) + mstore(0x6060, 0x5700) + mstore(0x6080, 0x5720) + mstore(0x60a0, 0x5780) + mstore(0x60c0, 0x57a0) + let q_eval_set_0 := mload(0x5280) + let q_eval_set_1 := mload(0x5320) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x5020, 0x40) + let eval_p := add(0x5fa0, 0x40) for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2004,12 +2047,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[2]: 2 commitment(s) - let q_eval_set_0 := mload(0x46c0) - let q_eval_set_1 := mload(0x46e0) - let q_eval_set_2 := mload(0x4700) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x4720), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x4740), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x4760), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + let q_eval_set_0 := mload(0x5640) + let q_eval_set_1 := mload(0x5660) + let q_eval_set_2 := mload(0x5680) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x56a0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x56c0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x56e0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) mstore(add(Q_EVAL_SET_MPTR, 0xa0), q_eval_set_2) @@ -2129,95 +2172,95 @@ contract Halo2Verifier { v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_3, r), r) - mcopy(0x5020, 0x49c0, 0x80) - mstore(0x50a0, 1) - mcopy(0x50c0, 0x4a40, 0x80) - mstore(0x5140, mload(add(X1_POWERS_MPTR, 0x20))) - mcopy(0x5160, 0x4ac0, 0x80) - mstore(0x51e0, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0x5200, 0x4cc0, 0x80) - mstore(0x5280, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0x52a0, 0x1f60, 0x80) - mstore(0x5320, mload(add(X1_POWERS_MPTR, 0xa0))) - mcopy(0x5340, 0x1ce0, 0x80) - mstore(0x53c0, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0x53e0, 0x1d60, 0x80) - mstore(0x5460, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0x5480, 0x1de0, 0x80) - mstore(0x5500, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0x5520, 0x1e60, 0x80) - mstore(0x55a0, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0x55c0, 0x1ee0, 0x80) - mstore(0x5640, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0x5660, 0x1ae0, 0x80) - mstore(0x56e0, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0x5700, 0x1b60, 0x80) - mstore(0x5780, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0x57a0, 0x1be0, 0x80) - mstore(0x5820, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0x5840, 0x1c60, 0x80) - mstore(0x58c0, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0x58e0, 0x1fe0, 0x80) - mstore(0x5960, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0x5980, 0x2060, 0x80) - mstore(0x5a00, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0x5a20, 0x20e0, 0x80) - mstore(0x5aa0, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0x5ac0, 0x22e0, 0x80) - mstore(0x5b40, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0x5b60, 0x2360, 0x80) - mstore(0x5be0, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0x5c00, 0x23e0, 0x80) - mstore(0x5c80, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0x5ca0, 0x2460, 0x80) - mstore(0x5d20, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0x5d40, 0x24e0, 0x80) - mstore(0x5dc0, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0x5de0, 0x2560, 0x80) - mstore(0x5e60, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0x5e80, 0x25e0, 0x80) - mstore(0x5f00, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0x5f20, 0x2660, 0x80) - mstore(0x5fa0, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0x5fc0, 0x26e0, 0x80) - mstore(0x6040, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0x5fa0, 0x5940, 0x80) + mstore(0x6020, 1) + mcopy(0x6040, 0x59c0, 0x80) + mstore(0x60c0, mload(add(X1_POWERS_MPTR, 0x20))) + mcopy(0x60e0, 0x5a40, 0x80) + mstore(0x6160, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0x6180, 0x5c40, 0x80) + mstore(0x6200, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0x6220, 0x2ee0, 0x80) + mstore(0x62a0, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0x62c0, 0x2c60, 0x80) + mstore(0x6340, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0x6360, 0x2ce0, 0x80) + mstore(0x63e0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0x6400, 0x2d60, 0x80) + mstore(0x6480, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0x64a0, 0x2de0, 0x80) + mstore(0x6520, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0x6540, 0x2e60, 0x80) + mstore(0x65c0, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0x65e0, 0x2a60, 0x80) + mstore(0x6660, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0x6680, 0x2ae0, 0x80) + mstore(0x6700, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0x6720, 0x2b60, 0x80) + mstore(0x67a0, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0x67c0, 0x2be0, 0x80) + mstore(0x6840, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0x6860, 0x2f60, 0x80) + mstore(0x68e0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0x6900, 0x2fe0, 0x80) + mstore(0x6980, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0x69a0, 0x3060, 0x80) + mstore(0x6a20, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0x6a40, 0x3260, 0x80) + mstore(0x6ac0, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0x6ae0, 0x32e0, 0x80) + mstore(0x6b60, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0x6b80, 0x3360, 0x80) + mstore(0x6c00, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0x6c20, 0x33e0, 0x80) + mstore(0x6ca0, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0x6cc0, 0x3460, 0x80) + mstore(0x6d40, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0x6d60, 0x34e0, 0x80) + mstore(0x6de0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0x6e00, 0x3560, 0x80) + mstore(0x6e80, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0x6ea0, 0x35e0, 0x80) + mstore(0x6f20, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0x6f40, 0x3660, 0x80) + mstore(0x6fc0, mload(add(X1_POWERS_MPTR, 0x340))) let lin_query_scalar_26 := mload(add(X1_POWERS_MPTR, 0x360)) let lin_cur_scalar_26 := mulmod(lin_query_scalar_26, lin_one_minus_x_n, r) - mcopy(0x6060, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0x60e0, lin_cur_scalar_26) + mcopy(0x6fe0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0x7060, lin_cur_scalar_26) lin_cur_scalar_26 := mulmod(lin_cur_scalar_26, lin_x_split, r) - mcopy(0x6100, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0x6180, lin_cur_scalar_26) + mcopy(0x7080, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0x7100, lin_cur_scalar_26) lin_cur_scalar_26 := mulmod(lin_cur_scalar_26, lin_x_split, r) - mcopy(0x61a0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0x6220, lin_cur_scalar_26) + mcopy(0x7120, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0x71a0, lin_cur_scalar_26) lin_cur_scalar_26 := mulmod(lin_cur_scalar_26, lin_x_split, r) - mcopy(0x6240, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0x62c0, lin_cur_scalar_26) - mcopy(0x62e0, 0x2160, 0x80) - mstore(0x6360, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0x6380, 0x21e0, 0x80) - mstore(0x6400, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0x6420, 0x2260, 0x80) - mstore(0x64a0, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0x64c0, 0x4840, 0x80) - mstore(0x6540, x4_pow_1) - mcopy(0x6560, 0x48c0, 0x80) - mstore(0x65e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0x6600, 0x4940, 0x80) - mstore(0x6680, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0x66a0, 0x4c40, 0x80) - mstore(0x6720, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_1, r)) - mcopy(0x6740, 0x4d40, 0x80) - mstore(0x67c0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_1, r)) - mcopy(0x67e0, 0x4b40, 0x80) - mstore(0x6860, x4_pow_2) - mcopy(0x6880, 0x4bc0, 0x80) - mstore(0x6900, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0x6920, F_COM_MPTR, 0x80) - mstore(0x69a0, x4_pow_3) + mcopy(0x71c0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0x7240, lin_cur_scalar_26) + mcopy(0x7260, 0x30e0, 0x80) + mstore(0x72e0, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0x7300, 0x3160, 0x80) + mstore(0x7380, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0x73a0, 0x31e0, 0x80) + mstore(0x7420, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0x7440, 0x57c0, 0x80) + mstore(0x74c0, x4_pow_1) + mcopy(0x74e0, 0x5840, 0x80) + mstore(0x7560, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0x7580, 0x58c0, 0x80) + mstore(0x7600, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0x7620, 0x5bc0, 0x80) + mstore(0x76a0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_1, r)) + mcopy(0x76c0, 0x5cc0, 0x80) + mstore(0x7740, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_1, r)) + mcopy(0x7760, 0x5ac0, 0x80) + mstore(0x77e0, x4_pow_2) + mcopy(0x7800, 0x5b40, 0x80) + mstore(0x7880, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0x78a0, F_COM_MPTR, 0x80) + mstore(0x7920, x4_pow_3) if success { - success := staticcall(gas(), 0x0c, 0x5020, 0x19a0, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0x5fa0, 0x19a0, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -2229,28 +2272,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } diff --git a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol index 9a8fd44af..150bbd4be 100644 --- a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol @@ -43,7 +43,7 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 8032; uint256 internal constant EXPECTED_VK_LENGTH = 8033; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x327e3a0642d1a157521eb891596256086b59006348710f0cd960b89ed35e5854; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x677b7b26592c167d44fd428a1d743abb1eb2fd8040804bd032440791caeb7471; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -55,8 +55,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0x1504; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -64,84 +64,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x1d20; - uint256 internal constant VK_DIGEST_MPTR = 0x1d20; - uint256 internal constant NUM_INSTANCES_MPTR = 0x1d40; - uint256 internal constant K_MPTR = 0x1d60; - uint256 internal constant N_INV_MPTR = 0x1d80; - uint256 internal constant OMEGA_MPTR = 0x1da0; - uint256 internal constant OMEGA_INV_MPTR = 0x1dc0; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x1de0; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x1e00; - uint256 internal constant ACC_OFFSET_MPTR = 0x1e20; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x1e40; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x1e60; - uint256 internal constant G1_BASE_MPTR = 0x1e80; - uint256 internal constant G2_BASE_MPTR = 0x1f00; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x2000; - - uint256 internal constant CHALLENGE_MPTR = 0x3c80; + uint256 internal constant VK_MPTR = 0x2ca0; + uint256 internal constant VK_DIGEST_MPTR = 0x2ca0; + uint256 internal constant NUM_INSTANCES_MPTR = 0x2cc0; + uint256 internal constant K_MPTR = 0x2ce0; + uint256 internal constant N_INV_MPTR = 0x2d00; + uint256 internal constant OMEGA_MPTR = 0x2d20; + uint256 internal constant OMEGA_INV_MPTR = 0x2d40; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x2d60; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x2d80; + uint256 internal constant ACC_OFFSET_MPTR = 0x2da0; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2dc0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x2de0; + uint256 internal constant G1_BASE_MPTR = 0x2e00; + uint256 internal constant G2_BASE_MPTR = 0x2e80; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x2f80; + + uint256 internal constant CHALLENGE_MPTR = 0x4c00; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x3c80; - uint256 internal constant BETA_MPTR = 0x3ca0; - uint256 internal constant GAMMA_MPTR = 0x3cc0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x3ce0; - uint256 internal constant Y_MPTR = 0x3d00; - uint256 internal constant X_MPTR = 0x3d20; - uint256 internal constant X1_MPTR = 0x3d40; - uint256 internal constant X2_MPTR = 0x3d60; - uint256 internal constant X3_MPTR = 0x3d80; - uint256 internal constant X4_MPTR = 0x3da0; + uint256 internal constant THETA_MPTR = 0x4c00; + uint256 internal constant BETA_MPTR = 0x4c20; + uint256 internal constant GAMMA_MPTR = 0x4c40; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x4c60; + uint256 internal constant Y_MPTR = 0x4c80; + uint256 internal constant X_MPTR = 0x4ca0; + uint256 internal constant X1_MPTR = 0x4cc0; + uint256 internal constant X2_MPTR = 0x4ce0; + uint256 internal constant X3_MPTR = 0x4d00; + uint256 internal constant X4_MPTR = 0x4d20; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x3dc0; - uint256 internal constant PI_MPTR = 0x3e40; + uint256 internal constant F_COM_MPTR = 0x4d40; + uint256 internal constant PI_MPTR = 0x4dc0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x3ec0; - uint256 internal constant ACC_RHS_MPTR = 0x3f40; + uint256 internal constant ACC_LHS_MPTR = 0x4e40; + uint256 internal constant ACC_RHS_MPTR = 0x4ec0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x3fc0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x3fe0; - uint256 internal constant L_LAST_MPTR = 0x4000; - uint256 internal constant L_BLIND_MPTR = 0x4020; - uint256 internal constant L_0_MPTR = 0x4040; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x4060; + uint256 internal constant X_N_MPTR = 0x4f40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x4f60; + uint256 internal constant L_LAST_MPTR = 0x4f80; + uint256 internal constant L_BLIND_MPTR = 0x4fa0; + uint256 internal constant L_0_MPTR = 0x4fc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x4fe0; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x4080; - uint256 internal constant QUOTIENT_MPTR = 0x40a0; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x4140; - uint256 internal constant V_MPTR = 0x4160; - uint256 internal constant FINAL_COM_MPTR = 0x4180; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x4200; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x4280; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x5000; + uint256 internal constant QUOTIENT_MPTR = 0x5020; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x50c0; + uint256 internal constant V_MPTR = 0x50e0; + uint256 internal constant FINAL_COM_MPTR = 0x5100; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x5180; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x5200; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x4300; - uint256 internal constant X1_POWERS_MPTR = 0x4680; + uint256 internal constant ROT_POINTS_MPTR = 0x5280; + uint256 internal constant X1_POWERS_MPTR = 0x5600; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x4ea0; - uint256 internal constant Q_EVAL_SET_MPTR = 0x4ea0; + uint256 internal constant Q_COM_MPTR = 0x5e20; + uint256 internal constant Q_EVAL_SET_MPTR = 0x5e20; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x55a0; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x6520; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x56a0; + uint256 internal constant G1_IDENTITY_MPTR = 0x6620; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -149,11 +149,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x5800; - uint256 internal constant SELECTOR_ACC_MPTR = 0x6b00; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x6b00; - uint256 internal constant TRACE_U256_MPTR = 0x9420; + uint256 internal constant REVERSED_EVALS_MPTR = 0x6780; + uint256 internal constant SELECTOR_ACC_MPTR = 0x7a80; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x7a80; + uint256 internal constant TRACE_U256_MPTR = 0xa3a0; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -170,13 +170,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x6080; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x6480; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x6580; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x6700; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x6800; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x6900; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x6900; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x7000; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x7400; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x7500; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x7680; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x7780; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x7880; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x7880; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -198,7 +198,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -222,11 +222,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0x6b00 + let msm_scratch := 0x7a80 for { let off := 0 } lt(off, 0x2760) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -329,7 +362,7 @@ contract Halo2Verifier { // guards against a future emitter passing a raw scalar. if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x1c20 + let p := 0x2ba0 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -566,7 +599,11 @@ contract Halo2Verifier { mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -598,7 +635,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -1374,15 +1417,15 @@ contract Halo2Verifier { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x2100 + let q_const_mptr := 0x3080 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x2660 + let q_program_mptr := 0x35e0 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0x6cc0, 0) + mstore(0x7c40, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -1403,13 +1446,13 @@ contract Halo2Verifier { // block multiplies by mload(selector_power_mptr + offset) // unconditionally -- so initialize the slot rather than // leaving correctness to two filters in another file. - mstore(0x6d00, 1) + mstore(0x7c80, 1) // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 35) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0x6d00, shl(5, q_y_power_i)), q_y_power) + mstore(add(0x7c80, shl(5, q_y_power_i)), q_y_power) } } @@ -1418,102 +1461,102 @@ contract Halo2Verifier { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x5c40) - let f_4 := mload(0x5b40) - let a_0 := mload(0x5820) + let f_3 := mload(0x6bc0) + let f_4 := mload(0x6ac0) + let a_0 := mload(0x67a0) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x5b60) - let a_1 := mload(0x5840) + let f_5 := mload(0x6ae0) + let a_1 := mload(0x67c0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x5b80) - let a_2 := mload(0x5860) + let f_6 := mload(0x6b00) + let a_2 := mload(0x67e0) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x5ba0) - let a_3 := mload(0x5880) + let f_7 := mload(0x6b20) + let a_3 := mload(0x6800) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x5bc0) - let a_4 := mload(0x58a0) + let f_8 := mload(0x6b40) + let a_4 := mload(0x6820) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x5be0) - let a_0_next_1 := mload(0x58c0) + let f_0 := mload(0x6b60) + let a_0_next_1 := mload(0x6840) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x5c00) + let f_1 := mload(0x6b80) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x5c20) + let f_2 := mload(0x6ba0) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0x7160, var19) + mstore(0x80e0, var19) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } { let var0 := 0x1 - let a_1 := mload(0x5840) - let a_2 := mload(0x5860) + let a_1 := mload(0x67c0) + let a_2 := mload(0x67e0) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x5880) + let a_3 := mload(0x6800) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x58a0) + let a_4 := mload(0x6820) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0x7160, var6) + mstore(0x80e0, var6) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } { let var0 := 0x1 - let a_0 := mload(0x5820) - let f_4 := mload(0x5b40) + let a_0 := mload(0x67a0) + let f_4 := mload(0x6ac0) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x7160, var4) + mstore(0x80e0, var4) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } { let var0 := 0x1 - let a_1 := mload(0x5840) - let f_5 := mload(0x5b60) + let a_1 := mload(0x67c0) + let f_5 := mload(0x6ae0) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x7160, var4) + mstore(0x80e0, var4) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x6d00, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x7c80, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } // VM registers: @@ -1533,7 +1576,7 @@ contract Halo2Verifier { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x019b) // q_sp starts at the first free stack word. - let q_sp := 0x7160 + let q_sp := 0x80e0 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -1864,70 +1907,70 @@ contract Halo2Verifier { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0x7160 + q_sp := 0x80e0 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0x7160 - let q_perm_sigmas := 0x7280 - let q_perm_z_cur := 0x73a0 - let q_perm_z_next := 0x7400 - let q_perm_z_last := 0x7460 - let q_perm_delta_base_ptr := 0x74a0 + let q_perm_vals := 0x80e0 + let q_perm_sigmas := 0x8200 + let q_perm_z_cur := 0x8320 + let q_perm_z_next := 0x8380 + let q_perm_z_last := 0x83e0 + let q_perm_delta_base_ptr := 0x8420 let q_perm_num_cols := 9 let q_perm_num_sets := 3 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x5b20)) + mstore(add(q_perm_vals, 0x0), mload(0x6aa0)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x5820, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x67a0, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x5800)) + mstore(add(q_perm_vals, 0xc0), mload(0x6780)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) - mstore(add(q_perm_vals, 0x100), mload(0x5920)) + mstore(add(q_perm_vals, 0x100), mload(0x68a0)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 9) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x5d60, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x6ce0, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 3) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x5e80, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x6e00, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 3) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x5ea0, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x6e20, q_perm_z_next_load_src_off))) } } - mstore(add(q_perm_z_last, 0x0), mload(0x5ec0)) - mstore(add(q_perm_z_last, 0x20), mload(0x5f20)) + mstore(add(q_perm_z_last, 0x0), mload(0x6e40)) + mstore(add(q_perm_z_last, 0x20), mload(0x6ea0)) let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_perm_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0x40)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_perm_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 3) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_perm_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 3) { q_perm_set := add(q_perm_set, 1) } { @@ -1946,8 +1989,8 @@ contract Halo2Verifier { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_perm_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -1968,13 +2011,13 @@ contract Halo2Verifier { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0x7160 + q_sp := 0x80e0 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0x7160 - let q_lookup_prefix := 0x71a0 - let q_lookup_suffix := 0x71e0 + let q_lookup_f := 0x80e0 + let q_lookup_prefix := 0x8120 + let q_lookup_suffix := 0x8160 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -1984,54 +2027,54 @@ contract Halo2Verifier { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x5fc0), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6f40), r) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } { - let f_10 := mload(0x5c60) + let f_10 := mload(0x6be0) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_1, r) - let q_lookup_eval := addmod(mulmod(mload(0x5fa0), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x6f20), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x5fa0) - let f_19 := mload(0x5d20) - let f_11 := mload(0x5c80) + let q_lookup_sum_h := mload(0x6f20) + let f_19 := mload(0x6ca0) + let f_11 := mload(0x6c00) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x5ca0) + let f_12 := mload(0x6c20) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_19, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x5fe0), sub(r, addmod(mload(0x5fc0), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x6f60), sub(r, addmod(mload(0x6f40), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x5f80), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x6f00), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } } { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6040), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6fc0), r) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } { - let f_0 := mload(0x5be0) + let f_0 := mload(0x6b60) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_0, r) - let a_6 := mload(0x5940) + let a_6 := mload(0x68c0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_6, r) - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_0, r) mstore(add(q_lookup_f, 0x0), addmod(var2, q_lookup_beta, r)) - let f_1 := mload(0x5c00) + let f_1 := mload(0x6b80) let var3 := addmod(mulmod(0, q_lookup_theta, r), f_1, r) - let a_7 := mload(0x5960) + let a_7 := mload(0x68e0) let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_7, r) - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_1, r) mstore(add(q_lookup_f, 0x20), addmod(var5, q_lookup_beta, r)) let q_lookup_product := 1 @@ -2052,26 +2095,26 @@ contract Halo2Verifier { for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 2) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) } - let q_lookup_eval := addmod(mulmod(mload(0x6020), q_lookup_product, r), sub(r, q_lookup_sum), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x6fa0), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x6020) - let f_20 := mload(0x5d40) - let f_13 := mload(0x5cc0) + let q_lookup_sum_h := mload(0x6fa0) + let f_20 := mload(0x6cc0) + let f_13 := mload(0x6c40) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_13, r) - let f_14 := mload(0x5ce0) + let f_14 := mload(0x6c60) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_14, r) - let f_15 := mload(0x5d00) + let f_15 := mload(0x6c80) let var2 := addmod(mulmod(var1, q_lookup_theta, r), f_15, r) let q_lookup_s_sum_h := mulmod(f_20, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x6060), sub(r, addmod(mload(0x6040), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x6fe0), sub(r, addmod(mload(0x6fc0), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var2, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x6000), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x6f80), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } } } @@ -2092,100 +2135,100 @@ contract Halo2Verifier { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0x7160 + q_sp := 0x80e0 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 - let a_3_prev_1 := mload(0x5980) - let a_4_prev_1 := mload(0x59a0) + let a_3_prev_1 := mload(0x6900) + let a_4_prev_1 := mload(0x6920) let var1 := addmod(a_3_prev_1, a_4_prev_1, r) - let a_3 := mload(0x5880) + let a_3 := mload(0x6800) let var2 := addmod(var1, a_3, r) let var3 := 0x40000000000 - let a_1_prev_1 := mload(0x59e0) + let a_1_prev_1 := mload(0x6960) let var4 := mulmod(var3, a_1_prev_1, r) let var5 := 0x100000 - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var6 := mulmod(var5, a_1, r) let var7 := addmod(var4, var6, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var8 := addmod(var7, a_1_next_1, r) let var9 := 0x2 - let a_0_prev_1 := mload(0x59c0) + let a_0_prev_1 := mload(0x6940) let var10 := mulmod(var3, a_0_prev_1, r) - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var11 := mulmod(var5, a_0, r) let var12 := addmod(var10, var11, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var13 := addmod(var12, a_0_next_1, r) let var14 := mulmod(var9, var13, r) let var15 := addmod(var8, var14, r) let var16 := addmod(0, sub(r, var15), r) let var17 := addmod(var2, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x7160, var18) + mstore(0x80e0, var18) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } } case 1 { { let var0 := 0x1 - let a_3_prev_1 := mload(0x5980) - let a_4_prev_1 := mload(0x59a0) + let a_3_prev_1 := mload(0x6900) + let a_4_prev_1 := mload(0x6920) let var1 := addmod(a_3_prev_1, a_4_prev_1, r) let var2 := 0x40000000000 - let a_1_prev_1 := mload(0x59e0) + let a_1_prev_1 := mload(0x6960) let var3 := mulmod(var2, a_1_prev_1, r) let var4 := 0x100000 - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var5 := mulmod(var4, a_1, r) let var6 := addmod(var3, var5, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var7 := addmod(var6, a_1_next_1, r) let var8 := 0x2 - let a_0_prev_1 := mload(0x59c0) + let a_0_prev_1 := mload(0x6940) let var9 := mulmod(var2, a_0_prev_1, r) - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var10 := mulmod(var4, a_0, r) let var11 := addmod(var9, var10, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var12 := addmod(var11, a_0_next_1, r) let var13 := mulmod(var8, var12, r) let var14 := addmod(var7, var13, r) let var15 := addmod(0, sub(r, var14), r) let var16 := addmod(var1, var15, r) let var17 := mulmod(var0, var16, r) - mstore(0x7160, var17) + mstore(0x80e0, var17) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x80) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } } case 2 { { let var0 := 0x1 let var1 := 0x1000000000000000 - let a_4 := mload(0x58a0) + let a_4 := mload(0x6820) let var2 := mulmod(var1, a_4, r) let var3 := 0x10000000000 - let a_3_prev_1 := mload(0x5980) + let a_3_prev_1 := mload(0x6900) let var4 := mulmod(var3, a_3_prev_1, r) let var5 := addmod(var2, var4, r) let var6 := 0x400000 - let a_4_prev_1 := mload(0x59a0) + let a_4_prev_1 := mload(0x6920) let var7 := mulmod(var6, a_4_prev_1, r) let var8 := addmod(var5, var7, r) - let a_3 := mload(0x5880) + let a_3 := mload(0x6800) let var9 := addmod(var8, a_3, r) let var10 := 0x40000000000 let var11 := mulmod(var10, a_3, r) @@ -2207,54 +2250,54 @@ contract Halo2Verifier { let var27 := addmod(var24, var26, r) let var28 := addmod(var27, a_3_prev_1, r) let var29 := addmod(var19, var28, r) - let a_0_prev_1 := mload(0x59c0) + let a_0_prev_1 := mload(0x6940) let var30 := mulmod(var10, a_0_prev_1, r) - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var31 := mulmod(var25, a_0, r) let var32 := addmod(var30, var31, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var33 := addmod(var32, a_0_next_1, r) let var34 := 0x2 - let a_1_prev_1 := mload(0x59e0) + let a_1_prev_1 := mload(0x6960) let var35 := mulmod(var10, a_1_prev_1, r) - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var36 := mulmod(var25, a_1, r) let var37 := addmod(var35, var36, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var38 := addmod(var37, a_1_next_1, r) let var39 := mulmod(var34, var38, r) let var40 := addmod(var33, var39, r) let var41 := addmod(0, sub(r, var40), r) let var42 := addmod(var29, var41, r) let var43 := mulmod(var0, var42, r) - mstore(0x7160, var43) + mstore(0x80e0, var43) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xa0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } } case 3 { { let var0 := 0x1 let var1 := 0x10000000000000 - let a_3_next_1 := mload(0x5a00) + let a_3_next_1 := mload(0x6980) let var2 := mulmod(var1, a_3_next_1, r) let var3 := 0x4000000000 - let a_3_prev_1 := mload(0x5980) + let a_3_prev_1 := mload(0x6900) let var4 := mulmod(var3, a_3_prev_1, r) let var5 := addmod(var2, var4, r) let var6 := 0x4000 - let a_4_prev_1 := mload(0x59a0) + let a_4_prev_1 := mload(0x6920) let var7 := mulmod(var6, a_4_prev_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x400 - let a_3 := mload(0x5880) + let a_3 := mload(0x6800) let var10 := mulmod(var9, a_3, r) let var11 := addmod(var8, var10, r) - let a_4 := mload(0x58a0) + let a_4 := mload(0x6820) let var12 := addmod(var11, a_4, r) let var13 := 0x40000000000000 let var14 := mulmod(var13, a_4, r) @@ -2281,34 +2324,34 @@ contract Halo2Verifier { let var35 := addmod(var33, var34, r) let var36 := addmod(var35, a_3_prev_1, r) let var37 := addmod(var25, var36, r) - let a_0_prev_1 := mload(0x59c0) + let a_0_prev_1 := mload(0x6940) let var38 := mulmod(var15, a_0_prev_1, r) let var39 := 0x100000 - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var40 := mulmod(var39, a_0, r) let var41 := addmod(var38, var40, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var42 := addmod(var41, a_0_next_1, r) let var43 := 0x2 - let a_1_prev_1 := mload(0x59e0) + let a_1_prev_1 := mload(0x6960) let var44 := mulmod(var15, a_1_prev_1, r) - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var45 := mulmod(var39, a_1, r) let var46 := addmod(var44, var45, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var47 := addmod(var46, a_1_next_1, r) let var48 := mulmod(var43, var47, r) let var49 := addmod(var42, var48, r) let var50 := addmod(0, sub(r, var49), r) let var51 := addmod(var37, var50, r) let var52 := mulmod(var0, var51, r) - mstore(0x7160, var52) + mstore(0x80e0, var52) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xc0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } } default { revert(0, 0) } @@ -2332,14 +2375,14 @@ contract Halo2Verifier { // The global fully-evaluated accumulator is still // multiplied by y so later main identities land at the // same y powers as Rust's reverse fold. - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) let q_sel_acc := mload(q_target_ptr) if q_sel_gap { // Selector buckets are sparse in the global // identity stream. Precomputed y^gap advances only // this selector's local accumulator. - q_sel_acc := mulmod(q_sel_acc, mload(add(0x6d00, shl(5, q_sel_gap))), r) + q_sel_acc := mulmod(q_sel_acc, mload(add(0x7c80, shl(5, q_sel_gap))), r) } mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) } @@ -2359,7 +2402,7 @@ contract Halo2Verifier { // top, leaving abandoned words below q_sp with q_has_top // clear -- so both checks above pass while an operand of the // identity has been silently dropped from nu_y(x). - if iszero(eq(q_sp, 0x7160)) { revert(0, 0) } + if iszero(eq(q_sp, 0x80e0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -2377,65 +2420,65 @@ contract Halo2Verifier { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0440)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0440)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0420)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0420)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x03c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x03c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x03a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x03a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0360)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0360)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0340)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0340)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0320)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0320)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0300)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0300)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x02e0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x02e0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x02c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x02c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0140) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0280)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0280)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0160) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0240)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0240)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0180) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x01c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x01c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x01a0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x01a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x01a0)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0x6cc0)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0x7c40)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } @@ -2538,42 +2581,42 @@ contract Halo2Verifier { { // q_eval_set[0]: 33 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x6cc0, 0x5800) - mstore(0x6ce0, 0x5f80) - mstore(0x6d00, 0x5fa0) - mstore(0x6d20, 0x6000) - mstore(0x6d40, 0x6020) - mstore(0x6d60, 0x5b20) - mstore(0x6d80, 0x5b40) - mstore(0x6da0, 0x5b60) - mstore(0x6dc0, 0x5b80) - mstore(0x6de0, 0x5ba0) - mstore(0x6e00, 0x5bc0) - mstore(0x6e20, 0x5be0) - mstore(0x6e40, 0x5c00) - mstore(0x6e60, 0x5c20) - mstore(0x6e80, 0x5c40) - mstore(0x6ea0, 0x5c60) - mstore(0x6ec0, 0x5c80) - mstore(0x6ee0, 0x5ca0) - mstore(0x6f00, 0x5cc0) - mstore(0x6f20, 0x5ce0) - mstore(0x6f40, 0x5d00) - mstore(0x6f60, 0x5d20) - mstore(0x6f80, 0x5d40) - mstore(0x6fa0, 0x5d60) - mstore(0x6fc0, 0x5d80) - mstore(0x6fe0, 0x5da0) - mstore(0x7000, 0x5dc0) - mstore(0x7020, 0x5de0) - mstore(0x7040, 0x5e00) - mstore(0x7060, 0x5e20) - mstore(0x7080, 0x5e40) - mstore(0x70a0, 0x5e60) - mstore(0x70c0, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x5800) + mstore(0x7c40, 0x6780) + mstore(0x7c60, 0x6f00) + mstore(0x7c80, 0x6f20) + mstore(0x7ca0, 0x6f80) + mstore(0x7cc0, 0x6fa0) + mstore(0x7ce0, 0x6aa0) + mstore(0x7d00, 0x6ac0) + mstore(0x7d20, 0x6ae0) + mstore(0x7d40, 0x6b00) + mstore(0x7d60, 0x6b20) + mstore(0x7d80, 0x6b40) + mstore(0x7da0, 0x6b60) + mstore(0x7dc0, 0x6b80) + mstore(0x7de0, 0x6ba0) + mstore(0x7e00, 0x6bc0) + mstore(0x7e20, 0x6be0) + mstore(0x7e40, 0x6c00) + mstore(0x7e60, 0x6c20) + mstore(0x7e80, 0x6c40) + mstore(0x7ea0, 0x6c60) + mstore(0x7ec0, 0x6c80) + mstore(0x7ee0, 0x6ca0) + mstore(0x7f00, 0x6cc0) + mstore(0x7f20, 0x6ce0) + mstore(0x7f40, 0x6d00) + mstore(0x7f60, 0x6d20) + mstore(0x7f80, 0x6d40) + mstore(0x7fa0, 0x6d60) + mstore(0x7fc0, 0x6d80) + mstore(0x7fe0, 0x6da0) + mstore(0x8000, 0x6dc0) + mstore(0x8020, 0x6de0) + mstore(0x8040, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x6780) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x6cc0, 0x20) + let eval_p := add(0x7c40, 0x20) for { let i := 1 } lt(i, 0x21) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2587,12 +2630,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[1]: 3 commitment(s) - let q_eval_set_0 := mload(0x5f40) - let q_eval_set_1 := mload(0x5f60) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x5fc0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x5fe0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6040), mload(add(X1_POWERS_MPTR, 0x40)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6060), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + let q_eval_set_0 := mload(0x6ec0) + let q_eval_set_1 := mload(0x6ee0) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6f40), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6f60), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6fc0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6fe0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) } @@ -2602,35 +2645,35 @@ contract Halo2Verifier { { // q_eval_set[2]: 8 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x6cc0, 0x5820) - mstore(0x6ce0, 0x58c0) - mstore(0x6d00, 0x59c0) - mstore(0x6d20, 0x5840) - mstore(0x6d40, 0x58e0) - mstore(0x6d60, 0x59e0) - mstore(0x6d80, 0x5860) - mstore(0x6da0, 0x5900) - mstore(0x6dc0, 0x5a80) - mstore(0x6de0, 0x5880) - mstore(0x6e00, 0x5a00) - mstore(0x6e20, 0x5980) - mstore(0x6e40, 0x58a0) - mstore(0x6e60, 0x5a20) - mstore(0x6e80, 0x59a0) - mstore(0x6ea0, 0x5920) - mstore(0x6ec0, 0x5ae0) - mstore(0x6ee0, 0x5ac0) - mstore(0x6f00, 0x5940) - mstore(0x6f20, 0x5a60) - mstore(0x6f40, 0x5a40) - mstore(0x6f60, 0x5960) - mstore(0x6f80, 0x5b00) - mstore(0x6fa0, 0x5aa0) - let q_eval_set_0 := mload(0x5820) - let q_eval_set_1 := mload(0x58c0) - let q_eval_set_2 := mload(0x59c0) + mstore(0x7c40, 0x67a0) + mstore(0x7c60, 0x6840) + mstore(0x7c80, 0x6940) + mstore(0x7ca0, 0x67c0) + mstore(0x7cc0, 0x6860) + mstore(0x7ce0, 0x6960) + mstore(0x7d00, 0x67e0) + mstore(0x7d20, 0x6880) + mstore(0x7d40, 0x6a00) + mstore(0x7d60, 0x6800) + mstore(0x7d80, 0x6980) + mstore(0x7da0, 0x6900) + mstore(0x7dc0, 0x6820) + mstore(0x7de0, 0x69a0) + mstore(0x7e00, 0x6920) + mstore(0x7e20, 0x68a0) + mstore(0x7e40, 0x6a60) + mstore(0x7e60, 0x6a40) + mstore(0x7e80, 0x68c0) + mstore(0x7ea0, 0x69e0) + mstore(0x7ec0, 0x69c0) + mstore(0x7ee0, 0x68e0) + mstore(0x7f00, 0x6a80) + mstore(0x7f20, 0x6a20) + let q_eval_set_0 := mload(0x67a0) + let q_eval_set_1 := mload(0x6840) + let q_eval_set_2 := mload(0x6940) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x6cc0, 0x60) + let eval_p := add(0x7c40, 0x60) for { let i := 1 } lt(i, 0x8) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2648,12 +2691,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[3]: 2 commitment(s) - let q_eval_set_0 := mload(0x5e80) - let q_eval_set_1 := mload(0x5ea0) - let q_eval_set_2 := mload(0x5ec0) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x5ee0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x5f00), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x5f20), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + let q_eval_set_0 := mload(0x6e00) + let q_eval_set_1 := mload(0x6e20) + let q_eval_set_2 := mload(0x6e40) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6e60), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6e80), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x6ea0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0xc0), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0xe0), q_eval_set_1) mstore(add(Q_EVAL_SET_MPTR, 0x100), q_eval_set_2) @@ -2821,139 +2864,139 @@ contract Halo2Verifier { v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_4, r), r) - mcopy(0x6cc0, 0x6480, 0x80) - mstore(0x6d40, mload(add(X1_POWERS_MPTR, 0x20))) - mcopy(0x6d60, 0x6700, 0x80) - mstore(0x6de0, mload(add(X1_POWERS_MPTR, 0x40))) - mcopy(0x6e00, 0x6500, 0x80) - mstore(0x6e80, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0x6ea0, 0x6780, 0x80) - mstore(0x6f20, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0x6f40, 0x2c80, 0x80) - mstore(0x6fc0, mload(add(X1_POWERS_MPTR, 0xa0))) - mcopy(0x6fe0, 0x2a00, 0x80) - mstore(0x7060, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0x7080, 0x2a80, 0x80) - mstore(0x7100, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0x7120, 0x2b00, 0x80) - mstore(0x71a0, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0x71c0, 0x2b80, 0x80) - mstore(0x7240, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0x7260, 0x2c00, 0x80) - mstore(0x72e0, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0x7300, 0x2800, 0x80) - mstore(0x7380, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0x73a0, 0x2880, 0x80) - mstore(0x7420, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0x7440, 0x2900, 0x80) - mstore(0x74c0, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0x74e0, 0x2980, 0x80) - mstore(0x7560, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0x7580, 0x2d00, 0x80) - mstore(0x7600, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0x7620, 0x2d80, 0x80) - mstore(0x76a0, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0x76c0, 0x2e00, 0x80) - mstore(0x7740, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0x7760, 0x2e80, 0x80) - mstore(0x77e0, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0x7800, 0x2f00, 0x80) - mstore(0x7880, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0x78a0, 0x2f80, 0x80) - mstore(0x7920, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0x7940, 0x3180, 0x80) - mstore(0x79c0, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0x79e0, 0x3200, 0x80) - mstore(0x7a60, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0x7a80, 0x3800, 0x80) - mstore(0x7b00, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0x7b20, 0x3880, 0x80) - mstore(0x7ba0, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0x7bc0, 0x3900, 0x80) - mstore(0x7c40, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0x7c60, 0x3980, 0x80) - mstore(0x7ce0, mload(add(X1_POWERS_MPTR, 0x340))) - mcopy(0x7d00, 0x3a00, 0x80) - mstore(0x7d80, mload(add(X1_POWERS_MPTR, 0x360))) - mcopy(0x7da0, 0x3a80, 0x80) - mstore(0x7e20, mload(add(X1_POWERS_MPTR, 0x380))) - mcopy(0x7e40, 0x3b00, 0x80) - mstore(0x7ec0, mload(add(X1_POWERS_MPTR, 0x3a0))) - mcopy(0x7ee0, 0x3b80, 0x80) - mstore(0x7f60, mload(add(X1_POWERS_MPTR, 0x3c0))) - mcopy(0x7f80, 0x3c00, 0x80) - mstore(0x8000, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0x7c40, 0x7400, 0x80) + mstore(0x7cc0, mload(add(X1_POWERS_MPTR, 0x20))) + mcopy(0x7ce0, 0x7680, 0x80) + mstore(0x7d60, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0x7d80, 0x7480, 0x80) + mstore(0x7e00, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0x7e20, 0x7700, 0x80) + mstore(0x7ea0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0x7ec0, 0x3c00, 0x80) + mstore(0x7f40, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0x7f60, 0x3980, 0x80) + mstore(0x7fe0, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0x8000, 0x3a00, 0x80) + mstore(0x8080, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0x80a0, 0x3a80, 0x80) + mstore(0x8120, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0x8140, 0x3b00, 0x80) + mstore(0x81c0, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0x81e0, 0x3b80, 0x80) + mstore(0x8260, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0x8280, 0x3780, 0x80) + mstore(0x8300, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0x8320, 0x3800, 0x80) + mstore(0x83a0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0x83c0, 0x3880, 0x80) + mstore(0x8440, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0x8460, 0x3900, 0x80) + mstore(0x84e0, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0x8500, 0x3c80, 0x80) + mstore(0x8580, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0x85a0, 0x3d00, 0x80) + mstore(0x8620, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0x8640, 0x3d80, 0x80) + mstore(0x86c0, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0x86e0, 0x3e00, 0x80) + mstore(0x8760, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0x8780, 0x3e80, 0x80) + mstore(0x8800, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0x8820, 0x3f00, 0x80) + mstore(0x88a0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0x88c0, 0x4100, 0x80) + mstore(0x8940, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0x8960, 0x4180, 0x80) + mstore(0x89e0, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0x8a00, 0x4780, 0x80) + mstore(0x8a80, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0x8aa0, 0x4800, 0x80) + mstore(0x8b20, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0x8b40, 0x4880, 0x80) + mstore(0x8bc0, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0x8be0, 0x4900, 0x80) + mstore(0x8c60, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0x8c80, 0x4980, 0x80) + mstore(0x8d00, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0x8d20, 0x4a00, 0x80) + mstore(0x8da0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0x8dc0, 0x4a80, 0x80) + mstore(0x8e40, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0x8e60, 0x4b00, 0x80) + mstore(0x8ee0, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0x8f00, 0x4b80, 0x80) + mstore(0x8f80, mload(add(X1_POWERS_MPTR, 0x3e0))) let lin_query_scalar_31 := mload(add(X1_POWERS_MPTR, 0x400)) let lin_cur_scalar_31 := mulmod(lin_query_scalar_31, lin_one_minus_x_n, r) - mcopy(0x8020, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0x80a0, lin_cur_scalar_31) + mcopy(0x8fa0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0x9020, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x80c0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0x8140, lin_cur_scalar_31) + mcopy(0x9040, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0x90c0, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x8160, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0x81e0, lin_cur_scalar_31) + mcopy(0x90e0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0x9160, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x8200, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0x8280, lin_cur_scalar_31) - mcopy(0x82a0, 0x3000, 0x80) - mstore(0x8320, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0x8340, 0x3080, 0x80) - mstore(0x83c0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0x83e0, 0x3100, 0x80) - mstore(0x8460, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0x8480, 0x3280, 0x80) - mstore(0x8500, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) - mcopy(0x8520, 0x3300, 0x80) - mstore(0x85a0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) - mcopy(0x85c0, 0x3380, 0x80) - mstore(0x8640, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) - mcopy(0x8660, 0x3400, 0x80) - mstore(0x86e0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) - mcopy(0x8700, 0x3480, 0x80) - mstore(0x8780, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) - mcopy(0x87a0, 0x3500, 0x80) - mstore(0x8820, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) - mcopy(0x8840, 0x3580, 0x80) - mstore(0x88c0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) - mcopy(0x88e0, 0x3600, 0x80) - mstore(0x8960, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x140)), r)) - mcopy(0x8980, 0x3680, 0x80) - mstore(0x8a00, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x160)), r)) - mcopy(0x8a20, 0x3700, 0x80) - mstore(0x8aa0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x180)), r)) - mcopy(0x8ac0, 0x3780, 0x80) - mstore(0x8b40, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x1a0)), r)) - mcopy(0x8b60, 0x6680, 0x80) - mstore(0x8be0, x4_pow_1) - mcopy(0x8c00, 0x6800, 0x80) - mstore(0x8c80, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0x8ca0, 0x6880, 0x80) - mstore(0x8d20, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0x8d40, 0x6080, 0x80) - mstore(0x8dc0, x4_pow_2) - mcopy(0x8de0, 0x6100, 0x80) - mstore(0x8e60, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0x8e80, 0x6180, 0x80) - mstore(0x8f00, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) - mcopy(0x8f20, 0x6200, 0x80) - mstore(0x8fa0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_2, r)) - mcopy(0x8fc0, 0x6280, 0x80) - mstore(0x9040, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_2, r)) - mcopy(0x9060, 0x6300, 0x80) - mstore(0x90e0, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_2, r)) - mcopy(0x9100, 0x6380, 0x80) - mstore(0x9180, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_2, r)) - mcopy(0x91a0, 0x6400, 0x80) - mstore(0x9220, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_2, r)) - mcopy(0x9240, 0x6580, 0x80) - mstore(0x92c0, x4_pow_3) - mcopy(0x92e0, 0x6600, 0x80) - mstore(0x9360, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) - mcopy(0x9380, F_COM_MPTR, 0x80) - mstore(0x9400, x4_pow_4) + mcopy(0x9180, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0x9200, lin_cur_scalar_31) + mcopy(0x9220, 0x3f80, 0x80) + mstore(0x92a0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0x92c0, 0x4000, 0x80) + mstore(0x9340, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0x9360, 0x4080, 0x80) + mstore(0x93e0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0x9400, 0x4200, 0x80) + mstore(0x9480, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0x94a0, 0x4280, 0x80) + mstore(0x9520, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0x9540, 0x4300, 0x80) + mstore(0x95c0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0x95e0, 0x4380, 0x80) + mstore(0x9660, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0x9680, 0x4400, 0x80) + mstore(0x9700, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0x9720, 0x4480, 0x80) + mstore(0x97a0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0x97c0, 0x4500, 0x80) + mstore(0x9840, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0x9860, 0x4580, 0x80) + mstore(0x98e0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x140)), r)) + mcopy(0x9900, 0x4600, 0x80) + mstore(0x9980, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x160)), r)) + mcopy(0x99a0, 0x4680, 0x80) + mstore(0x9a20, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x180)), r)) + mcopy(0x9a40, 0x4700, 0x80) + mstore(0x9ac0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x1a0)), r)) + mcopy(0x9ae0, 0x7600, 0x80) + mstore(0x9b60, x4_pow_1) + mcopy(0x9b80, 0x7780, 0x80) + mstore(0x9c00, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0x9c20, 0x7800, 0x80) + mstore(0x9ca0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0x9cc0, 0x7000, 0x80) + mstore(0x9d40, x4_pow_2) + mcopy(0x9d60, 0x7080, 0x80) + mstore(0x9de0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0x9e00, 0x7100, 0x80) + mstore(0x9e80, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0x9ea0, 0x7180, 0x80) + mstore(0x9f20, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_2, r)) + mcopy(0x9f40, 0x7200, 0x80) + mstore(0x9fc0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_2, r)) + mcopy(0x9fe0, 0x7280, 0x80) + mstore(0xa060, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_2, r)) + mcopy(0xa080, 0x7300, 0x80) + mstore(0xa100, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_2, r)) + mcopy(0xa120, 0x7380, 0x80) + mstore(0xa1a0, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_2, r)) + mcopy(0xa1c0, 0x7500, 0x80) + mstore(0xa240, x4_pow_3) + mcopy(0xa260, 0x7580, 0x80) + mstore(0xa2e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xa300, F_COM_MPTR, 0x80) + mstore(0xa380, x4_pow_4) if success { - success := staticcall(gas(), 0x0c, 0x6cc0, 0x2760, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0x7c40, 0x2760, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -2965,28 +3008,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } diff --git a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol index 7b15639cf..dc1527a1e 100644 --- a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol @@ -136,19 +136,19 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x08e0), 0x0000000000000000000000000000000000000000000000000000000000080000) // quotient_const mstore(add(payload, 0x0900), 0x0000000000000000000000000000000000000000000000000000000000020000) // quotient_const mstore(add(payload, 0x0920), 0x0000000000000000000000000000000000000000000000000000000100000000) // quotient_const - mstore(add(payload, 0x0940), 0x055860105b8005590008060d000b0200011b00001b00010558601058800558a0) // quotient_program - mstore(add(payload, 0x0960), 0x08060d000b0400011b00021b0003210001000007000158200258400358600458) // quotient_program - mstore(add(payload, 0x0980), 0x800558a00658c00758e00859000959800a59a00b59c00c59e00d5a000e5a200b) // quotient_program - mstore(add(payload, 0x09a0), 0x070000210001000007000158200258400f58601058801158a00658c00758e012) // quotient_program - mstore(add(payload, 0x09c0), 0x59001359801459a00b59c00c59e0155a00165a200b0800000918115a40135940) // quotient_program - mstore(add(payload, 0x09e0), 0x17105a60055a8008060d000b090000091b115a40135aa01a1359401910596005) // quotient_program - mstore(add(payload, 0x0a00), 0x5a8008060d000b0a0000091e1159c01359e01d1358201c10584005586008060d) // quotient_program - mstore(add(payload, 0x0a20), 0x000b0a00010921115a40135aa01a135940201359601f105a60055a8008060d00) // quotient_program - mstore(add(payload, 0x0a40), 0x0b0b000009231159c01359e01d1358201b135840221058c005586008060d000b) // quotient_program - mstore(add(payload, 0x0a60), 0x0b00011c245920255940265960275a40175aa0285ac0295ae0105a60055a8008) // quotient_program - mstore(add(payload, 0x0a80), 0x060d000b0c0000090008105ac0115ac00d000b0c00010900081059201159200d) // quotient_program - mstore(add(payload, 0x0aa0), 0x000b0c0001090008105ae0115ae00d000b0c00011c0058800058a00059000059) // quotient_program - mstore(add(payload, 0x0ac0), 0x800059a0005a00005a20055a80135b002a08060d000b0d0000191f0000000000) // quotient_program + mstore(add(payload, 0x0940), 0x0567e0106b0005688008060d000b0200011b00001b00010567e0106800056820) // quotient_program + mstore(add(payload, 0x0960), 0x08060d000b0400011b00021b0003210001000007000167a00267c00367e00468) // quotient_program + mstore(add(payload, 0x0980), 0x000568200668400768600868800969000a69200b69400c69600d69800e69a00b) // quotient_program + mstore(add(payload, 0x09a0), 0x070000210001000007000167a00267c00f67e010680011682006684007686012) // quotient_program + mstore(add(payload, 0x09c0), 0x68801369001469200b69400c69601569801669a00b08000009181169c01368c0) // quotient_program + mstore(add(payload, 0x09e0), 0x171069e0056a0008060d000b090000091b1169c0136a201a1368c0191068e005) // quotient_program + mstore(add(payload, 0x0a00), 0x6a0008060d000b0a0000091e1169401369601d1367a01c1067c00567e008060d) // quotient_program + mstore(add(payload, 0x0a20), 0x000b0a000109211169c0136a201a1368c0201368e01f1069e0056a0008060d00) // quotient_program + mstore(add(payload, 0x0a40), 0x0b0b000009231169401369601d1367a01b1367c0221068400567e008060d000b) // quotient_program + mstore(add(payload, 0x0a60), 0x0b00011c2468a02568c02668e02769c0176a20286a40296a601069e0056a0008) // quotient_program + mstore(add(payload, 0x0a80), 0x060d000b0c0000090008106a40116a400d000b0c00010900081068a01168a00d) // quotient_program + mstore(add(payload, 0x0aa0), 0x000b0c0001090008106a60116a600d000b0c00011c0068000068200068800069) // quotient_program + mstore(add(payload, 0x0ac0), 0x000069200069800069a0056a00136a802a08060d000b0d0000191f0000000000) // quotient_program // Fixed-column commitment 0, stored as one // EIP-2537 padded uncompressed G1 slot. mstore(add(payload, 0x0ae0), 0x000000000000000000000000000000001197f0fef4c3a1846341b3c9bbaf1bab) // fixed_comms[0].x_hi From 0b274854bb39710fe3939d0796123aa8ffdf6dac Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 20:34:48 +0100 Subject: [PATCH 68/72] Self-certify emitted quotient VM bytecode at render time The quotient lowering runs a peephole optimizer whose shape recognizers and run-compaction pass rewrite seven-limb foreign-field expressions into superinstructions. Both are pure encoding choices that must preserve the evaluated polynomial exactly, but their correctness was only checked indirectly by fixture-circuit trace differentials in CI. A recognizer bug on a previously unseen gate shape could therefore ship a wrong verifier without any generator-time failure. Add a two-part certification pass that runs on every LoweringPlan: * vm/reference.rs is an independent interpreter for finalized quotient bytecode, deliberately written against the ABI without sharing execution code with the emitter. * vm/certify.rs re-executes the emitted program with that reference interpreter and compares each identity against direct evaluation of the QuotientExpr tree it was lowered from, then repeats the check against a second build produced with the limb superinstructions disabled. Any disagreement fails the render before the bytecode can be pinned into a verifying key. Wire this in from LoweringPlan::new and validate_generator_invariants so the check runs unconditionally, not just in tests. Expose QuotientProgramBuilder::with_limb_vm_ops outside cfg(test) so the generator can build the baseline program, and add build_quotient_program_items_with_limb_ops to lower the same item stream under an explicit opcode policy. Add QuotientVmTestCircuit and lowering_plan_certifies_emitted_quotient_bytecode so the fast test suite actually reaches the VM path (the existing LoweringPlanTestCircuit has only one gate and fits entirely in the inline prefix). The test also asserts that the emitted program uses LIN7, so it fails loudly if the planner ever stops routing these identities through the recognizer. Update QUOTIENT_EVALUATOR_9KB_BYTECODE.md to fix the stale module path and document the new certification gate. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../QUOTIENT_EVALUATOR_9KB_BYTECODE.md | 12 +- proofs/solidity-verifier/src/lowering/plan.rs | 24 +- .../src/lowering/quotient.rs | 21 +- .../lowering/quotient_numerator/vm/certify.rs | 270 +++++++++ .../src/lowering/quotient_numerator/vm/mod.rs | 4 +- .../quotient_numerator/vm/reference.rs | 548 ++++++++++++++++++ .../solidity-verifier/src/lowering/tests.rs | 141 +++++ 7 files changed, 1015 insertions(+), 5 deletions(-) create mode 100644 proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs create mode 100644 proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs diff --git a/proofs/solidity-verifier/docs/reference/QUOTIENT_EVALUATOR_9KB_BYTECODE.md b/proofs/solidity-verifier/docs/reference/QUOTIENT_EVALUATOR_9KB_BYTECODE.md index 63e06a6b2..64e5c6230 100644 --- a/proofs/solidity-verifier/docs/reference/QUOTIENT_EVALUATOR_9KB_BYTECODE.md +++ b/proofs/solidity-verifier/docs/reference/QUOTIENT_EVALUATOR_9KB_BYTECODE.md @@ -104,8 +104,16 @@ normal ABI call. The largest win was the compact quotient VM. Instead of rendering most identities as Yul source, the generator lowers them to -a small bytecode language in `src/lowering/quotient/mod.rs`. The runtime consumer -is `templates/partials/quotient_numerator/QuotientNumeratorBlock.yul`. +a small bytecode language in `src/lowering/quotient_numerator/vm/mod.rs`. The +runtime consumer is +`templates/partials/quotient_numerator/QuotientNumeratorBlock.yul`. + +Every emitted program is certified before it can be pinned into a verifying key: +`src/lowering/quotient_numerator/vm/certify.rs` re-executes the finalized +bytecode with the independent interpreter in +`src/lowering/quotient_numerator/vm/reference.rs` and compares each identity +against direct evaluation of the expression tree it was lowered from, and +against a second build with the limb superinstructions disabled. The VK payload carries: diff --git a/proofs/solidity-verifier/src/lowering/plan.rs b/proofs/solidity-verifier/src/lowering/plan.rs index f8aaea1ad..9f3f60017 100644 --- a/proofs/solidity-verifier/src/lowering/plan.rs +++ b/proofs/solidity-verifier/src/lowering/plan.rs @@ -12,7 +12,9 @@ use crate::lowering::{ kzg, layout, layout::memory::{PcsMemoryRequirements, VerifierMemoryLayout, VerifierMemoryLayoutConfig}, quotient::{QuotientComputationBlocks, QuotientHelperFlags, QuotientStateSlots}, - quotient_numerator::vm::{QuotientProgramBuild, QuotientProgramPlan, RepackedProofLayoutPlan}, + quotient_numerator::vm::{ + certify, QuotientProgramBuild, QuotientProgramPlan, RepackedProofLayoutPlan, + }, render::{Halo2VerifyingKey, QuotientExternal, QuotientProgram}, VerifierBuildInputs, }; @@ -163,6 +165,22 @@ impl LoweringPlan { }; plan.validate_generator_invariants() .unwrap_or_else(|err| panic!("generator invariant violation: {err}")); + + // Certify the limb superinstructions against a generic-opcode build of + // the same identity stream. This needs `inputs`, so it runs here rather + // than inside `validate_generator_invariants`. + let baseline_build = inputs.build_quotient_program_items_with_limb_ops( + &plan.quotient.plan.items, + &plan.quotient.plan.selector_fold, + false, + ); + certify::certify_quotient_builds_agree( + &plan.quotient.plan, + &plan.quotient.build, + &baseline_build, + ) + .unwrap_or_else(|err| panic!("quotient dual-build certification failed: {err}")); + plan } @@ -320,6 +338,10 @@ impl LoweringPlan { self.quotient.program.eval_numer_mptr, self.quotient.state_slots.eval_numer_mptr )); } + // Prove the emitted bytecode still evaluates the identities it was + // lowered from, before it can be pinned into a verifying key. + certify::certify_quotient_program(&self.quotient.plan, &self.quotient.build) + .map_err(|err| format!("quotient program certification failed: {err}"))?; Ok(()) } diff --git a/proofs/solidity-verifier/src/lowering/quotient.rs b/proofs/solidity-verifier/src/lowering/quotient.rs index f1b38498b..b9b0bf28e 100644 --- a/proofs/solidity-verifier/src/lowering/quotient.rs +++ b/proofs/solidity-verifier/src/lowering/quotient.rs @@ -1959,7 +1959,26 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { items: &[QuotientProgramItem], selector_fold: &SelectorFoldPlan, ) -> QuotientProgramBuild { - let mut builder = QuotientProgramBuilder::default(); + self.build_quotient_program_items_with_limb_ops( + items, + selector_fold, + crate::lowering::config::DEFAULT_QUOTIENT_LIMB_VM_OPS, + ) + } + + /// Lower the same item stream under an explicit limb-opcode policy. + /// + /// Rendering always uses the crate default. The `false` build exists so the + /// generator can cross-check the limb superinstructions against a program + /// that uses only generic Fr opcodes; see + /// `quotient_numerator::vm::certify::certify_quotient_builds_agree`. + pub(super) fn build_quotient_program_items_with_limb_ops( + &self, + items: &[QuotientProgramItem], + selector_fold: &SelectorFoldPlan, + limb_vm_ops: bool, + ) -> QuotientProgramBuild { + let mut builder = QuotientProgramBuilder::with_limb_vm_ops(limb_vm_ops); // Lower the logical plan into bytecode in one pass. Repeated // subexpressions are emitted directly; native callbacks remain opaque // markers because their arithmetic is emitted as separate Yul kernels diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs new file mode 100644 index 000000000..a166ed24e --- /dev/null +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: CC0-1.0 +//! Render-time self-certification of the emitted quotient VM program. +//! +//! The quotient lowering runs a peephole optimizer: shape recognizers rewrite +//! seven-limb foreign-field expressions into superinstructions, and a +//! run-compaction pass rewrites adjacent affine terms into counted opcodes. Both +//! are pure encoding choices that must preserve the evaluated polynomial +//! exactly. This module proves that they did, for the specific program this +//! render is about to emit, by executing the finalized bytecode with the +//! independent interpreter in [`super::reference`] and comparing each identity +//! against direct evaluation of the [`QuotientExpr`] tree it was lowered from. +//! +//! This is a generator-time gate, not a test: every artifact the generator +//! produces is certified before it can be pinned into a verifying key, so a +//! recognizer bug on a previously unseen gate shape fails the render instead of +//! shipping a wrong verifier. +//! +//! See [`super::reference`] for what this does and does not cover. + +use ff::Field; +use midnight_curves::Fq; + +use super::{ + quotient_op_len, + reference::{eval_quotient_expr, eval_quotient_identity, QuotientRefMemory}, + QuotientProgramBuild, QuotientProgramItem, QuotientProgramPlan, QuotientTarget, Q_OP_FOLD_MAIN, + Q_OP_FOLD_SELECTOR, Q_OP_NATIVE_IDENTITY, Q_OP_NATIVE_LOOKUP, Q_OP_NATIVE_PERMUTATION, +}; + +/// Seed for the certification assignment. +/// +/// Fixed rather than random so a failing render reproduces exactly. The check +/// does not need unpredictability: the program is fixed at codegen time and +/// cannot adapt to the seed. +const QUOTIENT_CERTIFY_SEED: u64 = 0x6d69_6466_616c_6c01; + +/// Certify that the emitted bytecode evaluates the planned identities. +/// +/// Runs after [`super::validate_quotient_program`], which has already proven the +/// stream decodes and is stack-safe; this pass assumes well-formedness and +/// checks *meaning*. +pub(crate) fn certify_quotient_program( + plan: &QuotientProgramPlan, + build: &QuotientProgramBuild, +) -> Result<(), String> { + let mut mem = QuotientRefMemory::new(QUOTIENT_CERTIFY_SEED); + let bytes = &build.bytes; + let mut cursor = 0usize; + + for (item_idx, item) in plan.items.iter().enumerate() { + match item { + QuotientProgramItem::Identity(identity) => { + let (expr_end, fold_op) = identity_segment(bytes, cursor) + .map_err(|err| format!("quotient item {item_idx}: {err}"))?; + + let actual = + eval_quotient_identity(&bytes[cursor..expr_end], &build.consts, &mut mem) + .map_err(|err| { + format!( + "quotient identity {} ({:?}): {err}", + identity.meta.global_index, identity.meta.source + ) + })?; + let expected = eval_quotient_expr(&identity.expr, &mut mem); + + if actual != expected { + return Err(format!( + "quotient VM miscompiled identity {} ({:?}): bytecode evaluates to {:?} \ + but its expression evaluates to {:?}. This is a codegen bug in the \ + quotient lowering (shape recognizer, operand packing, or run \ + compaction), not a proof or verifying-key problem.", + identity.meta.global_index, identity.meta.source, actual, expected + )); + } + + check_fold(bytes, expr_end, fold_op, identity, plan).map_err(|err| { + format!( + "quotient identity {} ({:?}): {err}", + identity.meta.global_index, identity.meta.source + ) + })?; + + cursor = expr_end + quotient_op_len(bytes, expr_end); + } + QuotientProgramItem::NativePermutation + | QuotientProgramItem::NativeLookup + | QuotientProgramItem::NativeIdentity(_) => { + // Native markers carry no arithmetic here: the Yul template + // substitutes generated straight-line kernels. Certify only + // that the marker sits at the planned stream position. + let expected_op = match item { + QuotientProgramItem::NativePermutation => Q_OP_NATIVE_PERMUTATION, + QuotientProgramItem::NativeLookup => Q_OP_NATIVE_LOOKUP, + _ => Q_OP_NATIVE_IDENTITY, + }; + let actual_op = *bytes.get(cursor).ok_or_else(|| { + format!("quotient item {item_idx}: program ended before native marker") + })?; + if actual_op != expected_op { + return Err(format!( + "quotient item {item_idx}: expected native marker {expected_op:#x} at byte \ + {cursor}, found {actual_op:#x}" + )); + } + if let QuotientProgramItem::NativeIdentity(native_idx) = item { + // The marker index is a big-endian u16, not a single byte. + let hi = *bytes.get(cursor + 1).ok_or_else(|| { + format!("quotient item {item_idx}: truncated native identity index") + })?; + let lo = *bytes.get(cursor + 2).ok_or_else(|| { + format!("quotient item {item_idx}: truncated native identity index") + })?; + let encoded = u16::from_be_bytes([hi, lo]) as usize; + if encoded != *native_idx { + return Err(format!( + "quotient item {item_idx}: native identity index {encoded} does not \ + match planned index {native_idx}" + )); + } + } + cursor += quotient_op_len(bytes, cursor); + } + } + } + + if cursor != bytes.len() { + return Err(format!( + "quotient program has {} trailing byte(s) after the planned item stream", + bytes.len() - cursor + )); + } + + Ok(()) +} + +/// Find the end of one identity expression and the fold opcode that closes it. +fn identity_segment(bytes: &[u8], start: usize) -> Result<(usize, u8), String> { + let mut idx = start; + while idx < bytes.len() { + let op = bytes[idx]; + match op { + Q_OP_FOLD_MAIN | Q_OP_FOLD_SELECTOR => return Ok((idx, op)), + Q_OP_NATIVE_PERMUTATION | Q_OP_NATIVE_LOOKUP | Q_OP_NATIVE_IDENTITY => { + return Err(format!( + "native marker {op:#x} at byte {idx} interrupts an identity expression" + )); + } + _ => idx += quotient_op_len(bytes, idx), + } + } + Err(format!( + "identity expression starting at byte {start} is never folded" + )) +} + +/// Check that the emitted fold matches the planned target and selector gap. +fn check_fold( + bytes: &[u8], + fold_idx: usize, + fold_op: u8, + identity: &super::QuotientIdentity, + plan: &QuotientProgramPlan, +) -> Result<(), String> { + match (fold_op, identity.target) { + (Q_OP_FOLD_MAIN, QuotientTarget::Main) => Ok(()), + (Q_OP_FOLD_SELECTOR, QuotientTarget::Selector(selector_idx)) => { + let encoded_idx = *bytes + .get(fold_idx + 1) + .ok_or_else(|| "truncated selector fold index".to_string())? + as usize; + if encoded_idx != selector_idx { + return Err(format!( + "selector fold targets bucket {encoded_idx} but the plan says {selector_idx}" + )); + } + let hi = *bytes + .get(fold_idx + 2) + .ok_or_else(|| "truncated selector fold gap".to_string())?; + let lo = *bytes + .get(fold_idx + 3) + .ok_or_else(|| "truncated selector fold gap".to_string())?; + let encoded_gap = u16::from_be_bytes([hi, lo]) as usize; + let planned_gap = plan + .selector_fold + .gap_for(identity) + .ok_or_else(|| "selector identity has no planned fold gap".to_string())?; + if encoded_gap != planned_gap { + return Err(format!( + "selector fold gap {encoded_gap} does not match planned gap {planned_gap}" + )); + } + Ok(()) + } + (fold_op, target) => Err(format!( + "fold opcode {fold_op:#x} does not match planned target {target:?}" + )), + } +} + +/// Certify that two builds of the same identity stream agree. +/// +/// The limb-aware superinstructions are the least principled part of the +/// lowering: they pattern-match algebraic shapes out of a commutative-ring +/// expression tree. Building the same stream with those recognizers disabled +/// yields a program using only `PUSH`/`ADD`/`MUL`/`NEG`, which is +/// straightforward to audit. Requiring the two to agree identity-by-identity +/// turns every recognizer from trusted code into a checked optimization. +pub(crate) fn certify_quotient_builds_agree( + plan: &QuotientProgramPlan, + optimized: &QuotientProgramBuild, + baseline: &QuotientProgramBuild, +) -> Result<(), String> { + let mut mem = QuotientRefMemory::new(QUOTIENT_CERTIFY_SEED); + + let optimized_values = identity_values(plan, optimized, &mut mem)?; + let baseline_values = identity_values(plan, baseline, &mut mem)?; + + if optimized_values.len() != baseline_values.len() { + return Err(format!( + "quotient dual build disagrees on identity count: {} with limb opcodes, {} without", + optimized_values.len(), + baseline_values.len() + )); + } + + for (position, (lhs, rhs)) in optimized_values.iter().zip(baseline_values.iter()).enumerate() { + if lhs != rhs { + return Err(format!( + "quotient limb superinstructions changed the value of interpreted identity at \ + stream position {position}: {lhs:?} with limb opcodes, {rhs:?} without. One of \ + the shape recognizers is unsound for this gate shape." + )); + } + } + + Ok(()) +} + +/// Evaluate every interpreted identity in one build, in stream order. +fn identity_values( + plan: &QuotientProgramPlan, + build: &QuotientProgramBuild, + mem: &mut QuotientRefMemory, +) -> Result, String> { + let bytes = &build.bytes; + let mut values = Vec::new(); + let mut cursor = 0usize; + + for item in &plan.items { + match item { + QuotientProgramItem::Identity(_) => { + let (expr_end, _) = identity_segment(bytes, cursor)?; + values.push(eval_quotient_identity( + &bytes[cursor..expr_end], + &build.consts, + mem, + )?); + cursor = expr_end + quotient_op_len(bytes, expr_end); + } + _ => { + // Native markers evaluate no bytecode; both builds emit the + // same marker at the same stream position. + values.push(Fq::ZERO); + cursor += quotient_op_len(bytes, cursor); + } + } + } + + Ok(values) +} diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index 3c26bee4b..67a17b82f 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -52,6 +52,9 @@ //! underflow, and identity-boundary stack leaks before the bytes can be pinned //! into a VK runtime. +pub(crate) mod certify; +pub(crate) mod reference; + use std::collections::{HashMap, HashSet}; use ff::{Field, PrimeField}; @@ -1096,7 +1099,6 @@ impl Default for QuotientProgramBuilder { impl QuotientProgramBuilder { /// Create a builder, optionally enabling limb-specialized opcode emission. - #[cfg(test)] pub(crate) fn with_limb_vm_ops(enabled: bool) -> Self { Self { limb_vm_ops: enabled, diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs new file mode 100644 index 000000000..7cd60866e --- /dev/null +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: CC0-1.0 +//! Independent reference interpreter for finalized quotient VM bytecode. +//! +//! This is the second implementation of the quotient VM ABI. It exists so the +//! generator can certify its own output: for every render, the emitted bytecode +//! is executed here and compared against direct evaluation of the +//! [`QuotientExpr`] trees the bytecode was lowered from. A disagreement means +//! the emitter, one of its shape recognizers, or the run-compaction pass +//! miscompiled an identity, and the render is rejected before any artifact is +//! produced. +//! +//! Why a random-assignment check is sufficient: the quotient program is fixed +//! per verifying key and never sees attacker input, and Fr arithmetic has no +//! edge cases. Any miscompilation therefore yields a *value-independent* wrong +//! polynomial, which disagrees with the correct one at a random assignment with +//! probability `1 - deg/|Fr|`. One evaluation at a pseudorandom point is enough +//! to catch it with overwhelming probability. +//! +//! Scope. This certifies the **emitter to reference-interpreter** leg, which is +//! where the shape recognizers in the parent module live. The +//! **reference-interpreter to Yul** leg is covered separately by the opcode and +//! memory-token table conformance tests and by the per-identity Rust/Solidity +//! trace differential on fixture circuits. Identities executed as inline Yul, +//! native callbacks, or the structured tail are not lowered to bytecode at all, +//! so this module evaluates them directly from their expression trees and they +//! remain covered only by those other two mechanisms. + +use std::collections::HashMap; + +use ff::{Field, PrimeField}; +use midnight_curves::Fq; +use ruint::aliases::U256; +use sha3::{Digest, Keccak256}; + +use super::{ + QuotientExpr, QuotientMem, QUOTIENT_VM_LIMBS, QUOTIENT_VM_PAIRWISE_COEFFS, + Q_MODARITH7_FLAG_COND, Q_MODARITH7_FLAG_CONST, Q_OP_ADD, Q_OP_ADD_CONST, Q_OP_ADD_CONST_U8, + Q_OP_ADD_MEM_U16, Q_OP_ADD_MUL_CONST_U8_MEM_U16, Q_OP_ADD_MUL_MEM_MEM, + Q_OP_ADD_MUL_MEM_MEM_CONST_U8, Q_OP_AFFINE_SUM, Q_OP_BILIN7_PAIRWISE, Q_OP_BILIN7_ROW, + Q_OP_FOLD_MAIN, Q_OP_FOLD_SELECTOR, Q_OP_LIN7, Q_OP_MODARITH7, Q_OP_MUL, Q_OP_MUL_CONST, + Q_OP_MUL_CONST_U8, Q_OP_MUL_MEM_U16, Q_OP_NATIVE_IDENTITY, Q_OP_NATIVE_LOOKUP, + Q_OP_NATIVE_PERMUTATION, Q_OP_NEG, Q_OP_POW5, Q_OP_PUSH_CONST, Q_OP_PUSH_CONST_U8, + Q_OP_PUSH_MEM_LITERAL, Q_OP_PUSH_MEM_TOKEN, Q_OP_PUSH_MEM_TOKEN_OFFSET, Q_OP_PUSH_MEM_U16, + Q_OP_RUN_ADD_MUL_CONST_U8_MEM_U16, Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8, +}; +use crate::lowering::layout::WORD_BYTES; + +/// Deterministic pseudorandom assignment for every verifier memory slot. +/// +/// The bytecode addresses memory by absolute pointer or by symbolic token, and +/// the expression trees address the exact same slots. Deriving each value from +/// its own address means both sides observe identical memory without the +/// certifier having to enumerate the live address set up front, which in turn +/// means a pointer-packing bug shows up as a value mismatch rather than as a +/// missing map key. +#[derive(Clone, Debug)] +pub(crate) struct QuotientRefMemory { + seed: u64, + cache: HashMap<(u8, u32), Fq>, +} + +impl QuotientRefMemory { + /// Build an assignment for one certification run. + pub(crate) fn new(seed: u64) -> Self { + Self { + seed, + cache: HashMap::new(), + } + } + + /// Value at an absolute memory pointer. + pub(crate) fn literal(&mut self, ptr: u32) -> Fq { + self.derive(0, ptr) + } + + /// Value behind a symbolic memory token, optionally offset. + pub(crate) fn token(&mut self, token: u8, offset: u32) -> Fq { + // Tokens resolve to generated addresses disjoint from the literal + // pointer space, so they get their own domain tag. + self.derive(1 + token, offset) + } + + /// Derive one field element from a domain-separated address. + fn derive(&mut self, domain: u8, address: u32) -> Fq { + if let Some(value) = self.cache.get(&(domain, address)) { + return *value; + } + let mut hasher = Keccak256::new(); + hasher.update(b"midfall/quotient-vm/reference-memory/v1"); + hasher.update(self.seed.to_be_bytes()); + hasher.update([domain]); + hasher.update(address.to_be_bytes()); + let lo = hasher.finalize(); + + let mut hasher = Keccak256::new(); + hasher.update(b"midfall/quotient-vm/reference-memory/v1/hi"); + hasher.update(lo); + let hi = hasher.finalize(); + + let mut wide = [0u8; 64]; + wide[..32].copy_from_slice(&lo); + wide[32..].copy_from_slice(&hi); + let value = >::from_uniform_bytes(&wide); + self.cache.insert((domain, address), value); + value + } +} + +/// Evaluate a typed quotient expression directly, without going through the VM. +/// +/// This is the oracle side of the certification: it follows the same shape as +/// `Expression::evaluate` in the native verifier and knows nothing about +/// opcodes, shape recognizers, or operand packing. +pub(crate) fn eval_quotient_expr(expr: &QuotientExpr, mem: &mut QuotientRefMemory) -> Fq { + match expr { + QuotientExpr::Const(value) => fq_from_u256(*value), + QuotientExpr::Mem(QuotientMem::Literal(ptr)) => mem.literal(*ptr), + QuotientExpr::Mem(QuotientMem::Token(token)) => mem.token(*token, 0), + QuotientExpr::Mem(QuotientMem::TokenOffset(token, offset)) => mem.token(*token, *offset), + QuotientExpr::Add(lhs, rhs) => eval_quotient_expr(lhs, mem) + eval_quotient_expr(rhs, mem), + QuotientExpr::Mul(lhs, rhs) => eval_quotient_expr(lhs, mem) * eval_quotient_expr(rhs, mem), + QuotientExpr::Neg(inner) => -eval_quotient_expr(inner, mem), + } +} + +/// Evaluate one identity expression subprogram, up to but excluding its fold. +/// +/// Returns an error rather than panicking: this runs inside the generator, and +/// a malformed stream must surface as a `GeneratorError`, not a process abort. +pub(crate) fn eval_quotient_identity( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, +) -> Result { + let mut stack: Vec = Vec::new(); + let mut idx = 0usize; + + // Pop helpers keep the error path uniform; the offline validator has + // already proven depth safety, so these only fire on validator drift. + macro_rules! pop { + () => { + stack + .pop() + .ok_or_else(|| format!("reference VM stack underflow at byte {idx}"))? + }; + } + + while idx < bytes.len() { + let op = bytes[idx]; + match op { + Q_OP_PUSH_CONST => { + let slot = read_u16(bytes, idx + 1)? as usize; + stack.push(const_at(consts, slot, idx)?); + idx += 3; + } + Q_OP_PUSH_CONST_U8 => { + let slot = byte_at(bytes, idx + 1)? as usize; + stack.push(const_at(consts, slot, idx)?); + idx += 2; + } + Q_OP_PUSH_MEM_LITERAL => { + let ptr = read_u32(bytes, idx + 1)?; + stack.push(mem.literal(ptr)); + idx += 5; + } + Q_OP_PUSH_MEM_U16 => { + let ptr = read_u16(bytes, idx + 1)? as u32; + stack.push(mem.literal(ptr)); + idx += 3; + } + Q_OP_PUSH_MEM_TOKEN => { + let token = byte_at(bytes, idx + 1)?; + stack.push(mem.token(token, 0)); + idx += 2; + } + Q_OP_PUSH_MEM_TOKEN_OFFSET => { + let token = byte_at(bytes, idx + 1)?; + let offset = read_u32(bytes, idx + 2)?; + stack.push(mem.token(token, offset)); + idx += 6; + } + Q_OP_ADD => { + let rhs = pop!(); + let lhs = pop!(); + stack.push(lhs + rhs); + idx += 1; + } + Q_OP_MUL => { + let rhs = pop!(); + let lhs = pop!(); + stack.push(lhs * rhs); + idx += 1; + } + Q_OP_NEG => { + let value = pop!(); + stack.push(-value); + idx += 1; + } + Q_OP_POW5 => { + let value = pop!(); + let squared = value * value; + stack.push(value * squared * squared); + idx += 1; + } + Q_OP_ADD_CONST_U8 | Q_OP_MUL_CONST_U8 => { + let slot = byte_at(bytes, idx + 1)? as usize; + let value = const_at(consts, slot, idx)?; + let acc = pop!(); + stack.push(if op == Q_OP_ADD_CONST_U8 { + acc + value + } else { + acc * value + }); + idx += 2; + } + Q_OP_ADD_CONST | Q_OP_MUL_CONST => { + let slot = read_u16(bytes, idx + 1)? as usize; + let value = const_at(consts, slot, idx)?; + let acc = pop!(); + stack.push(if op == Q_OP_ADD_CONST { + acc + value + } else { + acc * value + }); + idx += 3; + } + Q_OP_ADD_MEM_U16 | Q_OP_MUL_MEM_U16 => { + let ptr = read_u16(bytes, idx + 1)? as u32; + let value = mem.literal(ptr); + let acc = pop!(); + stack.push(if op == Q_OP_ADD_MEM_U16 { + acc + value + } else { + acc * value + }); + idx += 3; + } + Q_OP_ADD_MUL_MEM_MEM_CONST_U8 => { + let acc = pop!(); + let (term, len) = affine_product_term(bytes, consts, mem, idx + 1)?; + stack.push(acc + term); + idx += 1 + len; + } + Q_OP_ADD_MUL_CONST_U8_MEM_U16 => { + let acc = pop!(); + let (term, len) = affine_linear_term(bytes, consts, mem, idx + 1)?; + stack.push(acc + term); + idx += 1 + len; + } + Q_OP_ADD_MUL_MEM_MEM => { + let lhs = read_u16(bytes, idx + 1)? as u32; + let rhs = read_u16(bytes, idx + 3)? as u32; + let acc = pop!(); + stack.push(acc + mem.literal(lhs) * mem.literal(rhs)); + idx += 5; + } + Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8 | Q_OP_RUN_ADD_MUL_CONST_U8_MEM_U16 => { + // Run compaction is a pure encoding change: the same terms in + // the same order, with one shared count instead of repeated + // opcode bytes. + let count = read_u16(bytes, idx + 1)? as usize; + if count == 0 { + return Err(format!("reference VM zero-length run at byte {idx}")); + } + let mut cursor = idx + 3; + let mut acc = pop!(); + for _ in 0..count { + let (term, len) = if op == Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8 { + affine_product_term(bytes, consts, mem, cursor)? + } else { + affine_linear_term(bytes, consts, mem, cursor)? + }; + acc += term; + cursor += len; + } + stack.push(acc); + idx = cursor; + } + Q_OP_AFFINE_SUM => { + // Mixed run: all linear terms first, then all product terms, + // matching `compact_quotient_runs`. + let lin_count = read_u16(bytes, idx + 1)? as usize; + let product_count = read_u16(bytes, idx + 3)? as usize; + if lin_count == 0 || product_count == 0 { + return Err(format!( + "reference VM AFFINE_SUM at byte {idx} requires nonzero counts" + )); + } + let mut cursor = idx + 5; + let mut acc = pop!(); + for _ in 0..lin_count { + let (term, len) = affine_linear_term(bytes, consts, mem, cursor)?; + acc += term; + cursor += len; + } + for _ in 0..product_count { + let (term, len) = affine_product_term(bytes, consts, mem, cursor)?; + acc += term; + cursor += len; + } + stack.push(acc); + idx = cursor; + } + Q_OP_LIN7 => { + let (value, len) = limb_linear_form(bytes, consts, mem, idx + 1)?; + stack.push(value); + idx += 1 + len; + } + Q_OP_BILIN7_ROW => { + let (value, len) = limb_row_form(bytes, consts, mem, idx + 1)?; + stack.push(value); + idx += 1 + len; + } + Q_OP_BILIN7_PAIRWISE => { + let (value, len) = limb_pairwise_form(bytes, consts, mem, idx + 1)?; + stack.push(value); + idx += 1 + len; + } + Q_OP_MODARITH7 => { + let (value, len) = modarith7_form(bytes, consts, mem, idx + 1)?; + stack.push(value); + idx += 1 + len; + } + Q_OP_FOLD_MAIN + | Q_OP_FOLD_SELECTOR + | Q_OP_NATIVE_PERMUTATION + | Q_OP_NATIVE_LOOKUP + | Q_OP_NATIVE_IDENTITY => { + return Err(format!( + "reference VM found stream opcode {op:#x} inside an identity expression at byte {idx}" + )); + } + _ => { + return Err(format!("reference VM unknown opcode {op:#x} at byte {idx}")); + } + } + } + + if stack.len() != 1 { + return Err(format!( + "reference VM identity left {} value(s) on the stack, expected 1", + stack.len() + )); + } + Ok(stack.pop().expect("checked length")) +} + +/// Decode one `const * mload(ptr)` term and return its byte length. +fn affine_linear_term( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let ptr = read_u16(bytes, idx)? as u32; + let slot = byte_at(bytes, idx + 2)? as usize; + Ok((const_at(consts, slot, idx)? * mem.literal(ptr), 3)) +} + +/// Decode one `mload(lhs) * mload(rhs) * const` term and return its byte length. +fn affine_product_term( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let lhs = read_u16(bytes, idx)? as u32; + let rhs = read_u16(bytes, idx + 2)? as u32; + let slot = byte_at(bytes, idx + 4)? as usize; + Ok(( + mem.literal(lhs) * mem.literal(rhs) * const_at(consts, slot, idx)?, + 5, + )) +} + +/// Decode a seven-limb linear form `sum_i const_i * mload(ptr_i)`. +fn limb_linear_form( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let mut acc = Fq::ZERO; + let mut cursor = idx; + for _ in 0..QUOTIENT_VM_LIMBS { + let slot = byte_at(bytes, cursor)? as usize; + let ptr = read_u16(bytes, cursor + 1)? as u32; + acc += const_at(consts, slot, cursor)? * mem.literal(ptr); + cursor += 3; + } + Ok((acc, cursor - idx)) +} + +/// Decode `mload(lhs) * sum_i const_i * mload(rhs_i)`. +fn limb_row_form( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let lhs = read_u16(bytes, idx)? as u32; + let lhs_value = mem.literal(lhs); + let (inner, len) = limb_linear_form(bytes, consts, mem, idx + 2)?; + Ok((lhs_value * inner, 2 + len)) +} + +/// Decode a 7x7 pairwise product with `i + j` indexed coefficients. +fn limb_pairwise_form( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let lhs_base = read_u16(bytes, idx)? as u32; + let rhs_base = read_u16(bytes, idx + 2)? as u32; + let coeff_idx = idx + 4; + // Bounds-check the whole coefficient block once so the inner loop cannot + // read past the program. + byte_at(bytes, coeff_idx + QUOTIENT_VM_PAIRWISE_COEFFS - 1)?; + + let mut acc = Fq::ZERO; + for i in 0..QUOTIENT_VM_LIMBS { + let lhs = mem.literal(lhs_base + (i as u32) * WORD_BYTES as u32); + for j in 0..QUOTIENT_VM_LIMBS { + let rhs = mem.literal(rhs_base + (j as u32) * WORD_BYTES as u32); + let slot = bytes[coeff_idx + i + j] as usize; + acc += lhs * rhs * const_at(consts, slot, coeff_idx)?; + } + } + Ok((acc, 4 + QUOTIENT_VM_PAIRWISE_COEFFS)) +} + +/// Decode the dynamic mixed seven-limb affine form. +fn modarith7_form( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let mut cursor = idx; + let flags = byte_at(bytes, cursor)?; + if flags & !(Q_MODARITH7_FLAG_COND | Q_MODARITH7_FLAG_CONST) != 0 { + return Err(format!( + "reference VM MODARITH7 unknown flag bits {flags:#x} at byte {idx}" + )); + } + cursor += 1; + + let cond = if flags & Q_MODARITH7_FLAG_COND != 0 { + let ptr = read_u16(bytes, cursor)? as u32; + cursor += 2; + Some(ptr) + } else { + None + }; + + let mut acc = Fq::ZERO; + if flags & Q_MODARITH7_FLAG_CONST != 0 { + let slot = byte_at(bytes, cursor)? as usize; + acc += const_at(consts, slot, cursor)?; + cursor += 1; + } + + let lin_count = byte_at(bytes, cursor)? as usize; + let row_count = byte_at(bytes, cursor + 1)? as usize; + let pairwise_count = byte_at(bytes, cursor + 2)? as usize; + let mem_count = byte_at(bytes, cursor + 3)? as usize; + let product_count = byte_at(bytes, cursor + 4)? as usize; + cursor += 5; + + for _ in 0..lin_count { + let (value, len) = limb_linear_form(bytes, consts, mem, cursor)?; + acc += value; + cursor += len; + } + for _ in 0..row_count { + let (value, len) = limb_row_form(bytes, consts, mem, cursor)?; + acc += value; + cursor += len; + } + for _ in 0..pairwise_count { + let (value, len) = limb_pairwise_form(bytes, consts, mem, cursor)?; + acc += value; + cursor += len; + } + for _ in 0..mem_count { + // Note the operand order here is const-slot first, unlike the standalone + // `ADD_MUL_CONST_U8_MEM_U16` term, so this cannot reuse the helper. + let slot = byte_at(bytes, cursor)? as usize; + let ptr = read_u16(bytes, cursor + 1)? as u32; + acc += const_at(consts, slot, cursor)? * mem.literal(ptr); + cursor += 3; + } + for _ in 0..product_count { + let slot = byte_at(bytes, cursor)? as usize; + let lhs = read_u16(bytes, cursor + 1)? as u32; + let rhs = read_u16(bytes, cursor + 3)? as u32; + acc += const_at(consts, slot, cursor)? * mem.literal(lhs) * mem.literal(rhs); + cursor += 5; + } + + if let Some(cond) = cond { + acc *= mem.literal(cond); + } + Ok((acc, cursor - idx)) +} + +/// Read one byte with an explicit bounds error. +fn byte_at(bytes: &[u8], idx: usize) -> Result { + bytes + .get(idx) + .copied() + .ok_or_else(|| format!("reference VM read past end of program at byte {idx}")) +} + +/// Read a big-endian `u16` operand. +fn read_u16(bytes: &[u8], idx: usize) -> Result { + Ok(u16::from_be_bytes([ + byte_at(bytes, idx)?, + byte_at(bytes, idx + 1)?, + ])) +} + +/// Read a big-endian `u32` operand. +fn read_u32(bytes: &[u8], idx: usize) -> Result { + Ok(u32::from_be_bytes([ + byte_at(bytes, idx)?, + byte_at(bytes, idx + 1)?, + byte_at(bytes, idx + 2)?, + byte_at(bytes, idx + 3)?, + ])) +} + +/// Look up a constant-table slot with an explicit bounds error. +fn const_at(consts: &[U256], slot: usize, idx: usize) -> Result { + consts + .get(slot) + .copied() + .map(fq_from_u256) + .ok_or_else(|| format!("reference VM constant slot {slot} out of range at byte {idx}")) +} + +/// Convert a canonical `U256` constant-table entry into Fr. +fn fq_from_u256(value: U256) -> Fq { + let bytes = value.to_le_bytes::<32>(); + let repr = ::Repr::from(bytes); + Option::::from(Fq::from_repr(repr)).expect("constant table holds canonical field elements") +} diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index e562978ab..c79674a82 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -278,6 +278,147 @@ impl Circuit for LoweringPlanTestCircuit { } } +/// Number of advice columns backing one seven-limb foreign-field shape. +const QUOTIENT_VM_TEST_LIMBS: usize = 7; +/// Gate count chosen to exceed the inline prefix plus the native-gate budget, +/// so identities are left over for the compact VM. +const QUOTIENT_VM_TEST_GATES: usize = + DEFAULT_HYBRID_QUOTIENT_INLINE_IDENTITIES + DEFAULT_QUOTIENT_NATIVE_GATES + 8; + +#[derive(Clone, Debug)] +struct QuotientVmTestConfig { + limbs: [Column; QUOTIENT_VM_TEST_LIMBS], + selector: Selector, +} + +/// Circuit whose quotient identities are numerous enough to reach the VM. +/// +/// `LoweringPlanTestCircuit` has a single gate, so its whole identity stream +/// fits in the inline prefix and the compact VM never runs. This circuit exists +/// so the fast test suite exercises the bytecode path — and therefore the +/// generator's own program certification — on a real `LoweringPlan`. +#[derive(Clone, Debug, Default)] +struct QuotientVmTestCircuit; + +impl Circuit for QuotientVmTestCircuit { + type Config = QuotientVmTestConfig; + type FloorPlanner = SimpleFloorPlanner; + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let limbs: [Column; QUOTIENT_VM_TEST_LIMBS] = + core::array::from_fn(|_| meta.advice_column()); + let selector = meta.selector(); + // The generator only supports one identity-committed plus one + // non-committed instance column, so mirror that shape here. + let committed_instance = meta.instance_column(); + let public_instance = meta.instance_column(); + + meta.create_gate("quotient vm instance balance", |meta| { + let advice = meta.query_advice(limbs[0], Rotation::cur()); + let committed = meta.query_instance(committed_instance, Rotation::cur()); + let public = meta.query_instance(public_instance, Rotation::cur()); + Constraints::without_selector(vec![advice + committed + public]) + }); + + // Seven-limb linear forms: the shape the LIN7 recognizer is built for. + // Distinct per-gate coefficients keep the gates from deduplicating. + for gate in 0..QUOTIENT_VM_TEST_GATES { + meta.create_gate("quotient vm limb form", move |meta| { + let terms = limbs + .iter() + .enumerate() + .map(|(limb, column)| { + let coeff = Fq::from(((gate + 1) * 16 + limb + 1) as u64); + meta.query_advice(*column, Rotation::cur()) * Expression::Constant(coeff) + }) + .reduce(|acc, term| acc + term) + .expect("limb count is nonzero"); + Constraints::without_selector(vec![terms]) + }); + } + + // One simple-selector gate so the selector fold path is covered too. + meta.create_gate("quotient vm selector form", |meta| { + let lhs = meta.query_advice(limbs[0], Rotation::cur()); + let rhs = meta.query_advice(limbs[1], Rotation::cur()); + Constraints::with_selector(selector, vec![("quotient vm selector form", lhs - rhs)]) + }); + + QuotientVmTestConfig { limbs, selector } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), PlonkError> { + layouter.assign_region( + || "quotient vm row", + |mut region| { + config.selector.enable(&mut region, 0)?; + for column in config.limbs { + region.assign_advice(|| "limb", column, 0, || Value::known(Fq::ZERO))?; + } + Ok(()) + }, + ) + } +} + +/// Generate parameters and VK for the VM-exercising lowering-plan tests. +fn quotient_vm_test_vk() -> ( + ParamsKZG, + VerifyingKey>, +) { + let mut rng = ChaCha8Rng::seed_from_u64(11); + let params = ParamsKZG::::unsafe_setup(6, &mut rng); + let circuit = QuotientVmTestCircuit; + let vk = keygen_vk_with_k::, _>(¶ms, &circuit, 6) + .expect("quotient VM test circuit VK should build"); + (params, vk) +} + +/// The generator certifies its own quotient bytecode on a real plan. +/// +/// `LoweringPlan::new` runs `certify_quotient_program` and the dual-build +/// agreement check, both of which panic on mismatch, so simply building the +/// plan is the assertion. The explicit checks below guard against this test +/// silently going vacuous if the planner ever stops routing these identities +/// through the VM. +#[test] +fn lowering_plan_certifies_emitted_quotient_bytecode() { + let (params, vk) = quotient_vm_test_vk(); + let generator = SolidityGenerator::new(¶ms, &vk, GeneratorConfig::new(1, 1)); + let plan = generator.inputs().lowering_plan(); + + let interpreted = plan + .quotient + .plan + .items + .iter() + .filter(|item| matches!(item, QuotientProgramItem::Identity(_))) + .count(); + assert!( + interpreted > 0, + "test circuit no longer routes any identity through the compact VM, so the \ + certification path is untested" + ); + assert!( + !plan.quotient.build.bytes.is_empty(), + "interpreted identities should emit bytecode" + ); + assert!( + plan.quotient.build.used_ops.contains(&Q_OP_LIN7), + "test circuit should exercise the seven-limb linear recognizer; used ops: {:?}", + plan.quotient.build.used_ops + ); +} + /// Generate parameters and VK for lowering-plan integration tests. fn lowering_plan_test_vk() -> ( ParamsKZG, From 94dfe37419dcec197969bf378dac5d911a5b1544 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sun, 19 Jul 2026 21:54:39 +0100 Subject: [PATCH 69/72] Refresh moonlight-wrap fixture calldata Re-ran Moonlight's wrap Solidity bench (wrap_circuit_composes_two_fold_children_from_four_dummy_fold_proofs with MOONLIGHT_RUN_WRAP_SOLIDITY_BENCH=1) against current codegen using the sibling-worktree pattern documented in the fixture README. Halo2Verifier.sol and Halo2VerifyingKey.sol come out byte-identical, so only calldata.bin needs refreshing to match the freshly serialized transcript. The bench reports 1,277,811 gas under revm Prague and 244 matched trace points; the CI replay wrap_point_pair_decoder_rejects_malformed_public_accumulator continues to pass against the updated calldata. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../fixtures/moonlight-wrap/README.md | 4 ++-- .../fixtures/moonlight-wrap/calldata.bin | Bin 8516 -> 8516 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md b/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md index 07efdfa66..0eaeca341 100644 --- a/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md +++ b/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md @@ -14,12 +14,12 @@ the replay deploys the verifier with the verifying key alone. | Field | Value | | --- | --- | -| Source commit | `5209a9c` | +| Source commit | `9bf8c51` | | Rendered by | Moonlight `wrap_circuit_composes_two_fold_children_from_four_dummy_fold_proofs` | | Moonlight revision | `origin/codex/wrap-bench-cherry-picks` | | Accumulator | `AccumulatorEncoding::point_pair(offset=11, num_limbs=7, num_limb_bits=56)` | | Public inputs | 19 (accumulator occupies the trailing 8 words) | -| Verified on-chain | yes, 1,277,823 gas under revm Prague | +| Verified on-chain | yes, 1,277,811 gas under revm Prague | | Native/Solidity trace | 244 trace points matched | ## Regenerating diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/calldata.bin b/proofs/solidity-verifier/fixtures/moonlight-wrap/calldata.bin index f19116b45b7a7b4a91f8cfd6990d9cb2af063bbd..b7b53e880f17a7187619a670ee5b1ac5ccf58ae0 100644 GIT binary patch literal 8516 zcmajkWl)^WmH=R6aCZ;xGPnkJf(CcD;4rvD1_HsI;O-Cz5-c#dyGw8nu0eP2R&CYZ z`Ks>rztiV^`gH$!tL0tXJ3N^G+$8>U`d#<`lQ)CT6A7rL?0%C!@?V*K4M!iQkO4>HzA~OGgFF%)RD428~Zo%}*opLVI|4*tPE+Z{9{))@-G;lKDt&56PPu zDnw?rWE>qPd~9~*1fK83zPb9Cwoe5%%uPXiY-{mcbeh^F#Sc#0o+w5tfR9WL&75qb zw8x#|&eM^UYt0)Am}Kzmdi$*9_UIABA&E?-OD_qlw4=^fQ^VK#OG zot|Vx_!GPec3fjvb;y}RCflrWw>q>()+C*9-6ZkeL@zUPOq$UeO@XBtBJx7tKfl15 z43kaeXww&Em?3-MtS;+kW_=Wd_6UJl0j7=zoI6e>*}`+v$<+tft0=7_@g?kW^|60@ zGr6VD;Tu;83hm-7&pk}U7N9*LoAliiOSNdg<6Ah-5J?uo=l<9a4LD3b@M%ffhG_f2W{CC6$R?L%|45`7np z$H&6kemG@>Sn9aLAJ87dZ>zMHrCXeZM1$ZC^Vytz%;@6y&IZn$r#uYQdUKR9RiYG4 zSXobD@H-d0YTyp-(Mqpk>bNiobPx_gPul0;f2=ITvV43TZ+_bi|GVLwVfm;hlR3fT z(Nq{o`0Csn0_{;D4}53N-4{HYuHWfz<8J6Vc427@Rdjz<+7F5!i!qUX8GmLk1e%)9 zP^F_yGxkDzfNh_@eCis;ewU|65Q+eUQBjR^gL=8jg4<{K)$CJDjD;{*n(T{Yu5GKV z>imrYXb*oLGFqjq{jug($X=T#g^8g=x8oZnIM)!e&AqnXW)sY~Jf<h&*>t|k0CEc$PmMc=`I0*0xKLr*-_12h6ftq;c=#5LpIjkM-n_ z&ENX5^&HH?mgQGZ`tFTTrbxzyGr!_<)EutZ1w`k>Vy2DC>-Y|Z61 z;`|D9wTRPcl13y?y zag76TW17xpXF^4SEkwL@<*hhAb#Qs`&CGdGlUmVl1nxMPR6%W0?Bg`h9)PSAXL*gL$2WZ(P#t)E zk3*!L!<&t!ci+s&y*^J_?HZ?}Pv}$91I2B0k z=BCpUOb(-4_;8c(z7L-qWRNaJIlsS!CkSr_?a?C#wdp*)pH)Q4W{1)gov*e9(SEc0 z$een=k@m6`e3~}TH%IuzX$zj7wHCS{LWA~L&ta`P>LHr0)oL~IH=Ja8=C(Fj5#n|PcUB}p4-<8v`Hj1Aj&gWbE z0)$t*9?R8emb8QJgsICr-YY|U7~@;jLg%0F#ezhczPXgKZ~F5%*)|O*Vc771wNI-T zfj*sRhbySaJa^T1Jp*{R%7Z-wXWUg9Eq(|@tuTJC@ZFgTH1LAr%JEb@noE%Yj z>@PLS>QhGL7rv+*YYy#k`unYCPR2oHtZLe2s=IBEwPRb42((4oZDyjIAFx{kvuIm} zd@n%gWtB1Mwn^? z0oCUcNcF}NF0@Ci9hqO&cr8GsChOa_cp-Rv$x_hhKjJ{z+4H9*yC__-Ndv)Yj)n&A zxHRQ^d1^y@40{@>U)L%$F)DN4?*J<1l{Y^UP2B5C`$GM2v>4{vRBdlLY=o|T zn-N0SCkCY}EOgrV5Ct5-b);9{sngSGCz7I3Xz+` zLwiK$H%q$1rh)gS36?E2Arvr@6p@i3tJl4u>!I7=w{Ns56)-iZAr;8@dmZ7P0%6b| zHPBiq6yn#ggFDSGj_|100O*nkDa~`cZ5K_~t*@hW92iJgCPW?N$1x<(yugW|@baY~XSgvjgOAz+;Si3{ZfK8TW2Ry} z>Yh8}LW<2%D;?~C9pXVe*TgAqr~jM+?(3hIXjbX_8M$t^}j(o38Tb>qY+Dpg8`ak}Em}fMZYBjD!UDR9| znP7W3%wo{7IEoN2-)}-$f8!hO3&9h~HMUxx72=&(iG*wu==!M0IU;Qt=$#XeCgO+$ zqK{$mkqDxvW+yai7NL^H8|oM9n=h(v_!dh~UKY3CT>r6u!syhXtn9zduXe>w>K95O z6cwq{?Le)Ovt#XUWZ}w5D;(CQ`2+LQk`DjTSN5l319W|&!#C$9_!iA6^6o%zhXsO2H$FqUntYjX$WtdQFK6TTX>M-56Mvo;J6soenO(CRv&rpkpA z(2qJ(m0*#S!l*Qyd6kPLql``6dGa$-IX-`R2kns#_Eqv~it~dcK)}R$Dc`K3BiO@d9*Dd&Z_Z~CGk6F+p8H}0%= z;Yp}WRPU87TepdQL&>Y5J;9S|%b(*dv7;Z%6LSPG>q6Y4HIw0OxmC@aA9VtxpEEg#z&!>_^IY)4F#pA zkD#L-+Qao5`#ixQc$EQS({;HERhx%IaBc?H_+C}(S~hy!cR|8;xg7`G2SmMJ;!dl1 zNuWIniEBxoriYGcqyB&NAohD5Gor|`vDtnfX*B~MfoYT zCkQ#6h^Kp&H_E4>p*3(XDqoaXxMwOIRK_x%CSe@ff!mR|?U@%sSFWx?PCp9&$Nrt< zqQiQ&DfjtYhi{8`%uzi{rdYYMUJIzPP{z;HWlVOuWyheP@vn9t6?ypEJ(=!Gx{E|Wb=6ZpAZXPs9d!lNogALejy?U@>Px~ znp>mPS!=_un6 zR#|lg((Xr8aJATJ)o%U9Pu1UoR4LZiwE9*WY*arK*M8L7Bk6? zpIN~{(4;nS320@$^CY;pZEM(B(#@L-hJonVFIbGkJhKB&u#`rh3ad0FrsEZR8Bbwp zmT*=c^lO_)S^oR_#6%u-p_#B5c|$IE?6os-X+T4Emru8Av80CtSu_DNU?db*f)2u= zX}57bp1~#~=t5m2^yhFT10YKM^P0OzGm!;-Oa`v92m!o`Y(%DQk-bpieIMX7gIu~x zG?XlJ`&u2IEYkCWp{EVP95?&)>(iYSuw@APTCfajmIAIsdwA)a{Wg^%EFit-rC*efKpVlW9?9#!% zt}8(rtfC9^uDLLV5pV|0Yk&FnUYB<{t9OrjBmE&K>p#IfRPYwrWCX7wgTxP88zsKm zCRcegz*kIx(15&vtZ)w3%4}gXe+%k^?wD(G9!*|%qE2)ZT7MhiST#x*4@i-`2cn1b z?XZo;7sTaoep=GkMsyi}fi)vO+ZOF3KXN)|1zGcNBTg|F`mKh1Ny!#2_c#&Rr)E~H z=_{;~ls5n}VYWCrbTRGP1KeU47MtI17hFV=$qspnpLF2q2vln6imz54L4`qLZQBO! z-PmF}EK$2EYYtfY!oD5cW`=1p(}!3(mc;3^M~RCU_6;~SpjZHAhm~3Oxxmuc!GXUG zn`d{)g_^t&JL(tZci;l#yQa};G3_rFQeXM%?5C)Vs^BBk|4tTvk@Ju6d~*M&rx9&n zgewUSA4BiL`YTq1H8+$u{7x7h`BG4B_Pi@nz}0$2>h*UE^Pob=+Ph4g}Jk^%hnix%CDee^*S#@n42%KLN|$gOU&o8V$^Y3x?7!BR!n{cdNJ z&NJ_!%9S2LRYo+ z-{t@^OYc|CoclPDQ*Ul)**<)KHujzps;c2p;N~&A znCRaPSl%r30jHXkFtdlY3M+VAeLJwWli(nDPiD*?#r@LatH<_z&1^U{_+-2!UI?F} zAJ`Rv4;EC&=nsDMCbE{L@r}b<=4Rx12jianF6m6_y&1NaqcC{&;`@^JWQFM^J_ZC= zK;~1(C|1h zD-!x|A>406^^#|5}y+Im2J7V-{moi;<_u+&(~Q;4zN2{xchy&%VpeDt;aOsa%gt znF&WCJVi{61ih2IsmQzys)II%^km1@7Rshi(+i#uzHCE84GHFRuDd|dL zMg575tLNAH2dbM#OIIXPDb$}9!jbP>CGOJ)rd9ZtG4;O0TMn%fROT^+i#-5kwHBVG zICt_KN%!2_uUa27OY79XCG2k^5>2qlqiNh`tDwd{!^xRxaj^)%z3nfDpq3-$J|wT3 z*k1ij)gRm`Q)QaKNf2QaatLl^9ewra8+M)0k7QOU?9gb}4%va5buED~lr~W6b_j|; zX)JHc90!E-0}k4vH8>YjOnLZNnPQmFLPc?5(~M~}s23cTk(hXRYYnzu7Z20>RNoEP zIVc3GOoXAb1o%=c9CiG973o#pFo;rV-*X{vI@zg6RaASi@_L=G*)ZDhAANaXR3YZU z78?3fV?`J;2}8X71aGs?K@-(lb0u#W7le4UT#*@#pw@-?q|Y(KS>{#bPLsG|7Qp`= zw=`KV{N}|>cm@< zX2n}<^rB({5Jlx)-HEGRiO6_i^*75MJ0szvRm_g-+sAL#fe1R^d6N?1VawLXOXV61 zrsgwBVAa?}d=C6LUeLR{8%*%DX}*nYud9L#Wsr*>%xA|WQZutlX>rPakZV4)uFG!fgg_CC0dXIvN z45OJ6iB>&D2I9w({t~4K>!^!=MrUz9)NsM(OHvUY8w@(j!ri};ZFHzr^|?8*$-d#( zjDfAI=xOi5*_G-WI_JWD$t|RAerd^s`>|uro`sOh8jDFcKMwR$VV(q&hT>+*l*Sg= zX4RgK9r;!4eFA*P&EQCkH6@CwT!YAGXS372U^qo>_%I(2osM-#Z-Fqa%H5lQM&60k zv!|WJY&524W5E3|)6z_;P25l6R@>X0zSzVZLe3 zU%$MlEn&6k4)`hl4U>Znb{kCpVq)}jsG8P|Nk$L-@50_5&w-6K42lGJ2X@3E&x3J6 zC%y*}8-PR=avx7O1(;ZEcaq{*?4&C`ENC7VRL02r?oTalt@*M$Ypf22H_hMdr>Ib(up*60;OW}@l$QPTlp3lvVQWHpxbWiQR5_4MM zJHLCHo3CcL7h^)*Dlo{FV*1|yBs zZ@FZFB(WI`5~Z#a7i=jeUcIW^{qV+Ks*T4AFPII=8r6M=q6J@WgW|_={>E>$TRV3H0ZbG1Lx1R)4OXZm&7;nK?Lm5bm zrCJhMI0x6Lh89bmY6d^&JHP)1NBZhbV zGG4OS;`z`X502bB*uIN6@B2e-$i~W;d#Irss#L2u5)x%gHDoqF1$jt+7}2)>7cmVu z;n49P=Ub@p{=fHaQmF5tLsaMvs2CF}h7R$eJD_4>$HSVi1e_F^sZKaVVP0Js408@I zQhj3!czyJIqxz;IG1KA6865gR^K%paU_ZLN(zWTSR&+K?+v;-qoNgC5sPUjfZmGU< zWgf%;jNndLL9EqZq-~%IMaPMC^sdqow+PyT3)<2SF-wRi3X_#&$W$qdM&=>YV-y;$m~j literal 8516 zcmajkWl$VZmoQ)kXK)>y;O+!>x50x24+M7!?(S|05}e==+=IIl+}+(}zujNoJE_|3 zf45FQeY(5q-rIc+X)Gx=)c<~n{_kPZuKy!$1R00J`#pp73q?=<;R6#(>GU@~kOt8) z2NFOxxuM_ea-dk52u>~dt2DQ6I;&boOXfAC2PK3y(uybI-Wyi>OZKvJBxSYvEkCL{ zG=s+XRZz#+3uEB;x5(BVFO*FyU2<>eAU*u3!XCFAugD46J>v<2wCL^WxnP3Mv8tZ0 z*>a&quv*UCb=c|M{)qLD7lzBxN*qW}AO-j1D95oV?OL-z=O;#O2tY;?6q{#eaIH}o zuS^6$c3Jd~HZ;;ZB;4kjvs4R#^vEBZN?8q^I!TR~!21=b!a1$F@PCuP6-smfCL~ zny;5HVcbVcnzYNElueyEimZg2&UtOSV`Qe4(e}}6hkAL#7)mp)(X27nN-Ql= z8WtRE&91-mkN*szW z;*U7p0_oxC8W`M7*F>nb*{kCtOA^#h^wC%XH2eVm0wMIH>H3Oy;BXuYl#n7?h3`>> z4ZDyY_oY7|BY(#lX&VS1J~bQyOKbEAAT|E5vmmbi;(_vRL+9TnL3|_Jq*j*H6l=){ z>7fHsUjgM9Y$m>q+r1>%nywjT!_WG0#+S43?h)#iK#Rs#vGDXM;|dgRV)o+_0Z0#% zEwqsq3!{>4WX^!hmiTl$Q667I0sBRrs zExx~c{5N-aCvrX*QLv~llNy=ucXE;>e1que>VOjV@LL6iMFy3L=Fw9xq{oT(c#D~^ z2iv6zpN|_gzTc?zFRm=wx-wilv1K7#*i11)U*58bfpK4+o9i-0|9SsV-rkrUnRBmN z+9*R-8{f50j6TX33u{7uKhNs#*?k`Ea*(`4<9yAzub)%TjrmLfnI9_WGa^D88rQZ5 z(jd`y3Vm&?+XIAmqVI9J^Pft%C~?=nToq;eej=8~$79~^*1d)F2yVYc5nxKN(@>JX zjr$VQ@JhOhCrP_25co48!i&I>WYg$=r=9F4jC&{~Z4ZL`CqKd)`c1ZZiUAcaje+vf ziBKK_z+%N@saKHjZ|b*_MtGPTcn>ejgO~jJU8Tb`RYJ)8xcRD*2kZQ_+2q_+Rr0ck zYz|%|NOIn@T+QV;+GM#_ub01N7L67{kzCgF{6&X0Aw7oUGvbizvS=trVD$U;UARsMEoY!OtG@I-djvj373hWS<2_ zs|af7l*lVEbB!n`85&VSdJJZi(xXT}5^s7WPV1~HxnA)V%Iu(}J-0ekK46?>;K_MP zau_{%a~%JM&wNhU4@i&HT68h;k|$DudGt)kVN~*W2~W;Twf2R~_D3R&>m%}&=#^ONoxRs`RHL-#_Pb~C5)fcf1(lM((5%5cRBDQh{B_d zDCKti9Wx#OnLi-zp;LlN0&lyzMTH_Aw~dmA-+#I~h>veDG0X47D7WU0x=>5!gwOt_ zNaMhd{Z)X>4|2Kv>W8*FRq)aETl&hF%9*|j^1Gn}oY%n7?xh_WriQDqn2i4YCSW^> z{>Uep8`5JHZPq?VB8gRP3niKiDy7+_n=lGm$(=WJqyg1r8zvZ%LSvs$VH45toSKcG z%*Frl7qRGj!ftZJEV3tBVR0=^#3A>DgBCu^u#w)e6bjR(ZG1mcz8h21M8VPixG-af z^vG9K>9DOr)9f5H8yOK=H-(R#TXWv^Hfw8ztO8PhG@Fzwo!~wFBWl3aLR)7S^N=1k z7+9q(+N$=V0DX$#tHle2yjih#6fFhk*<>P>-7}Dfz^~dudnumAV_!qs&vy zu9;_sZjPptI(mEr2HuRE;us)Aqw^9$ZKzU8Tj-5DUDUbGInP9QO5_)DLVEb}S>w}I z(-X5|k75h@nNz58$x(cf<#$&l!`dYU{k|ya^gkr!O#(fLyNgG>$%>F3Q`5%bRZGl% zDB6QQ_O}H41DkLF-HKcBlQk~O4s+$cLuk*DhGgVI7$LB6M5YP=>EV6v7E0eo%#_IA z=!hQPeou9PY&?H{v-5%}Q0gA0!cPexLnMfhIYIhB8xcwEB?{?L1bOTuR!y__c3=W_ z#IUZiY`PcvOwf%WU@ffxX}I?HcHTw7UWGX5Im%uMHTTm9`7Cy*W#QM|EhkEHPJ+0_3V1GbBv zKrPg)r)sSJ5TSExi?b0hpi3dYL!uja9j%ATKi>}N0hfwL)W&t0T9M1DU|D0?kE)3} zwVttwg>rh5XcsH%^y{? zdA6RvKD=6WmRHkNOrhCbKPh)_Lg6!m^k`1Kdh4(!1d2nbeyoLMvE7?OL>F^@&KULJ z147_?CYf#vG#_%O<#-?B%p!Vl7NmzFO-OxYGt-Nr{w2>?vxcK4{ekNTR@(x$5Bwf_ zy;H*tg7+qs+UDL0Nx6I|kRAonBgRiylXInF3yl^sCb?OQZgyoT{NcN8eLK!Kr=P}~ zeciGJS21}FIu7?N?IO+3L3;cY9Yt-ASnOs8;zp0cha&DDaDuaRD6DiA&!lS}eH13v~3$eLnC<+Jh#X_Q6*XQ}_ zpZyd6s(Hde@hRZ_Z62>jI2Hc-Nj3NM!o%bqf0|8!y;i}jyzg(t-0*S|02R|4j^#UK zexQtW>J7eN`!xQ@oYvbmU$j_?30xx&-}N(|zU*p^Jql(DJ8rL`E4Oc{LG=3=2c!p4 z+qk5-Hm>933iUZt(-W69S}jq>EgPCX@I5sQTCx6-^$vbL$ji8$5h3KdeH4N8_(KHN z0B)P2JwS?3zfdoBwM4{l&-s;!56-8ZSaN2x)E}`?tHYYmBua_COf4P%?4Q`eG=)QH z=KiH4Yx+d{pH|mm)GlTt`Jc#wngv_R;mzH0S8^Uk`fl5F2-Hea3uqzp!_$3-1#YDf z7bG^0tpnj4YI111m`I|;AL&h(o=ULGy;-eAKL>gTjTTD^LHd8jPKyo$4vk`x zH-!l<;yzLUu1f(+knNO5Le288i0lU*hNq{EZPP){L(JcP$>42sdH6d?el79iwO`6x z>6tj#4C2lRECqb;TpP$$`Z76jbeH&B7=JnIk3(5Bd8Yx+`W-AXD^%{?J;7uA))`sE)8!&h_&2@3d7ZWt7)GmULz`K6h!wg3rO852?VqB{71GU&&hPZPjPQQye_=lVu$QY^{BQvm8Hwc zhfOLC?#Ui&8}N%jYT`j=AVUBTDgW&DvTi%V>RYt55j3sHHFrS-CT72ao3s ztm>V*izvPLIA50xAj@W9E0P$^sA->6g!9`P){c8f{my#DJ-=eY4IRC_KD-Er!j$OR zcU&#v6p1U6J}v*C)}nWM>R0St9UD;)R{i}wHLur*b6`NW!sRU zWM7H{za;P-W$klaqmM;{-YO-=QT$t!6@zs(s+AH@2S++k@5O$ovRS9TtfbEui=k;p zPh0I(q?5fa7JDNKtJz251Q`>&@sJw19z#fns{bjXB}Cwd1#i}vF{_NPTF8Bc=<;bbqu~ppj~P?UsH`9$#CMIS2q^@X6G`R zC|rQa0@r}HY_d<^G>*htz`1x3D%cWs&SlU7F}(7$ILM)hpM|w z36{p1vQ)+p^`49+4=}SF_DDo|fdj2q2x|0{CQQKxqV=mbb1}2fxPesCgXAi1E2De8 zqD@6AQzahu3?`^4L+=`I9Kg^5l+Z2rhPNL}a&5+wXh>TK$yYGeSNuMBjON!RsF3AF z2J~fkwkx(xMT7_PH}&c*2mY<(oQC;8IKkfm)BPuhSADI1=))}fL`60{LN(7S2IyMA zpOdp%%yaI!=+LJ3VAUi1-QGzYG4_2sbllO(p(Ud*!*_coJFtr#wh60!FH+Dj2=Gfb zTc%fJ!OW{KEV8l#<8AAM+4(xEwP!Jm)-M09wF13Xc%$7!eCJe2R@vD{&b&f3#YvMw z5+%xwfo3i;o8$b$HS9kpS!4W0!qV|^2J8A%^5F%GBsjInRx8+%w-#)IgKgDrg+CgD z57Dn=i_BE~>|7*zs5v+(Xcs>@n$5N(_VDxhRK7ReD_-=c#xC3veQx@u6xjaj?+`py zr~>bqR;RuHXk+~zD=bdR;ko@-Kh06^MY=%Uv*Cjg_n>0-5Yk$w%zzLMZ*6T*6p2Y_ zm<4p%d%84?8+!~0f$>xr|3EMAJTneKaZS;Xy3N@)RKH30BQT9Rt5bu)qVVN#5fnHq zWs)&Fi*6__?bBL!A=y*U!!1K8W5&G>z+@)$P429&sX1B5qKKoHLfdB|r*-Qt*Ub*b zLuyjG72>60{xDgJlmQCTdwx78Klk$X#rG?jZHV*<5N1sm6~QaeSPJ+VN=V!tih}$B zQ(HD^6HXW#=rjUDPQZt*XCiWf0l6q$8`?lslxHa!*}pP@KHCf8Q; zP7|P**Uj-RG}n^F61r1mI6O9%WX`O~GvcU;9&l2xgYuF>L_)=OAve%fed|y6{7Mbe z&E^!J3uT-=*Qz;InHS7)$ z0|ndcHW484m;j7l@$c2gA0J4l2c`W>kIl&aur|B~dU(djV)qVWF-)cE(HLy+R4*3<5lGvjwk2Y??}0_aQzc=2!Zd?fb~8 zNQyTqq%!O`r_8)-f9To#;IzQpESHMxq1v2iqc3cth)ZX0LP1&J{_Ju?TzS|AJDRO} z`QazL+}6RJI*-ydB0L=yUm$txxB9&5-nNyvZBCMpwG$;ac`c_P3%@B)BR@*<8@;M- z4t3)ZkJRK>(^uaEs+Nz(u2b++L4v2xzYIG0KKp#&qZUP!i*FU2QpPe9b{?6g7a?RzBXPV-C78?g$J4Pup=l7 zcVePY>}mTrbOxnC;QB*PLl^#*ONCJNrBR8B~3k^z~ zfIJOL41~@+i&`Fk$Dm9UidjgReK~Y`I3;jAI@$WGw9yHwqB$!Fi>nVZ(mJxKws>BT zqL>g$Kwj0DJA*aUvBa}qLFE-m$xPg!MZUD$8BC&8bx*LviL`ej+yNR3wChkQBmpbW zXq-qhgjQR8LS$?YP2T#7w8~Tc#ZWQ9rwqKY_QN! zvU>>a9vakKO}2JL=8b_1!;J!K%)*N%%PcT9@q$M?g<9akbVgHEkE9z!!6RgBKt8#n zjFN+(h|T1`nQ`n))o&NC4DP}@$zeH9xap;atPJ)%y>{+PAO5^k0YW|Q83D7f&s#<-~MvyB#V#%@< z^HHc@RxY<^(MlvAC-pf*WfWKt7-0}A4iPuy(n#?R`e`og7iG6E7;|JD4MmS6nY5df zRALY44?X4#RG2Ts1MhLtxTr2W)0ArF8k(cN$Gpz5hr_9OMC~-vfh3#_B?mkD!#-QC zbCJ&xbtpc`-O*Zx9WwzeSZ9*BgQe9du>DB4WH9k}^0K-A782-H-Ld-A{*nFc@5qGc z{@2yP#?vzDoBln$Ynr$Afp6Yf^`-8DKTnBcp=>91sE995%TsfDEe7qTje zv`fL#%ojtA5adVQuQ$+!WA>Zkc$Nv6Ekk{>ww$uzpF{{m?kev2?w^Yg%|5q4KL*D! zww2P9mbZ;v1PC_^kK2>D&Lde`Q+G~84C8!pgb&Ee3NvG#2y5Cn-$RM=uo0P_HKE^@ z8t)pA0d3DbTlu7&rol)C|IP-N#CC{}2XHcc_M+n;Ab@WRHO!cf*IJ(xJiV!KhCXI3 zB11{&%jKn-P?lxTb_ySRh6$$+js9`Fx%+AF;}5;MdV@Ms93LICRHT3a5S-a@;tvTN z#l+k+LRT@e0(pzv0gKgMV3^$oTVrbu5G~-mRHUbdV18X)y~o(C8_0`EE9bX9A~#j7gW;H>IkMSAMYLGgC3a8#biz0X15OPVhfY!bG0_SFF81U+V^ ztJWdgvE*VvekSpubJ5!t#mYS-Z{w>>7|GwEC&IRtxm@Olt`ST$ep!Y`QjniSDBY0r zqpi6Yxyn2V4aqJ~QKD|r!A+grAc?UzRtC4DiobOjyovArH$;MycLY;~Uy^Ikms{bt zYj~HFJ33O13&n#}z`{GM98QVaSeXa0`uOCuz-qu74k~4~x}-^fHhrl{F4=>Z8!kO> zBze^Jo&!B{{c}9VuH;o`mmp~~`jR_J?meC!PU7g3ngvb^9fjW zt5IsXel!;-H<d){_7|@z`C^^Xk;JK9DzA5% z^ELm}f9j{`T==z9$M59e5di~ODEL(Bw@nq^)aM-c4@i4i#)eN4Dcx~T9AP75EP2se zKG`Xd|2x(;3G_VG1MrHh7@93;K{yQ*{9S+xB{*jNK| z*q^uWB-?@w(T0rYh{;KwW}>5HGX5mR^Q04=;J3r6Do+vwq{e_{Z#N{+Ta(PxI>rD~ zy?e_GzXzP?tff#dDJYJ1bkKwKV@zj#6(Zcw+tJU^gN`I$>*>`k6K z9#Mky5aa)=zx_{t2Eqd&#*o8*>T?h${L?>%h~wKFR0PIjCQDAXL&NiPX-T3Qv$zrJ z=$pXkpycY+)aMHu4oysBQu`R68*ux1Qsoq_OiebUFnuvEFQLxrvj3;P0&#r~@!k?e zPIzxrzji5J^yLo17MD^vw2`7>@x6NNjSPlGtaBV#0e<4hcvs*5r#@8(y;QeuRvUD5 zuSqK4Nl5IPMXD6PSX(ZU_^0UqzaQ*+o#Kj%mA%U4IfJLM#EtFY)IJC0{OU zH^U~TXRaTNql;1XP|VkKMlsO(kNPucKg{b5>a1g-Ue|Iw&gxnD1N!oRd+-2&tcudx zWOp-pb*qu-`K}4vI^al$S$3rQAN^~TjJXg%eD+iLuAo%n_`v#!knjJ@Zx3RN#5;HW wCe1R>k@#_Y^R%=CCf#+C`hGo?|L9*}d3-BmdhHJtYer6rsNY>C1K8aC7isx+d;kCd From 60f842be7c59e532e5832c44ca7eaee846d50744 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 20 Jul 2026 19:59:18 +0100 Subject: [PATCH 70/72] Move the pairing-batch and final-pairing frames above solc's spill window The accumulator/KZG pairing-batch hash frame (0x100) and the ec_pairing two-pair input frame (0x320) sat inside the [0x80, reserved_end) window solc's via-IR stack-to-memory mover uses for spill slots (observed up to 0x8e0), so the alpha Fiat-Shamir preimage and the final pairing inputs were protected only by an unenforced liveness coincidence. Root PAIRING_BATCH_PTR at LOW_MEMORY_SCRATCH_START like every other low-memory base, make VerifierMemoryLayout::validate() reject any generated region below that base, add the batch-frame end to the VK_MPTR floor, and extend the memoryguard test to also compile the accumulator-bearing variants whose FinalPairing block was never handed to solc by that guard. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../docs/architecture/MEMORY_LAYOUT.md | 18 +-- .../src/lowering/layout/memory.rs | 25 ++++- .../src/lowering/layout/mod.rs | 22 +++- proofs/solidity-verifier/src/lowering/vk.rs | 8 +- proofs/solidity-verifier/src/test.rs | 104 +++++++++++------- .../hybrid-mt-fixture-dump/Halo2Verifier.sol | 2 +- .../poseidon-fixture-dump/Halo2Verifier.sol | 2 +- .../Halo2Verifier.sol | 2 +- .../Halo2Verifier.sol | 2 +- 9 files changed, 127 insertions(+), 58 deletions(-) diff --git a/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md b/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md index 5ad79c2eb..6e669b3d3 100644 --- a/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md +++ b/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md @@ -44,11 +44,13 @@ own transcript buffer in the same bytes, separated only by live ranges that nothing enforced -- a recompilation could silently place a live spill across a verifier write and corrupt a challenge or pairing input. So the streaming transcript buffer, the main verifier return word, the split quotient return -frame, and low-memory precompile scratch are rooted at +frame, low-memory precompile scratch, the accumulator/KZG pairing-batch hash +frame, and the final two-pair pairing frame are rooted at `LOW_MEMORY_SCRATCH_START` (`0x1000`), above the largest observed reservation. -`compiled_memoryguard_does_not_overlap_generated_layout` compiles each rendered -variant and fails the build if a future circuit or compiler pushes the -reservation past that base. +`VerifierMemoryLayout::validate()` rejects any generated region below that +base, and `compiled_memoryguard_does_not_overlap_generated_layout` compiles +each rendered variant -- including the accumulator-bearing ones -- and fails +the build if a future circuit or compiler pushes the reservation past it. The VK constructor payload buffer is the exception: it lives in `Halo2VerifyingKey`, whose assembly carries no `memory-safe` annotation, so @@ -163,6 +165,8 @@ which changes the transcript-buffer bound. The verifier reserves: - unaligned starts or lengths; - any generated region inside Solidity-reserved memory `[0x00..0x80)`; +- any generated region below `LOW_MEMORY_SCRATCH_START` (`0x1000`), where a + live via-IR spill slot could share its bytes; - overlapping permanent regions; - overlapping scratch regions that are live in the same `MemoryPhase`; - PCS fixed-window overflows. @@ -268,7 +272,7 @@ The planner validates by lifetime, not just by address. | Phase | Region examples | Notes | | --- | --- | --- | -| `Transcript` | `[0, transcript_words * 0x20)` | Must stay below `VK_MPTR`. | +| `Transcript` | `[0x1000, 0x1000 + transcript_words * 0x20)` | Must stay below `VK_MPTR`. | | `ScalarInv` | `VK_MPTR - 0x100` frame | Historical modexp scratch near the VK payload. | | `LagrangeBatchInvert` | `batch_invert_scratch_mptr` | Reuses selector bytes before selector accumulators are live. | | `QuotientVm` | quotient temps and stack | Used before PCS final MSM. | @@ -276,8 +280,8 @@ The planner validates by lifetime, not just by address. | `PcsQComTrace` | optional q_com trace MSM | Aliases `pcs_scratch_mptr`; trace-only. | | `PcsFinalMsm` | final MSM input and selector accumulators | Selector accumulators and final MSM must not overlap in this phase. | | `AccumulatorMsm` | public accumulator MSM input | Length is derived from accumulator/VK shape. | -| `AccumulatorPairingBatch` | `[0x100, 0x320)` | Hash domain plus four G1 points for accumulator pairing batching. | -| `FinalPairing` | two-pair KZG pairing frame | Low-memory final precompile frame. | +| `AccumulatorPairingBatch` | `[0x1000, 0x1220)` | Hash domain plus four G1 points for accumulator pairing batching. | +| `FinalPairing` | `[0x1220, 0x1540)` two-pair KZG pairing frame | Final precompile frame, placed past the pairing-batch frame by construction. | ## Update Rules diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index fd91314d3..54a565c73 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -36,9 +36,10 @@ use crate::lowering::{ }; /// Accumulator pairing-batch hash frame. /// -/// The template starts this frame at `0x100`, writes a one-word domain tag, -/// then four G1 points: KZG rhs/lhs and accumulator rhs/lhs. The last copy ends -/// at `0x320`, so the registered range is `[0x100, 0x320)`. +/// The template starts this frame at `PAIRING_BATCH_PTR` (`0x1000`), writes a +/// one-word domain tag, then four G1 points: KZG rhs/lhs and accumulator +/// rhs/lhs. The last copy ends `0x220` bytes later, so the registered range is +/// `[0x1000, 0x1220)`. const ACCUMULATOR_PAIRING_BATCH_BYTES: usize = PAIRING_BATCH_HASH_BYTES; // Fixed word offsets from `THETA_MPTR`. @@ -1067,6 +1068,24 @@ impl VerifierMemoryLayout { } } + // The verifier body runs inside `assembly ("memory-safe")`, so solc's + // via-IR stack-to-memory mover reserves spill slots upward from 0x80. + // A generated region below `LOW_MEMORY_SCRATCH_START` could share + // bytes with a live spill slot, and the lifetime model cannot see + // solc's opaque spill liveness -- so enforce disjointness by address. + // `compiled_memoryguard_does_not_overlap_generated_layout` checks the + // complementary bound, `reserved_end <= LOW_MEMORY_SCRATCH_START`, + // against real compiled bytecode. + for region in &self.map.regions { + if region.len != 0 && region.start < LOW_MEMORY_SCRATCH_START { + return Err(format!( + "memory region {} starts at {:#x}, below LOW_MEMORY_SCRATCH_START ({:#x}); \ + it can overlap solc's via-IR stack-to-memory spill window [0x80, reserved_end)", + region.name, region.start, LOW_MEMORY_SCRATCH_START + )); + } + } + let expected_scalar_inv = self.vk_mptr.value().as_usize().saturating_sub(MODEXP_SCRATCH_BYTES); if self.scalar_inv_scratch_mptr != expected_scalar_inv { diff --git a/proofs/solidity-verifier/src/lowering/layout/mod.rs b/proofs/solidity-verifier/src/lowering/layout/mod.rs index 76a08f82f..6425310a4 100644 --- a/proofs/solidity-verifier/src/lowering/layout/mod.rs +++ b/proofs/solidity-verifier/src/lowering/layout/mod.rs @@ -112,8 +112,8 @@ pub(crate) const PAIRING_STATIC_WORKING_WORDS: usize = PAIRING_TWO_PAIR_BYTES / /// /// Placed past the end of the accumulator pairing-batch hash frame, which /// occupies `[PAIRING_BATCH_PTR, PAIRING_BATCH_PTR + PAIRING_BATCH_HASH_BYTES)` -/// = `[0x100, 0x320)`. Starting at `PAIRING_TWO_PAIR_BYTES` (0x300) instead -/// would put the last word of the hashed ACC_LHS copy inside this scratch. +/// = `[0x1000, 0x1220)`. Starting lower would put the last word of the hashed +/// ACC_LHS copy inside this scratch. /// The two regions carry different `MemoryPhase`s, and `MemoryLifetime:: /// intersects` treats distinct phases as never co-live, so the planner cannot /// catch that overlap -- it has to be avoided by construction here. @@ -170,7 +170,13 @@ pub(crate) mod accumulator { pub(crate) const CARRIED_SCALARS: usize = 2; /// Low-memory hash frame for batching the accumulator pairing with KZG: /// domain tag word, KZG rhs/lhs G1s, then accumulator rhs/lhs G1s. - pub(crate) const PAIRING_BATCH_PTR: usize = 0x100; + /// + /// Rooted at [`super::LOW_MEMORY_SCRATCH_START`] like every other + /// low-memory scratch base: the frame historically sat at `0x100`, inside + /// the `[0x80, reserved_end)` window solc's via-IR stack-to-memory mover + /// reserves for spill slots, so a live spill could silently corrupt the + /// alpha Fiat-Shamir preimage or the pairing inputs built here. + pub(crate) const PAIRING_BATCH_PTR: usize = super::LOW_MEMORY_SCRATCH_START; /// Domain-separation word for the accumulator pairing batch. /// /// This is a 29-byte numeric literal: ASCII `"pairing-batch-acc-kzg"` @@ -774,8 +780,16 @@ mod tests { assert_eq!(accumulator::LIMB_BITS, 56); assert_eq!(accumulator::LIMBS, 7); assert_eq!(accumulator::LIMBS_PER_WORD, 4); - assert_eq!(accumulator::PAIRING_BATCH_PTR, 0x100); + // Above solc's via-IR spill window like every other low-memory base. + assert_eq!( + accumulator::PAIRING_BATCH_PTR, + super::LOW_MEMORY_SCRATCH_START + ); assert_eq!(accumulator::PAIRING_BATCH_HASH_BYTES, 0x220); + assert_eq!( + super::FINAL_PAIRING_SCRATCH_START, + super::LOW_MEMORY_SCRATCH_START + 0x220 + ); assert_eq!(quotient_limb::LIMBS, 7); assert_eq!(quotient_limb::PAIRWISE_TERMS, 49); assert_eq!(quotient_limb::PAIRWISE_COEFFS, 13); diff --git a/proofs/solidity-verifier/src/lowering/vk.rs b/proofs/solidity-verifier/src/lowering/vk.rs index 33e173465..1e5d97623 100644 --- a/proofs/solidity-verifier/src/lowering/vk.rs +++ b/proofs/solidity-verifier/src/lowering/vk.rs @@ -387,6 +387,8 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { Self::transcript_buffer_layout_for_meta(meta, self.num_instances).words; let transcript_end = layout::TRANSCRIPT_BUFFER_START + transcript_words * WORD_BYTES; let pcs_end = layout::PCS_PAIRING_SCRATCH_START + pcs_computation * WORD_BYTES; + let pairing_batch_end = layout::accumulator::PAIRING_BATCH_PTR + + layout::accumulator::PAIRING_BATCH_HASH_BYTES; let final_pairing_end = layout::FINAL_PAIRING_SCRATCH_START + layout::PAIRING_STATIC_WORKING_WORDS * WORD_BYTES; let verifier_return_end = layout::VERIFIER_RETURN_BUFFER_START + WORD_BYTES; @@ -402,8 +404,12 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { transcript_end, // PCS computation scratch pcs_end, + // Accumulator/KZG pairing-batch hash frame. Currently ends where + // the final pairing frame starts, but list it explicitly so the + // bound survives if that derivation changes. + pairing_batch_end, // Pairing: two-pair input frame plus output word, rooted above - // Solidity's reserved memory prefix. + // solc's via-IR spill window. final_pairing_end, // Low-memory return frames. The quotient evaluator's output grows // with the number of simple selector buckets. diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index 5eab7d99b..02cbc706c 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -1370,18 +1370,20 @@ fn pinned_quotient_verifier_rejects_wrong_vk_and_quotient_contracts() { /// is not fixed -- observed values range from `0x80` to `0x8e0` depending on /// the circuit, the solc release, and the optimizer schedule -- so assert the /// property against real compiled bytecode rather than assuming it holds. +/// +/// `VerifierMemoryLayout::validate()` enforces the complementary bound: every +/// generated region starts at or above `LOW_MEMORY_SCRATCH_START`, so +/// `reserved_end <= LOW_MEMORY_SCRATCH_START` here proves full disjointness. +/// The accumulator-bearing variants are checked too, because their +/// FinalPairing pairing-batch block adds frames and live values the property +/// fixture never renders. #[test] fn compiled_memoryguard_does_not_overlap_generated_layout() { if !poseidon_inputs_available_for_evm() { return; } - let fixture = create_property_poseidon_fixture(); - for (name, source) in [ - ("embedded", fixture.embedded_verifier_solidity.as_str()), - ("separate", fixture.separate_verifier_solidity.as_str()), - ("quotient", fixture.quotient_verifier_solidity.as_str()), - ] { + fn assert_memoryguard_clears_generated_layout(name: &str, source: &str) { let runtime = compile_solidity_runtime(source); let reserved_end = runtime_free_memory_pointer_init(&runtime).unwrap_or_else(|| { panic!("{name}: could not read the free-memory-pointer prologue from runtime bytecode") @@ -1395,6 +1397,19 @@ fn compiled_memoryguard_does_not_overlap_generated_layout() { crate::lowering::layout::LOW_MEMORY_SCRATCH_START ); } + + let fixture = create_property_poseidon_fixture(); + for (name, source) in [ + ("embedded", fixture.embedded_verifier_solidity.as_str()), + ("separate", fixture.separate_verifier_solidity.as_str()), + ("quotient", fixture.quotient_verifier_solidity.as_str()), + ] { + assert_memoryguard_clears_generated_layout(name, source); + } + + for (name, _, artifacts) in render_accumulator_verifier_variants() { + assert_memoryguard_clears_generated_layout(name, &artifacts.verifier); + } } /// `Halo2VerifyingKey` is size-checked at render time by @@ -1637,27 +1652,10 @@ fn accumulator_fixed_base_tail_must_match_verifying_key() { } } -/// Compile the accumulator render arm. -/// -/// No production fixture enables `with_accumulator`, so before this test the -/// whole `{%- if self.expected_has_accumulator %}` branch of -/// AccumulatorHelpers.yul -- the limb decoder, the pre-transcript -/// public-accumulator MSM, and the fixed-base scalar tail -- was never handed -/// to solc by the default gate. Only the opt-in `ivc_keccak_solidity` bench -/// (k = 20, release, external SRS assets) rendered it, so a Yul syntax error -/// or a solc stack-depth regression in that branch could reach a release -/// unnoticed. -/// -/// This does not execute the accumulator logic against a real recursive proof -/// -- that still needs a decider circuit carrying a genuine accumulator in its -/// public inputs. It does guarantee the branch compiles, and it pins the -/// canonicality checks on the scalars the helper feeds to G1MSM. -#[test] -fn accumulator_verifier_variants_compile_with_pinned_solc() { - if !poseidon_inputs_available_for_evm() { - return; - } - +/// Render the three accumulator-bearing verifier variants over the Poseidon +/// fixture VK: fully collapsed, fixed-base scalar tail, and point-pair +/// encodings. Returns `(name, has_carried_scalars, artifacts)` per variant. +fn render_accumulator_verifier_variants() -> Vec<(&'static str, bool, crate::RenderedArtifacts)> { let srs_dir = srs_dir(); env::set_var("SRS_DIR", &srs_dir); let relation = PoseidonExample; @@ -1696,19 +1694,47 @@ fn accumulator_verifier_variants_compile_with_pinned_solc() { ), ]; - for (name, num_instances, acc, has_carried_scalars) in variants { - let generator = SolidityGenerator::new( - &srs, - vk.vk(), - GeneratorConfig::new(num_instances, 1).with_accumulator(acc), - ); - let artifacts = generator - .render(RenderOptions { - vk: RenderVk::Separate, - ..RenderOptions::default() - }) - .unwrap_or_else(|err| panic!("{name} should render: {err}")); + variants + .into_iter() + .map(|(name, num_instances, acc, has_carried_scalars)| { + let generator = SolidityGenerator::new( + &srs, + vk.vk(), + GeneratorConfig::new(num_instances, 1).with_accumulator(acc), + ); + let artifacts = generator + .render(RenderOptions { + vk: RenderVk::Separate, + ..RenderOptions::default() + }) + .unwrap_or_else(|err| panic!("{name} should render: {err}")); + (name, has_carried_scalars, artifacts) + }) + .collect() +} + +/// Compile the accumulator render arm. +/// +/// No production fixture enables `with_accumulator`, so before this test the +/// whole `{%- if self.expected_has_accumulator %}` branch of +/// AccumulatorHelpers.yul -- the limb decoder, the pre-transcript +/// public-accumulator MSM, and the fixed-base scalar tail -- was never handed +/// to solc by the default gate. Only the opt-in `ivc_keccak_solidity` bench +/// (k = 20, release, external SRS assets) rendered it, so a Yul syntax error +/// or a solc stack-depth regression in that branch could reach a release +/// unnoticed. +/// +/// This does not execute the accumulator logic against a real recursive proof +/// -- that still needs a decider circuit carrying a genuine accumulator in its +/// public inputs. It does guarantee the branch compiles, and it pins the +/// canonicality checks on the scalars the helper feeds to G1MSM. +#[test] +fn accumulator_verifier_variants_compile_with_pinned_solc() { + if !poseidon_inputs_available_for_evm() { + return; + } + for (name, has_carried_scalars, artifacts) in render_accumulator_verifier_variants() { let verifier = artifacts.verifier; assert!( verifier.contains("function validate_public_accumulator"), diff --git a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol index 6c05c2567..7f5d40d89 100644 --- a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol @@ -592,7 +592,7 @@ contract Halo2Verifier { // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0320 + let scratch := 0x1220 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) diff --git a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol index 8a2e5f8cf..7a6702aef 100644 --- a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol @@ -592,7 +592,7 @@ contract Halo2Verifier { // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0320 + let scratch := 0x1220 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) diff --git a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol index dea9593f8..f897a9c32 100644 --- a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol @@ -592,7 +592,7 @@ contract Halo2Verifier { // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0320 + let scratch := 0x1220 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) diff --git a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol index 150bbd4be..88bdcc4b8 100644 --- a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol @@ -592,7 +592,7 @@ contract Halo2Verifier { // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0320 + let scratch := 0x1220 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) From aa62d19dd95d7f747849ee9f47d65eab520a74fa Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 20 Jul 2026 20:32:56 +0100 Subject: [PATCH 71/72] Reject non-canonical scalars in batch_invert's general path The single-element fast path fails closed on any word >= r, but the multi-element path folded raw words straight through mulmod, so a non-canonical scalar was silently reduced and its slot filled with inverse(x mod r): accept/reject semantics depended on batch length. Unreachable from the one production call site (the Lagrange block feeds addmod/mulmod outputs), but a latent divergence inside a shared primitive whose other inversion helper, scalar_inv, already rejects x >= r. Guard every element of the forward pass like the singleton path, pin the guard text in the template greps, and add a revm harness test that executes the rendered helper on adversarial words in both paths (it reproduces the acceptance on the unfixed template) and checks canonical batches against native inverses. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../solidity-verifier/src/lowering/tests.rs | 20 +++ proofs/solidity-verifier/src/lowering/vk.rs | 4 +- proofs/solidity-verifier/src/test.rs | 162 ++++++++++++++++++ .../hybrid-mt-fixture-dump/Halo2Verifier.sol | 22 ++- .../poseidon-fixture-dump/Halo2Verifier.sol | 22 ++- .../Halo2Verifier.sol | 22 ++- .../Halo2Verifier.sol | 22 ++- .../partials/verifier/AssemblyHelpers.yul | 22 ++- 8 files changed, 284 insertions(+), 12 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index c79674a82..4f0b880d7 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -2071,6 +2071,26 @@ fn batch_invert_handles_empty_and_singleton_ranges() { verifier_template.contains("if ret { mstore(mptr_start, mload(single_scratch)) }"), "singleton batch inversion must store the single inverse in place" ); + // The general path must reject non-canonical words (x >= r) like the + // singleton path, so accept/reject semantics do not depend on batch + // length: one guard on the first element, one inside the prefix-product + // loop, one on the final element. + assert_eq!( + verifier_template.matches("if iszero(lt(gp, r)) {").count(), + 1, + "general batch inversion path must range-check the first element" + ); + assert_eq!( + verifier_template.matches("if iszero(lt(x, r)) {").count(), + 2, + "batch inversion must range-check the singleton element and every \ + prefix-product loop element" + ); + assert_eq!( + verifier_template.matches("if iszero(lt(x_last, r)) {").count(), + 1, + "general batch inversion path must range-check the final element" + ); } #[test] diff --git a/proofs/solidity-verifier/src/lowering/vk.rs b/proofs/solidity-verifier/src/lowering/vk.rs index 1e5d97623..af26bc0f7 100644 --- a/proofs/solidity-verifier/src/lowering/vk.rs +++ b/proofs/solidity-verifier/src/lowering/vk.rs @@ -387,8 +387,8 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { Self::transcript_buffer_layout_for_meta(meta, self.num_instances).words; let transcript_end = layout::TRANSCRIPT_BUFFER_START + transcript_words * WORD_BYTES; let pcs_end = layout::PCS_PAIRING_SCRATCH_START + pcs_computation * WORD_BYTES; - let pairing_batch_end = layout::accumulator::PAIRING_BATCH_PTR - + layout::accumulator::PAIRING_BATCH_HASH_BYTES; + let pairing_batch_end = + layout::accumulator::PAIRING_BATCH_PTR + layout::accumulator::PAIRING_BATCH_HASH_BYTES; let final_pairing_end = layout::FINAL_PAIRING_SCRATCH_START + layout::PAIRING_STATIC_WORKING_WORDS * WORD_BYTES; let verifier_return_end = layout::VERIFIER_RETURN_BUFFER_START + WORD_BYTES; diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index 02cbc706c..1a4f07793 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -1443,6 +1443,168 @@ fn compiled_verifier_runtime_fits_the_eip170_limit() { } } +/// Extract one rendered Yul function (signature through matching close brace) +/// from generated verifier source. The rendered helpers contain no string +/// literals, so plain brace counting is sufficient. +fn extract_yul_function<'a>(source: &'a str, signature_prefix: &str) -> &'a str { + let start = source + .find(signature_prefix) + .unwrap_or_else(|| panic!("rendered source should define {signature_prefix}")); + let tail = &source[start..]; + let open = tail.find('{').expect("function definition must open a brace"); + let mut depth = 0usize; + for (idx, byte) in tail.bytes().enumerate().skip(open) { + match byte { + b'{' => depth += 1, + b'}' => { + depth -= 1; + if depth == 0 { + return &tail[..=idx]; + } + } + _ => {} + } + } + panic!("unbalanced braces while extracting {signature_prefix}"); +} + +/// Test-only contract that runs the rendered `batch_invert` helper on +/// caller-chosen memory words. Raw calldata is `n || r || n words`; raw +/// returndata is `success flag || the n (possibly inverted) words`. +fn batch_invert_harness_source(verifier_solidity: &str) -> String { + let batch_invert = extract_yul_function( + verifier_solidity, + "function batch_invert(success, mptr_start, mptr_end, scratch_mptr, r) -> ret", + ); + format!( + r#"// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.24; + +contract BatchInvertHarness {{ + fallback() external {{ + assembly {{ + {batch_invert} + + let n := calldataload(0x00) + let r := calldataload(0x20) + let base := 0x1000 + calldatacopy(base, 0x40, mul(n, 0x20)) + let ok := batch_invert(1, base, add(base, mul(n, 0x20)), 0x8000, r) + mstore(0x80, ok) + mcopy(0xa0, base, mul(n, 0x20)) + return(0x80, add(0x20, mul(n, 0x20))) + }} + }} +}} +"# + ) +} + +/// Execute the rendered `batch_invert` helper against adversarial words. +/// +/// The template greps in `lowering/tests.rs` pin the guard text; this pins +/// the behavior: the singleton and general paths must both fail closed on +/// words outside the canonical range (`x >= r`, including invertible +/// residues, `x = r`, and literal zero) without touching the input run, +/// so accept/reject semantics never depend on batch length. Canonical +/// batches must produce exactly the native inverses. +#[test] +fn batch_invert_fails_closed_on_noncanonical_words_in_all_paths() { + if !poseidon_inputs_available_for_evm() { + return; + } + + let fixture = create_property_poseidon_fixture(); + let harness = batch_invert_harness_source(&fixture.embedded_verifier_solidity); + let mut evm = Evm::default(); + let address = evm.create(compile_solidity(&harness)); + + let r = fr_modulus_u256(); + let mut run = |elems: &[U256]| -> (bool, Vec) { + let mut calldata = Vec::with_capacity((2 + elems.len()) * 0x20); + calldata.extend_from_slice(&U256::from(elems.len()).to_be_bytes::<0x20>()); + calldata.extend_from_slice(&r.to_be_bytes::<0x20>()); + for elem in elems { + calldata.extend_from_slice(&elem.to_be_bytes::<0x20>()); + } + match evm.try_call(address, calldata) { + CallOutcome::Success { output, .. } => { + assert_eq!( + output.len(), + (1 + elems.len()) * 0x20, + "harness returndata shape" + ); + let flag = U256::try_from_be_slice(&output[..0x20]).unwrap(); + assert!(flag <= U256::from(1), "success flag must be boolean"); + let words = output[0x20..] + .chunks_exact(0x20) + .map(|word| U256::try_from_be_slice(word).unwrap()) + .collect(); + (flag == U256::from(1), words) + } + outcome => panic!("harness must not revert or halt: {outcome:?}"), + } + }; + + let word = |value: u64| U256::from(value); + let inv = + |value: u64| crate::lowering::encoding::fe_to_u256::(F::from(value).invert().unwrap()); + + // Canonical batches succeed and invert every element in place; the + // lengths cover the empty, singleton, two-element, and looped general + // paths. + let (ok, out) = run(&[]); + assert!(ok, "empty batch must be a no-op success"); + assert!(out.is_empty()); + for elems in [vec![7u64], vec![2, 3], vec![1, 2, 3, 5, 7]] { + let input: Vec = elems.iter().copied().map(word).collect(); + let (ok, out) = run(&input); + assert!(ok, "canonical batch of {} must succeed", elems.len()); + let expected: Vec = elems.iter().copied().map(inv).collect(); + assert_eq!( + out, + expected, + "batch of {} must produce native inverses", + elems.len() + ); + } + + // Every rejection leaves the input run untouched: zero and anything + // congruent to zero mod r has no inverse, and non-canonical words with + // invertible residues (x = r + 5, 2^256 - 1) must fail closed in both + // paths rather than being reduced by mulmod. The three r + 5 positions + // hit the general path's first-element, loop, and final-element guards. + let r_plus_5 = r + word(5); + for (name, elems) in [ + ("singleton literal zero", vec![U256::ZERO]), + ("singleton x = r", vec![r]), + ("singleton x = r + 5", vec![r_plus_5]), + ("singleton x = 2^256 - 1", vec![U256::MAX]), + ("general literal zero", vec![word(2), U256::ZERO, word(3)]), + ("general x = r", vec![word(2), r, word(3)]), + ( + "general first element x = r + 5", + vec![r_plus_5, word(2), word(3)], + ), + ( + "general loop element x = r + 5", + vec![word(2), r_plus_5, word(3)], + ), + ( + "general final element x = r + 5", + vec![word(2), word(3), r_plus_5], + ), + ( + "general two-element x = 2^256 - 1", + vec![word(2), U256::MAX], + ), + ] { + let (ok, out) = run(&elems); + assert!(!ok, "{name} must fail closed"); + assert_eq!(out, elems, "{name} must leave the input words untouched"); + } +} + #[test] fn verifier_constructor_rejects_missing_or_mismatched_eip2537_precompiles() { if !poseidon_inputs_available_for_evm() { diff --git a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol index 7f5d40d89..40e7e6568 100644 --- a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol @@ -524,16 +524,34 @@ contract Halo2Verifier { // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { diff --git a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol index 7a6702aef..ee59832c1 100644 --- a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol @@ -524,16 +524,34 @@ contract Halo2Verifier { // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { diff --git a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol index f897a9c32..7679a069d 100644 --- a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol @@ -524,16 +524,34 @@ contract Halo2Verifier { // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { diff --git a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol index 88bdcc4b8..36d30c3e4 100644 --- a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol @@ -524,16 +524,34 @@ contract Halo2Verifier { // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { diff --git a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul index 589fe1273..5ba918b36 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul @@ -175,16 +175,34 @@ // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { From 0cd1bdb1971ccc350ad784a3181a8a213e8a1cc7 Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 21 Jul 2026 00:52:02 +0100 Subject: [PATCH 72/72] Harden quotient certification and spill-window coverage Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../src/lowering/layout/memory.rs | 2 +- proofs/solidity-verifier/src/lowering/plan.rs | 3 +- .../lowering/quotient_numerator/vm/certify.rs | 111 ++++++++++++++++-- .../quotient_numerator/vm/reference.rs | 19 +-- .../solidity-verifier/src/lowering/tests.rs | 84 +++++++++++++ proofs/solidity-verifier/src/test.rs | 41 +++++-- 6 files changed, 230 insertions(+), 30 deletions(-) diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 54a565c73..5be8d69a1 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -1075,7 +1075,7 @@ impl VerifierMemoryLayout { // solc's opaque spill liveness -- so enforce disjointness by address. // `compiled_memoryguard_does_not_overlap_generated_layout` checks the // complementary bound, `reserved_end <= LOW_MEMORY_SCRATCH_START`, - // against real compiled bytecode. + // against real compiled verifier and quotient-evaluator bytecode. for region in &self.map.regions { if region.len != 0 && region.start < LOW_MEMORY_SCRATCH_START { return Err(format!( diff --git a/proofs/solidity-verifier/src/lowering/plan.rs b/proofs/solidity-verifier/src/lowering/plan.rs index 9f3f60017..6e001d3e6 100644 --- a/proofs/solidity-verifier/src/lowering/plan.rs +++ b/proofs/solidity-verifier/src/lowering/plan.rs @@ -178,6 +178,7 @@ impl LoweringPlan { &plan.quotient.plan, &plan.quotient.build, &baseline_build, + &plan.vk, ) .unwrap_or_else(|err| panic!("quotient dual-build certification failed: {err}")); @@ -340,7 +341,7 @@ impl LoweringPlan { } // Prove the emitted bytecode still evaluates the identities it was // lowered from, before it can be pinned into a verifying key. - certify::certify_quotient_program(&self.quotient.plan, &self.quotient.build) + certify::certify_quotient_program(&self.quotient.plan, &self.quotient.build, &self.vk) .map_err(|err| format!("quotient program certification failed: {err}"))?; Ok(()) } diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs index a166ed24e..a8348ec77 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs @@ -19,20 +19,107 @@ use ff::Field; use midnight_curves::Fq; +use sha3::{Digest, Keccak256}; use super::{ quotient_op_len, reference::{eval_quotient_expr, eval_quotient_identity, QuotientRefMemory}, - QuotientProgramBuild, QuotientProgramItem, QuotientProgramPlan, QuotientTarget, Q_OP_FOLD_MAIN, - Q_OP_FOLD_SELECTOR, Q_OP_NATIVE_IDENTITY, Q_OP_NATIVE_LOOKUP, Q_OP_NATIVE_PERMUTATION, + QuotientExpr, QuotientMem, QuotientProgramBuild, QuotientProgramItem, QuotientProgramPlan, + QuotientTarget, Q_OP_FOLD_MAIN, Q_OP_FOLD_SELECTOR, Q_OP_NATIVE_IDENTITY, Q_OP_NATIVE_LOOKUP, + Q_OP_NATIVE_PERMUTATION, }; +use crate::lowering::render::Halo2VerifyingKey; /// Seed for the certification assignment. /// -/// Fixed rather than random so a failing render reproduces exactly. The check -/// does not need unpredictability: the program is fixed at codegen time and -/// cannot adapt to the seed. -const QUOTIENT_CERTIFY_SEED: u64 = 0x6d69_6466_616c_6c01; +/// The seed is deterministic so failing renders reproduce exactly, but it is +/// derived from the finalized artifact rather than fixed globally. This keeps +/// the sampled assignment independent of any circuit author's pre-render view +/// of the quotient program while preserving reproducible diagnostics. +const QUOTIENT_CERTIFY_SEED_DOMAIN: &[u8] = b"midfall/quotient-vm/certify-seed/v3"; + +/// Derive the random-assignment seed from every compared artifact. +/// +/// Binding the oracle expressions closes the case where a miscompile drops a +/// tunable constant before it reaches the emitted constant table. Binding both +/// builds makes the dual-build challenge depend on the optimized and baseline +/// representations. The VK payload is included after generator invariants have +/// checked that its quotient sections match the finalized build. +pub(crate) fn derive_certify_seed( + builds: &[&QuotientProgramBuild], + exprs: &[&QuotientExpr], + vk_payload: &[u8], +) -> [u8; 32] { + let mut hasher = Keccak256::new(); + hasher.update(QUOTIENT_CERTIFY_SEED_DOMAIN); + + hasher.update((builds.len() as u64).to_be_bytes()); + for build in builds { + hasher.update((build.bytes.len() as u64).to_be_bytes()); + hasher.update(&build.bytes); + + hasher.update((build.consts.len() as u64).to_be_bytes()); + for value in &build.consts { + hasher.update(value.to_be_bytes::<32>()); + } + } + + hasher.update((exprs.len() as u64).to_be_bytes()); + for expr in exprs { + hash_quotient_expr(&mut hasher, expr); + } + + hasher.update((vk_payload.len() as u64).to_be_bytes()); + hasher.update(vk_payload); + + hasher.finalize().into() +} + +/// Hash one expression with explicit node and memory-address tags. +fn hash_quotient_expr(hasher: &mut Keccak256, expr: &QuotientExpr) { + match expr { + QuotientExpr::Const(value) => { + hasher.update([0]); + hasher.update(value.to_be_bytes::<32>()); + } + QuotientExpr::Mem(QuotientMem::Literal(ptr)) => { + hasher.update([1]); + hasher.update(ptr.to_be_bytes()); + } + QuotientExpr::Mem(QuotientMem::Token(token)) => { + hasher.update([2, *token]); + } + QuotientExpr::Mem(QuotientMem::TokenOffset(token, offset)) => { + hasher.update([3, *token]); + hasher.update(offset.to_be_bytes()); + } + QuotientExpr::Add(lhs, rhs) => { + hasher.update([4]); + hash_quotient_expr(hasher, lhs); + hash_quotient_expr(hasher, rhs); + } + QuotientExpr::Mul(lhs, rhs) => { + hasher.update([5]); + hash_quotient_expr(hasher, lhs); + hash_quotient_expr(hasher, rhs); + } + QuotientExpr::Neg(inner) => { + hasher.update([6]); + hash_quotient_expr(hasher, inner); + } + } +} + +/// Expressions evaluated by the compact quotient program. +fn interpreted_exprs(plan: &QuotientProgramPlan) -> Vec<&QuotientExpr> { + plan.items + .iter() + .filter_map(|item| match item { + QuotientProgramItem::Identity(identity) => Some(&identity.expr), + _ => None, + }) + .collect() +} /// Certify that the emitted bytecode evaluates the planned identities. /// @@ -42,8 +129,12 @@ const QUOTIENT_CERTIFY_SEED: u64 = 0x6d69_6466_616c_6c01; pub(crate) fn certify_quotient_program( plan: &QuotientProgramPlan, build: &QuotientProgramBuild, + vk: &Halo2VerifyingKey, ) -> Result<(), String> { - let mut mem = QuotientRefMemory::new(QUOTIENT_CERTIFY_SEED); + let exprs = interpreted_exprs(plan); + let vk_payload = vk.bytes(); + let seed = derive_certify_seed(&[build], &exprs, &vk_payload); + let mut mem = QuotientRefMemory::new(seed); let bytes = &build.bytes; let mut cursor = 0usize; @@ -209,8 +300,12 @@ pub(crate) fn certify_quotient_builds_agree( plan: &QuotientProgramPlan, optimized: &QuotientProgramBuild, baseline: &QuotientProgramBuild, + vk: &Halo2VerifyingKey, ) -> Result<(), String> { - let mut mem = QuotientRefMemory::new(QUOTIENT_CERTIFY_SEED); + let exprs = interpreted_exprs(plan); + let vk_payload = vk.bytes(); + let seed = derive_certify_seed(&[optimized, baseline], &exprs, &vk_payload); + let mut mem = QuotientRefMemory::new(seed); let optimized_values = identity_values(plan, optimized, &mut mem)?; let baseline_values = identity_values(plan, baseline, &mut mem)?; diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs index 7cd60866e..ceb954afb 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs @@ -9,12 +9,13 @@ //! miscompiled an identity, and the render is rejected before any artifact is //! produced. //! -//! Why a random-assignment check is sufficient: the quotient program is fixed -//! per verifying key and never sees attacker input, and Fr arithmetic has no -//! edge cases. Any miscompilation therefore yields a *value-independent* wrong -//! polynomial, which disagrees with the correct one at a random assignment with -//! probability `1 - deg/|Fr|`. One evaluation at a pseudorandom point is enough -//! to catch it with overwhelming probability. +//! Why a random-assignment check is sufficient: the certifier samples a +//! deterministic challenge from the finalized quotient bytecode, constant +//! table, oracle expression trees, and VK payload, then evaluates the fixed +//! program at that assignment. Any miscompilation therefore yields a wrong +//! polynomial that was fixed before the challenge was known, and it disagrees +//! with the correct one with probability `1 - deg/|Fr|`. One challenge-derived +//! evaluation is enough to catch it with overwhelming probability. //! //! Scope. This certifies the **emitter to reference-interpreter** leg, which is //! where the shape recognizers in the parent module live. The @@ -55,13 +56,13 @@ use crate::lowering::layout::WORD_BYTES; /// missing map key. #[derive(Clone, Debug)] pub(crate) struct QuotientRefMemory { - seed: u64, + seed: [u8; 32], cache: HashMap<(u8, u32), Fq>, } impl QuotientRefMemory { /// Build an assignment for one certification run. - pub(crate) fn new(seed: u64) -> Self { + pub(crate) fn new(seed: [u8; 32]) -> Self { Self { seed, cache: HashMap::new(), @@ -87,7 +88,7 @@ impl QuotientRefMemory { } let mut hasher = Keccak256::new(); hasher.update(b"midfall/quotient-vm/reference-memory/v1"); - hasher.update(self.seed.to_be_bytes()); + hasher.update(self.seed); hasher.update([domain]); hasher.update(address.to_be_bytes()); let lo = hasher.finalize(); diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index 4f0b880d7..d4c1c7f25 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -419,6 +419,90 @@ fn lowering_plan_certifies_emitted_quotient_bytecode() { ); } +#[test] +fn quotient_certification_seed_binds_all_compared_artifacts() { + let build = QuotientProgramBuild { + bytes: vec![Q_OP_PUSH_CONST_U8, 0], + consts: vec![U256::from(11u64)], + max_stack: 1, + used_ops: vec![Q_OP_PUSH_CONST_U8], + used_mem_tokens: Vec::new(), + }; + let baseline = QuotientProgramBuild { + bytes: vec![Q_OP_PUSH_CONST, 0, 0], + consts: vec![U256::from(13u64)], + max_stack: 1, + used_ops: vec![Q_OP_PUSH_CONST], + used_mem_tokens: Vec::new(), + }; + let expr = QuotientExpr::Add( + Box::new(QuotientExpr::Mem(QuotientMem::Literal(0x120))), + Box::new(QuotientExpr::Const(U256::from(17u64))), + ); + let vk_payload = [0xabu8; 64]; + let seed = super::quotient_numerator::vm::certify::derive_certify_seed( + &[&build, &baseline], + &[&expr], + &vk_payload, + ); + + let mut changed_bytecode = build.clone(); + changed_bytecode.bytes[0] ^= 1; + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&changed_bytecode, &baseline], + &[&expr], + &vk_payload, + ) + ); + + let mut changed_const = build.clone(); + changed_const.consts[0] = U256::from(19u64); + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&changed_const, &baseline], + &[&expr], + &vk_payload, + ) + ); + + let mut changed_baseline = baseline.clone(); + changed_baseline.consts[0] = U256::from(23u64); + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&build, &changed_baseline], + &[&expr], + &vk_payload, + ) + ); + + let changed_expr = QuotientExpr::Add( + Box::new(QuotientExpr::Mem(QuotientMem::Literal(0x120))), + Box::new(QuotientExpr::Const(U256::from(29u64))), + ); + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&build, &baseline], + &[&changed_expr], + &vk_payload, + ) + ); + + let changed_vk_payload = [0xcdu8; 64]; + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&build, &baseline], + &[&expr], + &changed_vk_payload, + ) + ); +} + /// Generate parameters and VK for lowering-plan integration tests. fn lowering_plan_test_vk() -> ( ParamsKZG, diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index 1a4f07793..e3e8747ab 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -1371,12 +1371,14 @@ fn pinned_quotient_verifier_rejects_wrong_vk_and_quotient_contracts() { /// the circuit, the solc release, and the optimizer schedule -- so assert the /// property against real compiled bytecode rather than assuming it holds. /// -/// `VerifierMemoryLayout::validate()` enforces the complementary bound: every -/// generated region starts at or above `LOW_MEMORY_SCRATCH_START`, so -/// `reserved_end <= LOW_MEMORY_SCRATCH_START` here proves full disjointness. -/// The accumulator-bearing variants are checked too, because their -/// FinalPairing pairing-batch block adds frames and live values the property -/// fixture never renders. +/// `VerifierMemoryLayout::validate()` enforces the complementary bound for the +/// generated layout: every generated region starts at or above +/// `LOW_MEMORY_SCRATCH_START`, so `reserved_end <= LOW_MEMORY_SCRATCH_START` +/// here proves full disjointness. Check both verifier and quotient evaluator +/// runtimes, because each via-IR compilation unit receives its own independent +/// spill reservation. The accumulator-bearing variants are checked too, +/// because their FinalPairing pairing-batch block adds frames and live values +/// the property fixture never renders. #[test] fn compiled_memoryguard_does_not_overlap_generated_layout() { if !poseidon_inputs_available_for_evm() { @@ -1403,12 +1405,24 @@ fn compiled_memoryguard_does_not_overlap_generated_layout() { ("embedded", fixture.embedded_verifier_solidity.as_str()), ("separate", fixture.separate_verifier_solidity.as_str()), ("quotient", fixture.quotient_verifier_solidity.as_str()), + ( + "quotient evaluator", + fixture.quotient_evaluator_solidity.as_str(), + ), + ( + "trace quotient evaluator", + fixture.trace_quotient_evaluator_solidity.as_str(), + ), ] { assert_memoryguard_clears_generated_layout(name, source); } - for (name, _, artifacts) in render_accumulator_verifier_variants() { + for (name, _, artifacts, quotient_evaluator) in render_accumulator_verifier_variants() { assert_memoryguard_clears_generated_layout(name, &artifacts.verifier); + assert_memoryguard_clears_generated_layout( + &format!("{name} quotient evaluator"), + "ient_evaluator, + ); } } @@ -1816,8 +1830,10 @@ fn accumulator_fixed_base_tail_must_match_verifying_key() { /// Render the three accumulator-bearing verifier variants over the Poseidon /// fixture VK: fully collapsed, fixed-base scalar tail, and point-pair -/// encodings. Returns `(name, has_carried_scalars, artifacts)` per variant. -fn render_accumulator_verifier_variants() -> Vec<(&'static str, bool, crate::RenderedArtifacts)> { +/// encodings. Returns `(name, has_carried_scalars, artifacts, evaluator)` per +/// variant. +fn render_accumulator_verifier_variants( +) -> Vec<(&'static str, bool, crate::RenderedArtifacts, String)> { let srs_dir = srs_dir(); env::set_var("SRS_DIR", &srs_dir); let relation = PoseidonExample; @@ -1870,7 +1886,10 @@ fn render_accumulator_verifier_variants() -> Vec<(&'static str, bool, crate::Ren ..RenderOptions::default() }) .unwrap_or_else(|err| panic!("{name} should render: {err}")); - (name, has_carried_scalars, artifacts) + let quotient_evaluator = generator + .render_quotient_evaluator(RenderDiagnostics::default()) + .unwrap_or_else(|err| panic!("{name} quotient evaluator should render: {err}")); + (name, has_carried_scalars, artifacts, quotient_evaluator) }) .collect() } @@ -1896,7 +1915,7 @@ fn accumulator_verifier_variants_compile_with_pinned_solc() { return; } - for (name, has_carried_scalars, artifacts) in render_accumulator_verifier_variants() { + for (name, has_carried_scalars, artifacts, _) in render_accumulator_verifier_variants() { let verifier = artifacts.verifier; assert!( verifier.contains("function validate_public_accumulator"),