use alloc::vec::Vec;
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::ristretto::CompressedRistretto;
use curve25519_dalek::{RistrettoPoint, Scalar};
use pakery_core::crypto::dh::DhGroup;
use pakery_core::crypto::group::CpaceGroup;
use pakery_core::PakeError;
use rand_core::CryptoRng;
use zeroize::{Zeroize, Zeroizing};
#[derive(Clone, PartialEq)]
pub struct Ristretto255Group {
point: RistrettoPoint,
}
impl CpaceGroup for Ristretto255Group {
type Scalar = Scalar;
fn scalar_mul(&self, scalar: &Scalar) -> Self {
Self {
point: self.point * scalar,
}
}
fn is_identity(&self) -> bool {
use curve25519_dalek::traits::Identity;
use subtle::ConstantTimeEq;
pakery_core::ct::declassify_choice(self.point.ct_eq(&RistrettoPoint::identity()))
}
fn to_bytes(&self) -> Vec<u8> {
self.point.compress().to_bytes().to_vec()
}
fn from_bytes(bytes: &[u8]) -> Result<Self, PakeError> {
let arr: [u8; 32] = bytes.try_into().map_err(|_| PakeError::InvalidPoint)?;
let point = CompressedRistretto(arr)
.decompress()
.ok_or(PakeError::InvalidPoint)?;
Ok(Self { point })
}
fn from_uniform_bytes(bytes: &[u8]) -> Result<Self, PakeError> {
let arr: &[u8; 64] = bytes
.try_into()
.map_err(|_| PakeError::InvalidInput("from_uniform_bytes requires 64 bytes"))?;
Ok(Self {
point: RistrettoPoint::from_uniform_bytes(arr),
})
}
fn random_scalar(rng: &mut impl CryptoRng) -> Scalar {
let mut wide = Zeroizing::new([0u8; 64]);
rng.fill_bytes(&mut *wide);
pakery_core::ct::mark_secret(&*wide);
Scalar::from_bytes_mod_order_wide(&wide)
}
fn add(&self, other: &Self) -> Self {
Self {
point: self.point + other.point,
}
}
fn negate(&self) -> Self {
Self { point: -self.point }
}
fn basepoint_mul(scalar: &Scalar) -> Self {
Self {
point: scalar * RISTRETTO_BASEPOINT_POINT,
}
}
fn scalar_from_wide_bytes(bytes: &[u8]) -> Result<Scalar, PakeError> {
let arr: [u8; 64] = bytes
.try_into()
.map_err(|_| PakeError::InvalidInput("scalar_from_wide_bytes requires 64 bytes"))?;
Ok(Scalar::from_bytes_mod_order_wide(&arr))
}
fn scalar_to_bytes(scalar: &Scalar) -> Vec<u8> {
scalar.to_bytes().to_vec()
}
}
pub struct Ristretto255Dh;
impl DhGroup for Ristretto255Dh {
fn diffie_hellman(sk: &[u8], pk: &[u8]) -> Result<Zeroizing<Vec<u8>>, PakeError> {
use curve25519_dalek::traits::Identity;
use subtle::ConstantTimeEq;
let sk_bytes: [u8; 32] = sk
.try_into()
.map_err(|_| PakeError::InvalidInput("invalid secret key length"))?;
let sk_bytes = Zeroizing::new(sk_bytes);
pakery_core::ct::declassify(&*sk_bytes);
let scalar = Scalar::from_canonical_bytes(*sk_bytes)
.into_option()
.ok_or(PakeError::InvalidInput("invalid scalar"))?;
let pk_bytes: [u8; 32] = pk
.try_into()
.map_err(|_| PakeError::InvalidInput("invalid public key length"))?;
let pk_point = CompressedRistretto(pk_bytes)
.decompress()
.ok_or(PakeError::InvalidInput("invalid public key point"))?;
let result = scalar * pk_point;
if pakery_core::ct::declassify_choice(result.ct_eq(&RistrettoPoint::identity())) {
return Err(PakeError::IdentityPoint);
}
let out = result.compress().to_bytes().to_vec();
pakery_core::ct::mark_secret(&out);
Ok(Zeroizing::new(out))
}
fn derive_keypair(seed: &[u8]) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>), PakeError> {
use crate::oprf_ristretto::Ristretto255Oprf;
use pakery_core::crypto::oprf::Oprf;
let sk_bytes = Ristretto255Oprf::derive_key(seed, b"OPAQUE-DeriveDiffieHellmanKeyPair")?;
let sk_arr: [u8; 32] = sk_bytes
.as_slice()
.try_into()
.map_err(|_| PakeError::ProtocolError("invalid key size"))?;
let sk_arr = Zeroizing::new(sk_arr);
pakery_core::ct::declassify(&*sk_arr);
let scalar =
Scalar::from_canonical_bytes(*sk_arr)
.into_option()
.ok_or(PakeError::ProtocolError(
"invalid scalar from DeriveKeyPair",
))?;
let pk = (scalar * RISTRETTO_BASEPOINT_POINT)
.compress()
.to_bytes()
.to_vec();
Ok((sk_bytes, pk))
}
fn generate_keypair(
rng: &mut impl CryptoRng,
) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>), PakeError> {
let mut seed = [0u8; 32];
rng.fill_bytes(&mut seed);
pakery_core::ct::mark_secret(&seed);
let result = Self::derive_keypair(&seed);
seed.zeroize();
result
}
fn public_key_from_private(sk: &[u8]) -> Result<Vec<u8>, PakeError> {
let sk_bytes: [u8; 32] = sk
.try_into()
.map_err(|_| PakeError::InvalidInput("invalid secret key length"))?;
let sk_bytes = Zeroizing::new(sk_bytes);
pakery_core::ct::declassify(&*sk_bytes);
let scalar = Scalar::from_canonical_bytes(*sk_bytes)
.into_option()
.ok_or(PakeError::InvalidInput("invalid scalar"))?;
let pk = (scalar * RISTRETTO_BASEPOINT_POINT)
.compress()
.to_bytes()
.to_vec();
Ok(pk)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pakery_core::crypto::CpaceGroup;
#[test]
fn scalar_to_bytes_roundtrips_canonical_encoding() {
let mut wide = [0u8; 64];
wide[0] = 5; let scalar = <Ristretto255Group as CpaceGroup>::scalar_from_wide_bytes(&wide).unwrap();
let bytes = <Ristretto255Group as CpaceGroup>::scalar_to_bytes(&scalar);
let mut expected = [0u8; 32];
expected[0] = 5;
assert_eq!(bytes, expected);
}
#[test]
fn public_key_from_private_matches_derive_keypair() {
use pakery_core::crypto::DhGroup;
let (sk, pk) = Ristretto255Dh::derive_keypair(&[7u8; 32]).unwrap();
let pk2 = Ristretto255Dh::public_key_from_private(&sk).unwrap();
assert_eq!(pk, pk2);
assert_eq!(pk.len(), 32);
assert!(Ristretto255Dh::public_key_from_private(&[0u8; 31]).is_err());
}
}