Skip to main content

rns_core/
destination.rs

1use ed25519_dalek::{Signature, SigningKey, VerifyingKey, SIGNATURE_LENGTH};
2use rand_core::CryptoRngCore;
3use x25519_dalek::PublicKey;
4
5use alloc::vec::Vec;
6use core::{fmt, marker::PhantomData};
7#[cfg(feature = "std")]
8use std::path::Path;
9
10use crate::{
11    error::RnsError,
12    hash::{AddressHash, Hash},
13    identity::{EmptyIdentity, HashIdentity, Identity, PrivateIdentity, PUBLIC_KEY_LENGTH},
14    packet::{
15        self, ContextFlag, DestinationType, Header, HeaderType, IfacFlag, Packet, PacketContext,
16        PacketDataBuffer, PacketType, PropagationType,
17    },
18    ratchets::{decrypt_with_identity, now_secs},
19};
20use sha2::Digest;
21
22#[path = "destination/primitives.rs"]
23mod primitives;
24#[path = "destination/ratchet.rs"]
25mod ratchet;
26#[cfg(test)]
27#[path = "destination/tests.rs"]
28mod tests;
29
30pub use primitives::{
31    group_decrypt, group_encrypt, Direction, Group, Input, Output, Plain, Single, Type,
32};
33pub use ratchet::RATCHET_LENGTH;
34use ratchet::{try_decrypt_with_ratchets, RatchetState};
35
36pub const NAME_HASH_LENGTH: usize = 10;
37pub const RAND_HASH_LENGTH: usize = 10;
38pub const MIN_ANNOUNCE_DATA_LENGTH: usize =
39    PUBLIC_KEY_LENGTH * 2 + NAME_HASH_LENGTH + RAND_HASH_LENGTH + SIGNATURE_LENGTH;
40
41#[derive(Copy, Clone)]
42pub struct DestinationName {
43    pub hash: Hash,
44}
45
46impl DestinationName {
47    pub fn new(app_name: &str, aspects: &str) -> Self {
48        let hash = Hash::new(
49            Hash::generator()
50                .chain_update(app_name.as_bytes())
51                .chain_update(".".as_bytes())
52                .chain_update(aspects.as_bytes())
53                .finalize()
54                .into(),
55        );
56
57        Self { hash }
58    }
59
60    pub fn new_from_hash_slice(hash_slice: &[u8]) -> Self {
61        let mut hash = [0u8; 32];
62        hash[..hash_slice.len()].copy_from_slice(hash_slice);
63
64        Self { hash: Hash::new(hash) }
65    }
66
67    pub fn as_name_hash_slice(&self) -> &[u8] {
68        &self.hash.as_slice()[..NAME_HASH_LENGTH]
69    }
70}
71
72#[derive(Copy, Clone)]
73pub struct DestinationDesc {
74    pub identity: Identity,
75    pub address_hash: AddressHash,
76    pub name: DestinationName,
77}
78
79impl fmt::Display for DestinationDesc {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(f, "{}", self.address_hash)?;
82
83        Ok(())
84    }
85}
86
87pub type DestinationAnnounce = Packet;
88
89pub struct AnnounceInfo<'a> {
90    pub destination: SingleOutputDestination,
91    pub app_data: &'a [u8],
92    pub ratchet: Option<[u8; RATCHET_LENGTH]>,
93}
94
95impl DestinationAnnounce {
96    pub fn validate(packet: &Packet) -> Result<AnnounceInfo<'_>, RnsError> {
97        let mut signed_data = [0u8; packet::PACKET_MDU];
98        Self::validate_with_buffer(packet, &mut signed_data)
99    }
100
101    pub fn validate_with_buffer<'a>(
102        packet: &'a Packet,
103        signed_data: &mut [u8],
104    ) -> Result<AnnounceInfo<'a>, RnsError> {
105        if packet.header.packet_type != PacketType::Announce {
106            return Err(RnsError::PacketError);
107        }
108
109        let announce_data = packet.data.as_slice();
110
111        if announce_data.len() < MIN_ANNOUNCE_DATA_LENGTH {
112            return Err(RnsError::OutOfMemory);
113        }
114
115        let mut offset = 0usize;
116
117        let public_key = {
118            let mut key_data = [0u8; PUBLIC_KEY_LENGTH];
119            key_data.copy_from_slice(&announce_data[offset..(offset + PUBLIC_KEY_LENGTH)]);
120            offset += PUBLIC_KEY_LENGTH;
121            PublicKey::from(key_data)
122        };
123
124        let verifying_key = {
125            let mut key_data = [0u8; PUBLIC_KEY_LENGTH];
126            key_data.copy_from_slice(&announce_data[offset..(offset + PUBLIC_KEY_LENGTH)]);
127            offset += PUBLIC_KEY_LENGTH;
128
129            VerifyingKey::from_bytes(&key_data).map_err(|_| RnsError::CryptoError)?
130        };
131
132        let identity = Identity::new(public_key, verifying_key);
133
134        let name_hash = &announce_data[offset..(offset + NAME_HASH_LENGTH)];
135        offset += NAME_HASH_LENGTH;
136        let rand_hash = &announce_data[offset..(offset + RAND_HASH_LENGTH)];
137        offset += RAND_HASH_LENGTH;
138        let destination = &packet.destination;
139        let expected_hash =
140            create_address_hash(&identity, &DestinationName::new_from_hash_slice(name_hash));
141        if expected_hash != *destination {
142            return Err(RnsError::IncorrectHash);
143        }
144
145        let remaining = announce_data.len().saturating_sub(offset);
146        if remaining < SIGNATURE_LENGTH {
147            return Err(RnsError::OutOfMemory);
148        }
149
150        let has_ratchet_flag = packet.header.context_flag == ContextFlag::Set;
151
152        if has_ratchet_flag {
153            if remaining < SIGNATURE_LENGTH + RATCHET_LENGTH {
154                return Err(RnsError::OutOfMemory);
155            }
156            let ratchet = &announce_data[offset..offset + RATCHET_LENGTH];
157            let sig_start = offset + RATCHET_LENGTH;
158            let sig_end = sig_start + SIGNATURE_LENGTH;
159            let signature = &announce_data[sig_start..sig_end];
160            let app_data = &announce_data[sig_end..];
161            verify_announce_with_buffer(
162                &identity,
163                destination.as_slice(),
164                public_key.as_bytes(),
165                verifying_key.as_bytes(),
166                name_hash,
167                rand_hash,
168                Some(ratchet),
169                signature,
170                app_data,
171                signed_data,
172            )?;
173            let mut ratchet_bytes = [0u8; RATCHET_LENGTH];
174            ratchet_bytes.copy_from_slice(ratchet);
175            return Ok(AnnounceInfo {
176                destination: SingleOutputDestination::new(
177                    identity,
178                    DestinationName::new_from_hash_slice(name_hash),
179                ),
180                app_data,
181                ratchet: Some(ratchet_bytes),
182            });
183        }
184
185        let signature = &announce_data[offset..(offset + SIGNATURE_LENGTH)];
186        let app_data = &announce_data[(offset + SIGNATURE_LENGTH)..];
187        verify_announce_with_buffer(
188            &identity,
189            destination.as_slice(),
190            public_key.as_bytes(),
191            verifying_key.as_bytes(),
192            name_hash,
193            rand_hash,
194            None,
195            signature,
196            app_data,
197            signed_data,
198        )?;
199
200        Ok(AnnounceInfo {
201            destination: SingleOutputDestination::new(
202                identity,
203                DestinationName::new_from_hash_slice(name_hash),
204            ),
205            app_data,
206            ratchet: None,
207        })
208    }
209}
210
211pub struct Destination<I: HashIdentity, D: Direction, T: Type> {
212    pub direction: PhantomData<D>,
213    pub r#type: PhantomData<T>,
214    pub identity: I,
215    pub desc: DestinationDesc,
216    ratchet_state: RatchetState,
217}
218
219impl<I: HashIdentity, D: Direction, T: Type> Destination<I, D, T> {
220    pub fn destination_type(&self) -> packet::DestinationType {
221        <T as Type>::destination_type()
222    }
223}
224
225// impl<I: DecryptIdentity + HashIdentity, T: Type> Destination<I, Input, T> {
226//     pub fn decrypt<'b, R: CryptoRngCore + Copy>(
227//         &self,
228//         rng: R,
229//         data: &[u8],
230//         out_buf: &'b mut [u8],
231//     ) -> Result<&'b [u8], RnsError> {
232//         self.identity.decrypt(rng, data, out_buf)
233//     }
234// }
235
236// impl<I: EncryptIdentity + HashIdentity, D: Direction, T: Type> Destination<I, D, T> {
237//     pub fn encrypt<'b, R: CryptoRngCore + Copy>(
238//         &self,
239//         rng: R,
240//         text: &[u8],
241//         out_buf: &'b mut [u8],
242//     ) -> Result<&'b [u8], RnsError> {
243//         // self.identity.encrypt(
244//         //     rng,
245//         //     text,
246//         //     Some(self.identity.as_address_hash_slice()),
247//         //     out_buf,
248//         // )
249//     }
250// }
251
252pub enum DestinationHandleStatus {
253    None,
254    LinkProof,
255}
256
257impl Destination<PrivateIdentity, Input, Single> {
258    pub fn new(identity: PrivateIdentity, name: DestinationName) -> Self {
259        let address_hash = create_address_hash(&identity, &name);
260        let pub_identity = *identity.as_identity();
261
262        Self {
263            direction: PhantomData,
264            r#type: PhantomData,
265            identity,
266            desc: DestinationDesc { identity: pub_identity, name, address_hash },
267            ratchet_state: RatchetState::default(),
268        }
269    }
270
271    #[cfg(feature = "std")]
272    pub fn enable_ratchets<P: AsRef<Path>>(&mut self, path: P) -> Result<(), RnsError> {
273        let path = path.as_ref().to_path_buf();
274        self.ratchet_state.enable(&self.identity, path)
275    }
276
277    pub fn set_retained_ratchets(&mut self, retained: usize) -> Result<(), RnsError> {
278        if retained == 0 {
279            return Err(RnsError::InvalidArgument);
280        }
281        self.ratchet_state.retained_ratchets = retained;
282        if self.ratchet_state.ratchets.len() > retained {
283            self.ratchet_state.ratchets.truncate(retained);
284        }
285        Ok(())
286    }
287
288    pub fn set_ratchet_interval_secs(&mut self, secs: u64) -> Result<(), RnsError> {
289        if secs == 0 {
290            return Err(RnsError::InvalidArgument);
291        }
292        self.ratchet_state.ratchet_interval_secs = secs;
293        Ok(())
294    }
295
296    pub fn enforce_ratchets(&mut self, enforce: bool) {
297        self.ratchet_state.enforce_ratchets = enforce;
298    }
299
300    pub fn decrypt_with_ratchets(
301        &mut self,
302        ciphertext: &[u8],
303    ) -> Result<(Vec<u8>, bool), RnsError> {
304        let salt = self.identity.as_identity().address_hash.as_slice();
305        if self.ratchet_state.enabled && !self.ratchet_state.ratchets.is_empty() {
306            if let Some(plaintext) =
307                try_decrypt_with_ratchets(&self.ratchet_state, salt, ciphertext)
308            {
309                return Ok((plaintext, true));
310            }
311            #[cfg(feature = "std")]
312            if let Some(path) = self.ratchet_state.ratchets_path.clone() {
313                if self.ratchet_state.reload(&self.identity, &path).is_ok() {
314                    if let Some(plaintext) =
315                        try_decrypt_with_ratchets(&self.ratchet_state, salt, ciphertext)
316                    {
317                        return Ok((plaintext, true));
318                    }
319                }
320            }
321            if self.ratchet_state.enforce_ratchets {
322                return Err(RnsError::CryptoError);
323            }
324        }
325
326        let plaintext = decrypt_with_identity(&self.identity, salt, ciphertext)?;
327        Ok((plaintext, false))
328    }
329
330    pub fn announce<R: CryptoRngCore + Copy>(
331        &mut self,
332        rng: R,
333        app_data: Option<&[u8]>,
334    ) -> Result<Packet, RnsError> {
335        let mut packet_data = PacketDataBuffer::new();
336
337        // Python Reticulum encodes announce randomness as 5 random bytes
338        // followed by a 5-byte big-endian unix timestamp. Matching this
339        // layout keeps announce freshness/path ordering interoperable.
340        let mut rand_hash = [0u8; RAND_HASH_LENGTH];
341        let mut random_part = [0u8; RAND_HASH_LENGTH / 2];
342        let mut rng_mut = rng;
343        rng_mut.fill_bytes(&mut random_part);
344        rand_hash[..RAND_HASH_LENGTH / 2].copy_from_slice(&random_part);
345        let emitted_secs = now_secs().floor() as u64;
346        let emitted_be = emitted_secs.to_be_bytes();
347        rand_hash[RAND_HASH_LENGTH / 2..].copy_from_slice(&emitted_be[3..8]);
348
349        let pub_key = self.identity.as_identity().public_key_bytes();
350        let verifying_key = self.identity.as_identity().verifying_key_bytes();
351
352        let ratchet = if self.ratchet_state.enabled {
353            let now = now_secs();
354            self.ratchet_state.rotate_if_needed(&self.identity, now)?;
355            self.ratchet_state.current_ratchet_public()
356        } else {
357            None
358        };
359
360        packet_data
361            .chain_safe_write(self.desc.address_hash.as_slice())
362            .chain_safe_write(pub_key)
363            .chain_safe_write(verifying_key)
364            .chain_safe_write(self.desc.name.as_name_hash_slice())
365            .chain_safe_write(&rand_hash);
366
367        if let Some(ratchet) = ratchet {
368            packet_data.chain_safe_write(&ratchet);
369        }
370
371        if let Some(data) = app_data {
372            packet_data.chain_safe_write(data);
373        }
374
375        let signature = self.identity.sign(packet_data.as_slice());
376
377        packet_data.reset();
378
379        packet_data
380            .chain_safe_write(pub_key)
381            .chain_safe_write(verifying_key)
382            .chain_safe_write(self.desc.name.as_name_hash_slice())
383            .chain_safe_write(&rand_hash);
384
385        if let Some(ratchet) = ratchet {
386            packet_data.chain_safe_write(&ratchet);
387        }
388
389        packet_data.chain_safe_write(&signature.to_bytes());
390
391        if let Some(data) = app_data {
392            packet_data.write(data)?;
393        }
394
395        Ok(Packet {
396            header: Header {
397                ifac_flag: IfacFlag::Open,
398                header_type: HeaderType::Type1,
399                context_flag: if ratchet.is_some() { ContextFlag::Set } else { ContextFlag::Unset },
400                propagation_type: PropagationType::Broadcast,
401                destination_type: DestinationType::Single,
402                packet_type: PacketType::Announce,
403                hops: 0,
404            },
405            ifac: None,
406            destination: self.desc.address_hash,
407            transport: None,
408            context: PacketContext::None,
409            data: packet_data,
410        })
411    }
412
413    pub fn path_response<R: CryptoRngCore + Copy>(
414        &mut self,
415        rng: R,
416        app_data: Option<&[u8]>,
417    ) -> Result<Packet, RnsError> {
418        let mut announce = self.announce(rng, app_data)?;
419        announce.context = PacketContext::PathResponse;
420
421        Ok(announce)
422    }
423
424    pub fn handle_packet(&mut self, packet: &Packet) -> DestinationHandleStatus {
425        if self.desc.address_hash != packet.destination {
426            return DestinationHandleStatus::None;
427        }
428
429        if packet.header.packet_type == PacketType::LinkRequest {
430            // Current policy: always emit a link proof for addressed link requests.
431            return DestinationHandleStatus::LinkProof;
432        }
433
434        DestinationHandleStatus::None
435    }
436
437    pub fn sign_key(&self) -> &SigningKey {
438        self.identity.sign_key()
439    }
440}
441
442impl Destination<Identity, Output, Single> {
443    pub fn new(identity: Identity, name: DestinationName) -> Self {
444        let address_hash = create_address_hash(&identity, &name);
445        Self {
446            direction: PhantomData,
447            r#type: PhantomData,
448            identity,
449            desc: DestinationDesc { identity, name, address_hash },
450            ratchet_state: RatchetState::default(),
451        }
452    }
453}
454
455impl<D: Direction> Destination<EmptyIdentity, D, Plain> {
456    pub fn new(identity: EmptyIdentity, name: DestinationName) -> Self {
457        let address_hash = create_address_hash(&identity, &name);
458        Self {
459            direction: PhantomData,
460            r#type: PhantomData,
461            identity,
462            desc: DestinationDesc { identity: Default::default(), name, address_hash },
463            ratchet_state: RatchetState::default(),
464        }
465    }
466}
467
468fn create_address_hash<I: HashIdentity>(identity: &I, name: &DestinationName) -> AddressHash {
469    AddressHash::new_from_hash(&Hash::new(
470        Hash::generator()
471            .chain_update(name.as_name_hash_slice())
472            .chain_update(identity.as_address_hash_slice())
473            .finalize()
474            .into(),
475    ))
476}
477
478#[allow(clippy::too_many_arguments)]
479fn verify_announce_with_buffer(
480    identity: &Identity,
481    destination: &[u8],
482    public_key: &[u8],
483    verifying_key: &[u8],
484    name_hash: &[u8],
485    rand_hash: &[u8],
486    ratchet: Option<&[u8]>,
487    signature: &[u8],
488    app_data: &[u8],
489    signed_data: &mut [u8],
490) -> Result<(), RnsError> {
491    let required_len = destination.len()
492        + public_key.len()
493        + verifying_key.len()
494        + name_hash.len()
495        + rand_hash.len()
496        + ratchet.map(|value| value.len()).unwrap_or(0)
497        + app_data.len();
498    if required_len > signed_data.len() {
499        return Err(RnsError::OutOfMemory);
500    }
501
502    let mut offset = 0usize;
503    signed_data[offset..offset + destination.len()].copy_from_slice(destination);
504    offset += destination.len();
505    signed_data[offset..offset + public_key.len()].copy_from_slice(public_key);
506    offset += public_key.len();
507    signed_data[offset..offset + verifying_key.len()].copy_from_slice(verifying_key);
508    offset += verifying_key.len();
509    signed_data[offset..offset + name_hash.len()].copy_from_slice(name_hash);
510    offset += name_hash.len();
511    signed_data[offset..offset + rand_hash.len()].copy_from_slice(rand_hash);
512    offset += rand_hash.len();
513    if let Some(ratchet) = ratchet {
514        signed_data[offset..offset + ratchet.len()].copy_from_slice(ratchet);
515        offset += ratchet.len();
516    }
517    if !app_data.is_empty() {
518        signed_data[offset..offset + app_data.len()].copy_from_slice(app_data);
519        offset += app_data.len();
520    }
521
522    let signature = Signature::from_slice(signature).map_err(|_| RnsError::CryptoError)?;
523    identity.verify(&signed_data[..offset], &signature).map_err(|_| RnsError::IncorrectSignature)
524}
525
526pub type SingleInputDestination = Destination<PrivateIdentity, Input, Single>;
527pub type SingleOutputDestination = Destination<Identity, Output, Single>;
528pub type PlainInputDestination = Destination<EmptyIdentity, Input, Plain>;
529pub type PlainOutputDestination = Destination<EmptyIdentity, Output, Plain>;
530
531pub fn new_in(identity: PrivateIdentity, app_name: &str, aspect: &str) -> SingleInputDestination {
532    SingleInputDestination::new(identity, DestinationName::new(app_name, aspect))
533}
534
535pub fn new_out(identity: Identity, app_name: &str, aspect: &str) -> SingleOutputDestination {
536    SingleOutputDestination::new(identity, DestinationName::new(app_name, aspect))
537}