Skip to main content

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