penumbra_sdk_proof_setup/single/
phase2.rs

1//! This module is very similar to the one for phase1, so reading that one might be useful.
2use ark_ec::Group;
3use ark_ff::Zero;
4use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
5use rand_core::{CryptoRngCore, OsRng};
6
7use crate::single::log::{ContributionHash, Hashable, Phase};
8use crate::single::{
9    dlog,
10    group::{BatchedPairingChecker11, GroupHasher, F, G1, G2},
11};
12
13/// Raw CRS elements, not yet validated for consistency.
14#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize, PartialEq)]
15pub struct RawCRSElements {
16    pub delta_1: G1,
17    pub delta_2: G2,
18    pub inv_delta_p_1: Vec<G1>,
19    pub inv_delta_t_1: Vec<G1>,
20}
21
22impl RawCRSElements {
23    #[must_use]
24    pub fn validate<R: CryptoRngCore>(
25        self,
26        rng: &mut R,
27        root: &CRSElements,
28    ) -> Option<CRSElements> {
29        // 0. Check that the lengths match that of the root.
30        if self.inv_delta_p_1.len() != root.raw.inv_delta_p_1.len()
31            || self.inv_delta_t_1.len() != root.raw.inv_delta_t_1.len()
32        {
33            return None;
34        }
35        // 1. Check that the elements committing to secret values are not 0.
36        if self.delta_1.is_zero() || self.delta_2.is_zero() {
37            return None;
38        }
39        // 2. Check that the two delta commitments match.
40        // 3. Check that 1/delta has multiplied the root polynomial p
41        // 3. Check that 1/delta has multiplied the root polynomial t
42        // We can use one batch check for all of these!
43        let mut checker = BatchedPairingChecker11::new(self.delta_2, G2::generator());
44        checker.add(G1::generator(), self.delta_1);
45        for (&inv_delta_p_i, &p_i) in self.inv_delta_p_1.iter().zip(root.raw.inv_delta_p_1.iter()) {
46            checker.add(inv_delta_p_i, p_i);
47        }
48        for (&inv_delta_t_i, &t_i) in self.inv_delta_t_1.iter().zip(root.raw.inv_delta_t_1.iter()) {
49            checker.add(inv_delta_t_i, t_i);
50        }
51        if !checker.check(rng) {
52            return None;
53        }
54
55        Some(CRSElements { raw: self })
56    }
57
58    /// Convert without checking validity.
59    pub(crate) fn assume_valid(self) -> CRSElements {
60        CRSElements { raw: self }
61    }
62
63    /// This is a replacement for the CanonicalDeserialize trait impl (more or less).
64    #[cfg(not(feature = "parallel"))]
65    pub(crate) fn checked_deserialize_parallel(
66        compress: Compress,
67        data: &[u8],
68    ) -> anyhow::Result<Self> {
69        Ok(Self::deserialize_with_mode(data, compress, Validate::Yes)?)
70    }
71
72    /// This is a replacement for the CanonicalDeserialize trait impl (more or less).
73    #[cfg(feature = "parallel")]
74    pub(crate) fn checked_deserialize_parallel(
75        compress: Compress,
76        data: &[u8],
77    ) -> anyhow::Result<Self> {
78        use ark_serialize::Valid;
79        use rayon::prelude::*;
80
81        let out = Self::deserialize_with_mode(data, compress, Validate::No)?;
82        out.delta_1.check()?;
83        out.delta_2.check()?;
84
85        let mut check_inv_delta_p_1 = Ok(());
86        let mut check_inv_delta_t_1 = Ok(());
87
88        rayon::join(
89            || {
90                check_inv_delta_p_1 = out
91                    .inv_delta_p_1
92                    .par_iter()
93                    .map(|x| x.check())
94                    .collect::<Result<_, _>>();
95            },
96            || {
97                check_inv_delta_t_1 = out
98                    .inv_delta_t_1
99                    .par_iter()
100                    .map(|x| x.check())
101                    .collect::<Result<_, _>>();
102            },
103        );
104
105        check_inv_delta_p_1?;
106        check_inv_delta_t_1?;
107        Ok(out)
108    }
109}
110
111impl Hashable for RawCRSElements {
112    /// Hash these elements, producing a succinct digest.
113    fn hash(&self) -> ContributionHash {
114        let mut hasher = GroupHasher::new(b"PC$:crs_elmnts2");
115        hasher.eat_g1(&self.delta_1);
116        hasher.eat_g2(&self.delta_2);
117
118        hasher.eat_usize(self.inv_delta_p_1.len());
119        for v in &self.inv_delta_p_1 {
120            hasher.eat_g1(v);
121        }
122
123        hasher.eat_usize(self.inv_delta_t_1.len());
124        for v in &self.inv_delta_t_1 {
125            hasher.eat_g1(v);
126        }
127
128        ContributionHash(hasher.finalize_bytes())
129    }
130}
131
132/// The CRS elements we produce in phase 2.
133///
134/// When combined with the elements of phase 1, the entire CRS will be present.
135#[derive(Clone, Debug, PartialEq)]
136pub struct CRSElements {
137    pub(crate) raw: RawCRSElements,
138}
139
140impl Hashable for CRSElements {
141    fn hash(&self) -> ContributionHash {
142        self.raw.hash()
143    }
144}
145
146impl CRSElements {
147    // TODO: Remove this when no longer needed for testing in summonerd
148    pub(crate) fn dummy_root(degree: usize) -> Self {
149        Self {
150            raw: RawCRSElements {
151                delta_1: G1::generator(),
152                delta_2: G2::generator(),
153                inv_delta_p_1: vec![G1::generator(); degree],
154                inv_delta_t_1: vec![G1::generator(); degree],
155            },
156        }
157    }
158}
159
160/// Represents a raw, unvalidatedontribution.
161#[derive(Clone, Debug)]
162pub struct RawContribution {
163    pub parent: ContributionHash,
164    pub new_elements: RawCRSElements,
165    pub(crate) linking_proof: dlog::Proof,
166}
167
168impl RawContribution {
169    /// Check the internal integrity of this contribution, potentially producing
170    /// a valid one.
171    pub fn validate<R: CryptoRngCore>(
172        self,
173        rng: &mut R,
174        root: &CRSElements,
175    ) -> Option<Contribution> {
176        self.new_elements
177            .validate(rng, root)
178            .map(|new_elements| Contribution {
179                parent: self.parent,
180                new_elements,
181                linking_proof: self.linking_proof,
182            })
183    }
184
185    /// Skip validation, and perform a conversion anyways.
186    ///
187    /// Can be useful when parsing data that's known to be good.
188    pub(crate) fn assume_valid(self) -> Contribution {
189        Contribution {
190            parent: self.parent,
191            new_elements: self.new_elements.assume_valid(),
192            linking_proof: self.linking_proof,
193        }
194    }
195}
196
197impl Hashable for RawContribution {
198    fn hash(&self) -> ContributionHash {
199        let mut hasher = GroupHasher::new(b"PC$:contrbution2");
200        hasher.eat_bytes(self.parent.as_ref());
201        hasher.eat_bytes(self.new_elements.hash().as_ref());
202        hasher.eat_bytes(self.linking_proof.hash().as_ref());
203
204        ContributionHash(hasher.finalize_bytes())
205    }
206}
207
208impl From<Contribution> for RawContribution {
209    fn from(value: Contribution) -> Self {
210        Self {
211            parent: value.parent,
212            new_elements: value.new_elements.raw,
213            linking_proof: value.linking_proof,
214        }
215    }
216}
217
218/// Represents a contribution to phase2 of the ceremony.
219///
220/// This contribution is linked to the previous contribution it builds upon.
221///
222/// The contribution contains new CRS elements, and a proof linking these elements
223/// to those of the parent contribution.
224#[derive(Clone, Debug)]
225pub struct Contribution {
226    pub parent: ContributionHash,
227    pub new_elements: CRSElements,
228    pub(crate) linking_proof: dlog::Proof,
229}
230
231impl Hashable for Contribution {
232    fn hash(&self) -> ContributionHash {
233        RawContribution::from(self.to_owned()).hash()
234    }
235}
236
237impl Contribution {
238    /// Make a new contribution, over the previous CRS elements.
239    ///
240    /// We also need a hash of the parent contribution we're building on.
241    pub fn make<R: CryptoRngCore>(
242        rng: &mut R,
243        parent: ContributionHash,
244        old: &CRSElements,
245    ) -> Self {
246        let delta = F::rand(rng);
247        // e.w. negligible probability this will panic (1 / 2^256)
248        let delta_inv = delta.inverse().expect("unable to inverse delta");
249
250        let mut new = old.clone();
251        new.raw.delta_1 *= delta;
252        new.raw.delta_2 *= delta;
253        for v in &mut new.raw.inv_delta_p_1 {
254            *v *= delta_inv;
255        }
256        for v in &mut new.raw.inv_delta_t_1 {
257            *v *= delta_inv;
258        }
259
260        let linking_proof = dlog::prove(
261            rng,
262            b"phase2 delta proof",
263            dlog::Statement {
264                result: new.raw.delta_1,
265                base: old.raw.delta_1,
266            },
267            dlog::Witness { dlog: delta },
268        );
269
270        Contribution {
271            parent,
272            new_elements: new,
273            linking_proof,
274        }
275    }
276
277    /// Verify that this contribution is linked to a previous list of elements.
278    #[must_use]
279    pub fn is_linked_to(&self, parent: &CRSElements) -> bool {
280        // 1. Check that the sizes match between the two elements.
281        if self.new_elements.raw.inv_delta_p_1.len() != parent.raw.inv_delta_p_1.len()
282            || self.new_elements.raw.inv_delta_t_1.len() != parent.raw.inv_delta_t_1.len()
283        {
284            return false;
285        }
286        // 2. Check that the linking proof verifies
287        if !dlog::verify(
288            b"phase2 delta proof",
289            dlog::Statement {
290                result: self.new_elements.raw.delta_1,
291                base: parent.raw.delta_1,
292            },
293            &self.linking_proof,
294        ) {
295            return false;
296        }
297        true
298    }
299}
300
301/// A dummy struct to implement the phase trait.
302#[derive(Clone, Debug, Default)]
303struct Phase2;
304
305impl Phase for Phase2 {
306    type CRSElements = CRSElements;
307
308    type RawContribution = RawContribution;
309
310    type Contribution = Contribution;
311
312    fn parent_hash(contribution: &Self::RawContribution) -> ContributionHash {
313        contribution.parent
314    }
315
316    fn elements(contribution: &Self::Contribution) -> &Self::CRSElements {
317        &contribution.new_elements
318    }
319
320    fn validate(
321        root: &Self::CRSElements,
322        contribution: &Self::RawContribution,
323    ) -> Option<Self::Contribution> {
324        contribution.to_owned().validate(&mut OsRng, root)
325    }
326
327    fn is_linked_to(contribution: &Self::Contribution, elements: &Self::CRSElements) -> bool {
328        contribution.is_linked_to(elements)
329    }
330}
331
332#[cfg(test)]
333mod test {
334    use super::*;
335
336    use crate::single::log::CONTRIBUTION_HASH_SIZE;
337
338    use rand_core::OsRng;
339
340    fn make_crs(delta: F, delta_inv: F) -> (CRSElements, RawCRSElements) {
341        let x = F::rand(&mut OsRng);
342
343        let root = CRSElements {
344            raw: RawCRSElements {
345                delta_1: G1::generator(),
346                delta_2: G2::generator(),
347                inv_delta_p_1: vec![G1::generator() * x],
348                inv_delta_t_1: vec![G1::generator() * (x * x)],
349            },
350        };
351
352        let new = RawCRSElements {
353            delta_1: root.raw.delta_1 * delta,
354            delta_2: root.raw.delta_2 * delta,
355            inv_delta_p_1: root
356                .raw
357                .inv_delta_p_1
358                .iter()
359                .map(|&x| x * delta_inv)
360                .collect(),
361            inv_delta_t_1: root
362                .raw
363                .inv_delta_t_1
364                .iter()
365                .map(|&x| x * delta_inv)
366                .collect(),
367        };
368
369        (root, new)
370    }
371
372    fn non_trivial_crs() -> (CRSElements, RawCRSElements) {
373        let delta = F::rand(&mut OsRng);
374        // Won't panic e.w. negligible probability
375        let delta_inv = delta.inverse().expect("unable to inverse delta");
376
377        make_crs(delta, delta_inv)
378    }
379
380    #[test]
381    fn test_nontrivial_crs_is_valid() {
382        let (root, crs) = non_trivial_crs();
383        assert!(crs.validate(&mut OsRng, &root).is_some());
384    }
385
386    #[test]
387    fn test_changing_delta_makes_crs_invalid() {
388        let (root, mut crs) = non_trivial_crs();
389        crs.delta_1 = G1::generator();
390        crs.delta_2 = G2::generator();
391        assert!(crs.validate(&mut OsRng, &root).is_none());
392    }
393
394    #[test]
395    fn test_different_deltas_makes_crs_invalid() {
396        let (root, mut crs) = non_trivial_crs();
397        crs.delta_1 = G1::generator();
398        assert!(crs.validate(&mut OsRng, &root).is_none());
399    }
400
401    #[test]
402    fn test_different_length_from_root_is_invalid_crs() {
403        let (root, mut crs) = non_trivial_crs();
404        crs.inv_delta_p_1.clear();
405        crs.inv_delta_t_1.clear();
406        assert!(crs.validate(&mut OsRng, &root).is_none());
407    }
408
409    #[test]
410    fn test_setting_zero_elements_makes_crs_invalid() {
411        let (root, crs) = make_crs(F::zero(), F::zero());
412        assert!(crs.validate(&mut OsRng, &root).is_none());
413    }
414
415    #[test]
416    fn test_not_inverting_delta_makes_crs_invalid() {
417        let delta = F::rand(&mut OsRng);
418        let (root, crs) = make_crs(delta, delta);
419        assert!(crs.validate(&mut OsRng, &root).is_none());
420    }
421
422    #[test]
423    fn test_contribution_produces_valid_crs() {
424        let (root, start) = non_trivial_crs();
425        let start = start
426            .validate(&mut OsRng, &root)
427            .expect("unable to validate start");
428        let contribution = Contribution::make(
429            &mut OsRng,
430            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
431            &start,
432        );
433        assert!(contribution
434            .new_elements
435            .raw
436            .validate(&mut OsRng, &root)
437            .is_some());
438    }
439
440    #[test]
441    fn test_can_calculate_contribution_hash() {
442        let (root, start) = non_trivial_crs();
443        let start = start
444            .validate(&mut OsRng, &root)
445            .expect("unable to validate start");
446        let contribution = Contribution::make(
447            &mut OsRng,
448            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
449            &start,
450        );
451        assert_ne!(contribution.hash(), contribution.parent);
452    }
453
454    #[test]
455    fn test_contribution_is_linked_to_parent() {
456        let (root, start) = non_trivial_crs();
457        let start = start
458            .validate(&mut OsRng, &root)
459            .expect("unable to validate start");
460        let contribution = Contribution::make(
461            &mut OsRng,
462            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
463            &start,
464        );
465        assert!(contribution.is_linked_to(&start));
466    }
467
468    #[test]
469    fn test_contribution_is_not_linked_to_itself() {
470        let (root, start) = non_trivial_crs();
471        let start = start
472            .validate(&mut OsRng, &root)
473            .expect("unable to validate start");
474        let contribution = Contribution::make(
475            &mut OsRng,
476            ContributionHash([0u8; CONTRIBUTION_HASH_SIZE]),
477            &start,
478        );
479        assert!(!contribution.is_linked_to(&contribution.new_elements));
480    }
481}