jf_vid/
advz.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the Jellyfish library.
3
4// You should have received a copy of the MIT License
5// along with the Jellyfish library. If not, see <https://mit-license.org/>.
6
7//! Implementation of Verifiable Information Dispersal (VID) from <https://eprint.iacr.org/2021/1500>.
8//!
9//! `advz` named for the authors Alhaddad-Duan-Varia-Zhang.
10
11use super::{vid, VidDisperse, VidError, VidResult, VidScheme};
12use ark_ec::{pairing::Pairing, AffineRepr};
13use ark_ff::{Field, PrimeField};
14use ark_poly::{
15    univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain, Radix2EvaluationDomain,
16};
17use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
18use ark_std::{
19    borrow::Borrow,
20    end_timer,
21    fmt::Debug,
22    format,
23    marker::PhantomData,
24    ops::{Add, Mul},
25    start_timer,
26    string::ToString,
27    vec,
28    vec::Vec,
29    Zero,
30};
31use bytes_to_field::{bytes_to_field, field_to_bytes};
32use core::mem;
33use derivative::Derivative;
34use digest::crypto_common::Output;
35use itertools::Itertools;
36use jf_merkle_tree::{
37    hasher::{HasherDigest, HasherMerkleTree, HasherNode},
38    prelude::{MerkleNode, MerkleProof},
39    MerkleCommitment, MerkleTreeScheme,
40};
41#[cfg(feature = "gpu-vid")]
42use jf_pcs::icicle_deps::*;
43use jf_pcs::{
44    prelude::{UnivariateKzgPCS, UnivariateKzgProof},
45    PolynomialCommitmentScheme, StructuredReferenceString, UnivariatePCS,
46};
47use jf_utils::{
48    canonical,
49    par_utils::{parallelizable_chunks, parallelizable_slice_iter},
50    reed_solomon_code::reed_solomon_erasure_decode_rou,
51};
52#[cfg(feature = "parallel")]
53use rayon::prelude::ParallelIterator;
54use serde::{Deserialize, Serialize};
55
56mod bytes_to_field;
57pub mod payload_prover;
58pub mod precomputable;
59
60/// Normal Advz VID that's only using CPU
61pub type Advz<E, H> = AdvzInternal<E, H, ()>;
62/// Advz with GPU support
63#[cfg(feature = "gpu-vid")]
64pub type AdvzGPU<'srs, E, H> = AdvzInternal<
65    E,
66    H,
67    (
68        HostOrDeviceSlice<'srs, IcicleAffine<<UnivariateKzgPCS<E> as GPUCommittable<E>>::IC>>,
69        CudaStream,
70    ),
71>;
72
73/// The [ADVZ VID scheme](https://eprint.iacr.org/2021/1500), a concrete impl for [`VidScheme`].
74/// Consider using either [`Advz`] or `AdvzGPU` (enabled via `gpu-vid` feature).
75///
76/// - `E` is any [`Pairing`]
77/// - `H` is a [`digest::Digest`]-compatible hash function.
78/// - `T` is a reference to GPU memory that's storing SRS
79#[derive(Clone, Debug, Eq, PartialEq)]
80pub struct AdvzInternal<E, H, T>
81where
82    E: Pairing,
83    T: Sync,
84{
85    recovery_threshold: u32,
86    num_storage_nodes: u32,
87    max_multiplicity: u32,
88    ck: KzgProverParam<E>,
89    vk: KzgVerifierParam<E>,
90
91    // tuple of
92    // - reference to the SRS/ProverParam loaded to GPU
93    // - cuda stream handle
94    srs_on_gpu_and_cuda_stream: Option<T>,
95    _pd: (PhantomData<H>, PhantomData<T>),
96}
97
98// [Nested associated type projection is overly conservative · Issue #38078 · rust-lang/rust](https://github.com/rust-lang/rust/issues/38078)
99// I want to do this but I can't:
100// type Kzg<E> = <UnivariateKzgPCS<E> as PolynomialCommitmentScheme>;
101// So instead I do this:
102type KzgPolynomial<E> = <UnivariateKzgPCS<E> as PolynomialCommitmentScheme>::Polynomial;
103type KzgCommit<E> = <UnivariateKzgPCS<E> as PolynomialCommitmentScheme>::Commitment;
104type KzgPoint<E> = <UnivariateKzgPCS<E> as PolynomialCommitmentScheme>::Point;
105type KzgEval<E> = <UnivariateKzgPCS<E> as PolynomialCommitmentScheme>::Evaluation;
106type KzgProof<E> = <UnivariateKzgPCS<E> as PolynomialCommitmentScheme>::Proof;
107type KzgSrs<E> = <UnivariateKzgPCS<E> as PolynomialCommitmentScheme>::SRS;
108type KzgProverParam<E> = <<UnivariateKzgPCS<E> as PolynomialCommitmentScheme>::SRS as StructuredReferenceString>::ProverParam;
109type KzgVerifierParam<E> = <<UnivariateKzgPCS<E> as PolynomialCommitmentScheme>::SRS as StructuredReferenceString>::VerifierParam;
110
111type KzgEvalsMerkleTree<E, H> = HasherMerkleTree<H, Vec<KzgEval<E>>>;
112type KzgEvalsMerkleTreeNode<E, H> = <KzgEvalsMerkleTree<E, H> as MerkleTreeScheme>::NodeValue;
113type KzgEvalsMerkleTreeIndex<E, H> = <KzgEvalsMerkleTree<E, H> as MerkleTreeScheme>::Index;
114type KzgEvalsMerkleTreeProof<E, H> =
115    <KzgEvalsMerkleTree<E, H> as MerkleTreeScheme>::MembershipProof;
116
117impl<E, H, T> AdvzInternal<E, H, T>
118where
119    E: Pairing,
120    T: Sync,
121{
122    pub(crate) fn new_internal(
123        num_storage_nodes: u32,  // n (code rate: r = k/n)
124        recovery_threshold: u32, // k
125        srs: impl Borrow<KzgSrs<E>>,
126    ) -> VidResult<Self> {
127        // TODO intelligent choice of multiplicity
128        // https://github.com/EspressoSystems/jellyfish/issues/534
129        let max_multiplicity = 1;
130
131        Self::with_multiplicity_internal(
132            num_storage_nodes,
133            recovery_threshold,
134            max_multiplicity,
135            srs,
136        )
137    }
138
139    pub(crate) fn with_multiplicity_internal(
140        num_storage_nodes: u32,  // n (code rate: r = k/n)
141        recovery_threshold: u32, // k
142        max_multiplicity: u32,   // batch m chunks, keep the rate r = (m*k)/(m*n)
143        srs: impl Borrow<KzgSrs<E>>,
144    ) -> VidResult<Self> {
145        if num_storage_nodes < recovery_threshold {
146            return Err(VidError::Argument(format!(
147                "recovery_threshold {} exceeds num_storage_nodes {}",
148                recovery_threshold, num_storage_nodes
149            )));
150        }
151
152        // TODO TEMPORARY: enforce power-of-2
153        // https://github.com/EspressoSystems/jellyfish/issues/668
154        if !recovery_threshold.is_power_of_two() {
155            return Err(VidError::Argument(format!(
156                "recovery_threshold {recovery_threshold} should be a power of two"
157            )));
158        }
159        if !max_multiplicity.is_power_of_two() {
160            return Err(VidError::Argument(format!(
161                "max_multiplicity {max_multiplicity} should be a power of two"
162            )));
163        }
164
165        let supported_degree =
166            usize::try_from(max_multiplicity * recovery_threshold - 1).map_err(vid)?;
167        let (ck, vk) = UnivariateKzgPCS::trim_fft_size(srs, supported_degree).map_err(vid)?;
168
169        Ok(Self {
170            recovery_threshold,
171            num_storage_nodes,
172            max_multiplicity,
173            ck,
174            vk,
175            srs_on_gpu_and_cuda_stream: None,
176            _pd: Default::default(),
177        })
178    }
179}
180
181impl<E, H> Advz<E, H>
182where
183    E: Pairing,
184{
185    /// Return a new instance of `Self` from (mostly)
186    /// implementation-independent arguments.
187    ///
188    /// # Implementation-independent arguments
189    /// - `num_storage_nodes`
190    /// - `recovery_threshold`
191    ///
192    /// # Implementation-specific arguments
193    /// - `srs`
194    ///
195    /// # Errors
196    /// Return [`VidError::Argument`] if
197    /// - `num_storage_nodes < recovery_threshold`
198    /// - TEMPORARY `recovery_threshold` is not a power of two [github issue](https://github.com/EspressoSystems/jellyfish/issues/668)
199    pub fn new(
200        num_storage_nodes: u32,
201        recovery_threshold: u32,
202        srs: impl Borrow<KzgSrs<E>>,
203    ) -> VidResult<Self> {
204        Self::new_internal(num_storage_nodes, recovery_threshold, srs)
205    }
206
207    /// Like [`Advz::new`] except with a `max_multiplicity` arg.
208    ///
209    /// `max_multiplicity` is an implementation-specific optimization arg.
210    /// Each storage node gets up to `max_multiplicity` evaluations per
211    /// polynomial. The actual multiplicity used will be the smallest value m
212    /// such that payload can fit (payload_len <= m * recovery_threshold).
213    ///
214    /// # Errors
215    /// In addition to [`Advz::new`], return [`VidError::Argument`] if
216    /// - TEMPORARY `max_multiplicity` is not a power of two [github issue](https://github.com/EspressoSystems/jellyfish/issues/668)
217    pub fn with_multiplicity(
218        num_storage_nodes: u32,
219        recovery_threshold: u32,
220        max_multiplicity: u32,
221        srs: impl Borrow<KzgSrs<E>>,
222    ) -> VidResult<Self> {
223        Self::with_multiplicity_internal(
224            num_storage_nodes,
225            recovery_threshold,
226            max_multiplicity,
227            srs,
228        )
229    }
230}
231
232#[cfg(feature = "gpu-vid")]
233impl<'srs, E, H> AdvzGPU<'srs, E, H>
234where
235    E: Pairing,
236    UnivariateKzgPCS<E>: GPUCommittable<E>,
237{
238    /// Like [`Advz::new`] except with SRS loaded to GPU
239    pub fn new(
240        num_storage_nodes: u32,
241        recovery_threshold: u32,
242        srs: impl Borrow<KzgSrs<E>>,
243    ) -> VidResult<Self> {
244        let mut advz = Self::new_internal(num_storage_nodes, recovery_threshold, srs)?;
245        advz.init_gpu_srs()?;
246        Ok(advz)
247    }
248    /// Like [`Advz::with_multiplicity`] except with SRS loaded to GPU
249    pub fn with_multiplicity(
250        num_storage_nodes: u32,
251        recovery_threshold: u32,
252        max_multiplicity: u32,
253        srs: impl Borrow<KzgSrs<E>>,
254    ) -> VidResult<Self> {
255        let mut advz = Self::with_multiplicity_internal(
256            num_storage_nodes,
257            recovery_threshold,
258            max_multiplicity,
259            srs,
260        )?;
261        advz.init_gpu_srs()?;
262        Ok(advz)
263    }
264
265    fn init_gpu_srs(&mut self) -> VidResult<()> {
266        let srs_on_gpu = <UnivariateKzgPCS<E> as GPUCommittable<E>>::load_prover_param_to_gpu(
267            &self.ck,
268            self.ck.powers_of_g.len() - 1,
269        )
270        .map_err(vid)?;
271        self.srs_on_gpu_and_cuda_stream = Some((srs_on_gpu, warmup_new_stream().map_err(vid)?));
272        Ok(())
273    }
274}
275
276/// The [`VidScheme::Share`] type for [`Advz`].
277#[derive(Derivative, Deserialize, Serialize)]
278#[serde(bound = "Output<H>: Serialize + for<'a> Deserialize<'a>")]
279#[derivative(
280    Clone(bound = ""),
281    Debug(bound = ""),
282    Eq(bound = ""),
283    Hash(bound = ""),
284    PartialEq(bound = "")
285)]
286pub struct Share<E, H>
287where
288    E: Pairing,
289    H: HasherDigest,
290{
291    index: u32,
292
293    #[serde(with = "canonical")]
294    // aggregate_proofs.len() equals multiplicity
295    // TODO further aggregate into a single KZG proof.
296    aggregate_proofs: Vec<KzgProof<E>>,
297
298    evals_proof: KzgEvalsMerkleTreeProof<E, H>,
299}
300
301impl<E, H> Share<E, H>
302where
303    E: Pairing,
304    H: HasherDigest,
305{
306    /// Return a [`Vec`] of payload data for this share.
307    ///
308    /// These data are extracted from [`MerkleProof`] structs. The returned
309    /// [`Vec`] has length `multiplicity * num_polys`
310    ///
311    /// TODO store these data in a new field `Share::evals` after fixing
312    /// https://github.com/EspressoSystems/jellyfish/issues/658
313    fn evals(&self) -> VidResult<&Vec<KzgEval<E>>> {
314        self.evals_proof
315            .elem()
316            .ok_or_else(|| VidError::Internal(anyhow::anyhow!("empty merkle proof")))
317    }
318}
319
320/// The [`VidScheme::Common`] type for [`Advz`].
321#[derive(CanonicalSerialize, CanonicalDeserialize, Derivative, Deserialize, Serialize)]
322#[derivative(
323    Clone(bound = ""),
324    Debug(bound = ""),
325    Eq(bound = ""),
326    Hash(bound = ""),
327    PartialEq(bound = "")
328)]
329pub struct Common<E, H>
330where
331    E: Pairing,
332    H: HasherDigest,
333{
334    #[serde(with = "canonical")]
335    poly_commits: Vec<KzgCommit<E>>,
336
337    #[serde(with = "canonical")]
338    all_evals_digest: KzgEvalsMerkleTreeNode<E, H>,
339
340    payload_byte_len: u32,
341    num_storage_nodes: u32,
342    multiplicity: u32,
343}
344
345/// A helper trait that cover API that maybe instantiated using GPU code
346/// in specialized implementation for concrete types
347pub trait MaybeGPU<E: Pairing> {
348    /// kzg batch commit
349    /// TODO: (alex) it's unfortnate that we are forced to use &mut self which
350    /// propagate out to `VidScheme::commit_only/disperse(&mut self)`
351    /// This should be fixed once ICICLE improve their `HostOrDeviceSlice`, and
352    /// we can update our `GPUCommittable::commit_on_gpu()` input type.
353    /// depends on <https://github.com/ingonyama-zk/icicle/pull/412>
354    fn kzg_batch_commit(
355        &mut self,
356        polys: &[DensePolynomial<E::ScalarField>],
357    ) -> VidResult<Vec<KzgCommit<E>>>;
358}
359
360impl<E, H> MaybeGPU<E> for Advz<E, H>
361where
362    E: Pairing,
363{
364    fn kzg_batch_commit(
365        &mut self,
366        polys: &[DensePolynomial<E::ScalarField>],
367    ) -> VidResult<Vec<KzgCommit<E>>> {
368        UnivariateKzgPCS::batch_commit(&self.ck, polys).map_err(vid)
369    }
370}
371
372#[cfg(feature = "gpu-vid")]
373impl<'srs, E, H> MaybeGPU<E> for AdvzGPU<'srs, E, H>
374where
375    E: Pairing,
376    UnivariateKzgPCS<E>: GPUCommittable<E>,
377{
378    fn kzg_batch_commit(
379        &mut self,
380        polys: &[DensePolynomial<E::ScalarField>],
381    ) -> VidResult<Vec<KzgCommit<E>>> {
382        if polys.is_empty() {
383            return Ok(vec![]);
384        }
385        let (srs_on_gpu, stream) = self.srs_on_gpu_and_cuda_stream.as_mut().map_err(vid)?; // safe by construction
386        <UnivariateKzgPCS<E> as GPUCommittable<E>>::gpu_batch_commit_with_loaded_prover_param(
387            srs_on_gpu, polys, stream,
388        )
389        .map_err(vid)
390    }
391}
392
393impl<E, H, T> VidScheme for AdvzInternal<E, H, T>
394where
395    E: Pairing,
396    H: HasherDigest,
397    T: Sync,
398    AdvzInternal<E, H, T>: MaybeGPU<E>,
399{
400    // use HasherNode<H> instead of Output<H> to easily meet trait bounds
401    type Commit = HasherNode<H>;
402
403    type Share = Share<E, H>;
404    type Common = Common<E, H>;
405
406    fn commit_only<B>(&mut self, payload: B) -> VidResult<Self::Commit>
407    where
408        B: AsRef<[u8]>,
409    {
410        let payload = payload.as_ref();
411        let payload_byte_len = payload.len().try_into().map_err(vid)?;
412        let multiplicity = self.min_multiplicity(payload_byte_len)?;
413        let chunk_size = multiplicity * self.recovery_threshold;
414        let polys = self.bytes_to_polys(payload)?;
415
416        let poly_commits_time = start_timer!(|| "batch poly commit");
417        let poly_commits = <Self as MaybeGPU<E>>::kzg_batch_commit(self, &polys)?;
418        end_timer!(poly_commits_time);
419
420        Self::derive_commit(&poly_commits, payload.len(), self.num_storage_nodes)
421    }
422
423    fn disperse<B>(&mut self, payload: B) -> VidResult<VidDisperse<Self>>
424    where
425        B: AsRef<[u8]>,
426    {
427        let payload = payload.as_ref();
428        let polys = self.bytes_to_polys(payload)?;
429        let poly_commits = <Self as MaybeGPU<E>>::kzg_batch_commit(self, &polys)?;
430
431        self.disperse_with_polys_and_commits(payload, polys, poly_commits)
432    }
433
434    fn verify_share(
435        &self,
436        share: &Self::Share,
437        common: &Self::Common,
438        commit: &Self::Commit,
439    ) -> VidResult<Result<(), ()>> {
440        // check arguments
441        if common.num_storage_nodes != self.num_storage_nodes {
442            return Err(VidError::Argument(format!(
443                "common num_storage_nodes {} differs from self {}",
444                common.num_storage_nodes, self.num_storage_nodes
445            )));
446        }
447        let multiplicity =
448            self.min_multiplicity(common.payload_byte_len.try_into().map_err(vid)?)?;
449        let multiplicity_usize = usize::try_from(multiplicity).map_err(vid)?;
450        if common.multiplicity != multiplicity {
451            return Err(VidError::Argument(format!(
452                "common multiplicity {} differs from derived min {}",
453                common.multiplicity, multiplicity
454            )));
455        }
456        let evals = share.evals()?;
457        if evals.len() / multiplicity as usize != common.poly_commits.len() {
458            return Err(VidError::Argument(format!(
459                "number of share evals / multiplicity {}/{} differs from number of common polynomial commitments {}",
460                evals.len(), multiplicity,
461                common.poly_commits.len()
462            )));
463        }
464        Self::is_consistent(commit, common)?;
465        if share.index >= self.num_storage_nodes {
466            return Ok(Err(())); // not an arg error
467        }
468
469        // verify eval proof
470        if KzgEvalsMerkleTree::<E, H>::verify(
471            common.all_evals_digest,
472            &KzgEvalsMerkleTreeIndex::<E, H>::from(share.index),
473            &share.evals_proof,
474        )
475        .map_err(vid)?
476        .is_err()
477        {
478            return Ok(Err(()));
479        }
480
481        // the magic pseudorandom scalar used for aggregation
482        let pseudorandom_scalar = Self::pseudorandom_scalar(common, commit)?;
483
484        // Compute an aggregate polynomial [commitment|evaluation] as a
485        // pseudorandom linear combo of [commitments|evaluations].
486        // To this end use function `polynomoial_eval` where each "coefficient"
487        // is actually a [commitment|evaluation] and the input point is
488        // `pseudorandom_scalar`.
489        //
490        // - Here: aggregate polynomial commitment in `aggregate_poly_commit`.
491        // - Below: aggregate polynomial evals in `aggregate_eval`.
492        let aggregate_poly_commit = KzgCommit::<E>::from(
493            polynomial_eval(
494                common
495                    .poly_commits
496                    .iter()
497                    .map(|x| CurveMultiplier(x.as_ref())),
498                pseudorandom_scalar,
499            )
500            .into(),
501        );
502
503        // convenience quantities
504        let multiplicities = Vec::from_iter((0..multiplicity_usize));
505        let polys_len = common.poly_commits.len();
506        let multi_open_domain = self.multi_open_domain(multiplicity)?;
507
508        // verify aggregate proofs
509        let verification_iter = parallelizable_slice_iter(&multiplicities).map(|m| {
510            let evals_iter = evals.iter().skip(*m).step_by(multiplicity_usize);
511            let aggregate_eval =
512                polynomial_eval(evals_iter.map(FieldMultiplier), pseudorandom_scalar);
513            let domain_index = usize::try_from(share.index).map_err(vid)?
514                + m * usize::try_from(self.num_storage_nodes).map_err(vid)?;
515            Ok(UnivariateKzgPCS::verify(
516                &self.vk,
517                &aggregate_poly_commit,
518                &multi_open_domain.element(domain_index),
519                &aggregate_eval,
520                &share.aggregate_proofs[*m],
521            )
522            .map_err(vid)?
523            .then_some(())
524            .ok_or(()))
525        });
526
527        // Boilerplate needed to accommodate builds without `parallel`feature.
528        let abort = |result: &VidResult<Result<(), ()>>| match result {
529            Ok(success) => success.is_err(),
530            Err(_) => true,
531        };
532
533        // abort immediately on any failure of verification
534        #[cfg(feature = "parallel")]
535        let result = verification_iter.find_any(abort);
536
537        #[cfg(not(feature = "parallel"))]
538        let result = verification_iter.clone().find(abort); // `clone` because we need mutable
539
540        result.unwrap_or(Ok(Ok(())))
541    }
542
543    fn recover_payload(&self, shares: &[Self::Share], common: &Self::Common) -> VidResult<Vec<u8>> {
544        // check args
545        if shares.len() < self.recovery_threshold as usize {
546            return Err(VidError::Argument(format!(
547                "not enough shares {}, expected at least {}",
548                shares.len(),
549                self.recovery_threshold
550            )));
551        }
552        if common.num_storage_nodes != self.num_storage_nodes {
553            return Err(VidError::Argument(format!(
554                "common num_storage_nodes differs from self ({},{})",
555                common.num_storage_nodes, self.num_storage_nodes
556            )));
557        }
558
559        let all_shares_evals = shares
560            .iter()
561            .map(|share| share.evals())
562            .collect::<VidResult<Vec<_>>>()?;
563
564        // check args: all shares must have equal evals len
565        let num_evals = all_shares_evals
566            .first()
567            .ok_or_else(|| VidError::Argument("shares is empty".into()))?
568            .len();
569        if let Some((index, share_evals)) = all_shares_evals
570            .iter()
571            .enumerate()
572            .find(|(_, evals)| evals.len() != num_evals)
573        {
574            return Err(VidError::Argument(format!(
575                "shares do not have equal evals lengths: share {} len {}, share {} len {}",
576                0,
577                num_evals,
578                index,
579                share_evals.len()
580            )));
581        }
582        if num_evals != common.multiplicity as usize * common.poly_commits.len() {
583            return Err(VidError::Argument(format!(
584                "num_evals should be (multiplicity * poly_commits): {} but is instead: {}",
585                common.multiplicity as usize * common.poly_commits.len(),
586                num_evals,
587            )));
588        }
589
590        // convenience quantities
591        let multiplicity = usize::try_from(common.multiplicity).map_err(vid)?;
592        let num_storage_nodes = usize::try_from(self.num_storage_nodes).map_err(vid)?;
593        let chunk_size = multiplicity * usize::try_from(self.recovery_threshold).map_err(vid)?;
594        let num_polys = common.poly_commits.len();
595        let elems_capacity = num_polys * chunk_size;
596        let fft_domain = Self::eval_domain(chunk_size)?;
597
598        let mut elems = Vec::with_capacity(elems_capacity);
599        let mut evals = Vec::with_capacity(num_evals);
600        for p in 0..num_polys {
601            for (share, share_evals) in shares.iter().zip(all_shares_evals.iter()) {
602                // extract all evaluations for polynomial p from the share
603                for m in 0..common.multiplicity as usize {
604                    evals.push((
605                        usize::try_from(share.index).map_err(vid)? + m * num_storage_nodes,
606                        share_evals[p * multiplicity + m],
607                    ))
608                }
609            }
610            let mut coeffs = reed_solomon_erasure_decode_rou(
611                mem::take(&mut evals),
612                chunk_size as usize,
613                &self.multi_open_domain(common.multiplicity)?,
614            )
615            .map_err(vid)?;
616
617            // TODO TEMPORARY: use FFT to encode polynomials in eval form
618            // Remove these FFTs after we get KZG in eval form
619            // https://github.com/EspressoSystems/jellyfish/issues/339
620            fft_domain.fft_in_place(&mut coeffs);
621
622            elems.append(&mut coeffs);
623        }
624        assert_eq!(elems.len(), elems_capacity);
625
626        let mut payload: Vec<_> = field_to_bytes(elems).collect();
627        payload.truncate(common.payload_byte_len.try_into().map_err(vid)?);
628        Ok(payload)
629    }
630
631    fn is_consistent(commit: &Self::Commit, common: &Self::Common) -> VidResult<()> {
632        if *commit
633            != Advz::<E, H>::derive_commit(
634                &common.poly_commits,
635                common.payload_byte_len,
636                common.num_storage_nodes,
637            )?
638        {
639            return Err(VidError::Argument(
640                "common inconsistent with commit".to_string(),
641            ));
642        }
643        Ok(())
644    }
645
646    fn get_payload_byte_len(common: &Self::Common) -> u32 {
647        common.payload_byte_len
648    }
649
650    fn get_num_storage_nodes(common: &Self::Common) -> u32 {
651        common.num_storage_nodes
652    }
653
654    fn get_multiplicity(common: &Self::Common) -> u32 {
655        common.multiplicity
656    }
657}
658
659impl<E, H, SrsRef> AdvzInternal<E, H, SrsRef>
660where
661    E: Pairing,
662    H: HasherDigest,
663    SrsRef: Sync,
664    AdvzInternal<E, H, SrsRef>: MaybeGPU<E>,
665{
666    fn disperse_with_polys_and_commits(
667        &self,
668        payload: &[u8],
669        polys: Vec<DensePolynomial<<E as Pairing>::ScalarField>>,
670        poly_commits: Vec<KzgCommit<E>>,
671    ) -> VidResult<VidDisperse<Self>> {
672        let payload_byte_len = payload.len().try_into().map_err(vid)?;
673        let disperse_time = start_timer!(|| format!(
674            "VID disperse {} payload bytes to {} nodes",
675            payload_byte_len, self.num_storage_nodes
676        ));
677        let multiplicity = self.min_multiplicity(payload.len())?;
678        let multiplicity_usize = usize::try_from(multiplicity).map_err(vid)?;
679        let num_storage_nodes_usize = usize::try_from(self.num_storage_nodes).map_err(vid)?;
680        let code_word_size = num_storage_nodes_usize * multiplicity_usize;
681        let multi_open_domain = self.multi_open_domain(multiplicity)?;
682
683        // evaluate polynomials
684        let all_storage_node_evals_timer = start_timer!(|| format!(
685            "compute all storage node evals for {} polynomials with {} coefficients",
686            polys.len(),
687            multiplicity * self.recovery_threshold
688        ));
689        let all_storage_node_evals = {
690            let all_poly_evals = parallelizable_slice_iter(&polys)
691                .map(|poly| {
692                    UnivariateKzgPCS::<E>::multi_open_rou_evals(
693                        poly,
694                        code_word_size,
695                        &multi_open_domain,
696                    )
697                    .map(|poly_evals| {
698                        assert_eq!(poly_evals.len(), code_word_size); // sanity
699                        poly_evals
700                    })
701                    .map_err(vid)
702                })
703                .collect::<Result<Vec<_>, VidError>>()?;
704            assert_eq!(all_poly_evals.len(), polys.len()); // sanity
705
706            // Populate a Vec of polynomial evaluations for all storage nodes:
707            //
708            // The `i`th item is a Vec of `polys.len() * multiplicity`
709            // polynomial evaluations.
710            //
711            // Evaluations for storage node `i` are ordered as follows. Define
712            // - `n`: the number of storage nodes: `self.num_storage_nodes`
713            // - `k`: the number of polynomials minus 1: `polys.len() - 1`
714            // - `m`: `multiplicity - 1`
715            // - `p[j](x)`: the value of the `j`th polynomial evaluated at `x`.
716            //
717            // p[0](i), p[0](i+n), ..., p[0](i+m*n),
718            // ...,
719            // p[k](i), p[k](i+n), ..., p[k](i+m*n),
720            let mut all_storage_node_evals =
721                vec![Vec::with_capacity(polys.len() * multiplicity_usize); num_storage_nodes_usize];
722            for poly_evals in all_poly_evals {
723                for (i, poly_eval) in poly_evals.into_iter().enumerate() {
724                    all_storage_node_evals[i % num_storage_nodes_usize].push(poly_eval);
725                }
726            }
727
728            // sanity checks
729            assert_eq!(all_storage_node_evals.len(), num_storage_nodes_usize);
730            for storage_node_evals in all_storage_node_evals.iter() {
731                assert_eq!(storage_node_evals.len(), polys.len() * multiplicity_usize);
732            }
733
734            all_storage_node_evals
735        };
736        end_timer!(all_storage_node_evals_timer);
737
738        // vector commitment to polynomial evaluations
739        let all_evals_commit_timer =
740            start_timer!(|| "compute merkle root of all storage node evals");
741        let all_evals_commit =
742            KzgEvalsMerkleTree::<E, H>::from_elems(None, all_storage_node_evals).map_err(vid)?;
743        end_timer!(all_evals_commit_timer);
744
745        let common = Common {
746            poly_commits,
747            all_evals_digest: all_evals_commit.commitment().digest(),
748            payload_byte_len,
749            num_storage_nodes: self.num_storage_nodes,
750            multiplicity,
751        };
752
753        let commit = Self::derive_commit(
754            &common.poly_commits,
755            payload_byte_len,
756            self.num_storage_nodes,
757        )?;
758        let pseudorandom_scalar = Self::pseudorandom_scalar(&common, &commit)?;
759
760        // Compute the aggregate polynomial as a pseudorandom linear combo of
761        // `polys`. To this end use function `polynomoial_eval` where each
762        // "coefficient" is actually a polynomial from `polys` and the input
763        // point is `pseudorandom_scalar`.
764        let aggregate_poly =
765            polynomial_eval(polys.iter().map(PolynomialMultiplier), pseudorandom_scalar);
766
767        let agg_proofs_timer = start_timer!(|| format!(
768            "compute aggregate proofs for {} storage nodes",
769            self.num_storage_nodes
770        ));
771        let aggregate_proofs = UnivariateKzgPCS::multi_open_rou_proofs(
772            &self.ck,
773            &aggregate_poly,
774            code_word_size as usize,
775            &multi_open_domain,
776        )
777        .map_err(vid)?;
778        end_timer!(agg_proofs_timer);
779
780        let assemblage_timer = start_timer!(|| "assemble shares for dispersal");
781
782        // Populate a Vec of aggregate proofs for all storage nodes:
783        //
784        // The `i`th item is a Vec of `multiplicity` KZG proofs.
785        //
786        // Proofs for storage node `i` are ordered as follows. Define
787        // - `n`: the number of storage nodes: `self.num_storage_nodes`
788        // - `m`: `multiplicity - 1`
789        // - `p(x)`: the value of the aggregate polynomial `p` evaluated at `x`.
790        //
791        // p(i), p(i+n), ..., p(i+m*n)
792        let mut all_storage_node_aggregate_proofs =
793            vec![Vec::with_capacity(multiplicity_usize); num_storage_nodes_usize];
794        for (i, aggregate_proof) in aggregate_proofs.into_iter().enumerate() {
795            all_storage_node_aggregate_proofs[i % num_storage_nodes_usize].push(aggregate_proof);
796        }
797
798        // sanity checks
799        assert_eq!(
800            all_storage_node_aggregate_proofs.len(),
801            num_storage_nodes_usize
802        );
803        for storage_node_aggregate_proof in all_storage_node_aggregate_proofs.iter() {
804            assert_eq!(storage_node_aggregate_proof.len(), multiplicity_usize);
805        }
806
807        let shares = all_storage_node_aggregate_proofs
808            .into_iter()
809            .enumerate()
810            .map(|(i, aggregate_proofs)| {
811                Ok(Share {
812                    index: u32::try_from(i).map_err(vid)?,
813                    aggregate_proofs,
814                    evals_proof: all_evals_commit
815                        .lookup(KzgEvalsMerkleTreeIndex::<E, H>::from(i as u64))
816                        .expect_ok()
817                        .map_err(vid)?
818                        .1,
819                })
820            })
821            .collect::<VidResult<Vec<_>>>()?;
822
823        end_timer!(assemblage_timer);
824        end_timer!(disperse_time);
825
826        Ok(VidDisperse {
827            shares,
828            common,
829            commit,
830        })
831    }
832
833    fn pseudorandom_scalar(
834        common: &<Self as VidScheme>::Common,
835        commit: &<Self as VidScheme>::Commit,
836    ) -> VidResult<KzgEval<E>> {
837        let mut hasher = H::new();
838        commit.serialize_uncompressed(&mut hasher).map_err(vid)?;
839        common
840            .all_evals_digest
841            .serialize_uncompressed(&mut hasher)
842            .map_err(vid)?;
843
844        // Notes on hash-to-field:
845        // - Can't use `Field::from_random_bytes` because it's fallible. (In what sense
846        //   is it from "random" bytes?!). This despite the docs explicitly say: "This
847        //   function is primarily intended for sampling random field elements from a
848        //   hash-function or RNG output."
849        // - We could use `ark_ff::fields::field_hashers::HashToField` but that forces
850        //   us to add additional trait bounds `Clone + Default + DynDigest` everywhere.
851        //   Also, `HashToField` does not expose an incremental API (ie. `update`) so we
852        //   would need to use an ordinary hasher and pipe `hasher.finalize()` through
853        //   `hash_to_field`. (Ugh!)
854        // - We don't need the resulting field element to be cryptographically
855        //   indistinguishable from uniformly random. We only need it to be
856        //   unpredictable. So it suffices to use
857        Ok(PrimeField::from_le_bytes_mod_order(&hasher.finalize()))
858    }
859
860    /// Partition payload into polynomial coefficients
861    fn bytes_to_polys(
862        &self,
863        payload: &[u8],
864    ) -> VidResult<Vec<DensePolynomial<<E as Pairing>::ScalarField>>>
865    where
866        E: Pairing,
867    {
868        let elem_bytes_len = bytes_to_field::elem_byte_capacity::<<E as Pairing>::ScalarField>();
869        let domain_size =
870            usize::try_from(self.min_multiplicity(payload.len())? * self.recovery_threshold)
871                .map_err(vid)?;
872
873        let bytes_to_polys_time = start_timer!(|| "encode payload bytes into polynomials");
874        let result = parallelizable_chunks(payload, domain_size * elem_bytes_len)
875            .map(|chunk| {
876                Self::interpolate_polynomial(bytes_to_field::<_, KzgEval<E>>(chunk), domain_size)
877            })
878            .collect::<VidResult<Vec<_>>>();
879        end_timer!(bytes_to_polys_time);
880        result
881    }
882
883    /// Consume `evals` and return a polynomial that interpolates `evals` on a
884    /// evaluation domain of size `domain_size`.
885    ///
886    /// Return an error if the length of `evals` exceeds `domain_size`.
887    ///
888    /// The degree-plus-1 of the returned polynomial is always a power of two
889    /// because:
890    ///
891    /// - We use FFT to interpolate, so `domain_size` is rounded up to the next
892    ///   power of two.
893    /// - [`KzgPolynomial`] implementation is stored in coefficient form.
894    ///
895    /// See https://github.com/EspressoSystems/jellyfish/issues/339
896    ///
897    /// Why is this method an associated function of `Self`? Because we want to
898    /// use a generic parameter of `Self`.
899    fn interpolate_polynomial<I>(evals: I, domain_size: usize) -> VidResult<KzgPolynomial<E>>
900    where
901        I: Iterator,
902        I::Item: Borrow<KzgEval<E>>,
903    {
904        // TODO TEMPORARY: use FFT to encode polynomials in eval form
905        // Remove these FFTs after we get KZG in eval form
906        // https://github.com/EspressoSystems/jellyfish/issues/339
907        let mut evals_vec: Vec<_> = evals.map(|c| *c.borrow()).collect();
908        let pre_fft_len = evals_vec.len();
909        if pre_fft_len > domain_size {
910            return Err(VidError::Internal(anyhow::anyhow!(
911                "number of evals {} exceeds domain_size {}",
912                pre_fft_len,
913                domain_size
914            )));
915        }
916        let domain = Self::eval_domain(domain_size)?;
917
918        domain.ifft_in_place(&mut evals_vec);
919
920        // sanity: the fft did not resize evals. If pre_fft_len < domain_size
921        // then we were given too few evals, in which case there's nothing to
922        // sanity check.
923        if pre_fft_len == domain_size && pre_fft_len != evals_vec.len() {
924            return Err(VidError::Internal(anyhow::anyhow!(
925                "unexpected output resize from {pre_fft_len} to {}",
926                evals_vec.len()
927            )));
928        }
929
930        Ok(DenseUVPolynomial::from_coefficients_vec(evals_vec))
931    }
932
933    fn min_multiplicity(&self, payload_byte_len: usize) -> VidResult<u32> {
934        let elem_bytes_len = bytes_to_field::elem_byte_capacity::<<E as Pairing>::ScalarField>();
935        let elems: u32 = payload_byte_len
936            .div_ceil(elem_bytes_len)
937            .try_into()
938            .map_err(vid)?;
939        if self.recovery_threshold * self.max_multiplicity < elems {
940            // payload is large. no change in multiplicity needed.
941            return Ok(self.max_multiplicity);
942        }
943
944        // payload is small: choose the smallest `m` such that `0 < m <
945        // multiplicity` and the entire payload fits into `m *
946        // recovery_threshold` elements.
947        let m = elems.div_ceil(self.recovery_threshold.max(1)).max(1);
948
949        // TODO TEMPORARY: enforce power-of-2
950        // https://github.com/EspressoSystems/jellyfish/issues/668
951        //
952        // Round up to the nearest power of 2.
953        //
954        // After the above issue is fixed: delete the following code and return
955        // `m` from above.
956        if m <= 1 {
957            Ok(1)
958        } else {
959            Ok(1 << ((m - 1).ilog2() + 1))
960        }
961    }
962
963    /// Derive a commitment from whatever data is needed.
964    ///
965    /// Generic types `T`, `U` allow caller to pass `usize` or anything else.
966    /// Yes, Rust really wants these horrible trait bounds on
967    /// `<T as TryInto<u32>>::Error`.
968    fn derive_commit<T, U>(
969        poly_commits: &[KzgCommit<E>],
970        payload_byte_len: T,
971        num_storage_nodes: U,
972    ) -> VidResult<<Self as VidScheme>::Commit>
973    where
974        T: TryInto<u32>,
975        <T as TryInto<u32>>::Error: ark_std::fmt::Display + Debug + Send + Sync + 'static,
976        U: TryInto<u32>,
977        <U as TryInto<u32>>::Error: ark_std::fmt::Display + Debug + Send + Sync + 'static,
978    {
979        let payload_byte_len: u32 = payload_byte_len.try_into().map_err(vid)?;
980        let num_storage_nodes: u32 = num_storage_nodes.try_into().map_err(vid)?;
981        let mut hasher = H::new();
982        payload_byte_len
983            .serialize_uncompressed(&mut hasher)
984            .map_err(vid)?;
985        num_storage_nodes
986            .serialize_uncompressed(&mut hasher)
987            .map_err(vid)?;
988        for poly_commit in poly_commits {
989            poly_commit
990                .serialize_uncompressed(&mut hasher)
991                .map_err(vid)?;
992        }
993        Ok(hasher.finalize().into())
994    }
995
996    fn multi_open_domain(
997        &self,
998        multiplicity: u32,
999    ) -> VidResult<Radix2EvaluationDomain<<E as Pairing>::ScalarField>> {
1000        let chunk_size = usize::try_from(multiplicity * self.recovery_threshold).map_err(vid)?;
1001        let code_word_size = usize::try_from(multiplicity * self.num_storage_nodes).map_err(vid)?;
1002        UnivariateKzgPCS::<E>::multi_open_rou_eval_domain(chunk_size - 1, code_word_size)
1003            .map_err(vid)
1004    }
1005
1006    fn eval_domain(
1007        domain_size: usize,
1008    ) -> VidResult<Radix2EvaluationDomain<<E as Pairing>::ScalarField>> {
1009        Radix2EvaluationDomain::<KzgPoint<E>>::new(domain_size).ok_or_else(|| {
1010            VidError::Internal(anyhow::anyhow!(
1011                "fail to construct domain of size {domain_size}"
1012            ))
1013        })
1014    }
1015}
1016
1017/// Evaluate a generalized polynomial at a given point using Horner's method.
1018///
1019/// Coefficients can be anything that can be multiplied by a point
1020/// and such that the result of such multiplications can be added.
1021fn polynomial_eval<U, F, I>(coeffs: I, point: impl Borrow<F>) -> U
1022where
1023    I: IntoIterator,
1024    I::Item: for<'a> Mul<&'a F, Output = U>,
1025    U: Add<Output = U> + Zero,
1026{
1027    coeffs
1028        .into_iter()
1029        .fold(U::zero(), |res, coeff| coeff * point.borrow() + res)
1030}
1031
1032struct FieldMultiplier<'a, F>(&'a F);
1033
1034/// Arkworks does not provide (&F,&F) multiplication
1035impl<F> Mul<&F> for FieldMultiplier<'_, F>
1036where
1037    F: Field,
1038{
1039    type Output = F;
1040
1041    fn mul(self, rhs: &F) -> Self::Output {
1042        *self.0 * rhs
1043    }
1044}
1045
1046/// Arkworks does not provide (&C,&F) multiplication
1047struct CurveMultiplier<'a, C>(&'a C);
1048
1049impl<C, F> Mul<&F> for CurveMultiplier<'_, C>
1050where
1051    C: AffineRepr<ScalarField = F>,
1052{
1053    type Output = C::Group;
1054
1055    fn mul(self, rhs: &F) -> Self::Output {
1056        *self.0 * rhs
1057    }
1058}
1059
1060/// Arkworks does not provide (&P,&F) multiplication
1061struct PolynomialMultiplier<'a, P>(&'a P);
1062
1063impl<P, F> Mul<&F> for PolynomialMultiplier<'_, P>
1064where
1065    P: DenseUVPolynomial<F>,
1066    F: Field,
1067{
1068    type Output = P;
1069
1070    fn mul(self, rhs: &F) -> Self::Output {
1071        // `Polynomial` does not impl `Mul` by scalar
1072        // so we need to multiply each coeff by `rhs`
1073        P::from_coefficients_vec(self.0.coeffs().iter().map(|coeff| *coeff * rhs).collect())
1074    }
1075}
1076
1077#[cfg(test)]
1078mod test;