Skip to main content

ecdh_omr/
hints.rs

1// SPDX-FileCopyrightText: 2024-2026 eaon <eaon@posteo.net>
2// SPDX-License-Identifier: EUPL-1.2
3
4use aead::{Aead, KeyInit};
5use hybrid_array::ArraySize;
6use rand::seq::SliceRandom;
7use rand_core::CryptoRng;
8use typenum::Unsigned;
9
10use crate::{Decoy, Hint, HintSeed, HintSized, Hinting, KeyPair, RandomSecretKey, error::*};
11
12/// Batch of [`Hint`]s that enforces inner vector size as well as shuffling, and also mitigates
13/// potential timing leaks at creation time.
14///
15/// 1. If fewer `HintSeed` items than `S` are supplied during creation, decoy `HintSeed` will fill
16///    the remaining slots.
17/// 2. A new temporary `K::SecretKey` is generated, which is used as a one-off contribution to the
18///    group secrets used to encrypt the respective `Hint`'s messages.
19/// 3. In order to make sure passive attackers don't know which `Hint` to brute force, the `Hints`
20///    order should not be deterministic, so decoy and real `Hint`s get shuffled before the result
21///    is returned.
22///
23/// In aggregate, this ensures that even if the same `HintSeed` is used to create multiple `Hints`
24/// instances, they are indistinguishable to passive observers that want to infer communication
25/// patterns by repeatedly polling a server or other intermediary.
26#[derive(Debug, Clone)]
27pub struct Hints<H, const S: usize> {
28    inner: Vec<H>,
29}
30
31impl<H, const S: usize> FromIterator<H> for Hints<H, S> {
32    fn from_iter<I: IntoIterator<Item = H>>(iter: I) -> Self {
33        let inner = iter.into_iter().collect();
34
35        Self { inner }
36    }
37}
38
39impl<K: KeyPair, A: Aead + KeyInit, L: ArraySize, const S: usize> Hints<Hint<K, A, L>, S>
40where
41    Hint<K, A, L>: HintSized<K, A, L> + Hinting<K, L>,
42{
43    /// Build shuffled batch of [`Hint`] instances from a [`HintSeed`] slice, context, and an RNG,
44    /// resulting in a total of `S` items.
45    ///
46    /// **Note**: Although this associated function attempts to account for it, timing leaks MAY
47    /// happen here. The mitigations' effectiveness has not yet been independently verified.
48    pub fn new(
49        hint_seeds: &[HintSeed<K, L>],
50        context: &[u8],
51        csprng: &mut impl CryptoRng,
52    ) -> Result<Self>
53    where
54        HintSeed<K, L>: Decoy,
55    {
56        let hint_seeds_len = hint_seeds.len();
57        if hint_seeds_len > S {
58            return Err(Error::HintsLength);
59        }
60
61        let hints_secret = K::SecretKey::random_secret_key(csprng);
62
63        // To mitigate timing leaks, we always generate as many decoy HintSeeds we would serve
64        // hints.
65        let decoys: Vec<_> = (0..S)
66            .map(|_| HintSeed::<K, L>::random_decoy(csprng))
67            .collect();
68
69        // Shuffle vector as we're building it to make brute forcing individual items pointless.
70        let mut indices: Vec<usize> = (0..S).collect();
71        indices.shuffle(csprng);
72
73        // We combine the real hint_seeds with as many decoys as we need, and then encrypt all of
74        // them, ensuring that all items take equal time to be created, provided that the underlying
75        // primitives have constant time implementations.
76        indices
77            .into_iter()
78            .map(|i| {
79                let hint_seed = if i < hint_seeds_len {
80                    &hint_seeds[i]
81                } else {
82                    &decoys[i - hint_seeds_len]
83                };
84
85                Hint::<K, A, L>::from_blinding_factor_secret(
86                    &hints_secret,
87                    &hint_seed.blinded_public_key,
88                    &hint_seed.message,
89                    context,
90                )
91            })
92            .collect::<Result<_>>()
93    }
94
95    /// View as slice.
96    pub fn as_slice(&self) -> &[Hint<K, A, L>] {
97        self.inner.as_slice()
98    }
99
100    /// Deserialize from byte slice.
101    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
102        let hint_bytes_length = <Hint<K, A, L> as HintSized<K, A, L>>::Size::USIZE;
103
104        if bytes.len() != hint_bytes_length * S {
105            return Err(Error::HintsLength);
106        }
107
108        Ok(Self {
109            inner: bytes
110                .chunks_exact(hint_bytes_length)
111                .map(|c| Hint::<K, A, L>::from_bytes(c))
112                .collect::<Result<Vec<_>>>()?,
113        })
114    }
115
116    /// Serialize to byte vector.
117    pub fn to_bytes(self) -> Vec<u8> {
118        let mut bytes = Vec::with_capacity(<Hint<K, A, L> as HintSized<K, A, L>>::Size::USIZE * S);
119
120        self.inner
121            .into_iter()
122            .for_each(|hint| bytes.extend(hint.to_bytes()));
123
124        bytes
125    }
126}