1#[cfg(feature = "rustcrypto-ec")]
5use std::ops::Add;
6
7#[cfg(feature = "dalek-ristretto255")]
8use curve25519_dalek::traits::Identity;
9#[cfg(feature = "rustcrypto-ec")]
10use elliptic_curve::{
11 AffinePoint, Curve, CurveArithmetic, Generate, ProjectivePoint,
12 point::PointCompression,
13 sec1::{CompressedPoint, CompressedPointSize, FromSec1Point, ModulusSize, ToSec1Point},
14};
15use hybrid_array::Array;
16#[cfg(feature = "rustcrypto-ec")]
17use hybrid_array::ArraySize;
18use rand_core::CryptoRng;
19use typenum::Sum;
20#[cfg(feature = "rustcrypto-ec")]
21use typenum::Unsigned;
22
23#[cfg(feature = "dalek-x25519")]
24use crate::DalekX25519;
25#[cfg(feature = "rustcrypto-ec")]
26use crate::EllipticCurve;
27#[cfg(feature = "dalek-ristretto255")]
28use crate::{DalekRistretto255, dalek_ristretto255};
29use crate::{KeyPair, error::*};
30
31type BlindedPublicKeySize<C> = Sum<<C as KeyPair>::PublicKeySize, <C as KeyPair>::PublicKeySize>;
32
33#[derive(Debug, Clone)]
36pub struct BlindedPublicKey<K: KeyPair> {
37 pub(crate) inner: K::PublicKey,
39 pub(crate) blinding_factor: K::PublicKey,
45}
46
47pub trait Blind<K: KeyPair> {
49 fn blind(&self, csprng: &mut impl CryptoRng) -> BlindedPublicKey<K>;
51}
52
53pub trait Blinded: Sized {
61 type BytesArray;
63 fn from_bytes(bytes: &Self::BytesArray) -> Result<Self>;
70 fn to_bytes(&self) -> Self::BytesArray;
72}
73
74impl<'a, K: KeyPair> TryFrom<&'a [u8]> for BlindedPublicKey<K>
75where
76 Self: Blinded,
77 <Self as Blinded>::BytesArray: TryFrom<&'a [u8]>,
78{
79 type Error = Error;
80
81 fn try_from(bytes: &'a [u8]) -> Result<Self> {
82 Self::from_bytes(
83 &<Self as Blinded>::BytesArray::try_from(bytes).map_err(|_| Error::Decoding)?,
84 )
85 }
86}
87
88#[cfg(feature = "dalek-ristretto255")]
89pub(crate) fn checked_decompress_ristretto255_public_key(
90 bytes: [u8; 32],
91) -> Result<dalek_ristretto255::PublicKey> {
92 let decompressed = curve25519_dalek::ristretto::CompressedRistretto(bytes)
93 .decompress()
94 .ok_or(Error::Decoding)?;
95
96 (decompressed != curve25519_dalek::ristretto::RistrettoPoint::identity())
97 .then_some(dalek_ristretto255::PublicKey(decompressed))
98 .ok_or(Error::InvalidPoint)
99}
100
101#[cfg(feature = "dalek-x25519")]
102pub(crate) fn checked_x25519_public_key(bytes: [u8; 32]) -> Result<x25519_dalek::PublicKey> {
103 curve25519_dalek::montgomery::MontgomeryPoint(bytes)
105 .to_edwards(0)
106 .filter(|point| !point.is_small_order())
107 .map(|_| x25519_dalek::PublicKey::from(bytes))
108 .ok_or(Error::InvalidPoint)
109}
110
111#[cfg(any(feature = "dalek-ristretto255", feature = "dalek-x25519"))]
112macro_rules! dalek_blinding {
113 (
114 $random_blind_fn:ident,
115 $blind_fn:ident,
116 (
117 $curve:ty,
118 $public:ty,
119 $secret:ty,
120 $public_from_secret:expr,
121 $shared_secret_to_public:expr,
122 $checked_public_key:expr,
123 $public_to_bytes:expr $(,)?
124 ) $(,)?
125 ) => {
126 pub(crate) fn $random_blind_fn(
127 public_key: &$public,
128 csprng: &mut impl CryptoRng,
129 ) -> ($public, $secret) {
130 let blinding_factor_secret = <$secret>::random_from_rng(csprng);
131 let blinded_public_key = $blind_fn(public_key, &blinding_factor_secret);
132
133 (blinded_public_key, blinding_factor_secret)
134 }
135
136 pub(crate) fn $blind_fn(public_key: &$public, blinding_factor_secret: &$secret) -> $public {
137 let blinded_shared_secret = blinding_factor_secret.diffie_hellman(public_key);
138
139 $shared_secret_to_public(blinded_shared_secret)
140 }
141
142 impl Blind<$curve> for $public {
143 fn blind(&self, csprng: &mut impl CryptoRng) -> BlindedPublicKey<$curve> {
144 let (inner, blinding_factor_secret) = $random_blind_fn(self, csprng);
145
146 BlindedPublicKey {
147 inner,
148 blinding_factor: $public_from_secret(&blinding_factor_secret),
149 }
150 }
151 }
152
153 impl Blinded for BlindedPublicKey<$curve> {
154 type BytesArray = Array<u8, BlindedPublicKeySize<$curve>>;
155
156 fn from_bytes(bytes: &Self::BytesArray) -> Result<Self> {
157 let (inner_bytes, blinding_factor_bytes) =
158 bytes.split::<<$curve as KeyPair>::PublicKeySize>();
159 let inner = $checked_public_key(inner_bytes.into())?;
160 let blinding_factor = $checked_public_key(blinding_factor_bytes.into())?;
161
162 Ok(Self {
163 inner,
164 blinding_factor,
165 })
166 }
167
168 fn to_bytes(&self) -> Self::BytesArray {
169 $public_to_bytes(&self.inner)
170 .into_iter()
171 .chain($public_to_bytes(&self.blinding_factor))
172 .collect()
173 }
174 }
175 };
176}
177
178#[cfg(feature = "dalek-ristretto255")]
179dalek_blinding!(
180 random_blind_dalek_ristretto255,
181 blind_dalek_ristretto255,
182 (
183 DalekRistretto255,
184 dalek_ristretto255::PublicKey,
185 dalek_ristretto255::StaticSecret,
186 dalek_ristretto255::PublicKey::from,
187 |secret: dalek_ristretto255::SharedSecret| dalek_ristretto255::PublicKey(secret.0),
188 checked_decompress_ristretto255_public_key,
189 |public: &dalek_ristretto255::PublicKey| public.to_bytes(),
190 ),
191);
192
193#[cfg(feature = "dalek-x25519")]
194dalek_blinding!(
195 random_blind_dalek_x25519,
196 blind_dalek_x25519,
197 (
198 DalekX25519,
199 x25519_dalek::PublicKey,
200 x25519_dalek::StaticSecret,
201 x25519_dalek::PublicKey::from,
202 |secret: x25519_dalek::SharedSecret| x25519_dalek::PublicKey::from(*secret.as_bytes()),
203 checked_x25519_public_key,
204 |public: &x25519_dalek::PublicKey| *public.as_bytes(),
205 ),
206);
207
208#[cfg(feature = "rustcrypto-ec")]
209fn diffie_hellman_affine<C: CurveArithmetic>(
210 secret_key: &elliptic_curve::SecretKey<C>,
211 public_key: &elliptic_curve::PublicKey<C>,
212) -> AffinePoint<C> {
213 use elliptic_curve::group::Curve;
214
215 let public_point = ProjectivePoint::<C>::from(*public_key.as_affine());
216
217 (public_point * secret_key.to_nonzero_scalar().as_ref()).to_affine()
218}
219
220#[cfg(feature = "rustcrypto-ec")]
221pub(crate) fn random_blind_rcec<C: CurveArithmetic>(
222 public_key: &elliptic_curve::PublicKey<C>,
223 csprng: &mut impl CryptoRng,
224) -> (elliptic_curve::PublicKey<C>, elliptic_curve::SecretKey<C>) {
225 let blinding_factor_secret = elliptic_curve::SecretKey::<C>::generate_from_rng(csprng);
226 let blinded_public_key = blind_rcec(public_key, &blinding_factor_secret);
227
228 (blinded_public_key, blinding_factor_secret)
229}
230
231#[cfg(feature = "rustcrypto-ec")]
232pub(crate) fn blind_rcec<C: CurveArithmetic>(
233 public_key: &elliptic_curve::PublicKey<C>,
234 blinding_factor_secret: &elliptic_curve::SecretKey<C>,
235) -> elliptic_curve::PublicKey<C> {
236 let blinded_shared_secret = diffie_hellman_affine(blinding_factor_secret, public_key);
237
238 #[allow(unsafe_code)]
240 unsafe {
241 elliptic_curve::PublicKey::<C>::from_affine(blinded_shared_secret).unwrap_unchecked()
242 }
243}
244
245#[cfg(feature = "rustcrypto-ec")]
246impl<C: CurveArithmetic> Blind<EllipticCurve<C>> for elliptic_curve::PublicKey<C>
247where
248 <C as Curve>::FieldBytesSize: ModulusSize,
249{
250 fn blind(&self, csprng: &mut impl CryptoRng) -> BlindedPublicKey<EllipticCurve<C>> {
251 let (inner, blinding_factor_secret) = random_blind_rcec(self, csprng);
252
253 BlindedPublicKey {
254 inner,
255 blinding_factor: blinding_factor_secret.public_key(),
256 }
257 }
258}
259
260#[cfg(feature = "rustcrypto-ec")]
261impl<C: CurveArithmetic + PointCompression> Blinded for BlindedPublicKey<EllipticCurve<C>>
262where
263 <C as Curve>::FieldBytesSize: ModulusSize,
264 <C as CurveArithmetic>::AffinePoint: ToSec1Point<C> + FromSec1Point<C>,
265 CompressedPointSize<C>: Add<CompressedPointSize<C>> + ArraySize,
266 Sum<CompressedPointSize<C>, CompressedPointSize<C>>: ArraySize,
267{
268 type BytesArray = Array<u8, BlindedPublicKeySize<EllipticCurve<C>>>;
269
270 fn from_bytes(bytes: &Self::BytesArray) -> Result<Self> {
271 let public_size = CompressedPointSize::<C>::USIZE;
274 let from_bytes = elliptic_curve::PublicKey::<C>::from_sec1_bytes;
275
276 Ok(Self {
277 inner: from_bytes(&bytes[..public_size]).map_err(|_| Error::Decoding)?,
278 blinding_factor: from_bytes(&bytes[public_size..]).map_err(|_| Error::Decoding)?,
279 })
280 }
281
282 fn to_bytes(&self) -> Self::BytesArray {
283 CompressedPoint::<C>::from(&self.inner)
284 .into_iter()
285 .chain(CompressedPoint::<C>::from(&self.blinding_factor))
286 .collect()
287 }
288}