1mod validation;
58pub use self::validation::{ValidationResult, VtxoValidationError};
59
60use std::collections::HashSet;
61use std::iter::FusedIterator;
62use std::{fmt, io};
63use std::str::FromStr;
64
65use bitcoin::{
66 taproot, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness
67};
68use bitcoin::absolute::LockTime;
69use bitcoin::hashes::{sha256, Hash};
70use bitcoin::secp256k1::{schnorr, PublicKey};
71use bitcoin::taproot::LeafVersion;
72
73use bitcoin_ext::{fee, BlockDelta, BlockHeight, TaprootSpendInfoExt};
74
75use crate::{musig, scripts, SECP};
76use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
77use crate::lightning::{server_htlc_receive_taproot, server_htlc_send_taproot, PaymentHash};
78use crate::tree::signed::{cosign_taproot, leaf_cosign_taproot, unlock_clause};
79
80
81
82pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
84
85const VTXO_CLAIM_INPUT_WEIGHT: Weight = Weight::from_wu(138);
87
88const VTXO_ENCODING_VERSION: u16 = 1;
90
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
93#[error("failed to parse vtxo id, must be 36 bytes")]
94pub struct VtxoIdParseError;
95
96#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub struct VtxoId([u8; 36]);
98
99impl VtxoId {
100 pub const ENCODE_SIZE: usize = 36;
102
103 pub fn from_slice(b: &[u8]) -> Result<VtxoId, VtxoIdParseError> {
104 if b.len() == 36 {
105 let mut ret = [0u8; 36];
106 ret[..].copy_from_slice(&b[0..36]);
107 Ok(Self(ret))
108 } else {
109 Err(VtxoIdParseError)
110 }
111 }
112
113 pub fn utxo(self) -> OutPoint {
114 let vout = [self.0[32], self.0[33], self.0[34], self.0[35]];
115 OutPoint::new(Txid::from_slice(&self.0[0..32]).unwrap(), u32::from_le_bytes(vout))
116 }
117
118 pub fn to_bytes(self) -> [u8; 36] {
119 self.0
120 }
121}
122
123impl From<OutPoint> for VtxoId {
124 fn from(p: OutPoint) -> VtxoId {
125 let mut ret = [0u8; 36];
126 ret[0..32].copy_from_slice(&p.txid[..]);
127 ret[32..].copy_from_slice(&p.vout.to_le_bytes());
128 VtxoId(ret)
129 }
130}
131
132impl AsRef<[u8]> for VtxoId {
133 fn as_ref(&self) -> &[u8] {
134 &self.0
135 }
136}
137
138impl fmt::Display for VtxoId {
139 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140 fmt::Display::fmt(&self.utxo(), f)
141 }
142}
143
144impl fmt::Debug for VtxoId {
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 fmt::Display::fmt(self, f)
147 }
148}
149
150impl FromStr for VtxoId {
151 type Err = VtxoIdParseError;
152 fn from_str(s: &str) -> Result<Self, Self::Err> {
153 Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
154 }
155}
156
157impl serde::Serialize for VtxoId {
158 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
159 if s.is_human_readable() {
160 s.collect_str(self)
161 } else {
162 s.serialize_bytes(self.as_ref())
163 }
164 }
165}
166
167impl<'de> serde::Deserialize<'de> for VtxoId {
168 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
169 struct Visitor;
170 impl<'de> serde::de::Visitor<'de> for Visitor {
171 type Value = VtxoId;
172 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 write!(f, "a VtxoId")
174 }
175 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
176 VtxoId::from_slice(v).map_err(serde::de::Error::custom)
177 }
178 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
179 VtxoId::from_str(v).map_err(serde::de::Error::custom)
180 }
181 }
182 if d.is_human_readable() {
183 d.deserialize_str(Visitor)
184 } else {
185 d.deserialize_bytes(Visitor)
186 }
187 }
188}
189
190fn exit_clause(
192 user_pubkey: PublicKey,
193 exit_delta: BlockDelta,
194) -> ScriptBuf {
195 scripts::delayed_sign(exit_delta, user_pubkey.x_only_public_key().0)
196}
197
198pub fn exit_taproot(
200 user_pubkey: PublicKey,
201 server_pubkey: PublicKey,
202 exit_delta: BlockDelta,
203) -> taproot::TaprootSpendInfo {
204 let combined_pk = musig::combine_keys([user_pubkey, server_pubkey]);
205 taproot::TaprootBuilder::new()
206 .add_leaf(0, exit_clause(user_pubkey, exit_delta)).unwrap()
207 .finalize(&SECP, combined_pk).unwrap()
208}
209
210pub fn create_exit_tx(
215 prevout: OutPoint,
216 output: TxOut,
217 signature: Option<&schnorr::Signature>,
218) -> Transaction {
219 Transaction {
220 version: bitcoin::transaction::Version(3),
221 lock_time: LockTime::ZERO,
222 input: vec![TxIn {
223 previous_output: prevout,
224 script_sig: ScriptBuf::new(),
225 sequence: Sequence::ZERO,
226 witness: {
227 let mut ret = Witness::new();
228 if let Some(sig) = signature {
229 ret.push(&sig[..]);
230 }
231 ret
232 },
233 }],
234 output: vec![output, fee::fee_anchor()],
235 }
236}
237
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
241pub enum VtxoPolicyKind {
242 Pubkey,
244 ServerHtlcSend,
246 ServerHtlcRecv,
248}
249
250impl fmt::Display for VtxoPolicyKind {
251 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252 match self {
253 Self::Pubkey => f.write_str("pubkey"),
254 Self::ServerHtlcSend => f.write_str("server-htlc-send"),
255 Self::ServerHtlcRecv => f.write_str("server-htlc-receive"),
256 }
257 }
258}
259
260impl FromStr for VtxoPolicyKind {
261 type Err = String;
262 fn from_str(s: &str) -> Result<Self, Self::Err> {
263 Ok(match s {
264 "pubkey" => Self::Pubkey,
265 "server-htlc-send" => Self::ServerHtlcSend,
266 "server-htlc-receive" => Self::ServerHtlcRecv,
267 _ => return Err(format!("unknown VtxoPolicyType: {}", s)),
268 })
269 }
270}
271
272impl serde::Serialize for VtxoPolicyKind {
273 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
274 s.collect_str(self)
275 }
276}
277
278impl<'de> serde::Deserialize<'de> for VtxoPolicyKind {
279 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
280 struct Visitor;
281 impl<'de> serde::de::Visitor<'de> for Visitor {
282 type Value = VtxoPolicyKind;
283 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284 write!(f, "a VtxoPolicyType")
285 }
286 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
287 VtxoPolicyKind::from_str(v).map_err(serde::de::Error::custom)
288 }
289 }
290 d.deserialize_str(Visitor)
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
295pub struct PubkeyVtxoPolicy {
296 pub user_pubkey: PublicKey,
297}
298
299impl From<PubkeyVtxoPolicy> for VtxoPolicy {
300 fn from(policy: PubkeyVtxoPolicy) -> Self {
301 Self::Pubkey(policy)
302 }
303}
304
305#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
306pub struct ServerHtlcSendVtxoPolicy {
307 pub user_pubkey: PublicKey,
308 pub payment_hash: PaymentHash,
309 pub htlc_expiry: BlockHeight,
310}
311
312impl From<ServerHtlcSendVtxoPolicy> for VtxoPolicy {
313 fn from(policy: ServerHtlcSendVtxoPolicy) -> Self {
314 Self::ServerHtlcSend(policy)
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
319pub struct ServerHtlcRecvVtxoPolicy {
320 pub user_pubkey: PublicKey,
321 pub payment_hash: PaymentHash,
322 pub htlc_expiry_delta: BlockDelta,
323 pub htlc_expiry: BlockHeight,
324}
325
326impl From<ServerHtlcRecvVtxoPolicy> for VtxoPolicy {
327 fn from(policy: ServerHtlcRecvVtxoPolicy) -> Self {
328 Self::ServerHtlcRecv(policy)
329 }
330}
331
332#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
334pub enum VtxoPolicy {
335 Pubkey(PubkeyVtxoPolicy),
343 ServerHtlcSend(ServerHtlcSendVtxoPolicy),
345 ServerHtlcRecv(ServerHtlcRecvVtxoPolicy),
347}
348
349impl VtxoPolicy {
350 pub fn new_pubkey(user_pubkey: PublicKey) -> Self {
351 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey })
352 }
353
354 pub fn new_server_htlc_send(
355 user_pubkey: PublicKey,
356 payment_hash: PaymentHash,
357 htlc_expiry: BlockHeight,
358 ) -> Self {
359 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry })
360 }
361
362 pub fn new_server_htlc_recv(
371 user_pubkey: PublicKey,
372 payment_hash: PaymentHash,
373 htlc_expiry: BlockHeight,
374 htlc_expiry_delta: BlockDelta,
375 ) -> Self {
376 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta })
377 }
378
379 pub fn as_pubkey(&self) -> Option<&PubkeyVtxoPolicy> {
380 match self {
381 Self::Pubkey(v) => Some(v),
382 _ => None,
383 }
384 }
385
386 pub fn as_server_htlc_send(&self) -> Option<&ServerHtlcSendVtxoPolicy> {
387 match self {
388 Self::ServerHtlcSend(v) => Some(v),
389 _ => None,
390 }
391 }
392
393 pub fn as_server_htlc_recv(&self) -> Option<&ServerHtlcRecvVtxoPolicy> {
394 match self {
395 Self::ServerHtlcRecv(v) => Some(v),
396 _ => None,
397 }
398 }
399
400 pub fn policy_type(&self) -> VtxoPolicyKind {
402 match self {
403 Self::Pubkey { .. } => VtxoPolicyKind::Pubkey,
404 Self::ServerHtlcSend { .. } => VtxoPolicyKind::ServerHtlcSend,
405 Self::ServerHtlcRecv { .. } => VtxoPolicyKind::ServerHtlcRecv,
406 }
407 }
408
409 pub fn is_arkoor_compatible(&self) -> bool {
411 match self {
412 Self::Pubkey { .. } => true,
413 Self::ServerHtlcSend { .. } => false,
414 Self::ServerHtlcRecv { .. } => false,
415 }
416 }
417
418 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
421 match self {
422 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
423 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
424 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
425 }
426 }
427
428 pub fn user_pubkey(&self) -> PublicKey {
430 match self {
431 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => *user_pubkey,
432 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
433 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
434 }
435 }
436
437 pub(crate) fn taproot(
438 &self,
439 server_pubkey: PublicKey,
440 exit_delta: BlockDelta,
441 ) -> taproot::TaprootSpendInfo {
442 match self {
443 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
444 exit_taproot(*user_pubkey, server_pubkey, exit_delta)
445 },
446 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
447 server_htlc_send_taproot(*payment_hash, server_pubkey, *user_pubkey, exit_delta, *htlc_expiry)
448 },
449 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
450 user_pubkey, payment_hash, htlc_expiry_delta, htlc_expiry
451 }) => {
452 server_htlc_receive_taproot(*payment_hash, server_pubkey, *user_pubkey, exit_delta, *htlc_expiry_delta, *htlc_expiry)
453 },
454 }
455 }
456
457 pub fn user_exit_clause(&self, exit_delta: BlockDelta) -> ScriptBuf {
463 match self {
464 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
465 exit_clause(*user_pubkey, exit_delta)
466 },
467 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, htlc_expiry, .. }) => {
468 scripts::delay_timelock_sign(
469 2 * exit_delta, *htlc_expiry, user_pubkey.x_only_public_key().0,
470 )
471 },
472 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
473 user_pubkey, payment_hash, htlc_expiry_delta, ..
474 }) => {
475 scripts::hash_delay_sign(
476 payment_hash.to_sha256_hash(),
477 exit_delta + *htlc_expiry_delta,
478 user_pubkey.x_only_public_key().0,
479 )
480 },
481 }
482 }
483
484 pub(crate) fn script_pubkey(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> ScriptBuf {
485 self.taproot(server_pubkey, exit_delta).script_pubkey()
486 }
487
488 pub(crate) fn txout(&self, amount: Amount, server_pubkey: PublicKey, exit_delta: BlockDelta) -> TxOut {
489 TxOut {
490 value: amount,
491 script_pubkey: self.script_pubkey(server_pubkey, exit_delta),
492 }
493 }
494}
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500pub(crate) enum MaybePreimage {
501 Preimage([u8; 32]),
502 Hash(sha256::Hash),
503}
504
505impl MaybePreimage {
506 fn hash(&self) -> sha256::Hash {
508 match self {
509 Self::Preimage(p) => sha256::Hash::hash(p),
510 Self::Hash(h) => *h,
511 }
512 }
513}
514
515#[derive(Debug, Clone, PartialEq, Eq)]
519pub(crate) enum GenesisTransition {
520 Cosigned {
525 pubkeys: Vec<PublicKey>,
530 signature: schnorr::Signature,
531 },
532 HashLockedCosigned {
541 user_pubkey: PublicKey,
543 signature: schnorr::Signature,
545 unlock: MaybePreimage,
547 },
548 Arkoor {
550 policy: VtxoPolicy,
551 signature: Option<schnorr::Signature>,
552 },
553}
554
555impl GenesisTransition {
556 fn input_taproot(
558 &self,
559 server_pubkey: PublicKey,
560 expiry_height: BlockHeight,
561 exit_delta: BlockDelta,
562 ) -> taproot::TaprootSpendInfo {
563 match self {
564 Self::Cosigned { pubkeys, .. } => {
565 let agg_pk = musig::combine_keys(pubkeys.iter().copied());
566 cosign_taproot(agg_pk, server_pubkey, expiry_height)
567 },
568 Self::HashLockedCosigned { user_pubkey, unlock, .. } => {
569 let agg_pk = musig::combine_keys([*user_pubkey, server_pubkey]);
570 leaf_cosign_taproot(agg_pk, server_pubkey, expiry_height, unlock.hash())
571 },
572 Self::Arkoor { policy, .. } => policy.taproot(server_pubkey, exit_delta),
573 }
574 }
575
576 fn input_txout(
578 &self,
579 amount: Amount,
580 server_pubkey: PublicKey,
581 expiry_height: BlockHeight,
582 exit_delta: BlockDelta,
583 ) -> TxOut {
584 let taproot = self.input_taproot(server_pubkey, expiry_height, exit_delta);
585 TxOut {
586 value: amount,
587 script_pubkey: taproot.script_pubkey(),
588 }
589 }
590
591 fn witness(
593 &self,
594 server_pubkey: PublicKey,
595 expiry_height: BlockHeight,
596 ) -> Witness {
597 match self {
598 Self::Cosigned { signature, .. } => Witness::from_slice(&[&signature[..]]),
599 Self::HashLockedCosigned {
600 user_pubkey,
601 signature,
602 unlock: MaybePreimage::Preimage(preimage),
603 } => {
604 let unlock_hash = sha256::Hash::hash(preimage);
605 let agg_pk = musig::combine_keys([*user_pubkey, server_pubkey]);
606 let taproot = leaf_cosign_taproot(
607 agg_pk, server_pubkey, expiry_height, unlock_hash,
608 );
609
610 let clause = unlock_clause(agg_pk, unlock_hash);
611 let script_leaf = (clause, LeafVersion::TapScript);
612 let cb = taproot.control_block(&script_leaf)
613 .expect("unlock clause not found in hArk taproot");
614 Witness::from_slice(&[
615 &signature.serialize()[..],
616 &preimage[..],
617 &script_leaf.0.as_bytes(),
618 &cb.serialize()[..],
619 ])
620 },
621 Self::HashLockedCosigned { unlock: MaybePreimage::Hash(_), .. } => {
622 Witness::new()
624 },
625 Self::Arkoor { signature: Some(sig), .. } => Witness::from_slice(&[&sig[..]]),
626 Self::Arkoor { signature: None, .. } => Witness::new(),
627 }
628 }
629
630 fn has_exit(&self) -> bool {
632 match self {
633 Self::Cosigned { .. } => false,
634 Self::HashLockedCosigned { .. } => false,
635 Self::Arkoor { .. } => true,
636 }
637 }
638
639 fn is_arkoor(&self) -> bool {
641 match self {
642 Self::Cosigned { .. } => false,
643 Self::HashLockedCosigned { .. } => false,
644 Self::Arkoor { .. } => true,
645 }
646 }
647
648 fn is_fully_signed(&self) -> bool {
650 match self {
651 Self::Cosigned { .. } => true,
652 Self::HashLockedCosigned { unlock: MaybePreimage::Hash(_), .. } => false,
653 Self::HashLockedCosigned { unlock: MaybePreimage::Preimage(_), .. } => true,
654 Self::Arkoor { signature, .. } => signature.is_some(),
655 }
656 }
657}
658
659#[derive(Debug, Clone, PartialEq, Eq)]
663pub(crate) struct GenesisItem {
664 pub(crate) transition: GenesisTransition,
666 pub(crate) output_idx: u8,
668 pub(crate) other_outputs: Vec<TxOut>,
671}
672
673impl GenesisItem {
674 fn tx(&self,
676 prev: OutPoint,
677 next: TxOut,
678 server_pubkey: PublicKey,
679 expiry_height: BlockHeight,
680 ) -> Transaction {
681 Transaction {
682 version: bitcoin::transaction::Version(3),
683 lock_time: bitcoin::absolute::LockTime::ZERO,
684 input: vec![TxIn {
685 previous_output: prev,
686 script_sig: ScriptBuf::new(),
687 sequence: Sequence::ZERO,
688 witness: self.transition.witness(server_pubkey, expiry_height),
689 }],
690 output: {
691 let mut out = Vec::with_capacity(self.other_outputs.len() + 2);
692 out.extend(self.other_outputs.iter().take(self.output_idx as usize).cloned());
693 out.push(next);
694 out.extend(self.other_outputs.iter().skip(self.output_idx as usize).cloned());
695 out.push(fee::fee_anchor());
696 out
697 },
698 }
699 }
700}
701
702#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
705pub struct VtxoTxIterItem {
706 pub tx: Transaction,
708 pub is_exit: bool,
710}
711
712pub struct VtxoTxIter<'a> {
714 vtxo: &'a Vtxo,
715
716 prev: OutPoint,
717 genesis_idx: usize,
718 current_amount: Amount,
719 exit: bool,
722 done: bool,
723}
724
725impl<'a> VtxoTxIter<'a> {
726 fn new(vtxo: &'a Vtxo) -> VtxoTxIter<'a> {
727 let onchain_amount = vtxo.amount() + vtxo.genesis.iter().map(|i| {
729 i.other_outputs.iter().map(|o| o.value).sum()
730 }).sum();
731 VtxoTxIter {
732 prev: vtxo.anchor_point,
733 vtxo: vtxo,
734 genesis_idx: 0,
735 current_amount: onchain_amount,
736 exit: false,
737 done: false,
738 }
739 }
740
741 pub fn first_exit(mut self) -> Option<Transaction> {
742 let mut current = self.next();
743 while !self.exit {
744 current = self.next();
745 }
746
747 current.map(|c| c.tx)
748 }
749}
750
751impl<'a> Iterator for VtxoTxIter<'a> {
752 type Item = VtxoTxIterItem;
753
754 fn next(&mut self) -> Option<Self::Item> {
755 if self.done {
756 return None;
757 }
758
759 let item = self.vtxo.genesis.get(self.genesis_idx).expect("broken impl");
760 let next_amount = self.current_amount.checked_sub(
761 item.other_outputs.iter().map(|o| o.value).sum()
762 ).expect("we calculated this amount beforehand");
763
764 let next_output = if let Some(item) = self.vtxo.genesis.get(self.genesis_idx + 1) {
765 self.exit = self.exit || item.transition.has_exit();
766 item.transition.input_txout(
767 next_amount,
768 self.vtxo.server_pubkey,
769 self.vtxo.expiry_height,
770 self.vtxo.exit_delta,
771 )
772 } else {
773 self.done = true;
775 self.exit = true;
776 self.vtxo.policy.txout(self.vtxo.amount, self.vtxo.server_pubkey, self.vtxo.exit_delta)
777 };
778
779 let tx = item.tx(self.prev, next_output, self.vtxo.server_pubkey, self.vtxo.expiry_height);
780 self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
781 self.genesis_idx += 1;
782 self.current_amount = next_amount;
783 Some(VtxoTxIterItem { tx, is_exit: self.exit })
784 }
785
786 fn size_hint(&self) -> (usize, Option<usize>) {
787 let len = self.vtxo.genesis.len().saturating_sub(self.genesis_idx);
788 (len, Some(len))
789 }
790}
791
792impl<'a> ExactSizeIterator for VtxoTxIter<'a> {}
793impl<'a> FusedIterator for VtxoTxIter<'a> {}
794
795
796#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
798pub struct VtxoSpec {
799 pub policy: VtxoPolicy,
800 pub amount: Amount,
801 pub expiry_height: BlockHeight,
802 pub server_pubkey: PublicKey,
803 pub exit_delta: BlockDelta,
804}
805
806impl VtxoSpec {
807 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
809 self.policy.taproot(self.server_pubkey, self.exit_delta)
810 }
811
812 pub fn output_script_pubkey(&self) -> ScriptBuf {
814 self.policy.script_pubkey(self.server_pubkey, self.exit_delta)
815 }
816
817 pub fn txout(&self) -> TxOut {
819 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta)
820 }
821}
822
823#[derive(Debug, Clone)]
837pub struct Vtxo {
838 pub(crate) policy: VtxoPolicy,
839 pub(crate) amount: Amount,
840 pub(crate) expiry_height: BlockHeight,
841
842 pub(crate) server_pubkey: PublicKey,
843 pub(crate) exit_delta: BlockDelta,
844
845 pub(crate) anchor_point: OutPoint,
846 pub(crate) genesis: Vec<GenesisItem>,
847
848 pub(crate) point: OutPoint,
855}
856
857impl Vtxo {
858 pub fn id(&self) -> VtxoId {
862 self.point.into()
863 }
864
865 pub fn spec(&self) -> VtxoSpec {
867 VtxoSpec {
868 policy: self.policy.clone(),
869 amount: self.amount,
870 expiry_height: self.expiry_height,
871 server_pubkey: self.server_pubkey,
872 exit_delta: self.exit_delta,
873 }
874 }
875
876 pub fn point(&self) -> OutPoint {
880 self.point
881 }
882
883 pub fn amount(&self) -> Amount {
885 self.amount
886 }
887
888 pub fn chain_anchor(&self) -> OutPoint {
892 self.anchor_point
893 }
894
895 pub fn policy(&self) -> &VtxoPolicy {
897 &self.policy
898 }
899
900 pub fn policy_type(&self) -> VtxoPolicyKind {
902 self.policy.policy_type()
903 }
904
905 pub fn expiry_height(&self) -> BlockHeight {
907 self.expiry_height
908 }
909
910 pub fn server_pubkey(&self) -> PublicKey {
912 self.server_pubkey
913 }
914
915 pub fn exit_delta(&self) -> BlockDelta {
917 self.exit_delta
918 }
919
920 pub fn exit_depth(&self) -> u16 {
922 self.genesis.len() as u16
923 }
924
925 pub fn arkoor_depth(&self) -> u16 {
927 self.genesis.iter().rev().take_while(|item| item.transition.is_arkoor()).count() as u16
930 }
931
932 pub fn server_htlc_out_payment_hash(&self) -> Option<PaymentHash> {
934 match self.policy {
935 VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { payment_hash, .. }) => Some(payment_hash),
936 VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { payment_hash, .. }) => Some(payment_hash),
937 VtxoPolicy::Pubkey { .. } => None,
938 }
939 }
940
941 pub fn is_arkoor_compatible(&self) -> bool {
943 self.genesis.iter().all(|i| match i.transition {
944 GenesisTransition::Cosigned { .. } => true,
945 GenesisTransition::HashLockedCosigned { .. } => true,
946 GenesisTransition::Arkoor { ref policy, .. } => policy.is_arkoor_compatible(),
947 }) && self.policy.is_arkoor_compatible()
948 }
949
950 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
953 self.policy.arkoor_pubkey()
954 }
955
956 pub fn past_arkoor_pubkeys(&self) -> impl Iterator<Item = PublicKey> + '_ {
961 self.genesis.iter().filter_map(|g| {
962 match g.transition {
963 GenesisTransition::Arkoor { ref policy, .. } => policy.arkoor_pubkey(),
966 _ => None,
967 }
968 })
969 }
970
971 pub fn user_pubkey(&self) -> PublicKey {
973 self.policy.user_pubkey()
974 }
975
976 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
978 self.policy.taproot(self.server_pubkey, self.exit_delta)
979 }
980
981 pub fn output_script_pubkey(&self) -> ScriptBuf {
983 self.policy.script_pubkey(self.server_pubkey, self.exit_delta)
984 }
985
986 pub fn txout(&self) -> TxOut {
988 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta)
989 }
990
991 pub fn is_arkoor(&self) -> bool {
994 self.genesis.iter().any(|t| t.transition.has_exit())
995 }
996
997 pub fn is_fully_signed(&self) -> bool {
1002 self.genesis.iter().all(|g| g.transition.is_fully_signed())
1003 }
1004
1005 pub fn transactions(&self) -> VtxoTxIter<'_> {
1007 VtxoTxIter::new(self)
1008 }
1009
1010 pub fn claim_satisfaction_weight(&self) -> Weight {
1013 match self.policy {
1014 VtxoPolicy::Pubkey { .. } => VTXO_CLAIM_INPUT_WEIGHT,
1015 VtxoPolicy::ServerHtlcSend { .. } => VTXO_CLAIM_INPUT_WEIGHT,
1019 VtxoPolicy::ServerHtlcRecv { .. } => VTXO_CLAIM_INPUT_WEIGHT,
1020 }
1021 }
1022
1023 pub fn round_cosign_pubkeys(&self) -> Vec<PublicKey> {
1026 let mut ret = Option::<Vec<PublicKey>>::None;
1027
1028 for item in self.genesis.iter().rev() {
1032 match &item.transition {
1033 GenesisTransition::Cosigned { pubkeys, .. } => {
1034 if let Some(ref mut keys) = ret {
1035 keys.retain(|p| pubkeys.contains(p));
1036 if keys.is_empty() {
1037 break;
1038 }
1039 } else {
1040 ret = Some(pubkeys.clone());
1042 }
1043 },
1044 GenesisTransition::HashLockedCosigned { .. } => {},
1045 GenesisTransition::Arkoor { .. } => {},
1046 }
1047 }
1048
1049 ret.unwrap_or_default()
1050 }
1051
1052 pub fn arkoor_pubkeys(&self) -> HashSet<PublicKey> {
1055 self.genesis.iter().filter_map(|i| match &i.transition {
1056 GenesisTransition::Arkoor { policy, .. } => policy.arkoor_pubkey(),
1057 GenesisTransition::Cosigned { .. } => None,
1058 GenesisTransition::HashLockedCosigned { .. } => None,
1059 }).collect()
1060 }
1061
1062 pub fn validate(
1067 &self,
1068 chain_anchor_tx: &Transaction,
1069 ) -> Result<ValidationResult, VtxoValidationError> {
1070 self::validation::validate(&self, chain_anchor_tx)
1071 }
1072}
1073
1074impl PartialEq for Vtxo {
1075 fn eq(&self, other: &Self) -> bool {
1076 PartialEq::eq(&self.id(), &other.id())
1077 }
1078}
1079
1080impl Eq for Vtxo {}
1081
1082impl PartialOrd for Vtxo {
1083 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1084 PartialOrd::partial_cmp(&self.id(), &other.id())
1085 }
1086}
1087
1088impl Ord for Vtxo {
1089 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1090 Ord::cmp(&self.id(), &other.id())
1091 }
1092}
1093
1094impl std::hash::Hash for Vtxo {
1095 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1096 std::hash::Hash::hash(&self.id(), state)
1097 }
1098}
1099
1100pub trait VtxoRef {
1102 fn vtxo_id(&self) -> VtxoId;
1104
1105 fn vtxo(&self) -> Option<&Vtxo>;
1107}
1108
1109impl VtxoRef for VtxoId {
1110 fn vtxo_id(&self) -> VtxoId { *self }
1111 fn vtxo(&self) -> Option<&Vtxo> { None }
1112}
1113
1114impl<'a> VtxoRef for &'a VtxoId {
1115 fn vtxo_id(&self) -> VtxoId { **self }
1116 fn vtxo(&self) -> Option<&Vtxo> { None }
1117}
1118
1119impl VtxoRef for Vtxo {
1120 fn vtxo_id(&self) -> VtxoId { self.id() }
1121 fn vtxo(&self) -> Option<&Vtxo> { Some(self) }
1122}
1123
1124impl<'a> VtxoRef for &'a Vtxo {
1125 fn vtxo_id(&self) -> VtxoId { self.id() }
1126 fn vtxo(&self) -> Option<&Vtxo> { Some(*self) }
1127}
1128
1129const VTXO_POLICY_PUBKEY: u8 = 0x00;
1131
1132const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
1134
1135const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
1137
1138impl ProtocolEncoding for VtxoPolicy {
1139 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1140 match self {
1141 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
1142 w.emit_u8(VTXO_POLICY_PUBKEY)?;
1143 user_pubkey.encode(w)?;
1144 },
1145 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
1146 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
1147 user_pubkey.encode(w)?;
1148 payment_hash.to_sha256_hash().encode(w)?;
1149 w.emit_u32(*htlc_expiry)?;
1150 },
1151 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1152 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1153 }) => {
1154 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
1155 user_pubkey.encode(w)?;
1156 payment_hash.to_sha256_hash().encode(w)?;
1157 w.emit_u32(*htlc_expiry)?;
1158 w.emit_u16(*htlc_expiry_delta)?;
1159 },
1160 }
1161 Ok(())
1162 }
1163
1164 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1165 match r.read_u8()? {
1166 VTXO_POLICY_PUBKEY => {
1167 let user_pubkey = PublicKey::decode(r)?;
1168 Ok(Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
1169 },
1170 VTXO_POLICY_SERVER_HTLC_SEND => {
1171 let user_pubkey = PublicKey::decode(r)?;
1172 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1173 let htlc_expiry = r.read_u32()?;
1174 Ok(Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
1175 },
1176 VTXO_POLICY_SERVER_HTLC_RECV => {
1177 let user_pubkey = PublicKey::decode(r)?;
1178 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1179 let htlc_expiry = r.read_u32()?;
1180 let htlc_expiry_delta = r.read_u16()?;
1181 Ok(Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
1182 },
1183 v => Err(ProtocolDecodingError::invalid(format_args!(
1184 "invalid VtxoType type byte: {v:#x}",
1185 ))),
1186 }
1187 }
1188}
1189
1190const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
1192
1193const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
1195
1196const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
1198
1199impl ProtocolEncoding for GenesisTransition {
1200 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1201 match self {
1202 Self::Cosigned { pubkeys, signature } => {
1203 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
1204 w.emit_u16(pubkeys.len().try_into().expect("cosign pubkey length overflow"))?;
1205 for pk in pubkeys {
1206 pk.encode(w)?;
1207 }
1208 signature.encode(w)?;
1209 },
1210 Self::HashLockedCosigned { user_pubkey, signature, unlock } => {
1211 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
1212 user_pubkey.encode(w)?;
1213 signature.encode(w)?;
1214 match unlock {
1215 MaybePreimage::Preimage(p) => {
1216 w.emit_u8(0)?;
1217 w.emit_slice(&p[..])?;
1218 },
1219 MaybePreimage::Hash(h) => {
1220 w.emit_u8(1)?;
1221 w.emit_slice(&h[..])?;
1222 },
1223 }
1224 },
1225 Self::Arkoor { policy, signature } => {
1226 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
1227 policy.encode(w)?;
1228 signature.encode(w)?;
1229 },
1230 }
1231 Ok(())
1232 }
1233
1234 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1235 match r.read_u8()? {
1236 GENESIS_TRANSITION_TYPE_COSIGNED => {
1237 let nb_pubkeys = r.read_u16()? as usize;
1238 let mut pubkeys = Vec::with_capacity(nb_pubkeys);
1239 for _ in 0..nb_pubkeys {
1240 pubkeys.push(PublicKey::decode(r)?);
1241 }
1242 let signature = schnorr::Signature::decode(r)?;
1243 Ok(Self::Cosigned { pubkeys, signature })
1244 },
1245 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
1246 let user_pubkey = PublicKey::decode(r)?;
1247 let signature = schnorr::Signature::decode(r)?;
1248 let unlock = match r.read_u8()? {
1249 0 => MaybePreimage::Preimage(r.read_byte_array()?),
1250 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
1251 v => return Err(ProtocolDecodingError::invalid(format_args!(
1252 "invalid HashLockedCosignedTransitionWitness type byte: {v:#x}",
1253 ))),
1254 };
1255 Ok(Self::HashLockedCosigned { user_pubkey, signature, unlock })
1256 },
1257 GENESIS_TRANSITION_TYPE_ARKOOR => {
1258 let policy = VtxoPolicy::decode(r)?;
1259 let signature = Option::<schnorr::Signature>::decode(r)?;
1260 Ok(Self::Arkoor { policy, signature })
1261 },
1262 v => Err(ProtocolDecodingError::invalid(format_args!(
1263 "invalid GenesisTransistion type byte: {v:#x}",
1264 ))),
1265 }
1266 }
1267}
1268
1269impl ProtocolEncoding for Vtxo {
1270 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1271 w.emit_u16(VTXO_ENCODING_VERSION)?;
1272 w.emit_u64(self.amount.to_sat())?;
1273 w.emit_u32(self.expiry_height)?;
1274 self.server_pubkey.encode(w)?;
1275 w.emit_u16(self.exit_delta)?;
1276 self.anchor_point.encode(w)?;
1277
1278 w.emit_u8(self.genesis.len().try_into().expect("genesis length overflow"))?;
1279 for item in &self.genesis {
1280 item.transition.encode(w)?;
1281 let nb_outputs = item.other_outputs.len() + 1;
1282 w.emit_u8(nb_outputs.try_into().expect("genesis item output length overflow"))?;
1283 w.emit_u8(item.output_idx)?;
1284 for txout in &item.other_outputs {
1285 txout.encode(w)?;
1286 }
1287 }
1288
1289 self.policy.encode(w)?;
1290 self.point.encode(w)?;
1291 Ok(())
1292 }
1293
1294 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1295 let version = r.read_u16()?;
1296 if version != VTXO_ENCODING_VERSION {
1297 return Err(ProtocolDecodingError::invalid(format_args!(
1298 "invalid Vtxo encoding version byte: {version:#x}",
1299 )));
1300 }
1301
1302 let amount = Amount::from_sat(r.read_u64()?);
1303 let expiry_height = r.read_u32()?;
1304 let server_pubkey = PublicKey::decode(r)?;
1305 let exit_delta = r.read_u16()?;
1306 let anchor_point = OutPoint::decode(r)?;
1307
1308 let nb_genesis_items = r.read_u8()? as usize;
1309 let mut genesis = Vec::with_capacity(nb_genesis_items);
1310 for _ in 0..nb_genesis_items {
1311 let transition = GenesisTransition::decode(r)?;
1312 let nb_outputs = r.read_u8()? as usize;
1313 let output_idx = r.read_u8()?;
1314 let nb_other = nb_outputs.checked_sub(1)
1315 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1316 let mut other_outputs = Vec::with_capacity(nb_other);
1317 for _ in 0..nb_other {
1318 other_outputs.push(TxOut::decode(r)?);
1319 }
1320 genesis.push(GenesisItem { transition, output_idx, other_outputs });
1321 }
1322
1323 let output = VtxoPolicy::decode(r)?;
1324 let point = OutPoint::decode(r)?;
1325
1326 Ok(Self {
1327 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy: output, point,
1328 })
1329 }
1330}
1331
1332
1333#[cfg(any(test, feature = "test-util"))]
1334pub mod test {
1335 use std::iter;
1336 use std::collections::HashMap;
1337
1338 use bitcoin::consensus::encode::{deserialize_hex, serialize_hex};
1339 use bitcoin::hex::DisplayHex;
1340 use bitcoin::secp256k1::Keypair;
1341 use bitcoin::transaction::Version;
1342
1343 use crate::arkoor::ArkoorBuilder;
1344 use crate::board::BoardBuilder;
1345 use crate::encode::test::encoding_roundtrip;
1346 use crate::tree::signed::VtxoTreeSpec;
1347 use crate::{SECP, SignedVtxoRequest, VtxoRequest};
1348
1349 use super::*;
1350
1351 #[allow(unused)]
1352 #[macro_export]
1353 macro_rules! assert_eq_vtxos {
1354 ($v1:expr, $v2:expr) => {
1355 let v1 = &$v1;
1356 let v2 = &$v2;
1357 assert_eq!(
1358 v1.serialize().as_hex().to_string(),
1359 v2.serialize().as_hex().to_string(),
1360 "vtxo {} != {}", v1.id(), v2.id(),
1361 );
1362 };
1363 }
1364
1365 #[derive(Debug, PartialEq, Eq)]
1366 pub struct VtxoTestVectors {
1367 pub anchor_tx: Transaction,
1368 pub board_vtxo: Vtxo,
1369
1370 pub arkoor_htlc_out_vtxo: Vtxo,
1371 pub arkoor2_vtxo: Vtxo,
1372
1373 pub round_tx: Transaction,
1374 pub round1_vtxo: Vtxo,
1375 pub round2_vtxo: Vtxo,
1376
1377 pub arkoor3_vtxo: Vtxo,
1378 }
1379
1380 #[allow(unused)] fn generate_vtxo_vectors() -> VtxoTestVectors {
1382 let expiry_height = 101_010;
1383 let exit_delta = 2016;
1384 let server_pubkey = Keypair::from_str("916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92").unwrap();
1385 let board_user_key = Keypair::from_str("fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879").unwrap();
1386 let amount = Amount::from_sat(10_000);
1387 let builder = BoardBuilder::new(
1388 board_user_key.public_key(),
1389 expiry_height,
1390 server_pubkey.public_key(),
1391 exit_delta,
1392 );
1393 let anchor_tx = Transaction {
1394 version: Version::TWO,
1395 lock_time: LockTime::ZERO,
1396 input: vec![TxIn {
1397 previous_output: OutPoint::null(),
1398 script_sig: ScriptBuf::new(),
1399 sequence: Sequence::ZERO,
1400 witness: Witness::new(),
1401 }],
1402 output: vec![TxOut {
1403 value: Amount::from_sat(10_000),
1404 script_pubkey: builder.funding_script_pubkey(),
1405 }],
1406 };
1407 println!("chain anchor tx: {}", serialize_hex(&anchor_tx));
1408 let anchor_point = OutPoint::new(anchor_tx.compute_txid(), 0);
1409 let builder = builder.set_funding_details(amount, anchor_point)
1410 .generate_user_nonces();
1411
1412 let board_cosign = {
1413 BoardBuilder::new_for_cosign(
1414 builder.user_pubkey,
1415 builder.expiry_height,
1416 builder.server_pubkey,
1417 builder.exit_delta,
1418 amount,
1419 anchor_point,
1420 *builder.user_pub_nonce(),
1421 ).server_cosign(&server_pubkey)
1422 };
1423
1424 assert!(builder.verify_cosign_response(&board_cosign));
1425 let board_vtxo = builder.build_vtxo(&board_cosign, &board_user_key).unwrap();
1426 encoding_roundtrip(&board_vtxo);
1427 println!("board vtxo: {}", board_vtxo.serialize().as_hex());
1428
1429 let arkoor_htlc_out_user_key = Keypair::from_str("33b6f3ede430a1a53229f55da7117242d8392cbfc64a57249ba70731dba71408").unwrap();
1432 let payment_hash = PaymentHash::from(sha256::Hash::hash("arkoor1".as_bytes()).to_byte_array());
1433 let arkoor1out1 = VtxoRequest {
1434 amount: Amount::from_sat(9000),
1435 policy: VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy {
1436 user_pubkey: arkoor_htlc_out_user_key.public_key(),
1437 payment_hash,
1438 htlc_expiry: expiry_height - 1000,
1439 }),
1440 };
1441 let arkoor1out2 = VtxoRequest {
1442 amount: Amount::from_sat(1000),
1443 policy: VtxoPolicy::new_pubkey("0229b7de0ce4d573192d002a6f9fd1109e00f7bae52bf10780d6f6e73e12a8390f".parse().unwrap()),
1444 };
1445 let outputs = [&arkoor1out1, &arkoor1out2];
1446 let (sec_nonce, pub_nonce) = musig::nonce_pair(&board_user_key);
1447 let builder = ArkoorBuilder::new(&board_vtxo, &pub_nonce, &outputs).unwrap();
1448 let cosign = builder.server_cosign(&server_pubkey);
1449 assert!(builder.verify_cosign_response(&cosign));
1450 let [arkoor_htlc_out_vtxo, change] = builder.build_vtxos(
1451 sec_nonce, &board_user_key, &cosign,
1452 ).unwrap().try_into().unwrap();
1453 encoding_roundtrip(&arkoor_htlc_out_vtxo);
1454 encoding_roundtrip(&change);
1455 println!("arkoor1_vtxo: {}", arkoor_htlc_out_vtxo.serialize().as_hex());
1456
1457 let arkoor2_user_key = Keypair::from_str("fcc43a4f03356092a945ca1d7218503156bed3f94c2fa224578ce5b158fbf5a6").unwrap();
1460 let arkoor2out1 = VtxoRequest {
1461 amount: Amount::from_sat(8000),
1462 policy: VtxoPolicy::new_pubkey(arkoor2_user_key.public_key()),
1463 };
1464 let arkoor2out2 = VtxoRequest {
1465 amount: Amount::from_sat(1000),
1466 policy: VtxoPolicy::new_pubkey("037039dc4f4b16e78059d2d56eb98d181cb1bdff2675694d39d92c4a2ea08ced88".parse().unwrap()),
1467 };
1468 let outputs = [&arkoor2out1, &arkoor2out2];
1469 let (sec_nonce, pub_nonce) = musig::nonce_pair(&arkoor_htlc_out_user_key);
1470 let builder = ArkoorBuilder::new(&arkoor_htlc_out_vtxo, &pub_nonce, &outputs).unwrap();
1471 let arkoor2_cosign = builder.server_cosign(&server_pubkey);
1472 assert!(builder.verify_cosign_response(&arkoor2_cosign));
1473 let [arkoor2_vtxo, change] = builder.build_vtxos(
1474 sec_nonce, &arkoor_htlc_out_user_key, &arkoor2_cosign,
1475 ).unwrap().try_into().unwrap();
1476 encoding_roundtrip(&arkoor2_vtxo);
1477 encoding_roundtrip(&change);
1478 println!("arkoor2_vtxo: {}", arkoor2_vtxo.serialize().as_hex());
1479
1480 let round1_user_key = Keypair::from_str("0a832e9574070c94b5b078600a18639321c880c830c5ba2f2a96850c7dcc4725").unwrap();
1484 let round1_cosign_key = Keypair::from_str("e14bfc3199842c76816eec1d93c9da00b850c4ed19e414e246d07e845e465a2b").unwrap();
1485 println!("round1_cosign_key: {}", round1_cosign_key.public_key());
1486 let round1_req = SignedVtxoRequest {
1487 vtxo: VtxoRequest {
1488 amount: Amount::from_sat(10_000),
1489 policy: VtxoPolicy::new_pubkey(round1_user_key.public_key()),
1490 },
1491 cosign_pubkey: Some(round1_cosign_key.public_key()),
1492 };
1493 let round1_nonces = iter::repeat_with(|| musig::nonce_pair(&round1_cosign_key)).take(5).collect::<Vec<_>>();
1494
1495 let round2_user_key = Keypair::from_str("c0b645b01cac427717a18b30c7c9238dee2b3885f659930144fbe05061ad6166").unwrap();
1496 let round2_cosign_key = Keypair::from_str("628789cd7b7e02766d184ecfecc433798c9640349e41822df7996c66a56fc633").unwrap();
1497 println!("round2_cosign_key: {}", round2_cosign_key.public_key());
1498 let round2_payment_hash = PaymentHash::from(sha256::Hash::hash("round2".as_bytes()).to_byte_array());
1499 let round2_req = SignedVtxoRequest {
1500 vtxo: VtxoRequest {
1501 amount: Amount::from_sat(10_000),
1502 policy: VtxoPolicy::new_server_htlc_recv(
1503 round2_user_key.public_key(),
1504 round2_payment_hash,
1505 expiry_height - 2000,
1506 40,
1507 ),
1508 },
1509 cosign_pubkey: Some(round2_cosign_key.public_key()),
1510 };
1511 let round2_nonces = iter::repeat_with(|| musig::nonce_pair(&round2_cosign_key)).take(5).collect::<Vec<_>>();
1512
1513 let others = [
1514 "93b376f64ada74f0fbf940be86f888459ac94655dc6a7805cc790b3c95a2a612",
1515 "00add86ff531ef53f877780622f0b376669ec6ad7e090131820ff7007e79f529",
1516 "775b836f2acf53de4ff9beeba2a17d5475e9b027d82fece72033ef06b954c7cd",
1517 "395c2c210481990a5d12d33dca37995e235a34b717c89647a33907c62e32dc09",
1518 "8f02f2a7aa1746bbcc92bba607b7166b6a77e9d0efd9d09dae7c2dc3addbdef1",
1519 ];
1520 let mut other_reqs = Vec::new();
1521 let mut other_nonces = Vec::new();
1522 for k in others {
1523 let user_key = Keypair::from_str(k).unwrap();
1524 let cosign_key = Keypair::from_seckey_slice(&SECP, &sha256::Hash::hash(k.as_bytes())[..]).unwrap();
1525 other_reqs.push(SignedVtxoRequest {
1526 vtxo: VtxoRequest {
1527 amount: Amount::from_sat(5_000),
1528 policy: VtxoPolicy::new_pubkey(user_key.public_key()),
1529 },
1530 cosign_pubkey: Some(cosign_key.public_key()),
1531 });
1532 other_nonces.push(iter::repeat_with(|| musig::nonce_pair(&cosign_key)).take(5).collect::<Vec<_>>());
1533 }
1534
1535 let server_cosign_key = Keypair::from_str("4371a4a7989b89ebe1b2582db4cd658cb95070977e6f10601ddc1e9b53edee79").unwrap();
1536 let spec = VtxoTreeSpec::new(
1537 [&round1_req, &round2_req].into_iter().chain(other_reqs.iter()).cloned().collect(),
1538 server_pubkey.public_key(),
1539 expiry_height,
1540 exit_delta,
1541 vec![server_cosign_key.public_key()],
1542 );
1543 let round_tx = Transaction {
1544 version: Version::TWO,
1545 lock_time: LockTime::ZERO,
1546 input: vec![TxIn {
1547 previous_output: OutPoint::null(),
1548 script_sig: ScriptBuf::new(),
1549 sequence: Sequence::ZERO,
1550 witness: Witness::new(),
1551 }],
1552 output: vec![TxOut {
1553 value: Amount::from_sat(45_000),
1554 script_pubkey: spec.funding_tx_script_pubkey(),
1555 }],
1556 };
1557 println!("round tx: {}", serialize_hex(&round_tx));
1558 let all_nonces = {
1559 let mut map = HashMap::new();
1560 map.insert(round1_cosign_key.public_key(), round1_nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1561 map.insert(round2_cosign_key.public_key(), round2_nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1562 for (req, nonces) in other_reqs.iter().zip(other_nonces.iter()) {
1563 map.insert(req.cosign_pubkey.unwrap(), nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1564 }
1565 map
1566 };
1567 let (server_cosign_sec_nonces, server_cosign_pub_nonces) = iter::repeat_with(|| {
1568 musig::nonce_pair(&server_cosign_key)
1569 }).take(spec.nb_nodes()).unzip::<_, _, Vec<_>, Vec<_>>();
1570 let cosign_agg_nonces = spec.calculate_cosign_agg_nonces(&all_nonces, &[&server_cosign_pub_nonces]).unwrap();
1571 let root_point = OutPoint::new(round_tx.compute_txid(), 0);
1572 let tree = spec.into_unsigned_tree(root_point);
1573 let part_sigs = {
1574 let mut map = HashMap::new();
1575 map.insert(round1_cosign_key.public_key(), {
1576 let secs = round1_nonces.into_iter().map(|(s, _)| s).collect();
1577 let r = tree.cosign_branch(&cosign_agg_nonces, 0, &round1_cosign_key, secs).unwrap();
1578 r
1579 });
1580 map.insert(round2_cosign_key.public_key(), {
1581 let secs = round2_nonces.into_iter().map(|(s, _)| s).collect();
1582 tree.cosign_branch(&cosign_agg_nonces, 1, &round2_cosign_key, secs).unwrap()
1583 });
1584 for (i, (req, nonces)) in other_reqs.iter().zip(other_nonces.into_iter()).enumerate() {
1585 let cosign_key = Keypair::from_seckey_slice(
1586 &SECP, &sha256::Hash::hash(others[i].as_bytes())[..],
1587 ).unwrap();
1588 map.insert(req.cosign_pubkey.unwrap(), {
1589 let secs = nonces.into_iter().map(|(s, _)| s).collect();
1590 tree.cosign_branch(&cosign_agg_nonces, 2 + i, &cosign_key, secs).unwrap()
1591 });
1592 }
1593 map
1594 };
1595 let server_cosign_sigs = tree.cosign_tree(
1596 &cosign_agg_nonces, &server_cosign_key, server_cosign_sec_nonces,
1597 );
1598 let cosign_sigs = tree.combine_partial_signatures(&cosign_agg_nonces, &part_sigs, &[&server_cosign_sigs]).unwrap();
1599 assert!(tree.verify_cosign_sigs(&cosign_sigs).is_ok());
1600 let signed = tree.into_signed_tree(cosign_sigs).into_cached_tree();
1601 let mut vtxo_iter = signed.all_vtxos();
1603 let round1_vtxo = vtxo_iter.next().unwrap();
1604 encoding_roundtrip(&round1_vtxo);
1605 println!("round1_vtxo: {}", round1_vtxo.serialize().as_hex());
1606 let round2_vtxo = vtxo_iter.next().unwrap();
1607 encoding_roundtrip(&round2_vtxo);
1608 println!("round2_vtxo: {}", round2_vtxo.serialize().as_hex());
1609
1610 let arkoor3_user_key = Keypair::from_str("ad12595bdbdab56cb61d1f60ccc46ff96b11c5d6fe06ae7ba03d3a5f4347440f").unwrap();
1613 let arkoor3out = VtxoRequest {
1614 amount: Amount::from_sat(10_000),
1615 policy: VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey: arkoor3_user_key.public_key() }),
1616 };
1617 let outputs = [&arkoor3out];
1618 let (sec_nonce, pub_nonce) = musig::nonce_pair(&round2_user_key);
1619 let builder = ArkoorBuilder::new(&round2_vtxo, &pub_nonce, &outputs).unwrap();
1620 let arkoor3_cosign = builder.server_cosign(&server_pubkey);
1621 assert!(builder.verify_cosign_response(&arkoor3_cosign));
1622 let [arkoor3_vtxo] = builder.build_vtxos(
1623 sec_nonce, &round2_user_key, &arkoor3_cosign,
1624 ).unwrap().try_into().unwrap();
1625 encoding_roundtrip(&arkoor3_vtxo);
1626 println!("arkoor3_vtxo: {}", arkoor3_vtxo.serialize().as_hex());
1627
1628 VtxoTestVectors {
1629 anchor_tx,
1630 board_vtxo,
1631 arkoor_htlc_out_vtxo,
1632 arkoor2_vtxo,
1633 round_tx,
1634 round1_vtxo,
1635 round2_vtxo,
1636 arkoor3_vtxo,
1637 }
1638 }
1639
1640 lazy_static! {
1641 pub static ref VTXO_VECTORS: VtxoTestVectors = VtxoTestVectors {
1643 anchor_tx: deserialize_hex("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0000000000011027000000000000225120652675904a84ea02e24b57b3d547203d2ce71526113d35bf4d02e0b4efbe9a2d00000000").unwrap(),
1644 board_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000001010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a010000030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4c99b744ad009b7070f330794bf003fa8e5cd46ea1a6eb854aaf469385e3080000000000").unwrap(),
1645 arkoor_htlc_out_vtxo: ProtocolEncoding::deserialize_hex("01002823000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000002010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a01000200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4a4402956ec89190685f761fbe77018d1727c01d62f877cc5737d3f951725a0629410c1a0ead31328f24bb54c856d6efb3205032c1fdf723bf2fed7a7c6dfbc90200e8030000000000002251209b987ec3c169c70d1ed6aef420a4858e3e3ec9d8404358787d4e06ba926a4ae40103eb4570ae385202d4a48f06bdb14126910b90c07f8e42d7dc5e28a860c085e73712358912c950a9a7d04bb9011ee9f6a16b6127a5aab7415803d48c0225f620f5aa860100c692b81703c12cac1e8d69b86fa9f0e2f167168d96ae1045ef8d9192bc4a6e4c00000000").unwrap(),
1646 arkoor2_vtxo: ProtocolEncoding::deserialize_hex("0100401f000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000003010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a01000200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4a4402956ec89190685f761fbe77018d1727c01d62f877cc5737d3f951725a0629410c1a0ead31328f24bb54c856d6efb3205032c1fdf723bf2fed7a7c6dfbc90200e8030000000000002251209b987ec3c169c70d1ed6aef420a4858e3e3ec9d8404358787d4e06ba926a4ae4020103eb4570ae385202d4a48f06bdb14126910b90c07f8e42d7dc5e28a860c085e73712358912c950a9a7d04bb9011ee9f6a16b6127a5aab7415803d48c0225f620f5aa860100ade724357d339cd6ffdd606fdd58d19540757673920512a5c01f6f9591adff3713240032fefacef370a91d268456484a460bbf992dea6872b5a751619f95560c0200e80300000000000022512018d297ade3cfbb7080b65e21af238ac88c15e38734f5f462530c34a225e80ca9000265cca13271cafd0b90c440e722a1937b7c4faf4ccd7dee0548d152c24ce4b2a8d4f7d410cf052720ffc5ce4668c4371448ffe98b7037f7c42aa943d717fcd67700000000").unwrap(),
1647 round_tx: deserialize_hex("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff000000000001c8af000000000000225120649657f65947abfd83ff629ad8a851c795f419ed4d52a2748d3f868cc3e6c94d00000000").unwrap(),
1648 round1_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000003010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427431fbf80b38758fed9f56d58a7b9f1b64d25c6219591637932f9939678e45d845cd725141a30d0b1ff28e56f7fdc838630b166449b2ad8953538006baf77a1294104038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427436f5c1527ec051272529b96c08fc51711565a71174fa762dfb0353d39f8d93f64215368326730353875cabd078be4badf8b82d1f5a6b0ca05aed5e49cc117e8ae04001027000000000000225120b54cbc99321d02aa2114fabb39dc5e8f346e88296dcec79b1b3c0849caba3d6f881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427437a733b847aed8ceda6460c70a55c80778115c536cfca265559d2f6890be7bd3ab55ef96405f2f136b185893b5d73696f866808229b1506c98b06d992a81b618c0100000374a3ec37cc4ccd29717388e6dc24f2aa366632f1a36a49e73cd7671b231792988588da5d9b08f1767aab3b3a78b6cd27deb937193e153300bcf84b3eeaaef07200000000").unwrap(),
1649 round2_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000003010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743fd28289b9179bcd986f615bfe559c17fe868b34739b9856cffc2b7d5e8f11c952579af829442116177efc4ea7867ab69376215c6eb30c9f2f79e97e1c06cf2f604038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743273b637dbe9a823fe1e04d0360d887914b83a9266e09ae0b893004fc633552038b01b2fa4f4db5dd811a87e42a4718adba852b9af5536109a07dfe81a1067a6b040110270000000000002251202df700706227474e89354e4ad3ac28f007952140fff42a5a0f0675bdff87f6b3881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc4024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743f622e425138d7c93cc09185f8328d8e113af4511c6b11053da214c65afc17f9aedca7bc667eafae74b9e232cbab6031b457c14886a3cf5573343654283b712cd0100020256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecd9ea50d885c3f66d40d27e779648ba8dc730629663f65a3e6f7749b4a35b6dfecc28201002800396ada529b0608572b3b0b6095394574c55f5b3e6911320608d35cc1cc200dab00000000").unwrap(),
1650 arkoor3_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000004010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427431fbf80b38758fed9f56d58a7b9f1b64d25c6219591637932f9939678e45d845cd725141a30d0b1ff28e56f7fdc838630b166449b2ad8953538006baf77a1294104038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427436f5c1527ec051272529b96c08fc51711565a71174fa762dfb0353d39f8d93f64215368326730353875cabd078be4badf8b82d1f5a6b0ca05aed5e49cc117e8ae040110270000000000002251202df700706227474e89354e4ad3ac28f007952140fff42a5a0f0675bdff87f6b3881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc4024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743b6c685b3ee849d2400f96fb5968ddd472e18c54b8910a8f952d5b127494cbd51556346bbb0a44d7f5f5150310535b4b2f2aa4412d7b88af929dae5a9652f7643010002020256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecd9ea50d885c3f66d40d27e779648ba8dc730629663f65a3e6f7749b4a35b6dfecc282010028003e6785232b2247cb57de941a898729170b3a50e1bed843700f5dfaeb90ee8e531aa4e35b2bf72ceb2de6bfd2d859be86bdbe5f75fbc90a17ab9c2babb40fc83501000002ed1334f116cea9128e1f59f1d5a431cb4f338f0998e2b32f654c310bf7831f97ff70cc93c752b2cdfa42fef244be8915b087a7e13d9cf6cb24b6443b6a8b87dc00000000").unwrap(),
1651 };
1652 }
1653
1654 #[test]
1655 fn test_generate_vtxo_vectors() {
1656 let g = generate_vtxo_vectors();
1657 let v = &*VTXO_VECTORS;
1660 println!("\n\nstatic:");
1661 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1662 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1663 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1664 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1665 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1666 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1667 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1668 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1669
1670 assert_eq!(g, *v);
1672 }
1673
1674 #[test]
1675 fn arkoor_depth() {
1676 let vtxos = &*VTXO_VECTORS;
1677 assert_eq!(vtxos.board_vtxo.arkoor_depth(), 0);
1679
1680 assert_eq!(vtxos.round1_vtxo.arkoor_depth(), 0);
1682
1683 assert_eq!(vtxos.arkoor_htlc_out_vtxo.arkoor_depth(), 1);
1685 assert_eq!(vtxos.arkoor2_vtxo.arkoor_depth(), 2);
1686 assert_eq!(vtxos.arkoor3_vtxo.arkoor_depth(), 1);
1687 }
1688
1689 #[test]
1690 fn exit_depth() {
1691 let vtxos = &*VTXO_VECTORS;
1692 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1694
1695 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1697
1698 assert_eq!(vtxos.arkoor_htlc_out_vtxo.exit_depth(), 1 + 1 );
1700 assert_eq!(vtxos.arkoor2_vtxo.exit_depth(), 1 + 2 );
1701 assert_eq!(vtxos.arkoor3_vtxo.exit_depth(), 3 + 1 );
1702 }
1703}