Skip to main content

ark/vtxo/policy/
mod.rs

1//!
2//! VTXO policies
3//! =============
4//!
5//! # Block height and block delta invariants
6//!
7//! Policy heights and deltas are `BlockHeight` (u32) and `BlockDelta`
8//! (u16 range), but every value crossing a deserialization boundary (protocol
9//! decode, gRPC ingress, JSON, postgres) must be validated through
10//! [check_block_height] / [check_block_delta]. The `arithmetic_side_effects`
11//! clippy lint enforces that interior arithmetic on these values goes through
12//! `checked_*`/`saturating_*`/`wrapping_*`.
13//!
14//! The bounds (see [MAX_BLOCK_DELTA], [MAX_BLOCK_HEIGHT] and the
15//! `const _: () = { ... }` block below):
16//!
17//! * Up to four policy deltas sum into a value that fits in `BlockDelta` (u16).
18//!   This lets clause `block_delta` (relative locktime) fields hold any
19//!   in-codebase composition without overflowing u16.
20//! * Any chain tip plus up to four policy deltas stays below
21//!   `LOCK_TIME_THRESHOLD`, so the result is always a valid absolute locktime
22//!   height (and therefore `LockTime::from_height` succeeds).
23//!
24//! Today's maximum composition is two policy deltas (htlc-send clause's
25//! `2 * exit_delta`, watchman `confirmed_at + 2 * exit_delta`, htlc-recv clause
26//! and watchman `exit_delta + htlc_expiry_delta`); the extra 2x of headroom is
27//! defensive so future operations can be added without retuning the bounds.
28
29pub mod clause;
30pub mod signing;
31
32use std::fmt;
33use std::str::FromStr;
34
35use bitcoin::{Amount, ScriptBuf, TxOut, taproot};
36use bitcoin::secp256k1::PublicKey;
37
38use bitcoin_ext::{BlockDelta, BlockHeight, TaprootSpendInfoExt};
39
40use crate::{SECP, musig };
41use crate::lightning::PaymentHash;
42use crate::tree::signed::UnlockHash;
43use crate::vtxo::{HashDelaySignClause, TapScriptClause};
44use crate::vtxo::policy::clause::{
45	DelayedSignClause, DelayedTimelockSignClause, HashDelaySignClause_v0, HashSignClause,
46	HashSignClause_v0, TimelockSignClause, VtxoClause,
47};
48
49#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
50#[error("invalid policy data: {msg}")]
51pub struct PolicyError {
52	msg: &'static str,
53}
54
55impl PolicyError {
56	fn new(msg: &'static str) -> Self {
57		Self { msg }
58	}
59}
60
61/// The maximum value of a block delta accepted in policies.
62///
63/// Equals `u16::MAX / 4 = 16383` blocks, or roughly 114 days (~3.8 months).
64pub const MAX_BLOCK_DELTA: BlockDelta = BlockDelta::new(u16::MAX / 4);
65
66/// The maximum value of a block height accepted in policies.
67///
68/// Reserves enough headroom below [bitcoin::absolute::LOCK_TIME_THRESHOLD]
69/// for any accepted height plus up to `4 * MAX_BLOCK_DELTA` of additional
70/// blocks to still produce a valid absolute locktime height.
71pub const MAX_BLOCK_HEIGHT: BlockHeight = BlockHeight::new(
72	bitcoin::absolute::LOCK_TIME_THRESHOLD - 1 - 4 * MAX_BLOCK_DELTA.to_u32(),
73);
74
75const _: () = {
76	// Up to four policy deltas fit in BlockDelta (u16).
77	assert!(4 * MAX_BLOCK_DELTA.to_u32() <= u16::MAX as u32);
78	// Any accepted height plus up to 4 deltas stays below LOCK_TIME_THRESHOLD.
79	assert!((MAX_BLOCK_HEIGHT.to_u32() as u64) + 4 * (MAX_BLOCK_DELTA.to_u32() as u64)
80		< (bitcoin::absolute::LOCK_TIME_THRESHOLD as u64));
81};
82
83/// Boundary check for a block delta arriving from an untrusted source (protocol
84/// decode, gRPC, JSON, DB).
85pub fn check_block_delta<T: TryInto<BlockDelta>>(v: T) -> Result<BlockDelta, PolicyError> {
86	let v: BlockDelta = v.try_into()
87		.map_err(|_| PolicyError::new("block delta out of u16 range"))?;
88	if v > MAX_BLOCK_DELTA {
89		Err(PolicyError::new("block delta exceeds maximum value"))
90	} else {
91		Ok(v)
92	}
93}
94
95/// Boundary check for a block height arriving from an untrusted source.
96pub fn check_block_height<T: TryInto<BlockHeight>>(v: T) -> Result<BlockHeight, PolicyError> {
97	let v: BlockHeight = v.try_into()
98		.map_err(|_| PolicyError::new("block height out of u32 range"))?;
99	if v > MAX_BLOCK_HEIGHT {
100		Err(PolicyError::new("block height exceeds maximum value"))
101	} else {
102		Ok(v)
103	}
104}
105
106/// Trait for policy types that can be used in a Vtxo.
107pub trait Policy: Clone + Send + Sync + 'static {
108	fn policy_type(&self) -> VtxoPolicyKind;
109
110	fn taproot(
111		&self,
112		server_pubkey: PublicKey,
113		exit_delta: BlockDelta,
114		expiry_height: BlockHeight,
115	) -> taproot::TaprootSpendInfo;
116
117	fn script_pubkey(
118		&self,
119		server_pubkey: PublicKey,
120		exit_delta: BlockDelta,
121		expiry_height: BlockHeight,
122	) -> ScriptBuf {
123		Policy::taproot(self, server_pubkey, exit_delta, expiry_height).script_pubkey()
124	}
125
126	fn txout(
127		&self,
128		amount: Amount,
129		server_pubkey: PublicKey,
130		exit_delta: BlockDelta,
131		expiry_height: BlockHeight,
132	) -> TxOut {
133		TxOut {
134			script_pubkey: Policy::script_pubkey(self, server_pubkey, exit_delta, expiry_height),
135			value: amount,
136		}
137	}
138
139	fn clauses(
140		&self,
141		exit_delta: BlockDelta,
142		expiry_height: BlockHeight,
143		server_pubkey: PublicKey,
144	) -> Vec<VtxoClause>;
145}
146
147/// Type enum of [VtxoPolicy].
148#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
149pub enum VtxoPolicyKind {
150	/// Standard VTXO output protected with a public key.
151	Pubkey,
152	/// A VTXO that represents an HTLC with the Ark server to send money.
153	ServerHtlcSend,
154	/// A VTXO that represents an HTLC with the Ark server to send money.
155	#[allow(non_camel_case_types)]
156	ServerHtlcSend_v0,
157	/// A VTXO that represents an HTLC with the Ark server to receive money.
158	ServerHtlcRecv,
159	/// A VTXO that represents an HTLC with the Ark server to receive money.
160	#[allow(non_camel_case_types)]
161	ServerHtlcRecv_v0,
162	/// Simple VTXO owned by the server key
163	ServerOwned,
164	/// A public policy that grants bitcoin back to the server after expiry
165	/// It is used to construct checkpoint transactions
166	Checkpoint,
167	/// Server-only policy where coins can only be swept by the server after expiry.
168	Expiry,
169	/// hArk leaf output policy (intermediate outputs spent by leaf txs).
170	HarkLeaf,
171	/// hArk leaf output policy (intermediate outputs spent by leaf txs).
172	#[allow(non_camel_case_types)]
173	HarkLeaf_v0,
174	/// hArk forfeit tx output policy
175	HarkForfeit,
176	/// hArk forfeit tx output policy
177	#[allow(non_camel_case_types)]
178	HarkForfeit_v0,
179}
180
181impl fmt::Display for VtxoPolicyKind {
182	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183		match self {
184			Self::Pubkey => f.write_str("pubkey"),
185			Self::ServerHtlcSend => f.write_str("server-htlc-send-v1"),
186			Self::ServerHtlcRecv => f.write_str("server-htlc-receive-v1"),
187			Self::ServerHtlcSend_v0 => f.write_str("server-htlc-send"),
188			Self::ServerHtlcRecv_v0 => f.write_str("server-htlc-receive"),
189			Self::ServerOwned => f.write_str("server-owned"),
190			Self::Checkpoint => f.write_str("checkpoint"),
191			Self::Expiry => f.write_str("expiry"),
192			Self::HarkLeaf => f.write_str("hark-leaf-v1"),
193			Self::HarkLeaf_v0 => f.write_str("hark-leaf"),
194			Self::HarkForfeit => f.write_str("hark-forfeit-v1"),
195			Self::HarkForfeit_v0 => f.write_str("hark-forfeit"),
196		}
197	}
198}
199
200impl FromStr for VtxoPolicyKind {
201	type Err = String;
202	fn from_str(s: &str) -> Result<Self, Self::Err> {
203		Ok(match s {
204			"pubkey" => Self::Pubkey,
205			"server-htlc-send-v1" => Self::ServerHtlcSend,
206			"server-htlc-receive-v1" => Self::ServerHtlcRecv,
207			"server-htlc-send" => Self::ServerHtlcSend_v0,
208			"server-htlc-receive" => Self::ServerHtlcRecv_v0,
209			"server-owned" => Self::ServerOwned,
210			"checkpoint" => Self::Checkpoint,
211			"expiry" => Self::Expiry,
212			"hark-leaf-v1" => Self::HarkLeaf,
213			"hark-leaf" => Self::HarkLeaf_v0,
214			"hark-forfeit-v1" => Self::HarkForfeit,
215			"hark-forfeit" => Self::HarkForfeit_v0,
216			_ => return Err(format!("unknown VtxoPolicyKind: {}", s)),
217		})
218	}
219}
220
221impl serde::Serialize for VtxoPolicyKind {
222	fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
223		s.collect_str(self)
224	}
225}
226
227impl<'de> serde::Deserialize<'de> for VtxoPolicyKind {
228	fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
229		struct Visitor;
230		impl<'de> serde::de::Visitor<'de> for Visitor {
231			type Value = VtxoPolicyKind;
232			fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233				write!(f, "a VtxoPolicyKind")
234			}
235			fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
236				VtxoPolicyKind::from_str(v).map_err(serde::de::Error::custom)
237			}
238		}
239		d.deserialize_str(Visitor)
240	}
241}
242
243/// Policy enabling VTXO protected with a public key.
244///
245/// This will build a taproot with 2 spending paths:
246/// 1. The keyspend path allows Alice and Server to collaborate to spend
247/// the VTXO.
248///
249/// 2. The script-spend path allows Alice to unilaterally spend the VTXO
250/// after a delay.
251#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
252pub struct PubkeyVtxoPolicy {
253	pub user_pubkey: PublicKey,
254}
255
256impl From<PubkeyVtxoPolicy> for VtxoPolicy {
257	fn from(policy: PubkeyVtxoPolicy) -> Self {
258		Self::Pubkey(policy)
259	}
260}
261
262impl PubkeyVtxoPolicy {
263	/// Allows Alice to spend the VTXO after a delay.
264	pub fn user_pubkey_claim_clause(&self, exit_delta: BlockDelta) -> DelayedSignClause {
265		DelayedSignClause { pubkey: self.user_pubkey, block_delta: exit_delta }
266	}
267
268	pub fn clauses(&self, exit_delta: BlockDelta) -> Vec<VtxoClause> {
269		vec![self.user_pubkey_claim_clause(exit_delta).into()]
270	}
271
272	pub fn taproot(
273		&self,
274		server_pubkey: PublicKey,
275		exit_delta: BlockDelta,
276	) -> taproot::TaprootSpendInfo {
277		let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
278			.x_only_public_key().0;
279
280		let user_pubkey_claim_clause = self.user_pubkey_claim_clause(exit_delta);
281		taproot::TaprootBuilder::new()
282			.add_leaf(0, user_pubkey_claim_clause.tapscript()).unwrap()
283			.finalize(&SECP, combined_pk).unwrap()
284	}
285}
286
287/// Policy enabling server checkpoints
288///
289/// This will build a taproot with 2 clauses:
290/// 1. The keyspend path allows Alice and Server to collaborate to spend
291/// the checkpoint.
292///
293/// 2. The script-spend path allows Server to spend the checkpoint after
294/// the expiry height.
295#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
296pub struct CheckpointVtxoPolicy {
297	pub user_pubkey: PublicKey,
298}
299
300impl From<CheckpointVtxoPolicy> for ServerVtxoPolicy {
301	fn from(policy: CheckpointVtxoPolicy) -> Self {
302		Self::Checkpoint(policy)
303	}
304}
305
306impl CheckpointVtxoPolicy {
307	/// Allows Server to spend the checkpoint after expiry height.
308	pub fn server_sweeping_clause(
309		&self,
310		expiry_height: BlockHeight,
311		server_pubkey: PublicKey,
312	) -> TimelockSignClause {
313		TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
314	}
315
316	pub fn clauses(
317		&self,
318		expiry_height: BlockHeight,
319		server_pubkey: PublicKey,
320	) -> Vec<VtxoClause> {
321		vec![self.server_sweeping_clause(expiry_height, server_pubkey).into()]
322	}
323
324	pub fn taproot(
325		&self,
326		server_pubkey: PublicKey,
327		expiry_height: BlockHeight,
328	) -> taproot::TaprootSpendInfo {
329		let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
330			.x_only_public_key().0;
331		let server_sweeping_clause = self.server_sweeping_clause(expiry_height, server_pubkey);
332
333		taproot::TaprootBuilder::new()
334			.add_leaf(0, server_sweeping_clause.tapscript()).unwrap()
335			.finalize(&SECP, combined_pk).unwrap()
336	}
337}
338
339/// Server-only policy where coins can only be swept by the server after expiry.
340#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
341pub struct ExpiryVtxoPolicy {
342	pub internal_key: bitcoin::secp256k1::XOnlyPublicKey,
343}
344
345impl ExpiryVtxoPolicy {
346	/// Creates a new expiry policy with the given internal key.
347	pub fn new(internal_key: bitcoin::secp256k1::XOnlyPublicKey) -> Self {
348		Self { internal_key }
349	}
350
351	/// Allows Server to spend after expiry height.
352	pub fn server_sweeping_clause(
353		&self,
354		expiry_height: BlockHeight,
355		server_pubkey: PublicKey,
356	) -> TimelockSignClause {
357		TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
358	}
359
360	pub fn clauses(
361		&self,
362		expiry_height: BlockHeight,
363		server_pubkey: PublicKey,
364	) -> Vec<VtxoClause> {
365		vec![self.server_sweeping_clause(expiry_height, server_pubkey).into()]
366	}
367
368	pub fn taproot(
369		&self,
370		server_pubkey: PublicKey,
371		expiry_height: BlockHeight,
372	) -> taproot::TaprootSpendInfo {
373		let server_sweeping_clause = self.server_sweeping_clause(expiry_height, server_pubkey);
374
375		taproot::TaprootBuilder::new()
376			.add_leaf(0, server_sweeping_clause.tapscript()).unwrap()
377			.finalize(&SECP, self.internal_key).unwrap()
378	}
379}
380
381/// Policy for hArk leaf outputs (intermediate outputs spent by leaf txs).
382///
383/// These are the outputs that feed into the final leaf transactions in a signed
384/// VTXO tree. They are locked by:
385/// 1. An expiry clause allowing the server to sweep after expiry
386/// 2. An unlock clause requiring a preimage and a signature from user+server
387///
388/// The internal key is set to the MuSig of user's VTXO key + server pubkey.
389#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
390pub struct HarkLeafVtxoPolicy {
391	pub user_pubkey: PublicKey,
392	pub unlock_hash: UnlockHash,
393}
394
395impl HarkLeafVtxoPolicy {
396	/// Creates the expiry clause allowing the server to sweep after expiry.
397	pub fn expiry_clause(
398		&self,
399		expiry_height: BlockHeight,
400		server_pubkey: PublicKey,
401	) -> TimelockSignClause {
402		TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
403	}
404
405	/// Creates the unlock clause requiring a preimage and aggregate signature.
406	pub fn unlock_clause(&self, server_pubkey: PublicKey) -> HashSignClause {
407		let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
408		HashSignClause { pubkey: agg_pk, hash: self.unlock_hash }
409	}
410
411	/// Returns the clauses for this policy.
412	pub fn clauses(
413		&self,
414		expiry_height: BlockHeight,
415		server_pubkey: PublicKey,
416	) -> Vec<VtxoClause> {
417		vec![
418			self.expiry_clause(expiry_height, server_pubkey).into(),
419			self.unlock_clause(server_pubkey).into(),
420		]
421	}
422
423	/// Build the taproot spend info for this policy.
424	pub fn taproot(
425		&self,
426		server_pubkey: PublicKey,
427		expiry_height: BlockHeight,
428	) -> taproot::TaprootSpendInfo {
429		let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
430		let expiry_clause = self.expiry_clause(expiry_height, server_pubkey);
431		let unlock_clause = self.unlock_clause(server_pubkey);
432
433		taproot::TaprootBuilder::new()
434			.add_leaf(1, expiry_clause.tapscript()).unwrap()
435			.add_leaf(1, unlock_clause.tapscript()).unwrap()
436			.finalize(&SECP, agg_pk.x_only_public_key().0).unwrap()
437	}
438}
439
440/// Policy for hArk leaf outputs (intermediate outputs spent by leaf txs).
441///
442/// These are the outputs that feed into the final leaf transactions in a signed
443/// VTXO tree. They are locked by:
444/// 1. An expiry clause allowing the server to sweep after expiry
445/// 2. An unlock clause requiring a preimage and a signature from user+server
446///
447/// The internal key is set to the MuSig of user's VTXO key + server pubkey.
448#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
449#[allow(non_camel_case_types)]
450pub struct HarkLeaf_v0_VtxoPolicy {
451	pub user_pubkey: PublicKey,
452	pub unlock_hash: UnlockHash,
453}
454
455impl HarkLeaf_v0_VtxoPolicy {
456	/// Creates the expiry clause allowing the server to sweep after expiry.
457	pub fn expiry_clause(
458		&self,
459		expiry_height: BlockHeight,
460		server_pubkey: PublicKey,
461	) -> TimelockSignClause {
462		TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
463	}
464
465	/// Creates the unlock clause requiring a preimage and aggregate signature.
466	pub fn unlock_clause(&self, server_pubkey: PublicKey) -> HashSignClause_v0 {
467		let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
468		HashSignClause_v0 { pubkey: agg_pk, hash: self.unlock_hash }
469	}
470
471	/// Returns the clauses for this policy.
472	pub fn clauses(
473		&self,
474		expiry_height: BlockHeight,
475		server_pubkey: PublicKey,
476	) -> Vec<VtxoClause> {
477		vec![
478			self.expiry_clause(expiry_height, server_pubkey).into(),
479			self.unlock_clause(server_pubkey).into(),
480		]
481	}
482
483	/// Build the taproot spend info for this policy.
484	pub fn taproot(
485		&self,
486		server_pubkey: PublicKey,
487		expiry_height: BlockHeight,
488	) -> taproot::TaprootSpendInfo {
489		let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
490		let expiry_clause = self.expiry_clause(expiry_height, server_pubkey);
491		let unlock_clause = self.unlock_clause(server_pubkey);
492
493		taproot::TaprootBuilder::new()
494			.add_leaf(1, expiry_clause.tapscript()).unwrap()
495			.add_leaf(1, unlock_clause.tapscript()).unwrap()
496			.finalize(&SECP, agg_pk.x_only_public_key().0).unwrap()
497	}
498}
499
500/// Policy enabling outgoing Lightning payments.
501///
502/// This will build a taproot with 3 clauses:
503/// 1. The keyspend path allows Alice and Server to collaborate to spend
504/// the HTLC. The Server can use this path to revoke the HTLC if payment
505/// failed
506///
507/// 2. The script-spend path contains one leaf that allows Server to spend
508/// the HTLC after the expiry, if it knows the preimage. Server can use
509/// this path if Alice tries to spend using her clause.
510///
511/// 3. The second leaf allows Alice to spend the HTLC after its expiry
512/// and with a delay. Alice must use this path if the server fails to
513/// provide the preimage and refuse to revoke the HTLC. It will either
514/// force the Server to reveal the preimage (by spending using her clause)
515/// or give Alice her money back.
516#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
517#[allow(non_camel_case_types)]
518pub struct ServerHtlcSend_v0_VtxoPolicy {
519	pub user_pubkey: PublicKey,
520	pub payment_hash: PaymentHash,
521	pub htlc_expiry: BlockHeight,
522}
523
524impl From<ServerHtlcSend_v0_VtxoPolicy> for VtxoPolicy {
525	fn from(policy: ServerHtlcSend_v0_VtxoPolicy) -> Self {
526		Self::ServerHtlcSend_v0(policy)
527	}
528}
529
530impl ServerHtlcSend_v0_VtxoPolicy {
531	/// Allows Server to spend the HTLC after the delta, if it knows the
532	/// preimage. Server can use this path if Alice tries to spend using her
533	/// clause.
534	pub fn server_reveals_preimage_clause(
535		&self,
536		server_pubkey: PublicKey,
537		exit_delta: BlockDelta,
538	) -> HashDelaySignClause_v0 {
539		HashDelaySignClause_v0 {
540			pubkey: server_pubkey,
541			hash: self.payment_hash.to_sha256_hash(),
542			block_delta: exit_delta
543		}
544	}
545
546	/// Allows Alice to spend the HTLC after its expiry and with a delay.
547	/// Alice must use this path if the server fails to provide the preimage
548	/// and refuse to revoke the HTLC. It will either force the server to
549	/// reveal the preimage (by spending using its clause) or give Alice her
550	/// money back.
551	pub fn user_claim_after_expiry_clause(
552		&self,
553		exit_delta: BlockDelta,
554	) -> DelayedTimelockSignClause {
555		DelayedTimelockSignClause {
556			pubkey: self.user_pubkey,
557			timelock_height: self.htlc_expiry,
558			block_delta: exit_delta * 2,
559		}
560	}
561
562
563	pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
564		vec![
565			self.server_reveals_preimage_clause(server_pubkey, exit_delta).into(),
566			self.user_claim_after_expiry_clause(exit_delta).into(),
567		]
568	}
569
570	pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
571		let server_reveals_preimage_clause = self.server_reveals_preimage_clause(server_pubkey, exit_delta);
572		let user_claim_after_expiry_clause = self.user_claim_after_expiry_clause(exit_delta);
573
574		let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
575			.x_only_public_key().0;
576		bitcoin::taproot::TaprootBuilder::new()
577			.add_leaf(1, server_reveals_preimage_clause.tapscript()).unwrap()
578			.add_leaf(1, user_claim_after_expiry_clause.tapscript()).unwrap()
579			.finalize(&SECP, combined_pk).unwrap()
580	}
581}
582
583/// Policy enabling outgoing Lightning payments.
584///
585/// This will build a taproot with 3 clauses:
586/// 1. The keyspend path allows Alice and Server to collaborate to spend
587/// the HTLC. The Server can use this path to revoke the HTLC if payment
588/// failed
589///
590/// 2. The script-spend path contains one leaf that allows Server to spend
591/// the HTLC after the expiry, if it knows the preimage. Server can use
592/// this path if Alice tries to spend using her clause.
593///
594/// 3. The second leaf allows Alice to spend the HTLC after its expiry
595/// and with a delay. Alice must use this path if the server fails to
596/// provide the preimage and refuse to revoke the HTLC. It will either
597/// force the Server to reveal the preimage (by spending using her clause)
598/// or give Alice her money back.
599#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
600pub struct ServerHtlcSendVtxoPolicy {
601	pub user_pubkey: PublicKey,
602	pub payment_hash: PaymentHash,
603	pub htlc_expiry: BlockHeight,
604}
605
606impl ServerHtlcSendVtxoPolicy {
607	/// Allows Server to spend the HTLC after the delta, if it knows the
608	/// preimage. Server can use this path if Alice tries to spend using her
609	/// clause.
610	pub fn server_reveals_preimage_clause(
611		&self,
612		server_pubkey: PublicKey,
613		exit_delta: BlockDelta,
614	) -> HashDelaySignClause {
615		HashDelaySignClause {
616			pubkey: server_pubkey,
617			hash: self.payment_hash.to_sha256_hash(),
618			block_delta: exit_delta
619		}
620	}
621
622	/// Allows Alice to spend the HTLC after its expiry and with a delay.
623	/// Alice must use this path if the server fails to provide the preimage
624	/// and refuse to revoke the HTLC. It will either force the server to
625	/// reveal the preimage (by spending using its clause) or give Alice her
626	/// money back.
627	pub fn user_claim_after_expiry_clause(
628		&self,
629		exit_delta: BlockDelta,
630	) -> DelayedTimelockSignClause {
631		DelayedTimelockSignClause {
632			pubkey: self.user_pubkey,
633			timelock_height: self.htlc_expiry,
634			block_delta: exit_delta * 2,
635		}
636	}
637
638	pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
639		vec![
640			self.server_reveals_preimage_clause(server_pubkey, exit_delta).into(),
641			self.user_claim_after_expiry_clause(exit_delta).into(),
642		]
643	}
644
645	pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
646		let server_reveals_preimage_clause = self.server_reveals_preimage_clause(server_pubkey, exit_delta);
647		let user_claim_after_expiry_clause = self.user_claim_after_expiry_clause(exit_delta);
648
649		let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
650			.x_only_public_key().0;
651		bitcoin::taproot::TaprootBuilder::new()
652			.add_leaf(1, server_reveals_preimage_clause.tapscript()).unwrap()
653			.add_leaf(1, user_claim_after_expiry_clause.tapscript()).unwrap()
654			.finalize(&SECP, combined_pk).unwrap()
655	}
656}
657
658impl From<ServerHtlcSendVtxoPolicy> for VtxoPolicy {
659	fn from(policy: ServerHtlcSendVtxoPolicy) -> Self {
660		Self::ServerHtlcSend(policy)
661	}
662}
663
664
665/// Policy enabling incoming Lightning payments.
666///
667/// This will build a taproot with 3 clauses:
668/// 1. The keyspend path allows Alice and Server to collaborate to spend
669/// the HTLC. This is the expected path to be used. Server should only
670/// accept to collaborate if Alice reveals the preimage.
671///
672/// 2. The script-spend path contains one leaf that allows Server to spend
673/// the HTLC after the expiry, with an exit delta delay. Server can use
674/// this path if Alice tries to spend the HTLC using the 3rd path after
675/// the HTLC expiry
676///
677/// 3. The second leaf allows Alice to spend the HTLC if she knows the
678/// preimage, but with a greater exit delta delay than server's clause.
679/// Alice must use this path if she revealed the preimage but Server
680/// refused to collaborate.
681#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
682#[allow(non_camel_case_types)]
683pub struct ServerHtlcRecv_v0_VtxoPolicy {
684	pub user_pubkey: PublicKey,
685	pub payment_hash: PaymentHash,
686	pub htlc_expiry_delta: BlockDelta,
687	pub htlc_expiry: BlockHeight,
688}
689
690impl ServerHtlcRecv_v0_VtxoPolicy {
691	/// Allows Alice to spend the HTLC if she knows the preimage, but with a
692	/// greater exit delta delay than server's clause. Alice must use this
693	/// path if she revealed the preimage but server refused to cosign
694	/// claim VTXO.
695	pub fn user_reveals_preimage_clause(&self, exit_delta: BlockDelta) -> HashDelaySignClause_v0 {
696		HashDelaySignClause_v0 {
697			pubkey: self.user_pubkey,
698			hash: self.payment_hash.to_sha256_hash(),
699			block_delta: self.htlc_expiry_delta + exit_delta,
700		}
701	}
702
703	/// Allows Server to spend the HTLC after the HTLC expiry, with an exit
704	/// delta delay. Server can use this path if Alice tries to spend the
705	/// HTLC using her clause after the HTLC expiry.
706	pub fn server_claim_after_expiry_clause(
707		&self,
708		server_pubkey: PublicKey,
709		exit_delta: BlockDelta,
710	) -> DelayedTimelockSignClause {
711		DelayedTimelockSignClause {
712			pubkey: server_pubkey,
713			timelock_height: self.htlc_expiry,
714			block_delta: exit_delta
715		}
716	}
717
718	pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
719		vec![
720			self.user_reveals_preimage_clause(exit_delta).into(),
721			self.server_claim_after_expiry_clause(server_pubkey, exit_delta).into(),
722		]
723	}
724
725	pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
726		let server_claim_after_expiry_clause = self.server_claim_after_expiry_clause(server_pubkey, exit_delta);
727		let user_reveals_preimage_clause = self.user_reveals_preimage_clause(exit_delta);
728
729		let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
730			.x_only_public_key().0;
731		bitcoin::taproot::TaprootBuilder::new()
732			.add_leaf(1, server_claim_after_expiry_clause.tapscript()).unwrap()
733			.add_leaf(1, user_reveals_preimage_clause.tapscript()).unwrap()
734			.finalize(&SECP, combined_pk).unwrap()
735	}
736}
737
738impl From<ServerHtlcRecv_v0_VtxoPolicy> for VtxoPolicy {
739	fn from(policy: ServerHtlcRecv_v0_VtxoPolicy) -> Self {
740		Self::ServerHtlcRecv_v0(policy)
741	}
742}
743
744/// Policy enabling incoming Lightning payments.
745///
746/// This will build a taproot with 3 clauses:
747/// 1. The keyspend path allows Alice and Server to collaborate to spend
748/// the HTLC. This is the expected path to be used. Server should only
749/// accept to collaborate if Alice reveals the preimage.
750///
751/// 2. The script-spend path contains one leaf that allows Server to spend
752/// the HTLC after the expiry, with an exit delta delay. Server can use
753/// this path if Alice tries to spend the HTLC using the 3rd path after
754/// the HTLC expiry
755///
756/// 3. The second leaf allows Alice to spend the HTLC if she knows the
757/// preimage, but with a greater exit delta delay than server's clause.
758/// Alice must use this path if she revealed the preimage but Server
759/// refused to collaborate.
760#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
761pub struct ServerHtlcRecvVtxoPolicy {
762	pub user_pubkey: PublicKey,
763	pub payment_hash: PaymentHash,
764	pub htlc_expiry_delta: BlockDelta,
765	pub htlc_expiry: BlockHeight,
766}
767
768impl ServerHtlcRecvVtxoPolicy {
769	/// Allows Alice to spend the HTLC if she knows the preimage, but with a
770	/// greater exit delta delay than server's clause. Alice must use this
771	/// path if she revealed the preimage but server refused to cosign
772	/// claim VTXO.
773	pub fn user_reveals_preimage_clause(&self, exit_delta: BlockDelta) -> HashDelaySignClause {
774		HashDelaySignClause {
775			pubkey: self.user_pubkey,
776			hash: self.payment_hash.to_sha256_hash(),
777			block_delta: self.htlc_expiry_delta + exit_delta,
778		}
779	}
780
781	/// Allows Server to spend the HTLC after the HTLC expiry, with an exit
782	/// delta delay. Server can use this path if Alice tries to spend the
783	/// HTLC using her clause after the HTLC expiry.
784	pub fn server_claim_after_expiry_clause(
785		&self,
786		server_pubkey: PublicKey,
787		exit_delta: BlockDelta,
788	) -> DelayedTimelockSignClause {
789		DelayedTimelockSignClause {
790			pubkey: server_pubkey,
791			timelock_height: self.htlc_expiry,
792			block_delta: exit_delta
793		}
794	}
795
796	pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
797		vec![
798			self.user_reveals_preimage_clause(exit_delta).into(),
799			self.server_claim_after_expiry_clause(server_pubkey, exit_delta).into(),
800		]
801	}
802
803	pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
804		let server_claim_after_expiry_clause = self.server_claim_after_expiry_clause(server_pubkey, exit_delta);
805		let user_reveals_preimage_clause = self.user_reveals_preimage_clause(exit_delta);
806
807		let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
808			.x_only_public_key().0;
809		bitcoin::taproot::TaprootBuilder::new()
810			.add_leaf(1, server_claim_after_expiry_clause.tapscript()).unwrap()
811			.add_leaf(1, user_reveals_preimage_clause.tapscript()).unwrap()
812			.finalize(&SECP, combined_pk).unwrap()
813	}
814}
815
816impl From<ServerHtlcRecvVtxoPolicy> for VtxoPolicy {
817	fn from(policy: ServerHtlcRecvVtxoPolicy) -> Self {
818		Self::ServerHtlcRecv(policy)
819	}
820}
821
822/// The server-only VTXO policy on hArk forfeit txs
823///
824/// This policy allows the server to claim the forfeited coins by revealing
825/// the hArk unlock preimage or allow the user to recover its money in case
826/// the server doesn't.
827#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
828pub struct HarkForfeitVtxoPolicy {
829	pub user_pubkey: PublicKey,
830	pub unlock_hash: UnlockHash,
831}
832
833impl HarkForfeitVtxoPolicy {
834	/// Server claims the forfeit revealing the unlock preimage
835	pub fn server_claim_clause(
836		&self,
837		server_pubkey: PublicKey,
838	) -> HashSignClause {
839		HashSignClause {
840			pubkey: server_pubkey,
841			hash: self.unlock_hash,
842		}
843	}
844
845	/// If the server doesn't reveal the preimage, the user can claim the funds
846	pub fn user_exit_clause(
847		&self,
848		exit_delta: BlockDelta,
849	) -> DelayedSignClause {
850		DelayedSignClause {
851			pubkey: self.user_pubkey,
852			block_delta: exit_delta
853		}
854	}
855
856	pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
857		vec![
858			self.server_claim_clause(server_pubkey).into(),
859			self.user_exit_clause(exit_delta).into(),
860		]
861	}
862
863	pub fn taproot(
864		&self,
865		server_pubkey: PublicKey,
866		exit_delta: BlockDelta,
867	) -> taproot::TaprootSpendInfo {
868		let server_claim_clause = self.server_claim_clause(server_pubkey);
869		let user_exit_clause = self.user_exit_clause(exit_delta);
870
871		let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
872			.x_only_public_key().0;
873		bitcoin::taproot::TaprootBuilder::new()
874			.add_leaf(1, server_claim_clause.tapscript()).unwrap()
875			.add_leaf(1, user_exit_clause.tapscript()).unwrap()
876			.finalize(&SECP, combined_pk).unwrap()
877	}
878}
879
880impl From<HarkForfeitVtxoPolicy> for ServerVtxoPolicy {
881	fn from(v: HarkForfeitVtxoPolicy) -> Self {
882	    ServerVtxoPolicy::HarkForfeit(v)
883	}
884}
885
886/// The server-only VTXO policy on hArk forfeit txs
887///
888/// This policy allows the server to claim the forfeited coins by revealing
889/// the hArk unlock preimage or allow the user to recover its money in case
890/// the server doesn't.
891#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
892#[allow(non_camel_case_types)]
893pub struct HarkForfeit_v0_VtxoPolicy {
894	pub user_pubkey: PublicKey,
895	pub unlock_hash: UnlockHash,
896}
897
898impl HarkForfeit_v0_VtxoPolicy {
899	/// Server claims the forfeit revealing the unlock preimage
900	pub fn server_claim_clause(
901		&self,
902		server_pubkey: PublicKey,
903	) -> HashSignClause_v0 {
904		HashSignClause_v0 {
905			pubkey: server_pubkey,
906			hash: self.unlock_hash,
907		}
908	}
909
910	/// If the server doesn't reveal the preimage, the user can claim the funds
911	pub fn user_exit_clause(
912		&self,
913		exit_delta: BlockDelta,
914	) -> DelayedSignClause {
915		DelayedSignClause {
916			pubkey: self.user_pubkey,
917			block_delta: exit_delta
918		}
919	}
920
921	pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
922		vec![
923			self.server_claim_clause(server_pubkey).into(),
924			self.user_exit_clause(exit_delta).into(),
925		]
926	}
927
928	pub fn taproot(
929		&self,
930		server_pubkey: PublicKey,
931		exit_delta: BlockDelta,
932	) -> taproot::TaprootSpendInfo {
933		let server_claim_clause = self.server_claim_clause(server_pubkey);
934		let user_exit_clause = self.user_exit_clause(exit_delta);
935
936		let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
937			.x_only_public_key().0;
938		bitcoin::taproot::TaprootBuilder::new()
939			.add_leaf(1, server_claim_clause.tapscript()).unwrap()
940			.add_leaf(1, user_exit_clause.tapscript()).unwrap()
941			.finalize(&SECP, combined_pk).unwrap()
942	}
943}
944
945impl From<HarkForfeit_v0_VtxoPolicy> for ServerVtxoPolicy {
946	fn from(v: HarkForfeit_v0_VtxoPolicy) -> Self {
947	    ServerVtxoPolicy::HarkForfeit_v0(v)
948	}
949}
950
951/// User-facing VTXO output policy.
952///
953/// All variants have an associated user public key, accessible via the infallible
954/// `user_pubkey()` method. These policies are used in protocol messages and by clients.
955#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
956pub enum VtxoPolicy {
957	/// Standard VTXO output protected with a public key.
958	///
959	/// This can be the result of either:
960	/// - a board
961	/// - a round
962	/// - an arkoor tx
963	/// - change from a LN payment
964	Pubkey(PubkeyVtxoPolicy),
965	/// A VTXO that represents an HTLC with the Ark server to send money.
966	ServerHtlcSend(ServerHtlcSendVtxoPolicy),
967	/// A VTXO that represents an HTLC with the Ark server to send money.
968	#[allow(non_camel_case_types)]
969	ServerHtlcSend_v0(ServerHtlcSend_v0_VtxoPolicy),
970	/// A VTXO that represents an HTLC with the Ark server to receive money.
971	ServerHtlcRecv(ServerHtlcRecvVtxoPolicy),
972	/// A VTXO that represents an HTLC with the Ark server to receive money.
973	#[allow(non_camel_case_types)]
974	ServerHtlcRecv_v0(ServerHtlcRecv_v0_VtxoPolicy),
975}
976
977impl VtxoPolicy {
978	pub fn new_pubkey(user_pubkey: PublicKey) -> Self {
979		Self::Pubkey(PubkeyVtxoPolicy { user_pubkey })
980	}
981
982	pub fn new_server_htlc_send(
983		user_pubkey: PublicKey,
984		payment_hash: PaymentHash,
985		htlc_expiry: BlockHeight,
986	) -> Self {
987		Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry })
988	}
989
990	/// Creates a new htlc from server to client
991	/// - user_pubkey: A public key owned by the client
992	/// - payment_hash: The payment hash, the client can claim the HTLC
993	/// by revealing the corresponding pre-image
994	/// - htlc_expiry: An absolute blockheight at which the HTLC expires
995	/// - htlc_expiry_delta: A safety margin for the server. If the user
996	/// tries to exit after time-out the server will have at-least
997	/// `htlc_expiry_delta` blocks to claim the payment
998	pub fn new_server_htlc_recv(
999		user_pubkey: PublicKey,
1000		payment_hash: PaymentHash,
1001		htlc_expiry: BlockHeight,
1002		htlc_expiry_delta: BlockDelta,
1003	) -> Self {
1004		Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1005			user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1006		})
1007	}
1008
1009	pub fn as_pubkey(&self) -> Option<&PubkeyVtxoPolicy> {
1010		match self {
1011			Self::Pubkey(v) => Some(v),
1012			_ => None,
1013		}
1014	}
1015
1016	pub fn as_server_htlc_send(&self) -> Option<&ServerHtlcSendVtxoPolicy> {
1017		match self {
1018			Self::ServerHtlcSend(v) => Some(v),
1019			_ => None,
1020		}
1021	}
1022
1023	pub fn as_server_htlc_recv(&self) -> Option<&ServerHtlcRecvVtxoPolicy> {
1024		match self {
1025			Self::ServerHtlcRecv(v) => Some(v),
1026			_ => None,
1027		}
1028	}
1029
1030	/// The policy type id.
1031	pub fn policy_type(&self) -> VtxoPolicyKind {
1032		match self {
1033			Self::Pubkey { .. } => VtxoPolicyKind::Pubkey,
1034			Self::ServerHtlcSend { .. } => VtxoPolicyKind::ServerHtlcSend,
1035			Self::ServerHtlcRecv { .. } => VtxoPolicyKind::ServerHtlcRecv,
1036			Self::ServerHtlcSend_v0 { .. } => VtxoPolicyKind::ServerHtlcSend_v0,
1037			Self::ServerHtlcRecv_v0 { .. } => VtxoPolicyKind::ServerHtlcRecv_v0,
1038		}
1039	}
1040
1041	/// Whether a [Vtxo](crate::Vtxo) with this output can be spent in an arkoor tx.
1042	pub fn is_arkoor_compatible(&self) -> bool {
1043		match self {
1044			Self::Pubkey { .. } => true,
1045			Self::ServerHtlcSend { .. } => false,
1046			Self::ServerHtlcRecv { .. } => false,
1047			Self::ServerHtlcSend_v0 { .. } => false,
1048			Self::ServerHtlcRecv_v0 { .. } => false,
1049		}
1050	}
1051
1052	/// The public key used to cosign arkoor txs spending a [Vtxo](crate::Vtxo)
1053	/// with this output.
1054	/// Returns [None] for HTLC policies.
1055	pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
1056		match self {
1057			Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
1058			Self::ServerHtlcSend { .. } => None,
1059			Self::ServerHtlcRecv { .. } => None,
1060			Self::ServerHtlcSend_v0 { .. } => None,
1061			Self::ServerHtlcRecv_v0 { .. } => None,
1062		}
1063	}
1064
1065	/// Returns the user pubkey associated with this policy.
1066	pub fn user_pubkey(&self) -> PublicKey {
1067		match self {
1068			Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => *user_pubkey,
1069			Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
1070			Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
1071			Self::ServerHtlcSend_v0(ServerHtlcSend_v0_VtxoPolicy { user_pubkey, .. }) => *user_pubkey,
1072			Self::ServerHtlcRecv_v0(ServerHtlcRecv_v0_VtxoPolicy { user_pubkey, .. }) => *user_pubkey,
1073		}
1074	}
1075
1076	pub fn taproot(
1077		&self,
1078		server_pubkey: PublicKey,
1079		exit_delta: BlockDelta,
1080		expiry_height: BlockHeight,
1081	) -> taproot::TaprootSpendInfo {
1082		let _ = expiry_height; // not used by user-facing policies
1083		match self {
1084			Self::Pubkey(policy) => policy.taproot(server_pubkey, exit_delta),
1085			Self::ServerHtlcSend(policy) => policy.taproot(server_pubkey, exit_delta),
1086			Self::ServerHtlcRecv(policy) => policy.taproot(server_pubkey, exit_delta),
1087			Self::ServerHtlcSend_v0(policy) => policy.taproot(server_pubkey, exit_delta),
1088			Self::ServerHtlcRecv_v0(policy) => policy.taproot(server_pubkey, exit_delta),
1089		}
1090	}
1091
1092	pub fn script_pubkey(
1093		&self,
1094		server_pubkey: PublicKey,
1095		exit_delta: BlockDelta,
1096		expiry_height: BlockHeight,
1097	) -> ScriptBuf {
1098		self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
1099	}
1100
1101	pub(crate) fn txout(
1102		&self,
1103		amount: Amount,
1104		server_pubkey: PublicKey,
1105		exit_delta: BlockDelta,
1106		expiry_height: BlockHeight,
1107	) -> TxOut {
1108		TxOut {
1109			value: amount,
1110			script_pubkey: self.script_pubkey(server_pubkey, exit_delta, expiry_height),
1111		}
1112	}
1113
1114	pub fn clauses(
1115		&self,
1116		exit_delta: BlockDelta,
1117		_expiry_height: BlockHeight,
1118		server_pubkey: PublicKey,
1119	) -> Vec<VtxoClause> {
1120		match self {
1121			Self::Pubkey(policy) => policy.clauses(exit_delta),
1122			Self::ServerHtlcSend(policy) => policy.clauses(exit_delta, server_pubkey),
1123			Self::ServerHtlcRecv(policy) => policy.clauses(exit_delta, server_pubkey),
1124			Self::ServerHtlcSend_v0(policy) => policy.clauses(exit_delta, server_pubkey),
1125			Self::ServerHtlcRecv_v0(policy) => policy.clauses(exit_delta, server_pubkey),
1126		}
1127	}
1128}
1129
1130/// Server-internal VTXO policy.
1131///
1132/// This is a superset of [VtxoPolicy] used by the server for internal tracking.
1133/// Includes policies without user public keys.
1134#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1135pub enum ServerVtxoPolicy {
1136	/// Wraps any user-facing policy.
1137	User(VtxoPolicy),
1138	/// Simple output owned only by the server key
1139	ServerOwned,
1140	/// A policy which returns all coins to the server after expiry.
1141	Checkpoint(CheckpointVtxoPolicy),
1142	/// Server-only policy where coins can only be swept by the server after expiry.
1143	Expiry(ExpiryVtxoPolicy),
1144	/// hArk leaf output policy (intermediate outputs spent by leaf txs).
1145	HarkLeaf(HarkLeafVtxoPolicy),
1146	/// hArk leaf output policy (intermediate outputs spent by leaf txs).
1147	#[allow(non_camel_case_types)]
1148	HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy),
1149	/// hArk forfeit tx output policy
1150	HarkForfeit(HarkForfeitVtxoPolicy),
1151	/// hArk forfeit tx output policy
1152	#[allow(non_camel_case_types)]
1153	HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy),
1154}
1155
1156impl From<VtxoPolicy> for ServerVtxoPolicy {
1157	fn from(p: VtxoPolicy) -> Self {
1158		Self::User(p)
1159	}
1160}
1161
1162impl From<HarkLeafVtxoPolicy> for ServerVtxoPolicy {
1163	fn from(p: HarkLeafVtxoPolicy) -> Self {
1164		Self::HarkLeaf(p)
1165	}
1166}
1167
1168impl From<HarkLeaf_v0_VtxoPolicy> for ServerVtxoPolicy {
1169	fn from(p: HarkLeaf_v0_VtxoPolicy) -> Self {
1170		Self::HarkLeaf_v0(p)
1171	}
1172}
1173
1174impl ServerVtxoPolicy {
1175	pub fn new_server_owned() -> Self {
1176		Self::ServerOwned
1177	}
1178
1179	pub fn new_checkpoint(user_pubkey: PublicKey) -> Self {
1180		Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey })
1181	}
1182
1183	pub fn new_expiry(internal_key: bitcoin::secp256k1::XOnlyPublicKey) -> Self {
1184		Self::Expiry(ExpiryVtxoPolicy { internal_key })
1185	}
1186
1187	pub fn new_hark_leaf(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
1188		Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash })
1189	}
1190
1191	pub fn new_hark_leaf_v0(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
1192		Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, unlock_hash })
1193	}
1194
1195	pub fn new_hark_forfeit(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
1196		Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash })
1197	}
1198
1199	/// The policy type id.
1200	pub fn policy_type(&self) -> VtxoPolicyKind {
1201		match self {
1202			Self::User(p) => p.policy_type(),
1203			Self::ServerOwned => VtxoPolicyKind::ServerOwned,
1204			Self::Checkpoint { .. } => VtxoPolicyKind::Checkpoint,
1205			Self::Expiry { .. } => VtxoPolicyKind::Expiry,
1206			Self::HarkLeaf { .. } => VtxoPolicyKind::HarkLeaf,
1207			Self::HarkLeaf_v0 { .. } => VtxoPolicyKind::HarkLeaf_v0,
1208			Self::HarkForfeit { .. } => VtxoPolicyKind::HarkForfeit,
1209			Self::HarkForfeit_v0 { .. } => VtxoPolicyKind::HarkForfeit_v0,
1210		}
1211	}
1212
1213	/// Whether a [Vtxo](crate::Vtxo) with this output can be spent in an arkoor tx.
1214	pub fn is_arkoor_compatible(&self) -> bool {
1215		match self {
1216			Self::User(p) => p.is_arkoor_compatible(),
1217			Self::ServerOwned => false,
1218			Self::Checkpoint { .. } => true,
1219			Self::Expiry { .. } => false,
1220			Self::HarkLeaf { .. } => false,
1221			Self::HarkLeaf_v0 { .. } => false,
1222			Self::HarkForfeit { .. } => false,
1223			Self::HarkForfeit_v0 { .. } => false,
1224		}
1225	}
1226
1227	/// Returns the user pubkey if this policy has one.
1228	pub fn user_pubkey(&self) -> Option<PublicKey> {
1229		match self {
1230			Self::User(p) => Some(p.user_pubkey()),
1231			Self::ServerOwned => None,
1232			Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
1233			Self::Expiry { .. } => None,
1234			Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1235			Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1236			Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1237			Self::HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1238		}
1239	}
1240
1241	pub fn taproot(
1242		&self,
1243		server_pubkey: PublicKey,
1244		exit_delta: BlockDelta,
1245		expiry_height: BlockHeight,
1246	) -> taproot::TaprootSpendInfo {
1247		match self {
1248			Self::User(p) => p.taproot(server_pubkey, exit_delta, expiry_height),
1249			Self::ServerOwned => {
1250				taproot::TaprootBuilder::new()
1251					.finalize(&SECP, server_pubkey.x_only_public_key().0).unwrap()
1252			},
1253			Self::Checkpoint(policy) => policy.taproot(server_pubkey, expiry_height),
1254			Self::Expiry(policy) => policy.taproot(server_pubkey, expiry_height),
1255			Self::HarkLeaf(policy) => policy.taproot(server_pubkey, expiry_height),
1256			Self::HarkLeaf_v0(policy) => policy.taproot(server_pubkey, expiry_height),
1257			Self::HarkForfeit(policy) => policy.taproot(server_pubkey, exit_delta),
1258			Self::HarkForfeit_v0(policy) => policy.taproot(server_pubkey, exit_delta),
1259		}
1260	}
1261
1262	pub fn script_pubkey(
1263		&self,
1264		server_pubkey: PublicKey,
1265		exit_delta: BlockDelta,
1266		expiry_height: BlockHeight,
1267	) -> ScriptBuf {
1268		self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
1269	}
1270
1271	pub fn clauses(
1272		&self,
1273		exit_delta: BlockDelta,
1274		expiry_height: BlockHeight,
1275		server_pubkey: PublicKey,
1276	) -> Vec<VtxoClause> {
1277		match self {
1278			Self::User(p) => p.clauses(exit_delta, expiry_height, server_pubkey),
1279			Self::ServerOwned => vec![], // only keyspend
1280			Self::Checkpoint(policy) => policy.clauses(expiry_height, server_pubkey),
1281			Self::Expiry(policy) => policy.clauses(expiry_height, server_pubkey),
1282			Self::HarkLeaf(policy) => policy.clauses(expiry_height, server_pubkey),
1283			Self::HarkLeaf_v0(policy) => policy.clauses(expiry_height, server_pubkey),
1284			Self::HarkForfeit(policy) => policy.clauses(exit_delta, server_pubkey),
1285			Self::HarkForfeit_v0(policy) => policy.clauses(exit_delta, server_pubkey),
1286		}
1287	}
1288
1289	/// Check whether this is a user policy
1290	pub fn is_user_policy(&self) -> bool {
1291		matches!(self, ServerVtxoPolicy::User(_))
1292	}
1293
1294	/// Try to convert to a user policy if it is one
1295	pub fn into_user_policy(self) -> Option<VtxoPolicy> {
1296		match self {
1297			ServerVtxoPolicy::User(p) => Some(p),
1298			_ => None,
1299		}
1300	}
1301}
1302
1303impl Policy for VtxoPolicy {
1304	fn policy_type(&self) -> VtxoPolicyKind {
1305		VtxoPolicy::policy_type(self)
1306	}
1307
1308	fn taproot(
1309		&self,
1310		server_pubkey: PublicKey,
1311		exit_delta: BlockDelta,
1312		expiry_height: BlockHeight,
1313	) -> taproot::TaprootSpendInfo {
1314		VtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
1315	}
1316
1317	fn clauses(
1318		&self,
1319		exit_delta: BlockDelta,
1320		expiry_height: BlockHeight,
1321		server_pubkey: PublicKey,
1322	) -> Vec<VtxoClause> {
1323		VtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
1324	}
1325}
1326
1327impl Policy for ServerVtxoPolicy {
1328	fn policy_type(&self) -> VtxoPolicyKind {
1329		ServerVtxoPolicy::policy_type(self)
1330	}
1331
1332	fn taproot(
1333		&self,
1334		server_pubkey: PublicKey,
1335		exit_delta: BlockDelta,
1336		expiry_height: BlockHeight,
1337	) -> taproot::TaprootSpendInfo {
1338		ServerVtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
1339	}
1340
1341	fn clauses(
1342		&self,
1343		exit_delta: BlockDelta,
1344		expiry_height: BlockHeight,
1345		server_pubkey: PublicKey,
1346	) -> Vec<VtxoClause> {
1347		ServerVtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
1348	}
1349}
1350
1351#[cfg(test)]
1352mod tests {
1353	use std::str::FromStr;
1354
1355	use bitcoin::hashes::{sha256, Hash};
1356	use bitcoin::key::Keypair;
1357	use bitcoin::sighash::{self, SighashCache};
1358	use bitcoin::{Amount, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, Witness};
1359	use bitcoin::taproot::{self, TapLeafHash};
1360	use bitcoin_ext::{TaprootSpendInfoExt, fee};
1361
1362	use crate::{SECP, musig};
1363	use crate::test_util::verify_tx;
1364	use crate::vtxo::policy::clause::TapScriptClause;
1365
1366	use super::*;
1367
1368	lazy_static! {
1369		static ref USER_KEYPAIR: Keypair = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1370		static ref SERVER_KEYPAIR: Keypair = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1371	}
1372
1373	fn transaction() -> bitcoin::Transaction {
1374		let address = bitcoin::Address::from_str("tb1q00h5delzqxl7xae8ufmsegghcl4jwfvdnd8530")
1375			.unwrap().assume_checked();
1376
1377		bitcoin::Transaction {
1378			version: bitcoin::transaction::Version(3),
1379			lock_time: bitcoin::absolute::LockTime::ZERO,
1380			input: vec![],
1381			output: vec![TxOut {
1382				script_pubkey: address.script_pubkey(),
1383				value: Amount::from_sat(900_000),
1384			}, fee::fee_anchor()]
1385		}
1386	}
1387
1388	#[test]
1389	fn test_hark_leaf_vtxo_policy_unlock_clause() {
1390		let preimage = [0u8; 32];
1391		let unlock_hash = sha256::Hash::hash(&preimage);
1392
1393		let policy = HarkLeafVtxoPolicy {
1394			user_pubkey: USER_KEYPAIR.public_key(),
1395			unlock_hash,
1396		};
1397
1398		let expiry_height = BlockHeight::new(100_000);
1399
1400		// Build the taproot spend info using the policy
1401		let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1402		let unlock_clause = policy.unlock_clause(SERVER_KEYPAIR.public_key());
1403
1404		let tx_in = TxOut {
1405			script_pubkey: taproot.script_pubkey(),
1406			value: Amount::from_sat(1_000_000),
1407		};
1408
1409		// Build the spending transaction
1410		let mut tx = transaction();
1411		tx.input.push(TxIn {
1412			previous_output: OutPoint::new(Txid::all_zeros(), 0),
1413			script_sig: ScriptBuf::default(),
1414			sequence: Sequence::ZERO,
1415			witness: Witness::new(),
1416		});
1417
1418		// Get the control block for the unlock clause
1419		let cb = taproot
1420			.control_block(&(unlock_clause.tapscript(), taproot::LeafVersion::TapScript))
1421			.expect("script is in taproot");
1422
1423		// Compute sighash
1424		let leaf_hash = TapLeafHash::from_script(
1425			&unlock_clause.tapscript(),
1426			taproot::LeafVersion::TapScript,
1427		);
1428		let mut shc = SighashCache::new(&tx);
1429		let sighash = shc.taproot_script_spend_signature_hash(
1430			0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1431		).expect("all prevouts provided");
1432
1433		// Create MuSig signature from user + server
1434		let (user_sec_nonce, user_pub_nonce) = musig::nonce_pair(&*USER_KEYPAIR);
1435		let (server_pub_nonce, server_part_sig) = musig::deterministic_partial_sign(
1436			&*SERVER_KEYPAIR,
1437			[USER_KEYPAIR.public_key()],
1438			&[&user_pub_nonce],
1439			sighash.to_byte_array(),
1440			None,
1441		);
1442		let agg_nonce = musig::nonce_agg(&[&user_pub_nonce, &server_pub_nonce]);
1443
1444		let (_user_part_sig, final_sig) = musig::partial_sign(
1445			[USER_KEYPAIR.public_key(), SERVER_KEYPAIR.public_key()],
1446			agg_nonce,
1447			&*USER_KEYPAIR,
1448			user_sec_nonce,
1449			sighash.to_byte_array(),
1450			None,
1451			Some(&[&server_part_sig]),
1452		);
1453		let final_sig = final_sig.expect("should have final signature");
1454
1455		tx.input[0].witness = unlock_clause.witness(&(final_sig, preimage), &cb);
1456
1457		// Verify the transaction
1458		verify_tx(&[tx_in], 0, &tx).expect("unlock clause spending should be valid");
1459	}
1460
1461	#[test]
1462	fn test_hark_leaf_vtxo_policy_expiry_clause() {
1463		let preimage = [0u8; 32];
1464		let unlock_hash = sha256::Hash::hash(&preimage);
1465
1466		let policy = HarkLeafVtxoPolicy {
1467			user_pubkey: USER_KEYPAIR.public_key(),
1468			unlock_hash,
1469		};
1470
1471		let expiry_height = BlockHeight::new(100);
1472
1473		// Build the taproot spend info using the policy
1474		let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1475		let expiry_clause = policy.expiry_clause(expiry_height, SERVER_KEYPAIR.public_key());
1476
1477		let tx_in = TxOut {
1478			script_pubkey: taproot.script_pubkey(),
1479			value: Amount::from_sat(1_000_000),
1480		};
1481
1482		// Build the spending transaction with locktime
1483		let mut tx = transaction();
1484		tx.lock_time = expiry_clause.locktime();
1485		tx.input.push(TxIn {
1486			previous_output: OutPoint::new(Txid::all_zeros(), 0),
1487			script_sig: ScriptBuf::default(),
1488			sequence: Sequence::ZERO,
1489			witness: Witness::new(),
1490		});
1491
1492		// Get the control block for the expiry clause
1493		let cb = taproot
1494			.control_block(&(expiry_clause.tapscript(), taproot::LeafVersion::TapScript))
1495			.expect("script is in taproot");
1496
1497		// Compute sighash
1498		let leaf_hash = TapLeafHash::from_script(
1499			&expiry_clause.tapscript(),
1500			taproot::LeafVersion::TapScript,
1501		);
1502		let mut shc = SighashCache::new(&tx);
1503		let sighash = shc.taproot_script_spend_signature_hash(
1504			0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1505		).expect("all prevouts provided");
1506
1507		// Server signs
1508		let signature = SECP.sign_schnorr(&sighash.into(), &*SERVER_KEYPAIR);
1509
1510		tx.input[0].witness = expiry_clause.witness(&signature, &cb);
1511
1512		// Verify the transaction
1513		verify_tx(&[tx_in], 0, &tx).expect("expiry clause spending should be valid");
1514	}
1515}