Skip to content

Commit bdae28c

Browse files
committed
Initial Group rework
1 parent c09a230 commit bdae28c

File tree

11 files changed

+548
-385
lines changed

11 files changed

+548
-385
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ p256 = [
2323
"num-traits",
2424
"once_cell",
2525
"p256_",
26+
"sha2",
2627
]
27-
ristretto255 = []
28+
ristretto255 = ["sha2"]
2829
ristretto255_fiat_u32 = ["curve25519-dalek/fiat_u32_backend", "ristretto255"]
2930
ristretto255_fiat_u64 = ["curve25519-dalek/fiat_u64_backend", "ristretto255"]
3031
ristretto255_simd = ["curve25519-dalek/simd_backend", "ristretto255"]
@@ -49,6 +50,7 @@ rand_core = { version = "0.6", default-features = false }
4950
serde = { version = "1", default-features = false, features = [
5051
"derive",
5152
], optional = true }
53+
sha2 = { version = "0.10", default-features = false, optional = true }
5254
subtle = { version = "2.3", default-features = false }
5355
zeroize = { version = "1", default-features = false }
5456

src/group/expand.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use core::ops::Add;
99

1010
use digest::core_api::BlockSizeUser;
11-
use digest::{Digest, FixedOutputReset};
11+
use digest::{Digest, FixedOutputReset, HashMarker};
1212
use generic_array::sequence::Concat;
1313
use generic_array::typenum::{Unsigned, U1, U2};
1414
use generic_array::{ArrayLength, GenericArray};
@@ -30,7 +30,7 @@ fn xor<L: ArrayLength<u8>>(x: GenericArray<u8, L>, y: GenericArray<u8, L>) -> Ge
3030
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt>
3131
pub fn expand_message_xmd<
3232
'a,
33-
H: BlockSizeUser + Digest + FixedOutputReset,
33+
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
3434
L: ArrayLength<u8>,
3535
M: IntoIterator<Item = &'a [u8]>,
3636
D: ArrayLength<u8> + Add<U1>,

src/group/mod.rs

Lines changed: 74 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,63 @@ mod ristretto;
1717
use core::ops::{Add, Mul, Sub};
1818

1919
use digest::core_api::BlockSizeUser;
20-
use digest::{Digest, FixedOutputReset};
20+
use digest::{Digest, FixedOutputReset, HashMarker};
2121
use generic_array::typenum::U1;
2222
use generic_array::{ArrayLength, GenericArray};
2323
use rand_core::{CryptoRng, RngCore};
24+
#[cfg(feature = "ristretto255")]
25+
pub use ristretto::Ristretto255;
2426
use subtle::ConstantTimeEq;
2527
use zeroize::Zeroize;
2628

2729
use crate::{Error, Result};
2830

