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