-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add the vrf implementation crate
This module contains implementations of a verifiable random function, currently only ECVRF. VRFs can be used in the consensus protocol for leader election.
- Loading branch information
1 parent
429bca1
commit dcd6e0d
Showing
10 changed files
with
952 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[package] | ||
name = "vrf" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
num-bigint = "0.4.6" | ||
num-traits = "0.2.19" | ||
bincode = "1.1.1" | ||
bytes = "1.6.1" | ||
curve25519-dalek = "4.1.3" | ||
derive_deref = "1.0.2" | ||
anyhow = { version = "1.0.79", features = ["backtrace"] } | ||
failure = "0.1.3" | ||
ed25519-dalek = { version = "2.1.1", features = ["serde", "digest", "rand_core", "pem", "pkcs8"] } | ||
hex = "0.4.3" | ||
lazy_static = "1.3.0" | ||
proptest = "1.5.0" | ||
proptest-derive = "0.5.0" | ||
rand = { version = "0.8.5" } | ||
rand_core = "0.6.4" | ||
serde = { version = "1.0.89", features = ["derive"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,337 @@ | ||
//! This module implements an instantiation of a verifiable random function known as | ||
//! [ECVRF-ED25519-SHA512-TAI](https://tools.ietf.org/html/draft-irtf-cfrg-vrf-04). | ||
//! | ||
//! # Examples | ||
//! | ||
//! ``` | ||
//! use vrf::{traits::Uniform, ecvrf::*}; | ||
//! use rand::{rngs::StdRng, SeedableRng}; | ||
//! | ||
//! let message = b"Test message"; | ||
//! let mut rng: StdRng = SeedableRng::from_seed([0; 32]); | ||
//! let private_key = VRFPrivateKey::generate_for_testing(&mut rng); | ||
//! let public_key: VRFPublicKey = (&private_key).into(); | ||
//! ``` | ||
//! **Note**: The above example generates a private key using a private function intended only for | ||
//! testing purposes. Production code should find an alternate means for secure key generation. | ||
//! | ||
//! Produce a proof for a message from a `VRFPrivateKey`, and verify the proof and message | ||
//! using a `VRFPublicKey`: | ||
//! | ||
//! ``` | ||
//! # use vrf::{traits::Uniform, ecvrf::*}; | ||
//! # use rand::{rngs::StdRng, SeedableRng}; | ||
//! # let message = b"Test message"; | ||
//! # let mut rng: StdRng = SeedableRng::from_seed([0; 32]); | ||
//! # let private_key = VRFPrivateKey::generate_for_testing(&mut rng); | ||
//! # let public_key: VRFPublicKey = (&private_key).into(); | ||
//! let proof = private_key.prove(message); | ||
//! assert!(public_key.verify(&proof, message).is_ok()); | ||
//! ``` | ||
//! | ||
//! Produce a pseudorandom output from a `Proof`: | ||
//! | ||
//! ``` | ||
//! # use vrf::{traits::Uniform, ecvrf::*}; | ||
//! # use rand::{rngs::StdRng, SeedableRng}; | ||
//! # let message = b"Test message"; | ||
//! # let mut rng: StdRng = SeedableRng::from_seed([0; 32]); | ||
//! # let private_key = VRFPrivateKey::generate_for_testing(&mut rng); | ||
//! # let public_key: VRFPublicKey = (&private_key).into(); | ||
//! # let proof = private_key.prove(message); | ||
//! let output: Output = (&proof).into(); | ||
//! ``` | ||
use failure::bail; | ||
use crate::traits::*; | ||
use core::convert::TryFrom; | ||
use curve25519_dalek::{ | ||
constants::ED25519_BASEPOINT_POINT, digest::Update, edwards::{CompressedEdwardsY, EdwardsPoint}, scalar::Scalar as ed25519_Scalar | ||
}; | ||
use derive_deref::Deref; | ||
use ed25519_dalek::{ | ||
self, Digest, VerifyingKey as ed25519_PublicKey, SecretKey as ed25519_PrivateKey, SigningKey, Sha512, | ||
}; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
const SUITE: u8 = 0x03; | ||
const ONE: u8 = 0x01; | ||
const TWO: u8 = 0x02; | ||
const THREE: u8 = 0x03; | ||
|
||
/// The number of bytes of [`Output`] | ||
pub const OUTPUT_LENGTH: usize = 64; | ||
/// The number of bytes of [`Proof`] | ||
pub const PROOF_LENGTH: usize = 80; | ||
|
||
/// An ECVRF private key | ||
#[derive(Serialize, Deserialize, Deref, Debug)] | ||
pub struct VRFPrivateKey(ed25519_PrivateKey); | ||
|
||
/// An ECVRF public key | ||
#[derive(Serialize, Deserialize, Deref, Debug, PartialEq, Eq)] | ||
pub struct VRFPublicKey(ed25519_PublicKey); | ||
|
||
/// A longer private key which is slightly optimized for proof generation. | ||
/// | ||
/// This is similar in structure to ed25519_dalek::ExpandedSecretKey. It can be produced from | ||
/// a VRFPrivateKey. | ||
pub struct VRFExpandedPrivateKey { | ||
pub(super) key: ed25519_Scalar, | ||
pub(super) nonce: [u8; 32], | ||
} | ||
|
||
impl VRFPrivateKey { | ||
/// Produces a proof for an input (using the private key) | ||
pub fn prove(&self, alpha: &[u8]) -> Proof { | ||
let sign_key = SigningKey::from_bytes(&self.0); | ||
VRFExpandedPrivateKey::from(self).prove(&VRFPublicKey(sign_key.verifying_key()), alpha) | ||
} | ||
} | ||
|
||
impl VRFExpandedPrivateKey { | ||
/// Produces a proof for an input (using the expanded private key) | ||
pub fn prove(&self, pk: &VRFPublicKey, alpha: &[u8]) -> Proof { | ||
let h_point = pk.hash_to_curve(alpha); | ||
let k_scalar = | ||
ed25519_Scalar::from_bytes_mod_order_wide(&nonce_generation_bytes(self.nonce, h_point)); | ||
let gamma = h_point * self.key; | ||
let c_scalar = hash_points(&[ | ||
h_point, | ||
gamma, | ||
ED25519_BASEPOINT_POINT * k_scalar, | ||
h_point * k_scalar, | ||
]); | ||
|
||
Proof { | ||
gamma, | ||
c: c_scalar, | ||
s: k_scalar + c_scalar * self.key, | ||
} | ||
} | ||
} | ||
|
||
impl Uniform for VRFPrivateKey { | ||
fn generate_for_testing<R>(rng: &mut R) -> Self | ||
where | ||
R: ::rand::SeedableRng + ::rand::RngCore + ::rand::CryptoRng, | ||
{ | ||
let sign_key = SigningKey::generate(rng); | ||
VRFPrivateKey(sign_key.to_bytes()) | ||
} | ||
} | ||
|
||
impl VRFPrivateKey { | ||
pub fn generate_keypair<R>(rng: &mut R) -> Self | ||
where | ||
R: ::rand::RngCore + ::rand::CryptoRng, | ||
{ | ||
let sign_key = SigningKey::generate(rng); | ||
VRFPrivateKey(sign_key.to_bytes()) | ||
} | ||
} | ||
|
||
impl TryFrom<&[u8]> for VRFPrivateKey { | ||
type Error = CryptoMaterialError; | ||
|
||
fn try_from(bytes: &[u8]) -> std::result::Result<VRFPrivateKey, CryptoMaterialError> { | ||
let mut bits: [u8; 32] = [0u8; 32]; | ||
bits.copy_from_slice(&bytes[..32]); | ||
let sign_key = SigningKey::from_bytes(&bits); | ||
Ok(VRFPrivateKey( | ||
sign_key.to_bytes(), | ||
)) | ||
} | ||
} | ||
|
||
impl TryFrom<&[u8]> for VRFPublicKey { | ||
type Error = CryptoMaterialError; | ||
|
||
fn try_from(bytes: &[u8]) -> std::result::Result<VRFPublicKey, CryptoMaterialError> { | ||
if bytes.len() != ed25519_dalek::PUBLIC_KEY_LENGTH { | ||
return Err(CryptoMaterialError::WrongLengthError); | ||
} | ||
|
||
let mut bits: [u8; 32] = [0u8; 32]; | ||
bits.copy_from_slice(&bytes[..32]); | ||
|
||
let compressed = curve25519_dalek::edwards::CompressedEdwardsY(bits); | ||
let point = compressed | ||
.decompress() | ||
.ok_or(CryptoMaterialError::DeserializationError)?; | ||
|
||
// Check if the point lies on a small subgroup. This is required | ||
// when using curves with a small cofactor (in ed25519, cofactor = 8). | ||
if point.is_small_order() { | ||
return Err(CryptoMaterialError::SmallSubgroupError); | ||
} | ||
|
||
Ok(VRFPublicKey(ed25519_PublicKey::from(point))) | ||
} | ||
} | ||
|
||
impl VRFPublicKey { | ||
/// Given a [`Proof`] and an input, returns whether or not the proof is valid for the input | ||
/// and public key | ||
pub fn verify(&self, proof: &Proof, alpha: &[u8]) -> Result<(), failure::Error> { | ||
let h_point = self.hash_to_curve(alpha); | ||
let pk_point = CompressedEdwardsY::from_slice(self.as_bytes()) | ||
.unwrap() | ||
.decompress() | ||
.unwrap(); | ||
let cprime = hash_points(&[ | ||
h_point, | ||
proof.gamma, | ||
ED25519_BASEPOINT_POINT * proof.s - pk_point * proof.c, | ||
h_point * proof.s - proof.gamma * proof.c, | ||
]); | ||
|
||
if proof.c == cprime { | ||
Ok(()) | ||
} else { | ||
bail!("The proof failed to verify for this public key") | ||
} | ||
} | ||
|
||
pub(super) fn hash_to_curve(&self, alpha: &[u8]) -> EdwardsPoint { | ||
let mut result = [0u8; 32]; | ||
let mut counter = 0; | ||
let mut wrapped_point: Option<EdwardsPoint> = None; | ||
|
||
while wrapped_point.is_none() { | ||
let mut hasher = Sha512::new(); | ||
Digest::update(&mut hasher, &[SUITE, ONE]); | ||
Digest::update(&mut hasher, self.as_bytes()); | ||
Digest::update(&mut hasher, alpha); | ||
Digest::update(&mut hasher, &[counter]); | ||
let hash_result = hasher.finalize(); | ||
|
||
result.copy_from_slice(&hash_result[..32]); | ||
|
||
wrapped_point = CompressedEdwardsY::from_slice(&result).unwrap().decompress(); | ||
counter += 1; | ||
} | ||
|
||
wrapped_point.unwrap().mul_by_cofactor() | ||
} | ||
} | ||
|
||
impl<'a> From<&'a VRFPrivateKey> for VRFPublicKey { | ||
fn from(private_key: &'a VRFPrivateKey) -> Self { | ||
let secret: &ed25519_PrivateKey = private_key; | ||
let sign_key = SigningKey::from_bytes(&secret); | ||
let public: ed25519_PublicKey = sign_key.verifying_key(); | ||
VRFPublicKey(public) | ||
} | ||
} | ||
|
||
impl<'a> From<&'a VRFPrivateKey> for VRFExpandedPrivateKey { | ||
fn from(private_key: &'a VRFPrivateKey) -> Self { | ||
let mut h: Sha512 = Sha512::default(); | ||
let mut hash: [u8; 64] = [0u8; 64]; | ||
let mut lower: [u8; 32] = [0u8; 32]; | ||
let mut upper: [u8; 32] = [0u8; 32]; | ||
|
||
Digest::update(&mut h, &private_key.0); | ||
hash.copy_from_slice(h.finalize().as_slice()); | ||
|
||
lower.copy_from_slice(&hash[00..32]); | ||
upper.copy_from_slice(&hash[32..64]); | ||
|
||
lower[0] &= 248; | ||
lower[31] &= 63; | ||
lower[31] |= 64; | ||
|
||
VRFExpandedPrivateKey { | ||
key: ed25519_Scalar::from_bytes_mod_order(lower), | ||
nonce: upper, | ||
} | ||
} | ||
} | ||
|
||
/// A VRF proof that can be used to validate an input with a public key | ||
pub struct Proof { | ||
gamma: EdwardsPoint, | ||
c: ed25519_Scalar, | ||
s: ed25519_Scalar, | ||
} | ||
|
||
impl Proof { | ||
/// Produces a new Proof struct from its fields | ||
pub fn new(gamma: EdwardsPoint, c: ed25519_Scalar, s: ed25519_Scalar) -> Proof { | ||
Proof { gamma, c, s } | ||
} | ||
|
||
/// Converts a Proof into bytes | ||
pub fn to_bytes(&self) -> [u8; PROOF_LENGTH] { | ||
let mut ret = [0u8; PROOF_LENGTH]; | ||
ret[..32].copy_from_slice(&self.gamma.compress().to_bytes()[..]); | ||
ret[32..48].copy_from_slice(&self.c.to_bytes()[..16]); | ||
ret[48..].copy_from_slice(&self.s.to_bytes()[..]); | ||
ret | ||
} | ||
} | ||
|
||
impl TryFrom<&[u8]> for Proof { | ||
type Error = CryptoMaterialError; | ||
|
||
fn try_from(bytes: &[u8]) -> std::result::Result<Proof, CryptoMaterialError> { | ||
let mut c_buf = [0u8; 32]; | ||
c_buf[..16].copy_from_slice(&bytes[32..48]); | ||
let mut s_buf = [0u8; 32]; | ||
s_buf.copy_from_slice(&bytes[48..]); | ||
Ok(Proof { | ||
gamma: CompressedEdwardsY::from_slice(&bytes[..32]) | ||
.unwrap() | ||
.decompress() | ||
.unwrap(), | ||
c: ed25519_Scalar::from_bytes_mod_order(c_buf), | ||
s: ed25519_Scalar::from_bytes_mod_order(s_buf), | ||
}) | ||
} | ||
} | ||
|
||
/// The ECVRF output produced from the proof | ||
pub struct Output([u8; OUTPUT_LENGTH]); | ||
|
||
impl Output { | ||
/// Converts an Output into bytes | ||
#[inline] | ||
pub fn to_bytes(&self) -> [u8; OUTPUT_LENGTH] { | ||
self.0 | ||
} | ||
} | ||
|
||
impl<'a> From<&'a Proof> for Output { | ||
fn from(proof: &'a Proof) -> Output { | ||
let mut output = [0u8; OUTPUT_LENGTH]; | ||
output.copy_from_slice( | ||
&Sha512::new() | ||
.chain(&[SUITE, THREE]) | ||
.chain(&proof.gamma.mul_by_cofactor().compress().to_bytes()[..]) | ||
.finalize()[..], | ||
); | ||
Output(output) | ||
} | ||
} | ||
|
||
pub(super) fn nonce_generation_bytes(nonce: [u8; 32], h_point: EdwardsPoint) -> [u8; 64] { | ||
let mut k_buf = [0u8; 64]; | ||
k_buf.copy_from_slice( | ||
&Sha512::new() | ||
.chain(nonce) | ||
.chain(h_point.compress().as_bytes()) | ||
.finalize()[..], | ||
); | ||
k_buf | ||
} | ||
|
||
pub(super) fn hash_points(points: &[EdwardsPoint]) -> ed25519_Scalar { | ||
let mut result = [0u8; 32]; | ||
let mut hash = Sha512::new().chain(&[SUITE, TWO]); | ||
for point in points.iter() { | ||
hash = hash.chain(point.compress().to_bytes()); | ||
} | ||
result[..16].copy_from_slice(&hash.finalize()[..16]); | ||
ed25519_Scalar::from_bytes_mod_order(result) | ||
} |
Oops, something went wrong.