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