From 5fd57e20fd8c13ed9c5c71c07ded690969bb99db Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 29 Aug 2023 11:52:11 +0200 Subject: [PATCH] HyperNova: move CCS struct outside of LCCCS & CCCS HyperNova nimfs: move CCS structure outside of LCCCS & CCCS, to avoid carrying around the whole CCS and duplicating data when is not needed. --- src/folding/hypernova/cccs.rs | 51 ++++++++------------ src/folding/hypernova/lcccs.rs | 25 +++++----- src/folding/hypernova/nimfs.rs | 87 ++++++++++++++++------------------ src/folding/hypernova/utils.rs | 28 +++++------ 4 files changed, 85 insertions(+), 106 deletions(-) diff --git a/src/folding/hypernova/cccs.rs b/src/folding/hypernova/cccs.rs index be1367b4..4723fc0e 100644 --- a/src/folding/hypernova/cccs.rs +++ b/src/folding/hypernova/cccs.rs @@ -27,9 +27,6 @@ pub struct Witness { /// Committed CCS instance #[derive(Debug, Clone)] pub struct CCCS { - // Underlying CCS structure - pub ccs: CCS, - // Commitment to witness pub C: C, // Public input/output @@ -49,29 +46,26 @@ impl CCS { ( CCCS:: { - ccs: self.clone(), C, x: z[1..(1 + self.l)].to_vec(), }, Witness:: { w, r_w }, ) } -} -impl CCCS { /// Computes q(x) = \sum^q c_i * \prod_{j \in S_i} ( \sum_{y \in {0,1}^s'} M_j(x, y) * z(y) ) /// polynomial over x pub fn compute_q(&self, z: &Vec) -> VirtualPolynomial { - let z_mle = vec_to_mle(self.ccs.s_prime, z); - let mut q = VirtualPolynomial::::new(self.ccs.s); + let z_mle = vec_to_mle(self.s_prime, z); + let mut q = VirtualPolynomial::::new(self.s); - for i in 0..self.ccs.q { + for i in 0..self.q { let mut prod: VirtualPolynomial = - VirtualPolynomial::::new(self.ccs.s); - for j in self.ccs.S[i].clone() { - let M_j = matrix_to_mle(self.ccs.M[j].clone()); + VirtualPolynomial::::new(self.s); + for j in self.S[i].clone() { + let M_j = matrix_to_mle(self.M[j].clone()); - let sum_Mz = compute_sum_Mz(M_j, &z_mle, self.ccs.s_prime); + let sum_Mz = compute_sum_Mz(M_j, &z_mle, self.s_prime); // Fold this sum into the running product if prod.products.is_empty() { @@ -86,7 +80,7 @@ impl CCCS { } } // Multiply by the product by the coefficient c_i - prod.scalar_mul(&self.ccs.c[i]); + prod.scalar_mul(&self.c[i]); // Add it to the running sum q = q.add(&prod); } @@ -104,11 +98,14 @@ impl CCCS { let q = self.compute_q(z); q.build_f_hat(beta).unwrap() } +} +impl CCCS { /// Perform the check of the CCCS instance described at section 4.1 pub fn check_relation( &self, pedersen_params: &PedersenParams, + ccs: &CCS, w: &Witness, ) -> Result<(), Error> { // check that C is the commitment of w. Notice that this is not verifying a Pedersen @@ -120,8 +117,8 @@ impl CCCS { [vec![C::ScalarField::one()], self.x.clone(), w.w.to_vec()].concat(); // A CCCS relation is satisfied if the q(x) multivariate polynomial evaluates to zero in the hypercube - let q_x = self.compute_q(&z); - for x in BooleanHypercube::new(self.ccs.s) { + let q_x = ccs.compute_q(&z); + for x in BooleanHypercube::new(ccs.s) { if !q_x.evaluate(&x).unwrap().is_zero() { return Err(Error::NotSatisfied); } @@ -149,9 +146,7 @@ pub mod test { let ccs = get_test_ccs::(); let z = get_test_z(3); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); - let (cccs, _) = ccs.to_cccs(&mut rng, &pedersen_params, &z); - let q = cccs.compute_q(&z); + let q = ccs.compute_q(&z); // Evaluate inside the hypercube for x in BooleanHypercube::new(ccs.s) { @@ -168,17 +163,14 @@ pub mod test { fn test_compute_Q() { let mut rng = test_rng(); - let ccs = get_test_ccs(); + let ccs: CCS = get_test_ccs(); let z = get_test_z(3); ccs.check_relation(&z).unwrap(); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); - let (cccs, _) = ccs.to_cccs(&mut rng, &pedersen_params, &z); - let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); // Compute Q(x) = eq(beta, x) * q(x). - let Q = cccs.compute_Q(&z, &beta); + let Q = ccs.compute_Q(&z, &beta); // Let's consider the multilinear polynomial G(x) = \sum_{y \in {0, 1}^s} eq(x, y) q(y) // which interpolates the multivariate polynomial q(x) inside the hypercube. @@ -205,18 +197,15 @@ pub mod test { fn test_Q_against_q() { let mut rng = test_rng(); - let ccs = get_test_ccs(); + let ccs: CCS = get_test_ccs(); let z = get_test_z(3); ccs.check_relation(&z).unwrap(); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); - let (cccs, _) = ccs.to_cccs(&mut rng, &pedersen_params, &z); - // Now test that if we create Q(x) with eq(d,y) where d is inside the hypercube, \sum Q(x) should be G(d) which // should be equal to q(d), since G(x) interpolates q(x) inside the hypercube - let q = cccs.compute_q(&z); + let q = ccs.compute_q(&z); for d in BooleanHypercube::new(ccs.s) { - let Q_at_d = cccs.compute_Q(&z, &d); + let Q_at_d = ccs.compute_Q(&z, &d); // Get G(d) by summing over Q_d(x) over the hypercube let G_at_d = BooleanHypercube::new(ccs.s) @@ -227,7 +216,7 @@ pub mod test { // Now test that they should disagree outside of the hypercube let r: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - let Q_at_r = cccs.compute_Q(&z, &r); + let Q_at_r = ccs.compute_Q(&z, &r); // Get G(d) by summing over Q_d(x) over the hypercube let G_at_r = BooleanHypercube::new(ccs.s) diff --git a/src/folding/hypernova/lcccs.rs b/src/folding/hypernova/lcccs.rs index cef88290..c9ec176c 100644 --- a/src/folding/hypernova/lcccs.rs +++ b/src/folding/hypernova/lcccs.rs @@ -17,9 +17,6 @@ use crate::utils::virtual_polynomial::VirtualPolynomial; /// Linearized Committed CCS instance #[derive(Debug, Clone, Eq, PartialEq)] pub struct LCCCS { - // Underlying CCS structure - pub ccs: CCS, // TODO maybe move CCS structure outside of LCCCS - // Commitment to witness pub C: C, // Relaxation factor of z for folded LCCCS @@ -54,7 +51,6 @@ impl CCS { ( LCCCS:: { - ccs: self.clone(), C, u: C::ScalarField::one(), x: z[1..(1 + self.l)].to_vec(), @@ -68,15 +64,19 @@ impl CCS { impl LCCCS { /// Compute all L_j(x) polynomials - pub fn compute_Ls(&self, z: &Vec) -> Vec> { - let z_mle = vec_to_mle(self.ccs.s_prime, z); + pub fn compute_Ls( + &self, + ccs: &CCS, + z: &Vec, + ) -> Vec> { + let z_mle = vec_to_mle(ccs.s_prime, z); // Convert all matrices to MLE let M_x_y_mle: Vec> = - self.ccs.M.clone().into_iter().map(matrix_to_mle).collect(); + ccs.M.clone().into_iter().map(matrix_to_mle).collect(); - let mut vec_L_j_x = Vec::with_capacity(self.ccs.t); + let mut vec_L_j_x = Vec::with_capacity(ccs.t); for M_j in M_x_y_mle { - let sum_Mz = compute_sum_Mz(M_j, &z_mle, self.ccs.s_prime); + let sum_Mz = compute_sum_Mz(M_j, &z_mle, ccs.s_prime); let sum_Mz_virtual = VirtualPolynomial::new_from_mle(&Arc::new(sum_Mz.clone()), C::ScalarField::one()); let L_j_x = sum_Mz_virtual.build_f_hat(&self.r_x).unwrap(); @@ -90,6 +90,7 @@ impl LCCCS { pub fn check_relation( &self, pedersen_params: &PedersenParams, + ccs: &CCS, w: &Witness, ) -> Result<(), Error> { // check that C is the commitment of w. Notice that this is not verifying a Pedersen @@ -98,7 +99,7 @@ impl LCCCS { // check CCS relation let z: Vec = [vec![self.u], self.x.clone(), w.w.to_vec()].concat(); - let computed_v = compute_all_sum_Mz_evals(&self.ccs.M, &z, &self.r_x, self.ccs.s_prime); + let computed_v = compute_all_sum_Mz_evals(&ccs.M, &z, &self.r_x, ccs.s_prime); assert_eq!(computed_v, self.v); Ok(()) } @@ -129,7 +130,7 @@ pub mod test { // with our test vector comming from R1CS, v should have length 3 assert_eq!(lcccs.v.len(), 3); - let vec_L_j_x = lcccs.compute_Ls(&z); + let vec_L_j_x = lcccs.compute_Ls(&ccs, &z); assert_eq!(vec_L_j_x.len(), lcccs.v.len()); for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) { @@ -161,7 +162,7 @@ pub mod test { assert_eq!(lcccs.v.len(), 3); // Bad compute L_j(x) with the bad z - let vec_L_j_x = lcccs.compute_Ls(&bad_z); + let vec_L_j_x = lcccs.compute_Ls(&ccs, &bad_z); assert_eq!(vec_L_j_x.len(), lcccs.v.len()); // Make sure that the LCCCS is not satisfied given these L_j(x) diff --git a/src/folding/hypernova/nimfs.rs b/src/folding/hypernova/nimfs.rs index 1754e2a3..58d53ead 100644 --- a/src/folding/hypernova/nimfs.rs +++ b/src/folding/hypernova/nimfs.rs @@ -8,6 +8,7 @@ use espresso_transcript::IOPTranscript; use super::cccs::{Witness, CCCS}; use super::lcccs::LCCCS; use super::utils::{compute_c_from_sigmas_and_thetas, compute_g, compute_sigmas_and_thetas}; +use crate::ccs::CCS; use crate::utils::hypercube::BooleanHypercube; use crate::utils::sum_check::structs::IOPProof as SumCheckProof; use crate::utils::sum_check::{verifier::interpolate_uni_poly, SumCheck}; @@ -90,7 +91,6 @@ impl NIMFS { LCCCS:: { C: C_folded, - ccs: lcccs[0].ccs.clone(), u: u_folded, x: x_folded, r_x: r_x_prime, @@ -143,6 +143,7 @@ impl NIMFS { /// sumcheck claim sigmas and thetas. pub fn prove( transcript: &mut IOPTranscript, + ccs: &CCS, running_instances: &[LCCCS], new_instances: &[CCCS], w_lcccs: &[Witness], @@ -180,18 +181,11 @@ impl NIMFS { // Step 1: Get some challenges let gamma: C::ScalarField = transcript.get_and_append_challenge(b"gamma").unwrap(); let beta: Vec = transcript - .get_and_append_challenge_vectors(b"beta", running_instances[0].ccs.s) + .get_and_append_challenge_vectors(b"beta", ccs.s) .unwrap(); // Compute g(x) - let g = compute_g( - running_instances, - new_instances, - &z_lcccs, - &z_cccs, - gamma, - &beta, - ); + let g = compute_g(ccs, running_instances, &z_lcccs, &z_cccs, gamma, &beta); // Step 3: Run the sumcheck prover let sumcheck_proof = @@ -204,7 +198,7 @@ impl NIMFS { // its sum is equal to the extracted_sum from the SumCheck. ////////////////////////////////////////////////////////////////////// let mut g_over_bhc = C::ScalarField::zero(); - for x in BooleanHypercube::new(running_instances[0].ccs.s) { + for x in BooleanHypercube::new(ccs.s) { g_over_bhc += g.evaluate(&x).unwrap(); } @@ -217,7 +211,7 @@ impl NIMFS { let mut sum_v_j_gamma = C::ScalarField::zero(); for (i, running_instance) in running_instances.iter().enumerate() { for j in 0..running_instance.v.len() { - let gamma_j = gamma.pow([(i * running_instances[0].ccs.t + j) as u64]); + let gamma_j = gamma.pow([(i * ccs.t + j) as u64]); sum_v_j_gamma += running_instance.v[j] * gamma_j; } } @@ -229,8 +223,7 @@ impl NIMFS { let r_x_prime = sumcheck_proof.point.clone(); // Step 4: compute sigmas and thetas - let sigmas_thetas = - compute_sigmas_and_thetas(&running_instances[0].ccs, &z_lcccs, &z_cccs, &r_x_prime); + let sigmas_thetas = compute_sigmas_and_thetas(ccs, &z_lcccs, &z_cccs, &r_x_prime); // Step 6: Get the folding challenge let rho: C::ScalarField = transcript.get_and_append_challenge(b"rho").unwrap(); @@ -262,6 +255,7 @@ impl NIMFS { /// Returns the folded LCCCS instance. pub fn verify( transcript: &mut IOPTranscript, + ccs: &CCS, running_instances: &[LCCCS], new_instances: &[CCCS], proof: Proof, @@ -274,12 +268,12 @@ impl NIMFS { // Step 1: Get some challenges let gamma: C::ScalarField = transcript.get_and_append_challenge(b"gamma").unwrap(); let beta: Vec = transcript - .get_and_append_challenge_vectors(b"beta", running_instances[0].ccs.s) + .get_and_append_challenge_vectors(b"beta", ccs.s) .unwrap(); let vp_aux_info = VPAuxInfo:: { - max_degree: running_instances[0].ccs.d + 1, - num_variables: running_instances[0].ccs.s, + max_degree: ccs.d + 1, + num_variables: ccs.s, phantom: PhantomData::, }; @@ -288,7 +282,7 @@ impl NIMFS { let mut sum_v_j_gamma = C::ScalarField::zero(); for (i, running_instance) in running_instances.iter().enumerate() { for j in 0..running_instance.v.len() { - let gamma_j = gamma.pow([(i * running_instances[0].ccs.t + j) as u64]); + let gamma_j = gamma.pow([(i * ccs.t + j) as u64]); sum_v_j_gamma += running_instance.v[j] * gamma_j; } } @@ -307,7 +301,7 @@ impl NIMFS { // Step 5: Finish verifying sumcheck (verify the claim c) let c = compute_c_from_sigmas_and_thetas( - &new_instances[0].ccs, + ccs, &proof.sigmas_thetas, gamma, &beta, @@ -368,35 +362,28 @@ pub mod tests { let mut rng = test_rng(); let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - // Initialize a multifolding object - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); - let (running_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1); - - let sigmas_thetas = compute_sigmas_and_thetas( - &running_instance.ccs, - &[z1.clone()], - &[z2.clone()], - &r_x_prime, - ); + let sigmas_thetas = + compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); let (lcccs, w1) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1); let (cccs, w2) = ccs.to_cccs(&mut rng, &pedersen_params, &z2); - lcccs.check_relation(&pedersen_params, &w1).unwrap(); - cccs.check_relation(&pedersen_params, &w2).unwrap(); + lcccs.check_relation(&pedersen_params, &ccs, &w1).unwrap(); + cccs.check_relation(&pedersen_params, &ccs, &w2).unwrap(); let mut rng = test_rng(); let rho = Fr::rand(&mut rng); - let folded = - NIMFS::::fold(&vec![lcccs], &vec![cccs], &sigmas_thetas, r_x_prime, rho); + let folded = NIMFS::::fold(&[lcccs], &[cccs], &sigmas_thetas, r_x_prime, rho); let w_folded = NIMFS::::fold_witness(&[w1], &[w2], rho); // check lcccs relation - folded.check_relation(&pedersen_params, &w_folded).unwrap(); + folded + .check_relation(&pedersen_params, &ccs, &w_folded) + .unwrap(); } /// Perform multifolding of an LCCCS instance with a CCCS instance (as described in the paper) @@ -425,8 +412,9 @@ pub mod tests { // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness) = NIMFS::::prove( &mut transcript_p, - &vec![running_instance.clone()], - &vec![new_instance.clone()], + &ccs, + &[running_instance.clone()], + &[new_instance.clone()], &[w1], &[w2], ); @@ -438,15 +426,16 @@ pub mod tests { // Run the verifier side of the multifolding let folded_lcccs_v = NIMFS::::verify( &mut transcript_v, - &vec![running_instance.clone()], - &vec![new_instance.clone()], + &ccs, + &[running_instance.clone()], + &[new_instance.clone()], proof, ); assert_eq!(folded_lcccs, folded_lcccs_v); // Check that the folded LCCCS instance is a valid instance with respect to the folded witness folded_lcccs - .check_relation(&pedersen_params, &folded_witness) + .check_relation(&pedersen_params, &ccs, &folded_witness) .unwrap(); } @@ -481,8 +470,9 @@ pub mod tests { // run the prover side of the multifolding let (proof, folded_lcccs, folded_witness) = NIMFS::::prove( &mut transcript_p, - &vec![running_instance.clone()], - &vec![new_instance.clone()], + &ccs, + &[running_instance.clone()], + &[new_instance.clone()], &[w1], &[w2], ); @@ -490,8 +480,9 @@ pub mod tests { // run the verifier side of the multifolding let folded_lcccs_v = NIMFS::::verify( &mut transcript_v, - &vec![running_instance.clone()], - &vec![new_instance.clone()], + &ccs, + &[running_instance.clone()], + &[new_instance.clone()], proof, ); @@ -500,7 +491,7 @@ pub mod tests { // check that the folded instance with the folded witness holds the LCCCS relation println!("check_relation {}", i); folded_lcccs - .check_relation(&pedersen_params, &folded_witness) + .check_relation(&pedersen_params, &ccs, &folded_witness) .unwrap(); running_instance = folded_lcccs; @@ -556,6 +547,7 @@ pub mod tests { // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness) = NIMFS::::prove( &mut transcript_p, + &ccs, &lcccs_instances, &cccs_instances, &w_lcccs, @@ -569,6 +561,7 @@ pub mod tests { // Run the verifier side of the multifolding let folded_lcccs_v = NIMFS::::verify( &mut transcript_v, + &ccs, &lcccs_instances, &cccs_instances, proof, @@ -577,7 +570,7 @@ pub mod tests { // Check that the folded LCCCS instance is a valid instance with respect to the folded witness folded_lcccs - .check_relation(&pedersen_params, &folded_witness) + .check_relation(&pedersen_params, &ccs, &folded_witness) .unwrap(); } @@ -638,6 +631,7 @@ pub mod tests { // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness) = NIMFS::::prove( &mut transcript_p, + &ccs, &lcccs_instances, &cccs_instances, &w_lcccs, @@ -647,6 +641,7 @@ pub mod tests { // Run the verifier side of the multifolding let folded_lcccs_v = NIMFS::::verify( &mut transcript_v, + &ccs, &lcccs_instances, &cccs_instances, proof, @@ -655,7 +650,7 @@ pub mod tests { // Check that the folded LCCCS instance is a valid instance with respect to the folded witness folded_lcccs - .check_relation(&pedersen_params, &folded_witness) + .check_relation(&pedersen_params, &ccs, &folded_witness) .unwrap(); } } diff --git a/src/folding/hypernova/utils.rs b/src/folding/hypernova/utils.rs index d77110f4..b8f65737 100644 --- a/src/folding/hypernova/utils.rs +++ b/src/folding/hypernova/utils.rs @@ -8,7 +8,6 @@ use std::ops::Add; use crate::utils::multilinear_polynomial::fix_variables; use crate::utils::multilinear_polynomial::scalar_mul; -use super::cccs::CCCS; use super::lcccs::LCCCS; use super::nimfs::SigmasThetas; use crate::ccs::CCS; @@ -131,8 +130,8 @@ pub fn compute_c_from_sigmas_and_thetas( /// Compute g(x) polynomial for the given inputs. pub fn compute_g( + ccs: &CCS, running_instances: &[LCCCS], - cccs_instances: &[CCCS], z_lcccs: &[Vec], z_cccs: &[Vec], gamma: C::ScalarField, @@ -141,12 +140,13 @@ pub fn compute_g( let mu = running_instances.len(); let mut vec_Ls: Vec> = Vec::new(); for (i, running_instance) in running_instances.iter().enumerate() { - let mut Ls = running_instance.compute_Ls(&z_lcccs[i]); + let mut Ls = running_instance.compute_Ls(ccs, &z_lcccs[i]); vec_Ls.append(&mut Ls); } let mut vec_Q: Vec> = Vec::new(); - for (i, cccs_instance) in cccs_instances.iter().enumerate() { - let Q = cccs_instance.compute_Q(&z_cccs[i], beta); + // for (i, _) in cccs_instances.iter().enumerate() { + for z_cccs_i in z_cccs.iter() { + let Q = ccs.compute_Q(z_cccs_i, beta); vec_Q.push(Q); } let mut g = vec_Ls[0].clone(); @@ -159,7 +159,7 @@ pub fn compute_g( g = g.add(L_j); } for (i, Q_i) in vec_Q.iter_mut().enumerate() { - let gamma_mut_i = gamma.pow([(mu * cccs_instances[0].ccs.t + i) as u64]); + let gamma_mut_i = gamma.pow([(mu * ccs.t + i) as u64]); Q_i.scalar_mul(&gamma_mut_i); g = g.add(Q_i); } @@ -270,18 +270,13 @@ pub mod tests { // Initialize a multifolding object let pedersen_params = Pedersen::new_params(&mut rng, ccs.n - ccs.l - 1); let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1); - let (cccs_instance, _) = ccs.to_cccs(&mut rng, &pedersen_params, &z2); - let sigmas_thetas = compute_sigmas_and_thetas( - &lcccs_instance.ccs, - &[z1.clone()], - &[z2.clone()], - &r_x_prime, - ); + let sigmas_thetas = + compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); let g = compute_g( + &ccs, &vec![lcccs_instance.clone()], - &vec![cccs_instance.clone()], &[z1.clone()], &[z2.clone()], gamma, @@ -318,7 +313,6 @@ pub mod tests { // Initialize a multifolding object let pedersen_params = Pedersen::new_params(&mut rng, ccs.n - ccs.l - 1); let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1); - let (cccs_instance, _) = ccs.to_cccs(&mut rng, &pedersen_params, &z2); let mut sum_v_j_gamma = Fr::zero(); for j in 0..lcccs_instance.v.len() { @@ -328,8 +322,8 @@ pub mod tests { // Compute g(x) with that r_x let g = compute_g::( + &ccs, &vec![lcccs_instance.clone()], - &vec![cccs_instance.clone()], &[z1.clone()], &[z2.clone()], gamma, @@ -344,7 +338,7 @@ pub mod tests { // evaluate sum_{j \in [t]} (gamma^j * Lj(x)) over x \in {0,1}^s let mut sum_Lj_on_bhc = Fr::zero(); - let vec_L = lcccs_instance.compute_Ls(&z1); + let vec_L = lcccs_instance.compute_Ls(&ccs, &z1); for x in BooleanHypercube::new(ccs.s) { for (j, Lj) in vec_L.iter().enumerate() { let gamma_j = gamma.pow([j as u64]);