SIGMA-I Key Exchange (#378)
* Move `KeGroup` to `KeyExchange::Group` - Introduce `KeyExchange::Hash`, which separates the OPRF hash from the one used in `KeyExchange`. - Remove `De/Serialize` requirement on key exchange messages and states, which forced a lot of where bounds on downstream users. - Rename `KeGroup` to `Group`. - Replace `D` generic for hash with `H`. * Use `voprf::derive_key()` directly * Implement SIGMA-I key exchange * Improve `KeyExchange` for SIGMA-I and Ed25519 * Implement EdDSA * Un-qualify some method calls * SIGMA-I: only include client identity in client mac * SIGMA-I: include server mac in client signature * Expose key exchange types in `crate` & move modules * Implement Ed25519ph * Document `ed25519` crate feature * Remove `ristretto255-voprf` crate feature * Adjust CI crate feature testing * Fix Rustdoc * Remove unnecessary generic parameters from SIGMA-I * Properly mark to-do's with TODO * Assorted fixes * SIGMA-I: include context in signature * SIGMA-I: include identifiers in signature * Merge `ServerLoginStart/FinishParameters` * Re-export more necessary types * More carefully expose types * Add ECDSA test * SIGMA-I: share context hashing * De-duplicate client static public key storage * Hide `KeyExchange` better * Use the correct hash in the root documentation * Bump `derive-where` * Format documentation examples a bit further * Add remote OPRF seed documentation * Rename `deserialize_key_pair` to `deserialize_take_key_pair` * Add more key tests * Remove `SharedSecret` trait * SIGMA-I refactor message API * Share more implementation between 3DH and SIGMA-I * Remove unnecessary zero scalar check for Curve25519 * Use correct hash in test * Add some more TODOs * Exclude `tests` folder from Cargo publishing * Enable missing dependencies * Use right crate for testing Ed25519 * Remove unnecessary `Sized` constraints * Remove unnecessary `ecdsa` crate features * Move signature de/serialization to trait methods * Nit: move import to appropriate location * Add warning to SIGMA-I
This commit is contained in:
@@ -8,26 +8,25 @@
|
||||
|
||||
//! Key Exchange group implementation for Curve25519
|
||||
|
||||
pub use curve25519_dalek;
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar;
|
||||
use curve25519_dalek::traits::Identity;
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{FixedOutput, HashMarker, OutputSizeUser};
|
||||
use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32};
|
||||
use generic_array::typenum::U32;
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::ConstantTimeEq;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::KeGroup;
|
||||
use super::Group;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// Implementation for Curve25519.
|
||||
pub struct Curve25519;
|
||||
|
||||
/// The implementation of such a subgroup for Curve25519
|
||||
impl KeGroup for Curve25519 {
|
||||
impl Group for Curve25519 {
|
||||
type Pk = MontgomeryPoint;
|
||||
type PkLen = U32;
|
||||
type Sk = Scalar;
|
||||
@@ -37,50 +36,28 @@ impl KeGroup for Curve25519 {
|
||||
pk.to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.take_array::<U32>("public key")
|
||||
.ok()
|
||||
.map(MontgomeryPoint)
|
||||
.map(|array| MontgomeryPoint(array.into()))
|
||||
.filter(|pk| pk != &MontgomeryPoint::identity())
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
loop {
|
||||
// Sample 32 random bytes and then clamp, as described in https://cr.yp.to/ecdh.html
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
let scalar = scalar::clamp_integer(scalar_bytes);
|
||||
// Sample 32 random bytes and then clamp, as described in https://cr.yp.to/ecdh.html
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
let scalar = scalar::clamp_integer(scalar_bytes);
|
||||
|
||||
if scalar != curve25519_dalek::Scalar::ZERO.to_bytes() {
|
||||
break Scalar(scalar);
|
||||
}
|
||||
}
|
||||
Scalar(scalar)
|
||||
}
|
||||
|
||||
fn hash_to_scalar<'a, H>(_input: &[&[u8]], _dst: &[&[u8]]) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn derive_auth_keypair<CS: voprf::CipherSuite>(
|
||||
seed: GenericArray<u8, Self::SkLen>,
|
||||
) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||
{
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
Ok(Scalar(scalar::clamp_integer(seed.into())))
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice {
|
||||
scalar.0.ct_eq(&curve25519_dalek::Scalar::ZERO.to_bytes())
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
MontgomeryPoint::mul_base_clamped(sk.0)
|
||||
}
|
||||
@@ -89,22 +66,21 @@ impl KeGroup for Curve25519 {
|
||||
sk.0.into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.take_array::<U32>("secret key")
|
||||
.ok()
|
||||
.and_then(|bytes| {
|
||||
let scalar = scalar::clamp_integer(bytes);
|
||||
(scalar == bytes).then_some(scalar)
|
||||
let scalar = scalar::clamp_integer(bytes.into());
|
||||
(scalar == *bytes).then_some(scalar)
|
||||
})
|
||||
.filter(|scalar| scalar != &curve25519_dalek::Scalar::ZERO.to_bytes())
|
||||
.map(Scalar)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
/// Curve25519 scalar.
|
||||
#[derive(Clone, Copy, Zeroize)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct Scalar([u8; 32]);
|
||||
|
||||
impl DiffieHellman<Curve25519> for Scalar {
|
||||
@@ -112,3 +88,36 @@ impl DiffieHellman<Curve25519> for Scalar {
|
||||
Curve25519::serialize_pk(pk.mul_clamped(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for MontgomeryPoint {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(*self, MontgomeryPoint::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for Scalar {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(*self, Scalar(<_>::default()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_zero_scalar() {
|
||||
use std::vec;
|
||||
|
||||
use crate::tests::mock_rng::CycleRng;
|
||||
|
||||
let mut rng = CycleRng::new(vec![0]);
|
||||
let sk = Curve25519::random_sk(&mut rng);
|
||||
assert_ne!(sk.0, curve25519_dalek::Scalar::ZERO.to_bytes());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,459 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// This source code is dual-licensed under either the MIT license found in the
|
||||
// LICENSE-MIT file in the root directory of this source tree or the Apache
|
||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
//! Key Exchange group implementation for Ed25519
|
||||
|
||||
use core::iter;
|
||||
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
use curve25519_dalek::traits::IsIdentity;
|
||||
use curve25519_dalek::{EdwardsPoint, Scalar};
|
||||
use digest::Digest;
|
||||
pub use ed25519_dalek;
|
||||
use ed25519_dalek::hazmat::ExpandedSecretKey;
|
||||
use ed25519_dalek::{SecretKey, Sha512};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{U32, U64};
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::Group;
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::sigma_i::hash_eddsa::implementation::HashEddsaImpl;
|
||||
use crate::key_exchange::sigma_i::pure_eddsa::implementation::PureEddsaImpl;
|
||||
pub use crate::key_exchange::sigma_i::shared::PreHash;
|
||||
use crate::key_exchange::sigma_i::{CachedMessage, Message, MessageBuilder};
|
||||
use crate::serialization::{SliceExt, UpdateExt};
|
||||
|
||||
/// Implementation for Ed25519.
|
||||
pub struct Ed25519;
|
||||
|
||||
impl Group for Ed25519 {
|
||||
type Pk = VerifyingKey;
|
||||
type PkLen = U32;
|
||||
type Sk = SigningKey;
|
||||
type SkLen = U32;
|
||||
|
||||
fn serialize_pk(pk: Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
pk.compressed.0.into()
|
||||
}
|
||||
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
let compressed = bytes
|
||||
.take_array("public key")
|
||||
.map(|bytes| CompressedEdwardsY(bytes.into()))?;
|
||||
|
||||
if let Some(point) = compressed.decompress().filter(|point| !point.is_identity()) {
|
||||
Ok(VerifyingKey { point, compressed })
|
||||
} else {
|
||||
Err(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
let mut sk = <[u8; 32]>::default();
|
||||
rng.fill_bytes(&mut sk);
|
||||
|
||||
SigningKey::from_bytes(sk)
|
||||
}
|
||||
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
Ok(SigningKey::from_bytes(seed.into()))
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
sk.verifying_key
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.sk.into()
|
||||
}
|
||||
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
Ok(SigningKey::from_bytes(
|
||||
bytes.take_array("secret key")?.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Ed25519 verifying key.
|
||||
// `ed25519_dalek::VerifyingKey` doesn't implement `Zeroize`.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/747.
|
||||
// Required for manual implementation of EdDSA.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Zeroize)]
|
||||
pub struct VerifyingKey {
|
||||
point: EdwardsPoint,
|
||||
compressed: CompressedEdwardsY,
|
||||
}
|
||||
|
||||
/// Ed25519 siging key.
|
||||
// We store the `ExpandedSecret` in memory to avoid computing it on demand and then discarding it
|
||||
// again.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Zeroize)]
|
||||
pub struct SigningKey {
|
||||
// `ed25519_dalek::SigningKey` doesn't implement `Zeroize`. See
|
||||
// https://github.com/dalek-cryptography/curve25519-dalek/pull/747
|
||||
// Required for manual implementation of EdDSA.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
sk: SecretKey,
|
||||
verifying_key: VerifyingKey,
|
||||
// `ed25519_dalek::ExpandedSecret` doesn't implement traits we need. See
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/748 and
|
||||
// https://github.com/dalek-cryptography/curve25519-dalek/pull/747.
|
||||
scalar: Scalar,
|
||||
hash_prefix: [u8; 32],
|
||||
}
|
||||
|
||||
impl SigningKey {
|
||||
fn from_bytes(sk: [u8; 32]) -> Self {
|
||||
let ExpandedSecretKey {
|
||||
scalar,
|
||||
hash_prefix,
|
||||
} = ExpandedSecretKey::from(&sk);
|
||||
let point = EdwardsPoint::mul_base(&scalar);
|
||||
let verifying_key = VerifyingKey {
|
||||
point,
|
||||
compressed: point.compress(),
|
||||
};
|
||||
|
||||
SigningKey {
|
||||
sk,
|
||||
verifying_key,
|
||||
scalar,
|
||||
hash_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PureEddsaImpl for Ed25519 {
|
||||
type Signature = Signature;
|
||||
type SignatureLen = U64;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, CachedMessage<CS, KE>) {
|
||||
(sign(sk, false, message.sign_message()), message.to_cached())
|
||||
}
|
||||
|
||||
/// Validates that the signature was created by signing the given message
|
||||
/// with the corresponding private key.
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: CachedMessage<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
verify(
|
||||
pk,
|
||||
false,
|
||||
message_builder.build::<KE>(state).verify_message(),
|
||||
signature,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
Signature::deserialize_take(bytes)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
signature.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
impl HashEddsaImpl for Ed25519 {
|
||||
type Signature = Signature;
|
||||
type SignatureLen = U64;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = PreHash<Sha512>;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
let hash = message.hash::<Sha512>();
|
||||
|
||||
(
|
||||
sign(sk, true, iter::once(hash.sign.finalize().as_slice())),
|
||||
PreHash(hash.verify.finalize()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Validates that the signature was created by signing the given message
|
||||
/// with the corresponding private key.
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
verify(pk, true, iter::once(state.0.as_slice()), signature)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
Signature::deserialize_take(bytes)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
signature.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
// This contains a manual implementation of EdDSA because `ed25519-dalek`
|
||||
// doesn't support message streaming. See
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
fn sign<'a>(
|
||||
sk: &SigningKey,
|
||||
pre_hash: bool,
|
||||
message: impl Clone + Iterator<Item = &'a [u8]>,
|
||||
) -> Signature {
|
||||
let mut h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(sk.hash_prefix);
|
||||
h.update_iter(message.clone());
|
||||
|
||||
let r = Scalar::from_hash(h);
|
||||
#[allow(non_snake_case)]
|
||||
let R = EdwardsPoint::mul_base(&r).compress();
|
||||
|
||||
h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(R.as_bytes());
|
||||
h.update(sk.verifying_key.compressed.0);
|
||||
h.update_iter(message);
|
||||
|
||||
let k = Scalar::from_hash(h);
|
||||
let s: Scalar = (k * sk.scalar) + r;
|
||||
|
||||
Signature { R, s }
|
||||
}
|
||||
|
||||
fn verify<'a>(
|
||||
pk: &VerifyingKey,
|
||||
pre_hash: bool,
|
||||
message: impl Iterator<Item = &'a [u8]>,
|
||||
signature: &Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
let mut h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(signature.R.as_bytes());
|
||||
h.update(pk.compressed.as_bytes());
|
||||
h.update_iter(message);
|
||||
let k = Scalar::from_hash(h);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let minus_A: EdwardsPoint = -pk.point;
|
||||
#[allow(non_snake_case)]
|
||||
let expected_R =
|
||||
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s).compress();
|
||||
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProtocolError::InvalidLoginError)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ed25519 Signature.
|
||||
// `ed25519_dalek::Signature` doesn't implement validation with Serde de/serialization.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct Signature {
|
||||
R: CompressedEdwardsY,
|
||||
s: Scalar,
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Expects the `R` and `s` components of a Ed25519 signature with no added
|
||||
/// framing.
|
||||
pub fn from_slice(mut bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
Self::deserialize_take(&mut bytes)
|
||||
}
|
||||
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
#[allow(non_snake_case)]
|
||||
let R = CompressedEdwardsY(bytes.take_array("signature R")?.into());
|
||||
|
||||
let s = Scalar::from_canonical_bytes(bytes.take_array("signature s")?.into())
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::SerializationError)?;
|
||||
|
||||
Ok(Self { R, s })
|
||||
}
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, U64> {
|
||||
GenericArray::from(self.R.0).concat(GenericArray::from(self.s.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for Signature {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
Signature::deserialize_take(
|
||||
&mut (GenericArray::<_, U64>::deserialize(deserializer)?.as_slice()),
|
||||
)
|
||||
.map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for Signature {
|
||||
fn serialize<SK>(&self, serializer: SK) -> Result<SK::Ok, SK::Error>
|
||||
where
|
||||
SK: serde::Serializer,
|
||||
{
|
||||
self.serialize().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Zeroize for Signature {
|
||||
fn zeroize(&mut self) {
|
||||
self.R.0 = [0; 32];
|
||||
self.s = Scalar::default();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for VerifyingKey {
|
||||
fn assert_zeroized(&self) {
|
||||
use curve25519_dalek::traits::Identity;
|
||||
|
||||
let Self { point, compressed } = self;
|
||||
|
||||
assert_eq!(point, &EdwardsPoint::identity());
|
||||
assert_eq!(compressed, &EdwardsPoint::identity().compress());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for SigningKey {
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
sk,
|
||||
verifying_key,
|
||||
scalar,
|
||||
hash_prefix,
|
||||
} = self;
|
||||
|
||||
verifying_key.assert_zeroized();
|
||||
|
||||
for byte in sk.iter().chain(scalar.to_bytes().iter()).chain(hash_prefix) {
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::iter;
|
||||
|
||||
use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pure_eddsa() {
|
||||
let mut message = [0; 1024];
|
||||
OsRng.fill_bytes(&mut message);
|
||||
|
||||
let mut sk = SecretKey::default();
|
||||
OsRng.fill_bytes(&mut sk);
|
||||
let signing_key = SigningKey::from_bytes(&sk);
|
||||
|
||||
let signature = signing_key.sign(&message);
|
||||
|
||||
let custom_sk = Ed25519::deserialize_take_sk(&mut sk.as_slice()).unwrap();
|
||||
let custom_signature = sign(&custom_sk, false, iter::once(message.as_slice()));
|
||||
|
||||
assert_eq!(
|
||||
signature.to_bytes(),
|
||||
custom_signature.serialize().as_slice()
|
||||
);
|
||||
|
||||
let verifying_key = VerifyingKey::from(&signing_key);
|
||||
verifying_key.verify(&message, &signature).unwrap();
|
||||
|
||||
let custom_pk = Ed25519::public_key(custom_sk);
|
||||
verify(
|
||||
&custom_pk,
|
||||
false,
|
||||
iter::once(message.as_slice()),
|
||||
&custom_signature,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_eddsa() {
|
||||
let mut message = [0; 1024];
|
||||
OsRng.fill_bytes(&mut message);
|
||||
let message = Sha512::new_with_prefix(message);
|
||||
let pre_hash = message.clone().finalize();
|
||||
|
||||
let mut sk = SecretKey::default();
|
||||
OsRng.fill_bytes(&mut sk);
|
||||
let signing_key = SigningKey::from_bytes(&sk);
|
||||
|
||||
let signature = signing_key.sign_prehashed(message.clone(), None).unwrap();
|
||||
|
||||
let custom_sk = Ed25519::deserialize_take_sk(&mut sk.as_slice()).unwrap();
|
||||
let custom_signature = sign(&custom_sk, true, iter::once(pre_hash.as_slice()));
|
||||
|
||||
assert_eq!(
|
||||
signature.to_bytes(),
|
||||
custom_signature.serialize().as_slice()
|
||||
);
|
||||
|
||||
let verifying_key = VerifyingKey::from(&signing_key);
|
||||
verifying_key
|
||||
.verify_prehashed(message, None, &signature)
|
||||
.unwrap();
|
||||
|
||||
let custom_pk = Ed25519::public_key(custom_sk);
|
||||
verify(
|
||||
&custom_pk,
|
||||
true,
|
||||
iter::once(pre_hash.as_slice()),
|
||||
&custom_signature,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -6,101 +6,157 @@
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{FixedOutput, HashMarker};
|
||||
use elliptic_curve::group::cofactor::CofactorGroup;
|
||||
use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest};
|
||||
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
|
||||
//! Implementation for EC curves via [`elliptic_curve`] traits.
|
||||
|
||||
use core::fmt::{self, Debug, Formatter};
|
||||
|
||||
use derive_where::derive_where;
|
||||
use elliptic_curve::group::GroupEncoding;
|
||||
use elliptic_curve::ops::MulByGenerator;
|
||||
use elliptic_curve::sec1::{ModulusSize, ToEncodedPoint};
|
||||
use elliptic_curve::{
|
||||
AffinePoint, Field, FieldBytesSize, Group, ProjectivePoint, PublicKey, Scalar, SecretKey,
|
||||
point, CurveArithmetic, FieldBytesSize, Group as _, NonZeroScalar, ProjectivePoint, Scalar,
|
||||
SecretKey,
|
||||
};
|
||||
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use voprf::Mode;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::KeGroup;
|
||||
use super::{Group, STR_OPAQUE_DERIVE_AUTH_KEY_PAIR};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
impl<G> KeGroup for G
|
||||
impl<G> Group for G
|
||||
where
|
||||
G: GroupDigest,
|
||||
Self: CurveArithmetic + voprf::CipherSuite<Group = Self> + voprf::Group<Scalar = Scalar<Self>>,
|
||||
FieldBytesSize<Self>: ModulusSize,
|
||||
AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>,
|
||||
ProjectivePoint<Self>: CofactorGroup + ToEncodedPoint<Self>,
|
||||
Scalar<Self>: FromOkm,
|
||||
ProjectivePoint<Self>: GroupEncoding<
|
||||
Repr = GenericArray<u8, <FieldBytesSize<Self> as ModulusSize>::CompressedPointSize>,
|
||||
> + ToEncodedPoint<Self>,
|
||||
{
|
||||
type Pk = ProjectivePoint<Self>;
|
||||
type Pk = NonIdentity<Self>;
|
||||
|
||||
type PkLen = <FieldBytesSize<Self> as ModulusSize>::CompressedPointSize;
|
||||
|
||||
type Sk = Scalar<Self>;
|
||||
type Sk = NonZeroScalar<Self>;
|
||||
|
||||
type SkLen = FieldBytesSize<Self>;
|
||||
|
||||
fn serialize_pk(pk: Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
GenericArray::clone_from_slice(pk.to_encoded_point(true).as_bytes())
|
||||
GenericArray::clone_from_slice(pk.0.to_encoded_point(true).as_bytes())
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
PublicKey::<Self>::from_sec1_bytes(bytes)
|
||||
.map(|public_key| public_key.to_projective())
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
point::NonIdentity::<ProjectivePoint<Self>>::from_bytes(&bytes.take_array("public key")?)
|
||||
.into_option()
|
||||
.map(NonIdentity)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
*SecretKey::<Self>::random(rng).to_nonzero_scalar()
|
||||
SecretKey::<Self>::random(rng).to_nonzero_scalar()
|
||||
}
|
||||
|
||||
// Implements the `HashToScalar()` function from
|
||||
// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-19.html#section-4>
|
||||
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
Self::hash_to_scalar::<ExpandMsgXmd<H>>(input, dst)
|
||||
.map_err(|_| InternalError::HashToScalar)
|
||||
.and_then(|scalar| {
|
||||
if bool::from(scalar.is_zero()) {
|
||||
Err(InternalError::HashToScalar)
|
||||
} else {
|
||||
Ok(scalar)
|
||||
}
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
voprf::derive_key::<Self>(&seed, &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, Mode::Oprf)
|
||||
.map(|scalar| {
|
||||
NonZeroScalar::new(scalar).expect("`voprf::derive_key()` returned a zero scalar")
|
||||
})
|
||||
.map_err(InternalError::from)
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
ProjectivePoint::<Self>::generator() * sk
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice {
|
||||
scalar.is_zero()
|
||||
// Non-panicking version in https://github.com/RustCrypto/traits/pull/1833.
|
||||
NonIdentity(
|
||||
point::NonIdentity::new(ProjectivePoint::<Self>::mul_by_generator(&*sk))
|
||||
.expect("multiplying with a non-zero scalar can never yield the identity element"),
|
||||
)
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
SecretKey::<Self>::from_slice(bytes)
|
||||
.map(|secret_key| *secret_key.to_nonzero_scalar())
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
SecretKey::<Self>::from_bytes(&bytes.take_array("secret key")?)
|
||||
.map(|secret_key| secret_key.to_nonzero_scalar())
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G> DiffieHellman<G> for Scalar<G>
|
||||
/// Wrapper around [`NonIdentity`](point::NonIdentity) to implement [`Zeroize`].
|
||||
// TODO: remove after https://github.com/RustCrypto/traits/pull/1832.
|
||||
#[derive_where(Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "point::NonIdentity<ProjectivePoint<G>>: serde::Deserialize<'de>",
|
||||
serialize = "point::NonIdentity<ProjectivePoint<G>>: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
pub struct NonIdentity<G: CurveArithmetic>(pub point::NonIdentity<ProjectivePoint<G>>);
|
||||
|
||||
impl<G: CurveArithmetic> Debug for NonIdentity<G> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("NonIdentity")
|
||||
.field(&self.0.to_point())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: CurveArithmetic> PartialEq for NonIdentity<G> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.to_point().eq(&other.0.to_point())
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: CurveArithmetic> Eq for NonIdentity<G> {}
|
||||
|
||||
impl<G: CurveArithmetic> Zeroize for NonIdentity<G> {
|
||||
fn zeroize(&mut self) {
|
||||
self.0 = point::NonIdentity::new(ProjectivePoint::<G>::generator()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<G> DiffieHellman<G> for NonZeroScalar<G>
|
||||
where
|
||||
G: GroupDigest,
|
||||
G: CurveArithmetic + voprf::CipherSuite<Group = G> + voprf::Group<Scalar = Scalar<G>>,
|
||||
FieldBytesSize<G>: ModulusSize,
|
||||
AffinePoint<G>: FromEncodedPoint<G> + ToEncodedPoint<G>,
|
||||
ProjectivePoint<G>: CofactorGroup + ToEncodedPoint<G>,
|
||||
Scalar<G>: FromOkm,
|
||||
ProjectivePoint<G>: GroupEncoding<
|
||||
Repr = GenericArray<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize>,
|
||||
> + ToEncodedPoint<G>,
|
||||
{
|
||||
fn diffie_hellman(
|
||||
self,
|
||||
pk: ProjectivePoint<G>,
|
||||
pk: NonIdentity<G>,
|
||||
) -> GenericArray<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize> {
|
||||
GenericArray::clone_from_slice((pk * self).to_encoded_point(true).as_bytes())
|
||||
GenericArray::clone_from_slice((pk.0 * self).to_encoded_point(true).as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl<G: CurveArithmetic> AssertZeroized for NonIdentity<G> {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(self.0.to_point(), ProjectivePoint::<G>::generator());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<G: CurveArithmetic> AssertZeroized for NonZeroScalar<G> {
|
||||
fn assert_zeroized(&self) {
|
||||
use elliptic_curve::Field;
|
||||
|
||||
assert_eq!(**self, Scalar::<G>::ONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,16 @@
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
//! Includes the [`KeGroup`] trait and definitions for the key exchange groups
|
||||
//! Includes the [`Group`] trait and definitions for the key exchange groups
|
||||
|
||||
#[cfg(feature = "curve25519")]
|
||||
pub mod curve25519;
|
||||
mod elliptic_curve;
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub mod ed25519;
|
||||
pub mod elliptic_curve;
|
||||
#[cfg(feature = "ristretto255")]
|
||||
pub mod ristretto255;
|
||||
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{FixedOutput, HashMarker, OutputSizeUser};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
@@ -27,7 +25,7 @@ use crate::errors::{InternalError, ProtocolError};
|
||||
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: [u8; 33] = *b"OPAQUE-DeriveDiffieHellmanKeyPair";
|
||||
|
||||
/// A group representation for use in the key exchange
|
||||
pub trait KeGroup {
|
||||
pub trait Group {
|
||||
/// Public key
|
||||
type Pk: Copy + Zeroize;
|
||||
/// Length of the public key
|
||||
@@ -41,65 +39,15 @@ pub trait KeGroup {
|
||||
fn serialize_pk(pk: Self::Pk) -> GenericArray<u8, Self::PkLen>;
|
||||
|
||||
/// Return a public key from its fixed-length bytes representation
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError>;
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError>;
|
||||
|
||||
/// Generate a random secret key
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk;
|
||||
|
||||
/// Hashes a slice of pseudo-random bytes to a scalar
|
||||
///
|
||||
/// # Errors
|
||||
/// [`InternalError::HashToScalar`] if the `input` is empty or longer then
|
||||
/// [`u16::MAX`].
|
||||
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
|
||||
|
||||
/// Corresponds to the `DeriveAuthKeyPair()` function defined in
|
||||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-08.html#section-6.4.2>
|
||||
///
|
||||
/// Note that we cannot call the voprf crate directly since we need to
|
||||
/// ensure that the [`KeGroup`] is used for the
|
||||
/// [`hash_to_scalar`](Self::hash_to_scalar) operation (as opposed to
|
||||
/// the [`OprfGroup`](voprf::Group)).
|
||||
fn derive_auth_keypair<CS: voprf::CipherSuite>(
|
||||
seed: GenericArray<u8, Self::SkLen>,
|
||||
) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||
{
|
||||
let info = &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR;
|
||||
let dst_1 = GenericArray::from(STR_DERIVE_KEYPAIR)
|
||||
.concat(STR_OPRF.into())
|
||||
.concat([voprf::Mode::Oprf.to_u8()].into())
|
||||
.concat([b'-'].into());
|
||||
let dst_2 = CS::ID.as_bytes();
|
||||
|
||||
let info_len = i2osp_2(info.len())
|
||||
.map_err(|_| InternalError::OprfError(voprf::Error::DeriveKeyPair))?;
|
||||
|
||||
for counter in 0_u8..=u8::MAX {
|
||||
// deriveInput = seed || I2OSP(len(info), 2) || info
|
||||
// skS = G.HashToScalar(deriveInput || I2OSP(counter, 1), DST = "DeriveKeyPair"
|
||||
// || contextString)
|
||||
let sk_s = Self::hash_to_scalar::<CS::Hash>(
|
||||
&[&seed, &info_len, info, &counter.to_be_bytes()],
|
||||
&[&dst_1, dst_2],
|
||||
)
|
||||
.map_err(|_| InternalError::OprfError(voprf::Error::DeriveKeyPair))?;
|
||||
|
||||
if !bool::from(Self::is_zero_scalar(sk_s)) {
|
||||
return Ok(sk_s);
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalError::OprfError(voprf::Error::DeriveKeyPair))
|
||||
}
|
||||
|
||||
/// Returns `true` if the scalar is zero.
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice;
|
||||
/// Deterministically derive a [`Self::Sk`] from `seed`.
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError>;
|
||||
|
||||
/// Return a public key from its secret key
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk;
|
||||
@@ -108,17 +56,7 @@ pub trait KeGroup {
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen>;
|
||||
|
||||
/// Return a public key from its fixed-length bytes representation
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError>;
|
||||
}
|
||||
|
||||
// Helper functions used to compute DeriveAuthKeyPair() (taken from the voprf
|
||||
// crate)
|
||||
|
||||
const STR_OPRF: [u8; 7] = *b"OPRFV1-";
|
||||
const STR_DERIVE_KEYPAIR: [u8; 13] = *b"DeriveKeyPair";
|
||||
|
||||
fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> {
|
||||
u16::try_from(input)
|
||||
.map(|input| input.to_be_bytes())
|
||||
.map_err(|_| InternalError::OprfInternalError(voprf::InternalError::I2osp))
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError>;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
//! Key Exchange group implementation for ristretto255
|
||||
|
||||
pub use curve25519_dalek;
|
||||
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
|
||||
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
@@ -17,19 +18,19 @@ use digest::{FixedOutput, HashMarker};
|
||||
use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32};
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::ConstantTimeEq;
|
||||
use voprf::Group;
|
||||
use voprf::Mode;
|
||||
|
||||
use super::KeGroup;
|
||||
use super::{Group, STR_OPAQUE_DERIVE_AUTH_KEY_PAIR};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// Implementation for Ristretto255.
|
||||
// This is necessary because Rust lacks specialization, otherwise we could
|
||||
// implement `KeGroup` for `voprf::Ristretto255`.
|
||||
pub struct Ristretto255;
|
||||
|
||||
impl KeGroup for Ristretto255 {
|
||||
impl Group for Ristretto255 {
|
||||
type Pk = RistrettoPoint;
|
||||
type PkLen = U32;
|
||||
type Sk = Scalar;
|
||||
@@ -39,8 +40,8 @@ impl KeGroup for Ristretto255 {
|
||||
pk.compress().to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
CompressedRistretto::from_slice(bytes)
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
CompressedRistretto::from_slice(&bytes.take_array::<U32>("public key")?)
|
||||
.map_err(|_| ProtocolError::SerializationError)?
|
||||
.decompress()
|
||||
.filter(|point| point != &RistrettoPoint::identity())
|
||||
@@ -49,21 +50,7 @@ impl KeGroup for Ristretto255 {
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
loop {
|
||||
let scalar = {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
Scalar::random(rng)
|
||||
}
|
||||
|
||||
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes
|
||||
// from rng
|
||||
#[cfg(test)]
|
||||
{
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
Scalar::from_bytes_mod_order(scalar_bytes)
|
||||
}
|
||||
};
|
||||
let scalar = Scalar::random(rng);
|
||||
|
||||
if scalar != Scalar::ZERO {
|
||||
break scalar;
|
||||
@@ -71,19 +58,9 @@ impl KeGroup for Ristretto255 {
|
||||
}
|
||||
}
|
||||
|
||||
// Implements the `HashToScalar()` function from
|
||||
// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-19.html#section-4>
|
||||
fn hash_to_scalar<'a, H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
<voprf::Ristretto255 as Group>::hash_to_scalar::<H>(input, dst)
|
||||
.map_err(InternalError::OprfInternalError)
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice {
|
||||
scalar.ct_eq(&Scalar::ZERO)
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
voprf::derive_key::<Self>(&seed, &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, Mode::Oprf)
|
||||
.map_err(InternalError::from)
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
@@ -94,17 +71,16 @@ impl KeGroup for Ristretto255 {
|
||||
sk.to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.take_array::<U32>("secret key")
|
||||
.ok()
|
||||
.and_then(|bytes| Scalar::from_canonical_bytes(bytes).into())
|
||||
.and_then(|bytes| Scalar::from_canonical_bytes(bytes.into()).into())
|
||||
.filter(|scalar| scalar != &Scalar::ZERO)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ristretto255-voprf")]
|
||||
impl voprf::CipherSuite for Ristretto255 {
|
||||
const ID: &'static str = voprf::Ristretto255::ID;
|
||||
|
||||
@@ -113,14 +89,14 @@ impl voprf::CipherSuite for Ristretto255 {
|
||||
type Hash = <voprf::Ristretto255 as voprf::CipherSuite>::Hash;
|
||||
}
|
||||
|
||||
impl Group for Ristretto255 {
|
||||
type Elem = <voprf::Ristretto255 as Group>::Elem;
|
||||
impl voprf::Group for Ristretto255 {
|
||||
type Elem = <voprf::Ristretto255 as voprf::Group>::Elem;
|
||||
|
||||
type ElemLen = <voprf::Ristretto255 as Group>::ElemLen;
|
||||
type ElemLen = <voprf::Ristretto255 as voprf::Group>::ElemLen;
|
||||
|
||||
type Scalar = <voprf::Ristretto255 as Group>::Scalar;
|
||||
type Scalar = <voprf::Ristretto255 as voprf::Group>::Scalar;
|
||||
|
||||
type ScalarLen = <voprf::Ristretto255 as Group>::ScalarLen;
|
||||
type ScalarLen = <voprf::Ristretto255 as voprf::Group>::ScalarLen;
|
||||
|
||||
fn hash_to_curve<H>(
|
||||
input: &[&[u8]],
|
||||
@@ -130,7 +106,7 @@ impl Group for Ristretto255 {
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
<voprf::Ristretto255 as Group>::hash_to_curve::<H>(input, dst)
|
||||
<voprf::Ristretto255 as voprf::Group>::hash_to_curve::<H>(input, dst)
|
||||
}
|
||||
|
||||
fn hash_to_scalar<H>(
|
||||
@@ -141,43 +117,43 @@ impl Group for Ristretto255 {
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
<voprf::Ristretto255 as Group>::hash_to_scalar::<H>(input, dst)
|
||||
<voprf::Ristretto255 as voprf::Group>::hash_to_scalar::<H>(input, dst)
|
||||
}
|
||||
|
||||
fn base_elem() -> Self::Elem {
|
||||
<voprf::Ristretto255 as Group>::base_elem()
|
||||
<voprf::Ristretto255 as voprf::Group>::base_elem()
|
||||
}
|
||||
|
||||
fn identity_elem() -> Self::Elem {
|
||||
<voprf::Ristretto255 as Group>::identity_elem()
|
||||
<voprf::Ristretto255 as voprf::Group>::identity_elem()
|
||||
}
|
||||
|
||||
fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen> {
|
||||
<voprf::Ristretto255 as Group>::serialize_elem(elem)
|
||||
<voprf::Ristretto255 as voprf::Group>::serialize_elem(elem)
|
||||
}
|
||||
|
||||
fn deserialize_elem(element_bits: &[u8]) -> voprf::Result<Self::Elem> {
|
||||
<voprf::Ristretto255 as Group>::deserialize_elem(element_bits)
|
||||
<voprf::Ristretto255 as voprf::Group>::deserialize_elem(element_bits)
|
||||
}
|
||||
|
||||
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||
<voprf::Ristretto255 as Group>::random_scalar(rng)
|
||||
<voprf::Ristretto255 as voprf::Group>::random_scalar(rng)
|
||||
}
|
||||
|
||||
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
|
||||
<voprf::Ristretto255 as Group>::invert_scalar(scalar)
|
||||
<voprf::Ristretto255 as voprf::Group>::invert_scalar(scalar)
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
|
||||
<voprf::Ristretto255 as Group>::is_zero_scalar(scalar)
|
||||
<voprf::Ristretto255 as voprf::Group>::is_zero_scalar(scalar)
|
||||
}
|
||||
|
||||
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
|
||||
<voprf::Ristretto255 as Group>::serialize_scalar(scalar)
|
||||
<voprf::Ristretto255 as voprf::Group>::serialize_scalar(scalar)
|
||||
}
|
||||
|
||||
fn deserialize_scalar(scalar_bits: &[u8]) -> voprf::Result<Self::Scalar> {
|
||||
<voprf::Ristretto255 as Group>::deserialize_scalar(scalar_bits)
|
||||
<voprf::Ristretto255 as voprf::Group>::deserialize_scalar(scalar_bits)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,3 +162,25 @@ impl DiffieHellman<Ristretto255> for Scalar {
|
||||
Ristretto255::serialize_pk(pk * self)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for RistrettoPoint {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(*self, RistrettoPoint::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for Scalar {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(*self, Scalar::default());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user