1use std::marker::PhantomData;
5use std::ops::Add;
6
7use aead::{Aead, AeadCore, KeyInit, Payload};
8#[cfg(feature = "rustcrypto-ec")]
9use elliptic_curve::{
10 Curve, CurveArithmetic,
11 point::PointCompression,
12 sec1::{CompressedPoint, CompressedPointSize, FromSec1Point, ModulusSize, ToSec1Point},
13};
14use hybrid_array::ArraySize;
15use rand_core::CryptoRng;
16use typenum::{Sum, Unsigned};
17
18#[cfg(feature = "dalek-ristretto255")]
19use crate::{
20 DalekRistretto255, blind_dalek_ristretto255, checked_decompress_ristretto255_public_key,
21 dalek_ristretto255, random_blind_dalek_ristretto255,
22};
23#[cfg(feature = "dalek-x25519")]
24use crate::{
25 DalekX25519, blind_dalek_x25519, checked_x25519_public_key, random_blind_dalek_x25519,
26};
27#[cfg(feature = "rustcrypto-ec")]
28use crate::{EllipticCurve, blind_rcec, random_blind_rcec};
29use crate::{error::*, *};
30
31pub trait Hinting<K: KeyPair, L: ArraySize>: Sized {
33 type HintSize: ArraySize;
35
36 fn new(
38 blinded_public_key: &BlindedPublicKey<K>,
39 message: &Array<u8, L>,
40 context: &[u8],
41 csprng: &mut impl CryptoRng,
42 ) -> Result<Self>;
43
44 fn from_blinding_factor_secret(
46 blinding_factor_secret: &K::SecretKey,
47 blinded_public_key: &BlindedPublicKey<K>,
48 message: &Array<u8, L>,
49 context: &[u8],
50 ) -> Result<Self>;
51
52 fn from_bytes(bytes: &[u8]) -> Result<Self>;
59
60 fn to_bytes(self) -> Array<u8, Self::HintSize>;
62}
63
64type HintCiphertextSize<A, L> = Sum<<A as AeadCore>::TagSize, L>;
65type HintSize<K, A, L> = Sum<<K as KeyPair>::PublicKeySize, HintCiphertextSize<A, L>>;
66
67pub trait HintSized<K: KeyPair, A: AeadCore, L: ArraySize>: Sized {
72 type CiphertextSize: ArraySize;
74 type Size: ArraySize;
76}
77
78impl<K: KeyPair, A: Aead + KeyInit, L: ArraySize> HintSized<K, A, L> for Hint<K, A, L>
79where
80 <A as AeadCore>::TagSize: Add<L>,
81 <<A as AeadCore>::TagSize as Add<L>>::Output: ArraySize,
82 <K as KeyPair>::PublicKeySize: Add<HintCiphertextSize<A, L>>,
83 <<K as KeyPair>::PublicKeySize as Add<HintCiphertextSize<A, L>>>::Output: ArraySize,
84{
85 type CiphertextSize = HintCiphertextSize<A, L>;
86 type Size = HintSize<K, A, L>;
87}
88
89#[derive(Debug, Clone)]
92pub struct Hint<K: KeyPair, A: Aead + KeyInit, L: ArraySize>
93where
94 Self: HintSized<K, A, L>,
95{
96 pub(crate) blinded_blinding_factor: K::PublicKey,
101 pub(crate) ciphertext: Array<u8, <Self as HintSized<K, A, L>>::CiphertextSize>,
103 _aead: PhantomData<A>,
105}
106
107fn encrypt<A: Aead + KeyInit>(
108 shared_secret: &[u8],
109 blinded_blinding_factor: &[u8],
110 message: &[u8],
111) -> Result<Vec<u8>> {
112 let cipher = cipher_from_shared_secret::<A>(shared_secret)?;
113 let nonce = nonce::<A>(blinded_blinding_factor)?;
114 let ciphertext = cipher.encrypt(
115 &nonce,
116 Payload {
117 msg: message,
118 aad: blinded_blinding_factor,
119 },
120 )?;
121
122 Ok(ciphertext)
123}
124
125impl<K: KeyPair, A: Aead + KeyInit, L: ArraySize> TryFrom<&[u8]> for Hint<K, A, L>
126where
127 Self: Hinting<K, L> + HintSized<K, A, L>,
128{
129 type Error = Error;
130
131 fn try_from(bytes: &[u8]) -> Result<Self> {
132 Self::from_bytes(bytes)
133 }
134}
135
136#[cfg(any(feature = "dalek-ristretto255", feature = "dalek-x25519"))]
137macro_rules! dalek_hinting {
138 (
139 $construct:ident,
140 (
141 $curve:ty,
142 $public:ty,
143 $secret:ty,
144 $random_blind:ident,
145 $blind:ident,
146 $checked_public_key:expr,
147 $secret_to_bytes:expr,
148 $public_to_bytes:expr $(,)?
149 ) $(,)?
150 ) => {
151 fn $construct<A: Aead + KeyInit, L: ArraySize>(
152 blinded_blinding_factor: $public,
153 blinding_factor_secret: &$secret,
154 blinded_public_key_inner: &$public,
155 message: &Array<u8, L>,
156 context: &[u8],
157 ) -> Result<Hint<$curve, A, L>>
158 where
159 Hint<$curve, A, L>: HintSized<$curve, A, L>,
160 {
161 let raw_shared_secret = blinding_factor_secret.diffie_hellman(blinded_public_key_inner);
162
163 let blinded_blinding_factor_bytes = $public_to_bytes(&blinded_blinding_factor);
164 let raw_shared_secret_bytes = $secret_to_bytes(&raw_shared_secret);
165
166 let shared_secret = shared_secret(
167 &raw_shared_secret_bytes,
168 &blinded_blinding_factor_bytes,
169 context,
170 );
171
172 let ciphertext = encrypt::<A>(
173 &shared_secret,
174 &blinded_blinding_factor_bytes,
175 message.as_ref(),
176 )?
177 .into_iter()
178 .collect();
179
180 Ok(Hint {
181 blinded_blinding_factor,
182 ciphertext,
183 _aead: PhantomData,
184 })
185 }
186
187 impl<A: Aead + KeyInit, L: ArraySize> Hinting<$curve, L> for Hint<$curve, A, L>
188 where
189 Self: HintSized<$curve, A, L>,
190 {
191 type HintSize = <Self as HintSized<$curve, A, L>>::Size;
192
193 fn new(
194 blinded_public_key: &BlindedPublicKey<$curve>,
195 message: &Array<u8, L>,
196 context: &[u8],
197 csprng: &mut impl CryptoRng,
198 ) -> Result<Self> {
199 let (blinded_blinding_factor, blinding_factor_secret) =
200 $random_blind(&blinded_public_key.blinding_factor, csprng);
201
202 $construct(
203 blinded_blinding_factor,
204 &blinding_factor_secret,
205 &blinded_public_key.inner,
206 message,
207 context,
208 )
209 }
210
211 fn from_blinding_factor_secret(
212 blinding_factor_secret: &$secret,
213 blinded_public_key: &BlindedPublicKey<$curve>,
214 message: &Array<u8, L>,
215 context: &[u8],
216 ) -> Result<Self> {
217 let blinded_blinding_factor =
218 $blind(&blinded_public_key.blinding_factor, blinding_factor_secret);
219
220 $construct(
221 blinded_blinding_factor,
222 blinding_factor_secret,
223 &blinded_public_key.inner,
224 message,
225 context,
226 )
227 }
228
229 fn from_bytes(bytes: &[u8]) -> Result<Self> {
230 if bytes.len() != <Self as HintSized<$curve, A, L>>::Size::USIZE {
231 return Err(Error::Decoding);
232 }
233
234 let public_size = <$curve as KeyPair>::PublicKeySize::USIZE;
235 let blinded_blinding_factor_bytes = bytes
236 .iter()
237 .take(public_size)
238 .copied()
239 .collect::<Array<u8, <$curve as KeyPair>::PublicKeySize>>();
240 let blinded_blinding_factor =
241 $checked_public_key(blinded_blinding_factor_bytes.into())?;
242 let ciphertext = bytes.iter().skip(public_size).copied().collect();
243
244 Ok(Self {
245 blinded_blinding_factor,
246 ciphertext,
247 _aead: PhantomData,
248 })
249 }
250
251 fn to_bytes(self) -> Array<u8, Self::HintSize> {
252 $public_to_bytes(&self.blinded_blinding_factor)
253 .into_iter()
254 .chain(self.ciphertext)
255 .collect()
256 }
257 }
258 };
259}
260
261#[cfg(feature = "dalek-ristretto255")]
262dalek_hinting!(
263 construct_dalek_ristretto255_hint,
264 (
265 DalekRistretto255,
266 dalek_ristretto255::PublicKey,
267 dalek_ristretto255::StaticSecret,
268 random_blind_dalek_ristretto255,
269 blind_dalek_ristretto255,
270 checked_decompress_ristretto255_public_key,
271 |ss: &dalek_ristretto255::SharedSecret| ss.to_bytes(),
272 |pk: &dalek_ristretto255::PublicKey| pk.to_bytes(),
273 ),
274);
275
276#[cfg(feature = "dalek-x25519")]
277dalek_hinting!(
278 construct_dalek_x25519_hint,
279 (
280 DalekX25519,
281 x25519_dalek::PublicKey,
282 x25519_dalek::StaticSecret,
283 random_blind_dalek_x25519,
284 blind_dalek_x25519,
285 checked_x25519_public_key,
286 |ss: &x25519_dalek::SharedSecret| *ss.as_bytes(),
287 |pk: &x25519_dalek::PublicKey| *pk.as_bytes(),
288 ),
289);
290
291#[cfg(feature = "rustcrypto-ec")]
292fn construct_rustcrypto_ec_hint<A, C, L: ArraySize>(
293 blinded_blinding_factor: elliptic_curve::PublicKey<C>,
294 blinding_factor_secret: &elliptic_curve::SecretKey<C>,
295 blinded_public_key_inner: &elliptic_curve::PublicKey<C>,
296 message: &Array<u8, L>,
297 context: &[u8],
298) -> Result<Hint<EllipticCurve<C>, A, L>>
299where
300 A: Aead + KeyInit,
301 C: CurveArithmetic + PointCompression,
302 <C as Curve>::FieldBytesSize: ModulusSize,
303 <C as CurveArithmetic>::AffinePoint: ToSec1Point<C> + FromSec1Point<C>,
304 Hint<EllipticCurve<C>, A, L>: HintSized<EllipticCurve<C>, A, L>,
305{
306 let raw_shared_secret = elliptic_curve::ecdh::diffie_hellman(
307 blinding_factor_secret.to_nonzero_scalar(),
308 blinded_public_key_inner.as_affine(),
309 );
310
311 let blinded_blinding_factor_cp = CompressedPoint::<C>::from(&blinded_blinding_factor);
312
313 let shared_secret = shared_secret(
314 raw_shared_secret.raw_secret_bytes(),
315 blinded_blinding_factor_cp.as_slice(),
316 context,
317 );
318
319 let ciphertext = encrypt::<A>(
320 &shared_secret,
321 blinded_blinding_factor_cp.as_slice(),
322 message,
323 )?
324 .into_iter()
325 .collect();
326
327 Ok(Hint {
328 blinded_blinding_factor,
329 ciphertext,
330 _aead: PhantomData,
331 })
332}
333
334#[cfg(feature = "rustcrypto-ec")]
335impl<A: Aead + KeyInit, C: CurveArithmetic + PointCompression, L: ArraySize>
336 Hinting<EllipticCurve<C>, L> for Hint<EllipticCurve<C>, A, L>
337where
338 <C as Curve>::FieldBytesSize: ModulusSize,
339 <C as CurveArithmetic>::AffinePoint: ToSec1Point<C> + FromSec1Point<C>,
340 Self: HintSized<EllipticCurve<C>, A, L>,
341{
342 type HintSize = <Self as HintSized<EllipticCurve<C>, A, L>>::Size;
343
344 fn new(
345 blinded_public_key: &BlindedPublicKey<EllipticCurve<C>>,
346 message: &Array<u8, L>,
347 context: &[u8],
348 csprng: &mut impl CryptoRng,
349 ) -> Result<Self> {
350 let (blinded_blinding_factor, blinding_factor_secret) =
351 random_blind_rcec(&blinded_public_key.blinding_factor, csprng);
352
353 construct_rustcrypto_ec_hint(
354 blinded_blinding_factor,
355 &blinding_factor_secret,
356 &blinded_public_key.inner,
357 message,
358 context,
359 )
360 }
361
362 fn from_blinding_factor_secret(
363 blinding_factor_secret: &elliptic_curve::SecretKey<C>,
364 blinded_public_key: &BlindedPublicKey<EllipticCurve<C>>,
365 message: &Array<u8, L>,
366 context: &[u8],
367 ) -> Result<Self> {
368 let blinded_blinding_factor =
369 blind_rcec(&blinded_public_key.blinding_factor, blinding_factor_secret);
370
371 construct_rustcrypto_ec_hint(
372 blinded_blinding_factor,
373 blinding_factor_secret,
374 &blinded_public_key.inner,
375 message,
376 context,
377 )
378 }
379
380 fn from_bytes(bytes: &[u8]) -> Result<Self> {
381 if bytes.len() != <Self as HintSized<EllipticCurve<C>, A, L>>::Size::USIZE {
382 return Err(Error::Decoding);
383 }
384
385 let public_size = CompressedPointSize::<C>::USIZE;
386 let blinded_blinding_factor =
387 elliptic_curve::PublicKey::<C>::from_sec1_bytes(&bytes[..public_size])
388 .map_err(|_| Error::Decoding)?;
389 let ciphertext = bytes.iter().skip(public_size).copied().collect();
390
391 Ok(Self {
392 blinded_blinding_factor,
393 ciphertext,
394 _aead: PhantomData,
395 })
396 }
397
398 fn to_bytes(self) -> Array<u8, Self::HintSize> {
399 CompressedPoint::<C>::from(&self.blinded_blinding_factor)
400 .into_iter()
401 .chain(self.ciphertext)
402 .collect()
403 }
404}
405
406#[derive(Debug, Clone)]
410pub struct HintSeed<K: KeyPair, L: ArraySize> {
411 pub blinded_public_key: BlindedPublicKey<K>,
413 pub message: Array<u8, L>,
415}
416
417impl<K: KeyPair, L: ArraySize> HintSeed<K, L> {
418 pub fn new(blinded_public_key: BlindedPublicKey<K>, message: Array<u8, L>) -> Self {
420 Self {
421 blinded_public_key,
422 message,
423 }
424 }
425}
426
427impl<K: KeyPair, L: ArraySize> Decoy for HintSeed<K, L> {
428 fn random_decoy(csprng: &mut impl CryptoRng) -> Self {
429 Self {
430 blinded_public_key: BlindedPublicKey {
431 inner: K::PublicKey::random_decoy(csprng),
432 blinding_factor: K::PublicKey::random_decoy(csprng),
433 },
434 message: Array::default(),
435 }
436 }
437}