Skip to main content

blvm_protocol/
payment.rs

1//! BIP70: Payment Protocol (P2P Variant)
2//!
3//! Specification: https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki
4//!
5//! This is a P2P-based variant of BIP70 that addresses security concerns:
6//! - Uses Bitcoin P2P network instead of HTTP/HTTPS (privacy-preserving)
7//! - Uses Bitcoin public key signatures instead of X.509 certificates (decentralized)
8//! - Supports signed refund addresses (prevents refund attacks)
9//! - Works with TCP, Iroh, and QUIC transports
10//!
11//! Core messages:
12//! - PaymentRequest: Merchant payment details signed with Bitcoin key
13//! - Payment: Customer payment transaction(s)
14//! - PaymentACK: Merchant confirmation of payment
15//!
16//! Security enhancements:
17//! - Merchant authentication via Bitcoin public keys (on-chain verifiable)
18//! - Signed refund addresses prevent refund attacks
19//! - P2P routing preserves customer privacy (no direct merchant connection)
20
21use crate::Hash;
22use hex;
23use secp256k1::ecdsa::Signature;
24use secp256k1::{Message, Secp256k1};
25use serde::{Deserialize, Serialize};
26use sha2::{Digest, Sha256};
27
28/// BIP70 Payment Protocol version
29pub const PAYMENT_PROTOCOL_VERSION: u32 = 1;
30
31/// Payment Details - Core payment information
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct PaymentDetails {
34    /// Network identifier (mainnet, testnet, regtest)
35    pub network: String,
36    /// Payment outputs (address, amount)
37    pub outputs: Vec<PaymentOutput>,
38    /// Payment expiration time (Unix timestamp)
39    pub time: u64,
40    /// Payment expiration time
41    pub expires: Option<u64>,
42    /// Memo for merchant
43    pub memo: Option<String>,
44    /// Memo for customer
45    pub payment_url: Option<String>,
46    /// Merchant data (opaque to customer)
47    pub merchant_data: Option<Vec<u8>>,
48}
49
50/// Payment Output - Address and amount
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub struct PaymentOutput {
53    /// Bitcoin address or script
54    pub script: Vec<u8>,
55    /// Amount in satoshis (None = all available)
56    pub amount: Option<u64>,
57}
58
59/// Signed refund address - Pre-authorized refund address with merchant signature
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct SignedRefundAddress {
62    /// Refund address/script
63    pub address: PaymentOutput,
64    /// Merchant signature over address (prevents refund attacks)
65    pub signature: Vec<u8>,
66}
67
68/// Payment Request - Main payment protocol message
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct PaymentRequest {
71    /// Payment details
72    pub payment_details: PaymentDetails,
73    /// Merchant's Bitcoin public key (compressed, 33 bytes)
74    /// Replaces X.509 certificates with on-chain verifiable keys
75    pub merchant_pubkey: Option<Vec<u8>>,
76    /// Signature over payment_details by merchant's Bitcoin key
77    pub signature: Option<Vec<u8>>,
78    /// Pre-authorized refund addresses (signed by merchant)
79    /// Prevents refund address attacks by requiring merchant signature
80    pub authorized_refund_addresses: Option<Vec<SignedRefundAddress>>,
81}
82
83/// Payment - Customer payment transaction(s)
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct Payment {
86    /// Serialized transaction(s)
87    pub transactions: Vec<Vec<u8>>,
88    /// Refund addresses (if change needed)
89    pub refund_to: Option<Vec<PaymentOutput>>,
90    /// Merchant data (echo back from PaymentRequest)
91    pub merchant_data: Option<Vec<u8>>,
92    /// Memo from customer
93    pub memo: Option<String>,
94}
95
96/// Payment ACK - Merchant confirmation
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct PaymentACK {
99    /// Original payment message
100    pub payment: Payment,
101    /// Confirmation memo from merchant
102    pub memo: Option<String>,
103    /// Merchant signature over PaymentACK (optional)
104    pub signature: Option<Vec<u8>>,
105}
106
107impl PaymentACK {
108    /// Sign PaymentACK with merchant's private key
109    pub fn sign(&mut self, private_key: &secp256k1::SecretKey) -> Result<(), Bip70Error> {
110        // Serialize PaymentACK for signing (excluding signature field)
111        let mut ack_for_signing = self.clone();
112        ack_for_signing.signature = None;
113
114        let serialized = bincode::serialize(&ack_for_signing)
115            .map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
116
117        // Hash PaymentACK
118        let mut hasher = Sha256::new();
119        hasher.update(&serialized);
120        let hash = hasher.finalize();
121
122        // Create message for signing
123        let message = Message::from_digest(hash.into());
124
125        // Sign with secp256k1
126        let secp = Secp256k1::new();
127        let signature = secp.sign_ecdsa(&message, private_key);
128
129        // Store signature
130        self.signature = Some(signature.serialize_compact().to_vec());
131
132        Ok(())
133    }
134
135    /// Verify PaymentACK signature
136    pub fn verify_signature(&self, merchant_pubkey: &[u8]) -> Result<(), Bip70Error> {
137        let signature_bytes = self
138            .signature
139            .as_ref()
140            .ok_or_else(|| Bip70Error::SignatureError("No signature".to_string()))?;
141
142        // Parse public key
143        let pubkey = secp256k1::PublicKey::from_slice(merchant_pubkey)
144            .map_err(|e| Bip70Error::SignatureError(format!("Invalid public key: {e}")))?;
145
146        // Parse signature
147        let signature = Signature::from_compact(signature_bytes)
148            .map_err(|e| Bip70Error::SignatureError(format!("Invalid signature: {e}")))?;
149
150        // Serialize PaymentACK for verification (excluding signature)
151        let mut ack_for_verification = self.clone();
152        ack_for_verification.signature = None;
153
154        let serialized = bincode::serialize(&ack_for_verification)
155            .map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
156
157        // Hash PaymentACK
158        let mut hasher = Sha256::new();
159        hasher.update(&serialized);
160        let hash = hasher.finalize();
161
162        // Create message for verification
163        let message = Message::from_digest(hash.into());
164
165        // Verify signature
166        let secp = Secp256k1::new();
167        secp.verify_ecdsa(&message, &signature, &pubkey)
168            .map_err(|_| {
169                Bip70Error::SignatureError("PaymentACK signature verification failed".to_string())
170            })?;
171
172        Ok(())
173    }
174}
175
176impl PaymentRequest {
177    /// Create a new payment request
178    pub fn new(network: String, outputs: Vec<PaymentOutput>, time: u64) -> Self {
179        Self {
180            payment_details: PaymentDetails {
181                network,
182                outputs,
183                time,
184                expires: None,
185                memo: None,
186                payment_url: None,
187                merchant_data: None,
188            },
189            merchant_pubkey: None,
190            signature: None,
191            authorized_refund_addresses: None,
192        }
193    }
194
195    /// Set merchant public key
196    pub fn with_merchant_key(mut self, pubkey: [u8; 33]) -> Self {
197        self.merchant_pubkey = Some(pubkey.to_vec());
198        self
199    }
200
201    /// Add authorized refund address (signed by merchant)
202    pub fn with_authorized_refund(mut self, signed_refund: SignedRefundAddress) -> Self {
203        if self.authorized_refund_addresses.is_none() {
204            self.authorized_refund_addresses = Some(Vec::new());
205        }
206        self.authorized_refund_addresses
207            .as_mut()
208            .unwrap()
209            .push(signed_refund);
210        self
211    }
212
213    /// Set expiration time
214    pub fn with_expires(mut self, expires: u64) -> Self {
215        self.payment_details.expires = Some(expires);
216        self
217    }
218
219    /// Set memo for merchant
220    pub fn with_memo(mut self, memo: String) -> Self {
221        self.payment_details.memo = Some(memo);
222        self
223    }
224
225    /// Set payment URL (where to send Payment message)
226    pub fn with_payment_url(mut self, url: String) -> Self {
227        self.payment_details.payment_url = Some(url);
228        self
229    }
230
231    /// Set merchant data (opaque customer data)
232    pub fn with_merchant_data(mut self, data: Vec<u8>) -> Self {
233        self.payment_details.merchant_data = Some(data);
234        self
235    }
236
237    /// Sign payment request with merchant's private key
238    pub fn sign(&mut self, private_key: &secp256k1::SecretKey) -> Result<(), Bip70Error> {
239        // Serialize payment_details for signing
240        let serialized = bincode::serialize(&self.payment_details)
241            .map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
242
243        // Hash payment_details
244        let mut hasher = Sha256::new();
245        hasher.update(&serialized);
246        let hash = hasher.finalize();
247
248        // Create message for signing
249        let message = Message::from_digest(hash.into());
250
251        // Sign with secp256k1
252        let secp = Secp256k1::new();
253        let signature = secp.sign_ecdsa(&message, private_key);
254        let pubkey = secp256k1::PublicKey::from_secret_key(&secp, private_key);
255        let pubkey_serialized = pubkey.serialize();
256
257        // Store signature and public key
258        self.signature = Some(signature.serialize_compact().to_vec());
259        self.merchant_pubkey = Some(pubkey_serialized.to_vec());
260
261        Ok(())
262    }
263
264    /// Verify payment request signature
265    pub fn verify_signature(&self) -> Result<(), Bip70Error> {
266        let pubkey = self
267            .merchant_pubkey
268            .as_ref()
269            .ok_or_else(|| Bip70Error::SignatureError("No merchant public key".to_string()))?;
270        let signature_bytes = self
271            .signature
272            .as_ref()
273            .ok_or_else(|| Bip70Error::SignatureError("No signature".to_string()))?;
274
275        // Parse public key
276        let pubkey = secp256k1::PublicKey::from_slice(pubkey)
277            .map_err(|e| Bip70Error::SignatureError(format!("Invalid public key: {e}")))?;
278
279        // Parse signature
280        let signature = Signature::from_compact(signature_bytes)
281            .map_err(|e| Bip70Error::SignatureError(format!("Invalid signature: {e}")))?;
282
283        // Serialize payment_details for verification
284        let serialized = bincode::serialize(&self.payment_details)
285            .map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
286
287        // Hash payment_details
288        let mut hasher = Sha256::new();
289        hasher.update(&serialized);
290        let hash = hasher.finalize();
291
292        // Create message for verification
293        let message = Message::from_digest(hash.into());
294
295        // Verify signature
296        let secp = Secp256k1::new();
297        secp.verify_ecdsa(&message, &signature, &pubkey)
298            .map_err(|_| Bip70Error::SignatureError("Signature verification failed".to_string()))?;
299
300        Ok(())
301    }
302
303    /// Validate payment request
304    pub fn validate(&self) -> Result<(), Bip70Error> {
305        // Check expiration
306        if let Some(expires) = self.payment_details.expires {
307            let now = crate::time::current_timestamp();
308            if now > expires {
309                return Err(Bip70Error::Expired);
310            }
311        }
312
313        // Validate outputs
314        if self.payment_details.outputs.is_empty() {
315            return Err(Bip70Error::InvalidRequest("No payment outputs".to_string()));
316        }
317
318        // Validate network
319        let valid_networks = ["main", "test", "regtest"];
320        if !valid_networks.contains(&self.payment_details.network.as_str()) {
321            return Err(Bip70Error::InvalidRequest(format!(
322                "Invalid network: {}",
323                self.payment_details.network
324            )));
325        }
326
327        Ok(())
328    }
329}
330
331impl Payment {
332    /// Create a new payment
333    pub fn new(transactions: Vec<Vec<u8>>) -> Self {
334        Self {
335            transactions,
336            refund_to: None,
337            merchant_data: None,
338            memo: None,
339        }
340    }
341
342    /// Add refund address (must be pre-authorized in PaymentRequest)
343    pub fn with_refund_to(mut self, outputs: Vec<PaymentOutput>) -> Self {
344        self.refund_to = Some(outputs);
345        self
346    }
347
348    /// Validate refund addresses against PaymentRequest authorized list
349    pub fn validate_refund_addresses(
350        &self,
351        authorized_refunds: &[SignedRefundAddress],
352    ) -> Result<(), Bip70Error> {
353        if let Some(ref refund_to) = self.refund_to {
354            for refund_addr in refund_to {
355                // Check if refund address is in authorized list
356                let is_authorized = authorized_refunds.iter().any(|auth| {
357                    auth.address.script == refund_addr.script
358                        && auth.address.amount == refund_addr.amount
359                });
360
361                if !is_authorized {
362                    return Err(Bip70Error::InvalidPayment(format!(
363                        "Refund address not authorized: {:?}",
364                        refund_addr.script
365                    )));
366                }
367            }
368        }
369        Ok(())
370    }
371
372    /// Set merchant data (echo from PaymentRequest)
373    pub fn with_merchant_data(mut self, data: Vec<u8>) -> Self {
374        self.merchant_data = Some(data);
375        self
376    }
377
378    /// Set customer memo
379    pub fn with_memo(mut self, memo: String) -> Self {
380        self.memo = Some(memo);
381        self
382    }
383
384    /// Validate payment
385    pub fn validate(&self) -> Result<(), Bip70Error> {
386        if self.transactions.is_empty() {
387            return Err(Bip70Error::InvalidPayment("No transactions".to_string()));
388        }
389
390        Ok(())
391    }
392}
393
394/// BIP70 Error types
395#[derive(Debug, thiserror::Error)]
396pub enum Bip70Error {
397    #[error("Payment request expired")]
398    Expired,
399
400    #[error("Invalid payment request: {0}")]
401    InvalidRequest(String),
402
403    #[error("Invalid payment: {0}")]
404    InvalidPayment(String),
405
406    #[error("Certificate validation failed: {0}")]
407    CertificateError(String),
408
409    #[error("Signature verification failed: {0}")]
410    SignatureError(String),
411
412    #[error("HTTP error: {0}")]
413    HttpError(String),
414
415    #[error("Serialization error: {0}")]
416    SerializationError(String),
417
418    #[error("Validation error: {0}")]
419    ValidationError(String),
420}
421
422/// BIP70 Payment Protocol client (for making payments via P2P)
423///
424/// Note: Node-specific message creation functions are in blvm-node.
425/// This struct provides protocol-level validation only.
426pub struct PaymentProtocolClient;
427
428impl PaymentProtocolClient {
429    /// Validate a PaymentRequest (protocol-level validation)
430    pub fn validate_payment_request(
431        payment_request: &PaymentRequest,
432        expected_merchant_pubkey: Option<&[u8]>,
433    ) -> Result<(), Bip70Error> {
434        // Verify signature
435        payment_request.verify_signature()?;
436
437        // Validate payment request
438        payment_request.validate()?;
439
440        // Verify merchant pubkey matches if provided
441        if let Some(expected_pubkey) = expected_merchant_pubkey {
442            if let Some(ref req_pubkey) = payment_request.merchant_pubkey {
443                if req_pubkey.as_slice() != expected_pubkey {
444                    return Err(Bip70Error::SignatureError(
445                        "PaymentRequest pubkey mismatch".to_string(),
446                    ));
447                }
448            }
449        }
450
451        Ok(())
452    }
453
454    /// Validate PaymentACK from merchant (protocol-level validation)
455    ///
456    /// This is a legacy method for backward compatibility.
457    /// Prefer using PaymentACK::verify_signature() directly.
458    pub fn validate_payment_ack(
459        payment_ack: &PaymentACK,
460        merchant_signature: &[u8],
461        merchant_pubkey: &[u8],
462    ) -> Result<(), Bip70Error> {
463        // If PaymentACK has embedded signature, use that
464        if let Some(ref sig) = payment_ack.signature {
465            if !sig.is_empty() {
466                return payment_ack.verify_signature(merchant_pubkey);
467            }
468        }
469
470        // Legacy: Verify external signature (for backward compatibility)
471        if !merchant_signature.is_empty() {
472            let pubkey = secp256k1::PublicKey::from_slice(merchant_pubkey)
473                .map_err(|e| Bip70Error::SignatureError(format!("Invalid pubkey: {e}")))?;
474
475            // Serialize payment_ack for verification (excluding signature field)
476            let mut ack_for_verification = payment_ack.clone();
477            ack_for_verification.signature = None;
478
479            let serialized = bincode::serialize(&ack_for_verification)
480                .map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
481
482            let mut hasher = Sha256::new();
483            hasher.update(&serialized);
484            let hash = hasher.finalize();
485
486            let message = Message::from_digest(hash.into());
487
488            let signature = Signature::from_compact(merchant_signature)
489                .map_err(|e| Bip70Error::SignatureError(format!("Invalid signature: {e}")))?;
490
491            let secp = Secp256k1::new();
492            secp.verify_ecdsa(&message, &signature, &pubkey)
493                .map_err(|_| {
494                    Bip70Error::SignatureError(
495                        "PaymentACK signature verification failed".to_string(),
496                    )
497                })?;
498        }
499
500        Ok(())
501    }
502}
503
504/// BIP70 Payment Protocol server (for receiving payments via P2P)
505///
506/// Note: Node-specific message creation functions are in blvm-node.
507/// This struct provides protocol-level processing only.
508pub struct PaymentProtocolServer;
509
510impl PaymentProtocolServer {
511    /// Create signed payment request (protocol-level)
512    pub fn create_signed_payment_request(
513        details: PaymentDetails,
514        merchant_private_key: &secp256k1::SecretKey,
515        authorized_refunds: Option<Vec<SignedRefundAddress>>,
516    ) -> Result<PaymentRequest, Bip70Error> {
517        let mut payment_request = PaymentRequest {
518            payment_details: details,
519            merchant_pubkey: None,
520            signature: None,
521            authorized_refund_addresses: authorized_refunds,
522        };
523
524        // Sign payment request
525        payment_request.sign(merchant_private_key)?;
526
527        Ok(payment_request)
528    }
529
530    /// Process incoming payment (protocol-level validation and ACK creation)
531    pub fn process_payment(
532        payment: &Payment,
533        original_request: &PaymentRequest,
534        merchant_private_key: Option<&secp256k1::SecretKey>,
535    ) -> Result<PaymentACK, Bip70Error> {
536        // Validate payment
537        payment.validate()?;
538
539        // Validate refund addresses if provided
540        if let Some(ref authorized_refunds) = original_request.authorized_refund_addresses {
541            payment.validate_refund_addresses(authorized_refunds)?;
542        }
543
544        // Validate merchant_data matches original request
545        if let Some(ref payment_merchant_data) = payment.merchant_data {
546            if let Some(ref request_merchant_data) = original_request.payment_details.merchant_data
547            {
548                if payment_merchant_data != request_merchant_data {
549                    return Err(Bip70Error::ValidationError(
550                        "Merchant data mismatch".to_string(),
551                    ));
552                }
553            }
554        } else if original_request.payment_details.merchant_data.is_some() {
555            return Err(Bip70Error::ValidationError(
556                "Payment missing merchant data".to_string(),
557            ));
558        }
559
560        // Verify transactions match PaymentRequest outputs
561        Self::verify_payment_transactions(payment, original_request)?;
562
563        // Create payment ACK
564        let mut payment_ack = PaymentACK {
565            payment: payment.clone(),
566            memo: Some("Payment received".to_string()),
567            signature: None, // Will be set by sign() method
568        };
569
570        // Sign PaymentACK if merchant key provided
571        if let Some(private_key) = merchant_private_key {
572            payment_ack.sign(private_key)?;
573        }
574
575        Ok(payment_ack)
576    }
577
578    /// Verify that payment transactions match PaymentRequest outputs
579    fn verify_payment_transactions(
580        payment: &Payment,
581        original_request: &PaymentRequest,
582    ) -> Result<(), Bip70Error> {
583        use crate::Transaction;
584
585        // Deserialize all transactions
586        let mut all_outputs = Vec::new();
587        for tx_bytes in &payment.transactions {
588            let tx: Transaction = bincode::deserialize(tx_bytes)
589                .map_err(|e| Bip70Error::SerializationError(format!("Invalid transaction: {e}")))?;
590
591            // Collect all outputs from this transaction
592            for output in &tx.outputs {
593                all_outputs.push(PaymentOutput {
594                    script: output.script_pubkey.clone(),
595                    amount: Some(output.value as u64), // Convert i64 to u64
596                });
597            }
598        }
599
600        // Check that all requested outputs are present
601        // Note: Payment may include additional outputs (change, refunds), but must include all requested
602        for requested_output in &original_request.payment_details.outputs {
603            let found = all_outputs.iter().any(|output| {
604                output.script == requested_output.script
605                    && match (output.amount, requested_output.amount) {
606                        (Some(amt), Some(req_amt)) => amt >= req_amt, // Allow overpayment
607                        (Some(_), None) => true,                      // Requested "all available"
608                        (None, Some(_)) => false, // Output has no amount but request requires one
609                        (None, None) => true,     // Both "all available"
610                    }
611            });
612
613            if !found {
614                return Err(Bip70Error::ValidationError(format!(
615                    "Payment missing required output: script={}, amount={:?}",
616                    hex::encode(&requested_output.script),
617                    requested_output.amount
618                )));
619            }
620        }
621
622        Ok(())
623    }
624
625    /// Sign a refund address for inclusion in PaymentRequest
626    pub fn sign_refund_address(
627        address: PaymentOutput,
628        merchant_private_key: &secp256k1::SecretKey,
629    ) -> Result<SignedRefundAddress, Bip70Error> {
630        // Serialize address for signing
631        let serialized = bincode::serialize(&address)
632            .map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
633
634        // Hash address
635        let mut hasher = Sha256::new();
636        hasher.update(&serialized);
637        let hash = hasher.finalize();
638
639        // Sign
640        let message = Message::from_digest(hash.into());
641
642        let secp = Secp256k1::new();
643        let signature = secp.sign_ecdsa(&message, merchant_private_key);
644
645        Ok(SignedRefundAddress {
646            address,
647            signature: signature.serialize_compact().to_vec(),
648        })
649    }
650
651    /// Verify signed refund address
652    pub fn verify_refund_address(
653        signed_refund: &SignedRefundAddress,
654        merchant_pubkey: &[u8],
655    ) -> Result<(), Bip70Error> {
656        let pubkey = secp256k1::PublicKey::from_slice(merchant_pubkey)
657            .map_err(|e| Bip70Error::SignatureError(format!("Invalid pubkey: {e}")))?;
658
659        let serialized = bincode::serialize(&signed_refund.address)
660            .map_err(|e| Bip70Error::SerializationError(e.to_string()))?;
661
662        let mut hasher = Sha256::new();
663        hasher.update(&serialized);
664        let hash = hasher.finalize();
665
666        let message = Message::from_digest(hash.into());
667
668        let signature = Signature::from_compact(&signed_refund.signature)
669            .map_err(|e| Bip70Error::SignatureError(format!("Invalid signature: {e}")))?;
670
671        let secp = Secp256k1::new();
672        secp.verify_ecdsa(&message, &signature, &pubkey)
673            .map_err(|_| {
674                Bip70Error::SignatureError(
675                    "Refund address signature verification failed".to_string(),
676                )
677            })?;
678
679        Ok(())
680    }
681}
682
683// --- CTV covenant types (shared by P2P payment messages and node covenant engine; serde-stable) ---
684
685/// CTV covenant proof for payment commitment
686#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
687pub struct CovenantProof {
688    /// CTV template hash (32 bytes)
689    pub template_hash: Hash,
690    /// Transaction template structure (without signatures)
691    pub transaction_template: TransactionTemplate,
692    /// Payment request ID this proof commits to
693    pub payment_request_id: String,
694    /// Timestamp when proof was created
695    pub created_at: u64,
696    /// Optional cryptographic signature of the proof
697    pub signature: Option<Vec<u8>>,
698}
699
700/// Transaction template for CTV (without scriptSig)
701#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
702pub struct TransactionTemplate {
703    pub version: u32,
704    pub inputs: Vec<TemplateInput>,
705    pub outputs: Vec<TemplateOutput>,
706    pub lock_time: u32,
707}
708
709/// Template input (CTV format: no scriptSig)
710#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
711pub struct TemplateInput {
712    pub prevout_hash: Hash,
713    pub prevout_index: u32,
714    pub sequence: u32,
715}
716
717/// Template output
718#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
719pub struct TemplateOutput {
720    pub value: u64,
721    pub script_pubkey: Vec<u8>,
722}
723
724/// Settlement status for payment
725#[derive(Debug, Clone, Serialize, Deserialize)]
726pub enum SettlementStatus {
727    /// Payment proof created, not yet broadcast
728    ProofCreated,
729    /// Payment proof broadcast, transaction not yet in mempool
730    ProofBroadcast,
731    /// Transaction in mempool, waiting for confirmation
732    InMempool { tx_hash: Hash },
733    /// Settlement confirmed on-chain
734    Settled {
735        tx_hash: Hash,
736        block_hash: Hash,
737        confirmation_count: u32,
738    },
739    /// Payment failed or rejected
740    Failed { reason: String },
741}
742
743#[cfg(test)]
744mod tests {
745    use super::*;
746
747    #[test]
748    fn test_payment_request_creation() {
749        let output = PaymentOutput {
750            script: vec![blvm_consensus::opcodes::OP_1],
751            amount: Some(100000), // 0.001 BTC
752        };
753
754        let request = PaymentRequest::new("main".to_string(), vec![output], 1234567890);
755
756        assert_eq!(request.payment_details.network, "main");
757        assert_eq!(request.payment_details.outputs.len(), 1);
758        assert_eq!(request.payment_details.time, 1234567890);
759    }
760
761    #[test]
762    fn test_payment_request_validation() {
763        let request = PaymentRequest::new(
764            "main".to_string(),
765            vec![PaymentOutput {
766                script: vec![blvm_consensus::opcodes::OP_1],
767                amount: Some(100000),
768            }],
769            1234567890,
770        );
771
772        assert!(request.validate().is_ok());
773    }
774
775    #[test]
776    fn test_payment_request_expired() {
777        let expired_time = 1000;
778        let request = PaymentRequest::new(
779            "main".to_string(),
780            vec![PaymentOutput {
781                script: vec![blvm_consensus::opcodes::OP_1],
782                amount: Some(100000),
783            }],
784            expired_time,
785        )
786        .with_expires(1001);
787
788        // Should fail validation (expired)
789        let result = request.validate();
790        assert!(result.is_err());
791    }
792
793    #[test]
794    fn test_payment_creation() {
795        let tx = vec![0x01, 0x00, 0x00, 0x00]; // Arbitrary bytes for Payment::new structure test (not a valid tx)
796        let payment = Payment::new(vec![tx.clone()]);
797
798        assert_eq!(payment.transactions.len(), 1);
799        assert_eq!(payment.transactions[0], tx);
800    }
801
802    #[test]
803    fn test_payment_validation() {
804        let payment = Payment::new(vec![vec![0x01, 0x02, 0x03]]);
805        assert!(payment.validate().is_ok());
806
807        let empty_payment = Payment::new(vec![]);
808        assert!(empty_payment.validate().is_err());
809    }
810}