rustywallet_lightning/
payment.rs

1//! Payment hash and preimage handling.
2//!
3//! This module provides types for working with Lightning payment
4//! hashes and preimages.
5
6use crate::error::LightningError;
7use sha2::{Digest, Sha256};
8use std::fmt;
9
10/// A 32-byte payment preimage.
11///
12/// The preimage is the secret that, when hashed with SHA256,
13/// produces the payment hash. Revealing the preimage proves
14/// that a payment was received.
15#[derive(Clone, PartialEq, Eq)]
16pub struct PaymentPreimage([u8; 32]);
17
18impl PaymentPreimage {
19    /// Create a new payment preimage from bytes.
20    pub fn from_bytes(bytes: [u8; 32]) -> Self {
21        Self(bytes)
22    }
23
24    /// Create a new payment preimage from a hex string.
25    pub fn from_hex(hex: &str) -> Result<Self, LightningError> {
26        let bytes = hex::decode(hex)
27            .map_err(|e| LightningError::InvalidPreimage(e.to_string()))?;
28        
29        if bytes.len() != 32 {
30            return Err(LightningError::InvalidPreimage(format!(
31                "Expected 32 bytes, got {}",
32                bytes.len()
33            )));
34        }
35
36        let mut arr = [0u8; 32];
37        arr.copy_from_slice(&bytes);
38        Ok(Self(arr))
39    }
40
41    /// Generate a random payment preimage.
42    pub fn random() -> Self {
43        use rand::RngCore;
44        let mut bytes = [0u8; 32];
45        rand::rngs::OsRng.fill_bytes(&mut bytes);
46        Self(bytes)
47    }
48
49    /// Get the raw bytes.
50    pub fn as_bytes(&self) -> &[u8; 32] {
51        &self.0
52    }
53
54    /// Convert to hex string.
55    pub fn to_hex(&self) -> String {
56        hex::encode(self.0)
57    }
58
59    /// Compute the payment hash from this preimage.
60    pub fn payment_hash(&self) -> PaymentHash {
61        let mut hasher = Sha256::new();
62        hasher.update(self.0);
63        let result = hasher.finalize();
64        
65        let mut hash = [0u8; 32];
66        hash.copy_from_slice(&result);
67        PaymentHash(hash)
68    }
69}
70
71impl fmt::Debug for PaymentPreimage {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "PaymentPreimage([REDACTED])")
74    }
75}
76
77impl fmt::Display for PaymentPreimage {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(f, "{}", self.to_hex())
80    }
81}
82
83/// A 32-byte payment hash.
84///
85/// The payment hash is the SHA256 hash of the preimage and is
86/// used to identify payments in the Lightning Network.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88pub struct PaymentHash([u8; 32]);
89
90impl PaymentHash {
91    /// Create a new payment hash from bytes.
92    pub fn from_bytes(bytes: [u8; 32]) -> Self {
93        Self(bytes)
94    }
95
96    /// Create a new payment hash from a hex string.
97    pub fn from_hex(hex: &str) -> Result<Self, LightningError> {
98        let bytes = hex::decode(hex)
99            .map_err(|e| LightningError::InvalidPaymentHash(e.to_string()))?;
100        
101        if bytes.len() != 32 {
102            return Err(LightningError::InvalidPaymentHash(format!(
103                "Expected 32 bytes, got {}",
104                bytes.len()
105            )));
106        }
107
108        let mut arr = [0u8; 32];
109        arr.copy_from_slice(&bytes);
110        Ok(Self(arr))
111    }
112
113    /// Get the raw bytes.
114    pub fn as_bytes(&self) -> &[u8; 32] {
115        &self.0
116    }
117
118    /// Convert to hex string.
119    pub fn to_hex(&self) -> String {
120        hex::encode(self.0)
121    }
122
123    /// Verify that a preimage matches this payment hash.
124    pub fn verify(&self, preimage: &PaymentPreimage) -> bool {
125        preimage.payment_hash() == *self
126    }
127}
128
129impl fmt::Display for PaymentHash {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        write!(f, "{}", self.to_hex())
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_preimage_to_hash() {
141        let preimage = PaymentPreimage::from_bytes([0u8; 32]);
142        let hash = preimage.payment_hash();
143        
144        // SHA256 of 32 zero bytes
145        assert_eq!(
146            hash.to_hex(),
147            "66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925"
148        );
149    }
150
151    #[test]
152    fn test_random_preimage() {
153        let preimage1 = PaymentPreimage::random();
154        let preimage2 = PaymentPreimage::random();
155        
156        assert_ne!(preimage1.as_bytes(), preimage2.as_bytes());
157    }
158
159    #[test]
160    fn test_hash_verification() {
161        let preimage = PaymentPreimage::random();
162        let hash = preimage.payment_hash();
163        
164        assert!(hash.verify(&preimage));
165        
166        let wrong_preimage = PaymentPreimage::random();
167        assert!(!hash.verify(&wrong_preimage));
168    }
169
170    #[test]
171    fn test_hex_roundtrip() {
172        let preimage = PaymentPreimage::random();
173        let hex = preimage.to_hex();
174        let recovered = PaymentPreimage::from_hex(&hex).unwrap();
175        
176        assert_eq!(preimage.as_bytes(), recovered.as_bytes());
177    }
178
179    #[test]
180    fn test_preimage_debug_redacted() {
181        let preimage = PaymentPreimage::random();
182        let debug = format!("{:?}", preimage);
183        
184        assert!(debug.contains("REDACTED"));
185        assert!(!debug.contains(&preimage.to_hex()));
186    }
187}