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> {
98 let secret: Secret = self.secret.clone().try_into()?;
99 let spending_conditions: Conditions = secret
100 .secret_data()
101 .tags()
102 .cloned()
103 .unwrap_or_default()
104 .try_into()?;
105
106 if spending_conditions.sig_flag == super::SigFlag::SigAll {
107 return Err(Error::SigAllNotSupportedHere);
108 }
109
110 if secret.kind() != super::Kind::HTLC {
111 return Err(Error::IncorrectSecretKind);
112 }
113
114 let now = unix_time();
116 let requirements =
117 super::nut10::get_pubkeys_and_required_sigs(&secret, now).map_err(Error::NUT11)?;
118
119 if requirements.preimage_needed {
130 let htlc_witness = match &self.witness {
132 Some(Witness::HTLCWitness(witness)) => witness,
133 _ => return Err(Error::IncorrectSecretKind),
134 };
135
136 super::nut10::verify_htlc_preimage(htlc_witness, &secret)?;
138 }
139
140 if requirements.required_sigs == 0 {
141 return Ok(());
142 }
143
144 let htlc_witness = match &self.witness {
150 Some(Witness::HTLCWitness(witness)) => witness,
151 _ => return Err(Error::IncorrectSecretKind),
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
160 .iter()
161 .map(|s| Signature::from_str(s))
162 .collect::<Result<Vec<_>, _>>()?;
163
164 let msg: &[u8] = self.secret.as_bytes();
166 let valid_sig_count = valid_signatures(msg, &requirements.pubkeys, &signatures)?;
167
168 if valid_sig_count >= requirements.required_sigs {
170 Ok(())
171 } else {
172 Err(Error::NUT11(super::nut11::Error::SpendConditionsNotMet))
173 }
174 }
175
176 #[inline]
178 pub fn add_preimage(&mut self, preimage: String) {
179 let signatures = self
180 .witness
181 .as_ref()
182 .map(|w| w.signatures())
183 .unwrap_or_default();
184
185 self.witness = Some(Witness::HTLCWitness(HTLCWitness {
186 preimage,
187 signatures,
188 }))
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use bitcoin::hashes::sha256::Hash as Sha256Hash;
195 use bitcoin::hashes::Hash;
196
197 use super::*;
198 use crate::nuts::nut00::Witness;
199 use crate::nuts::nut10::Kind;
200 use crate::nuts::Nut10Secret;
201 use crate::secret::Secret as SecretString;
202
203 #[test]
211 fn test_verify_htlc_valid() {
212 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
215 let hash_str = hash.to_string();
216
217 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, None::<Vec<Vec<String>>>);
218 let secret: SecretString = nut10_secret.try_into().unwrap();
219
220 let htlc_witness = HTLCWitness {
221 preimage: hex::encode(&preimage_bytes),
222 signatures: None,
223 };
224
225 let proof = Proof {
226 amount: crate::Amount::from(1),
227 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
228 secret,
229 c: crate::nuts::nut01::PublicKey::from_hex(
230 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
231 )
232 .unwrap(),
233 witness: Some(Witness::HTLCWitness(htlc_witness)),
234 dleq: None,
235 };
236
237 assert!(proof.verify_htlc().is_ok());
239 }
240
241 #[test]
250 fn test_verify_htlc_wrong_preimage() {
251 let correct_preimage_bytes = [42u8; 32];
253 let hash = Sha256Hash::hash(&correct_preimage_bytes);
254 let hash_str = hash.to_string();
255
256 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, None::<Vec<Vec<String>>>);
257 let secret: SecretString = nut10_secret.try_into().unwrap();
258
259 let wrong_preimage_bytes = [99u8; 32]; let htlc_witness = HTLCWitness {
262 preimage: hex::encode(&wrong_preimage_bytes),
263 signatures: None,
264 };
265
266 let proof = Proof {
267 amount: crate::Amount::from(1),
268 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
269 secret,
270 c: crate::nuts::nut01::PublicKey::from_hex(
271 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
272 )
273 .unwrap(),
274 witness: Some(Witness::HTLCWitness(htlc_witness)),
275 dleq: None,
276 };
277
278 let result = proof.verify_htlc();
280 assert!(result.is_err());
281 assert!(matches!(result.unwrap_err(), Error::Preimage));
282 }
283
284 #[test]
292 fn test_verify_htlc_invalid_hash() {
293 let invalid_hash = "not_a_valid_hash";
295
296 let nut10_secret = Nut10Secret::new(
297 Kind::HTLC,
298 invalid_hash.to_string(),
299 None::<Vec<Vec<String>>>,
300 );
301 let secret: SecretString = nut10_secret.try_into().unwrap();
302
303 let preimage_bytes = [42u8; 32]; let htlc_witness = HTLCWitness {
305 preimage: hex::encode(&preimage_bytes),
306 signatures: None,
307 };
308
309 let proof = Proof {
310 amount: crate::Amount::from(1),
311 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
312 secret,
313 c: crate::nuts::nut01::PublicKey::from_hex(
314 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
315 )
316 .unwrap(),
317 witness: Some(Witness::HTLCWitness(htlc_witness)),
318 dleq: None,
319 };
320
321 let result = proof.verify_htlc();
323 assert!(result.is_err());
324 assert!(matches!(result.unwrap_err(), Error::InvalidHash));
325 }
326
327 #[test]
335 fn test_verify_htlc_wrong_witness_type() {
336 let preimage = "test_preimage";
338 let hash = Sha256Hash::hash(preimage.as_bytes());
339 let hash_str = hash.to_string();
340
341 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, None::<Vec<Vec<String>>>);
342 let secret: SecretString = nut10_secret.try_into().unwrap();
343
344 let proof = Proof {
346 amount: crate::Amount::from(1),
347 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
348 secret,
349 c: crate::nuts::nut01::PublicKey::from_hex(
350 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
351 )
352 .unwrap(),
353 witness: Some(Witness::P2PKWitness(super::super::nut11::P2PKWitness {
354 signatures: vec![],
355 })),
356 dleq: None,
357 };
358
359 let result = proof.verify_htlc();
361 assert!(result.is_err());
362 assert!(matches!(result.unwrap_err(), Error::IncorrectSecretKind));
363 }
364
365 #[test]
373 fn test_add_preimage() {
374 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
376 let hash_str = hash.to_string();
377
378 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, None::<Vec<Vec<String>>>);
379 let secret: SecretString = nut10_secret.try_into().unwrap();
380
381 let mut proof = Proof {
382 amount: crate::Amount::from(1),
383 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
384 secret,
385 c: crate::nuts::nut01::PublicKey::from_hex(
386 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
387 )
388 .unwrap(),
389 witness: None,
390 dleq: None,
391 };
392
393 assert!(proof.witness.is_none());
395
396 let preimage_hex = hex::encode(&preimage_bytes);
398 proof.add_preimage(preimage_hex.clone());
399
400 assert!(proof.witness.is_some());
402 if let Some(Witness::HTLCWitness(witness)) = &proof.witness {
403 assert_eq!(witness.preimage, preimage_hex);
404 } else {
405 panic!("Expected HTLCWitness");
406 }
407
408 assert!(proof.verify_htlc().is_ok());
410 }
411
412 #[test]
420 fn test_htlc_locktime_and_refund_keys_logic() {
421 use crate::nuts::nut01::PublicKey;
422 use crate::nuts::nut11::Conditions;
423
424 let preimage_bytes = [42u8; 32]; let hash = Sha256Hash::hash(&preimage_bytes);
426 let hash_str = hash.to_string();
427
428 let refund_pubkey = PublicKey::from_hex(
434 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
435 )
436 .unwrap();
437
438 let conditions_with_refund = Conditions {
439 locktime: Some(1), pubkeys: None,
441 refund_keys: Some(vec![refund_pubkey]), num_sigs: None,
443 sig_flag: crate::nuts::nut11::SigFlag::default(),
444 num_sigs_refund: None,
445 };
446
447 let nut10_secret = Nut10Secret::new(Kind::HTLC, hash_str, Some(conditions_with_refund));
448 let secret: SecretString = nut10_secret.try_into().unwrap();
449
450 let htlc_witness = HTLCWitness {
451 preimage: hex::encode(&preimage_bytes),
452 signatures: None, };
454
455 let proof = Proof {
456 amount: crate::Amount::from(1),
457 keyset_id: crate::nuts::nut02::Id::from_str("00deadbeef123456").unwrap(),
458 secret,
459 c: crate::nuts::nut01::PublicKey::from_hex(
460 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
461 )
462 .unwrap(),
463 witness: Some(Witness::HTLCWitness(htlc_witness)),
464 dleq: None,
465 };
466
467 let result = proof.verify_htlc();
471 assert!(
472 result.is_err(),
473 "Should fail when locktime passed but refund keys present without signature"
474 );
475 }
476}