solana-ecvrf 0.0.1

ECVRF-EDWARDS25519-SHA512-TAI (RFC 9381) verification for Solana programs using curve25519 and sha512 syscalls
Documentation
//! RFC 9381 §5.1 proving, host-only. Runs on `curve25519-dalek`; there is no
//! on-chain prover because proving reveals the secret key.

use crate::{PROOF_LENGTH, Proof, PublicKey, challenge, sha512};
use curve25519_dalek::{
    Scalar, constants::ED25519_BASEPOINT_POINT as B, edwards::CompressedEdwardsY,
    scalar::clamp_integer,
};

/// A 32-byte Ed25519 seed (RFC 8032 §5.1.5).
#[derive(Clone, Copy)]
pub struct SecretKey(pub [u8; 32]);

impl SecretKey {
    fn expand(&self) -> (Scalar, [u8; 32]) {
        let h = sha512::hashv(&[&self.0]);
        let x = Scalar::from_bytes_mod_order(clamp_integer(h[..32].try_into().unwrap()));
        (x, h[32..].try_into().unwrap())
    }

    pub fn public_key(&self) -> PublicKey {
        PublicKey((self.expand().0 * B).compress().0)
    }

    /// `ECVRF_prove(SK, alpha_string)`.
    pub fn prove(&self, alpha: &[u8]) -> Proof {
        let (x, nonce_key) = self.expand();
        let y = (x * B).compress().0;
        let h_bytes = crate::encode_to_curve(&y, alpha).expect("2^-256");
        let h = CompressedEdwardsY(h_bytes).decompress().unwrap();
        let gamma = x * h;
        // §5.4.2.2 nonce from RFC 8032.
        let k = Scalar::from_bytes_mod_order_wide(&sha512::hashv(&[&nonce_key, &h_bytes]));
        let u = (k * B).compress().0;
        let v = (k * h).compress().0;
        let gamma_bytes = gamma.compress().0;
        let c = challenge(&y, &h_bytes, &gamma_bytes, &u, &v);
        let mut c_wide = [0u8; 32];
        c_wide[..16].copy_from_slice(&c);
        let s = k + Scalar::from_bytes_mod_order(c_wide) * x;

        let mut pi = [0u8; PROOF_LENGTH];
        pi[..32].copy_from_slice(&gamma_bytes);
        pi[32..48].copy_from_slice(&c);
        pi[48..].copy_from_slice(&s.to_bytes());
        Proof(pi)
    }
}