hopr_internal_types/
protocol.rs

1use bloomfilter::Bloom;
2use ethers::utils::hex;
3use serde::{Deserialize, Serialize};
4use std::fmt::{Display, Formatter};
5use tracing::warn;
6
7use hopr_crypto_random::random_bytes;
8use hopr_crypto_types::prelude::*;
9use hopr_primitive_types::prelude::*;
10
11use crate::errors::{CoreTypesError, CoreTypesError::PayloadSizeExceeded, Result};
12use crate::tickets::UnacknowledgedTicket;
13
14/// Number of intermediate hops: 3 relayers and 1 destination
15pub const INTERMEDIATE_HOPS: usize = 3;
16
17/// Maximum size of the packet payload in bytes.
18pub const PAYLOAD_SIZE: usize = 496;
19
20/// Default required minimum incoming ticket winning probability
21pub const DEFAULT_MINIMUM_INCOMING_TICKET_WIN_PROB: f64 = 1.0;
22
23/// Default maximum incoming ticket winning probability, above which tickets will not be accepted
24/// due to privacy.
25pub const DEFAULT_MAXIMUM_INCOMING_TICKET_WIN_PROB: f64 = 1.0; // TODO: change this in 3.0
26
27/// Default ticket winning probability that will be printed on outgoing tickets
28pub const DEFAULT_OUTGOING_TICKET_WIN_PROB: f64 = 1.0;
29
30/// The lowest possible ticket winning probability due to SC representation limit.
31pub const LOWEST_POSSIBLE_WINNING_PROB: f64 = 0.00000001;
32
33/// Tags are currently 16-bit unsigned integers
34pub type Tag = u16;
35
36/// Represent a default application tag if none is specified in `send_packet`.
37pub const DEFAULT_APPLICATION_TAG: Tag = 0;
38
39/// Represents packet acknowledgement
40#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
41pub struct Acknowledgement {
42    ack_signature: OffchainSignature,
43    pub ack_key_share: HalfKey,
44    validated: bool,
45}
46
47impl Acknowledgement {
48    pub fn new(ack_key_share: HalfKey, node_keypair: &OffchainKeypair) -> Self {
49        Self {
50            ack_signature: OffchainSignature::sign_message(ack_key_share.as_ref(), node_keypair),
51            ack_key_share,
52            validated: true,
53        }
54    }
55
56    /// Generates random, but still a valid acknowledgement.
57    pub fn random(offchain_keypair: &OffchainKeypair) -> Self {
58        Self::new(HalfKey::random(), offchain_keypair)
59    }
60
61    /// Validates the acknowledgement.
62    ///
63    /// Must be called immediately after deserialization, or otherwise
64    /// any operations with the deserialized acknowledgement will panic.
65    #[tracing::instrument(level = "debug", skip(self, sender_node_key))]
66    pub fn validate(&mut self, sender_node_key: &OffchainPublicKey) -> bool {
67        self.validated = self
68            .ack_signature
69            .verify_message(self.ack_key_share.as_ref(), sender_node_key);
70
71        self.validated
72    }
73
74    /// Obtains the acknowledged challenge out of this acknowledgment.
75    pub fn ack_challenge(&self) -> HalfKeyChallenge {
76        assert!(self.validated, "acknowledgement not validated");
77        self.ack_key_share.to_challenge()
78    }
79}
80
81/// Contains either unacknowledged ticket if we're waiting for the acknowledgement as a relayer
82/// or information if we wait for the acknowledgement as a sender.
83#[allow(clippy::large_enum_variant)]
84#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
85pub enum PendingAcknowledgement {
86    /// We're waiting for acknowledgement as a sender
87    WaitingAsSender,
88    /// We're waiting for the acknowledgement as a relayer with a ticket
89    WaitingAsRelayer(UnacknowledgedTicket),
90}
91
92const TAGBLOOM_BINCODE_CONFIGURATION: bincode::config::Configuration = bincode::config::standard()
93    .with_little_endian()
94    .with_variable_int_encoding();
95
96#[derive(Debug, Clone)]
97struct SerializableBloomWrapper(Bloom<PacketTag>);
98
99impl Serialize for SerializableBloomWrapper {
100    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
101    where
102        S: serde::Serializer,
103    {
104        bloomfilter::serialize(&self.0, serializer)
105    }
106}
107
108impl<'de> Deserialize<'de> for SerializableBloomWrapper {
109    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
110    where
111        D: serde::Deserializer<'de>,
112    {
113        bloomfilter::deserialize(deserializer).map(Self)
114    }
115}
116
117/// Bloom filter for packet tags to detect packet replays.
118///
119/// In addition, this structure also holds the number of items in the filter
120/// to determine if the filter needs to be refreshed. Once this happens, packet replays
121/// of past packets might be possible.
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct TagBloomFilter {
124    bloom: SerializableBloomWrapper,
125    count: usize,
126    capacity: usize,
127}
128
129impl TagBloomFilter {
130    // Allowed false positive rate. This amounts to 0.001% chance
131    const FALSE_POSITIVE_RATE: f64 = 0.00001_f64;
132
133    // The default maximum number of packet tags this Bloom filter can hold.
134    // After these many packets, the Bloom filter resets and packet replays are possible.
135    const DEFAULT_MAX_ITEMS: usize = 10_000_000;
136
137    /// Returns the current number of items in this Bloom filter.
138    pub fn count(&self) -> usize {
139        self.count
140    }
141
142    pub fn capacity(&self) -> usize {
143        self.capacity
144    }
145
146    /// Puts a packet tag into the Bloom filter
147    pub fn set(&mut self, tag: &PacketTag) {
148        if self.count == self.capacity {
149            warn!("maximum number of items in the Bloom filter reached!");
150            self.bloom.0.clear();
151            self.count = 0;
152        }
153
154        self.bloom.0.set(tag);
155        self.count += 1;
156    }
157
158    /// Check if the packet tag is in the Bloom filter.
159    /// False positives are possible.
160    pub fn check(&self, tag: &PacketTag) -> bool {
161        self.bloom.0.check(tag)
162    }
163
164    /// Checks and sets a packet tag (if not present) in a single operation.
165    pub fn check_and_set(&mut self, tag: &PacketTag) -> bool {
166        // If we're at full capacity, we do only "check" and conditionally reset with the new entry
167        if self.count == self.capacity {
168            let is_present = self.bloom.0.check(tag);
169            if !is_present {
170                // There cannot be false negatives, so we can reset the filter
171                warn!("maximum number of items in the Bloom filter reached!");
172                self.bloom.0.clear();
173                self.bloom.0.set(tag);
174                self.count = 1;
175            }
176            is_present
177        } else {
178            // If not at full capacity, we can do check_and_set
179            let was_present = self.bloom.0.check_and_set(tag);
180            if !was_present {
181                self.count += 1;
182            }
183            was_present
184        }
185    }
186
187    /// Deserializes the filter from the given byte array.
188    pub fn from_bytes(data: &[u8]) -> Result<Self> {
189        bincode::serde::borrow_decode_from_slice(data, TAGBLOOM_BINCODE_CONFIGURATION)
190            .map(|(v, _bytes)| v)
191            .map_err(|e| CoreTypesError::ParseError(e.to_string()))
192    }
193
194    /// Serializes the filter to the given byte array.
195    pub fn to_bytes(&self) -> Box<[u8]> {
196        bincode::serde::encode_to_vec(self, TAGBLOOM_BINCODE_CONFIGURATION)
197            .expect("serialization of bloom filter must not fail")
198            .into_boxed_slice()
199    }
200
201    fn with_capacity(size: usize) -> Self {
202        Self {
203            bloom: SerializableBloomWrapper(
204                Bloom::new_for_fp_rate_with_seed(size, Self::FALSE_POSITIVE_RATE, &random_bytes())
205                    .expect("bloom filter with the specified capacity is constructible"),
206            ),
207            count: 0,
208            capacity: size,
209        }
210    }
211}
212
213impl Default for TagBloomFilter {
214    fn default() -> Self {
215        Self::with_capacity(Self::DEFAULT_MAX_ITEMS)
216    }
217}
218
219/// Represents the received decrypted packet carrying the application-layer data.
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
221pub struct ApplicationData {
222    // TODO: 3.0: Remove the Option and replace with the Tag.
223    pub application_tag: Option<Tag>,
224    #[serde(with = "serde_bytes")]
225    pub plain_text: Box<[u8]>,
226}
227
228impl ApplicationData {
229    pub fn new(application_tag: Option<Tag>, plain_text: &[u8]) -> Result<Self> {
230        if plain_text.len() <= PAYLOAD_SIZE - Self::SIZE {
231            Ok(Self {
232                application_tag,
233                plain_text: plain_text.into(),
234            })
235        } else {
236            Err(PayloadSizeExceeded)
237        }
238    }
239
240    pub fn new_from_owned(application_tag: Option<Tag>, plain_text: Box<[u8]>) -> Result<Self> {
241        if plain_text.len() <= PAYLOAD_SIZE - Self::SIZE {
242            Ok(Self {
243                application_tag,
244                plain_text,
245            })
246        } else {
247            Err(PayloadSizeExceeded)
248        }
249    }
250}
251
252impl Display for ApplicationData {
253    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
254        write!(
255            f,
256            "({}): {}",
257            self.application_tag.unwrap_or(DEFAULT_APPLICATION_TAG),
258            hex::encode(&self.plain_text)
259        )
260    }
261}
262
263impl ApplicationData {
264    const SIZE: usize = 2; // minimum size
265
266    pub fn from_bytes(data: &[u8]) -> hopr_primitive_types::errors::Result<Self> {
267        if data.len() <= PAYLOAD_SIZE && data.len() >= Self::SIZE {
268            let mut tag = [0u8; 2];
269            tag.copy_from_slice(&data[0..2]);
270            let tag = u16::from_be_bytes(tag);
271            Ok(Self {
272                application_tag: if tag != DEFAULT_APPLICATION_TAG {
273                    Some(tag)
274                } else {
275                    None
276                },
277                plain_text: (&data[2..]).into(),
278            })
279        } else {
280            Err(GeneralError::ParseError("ApplicationData".into()))
281        }
282    }
283
284    pub fn to_bytes(&self) -> Box<[u8]> {
285        let mut buf = Vec::with_capacity(Self::SIZE + self.plain_text.len());
286        let tag = self.application_tag.unwrap_or(DEFAULT_APPLICATION_TAG);
287        buf.extend_from_slice(&tag.to_be_bytes());
288        buf.extend_from_slice(&self.plain_text);
289        buf.into_boxed_slice()
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use hopr_crypto_random::random_bytes;
297
298    use hex_literal::hex;
299
300    const PRIVATE_KEY: [u8; 32] = hex!("51d3003d908045a4d76d0bfc0d84f6ff946b5934b7ea6a2958faf02fead4567a");
301
302    #[test]
303    fn acknowledgement_binary_compatibility_with_the_v2_format() -> anyhow::Result<()> {
304        let offchain_kp = OffchainKeypair::from_secret(&PRIVATE_KEY)?;
305        let mut ack = Acknowledgement::new(HalfKey::default(), &offchain_kp);
306
307        assert!(ack.validate(offchain_kp.public()));
308
309        let buf = Vec::new();
310        let serialized = cbor4ii::serde::to_vec(buf, &ack)?;
311
312        const EXPECTED_V2_BINARY_REPRESENTATION_CBOR_HEX: [u8; 213] = hex!("a36d61636b5f7369676e6174757265a1697369676e617475726598401859182418be184818a218c318cb1869186018270218391853186c18ff18e018b518d9187b187900188218da184e1869187518ec1828181b081821187718bb0c18ba18f418331218ea187c1880182318d6189f189f18d7141876186a1890186b1885189718a718b9189018fc18bc18260918e318a5182a006d61636b5f6b65795f7368617265a164686b6579982000000000000000000000000000000000000000000000000000000000000000016976616c696461746564f5");
313
314        assert_eq!(&serialized, &EXPECTED_V2_BINARY_REPRESENTATION_CBOR_HEX);
315
316        Ok(())
317    }
318
319    #[test]
320    fn test_application_data() -> anyhow::Result<()> {
321        let ad_1 = ApplicationData::new(Some(10), &[0_u8, 1_u8])?;
322        let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
323        assert_eq!(ad_1, ad_2);
324
325        let ad_1 = ApplicationData::new(None, &[])?;
326        let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
327        assert_eq!(ad_1, ad_2);
328
329        let ad_1 = ApplicationData::new(Some(10), &[0_u8, 1_u8])?;
330        let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
331        assert_eq!(ad_1, ad_2);
332
333        Ok(())
334    }
335
336    const ZEROS_TAG: [u8; PACKET_TAG_LENGTH] = [0; PACKET_TAG_LENGTH];
337    const ONES_TAG: [u8; PACKET_TAG_LENGTH] = [1; PACKET_TAG_LENGTH];
338
339    #[test]
340    fn test_packet_tag_bloom_filter() -> anyhow::Result<()> {
341        let mut filter1 = TagBloomFilter::default();
342
343        let items = (0..10_000)
344            .map(|i| {
345                let mut ret = random_bytes::<{ hopr_crypto_types::types::PACKET_TAG_LENGTH }>();
346                ret[i % hopr_crypto_types::types::PACKET_TAG_LENGTH] = 0xaa; // ensure it is not completely just zeroes
347                ret
348            })
349            .collect::<Vec<_>>();
350
351        // Insert items into the BF
352        items.iter().for_each(|item| filter1.set(item));
353
354        assert_eq!(items.len(), filter1.count(), "invalid number of items in bf");
355
356        //let len = filter1.to_bytes().len();
357
358        // Count the number of items in the BF (incl. false positives)
359        let match_count_1 = items.iter().filter(|item| filter1.check(item)).count();
360
361        let filter2 = TagBloomFilter::from_bytes(&filter1.to_bytes())?;
362
363        // Count the number of items in the BF (incl. false positives)
364        let match_count_2 = items.iter().filter(|item| filter2.check(item)).count();
365
366        assert_eq!(
367            match_count_1, match_count_2,
368            "the number of false positives must be equal"
369        );
370        assert_eq!(filter1.count(), filter2.count(), "the number of items must be equal");
371
372        // All zeroes must be present in neither BF; we ensured we never insert a zero tag
373        assert!(!filter1.check(&ZEROS_TAG), "bf 1 must not contain zero tag");
374        assert!(!filter2.check(&ZEROS_TAG), "bf 2 must not contain zero tag");
375
376        Ok(())
377    }
378
379    #[test]
380    fn tag_bloom_filter_count() {
381        let mut filter = TagBloomFilter::default();
382        assert!(!filter.check_and_set(&ZEROS_TAG));
383        assert_eq!(1, filter.count());
384
385        assert!(filter.check_and_set(&ZEROS_TAG));
386        assert_eq!(1, filter.count());
387
388        assert!(!filter.check_and_set(&ONES_TAG));
389        assert_eq!(2, filter.count());
390
391        assert!(filter.check_and_set(&ZEROS_TAG));
392        assert_eq!(2, filter.count());
393    }
394
395    #[test]
396    fn tag_bloom_filter_wrap_around() {
397        let mut filter = TagBloomFilter::with_capacity(1000);
398        for _ in 1..filter.capacity() {
399            let mut tag: PacketTag = hopr_crypto_random::random_bytes();
400            tag[0] = 0xaa; // ensure it's not all zeroes
401            assert!(!filter.check_and_set(&tag));
402        }
403        // Not yet at full capacity
404        assert_eq!(filter.capacity() - 1, filter.count());
405
406        // This entry is not there yet
407        assert!(!filter.check_and_set(&ZEROS_TAG));
408
409        // Now the filter is at capacity and contains the previously inserted entry
410        assert_eq!(filter.capacity(), filter.count());
411        assert!(filter.check(&ZEROS_TAG));
412
413        // This will not clear out the filter, since the entry is there
414        assert!(filter.check_and_set(&ZEROS_TAG));
415        assert_eq!(filter.capacity(), filter.count());
416
417        // This will clear out the filter, since this other entry is definitely not there
418        assert!(!filter.check_and_set(&ONES_TAG));
419        assert_eq!(1, filter.count());
420        assert!(filter.check(&ONES_TAG));
421    }
422}