ecdh-omr 0.2.0

ECDH based Oblivious Message Retrieval
Documentation
// SPDX-FileCopyrightText: 2024 eaon <eaon@posteo.net>
// SPDX-License-Identifier: EUPL-1.2

#[cfg(feature = "rustcrypto-ec")]
use elliptic_curve::{
    point::PointCompression,
    sec1::{CompressedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint},
    AffinePoint, Curve, CurveArithmetic,
};
use rand_core::CryptoRngCore;

use crate::{curves, curves::KeyPair, error::*};
#[cfg(feature = "dalek")]
use curves::{dalek, X25519};
#[cfg(feature = "rustcrypto-ec")]
use curves::{rcec, EllipticCurve};

/// An ECDH public key that has been blinded, enabling a third party to send a message without
/// knowing the cryptographic identity of the recipient.
#[derive(Clone, Debug)]
pub struct BlindedPublicKey<K: KeyPair> {
    /// The blinded public key that can be used to perform a normal Diffie-Hellman key agreement.
    pub inner: K::PublicKey,
    /// Blinding factor used to create the blinded public key.
    ///
    /// Carrying this piece of information is necessary because we want a third party to be able to
    /// share a secret of its choosing with the party controlling the secret key of the blinded
    /// public key.
    pub blinding_factor: K::PublicKey,
}

#[cfg(feature = "dalek")]
impl Blinded for BlindedPublicKey<X25519> {
    type BytesArray = [u8; 64];

    fn from_bytes(bytes: &Self::BytesArray) -> Result<Self> {
        let inner_bytes: [u8; 32] = bytes[0..32].try_into().map_err(|_| Error::Decoding)?;
        let inner = dalek::PublicKey::from(inner_bytes);
        let blinding_factor_bytes: [u8; 32] =
            bytes[32..64].try_into().map_err(|_| Error::Decoding)?;
        let blinding_factor = dalek::PublicKey::from(blinding_factor_bytes);

        Ok(Self {
            inner,
            blinding_factor,
        })
    }

    fn to_bytes(&self) -> Self::BytesArray {
        let mut output = [0u8; 64];
        output[0..32].copy_from_slice(self.inner.as_bytes());
        output[32..64].copy_from_slice(self.blinding_factor.as_bytes());

        output
    }
}

#[cfg(feature = "rustcrypto-ec")]
impl<C: CurveArithmetic + PointCompression> Blinded for BlindedPublicKey<EllipticCurve<C>>
where
    <C as Curve>::FieldBytesSize: ModulusSize,
    <C as CurveArithmetic>::AffinePoint: ToEncodedPoint<C> + FromEncodedPoint<C>,
{
    type BytesArray = [u8; 66];

    fn from_bytes(bytes: &Self::BytesArray) -> Result<Self> {
        let inner =
            rcec::PublicKey::<C>::from_sec1_bytes(&bytes[0..33]).map_err(|_| Error::Decoding)?;
        let blinding_factor =
            rcec::PublicKey::<C>::from_sec1_bytes(&bytes[33..66]).map_err(|_| Error::Decoding)?;

        Ok(Self {
            inner,
            blinding_factor,
        })
    }

    fn to_bytes(&self) -> [u8; 66] {
        let inner_cp = CompressedPoint::<C>::from(&self.inner);
        let blinding_factor_cp = CompressedPoint::<C>::from(&self.blinding_factor);

        let mut output = [0u8; 66];
        output[0..33].copy_from_slice(inner_cp.as_slice());
        output[33..66].copy_from_slice(blinding_factor_cp.as_slice());

        output
    }
}

/// Blind a public key.
pub trait Blind<K: KeyPair> {
    /// Blind a public key with the supplied RNG.
    fn blind(&self, csprng: &mut impl CryptoRngCore) -> BlindedPublicKey<K>;
}

/// (De)serialization for [`BlindedPublicKey`].
pub trait Blinded: Sized {
    /// A bytes array type with the size of the serialized [`BlindedPublicKey`].
    type BytesArray;
    /// Parse a [`BlindedPublicKey`] from a `BytesArray`.
    fn from_bytes(bytes: &Self::BytesArray) -> Result<Self>;
    /// Serialize [`BlindedPublicKey`] to a `BytesArray`.
    fn to_bytes(&self) -> Self::BytesArray;
}

