penumbra_sdk_proof_setup/single/
phase2.rs1use 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#[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 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 if self.delta_1.is_zero() || self.delta_2.is_zero() {
37 return None;
38 }
39 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 pub(crate) fn assume_valid(self) -> CRSElements {
60 CRSElements { raw: self }
61 }
62
63 #[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 #[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 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#[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 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#[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 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 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#[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 pub fn make<R: CryptoRngCore>(
242 rng: &mut R,
243 parent: ContributionHash,
244 old: &CRSElements,
245 ) -> Self {
246 let delta = F::rand(rng);
247 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 #[must_use]
279 pub fn is_linked_to(&self, parent: &CRSElements) -> bool {
280 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 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#[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 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}