Files
opaque-vx/src/key_exchange/group/curve25519.rs
T
breakingbread efa98962b7
Publish / publish (release) Successful in 1m2s
Rust CI / cargo fmt (push) Successful in 3s
Rust CI / test (stable / no backend / no frontend) (push) Successful in 2m31s
Rust CI / cargo clippy (push) Successful in 1m24s
Rust CI / test (1.90.0 / no backend / no frontend) (push) Successful in 2m28s
Rust CI / test (1.90.0 / no backend / --features argon2) (push) Successful in 2m35s
Rust CI / test (stable / no backend / --features argon2) (push) Successful in 2m33s
Rust CI / test (1.90.0 / no backend / --features serde) (push) Successful in 2m53s
Rust CI / test (1.90.0 / --features curve25519 / --features argon2) (push) Successful in 2m36s
Rust CI / test (stable / no backend / --features serde) (push) Successful in 2m51s
Rust CI / test (1.90.0 / --features curve25519 / no frontend) (push) Successful in 2m29s
Rust CI / test (stable / --features curve25519 / no frontend) (push) Successful in 2m29s
Rust CI / test (stable / --features curve25519 / --features serde) (push) Successful in 2m55s
Rust CI / test (stable / --features curve25519 / --features argon2) (push) Successful in 2m35s
Rust CI / test (1.90.0 / --features curve25519 / --features serde) (push) Successful in 2m56s
Rust CI / test (stable / --features ecdsa / no frontend) (push) Successful in 2m53s
Rust CI / test (1.90.0 / --features ecdsa / no frontend) (push) Successful in 2m51s
Rust CI / test (1.90.0 / --features ecdsa / --features argon2) (push) Successful in 2m57s
Rust CI / test (1.90.0 / --features ecdsa / --features serde) (push) Successful in 3m20s
Rust CI / test (stable / --features ed25519 / no frontend) (push) Successful in 2m54s
Rust CI / test (stable / --features ecdsa / --features argon2) (push) Successful in 3m0s
Rust CI / test (stable / --features ecdsa / --features serde) (push) Successful in 3m19s
Rust CI / test (1.90.0 / --features ed25519 / no frontend) (push) Successful in 2m52s
Rust CI / test (1.90.0 / --features ed25519 / --features argon2) (push) Successful in 2m57s
Rust CI / test (1.90.0 / --features ristretto255 / no frontend) (push) Successful in 3m2s
Rust CI / test (stable / --features ed25519 / --features argon2) (push) Successful in 3m1s
Rust CI / test (1.90.0 / --features ed25519 / --features serde) (push) Successful in 3m20s
Rust CI / test (stable / --features ed25519 / --features serde) (push) Successful in 3m21s
Rust CI / test (stable / --features ristretto255 / no frontend) (push) Successful in 3m4s
Rust CI / test (1.90.0 / --features ristretto255 / --features argon2) (push) Successful in 3m8s
Rust CI / test (1.90.0 / --features ristretto255 / --features serde) (push) Successful in 3m28s
Rust CI / test (stable / --features ristretto255 / --features argon2) (push) Successful in 3m9s
Rust CI / test (stable / --features ristretto255 / --features serde) (push) Successful in 3m32s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / no frontend) (push) Successful in 5m21s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / no frontend) (push) Successful in 5m11s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / --features argon2) (push) Successful in 5m27s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / --features argon2) (push) Successful in 5m14s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / --features serde) (push) Successful in 5m45s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / --features serde) (push) Successful in 5m58s
Rust CI / test (1.90.0 / --features ristretto255,kem / no frontend) (push) Successful in 3m59s
Rust CI / test (stable / --features ristretto255,kem / no frontend) (push) Successful in 4m0s
Rust CI / test (1.90.0 / --features ristretto255,kem / --features argon2) (push) Successful in 4m12s
Rust CI / test (stable / --features ristretto255,kem / --features argon2) (push) Successful in 4m4s
Rust CI / test (1.90.0 / --features ristretto255,kem / --features serde) (push) Successful in 4m34s
Rust CI / test (stable / --features ristretto255,kem / --features serde) (push) Successful in 4m27s
Rust CI / test simple_login example (push) Successful in 18s
Rust CI / test digital_locker example (push) Successful in 19s
Rust CI / cargo bench compilation () (push) Successful in 1m46s
Rust CI / cargo bench compilation (--features ristretto255) (push) Successful in 1m54s
Rust CI / cargo audit (push) Successful in 6s
Rust CI / no-std (wasm32-unknown-unknown / ristretto255,curve25519,ecdsa,ed25519) (push) Successful in 29s
Rust CI / cargo bench compilation (--features ristretto255,kem) (push) Successful in 2m32s
Rust CI / no-std (wasm32-unknown-unknown / no backend) (push) Successful in 28s
Rust CI / no-std (wasm32-unknown-unknown / curve25519) (push) Successful in 18s
Rust CI / no-std (thumbv6m-none-eabi / no backend) (push) Successful in 28s
Rust CI / no-std (thumbv6m-none-eabi / curve25519) (push) Successful in 28s
Rust CI / no-std (wasm32-unknown-unknown / ecdsa) (push) Successful in 18s
Rust CI / no-std (thumbv6m-none-eabi / ecdsa) (push) Successful in 29s
Rust CI / no-std (thumbv6m-none-eabi / ed25519) (push) Successful in 20s
Rust CI / no-std (wasm32-unknown-unknown / ed25519) (push) Successful in 20s
Rust CI / no-std (thumbv6m-none-eabi / ristretto255) (push) Successful in 28s
Rust CI / no-std (wasm32-unknown-unknown / ristretto255) (push) Successful in 27s
Rust CI / no-std (thumbv6m-none-eabi / ristretto255,curve25519,ecdsa,ed25519) (push) Successful in 18s
chore: update copyright headers to SPDX format and code cleanup (#3)
- Added VexaHub copyright alongside original Meta copyright
- Replaced verbose license blocks with SPDX-License-Identifier
- Minor code improvements

Reviewed-on: #3
Co-authored-by: UneBaguette <[email protected]>
Co-committed-by: UneBaguette <[email protected]>
2026-07-01 20:48:44 +02:00

151 lines
4.3 KiB
Rust

// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Key Exchange group implementation for Curve25519
pub use curve25519_dalek;
use curve25519_dalek::montgomery::MontgomeryPoint;
use curve25519_dalek::scalar;
use curve25519_dalek::traits::IsIdentity;
use generic_array::GenericArray;
use generic_array::typenum::U32;
use rand::{CryptoRng, Rng};
use subtle::ConstantTimeEq;
use zeroize::ZeroizeOnDrop;
use super::Group;
use crate::errors::{InternalError, ProtocolError};
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 Group for Curve25519 {
type Pk = NonIdentity;
type PkLen = U32;
type Sk = Scalar;
type SkLen = U32;
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
pk.0.to_bytes().into()
}
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
bytes
.take_array::<U32>("public key")
.and_then(|bytes| NonIdentity::from_bytes(bytes.into()))
}
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
// 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);
Scalar(scalar)
}
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
Ok(Scalar(scalar::clamp_integer(seed.into())))
}
fn public_key(sk: &Self::Sk) -> Self::Pk {
NonIdentity(MontgomeryPoint::mul_base_clamped(sk.0))
}
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
sk.0.into()
}
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
bytes
.take_array::<U32>("secret key")
.and_then(|bytes| Scalar::from_bytes(bytes.into()))
}
}
impl DiffieHellman<Curve25519> for Scalar {
fn diffie_hellman(&self, pk: &NonIdentity) -> GenericArray<u8, U32> {
Curve25519::serialize_pk(&NonIdentity(pk.0.mul_clamped(self.0)))
}
}
/// Non-identity point wrapper for [`MontgomeryPoint`].
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct NonIdentity(
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_pk"))]
MontgomeryPoint,
);
impl NonIdentity {
fn from_bytes(bytes: [u8; 32]) -> Result<Self, ProtocolError> {
let point = MontgomeryPoint(bytes);
if point.is_identity() {
Err(ProtocolError::SerializationError)
} else {
Ok(NonIdentity(point))
}
}
}
#[cfg(feature = "serde")]
fn serde_deserialize_pk<'de, D>(deserializer: D) -> Result<MontgomeryPoint, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Deserialize, Error};
let point = MontgomeryPoint::deserialize(deserializer)?;
NonIdentity::from_bytes(point.0)
.map(|point| point.0)
.map_err(Error::custom)
}
/// Curve25519 scalar.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct Scalar(
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_sk"))] [u8; 32],
);
impl Scalar {
fn from_bytes(bytes: [u8; 32]) -> Result<Self, ProtocolError> {
let scalar = scalar::clamp_integer(bytes);
if scalar.ct_eq(&bytes).into() {
Ok(Self(scalar))
} else {
Err(ProtocolError::SerializationError)
}
}
}
#[cfg(feature = "serde")]
fn serde_deserialize_sk<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Deserialize, Error};
Scalar::from_bytes(<[u8; 32]>::deserialize(deserializer)?)
.map(|scalar| scalar.0)
.map_err(D::Error::custom)
}
#[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());
}