Skip to main content

jam_std_common/crypto/
bandersnatch.rs

1//! VRFs backed by [Bandersnatch](https://neuromancer.sk/std/bls/Bandersnatch),
2//! an elliptic curve built over BLS12-381 scalar field.
3//!
4//! The primitive can operate both as a regular VRF or as an anonymized Ring VRF.
5
6use super::concat;
7use crate::{Entropy, TicketId, simple::TrancheIndex};
8use ark_vrf::{
9	reexports::ark_serialize::{CanonicalDeserialize, CanonicalSerialize},
10	suites::bandersnatch::{self, AffinePoint, Secret as SecretImpl},
11};
12use codec::{Decode, Encode, MaxEncodedLen};
13use jam_types::{AnyVec, OpaqueBandersnatchPublic, TicketAttempt};
14
15pub use ark_vrf::Error;
16
17const SCALAR_SERIALIZED_SIZE: usize = 32;
18const POINT_SERIALIZED_SIZE: usize = 32;
19
20pub const PUBLIC_SERIALIZED_SIZE: usize = POINT_SERIALIZED_SIZE;
21pub const PREOUT_SERIALIZED_SIZE: usize = POINT_SERIALIZED_SIZE;
22
23const ERR_MSG: &str = "object length is constant and checked by test; qed";
24
25/// The raw secret seed, which can be used to reconstruct the secret [`Secret`].
26type Seed = [u8; SCALAR_SERIALIZED_SIZE];
27
28/// Bandersnatch public key.
29#[derive(Clone, Copy, PartialEq, Eq, Hash, Encode, Decode, MaxEncodedLen, Default)]
30pub struct Public(pub [u8; PUBLIC_SERIALIZED_SIZE]);
31
32impl Public {
33	pub fn is_valid(&self) -> bool {
34		AffinePoint::deserialize_compressed_unchecked(&self.0[..]).is_ok()
35	}
36}
37
38impl std::fmt::Debug for Public {
39	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40		write!(f, "{:?}", AnyVec(self.0.to_vec()))
41	}
42}
43
44impl std::fmt::Display for Public {
45	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46		write!(f, "{}", AnyVec(self.0.to_vec()))
47	}
48}
49
50impl From<OpaqueBandersnatchPublic> for Public {
51	fn from(opaque: OpaqueBandersnatchPublic) -> Self {
52		Self(opaque.0)
53	}
54}
55impl From<Public> for OpaqueBandersnatchPublic {
56	fn from(public: Public) -> Self {
57		Self(public.0)
58	}
59}
60
61/// Bandersnatch secret key.
62#[derive(Clone)]
63pub struct Secret(SecretImpl);
64
65impl Secret {
66	/// Construct a new secret from the given `rng`.
67	pub fn new(rng: &mut impl rand::CryptoRng) -> Self {
68		Self::from_seed(rand::Rng::random(rng))
69	}
70
71	/// Construct from seed.
72	pub fn from_seed(seed: Seed) -> Self {
73		Self(SecretImpl::from_seed(&seed))
74	}
75
76	/// Get public key component.
77	pub fn public(&self) -> Public {
78		let public = self.0.public();
79		let mut raw = [0; PUBLIC_SERIALIZED_SIZE];
80		public.serialize_compressed(raw.as_mut_slice()).expect(ERR_MSG);
81		Public(raw)
82	}
83
84	/// Generate from system entropy.
85	pub fn generate() -> Self {
86		Self::new(&mut rand::rng())
87	}
88}
89
90/// A message signed by a node's Bandersnatch key.
91pub enum Message {
92	/// RingVRF ticket generation and regular block seal.
93	TicketSeal(Entropy, TicketAttempt),
94	/// Fallback block seal.
95	FallbackSeal(Entropy),
96	/// On-chain entropy generation.
97	Entropy(TicketId),
98	// Audit selection entropy for initial tranche
99	AuditInitial(Entropy),
100	// Audit selection entropy for subsequent tranches
101	AuditSubsequent(Entropy, jam_types::WorkReportHash, TrancheIndex),
102}
103
104impl Message {
105	/// Call `f` with the encoded message.
106	pub fn using_encoded<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
107		match self {
108			Self::TicketSeal(entropy, attempt) =>
109				f(concat(&mut [0; 15 + 32 + 1], &[b"jam_ticket_seal", &entropy.0, &[*attempt]])),
110			Self::FallbackSeal(entropy) =>
111				f(concat(&mut [0; 17 + 32], &[b"jam_fallback_seal", &entropy.0])),
112			Self::Entropy(ticket_id) =>
113				f(concat(&mut [0; 11 + 32], &[b"jam_entropy", &ticket_id.0])),
114			Self::AuditInitial(entropy_source) =>
115				f(concat(&mut [0; 9 + 32], &[b"jam_audit", &entropy_source.0])),
116			Self::AuditSubsequent(entropy_source, report, index) => f(concat(
117				&mut [0; 9 + 64 + 1],
118				&[b"jam_audit", &entropy_source.0, &report.0, &[*index]],
119			)),
120		}
121	}
122}
123
124/// Generate VRF input point from raw bytes.
125#[inline(always)]
126fn vrf_input(message: &Message) -> bandersnatch::Input {
127	message
128		.using_encoded(|enc| bandersnatch::Input::new(enc).expect("Elligator2 H2C is infallible"))
129}
130
131/// Bandersnatch VRF types and operations.
132pub mod vrf {
133	use super::*;
134	use ark_vrf::ietf::{Prover, Verifier};
135	use codec::ConstEncodedLen;
136
137	pub(crate) const PROOF_SERIALIZED_SIZE: usize = 64;
138	pub const SIGNATURE_SERIALIZED_SIZE: usize = PREOUT_SERIALIZED_SIZE + PROOF_SERIALIZED_SIZE;
139
140	/// VRF signature.
141	#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen)]
142	pub struct Signature(pub [u8; SIGNATURE_SERIALIZED_SIZE]);
143
144	impl ConstEncodedLen for Signature {}
145
146	impl Signature {
147		/// Generate VRF output bytes from the signature.
148		pub fn vrf_output(&self) -> Entropy {
149			bandersnatch::Output::deserialize_compressed_unchecked(
150				&self.0[..PREOUT_SERIALIZED_SIZE],
151			)
152			.map(|p| {
153				let mut raw = [0u8; 32];
154				raw.copy_from_slice(&p.hash()[..32]);
155				Entropy(raw)
156			})
157			.unwrap_or_default()
158		}
159
160		/// Verify the signature.
161		pub fn vrf_verify(
162			&self,
163			message: &Message,
164			aux_data: &[u8],
165			public: &Public,
166		) -> Result<(), Error> {
167			public.vrf_verify(message, aux_data, self)
168		}
169	}
170
171	impl Secret {
172		/// Creates a VRF signature of `message` and `aux_data`.
173		///
174		/// Auxiliary data (`aux_data`) is signed but doesn't contribute to the VRF output.
175		pub fn vrf_sign(&self, message: &Message, aux_data: &[u8]) -> Signature {
176			let input = vrf_input(message);
177			let output = self.0.output(input);
178			let proof = self.0.prove(input, output, aux_data);
179			let mut raw = [0_u8; SIGNATURE_SERIALIZED_SIZE];
180			output.serialize_compressed(&mut raw[..PREOUT_SERIALIZED_SIZE]).expect(ERR_MSG);
181			proof.serialize_compressed(&mut raw[PREOUT_SERIALIZED_SIZE..]).expect(ERR_MSG);
182			Signature(raw)
183		}
184
185		/// Generate VRF output from the given `message`.
186		pub fn vrf_output(&self, message: &Message) -> Entropy {
187			let input = vrf_input(message);
188			let hash = self.0.output(input).hash();
189			let mut raw = [0u8; 32];
190			raw.copy_from_slice(&hash[..32]);
191			Entropy(raw)
192		}
193	}
194
195	impl Public {
196		/// Verify a VRF signature.
197		pub fn vrf_verify(
198			&self,
199			message: &Message,
200			aux_data: &[u8],
201			signature: &Signature,
202		) -> Result<(), Error> {
203			let public = bandersnatch::Public::deserialize_compressed_unchecked(self.0.as_ref())
204				.map_err(|_| Error::InvalidData)?;
205			let output = bandersnatch::Output::deserialize_compressed_unchecked(
206				&signature.0[..PREOUT_SERIALIZED_SIZE],
207			)
208			.map_err(|_| Error::InvalidData)?;
209			let proof = bandersnatch::IetfProof::deserialize_compressed_unchecked(
210				&signature.0[PREOUT_SERIALIZED_SIZE..],
211			)
212			.map_err(|_| Error::InvalidData)?;
213			let input = vrf_input(message);
214			public.verify(input, output, aux_data, &proof)
215		}
216	}
217
218	impl AsRef<[u8]> for Public {
219		fn as_ref(&self) -> &[u8] {
220			&self.0
221		}
222	}
223}
224
225pub mod ring_vrf {
226	use super::*;
227	use ark_vrf::ring::{Prover, Verifier};
228	pub use bandersnatch::{RingBatchItem, RingBatchVerifier, RingProver, RingVerifier};
229	use bandersnatch::{RingCommitment as RingCommitmentImpl, RingProofParams};
230
231	pub const RING_COMMITMENT_SERIALIZED_SIZE: usize = 144;
232
233	pub const RING_PROOF_SERIALIZED_SIZE: usize = 752;
234
235	pub const RING_SIGNATURE_SERIALIZED_SIZE: usize =
236		PREOUT_SERIALIZED_SIZE + RING_PROOF_SERIALIZED_SIZE;
237
238	#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen)]
239	pub struct RingCommitment(pub [u8; RING_COMMITMENT_SERIALIZED_SIZE]);
240
241	/// Ring VRF signature.
242	#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen)]
243	pub struct Signature(pub [u8; RING_SIGNATURE_SERIALIZED_SIZE]);
244
245	impl Signature {
246		/// Verify a ring-vrf signature.
247		///
248		/// The signature is verifiable if it has been produced by a member of the ring
249		/// from which the [`RingVerifier`] has been constructed.
250		pub fn ring_vrf_verify(
251			&self,
252			message: &Message,
253			aux_data: &[u8],
254			verifier: &RingVerifier,
255		) -> Result<(), Error> {
256			let output = bandersnatch::Output::deserialize_compressed_unchecked(
257				&self.0[..PREOUT_SERIALIZED_SIZE],
258			)
259			.map_err(|_| Error::InvalidData)?;
260			let proof = bandersnatch::RingProof::deserialize_compressed_unchecked(
261				&self.0[PREOUT_SERIALIZED_SIZE..],
262			)
263			.map_err(|_| Error::InvalidData)?;
264			let input = vrf_input(message);
265			bandersnatch::Public::verify(input, output, aux_data, &proof, verifier)
266		}
267
268		/// Prepare a ring-vrf signature for batch verification.
269		///
270		/// This is the cheap per-proof step that deserializes the output and proof,
271		/// then computes transcript data without performing pairings or MSM checks.
272		/// The actual verification is deferred to [`RingBatchVerifier::verify`].
273		pub fn ring_vrf_prepare(
274			&self,
275			message: &Message,
276			aux_data: &[u8],
277			batch_verifier: &RingBatchVerifier,
278		) -> Result<RingBatchItem, Error> {
279			let output = bandersnatch::Output::deserialize_compressed_unchecked(
280				&self.0[..PREOUT_SERIALIZED_SIZE],
281			)
282			.map_err(|_| Error::InvalidData)?;
283			let proof = bandersnatch::RingProof::deserialize_compressed_unchecked(
284				&self.0[PREOUT_SERIALIZED_SIZE..],
285			)
286			.map_err(|_| Error::InvalidData)?;
287			let input = vrf_input(message);
288			Ok(batch_verifier.prepare(input, output, aux_data, &proof))
289		}
290
291		/// Generate VRF output bytes from the signature.
292		pub fn vrf_output(&self) -> Entropy {
293			bandersnatch::Output::deserialize_compressed_unchecked(
294				&self.0[..PREOUT_SERIALIZED_SIZE],
295			)
296			.map(|p| {
297				let mut raw = [0u8; 32];
298				raw.copy_from_slice(&p.hash()[..32]);
299				Entropy(raw)
300			})
301			.unwrap_or_default()
302		}
303	}
304
305	impl Secret {
306		/// Construct a Ring VRF signature.
307		///
308		/// Auxiliary data (`aux_data`) is signed but doesn't contribute to the VRF output.
309		pub fn ring_vrf_sign(
310			&self,
311			message: &Message,
312			aux_data: &[u8],
313			prover: &RingProver,
314		) -> Signature {
315			let input = vrf_input(message);
316			let output = self.0.output(input);
317			let proof = self.0.prove(input, output, aux_data, prover);
318			let mut raw = [0_u8; RING_SIGNATURE_SERIALIZED_SIZE];
319			output.serialize_compressed(&mut raw[..PREOUT_SERIALIZED_SIZE]).expect(ERR_MSG);
320			proof.serialize_compressed(&mut raw[PREOUT_SERIALIZED_SIZE..]).expect(ERR_MSG);
321			Signature(raw)
322		}
323	}
324
325	/// Ring context.
326	#[derive(Clone)]
327	pub struct RingContext;
328
329	impl RingContext {
330		/// Get a reference to the ring context instance.
331		///
332		/// Params are constructed once, for rings of the maximum supported number
333		/// of validators (`max_val_count`), which here acts as a capacity bound rather
334		/// than the actual validator set size. Rings with fewer keys are implicitly
335		/// padded up to capacity by the underlying proof system, so the same params
336		/// serve any active validator set size.
337		pub fn params() -> &'static RingProofParams {
338			use std::sync::OnceLock;
339			static PARAMS: OnceLock<RingProofParams> = OnceLock::new();
340			PARAMS.get_or_init(|| {
341				use bandersnatch::PcsParams;
342				// Zcash SRS file derived from (https://zfnd.org/conclusion-of-the-powers-of-tau-ceremony).
343				let buf = include_bytes!(concat!(
344					env!("CARGO_MANIFEST_DIR"),
345					"/data/zcash-srs-2-11-uncompressed.bin"
346				));
347				let pcs_params = PcsParams::deserialize_uncompressed_unchecked(&mut &buf[..])
348					.expect("Error deserializing PcsParam");
349				RingProofParams::from_pcs_params(jam_types::max_val_count() as usize, pcs_params)
350					.expect("Error constructing RingProofParams from PcsParams")
351			})
352		}
353
354		/// Get ring prover for the key at index `public_index` in the `public_keys` sequence.
355		///
356		/// Public keys sequence order matters.
357		pub fn prover(public_keys: &[Public], public_index: usize) -> RingProver {
358			let params = Self::params();
359			let pks = public_to_affine(public_keys);
360			let prover_key = params.prover_key(&pks);
361			params.prover(prover_key, public_index)
362		}
363
364		/// Get ring verifier for the `public_keys` sequence.
365		///
366		/// Public keys sequence order matters.
367		pub fn verifier(public_keys: &[Public]) -> RingVerifier {
368			let params = Self::params();
369			let pks = public_to_affine(public_keys);
370			let verifier_key = params.verifier_key(&pks);
371			params.verifier(verifier_key)
372		}
373
374		/// Get ring commitment which may be used for lazy ring verifier construction.
375		///
376		/// Public keys sequence order matters.
377		pub fn commitment(public_keys: &[Public]) -> RingCommitment {
378			let params = Self::params();
379			let pks = public_to_affine(public_keys);
380			let verifier_key = params.verifier_key(&pks);
381			let commitment = verifier_key.commitment();
382			let mut raw = [0; RING_COMMITMENT_SERIALIZED_SIZE];
383			commitment.serialize_compressed(&mut raw[..]).expect(ERR_MSG);
384			RingCommitment(raw)
385		}
386
387		/// Construct a ring verifier using the ring commitment.
388		pub fn verifier_from_commitment(commitment: RingCommitment) -> Result<RingVerifier, Error> {
389			let params = Self::params();
390			let mut raw = &commitment.0[..];
391			let commitment = RingCommitmentImpl::deserialize_compressed_unchecked(&mut raw)
392				.map_err(|_| Error::InvalidData)?;
393			let verifier_key = params.verifier_key_from_commitment(commitment);
394			Ok(params.verifier(verifier_key))
395		}
396	}
397
398	/// Maps a sequence of public keys to affine points on the Bandersnatch curve.
399	///
400	/// Any Public that cannot be mapped to a specific point will have the padding point used in its
401	/// place.
402	#[inline(always)]
403	fn public_to_affine(pks: &[Public]) -> Vec<AffinePoint> {
404		pks.iter()
405			.map(|pk| {
406				AffinePoint::deserialize_compressed_unchecked(&pk.0[..])
407					.unwrap_or(RingProofParams::padding_point())
408			})
409			.collect()
410	}
411}
412
413#[cfg(all(test, feature = "full-test-suite"))]
414mod tests {
415	#![allow(clippy::unwrap_used)]
416	use super::{ring_vrf::*, vrf::*, *};
417	use crate::NewNull;
418	use ring_vrf::RING_COMMITMENT_SERIALIZED_SIZE;
419
420	const SEED_SERIALIZED_SIZE: usize = 32;
421	const RING_KEYSET_SIZE: usize = 1023;
422
423	const DEV_SEED: [u8; SEED_SERIALIZED_SIZE] = [0xcb; SEED_SERIALIZED_SIZE];
424
425	#[allow(dead_code)]
426	fn serialize<T: CanonicalSerialize>(obj: &T) -> Vec<u8> {
427		let mut buf = Vec::new();
428		obj.serialize_compressed(&mut buf).unwrap();
429		buf
430	}
431
432	#[test]
433	fn backend_assumptions_check() {
434		use ark_vrf::reexports::ark_std;
435		use bandersnatch::RingProofParams;
436		const EXPECTED_DOMAIN_OVERHEAD: usize = 257;
437
438		let ctx = RingProofParams::from_seed(RING_KEYSET_SIZE, [0; 32]);
439
440		let expected_domain_size = 1 << ark_std::log2(RING_KEYSET_SIZE + EXPECTED_DOMAIN_OVERHEAD);
441		assert_eq!(ctx.max_ring_size(), expected_domain_size - EXPECTED_DOMAIN_OVERHEAD);
442
443		let pks: Vec<_> =
444			(0..16).map(|i| SecretImpl::from_seed(&[i as u8; 32]).public().0).collect();
445
446		let secret = SecretImpl::from_seed(&[0u8; 32]);
447
448		let public = secret.public();
449		assert_eq!(public.compressed_size(), PUBLIC_SERIALIZED_SIZE);
450
451		let input = vrf_input(&Message::FallbackSeal(Default::default()));
452		let output = secret.output(input);
453		assert_eq!(output.compressed_size(), PREOUT_SERIALIZED_SIZE);
454
455		let prover_key = ctx.prover_key(&pks);
456		let prover = ctx.prover(prover_key, 0);
457
458		let verifier_key = ctx.verifier_key(&pks);
459		let commitment = verifier_key.commitment();
460		assert_eq!(commitment.compressed_size(), RING_COMMITMENT_SERIALIZED_SIZE);
461
462		let ietf_proof = {
463			use ark_vrf::ietf::Prover;
464			secret.prove(input, output, [])
465		};
466		assert_eq!(ietf_proof.compressed_size(), PROOF_SERIALIZED_SIZE);
467
468		let ring_proof = {
469			use ark_vrf::ring::Prover;
470			secret.prove(input, output, [], &prover)
471		};
472		assert_eq!(ring_proof.compressed_size(), RING_PROOF_SERIALIZED_SIZE);
473
474		// Well known Bandersnatch padding point defined within the Bandersnatch-EC-VRFs
475		// specification: https://github.com/davxy/bandersnatch-vrfs-spec
476		let padding_raw = [
477			0x92, 0xca, 0x79, 0xe6, 0x1d, 0xd9, 0x0c, 0x15, 0x73, 0xa8, 0x69, 0x3f, 0x19, 0x9b,
478			0xf6, 0xe1, 0xe8, 0x68, 0x35, 0xcc, 0x71, 0x5c, 0xdc, 0xf9, 0x3f, 0x5e, 0xf2, 0x22,
479			0x56, 0x00, 0x23, 0xaa,
480		];
481		let padding_point = AffinePoint::deserialize_compressed(&padding_raw[..]).unwrap();
482		assert_eq!(RingProofParams::padding_point(), padding_point);
483	}
484
485	fn encode_decode<T: Encode + Decode + PartialEq + std::fmt::Debug>(expected_len: usize) {
486		let obj = T::new_null();
487		let buf = obj.encode();
488		assert_eq!(buf.len(), expected_len);
489		let dec = T::decode(&mut buf.as_slice()).unwrap();
490		assert_eq!(obj, dec);
491	}
492
493	#[test]
494	fn codec_works() {
495		encode_decode::<Public>(PUBLIC_SERIALIZED_SIZE);
496		encode_decode::<vrf::Signature>(SIGNATURE_SERIALIZED_SIZE);
497		encode_decode::<ring_vrf::Signature>(RING_SIGNATURE_SERIALIZED_SIZE);
498	}
499
500	#[test]
501	fn vrf_sign_verify() {
502		let secret = Secret::from_seed(DEV_SEED);
503		let public = secret.public();
504
505		let input = Message::FallbackSeal(Default::default());
506		let ad = b"data";
507
508		let signature = secret.vrf_sign(&input, ad);
509
510		assert!(public.vrf_verify(&input, ad, &signature).is_ok());
511
512		// Fail with bad additional data
513		assert!(public.vrf_verify(&input, b"bad", &signature).is_err());
514
515		// Fail with bad input
516		assert!(
517			public
518				.vrf_verify(&Message::FallbackSeal([1; 32].into()), ad, &signature)
519				.is_err()
520		);
521	}
522
523	#[test]
524	fn vrf_output_hash_matches() {
525		let secret = Secret::from_seed(DEV_SEED);
526
527		let input = Message::FallbackSeal(Default::default());
528		let signature = secret.vrf_sign(&input, b"data");
529
530		let output1 = secret.vrf_output(&input);
531		let output2 = signature.vrf_output();
532		assert_eq!(output1, output2);
533	}
534
535	#[test]
536	fn ring_vrf_sign_verify() {
537		let mut pks: Vec<_> = (0..16).map(|i| Secret::from_seed([i as u8; 32]).public()).collect();
538		assert!(pks.len() <= RING_KEYSET_SIZE);
539
540		let secret = Secret::from_seed(DEV_SEED);
541
542		let input = Message::FallbackSeal(Default::default());
543		let ad = b"data";
544
545		// Just pick one index to patch with the used public key
546		let prover_index = 3;
547		pks[prover_index] = secret.public();
548
549		let prover = RingContext::prover(&pks, prover_index);
550		let signature = secret.ring_vrf_sign(&input, ad, &prover);
551
552		let verifier = RingContext::verifier(&pks);
553		assert!(signature.ring_vrf_verify(&input, ad, &verifier).is_ok());
554
555		// Fail with bad additional data
556		assert!(signature.ring_vrf_verify(&input, b"bad", &verifier).is_err());
557
558		// Fail with bad input
559		assert!(
560			signature
561				.ring_vrf_verify(&Message::FallbackSeal([1; 32].into()), ad, &verifier)
562				.is_err()
563		);
564	}
565
566	#[test]
567	fn ring_vrf_sign_verify_with_out_of_ring_key() {
568		let pks: Vec<_> = (0..16).map(|i| Secret::from_seed([i as u8; 32]).public()).collect();
569		let secret = Secret::from_seed(DEV_SEED);
570
571		let input = Message::FallbackSeal(Default::default());
572		let ad = b"data";
573
574		// Fair assuption that secret.public() != pks[0]
575		let prover = RingContext::prover(&pks, 0);
576		let signature = secret.ring_vrf_sign(&input, ad, &prover);
577
578		let verifier = RingContext::verifier(&pks);
579		assert!(signature.ring_vrf_verify(&input, ad, &verifier).is_err());
580	}
581
582	#[test]
583	fn ring_vrf_batch_verify() {
584		let mut pks: Vec<_> = (0..16).map(|i| Secret::from_seed([i as u8; 32]).public()).collect();
585		assert!(pks.len() <= RING_KEYSET_SIZE);
586
587		let secrets = [
588			Secret::from_seed(DEV_SEED),
589			Secret::from_seed([0xab; SEED_SERIALIZED_SIZE]),
590			Secret::from_seed([0xcd; SEED_SERIALIZED_SIZE]),
591		];
592		let indices = [3, 7, 12];
593		for (i, secret) in secrets.iter().enumerate() {
594			pks[indices[i]] = secret.public();
595		}
596
597		let messages = [
598			Message::FallbackSeal(Default::default()),
599			Message::FallbackSeal([1; 32].into()),
600			Message::FallbackSeal([2; 32].into()),
601		];
602		let ad = b"data";
603
604		let signatures: Vec<_> = secrets
605			.iter()
606			.enumerate()
607			.map(|(i, secret)| {
608				let prover = RingContext::prover(&pks, indices[i]);
609				secret.ring_vrf_sign(&messages[i], ad, &prover)
610			})
611			.collect();
612
613		// Batch verify - should succeed
614		let verifier = RingContext::verifier(&pks);
615		let mut batch = RingBatchVerifier::new(verifier);
616		for (i, sig) in signatures.iter().enumerate() {
617			let item = sig.ring_vrf_prepare(&messages[i], ad, &batch).unwrap();
618			batch.push_prepared(item);
619		}
620		assert!(batch.verify().is_ok());
621
622		// Batch verify with one bad proof - should fail
623		let verifier = RingContext::verifier(&pks);
624		let mut batch = RingBatchVerifier::new(verifier);
625		for (i, sig) in signatures.iter().enumerate() {
626			let msg = if i == 2 { &messages[0] } else { &messages[i] };
627			let item = sig.ring_vrf_prepare(msg, ad, &batch).unwrap();
628			batch.push_prepared(item);
629		}
630		assert!(batch.verify().is_err());
631	}
632
633	#[test]
634	fn ring_vrf_make_bytes_matches() {
635		let mut pks: Vec<_> = (0..16).map(|i| Secret::from_seed([i as u8; 32]).public()).collect();
636		assert!(pks.len() <= RING_KEYSET_SIZE);
637
638		let secret = Secret::from_seed(DEV_SEED);
639
640		// Just pick one index to patch with the used public key
641		let prover_index = 3;
642		pks[prover_index] = secret.public();
643
644		let input = Message::FallbackSeal(Default::default());
645
646		let prover = RingContext::prover(&pks, prover_index);
647		let signature = secret.ring_vrf_sign(&input, b"data", &prover);
648
649		let output1 = secret.vrf_output(&input);
650		let output2 = signature.vrf_output();
651		assert_eq!(output1, output2);
652	}
653}