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)]
76pub struct HTLCWitness {
77 pub preimage: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub signatures: Option<Vec<String>>,
82}
83
84impl HTLCWitness {
85 pub fn preimage_data(&self) -> Result<[u8; 32], Error> {
91 const REQUIRED_PREIMAGE_BYTES: usize = 32;
92
93 let preimage_bytes = hex::decode(&self.preimage).map_err(|_| Error::InvalidHexPreimage)?;
95
96 if preimage_bytes.len() != REQUIRED_PREIMAGE_BYTES {
98 return Err(Error::PreimageInvalidSize);
99 }
100
101 let mut array = [0u8; 32];
103 array.copy_from_slice(&preimage_bytes);
104 Ok(array)
105 }
106}
107
108impl Proof {
109 pub fn verify_htlc(&self) -> Result<(), Error> {
118 let secret: Secret = self.secret.clone().try_into()?;
119 let spending_conditions: Conditions = secret
120 .secret_data()
121 .tags()
122 .cloned()
123 .unwrap_or_default()
124 .try_into()
125 .map_err(|_| Error::SpendConditionsNotMet)?;
126
127 if spending_conditions.sig_flag == super::SigFlag::SigAll {
128 return Err(Error::SigAllNotSupportedHere);
129 }
130
131 if secret.kind() != super::Kind::HTLC {
132 return Err(Error::IncorrectSecretKind);
133 }
134
135 let now = unix_time();
137 let requirements =
138 super::nut10::get_pubkeys_and_required_sigs(&secret, now).map_err(|err| match err {
139 super::nut10::Error::NUT14(nut14_err) => nut14_err,
140 _ => Error::SpendConditionsNotMet,
141 })?;
142
143 let htlc_witness = match &self.witness {
145 Some(Witness::HTLCWitness(witness)) => witness,
146 _ => {
147 if let Some(refund_path) = &requirements.refund_path {
150 if refund_path.required_sigs == 0 {
151 return Ok(());
152 }
153 }
154 return Err(Error::IncorrectSecretKind);
155 }
156 };
157
158 let preimage_result = verify_htlc_preimage(htlc_witness, &secret);
160
161 if preimage_result.is_ok() {
165 if requirements.required_sigs == 0 {
167 return Ok(());
168 }
169
170 let witness_signatures = htlc_witness
171 .signatures
172 .as_ref()
173 .ok_or(Error::SignaturesNotProvided)?;
174
175 let signatures: Vec<Signature> = witness_signatures
176 .iter()
177 .map(|s| Signature::from_str(s))
178 .collect::<Result<Vec<_>, _>>()?;
179
180 let msg: &[u8] = self.secret.as_bytes();
181 let valid_sig_count = valid_signatures(msg, &requirements.pubkeys, &signatures)?;
182
183 if valid_sig_count >= requirements.required_sigs {
184 Ok(())
185 } else {
186 Err(Error::NUT11(super::nut11::Error::SpendConditionsNotMet))
187 }
188 } else if let Some(refund_path) = &requirements.refund_path {
189 if refund_path.required_sigs == 0 {
192 return Ok(());
194 }
195
196 let witness_signatures = htlc_witness
197 .signatures
198 .as_ref()
199 .ok_or(Error::SignaturesNotProvided)?;
200
201 let signatures: Vec<Signature> = witness_signatures
202 .iter()
203 .map(|s| Signature::from_str(s))
204 .collect::<Result<Vec<_>, _>>()?;
205
206 let msg: &[u8] = self.secret.as_bytes();
207 let valid_sig_count = valid_signatures(msg, &refund_path.pubkeys, &signatures)?;
208
209 if valid_sig_count >= refund_path.required_sigs {
210 Ok(())
211 } else {
212 Err(Error::NUT11(super::nut11::Error::SpendConditionsNotMet))
213 }
214 } else {
215 preimage_result
218 }
219 }
220
221 #[inline]
223 pub fn add_preimage(&mut self, preimage: String) {
224 let signatures = self
225 .witness
226 .as_ref()
227 .map(super::nut00::Witness::signatures)
228 .unwrap_or_default();
229
230 self.witness = Some(Witness::HTLCWitness(HTLCWitness {
231 preimage,
232 signatures,
233 }))
234 }
235}
236
237impl SpendingConditions {
238 pub fn new_htlc(preimage: String, conditions: Option<Conditions>) -> Result<Self, Error> {
240 const MAX_PREIMAGE_BYTES: usize = 32;
241
242 let preimage_bytes = hex::decode(preimage)?;
243
244 if preimage_bytes.len() != MAX_PREIMAGE_BYTES {
245 return Err(Error::PreimageInvalidSize);
246 }
247
248 let htlc = Sha256Hash::hash(&preimage_bytes);
249
250 Ok(Self::HTLCConditions {
251 data: htlc,
252 conditions,
253 })
254 }
255
256 pub fn new_htlc_hash(hash: &str, conditions: Option<Conditions>) -> Result<Self, Error> {
258 let hash = Sha256Hash::from_str(hash).map_err(|_| Error::InvalidHash)?;
259
260 Ok(Self::HTLCConditions {
261 data: hash,
262 conditions,
263 })
264 }
265}
266
267fn verify_htlc_preimage(witness: &HTLCWitness, secret: &Secret) -> Result<(), Error> {
272 use bitcoin::hashes::sha256::Hash as Sha256Hash;
273 use bitcoin::hashes::Hash;
274
275 let hash_lock =
277 Sha256Hash::from_str(secret.secret_data().data()).map_err(|_| Error::InvalidHash)?;
278
279 let preimage_bytes = witness.preimage_data()?;
281
282 let preimage_hash = Sha256Hash::hash(&preimage_bytes);
284
285 if hash_lock.ne(&preimage_hash) {
287 return Err(Error::Preimage);
288 }
289
290 Ok(())
291}
292
293pub(crate) fn verify_sig_all_htlc(first_input: &Proof, msg_to_sign: String) -> Result<(), Error> {
303 let first_secret =
305 Secret::try_from(&first_input.secret).map_err(|_| Error::IncorrectSecretKind)?;
306
307 let current_time = crate::util::unix_time();
309
310 let requirements = get_pubkeys_and_required_sigs(&first_secret, current_time)
312 .map_err(|_| Error::SpendConditionsNotMet)?;
313
314 let htlc_witness = match first_input.witness.as_ref() {
316 Some(super::Witness::HTLCWitness(witness)) => Some(witness),
317 _ => None,
318 };
319
320 let preimage_valid = htlc_witness
322 .map(|w| verify_htlc_preimage(w, &first_secret).is_ok())
323 .unwrap_or(false);
324
325 if !preimage_valid {
328 if let Some(refund_path) = &requirements.refund_path {
329 if refund_path.required_sigs == 0 {
330 return Ok(());
331 }
332 }
333 }
334
335 let first_witness = first_input
337 .witness
338 .as_ref()
339 .ok_or(Error::SignaturesNotProvided)?;
340
341 if preimage_valid {
345 if requirements.required_sigs == 0 {
347 return Ok(());
348 }
349
350 let signatures = extract_signatures_from_witness(first_witness)?;
351 let valid_sig_count = super::nut11::valid_signatures(
352 msg_to_sign.as_bytes(),
353 &requirements.pubkeys,
354 &signatures,
355 )
356 .map_err(|_| Error::InvalidSignature)?;
357
358 if valid_sig_count >= requirements.required_sigs {
359 Ok(())
360 } else {
361 Err(Error::SpendConditionsNotMet)
362 }
363 } else if let Some(refund_path) = &requirements.refund_path {
364 let signatures = extract_signatures_from_witness(first_witness)?;
367 let valid_sig_count = super::nut11::valid_signatures(
368 msg_to_sign.as_bytes(),
369 &refund_path.pubkeys,
370 &signatures,
371 )
372 .map_err(|_| Error::InvalidSignature)?;
373
374 if valid_sig_count >= refund_path.required_sigs {
375 Ok(())
376 } else {
377 Err(Error::SpendConditionsNotMet)
378 }
379 } else {
380 Err(Error::SpendConditionsNotMet)
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use bitcoin::hashes::sha256::Hash as Sha256Hash;
388 use bitcoin::hashes::Hash;
389
390 use super::*;
391 use crate::nuts::nut00::Witness;
392 use crate::nuts::nut10::Kind;
393 use crate::nuts::Nut10Secret;
394 use crate::secret::Secret as SecretString;
395 use crate::SecretData;
396
397 #[test]
405 fn test_verify_htlc_valid() {
406 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
409 let hash_str = hash.to_string();
410
411 let nut10_secret = Nut10Secret::new(
412 Kind::HTLC,
413 SecretData::new(hash_str, None::<Vec<Vec<String>>>),
414 );
415 let secret: SecretString = nut10_secret.try_into().unwrap();
416
417 let htlc_witness = HTLCWitness {
418 preimage: hex::encode(preimage_bytes),
419 signatures: None,
420 };
421
422 let proof = Proof {
423 amount: crate::Amount::from(1),
424 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
425 secret,
426 c: crate::nuts::nut01::PublicKey::from_hex(
427 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
428 )
429 .unwrap(),
430 witness: Some(Witness::HTLCWitness(htlc_witness)),
431 dleq: None,
432 p2pk_e: None,
433 };
434
435 assert!(proof.verify_htlc().is_ok());
437 }
438
439 #[test]
448 fn test_verify_htlc_wrong_preimage() {
449 let correct_preimage_bytes = [42u8; 32];
451 let hash = Sha256Hash::hash(&correct_preimage_bytes);
452 let hash_str = hash.to_string();
453
454 let nut10_secret = Nut10Secret::new(
455 Kind::HTLC,
456 SecretData::new(hash_str, None::<Vec<Vec<String>>>),
457 );
458 let secret: SecretString = nut10_secret.try_into().unwrap();
459
460 let wrong_preimage_bytes = [99u8; 32]; let htlc_witness = HTLCWitness {
463 preimage: hex::encode(wrong_preimage_bytes),
464 signatures: None,
465 };
466
467 let proof = Proof {
468 amount: crate::Amount::from(1),
469 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
470 secret,
471 c: crate::nuts::nut01::PublicKey::from_hex(
472 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
473 )
474 .unwrap(),
475 witness: Some(Witness::HTLCWitness(htlc_witness)),
476 dleq: None,
477 p2pk_e: None,
478 };
479
480 let result = proof.verify_htlc();
482 assert!(result.is_err());
483 assert!(matches!(result.unwrap_err(), Error::Preimage));
484 }
485
486 #[test]
494 fn test_verify_htlc_invalid_hash() {
495 let invalid_hash = "not_a_valid_hash";
497
498 let nut10_secret = Nut10Secret::new(
499 Kind::HTLC,
500 SecretData::new(invalid_hash.to_string(), None::<Vec<Vec<String>>>),
501 );
502 let secret: SecretString = nut10_secret.try_into().unwrap();
503
504 let preimage_bytes = [42u8; 32]; let htlc_witness = HTLCWitness {
506 preimage: hex::encode(preimage_bytes),
507 signatures: None,
508 };
509
510 let proof = Proof {
511 amount: crate::Amount::from(1),
512 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
513 secret,
514 c: crate::nuts::nut01::PublicKey::from_hex(
515 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
516 )
517 .unwrap(),
518 witness: Some(Witness::HTLCWitness(htlc_witness)),
519 dleq: None,
520 p2pk_e: None,
521 };
522
523 let result = proof.verify_htlc();
525 assert!(result.is_err());
526 assert!(matches!(result.unwrap_err(), Error::InvalidHash));
527 }
528
529 #[test]
530 fn test_htlc_num_sigs_zero_bypasses_signature_requirement() {
531 let pubkey = crate::nuts::nut01::PublicKey::from_hex(
532 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
533 )
534 .unwrap();
535
536 let preimage_bytes = [42u8; 32];
537 let hash = Sha256Hash::hash(&preimage_bytes);
538 let hash_str = hash.to_string();
539
540 let tags = vec![
541 vec!["pubkeys".to_string(), pubkey.to_string()],
542 vec!["n_sigs".to_string(), "0".to_string()],
543 ];
544
545 let nut10_secret = Nut10Secret::new(Kind::HTLC, SecretData::new(hash_str, Some(tags)));
546 let conditions_res = crate::nuts::nut10::Conditions::try_from(
549 nut10_secret.secret_data().tags().cloned().unwrap(),
550 );
551 assert!(
552 conditions_res.is_err(),
553 "Conditions should fail to parse due to n_sigs=0"
554 );
555 }
556
557 #[test]
558 fn test_verify_sig_all_htlc_nsigs_zero_bypasses_sig_check() {
559 let preimage_bytes = [42u8; 32];
560 let hash = Sha256Hash::hash(&preimage_bytes);
561 let hash_str = hash.to_string();
562
563 let required_pubkey = crate::nuts::nut01::PublicKey::from_hex(
564 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
565 )
566 .unwrap();
567
568 let tags = vec![
570 vec!["pubkeys".to_string(), required_pubkey.to_string()],
571 vec!["n_sigs".to_string(), "0".to_string()],
572 vec!["sigflag".to_string(), "SIG_ALL".to_string()],
573 ];
574
575 let nut10_secret = Nut10Secret::new(Kind::HTLC, SecretData::new(hash_str, Some(tags)));
576
577 let conditions_res = crate::nuts::nut10::Conditions::try_from(
578 nut10_secret.secret_data().tags().cloned().unwrap(),
579 );
580 assert!(
581 conditions_res.is_err(),
582 "Conditions should fail to parse due to n_sigs=0"
583 );
584 }
585
586 #[test]
594 fn test_verify_htlc_wrong_witness_type() {
595 let preimage = "test_preimage";
597 let hash = Sha256Hash::hash(preimage.as_bytes());
598 let hash_str = hash.to_string();
599
600 let nut10_secret = Nut10Secret::new(
601 Kind::HTLC,
602 SecretData::new(hash_str, None::<Vec<Vec<String>>>),
603 );
604 let secret: SecretString = nut10_secret.try_into().unwrap();
605
606 let proof = Proof {
608 amount: crate::Amount::from(1),
609 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
610 secret,
611 c: crate::nuts::nut01::PublicKey::from_hex(
612 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
613 )
614 .unwrap(),
615 witness: Some(Witness::P2PKWitness(super::super::nut11::P2PKWitness {
616 signatures: vec![],
617 })),
618 dleq: None,
619 p2pk_e: None,
620 };
621
622 let result = proof.verify_htlc();
624 assert!(result.is_err());
625 assert!(matches!(result.unwrap_err(), Error::IncorrectSecretKind));
626 }
627
628 #[test]
636 fn test_add_preimage() {
637 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
639 let hash_str = hash.to_string();
640
641 let nut10_secret = Nut10Secret::new(
642 Kind::HTLC,
643 SecretData::new(hash_str, None::<Vec<Vec<String>>>),
644 );
645 let secret: SecretString = nut10_secret.try_into().unwrap();
646
647 let mut proof = Proof {
648 amount: crate::Amount::from(1),
649 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
650 secret,
651 c: crate::nuts::nut01::PublicKey::from_hex(
652 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
653 )
654 .unwrap(),
655 witness: None,
656 dleq: None,
657 p2pk_e: None,
658 };
659
660 assert!(proof.witness.is_none());
662
663 let preimage_hex = hex::encode(preimage_bytes);
665 proof.add_preimage(preimage_hex.clone());
666
667 assert!(proof.witness.is_some());
669 if let Some(Witness::HTLCWitness(witness)) = &proof.witness {
670 assert_eq!(witness.preimage, preimage_hex);
671 } else {
672 panic!("Expected HTLCWitness");
673 }
674
675 assert!(proof.verify_htlc().is_ok());
677 }
678
679 #[test]
687 fn test_htlc_locktime_and_refund_keys_logic() {
688 use crate::nuts::nut01::PublicKey;
689 use crate::nuts::nut10::Conditions;
690
691 let correct_preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&correct_preimage_bytes);
693 let hash_str = hash.to_string();
694
695 let wrong_preimage_bytes = [99u8; 32];
697
698 let refund_pubkey = PublicKey::from_hex(
702 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
703 )
704 .unwrap();
705
706 let conditions_with_refund = Conditions {
707 locktime: Some(1), pubkeys: None,
709 refund_keys: Some(vec![refund_pubkey]), num_sigs: None,
711 sig_flag: crate::nuts::nut11::SigFlag::default(),
712 num_sigs_refund: None,
713 };
714
715 let nut10_secret = Nut10Secret::new(
716 Kind::HTLC,
717 SecretData::new(hash_str, Some(conditions_with_refund)),
718 );
719 let secret: SecretString = nut10_secret.try_into().unwrap();
720
721 let htlc_witness = HTLCWitness {
722 preimage: hex::encode(wrong_preimage_bytes), signatures: None, };
725
726 let proof = Proof {
727 amount: crate::Amount::from(1),
728 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
729 secret,
730 c: crate::nuts::nut01::PublicKey::from_hex(
731 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
732 )
733 .unwrap(),
734 witness: Some(Witness::HTLCWitness(htlc_witness)),
735 dleq: None,
736 p2pk_e: None,
737 };
738
739 let result = proof.verify_htlc();
745 assert!(
746 result.is_err(),
747 "Should fail when using refund path with refund keys but no signature"
748 );
749 }
750
751 #[test]
752 fn test_htlc_generated_empty_refund_keys_are_omitted() {
753 use crate::nuts::nut10::Conditions;
754
755 let preimage_bytes = [42u8; 32];
756 let hash = Sha256Hash::hash(&preimage_bytes);
757 let hash_str = hash.to_string();
758
759 let conditions = Conditions {
760 locktime: Some(1),
761 pubkeys: None,
762 refund_keys: Some(vec![]),
763 num_sigs: None,
764 sig_flag: crate::nuts::nut11::SigFlag::default(),
765 num_sigs_refund: None,
766 };
767
768 let nut10_secret =
769 Nut10Secret::new(Kind::HTLC, SecretData::new(hash_str, Some(conditions)));
770 let secret: SecretString = nut10_secret.try_into().unwrap();
771
772 let htlc_witness = HTLCWitness {
773 preimage: hex::encode([0xffu8; 32]),
774 signatures: None,
775 };
776
777 let proof = Proof {
778 amount: crate::Amount::from(1),
779 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
780 secret,
781 c: crate::nuts::nut01::PublicKey::from_hex(
782 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
783 )
784 .unwrap(),
785 witness: Some(Witness::HTLCWitness(htlc_witness)),
786 dleq: None,
787 p2pk_e: None,
788 };
789
790 assert!(proof.verify_htlc().is_ok());
791 }
792
793 #[test]
794 fn test_htlc_empty_refund_tag_is_rejected() {
795 let preimage_bytes = [42u8; 32];
796 let hash = Sha256Hash::hash(&preimage_bytes);
797 let hash_str = hash.to_string();
798
799 let tags = vec![
800 vec!["locktime".to_string(), "1".to_string()],
801 vec!["refund".to_string()],
802 ];
803
804 let nut10_secret = Nut10Secret::new(Kind::HTLC, SecretData::new(hash_str, Some(tags)));
805 let secret: SecretString = nut10_secret.try_into().unwrap();
806
807 let htlc_witness = HTLCWitness {
808 preimage: hex::encode([0xffu8; 32]),
809 signatures: None,
810 };
811
812 let proof = Proof {
813 amount: crate::Amount::from(1),
814 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
815 secret,
816 c: crate::nuts::nut01::PublicKey::from_hex(
817 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
818 )
819 .unwrap(),
820 witness: Some(Witness::HTLCWitness(htlc_witness)),
821 dleq: None,
822 p2pk_e: None,
823 };
824
825 assert!(matches!(
826 proof.verify_htlc(),
827 Err(Error::SpendConditionsNotMet)
828 ));
829 }
830}