ecdh_omr/
blinding.rs

1// SPDX-FileCopyrightText: 2024 eaon <eaon@posteo.net>
2// SPDX-License-Identifier: EUPL-1.2
3
4#[cfg(feature = "rustcrypto-ec")]
5use elliptic_curve::{
6    point::PointCompression,
7    sec1::{CompressedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint},
8    AffinePoint, Curve, CurveArithmetic,
9};
10use rand_core::CryptoRngCore;
11
12use crate::{curves, curves::KeyPair, error::*};
13#[cfg(feature = "dalek")]
14use curves::{dalek, X25519};
15#[cfg(feature = "rustcrypto-ec")]
16use curves::{rcec, EllipticCurve};
17
18/// An ECDH public key that has been blinded, enabling a third party to send a message without
19/// knowing the cryptographic identity of the recipient.
20#[derive(Clone, Debug)]
21pub struct BlindedPublicKey<K: KeyPair> {
22    /// The blinded public key that can be used to perform a normal Diffie-Hellman key agreement.
23    pub inner: K::PublicKey,
24    /// Blinding factor used to create the blinded public key.
25    ///
26    /// Carrying this piece of information is necessary because we want a third party to be able to
27    /// share a secret of its choosing with the party controlling the secret key of the blinded
28    /// public key.
29    pub blinding_factor: K::PublicKey,
30}
31
32#[cfg(feature = "dalek")]
33impl Blinded for BlindedPublicKey<X25519> {
34    type BytesArray = [u8; 64];
35
36    fn from_bytes(bytes: &Self::BytesArray) -> Result<Self> {
37        let inner_bytes: [u8; 32] = bytes[0..32].try_into().map_err(|_| Error::Decoding)?;
38        let inner = dalek::PublicKey::from(inner_bytes);
39        let blinding_factor_bytes: [u8; 32] =
40            bytes[32..64].try_into().map_err(|_| Error::Decoding)?;
41        let blinding_factor = dalek::PublicKey::from(blinding_factor_bytes);
42
43        Ok(Self {
44            inner,
45            blinding_factor,
46        })
47    }
48
49    fn to_bytes(&self) -> Self::BytesArray {
50        let mut output = [0u8; 64];
51        output[0..32].copy_from_slice(self.inner.as_bytes());
52        output[32..64].copy_from_slice(self.blinding_factor.as_bytes());
53
54        output
55    }
56}
57
58#[cfg(feature = "rustcrypto-ec")]
59impl<C: CurveArithmetic + PointCompression> Blinded for BlindedPublicKey<EllipticCurve<C>>
60where
61    <C as Curve>::FieldBytesSize: ModulusSize,
62    <C as CurveArithmetic>::AffinePoint: ToEncodedPoint<C> + FromEncodedPoint<C>,
63{
64    type BytesArray = [u8; 66];
65
66    fn from_bytes(bytes: &Self::BytesArray) -> Result<Self> {
67        let inner =
68            rcec::PublicKey::<C>::from_sec1_bytes(&bytes[0..33]).map_err(|_| Error::Decoding)?;
69        let blinding_factor =
70            rcec::PublicKey::<C>::from_sec1_bytes(&bytes[33..66]).map_err(|_| Error::Decoding)?;
71
72        Ok(Self {
73            inner,
74            blinding_factor,
75        })
76    }
77
78    fn to_bytes(&self) -> [u8; 66] {
79        let inner_cp = CompressedPoint::<C>::from(&self.inner);
80        let blinding_factor_cp = CompressedPoint::<C>::from(&self.blinding_factor);
81
82        let mut output = [0u8; 66];
83        output[0..33].copy_from_slice(inner_cp.as_slice());
84        output[33..66].copy_from_slice(blinding_factor_cp.as_slice());
85
86        output
87    }
88}
89
90/// Blind a public key.
91pub trait Blind<K: KeyPair> {
92    /// Blind a public key with the supplied RNG.
93    fn blind(&self, csprng: &mut impl CryptoRngCore) -> BlindedPublicKey<K>;
94}
95
96/// (De)serialization for [`BlindedPublicKey`].
97pub trait Blinded: Sized {
98    /// A bytes array type with the size of the serialized [`BlindedPublicKey`].
99    type BytesArray;
100    /// Parse a [`BlindedPublicKey`] from a `BytesArray`.
101    fn from_bytes(bytes: &Self::BytesArray) -> Result<Self>;
102    /// Serialize [`BlindedPublicKey`] to a `BytesArray`.
103    fn to_bytes(&self) -> Self::BytesArray;
104}
105
106impl<'a, K: KeyPair> TryFrom<&'a [u8]> for BlindedPublicKey<K>
107where
108    Self: Blinded,
109    <Self as Blinded>::BytesArray: TryFrom<&'a [u8]>,
110{
111    type Error = Error;
112
113    fn try_from(bytes: &'a [u8]) -> Result<Self> {
114        Self::from_bytes(
115            &<Self as Blinded>::BytesArray::try_from(bytes).map_err(|_| Error::Decoding)?,
116        )
117    }
118}
119
120#[cfg(feature = "dalek")]
121pub(crate) fn random_blind_dalek(
122    public_key: &dalek::PublicKey,
123    return_blinding_factor: bool,
124    csprng: &mut impl CryptoRngCore,
125) -> (
126    dalek::PublicKey,
127    Option<dalek::PublicKey>,
128    dalek::StaticSecret,
129) {
130    let blinding_factor_secret = dalek::StaticSecret::random_from_rng(csprng);
131    let blinding_factor = if return_blinding_factor {
132        Some(dalek::PublicKey::from(&blinding_factor_secret))
133    } else {
134        None
135    };
136    let blinded_public_key = blind_dalek(public_key, &blinding_factor_secret);
137
138    (blinded_public_key, blinding_factor, blinding_factor_secret)
139}
140
141#[cfg(feature = "dalek")]
142pub(crate) fn blind_dalek(
143    public_key: &dalek::PublicKey,
144    blinding_factor_secret: &dalek::StaticSecret,
145) -> dalek::PublicKey {
146    let blinded_shared_secret = blinding_factor_secret.diffie_hellman(public_key);
147
148    dalek::PublicKey::from(*blinded_shared_secret.as_bytes())
149}
150
151#[cfg(feature = "dalek")]
152impl Blind<X25519> for dalek::PublicKey {
153    fn blind(&self, csprng: &mut impl CryptoRngCore) -> BlindedPublicKey<X25519> {
154        let (inner, blinding_factor, _) = random_blind_dalek(self, true, csprng);
155
156        BlindedPublicKey {
157            inner,
158            blinding_factor: blinding_factor.expect("Infallible"),
159        }
160    }
161}
162
163#[cfg(feature = "rustcrypto-ec")]
164fn diffie_hellman_affine<C: CurveArithmetic>(
165    secret_key: &rcec::SecretKey<C>,
166    public_key: &rcec::PublicKey<C>,
167) -> AffinePoint<C> {
168    use elliptic_curve::{group::Curve, ProjectivePoint};
169
170    let public_point = ProjectivePoint::<C>::from(*public_key.as_affine());
171
172    (public_point * secret_key.to_nonzero_scalar().as_ref()).to_affine()
173}
174
175#[cfg(feature = "rustcrypto-ec")]
176pub(crate) fn random_blind_rcec<C: CurveArithmetic>(
177    public_key: &rcec::PublicKey<C>,
178    return_blinding_factor: bool,
179    csprng: &mut impl CryptoRngCore,
180) -> (
181    rcec::PublicKey<C>,
182    Option<rcec::PublicKey<C>>,
183    rcec::SecretKey<C>,
184) {
185    let blinding_factor_secret = rcec::SecretKey::<C>::random(csprng);
186    let blinding_factor = if return_blinding_factor {
187        Some(blinding_factor_secret.public_key())
188    } else {
189        None
190    };
191    let blinded_public_key = blind_rcec(public_key, &blinding_factor_secret);
192
193    (blinded_public_key, blinding_factor, blinding_factor_secret)
194}
195
196#[cfg(feature = "rustcrypto-ec")]
197pub(crate) fn blind_rcec<C: CurveArithmetic>(
198    public_key: &rcec::PublicKey<C>,
199    blinding_factor_secret: &rcec::SecretKey<C>,
200) -> rcec::PublicKey<C> {
201    let blinded_shared_secret = diffie_hellman_affine(blinding_factor_secret, public_key);
202
203    rcec::PublicKey::<C>::from_affine(blinded_shared_secret)
204        .expect("Should not be an identity point")
205}
206
207#[cfg(feature = "rustcrypto-ec")]
208impl<C: CurveArithmetic> Blind<EllipticCurve<C>> for rcec::PublicKey<C> {
209    fn blind(&self, csprng: &mut impl CryptoRngCore) -> BlindedPublicKey<EllipticCurve<C>> {
210        let (inner, blinding_factor, _) = random_blind_rcec(self, true, csprng);
211
212        BlindedPublicKey {
213            inner,
214            blinding_factor: blinding_factor.expect("Infallible"),
215        }
216    }
217}