penumbra_sdk_proof_setup/single/
phase1.rs

1use anyhow::anyhow;
2use ark_ec::Group;
3use ark_ff::{One, Zero};
4use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
5use rand_core::{CryptoRngCore, OsRng};
6
7use crate::parallel_utils::{flatten_results, transform_parallel, zip_map_parallel};
8use crate::single::dlog;
9use crate::single::group::{
10    BatchedPairingChecker11, BatchedPairingChecker12, GroupHasher, F, G1, G2,
11};
12use crate::single::log::{ContributionHash, Hashable, Phase};
13
14/// Check that a given degree is high enough.
15///
16/// (We use this enough times to warrant separating it out).
17const fn is_degree_large_enough(d: usize) -> bool {
18    // We need to have at least the index 1 for our x_i values, so we need d >= 2.
19    d >= 2
20}
21
22// Some utility functions for encoding the relation between CRS length and degree
23
24const fn short_len(d: usize) -> usize {
25    (d - 1) + 1
26}
27
28const fn short_len_to_degree(l: usize) -> usize {
29    l
30}
31
32const fn long_len(d: usize) -> usize {
33    (2 * d - 2) + 1
34}
35
36/// Raw CRS elements, not yet validated for consistency.
37#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize, PartialEq)]
38pub struct RawCRSElements {
39    pub alpha_1: G1,
40    pub beta_1: G1,
41    pub beta_2: G2,
42    pub x_1: Vec<G1>,
43    pub x_2: Vec<G2>,
44    pub alpha_x_1: Vec<G1>,
45    pub beta_x_1: Vec<G1>,
46}
47
48impl RawCRSElements {
49    /// Extract a degree, if possible, from these elements.
50    ///
51    /// This can fail if the elements aren't using a consistent degree size,
52    /// or this degree isn't large enough.
53    fn get_degree(&self) -> Option<usize> {
54        let l = self.x_2.len();
55        let d = short_len_to_degree(l);
56        if !is_degree_large_enough(d) {
57            return None;
58        }
59        if self.alpha_x_1.len() != short_len(d) {
60            return None;
61        }
62        if self.beta_x_1.len() != short_len(d) {
63            return None;
64        }
65        if self.x_1.len() != long_len(d) {
66            return None;
67        }
68        Some(d)
69    }
70
71    /// Validate the internal consistency of these elements, producing a validated struct.
72    ///
73    /// This checks if the structure of the elements uses the secret scalars
74    /// hidden behind the group elements correctly.
75    pub fn validate(self) -> anyhow::Result<CRSElements> {
76        // 0. Check that we can extract a valid degree out of these elements.
77        let d = self
78            .get_degree()
79            .ok_or_else(|| anyhow!("failed to get degree"))?;
80        // 1. Check that the elements committing to the secret values are not 0.
81        if self.alpha_1.is_zero()
82            || self.beta_1.is_zero()
83            || self.beta_2.is_zero()
84            || self.x_1[1].is_zero()
85            || self.x_2[1].is_zero()
86        {
87            anyhow::bail!("one of alpha_1, beta_1, beta_2, x_1[1], x_2[1] was zero");
88        }
89        // 2. Check that the two beta commitments match.
90        // 3. Check that the x values match on both groups.
91        let mut checker0 = BatchedPairingChecker12::new(G2::generator(), G1::generator());
92        checker0.add(self.beta_1, self.beta_2);
93        for (&x_1_i, &x_2_i) in self.x_1.iter().zip(self.x_2.iter()) {
94            checker0.add(x_1_i, x_2_i);
95        }
96
97        // 4. Check that alpha and x are connected in alpha_x.
98        let mut checker1 = BatchedPairingChecker12::new(G2::generator(), self.alpha_1);
99        for (&alpha_x_i, &x_i) in self.alpha_x_1.iter().zip(self.x_2.iter()) {
100            checker1.add(alpha_x_i, x_i);
101        }
102        //
103        // 5. Check that beta and x are connected in beta_x.
104        let mut checker2 = BatchedPairingChecker12::new(G2::generator(), self.beta_1);
105        for (&beta_x_i, &x_i) in self.beta_x_1.iter().zip(self.x_2.iter()) {
106            checker2.add(beta_x_i, x_i);
107        }
108        // 6. Check that the x_i are the correct powers of x.
109        let mut checker3 = BatchedPairingChecker11::new(self.x_2[1], G2::generator());
110        for (&x_i, &x_i_plus_1) in self.x_1.iter().zip(self.x_1.iter().skip(1)) {
111            checker3.add(x_i, x_i_plus_1);
112        }
113        // "神だけが私を裁ける"
114        flatten_results(transform_parallel(
115            [
116                (0, Ok(checker0)),
117                (1, Ok(checker1)),
118                (2, Ok(checker2)),
119                (3, Err(checker3)),
120            ],
121            |(i, x)| {
122                let ok = match x {
123                    Ok(x) => x.check(&mut OsRng),
124                    Err(x) => x.check(&mut OsRng),
125                };
126                if ok {
127                    Ok(())
128                } else {
129                    Err(anyhow!("checker {} failed", i))
130                }
131            },
132        ))?;
133
134        Ok(CRSElements {
135            degree: d,
136            raw: self,
137        })
138    }
139
140    /// Convert without checking validity.
141    pub(crate) fn assume_valid(self) -> CRSElements {
142        let d = self
143            .get_degree()
144            .expect("can get degree from valid elements");
145
146        CRSElements {
147            degree: d,
148            raw: self,
149        }
150    }
151
152    /// This is a replacement for the CanonicalDeserialize trait impl (more or less).
153    #[cfg(not(feature = "parallel"))]
154    pub(crate) fn checked_deserialize_parallel(
155        compress: Compress,
156        data: &[u8],
157    ) -> anyhow::Result<Self> {
158        Ok(Self::deserialize_with_mode(data, compress, Validate::Yes)?)
159    }
160
161    /// This is a replacement for the CanonicalDeserialize trait impl (more or less).
162    #[cfg(feature = "parallel")]
163    pub(crate) fn checked_deserialize_parallel(
164        compress: Compress,
165        data: &[u8],
166    ) -> anyhow::Result<Self> {
167        use ark_serialize::Valid;
168        use rayon::prelude::*;
169
170        let out = Self::deserialize_with_mode(data, compress, Validate::No)?;
171        out.alpha_1.check()?;
172        out.beta_1.check()?;
173        out.beta_2.check()?;
174
175        let mut check_x_1 = Ok(());
176        let mut check_x_2 = Ok(());
177        let mut check_alpha_x_1 = Ok(());
178        let mut check_beta_x_1 = Ok(());
179
180        rayon::join(
181            || {
182                rayon::join(
183                    || {
184                        check_x_1 = out
185                            .x_1
186                            .par_iter()
187                            .map(|x| x.check())
188                            .collect::<Result<_, _>>();
189                    },
190                    || {
191                        check_x_2 = out
192                            .x_2
193                            .par_iter()
194                            .map(|x| x.check())
195                            .collect::<Result<_, _>>();
196                    },
197                )
198            },
199            || {
200                rayon::join(
201                    || {
202                        check_alpha_x_1 = out
203                            .alpha_x_1
204                            .par_iter()
205                            .map(|x| x.check())
206                            .collect::<Result<_, _>>();
207                    },
208                    || {
209                        check_beta_x_1 = out
210                            .beta_x_1
211                            .par_iter()
212                            .map(|x| x.check())
213                            .collect::<Result<_, _>>();
214                    },
215                )
216            },
217        );
218
219        check_x_1?;
220        check_x_2?;
221        check_alpha_x_1?;
222        check_beta_x_1?;
223        Ok(out)
224    }
225}
226
227impl Hashable for RawCRSElements {
228    fn hash(&self) -> ContributionHash {
229        let mut hasher = GroupHasher::new(b"PC$:crs_elmnts1");
230        hasher.eat_g1(&self.alpha_1);
231        hasher.eat_g1(&self.beta_1);
232        hasher.eat_g2(&self.beta_2);
233
234        hasher.eat_usize(self.x_1.len());
235        for v in &self.x_1 {
236            hasher.eat_g1(v);
237        }
238
239        hasher.eat_usize(self.x_2.len());
240        for v in &self.x_2 {
241            hasher.eat_g2(v);
242        }
243
244        hasher.eat_usize(self.alpha_x_1.len());
245        for v in &self.alpha_x_1 {
246            hasher.eat_g1(v);
247        }
248
249        hasher.eat_usize(self.beta_x_1.len());
250        for v in &self.beta_x_1 {
251            hasher.eat_g1(v);
252        }
253
254        ContributionHash(hasher.finalize_bytes())
255    }
256}
257
258/// The CRS elements we produce in phase 1.
259///
260/// Not all elements of the final CRS are present here.
261#[derive(Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
262pub struct CRSElements {
263    pub(crate) degree: usize,
264    pub(crate) raw: RawCRSElements,
265}
266
267impl CRSElements {
268    /// Generate a "root" CRS, containing the value 1 for each secret element.
269    ///
270    /// This takes in the degree "d" associated with the circuit we need
271    /// to do a setup for, as per the docs.
272    ///
273    /// Naturally, these elements shouldn't actually be used as-is, but this
274    /// serves as a logical basis for the start of the phase.
275    pub fn root(d: usize) -> Self {
276        assert!(is_degree_large_enough(d));
277
278        let raw = RawCRSElements {
279            alpha_1: G1::generator(),
280            beta_1: G1::generator(),
281            beta_2: G2::generator(),
282            x_1: vec![G1::generator(); (2 * d - 2) + 1],
283            x_2: vec![G2::generator(); (d - 1) + 1],
284            alpha_x_1: vec![G1::generator(); (d - 1) + 1],
285            beta_x_1: vec![G1::generator(); (d - 1) + 1],
286        };
287        Self { degree: d, raw }
288    }
289}
290
291impl Hashable for CRSElements {
292    fn hash(&self) -> ContributionHash {
293        // No need to hash the degree, already implied by the lengths of the elements.
294        self.raw.hash()
295    }
296}
297
298/// A linking proof shows knowledge of the new secret elements linking two sets of CRS elements.
299///
300/// This pets two cats with one hand:
301/// 1. We show that we're actually building off of the previous elements.
302/// 2. We show that we know the secret elements we're using, avoiding rogue key chicanery.
303#[derive(Clone, Copy, Debug, CanonicalSerialize, CanonicalDeserialize)]
304pub(crate) struct LinkingProof {
305    alpha_proof: dlog::Proof,
306    beta_proof: dlog::Proof,
307    x_proof: dlog::Proof,
308}
309
310/// Represents a contribution before validation.
311#[derive(Clone, Debug)]
312pub struct RawContribution {
313    pub parent: ContributionHash,
314    pub new_elements: RawCRSElements,
315    pub(crate) linking_proof: LinkingProof,
316}
317
318impl RawContribution {
319    /// Validate this raw contribution, potentially producing a valid one.
320    pub fn validate(&self) -> Option<Contribution> {
321        self.new_elements
322            .to_owned()
323            .validate()
324            .ok()
325            .map(|new_elements| Contribution {
326                parent: self.parent,
327                new_elements,
328                linking_proof: self.linking_proof,
329            })
330    }
331
332    pub(crate) fn assume_valid(self) -> Contribution {
333        Contribution {
334            parent: self.parent,
335            new_elements: self.new_elements.assume_valid(),
336            linking_proof: self.linking_proof,
337        }
338    }
339}
340
341impl Hashable for RawContribution {
342    fn hash(&self) -> ContributionHash {
343        let mut hasher = GroupHasher::new(b"PC$:contrbution1");
344        hasher.eat_bytes(self.parent.as_ref());
345        hasher.eat_bytes(self.new_elements.hash().as_ref());
346        // Note: we could hide this behind another level of indirection, but contribution
347        // already uses the internals of the linking proof anyways, so this doesn't
348        // feel egregious to me.
349        hasher.eat_bytes(self.linking_proof.alpha_proof.hash().as_ref());
350        hasher.eat_bytes(self.linking_proof.beta_proof.hash().as_ref());
351        hasher.eat_bytes(self.linking_proof.x_proof.hash().as_ref());
352        ContributionHash(hasher.finalize_bytes())
353    }
354}
355
356impl From<Contribution> for RawContribution {
357    fn from(value: Contribution) -> Self {
358        Self {
359            parent: value.parent,
360            new_elements: value.new_elements.raw,
361            linking_proof: value.linking_proof,
362        }
363    }
364}
365
366/// Represents a contribution to phase1 of the ceremony.
367///
368/// This contribution is linked to a previous contribution, which it builds upon.
369///
370/// The contribution includes new elements for the CRS, along with a proof that these elements
371/// build upon the claimed parent contribution.
372#[derive(Clone, Debug)]
373pub struct Contribution {
374    pub parent: ContributionHash,
375    pub new_elements: CRSElements,
376    pub(crate) linking_proof: LinkingProof,
377}
378
379impl Hashable for Contribution {
380    fn hash(&self) -> ContributionHash {
381        RawContribution::from(self.to_owned()).hash()
382    }
383}
384
385impl Contribution {
386    /// Make a new contribution, over the previous CRS elements.
387    ///
388    /// We also need a contribution hash, for the parent we're building on,
389    /// including those elements and other information, which will then appear
390    /// in the resulting contribution we're making.
391    pub fn make<R: CryptoRngCore>(
392        rng: &mut R,
393        parent: ContributionHash,
394        old: &CRSElements,
395    ) -> Self {
396        let alpha = F::rand(rng);
397        let beta = F::rand(rng);
398        let x = F::rand(rng);
399
400        let alpha_proof = dlog::prove(
401            rng,
402            b"phase1 alpha proof",
403            dlog::Statement {
404                result: old.raw.alpha_1 * alpha,
405                base: old.raw.alpha_1,
406            },
407            dlog::Witness { dlog: alpha },
408        );
409        let beta_proof = dlog::prove(
410            rng,
411            b"phase1 beta proof",
412            dlog::Statement {
413                result: old.raw.beta_1 * beta,
414                base: old.raw.beta_1,
415            },
416            dlog::Witness { dlog: beta },
417        );
418        let x_proof = dlog::prove(
419            rng,
420            b"phase1 x proof",
421            dlog::Statement {
422                result: old.raw.x_1[1] * x,
423                base: old.raw.x_1[1],
424            },
425            dlog::Witness { dlog: x },
426        );
427
428        let d = old.degree;
429
430        let (mut x_i_tweaks, mut alpha_x_i_tweaks, mut beta_x_i_tweaks) = {
431            let mut x_i_tweaks = Vec::new();
432            let mut alpha_x_i_tweaks = Vec::new();
433            let mut beta_x_i_tweaks = Vec::new();
434
435            let mut x_i = F::one();
436            let mut alpha_x_i = alpha;
437            let mut beta_x_i = beta;
438
439            for _ in 0..short_len(d) {
440                x_i_tweaks.push(x_i);
441                alpha_x_i_tweaks.push(alpha_x_i);
442                beta_x_i_tweaks.push(beta_x_i);
443
444                x_i *= x;
445                alpha_x_i *= x;
446                beta_x_i *= x;
447            }
448            for _ in short_len(d)..long_len(d) {
449                x_i_tweaks.push(x_i);
450
451                x_i *= x;
452            }
453
454            (x_i_tweaks, alpha_x_i_tweaks, beta_x_i_tweaks)
455        };
456
457        let mut old = old.clone();
458        let x_1 = zip_map_parallel(&mut old.raw.x_1, &mut x_i_tweaks, |g, x| *g * x);
459        let x_2 = zip_map_parallel(&mut old.raw.x_2, &mut x_i_tweaks, |g, x| *g * x);
460        let alpha_x_1 =
461            zip_map_parallel(&mut old.raw.alpha_x_1, &mut alpha_x_i_tweaks, |g, x| *g * x);
462        let beta_x_1 = zip_map_parallel(&mut old.raw.beta_x_1, &mut beta_x_i_tweaks, |g, x| *g * x);
463
464        let new_elements = CRSElements {
465            degree: d,
466            raw: RawCRSElements {
467                alpha_1: old.raw.alpha_1 * alpha,
468                beta_1: old.raw.beta_1 * beta,
469                beta_2: old.raw.beta_2 * beta,
470                x_1,
471                x_2,
472                alpha_x_1,
473                beta_x_1,
474            },
475        };
476
477        Self {
478            parent,
479            new_elements,
480            linking_proof: LinkingProof {
481                alpha_proof,
482                beta_proof,
483                x_proof,
484            },
485        }
486    }
487
488    /// Verify that this contribution is linked to a previous list of elements.
489    #[must_use]
490    pub fn is_linked_to(&self, parent: &CRSElements) -> bool {
491        // 1. Check that the degrees match between the two CRS elements.
492        if self.new_elements.degree != parent.degree {
493            return false;
494        }
495        // 2. Check that the linking proofs verify
496        let ctxs: [&'static [u8]; 3] = [
497            b"phase1 alpha proof",
498            b"phase1 beta proof",
499            b"phase1 x proof",
500        ];
501        let statements = [
502            dlog::Statement {
503                result: self.new_elements.raw.alpha_1,
504                base: parent.raw.alpha_1,
505            },
506            dlog::Statement {
507                result: self.new_elements.raw.beta_1,
508                base: parent.raw.beta_1,
509            },
510            dlog::Statement {
511                result: self.new_elements.raw.x_1[1],
512                base: parent.raw.x_1[1],
513            },
514        ];
515        let proofs = [
516            self.linking_proof.alpha_proof,
517            self.linking_proof.beta_proof,
518            self.linking_proof.x_proof,
519        ];
520        if !ctxs
521            .iter()
522            .zip(statements.iter())
523            .zip(proofs.iter())
524            .all(|((c, &s), p)| dlog::verify(c, s, p))
525        {
526            return false;
527        }
528
529        true
530    }
531}
532
533/// A dummy struct representing this phase, for the sake of implementing the right trait.
534#[derive(Clone, Debug, Default)]
535struct Phase1;
536
537impl Phase for Phase1 {
538    type CRSElements = CRSElements;
539
540    type RawContribution = RawContribution;
541
542    type Contribution = Contribution;
543
544    fn parent_hash(contribution: &Self::RawContribution) -> ContributionHash {
545        contribution.parent
546    }
547
548    fn elements(contribution: &Self::Contribution) -> &Self::CRSElements {
549        &contribution.new_elements
550    }
551
552    fn validate(
553        _root: &Self::CRSElements,
554        contribution: &Self::RawContribution,
555    ) -> Option<Self::Contribution> {
556        contribution.validate()
557    }
558
559    fn is_linked_to(contribution: &Self::Contribution, elements: &Self::CRSElements) -> bool {
560        contribution.is_linked_to(elements)
561    }
562}
563
564#[cfg(test)]
565mod test {
566    use super::*;
567
568    use rand_core::OsRng;
569
570    use crate::single::group::F;
571    use crate::single::log::CONTRIBUTION_HASH_SIZE;
572
573    /// The degree we use for tests.
574    ///
575    /// Keeping this small makes tests go faster.
576    const D: usize = 2;
577
578    fn make_crs(alpha: F, beta: F, x: F) -> RawCRSElements {
579        RawCRSElements {
580            alpha_1: G1::generator() * alpha,
581            beta_1: G1::generator() * beta,
582            beta_2: G2::generator() * beta,
583            x_1: vec![
584                G1::generator(),
585                G1::generator() * x,
586                G1::generator() * (x * x),
587            ],
588            x_2: vec![G2::generator(), G2::generator() * x],
589            alpha_x_1: vec![G1::generator() * alpha, G1::generator() * (alpha * x)],
590            beta_x_1: vec![G1::generator() * beta, G1::generator() * (beta * x)],
591        }
592    }
593
594    fn non_trivial_crs() -> RawCRSElements {
595        let alpha = F::rand(&mut OsRng);
596        let beta = F::rand(&mut OsRng);
597        let x = F::rand(&mut OsRng);
598
599        make_crs(alpha, beta, x)
600    }
601
602    #[test]
603    fn test_root_crs_is_valid() {
604        let root = CRSElements::root(D);
605        assert!(root.raw.validate().is_ok());
606    }
607
608    #[test]
609    fn test_nontrivial_crs_is_valid() {
610        let crs = non_trivial_crs();
611        assert!(crs.validate().is_ok());
612    }
613
614    #[test]
615    fn test_changing_alpha_makes_crs_invalid() {
616        let mut crs = non_trivial_crs();
617        crs.alpha_1 = G1::generator();
618        assert!(crs.validate().is_err());
619    }
620
621    #[test]
622    fn test_changing_beta_makes_crs_invalid() {
623        let mut crs = non_trivial_crs();
624        crs.beta_1 = G1::generator();
625        assert!(crs.validate().is_err());
626    }
627
628    #[test]
629    fn test_setting_zero_elements_makes_crs_invalid() {
630        let alpha = F::rand(&mut OsRng);
631        let beta = F::rand(&mut OsRng);
632        let x = F::rand(&mut OsRng);
633
634        let crs0 = make_crs(F::zero(), beta, x);
635        assert!(crs0.validate().is_err());
636        let crs1 = make_crs(alpha, F::zero(), x);
637        assert!(crs1.validate().is_err());
638        let crs2 = make_crs(alpha, beta, F::zero());
639        assert!(crs2.validate().is_err());
640    }
641
642    #[test]
643    fn test_bad_powers_of_x_makes_crs_invalid() {
644        let alpha = F::rand(&mut OsRng);
645        let beta = F::rand(&mut OsRng);
646        let x = F::rand(&mut OsRng);
647        let crs = RawCRSElements {
648            alpha_1: G1::generator() * alpha,
649            beta_1: G1::generator() * beta,
650            beta_2: G2::generator() * beta,
651            x_1: vec![
652                G1::generator(),
653                G1::generator() * x,
654                G1::generator() * (x * x),
655                // The important part
656                G1::generator() * (x * x),
657            ],
658            x_2: vec![G2::generator(), G2::generator() * x],
659            alpha_x_1: vec![G1::generator() * alpha, G1::generator() * (alpha * x)],
660            beta_x_1: vec![G1::generator() * beta, G1::generator() * (beta * x)],
661        };
662        assert!(crs.validate().is_err());
663    }
664
665    #[test]
666    fn test_contribution_produces_valid_crs() {
667        let root = CRSElements::root(D);
668        let contribution = Contribution::make(
669            &mut OsRng,
670            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
671            &root,
672        );
673        assert!(contribution.new_elements.raw.validate().is_ok());
674    }
675
676    #[test]
677    fn test_contribution_is_linked_to_parent() {
678        let root = CRSElements::root(D);
679        let contribution = Contribution::make(
680            &mut OsRng,
681            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
682            &root,
683        );
684        assert!(contribution.is_linked_to(&root));
685    }
686
687    #[test]
688    fn test_can_calculate_contribution_hash() {
689        let root = CRSElements::root(D);
690        let contribution = Contribution::make(
691            &mut OsRng,
692            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
693            &root,
694        );
695        assert_ne!(contribution.hash(), contribution.parent)
696    }
697
698    #[test]
699    fn test_contribution_is_not_linked_to_itself() {
700        let root = CRSElements::root(D);
701        let contribution = Contribution::make(
702            &mut OsRng,
703            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
704            &root,
705        );
706        assert!(!contribution.is_linked_to(&contribution.new_elements));
707    }
708
709    #[test]
710    fn test_contribution_is_not_linked_if_degree_changes() {
711        // Same elements, the latter just has more
712        let root0 = CRSElements::root(D);
713        let root1 = CRSElements::root(D + 1);
714        let contribution = Contribution::make(
715            &mut OsRng,
716            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
717            &root0,
718        );
719        assert!(!contribution.is_linked_to(&root1));
720    }
721}