1use hopr_types::{
16 crypto::prelude::{HalfKey, HalfKeyChallenge, OffchainPublicKey},
17 internal::prelude::Acknowledgement,
18};
19#[cfg(feature = "rayon")]
20use hopr_utils::parallelize::cpu::rayon::prelude::*;
21
22pub fn verify_expected_acknowledgements<V>(
38 peer: OffchainPublicKey,
39 acks: Vec<Acknowledgement>,
40 awaiting_acks: &moka::sync::Cache<OffchainPublicKey, V>,
41 use_batch_verification: bool,
42) -> Option<(V, Vec<(HalfKey, HalfKeyChallenge)>)>
43where
44 V: Clone + Send + Sync + 'static,
45{
46 if !awaiting_acks.contains_key(&peer) {
51 tracing::trace!("not awaiting any acknowledgement from peer");
52 return None;
53 }
54 let Some(awaiting_ack_from_peer) = awaiting_acks.get(&peer) else {
55 tracing::trace!("not awaiting any acknowledgement from peer");
56 return None;
57 };
58
59 let half_keys_challenges = if use_batch_verification {
61 let acks = Acknowledgement::verify_batch(acks.into_iter().map(|ack| (peer, ack)));
64
65 #[cfg(feature = "rayon")]
66 let iter = acks.into_par_iter();
67
68 #[cfg(not(feature = "rayon"))]
69 let iter = acks.into_iter();
70
71 iter.map(|verified| {
72 verified.and_then(|verified| Ok((*verified.ack_key_share(), verified.ack_key_share().to_challenge()?)))
73 })
74 .filter_map(|res| {
75 res.inspect_err(|error| tracing::error!(%error, "failed to process acknowledgement"))
76 .ok()
77 })
78 .collect::<Vec<_>>()
79 } else {
80 #[cfg(feature = "rayon")]
81 let iter = acks.into_par_iter();
82
83 #[cfg(not(feature = "rayon"))]
84 let iter = acks.into_iter();
85
86 iter.map(|ack| {
87 ack.verify(&peer)
88 .and_then(|verified| Ok((*verified.ack_key_share(), verified.ack_key_share().to_challenge()?)))
89 })
90 .filter_map(|res| {
91 res.inspect_err(|error| tracing::error!(%error, "failed to process acknowledgement"))
92 .ok()
93 })
94 .collect::<Vec<_>>()
95 };
96
97 Some((awaiting_ack_from_peer, half_keys_challenges))
98}
99
100#[cfg(test)]
101mod tests {
102 use hopr_types::{
103 crypto::prelude::{HalfKey, Keypair, OffchainKeypair},
104 crypto_random::Randomizable,
105 internal::prelude::VerifiedAcknowledgement,
106 };
107
108 use super::*;
109
110 type Marker = u32;
113
114 fn cache() -> moka::sync::Cache<OffchainPublicKey, Marker> {
115 moka::sync::CacheBuilder::new(16).build()
116 }
117
118 fn make_acks(count: usize, signer: &OffchainKeypair) -> Vec<(HalfKey, Acknowledgement)> {
119 (0..count)
120 .map(|_| {
121 let hk = HalfKey::random();
122 let ack = VerifiedAcknowledgement::new(hk, signer).leak();
123 (hk, ack)
124 })
125 .collect()
126 }
127
128 #[test]
129 fn returns_none_when_peer_is_not_in_cache() {
130 let cache = cache();
131 let peer = OffchainKeypair::random();
132
133 assert!(verify_expected_acknowledgements(*peer.public(), vec![], &cache, false).is_none());
135 assert!(verify_expected_acknowledgements(*peer.public(), vec![], &cache, true).is_none());
136
137 let other = OffchainKeypair::random();
139 cache.insert(*other.public(), 1);
140 assert!(verify_expected_acknowledgements(*peer.public(), vec![], &cache, false).is_none());
141 assert!(verify_expected_acknowledgements(*peer.public(), vec![], &cache, true).is_none());
142 }
143
144 #[test]
145 fn returns_empty_pairs_for_empty_acks() -> anyhow::Result<()> {
146 for use_batch in [false, true] {
147 let cache = cache();
148 let peer = OffchainKeypair::random();
149 cache.insert(*peer.public(), 42);
150
151 let (value, pairs) = verify_expected_acknowledgements(*peer.public(), vec![], &cache, use_batch)
152 .ok_or_else(|| anyhow::anyhow!("expected Some for peer in cache"))?;
153 assert_eq!(42, value, "must return the cached value for the peer");
154 assert!(pairs.is_empty());
155 }
156 Ok(())
157 }
158
159 #[test]
160 fn verifies_all_valid_acknowledgements() -> anyhow::Result<()> {
161 for use_batch in [false, true] {
162 let cache = cache();
163 let peer = OffchainKeypair::random();
164 cache.insert(*peer.public(), 7);
165
166 const N: usize = 5;
167 let prepared = make_acks(N, &peer);
168 let mut expected: Vec<(HalfKey, HalfKeyChallenge)> = prepared
169 .iter()
170 .map(|(hk, _)| Ok::<_, anyhow::Error>((*hk, hk.to_challenge()?)))
171 .collect::<Result<_, _>>()?;
172 let acks = prepared.into_iter().map(|(_, a)| a).collect();
173
174 let (value, mut pairs) = verify_expected_acknowledgements(*peer.public(), acks, &cache, use_batch)
175 .ok_or_else(|| anyhow::anyhow!("expected Some"))?;
176
177 assert_eq!(7, value);
178 assert_eq!(N, pairs.len());
179
180 let key = |(_, ch): &(HalfKey, HalfKeyChallenge)| ch.as_ref().to_vec();
183 pairs.sort_by_key(key);
184 expected.sort_by_key(key);
185 assert_eq!(expected, pairs);
186 }
187 Ok(())
188 }
189
190 #[test]
191 fn drops_acknowledgements_signed_by_wrong_peer() -> anyhow::Result<()> {
192 for use_batch in [false, true] {
193 let cache = cache();
194 let peer = OffchainKeypair::random();
195 let imposter = OffchainKeypair::random();
196 cache.insert(*peer.public(), 0);
197
198 let valid = make_acks(3, &peer);
200 let bogus = make_acks(2, &imposter);
201
202 let expected_challenges: std::collections::HashSet<_> = valid
203 .iter()
204 .map(|(hk, _)| hk.to_challenge())
205 .collect::<Result<_, _>>()?;
206
207 let mut acks: Vec<Acknowledgement> = valid.into_iter().map(|(_, a)| a).collect();
208 acks.extend(bogus.into_iter().map(|(_, a)| a));
209
210 let (_, pairs) = verify_expected_acknowledgements(*peer.public(), acks, &cache, use_batch)
211 .ok_or_else(|| anyhow::anyhow!("expected Some"))?;
212
213 assert_eq!(3, pairs.len(), "only valid acks must survive (use_batch={use_batch})");
214 let got: std::collections::HashSet<_> = pairs.into_iter().map(|(_, ch)| ch).collect();
215 assert_eq!(expected_challenges, got);
216 }
217 Ok(())
218 }
219
220 #[test]
221 fn existence_check_does_not_load_default_value() {
222 let cache = cache();
225 let peer = OffchainKeypair::random();
226
227 assert!(verify_expected_acknowledgements(*peer.public(), vec![], &cache, false).is_none());
228 assert_eq!(0, cache.entry_count(), "no entry must be inserted on miss");
229 }
230}