use curve25519_dalek::{
constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY, scalar::Scalar,
};
use sha2::{Digest, Sha512};
use super::point::{cardano_clear_cofactor, cardano_hash_to_curve};
use crate::common::{point_to_bytes, CryptoError, CryptoResult, SUITE_DRAFT03, THREE, TWO};
pub fn cardano_vrf_verify(
public_key: &[u8; 32],
proof: &[u8; 80],
message: &[u8],
) -> CryptoResult<[u8; 64]> {
let gamma_bytes: [u8; 32] = proof[0..32]
.try_into()
.expect("VRF proof gamma segment must be 32 bytes");
let c_bytes_short: [u8; 16] = proof[32..48]
.try_into()
.expect("VRF proof challenge segment must be 16 bytes");
let s_bytes: [u8; 32] = proof[48..80]
.try_into()
.expect("VRF proof scalar segment must be 32 bytes");
let y_point = CompressedEdwardsY(*public_key)
.decompress()
.ok_or(CryptoError::InvalidPublicKey)?;
let gamma = CompressedEdwardsY(gamma_bytes)
.decompress()
.ok_or(CryptoError::InvalidProof)?;
let s = Scalar::from_bytes_mod_order(s_bytes);
let mut c_bytes = [0u8; 32];
c_bytes[0..16].copy_from_slice(&c_bytes_short);
let c = Scalar::from_bytes_mod_order(c_bytes);
let (h_point, h_string) = cardano_hash_to_curve(public_key, message)?;
let neg_c = -c;
let k_b = (ED25519_BASEPOINT_POINT * s) + (y_point * neg_c);
let s_h = h_point * s;
let c_gamma = gamma * neg_c;
let k_h = s_h + c_gamma;
let k_b_bytes = point_to_bytes(&k_b);
let k_h_bytes = point_to_bytes(&k_h);
let mut c_hasher = Sha512::new();
c_hasher.update([SUITE_DRAFT03]);
c_hasher.update([TWO]);
c_hasher.update(h_string);
c_hasher.update(gamma_bytes);
c_hasher.update(k_b_bytes);
c_hasher.update(k_h_bytes);
let c_hash = c_hasher.finalize();
let challenge_matches = c_hash[0..16] == c_bytes_short[..];
if !challenge_matches {
return Err(CryptoError::VerificationFailed);
}
let gamma_cleared = cardano_clear_cofactor(&gamma);
let mut output_hasher = Sha512::new();
output_hasher.update([SUITE_DRAFT03]);
output_hasher.update([THREE]);
output_hasher.update(point_to_bytes(&gamma_cleared));
let output_hash = output_hasher.finalize();
let mut output = [0u8; 64];
output.copy_from_slice(&output_hash);
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::point_to_bytes;
use crate::vrf::cardano_compat::prove::cardano_vrf_prove;
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use sha2::{Digest, Sha512};
#[test]
fn test_verify_roundtrip() {
let seed = [1u8; 32];
let mut hasher = Sha512::new();
hasher.update(seed);
let hash = hasher.finalize();
let mut secret_scalar_bytes = [0u8; 32];
secret_scalar_bytes.copy_from_slice(&hash[0..32]);
secret_scalar_bytes[0] &= 248;
secret_scalar_bytes[31] &= 127;
secret_scalar_bytes[31] |= 64;
let scalar = Scalar::from_bytes_mod_order(secret_scalar_bytes);
let public_point = ED25519_BASEPOINT_POINT * scalar;
let public_key = point_to_bytes(&public_point);
let mut sk = [0u8; 64];
sk[0..32].copy_from_slice(&seed);
sk[32..64].copy_from_slice(&public_key);
let message = b"test";
let proof = cardano_vrf_prove(&sk, message).expect("prove failed");
let output = cardano_vrf_verify(&public_key, &proof, message).expect("verify failed");
assert_eq!(output.len(), 64);
}
#[test]
fn test_verify_rejects_invalid_proof() {
let pk = [0u8; 32];
let message = b"test";
let invalid_proof = [0u8; 80];
let result = cardano_vrf_verify(&pk, &invalid_proof, message);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_wrong_message() {
let mut sk = [0u8; 64];
sk[0..32].fill(1);
sk[32..64].copy_from_slice(&[2u8; 32]);
let pk = &sk[32..64];
let message = b"test";
let proof = cardano_vrf_prove(&sk, message).expect("prove failed");
let result = cardano_vrf_verify(pk.try_into().unwrap(), &proof, b"wrong");
assert!(result.is_err());
}
}