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