ecdh-omr 0.2.0

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

use aead::{Aead, KeyInit};
use rand::seq::SliceRandom;
use rand_core::CryptoRngCore;

use crate::{
    Decoy, Hint, HintSeed, Hinting,
    curves::{KeyPair, sealed},
    error::*,
};

/// Batch of [`Hint`]s that enforces inner vector size as well as shuffling, and also mitigates
/// potential timing leaks at creation time.
///
/// 1. If fewer `HintSeed` items than `S` are supplied during creation, decoy `HintSeed` will fill
///    the remaining slots.
/// 2. A new temporary `K::SecretKey` is generated, which is used as a one-off contribution to the
///    group secrets used to encrypt the respective `Hint`'s messages.
/// 3. In order to make sure passive attackers don't know which `Hint` to brute force, the `Hints`
///    order should not be deterministic, so decoy and real `Hint`s get shuffled before the result
///    is returned.
///
/// In aggregate, this ensures that even if the same `HintSeed` is used to create multiple `Hints`
/// instances, they are indistinguishable to passive observers that want to infer communication
/// patterns by repeatedly polling a server or other intermediary.
pub struct Hints<H, const S: usize> {
    inner: Vec<H>,
}

impl<K: KeyPair, A: Aead + KeyInit, const L: usize, const S: usize> Hints<Hint<K, A, L>, S>
where
    Hint<K, A, L>: Hinting<K, L>,
{
    /// Build shuffled batch of [`Hint`] instances from a [`HintSeed`] slice, salt, and an RNG,
    /// resulting in a total of `S` items.
    ///
    /// **Note**: Although this associated function attempts to account for it, timing leaks MAY
    /// happen here. The mitigations' effectiveness has not yet been independently verified.
    pub fn new(
        hint_seeds: &[HintSeed<K, L>],
        salt: &[u8],
        csprng: &mut impl CryptoRngCore,
    ) -> Result<Self>
    where
        HintSeed<K, L>: Decoy,
        K::SecretKey: sealed::RandomSecretKey,
    {
        let hint_seeds_len = hint_seeds.len();
        if hint_seeds_len > S {
            return Err(Error::HintsLength);
        }

        let hints_secret = <K::SecretKey as sealed::RandomSecretKey>::random_secret_key(csprng);

        // To mitigate timing leaks, we always generate as many decoy HintSeeds we would serve
        // hints.
        let decoys: Vec<_> = (0..S)
            .map(|_| HintSeed::<K, L>::random_decoy(csprng))
            .collect();

        // Shuffle vector as we're building it to make brute forcing individual items pointless.
        let mut indices: Vec<usize> = (0..S).collect();
        indices.shuffle(csprng);

        // We combine the real hint_seeds with as many decoys as we need, and then encrypt all of
        // them, ensuring that all items take equal time to be created, provided that the underlying
        // primitives have constant time implementations.
        let inner: Vec<_> = indices
            .into_iter()
            .map(|i| {
                let hint_seed = if i < hint_seeds_len {
                    &hint_seeds[i]
                } else {
                    &decoys[i - hint_seeds_len]
                };
                Hint::<K, A, L>::from_blinding_factor_secret(
                    &hints_secret,
                    &hint_seed.blinded_public_key,
                    &hint_seed.message,
                    salt,
                )
            })
            .collect::<Result<_>>()?;

        Ok(Self { inner })
    }

    /// View as slice.
    pub fn as_slice(&self) -> &[Hint<K, A, L>] {
        self.inner.as_slice()
    }

    /// Deserialize from byte slice.
    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
        let hint_bytes_length = Hint::<K, A, L>::bytes_length();

        if bytes.len() / hint_bytes_length != S {
            return Err(Error::HintsLength);
        }

        Ok(Self {
            inner: bytes
                .chunks_exact(hint_bytes_length)
                .map(|c| Hint::<K, A, L>::from_bytes(c))
                .collect::<Result<Vec<_>>>()?,
        })
    }

    /// Serialize to byte vector.
    pub fn to_bytes(self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(Hint::<K, A, L>::bytes_length() * S);

        for hint in self.inner {
            bytes.append(&mut hint.to_bytes());
        }

        bytes
    }
}