use sha2::{Digest, Sha512};
pub use curve25519_dalek_ng::ristretto::{CompressedRistretto, RistrettoPoint};
pub use curve25519_dalek_ng::scalar::Scalar;
const VRF_VERSION: &str = "2.0";
const SUITE_STRING: &[u8; 7] = b"sui_vrf";
const CHALLENGE_GENERATION_DOMAIN_SEPARATOR_FRONT: u8 = 0x02;
const CHALLENGE_GENERATION_DOMAIN_SEPARATOR_BACK: u8 = 0x00;
const PROOF_TO_HASH_DOMAIN_SEPARATOR_FRONT: u8 = 0x03;
const PROOF_TO_HASH_DOMAIN_SEPARATOR_BACK: u8 = 0x00;
const VRF_HASH_TO_CURVE_DOMAIN: &[u8] = b"ECVRF_ristretto255_XMD:SHA-512_R255MAP_RO_sui_vrf";
const CHALLENGE_LENGTH: usize = 16; const EXPAND_MESSAGE_OUTPUT_LENGTH: usize = 64;
pub fn get_vrf_version() -> &'static str {
VRF_VERSION
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VerificationError {
InvalidProof,
InvalidInput,
InvalidPublicKey,
InvalidProofLength,
DecompressionFailed,
InvalidScalar,
InvalidGamma,
ZeroPublicKey,
ExpandMessageXmdFailed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VrfPublicKey(pub [u8; 32]);
impl VrfPublicKey {
pub fn validate(&self) -> Result<RistrettoPoint, VerificationError> {
if self.0.iter().all(|&b| b == 0) {
return Err(VerificationError::ZeroPublicKey);
}
let pk_point = CompressedRistretto(self.0)
.decompress()
.ok_or(VerificationError::InvalidPublicKey)?;
if pk_point == RistrettoPoint::default() {
return Err(VerificationError::ZeroPublicKey);
}
Ok(pk_point)
}
}
#[cfg_attr(feature = "near", near_sdk::near(serializers = [borsh, json]))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VrfProof {
pub gamma: [u8; 32], pub c: [u8; CHALLENGE_LENGTH], pub s: [u8; 32], }
pub type VrfOutput = [u8; 64];
impl VrfProof {
pub fn verify(
&self,
input: &[u8],
public_key: &VrfPublicKey,
) -> Result<VrfOutput, VerificationError> {
let output = self.to_output()?;
self.verify_output(input, public_key, &output)?;
Ok(output)
}
pub fn verify_output(
&self,
input: &[u8],
public_key: &VrfPublicKey,
expected_output: &VrfOutput,
) -> Result<(), VerificationError> {
let pk_point = public_key.validate()?;
let gamma = CompressedRistretto(self.gamma)
.decompress()
.ok_or(VerificationError::DecompressionFailed)?;
let s = Scalar::from_canonical_bytes(self.s)
.ok_or(VerificationError::InvalidScalar)?;
let h = hash_to_curve(&pk_point, input)?;
let challenge = challenge_from_hash(&self.c);
use curve25519_dalek_ng::constants::RISTRETTO_BASEPOINT_POINT;
let u = &s * &RISTRETTO_BASEPOINT_POINT - &challenge * &pk_point;
let v = &s * &h - &challenge * γ
let c_prime = generate_challenge(&pk_point, &h, &gamma, &u, &v)?;
if c_prime != self.c {
return Err(VerificationError::InvalidProof);
}
let computed_output = proof_to_hash(&gamma)?;
if computed_output != *expected_output {
return Err(VerificationError::InvalidProof);
}
Ok(())
}
pub fn to_output(&self) -> Result<VrfOutput, VerificationError> {
let gamma = CompressedRistretto(self.gamma)
.decompress()
.ok_or(VerificationError::InvalidGamma)?;
proof_to_hash(&gamma)
}
}
fn expand_message_xmd(msg: &[u8], dst: &[u8], len_in_bytes: usize) -> Result<Vec<u8>, VerificationError> {
let b_in_bytes = 64; let r_in_bytes = 64;
if len_in_bytes >= (1 << 16) || dst.len() > 255 {
return Err(VerificationError::ExpandMessageXmdFailed);
}
let ell = (len_in_bytes + r_in_bytes - 1) / r_in_bytes;
if ell >= 256 {
return Err(VerificationError::ExpandMessageXmdFailed);
}
let dst_prime = {
let mut dst_prime = dst.to_vec();
dst_prime.push(dst.len() as u8);
dst_prime
};
let z_pad = vec![0u8; b_in_bytes];
let l_i_b_str = [(len_in_bytes >> 8) as u8, len_in_bytes as u8];
let mut hasher = Sha512::new();
hasher.update(&z_pad);
hasher.update(msg);
hasher.update(&l_i_b_str);
hasher.update([0u8]);
hasher.update(&dst_prime);
let b_0 = hasher.finalize();
let mut hasher = Sha512::new();
hasher.update(&b_0);
hasher.update([1u8]);
hasher.update(&dst_prime);
let mut b_i = hasher.finalize();
let mut uniform_bytes = b_i.to_vec();
for i in 2..=ell {
let mut strxor_input = b_0.clone();
for (j, &byte) in b_i.iter().enumerate() {
strxor_input[j] ^= byte;
}
let mut hasher = Sha512::new();
hasher.update(&strxor_input);
hasher.update([i as u8]);
hasher.update(&dst_prime);
b_i = hasher.finalize();
uniform_bytes.extend_from_slice(&b_i);
}
uniform_bytes.truncate(len_in_bytes);
Ok(uniform_bytes)
}
fn hash_to_curve(pk: &RistrettoPoint, input: &[u8]) -> Result<RistrettoPoint, VerificationError> {
let mut msg = Vec::new();
msg.extend_from_slice(&pk.compress().0);
msg.extend_from_slice(input);
let uniform_bytes = expand_message_xmd(&msg, VRF_HASH_TO_CURVE_DOMAIN, EXPAND_MESSAGE_OUTPUT_LENGTH)?;
let mut uniform_array = [0u8; EXPAND_MESSAGE_OUTPUT_LENGTH];
uniform_array.copy_from_slice(&uniform_bytes);
Ok(RistrettoPoint::from_uniform_bytes(&uniform_array))
}
fn challenge_from_hash(hash: &[u8; CHALLENGE_LENGTH]) -> Scalar {
let mut scalar_bytes = [0u8; 32];
scalar_bytes[..CHALLENGE_LENGTH].copy_from_slice(hash);
Scalar::from_bytes_mod_order(scalar_bytes)
}
fn generate_challenge(
pk: &RistrettoPoint,
h: &RistrettoPoint,
gamma: &RistrettoPoint,
u: &RistrettoPoint,
v: &RistrettoPoint,
) -> Result<[u8; CHALLENGE_LENGTH], VerificationError> {
let mut hasher = Sha512::new();
hasher.update(SUITE_STRING);
hasher.update([CHALLENGE_GENERATION_DOMAIN_SEPARATOR_FRONT]);
hasher.update(&pk.compress().0);
hasher.update(&h.compress().0);
hasher.update(&gamma.compress().0);
hasher.update(&u.compress().0);
hasher.update(&v.compress().0);
hasher.update([CHALLENGE_GENERATION_DOMAIN_SEPARATOR_BACK]);
let hash = hasher.finalize();
let mut challenge = [0u8; CHALLENGE_LENGTH];
challenge.copy_from_slice(&hash[..CHALLENGE_LENGTH]);
Ok(challenge)
}
fn proof_to_hash(gamma: &RistrettoPoint) -> Result<VrfOutput, VerificationError> {
let mut hasher = Sha512::new();
hasher.update(SUITE_STRING);
hasher.update([PROOF_TO_HASH_DOMAIN_SEPARATOR_FRONT]);
hasher.update(&gamma.compress().0);
hasher.update([PROOF_TO_HASH_DOMAIN_SEPARATOR_BACK]);
Ok(hasher.finalize().into())
}
pub mod near_vrf_verifier {
use super::*;
const RFC_VRF_PROOF_LENGTH: usize = 32 + CHALLENGE_LENGTH + 32;
pub fn verify_vrf_fixed(
proof_bytes: &[u8; RFC_VRF_PROOF_LENGTH], public_key_bytes: &[u8; 32], input: &[u8],
) -> Result<VrfOutput, VerificationError> {
let public_key = VrfPublicKey(*public_key_bytes);
let proof = VrfProof {
gamma: proof_bytes[0..32].try_into()
.map_err(|_| VerificationError::InvalidProofLength)?,
c: proof_bytes[32..(32 + CHALLENGE_LENGTH)].try_into()
.map_err(|_| VerificationError::InvalidProofLength)?,
s: proof_bytes[(32 + CHALLENGE_LENGTH)..RFC_VRF_PROOF_LENGTH].try_into()
.map_err(|_| VerificationError::InvalidProofLength)?,
};
proof.verify(input, &public_key)
}
pub fn verify_vrf(
proof_bytes: Vec<u8>,
public_key_bytes: Vec<u8>,
input: Vec<u8>,
) -> Result<VrfOutput, VerificationError> {
if public_key_bytes.len() != 32 {
return Err(VerificationError::InvalidPublicKey);
}
if proof_bytes.len() != RFC_VRF_PROOF_LENGTH { return Err(VerificationError::InvalidProofLength);
}
let public_key_array: [u8; 32] = public_key_bytes.try_into()
.map_err(|_| VerificationError::InvalidPublicKey)?;
let proof_array: [u8; RFC_VRF_PROOF_LENGTH] = proof_bytes.try_into()
.map_err(|_| VerificationError::InvalidProofLength)?;
verify_vrf_fixed(&proof_array, &public_key_array, &input)
}
pub fn verify_vrf_bool(
proof_bytes: Vec<u8>,
public_key_bytes: Vec<u8>,
input: Vec<u8>,
) -> bool {
verify_vrf(proof_bytes, public_key_bytes, input).is_ok()
}
}