distributed_topic_tracker/crypto/
record.rs1use std::{collections::HashSet, str::FromStr, time::Duration};
2
3use anyhow::{Result, bail};
4use ed25519_dalek::{SigningKey, VerifyingKey, ed25519::signature::SignerMut};
5use ed25519_dalek_hpke::{Ed25519hpkeDecryption, Ed25519hpkeEncryption};
6use serde::{Deserialize, Serialize};
7use sha2::Digest;
8
9#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
10pub struct RecordTopic([u8; 32]);
11
12impl FromStr for RecordTopic {
13 type Err = anyhow::Error;
14
15 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
16 let mut hasher = sha2::Sha512::new();
17 hasher.update(s.as_bytes());
18 let hash: [u8; 32] = hasher.finalize()[..32]
19 .try_into()
20 .map_err(|_| anyhow::anyhow!("hashing failed"))?;
21 Ok(RecordTopic(hash))
22 }
23}
24
25impl RecordTopic {
26 pub fn from_bytes(bytes: &[u8; 32]) -> Self {
27 Self(*bytes)
28 }
29
30 pub fn hash(&self) -> [u8; 32] {
31 self.0
32 }
33}
34
35#[derive(Debug, Clone)]
36pub struct EncryptedRecord {
37 encrypted_record: Vec<u8>,
38 encrypted_decryption_key: Vec<u8>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub struct Record {
43 topic: [u8; 32],
44 unix_minute: u64,
45 pub_key: [u8; 32],
46 content: RecordContent,
47 signature: [u8; 64],
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub struct RecordContent(pub Vec<u8>);
52
53impl RecordContent {
54 pub fn to<'a, T: Deserialize<'a>>(&'a self) -> anyhow::Result<T> {
55 postcard::from_bytes::<T>(&self.0).map_err(|e| anyhow::anyhow!(e))
56 }
57 pub fn from_arbitrary<T: Serialize>(from: &T) -> anyhow::Result<Self> {
58 Ok(Self(
59 postcard::to_allocvec(from).map_err(|e| anyhow::anyhow!(e))?,
60 ))
61 }
62}
63
64#[derive(Debug, Clone)]
65pub struct RecordPublisher {
66 dht: crate::dht::Dht,
67
68 record_topic: RecordTopic,
69 pub_key: VerifyingKey,
70 signing_key: SigningKey,
71 secret_rotation: Option<crate::crypto::keys::RotationHandle>,
72 initial_secret_hash: [u8; 32],
73}
74
75impl RecordPublisher {
76 pub fn new(
77 record_topic: impl Into<RecordTopic>,
78 pub_key: VerifyingKey,
79 signing_key: SigningKey,
80 secret_rotation: Option<crate::crypto::keys::RotationHandle>,
81 initial_secret: Vec<u8>,
82 ) -> Self {
83 let mut initial_secret_hash = sha2::Sha512::new();
84 initial_secret_hash.update(initial_secret);
85 let initial_secret_hash: [u8; 32] = initial_secret_hash.finalize()[..32]
86 .try_into()
87 .expect("hashing failed");
88
89 Self {
90 dht: crate::dht::Dht::new(),
91 record_topic: record_topic.into(),
92 pub_key,
93 signing_key,
94 secret_rotation,
95 initial_secret_hash,
96 }
97 }
98
99 pub fn new_record<'a>(
100 &'a self,
101 unix_minute: u64,
102 record_content: impl Serialize + Deserialize<'a> + Sized,
103 ) -> Result<Record> {
104 Record::sign(
105 self.record_topic.hash(),
106 unix_minute,
107 self.pub_key.to_bytes(),
108 record_content,
109 &self.signing_key,
110 )
111 }
112
113 pub fn pub_key(&self) -> ed25519_dalek::VerifyingKey {
114 self.pub_key.clone()
115 }
116
117 pub fn record_topic(&self) -> RecordTopic {
118 self.record_topic.clone()
119 }
120
121 pub fn signing_key(&self) -> ed25519_dalek::SigningKey {
122 self.signing_key.clone()
123 }
124
125 pub fn secret_rotation(&self) -> Option<crate::crypto::keys::RotationHandle> {
126 self.secret_rotation.clone()
127 }
128
129 pub fn initial_secret_hash(&self) -> [u8; 32] {
130 self.initial_secret_hash
131 }
132}
133
134impl RecordPublisher {
135 pub async fn publish_record(&self, record: Record) -> Result<()> {
137 let records = self
139 .get_records(record.unix_minute())
140 .await
141 .iter()
142 .cloned()
143 .collect::<HashSet<_>>();
144
145 if records.len() >= crate::MAX_BOOTSTRAP_RECORDS {
148 return Ok(());
149 }
150
151 let sign_key = crate::crypto::keys::signing_keypair(self.record_topic, record.unix_minute);
153 let salt = crate::crypto::keys::salt(self.record_topic, record.unix_minute);
154 let encryption_key = crate::crypto::keys::encryption_keypair(
155 self.record_topic.clone(),
156 &self.secret_rotation.clone().unwrap_or_default(),
157 self.initial_secret_hash,
158 record.unix_minute,
159 );
160 let encrypted_record = record.encrypt(&encryption_key);
161
162 self.dht
163 .put_mutable(
164 sign_key.clone(),
165 sign_key.verifying_key().into(),
166 Some(salt.to_vec()),
167 encrypted_record.to_bytes().to_vec(),
168 Some(3),
169 Duration::from_secs(10),
170 )
171 .await?;
172
173 Ok(())
174 }
175
176 pub async fn get_records(&self, unix_minute: u64) -> HashSet<Record> {
177 let topic_sign = crate::crypto::keys::signing_keypair(self.record_topic, unix_minute);
178 let encryption_key = crate::crypto::keys::encryption_keypair(
179 self.record_topic.clone(),
180 &self.secret_rotation.clone().unwrap_or_default(),
181 self.initial_secret_hash,
182 unix_minute,
183 );
184 let salt = crate::crypto::keys::salt(self.record_topic, unix_minute);
185
186 let records_iter = self
188 .dht
189 .get(
190 topic_sign.verifying_key().into(),
191 Some(salt.to_vec()),
192 None,
193 Duration::from_secs(10),
194 )
195 .await
196 .unwrap_or_default();
197
198 records_iter
199 .iter()
200 .filter_map(
201 |record| match EncryptedRecord::from_bytes(record.value().to_vec()) {
202 Ok(encrypted_record) => match encrypted_record.decrypt(&encryption_key) {
203 Ok(record) => match record.verify(&self.record_topic.hash(), unix_minute) {
204 Ok(_) => match record.node_id().eq(self.pub_key.as_bytes()) {
205 true => None,
206 false => Some(record),
207 },
208 Err(_) => None,
209 },
210 Err(_) => None,
211 },
212 Err(_) => None,
213 },
214 )
215 .collect::<HashSet<_>>()
216 }
217}
218
219impl EncryptedRecord {
220 pub fn decrypt(&self, decryption_key: &ed25519_dalek::SigningKey) -> Result<Record> {
221 let one_time_key_bytes: [u8; 32] = decryption_key
222 .decrypt(&self.encrypted_decryption_key)?
223 .as_slice()
224 .try_into()?;
225 let one_time_key = ed25519_dalek::SigningKey::from_bytes(&one_time_key_bytes);
226
227 let decrypted_record = one_time_key.decrypt(&self.encrypted_record)?;
228 let record = Record::from_bytes(decrypted_record)?;
229 Ok(record)
230 }
231
232 pub fn to_bytes(&self) -> Vec<u8> {
233 let mut buf = Vec::new();
234 let encrypted_record_len = self.encrypted_record.len() as u32;
235 buf.extend_from_slice(&encrypted_record_len.to_le_bytes());
236 buf.extend_from_slice(&self.encrypted_record);
237 buf.extend_from_slice(&self.encrypted_decryption_key);
238 buf
239 }
240
241 pub fn from_bytes(buf: Vec<u8>) -> Result<Self> {
242 let (encrypted_record_len, buf) = buf.split_at(4);
243 let encrypted_record_len = u32::from_le_bytes(encrypted_record_len.try_into()?);
244 let (encrypted_record, encrypted_decryption_key) =
245 buf.split_at(encrypted_record_len as usize);
246
247 Ok(Self {
248 encrypted_record: encrypted_record.to_vec(),
249 encrypted_decryption_key: encrypted_decryption_key.to_vec(),
250 })
251 }
252}
253
254impl Record {
255 pub fn sign<'a>(
256 topic: [u8; 32],
257 unix_minute: u64,
258 node_id: [u8; 32],
259 record_content: impl Serialize + Deserialize<'a>,
260 signing_key: &ed25519_dalek::SigningKey,
261 ) -> anyhow::Result<Self> {
262 let record_content = RecordContent::from_arbitrary(&record_content)?;
263 let mut signature_data = Vec::new();
264 signature_data.extend_from_slice(&topic);
265 signature_data.extend_from_slice(&unix_minute.to_le_bytes());
266 signature_data.extend_from_slice(&node_id);
267 signature_data.extend(&record_content.clone().0);
268 let mut signing_key = signing_key.clone();
269 let signature = signing_key.sign(&signature_data);
270 Ok(Self {
271 topic,
272 unix_minute,
273 pub_key: node_id,
274 content: record_content,
275 signature: signature.to_bytes(),
276 })
277 }
278
279 pub fn from_bytes(buf: Vec<u8>) -> Result<Self> {
280 let (topic, buf) = buf.split_at(32);
281 let (unix_minute, buf) = buf.split_at(8);
282 let (node_id, buf) = buf.split_at(32);
283 let (record_content, buf) = buf.split_at(buf.len() - 64);
284
285 let (signature, buf) = buf.split_at(64);
286
287 if !buf.is_empty() {
288 bail!("buffer not empty after reconstruction")
289 }
290
291 Ok(Self {
292 topic: topic.try_into()?,
293 unix_minute: u64::from_le_bytes(unix_minute.try_into()?),
294 pub_key: node_id.try_into()?,
295 content: RecordContent(record_content.to_vec()),
296 signature: signature.try_into()?,
297 })
298 }
299
300 pub fn to_bytes(&self) -> Vec<u8> {
301 let mut buf = Vec::new();
302 buf.extend_from_slice(&self.topic);
303 buf.extend_from_slice(&self.unix_minute.to_le_bytes());
304 buf.extend_from_slice(&self.pub_key);
305 buf.extend(&self.content.0);
306 buf.extend_from_slice(&self.signature);
307 buf
308 }
309
310 pub fn verify(&self, actual_topic: &[u8; 32], actual_unix_minute: u64) -> Result<()> {
311 if self.topic != *actual_topic {
312 bail!("topic mismatch")
313 }
314 if self.unix_minute != actual_unix_minute {
315 bail!("unix minute mismatch")
316 }
317
318 let record_bytes = self.to_bytes();
319 let signature_data = record_bytes[..record_bytes.len() - 64].to_vec();
320 let signature = ed25519_dalek::Signature::from_bytes(&self.signature);
321 let node_id = ed25519_dalek::VerifyingKey::from_bytes(&self.pub_key)?;
322
323 node_id.verify_strict(signature_data.as_slice(), &signature)?;
324
325 Ok(())
326 }
327
328 pub fn encrypt(&self, encryption_key: &ed25519_dalek::SigningKey) -> EncryptedRecord {
329 let one_time_key = ed25519_dalek::SigningKey::generate(&mut rand::thread_rng());
330 let p_key = one_time_key.verifying_key();
331 let data_enc = p_key.encrypt(&self.to_bytes()).expect("encryption failed");
332 let key_enc = encryption_key
333 .verifying_key()
334 .encrypt(&one_time_key.to_bytes())
335 .expect("encryption failed");
336
337 EncryptedRecord {
338 encrypted_record: data_enc,
339 encrypted_decryption_key: key_enc,
340 }
341 }
342}
343
344impl Record {
346 pub fn topic(&self) -> [u8; 32] {
347 self.topic
348 }
349
350 pub fn unix_minute(&self) -> u64 {
351 self.unix_minute
352 }
353
354 pub fn node_id(&self) -> [u8; 32] {
355 self.pub_key
356 }
357
358 pub fn content<'a, T: Deserialize<'a>>(&'a self) -> anyhow::Result<T> {
359 self.content.to::<T>()
360 }
361
362 pub fn signature(&self) -> [u8; 64] {
363 self.signature
364 }
365}