diff --git a/src/group/mod.rs b/src/group/mod.rs index 360ec5e..6a8a810 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -5,8 +5,7 @@ // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // of this source tree. -//! Defines the Group trait to specify the underlying prime order group used in -//! OPAQUE's OPRF +//! Defines the Group trait to specify the underlying prime order group mod expand; #[cfg(feature = "p256")] diff --git a/src/group/p256.rs b/src/group/p256.rs index 8c1d7ba..eb78f03 100644 --- a/src/group/p256.rs +++ b/src/group/p256.rs @@ -5,6 +5,9 @@ // License, Version 2.0 found in the LICENSE-APACHE file in the root directory // 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( clippy::borrow_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 /// +/// +/// `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)] fn hash_to_curve_simple_swu>( u: &[u8], diff --git a/src/lib.rs b/src/lib.rs index dedb75a..1d01cbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -459,7 +459,8 @@ //! //! # 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 //! [serde](https://serde.rs/). diff --git a/src/voprf.rs b/src/voprf.rs index 546fb28..d73e897 100644 --- a/src/voprf.rs +++ b/src/voprf.rs @@ -35,6 +35,7 @@ static STR_VOPRF: &[u8] = b"VOPRF07-"; /// Determines the mode of operation (either base mode or /// verifiable mode) +#[derive(Clone, Copy)] enum Mode { Base = 0, Verifiable = 1, @@ -778,50 +779,38 @@ fn get_context_string(mode: Mode) -> Result mod tests { use super::*; use crate::group::Group; - use generic_array::{arr, GenericArray}; + use generic_array::GenericArray; use rand::rngs::OsRng; fn prf( input: &[u8], - oprf_key: &[u8], + key: ::Scalar, info: &[u8], + mode: Mode, ) -> GenericArray::OutputSize> { - let dst = [ - STR_HASH_TO_GROUP, - &get_context_string::(Mode::Base).unwrap(), - ] - .concat(); + let dst = [STR_HASH_TO_GROUP, &get_context_string::(mode).unwrap()].concat(); let point = CS::Group::hash_to_curve::(input, &dst).unwrap(); - let scalar = CS::Group::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap(); let context = [ STR_CONTEXT, - &get_context_string::(Mode::Base).unwrap(), + &get_context_string::(mode).unwrap(), &serialize(info, 2).unwrap(), ] .concat(); - let dst = [ - STR_HASH_TO_SCALAR, - &get_context_string::(Mode::Base).unwrap(), - ] - .concat(); + let dst = [STR_HASH_TO_SCALAR, &get_context_string::(mode).unwrap()].concat(); let m = ::hash_to_scalar::(&context, &dst).unwrap(); - let res = point * &::scalar_invert(&(scalar + &m)); + let res = point * &::scalar_invert(&(key + &m)); - finalize_after_unblind::(&[(input.to_vec(), res)], info, Mode::Base).unwrap()[0].clone() + finalize_after_unblind::(&[(input.to_vec(), res)], info, mode).unwrap()[0].clone() } - fn oprf_retrieval() { - let input = b"hunter2"; + fn base_retrieval() { + let input = b"input"; let info = b"info"; let mut rng = OsRng; let client_blind_result = NonVerifiableClient::::blind(&input[..], &mut rng).unwrap(); - let oprf_key_bytes = arr![ - 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::::new_with_key(&oprf_key_bytes).unwrap(); + let server = NonVerifiableServer::::new(&mut rng).unwrap(); let server_result = server .evaluate(client_blind_result.message, &Metadata(info.to_vec())) .unwrap(); @@ -829,11 +818,136 @@ mod tests { .state .finalize(server_result.message, &Metadata(info.to_vec())) .unwrap(); - let res2 = prf::(&input[..], &oprf_key_bytes, info); + let res2 = prf::(&input[..], server.get_private_key(), info, Mode::Base); assert_eq!(client_finalize_result.output, res2); } - fn oprf_inversion_unsalted() { + fn verifiable_retrieval() { + let input = b"input"; + let info = b"info"; + let mut rng = OsRng; + let client_blind_result = VerifiableClient::::blind(&input[..], &mut rng).unwrap(); + let server = VerifiableServer::::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::(&input[..], server.get_private_key(), info, Mode::Verifiable); + assert_eq!(client_finalize_result.output, res2); + } + + fn verifiable_bad_public_key() { + let input = b"input"; + let info = b"info"; + let mut rng = OsRng; + let client_blind_result = VerifiableClient::::blind(&input[..], &mut rng).unwrap(); + let server = VerifiableServer::::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::(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() { + 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::::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::::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::( + &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() { + 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::::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::::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::(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() { let mut rng = OsRng; let mut input = alloc::vec![0u8; 64]; rng.fill_bytes(&mut input); @@ -866,15 +980,23 @@ mod tests { fn test_functionality() -> Result<(), InternalError> { use crate::tests::Ristretto255Sha512; - oprf_retrieval::(); - oprf_inversion_unsalted::(); + base_retrieval::(); + base_inversion_unsalted::(); + verifiable_retrieval::(); + verifiable_batch_retrieval::(); + verifiable_bad_public_key::(); + verifiable_batch_bad_public_key::(); #[cfg(feature = "p256")] { use crate::tests::P256Sha256; - oprf_retrieval::(); - oprf_inversion_unsalted::(); + base_retrieval::(); + base_inversion_unsalted::(); + verifiable_retrieval::(); + verifiable_batch_retrieval::(); + verifiable_bad_public_key::(); + verifiable_batch_bad_public_key::(); } Ok(())