Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group trait experiment #49

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ p256 = [
"num-traits",
"once_cell",
"p256_",
"sha2",
]
ristretto255 = []
ristretto255 = ["sha2"]
ristretto255_fiat_u32 = ["curve25519-dalek/fiat_u32_backend", "ristretto255"]
ristretto255_fiat_u64 = ["curve25519-dalek/fiat_u64_backend", "ristretto255"]
ristretto255_simd = ["curve25519-dalek/simd_backend", "ristretto255"]
Expand All @@ -49,6 +50,7 @@ rand_core = { version = "0.6", default-features = false }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
sha2 = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3", default-features = false }
zeroize = { version = "1", default-features = false }

Expand Down
4 changes: 2 additions & 2 deletions src/group/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use core::ops::Add;

use digest::core_api::BlockSizeUser;
use digest::{Digest, FixedOutputReset};
use digest::{Digest, FixedOutputReset, HashMarker};
use generic_array::sequence::Concat;
use generic_array::typenum::{Unsigned, U1, U2};
use generic_array::{ArrayLength, GenericArray};
Expand All @@ -30,7 +30,7 @@ fn xor<L: ArrayLength<u8>>(x: GenericArray<u8, L>, y: GenericArray<u8, L>) -> Ge
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt>
pub fn expand_message_xmd<
'a,
H: BlockSizeUser + Digest + FixedOutputReset,
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
L: ArrayLength<u8>,
M: IntoIterator<Item = &'a [u8]>,
D: ArrayLength<u8> + Add<U1>,
Expand Down
140 changes: 74 additions & 66 deletions src/group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,63 @@ mod ristretto;
use core::ops::{Add, Mul, Sub};

use digest::core_api::BlockSizeUser;
use digest::{Digest, FixedOutputReset};
use digest::{Digest, FixedOutputReset, HashMarker};
use generic_array::typenum::U1;
use generic_array::{ArrayLength, GenericArray};
use rand_core::{CryptoRng, RngCore};
#[cfg(feature = "ristretto255")]
pub use ristretto::Ristretto255;
use subtle::ConstantTimeEq;
use zeroize::Zeroize;

use crate::{Error, Result};

