Skip to main content

rns_core/destination_parts/
module_core.rs

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