1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum TimeLockType {
18 BlockHeight(u32),
20 Timestamp(u32),
22 RelativeBlocks(u16),
24 RelativeTime(u16),
26}
27
28impl TimeLockType {
29 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 false
37 }
38 }
39 }
40
41 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 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#[derive(Debug, Clone)]
64pub struct HtlcConfig {
65 pub payment_hash: [u8; 32],
67 pub sender_pubkey: Vec<u8>,
69 pub receiver_pubkey: Vec<u8>,
71 pub timelock: TimeLockType,
73 pub network: Network,
75}
76
77pub struct HtlcScriptBuilder {
79 config: HtlcConfig,
80}
81
82impl HtlcScriptBuilder {
83 pub fn new(config: HtlcConfig) -> Self {
85 Self { config }
86 }
87
88 pub fn build_script(&self) -> Result<ScriptBuf> {
102 let mut script_bytes = Vec::new();
107
108 script_bytes.push(opcodes::all::OP_IF.to_u8());
110
111 script_bytes.push(opcodes::all::OP_SHA256.to_u8());
113 script_bytes.push(32); script_bytes.extend_from_slice(&self.config.payment_hash);
115 script_bytes.push(opcodes::all::OP_EQUALVERIFY.to_u8());
116
117 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 script_bytes.push(opcodes::all::OP_ELSE.to_u8());
126
127 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 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 script_bytes.push(opcodes::all::OP_ENDIF.to_u8());
156
157 Ok(ScriptBuf::from_bytes(script_bytes))
158 }
159
160 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
168pub struct HtlcManager {
170 network: Network,
171}
172
173impl HtlcManager {
174 pub fn new(network: Network) -> Self {
176 Self { network }
177 }
178
179 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 pub fn generate_payment_hash(preimage: &[u8]) -> [u8; 32] {
210 let hash = sha256::Hash::hash(preimage);
211 hash.to_byte_array()
212 }
213
214 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#[derive(Debug, Clone)]
223pub struct HtlcContract {
224 pub config: HtlcConfig,
226 pub script: ScriptBuf,
228 pub address: Address,
230 pub status: HtlcStatus,
232 pub created_at: DateTime<Utc>,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
238pub enum HtlcStatus {
239 Pending,
241 Active,
243 Claimed,
245 Refunded,
247 Expired,
249}
250
251impl HtlcContract {
252 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 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 pub fn mark_claimed(&mut self) {
272 self.status = HtlcStatus::Claimed;
273 }
274
275 pub fn mark_refunded(&mut self) {
277 self.status = HtlcStatus::Refunded;
278 }
279
280 pub fn mark_active(&mut self) {
282 self.status = HtlcStatus::Active;
283 }
284}
285
286pub struct TimelockTxBuilder {
288 #[allow(dead_code)]
289 network: Network,
290}
291
292impl TimelockTxBuilder {
293 pub fn new(network: Network) -> Self {
295 Self { network }
296 }
297
298 pub fn create_timelock_output(
300 &self,
301 recipient: &Address,
302 amount: Amount,
303 _timelock: TimeLockType,
304 ) -> Result<TxOut> {
305 Ok(TxOut {
308 value: amount,
309 script_pubkey: recipient.script_pubkey(),
310 })
311 }
312
313 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 assert!(htlc.can_claim(99, 0));
390 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 assert!(!htlc.can_refund(99, 0));
408 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 let timestamp = TimeLockType::Timestamp(1600000000); assert!(timestamp.to_lock_time().is_some());
439
440 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}