diff --git a/Cargo.lock b/Cargo.lock index f173bac..efc6a67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,6 +567,7 @@ dependencies = [ "ff_ext", "itertools 0.13.0", "mpcs", + "multilinear_extensions", "openvm", "openvm-circuit", "openvm-native-circuit", @@ -576,6 +577,7 @@ dependencies = [ "openvm-stark-backend", "openvm-stark-sdk", "p3-air", + "p3-baby-bear 0.1.0 (git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c)", "p3-challenger 0.1.0 (git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c)", "p3-commit 0.1.0 (git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c)", "p3-field 0.1.0 (git+https://github.com/Plonky3/Plonky3.git?rev=1ba4e5c)", @@ -587,6 +589,8 @@ dependencies = [ "rand", "serde", "serde_json", + "sumcheck", + "transcript", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c4ecc8b..941091f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ eyre = "0.6.12" # Plonky3 p3-air = { git = "https://github.com/Plonky3/Plonky3.git", rev = "1ba4e5c" } +p3-baby-bear = { git = "https://github.com/Plonky3/Plonky3.git", rev = "1ba4e5c" } p3-field = { git = "https://github.com/Plonky3/Plonky3.git", rev = "1ba4e5c" } p3-commit = { git = "https://github.com/Plonky3/Plonky3.git", rev = "1ba4e5c" } p3-matrix = { git = "https://github.com/Plonky3/Plonky3.git", rev = "1ba4e5c" } @@ -38,7 +39,10 @@ ark-serialize = "0.5" # Ceno ceno_zkvm = { git = "https://github.com/scroll-tech/ceno.git", branch = "feat/export_ff_ext" } ceno_emul = { git = "https://github.com/scroll-tech/ceno.git", branch = "feat/export_ff_ext" } +ceno_sumcheck = { git = "https://github.com/scroll-tech/ceno.git", branch = "feat/export_ff_ext", package = "sumcheck" } +ceno_mle = { git = "https://github.com/scroll-tech/ceno.git", branch = "feat/export_ff_ext", package = "multilinear_extensions" } mpcs = { git = "https://github.com/scroll-tech/ceno.git", branch = "feat/export_ff_ext" } +ceno_transcript = { git = "https://github.com/scroll-tech/ceno.git", branch = "feat/export_ff_ext", package = "transcript" } ff_ext = { git = "https://github.com/scroll-tech/ceno.git", branch = "feat/export_ff_ext" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -53,4 +57,4 @@ parallel = [ "openvm-native-recursion/parallel", "openvm-stark-backend/parallel", "openvm-stark-sdk/parallel", -] \ No newline at end of file +] diff --git a/src/e2e/mod.rs b/src/e2e/mod.rs index a480282..a790cab 100644 --- a/src/e2e/mod.rs +++ b/src/e2e/mod.rs @@ -1,7 +1,7 @@ use crate::tower_verifier::binding::IOPProverMessage; use crate::zkvm_verifier::binding::ZKVMProofInput; use crate::zkvm_verifier::binding::{ - E, F, TowerProofInput, ZKVMOpcodeProofInput, ZKVMTableProofInput, + TowerProofInput, ZKVMOpcodeProofInput, ZKVMTableProofInput, E, F, }; use crate::zkvm_verifier::verifier::verify_zkvm_proof; use eyre::Result; @@ -9,9 +9,11 @@ use ff_ext::BabyBearExt4; use itertools::Itertools; use mpcs::BasefoldCommitment; use mpcs::{Basefold, BasefoldRSParams}; -use openvm_circuit::arch::{SystemConfig, VmExecutor, instructions::program::Program}; +use openvm_circuit::arch::{instructions::program::Program, SystemConfig, VmExecutor}; use openvm_native_circuit::{Native, NativeConfig}; -use openvm_native_compiler::{asm::AsmBuilder, conversion::CompilerOptions, conversion::convert_program, asm::AsmCompiler}; +use openvm_native_compiler::{ + asm::AsmBuilder, asm::AsmCompiler, conversion::convert_program, conversion::CompilerOptions, +}; use openvm_native_recursion::hints::Hintable; use openvm_stark_backend::config::StarkGenericConfig; use openvm_stark_sdk::{ @@ -25,7 +27,7 @@ type SC = BabyBearPoseidon2Config; type EF = ::Challenge; use ceno_zkvm::{ - scheme::{ZKVMProof, verifier::ZKVMVerifier}, + scheme::{verifier::ZKVMVerifier, ZKVMProof}, structs::ZKVMVerifyingKey, }; @@ -468,7 +470,9 @@ pub fn test_zkvm_proof_verifier_from_bincode_exports() { let asm_code = compiler.code(); println!("=> asm_code.blocks: {:?}", asm_code.blocks); println!("=> asm_code.labels: {:?}", asm_code.labels); - let program: Program,> = convert_program(asm_code, options); + let program: Program< + p3_monty_31::MontyField31, + > = convert_program(asm_code, options); return (); @@ -492,14 +496,9 @@ pub fn test_zkvm_proof_verifier_from_bincode_exports() { }); */ - let res = executor.execute_and_then( - program, - witness_stream, - |_, seg| { - Ok(seg) - }, - |err| err, - ).unwrap(); + let res = executor + .execute_and_then(program, witness_stream, |_, seg| Ok(seg), |err| err) + .unwrap(); for (i, seg) in res.iter().enumerate() { // println!("=> segment {:?} metrics: {:?}", i, seg.metrics); diff --git a/src/tower_verifier/program.rs b/src/tower_verifier/program.rs index 6204cbd..70edf3c 100644 --- a/src/tower_verifier/program.rs +++ b/src/tower_verifier/program.rs @@ -21,6 +21,7 @@ pub(crate) fn interpolate_uni_poly( p_i: &Array>, eval_at: Ext, ) -> Ext { + let len = p_i.len(); let evals: Array> = builder.dyn_array(len.clone()); let prod: Ext = builder.eval(eval_at); @@ -103,19 +104,16 @@ pub fn iop_verifier_state_verify( challenger.observe(builder, max_num_variables); challenger.observe(builder, max_degree); - let one: Ext = builder.constant(C::EF::ONE); - let round: Ext = builder.constant(C::EF::ONE); - let polynomials_received: Array>> = - builder.dyn_array(max_num_variables_usize.clone()); + let round: Var = Var::eval(builder, C::N::ONE); let challenges: Array> = builder.dyn_array(max_num_variables_usize.clone()); builder .range(0, max_num_variables_usize.clone()) .for_each(|i_vec, builder| { + builder.cycle_tracker_start("IOPVerifierState::verify_round_and_update_state"); let i = i_vec[0]; let prover_msg = builder.get(&prover_messages, i); - builder.cycle_tracker_start("IOPVerifierState::verify_round_and_update_state"); iter_zip!(builder, prover_msg.evaluations).for_each(|ptr_vec, builder| { let e = builder.iter_ptr_get(&prover_msg.evaluations, ptr_vec[0]); let e_felts = builder.ext2felt(e); @@ -126,44 +124,42 @@ pub fn iop_verifier_state_verify( let challenge = challenger.sample_ext(builder); builder.set(&challenges, i, challenge); - builder.set(&polynomials_received, i, prover_msg.evaluations); - builder.assign(&round, round + one); + builder.assign(&round, round + C::N::ONE); builder.cycle_tracker_end("IOPVerifierState::verify_round_and_update_state"); }); - + builder.cycle_tracker_start("IOPVerifierState::check_and_generate_subclaim"); // set `expected` to P(r)` - let expected_len: RVar<_> = builder.eval_expr(polynomials_received.len() + RVar::from(1)); + let expected_len: RVar<_> = builder.eval_expr(max_num_variables_usize.clone() + RVar::from(1)); let expected_vec: Array> = builder.dyn_array(expected_len.clone()); builder.set(&expected_vec, 0, out_claim.clone()); let truncated_expected_vec = expected_vec.slice(builder, 1, expected_len); - iter_zip!( - builder, - polynomials_received, - challenges, - truncated_expected_vec - ) - .for_each(|idx_vec, builder| { - let poly_ptr = idx_vec[0]; - let c_ptr = idx_vec[1]; + iter_zip!(builder, prover_messages, challenges, truncated_expected_vec).for_each( + |idx_vec, builder| { + builder.cycle_tracker_start("interpolate_uni_poly"); + let poly_ptr = idx_vec[0]; + let c_ptr = idx_vec[1]; - let evaluations = builder.iter_ptr_get(&polynomials_received, poly_ptr); - let c = builder.iter_ptr_get(&challenges, c_ptr); + let msg = builder.iter_ptr_get(&prover_messages, poly_ptr); + let c = builder.iter_ptr_get(&challenges, c_ptr); - let expected_ptr = idx_vec[2]; - let expected = interpolate_uni_poly(builder, &evaluations, c); + let expected_ptr = idx_vec[2]; + // evaluate p(r) from evaluations of p(x) on { 0, 1, ..., max_deg } using barycentric interpolation + let expected = interpolate_uni_poly(builder, &msg.evaluations, c); - builder.iter_ptr_set(&truncated_expected_vec, expected_ptr, expected); - }); + builder.iter_ptr_set(&truncated_expected_vec, expected_ptr, expected); + builder.cycle_tracker_end("interpolate_uni_poly"); + }, + ); // l-append asserted_sum to the first position of the expected vector - iter_zip!(builder, polynomials_received, expected_vec).for_each(|idx_vec, builder| { - let evaluations = builder.iter_ptr_get(&polynomials_received, idx_vec[0]); + iter_zip!(builder, prover_messages, expected_vec).for_each(|idx_vec, builder| { + let msg = builder.iter_ptr_get(&prover_messages, idx_vec[0]); let expected = builder.iter_ptr_get(&expected_vec, idx_vec[1]); - let e1 = builder.get(&evaluations, 0); - let e2 = builder.get(&evaluations, 1); + let e1 = builder.get(&msg.evaluations, 0); + let e2 = builder.get(&msg.evaluations, 1); let target: Ext<::F, ::EF> = builder.eval(e1 + e2); builder.assert_ext_eq(expected, target); @@ -175,6 +171,45 @@ pub fn iop_verifier_state_verify( (challenges, expected) } +#[cfg(test)] +pub(crate) fn interpolate_uni_poly_with_weights( + builder: &mut Builder, + p_i: &Array>, + eval_at: Ext, + weights: Array>, +) -> Ext { + // \prod_i (eval_at - i) + let num_points = p_i.len().get_var(); + + let one: Ext = builder.constant(C::EF::ONE); + let zero: Ext = builder.constant(C::EF::ZERO); + let mut iter_i: Ext = builder.eval(zero + zero); // 0 + 0 to take advantage of AddE + let prod: Ext = builder.eval(one + zero); // 1 + 0 to take advantage of AddE + builder.range(0, num_points).for_each(|_, builder| { + builder.assign(&prod, prod * (eval_at - iter_i)); + builder.assign(&iter_i, iter_i + one); + }); + + iter_i = builder.eval(zero + zero); // reset to 0 + let result = zero; // take ownership + iter_zip!(builder, p_i, weights).for_each(|ptr_vec, builder| { + let pi_ptr = ptr_vec[0]; + let w_ptr = ptr_vec[1]; + + let p_i_val = builder.iter_ptr_get(p_i, pi_ptr); + let weight = builder.iter_ptr_get(&weights, w_ptr); + + // weight_i = \prod_{j!=i} 1/(i-j) + // \sum_{i=0}^len p_i * weight_i * prod / (eval_at-i) + let e: Ext = builder.eval(eval_at - iter_i); + let term = p_i_val * weight * prod / e; // TODO: how to handle e = 0 + builder.assign(&iter_i, iter_i + one); + builder.assign(&result, result + term); + }); + + result +} + pub fn verify_tower_proof( builder: &mut Builder, challenger: &mut impl ChallengerVariable, @@ -616,3 +651,234 @@ pub fn verify_tower_proof( logup_spec_q_point_n_eval, ) } + +#[cfg(test)] +mod tests { + use crate::tower_verifier::binding::IOPProverMessage; + use crate::tower_verifier::program::interpolate_uni_poly_with_weights; + use crate::tower_verifier::program::iop_verifier_state_verify; + use ceno_mle::mle::DenseMultilinearExtension; + use ceno_mle::virtual_poly::ArcMultilinearExtension; + use ceno_mle::virtual_polys::VirtualPolynomials; + use ceno_sumcheck::structs::IOPProverState; + use ceno_transcript::BasicTranscript; + use ff_ext::BabyBearExt4; + use ff_ext::FieldFrom; + use itertools::Itertools; + use openvm_circuit::arch::SystemConfig; + use openvm_circuit::arch::VmExecutor; + use openvm_native_circuit::Native; + use openvm_native_circuit::NativeConfig; + use openvm_native_compiler::asm::AsmCompiler; + use openvm_native_compiler::asm::{AsmBuilder, AsmConfig}; + use openvm_native_compiler::conversion::convert_program; + use openvm_native_compiler::conversion::CompilerOptions; + use openvm_native_compiler::ir::Array; + use openvm_native_compiler::ir::Ext; + use openvm_native_compiler::prelude::Felt; + use openvm_native_recursion::challenger::duplex::DuplexChallengerVariable; + use p3_baby_bear::BabyBear; + use p3_field::extension::BinomialExtensionField; + use p3_field::Field; + use p3_field::FieldAlgebra; + use rand::thread_rng; + + use openvm_native_recursion::hints::Hintable; + + #[test] + fn test_barycentric_eval() { + type F = BabyBear; + type E = BabyBearExt4; + type EF = BinomialExtensionField; + type C = AsmConfig; + + let mut builder = AsmBuilder::::default(); + + let deg = 3; + // p(x) = x^3 + x^2 + x + 1 + let p_i: Array> = builder.dyn_array(deg + 1); + let eval_p_x = |x: usize| x.pow(3) + x.pow(2) + x + 1; + for i in 0..=deg { + let px = eval_p_x(i); + let px: Ext = builder.constant(EF::from_canonical_u32(px as u32)); + builder.set(&p_i, i, px); + } + + let r = 5; + let eval_at: Ext = builder.constant(EF::from_canonical_u32(r as u32)); + + let points = (0..=deg) + .into_iter() + .map(|i| EF::from_canonical_u32(i as u32)) + .collect_vec(); + let weights = points + .iter() + .enumerate() + .map(|(j, point_j)| { + points + .iter() + .enumerate() + .filter(|&(i, _)| (i != j)) + .map(|(_, point_i)| *point_j - *point_i) + .reduce(|acc, value| acc * value) + .unwrap_or(EF::ONE) + .inverse() + }) + .collect::>(); + + let weight_array: Array> = builder.dyn_array(4); + weights.into_iter().enumerate().for_each(|(i, w)| { + let w: Ext = builder.constant(w); + builder.set(&weight_array, i, w); + }); + + builder.cycle_tracker_start("interpolate_uni_poly_with_weights"); + let res = interpolate_uni_poly_with_weights(&mut builder, &p_i, eval_at, weight_array); + builder.cycle_tracker_end("interpolate_uni_poly_with_weights"); + let expected_res: Ext = builder.constant(EF::from_canonical_u32(eval_p_x(r) as u32)); + builder.assert_ext_eq(res, expected_res); + builder.halt(); + + let options = CompilerOptions::default(); + let mut compiler = AsmCompiler::new(options.word_size); + compiler.build(builder.operations); + let asm_code = compiler.code(); + println!("asm code"); + println!("{}", asm_code); + let program = convert_program(asm_code, options); + let system_config = SystemConfig::default() + .with_public_values(4) + .with_max_segment_len((1 << 22) - 100) + .with_profiling(); + let config = NativeConfig::new(system_config, Native); + let executor = VmExecutor::::new(config); + + let res = executor + .execute_and_then(program, vec![], |_, seg| Ok(seg), |err| err) + .unwrap(); + + #[cfg(feature = "bench-metrics")] + { + println!("cyclces: {}", res[0].metrics.cycle_count); + } + } + + #[test] + fn test_simple_sumcheck() { + type F = BabyBear; + type E = BabyBearExt4; + type EF = BinomialExtensionField; + type C = AsmConfig; + + let nv = 5; + let degree = 3; + + let mut builder = AsmBuilder::::default(); + + let out_claim = EF::read(&mut builder); + let prover_msgs = Vec::::read(&mut builder); + + let max_num_variables: Felt = builder.constant(F::from_canonical_u32(nv as u32)); + let max_degree: Felt = builder.constant(F::from_canonical_u32(degree as u32)); + + let mut challenger: DuplexChallengerVariable = + DuplexChallengerVariable::new(&mut builder); + + iop_verifier_state_verify( + &mut builder, + &mut challenger, + &out_claim, + &prover_msgs, + max_num_variables, + max_degree, + ); + + builder.halt(); + + // get the assembly code + let options = CompilerOptions::default(); + let mut compiler = AsmCompiler::new(options.word_size); + compiler.build(builder.operations); + let asm_code = compiler.code(); + println!("asm code"); + println!("{asm_code}"); + + // run sumcheck prover to get sumcheck proof + let mut rng = thread_rng(); + let (mles, expected_sum) = + DenseMultilinearExtension::::random_mle_list(nv, degree, &mut rng); + let mles: Vec> = + mles.into_iter().map(|mle| mle as _).collect_vec(); + let mut virtual_poly: VirtualPolynomials<'_, E> = VirtualPolynomials::new(1, nv); + virtual_poly.add_mle_list(mles.iter().collect_vec(), E::from_v(1)); + + let mut transcript = BasicTranscript::new(&[]); + let (sumcheck_proof, _) = IOPProverState::prove(virtual_poly, &mut transcript); + let mut input_stream = Vec::new(); + + // hacky way: convert E to EF but actually they are the same + let expected_sum: EF = cast_vec(vec![expected_sum])[0]; + input_stream.extend(expected_sum.write()); + input_stream.extend( + sumcheck_proof + .proofs + .into_iter() + .map(|msg| { + let evaluations: Vec = cast_vec(msg.evaluations); + IOPProverMessage { evaluations } + }) + .collect_vec() + .write(), + ); + + // get execution result + let program = convert_program(asm_code, options); + let system_config = SystemConfig::default() + .with_public_values(4) + .with_max_segment_len((1 << 25) - 100) + .with_profiling(); + let config = NativeConfig::new(system_config, Native); + let executor = VmExecutor::::new(config); + + let res = executor + .execute_and_then(program, input_stream, |_, seg| Ok(seg), |err| err) + .unwrap(); + + for (i, seg) in res.iter().enumerate() { + #[cfg(feature = "bench-metrics")] + { + println!( + "=> segment {} metrics.cycle_count: {:?}", + i, seg.metrics.cycle_count + ); + for (insn, count) in seg.metrics.counts.iter() { + println!("insn: {:?}, count: {:?}", insn, count); + } + println!( + "=> segment {} #(insns): {}", + i, + seg.metrics + .counts + .values() + .copied() + .into_iter() + .sum::() + ); + } + } + } + + fn cast_vec(mut vec: Vec) -> Vec { + let length = vec.len(); + let capacity = vec.capacity(); + let ptr = vec.as_mut_ptr(); + // Prevent `vec` from dropping its contents + std::mem::forget(vec); + + // Convert the pointer to the new type + let new_ptr = ptr as *mut B; + + // Create a new vector with the same length and capacity, but different type + unsafe { Vec::from_raw_parts(new_ptr, length, capacity) } + } +} diff --git a/src/transcript/mod.rs b/src/transcript/mod.rs index ec1facc..58eb2a6 100644 --- a/src/transcript/mod.rs +++ b/src/transcript/mod.rs @@ -14,3 +14,67 @@ pub fn transcript_observe_label( challenger.observe(builder, f); } } + +#[cfg(test)] +mod tests { + use ff_ext::BabyBearExt4; + use openvm_circuit::arch::{SystemConfig, VmExecutor}; + use openvm_native_circuit::{Native, NativeConfig}; + use openvm_native_compiler::conversion::{convert_program, CompilerOptions}; + use openvm_native_compiler::ir::Felt; + use openvm_native_compiler::prelude::AsmCompiler; + use openvm_native_compiler::{asm::AsmConfig, ir::Builder}; + use openvm_native_recursion::challenger::FeltChallenger; + use openvm_native_recursion::challenger::{ + duplex::DuplexChallengerVariable, CanObserveVariable, + }; + use p3_baby_bear::BabyBear; + use p3_field::extension::BinomialExtensionField; + use p3_field::FieldAlgebra; + + #[test] + fn test_sample_ext() { + type F = BabyBear; + type _E = BabyBearExt4; + type EF = BinomialExtensionField; + type C = AsmConfig; + + let mut builder = Builder::::default(); + let mut challenger = DuplexChallengerVariable::::new(&mut builder); + + // simple program + let f: Felt = builder.eval(F::from_canonical_u32(1)); + challenger.observe(&mut builder, f); + let _ext = challenger.sample_ext(&mut builder); + builder.halt(); + + // get the assembly code + let options = CompilerOptions::default(); + let mut compiler = AsmCompiler::new(options.word_size); + compiler.build(builder.operations); + let asm_code = compiler.code(); + println!("asm code"); + println!("{asm_code}"); + + // execute the program + // get execution result + let program = convert_program(asm_code, options); + let system_config = SystemConfig::default() + .with_public_values(4) + .with_max_segment_len((1 << 22) - 100); + let config = NativeConfig::new(system_config, Native); + let executor = VmExecutor::::new(config); + + let input_stream = Vec::new(); + let res = executor + .execute_and_then(program, input_stream, |_, seg| Ok(seg), |err| err) + .unwrap(); + + for (i, seg) in res.iter().enumerate() { + #[cfg(feature = "bench-metrics")] + { + println!("=> segment {:?} metrics: {:?}", i, seg.metrics); + } + } + } +} diff --git a/src/zkvm_verifier/verifier.rs b/src/zkvm_verifier/verifier.rs index d045671..1fa9f75 100644 --- a/src/zkvm_verifier/verifier.rs +++ b/src/zkvm_verifier/verifier.rs @@ -168,7 +168,7 @@ pub fn verify_zkvm_proof( let id_f: Felt = builder.constant(C::F::from_canonical_usize(subcircuit_params.id)); challenger.observe(builder, id_f); - + verify_opcode_proof( builder, &mut challenger, @@ -178,11 +178,11 @@ pub fn verify_zkvm_proof( &subcircuit_params, &ceno_constraint_system, ); - + /* _debug let cs = ceno_constraint_system.vk.circuit_vks[&subcircuit_params.name].get_cs(); let num_lks = cs.lk_expressions.len(); - + let num_instances = subcircuit_params.num_instances; let num_padded_lks_per_instance = next_pow2_instance_padding(num_lks) - num_lks; let num_padded_instance = next_pow2_instance_padding(num_instances) - num_instances; @@ -190,18 +190,18 @@ pub fn verify_zkvm_proof( num_padded_lks_per_instance * num_instances + num_lks.next_power_of_two() * num_padded_instance, )); - + builder.assign( &dummy_table_item_multiplicity, dummy_table_item_multiplicity + new_multiplicity, ); - + let record_r_out_evals_prod = product(builder, &opcode_proof.record_r_out_evals); builder.assign(&prod_r, prod_r * record_r_out_evals_prod); - + let record_w_out_evals_prod = product(builder, &opcode_proof.record_w_out_evals); builder.assign(&prod_w, prod_w * record_w_out_evals_prod); - + builder.assign( &logup_sum, logup_sum + opcode_proof.lk_p1_out_eval * opcode_proof.lk_q1_out_eval.inverse(), @@ -219,7 +219,7 @@ pub fn verify_zkvm_proof( let id_f: Felt = builder.constant(C::F::from_canonical_usize(subcircuit_params.id)); challenger.observe(builder, id_f); - + verify_table_proof( builder, &mut challenger, @@ -231,7 +231,7 @@ pub fn verify_zkvm_proof( &subcircuit_params, ceno_constraint_system, ); - + let step = C::N::from_canonical_usize(4); builder .range_with_step(0, table_proof.lk_out_evals.len(), step) @@ -239,18 +239,18 @@ pub fn verify_zkvm_proof( let p2_idx: Usize = builder.eval(idx_vec[0] + RVar::from(1)); let q1_idx: Usize = builder.eval(p2_idx.clone() + RVar::from(1)); let q2_idx: Usize = builder.eval(q1_idx.clone() + RVar::from(1)); - + let p1 = builder.get(&table_proof.lk_out_evals, idx_vec[0]); let p2 = builder.get(&table_proof.lk_out_evals, p2_idx); let q1 = builder.get(&table_proof.lk_out_evals, q1_idx); let q2 = builder.get(&table_proof.lk_out_evals, q2_idx); - + builder.assign( &logup_sum, logup_sum - p1 * q1.inverse() - p2 * q2.inverse(), ); }); - + let w_out_evals_prod = product(builder, &table_proof.w_out_evals); builder.assign(&prod_w, prod_w * w_out_evals_prod); let r_out_evals_prod = product(builder, &table_proof.r_out_evals);