use core::fmt;
use crate::error::{Error, Result};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VrfOutput([u8; 64]);
impl VrfOutput {
#[must_use]
pub fn from_bytes(bytes: [u8; 64]) -> Self {
Self(bytes)
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; 64] {
&self.0
}
#[must_use]
pub fn index(&self) -> [u8; 32] {
let mut index = [0u8; 32];
index.copy_from_slice(&self.0[..32]);
index
}
}
impl fmt::Debug for VrfOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("VrfOutput(..)")
}
}
#[derive(Clone)]
pub struct VrfSecretKey(Vec<u8>);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct VrfPublicKey(Vec<u8>);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct VrfProof(Vec<u8>);
macro_rules! byte_wrapper {
($t:ty, $what:literal) => {
impl $t {
#[doc = concat!("Wrap raw ", $what, " bytes.")]
#[must_use]
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self(bytes)
}
#[doc = concat!("Borrow the raw ", $what, " bytes.")]
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[doc = concat!("Consume into the raw ", $what, " bytes.")]
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.0
}
}
};
}
byte_wrapper!(VrfSecretKey, "secret-key");
byte_wrapper!(VrfPublicKey, "public-key");
byte_wrapper!(VrfProof, "proof");
impl fmt::Debug for VrfSecretKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("VrfSecretKey(..)")
}
}
pub trait Vrf {
fn suite_id(&self) -> u8;
fn generate_keypair(&self) -> (VrfSecretKey, VrfPublicKey);
fn derive_public_key(&self, secret_key: &VrfSecretKey) -> Result<VrfPublicKey>;
fn prove(&self, secret_key: &VrfSecretKey, alpha: &[u8]) -> Result<VrfProof>;
fn verify(
&self,
public_key: &VrfPublicKey,
alpha: &[u8],
proof: &VrfProof,
) -> Result<Option<VrfOutput>>;
fn proof_to_output(&self, proof: &VrfProof) -> Result<VrfOutput>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Ecvrf;
impl Vrf for Ecvrf {
fn suite_id(&self) -> u8 {
metamorphic_crypto::ECVRF_EDWARDS25519_SHA512_TAI_SUITE
}
fn generate_keypair(&self) -> (VrfSecretKey, VrfPublicKey) {
let (sk, pk) = metamorphic_crypto::ecvrf_generate_keypair();
(VrfSecretKey(sk.to_vec()), VrfPublicKey(pk.to_vec()))
}
fn derive_public_key(&self, secret_key: &VrfSecretKey) -> Result<VrfPublicKey> {
let pk = metamorphic_crypto::ecvrf_public_key(secret_key.as_bytes())
.map_err(|e| Error::Vrf(e.to_string()))?;
Ok(VrfPublicKey(pk.to_vec()))
}
fn prove(&self, secret_key: &VrfSecretKey, alpha: &[u8]) -> Result<VrfProof> {
let pi = metamorphic_crypto::ecvrf_prove(secret_key.as_bytes(), alpha)
.map_err(|e| Error::Vrf(e.to_string()))?;
Ok(VrfProof(pi.to_vec()))
}
fn verify(
&self,
public_key: &VrfPublicKey,
alpha: &[u8],
proof: &VrfProof,
) -> Result<Option<VrfOutput>> {
let beta = metamorphic_crypto::ecvrf_verify(public_key.as_bytes(), alpha, proof.as_bytes())
.map_err(|e| Error::Vrf(e.to_string()))?;
Ok(beta.map(VrfOutput))
}
fn proof_to_output(&self, proof: &VrfProof) -> Result<VrfOutput> {
let beta = metamorphic_crypto::ecvrf_proof_to_hash(proof.as_bytes())
.map_err(|e| Error::Vrf(e.to_string()))?;
Ok(VrfOutput(beta))
}
}
pub const HYBRID_OUTPUT_DST: &str = "metamorphic.app/vrf-hybrid-output/v1";
#[must_use]
pub fn hybrid_output(classical: &VrfOutput, pq: &VrfOutput) -> VrfOutput {
let mut framed = [0u8; 128];
framed[..64].copy_from_slice(classical.as_bytes());
framed[64..].copy_from_slice(pq.as_bytes());
VrfOutput(metamorphic_crypto::hash::sha3_512_with_context(
HYBRID_OUTPUT_DST,
&framed,
))
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
#[test]
fn ecvrf_suite_id_is_tai() {
assert_eq!(Ecvrf.suite_id(), 0x03);
}
#[test]
fn prove_verify_roundtrip_through_trait() {
let vrf = Ecvrf;
let (sk, pk) = vrf.generate_keypair();
let alpha = b"alice@example.com";
let pi = vrf.prove(&sk, alpha).unwrap();
let out = vrf.verify(&pk, alpha, &pi).unwrap();
assert_eq!(out, Some(vrf.proof_to_output(&pi).unwrap()));
}
#[test]
fn derive_public_key_matches_keygen() {
let vrf = Ecvrf;
let (sk, pk) = vrf.generate_keypair();
assert_eq!(vrf.derive_public_key(&sk).unwrap(), pk);
}
#[test]
fn verify_rejects_tampered_input() {
let vrf = Ecvrf;
let (sk, pk) = vrf.generate_keypair();
let pi = vrf.prove(&sk, b"original").unwrap();
assert_eq!(vrf.verify(&pk, b"tampered", &pi).unwrap(), None);
}
#[test]
fn verify_rejects_wrong_key() {
let vrf = Ecvrf;
let (sk, _pk) = vrf.generate_keypair();
let (_sk2, pk2) = vrf.generate_keypair();
let pi = vrf.prove(&sk, b"x").unwrap();
assert_eq!(vrf.verify(&pk2, b"x", &pi).unwrap(), None);
}
#[test]
fn structural_errors_surface_as_vrf_error() {
let vrf = Ecvrf;
let bad_pk = VrfPublicKey::from_bytes(vec![0u8; 31]);
let pi = VrfProof::from_bytes(vec![0u8; 80]);
assert!(matches!(vrf.verify(&bad_pk, b"x", &pi), Err(Error::Vrf(_))));
}
#[test]
fn index_is_first_32_bytes_of_output() {
let mut beta = [0u8; 64];
for (i, b) in beta.iter_mut().enumerate() {
*b = i as u8;
}
let out = VrfOutput::from_bytes(beta);
assert_eq!(&out.index()[..], &beta[..32]);
}
#[test]
fn hybrid_output_is_deterministic_and_order_sensitive() {
let a = VrfOutput::from_bytes([1u8; 64]);
let b = VrfOutput::from_bytes([2u8; 64]);
assert_eq!(hybrid_output(&a, &b), hybrid_output(&a, &b));
assert_ne!(hybrid_output(&a, &b), hybrid_output(&b, &a));
}
#[test]
fn hybrid_output_matches_documented_framing() {
let a = VrfOutput::from_bytes([7u8; 64]);
let b = VrfOutput::from_bytes([9u8; 64]);
let mut framed = Vec::new();
framed.extend_from_slice(a.as_bytes());
framed.extend_from_slice(b.as_bytes());
let expected = metamorphic_crypto::hash::sha3_512_with_context(HYBRID_OUTPUT_DST, &framed);
assert_eq!(hybrid_output(&a, &b).as_bytes(), &expected);
}
#[test]
fn vrf_is_object_safe() {
let vrf: Box<dyn Vrf> = Box::new(Ecvrf);
assert_eq!(vrf.suite_id(), 0x03);
}
}