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