1use halo2_gadgets::poseidon::{
26 primitives::{self as poseidon, ConstantLength},
27 Hash as PoseidonHash, Pow5Chip as PoseidonChip,
28};
29use halo2_proofs::{
30 circuit::{AssignedCell, Layouter},
31 plonk,
32};
33use itertools::Itertools;
34use pasta_curves::pallas;
35
36pub fn share_commitment(
47 blind: pallas::Base,
48 c1_x: pallas::Base,
49 c2_x: pallas::Base,
50 c1_y: pallas::Base,
51 c2_y: pallas::Base,
52) -> pallas::Base {
53 poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<5>, 3, 2>::init()
54 .hash([blind, c1_x, c2_x, c1_y, c2_y])
55}
56
57pub fn shares_hash(
66 share_blinds: [pallas::Base; 16],
67 enc_share_c1_x: [pallas::Base; 16],
68 enc_share_c2_x: [pallas::Base; 16],
69 enc_share_c1_y: [pallas::Base; 16],
70 enc_share_c2_y: [pallas::Base; 16],
71) -> pallas::Base {
72 let comms: [pallas::Base; 16] = core::array::from_fn(|i| {
73 share_commitment(
74 share_blinds[i],
75 enc_share_c1_x[i],
76 enc_share_c2_x[i],
77 enc_share_c1_y[i],
78 enc_share_c2_y[i],
79 )
80 });
81 shares_hash_from_comms(comms)
82}
83
84pub(crate) fn hash_share_commitment_in_circuit(
94 chip: PoseidonChip<pallas::Base, 3, 2>,
95 mut layouter: impl Layouter<pallas::Base>,
96 blind: AssignedCell<pallas::Base, pallas::Base>,
97 enc_c1_x: AssignedCell<pallas::Base, pallas::Base>,
98 enc_c2_x: AssignedCell<pallas::Base, pallas::Base>,
99 enc_c1_y: AssignedCell<pallas::Base, pallas::Base>,
100 enc_c2_y: AssignedCell<pallas::Base, pallas::Base>,
101 index: usize,
102) -> Result<AssignedCell<pallas::Base, pallas::Base>, plonk::Error> {
103 let hasher =
104 PoseidonHash::<pallas::Base, _, poseidon::P128Pow5T3, ConstantLength<5>, 3, 2>::init(
105 chip,
106 layouter.namespace(|| format!("share_comm_{index} Poseidon init")),
107 )?;
108 hasher.hash(
109 layouter.namespace(|| {
110 format!("share_comm_{index} = Poseidon(blind, c1_x, c2_x, c1_y, c2_y)[{index}]")
111 }),
112 [blind, enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y],
113 )
114}
115
116pub(crate) fn compute_shares_hash_in_circuit(
139 poseidon_chip: impl Fn() -> PoseidonChip<pallas::Base, 3, 2>,
140 mut layouter: impl Layouter<pallas::Base>,
141 blinds: [AssignedCell<pallas::Base, pallas::Base>; 16],
142 enc_c1_x: [AssignedCell<pallas::Base, pallas::Base>; 16],
143 enc_c2_x: [AssignedCell<pallas::Base, pallas::Base>; 16],
144 enc_c1_y: [AssignedCell<pallas::Base, pallas::Base>; 16],
145 enc_c2_y: [AssignedCell<pallas::Base, pallas::Base>; 16],
146) -> Result<AssignedCell<pallas::Base, pallas::Base>, plonk::Error> {
147 let share_comms: [_; 16] = IntoIterator::into_iter(blinds)
148 .zip_eq(enc_c1_x)
149 .zip_eq(enc_c2_x)
150 .zip_eq(enc_c1_y)
151 .zip_eq(enc_c2_y)
152 .enumerate()
153 .map(|(i, ((((blind, c1x), c2x), c1y), c2y))| {
154 hash_share_commitment_in_circuit(
155 poseidon_chip(),
156 layouter.namespace(|| format!("share_comm_{i}")),
157 blind,
158 c1x,
159 c2x,
160 c1y,
161 c2y,
162 i,
163 )
164 })
165 .collect::<Result<Vec<_>, _>>()?
166 .try_into()
167 .expect("always 16 elements");
168
169 let hasher = PoseidonHash::<
171 pallas::Base,
172 _,
173 poseidon::P128Pow5T3,
174 ConstantLength<16>,
175 3, 2, >::init(
178 poseidon_chip(),
179 layouter.namespace(|| "shares_hash Poseidon init"),
180 )?;
181 hasher.hash(
182 layouter.namespace(|| "shares_hash = Poseidon(share_comms)"),
183 share_comms,
184 )
185}
186
187pub(crate) fn compute_shares_hash_from_comms_in_circuit(
200 poseidon_chip: PoseidonChip<pallas::Base, 3, 2>,
201 mut layouter: impl Layouter<pallas::Base>,
202 share_comms: [AssignedCell<pallas::Base, pallas::Base>; 16],
203) -> Result<AssignedCell<pallas::Base, pallas::Base>, plonk::Error> {
204 let hasher = PoseidonHash::<
205 pallas::Base,
206 _,
207 poseidon::P128Pow5T3,
208 ConstantLength<16>,
209 3, 2, >::init(
212 poseidon_chip,
213 layouter.namespace(|| "shares_hash Poseidon init"),
214 )?;
215 hasher.hash(
216 layouter.namespace(|| "shares_hash = Poseidon(share_comms)"),
217 share_comms,
218 )
219}
220
221pub fn shares_hash_from_comms(share_comms: [pallas::Base; 16]) -> pallas::Base {
225 poseidon::Hash::<_, poseidon::P128Pow5T3, ConstantLength<16>, 3, 2>::init().hash(share_comms)
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 use ff::{Field, PrimeField};
233 use halo2_gadgets::poseidon::Pow5Config as PoseidonConfig;
234 use halo2_proofs::{
235 circuit::{floor_planner, Value},
236 dev::MockProver,
237 plonk::{Advice, Column, ConstraintSystem, Fixed, Instance as InstanceColumn},
238 };
239 use rand::rngs::OsRng;
240
241 #[derive(Clone)]
246 struct TestConfig {
247 primary: Column<InstanceColumn>,
248 advice: Column<Advice>,
249 poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
250 }
251
252 impl TestConfig {
253 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self {
254 let primary = meta.instance_column();
255 meta.enable_equality(primary);
256
257 let advices: [Column<Advice>; 5] = core::array::from_fn(|_| meta.advice_column());
259 for col in &advices {
260 meta.enable_equality(*col);
261 }
262
263 let fixed: [Column<Fixed>; 6] = core::array::from_fn(|_| meta.fixed_column());
264 let constants = meta.fixed_column();
266 meta.enable_constant(constants);
267 let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
268 meta,
269 advices[1..4].try_into().unwrap(),
270 advices[4],
271 fixed[0..3].try_into().unwrap(),
272 fixed[3..6].try_into().unwrap(),
273 );
274
275 TestConfig {
276 primary,
277 advice: advices[0],
278 poseidon_config,
279 }
280 }
281
282 fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
283 PoseidonChip::construct(self.poseidon_config.clone())
284 }
285 }
286
287 fn witness(
289 mut layouter: impl Layouter<pallas::Base>,
290 col: Column<Advice>,
291 val: Value<pallas::Base>,
292 ) -> Result<AssignedCell<pallas::Base, pallas::Base>, plonk::Error> {
293 layouter.assign_region(
294 || "witness",
295 |mut region| region.assign_advice(|| "val", col, 0, || val),
296 )
297 }
298
299 fn base_from_repr(bytes: [u8; 32]) -> pallas::Base {
304 pallas::Base::from_repr(bytes).expect("frozen vector must be canonical")
305 }
306
307 #[test]
308 fn share_commitment_frozen_vector() {
309 let actual = share_commitment(
310 pallas::Base::from(1u64),
311 pallas::Base::from(2u64),
312 pallas::Base::from(3u64),
313 pallas::Base::from(4u64),
314 pallas::Base::from(5u64),
315 );
316
317 assert_eq!(
318 actual,
319 base_from_repr([
320 183, 66, 173, 64, 240, 83, 206, 161, 132, 211, 79, 38, 240, 12, 144, 142, 247, 139,
321 173, 56, 54, 59, 51, 73, 42, 113, 240, 242, 21, 103, 150, 29,
322 ])
323 );
324 }
325
326 #[test]
327 fn shares_hash_frozen_vector() {
328 let blinds = core::array::from_fn(|i| pallas::Base::from((i + 1) as u64));
329 let enc_c1_x = core::array::from_fn(|i| pallas::Base::from((i + 17) as u64));
330 let enc_c2_x = core::array::from_fn(|i| pallas::Base::from((i + 33) as u64));
331 let enc_c1_y = core::array::from_fn(|i| pallas::Base::from((i + 49) as u64));
332 let enc_c2_y = core::array::from_fn(|i| pallas::Base::from((i + 65) as u64));
333
334 assert_eq!(
335 shares_hash(blinds, enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y),
336 base_from_repr([
337 125, 88, 190, 64, 180, 158, 228, 46, 43, 173, 80, 255, 152, 160, 47, 234, 86, 36,
338 157, 87, 187, 167, 86, 239, 58, 45, 222, 42, 111, 6, 63, 28,
339 ])
340 );
341 }
342
343 #[derive(Clone, Default)]
346 struct HashShareCommCircuit {
347 blind: pallas::Base,
348 c1_x: pallas::Base,
349 c2_x: pallas::Base,
350 c1_y: pallas::Base,
351 c2_y: pallas::Base,
352 }
353
354 impl plonk::Circuit<pallas::Base> for HashShareCommCircuit {
355 type Config = TestConfig;
356 type FloorPlanner = floor_planner::V1;
357
358 fn without_witnesses(&self) -> Self {
359 Self::default()
360 }
361
362 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
363 TestConfig::configure(meta)
364 }
365
366 fn synthesize(
367 &self,
368 config: Self::Config,
369 mut layouter: impl Layouter<pallas::Base>,
370 ) -> Result<(), plonk::Error> {
371 let blind = witness(
372 layouter.namespace(|| "blind"),
373 config.advice,
374 Value::known(self.blind),
375 )?;
376 let c1x = witness(
377 layouter.namespace(|| "c1_x"),
378 config.advice,
379 Value::known(self.c1_x),
380 )?;
381 let c2x = witness(
382 layouter.namespace(|| "c2_x"),
383 config.advice,
384 Value::known(self.c2_x),
385 )?;
386 let c1y = witness(
387 layouter.namespace(|| "c1_y"),
388 config.advice,
389 Value::known(self.c1_y),
390 )?;
391 let c2y = witness(
392 layouter.namespace(|| "c2_y"),
393 config.advice,
394 Value::known(self.c2_y),
395 )?;
396
397 let result = hash_share_commitment_in_circuit(
398 config.poseidon_chip(),
399 layouter.namespace(|| "hash_share_comm"),
400 blind,
401 c1x,
402 c2x,
403 c1y,
404 c2y,
405 0,
406 )?;
407 layouter.constrain_instance(result.cell(), config.primary, 0)
408 }
409 }
410
411 #[test]
413 fn hash_share_commitment_matches_native() {
414 let mut rng = OsRng;
415 let blind = pallas::Base::random(&mut rng);
416 let c1_x = pallas::Base::random(&mut rng);
417 let c2_x = pallas::Base::random(&mut rng);
418 let c1_y = pallas::Base::random(&mut rng);
419 let c2_y = pallas::Base::random(&mut rng);
420
421 let expected = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
422 let circuit = HashShareCommCircuit {
423 blind,
424 c1_x,
425 c2_x,
426 c1_y,
427 c2_y,
428 };
429 let prover =
430 MockProver::run(10, &circuit, vec![vec![expected]]).expect("MockProver::run failed");
431 assert_eq!(prover.verify(), Ok(()));
432 }
433
434 #[test]
436 fn hash_share_commitment_input_order_matters() {
437 let mut rng = OsRng;
438 let blind = pallas::Base::random(&mut rng);
439 let c1_x = pallas::Base::random(&mut rng);
440 let c2_x = pallas::Base::random(&mut rng);
441 let c1_y = pallas::Base::random(&mut rng);
442 let c2_y = pallas::Base::random(&mut rng);
443
444 let wrong = share_commitment(blind, c2_x, c1_x, c2_y, c1_y);
445 let circuit = HashShareCommCircuit {
446 blind,
447 c1_x,
448 c2_x,
449 c1_y,
450 c2_y,
451 };
452 let prover =
453 MockProver::run(10, &circuit, vec![vec![wrong]]).expect("MockProver::run failed");
454 assert!(prover.verify().is_err());
455 }
456
457 #[test]
459 fn hash_share_commitment_y_negate_changes_hash() {
460 let mut rng = OsRng;
461 let blind = pallas::Base::random(&mut rng);
462 let c1_x = pallas::Base::random(&mut rng);
463 let c2_x = pallas::Base::random(&mut rng);
464 let c1_y = pallas::Base::random(&mut rng);
465 let c2_y = pallas::Base::random(&mut rng);
466
467 let correct = share_commitment(blind, c1_x, c2_x, c1_y, c2_y);
468 let negated = share_commitment(blind, c1_x, c2_x, -c1_y, c2_y);
469 assert_ne!(
470 correct, negated,
471 "negating c1_y must change the share commitment"
472 );
473 }
474
475 #[derive(Clone)]
482 struct ComputeSharesHashCircuit {
483 blinds: [pallas::Base; 16],
484 enc_c1_x: [pallas::Base; 16],
485 enc_c2_x: [pallas::Base; 16],
486 enc_c1_y: [pallas::Base; 16],
487 enc_c2_y: [pallas::Base; 16],
488 }
489
490 impl Default for ComputeSharesHashCircuit {
491 fn default() -> Self {
492 Self {
493 blinds: [pallas::Base::zero(); 16],
494 enc_c1_x: [pallas::Base::zero(); 16],
495 enc_c2_x: [pallas::Base::zero(); 16],
496 enc_c1_y: [pallas::Base::zero(); 16],
497 enc_c2_y: [pallas::Base::zero(); 16],
498 }
499 }
500 }
501
502 impl plonk::Circuit<pallas::Base> for ComputeSharesHashCircuit {
503 type Config = TestConfig;
504 type FloorPlanner = floor_planner::V1;
505
506 fn without_witnesses(&self) -> Self {
507 Self::default()
508 }
509
510 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
511 TestConfig::configure(meta)
512 }
513
514 fn synthesize(
515 &self,
516 config: Self::Config,
517 mut layouter: impl Layouter<pallas::Base>,
518 ) -> Result<(), plonk::Error> {
519 let mut blind_cells = Vec::with_capacity(16);
520 let mut c1x_cells = Vec::with_capacity(16);
521 let mut c2x_cells = Vec::with_capacity(16);
522 let mut c1y_cells = Vec::with_capacity(16);
523 let mut c2y_cells = Vec::with_capacity(16);
524 for i in 0..16 {
525 blind_cells.push(witness(
526 layouter.namespace(|| format!("blind_{i}")),
527 config.advice,
528 Value::known(self.blinds[i]),
529 )?);
530 c1x_cells.push(witness(
531 layouter.namespace(|| format!("c1x_{i}")),
532 config.advice,
533 Value::known(self.enc_c1_x[i]),
534 )?);
535 c2x_cells.push(witness(
536 layouter.namespace(|| format!("c2x_{i}")),
537 config.advice,
538 Value::known(self.enc_c2_x[i]),
539 )?);
540 c1y_cells.push(witness(
541 layouter.namespace(|| format!("c1y_{i}")),
542 config.advice,
543 Value::known(self.enc_c1_y[i]),
544 )?);
545 c2y_cells.push(witness(
546 layouter.namespace(|| format!("c2y_{i}")),
547 config.advice,
548 Value::known(self.enc_c2_y[i]),
549 )?);
550 }
551 let blinds: [AssignedCell<pallas::Base, pallas::Base>; 16] =
552 blind_cells.try_into().unwrap();
553 let enc_c1_x: [AssignedCell<pallas::Base, pallas::Base>; 16] =
554 c1x_cells.try_into().unwrap();
555 let enc_c2_x: [AssignedCell<pallas::Base, pallas::Base>; 16] =
556 c2x_cells.try_into().unwrap();
557 let enc_c1_y: [AssignedCell<pallas::Base, pallas::Base>; 16] =
558 c1y_cells.try_into().unwrap();
559 let enc_c2_y: [AssignedCell<pallas::Base, pallas::Base>; 16] =
560 c2y_cells.try_into().unwrap();
561
562 let result = compute_shares_hash_in_circuit(
563 || config.poseidon_chip(),
564 layouter.namespace(|| "compute_shares_hash"),
565 blinds,
566 enc_c1_x,
567 enc_c2_x,
568 enc_c1_y,
569 enc_c2_y,
570 )?;
571 layouter.constrain_instance(result.cell(), config.primary, 0)
572 }
573 }
574
575 #[test]
577 fn compute_shares_hash_matches_native() {
578 let mut rng = OsRng;
579 let blinds: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
580 let enc_c1_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
581 let enc_c2_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
582 let enc_c1_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
583 let enc_c2_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
584
585 let expected = shares_hash(blinds, enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y);
586 let circuit = ComputeSharesHashCircuit {
587 blinds,
588 enc_c1_x,
589 enc_c2_x,
590 enc_c1_y,
591 enc_c2_y,
592 };
593 let prover =
595 MockProver::run(12, &circuit, vec![vec![expected]]).expect("MockProver::run failed");
596 assert_eq!(prover.verify(), Ok(()));
597 }
598
599 #[test]
601 fn compute_shares_hash_wrong_enc_c1_fails() {
602 let mut rng = OsRng;
603 let blinds: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
604 let enc_c1_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
605 let enc_c2_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
606 let enc_c1_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
607 let enc_c2_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
608
609 let correct = shares_hash(blinds, enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y);
610
611 let mut circuit = ComputeSharesHashCircuit {
612 blinds,
613 enc_c1_x,
614 enc_c2_x,
615 enc_c1_y,
616 enc_c2_y,
617 };
618 circuit.enc_c1_x[2] = pallas::Base::random(&mut rng);
619
620 let prover =
621 MockProver::run(12, &circuit, vec![vec![correct]]).expect("MockProver::run failed");
622 assert!(prover.verify().is_err());
623 }
624
625 #[test]
631 fn all_16_share_positions_are_hashed() {
632 let mut rng = OsRng;
633 let blinds: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
634 let enc_c1_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
635 let enc_c2_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
636 let enc_c1_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
637 let enc_c2_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
638
639 let correct = shares_hash(blinds, enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y);
640
641 for i in 0..16 {
642 let mut perturbed_enc_c1_x = enc_c1_x;
643 perturbed_enc_c1_x[i] += pallas::Base::one();
644
645 assert_ne!(
646 shares_hash(blinds, perturbed_enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y),
647 correct,
648 "perturbing enc_c1_x[{i}] did not change the shares_hash"
649 );
650 }
651 }
652
653 #[test]
655 fn compute_shares_hash_wrong_blind_fails() {
656 let mut rng = OsRng;
657 let blinds: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
658 let enc_c1_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
659 let enc_c2_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
660 let enc_c1_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
661 let enc_c2_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
662
663 let correct = shares_hash(blinds, enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y);
664
665 let mut circuit = ComputeSharesHashCircuit {
666 blinds,
667 enc_c1_x,
668 enc_c2_x,
669 enc_c1_y,
670 enc_c2_y,
671 };
672 circuit.blinds[0] = pallas::Base::random(&mut rng);
673
674 let prover =
675 MockProver::run(12, &circuit, vec![vec![correct]]).expect("MockProver::run failed");
676 assert!(prover.verify().is_err());
677 }
678
679 #[derive(Clone)]
686 struct ComputeSharesHashFromCommsCircuit {
687 share_comms: [pallas::Base; 16],
688 }
689
690 impl Default for ComputeSharesHashFromCommsCircuit {
691 fn default() -> Self {
692 Self {
693 share_comms: [pallas::Base::zero(); 16],
694 }
695 }
696 }
697
698 impl plonk::Circuit<pallas::Base> for ComputeSharesHashFromCommsCircuit {
699 type Config = TestConfig;
700 type FloorPlanner = floor_planner::V1;
701
702 fn without_witnesses(&self) -> Self {
703 Self::default()
704 }
705
706 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
707 TestConfig::configure(meta)
708 }
709
710 fn synthesize(
711 &self,
712 config: Self::Config,
713 mut layouter: impl Layouter<pallas::Base>,
714 ) -> Result<(), plonk::Error> {
715 let mut comm_cells = Vec::with_capacity(16);
716 for i in 0..16 {
717 comm_cells.push(witness(
718 layouter.namespace(|| format!("comm_{i}")),
719 config.advice,
720 Value::known(self.share_comms[i]),
721 )?);
722 }
723 let comms: [AssignedCell<pallas::Base, pallas::Base>; 16] =
724 comm_cells.try_into().unwrap();
725
726 let result = super::compute_shares_hash_from_comms_in_circuit(
727 config.poseidon_chip(),
728 layouter.namespace(|| "hash_from_comms"),
729 comms,
730 )?;
731 layouter.constrain_instance(result.cell(), config.primary, 0)
732 }
733 }
734
735 #[derive(Clone)]
738 struct SharesHashInCircuitEquivalenceCircuit {
739 blinds: [pallas::Base; 16],
740 enc_c1_x: [pallas::Base; 16],
741 enc_c2_x: [pallas::Base; 16],
742 enc_c1_y: [pallas::Base; 16],
743 enc_c2_y: [pallas::Base; 16],
744 }
745
746 impl Default for SharesHashInCircuitEquivalenceCircuit {
747 fn default() -> Self {
748 Self {
749 blinds: [pallas::Base::zero(); 16],
750 enc_c1_x: [pallas::Base::zero(); 16],
751 enc_c2_x: [pallas::Base::zero(); 16],
752 enc_c1_y: [pallas::Base::zero(); 16],
753 enc_c2_y: [pallas::Base::zero(); 16],
754 }
755 }
756 }
757
758 impl plonk::Circuit<pallas::Base> for SharesHashInCircuitEquivalenceCircuit {
759 type Config = TestConfig;
760 type FloorPlanner = floor_planner::V1;
761
762 fn without_witnesses(&self) -> Self {
763 Self::default()
764 }
765
766 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
767 TestConfig::configure(meta)
768 }
769
770 fn synthesize(
771 &self,
772 config: Self::Config,
773 mut layouter: impl Layouter<pallas::Base>,
774 ) -> Result<(), plonk::Error> {
775 let mut blind_cells = Vec::with_capacity(16);
776 let mut c1x_cells = Vec::with_capacity(16);
777 let mut c2x_cells = Vec::with_capacity(16);
778 let mut c1y_cells = Vec::with_capacity(16);
779 let mut c2y_cells = Vec::with_capacity(16);
780 for i in 0..16 {
781 blind_cells.push(witness(
782 layouter.namespace(|| format!("blind_{i}")),
783 config.advice,
784 Value::known(self.blinds[i]),
785 )?);
786 c1x_cells.push(witness(
787 layouter.namespace(|| format!("c1x_{i}")),
788 config.advice,
789 Value::known(self.enc_c1_x[i]),
790 )?);
791 c2x_cells.push(witness(
792 layouter.namespace(|| format!("c2x_{i}")),
793 config.advice,
794 Value::known(self.enc_c2_x[i]),
795 )?);
796 c1y_cells.push(witness(
797 layouter.namespace(|| format!("c1y_{i}")),
798 config.advice,
799 Value::known(self.enc_c1_y[i]),
800 )?);
801 c2y_cells.push(witness(
802 layouter.namespace(|| format!("c2y_{i}")),
803 config.advice,
804 Value::known(self.enc_c2_y[i]),
805 )?);
806 }
807
808 let blinds_full: [AssignedCell<pallas::Base, pallas::Base>; 16] =
809 core::array::from_fn(|i| blind_cells[i].clone());
810 let enc_c1_x_full: [AssignedCell<pallas::Base, pallas::Base>; 16] =
811 core::array::from_fn(|i| c1x_cells[i].clone());
812 let enc_c2_x_full: [AssignedCell<pallas::Base, pallas::Base>; 16] =
813 core::array::from_fn(|i| c2x_cells[i].clone());
814 let enc_c1_y_full: [AssignedCell<pallas::Base, pallas::Base>; 16] =
815 core::array::from_fn(|i| c1y_cells[i].clone());
816 let enc_c2_y_full: [AssignedCell<pallas::Base, pallas::Base>; 16] =
817 core::array::from_fn(|i| c2y_cells[i].clone());
818
819 let full_hash = compute_shares_hash_in_circuit(
820 || config.poseidon_chip(),
821 layouter.namespace(|| "full shares_hash path"),
822 blinds_full,
823 enc_c1_x_full,
824 enc_c2_x_full,
825 enc_c1_y_full,
826 enc_c2_y_full,
827 )?;
828
829 let share_comms: [AssignedCell<pallas::Base, pallas::Base>; 16] = (0..16)
830 .map(|i| {
831 hash_share_commitment_in_circuit(
832 config.poseidon_chip(),
833 layouter.namespace(|| format!("from-comms share_comm_{i}")),
834 blind_cells[i].clone(),
835 c1x_cells[i].clone(),
836 c2x_cells[i].clone(),
837 c1y_cells[i].clone(),
838 c2y_cells[i].clone(),
839 i,
840 )
841 })
842 .collect::<Result<Vec<_>, _>>()?
843 .try_into()
844 .expect("always 16 elements");
845
846 let from_comms_hash = super::compute_shares_hash_from_comms_in_circuit(
847 config.poseidon_chip(),
848 layouter.namespace(|| "from-comms shares_hash path"),
849 share_comms,
850 )?;
851
852 layouter.assign_region(
853 || "full shares_hash == from-comms shares_hash",
854 |mut region| region.constrain_equal(full_hash.cell(), from_comms_hash.cell()),
855 )
856 }
857 }
858
859 #[test]
861 fn shares_hash_from_comms_matches_native() {
862 let mut rng = OsRng;
863 let blinds: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
864 let enc_c1_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
865 let enc_c2_x: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
866 let enc_c1_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
867 let enc_c2_y: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
868
869 let comms: [pallas::Base; 16] = core::array::from_fn(|i| {
870 share_commitment(
871 blinds[i],
872 enc_c1_x[i],
873 enc_c2_x[i],
874 enc_c1_y[i],
875 enc_c2_y[i],
876 )
877 });
878 let expected = super::shares_hash_from_comms(comms);
879
880 assert_eq!(
881 expected,
882 shares_hash(blinds, enc_c1_x, enc_c2_x, enc_c1_y, enc_c2_y)
883 );
884
885 let circuit = ComputeSharesHashFromCommsCircuit { share_comms: comms };
886 let prover =
887 MockProver::run(12, &circuit, vec![vec![expected]]).expect("MockProver::run failed");
888 assert_eq!(prover.verify(), Ok(()));
889 }
890
891 #[test]
894 fn compute_shares_hash_in_circuit_matches_from_comms_in_circuit() {
895 let mut rng = OsRng;
896 let circuit = SharesHashInCircuitEquivalenceCircuit {
897 blinds: core::array::from_fn(|_| pallas::Base::random(&mut rng)),
898 enc_c1_x: core::array::from_fn(|_| pallas::Base::random(&mut rng)),
899 enc_c2_x: core::array::from_fn(|_| pallas::Base::random(&mut rng)),
900 enc_c1_y: core::array::from_fn(|_| pallas::Base::random(&mut rng)),
901 enc_c2_y: core::array::from_fn(|_| pallas::Base::random(&mut rng)),
902 };
903
904 let prover = MockProver::run(13, &circuit, vec![vec![]]).expect("MockProver::run failed");
906 assert_eq!(prover.verify(), Ok(()));
907 }
908
909 #[test]
911 fn shares_hash_from_comms_wrong_comm_fails() {
912 let mut rng = OsRng;
913 let comms: [pallas::Base; 16] = core::array::from_fn(|_| pallas::Base::random(&mut rng));
914 let expected = super::shares_hash_from_comms(comms);
915
916 let mut bad_comms = comms;
917 bad_comms[7] = pallas::Base::random(&mut rng);
918 let circuit = ComputeSharesHashFromCommsCircuit {
919 share_comms: bad_comms,
920 };
921 let prover =
922 MockProver::run(12, &circuit, vec![vec![expected]]).expect("MockProver::run failed");
923 assert!(prover.verify().is_err());
924 }
925}