P256 implementation (#213)
* Initial P256 implementation * Add P256 VOPRF test vectors * Fix implementation * Forgot `Cargo.toml` and CI * Fix MSRV * Fix MSRV tests * Remove accidental leftovers * Document constants * Use included constant time checks * Add P-256 VOPRF test vector checks * Remove unnecessary bound on `CipherSuite::Group` * P-256 improvement and fixes * Add OPAQUE test vectors for P-256 * Remove fixed key size for 3DH * Hide P-256 test vectors behind feature flag * Split `Group` into modules * Fix `hash_to_scalar` * Point to source * Move constants to `once_cell` Co-authored-by: Kevin Lewi <[email protected]>
This commit is contained in:
@@ -15,9 +15,13 @@ jobs:
|
||||
backend_feature:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
- p256,u64_backend
|
||||
toolchain:
|
||||
- stable
|
||||
- 1.51.0
|
||||
exclude:
|
||||
- backend_feature: p256,u64_backend
|
||||
toolchain: 1.51.0
|
||||
name: test
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
@@ -49,6 +53,7 @@ jobs:
|
||||
backend_feature:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
- p256,u64_backend
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
@@ -67,6 +72,7 @@ jobs:
|
||||
backend_feature:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
- p256,u64_backend
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
@@ -81,6 +87,7 @@ jobs:
|
||||
backend_feature:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
- p256,u64_backend
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
|
||||
@@ -12,6 +12,7 @@ readme = "README.md"
|
||||
[features]
|
||||
default = ["u64_backend", "serialize"]
|
||||
slow-hash = ["argon2"]
|
||||
p256 = ["num-bigint", "num-integer", "num-traits", "once_cell", "p256_"]
|
||||
bench = []
|
||||
u64_backend = ["curve25519-dalek/u64_backend"]
|
||||
u32_backend = ["curve25519-dalek/u32_backend"]
|
||||
@@ -28,6 +29,11 @@ generic-array = "0.14"
|
||||
generic-bytes = { version = "0.1" }
|
||||
hkdf = "0.11"
|
||||
hmac = "0.11"
|
||||
num-bigint = { version = "0.4", optional = true }
|
||||
num-integer = { version = "0.1", optional = true }
|
||||
num-traits = { version = "0.2", optional = true }
|
||||
once_cell = { version = "1", optional = true }
|
||||
p256_ = { package = "p256", version = "0.9", optional = true }
|
||||
rand = "0.8"
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
subtle = { version = "2.3", default-features = false }
|
||||
|
||||
+1
-2
@@ -9,7 +9,6 @@ use crate::{
|
||||
hash::Hash, key_exchange::traits::KeyExchange, map_to_curve::GroupWithMapToCurve,
|
||||
slow_hash::SlowHash,
|
||||
};
|
||||
use digest::Digest;
|
||||
|
||||
/// Configures the underlying primitives used in OPAQUE
|
||||
/// * `Group`: a finite cyclic group along with a point representation, along
|
||||
@@ -24,7 +23,7 @@ pub trait CipherSuite {
|
||||
/// an extension trait PasswordToCurve that allows some customization on
|
||||
/// how to hash a password to a curve point. See `group::Group` and
|
||||
/// `map_to_curve::GroupWithMapToCurve`.
|
||||
type Group: GroupWithMapToCurve<UniformBytesLen = <Self::Hash as Digest>::OutputSize>;
|
||||
type Group: GroupWithMapToCurve;
|
||||
/// A key exchange protocol
|
||||
type KeyExchange: KeyExchange<Self::Hash, Self::Group>;
|
||||
/// The main hash function use (for HKDF computations and hashing transcripts)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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.
|
||||
|
||||
//! Defines the Group trait to specify the underlying prime order group used in
|
||||
//! OPAQUE's OPRF
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
pub(crate) mod p256;
|
||||
mod ristretto;
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::ops::Mul;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
|
||||
/// subgroup is noted additively — as in the draft RFC — in this trait.
|
||||
pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
|
||||
/// The type of base field scalars
|
||||
type Scalar: Zeroize + Copy;
|
||||
/// The byte length necessary to represent scalars
|
||||
type ScalarLen: ArrayLength<u8> + 'static;
|
||||
/// Return a scalar from its fixed-length bytes representation
|
||||
fn from_scalar_slice(
|
||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
||||
) -> Result<Self::Scalar, InternalPakeError>;
|
||||
/// picks a scalar at random
|
||||
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
|
||||
/// Serializes a scalar to bytes
|
||||
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
|
||||
/// The multiplicative inverse of this scalar
|
||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
|
||||
|
||||
/// The byte length necessary to represent group elements
|
||||
type ElemLen: ArrayLength<u8> + 'static;
|
||||
/// Return an element from its fixed-length bytes representation
|
||||
fn from_element_slice(
|
||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
||||
) -> Result<Self, InternalPakeError>;
|
||||
/// Serializes the `self` group element
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;
|
||||
|
||||
/// Hashes points presumed to be uniformly random to the curve. The
|
||||
/// impl is allowed to perform additional hashes if it needs to, but this
|
||||
/// may not be necessary as this function is going to be called with the
|
||||
/// output of a kdf.
|
||||
type UniformBytesLen: ArrayLength<u8>;
|
||||
|
||||
/// Hashes a slice of pseudo-random bytes of the correct length to a curve point
|
||||
fn hash_to_curve(
|
||||
uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>,
|
||||
) -> Result<Self, InternalPakeError>;
|
||||
|
||||
/// Get the base point for the group
|
||||
fn base_point() -> Self;
|
||||
|
||||
/// Multiply the point by a scalar, represented as a slice
|
||||
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
|
||||
|
||||
/// Returns if the group element is equal to the identity (1)
|
||||
fn is_identity(&self) -> bool;
|
||||
|
||||
/// Compares in constant time if the group elements are equal
|
||||
fn ct_equal(&self, other: &Self) -> bool;
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
// 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.
|
||||
|
||||
#![allow(
|
||||
clippy::borrow_interior_mutable_const,
|
||||
clippy::declare_interior_mutable_const
|
||||
)]
|
||||
|
||||
use std::ops::Mul;
|
||||
use std::str::FromStr;
|
||||
|
||||
use generic_array::typenum::{U32, U33, U96};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use num_integer::Integer;
|
||||
use num_traits::{One, ToPrimitive};
|
||||
use once_cell::sync::Lazy;
|
||||
use p256_::elliptic_curve::group::prime::PrimeCurveAffine;
|
||||
use p256_::elliptic_curve::group::GroupEncoding;
|
||||
use p256_::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
||||
use p256_::elliptic_curve::subtle::ConstantTimeEq;
|
||||
use p256_::elliptic_curve::Field;
|
||||
use p256_::{AffinePoint, EncodedPoint, ProjectivePoint};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::ops::{Add, Div, Neg, Sub};
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
|
||||
use super::Group;
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
|
||||
// `p: 2^256 - 2^224 + 2^192 + 2^96 - 1`
|
||||
const P: Lazy<BigInt> = Lazy::new(|| {
|
||||
BigInt::from_str(
|
||||
"115792089210356248762697446949407573530086143415290314195533631308867097853951",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
// `A: -3`
|
||||
const A: Lazy<BigInt> = Lazy::new(|| BigInt::from(-3));
|
||||
// `B: 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b`
|
||||
const B: Lazy<BigInt> = Lazy::new(|| {
|
||||
BigInt::parse_bytes(
|
||||
b"5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
|
||||
16,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
// `L: 48`
|
||||
pub const L: usize = 48;
|
||||
// `Z: -10`
|
||||
const Z: Lazy<BigInt> = Lazy::new(|| BigInt::from(-10));
|
||||
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#[{%22num%22:211,%22gen%22:0},{%22name%22:%22XYZ%22},70,700,0]
|
||||
// P-256 `n` is defined as `115792089210356248762697446949407573529996955224135760342 422259061068512044369`
|
||||
pub const R: Lazy<BigInt> = Lazy::new(|| {
|
||||
BigInt::from_str(
|
||||
"115792089210356248762697446949407573529996955224135760342422259061068512044369",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
impl Group for ProjectivePoint {
|
||||
type ElemLen = U33;
|
||||
type Scalar = p256_::Scalar;
|
||||
type ScalarLen = U32;
|
||||
type UniformBytesLen = U96;
|
||||
|
||||
fn from_scalar_slice(
|
||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
||||
) -> Result<Self::Scalar, InternalPakeError> {
|
||||
Ok(Self::Scalar::from_bytes_reduced(scalar_bits))
|
||||
}
|
||||
|
||||
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||
Self::Scalar::random(rng)
|
||||
}
|
||||
|
||||
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
|
||||
scalar.into()
|
||||
}
|
||||
|
||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
|
||||
scalar.invert().unwrap_or(Self::Scalar::zero())
|
||||
}
|
||||
|
||||
fn from_element_slice(
|
||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
||||
) -> Result<Self, InternalPakeError> {
|
||||
Option::from(Self::from_bytes(element_bits)).ok_or(InternalPakeError::PointError)
|
||||
}
|
||||
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
|
||||
let mut bytes = self.to_affine().to_encoded_point(true).as_bytes().to_vec();
|
||||
bytes.resize(33, 0);
|
||||
GenericArray::clone_from_slice(&bytes)
|
||||
}
|
||||
|
||||
fn hash_to_curve(
|
||||
uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>,
|
||||
) -> Result<Self, InternalPakeError> {
|
||||
// extract points
|
||||
let u0 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[0..L]);
|
||||
let u1 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[L..L * 2]);
|
||||
|
||||
// map to curve
|
||||
let (q0x, q0y) = map_to_curve_simple_swu(&u0, &A, &B, &P, &Z);
|
||||
let (q1x, q1y) = map_to_curve_simple_swu(&u1, &A, &B, &P, &Z);
|
||||
|
||||
// convert to `p256` types
|
||||
let p0 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
|
||||
&q0x, &q0y, false,
|
||||
))
|
||||
.ok_or(InternalPakeError::PointError)?
|
||||
.to_curve();
|
||||
let p1 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
|
||||
&q1x, &q1y, false,
|
||||
))
|
||||
.ok_or(InternalPakeError::PointError)?;
|
||||
|
||||
Ok(p0 + p1)
|
||||
}
|
||||
|
||||
fn base_point() -> Self {
|
||||
Self::generator()
|
||||
}
|
||||
|
||||
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self {
|
||||
self * &Self::Scalar::from_bytes_reduced(scalar)
|
||||
}
|
||||
|
||||
fn is_identity(&self) -> bool {
|
||||
self == &Self::identity()
|
||||
}
|
||||
fn ct_equal(&self, other: &Self) -> bool {
|
||||
self.ct_eq(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the map_to_curve_simple_swu() function defined in
|
||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-F.2>
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn map_to_curve_simple_swu<N: ArrayLength<u8>>(
|
||||
u: &BigInt,
|
||||
a: &BigInt,
|
||||
b: &BigInt,
|
||||
p: &BigInt,
|
||||
z: &BigInt,
|
||||
) -> (GenericArray<u8, N>, GenericArray<u8, N>) {
|
||||
#[derive(Clone)]
|
||||
struct Field<'a>(&'a BigInt);
|
||||
|
||||
impl<'a> Field<'a> {
|
||||
fn new(p: &'a BigInt) -> Self {
|
||||
Self(p)
|
||||
}
|
||||
|
||||
fn element(&'a self, number: &BigInt) -> FieldElement<'a> {
|
||||
FieldElement {
|
||||
number: number.mod_floor(self.0),
|
||||
f: self,
|
||||
}
|
||||
}
|
||||
|
||||
fn one(&'a self) -> FieldElement<'a> {
|
||||
self.element(&BigInt::one())
|
||||
}
|
||||
|
||||
/// See <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
|
||||
fn inv0(&'a self, number: &FieldElement<'a>) -> FieldElement<'a> {
|
||||
number.pow_internal(&(self.0 - 2))
|
||||
}
|
||||
}
|
||||
|
||||
/// Finite field arithmetic
|
||||
#[derive(Clone)]
|
||||
struct FieldElement<'a> {
|
||||
number: BigInt,
|
||||
f: &'a Field<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Add for FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
&self + &rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add for &FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
self.f.element(&(&self.number + &rhs.number))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sub for &FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
self.f.element(&(&self.number - &rhs.number))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Neg for FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
-&self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Neg for &FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
self.f.element(&-&self.number)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul for FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
&self * &rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<&Self> for FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn mul(self, rhs: &Self) -> Self::Output {
|
||||
&self * rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<FieldElement<'a>> for &FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn mul(self, rhs: FieldElement<'a>) -> Self::Output {
|
||||
self * &rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul for &FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
self.f.element(&(&self.number * &rhs.number))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Div<&Self> for FieldElement<'a> {
|
||||
type Output = FieldElement<'a>;
|
||||
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn div(self, rhs: &Self) -> Self::Output {
|
||||
self * rhs.f.inv0(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FieldElement<'a> {
|
||||
fn square(&self) -> Self {
|
||||
self * self
|
||||
}
|
||||
|
||||
fn pow_internal(&self, exponent: &BigInt) -> Self {
|
||||
let exponent = exponent.mod_floor(&(self.f.0 - 1));
|
||||
self.f.element(&self.number.modpow(&exponent, self.f.0))
|
||||
}
|
||||
|
||||
/// Corresponds to the sqrt_3mod4() function defined in
|
||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-I.1>
|
||||
fn sqrt(&self) -> Self {
|
||||
// constant
|
||||
let c1 = (self.f.0 + 1) >> 2;
|
||||
|
||||
self.pow_internal(&c1)
|
||||
}
|
||||
|
||||
/// Corresponds to the sgn0_m_eq_1() function defined in
|
||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4.1>
|
||||
fn sgn0(&self) -> i32 {
|
||||
(&self.number % 2_usize).to_i32().unwrap()
|
||||
}
|
||||
|
||||
fn is_zero(&self) -> bool {
|
||||
self.number.is_one()
|
||||
}
|
||||
|
||||
/// Corresponds to the is_square() function defined in
|
||||
/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-4>
|
||||
fn is_square(&self) -> bool {
|
||||
// constant
|
||||
let exponent = (self.f.0 - 1) >> 1;
|
||||
|
||||
let result = self.pow_internal(&exponent);
|
||||
result.number.is_one() || result.is_zero()
|
||||
}
|
||||
|
||||
fn to_bytes<N: ArrayLength<u8>>(&self) -> GenericArray<u8, N> {
|
||||
GenericArray::clone_from_slice(&self.number.mod_floor(self.f.0).to_bytes_be().1)
|
||||
}
|
||||
}
|
||||
|
||||
fn cmov<'a>(x: &FieldElement<'a>, y: &FieldElement<'a>, b: bool) -> FieldElement<'a> {
|
||||
if b {
|
||||
y.clone()
|
||||
} else {
|
||||
x.clone()
|
||||
}
|
||||
}
|
||||
|
||||
let f = Field::new(p);
|
||||
let a = f.element(a);
|
||||
let b = f.element(b);
|
||||
let z = f.element(z);
|
||||
let u = f.element(u);
|
||||
|
||||
// Constants:
|
||||
// 1. c1 = -B / A
|
||||
let c1 = -&b / &a;
|
||||
// 2. c2 = -1 / Z
|
||||
let c2 = -f.one() / &z;
|
||||
|
||||
// Steps:
|
||||
// 1. tv1 = Z * u^2
|
||||
let tv1 = z * u.square();
|
||||
// 2. tv2 = tv1^2
|
||||
let mut tv2 = tv1.square();
|
||||
// 3. x1 = tv1 + tv2
|
||||
let mut x1 = &tv1 + &tv2;
|
||||
// 4. x1 = inv0(x1)
|
||||
x1 = f.inv0(&x1);
|
||||
// 5. e1 = x1 == 0
|
||||
let e1 = x1.is_zero();
|
||||
// 6. x1 = x1 + 1
|
||||
x1 = x1 + f.one();
|
||||
// 7. x1 = CMOV(x1, c2, e1) # If (tv1 + tv2) == 0, set x1 = -1 / Z
|
||||
x1 = cmov(&x1, &c2, e1);
|
||||
// 8. x1 = x1 * c1 # x1 = (-B / A) * (1 + (1 / (Z^2 * u^4 + Z * u^2)))
|
||||
x1 = x1 * c1;
|
||||
// 9. gx1 = x1^2
|
||||
let mut gx1 = x1.square();
|
||||
// 10. gx1 = gx1 + A
|
||||
gx1 = gx1 + a;
|
||||
// 11. gx1 = gx1 * x1
|
||||
gx1 = gx1 * &x1;
|
||||
// 12. gx1 = gx1 + B # gx1 = g(x1) = x1^3 + A * x1 + B
|
||||
gx1 = gx1 + b;
|
||||
// 13. x2 = tv1 * x1 # x2 = Z * u^2 * x1
|
||||
let x2 = &tv1 * &x1;
|
||||
// 14. tv2 = tv1 * tv2
|
||||
tv2 = tv1 * tv2;
|
||||
// 15. gx2 = gx1 * tv2 # gx2 = (Z * u^2)^3 * gx1
|
||||
let gx2 = &gx1 * tv2;
|
||||
// 16. e2 = is_square(gx1)
|
||||
let e2 = gx1.is_square();
|
||||
// 17. x = CMOV(x2, x1, e2) # If is_square(gx1), x = x1, else x = x2
|
||||
let x = cmov(&x2, &x1, e2);
|
||||
// 18. y2 = CMOV(gx2, gx1, e2) # If is_square(gx1), y2 = gx1, else y2 = gx2
|
||||
let y2 = cmov(&gx2, &gx1, e2);
|
||||
// 19. y = sqrt(y2)
|
||||
let mut y = y2.sqrt();
|
||||
// 20. e3 = sgn0(u) == sgn0(y) # Fix sign of y
|
||||
let e3 = u.sgn0() == y.sgn0();
|
||||
// 21. y = CMOV(-y, y, e3)
|
||||
y = cmov(&-&y, &y, e3);
|
||||
// 22. return (x, y)
|
||||
(x.to_bytes(), y.to_bytes())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct Params {
|
||||
msg: &'static str,
|
||||
px: &'static str,
|
||||
py: &'static str,
|
||||
u0: &'static str,
|
||||
u1: &'static str,
|
||||
q0x: &'static str,
|
||||
q0y: &'static str,
|
||||
q1x: &'static str,
|
||||
q1y: &'static str,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_to_curve_simple_swu() {
|
||||
// Test vectors taken from https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#appendix-J.1.1
|
||||
let test_vectors: Vec<Params> = vec![
|
||||
Params {
|
||||
msg: "",
|
||||
px: "2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4",
|
||||
py: "8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415",
|
||||
u0: "ad5342c66a6dd0ff080df1da0ea1c04b96e0330dd89406465eeba11582515009",
|
||||
u1: "8c0f1d43204bd6f6ea70ae8013070a1518b43873bcd850aafa0a9e220e2eea5a",
|
||||
q0x: "ab640a12220d3ff283510ff3f4b1953d09fad35795140b1c5d64f313967934d5",
|
||||
q0y: "dccb558863804a881d4fff3455716c836cef230e5209594ddd33d85c565b19b1",
|
||||
q1x: "51cce63c50d972a6e51c61334f0f4875c9ac1cd2d3238412f84e31da7d980ef5",
|
||||
q1y: "b45d1a36d00ad90e5ec7840a60a4de411917fbe7c82c3949a6e699e5a1b66aac",
|
||||
},
|
||||
Params {
|
||||
msg: "abc",
|
||||
px: "0bb8b87485551aa43ed54f009230450b492fead5f1cc91658775dac4a3388a0f",
|
||||
py: "5c41b3d0731a27a7b14bc0bf0ccded2d8751f83493404c84a88e71ffd424212e",
|
||||
u0: "afe47f2ea2b10465cc26ac403194dfb68b7f5ee865cda61e9f3e07a537220af1",
|
||||
u1: "379a27833b0bfe6f7bdca08e1e83c760bf9a338ab335542704edcd69ce9e46e0",
|
||||
q0x: "5219ad0ddef3cc49b714145e91b2f7de6ce0a7a7dc7406c7726c7e373c58cb48",
|
||||
q0y: "7950144e52d30acbec7b624c203b1996c99617d0b61c2442354301b191d93ecf",
|
||||
q1x: "019b7cb4efcfeaf39f738fe638e31d375ad6837f58a852d032ff60c69ee3875f",
|
||||
q1y: "589a62d2b22357fed5449bc38065b760095ebe6aeac84b01156ee4252715446e",
|
||||
},
|
||||
Params {
|
||||
msg: "abcdef0123456789",
|
||||
px: "65038ac8f2b1def042a5df0b33b1f4eca6bff7cb0f9c6c1526811864e544ed80",
|
||||
py: "cad44d40a656e7aff4002a8de287abc8ae0482b5ae825822bb870d6df9b56ca3",
|
||||
u0: "0fad9d125a9477d55cf9357105b0eb3a5c4259809bf87180aa01d651f53d312c",
|
||||
u1: "b68597377392cd3419d8fcc7d7660948c8403b19ea78bbca4b133c9d2196c0fb",
|
||||
q0x: "a17bdf2965eb88074bc01157e644ed409dac97cfcf0c61c998ed0fa45e79e4a2",
|
||||
q0y: "4f1bc80c70d411a3cc1d67aeae6e726f0f311639fee560c7f5a664554e3c9c2e",
|
||||
q1x: "7da48bb67225c1a17d452c983798113f47e438e4202219dd0715f8419b274d66",
|
||||
q1y: "b765696b2913e36db3016c47edb99e24b1da30e761a8a3215dc0ec4d8f96e6f9",
|
||||
},
|
||||
Params {
|
||||
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
||||
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
||||
qqqqqqqqqqqqqqqqqqqqqqqqq",
|
||||
px: "4be61ee205094282ba8a2042bcb48d88dfbb609301c49aa8b078533dc65a0b5d",
|
||||
py: "98f8df449a072c4721d241a3b1236d3caccba603f916ca680f4539d2bfb3c29e",
|
||||
u0: "3bbc30446f39a7befad080f4d5f32ed116b9534626993d2cc5033f6f8d805919",
|
||||
u1: "76bb02db019ca9d3c1e02f0c17f8baf617bbdae5c393a81d9ce11e3be1bf1d33",
|
||||
q0x: "c76aaa823aeadeb3f356909cb08f97eee46ecb157c1f56699b5efebddf0e6398",
|
||||
q0y: "776a6f45f528a0e8d289a4be12c4fab80762386ec644abf2bffb9b627e4352b1",
|
||||
q1x: "418ac3d85a5ccc4ea8dec14f750a3a9ec8b85176c95a7022f391826794eb5a75",
|
||||
q1y: "fd6604f69e9d9d2b74b072d14ea13050db72c932815523305cb9e807cc900aff",
|
||||
},
|
||||
Params {
|
||||
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
px: "457ae2981f70ca85d8e24c308b14db22f3e3862c5ea0f652ca38b5e49cd64bc5",
|
||||
py: "ecb9f0eadc9aeed232dabc53235368c1394c78de05dd96893eefa62b0f4757dc",
|
||||
u0: "4ebc95a6e839b1ae3c63b847798e85cb3c12d3817ec6ebc10af6ee51adb29fec",
|
||||
u1: "4e21af88e22ea80156aff790750121035b3eefaa96b425a8716e0d20b4e269ee",
|
||||
q0x: "d88b989ee9d1295df413d4456c5c850b8b2fb0f5402cc5c4c7e815412e926db8",
|
||||
q0y: "bb4a1edeff506cf16def96afff41b16fc74f6dbd55c2210e5b8f011ba32f4f40",
|
||||
q1x: "a281e34e628f3a4d2a53fa87ff973537d68ad4fbc28d3be5e8d9f6a2571c5a4b",
|
||||
q1y: "f6ed88a7aab56a488100e6f1174fa9810b47db13e86be999644922961206e184",
|
||||
},
|
||||
];
|
||||
let dst = "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_";
|
||||
|
||||
for tv in test_vectors {
|
||||
let uniform_bytes = crate::map_to_curve::expand_message_xmd::<sha2::Sha256>(
|
||||
tv.msg.as_bytes(),
|
||||
dst.as_bytes(),
|
||||
96,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let u0 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[..48]).mod_floor(&P);
|
||||
let u1 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[48..]).mod_floor(&P);
|
||||
|
||||
assert_eq!(BigInt::parse_bytes(tv.u0.as_bytes(), 16).unwrap(), u0);
|
||||
assert_eq!(BigInt::parse_bytes(tv.u1.as_bytes(), 16).unwrap(), u1);
|
||||
|
||||
let (q0x, q0y) = super::map_to_curve_simple_swu(&u0, &A, &B, &P, &Z);
|
||||
let (q1x, q1y) = super::map_to_curve_simple_swu(&u1, &A, &B, &P, &Z);
|
||||
|
||||
assert_eq!(tv.q0x, hex::encode(q0x));
|
||||
assert_eq!(tv.q0y, hex::encode(q0y));
|
||||
assert_eq!(tv.q1x, hex::encode(q1x));
|
||||
assert_eq!(tv.q1y, hex::encode(q1y));
|
||||
|
||||
let p0 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
|
||||
&q0x, &q0y, false,
|
||||
))
|
||||
.unwrap()
|
||||
.to_curve();
|
||||
let p1 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
|
||||
&q1x, &q1y, false,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let p = (p0 + p1).to_encoded_point(false);
|
||||
|
||||
assert_eq!(tv.px, hex::encode(p.x().unwrap()));
|
||||
assert_eq!(tv.py, hex::encode(p.y().unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,6 @@
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE 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
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
|
||||
use curve25519_dalek::{
|
||||
@@ -16,64 +13,13 @@ use curve25519_dalek::{
|
||||
};
|
||||
use generic_array::{
|
||||
typenum::{U32, U64},
|
||||
ArrayLength, GenericArray,
|
||||
GenericArray,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::ops::Mul;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
|
||||
/// subgroup is noted additively — as in the draft RFC — in this trait.
|
||||
pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
|
||||
/// The type of base field scalars
|
||||
type Scalar: Zeroize + Copy;
|
||||
/// The byte length necessary to represent scalars
|
||||
type ScalarLen: ArrayLength<u8> + 'static;
|
||||
/// Return a scalar from its fixed-length bytes representation
|
||||
fn from_scalar_slice(
|
||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
||||
) -> Result<Self::Scalar, InternalPakeError>;
|
||||
/// picks a scalar at random
|
||||
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
|
||||
/// Serializes a scalar to bytes
|
||||
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
|
||||
/// The multiplicative inverse of this scalar
|
||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
|
||||
|
||||
/// The byte length necessary to represent group elements
|
||||
type ElemLen: ArrayLength<u8> + 'static;
|
||||
/// Return an element from its fixed-length bytes representation
|
||||
fn from_element_slice(
|
||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
||||
) -> Result<Self, InternalPakeError>;
|
||||
/// Serializes the `self` group element
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;
|
||||
|
||||
/// Hashes points presumed to be uniformly random to the curve. The
|
||||
/// impl is allowed to perform additional hashes if it needs to, but this
|
||||
/// may not be necessary as this function is going to be called with the
|
||||
/// output of a kdf.
|
||||
type UniformBytesLen: ArrayLength<u8>;
|
||||
|
||||
/// Hashes a slice of pseudo-random bytes of the correct length to a curve point
|
||||
fn hash_to_curve(
|
||||
uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>,
|
||||
) -> Result<Self, InternalPakeError>;
|
||||
|
||||
/// Get the base point for the group
|
||||
fn base_point() -> Self;
|
||||
|
||||
/// Multiply the point by a scalar, represented as a slice
|
||||
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
|
||||
|
||||
/// Returns if the group element is equal to the identity (1)
|
||||
fn is_identity(&self) -> bool;
|
||||
|
||||
/// Compares in constant time if the group elements are equal
|
||||
fn ct_equal(&self, other: &Self) -> bool;
|
||||
}
|
||||
use super::Group;
|
||||
|
||||
/// The implementation of such a subgroup for Ristretto
|
||||
impl Group for RistrettoPoint {
|
||||
@@ -30,7 +30,6 @@ use rand::{CryptoRng, RngCore};
|
||||
use std::convert::TryFrom;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
const KEY_LEN: usize = 32;
|
||||
pub(crate) type NonceLen = U32;
|
||||
|
||||
static STR_RFC: &[u8] = b"RFCXXXX";
|
||||
@@ -213,7 +212,9 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
|
||||
}
|
||||
|
||||
fn ke2_message_size() -> usize {
|
||||
NonceLen::to_usize() + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
|
||||
NonceLen::to_usize()
|
||||
+ <G as Group>::ElemLen::to_usize()
|
||||
+ <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,13 +258,15 @@ pub struct Ke1Message<G: Group> {
|
||||
|
||||
impl<G: Group> FromBytes for Ke1State<G> {
|
||||
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, PakeError> {
|
||||
let key_len = <G as Group>::ElemLen::to_usize();
|
||||
|
||||
let nonce_len = NonceLen::to_usize();
|
||||
let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
|
||||
let checked_bytes = check_slice_size_atleast(bytes, key_len + nonce_len, "ke1_state")?;
|
||||
|
||||
Ok(Self {
|
||||
client_e_sk: PrivateKey::from_bytes(&checked_bytes[..KEY_LEN])?,
|
||||
client_e_sk: PrivateKey::from_bytes(&checked_bytes[..key_len])?,
|
||||
client_nonce: GenericArray::clone_from_slice(
|
||||
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
|
||||
&checked_bytes[key_len..key_len + nonce_len],
|
||||
),
|
||||
})
|
||||
}
|
||||
@@ -296,8 +299,11 @@ impl<G: Group> ToBytes for Ke1Message<G> {
|
||||
impl<G: Group> FromBytes for Ke1Message<G> {
|
||||
fn from_bytes<CS: CipherSuite>(ke1_message_bytes: &[u8]) -> Result<Self, PakeError> {
|
||||
let nonce_len = NonceLen::to_usize();
|
||||
let checked_nonce =
|
||||
check_slice_size(ke1_message_bytes, nonce_len + KEY_LEN, "ke1_message nonce")?;
|
||||
let checked_nonce = check_slice_size(
|
||||
ke1_message_bytes,
|
||||
nonce_len + <G as Group>::ElemLen::to_usize(),
|
||||
"ke1_message nonce",
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
|
||||
@@ -389,23 +395,24 @@ impl<G: Group, HashLen: ArrayLength<u8>> Ke2Message<G, HashLen> {
|
||||
|
||||
impl<G: Group, HashLen: ArrayLength<u8>> FromBytes for Ke2Message<G, HashLen> {
|
||||
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError> {
|
||||
let key_len = <G as Group>::ElemLen::to_usize();
|
||||
let nonce_len = NonceLen::to_usize();
|
||||
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
|
||||
|
||||
let unchecked_server_e_pk = check_slice_size_atleast(
|
||||
&checked_nonce[nonce_len..],
|
||||
KEY_LEN,
|
||||
key_len,
|
||||
"ke2_message server_e_pk",
|
||||
)?;
|
||||
let checked_mac = check_slice_size(
|
||||
&unchecked_server_e_pk[KEY_LEN..],
|
||||
&unchecked_server_e_pk[key_len..],
|
||||
HashLen::to_usize(),
|
||||
"ke1_message mac",
|
||||
)?;
|
||||
|
||||
// Check the public key bytes
|
||||
let server_e_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
|
||||
&unchecked_server_e_pk[..KEY_LEN],
|
||||
&unchecked_server_e_pk[..key_len],
|
||||
)?)?;
|
||||
|
||||
Ok(Self {
|
||||
|
||||
@@ -799,6 +799,8 @@
|
||||
//! [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) and allow for selecting
|
||||
//! the corresponding backend for the curve arithmetic used. The `u64_backend` feature is included as the default.
|
||||
//!
|
||||
//! - The `p256` feature enables the use of `p256::ProjectivePoint` as a `Group` for `CipherSuite`.
|
||||
//!
|
||||
//! - The `bench` feature is used only for running performance benchmarks for this implementation.
|
||||
//!
|
||||
|
||||
|
||||
@@ -56,6 +56,36 @@ impl GroupWithMapToCurve for RistrettoPoint {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
impl GroupWithMapToCurve for p256_::ProjectivePoint {
|
||||
const SUITE_ID: usize = 0x0003;
|
||||
|
||||
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, ProtocolError> {
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
||||
// `hash_to_curve` calls `hash_to_field` with a `count` of `2`
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
||||
// `hash_to_field` calls `expand_message` with a `len_in_bytes` of `count * L`
|
||||
let uniform_bytes = expand_message_xmd::<H>(msg, dst, 2 * crate::group::p256::L)?;
|
||||
|
||||
<Self as Group>::hash_to_curve(&GenericArray::clone_from_slice(&uniform_bytes[..]))
|
||||
.map_err(ProtocolError::from)
|
||||
}
|
||||
|
||||
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8]) -> Result<Self::Scalar, ProtocolError> {
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use num_integer::Integer;
|
||||
|
||||
let uniform_bytes = expand_message_xmd::<H>(input, dst, crate::group::p256::L)?;
|
||||
#[allow(clippy::borrow_interior_mutable_const)]
|
||||
let bytes =
|
||||
BigInt::from_bytes_be(Sign::Plus, &uniform_bytes).mod_floor(&crate::group::p256::R);
|
||||
|
||||
Ok(p256_::Scalar::from_bytes_reduced(GenericArray::from_slice(
|
||||
&bytes.to_bytes_be().1,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
// Computes ceil(x / y)
|
||||
fn div_ceil(x: usize, y: usize) -> usize {
|
||||
let additive = (x % y != 0) as usize;
|
||||
|
||||
@@ -23,6 +23,16 @@ impl CipherSuite for Ristretto255Sha512NoSlowHash {
|
||||
type SlowHash = NoOpHash;
|
||||
}
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
struct P256Sha256NoSlowHash;
|
||||
#[cfg(feature = "p256")]
|
||||
impl CipherSuite for P256Sha256NoSlowHash {
|
||||
type Group = p256_::ProjectivePoint;
|
||||
type KeyExchange = TripleDH;
|
||||
type Hash = sha2::Sha256;
|
||||
type SlowHash = NoOpHash;
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum EnvelopeMode {
|
||||
Base,
|
||||
@@ -71,9 +81,8 @@ pub struct TestVectorParameters {
|
||||
pub oprf_key: Vec<u8>,
|
||||
}
|
||||
|
||||
// Pulled from "OPAQUE-3DH Test Vector 1" and "OPAQUE-3DH Test Vector 6"
|
||||
// of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
|
||||
static TEST_VECTORS: &[&str] = &[
|
||||
// Pulled from https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-opaque-06#appendix-C
|
||||
static RISTRETTO_TEST_VECTORS: &[&str] = &[
|
||||
r#"
|
||||
### OPAQUE-3DH Real Test Vector 1
|
||||
|
||||
@@ -316,7 +325,7 @@ a75706750b72c4943c3985cae5f31969dea9c74192c8bf601e2d062fcb141c89234ff
|
||||
"#,
|
||||
];
|
||||
|
||||
static FAKE_TEST_VECTORS: &[&str] = &[r#"
|
||||
static RISTRETTO_FAKE_TEST_VECTORS: &[&str] = &[r#"
|
||||
### OPAQUE-3DH Fake Test Vector 1
|
||||
|
||||
#### Configuration
|
||||
@@ -387,6 +396,294 @@ b9467e8bfa9a4006aba7f21b74b4ce3bccd686785878b0ec9b3fc4200228014d5d073
|
||||
~~~
|
||||
"#];
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
static P256_TEST_VECTORS: &[&str] = &[
|
||||
r#"
|
||||
### OPAQUE-3DH Real Test Vector 3
|
||||
|
||||
#### Configuration
|
||||
|
||||
~~~
|
||||
OPRF: 0003
|
||||
Hash: SHA256
|
||||
MHF: Identity
|
||||
KDF: HKDF-SHA256
|
||||
MAC: HMAC-SHA256
|
||||
EnvelopeMode: 01
|
||||
Group: P256_XMD:SHA-256_SSWU_RO_
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 32
|
||||
Npk: 33
|
||||
Nsk: 32
|
||||
Nm: 32
|
||||
Nx: 32
|
||||
Nok: 32
|
||||
~~~
|
||||
|
||||
#### Input Values
|
||||
|
||||
~~~
|
||||
oprf_seed: 77bfc065218c9a5593c952161b93193f025b3474102519e6984fa64831
|
||||
0dd1bf
|
||||
credential_identifier: 31323334
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: 2527e48c983deeb54c9c6337fdd9e120de85343dc7887f00248f1
|
||||
acacc4a8319
|
||||
masking_nonce: cb792f3657240ce5296dd5633e7333531009c11ee6ab46b6111f15
|
||||
6d96a160b2
|
||||
server_private_key: 87ef09986545b295e8f5bbbaa7ad3dce15eb299eb2a5b3487
|
||||
5ff421b1d63d7a3
|
||||
server_public_key: 025b95a6add1f2f3d038811b5ad3494bed73b1e2500d8dadec
|
||||
592d88406e25c2f2
|
||||
server_nonce: 8018e88ecfc53891529278c47239f8fe6f1be88972721898ef81cc0
|
||||
a76a0b550
|
||||
client_nonce: 967fcded96ed46986e60fcbdf985232639f537377ca3fcf07ad4899
|
||||
56b2e9019
|
||||
server_keyshare: 0242bc29993976185dacf6be815cbfa923aac80fad8b7f020c9d
|
||||
4f18e0b6867a17
|
||||
client_keyshare: 03358b4eae039953116889466bfddeb40168e39ed83809fd5f0d
|
||||
5f2de9c5234398
|
||||
server_private_keyshare: b1c0063e442238bdd89cd62b4c3ad31f016b68085d25
|
||||
f85613f5838cd7c6b16a
|
||||
client_private_keyshare: 10256ab078bc1edbaf79bee4cd28dd9db89179dcc921
|
||||
9bc8f388b533f5439099
|
||||
blind_registration: d50e29b581d716c3c05c4a0d6110b510cb5c9959bee817fde
|
||||
b1eabd7ccd74fee
|
||||
blind_login: 503d8495c6d04efaee8370c45fa1dfad70201edd140cec8ed6c73b5f
|
||||
cd15c478
|
||||
~~~
|
||||
|
||||
#### Intermediate Values
|
||||
|
||||
~~~
|
||||
client_public_key: 02680493263d3bc4c7af455ba1219fd9bbe329fd0c2a0248e8
|
||||
7321ded8ff17b386
|
||||
auth_key: 570a8105a7d86679b4c9d009edc9627af6b17e8b2d2f0d50cbd13ea8a00
|
||||
82cd7
|
||||
randomized_pwd: 04f1615bc400765f22f7af1277a0814b5665ad1d4ef9bf1829880
|
||||
2a0f6b4636b
|
||||
envelope: 2527e48c983deeb54c9c6337fdd9e120de85343dc7887f00248f1acacc4
|
||||
a8319890e251c4b6397fb35900ff46ae1df1e86eed2d23005c6b9c61caa4e12af8bf5
|
||||
handshake_secret: 56212ac60eca9f917f6a4ce6aefe762743da701b008ec986cd1
|
||||
87ff75df5df84
|
||||
server_mac_key: 8c7f132b6cd9a7e4ce9171cd469d02dc1ab0e8d96f4e1ddca1718
|
||||
55fc723203f
|
||||
client_mac_key: 348cb4526417423090d386ce43459d652a6489122e27d3953ed47
|
||||
5b3ab9cd336
|
||||
oprf_key: d153d662a1e7dd4383837aa7125685d2be6f8041472ecbfd610e46952a6
|
||||
a24f1
|
||||
~~~
|
||||
|
||||
#### Output Values
|
||||
|
||||
~~~
|
||||
registration_request: 0325768a660df0c15f6f2a1dcbb7efd4f1c92702401edf3
|
||||
e2f0742c8dce85d5fa8
|
||||
registration_response: 0244211a4d2a067f7a61ed88dff6764856d347465f330d
|
||||
0e15502700afd1865911025b95a6add1f2f3d038811b5ad3494bed73b1e2500d8dade
|
||||
c592d88406e25c2f2
|
||||
registration_upload: 02680493263d3bc4c7af455ba1219fd9bbe329fd0c2a0248
|
||||
e87321ded8ff17b3868efb26f2bb390fd23b90c49ae680c4560fbd2b3c4f32891505c
|
||||
ad7d95b7bc58e2527e48c983deeb54c9c6337fdd9e120de85343dc7887f00248f1aca
|
||||
cc4a8319890e251c4b6397fb35900ff46ae1df1e86eed2d23005c6b9c61caa4e12af8
|
||||
bf5
|
||||
KE1: 03884e56429f1ee53559f2e244392eb8f994fd46c8fd9ffdd24ac5a7af963a66
|
||||
3b967fcded96ed46986e60fcbdf985232639f537377ca3fcf07ad489956b2e9019033
|
||||
58b4eae039953116889466bfddeb40168e39ed83809fd5f0d5f2de9c5234398
|
||||
KE2: 0383fff1b3e8003723dff1b1f90a7934a036bd6691aca0366b07a100bf2bb3dc
|
||||
2acb792f3657240ce5296dd5633e7333531009c11ee6ab46b6111f156d96a160b23b6
|
||||
a5ff1ce8035a1dca4776f32f43c7ce626d796da0f27fc9897522fc1fab70d2fb443d8
|
||||
2a4333770057e929c2f9977d40a64e8b4a5a553d25a8b8392b4adbf0a0a6e87f26165
|
||||
0d04084823b23b07d351e3b947778a43859be3ba218b22d054edf8018e88ecfc53891
|
||||
529278c47239f8fe6f1be88972721898ef81cc0a76a0b5500242bc29993976185dacf
|
||||
6be815cbfa923aac80fad8b7f020c9d4f18e0b6867a171e7fda886b9fd9f3bc9e37c4
|
||||
04ca07f7a5c9a6f98df20a5cac42371162731faa
|
||||
KE3: 86a57a3e1a2e537ea667031091c025cb539826dbbb1756683220dd239d4a7bff
|
||||
export_key: a83a3fe26af0dadb63d15ed808a4dc2edb57f45212554ecc1af5e0273
|
||||
50651de
|
||||
session_key: e26d54798ce8a66fb415cb67f4d87647dcd3d8aa79a7ab6a5f701b82
|
||||
f037b1e3
|
||||
~~~
|
||||
"#,
|
||||
r#"
|
||||
### OPAQUE-3DH Real Test Vector 4
|
||||
|
||||
#### Configuration
|
||||
|
||||
~~~
|
||||
OPRF: 0003
|
||||
Hash: SHA256
|
||||
MHF: Identity
|
||||
KDF: HKDF-SHA256
|
||||
MAC: HMAC-SHA256
|
||||
EnvelopeMode: 01
|
||||
Group: P256_XMD:SHA-256_SSWU_RO_
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 32
|
||||
Npk: 33
|
||||
Nsk: 32
|
||||
Nm: 32
|
||||
Nx: 32
|
||||
Nok: 32
|
||||
~~~
|
||||
|
||||
#### Input Values
|
||||
|
||||
~~~
|
||||
client_identity: 616c696365
|
||||
server_identity: 626f62
|
||||
oprf_seed: 482123652ea37c7e4a0f9f1984ff1f2a310fe428d9de5819bf63b3942d
|
||||
be09f9
|
||||
credential_identifier: 31323334
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: 75c245690f9669a9af5699e8b23d6d1fa9e697aeb4526267d942b
|
||||
842e4426e42
|
||||
masking_nonce: 5947586f69259e0708bdfab794f689eec14c7deb7edde68c816451
|
||||
56cf278f21
|
||||
server_private_key: c728ebf47b1c65594d77dab871872dba848bdf20ed725f0fa
|
||||
3b58e7d8f3eab2b
|
||||
server_public_key: 029a2c6097fbbcf3457fe3ff7d4ef8e89dab585a67dfed0905
|
||||
c9f104d909138bae
|
||||
server_nonce: 581ac468101aee528cc6b69daac7a90de8837d49708e76310767cbe
|
||||
4af18594d
|
||||
client_nonce: 46498f95ec7986f0602019b3fbb646db87a2fdbc12176d4f7ab74fa
|
||||
5fadace60
|
||||
server_keyshare: 022aa8746ab4329d591296652d44f6dfb04470103311bacd7ad5
|
||||
1060ef5abac41b
|
||||
client_keyshare: 02a9f857ad3eabe09047049e8b8cee72feea2acb7fc487777c0b
|
||||
22d3add6a0e0c0
|
||||
server_private_keyshare: 48a5baa24274d5acc5e007a44f2147549ac8dd675564
|
||||
2638f1029631944beed4
|
||||
client_private_keyshare: 161e3aaa50f50e33344022969d17d9cf4c88b7a9eec4
|
||||
c36bf64de079abb6dc7b
|
||||
blind_registration: 9280e203ef27d9ef0d1d189bb3c02a66ef9a72d48cca6c1f9
|
||||
afc1fedea22567c
|
||||
blind_login: 4308682dc1bdab92ff91bb1a5fc5bc084223fe4369beddca3f1640a6
|
||||
645455ad
|
||||
~~~
|
||||
|
||||
#### Intermediate Values
|
||||
|
||||
~~~
|
||||
client_public_key: 02e89507b3a1a946e8096cd7e1e8fbc31e2dd39fecc49580ed
|
||||
2659262c08ea33eb
|
||||
auth_key: 76cba5b349c60c5a19ab06b70a3191d3418318b5a203fd298b18a0eda53
|
||||
efd1a
|
||||
randomized_pwd: 74649c9c7b0d7436c4873984732fe45e19dabd1a96d7e9175468a
|
||||
85ed16bea65
|
||||
envelope: 75c245690f9669a9af5699e8b23d6d1fa9e697aeb4526267d942b842e44
|
||||
26e423938d818ea53f58fdaab8541765d5171e99b1bdc2c63e8e1eaf62d3a60aacabe
|
||||
handshake_secret: 22f608959308cb4dff55cf77c006ea8e9bc66df75d7076a927a
|
||||
3d21d3fce5562
|
||||
server_mac_key: 76e1415cfcf0ff271533fdd4ce4fffb4110ba1ff4aa9a02a1734d
|
||||
9ae0e0ce47a
|
||||
client_mac_key: 9341b0b36ac36875910cd1260cd8dc6d6cd58e0fb6503fece6524
|
||||
11b6f627bf7
|
||||
oprf_key: f14e1fc34ba1218bfd3f7373f036889bf4f35a8fbc9e8c9c07ccf2d2388
|
||||
79d9c
|
||||
~~~
|
||||
|
||||
#### Output Values
|
||||
|
||||
~~~
|
||||
registration_request: 02792b0f4670aced5970a68b01bb951004ccad962159be4
|
||||
b6783170c9ad68f6052
|
||||
registration_response: 03cc3491b4bcb3e4804f3eadbc6a04c8fff18cc9ca5a4f
|
||||
eeb577fdfebd71f5060f029a2c6097fbbcf3457fe3ff7d4ef8e89dab585a67dfed090
|
||||
5c9f104d909138bae
|
||||
registration_upload: 02e89507b3a1a946e8096cd7e1e8fbc31e2dd39fecc49580
|
||||
ed2659262c08ea33eb260603b2690f3d466fb0b747e256283bed94836ac98c10d4588
|
||||
1372046d3b1e875c245690f9669a9af5699e8b23d6d1fa9e697aeb4526267d942b842
|
||||
e4426e423938d818ea53f58fdaab8541765d5171e99b1bdc2c63e8e1eaf62d3a60aac
|
||||
abe
|
||||
KE1: 02fe96fc48d9fc921edd8e92ada581cbcc2a65e30962d0002ea5242f5baf627f
|
||||
f646498f95ec7986f0602019b3fbb646db87a2fdbc12176d4f7ab74fa5fadace6002a
|
||||
9f857ad3eabe09047049e8b8cee72feea2acb7fc487777c0b22d3add6a0e0c0
|
||||
KE2: 035115b21dde0992cb812926d65c7dccd5e0f8ffff573da4a7c1e603e0e40827
|
||||
895947586f69259e0708bdfab794f689eec14c7deb7edde68c81645156cf278f21cef
|
||||
3adc4e524db33258c5774efaec59750eaf3755a2dfa194ec593ce41a7a17f889978a2
|
||||
f97ced10bd1592793497e58b5d05a02ebf003f8a8949a2f8a22a09e4d1b8ba19c9e77
|
||||
4b6f31545ac4c02aba4ad8e26b4f43d65319f8d1c5a5a04668d4b581ac468101aee52
|
||||
8cc6b69daac7a90de8837d49708e76310767cbe4af18594d022aa8746ab4329d59129
|
||||
6652d44f6dfb04470103311bacd7ad51060ef5abac41bbe51a3c1deeeab8ded9273ad
|
||||
681001416cbb6d1f0976548f36d1ddb1d3b1f948
|
||||
KE3: 5770a1ce912fecf1fa339fe1646b2abfe8dd683767c885a0f1dedba8dfab653e
|
||||
export_key: 5b92e3454d59062460a87ad2ff6546d862f722c6fbd7678a0997b3c9d
|
||||
c61e9a0
|
||||
session_key: 3a04636e2c14b4ef3a01070a2ff129cd2248318d8b85d6c4368f5115
|
||||
0f0348ff
|
||||
~~~
|
||||
"#,
|
||||
];
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
static P256_FAKE_TEST_VECTORS: &[&str] = &[r#"
|
||||
### OPAQUE-3DH Fake Test Vector 2
|
||||
|
||||
#### Configuration
|
||||
|
||||
~~~
|
||||
OPRF: 0003
|
||||
Hash: SHA256
|
||||
MHF: Identity
|
||||
KDF: HKDF-SHA256
|
||||
MAC: HMAC-SHA256
|
||||
EnvelopeMode: 01
|
||||
Group: P256_XMD:SHA-256_SSWU_RO_
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 32
|
||||
Npk: 33
|
||||
Nsk: 32
|
||||
Nm: 32
|
||||
Nx: 32
|
||||
Nok: 32
|
||||
~~~
|
||||
|
||||
#### Input Values
|
||||
|
||||
~~~
|
||||
client_identity: 616c696365
|
||||
server_identity: 626f62
|
||||
oprf_seed: 42cd4f606841ca8f403920a8ecf2d60399962f49d83f857ca86676b272
|
||||
1c4366
|
||||
credential_identifier: 31323334
|
||||
masking_nonce: d3974af728aeafc9e5af4b4cab57d7e7dfbe0ef6b08df28fae5269
|
||||
229cac2332
|
||||
client_private_key: 0e6b97ef90ea8cedbada0e1295233ba417790ed8e99676903
|
||||
71d527ddad59a64
|
||||
client_public_key: 033043e30c3dd5fb22d0b3d167acc28878ea7c3ac49cf82b2e
|
||||
b4b60a8299a67f7a
|
||||
server_private_key: b08b686382820021a7d32ad3cb8ff60f15437b5cb00c53f21
|
||||
f3fa17ac31d2bc0
|
||||
server_public_key: 03983ac5783e6a460a526066f1398cdc648518a985cc26a66f
|
||||
c7573a71ce36dbe5
|
||||
server_nonce: 1a60a3e31bb007db74b7114aab2f196ef6bec942a9b4fe6c61143fa
|
||||
c34d42143
|
||||
server_keyshare: 03eefd21dd74c665064ebcbf63ac5ebce9a45097d47dfc08d845
|
||||
52a105419b44aa
|
||||
server_private_keyshare: 751e5012ba0c535e008b2389bea166a5d59a49353f12
|
||||
20f5e345f0546463ccdf
|
||||
masking_key: 5b8caab90accd4f239e85ec978f6a6346edc0019c5671e81034ead61
|
||||
5ce096fc
|
||||
KE1: 028bc054fff79a9e0f0315e31cc035384aedd9d50ea8ee36630d39876ca4e592
|
||||
93d797d24fe5ad528130825016bfdc2eeaeef19914c366a615bcdbefd1f04b7208023
|
||||
843b78440c0e79d828ac4c2658d1cedf7e9795f2242527a4c1a254501d2ca1a
|
||||
~~~
|
||||
|
||||
#### Output Values
|
||||
|
||||
~~~
|
||||
KE2: 0353685a152940706b1ed877b2da12f3c9f417d38fab56f3228c60f72429f602
|
||||
d9d3974af728aeafc9e5af4b4cab57d7e7dfbe0ef6b08df28fae5269229cac23329a9
|
||||
93151e43ac41ce18939444cea5d012b8a8316ed439d6fccf06b064f7564722f555750
|
||||
61897fbb6051f37e3247d08804437259fb9b022cc12715caca4ac12ef7a8b2f101269
|
||||
37619ce4725e6b821de5f44ddb71a8582883aa9b5aaefa9e3d0231a60a3e31bb007db
|
||||
74b7114aab2f196ef6bec942a9b4fe6c61143fac34d4214303eefd21dd74c665064eb
|
||||
cbf63ac5ebce9a45097d47dfc08d84552a105419b44aadb37380855acdd939b7eb300
|
||||
708d78b17ff0f99cee4ca4777c7628fb8ff591d1
|
||||
~~~
|
||||
"#];
|
||||
|
||||
macro_rules! parse {
|
||||
( $v:ident, $s:expr ) => {
|
||||
parse_default!($v, $s, vec![])
|
||||
@@ -405,7 +702,9 @@ macro_rules! parse_default {
|
||||
macro_rules! rfc_to_params {
|
||||
( $v:ident ) => {
|
||||
$v.iter()
|
||||
.map(|x| populate_test_vectors(&serde_json::from_str(rfc_to_json(x).as_str()).unwrap()))
|
||||
.map(|x| {
|
||||
populate_test_vectors::<CS>(&serde_json::from_str(rfc_to_json(x).as_str()).unwrap())
|
||||
})
|
||||
.collect::<Vec<TestVectorParameters>>()
|
||||
};
|
||||
}
|
||||
@@ -446,12 +745,12 @@ fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
|
||||
.and_then(|s| hex::decode(&s.to_string()).ok())
|
||||
}
|
||||
|
||||
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
|
||||
fn populate_test_vectors<CS: CipherSuite>(values: &Value) -> TestVectorParameters {
|
||||
TestVectorParameters {
|
||||
dummy_private_key: parse_default!(
|
||||
values,
|
||||
"client_private_key",
|
||||
vec![0u8; <PrivateKey<RistrettoPoint> as SizedBytes>::Len::to_usize()]
|
||||
vec![0u8; <PrivateKey<CS::Group> as SizedBytes>::Len::to_usize()]
|
||||
),
|
||||
dummy_masking_key: parse_default!(values, "masking_key", vec![0u8; 64]),
|
||||
context: parse!(values, "Context"),
|
||||
@@ -497,8 +796,10 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> {
|
||||
let password_file = ServerRegistration::<Ristretto255Sha512NoSlowHash>::finish(
|
||||
fn get_password_file_bytes<CS: CipherSuite>(
|
||||
parameters: &TestVectorParameters,
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let password_file = ServerRegistration::<CS>::finish(
|
||||
RegistrationUpload::deserialize(¶meters.registration_upload[..]).unwrap(),
|
||||
);
|
||||
|
||||
@@ -518,14 +819,36 @@ fn parse_identifiers(
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_request() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
fn tests() -> Result<(), ProtocolError> {
|
||||
test_registration_request::<Ristretto255Sha512NoSlowHash>(RISTRETTO_TEST_VECTORS)?;
|
||||
test_registration_response::<Ristretto255Sha512NoSlowHash>(RISTRETTO_TEST_VECTORS)?;
|
||||
test_registration_upload::<Ristretto255Sha512NoSlowHash>(RISTRETTO_TEST_VECTORS)?;
|
||||
test_ke1::<Ristretto255Sha512NoSlowHash>(RISTRETTO_TEST_VECTORS)?;
|
||||
test_ke2::<Ristretto255Sha512NoSlowHash>(RISTRETTO_TEST_VECTORS)?;
|
||||
test_ke3::<Ristretto255Sha512NoSlowHash>(RISTRETTO_TEST_VECTORS)?;
|
||||
test_server_login_finish::<Ristretto255Sha512NoSlowHash>(RISTRETTO_TEST_VECTORS)?;
|
||||
test_fake_vectors::<Ristretto255Sha512NoSlowHash>(RISTRETTO_FAKE_TEST_VECTORS)?;
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
{
|
||||
test_registration_request::<P256Sha256NoSlowHash>(P256_TEST_VECTORS)?;
|
||||
test_registration_response::<P256Sha256NoSlowHash>(P256_TEST_VECTORS)?;
|
||||
test_registration_upload::<P256Sha256NoSlowHash>(P256_TEST_VECTORS)?;
|
||||
test_ke1::<P256Sha256NoSlowHash>(P256_TEST_VECTORS)?;
|
||||
test_ke2::<P256Sha256NoSlowHash>(P256_TEST_VECTORS)?;
|
||||
test_ke3::<P256Sha256NoSlowHash>(P256_TEST_VECTORS)?;
|
||||
test_server_login_finish::<P256Sha256NoSlowHash>(P256_TEST_VECTORS)?;
|
||||
test_fake_vectors::<P256Sha256NoSlowHash>(P256_FAKE_TEST_VECTORS)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_registration_request<CS: CipherSuite>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(tvs) {
|
||||
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut rng,
|
||||
¶meters.password,
|
||||
)?;
|
||||
ClientRegistration::<CS>::start(&mut rng, ¶meters.password)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.registration_request),
|
||||
hex::encode(client_registration_start_result.message.serialize())
|
||||
@@ -534,10 +857,9 @@ fn test_registration_request() -> Result<(), ProtocolError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_response() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
|
||||
fn test_registration_response<CS: CipherSuite>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(tvs) {
|
||||
let server_setup = ServerSetup::<CS>::deserialize(
|
||||
&[
|
||||
¶meters.oprf_seed[..],
|
||||
¶meters.server_private_key[..],
|
||||
@@ -545,12 +867,11 @@ fn test_registration_response() -> Result<(), ProtocolError> {
|
||||
]
|
||||
.concat(),
|
||||
)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&server_setup,
|
||||
RegistrationRequest::deserialize(¶meters.registration_request[..]).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
)?;
|
||||
let server_registration_start_result = ServerRegistration::<CS>::start(
|
||||
&server_setup,
|
||||
RegistrationRequest::deserialize(¶meters.registration_request[..]).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(parameters.oprf_key),
|
||||
hex::encode(server_registration_start_result.oprf_key)
|
||||
@@ -563,15 +884,11 @@ fn test_registration_response() -> Result<(), ProtocolError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_upload() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
fn test_registration_upload<CS: CipherSuite>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(tvs) {
|
||||
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut rng,
|
||||
¶meters.password,
|
||||
)?;
|
||||
ClientRegistration::<CS>::start(&mut rng, ¶meters.password)?;
|
||||
|
||||
let mut finish_registration_rng = CycleRng::new(parameters.envelope_nonce);
|
||||
let result = client_registration_start_result.state.finish(
|
||||
@@ -604,9 +921,8 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ke1() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
fn test_ke1<CS: CipherSuite>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(tvs) {
|
||||
let client_login_start = [
|
||||
parameters.blind_login,
|
||||
parameters.client_private_keyshare,
|
||||
@@ -614,10 +930,8 @@ fn test_ke1() -> Result<(), ProtocolError> {
|
||||
]
|
||||
.concat();
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut client_login_start_rng,
|
||||
¶meters.password,
|
||||
)?;
|
||||
let client_login_start_result =
|
||||
ClientLogin::<CS>::start(&mut client_login_start_rng, ¶meters.password)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE1),
|
||||
hex::encode(client_login_start_result.message.serialize())
|
||||
@@ -626,10 +940,9 @@ fn test_ke1() -> Result<(), ProtocolError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ke2() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
|
||||
fn test_ke2<CS: CipherSuite>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(tvs) {
|
||||
let server_setup = ServerSetup::<CS>::deserialize(
|
||||
&[
|
||||
¶meters.oprf_seed[..],
|
||||
¶meters.server_private_key[..],
|
||||
@@ -638,8 +951,8 @@ fn test_ke2() -> Result<(), ProtocolError> {
|
||||
.concat(),
|
||||
)?;
|
||||
|
||||
let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
|
||||
&get_password_file_bytes(¶meters)?[..],
|
||||
let record = ServerRegistration::<CS>::deserialize(
|
||||
&get_password_file_bytes::<CS>(¶meters)?[..],
|
||||
)?;
|
||||
|
||||
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
|
||||
@@ -650,12 +963,11 @@ fn test_ke2() -> Result<(), ProtocolError> {
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
let server_login_start_result = ServerLogin::<CS>::start(
|
||||
&mut server_private_keyshare_and_nonce_rng,
|
||||
&server_setup,
|
||||
Some(record),
|
||||
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(¶meters.KE1[..])
|
||||
.unwrap(),
|
||||
CredentialRequest::<CS>::deserialize(¶meters.KE1[..]).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
|
||||
None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
|
||||
@@ -685,9 +997,8 @@ fn test_ke2() -> Result<(), ProtocolError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ke3() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
fn test_ke3<CS: CipherSuite>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(tvs) {
|
||||
let client_login_start = [
|
||||
parameters.blind_login,
|
||||
parameters.client_private_keyshare,
|
||||
@@ -695,13 +1006,11 @@ fn test_ke3() -> Result<(), ProtocolError> {
|
||||
]
|
||||
.concat();
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut client_login_start_rng,
|
||||
¶meters.password,
|
||||
)?;
|
||||
let client_login_start_result =
|
||||
ClientLogin::<CS>::start(&mut client_login_start_rng, ¶meters.password)?;
|
||||
|
||||
let client_login_finish_result = client_login_start_result.state.finish(
|
||||
CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(¶meters.KE2[..])?,
|
||||
CredentialResponse::<CS>::deserialize(¶meters.KE2[..])?,
|
||||
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
|
||||
None => ClientLoginFinishParameters::WithContext(parameters.context),
|
||||
Some(ids) => {
|
||||
@@ -734,10 +1043,9 @@ fn test_ke3() -> Result<(), ProtocolError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
|
||||
fn test_server_login_finish<CS: CipherSuite>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(tvs) {
|
||||
let server_setup = ServerSetup::<CS>::deserialize(
|
||||
&[
|
||||
¶meters.oprf_seed[..],
|
||||
¶meters.server_private_key[..],
|
||||
@@ -746,8 +1054,8 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
.concat(),
|
||||
)?;
|
||||
|
||||
let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
|
||||
&get_password_file_bytes(¶meters)?[..],
|
||||
let record = ServerRegistration::<CS>::deserialize(
|
||||
&get_password_file_bytes::<CS>(¶meters)?[..],
|
||||
)?;
|
||||
|
||||
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
|
||||
@@ -758,12 +1066,11 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
let server_login_start_result = ServerLogin::<CS>::start(
|
||||
&mut server_private_keyshare_and_nonce_rng,
|
||||
&server_setup,
|
||||
Some(record),
|
||||
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(¶meters.KE1[..])
|
||||
.unwrap(),
|
||||
CredentialRequest::<CS>::deserialize(¶meters.KE1[..]).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
|
||||
None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
|
||||
@@ -786,10 +1093,9 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fake_vectors() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(FAKE_TEST_VECTORS) {
|
||||
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
|
||||
fn test_fake_vectors<CS: CipherSuite>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(tvs) {
|
||||
let server_setup = ServerSetup::<CS>::deserialize(
|
||||
&[
|
||||
¶meters.oprf_seed[..],
|
||||
¶meters.server_private_key[..],
|
||||
@@ -807,12 +1113,11 @@ fn test_fake_vectors() -> Result<(), ProtocolError> {
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
let server_login_start_result = ServerLogin::<CS>::start(
|
||||
&mut server_private_keyshare_and_nonce_rng,
|
||||
&server_setup,
|
||||
None,
|
||||
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(¶meters.KE1[..])
|
||||
.unwrap(),
|
||||
CredentialRequest::<CS>::deserialize(¶meters.KE1[..]).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
|
||||
None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::hash::Hash;
|
||||
use crate::map_to_curve::GroupWithMapToCurve;
|
||||
use crate::tests::mock_rng::CycleRng;
|
||||
use crate::{errors::*, group::Group, oprf};
|
||||
use crate::{errors::*, oprf};
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::GenericArray;
|
||||
use serde_json::Value;
|
||||
@@ -43,6 +45,29 @@ static OPRF_RISTRETTO255_SHA512: &[&str] = &[
|
||||
}
|
||||
"#,
|
||||
];
|
||||
#[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]
|
||||
@@ -61,20 +86,34 @@ fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests input -> blind, blinded_element
|
||||
#[test]
|
||||
fn test_blind() -> Result<(), ProtocolError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
fn tests() -> Result<(), ProtocolError> {
|
||||
test_blind::<RistrettoPoint, Sha512>(OPRF_RISTRETTO255_SHA512)?;
|
||||
test_evaluate::<RistrettoPoint>(OPRF_RISTRETTO255_SHA512)?;
|
||||
test_finalize::<RistrettoPoint, Sha512>(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<G: GroupWithMapToCurve, H: Hash>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
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 (token, blinded_element) =
|
||||
oprf::blind::<_, RistrettoPoint, Sha512>(¶meters.input, &mut rng)?;
|
||||
let (token, blinded_element) = oprf::blind::<_, G, H>(¶meters.input, &mut rng)?;
|
||||
|
||||
assert_eq!(
|
||||
¶meters.blind,
|
||||
&RistrettoPoint::scalar_as_bytes(token.blind).to_vec()
|
||||
);
|
||||
assert_eq!(¶meters.blind, &G::scalar_as_bytes(token.blind).to_vec());
|
||||
assert_eq!(
|
||||
¶meters.blinded_element,
|
||||
&blinded_element.to_arr().to_vec()
|
||||
@@ -84,16 +123,12 @@ fn test_blind() -> Result<(), ProtocolError> {
|
||||
}
|
||||
|
||||
// Tests sksm, blinded_element -> evaluation_element
|
||||
#[test]
|
||||
fn test_evaluate() -> Result<(), PakeError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
fn test_evaluate<G: GroupWithMapToCurve>(tvs: &[&str]) -> Result<(), PakeError> {
|
||||
for tv in tvs {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
|
||||
let evaluation_element = oprf::evaluate::<RistrettoPoint>(
|
||||
RistrettoPoint::from_element_slice(GenericArray::from_slice(
|
||||
¶meters.blinded_element,
|
||||
))
|
||||
.unwrap(),
|
||||
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(¶meters.sksm)).unwrap(),
|
||||
let evaluation_element = oprf::evaluate::<G>(
|
||||
G::from_element_slice(GenericArray::from_slice(¶meters.blinded_element)).unwrap(),
|
||||
&G::from_scalar_slice(GenericArray::from_slice(¶meters.sksm)).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -105,17 +140,14 @@ fn test_evaluate() -> Result<(), PakeError> {
|
||||
}
|
||||
|
||||
// Tests input, blind, evaluation_element -> output
|
||||
#[test]
|
||||
fn test_finalize() -> Result<(), ProtocolError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
fn test_finalize<G: GroupWithMapToCurve, H: Hash>(tvs: &[&str]) -> Result<(), ProtocolError> {
|
||||
for tv in tvs {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
|
||||
|
||||
let output = oprf::finalize::<RistrettoPoint, Sha512>(
|
||||
let output = oprf::finalize::<G, H>(
|
||||
¶meters.input,
|
||||
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(¶meters.blind))?,
|
||||
RistrettoPoint::from_element_slice(GenericArray::from_slice(
|
||||
¶meters.evaluation_element,
|
||||
))?,
|
||||
&G::from_scalar_slice(GenericArray::from_slice(¶meters.blind))?,
|
||||
G::from_element_slice(GenericArray::from_slice(¶meters.evaluation_element))?,
|
||||
)?;
|
||||
|
||||
assert_eq!(¶meters.output, &output.to_vec());
|
||||
|
||||
Reference in New Issue
Block a user