1pub mod policy;
57
58pub use self::validation::VtxoValidationError;
59pub use self::policy::{VtxoPolicy, VtxoPolicyKind};
60
61pub use self::policy::{
62 PubkeyVtxoPolicy, CheckpointVtxoPolicy, ServerHtlcRecvVtxoPolicy,
63 ServerHtlcSendVtxoPolicy
64};
65pub use self::policy::clause::{
66 VtxoClause, DelayedSignClause, DelayedTimelockSignClause, HashDelaySignClause,
67 TapScriptClause,
68};
69
70mod validation;
71
72use std::collections::HashSet;
73use std::iter::FusedIterator;
74use std::{fmt, io};
75use std::str::FromStr;
76
77use bitcoin::{
78 taproot, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness
79};
80use bitcoin::absolute::LockTime;
81use bitcoin::hashes::{sha256, Hash};
82use bitcoin::secp256k1::{schnorr, PublicKey, XOnlyPublicKey};
83use bitcoin::taproot::LeafVersion;
84
85use bitcoin_ext::{fee, BlockDelta, BlockHeight, TaprootSpendInfoExt};
86
87use crate::{musig, scripts};
88use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
89use crate::lightning::PaymentHash;
90use crate::tree::signed::{cosign_taproot, leaf_cosign_taproot, unlock_clause, UnlockHash, UnlockPreimage};
91
92pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
94
95const VTXO_ENCODING_VERSION: u16 = 1;
97
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
100#[error("failed to parse vtxo id, must be 36 bytes")]
101pub struct VtxoIdParseError;
102
103#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
104pub struct VtxoId([u8; 36]);
105
106impl VtxoId {
107 pub const ENCODE_SIZE: usize = 36;
109
110 pub fn from_slice(b: &[u8]) -> Result<VtxoId, VtxoIdParseError> {
111 if b.len() == 36 {
112 let mut ret = [0u8; 36];
113 ret[..].copy_from_slice(&b[0..36]);
114 Ok(Self(ret))
115 } else {
116 Err(VtxoIdParseError)
117 }
118 }
119
120 pub fn utxo(self) -> OutPoint {
121 let vout = [self.0[32], self.0[33], self.0[34], self.0[35]];
122 OutPoint::new(Txid::from_slice(&self.0[0..32]).unwrap(), u32::from_le_bytes(vout))
123 }
124
125 pub fn to_bytes(self) -> [u8; 36] {
126 self.0
127 }
128}
129
130impl From<OutPoint> for VtxoId {
131 fn from(p: OutPoint) -> VtxoId {
132 let mut ret = [0u8; 36];
133 ret[0..32].copy_from_slice(&p.txid[..]);
134 ret[32..].copy_from_slice(&p.vout.to_le_bytes());
135 VtxoId(ret)
136 }
137}
138
139impl AsRef<[u8]> for VtxoId {
140 fn as_ref(&self) -> &[u8] {
141 &self.0
142 }
143}
144
145impl fmt::Display for VtxoId {
146 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147 fmt::Display::fmt(&self.utxo(), f)
148 }
149}
150
151impl fmt::Debug for VtxoId {
152 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153 fmt::Display::fmt(self, f)
154 }
155}
156
157impl FromStr for VtxoId {
158 type Err = VtxoIdParseError;
159 fn from_str(s: &str) -> Result<Self, Self::Err> {
160 Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
161 }
162}
163
164impl serde::Serialize for VtxoId {
165 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
166 if s.is_human_readable() {
167 s.collect_str(self)
168 } else {
169 s.serialize_bytes(self.as_ref())
170 }
171 }
172}
173
174impl<'de> serde::Deserialize<'de> for VtxoId {
175 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
176 struct Visitor;
177 impl<'de> serde::de::Visitor<'de> for Visitor {
178 type Value = VtxoId;
179 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 write!(f, "a VtxoId")
181 }
182 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
183 VtxoId::from_slice(v).map_err(serde::de::Error::custom)
184 }
185 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
186 VtxoId::from_str(v).map_err(serde::de::Error::custom)
187 }
188 }
189 if d.is_human_readable() {
190 d.deserialize_str(Visitor)
191 } else {
192 d.deserialize_bytes(Visitor)
193 }
194 }
195}
196
197impl ProtocolEncoding for VtxoId {
198 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
199 w.emit_slice(&self.0)
200 }
201 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
202 let array: [u8; 36] = r.read_byte_array()
203 .map_err(|_| ProtocolDecodingError::invalid("invalid vtxo id. Expected 36 bytes"))?;
204
205 Ok(VtxoId(array))
206 }
207}
208
209pub(crate) fn exit_clause(
211 user_pubkey: PublicKey,
212 exit_delta: BlockDelta,
213) -> ScriptBuf {
214 scripts::delayed_sign(exit_delta, user_pubkey.x_only_public_key().0)
215}
216
217pub fn create_exit_tx(
222 prevout: OutPoint,
223 output: TxOut,
224 signature: Option<&schnorr::Signature>,
225) -> Transaction {
226 Transaction {
227 version: bitcoin::transaction::Version(3),
228 lock_time: LockTime::ZERO,
229 input: vec![TxIn {
230 previous_output: prevout,
231 script_sig: ScriptBuf::new(),
232 sequence: Sequence::ZERO,
233 witness: {
234 let mut ret = Witness::new();
235 if let Some(sig) = signature {
236 ret.push(&sig[..]);
237 }
238 ret
239 },
240 }],
241 output: vec![output, fee::fee_anchor()],
242 }
243}
244
245pub(crate) enum TransitionKind {
247 Cosigned,
248 HashLockedCosigned,
249 Arkoor,
250}
251
252impl TransitionKind {
253 pub fn as_str(&self) -> &'static str {
254 match self {
255 Self::Cosigned => "cosigned",
256 Self::HashLockedCosigned => "hash-locked-cosigned",
257 Self::Arkoor => "arkoor",
258 }
259 }
260}
261
262impl fmt::Display for TransitionKind {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 f.write_str(self.as_str())
265 }
266}
267
268impl fmt::Debug for TransitionKind {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 fmt::Display::fmt(self, f)
271 }
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub(crate) enum MaybePreimage {
279 Preimage([u8; 32]),
280 Hash(sha256::Hash),
281}
282
283impl MaybePreimage {
284 pub fn hash(&self) -> sha256::Hash {
286 match self {
287 Self::Preimage(p) => sha256::Hash::hash(p),
288 Self::Hash(h) => *h,
289 }
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
297pub(crate) enum GenesisTransition {
298 Cosigned {
303 pubkeys: Vec<PublicKey>,
308 signature: schnorr::Signature,
309 },
310 HashLockedCosigned {
319 user_pubkey: PublicKey,
321 signature: Option<schnorr::Signature>,
323 unlock: MaybePreimage,
325 },
326 Arkoor {
328 policy: VtxoPolicy,
329 signature: Option<schnorr::Signature>,
330 },
331}
332
333impl GenesisTransition {
334 fn input_taproot(
336 &self,
337 server_pubkey: PublicKey,
338 expiry_height: BlockHeight,
339 exit_delta: BlockDelta,
340 ) -> taproot::TaprootSpendInfo {
341 match self {
342 Self::Cosigned { pubkeys, .. } => {
343 let agg_pk = musig::combine_keys(pubkeys.iter().copied());
344 cosign_taproot(agg_pk, server_pubkey, expiry_height)
345 },
346 Self::HashLockedCosigned { user_pubkey, unlock, .. } => {
347 leaf_cosign_taproot(*user_pubkey, server_pubkey, expiry_height, unlock.hash())
348 },
349 Self::Arkoor { policy, .. } => policy.taproot(server_pubkey, exit_delta, expiry_height),
350 }
351 }
352
353 fn input_txout(
355 &self,
356 amount: Amount,
357 server_pubkey: PublicKey,
358 expiry_height: BlockHeight,
359 exit_delta: BlockDelta,
360 ) -> TxOut {
361 let taproot = self.input_taproot(server_pubkey, expiry_height, exit_delta);
362 TxOut {
363 value: amount,
364 script_pubkey: taproot.script_pubkey(),
365 }
366 }
367
368 fn witness(
370 &self,
371 server_pubkey: PublicKey,
372 expiry_height: BlockHeight,
373 ) -> Witness {
374 match self {
375 Self::Cosigned { signature, .. } => Witness::from_slice(&[&signature[..]]),
376 Self::HashLockedCosigned {
377 user_pubkey,
378 signature: Some(sig),
379 unlock: MaybePreimage::Preimage(preimage),
380 } => {
381 let unlock_hash = sha256::Hash::hash(preimage);
382 let taproot = leaf_cosign_taproot(
383 *user_pubkey, server_pubkey, expiry_height, unlock_hash,
384 );
385 let clause = unlock_clause(taproot.internal_key(), unlock_hash);
386 let script_leaf = (clause, LeafVersion::TapScript);
387 let cb = taproot.control_block(&script_leaf)
388 .expect("unlock clause not found in hArk taproot");
389 Witness::from_slice(&[
390 &sig.serialize()[..],
391 &preimage[..],
392 &script_leaf.0.as_bytes(),
393 &cb.serialize()[..],
394 ])
395 },
396 Self::HashLockedCosigned { .. } => {
397 Witness::new()
399 },
400 Self::Arkoor { signature: Some(sig), .. } => Witness::from_slice(&[&sig[..]]),
401 Self::Arkoor { signature: None, .. } => Witness::new(),
402 }
403 }
404
405
406 fn is_fully_signed(&self) -> bool {
408 match self {
409 Self::Cosigned { .. } => true,
410 Self::HashLockedCosigned {
411 unlock: MaybePreimage::Preimage(_),
412 signature: Some(_),
413 ..
414 } => true,
415 Self::HashLockedCosigned { .. } => false,
416 Self::Arkoor { signature, .. } => signature.is_some(),
417 }
418 }
419
420 fn kind(&self) -> TransitionKind {
422 match self {
423 Self::Cosigned { .. } => TransitionKind::Cosigned,
424 Self::HashLockedCosigned { .. } => TransitionKind::HashLockedCosigned,
425 Self::Arkoor { .. } => TransitionKind::Arkoor,
426 }
427 }
428}
429
430#[derive(Debug, Clone, PartialEq, Eq)]
434pub(crate) struct GenesisItem {
435 pub(crate) transition: GenesisTransition,
437 pub(crate) output_idx: u8,
439 pub(crate) other_outputs: Vec<TxOut>,
442}
443
444impl GenesisItem {
445 fn tx(&self,
447 prev: OutPoint,
448 next: TxOut,
449 server_pubkey: PublicKey,
450 expiry_height: BlockHeight,
451 ) -> Transaction {
452 Transaction {
453 version: bitcoin::transaction::Version(3),
454 lock_time: bitcoin::absolute::LockTime::ZERO,
455 input: vec![TxIn {
456 previous_output: prev,
457 script_sig: ScriptBuf::new(),
458 sequence: Sequence::ZERO,
459 witness: self.transition.witness(server_pubkey, expiry_height),
460 }],
461 output: {
462 let mut out = Vec::with_capacity(self.other_outputs.len() + 2);
463 out.extend(self.other_outputs.iter().take(self.output_idx as usize).cloned());
464 out.push(next);
465 out.extend(self.other_outputs.iter().skip(self.output_idx as usize).cloned());
466 out.push(fee::fee_anchor());
467 out
468 },
469 }
470 }
471}
472
473#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
476pub struct VtxoTxIterItem {
477 pub tx: Transaction,
479 pub output_idx: usize,
481}
482
483pub struct VtxoTxIter<'a> {
485 vtxo: &'a Vtxo,
486
487 prev: OutPoint,
488 genesis_idx: usize,
489 current_amount: Amount,
490 done: bool,
491}
492
493impl<'a> VtxoTxIter<'a> {
494 fn new(vtxo: &'a Vtxo) -> VtxoTxIter<'a> {
495 let onchain_amount = vtxo.amount() + vtxo.genesis.iter().map(|i| {
497 i.other_outputs.iter().map(|o| o.value).sum()
498 }).sum();
499 VtxoTxIter {
500 prev: vtxo.anchor_point,
501 vtxo: vtxo,
502 genesis_idx: 0,
503 current_amount: onchain_amount,
504 done: false,
505 }
506 }
507}
508
509impl<'a> Iterator for VtxoTxIter<'a> {
510 type Item = VtxoTxIterItem;
511
512 fn next(&mut self) -> Option<Self::Item> {
513 if self.done {
514 return None;
515 }
516
517 let item = self.vtxo.genesis.get(self.genesis_idx).expect("broken impl");
518 let next_amount = self.current_amount.checked_sub(
519 item.other_outputs.iter().map(|o| o.value).sum()
520 ).expect("we calculated this amount beforehand");
521
522 let next_output = if let Some(item) = self.vtxo.genesis.get(self.genesis_idx + 1) {
523 item.transition.input_txout(
524 next_amount,
525 self.vtxo.server_pubkey,
526 self.vtxo.expiry_height,
527 self.vtxo.exit_delta,
528 )
529 } else {
530 self.done = true;
532 self.vtxo.policy.txout(self.vtxo.amount, self.vtxo.server_pubkey, self.vtxo.exit_delta, self.vtxo.expiry_height)
533 };
534
535 let tx = item.tx(self.prev, next_output, self.vtxo.server_pubkey, self.vtxo.expiry_height);
536 self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
537 self.genesis_idx += 1;
538 self.current_amount = next_amount;
539 let output_idx = item.output_idx as usize;
540 Some(VtxoTxIterItem { tx, output_idx })
541 }
542
543 fn size_hint(&self) -> (usize, Option<usize>) {
544 let len = self.vtxo.genesis.len().saturating_sub(self.genesis_idx);
545 (len, Some(len))
546 }
547}
548
549impl<'a> ExactSizeIterator for VtxoTxIter<'a> {}
550impl<'a> FusedIterator for VtxoTxIter<'a> {}
551
552
553#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
555pub struct VtxoSpec {
556 pub policy: VtxoPolicy,
557 pub amount: Amount,
558 pub expiry_height: BlockHeight,
559 pub server_pubkey: PublicKey,
560 pub exit_delta: BlockDelta,
561}
562
563impl VtxoSpec {
564 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
566 self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
567 }
568
569 pub fn output_script_pubkey(&self) -> ScriptBuf {
571 self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
572 }
573
574 pub fn txout(&self) -> TxOut {
576 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
577 }
578}
579
580#[derive(Debug, Clone)]
594pub struct Vtxo {
595 pub(crate) policy: VtxoPolicy,
596 pub(crate) amount: Amount,
597 pub(crate) expiry_height: BlockHeight,
598
599 pub(crate) server_pubkey: PublicKey,
600 pub(crate) exit_delta: BlockDelta,
601
602 pub(crate) anchor_point: OutPoint,
603 pub(crate) genesis: Vec<GenesisItem>,
604
605 pub(crate) point: OutPoint,
612}
613
614impl Vtxo {
615 pub fn id(&self) -> VtxoId {
619 self.point.into()
620 }
621
622 pub fn spec(&self) -> VtxoSpec {
624 VtxoSpec {
625 policy: self.policy.clone(),
626 amount: self.amount,
627 expiry_height: self.expiry_height,
628 server_pubkey: self.server_pubkey,
629 exit_delta: self.exit_delta,
630 }
631 }
632
633 pub fn point(&self) -> OutPoint {
637 self.point
638 }
639
640 pub fn amount(&self) -> Amount {
642 self.amount
643 }
644
645 pub fn chain_anchor(&self) -> OutPoint {
649 self.anchor_point
650 }
651
652 pub fn policy(&self) -> &VtxoPolicy {
654 &self.policy
655 }
656
657 pub fn policy_type(&self) -> VtxoPolicyKind {
659 self.policy.policy_type()
660 }
661
662 pub fn expiry_height(&self) -> BlockHeight {
664 self.expiry_height
665 }
666
667 pub fn server_pubkey(&self) -> PublicKey {
669 self.server_pubkey
670 }
671
672 pub fn exit_delta(&self) -> BlockDelta {
674 self.exit_delta
675 }
676
677 pub fn exit_depth(&self) -> u16 {
679 self.genesis.len() as u16
680 }
681
682 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
686 self.policy.arkoor_pubkey()
687 }
688
689 pub fn past_arkoor_pubkeys(&self) -> impl Iterator<Item = PublicKey> + '_ {
694 self.genesis.iter().filter_map(|g| {
695 match g.transition {
696 GenesisTransition::Arkoor { ref policy, .. } => policy.arkoor_pubkey(),
699 _ => None,
700 }
701 })
702 }
703
704
705 pub fn user_pubkey(&self) -> PublicKey {
707 self.policy.user_pubkey()
708 }
709
710 pub(crate) fn forfeit_agg_pubkey(&self) -> XOnlyPublicKey {
713 let ret = musig::combine_keys([self.user_pubkey(), self.server_pubkey()]);
714 debug_assert_eq!(ret, self.output_taproot().internal_key());
715 ret
716 }
717
718 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
720 self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
721 }
722
723 pub fn output_script_pubkey(&self) -> ScriptBuf {
725 self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
726 }
727
728 pub fn txout(&self) -> TxOut {
730 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
731 }
732
733 pub fn is_fully_signed(&self) -> bool {
738 self.genesis.iter().all(|g| g.transition.is_fully_signed())
739 }
740
741 pub fn transactions(&self) -> VtxoTxIter<'_> {
743 VtxoTxIter::new(self)
744 }
745
746 pub fn arkoor_pubkeys(&self) -> HashSet<PublicKey> {
749 self.genesis.iter().filter_map(|i| match &i.transition {
750 GenesisTransition::Arkoor { policy, .. } => policy.arkoor_pubkey(),
751 GenesisTransition::Cosigned { .. } => None,
752 GenesisTransition::HashLockedCosigned { .. } => None,
753 }).collect()
754 }
755
756 pub fn validate(
761 &self,
762 chain_anchor_tx: &Transaction,
763 ) -> Result<(), VtxoValidationError> {
764 self::validation::validate(&self, chain_anchor_tx)
765 }
766
767 pub fn unlock_hash(&self) -> Option<UnlockHash> {
769 match self.genesis.last()?.transition {
770 GenesisTransition::HashLockedCosigned { unlock, .. } => Some(unlock.hash()),
771 _ => None,
772 }
773 }
774
775 pub fn provide_unlock_signature(&mut self, signature: schnorr::Signature) -> bool {
779 match self.genesis.last_mut().map(|g| &mut g.transition) {
780 Some(GenesisTransition::HashLockedCosigned { signature: ref mut sig, .. }) => {
781 *sig = Some(signature);
782 true
783 },
784 _ => false,
785 }
786 }
787
788 pub fn provide_unlock_preimage(&mut self, preimage: UnlockPreimage) -> bool {
792 match self.genesis.last_mut().map(|g| &mut g.transition) {
793 Some(GenesisTransition::HashLockedCosigned { ref mut unlock, .. }) => {
794 if unlock.hash() == UnlockHash::hash(&preimage) {
795 *unlock = MaybePreimage::Preimage(preimage);
796 true
797 } else {
798 false
799 }
800 },
801 _ => false,
802 }
803 }
804
805 #[cfg(any(test, feature = "test-util"))]
807 pub fn finalize_hark_leaf(
808 &mut self,
809 user_key: &bitcoin::secp256k1::Keypair,
810 server_key: &bitcoin::secp256k1::Keypair,
811 chain_anchor: &Transaction,
812 unlock_preimage: UnlockPreimage,
813 ) {
814 use crate::tree::signed::{LeafVtxoCosignContext, LeafVtxoCosignResponse};
815
816 let (ctx, req) = LeafVtxoCosignContext::new(self, chain_anchor, user_key);
818 let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key);
819 assert!(ctx.finalize(self, cosign));
820 assert!(self.provide_unlock_preimage(unlock_preimage));
822 }
823}
824
825impl PartialEq for Vtxo {
826 fn eq(&self, other: &Self) -> bool {
827 PartialEq::eq(&self.id(), &other.id())
828 }
829}
830
831impl Eq for Vtxo {}
832
833impl PartialOrd for Vtxo {
834 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
835 PartialOrd::partial_cmp(&self.id(), &other.id())
836 }
837}
838
839impl Ord for Vtxo {
840 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
841 Ord::cmp(&self.id(), &other.id())
842 }
843}
844
845impl std::hash::Hash for Vtxo {
846 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
847 std::hash::Hash::hash(&self.id(), state)
848 }
849}
850
851impl AsRef<Vtxo> for Vtxo {
852 fn as_ref(&self) -> &Vtxo {
853 self
854 }
855}
856
857pub trait VtxoRef {
859 fn vtxo_id(&self) -> VtxoId;
861
862 fn vtxo(&self) -> Option<&Vtxo>;
864}
865
866impl VtxoRef for VtxoId {
867 fn vtxo_id(&self) -> VtxoId { *self }
868 fn vtxo(&self) -> Option<&Vtxo> { None }
869}
870
871impl<'a> VtxoRef for &'a VtxoId {
872 fn vtxo_id(&self) -> VtxoId { **self }
873 fn vtxo(&self) -> Option<&Vtxo> { None }
874}
875
876impl VtxoRef for Vtxo {
877 fn vtxo_id(&self) -> VtxoId { self.id() }
878 fn vtxo(&self) -> Option<&Vtxo> { Some(self) }
879}
880
881impl<'a> VtxoRef for &'a Vtxo {
882 fn vtxo_id(&self) -> VtxoId { self.id() }
883 fn vtxo(&self) -> Option<&Vtxo> { Some(*self) }
884}
885
886const VTXO_POLICY_PUBKEY: u8 = 0x00;
888
889const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
891
892const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
894
895const VTXO_CHECKPOINT_CHECKPOINT: u8 = 0x03;
897
898impl ProtocolEncoding for VtxoPolicy {
899 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
900 match self {
901 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
902 w.emit_u8(VTXO_POLICY_PUBKEY)?;
903 user_pubkey.encode(w)?;
904 },
905 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
906 w.emit_u8(VTXO_CHECKPOINT_CHECKPOINT)?;
907 user_pubkey.encode(w)?;
908 },
909 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
910 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
911 user_pubkey.encode(w)?;
912 payment_hash.to_sha256_hash().encode(w)?;
913 w.emit_u32(*htlc_expiry)?;
914 },
915 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
916 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
917 }) => {
918 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
919 user_pubkey.encode(w)?;
920 payment_hash.to_sha256_hash().encode(w)?;
921 w.emit_u32(*htlc_expiry)?;
922 w.emit_u16(*htlc_expiry_delta)?;
923 },
924 }
925 Ok(())
926 }
927
928 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
929 match r.read_u8()? {
930 VTXO_POLICY_PUBKEY => {
931 let user_pubkey = PublicKey::decode(r)?;
932 Ok(Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
933 },
934 VTXO_CHECKPOINT_CHECKPOINT => {
935 let user_pubkey = PublicKey::decode(r)?;
936 Ok(Self::new_checkpoint(user_pubkey))
937 }
938 VTXO_POLICY_SERVER_HTLC_SEND => {
939 let user_pubkey = PublicKey::decode(r)?;
940 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
941 let htlc_expiry = r.read_u32()?;
942 Ok(Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
943 },
944 VTXO_POLICY_SERVER_HTLC_RECV => {
945 let user_pubkey = PublicKey::decode(r)?;
946 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
947 let htlc_expiry = r.read_u32()?;
948 let htlc_expiry_delta = r.read_u16()?;
949 Ok(Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
950 },
951 v => Err(ProtocolDecodingError::invalid(format_args!(
952 "invalid VtxoType type byte: {v:#x}",
953 ))),
954 }
955 }
956}
957
958const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
960
961const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
963
964const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
966
967impl ProtocolEncoding for GenesisTransition {
968 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
969 match self {
970 Self::Cosigned { pubkeys, signature } => {
971 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
972 w.emit_u16(pubkeys.len().try_into().expect("cosign pubkey length overflow"))?;
973 for pk in pubkeys {
974 pk.encode(w)?;
975 }
976 signature.encode(w)?;
977 },
978 Self::HashLockedCosigned { user_pubkey, signature, unlock } => {
979 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
980 user_pubkey.encode(w)?;
981 signature.encode(w)?;
982 match unlock {
983 MaybePreimage::Preimage(p) => {
984 w.emit_u8(0)?;
985 w.emit_slice(&p[..])?;
986 },
987 MaybePreimage::Hash(h) => {
988 w.emit_u8(1)?;
989 w.emit_slice(&h[..])?;
990 },
991 }
992 },
993 Self::Arkoor { policy, signature } => {
994 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
995 policy.encode(w)?;
996 signature.encode(w)?;
997 },
998 }
999 Ok(())
1000 }
1001
1002 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1003 match r.read_u8()? {
1004 GENESIS_TRANSITION_TYPE_COSIGNED => {
1005 let nb_pubkeys = r.read_u16()? as usize;
1006 let mut pubkeys = Vec::with_capacity(nb_pubkeys);
1007 for _ in 0..nb_pubkeys {
1008 pubkeys.push(PublicKey::decode(r)?);
1009 }
1010 let signature = schnorr::Signature::decode(r)?;
1011 Ok(Self::Cosigned { pubkeys, signature })
1012 },
1013 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
1014 let user_pubkey = PublicKey::decode(r)?;
1015 let signature = Option::<schnorr::Signature>::decode(r)?;
1016 let unlock = match r.read_u8()? {
1017 0 => MaybePreimage::Preimage(r.read_byte_array()?),
1018 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
1019 v => return Err(ProtocolDecodingError::invalid(format_args!(
1020 "invalid HashLockedCosignedTransitionWitness type byte: {v:#x}",
1021 ))),
1022 };
1023 Ok(Self::HashLockedCosigned { user_pubkey, signature, unlock })
1024 },
1025 GENESIS_TRANSITION_TYPE_ARKOOR => {
1026 let policy = VtxoPolicy::decode(r)?;
1027 let signature = Option::<schnorr::Signature>::decode(r)?;
1028 Ok(Self::Arkoor { policy, signature })
1029 },
1030 v => Err(ProtocolDecodingError::invalid(format_args!(
1031 "invalid GenesisTransistion type byte: {v:#x}",
1032 ))),
1033 }
1034 }
1035}
1036
1037impl ProtocolEncoding for Vtxo {
1038 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1039 w.emit_u16(VTXO_ENCODING_VERSION)?;
1040 w.emit_u64(self.amount.to_sat())?;
1041 w.emit_u32(self.expiry_height)?;
1042 self.server_pubkey.encode(w)?;
1043 w.emit_u16(self.exit_delta)?;
1044 self.anchor_point.encode(w)?;
1045
1046 w.emit_u8(self.genesis.len().try_into().expect("genesis length overflow"))?;
1047 for item in &self.genesis {
1048 item.transition.encode(w)?;
1049 let nb_outputs = item.other_outputs.len() + 1;
1050 w.emit_u8(nb_outputs.try_into().expect("genesis item output length overflow"))?;
1051 w.emit_u8(item.output_idx)?;
1052 for txout in &item.other_outputs {
1053 txout.encode(w)?;
1054 }
1055 }
1056
1057 self.policy.encode(w)?;
1058 self.point.encode(w)?;
1059 Ok(())
1060 }
1061
1062 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1063 let version = r.read_u16()?;
1064 if version != VTXO_ENCODING_VERSION {
1065 return Err(ProtocolDecodingError::invalid(format_args!(
1066 "invalid Vtxo encoding version byte: {version:#x}",
1067 )));
1068 }
1069
1070 let amount = Amount::from_sat(r.read_u64()?);
1071 let expiry_height = r.read_u32()?;
1072 let server_pubkey = PublicKey::decode(r)?;
1073 let exit_delta = r.read_u16()?;
1074 let anchor_point = OutPoint::decode(r)?;
1075
1076 let nb_genesis_items = r.read_u8()? as usize;
1077 let mut genesis = Vec::with_capacity(nb_genesis_items);
1078 for _ in 0..nb_genesis_items {
1079 let transition = GenesisTransition::decode(r)?;
1080 let nb_outputs = r.read_u8()? as usize;
1081 let output_idx = r.read_u8()?;
1082 let nb_other = nb_outputs.checked_sub(1)
1083 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1084 let mut other_outputs = Vec::with_capacity(nb_other);
1085 for _ in 0..nb_other {
1086 other_outputs.push(TxOut::decode(r)?);
1087 }
1088 genesis.push(GenesisItem { transition, output_idx, other_outputs });
1089 }
1090
1091 let output = VtxoPolicy::decode(r)?;
1092 let point = OutPoint::decode(r)?;
1093
1094 Ok(Self {
1095 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy: output, point,
1096 })
1097 }
1098}
1099
1100
1101#[cfg(any(test, feature = "test-util"))]
1102pub mod test {
1103 use std::iter;
1104 use std::collections::HashMap;
1105
1106 use bitcoin::consensus::encode::{deserialize_hex, serialize_hex};
1107 use bitcoin::hex::{DisplayHex, FromHex};
1108 use bitcoin::secp256k1::Keypair;
1109 use bitcoin::transaction::Version;
1110
1111 use crate::tree::signed::{VtxoLeafSpec, VtxoTreeSpec};
1112use crate::{VtxoRequest, SECP};
1113 use crate::arkoor::ArkoorBuilder;
1114 use crate::board::BoardBuilder;
1115 use crate::encode::test::encoding_roundtrip;
1116
1117 use super::*;
1118
1119 #[allow(unused)]
1120 #[macro_export]
1121 macro_rules! assert_eq_vtxos {
1122 ($v1:expr, $v2:expr) => {
1123 let v1 = &$v1;
1124 let v2 = &$v2;
1125 assert_eq!(
1126 v1.serialize().as_hex().to_string(),
1127 v2.serialize().as_hex().to_string(),
1128 "vtxo {} != {}", v1.id(), v2.id(),
1129 );
1130 };
1131 }
1132
1133 #[derive(Debug, PartialEq, Eq)]
1134 pub struct VtxoTestVectors {
1135 pub server_key: Keypair,
1136
1137 pub anchor_tx: Transaction,
1138 pub board_vtxo: Vtxo,
1139
1140 pub arkoor_htlc_out_vtxo: Vtxo,
1141 pub arkoor2_vtxo: Vtxo,
1142
1143 pub round_tx: Transaction,
1144 pub round1_vtxo: Vtxo,
1145 pub round2_vtxo: Vtxo,
1146
1147 pub arkoor3_user_key: Keypair,
1148 pub arkoor3_vtxo: Vtxo,
1149 }
1150
1151 #[allow(unused)] fn generate_vtxo_vectors() -> VtxoTestVectors {
1153 let expiry_height = 101_010;
1154 let exit_delta = 2016;
1155 let server_key = Keypair::from_str("916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92").unwrap();
1156 let board_user_key = Keypair::from_str("fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879").unwrap();
1157 let amount = Amount::from_sat(10_000);
1158 let builder = BoardBuilder::new(
1159 board_user_key.public_key(),
1160 expiry_height,
1161 server_key.public_key(),
1162 exit_delta,
1163 );
1164 let anchor_tx = Transaction {
1165 version: Version::TWO,
1166 lock_time: LockTime::ZERO,
1167 input: vec![TxIn {
1168 previous_output: OutPoint::null(),
1169 script_sig: ScriptBuf::new(),
1170 sequence: Sequence::ZERO,
1171 witness: Witness::new(),
1172 }],
1173 output: vec![TxOut {
1174 value: Amount::from_sat(10_000),
1175 script_pubkey: builder.funding_script_pubkey(),
1176 }],
1177 };
1178 println!("chain anchor tx: {}", serialize_hex(&anchor_tx));
1179 let anchor_point = OutPoint::new(anchor_tx.compute_txid(), 0);
1180 let builder = builder.set_funding_details(amount, anchor_point)
1181 .generate_user_nonces();
1182
1183 let board_cosign = {
1184 BoardBuilder::new_for_cosign(
1185 builder.user_pubkey,
1186 builder.expiry_height,
1187 builder.server_pubkey,
1188 builder.exit_delta,
1189 amount,
1190 anchor_point,
1191 *builder.user_pub_nonce(),
1192 ).server_cosign(&server_key)
1193 };
1194
1195 assert!(builder.verify_cosign_response(&board_cosign));
1196 let board_vtxo = builder.build_vtxo(&board_cosign, &board_user_key).unwrap();
1197 encoding_roundtrip(&board_vtxo);
1198 println!("board vtxo: {}", board_vtxo.serialize().as_hex());
1199
1200 let arkoor_htlc_out_user_key = Keypair::from_str("33b6f3ede430a1a53229f55da7117242d8392cbfc64a57249ba70731dba71408").unwrap();
1203 let payment_hash = PaymentHash::from(sha256::Hash::hash("arkoor1".as_bytes()).to_byte_array());
1204 let arkoor1out1 = VtxoRequest {
1205 amount: Amount::from_sat(9000),
1206 policy: VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy {
1207 user_pubkey: arkoor_htlc_out_user_key.public_key(),
1208 payment_hash,
1209 htlc_expiry: expiry_height - 1000,
1210 }),
1211 };
1212 let arkoor1out2 = VtxoRequest {
1213 amount: Amount::from_sat(1000),
1214 policy: VtxoPolicy::new_pubkey("0229b7de0ce4d573192d002a6f9fd1109e00f7bae52bf10780d6f6e73e12a8390f".parse().unwrap()),
1215 };
1216 let outputs = [&arkoor1out1, &arkoor1out2];
1217 let (sec_nonce, pub_nonce) = musig::nonce_pair(&board_user_key);
1218 let builder = ArkoorBuilder::new(&board_vtxo, &pub_nonce, &outputs).unwrap();
1219 let cosign = builder.server_cosign(&server_key);
1220 assert!(builder.verify_cosign_response(&cosign));
1221 let [arkoor_htlc_out_vtxo, change] = builder.build_vtxos(
1222 sec_nonce, &board_user_key, &cosign,
1223 ).unwrap().try_into().unwrap();
1224 encoding_roundtrip(&arkoor_htlc_out_vtxo);
1225 encoding_roundtrip(&change);
1226 println!("arkoor1_vtxo: {}", arkoor_htlc_out_vtxo.serialize().as_hex());
1227
1228 let arkoor2_user_key = Keypair::from_str("fcc43a4f03356092a945ca1d7218503156bed3f94c2fa224578ce5b158fbf5a6").unwrap();
1231 let arkoor2out1 = VtxoRequest {
1232 amount: Amount::from_sat(8000),
1233 policy: VtxoPolicy::new_pubkey(arkoor2_user_key.public_key()),
1234 };
1235 let arkoor2out2 = VtxoRequest {
1236 amount: Amount::from_sat(1000),
1237 policy: VtxoPolicy::new_pubkey("037039dc4f4b16e78059d2d56eb98d181cb1bdff2675694d39d92c4a2ea08ced88".parse().unwrap()),
1238 };
1239 let outputs = [&arkoor2out1, &arkoor2out2];
1240 let (sec_nonce, pub_nonce) = musig::nonce_pair(&arkoor_htlc_out_user_key);
1241 let builder = ArkoorBuilder::new(&arkoor_htlc_out_vtxo, &pub_nonce, &outputs).unwrap();
1242 let arkoor2_cosign = builder.server_cosign(&server_key);
1243 assert!(builder.verify_cosign_response(&arkoor2_cosign));
1244 let [arkoor2_vtxo, change] = builder.build_vtxos(
1245 sec_nonce, &arkoor_htlc_out_user_key, &arkoor2_cosign,
1246 ).unwrap().try_into().unwrap();
1247 encoding_roundtrip(&arkoor2_vtxo);
1248 encoding_roundtrip(&change);
1249 println!("arkoor2_vtxo: {}", arkoor2_vtxo.serialize().as_hex());
1250
1251 let round1_user_key = Keypair::from_str("0a832e9574070c94b5b078600a18639321c880c830c5ba2f2a96850c7dcc4725").unwrap();
1255 let round1_cosign_key = Keypair::from_str("e14bfc3199842c76816eec1d93c9da00b850c4ed19e414e246d07e845e465a2b").unwrap();
1256 let round1_unlock_preimage = UnlockPreimage::from_hex("c05bc2f82c8c64e470cd4d87aca42989b46879ca32320cd035db124bb78c4e74").unwrap();
1257 let round1_unlock_hash = UnlockHash::hash(&round1_unlock_preimage);
1258 println!("round1_cosign_key: {}", round1_cosign_key.public_key());
1259 let round1_req = VtxoLeafSpec {
1260 vtxo: VtxoRequest {
1261 amount: Amount::from_sat(10_000),
1262 policy: VtxoPolicy::new_pubkey(round1_user_key.public_key()),
1263 },
1264 cosign_pubkey: Some(round1_cosign_key.public_key()),
1265 unlock_hash: round1_unlock_hash,
1266 };
1267 let round1_nonces = iter::repeat_with(|| musig::nonce_pair(&round1_cosign_key)).take(5).collect::<Vec<_>>();
1268
1269 let round2_user_key = Keypair::from_str("c0b645b01cac427717a18b30c7c9238dee2b3885f659930144fbe05061ad6166").unwrap();
1270 let round2_cosign_key = Keypair::from_str("628789cd7b7e02766d184ecfecc433798c9640349e41822df7996c66a56fc633").unwrap();
1271 let round2_unlock_preimage = UnlockPreimage::from_hex("61050792ef121826fda248a789c8ba75b955844c65acd2c6361950bdd31dae7d").unwrap();
1272 let round2_unlock_hash = UnlockHash::hash(&round2_unlock_preimage);
1273 println!("round2_cosign_key: {}", round2_cosign_key.public_key());
1274 let round2_payment_hash = PaymentHash::from(sha256::Hash::hash("round2".as_bytes()).to_byte_array());
1275 let round2_req = VtxoLeafSpec {
1276 vtxo: VtxoRequest {
1277 amount: Amount::from_sat(10_000),
1278 policy: VtxoPolicy::new_server_htlc_recv(
1279 round2_user_key.public_key(),
1280 round2_payment_hash,
1281 expiry_height - 2000,
1282 40,
1283 ),
1284 },
1285 cosign_pubkey: Some(round2_cosign_key.public_key()),
1286 unlock_hash: round2_unlock_hash,
1287 };
1288 let round2_nonces = iter::repeat_with(|| musig::nonce_pair(&round2_cosign_key)).take(5).collect::<Vec<_>>();
1289
1290 let others = [
1291 "93b376f64ada74f0fbf940be86f888459ac94655dc6a7805cc790b3c95a2a612",
1292 "00add86ff531ef53f877780622f0b376669ec6ad7e090131820ff7007e79f529",
1293 "775b836f2acf53de4ff9beeba2a17d5475e9b027d82fece72033ef06b954c7cd",
1294 "395c2c210481990a5d12d33dca37995e235a34b717c89647a33907c62e32dc09",
1295 "8f02f2a7aa1746bbcc92bba607b7166b6a77e9d0efd9d09dae7c2dc3addbdef1",
1296 ];
1297 let mut other_reqs = Vec::new();
1298 let mut other_nonces = Vec::new();
1299 for k in others {
1300 let user_key = Keypair::from_str(k).unwrap();
1301 let cosign_key = Keypair::from_seckey_slice(&SECP, &sha256::Hash::hash(k.as_bytes())[..]).unwrap();
1302 other_reqs.push(VtxoLeafSpec {
1303 vtxo: VtxoRequest {
1304 amount: Amount::from_sat(5_000),
1305 policy: VtxoPolicy::new_pubkey(user_key.public_key()),
1306 },
1307 cosign_pubkey: Some(cosign_key.public_key()),
1308 unlock_hash: sha256::Hash::hash(k.as_bytes()),
1309 });
1310 other_nonces.push(iter::repeat_with(|| musig::nonce_pair(&cosign_key)).take(5).collect::<Vec<_>>());
1311 }
1312
1313 let server_cosign_key = Keypair::from_str("4371a4a7989b89ebe1b2582db4cd658cb95070977e6f10601ddc1e9b53edee79").unwrap();
1314 let spec = VtxoTreeSpec::new(
1315 [&round1_req, &round2_req].into_iter().chain(other_reqs.iter()).cloned().collect(),
1316 server_key.public_key(),
1317 expiry_height,
1318 exit_delta,
1319 vec![server_cosign_key.public_key()],
1320 );
1321 let round_tx = Transaction {
1322 version: Version::TWO,
1323 lock_time: LockTime::ZERO,
1324 input: vec![TxIn {
1325 previous_output: OutPoint::null(),
1326 script_sig: ScriptBuf::new(),
1327 sequence: Sequence::ZERO,
1328 witness: Witness::new(),
1329 }],
1330 output: vec![TxOut {
1331 value: Amount::from_sat(45_000),
1332 script_pubkey: spec.funding_tx_script_pubkey(),
1333 }],
1334 };
1335 println!("round tx: {}", serialize_hex(&round_tx));
1336 let all_nonces = {
1337 let mut map = HashMap::new();
1338 map.insert(round1_cosign_key.public_key(), round1_nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1339 map.insert(round2_cosign_key.public_key(), round2_nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1340 for (req, nonces) in other_reqs.iter().zip(other_nonces.iter()) {
1341 map.insert(req.cosign_pubkey.unwrap(), nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1342 }
1343 map
1344 };
1345 let (server_cosign_sec_nonces, server_cosign_pub_nonces) = iter::repeat_with(|| {
1346 musig::nonce_pair(&server_cosign_key)
1347 }).take(spec.nb_internal_nodes()).unzip::<_, _, Vec<_>, Vec<_>>();
1348 let cosign_agg_nonces = spec.calculate_cosign_agg_nonces(&all_nonces, &[&server_cosign_pub_nonces]).unwrap();
1349 let root_point = OutPoint::new(round_tx.compute_txid(), 0);
1350 let tree = spec.into_unsigned_tree(root_point);
1351 let part_sigs = {
1352 let mut map = HashMap::new();
1353 map.insert(round1_cosign_key.public_key(), {
1354 let secs = round1_nonces.into_iter().map(|(s, _)| s).collect();
1355 let r = tree.cosign_branch(&cosign_agg_nonces, 0, &round1_cosign_key, secs).unwrap();
1356 r
1357 });
1358 map.insert(round2_cosign_key.public_key(), {
1359 let secs = round2_nonces.into_iter().map(|(s, _)| s).collect();
1360 tree.cosign_branch(&cosign_agg_nonces, 1, &round2_cosign_key, secs).unwrap()
1361 });
1362 for (i, (req, nonces)) in other_reqs.iter().zip(other_nonces.into_iter()).enumerate() {
1363 let cosign_key = Keypair::from_seckey_slice(
1364 &SECP, &sha256::Hash::hash(others[i].as_bytes())[..],
1365 ).unwrap();
1366 map.insert(req.cosign_pubkey.unwrap(), {
1367 let secs = nonces.into_iter().map(|(s, _)| s).collect();
1368 tree.cosign_branch(&cosign_agg_nonces, 2 + i, &cosign_key, secs).unwrap()
1369 });
1370 }
1371 map
1372 };
1373 let server_cosign_sigs = tree.cosign_tree(
1374 &cosign_agg_nonces, &server_cosign_key, server_cosign_sec_nonces,
1375 );
1376 let cosign_sigs = tree.combine_partial_signatures(&cosign_agg_nonces, &part_sigs, &[&server_cosign_sigs]).unwrap();
1377 if let Err(pk) = tree.verify_cosign_sigs(&cosign_sigs) {
1378 panic!("invalid cosign sig for pk: {}", pk);
1379 }
1380 let signed = tree.into_signed_tree(cosign_sigs).into_cached_tree();
1381 let mut vtxo_iter = signed.all_vtxos();
1383 let round1_vtxo = {
1384 let mut ret = vtxo_iter.next().unwrap();
1385 ret.finalize_hark_leaf(&round1_user_key, &server_key, &round_tx, round1_unlock_preimage);
1386 ret
1387 };
1388 encoding_roundtrip(&round1_vtxo);
1389 println!("round1_vtxo: {}", round1_vtxo.serialize().as_hex());
1390 let round2_vtxo = {
1391 let mut ret = vtxo_iter.next().unwrap();
1392 ret.finalize_hark_leaf(&round2_user_key, &server_key, &round_tx, round2_unlock_preimage);
1393 ret
1394 };
1395 encoding_roundtrip(&round2_vtxo);
1396 println!("round2_vtxo: {}", round2_vtxo.serialize().as_hex());
1397
1398 let arkoor3_user_key = Keypair::from_str("ad12595bdbdab56cb61d1f60ccc46ff96b11c5d6fe06ae7ba03d3a5f4347440f").unwrap();
1401 let arkoor3out = VtxoRequest {
1402 amount: Amount::from_sat(10_000),
1403 policy: VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey: arkoor3_user_key.public_key() }),
1404 };
1405 let outputs = [&arkoor3out];
1406 let (sec_nonce, pub_nonce) = musig::nonce_pair(&round2_user_key);
1407 let builder = ArkoorBuilder::new(&round2_vtxo, &pub_nonce, &outputs).unwrap();
1408 let arkoor3_cosign = builder.server_cosign(&server_key);
1409 assert!(builder.verify_cosign_response(&arkoor3_cosign));
1410 let [arkoor3_vtxo] = builder.build_vtxos(
1411 sec_nonce, &round2_user_key, &arkoor3_cosign,
1412 ).unwrap().try_into().unwrap();
1413 encoding_roundtrip(&arkoor3_vtxo);
1414 println!("arkoor3_vtxo: {}", arkoor3_vtxo.serialize().as_hex());
1415
1416 VtxoTestVectors {
1417 server_key,
1418 anchor_tx,
1419 board_vtxo,
1420 arkoor_htlc_out_vtxo,
1421 arkoor2_vtxo,
1422 round_tx,
1423 round1_vtxo,
1424 round2_vtxo,
1425 arkoor3_user_key,
1426 arkoor3_vtxo,
1427 }
1428 }
1429
1430 lazy_static! {
1431 pub static ref VTXO_VECTORS: VtxoTestVectors = VtxoTestVectors {
1433 server_key: Keypair::from_str("916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92").unwrap(),
1434 anchor_tx: deserialize_hex("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0000000000011027000000000000225120652675904a84ea02e24b57b3d547203d2ce71526113d35bf4d02e0b4efbe9a2d00000000").unwrap(),
1435 board_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000001010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62be19abd3f0f0ee3a1ffa9a86b6e13ab9899aef88e5c10c14a047c0256ea880a234913cf2a63c90c851bd9a3e7cd7ce1eba919726a406d34c59123275536622f5010000030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4c99b744ad009b7070f330794bf003fa8e5cd46ea1a6eb854aaf469385e3080000000000").unwrap(),
1436 arkoor_htlc_out_vtxo: ProtocolEncoding::deserialize_hex("01002823000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000002010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62be19abd3f0f0ee3a1ffa9a86b6e13ab9899aef88e5c10c14a047c0256ea880a234913cf2a63c90c851bd9a3e7cd7ce1eba919726a406d34c59123275536622f501000200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee276bf265c622ad9953eb58fd9ffe6149db0a73ea8c96440a41abfcd93607d2eff4dd118204706a0fbc4d5b39928f9cdb9b8ded7897a88f2342c57286ee62a9a70200e8030000000000002251209b987ec3c169c70d1ed6aef420a4858e3e3ec9d8404358787d4e06ba926a4ae40103eb4570ae385202d4a48f06bdb14126910b90c07f8e42d7dc5e28a860c085e73712358912c950a9a7d04bb9011ee9f6a16b6127a5aab7415803d48c0225f620f5aa860100c692b81703c12cac1e8d69b86fa9f0e2f167168d96ae1045ef8d9192bc4a6e4c00000000").unwrap(),
1437 arkoor2_vtxo: ProtocolEncoding::deserialize_hex("0100401f000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000003010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62be19abd3f0f0ee3a1ffa9a86b6e13ab9899aef88e5c10c14a047c0256ea880a234913cf2a63c90c851bd9a3e7cd7ce1eba919726a406d34c59123275536622f501000200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee276bf265c622ad9953eb58fd9ffe6149db0a73ea8c96440a41abfcd93607d2eff4dd118204706a0fbc4d5b39928f9cdb9b8ded7897a88f2342c57286ee62a9a70200e8030000000000002251209b987ec3c169c70d1ed6aef420a4858e3e3ec9d8404358787d4e06ba926a4ae4020103eb4570ae385202d4a48f06bdb14126910b90c07f8e42d7dc5e28a860c085e73712358912c950a9a7d04bb9011ee9f6a16b6127a5aab7415803d48c0225f620f5aa8601005389e08b902a976190d3b82840b22bce981da8bbc9bd2542e233e55bde8fc9d013a866de301e4520a3c46ca8e5db5fac5148b4800db1acef551460346ee00b210200e80300000000000022512018d297ade3cfbb7080b65e21af238ac88c15e38734f5f462530c34a225e80ca9000265cca13271cafd0b90c440e722a1937b7c4faf4ccd7dee0548d152c24ce4b2a8d4f7d410cf052720ffc5ce4668c4371448ffe98b7037f7c42aa943d717fcd67700000000").unwrap(),
1438 round_tx: deserialize_hex("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff000000000001c8af000000000000225120649657f65947abfd83ff629ad8a851c795f419ed4d52a2748d3f868cc3e6c94d00000000").unwrap(),
1439 round1_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000003010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743d68dced601990ed5e801408667b4744139013fa7acc805de339273781d1c3772334dcc197608e27e3bad4fa945b7f1e23cfe937a435761efe6600c92a46ee5e4040388130000000000002251205acb7b65f8da14622a055640893e952e20f68e051087b85be4d56e50cdafd4318813000000000000225120973b9be7e6ee51f8851347130113e4001ab1d01252dd1d09713a6c900cb327f2881300000000000022512052cc228fe0f4951032fbaeb45ed8b73163cedb897412407e5b431d740040a951010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b24274314d914345bc48d57028578041cff47fa577882c06fb41dadbc75fbb98584d0832749a1c342d40d5f37144c1273e1de62ab9e8ff449489ee97a9dd1969c0369d304001027000000000000225120e9d56cdf22598ce6c05950b3580e194a19e53f8b887fc6c4111ca2a82a0608a88813000000000000225120c3731a9dc38c67dfa2dd206ee346d6225f1f37b97d77d518c59b9c9a291762288813000000000000225120a4ad17a5f329a164977981f1b7638c7a70b0dd1bed29a85637aed2952dd2e38c030374a3ec37cc4ccd29717388e6dc24f2aa366632f1a36a49e73cd7671b2317929869f0d1cbbb1c8619d468452423e12a9a9d907dfdece1c34ad2839ed426f3595d699968a34e8f749e76840d60a077b052afeee0d4503a0ba66b153ae14f4bd26600c05bc2f82c8c64e470cd4d87aca42989b46879ca32320cd035db124bb78c4e740100000374a3ec37cc4ccd29717388e6dc24f2aa366632f1a36a49e73cd7671b2317929862d6c4b8e408915af8279d4f14431f517a0c9ecc46fae2e8b0a5f72cfcf506c800000000").unwrap(),
1440 round2_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000003010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743d68dced601990ed5e801408667b4744139013fa7acc805de339273781d1c3772334dcc197608e27e3bad4fa945b7f1e23cfe937a435761efe6600c92a46ee5e4040388130000000000002251205acb7b65f8da14622a055640893e952e20f68e051087b85be4d56e50cdafd4318813000000000000225120973b9be7e6ee51f8851347130113e4001ab1d01252dd1d09713a6c900cb327f2881300000000000022512052cc228fe0f4951032fbaeb45ed8b73163cedb897412407e5b431d740040a951010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b24274314d914345bc48d57028578041cff47fa577882c06fb41dadbc75fbb98584d0832749a1c342d40d5f37144c1273e1de62ab9e8ff449489ee97a9dd1969c0369d3040110270000000000002251202ec5640d3ba147e40c916e8fa9b0ee89557d10465db1d55a49c87edebe53104c8813000000000000225120c3731a9dc38c67dfa2dd206ee346d6225f1f37b97d77d518c59b9c9a291762288813000000000000225120a4ad17a5f329a164977981f1b7638c7a70b0dd1bed29a85637aed2952dd2e38c030256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecda051e991f7f5216f09f5e0ddac4b32ad401920a914582385471ec9c7637e31410372c3f9398035291a7ca84e0a0ff221dea11d19d5b5b4fb56d8b88f4504c6f80061050792ef121826fda248a789c8ba75b955844c65acd2c6361950bdd31dae7d0100020256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecd9ea50d885c3f66d40d27e779648ba8dc730629663f65a3e6f7749b4a35b6dfecc28201002800ca6a1d9ccb57f92a11eb4383517f0046482462046eeb9090496785f1893b766f00000000").unwrap(),
1441 arkoor3_user_key: Keypair::from_str("ad12595bdbdab56cb61d1f60ccc46ff96b11c5d6fe06ae7ba03d3a5f4347440f").unwrap(),
1442 arkoor3_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000004010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743d68dced601990ed5e801408667b4744139013fa7acc805de339273781d1c3772334dcc197608e27e3bad4fa945b7f1e23cfe937a435761efe6600c92a46ee5e4040388130000000000002251205acb7b65f8da14622a055640893e952e20f68e051087b85be4d56e50cdafd4318813000000000000225120973b9be7e6ee51f8851347130113e4001ab1d01252dd1d09713a6c900cb327f2881300000000000022512052cc228fe0f4951032fbaeb45ed8b73163cedb897412407e5b431d740040a951010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b24274314d914345bc48d57028578041cff47fa577882c06fb41dadbc75fbb98584d0832749a1c342d40d5f37144c1273e1de62ab9e8ff449489ee97a9dd1969c0369d3040110270000000000002251202ec5640d3ba147e40c916e8fa9b0ee89557d10465db1d55a49c87edebe53104c8813000000000000225120c3731a9dc38c67dfa2dd206ee346d6225f1f37b97d77d518c59b9c9a291762288813000000000000225120a4ad17a5f329a164977981f1b7638c7a70b0dd1bed29a85637aed2952dd2e38c030256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecda051e991f7f5216f09f5e0ddac4b32ad401920a914582385471ec9c7637e31410372c3f9398035291a7ca84e0a0ff221dea11d19d5b5b4fb56d8b88f4504c6f80061050792ef121826fda248a789c8ba75b955844c65acd2c6361950bdd31dae7d010002020256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecd9ea50d885c3f66d40d27e779648ba8dc730629663f65a3e6f7749b4a35b6dfecc28201002800d8514cc3341861cd91d1312562290f5961ce4355ce4f35bf40cd7d034a2d7eabc9d9c35b02428ef1e6714963bc1dde192efb4294ac75e76cfe560acfa13d643901000002ed1334f116cea9128e1f59f1d5a431cb4f338f0998e2b32f654c310bf7831f97acd2a2ebbd944dcb426f6514afc43e256fbe2f41d0402c7e012bbd5e0c30cfa600000000").unwrap(),
1443 };
1444 }
1445
1446 #[test]
1447 fn test_generate_vtxo_vectors() {
1448 let g = generate_vtxo_vectors();
1449 let v = &*VTXO_VECTORS;
1452 println!("\n\nstatic:");
1453 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1454 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1455 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1456 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1457 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1458 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1459 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1460 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1461
1462 assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1463 assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1464 assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1465 assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1466 assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1467 assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1468 assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1469 assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1470
1471 assert_eq!(g, *v);
1473 }
1474
1475 #[test]
1476 fn exit_depth() {
1477 let vtxos = &*VTXO_VECTORS;
1478 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1480
1481 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1483
1484 assert_eq!(vtxos.arkoor_htlc_out_vtxo.exit_depth(), 1 + 1 );
1486 assert_eq!(vtxos.arkoor2_vtxo.exit_depth(), 1 + 2 );
1487 assert_eq!(vtxos.arkoor3_vtxo.exit_depth(), 3 + 1 );
1488 }
1489}