Group improvements (#28)

* Implement `ConstantTimeEq` for `Group` and `Group::Scalar`

* Changed `expand_message_xmd` `dst` parameter to `GenericArray<u8, D>`

* Remove allocation for `dst_prime` in `expand_message_xmd`

* Remove allocation for `msg_prime` and `b` in `expand_message_xmd`

* Remove unnecessary references in `expand_message_xmd`

* Return `GenericArray` from `expand_message_xmd`

* Oxidize loop in `expand_message_xmd`

* Remove allocation for `b` in `expand_message_xmd`

* Remove allocation for `xor`

* De-duplicate code in `expand_message_xmd`

* Remove unnecessary features

* Make ristretto optional and export all backends

* Make `rand` optional

* Fix wrong documentation

* Document `rand` feature

* Remove empty line

* Fix rustfmt

* Fix testing with other backends

* Iterate by value in `expand_message_xmd`

* Fix typo

* Rename `ristretto_x` features to `ristretto255_x`

* Remove `rand` re-export

* Improve `expand_message_xmd` loop

* Remove unnecessary `#[doc(cfg())]`

* Remove unnecessary `pub(crate)` for `mod p256`

* Fix `p256` only build

* Rename `serialize` feature to `serde`

* Fix missing `Cargo.toml` update
This commit is contained in:
daxpedda
2021-10-14 11:09:00 -07:00
committed by GitHub
parent 5ac52388ff
commit de91fafdb3
11 changed files with 198 additions and 163 deletions
+53 -43
View File
@@ -7,9 +7,13 @@
use crate::errors::InternalError;
use crate::serialization::i2osp;
use alloc::vec::Vec;
use core::ops::Add;
use digest::{BlockInput, Digest};
use generic_array::typenum::{Unsigned, U1, U2};
use generic_array::{
sequence::Concat,
typenum::{Unsigned, U1, U2},
ArrayLength, GenericArray,
};
// Computes ceil(x / y)
fn div_ceil(x: usize, y: usize) -> usize {
@@ -17,61 +21,65 @@ fn div_ceil(x: usize, y: usize) -> usize {
x / y + additive
}
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalError> {
if x.len() != y.len() {
return Err(InternalError::HashToCurveError);
}
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
fn xor<L: ArrayLength<u8>>(x: GenericArray<u8, L>, y: GenericArray<u8, L>) -> GenericArray<u8, L> {
x.into_iter().zip(y).map(|(x1, x2)| x1 ^ x2).collect()
}
/// Corresponds to the expand_message_xmd() function defined in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt>
pub fn expand_message_xmd<H: BlockInput + Digest>(
pub fn expand_message_xmd<
H: BlockInput + Digest,
L: ArrayLength<u8>,
D: ArrayLength<u8> + Add<U1>,
>(
msg: &[u8],
dst: &[u8],
len_in_bytes: usize,
) -> Result<Vec<u8>, InternalError> {
let ell = div_ceil(len_in_bytes, <H as Digest>::OutputSize::USIZE);
dst: GenericArray<u8, D>,
) -> Result<GenericArray<u8, L>, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
let digest_len = <H as Digest>::OutputSize::USIZE;
let ell = div_ceil(L::USIZE, digest_len);
if ell > 255 {
return Err(InternalError::HashToCurveError);
}
let dst_prime = [dst, &i2osp::<U1>(dst.len())?].concat();
let dst_prime = dst.concat(i2osp::<U1>(D::USIZE)?);
let z_pad = i2osp::<<H as BlockInput>::BlockSize>(0)?;
let l_i_b_str = i2osp::<U2>(len_in_bytes)?;
let msg_prime = [
&z_pad,
msg,
&l_i_b_str,
i2osp::<U1>(0)?.as_slice(),
&dst_prime,
]
.concat();
let mut b: Vec<Vec<u8>> = alloc::vec![H::digest(&msg_prime).to_vec()]; // b[0]
let l_i_b_str = i2osp::<U2>(L::USIZE)?;
let msg_0 = i2osp::<U1>(0)?;
let msg_prime =
core::array::IntoIter::new([z_pad.as_slice(), msg, &l_i_b_str, &msg_0, &dst_prime]);
let mut h = H::new();
h.update(&b[0]);
h.update(&i2osp::<U1>(1)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
// b[0]
let b_0 = msg_prime
.into_iter()
.fold(&mut h, |h, msg| {
h.update(msg);
h
})
.finalize_reset();
let mut b_i = GenericArray::default();
let mut uniform_bytes: Vec<u8> = Vec::new();
uniform_bytes.extend_from_slice(&b[1]);
let mut uniform_bytes = GenericArray::default();
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp::<U1>(i)?);
for (i, chunk) in (1..(ell + 1)).zip(uniform_bytes.chunks_mut(digest_len)) {
h.update(xor(b_0.clone(), b_i.clone()));
h.update(i2osp::<U1>(i)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[i]
uniform_bytes.extend_from_slice(&b[i]);
b_i = h.finalize_reset();
chunk.copy_from_slice(&b_i[..digest_len.min(chunk.len())]);
}
Ok(uniform_bytes[..len_in_bytes].to_vec())
Ok(uniform_bytes)
}
#[cfg(test)]
mod tests {
use generic_array::{
typenum::{U128, U32},
GenericArray,
};
struct Params {
msg: &'static str,
@@ -180,14 +188,16 @@ mod tests {
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
},
];
let dst = "QUUX-V01-CS02-with-expander";
let dst = GenericArray::from(*b"QUUX-V01-CS02-with-expander");
for tv in test_vectors {
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
tv.len_in_bytes,
)
let uniform_bytes = match tv.len_in_bytes {
32 => super::expand_message_xmd::<sha2::Sha256, U32, _>(tv.msg.as_bytes(), dst)
.map(|bytes| bytes.to_vec()),
128 => super::expand_message_xmd::<sha2::Sha256, U128, _>(tv.msg.as_bytes(), dst)
.map(|bytes| bytes.to_vec()),
_ => unimplemented!(),
}
.unwrap();
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
}
+35 -18
View File
@@ -7,17 +7,32 @@
//! Defines the Group trait to specify the underlying prime order group
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
feature = "p256",
))]
mod expand;
#[cfg(feature = "p256")]
#[cfg_attr(docsrs, doc(cfg(feature = "p256")))]
pub(crate) mod p256;
mod p256;
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
))]
mod ristretto;
use crate::errors::InternalError;
use core::ops::{Add, Mul, Sub};
use digest::{BlockInput, Digest};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, RngCore};
use generic_array::{typenum::U1, ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
use zeroize::Zeroize;
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
@@ -25,6 +40,7 @@ use zeroize::Zeroize;
pub trait Group:
Copy
+ Sized
+ ConstantTimeEq
+ for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
+ for<'a> Add<&'a Self, Output = Self>
{
@@ -33,18 +49,25 @@ pub trait Group:
const SUITE_ID: usize;
/// transforms a password and domain separation tag (DST) into a curve point
fn hash_to_curve<H: BlockInput + Digest>(msg: &[u8], dst: &[u8])
-> Result<Self, InternalError>;
fn hash_to_curve<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>;
/// Hashes a slice of pseudo-random bytes to a scalar
fn hash_to_scalar<H: BlockInput + Digest>(
fn hash_to_scalar<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
input: &[u8],
dst: &[u8],
) -> Result<Self::Scalar, InternalError>;
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>;
/// The type of base field scalars
type Scalar: Zeroize
+ Copy
+ ConstantTimeEq
+ for<'a> Add<&'a Self::Scalar, Output = Self::Scalar>
+ for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Scalar>;
@@ -63,7 +86,7 @@ pub trait Group:
scalar_bits: impl Into<&'a GenericArray<u8, Self::ScalarLen>>,
) -> Result<Self::Scalar, InternalError> {
let scalar = Self::from_scalar_slice_unchecked(scalar_bits.into())?;
if Self::ct_equal_scalar(&scalar, &Self::scalar_zero()) {
if scalar.ct_eq(&Self::scalar_zero()).into() {
return Err(InternalError::ZeroScalarError);
}
Ok(scalar)
@@ -93,7 +116,7 @@ pub trait Group:
) -> Result<Self, InternalError> {
let elem = Self::from_element_slice_unchecked(element_bits.into())?;
if Self::ct_equal(&elem, &<Self as Group>::identity()) {
if Self::ct_eq(&elem, &<Self as Group>::identity()).into() {
// found the identity element
return Err(InternalError::PointError);
}
@@ -109,7 +132,7 @@ pub trait Group:
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool {
self.ct_equal(&<Self as Group>::identity())
self.ct_eq(&<Self as Group>::identity()).into()
}
/// Returns the identity group element
@@ -118,12 +141,6 @@ pub trait Group:
/// Returns the scalar representing zero
fn scalar_zero() -> Self::Scalar;
/// Compares in constant time if the group elements are equal
fn ct_equal(&self, other: &Self) -> bool;
/// Compares in constant time if the scalars are equal
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool;
/// Set the contents of self to the identity value
fn zeroize(&mut self) {
*self = <Self as Group>::identity();
+27 -26
View File
@@ -18,7 +18,7 @@ use crate::errors::InternalError;
use core::ops::{Add, Div, Mul, Neg};
use core::str::FromStr;
use digest::{BlockInput, Digest};
use generic_array::typenum::{U32, U33};
use generic_array::typenum::{Unsigned, U1, U2, U32, U33, U48};
use generic_array::{ArrayLength, GenericArray};
use num_bigint::{BigInt, Sign};
use num_integer::Integer;
@@ -29,21 +29,26 @@ use p256_::elliptic_curve::group::GroupEncoding;
use p256_::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
use p256_::elliptic_curve::Field;
use p256_::{AffinePoint, EncodedPoint, ProjectivePoint};
use rand::{CryptoRng, RngCore};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use rand_core::{CryptoRng, RngCore};
use subtle::{Choice, ConditionallySelectable};
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
// `L: 48`
pub const L: usize = 48;
pub type L = U48;
#[cfg(feature = "p256")]
impl Group for ProjectivePoint {
const SUITE_ID: usize = 0x0003;
// Implements the `hash_to_curve()` function from
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
fn hash_to_curve<H: BlockInput + Digest>(
fn hash_to_curve<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: &[u8],
) -> Result<Self, InternalError> {
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
// 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(|| {
@@ -69,11 +74,12 @@ impl Group for ProjectivePoint {
// `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 = super::expand::expand_message_xmd::<H>(msg, dst, 2 * L)?;
let uniform_bytes =
super::expand::expand_message_xmd::<H, <L as Mul<U2>>::Output, _>(msg, dst)?;
// hash to curve
let (q0x, q0y) = hash_to_curve_simple_swu(&uniform_bytes[..L], &A, &B, &P, &Z);
let (q1x, q1y) = hash_to_curve_simple_swu(&uniform_bytes[L..], &A, &B, &P, &Z);
let (q0x, q0y) = hash_to_curve_simple_swu(&uniform_bytes[..L::USIZE], &A, &B, &P, &Z);
let (q1x, q1y) = hash_to_curve_simple_swu(&uniform_bytes[L::USIZE..], &A, &B, &P, &Z);
// convert to `p256` types
let p0 = AffinePoint::from_encoded_point(&EncodedPoint::from_affine_coordinates(
@@ -91,10 +97,13 @@ impl Group for ProjectivePoint {
// Implements the `HashToScalar()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.3
fn hash_to_scalar<H: BlockInput + Digest>(
fn hash_to_scalar<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
input: &[u8],
dst: &[u8],
) -> Result<Self::Scalar, InternalError> {
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
// 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`
const N: Lazy<BigInt> = Lazy::new(|| {
@@ -106,7 +115,7 @@ impl Group for ProjectivePoint {
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
// `HashToScalar` is `hash_to_field`
let uniform_bytes = super::expand::expand_message_xmd::<H>(input, dst, L)?;
let uniform_bytes = super::expand::expand_message_xmd::<H, L, _>(input, dst)?;
let bytes = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes)
.mod_floor(&N)
.to_bytes_be()
@@ -164,14 +173,6 @@ impl Group for ProjectivePoint {
fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
}
fn ct_equal(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool {
s1.ct_eq(s2).into()
}
}
/// Corresponds to the hash_to_curve_simple_swu() function defined in
@@ -431,6 +432,7 @@ fn hash_to_curve_simple_swu<N: ArrayLength<u8>>(
#[cfg(test)]
mod tests {
use super::*;
use generic_array::typenum::U96;
struct Params {
msg: &'static str,
@@ -531,13 +533,12 @@ mod tests {
q1y: "f6ed88a7aab56a488100e6f1174fa9810b47db13e86be999644922961206e184",
},
];
let dst = "QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_";
let dst = GenericArray::from(*b"QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_");
for tv in test_vectors {
let uniform_bytes = super::super::expand::expand_message_xmd::<sha2::Sha256>(
let uniform_bytes = super::super::expand::expand_message_xmd::<sha2::Sha256, U96, _>(
tv.msg.as_bytes(),
dst.as_bytes(),
96,
dst,
)
.unwrap();
+27 -19
View File
@@ -8,6 +8,7 @@
use super::Group;
use crate::errors::InternalError;
use core::convert::TryInto;
use core::ops::Add;
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint},
@@ -15,21 +16,33 @@ use curve25519_dalek::{
traits::Identity,
};
use digest::{BlockInput, Digest};
use generic_array::{typenum::U32, GenericArray};
use rand::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
use generic_array::{
typenum::{U1, U32, U64},
ArrayLength, GenericArray,
};
use rand_core::{CryptoRng, RngCore};
/// The implementation of such a subgroup for Ristretto
#[cfg(any(
feature = "ristretto255_u64",
feature = "ristretto255_u32",
feature = "ristretto255_fiat_u64",
feature = "ristretto255_fiat_u32",
feature = "ristretto255_simd",
))]
impl Group for RistrettoPoint {
const SUITE_ID: usize = 0x0001;
// Implements the `hash_to_ristretto255()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
fn hash_to_curve<H: BlockInput + Digest>(
fn hash_to_curve<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
msg: &[u8],
dst: &[u8],
) -> Result<Self, InternalError> {
let uniform_bytes = super::expand::expand_message_xmd::<H>(msg, dst, 64)?;
dst: GenericArray<u8, D>,
) -> Result<Self, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
let uniform_bytes = super::expand::expand_message_xmd::<H, U64, _>(msg, dst)?;
Ok(RistrettoPoint::from_uniform_bytes(
uniform_bytes
@@ -41,11 +54,14 @@ impl Group for RistrettoPoint {
// Implements the `HashToScalar()` function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1
fn hash_to_scalar<H: BlockInput + Digest>(
fn hash_to_scalar<H: BlockInput + Digest, D: ArrayLength<u8> + Add<U1>>(
input: &[u8],
dst: &[u8],
) -> Result<Self::Scalar, InternalError> {
let uniform_bytes = super::expand::expand_message_xmd::<H>(input, dst, 64)?;
dst: GenericArray<u8, D>,
) -> Result<Self::Scalar, InternalError>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
let uniform_bytes = super::expand::expand_message_xmd::<H, U64, _>(input, dst)?;
Ok(Scalar::from_bytes_mod_order_wide(
uniform_bytes
@@ -121,12 +137,4 @@ impl Group for RistrettoPoint {
fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
}
fn ct_equal(&self, other: &Self) -> bool {
ConstantTimeEq::ct_eq(self, other).into()
}
fn ct_equal_scalar(s1: &Self::Scalar, s2: &Self::Scalar) -> bool {
ConstantTimeEq::ct_eq(s1, s2).into()
}
}