use core::borrow::Borrow;
#[cfg(any(feature = "alloc", feature = "std"))]
use core::iter::once;
#[cfg(feature = "alloc")]
use alloc::{boxed::Box, vec::Vec};
#[cfg(feature = "std")]
use std::{boxed::Box, vec::Vec};
use curve25519_dalek::constants;
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::{IsIdentity,MultiscalarMul,VartimeMultiscalarMul};
use merlin::Transcript;
use super::*;
use crate::context::SigningTranscript;
use crate::points::RistrettoBoth;
pub const KUSAMA_VRF : bool = true;
pub const VRF_OUTPUT_LENGTH : usize = 32;
pub const VRF_PROOF_LENGTH : usize = 64;
pub const VRF_PROOF_BATCHABLE_LENGTH : usize = 96;
pub trait VRFSigningTranscript {
type T: SigningTranscript;
fn transcript_with_malleability_addressed(self, publickey: &PublicKey) -> Self::T;
}
impl<T> VRFSigningTranscript for T where T: SigningTranscript {
type T = T;
#[inline(always)]
fn transcript_with_malleability_addressed(mut self, publickey: &PublicKey) -> T {
self.commit_point(b"vrf-nm-pk", publickey.as_compressed());
self
}
}
#[derive(Clone)]
pub struct Malleable<T: SigningTranscript>(pub T);
impl<T> VRFSigningTranscript for Malleable<T> where T: SigningTranscript {
type T = T;
#[inline(always)]
fn transcript_with_malleability_addressed(self, _publickey: &PublicKey) -> T { self.0 }
}
pub fn vrf_malleable_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))
}
impl PublicKey {
pub fn vrf_hash<T>(&self, t: T) -> RistrettoBoth
where T: VRFSigningTranscript {
vrf_malleable_hash(t.transcript_with_malleability_addressed(self))
}
pub fn vrf_attach_hash<T>(&self, output: VRFOutput, t: T) -> SignatureResult<VRFInOut>
where T: VRFSigningTranscript {
output.attach_input_hash(self,t)
}
}
#[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, public: &PublicKey, t: T) -> SignatureResult<VRFInOut>
where T: VRFSigningTranscript {
let input = public.vrf_hash(t);
let output = RistrettoBoth::from_bytes_ser("VRFOutput", VRFOutput::DESCRIPTION, &self.0) ?;
if output.as_point().is_identity() { return Err(SignatureError::PointDecompressionError); }
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))
}
}
impl Keypair {
pub fn vrf_create_hash<T: VRFSigningTranscript>(&self, t: T) -> VRFInOut {
self.secret.vrf_create_from_point(self.public.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: &[u8]) -> B {
let mut t = Transcript::new(b"VRFResult");
t.append_message(b"",context);
self.commit(&mut t);
let mut seed = B::default();
t.challenge_bytes(b"", seed.as_mut());
seed
}
pub fn make_rng<R: ::rand_core::SeedableRng>(&self, context: &[u8]) -> R {
R::from_seed(self.make_bytes::<R::Seed>(context))
}
#[cfg(feature = "rand_chacha")]
pub fn make_chacharng(&self, context: &[u8]) -> ::rand_chacha::ChaChaRng {
self.make_rng::<::rand_chacha::ChaChaRng>(context)
}
#[inline(always)]
pub fn make_merlin_rng(&self, context: &[u8]) -> merlin::TranscriptRng {
struct ZeroFakeRng;
impl ::rand_core::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_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl ::rand_core::CryptoRng for ZeroFakeRng {}
let mut t = Transcript::new(b"VRFResult");
t.append_message(b"",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 {
pub fn vrfs_merge<B>(&self, ps: &[B], vartime: bool) -> VRFInOut
where
B: Borrow<VRFInOut>,
{
assert!( ps.len() > 0);
let mut t = ::merlin::Transcript::new(b"MergeVRFs");
t.commit_point(b"vrf:pk", self.as_compressed());
for p in ps.iter() {
p.borrow().commit(&mut t);
}
let zf = || ps.iter().map(|p| {
let mut t0 = t.clone();
p.borrow().commit(&mut t0);
challenge_scalar_128(t0)
});
#[cfg(any(feature = "alloc", feature = "std"))]
let zs: Vec<Scalar> = zf().collect();
#[cfg(any(feature = "alloc", feature = "std"))]
let zf = || zs.iter();
fn get_input(p: &VRFInOut) -> &RistrettoPoint { p.input.as_point() }
fn get_output(p: &VRFInOut) -> &RistrettoPoint { p.output.as_point() }
#[cfg(any(feature = "alloc", feature = "std"))]
let go = |io: fn(p: &VRFInOut) -> &RistrettoPoint| {
let ps = ps.iter().map( |p| io(p.borrow()) );
RistrettoBoth::from_point(if vartime {
RistrettoPoint::vartime_multiscalar_mul(zf(), ps)
} else {
RistrettoPoint::multiscalar_mul(zf(), ps)
})
};
#[cfg(not(any(feature = "alloc", feature = "std")))]
let go = |io: fn(p: &VRFInOut) -> &RistrettoPoint| {
use curve25519_dalek::traits::Identity;
let mut acc = RistrettoPoint::identity();
for (z,p) in zf().zip(ps) {
acc += z * io(p.borrow());
}
RistrettoBoth::from_point(acc)
};
let input = go( get_input );
let output = go( get_output );
VRFInOut { input, output }
}
}
#[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, kusama: bool) -> VRFProof
where T: SigningTranscript,
{
t.proto_name(b"DLEQProof");
t.commit_point(b"vrf:h", p.input.as_compressed());
if !kusama { t.commit_point(b"vrf:pk", public.as_compressed()); }
t.commit_point(b"vrf:R=g^r", &self.R);
t.commit_point(b"vrf:h^r", &self.Hr);
if kusama { t.commit_point(b"vrf:pk", public.as_compressed()); }
t.commit_point(b"vrf:h^sk", p.output.as_compressed());
VRFProof {
c: t.challenge_scalar(b"prove"),
s: self.s,
}
}
pub fn shorten_vrf<T>( &self, public: &PublicKey, t: T, out: &VRFOutput)
-> SignatureResult<VRFProof>
where T: VRFSigningTranscript,
{
let p = out.attach_input_hash(public,t) ?;
let t0 = Transcript::new(b"VRF");
Ok(self.shorten_dleq(t0, public, &p, KUSAMA_VRF))
}
}
serde_boilerplate!(VRFProofBatchable);
impl Keypair {
#[allow(non_snake_case)]
pub fn dleq_proove<T>(&self, mut t: T, p: &VRFInOut, kusama: bool) -> (VRFProof, VRFProofBatchable)
where
T: SigningTranscript,
{
t.proto_name(b"DLEQProof");
t.commit_point(b"vrf:h", p.input.as_compressed());
if !kusama { t.commit_point(b"vrf:pk", self.public.as_compressed()); }
let mut r = t.witness_scalar(b"proving\00",&[&self.secret.nonce]);
let R = (&r * &constants::RISTRETTO_BASEPOINT_TABLE).compress();
t.commit_point(b"vrf:R=g^r", &R);
let Hr = (&r * p.input.as_point()).compress();
t.commit_point(b"vrf:h^r", &Hr);
if kusama { t.commit_point(b"vrf:pk", self.public.as_compressed()); }
t.commit_point(b"vrf:h^sk", p.output.as_compressed());
let c = t.challenge_scalar(b"prove");
let s = &r - &(&c * &self.secret.key);
::zeroize::Zeroize::zeroize(&mut r);
(VRFProof { c, s }, VRFProofBatchable { R, Hr, s })
}
pub fn vrf_sign<T>(&self, t: T) -> (VRFInOut, VRFProof, VRFProofBatchable)
where T: VRFSigningTranscript,
{
self.vrf_sign_extra(t,Transcript::new(b"VRF"))
}
pub fn vrf_sign_extra<T,E>(&self, t: T, extra: E) -> (VRFInOut, VRFProof, VRFProofBatchable)
where T: VRFSigningTranscript,
E: SigningTranscript,
{
let p = self.vrf_create_hash(t);
let (proof, proof_batchable) = self.dleq_proove(extra, &p, KUSAMA_VRF);
(p, proof, proof_batchable)
}
pub fn vrf_sign_after_check<T,F>(&self, t: T, mut check: F)
-> Option<(VRFInOut, VRFProof, VRFProofBatchable)>
where T: VRFSigningTranscript,
F: FnMut(&VRFInOut) -> bool,
{
self.vrf_sign_extra_after_check(t,
|io| if check(io) { Some(Transcript::new(b"VRF")) } else { None }
)
}
pub fn vrf_sign_extra_after_check<T,E,F>(&self, t: T, mut check: F)
-> Option<(VRFInOut, VRFProof, VRFProofBatchable)>
where T: VRFSigningTranscript,
E: SigningTranscript,
F: FnMut(&VRFInOut) -> Option<E>,
{
let p = self.vrf_create_hash(t);
let extra = check(&p) ?;
let (proof, proof_batchable) = self.dleq_proove(extra, &p, KUSAMA_VRF);
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: VRFSigningTranscript,
I: IntoIterator<Item = T>,
{
self.vrfs_sign_extra(ts, Transcript::new(b"VRF"))
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn vrfs_sign_extra<T,E,I>(&self, ts: I, extra: E) -> (Box<[VRFInOut]>, VRFProof, VRFProofBatchable)
where
T: VRFSigningTranscript,
E: SigningTranscript,
I: IntoIterator<Item = T>,
{
let ps = ts.into_iter()
.map(|t| self.vrf_create_hash(t))
.collect::<Vec<VRFInOut>>();
let p = self.public.vrfs_merge(&ps,true);
let (proof, proof_batchable) = self.dleq_proove(extra, &p, KUSAMA_VRF);
(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,
kusama: bool,
) -> SignatureResult<VRFProofBatchable>
where
T: SigningTranscript,
{
t.proto_name(b"DLEQProof");
t.commit_point(b"vrf:h", p.input.as_compressed());
if !kusama { t.commit_point(b"vrf:pk", self.as_compressed()); }
let R = RistrettoPoint::vartime_double_scalar_mul_basepoint(
&proof.c,
self.as_point(),
&proof.s,
).compress();
t.commit_point(b"vrf:R=g^r", &R);
#[cfg(not(any(feature = "alloc", feature = "std")))]
let Hr = (&proof.c * p.output.as_point()) + (&proof.s * p.input.as_point());
#[cfg(any(feature = "alloc", feature = "std"))]
let Hr = RistrettoPoint::vartime_multiscalar_mul(
&[proof.c, proof.s],
&[*p.output.as_point(), *p.input.as_point()],
);
let Hr = Hr.compress();
t.commit_point(b"vrf:h^r", &Hr);
if kusama { t.commit_point(b"vrf:pk", self.as_compressed()); }
t.commit_point(b"vrf:h^sk", p.output.as_compressed());
let VRFProof { c, s } = *proof;
if c == t.challenge_scalar(b"prove") {
Ok(VRFProofBatchable { R, Hr, s })
} else {
Err(SignatureError::EquationFalse)
}
}
pub fn vrf_verify<T: VRFSigningTranscript>(
&self,
t: T,
out: &VRFOutput,
proof: &VRFProof,
) -> SignatureResult<(VRFInOut, VRFProofBatchable)> {
self.vrf_verify_extra(t,out,proof,Transcript::new(b"VRF"))
}
pub fn vrf_verify_extra<T,E>(
&self,
t: T,
out: &VRFOutput,
proof: &VRFProof,
extra: E,
) -> SignatureResult<(VRFInOut, VRFProofBatchable)>
where T: VRFSigningTranscript,
E: SigningTranscript,
{
let p = out.attach_input_hash(self,t)?;
let proof_batchable = self.dleq_verify(extra, &p, proof, KUSAMA_VRF)?;
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: VRFSigningTranscript,
I: IntoIterator<Item = T>,
O: Borrow<VRFOutput>,
{
self.vrfs_verify_extra(transcripts,outs,proof,Transcript::new(b"VRF"))
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn vrfs_verify_extra<T,E,I,O>(
&self,
transcripts: I,
outs: &[O],
proof: &VRFProof,
extra: E,
) -> SignatureResult<(Box<[VRFInOut]>, VRFProofBatchable)>
where
T: VRFSigningTranscript,
E: 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(self,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(&ps[..],true);
let proof_batchable = self.dleq_verify(extra, &p, proof, KUSAMA_VRF)?;
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],
kusama: bool,
) -> SignatureResult<()> {
const ASSERT_MESSAGE: &'static str = "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 csprng = {
let mut t = Transcript::new(b"VB-RNG");
for (pk,p) in public_keys.iter().zip(ps) {
t.commit_point(b"",pk.as_compressed());
p.commit(&mut t);
}
t.build_rng().finalize(&mut rand_hack())
};
let rnd_128bit_scalar = |_| {
let mut r = [0u8; 16];
csprng.fill_bytes(&mut r);
Scalar::from(u128::from_le_bytes(r))
};
let zz: Vec<Scalar> = proofs.iter().map(rnd_128bit_scalar).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], kusama).c )
.collect();
let mut 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);
if b { Ok(()) } else { Err(SignatureError::EquationFalse) }
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn vrf_verify_batch<T, I>(
transcripts: I,
outs: &[VRFOutput],
proofs: &[VRFProofBatchable],
publickeys: &[PublicKey],
) -> SignatureResult<Box<[VRFInOut]>>
where
T: VRFSigningTranscript,
I: IntoIterator<Item = T>,
{
let mut ts = transcripts.into_iter();
let ps = ts.by_ref()
.zip(publickeys)
.zip(outs)
.map(|((t, pk), out)| out.attach_input_hash(pk,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, publickeys, KUSAMA_VRF).is_ok() {
Ok(ps.into_boxed_slice())
} else {
Err(SignatureError::EquationFalse)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
use super::*;
#[test]
fn vrf_single() {
let mut csprng = ::rand_core::OsRng;
let keypair1 = Keypair::generate_with(&mut csprng);
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_with(&mut csprng);
assert!(
keypair2.public.vrf_verify(ctx.bytes(msg), &out1, &proof1).is_err(),
"VRF verification with incorrect signer passed!"
);
}
#[test]
fn vrf_malleable() {
let mut csprng = ::rand_core::OsRng;
let keypair1 = Keypair::generate_with(&mut csprng);
let ctx = signing_context(b"yo!");
let msg = b"meow";
let (io1, proof1, proof1batchable) = keypair1.vrf_sign(Malleable(ctx.bytes(msg)));
let out1 = &io1.to_output();
assert_eq!(
proof1,
proof1batchable.shorten_vrf(&keypair1.public, Malleable(ctx.bytes(msg)), &out1).unwrap(),
"Oops `shorten_vrf` failed"
);
let (io1too, proof1too) = keypair1
.public.vrf_verify(Malleable(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(Malleable(ctx.bytes(msg))).0,
io1,
"Rerunning VRF gave different output"
);
assert!(
keypair1.public.vrf_verify(Malleable(ctx.bytes(b"not meow")), &out1, &proof1).is_err(),
"VRF verification with incorrect message passed!"
);
let keypair2 = Keypair::generate_with(&mut csprng);
assert!(
keypair2.public.vrf_verify(Malleable(ctx.bytes(msg)), &out1, &proof1).is_err(),
"VRF verification with incorrect signer passed!"
);
let (io2, _proof2, _proof2batchable) = keypair2.vrf_sign(Malleable(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, KUSAMA_VRF);
let io12 = keypair1.secret.vrf_create_from_compressed_point(out2).unwrap();
let proofs12 = keypair1.dleq_proove(t0.clone(), &io12, KUSAMA_VRF);
assert_eq!(io12.output, io21.output, "Sequential two-party VRF failed");
assert_eq!(
proofs21.0,
proofs21.1.shorten_dleq(t0.clone(), &keypair2.public, &io21, KUSAMA_VRF),
"Oops `shorten_dleq` failed"
);
assert_eq!(
proofs12.0,
proofs12.1.shorten_dleq(t0.clone(), &keypair1.public, &io12, KUSAMA_VRF),
"Oops `shorten_dleq` failed"
);
assert!(keypair1
.public
.dleq_verify(t0.clone(), &io12, &proofs12.0, KUSAMA_VRF)
.is_ok());
assert!(keypair2
.public
.dleq_verify(t0.clone(), &io21, &proofs21.0, KUSAMA_VRF)
.is_ok());
}
#[cfg(any(feature = "alloc", feature = "std"))]
#[test]
fn vrfs_merged_and_batched() {
let mut csprng = ::rand_core::OsRng;
let keypairs: Vec<Keypair> = (0..4)
.map(|_| Keypair::generate_with(&mut csprng))
.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(&ios_n_proofs[i].0,true))
.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, KUSAMA_VRF).is_ok(),
"Batch verification failed!"
);
proofs.reverse();
assert!(
dleq_verify_batch(&ios, &proofs, &public_keys, KUSAMA_VRF).is_err(),
"Batch verification with incorrect proofs passed!"
);
proofs.reverse();
public_keys.reverse();
assert!(
dleq_verify_batch(&ios, &proofs, &public_keys, KUSAMA_VRF).is_err(),
"Batch verification with incorrect public keys passed!"
);
public_keys.reverse();
ios.reverse();
assert!(
dleq_verify_batch(&ios, &proofs, &public_keys, KUSAMA_VRF).is_err(),
"Batch verification with incorrect points passed!"
);
}
}