1mod bandersnatch;
2
3use crate::{
4 bls12381::primitives::group::{Scalar, G1},
5 transcript::{Summary, Transcript},
6 zk::{
7 bulletproofs::circuit::{self, prove, verify},
8 pedersen_to_plain,
9 },
10 Secret,
11};
12use bandersnatch::{vrf_batch_checked, vrf_batch_checked_circuit, vrf_recv, F, G};
13use bytes::{Buf, BufMut, Bytes};
14use commonware_codec::{
15 Encode, EncodeFixed, EncodeSize, Error as CodecError, FixedSize, Read, ReadExt, Write,
16};
17use commonware_formatting::hex;
18use commonware_math::algebra::{Additive as _, CryptoGroup, Random};
19use commonware_parallel::Strategy;
20use commonware_utils::{
21 ordered::{Map, Set},
22 Array, Span, TryCollect, TryFromIterator,
23};
24use core::{
25 fmt::{Debug, Display},
26 hash::{Hash, Hasher},
27 ops::Deref,
28};
29use rand_core::CryptoRngCore;
30use std::num::NonZeroU32;
31use zeroize::Zeroizing;
32
33const SCHNORR_NS: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_BANDERSNATCH_SCHNORR";
34
35const BULLETPROOFS_DST: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_GOLDEN_DKG_BULLETPROOFS";
36
37const WIRES_PER_PLAYER: usize = 8664;
48const WIRES_BASE: usize = 3065;
49
50const fn lg_len_for_players(num_players: u32) -> u8 {
56 let internal = WIRES_PER_PLAYER * (num_players as usize) + WIRES_BASE;
57 let mut padded: usize = 1;
59 let mut lg: u8 = 0;
60 while padded < internal {
61 padded <<= 1;
62 lg += 1;
63 }
64 lg
65}
66
67pub struct Setup {
84 inner: circuit::Setup<G1>,
85 max_players: NonZeroU32,
86}
87
88impl Setup {
89 pub fn new(max_players: NonZeroU32) -> Self {
95 let lg_len = lg_len_for_players(max_players.get());
96 let inner = circuit::Setup::hashed(BULLETPROOFS_DST, lg_len, G1::generator());
100 Self { inner, max_players }
101 }
102
103 #[must_use]
106 pub const fn supports(&self, num_players: u32) -> bool {
107 num_players <= self.max_players.get()
108 }
109
110 pub(super) const fn max_players(&self) -> NonZeroU32 {
112 self.max_players
113 }
114
115 pub(super) const fn inner(&self) -> &circuit::Setup<G1> {
116 &self.inner
117 }
118}
119
120impl Write for Setup {
121 fn write(&self, buf: &mut impl BufMut) {
122 self.max_players.get().write(buf);
123 self.inner.write(buf);
124 }
125}
126
127impl EncodeSize for Setup {
128 fn encode_size(&self) -> usize {
129 self.max_players.get().encode_size() + self.inner.encode_size()
130 }
131}
132
133impl Read for Setup {
134 type Cfg = NonZeroU32;
137
138 fn read_cfg(buf: &mut impl Buf, expected_max_players: &Self::Cfg) -> Result<Self, CodecError> {
139 let max_players_raw = u32::read(buf)?;
140 let max_players = NonZeroU32::new(max_players_raw)
141 .ok_or(CodecError::Invalid("Setup", "max_players must be nonzero"))?;
142 if max_players != *expected_max_players {
143 return Err(CodecError::Invalid("Setup", "max_players mismatch"));
144 }
145 let lg_len = lg_len_for_players(max_players.get());
146 let max_len = 1usize << lg_len;
147 let inner = circuit::Setup::<G1>::read_cfg(buf, &(max_len, ()))?;
148 if !inner.supports(lg_len) {
149 return Err(CodecError::Invalid("Setup", "inner setup too small"));
150 }
151 Ok(Self { inner, max_players })
152 }
153}
154
155#[derive(Clone, Debug)]
156pub struct PrivateKey {
157 inner: Secret<F>,
158}
159
160impl Random for PrivateKey {
161 fn random(rng: impl CryptoRngCore) -> Self {
162 Self {
163 inner: Secret::new(F::random(rng)),
164 }
165 }
166}
167
168impl crate::Signer for PrivateKey {
169 type Signature = Signature;
170 type PublicKey = PublicKey;
171
172 fn public_key(&self) -> Self::PublicKey {
173 self.inner
174 .expose(|x| PublicKey::from_point(G::generator() * x))
175 }
176
177 fn sign(&self, namespace: &[u8], msg: &[u8]) -> Signature {
178 let pk = self.public();
179 let mut t = Transcript::new(SCHNORR_NS);
180 t.commit(namespace).commit(msg).commit(pk.raw.as_slice());
181
182 let k = self.inner.expose(|x| {
184 let mut nonce_t = t.fork(b"nonce");
185 let x_bytes = Zeroizing::new(x.encode_fixed::<{ F::SIZE }>());
186 nonce_t.commit(x_bytes.as_slice());
187 F::random(&mut nonce_t.noise(b"k"))
188 });
189
190 let k_big = G::generator() * &k;
191 let k_big_bytes: [u8; G::SIZE] = k_big.encode_fixed();
192 t.commit(k_big_bytes.as_slice());
193 let e = F::random(&mut t.noise(b"challenge"));
194
195 let s = self.inner.expose(|x| e * x + &k);
197
198 let mut raw = [0u8; Signature::SIZE];
199 raw[..G::SIZE].copy_from_slice(&k_big_bytes);
200 raw[G::SIZE..].copy_from_slice(&s.encode_fixed::<{ F::SIZE }>());
201 Signature { raw }
202 }
203}
204
205impl PrivateKey {
206 pub fn public(&self) -> PublicKey {
208 crate::Signer::public_key(self)
209 }
210
211 pub(super) fn vrf_recv(&self, msg: &Summary, sender: &PublicKey) -> Scalar {
220 self.inner
221 .expose(|inner| vrf_recv(msg, sender.point.clone(), inner))
222 }
223
224 pub(super) fn vrf_batch_checked(
231 &self,
232 rng: &mut impl CryptoRngCore,
233 setup: &Setup,
234 transcript: &mut Transcript,
235 msg: &Summary,
236 receivers: impl IntoIterator<Item = PublicKey>,
237 strategy: &impl Strategy,
238 ) -> (Map<PublicKey, Scalar>, VrfCommitments) {
239 let receivers = Map::from_iter_dedup(receivers.into_iter().map(|x| {
240 let point = x.point.clone();
241 (x, point)
242 }));
243 let (circuit, witness) = self
244 .inner
245 .expose(|x| vrf_batch_checked(msg, x, receivers.values()));
246 let claim = witness.claim(setup.inner());
247 let circuit_proof = prove(
248 &mut *rng,
249 transcript,
250 setup.inner(),
251 &circuit,
252 &claim,
253 &witness,
254 strategy,
255 )
256 .expect("proving should succeed");
257 let outputs = Map::try_from_iter(
258 receivers
259 .into_iter()
260 .zip(witness.values())
261 .map(|((receiver, _), output)| (receiver, output.clone())),
262 )
263 .expect("receivers was already deduplicated");
264 let commitments = Map::try_from_iter(outputs.keys().iter().cloned().zip(claim.commitments))
265 .expect("receivers was already deduplicated");
266 let pedersen_to_plain = {
267 let setup = pedersen_to_plain::Setup {
268 value_generator: *setup.inner().value_generator(),
269 blinding_generator: *setup.inner().blinding_generator(),
270 };
271 let mut out = Vec::new();
272 for (receiver, output) in outputs.iter_pairs() {
273 let commitment = *commitments
274 .get_value(receiver)
275 .expect("output should have commitment");
276 let proof = pedersen_to_plain::prove(
277 &mut *rng,
278 transcript,
279 &setup,
280 &pedersen_to_plain::Claim {
281 plain: commitment,
282 pedersen: commitment,
283 },
284 &pedersen_to_plain::Witness {
285 value: output.clone(),
286 blinding: Scalar::zero(),
287 },
288 );
289 out.push(proof);
290 }
291 out
292 };
293 let proof = Proof {
294 circuit_proof,
295 pedersen_to_plain,
296 };
297 (outputs, VrfCommitments { proof, commitments })
298 }
299}
300
301impl Write for PrivateKey {
302 fn write(&self, buf: &mut impl BufMut) {
303 self.inner
304 .expose(|x| buf.put_slice(&x.encode_fixed::<{ F::SIZE }>()));
305 }
306}
307
308impl Read for PrivateKey {
309 type Cfg = ();
310
311 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
312 let raw = Zeroizing::new(<[u8; Self::SIZE]>::read(buf)?);
313 let x: F = ReadExt::read(&mut raw.as_slice())?;
314 Ok(Self {
315 inner: Secret::new(x),
316 })
317 }
318}
319
320impl FixedSize for PrivateKey {
321 const SIZE: usize = F::SIZE;
322}
323
324#[derive(Clone, Eq, PartialEq)]
328pub struct Signature {
329 raw: [u8; G::SIZE + F::SIZE],
330}
331
332impl Write for Signature {
333 fn write(&self, buf: &mut impl BufMut) {
334 self.raw.write(buf);
335 }
336}
337
338impl Read for Signature {
339 type Cfg = ();
340
341 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
342 let raw = <[u8; Self::SIZE]>::read(buf)?;
343 Ok(Self { raw })
344 }
345}
346
347impl FixedSize for Signature {
348 const SIZE: usize = G::SIZE + F::SIZE;
349}
350
351impl crate::Signature for Signature {}
352
353impl Span for Signature {}
354
355impl Array for Signature {}
356
357impl Hash for Signature {
358 fn hash<H: Hasher>(&self, state: &mut H) {
359 self.raw.hash(state);
360 }
361}
362
363impl Ord for Signature {
364 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
365 self.raw.cmp(&other.raw)
366 }
367}
368
369impl PartialOrd for Signature {
370 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
371 Some(self.cmp(other))
372 }
373}
374
375impl AsRef<[u8]> for Signature {
376 fn as_ref(&self) -> &[u8] {
377 &self.raw
378 }
379}
380
381impl Deref for Signature {
382 type Target = [u8];
383 fn deref(&self) -> &[u8] {
384 &self.raw
385 }
386}
387
388impl Debug for Signature {
389 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
390 write!(f, "{}", hex(&self.raw))
391 }
392}
393
394impl Display for Signature {
395 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
396 write!(f, "{}", hex(&self.raw))
397 }
398}
399
400#[derive(Clone)]
404pub struct PublicKey {
405 raw: [u8; G::SIZE],
406 point: G,
407}
408
409impl PublicKey {
410 fn from_point(point: G) -> Self {
411 let raw: [u8; G::SIZE] = point.encode_fixed();
412 Self { raw, point }
413 }
414}
415
416impl crate::Verifier for PublicKey {
417 type Signature = Signature;
418
419 fn verify(&self, namespace: &[u8], msg: &[u8], sig: &Signature) -> bool {
420 let k_big: G = match ReadExt::read(&mut &sig.raw[..G::SIZE]) {
421 Ok(p) => p,
422 Err(_) => return false,
423 };
424 let s: F = match ReadExt::read(&mut &sig.raw[G::SIZE..]) {
425 Ok(s) => s,
426 Err(_) => return false,
427 };
428
429 let mut t = Transcript::new(SCHNORR_NS);
431 t.commit(namespace)
432 .commit(msg)
433 .commit(self.raw.as_slice())
434 .commit(sig.raw[..G::SIZE].as_ref());
435 let e = F::random(&mut t.noise(b"challenge"));
436
437 let lhs = G::generator() * &s;
439 let rhs = k_big + &(self.point.clone() * &e);
440 lhs == rhs
441 }
442}
443
444impl crate::PublicKey for PublicKey {}
445
446impl Write for PublicKey {
447 fn write(&self, buf: &mut impl BufMut) {
448 self.raw.write(buf);
449 }
450}
451
452impl Read for PublicKey {
453 type Cfg = ();
454
455 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
456 let raw = <[u8; Self::SIZE]>::read(buf)?;
457 let point: G = ReadExt::read(&mut raw.as_slice())?;
458 Ok(Self { raw, point })
459 }
460}
461
462impl FixedSize for PublicKey {
463 const SIZE: usize = G::SIZE;
464}
465
466impl Span for PublicKey {}
467
468impl Array for PublicKey {}
469
470impl AsRef<[u8]> for PublicKey {
471 fn as_ref(&self) -> &[u8] {
472 &self.raw
473 }
474}
475
476impl Deref for PublicKey {
477 type Target = [u8];
478 fn deref(&self) -> &[u8] {
479 &self.raw
480 }
481}
482
483impl Eq for PublicKey {}
484
485impl PartialEq for PublicKey {
486 fn eq(&self, other: &Self) -> bool {
487 self.raw == other.raw
488 }
489}
490
491impl Ord for PublicKey {
492 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
493 self.raw.cmp(&other.raw)
494 }
495}
496
497impl PartialOrd for PublicKey {
498 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
499 Some(self.cmp(other))
500 }
501}
502
503impl Hash for PublicKey {
504 fn hash<H: Hasher>(&self, state: &mut H) {
505 self.raw.hash(state);
506 }
507}
508
509impl Debug for PublicKey {
510 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
511 write!(f, "{}", hex(self))
512 }
513}
514
515impl Display for PublicKey {
516 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
517 write!(f, "{}", hex(self))
518 }
519}
520
521#[derive(Clone)]
524struct Proof {
525 circuit_proof: circuit::Proof<Scalar, G1>,
526 pedersen_to_plain: Vec<pedersen_to_plain::Proof<Scalar, G1>>,
527}
528
529impl Write for Proof {
530 fn write(&self, buf: &mut impl BufMut) {
531 self.circuit_proof.write(buf);
532 self.pedersen_to_plain.write(buf);
533 }
534}
535
536impl EncodeSize for Proof {
537 fn encode_size(&self) -> usize {
538 self.circuit_proof.encode_size() + self.pedersen_to_plain.encode_size()
539 }
540}
541
542impl Read for Proof {
543 type Cfg = NonZeroU32;
548
549 fn read_cfg(buf: &mut impl Buf, max_players: &Self::Cfg) -> Result<Self, CodecError> {
550 let max_proof_len = 1usize << lg_len_for_players(max_players.get());
551 let circuit_proof =
552 circuit::Proof::<Scalar, G1>::read_cfg(buf, &(max_proof_len, ((), ())))?;
553 let range = commonware_codec::RangeCfg::new(0..=max_players.get() as usize);
554 let pedersen_to_plain =
555 Vec::<pedersen_to_plain::Proof<Scalar, G1>>::read_cfg(buf, &(range, ((), ())))?;
556 Ok(Self {
557 circuit_proof,
558 pedersen_to_plain,
559 })
560 }
561}
562
563impl Write for VrfCommitments {
564 fn write(&self, buf: &mut impl BufMut) {
565 self.proof.write(buf);
566 self.commitments.write(buf);
567 }
568}
569
570impl EncodeSize for VrfCommitments {
571 fn encode_size(&self) -> usize {
572 self.proof.encode_size() + self.commitments.encode_size()
573 }
574}
575
576impl Read for VrfCommitments {
577 type Cfg = NonZeroU32;
578
579 fn read_cfg(buf: &mut impl Buf, max_players: &Self::Cfg) -> Result<Self, CodecError> {
580 let proof = Proof::read_cfg(buf, max_players)?;
581 let range = commonware_codec::RangeCfg::new(0..=max_players.get() as usize);
582 let commitments = Read::read_cfg(buf, &(range, (), ()))?;
583 Ok(Self { proof, commitments })
584 }
585}
586
587#[derive(Clone)]
592pub struct VrfCommitments {
593 proof: Proof,
594 commitments: Map<PublicKey, G1>,
595}
596
597impl VrfCommitments {
598 #[cfg(any(feature = "arbitrary", test))]
601 pub(super) fn perturb(&mut self, receiver: &PublicKey, delta: &G1) {
602 if let Some(c) = self.commitments.get_value_mut(receiver) {
603 *c += delta;
604 }
605 }
606
607 pub fn check_batch(
632 rng: &mut impl CryptoRngCore,
633 setup: &Setup,
634 transcript: &Transcript,
635 players: &Set<PublicKey>,
636 outputs: impl IntoIterator<Item = (PublicKey, Bytes, Self)>,
637 strategy: &impl Strategy,
638 ) -> Map<PublicKey, Map<PublicKey, G1>> {
639 let outputs: Vec<(PublicKey, Bytes, Self)> = outputs
650 .into_iter()
651 .filter_map(|(sender, msg, commitments)| {
652 let mut buf: &[u8] = msg.as_ref();
653 let _: Summary = ReadExt::read(&mut buf).ok()?;
654 if commitments.proof.pedersen_to_plain.len() != commitments.commitments.len() {
655 return None;
656 }
657 if commitments
658 .commitments
659 .keys()
660 .iter()
661 .any(|pk| players.position(pk).is_none())
662 {
663 return None;
664 }
665 Some((sender, msg, commitments))
666 })
667 .collect();
668
669 let per_sender = setup.inner().eval_check_batched(
673 rng,
674 |vs, rng| {
675 let pp_setup = pedersen_to_plain::Setup {
679 value_generator: vs.value_generator().clone(),
680 blinding_generator: vs.blinding_generator().clone(),
681 };
682
683 let mut per_sender = Vec::with_capacity(outputs.len());
684 for (sender, msg, commitments) in &outputs {
685 let receivers: Vec<G> = commitments
689 .commitments
690 .keys()
691 .iter()
692 .map(|pk| pk.point.clone())
693 .collect();
694 let circuit =
695 vrf_batch_checked_circuit(msg.as_ref(), sender.point.clone(), &receivers);
696 let claim = circuit::Claim {
697 commitments: commitments.commitments.values().to_vec(),
698 };
699
700 let mut t = transcript.fork(b"dealer vrf");
704 t.commit(sender.encode());
705
706 let Some(circuit_synth) = verify(
707 &mut *rng,
708 &mut t,
709 vs,
710 &circuit,
711 &claim,
712 commitments.proof.circuit_proof.clone(),
713 strategy,
714 ) else {
715 per_sender.push(None);
719 continue;
720 };
721 let mut sender_acc = circuit_synth * &Scalar::random(&mut *rng);
722
723 for ((_, comm), pp_proof) in commitments
726 .commitments
727 .iter_pairs()
728 .zip(commitments.proof.pedersen_to_plain.iter().cloned())
729 {
730 let pp_claim = pedersen_to_plain::Claim {
731 plain: *comm,
732 pedersen: *comm,
733 };
734 let pp_synth = pedersen_to_plain::verify(
735 &mut *rng, &mut t, &pp_setup, &pp_claim, pp_proof,
736 );
737 sender_acc += &(pp_synth * &Scalar::random(&mut *rng));
738 }
739 per_sender.push(Some(sender_acc));
740 }
741 Some(per_sender)
742 },
743 strategy,
744 );
745
746 let Some(per_sender) = per_sender else {
747 return Map::default();
748 };
749
750 outputs
751 .into_iter()
752 .zip(per_sender)
753 .filter_map(|((sender, _, commitments), valid)| {
754 valid.then_some((sender, commitments.commitments))
755 })
756 .try_collect()
757 .expect("senders must be unique")
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764 use commonware_macros::test_group;
765 use commonware_parallel::Sequential;
766 use commonware_utils::test_rng;
767 use std::sync::LazyLock;
768
769 static TEST_SETUP: LazyLock<Setup> = LazyLock::new(|| Setup::new(NonZeroU32::new(3).unwrap()));
772
773 #[test_group("slow")]
774 #[test]
775 fn vrf_batch_checked_roundtrips_through_check_batch() {
776 let mut rng = test_rng();
777
778 let sender_sk = PrivateKey::random(&mut rng);
779 let sender_pk = sender_sk.public();
780 let receiver_pks: Vec<PublicKey> = (0..3)
781 .map(|_| PrivateKey::random(&mut rng).public())
782 .collect();
783
784 let nonce = Summary::random(&mut rng);
785 let msg = Bytes::copy_from_slice(nonce.as_ref());
786
787 let outer_transcript = Transcript::new(b"vrf-batch-checked-test");
791
792 let mut prover_t = outer_transcript.fork(b"dealer vrf");
793 prover_t.commit(sender_pk.encode());
794 let (_outputs, commitments) = sender_sk.vrf_batch_checked(
795 &mut rng,
796 &TEST_SETUP,
797 &mut prover_t,
798 &nonce,
799 receiver_pks.iter().cloned(),
800 &Sequential,
801 );
802
803 let players: Set<PublicKey> = receiver_pks.iter().cloned().try_collect().unwrap();
804 let result = VrfCommitments::check_batch(
805 &mut rng,
806 &TEST_SETUP,
807 &outer_transcript,
808 &players,
809 std::iter::once((sender_pk.clone(), msg, commitments.clone())),
810 &Sequential,
811 );
812
813 assert_eq!(result.len(), 1);
814 let checked = result
815 .get_value(&sender_pk)
816 .expect("sender should appear in batch result");
817 assert_eq!(checked, &commitments.commitments);
818 }
819
820 #[test_group("slow")]
821 #[test]
822 fn check_batch_rejects_perturbed_commitments() {
823 let mut rng = test_rng();
824
825 let sender_sk = PrivateKey::random(&mut rng);
826 let sender_pk = sender_sk.public();
827 let receiver_pks: Vec<PublicKey> = (0..3)
828 .map(|_| PrivateKey::random(&mut rng).public())
829 .collect();
830
831 let nonce = Summary::random(&mut rng);
832 let msg = Bytes::copy_from_slice(nonce.as_ref());
833
834 let outer_transcript = Transcript::new(b"vrf-batch-checked-test");
835
836 let mut prover_t = outer_transcript.fork(b"dealer vrf");
837 prover_t.commit(sender_pk.encode());
838 let (_outputs, mut commitments) = sender_sk.vrf_batch_checked(
839 &mut rng,
840 &TEST_SETUP,
841 &mut prover_t,
842 &nonce,
843 receiver_pks.iter().cloned(),
844 &Sequential,
845 );
846
847 commitments.perturb(&receiver_pks[0], &G1::generator());
849
850 let players: Set<PublicKey> = receiver_pks.iter().cloned().try_collect().unwrap();
851 let result = VrfCommitments::check_batch(
852 &mut rng,
853 &TEST_SETUP,
854 &outer_transcript,
855 &players,
856 std::iter::once((sender_pk, msg, commitments)),
857 &Sequential,
858 );
859 assert!(result.is_empty());
860 }
861
862 #[test]
863 fn check_batch_rejects_short_pedersen_to_plain_vector() {
864 let mut rng = test_rng();
865
866 let sender_sk = PrivateKey::random(&mut rng);
867 let sender_pk = sender_sk.public();
868 let receiver_pks: Vec<PublicKey> = (0..3)
869 .map(|_| PrivateKey::random(&mut rng).public())
870 .collect();
871
872 let nonce = Summary::random(&mut rng);
873 let msg = Bytes::copy_from_slice(nonce.as_ref());
874
875 let outer_transcript = Transcript::new(b"vrf-batch-checked-test");
876
877 let mut prover_t = outer_transcript.fork(b"dealer vrf");
878 prover_t.commit(sender_pk.encode());
879 let (_outputs, mut commitments) = sender_sk.vrf_batch_checked(
880 &mut rng,
881 &TEST_SETUP,
882 &mut prover_t,
883 &nonce,
884 receiver_pks.iter().cloned(),
885 &Sequential,
886 );
887
888 commitments.proof.pedersen_to_plain.pop().unwrap();
892 assert!(
893 commitments.proof.pedersen_to_plain.len() < commitments.commitments.len(),
894 "test setup expects fewer proofs than commitments",
895 );
896
897 let players: Set<PublicKey> = receiver_pks.iter().cloned().try_collect().unwrap();
898 let result = VrfCommitments::check_batch(
899 &mut rng,
900 &TEST_SETUP,
901 &outer_transcript,
902 &players,
903 std::iter::once((sender_pk, msg, commitments)),
904 &Sequential,
905 );
906 assert!(result.is_empty());
907 }
908
909 #[test_group("slow")]
910 #[test]
911 fn check_batch_falls_back_to_per_sender_on_failure() {
912 let mut rng = test_rng();
913
914 let senders: Vec<(PrivateKey, PublicKey)> = (0..2)
916 .map(|_| {
917 let sk = PrivateKey::random(&mut rng);
918 let pk = sk.public();
919 (sk, pk)
920 })
921 .collect();
922 let receiver_pks: Vec<PublicKey> = (0..3)
923 .map(|_| PrivateKey::random(&mut rng).public())
924 .collect();
925
926 let outer_transcript = Transcript::new(b"vrf-batch-checked-test");
927
928 let mut prepared = Vec::new();
929 for (sk, pk) in &senders {
930 let nonce = Summary::random(&mut rng);
931 let msg = Bytes::copy_from_slice(nonce.as_ref());
932 let mut prover_t = outer_transcript.fork(b"dealer vrf");
933 prover_t.commit(pk.encode());
934 let (_outputs, commitments) = sk.vrf_batch_checked(
935 &mut rng,
936 &TEST_SETUP,
937 &mut prover_t,
938 &nonce,
939 receiver_pks.iter().cloned(),
940 &Sequential,
941 );
942 prepared.push((pk.clone(), msg, commitments));
943 }
944
945 prepared[1].2.perturb(&receiver_pks[0], &G1::generator());
948
949 let players: Set<PublicKey> = receiver_pks.iter().cloned().try_collect().unwrap();
950 let result = VrfCommitments::check_batch(
951 &mut rng,
952 &TEST_SETUP,
953 &outer_transcript,
954 &players,
955 prepared.iter().cloned(),
956 &Sequential,
957 );
958
959 assert_eq!(result.len(), 1);
961 let good_pk = &senders[0].1;
962 let bad_pk = &senders[1].1;
963 assert_eq!(result.get_value(good_pk), Some(&prepared[0].2.commitments));
964 assert!(result.get_value(bad_pk).is_none());
965 }
966
967 #[test]
968 fn setup_codec_roundtrip() {
969 let s = Setup::new(NonZeroU32::new(3).unwrap());
970 let bytes = s.encode();
971 let decoded = Setup::read_cfg(&mut bytes.as_ref(), &NonZeroU32::new(3).unwrap()).unwrap();
972 assert_eq!(decoded.max_players(), s.max_players());
973 assert_eq!(decoded.encode(), bytes);
975 }
976}