1
2
3use bitcoin::{OutPoint, ScriptBuf, Sequence, TapLeafHash, Transaction, TxIn, TxOut, Witness};
4use bitcoin::hashes::Hash;
5use bitcoin::secp256k1::{schnorr, Keypair, PublicKey};
6use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
7use bitcoin::taproot::{self, LeafVersion, TaprootSpendInfo};
8
9use bitcoin_ext::{fee, TaprootSpendInfoExt, P2TR_DUST};
10
11use crate::{musig, Vtxo, VtxoId, SECP};
12use crate::connectors::ConnectorChain;
13use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
14use crate::tree::signed::{unlock_clause, UnlockHash, UnlockPreimage};
15use crate::vtxo::exit_clause;
16
17
18#[inline]
23pub fn hark_forfeit_claim_taproot(
24 vtxo: &Vtxo,
25 unlock_hash: UnlockHash,
26) -> TaprootSpendInfo {
27 let agg_pk = vtxo.forfeit_agg_pubkey();
28 taproot::TaprootBuilder::new()
29 .add_leaf(1, exit_clause(vtxo.user_pubkey(), vtxo.exit_delta())).unwrap()
30 .add_leaf(1, unlock_clause(agg_pk, unlock_hash)).unwrap()
31 .finalize(&SECP, agg_pk).unwrap()
32}
33
34#[inline]
36pub fn create_hark_forfeit_tx(
37 vtxo: &Vtxo,
38 unlock_hash: UnlockHash,
39 signature: Option<&schnorr::Signature>,
40) -> Transaction {
41 let claim_taproot = hark_forfeit_claim_taproot(vtxo, unlock_hash);
42 Transaction {
43 version: bitcoin::transaction::Version(3),
44 lock_time: bitcoin::absolute::LockTime::ZERO,
45 input: vec![
46 TxIn {
47 previous_output: vtxo.point(),
48 sequence: Sequence::MAX,
49 script_sig: ScriptBuf::new(),
50 witness: signature.map(|s| Witness::from_slice(&[&s[..]])).unwrap_or_default(),
51 },
52 ],
53 output: vec![
54 TxOut {
55 value: vtxo.amount(),
56 script_pubkey: claim_taproot.script_pubkey(),
57 },
58 fee::fee_anchor(),
59 ],
60 }
61}
62
63#[inline]
65pub fn create_hark_forfeit_claim_tx(
66 vtxo: &Vtxo,
67 forfeit_point: OutPoint,
68 unlock_hash: UnlockHash,
69 witness: Option<(&schnorr::Signature, UnlockPreimage)>,
70) -> Transaction {
71 Transaction {
72 version: bitcoin::transaction::Version(3),
73 lock_time: bitcoin::absolute::LockTime::ZERO,
74 input: vec![
75 TxIn {
76 previous_output: forfeit_point,
77 sequence: Sequence::MAX,
78 script_sig: ScriptBuf::new(),
79 witness: witness.map(|(signature, unlock_preimage)| {
80 let taproot = hark_forfeit_claim_taproot(vtxo, unlock_hash);
81 let agg_pk = taproot.internal_key();
82 debug_assert_eq!(agg_pk, vtxo.forfeit_agg_pubkey());
83 let clause = unlock_clause(agg_pk, unlock_hash);
84 let script_leaf = (clause, LeafVersion::TapScript);
85 let cb = taproot.control_block(&script_leaf)
86 .expect("unlock clause not found in hArk forfeit claim taproot");
87 Witness::from_slice(&[
88 &signature.serialize()[..],
89 &unlock_preimage[..],
90 &script_leaf.0.as_bytes(),
91 &cb.serialize()[..],
92 ])
93 }).unwrap_or_default(),
94 },
95 ],
96 output: vec![
97 TxOut {
98 value: vtxo.amount(),
99 script_pubkey: ScriptBuf::new_p2tr(&SECP, vtxo.server_pubkey().into(), None),
100 },
101 fee::fee_anchor(),
102 ],
103 }
104}
105
106#[inline]
107pub fn hark_forfeit_sighash(
108 vtxo: &Vtxo,
109 unlock_hash: UnlockHash,
110) -> (TapSighash, Transaction) {
111 let exit_prevout = vtxo.txout();
112 let tx = create_hark_forfeit_tx(vtxo, unlock_hash, None);
113 let sighash = SighashCache::new(&tx).taproot_key_spend_signature_hash(
114 0, &sighash::Prevouts::All(&[exit_prevout]), TapSighashType::Default,
115 ).expect("sighash error");
116 (sighash, tx)
117}
118
119#[inline]
120pub fn hark_forfeit_claim_sighash(
121 vtxo: &Vtxo,
122 forfeit_point: OutPoint,
123 unlock_hash: UnlockHash,
124) -> (TapSighash, Transaction) {
125 let claim_taproot = hark_forfeit_claim_taproot(vtxo, unlock_hash);
126 let claim_txout = TxOut {
127 script_pubkey: claim_taproot.script_pubkey(),
128 value: vtxo.amount(),
129 };
130 let tx = create_hark_forfeit_claim_tx(vtxo, forfeit_point, unlock_hash, None);
131 let agg_pk = claim_taproot.internal_key();
132 debug_assert_eq!(agg_pk, vtxo.forfeit_agg_pubkey());
133 let clause = unlock_clause(agg_pk, unlock_hash);
134 let leaf = TapLeafHash::from_script(&clause, LeafVersion::TapScript);
135 let sighash = SighashCache::new(&tx).taproot_script_spend_signature_hash(
136 0, &sighash::Prevouts::All(&[claim_txout]), leaf, TapSighashType::Default,
137 ).expect("sighash error");
138 (sighash, tx)
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct HashLockedForfeitNonces {
144 pub forfeit_tx_nonce: musig::PublicNonce,
145 pub forfeit_claim_tx_nonce: musig::PublicNonce,
146}
147
148impl ProtocolEncoding for HashLockedForfeitNonces {
149 fn encode<W: std::io::Write + ?Sized>(&self, w: &mut W) -> Result<(), std::io::Error> {
150 self.forfeit_tx_nonce.encode(w)?;
151 self.forfeit_claim_tx_nonce.encode(w)?;
152 Ok(())
153 }
154
155 fn decode<R: std::io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
156 Ok(Self {
157 forfeit_tx_nonce: ProtocolEncoding::decode(r)?,
158 forfeit_claim_tx_nonce: ProtocolEncoding::decode(r)?,
159 })
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
176pub struct HashLockedForfeitBundle {
177 pub vtxo_id: VtxoId,
178 pub unlock_hash: UnlockHash,
179 pub user_nonces: HashLockedForfeitNonces,
180 pub forfeit_tx_part_sig: musig::PartialSignature,
182 pub forfeit_claim_tx_part_sig: musig::PartialSignature,
184}
185
186impl HashLockedForfeitBundle {
187 pub fn forfeit_vtxo(
189 vtxo: &Vtxo,
190 unlock_hash: UnlockHash,
191 user_key: &Keypair,
192 server_nonces: &HashLockedForfeitNonces,
193 ) -> Self {
194 let vtxo_exit_taproot = vtxo.output_taproot();
195 let (ff_sighash, ff_tx) = hark_forfeit_sighash(vtxo, unlock_hash);
196 let (ff_sec_nonce, ff_pub_nonce) = musig::nonce_pair_with_msg(
197 user_key, &ff_sighash.to_byte_array(),
198 );
199 let ff_agg_nonce = musig::nonce_agg(&[&ff_pub_nonce, &server_nonces.forfeit_tx_nonce]);
200 let ff_point = OutPoint::new(ff_tx.compute_txid(), 0);
201 let (ff_part_sig, _sig) = musig::partial_sign(
202 [vtxo.user_pubkey(), vtxo.server_pubkey()],
203 ff_agg_nonce,
204 user_key,
205 ff_sec_nonce,
206 ff_sighash.to_byte_array(),
207 Some(vtxo_exit_taproot.tap_tweak().to_byte_array()),
208 None,
209 );
210
211 let (claim_sighash, _tx) = hark_forfeit_claim_sighash(vtxo, ff_point, unlock_hash);
212 let (claim_sec_nonce, claim_pub_nonce) = musig::nonce_pair_with_msg(
213 user_key, &claim_sighash.to_byte_array(),
214 );
215 let claim_agg_nonce = musig::nonce_agg(
216 &[&claim_pub_nonce, &server_nonces.forfeit_claim_tx_nonce],
217 );
218 let (claim_part_sig, _sig) = musig::partial_sign(
219 [vtxo.user_pubkey(), vtxo.server_pubkey()],
220 claim_agg_nonce,
221 user_key,
222 claim_sec_nonce,
223 claim_sighash.to_byte_array(),
224 None,
225 None,
226 );
227
228 Self {
229 vtxo_id: vtxo.id(),
230 unlock_hash: unlock_hash,
231 user_nonces: HashLockedForfeitNonces {
232 forfeit_tx_nonce: ff_pub_nonce,
233 forfeit_claim_tx_nonce: claim_pub_nonce,
234 },
235 forfeit_tx_part_sig: ff_part_sig,
236 forfeit_claim_tx_part_sig: claim_part_sig,
237 }
238 }
239
240 pub fn verify(
243 &self,
244 vtxo: &Vtxo,
245 server_nonces: &HashLockedForfeitNonces,
246 ) -> Result<(), &'static str> {
247 if vtxo.id() != self.vtxo_id {
248 return Err("VTXO mismatch");
249 }
250
251 let ff_agg_nonce = musig::nonce_agg(
252 &[&self.user_nonces.forfeit_tx_nonce, &server_nonces.forfeit_tx_nonce],
253 );
254 let vtxo_exit_taproot = vtxo.output_taproot();
255 let (ff_sighash, ff_tx) = hark_forfeit_sighash(vtxo, self.unlock_hash);
256 let (ff_key_agg, _) = musig::tweaked_key_agg(
257 [vtxo.user_pubkey(), vtxo.server_pubkey()],
258 vtxo_exit_taproot.tap_tweak().to_byte_array(),
259 );
260 let ff_point = OutPoint::new(ff_tx.compute_txid(), 0);
261 let ff_session = musig::Session::new(
262 &ff_key_agg,
263 ff_agg_nonce,
264 &ff_sighash.to_byte_array(),
265 );
266 let success = ff_session.partial_verify(
267 &ff_key_agg,
268 &self.forfeit_tx_part_sig,
269 &self.user_nonces.forfeit_tx_nonce,
270 musig::pubkey_to(vtxo.user_pubkey()),
271 );
272 if !success {
273 return Err("invalid partial sig for forfeit tx");
274 }
275
276 let claim_agg_nonce = musig::nonce_agg(
277 &[&self.user_nonces.forfeit_claim_tx_nonce, &server_nonces.forfeit_claim_tx_nonce],
278 );
279 let (claim_sighash, _tx) = hark_forfeit_claim_sighash(vtxo, ff_point, self.unlock_hash);
280 let claim_key_agg = musig::key_agg([vtxo.user_pubkey(), vtxo.server_pubkey()]);
281 let claim_session = musig::Session::new(
282 &claim_key_agg,
283 claim_agg_nonce,
284 &claim_sighash.to_byte_array(),
285 );
286 let success = claim_session.partial_verify(
287 &claim_key_agg,
288 &self.forfeit_claim_tx_part_sig,
289 &self.user_nonces.forfeit_claim_tx_nonce,
290 musig::pubkey_to(vtxo.user_pubkey()),
291 );
292 if !success {
293 return Err("invalid partial sig for forfeit claim tx");
294 }
295 Ok(())
296 }
297
298 pub fn finish(
303 self,
304 vtxo: &Vtxo,
305 server_pub_nonces: &HashLockedForfeitNonces,
306 [ff_sec_nonce, claim_sec_nonce]: [musig::SecretNonce; 2],
307 server_key: &Keypair,
308 ) -> [schnorr::Signature; 2] {
309 assert_eq!(vtxo.id(), self.vtxo_id);
310
311 let ff_agg_nonce = musig::nonce_agg(
312 &[&self.user_nonces.forfeit_tx_nonce, &server_pub_nonces.forfeit_tx_nonce],
313 );
314 let vtxo_exit_taproot = vtxo.output_taproot();
315 let (ff_sighash, ff_tx) = hark_forfeit_sighash(vtxo, self.unlock_hash);
316 let ff_point = OutPoint::new(ff_tx.compute_txid(), 0);
317 let (_ff_part_sig, ff_sig) = musig::partial_sign(
318 [vtxo.user_pubkey(), vtxo.server_pubkey()],
319 ff_agg_nonce,
320 server_key,
321 ff_sec_nonce,
322 ff_sighash.to_byte_array(),
323 Some(vtxo_exit_taproot.tap_tweak().to_byte_array()),
324 Some(&[&self.forfeit_tx_part_sig]),
325 );
326 let ff_sig = ff_sig.expect("forfeit tx sig error");
327 debug_assert!({
328 let (ff_key_agg, _) = musig::tweaked_key_agg(
329 [vtxo.user_pubkey(), vtxo.server_pubkey()],
330 vtxo_exit_taproot.tap_tweak().to_byte_array(),
331 );
332 let ff_session = musig::Session::new(
333 &ff_key_agg,
334 ff_agg_nonce,
335 &ff_sighash.to_byte_array(),
336 );
337 ff_session.partial_verify(
338 &ff_key_agg,
339 &_ff_part_sig,
340 &server_pub_nonces.forfeit_tx_nonce,
341 musig::pubkey_to(vtxo.server_pubkey()),
342 )
343 });
344 debug_assert_eq!(Ok(()), SECP.verify_schnorr(
345 &ff_sig, &ff_sighash.into(), &vtxo_exit_taproot.output_key().to_x_only_public_key(),
346 ));
347
348 let claim_agg_nonce = musig::nonce_agg(
349 &[&self.user_nonces.forfeit_claim_tx_nonce, &server_pub_nonces.forfeit_claim_tx_nonce],
350 );
351 let claim_taproot = hark_forfeit_claim_taproot(vtxo, self.unlock_hash);
352 let (claim_sighash, _tx) = hark_forfeit_claim_sighash(vtxo, ff_point, self.unlock_hash);
353 let (_claim_part_sig, claim_sig) = musig::partial_sign(
354 [vtxo.user_pubkey(), vtxo.server_pubkey()],
355 claim_agg_nonce,
356 server_key,
357 claim_sec_nonce,
358 claim_sighash.to_byte_array(),
359 None,
360 Some(&[&self.forfeit_claim_tx_part_sig]),
361 );
362 let claim_sig = claim_sig.expect("forfeit claim tx sig error");
363 debug_assert!({
364 let claim_key_agg = musig::key_agg([vtxo.user_pubkey(), vtxo.server_pubkey()]);
365 let claim_session = musig::Session::new(
366 &claim_key_agg,
367 claim_agg_nonce,
368 &claim_sighash.to_byte_array(),
369 );
370 claim_session.partial_verify(
371 &claim_key_agg,
372 &_claim_part_sig,
373 &server_pub_nonces.forfeit_claim_tx_nonce,
374 musig::pubkey_to(vtxo.server_pubkey()),
375 )
376 });
377 debug_assert_eq!(Ok(()), SECP.verify_schnorr(
378 &claim_sig, &claim_sighash.into(), &claim_taproot.internal_key(),
379 ));
380
381 [ff_sig, claim_sig]
382 }
383}
384
385const HASH_LOCKED_FORFEIT_BUNDLE_VERSION: u8 = 0x00;
387
388impl ProtocolEncoding for HashLockedForfeitBundle {
389 fn encode<W: std::io::Write + ?Sized>(&self, w: &mut W) -> Result<(), std::io::Error> {
390 w.emit_u8(HASH_LOCKED_FORFEIT_BUNDLE_VERSION)?;
391 self.vtxo_id.encode(w)?;
392 self.unlock_hash.encode(w)?;
393 self.user_nonces.encode(w)?;
394 self.forfeit_tx_part_sig.encode(w)?;
395 self.forfeit_claim_tx_part_sig.encode(w)?;
396 Ok(())
397 }
398
399 fn decode<R: std::io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
400 let ver = r.read_u8()?;
401 if ver != HASH_LOCKED_FORFEIT_BUNDLE_VERSION {
402 return Err(ProtocolDecodingError::invalid("unknown encoding version"));
403 }
404 Ok(Self {
405 vtxo_id: ProtocolEncoding::decode(r)?,
406 unlock_hash: ProtocolEncoding::decode(r)?,
407 user_nonces: ProtocolEncoding::decode(r)?,
408 forfeit_tx_part_sig: ProtocolEncoding::decode(r)?,
409 forfeit_claim_tx_part_sig: ProtocolEncoding::decode(r)?,
410 })
411 }
412}
413
414#[inline]
415pub fn create_connector_forfeit_tx(
416 vtxo: &Vtxo,
417 connector: OutPoint,
418 forfeit_sig: Option<&schnorr::Signature>,
419 connector_sig: Option<&schnorr::Signature>,
420) -> Transaction {
421 Transaction {
422 version: bitcoin::transaction::Version(3),
423 lock_time: bitcoin::absolute::LockTime::ZERO,
424 input: vec![
425 TxIn {
426 previous_output: vtxo.point(),
427 sequence: Sequence::ZERO,
428 script_sig: ScriptBuf::new(),
429 witness: forfeit_sig.map(|s| Witness::from_slice(&[&s[..]])).unwrap_or_default(),
430 },
431 TxIn {
432 previous_output: connector,
433 sequence: Sequence::ZERO,
434 script_sig: ScriptBuf::new(),
435 witness: connector_sig.map(|s| Witness::from_slice(&[&s[..]])).unwrap_or_default(),
436 },
437 ],
438 output: vec![
439 TxOut {
440 value: vtxo.amount(),
441 script_pubkey: ScriptBuf::new_p2tr(&SECP, vtxo.server_pubkey().into(), None),
442 },
443 fee::fee_anchor_with_amount(P2TR_DUST),
446 ],
447 }
448}
449
450#[inline]
451fn connector_forfeit_input_sighash(
452 vtxo: &Vtxo,
453 connector: OutPoint,
454 connector_pk: PublicKey,
455 input_idx: usize,
456) -> (TapSighash, Transaction) {
457 let exit_prevout = vtxo.txout();
458 let connector_prevout = TxOut {
459 script_pubkey: ConnectorChain::output_script(connector_pk),
460 value: P2TR_DUST,
461 };
462 let tx = create_connector_forfeit_tx(vtxo, connector, None, None);
463 let sighash = SighashCache::new(&tx).taproot_key_spend_signature_hash(
464 input_idx,
465 &sighash::Prevouts::All(&[exit_prevout, connector_prevout]),
466 TapSighashType::Default,
467 ).expect("sighash error");
468 (sighash, tx)
469}
470
471#[inline]
473pub fn connector_forfeit_sighash_exit(
474 vtxo: &Vtxo,
475 connector: OutPoint,
476 connector_pk: PublicKey,
477) -> (TapSighash, Transaction) {
478 connector_forfeit_input_sighash(vtxo, connector, connector_pk, 0)
479}
480
481#[inline]
483pub fn connector_forfeit_sighash_connector(
484 vtxo: &Vtxo,
485 connector: OutPoint,
486 connector_pk: PublicKey,
487) -> (TapSighash, Transaction) {
488 connector_forfeit_input_sighash(vtxo, connector, connector_pk, 1)
489}
490
491#[cfg(test)]
492mod test {
493 use bitcoin::hex::{DisplayHex, FromHex};
494 use crate::{test::verify_tx, vtxo::test::VTXO_VECTORS};
495 use super::*;
496
497 fn verify_hark_forfeits(
498 vtxo: &Vtxo,
499 unlock_preimage: UnlockPreimage,
500 server_sec_nonces: [musig::SecretNonce; 2],
501 server_pub_nonces: &HashLockedForfeitNonces,
502 bundle: HashLockedForfeitBundle,
503 ) {
504 let unlock_hash = UnlockHash::hash(&unlock_preimage);
505 assert_eq!(Ok(()), bundle.verify(vtxo, server_pub_nonces));
506
507 let sigs = bundle.finish(vtxo, server_pub_nonces, server_sec_nonces, &VTXO_VECTORS.server_key);
509
510 let (ff_sighash, ff_tx) = hark_forfeit_sighash(vtxo, unlock_hash);
511 SECP.verify_schnorr(
512 &sigs[0],
513 &ff_sighash.into(),
514 &vtxo.output_taproot().output_key().to_x_only_public_key(),
515 ).expect("forfeit tx sig check failed");
516 let ff_point = OutPoint::new(ff_tx.compute_txid(), 0);
517 let claim_taproot = hark_forfeit_claim_taproot(vtxo, unlock_hash);
518 let (claim_sighash, _tx) = hark_forfeit_claim_sighash(vtxo, ff_point, unlock_hash);
519 SECP.verify_schnorr(
520 &sigs[1],
521 &claim_sighash.into(),
522 &claim_taproot.internal_key(),
523 ).expect("forfeit claim tx sig check failed");
524
525 let ff_input = vtxo.txout();
527 let ff_tx = create_hark_forfeit_tx(vtxo, unlock_hash, Some(&sigs[0]));
528 verify_tx(&[ff_input], 0, &ff_tx).expect("forfeit tx error");
529 assert_eq!(ff_tx.compute_txid(), ff_point.txid);
530
531 let claim_input = ff_tx.output[0].clone();
532 let claim_tx = create_hark_forfeit_claim_tx(
533 vtxo, ff_point, unlock_hash, Some((&sigs[1], unlock_preimage)),
534 );
535 verify_tx(&[claim_input], 0, &claim_tx).expect("claim tx error");
536 }
537
538 #[test]
539 fn test_hark_forfeits() {
540 let server_ff_nonces = musig::nonce_pair(&VTXO_VECTORS.server_key);
541 let server_claim_nonces = musig::nonce_pair(&VTXO_VECTORS.server_key);
542 let server_ff_sec_bytes = server_ff_nonces.0.dangerous_into_bytes();
544 let server_claim_sec_bytes = server_claim_nonces.0.dangerous_into_bytes();
545 println!("server ff sec nonce: {}", server_ff_sec_bytes.as_hex());
546 println!("server claim sec nonce: {}", server_claim_sec_bytes.as_hex());
547 let server_sec_nonces = [
548 musig::SecretNonce::dangerous_from_bytes(server_ff_sec_bytes),
549 musig::SecretNonce::dangerous_from_bytes(server_claim_sec_bytes),
550 ];
551 let server_nonces = HashLockedForfeitNonces {
552 forfeit_tx_nonce: server_ff_nonces.1,
553 forfeit_claim_tx_nonce: server_claim_nonces.1,
554 };
555 println!("server pub nonces: {}", server_nonces.serialize_hex());
556
557 let vtxo = &VTXO_VECTORS.arkoor3_vtxo;
558 let unlock_preimage = UnlockPreimage::from_hex("c65f29e65dbc6cbad3e7f35c41986487c74ed513aeb37778354d42f3b0714645").unwrap();
559 let unlock_hash = UnlockHash::hash(&unlock_preimage);
560 let bundle = HashLockedForfeitBundle::forfeit_vtxo(
561 vtxo,
562 unlock_hash,
563 &VTXO_VECTORS.arkoor3_user_key,
564 &server_nonces,
565 );
566
567 let encoded = bundle.serialize();
569 println!("bundle: {}", encoded.as_hex());
570 let decoded = HashLockedForfeitBundle::deserialize(&encoded).unwrap();
571 assert_eq!(bundle, decoded);
572 let bundle = decoded;
573
574 println!("verifying generated forfeits");
575 verify_hark_forfeits(
576 vtxo, unlock_preimage, server_sec_nonces, &server_nonces, bundle.clone(),
577 );
578
579 let (_sec, bad_nonce) = musig::nonce_pair(&VTXO_VECTORS.server_key);
580 assert_eq!(
581 bundle.verify(vtxo, &HashLockedForfeitNonces {
582 forfeit_tx_nonce: server_nonces.forfeit_tx_nonce,
583 forfeit_claim_tx_nonce: bad_nonce,
584 }),
585 Err("invalid partial sig for forfeit claim tx"),
586 );
587 assert_eq!(
588 bundle.verify(vtxo, &HashLockedForfeitNonces {
589 forfeit_tx_nonce: bad_nonce,
590 forfeit_claim_tx_nonce: server_nonces.forfeit_claim_tx_nonce,
591 }),
592 Err("invalid partial sig for forfeit tx"),
593 );
594
595
596 let server_sec_nonces = [
598 musig::SecretNonce::dangerous_from_bytes(FromHex::from_hex("220edcf16a908aa082e1009ec7af0385c6027e39bf95024b8062e7cd4497b640c18690e8512756dc5d30f04c43ab7ebb7e77815119ab7a1113c932e80afc1d58ac701ea2622bf70a8243580d1879746ffe940588c5ad9d478d1b46e2bb9318743312a8657f684b47f963f7a0e95927b2c71005112d8edc5821a3f6f0f7bd6354947ff8ac").unwrap()),
599 musig::SecretNonce::dangerous_from_bytes(FromHex::from_hex("220edcf124c12726825b9615bd47f71c78603cb249752ff6b525cc00d460a9d31895411177c29b1052e2fecae8c791e32b0b5d13117973ab1d7d3fbb2d6ddf66b4a51241622bf70a8243580d1879746ffe940588c5ad9d478d1b46e2bb9318743312a8657f684b47f963f7a0e95927b2c71005112d8edc5821a3f6f0f7bd6354947ff8ac").unwrap()),
600 ];
601 let server_nonces = HashLockedForfeitNonces::deserialize_hex("03e0e0644d80603dadedc1b2ee24f7b8b40e42bcb8e7bd59168eb17ba3afdd03c003ce6d038de9ae7a7a30b6847fb8f3e4b8b80c29f16ea34fb9ae21db87db7231ff020377a71d05a338b778163912ac99d3ebba6248aed0bf06d7b8964bfa8e3c04c802a50b14ad326f3aeccbe6f1ad752ad26d92ffa7e34a0aa67d7c1d449446292b77").unwrap();
602 let bundle = HashLockedForfeitBundle::deserialize_hex("00ff70cc93c752b2cdfa42fef244be8915b087a7e13d9cf6cb24b6443b6a8b87dc000000003d5491373df6a016f78b3f46d65a4fc6948824c43a59620404e8719cfee05d1a03e2f25e2b8414e8b0b313f9571165696c9aafbaca9e084cb7cb4fef7694641f8d035ced88a19e0718e72f12e01763f7e1cf525e1780cb436b5bc25cef17fd0f8a2502317251952a7a5bdf8c37515387e7f68a3343be642eda882b2279d869c3b2dd9d03511337c67552f9c4249ebc1ef225e26515adf9b54a508b93a3cf024d75b38389393bfe384fd0a91b6c8734ec9f37bee9937fb494fa0e5eb08f004208c658f54839454f8d29d0c41c641e9b8dba8d953f680ada737f73462b291281beaa5cd173").unwrap();
603
604 println!("verifying hard-coded forfeits");
605 verify_hark_forfeits(vtxo, unlock_preimage, server_sec_nonces, &server_nonces, bundle);
606 }
607}