use core::borrow::Borrow;
use core::iter::once;
#[cfg(feature = "alloc")]
use alloc::{boxed::Box, vec::Vec};
#[cfg(feature = "std")]
use std::{boxed::Box, vec::Vec};
use rand::prelude::*; use rand_chacha::ChaChaRng;
use curve25519_dalek::constants;
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::VartimeMultiscalarMul;
use merlin::Transcript;
use super::*;
use crate::context::SigningTranscript;
use crate::points::RistrettoBoth;
pub const VRF_OUTPUT_LENGTH : usize = 32;
pub const VRF_PROOF_LENGTH : usize = 64;
pub const VRF_PROOF_BATCHABLE_LENGTH : usize = 96;
pub fn vrf_hash<T: SigningTranscript>(mut t: T) -> RistrettoBoth {
let mut b = [0u8; 64];
t.challenge_bytes(b"VRFHash", &mut b);
RistrettoBoth::from_point(RistrettoPoint::from_uniform_bytes(&b))
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VRFOutput(pub [u8; PUBLIC_KEY_LENGTH]);
impl VRFOutput {
const DESCRIPTION: &'static str =
"A Ristretto Schnorr VRF output represented as a 32-byte Ristretto compressed point";
#[inline]
pub fn to_bytes(&self) -> [u8; VRF_OUTPUT_LENGTH] {
self.0
}
#[inline]
pub fn as_bytes(&self) -> &[u8; VRF_OUTPUT_LENGTH] {
&self.0
}
#[inline]
pub fn from_bytes(bytes: &[u8]) -> SignatureResult<VRFOutput> {
if bytes.len() != VRF_OUTPUT_LENGTH {
return Err(SignatureError::BytesLengthError {
name: "VRFOutput",
description: VRFOutput::DESCRIPTION,
length: VRF_OUTPUT_LENGTH
});
}
let mut bits: [u8; 32] = [0u8; 32];
bits.copy_from_slice(&bytes[..32]);
Ok(VRFOutput(bits))
}
pub fn attach_input_hash<T>(&self, t: T) -> SignatureResult<VRFInOut>
where T: SigningTranscript {
let input = vrf_hash(t);
let output = RistrettoBoth::from_bytes_ser("VRFOutput", VRFOutput::DESCRIPTION, &self.0)?;
Ok(VRFInOut { input, output })
}
}
serde_boilerplate!(VRFOutput);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VRFInOut {
pub input: RistrettoBoth,
pub output: RistrettoBoth,
}
impl SecretKey {
pub fn vrf_create_from_point(&self, input: RistrettoBoth) -> VRFInOut {
let output = RistrettoBoth::from_point(&self.key * input.as_point());
VRFInOut { input, output }
}
pub fn vrf_create_from_compressed_point(&self, input: &VRFOutput) -> SignatureResult<VRFInOut> {
let input = RistrettoBoth::from_compressed(CompressedRistretto(input.0)) ?;
Ok(self.vrf_create_from_point(input))
}
pub fn vrf_create_hash<T: SigningTranscript>(&self, t: T) -> VRFInOut {
self.vrf_create_from_point(vrf_hash(t))
}
}
impl VRFInOut {
pub fn as_output_bytes(&self) -> &[u8; 32] {
self.output.as_compressed().as_bytes()
}
pub fn to_output(&self) -> VRFOutput {
VRFOutput(self.output.as_compressed().to_bytes())
}
pub fn commit<T: SigningTranscript>(&self, t: &mut T) {
t.commit_point(b"vrf-in", self.input.as_compressed());
t.commit_point(b"vrf-out", self.output.as_compressed());
}
pub fn make_bytes<B: Default + AsMut<[u8]>>(&self, context: &'static [u8]) -> B {
let mut t = Transcript::new(context);
self.commit(&mut t);
let mut seed = B::default();
t.challenge_bytes(b"", seed.as_mut());
seed
}
pub fn make_rng<R: SeedableRng>(&self, context: &'static [u8]) -> R {
R::from_seed(self.make_bytes::<R::Seed>(context))
}
pub fn make_chacharng(&self, context: &'static [u8]) -> ChaChaRng {
self.make_rng::<ChaChaRng>(context)
}
pub fn make_merlin_rng(&self, context: &'static [u8]) -> merlin::TranscriptRng {
struct ZeroFakeRng;
impl ::rand::RngCore for ZeroFakeRng {
fn next_u32(&mut self) -> u32 { panic!() }
fn next_u64(&mut self) -> u64 { panic!() }
fn fill_bytes(&mut self, dest: &mut [u8]) {
for i in dest.iter_mut() { *i = 0; }
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl ::rand::CryptoRng for ZeroFakeRng {}
let mut t = Transcript::new(context);
self.commit(&mut t);
t.build_rng().finalize(&mut ZeroFakeRng)
}
}
fn challenge_scalar_128<T: SigningTranscript>(mut t: T) -> Scalar {
let mut s = [0u8; 16];
t.challenge_bytes(b"", &mut s);
Scalar::from(u128::from_le_bytes(s))
}
impl PublicKey {
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn vrfs_merge<B>(&self, ps: &[B]) -> VRFInOut
where
B: Borrow<VRFInOut>,
{
let mut t = ::merlin::Transcript::new(b"MergeVRFs");
t.commit_point(b"pk", self.as_compressed());
for p in ps.iter() {
p.borrow().commit(&mut t);
}
let mut input = ps[0].borrow().input.as_point().clone();
let mut output = ps[0].borrow().output.as_point().clone();
for p in ps.iter().skip(1).map(|p| p.borrow()) {
let mut t0 = t.clone();
p.commit(&mut t0);
let z = challenge_scalar_128(t0);
input += &z * p.input.as_point();
output += &z * p.output.as_point();
}
VRFInOut {
input: RistrettoBoth::from_point(input),
output: RistrettoBoth::from_point(output),
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn vrfs_merge_vartime<B>(&self, ps: &[B]) -> VRFInOut
where
B: Borrow<VRFInOut>,
{
let mut t = ::merlin::Transcript::new(b"MergeVRFs");
t.commit_point(b"pk", self.as_compressed());
for p in ps.iter() {
p.borrow().commit(&mut t);
}
let zs: Vec<Scalar> = ps.iter().skip(1)
.map(|p| {
let mut t0 = t.clone();
p.borrow().commit(&mut t0);
challenge_scalar_128(t0)
}).collect();
let one = Scalar::one();
let zf = || once(&one).chain(zs.iter());
VRFInOut {
input: RistrettoBoth::from_point(RistrettoPoint::vartime_multiscalar_mul(
zf(),
ps.iter().map(|p| p.borrow().input.as_point()),
)),
output: RistrettoBoth::from_point(RistrettoPoint::vartime_multiscalar_mul(
zf(),
ps.iter().map(|p| p.borrow().output.as_point()),
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)] pub struct VRFProof {
c: Scalar,
s: Scalar,
}
impl VRFProof {
const DESCRIPTION : &'static str = "A Ristretto Schnorr VRF proof without batch verification support, which consists of two scalars, making it 64 bytes.";
#[inline]
pub fn to_bytes(&self) -> [u8; VRF_PROOF_LENGTH] {
let mut bytes = [0u8; VRF_PROOF_LENGTH];
bytes[..32].copy_from_slice(&self.c.as_bytes()[..]);
bytes[32..].copy_from_slice(&self.s.as_bytes()[..]);
bytes
}
#[inline]
pub fn from_bytes(bytes: &[u8]) -> SignatureResult<VRFProof> {
if bytes.len() != VRF_PROOF_LENGTH {
return Err(SignatureError::BytesLengthError {
name: "VRFProof",
description: VRFProof::DESCRIPTION,
length: VRF_PROOF_LENGTH
});
}
let mut c: [u8; 32] = [0u8; 32];
let mut s: [u8; 32] = [0u8; 32];
c.copy_from_slice(&bytes[..32]);
s.copy_from_slice(&bytes[32..]);
let c = Scalar::from_canonical_bytes(c).ok_or(SignatureError::ScalarFormatError) ?;
let s = Scalar::from_canonical_bytes(s).ok_or(SignatureError::ScalarFormatError) ?;
Ok(VRFProof { c, s })
}
}
serde_boilerplate!(VRFProof);
#[derive(Debug, Clone, PartialEq, Eq)] #[allow(non_snake_case)]
pub struct VRFProofBatchable {
R: CompressedRistretto,
Hr: CompressedRistretto,
s: Scalar,
}
impl VRFProofBatchable {
const DESCRIPTION : &'static str = "A Ristretto Schnorr VRF proof that supports batch verification, which consists of two Ristretto compressed points and one scalar, making it 96 bytes.";
#[allow(non_snake_case)]
#[inline]
pub fn to_bytes(&self) -> [u8; VRF_PROOF_BATCHABLE_LENGTH] {
let mut bytes = [0u8; VRF_PROOF_BATCHABLE_LENGTH];
bytes[0..32].copy_from_slice(&self.R.as_bytes()[..]);
bytes[32..64].copy_from_slice(&self.Hr.as_bytes()[..]);
bytes[64..96].copy_from_slice(&self.s.as_bytes()[..]);
bytes
}
#[allow(non_snake_case)]
#[inline]
pub fn from_bytes(bytes: &[u8]) -> SignatureResult<VRFProofBatchable> {
if bytes.len() != VRF_PROOF_BATCHABLE_LENGTH {
return Err(SignatureError::BytesLengthError {
name: "VRFProofBatchable",
description: VRFProofBatchable::DESCRIPTION,
length: VRF_PROOF_BATCHABLE_LENGTH,
});
}
let mut R: [u8; 32] = [0u8; 32];
let mut Hr: [u8; 32] = [0u8; 32];
let mut s: [u8; 32] = [0u8; 32];
R.copy_from_slice(&bytes[0..32]);
Hr.copy_from_slice(&bytes[32..64]);
s.copy_from_slice(&bytes[64..96]);
let s = Scalar::from_canonical_bytes(s).ok_or(SignatureError::ScalarFormatError) ?;
Ok(VRFProofBatchable { R: CompressedRistretto(R), Hr: CompressedRistretto(Hr), s })
}
#[allow(non_snake_case)]
pub fn shorten_dleq<T>(&self, mut t: T, public: &PublicKey, p: &VRFInOut) -> VRFProof
where T: SigningTranscript,
{
t.proto_name(b"DLEQProof");
t.commit_point(b"h", p.input.as_compressed());
t.commit_point(b"R=g^r", &self.R);
t.commit_point(b"h^r", &self.Hr);
t.commit_point(b"pk", public.as_compressed());
t.commit_point(b"h^sk", p.output.as_compressed());
VRFProof {
c: t.challenge_scalar(b""), s: self.s,
}
}
pub fn shorten_vrf<T>( &self, public: &PublicKey, t: T, out: &VRFOutput) -> SignatureResult<VRFProof>
where T: SigningTranscript,
{
let p = out.attach_input_hash(t)?; let t0 = Transcript::new(b"VRF"); Ok(self.shorten_dleq(t0, public, &p))
}
}
serde_boilerplate!(VRFProofBatchable);
impl Keypair {
#[allow(non_snake_case)]
pub fn dleq_proove<T>(&self, mut t: T, p: &VRFInOut) -> (VRFProof, VRFProofBatchable)
where
T: SigningTranscript,
{
t.proto_name(b"DLEQProof");
t.commit_point(b"h", p.input.as_compressed());
let r = t.witness_scalar(&[&self.secret.nonce]);
let R = (&r * &constants::RISTRETTO_BASEPOINT_TABLE).compress();
t.commit_point(b"R=g^r", &R);
let Hr = (&r * p.input.as_point()).compress();
t.commit_point(b"h^r", &Hr);
t.commit_point(b"pk", self.public.as_compressed());
t.commit_point(b"h^sk", p.output.as_compressed());
let c = t.challenge_scalar(b""); let s = &r - &(&c * &self.secret.key);
(VRFProof { c, s }, VRFProofBatchable { R, Hr, s })
}
pub fn vrf_sign<T: SigningTranscript>(&self, t: T) -> (VRFInOut, VRFProof, VRFProofBatchable) {
let p = self.secret.vrf_create_hash(t);
let t0 = Transcript::new(b"VRF"); let (proof, proof_batchable) = self.dleq_proove(t0, &p);
(p, proof, proof_batchable)
}
pub fn vrf_sign_n_check<T,F>(&self, t: T, mut check: F) -> Option<(VRFInOut, VRFProof, VRFProofBatchable)>
where T: SigningTranscript,
F: FnMut(&VRFInOut) -> bool
{
let p = self.secret.vrf_create_hash(t);
if ! check(&p) { return None; }
let t0 = Transcript::new(b"VRF"); let (proof, proof_batchable) = self.dleq_proove(t0, &p);
Some((p, proof, proof_batchable))
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn vrfs_sign<T, I>(&self, ts: I) -> (Box<[VRFInOut]>, VRFProof, VRFProofBatchable)
where
T: SigningTranscript,
I: IntoIterator<Item = T>,
{
let ps = ts.into_iter()
.map(|t| self.secret.vrf_create_hash(t))
.collect::<Vec<VRFInOut>>();
let p = self.public.vrfs_merge_vartime(&ps);
let t0 = Transcript::new(b"VRF");
let (proof, proof_batchable) = self.dleq_proove(t0, &p);
(ps.into_boxed_slice(), proof, proof_batchable)
}
}
impl PublicKey {
#[allow(non_snake_case)]
pub fn dleq_verify<T>(
&self,
mut t: T,
p: &VRFInOut,
proof: &VRFProof,
) -> SignatureResult<VRFProofBatchable>
where
T: SigningTranscript,
{
t.proto_name(b"DLEQProof");
t.commit_point(b"h", p.input.as_compressed());
let R = RistrettoPoint::vartime_double_scalar_mul_basepoint(
&proof.c,
self.as_point(),
&proof.s,
)
.compress();
t.commit_point(b"R=g^r", &R);
let Hr = RistrettoPoint::vartime_multiscalar_mul(
&[proof.c, proof.s],
&[*p.output.as_point(), *p.input.as_point()],
)
.compress();
t.commit_point(b"h^r", &Hr);
t.commit_point(b"pk", self.as_compressed());
t.commit_point(b"h^sk", p.output.as_compressed());
let VRFProof { c, s } = *proof;
if c == t.challenge_scalar(b"") {
Ok(VRFProofBatchable { R, Hr, s }) } else {
Err(SignatureError::EquationFalse)
}
}
pub fn vrf_verify<T: SigningTranscript>(
&self,
t: T,
out: &VRFOutput,
proof: &VRFProof,
) -> SignatureResult<(VRFInOut, VRFProofBatchable)> {
let p = out.attach_input_hash(t)?;
let t0 = Transcript::new(b"VRF"); let proof_batchable = self.dleq_verify(t0, &p, proof)?;
Ok((p, proof_batchable))
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn vrfs_verify<T, I, O>(
&self,
transcripts: I,
outs: &[O],
proof: &VRFProof,
) -> SignatureResult<(Box<[VRFInOut]>, VRFProofBatchable)>
where
T: SigningTranscript,
I: IntoIterator<Item = T>,
O: Borrow<VRFOutput>,
{
let mut ts = transcripts.into_iter();
let ps = ts.by_ref().zip(outs)
.map(|(t, out)| out.borrow().attach_input_hash(t))
.collect::<SignatureResult<Vec<VRFInOut>>>()?;
assert!(ts.next().is_none(), "Too few VRF outputs for VRF inputs.");
assert!(
ps.len() == outs.len(),
"Too few VRF inputs for VRF outputs."
);
let p = self.vrfs_merge_vartime(&ps[..]);
let t0 = Transcript::new(b"VRF"); let proof_batchable = self.dleq_verify(t0, &p, proof)?;
Ok((ps.into_boxed_slice(), proof_batchable))
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[allow(non_snake_case)]
pub fn dleq_verify_batch(
ps: &[VRFInOut],
proofs: &[VRFProofBatchable],
public_keys: &[PublicKey],
) -> bool {
use curve25519_dalek::traits::IsIdentity;
const ASSERT_MESSAGE: &'static [u8] = b"The number of messages/transcripts / input points, output points, proofs, and public keys must be equal.";
assert!(ps.len() == proofs.len(), ASSERT_MESSAGE);
assert!(proofs.len() == public_keys.len(), ASSERT_MESSAGE);
let mut rng = rand::prelude::thread_rng();
let zz: Vec<Scalar> = proofs.iter()
.map(|_| Scalar::from(rng.gen::<u128>()))
.collect();
let z_s: Vec<Scalar> = zz.iter().zip(proofs)
.map(|(z, proof)| z * proof.s)
.collect();
let B_coefficient: Scalar = z_s.iter().sum();
let t0 = Transcript::new(b"VRF");
let z_c: Vec<Scalar> = zz.iter().enumerate()
.map( |(i, z)| z * proofs[i].shorten_dleq(t0.clone(), &public_keys[i], &ps[i]).c )
.collect();
let b = RistrettoPoint::optional_multiscalar_mul(
zz.iter().map(|z| -z)
.chain(z_c.iter().cloned())
.chain(once(B_coefficient)),
proofs.iter().map(|proof| proof.R.decompress())
.chain(public_keys.iter().map(|pk| Some(*pk.as_point())))
.chain(once(Some(constants::RISTRETTO_BASEPOINT_POINT))),
)
.map(|id| id.is_identity())
.unwrap_or(false);
b & RistrettoPoint::optional_multiscalar_mul(
zz.iter().map(|z| -z)
.chain(z_c)
.chain(z_s),
proofs.iter().map(|proof| proof.Hr.decompress())
.chain(ps.iter().map(|p| Some(*p.output.as_point())))
.chain(ps.iter().map(|p| Some(*p.input.as_point()))),
)
.map(|id| id.is_identity())
.unwrap_or(false)
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn vrf_verify_batch<T, I>(
transcripts: I,
outs: &[VRFOutput],
proofs: &[VRFProofBatchable],
public_keys: &[PublicKey],
) -> SignatureResult<Box<[VRFInOut]>>
where
T: SigningTranscript,
I: IntoIterator<Item = T>,
{
let mut ts = transcripts.into_iter();
let ps = ts.by_ref()
.zip(outs)
.map(|(t, out)| out.attach_input_hash(t))
.collect::<SignatureResult<Vec<VRFInOut>>>()?;
assert!(ts.next().is_none(), "Too few VRF outputs for VRF inputs.");
assert!(
ps.len() == outs.len(),
"Too few VRF inputs for VRF outputs."
);
if dleq_verify_batch(&ps[..], proofs, public_keys) {
Ok(ps.into_boxed_slice())
} else {
Err(SignatureError::EquationFalse)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::prelude::*;
use std::vec::Vec;
#[test]
fn vrf_single() {
let keypair1 = Keypair::generate(&mut thread_rng());
let ctx = signing_context(b"yo!");
let msg = b"meow";
let (io1, proof1, proof1batchable) = keypair1.vrf_sign(ctx.bytes(msg));
let out1 = &io1.to_output();
assert_eq!(
proof1,
proof1batchable
.shorten_vrf(&keypair1.public, ctx.bytes(msg), &out1)
.unwrap(),
"Oops `shorten_vrf` failed"
);
let (io1too, proof1too) = keypair1
.public
.vrf_verify(ctx.bytes(msg), &out1, &proof1)
.expect("Correct VRF verification failed!");
assert_eq!(
io1too, io1,
"Output differs between signing and verification!"
);
assert_eq!(
proof1batchable, proof1too,
"VRF verification yielded incorrect batchable proof"
);
assert_eq!(
keypair1.vrf_sign(ctx.bytes(msg)).0,
io1,
"Rerunning VRF gave different output"
);
assert!(
keypair1
.public
.vrf_verify(ctx.bytes(b"not meow"), &out1, &proof1)
.is_err(),
"VRF verification with incorrect message passed!"
);
let keypair2 = Keypair::generate(&mut thread_rng());
assert!(
keypair2
.public
.vrf_verify(ctx.bytes(msg), &out1, &proof1)
.is_err(),
"VRF verification with incorrect signer passed!"
);
let (io2, _proof2, _proof2batchable) = keypair2.vrf_sign(ctx.bytes(msg));
let out2 = &io2.to_output();
let t0 = Transcript::new(b"VRF");
let io21 = keypair2
.secret
.vrf_create_from_compressed_point(out1)
.unwrap();
let proofs21 = keypair2.dleq_proove(t0.clone(), &io21);
let io12 = keypair1
.secret
.vrf_create_from_compressed_point(out2)
.unwrap();
let proofs12 = keypair1.dleq_proove(t0.clone(), &io12);
assert_eq!(io12.output, io21.output, "Sequential two-party VRF failed");
assert_eq!(
proofs21.0,
proofs21.1.shorten_dleq(t0.clone(), &keypair2.public, &io21),
"Oops `shorten_dleq` failed"
);
assert_eq!(
proofs12.0,
proofs12.1.shorten_dleq(t0.clone(), &keypair1.public, &io12),
"Oops `shorten_dleq` failed"
);
assert!(keypair1
.public
.dleq_verify(t0.clone(), &io12, &proofs12.0)
.is_ok());
assert!(keypair2
.public
.dleq_verify(t0.clone(), &io21, &proofs21.0)
.is_ok());
}
#[test]
fn vrfs_merged_and_batched() {
let keypairs: Vec<Keypair> = (0..4)
.map(|_| Keypair::generate(&mut thread_rng()))
.collect();
let ctx = signing_context(b"yo!");
let messages: [&[u8; 4]; 2] = [b"meow", b"woof"];
let ts = || messages.iter().map(|m| ctx.bytes(*m));
let ios_n_proofs = keypairs.iter().map(|k| k.vrfs_sign(ts())).collect::<Vec<(
Box<[VRFInOut]>,
VRFProof,
VRFProofBatchable,
)>>();
for (k, (ios, proof, proof_batchable)) in keypairs.iter().zip(&ios_n_proofs) {
let outs = ios
.iter()
.map(|io| io.to_output())
.collect::<Vec<VRFOutput>>();
let (ios_too, proof_too) = k
.public
.vrfs_verify(ts(), &outs, &proof)
.expect("Valid VRF output verification failed!");
assert_eq!(
ios_too, *ios,
"Output differs between signing and verification!"
);
assert_eq!(
proof_too, *proof_batchable,
"Returning batchable proof failed!"
);
}
for (k, (ios, proof, _proof_batchable)) in keypairs.iter().zip(&ios_n_proofs) {
let outs = ios.iter()
.rev()
.map(|io| io.to_output())
.collect::<Vec<VRFOutput>>();
assert!(
k.public.vrfs_verify(ts(), &outs, &proof).is_err(),
"Incorrect VRF output verification passed!"
);
}
for (k, (ios, proof, _proof_batchable)) in keypairs.iter().rev().zip(&ios_n_proofs) {
let outs = ios.iter()
.map(|io| io.to_output())
.collect::<Vec<VRFOutput>>();
assert!(
k.public.vrfs_verify(ts(), &outs, &proof).is_err(),
"VRF output verification by a different signer passed!"
);
}
let mut ios = keypairs.iter().enumerate()
.map(|(i, keypair)| keypair.public.vrfs_merge_vartime(&ios_n_proofs[i].0))
.collect::<Vec<VRFInOut>>();
let mut proofs = ios_n_proofs.iter()
.map(|(_ios, _proof, proof_batchable)| proof_batchable.clone())
.collect::<Vec<VRFProofBatchable>>();
let mut public_keys = keypairs.iter()
.map(|keypair| keypair.public.clone())
.collect::<Vec<PublicKey>>();
assert!(
dleq_verify_batch(&ios, &proofs, &public_keys),
"Batch verification failed!"
);
proofs.reverse();
assert!(
dleq_verify_batch(&ios, &proofs, &public_keys) == false,
"Batch verification with incorrect proofs passed!"
);
proofs.reverse();
public_keys.reverse();
assert!(
dleq_verify_batch(&ios, &proofs, &public_keys) == false,
"Batch verification with incorrect public keys passed!"
);
public_keys.reverse();
ios.reverse();
assert!(
dleq_verify_batch(&ios, &proofs, &public_keys) == false,
"Batch verification with incorrect points passed!"
);
}
}