1use crate::ant_protocol::{PROOF_TAG_MERKLE, PROOF_TAG_SINGLE_NODE};
7use ant_evm::merkle_payments::MerklePaymentProof;
8use ant_evm::ProofOfPayment;
9use evmlib::common::TxHash;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct PaymentProof {
18 pub proof_of_payment: ProofOfPayment,
20 pub tx_hashes: Vec<TxHash>,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ProofType {
28 SingleNode,
30 Merkle,
32}
33
34#[must_use]
38pub fn detect_proof_type(bytes: &[u8]) -> Option<ProofType> {
39 match bytes.first() {
40 Some(&PROOF_TAG_SINGLE_NODE) => Some(ProofType::SingleNode),
41 Some(&PROOF_TAG_MERKLE) => Some(ProofType::Merkle),
42 _ => None,
43 }
44}
45
46pub fn serialize_single_node_proof(
52 proof: &PaymentProof,
53) -> std::result::Result<Vec<u8>, rmp_serde::encode::Error> {
54 let body = rmp_serde::to_vec(proof)?;
55 let mut tagged = Vec::with_capacity(1 + body.len());
56 tagged.push(PROOF_TAG_SINGLE_NODE);
57 tagged.extend_from_slice(&body);
58 Ok(tagged)
59}
60
61pub fn serialize_merkle_proof(
67 proof: &MerklePaymentProof,
68) -> std::result::Result<Vec<u8>, rmp_serde::encode::Error> {
69 let body = rmp_serde::to_vec(proof)?;
70 let mut tagged = Vec::with_capacity(1 + body.len());
71 tagged.push(PROOF_TAG_MERKLE);
72 tagged.extend_from_slice(&body);
73 Ok(tagged)
74}
75
76pub fn deserialize_proof(bytes: &[u8]) -> Result<(ProofOfPayment, Vec<TxHash>), String> {
85 if bytes.first() != Some(&PROOF_TAG_SINGLE_NODE) {
86 return Err("Missing single-node proof tag byte".to_string());
87 }
88 let payload = bytes
89 .get(1..)
90 .ok_or_else(|| "Single-node proof tag present but no payload".to_string())?;
91 let proof = rmp_serde::from_slice::<PaymentProof>(payload)
92 .map_err(|e| format!("Failed to deserialize single-node proof: {e}"))?;
93 Ok((proof.proof_of_payment, proof.tx_hashes))
94}
95
96pub fn deserialize_merkle_proof(bytes: &[u8]) -> std::result::Result<MerklePaymentProof, String> {
104 if bytes.first() != Some(&PROOF_TAG_MERKLE) {
105 return Err("Missing merkle proof tag byte".to_string());
106 }
107 let payload = bytes
108 .get(1..)
109 .ok_or_else(|| "Merkle proof tag present but no payload".to_string())?;
110 rmp_serde::from_slice::<MerklePaymentProof>(payload)
111 .map_err(|e| format!("Failed to deserialize merkle proof: {e}"))
112}
113
114#[cfg(test)]
115#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
116mod tests {
117 use super::*;
118 use alloy::primitives::FixedBytes;
119 use ant_evm::merkle_payments::{
120 MerklePaymentCandidateNode, MerklePaymentCandidatePool, MerklePaymentProof, MerkleTree,
121 CANDIDATES_PER_POOL,
122 };
123 use ant_evm::RewardsAddress;
124 use ant_evm::{EncodedPeerId, PaymentQuote};
125 use evmlib::quoting_metrics::QuotingMetrics;
126 use libp2p::identity::Keypair;
127 use libp2p::PeerId;
128 use saorsa_core::MlDsa65;
129 use saorsa_pqc::pqc::types::MlDsaSecretKey;
130 use saorsa_pqc::pqc::MlDsaOperations;
131 use std::time::SystemTime;
132 use xor_name::XorName;
133
134 fn make_test_quote() -> PaymentQuote {
135 PaymentQuote {
136 content: XorName::random(&mut rand::thread_rng()),
137 timestamp: SystemTime::now(),
138 quoting_metrics: QuotingMetrics {
139 data_size: 1024,
140 data_type: 0,
141 close_records_stored: 0,
142 records_per_type: vec![],
143 max_records: 1000,
144 received_payment_count: 0,
145 live_time: 0,
146 network_density: None,
147 network_size: None,
148 },
149 rewards_address: RewardsAddress::new([1u8; 20]),
150 pub_key: vec![],
151 signature: vec![],
152 }
153 }
154
155 fn make_proof_of_payment() -> ProofOfPayment {
156 let keypair = Keypair::generate_ed25519();
157 let peer_id = PeerId::from_public_key(&keypair.public());
158 ProofOfPayment {
159 peer_quotes: vec![(EncodedPeerId::from(peer_id), make_test_quote())],
160 }
161 }
162
163 #[test]
164 fn test_payment_proof_serialization_roundtrip() {
165 let tx_hash = FixedBytes::from([0xABu8; 32]);
166 let proof = PaymentProof {
167 proof_of_payment: make_proof_of_payment(),
168 tx_hashes: vec![tx_hash],
169 };
170
171 let bytes = serialize_single_node_proof(&proof).unwrap();
172 let (pop, hashes) = deserialize_proof(&bytes).unwrap();
173
174 assert_eq!(pop.peer_quotes.len(), 1);
175 assert_eq!(hashes.len(), 1);
176 assert_eq!(hashes.first().unwrap(), &tx_hash);
177 }
178
179 #[test]
180 fn test_payment_proof_with_empty_tx_hashes() {
181 let proof = PaymentProof {
182 proof_of_payment: make_proof_of_payment(),
183 tx_hashes: vec![],
184 };
185
186 let bytes = serialize_single_node_proof(&proof).unwrap();
187 let (pop, hashes) = deserialize_proof(&bytes).unwrap();
188
189 assert_eq!(pop.peer_quotes.len(), 1);
190 assert!(hashes.is_empty());
191 }
192
193 #[test]
194 fn test_deserialize_proof_rejects_garbage() {
195 let garbage = vec![0xFF, 0x00, 0x01, 0x02];
196 let result = deserialize_proof(&garbage);
197 assert!(result.is_err());
198 }
199
200 #[test]
201 fn test_deserialize_proof_rejects_untagged() {
202 let proof = PaymentProof {
204 proof_of_payment: make_proof_of_payment(),
205 tx_hashes: vec![],
206 };
207 let raw_bytes = rmp_serde::to_vec(&proof).unwrap();
208 let result = deserialize_proof(&raw_bytes);
209 assert!(result.is_err());
210 }
211
212 #[test]
213 fn test_payment_proof_multiple_tx_hashes() {
214 let tx1 = FixedBytes::from([0x11u8; 32]);
215 let tx2 = FixedBytes::from([0x22u8; 32]);
216 let proof = PaymentProof {
217 proof_of_payment: make_proof_of_payment(),
218 tx_hashes: vec![tx1, tx2],
219 };
220
221 let bytes = serialize_single_node_proof(&proof).unwrap();
222 let (_, hashes) = deserialize_proof(&bytes).unwrap();
223
224 assert_eq!(hashes.len(), 2);
225 assert_eq!(hashes.first().unwrap(), &tx1);
226 assert_eq!(hashes.get(1).unwrap(), &tx2);
227 }
228
229 #[test]
234 fn test_detect_proof_type_single_node() {
235 let bytes = [PROOF_TAG_SINGLE_NODE, 0x00, 0x01];
236 let result = detect_proof_type(&bytes);
237 assert_eq!(result, Some(ProofType::SingleNode));
238 }
239
240 #[test]
241 fn test_detect_proof_type_merkle() {
242 let bytes = [PROOF_TAG_MERKLE, 0x00, 0x01];
243 let result = detect_proof_type(&bytes);
244 assert_eq!(result, Some(ProofType::Merkle));
245 }
246
247 #[test]
248 fn test_detect_proof_type_unknown_tag() {
249 let bytes = [0xFF, 0x00, 0x01];
250 let result = detect_proof_type(&bytes);
251 assert_eq!(result, None);
252 }
253
254 #[test]
255 fn test_detect_proof_type_empty_bytes() {
256 let bytes: &[u8] = &[];
257 let result = detect_proof_type(bytes);
258 assert_eq!(result, None);
259 }
260
261 #[test]
266 fn test_serialize_single_node_proof_roundtrip_with_tag() {
267 let tx_hash = FixedBytes::from([0xCCu8; 32]);
268 let proof = PaymentProof {
269 proof_of_payment: make_proof_of_payment(),
270 tx_hashes: vec![tx_hash],
271 };
272
273 let tagged_bytes = serialize_single_node_proof(&proof).unwrap();
274
275 assert_eq!(
277 tagged_bytes.first().copied(),
278 Some(PROOF_TAG_SINGLE_NODE),
279 "Tagged proof must start with PROOF_TAG_SINGLE_NODE"
280 );
281
282 assert_eq!(
284 detect_proof_type(&tagged_bytes),
285 Some(ProofType::SingleNode)
286 );
287
288 let (pop, hashes) = deserialize_proof(&tagged_bytes).unwrap();
290 assert_eq!(pop.peer_quotes.len(), 1);
291 assert_eq!(hashes.len(), 1);
292 assert_eq!(hashes.first().unwrap(), &tx_hash);
293 }
294
295 fn make_test_merkle_proof() -> MerklePaymentProof {
301 let timestamp = std::time::SystemTime::now()
302 .duration_since(std::time::UNIX_EPOCH)
303 .unwrap()
304 .as_secs();
305
306 let addresses: Vec<xor_name::XorName> = (0..4u8)
308 .map(|i| xor_name::XorName::from_content(&[i]))
309 .collect();
310 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
311
312 let candidate_nodes: [MerklePaymentCandidateNode; CANDIDATES_PER_POOL] =
314 std::array::from_fn(|i| {
315 let ml_dsa = MlDsa65::new();
316 let (pub_key, secret_key) = ml_dsa.generate_keypair().expect("keygen");
317 let metrics = QuotingMetrics {
318 data_size: 1024,
319 data_type: 0,
320 close_records_stored: i * 10,
321 records_per_type: vec![],
322 max_records: 500,
323 received_payment_count: 0,
324 live_time: 100,
325 network_density: None,
326 network_size: None,
327 };
328 #[allow(clippy::cast_possible_truncation)]
329 let reward_address = RewardsAddress::new([i as u8; 20]);
330 let msg =
331 MerklePaymentCandidateNode::bytes_to_sign(&metrics, &reward_address, timestamp);
332 let sk = MlDsaSecretKey::from_bytes(secret_key.as_bytes()).expect("sk");
333 let signature = ml_dsa.sign(&sk, &msg).expect("sign").as_bytes().to_vec();
334
335 MerklePaymentCandidateNode {
336 pub_key: pub_key.as_bytes().to_vec(),
337 quoting_metrics: metrics,
338 reward_address,
339 merkle_payment_timestamp: timestamp,
340 signature,
341 }
342 });
343
344 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
345 let midpoint_proof = reward_candidates.first().unwrap().clone();
346
347 let pool = MerklePaymentCandidatePool {
348 midpoint_proof,
349 candidate_nodes,
350 };
351
352 let first_address = *addresses.first().unwrap();
353 let address_proof = tree.generate_address_proof(0, first_address).unwrap();
354
355 MerklePaymentProof::new(first_address, address_proof, pool)
356 }
357
358 #[test]
359 fn test_serialize_merkle_proof_roundtrip() {
360 let merkle_proof = make_test_merkle_proof();
361
362 let tagged_bytes = serialize_merkle_proof(&merkle_proof).unwrap();
363
364 assert_eq!(
366 tagged_bytes.first().copied(),
367 Some(PROOF_TAG_MERKLE),
368 "Tagged merkle proof must start with PROOF_TAG_MERKLE"
369 );
370
371 assert_eq!(detect_proof_type(&tagged_bytes), Some(ProofType::Merkle));
373
374 let recovered = deserialize_merkle_proof(&tagged_bytes).unwrap();
376 assert_eq!(recovered.address, merkle_proof.address);
377 assert_eq!(
378 recovered.winner_pool.candidate_nodes.len(),
379 CANDIDATES_PER_POOL
380 );
381 }
382
383 #[test]
384 fn test_deserialize_merkle_proof_rejects_wrong_tag() {
385 let merkle_proof = make_test_merkle_proof();
386 let mut tagged_bytes = serialize_merkle_proof(&merkle_proof).unwrap();
387
388 if let Some(first) = tagged_bytes.first_mut() {
390 *first = PROOF_TAG_SINGLE_NODE;
391 }
392
393 let result = deserialize_merkle_proof(&tagged_bytes);
394 assert!(result.is_err(), "Should reject wrong tag byte");
395 let err_msg = result.unwrap_err();
396 assert!(
397 err_msg.contains("Missing merkle proof tag"),
398 "Error should mention missing tag: {err_msg}"
399 );
400 }
401
402 #[test]
403 fn test_deserialize_merkle_proof_rejects_empty() {
404 let result = deserialize_merkle_proof(&[]);
405 assert!(result.is_err());
406 }
407}