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, SECP};
88use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
89use crate::lightning::{PaymentHash};
90use crate::tree::signed::{cosign_taproot, leaf_cosign_taproot, unlock_clause};
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 input weight required to claim a VTXO.
96const VTXO_CLAIM_INPUT_WEIGHT: Weight = Weight::from_wu(138);
97
98/// The current version of the vtxo encoding.
99const VTXO_ENCODING_VERSION: u16 = 1;
100
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
103#[error("failed to parse vtxo id, must be 36 bytes")]
104pub struct VtxoIdParseError;
105
106#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
107pub struct VtxoId([u8; 36]);
108
109impl VtxoId {
110	/// Size in bytes of an encoded [VtxoId].
111	pub const ENCODE_SIZE: usize = 36;
112
113	pub fn from_slice(b: &[u8]) -> Result<VtxoId, VtxoIdParseError> {
114		if b.len() == 36 {
115			let mut ret = [0u8; 36];
116			ret[..].copy_from_slice(&b[0..36]);
117			Ok(Self(ret))
118		} else {
119			Err(VtxoIdParseError)
120		}
121	}
122
123	pub fn utxo(self) -> OutPoint {
124		let vout = [self.0[32], self.0[33], self.0[34], self.0[35]];
125		OutPoint::new(Txid::from_slice(&self.0[0..32]).unwrap(), u32::from_le_bytes(vout))
126	}
127
128	pub fn to_bytes(self) -> [u8; 36] {
129		self.0
130	}
131}
132
133impl From<OutPoint> for VtxoId {
134	fn from(p: OutPoint) -> VtxoId {
135		let mut ret = [0u8; 36];
136		ret[0..32].copy_from_slice(&p.txid[..]);
137		ret[32..].copy_from_slice(&p.vout.to_le_bytes());
138		VtxoId(ret)
139	}
140}
141
142impl AsRef<[u8]> for VtxoId {
143	fn as_ref(&self) -> &[u8] {
144		&self.0
145	}
146}
147
148impl fmt::Display for VtxoId {
149	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150		fmt::Display::fmt(&self.utxo(), f)
151	}
152}
153
154impl fmt::Debug for VtxoId {
155	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156		fmt::Display::fmt(self, f)
157	}
158}
159
160impl FromStr for VtxoId {
161	type Err = VtxoIdParseError;
162	fn from_str(s: &str) -> Result<Self, Self::Err> {
163		Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
164	}
165}
166
167impl serde::Serialize for VtxoId {
168	fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
169		if s.is_human_readable() {
170			s.collect_str(self)
171		} else {
172			s.serialize_bytes(self.as_ref())
173		}
174	}
175}
176
177impl<'de> serde::Deserialize<'de> for VtxoId {
178	fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
179		struct Visitor;
180		impl<'de> serde::de::Visitor<'de> for Visitor {
181			type Value = VtxoId;
182			fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183				write!(f, "a VtxoId")
184			}
185			fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
186				VtxoId::from_slice(v).map_err(serde::de::Error::custom)
187			}
188			fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
189				VtxoId::from_str(v).map_err(serde::de::Error::custom)
190			}
191		}
192		if d.is_human_readable() {
193			d.deserialize_str(Visitor)
194		} else {
195			d.deserialize_bytes(Visitor)
196		}
197	}
198}
199
200impl ProtocolEncoding for VtxoId {
201	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
202		w.emit_slice(&self.0)
203	}
204	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
205		let array: [u8; 36] = r.read_byte_array()
206			.map_err(|_| ProtocolDecodingError::invalid("invalid vtxo id. Expected 36 bytes"))?;
207
208		Ok(VtxoId(array))
209	}
210}
211
212/// Returns the clause to unilaterally spend a VTXO
213pub(crate) fn exit_clause(
214	user_pubkey: PublicKey,
215	exit_delta: BlockDelta,
216) -> ScriptBuf {
217	scripts::delayed_sign(exit_delta, user_pubkey.x_only_public_key().0)
218}
219
220/// Returns taproot spend info for a regular vtxo exit output.
221pub fn exit_taproot(
222	user_pubkey: PublicKey,
223	server_pubkey: PublicKey,
224	exit_delta: BlockDelta,
225) -> taproot::TaprootSpendInfo {
226	let combined_pk = musig::combine_keys([user_pubkey, server_pubkey]);
227	taproot::TaprootBuilder::new()
228		.add_leaf(0, exit_clause(user_pubkey, exit_delta)).unwrap()
229		.finalize(&SECP, combined_pk).unwrap()
230}
231
232/// Returns the clause which allows the server to sweep funds after expiry
233pub fn expiry_clause(
234	server_pubkey: PublicKey,
235	expiry_height: BlockHeight,
236) -> ScriptBuf {
237	let pk = server_pubkey.x_only_public_key().0;
238	scripts::timelock_sign(expiry_height, pk)
239}
240
241/// The Taproot spend info for the checkpoint policy
242///
243/// user_pubkey: The public key of the user
244/// server_pubkey: The public key of the serve
245/// expiry_height; The height at which the checkpoint will expire
246pub fn checkpoint_taproot(
247	user_pubkey: PublicKey,
248	server_pubkey: PublicKey,
249	expiry_height: BlockHeight,
250) -> taproot::TaprootSpendInfo {
251	let combined_pk = musig::combine_keys([user_pubkey, server_pubkey]);
252	taproot::TaprootBuilder::new()
253		.add_leaf(0, expiry_clause(server_pubkey, expiry_height)).unwrap()
254		.finalize(&SECP, combined_pk).unwrap()
255}
256
257
258/// Create an exit tx.
259///
260/// When the `signature` argument is provided,
261/// it will be placed in the input witness.
262pub fn create_exit_tx(
263	prevout: OutPoint,
264	output: TxOut,
265	signature: Option<&schnorr::Signature>,
266) -> Transaction {
267	Transaction {
268		version: bitcoin::transaction::Version(3),
269		lock_time: LockTime::ZERO,
270		input: vec![TxIn {
271			previous_output: prevout,
272			script_sig: ScriptBuf::new(),
273			sequence: Sequence::ZERO,
274			witness: {
275				let mut ret = Witness::new();
276				if let Some(sig) = signature {
277					ret.push(&sig[..]);
278				}
279				ret
280			},
281		}],
282		output: vec![output, fee::fee_anchor()],
283	}
284}
285
286
287
288/// Enum type used to represent a preimage<>hash relationship
289/// for which the preimage might be known but the hash always
290/// should be known.
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub(crate) enum MaybePreimage {
293	Preimage([u8; 32]),
294	Hash(sha256::Hash),
295}
296
297impl MaybePreimage {
298	/// Get the hash
299	fn hash(&self) -> sha256::Hash {
300		match self {
301			Self::Preimage(p) => sha256::Hash::hash(p),
302			Self::Hash(h) => *h,
303		}
304	}
305}
306
307/// A transition from one genesis tx to the next.
308///
309/// See private module-level documentation for more info.
310#[derive(Debug, Clone, PartialEq, Eq)]
311pub(crate) enum GenesisTransition {
312	/// A transition based on a cosignature.
313	///
314	/// This can be either the result of a cosigned "clArk" tree branch transition
315	/// or a board which is cosigned just with the server.
316	Cosigned {
317		/// All the cosign pubkeys signing the node.
318		///
319		/// Has to include server's cosign pubkey because it differs
320		/// from its regular pubkey.
321		pubkeys: Vec<PublicKey>,
322		signature: schnorr::Signature,
323	},
324	/// A transition based on a cosignature and a hash lock
325	///
326	/// This is the transition type for hArk leaf policy outputs,
327	/// that spend into the leaf transaction.
328	///
329	/// Refraining from any optimizations, this type is implemented the naive way:
330	/// - the keyspend path is currently unused, could be used later
331	/// - witness will always contain the cosignature and preimage in the script spend
332	HashLockedCosigned {
333		/// User pubkey that is combined with the server pubkey
334		user_pubkey: PublicKey,
335		/// The script-spend signature
336		signature: schnorr::Signature,
337		/// The unlock preimage or the unlock hash
338		unlock: MaybePreimage,
339	},
340	/// A regular arkoor spend, using the co-signed p2tr key-spend path.
341	Arkoor {
342		policy: VtxoPolicy,
343		signature: Option<schnorr::Signature>,
344	},
345}
346
347impl GenesisTransition {
348	/// Taproot that this transition is satisfying.
349	fn input_taproot(
350		&self,
351		server_pubkey: PublicKey,
352		expiry_height: BlockHeight,
353		exit_delta: BlockDelta,
354	) -> taproot::TaprootSpendInfo {
355		match self {
356			Self::Cosigned { pubkeys, .. } => {
357				let agg_pk = musig::combine_keys(pubkeys.iter().copied());
358				cosign_taproot(agg_pk, server_pubkey, expiry_height)
359			},
360			Self::HashLockedCosigned { user_pubkey, unlock, .. } => {
361				let agg_pk = musig::combine_keys([*user_pubkey, server_pubkey]);
362				leaf_cosign_taproot(agg_pk, server_pubkey, expiry_height, unlock.hash())
363			},
364			Self::Arkoor { policy, .. } => policy.taproot(server_pubkey, exit_delta, expiry_height),
365		}
366	}
367
368	/// Output that this transition is spending.
369	fn input_txout(
370		&self,
371		amount: Amount,
372		server_pubkey: PublicKey,
373		expiry_height: BlockHeight,
374		exit_delta: BlockDelta,
375	) -> TxOut {
376		let taproot = self.input_taproot(server_pubkey, expiry_height, exit_delta);
377		TxOut {
378			value: amount,
379			script_pubkey: taproot.script_pubkey(),
380		}
381	}
382
383	/// The transaction witness for this transition.
384	fn witness(
385		&self,
386		server_pubkey: PublicKey,
387		expiry_height: BlockHeight,
388	) -> Witness {
389		match self {
390			Self::Cosigned { signature, .. } => Witness::from_slice(&[&signature[..]]),
391			Self::HashLockedCosigned {
392				user_pubkey,
393				signature,
394				unlock: MaybePreimage::Preimage(preimage),
395			} => {
396				let unlock_hash = sha256::Hash::hash(preimage);
397				let agg_pk = musig::combine_keys([*user_pubkey, server_pubkey]);
398				let taproot = leaf_cosign_taproot(
399					agg_pk, server_pubkey, expiry_height, unlock_hash,
400				);
401
402				let clause = unlock_clause(agg_pk, unlock_hash);
403				let script_leaf = (clause, LeafVersion::TapScript);
404				let cb = taproot.control_block(&script_leaf)
405					.expect("unlock clause not found in hArk taproot");
406				Witness::from_slice(&[
407					&signature.serialize()[..],
408					&preimage[..],
409					&script_leaf.0.as_bytes(),
410					&cb.serialize()[..],
411				])
412			},
413			Self::HashLockedCosigned { unlock: MaybePreimage::Hash(_), .. } => {
414				// without preimage this transition is unfulfilled
415				Witness::new()
416			},
417			Self::Arkoor { signature: Some(sig), .. } => Witness::from_slice(&[&sig[..]]),
418			Self::Arkoor { signature: None, .. } => Witness::new(),
419		}
420	}
421
422
423	/// Whether the transition is fully signed
424	fn is_fully_signed(&self) -> bool {
425		match self {
426			Self::Cosigned { .. } => true,
427			Self::HashLockedCosigned { unlock: MaybePreimage::Hash(_), .. } => false,
428			Self::HashLockedCosigned { unlock: MaybePreimage::Preimage(_), .. } => true,
429			Self::Arkoor { signature, .. } => signature.is_some(),
430		}
431	}
432
433	/// String of the transition type, for error reporting
434	fn transition_type(&self) -> &'static str {
435		match self {
436			Self::Cosigned { .. } => "cosigned",
437			Self::HashLockedCosigned { .. } => "hash-locked-cosigned",
438			Self::Arkoor { .. } => "arkoor",
439		}
440	}
441}
442
443/// An item in a VTXO's genesis.
444///
445/// See private module-level documentation for more info.
446#[derive(Debug, Clone, PartialEq, Eq)]
447pub(crate) struct GenesisItem {
448	/// The transition from the previous tx to this one.
449	pub(crate) transition: GenesisTransition,
450	/// The output index ("vout") of the output going to the next genesis item.
451	pub(crate) output_idx: u8,
452	/// The other outputs to construct the exit tx.
453	// NB empty for the first item
454	pub(crate) other_outputs: Vec<TxOut>,
455}
456
457impl GenesisItem {
458	/// Construct the exit transaction at this level of the genesis.
459	fn tx(&self,
460		prev: OutPoint,
461		next: TxOut,
462		server_pubkey: PublicKey,
463		expiry_height: BlockHeight,
464	) -> Transaction {
465		Transaction {
466			version: bitcoin::transaction::Version(3),
467			lock_time: bitcoin::absolute::LockTime::ZERO,
468			input: vec![TxIn {
469				previous_output: prev,
470				script_sig: ScriptBuf::new(),
471				sequence: Sequence::ZERO,
472				witness: self.transition.witness(server_pubkey, expiry_height),
473			}],
474			output: {
475				let mut out = Vec::with_capacity(self.other_outputs.len() + 2);
476				out.extend(self.other_outputs.iter().take(self.output_idx as usize).cloned());
477				out.push(next);
478				out.extend(self.other_outputs.iter().skip(self.output_idx as usize).cloned());
479				out.push(fee::fee_anchor());
480				out
481			},
482		}
483	}
484}
485
486/// Type of the items yielded by [VtxoTxIter], the iterator returned by
487/// [Vtxo::transactions].
488#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
489pub struct VtxoTxIterItem {
490	/// The actual transaction.
491	pub tx: Transaction,
492	/// The index of the relevant output of this tx
493	pub output_idx: usize,
494}
495
496/// Iterator returned by [Vtxo::transactions].
497pub struct VtxoTxIter<'a> {
498	vtxo: &'a Vtxo,
499
500	prev: OutPoint,
501	genesis_idx: usize,
502	current_amount: Amount,
503	done: bool,
504}
505
506impl<'a> VtxoTxIter<'a> {
507	fn new(vtxo: &'a Vtxo) -> VtxoTxIter<'a> {
508		// Add all the amounts that go into the other outputs.
509		let onchain_amount = vtxo.amount() + vtxo.genesis.iter().map(|i| {
510			i.other_outputs.iter().map(|o| o.value).sum()
511		}).sum();
512		VtxoTxIter {
513			prev: vtxo.anchor_point,
514			vtxo: vtxo,
515			genesis_idx: 0,
516			current_amount: onchain_amount,
517			done: false,
518		}
519	}
520}
521
522impl<'a> Iterator for VtxoTxIter<'a> {
523	type Item = VtxoTxIterItem;
524
525	fn next(&mut self) -> Option<Self::Item> {
526		if self.done {
527			return None;
528		}
529
530		let item = self.vtxo.genesis.get(self.genesis_idx).expect("broken impl");
531		let next_amount = self.current_amount.checked_sub(
532			item.other_outputs.iter().map(|o| o.value).sum()
533		).expect("we calculated this amount beforehand");
534
535		let next_output = if let Some(item) = self.vtxo.genesis.get(self.genesis_idx + 1) {
536			item.transition.input_txout(
537				next_amount,
538				self.vtxo.server_pubkey,
539				self.vtxo.expiry_height,
540				self.vtxo.exit_delta,
541			)
542		} else {
543			// when we reach the end of the chain, we take the eventual output of the vtxo
544			self.done = true;
545			self.vtxo.policy.txout(self.vtxo.amount, self.vtxo.server_pubkey, self.vtxo.exit_delta, self.vtxo.expiry_height)
546		};
547
548		let tx = item.tx(self.prev, next_output, self.vtxo.server_pubkey, self.vtxo.expiry_height);
549		self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
550		self.genesis_idx += 1;
551		self.current_amount = next_amount;
552		let output_idx = item.output_idx as usize;
553		Some(VtxoTxIterItem { tx, output_idx })
554	}
555
556	fn size_hint(&self) -> (usize, Option<usize>) {
557		let len = self.vtxo.genesis.len().saturating_sub(self.genesis_idx);
558		(len, Some(len))
559	}
560}
561
562impl<'a> ExactSizeIterator for VtxoTxIter<'a> {}
563impl<'a> FusedIterator for VtxoTxIter<'a> {}
564
565
566/// Information that specifies a VTXO, independent of its origin.
567#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
568pub struct VtxoSpec {
569	pub policy: VtxoPolicy,
570	pub amount: Amount,
571	pub expiry_height: BlockHeight,
572	pub server_pubkey: PublicKey,
573	pub exit_delta: BlockDelta,
574}
575
576impl VtxoSpec {
577	/// The taproot spend info for the output of this [Vtxo].
578	pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
579		self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
580	}
581
582	/// The scriptPubkey of the output of this [Vtxo].
583	pub fn output_script_pubkey(&self) -> ScriptBuf {
584		self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
585	}
586
587	/// The transaction output (eventual UTXO) of this [Vtxo].
588	pub fn txout(&self) -> TxOut {
589		self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
590	}
591}
592
593/// Represents a VTXO in the Ark.
594///
595/// The correctness of the return values of methods on this type is conditional
596/// on the VTXO being valid. For invalid VTXOs, the methods should never panic,
597/// but can return incorrect values.
598/// It is advised to always validate a VTXO upon receipt using [Vtxo::validate].
599///
600/// Be mindful of calling [Clone] on a [Vtxo], as they can be of
601/// non-negligible size. It is advised to use references where possible
602/// or use an [std::rc::Rc] or [std::sync::Arc] if needed.
603///
604/// Implementations of [PartialEq], [Eq], [PartialOrd], [Ord] and [Hash] are
605/// proxied to the implementation on [Vtxo::id].
606#[derive(Debug, Clone)]
607pub struct Vtxo {
608	pub(crate) policy: VtxoPolicy,
609	pub(crate) amount: Amount,
610	pub(crate) expiry_height: BlockHeight,
611
612	pub(crate) server_pubkey: PublicKey,
613	pub(crate) exit_delta: BlockDelta,
614
615	pub(crate) anchor_point: OutPoint,
616	pub(crate) genesis: Vec<GenesisItem>,
617
618	/// The resulting actual "point" of the VTXO. I.e. the output of the last
619	/// exit tx of this VTXO.
620	///
621	/// We keep this for two reasons:
622	/// - the ID is based on this, so it should be cheaply accessible
623	/// - it forms as a good checksum for all the internal genesis data
624	pub(crate) point: OutPoint,
625}
626
627impl Vtxo {
628	/// Get the identifier for this [Vtxo].
629	///
630	/// This is the same as [Vtxo::point] but encoded as a byte array.
631	pub fn id(&self) -> VtxoId {
632		self.point.into()
633	}
634
635	/// Get the spec for this VTXO.
636	pub fn spec(&self) -> VtxoSpec {
637		VtxoSpec {
638			policy: self.policy.clone(),
639			amount: self.amount,
640			expiry_height: self.expiry_height,
641			server_pubkey: self.server_pubkey,
642			exit_delta: self.exit_delta,
643		}
644	}
645
646	/// The outpoint from which to build forfeit or arkoor txs.
647	///
648	/// This can be an on-chain utxo or an off-chain vtxo.
649	pub fn point(&self) -> OutPoint {
650		self.point
651	}
652
653	/// The amount of the [Vtxo].
654	pub fn amount(&self) -> Amount {
655		self.amount
656	}
657
658	/// The UTXO that should be confirmed for this [Vtxo] to be valid.
659	///
660	/// It is the very root of the VTXO.
661	pub fn chain_anchor(&self) -> OutPoint {
662		self.anchor_point
663	}
664
665	/// The output policy of this VTXO.
666	pub fn policy(&self) -> &VtxoPolicy {
667		&self.policy
668	}
669
670	/// The output policy type of this VTXO.
671	pub fn policy_type(&self) -> VtxoPolicyKind {
672		self.policy.policy_type()
673	}
674
675	/// The expiry height of the [Vtxo].
676	pub fn expiry_height(&self) -> BlockHeight {
677		self.expiry_height
678	}
679
680	/// The server pubkey used in arkoor transitions.
681	pub fn server_pubkey(&self) -> PublicKey {
682		self.server_pubkey
683	}
684
685	/// The relative timelock block delta used for exits.
686	pub fn exit_delta(&self) -> BlockDelta {
687		self.exit_delta
688	}
689
690	/// Returns the total exit depth (including OOR depth) of the vtxo.
691	pub fn exit_depth(&self) -> u16 {
692		self.genesis.len() as u16
693	}
694
695	/// Get the payment hash if this vtxo is an HTLC send arkoor vtxo.
696	pub fn server_htlc_out_payment_hash(&self) -> Option<PaymentHash> {
697		match self.policy {
698			VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { payment_hash, .. }) => {
699				Some(payment_hash)
700			},
701			VtxoPolicy::ServerHtlcRecv(_) => None,
702			VtxoPolicy::Pubkey(_) => None,
703			VtxoPolicy::Checkpoint(_) => None,
704		}
705	}
706
707	/// The public key used to cosign arkoor txs spending this [Vtxo].
708	/// This will return [None] if [Vtxo::is_arkoor_compatible] returns false.
709	pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
710		self.policy.arkoor_pubkey()
711	}
712
713	/// Iterate over all arkoor pubkeys in the arkoor chain of this vtxo.
714	///
715	/// This does not include the current arkoor pubkey, for that use
716	/// [Vtxo::arkoor_pubkey].
717	pub fn past_arkoor_pubkeys(&self) -> impl Iterator<Item = PublicKey> + '_ {
718		self.genesis.iter().filter_map(|g| {
719			match g.transition {
720				// NB in principle, a genesis item's transition MUST have
721				// an arkoor pubkey, otherwise the vtxo is invalid
722				GenesisTransition::Arkoor { ref policy, .. } => policy.arkoor_pubkey(),
723				_ => None,
724			}
725		})
726	}
727
728
729	/// Returns the user pubkey associated with this [Vtxo].
730	pub fn user_pubkey(&self) -> PublicKey {
731		self.policy.user_pubkey()
732	}
733
734	/// Return the aggregate pubkey of the user and server pubkey used in
735	/// hArk forfeit transactions
736	pub(crate) fn forfeit_agg_pubkey(&self) -> XOnlyPublicKey {
737		let ret = musig::combine_keys([self.user_pubkey(), self.server_pubkey()]);
738		debug_assert_eq!(ret, self.output_taproot().internal_key());
739		ret
740	}
741
742	/// The taproot spend info for the output of this [Vtxo].
743	pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
744		self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
745	}
746
747	/// The scriptPubkey of the output of this [Vtxo].
748	pub fn output_script_pubkey(&self) -> ScriptBuf {
749		self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
750	}
751
752	/// The transaction output (eventual UTXO) of this [Vtxo].
753	pub fn txout(&self) -> TxOut {
754		self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
755	}
756
757	/// Whether this VTXO is fully signed
758	///
759	/// It is possible to represent unsigned VTXOs, for which this method
760	/// will return false.
761	pub fn is_fully_signed(&self) -> bool {
762		self.genesis.iter().all(|g| g.transition.is_fully_signed())
763	}
764
765	/// Iterator that constructs all the exit txs for this [Vtxo].
766	pub fn transactions(&self) -> VtxoTxIter<'_> {
767		VtxoTxIter::new(self)
768	}
769
770	/// The satisfaction weight required to spend the output
771	/// when doing a unilateral exit.
772	pub fn claim_satisfaction_weight(&self)  -> Weight {
773		match self.policy {
774			VtxoPolicy::Pubkey { .. } => VTXO_CLAIM_INPUT_WEIGHT,
775			VtxoPolicy::Checkpoint( ..) => VTXO_CLAIM_INPUT_WEIGHT,
776			//TODO(stevenroose) think about this. it's the same if you use keyspend
777			// but it's not the same if you have to use exit spend
778			// I guess the same holds for any vtxo
779			VtxoPolicy::ServerHtlcSend { .. } => VTXO_CLAIM_INPUT_WEIGHT,
780			VtxoPolicy::ServerHtlcRecv { .. } => VTXO_CLAIM_INPUT_WEIGHT,
781		}
782	}
783
784	/// The set of all arkoor pubkeys present in the arkoor part
785	/// of the VTXO exit path.
786	pub fn arkoor_pubkeys(&self) -> HashSet<PublicKey> {
787		self.genesis.iter().filter_map(|i| match &i.transition {
788			GenesisTransition::Arkoor { policy, .. } => policy.arkoor_pubkey(),
789			GenesisTransition::Cosigned { .. } => None,
790			GenesisTransition::HashLockedCosigned { .. } => None,
791		}).collect()
792	}
793
794	/// Fully validate this VTXO and its entire transaction chain.
795	///
796	/// The `chain_anchor_tx` must be the tx with txid matching
797	/// [Vtxo::chain_anchor].
798	pub fn validate(
799		&self,
800		chain_anchor_tx: &Transaction,
801	) -> Result<(), VtxoValidationError> {
802		self::validation::validate(&self, chain_anchor_tx)
803	}
804}
805
806impl PartialEq for Vtxo {
807	fn eq(&self, other: &Self) -> bool {
808		PartialEq::eq(&self.id(), &other.id())
809	}
810}
811
812impl Eq for Vtxo {}
813
814impl PartialOrd for Vtxo {
815	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
816		PartialOrd::partial_cmp(&self.id(), &other.id())
817	}
818}
819
820impl Ord for Vtxo {
821	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
822		Ord::cmp(&self.id(), &other.id())
823	}
824}
825
826impl std::hash::Hash for Vtxo {
827	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
828		std::hash::Hash::hash(&self.id(), state)
829	}
830}
831
832/// Implemented on anything that is kinda a [Vtxo]
833pub trait VtxoRef {
834	/// The [VtxoId] of the VTXO
835	fn vtxo_id(&self) -> VtxoId;
836
837	/// If the [Vtxo] can be provided, provides it
838	fn vtxo(&self) -> Option<&Vtxo>;
839}
840
841impl VtxoRef for VtxoId {
842	fn vtxo_id(&self) -> VtxoId { *self }
843	fn vtxo(&self) -> Option<&Vtxo> { None }
844}
845
846impl<'a> VtxoRef for &'a VtxoId {
847	fn vtxo_id(&self) -> VtxoId { **self }
848	fn vtxo(&self) -> Option<&Vtxo> { None }
849}
850
851impl VtxoRef for Vtxo {
852	fn vtxo_id(&self) -> VtxoId { self.id() }
853	fn vtxo(&self) -> Option<&Vtxo> { Some(self) }
854}
855
856impl<'a> VtxoRef for &'a Vtxo {
857	fn vtxo_id(&self) -> VtxoId { self.id() }
858	fn vtxo(&self) -> Option<&Vtxo> { Some(*self) }
859}
860
861/// The byte used to encode the [VtxoPolicy::Pubkey] output type.
862const VTXO_POLICY_PUBKEY: u8 = 0x00;
863
864/// The byte used to encode the [VtxoPolicy::ServerHtlcSend] output type.
865const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
866
867/// The byte used to encode the [VtxoPolicy::ServerHtlcRecv] output type.
868const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
869
870/// The byte used to encode the [VtxoPolicy::Checkpoint] output type.
871const VTXO_CHECKPOINT_CHECKPOINT: u8 = 0x03;
872
873impl ProtocolEncoding for VtxoPolicy {
874	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
875		match self {
876			Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
877				w.emit_u8(VTXO_POLICY_PUBKEY)?;
878				user_pubkey.encode(w)?;
879			},
880			Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
881				w.emit_u8(VTXO_CHECKPOINT_CHECKPOINT)?;
882				user_pubkey.encode(w)?;
883			},
884			Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
885				w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
886				user_pubkey.encode(w)?;
887				payment_hash.to_sha256_hash().encode(w)?;
888				w.emit_u32(*htlc_expiry)?;
889			},
890			Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
891				user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
892			}) => {
893				w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
894				user_pubkey.encode(w)?;
895				payment_hash.to_sha256_hash().encode(w)?;
896				w.emit_u32(*htlc_expiry)?;
897				w.emit_u16(*htlc_expiry_delta)?;
898			},
899		}
900		Ok(())
901	}
902
903	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
904		match r.read_u8()? {
905			VTXO_POLICY_PUBKEY => {
906				let user_pubkey = PublicKey::decode(r)?;
907				Ok(Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
908			},
909			VTXO_CHECKPOINT_CHECKPOINT => {
910				let user_pubkey = PublicKey::decode(r)?;
911				Ok(Self::new_checkpoint(user_pubkey))
912			}
913			VTXO_POLICY_SERVER_HTLC_SEND => {
914				let user_pubkey = PublicKey::decode(r)?;
915				let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
916				let htlc_expiry = r.read_u32()?;
917				Ok(Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
918			},
919			VTXO_POLICY_SERVER_HTLC_RECV => {
920				let user_pubkey = PublicKey::decode(r)?;
921				let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
922				let htlc_expiry = r.read_u32()?;
923				let htlc_expiry_delta = r.read_u16()?;
924				Ok(Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
925			},
926			v => Err(ProtocolDecodingError::invalid(format_args!(
927				"invalid VtxoType type byte: {v:#x}",
928			))),
929		}
930	}
931}
932
933/// The byte used to encode the [GenesisTransition::Cosigned] gen transition type.
934const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
935
936/// The byte used to encode the [GenesisTransition::Arkoor] gen transition type.
937const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
938
939/// The byte used to encode the [GenesisTransition::HashLockedCosigned] gen transition type.
940const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
941
942impl ProtocolEncoding for GenesisTransition {
943	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
944		match self {
945			Self::Cosigned { pubkeys, signature } => {
946				w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
947				w.emit_u16(pubkeys.len().try_into().expect("cosign pubkey length overflow"))?;
948				for pk in pubkeys {
949					pk.encode(w)?;
950				}
951				signature.encode(w)?;
952			},
953			Self::HashLockedCosigned { user_pubkey, signature, unlock } => {
954				w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
955				user_pubkey.encode(w)?;
956				signature.encode(w)?;
957				match unlock {
958					MaybePreimage::Preimage(p) => {
959						w.emit_u8(0)?;
960						w.emit_slice(&p[..])?;
961					},
962					MaybePreimage::Hash(h) => {
963						w.emit_u8(1)?;
964						w.emit_slice(&h[..])?;
965					},
966				}
967			},
968			Self::Arkoor { policy, signature } => {
969				w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
970				policy.encode(w)?;
971				signature.encode(w)?;
972			},
973		}
974		Ok(())
975	}
976
977	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
978		match r.read_u8()? {
979			GENESIS_TRANSITION_TYPE_COSIGNED => {
980				let nb_pubkeys = r.read_u16()? as usize;
981				let mut pubkeys = Vec::with_capacity(nb_pubkeys);
982				for _ in 0..nb_pubkeys {
983					pubkeys.push(PublicKey::decode(r)?);
984				}
985				let signature = schnorr::Signature::decode(r)?;
986				Ok(Self::Cosigned { pubkeys, signature })
987			},
988			GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
989				let user_pubkey = PublicKey::decode(r)?;
990				let signature = schnorr::Signature::decode(r)?;
991				let unlock = match r.read_u8()? {
992					0 => MaybePreimage::Preimage(r.read_byte_array()?),
993					1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
994					v => return Err(ProtocolDecodingError::invalid(format_args!(
995						"invalid HashLockedCosignedTransitionWitness type byte: {v:#x}",
996					))),
997				};
998				Ok(Self::HashLockedCosigned { user_pubkey, signature, unlock })
999			},
1000			GENESIS_TRANSITION_TYPE_ARKOOR => {
1001				let policy = VtxoPolicy::decode(r)?;
1002				let signature = Option::<schnorr::Signature>::decode(r)?;
1003				Ok(Self::Arkoor { policy, signature })
1004			},
1005			v => Err(ProtocolDecodingError::invalid(format_args!(
1006				"invalid GenesisTransistion type byte: {v:#x}",
1007			))),
1008		}
1009	}
1010}
1011
1012impl ProtocolEncoding for Vtxo {
1013	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1014		w.emit_u16(VTXO_ENCODING_VERSION)?;
1015		w.emit_u64(self.amount.to_sat())?;
1016		w.emit_u32(self.expiry_height)?;
1017		self.server_pubkey.encode(w)?;
1018		w.emit_u16(self.exit_delta)?;
1019		self.anchor_point.encode(w)?;
1020
1021		w.emit_u8(self.genesis.len().try_into().expect("genesis length overflow"))?;
1022		for item in &self.genesis {
1023			item.transition.encode(w)?;
1024			let nb_outputs = item.other_outputs.len() + 1;
1025			w.emit_u8(nb_outputs.try_into().expect("genesis item output length overflow"))?;
1026			w.emit_u8(item.output_idx)?;
1027			for txout in &item.other_outputs {
1028				txout.encode(w)?;
1029			}
1030		}
1031
1032		self.policy.encode(w)?;
1033		self.point.encode(w)?;
1034		Ok(())
1035	}
1036
1037	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1038		let version = r.read_u16()?;
1039		if version != VTXO_ENCODING_VERSION {
1040			return Err(ProtocolDecodingError::invalid(format_args!(
1041				"invalid Vtxo encoding version byte: {version:#x}",
1042			)));
1043		}
1044
1045		let amount = Amount::from_sat(r.read_u64()?);
1046		let expiry_height = r.read_u32()?;
1047		let server_pubkey = PublicKey::decode(r)?;
1048		let exit_delta = r.read_u16()?;
1049		let anchor_point = OutPoint::decode(r)?;
1050
1051		let nb_genesis_items = r.read_u8()? as usize;
1052		let mut genesis = Vec::with_capacity(nb_genesis_items);
1053		for _ in 0..nb_genesis_items {
1054			let transition = GenesisTransition::decode(r)?;
1055			let nb_outputs = r.read_u8()? as usize;
1056			let output_idx = r.read_u8()?;
1057			let nb_other = nb_outputs.checked_sub(1)
1058				.ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1059			let mut other_outputs = Vec::with_capacity(nb_other);
1060			for _ in 0..nb_other {
1061				other_outputs.push(TxOut::decode(r)?);
1062			}
1063			genesis.push(GenesisItem { transition, output_idx, other_outputs });
1064		}
1065
1066		let output = VtxoPolicy::decode(r)?;
1067		let point = OutPoint::decode(r)?;
1068
1069		Ok(Self {
1070			amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy: output, point,
1071		})
1072	}
1073}
1074
1075
1076#[cfg(any(test, feature = "test-util"))]
1077pub mod test {
1078	use std::iter;
1079	use std::collections::HashMap;
1080
1081	use bitcoin::consensus::encode::{deserialize_hex, serialize_hex};
1082	use bitcoin::hex::DisplayHex;
1083	use bitcoin::secp256k1::Keypair;
1084	use bitcoin::transaction::Version;
1085
1086	use crate::arkoor::ArkoorBuilder;
1087	use crate::board::BoardBuilder;
1088	use crate::encode::test::encoding_roundtrip;
1089	use crate::tree::signed::VtxoTreeSpec;
1090	use crate::{SignedVtxoRequest, VtxoRequest, SECP};
1091
1092	use super::*;
1093
1094	#[allow(unused)]
1095	#[macro_export]
1096	macro_rules! assert_eq_vtxos {
1097		($v1:expr, $v2:expr) => {
1098			let v1 = &$v1;
1099			let v2 = &$v2;
1100			assert_eq!(
1101				v1.serialize().as_hex().to_string(),
1102				v2.serialize().as_hex().to_string(),
1103				"vtxo {} != {}", v1.id(), v2.id(),
1104			);
1105		};
1106	}
1107
1108	#[derive(Debug, PartialEq, Eq)]
1109	pub struct VtxoTestVectors {
1110		pub server_key: Keypair,
1111
1112		pub anchor_tx: Transaction,
1113		pub board_vtxo: Vtxo,
1114
1115		pub arkoor_htlc_out_vtxo: Vtxo,
1116		pub arkoor2_vtxo: Vtxo,
1117
1118		pub round_tx: Transaction,
1119		pub round1_vtxo: Vtxo,
1120		pub round2_vtxo: Vtxo,
1121
1122		pub arkoor3_user_key: Keypair,
1123		pub arkoor3_vtxo: Vtxo,
1124	}
1125
1126	#[allow(unused)] // under the "test-util" feature it's unused
1127	fn generate_vtxo_vectors() -> VtxoTestVectors {
1128		let expiry_height = 101_010;
1129		let exit_delta = 2016;
1130		let server_key = Keypair::from_str("916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92").unwrap();
1131		let board_user_key = Keypair::from_str("fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879").unwrap();
1132		let amount = Amount::from_sat(10_000);
1133		let builder = BoardBuilder::new(
1134			board_user_key.public_key(),
1135			expiry_height,
1136			server_key.public_key(),
1137			exit_delta,
1138		);
1139		let anchor_tx = Transaction {
1140			version: Version::TWO,
1141			lock_time: LockTime::ZERO,
1142			input: vec![TxIn {
1143				previous_output: OutPoint::null(),
1144				script_sig: ScriptBuf::new(),
1145				sequence: Sequence::ZERO,
1146				witness: Witness::new(),
1147			}],
1148			output: vec![TxOut {
1149				value: Amount::from_sat(10_000),
1150				script_pubkey: builder.funding_script_pubkey(),
1151			}],
1152		};
1153		println!("chain anchor tx: {}", serialize_hex(&anchor_tx));
1154		let anchor_point = OutPoint::new(anchor_tx.compute_txid(), 0);
1155		let builder = builder.set_funding_details(amount, anchor_point)
1156			.generate_user_nonces();
1157
1158		let board_cosign = {
1159			BoardBuilder::new_for_cosign(
1160				builder.user_pubkey,
1161				builder.expiry_height,
1162				builder.server_pubkey,
1163				builder.exit_delta,
1164				amount,
1165				anchor_point,
1166				*builder.user_pub_nonce(),
1167			).server_cosign(&server_key)
1168		};
1169
1170		assert!(builder.verify_cosign_response(&board_cosign));
1171		let board_vtxo = builder.build_vtxo(&board_cosign, &board_user_key).unwrap();
1172		encoding_roundtrip(&board_vtxo);
1173		println!("board vtxo: {}", board_vtxo.serialize().as_hex());
1174
1175		// arkoor1: htlc send
1176
1177		let arkoor_htlc_out_user_key = Keypair::from_str("33b6f3ede430a1a53229f55da7117242d8392cbfc64a57249ba70731dba71408").unwrap();
1178		let payment_hash = PaymentHash::from(sha256::Hash::hash("arkoor1".as_bytes()).to_byte_array());
1179		let arkoor1out1 = VtxoRequest {
1180			amount: Amount::from_sat(9000),
1181			policy: VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy {
1182				user_pubkey: arkoor_htlc_out_user_key.public_key(),
1183				payment_hash,
1184				htlc_expiry: expiry_height - 1000,
1185			}),
1186		};
1187		let arkoor1out2 = VtxoRequest {
1188			amount: Amount::from_sat(1000),
1189			policy: VtxoPolicy::new_pubkey("0229b7de0ce4d573192d002a6f9fd1109e00f7bae52bf10780d6f6e73e12a8390f".parse().unwrap()),
1190		};
1191		let outputs = [&arkoor1out1, &arkoor1out2];
1192		let (sec_nonce, pub_nonce) = musig::nonce_pair(&board_user_key);
1193		let builder = ArkoorBuilder::new(&board_vtxo, &pub_nonce, &outputs).unwrap();
1194		let cosign = builder.server_cosign(&server_key);
1195		assert!(builder.verify_cosign_response(&cosign));
1196		let [arkoor_htlc_out_vtxo, change] = builder.build_vtxos(
1197			sec_nonce, &board_user_key, &cosign,
1198		).unwrap().try_into().unwrap();
1199		encoding_roundtrip(&arkoor_htlc_out_vtxo);
1200		encoding_roundtrip(&change);
1201		println!("arkoor1_vtxo: {}", arkoor_htlc_out_vtxo.serialize().as_hex());
1202
1203		// arkoor2: regular pubkey
1204
1205		let arkoor2_user_key = Keypair::from_str("fcc43a4f03356092a945ca1d7218503156bed3f94c2fa224578ce5b158fbf5a6").unwrap();
1206		let arkoor2out1 = VtxoRequest {
1207			amount: Amount::from_sat(8000),
1208			policy: VtxoPolicy::new_pubkey(arkoor2_user_key.public_key()),
1209		};
1210		let arkoor2out2 = VtxoRequest {
1211			amount: Amount::from_sat(1000),
1212			policy: VtxoPolicy::new_pubkey("037039dc4f4b16e78059d2d56eb98d181cb1bdff2675694d39d92c4a2ea08ced88".parse().unwrap()),
1213		};
1214		let outputs = [&arkoor2out1, &arkoor2out2];
1215		let (sec_nonce, pub_nonce) = musig::nonce_pair(&arkoor_htlc_out_user_key);
1216		let builder = ArkoorBuilder::new(&arkoor_htlc_out_vtxo, &pub_nonce, &outputs).unwrap();
1217		let arkoor2_cosign = builder.server_cosign(&server_key);
1218		assert!(builder.verify_cosign_response(&arkoor2_cosign));
1219		let [arkoor2_vtxo, change] = builder.build_vtxos(
1220			sec_nonce, &arkoor_htlc_out_user_key, &arkoor2_cosign,
1221		).unwrap().try_into().unwrap();
1222		encoding_roundtrip(&arkoor2_vtxo);
1223		encoding_roundtrip(&change);
1224		println!("arkoor2_vtxo: {}", arkoor2_vtxo.serialize().as_hex());
1225
1226		// round
1227
1228		//TODO(stevenroose) rename to round htlc in
1229		let round1_user_key = Keypair::from_str("0a832e9574070c94b5b078600a18639321c880c830c5ba2f2a96850c7dcc4725").unwrap();
1230		let round1_cosign_key = Keypair::from_str("e14bfc3199842c76816eec1d93c9da00b850c4ed19e414e246d07e845e465a2b").unwrap();
1231		println!("round1_cosign_key: {}", round1_cosign_key.public_key());
1232		let round1_req = SignedVtxoRequest {
1233			vtxo: VtxoRequest {
1234				amount: Amount::from_sat(10_000),
1235				policy: VtxoPolicy::new_pubkey(round1_user_key.public_key()),
1236			},
1237			cosign_pubkey: Some(round1_cosign_key.public_key()),
1238		};
1239		let round1_nonces = iter::repeat_with(|| musig::nonce_pair(&round1_cosign_key)).take(5).collect::<Vec<_>>();
1240
1241		let round2_user_key = Keypair::from_str("c0b645b01cac427717a18b30c7c9238dee2b3885f659930144fbe05061ad6166").unwrap();
1242		let round2_cosign_key = Keypair::from_str("628789cd7b7e02766d184ecfecc433798c9640349e41822df7996c66a56fc633").unwrap();
1243		println!("round2_cosign_key: {}", round2_cosign_key.public_key());
1244		let round2_payment_hash = PaymentHash::from(sha256::Hash::hash("round2".as_bytes()).to_byte_array());
1245		let round2_req = SignedVtxoRequest {
1246			vtxo: VtxoRequest {
1247				amount: Amount::from_sat(10_000),
1248				policy: VtxoPolicy::new_server_htlc_recv(
1249					round2_user_key.public_key(),
1250					round2_payment_hash,
1251					expiry_height - 2000,
1252					40,
1253				),
1254			},
1255			cosign_pubkey: Some(round2_cosign_key.public_key()),
1256		};
1257		let round2_nonces = iter::repeat_with(|| musig::nonce_pair(&round2_cosign_key)).take(5).collect::<Vec<_>>();
1258
1259		let others = [
1260			"93b376f64ada74f0fbf940be86f888459ac94655dc6a7805cc790b3c95a2a612",
1261			"00add86ff531ef53f877780622f0b376669ec6ad7e090131820ff7007e79f529",
1262			"775b836f2acf53de4ff9beeba2a17d5475e9b027d82fece72033ef06b954c7cd",
1263			"395c2c210481990a5d12d33dca37995e235a34b717c89647a33907c62e32dc09",
1264			"8f02f2a7aa1746bbcc92bba607b7166b6a77e9d0efd9d09dae7c2dc3addbdef1",
1265		];
1266		let mut other_reqs = Vec::new();
1267		let mut other_nonces = Vec::new();
1268		for k in others {
1269			let user_key = Keypair::from_str(k).unwrap();
1270			let cosign_key = Keypair::from_seckey_slice(&SECP, &sha256::Hash::hash(k.as_bytes())[..]).unwrap();
1271			other_reqs.push(SignedVtxoRequest {
1272				vtxo: VtxoRequest {
1273					amount: Amount::from_sat(5_000),
1274					policy: VtxoPolicy::new_pubkey(user_key.public_key()),
1275				},
1276				cosign_pubkey: Some(cosign_key.public_key()),
1277			});
1278			other_nonces.push(iter::repeat_with(|| musig::nonce_pair(&cosign_key)).take(5).collect::<Vec<_>>());
1279		}
1280
1281		let server_cosign_key = Keypair::from_str("4371a4a7989b89ebe1b2582db4cd658cb95070977e6f10601ddc1e9b53edee79").unwrap();
1282		let spec = VtxoTreeSpec::new(
1283			[&round1_req, &round2_req].into_iter().chain(other_reqs.iter()).cloned().collect(),
1284			server_key.public_key(),
1285			expiry_height,
1286			exit_delta,
1287			vec![server_cosign_key.public_key()],
1288		);
1289		let round_tx = Transaction {
1290			version: Version::TWO,
1291			lock_time: LockTime::ZERO,
1292			input: vec![TxIn {
1293				previous_output: OutPoint::null(),
1294				script_sig: ScriptBuf::new(),
1295				sequence: Sequence::ZERO,
1296				witness: Witness::new(),
1297			}],
1298			output: vec![TxOut {
1299				value: Amount::from_sat(45_000),
1300				script_pubkey: spec.funding_tx_script_pubkey(),
1301			}],
1302		};
1303		println!("round tx: {}", serialize_hex(&round_tx));
1304		let all_nonces = {
1305			let mut map = HashMap::new();
1306			map.insert(round1_cosign_key.public_key(), round1_nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1307			map.insert(round2_cosign_key.public_key(), round2_nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1308			for (req, nonces) in other_reqs.iter().zip(other_nonces.iter()) {
1309				map.insert(req.cosign_pubkey.unwrap(), nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1310			}
1311			map
1312		};
1313		let (server_cosign_sec_nonces, server_cosign_pub_nonces) = iter::repeat_with(|| {
1314			musig::nonce_pair(&server_cosign_key)
1315		}).take(spec.nb_nodes()).unzip::<_, _, Vec<_>, Vec<_>>();
1316		let cosign_agg_nonces = spec.calculate_cosign_agg_nonces(&all_nonces, &[&server_cosign_pub_nonces]).unwrap();
1317		let root_point = OutPoint::new(round_tx.compute_txid(), 0);
1318		let tree = spec.into_unsigned_tree(root_point);
1319		let part_sigs = {
1320			let mut map = HashMap::new();
1321			map.insert(round1_cosign_key.public_key(), {
1322				let secs = round1_nonces.into_iter().map(|(s, _)| s).collect();
1323				let r = tree.cosign_branch(&cosign_agg_nonces, 0, &round1_cosign_key, secs).unwrap();
1324				r
1325			});
1326			map.insert(round2_cosign_key.public_key(), {
1327				let secs = round2_nonces.into_iter().map(|(s, _)| s).collect();
1328				tree.cosign_branch(&cosign_agg_nonces, 1, &round2_cosign_key, secs).unwrap()
1329			});
1330			for (i, (req, nonces)) in other_reqs.iter().zip(other_nonces.into_iter()).enumerate() {
1331				let cosign_key = Keypair::from_seckey_slice(
1332					&SECP, &sha256::Hash::hash(others[i].as_bytes())[..],
1333				).unwrap();
1334				map.insert(req.cosign_pubkey.unwrap(), {
1335					let secs = nonces.into_iter().map(|(s, _)| s).collect();
1336					tree.cosign_branch(&cosign_agg_nonces, 2 + i, &cosign_key, secs).unwrap()
1337				});
1338			}
1339			map
1340		};
1341		let server_cosign_sigs = tree.cosign_tree(
1342			&cosign_agg_nonces, &server_cosign_key, server_cosign_sec_nonces,
1343		);
1344		let cosign_sigs = tree.combine_partial_signatures(&cosign_agg_nonces, &part_sigs, &[&server_cosign_sigs]).unwrap();
1345		assert!(tree.verify_cosign_sigs(&cosign_sigs).is_ok());
1346		let signed = tree.into_signed_tree(cosign_sigs).into_cached_tree();
1347		// we don't need forfeits
1348		let mut vtxo_iter = signed.all_vtxos();
1349		let round1_vtxo = vtxo_iter.next().unwrap();
1350		encoding_roundtrip(&round1_vtxo);
1351		println!("round1_vtxo: {}", round1_vtxo.serialize().as_hex());
1352		let round2_vtxo = vtxo_iter.next().unwrap();
1353		encoding_roundtrip(&round2_vtxo);
1354		println!("round2_vtxo: {}", round2_vtxo.serialize().as_hex());
1355
1356		// arkoor3: off from round2's htlc
1357
1358		let arkoor3_user_key = Keypair::from_str("ad12595bdbdab56cb61d1f60ccc46ff96b11c5d6fe06ae7ba03d3a5f4347440f").unwrap();
1359		let arkoor3out = VtxoRequest {
1360			amount: Amount::from_sat(10_000),
1361			policy: VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey: arkoor3_user_key.public_key() }),
1362		};
1363		let outputs = [&arkoor3out];
1364		let (sec_nonce, pub_nonce) = musig::nonce_pair(&round2_user_key);
1365		let builder = ArkoorBuilder::new(&round2_vtxo, &pub_nonce, &outputs).unwrap();
1366		let arkoor3_cosign = builder.server_cosign(&server_key);
1367		assert!(builder.verify_cosign_response(&arkoor3_cosign));
1368		let [arkoor3_vtxo] = builder.build_vtxos(
1369			sec_nonce, &round2_user_key, &arkoor3_cosign,
1370		).unwrap().try_into().unwrap();
1371		encoding_roundtrip(&arkoor3_vtxo);
1372		println!("arkoor3_vtxo: {}", arkoor3_vtxo.serialize().as_hex());
1373
1374		VtxoTestVectors {
1375			server_key,
1376			anchor_tx,
1377			board_vtxo,
1378			arkoor_htlc_out_vtxo,
1379			arkoor2_vtxo,
1380			round_tx,
1381			round1_vtxo,
1382			round2_vtxo,
1383			arkoor3_user_key,
1384			arkoor3_vtxo,
1385		}
1386	}
1387
1388	lazy_static! {
1389		/// A set of deterministically generated and fully correct VTXOs.
1390		pub static ref VTXO_VECTORS: VtxoTestVectors = VtxoTestVectors {
1391			server_key: Keypair::from_str("916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92").unwrap(),
1392			anchor_tx: deserialize_hex("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0000000000011027000000000000225120652675904a84ea02e24b57b3d547203d2ce71526113d35bf4d02e0b4efbe9a2d00000000").unwrap(),
1393			board_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000001010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a010000030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4c99b744ad009b7070f330794bf003fa8e5cd46ea1a6eb854aaf469385e3080000000000").unwrap(),
1394			arkoor_htlc_out_vtxo: ProtocolEncoding::deserialize_hex("01002823000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000002010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a01000200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4a4402956ec89190685f761fbe77018d1727c01d62f877cc5737d3f951725a0629410c1a0ead31328f24bb54c856d6efb3205032c1fdf723bf2fed7a7c6dfbc90200e8030000000000002251209b987ec3c169c70d1ed6aef420a4858e3e3ec9d8404358787d4e06ba926a4ae40103eb4570ae385202d4a48f06bdb14126910b90c07f8e42d7dc5e28a860c085e73712358912c950a9a7d04bb9011ee9f6a16b6127a5aab7415803d48c0225f620f5aa860100c692b81703c12cac1e8d69b86fa9f0e2f167168d96ae1045ef8d9192bc4a6e4c00000000").unwrap(),
1395			arkoor2_vtxo: ProtocolEncoding::deserialize_hex("0100401f000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000003010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a01000200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4a4402956ec89190685f761fbe77018d1727c01d62f877cc5737d3f951725a0629410c1a0ead31328f24bb54c856d6efb3205032c1fdf723bf2fed7a7c6dfbc90200e8030000000000002251209b987ec3c169c70d1ed6aef420a4858e3e3ec9d8404358787d4e06ba926a4ae4020103eb4570ae385202d4a48f06bdb14126910b90c07f8e42d7dc5e28a860c085e73712358912c950a9a7d04bb9011ee9f6a16b6127a5aab7415803d48c0225f620f5aa860100ade724357d339cd6ffdd606fdd58d19540757673920512a5c01f6f9591adff3713240032fefacef370a91d268456484a460bbf992dea6872b5a751619f95560c0200e80300000000000022512018d297ade3cfbb7080b65e21af238ac88c15e38734f5f462530c34a225e80ca9000265cca13271cafd0b90c440e722a1937b7c4faf4ccd7dee0548d152c24ce4b2a8d4f7d410cf052720ffc5ce4668c4371448ffe98b7037f7c42aa943d717fcd67700000000").unwrap(),
1396			round_tx: deserialize_hex("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff000000000001c8af000000000000225120649657f65947abfd83ff629ad8a851c795f419ed4d52a2748d3f868cc3e6c94d00000000").unwrap(),
1397			round1_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000003010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427431fbf80b38758fed9f56d58a7b9f1b64d25c6219591637932f9939678e45d845cd725141a30d0b1ff28e56f7fdc838630b166449b2ad8953538006baf77a1294104038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427436f5c1527ec051272529b96c08fc51711565a71174fa762dfb0353d39f8d93f64215368326730353875cabd078be4badf8b82d1f5a6b0ca05aed5e49cc117e8ae04001027000000000000225120b54cbc99321d02aa2114fabb39dc5e8f346e88296dcec79b1b3c0849caba3d6f881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427437a733b847aed8ceda6460c70a55c80778115c536cfca265559d2f6890be7bd3ab55ef96405f2f136b185893b5d73696f866808229b1506c98b06d992a81b618c0100000374a3ec37cc4ccd29717388e6dc24f2aa366632f1a36a49e73cd7671b231792988588da5d9b08f1767aab3b3a78b6cd27deb937193e153300bcf84b3eeaaef07200000000").unwrap(),
1398			round2_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000003010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743fd28289b9179bcd986f615bfe559c17fe868b34739b9856cffc2b7d5e8f11c952579af829442116177efc4ea7867ab69376215c6eb30c9f2f79e97e1c06cf2f604038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743273b637dbe9a823fe1e04d0360d887914b83a9266e09ae0b893004fc633552038b01b2fa4f4db5dd811a87e42a4718adba852b9af5536109a07dfe81a1067a6b040110270000000000002251202df700706227474e89354e4ad3ac28f007952140fff42a5a0f0675bdff87f6b3881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc4024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743f622e425138d7c93cc09185f8328d8e113af4511c6b11053da214c65afc17f9aedca7bc667eafae74b9e232cbab6031b457c14886a3cf5573343654283b712cd0100020256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecd9ea50d885c3f66d40d27e779648ba8dc730629663f65a3e6f7749b4a35b6dfecc28201002800396ada529b0608572b3b0b6095394574c55f5b3e6911320608d35cc1cc200dab00000000").unwrap(),
1399			arkoor3_user_key: Keypair::from_str("ad12595bdbdab56cb61d1f60ccc46ff96b11c5d6fe06ae7ba03d3a5f4347440f").unwrap(),
1400			arkoor3_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000004010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427431fbf80b38758fed9f56d58a7b9f1b64d25c6219591637932f9939678e45d845cd725141a30d0b1ff28e56f7fdc838630b166449b2ad8953538006baf77a1294104038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427436f5c1527ec051272529b96c08fc51711565a71174fa762dfb0353d39f8d93f64215368326730353875cabd078be4badf8b82d1f5a6b0ca05aed5e49cc117e8ae040110270000000000002251202df700706227474e89354e4ad3ac28f007952140fff42a5a0f0675bdff87f6b3881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc4024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743b6c685b3ee849d2400f96fb5968ddd472e18c54b8910a8f952d5b127494cbd51556346bbb0a44d7f5f5150310535b4b2f2aa4412d7b88af929dae5a9652f7643010002020256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecd9ea50d885c3f66d40d27e779648ba8dc730629663f65a3e6f7749b4a35b6dfecc282010028003e6785232b2247cb57de941a898729170b3a50e1bed843700f5dfaeb90ee8e531aa4e35b2bf72ceb2de6bfd2d859be86bdbe5f75fbc90a17ab9c2babb40fc83501000002ed1334f116cea9128e1f59f1d5a431cb4f338f0998e2b32f654c310bf7831f97ff70cc93c752b2cdfa42fef244be8915b087a7e13d9cf6cb24b6443b6a8b87dc00000000").unwrap(),
1401		};
1402	}
1403
1404	#[test]
1405	fn test_generate_vtxo_vectors() {
1406		let g = generate_vtxo_vectors();
1407		// the generation code prints its inner values
1408
1409		let v = &*VTXO_VECTORS;
1410		println!("\n\nstatic:");
1411		println!("  anchor_tx: {}", serialize_hex(&v.anchor_tx));
1412		println!("  board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1413		println!("  arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1414		println!("  arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1415		println!("  round_tx: {}", serialize_hex(&v.round_tx));
1416		println!("  round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1417		println!("  round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1418		println!("  arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1419
1420		assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1421		assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1422		assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1423		assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1424		assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1425		assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1426		assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1427		assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1428
1429		// this passes because the Eq is based on id which doesn't compare signatures
1430		assert_eq!(g, *v);
1431	}
1432
1433	#[test]
1434	fn exit_depth() {
1435		let vtxos = &*VTXO_VECTORS;
1436		// board
1437		assert_eq!(vtxos.board_vtxo.exit_depth(), 1 /* cosign */);
1438
1439		// round
1440		assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 /* cosign */);
1441
1442		// arkoor
1443		assert_eq!(vtxos.arkoor_htlc_out_vtxo.exit_depth(), 1 /* cosign */ + 1 /* arkoor */);
1444		assert_eq!(vtxos.arkoor2_vtxo.exit_depth(), 1 /* cosign */ + 2 /* arkoor */);
1445		assert_eq!(vtxos.arkoor3_vtxo.exit_depth(), 3 /* cosign */ + 1 /* arkoor */);
1446	}
1447}