diff --git a/src/util/vrf.rs b/src/util/vrf.rs index d5945645f..d6f7f6444 100644 --- a/src/util/vrf.rs +++ b/src/util/vrf.rs @@ -19,7 +19,6 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] -#![allow(dead_code)] // TODO: remove once we start using the VRF for things /// This codebase is based on routines defined in the IETF draft for verifiable random functions /// over elliptic curves (https://tools.ietf.org/id/draft-irtf-cfrg-vrf-02.html). @@ -83,13 +82,54 @@ impl error::Error for ECVRF_Error { pub const SUITE : u8 = 0x05; /* cipher suite (not standardized yet). This would be ECVRF-ED25519-SHA512-RistrettoElligator -- i.e. using the Ristretto group on ed25519 and its elligator function */ +#[derive(Debug, Clone, PartialEq)] pub struct ECVRF_Proof { - pub Gamma: RistrettoPoint, - pub c: ed25519_Scalar, - pub s: ed25519_Scalar + // force private so we don't accidentally expose + // an invalid c point + Gamma: RistrettoPoint, + c: ed25519_Scalar, + s: ed25519_Scalar } +pub const ECVRF_PROOF_ENCODED_SIZE : u32 = 80; + impl ECVRF_Proof { + pub fn Gamma(&self) -> &RistrettoPoint { + &self.Gamma + } + + pub fn s(&self) -> &ed25519_Scalar { + &self.s + } + + pub fn c(&self) -> &ed25519_Scalar { + &self.c + } + + pub fn check_c(c: &ed25519_Scalar) -> bool { + let c_bytes = c.reduce().to_bytes(); + + // upper 16 bytes of c must be 0's + for i in 16..32 { + if c_bytes[i] != 0 { + return false; + } + } + return true; + } + + pub fn new(Gamma: RistrettoPoint, c: ed25519_Scalar, s: ed25519_Scalar) -> Result { + if !ECVRF_Proof::check_c(&c) { + return Err(ECVRF_Error::InvalidDataError); + } + + Ok(ECVRF_Proof { + Gamma, + c, + s + }) + } + pub fn from_slice(bytes: &[u8]) -> Result { match bytes.len() { 80 => { @@ -129,16 +169,14 @@ impl ECVRF_Proof { ECVRF_Proof::from_slice(&bytes[..]) } - pub fn to_bytes(&self) -> Result<[u8; 80], ECVRF_Error> { + pub fn to_bytes(&self) -> [u8; 80] { let mut c_bytes_16 = [0u8; 16]; + assert!(ECVRF_Proof::check_c(&self.c), "FATAL ERROR: somehow constructed an invalid ECVRF proof"); + let c_bytes = self.c.reduce().to_bytes(); // upper 16 bytes of c must be 0's for i in 16..32 { - if c_bytes[i] != 0 { - return Err(ECVRF_Error::InvalidDataError); - } - c_bytes_16[i-16] = c_bytes[i-16]; } @@ -152,7 +190,7 @@ impl ECVRF_Proof { let mut proof_bytes = [0u8; 80]; proof_bytes.copy_from_slice(&ret[..]); - Ok(proof_bytes) + proof_bytes } } @@ -256,7 +294,8 @@ fn ECVRF_nonce_generation(trunc_hash: &[u8; 32], H_point: &RistrettoPoint) -> ed k.reduce() } -/// Convert a 16-byte string into a scalar +/// Convert a 16-byte string into a scalar. +/// The upper 16 bytes in the resulting scalar MUST BE 0's fn ECVRF_ed25519_scalar_from_hash128(hash128: &[u8; 16]) -> ed25519_Scalar { let mut scalar_buf = [0u8; 32]; for i in 0..16 { @@ -284,11 +323,9 @@ pub fn ECVRF_prove(secret: &ed25519_PrivateKey, alpha: &Vec) -> ECVRF_Proof let s_full_scalar = &c_scalar * &x_scalar + &k_scalar; let s_scalar = s_full_scalar.reduce(); - ECVRF_Proof { - Gamma: Gamma_point, - c: c_scalar, - s: s_scalar - } + // NOTE: unwrap() won't panic because c_scalar is guaranteed to have + // its upper 16 bytes as 0 + ECVRF_Proof::new(Gamma_point, c_scalar, s_scalar).unwrap() } /// Auxilliary routine to convert an ed25519 public key point to a Ristretto point. @@ -323,16 +360,16 @@ fn ECVRF_ed25519_PublicKey_to_RistrettoPoint(public_key: &ed25519_PublicKey) -> pub fn ECVRF_verify(Y_point: &ed25519_PublicKey, proof: &ECVRF_Proof, alpha: &Vec) -> Result { let H_point = ECVRF_hash_to_curve(Y_point, alpha); let Y_ristretto_point = ECVRF_ed25519_PublicKey_to_RistrettoPoint(Y_point)?; - let s_reduced = proof.s.reduce(); + let s_reduced = proof.s().reduce(); - let U_point = &s_reduced * &RISTRETTO_BASEPOINT_POINT - &proof.c * Y_ristretto_point; - let V_point = &s_reduced * &H_point - &proof.c * &proof.Gamma; + let U_point = &s_reduced * &RISTRETTO_BASEPOINT_POINT - proof.c() * Y_ristretto_point; + let V_point = &s_reduced * &H_point - proof.c() * proof.Gamma(); - let c_prime_hashbuf = ECVRF_hash_points(&H_point, &proof.Gamma, &U_point, &V_point); + let c_prime_hashbuf = ECVRF_hash_points(&H_point, proof.Gamma(), &U_point, &V_point); let c_prime = ECVRF_ed25519_scalar_from_hash128(&c_prime_hashbuf); // NOTE: this leverages constant-time comparison inherited from the Scalar impl - Ok(c_prime == proof.c) + Ok(c_prime == *(proof.c())) } /// Verify that a given byte string is a well-formed EdDSA public key (i.e. it's a compressed @@ -430,7 +467,7 @@ mod tests { let expected_proof_bytes = &proof_fixture.proof[..]; let proof = ECVRF_prove(&privk, &alpha.to_vec()); - let proof_bytes = proof.to_bytes().unwrap(); + let proof_bytes = proof.to_bytes(); assert_eq!(proof_bytes.to_vec(), expected_proof_bytes.to_vec()); } @@ -564,23 +601,12 @@ mod tests { assert!(!proof_res.is_err()); // should re-encode - assert!(proof_res.unwrap().to_bytes().unwrap().to_vec() == proof_fixture.proof.to_vec()); + assert!(proof_res.unwrap().to_bytes().to_vec() == proof_fixture.proof.to_vec()); } else { assert!(proof_res.is_err()); } } - - // confirm that a proof structure with an invalid c-value does not encode - let valid_proof = ECVRF_Proof::from_bytes(&hex_bytes("024c1484fcb05cecdb4dbfb9bf4e08e7f529aea3b3a2515716ad4e9cf7bace6c91181b6bb7d8201c5a85a11c626d1848aa2ac4d188c7e24a94faa32d1eec48d600fad7c55c7e71adb6a7dd6c73f6fc02").unwrap()).unwrap(); - let bad_proof = ECVRF_Proof { - Gamma: valid_proof.Gamma, - c: ed25519_Scalar::from_bits([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]), - s: valid_proof.s - }; - - let bad_proof_bytes_res = bad_proof.to_bytes(); - assert!(bad_proof_bytes_res.is_err()); } #[test]