kaccy_bitcoin/
timelock.rs

1//! Time-locked transactions and Hash Time-Locked Contracts (HTLC)
2//!
3//! This module provides support for time-locked transactions, which are essential
4//! for advanced payment flows like Lightning Network and atomic swaps.
5
6use bitcoin::absolute::LockTime;
7use bitcoin::blockdata::opcodes;
8use bitcoin::hashes::{Hash, sha256};
9use bitcoin::{Address, Amount, Network, ScriptBuf, Sequence, TxOut};
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12
13use crate::error::Result;
14
15/// Time-lock type
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum TimeLockType {
18    /// Absolute block height (nLockTime with block height)
19    BlockHeight(u32),
20    /// Absolute timestamp (nLockTime with UNIX timestamp)
21    Timestamp(u32),
22    /// Relative block height (nSequence with blocks)
23    RelativeBlocks(u16),
24    /// Relative time (nSequence with 512-second intervals)
25    RelativeTime(u16),
26}
27
28impl TimeLockType {
29    /// Check if the timelock has expired
30    pub fn is_expired(&self, current_height: u32, current_time: u32) -> bool {
31        match self {
32            TimeLockType::BlockHeight(height) => current_height >= *height,
33            TimeLockType::Timestamp(timestamp) => current_time >= *timestamp,
34            TimeLockType::RelativeBlocks(_) | TimeLockType::RelativeTime(_) => {
35                // Relative timelocks are evaluated differently
36                false
37            }
38        }
39    }
40
41    /// Convert to nLockTime value
42    pub fn to_lock_time(&self) -> Option<LockTime> {
43        match self {
44            TimeLockType::BlockHeight(height) => LockTime::from_height(*height).ok(),
45            TimeLockType::Timestamp(timestamp) => LockTime::from_time(*timestamp).ok(),
46            _ => None,
47        }
48    }
49
50    /// Convert to nSequence value
51    pub fn to_sequence(&self) -> Option<Sequence> {
52        match self {
53            TimeLockType::RelativeBlocks(blocks) => Some(Sequence::from_height(*blocks)),
54            TimeLockType::RelativeTime(intervals) => {
55                Some(Sequence::from_512_second_intervals(*intervals))
56            }
57            _ => None,
58        }
59    }
60}
61
62/// Hash Time-Locked Contract (HTLC) configuration
63#[derive(Debug, Clone)]
64pub struct HtlcConfig {
65    /// Payment hash (SHA256)
66    pub payment_hash: [u8; 32],
67    /// Sender's public key
68    pub sender_pubkey: Vec<u8>,
69    /// Receiver's public key
70    pub receiver_pubkey: Vec<u8>,
71    /// Time lock
72    pub timelock: TimeLockType,
73    /// Network
74    pub network: Network,
75}
76
77/// HTLC script builder
78pub struct HtlcScriptBuilder {
79    config: HtlcConfig,
80}
81
82impl HtlcScriptBuilder {
83    /// Create a new HTLC script builder
84    pub fn new(config: HtlcConfig) -> Self {
85        Self { config }
86    }
87
88    /// Build the HTLC script
89    ///
90    /// Script format (simplified for compatibility):
91    /// ```text
92    /// OP_IF
93    ///     OP_SHA256 <payment_hash> OP_EQUALVERIFY <receiver_pubkey> OP_CHECKSIG
94    /// OP_ELSE
95    ///     <timelock> OP_CHECKLOCKTIMEVERIFY OP_DROP <sender_pubkey> OP_CHECKSIG
96    /// OP_ENDIF
97    /// ```
98    ///
99    /// Note: This is a simplified implementation. For production use,
100    /// consider using a proper script template library or manual script construction.
101    pub fn build_script(&self) -> Result<ScriptBuf> {
102        // Simplified implementation: create a basic script structure
103        // In a production implementation, you would use proper script building with
104        // the correct types and methods
105
106        let mut script_bytes = Vec::new();
107
108        // OP_IF
109        script_bytes.push(opcodes::all::OP_IF.to_u8());
110
111        // Receiver path: OP_SHA256 <hash> OP_EQUALVERIFY ...
112        script_bytes.push(opcodes::all::OP_SHA256.to_u8());
113        script_bytes.push(32); // Push 32 bytes
114        script_bytes.extend_from_slice(&self.config.payment_hash);
115        script_bytes.push(opcodes::all::OP_EQUALVERIFY.to_u8());
116
117        // Receiver pubkey (simplified)
118        if !self.config.receiver_pubkey.is_empty() {
119            script_bytes.push(self.config.receiver_pubkey.len() as u8);
120            script_bytes.extend_from_slice(&self.config.receiver_pubkey);
121        }
122        script_bytes.push(opcodes::all::OP_CHECKSIG.to_u8());
123
124        // OP_ELSE
125        script_bytes.push(opcodes::all::OP_ELSE.to_u8());
126
127        // Sender path with timelock
128        match self.config.timelock {
129            TimeLockType::BlockHeight(height) | TimeLockType::Timestamp(height) => {
130                let height_bytes = height.to_le_bytes();
131                script_bytes.push(height_bytes.len() as u8);
132                script_bytes.extend_from_slice(&height_bytes);
133                script_bytes.push(opcodes::all::OP_CLTV.to_u8());
134                script_bytes.push(opcodes::all::OP_DROP.to_u8());
135            }
136            TimeLockType::RelativeBlocks(_) | TimeLockType::RelativeTime(_) => {
137                if let Some(sequence) = self.config.timelock.to_sequence() {
138                    let seq_bytes = sequence.to_consensus_u32().to_le_bytes();
139                    script_bytes.push(seq_bytes.len() as u8);
140                    script_bytes.extend_from_slice(&seq_bytes);
141                    script_bytes.push(opcodes::all::OP_CSV.to_u8());
142                    script_bytes.push(opcodes::all::OP_DROP.to_u8());
143                }
144            }
145        }
146
147        // Sender pubkey
148        if !self.config.sender_pubkey.is_empty() {
149            script_bytes.push(self.config.sender_pubkey.len() as u8);
150            script_bytes.extend_from_slice(&self.config.sender_pubkey);
151        }
152        script_bytes.push(opcodes::all::OP_CHECKSIG.to_u8());
153
154        // OP_ENDIF
155        script_bytes.push(opcodes::all::OP_ENDIF.to_u8());
156
157        Ok(ScriptBuf::from_bytes(script_bytes))
158    }
159
160    /// Create P2WSH address for the HTLC
161    pub fn create_address(&self) -> Result<Address> {
162        let script = self.build_script()?;
163        let address = Address::p2wsh(&script, self.config.network);
164        Ok(address)
165    }
166}
167
168/// HTLC manager
169pub struct HtlcManager {
170    network: Network,
171}
172
173impl HtlcManager {
174    /// Create a new HTLC manager
175    pub fn new(network: Network) -> Self {
176        Self { network }
177    }
178
179    /// Create a new HTLC
180    pub fn create_htlc(
181        &self,
182        payment_hash: [u8; 32],
183        sender_pubkey: Vec<u8>,
184        receiver_pubkey: Vec<u8>,
185        timelock: TimeLockType,
186    ) -> Result<HtlcContract> {
187        let config = HtlcConfig {
188            payment_hash,
189            sender_pubkey,
190            receiver_pubkey,
191            timelock,
192            network: self.network,
193        };
194
195        let builder = HtlcScriptBuilder::new(config.clone());
196        let script = builder.build_script()?;
197        let address = builder.create_address()?;
198
199        Ok(HtlcContract {
200            config,
201            script,
202            address,
203            status: HtlcStatus::Pending,
204            created_at: Utc::now(),
205        })
206    }
207
208    /// Generate payment hash from preimage
209    pub fn generate_payment_hash(preimage: &[u8]) -> [u8; 32] {
210        let hash = sha256::Hash::hash(preimage);
211        hash.to_byte_array()
212    }
213
214    /// Verify payment preimage
215    pub fn verify_preimage(preimage: &[u8], payment_hash: &[u8; 32]) -> bool {
216        let computed_hash = Self::generate_payment_hash(preimage);
217        &computed_hash == payment_hash
218    }
219}
220
221/// HTLC contract
222#[derive(Debug, Clone)]
223pub struct HtlcContract {
224    /// Configuration
225    pub config: HtlcConfig,
226    /// The HTLC script
227    pub script: ScriptBuf,
228    /// The P2WSH address
229    pub address: Address,
230    /// Current status
231    pub status: HtlcStatus,
232    /// Creation time
233    pub created_at: DateTime<Utc>,
234}
235
236/// HTLC status
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
238pub enum HtlcStatus {
239    /// Waiting for funding
240    Pending,
241    /// Funded and active
242    Active,
243    /// Claimed by receiver (preimage revealed)
244    Claimed,
245    /// Refunded to sender (timelock expired)
246    Refunded,
247    /// Expired without claim or refund
248    Expired,
249}
250
251impl HtlcContract {
252    /// Check if the HTLC can be claimed
253    pub fn can_claim(&self, current_height: u32, current_time: u32) -> bool {
254        matches!(self.status, HtlcStatus::Active)
255            && !self
256                .config
257                .timelock
258                .is_expired(current_height, current_time)
259    }
260
261    /// Check if the HTLC can be refunded
262    pub fn can_refund(&self, current_height: u32, current_time: u32) -> bool {
263        matches!(self.status, HtlcStatus::Active)
264            && self
265                .config
266                .timelock
267                .is_expired(current_height, current_time)
268    }
269
270    /// Update status to claimed
271    pub fn mark_claimed(&mut self) {
272        self.status = HtlcStatus::Claimed;
273    }
274
275    /// Update status to refunded
276    pub fn mark_refunded(&mut self) {
277        self.status = HtlcStatus::Refunded;
278    }
279
280    /// Update status to active
281    pub fn mark_active(&mut self) {
282        self.status = HtlcStatus::Active;
283    }
284}
285
286/// Simple timelock transaction builder
287pub struct TimelockTxBuilder {
288    #[allow(dead_code)]
289    network: Network,
290}
291
292impl TimelockTxBuilder {
293    /// Create a new timelock transaction builder
294    pub fn new(network: Network) -> Self {
295        Self { network }
296    }
297
298    /// Create a time-locked output
299    pub fn create_timelock_output(
300        &self,
301        recipient: &Address,
302        amount: Amount,
303        _timelock: TimeLockType,
304    ) -> Result<TxOut> {
305        // For simple timelocks, we use a standard output
306        // The timelock is enforced by the transaction's nLockTime or input's nSequence
307        Ok(TxOut {
308            value: amount,
309            script_pubkey: recipient.script_pubkey(),
310        })
311    }
312
313    /// Check if a transaction's timelock has expired
314    pub fn is_timelock_expired(
315        &self,
316        timelock: &TimeLockType,
317        current_height: u32,
318        current_time: u32,
319    ) -> bool {
320        timelock.is_expired(current_height, current_time)
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_timelock_block_height() {
330        let timelock = TimeLockType::BlockHeight(100);
331        assert!(!timelock.is_expired(99, 0));
332        assert!(timelock.is_expired(100, 0));
333        assert!(timelock.is_expired(101, 0));
334    }
335
336    #[test]
337    fn test_timelock_timestamp() {
338        let timelock = TimeLockType::Timestamp(1000000);
339        assert!(!timelock.is_expired(0, 999999));
340        assert!(timelock.is_expired(0, 1000000));
341        assert!(timelock.is_expired(0, 1000001));
342    }
343
344    #[test]
345    fn test_payment_hash_generation() {
346        let preimage = b"test_preimage";
347        let hash = HtlcManager::generate_payment_hash(preimage);
348        assert_eq!(hash.len(), 32);
349        assert!(HtlcManager::verify_preimage(preimage, &hash));
350    }
351
352    #[test]
353    fn test_payment_hash_verification() {
354        let preimage = b"test_preimage";
355        let hash = HtlcManager::generate_payment_hash(preimage);
356        let wrong_preimage = b"wrong_preimage";
357        assert!(!HtlcManager::verify_preimage(wrong_preimage, &hash));
358    }
359
360    #[test]
361    fn test_htlc_creation() {
362        let manager = HtlcManager::new(Network::Testnet);
363        let payment_hash = HtlcManager::generate_payment_hash(b"secret");
364        let sender_pubkey = vec![0x02; 33];
365        let receiver_pubkey = vec![0x03; 33];
366        let timelock = TimeLockType::BlockHeight(100);
367
368        let htlc = manager
369            .create_htlc(payment_hash, sender_pubkey, receiver_pubkey, timelock)
370            .unwrap();
371
372        assert_eq!(htlc.status, HtlcStatus::Pending);
373        assert!(!htlc.script.is_empty());
374    }
375
376    #[test]
377    fn test_htlc_can_claim() {
378        let manager = HtlcManager::new(Network::Testnet);
379        let payment_hash = HtlcManager::generate_payment_hash(b"secret");
380        let timelock = TimeLockType::BlockHeight(100);
381
382        let mut htlc = manager
383            .create_htlc(payment_hash, vec![0x02; 33], vec![0x03; 33], timelock)
384            .unwrap();
385
386        htlc.mark_active();
387
388        // Can claim before timelock expiry
389        assert!(htlc.can_claim(99, 0));
390        // Cannot claim after timelock expiry
391        assert!(!htlc.can_claim(100, 0));
392    }
393
394    #[test]
395    fn test_htlc_can_refund() {
396        let manager = HtlcManager::new(Network::Testnet);
397        let payment_hash = HtlcManager::generate_payment_hash(b"secret");
398        let timelock = TimeLockType::BlockHeight(100);
399
400        let mut htlc = manager
401            .create_htlc(payment_hash, vec![0x02; 33], vec![0x03; 33], timelock)
402            .unwrap();
403
404        htlc.mark_active();
405
406        // Cannot refund before timelock expiry
407        assert!(!htlc.can_refund(99, 0));
408        // Can refund after timelock expiry
409        assert!(htlc.can_refund(100, 0));
410    }
411
412    #[test]
413    fn test_htlc_status_transitions() {
414        let manager = HtlcManager::new(Network::Testnet);
415        let payment_hash = HtlcManager::generate_payment_hash(b"secret");
416        let timelock = TimeLockType::BlockHeight(100);
417
418        let mut htlc = manager
419            .create_htlc(payment_hash, vec![0x02; 33], vec![0x03; 33], timelock)
420            .unwrap();
421
422        assert_eq!(htlc.status, HtlcStatus::Pending);
423
424        htlc.mark_active();
425        assert_eq!(htlc.status, HtlcStatus::Active);
426
427        htlc.mark_claimed();
428        assert_eq!(htlc.status, HtlcStatus::Claimed);
429    }
430
431    #[test]
432    fn test_timelock_to_lock_time() {
433        let block_height = TimeLockType::BlockHeight(100);
434        assert!(block_height.to_lock_time().is_some());
435
436        // nLockTime timestamps must be >= 500000000 (LOCKTIME_THRESHOLD)
437        let timestamp = TimeLockType::Timestamp(1600000000); // Valid Unix timestamp
438        assert!(timestamp.to_lock_time().is_some());
439
440        // Invalid timestamp (too low)
441        let invalid_timestamp = TimeLockType::Timestamp(1000);
442        assert!(invalid_timestamp.to_lock_time().is_none());
443
444        let relative = TimeLockType::RelativeBlocks(10);
445        assert!(relative.to_lock_time().is_none());
446    }
447
448    #[test]
449    fn test_timelock_to_sequence() {
450        let relative_blocks = TimeLockType::RelativeBlocks(10);
451        assert!(relative_blocks.to_sequence().is_some());
452
453        let relative_time = TimeLockType::RelativeTime(100);
454        assert!(relative_time.to_sequence().is_some());
455
456        let absolute = TimeLockType::BlockHeight(100);
457        assert!(absolute.to_sequence().is_none());
458    }
459}