ark/vtxo/policy/
clause.rs

1
2use bitcoin::absolute::LockTime;
3use bitcoin::key::constants::SCHNORR_SIGNATURE_SIZE;
4use bitcoin::secp256k1::schnorr;
5use bitcoin::taproot::{self, ControlBlock};
6use bitcoin::{Sequence, VarInt, Witness};
7use bitcoin::{secp256k1::PublicKey, ScriptBuf};
8
9use bitcoin_ext::{BlockDelta, BlockHeight};
10
11use crate::lightning::{PREIMAGE_SIZE, PaymentHash, Preimage};
12use crate::{Vtxo, scripts};
13
14/// A trait describing a VTXO policy clause.
15///
16/// It can be used when creating the VTXO, specifying the script pubkey,
17/// and check the satisfaction weight when spending it.
18pub trait TapScriptClause: Sized + Clone {
19	/// The type of witness data required to sign the clause.
20	type WitnessData;
21
22	/// Returns the tapscript for the clause.
23	fn tapscript(&self) -> ScriptBuf;
24
25	/// Construct the taproot control block for spending the VTXO using this clause
26	fn control_block(&self, vtxo: &Vtxo) -> ControlBlock {
27		vtxo.output_taproot()
28			.control_block(&(self.tapscript(), taproot::LeafVersion::TapScript))
29			.expect("clause is not in taproot tree")
30	}
31
32	/// Computes the total witness size in bytes for spending via this clause.
33	fn witness_size(&self, vtxo: &Vtxo) -> usize;
34
35	/// Constructs the witness for the clause.
36	fn witness(
37		&self,
38		data: &Self::WitnessData,
39		control_block: &ControlBlock,
40	) -> Witness;
41}
42
43/// A clause that allows to sign and spend the UTXO after a relative
44/// timelock.
45#[derive(Debug, Clone)]
46pub struct DelayedSignClause {
47	pub pubkey: PublicKey,
48	pub block_delta: BlockDelta,
49}
50
51impl DelayedSignClause {
52	/// Returns the input sequence value for this clause.
53	pub fn sequence(&self) -> Sequence {
54		Sequence::from_height(self.block_delta)
55	}
56}
57
58impl TapScriptClause for DelayedSignClause {
59	type WitnessData = schnorr::Signature;
60
61	fn tapscript(&self) -> ScriptBuf {
62		assert_ne!(self.block_delta, 0, "block delta must be non-zero");
63		scripts::delayed_sign(self.block_delta, self.pubkey.x_only_public_key().0)
64	}
65
66	fn witness(
67		&self,
68		signature: &Self::WitnessData,
69		control_block: &ControlBlock,
70	) -> Witness {
71		Witness::from_slice(&[
72			&signature[..],
73			self.tapscript().as_bytes(),
74			&control_block.serialize()[..],
75		])
76	}
77
78
79	fn witness_size(&self, vtxo: &Vtxo) -> usize {
80		let cb_size = self.control_block(vtxo).size();
81		let tapscript_size = self.tapscript().as_bytes().len();
82
83		1 // byte for the number of witness elements
84		+ 1  // schnorr signature size byte
85		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
86		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
87		+ tapscript_size // tapscript bytes
88		+ VarInt::from(cb_size).size()  // control block size bytes
89		+ cb_size // control block bytes
90	}
91}
92
93impl Into<VtxoClause> for DelayedSignClause {
94	fn into(self) -> VtxoClause {
95		VtxoClause::DelayedSign(self)
96	}
97}
98
99/// A clause that allows to sign and spend the UTXO after an absolute
100/// timelock.
101#[derive(Debug, Clone)]
102pub struct TimelockSignClause {
103	pub pubkey: PublicKey,
104	pub timelock_height: BlockHeight,
105}
106
107impl TimelockSignClause {
108	/// Returns the absolute locktime for this clause.
109	pub fn locktime(&self) -> LockTime {
110		LockTime::from_height(self.timelock_height).expect("timelock height is valid")
111	}
112}
113
114impl TapScriptClause for TimelockSignClause {
115	type WitnessData = schnorr::Signature;
116
117	fn tapscript(&self) -> ScriptBuf {
118		scripts::timelock_sign(self.timelock_height, self.pubkey.x_only_public_key().0)
119	}
120
121	fn witness(
122		&self,
123		signature: &Self::WitnessData,
124		control_block: &ControlBlock,
125	) -> Witness {
126		Witness::from_slice(&[
127			&signature[..],
128			self.tapscript().as_bytes(),
129			&control_block.serialize()[..],
130		])
131	}
132
133	fn witness_size(&self, vtxo: &Vtxo) -> usize {
134		let cb_size = self.control_block(vtxo).size();
135		let tapscript_size = self.tapscript().as_bytes().len();
136
137		1 // byte for the number of witness elements
138		+ 1  // schnorr signature size byte
139		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
140		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
141		+ tapscript_size // tapscript bytes
142		+ VarInt::from(cb_size).size()  // control block size bytes
143		+ cb_size // control block bytes
144	}
145}
146
147impl Into<VtxoClause> for TimelockSignClause {
148	fn into(self) -> VtxoClause {
149		VtxoClause::TimelockSign(self)
150	}
151}
152
153/// A clause that allows to sign and spend the UTXO after a relative
154/// timelock, with an additional absolute one.
155#[derive(Debug, Clone)]
156pub struct DelayedTimelockSignClause {
157	pub pubkey: PublicKey,
158	pub timelock_height: BlockHeight,
159	pub block_delta: BlockDelta,
160}
161
162impl DelayedTimelockSignClause {
163	/// Returns the input sequence for this clause.
164	pub fn sequence(&self) -> Sequence {
165		Sequence::from_height(self.block_delta)
166	}
167
168	/// Returns the absolute locktime for this clause.
169	pub fn locktime(&self) -> LockTime {
170		LockTime::from_height(self.timelock_height).expect("timelock height is valid")
171	}
172}
173
174impl TapScriptClause for DelayedTimelockSignClause {
175	type WitnessData = schnorr::Signature;
176
177	fn tapscript(&self) -> ScriptBuf {
178		assert_ne!(self.block_delta, 0, "block delta must be non-zero");
179		scripts::delay_timelock_sign(
180			self.block_delta,
181			self.timelock_height,
182			self.pubkey.x_only_public_key().0,
183		)
184	}
185
186	fn witness(
187		&self,
188		signature: &Self::WitnessData,
189		control_block: &ControlBlock,
190	) -> Witness {
191		Witness::from_slice(&[
192			&signature[..],
193			self.tapscript().as_bytes(),
194			&control_block.serialize()[..],
195		])
196	}
197
198	fn witness_size(&self, vtxo: &Vtxo) -> usize {
199		let cb_size = self.control_block(vtxo).size();
200		let tapscript_size = self.tapscript().as_bytes().len();
201
202		1 // byte for the number of witness elements
203		+ 1  // schnorr signature size byte
204		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
205		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
206		+ tapscript_size // tapscript bytes
207		+ VarInt::from(cb_size).size()  // control block size bytes
208		+ cb_size // control block bytes
209	}
210}
211
212impl Into<VtxoClause> for DelayedTimelockSignClause {
213	fn into(self) -> VtxoClause {
214		VtxoClause::DelayedTimelockSign(self)
215	}
216}
217
218/// A clause that allows to sign and spend the UTXO after a relative
219/// timelock, if preimage matching the payment hash is provided.
220#[derive(Debug, Clone)]
221pub struct HashDelaySignClause {
222	pub pubkey: PublicKey,
223	pub payment_hash: PaymentHash,
224	pub block_delta: BlockDelta,
225}
226
227impl HashDelaySignClause {
228	/// Returns the input sequence for this clause.
229	pub fn sequence(&self) -> Sequence {
230		assert_ne!(self.block_delta, 0, "block delta must be non-zero");
231		Sequence::from_height(self.block_delta)
232	}
233}
234
235impl TapScriptClause for HashDelaySignClause {
236	type WitnessData = (schnorr::Signature, Preimage);
237
238	fn tapscript(&self) -> ScriptBuf {
239		assert_ne!(self.block_delta, 0, "block delta must be non-zero");
240		scripts::hash_delay_sign(
241			self.payment_hash.to_sha256_hash(),
242			self.block_delta,
243			self.pubkey.x_only_public_key().0,
244		)
245	}
246
247	fn witness(
248		&self,
249		data: &Self::WitnessData,
250		control_block: &ControlBlock,
251	) -> Witness {
252		let (signature, preimage) = data;
253		Witness::from_slice(&[
254			&signature[..],
255			&preimage.as_ref()[..],
256			self.tapscript().as_bytes(),
257			&control_block.serialize()[..],
258		])
259	}
260
261	fn witness_size(&self, vtxo: &Vtxo) -> usize {
262		let cb_size = self.control_block(vtxo).size();
263		let tapscript_size = self.tapscript().as_bytes().len();
264
265		1 // byte for the number of witness elements
266		+ 1  // schnorr signature size byte
267		+ SCHNORR_SIGNATURE_SIZE // schnorr sig bytes
268		+ 1  // preimage size byte
269		+ PREIMAGE_SIZE // preimage bytes
270		+ VarInt::from(tapscript_size).size()  // tapscript size bytes
271		+ tapscript_size // tapscript bytes
272		+ VarInt::from(cb_size).size()  // control block size bytes
273		+ cb_size // control block bytes
274	}
275}
276
277impl Into<VtxoClause> for HashDelaySignClause {
278	fn into(self) -> VtxoClause {
279		VtxoClause::HashDelaySign(self)
280	}
281}
282
283#[derive(Debug, Clone)]
284pub enum VtxoClause {
285	DelayedSign(DelayedSignClause),
286	TimelockSign(TimelockSignClause),
287	DelayedTimelockSign(DelayedTimelockSignClause),
288	HashDelaySign(HashDelaySignClause),
289}
290
291impl VtxoClause {
292	/// Returns the public key associated with this clause.
293	pub fn pubkey(&self) -> PublicKey {
294		match self {
295			Self::DelayedSign(c) => c.pubkey,
296			Self::TimelockSign(c) => c.pubkey,
297			Self::DelayedTimelockSign(c) => c.pubkey,
298			Self::HashDelaySign(c) => c.pubkey,
299		}
300	}
301
302	/// Returns the tapscript for this clause.
303	pub fn tapscript(&self) -> ScriptBuf {
304		match self {
305			Self::DelayedSign(c) => c.tapscript(),
306			Self::TimelockSign(c) => c.tapscript(),
307			Self::DelayedTimelockSign(c) => c.tapscript(),
308			Self::HashDelaySign(c) => c.tapscript(),
309		}
310	}
311
312	/// Returns the input sequence for this clause, if applicable.
313	pub fn sequence(&self) -> Option<Sequence> {
314		match self {
315			Self::DelayedSign(c) => Some(c.sequence()),
316			Self::TimelockSign(_) => None,
317			Self::DelayedTimelockSign(c) => Some(c.sequence()),
318			Self::HashDelaySign(c) => Some(c.sequence()),
319		}
320	}
321
322	/// Computes the total witness size in bytes for spending the VTXO via this clause.
323	pub fn control_block(&self, vtxo: &Vtxo) -> ControlBlock {
324		match self {
325			Self::DelayedSign(c) => c.control_block(vtxo),
326			Self::TimelockSign(c) => c.control_block(vtxo),
327			Self::DelayedTimelockSign(c) => c.control_block(vtxo),
328			Self::HashDelaySign(c) => c.control_block(vtxo),
329		}
330	}
331
332	/// Computes the total witness size in bytes for spending the VTXO via this clause.
333	pub fn witness_size(&self, vtxo: &Vtxo) -> usize {
334		match self {
335			Self::DelayedSign(c) => c.witness_size(vtxo),
336			Self::TimelockSign(c) => c.witness_size(vtxo),
337			Self::DelayedTimelockSign(c) => c.witness_size(vtxo),
338			Self::HashDelaySign(c) => c.witness_size(vtxo),
339		}
340	}
341}
342
343#[cfg(test)]
344mod tests {
345	use std::str::FromStr;
346
347	use bitcoin::taproot::TaprootSpendInfo;
348	use bitcoin::{Amount, OutPoint, Transaction, TxIn, TxOut, Txid, sighash};
349	use bitcoin::hashes::Hash;
350	use bitcoin::key::Keypair;
351	use bitcoin_ext::{TaprootSpendInfoExt, fee};
352
353	use crate::{SECP, musig};
354	use crate::test::verify_tx;
355
356	use super::*;
357
358	lazy_static! {
359		static ref USER_KEYPAIR: Keypair = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
360		static ref SERVER_KEYPAIR: Keypair = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
361	}
362
363	#[allow(unused)]
364	fn all_clause_tested(clause: VtxoClause) -> bool {
365		// NB: matcher to ensure all clauses are tested
366		match clause {
367			VtxoClause::DelayedSign(_) => true,
368			VtxoClause::TimelockSign(_) => true,
369			VtxoClause::DelayedTimelockSign(_) => true,
370			VtxoClause::HashDelaySign(_) => true,
371		}
372	}
373
374	fn transaction() -> Transaction {
375		let address = bitcoin::Address::from_str("tb1q00h5delzqxl7xae8ufmsegghcl4jwfvdnd8530")
376			.unwrap().assume_checked();
377
378		Transaction {
379			version: bitcoin::transaction::Version(3),
380			lock_time: bitcoin::absolute::LockTime::ZERO,
381			input: vec![],
382			output: vec![TxOut {
383				script_pubkey: address.script_pubkey(),
384				value: Amount::from_sat(900_000),
385			}, fee::fee_anchor()]
386		}
387	}
388
389	fn taproot_material(clause_spk: ScriptBuf) -> (TaprootSpendInfo, ControlBlock) {
390		let user_pubkey = USER_KEYPAIR.public_key();
391		let server_pubkey = SERVER_KEYPAIR.public_key();
392
393		let combined_pk = musig::combine_keys([user_pubkey, server_pubkey]);
394		let taproot = taproot::TaprootBuilder::new()
395			.add_leaf(0, clause_spk.clone()).unwrap()
396			.finalize(&SECP, combined_pk).unwrap();
397
398		let cb = taproot
399			.control_block(&(clause_spk.clone(), taproot::LeafVersion::TapScript))
400			.expect("script is in taproot");
401
402		(taproot, cb)
403	}
404
405	fn signature(tx: &Transaction, input: &TxOut, clause_spk: ScriptBuf) -> schnorr::Signature {
406		let leaf_hash = taproot::TapLeafHash::from_script(
407			&clause_spk,
408			taproot::LeafVersion::TapScript,
409		);
410
411		let mut shc = sighash::SighashCache::new(tx);
412		let sighash = shc.taproot_script_spend_signature_hash(
413			0, &sighash::Prevouts::All(&[input.clone()]), leaf_hash, sighash::TapSighashType::Default,
414		).expect("all prevouts provided");
415
416		SECP.sign_schnorr(&sighash.into(), &*USER_KEYPAIR)
417	}
418
419	#[test]
420	fn test_delayed_sign_clause() {
421		let clause = DelayedSignClause {
422			pubkey: USER_KEYPAIR.public_key(),
423			block_delta: 100,
424		};
425
426		// We compute taproot material for the clause
427		let (taproot, cb) = taproot_material(clause.tapscript());
428		let tx_in = TxOut {
429			script_pubkey: taproot.script_pubkey(),
430			value: Amount::from_sat(1_000_000),
431		};
432
433		// We build transaction spending input containing clause
434		let mut tx = transaction();
435		tx.input.push(TxIn {
436			previous_output: OutPoint::new(Txid::all_zeros(), 0),
437			script_sig: ScriptBuf::default(),
438			sequence: clause.sequence(),
439			witness: Witness::new(),
440		});
441
442		// We compute the signature for the transaction
443		let signature = signature(&tx, &tx_in, clause.tapscript());
444		tx.input[0].witness = clause.witness(&signature, &cb);
445
446		// We verify the transaction
447		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
448	}
449
450	#[test]
451	fn test_timelock_sign_clause() {
452		let clause = TimelockSignClause {
453			pubkey: USER_KEYPAIR.public_key(),
454			timelock_height: 100,
455		};
456
457		// We compute taproot material for the clause
458		let (taproot, cb) = taproot_material(clause.tapscript());
459		let tx_in = TxOut {
460			script_pubkey: taproot.script_pubkey(),
461			value: Amount::from_sat(1_000_000),
462		};
463
464		// We build transaction spending input containing clause
465		let mut tx = transaction();
466		tx.lock_time = clause.locktime();
467		tx.input.push(TxIn {
468			previous_output: OutPoint::new(Txid::all_zeros(), 0),
469			script_sig: ScriptBuf::default(),
470			sequence: Sequence::ZERO,
471			witness: Witness::new(),
472		});
473
474		// We compute the signature for the transaction
475		let signature = signature(&tx, &tx_in, clause.tapscript());
476		tx.input[0].witness = clause.witness(&signature, &cb);
477
478		// We verify the transaction
479		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
480	}
481
482	#[test]
483	fn test_delayed_timelock_clause() {
484		let clause = DelayedTimelockSignClause {
485			pubkey: USER_KEYPAIR.public_key(),
486			timelock_height: 100,
487			block_delta: 24,
488		};
489
490		// We compute taproot material for the clause
491		let (taproot, cb) = taproot_material(clause.tapscript());
492		let tx_in = TxOut {
493			script_pubkey: taproot.script_pubkey(),
494			value: Amount::from_sat(1_000_000),
495		};
496
497		// We build transaction spending input containing clause
498		let mut tx = transaction();
499		tx.lock_time = clause.locktime();
500		tx.input.push(TxIn {
501			previous_output: OutPoint::new(Txid::all_zeros(), 0),
502			script_sig: ScriptBuf::default(),
503			sequence: clause.sequence(),
504			witness: Witness::new(),
505		});
506
507		// We compute the signature for the transaction
508		let signature = signature(&tx, &tx_in, clause.tapscript());
509		tx.input[0].witness = clause.witness(&signature, &cb);
510
511		// We verify the transaction
512		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
513	}
514
515	#[test]
516	fn test_hash_delay_clause() {
517		let preimage = Preimage::from_slice(&[0; 32]).unwrap();
518
519		let clause = HashDelaySignClause {
520			pubkey: USER_KEYPAIR.public_key(),
521			payment_hash: preimage.compute_payment_hash(),
522			block_delta: 24,
523		};
524
525		// We compute taproot material for the clause
526		let (taproot, cb) = taproot_material(clause.tapscript());
527		let tx_in = TxOut {
528			script_pubkey: taproot.script_pubkey(),
529			value: Amount::from_sat(1_000_000),
530		};
531
532		// We build transaction spending input containing clause
533		let mut tx = transaction();
534		tx.input.push(TxIn {
535			previous_output: OutPoint::new(Txid::all_zeros(), 0),
536			script_sig: ScriptBuf::default(),
537			sequence: clause.sequence(),
538			witness: Witness::new(),
539		});
540
541		// We compute the signature for the transaction
542		let signature = signature(&tx, &tx_in, clause.tapscript());
543		tx.input[0].witness = clause.witness(&(signature, preimage), &cb);
544
545		// We verify the transaction
546		verify_tx(&[tx_in], 0, &tx).expect("transaction is invalid");
547	}
548}