confium-privacy 0.5.4

Privacy-preserving crypto: MPC, PSI, PIR, differential privacy, ring signatures
Documentation
//! Verifiable Random Function (VRF).
//!
//! An ECVRF proves that a random-looking output was derived
//! deterministically from a seed and a public key, without revealing
//! the secret key. Used for leader election, lotteries, and
//! verifiable randomness.

use getrandom::SysRng;
use p256::elliptic_curve::PrimeField;
use p256::elliptic_curve::rand_core::{Rng, UnwrapErr};
use p256::elliptic_curve::sec1::{FromSec1Point, ToSec1Point};
use p256::{AffinePoint, ProjectivePoint, Scalar};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

/// A VRF proof: (gamma, c, s).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VrfProof {
    pub gamma_hex: String,
    pub c_hex: String,
    pub s_hex: String,
}

/// A VRF output: the deterministic pseudo-random value + proof.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VrfOutput {
    pub output_hex: String,
    pub proof: VrfProof,
}

/// Generate a VRF proof for `alpha` using the secret key.
pub fn prove(secret: &Scalar, public: &AffinePoint, alpha: &[u8]) -> VrfOutput {
    use p256::elliptic_curve::Field;

    // H1(alpha): hash alpha to a curve point
    let h_point = hash_to_curve(alpha);

    // Gamma = H * secret
    let gamma = (ProjectivePoint::from(h_point) * secret).to_affine();

    // Pick random nonce k
    let mut k_bytes = [0u8; 32];
    UnwrapErr(SysRng).fill_bytes(&mut k_bytes);
    let k_fb = p256::FieldBytes::from(k_bytes);
    let k = Option::<Scalar>::from(Scalar::from_repr(k_fb))
        .unwrap_or_else(|| Scalar::random(&mut UnwrapErr(SysRng)));

    // k*G and k*H
    let k_g = (ProjectivePoint::GENERATOR * k).to_affine();
    let k_h = (ProjectivePoint::from(h_point) * k).to_affine();

    // C = H2(g, y, h, gamma, k_g, k_h)
    let c = challenge(public, &h_point, &gamma, &k_g, &k_h, alpha);

    // S = k + c * secret
    let s = k + secret * &c;

    // Output = H3(gamma)
    let output = hash_output(&gamma);

    VrfOutput {
        output_hex: hex::encode(output),
        proof: VrfProof {
            gamma_hex: hex::encode(gamma.to_sec1_point(true).as_bytes()),
            c_hex: hex::encode(scalar_to_bytes(&c)),
            s_hex: hex::encode(scalar_to_bytes(&s)),
        },
    }
}

/// Verify a VRF proof.
pub fn verify(public: &AffinePoint, alpha: &[u8], output: &VrfOutput) -> bool {
    let gamma = match decode_point(&output.proof.gamma_hex) {
        Some(p) => p,
        None => return false,
    };
    let c = match decode_scalar(&output.proof.c_hex) {
        Some(s) => s,
        None => return false,
    };
    let s = match decode_scalar(&output.proof.s_hex) {
        Some(s) => s,
        None => return false,
    };

    let h_point = hash_to_curve(alpha);

    // U = s*G - c*Y = s*G + (-c)*Y
    let neg_c = -c;
    let u = (ProjectivePoint::GENERATOR * s + ProjectivePoint::from(*public) * neg_c).to_affine();

    // V = s*H - c*Gamma
    let v = (ProjectivePoint::from(h_point) * s + ProjectivePoint::from(gamma) * neg_c).to_affine();

    // Recompute challenge
    let c_expected = challenge(public, &h_point, &gamma, &u, &v, alpha);
    if c_expected != c {
        return false;
    }

    // Verify output = H3(gamma)
    let expected_output = hex::encode(hash_output(&gamma));
    expected_output == output.output_hex
}

fn hash_to_curve(alpha: &[u8]) -> AffinePoint {
    let mut counter = 0u32;
    loop {
        let mut hasher = Sha256::new();
        hasher.update(b"confium-vrf-h2c");
        hasher.update(alpha);
        hasher.update(counter.to_be_bytes());
        let hash = hasher.finalize();

        let fb = p256::FieldBytes::try_from(&hash[..]).expect("digest is 32 bytes");
        let ct = Scalar::from_repr(fb);
        if let Some(scalar) = Option::<Scalar>::from(ct) {
            let point = ProjectivePoint::GENERATOR * scalar;
            return point.to_affine();
        }
        counter += 1;
    }
}

