hopr_internal_types/
protocol.rs

1use hopr_crypto_random::Randomizable;
2use hopr_crypto_types::prelude::*;
3#[cfg(feature = "rayon")]
4use hopr_parallelize::cpu::rayon::prelude::*;
5use hopr_primitive_types::prelude::*;
6
7use crate::{
8    errors::{CoreTypesError, Result},
9    prelude::UnacknowledgedTicket,
10};
11
12/// Number of intermediate hops: 3 relayers and 1 destination
13pub const INTERMEDIATE_HOPS: usize = 3;
14
15/// Default required minimum incoming ticket winning probability
16pub const DEFAULT_MINIMUM_INCOMING_TICKET_WIN_PROB: f64 = 1.0;
17
18/// Default maximum incoming ticket winning probability, above which tickets will not be accepted
19/// due to privacy.
20pub const DEFAULT_MAXIMUM_INCOMING_TICKET_WIN_PROB: f64 = 1.0; // TODO: change this in 3.0
21
22/// Alias for the [`Pseudonym`](`hopr_crypto_types::types::Pseudonym`) used in the HOPR protocol.
23pub type HoprPseudonym = SimplePseudonym;
24
25/// Unverified packet acknowledgement.
26///
27/// This acknowledgement can be serialized and deserialized to be sent over the wire.
28///
29/// To retrieve useful data, it has to be [verified](`Acknowledgement::verify`) using the public
30/// key of its sender.
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct Acknowledgement(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; Self::SIZE]);
34
35impl AsRef<[u8]> for Acknowledgement {
36    fn as_ref(&self) -> &[u8] {
37        &self.0
38    }
39}
40
41impl TryFrom<&[u8]> for Acknowledgement {
42    type Error = GeneralError;
43
44    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
45        Ok(Self(
46            value
47                .try_into()
48                .map_err(|_| GeneralError::ParseError("Acknowledgement".into()))?,
49        ))
50    }
51}
52
53impl BytesRepresentable for Acknowledgement {
54    const SIZE: usize = HalfKey::SIZE + OffchainSignature::SIZE;
55}
56
57/// Minimum batch size of acknowledgements to use EdDSA batch verification algorithm.
58pub const MIN_BATCH_SIZE: usize = 4; // TODO: update this based on benchmarks
59
60impl Acknowledgement {
61    /// Attempts to verify the acknowledgement given the `sender_node_key` that sent the acknowledgement.
62    pub fn verify(self, sender_node_key: &OffchainPublicKey) -> Result<VerifiedAcknowledgement> {
63        let signature = OffchainSignature::try_from(&self.0[HalfKey::SIZE..HalfKey::SIZE + OffchainSignature::SIZE])?;
64        if signature.verify_message(&self.0[0..HalfKey::SIZE], sender_node_key) {
65            Ok(VerifiedAcknowledgement {
66                ack_key_share: HalfKey::try_from(&self.0[0..HalfKey::SIZE])?,
67                signature,
68            })
69        } else {
70            Err(CoreTypesError::InvalidAcknowledgement)
71        }
72    }
73
74    /// Performs batch verification of acknowledgements received from given senders.
75    ///
76    /// For batches of sizes smaller than [`MIN_BATCH_SIZE`] or for iterators with an unknown lower bound of size,
77    /// the regular sequential verification of each signature is always performed.
78    ///
79    /// For larger batches, this method makes use of the EdDSA batch signature verification algorithm,
80    /// which more effectively verifies the batch. This is faster than verifying the signatures individually.
81    /// However, it comes at a cost of not knowing which signature was invalid in the case of a failure.
82    ///
83    /// This method first tries to verify the batch using the batch signature verification, returning a fast
84    /// successful verification result quickly.
85    ///
86    /// If one or more of the acknowledgements in the batch were invalid, it preforms individual checks for
87    /// each signature in the batch, returning the vector of results.
88    ///
89    /// These individual signature checks could be sequential or parallel if the `rayon` feature is enabled.
90    pub fn verify_batch<I: IntoIterator<Item = (OffchainPublicKey, Self)>>(
91        acknowledgements: I,
92    ) -> Vec<Result<VerifiedAcknowledgement>> {
93        let acknowledgements = acknowledgements.into_iter();
94        let (sz_lower_bound, _) = acknowledgements.size_hint();
95
96        // Use regular verification for batches of small or unknown sizes
97        if sz_lower_bound < MIN_BATCH_SIZE {
98            return acknowledgements
99                .into_iter()
100                .map(|(key, ack)| ack.verify(&key))
101                .collect();
102        }
103
104        let mut optimistic_result = Vec::with_capacity(sz_lower_bound);
105        let mut keys = Vec::with_capacity(sz_lower_bound);
106
107        let optimistic_check = OffchainSignature::verify_batch(acknowledgements.filter_map(|(key, ack)| {
108            let maybe_ack_int = HalfKey::try_from(&ack.0[0..HalfKey::SIZE]).and_then(|ack_key_share| {
109                OffchainSignature::try_from(&ack.0[HalfKey::SIZE..HalfKey::SIZE + OffchainSignature::SIZE])
110                    .map(|signature| (ack_key_share, signature))
111            });
112
113            match maybe_ack_int {
114                Ok((ack_key_share, signature)) => {
115                    optimistic_result.push(Ok(VerifiedAcknowledgement {
116                        ack_key_share,
117                        signature,
118                    }));
119                    keys.push(Some(key)); // Store the keys in case we need to verify again
120                    Some(((ack_key_share, signature), key))
121                }
122                Err(err) => {
123                    optimistic_result.push(Err(err.into()));
124                    keys.push(None);
125                    None
126                }
127            }
128        }));
129
130        // If the batch verification succeeded, we can return the entire batch as verified.
131        // Otherwise, we need to check individual acknowledgements to see which ones failed.
132        if optimistic_check {
133            optimistic_result
134        } else {
135            #[cfg(feature = "rayon")]
136            let iter = (keys, optimistic_result).into_par_iter();
137
138            #[cfg(not(feature = "rayon"))]
139            let iter = keys.into_iter().zip(optimistic_result.into_iter());
140
141            iter.filter_map(|(key, res)| key.zip(res.ok().map(VerifiedAcknowledgement::leak)))
142                .map(|(key, ack)| ack.verify(&key))
143                .collect()
144        }
145    }
146}
147
148/// Represents packet acknowledgement whose signature has been already verified.
149///
150/// This acknowledgement cannot be serialized.
151#[derive(Copy, Clone, Debug, PartialEq)]
152pub struct VerifiedAcknowledgement {
153    ack_key_share: HalfKey,
154    signature: OffchainSignature,
155}
156
157impl VerifiedAcknowledgement {
158    /// Creates a new verified acknowledgement by signing the given [`HalfKey`] with the given [`OffchainKeypair`].
159    pub fn new(ack_key_share: HalfKey, node_keypair: &OffchainKeypair) -> Self {
160        let signature = OffchainSignature::sign_message(ack_key_share.as_ref(), node_keypair);
161        Self {
162            ack_key_share,
163            signature,
164        }
165    }
166
167    /// Generates random but still a valid acknowledgement.
168    pub fn random(offchain_keypair: &OffchainKeypair) -> Self {
169        Self::new(HalfKey::random(), offchain_keypair)
170    }
171
172    /// Downgrades this verified acknowledgement to an unverified serializable one.
173    pub fn leak(self) -> Acknowledgement {
174        let mut ret = [0u8; Acknowledgement::SIZE];
175        ret[0..HalfKey::SIZE].copy_from_slice(self.ack_key_share.as_ref());
176        ret[HalfKey::SIZE..HalfKey::SIZE + OffchainSignature::SIZE].copy_from_slice(self.signature.as_ref());
177        Acknowledgement(ret)
178    }
179
180    /// Gets the acknowledged key out of this acknowledgement.
181    ///
182    /// This is the remaining part of the solution of the `Ticket` challenge.
183    pub fn ack_key_share(&self) -> &HalfKey {
184        &self.ack_key_share
185    }
186}
187
188/// Contains either unacknowledged ticket if we're waiting for the acknowledgement as a relayer
189/// or information if we wait for the acknowledgement as a sender.
190#[derive(Clone, Debug, PartialEq, Eq)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192pub enum PendingAcknowledgement {
193    /// We're waiting for acknowledgement as a sender
194    WaitingAsSender,
195    /// We're waiting for the acknowledgement as a relayer with a ticket
196    WaitingAsRelayer(Box<UnacknowledgedTicket>),
197}
198
199#[cfg(test)]
200mod tests {
201    use std::ops::Not;
202
203    use super::*;
204
205    #[test]
206    fn acknowledgement_should_verify() -> anyhow::Result<()> {
207        let kp = OffchainKeypair::random();
208        let v_ack_1 = VerifiedAcknowledgement::random(&kp);
209        let v_ack_2 = v_ack_1.leak().verify(kp.public())?;
210
211        assert_eq!(v_ack_1, v_ack_2);
212        Ok(())
213    }
214
215    #[parameterized::parameterized(batch_size = {1, 2, 100 })]
216    fn acknowledgement_should_verify_batch(batch_size: usize) -> anyhow::Result<()> {
217        let mut verified_acks = Vec::with_capacity(100);
218        let batch = (0..batch_size)
219            .map(|_| {
220                let kp = OffchainKeypair::random();
221                let ack = VerifiedAcknowledgement::random(&kp);
222                verified_acks.push(ack);
223                (*kp.public(), ack.leak())
224            })
225            .collect::<Vec<_>>();
226
227        let res = Acknowledgement::verify_batch(batch);
228
229        assert_eq!(batch_size, res.len());
230        assert!(res.iter().all(|r| r.is_ok()));
231        assert_eq!(verified_acks, res.into_iter().map(|r| r.unwrap()).collect::<Vec<_>>());
232
233        Ok(())
234    }
235
236    #[test]
237    fn acknowledgement_should_return_failed_ack_in_the_batch_verification() -> anyhow::Result<()> {
238        let mut verified_acks = Vec::with_capacity(100);
239        let batch = (0..100)
240            .map(|i| {
241                let kp = OffchainKeypair::random();
242                let ack = VerifiedAcknowledgement::random(&kp);
243                if i == 50 {
244                    let mut non_verified = ack.leak();
245                    non_verified.0[3] = non_verified.0[3].not();
246                    (*kp.public(), non_verified)
247                } else {
248                    verified_acks.push(ack);
249                    (*kp.public(), ack.leak())
250                }
251            })
252            .collect::<Vec<_>>();
253
254        let res = Acknowledgement::verify_batch(batch);
255
256        assert_eq!(100, res.len());
257        assert!(res[50].is_err());
258        assert!(res.iter().enumerate().all(|(i, r)| r.is_ok() || i == 50));
259        assert_eq!(
260            verified_acks,
261            res.into_iter().filter_map(|r| r.ok()).collect::<Vec<_>>()
262        );
263
264        Ok(())
265    }
266}