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