1
2pub mod clause;
3pub mod signing;
4
5use std::fmt;
6use std::str::FromStr;
7
8use bitcoin::{Amount, ScriptBuf, TxOut, taproot};
9use bitcoin::secp256k1::PublicKey;
10
11use bitcoin_ext::{BlockDelta, BlockHeight, TaprootSpendInfoExt};
12
13use crate::{SECP, musig };
14use crate::lightning::PaymentHash;
15use crate::tree::signed::UnlockHash;
16use crate::vtxo::TapScriptClause;
17use crate::vtxo::policy::clause::{
18 DelayedSignClause, DelayedTimelockSignClause, HashDelaySignClause, HashSignClause,
19 TimelockSignClause, VtxoClause,
20};
21
22pub trait Policy: Clone + Send + Sync + 'static {
24 fn policy_type(&self) -> VtxoPolicyKind;
25
26 fn taproot(
27 &self,
28 server_pubkey: PublicKey,
29 exit_delta: BlockDelta,
30 expiry_height: BlockHeight,
31 ) -> taproot::TaprootSpendInfo;
32
33 fn script_pubkey(
34 &self,
35 server_pubkey: PublicKey,
36 exit_delta: BlockDelta,
37 expiry_height: BlockHeight,
38 ) -> ScriptBuf {
39 Policy::taproot(self, server_pubkey, exit_delta, expiry_height).script_pubkey()
40 }
41
42 fn txout(
43 &self,
44 amount: Amount,
45 server_pubkey: PublicKey,
46 exit_delta: BlockDelta,
47 expiry_height: BlockHeight,
48 ) -> TxOut {
49 TxOut {
50 script_pubkey: Policy::script_pubkey(self, server_pubkey, exit_delta, expiry_height),
51 value: amount,
52 }
53 }
54
55 fn clauses(
56 &self,
57 exit_delta: u16,
58 expiry_height: BlockHeight,
59 server_pubkey: PublicKey,
60 ) -> Vec<VtxoClause>;
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub enum VtxoPolicyKind {
66 Pubkey,
68 ServerHtlcSend,
70 ServerHtlcRecv,
72 ServerOwned,
74 Checkpoint,
77 Expiry,
79 HarkLeaf,
81 HarkForfeit,
83}
84
85impl fmt::Display for VtxoPolicyKind {
86 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87 match self {
88 Self::Pubkey => f.write_str("pubkey"),
89 Self::ServerHtlcSend => f.write_str("server-htlc-send"),
90 Self::ServerHtlcRecv => f.write_str("server-htlc-receive"),
91 Self::ServerOwned => f.write_str("server-owned"),
92 Self::Checkpoint => f.write_str("checkpoint"),
93 Self::Expiry => f.write_str("expiry"),
94 Self::HarkLeaf => f.write_str("hark-leaf"),
95 Self::HarkForfeit => f.write_str("hark-forfeit"),
96 }
97 }
98}
99
100impl FromStr for VtxoPolicyKind {
101 type Err = String;
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 Ok(match s {
104 "pubkey" => Self::Pubkey,
105 "server-htlc-send" => Self::ServerHtlcSend,
106 "server-htlc-receive" => Self::ServerHtlcRecv,
107 "server-owned" => Self::ServerOwned,
108 "checkpoint" => Self::Checkpoint,
109 "expiry" => Self::Expiry,
110 "hark-leaf" => Self::HarkLeaf,
111 "hark-forfeit" => Self::HarkForfeit,
112 _ => return Err(format!("unknown VtxoPolicyKind: {}", s)),
113 })
114 }
115}
116
117impl serde::Serialize for VtxoPolicyKind {
118 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
119 s.collect_str(self)
120 }
121}
122
123impl<'de> serde::Deserialize<'de> for VtxoPolicyKind {
124 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
125 struct Visitor;
126 impl<'de> serde::de::Visitor<'de> for Visitor {
127 type Value = VtxoPolicyKind;
128 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 write!(f, "a VtxoPolicyKind")
130 }
131 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
132 VtxoPolicyKind::from_str(v).map_err(serde::de::Error::custom)
133 }
134 }
135 d.deserialize_str(Visitor)
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
148pub struct PubkeyVtxoPolicy {
149 pub user_pubkey: PublicKey,
150}
151
152impl From<PubkeyVtxoPolicy> for VtxoPolicy {
153 fn from(policy: PubkeyVtxoPolicy) -> Self {
154 Self::Pubkey(policy)
155 }
156}
157
158impl PubkeyVtxoPolicy {
159 pub fn user_pubkey_claim_clause(&self, exit_delta: BlockDelta) -> DelayedSignClause {
161 DelayedSignClause { pubkey: self.user_pubkey, block_delta: exit_delta }
162 }
163
164 pub fn clauses(&self, exit_delta: BlockDelta) -> Vec<VtxoClause> {
165 vec![self.user_pubkey_claim_clause(exit_delta).into()]
166 }
167
168 pub fn taproot(
169 &self,
170 server_pubkey: PublicKey,
171 exit_delta: BlockDelta,
172 ) -> taproot::TaprootSpendInfo {
173 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
174 .x_only_public_key().0;
175
176 let user_pubkey_claim_clause = self.user_pubkey_claim_clause(exit_delta);
177 taproot::TaprootBuilder::new()
178 .add_leaf(0, user_pubkey_claim_clause.tapscript()).unwrap()
179 .finalize(&SECP, combined_pk).unwrap()
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
192pub struct CheckpointVtxoPolicy {
193 pub user_pubkey: PublicKey,
194}
195
196impl From<CheckpointVtxoPolicy> for ServerVtxoPolicy {
197 fn from(policy: CheckpointVtxoPolicy) -> Self {
198 Self::Checkpoint(policy)
199 }
200}
201
202impl CheckpointVtxoPolicy {
203 pub fn server_sweeping_clause(
205 &self,
206 expiry_height: BlockHeight,
207 server_pubkey: PublicKey,
208 ) -> TimelockSignClause {
209 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
210 }
211
212 pub fn clauses(
213 &self,
214 expiry_height: BlockHeight,
215 server_pubkey: PublicKey,
216 ) -> Vec<VtxoClause> {
217 vec![self.server_sweeping_clause(expiry_height, server_pubkey).into()]
218 }
219
220 pub fn taproot(
221 &self,
222 server_pubkey: PublicKey,
223 expiry_height: BlockHeight,
224 ) -> taproot::TaprootSpendInfo {
225 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
226 .x_only_public_key().0;
227 let server_sweeping_clause = self.server_sweeping_clause(expiry_height, server_pubkey);
228
229 taproot::TaprootBuilder::new()
230 .add_leaf(0, server_sweeping_clause.tapscript()).unwrap()
231 .finalize(&SECP, combined_pk).unwrap()
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
237pub struct ExpiryVtxoPolicy {
238 pub internal_key: bitcoin::secp256k1::XOnlyPublicKey,
239}
240
241impl ExpiryVtxoPolicy {
242 pub fn new(internal_key: bitcoin::secp256k1::XOnlyPublicKey) -> Self {
244 Self { internal_key }
245 }
246
247 pub fn server_sweeping_clause(
249 &self,
250 expiry_height: BlockHeight,
251 server_pubkey: PublicKey,
252 ) -> TimelockSignClause {
253 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
254 }
255
256 pub fn clauses(
257 &self,
258 expiry_height: BlockHeight,
259 server_pubkey: PublicKey,
260 ) -> Vec<VtxoClause> {
261 vec![self.server_sweeping_clause(expiry_height, server_pubkey).into()]
262 }
263
264 pub fn taproot(
265 &self,
266 server_pubkey: PublicKey,
267 expiry_height: BlockHeight,
268 ) -> taproot::TaprootSpendInfo {
269 let server_sweeping_clause = self.server_sweeping_clause(expiry_height, server_pubkey);
270
271 taproot::TaprootBuilder::new()
272 .add_leaf(0, server_sweeping_clause.tapscript()).unwrap()
273 .finalize(&SECP, self.internal_key).unwrap()
274 }
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
286pub struct HarkLeafVtxoPolicy {
287 pub user_pubkey: PublicKey,
288 pub unlock_hash: UnlockHash,
289}
290
291impl HarkLeafVtxoPolicy {
292 pub fn expiry_clause(
294 &self,
295 expiry_height: BlockHeight,
296 server_pubkey: PublicKey,
297 ) -> TimelockSignClause {
298 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
299 }
300
301 pub fn unlock_clause(&self, server_pubkey: PublicKey) -> HashSignClause {
303 let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
304 HashSignClause { pubkey: agg_pk, hash: self.unlock_hash }
305 }
306
307 pub fn clauses(
309 &self,
310 expiry_height: BlockHeight,
311 server_pubkey: PublicKey,
312 ) -> Vec<VtxoClause> {
313 vec![
314 self.expiry_clause(expiry_height, server_pubkey).into(),
315 self.unlock_clause(server_pubkey).into(),
316 ]
317 }
318
319 pub fn taproot(
321 &self,
322 server_pubkey: PublicKey,
323 expiry_height: BlockHeight,
324 ) -> taproot::TaprootSpendInfo {
325 let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
326 let expiry_clause = self.expiry_clause(expiry_height, server_pubkey);
327 let unlock_clause = self.unlock_clause(server_pubkey);
328
329 taproot::TaprootBuilder::new()
330 .add_leaf(1, expiry_clause.tapscript()).unwrap()
331 .add_leaf(1, unlock_clause.tapscript()).unwrap()
332 .finalize(&SECP, agg_pk.x_only_public_key().0).unwrap()
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
353pub struct ServerHtlcSendVtxoPolicy {
354 pub user_pubkey: PublicKey,
355 pub payment_hash: PaymentHash,
356 pub htlc_expiry: BlockHeight,
357}
358
359impl From<ServerHtlcSendVtxoPolicy> for VtxoPolicy {
360 fn from(policy: ServerHtlcSendVtxoPolicy) -> Self {
361 Self::ServerHtlcSend(policy)
362 }
363}
364
365impl ServerHtlcSendVtxoPolicy {
366 pub fn server_reveals_preimage_clause(
370 &self,
371 server_pubkey: PublicKey,
372 exit_delta: BlockDelta,
373 ) -> HashDelaySignClause {
374 HashDelaySignClause {
375 pubkey: server_pubkey,
376 hash: self.payment_hash.to_sha256_hash(),
377 block_delta: exit_delta
378 }
379 }
380
381 pub fn user_claim_after_expiry_clause(
387 &self,
388 exit_delta: BlockDelta,
389 ) -> DelayedTimelockSignClause {
390 DelayedTimelockSignClause {
391 pubkey: self.user_pubkey,
392 timelock_height: self.htlc_expiry,
393 block_delta: 2 * exit_delta
394 }
395 }
396
397
398 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
399 vec![
400 self.server_reveals_preimage_clause(server_pubkey, exit_delta).into(),
401 self.user_claim_after_expiry_clause(exit_delta).into(),
402 ]
403 }
404
405 pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
406 let server_reveals_preimage_clause = self.server_reveals_preimage_clause(server_pubkey, exit_delta);
407 let user_claim_after_expiry_clause = self.user_claim_after_expiry_clause(exit_delta);
408
409 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
410 .x_only_public_key().0;
411 bitcoin::taproot::TaprootBuilder::new()
412 .add_leaf(1, server_reveals_preimage_clause.tapscript()).unwrap()
413 .add_leaf(1, user_claim_after_expiry_clause.tapscript()).unwrap()
414 .finalize(&SECP, combined_pk).unwrap()
415 }
416}
417
418
419#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
436pub struct ServerHtlcRecvVtxoPolicy {
437 pub user_pubkey: PublicKey,
438 pub payment_hash: PaymentHash,
439 pub htlc_expiry_delta: BlockDelta,
440 pub htlc_expiry: BlockHeight,
441}
442
443impl ServerHtlcRecvVtxoPolicy {
444 pub fn user_reveals_preimage_clause(&self, exit_delta: BlockDelta) -> HashDelaySignClause {
449 HashDelaySignClause {
450 pubkey: self.user_pubkey,
451 hash: self.payment_hash.to_sha256_hash(),
452 block_delta: self.htlc_expiry_delta + exit_delta
453 }
454 }
455
456 pub fn server_claim_after_expiry_clause(
460 &self,
461 server_pubkey: PublicKey,
462 exit_delta: BlockDelta,
463 ) -> DelayedTimelockSignClause {
464 DelayedTimelockSignClause {
465 pubkey: server_pubkey,
466 timelock_height: self.htlc_expiry,
467 block_delta: exit_delta
468 }
469 }
470
471 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
472 vec![
473 self.user_reveals_preimage_clause(exit_delta).into(),
474 self.server_claim_after_expiry_clause(server_pubkey, exit_delta).into(),
475 ]
476 }
477
478 pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
479 let server_claim_after_expiry_clause = self.server_claim_after_expiry_clause(server_pubkey, exit_delta);
480 let user_reveals_preimage_clause = self.user_reveals_preimage_clause(exit_delta);
481
482 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
483 .x_only_public_key().0;
484 bitcoin::taproot::TaprootBuilder::new()
485 .add_leaf(1, server_claim_after_expiry_clause.tapscript()).unwrap()
486 .add_leaf(1, user_reveals_preimage_clause.tapscript()).unwrap()
487 .finalize(&SECP, combined_pk).unwrap()
488 }
489}
490
491impl From<ServerHtlcRecvVtxoPolicy> for VtxoPolicy {
492 fn from(policy: ServerHtlcRecvVtxoPolicy) -> Self {
493 Self::ServerHtlcRecv(policy)
494 }
495}
496
497#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
503pub struct HarkForfeitVtxoPolicy {
504 pub user_pubkey: PublicKey,
505 pub unlock_hash: UnlockHash,
506}
507
508impl HarkForfeitVtxoPolicy {
509 pub fn server_claim_clause(
511 &self,
512 server_pubkey: PublicKey,
513 ) -> HashSignClause {
514 HashSignClause {
515 pubkey: server_pubkey,
516 hash: self.unlock_hash,
517 }
518 }
519
520 pub fn user_exit_clause(
522 &self,
523 exit_delta: BlockDelta,
524 ) -> DelayedSignClause {
525 DelayedSignClause {
526 pubkey: self.user_pubkey,
527 block_delta: exit_delta
528 }
529 }
530
531 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
532 vec![
533 self.server_claim_clause(server_pubkey).into(),
534 self.user_exit_clause(exit_delta).into(),
535 ]
536 }
537
538 pub fn taproot(
539 &self,
540 server_pubkey: PublicKey,
541 exit_delta: BlockDelta,
542 ) -> taproot::TaprootSpendInfo {
543 let server_claim_clause = self.server_claim_clause(server_pubkey);
544 let user_exit_clause = self.user_exit_clause(exit_delta);
545
546 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
547 .x_only_public_key().0;
548 bitcoin::taproot::TaprootBuilder::new()
549 .add_leaf(1, server_claim_clause.tapscript()).unwrap()
550 .add_leaf(1, user_exit_clause.tapscript()).unwrap()
551 .finalize(&SECP, combined_pk).unwrap()
552 }
553}
554
555impl From<HarkForfeitVtxoPolicy> for ServerVtxoPolicy {
556 fn from(v: HarkForfeitVtxoPolicy) -> Self {
557 ServerVtxoPolicy::HarkForfeit(v)
558 }
559}
560
561#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
566pub enum VtxoPolicy {
567 Pubkey(PubkeyVtxoPolicy),
575 ServerHtlcSend(ServerHtlcSendVtxoPolicy),
577 ServerHtlcRecv(ServerHtlcRecvVtxoPolicy),
579}
580
581impl VtxoPolicy {
582 pub fn new_pubkey(user_pubkey: PublicKey) -> Self {
583 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey })
584 }
585
586 pub fn new_server_htlc_send(
587 user_pubkey: PublicKey,
588 payment_hash: PaymentHash,
589 htlc_expiry: BlockHeight,
590 ) -> Self {
591 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry })
592 }
593
594 pub fn new_server_htlc_recv(
603 user_pubkey: PublicKey,
604 payment_hash: PaymentHash,
605 htlc_expiry: BlockHeight,
606 htlc_expiry_delta: BlockDelta,
607 ) -> Self {
608 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
609 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
610 })
611 }
612
613 pub fn as_pubkey(&self) -> Option<&PubkeyVtxoPolicy> {
614 match self {
615 Self::Pubkey(v) => Some(v),
616 _ => None,
617 }
618 }
619
620 pub fn as_server_htlc_send(&self) -> Option<&ServerHtlcSendVtxoPolicy> {
621 match self {
622 Self::ServerHtlcSend(v) => Some(v),
623 _ => None,
624 }
625 }
626
627 pub fn as_server_htlc_recv(&self) -> Option<&ServerHtlcRecvVtxoPolicy> {
628 match self {
629 Self::ServerHtlcRecv(v) => Some(v),
630 _ => None,
631 }
632 }
633
634 pub fn policy_type(&self) -> VtxoPolicyKind {
636 match self {
637 Self::Pubkey { .. } => VtxoPolicyKind::Pubkey,
638 Self::ServerHtlcSend { .. } => VtxoPolicyKind::ServerHtlcSend,
639 Self::ServerHtlcRecv { .. } => VtxoPolicyKind::ServerHtlcRecv,
640 }
641 }
642
643 pub fn is_arkoor_compatible(&self) -> bool {
645 match self {
646 Self::Pubkey { .. } => true,
647 Self::ServerHtlcSend { .. } => false,
648 Self::ServerHtlcRecv { .. } => false,
649 }
650 }
651
652 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
656 match self {
657 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
658 Self::ServerHtlcSend { .. } => None,
659 Self::ServerHtlcRecv { .. } => None,
660 }
661 }
662
663 pub fn user_pubkey(&self) -> PublicKey {
665 match self {
666 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => *user_pubkey,
667 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
668 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
669 }
670 }
671
672 pub fn taproot(
673 &self,
674 server_pubkey: PublicKey,
675 exit_delta: BlockDelta,
676 expiry_height: BlockHeight,
677 ) -> taproot::TaprootSpendInfo {
678 let _ = expiry_height; match self {
680 Self::Pubkey(policy) => policy.taproot(server_pubkey, exit_delta),
681 Self::ServerHtlcSend(policy) => policy.taproot(server_pubkey, exit_delta),
682 Self::ServerHtlcRecv(policy) => policy.taproot(server_pubkey, exit_delta),
683 }
684 }
685
686 pub fn script_pubkey(
687 &self,
688 server_pubkey: PublicKey,
689 exit_delta: BlockDelta,
690 expiry_height: BlockHeight,
691 ) -> ScriptBuf {
692 self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
693 }
694
695 pub(crate) fn txout(
696 &self,
697 amount: Amount,
698 server_pubkey: PublicKey,
699 exit_delta: BlockDelta,
700 expiry_height: BlockHeight,
701 ) -> TxOut {
702 TxOut {
703 value: amount,
704 script_pubkey: self.script_pubkey(server_pubkey, exit_delta, expiry_height),
705 }
706 }
707
708 pub fn clauses(
709 &self,
710 exit_delta: u16,
711 _expiry_height: BlockHeight,
712 server_pubkey: PublicKey,
713 ) -> Vec<VtxoClause> {
714 match self {
715 Self::Pubkey(policy) => policy.clauses(exit_delta),
716 Self::ServerHtlcSend(policy) => policy.clauses(exit_delta, server_pubkey),
717 Self::ServerHtlcRecv(policy) => policy.clauses(exit_delta, server_pubkey),
718 }
719 }
720}
721
722#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
727pub enum ServerVtxoPolicy {
728 User(VtxoPolicy),
730 ServerOwned,
732 Checkpoint(CheckpointVtxoPolicy),
734 Expiry(ExpiryVtxoPolicy),
736 HarkLeaf(HarkLeafVtxoPolicy),
738 HarkForfeit(HarkForfeitVtxoPolicy),
740}
741
742impl From<VtxoPolicy> for ServerVtxoPolicy {
743 fn from(p: VtxoPolicy) -> Self {
744 Self::User(p)
745 }
746}
747
748impl From<HarkLeafVtxoPolicy> for ServerVtxoPolicy {
749 fn from(p: HarkLeafVtxoPolicy) -> Self {
750 Self::HarkLeaf(p)
751 }
752}
753
754impl ServerVtxoPolicy {
755 pub fn new_server_owned() -> Self {
756 Self::ServerOwned
757 }
758
759 pub fn new_checkpoint(user_pubkey: PublicKey) -> Self {
760 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey })
761 }
762
763 pub fn new_expiry(internal_key: bitcoin::secp256k1::XOnlyPublicKey) -> Self {
764 Self::Expiry(ExpiryVtxoPolicy { internal_key })
765 }
766
767 pub fn new_hark_leaf(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
768 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash })
769 }
770
771 pub fn new_hark_forfeit(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
772 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash })
773 }
774
775 pub fn policy_type(&self) -> VtxoPolicyKind {
777 match self {
778 Self::User(p) => p.policy_type(),
779 Self::ServerOwned => VtxoPolicyKind::ServerOwned,
780 Self::Checkpoint { .. } => VtxoPolicyKind::Checkpoint,
781 Self::Expiry { .. } => VtxoPolicyKind::Expiry,
782 Self::HarkLeaf { .. } => VtxoPolicyKind::HarkLeaf,
783 Self::HarkForfeit { .. } => VtxoPolicyKind::HarkForfeit,
784 }
785 }
786
787 pub fn is_arkoor_compatible(&self) -> bool {
789 match self {
790 Self::User(p) => p.is_arkoor_compatible(),
791 Self::ServerOwned => false,
792 Self::Checkpoint { .. } => true,
793 Self::Expiry { .. } => false,
794 Self::HarkLeaf { .. } => false,
795 Self::HarkForfeit { .. } => false,
796 }
797 }
798
799 pub fn user_pubkey(&self) -> Option<PublicKey> {
801 match self {
802 Self::User(p) => Some(p.user_pubkey()),
803 Self::ServerOwned => None,
804 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
805 Self::Expiry { .. } => None,
806 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
807 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
808 }
809 }
810
811 pub fn taproot(
812 &self,
813 server_pubkey: PublicKey,
814 exit_delta: BlockDelta,
815 expiry_height: BlockHeight,
816 ) -> taproot::TaprootSpendInfo {
817 match self {
818 Self::User(p) => p.taproot(server_pubkey, exit_delta, expiry_height),
819 Self::ServerOwned => {
820 taproot::TaprootBuilder::new()
821 .finalize(&SECP, server_pubkey.x_only_public_key().0).unwrap()
822 },
823 Self::Checkpoint(policy) => policy.taproot(server_pubkey, expiry_height),
824 Self::Expiry(policy) => policy.taproot(server_pubkey, expiry_height),
825 Self::HarkLeaf(policy) => policy.taproot(server_pubkey, expiry_height),
826 Self::HarkForfeit(policy) => policy.taproot(server_pubkey, exit_delta),
827 }
828 }
829
830 pub fn script_pubkey(
831 &self,
832 server_pubkey: PublicKey,
833 exit_delta: BlockDelta,
834 expiry_height: BlockHeight,
835 ) -> ScriptBuf {
836 self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
837 }
838
839 pub fn clauses(
840 &self,
841 exit_delta: u16,
842 expiry_height: BlockHeight,
843 server_pubkey: PublicKey,
844 ) -> Vec<VtxoClause> {
845 match self {
846 Self::User(p) => p.clauses(exit_delta, expiry_height, server_pubkey),
847 Self::ServerOwned => vec![], Self::Checkpoint(policy) => policy.clauses(expiry_height, server_pubkey),
849 Self::Expiry(policy) => policy.clauses(expiry_height, server_pubkey),
850 Self::HarkLeaf(policy) => policy.clauses(expiry_height, server_pubkey),
851 Self::HarkForfeit(policy) => policy.clauses(exit_delta, server_pubkey),
852 }
853 }
854
855 pub fn is_user_policy(&self) -> bool {
857 matches!(self, ServerVtxoPolicy::User(_))
858 }
859
860 pub fn into_user_policy(self) -> Option<VtxoPolicy> {
862 match self {
863 ServerVtxoPolicy::User(p) => Some(p),
864 _ => None,
865 }
866 }
867}
868
869impl Policy for VtxoPolicy {
870 fn policy_type(&self) -> VtxoPolicyKind {
871 VtxoPolicy::policy_type(self)
872 }
873
874 fn taproot(
875 &self,
876 server_pubkey: PublicKey,
877 exit_delta: BlockDelta,
878 expiry_height: BlockHeight,
879 ) -> taproot::TaprootSpendInfo {
880 VtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
881 }
882
883 fn clauses(
884 &self,
885 exit_delta: u16,
886 expiry_height: BlockHeight,
887 server_pubkey: PublicKey,
888 ) -> Vec<VtxoClause> {
889 VtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
890 }
891}
892
893impl Policy for ServerVtxoPolicy {
894 fn policy_type(&self) -> VtxoPolicyKind {
895 ServerVtxoPolicy::policy_type(self)
896 }
897
898 fn taproot(
899 &self,
900 server_pubkey: PublicKey,
901 exit_delta: BlockDelta,
902 expiry_height: BlockHeight,
903 ) -> taproot::TaprootSpendInfo {
904 ServerVtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
905 }
906
907 fn clauses(
908 &self,
909 exit_delta: u16,
910 expiry_height: BlockHeight,
911 server_pubkey: PublicKey,
912 ) -> Vec<VtxoClause> {
913 ServerVtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
914 }
915}
916
917#[cfg(test)]
918mod tests {
919 use std::str::FromStr;
920
921 use bitcoin::hashes::{sha256, Hash};
922 use bitcoin::key::Keypair;
923 use bitcoin::sighash::{self, SighashCache};
924 use bitcoin::{Amount, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, Witness};
925 use bitcoin::taproot::{self, TapLeafHash};
926 use bitcoin_ext::{TaprootSpendInfoExt, fee};
927
928 use crate::{SECP, musig};
929 use crate::test_util::verify_tx;
930 use crate::vtxo::policy::clause::TapScriptClause;
931
932 use super::*;
933
934 lazy_static! {
935 static ref USER_KEYPAIR: Keypair = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
936 static ref SERVER_KEYPAIR: Keypair = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
937 }
938
939 fn transaction() -> bitcoin::Transaction {
940 let address = bitcoin::Address::from_str("tb1q00h5delzqxl7xae8ufmsegghcl4jwfvdnd8530")
941 .unwrap().assume_checked();
942
943 bitcoin::Transaction {
944 version: bitcoin::transaction::Version(3),
945 lock_time: bitcoin::absolute::LockTime::ZERO,
946 input: vec![],
947 output: vec![TxOut {
948 script_pubkey: address.script_pubkey(),
949 value: Amount::from_sat(900_000),
950 }, fee::fee_anchor()]
951 }
952 }
953
954 #[test]
955 fn test_hark_leaf_vtxo_policy_unlock_clause() {
956 let preimage = [0u8; 32];
957 let unlock_hash = sha256::Hash::hash(&preimage);
958
959 let policy = HarkLeafVtxoPolicy {
960 user_pubkey: USER_KEYPAIR.public_key(),
961 unlock_hash,
962 };
963
964 let expiry_height = 100_000;
965
966 let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
968 let unlock_clause = policy.unlock_clause(SERVER_KEYPAIR.public_key());
969
970 let tx_in = TxOut {
971 script_pubkey: taproot.script_pubkey(),
972 value: Amount::from_sat(1_000_000),
973 };
974
975 let mut tx = transaction();
977 tx.input.push(TxIn {
978 previous_output: OutPoint::new(Txid::all_zeros(), 0),
979 script_sig: ScriptBuf::default(),
980 sequence: Sequence::ZERO,
981 witness: Witness::new(),
982 });
983
984 let cb = taproot
986 .control_block(&(unlock_clause.tapscript(), taproot::LeafVersion::TapScript))
987 .expect("script is in taproot");
988
989 let leaf_hash = TapLeafHash::from_script(
991 &unlock_clause.tapscript(),
992 taproot::LeafVersion::TapScript,
993 );
994 let mut shc = SighashCache::new(&tx);
995 let sighash = shc.taproot_script_spend_signature_hash(
996 0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
997 ).expect("all prevouts provided");
998
999 let (user_sec_nonce, user_pub_nonce) = musig::nonce_pair(&*USER_KEYPAIR);
1001 let (server_pub_nonce, server_part_sig) = musig::deterministic_partial_sign(
1002 &*SERVER_KEYPAIR,
1003 [USER_KEYPAIR.public_key()],
1004 &[&user_pub_nonce],
1005 sighash.to_byte_array(),
1006 None,
1007 );
1008 let agg_nonce = musig::nonce_agg(&[&user_pub_nonce, &server_pub_nonce]);
1009
1010 let (_user_part_sig, final_sig) = musig::partial_sign(
1011 [USER_KEYPAIR.public_key(), SERVER_KEYPAIR.public_key()],
1012 agg_nonce,
1013 &*USER_KEYPAIR,
1014 user_sec_nonce,
1015 sighash.to_byte_array(),
1016 None,
1017 Some(&[&server_part_sig]),
1018 );
1019 let final_sig = final_sig.expect("should have final signature");
1020
1021 tx.input[0].witness = unlock_clause.witness(&(final_sig, preimage), &cb);
1022
1023 verify_tx(&[tx_in], 0, &tx).expect("unlock clause spending should be valid");
1025 }
1026
1027 #[test]
1028 fn test_hark_leaf_vtxo_policy_expiry_clause() {
1029 let preimage = [0u8; 32];
1030 let unlock_hash = sha256::Hash::hash(&preimage);
1031
1032 let policy = HarkLeafVtxoPolicy {
1033 user_pubkey: USER_KEYPAIR.public_key(),
1034 unlock_hash,
1035 };
1036
1037 let expiry_height = 100;
1038
1039 let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1041 let expiry_clause = policy.expiry_clause(expiry_height, SERVER_KEYPAIR.public_key());
1042
1043 let tx_in = TxOut {
1044 script_pubkey: taproot.script_pubkey(),
1045 value: Amount::from_sat(1_000_000),
1046 };
1047
1048 let mut tx = transaction();
1050 tx.lock_time = expiry_clause.locktime();
1051 tx.input.push(TxIn {
1052 previous_output: OutPoint::new(Txid::all_zeros(), 0),
1053 script_sig: ScriptBuf::default(),
1054 sequence: Sequence::ZERO,
1055 witness: Witness::new(),
1056 });
1057
1058 let cb = taproot
1060 .control_block(&(expiry_clause.tapscript(), taproot::LeafVersion::TapScript))
1061 .expect("script is in taproot");
1062
1063 let leaf_hash = TapLeafHash::from_script(
1065 &expiry_clause.tapscript(),
1066 taproot::LeafVersion::TapScript,
1067 );
1068 let mut shc = SighashCache::new(&tx);
1069 let sighash = shc.taproot_script_spend_signature_hash(
1070 0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1071 ).expect("all prevouts provided");
1072
1073 let signature = SECP.sign_schnorr(&sighash.into(), &*SERVER_KEYPAIR);
1075
1076 tx.input[0].witness = expiry_clause.witness(&signature, &cb);
1077
1078 verify_tx(&[tx_in], 0, &tx).expect("expiry clause spending should be valid");
1080 }
1081}