#![cfg_attr(not(feature = "std"), no_std)]
#![deny(unsafe_code)]
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::{PrimeField, Zero};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::vec::Vec;
use utils::transcript::Transcript;
use zeroize::Zeroize;
pub mod pedersen;
pub mod suites;
pub mod thin;
pub mod tiny;
pub mod utils;
#[cfg(feature = "ring")]
pub mod ring;
#[cfg(test)]
mod testing;
pub mod reexports {
pub use ark_ec;
pub use ark_ff;
pub use ark_serialize;
pub use ark_std;
}
pub type AffinePoint<S> = <S as Suite>::Affine;
pub type BaseField<S> = <AffinePoint<S> as AffineRepr>::BaseField;
pub type ScalarField<S> = <AffinePoint<S> as AffineRepr>::ScalarField;
pub type CurveConfig<S> = <AffinePoint<S> as AffineRepr>::Config;
#[derive(Debug)]
pub enum Error {
VerificationFailure,
InvalidData,
}
impl From<ark_serialize::SerializationError> for Error {
fn from(_err: ark_serialize::SerializationError) -> Self {
Error::InvalidData
}
}
pub trait Suite: Copy {
const SUITE_ID: &'static [u8];
type Affine: AffineRepr;
type Transcript: Transcript;
#[inline(always)]
fn generator() -> AffinePoint<Self> {
Self::Affine::generator()
}
#[inline(always)]
fn nonce(sk: &ScalarField<Self>, transcript: Option<Self::Transcript>) -> ScalarField<Self> {
utils::nonce::<Self>(sk, transcript)
}
#[inline(always)]
fn challenge(
pts: &[&AffinePoint<Self>],
transcript: Option<Self::Transcript>,
) -> ScalarField<Self> {
utils::challenge::<Self>(pts, transcript)
}
#[inline(always)]
fn data_to_point(data: &[u8]) -> Option<AffinePoint<Self>> {
utils::hash_to_curve_tai::<Self>(data)
}
#[inline(always)]
fn point_to_hash<const N: usize>(pt: &AffinePoint<Self>) -> [u8; N] {
utils::point_to_hash::<Self, N>(pt, false)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Secret<S: Suite> {
pub(crate) scalar: ScalarField<S>,
pub(crate) public: Public<S>,
}
impl<S: Suite> Drop for Secret<S> {
fn drop(&mut self) {
self.scalar.zeroize()
}
}
impl<S: Suite> CanonicalSerialize for Secret<S> {
fn serialize_with_mode<W: ark_std::io::prelude::Write>(
&self,
writer: W,
compress: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
self.scalar.serialize_with_mode(writer, compress)
}
fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
self.scalar.serialized_size(compress)
}
}
impl<S: Suite> CanonicalDeserialize for Secret<S> {
fn deserialize_with_mode<R: ark_std::io::prelude::Read>(
reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
let scalar = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
reader, compress, validate,
)?;
Ok(Self::from_scalar(scalar))
}
}
impl<S: Suite> ark_serialize::Valid for Secret<S> {
fn check(&self) -> Result<(), ark_serialize::SerializationError> {
self.scalar.check()
}
}
impl<S: Suite> Secret<S> {
pub fn from_scalar(scalar: ScalarField<S>) -> Self {
let public = Public((S::generator() * scalar).into_affine());
Self { scalar, public }
}
pub fn from_seed(seed: [u8; 32]) -> Self {
let mut cnt = 0_u8;
let sk = ScalarField::<S>::from_le_bytes_mod_order(&seed);
let scalar = loop {
let mut transcript = S::Transcript::new(S::SUITE_ID);
transcript.absorb_raw(&seed);
if cnt > 0 {
transcript.absorb_raw(&[cnt]);
}
let scalar = utils::nonce::<S>(&sk, Some(transcript.clone()));
if !scalar.is_zero() {
break scalar;
}
cnt = cnt
.checked_add(1)
.expect("unreachable: transcript hash produced 256 consecutive zero scalars");
};
Self::from_scalar(scalar)
}
pub fn from_rand(rng: &mut impl ark_std::rand::RngCore) -> Self {
let mut seed = [0u8; 32];
rng.fill_bytes(&mut seed);
Self::from_seed(seed)
}
pub fn scalar(&self) -> &ScalarField<S> {
&self.scalar
}
pub fn public(&self) -> Public<S> {
self.public
}
pub fn output(&self, input: Input<S>) -> Output<S> {
Output(smul!(input.0, self.scalar).into_affine())
}
pub fn vrf_io(&self, input: Input<S>) -> VrfIo<S> {
VrfIo {
input,
output: self.output(input),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Public<S: Suite>(pub AffinePoint<S>);
impl<S: Suite> Public<S> {
pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
ark_serialize::Valid::check(&value).map_err(|_| Error::InvalidData)?;
Ok(Self(value))
}
pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Input<S: Suite>(pub AffinePoint<S>);
impl<S: Suite> Input<S> {
pub fn new(data: &[u8]) -> Option<Self> {
S::data_to_point(data).map(Input)
}
}
impl<S: Suite> Input<S> {
pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
ark_serialize::Valid::check(&value).map_err(|_| Error::InvalidData)?;
Ok(Self(value))
}
pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Output<S: Suite>(pub AffinePoint<S>);
impl<S: Suite> Output<S> {
pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
ark_serialize::Valid::check(&value).map_err(|_| Error::InvalidData)?;
Ok(Self(value))
}
pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
Self(value)
}
}
impl<S: Suite> Output<S> {
pub fn hash<const N: usize>(&self) -> [u8; N] {
S::point_to_hash(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
pub struct VrfIo<S: Suite> {
pub input: Input<S>,
pub output: Output<S>,
}
impl<S: Suite> AsRef<[VrfIo<S>]> for VrfIo<S> {
fn as_ref(&self) -> &[VrfIo<S>] {
core::slice::from_ref(self)
}
}
#[macro_export]
macro_rules! suite_types {
($suite:ident) => {
#[allow(dead_code)]
pub type Secret = $crate::Secret<$suite>;
#[allow(dead_code)]
pub type Public = $crate::Public<$suite>;
#[allow(dead_code)]
pub type Input = $crate::Input<$suite>;
#[allow(dead_code)]
pub type Output = $crate::Output<$suite>;
#[allow(dead_code)]
pub type AffinePoint = $crate::AffinePoint<$suite>;
#[allow(dead_code)]
pub type ScalarField = $crate::ScalarField<$suite>;
#[allow(dead_code)]
pub type BaseField = $crate::BaseField<$suite>;
#[allow(dead_code)]
pub type TinyProof = $crate::tiny::Proof<$suite>;
#[allow(dead_code)]
pub type PedersenProof = $crate::pedersen::Proof<$suite>;
#[allow(dead_code)]
pub type PedersenBatchItem = $crate::pedersen::BatchItem<$suite>;
#[allow(dead_code)]
pub type PedersenBatchVerifier = $crate::pedersen::BatchVerifier<$suite>;
#[allow(dead_code)]
pub type ThinProof = $crate::thin::Proof<$suite>;
#[allow(dead_code)]
pub type ThinBatchItem = $crate::thin::BatchItem<$suite>;
#[allow(dead_code)]
pub type ThinBatchVerifier = $crate::thin::BatchVerifier<$suite>;
#[allow(dead_code)]
pub type VrfIo = $crate::VrfIo<$suite>;
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tiny::{Prover, Verifier};
use ark_ec::AffineRepr;
use suites::testing::{Input, Secret, TestSuite};
use testing::{TEST_SEED, random_val};
#[test]
fn vrf_output_check() {
use ark_std::rand::SeedableRng;
let mut rng = ark_std::rand::rngs::StdRng::from_seed([42; 32]);
let secret = Secret::from_seed(TEST_SEED);
let input = Input::from_affine_unchecked(random_val(Some(&mut rng)));
let output = secret.output(input);
let expected = "4af9bf572a107a8f61faa380667efe27eaf399cc8e718d57ef328924eb51d450";
assert_eq!(expected, hex::encode(output.hash::<32>()));
}
#[test]
fn prove_uniqueness_vulnerability() {
use ark_ff::BigInteger;
use ark_std::{One, Zero};
use utils::common::{DomSep, ExactChain};
type S = TestSuite;
type Sc = ScalarField<S>;
let secret = crate::Secret::<S>::from_seed(TEST_SEED);
let public = secret.public();
let input = Input::new(b"uniqueness attack").unwrap();
let honest_output = secret.output(input);
let low_order_pt =
AffinePoint::<S>::new_unchecked(BaseField::<S>::zero(), -BaseField::<S>::one());
assert!(!low_order_pt.is_zero());
assert!((low_order_pt.into_group() + low_order_pt.into_group()).is_zero());
let malicious_output =
Output::from_affine_unchecked((honest_output.0 + low_order_pt).into_affine());
assert_ne!(honest_output, malicious_output);
assert_ne!(honest_output.hash::<32>(), malicious_output.hash::<32>());
let malicious_io = VrfIo {
input,
output: malicious_output,
};
let mal_ios = [malicious_io];
let mut ad_ctr = 0u32;
let (ad, t, merged_input) = loop {
let ad = format!("ad-{ad_ctr}");
let schnorr = core::iter::once(VrfIo {
input: Input(S::generator()),
output: Output(public.0),
});
let chain = ExactChain::new(schnorr, mal_ios.iter().copied());
let (t, zs) =
utils::vrf_transcript_scalars_from_iter(DomSep::TinyVrf, chain, ad.as_bytes());
if zs[1].into_bigint().is_even() {
let i_m = (S::generator() * zs[0] + input.0 * zs[1]).into_affine();
break (ad, t, i_m);
}
ad_ctr += 1;
assert!(ad_ctr < 100, "Failed to find suitable ad");
};
let mut ctr = 0u64;
let proof = loop {
let mut k_seed = [0u8; 8];
k_seed.copy_from_slice(&ctr.to_le_bytes());
let k = Sc::from_le_bytes_mod_order(&k_seed);
let r = (merged_input * k).into_affine();
let c = S::challenge(&[&r], Some(t.clone()));
if !c.into_bigint().is_even() {
let s = k + c * secret.scalar;
break crate::tiny::Proof { c, s };
}
ctr += 1;
assert!(ctr <= 1000, "Grinding failed");
};
assert!(public.verify(malicious_io, ad.as_bytes(), &proof).is_ok());
let honest_io = VrfIo {
input,
output: honest_output,
};
let honest_proof = secret.prove(honest_io, ad.as_bytes());
assert!(
public
.verify(honest_io, ad.as_bytes(), &honest_proof)
.is_ok()
);
assert_ne!(honest_output.hash::<32>(), malicious_output.hash::<32>());
}
}