penumbra_sdk_proof_setup/
all.rs

1//! A module for grouping several setup ceremonies into a single one.
2//!
3//! This also matches the coordination strategy we have for phase2,
4//! along with the corresponding protobufs.
5use std::array;
6
7use crate::parallel_utils::{flatten_results, transform, transform_parallel};
8use crate::single::group::GroupHasher;
9use crate::single::{
10    self, circuit_degree,
11    group::F,
12    log::{ContributionHash, Hashable},
13    DLogProof, ExtraTransitionInformation, LinkingProof, Phase1CRSElements, Phase1Contribution,
14    Phase1RawCRSElements, Phase1RawContribution, Phase2CRSElements, Phase2Contribution,
15    Phase2RawCRSElements, Phase2RawContribution,
16};
17use anyhow::{anyhow, Result};
18use ark_groth16::ProvingKey;
19use ark_relations::r1cs::ConstraintMatrices;
20use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
21use decaf377::Bls12_377;
22use penumbra_sdk_dex::{swap::proof::SwapCircuit, swap_claim::proof::SwapClaimCircuit};
23use penumbra_sdk_governance::DelegatorVoteCircuit;
24use penumbra_sdk_proof_params::generate_constraint_matrices;
25use penumbra_sdk_proto::tools::summoning::v1::{self as pb};
26use penumbra_sdk_shielded_pool::{
27    ConvertCircuit, NullifierDerivationCircuit, OutputCircuit, SpendCircuit,
28};
29
30use rand_core::OsRng;
31
32// Some helper functions since we have to use these seventeen billion times
33
34const SERIALIZATION_COMPRESSION: Compress = Compress::No;
35
36fn to_bytes<T: CanonicalSerialize>(t: &T) -> Result<Vec<u8>> {
37    let mut out = Vec::new();
38    t.serialize_with_mode(&mut out, SERIALIZATION_COMPRESSION)?;
39    Ok(out)
40}
41
42fn from_bytes<T: CanonicalDeserialize>(data: &[u8]) -> Result<T> {
43    Ok(T::deserialize_with_mode(
44        data,
45        SERIALIZATION_COMPRESSION,
46        Validate::Yes,
47    )?)
48}
49
50fn from_bytes_unchecked<T: CanonicalDeserialize>(data: &[u8]) -> Result<T> {
51    Ok(T::deserialize_with_mode(
52        data,
53        SERIALIZATION_COMPRESSION,
54        Validate::No,
55    )?)
56}
57
58pub const NUM_CIRCUITS: usize = 7;
59
60/// Generate all of the circuits as matrices.
61fn circuits() -> [ConstraintMatrices<F>; NUM_CIRCUITS] {
62    [
63        generate_constraint_matrices::<SpendCircuit>(),
64        generate_constraint_matrices::<OutputCircuit>(),
65        generate_constraint_matrices::<DelegatorVoteCircuit>(),
66        generate_constraint_matrices::<ConvertCircuit>(),
67        generate_constraint_matrices::<SwapCircuit>(),
68        generate_constraint_matrices::<SwapClaimCircuit>(),
69        generate_constraint_matrices::<NullifierDerivationCircuit>(),
70    ]
71}
72
73/// Holds all of the CRS elements for phase2 in one struct, before validation.
74#[derive(Clone, Debug)]
75pub struct Phase2RawCeremonyCRS([Phase2RawCRSElements; NUM_CIRCUITS]);
76
77impl Phase2RawCeremonyCRS {
78    /// Skip validation, performing the conversion anyways.
79    ///
80    /// Useful when parsing known good data.
81    pub fn assume_valid(self) -> Phase2CeremonyCRS {
82        match self.0 {
83            [x0, x1, x2, x3, x4, x5, x6] => Phase2CeremonyCRS([
84                x0.assume_valid(),
85                x1.assume_valid(),
86                x2.assume_valid(),
87                x3.assume_valid(),
88                x4.assume_valid(),
89                x5.assume_valid(),
90                x6.assume_valid(),
91            ]),
92        }
93    }
94
95    pub fn unchecked_from_protobuf(value: pb::CeremonyCrs) -> anyhow::Result<Self> {
96        Ok(Self([
97            from_bytes_unchecked::<Phase2RawCRSElements>(value.spend.as_slice())?,
98            from_bytes_unchecked::<Phase2RawCRSElements>(value.output.as_slice())?,
99            from_bytes_unchecked::<Phase2RawCRSElements>(value.delegator_vote.as_slice())?,
100            from_bytes_unchecked::<Phase2RawCRSElements>(value.undelegate_claim.as_slice())?,
101            from_bytes_unchecked::<Phase2RawCRSElements>(value.swap.as_slice())?,
102            from_bytes_unchecked::<Phase2RawCRSElements>(value.swap_claim.as_slice())?,
103            from_bytes_unchecked::<Phase2RawCRSElements>(value.nullifer_derivation_crs.as_slice())?,
104        ]))
105    }
106}
107
108impl TryInto<pb::CeremonyCrs> for Phase2RawCeremonyCRS {
109    type Error = anyhow::Error;
110
111    fn try_into(self) -> Result<pb::CeremonyCrs> {
112        Ok(pb::CeremonyCrs {
113            spend: to_bytes(&self.0[0])?,
114            output: to_bytes(&self.0[1])?,
115            delegator_vote: to_bytes(&self.0[2])?,
116            undelegate_claim: to_bytes(&self.0[3])?,
117            swap: to_bytes(&self.0[4])?,
118            swap_claim: to_bytes(&self.0[5])?,
119            nullifer_derivation_crs: to_bytes(&self.0[6])?,
120        })
121    }
122}
123
124impl TryFrom<pb::CeremonyCrs> for Phase2RawCeremonyCRS {
125    type Error = anyhow::Error;
126
127    fn try_from(value: pb::CeremonyCrs) -> std::result::Result<Self, Self::Error> {
128        Ok(Self([
129            from_bytes::<Phase2RawCRSElements>(value.spend.as_slice())?,
130            from_bytes::<Phase2RawCRSElements>(value.output.as_slice())?,
131            from_bytes::<Phase2RawCRSElements>(value.delegator_vote.as_slice())?,
132            from_bytes::<Phase2RawCRSElements>(value.undelegate_claim.as_slice())?,
133            from_bytes::<Phase2RawCRSElements>(value.swap.as_slice())?,
134            from_bytes::<Phase2RawCRSElements>(value.swap_claim.as_slice())?,
135            from_bytes::<Phase2RawCRSElements>(value.nullifer_derivation_crs.as_slice())?,
136        ]))
137    }
138}
139
140/// Holds all of the CRS elements for phase2 in one struct.
141#[derive(Clone, Debug)]
142pub struct Phase2CeremonyCRS([Phase2CRSElements; NUM_CIRCUITS]);
143
144impl From<Phase2CeremonyCRS> for Phase2RawCeremonyCRS {
145    fn from(value: Phase2CeremonyCRS) -> Self {
146        Self(array::from_fn(|i| value.0[i].raw.clone()))
147    }
148}
149
150impl TryFrom<Phase2CeremonyCRS> for pb::CeremonyCrs {
151    type Error = anyhow::Error;
152
153    fn try_from(data: Phase2CeremonyCRS) -> Result<pb::CeremonyCrs> {
154        Phase2RawCeremonyCRS::from(data).try_into()
155    }
156}
157
158impl Phase2CeremonyCRS {
159    pub fn root() -> Result<Self> {
160        let [c0, c1, c2, c3, c4, c5, c6] = circuits();
161        Ok(Self([
162            Phase2CRSElements::dummy_root(circuit_degree(&c0)?),
163            Phase2CRSElements::dummy_root(circuit_degree(&c1)?),
164            Phase2CRSElements::dummy_root(circuit_degree(&c2)?),
165            Phase2CRSElements::dummy_root(circuit_degree(&c3)?),
166            Phase2CRSElements::dummy_root(circuit_degree(&c4)?),
167            Phase2CRSElements::dummy_root(circuit_degree(&c5)?),
168            Phase2CRSElements::dummy_root(circuit_degree(&c6)?),
169        ]))
170    }
171}
172
173/// All phase2 contributions, before they've been validated.
174#[derive(Clone, Debug)]
175pub struct Phase2RawCeremonyContribution([Phase2RawContribution; NUM_CIRCUITS]);
176
177impl TryInto<pb::participate_request::Contribution> for Phase2RawCeremonyContribution {
178    type Error = anyhow::Error;
179
180    fn try_into(self) -> Result<pb::participate_request::Contribution> {
181        Ok(pb::participate_request::Contribution {
182            updated: Some(pb::CeremonyCrs {
183                spend: to_bytes(&self.0[0].new_elements)?,
184                output: to_bytes(&self.0[1].new_elements)?,
185                delegator_vote: to_bytes(&self.0[2].new_elements)?,
186                undelegate_claim: to_bytes(&self.0[3].new_elements)?,
187                swap: to_bytes(&self.0[4].new_elements)?,
188                swap_claim: to_bytes(&self.0[5].new_elements)?,
189                nullifer_derivation_crs: to_bytes(&self.0[6].new_elements)?,
190            }),
191            update_proofs: Some(pb::CeremonyLinkingProof {
192                spend: to_bytes(&self.0[0].linking_proof)?,
193                output: to_bytes(&self.0[1].linking_proof)?,
194                delegator_vote: to_bytes(&self.0[2].linking_proof)?,
195                undelegate_claim: to_bytes(&self.0[3].linking_proof)?,
196                swap: to_bytes(&self.0[4].linking_proof)?,
197                swap_claim: to_bytes(&self.0[5].linking_proof)?,
198                nullifer_derivation_crs: to_bytes(&self.0[6].linking_proof)?,
199            }),
200            parent_hashes: Some(pb::CeremonyParentHashes {
201                spend: self.0[0].parent.0.to_vec(),
202                output: self.0[1].parent.0.to_vec(),
203                delegator_vote: self.0[2].parent.0.to_vec(),
204                undelegate_claim: self.0[3].parent.0.to_vec(),
205                swap: self.0[4].parent.0.to_vec(),
206                swap_claim: self.0[5].parent.0.to_vec(),
207                nullifer_derivation_crs: self.0[6].parent.0.to_vec(),
208            }),
209        })
210    }
211}
212
213impl TryFrom<pb::participate_request::Contribution> for Phase2RawCeremonyContribution {
214    type Error = anyhow::Error;
215
216    fn try_from(value: pb::participate_request::Contribution) -> Result<Self> {
217        let (parent_hashes, updated, update_proofs) = match value {
218            pb::participate_request::Contribution {
219                parent_hashes: Some(x0),
220                updated: Some(x1),
221                update_proofs: Some(x2),
222            } => (x0, x1, x2),
223            _ => anyhow::bail!("missing contribution data"),
224        };
225        let data = [
226            (parent_hashes.spend, updated.spend, update_proofs.spend),
227            (parent_hashes.output, updated.output, update_proofs.output),
228            (
229                parent_hashes.delegator_vote,
230                updated.delegator_vote,
231                update_proofs.delegator_vote,
232            ),
233            (
234                parent_hashes.undelegate_claim,
235                updated.undelegate_claim,
236                update_proofs.undelegate_claim,
237            ),
238            (parent_hashes.swap, updated.swap, update_proofs.swap),
239            (
240                parent_hashes.swap_claim,
241                updated.swap_claim,
242                update_proofs.swap_claim,
243            ),
244            (
245                parent_hashes.nullifer_derivation_crs,
246                updated.nullifer_derivation_crs,
247                update_proofs.nullifer_derivation_crs,
248            ),
249        ];
250        let out = transform_parallel(data, |(parent_hash, updated, update_proof)| {
251            Ok::<_, anyhow::Error>(Phase2RawContribution {
252                parent: ContributionHash::try_from(parent_hash.as_slice())?,
253                new_elements: Phase2RawCRSElements::checked_deserialize_parallel(
254                    SERIALIZATION_COMPRESSION,
255                    updated.as_slice(),
256                )?,
257                linking_proof: from_bytes::<DLogProof>(update_proof.as_slice())?,
258            })
259        });
260        Ok(Self(flatten_results(out)?))
261    }
262}
263
264impl Phase2RawCeremonyContribution {
265    /// Validate that this contribution is internally consistent.
266    ///
267    /// This doesn't check that it's connected to the right parent though, which is an additional
268    /// step you want to do.
269    pub fn validate(self, root: &Phase2CeremonyCRS) -> Option<Phase2CeremonyContribution> {
270        let data: [_; 7] = self
271            .0
272            .into_iter()
273            .zip(root.0.iter())
274            .collect::<Vec<_>>()
275            .try_into()
276            .expect("iterator should have the same size");
277        let out = transform_parallel(data, |(x, root)| {
278            x.validate(&mut OsRng, root)
279                .ok_or(anyhow!("failed to validate"))
280        });
281        Some(Phase2CeremonyContribution(flatten_results(out).ok()?))
282    }
283
284    /// Skip validation, performing the conversion anyways.
285    ///
286    /// Useful when parsing known good data.
287    pub fn assume_valid(self) -> Phase2CeremonyContribution {
288        // This avoids a copy, and will break if we change the size:
289        Phase2CeremonyContribution(transform(self.0, |x| x.assume_valid()))
290    }
291
292    pub fn unchecked_from_protobuf(value: pb::participate_request::Contribution) -> Result<Self> {
293        let (parent_hashes, updated, update_proofs) = match value {
294            pb::participate_request::Contribution {
295                parent_hashes: Some(x0),
296                updated: Some(x1),
297                update_proofs: Some(x2),
298            } => (x0, x1, x2),
299            _ => anyhow::bail!("missing contribution data"),
300        };
301        let data = [
302            (parent_hashes.spend, updated.spend, update_proofs.spend),
303            (parent_hashes.output, updated.output, update_proofs.output),
304            (
305                parent_hashes.delegator_vote,
306                updated.delegator_vote,
307                update_proofs.delegator_vote,
308            ),
309            (
310                parent_hashes.undelegate_claim,
311                updated.undelegate_claim,
312                update_proofs.undelegate_claim,
313            ),
314            (parent_hashes.swap, updated.swap, update_proofs.swap),
315            (
316                parent_hashes.swap_claim,
317                updated.swap_claim,
318                update_proofs.swap_claim,
319            ),
320            (
321                parent_hashes.nullifer_derivation_crs,
322                updated.nullifer_derivation_crs,
323                update_proofs.nullifer_derivation_crs,
324            ),
325        ];
326        let out = transform(data, |(parent_hash, updated, update_proof)| {
327            Ok::<_, anyhow::Error>(Phase2RawContribution {
328                parent: ContributionHash::try_from(parent_hash.as_slice())?,
329                new_elements: from_bytes_unchecked::<Phase2RawCRSElements>(updated.as_slice())?,
330                linking_proof: from_bytes_unchecked::<DLogProof>(update_proof.as_slice())?,
331            })
332        });
333        Ok(Self(flatten_results(out)?))
334    }
335}
336
337/// Holds all of the phase2 contributions in a single package.
338#[derive(Clone, Debug)]
339pub struct Phase2CeremonyContribution([Phase2Contribution; NUM_CIRCUITS]);
340
341impl From<Phase2CeremonyContribution> for Phase2RawCeremonyContribution {
342    fn from(value: Phase2CeremonyContribution) -> Self {
343        let out: [Phase2RawContribution; NUM_CIRCUITS] =
344            array::from_fn(|i| Phase2RawContribution::from(value.0[i].clone()));
345        Self(out)
346    }
347}
348
349impl TryFrom<Phase2CeremonyContribution> for pb::participate_request::Contribution {
350    type Error = anyhow::Error;
351
352    fn try_from(data: Phase2CeremonyContribution) -> Result<pb::participate_request::Contribution> {
353        Phase2RawCeremonyContribution::from(data).try_into()
354    }
355}
356
357impl Phase2CeremonyContribution {
358    /// Get the new elements contained in this contribution
359    pub fn new_elements(&self) -> Phase2CeremonyCRS {
360        Phase2CeremonyCRS(array::from_fn(|i| self.0[i].new_elements.clone()))
361    }
362
363    /// Check that this contribution is linked to some specific parent elements.
364    #[must_use]
365    pub fn is_linked_to(&self, parent: &Phase2CeremonyCRS) -> bool {
366        self.0
367            .iter()
368            .zip(parent.0.iter())
369            .all(|(x, y)| x.is_linked_to(y))
370    }
371
372    pub fn make(old: &Phase2CeremonyCRS) -> Self {
373        let data = [
374            &old.0[0], &old.0[1], &old.0[2], &old.0[3], &old.0[4], &old.0[5], &old.0[6],
375        ];
376        Self(transform_parallel(data, |old_i| {
377            Phase2Contribution::make(&mut OsRng, ContributionHash::dummy(), old_i)
378        }))
379    }
380}
381
382impl Hashable for Phase2CeremonyContribution {
383    fn hash(&self) -> ContributionHash {
384        let hashes = transform(self.0.clone(), |x| x.hash());
385        let mut hasher = GroupHasher::new(b"phase2contr");
386        for h in hashes {
387            hasher.eat_bytes(h.as_ref());
388        }
389        ContributionHash(hasher.finalize_bytes())
390    }
391}
392
393// TODO: Make the phase 1 and phase 2 functionality generic
394
395/// Holds all of the CRS elements for phase1 in one struct, before validation.
396#[derive(Clone, Debug)]
397pub struct Phase1RawCeremonyCRS([Phase1RawCRSElements; NUM_CIRCUITS]);
398
399impl Phase1RawCeremonyCRS {
400    /// Skip validation, performing the conversion anyways.
401    ///
402    /// Useful when parsing known good data.
403    pub fn assume_valid(self) -> Phase1CeremonyCRS {
404        match self.0 {
405            [x0, x1, x2, x3, x4, x5, x6] => Phase1CeremonyCRS([
406                x0.assume_valid(),
407                x1.assume_valid(),
408                x2.assume_valid(),
409                x3.assume_valid(),
410                x4.assume_valid(),
411                x5.assume_valid(),
412                x6.assume_valid(),
413            ]),
414        }
415    }
416
417    /// This should only be used when the data is known to be from a trusted source.
418    pub fn unchecked_from_protobuf(value: pb::CeremonyCrs) -> anyhow::Result<Self> {
419        Ok(Self([
420            from_bytes_unchecked::<Phase1RawCRSElements>(value.spend.as_slice())?,
421            from_bytes_unchecked::<Phase1RawCRSElements>(value.output.as_slice())?,
422            from_bytes_unchecked::<Phase1RawCRSElements>(value.delegator_vote.as_slice())?,
423            from_bytes_unchecked::<Phase1RawCRSElements>(value.undelegate_claim.as_slice())?,
424            from_bytes_unchecked::<Phase1RawCRSElements>(value.swap.as_slice())?,
425            from_bytes_unchecked::<Phase1RawCRSElements>(value.swap_claim.as_slice())?,
426            from_bytes_unchecked::<Phase1RawCRSElements>(value.nullifer_derivation_crs.as_slice())?,
427        ]))
428    }
429}
430
431impl TryInto<pb::CeremonyCrs> for Phase1RawCeremonyCRS {
432    type Error = anyhow::Error;
433
434    fn try_into(self) -> Result<pb::CeremonyCrs> {
435        Ok(pb::CeremonyCrs {
436            spend: to_bytes(&self.0[0])?,
437            output: to_bytes(&self.0[1])?,
438            delegator_vote: to_bytes(&self.0[2])?,
439            undelegate_claim: to_bytes(&self.0[3])?,
440            swap: to_bytes(&self.0[4])?,
441            swap_claim: to_bytes(&self.0[5])?,
442            nullifer_derivation_crs: to_bytes(&self.0[6])?,
443        })
444    }
445}
446
447impl TryFrom<pb::CeremonyCrs> for Phase1RawCeremonyCRS {
448    type Error = anyhow::Error;
449
450    fn try_from(value: pb::CeremonyCrs) -> std::result::Result<Self, Self::Error> {
451        Ok(Self([
452            from_bytes::<Phase1RawCRSElements>(value.spend.as_slice())?,
453            from_bytes::<Phase1RawCRSElements>(value.output.as_slice())?,
454            from_bytes::<Phase1RawCRSElements>(value.delegator_vote.as_slice())?,
455            from_bytes::<Phase1RawCRSElements>(value.undelegate_claim.as_slice())?,
456            from_bytes::<Phase1RawCRSElements>(value.swap.as_slice())?,
457            from_bytes::<Phase1RawCRSElements>(value.swap_claim.as_slice())?,
458            from_bytes::<Phase1RawCRSElements>(value.nullifer_derivation_crs.as_slice())?,
459        ]))
460    }
461}
462
463/// Holds all of the CRS elements for phase1 in one struct.
464#[derive(Clone, Debug, PartialEq)]
465pub struct Phase1CeremonyCRS([Phase1CRSElements; NUM_CIRCUITS]);
466
467impl From<Phase1CeremonyCRS> for Phase1RawCeremonyCRS {
468    fn from(value: Phase1CeremonyCRS) -> Self {
469        Self(array::from_fn(|i| value.0[i].raw.clone()))
470    }
471}
472
473impl TryFrom<Phase1CeremonyCRS> for pb::CeremonyCrs {
474    type Error = anyhow::Error;
475
476    fn try_from(data: Phase1CeremonyCRS) -> Result<pb::CeremonyCrs> {
477        Phase1RawCeremonyCRS::from(data).try_into()
478    }
479}
480
481impl Phase1CeremonyCRS {
482    pub fn root() -> Result<Self> {
483        let [c0, c1, c2, c3, c4, c5, c6] = circuits();
484        Ok(Self([
485            Phase1CRSElements::root(circuit_degree(&c0)?),
486            Phase1CRSElements::root(circuit_degree(&c1)?),
487            Phase1CRSElements::root(circuit_degree(&c2)?),
488            Phase1CRSElements::root(circuit_degree(&c3)?),
489            Phase1CRSElements::root(circuit_degree(&c4)?),
490            Phase1CRSElements::root(circuit_degree(&c5)?),
491            Phase1CRSElements::root(circuit_degree(&c6)?),
492        ]))
493    }
494}
495
496/// All phase1 contributions, before they've been validated.
497#[derive(Clone, Debug)]
498pub struct Phase1RawCeremonyContribution([Phase1RawContribution; NUM_CIRCUITS]);
499
500impl TryInto<pb::participate_request::Contribution> for Phase1RawCeremonyContribution {
501    type Error = anyhow::Error;
502
503    fn try_into(self) -> Result<pb::participate_request::Contribution> {
504        Ok(pb::participate_request::Contribution {
505            updated: Some(pb::CeremonyCrs {
506                spend: to_bytes(&self.0[0].new_elements)?,
507                output: to_bytes(&self.0[1].new_elements)?,
508                delegator_vote: to_bytes(&self.0[2].new_elements)?,
509                undelegate_claim: to_bytes(&self.0[3].new_elements)?,
510                swap: to_bytes(&self.0[4].new_elements)?,
511                swap_claim: to_bytes(&self.0[5].new_elements)?,
512                nullifer_derivation_crs: to_bytes(&self.0[6].new_elements)?,
513            }),
514            update_proofs: Some(pb::CeremonyLinkingProof {
515                spend: to_bytes(&self.0[0].linking_proof)?,
516                output: to_bytes(&self.0[1].linking_proof)?,
517                delegator_vote: to_bytes(&self.0[2].linking_proof)?,
518                undelegate_claim: to_bytes(&self.0[3].linking_proof)?,
519                swap: to_bytes(&self.0[4].linking_proof)?,
520                swap_claim: to_bytes(&self.0[5].linking_proof)?,
521                nullifer_derivation_crs: to_bytes(&self.0[6].linking_proof)?,
522            }),
523            parent_hashes: Some(pb::CeremonyParentHashes {
524                spend: self.0[0].parent.0.to_vec(),
525                output: self.0[1].parent.0.to_vec(),
526                delegator_vote: self.0[2].parent.0.to_vec(),
527                undelegate_claim: self.0[3].parent.0.to_vec(),
528                swap: self.0[4].parent.0.to_vec(),
529                swap_claim: self.0[5].parent.0.to_vec(),
530                nullifer_derivation_crs: self.0[6].parent.0.to_vec(),
531            }),
532        })
533    }
534}
535
536impl TryFrom<pb::participate_request::Contribution> for Phase1RawCeremonyContribution {
537    type Error = anyhow::Error;
538
539    fn try_from(value: pb::participate_request::Contribution) -> Result<Self> {
540        let (parent_hashes, updated, update_proofs) = match value {
541            pb::participate_request::Contribution {
542                parent_hashes: Some(x0),
543                updated: Some(x1),
544                update_proofs: Some(x2),
545            } => (x0, x1, x2),
546            _ => anyhow::bail!("missing contribution data"),
547        };
548        let data = [
549            (parent_hashes.spend, updated.spend, update_proofs.spend),
550            (parent_hashes.output, updated.output, update_proofs.output),
551            (
552                parent_hashes.delegator_vote,
553                updated.delegator_vote,
554                update_proofs.delegator_vote,
555            ),
556            (
557                parent_hashes.undelegate_claim,
558                updated.undelegate_claim,
559                update_proofs.undelegate_claim,
560            ),
561            (parent_hashes.swap, updated.swap, update_proofs.swap),
562            (
563                parent_hashes.swap_claim,
564                updated.swap_claim,
565                update_proofs.swap_claim,
566            ),
567            (
568                parent_hashes.nullifer_derivation_crs,
569                updated.nullifer_derivation_crs,
570                update_proofs.nullifer_derivation_crs,
571            ),
572        ];
573        let out = transform_parallel(data, |(parent_hash, updated, update_proof)| {
574            Ok::<_, anyhow::Error>(Phase1RawContribution {
575                parent: ContributionHash::try_from(parent_hash.as_slice())?,
576                new_elements: Phase1RawCRSElements::checked_deserialize_parallel(
577                    SERIALIZATION_COMPRESSION,
578                    updated.as_slice(),
579                )?,
580                linking_proof: from_bytes::<LinkingProof>(update_proof.as_slice())?,
581            })
582        });
583        Ok(Self(flatten_results(out)?))
584    }
585}
586
587impl Phase1RawCeremonyContribution {
588    /// Validate that this contribution is internally consistent.
589    ///
590    /// This doesn't check that it's connected to the right parent though, which is an additional
591    /// step you want to do.
592    pub fn validate(self) -> Option<Phase1CeremonyContribution> {
593        let out = transform_parallel(self.0, |x| {
594            x.validate().ok_or(anyhow!("failed to validate"))
595        });
596        Some(Phase1CeremonyContribution(flatten_results(out).ok()?))
597    }
598
599    /// Skip validation, performing the conversion anyways.
600    ///
601    /// Useful when parsing known good data.
602    pub fn assume_valid(self) -> Phase1CeremonyContribution {
603        // This avoids a copy, and will break if we change the size:
604        match self.0 {
605            [x0, x1, x2, x3, x4, x5, x6] => Phase1CeremonyContribution([
606                x0.assume_valid(),
607                x1.assume_valid(),
608                x2.assume_valid(),
609                x3.assume_valid(),
610                x4.assume_valid(),
611                x5.assume_valid(),
612                x6.assume_valid(),
613            ]),
614        }
615    }
616
617    pub fn unchecked_from_protobuf(value: pb::participate_request::Contribution) -> Result<Self> {
618        let (parent_hashes, updated, update_proofs) = match value {
619            pb::participate_request::Contribution {
620                parent_hashes: Some(x0),
621                updated: Some(x1),
622                update_proofs: Some(x2),
623            } => (x0, x1, x2),
624            _ => anyhow::bail!("missing contribution data"),
625        };
626        let data = [
627            (parent_hashes.spend, updated.spend, update_proofs.spend),
628            (parent_hashes.output, updated.output, update_proofs.output),
629            (
630                parent_hashes.delegator_vote,
631                updated.delegator_vote,
632                update_proofs.delegator_vote,
633            ),
634            (
635                parent_hashes.undelegate_claim,
636                updated.undelegate_claim,
637                update_proofs.undelegate_claim,
638            ),
639            (parent_hashes.swap, updated.swap, update_proofs.swap),
640            (
641                parent_hashes.swap_claim,
642                updated.swap_claim,
643                update_proofs.swap_claim,
644            ),
645            (
646                parent_hashes.nullifer_derivation_crs,
647                updated.nullifer_derivation_crs,
648                update_proofs.nullifer_derivation_crs,
649            ),
650        ];
651        let out = transform(data, |(parent_hash, updated, update_proof)| {
652            Ok::<_, anyhow::Error>(Phase1RawContribution {
653                parent: ContributionHash::try_from(parent_hash.as_slice())?,
654                new_elements: from_bytes_unchecked::<Phase1RawCRSElements>(updated.as_slice())?,
655                linking_proof: from_bytes_unchecked::<LinkingProof>(update_proof.as_slice())?,
656            })
657        });
658        Ok(Self(flatten_results(out)?))
659    }
660}
661
662/// Holds all of the phase1 contributions in a single package.
663#[derive(Clone, Debug)]
664pub struct Phase1CeremonyContribution([Phase1Contribution; NUM_CIRCUITS]);
665
666impl From<Phase1CeremonyContribution> for Phase1RawCeremonyContribution {
667    fn from(value: Phase1CeremonyContribution) -> Self {
668        let out: [Phase1RawContribution; NUM_CIRCUITS] =
669            array::from_fn(|i| Phase1RawContribution::from(value.0[i].clone()));
670        Self(out)
671    }
672}
673
674impl TryFrom<Phase1CeremonyContribution> for pb::participate_request::Contribution {
675    type Error = anyhow::Error;
676
677    fn try_from(data: Phase1CeremonyContribution) -> Result<pb::participate_request::Contribution> {
678        Phase1RawCeremonyContribution::from(data).try_into()
679    }
680}
681
682impl Phase1CeremonyContribution {
683    /// Get the new elements contained in this contribution
684    pub fn new_elements(&self) -> Phase1CeremonyCRS {
685        Phase1CeremonyCRS(array::from_fn(|i| self.0[i].new_elements.clone()))
686    }
687
688    /// Check that this contribution is linked to some specific parent elements.
689    #[must_use]
690    pub fn is_linked_to(&self, parent: &Phase1CeremonyCRS) -> bool {
691        self.0
692            .iter()
693            .zip(parent.0.iter())
694            .all(|(x, y)| x.is_linked_to(y))
695    }
696
697    pub fn make(old: &Phase1CeremonyCRS) -> Self {
698        let data = [
699            &old.0[0], &old.0[1], &old.0[2], &old.0[3], &old.0[4], &old.0[5], &old.0[6],
700        ];
701        Self(transform_parallel(data, |old_i| {
702            Phase1Contribution::make(&mut OsRng, ContributionHash::dummy(), old_i)
703        }))
704    }
705}
706
707impl Hashable for Phase1CeremonyContribution {
708    fn hash(&self) -> ContributionHash {
709        let hashes = transform(self.0.clone(), |x| x.hash());
710        let mut hasher = GroupHasher::new(b"phase1contr");
711        for h in hashes {
712            hasher.eat_bytes(h.as_ref());
713        }
714        ContributionHash(hasher.finalize_bytes())
715    }
716}
717
718#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)]
719pub struct AllExtraTransitionInformation([ExtraTransitionInformation; NUM_CIRCUITS]);
720
721impl AllExtraTransitionInformation {
722    pub fn to_bytes(&self) -> Result<Vec<u8>> {
723        to_bytes(self)
724    }
725
726    pub fn from_bytes(data: &[u8]) -> Result<Self> {
727        from_bytes_unchecked::<Self>(data)
728    }
729}
730
731/// Transition between phase1 and phase2, producing extra information to be saved.
732pub fn transition(
733    phase1: &Phase1CeremonyCRS,
734) -> Result<(AllExtraTransitionInformation, Phase2CeremonyCRS)> {
735    let circuits = circuits();
736    let indices = [0, 1, 2, 3, 4, 5, 6];
737    let [(e0, p0), (e1, p1), (e2, p2), (e3, p3), (e4, p4), (e5, p5), (e6, p6)] =
738        flatten_results(transform_parallel(indices, |i| {
739            single::transition(&phase1.0[i], &circuits[i])
740        }))?;
741    Ok((
742        AllExtraTransitionInformation([e0, e1, e2, e3, e4, e5, e6]),
743        Phase2CeremonyCRS([p0, p1, p2, p3, p4, p5, p6]),
744    ))
745}
746
747pub fn combine(
748    phase1out: &Phase1CeremonyCRS,
749    phase2out: &Phase2CeremonyCRS,
750    extra: &AllExtraTransitionInformation,
751) -> [ProvingKey<Bls12_377>; NUM_CIRCUITS] {
752    let circuits = circuits();
753    let indices = [0, 1, 2, 3, 4, 5, 6];
754    transform_parallel(indices, |i| {
755        single::combine(&circuits[i], &phase1out.0[i], &phase2out.0[i], &extra.0[i])
756    })
757}