1use std::str::FromStr;
6
7use bitcoin::hashes::sha256::Hash as Sha256Hash;
8use bitcoin::hashes::Hash;
9use bitcoin::secp256k1::schnorr::Signature;
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13use super::nut00::Witness;
14use super::nut10::Secret;
15use super::nut11::valid_signatures;
16use super::{Conditions, Proof};
17use crate::nut10::get_pubkeys_and_required_sigs;
18use crate::nut11::extract_signatures_from_witness;
19use crate::util::{hex, unix_time};
20use crate::SpendingConditions;
21
22pub mod serde_htlc_witness;
23
24#[derive(Debug, Error)]
26pub enum Error {
27 #[error("Secret is not a HTLC secret")]
29 IncorrectSecretKind,
30 #[error("Locktime in past")]
32 LocktimeInPast,
33 #[error("Invalid signature")]
35 InvalidSignature,
36 #[error("Hash required")]
38 HashRequired,
39 #[error("Hash is not valid")]
41 InvalidHash,
42 #[error("Preimage does not match")]
44 Preimage,
45 #[error("Preimage must be valid hex encoding")]
47 InvalidHexPreimage,
48 #[error("Preimage must be exactly 32 bytes (64 hex characters)")]
50 PreimageInvalidSize,
51 #[error("Witness did not provide signatures")]
53 SignaturesNotProvided,
54 #[error("SIG_ALL proofs must be verified using a different method")]
56 SigAllNotSupportedHere,
57 #[error("HTLC spend conditions are not met")]
59 SpendConditionsNotMet,
60 #[error(transparent)]
62 HexError(#[from] hex::Error),
63 #[error(transparent)]
65 Secp256k1(#[from] bitcoin::secp256k1::Error),
66 #[error(transparent)]
68 NUT11(#[from] super::nut11::Error),
69 #[error(transparent)]
70 Serde(#[from] serde_json::Error),
72}
73
74#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
76#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
77pub struct HTLCWitness {
78 pub preimage: String,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub signatures: Option<Vec<String>>,
83}
84
85impl HTLCWitness {
86 pub fn preimage_data(&self) -> Result<[u8; 32], Error> {
92 const REQUIRED_PREIMAGE_BYTES: usize = 32;
93
94 let preimage_bytes = hex::decode(&self.preimage).map_err(|_| Error::InvalidHexPreimage)?;
96
97 if preimage_bytes.len() != REQUIRED_PREIMAGE_BYTES {
99 return Err(Error::PreimageInvalidSize);
100 }
101
102 let mut array = [0u8; 32];
104 array.copy_from_slice(&preimage_bytes);
105 Ok(array)
106 }
107}
108
109impl Proof {
110 pub fn verify_htlc(&self) -> Result<(), Error> {
119 let secret: Secret = self.secret.clone().try_into()?;
120 let spending_conditions: Conditions = secret
121 .secret_data()
122 .tags()
123 .cloned()
124 .unwrap_or_default()
125 .try_into()
126 .map_err(|_| Error::SpendConditionsNotMet)?;
127
128 if spending_conditions.sig_flag == super::SigFlag::SigAll {
129 return Err(Error::SigAllNotSupportedHere);
130 }
131
132 if secret.kind() != super::Kind::HTLC {
133 return Err(Error::IncorrectSecretKind);
134 }
135
136 let now = unix_time();
138 let requirements =
139 super::nut10::get_pubkeys_and_required_sigs(&secret, now).map_err(|err| match err {
140 super::nut10::Error::NUT14(nut14_err) => nut14_err,
141 _ => Error::SpendConditionsNotMet,
142 })?;
143
144 let htlc_witness = match &self.witness {
146 Some(Witness::HTLCWitness(witness)) => witness,
147 _ => {
148 if let Some(refund_path) = &requirements.refund_path {
151 if refund_path.required_sigs == 0 {
152 return Ok(());
153 }
154 }
155 return Err(Error::IncorrectSecretKind);
156 }
157 };
158
159 let preimage_result = verify_htlc_preimage(htlc_witness, &secret);
161
162 if preimage_result.is_ok() {
166 if requirements.required_sigs == 0 {
168 return Ok(());
169 }
170
171 let witness_signatures = htlc_witness
172 .signatures
173 .as_ref()
174 .ok_or(Error::SignaturesNotProvided)?;
175
176 let signatures: Vec<Signature> = witness_signatures
177 .iter()
178 .map(|s| Signature::from_str(s))
179 .collect::<Result<Vec<_>, _>>()?;
180
181 let msg: &[u8] = self.secret.as_bytes();
182 let valid_sig_count = valid_signatures(msg, &requirements.pubkeys, &signatures)?;
183
184 if valid_sig_count >= requirements.required_sigs {
185 Ok(())
186 } else {
187 Err(Error::NUT11(super::nut11::Error::SpendConditionsNotMet))
188 }
189 } else if let Some(refund_path) = &requirements.refund_path {
190 if refund_path.required_sigs == 0 {
193 return Ok(());
195 }
196
197 let witness_signatures = htlc_witness
198 .signatures
199 .as_ref()
200 .ok_or(Error::SignaturesNotProvided)?;
201
202 let signatures: Vec<Signature> = witness_signatures
203 .iter()
204 .map(|s| Signature::from_str(s))
205 .collect::<Result<Vec<_>, _>>()?;
206
207 let msg: &[u8] = self.secret.as_bytes();
208 let valid_sig_count = valid_signatures(msg, &refund_path.pubkeys, &signatures)?;
209
210 if valid_sig_count >= refund_path.required_sigs {
211 Ok(())
212 } else {
213 Err(Error::NUT11(super::nut11::Error::SpendConditionsNotMet))
214 }
215 } else {
216 preimage_result
219 }
220 }
221
222 #[inline]
224 pub fn add_preimage(&mut self, preimage: String) {
225 let signatures = self
226 .witness
227 .as_ref()
228 .map(super::nut00::Witness::signatures)
229 .unwrap_or_default();
230
231 self.witness = Some(Witness::HTLCWitness(HTLCWitness {
232 preimage,
233 signatures,
234 }))
235 }
236}
237
238impl SpendingConditions {
239 pub fn new_htlc(preimage: String, conditions: Option<Conditions>) -> Result<Self, Error> {
241 const MAX_PREIMAGE_BYTES: usize = 32;
242
243 let preimage_bytes = hex::decode(preimage)?;
244
245 if preimage_bytes.len() != MAX_PREIMAGE_BYTES {
246 return Err(Error::PreimageInvalidSize);
247 }
248
249 let htlc = Sha256Hash::hash(&preimage_bytes);
250
251 Ok(Self::HTLCConditions {
252 data: htlc,
253 conditions,
254 })
255 }
256
257 pub fn new_htlc_hash(hash: &str, conditions: Option<Conditions>) -> Result<Self, Error> {
259 let hash = Sha256Hash::from_str(hash).map_err(|_| Error::InvalidHash)?;
260
261 Ok(Self::HTLCConditions {
262 data: hash,
263 conditions,
264 })
265 }
266}
267
268fn verify_htlc_preimage(witness: &HTLCWitness, secret: &Secret) -> Result<(), Error> {
273 use bitcoin::hashes::sha256::Hash as Sha256Hash;
274 use bitcoin::hashes::Hash;
275
276 let hash_lock =
278 Sha256Hash::from_str(secret.secret_data().data()).map_err(|_| Error::InvalidHash)?;
279
280 let preimage_bytes = witness.preimage_data()?;
282
283 let preimage_hash = Sha256Hash::hash(&preimage_bytes);
285
286 if hash_lock.ne(&preimage_hash) {
288 return Err(Error::Preimage);
289 }
290
291 Ok(())
292}
293
294pub(crate) fn verify_sig_all_htlc(first_input: &Proof, msg_to_sign: String) -> Result<(), Error> {
304 let first_secret =
306 Secret::try_from(&first_input.secret).map_err(|_| Error::IncorrectSecretKind)?;
307
308 let current_time = crate::util::unix_time();
310
311 let requirements = get_pubkeys_and_required_sigs(&first_secret, current_time)
313 .map_err(|_| Error::SpendConditionsNotMet)?;
314
315 let htlc_witness = match first_input.witness.as_ref() {
317 Some(super::Witness::HTLCWitness(witness)) => Some(witness),
318 _ => None,
319 };
320
321 let preimage_valid = htlc_witness
323 .map(|w| verify_htlc_preimage(w, &first_secret).is_ok())
324 .unwrap_or(false);
325
326 if !preimage_valid {
329 if let Some(refund_path) = &requirements.refund_path {
330 if refund_path.required_sigs == 0 {
331 return Ok(());
332 }
333 }
334 }
335
336 let first_witness = first_input
338 .witness
339 .as_ref()
340 .ok_or(Error::SignaturesNotProvided)?;
341
342 if preimage_valid {
346 if requirements.required_sigs == 0 {
348 return Ok(());
349 }
350
351 let signatures = extract_signatures_from_witness(first_witness)?;
352 let valid_sig_count = super::nut11::valid_signatures(
353 msg_to_sign.as_bytes(),
354 &requirements.pubkeys,
355 &signatures,
356 )
357 .map_err(|_| Error::InvalidSignature)?;
358
359 if valid_sig_count >= requirements.required_sigs {
360 Ok(())
361 } else {
362 Err(Error::SpendConditionsNotMet)
363 }
364 } else if let Some(refund_path) = &requirements.refund_path {
365 let signatures = extract_signatures_from_witness(first_witness)?;
368 let valid_sig_count = super::nut11::valid_signatures(
369 msg_to_sign.as_bytes(),
370 &refund_path.pubkeys,
371 &signatures,
372 )
373 .map_err(|_| Error::InvalidSignature)?;
374
375 if valid_sig_count >= refund_path.required_sigs {
376 Ok(())
377 } else {
378 Err(Error::SpendConditionsNotMet)
379 }
380 } else {
381 Err(Error::SpendConditionsNotMet)
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use bitcoin::hashes::sha256::Hash as Sha256Hash;
389 use bitcoin::hashes::Hash;
390
391 use super::*;
392 use crate::nuts::nut00::Witness;
393 use crate::nuts::nut10::Kind;
394 use crate::nuts::Nut10Secret;
395 use crate::secret::Secret as SecretString;
396 use crate::SecretData;
397
398 #[test]
406 fn test_verify_htlc_valid() {
407 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
410 let hash_str = hash.to_string();
411
412 let nut10_secret = Nut10Secret::new(
413 Kind::HTLC,
414 SecretData::new(hash_str, None::<Vec<Vec<String>>>),
415 );
416 let secret: SecretString = nut10_secret.try_into().unwrap();
417
418 let htlc_witness = HTLCWitness {
419 preimage: hex::encode(preimage_bytes),
420 signatures: None,
421 };
422
423 let proof = Proof {
424 amount: crate::Amount::from(1),
425 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
426 secret,
427 c: crate::nuts::nut01::PublicKey::from_hex(
428 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
429 )
430 .unwrap(),
431 witness: Some(Witness::HTLCWitness(htlc_witness)),
432 dleq: None,
433 p2pk_e: None,
434 };
435
436 assert!(proof.verify_htlc().is_ok());
438 }
439
440 #[test]
449 fn test_verify_htlc_wrong_preimage() {
450 let correct_preimage_bytes = [42u8; 32];
452 let hash = Sha256Hash::hash(&correct_preimage_bytes);
453 let hash_str = hash.to_string();
454
455 let nut10_secret = Nut10Secret::new(
456 Kind::HTLC,
457 SecretData::new(hash_str, None::<Vec<Vec<String>>>),
458 );
459 let secret: SecretString = nut10_secret.try_into().unwrap();
460
461 let wrong_preimage_bytes = [99u8; 32]; let htlc_witness = HTLCWitness {
464 preimage: hex::encode(wrong_preimage_bytes),
465 signatures: None,
466 };
467
468 let proof = Proof {
469 amount: crate::Amount::from(1),
470 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
471 secret,
472 c: crate::nuts::nut01::PublicKey::from_hex(
473 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
474 )
475 .unwrap(),
476 witness: Some(Witness::HTLCWitness(htlc_witness)),
477 dleq: None,
478 p2pk_e: None,
479 };
480
481 let result = proof.verify_htlc();
483 assert!(result.is_err());
484 assert!(matches!(result.unwrap_err(), Error::Preimage));
485 }
486
487 #[test]
495 fn test_verify_htlc_invalid_hash() {
496 let invalid_hash = "not_a_valid_hash";
498
499 let nut10_secret = Nut10Secret::new(
500 Kind::HTLC,
501 SecretData::new(invalid_hash.to_string(), None::<Vec<Vec<String>>>),
502 );
503 let secret: SecretString = nut10_secret.try_into().unwrap();
504
505 let preimage_bytes = [42u8; 32]; let htlc_witness = HTLCWitness {
507 preimage: hex::encode(preimage_bytes),
508 signatures: None,
509 };
510
511 let proof = Proof {
512 amount: crate::Amount::from(1),
513 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
514 secret,
515 c: crate::nuts::nut01::PublicKey::from_hex(
516 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
517 )
518 .unwrap(),
519 witness: Some(Witness::HTLCWitness(htlc_witness)),
520 dleq: None,
521 p2pk_e: None,
522 };
523
524 let result = proof.verify_htlc();
526 assert!(result.is_err());
527 assert!(matches!(result.unwrap_err(), Error::InvalidHash));
528 }
529
530 #[test]
538 fn test_verify_htlc_wrong_witness_type() {
539 let preimage = "test_preimage";
541 let hash = Sha256Hash::hash(preimage.as_bytes());
542 let hash_str = hash.to_string();
543
544 let nut10_secret = Nut10Secret::new(
545 Kind::HTLC,
546 SecretData::new(hash_str, None::<Vec<Vec<String>>>),
547 );
548 let secret: SecretString = nut10_secret.try_into().unwrap();
549
550 let proof = Proof {
552 amount: crate::Amount::from(1),
553 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
554 secret,
555 c: crate::nuts::nut01::PublicKey::from_hex(
556 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
557 )
558 .unwrap(),
559 witness: Some(Witness::P2PKWitness(super::super::nut11::P2PKWitness {
560 signatures: vec![],
561 })),
562 dleq: None,
563 p2pk_e: None,
564 };
565
566 let result = proof.verify_htlc();
568 assert!(result.is_err());
569 assert!(matches!(result.unwrap_err(), Error::IncorrectSecretKind));
570 }
571
572 #[test]
580 fn test_add_preimage() {
581 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
583 let hash_str = hash.to_string();
584
585 let nut10_secret = Nut10Secret::new(
586 Kind::HTLC,
587 SecretData::new(hash_str, None::<Vec<Vec<String>>>),
588 );
589 let secret: SecretString = nut10_secret.try_into().unwrap();
590
591 let mut proof = Proof {
592 amount: crate::Amount::from(1),
593 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
594 secret,
595 c: crate::nuts::nut01::PublicKey::from_hex(
596 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
597 )
598 .unwrap(),
599 witness: None,
600 dleq: None,
601 p2pk_e: None,
602 };
603
604 assert!(proof.witness.is_none());
606
607 let preimage_hex = hex::encode(preimage_bytes);
609 proof.add_preimage(preimage_hex.clone());
610
611 assert!(proof.witness.is_some());
613 if let Some(Witness::HTLCWitness(witness)) = &proof.witness {
614 assert_eq!(witness.preimage, preimage_hex);
615 } else {
616 panic!("Expected HTLCWitness");
617 }
618
619 assert!(proof.verify_htlc().is_ok());
621 }
622
623 #[test]
631 fn test_htlc_locktime_and_refund_keys_logic() {
632 use crate::nuts::nut01::PublicKey;
633 use crate::nuts::nut10::Conditions;
634
635 let correct_preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&correct_preimage_bytes);
637 let hash_str = hash.to_string();
638
639 let wrong_preimage_bytes = [99u8; 32];
641
642 let refund_pubkey = PublicKey::from_hex(
646 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
647 )
648 .unwrap();
649
650 let conditions_with_refund = Conditions {
651 locktime: Some(1), pubkeys: None,
653 refund_keys: Some(vec![refund_pubkey]), num_sigs: None,
655 sig_flag: crate::nuts::nut11::SigFlag::default(),
656 num_sigs_refund: None,
657 };
658
659 let nut10_secret = Nut10Secret::new(
660 Kind::HTLC,
661 SecretData::new(hash_str, Some(conditions_with_refund)),
662 );
663 let secret: SecretString = nut10_secret.try_into().unwrap();
664
665 let htlc_witness = HTLCWitness {
666 preimage: hex::encode(wrong_preimage_bytes), signatures: None, };
669
670 let proof = Proof {
671 amount: crate::Amount::from(1),
672 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
673 secret,
674 c: crate::nuts::nut01::PublicKey::from_hex(
675 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
676 )
677 .unwrap(),
678 witness: Some(Witness::HTLCWitness(htlc_witness)),
679 dleq: None,
680 p2pk_e: None,
681 };
682
683 let result = proof.verify_htlc();
689 assert!(
690 result.is_err(),
691 "Should fail when using refund path with refund keys but no signature"
692 );
693 }
694}