From c9348ca51600ba6671e7e194eb4ad41ab1452727 Mon Sep 17 00:00:00 2001 From: Gus Gutoski Date: Wed, 11 Oct 2023 11:52:04 -0400 Subject: [PATCH 1/8] tidy PhantomData --- primitives/src/vid/advz.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/primitives/src/vid/advz.rs b/primitives/src/vid/advz.rs index 9000dbc33..2131b7098 100644 --- a/primitives/src/vid/advz.rs +++ b/primitives/src/vid/advz.rs @@ -70,9 +70,7 @@ where num_storage_nodes: usize, ck: ::ProverParam, vk: ::VerifierParam, - _phantom_t: PhantomData, // needed for trait bounds - _phantom_h: PhantomData, // needed for trait bounds - _phantom_v: PhantomData, // needed for trait bounds + _pd: (PhantomData, PhantomData, PhantomData), } impl GenericAdvz @@ -102,9 +100,7 @@ where num_storage_nodes, ck, vk, - _phantom_t: PhantomData, - _phantom_h: PhantomData, - _phantom_v: PhantomData, + _pd: Default::default(), }) } } From f91b47d13ffaa2eb47a4debcdf8aafb221d5ca04 Mon Sep 17 00:00:00 2001 From: Gus Gutoski Date: Wed, 11 Oct 2023 12:00:40 -0400 Subject: [PATCH 2/8] eval domain too large by 1: multi_open_rou_eval_domain takes degree, but we give it num_coeffs, which is degree+1 --- primitives/src/vid/advz.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/primitives/src/vid/advz.rs b/primitives/src/vid/advz.rs index 2131b7098..3c3b29418 100644 --- a/primitives/src/vid/advz.rs +++ b/primitives/src/vid/advz.rs @@ -240,8 +240,9 @@ where // prepare eval point for aggregate proof // TODO(Gus) perf: don't re-compute domain elements: https://github.com/EspressoSystems/jellyfish/issues/313 - let domain = P::multi_open_rou_eval_domain(self.payload_chunk_size, self.num_storage_nodes) - .map_err(vid)?; + let domain = + P::multi_open_rou_eval_domain(self.payload_chunk_size - 1, self.num_storage_nodes) + .map_err(vid)?; let point = domain.element(share.index); // verify aggregate proof @@ -282,8 +283,9 @@ where I: IntoIterator, I::Item: Borrow, { - let domain = P::multi_open_rou_eval_domain(self.payload_chunk_size, self.num_storage_nodes) - .map_err(vid)?; + let domain = + P::multi_open_rou_eval_domain(self.payload_chunk_size - 1, self.num_storage_nodes) + .map_err(vid)?; // partition payload into polynomial coefficients // and count `elems_len` for later @@ -434,8 +436,9 @@ where let result_len = num_polys * self.payload_chunk_size; let mut result = Vec::with_capacity(result_len); - let domain = P::multi_open_rou_eval_domain(self.payload_chunk_size, self.num_storage_nodes) - .map_err(vid)?; + let domain = + P::multi_open_rou_eval_domain(self.payload_chunk_size - 1, self.num_storage_nodes) + .map_err(vid)?; for i in 0..num_polys { let mut coeffs = reed_solomon_erasure_decode_rou( shares.iter().map(|s| (s.index, s.evals[i])), @@ -752,7 +755,7 @@ mod tests { { let mut shares_bad_indices = shares; let domain = UnivariateKzgPCS::::multi_open_rou_eval_domain( - advz.payload_chunk_size, + advz.payload_chunk_size - 1, advz.num_storage_nodes, ) .unwrap(); From 4cbea7ac8d030afbe799292cb6b5970ca31f4433 Mon Sep 17 00:00:00 2001 From: Gus Gutoski Date: Wed, 11 Oct 2023 12:25:52 -0400 Subject: [PATCH 3/8] fix https://github.com/EspressoSystems/jellyfish/issues/313 --- primitives/src/vid/advz.rs | 46 ++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/primitives/src/vid/advz.rs b/primitives/src/vid/advz.rs index 3c3b29418..522d0f5de 100644 --- a/primitives/src/vid/advz.rs +++ b/primitives/src/vid/advz.rs @@ -23,7 +23,7 @@ use ark_ff::{ fields::field_hashers::{DefaultFieldHasher, HashToField}, FftField, Field, PrimeField, }; -use ark_poly::{DenseUVPolynomial, EvaluationDomain}; +use ark_poly::{DenseUVPolynomial, EvaluationDomain, Radix2EvaluationDomain}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Write}; use ark_std::{ borrow::Borrow, @@ -65,11 +65,13 @@ pub type Advz = GenericAdvz< pub struct GenericAdvz where P: PolynomialCommitmentScheme, + P::Evaluation: FftField, { payload_chunk_size: usize, num_storage_nodes: usize, ck: ::ProverParam, vk: ::VerifierParam, + multi_open_domain: Radix2EvaluationDomain, _pd: (PhantomData, PhantomData, PhantomData), } @@ -95,11 +97,15 @@ where ))); } let (ck, vk) = P::trim_fft_size(srs, payload_chunk_size).map_err(vid)?; + let multi_open_domain = + P::multi_open_rou_eval_domain(payload_chunk_size - 1, num_storage_nodes) + .map_err(vid)?; Ok(Self { payload_chunk_size, num_storage_nodes, ck, vk, + multi_open_domain, _pd: Default::default(), }) } @@ -238,18 +244,11 @@ where let aggregate_eval = polynomial_eval(share.evals.iter().map(FieldMultiplier), pseudorandom_scalar); - // prepare eval point for aggregate proof - // TODO(Gus) perf: don't re-compute domain elements: https://github.com/EspressoSystems/jellyfish/issues/313 - let domain = - P::multi_open_rou_eval_domain(self.payload_chunk_size - 1, self.num_storage_nodes) - .map_err(vid)?; - let point = domain.element(share.index); - // verify aggregate proof Ok(P::verify( &self.vk, &aggregate_poly_commit, - &point, + &self.multi_open_domain.element(share.index), &aggregate_eval, &share.aggregate_proof, ) @@ -283,10 +282,6 @@ where I: IntoIterator, I::Item: Borrow, { - let domain = - P::multi_open_rou_eval_domain(self.payload_chunk_size - 1, self.num_storage_nodes) - .map_err(vid)?; - // partition payload into polynomial coefficients // and count `elems_len` for later let elems_iter = payload.into_iter().map(|elem| *elem.borrow()); @@ -305,7 +300,8 @@ where for poly in polys.iter() { let poly_evals = - P::multi_open_rou_evals(poly, self.num_storage_nodes, &domain).map_err(vid)?; + P::multi_open_rou_evals(poly, self.num_storage_nodes, &self.multi_open_domain) + .map_err(vid)?; for (storage_node_evals, poly_eval) in all_storage_node_evals.iter_mut().zip(poly_evals) @@ -370,9 +366,13 @@ where let aggregate_poly = polynomial_eval(polys.iter().map(PolynomialMultiplier), pseudorandom_scalar); - let aggregate_proofs = - P::multi_open_rou_proofs(&self.ck, &aggregate_poly, self.num_storage_nodes, &domain) - .map_err(vid)?; + let aggregate_proofs = P::multi_open_rou_proofs( + &self.ck, + &aggregate_poly, + self.num_storage_nodes, + &self.multi_open_domain, + ) + .map_err(vid)?; let shares = all_storage_node_evals .into_iter() @@ -436,14 +436,11 @@ where let result_len = num_polys * self.payload_chunk_size; let mut result = Vec::with_capacity(result_len); - let domain = - P::multi_open_rou_eval_domain(self.payload_chunk_size - 1, self.num_storage_nodes) - .map_err(vid)?; for i in 0..num_polys { let mut coeffs = reed_solomon_erasure_decode_rou( shares.iter().map(|s| (s.index, s.evals[i])), self.payload_chunk_size, - &domain, + &self.multi_open_domain, ) .map_err(vid)?; result.append(&mut coeffs); @@ -754,13 +751,8 @@ mod tests { // corrupted index, out of bounds { let mut shares_bad_indices = shares; - let domain = UnivariateKzgPCS::::multi_open_rou_eval_domain( - advz.payload_chunk_size - 1, - advz.num_storage_nodes, - ) - .unwrap(); for i in 0..shares_bad_indices.len() { - shares_bad_indices[i].index += domain.size(); + shares_bad_indices[i].index += advz.multi_open_domain.size(); advz.recover_payload(&shares_bad_indices, &common) .expect_err("recover_payload should fail when indices are out of bounds"); } From a2b85dfb3d6eef4c5c3160713cede8184ea81478 Mon Sep 17 00:00:00 2001 From: Gus Gutoski Date: Thu, 12 Oct 2023 09:57:48 -0400 Subject: [PATCH 4/8] use fft to encode polynomials in eval form --- primitives/src/vid/advz.rs | 58 ++++++++++++++++++++++++++++++++++---- primitives/tests/advz.rs | 2 +- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/primitives/src/vid/advz.rs b/primitives/src/vid/advz.rs index 522d0f5de..10b61016a 100644 --- a/primitives/src/vid/advz.rs +++ b/primitives/src/vid/advz.rs @@ -72,6 +72,12 @@ where ck: ::ProverParam, vk: ::VerifierParam, multi_open_domain: Radix2EvaluationDomain, + + // TODO might be able to eliminate this field and instead use + // `EvaluationDomain::reindex_by_subdomain()` on `multi_open_domain` + // but that method consumes `other` and its doc is unclear. + eval_domain: Radix2EvaluationDomain, + _pd: (PhantomData, PhantomData, PhantomData), } @@ -100,12 +106,31 @@ where let multi_open_domain = P::multi_open_rou_eval_domain(payload_chunk_size - 1, num_storage_nodes) .map_err(vid)?; + let eval_domain = Radix2EvaluationDomain::new(payload_chunk_size).ok_or_else(|| { + VidError::Internal(anyhow::anyhow!( + "fail to construct doman of size {}", + payload_chunk_size + )) + })?; + + // TODO TEMPORARY: enforce power-of-2 chunk size + // Remove this restriction after we get KZG in eval form + // https://github.com/EspressoSystems/jellyfish/issues/339 + if payload_chunk_size != eval_domain.size() { + return Err(VidError::Argument(format!( + "payload_chunk_size {} currently unsupported, round to {} instead", + payload_chunk_size, + eval_domain.size() + ))); + } + Ok(Self { payload_chunk_size, num_storage_nodes, ck, vk, multi_open_domain, + eval_domain, _pd: Default::default(), }) } @@ -177,8 +202,14 @@ where { let mut hasher = H::new(); let elems_iter = bytes_to_field::<_, P::Evaluation>(payload); - for coeffs in elems_iter.chunks(self.payload_chunk_size).into_iter() { - let poly = DenseUVPolynomial::from_coefficients_vec(coeffs.collect()); + for coeffs_iter in elems_iter.chunks(self.payload_chunk_size).into_iter() { + // TODO TEMPORARY: use FFT to encode polynomials in eval form + // Remove these FFTs after we get KZG in eval form + // https://github.com/EspressoSystems/jellyfish/issues/339 + let mut coeffs: Vec<_> = coeffs_iter.collect(); + self.eval_domain.fft_in_place(&mut coeffs); + + let poly = DenseUVPolynomial::from_coefficients_vec(coeffs); let commitment = P::commit(&self.ck, &poly).map_err(vid)?; commitment .serialize_uncompressed(&mut hasher) @@ -275,7 +306,7 @@ where V::MembershipProof: Sync + Debug, /* TODO https://github.com/EspressoSystems/jellyfish/issues/253 */ V::Index: From, { - /// Same as [`VidScheme::disperse`] except `payload` is a slice of + /// Same as [`VidScheme::disperse`] except `payload` iterates over /// field elements. pub fn disperse_from_elems(&self, payload: I) -> VidResult> where @@ -288,8 +319,17 @@ where let mut elems_len = 0; let mut polys = Vec::new(); for coeffs_iter in elems_iter.chunks(self.payload_chunk_size).into_iter() { - let coeffs: Vec<_> = coeffs_iter.collect(); - elems_len += coeffs.len(); + // TODO TEMPORARY: use FFT to encode polynomials in eval form + // Remove these FFTs after we get KZG in eval form + // https://github.com/EspressoSystems/jellyfish/issues/339 + let mut coeffs: Vec<_> = coeffs_iter.collect(); + let pre_fft_len = coeffs.len(); + self.eval_domain.fft_in_place(&mut coeffs); + if pre_fft_len == self.payload_chunk_size { + assert_eq!(coeffs.len(), pre_fft_len); // sanity + } + + elems_len += pre_fft_len; polys.push(DenseUVPolynomial::from_coefficients_vec(coeffs)); } @@ -443,6 +483,12 @@ where &self.multi_open_domain, ) .map_err(vid)?; + + // TODO TEMPORARY: use FFT to encode polynomials in eval form + // Remove these FFTs after we get KZG in eval form + // https://github.com/EspressoSystems/jellyfish/issues/339 + self.eval_domain.ifft_in_place(&mut coeffs); + result.append(&mut coeffs); } assert_eq!(result.len(), result_len); @@ -765,7 +811,7 @@ mod tests { /// 1. An initialized [`Advz`] instance. /// 2. A `Vec` filled with random bytes. fn avdz_init() -> (Advz, Vec) { - let (payload_chunk_size, num_storage_nodes) = (3, 5); + let (payload_chunk_size, num_storage_nodes) = (4, 6); let mut rng = jf_utils::test_rng(); let srs = UnivariateKzgPCS::::gen_srs_for_testing( &mut rng, diff --git a/primitives/tests/advz.rs b/primitives/tests/advz.rs index 8c2d4b1ab..95ca67900 100644 --- a/primitives/tests/advz.rs +++ b/primitives/tests/advz.rs @@ -12,7 +12,7 @@ mod vid; #[test] fn round_trip() { // play with these items - let vid_sizes = [(2, 3), (5, 9)]; + let vid_sizes = [(2, 3), (8, 11)]; let byte_lens = [0, 1, 2, 16, 32, 47, 48, 49, 64, 100, 400]; // more items as a function of the above From 77304038b72608919cc8b2c83c4d06671d1a00a3 Mon Sep 17 00:00:00 2001 From: Gus Gutoski Date: Thu, 12 Oct 2023 17:06:22 -0400 Subject: [PATCH 5/8] add test to verify a 'namespace' aka polynomial --- primitives/src/vid/advz.rs | 95 ++++++++++++++++++++++++++++++++++++-- primitives/tests/advz.rs | 2 +- 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/primitives/src/vid/advz.rs b/primitives/src/vid/advz.rs index 10b61016a..c298d6de4 100644 --- a/primitives/src/vid/advz.rs +++ b/primitives/src/vid/advz.rs @@ -102,7 +102,7 @@ where payload_chunk_size, num_storage_nodes ))); } - let (ck, vk) = P::trim_fft_size(srs, payload_chunk_size).map_err(vid)?; + let (ck, vk) = P::trim_fft_size(srs, payload_chunk_size - 1).map_err(vid)?; let multi_open_domain = P::multi_open_rou_eval_domain(payload_chunk_size - 1, num_storage_nodes) .map_err(vid)?; @@ -619,9 +619,16 @@ where mod tests { use super::{VidError::Argument, *}; - use crate::{merkle_tree::hasher::HasherNode, pcs::checked_fft_size}; + use crate::{ + merkle_tree::hasher::HasherNode, + pcs::{checked_fft_size, prelude::UnivariateUniversalParams}, + }; use ark_bls12_381::Bls12_381; - use ark_std::{rand::RngCore, vec}; + use ark_std::{ + rand::{CryptoRng, RngCore}, + vec, + }; + use digest::{generic_array::ArrayLength, OutputSizeUser}; use sha2::Sha256; #[test] @@ -805,6 +812,62 @@ mod tests { } } + fn prove_polys_and_elements_generic() + where + E: Pairing, + H: Digest + DynDigest + Default + Clone + Write, + <::OutputSize as ArrayLength>::ArrayType: Copy, + { + // play with these items + let (payload_chunk_size, num_storage_nodes) = (4, 6); + let num_polys = 4; + + // more items as a function of the above + let payload_elems_len = num_polys * payload_chunk_size; + let payload_bytes_len = payload_elems_len * modulus_byte_len::(); + let mut rng = jf_utils::test_rng(); + let payload_bytes = init_random_bytes(payload_bytes_len, &mut rng); + let srs = init_srs(payload_elems_len, &mut rng); + + let advz = Advz::::new(payload_chunk_size, num_storage_nodes, srs).unwrap(); + let d = advz.disperse(&payload_bytes).unwrap(); + + // BEGIN TEST 1: verify "namespaces" (each namespace is a polynomial) + // encode payload as field elements, partition into polynomials, compute + // commitments, compare against VID common data + let elems_iter = bytes_to_field::<_, E::ScalarField>(payload_bytes); + for (coeffs_iter, poly_commit) in elems_iter + .chunks(payload_chunk_size) + .into_iter() + .zip(d.common.poly_commits.iter()) + { + let mut coeffs: Vec<_> = coeffs_iter.collect(); + advz.eval_domain.fft_in_place(&mut coeffs); + + let poly = as PolynomialCommitmentScheme>::Polynomial::from_coefficients_vec(coeffs); + let my_poly_commit = UnivariateKzgPCS::::commit(&advz.ck, &poly).unwrap(); + assert_eq!(my_poly_commit, *poly_commit); + } + + // compute payload commitment + let commit = { + let mut hasher = H::new(); + for poly_commit in d.common.poly_commits.iter() { + // TODO compiler bug? `as` should not be needed here! + (poly_commit as & as PolynomialCommitmentScheme>::Commitment) + .serialize_uncompressed(&mut hasher) + .unwrap(); + } + hasher.finalize() + }; + assert_eq!(commit, d.commit); + } + + #[test] + fn prove_polys_and_elements() { + prove_polys_and_elements_generic::(); + } + /// Routine initialization tasks. /// /// Returns the following tuple: @@ -830,4 +893,30 @@ mod tests { fn assert_arg_err(res: VidResult, msg: &str) { assert!(matches!(res, Err(Argument(_))), "{}", msg); } + + fn init_random_bytes(len: usize, rng: &mut R) -> Vec + where + R: RngCore + CryptoRng, + { + let mut bytes_random = vec![0u8; len]; + rng.fill_bytes(&mut bytes_random); + bytes_random + } + + fn init_srs(num_coeffs: usize, rng: &mut R) -> UnivariateUniversalParams + where + E: Pairing, + R: RngCore + CryptoRng, + { + UnivariateKzgPCS::gen_srs_for_testing(rng, checked_fft_size(num_coeffs - 1).unwrap()) + .unwrap() + } + + fn modulus_byte_len() -> usize + where + E: Pairing, + { + usize::try_from((< as PolynomialCommitmentScheme>::Evaluation as Field>::BasePrimeField + ::MODULUS_BIT_SIZE - 7)/8 + 1).unwrap() + } } diff --git a/primitives/tests/advz.rs b/primitives/tests/advz.rs index 95ca67900..88db3a096 100644 --- a/primitives/tests/advz.rs +++ b/primitives/tests/advz.rs @@ -16,7 +16,7 @@ fn round_trip() { let byte_lens = [0, 1, 2, 16, 32, 47, 48, 49, 64, 100, 400]; // more items as a function of the above - let supported_degree = vid_sizes.iter().max_by_key(|v| v.0).unwrap().0; + let supported_degree = vid_sizes.iter().max_by_key(|v| v.0).unwrap().0 - 1; let mut rng = jf_utils::test_rng(); let srs = UnivariateKzgPCS::::gen_srs_for_testing( &mut rng, From a9b119710498d2201e49998bb6e946e29cde836d Mon Sep 17 00:00:00 2001 From: Gus Gutoski Date: Fri, 13 Oct 2023 10:48:48 -0400 Subject: [PATCH 6/8] tidy tests --- primitives/src/vid/advz.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/primitives/src/vid/advz.rs b/primitives/src/vid/advz.rs index c298d6de4..d66d2b791 100644 --- a/primitives/src/vid/advz.rs +++ b/primitives/src/vid/advz.rs @@ -812,7 +812,7 @@ mod tests { } } - fn prove_polys_and_elements_generic() + fn prove_namespace_generic() where E: Pairing, H: Digest + DynDigest + Default + Clone + Write, @@ -832,7 +832,10 @@ mod tests { let advz = Advz::::new(payload_chunk_size, num_storage_nodes, srs).unwrap(); let d = advz.disperse(&payload_bytes).unwrap(); - // BEGIN TEST 1: verify "namespaces" (each namespace is a polynomial) + // TEST: verify "namespaces" (each namespace is a polynomial) + // This test is currently trivial: we simply repeat the commit computation. + // In the future there will be a proper API that can be tested meaningfully. + // encode payload as field elements, partition into polynomials, compute // commitments, compare against VID common data let elems_iter = bytes_to_field::<_, E::ScalarField>(payload_bytes); @@ -849,7 +852,7 @@ mod tests { assert_eq!(my_poly_commit, *poly_commit); } - // compute payload commitment + // compute payload commitment and verify let commit = { let mut hasher = H::new(); for poly_commit in d.common.poly_commits.iter() { @@ -864,8 +867,8 @@ mod tests { } #[test] - fn prove_polys_and_elements() { - prove_polys_and_elements_generic::(); + fn prove_namespace() { + prove_namespace_generic::(); } /// Routine initialization tasks. @@ -876,16 +879,9 @@ mod tests { fn avdz_init() -> (Advz, Vec) { let (payload_chunk_size, num_storage_nodes) = (4, 6); let mut rng = jf_utils::test_rng(); - let srs = UnivariateKzgPCS::::gen_srs_for_testing( - &mut rng, - checked_fft_size(payload_chunk_size).unwrap(), - ) - .unwrap(); + let srs = init_srs(payload_chunk_size, &mut rng); let advz = Advz::new(payload_chunk_size, num_storage_nodes, srs).unwrap(); - - let mut bytes_random = vec![0u8; 4000]; - rng.fill_bytes(&mut bytes_random); - + let bytes_random = init_random_bytes(4000, &mut rng); (advz, bytes_random) } From 6da991e6520426e772d7e0391d3dd800fdb76e5e Mon Sep 17 00:00:00 2001 From: Gus Gutoski Date: Fri, 13 Oct 2023 11:42:17 -0400 Subject: [PATCH 7/8] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5bad3316..1a3d40eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and follow [semantic versioning](https://semver.org/) for our releases. - [#302](https://github.com/EspressoSystems/jellyfish/pull/302) Followup APIs for non-native ECC circuit support. - [#323](https://github.com/EspressoSystems/jellyfish/pull/323) Improve performance of range gate in ultra plonk. - [#371](https://github.com/EspressoSystems/jellyfish/pull/371) VID disperse also return payload commitment +- [#385](https://github.com/EspressoSystems/jellyfish/pull/385) Use FFT to encode polynomials in eval form. ### Removed From 044e82cebdabe0653d38ea86ba6e041192cc8310 Mon Sep 17 00:00:00 2001 From: Gus Gutoski Date: Mon, 16 Oct 2023 11:05:48 -0400 Subject: [PATCH 8/8] [no ci] add explanatory comment to address https://github.com/EspressoSystems/jellyfish/pull/385#discussion_r1360751399 --- primitives/src/vid/advz.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/primitives/src/vid/advz.rs b/primitives/src/vid/advz.rs index d66d2b791..acc14b855 100644 --- a/primitives/src/vid/advz.rs +++ b/primitives/src/vid/advz.rs @@ -325,8 +325,13 @@ where let mut coeffs: Vec<_> = coeffs_iter.collect(); let pre_fft_len = coeffs.len(); self.eval_domain.fft_in_place(&mut coeffs); + + // sanity check: the fft did not resize coeffs. + // If pre_fft_len != self.payload_chunk_size then we must be in the final chunk. + // In that case coeffs.len() could be anything, so there's nothing to sanity + // check. if pre_fft_len == self.payload_chunk_size { - assert_eq!(coeffs.len(), pre_fft_len); // sanity + assert_eq!(coeffs.len(), pre_fft_len); } elems_len += pre_fft_len;