/// 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
+ ConstantTimeEq
+ for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
+ for<'a> Add<&'a Self, Output = Self>
{
/// Ciphersuite identifier for a combination of a [`Group`] and a
/// [hash](HashMarker).
pub trait Voprf<H: HashMarker> {
/// The ciphersuite identifier as dictated by
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
const SUITE_ID: usize;
const SUITE_ID: u16;
}

/// 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 {
/// Type representing a group element.
type Elem: ConstantTimeEq
+ Copy
+ Sized
+ Zeroize
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Elem>
+ for<'a> Add<&'a Self::Elem, Output = Self::Elem>;
/// The byte length necessary to represent group elements
type ElemLen: ArrayLength<u8> + 'static;

/// The type of base field scalars
type Scalar: ConstantTimeEq
+ Copy
+ Zeroize
+ 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>;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8> + 'static;

/// transforms a password and domain separation tag (DST) into a curve point
fn hash_to_curve<H: BlockSizeUser + Digest + FixedOutputReset, D: ArrayLength<u8> + Add<U1>>(
fn hash_to_curve<
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
D: ArrayLength<u8> + Add<U1>,
>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self>
) -> Result<Self::Elem>
where
<D as Add<U1>>::Output: ArrayLength<u8>;

/// Hashes a slice of pseudo-random bytes to a scalar
fn hash_to_scalar<
'a,
H: BlockSizeUser + Digest + FixedOutputReset,
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
D: ArrayLength<u8> + Add<U1>,
I: IntoIterator<Item = &'a [u8]>,
>(
Expand All @@ -60,58 +83,32 @@ pub trait Group:
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>;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8> + 'static;
/// Get the base point for the group
fn base_point() -> Self::Elem;

/// Return a scalar from its fixed-length bytes representation, without
/// checking if the scalar is zero.
fn from_scalar_slice_unchecked(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar>;
/// Returns the identity group element
fn identity() -> Self::Elem;

/// Return a scalar from its fixed-length bytes representation. If the
/// scalar is zero, then return an error.
fn from_scalar_slice<'a>(
scalar_bits: impl Into<&'a GenericArray<u8, Self::ScalarLen>>,
) -> Result<Self::Scalar> {
let scalar = Self::from_scalar_slice_unchecked(scalar_bits.into())?;
if scalar.ct_eq(&Self::scalar_zero()).into() {
return Err(Error::ZeroScalarError);
}
Ok(scalar)
/// Returns if the group element is equal to the identity (1)
fn is_identity(elem: Self::Elem) -> bool {
elem.ct_eq(&<Self as Group>::identity()).into()
}

/// 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. This is
/// the unchecked version, which does not check for deserializing the
/// identity element
fn from_element_slice_unchecked(element_bits: &GenericArray<u8, Self::ElemLen>)
-> Result<Self>;
fn from_element_slice_unchecked(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self::Elem>;

/// Return an element from its fixed-length bytes representation. If the
/// element is the identity element, return an error.
fn from_element_slice<'a>(
element_bits: impl Into<&'a GenericArray<u8, Self::ElemLen>>,
) -> Result<Self> {
) -> Result<Self::Elem> {
let elem = Self::from_element_slice_unchecked(element_bits.into())?;

if Self::ct_eq(&elem, &<Self as Group>::identity()).into() {
if elem.ct_eq(&Self::identity()).into() {
// found the identity element
return Err(Error::PointError);
}
Expand All @@ -120,26 +117,37 @@ pub trait Group:
}

/// Serializes the `self` group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;

/// Get the base point for the group
fn base_point() -> Self;

/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool {
self.ct_eq(&<Self as Group>::identity()).into()
}
fn element_to_bytes(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen>;

/// Returns the identity group element
fn identity() -> Self;
/// picks a scalar at random
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;

/// Returns the scalar representing zero
fn scalar_zero() -> Self::Scalar;

/// Set the contents of self to the identity value
fn zeroize(&mut self) {
*self = <Self as Group>::identity();
/// The multiplicative inverse of this scalar
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;

/// Return a scalar from its fixed-length bytes representation, without
/// checking if the scalar is zero.
fn from_scalar_slice_unchecked(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar>;

/// Return a scalar from its fixed-length bytes representation. If the
/// scalar is zero, then return an error.
fn from_scalar_slice<'a>(
scalar_bits: impl Into<&'a GenericArray<u8, Self::ScalarLen>>,
) -> Result<Self::Scalar> {
let scalar = Self::from_scalar_slice_unchecked(scalar_bits.into())?;
if scalar.ct_eq(&Self::scalar_zero()).into() {
return Err(Error::ZeroScalarError);
}
Ok(scalar)
}

/// Serializes a scalar to bytes
fn scalar_to_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
}

#[cfg(test)]
Expand Down
82 changes: 46 additions & 36 deletions src/group/p256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use core::ops::{Add, Div, Mul, Neg};
use core::str::FromStr;

use digest::core_api::BlockSizeUser;
use digest::{Digest, FixedOutputReset};
use digest::{Digest, FixedOutputReset, HashMarker};
use generic_array::typenum::{Unsigned, U1, U2, U32, U33, U48};
use generic_array::{ArrayLength, GenericArray};
use num_bigint::{BigInt, Sign};
Expand All @@ -29,27 +29,41 @@ use p256_::elliptic_curve::group::GroupEncoding;
use p256_::elliptic_curve::ops::Reduce;
use p256_::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
use p256_::elliptic_curve::Field;
use p256_::{AffinePoint, EncodedPoint, ProjectivePoint};
use p256_::{AffinePoint, EncodedPoint, NistP256, ProjectivePoint};
use rand_core::{CryptoRng, RngCore};
use sha2::Sha256;
use subtle::{Choice, ConditionallySelectable};

use super::Group;
use super::{Group, Voprf};
use crate::{Error, Result};

// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
// `L: 48`
pub type L = U48;

// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428
#[cfg(feature = "p256")]
impl Group for ProjectivePoint {
const SUITE_ID: usize = 0x0003;
impl Voprf<Sha256> for NistP256 {
const SUITE_ID: u16 = 0x0003;
}

// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428
#[cfg(feature = "p256")]
impl Group for NistP256 {
type Elem = ProjectivePoint;
type ElemLen = U33;
type Scalar = p256_::Scalar;
type ScalarLen = U32;

// 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: BlockSizeUser + Digest + FixedOutputReset, D: ArrayLength<u8> + Add<U1>>(
fn hash_to_curve<
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
D: ArrayLength<u8> + Add<U1>,
>(
msg: &[u8],
dst: GenericArray<u8, D>,
) -> Result<Self>
) -> Result<Self::Elem>
where
<D as Add<U1>>::Output: ArrayLength<u8>,
{
Expand Down Expand Up @@ -102,7 +116,7 @@ impl Group for ProjectivePoint {
// Implements the `HashToScalar()` function
fn hash_to_scalar<
'a,
H: BlockSizeUser + Digest + FixedOutputReset,
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
D: ArrayLength<u8> + Add<U1>,
I: IntoIterator<Item = &'a [u8]>,
>(
Expand Down Expand Up @@ -136,53 +150,49 @@ impl Group for ProjectivePoint {
Ok(p256_::Scalar::from_be_bytes_reduced(result))
}

type ElemLen = U33;
type Scalar = p256_::Scalar;
type ScalarLen = U32;

fn from_scalar_slice_unchecked(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar> {
Ok(Self::Scalar::from_be_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 base_point() -> Self::Elem {
ProjectivePoint::generator()
}

fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert().unwrap_or(Self::Scalar::zero())
fn identity() -> Self::Elem {
ProjectivePoint::identity()
}

fn from_element_slice_unchecked(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self> {
Option::from(Self::from_bytes(element_bits)).ok_or(Error::PointError)
) -> Result<Self::Elem> {
Option::from(ProjectivePoint::from_bytes(element_bits)).ok_or(Error::PointError)
}

fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
let bytes = self.to_affine().to_encoded_point(true);
fn element_to_bytes(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen> {
let bytes = elem.to_affine().to_encoded_point(true);
let bytes = bytes.as_bytes();
let mut result = GenericArray::default();
result[..bytes.len()].copy_from_slice(bytes);
result
}

fn base_point() -> Self {
Self::generator()
}

fn identity() -> Self {
Self::identity()
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Self::Scalar::random(rng)
}

fn scalar_zero() -> Self::Scalar {
Self::Scalar::zero()
}

fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert().unwrap_or(Self::Scalar::zero())
}

fn from_scalar_slice_unchecked(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar> {
Ok(Self::Scalar::from_be_bytes_reduced(*scalar_bits))
}

fn scalar_to_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
scalar.into()
}
}

/// Corresponds to the hash_to_curve_simple_swu() function defined in
Expand Down
Loading