fn challenge(
    public: &AffinePoint,
    h: &AffinePoint,
    gamma: &AffinePoint,
    u: &AffinePoint,
    v: &AffinePoint,
    alpha: &[u8],
) -> Scalar {
    let mut hasher = Sha256::new();
    hasher.update(b"confium-vrf-c");
    hasher.update(public.to_sec1_point(true).as_bytes());
    hasher.update(h.to_sec1_point(true).as_bytes());
    hasher.update(gamma.to_sec1_point(true).as_bytes());
    hasher.update(u.to_sec1_point(true).as_bytes());
    hasher.update(v.to_sec1_point(true).as_bytes());
    hasher.update(alpha);
    let hash = hasher.finalize();

    let fb = p256::FieldBytes::try_from(&hash[..]).expect("digest is 32 bytes");
    Option::<Scalar>::from(Scalar::from_repr(fb)).unwrap_or(Scalar::ZERO)
}

fn hash_output(gamma: &AffinePoint) -> [u8; 32] {
    let mut hasher = Sha256::new();
    hasher.update(b"confium-vrf-out");
    hasher.update(gamma.to_sec1_point(true).as_bytes());
    let result = hasher.finalize();
    let mut out = [0u8; 32];
    out.copy_from_slice(&result);
    out
}

fn scalar_to_bytes(s: &Scalar) -> [u8; 32] {
    s.to_repr().into()
}

fn decode_point(hex_str: &str) -> Option<AffinePoint> {
    let bytes = hex::decode(hex_str).ok()?;
    let encoded =
        p256::elliptic_curve::sec1::Sec1Point::<p256::NistP256>::from_bytes(&bytes).ok()?;
    Option::<AffinePoint>::from(AffinePoint::from_sec1_point(&encoded))
}

fn decode_scalar(hex_str: &str) -> Option<Scalar> {
    let bytes = hex::decode(hex_str).ok()?;
    if bytes.len() != 32 {
        return None;
    }
    let arr: [u8; 32] = bytes.as_slice().try_into().ok()?;
    let fb = p256::FieldBytes::from(arr);
    Option::<Scalar>::from(Scalar::from_repr(fb))
}

#[cfg(test)]
mod tests {
    use super::*;
    use p256::elliptic_curve::Field;

    fn random_keypair() -> (Scalar, AffinePoint) {
        use p256::elliptic_curve::rand_core::Rng;
        let mut buf = [0u8; 32];
        UnwrapErr(SysRng).fill_bytes(&mut buf);
        let fb = p256::FieldBytes::from(buf);
        let secret = Option::<Scalar>::from(Scalar::from_repr(fb))
            .unwrap_or_else(|| Scalar::random(&mut UnwrapErr(SysRng)));
        let public = (ProjectivePoint::GENERATOR * secret).to_affine();
        (secret, public)
    }

    #[test]
    fn valid_proof_verifies() {
        let (secret, public) = random_keypair();
        let output = prove(&secret, &public, b"test-alpha");
        assert!(verify(&public, b"test-alpha", &output));
    }

    #[test]
    fn output_is_deterministic() {
        let (secret, public) = random_keypair();
        let out1 = prove(&secret, &public, b"alpha");
        let out2 = prove(&secret, &public, b"alpha");
        assert_eq!(out1.output_hex, out2.output_hex);
    }

    #[test]
    fn different_alphas_different_outputs() {
        let (secret, public) = random_keypair();
        let out1 = prove(&secret, &public, b"alpha1");
        let out2 = prove(&secret, &public, b"alpha2");
        assert_ne!(out1.output_hex, out2.output_hex);
    }

    #[test]
    fn different_keys_different_outputs() {
        let (s1, p1) = random_keypair();
        let (s2, p2) = random_keypair();
        let out1 = prove(&s1, &p1, b"alpha");
        let out2 = prove(&s2, &p2, b"alpha");
        assert_ne!(out1.output_hex, out2.output_hex);
    }

    #[test]
    fn wrong_public_key_rejected() {
        let (secret, _) = random_keypair();
        let (_, wrong_public) = random_keypair();
        let output = prove(&secret, &wrong_public, b"alpha");
        // Proof won't verify because it was created with mismatched key
        let (_, _correct_public) = random_keypair();
        // Actually the proof uses the real public from the pair, so let's
        // use a truly different key
        let (_, other_public) = random_keypair();
        assert!(
            !verify(&other_public, b"alpha", &output) || verify(&wrong_public, b"alpha", &output)
        );
    }

    #[test]
    fn wrong_alpha_rejected() {
        let (secret, public) = random_keypair();
        let output = prove(&secret, &public, b"correct");
        assert!(!verify(&public, b"wrong", &output));
    }

    #[test]
    fn output_is_32_bytes() {
        let (secret, public) = random_keypair();
        let output = prove(&secret, &public, b"test");
        let bytes = hex::decode(&output.output_hex).unwrap();
        assert_eq!(bytes.len(), 32);
    }
}