ark/vtxo/
mod.rs

1//! Representations of VTXOs in an Ark.
2
3
4// # The internal representation of VTXOs.
5//
6// The [Vtxo] type is a struct that exposes a public API through methods, but
7// we have deliberately decided to hide all its internal representation from
8// the user.
9//
10// ## Objectives
11//
12// The objectives of the internal structure of [Vtxo] are the following:
13// - have a stable encoding and decoding through [ProtocolEncoding]
14// - enable constructing all exit transactions required to perform a
15//   unilateral exit for the VTXO
16// - enable a user to validate that the exit transaction chain is safe,
17//   meaning that there are no unexpected spend paths that could break
18//   the exit. this means that
19//   - all transitions between transactions (i.e. where a child spends its
20//     parent) have only known spend paths and no malicious additional ones
21//   - all outputs of all exit transactions are standard, so they can be
22//     relayed on the public relay network
23//   - the necessary fee anchors are in place to allow the user to fund his
24//     exit
25//
26// ## Internal structure
27//
28// Each [Vtxo] has what we call a "chain anchor" and a "genesis". The chain
29// anchor is the transaction that is to be confirmed on-chain to anchor the
30// VTXO's existence into the chain. The genesis represents the data required
31// to "conceive" the [Vtxo]'s UTXO on the chain, connected to the chain anchor.
32// Conceptually, the genesis data consists of two main things:
33// - the output policy data and input witness data for each transition.
34//   This ensures we can validate the policy used for the transition and we have
35//   the necessary data to satisfy it.
36// - the additional output data to reconstruct the transactions in full
37//   (since our own transition is just one of the outputs)
38//
39// Since an exit of N transactions has N times the tx construction data,
40// but N+1 times the transition policy data, we decided to structure the
41// genesis series as follows:
42//
43// The genesis consists of "genesis items", which contain:
44// - the output policy of the previous output (of the parent)
45// - the witness to satisfy this policy
46// - the additional output data to construct an exit tx
47//
48// This means that
49// - there are an equal number of genesis items as there are exit transactions
50// - the first item will hold the output policy of the chain anchor
51// - to construct the output of the exit tx at a certain level, we get the
52//   output policy from the next genesis item
53// - the last tx's output policy is not held in the genesis, but it is held as
54//   the VTXO's own output policy
55
56pub mod policy;
57pub(crate) mod genesis;
58mod validation;
59
60pub use self::validation::VtxoValidationError;
61pub use self::policy::{Policy, VtxoPolicy, VtxoPolicyKind, ServerVtxoPolicy};
62pub(crate) use self::genesis::{GenesisItem, GenesisTransition};
63
64pub use self::policy::{
65	PubkeyVtxoPolicy, CheckpointVtxoPolicy, ExpiryVtxoPolicy, HarkLeafVtxoPolicy,
66	ServerHtlcRecvVtxoPolicy, ServerHtlcSendVtxoPolicy
67};
68pub use self::policy::clause::{
69	VtxoClause, DelayedSignClause, DelayedTimelockSignClause, HashDelaySignClause,
70	TapScriptClause,
71};
72
73/// Type alias for a server-internal VTXO that may have policies without user pubkeys.
74pub type ServerVtxo<G = Bare> = Vtxo<G, ServerVtxoPolicy>;
75
76use std::borrow::Cow;
77use std::iter::FusedIterator;
78use std::{fmt, io};
79use std::str::FromStr;
80
81use bitcoin::{
82	taproot, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness
83};
84use bitcoin::absolute::LockTime;
85use bitcoin::hashes::{sha256, Hash};
86use bitcoin::secp256k1::{schnorr, PublicKey, XOnlyPublicKey};
87use bitcoin::taproot::TapTweakHash;
88
89use bitcoin_ext::{fee, BlockDelta, BlockHeight, TxOutExt};
90
91use crate::vtxo::policy::HarkForfeitVtxoPolicy;
92use crate::scripts;
93use crate::encode::{
94	LengthPrefixedVector, OversizedVectorError, ProtocolDecodingError, ProtocolEncoding, ReadExt,
95	WriteExt,
96};
97use crate::lightning::PaymentHash;
98use crate::tree::signed::{UnlockHash, UnlockPreimage};
99
100/// The total signed tx weight of a exit tx.
101pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
102
103/// The current version of the vtxo encoding.
104const VTXO_ENCODING_VERSION: u16 = 2;
105/// The version before a fee amount was added to each genesis item.
106const VTXO_NO_FEE_AMOUNT_VERSION: u16 = 1;
107
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
110#[error("failed to parse vtxo id, must be 36 bytes")]
111pub struct VtxoIdParseError;
112
113#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
114pub struct VtxoId([u8; 36]);
115
116impl VtxoId {
117	/// Size in bytes of an encoded [VtxoId].
118	pub const ENCODE_SIZE: usize = 36;
119
120	pub fn from_slice(b: &[u8]) -> Result<VtxoId, VtxoIdParseError> {
121		if b.len() == 36 {
122			let mut ret = [0u8; 36];
123			ret[..].copy_from_slice(&b[0..36]);
124			Ok(Self(ret))
125		} else {
126			Err(VtxoIdParseError)
127		}
128	}
129
130	pub fn utxo(self) -> OutPoint {
131		let vout = [self.0[32], self.0[33], self.0[34], self.0[35]];
132		OutPoint::new(Txid::from_slice(&self.0[0..32]).unwrap(), u32::from_le_bytes(vout))
133	}
134
135	pub fn to_bytes(self) -> [u8; 36] {
136		self.0
137	}
138}
139
140impl From<OutPoint> for VtxoId {
141	fn from(p: OutPoint) -> VtxoId {
142		let mut ret = [0u8; 36];
143		ret[0..32].copy_from_slice(&p.txid[..]);
144		ret[32..].copy_from_slice(&p.vout.to_le_bytes());
145		VtxoId(ret)
146	}
147}
148
149impl AsRef<[u8]> for VtxoId {
150	fn as_ref(&self) -> &[u8] {
151		&self.0
152	}
153}
154
155impl fmt::Display for VtxoId {
156	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157		fmt::Display::fmt(&self.utxo(), f)
158	}
159}
160
161impl fmt::Debug for VtxoId {
162	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163		fmt::Display::fmt(self, f)
164	}
165}
166
167impl FromStr for VtxoId {
168	type Err = VtxoIdParseError;
169	fn from_str(s: &str) -> Result<Self, Self::Err> {
170		Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
171	}
172}
173
174impl serde::Serialize for VtxoId {
175	fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
176		if s.is_human_readable() {
177			s.collect_str(self)
178		} else {
179			s.serialize_bytes(self.as_ref())
180		}
181	}
182}
183
184impl<'de> serde::Deserialize<'de> for VtxoId {
185	fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
186		struct Visitor;
187		impl<'de> serde::de::Visitor<'de> for Visitor {
188			type Value = VtxoId;
189			fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190				write!(f, "a VtxoId")
191			}
192			fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
193				VtxoId::from_slice(v).map_err(serde::de::Error::custom)
194			}
195			fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
196				VtxoId::from_str(v).map_err(serde::de::Error::custom)
197			}
198		}
199		if d.is_human_readable() {
200			d.deserialize_str(Visitor)
201		} else {
202			d.deserialize_bytes(Visitor)
203		}
204	}
205}
206
207impl ProtocolEncoding for VtxoId {
208	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
209		w.emit_slice(&self.0)
210	}
211	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
212		let array: [u8; 36] = r.read_byte_array()
213			.map_err(|_| ProtocolDecodingError::invalid("invalid vtxo id. Expected 36 bytes"))?;
214
215		Ok(VtxoId(array))
216	}
217}
218
219/// Returns the clause to unilaterally spend a VTXO
220pub(crate) fn exit_clause(
221	user_pubkey: PublicKey,
222	exit_delta: BlockDelta,
223) -> ScriptBuf {
224	scripts::delayed_sign(exit_delta, user_pubkey.x_only_public_key().0)
225}
226
227/// Create an exit tx.
228///
229/// When the `signature` argument is provided,
230/// it will be placed in the input witness.
231pub fn create_exit_tx(
232	prevout: OutPoint,
233	output: TxOut,
234	signature: Option<&schnorr::Signature>,
235	fee: Amount,
236) -> Transaction {
237	Transaction {
238		version: bitcoin::transaction::Version(3),
239		lock_time: LockTime::ZERO,
240		input: vec![TxIn {
241			previous_output: prevout,
242			script_sig: ScriptBuf::new(),
243			sequence: Sequence::ZERO,
244			witness: {
245				let mut ret = Witness::new();
246				if let Some(sig) = signature {
247					ret.push(&sig[..]);
248				}
249				ret
250			},
251		}],
252		output: vec![output, fee::fee_anchor_with_amount(fee)],
253	}
254}
255
256/// Enum type used to represent a preimage<>hash relationship
257/// for which the preimage might be known but the hash always
258/// should be known.
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub(crate) enum MaybePreimage {
261	Preimage([u8; 32]),
262	Hash(sha256::Hash),
263}
264
265impl MaybePreimage {
266	/// Get the hash
267	pub fn hash(&self) -> sha256::Hash {
268		match self {
269			Self::Preimage(p) => sha256::Hash::hash(p),
270			Self::Hash(h) => *h,
271		}
272	}
273}
274
275/// Type of the items yielded by [VtxoTxIter], the iterator returned by
276/// [Vtxo::transactions].
277#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
278pub struct VtxoTxIterItem {
279	/// The actual transaction.
280	pub tx: Transaction,
281	/// The index of the relevant output of this tx
282	pub output_idx: usize,
283}
284
285/// Iterator returned by [Vtxo::transactions].
286pub struct VtxoTxIter<'a, P: Policy = VtxoPolicy> {
287	vtxo: &'a Vtxo<Full, P>,
288
289	prev: OutPoint,
290	genesis_idx: usize,
291	current_amount: Amount,
292}
293
294impl<'a, P: Policy> VtxoTxIter<'a, P> {
295	fn new(vtxo: &'a Vtxo<Full, P>) -> VtxoTxIter<'a, P> {
296		// Add all the amounts that go into the other outputs.
297		let onchain_amount = vtxo.chain_anchor_amount()
298			.expect("This should only fail if the VTXO is invalid.");
299		VtxoTxIter {
300			prev: vtxo.anchor_point,
301			vtxo: vtxo,
302			genesis_idx: 0,
303			current_amount: onchain_amount,
304		}
305	}
306}
307
308impl<'a, P: Policy> Iterator for VtxoTxIter<'a, P> {
309	type Item = VtxoTxIterItem;
310
311	fn next(&mut self) -> Option<Self::Item> {
312		let item = self.vtxo.genesis.items.get(self.genesis_idx)?;
313		let next_amount = self.current_amount.checked_sub(
314			item.other_output_sum().expect("we calculated this amount beforehand")
315		).expect("we calculated this amount beforehand");
316
317		let next_output = if let Some(item) = self.vtxo.genesis.items.get(self.genesis_idx + 1) {
318			item.transition.input_txout(
319				next_amount,
320				self.vtxo.server_pubkey,
321				self.vtxo.expiry_height,
322				self.vtxo.exit_delta,
323			)
324		} else {
325			// when we reach the end of the chain, we take the eventual output of the vtxo
326			self.vtxo.policy.txout(
327				self.vtxo.amount,
328				self.vtxo.server_pubkey,
329				self.vtxo.exit_delta,
330				self.vtxo.expiry_height,
331			)
332		};
333
334		let tx = item.tx(self.prev, next_output, self.vtxo.server_pubkey, self.vtxo.expiry_height);
335		self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
336		self.genesis_idx += 1;
337		self.current_amount = next_amount;
338		let output_idx = item.output_idx as usize;
339		Some(VtxoTxIterItem { tx, output_idx })
340	}
341
342	fn size_hint(&self) -> (usize, Option<usize>) {
343		let len = self.vtxo.genesis.items.len().saturating_sub(self.genesis_idx);
344		(len, Some(len))
345	}
346}
347
348impl<'a, P: Policy> ExactSizeIterator for VtxoTxIter<'a, P> {}
349impl<'a, P: Policy> FusedIterator for VtxoTxIter<'a, P> {}
350
351/// Representing "bare" VTXOs that are just output details without genesis
352#[derive(Debug, Clone)]
353pub struct Bare;
354
355/// Representing "full" VTXOs that contain the full genesis
356#[derive(Debug, Clone)]
357pub struct Full {
358	pub(crate) items: Vec<genesis::GenesisItem>,
359}
360
361/// Represents a VTXO in the Ark.
362///
363/// The correctness of the return values of methods on this type is conditional
364/// on the VTXO being valid. For invalid VTXOs, the methods should never panic,
365/// but can return incorrect values.
366/// It is advised to always validate a VTXO upon receipt using [Vtxo::validate].
367///
368/// Be mindful of calling [Clone] on a [Vtxo], as they can be of
369/// non-negligible size. It is advised to use references where possible
370/// or use an [std::rc::Rc] or [std::sync::Arc] if needed.
371///
372/// Implementations of [PartialEq], [Eq], [PartialOrd], [Ord] and [Hash] are
373/// proxied to the implementation on [Vtxo::id].
374#[derive(Debug, Clone)]
375pub struct Vtxo<G = Full, P = VtxoPolicy> {
376	pub(crate) policy: P,
377	pub(crate) amount: Amount,
378	pub(crate) expiry_height: BlockHeight,
379
380	pub(crate) server_pubkey: PublicKey,
381	pub(crate) exit_delta: BlockDelta,
382
383	pub(crate) anchor_point: OutPoint,
384	/// The genesis is generic and can be either present or not
385	pub(crate) genesis: G,
386
387	/// The resulting actual "point" of the VTXO. I.e. the output of the last
388	/// exit tx of this VTXO.
389	///
390	/// We keep this for two reasons:
391	/// - the ID is based on this, so it should be cheaply accessible
392	/// - it forms as a good checksum for all the internal genesis data
393	pub(crate) point: OutPoint,
394}
395
396impl<G, P: Policy> Vtxo<G, P> {
397	/// Get the identifier for this [Vtxo].
398	///
399	/// This is the same as [Vtxo::point] but encoded as a byte array.
400	pub fn id(&self) -> VtxoId {
401		self.point.into()
402	}
403
404	/// The outpoint from which to build forfeit or arkoor txs.
405	///
406	/// This can be an on-chain utxo or an off-chain vtxo.
407	pub fn point(&self) -> OutPoint {
408		self.point
409	}
410
411	/// The amount of the [Vtxo].
412	pub fn amount(&self) -> Amount {
413		self.amount
414	}
415
416	/// The UTXO that should be confirmed for this [Vtxo] to be valid.
417	///
418	/// It is the very root of the VTXO.
419	pub fn chain_anchor(&self) -> OutPoint {
420		self.anchor_point
421	}
422
423	/// The output policy of this VTXO.
424	pub fn policy(&self) -> &P {
425		&self.policy
426	}
427
428	/// The output policy type of this VTXO.
429	pub fn policy_type(&self) -> VtxoPolicyKind {
430		self.policy.policy_type()
431	}
432
433	/// The expiry height of the [Vtxo].
434	pub fn expiry_height(&self) -> BlockHeight {
435		self.expiry_height
436	}
437
438	/// The server pubkey used in arkoor transitions.
439	pub fn server_pubkey(&self) -> PublicKey {
440		self.server_pubkey
441	}
442
443	/// The relative timelock block delta used for exits.
444	pub fn exit_delta(&self) -> BlockDelta {
445		self.exit_delta
446	}
447
448	/// The taproot spend info for the output of this [Vtxo].
449	pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
450		self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
451	}
452
453	/// The scriptPubkey of the output of this [Vtxo].
454	pub fn output_script_pubkey(&self) -> ScriptBuf {
455		self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
456	}
457
458	/// The transaction output (eventual UTXO) of this [Vtxo].
459	pub fn txout(&self) -> TxOut {
460		self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
461	}
462
463	/// Convert to a bare VTXO, [Vtxo<Bare>]
464	pub fn to_bare(&self) -> Vtxo<Bare, P> {
465		Vtxo {
466			point: self.point,
467			policy: self.policy.clone(),
468			amount: self.amount,
469			expiry_height: self.expiry_height,
470			server_pubkey: self.server_pubkey,
471			exit_delta: self.exit_delta,
472			anchor_point: self.anchor_point,
473			genesis: Bare,
474		}
475	}
476
477	/// Convert into a bare VTXO, [Vtxo<Bare>]
478	pub fn into_bare(self) -> Vtxo<Bare, P> {
479		Vtxo {
480			point: self.point,
481			policy: self.policy,
482			amount: self.amount,
483			expiry_height: self.expiry_height,
484			server_pubkey: self.server_pubkey,
485			exit_delta: self.exit_delta,
486			anchor_point: self.anchor_point,
487			genesis: Bare,
488		}
489	}
490}
491
492impl<P: Policy> Vtxo<Full, P> {
493	/// Returns the total exit depth (including OOR depth) of the vtxo.
494	pub fn exit_depth(&self) -> u16 {
495		self.genesis.items.len() as u16
496	}
497
498	/// Iterate over all oor transitions in this VTXO
499	///
500	/// The outer `Vec` cointains one element for each transition.
501	/// The inner `Vec` contains all pubkeys within that transition.
502	///
503	/// This does not include the current arkoor pubkey, for that use
504	/// [Vtxo::arkoor_pubkey].
505	pub fn past_arkoor_pubkeys(&self) -> Vec<Vec<PublicKey>> {
506		self.genesis.items.iter().filter_map(|g| {
507			match &g.transition {
508				// NB in principle, a genesis item's transition MUST have
509				// an arkoor pubkey, otherwise the vtxo is invalid
510				GenesisTransition::Arkoor(inner) => Some(inner.client_cosigners().collect()),
511				_ => None,
512			}
513		}).collect()
514	}
515
516	/// Whether all transaction witnesses are present
517	///
518	/// It is possible to represent unsigned or otherwise unfinished VTXOs,
519	/// for which this method will return false.
520	pub fn has_all_witnesses(&self) -> bool {
521		self.genesis.items.iter().all(|g| g.transition.has_all_witnesses())
522	}
523
524	/// Check if this VTXO is standard for relay purposes
525	///
526	/// A VTXO is standard if:
527	/// - Its own output is standard
528	/// - all sibling outputs in the exit path are standard
529	/// - each part of the exit path should have a P2A output
530	pub fn is_standard(&self) -> bool {
531		self.txout().is_standard() && self.genesis.items.iter()
532			.all(|i| i.other_outputs.iter().all(|o| o.is_standard()))
533	}
534
535	/// Returns the "hArk" unlock hash if this is a hArk leaf VTXO
536	pub fn unlock_hash(&self) -> Option<UnlockHash> {
537		match self.genesis.items.last()?.transition {
538			GenesisTransition::HashLockedCosigned(ref inner) => Some(inner.unlock.hash()),
539			_ => None,
540		}
541	}
542
543	/// Provide the leaf signature for an unfinalized hArk VTXO
544	///
545	/// Returns true if this VTXO was an unfinalized hArk VTXO.
546	pub fn provide_unlock_signature(&mut self, signature: schnorr::Signature) -> bool {
547		match self.genesis.items.last_mut().map(|g| &mut g.transition) {
548			Some(GenesisTransition::HashLockedCosigned(inner)) => {
549				inner.signature.replace(signature);
550				true
551			},
552			_ => false,
553		}
554	}
555
556	/// Provide the unlock preimage for an unfinalized hArk VTXO
557	///
558	/// Returns true if this VTXO was an unfinalized hArk VTXO and the preimage matched.
559	pub fn provide_unlock_preimage(&mut self, preimage: UnlockPreimage) -> bool {
560		match self.genesis.items.last_mut().map(|g| &mut g.transition) {
561			Some(GenesisTransition::HashLockedCosigned(ref mut inner)) => {
562				if inner.unlock.hash() == UnlockHash::hash(&preimage) {
563					inner.unlock = MaybePreimage::Preimage(preimage);
564					true
565				} else {
566					false
567				}
568			},
569			_ => false,
570		}
571	}
572
573	/// Iterator that constructs all the exit txs for this [Vtxo].
574	pub fn transactions(&self) -> VtxoTxIter<'_, P> {
575		VtxoTxIter::new(self)
576	}
577
578	/// Fully validate this VTXO and its entire transaction chain.
579	///
580	/// The `chain_anchor_tx` must be the tx with txid matching
581	/// [Vtxo::chain_anchor].
582	pub fn validate(
583		&self,
584		chain_anchor_tx: &Transaction,
585	) -> Result<(), VtxoValidationError> {
586		self::validation::validate(self, chain_anchor_tx)
587	}
588
589	/// Validate VTXO structure without checking signatures.
590	pub fn validate_unsigned(
591		&self,
592		chain_anchor_tx: &Transaction,
593	) -> Result<(), VtxoValidationError> {
594		self::validation::validate_unsigned(self, chain_anchor_tx)
595	}
596
597	/// Calculates the onchain amount for the [Vtxo].
598	///
599	/// Returns `None` if any overflow occurs. This should be impossible for any VTXO that is valid.
600	pub(crate) fn chain_anchor_amount(&self) -> Option<Amount> {
601		self.amount.checked_add(self.genesis.items.iter().try_fold(Amount::ZERO, |sum, i| {
602			i.other_output_sum().and_then(|amt| sum.checked_add(amt))
603		})?)
604	}
605}
606
607impl<G> Vtxo<G, VtxoPolicy> {
608	/// Returns the user pubkey associated with this [Vtxo].
609	pub fn user_pubkey(&self) -> PublicKey {
610		self.policy.user_pubkey()
611	}
612
613	/// The public key used to cosign arkoor txs spending this [Vtxo].
614	/// This will return [None] if [VtxoPolicy::is_arkoor_compatible] returns false
615	/// for this VTXO's policy.
616	pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
617		self.policy.arkoor_pubkey()
618	}
619}
620
621impl Vtxo<Full, VtxoPolicy> {
622	/// Shortcut to fully finalize a hark leaf using both keys
623	#[cfg(any(test, feature = "test-util"))]
624	pub fn finalize_hark_leaf(
625		&mut self,
626		user_key: &bitcoin::secp256k1::Keypair,
627		server_key: &bitcoin::secp256k1::Keypair,
628		chain_anchor: &Transaction,
629		unlock_preimage: UnlockPreimage,
630	) {
631		use crate::tree::signed::{LeafVtxoCosignContext, LeafVtxoCosignResponse};
632
633		// first sign and provide the signature
634		let (ctx, req) = LeafVtxoCosignContext::new(self, chain_anchor, user_key);
635		let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key);
636		assert!(ctx.finalize(self, cosign));
637		// then provide preimage
638		assert!(self.provide_unlock_preimage(unlock_preimage));
639	}
640}
641
642impl<G> Vtxo<G, ServerVtxoPolicy> {
643	/// Try to convert into a user [Vtxo]
644	///
645	/// Returns the original value on failure.
646	pub fn try_into_user_vtxo(self) -> Result<Vtxo<G, VtxoPolicy>, ServerVtxo<G>> {
647		if let Some(p) = self.policy.clone().into_user_policy() {
648			Ok(Vtxo {
649				policy: p,
650				amount: self.amount,
651				expiry_height: self.expiry_height,
652				server_pubkey: self.server_pubkey,
653				exit_delta: self.exit_delta,
654				anchor_point: self.anchor_point,
655				genesis: self.genesis,
656				point: self.point,
657			})
658		} else {
659			Err(self)
660		}
661	}
662}
663
664impl<G, P: Policy> PartialEq for Vtxo<G, P> {
665	fn eq(&self, other: &Self) -> bool {
666		PartialEq::eq(&self.id(), &other.id())
667	}
668}
669
670impl<G, P: Policy> Eq for Vtxo<G, P> {}
671
672impl<G, P: Policy> PartialOrd for Vtxo<G, P> {
673	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
674		PartialOrd::partial_cmp(&self.id(), &other.id())
675	}
676}
677
678impl<G, P: Policy> Ord for Vtxo<G, P> {
679	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
680		Ord::cmp(&self.id(), &other.id())
681	}
682}
683
684impl<G, P: Policy> std::hash::Hash for Vtxo<G, P> {
685	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
686		std::hash::Hash::hash(&self.id(), state)
687	}
688}
689
690impl<G, P: Policy> AsRef<Vtxo<G, P>> for Vtxo<G, P> {
691	fn as_ref(&self) -> &Vtxo<G, P> {
692	    self
693	}
694}
695
696impl<G> From<Vtxo<G>> for ServerVtxo<G> {
697	fn from(vtxo: Vtxo<G>) -> ServerVtxo<G> {
698		ServerVtxo {
699			policy: vtxo.policy.into(),
700			amount: vtxo.amount,
701			expiry_height: vtxo.expiry_height,
702			server_pubkey: vtxo.server_pubkey,
703			exit_delta: vtxo.exit_delta,
704			anchor_point: vtxo.anchor_point,
705			genesis: vtxo.genesis,
706			point: vtxo.point,
707		}
708	}
709}
710
711/// Implemented on anything that is kinda a [Vtxo]
712pub trait VtxoRef<P: Policy = VtxoPolicy> {
713	/// The [VtxoId] of the VTXO
714	fn vtxo_id(&self) -> VtxoId;
715
716	/// If the bare [Vtxo] can be provided, provides it by reference
717	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { None }
718
719	/// If the bare [Vtxo] can be provided, provides it by reference
720	fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { None }
721
722	/// If the bare [Vtxo] can be provided, provides it by value, either directly or via cloning
723	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> where Self: Sized;
724}
725
726impl<P: Policy> VtxoRef<P> for VtxoId {
727	fn vtxo_id(&self) -> VtxoId { *self }
728	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
729}
730
731impl<'a, P: Policy> VtxoRef<P> for &'a VtxoId {
732	fn vtxo_id(&self) -> VtxoId { **self }
733	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
734}
735
736impl<P: Policy> VtxoRef<P> for Vtxo<Bare, P> {
737	fn vtxo_id(&self) -> VtxoId { self.id() }
738	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(self)) }
739	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
740}
741
742impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Bare, P> {
743	fn vtxo_id(&self) -> VtxoId { self.id() }
744	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(*self)) }
745	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
746}
747
748impl<P: Policy> VtxoRef<P> for Vtxo<Full, P> {
749	fn vtxo_id(&self) -> VtxoId { self.id() }
750	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
751	fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(self) }
752	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self) }
753}
754
755impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Full, P> {
756	fn vtxo_id(&self) -> VtxoId { self.id() }
757	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
758	fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(*self) }
759	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self.clone()) }
760}
761
762/// The byte used to encode the [VtxoPolicy::Pubkey] output type.
763const VTXO_POLICY_PUBKEY: u8 = 0x00;
764
765/// The byte used to encode the [VtxoPolicy::ServerHtlcSend] output type.
766const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
767
768/// The byte used to encode the [VtxoPolicy::ServerHtlcRecv] output type.
769const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
770
771/// The byte used to encode the [ServerVtxoPolicy::Checkpoint] output type.
772const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
773
774/// The byte used to encode the [ServerVtxoPolicy::Expiry] output type.
775const VTXO_POLICY_EXPIRY: u8 = 0x04;
776
777/// The byte used to encode the [ServerVtxoPolicy::HarkLeaf] output type.
778const VTXO_POLICY_HARK_LEAF: u8 = 0x05;
779
780/// The byte used to encode the [ServerVtxoPolicy::HarkForfeit] output type.
781const VTXO_POLICY_HARK_FORFEIT: u8 = 0x06;
782
783impl ProtocolEncoding for VtxoPolicy {
784	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
785		match self {
786			Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
787				w.emit_u8(VTXO_POLICY_PUBKEY)?;
788				user_pubkey.encode(w)?;
789			},
790			Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
791				w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
792				user_pubkey.encode(w)?;
793				payment_hash.to_sha256_hash().encode(w)?;
794				w.emit_u32(*htlc_expiry)?;
795			},
796			Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
797				user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
798			}) => {
799				w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
800				user_pubkey.encode(w)?;
801				payment_hash.to_sha256_hash().encode(w)?;
802				w.emit_u32(*htlc_expiry)?;
803				w.emit_u16(*htlc_expiry_delta)?;
804			},
805		}
806		Ok(())
807	}
808
809	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
810		let type_byte = r.read_u8()?;
811		decode_vtxo_policy(type_byte, r)
812	}
813}
814
815/// Decode a [VtxoPolicy] with the given type byte
816///
817/// We have this function so it can be reused in [VtxoPolicy] and [ServerVtxoPolicy].
818fn decode_vtxo_policy<R: io::Read + ?Sized>(
819	type_byte: u8,
820	r: &mut R,
821) -> Result<VtxoPolicy, ProtocolDecodingError> {
822	match type_byte {
823		VTXO_POLICY_PUBKEY => {
824			let user_pubkey = PublicKey::decode(r)?;
825			Ok(VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
826		},
827		VTXO_POLICY_SERVER_HTLC_SEND => {
828			let user_pubkey = PublicKey::decode(r)?;
829			let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
830			let htlc_expiry = r.read_u32()?;
831			Ok(VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
832		},
833		VTXO_POLICY_SERVER_HTLC_RECV => {
834			let user_pubkey = PublicKey::decode(r)?;
835			let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
836			let htlc_expiry = r.read_u32()?;
837			let htlc_expiry_delta = r.read_u16()?;
838			Ok(VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
839		},
840
841		// IMPORTANT:
842		// When adding a new user vtxo policy variant, don't forget
843		// to also add it to the ServerVtxoPolicy decode match arm.
844
845		v => Err(ProtocolDecodingError::invalid(format_args!(
846			"invalid VtxoPolicy type byte: {v:#x}",
847		))),
848	}
849}
850
851impl ProtocolEncoding for ServerVtxoPolicy {
852	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
853		match self {
854			Self::User(p) => p.encode(w)?,
855			Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
856				w.emit_u8(VTXO_POLICY_CHECKPOINT)?;
857				user_pubkey.encode(w)?;
858			},
859			Self::Expiry(ExpiryVtxoPolicy { internal_key }) => {
860				w.emit_u8(VTXO_POLICY_EXPIRY)?;
861				internal_key.encode(w)?;
862			},
863			Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }) => {
864				w.emit_u8(VTXO_POLICY_HARK_LEAF)?;
865				user_pubkey.encode(w)?;
866				unlock_hash.encode(w)?;
867			},
868			Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }) => {
869				w.emit_u8(VTXO_POLICY_HARK_FORFEIT)?;
870				user_pubkey.encode(w)?;
871				unlock_hash.encode(w)?;
872			},
873		}
874		Ok(())
875	}
876
877	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
878		let type_byte = r.read_u8()?;
879		match type_byte {
880			VTXO_POLICY_PUBKEY | VTXO_POLICY_SERVER_HTLC_SEND | VTXO_POLICY_SERVER_HTLC_RECV => {
881				Ok(Self::User(decode_vtxo_policy(type_byte, r)?))
882			},
883			VTXO_POLICY_CHECKPOINT => {
884				let user_pubkey = PublicKey::decode(r)?;
885				Ok(Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }))
886			},
887			VTXO_POLICY_EXPIRY => {
888				let internal_key = XOnlyPublicKey::decode(r)?;
889				Ok(Self::Expiry(ExpiryVtxoPolicy { internal_key }))
890			},
891			VTXO_POLICY_HARK_LEAF => {
892				let user_pubkey = PublicKey::decode(r)?;
893				let unlock_hash = sha256::Hash::decode(r)?;
894				Ok(Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }))
895			},
896			VTXO_POLICY_HARK_FORFEIT => {
897				let user_pubkey = PublicKey::decode(r)?;
898				let unlock_hash = sha256::Hash::decode(r)?;
899				Ok(Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }))
900			},
901			v => Err(ProtocolDecodingError::invalid(format_args!(
902				"invalid ServerVtxoPolicy type byte: {v:#x}",
903			))),
904		}
905	}
906}
907
908/// The byte used to encode the [GenesisTransition::Cosigned] gen transition type.
909const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
910
911/// The byte used to encode the [GenesisTransition::Arkoor] gen transition type.
912const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
913
914/// The byte used to encode the [GenesisTransition::HashLockedCosigned] gen transition type.
915const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
916
917impl ProtocolEncoding for GenesisTransition {
918	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
919		match self {
920			Self::Cosigned(t) => {
921				w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
922				LengthPrefixedVector::new(&t.pubkeys).encode(w)?;
923				t.signature.encode(w)?;
924			},
925			Self::HashLockedCosigned(t) => {
926				w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
927				t.user_pubkey.encode(w)?;
928				t.signature.encode(w)?;
929				match t.unlock {
930					MaybePreimage::Preimage(p) => {
931						w.emit_u8(0)?;
932						w.emit_slice(&p[..])?;
933					},
934					MaybePreimage::Hash(h) => {
935						w.emit_u8(1)?;
936						w.emit_slice(&h[..])?;
937					},
938				}
939			},
940			Self::Arkoor(t) => {
941				w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
942				LengthPrefixedVector::new(&t.client_cosigners).encode(w)?;
943				t.tap_tweak.encode(w)?;
944				t.signature.encode(w)?;
945			},
946		}
947		Ok(())
948	}
949
950	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
951		match r.read_u8()? {
952			GENESIS_TRANSITION_TYPE_COSIGNED => {
953				let pubkeys = LengthPrefixedVector::decode(r)?.into_inner();
954				let signature = Option::<schnorr::Signature>::decode(r)?;
955				Ok(Self::new_cosigned(pubkeys, signature))
956			},
957			GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
958				let user_pubkey = PublicKey::decode(r)?;
959				let signature = Option::<schnorr::Signature>::decode(r)?;
960				let unlock = match r.read_u8()? {
961					0 => MaybePreimage::Preimage(r.read_byte_array()?),
962					1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
963					v => return Err(ProtocolDecodingError::invalid(format_args!(
964						"invalid MaybePreimage type byte: {v:#x}",
965					))),
966				};
967				Ok(Self::new_hash_locked_cosigned(user_pubkey, signature, unlock))
968			},
969			GENESIS_TRANSITION_TYPE_ARKOOR => {
970				let cosigners = LengthPrefixedVector::decode(r)?.into_inner();
971				let taptweak = TapTweakHash::decode(r)?;
972				let signature = Option::<schnorr::Signature>::decode(r)?;
973				Ok(Self::new_arkoor(cosigners, taptweak, signature))
974			},
975			v => Err(ProtocolDecodingError::invalid(format_args!(
976				"invalid GenesisTransistion type byte: {v:#x}",
977			))),
978		}
979	}
980}
981
982/// A private trait for VTXO sub-objects that have different encodings dependent on
983/// the VTXO encoding version
984trait VtxoVersionedEncoding: Sized {
985	fn encode<W: io::Write + ?Sized>(&self, w: &mut W, version: u16) -> Result<(), io::Error>;
986
987	fn decode<R: io::Read + ?Sized>(
988		r: &mut R,
989		version: u16,
990	) -> Result<Self, ProtocolDecodingError>;
991}
992
993impl VtxoVersionedEncoding for Bare {
994	fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
995		w.emit_compact_size(0u64)?;
996		Ok(())
997	}
998
999	fn decode<R: io::Read + ?Sized>(
1000		r: &mut R,
1001		version: u16,
1002	) -> Result<Self, ProtocolDecodingError> {
1003		// We want to be comaptible with [Full] encoded VTXOs, so we just ignore
1004		// whatever genesis there might be.
1005		let _full = Full::decode(r, version)?;
1006
1007		Ok(Bare)
1008	}
1009}
1010
1011impl VtxoVersionedEncoding for Full {
1012	fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1013		w.emit_compact_size(self.items.len() as u64)?;
1014		for item in &self.items {
1015			item.transition.encode(w)?;
1016			let nb_outputs = item.other_outputs.len() + 1;
1017			w.emit_u8(nb_outputs.try_into()
1018				.map_err(|_| io::Error::other("too many outputs on genesis transaction"))?)?;
1019			w.emit_u8(item.output_idx)?;
1020			for txout in &item.other_outputs {
1021				txout.encode(w)?;
1022			}
1023			w.emit_u64(item.fee_amount.to_sat())?;
1024		}
1025		Ok(())
1026	}
1027
1028	fn decode<R: io::Read + ?Sized>(
1029		r: &mut R,
1030		version: u16,
1031	) -> Result<Self, ProtocolDecodingError> {
1032		let nb_genesis_items = r.read_compact_size()? as usize;
1033		OversizedVectorError::check::<GenesisItem>(nb_genesis_items)?;
1034		let mut genesis = Vec::with_capacity(nb_genesis_items);
1035		for _ in 0..nb_genesis_items {
1036			let transition = GenesisTransition::decode(r)?;
1037			let nb_outputs = r.read_u8()? as usize;
1038			let output_idx = r.read_u8()?;
1039			let nb_other = nb_outputs.checked_sub(1)
1040				.ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1041			let mut other_outputs = Vec::with_capacity(nb_other);
1042			for _ in 0..nb_other {
1043				other_outputs.push(TxOut::decode(r)?);
1044			}
1045			let fee_amount = if version == VTXO_NO_FEE_AMOUNT_VERSION {
1046				// Maintain backwards compatibility by assuming a fee of zero.
1047				Amount::ZERO
1048			} else {
1049				Amount::from_sat(r.read_u64()?)
1050			};
1051			genesis.push(GenesisItem { transition, output_idx, other_outputs, fee_amount });
1052		}
1053		Ok(Full { items: genesis })
1054	}
1055}
1056
1057impl<G: VtxoVersionedEncoding, P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<G, P> {
1058	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1059		let version = VTXO_ENCODING_VERSION;
1060		w.emit_u16(version)?;
1061		w.emit_u64(self.amount.to_sat())?;
1062		w.emit_u32(self.expiry_height)?;
1063		self.server_pubkey.encode(w)?;
1064		w.emit_u16(self.exit_delta)?;
1065		self.anchor_point.encode(w)?;
1066
1067		self.genesis.encode(w, version)?;
1068
1069		self.policy.encode(w)?;
1070		self.point.encode(w)?;
1071		Ok(())
1072	}
1073
1074	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1075		let version = r.read_u16()?;
1076		if version != VTXO_ENCODING_VERSION && version != VTXO_NO_FEE_AMOUNT_VERSION {
1077			return Err(ProtocolDecodingError::invalid(format_args!(
1078				"invalid Vtxo encoding version byte: {version:#x}",
1079			)));
1080		}
1081
1082		let amount = Amount::from_sat(r.read_u64()?);
1083		let expiry_height = r.read_u32()?;
1084		let server_pubkey = PublicKey::decode(r)?;
1085		let exit_delta = r.read_u16()?;
1086		let anchor_point = OutPoint::decode(r)?;
1087
1088		let genesis = VtxoVersionedEncoding::decode(r, version)?;
1089
1090		let policy = P::decode(r)?;
1091		let point = OutPoint::decode(r)?;
1092
1093		Ok(Self {
1094			amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy, point,
1095		})
1096	}
1097}
1098
1099
1100#[cfg(test)]
1101mod test {
1102	use bitcoin::consensus::encode::serialize_hex;
1103	use bitcoin::hex::DisplayHex;
1104
1105	use crate::test_util::encoding_roundtrip;
1106	use crate::test_util::dummy::{DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1107	use crate::test_util::vectors::{
1108		generate_vtxo_vectors, VTXO_VECTORS, VTXO_NO_FEE_AMOUNT_VERSION_HEXES,
1109	};
1110
1111	use super::*;
1112
1113	#[test]
1114	fn test_generate_vtxo_vectors() {
1115		let g = generate_vtxo_vectors();
1116		// the generation code prints its inner values
1117
1118		println!("\n\ngenerated:");
1119		println!("  anchor_tx: {}", serialize_hex(&g.anchor_tx));
1120		println!("  board_vtxo: {}", g.board_vtxo.serialize().as_hex().to_string());
1121		println!("  arkoor_htlc_out_vtxo: {}", g.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1122		println!("  arkoor2_vtxo: {}", g.arkoor2_vtxo.serialize().as_hex().to_string());
1123		println!("  round_tx: {}", serialize_hex(&g.round_tx));
1124		println!("  round1_vtxo: {}", g.round1_vtxo.serialize().as_hex().to_string());
1125		println!("  round2_vtxo: {}", g.round2_vtxo.serialize().as_hex().to_string());
1126		println!("  arkoor3_vtxo: {}", g.arkoor3_vtxo.serialize().as_hex().to_string());
1127
1128
1129		let v = &*VTXO_VECTORS;
1130		println!("\n\nstatic:");
1131		println!("  anchor_tx: {}", serialize_hex(&v.anchor_tx));
1132		println!("  board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1133		println!("  arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1134		println!("  arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1135		println!("  round_tx: {}", serialize_hex(&v.round_tx));
1136		println!("  round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1137		println!("  round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1138		println!("  arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1139
1140		assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1141		assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1142		assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1143		assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1144		assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1145		assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1146		assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1147		assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1148
1149		// this passes because the Eq is based on id which doesn't compare signatures
1150		assert_eq!(g, *v);
1151	}
1152
1153	#[test]
1154	fn test_vtxo_no_fee_amount_version_upgrade() {
1155		let hexes = &*VTXO_NO_FEE_AMOUNT_VERSION_HEXES;
1156		let v = hexes.deserialize_test_vectors();
1157
1158		// Ensure all VTXOs validate correctly.
1159		v.validate_vtxos();
1160
1161		// Ensure each VTXO serializes and is different from the old hex.
1162		let board_hex = v.board_vtxo.serialize().as_hex().to_string();
1163		let arkoor_htlc_out_vtxo_hex = v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string();
1164		let arkoor2_vtxo_hex = v.arkoor2_vtxo.serialize().as_hex().to_string();
1165		let round1_vtxo_hex = v.round1_vtxo.serialize().as_hex().to_string();
1166		let round2_vtxo_hex = v.round2_vtxo.serialize().as_hex().to_string();
1167		let arkoor3_vtxo_hex = v.arkoor3_vtxo.serialize().as_hex().to_string();
1168		assert_ne!(board_hex, hexes.board_vtxo);
1169		assert_ne!(arkoor_htlc_out_vtxo_hex, hexes.arkoor_htlc_out_vtxo);
1170		assert_ne!(arkoor2_vtxo_hex, hexes.arkoor2_vtxo);
1171		assert_ne!(round1_vtxo_hex, hexes.round1_vtxo);
1172		assert_ne!(round2_vtxo_hex, hexes.round2_vtxo);
1173		assert_ne!(arkoor3_vtxo_hex, hexes.arkoor3_vtxo);
1174
1175		// Now verify that deserializing them again results in exactly the same hex. This should be
1176		// the case because the initial hex strings should have been created with a different
1177		// version, then, when we serialize the VTXOs, we should use the newest version. If you
1178		// deserialize a VTXO with the latest version and serialize it, you should get the same
1179		// result.
1180		let board_vtxo = Vtxo::<Full>::deserialize_hex(&board_hex).unwrap();
1181		assert_eq!(board_vtxo.serialize().as_hex().to_string(), board_hex);
1182		let arkoor_htlc_out_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor_htlc_out_vtxo_hex).unwrap();
1183		assert_eq!(arkoor_htlc_out_vtxo.serialize().as_hex().to_string(), arkoor_htlc_out_vtxo_hex);
1184		let arkoor2_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor2_vtxo_hex).unwrap();
1185		assert_eq!(arkoor2_vtxo.serialize().as_hex().to_string(), arkoor2_vtxo_hex);
1186		let round1_vtxo = Vtxo::<Full>::deserialize_hex(&round1_vtxo_hex).unwrap();
1187		assert_eq!(round1_vtxo.serialize().as_hex().to_string(), round1_vtxo_hex);
1188		let round2_vtxo = Vtxo::<Full>::deserialize_hex(&round2_vtxo_hex).unwrap();
1189		assert_eq!(round2_vtxo.serialize().as_hex().to_string(), round2_vtxo_hex);
1190		let arkoor3_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor3_vtxo_hex).unwrap();
1191		assert_eq!(arkoor3_vtxo.serialize().as_hex().to_string(), arkoor3_vtxo_hex);
1192	}
1193
1194	#[test]
1195	fn exit_depth() {
1196		let vtxos = &*VTXO_VECTORS;
1197		// board
1198		assert_eq!(vtxos.board_vtxo.exit_depth(), 1 /* cosign */);
1199
1200		// round
1201		assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 /* cosign */);
1202
1203		// arkoor
1204		assert_eq!(
1205			vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1206			1 /* cosign */ + 1 /* checkpoint*/ + 1 /* arkoor */,
1207		);
1208		assert_eq!(
1209			vtxos.arkoor2_vtxo.exit_depth(),
1210			1 /* cosign */ + 2 /* checkpoint */ + 2 /* arkoor */,
1211		);
1212		assert_eq!(
1213			vtxos.arkoor3_vtxo.exit_depth(),
1214			3 /* cosign */ + 1 /* checkpoint */ + 1 /* arkoor */,
1215		);
1216	}
1217
1218	#[test]
1219	fn test_genesis_length_257() {
1220		let vtxo: Vtxo<Full> = Vtxo {
1221			policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1222			amount: Amount::from_sat(10_000),
1223			expiry_height: 101_010,
1224			server_pubkey: DUMMY_SERVER_KEY.public_key(),
1225			exit_delta: 2016,
1226			anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1227			genesis: Full {
1228				items: (0..257).map(|_| {
1229					GenesisItem {
1230						transition: GenesisTransition::new_cosigned(
1231							vec![DUMMY_USER_KEY.public_key()],
1232							Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1233						),
1234						output_idx: 0,
1235						other_outputs: vec![],
1236						fee_amount: Amount::ZERO,
1237					}
1238				}).collect(),
1239			},
1240			point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1241		};
1242		assert_eq!(vtxo.genesis.items.len(), 257);
1243		encoding_roundtrip(&vtxo);
1244	}
1245
1246	mod genesis_transition_encoding {
1247		use bitcoin::hashes::{sha256, Hash};
1248		use bitcoin::secp256k1::{Keypair, PublicKey};
1249		use bitcoin::taproot::TapTweakHash;
1250		use std::str::FromStr;
1251
1252		use crate::test_util::encoding_roundtrip;
1253		use super::genesis::{
1254			GenesisTransition, CosignedGenesis, HashLockedCosignedGenesis, ArkoorGenesis,
1255		};
1256		use super::MaybePreimage;
1257
1258		fn test_pubkey() -> PublicKey {
1259			Keypair::from_str(
1260				"916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92"
1261			).unwrap().public_key()
1262		}
1263
1264		fn test_signature() -> bitcoin::secp256k1::schnorr::Signature {
1265			"cc8b93e9f6fbc2506bb85ae8bbb530b178daac49704f5ce2e3ab69c266fd5932\
1266			 0b28d028eef212e3b9fdc42cfd2e0760a0359d3ea7d2e9e8cfe2040e3f1b71ea"
1267				.parse().unwrap()
1268		}
1269
1270		#[test]
1271		fn cosigned_with_signature() {
1272			let transition = GenesisTransition::Cosigned(CosignedGenesis {
1273				pubkeys: vec![test_pubkey()],
1274				signature: Some(test_signature()),
1275			});
1276			encoding_roundtrip(&transition);
1277		}
1278
1279		#[test]
1280		fn cosigned_without_signature() {
1281			let transition = GenesisTransition::Cosigned(CosignedGenesis {
1282				pubkeys: vec![test_pubkey()],
1283				signature: None,
1284			});
1285			encoding_roundtrip(&transition);
1286		}
1287
1288		#[test]
1289		fn cosigned_multiple_pubkeys() {
1290			let pk1 = test_pubkey();
1291			let pk2 = Keypair::from_str(
1292				"fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879"
1293			).unwrap().public_key();
1294
1295			let transition = GenesisTransition::Cosigned(CosignedGenesis {
1296				pubkeys: vec![pk1, pk2],
1297				signature: Some(test_signature()),
1298			});
1299			encoding_roundtrip(&transition);
1300		}
1301
1302		#[test]
1303		fn hash_locked_cosigned_with_preimage() {
1304			let preimage = [0x42u8; 32];
1305			let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1306				user_pubkey: test_pubkey(),
1307				signature: Some(test_signature()),
1308				unlock: MaybePreimage::Preimage(preimage),
1309			});
1310			encoding_roundtrip(&transition);
1311		}
1312
1313		#[test]
1314		fn hash_locked_cosigned_with_hash() {
1315			let hash = sha256::Hash::hash(b"test preimage");
1316			let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1317				user_pubkey: test_pubkey(),
1318				signature: Some(test_signature()),
1319				unlock: MaybePreimage::Hash(hash),
1320			});
1321			encoding_roundtrip(&transition);
1322		}
1323
1324		#[test]
1325		fn hash_locked_cosigned_without_signature() {
1326			let preimage = [0x42u8; 32];
1327			let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1328				user_pubkey: test_pubkey(),
1329				signature: None,
1330				unlock: MaybePreimage::Preimage(preimage),
1331			});
1332			encoding_roundtrip(&transition);
1333		}
1334
1335		#[test]
1336		fn arkoor_with_signature() {
1337			let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1338			let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1339				client_cosigners: vec![test_pubkey()],
1340				tap_tweak,
1341				signature: Some(test_signature()),
1342			});
1343			encoding_roundtrip(&transition);
1344		}
1345
1346		#[test]
1347		fn arkoor_without_signature() {
1348			let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1349			let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1350				client_cosigners: vec![test_pubkey()],
1351				tap_tweak,
1352				signature: None,
1353			});
1354			encoding_roundtrip(&transition);
1355		}
1356	}
1357}