Skip to main content

ark/vtxo/policy/
clause.rs

1
2use bitcoin::absolute::LockTime;
3use bitcoin::hashes::sha256;
4use bitcoin::key::constants::SCHNORR_SIGNATURE_SIZE;
5use bitcoin::secp256k1::schnorr;
6use bitcoin::taproot::{self, ControlBlock};
7use bitcoin::{Sequence, VarInt, Witness};
8use bitcoin::{secp256k1::PublicKey, ScriptBuf};
9
10use bitcoin_ext::{BlockDelta, BlockHeight};
11
12use crate::lightning::{PaymentHash, Preimage};
13use crate::vtxo::policy::Policy;
14use crate::Vtxo;
15use crate::scripts;
16
17/// A trait describing a VTXO policy clause.
18///
19/// It can be used when creating the VTXO, specifying the script pubkey,
20/// and check the satisfaction weight when spending it.
21pub trait TapScriptClause: Sized + Clone {
22	/// The type of witness data required to sign the clause.
23	type WitnessData;
24
25	/// Returns the tapscript for the clause.
26	fn tapscript(&self) -> ScriptBuf;
27
28	/// Construct the taproot control block for spending the VTXO using this clause
29	fn control_block<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> ControlBlock {
30		vtxo.output_taproot()
31			.control_block(&(self.tapscript(), taproot::LeafVersion::TapScript))
32			.expect("clause is not in taproot tree")
33	}
34
35	/// Computes the total witness size in bytes for spending via this clause.
36	///
37	/// Implementations sum bounded components and so cannot overflow `usize`
38	/// on any supported platform:
39	///   tapscript_size: <= MAX_STANDARD_TAPSCRIPT_SIZE (10_000 bytes)
40	///   cb_size: <= TAPROOT_CONTROL_MAX_SIZE (33 + 32*128 = 4_129 bytes)
41	///   VarInt sizes: <= 9 bytes each
42	///   constants: <= 1 + 1 + 64 (+ 1 + 32 for hash variants)
43	/// Sum is on the order of ~14 KB, well below u32::MAX, so the
44	/// usize addition cannot overflow even when usize is 32 bits.
45	fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize;
46
47	/// Constructs the witness for the clause.
48	fn witness(
49		&self,
50		data: &Self::WitnessData,
51		control_block: &ControlBlock,
52	) -> Witness;
53}
54
55/// A clause that allows to sign and spend the UTXO after a relative
56/// timelock.
57#[derive(Debug, Clone)]
58pub struct DelayedSignClause {
59	pub pubkey: PublicKey,
60	pub block_delta: BlockDelta,
61}
62
63impl DelayedSignClause {
64	/// Returns the input sequence value for this clause.
65	pub fn sequence(&self) -> Sequence {
66		Sequence::from_height(self.block_delta)
67	}
68}
69
70impl TapScriptClause for DelayedSignClause {
71	type WitnessData = schnorr::Signature;
72
73	fn tapscript(&self) -> ScriptBuf {
74		scripts::delayed_sign(self.block_delta, self.pubkey.x_only_public_key().0)
75	}
76
77	fn witness(
78		&self,
79		signature: &Self::WitnessData,
80		control_block: &ControlBlock,
81	) -> Witness {
82		Witness::from_slice(&[
83			&signature[..],
84			self.tapscript().as_bytes(),
85			&control_block.serialize()[..],
86		])
87	}
88
89
90	// See `TapScriptClause::witness_size` for the overflow analysis.
91	#[allow(clippy::arithmetic_side_effects)]
92	fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize {
93		let cb_size = self.control_block(vtxo).size();
94		let tapscript_size = self.tapscript().as_bytes().len();
95
96		1 // byte for the number of witness elements
97		+ 1  // schnorr signature size byte
98		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
99		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
100		+ tapscript_size // tapscript bytes
101		+ VarInt::from(cb_size).size()  // control block size bytes
102		+ cb_size // control block bytes
103	}
104}
105
106impl Into<VtxoClause> for DelayedSignClause {
107	fn into(self) -> VtxoClause {
108		VtxoClause::DelayedSign(self)
109	}
110}
111
112/// A clause that allows to sign and spend the UTXO after an absolute
113/// timelock.
114#[derive(Debug, Clone)]
115pub struct TimelockSignClause {
116	pub pubkey: PublicKey,
117	pub timelock_height: BlockHeight,
118}
119
120impl TimelockSignClause {
121	/// Returns the absolute locktime for this clause.
122	pub fn locktime(&self) -> LockTime {
123		LockTime::from_height(self.timelock_height).expect("timelock height is valid")
124	}
125}
126
127impl TapScriptClause for TimelockSignClause {
128	type WitnessData = schnorr::Signature;
129
130	fn tapscript(&self) -> ScriptBuf {
131		scripts::timelock_sign(self.timelock_height, self.pubkey.x_only_public_key().0)
132	}
133
134	fn witness(
135		&self,
136		signature: &Self::WitnessData,
137		control_block: &ControlBlock,
138	) -> Witness {
139		Witness::from_slice(&[
140			&signature[..],
141			self.tapscript().as_bytes(),
142			&control_block.serialize()[..],
143		])
144	}
145
146	// See `TapScriptClause::witness_size` for the overflow analysis.
147	#[allow(clippy::arithmetic_side_effects)]
148	fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize {
149		let cb_size = self.control_block(vtxo).size();
150		let tapscript_size = self.tapscript().as_bytes().len();
151
152		1 // byte for the number of witness elements
153		+ 1  // schnorr signature size byte
154		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
155		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
156		+ tapscript_size // tapscript bytes
157		+ VarInt::from(cb_size).size()  // control block size bytes
158		+ cb_size // control block bytes
159	}
160}
161
162impl Into<VtxoClause> for TimelockSignClause {
163	fn into(self) -> VtxoClause {
164		VtxoClause::TimelockSign(self)
165	}
166}
167
168/// A clause that allows to sign and spend the UTXO after a relative
169/// timelock, with an additional absolute one.
170#[derive(Debug, Clone)]
171pub struct DelayedTimelockSignClause {
172	pub pubkey: PublicKey,
173	pub timelock_height: BlockHeight,
174	pub block_delta: BlockDelta,
175}
176
177impl DelayedTimelockSignClause {
178	/// Returns the input sequence for this clause.
179	pub fn sequence(&self) -> Sequence {
180		Sequence::from_height(self.block_delta)
181	}
182
183	/// Returns the absolute locktime for this clause.
184	pub fn locktime(&self) -> LockTime {
185		LockTime::from_height(self.timelock_height).expect("timelock height is valid")
186	}
187}
188
189impl TapScriptClause for DelayedTimelockSignClause {
190	type WitnessData = schnorr::Signature;
191
192	fn tapscript(&self) -> ScriptBuf {
193		scripts::delay_timelock_sign(
194			self.block_delta,
195			self.timelock_height,
196			self.pubkey.x_only_public_key().0,
197		)
198	}
199
200	fn witness(
201		&self,
202		signature: &Self::WitnessData,
203		control_block: &ControlBlock,
204	) -> Witness {
205		Witness::from_slice(&[
206			&signature[..],
207			self.tapscript().as_bytes(),
208			&control_block.serialize()[..],
209		])
210	}
211
212	// See `TapScriptClause::witness_size` for the overflow analysis.
213	#[allow(clippy::arithmetic_side_effects)]
214	fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize {
215		let cb_size = self.control_block(vtxo).size();
216		let tapscript_size = self.tapscript().as_bytes().len();
217
218		1 // byte for the number of witness elements
219		+ 1  // schnorr signature size byte
220		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
221		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
222		+ tapscript_size // tapscript bytes
223		+ VarInt::from(cb_size).size()  // control block size bytes
224		+ cb_size // control block bytes
225	}
226}
227
228impl Into<VtxoClause> for DelayedTimelockSignClause {
229	fn into(self) -> VtxoClause {
230		VtxoClause::DelayedTimelockSign(self)
231	}
232}
233
234/// A clause that allows to sign and spend the UTXO after a relative
235/// timelock, if preimage matching the hash is provided.
236#[derive(Debug, Clone)]
237pub struct HashDelaySignClause {
238	pub pubkey: PublicKey,
239	pub hash: sha256::Hash,
240	pub block_delta: BlockDelta,
241}
242
243impl HashDelaySignClause {
244	/// Returns the input sequence for this clause.
245	pub fn sequence(&self) -> Sequence {
246		Sequence::from_height(self.block_delta)
247	}
248
249	/// Try to extract the preimage from a witness that spends this clause.
250	///
251	/// Witness layout: `[signature, preimage, tapscript, control_block]`.
252	/// Returns the preimage if it is 32 bytes and hashes to the given payment hash.
253	pub fn extract_preimage_from_witness(
254		witness: &Witness,
255		payment_hash: PaymentHash,
256	) -> Option<Preimage> {
257		if witness.len() != 4 {
258			return None;
259		}
260
261		let bytes = witness.nth(1)?;
262		let bytes: [u8; 32] = bytes.try_into().ok()?;
263
264		let preimage = Preimage::from(bytes);
265		if preimage.compute_payment_hash() != payment_hash {
266			return None;
267		}
268
269		Some(preimage)
270	}
271}
272
273impl TapScriptClause for HashDelaySignClause {
274	type WitnessData = (schnorr::Signature, [u8; 32]);
275
276	fn tapscript(&self) -> ScriptBuf {
277		scripts::hash_delay_sign(
278			self.hash,
279			self.block_delta,
280			self.pubkey.x_only_public_key().0,
281		)
282	}
283
284	fn witness(
285		&self,
286		data: &Self::WitnessData,
287		control_block: &ControlBlock,
288	) -> Witness {
289		let (signature, preimage) = data;
290		Witness::from_slice(&[
291			&signature[..],
292			&preimage[..],
293			self.tapscript().as_bytes(),
294			&control_block.serialize()[..],
295		])
296	}
297
298	// See `TapScriptClause::witness_size` for the overflow analysis.
299	#[allow(clippy::arithmetic_side_effects)]
300	fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize {
301		let cb_size = self.control_block(vtxo).size();
302		let tapscript_size = self.tapscript().as_bytes().len();
303
304		1 // byte for the number of witness elements
305		+ 1  // schnorr signature size byte
306		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
307		+ 1  // preimage size byte
308		+ 32 // preimage bytes
309		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
310		+ tapscript_size // tapscript bytes
311		+ VarInt::from(cb_size).size()  // control block size bytes
312		+ cb_size // control block bytes
313	}
314}
315
316impl Into<VtxoClause> for HashDelaySignClause {
317	fn into(self) -> VtxoClause {
318		VtxoClause::HashDelaySign(self)
319	}
320}
321
322/// A clause that allows to sign and spend the UTXO after a relative
323/// timelock, if preimage matching the hash is provided.
324#[derive(Debug, Clone)]
325#[allow(non_camel_case_types)]
326pub struct HashDelaySignClause_v0 {
327	pub pubkey: PublicKey,
328	pub hash: sha256::Hash,
329	pub block_delta: BlockDelta,
330}
331
332impl HashDelaySignClause_v0 {
333	/// Returns the input sequence for this clause.
334	pub fn sequence(&self) -> Sequence {
335		Sequence::from_height(self.block_delta)
336	}
337
338	/// Try to extract the preimage from a witness that spends this clause.
339	///
340	/// Witness layout: `[signature, preimage, tapscript, control_block]`.
341	/// Returns the preimage if it is 32 bytes and hashes to the given payment hash.
342	pub fn extract_preimage_from_witness(
343		witness: &Witness,
344		payment_hash: PaymentHash,
345	) -> Option<Preimage> {
346		if witness.len() != 4 {
347			return None;
348		}
349
350		let bytes = witness.nth(1)?;
351		let bytes: [u8; 32] = bytes.try_into().ok()?;
352
353		let preimage = Preimage::from(bytes);
354		if preimage.compute_payment_hash() != payment_hash {
355			return None;
356		}
357
358		Some(preimage)
359	}
360}
361
362impl TapScriptClause for HashDelaySignClause_v0 {
363	type WitnessData = (schnorr::Signature, [u8; 32]);
364
365	fn tapscript(&self) -> ScriptBuf {
366		scripts::hash_delay_sign_v0(
367			self.hash,
368			self.block_delta,
369			self.pubkey.x_only_public_key().0,
370		)
371	}
372
373	fn witness(
374		&self,
375		data: &Self::WitnessData,
376		control_block: &ControlBlock,
377	) -> Witness {
378		let (signature, preimage) = data;
379		Witness::from_slice(&[
380			&signature[..],
381			&preimage[..],
382			self.tapscript().as_bytes(),
383			&control_block.serialize()[..],
384		])
385	}
386
387	// See `TapScriptClause::witness_size` for the overflow analysis.
388	#[allow(clippy::arithmetic_side_effects)]
389	fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize {
390		let cb_size = self.control_block(vtxo).size();
391		let tapscript_size = self.tapscript().as_bytes().len();
392
393		1 // byte for the number of witness elements
394		+ 1  // schnorr signature size byte
395		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
396		+ 1  // preimage size byte
397		+ 32 // preimage bytes
398		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
399		+ tapscript_size // tapscript bytes
400		+ VarInt::from(cb_size).size()  // control block size bytes
401		+ cb_size // control block bytes
402	}
403}
404
405impl Into<VtxoClause> for HashDelaySignClause_v0 {
406	fn into(self) -> VtxoClause {
407		VtxoClause::HashDelaySign_v0(self)
408	}
409}
410
411/// A clause that allows spending by revealing a preimage and providing a signature.
412///
413/// This is used for the unlock clause in hArk leaf outputs, where the aggregate
414/// pubkey of user+server must sign, and a preimage must be revealed.
415#[derive(Debug, Clone)]
416pub struct HashSignClause {
417	pub pubkey: PublicKey,
418	pub hash: sha256::Hash,
419}
420
421impl TapScriptClause for HashSignClause {
422	type WitnessData = (schnorr::Signature, [u8; 32]);
423
424	fn tapscript(&self) -> ScriptBuf {
425		scripts::hash_and_sign(self.hash, self.pubkey.x_only_public_key().0)
426	}
427
428	fn witness(
429		&self,
430		data: &Self::WitnessData,
431		control_block: &ControlBlock,
432	) -> Witness {
433		let (signature, preimage) = data;
434		Witness::from_slice(&[
435			&signature[..],
436			&preimage[..],
437			self.tapscript().as_bytes(),
438			&control_block.serialize()[..],
439		])
440	}
441
442	// See `TapScriptClause::witness_size` for the overflow analysis.
443	#[allow(clippy::arithmetic_side_effects)]
444	fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize {
445		let cb_size = self.control_block(vtxo).size();
446		let tapscript_size = self.tapscript().as_bytes().len();
447
448		1 // byte for the number of witness elements
449		+ 1  // schnorr signature size byte
450		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
451		+ 1  // preimage size byte
452		+ 32 // preimage bytes
453		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
454		+ tapscript_size // tapscript bytes
455		+ VarInt::from(cb_size).size()  // control block size bytes
456		+ cb_size // control block bytes
457	}
458}
459
460impl Into<VtxoClause> for HashSignClause {
461	fn into(self) -> VtxoClause {
462		VtxoClause::HashSign(self)
463	}
464}
465
466/// A clause that allows spending by revealing a preimage and providing a signature.
467///
468/// This is used for the unlock clause in hArk leaf outputs, where the aggregate
469/// pubkey of user+server must sign, and a preimage must be revealed.
470#[derive(Debug, Clone)]
471#[allow(non_camel_case_types)]
472pub struct HashSignClause_v0 {
473	pub pubkey: PublicKey,
474	pub hash: sha256::Hash,
475}
476
477impl TapScriptClause for HashSignClause_v0 {
478	type WitnessData = (schnorr::Signature, [u8; 32]);
479
480	fn tapscript(&self) -> ScriptBuf {
481		scripts::hash_and_sign_v0(self.hash, self.pubkey.x_only_public_key().0)
482	}
483
484	fn witness(
485		&self,
486		data: &Self::WitnessData,
487		control_block: &ControlBlock,
488	) -> Witness {
489		let (signature, preimage) = data;
490		Witness::from_slice(&[
491			&signature[..],
492			&preimage[..],
493			self.tapscript().as_bytes(),
494			&control_block.serialize()[..],
495		])
496	}
497
498	// See `TapScriptClause::witness_size` for the overflow analysis.
499	#[allow(clippy::arithmetic_side_effects)]
500	fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize {
501		let cb_size = self.control_block(vtxo).size();
502		let tapscript_size = 57;
503
504		debug_assert_eq!(tapscript_size, self.tapscript().as_bytes().len());
505
506		1 // byte for the number of witness elements
507		+ 1  // schnorr signature size byte
508		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
509		+ 1  // preimage size byte
510		+ 32 // preimage bytes
511		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
512		+ tapscript_size // tapscript bytes
513		+ VarInt::from(cb_size).size()  // control block size bytes
514		+ cb_size // control block bytes
515	}
516}
517
518impl Into<VtxoClause> for HashSignClause_v0 {
519	fn into(self) -> VtxoClause {
520		VtxoClause::HashSign_v0(self)
521	}
522}
523
524#[derive(Debug, Clone)]
525pub enum VtxoClause {
526	DelayedSign(DelayedSignClause),
527	TimelockSign(TimelockSignClause),
528	DelayedTimelockSign(DelayedTimelockSignClause),
529	HashDelaySign(HashDelaySignClause),
530	HashSign(HashSignClause),
531	#[allow(non_camel_case_types)]
532	HashDelaySign_v0(HashDelaySignClause_v0),
533	#[allow(non_camel_case_types)]
534	HashSign_v0(HashSignClause_v0),
535}
536
537impl VtxoClause {
538	/// Returns the public key associated with this clause.
539	pub fn pubkey(&self) -> PublicKey {
540		match self {
541			Self::DelayedSign(c) => c.pubkey,
542			Self::TimelockSign(c) => c.pubkey,
543			Self::DelayedTimelockSign(c) => c.pubkey,
544			Self::HashDelaySign(c) => c.pubkey,
545			Self::HashSign(c) => c.pubkey,
546			Self::HashDelaySign_v0(c) => c.pubkey,
547			Self::HashSign_v0(c) => c.pubkey,
548		}
549	}
550
551
552	/// Returns the tapscript for this clause.
553	pub fn tapscript(&self) -> ScriptBuf {
554		match self {
555			Self::DelayedSign(c) => c.tapscript(),
556			Self::TimelockSign(c) => c.tapscript(),
557			Self::DelayedTimelockSign(c) => c.tapscript(),
558			Self::HashDelaySign(c) => c.tapscript(),
559			Self::HashSign(c) => c.tapscript(),
560			Self::HashDelaySign_v0(c) => c.tapscript(),
561			Self::HashSign_v0(c) => c.tapscript(),
562		}
563	}
564
565	/// Returns the input sequence for this clause, if applicable.
566	pub fn sequence(&self) -> Option<Sequence> {
567		match self {
568			Self::DelayedSign(c) => Some(c.sequence()),
569			Self::TimelockSign(_) => None,
570			Self::DelayedTimelockSign(c) => Some(c.sequence()),
571			Self::HashDelaySign(c) => Some(c.sequence()),
572			Self::HashSign(_) => None,
573			Self::HashDelaySign_v0(c) => Some(c.sequence()),
574			Self::HashSign_v0(_) => None,
575		}
576	}
577
578	/// Computes the total witness size in bytes for spending the VTXO via this clause.
579	pub fn control_block<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> ControlBlock {
580		match self {
581			Self::DelayedSign(c) => c.control_block(vtxo),
582			Self::TimelockSign(c) => c.control_block(vtxo),
583			Self::DelayedTimelockSign(c) => c.control_block(vtxo),
584			Self::HashDelaySign(c) => c.control_block(vtxo),
585			Self::HashSign(c) => c.control_block(vtxo),
586			Self::HashDelaySign_v0(c) => c.control_block(vtxo),
587			Self::HashSign_v0(c) => c.control_block(vtxo),
588		}
589	}
590
591	/// Computes the total witness size in bytes for spending the VTXO via this clause.
592	pub fn witness_size<G, P: Policy>(&self, vtxo: &Vtxo<G, P>) -> usize {
593		match self {
594			Self::DelayedSign(c) => c.witness_size(vtxo),
595			Self::TimelockSign(c) => c.witness_size(vtxo),
596			Self::DelayedTimelockSign(c) => c.witness_size(vtxo),
597			Self::HashDelaySign(c) => c.witness_size(vtxo),
598			Self::HashSign(c) => c.witness_size(vtxo),
599			Self::HashDelaySign_v0(c) => c.witness_size(vtxo),
600			Self::HashSign_v0(c) => c.witness_size(vtxo),
601		}
602	}
603}
604
605#[cfg(test)]
606mod tests {
607	use std::str::FromStr;
608
609	use bitcoin::taproot::TaprootSpendInfo;
610	use bitcoin::{Amount, OutPoint, Transaction, TxIn, TxOut, Txid, sighash};
611	use bitcoin::hashes::Hash;
612	use bitcoin::key::Keypair;
613	use bitcoin_ext::{TaprootSpendInfoExt, fee};
614
615	use crate::{SECP, musig};
616	use crate::test_util::verify_tx;
617
618	use super::*;
619
620	lazy_static! {
621		static ref USER_KEYPAIR: Keypair = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
622		static ref SERVER_KEYPAIR: Keypair = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
623	}
624
625	#[allow(unused)]
626	fn all_clause_tested(clause: VtxoClause) -> bool {
627		// NB: matcher to ensure all clauses are tested
628		match clause {
629			VtxoClause::DelayedSign(_) => true,
630			VtxoClause::TimelockSign(_) => true,
631			VtxoClause::DelayedTimelockSign(_) => true,
632			VtxoClause::HashDelaySign(_) => true,
633			VtxoClause::HashSign(_) => true,
634			VtxoClause::HashDelaySign_v0(_) => true,
635			VtxoClause::HashSign_v0(_) => true,
636		}
637	}
638
639	fn transaction() -> Transaction {
640		let address = bitcoin::Address::from_str("tb1q00h5delzqxl7xae8ufmsegghcl4jwfvdnd8530")
641			.unwrap().assume_checked();
642
643		Transaction {
644			version: bitcoin::transaction::Version(3),
645			lock_time: bitcoin::absolute::LockTime::ZERO,
646			input: vec![],
647			output: vec![TxOut {
648				script_pubkey: address.script_pubkey(),
649				value: Amount::from_sat(900_000),
650			}, fee::fee_anchor()]
651		}
652	}
653
654	fn taproot_material(clause_spk: ScriptBuf) -> (TaprootSpendInfo, ControlBlock) {
655		let user_pubkey = USER_KEYPAIR.public_key();
656		let server_pubkey = SERVER_KEYPAIR.public_key();
657
658		let combined_pk = musig::combine_keys([user_pubkey, server_pubkey])
659			.x_only_public_key().0;
660		let taproot = taproot::TaprootBuilder::new()
661			.add_leaf(0, clause_spk.clone()).unwrap()
662			.finalize(&SECP, combined_pk).unwrap();
663
664		let cb = taproot
665			.control_block(&(clause_spk.clone(), taproot::LeafVersion::TapScript))
666			.expect("script is in taproot");
667
668		(taproot, cb)
669	}
670
671	fn signature(tx: &Transaction, input: &TxOut, clause_spk: ScriptBuf) -> schnorr::Signature {
672		let leaf_hash = taproot::TapLeafHash::from_script(
673			&clause_spk,
674			taproot::LeafVersion::TapScript,
675		);
676
677		let mut shc = sighash::SighashCache::new(tx);
678		let sighash = shc.taproot_script_spend_signature_hash(
679			0, &sighash::Prevouts::All(&[input.clone()]), leaf_hash, sighash::TapSighashType::Default,
680		).expect("all prevouts provided");
681
682		SECP.sign_schnorr(&sighash.into(), &*USER_KEYPAIR)
683	}
684
685	#[test]
686	fn test_delayed_sign_clause() {
687		let clause = DelayedSignClause {
688			pubkey: USER_KEYPAIR.public_key(),
689			block_delta: 100,
690		};
691
692		// We compute taproot material for the clause
693		let (taproot, cb) = taproot_material(clause.tapscript());
694		let tx_in = TxOut {
695			script_pubkey: taproot.script_pubkey(),
696			value: Amount::from_sat(1_000_000),
697		};
698
699		// We build transaction spending input containing clause
700		let mut tx = transaction();
701		tx.input.push(TxIn {
702			previous_output: OutPoint::new(Txid::all_zeros(), 0),
703			script_sig: ScriptBuf::default(),
704			sequence: clause.sequence(),
705			witness: Witness::new(),
706		});
707
708		// We compute the signature for the transaction
709		let signature = signature(&tx, &tx_in, clause.tapscript());
710		tx.input[0].witness = clause.witness(&signature, &cb);
711
712		// We verify the transaction
713		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
714	}
715
716	#[test]
717	fn test_timelock_sign_clause() {
718		let clause = TimelockSignClause {
719			pubkey: USER_KEYPAIR.public_key(),
720			timelock_height: 100,
721		};
722
723		// We compute taproot material for the clause
724		let (taproot, cb) = taproot_material(clause.tapscript());
725		let tx_in = TxOut {
726			script_pubkey: taproot.script_pubkey(),
727			value: Amount::from_sat(1_000_000),
728		};
729
730		// We build transaction spending input containing clause
731		let mut tx = transaction();
732		tx.lock_time = clause.locktime();
733		tx.input.push(TxIn {
734			previous_output: OutPoint::new(Txid::all_zeros(), 0),
735			script_sig: ScriptBuf::default(),
736			sequence: Sequence::ZERO,
737			witness: Witness::new(),
738		});
739
740		// We compute the signature for the transaction
741		let signature = signature(&tx, &tx_in, clause.tapscript());
742		tx.input[0].witness = clause.witness(&signature, &cb);
743
744		// We verify the transaction
745		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
746	}
747
748	#[test]
749	fn test_delayed_timelock_clause() {
750		let clause = DelayedTimelockSignClause {
751			pubkey: USER_KEYPAIR.public_key(),
752			timelock_height: 100,
753			block_delta: 24,
754		};
755
756		// We compute taproot material for the clause
757		let (taproot, cb) = taproot_material(clause.tapscript());
758		let tx_in = TxOut {
759			script_pubkey: taproot.script_pubkey(),
760			value: Amount::from_sat(1_000_000),
761		};
762
763		// We build transaction spending input containing clause
764		let mut tx = transaction();
765		tx.lock_time = clause.locktime();
766		tx.input.push(TxIn {
767			previous_output: OutPoint::new(Txid::all_zeros(), 0),
768			script_sig: ScriptBuf::default(),
769			sequence: clause.sequence(),
770			witness: Witness::new(),
771		});
772
773		// We compute the signature for the transaction
774		let signature = signature(&tx, &tx_in, clause.tapscript());
775		tx.input[0].witness = clause.witness(&signature, &cb);
776
777		// We verify the transaction
778		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
779	}
780
781	#[test]
782	fn test_hash_delay_clause() {
783		let preimage = [0; 32];
784
785		let clause = HashDelaySignClause_v0 {
786			pubkey: USER_KEYPAIR.public_key(),
787			hash: sha256::Hash::hash(&preimage),
788			block_delta: 24,
789		};
790
791		// We compute taproot material for the clause
792		let (taproot, cb) = taproot_material(clause.tapscript());
793		let tx_in = TxOut {
794			script_pubkey: taproot.script_pubkey(),
795			value: Amount::from_sat(1_000_000),
796		};
797
798		// We build transaction spending input containing clause
799		let mut tx = transaction();
800		tx.input.push(TxIn {
801			previous_output: OutPoint::new(Txid::all_zeros(), 0),
802			script_sig: ScriptBuf::default(),
803			sequence: clause.sequence(),
804			witness: Witness::new(),
805		});
806
807		// We compute the signature for the transaction
808		let signature = signature(&tx, &tx_in, clause.tapscript());
809		tx.input[0].witness = clause.witness(&(signature, preimage), &cb);
810
811		// We verify the transaction
812		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
813	}
814
815	#[test]
816	fn test_extract_preimage_from_witness() {
817		let preimage_bytes = [42u8; 32];
818		let payment_hash = sha256::Hash::hash(&preimage_bytes);
819
820		let clause = HashDelaySignClause_v0 {
821			pubkey: USER_KEYPAIR.public_key(),
822			hash: payment_hash,
823			block_delta: 24,
824		};
825
826		// Build a valid witness via the clause
827		let (taproot, cb) = taproot_material(clause.tapscript());
828		let tx_in = TxOut {
829			script_pubkey: taproot.script_pubkey(),
830			value: Amount::from_sat(1_000_000),
831		};
832
833		let mut tx = transaction();
834		tx.input.push(TxIn {
835			previous_output: OutPoint::new(Txid::all_zeros(), 0),
836			script_sig: ScriptBuf::default(),
837			sequence: clause.sequence(),
838			witness: Witness::new(),
839		});
840
841		let sig = signature(&tx, &tx_in, clause.tapscript());
842		let witness = clause.witness(&(sig, preimage_bytes), &cb);
843
844		// Extract should succeed with correct payment hash
845		let extracted = HashDelaySignClause_v0::extract_preimage_from_witness(
846			&witness,
847			payment_hash.into(),
848		);
849		assert!(extracted.is_some());
850		assert_eq!(extracted.unwrap().as_ref(), &preimage_bytes);
851
852		// Extract should fail with wrong payment hash
853		let wrong_hash = sha256::Hash::hash(&[0u8; 32]);
854		let extracted = HashDelaySignClause_v0::extract_preimage_from_witness(
855			&witness,
856			wrong_hash.into(),
857		);
858		assert!(extracted.is_none());
859
860		// Extract should fail with wrong witness length
861		let short_witness = Witness::from_slice(&[&sig[..], &preimage_bytes[..]]);
862		let extracted = HashDelaySignClause_v0::extract_preimage_from_witness(
863			&short_witness,
864			payment_hash.into(),
865		);
866		assert!(extracted.is_none());
867	}
868
869	#[test]
870	fn test_hash_sign_clause() {
871		let preimage = [0u8; 32];
872		let hash = sha256::Hash::hash(&preimage);
873
874		// HashSignClause uses an x-only aggregate public key
875		let agg_pk = musig::combine_keys([USER_KEYPAIR.public_key(), SERVER_KEYPAIR.public_key()]);
876
877		let clause = HashSignClause_v0 {
878			pubkey: agg_pk,
879			hash,
880		};
881
882		// We compute taproot material for the clause
883		let (taproot, cb) = taproot_material(clause.tapscript());
884		let tx_in = TxOut {
885			script_pubkey: taproot.script_pubkey(),
886			value: Amount::from_sat(1_000_000),
887		};
888
889		// We build transaction spending input containing clause
890		let mut tx = transaction();
891		tx.input.push(TxIn {
892			previous_output: OutPoint::new(Txid::all_zeros(), 0),
893			script_sig: ScriptBuf::default(),
894			sequence: Sequence::ZERO, // HashSignClause has no relative timelock
895			witness: Witness::new(),
896		});
897
898		// For HashSignClause, we need a MuSig signature from both parties
899		let leaf_hash = taproot::TapLeafHash::from_script(
900			&clause.tapscript(),
901			taproot::LeafVersion::TapScript,
902		);
903
904		let mut shc = sighash::SighashCache::new(&tx);
905		let sighash = shc.taproot_script_spend_signature_hash(
906			0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
907		).expect("all prevouts provided");
908
909		// Create MuSig signature
910		let (user_sec_nonce, user_pub_nonce) = musig::nonce_pair(&*USER_KEYPAIR);
911		let (server_pub_nonce, server_part_sig) = musig::deterministic_partial_sign(
912			&*SERVER_KEYPAIR,
913			[USER_KEYPAIR.public_key()],
914			&[&user_pub_nonce],
915			sighash.to_byte_array(),
916			None,
917		);
918		let agg_nonce = musig::nonce_agg(&[&user_pub_nonce, &server_pub_nonce]);
919
920		let (_user_part_sig, final_sig) = musig::partial_sign(
921			[USER_KEYPAIR.public_key(), SERVER_KEYPAIR.public_key()],
922			agg_nonce,
923			&*USER_KEYPAIR,
924			user_sec_nonce,
925			sighash.to_byte_array(),
926			None,
927			Some(&[&server_part_sig]),
928		);
929		let final_sig = final_sig.expect("should have final signature");
930
931		tx.input[0].witness = clause.witness(&(final_sig, preimage), &cb);
932
933		// We verify the transaction
934		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
935	}
936}