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