1use crate::chunk::{PROOF_TAG_MERKLE, PROOF_TAG_SINGLE_NODE};
7use evmlib::common::TxHash;
8use evmlib::merkle_payments::MerklePaymentProof;
9use evmlib::ProofOfPayment;
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)]
27#[non_exhaustive]
28pub enum ProofType {
29 SingleNode,
31 Merkle,
33}
34
35#[must_use]
39pub fn detect_proof_type(bytes: &[u8]) -> Option<ProofType> {
40 match bytes.first() {
41 Some(&PROOF_TAG_SINGLE_NODE) => Some(ProofType::SingleNode),
42 Some(&PROOF_TAG_MERKLE) => Some(ProofType::Merkle),
43 _ => None,
44 }
45}
46
47pub fn serialize_single_node_proof(
53 proof: &PaymentProof,
54) -> std::result::Result<Vec<u8>, rmp_serde::encode::Error> {
55 let body = rmp_serde::to_vec(proof)?;
56 let mut tagged = Vec::with_capacity(1 + body.len());
57 tagged.push(PROOF_TAG_SINGLE_NODE);
58 tagged.extend_from_slice(&body);
59 Ok(tagged)
60}
61
62pub fn serialize_merkle_proof(
68 proof: &MerklePaymentProof,
69) -> std::result::Result<Vec<u8>, rmp_serde::encode::Error> {
70 let body = rmp_serde::to_vec(proof)?;
71 let mut tagged = Vec::with_capacity(1 + body.len());
72 tagged.push(PROOF_TAG_MERKLE);
73 tagged.extend_from_slice(&body);
74 Ok(tagged)
75}
76
77pub fn deserialize_proof(bytes: &[u8]) -> Result<(ProofOfPayment, Vec<TxHash>), String> {
86 if bytes.first() != Some(&PROOF_TAG_SINGLE_NODE) {
87 return Err("Missing single-node proof tag byte".to_string());
88 }
89 let payload = bytes
90 .get(1..)
91 .ok_or_else(|| "Single-node proof tag present but no payload".to_string())?;
92 let proof = rmp_serde::from_slice::<PaymentProof>(payload)
93 .map_err(|e| format!("Failed to deserialize single-node proof: {e}"))?;
94 Ok((proof.proof_of_payment, proof.tx_hashes))
95}
96
97pub fn deserialize_merkle_proof(bytes: &[u8]) -> std::result::Result<MerklePaymentProof, String> {
105 if bytes.first() != Some(&PROOF_TAG_MERKLE) {
106 return Err("Missing merkle proof tag byte".to_string());
107 }
108 let payload = bytes
109 .get(1..)
110 .ok_or_else(|| "Merkle proof tag present but no payload".to_string())?;
111 rmp_serde::from_slice::<MerklePaymentProof>(payload)
112 .map_err(|e| format!("Failed to deserialize merkle proof: {e}"))
113}
114
115#[cfg(test)]
116#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
117mod tests {
118 use super::*;
119 use alloy::primitives::FixedBytes;
120 use evmlib::common::Amount;
121 use evmlib::merkle_payments::{
122 MerklePaymentCandidateNode, MerklePaymentCandidatePool, MerklePaymentProof, MerkleTree,
123 CANDIDATES_PER_POOL,
124 };
125 use evmlib::EncodedPeerId;
126 use evmlib::PaymentQuote;
127 use evmlib::RewardsAddress;
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 price: Amount::from(1u64),
139 rewards_address: RewardsAddress::new([1u8; 20]),
140 pub_key: vec![],
141 signature: vec![],
142 }
143 }
144
145 fn make_proof_of_payment() -> ProofOfPayment {
146 let random_peer = EncodedPeerId::new(rand::random());
147 ProofOfPayment {
148 peer_quotes: vec![(random_peer, make_test_quote())],
149 }
150 }
151
152 #[test]
153 fn test_payment_proof_serialization_roundtrip() {
154 let tx_hash = FixedBytes::from([0xABu8; 32]);
155 let proof = PaymentProof {
156 proof_of_payment: make_proof_of_payment(),
157 tx_hashes: vec![tx_hash],
158 };
159
160 let bytes = serialize_single_node_proof(&proof).unwrap();
161 let (pop, hashes) = deserialize_proof(&bytes).unwrap();
162
163 assert_eq!(pop.peer_quotes.len(), 1);
164 assert_eq!(hashes.len(), 1);
165 assert_eq!(hashes.first().unwrap(), &tx_hash);
166 }
167
168 #[test]
169 fn test_payment_proof_with_empty_tx_hashes() {
170 let proof = PaymentProof {
171 proof_of_payment: make_proof_of_payment(),
172 tx_hashes: vec![],
173 };
174
175 let bytes = serialize_single_node_proof(&proof).unwrap();
176 let (pop, hashes) = deserialize_proof(&bytes).unwrap();
177
178 assert_eq!(pop.peer_quotes.len(), 1);
179 assert!(hashes.is_empty());
180 }
181
182 #[test]
183 fn test_deserialize_proof_rejects_garbage() {
184 let garbage = vec![0xFF, 0x00, 0x01, 0x02];
185 let result = deserialize_proof(&garbage);
186 assert!(result.is_err());
187 }
188
189 #[test]
190 fn test_deserialize_proof_rejects_untagged() {
191 let proof = PaymentProof {
193 proof_of_payment: make_proof_of_payment(),
194 tx_hashes: vec![],
195 };
196 let raw_bytes = rmp_serde::to_vec(&proof).unwrap();
197 let result = deserialize_proof(&raw_bytes);
198 assert!(result.is_err());
199 }
200
201 #[test]
202 fn test_payment_proof_multiple_tx_hashes() {
203 let tx1 = FixedBytes::from([0x11u8; 32]);
204 let tx2 = FixedBytes::from([0x22u8; 32]);
205 let proof = PaymentProof {
206 proof_of_payment: make_proof_of_payment(),
207 tx_hashes: vec![tx1, tx2],
208 };
209
210 let bytes = serialize_single_node_proof(&proof).unwrap();
211 let (_, hashes) = deserialize_proof(&bytes).unwrap();
212
213 assert_eq!(hashes.len(), 2);
214 assert_eq!(hashes.first().unwrap(), &tx1);
215 assert_eq!(hashes.get(1).unwrap(), &tx2);
216 }
217
218 #[test]
223 fn test_detect_proof_type_single_node() {
224 let bytes = [PROOF_TAG_SINGLE_NODE, 0x00, 0x01];
225 let result = detect_proof_type(&bytes);
226 assert_eq!(result, Some(ProofType::SingleNode));
227 }
228
229 #[test]
230 fn test_detect_proof_type_merkle() {
231 let bytes = [PROOF_TAG_MERKLE, 0x00, 0x01];
232 let result = detect_proof_type(&bytes);
233 assert_eq!(result, Some(ProofType::Merkle));
234 }
235
236 #[test]
237 fn test_detect_proof_type_unknown_tag() {
238 let bytes = [0xFF, 0x00, 0x01];
239 let result = detect_proof_type(&bytes);
240 assert_eq!(result, None);
241 }
242
243 #[test]
244 fn test_detect_proof_type_empty_bytes() {
245 let bytes: &[u8] = &[];
246 let result = detect_proof_type(bytes);
247 assert_eq!(result, None);
248 }
249
250 #[test]
255 fn test_serialize_single_node_proof_roundtrip_with_tag() {
256 let tx_hash = FixedBytes::from([0xCCu8; 32]);
257 let proof = PaymentProof {
258 proof_of_payment: make_proof_of_payment(),
259 tx_hashes: vec![tx_hash],
260 };
261
262 let tagged_bytes = serialize_single_node_proof(&proof).unwrap();
263
264 assert_eq!(
266 tagged_bytes.first().copied(),
267 Some(PROOF_TAG_SINGLE_NODE),
268 "Tagged proof must start with PROOF_TAG_SINGLE_NODE"
269 );
270
271 assert_eq!(
273 detect_proof_type(&tagged_bytes),
274 Some(ProofType::SingleNode)
275 );
276
277 let (pop, hashes) = deserialize_proof(&tagged_bytes).unwrap();
279 assert_eq!(pop.peer_quotes.len(), 1);
280 assert_eq!(hashes.len(), 1);
281 assert_eq!(hashes.first().unwrap(), &tx_hash);
282 }
283
284 fn make_test_merkle_proof() -> MerklePaymentProof {
290 let timestamp = std::time::SystemTime::now()
291 .duration_since(std::time::UNIX_EPOCH)
292 .unwrap()
293 .as_secs();
294
295 let addresses: Vec<xor_name::XorName> = (0..4u8)
297 .map(|i| xor_name::XorName::from_content(&[i]))
298 .collect();
299 let tree = MerkleTree::from_xornames(addresses.clone()).unwrap();
300
301 let candidate_nodes: [MerklePaymentCandidateNode; CANDIDATES_PER_POOL] =
303 std::array::from_fn(|i| {
304 let ml_dsa = MlDsa65::new();
305 let (pub_key, secret_key) = ml_dsa.generate_keypair().expect("keygen");
306 let price = Amount::from(1024u64);
307 #[allow(clippy::cast_possible_truncation)]
308 let reward_address = RewardsAddress::new([i as u8; 20]);
309 let msg =
310 MerklePaymentCandidateNode::bytes_to_sign(&price, &reward_address, timestamp);
311 let sk = MlDsaSecretKey::from_bytes(secret_key.as_bytes()).expect("sk");
312 let signature = ml_dsa.sign(&sk, &msg).expect("sign").as_bytes().to_vec();
313
314 MerklePaymentCandidateNode {
315 pub_key: pub_key.as_bytes().to_vec(),
316 price,
317 reward_address,
318 merkle_payment_timestamp: timestamp,
319 signature,
320 }
321 });
322
323 let reward_candidates = tree.reward_candidates(timestamp).unwrap();
324 let midpoint_proof = reward_candidates.first().unwrap().clone();
325
326 let pool = MerklePaymentCandidatePool {
327 midpoint_proof,
328 candidate_nodes,
329 };
330
331 let first_address = *addresses.first().unwrap();
332 let address_proof = tree.generate_address_proof(0, first_address).unwrap();
333
334 MerklePaymentProof::new(first_address, address_proof, pool)
335 }
336
337 #[test]
338 fn test_serialize_merkle_proof_roundtrip() {
339 let merkle_proof = make_test_merkle_proof();
340
341 let tagged_bytes = serialize_merkle_proof(&merkle_proof).unwrap();
342
343 assert_eq!(
345 tagged_bytes.first().copied(),
346 Some(PROOF_TAG_MERKLE),
347 "Tagged merkle proof must start with PROOF_TAG_MERKLE"
348 );
349
350 assert_eq!(detect_proof_type(&tagged_bytes), Some(ProofType::Merkle));
352
353 let recovered = deserialize_merkle_proof(&tagged_bytes).unwrap();
355 assert_eq!(recovered.address, merkle_proof.address);
356 assert_eq!(
357 recovered.winner_pool.candidate_nodes.len(),
358 CANDIDATES_PER_POOL
359 );
360 }
361
362 #[test]
363 fn test_deserialize_merkle_proof_rejects_wrong_tag() {
364 let merkle_proof = make_test_merkle_proof();
365 let mut tagged_bytes = serialize_merkle_proof(&merkle_proof).unwrap();
366
367 if let Some(first) = tagged_bytes.first_mut() {
369 *first = PROOF_TAG_SINGLE_NODE;
370 }
371
372 let result = deserialize_merkle_proof(&tagged_bytes);
373 assert!(result.is_err(), "Should reject wrong tag byte");
374 let err_msg = result.unwrap_err();
375 assert!(
376 err_msg.contains("Missing merkle proof tag"),
377 "Error should mention missing tag: {err_msg}"
378 );
379 }
380
381 #[test]
382 fn test_deserialize_merkle_proof_rejects_empty() {
383 let result = deserialize_merkle_proof(&[]);
384 assert!(result.is_err());
385 }
386}