Skip to main content

ling_crypto/
vrf.rs

1//! Verifiable Random Function (VRF) using Ed25519.
2//!
3//! A VRF produces a pseudorandom output together with a proof that the
4//! output was computed correctly. Given `(pubkey, input)`, anyone can
5//! verify that `output = VRF(privkey, input)` without seeing privkey.
6//!
7//! Construction: ECVRF-EDWARDS25519-SHA512-TAI (simplified, not full RFC 9381).
8
9use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
10use rand::rngs::OsRng;
11use sha3::{Digest, Sha3_512};
12
13pub struct VrfKeypair {
14    signing_key: SigningKey,
15}
16
17#[derive(Clone, Debug)]
18pub struct VrfProof {
19    pub proof_bytes: [u8; 64], // Ed25519 signature over H(input)
20    pub output: [u8; 32],      // pseudorandom output derived from signature
21}
22
23impl VrfKeypair {
24    pub fn generate() -> Self {
25        Self { signing_key: SigningKey::generate(&mut OsRng) }
26    }
27
28    pub fn from_seed(seed: [u8; 32]) -> Self {
29        Self { signing_key: SigningKey::from_bytes(&seed) }
30    }
31
32    pub fn public_key(&self) -> [u8; 32] {
33        self.signing_key.verifying_key().to_bytes()
34    }
35
36    /// Evaluate the VRF: sign H("vrf:"||input) and hash the signature.
37    pub fn evaluate(&self, input: &[u8]) -> VrfProof {
38        let h = domain_hash(input);
39        let sig = self.signing_key.sign(&h);
40        let proof_bytes = sig.to_bytes();
41        let output = output_hash(&proof_bytes);
42        VrfProof { proof_bytes, output }
43    }
44}
45
46/// Verify that `proof.output` is the correct VRF output for `pubkey` + `input`.
47pub fn vrf_verify(pubkey: &[u8; 32], input: &[u8], proof: &VrfProof) -> bool {
48    let vk = match VerifyingKey::from_bytes(pubkey) {
49        Ok(k) => k,
50        Err(_) => return false,
51    };
52    let h = domain_hash(input);
53    let sig = Signature::from_bytes(&proof.proof_bytes);
54    if vk.verify_strict(&h, &sig).is_err() {
55        return false;
56    }
57    // Verify output matches
58    proof.output == output_hash(&proof.proof_bytes)
59}
60
61fn domain_hash(input: &[u8]) -> Vec<u8> {
62    let mut h = Sha3_512::new();
63    h.update(b"ling-vrf-v1:");
64    h.update(input);
65    h.finalize().to_vec()
66}
67
68fn output_hash(sig: &[u8; 64]) -> [u8; 32] {
69    let mut h = Sha3_512::new();
70    h.update(b"ling-vrf-out-v1:");
71    h.update(sig);
72    let out = h.finalize();
73    out[..32].try_into().unwrap()
74}