securedrop_protocol_minimal/primitives/
ristretto255.rs1use 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
11const DH_SEED_LEN: usize = provider::ristretto255::SEED_LEN;
13
14#[derive(Debug, Clone, Copy)]
23pub struct DHPublicKey(Point);
24
25#[derive(Debug, Clone)]
30pub struct DHPrivateKey(Scalar);
31
32impl DHPublicKey {
33 pub const LEN: usize = DH_PUBLIC_KEY_LEN;
34
35 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 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 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 pub fn public_key(&self) -> DHPublicKey {
66 DHPublicKey(provider::ristretto255::secret_to_public(&self.0))
67 }
68
69 pub fn to_bytes(&self) -> [u8; DH_PRIVATE_KEY_LEN] {
71 provider::ristretto255::scalar_encode(&self.0)
72 }
73}
74
75pub(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
88pub 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
96const PLACEHOLDER_SEED: &[u8] = b"securedrop-protocol-placeholder-v1";
98
99pub fn placeholder_public_key() -> DHPublicKey {
101 let seed = provider::sha2::sha512(PLACEHOLDER_SEED);
102
103 DHPublicKey(provider::ristretto255::from_uniform_bytes(&seed))
104}
105
106pub 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
114pub 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
122pub 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 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}