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_forfeit(user_pubkey: PublicKey, unlock_hash: UnlockHash) -> Self {
1195 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash })
1196 }
1197
1198 pub fn policy_type(&self) -> VtxoPolicyKind {
1200 match self {
1201 Self::User(p) => p.policy_type(),
1202 Self::ServerOwned => VtxoPolicyKind::ServerOwned,
1203 Self::Checkpoint { .. } => VtxoPolicyKind::Checkpoint,
1204 Self::Expiry { .. } => VtxoPolicyKind::Expiry,
1205 Self::HarkLeaf { .. } => VtxoPolicyKind::HarkLeaf,
1206 Self::HarkLeaf_v0 { .. } => VtxoPolicyKind::HarkLeaf_v0,
1207 Self::HarkForfeit { .. } => VtxoPolicyKind::HarkForfeit,
1208 Self::HarkForfeit_v0 { .. } => VtxoPolicyKind::HarkForfeit_v0,
1209 }
1210 }
1211
1212 pub fn is_arkoor_compatible(&self) -> bool {
1214 match self {
1215 Self::User(p) => p.is_arkoor_compatible(),
1216 Self::ServerOwned => false,
1217 Self::Checkpoint { .. } => true,
1218 Self::Expiry { .. } => false,
1219 Self::HarkLeaf { .. } => false,
1220 Self::HarkLeaf_v0 { .. } => false,
1221 Self::HarkForfeit { .. } => false,
1222 Self::HarkForfeit_v0 { .. } => false,
1223 }
1224 }
1225
1226 pub fn user_pubkey(&self) -> Option<PublicKey> {
1228 match self {
1229 Self::User(p) => Some(p.user_pubkey()),
1230 Self::ServerOwned => None,
1231 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
1232 Self::Expiry { .. } => None,
1233 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1234 Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1235 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1236 Self::HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
1237 }
1238 }
1239
1240 pub fn taproot(
1241 &self,
1242 server_pubkey: PublicKey,
1243 exit_delta: BlockDelta,
1244 expiry_height: BlockHeight,
1245 ) -> taproot::TaprootSpendInfo {
1246 match self {
1247 Self::User(p) => p.taproot(server_pubkey, exit_delta, expiry_height),
1248 Self::ServerOwned => {
1249 taproot::TaprootBuilder::new()
1250 .finalize(&SECP, server_pubkey.x_only_public_key().0).unwrap()
1251 },
1252 Self::Checkpoint(policy) => policy.taproot(server_pubkey, expiry_height),
1253 Self::Expiry(policy) => policy.taproot(server_pubkey, expiry_height),
1254 Self::HarkLeaf(policy) => policy.taproot(server_pubkey, expiry_height),
1255 Self::HarkLeaf_v0(policy) => policy.taproot(server_pubkey, expiry_height),
1256 Self::HarkForfeit(policy) => policy.taproot(server_pubkey, exit_delta),
1257 Self::HarkForfeit_v0(policy) => policy.taproot(server_pubkey, exit_delta),
1258 }
1259 }
1260
1261 pub fn script_pubkey(
1262 &self,
1263 server_pubkey: PublicKey,
1264 exit_delta: BlockDelta,
1265 expiry_height: BlockHeight,
1266 ) -> ScriptBuf {
1267 self.taproot(server_pubkey, exit_delta, expiry_height).script_pubkey()
1268 }
1269
1270 pub fn clauses(
1271 &self,
1272 exit_delta: u16,
1273 expiry_height: BlockHeight,
1274 server_pubkey: PublicKey,
1275 ) -> Vec<VtxoClause> {
1276 match self {
1277 Self::User(p) => p.clauses(exit_delta, expiry_height, server_pubkey),
1278 Self::ServerOwned => vec![], Self::Checkpoint(policy) => policy.clauses(expiry_height, server_pubkey),
1280 Self::Expiry(policy) => policy.clauses(expiry_height, server_pubkey),
1281 Self::HarkLeaf(policy) => policy.clauses(expiry_height, server_pubkey),
1282 Self::HarkLeaf_v0(policy) => policy.clauses(expiry_height, server_pubkey),
1283 Self::HarkForfeit(policy) => policy.clauses(exit_delta, server_pubkey),
1284 Self::HarkForfeit_v0(policy) => policy.clauses(exit_delta, server_pubkey),
1285 }
1286 }
1287
1288 pub fn is_user_policy(&self) -> bool {
1290 matches!(self, ServerVtxoPolicy::User(_))
1291 }
1292
1293 pub fn into_user_policy(self) -> Option<VtxoPolicy> {
1295 match self {
1296 ServerVtxoPolicy::User(p) => Some(p),
1297 _ => None,
1298 }
1299 }
1300}
1301
1302impl Policy for VtxoPolicy {
1303 fn policy_type(&self) -> VtxoPolicyKind {
1304 VtxoPolicy::policy_type(self)
1305 }
1306
1307 fn taproot(
1308 &self,
1309 server_pubkey: PublicKey,
1310 exit_delta: BlockDelta,
1311 expiry_height: BlockHeight,
1312 ) -> taproot::TaprootSpendInfo {
1313 VtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
1314 }
1315
1316 fn clauses(
1317 &self,
1318 exit_delta: u16,
1319 expiry_height: BlockHeight,
1320 server_pubkey: PublicKey,
1321 ) -> Vec<VtxoClause> {
1322 VtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
1323 }
1324}
1325
1326impl Policy for ServerVtxoPolicy {
1327 fn policy_type(&self) -> VtxoPolicyKind {
1328 ServerVtxoPolicy::policy_type(self)
1329 }
1330
1331 fn taproot(
1332 &self,
1333 server_pubkey: PublicKey,
1334 exit_delta: BlockDelta,
1335 expiry_height: BlockHeight,
1336 ) -> taproot::TaprootSpendInfo {
1337 ServerVtxoPolicy::taproot(self, server_pubkey, exit_delta, expiry_height)
1338 }
1339
1340 fn clauses(
1341 &self,
1342 exit_delta: u16,
1343 expiry_height: BlockHeight,
1344 server_pubkey: PublicKey,
1345 ) -> Vec<VtxoClause> {
1346 ServerVtxoPolicy::clauses(self, exit_delta, expiry_height, server_pubkey)
1347 }
1348}
1349
1350#[cfg(test)]
1351mod tests {
1352 use std::str::FromStr;
1353
1354 use bitcoin::hashes::{sha256, Hash};
1355 use bitcoin::key::Keypair;
1356 use bitcoin::sighash::{self, SighashCache};
1357 use bitcoin::{Amount, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, Witness};
1358 use bitcoin::taproot::{self, TapLeafHash};
1359 use bitcoin_ext::{TaprootSpendInfoExt, fee};
1360
1361 use crate::{SECP, musig};
1362 use crate::test_util::verify_tx;
1363 use crate::vtxo::policy::clause::TapScriptClause;
1364
1365 use super::*;
1366
1367 lazy_static! {
1368 static ref USER_KEYPAIR: Keypair = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1369 static ref SERVER_KEYPAIR: Keypair = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1370 }
1371
1372 fn transaction() -> bitcoin::Transaction {
1373 let address = bitcoin::Address::from_str("tb1q00h5delzqxl7xae8ufmsegghcl4jwfvdnd8530")
1374 .unwrap().assume_checked();
1375
1376 bitcoin::Transaction {
1377 version: bitcoin::transaction::Version(3),
1378 lock_time: bitcoin::absolute::LockTime::ZERO,
1379 input: vec![],
1380 output: vec![TxOut {
1381 script_pubkey: address.script_pubkey(),
1382 value: Amount::from_sat(900_000),
1383 }, fee::fee_anchor()]
1384 }
1385 }
1386
1387 #[test]
1388 fn test_hark_leaf_vtxo_policy_unlock_clause() {
1389 let preimage = [0u8; 32];
1390 let unlock_hash = sha256::Hash::hash(&preimage);
1391
1392 let policy = HarkLeafVtxoPolicy {
1393 user_pubkey: USER_KEYPAIR.public_key(),
1394 unlock_hash,
1395 };
1396
1397 let expiry_height = 100_000;
1398
1399 let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1401 let unlock_clause = policy.unlock_clause(SERVER_KEYPAIR.public_key());
1402
1403 let tx_in = TxOut {
1404 script_pubkey: taproot.script_pubkey(),
1405 value: Amount::from_sat(1_000_000),
1406 };
1407
1408 let mut tx = transaction();
1410 tx.input.push(TxIn {
1411 previous_output: OutPoint::new(Txid::all_zeros(), 0),
1412 script_sig: ScriptBuf::default(),
1413 sequence: Sequence::ZERO,
1414 witness: Witness::new(),
1415 });
1416
1417 let cb = taproot
1419 .control_block(&(unlock_clause.tapscript(), taproot::LeafVersion::TapScript))
1420 .expect("script is in taproot");
1421
1422 let leaf_hash = TapLeafHash::from_script(
1424 &unlock_clause.tapscript(),
1425 taproot::LeafVersion::TapScript,
1426 );
1427 let mut shc = SighashCache::new(&tx);
1428 let sighash = shc.taproot_script_spend_signature_hash(
1429 0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1430 ).expect("all prevouts provided");
1431
1432 let (user_sec_nonce, user_pub_nonce) = musig::nonce_pair(&*USER_KEYPAIR);
1434 let (server_pub_nonce, server_part_sig) = musig::deterministic_partial_sign(
1435 &*SERVER_KEYPAIR,
1436 [USER_KEYPAIR.public_key()],
1437 &[&user_pub_nonce],
1438 sighash.to_byte_array(),
1439 None,
1440 );
1441 let agg_nonce = musig::nonce_agg(&[&user_pub_nonce, &server_pub_nonce]);
1442
1443 let (_user_part_sig, final_sig) = musig::partial_sign(
1444 [USER_KEYPAIR.public_key(), SERVER_KEYPAIR.public_key()],
1445 agg_nonce,
1446 &*USER_KEYPAIR,
1447 user_sec_nonce,
1448 sighash.to_byte_array(),
1449 None,
1450 Some(&[&server_part_sig]),
1451 );
1452 let final_sig = final_sig.expect("should have final signature");
1453
1454 tx.input[0].witness = unlock_clause.witness(&(final_sig, preimage), &cb);
1455
1456 verify_tx(&[tx_in], 0, &tx).expect("unlock clause spending should be valid");
1458 }
1459
1460 #[test]
1461 fn test_hark_leaf_vtxo_policy_expiry_clause() {
1462 let preimage = [0u8; 32];
1463 let unlock_hash = sha256::Hash::hash(&preimage);
1464
1465 let policy = HarkLeafVtxoPolicy {
1466 user_pubkey: USER_KEYPAIR.public_key(),
1467 unlock_hash,
1468 };
1469
1470 let expiry_height = 100;
1471
1472 let taproot = policy.taproot(SERVER_KEYPAIR.public_key(), expiry_height);
1474 let expiry_clause = policy.expiry_clause(expiry_height, SERVER_KEYPAIR.public_key());
1475
1476 let tx_in = TxOut {
1477 script_pubkey: taproot.script_pubkey(),
1478 value: Amount::from_sat(1_000_000),
1479 };
1480
1481 let mut tx = transaction();
1483 tx.lock_time = expiry_clause.locktime();
1484 tx.input.push(TxIn {
1485 previous_output: OutPoint::new(Txid::all_zeros(), 0),
1486 script_sig: ScriptBuf::default(),
1487 sequence: Sequence::ZERO,
1488 witness: Witness::new(),
1489 });
1490
1491 let cb = taproot
1493 .control_block(&(expiry_clause.tapscript(), taproot::LeafVersion::TapScript))
1494 .expect("script is in taproot");
1495
1496 let leaf_hash = TapLeafHash::from_script(
1498 &expiry_clause.tapscript(),
1499 taproot::LeafVersion::TapScript,
1500 );
1501 let mut shc = SighashCache::new(&tx);
1502 let sighash = shc.taproot_script_spend_signature_hash(
1503 0, &sighash::Prevouts::All(&[tx_in.clone()]), leaf_hash, sighash::TapSighashType::Default,
1504 ).expect("all prevouts provided");
1505
1506 let signature = SECP.sign_schnorr(&sighash.into(), &*SERVER_KEYPAIR);
1508
1509 tx.input[0].witness = expiry_clause.witness(&signature, &cb);
1510
1511 verify_tx(&[tx_in], 0, &tx).expect("expiry clause spending should be valid");
1513 }
1514}