distributed_topic_tracker/crypto/
record.rs

1use 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    // returns records it checked before publishing so we don't have to get twice
136    pub async fn publish_record(&self, record: Record) -> Result<()> {
137        // Get verified records that have active_peers or last_message_hashes set (active participants)
138        let records = self
139            .get_records(record.unix_minute())
140            .await
141            .iter()
142            .cloned()
143            .collect::<HashSet<_>>();
144
145        // Don't publish if there are more then MAX_BOOTSTRAP_RECORDS already written
146        // that either have active_peers or last_message_hashes set (active participants)
147        if records.len() >= crate::MAX_BOOTSTRAP_RECORDS {
148            return Ok(());
149        }
150
151        // Publish own records
152        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        // Get records, decrypt and verify
187        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
344// fields only
345impl 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}