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::{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
61pub const MAX_BLOCK_DELTA: BlockDelta = BlockDelta::new(u16::MAX / 4);
65
66pub const MAX_BLOCK_HEIGHT: BlockHeight = BlockHeight::new(
72 bitcoin::absolute::LOCK_TIME_THRESHOLD - 1 - 4 * MAX_BLOCK_DELTA.to_u32(),
73);
74
75const _: () = {
76 assert!(4 * MAX_BLOCK_DELTA.to_u32() <= u16::MAX as u32);
78 assert!((MAX_BLOCK_HEIGHT.to_u32() as u64) + 4 * (MAX_BLOCK_DELTA.to_u32() as u64)
80 < (bitcoin::absolute::LOCK_TIME_THRESHOLD as u64));
81};
82
83pub fn check_block_delta<T: TryInto<BlockDelta>>(v: T) -> Result<BlockDelta, PolicyError> {
86 let v: BlockDelta = v.try_into()
87 .map_err(|_| PolicyError::new("block delta out of u16 range"))?;
88 if v > MAX_BLOCK_DELTA {
89 Err(PolicyError::new("block delta exceeds maximum value"))
90 } else {
91 Ok(v)
92 }
93}
94
95pub fn check_block_height<T: TryInto<BlockHeight>>(v: T) -> Result<BlockHeight, PolicyError> {
97 let v: BlockHeight = v.try_into()
98 .map_err(|_| PolicyError::new("block height out of u32 range"))?;
99 if v > MAX_BLOCK_HEIGHT {
100 Err(PolicyError::new("block height exceeds maximum value"))
101 } else {
102 Ok(v)
103 }
104}
105
106pub trait Policy: Clone + Send + Sync + 'static {
108 fn policy_type(&self) -> VtxoPolicyKind;
109
110 fn taproot(
111 &self,
112 server_pubkey: PublicKey,
113 exit_delta: BlockDelta,
114 expiry_height: BlockHeight,
115 ) -> taproot::TaprootSpendInfo;
116
117 fn script_pubkey(
118 &self,
119 server_pubkey: PublicKey,
120 exit_delta: BlockDelta,
121 expiry_height: BlockHeight,
122 ) -> ScriptBuf {
123 Policy::taproot(self, server_pubkey, exit_delta, expiry_height).script_pubkey()
124 }
125
126 fn txout(
127 &self,
128 amount: Amount,
129 server_pubkey: PublicKey,
130 exit_delta: BlockDelta,
131 expiry_height: BlockHeight,
132 ) -> TxOut {
133 TxOut {
134 script_pubkey: Policy::script_pubkey(self, server_pubkey, exit_delta, expiry_height),
135 value: amount,
136 }
137 }
138
139 fn clauses(
140 &self,
141 exit_delta: BlockDelta,
142 expiry_height: BlockHeight,
143 server_pubkey: PublicKey,
144 ) -> Vec<VtxoClause>;
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
149pub enum VtxoPolicyKind {
150 Pubkey,
152 ServerHtlcSend,
154 #[allow(non_camel_case_types)]
156 ServerHtlcSend_v0,
157 ServerHtlcRecv,
159 #[allow(non_camel_case_types)]
161 ServerHtlcRecv_v0,
162 ServerOwned,
164 Checkpoint,
167 Expiry,
169 HarkLeaf,
171 #[allow(non_camel_case_types)]
173 HarkLeaf_v0,
174 HarkForfeit,
176 #[allow(non_camel_case_types)]
178 HarkForfeit_v0,
179}
180
181impl fmt::Display for VtxoPolicyKind {
182 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183 match self {
184 Self::Pubkey => f.write_str("pubkey"),
185 Self::ServerHtlcSend => f.write_str("server-htlc-send-v1"),
186 Self::ServerHtlcRecv => f.write_str("server-htlc-receive-v1"),
187 Self::ServerHtlcSend_v0 => f.write_str("server-htlc-send"),
188 Self::ServerHtlcRecv_v0 => f.write_str("server-htlc-receive"),
189 Self::ServerOwned => f.write_str("server-owned"),
190 Self::Checkpoint => f.write_str("checkpoint"),
191 Self::Expiry => f.write_str("expiry"),
192 Self::HarkLeaf => f.write_str("hark-leaf-v1"),
193 Self::HarkLeaf_v0 => f.write_str("hark-leaf"),
194 Self::HarkForfeit => f.write_str("hark-forfeit-v1"),
195 Self::HarkForfeit_v0 => f.write_str("hark-forfeit"),
196 }
197 }
198}
199
200impl FromStr for VtxoPolicyKind {
201 type Err = String;
202 fn from_str(s: &str) -> Result<Self, Self::Err> {
203 Ok(match s {
204 "pubkey" => Self::Pubkey,
205 "server-htlc-send-v1" => Self::ServerHtlcSend,
206 "server-htlc-receive-v1" => Self::ServerHtlcRecv,
207 "server-htlc-send" => Self::ServerHtlcSend_v0,
208 "server-htlc-receive" => Self::ServerHtlcRecv_v0,
209 "server-owned" => Self::ServerOwned,
210 "checkpoint" => Self::Checkpoint,
211 "expiry" => Self::Expiry,
212 "hark-leaf-v1" => Self::HarkLeaf,
213 "hark-leaf" => Self::HarkLeaf_v0,
214 "hark-forfeit-v1" => Self::HarkForfeit,
215 "hark-forfeit" => Self::HarkForfeit_v0,
216 _ => return Err(format!("unknown VtxoPolicyKind: {}", s)),
217 })
218 }
219}
220
221impl serde::Serialize for VtxoPolicyKind {
222 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
223 s.collect_str(self)
224 }
225}
226
227impl<'de> serde::Deserialize<'de> for VtxoPolicyKind {
228 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
229 struct Visitor;
230 impl<'de> serde::de::Visitor<'de> for Visitor {
231 type Value = VtxoPolicyKind;
232 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 write!(f, "a VtxoPolicyKind")
234 }
235 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
236 VtxoPolicyKind::from_str(v).map_err(serde::de::Error::custom)
237 }
238 }
239 d.deserialize_str(Visitor)
240 }
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
252pub struct PubkeyVtxoPolicy {
253 pub user_pubkey: PublicKey,
254}
255
256impl From<PubkeyVtxoPolicy> for VtxoPolicy {
257 fn from(policy: PubkeyVtxoPolicy) -> Self {
258 Self::Pubkey(policy)
259 }
260}
261
262impl PubkeyVtxoPolicy {
263 pub fn user_pubkey_claim_clause(&self, exit_delta: BlockDelta) -> DelayedSignClause {
265 DelayedSignClause { pubkey: self.user_pubkey, block_delta: exit_delta }
266 }
267
268 pub fn clauses(&self, exit_delta: BlockDelta) -> Vec<VtxoClause> {
269 vec![self.user_pubkey_claim_clause(exit_delta).into()]
270 }
271
272 pub fn taproot(
273 &self,
274 server_pubkey: PublicKey,
275 exit_delta: BlockDelta,
276 ) -> taproot::TaprootSpendInfo {
277 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
278 .x_only_public_key().0;
279
280 let user_pubkey_claim_clause = self.user_pubkey_claim_clause(exit_delta);
281 taproot::TaprootBuilder::new()
282 .add_leaf(0, user_pubkey_claim_clause.tapscript()).unwrap()
283 .finalize(&SECP, combined_pk).unwrap()
284 }
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
296pub struct CheckpointVtxoPolicy {
297 pub user_pubkey: PublicKey,
298}
299
300impl From<CheckpointVtxoPolicy> for ServerVtxoPolicy {
301 fn from(policy: CheckpointVtxoPolicy) -> Self {
302 Self::Checkpoint(policy)
303 }
304}
305
306impl CheckpointVtxoPolicy {
307 pub fn server_sweeping_clause(
309 &self,
310 expiry_height: BlockHeight,
311 server_pubkey: PublicKey,
312 ) -> TimelockSignClause {
313 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
314 }
315
316 pub fn clauses(
317 &self,
318 expiry_height: BlockHeight,
319 server_pubkey: PublicKey,
320 ) -> Vec<VtxoClause> {
321 vec![self.server_sweeping_clause(expiry_height, server_pubkey).into()]
322 }
323
324 pub fn taproot(
325 &self,
326 server_pubkey: PublicKey,
327 expiry_height: BlockHeight,
328 ) -> taproot::TaprootSpendInfo {
329 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
330 .x_only_public_key().0;
331 let server_sweeping_clause = self.server_sweeping_clause(expiry_height, server_pubkey);
332
333 taproot::TaprootBuilder::new()
334 .add_leaf(0, server_sweeping_clause.tapscript()).unwrap()
335 .finalize(&SECP, combined_pk).unwrap()
336 }
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
341pub struct ExpiryVtxoPolicy {
342 pub internal_key: bitcoin::secp256k1::XOnlyPublicKey,
343}
344
345impl ExpiryVtxoPolicy {
346 pub fn new(internal_key: bitcoin::secp256k1::XOnlyPublicKey) -> Self {
348 Self { internal_key }
349 }
350
351 pub fn server_sweeping_clause(
353 &self,
354 expiry_height: BlockHeight,
355 server_pubkey: PublicKey,
356 ) -> TimelockSignClause {
357 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
358 }
359
360 pub fn clauses(
361 &self,
362 expiry_height: BlockHeight,
363 server_pubkey: PublicKey,
364 ) -> Vec<VtxoClause> {
365 vec![self.server_sweeping_clause(expiry_height, server_pubkey).into()]
366 }
367
368 pub fn taproot(
369 &self,
370 server_pubkey: PublicKey,
371 expiry_height: BlockHeight,
372 ) -> taproot::TaprootSpendInfo {
373 let server_sweeping_clause = self.server_sweeping_clause(expiry_height, server_pubkey);
374
375 taproot::TaprootBuilder::new()
376 .add_leaf(0, server_sweeping_clause.tapscript()).unwrap()
377 .finalize(&SECP, self.internal_key).unwrap()
378 }
379}
380
381#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
390pub struct HarkLeafVtxoPolicy {
391 pub user_pubkey: PublicKey,
392 pub unlock_hash: UnlockHash,
393}
394
395impl HarkLeafVtxoPolicy {
396 pub fn expiry_clause(
398 &self,
399 expiry_height: BlockHeight,
400 server_pubkey: PublicKey,
401 ) -> TimelockSignClause {
402 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
403 }
404
405 pub fn unlock_clause(&self, server_pubkey: PublicKey) -> HashSignClause {
407 let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
408 HashSignClause { pubkey: agg_pk, hash: self.unlock_hash }
409 }
410
411 pub fn clauses(
413 &self,
414 expiry_height: BlockHeight,
415 server_pubkey: PublicKey,
416 ) -> Vec<VtxoClause> {
417 vec![
418 self.expiry_clause(expiry_height, server_pubkey).into(),
419 self.unlock_clause(server_pubkey).into(),
420 ]
421 }
422
423 pub fn taproot(
425 &self,
426 server_pubkey: PublicKey,
427 expiry_height: BlockHeight,
428 ) -> taproot::TaprootSpendInfo {
429 let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
430 let expiry_clause = self.expiry_clause(expiry_height, server_pubkey);
431 let unlock_clause = self.unlock_clause(server_pubkey);
432
433 taproot::TaprootBuilder::new()
434 .add_leaf(1, expiry_clause.tapscript()).unwrap()
435 .add_leaf(1, unlock_clause.tapscript()).unwrap()
436 .finalize(&SECP, agg_pk.x_only_public_key().0).unwrap()
437 }
438}
439
440#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
449#[allow(non_camel_case_types)]
450pub struct HarkLeaf_v0_VtxoPolicy {
451 pub user_pubkey: PublicKey,
452 pub unlock_hash: UnlockHash,
453}
454
455impl HarkLeaf_v0_VtxoPolicy {
456 pub fn expiry_clause(
458 &self,
459 expiry_height: BlockHeight,
460 server_pubkey: PublicKey,
461 ) -> TimelockSignClause {
462 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }
463 }
464
465 pub fn unlock_clause(&self, server_pubkey: PublicKey) -> HashSignClause_v0 {
467 let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
468 HashSignClause_v0 { pubkey: agg_pk, hash: self.unlock_hash }
469 }
470
471 pub fn clauses(
473 &self,
474 expiry_height: BlockHeight,
475 server_pubkey: PublicKey,
476 ) -> Vec<VtxoClause> {
477 vec![
478 self.expiry_clause(expiry_height, server_pubkey).into(),
479 self.unlock_clause(server_pubkey).into(),
480 ]
481 }
482
483 pub fn taproot(
485 &self,
486 server_pubkey: PublicKey,
487 expiry_height: BlockHeight,
488 ) -> taproot::TaprootSpendInfo {
489 let agg_pk = musig::combine_keys([self.user_pubkey, server_pubkey]);
490 let expiry_clause = self.expiry_clause(expiry_height, server_pubkey);
491 let unlock_clause = self.unlock_clause(server_pubkey);
492
493 taproot::TaprootBuilder::new()
494 .add_leaf(1, expiry_clause.tapscript()).unwrap()
495 .add_leaf(1, unlock_clause.tapscript()).unwrap()
496 .finalize(&SECP, agg_pk.x_only_public_key().0).unwrap()
497 }
498}
499
500#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
517#[allow(non_camel_case_types)]
518pub struct ServerHtlcSend_v0_VtxoPolicy {
519 pub user_pubkey: PublicKey,
520 pub payment_hash: PaymentHash,
521 pub htlc_expiry: BlockHeight,
522}
523
524impl From<ServerHtlcSend_v0_VtxoPolicy> for VtxoPolicy {
525 fn from(policy: ServerHtlcSend_v0_VtxoPolicy) -> Self {
526 Self::ServerHtlcSend_v0(policy)
527 }
528}
529
530impl ServerHtlcSend_v0_VtxoPolicy {
531 pub fn server_reveals_preimage_clause(
535 &self,
536 server_pubkey: PublicKey,
537 exit_delta: BlockDelta,
538 ) -> HashDelaySignClause_v0 {
539 HashDelaySignClause_v0 {
540 pubkey: server_pubkey,
541 hash: self.payment_hash.to_sha256_hash(),
542 block_delta: exit_delta
543 }
544 }
545
546 pub fn user_claim_after_expiry_clause(
552 &self,
553 exit_delta: BlockDelta,
554 ) -> DelayedTimelockSignClause {
555 DelayedTimelockSignClause {
556 pubkey: self.user_pubkey,
557 timelock_height: self.htlc_expiry,
558 block_delta: exit_delta * 2,
559 }
560 }
561
562
563 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
564 vec![
565 self.server_reveals_preimage_clause(server_pubkey, exit_delta).into(),
566 self.user_claim_after_expiry_clause(exit_delta).into(),
567 ]
568 }
569
570 pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
571 let server_reveals_preimage_clause = self.server_reveals_preimage_clause(server_pubkey, exit_delta);
572 let user_claim_after_expiry_clause = self.user_claim_after_expiry_clause(exit_delta);
573
574 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
575 .x_only_public_key().0;
576 bitcoin::taproot::TaprootBuilder::new()
577 .add_leaf(1, server_reveals_preimage_clause.tapscript()).unwrap()
578 .add_leaf(1, user_claim_after_expiry_clause.tapscript()).unwrap()
579 .finalize(&SECP, combined_pk).unwrap()
580 }
581}
582
583#[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 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 pub fn user_claim_after_expiry_clause(
628 &self,
629 exit_delta: BlockDelta,
630 ) -> DelayedTimelockSignClause {
631 DelayedTimelockSignClause {
632 pubkey: self.user_pubkey,
633 timelock_height: self.htlc_expiry,
634 block_delta: exit_delta * 2,
635 }
636 }
637
638 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
639 vec![
640 self.server_reveals_preimage_clause(server_pubkey, exit_delta).into(),
641 self.user_claim_after_expiry_clause(exit_delta).into(),
642 ]
643 }
644
645 pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
646 let server_reveals_preimage_clause = self.server_reveals_preimage_clause(server_pubkey, exit_delta);
647 let user_claim_after_expiry_clause = self.user_claim_after_expiry_clause(exit_delta);
648
649 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
650 .x_only_public_key().0;
651 bitcoin::taproot::TaprootBuilder::new()
652 .add_leaf(1, server_reveals_preimage_clause.tapscript()).unwrap()
653 .add_leaf(1, user_claim_after_expiry_clause.tapscript()).unwrap()
654 .finalize(&SECP, combined_pk).unwrap()
655 }
656}
657
658impl From<ServerHtlcSendVtxoPolicy> for VtxoPolicy {
659 fn from(policy: ServerHtlcSendVtxoPolicy) -> Self {
660 Self::ServerHtlcSend(policy)
661 }
662}
663
664
665#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
682#[allow(non_camel_case_types)]
683pub struct ServerHtlcRecv_v0_VtxoPolicy {
684 pub user_pubkey: PublicKey,
685 pub payment_hash: PaymentHash,
686 pub htlc_expiry_delta: BlockDelta,
687 pub htlc_expiry: BlockHeight,
688}
689
690impl ServerHtlcRecv_v0_VtxoPolicy {
691 pub fn user_reveals_preimage_clause(&self, exit_delta: BlockDelta) -> HashDelaySignClause_v0 {
696 HashDelaySignClause_v0 {
697 pubkey: self.user_pubkey,
698 hash: self.payment_hash.to_sha256_hash(),
699 block_delta: self.htlc_expiry_delta + exit_delta,
700 }
701 }
702
703 pub fn server_claim_after_expiry_clause(
707 &self,
708 server_pubkey: PublicKey,
709 exit_delta: BlockDelta,
710 ) -> DelayedTimelockSignClause {
711 DelayedTimelockSignClause {
712 pubkey: server_pubkey,
713 timelock_height: self.htlc_expiry,
714 block_delta: exit_delta
715 }
716 }
717
718 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
719 vec![
720 self.user_reveals_preimage_clause(exit_delta).into(),
721 self.server_claim_after_expiry_clause(server_pubkey, exit_delta).into(),
722 ]
723 }
724
725 pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
726 let server_claim_after_expiry_clause = self.server_claim_after_expiry_clause(server_pubkey, exit_delta);
727 let user_reveals_preimage_clause = self.user_reveals_preimage_clause(exit_delta);
728
729 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
730 .x_only_public_key().0;
731 bitcoin::taproot::TaprootBuilder::new()
732 .add_leaf(1, server_claim_after_expiry_clause.tapscript()).unwrap()
733 .add_leaf(1, user_reveals_preimage_clause.tapscript()).unwrap()
734 .finalize(&SECP, combined_pk).unwrap()
735 }
736}
737
738impl From<ServerHtlcRecv_v0_VtxoPolicy> for VtxoPolicy {
739 fn from(policy: ServerHtlcRecv_v0_VtxoPolicy) -> Self {
740 Self::ServerHtlcRecv_v0(policy)
741 }
742}
743
744#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
761pub struct ServerHtlcRecvVtxoPolicy {
762 pub user_pubkey: PublicKey,
763 pub payment_hash: PaymentHash,
764 pub htlc_expiry_delta: BlockDelta,
765 pub htlc_expiry: BlockHeight,
766}
767
768impl ServerHtlcRecvVtxoPolicy {
769 pub fn user_reveals_preimage_clause(&self, exit_delta: BlockDelta) -> HashDelaySignClause {
774 HashDelaySignClause {
775 pubkey: self.user_pubkey,
776 hash: self.payment_hash.to_sha256_hash(),
777 block_delta: self.htlc_expiry_delta + exit_delta,
778 }
779 }
780
781 pub fn server_claim_after_expiry_clause(
785 &self,
786 server_pubkey: PublicKey,
787 exit_delta: BlockDelta,
788 ) -> DelayedTimelockSignClause {
789 DelayedTimelockSignClause {
790 pubkey: server_pubkey,
791 timelock_height: self.htlc_expiry,
792 block_delta: exit_delta
793 }
794 }
795
796 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
797 vec![
798 self.user_reveals_preimage_clause(exit_delta).into(),
799 self.server_claim_after_expiry_clause(server_pubkey, exit_delta).into(),
800 ]
801 }
802
803 pub fn taproot(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> taproot::TaprootSpendInfo {
804 let server_claim_after_expiry_clause = self.server_claim_after_expiry_clause(server_pubkey, exit_delta);
805 let user_reveals_preimage_clause = self.user_reveals_preimage_clause(exit_delta);
806
807 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
808 .x_only_public_key().0;
809 bitcoin::taproot::TaprootBuilder::new()
810 .add_leaf(1, server_claim_after_expiry_clause.tapscript()).unwrap()
811 .add_leaf(1, user_reveals_preimage_clause.tapscript()).unwrap()
812 .finalize(&SECP, combined_pk).unwrap()
813 }
814}
815
816impl From<ServerHtlcRecvVtxoPolicy> for VtxoPolicy {
817 fn from(policy: ServerHtlcRecvVtxoPolicy) -> Self {
818 Self::ServerHtlcRecv(policy)
819 }
820}
821
822#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
828pub struct HarkForfeitVtxoPolicy {
829 pub user_pubkey: PublicKey,
830 pub unlock_hash: UnlockHash,
831}
832
833impl HarkForfeitVtxoPolicy {
834 pub fn server_claim_clause(
836 &self,
837 server_pubkey: PublicKey,
838 ) -> HashSignClause {
839 HashSignClause {
840 pubkey: server_pubkey,
841 hash: self.unlock_hash,
842 }
843 }
844
845 pub fn user_exit_clause(
847 &self,
848 exit_delta: BlockDelta,
849 ) -> DelayedSignClause {
850 DelayedSignClause {
851 pubkey: self.user_pubkey,
852 block_delta: exit_delta
853 }
854 }
855
856 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
857 vec![
858 self.server_claim_clause(server_pubkey).into(),
859 self.user_exit_clause(exit_delta).into(),
860 ]
861 }
862
863 pub fn taproot(
864 &self,
865 server_pubkey: PublicKey,
866 exit_delta: BlockDelta,
867 ) -> taproot::TaprootSpendInfo {
868 let server_claim_clause = self.server_claim_clause(server_pubkey);
869 let user_exit_clause = self.user_exit_clause(exit_delta);
870
871 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
872 .x_only_public_key().0;
873 bitcoin::taproot::TaprootBuilder::new()
874 .add_leaf(1, server_claim_clause.tapscript()).unwrap()
875 .add_leaf(1, user_exit_clause.tapscript()).unwrap()
876 .finalize(&SECP, combined_pk).unwrap()
877 }
878}
879
880impl From<HarkForfeitVtxoPolicy> for ServerVtxoPolicy {
881 fn from(v: HarkForfeitVtxoPolicy) -> Self {
882 ServerVtxoPolicy::HarkForfeit(v)
883 }
884}
885
886#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
892#[allow(non_camel_case_types)]
893pub struct HarkForfeit_v0_VtxoPolicy {
894 pub user_pubkey: PublicKey,
895 pub unlock_hash: UnlockHash,
896}
897
898impl HarkForfeit_v0_VtxoPolicy {
899 pub fn server_claim_clause(
901 &self,
902 server_pubkey: PublicKey,
903 ) -> HashSignClause_v0 {
904 HashSignClause_v0 {
905 pubkey: server_pubkey,
906 hash: self.unlock_hash,
907 }
908 }
909
910 pub fn user_exit_clause(
912 &self,
913 exit_delta: BlockDelta,
914 ) -> DelayedSignClause {
915 DelayedSignClause {
916 pubkey: self.user_pubkey,
917 block_delta: exit_delta
918 }
919 }
920
921 pub fn clauses(&self, exit_delta: BlockDelta, server_pubkey: PublicKey) -> Vec<VtxoClause> {
922 vec![
923 self.server_claim_clause(server_pubkey).into(),
924 self.user_exit_clause(exit_delta).into(),
925 ]
926 }
927
928 pub fn taproot(
929 &self,
930 server_pubkey: PublicKey,
931 exit_delta: BlockDelta,
932 ) -> taproot::TaprootSpendInfo {
933 let server_claim_clause = self.server_claim_clause(server_pubkey);
934 let user_exit_clause = self.user_exit_clause(exit_delta);
935
936 let combined_pk = musig::combine_keys([self.user_pubkey, server_pubkey])
937 .x_only_public_key().0;
938 bitcoin::taproot::TaprootBuilder::new()
939 .add_leaf(1, server_claim_clause.tapscript()).unwrap()
940 .add_leaf(1, user_exit_clause.tapscript()).unwrap()
941 .finalize(&SECP, combined_pk).unwrap()
942 }
943}
944
945impl From<HarkForfeit_v0_VtxoPolicy> for ServerVtxoPolicy {
946 fn from(v: HarkForfeit_v0_VtxoPolicy) -> Self {
947 ServerVtxoPolicy::HarkForfeit_v0(v)
948 }
949}
950
951#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
956pub enum VtxoPolicy {
957 Pubkey(PubkeyVtxoPolicy),
965 ServerHtlcSend(ServerHtlcSendVtxoPolicy),
967 #[allow(non_camel_case_types)]
969 ServerHtlcSend_v0(ServerHtlcSend_v0_VtxoPolicy),
970 ServerHtlcRecv(ServerHtlcRecvVtxoPolicy),
972 #[allow(non_camel_case_types)]
974 ServerHtlcRecv_v0(ServerHtlcRecv_v0_VtxoPolicy),
975}
976
977impl VtxoPolicy {
978 pub fn new_pubkey(user_pubkey: PublicKey) -> Self {
979 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey })
980 }
981
982 pub fn new_server_htlc_send(
983 user_pubkey: PublicKey,
984 payment_hash: PaymentHash,
985 htlc_expiry: BlockHeight,
986 ) -> Self {
987 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry })
988 }
989
990 pub fn new_server_htlc_recv(
999 user_pubkey: PublicKey,
1000 payment_hash: PaymentHash,
1001 htlc_expiry: BlockHeight,
1002 htlc_expiry_delta: BlockDelta,
1003 ) -> Self {
1004 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1005 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1006 })
1007 }
1008
1009 pub fn as_pubkey(&self) -> Option<&PubkeyVtxoPolicy> {
1010 match self {
1011 Self::Pubkey(v) => Some(v),
1012 _ => None,
1013 }
1014 }
1015
1016 pub fn as_server_htlc_send(&self) -> Option<&ServerHtlcSendVtxoPolicy> {
1017 match self {
1018 Self::ServerHtlcSend(v) => Some(v),
1019 _ => None,
1020 }
1021 }
1022
1023 pub fn as_server_htlc_recv(&self) -> Option<&ServerHtlcRecvVtxoPolicy> {
1024 match self {
1025 Self::ServerHtlcRecv(v) => Some(v),
1026 _ => None,
1027 }
1028 }
1029
1030 pub fn policy_type(&self) -> VtxoPolicyKind {
1032 match self {
1033 Self::Pubkey { .. } => VtxoPolicyKind::Pubkey,
1034 Self::ServerHtlcSend { .. } => VtxoPolicyKind::ServerHtlcSend,
1035 Self::ServerHtlcRecv { .. } => VtxoPolicyKind::ServerHtlcRecv,
1036 Self::ServerHtlcSend_v0 { .. } => VtxoPolicyKind::ServerHtlcSend_v0,
1037 Self::ServerHtlcRecv_v0 { .. } => VtxoPolicyKind::ServerHtlcRecv_v0,
1038 }
1039 }
1040
1041 pub fn is_arkoor_compatible(&self) -> bool {
1043 match self {
1044 Self::Pubkey { .. } => true,
1045 Self::ServerHtlcSend { .. } => false,
1046 Self::ServerHtlcRecv { .. } => false,
1047 Self::ServerHtlcSend_v0 { .. } => false,
1048 Self::ServerHtlcRecv_v0 { .. } => false,
1049 }
1050 }
1051
1052 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
1056 match self {
1057 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
1058 Self::ServerHtlcSend { .. } => None,
1059 Self::ServerHtlcRecv { .. } => None,
1060 Self::ServerHtlcSend_v0 { .. } => None,
1061 Self::ServerHtlcRecv_v0 { .. } => None,
1062 }
1063 }
1064
1065 pub fn user_pubkey(&self) -> PublicKey {
1067 match self {
1068 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => *user_pubkey,
1069 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
1070 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
1071 Self::ServerHtlcSend_v0(ServerHtlcSend_v0_VtxoPolicy { user_pubkey, .. }) => *user_pubkey,
1072 Self::ServerHtlcRecv_v0(ServerHtlcRecv_v0_VtxoPolicy { user_pubkey, .. }) => *user_pubkey,
1073 }
1074 }
1075
1076 pub fn taproot(
1077 &self,
1078 server_pubkey: PublicKey,
1079 exit_delta: BlockDelta,
1080 expiry_height: BlockHeight,
1081 ) -> taproot::TaprootSpendInfo {
1082 let _ = expiry_height; match self {
1084 Self::Pubkey(policy) => policy.taproot(server_pubkey, exit_delta),
1085 Self::ServerHtlcSend(policy) => policy.taproot(server_pubkey, exit_delta),
1086 Self::ServerHtlcRecv(policy) => policy.taproot(server_pubkey, exit_delta),
1087 Self::ServerHtlcSend_v0(policy) => policy.taproot(server_pubkey, exit_delta),
1088 Self::ServerHtlcRecv_v0(policy) => policy.taproot(server_pubkey, exit_delta),
1089 }
1090 }
1091
1092 pub fn script_pubkey(
1093 &self,
1094 server_pubkey: PublicKey,
1095 exit_delta: BlockDelta,
1096 expiry_height: BlockHeight,
1097 ) -> ScriptBuf {
1098 self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
1099 }
1100
1101 pub(crate) fn txout(
1102 &self,
1103 amount: Amount,
1104 server_pubkey: PublicKey,
1105 exit_delta: BlockDelta,
1106 expiry_height: BlockHeight,
1107 ) -> TxOut {
1108 TxOut {
1109 value: amount,
1110 script_pubkey: self.script_pubkey(server_pubkey, exit_delta, expiry_height),
1111 }
1112 }
1113
1114 pub fn clauses(
1115 &self,
1116 exit_delta: BlockDelta,
1117 _expiry_height: BlockHeight,
1118 server_pubkey: PublicKey,
1119 ) -> Vec<VtxoClause> {
1120 match self {
1121 Self::Pubkey(policy) => policy.clauses(exit_delta),
1122 Self::ServerHtlcSend(policy) => policy.clauses(exit_delta, server_pubkey),
1123 Self::ServerHtlcRecv(policy) => policy.clauses(exit_delta, server_pubkey),
1124 Self::ServerHtlcSend_v0(policy) => policy.clauses(exit_delta, server_pubkey),
1125 Self::ServerHtlcRecv_v0(policy) => policy.clauses(exit_delta, server_pubkey),
1126 }
1127 }
1128}
1129
1130#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1135pub enum ServerVtxoPolicy {
1136 User(VtxoPolicy),
1138 ServerOwned,
1140 Checkpoint(CheckpointVtxoPolicy),
1142 Expiry(ExpiryVtxoPolicy),
1144 HarkLeaf(HarkLeafVtxoPolicy),
1146 #[allow(non_camel_case_types)]
1148 HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy),
1149 HarkForfeit(HarkForfeitVtxoPolicy),
1151 #[allow(non_camel_case_types)]
1153 HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy),
1154}
1155
1156impl From<VtxoPolicy> for ServerVtxoPolicy {
1157 fn from(p: VtxoPolicy) -> Self {
1158 Self::User(p)
1159 }
1160}
1161
1162impl From<HarkLeafVtxoPolicy> for ServerVtxoPolicy {
1163 fn from(p: HarkLeafVtxoPolicy) -> Self {
1164 Self::HarkLeaf(p)
1165 }
1166}
1167
1168impl From<HarkLeaf_v0_VtxoPolicy> for ServerVtxoPolicy {
1169 fn from(p: HarkLeaf_v0_VtxoPolicy) -> Self {
1170 Self::HarkLeaf_v0(p)
1171 }
1172}
1173
1174impl ServerVtxoPolicy {
1175 pub fn new_server_owned() -> Self {
1176 Self::ServerOwned
1177 }
1178
1179 pub fn new_checkpoint(user_pubkey: PublicKey) -> Self {
1180 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey })
1181 }
1182
1183 pub fn new_expiry(internal_key: bitcoin::secp256k1::XOnlyPublicKey) -> Self {
1184 Self::Expiry(ExpiryVtxoPolicy { internal_key })
1185 }
1186
1187 pub fn new_hark_leaf(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
1188 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash })
1189 }
1190
1191 pub fn new_hark_leaf_v0(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
1192 Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, unlock_hash })
1193 }
1194
1195 pub fn new_hark_forfeit(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
1196 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash })
1197 }
1198
1199 pub fn policy_type(&self) -> VtxoPolicyKind {
1201 match self {
1202 Self::User(p) => p.policy_type(),
1203 Self::ServerOwned => VtxoPolicyKind::ServerOwned,
1204 Self::Checkpoint { .. } => VtxoPolicyKind::Checkpoint,
1205 Self::Expiry { .. } => VtxoPolicyKind::Expiry,
1206 Self::HarkLeaf { .. } => VtxoPolicyKind::HarkLeaf,
1207 Self::HarkLeaf_v0 { .. } => VtxoPolicyKind::HarkLeaf_v0,
1208 Self::HarkForfeit { .. } => VtxoPolicyKind::HarkForfeit,
1209 Self::HarkForfeit_v0 { .. } => VtxoPolicyKind::HarkForfeit_v0,
1210 }
1211 }
1212
1213 pub fn is_arkoor_compatible(&self) -> bool {
1215 match self {
1216 Self::User(p) => p.is_arkoor_compatible(),
1217 Self::ServerOwned => false,
1218 Self::Checkpoint { .. } => true,
1219 Self::Expiry { .. } => false,
1220 Self::HarkLeaf { .. } => false,
1221 Self::HarkLeaf_v0 { .. } => false,
1222 Self::HarkForfeit { .. } => false,
1223 Self::HarkForfeit_v0 { .. } => false,
1224 }
1225 }
1226
1227 pub fn user_pubkey(&self) -> Option<PublicKey> {
1229 match self {
1230 Self::User(p) => Some(p.user_pubkey()),
1231 Self::ServerOwned => None,
1232 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
1233 Self::Expiry { .. } => None,
1234 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1235 Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1236 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1237 Self::HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1238 }
1239 }
1240
1241 pub fn taproot(
1242 &self,
1243 server_pubkey: PublicKey,
1244 exit_delta: BlockDelta,
1245 expiry_height: BlockHeight,
1246 ) -> taproot::TaprootSpendInfo {
1247 match self {
1248 Self::User(p) => p.taproot(server_pubkey, exit_delta, expiry_height),
1249 Self::ServerOwned => {
1250 taproot::TaprootBuilder::new()
1251 .finalize(&SECP, server_pubkey.x_only_public_key().0).unwrap()
1252 },
1253 Self::Checkpoint(policy) => policy.taproot(server_pubkey, expiry_height),
1254 Self::Expiry(policy) => policy.taproot(server_pubkey, expiry_height),
1255 Self::HarkLeaf(policy) => policy.taproot(server_pubkey, expiry_height),
1256 Self::HarkLeaf_v0(policy) => policy.taproot(server_pubkey, expiry_height),
1257 Self::HarkForfeit(policy) => policy.taproot(server_pubkey, exit_delta),
1258 Self::HarkForfeit_v0(policy) => policy.taproot(server_pubkey, exit_delta),
1259 }
1260 }
1261
1262 pub fn script_pubkey(
1263 &self,
1264 server_pubkey: PublicKey,
1265 exit_delta: BlockDelta,
1266 expiry_height: BlockHeight,
1267 ) -> ScriptBuf {
1268 self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
1269 }
1270
1271 pub fn clauses(
1272 &self,
1273 exit_delta: BlockDelta,
1274 expiry_height: BlockHeight,
1275 server_pubkey: PublicKey,
1276 ) -> Vec<VtxoClause> {
1277 match self {
1278 Self::User(p) => p.clauses(exit_delta, expiry_height, server_pubkey),
1279 Self::ServerOwned => vec![], Self::Checkpoint(policy) => policy.clauses(expiry_height, server_pubkey),
1281 Self::Expiry(policy) => policy.clauses(expiry_height, server_pubkey),
1282 Self::HarkLeaf(policy) => policy.clauses(expiry_height, server_pubkey),
1283 Self::HarkLeaf_v0(policy) => policy.clauses(expiry_height, server_pubkey),
1284 Self::HarkForfeit(policy) => policy.clauses(exit_delta, server_pubkey),
1285 Self::HarkForfeit_v0(policy) => policy.clauses(exit_delta, server_pubkey),
1286 }
1287 }
1288
1289 pub fn is_user_policy(&self) -> bool {
1291 matches!(self, ServerVtxoPolicy::User(_))
1292 }
1293
1294 pub fn into_user_policy(self) -> Option<VtxoPolicy> {
1296 match self {
1297 ServerVtxoPolicy::User(p) => Some(p),
1298 _ => None,
1299 }
1300 }
1301}
1302
1303impl Policy for VtxoPolicy {
1304 fn policy_type(&self) -> VtxoPolicyKind {
1305 VtxoPolicy::policy_type(self)
1306 }
1307
1308 fn taproot(
1309 &self,
1310 server_pubkey: PublicKey,
1311 exit_delta: BlockDelta,
1312 expiry_height: BlockHeight,
1313 ) -> taproot::TaprootSpendInfo {
1314 VtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
1315 }
1316
1317 fn clauses(
1318 &self,
1319 exit_delta: BlockDelta,
1320 expiry_height: BlockHeight,
1321 server_pubkey: PublicKey,
1322 ) -> Vec<VtxoClause> {
1323 VtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
1324 }
1325}
1326
1327impl Policy for ServerVtxoPolicy {
1328 fn policy_type(&self) -> VtxoPolicyKind {
1329 ServerVtxoPolicy::policy_type(self)
1330 }
1331
1332 fn taproot(
1333 &self,
1334 server_pubkey: PublicKey,
1335 exit_delta: BlockDelta,
1336 expiry_height: BlockHeight,
1337 ) -> taproot::TaprootSpendInfo {
1338 ServerVtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
1339 }
1340
1341 fn clauses(
1342 &self,
1343 exit_delta: BlockDelta,
1344 expiry_height: BlockHeight,
1345 server_pubkey: PublicKey,
1346 ) -> Vec<VtxoClause> {
1347 ServerVtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
1348 }
1349}
1350
1351#[cfg(test)]
1352mod tests {
1353 use std::str::FromStr;
1354
1355 use bitcoin::hashes::{sha256, Hash};
1356 use bitcoin::key::Keypair;
1357 use bitcoin::sighash::{self, SighashCache};
1358 use bitcoin::{Amount, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, Witness};
1359 use bitcoin::taproot::{self, TapLeafHash};
1360 use bitcoin_ext::{TaprootSpendInfoExt, fee};
1361
1362 use crate::{SECP, musig};
1363 use crate::test_util::verify_tx;
1364 use crate::vtxo::policy::clause::TapScriptClause;
1365
1366 use super::*;
1367
1368 lazy_static! {
1369 static ref USER_KEYPAIR: Keypair = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1370 static ref SERVER_KEYPAIR: Keypair = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1371 }
1372
1373 fn transaction() -> bitcoin::Transaction {
1374 let address = bitcoin::Address::from_str("tb1q00h5delzqxl7xae8ufmsegghcl4jwfvdnd8530")
1375 .unwrap().assume_checked();
1376
1377 bitcoin::Transaction {
1378 version: bitcoin::transaction::Version(3),
1379 lock_time: bitcoin::absolute::LockTime::ZERO,
1380 input: vec![],
1381 output: vec![TxOut {
1382 script_pubkey: address.script_pubkey(),
1383 value: Amount::from_sat(900_000),
1384 }, fee::fee_anchor()]
1385 }
1386 }
1387
1388 #[test]
1389 fn test_hark_leaf_vtxo_policy_unlock_clause() {
1390 let preimage = [0u8; 32];
1391 let unlock_hash = sha256::Hash::hash(&preimage);
1392
1393 let policy = HarkLeafVtxoPolicy {
1394 user_pubkey: USER_KEYPAIR.public_key(),
1395 unlock_hash,
1396 };
1397
1398 let expiry_height = BlockHeight::new(100_000);
1399
1400 let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1402 let unlock_clause = policy.unlock_clause(SERVER_KEYPAIR.public_key());
1403
1404 let tx_in = TxOut {
1405 script_pubkey: taproot.script_pubkey(),
1406 value: Amount::from_sat(1_000_000),
1407 };
1408
1409 let mut tx = transaction();
1411 tx.input.push(TxIn {
1412 previous_output: OutPoint::new(Txid::all_zeros(), 0),
1413 script_sig: ScriptBuf::default(),
1414 sequence: Sequence::ZERO,
1415 witness: Witness::new(),
1416 });
1417
1418 let cb = taproot
1420 .control_block(&(unlock_clause.tapscript(), taproot::LeafVersion::TapScript))
1421 .expect("script is in taproot");
1422
1423 let leaf_hash = TapLeafHash::from_script(
1425 &unlock_clause.tapscript(),
1426 taproot::LeafVersion::TapScript,
1427 );
1428 let mut shc = SighashCache::new(&tx);
1429 let sighash = shc.taproot_script_spend_signature_hash(
1430 0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1431 ).expect("all prevouts provided");
1432
1433 let (user_sec_nonce, user_pub_nonce) = musig::nonce_pair(&*USER_KEYPAIR);
1435 let (server_pub_nonce, server_part_sig) = musig::deterministic_partial_sign(
1436 &*SERVER_KEYPAIR,
1437 [USER_KEYPAIR.public_key()],
1438 &[&user_pub_nonce],
1439 sighash.to_byte_array(),
1440 None,
1441 );
1442 let agg_nonce = musig::nonce_agg(&[&user_pub_nonce, &server_pub_nonce]);
1443
1444 let (_user_part_sig, final_sig) = musig::partial_sign(
1445 [USER_KEYPAIR.public_key(), SERVER_KEYPAIR.public_key()],
1446 agg_nonce,
1447 &*USER_KEYPAIR,
1448 user_sec_nonce,
1449 sighash.to_byte_array(),
1450 None,
1451 Some(&[&server_part_sig]),
1452 );
1453 let final_sig = final_sig.expect("should have final signature");
1454
1455 tx.input[0].witness = unlock_clause.witness(&(final_sig, preimage), &cb);
1456
1457 verify_tx(&[tx_in], 0, &tx).expect("unlock clause spending should be valid");
1459 }
1460
1461 #[test]
1462 fn test_hark_leaf_vtxo_policy_expiry_clause() {
1463 let preimage = [0u8; 32];
1464 let unlock_hash = sha256::Hash::hash(&preimage);
1465
1466 let policy = HarkLeafVtxoPolicy {
1467 user_pubkey: USER_KEYPAIR.public_key(),
1468 unlock_hash,
1469 };
1470
1471 let expiry_height = BlockHeight::new(100);
1472
1473 let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1475 let expiry_clause = policy.expiry_clause(expiry_height, SERVER_KEYPAIR.public_key());
1476
1477 let tx_in = TxOut {
1478 script_pubkey: taproot.script_pubkey(),
1479 value: Amount::from_sat(1_000_000),
1480 };
1481
1482 let mut tx = transaction();
1484 tx.lock_time = expiry_clause.locktime();
1485 tx.input.push(TxIn {
1486 previous_output: OutPoint::new(Txid::all_zeros(), 0),
1487 script_sig: ScriptBuf::default(),
1488 sequence: Sequence::ZERO,
1489 witness: Witness::new(),
1490 });
1491
1492 let cb = taproot
1494 .control_block(&(expiry_clause.tapscript(), taproot::LeafVersion::TapScript))
1495 .expect("script is in taproot");
1496
1497 let leaf_hash = TapLeafHash::from_script(
1499 &expiry_clause.tapscript(),
1500 taproot::LeafVersion::TapScript,
1501 );
1502 let mut shc = SighashCache::new(&tx);
1503 let sighash = shc.taproot_script_spend_signature_hash(
1504 0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1505 ).expect("all prevouts provided");
1506
1507 let signature = SECP.sign_schnorr(&sighash.into(), &*SERVER_KEYPAIR);
1509
1510 tx.input[0].witness = expiry_clause.witness(&signature, &cb);
1511
1512 verify_tx(&[tx_in], 0, &tx).expect("expiry clause spending should be valid");
1514 }
1515}