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