Minor fixups, adding more tests, making p256 experimental (#14)
This commit is contained in:
+1
-2
@@ -5,8 +5,7 @@
|
|||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
// of this source tree.
|
// of this source tree.
|
||||||
|
|
||||||
//! Defines the Group trait to specify the underlying prime order group used in
|
//! Defines the Group trait to specify the underlying prime order group
|
||||||
//! OPAQUE's OPRF
|
|
||||||
|
|
||||||
mod expand;
|
mod expand;
|
||||||
#[cfg(feature = "p256")]
|
#[cfg(feature = "p256")]
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||||
// of this source tree.
|
// of this source tree.
|
||||||
|
|
||||||
|
// Note: This group implementation of p256 is experimental for now,
|
||||||
|
// until hash-to-curve or crypto-bigint are fully supported.
|
||||||
|
|
||||||
#![allow(
|
#![allow(
|
||||||
clippy::borrow_interior_mutable_const,
|
clippy::borrow_interior_mutable_const,
|
||||||
clippy::declare_interior_mutable_const
|
clippy::declare_interior_mutable_const
|
||||||
@@ -166,6 +169,11 @@ impl Group for ProjectivePoint {
|
|||||||
|
|
||||||
/// Corresponds to the hash_to_curve_simple_swu() function defined in
|
/// Corresponds to the hash_to_curve_simple_swu() function defined in
|
||||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-F.2>
|
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-F.2>
|
||||||
|
///
|
||||||
|
/// `cmov`, `mod_floor` and `modpow` needs to be made constant-time, which
|
||||||
|
/// will be supported after crypto-bigint is no longer experimental. See
|
||||||
|
/// https://github.com/novifinancial/opaque-ke/issues/239 for more context.
|
||||||
|
|
||||||
#[allow(clippy::many_single_char_names)]
|
#[allow(clippy::many_single_char_names)]
|
||||||
fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
|
fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
|
||||||
u: &[u8],
|
u: &[u8],
|
||||||
|
|||||||
+2
-1
@@ -459,7 +459,8 @@
|
|||||||
//!
|
//!
|
||||||
//! # Features
|
//! # Features
|
||||||
//!
|
//!
|
||||||
//! - The `p256` feature enables using p256 as the underlying group for the [Ciphersuite] choice
|
//! - The `p256` feature enables using p256 as the underlying group for the [Ciphersuite] choice.
|
||||||
|
//! Note that this is currently an experimental feature ⚠️, and is not yet ready for production use.
|
||||||
//!
|
//!
|
||||||
//! - The `serialize` feature, enabled by default, provides convenience functions for serializing and deserializing with
|
//! - The `serialize` feature, enabled by default, provides convenience functions for serializing and deserializing with
|
||||||
//! [serde](https://serde.rs/).
|
//! [serde](https://serde.rs/).
|
||||||
|
|||||||
+151
-29
@@ -35,6 +35,7 @@ static STR_VOPRF: &[u8] = b"VOPRF07-";
|
|||||||
|
|
||||||
/// Determines the mode of operation (either base mode or
|
/// Determines the mode of operation (either base mode or
|
||||||
/// verifiable mode)
|
/// verifiable mode)
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
Base = 0,
|
Base = 0,
|
||||||
Verifiable = 1,
|
Verifiable = 1,
|
||||||
@@ -778,50 +779,38 @@ fn get_context_string<CS: CipherSuite>(mode: Mode) -> Result<alloc::vec::Vec<u8>
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::group::Group;
|
use crate::group::Group;
|
||||||
use generic_array::{arr, GenericArray};
|
use generic_array::GenericArray;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
fn prf<CS: CipherSuite>(
|
fn prf<CS: CipherSuite>(
|
||||||
input: &[u8],
|
input: &[u8],
|
||||||
oprf_key: &[u8],
|
key: <CS::Group as Group>::Scalar,
|
||||||
info: &[u8],
|
info: &[u8],
|
||||||
|
mode: Mode,
|
||||||
) -> GenericArray<u8, <CS::Hash as Digest>::OutputSize> {
|
) -> GenericArray<u8, <CS::Hash as Digest>::OutputSize> {
|
||||||
let dst = [
|
let dst = [STR_HASH_TO_GROUP, &get_context_string::<CS>(mode).unwrap()].concat();
|
||||||
STR_HASH_TO_GROUP,
|
|
||||||
&get_context_string::<CS>(Mode::Base).unwrap(),
|
|
||||||
]
|
|
||||||
.concat();
|
|
||||||
let point = CS::Group::hash_to_curve::<CS::Hash>(input, &dst).unwrap();
|
let point = CS::Group::hash_to_curve::<CS::Hash>(input, &dst).unwrap();
|
||||||
let scalar = CS::Group::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
|
|
||||||
|
|
||||||
let context = [
|
let context = [
|
||||||
STR_CONTEXT,
|
STR_CONTEXT,
|
||||||
&get_context_string::<CS>(Mode::Base).unwrap(),
|
&get_context_string::<CS>(mode).unwrap(),
|
||||||
&serialize(info, 2).unwrap(),
|
&serialize(info, 2).unwrap(),
|
||||||
]
|
]
|
||||||
.concat();
|
.concat();
|
||||||
let dst = [
|
let dst = [STR_HASH_TO_SCALAR, &get_context_string::<CS>(mode).unwrap()].concat();
|
||||||
STR_HASH_TO_SCALAR,
|
|
||||||
&get_context_string::<CS>(Mode::Base).unwrap(),
|
|
||||||
]
|
|
||||||
.concat();
|
|
||||||
let m = <CS::Group as Group>::hash_to_scalar::<CS::Hash>(&context, &dst).unwrap();
|
let m = <CS::Group as Group>::hash_to_scalar::<CS::Hash>(&context, &dst).unwrap();
|
||||||
|
|
||||||
let res = point * &<CS::Group as Group>::scalar_invert(&(scalar + &m));
|
let res = point * &<CS::Group as Group>::scalar_invert(&(key + &m));
|
||||||
|
|
||||||
finalize_after_unblind::<CS>(&[(input.to_vec(), res)], info, Mode::Base).unwrap()[0].clone()
|
finalize_after_unblind::<CS>(&[(input.to_vec(), res)], info, mode).unwrap()[0].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn oprf_retrieval<CS: CipherSuite>() {
|
fn base_retrieval<CS: CipherSuite>() {
|
||||||
let input = b"hunter2";
|
let input = b"input";
|
||||||
let info = b"info";
|
let info = b"info";
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let client_blind_result = NonVerifiableClient::<CS>::blind(&input[..], &mut rng).unwrap();
|
let client_blind_result = NonVerifiableClient::<CS>::blind(&input[..], &mut rng).unwrap();
|
||||||
let oprf_key_bytes = arr![
|
let server = NonVerifiableServer::<CS>::new(&mut rng).unwrap();
|
||||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
|
||||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
|
||||||
];
|
|
||||||
let server = NonVerifiableServer::<CS>::new_with_key(&oprf_key_bytes).unwrap();
|
|
||||||
let server_result = server
|
let server_result = server
|
||||||
.evaluate(client_blind_result.message, &Metadata(info.to_vec()))
|
.evaluate(client_blind_result.message, &Metadata(info.to_vec()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -829,11 +818,136 @@ mod tests {
|
|||||||
.state
|
.state
|
||||||
.finalize(server_result.message, &Metadata(info.to_vec()))
|
.finalize(server_result.message, &Metadata(info.to_vec()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let res2 = prf::<CS>(&input[..], &oprf_key_bytes, info);
|
let res2 = prf::<CS>(&input[..], server.get_private_key(), info, Mode::Base);
|
||||||
assert_eq!(client_finalize_result.output, res2);
|
assert_eq!(client_finalize_result.output, res2);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn oprf_inversion_unsalted<CS: CipherSuite>() {
|
fn verifiable_retrieval<CS: CipherSuite>() {
|
||||||
|
let input = b"input";
|
||||||
|
let info = b"info";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let client_blind_result = VerifiableClient::<CS>::blind(&input[..], &mut rng).unwrap();
|
||||||
|
let server = VerifiableServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let server_result = server
|
||||||
|
.evaluate(
|
||||||
|
&mut rng,
|
||||||
|
client_blind_result.message,
|
||||||
|
&Metadata(info.to_vec()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let client_finalize_result = client_blind_result
|
||||||
|
.state
|
||||||
|
.finalize(
|
||||||
|
server_result.message,
|
||||||
|
server_result.proof,
|
||||||
|
server.get_public_key(),
|
||||||
|
&Metadata(info.to_vec()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let res2 = prf::<CS>(&input[..], server.get_private_key(), info, Mode::Verifiable);
|
||||||
|
assert_eq!(client_finalize_result.output, res2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verifiable_bad_public_key<CS: CipherSuite>() {
|
||||||
|
let input = b"input";
|
||||||
|
let info = b"info";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let client_blind_result = VerifiableClient::<CS>::blind(&input[..], &mut rng).unwrap();
|
||||||
|
let server = VerifiableServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let server_result = server
|
||||||
|
.evaluate(
|
||||||
|
&mut rng,
|
||||||
|
client_blind_result.message,
|
||||||
|
&Metadata(info.to_vec()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let wrong_pk = {
|
||||||
|
// Choose a group element that is unlikely to be the right public key
|
||||||
|
CS::Group::hash_to_curve::<CS::Hash>(b"msg", b"dst").unwrap()
|
||||||
|
};
|
||||||
|
let client_finalize_result = client_blind_result.state.finalize(
|
||||||
|
server_result.message,
|
||||||
|
server_result.proof,
|
||||||
|
wrong_pk,
|
||||||
|
&Metadata(info.to_vec()),
|
||||||
|
);
|
||||||
|
assert!(client_finalize_result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verifiable_batch_retrieval<CS: CipherSuite>() {
|
||||||
|
let info = b"info";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let mut inputs = vec![];
|
||||||
|
let mut client_states = vec![];
|
||||||
|
let mut client_messages = vec![];
|
||||||
|
let num_iterations = 10;
|
||||||
|
for _ in 0..num_iterations {
|
||||||
|
let mut input = vec![0u8; 32];
|
||||||
|
rng.fill_bytes(&mut input);
|
||||||
|
let client_blind_result = VerifiableClient::<CS>::blind(&input[..], &mut rng).unwrap();
|
||||||
|
inputs.push(input);
|
||||||
|
client_states.push(client_blind_result.state);
|
||||||
|
client_messages.push(client_blind_result.message);
|
||||||
|
}
|
||||||
|
let server = VerifiableServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let server_result = server
|
||||||
|
.batch_evaluate(&mut rng, &client_messages, &Metadata(info.to_vec()))
|
||||||
|
.unwrap();
|
||||||
|
let batch_finalize_input = BatchFinalizeInput::new(client_states, server_result.messages);
|
||||||
|
let client_finalize_result = VerifiableClient::batch_finalize(
|
||||||
|
batch_finalize_input,
|
||||||
|
server_result.proof,
|
||||||
|
server.get_public_key(),
|
||||||
|
&Metadata(info.to_vec()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut res2 = vec![];
|
||||||
|
for i in 0..num_iterations {
|
||||||
|
let output = prf::<CS>(
|
||||||
|
&inputs[i][..],
|
||||||
|
server.get_private_key(),
|
||||||
|
info,
|
||||||
|
Mode::Verifiable,
|
||||||
|
);
|
||||||
|
res2.push(output);
|
||||||
|
}
|
||||||
|
assert_eq!(client_finalize_result.outputs, res2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verifiable_batch_bad_public_key<CS: CipherSuite>() {
|
||||||
|
let info = b"info";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let mut inputs = vec![];
|
||||||
|
let mut client_states = vec![];
|
||||||
|
let mut client_messages = vec![];
|
||||||
|
let num_iterations = 10;
|
||||||
|
for _ in 0..num_iterations {
|
||||||
|
let mut input = vec![0u8; 32];
|
||||||
|
rng.fill_bytes(&mut input);
|
||||||
|
let client_blind_result = VerifiableClient::<CS>::blind(&input[..], &mut rng).unwrap();
|
||||||
|
inputs.push(input);
|
||||||
|
client_states.push(client_blind_result.state);
|
||||||
|
client_messages.push(client_blind_result.message);
|
||||||
|
}
|
||||||
|
let server = VerifiableServer::<CS>::new(&mut rng).unwrap();
|
||||||
|
let server_result = server
|
||||||
|
.batch_evaluate(&mut rng, &client_messages, &Metadata(info.to_vec()))
|
||||||
|
.unwrap();
|
||||||
|
let batch_finalize_input = BatchFinalizeInput::new(client_states, server_result.messages);
|
||||||
|
let wrong_pk = {
|
||||||
|
// Choose a group element that is unlikely to be the right public key
|
||||||
|
CS::Group::hash_to_curve::<CS::Hash>(b"msg", b"dst").unwrap()
|
||||||
|
};
|
||||||
|
let client_finalize_result = VerifiableClient::batch_finalize(
|
||||||
|
batch_finalize_input,
|
||||||
|
server_result.proof,
|
||||||
|
wrong_pk,
|
||||||
|
&Metadata(info.to_vec()),
|
||||||
|
);
|
||||||
|
assert!(client_finalize_result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_inversion_unsalted<CS: CipherSuite>() {
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let mut input = alloc::vec![0u8; 64];
|
let mut input = alloc::vec![0u8; 64];
|
||||||
rng.fill_bytes(&mut input);
|
rng.fill_bytes(&mut input);
|
||||||
@@ -866,15 +980,23 @@ mod tests {
|
|||||||
fn test_functionality() -> Result<(), InternalError> {
|
fn test_functionality() -> Result<(), InternalError> {
|
||||||
use crate::tests::Ristretto255Sha512;
|
use crate::tests::Ristretto255Sha512;
|
||||||
|
|
||||||
oprf_retrieval::<Ristretto255Sha512>();
|
base_retrieval::<Ristretto255Sha512>();
|
||||||
oprf_inversion_unsalted::<Ristretto255Sha512>();
|
base_inversion_unsalted::<Ristretto255Sha512>();
|
||||||
|
verifiable_retrieval::<Ristretto255Sha512>();
|
||||||
|
verifiable_batch_retrieval::<Ristretto255Sha512>();
|
||||||
|
verifiable_bad_public_key::<Ristretto255Sha512>();
|
||||||
|
verifiable_batch_bad_public_key::<Ristretto255Sha512>();
|
||||||
|
|
||||||
#[cfg(feature = "p256")]
|
#[cfg(feature = "p256")]
|
||||||
{
|
{
|
||||||
use crate::tests::P256Sha256;
|
use crate::tests::P256Sha256;
|
||||||
|
|
||||||
oprf_retrieval::<P256Sha256>();
|
base_retrieval::<P256Sha256>();
|
||||||
oprf_inversion_unsalted::<P256Sha256>();
|
base_inversion_unsalted::<P256Sha256>();
|
||||||
|
verifiable_retrieval::<P256Sha256>();
|
||||||
|
verifiable_batch_retrieval::<P256Sha256>();
|
||||||
|
verifiable_bad_public_key::<P256Sha256>();
|
||||||
|
verifiable_batch_bad_public_key::<P256Sha256>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user