Skip to main content

ecdh_omr/
lib.rs

1// SPDX-FileCopyrightText: 2024-2026 eaon <eaon@posteo.net>
2// SPDX-License-Identifier: EUPL-1.2
3
4#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5#![allow(clippy::needless_doctest_main)]
6#![doc = include_str!("../README.md")]
7#![warn(missing_docs)]
8
9use std::fmt::Debug;
10
11use aead::{Aead, AeadCore, KeyInit, Nonce};
12use hybrid_array::{Array, ArraySize};
13use rand_core::CryptoRng;
14use sha3::{Digest, Sha3_256};
15use typenum::{U32, Unsigned};
16
17mod curves;
18pub use curves::*;
19
20mod blinding;
21pub use blinding::*;
22
23mod hint;
24pub use hint::*;
25
26mod hints;
27pub use hints::*;
28
29mod take_the;
30pub use take_the::*;
31
32mod error;
33pub use error::*;
34
35/// Curve implementation agnostic interface for generating random secret keys
36pub trait RandomSecretKey {
37    /// Create a secret key from provided RNG.
38    fn random_secret_key(csprng: &mut impl CryptoRng) -> Self;
39}
40
41/// Generate legitimate looking instances of data structures without user input.
42pub trait Decoy {
43    /// Create a decoy instance from provided RNG.
44    ///
45    /// # Security
46    ///
47    /// Implementers **must** ensure this function generates valid cryptographic values, as invalid
48    /// values may make an honest actor using ECDH-OMR believe the entity relaying [`Hints`] is
49    /// acting dishonestly. In other words: decoys must be valid values and be indistinguishable
50    /// from real values to passive observers.
51    fn random_decoy(csprng: &mut impl CryptoRng) -> Self;
52}
53
54/// An elliptic curve key pair.
55///
56/// Creates a formal relationship between public and secret types.
57pub trait KeyPair {
58    /// An ECDH secret key.
59    type SecretKey: RandomSecretKey;
60    /// An ECDH public key.
61    type PublicKey: Debug + Clone + Decoy;
62    /// Bytes length of the public key
63    type PublicKeySize: ArraySize;
64}
65
66// Turn a shared secret supplied in the form of bytes into a key usable by `aead` implementations
67pub(crate) fn cipher_from_shared_secret<A: Aead + KeyInit>(shared_secret: &[u8]) -> Result<A> {
68    Array::try_from_iter(shared_secret.iter().take(A::KeySize::USIZE).copied())
69        .map(|zz| A::new(&zz))
70        .map_err(|_| Error::Aead(aead::Error))
71}
72
73pub(crate) fn shared_secret(
74    raw_shared_secret: &[u8],
75    blinded_blinding_factor: &[u8],
76    context: &[u8],
77) -> Array<u8, U32> {
78    let mut hasher = <Sha3_256 as Digest>::new();
79    hasher.update(raw_shared_secret);
80    hasher.update(blinded_blinding_factor);
81    hasher.update(context);
82
83    hasher.finalize()
84}
85
86pub(crate) fn nonce<A: AeadCore>(blinded_blinding_factor: &[u8]) -> Result<Nonce<A>> {
87    let mut hasher = <Sha3_256 as Digest>::new();
88    hasher.update("ecdh-omr-nonce-");
89    hasher.update(blinded_blinding_factor);
90    let nonce_bytes = hasher.finalize();
91
92    Array::try_from_iter(nonce_bytes.into_iter().take(A::NonceSize::USIZE))
93        .map_err(|_| Error::Aead(aead::Error))
94}