1use crate::EvmError;
10use evmlib::{
11 common::{Address as RewardsAddress, QuoteHash},
12 quoting_metrics::QuotingMetrics,
13};
14use libp2p::{Multiaddr, PeerId, identity::PublicKey};
15use serde::{Deserialize, Serialize};
16pub use std::time::SystemTime;
17use xor_name::XorName;
18
19const LIVE_TIME_MARGIN: u64 = 10;
21
22#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
23pub struct EncodedPeerId(Vec<u8>);
24
25impl EncodedPeerId {
26 pub fn to_peer_id(&self) -> Result<PeerId, libp2p::identity::ParseError> {
27 PeerId::from_bytes(&self.0)
28 }
29}
30
31impl From<PeerId> for EncodedPeerId {
32 fn from(peer_id: PeerId) -> Self {
33 let bytes = peer_id.to_bytes();
34 EncodedPeerId(bytes)
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
40pub struct ClientProofOfPayment {
41 pub peer_quotes: Vec<(EncodedPeerId, Vec<Multiaddr>, PaymentQuote)>,
42}
43
44impl ClientProofOfPayment {
45 pub fn payees(&self) -> Vec<(PeerId, Vec<Multiaddr>)> {
47 self.peer_quotes
48 .iter()
49 .filter_map(|(peer_id, addrs, _)| {
50 if let Ok(peer_id) = peer_id.to_peer_id() {
51 Some((peer_id, addrs.clone()))
52 } else {
53 None
54 }
55 })
56 .collect()
57 }
58
59 pub fn to_proof_of_payment(&self) -> ProofOfPayment {
61 let peer_quotes = self
62 .peer_quotes
63 .iter()
64 .map(|(peer_id, _addrs, quote)| (peer_id.clone(), quote.clone()))
65 .collect();
66 ProofOfPayment { peer_quotes }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
72pub struct ProofOfPayment {
73 pub peer_quotes: Vec<(EncodedPeerId, PaymentQuote)>,
74}
75
76impl ProofOfPayment {
77 pub fn digest(&self) -> Vec<(QuoteHash, QuotingMetrics, RewardsAddress)> {
79 self.peer_quotes
80 .clone()
81 .into_iter()
82 .map(|(_, quote)| (quote.hash(), quote.quoting_metrics, quote.rewards_address))
83 .collect()
84 }
85
86 pub fn payees(&self) -> Vec<PeerId> {
88 self.peer_quotes
89 .iter()
90 .filter_map(|(peer_id, _)| peer_id.to_peer_id().ok())
91 .collect()
92 }
93
94 pub fn quotes_by_peer(&self, peer_id: &PeerId) -> Vec<&PaymentQuote> {
96 self.peer_quotes
97 .iter()
98 .filter_map(|(_id, quote)| {
99 if let Ok(quote_peer_id) = quote.peer_id()
100 && *peer_id == quote_peer_id
101 {
102 return Some(quote);
103 }
104 None
105 })
106 .collect()
107 }
108
109 pub fn verify_for(&self, peer_id: PeerId) -> bool {
111 if !self.payees().contains(&peer_id) {
113 warn!("Payment does not contain node peer id");
114 debug!("Payment contains peer ids: {:?}", self.payees());
115 debug!("Node peer id: {:?}", peer_id);
116 return false;
117 }
118
119 for (encoded_peer_id, quote) in self.peer_quotes.iter() {
121 let peer_id = match encoded_peer_id.to_peer_id() {
122 Ok(peer_id) => peer_id,
123 Err(e) => {
124 warn!("Invalid encoded peer id: {e}");
125 return false;
126 }
127 };
128 if !quote.check_is_signed_by_claimed_peer(peer_id) {
129 warn!("Payment is not signed by claimed peer");
130 return false;
131 }
132 }
133 true
134 }
135
136 pub fn verify_data_type(&self, data_type: u32) -> bool {
138 for (_, quote) in self.peer_quotes.iter() {
139 if quote.quoting_metrics.data_type != data_type {
140 return false;
141 }
142 }
143
144 true
145 }
146}
147
148#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, custom_debug::Debug)]
152pub struct PaymentQuote {
153 pub content: XorName,
155 pub timestamp: SystemTime,
157 pub quoting_metrics: QuotingMetrics,
159 pub rewards_address: RewardsAddress,
161 #[debug(skip)]
163 pub pub_key: Vec<u8>,
164 #[debug(skip)]
166 pub signature: Vec<u8>,
167}
168
169impl PaymentQuote {
170 pub fn hash(&self) -> QuoteHash {
171 let mut bytes = self.bytes_for_sig();
172 bytes.extend_from_slice(self.pub_key.as_slice());
173 bytes.extend_from_slice(self.signature.as_slice());
174 evmlib::cryptography::hash(bytes)
175 }
176
177 pub fn bytes_for_signing(
179 xorname: XorName,
180 timestamp: SystemTime,
181 quoting_metrics: &QuotingMetrics,
182 rewards_address: &RewardsAddress,
183 ) -> Vec<u8> {
184 let mut bytes = xorname.to_vec();
185 bytes.extend_from_slice(
186 ×tamp
187 .duration_since(SystemTime::UNIX_EPOCH)
188 .expect("Unix epoch to be in the past")
189 .as_secs()
190 .to_le_bytes(),
191 );
192 let serialised_quoting_metrics = rmp_serde::to_vec(quoting_metrics).unwrap_or_default();
193 bytes.extend_from_slice(&serialised_quoting_metrics);
194 bytes.extend_from_slice(rewards_address.as_slice());
195 bytes
196 }
197
198 pub fn bytes_for_sig(&self) -> Vec<u8> {
200 Self::bytes_for_signing(
201 self.content,
202 self.timestamp,
203 &self.quoting_metrics,
204 &self.rewards_address,
205 )
206 }
207
208 pub fn peer_id(&self) -> Result<PeerId, EvmError> {
210 if let Ok(pub_key) = libp2p::identity::PublicKey::try_decode_protobuf(&self.pub_key) {
211 Ok(PeerId::from(pub_key.clone()))
212 } else {
213 error!("Can't parse PublicKey from protobuf");
214 Err(EvmError::InvalidQuotePublicKey)
215 }
216 }
217
218 pub fn check_is_signed_by_claimed_peer(&self, claimed_peer: PeerId) -> bool {
220 let pub_key = if let Ok(pub_key) = PublicKey::try_decode_protobuf(&self.pub_key) {
221 pub_key
222 } else {
223 error!("Can't parse PublicKey from protobuf");
224 return false;
225 };
226
227 let self_peer_id = PeerId::from(pub_key.clone());
228
229 if self_peer_id != claimed_peer {
230 error!("This quote {self:?} of {self_peer_id:?} is not signed by {claimed_peer:?}");
231 return false;
232 }
233
234 let bytes = self.bytes_for_sig();
235
236 if !pub_key.verify(&bytes, &self.signature) {
237 error!("Signature is not signed by claimed pub_key");
238 return false;
239 }
240
241 true
242 }
243
244 #[cfg(test)]
246 pub fn test_dummy(xorname: XorName) -> Self {
247 use evmlib::utils::dummy_address;
248
249 Self {
250 content: xorname,
251 timestamp: SystemTime::now(),
252 quoting_metrics: QuotingMetrics {
253 data_size: 0,
254 data_type: 0,
255 close_records_stored: 0,
256 records_per_type: vec![],
257 max_records: 0,
258 received_payment_count: 0,
259 live_time: 0,
260 network_density: None,
261 network_size: None,
262 },
263 pub_key: vec![],
264 signature: vec![],
265 rewards_address: dummy_address(),
266 }
267 }
268
269 pub fn is_newer_than(&self, other: &Self) -> bool {
271 self.timestamp > other.timestamp
272 }
273
274 pub fn historical_verify(&self, other: &Self) -> bool {
277 let self_is_newer = self.is_newer_than(other);
279 let (old_quote, new_quote) = if self_is_newer {
280 (other, self)
281 } else {
282 (self, other)
283 };
284
285 if new_quote.quoting_metrics.live_time < old_quote.quoting_metrics.live_time {
286 info!("Claimed live_time out of sequence");
287 return false;
288 }
289
290 if new_quote.quoting_metrics.received_payment_count
292 < old_quote.quoting_metrics.received_payment_count
293 {
294 info!("claimed received_payment_count out of sequence");
295 return false;
296 }
297
298 let old_elapsed = if let Ok(elapsed) = old_quote.timestamp.elapsed() {
299 elapsed
300 } else {
301 info!("old_quote timestamp elapsed call failure");
304 return true;
305 };
306 let new_elapsed = if let Ok(elapsed) = new_quote.timestamp.elapsed() {
307 elapsed
308 } else {
309 info!("new_quote timestamp elapsed call failure");
312 return true;
313 };
314
315 let time_diff = old_elapsed.as_secs().saturating_sub(new_elapsed.as_secs());
316 let live_time_diff =
317 new_quote.quoting_metrics.live_time - old_quote.quoting_metrics.live_time;
318 if live_time_diff > time_diff + LIVE_TIME_MARGIN {
320 info!("claimed live_time out of sync with the timestamp");
321 return false;
322 }
323
324 debug!(
328 "The new quote has {} close records stored, meanwhile old one has {}.",
329 new_quote.quoting_metrics.close_records_stored,
330 old_quote.quoting_metrics.close_records_stored
331 );
332
333 true
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 use libp2p::identity::Keypair;
342 use std::{thread::sleep, time::Duration};
343
344 #[test]
345 fn test_encode_decode_peer_id() {
346 let id = PeerId::random();
347 let encoded = EncodedPeerId::from(id);
348 let decoded = encoded.to_peer_id().expect("decode to work");
349 assert_eq!(id, decoded);
350 }
351
352 #[test]
353 fn test_is_newer_than() {
354 let old_quote = PaymentQuote::test_dummy(Default::default());
355 sleep(Duration::from_millis(100));
356 let new_quote = PaymentQuote::test_dummy(Default::default());
357 assert!(new_quote.is_newer_than(&old_quote));
358 assert!(!old_quote.is_newer_than(&new_quote));
359 }
360
361 #[test]
362 fn test_is_signed_by_claimed_peer() {
363 let keypair = Keypair::generate_ed25519();
364 let peer_id = keypair.public().to_peer_id();
365
366 let false_peer = PeerId::random();
367
368 let mut quote = PaymentQuote::test_dummy(Default::default());
369 let bytes = quote.bytes_for_sig();
370 let signature = if let Ok(sig) = keypair.sign(&bytes) {
371 sig
372 } else {
373 panic!("Cannot sign the quote!");
374 };
375
376 assert!(!quote.check_is_signed_by_claimed_peer(peer_id));
378 assert!(!quote.check_is_signed_by_claimed_peer(false_peer));
379
380 quote.pub_key = keypair.public().encode_protobuf();
382 assert!(!quote.check_is_signed_by_claimed_peer(peer_id));
383 assert!(!quote.check_is_signed_by_claimed_peer(false_peer));
384
385 quote.signature = signature;
388 assert!(quote.check_is_signed_by_claimed_peer(peer_id));
389 assert!(!quote.check_is_signed_by_claimed_peer(false_peer));
390
391 quote.pub_key = Keypair::generate_ed25519().public().encode_protobuf();
393 assert!(!quote.check_is_signed_by_claimed_peer(peer_id));
394 assert!(!quote.check_is_signed_by_claimed_peer(false_peer));
395 }
396
397 #[test]
398 fn test_historical_verify() {
399 let mut old_quote = PaymentQuote::test_dummy(Default::default());
400 sleep(Duration::from_millis(100));
401 let mut new_quote = PaymentQuote::test_dummy(Default::default());
402
403 assert!(new_quote.historical_verify(&old_quote));
405 assert!(old_quote.historical_verify(&new_quote));
406
407 old_quote.quoting_metrics.received_payment_count = 10;
409 new_quote.quoting_metrics.received_payment_count = 9;
410 assert!(!new_quote.historical_verify(&old_quote));
411 assert!(!old_quote.historical_verify(&new_quote));
412 new_quote.quoting_metrics.received_payment_count = 11;
414 assert!(new_quote.historical_verify(&old_quote));
415 assert!(old_quote.historical_verify(&new_quote));
416
417 new_quote.quoting_metrics.live_time = 10;
419 old_quote.quoting_metrics.live_time = 11;
420 assert!(!new_quote.historical_verify(&old_quote));
421 assert!(!old_quote.historical_verify(&new_quote));
422 new_quote.quoting_metrics.live_time = 11 + LIVE_TIME_MARGIN + 1;
424 assert!(!new_quote.historical_verify(&old_quote));
425 assert!(!old_quote.historical_verify(&new_quote));
426 new_quote.quoting_metrics.live_time = 11 + LIVE_TIME_MARGIN - 1;
428 assert!(new_quote.historical_verify(&old_quote));
429 assert!(old_quote.historical_verify(&new_quote));
430 }
431}