29-
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
30-
/// subgroup is noted additively — as in the draft RFC — in this trait.
31-
pub trait Group:
32-
Copy
33-
+ Sized
34-
+ ConstantTimeEq
35-
+ for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
36-
+ for<'a> Add<&'a Self, Output = Self>
37-
{
31+
/// Ciphersuite identifier for a combination of a [`Group`] and a
32+
/// [hash](HashMarker).
33+
pub trait Voprf<H: HashMarker> {
3834
/// The ciphersuite identifier as dictated by
3935
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
40-
const SUITE_ID: usize;
36+
const SUITE_ID: u16;
37+
}
38+
39+
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
40+
/// subgroup is noted additively — as in the draft RFC — in this trait.
41+
pub trait Group {
42+
/// Type representing a group element.
43+
type Elem: ConstantTimeEq
44+
+ Copy
45+
+ Sized
46+
+ Zeroize
47+
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Elem>
48+
+ for<'a> Add<&'a Self::Elem, Output = Self::Elem>;
49+
/// The byte length necessary to represent group elements
50+
type ElemLen: ArrayLength<u8> + 'static;
51+
52+
/// The type of base field scalars
53+
type Scalar: ConstantTimeEq
54+
+ Copy
55+
+ Zeroize
56+
+ for<'a> Add<&'a Self::Scalar, Output = Self::Scalar>
57+
+ for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>
58+
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Scalar>;
59+
/// The byte length necessary to represent scalars
60+
type ScalarLen: ArrayLength<u8> + 'static;
4161

4262
/// transforms a password and domain separation tag (DST) into a curve point
43-
fn hash_to_curve<H: BlockSizeUser + Digest + FixedOutputReset, D: ArrayLength<u8> + Add<U1>>(
63+
fn hash_to_curve<
64+
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
65+
D: ArrayLength<u8> + Add<U1>,
66+
>(
4467
msg: &[u8],
4568
dst: GenericArray<u8, D>,
46-
) -> Result<Self>
69+
) -> Result<Self::Elem>
4770
where
4871
<D as Add<U1>>::Output: ArrayLength<u8>;
4972

5073
/// Hashes a slice of pseudo-random bytes to a scalar
5174
fn hash_to_scalar<
5275
'a,
53-
H: BlockSizeUser + Digest + FixedOutputReset,
76+
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
5477
D: ArrayLength<u8> + Add<U1>,
5578
I: IntoIterator<Item = &'a [u8]>,
5679
>(
@@ -60,58 +83,32 @@ pub trait Group:
6083
where
6184
<D as Add<U1>>::Output: ArrayLength<u8>;
6285

63-
/// The type of base field scalars
64-
type Scalar: Zeroize
65-
+ Copy
66-
+ ConstantTimeEq
67-
+ for<'a> Add<&'a Self::Scalar, Output = Self::Scalar>
68-
+ for<'a> Sub<&'a Self::Scalar, Output = Self::Scalar>
69-
+ for<'a> Mul<&'a Self::Scalar, Output = Self::Scalar>;
70-
/// The byte length necessary to represent scalars
71-
type ScalarLen: ArrayLength<u8> + 'static;
86+
/// Get the base point for the group
87+
fn base_point() -> Self::Elem;
7288

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

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

91-
/// picks a scalar at random
92-
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
93-
/// Serializes a scalar to bytes
94-
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
95-
/// The multiplicative inverse of this scalar
96-
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
97-
98-
/// The byte length necessary to represent group elements
99-
type ElemLen: ArrayLength<u8> + 'static;
100-
10197
/// Return an element from its fixed-length bytes representation. This is
10298
/// the unchecked version, which does not check for deserializing the
10399
/// identity element
104-
fn from_element_slice_unchecked(element_bits: &GenericArray<u8, Self::ElemLen>)
105-
-> Result<Self>;
100+
fn from_element_slice_unchecked(
101+
element_bits: &GenericArray<u8, Self::ElemLen>,
102+
) -> Result<Self::Elem>;
106103

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

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

122119
/// Serializes the `self` group element
123-
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;
124-
125-
/// Get the base point for the group
126-
fn base_point() -> Self;
127-
128-
/// Returns if the group element is equal to the identity (1)
129-
fn is_identity(&self) -> bool {
130-
self.ct_eq(&<Self as Group>::identity()).into()
131-
}
120+
fn element_to_bytes(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen>;
132121

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

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

139-
/// Set the contents of self to the identity value
140-
fn zeroize(&mut self) {
141-
*self = <Self as Group>::identity();
128+
/// The multiplicative inverse of this scalar
129+
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
130+
131+
/// Return a scalar from its fixed-length bytes representation, without
132+
/// checking if the scalar is zero.
133+
fn from_scalar_slice_unchecked(
134+
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
135+
) -> Result<Self::Scalar>;
136+
137+
/// Return a scalar from its fixed-length bytes representation. If the
138+
/// scalar is zero, then return an error.
139+
fn from_scalar_slice<'a>(
140+
scalar_bits: impl Into<&'a GenericArray<u8, Self::ScalarLen>>,
141+
) -> Result<Self::Scalar> {
142+
let scalar = Self::from_scalar_slice_unchecked(scalar_bits.into())?;
143+
if scalar.ct_eq(&Self::scalar_zero()).into() {
144+
return Err(Error::ZeroScalarError);
145+
}
146+
Ok(scalar)
142147
}
148+
149+
/// Serializes a scalar to bytes
150+
fn scalar_to_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen>;
143151
}
144152

145153
#[cfg(test)]

src/group/p256.rs

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use core::ops::{Add, Div, Mul, Neg};
1717
use core::str::FromStr;
1818

1919
use digest::core_api::BlockSizeUser;
20-
use digest::{Digest, FixedOutputReset};
20+
use digest::{Digest, FixedOutputReset, HashMarker};
2121
use generic_array::typenum::{Unsigned, U1, U2, U32, U33, U48};
2222
use generic_array::{ArrayLength, GenericArray};
2323
use num_bigint::{BigInt, Sign};
@@ -29,27 +29,41 @@ use p256_::elliptic_curve::group::GroupEncoding;
2929
use p256_::elliptic_curve::ops::Reduce;
3030
use p256_::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
3131
use p256_::elliptic_curve::Field;
32-
use p256_::{AffinePoint, EncodedPoint, ProjectivePoint};
32+
use p256_::{AffinePoint, EncodedPoint, NistP256, ProjectivePoint};
3333
use rand_core::{CryptoRng, RngCore};
34+
use sha2::Sha256;
3435
use subtle::{Choice, ConditionallySelectable};
3536

36-
use super::Group;
37+
use super::{Group, Voprf};
3738
use crate::{Error, Result};
3839

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

44+
// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428
4345
#[cfg(feature = "p256")]
44-
impl Group for ProjectivePoint {
45-
const SUITE_ID: usize = 0x0003;
46+
impl Voprf<Sha256> for NistP256 {
47+
const SUITE_ID: u16 = 0x0003;
48+
}
49+
50+
// `cfg` here is only needed because of a bug in Rust's crate feature documentation. See: https://github.com/rust-lang/rust/issues/83428
51+
#[cfg(feature = "p256")]
52+
impl Group for NistP256 {
53+
type Elem = ProjectivePoint;
54+
type ElemLen = U33;
55+
type Scalar = p256_::Scalar;
56+
type ScalarLen = U32;
4657

4758
// Implements the `hash_to_curve()` function from
4859
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
49-
fn hash_to_curve<H: BlockSizeUser + Digest + FixedOutputReset, D: ArrayLength<u8> + Add<U1>>(
60+
fn hash_to_curve<
61+
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
62+
D: ArrayLength<u8> + Add<U1>,
63+
>(
5064
msg: &[u8],
5165
dst: GenericArray<u8, D>,
52-
) -> Result<Self>
66+
) -> Result<Self::Elem>
5367
where
5468
<D as Add<U1>>::Output: ArrayLength<u8>,
5569
{
@@ -102,7 +116,7 @@ impl Group for ProjectivePoint {
102116
// Implements the `HashToScalar()` function
103117
fn hash_to_scalar<
104118
'a,
105-
H: BlockSizeUser + Digest + FixedOutputReset,
119+
H: BlockSizeUser + Digest + FixedOutputReset + HashMarker,
106120
D: ArrayLength<u8> + Add<U1>,
107121
I: IntoIterator<Item = &'a [u8]>,
108122
>(
@@ -136,53 +150,49 @@ impl Group for ProjectivePoint {
136150
Ok(p256_::Scalar::from_be_bytes_reduced(result))
137151
}
138152

139-
type ElemLen = U33;
140-
type Scalar = p256_::Scalar;
141-
type ScalarLen = U32;
142-
143-
fn from_scalar_slice_unchecked(
144-
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
145-
) -> Result<Self::Scalar> {
146-
Ok(Self::Scalar::from_be_bytes_reduced(*scalar_bits))
147-
}
148-
149-
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
150-
Self::Scalar::random(rng)
151-
}
152-
153-
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
154-
scalar.into()
153+
fn base_point() -> Self::Elem {
154+
ProjectivePoint::generator()
155155
}
156156

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

161161
fn from_element_slice_unchecked(
162162
element_bits: &GenericArray<u8, Self::ElemLen>,
163-
) -> Result<Self> {
164-
Option::from(Self::from_bytes(element_bits)).ok_or(Error::PointError)
163+
) -> Result<Self::Elem> {
164+
Option::from(ProjectivePoint::from_bytes(element_bits)).ok_or(Error::PointError)
165165
}
166166

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

175-
fn base_point() -> Self {
176-
Self::generator()
177-
}
178-
179-
fn identity() -> Self {
180-
Self::identity()
175+
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
176+
Self::Scalar::random(rng)
181177
}
182178

183179
fn scalar_zero() -> Self::Scalar {
184180
Self::Scalar::zero()
185181
}
182+
183+
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
184+
scalar.invert().unwrap_or(Self::Scalar::zero())
185+
}
186+
187+
fn from_scalar_slice_unchecked(
188+
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
189+
) -> Result<Self::Scalar> {
190+
Ok(Self::Scalar::from_be_bytes_reduced(*scalar_bits))
191+
}
192+
193+
fn scalar_to_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
194+
scalar.into()
195+
}
186196
}
187197

188198
/// Corresponds to the hash_to_curve_simple_swu() function defined in

0 commit comments

Comments
 (0)