ark/vtxo/
mod.rs

1//! Representations of VTXOs in an Ark.
2
3
4// # The internal representation of VTXOs.
5//
6// The [Vtxo] type is a struct that exposes a public API through methods, but
7// we have deliberately decided to hide all its internal representation from
8// the user.
9//
10// ## Objectives
11//
12// The objectives of the internal structure of [Vtxo] are the following:
13// - have a stable encoding and decoding through [ProtocolEncoding]
14// - enable constructing all exit transactions required to perform a
15//   unilateral exit for the VTXO
16// - enable a user to validate that the exit transaction chain is safe,
17//   meaning that there are no unexpected spend paths that could break
18//   the exit. this means that
19//   - all transitions between transactions (i.e. where a child spends its
20//     parent) have only known spend paths and no malicious additional ones
21//   - all outputs of all exit transactions are standard, so they can be
22//     relayed on the public relay network
23//   - the necessary fee anchors are in place to allow the user to fund his
24//     exit
25//
26// ## Internal structure
27//
28// Each [Vtxo] has what we call a "chain anchor" and a "genesis". The chain
29// anchor is the transaction that is to be confirmed on-chain to anchor the
30// VTXO's existence into the chain. The genesis represents the data required
31// to "conceive" the [Vtxo]'s UTXO on the chain, connected to the chain anchor.
32// Conceptually, the genesis data consists of two main things:
33// - the output policy data and input witness data for each transition.
34//   This ensures we can validate the policy used for the transition and we have
35//   the necessary data to satisfy it.
36// - the additional output data to reconstruct the transactions in full
37//   (since our own transition is just one of the outputs)
38//
39// Since an exit of N transactions has N times the tx construction data,
40// but N+1 times the transition policy data, we decided to structure the
41// genesis series as follows:
42//
43// The genesis consists of "genesis items", which contain:
44// - the output policy of the previous output (of the parent)
45// - the witness to satisfy this policy
46// - the additional output data to construct an exit tx
47//
48// This means that
49// - there are an equal number of genesis items as there are exit transactions
50// - the first item will hold the output policy of the chain anchor
51// - to construct the output of the exit tx at a certain level, we get the
52//   output policy from the next genesis item
53// - the last tx's output policy is not held in the genesis, but it is held as
54//   the VTXO's own output policy
55
56pub 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
92/// The total signed tx weight of a exit tx.
93pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
94
95/// The current version of the vtxo encoding.
96const 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	/// Size in bytes of an encoded [VtxoId].
108	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
209/// Returns the clause to unilaterally spend a VTXO
210pub(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
217/// Create an exit tx.
218///
219/// When the `signature` argument is provided,
220/// it will be placed in the input witness.
221pub 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
245/// Represents the kind of [GenesisTransition]
246pub(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/// Enum type used to represent a preimage<>hash relationship
275/// for which the preimage might be known but the hash always
276/// should be known.
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub(crate) enum MaybePreimage {
279	Preimage([u8; 32]),
280	Hash(sha256::Hash),
281}
282
283impl MaybePreimage {
284	/// Get the hash
285	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/// A transition from one genesis tx to the next.
294///
295/// See private module-level documentation for more info.
296#[derive(Debug, Clone, PartialEq, Eq)]
297pub(crate) enum GenesisTransition {
298	/// A transition based on a cosignature.
299	///
300	/// This can be either the result of a cosigned "clArk" tree branch transition
301	/// or a board which is cosigned just with the server.
302	Cosigned {
303		/// All the cosign pubkeys signing the node.
304		///
305		/// Has to include server's cosign pubkey because it differs
306		/// from its regular pubkey.
307		pubkeys: Vec<PublicKey>,
308		signature: schnorr::Signature,
309	},
310	/// A transition based on a cosignature and a hash lock
311	///
312	/// This is the transition type for hArk leaf policy outputs,
313	/// that spend into the leaf transaction.
314	///
315	/// Refraining from any optimizations, this type is implemented the naive way:
316	/// - the keyspend path is currently unused, could be used later
317	/// - witness will always contain the cosignature and preimage in the script spend
318	HashLockedCosigned {
319		/// User pubkey that is combined with the server pubkey
320		user_pubkey: PublicKey,
321		/// The script-spend signature
322		signature: Option<schnorr::Signature>,
323		/// The unlock preimage or the unlock hash
324		unlock: MaybePreimage,
325	},
326	/// A regular arkoor spend, using the co-signed p2tr key-spend path.
327	Arkoor {
328		policy: VtxoPolicy,
329		signature: Option<schnorr::Signature>,
330	},
331}
332
333impl GenesisTransition {
334	/// Taproot that this transition is satisfying.
335	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	/// Output that this transition is spending.
354	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	/// The transaction witness for this transition.
369	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				// without preimage or signature this transition is unfulfilled
398				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	/// Whether the transition is fully signed
407	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	/// String of the transition kind, for error reporting
421	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/// An item in a VTXO's genesis.
431///
432/// See private module-level documentation for more info.
433#[derive(Debug, Clone, PartialEq, Eq)]
434pub(crate) struct GenesisItem {
435	/// The transition from the previous tx to this one.
436	pub(crate) transition: GenesisTransition,
437	/// The output index ("vout") of the output going to the next genesis item.
438	pub(crate) output_idx: u8,
439	/// The other outputs to construct the exit tx.
440	// NB empty for the first item
441	pub(crate) other_outputs: Vec<TxOut>,
442}
443
444impl GenesisItem {
445	/// Construct the exit transaction at this level of the genesis.
446	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/// Type of the items yielded by [VtxoTxIter], the iterator returned by
474/// [Vtxo::transactions].
475#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
476pub struct VtxoTxIterItem {
477	/// The actual transaction.
478	pub tx: Transaction,
479	/// The index of the relevant output of this tx
480	pub output_idx: usize,
481}
482
483/// Iterator returned by [Vtxo::transactions].
484pub 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		// Add all the amounts that go into the other outputs.
496		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			// when we reach the end of the chain, we take the eventual output of the vtxo
531			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/// Information that specifies a VTXO, independent of its origin.
554#[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	/// The taproot spend info for the output of this [Vtxo].
565	pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
566		self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
567	}
568
569	/// The scriptPubkey of the output of this [Vtxo].
570	pub fn output_script_pubkey(&self) -> ScriptBuf {
571		self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
572	}
573
574	/// The transaction output (eventual UTXO) of this [Vtxo].
575	pub fn txout(&self) -> TxOut {
576		self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
577	}
578}
579
580/// Represents a VTXO in the Ark.
581///
582/// The correctness of the return values of methods on this type is conditional
583/// on the VTXO being valid. For invalid VTXOs, the methods should never panic,
584/// but can return incorrect values.
585/// It is advised to always validate a VTXO upon receipt using [Vtxo::validate].
586///
587/// Be mindful of calling [Clone] on a [Vtxo], as they can be of
588/// non-negligible size. It is advised to use references where possible
589/// or use an [std::rc::Rc] or [std::sync::Arc] if needed.
590///
591/// Implementations of [PartialEq], [Eq], [PartialOrd], [Ord] and [Hash] are
592/// proxied to the implementation on [Vtxo::id].
593#[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	/// The resulting actual "point" of the VTXO. I.e. the output of the last
606	/// exit tx of this VTXO.
607	///
608	/// We keep this for two reasons:
609	/// - the ID is based on this, so it should be cheaply accessible
610	/// - it forms as a good checksum for all the internal genesis data
611	pub(crate) point: OutPoint,
612}
613
614impl Vtxo {
615	/// Get the identifier for this [Vtxo].
616	///
617	/// This is the same as [Vtxo::point] but encoded as a byte array.
618	pub fn id(&self) -> VtxoId {
619		self.point.into()
620	}
621
622	/// Get the spec for this VTXO.
623	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	/// The outpoint from which to build forfeit or arkoor txs.
634	///
635	/// This can be an on-chain utxo or an off-chain vtxo.
636	pub fn point(&self) -> OutPoint {
637		self.point
638	}
639
640	/// The amount of the [Vtxo].
641	pub fn amount(&self) -> Amount {
642		self.amount
643	}
644
645	/// The UTXO that should be confirmed for this [Vtxo] to be valid.
646	///
647	/// It is the very root of the VTXO.
648	pub fn chain_anchor(&self) -> OutPoint {
649		self.anchor_point
650	}
651
652	/// The output policy of this VTXO.
653	pub fn policy(&self) -> &VtxoPolicy {
654		&self.policy
655	}
656
657	/// The output policy type of this VTXO.
658	pub fn policy_type(&self) -> VtxoPolicyKind {
659		self.policy.policy_type()
660	}
661
662	/// The expiry height of the [Vtxo].
663	pub fn expiry_height(&self) -> BlockHeight {
664		self.expiry_height
665	}
666
667	/// The server pubkey used in arkoor transitions.
668	pub fn server_pubkey(&self) -> PublicKey {
669		self.server_pubkey
670	}
671
672	/// The relative timelock block delta used for exits.
673	pub fn exit_delta(&self) -> BlockDelta {
674		self.exit_delta
675	}
676
677	/// Returns the total exit depth (including OOR depth) of the vtxo.
678	pub fn exit_depth(&self) -> u16 {
679		self.genesis.len() as u16
680	}
681
682	/// The public key used to cosign arkoor txs spending this [Vtxo].
683	/// This will return [None] if [VtxoPolicy::is_arkoor_compatible] returns false
684	/// for this VTXO's policy.
685	pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
686		self.policy.arkoor_pubkey()
687	}
688
689	/// Iterate over all arkoor pubkeys in the arkoor chain of this vtxo.
690	///
691	/// This does not include the current arkoor pubkey, for that use
692	/// [Vtxo::arkoor_pubkey].
693	pub fn past_arkoor_pubkeys(&self) -> impl Iterator<Item = PublicKey> + '_ {
694		self.genesis.iter().filter_map(|g| {
695			match g.transition {
696				// NB in principle, a genesis item's transition MUST have
697				// an arkoor pubkey, otherwise the vtxo is invalid
698				GenesisTransition::Arkoor { ref policy, .. } => policy.arkoor_pubkey(),
699				_ => None,
700			}
701		})
702	}
703
704
705	/// Returns the user pubkey associated with this [Vtxo].
706	pub fn user_pubkey(&self) -> PublicKey {
707		self.policy.user_pubkey()
708	}
709
710	/// Return the aggregate pubkey of the user and server pubkey used in
711	/// hArk forfeit transactions
712	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	/// The taproot spend info for the output of this [Vtxo].
719	pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
720		self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
721	}
722
723	/// The scriptPubkey of the output of this [Vtxo].
724	pub fn output_script_pubkey(&self) -> ScriptBuf {
725		self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
726	}
727
728	/// The transaction output (eventual UTXO) of this [Vtxo].
729	pub fn txout(&self) -> TxOut {
730		self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
731	}
732
733	/// Whether this VTXO is fully signed
734	///
735	/// It is possible to represent unsigned VTXOs, for which this method
736	/// will return false.
737	pub fn is_fully_signed(&self) -> bool {
738		self.genesis.iter().all(|g| g.transition.is_fully_signed())
739	}
740
741	/// Iterator that constructs all the exit txs for this [Vtxo].
742	pub fn transactions(&self) -> VtxoTxIter<'_> {
743		VtxoTxIter::new(self)
744	}
745
746	/// The set of all arkoor pubkeys present in the arkoor part
747	/// of the VTXO exit path.
748	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	/// Fully validate this VTXO and its entire transaction chain.
757	///
758	/// The `chain_anchor_tx` must be the tx with txid matching
759	/// [Vtxo::chain_anchor].
760	pub fn validate(
761		&self,
762		chain_anchor_tx: &Transaction,
763	) -> Result<(), VtxoValidationError> {
764		self::validation::validate(&self, chain_anchor_tx)
765	}
766
767	/// Returns the "hArk" unlock hash if this is a hArk leaf VTXO
768	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	/// Provide the leaf signature for an unfinalized hArk VTXO
776	///
777	/// Returns true if this VTXO was an unfinalized hArk VTXO.
778	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	/// Provide the unlock preimage for an unfinalized hArk VTXO
789	///
790	/// Returns true if this VTXO was an unfinalized hArk VTXO and the preimage matched.
791	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	/// Shortcut to fully finalize a hark leaf using both keys
806	#[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		// first sign and provide the signature
817		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		// then provide preimage
821		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
857/// Implemented on anything that is kinda a [Vtxo]
858pub trait VtxoRef {
859	/// The [VtxoId] of the VTXO
860	fn vtxo_id(&self) -> VtxoId;
861
862	/// If the [Vtxo] can be provided, provides it
863	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
886/// The byte used to encode the [VtxoPolicy::Pubkey] output type.
887const VTXO_POLICY_PUBKEY: u8 = 0x00;
888
889/// The byte used to encode the [VtxoPolicy::ServerHtlcSend] output type.
890const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
891
892/// The byte used to encode the [VtxoPolicy::ServerHtlcRecv] output type.
893const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
894
895/// The byte used to encode the [VtxoPolicy::Checkpoint] output type.
896const 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
958/// The byte used to encode the [GenesisTransition::Cosigned] gen transition type.
959const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
960
961/// The byte used to encode the [GenesisTransition::Arkoor] gen transition type.
962const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
963
964/// The byte used to encode the [GenesisTransition::HashLockedCosigned] gen transition type.
965const 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)] // under the "test-util" feature it's unused
1152	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		// arkoor1: htlc send
1201
1202		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		// arkoor2: regular pubkey
1229
1230		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		// round 1
1252
1253		//TODO(stevenroose) rename to round htlc in
1254		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		// we don't need forfeits
1382		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		// arkoor3: off from round2's htlc
1399
1400		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		/// A set of deterministically generated and fully correct VTXOs.
1432		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		// the generation code prints its inner values
1450
1451		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		// this passes because the Eq is based on id which doesn't compare signatures
1472		assert_eq!(g, *v);
1473	}
1474
1475	#[test]
1476	fn exit_depth() {
1477		let vtxos = &*VTXO_VECTORS;
1478		// board
1479		assert_eq!(vtxos.board_vtxo.exit_depth(), 1 /* cosign */);
1480
1481		// round
1482		assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 /* cosign */);
1483
1484		// arkoor
1485		assert_eq!(vtxos.arkoor_htlc_out_vtxo.exit_depth(), 1 /* cosign */ + 1 /* arkoor */);
1486		assert_eq!(vtxos.arkoor2_vtxo.exit_depth(), 1 /* cosign */ + 2 /* arkoor */);
1487		assert_eq!(vtxos.arkoor3_vtxo.exit_depth(), 3 /* cosign */ + 1 /* arkoor */);
1488	}
1489}