1use crate::error::{Error, Result};
11use crate::logging::debug;
12use crate::payment::metrics::QuotingMetricsTracker;
13use crate::payment::pricing::calculate_price;
14use evmlib::merkle_payments::MerklePaymentCandidateNode;
15use evmlib::PaymentQuote;
16use evmlib::RewardsAddress;
17use saorsa_core::MlDsa65;
18use saorsa_pqc::pqc::types::MlDsaSecretKey;
19use saorsa_pqc::pqc::MlDsaOperations;
20use std::time::SystemTime;
21
22pub type XorName = [u8; 32];
24
25pub type SignFn = Box<dyn Fn(&[u8]) -> Vec<u8> + Send + Sync>;
27
28pub struct QuoteGenerator {
33 rewards_address: RewardsAddress,
35 metrics_tracker: QuotingMetricsTracker,
37 sign_fn: Option<SignFn>,
40 pub_key: Vec<u8>,
42}
43
44impl QuoteGenerator {
45 #[must_use]
54 pub fn new(rewards_address: RewardsAddress, metrics_tracker: QuotingMetricsTracker) -> Self {
55 Self {
56 rewards_address,
57 metrics_tracker,
58 sign_fn: None,
59 pub_key: Vec::new(),
60 }
61 }
62
63 pub fn set_signer<F>(&mut self, pub_key: Vec<u8>, sign_fn: F)
70 where
71 F: Fn(&[u8]) -> Vec<u8> + Send + Sync + 'static,
72 {
73 self.pub_key = pub_key;
74 self.sign_fn = Some(Box::new(sign_fn));
75 }
76
77 #[must_use]
79 pub fn can_sign(&self) -> bool {
80 self.sign_fn.is_some()
81 }
82
83 pub fn probe_signer(&self) -> Result<()> {
89 let sign_fn = self
90 .sign_fn
91 .as_ref()
92 .ok_or_else(|| Error::Payment("Signer not set".to_string()))?;
93 let test_msg = b"ant-signing-probe";
94 let test_sig = sign_fn(test_msg);
95 if test_sig.is_empty() {
96 return Err(Error::Payment(
97 "ML-DSA-65 signing probe failed: empty signature produced".to_string(),
98 ));
99 }
100 Ok(())
101 }
102
103 pub fn create_quote(
119 &self,
120 content: XorName,
121 data_size: usize,
122 data_type: u32,
123 ) -> Result<PaymentQuote> {
124 let sign_fn = self
125 .sign_fn
126 .as_ref()
127 .ok_or_else(|| Error::Payment("Quote signing not configured".to_string()))?;
128
129 let timestamp = SystemTime::now();
130
131 let price = calculate_price(self.metrics_tracker.records_stored());
133
134 let xor_name = xor_name::XorName(content);
136
137 let bytes =
139 PaymentQuote::bytes_for_signing(xor_name, timestamp, &price, &self.rewards_address);
140
141 let signature = sign_fn(&bytes);
143 if signature.is_empty() {
144 return Err(Error::Payment(
145 "Signing produced empty signature".to_string(),
146 ));
147 }
148
149 let quote = PaymentQuote {
150 content: xor_name,
151 timestamp,
152 price,
153 pub_key: self.pub_key.clone(),
154 rewards_address: self.rewards_address,
155 signature,
156 };
157
158 if crate::logging::enabled!(crate::logging::Level::DEBUG) {
159 let content_hex = hex::encode(content);
160 debug!("Generated quote for {content_hex} (size: {data_size}, type: {data_type})");
161 }
162
163 Ok(quote)
164 }
165
166 #[must_use]
168 pub fn rewards_address(&self) -> &RewardsAddress {
169 &self.rewards_address
170 }
171
172 #[must_use]
174 pub fn records_stored(&self) -> usize {
175 self.metrics_tracker.records_stored()
176 }
177
178 pub fn record_store(&self) {
180 self.metrics_tracker.record_store();
181 }
182
183 pub fn create_merkle_candidate_quote(
198 &self,
199 data_size: usize,
200 data_type: u32,
201 merkle_payment_timestamp: u64,
202 ) -> Result<MerklePaymentCandidateNode> {
203 let sign_fn = self
204 .sign_fn
205 .as_ref()
206 .ok_or_else(|| Error::Payment("Quote signing not configured".to_string()))?;
207
208 let price = calculate_price(self.metrics_tracker.records_stored());
209
210 let msg = MerklePaymentCandidateNode::bytes_to_sign(
212 &price,
213 &self.rewards_address,
214 merkle_payment_timestamp,
215 );
216
217 let signature = sign_fn(&msg);
219 if signature.is_empty() {
220 return Err(Error::Payment(
221 "ML-DSA-65 signing produced empty signature for merkle candidate".to_string(),
222 ));
223 }
224
225 let candidate = MerklePaymentCandidateNode {
226 pub_key: self.pub_key.clone(),
227 price,
228 reward_address: self.rewards_address,
229 merkle_payment_timestamp,
230 signature,
231 };
232
233 if crate::logging::enabled!(crate::logging::Level::DEBUG) {
234 debug!(
235 "Generated ML-DSA-65 merkle candidate quote (size: {data_size}, type: {data_type}, ts: {merkle_payment_timestamp})"
236 );
237 }
238
239 Ok(candidate)
240 }
241}
242
243pub fn wire_ml_dsa_signer(
263 generator: &mut QuoteGenerator,
264 identity: &saorsa_core::identity::NodeIdentity,
265) -> Result<()> {
266 let pub_key_bytes = identity.public_key().as_bytes().to_vec();
267 let sk_bytes = identity.secret_key_bytes().to_vec();
268 let sk = MlDsaSecretKey::from_bytes(&sk_bytes)
269 .map_err(|e| Error::Crypto(format!("Failed to deserialize ML-DSA-65 secret key: {e}")))?;
270 let ml_dsa = MlDsa65::new();
271 generator.set_signer(pub_key_bytes, move |msg| match ml_dsa.sign(&sk, msg) {
272 Ok(sig) => sig.as_bytes().to_vec(),
273 Err(e) => {
274 crate::logging::error!("ML-DSA-65 signing failed: {e}");
275 vec![]
276 }
277 });
278 generator.probe_signer()?;
279 Ok(())
280}
281
282#[cfg(test)]
283#[allow(clippy::expect_used)]
284mod tests {
285 use super::*;
286 use crate::payment::metrics::QuotingMetricsTracker;
287 use ant_protocol::payment::verify::{
292 verify_merkle_candidate_signature, verify_quote_content, verify_quote_signature,
293 };
294 use evmlib::common::Amount;
295 use saorsa_pqc::pqc::types::MlDsaSecretKey;
296
297 fn create_test_generator() -> QuoteGenerator {
298 let rewards_address = RewardsAddress::new([1u8; 20]);
299 let metrics_tracker = QuotingMetricsTracker::new(100);
300
301 let mut generator = QuoteGenerator::new(rewards_address, metrics_tracker);
302
303 generator.set_signer(vec![0u8; 64], |bytes| {
305 let mut sig = vec![0u8; 64];
307 for (i, b) in bytes.iter().take(64).enumerate() {
308 sig[i] = *b;
309 }
310 sig
311 });
312
313 generator
314 }
315
316 #[test]
317 fn test_create_quote() {
318 let generator = create_test_generator();
319 let content = [42u8; 32];
320
321 let quote = generator.create_quote(content, 1024, 0);
322 assert!(quote.is_ok());
323
324 let quote = quote.expect("valid quote");
325 assert_eq!(quote.content.0, content);
326 }
327
328 #[test]
329 fn test_verify_quote_content() {
330 let generator = create_test_generator();
331 let content = [42u8; 32];
332
333 let quote = generator
334 .create_quote(content, 1024, 0)
335 .expect("valid quote");
336 assert!(verify_quote_content("e, &content));
337
338 let wrong_content = [99u8; 32];
340 assert!(!verify_quote_content("e, &wrong_content));
341 }
342
343 #[test]
344 fn test_generator_without_signer() {
345 let rewards_address = RewardsAddress::new([1u8; 20]);
346 let metrics_tracker = QuotingMetricsTracker::new(100);
347 let generator = QuoteGenerator::new(rewards_address, metrics_tracker);
348
349 assert!(!generator.can_sign());
350
351 let content = [42u8; 32];
352 let result = generator.create_quote(content, 1024, 0);
353 assert!(result.is_err());
354 }
355
356 #[test]
357 fn test_quote_signature_round_trip_real_keys() {
358 let ml_dsa = MlDsa65::new();
359 let (public_key, secret_key) = ml_dsa.generate_keypair().expect("keypair generation");
360
361 let rewards_address = RewardsAddress::new([2u8; 20]);
362 let metrics_tracker = QuotingMetricsTracker::new(100);
363 let mut generator = QuoteGenerator::new(rewards_address, metrics_tracker);
364
365 let pub_key_bytes = public_key.as_bytes().to_vec();
366 let sk_bytes = secret_key.as_bytes().to_vec();
367 generator.set_signer(pub_key_bytes, move |msg| {
368 let sk = MlDsaSecretKey::from_bytes(&sk_bytes).expect("secret key parse");
369 let ml_dsa = MlDsa65::new();
370 ml_dsa.sign(&sk, msg).expect("signing").as_bytes().to_vec()
371 });
372
373 let content = [7u8; 32];
374 let quote = generator
375 .create_quote(content, 2048, 0)
376 .expect("create quote");
377
378 assert!(verify_quote_signature("e));
380
381 let mut tampered_quote = quote;
383 if let Some(byte) = tampered_quote.signature.first_mut() {
384 *byte ^= 0xFF;
385 }
386 assert!(!verify_quote_signature(&tampered_quote));
387 }
388
389 #[test]
390 fn test_empty_signature_fails_verification() {
391 let generator = create_test_generator();
392 let content = [42u8; 32];
393
394 let quote = generator
395 .create_quote(content, 1024, 0)
396 .expect("create quote");
397
398 assert!(!verify_quote_signature("e));
401 }
402
403 #[test]
404 fn test_rewards_address_getter() {
405 let addr = RewardsAddress::new([42u8; 20]);
406 let metrics_tracker = QuotingMetricsTracker::new(0);
407 let generator = QuoteGenerator::new(addr, metrics_tracker);
408
409 assert_eq!(*generator.rewards_address(), addr);
410 }
411
412 #[test]
413 fn test_records_stored() {
414 let rewards_address = RewardsAddress::new([1u8; 20]);
415 let metrics_tracker = QuotingMetricsTracker::new(50);
416 let generator = QuoteGenerator::new(rewards_address, metrics_tracker);
417
418 assert_eq!(generator.records_stored(), 50);
419 }
420
421 #[test]
422 fn test_record_store_delegation() {
423 let rewards_address = RewardsAddress::new([1u8; 20]);
424 let metrics_tracker = QuotingMetricsTracker::new(0);
425 let generator = QuoteGenerator::new(rewards_address, metrics_tracker);
426
427 generator.record_store();
428 generator.record_store();
429 generator.record_store();
430
431 assert_eq!(generator.records_stored(), 3);
432 }
433
434 #[test]
435 fn test_create_quote_different_data_types() {
436 let generator = create_test_generator();
437 let content = [10u8; 32];
438
439 let q0 = generator.create_quote(content, 1024, 0).expect("type 0");
441 let q1 = generator.create_quote(content, 512, 1).expect("type 1");
442 let q2 = generator.create_quote(content, 256, 2).expect("type 2");
443
444 assert!(q0.price >= Amount::from(1u64));
446 assert!(q1.price >= Amount::from(1u64));
447 assert!(q2.price >= Amount::from(1u64));
448 }
449
450 #[test]
451 fn test_create_quote_zero_size() {
452 let generator = create_test_generator();
453 let content = [11u8; 32];
454
455 let quote = generator.create_quote(content, 0, 0).expect("zero size");
457 assert!(quote.price >= Amount::from(1u64));
458 }
459
460 #[test]
461 fn test_create_quote_large_size() {
462 let generator = create_test_generator();
463 let content = [12u8; 32];
464
465 let quote = generator
467 .create_quote(content, 10_000_000, 0)
468 .expect("large size");
469 assert!(quote.price >= Amount::from(1u64));
470 }
471
472 #[test]
473 fn test_verify_quote_signature_empty_pub_key() {
474 let quote = PaymentQuote {
475 content: xor_name::XorName([0u8; 32]),
476 timestamp: SystemTime::now(),
477 price: Amount::from(1u64),
478 rewards_address: RewardsAddress::new([0u8; 20]),
479 pub_key: vec![],
480 signature: vec![],
481 };
482
483 assert!(!verify_quote_signature("e));
485 }
486
487 #[test]
488 fn test_can_sign_after_set_signer() {
489 let rewards_address = RewardsAddress::new([1u8; 20]);
490 let metrics_tracker = QuotingMetricsTracker::new(0);
491 let mut generator = QuoteGenerator::new(rewards_address, metrics_tracker);
492
493 assert!(!generator.can_sign());
494
495 generator.set_signer(vec![0u8; 32], |_| vec![0u8; 32]);
496
497 assert!(generator.can_sign());
498 }
499
500 #[test]
501 fn test_wire_ml_dsa_signer_returns_ok_with_valid_identity() {
502 let identity = saorsa_core::identity::NodeIdentity::generate().expect("keypair generation");
503 let rewards_address = RewardsAddress::new([3u8; 20]);
504 let metrics_tracker = QuotingMetricsTracker::new(0);
505 let mut generator = QuoteGenerator::new(rewards_address, metrics_tracker);
506
507 let result = wire_ml_dsa_signer(&mut generator, &identity);
508 assert!(
509 result.is_ok(),
510 "wire_ml_dsa_signer should succeed: {result:?}"
511 );
512 assert!(generator.can_sign());
513 }
514
515 #[test]
516 fn test_probe_signer_fails_without_signer() {
517 let rewards_address = RewardsAddress::new([1u8; 20]);
518 let metrics_tracker = QuotingMetricsTracker::new(0);
519 let generator = QuoteGenerator::new(rewards_address, metrics_tracker);
520
521 let result = generator.probe_signer();
522 assert!(result.is_err());
523 }
524
525 #[test]
526 fn test_probe_signer_fails_with_empty_signature() {
527 let rewards_address = RewardsAddress::new([1u8; 20]);
528 let metrics_tracker = QuotingMetricsTracker::new(0);
529 let mut generator = QuoteGenerator::new(rewards_address, metrics_tracker);
530
531 generator.set_signer(vec![0u8; 32], |_| vec![]);
532
533 let result = generator.probe_signer();
534 assert!(result.is_err());
535 }
536
537 #[test]
538 fn test_create_merkle_candidate_quote_with_ml_dsa() {
539 let ml_dsa = MlDsa65::new();
540 let (public_key, secret_key) = ml_dsa.generate_keypair().expect("keypair generation");
541
542 let rewards_address = RewardsAddress::new([0x42u8; 20]);
543 let metrics_tracker = QuotingMetricsTracker::new(50);
544 let mut generator = QuoteGenerator::new(rewards_address, metrics_tracker);
545
546 let pub_key_bytes = public_key.as_bytes().to_vec();
548 let sk_bytes = secret_key.as_bytes().to_vec();
549 generator.set_signer(pub_key_bytes.clone(), move |msg| {
550 let sk = MlDsaSecretKey::from_bytes(&sk_bytes).expect("sk parse");
551 let ml_dsa = MlDsa65::new();
552 ml_dsa.sign(&sk, msg).expect("sign").as_bytes().to_vec()
553 });
554
555 let timestamp = std::time::SystemTime::now()
556 .duration_since(std::time::UNIX_EPOCH)
557 .expect("system time")
558 .as_secs();
559
560 let result = generator.create_merkle_candidate_quote(2048, 0, timestamp);
561
562 assert!(
563 result.is_ok(),
564 "create_merkle_candidate_quote should succeed: {result:?}"
565 );
566
567 let candidate = result.expect("valid candidate");
568
569 assert_eq!(candidate.reward_address, rewards_address);
571
572 assert_eq!(candidate.merkle_payment_timestamp, timestamp);
574
575 assert_eq!(candidate.price, calculate_price(50));
577
578 assert_eq!(
580 candidate.pub_key, pub_key_bytes,
581 "Public key should be raw ML-DSA-65 bytes"
582 );
583
584 assert!(
586 verify_merkle_candidate_signature(&candidate),
587 "ML-DSA-65 merkle candidate signature must be valid"
588 );
589
590 let mut tampered = candidate;
592 tampered.merkle_payment_timestamp = timestamp + 1;
593 assert!(
594 !verify_merkle_candidate_signature(&tampered),
595 "Tampered timestamp should invalidate the ML-DSA-65 signature"
596 );
597 }
598
599 fn make_valid_merkle_candidate() -> MerklePaymentCandidateNode {
605 let ml_dsa = MlDsa65::new();
606 let (public_key, secret_key) = ml_dsa.generate_keypair().expect("keygen");
607
608 let rewards_address = RewardsAddress::new([0xABu8; 20]);
609 let timestamp = std::time::SystemTime::now()
610 .duration_since(std::time::UNIX_EPOCH)
611 .expect("system time")
612 .as_secs();
613 let price = Amount::from(42u64);
614
615 let msg = MerklePaymentCandidateNode::bytes_to_sign(&price, &rewards_address, timestamp);
616 let sk = MlDsaSecretKey::from_bytes(secret_key.as_bytes()).expect("sk");
617 let signature = ml_dsa.sign(&sk, &msg).expect("sign").as_bytes().to_vec();
618
619 MerklePaymentCandidateNode {
620 pub_key: public_key.as_bytes().to_vec(),
621 price,
622 reward_address: rewards_address,
623 merkle_payment_timestamp: timestamp,
624 signature,
625 }
626 }
627
628 #[test]
629 fn test_verify_merkle_candidate_valid_signature() {
630 let candidate = make_valid_merkle_candidate();
631 assert!(
632 verify_merkle_candidate_signature(&candidate),
633 "Freshly signed merkle candidate must verify"
634 );
635 }
636
637 #[test]
638 fn test_verify_merkle_candidate_tampered_pub_key() {
639 let mut candidate = make_valid_merkle_candidate();
640 if let Some(byte) = candidate.pub_key.first_mut() {
642 *byte ^= 0xFF;
643 }
644 assert!(
645 !verify_merkle_candidate_signature(&candidate),
646 "Tampered pub_key must invalidate the signature"
647 );
648 }
649
650 #[test]
651 fn test_verify_merkle_candidate_tampered_reward_address() {
652 let mut candidate = make_valid_merkle_candidate();
653 candidate.reward_address = RewardsAddress::new([0xFFu8; 20]);
654 assert!(
655 !verify_merkle_candidate_signature(&candidate),
656 "Tampered reward_address must invalidate the signature"
657 );
658 }
659
660 #[test]
661 fn test_verify_merkle_candidate_tampered_price() {
662 let mut candidate = make_valid_merkle_candidate();
663 candidate.price = Amount::from(999_999u64);
664 assert!(
665 !verify_merkle_candidate_signature(&candidate),
666 "Tampered price must invalidate the signature"
667 );
668 }
669
670 #[test]
671 fn test_verify_merkle_candidate_tampered_signature_byte() {
672 let mut candidate = make_valid_merkle_candidate();
673 if let Some(byte) = candidate.signature.first_mut() {
674 *byte ^= 0xFF;
675 }
676 assert!(
677 !verify_merkle_candidate_signature(&candidate),
678 "Tampered signature byte must fail verification"
679 );
680 }
681
682 #[test]
683 fn test_verify_merkle_candidate_empty_pub_key() {
684 let mut candidate = make_valid_merkle_candidate();
685 candidate.pub_key = vec![];
686 assert!(
687 !verify_merkle_candidate_signature(&candidate),
688 "Empty pub_key must fail verification"
689 );
690 }
691
692 #[test]
693 fn test_verify_merkle_candidate_empty_signature() {
694 let mut candidate = make_valid_merkle_candidate();
695 candidate.signature = vec![];
696 assert!(
697 !verify_merkle_candidate_signature(&candidate),
698 "Empty signature must fail verification"
699 );
700 }
701
702 #[test]
703 fn test_verify_merkle_candidate_wrong_length_signature() {
704 let mut candidate = make_valid_merkle_candidate();
705 candidate.signature = vec![0xAA; 100];
707 assert!(
708 !verify_merkle_candidate_signature(&candidate),
709 "Wrong-length signature must fail verification"
710 );
711 }
712
713 #[test]
714 fn test_verify_merkle_candidate_wrong_length_pub_key() {
715 let mut candidate = make_valid_merkle_candidate();
716 candidate.pub_key = vec![0xBB; 100];
718 assert!(
719 !verify_merkle_candidate_signature(&candidate),
720 "Wrong-length pub_key must fail verification"
721 );
722 }
723
724 #[test]
725 fn test_verify_merkle_candidate_cross_key_rejection() {
726 let candidate = make_valid_merkle_candidate();
728 let ml_dsa = MlDsa65::new();
729 let (other_pk, _) = ml_dsa.generate_keypair().expect("keygen");
730
731 let mut swapped = candidate;
732 swapped.pub_key = other_pk.as_bytes().to_vec();
733 assert!(
734 !verify_merkle_candidate_signature(&swapped),
735 "Signature from key A must not verify under key B"
736 );
737 }
738}