impl<'a, K: KeyPair> TryFrom<&'a [u8]> for BlindedPublicKey<K>
where
    Self: Blinded,
    <Self as Blinded>::BytesArray: TryFrom<&'a [u8]>,
{
    type Error = Error;

    fn try_from(bytes: &'a [u8]) -> Result<Self> {
        Self::from_bytes(
            &<Self as Blinded>::BytesArray::try_from(bytes).map_err(|_| Error::Decoding)?,
        )
    }
}

#[cfg(feature = "dalek")]
pub(crate) fn random_blind_dalek(
    public_key: &dalek::PublicKey,
    return_blinding_factor: bool,
    csprng: &mut impl CryptoRngCore,
) -> (
    dalek::PublicKey,
    Option<dalek::PublicKey>,
    dalek::StaticSecret,
) {
    let blinding_factor_secret = dalek::StaticSecret::random_from_rng(csprng);
    let blinding_factor = if return_blinding_factor {
        Some(dalek::PublicKey::from(&blinding_factor_secret))
    } else {
        None
    };
    let blinded_public_key = blind_dalek(public_key, &blinding_factor_secret);

    (blinded_public_key, blinding_factor, blinding_factor_secret)
}

#[cfg(feature = "dalek")]
pub(crate) fn blind_dalek(
    public_key: &dalek::PublicKey,
    blinding_factor_secret: &dalek::StaticSecret,
) -> dalek::PublicKey {
    let blinded_shared_secret = blinding_factor_secret.diffie_hellman(public_key);

    dalek::PublicKey::from(*blinded_shared_secret.as_bytes())
}

#[cfg(feature = "dalek")]
impl Blind<X25519> for dalek::PublicKey {
    fn blind(&self, csprng: &mut impl CryptoRngCore) -> BlindedPublicKey<X25519> {
        let (inner, blinding_factor, _) = random_blind_dalek(self, true, csprng);

        BlindedPublicKey {
            inner,
            blinding_factor: blinding_factor.expect("Infallible"),
        }
    }
}

#[cfg(feature = "rustcrypto-ec")]
fn diffie_hellman_affine<C: CurveArithmetic>(
    secret_key: &rcec::SecretKey<C>,
    public_key: &rcec::PublicKey<C>,
) -> AffinePoint<C> {
    use elliptic_curve::{group::Curve, ProjectivePoint};

    let public_point = ProjectivePoint::<C>::from(*public_key.as_affine());

    (public_point * secret_key.to_nonzero_scalar().as_ref()).to_affine()
}

#[cfg(feature = "rustcrypto-ec")]
pub(crate) fn random_blind_rcec<C: CurveArithmetic>(
    public_key: &rcec::PublicKey<C>,
    return_blinding_factor: bool,
    csprng: &mut impl CryptoRngCore,
) -> (
    rcec::PublicKey<C>,
    Option<rcec::PublicKey<C>>,
    rcec::SecretKey<C>,
) {
    let blinding_factor_secret = rcec::SecretKey::<C>::random(csprng);
    let blinding_factor = if return_blinding_factor {
        Some(blinding_factor_secret.public_key())
    } else {
        None
    };
    let blinded_public_key = blind_rcec(public_key, &blinding_factor_secret);

    (blinded_public_key, blinding_factor, blinding_factor_secret)
}

#[cfg(feature = "rustcrypto-ec")]
pub(crate) fn blind_rcec<C: CurveArithmetic>(
    public_key: &rcec::PublicKey<C>,
    blinding_factor_secret: &rcec::SecretKey<C>,
) -> rcec::PublicKey<C> {
    let blinded_shared_secret = diffie_hellman_affine(blinding_factor_secret, public_key);

    rcec::PublicKey::<C>::from_affine(blinded_shared_secret)
        .expect("Should not be an identity point")
}

#[cfg(feature = "rustcrypto-ec")]
impl<C: CurveArithmetic> Blind<EllipticCurve<C>> for rcec::PublicKey<C> {
    fn blind(&self, csprng: &mut impl CryptoRngCore) -> BlindedPublicKey<EllipticCurve<C>> {
        let (inner, blinding_factor, _) = random_blind_rcec(self, true, csprng);

        BlindedPublicKey {
            inner,
            blinding_factor: blinding_factor.expect("Infallible"),
        }
    }
}