Skip to content

Commit

Permalink
Separate VRF output and proof (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
davxy authored May 22, 2024
1 parent 3229bde commit 9e6cd31
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 135 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ ring = [
"merlin",
]
rfc-6979 = [ "hmac" ]
full = [
"curves",
"ring",
]
79 changes: 26 additions & 53 deletions src/ietf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ impl<T> IetfSuite for T where T: Suite {}
///
/// An output point which can be used to derive the actual output together
/// with the actual signature of the input point and the associated data.
// TODO: manually implement serialization to respect S::CHALLENGE_LEN value.
#[derive(Debug, Clone)]
pub struct Signature<S: IetfSuite> {
pub gamma: Output<S>,
pub c: ScalarField<S>,
pub s: ScalarField<S>,
}
Expand All @@ -26,9 +24,8 @@ impl<S: IetfSuite + Sync> CanonicalSerialize for Signature<S> {
fn serialize_with_mode<W: ark_serialize::Write>(
&self,
mut writer: W,
compress: ark_serialize::Compress,
_compress_always: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
self.gamma.serialize_with_mode(&mut writer, compress)?;
let buf = utils::encode_scalar::<S>(&self.c);
if buf.len() < S::CHALLENGE_LEN {
// Encoded scalar length must be at least S::CHALLENGE_LEN
Expand All @@ -39,110 +36,90 @@ impl<S: IetfSuite + Sync> CanonicalSerialize for Signature<S> {
Ok(())
}

fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
self.gamma.serialized_size(compress) + S::CHALLENGE_LEN + self.s.compressed_size()
fn serialized_size(&self, _compress_always: ark_serialize::Compress) -> usize {
S::CHALLENGE_LEN + self.s.compressed_size()
}
}

impl<S: IetfSuite + Sync> CanonicalDeserialize for Signature<S> {
fn deserialize_with_mode<R: ark_serialize::Read>(
mut reader: R,
compress: ark_serialize::Compress,
_compress_always: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
let gamma = <AffinePoint<S> as CanonicalDeserialize>::deserialize_with_mode(
&mut reader,
compress,
validate,
)?;
let c = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
&mut reader,
ark_serialize::Compress::No,
ark_serialize::Validate::Yes,
validate,
)?;
let s = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
&mut reader,
ark_serialize::Compress::No,
ark_serialize::Validate::Yes,
validate,
)?;
Ok(Signature {
gamma: Output(gamma),
c,
s,
})
Ok(Signature { c, s })
}
}

impl<S: IetfSuite + Sync> ark_serialize::Valid for Signature<S> {
fn check(&self) -> Result<(), ark_serialize::SerializationError> {
self.gamma.check()?;
self.c.check()?;
self.s.check()?;
Ok(())
}
}

impl<S: Suite> Signature<S> {
/// Proof to hash as defined by RFC-9381 section 5.2
pub fn hash(&self) -> HashOutput<S> {
self.gamma.hash()
}

pub fn output(&self) -> Output<S> {
self.gamma
}
}

pub trait IetfSigner<S: Suite> {
/// Sign the input and the user additional data `ad`.
fn sign(&self, input: Input<S>, ad: impl AsRef<[u8]>) -> Signature<S>;
fn sign(&self, input: Input<S>, output: Output<S>, ad: impl AsRef<[u8]>) -> Signature<S>;
}

pub trait IetfVerifier<S: Suite> {
/// Verify the VRF signature.
fn verify(
&self,
input: Input<S>,
output: Output<S>,
ad: impl AsRef<[u8]>,
sig: &Signature<S>,
) -> Result<(), Error>;
}

impl<S: Suite> IetfSigner<S> for Secret<S> {
fn sign(&self, input: Input<S>, ad: impl AsRef<[u8]>) -> Signature<S> {
let gamma = self.output(input);
impl<S: IetfSuite> IetfSigner<S> for Secret<S> {
fn sign(&self, input: Input<S>, output: Output<S>, ad: impl AsRef<[u8]>) -> Signature<S> {
let k = S::nonce(&self.scalar, input);
let k_b = (S::Affine::generator() * k).into_affine();

let k_h = (input.0 * k).into_affine();

let c = S::challenge(
&[&self.public.0, &input.0, &gamma.0, &k_b, &k_h],
&[&self.public.0, &input.0, &output.0, &k_b, &k_h],
ad.as_ref(),
);
let s = k + c * self.scalar;
Signature { gamma, c, s }
Signature { c, s }
}
}

impl<S: Suite> IetfVerifier<S> for Public<S> {
fn verify(
&self,
input: Input<S>,
output: Output<S>,
ad: impl AsRef<[u8]>,
signature: &Signature<S>,
) -> Result<(), Error> {
let Signature { gamma, c, s } = signature;
let Signature { c, s } = signature;

let s_b = S::Affine::generator() * s;
let c_y = self.0 * c;
let u = (s_b - c_y).into_affine();

let s_h = input.0 * s;
let c_o = gamma.0 * c;
let c_o = output.0 * c;
let v = (s_h - c_o).into_affine();

let c_exp = S::challenge(&[&self.0, &input.0, &gamma.0, &u, &v], ad.as_ref());
let c_exp = S::challenge(&[&self.0, &input.0, &output.0, &u, &v], ad.as_ref());
(&c_exp == c)
.then_some(())
.ok_or(Error::VerificationFailure)
Expand Down Expand Up @@ -184,9 +161,10 @@ pub mod testing {
assert_eq!(v.h, hex::encode(h_bytes));

let input = Input::from(h);
let signature = sk.sign(input, []);
let output = sk.output(input);
let signature = sk.sign(input, output, []);

let gamma_bytes = utils::encode_point::<S>(&signature.output().0);
let gamma_bytes = utils::encode_point::<S>(&output.0);
assert_eq!(v.gamma, hex::encode(gamma_bytes));

if v.flags & TEST_FLAG_SKIP_SIGN_CHECK != 0 {
Expand All @@ -199,7 +177,7 @@ pub mod testing {
let s_bytes = utils::encode_scalar::<S>(&signature.s);
assert_eq!(v.s, hex::encode(s_bytes));

let beta = signature.gamma.hash();
let beta = output.hash();
assert_eq!(v.beta, hex::encode(beta));
}
}
Expand All @@ -216,31 +194,26 @@ mod tests {
let secret = Secret::from_seed(TEST_SEED);
let public = secret.public();
let input = Input::from(random_val::<AffinePoint>(None));
let output = secret.output(input);

let signature = secret.sign(input, b"foo");
assert_eq!(signature.gamma, secret.output(input));
let signature = secret.sign(input, output, b"foo");

let result = public.verify(input, b"foo", &signature);
let result = public.verify(input, output, b"foo", &signature);
assert!(result.is_ok());
}

#[test]
fn signature_encode_decode() {
let gamma = utils::hash_to_curve_tai::<TestSuite>(b"foobar", false).unwrap();
let c = hex::decode("d091c00b0f5c3619d10ecea44363b5a5").unwrap();
let c = ScalarField::from_be_bytes_mod_order(&c[..]);
let s = hex::decode("99cadc5b2957e223fec62e81f7b4825fc799a771a3d7334b9186bdbee87316b1")
.unwrap();
let s = ScalarField::from_be_bytes_mod_order(&s[..]);

let signature = Signature::<TestSuite> {
gamma: Output(gamma),
c,
s,
};
let signature = Signature::<TestSuite> { c, s };

let mut buf = Vec::new();
signature.serialize_compressed(&mut buf).unwrap();
assert_eq!(buf.len(), TestSuite::CHALLENGE_LEN + 64);
assert_eq!(buf.len(), TestSuite::CHALLENGE_LEN + 32);
}
}
55 changes: 24 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use zeroize::Zeroize;
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::PrimeField;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{vec, vec::Vec};
use ark_std::vec::Vec;

use digest::Digest;

Expand All @@ -32,6 +32,8 @@ pub mod utils;
pub mod ring;

pub mod prelude {
pub use ark_ec;
pub use ark_ff;
pub use ark_serialize;
pub use ark_std;
}
Expand All @@ -52,13 +54,16 @@ pub const CUSTOM_SUITE_ID_FLAG: u8 = 0x80;

/// Defines a cipher suite.
///
/// This trait can be used to implement a VRF which follows the guidelines
/// given by RFC-9381 section 5.5 for cipher-suite implementation.
/// This trait can be used to easily implement a VRF which follows the guidelines
/// given by RFC-9381 section 5.5.
///
/// Can be easily customized to implement more exotic VRF types by overwriting
/// the default methods implementations.
pub trait Suite: Copy + Clone {
/// Suite identifier (aka `suite_string` in RFC-9381)
const SUITE_ID: u8;

/// Challenge length.
/// Challenge encoded length.
///
/// Must be at least equal to the Hash length.
const CHALLENGE_LEN: usize;
Expand Down Expand Up @@ -100,45 +105,31 @@ pub trait Suite: Copy + Clone {
/// This implementation extends the RFC procedure to allow adding
/// some optional additional data too the hashing procedure.
fn challenge(pts: &[&AffinePoint<Self>], ad: &[u8]) -> ScalarField<Self> {
const DOM_SEP_START: u8 = 0x02;
const DOM_SEP_END: u8 = 0x00;
let mut buf = vec![Self::SUITE_ID, DOM_SEP_START];
pts.iter().for_each(|p| {
Self::point_encode(p, &mut buf);
});
buf.extend_from_slice(ad);
buf.push(DOM_SEP_END);
let hash = &utils::hash::<Self::Hasher>(&buf)[..Self::CHALLENGE_LEN];
ScalarField::<Self>::from_be_bytes_mod_order(hash)
utils::challenge_rfc_9381::<Self>(pts, ad)
}

/// Hash data to a curve point.
///
/// By default uses try and increment method.
/// By default uses "try and increment" method described by RFC 9381.
fn data_to_point(data: &[u8]) -> Option<AffinePoint<Self>> {
utils::hash_to_curve_tai::<Self>(data, false)
utils::hash_to_curve_tai_rfc_9381::<Self>(data, false)
}

/// Map the point to a hash value using `Self::Hasher`.
///
/// By default uses the algorithm described by RFC 9381.
fn point_to_hash(pt: &AffinePoint<Self>) -> HashOutput<Self> {
const DOM_SEP_START: u8 = 0x03;
const DOM_SEP_END: u8 = 0x00;
let mut buf = vec![Self::SUITE_ID, DOM_SEP_START];
Self::point_encode(pt, &mut buf);
buf.push(DOM_SEP_END);
utils::hash::<Self::Hasher>(&buf)
utils::point_to_hash_rfc_9381::<Self>(pt)
}

#[inline(always)]
fn point_encode(pt: &AffinePoint<Self>, buf: &mut Vec<u8>) {
pt.serialize_compressed(buf).unwrap();
}

#[inline(always)]
fn scalar_encode(sc: &ScalarField<Self>, buf: &mut Vec<u8>) {
sc.serialize_compressed(buf).unwrap();
}

#[inline(always)]
fn scalar_decode(buf: &[u8]) -> ScalarField<Self> {
<ScalarField<Self>>::from_le_bytes_mod_order(buf)
}
Expand All @@ -148,9 +139,9 @@ pub trait Suite: Copy + Clone {
#[derive(Debug, Clone, PartialEq)]
pub struct Secret<S: Suite> {
// Secret scalar.
scalar: ScalarField<S>,
pub scalar: ScalarField<S>,
// Cached public point.
public: Public<S>,
pub public: Public<S>,
}

impl<S: Suite> Drop for Secret<S> {
Expand Down Expand Up @@ -223,13 +214,15 @@ impl<S: Suite> Secret<S> {
self.public
}

/// Get the VRF `output` point relative to `input` without generating the signature.
///
/// This is a relatively fast step that we may want to perform before generating
/// the signature.
/// Get the VRF output point relative to input.
pub fn output(&self, input: Input<S>) -> Output<S> {
Output((input.0 * self.scalar).into_affine())
}

/// Get the VRF `nonce`
pub fn nonce(&self, pt: Input<S>) -> ScalarField<S> {
S::nonce(&self.scalar, pt)
}
}

/// Public key generic over the cipher suite.
Expand Down
Loading

0 comments on commit 9e6cd31

Please sign in to comment.