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};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VrfProof {
pub gamma_hex: String,
pub c_hex: String,
pub s_hex: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VrfOutput {
pub output_hex: String,
pub proof: VrfProof,
}
pub fn prove(secret: &Scalar, public: &AffinePoint, alpha: &[u8]) -> VrfOutput {
use p256::elliptic_curve::Field;
let h_point = hash_to_curve(alpha);
let gamma = (ProjectivePoint::from(h_point) * secret).to_affine();
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)));
let k_g = (ProjectivePoint::GENERATOR * k).to_affine();
let k_h = (ProjectivePoint::from(h_point) * k).to_affine();
let c = challenge(public, &h_point, &gamma, &k_g, &k_h, alpha);
let s = k + secret * &c;
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)),
},
}
}
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);
let neg_c = -c;
let u = (ProjectivePoint::GENERATOR * s + ProjectivePoint::from(*public) * neg_c).to_affine();
let v = (ProjectivePoint::from(h_point) * s + ProjectivePoint::from(gamma) * neg_c).to_affine();
let c_expected = challenge(public, &h_point, &gamma, &u, &v, alpha);
if c_expected != c {
return false;
}
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");
let (_, _correct_public) = random_keypair();
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);
}
}