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