Skip to main content

securedrop_protocol_minimal/primitives/
ristretto255.rs

1//! Diffie-Hellman over ristretto255 [RFC 9496](https://www.rfc-editor.org/rfc/rfc9496)
2use anyhow::Error;
3use rand_core::{CryptoRng, RngCore};
4
5use crate::primitives::provider;
6use crate::primitives::provider::ristretto255::{Point, Scalar};
7
8pub const DH_PUBLIC_KEY_LEN: usize = provider::ristretto255::PK_LEN;
9pub(crate) const DH_PRIVATE_KEY_LEN: usize = provider::ristretto255::SK_LEN;
10
11/// Uniform bytes required to derive a scalar per [RFC 9496] section 4.4.
12pub const DH_SEED_LEN: usize = provider::ristretto255::SEED_LEN;
13
14/// A ristretto255 group element.
15///
16/// # Security
17///
18/// This can be instantiated only by decoding, by the element derivation
19/// function, or by a group operation, so it is always a valid ristretto255
20/// element. The element is held decompressed, so repeated operations on the
21/// same key do not repeat point decompression.
22#[derive(Debug, Clone, Copy)]
23pub struct DHPublicKey(Point);
24
25/// A ristretto255 scalar in $\mathbb{Z}_\ell$.
26///
27/// This can be instantiated only by validating decode or by wide reduction, so
28/// it is always canonical.
29#[derive(Debug, Clone)]
30pub struct DHPrivateKey(Scalar);
31
32impl DHPublicKey {
33    /// Decode a group element from its 32 byte encoding, validating that it is a
34    /// real ristretto255 element.
35    ///
36    /// This must be used for any untrusted bytes (wire or storage) such that an
37    /// invalid element cannot be instantiated.
38    pub fn decode(bytes: [u8; DH_PUBLIC_KEY_LEN]) -> Result<Self, Error> {
39        provider::ristretto255::decode(&bytes)
40            .map(Self)
41            .ok_or_else(|| anyhow::anyhow!("invalid ristretto255 point encoding"))
42    }
43
44    /// The canonical 32-byte encoding of this element.
45    pub fn into_bytes(self) -> [u8; DH_PUBLIC_KEY_LEN] {
46        provider::ristretto255::encode(&self.0)
47    }
48}
49
50impl DHPrivateKey {
51    /// Decode a scalar from bytes, validating it is a canonical element of
52    /// $\mathbb{Z}_\ell$.
53    pub fn decode(bytes: [u8; DH_PRIVATE_KEY_LEN]) -> Result<Self, Error> {
54        provider::ristretto255::scalar_decode(&bytes)
55            .map(Self)
56            .ok_or_else(|| anyhow::anyhow!("non-canonical ristretto255 scalar"))
57    }
58
59    /// Derive the public key $[sk] B$.
60    pub fn public_key(&self) -> DHPublicKey {
61        DHPublicKey(provider::ristretto255::secret_to_public(&self.0))
62    }
63
64    /// The canonical encoding of this scalar.
65    pub fn to_bytes(&self) -> [u8; DH_PRIVATE_KEY_LEN] {
66        provider::ristretto255::scalar_encode(&self.0)
67    }
68}
69
70/// Derive a DH keypair from a caller-supplied uniform seed.
71///
72/// Used for keys that must be reproducible from a key hierarchy (the source's
73/// $sk_S^{fetch}$), and for deterministic tests.
74pub(crate) fn deterministic_dh_keygen(
75    randomness: [u8; DH_SEED_LEN],
76) -> (DHPrivateKey, DHPublicKey) {
77    let secret_key = DHPrivateKey(provider::ristretto255::scalar_from_wide(&randomness));
78    let public_key = secret_key.public_key();
79
80    (secret_key, public_key)
81}
82
83/// Generate a new ristretto255 DH keypair
84pub fn generate_dh_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> (DHPrivateKey, DHPublicKey) {
85    let mut randomness = [0u8; DH_SEED_LEN];
86    provider::rng::fill_bytes(rng, &mut randomness);
87
88    deterministic_dh_keygen(randomness)
89}
90
91/// The seed to [`placeholder_public_key`].
92const PLACEHOLDER_SEED: &[u8] = b"securedrop-protocol-placeholder-v1";
93
94/// A fixed group element used as a placeholder value.
95pub fn placeholder_public_key() -> DHPublicKey {
96    let seed = provider::sha2::sha512(PLACEHOLDER_SEED);
97
98    DHPublicKey(provider::ristretto255::from_uniform_bytes(&seed))
99}
100
101/// Sample a uniformly random group element.
102pub fn random_dh_public_key<R: RngCore + CryptoRng>(rng: &mut R) -> DHPublicKey {
103    let mut randomness = [0u8; DH_SEED_LEN];
104    provider::rng::fill_bytes(rng, &mut randomness);
105
106    DHPublicKey(provider::ristretto255::from_uniform_bytes(&randomness))
107}
108
109/// Sample a scalar $x \gets^{\$} \mathbb{F}_\ell$.
110pub fn generate_random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> DHPrivateKey {
111    let mut randomness = [0u8; DH_SEED_LEN];
112    provider::rng::fill_bytes(rng, &mut randomness);
113
114    DHPrivateKey(provider::ristretto255::scalar_from_wide(&randomness))
115}
116
117/// Compute DH agreement.
118pub fn dh_shared_secret(public_key: &DHPublicKey, scalar: &DHPrivateKey) -> DHPublicKey {
119    DHPublicKey(provider::ristretto255::dh(&public_key.0, &scalar.0))
120}
121
122#[cfg_attr(hax, hax_lib::exclude)]
123impl serde::Serialize for DHPublicKey {
124    fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
125        ser.serialize_str(&hex::encode(self.into_bytes()))
126    }
127}
128
129#[cfg_attr(hax, hax_lib::exclude)]
130impl<'de> serde::Deserialize<'de> for DHPublicKey {
131    fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
132        use serde::de::Error as _;
133        let s = alloc::string::String::deserialize(de)?;
134        let mut bytes = [0u8; DH_PUBLIC_KEY_LEN];
135        hex::decode_to_slice(s.trim(), &mut bytes).map_err(D::Error::custom)?;
136        // We validate at the wire boundary and reject a malformed encoding here.
137        Self::decode(bytes).map_err(D::Error::custom)
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use proptest::prelude::*;
145    use rand_chacha::ChaCha20Rng;
146    use rand_core::SeedableRng;
147
148    fn get_rng() -> ChaCha20Rng {
149        let mut seed = [0u8; 32];
150        getrandom::fill(&mut seed).expect("OS random source failed");
151        ChaCha20Rng::from_seed(seed)
152    }
153
154    #[test]
155    fn test_deterministic_dh_keygen() {
156        proptest!(|(randomness in proptest::collection::vec(any::<u8>(), DH_SEED_LEN))| {
157            let seed: [u8; DH_SEED_LEN] = randomness.try_into().unwrap();
158            let (sk, pk) = deterministic_dh_keygen(seed);
159
160            let (sk2, pk2) = deterministic_dh_keygen(seed);
161            prop_assert_eq!(sk.to_bytes(), sk2.to_bytes());
162            prop_assert_eq!(pk.into_bytes(), pk2.into_bytes());
163            prop_assert_eq!(pk.into_bytes(), sk.public_key().into_bytes());
164        });
165    }
166
167    #[test]
168    fn test_dh_shared_secret() {
169        let mut rng = get_rng();
170
171        let (sk1, pk1) = generate_dh_keypair(&mut rng);
172
173        let (sk2, pk2) = generate_dh_keypair(&mut rng);
174
175        let ss1 = dh_shared_secret(&pk1, &sk2);
176        let ss2 = dh_shared_secret(&pk2, &sk1);
177
178        assert_eq!(ss1.into_bytes(), ss2.into_bytes());
179        assert_ne!(ss1.into_bytes(), [0u8; DH_PUBLIC_KEY_LEN])
180    }
181
182    #[test]
183    fn test_three_party_dh() {
184        let mut rng = get_rng();
185
186        let (sk_r, pk_r) = generate_dh_keypair(&mut rng);
187        let (x, big_x) = generate_dh_keypair(&mut rng);
188        let y = generate_random_scalar(&mut rng);
189
190        let z = dh_shared_secret(&pk_r, &x);
191        let server = dh_shared_secret(&z, &y);
192
193        let pmgdh = dh_shared_secret(&big_x, &y);
194        let recipient = dh_shared_secret(&pmgdh, &sk_r);
195
196        assert_eq!(server.into_bytes(), recipient.into_bytes());
197    }
198}