Make oprf::generate_oprf1 generic in the Digest, as long as it matches the hash-to-curve intake of the group

We used to have three problems:
- overuse of the <Sha256 as Digest>::OutputSize, which is just, well, U32. Sometimes used as a parameter (as in generate_oprf1), sometimes as a constant (as in generate_oprf3).
- the `hash_to_curve` operation for `RistrettoPoint` which requires 64 bits of input entropy, is fed 64 bits of which the last 32 are zero,
- the `hash_to_curve` operation for `Curve25519Point` which requires 32 bits of input entropy, is fed 64 bits of which the last 32 are discarded,

This corrects all three and uses U32 where the size of the digest is not meant to be a constraint.

Addresses #15 partially.
This commit is contained in:
François Garillot
2020-07-03 18:28:34 -04:00
parent 45ea7b6e84
commit 41cd80ccb5
12 changed files with 99 additions and 63 deletions
Generated
+1
View File
@@ -202,6 +202,7 @@ dependencies = [
"aead 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"curve25519-dalek 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hkdf 0.9.0-alpha.0 (registry+https://github.com/rust-lang/crates.io-index)",
+1
View File
@@ -15,6 +15,7 @@ slow-hash = ["scrypt"]
[dependencies]
aead = "0.3.1"
curve25519-dalek = "2.1.0"
digest = "0.9"
generic-array = "0.14.2"
hkdf = "0.9.0-alpha.0"
hmac = "0.8.0"
+10 -5
View File
@@ -9,17 +9,22 @@ use crate::{
errors::InternalPakeError,
group::Group,
keypair::{Key, KeyPair},
oprf::HkdfDigest,
slow_hash::SlowHash,
};
use generic_array::typenum::{U32, U64};
use digest::FixedOutput;
use generic_array::typenum::{U32};
use rand_core::{CryptoRng, RngCore};
/// Configures the underlying primitives used in OPAQUE
/// * Group: a finite cyclic group along with a point representation
/// * KeyFormat: a keypair type composed of public and private components
/// * SlowHash: a slow hashing function, typically used for password hashing
/// * `Digest`: a digest suitable for use in an Hkdf, with an output length equal
/// to the input of the hash-to-curve function of the `Group` parameter.
/// * `Group`: a finite cyclic group along with a point representation
/// * `KeyFormat`: a keypair type composed of public and private components
/// * `SlowHash`: a slow hashing function, typically used for password hashing
pub trait CipherSuite {
type Group: Group<ScalarLen = U32, UniformBytesLen = U64>;
type Digest: HkdfDigest;
type Group: Group<ScalarLen = U32, UniformBytesLen = <Self::Digest as FixedOutput>::OutputSize>;
type KeyFormat: KeyPair<Repr = Key> + PartialEq;
type SlowHash: SlowHash;
+14 -22
View File
@@ -11,14 +11,16 @@ use generic_array::{
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use sha2::Sha256;
// Constant string used as salt for HKDF computation
const STR_ENVU: &[u8] = b"EnvU";
/// The length of the "export key" output by the client registration
/// and login finish steps
pub(crate) type ExportKeySize = <Sha256 as Digest>::OutputSize;
pub(crate) type ExportKeySize = U32;
const NONCE_LEN: usize = 32;
/// This struct is an instantiation of the envelope as described in
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
@@ -33,42 +35,32 @@ pub(crate) type ExportKeySize = <Sha256 as Digest>::OutputSize;
pub(crate) struct Envelope {
nonce: Vec<u8>,
ciphertext: Vec<u8>,
hmac: Vec<u8>,
hmac: GenericArray<u8, U32>,
}
type NonceLen = U32;
impl Envelope {
/// The additional number of bytes added to the plaintext
pub(crate) fn additional_size() -> usize {
Self::nonce_size() + <Sha256 as Digest>::OutputSize::to_usize()
NONCE_LEN + U32::to_usize()
}
fn hmac_key_size() -> usize {
<Sha256 as Digest>::OutputSize::to_usize()
U32::to_usize()
}
fn hmac_size() -> usize {
<Sha256 as Digest>::OutputSize::to_usize()
}
fn nonce_size() -> usize {
NonceLen::to_usize()
U32::to_usize()
}
fn export_key_size() -> usize {
ExportKeySize::to_usize()
}
pub(crate) fn new(
nonce: Vec<u8>,
ciphertext: Vec<u8>,
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
) -> Self {
pub(crate) fn new(nonce: Vec<u8>, ciphertext: Vec<u8>, hmac: GenericArray<u8, U32>) -> Self {
Self {
nonce,
ciphertext,
hmac: hmac.to_vec(),
hmac,
}
}
@@ -76,13 +68,13 @@ impl Envelope {
/// nonce | ciphertext | hmac
/// nonce_size bytes | variable length | hmac_size bytes
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let ciphertext_start = Self::nonce_size();
let ciphertext_start = NONCE_LEN;
let ciphertext_end = bytes.len() - Self::hmac_size();
Ok(Self::new(
bytes[..ciphertext_start].to_vec(),
bytes[ciphertext_start..ciphertext_end].to_vec(),
GenericArray::from_slice(&bytes[ciphertext_end..]),
GenericArray::clone_from_slice(&bytes[ciphertext_end..]),
))
}
@@ -98,7 +90,7 @@ impl Envelope {
aad: &[u8],
rng: &mut R,
) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
let mut nonce = vec![0u8; Self::nonce_size()];
let mut nonce = vec![0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
let h = Hkdf::<Sha256>::new(Some(&nonce), &key);
@@ -121,7 +113,7 @@ impl Envelope {
hmac.update(&aad);
Ok((
Self::new(nonce, ciphertext.to_vec(), &hmac.finalize().into_bytes()),
Self::new(nonce, ciphertext.to_vec(), hmac.finalize().into_bytes()),
*GenericArray::from_slice(&export_key),
))
}
+1 -1
View File
@@ -140,7 +140,7 @@ impl Group for EdwardsPoint {
*GenericArray::from_slice(c.as_bytes())
}
type UniformBytesLen = U64;
type UniformBytesLen = U32;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
let mut result = [0u8; 32];
let mut counter = 0;
+4 -4
View File
@@ -9,7 +9,7 @@ use crate::{
sized_bytes_using_constant_and_try_from,
};
use generic_array::{
typenum::{U64, U96},
typenum::{U32, U64, U96},
GenericArray,
};
use hkdf::Hkdf;
@@ -199,9 +199,9 @@ struct TripleDHComponents {
// Consists of a shared secret, followed by two mac keys
type TripleDHDerivationResult = (
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, U32>,
GenericArray<u8, U32>,
GenericArray<u8, U32>,
);
// Internal function which takes the public and private components of the client and server keypairs, along
+10
View File
@@ -21,6 +21,7 @@
//! use opaque_ke::ciphersuite::CipherSuite;
//! struct Default;
//! impl CipherSuite for Default {
//! type Digest = sha2::Sha512;
//! type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -41,6 +42,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -72,6 +74,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -100,6 +103,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -131,6 +135,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -164,6 +169,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -205,6 +211,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -233,6 +240,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -276,6 +284,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -331,6 +340,7 @@
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Digest = sha2::Sha512;
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
+14 -2
View File
@@ -290,6 +290,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Digest = sha2::Sha512;
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyFormat = opaque_ke::keypair::X25519KeyPair;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -306,7 +307,11 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
let OprfClientBytes {
alpha,
blinding_factor,
} = oprf::generate_oprf1::<R, CS::Group>(&password, pepper, blinding_factor_rng)?;
} = oprf::generate_oprf1::<R, CS::Digest, CS::Group>(
&password,
pepper,
blinding_factor_rng,
)?;
Ok((
RegisterFirstMessage::<CS::Group> { alpha },
@@ -340,6 +345,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Digest = sha2::Sha512;
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyFormat = opaque_ke::keypair::X25519KeyPair;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -503,6 +509,7 @@ where
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Digest = sha2::Sha512;
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyFormat = opaque_ke::keypair::X25519KeyPair;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -549,6 +556,7 @@ where
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Digest = sha2::Sha512;
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyFormat = opaque_ke::keypair::X25519KeyPair;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -637,6 +645,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Digest = sha2::Sha512;
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyFormat = opaque_ke::keypair::X25519KeyPair;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -653,7 +662,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
let OprfClientBytes {
alpha,
blinding_factor,
} = oprf::generate_oprf1::<R, CS::Group>(&password, pepper, rng)?;
} = oprf::generate_oprf1::<R, CS::Digest, CS::Group>(&password, pepper, rng)?;
let (ke1_state, ke1_message) =
generate_ke1::<_, CS::KeyFormat>(alpha.to_arr().to_vec(), rng)?;
@@ -688,6 +697,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Digest = sha2::Sha512;
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyFormat = opaque_ke::keypair::X25519KeyPair;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -783,6 +793,7 @@ impl ServerLogin {
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Digest = sha2::Sha512;
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyFormat = opaque_ke::keypair::X25519KeyPair;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -852,6 +863,7 @@ impl ServerLogin {
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Digest = sha2::Sha512;
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyFormat = opaque_ke::keypair::X25519KeyPair;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
+38 -18
View File
@@ -4,29 +4,51 @@
// LICENSE file in the root directory of this source tree.
use crate::{errors::InternalPakeError, group::Group};
use generic_array::{typenum::U64, GenericArray};
use digest::{BlockInput, FixedOutput, Reset, Update};
use generic_array::{typenum::U32, ArrayLength, GenericArray};
use hkdf::Hkdf;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
pub(crate) struct OprfClientBytes<Grp: Group> {
pub(crate) alpha: Grp,
pub(crate) blinding_factor: Grp::Scalar,
}
/// The `HkDFDigest` trait specifies the interface required for a parameter of `hkdf::Hkdf`.
///
/// It's a convenience wrapper around [`Blockinput`], [`Update`], [`FixedOutput`], [`Reset`],
/// [`Clone`], and [`Default`] traits.
pub trait HkdfDigest: Update + BlockInput + FixedOutput + Reset + Default + Clone
where
Self::BlockSize: ArrayLength<u8>,
Self::OutputSize: ArrayLength<u8>,
{
}
impl<T> HkdfDigest for T
where
T: Update + BlockInput + FixedOutput + Reset + Default + Clone,
T::BlockSize: ArrayLength<u8>,
T::OutputSize: ArrayLength<u8>,
{
}
/// Computes the first step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
/// an HKDF computation.
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: Group<UniformBytesLen = U64>>(
pub(crate) fn generate_oprf1<
R: RngCore + CryptoRng,
D: HkdfDigest,
G: Group<UniformBytesLen = D::OutputSize>,
>(
input: &[u8],
pepper: Option<&[u8]>,
blinding_factor_rng: &mut R,
) -> Result<OprfClientBytes<G>, InternalPakeError> {
let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, &input);
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
let (hashed_input, _) = Hkdf::<D>::extract(pepper, &input);
let blinding_factor = G::random_scalar(blinding_factor_rng);
let alpha = G::hash_to_curve(GenericArray::from_slice(&curve_input)) * &blinding_factor;
let alpha = G::hash_to_curve(GenericArray::from_slice(&hashed_input)) * &blinding_factor;
Ok(OprfClientBytes {
alpha,
blinding_factor,
@@ -48,10 +70,10 @@ pub(crate) fn generate_oprf3<G: Group>(
input: &[u8],
point: G,
blinding_factor: &G::Scalar,
) -> Result<GenericArray<u8, <Sha256 as Digest>::OutputSize>, InternalPakeError> {
) -> Result<GenericArray<u8, U32>, InternalPakeError> {
let unblinded = point * &G::scalar_invert(&blinding_factor);
let ikm: Vec<u8> = [&unblinded.to_arr()[..], input].concat();
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
let (prk, _) = Hkdf::<sha2::Sha256>::extract(None, &ikm);
Ok(prk)
}
@@ -66,14 +88,14 @@ mod tests {
use generic_array::{arr, GenericArray};
use hkdf::Hkdf;
use rand_core::OsRng;
use sha2::{Digest, Sha256, Sha512};
fn prf(
input: &[u8],
oprf_key: &[u8; 32],
) -> GenericArray<u8, <RistrettoPoint as Group>::ElemLen> {
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&curve_input));
let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&hashed_input));
let scalar =
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
let res = point * scalar;
@@ -90,7 +112,7 @@ mod tests {
let OprfClientBytes {
alpha,
blinding_factor,
} = generate_oprf1::<_, RistrettoPoint>(&input[..], None, &mut rng)?;
} = generate_oprf1::<_, Sha512, RistrettoPoint>(&input[..], None, &mut rng)?;
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
@@ -111,17 +133,15 @@ mod tests {
let OprfClientBytes {
alpha,
blinding_factor,
} = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap();
} = generate_oprf1::<_, Sha512, RistrettoPoint>(&input, None, &mut rng).unwrap();
let res = generate_oprf3::<RistrettoPoint>(&input, alpha, &blinding_factor).unwrap();
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
let mut curve_input: Vec<u8> = Vec::new();
curve_input.extend_from_slice(&hashed_input);
curve_input.extend_from_slice(&[0u8; 32]);
let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
// This is because RistrettoPoint is on an obsolete sha2 version
let mut bits = [0u8; 64];
let mut hasher = sha2::Sha512::new();
hasher.update(&curve_input[..]);
Digest::update(&mut hasher, &hashed_input[..]);
bits.copy_from_slice(&hasher.finalize());
let point = RistrettoPoint::from_uniform_bytes(&bits);
+4 -11
View File
@@ -7,33 +7,26 @@
use crate::errors::InternalPakeError;
use generic_array::GenericArray;
use sha2::{Digest, Sha256};
use generic_array::{typenum::U32, GenericArray};
/// Used for the slow hashing function in OPAQUE
pub trait SlowHash {
/// Computes the slow hashing function
fn hash(
input: GenericArray<u8, <Sha256 as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError>;
fn hash(input: GenericArray<u8, U32>) -> Result<Vec<u8>, InternalPakeError>;
}
/// A no-op hash which simply returns its input
pub struct NoOpHash;
impl SlowHash for NoOpHash {
fn hash(
input: GenericArray<u8, <Sha256 as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
fn hash(input: GenericArray<u8, U32>) -> Result<Vec<u8>, InternalPakeError> {
Ok(input.to_vec())
}
}
#[cfg(feature = "slow-hash")]
impl SlowHash for scrypt::ScryptParams {
fn hash(
input: GenericArray<u8, <Sha256 as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
fn hash(input: GenericArray<u8, U32>) -> Result<Vec<u8>, InternalPakeError> {
let params = scrypt::ScryptParams::new(15, 8, 1).unwrap();
let mut output = [0u8; 32];
scrypt::scrypt(&input, &[], &params, &mut output)
+1
View File
@@ -23,6 +23,7 @@ use std::convert::TryFrom;
struct X255193dhNoSlowHash;
impl CipherSuite for X255193dhNoSlowHash {
type Digest = sha2::Sha256;
type Group = EdwardsPoint;
type KeyFormat = X25519KeyPair;
type SlowHash = NoOpHash;
+1
View File
@@ -21,6 +21,7 @@ use std::convert::TryFrom;
struct Default;
impl CipherSuite for Default {
type Digest = sha2::Sha512;
type Group = RistrettoPoint;
type KeyFormat = crate::keypair::X25519KeyPair;
type SlowHash = crate::slow_hash::NoOpHash;