Initial implementation (#1)

This commit is contained in:
Kevin Lewi
2021-09-09 01:56:54 -07:00
committed by GitHub
parent 743692882d
commit bb4b9d0909
23 changed files with 2093 additions and 1 deletions
+64
View File
@@ -0,0 +1,64 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use alloc::vec::Vec;
use core::cmp::min;
use rand::{CryptoRng, Error, RngCore};
/// A simple implementation of `RngCore` for testing purposes.
///
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
///
///
#[derive(Debug, Clone)]
pub struct CycleRng {
v: Vec<u8>,
}
impl CycleRng {
/// Create a `CycleRng`, yielding a sequence starting with
/// `initial` and looping thereafter
pub fn new(initial: Vec<u8>) -> Self {
CycleRng { v: initial }
}
}
fn rotate_left<T>(data: &mut [T], steps: usize) {
if data.is_empty() {
return;
}
let steps = steps % data.len();
data[..steps].reverse();
data[steps..].reverse();
data.reverse();
}
impl RngCore for CycleRng {
fn next_u32(&mut self) -> u32 {
unimplemented!()
}
#[inline]
fn next_u64(&mut self) -> u64 {
unimplemented!()
}
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let len = min(self.v.len(), dest.len());
(&mut dest[..len]).copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len);
}
#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.fill_bytes(dest);
Ok(())
}
}
// This is meant for testing only
impl CryptoRng for CycleRng {}
+7
View File
@@ -0,0 +1,7 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
mod mock_rng;
mod voprf_test_vectors;
+176
View File
@@ -0,0 +1,176 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::ciphersuite::CipherSuite;
use crate::errors::*;
use crate::group::Group;
use crate::tests::mock_rng::CycleRng;
use crate::voprf::{Client, Server};
use alloc::string::ToString;
use alloc::vec::Vec;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::GenericArray;
use serde_json::Value;
use sha2::Sha512;
struct VOPRFTestVectorParameters {
sksm: Vec<u8>,
input: Vec<u8>,
blind: Vec<u8>,
blinded_element: Vec<u8>,
evaluation_element: Vec<u8>,
output: Vec<u8>,
}
// Taken from https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
// in base mode
static OPRF_RISTRETTO255_SHA512: &[&str] = &[
r#"
{
"sksm": "caeff69352df4905a9121a4997704ca8cee1524a110819eb87deba1a39ec1701",
"input": "00",
"blind": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
"blinded_element": "fc20e03aff3a9de9b37e8d35886ade11ec7d85c2a1fb5bb0b1686c64e07ac467",
"evaluation_element": "7c72cc293cd7d44c0b57c273f27befd598b132edc665694bdc9c42a4d3083c0a",
"output": "e3a209dce2d3ea3d84fcddb282818caebb756a341e08a310d9904314f5392085d13c3f76339d745db0f46974a6049c3ea9546305af55d37760b2136d9b3f0134"
}
"#,
r#"
{
"sksm": "caeff69352df4905a9121a4997704ca8cee1524a110819eb87deba1a39ec1701",
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
"blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
"blinded_element": "483d4f39de5ff77fa0f9a0ad2334dd5bf87f2cda868539d21de67ce49e7d1536",
"evaluation_element": "026f2758fc62f02a7ff95f35ec6f20186aa57c0274361655543ea235d7b2aa34",
"output": "2c17dc3e9398dadb44bb2d3360c446302e99f1fe0ec40f0b1ad25c9cf002be1e4b41b4900ef056537fe8c14532ccea4d796f5feab9541af48057d83c0db86fe9"
}
"#,
];
#[cfg(feature = "p256")]
static OPRF_P256_SHA256: &[&str] = &[
r#"
{
"sksm": "a1b2355828f2c76de6749af9d093bd9fe0f2cada3ec653cd9a6d3126a7a7827b",
"input": "00",
"blind": "5d9e7f6efd3093c32ecceabd57fb03cf760c926d2a7bfa265babf29ec98af0d0",
"blinded_element": "03e3c379698da853d9844098fa0ac676970d5ec24167b598714cd2ee188604ddd2",
"evaluation_element": "03ea54e8d095332d1a601a3f8a5013188aea036bf9b563236f7fd3b046908b42fd",
"output": "464e3e51e4086a824d9a2f939524d7069ae4072a788bc9d5daa0762b25826437"
}
"#,
r#"
{
"sksm": "a1b2355828f2c76de6749af9d093bd9fe0f2cada3ec653cd9a6d3126a7a7827b",
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
"blind": "825155ab61f17605af2ae2e935c78d857c9407bcd45128d57d338f1671b5fcbe",
"blinded_element": "030b40be181ffbb3c3ae4a4911287c43261f5e4034781def69c51608f372a02102",
"evaluation_element": "03115ad70ea55dbb4006da0ee3589a3582f31ef9cd143996d1e31a25ad3abdcf6f",
"output": "b597d58c843d0f9d2712121b0a3e2912ebee1c829eed3089eade9af4359ab275"
}
"#,
];
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters {
sksm: decode(&values, "sksm").unwrap(),
input: decode(&values, "input").unwrap(),
blind: decode(&values, "blind").unwrap(),
blinded_element: decode(&values, "blinded_element").unwrap(),
evaluation_element: decode(&values, "evaluation_element").unwrap(),
output: decode(&values, "output").unwrap(),
}
}
struct Ristretto255Sha512;
impl CipherSuite for Ristretto255Sha512 {
type Group = RistrettoPoint;
type Hash = Sha512;
}
#[test]
fn tests() -> Result<(), InternalError> {
test_blind::<Ristretto255Sha512>(OPRF_RISTRETTO255_SHA512)?;
test_evaluate::<Ristretto255Sha512>(OPRF_RISTRETTO255_SHA512)?;
test_finalize::<Ristretto255Sha512>(OPRF_RISTRETTO255_SHA512)?;
#[cfg(feature = "p256")]
{
use p256_::ProjectivePoint;
use sha2::Sha256;
test_blind::<ProjectivePoint, Sha256>(OPRF_P256_SHA256)?;
test_evaluate::<ProjectivePoint>(OPRF_P256_SHA256)?;
test_finalize::<ProjectivePoint, Sha256>(OPRF_P256_SHA256)?;
}
Ok(())
}
// Tests input -> blind, blinded_element
fn test_blind<CS: CipherSuite>(tvs: &[&str]) -> Result<(), InternalError> {
for tv in tvs {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let mut rng = CycleRng::new(parameters.blind.to_vec());
let (client, blinded_element) = Client::<CS>::blind(&parameters.input, &mut rng)?;
assert_eq!(
&parameters.blind,
&CS::Group::scalar_as_bytes(client.get_blind()).to_vec()
);
assert_eq!(
&parameters.blinded_element,
&blinded_element.to_arr().to_vec()
);
}
Ok(())
}
// Tests sksm, blinded_element -> evaluation_element
fn test_evaluate<CS: CipherSuite>(tvs: &[&str]) -> Result<(), InternalError> {
for tv in tvs {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let server = Server::<CS>::new_with_key(&parameters.sksm).unwrap();
let evaluation_element = server.evaluate(
CS::Group::from_element_slice(GenericArray::from_slice(&parameters.blinded_element))
.unwrap(),
);
assert_eq!(
&parameters.evaluation_element,
&evaluation_element.to_arr().to_vec()
);
}
Ok(())
}
// Tests input, blind, evaluation_element -> output
fn test_finalize<CS: CipherSuite>(tvs: &[&str]) -> Result<(), InternalError> {
for tv in tvs {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let client = Client::<CS>::from_data_and_blind(
&parameters.input,
&<CS::Group as Group>::from_scalar_slice(&GenericArray::clone_from_slice(
&parameters.blind,
))
.unwrap(),
);
let output = client.finalize(<CS::Group as Group>::from_element_slice(
GenericArray::from_slice(&parameters.evaluation_element),
)?)?;
assert_eq!(&parameters.output, &output.to_vec());
}
Ok(())
}