1use std::str::FromStr;
6
7use bitcoin::secp256k1::schnorr::Signature;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use super::nut00::Witness;
12use super::nut10::Secret;
13use super::nut11::valid_signatures;
14use super::{Conditions, Proof};
15use crate::util::{hex, unix_time};
16
17pub mod serde_htlc_witness;
18
19#[derive(Debug, Error)]
21pub enum Error {
22 #[error("Secret is not a HTLC secret")]
24 IncorrectSecretKind,
25 #[error("Locktime in past")]
27 LocktimeInPast,
28 #[error("Hash required")]
30 HashRequired,
31 #[error("Hash is not valid")]
33 InvalidHash,
34 #[error("Preimage does not match")]
36 Preimage,
37 #[error("Preimage must be valid hex encoding")]
39 InvalidHexPreimage,
40 #[error("Preimage must be exactly 32 bytes (64 hex characters)")]
42 PreimageInvalidSize,
43 #[error("Witness did not provide signatures")]
45 SignaturesNotProvided,
46 #[error("SIG_ALL proofs must be verified using a different method")]
48 SigAllNotSupportedHere,
49 #[error(transparent)]
51 Secp256k1(#[from] bitcoin::secp256k1::Error),
52 #[error(transparent)]
54 NUT11(#[from] super::nut11::Error),
55 #[error(transparent)]
56 Serde(#[from] serde_json::Error),
58}
59
60#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
62#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
63pub struct HTLCWitness {
64 pub preimage: String,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub signatures: Option<Vec<String>>,
69}
70
71impl HTLCWitness {
72 pub fn preimage_data(&self) -> Result<[u8; 32], Error> {
78 const REQUIRED_PREIMAGE_BYTES: usize = 32;
79
80 let preimage_bytes = hex::decode(&self.preimage).map_err(|_| Error::InvalidHexPreimage)?;
82
83 if preimage_bytes.len() != REQUIRED_PREIMAGE_BYTES {
85 return Err(Error::PreimageInvalidSize);
86 }
87
88 let mut array = [0u8; 32];
90 array.copy_from_slice(&preimage_bytes);
91 Ok(array)
92 }
93}
94
95impl Proof {
96 pub fn verify_htlc(&self) -> Result<(), Error> {
105 let secret: Secret = self.secret.clone().try_into()?;
106 let spending_conditions: Conditions = secret
107 .secret_data()
108 .tags()
109 .cloned()
110 .unwrap_or_default()
111 .try_into()?;
112
113 if spending_conditions.sig_flag == super::SigFlag::SigAll {
114 return Err(Error::SigAllNotSupportedHere);
115 }
116
117 if secret.kind() != super::Kind::HTLC {
118 return Err(Error::IncorrectSecretKind);
119 }
120
121 let now = unix_time();
123 let requirements =
124 super::nut10::get_pubkeys_and_required_sigs(&secret, now).map_err(Error::NUT11)?;
125
126 let htlc_witness = match &self.witness {
128 Some(Witness::HTLCWitness(witness)) => witness,
129 _ => {
130 if let Some(refund_path) = &requirements.refund_path {
133 if refund_path.required_sigs == 0 {
134 return Ok(());
135 }
136 }
137 return Err(Error::IncorrectSecretKind);
138 }
139 };
140
141 let preimage_result = super::nut10::verify_htlc_preimage(htlc_witness, &secret);
143
144 if preimage_result.is_ok() {
148 if requirements.required_sigs == 0 {
150 return Ok(());
151 }
152
153 let witness_signatures = htlc_witness
154 .signatures
155 .as_ref()
156 .ok_or(Error::SignaturesNotProvided)?;
157
158 let signatures: Vec<Signature> = witness_signatures
159 .iter()
160 .map(|s| Signature::from_str(s))
161 .collect::<Result<Vec<_>, _>>()?;
162
163 let msg: &[u8] = self.secret.as_bytes();
164 let valid_sig_count = valid_signatures(msg, &requirements.pubkeys, &signatures)?;
165
166 if valid_sig_count >= requirements.required_sigs {
167 Ok(())
168 } else {
169 Err(Error::NUT11(super::nut11::Error::SpendConditionsNotMet))
170 }
171 } else if let Some(refund_path) = &requirements.refund_path {
172 if refund_path.required_sigs == 0 {
175 return Ok(());
177 }
178
179 let witness_signatures = htlc_witness
180 .signatures
181 .as_ref()
182 .ok_or(Error::SignaturesNotProvided)?;
183
184 let signatures: Vec<Signature> = witness_signatures
185 .iter()
186 .map(|s| Signature::from_str(s))
187 .collect::<Result<Vec<_>, _>>()?;
188
189 let msg: &[u8] = self.secret.as_bytes();
190 let valid_sig_count = valid_signatures(msg, &refund_path.pubkeys, &signatures)?;
191
192 if valid_sig_count >= refund_path.required_sigs {
193 Ok(())
194 } else {
195 Err(Error::NUT11(super::nut11::Error::SpendConditionsNotMet))
196 }
197 } else {
198 preimage_result
201 }
202 }
203
204 #[inline]
206 pub fn add_preimage(&mut self, preimage: String) {
207 let signatures = self
208 .witness
209 .as_ref()
210 .map(super::nut00::Witness::signatures)
211 .unwrap_or_default();
212
213 self.witness = Some(Witness::HTLCWitness(HTLCWitness {
214 preimage,
215 signatures,
216 }))
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use bitcoin::hashes::sha256::Hash as Sha256Hash;
223 use bitcoin::hashes::Hash;
224
225 use super::*;
226 use crate::nuts::nut00::Witness;
227 use crate::nuts::nut10::Kind;
228 use crate::nuts::Nut10Secret;
229 use crate::secret::Secret as SecretString;
230
231 #[test]
239 fn test_verify_htlc_valid() {
240 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
243 let hash_str = hash.to_string();
244
245 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, None::<Vec<Vec<String>>>);
246 let secret: SecretString = nut10_secret.try_into().unwrap();
247
248 let htlc_witness = HTLCWitness {
249 preimage: hex::encode(&preimage_bytes),
250 signatures: None,
251 };
252
253 let proof = Proof {
254 amount: crate::Amount::from(1),
255 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
256 secret,
257 c: crate::nuts::nut01::PublicKey::from_hex(
258 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
259 )
260 .unwrap(),
261 witness: Some(Witness::HTLCWitness(htlc_witness)),
262 dleq: None,
263 };
264
265 assert!(proof.verify_htlc().is_ok());
267 }
268
269 #[test]
278 fn test_verify_htlc_wrong_preimage() {
279 let correct_preimage_bytes = [42u8; 32];
281 let hash = Sha256Hash::hash(&correct_preimage_bytes);
282 let hash_str = hash.to_string();
283
284 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, None::<Vec<Vec<String>>>);
285 let secret: SecretString = nut10_secret.try_into().unwrap();
286
287 let wrong_preimage_bytes = [99u8; 32]; let htlc_witness = HTLCWitness {
290 preimage: hex::encode(&wrong_preimage_bytes),
291 signatures: None,
292 };
293
294 let proof = Proof {
295 amount: crate::Amount::from(1),
296 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
297 secret,
298 c: crate::nuts::nut01::PublicKey::from_hex(
299 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
300 )
301 .unwrap(),
302 witness: Some(Witness::HTLCWitness(htlc_witness)),
303 dleq: None,
304 };
305
306 let result = proof.verify_htlc();
308 assert!(result.is_err());
309 assert!(matches!(result.unwrap_err(), Error::Preimage));
310 }
311
312 #[test]
320 fn test_verify_htlc_invalid_hash() {
321 let invalid_hash = "not_a_valid_hash";
323
324 let nut10_secret = Nut10Secret::new(
325 Kind::HTLC,
326 invalid_hash.to_string(),
327 None::<Vec<Vec<String>>>,
328 );
329 let secret: SecretString = nut10_secret.try_into().unwrap();
330
331 let preimage_bytes = [42u8; 32]; let htlc_witness = HTLCWitness {
333 preimage: hex::encode(&preimage_bytes),
334 signatures: None,
335 };
336
337 let proof = Proof {
338 amount: crate::Amount::from(1),
339 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
340 secret,
341 c: crate::nuts::nut01::PublicKey::from_hex(
342 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
343 )
344 .unwrap(),
345 witness: Some(Witness::HTLCWitness(htlc_witness)),
346 dleq: None,
347 };
348
349 let result = proof.verify_htlc();
351 assert!(result.is_err());
352 assert!(matches!(result.unwrap_err(), Error::InvalidHash));
353 }
354
355 #[test]
363 fn test_verify_htlc_wrong_witness_type() {
364 let preimage = "test_preimage";
366 let hash = Sha256Hash::hash(preimage.as_bytes());
367 let hash_str = hash.to_string();
368
369 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, None::<Vec<Vec<String>>>);
370 let secret: SecretString = nut10_secret.try_into().unwrap();
371
372 let proof = Proof {
374 amount: crate::Amount::from(1),
375 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
376 secret,
377 c: crate::nuts::nut01::PublicKey::from_hex(
378 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
379 )
380 .unwrap(),
381 witness: Some(Witness::P2PKWitness(super::super::nut11::P2PKWitness {
382 signatures: vec![],
383 })),
384 dleq: None,
385 };
386
387 let result = proof.verify_htlc();
389 assert!(result.is_err());
390 assert!(matches!(result.unwrap_err(), Error::IncorrectSecretKind));
391 }
392
393 #[test]
401 fn test_add_preimage() {
402 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
404 let hash_str = hash.to_string();
405
406 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, None::<Vec<Vec<String>>>);
407 let secret: SecretString = nut10_secret.try_into().unwrap();
408
409 let mut proof = Proof {
410 amount: crate::Amount::from(1),
411 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
412 secret,
413 c: crate::nuts::nut01::PublicKey::from_hex(
414 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
415 )
416 .unwrap(),
417 witness: None,
418 dleq: None,
419 };
420
421 assert!(proof.witness.is_none());
423
424 let preimage_hex = hex::encode(&preimage_bytes);
426 proof.add_preimage(preimage_hex.clone());
427
428 assert!(proof.witness.is_some());
430 if let Some(Witness::HTLCWitness(witness)) = &proof.witness {
431 assert_eq!(witness.preimage, preimage_hex);
432 } else {
433 panic!("Expected HTLCWitness");
434 }
435
436 assert!(proof.verify_htlc().is_ok());
438 }
439
440 #[test]
448 fn test_htlc_locktime_and_refund_keys_logic() {
449 use crate::nuts::nut01::PublicKey;
450 use crate::nuts::nut11::Conditions;
451
452 let correct_preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&correct_preimage_bytes);
454 let hash_str = hash.to_string();
455
456 let wrong_preimage_bytes = [99u8; 32];
458
459 let refund_pubkey = PublicKey::from_hex(
463 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
464 )
465 .unwrap();
466
467 let conditions_with_refund = Conditions {
468 locktime: Some(1), pubkeys: None,
470 refund_keys: Some(vec![refund_pubkey]), num_sigs: None,
472 sig_flag: crate::nuts::nut11::SigFlag::default(),
473 num_sigs_refund: None,
474 };
475
476 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, Some(conditions_with_refund));
477 let secret: SecretString = nut10_secret.try_into().unwrap();
478
479 let htlc_witness = HTLCWitness {
480 preimage: hex::encode(&wrong_preimage_bytes), signatures: None, };
483
484 let proof = Proof {
485 amount: crate::Amount::from(1),
486 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
487 secret,
488 c: crate::nuts::nut01::PublicKey::from_hex(
489 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
490 )
491 .unwrap(),
492 witness: Some(Witness::HTLCWitness(htlc_witness)),
493 dleq: None,
494 };
495
496 let result = proof.verify_htlc();
502 assert!(
503 result.is_err(),
504 "Should fail when using refund path with refund keys but no signature"
505 );
506 }
507}