1pub 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::TapScriptClause;
44use crate::vtxo::policy::clause::{
45 DelayedSignClause, DelayedTimelockSignClause, HashDelaySignClause, HashSignClause,
46 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
61pub const MAX_BLOCK_DELTA: BlockDelta = u16::MAX / 4;
65
66pub const MAX_BLOCK_HEIGHT: BlockHeight =
72 bitcoin::absolute::LOCK_TIME_THRESHOLD - 1 - 4 * MAX_BLOCK_DELTA as BlockHeight;
73
74const _: () = {
75 assert!(4 * (MAX_BLOCK_DELTA as u32) <= u16::MAX as u32);
77 assert!((MAX_BLOCK_HEIGHT as u64) + 4 * (MAX_BLOCK_DELTA as u64)
79 < (bitcoin::absolute::LOCK_TIME_THRESHOLD as u64));
80};
81
82pub 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
94pub 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
105pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
148pub enum VtxoPolicyKind {
149 Pubkey,
151 ServerHtlcSend,
153 ServerHtlcRecv,
155 ServerOwned,
157 Checkpoint,
160 Expiry,
162 HarkLeaf,
164 HarkForfeit,
166}
167
168impl fmt::Display for VtxoPolicyKind {
169 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170 match self {
171 Self::Pubkey => f.write_str("pubkey"),
172 Self::ServerHtlcSend => f.write_str("server-htlc-send"),
173 Self::ServerHtlcRecv => f.write_str("server-htlc-receive"),
174 Self::ServerOwned => f.write_str("server-owned"),
175 Self::Checkpoint => f.write_str("checkpoint"),
176 Self::Expiry => f.write_str("expiry"),
177 Self::HarkLeaf => f.write_str("hark-leaf"),
178 Self::HarkForfeit => f.write_str("hark-forfeit"),
179 }
180 }
181}
182
183impl FromStr for VtxoPolicyKind {
184 type Err = String;
185 fn from_str(s: &str) -> Result<Self, Self::Err> {
186 Ok(match s {
187 "pubkey" => Self::Pubkey,
188 "server-htlc-send" => Self::ServerHtlcSend,
189 "server-htlc-receive" => Self::ServerHtlcRecv,
190 "server-owned" => Self::ServerOwned,
191 "checkpoint" => Self::Checkpoint,
192 "expiry" => Self::Expiry,
193 "hark-leaf" => Self::HarkLeaf,
194 "hark-forfeit" => Self::HarkForfeit,
195 _ => return Err(format!("unknown VtxoPolicyKind: {}", s)),
196 })
197 }
198}
199
200impl serde::Serialize for VtxoPolicyKind {
201 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
202 s.collect_str(self)
203 }
204}
205
206impl<'de> serde::Deserialize<'de> for VtxoPolicyKind {
207 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
208 struct Visitor;
209 impl<'de> serde::de::Visitor<'de> for Visitor {
210 type Value = VtxoPolicyKind;
211 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 write!(f, "a VtxoPolicyKind")
213 }
214 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
215 VtxoPolicyKind::from_str(v).map_err(serde::de::Error::custom)
216 }
217 }
218 d.deserialize_str(Visitor)
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
231pub struct PubkeyVtxoPolicy {
232 pub user_pubkey: PublicKey,
233}
234
235impl From<PubkeyVtxoPolicy> for VtxoPolicy {
236 fn from(policy: PubkeyVtxoPolicy) -> Self {
237 Self::Pubkey(policy)
238 }
239}
240
241impl PubkeyVtxoPolicy {
242 pub fn user_pubkey_claim_clause(&self, exit_delta: BlockDelta) -> DelayedSignClause {
244 DelayedSignClause { pubkey: self.user_pubkey, block_delta: exit_delta }
245 }
246
247 pub fn clauses(&self, exit_delta: BlockDelta) -> Vec<VtxoClause> {
248 vec![self.user_pubkey_claim_clause(exit_delta).into()]
249 }
250
251 pub fn taproot(
252 &self,
253 server_pubkey: PublicKey,
254 exit_delta: BlockDelta,
255 ) -> taproot::TaprootSpendInfo {
256 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
257 .x_only_public_key().0;
258
259 let user_pubkey_claim_clause = self.user_pubkey_claim_clause(exit_delta);
260 taproot::TaprootBuilder::new()
261 .add_leaf(0, user_pubkey_claim_clause.tapscript()).unwrap()
262 .finalize(&SECP, combined_pk).unwrap()
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub struct CheckpointVtxoPolicy {
276 pub user_pubkey: PublicKey,
277}
278
279impl From<CheckpointVtxoPolicy> for ServerVtxoPolicy {
280 fn from(policy: CheckpointVtxoPolicy) -> Self {
281 Self::Checkpoint(policy)
282 }
283}
284
285impl CheckpointVtxoPolicy {
286 pub fn server_sweeping_clause(
288 &self,
289 expiry_height: BlockHeight,
290 server_pubkey: PublicKey,
291 ) -> TimelockSignClause {
292 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
293 }
294
295 pub fn clauses(
296 &self,
297 expiry_height: BlockHeight,
298 server_pubkey: PublicKey,
299 ) -> Vec<VtxoClause> {
300 vec![self.server_sweeping_clause(expiry_height, server_pubkey).into()]
301 }
302
303 pub fn taproot(
304 &self,
305 server_pubkey: PublicKey,
306 expiry_height: BlockHeight,
307 ) -> taproot::TaprootSpendInfo {
308 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
309 .x_only_public_key().0;
310 let server_sweeping_clause = self.server_sweeping_clause(expiry_height, server_pubkey);
311
312 taproot::TaprootBuilder::new()
313 .add_leaf(0, server_sweeping_clause.tapscript()).unwrap()
314 .finalize(&SECP, combined_pk).unwrap()
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
320pub struct ExpiryVtxoPolicy {
321 pub internal_key: bitcoin::secp256k1::XOnlyPublicKey,
322}
323
324impl ExpiryVtxoPolicy {
325 pub fn new(internal_key: bitcoin::secp256k1::XOnlyPublicKey) -> Self {
327 Self { internal_key }
328 }
329
330 pub fn server_sweeping_clause(
332 &self,
333 expiry_height: BlockHeight,
334 server_pubkey: PublicKey,
335 ) -> TimelockSignClause {
336 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
337 }
338
339 pub fn clauses(
340 &self,
341 expiry_height: BlockHeight,
342 server_pubkey: PublicKey,
343 ) -> Vec<VtxoClause> {
344 vec![self.server_sweeping_clause(expiry_height, server_pubkey).into()]
345 }
346
347 pub fn taproot(
348 &self,
349 server_pubkey: PublicKey,
350 expiry_height: BlockHeight,
351 ) -> taproot::TaprootSpendInfo {
352 let server_sweeping_clause = self.server_sweeping_clause(expiry_height, server_pubkey);
353
354 taproot::TaprootBuilder::new()
355 .add_leaf(0, server_sweeping_clause.tapscript()).unwrap()
356 .finalize(&SECP, self.internal_key).unwrap()
357 }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
369pub struct HarkLeafVtxoPolicy {
370 pub user_pubkey: PublicKey,
371 pub unlock_hash: UnlockHash,
372}
373
374impl HarkLeafVtxoPolicy {
375 pub fn expiry_clause(
377 &self,
378 expiry_height: BlockHeight,
379 server_pubkey: PublicKey,
380 ) -> TimelockSignClause {
381 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
382 }
383
384 pub fn unlock_clause(&self, server_pubkey: PublicKey) -> HashSignClause {
386 let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
387 HashSignClause { pubkey: agg_pk, hash: self.unlock_hash }
388 }
389
390 pub fn clauses(
392 &self,
393 expiry_height: BlockHeight,
394 server_pubkey: PublicKey,
395 ) -> Vec<VtxoClause> {
396 vec![
397 self.expiry_clause(expiry_height, server_pubkey).into(),
398 self.unlock_clause(server_pubkey).into(),
399 ]
400 }
401
402 pub fn taproot(
404 &self,
405 server_pubkey: PublicKey,
406 expiry_height: BlockHeight,
407 ) -> taproot::TaprootSpendInfo {
408 let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
409 let expiry_clause = self.expiry_clause(expiry_height, server_pubkey);
410 let unlock_clause = self.unlock_clause(server_pubkey);
411
412 taproot::TaprootBuilder::new()
413 .add_leaf(1, expiry_clause.tapscript()).unwrap()
414 .add_leaf(1, unlock_clause.tapscript()).unwrap()
415 .finalize(&SECP, agg_pk.x_only_public_key().0).unwrap()
416 }
417}
418
419#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
436pub struct ServerHtlcSendVtxoPolicy {
437 pub user_pubkey: PublicKey,
438 pub payment_hash: PaymentHash,
439 pub htlc_expiry: BlockHeight,
440}
441
442impl From<ServerHtlcSendVtxoPolicy> for VtxoPolicy {
443 fn from(policy: ServerHtlcSendVtxoPolicy) -> Self {
444 Self::ServerHtlcSend(policy)
445 }
446}
447
448impl ServerHtlcSendVtxoPolicy {
449 pub fn server_reveals_preimage_clause(
453 &self,
454 server_pubkey: PublicKey,
455 exit_delta: BlockDelta,
456 ) -> HashDelaySignClause {
457 HashDelaySignClause {
458 pubkey: server_pubkey,
459 hash: self.payment_hash.to_sha256_hash(),
460 block_delta: exit_delta
461 }
462 }
463
464 pub fn user_claim_after_expiry_clause(
470 &self,
471 exit_delta: BlockDelta,
472 ) -> DelayedTimelockSignClause {
473 DelayedTimelockSignClause {
474 pubkey: self.user_pubkey,
475 timelock_height: self.htlc_expiry,
476 block_delta: exit_delta.checked_mul(2)
477 .expect("2*exit_delta fits in BlockDelta by MAX_BLOCK_DELTA invariant"),
478 }
479 }
480
481
482 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
483 vec![
484 self.server_reveals_preimage_clause(server_pubkey, exit_delta).into(),
485 self.user_claim_after_expiry_clause(exit_delta).into(),
486 ]
487 }
488
489 pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
490 let server_reveals_preimage_clause = self.server_reveals_preimage_clause(server_pubkey, exit_delta);
491 let user_claim_after_expiry_clause = self.user_claim_after_expiry_clause(exit_delta);
492
493 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
494 .x_only_public_key().0;
495 bitcoin::taproot::TaprootBuilder::new()
496 .add_leaf(1, server_reveals_preimage_clause.tapscript()).unwrap()
497 .add_leaf(1, user_claim_after_expiry_clause.tapscript()).unwrap()
498 .finalize(&SECP, combined_pk).unwrap()
499 }
500}
501
502
503#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
520pub struct ServerHtlcRecvVtxoPolicy {
521 pub user_pubkey: PublicKey,
522 pub payment_hash: PaymentHash,
523 pub htlc_expiry_delta: BlockDelta,
524 pub htlc_expiry: BlockHeight,
525}
526
527impl ServerHtlcRecvVtxoPolicy {
528 pub fn user_reveals_preimage_clause(&self, exit_delta: BlockDelta) -> HashDelaySignClause {
533 HashDelaySignClause {
534 pubkey: self.user_pubkey,
535 hash: self.payment_hash.to_sha256_hash(),
536 block_delta: self.htlc_expiry_delta.checked_add(exit_delta)
537 .expect("htlc_expiry_delta+exit_delta fits in BlockDelta by MAX_BLOCK_DELTA invariant"),
538 }
539 }
540
541 pub fn server_claim_after_expiry_clause(
545 &self,
546 server_pubkey: PublicKey,
547 exit_delta: BlockDelta,
548 ) -> DelayedTimelockSignClause {
549 DelayedTimelockSignClause {
550 pubkey: server_pubkey,
551 timelock_height: self.htlc_expiry,
552 block_delta: exit_delta
553 }
554 }
555
556 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
557 vec![
558 self.user_reveals_preimage_clause(exit_delta).into(),
559 self.server_claim_after_expiry_clause(server_pubkey, exit_delta).into(),
560 ]
561 }
562
563 pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
564 let server_claim_after_expiry_clause = self.server_claim_after_expiry_clause(server_pubkey, exit_delta);
565 let user_reveals_preimage_clause = self.user_reveals_preimage_clause(exit_delta);
566
567 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
568 .x_only_public_key().0;
569 bitcoin::taproot::TaprootBuilder::new()
570 .add_leaf(1, server_claim_after_expiry_clause.tapscript()).unwrap()
571 .add_leaf(1, user_reveals_preimage_clause.tapscript()).unwrap()
572 .finalize(&SECP, combined_pk).unwrap()
573 }
574}
575
576impl From<ServerHtlcRecvVtxoPolicy> for VtxoPolicy {
577 fn from(policy: ServerHtlcRecvVtxoPolicy) -> Self {
578 Self::ServerHtlcRecv(policy)
579 }
580}
581
582#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
588pub struct HarkForfeitVtxoPolicy {
589 pub user_pubkey: PublicKey,
590 pub unlock_hash: UnlockHash,
591}
592
593impl HarkForfeitVtxoPolicy {
594 pub fn server_claim_clause(
596 &self,
597 server_pubkey: PublicKey,
598 ) -> HashSignClause {
599 HashSignClause {
600 pubkey: server_pubkey,
601 hash: self.unlock_hash,
602 }
603 }
604
605 pub fn user_exit_clause(
607 &self,
608 exit_delta: BlockDelta,
609 ) -> DelayedSignClause {
610 DelayedSignClause {
611 pubkey: self.user_pubkey,
612 block_delta: exit_delta
613 }
614 }
615
616 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
617 vec![
618 self.server_claim_clause(server_pubkey).into(),
619 self.user_exit_clause(exit_delta).into(),
620 ]
621 }
622
623 pub fn taproot(
624 &self,
625 server_pubkey: PublicKey,
626 exit_delta: BlockDelta,
627 ) -> taproot::TaprootSpendInfo {
628 let server_claim_clause = self.server_claim_clause(server_pubkey);
629 let user_exit_clause = self.user_exit_clause(exit_delta);
630
631 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
632 .x_only_public_key().0;
633 bitcoin::taproot::TaprootBuilder::new()
634 .add_leaf(1, server_claim_clause.tapscript()).unwrap()
635 .add_leaf(1, user_exit_clause.tapscript()).unwrap()
636 .finalize(&SECP, combined_pk).unwrap()
637 }
638}
639
640impl From<HarkForfeitVtxoPolicy> for ServerVtxoPolicy {
641 fn from(v: HarkForfeitVtxoPolicy) -> Self {
642 ServerVtxoPolicy::HarkForfeit(v)
643 }
644}
645
646#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
651pub enum VtxoPolicy {
652 Pubkey(PubkeyVtxoPolicy),
660 ServerHtlcSend(ServerHtlcSendVtxoPolicy),
662 ServerHtlcRecv(ServerHtlcRecvVtxoPolicy),
664}
665
666impl VtxoPolicy {
667 pub fn new_pubkey(user_pubkey: PublicKey) -> Self {
668 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey })
669 }
670
671 pub fn new_server_htlc_send(
672 user_pubkey: PublicKey,
673 payment_hash: PaymentHash,
674 htlc_expiry: BlockHeight,
675 ) -> Self {
676 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry })
677 }
678
679 pub fn new_server_htlc_recv(
688 user_pubkey: PublicKey,
689 payment_hash: PaymentHash,
690 htlc_expiry: BlockHeight,
691 htlc_expiry_delta: BlockDelta,
692 ) -> Self {
693 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
694 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
695 })
696 }
697
698 pub fn as_pubkey(&self) -> Option<&PubkeyVtxoPolicy> {
699 match self {
700 Self::Pubkey(v) => Some(v),
701 _ => None,
702 }
703 }
704
705 pub fn as_server_htlc_send(&self) -> Option<&ServerHtlcSendVtxoPolicy> {
706 match self {
707 Self::ServerHtlcSend(v) => Some(v),
708 _ => None,
709 }
710 }
711
712 pub fn as_server_htlc_recv(&self) -> Option<&ServerHtlcRecvVtxoPolicy> {
713 match self {
714 Self::ServerHtlcRecv(v) => Some(v),
715 _ => None,
716 }
717 }
718
719 pub fn policy_type(&self) -> VtxoPolicyKind {
721 match self {
722 Self::Pubkey { .. } => VtxoPolicyKind::Pubkey,
723 Self::ServerHtlcSend { .. } => VtxoPolicyKind::ServerHtlcSend,
724 Self::ServerHtlcRecv { .. } => VtxoPolicyKind::ServerHtlcRecv,
725 }
726 }
727
728 pub fn is_arkoor_compatible(&self) -> bool {
730 match self {
731 Self::Pubkey { .. } => true,
732 Self::ServerHtlcSend { .. } => false,
733 Self::ServerHtlcRecv { .. } => false,
734 }
735 }
736
737 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
741 match self {
742 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
743 Self::ServerHtlcSend { .. } => None,
744 Self::ServerHtlcRecv { .. } => None,
745 }
746 }
747
748 pub fn user_pubkey(&self) -> PublicKey {
750 match self {
751 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => *user_pubkey,
752 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
753 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
754 }
755 }
756
757 pub fn taproot(
758 &self,
759 server_pubkey: PublicKey,
760 exit_delta: BlockDelta,
761 expiry_height: BlockHeight,
762 ) -> taproot::TaprootSpendInfo {
763 let _ = expiry_height; match self {
765 Self::Pubkey(policy) => policy.taproot(server_pubkey, exit_delta),
766 Self::ServerHtlcSend(policy) => policy.taproot(server_pubkey, exit_delta),
767 Self::ServerHtlcRecv(policy) => policy.taproot(server_pubkey, exit_delta),
768 }
769 }
770
771 pub fn script_pubkey(
772 &self,
773 server_pubkey: PublicKey,
774 exit_delta: BlockDelta,
775 expiry_height: BlockHeight,
776 ) -> ScriptBuf {
777 self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
778 }
779
780 pub(crate) fn txout(
781 &self,
782 amount: Amount,
783 server_pubkey: PublicKey,
784 exit_delta: BlockDelta,
785 expiry_height: BlockHeight,
786 ) -> TxOut {
787 TxOut {
788 value: amount,
789 script_pubkey: self.script_pubkey(server_pubkey, exit_delta, expiry_height),
790 }
791 }
792
793 pub fn clauses(
794 &self,
795 exit_delta: u16,
796 _expiry_height: BlockHeight,
797 server_pubkey: PublicKey,
798 ) -> Vec<VtxoClause> {
799 match self {
800 Self::Pubkey(policy) => policy.clauses(exit_delta),
801 Self::ServerHtlcSend(policy) => policy.clauses(exit_delta, server_pubkey),
802 Self::ServerHtlcRecv(policy) => policy.clauses(exit_delta, server_pubkey),
803 }
804 }
805}
806
807#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
812pub enum ServerVtxoPolicy {
813 User(VtxoPolicy),
815 ServerOwned,
817 Checkpoint(CheckpointVtxoPolicy),
819 Expiry(ExpiryVtxoPolicy),
821 HarkLeaf(HarkLeafVtxoPolicy),
823 HarkForfeit(HarkForfeitVtxoPolicy),
825}
826
827impl From<VtxoPolicy> for ServerVtxoPolicy {
828 fn from(p: VtxoPolicy) -> Self {
829 Self::User(p)
830 }
831}
832
833impl From<HarkLeafVtxoPolicy> for ServerVtxoPolicy {
834 fn from(p: HarkLeafVtxoPolicy) -> Self {
835 Self::HarkLeaf(p)
836 }
837}
838
839impl ServerVtxoPolicy {
840 pub fn new_server_owned() -> Self {
841 Self::ServerOwned
842 }
843
844 pub fn new_checkpoint(user_pubkey: PublicKey) -> Self {
845 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey })
846 }
847
848 pub fn new_expiry(internal_key: bitcoin::secp256k1::XOnlyPublicKey) -> Self {
849 Self::Expiry(ExpiryVtxoPolicy { internal_key })
850 }
851
852 pub fn new_hark_leaf(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
853 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash })
854 }
855
856 pub fn new_hark_forfeit(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
857 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash })
858 }
859
860 pub fn policy_type(&self) -> VtxoPolicyKind {
862 match self {
863 Self::User(p) => p.policy_type(),
864 Self::ServerOwned => VtxoPolicyKind::ServerOwned,
865 Self::Checkpoint { .. } => VtxoPolicyKind::Checkpoint,
866 Self::Expiry { .. } => VtxoPolicyKind::Expiry,
867 Self::HarkLeaf { .. } => VtxoPolicyKind::HarkLeaf,
868 Self::HarkForfeit { .. } => VtxoPolicyKind::HarkForfeit,
869 }
870 }
871
872 pub fn is_arkoor_compatible(&self) -> bool {
874 match self {
875 Self::User(p) => p.is_arkoor_compatible(),
876 Self::ServerOwned => false,
877 Self::Checkpoint { .. } => true,
878 Self::Expiry { .. } => false,
879 Self::HarkLeaf { .. } => false,
880 Self::HarkForfeit { .. } => false,
881 }
882 }
883
884 pub fn user_pubkey(&self) -> Option<PublicKey> {
886 match self {
887 Self::User(p) => Some(p.user_pubkey()),
888 Self::ServerOwned => None,
889 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
890 Self::Expiry { .. } => None,
891 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
892 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
893 }
894 }
895
896 pub fn taproot(
897 &self,
898 server_pubkey: PublicKey,
899 exit_delta: BlockDelta,
900 expiry_height: BlockHeight,
901 ) -> taproot::TaprootSpendInfo {
902 match self {
903 Self::User(p) => p.taproot(server_pubkey, exit_delta, expiry_height),
904 Self::ServerOwned => {
905 taproot::TaprootBuilder::new()
906 .finalize(&SECP, server_pubkey.x_only_public_key().0).unwrap()
907 },
908 Self::Checkpoint(policy) => policy.taproot(server_pubkey, expiry_height),
909 Self::Expiry(policy) => policy.taproot(server_pubkey, expiry_height),
910 Self::HarkLeaf(policy) => policy.taproot(server_pubkey, expiry_height),
911 Self::HarkForfeit(policy) => policy.taproot(server_pubkey, exit_delta),
912 }
913 }
914
915 pub fn script_pubkey(
916 &self,
917 server_pubkey: PublicKey,
918 exit_delta: BlockDelta,
919 expiry_height: BlockHeight,
920 ) -> ScriptBuf {
921 self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
922 }
923
924 pub fn clauses(
925 &self,
926 exit_delta: u16,
927 expiry_height: BlockHeight,
928 server_pubkey: PublicKey,
929 ) -> Vec<VtxoClause> {
930 match self {
931 Self::User(p) => p.clauses(exit_delta, expiry_height, server_pubkey),
932 Self::ServerOwned => vec![], Self::Checkpoint(policy) => policy.clauses(expiry_height, server_pubkey),
934 Self::Expiry(policy) => policy.clauses(expiry_height, server_pubkey),
935 Self::HarkLeaf(policy) => policy.clauses(expiry_height, server_pubkey),
936 Self::HarkForfeit(policy) => policy.clauses(exit_delta, server_pubkey),
937 }
938 }
939
940 pub fn is_user_policy(&self) -> bool {
942 matches!(self, ServerVtxoPolicy::User(_))
943 }
944
945 pub fn into_user_policy(self) -> Option<VtxoPolicy> {
947 match self {
948 ServerVtxoPolicy::User(p) => Some(p),
949 _ => None,
950 }
951 }
952}
953
954impl Policy for VtxoPolicy {
955 fn policy_type(&self) -> VtxoPolicyKind {
956 VtxoPolicy::policy_type(self)
957 }
958
959 fn taproot(
960 &self,
961 server_pubkey: PublicKey,
962 exit_delta: BlockDelta,
963 expiry_height: BlockHeight,
964 ) -> taproot::TaprootSpendInfo {
965 VtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
966 }
967
968 fn clauses(
969 &self,
970 exit_delta: u16,
971 expiry_height: BlockHeight,
972 server_pubkey: PublicKey,
973 ) -> Vec<VtxoClause> {
974 VtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
975 }
976}
977
978impl Policy for ServerVtxoPolicy {
979 fn policy_type(&self) -> VtxoPolicyKind {
980 ServerVtxoPolicy::policy_type(self)
981 }
982
983 fn taproot(
984 &self,
985 server_pubkey: PublicKey,
986 exit_delta: BlockDelta,
987 expiry_height: BlockHeight,
988 ) -> taproot::TaprootSpendInfo {
989 ServerVtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
990 }
991
992 fn clauses(
993 &self,
994 exit_delta: u16,
995 expiry_height: BlockHeight,
996 server_pubkey: PublicKey,
997 ) -> Vec<VtxoClause> {
998 ServerVtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 use std::str::FromStr;
1005
1006 use bitcoin::hashes::{sha256, Hash};
1007 use bitcoin::key::Keypair;
1008 use bitcoin::sighash::{self, SighashCache};
1009 use bitcoin::{Amount, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, Witness};
1010 use bitcoin::taproot::{self, TapLeafHash};
1011 use bitcoin_ext::{TaprootSpendInfoExt, fee};
1012
1013 use crate::{SECP, musig};
1014 use crate::test_util::verify_tx;
1015 use crate::vtxo::policy::clause::TapScriptClause;
1016
1017 use super::*;
1018
1019 lazy_static! {
1020 static ref USER_KEYPAIR: Keypair = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1021 static ref SERVER_KEYPAIR: Keypair = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1022 }
1023
1024 fn transaction() -> bitcoin::Transaction {
1025 let address = bitcoin::Address::from_str("tb1q00h5delzqxl7xae8ufmsegghcl4jwfvdnd8530")
1026 .unwrap().assume_checked();
1027
1028 bitcoin::Transaction {
1029 version: bitcoin::transaction::Version(3),
1030 lock_time: bitcoin::absolute::LockTime::ZERO,
1031 input: vec![],
1032 output: vec![TxOut {
1033 script_pubkey: address.script_pubkey(),
1034 value: Amount::from_sat(900_000),
1035 }, fee::fee_anchor()]
1036 }
1037 }
1038
1039 #[test]
1040 fn test_hark_leaf_vtxo_policy_unlock_clause() {
1041 let preimage = [0u8; 32];
1042 let unlock_hash = sha256::Hash::hash(&preimage);
1043
1044 let policy = HarkLeafVtxoPolicy {
1045 user_pubkey: USER_KEYPAIR.public_key(),
1046 unlock_hash,
1047 };
1048
1049 let expiry_height = 100_000;
1050
1051 let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1053 let unlock_clause = policy.unlock_clause(SERVER_KEYPAIR.public_key());
1054
1055 let tx_in = TxOut {
1056 script_pubkey: taproot.script_pubkey(),
1057 value: Amount::from_sat(1_000_000),
1058 };
1059
1060 let mut tx = transaction();
1062 tx.input.push(TxIn {
1063 previous_output: OutPoint::new(Txid::all_zeros(), 0),
1064 script_sig: ScriptBuf::default(),
1065 sequence: Sequence::ZERO,
1066 witness: Witness::new(),
1067 });
1068
1069 let cb = taproot
1071 .control_block(&(unlock_clause.tapscript(), taproot::LeafVersion::TapScript))
1072 .expect("script is in taproot");
1073
1074 let leaf_hash = TapLeafHash::from_script(
1076 &unlock_clause.tapscript(),
1077 taproot::LeafVersion::TapScript,
1078 );
1079 let mut shc = SighashCache::new(&tx);
1080 let sighash = shc.taproot_script_spend_signature_hash(
1081 0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1082 ).expect("all prevouts provided");
1083
1084 let (user_sec_nonce, user_pub_nonce) = musig::nonce_pair(&*USER_KEYPAIR);
1086 let (server_pub_nonce, server_part_sig) = musig::deterministic_partial_sign(
1087 &*SERVER_KEYPAIR,
1088 [USER_KEYPAIR.public_key()],
1089 &[&user_pub_nonce],
1090 sighash.to_byte_array(),
1091 None,
1092 );
1093 let agg_nonce = musig::nonce_agg(&[&user_pub_nonce, &server_pub_nonce]);
1094
1095 let (_user_part_sig, final_sig) = musig::partial_sign(
1096 [USER_KEYPAIR.public_key(), SERVER_KEYPAIR.public_key()],
1097 agg_nonce,
1098 &*USER_KEYPAIR,
1099 user_sec_nonce,
1100 sighash.to_byte_array(),
1101 None,
1102 Some(&[&server_part_sig]),
1103 );
1104 let final_sig = final_sig.expect("should have final signature");
1105
1106 tx.input[0].witness = unlock_clause.witness(&(final_sig, preimage), &cb);
1107
1108 verify_tx(&[tx_in], 0, &tx).expect("unlock clause spending should be valid");
1110 }
1111
1112 #[test]
1113 fn test_hark_leaf_vtxo_policy_expiry_clause() {
1114 let preimage = [0u8; 32];
1115 let unlock_hash = sha256::Hash::hash(&preimage);
1116
1117 let policy = HarkLeafVtxoPolicy {
1118 user_pubkey: USER_KEYPAIR.public_key(),
1119 unlock_hash,
1120 };
1121
1122 let expiry_height = 100;
1123
1124 let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1126 let expiry_clause = policy.expiry_clause(expiry_height, SERVER_KEYPAIR.public_key());
1127
1128 let tx_in = TxOut {
1129 script_pubkey: taproot.script_pubkey(),
1130 value: Amount::from_sat(1_000_000),
1131 };
1132
1133 let mut tx = transaction();
1135 tx.lock_time = expiry_clause.locktime();
1136 tx.input.push(TxIn {
1137 previous_output: OutPoint::new(Txid::all_zeros(), 0),
1138 script_sig: ScriptBuf::default(),
1139 sequence: Sequence::ZERO,
1140 witness: Witness::new(),
1141 });
1142
1143 let cb = taproot
1145 .control_block(&(expiry_clause.tapscript(), taproot::LeafVersion::TapScript))
1146 .expect("script is in taproot");
1147
1148 let leaf_hash = TapLeafHash::from_script(
1150 &expiry_clause.tapscript(),
1151 taproot::LeafVersion::TapScript,
1152 );
1153 let mut shc = SighashCache::new(&tx);
1154 let sighash = shc.taproot_script_spend_signature_hash(
1155 0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1156 ).expect("all prevouts provided");
1157
1158 let signature = SECP.sign_schnorr(&sighash.into(), &*SERVER_KEYPAIR);
1160
1161 tx.input[0].witness = expiry_clause.witness(&signature, &cb);
1162
1163 verify_tx(&[tx_in], 0, &tx).expect("expiry clause spending should be valid");
1165 }
1166}