diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0f00ab7..f843217 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 0c4d546..6648185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs index 9c90601..1e0987b 100644 --- a/src/ciphersuite.rs +++ b/src/ciphersuite.rs @@ -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::OutputSize>; + type Group: GroupWithMapToCurve; /// A key exchange protocol type KeyExchange: KeyExchange; /// The main hash function use (for HKDF computations and hashing transcripts) diff --git a/src/group/mod.rs b/src/group/mod.rs new file mode 100644 index 0000000..1c6ab83 --- /dev/null +++ b/src/group/mod.rs @@ -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 ::Scalar, Output = Self> { + /// The type of base field scalars + type Scalar: Zeroize + Copy; + /// The byte length necessary to represent scalars + type ScalarLen: ArrayLength + 'static; + /// Return a scalar from its fixed-length bytes representation + fn from_scalar_slice( + scalar_bits: &GenericArray, + ) -> Result; + /// picks a scalar at random + fn random_nonzero_scalar(rng: &mut R) -> Self::Scalar; + /// Serializes a scalar to bytes + fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray; + /// 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 + 'static; + /// Return an element from its fixed-length bytes representation + fn from_element_slice( + element_bits: &GenericArray, + ) -> Result; + /// Serializes the `self` group element + fn to_arr(&self) -> GenericArray; + + /// 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; + + /// Hashes a slice of pseudo-random bytes of the correct length to a curve point + fn hash_to_curve( + uniform_bytes: &GenericArray, + ) -> Result; + + /// 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) -> 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; +} diff --git a/src/group/p256.rs b/src/group/p256.rs new file mode 100644 index 0000000..750fa6d --- /dev/null +++ b/src/group/p256.rs @@ -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 = Lazy::new(|| { + BigInt::from_str( + "115792089210356248762697446949407573530086143415290314195533631308867097853951", + ) + .unwrap() +}); +// `A: -3` +const A: Lazy = Lazy::new(|| BigInt::from(-3)); +// `B: 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b` +const B: Lazy = Lazy::new(|| { + BigInt::parse_bytes( + b"5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", + 16, + ) + .unwrap() +}); +// `L: 48` +pub const L: usize = 48; +// `Z: -10` +const Z: Lazy = 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 = 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, + ) -> Result { + Ok(Self::Scalar::from_bytes_reduced(scalar_bits)) + } + + fn random_nonzero_scalar(rng: &mut R) -> Self::Scalar { + Self::Scalar::random(rng) + } + + fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray { + scalar.into() + } + + fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar { + scalar.invert().unwrap_or(Self::Scalar::zero()) + } + + fn from_element_slice( + element_bits: &GenericArray, + ) -> Result { + Option::from(Self::from_bytes(element_bits)).ok_or(InternalPakeError::PointError) + } + + fn to_arr(&self) -> GenericArray { + 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, + ) -> Result { + // 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) -> 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 +/// +#[allow(clippy::many_single_char_names)] +fn map_to_curve_simple_swu>( + u: &BigInt, + a: &BigInt, + b: &BigInt, + p: &BigInt, + z: &BigInt, +) -> (GenericArray, GenericArray) { + #[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 + 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> 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 + /// + 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 + /// + 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 + /// + 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>(&self) -> GenericArray { + 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 = 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::( + 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())); + } + } +} diff --git a/src/group.rs b/src/group/ristretto.rs similarity index 58% rename from src/group.rs rename to src/group/ristretto.rs index 8a06b87..b799c19 100644 --- a/src/group.rs +++ b/src/group/ristretto.rs @@ -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 ::Scalar, Output = Self> { - /// The type of base field scalars - type Scalar: Zeroize + Copy; - /// The byte length necessary to represent scalars - type ScalarLen: ArrayLength + 'static; - /// Return a scalar from its fixed-length bytes representation - fn from_scalar_slice( - scalar_bits: &GenericArray, - ) -> Result; - /// picks a scalar at random - fn random_nonzero_scalar(rng: &mut R) -> Self::Scalar; - /// Serializes a scalar to bytes - fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray; - /// 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 + 'static; - /// Return an element from its fixed-length bytes representation - fn from_element_slice( - element_bits: &GenericArray, - ) -> Result; - /// Serializes the `self` group element - fn to_arr(&self) -> GenericArray; - - /// 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; - - /// Hashes a slice of pseudo-random bytes of the correct length to a curve point - fn hash_to_curve( - uniform_bytes: &GenericArray, - ) -> Result; - - /// 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) -> 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 { diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs index 91dba53..616ec8c 100644 --- a/src/key_exchange/tripledh.rs +++ b/src/key_exchange/tripledh.rs @@ -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 KeyExchange for TripleDH { } fn ke2_message_size() -> usize { - NonceLen::to_usize() + KEY_LEN + <::OutputSize as Unsigned>::to_usize() + NonceLen::to_usize() + + ::ElemLen::to_usize() + + <::OutputSize as Unsigned>::to_usize() } } @@ -257,13 +258,15 @@ pub struct Ke1Message { impl FromBytes for Ke1State { fn from_bytes(bytes: &[u8]) -> Result { + let key_len = ::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 ToBytes for Ke1Message { impl FromBytes for Ke1Message { fn from_bytes(ke1_message_bytes: &[u8]) -> Result { 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 + ::ElemLen::to_usize(), + "ke1_message nonce", + )?; Ok(Self { client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]), @@ -389,23 +395,24 @@ impl> Ke2Message { impl> FromBytes for Ke2Message { fn from_bytes(input: &[u8]) -> Result { + let key_len = ::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::::check_public_key(PublicKey::from_bytes( - &unchecked_server_e_pk[..KEY_LEN], + &unchecked_server_e_pk[..key_len], )?)?; Ok(Self { diff --git a/src/lib.rs b/src/lib.rs index b23d663..5a877b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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. //! diff --git a/src/map_to_curve.rs b/src/map_to_curve.rs index 5ba2811..630329a 100644 --- a/src/map_to_curve.rs +++ b/src/map_to_curve.rs @@ -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(msg: &[u8], dst: &[u8]) -> Result { + // 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::(msg, dst, 2 * crate::group::p256::L)?; + + ::hash_to_curve(&GenericArray::clone_from_slice(&uniform_bytes[..])) + .map_err(ProtocolError::from) + } + + fn hash_to_scalar(input: &[u8], dst: &[u8]) -> Result { + use num_bigint::{BigInt, Sign}; + use num_integer::Integer; + + let uniform_bytes = expand_message_xmd::(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; diff --git a/src/tests/opaque_test_vectors.rs b/src/tests/opaque_test_vectors.rs index e4f6aba..776f631 100644 --- a/src/tests/opaque_test_vectors.rs +++ b/src/tests/opaque_test_vectors.rs @@ -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, } -// 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::(&serde_json::from_str(rfc_to_json(x).as_str()).unwrap()) + }) .collect::>() }; } @@ -446,12 +745,12 @@ fn decode(values: &Value, key: &str) -> Option> { .and_then(|s| hex::decode(&s.to_string()).ok()) } -fn populate_test_vectors(values: &Value) -> TestVectorParameters { +fn populate_test_vectors(values: &Value) -> TestVectorParameters { TestVectorParameters { dummy_private_key: parse_default!( values, "client_private_key", - vec![0u8; as SizedBytes>::Len::to_usize()] + vec![0u8; 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, ProtocolError> { - let password_file = ServerRegistration::::finish( +fn get_password_file_bytes( + parameters: &TestVectorParameters, +) -> Result, ProtocolError> { + let password_file = ServerRegistration::::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::(RISTRETTO_TEST_VECTORS)?; + test_registration_response::(RISTRETTO_TEST_VECTORS)?; + test_registration_upload::(RISTRETTO_TEST_VECTORS)?; + test_ke1::(RISTRETTO_TEST_VECTORS)?; + test_ke2::(RISTRETTO_TEST_VECTORS)?; + test_ke3::(RISTRETTO_TEST_VECTORS)?; + test_server_login_finish::(RISTRETTO_TEST_VECTORS)?; + test_fake_vectors::(RISTRETTO_FAKE_TEST_VECTORS)?; + + #[cfg(feature = "p256")] + { + test_registration_request::(P256_TEST_VECTORS)?; + test_registration_response::(P256_TEST_VECTORS)?; + test_registration_upload::(P256_TEST_VECTORS)?; + test_ke1::(P256_TEST_VECTORS)?; + test_ke2::(P256_TEST_VECTORS)?; + test_ke3::(P256_TEST_VECTORS)?; + test_server_login_finish::(P256_TEST_VECTORS)?; + test_fake_vectors::(P256_FAKE_TEST_VECTORS)?; + } + + Ok(()) +} + +fn test_registration_request(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::::start( - &mut rng, - ¶meters.password, - )?; + ClientRegistration::::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::::deserialize( +fn test_registration_response(tvs: &[&str]) -> Result<(), ProtocolError> { + for parameters in rfc_to_params!(tvs) { + let server_setup = ServerSetup::::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::::start( - &server_setup, - RegistrationRequest::deserialize(¶meters.registration_request[..]).unwrap(), - ¶meters.credential_identifier, - )?; + let server_registration_start_result = ServerRegistration::::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(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::::start( - &mut rng, - ¶meters.password, - )?; + ClientRegistration::::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(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::::start( - &mut client_login_start_rng, - ¶meters.password, - )?; + let client_login_start_result = + ClientLogin::::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::::deserialize( +fn test_ke2(tvs: &[&str]) -> Result<(), ProtocolError> { + for parameters in rfc_to_params!(tvs) { + let server_setup = ServerSetup::::deserialize( &[ ¶meters.oprf_seed[..], ¶meters.server_private_key[..], @@ -638,8 +951,8 @@ fn test_ke2() -> Result<(), ProtocolError> { .concat(), )?; - let record = ServerRegistration::::deserialize( - &get_password_file_bytes(¶meters)?[..], + let record = ServerRegistration::::deserialize( + &get_password_file_bytes::(¶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::::start( + let server_login_start_result = ServerLogin::::start( &mut server_private_keyshare_and_nonce_rng, &server_setup, Some(record), - CredentialRequest::::deserialize(¶meters.KE1[..]) - .unwrap(), + CredentialRequest::::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(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::::start( - &mut client_login_start_rng, - ¶meters.password, - )?; + let client_login_start_result = + ClientLogin::::start(&mut client_login_start_rng, ¶meters.password)?; let client_login_finish_result = client_login_start_result.state.finish( - CredentialResponse::::deserialize(¶meters.KE2[..])?, + CredentialResponse::::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::::deserialize( +fn test_server_login_finish(tvs: &[&str]) -> Result<(), ProtocolError> { + for parameters in rfc_to_params!(tvs) { + let server_setup = ServerSetup::::deserialize( &[ ¶meters.oprf_seed[..], ¶meters.server_private_key[..], @@ -746,8 +1054,8 @@ fn test_server_login_finish() -> Result<(), ProtocolError> { .concat(), )?; - let record = ServerRegistration::::deserialize( - &get_password_file_bytes(¶meters)?[..], + let record = ServerRegistration::::deserialize( + &get_password_file_bytes::(¶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::::start( + let server_login_start_result = ServerLogin::::start( &mut server_private_keyshare_and_nonce_rng, &server_setup, Some(record), - CredentialRequest::::deserialize(¶meters.KE1[..]) - .unwrap(), + CredentialRequest::::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::::deserialize( +fn test_fake_vectors(tvs: &[&str]) -> Result<(), ProtocolError> { + for parameters in rfc_to_params!(tvs) { + let server_setup = ServerSetup::::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::::start( + let server_login_start_result = ServerLogin::::start( &mut server_private_keyshare_and_nonce_rng, &server_setup, None, - CredentialRequest::::deserialize(¶meters.KE1[..]) - .unwrap(), + CredentialRequest::::deserialize(¶meters.KE1[..]).unwrap(), ¶meters.credential_identifier, match parse_identifiers(parameters.client_identity, parameters.server_identity) { None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()), diff --git a/src/tests/voprf_test_vectors.rs b/src/tests/voprf_test_vectors.rs index 509a650..5dc5601 100644 --- a/src/tests/voprf_test_vectors.rs +++ b/src/tests/voprf_test_vectors.rs @@ -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> { 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::(OPRF_RISTRETTO255_SHA512)?; + test_evaluate::(OPRF_RISTRETTO255_SHA512)?; + test_finalize::(OPRF_RISTRETTO255_SHA512)?; + + #[cfg(feature = "p256")] + { + use p256_::ProjectivePoint; + use sha2::Sha256; + + test_blind::(OPRF_P256_SHA256)?; + test_evaluate::(OPRF_P256_SHA256)?; + test_finalize::(OPRF_P256_SHA256)?; + } + + Ok(()) +} + +// Tests input -> blind, blinded_element +fn test_blind(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(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::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::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(tvs: &[&str]) -> Result<(), ProtocolError> { + for tv in tvs { let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap()); - let output = oprf::finalize::( + let output = oprf::finalize::( ¶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());