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::{check_block_delta, check_block_height, 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.saturating_add(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 = self.genesis_idx.saturating_add(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	/// Upgrade this bare VTXO to a [Vtxo<Full, P>] by attaching a previously
518	/// stripped or decoded genesis chain.
519	///
520	/// Field-by-field copy mirroring the inverse of [Vtxo::into_bare]. The
521	/// VTXO's `point` is a deterministic checksum of the genesis chain, so a
522	/// caller passing a mismatched `genesis` would simply produce an invalid
523	/// VTXO; use [Vtxo::with_genesis_checked] when paranoia is warranted.
524	///
525	/// A [VtxoValidationError::MissingGenesisItems] will be returned if the provided `genesis`
526	/// contains no genesis transitions and the [Vtxo::point] and [Vtxo::chain_anchor] are not
527	/// equal.
528	///
529	/// No further validation of the VTXO will be performed. It's recommended to run
530	/// [Vtxo::validate] to ensure the VTXO data is consistent with the provided `genesis`.
531	pub fn with_genesis(self, genesis: Full) -> Result<Vtxo<Full, P>, VtxoValidationError> {
532		// Allow VTXOs with no genesis items if the chain anchor is equal to the VTXO point. This
533		// is effectively a virtual representation of a UTXO.
534		if self.point() != self.chain_anchor() {
535			if genesis.items.is_empty() {
536				return Err(VtxoValidationError::MissingGenesisItems);
537			}
538		}
539		else {
540			if !genesis.items.is_empty() {
541				return Err(VtxoValidationError::UnexpectedGenesisItems);
542			}
543		}
544		Ok(Vtxo {
545			policy: self.policy,
546			amount: self.amount,
547			expiry_height: self.expiry_height,
548			server_pubkey: self.server_pubkey,
549			exit_delta: self.exit_delta,
550			anchor_point: self.anchor_point,
551			genesis,
552			point: self.point,
553		})
554	}
555}
556
557impl<P: Policy> Vtxo<Full, P> {
558	/// Returns the total exit depth (including OOR depth) of the vtxo.
559	pub fn exit_depth(&self) -> u16 {
560		self.genesis.items.len() as u16
561	}
562
563	/// Iterate over all oor transitions in this VTXO
564	///
565	/// The outer `Vec` cointains one element for each transition.
566	/// The inner `Vec` contains all pubkeys within that transition.
567	///
568	/// This does not include the current arkoor pubkey, for that use
569	/// [Vtxo::arkoor_pubkey].
570	pub fn past_arkoor_pubkeys(&self) -> Vec<Vec<PublicKey>> {
571		self.genesis.items.iter().filter_map(|g| {
572			match &g.transition {
573				// NB in principle, a genesis item's transition MUST have
574				// an arkoor pubkey, otherwise the vtxo is invalid
575				GenesisTransition::Arkoor(inner) => Some(inner.client_cosigners().collect()),
576				_ => None,
577			}
578		}).collect()
579	}
580
581	/// Whether all transaction witnesses are present
582	///
583	/// It is possible to represent unsigned or otherwise unfinished VTXOs,
584	/// for which this method will return false.
585	pub fn has_all_witnesses(&self) -> bool {
586		self.genesis.items.iter().all(|g| g.transition.has_all_witnesses())
587	}
588
589	/// Check if this VTXO is standard for relay purposes
590	///
591	/// A VTXO is standard if:
592	/// - Its own output is standard
593	/// - all sibling outputs in the exit path are standard
594	/// - each part of the exit path should have a P2A output
595	pub fn is_standard(&self) -> bool {
596		self.txout().is_standard() && self.genesis.items.iter()
597			.all(|i| i.other_outputs.iter().all(|o| o.is_standard()))
598	}
599
600	/// Returns the "hArk" unlock hash if this is a hArk leaf VTXO
601	pub fn unlock_hash(&self) -> Option<UnlockHash> {
602		match self.genesis.items.last()?.transition {
603			GenesisTransition::HashLockedCosigned(ref inner) => Some(inner.unlock.hash()),
604			_ => None,
605		}
606	}
607
608	/// Provide the leaf signature for an unfinalized hArk VTXO
609	///
610	/// Returns true if this VTXO was an unfinalized hArk VTXO.
611	pub fn provide_unlock_signature(&mut self, signature: schnorr::Signature) -> bool {
612		match self.genesis.items.last_mut().map(|g| &mut g.transition) {
613			Some(GenesisTransition::HashLockedCosigned(inner)) => {
614				inner.signature.replace(signature);
615				true
616			},
617			_ => false,
618		}
619	}
620
621	/// Provide the unlock preimage for an unfinalized hArk VTXO
622	///
623	/// Returns true if this VTXO was an unfinalized hArk VTXO and the preimage matched.
624	pub fn provide_unlock_preimage(&mut self, preimage: UnlockPreimage) -> bool {
625		match self.genesis.items.last_mut().map(|g| &mut g.transition) {
626			Some(GenesisTransition::HashLockedCosigned(ref mut inner)) => {
627				if inner.unlock.hash() == UnlockHash::hash(&preimage) {
628					inner.unlock = MaybePreimage::Preimage(preimage);
629					true
630				} else {
631					false
632				}
633			},
634			_ => false,
635		}
636	}
637
638	/// Iterator that constructs all the exit txs for this [Vtxo].
639	pub fn transactions(&self) -> VtxoTxIter<'_, P> {
640		VtxoTxIter::new(self)
641	}
642
643	/// Encode just the genesis chain.
644	///
645	/// The wire format is the same as the genesis section embedded inside a
646	/// full VTXO encoding at [VTXO_ENCODING_VERSION], so callers that already
647	/// store a [Vtxo<Bare>] alongside this blob can reassemble the full VTXO
648	/// via [Vtxo::<Bare>::decode_genesis] and [Vtxo::with_genesis].
649	pub fn encode_genesis<W: io::Write + ?Sized>(
650		&self,
651		w: &mut W,
652	) -> Result<(), io::Error> {
653		Full::encode(&self.genesis, w, VTXO_ENCODING_VERSION)
654	}
655
656	/// Similar to `Vtxo::deserialize` but it takes two byte splices, one containing `Vtxo<Bare>`
657	/// data and one for the `Full` genesis data.
658	pub fn deserialize_with_genesis(
659		mut vtxo_bytes: &[u8],
660		mut genesis_bytes: &[u8],
661	) -> Result<Self, ProtocolDecodingError>
662	where
663		P: ProtocolEncoding,
664	{
665		let (vtxo, version) = vtxo_decode_inner::<Bare, P, _>(&mut vtxo_bytes)?;
666		let genesis = Full::decode(&mut genesis_bytes, version)?;
667		vtxo.with_genesis(genesis)
668			.map_err(|e| ProtocolDecodingError::invalid_err(
669				e, "unable to decode VTXO with genesis",
670			))
671	}
672
673	/// Serialize the genesis chain into a fresh `Vec<u8>`.
674	pub fn serialize_genesis(&self) -> Vec<u8> {
675		let mut out = Vec::new();
676		self.encode_genesis(&mut out).expect("writing to a Vec doesn't fail");
677		out
678	}
679
680	/// Fully validate this VTXO and its entire transaction chain.
681	///
682	/// The `chain_anchor_tx` must be the tx with txid matching
683	/// [Vtxo::chain_anchor].
684	pub fn validate(
685		&self,
686		chain_anchor_tx: &Transaction,
687	) -> Result<(), VtxoValidationError> {
688		self::validation::validate(self, chain_anchor_tx)
689	}
690
691	/// Validate VTXO structure without checking signatures.
692	pub fn validate_unsigned(
693		&self,
694		chain_anchor_tx: &Transaction,
695	) -> Result<(), VtxoValidationError> {
696		self::validation::validate_unsigned(self, chain_anchor_tx)
697	}
698
699	/// Calculates the onchain amount for the [Vtxo].
700	///
701	/// Returns `None` if any overflow occurs. This should be impossible for any VTXO that is valid.
702	pub(crate) fn chain_anchor_amount(&self) -> Option<Amount> {
703		self.amount.checked_add(self.genesis.items.iter().try_fold(Amount::ZERO, |sum, i| {
704			i.other_output_sum().and_then(|amt| sum.checked_add(amt))
705		})?)
706	}
707}
708
709impl<G> Vtxo<G, VtxoPolicy> {
710	/// Returns the user pubkey associated with this [Vtxo].
711	pub fn user_pubkey(&self) -> PublicKey {
712		self.policy.user_pubkey()
713	}
714
715	/// The public key used to cosign arkoor txs spending this [Vtxo].
716	/// This will return [None] if [VtxoPolicy::is_arkoor_compatible] returns false
717	/// for this VTXO's policy.
718	pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
719		self.policy.arkoor_pubkey()
720	}
721}
722
723impl Vtxo<Full, VtxoPolicy> {
724	/// Shortcut to fully finalize a hark leaf using both keys
725	#[cfg(any(test, feature = "test-util"))]
726	pub fn finalize_hark_leaf(
727		&mut self,
728		user_key: &bitcoin::secp256k1::Keypair,
729		server_key: &bitcoin::secp256k1::Keypair,
730		chain_anchor: &Transaction,
731		unlock_preimage: UnlockPreimage,
732	) {
733		use crate::tree::signed::{LeafVtxoCosignContext, LeafVtxoCosignResponse};
734
735		// first sign and provide the signature
736		let (ctx, req) = LeafVtxoCosignContext::new(self, chain_anchor, user_key);
737		let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key);
738		assert!(ctx.finalize(self, cosign));
739		// then provide preimage
740		assert!(self.provide_unlock_preimage(unlock_preimage));
741	}
742}
743
744impl<G> Vtxo<G, ServerVtxoPolicy> {
745	/// Try to convert into a user [Vtxo]
746	///
747	/// Returns the original value on failure.
748	pub fn try_into_user_vtxo(self) -> Result<Vtxo<G, VtxoPolicy>, ServerVtxo<G>> {
749		if let Some(p) = self.policy.clone().into_user_policy() {
750			Ok(Vtxo {
751				policy: p,
752				amount: self.amount,
753				expiry_height: self.expiry_height,
754				server_pubkey: self.server_pubkey,
755				exit_delta: self.exit_delta,
756				anchor_point: self.anchor_point,
757				genesis: self.genesis,
758				point: self.point,
759			})
760		} else {
761			Err(self)
762		}
763	}
764}
765
766impl<G, P: Policy> PartialEq for Vtxo<G, P> {
767	fn eq(&self, other: &Self) -> bool {
768		PartialEq::eq(&self.id(), &other.id())
769	}
770}
771
772impl<G, P: Policy> Eq for Vtxo<G, P> {}
773
774impl<G, P: Policy> PartialOrd for Vtxo<G, P> {
775	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
776		PartialOrd::partial_cmp(&self.id(), &other.id())
777	}
778}
779
780impl<G, P: Policy> Ord for Vtxo<G, P> {
781	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
782		Ord::cmp(&self.id(), &other.id())
783	}
784}
785
786impl<G, P: Policy> std::hash::Hash for Vtxo<G, P> {
787	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
788		std::hash::Hash::hash(&self.id(), state)
789	}
790}
791
792impl<G, P: Policy> AsRef<Vtxo<G, P>> for Vtxo<G, P> {
793	fn as_ref(&self) -> &Vtxo<G, P> {
794	    self
795	}
796}
797
798impl<G> From<Vtxo<G>> for ServerVtxo<G> {
799	fn from(vtxo: Vtxo<G>) -> ServerVtxo<G> {
800		ServerVtxo {
801			policy: vtxo.policy.into(),
802			amount: vtxo.amount,
803			expiry_height: vtxo.expiry_height,
804			server_pubkey: vtxo.server_pubkey,
805			exit_delta: vtxo.exit_delta,
806			anchor_point: vtxo.anchor_point,
807			genesis: vtxo.genesis,
808			point: vtxo.point,
809		}
810	}
811}
812
813/// Implemented on anything that is kinda a [Vtxo]
814pub trait VtxoRef<P: Policy = VtxoPolicy> {
815	/// The [VtxoId] of the VTXO
816	fn vtxo_id(&self) -> VtxoId;
817
818	/// If the bare [Vtxo] can be provided, provides it by reference
819	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { None }
820
821	/// If the full [Vtxo] can be provided, provides it by reference
822	fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { None }
823
824	/// If the full [Vtxo] can be provided, provides it by value, either directly or via cloning
825	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> where Self: Sized;
826}
827
828impl<P: Policy> VtxoRef<P> for VtxoId {
829	fn vtxo_id(&self) -> VtxoId { *self }
830	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
831}
832
833impl<'a, P: Policy> VtxoRef<P> for &'a VtxoId {
834	fn vtxo_id(&self) -> VtxoId { **self }
835	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
836}
837
838impl<P: Policy> VtxoRef<P> for Vtxo<Bare, P> {
839	fn vtxo_id(&self) -> VtxoId { self.id() }
840	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(self)) }
841	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
842}
843
844impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Bare, P> {
845	fn vtxo_id(&self) -> VtxoId { self.id() }
846	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(*self)) }
847	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
848}
849
850impl<P: Policy> VtxoRef<P> for Vtxo<Full, P> {
851	fn vtxo_id(&self) -> VtxoId { self.id() }
852	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
853	fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(self) }
854	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self) }
855}
856
857impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Full, P> {
858	fn vtxo_id(&self) -> VtxoId { self.id() }
859	fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
860	fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(*self) }
861	fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self.clone()) }
862}
863
864/// The byte used to encode the [VtxoPolicy::Pubkey] output type.
865const VTXO_POLICY_PUBKEY: u8 = 0x00;
866
867/// The byte used to encode the [VtxoPolicy::ServerHtlcSend] output type.
868const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
869
870/// The byte used to encode the [VtxoPolicy::ServerHtlcRecv] output type.
871const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
872
873/// The byte used to encode the [ServerVtxoPolicy::Checkpoint] output type.
874const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
875
876/// The byte used to encode the [ServerVtxoPolicy::Expiry] output type.
877const VTXO_POLICY_EXPIRY: u8 = 0x04;
878
879/// The byte used to encode the [ServerVtxoPolicy::HarkLeaf] output type.
880const VTXO_POLICY_HARK_LEAF: u8 = 0x05;
881
882/// The byte used to encode the [ServerVtxoPolicy::HarkForfeit] output type.
883const VTXO_POLICY_HARK_FORFEIT: u8 = 0x06;
884
885/// The byte used to encode the [ServerVtxoPolicy::ServerOwned] output type.
886const VTXO_POLICY_SERVER_OWNED: u8 = 0x07;
887
888impl ProtocolEncoding for VtxoPolicy {
889	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
890		match self {
891			Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
892				w.emit_u8(VTXO_POLICY_PUBKEY)?;
893				user_pubkey.encode(w)?;
894			},
895			Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
896				w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
897				user_pubkey.encode(w)?;
898				payment_hash.to_sha256_hash().encode(w)?;
899				w.emit_u32(*htlc_expiry)?;
900			},
901			Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
902				user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
903			}) => {
904				w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
905				user_pubkey.encode(w)?;
906				payment_hash.to_sha256_hash().encode(w)?;
907				w.emit_u32(*htlc_expiry)?;
908				w.emit_u16(*htlc_expiry_delta)?;
909			},
910		}
911		Ok(())
912	}
913
914	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
915		let type_byte = r.read_u8()?;
916		decode_vtxo_policy(type_byte, r)
917	}
918}
919
920/// Decode a [VtxoPolicy] with the given type byte
921///
922/// We have this function so it can be reused in [VtxoPolicy] and [ServerVtxoPolicy].
923fn decode_vtxo_policy<R: io::Read + ?Sized>(
924	type_byte: u8,
925	r: &mut R,
926) -> Result<VtxoPolicy, ProtocolDecodingError> {
927	match type_byte {
928		VTXO_POLICY_PUBKEY => {
929			let user_pubkey = PublicKey::decode(r)?;
930			Ok(VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
931		},
932		VTXO_POLICY_SERVER_HTLC_SEND => {
933			let user_pubkey = PublicKey::decode(r)?;
934			let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
935			let htlc_expiry = check_block_height(r.read_u32()?)
936				.map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
937			Ok(VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
938		},
939		VTXO_POLICY_SERVER_HTLC_RECV => {
940			let user_pubkey = PublicKey::decode(r)?;
941			let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
942			let htlc_expiry = check_block_height(r.read_u32()?)
943				.map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
944			let htlc_expiry_delta = check_block_delta(r.read_u16()?)
945				.map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry_delta"))?;
946			Ok(VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
947		},
948
949		// IMPORTANT:
950		// When adding a new user vtxo policy variant, don't forget
951		// to also add it to the ServerVtxoPolicy decode match arm.
952
953		v => Err(ProtocolDecodingError::invalid(format_args!(
954			"invalid VtxoPolicy type byte: {v:#x}",
955		))),
956	}
957}
958
959impl ProtocolEncoding for ServerVtxoPolicy {
960	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
961		match self {
962			Self::User(p) => p.encode(w)?,
963			Self::ServerOwned => {
964				w.emit_u8(VTXO_POLICY_SERVER_OWNED)?;
965			},
966			Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
967				w.emit_u8(VTXO_POLICY_CHECKPOINT)?;
968				user_pubkey.encode(w)?;
969			},
970			Self::Expiry(ExpiryVtxoPolicy { internal_key }) => {
971				w.emit_u8(VTXO_POLICY_EXPIRY)?;
972				internal_key.encode(w)?;
973			},
974			Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }) => {
975				w.emit_u8(VTXO_POLICY_HARK_LEAF)?;
976				user_pubkey.encode(w)?;
977				unlock_hash.encode(w)?;
978			},
979			Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }) => {
980				w.emit_u8(VTXO_POLICY_HARK_FORFEIT)?;
981				user_pubkey.encode(w)?;
982				unlock_hash.encode(w)?;
983			},
984		}
985		Ok(())
986	}
987
988	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
989		let type_byte = r.read_u8()?;
990		match type_byte {
991			VTXO_POLICY_PUBKEY | VTXO_POLICY_SERVER_HTLC_SEND | VTXO_POLICY_SERVER_HTLC_RECV => {
992				Ok(Self::User(decode_vtxo_policy(type_byte, r)?))
993			},
994			VTXO_POLICY_SERVER_OWNED => Ok(Self::ServerOwned),
995			VTXO_POLICY_CHECKPOINT => {
996				let user_pubkey = PublicKey::decode(r)?;
997				Ok(Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }))
998			},
999			VTXO_POLICY_EXPIRY => {
1000				let internal_key = XOnlyPublicKey::decode(r)?;
1001				Ok(Self::Expiry(ExpiryVtxoPolicy { internal_key }))
1002			},
1003			VTXO_POLICY_HARK_LEAF => {
1004				let user_pubkey = PublicKey::decode(r)?;
1005				let unlock_hash = sha256::Hash::decode(r)?;
1006				Ok(Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }))
1007			},
1008			VTXO_POLICY_HARK_FORFEIT => {
1009				let user_pubkey = PublicKey::decode(r)?;
1010				let unlock_hash = sha256::Hash::decode(r)?;
1011				Ok(Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }))
1012			},
1013			v => Err(ProtocolDecodingError::invalid(format_args!(
1014				"invalid ServerVtxoPolicy type byte: {v:#x}",
1015			))),
1016		}
1017	}
1018}
1019
1020/// The byte used to encode the [GenesisTransition::Cosigned] gen transition type.
1021const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
1022
1023/// The byte used to encode the [GenesisTransition::Arkoor] gen transition type.
1024const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
1025
1026/// The byte used to encode the [GenesisTransition::HashLockedCosigned] gen transition type.
1027const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
1028
1029impl ProtocolEncoding for GenesisTransition {
1030	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1031		match self {
1032			Self::Cosigned(t) => {
1033				w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
1034				LengthPrefixedVector::new(&t.pubkeys).encode(w)?;
1035				t.signature.encode(w)?;
1036			},
1037			Self::HashLockedCosigned(t) => {
1038				w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
1039				t.user_pubkey.encode(w)?;
1040				t.signature.encode(w)?;
1041				match t.unlock {
1042					MaybePreimage::Preimage(p) => {
1043						w.emit_u8(0)?;
1044						w.emit_slice(&p[..])?;
1045					},
1046					MaybePreimage::Hash(h) => {
1047						w.emit_u8(1)?;
1048						w.emit_slice(&h[..])?;
1049					},
1050				}
1051			},
1052			Self::Arkoor(t) => {
1053				w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
1054				LengthPrefixedVector::new(&t.client_cosigners).encode(w)?;
1055				t.tap_tweak.encode(w)?;
1056				t.signature.encode(w)?;
1057			},
1058		}
1059		Ok(())
1060	}
1061
1062	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1063		match r.read_u8()? {
1064			GENESIS_TRANSITION_TYPE_COSIGNED => {
1065				let pubkeys: Vec<PublicKey> = LengthPrefixedVector::decode(r)?.into_inner();
1066				if pubkeys.is_empty() {
1067					return Err(ProtocolDecodingError::invalid(
1068						"cosigned genesis transition with empty pubkey list",
1069					));
1070				}
1071				let signature = Option::<schnorr::Signature>::decode(r)?;
1072				Ok(Self::new_cosigned(pubkeys, signature))
1073			},
1074			GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
1075				let user_pubkey = PublicKey::decode(r)?;
1076				let signature = Option::<schnorr::Signature>::decode(r)?;
1077				let unlock = match r.read_u8()? {
1078					0 => MaybePreimage::Preimage(r.read_byte_array()?),
1079					1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
1080					v => return Err(ProtocolDecodingError::invalid(format_args!(
1081						"invalid MaybePreimage type byte: {v:#x}",
1082					))),
1083				};
1084				Ok(Self::new_hash_locked_cosigned(user_pubkey, signature, unlock))
1085			},
1086			GENESIS_TRANSITION_TYPE_ARKOOR => {
1087				let cosigners = LengthPrefixedVector::decode(r)?.into_inner();
1088				let taptweak = TapTweakHash::decode(r)?;
1089				let signature = Option::<schnorr::Signature>::decode(r)?;
1090				Ok(Self::new_arkoor(cosigners, taptweak, signature))
1091			},
1092			v => Err(ProtocolDecodingError::invalid(format_args!(
1093				"invalid GenesisTransistion type byte: {v:#x}",
1094			))),
1095		}
1096	}
1097}
1098
1099/// A private trait for VTXO sub-objects that have different encodings dependent on
1100/// the VTXO encoding version
1101trait VtxoVersionedEncoding: Sized {
1102	fn encode<W: io::Write + ?Sized>(&self, w: &mut W, version: u16) -> Result<(), io::Error>;
1103
1104	fn decode<R: io::Read + ?Sized>(
1105		r: &mut R,
1106		version: u16,
1107	) -> Result<Self, ProtocolDecodingError>;
1108}
1109
1110impl VtxoVersionedEncoding for Bare {
1111	fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1112		w.emit_compact_size(0u64)?;
1113		Ok(())
1114	}
1115
1116	fn decode<R: io::Read + ?Sized>(
1117		r: &mut R,
1118		version: u16,
1119	) -> Result<Self, ProtocolDecodingError> {
1120		// We want to be compatible with [Full] encoded VTXOs, so we just ignore
1121		// whatever genesis there might be.
1122		let _full = Full::decode(r, version)?;
1123
1124		Ok(Bare)
1125	}
1126}
1127
1128impl VtxoVersionedEncoding for Full {
1129	fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1130		w.emit_compact_size(self.items.len() as u64)?;
1131		for item in &self.items {
1132			item.transition.encode(w)?;
1133			let nb_outputs = item.other_outputs.len().saturating_add(1);
1134			w.emit_u8(nb_outputs.try_into()
1135				.map_err(|_| io::Error::other("too many outputs on genesis transaction"))?)?;
1136			w.emit_u8(item.output_idx)?;
1137			for txout in &item.other_outputs {
1138				txout.encode(w)?;
1139			}
1140			w.emit_u64(item.fee_amount.to_sat())?;
1141		}
1142		Ok(())
1143	}
1144
1145	fn decode<R: io::Read + ?Sized>(
1146		r: &mut R,
1147		version: u16,
1148	) -> Result<Self, ProtocolDecodingError> {
1149		let nb_genesis_items = r.read_compact_size()? as usize;
1150		OversizedVectorError::check::<GenesisItem>(nb_genesis_items)?;
1151		let mut genesis = Vec::with_capacity(nb_genesis_items);
1152		for _ in 0..nb_genesis_items {
1153			let transition = GenesisTransition::decode(r)?;
1154			let nb_outputs = r.read_u8()? as usize;
1155			let output_idx = r.read_u8()?;
1156			let nb_other = nb_outputs.checked_sub(1)
1157				.ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1158			let mut other_outputs = Vec::with_capacity(nb_other);
1159			for _ in 0..nb_other {
1160				other_outputs.push(TxOut::decode(r)?);
1161			}
1162			let fee_amount = if version == VTXO_NO_FEE_AMOUNT_VERSION {
1163				// Maintain backwards compatibility by assuming a fee of zero.
1164				Amount::ZERO
1165			} else {
1166				Amount::from_sat(r.read_u64()?)
1167			};
1168			genesis.push(GenesisItem { transition, output_idx, other_outputs, fee_amount });
1169		}
1170		Ok(Full { items: genesis })
1171	}
1172}
1173
1174impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<Bare, P> {
1175	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1176		vtxo_encode_inner(&self, w)
1177	}
1178
1179	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1180		Ok(vtxo_decode_inner(r)?.0)
1181	}
1182}
1183
1184impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<Full, P> {
1185	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1186		vtxo_encode_inner(&self, w)
1187	}
1188
1189	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1190		// Only allow coding Vtxo<Full> with no genesis items if the VTXO is a virtual
1191		// representation of an onchain UTXO.
1192		let (vtxo, _) = vtxo_decode_inner::<Full, P, _>(r)?;
1193		if vtxo.point() != vtxo.chain_anchor() {
1194			if vtxo.genesis.items.is_empty() {
1195				return Err(ProtocolDecodingError::invalid_err(
1196					VtxoValidationError::MissingGenesisItems,
1197					format!("VTXO {} has no genesis item data", vtxo.id()),
1198				));
1199			}
1200		} else {
1201			if !vtxo.genesis.items.is_empty() {
1202				return Err(ProtocolDecodingError::invalid_err(
1203					VtxoValidationError::UnexpectedGenesisItems,
1204					format!("decoded genesis item data when there shouldn't be any for VTXO {}", vtxo.id()),
1205				));
1206			}
1207		}
1208		Ok(vtxo)
1209	}
1210}
1211
1212fn vtxo_encode_inner<G, P, W>(vtxo: &Vtxo<G, P>, w: &mut W) -> Result<(), io::Error>
1213where
1214	G: VtxoVersionedEncoding,
1215	P: Policy + ProtocolEncoding,
1216	W: io::Write + ?Sized,
1217{
1218	let version = VTXO_ENCODING_VERSION;
1219	w.emit_u16(version)?;
1220	w.emit_u64(vtxo.amount.to_sat())?;
1221	w.emit_u32(vtxo.expiry_height)?;
1222	vtxo.server_pubkey.encode(w)?;
1223	w.emit_u16(vtxo.exit_delta)?;
1224	vtxo.anchor_point.encode(w)?;
1225
1226	vtxo.genesis.encode(w, version)?;
1227
1228	vtxo.policy.encode(w)?;
1229	vtxo.point.encode(w)?;
1230	Ok(())
1231}
1232
1233fn vtxo_decode_inner<G, P, R>(r: &mut R) -> Result<(Vtxo<G, P>, u16), ProtocolDecodingError>
1234where
1235	G: VtxoVersionedEncoding,
1236	P: Policy + ProtocolEncoding,
1237	R: io::Read + ?Sized,
1238{
1239	let version = r.read_u16()?;
1240	if version != VTXO_ENCODING_VERSION && version != VTXO_NO_FEE_AMOUNT_VERSION {
1241		return Err(ProtocolDecodingError::invalid(format_args!(
1242			"invalid Vtxo encoding version byte: {version:#x}",
1243		)));
1244	}
1245
1246	let amount = Amount::from_sat(r.read_u64()?);
1247	let expiry_height = check_block_height(r.read_u32()?)
1248		.map_err(|e| ProtocolDecodingError::invalid_err(e, "expiry_height"))?;
1249	let server_pubkey = PublicKey::decode(r)?;
1250	let exit_delta = check_block_delta(r.read_u16()?)
1251		.map_err(|e| ProtocolDecodingError::invalid_err(e, "exit_delta"))?;
1252	let anchor_point = OutPoint::decode(r)?;
1253
1254	let genesis = VtxoVersionedEncoding::decode(r, version)?;
1255
1256	let policy = P::decode(r)?;
1257	let point = OutPoint::decode(r)?;
1258	let vtxo = Vtxo {
1259		amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy, point,
1260	};
1261	Ok((vtxo, version))
1262}
1263
1264#[cfg(test)]
1265mod test {
1266	use bitcoin::consensus::encode::serialize_hex;
1267	use bitcoin::hex::DisplayHex;
1268
1269	use crate::test_util::encoding_roundtrip;
1270	use crate::test_util::dummy::{DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1271	use crate::test_util::vectors::{
1272		generate_vtxo_vectors, VTXO_VECTORS, VTXO_NO_FEE_AMOUNT_VERSION_HEXES,
1273	};
1274
1275	use super::*;
1276
1277	#[test]
1278	fn test_generate_vtxo_vectors() {
1279		let g = generate_vtxo_vectors();
1280		// the generation code prints its inner values
1281
1282		println!("\n\ngenerated:");
1283		println!("  anchor_tx: {}", serialize_hex(&g.anchor_tx));
1284		println!("  board_vtxo: {}", g.board_vtxo.serialize().as_hex().to_string());
1285		println!("  arkoor_htlc_out_vtxo: {}", g.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1286		println!("  arkoor2_vtxo: {}", g.arkoor2_vtxo.serialize().as_hex().to_string());
1287		println!("  round_tx: {}", serialize_hex(&g.round_tx));
1288		println!("  round1_vtxo: {}", g.round1_vtxo.serialize().as_hex().to_string());
1289		println!("  round2_vtxo: {}", g.round2_vtxo.serialize().as_hex().to_string());
1290		println!("  arkoor3_vtxo: {}", g.arkoor3_vtxo.serialize().as_hex().to_string());
1291
1292
1293		let v = &*VTXO_VECTORS;
1294		println!("\n\nstatic:");
1295		println!("  anchor_tx: {}", serialize_hex(&v.anchor_tx));
1296		println!("  board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1297		println!("  arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1298		println!("  arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1299		println!("  round_tx: {}", serialize_hex(&v.round_tx));
1300		println!("  round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1301		println!("  round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1302		println!("  arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1303
1304		assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1305		assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1306		assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1307		assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1308		assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1309		assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1310		assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1311		assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1312
1313		// this passes because the Eq is based on id which doesn't compare signatures
1314		assert_eq!(g, *v);
1315	}
1316
1317	#[test]
1318	fn test_vtxo_no_fee_amount_version_upgrade() {
1319		let hexes = &*VTXO_NO_FEE_AMOUNT_VERSION_HEXES;
1320		let v = hexes.deserialize_test_vectors();
1321
1322		// Ensure all VTXOs validate correctly.
1323		v.validate_vtxos();
1324
1325		// Ensure each VTXO serializes and is different from the old hex.
1326		let board_hex = v.board_vtxo.serialize().as_hex().to_string();
1327		let arkoor_htlc_out_vtxo_hex = v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string();
1328		let arkoor2_vtxo_hex = v.arkoor2_vtxo.serialize().as_hex().to_string();
1329		let round1_vtxo_hex = v.round1_vtxo.serialize().as_hex().to_string();
1330		let round2_vtxo_hex = v.round2_vtxo.serialize().as_hex().to_string();
1331		let arkoor3_vtxo_hex = v.arkoor3_vtxo.serialize().as_hex().to_string();
1332		assert_ne!(board_hex, hexes.board_vtxo);
1333		assert_ne!(arkoor_htlc_out_vtxo_hex, hexes.arkoor_htlc_out_vtxo);
1334		assert_ne!(arkoor2_vtxo_hex, hexes.arkoor2_vtxo);
1335		assert_ne!(round1_vtxo_hex, hexes.round1_vtxo);
1336		assert_ne!(round2_vtxo_hex, hexes.round2_vtxo);
1337		assert_ne!(arkoor3_vtxo_hex, hexes.arkoor3_vtxo);
1338
1339		// Now verify that deserializing them again results in exactly the same hex. This should be
1340		// the case because the initial hex strings should have been created with a different
1341		// version, then, when we serialize the VTXOs, we should use the newest version. If you
1342		// deserialize a VTXO with the latest version and serialize it, you should get the same
1343		// result.
1344		let board_vtxo = Vtxo::<Full>::deserialize_hex(&board_hex).unwrap();
1345		assert_eq!(board_vtxo.serialize().as_hex().to_string(), board_hex);
1346		let arkoor_htlc_out_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor_htlc_out_vtxo_hex).unwrap();
1347		assert_eq!(arkoor_htlc_out_vtxo.serialize().as_hex().to_string(), arkoor_htlc_out_vtxo_hex);
1348		let arkoor2_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor2_vtxo_hex).unwrap();
1349		assert_eq!(arkoor2_vtxo.serialize().as_hex().to_string(), arkoor2_vtxo_hex);
1350		let round1_vtxo = Vtxo::<Full>::deserialize_hex(&round1_vtxo_hex).unwrap();
1351		assert_eq!(round1_vtxo.serialize().as_hex().to_string(), round1_vtxo_hex);
1352		let round2_vtxo = Vtxo::<Full>::deserialize_hex(&round2_vtxo_hex).unwrap();
1353		assert_eq!(round2_vtxo.serialize().as_hex().to_string(), round2_vtxo_hex);
1354		let arkoor3_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor3_vtxo_hex).unwrap();
1355		assert_eq!(arkoor3_vtxo.serialize().as_hex().to_string(), arkoor3_vtxo_hex);
1356	}
1357
1358	#[test]
1359	fn exit_depth() {
1360		let vtxos = &*VTXO_VECTORS;
1361		// board
1362		assert_eq!(vtxos.board_vtxo.exit_depth(), 1 /* cosign */);
1363
1364		// round
1365		assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 /* cosign */);
1366
1367		// arkoor
1368		assert_eq!(
1369			vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1370			1 /* cosign */ + 1 /* checkpoint*/ + 1 /* arkoor */,
1371		);
1372		assert_eq!(
1373			vtxos.arkoor2_vtxo.exit_depth(),
1374			1 /* cosign */ + 2 /* checkpoint */ + 2 /* arkoor */,
1375		);
1376		assert_eq!(
1377			vtxos.arkoor3_vtxo.exit_depth(),
1378			3 /* cosign */ + 1 /* checkpoint */ + 1 /* arkoor */,
1379		);
1380	}
1381
1382	#[test]
1383	fn test_split_genesis_roundtrip() {
1384		// For each fixture, splitting the encoding into bare bytes + genesis
1385		// bytes and reassembling must produce a byte-identical full VTXO. This
1386		// is the load-bearing invariant for the m0029 storage migration.
1387		fn check<P: Policy + ProtocolEncoding + Clone + std::fmt::Debug>(
1388			vtxo: &Vtxo<Full, P>,
1389		) where
1390			Vtxo<Full, P>: PartialEq,
1391		{
1392			let original = vtxo.serialize();
1393
1394			let bare_bytes = vtxo.to_bare().serialize();
1395			let genesis_bytes = vtxo.serialize_genesis();
1396
1397			let bare = Vtxo::<Bare, P>::deserialize(&bare_bytes)
1398				.expect("bare deserialize");
1399			let genesis = Full::decode(&mut &genesis_bytes[..], VTXO_ENCODING_VERSION)
1400				.expect("decode_genesis");
1401			let reassembled = bare.with_genesis(genesis)
1402				.expect("reassemble");
1403
1404			assert_eq!(*vtxo, reassembled, "reassembled vtxo differs from original");
1405			assert_eq!(reassembled.serialize(), original,
1406				"reassembled bytes differ from original");
1407		}
1408
1409		let v = &*VTXO_VECTORS;
1410		check(&v.board_vtxo);
1411		check(&v.arkoor_htlc_out_vtxo);
1412		check(&v.arkoor2_vtxo);
1413		check(&v.round1_vtxo);
1414		check(&v.round2_vtxo);
1415		check(&v.arkoor3_vtxo);
1416
1417		// Also exercise a depth-257 genesis to cover compact_size > 252.
1418		let big: Vtxo<Full> = Vtxo {
1419			policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1420			amount: Amount::from_sat(10_000),
1421			expiry_height: 101_010,
1422			server_pubkey: DUMMY_SERVER_KEY.public_key(),
1423			exit_delta: 2016,
1424			anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1425			genesis: Full {
1426				items: vec![GenesisItem {
1427					transition: GenesisTransition::new_cosigned(
1428						vec![DUMMY_USER_KEY.public_key()],
1429						Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1430					),
1431					output_idx: 0,
1432					other_outputs: vec![],
1433					fee_amount: Amount::ZERO,
1434				}; 257],
1435			},
1436			point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1437		};
1438		check(&big);
1439	}
1440
1441	#[test]
1442	fn test_genesis_length_257() {
1443		let vtxo: Vtxo<Full> = Vtxo {
1444			policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1445			amount: Amount::from_sat(10_000),
1446			expiry_height: 101_010,
1447			server_pubkey: DUMMY_SERVER_KEY.public_key(),
1448			exit_delta: 2016,
1449			anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1450			genesis: Full {
1451				items: vec![GenesisItem {
1452					transition: GenesisTransition::new_cosigned(
1453						vec![DUMMY_USER_KEY.public_key()],
1454						Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1455					),
1456					output_idx: 0,
1457					other_outputs: vec![],
1458					fee_amount: Amount::ZERO,
1459				}; 257],
1460			},
1461			point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1462		};
1463		assert_eq!(vtxo.genesis.items.len(), 257);
1464		encoding_roundtrip(&vtxo);
1465	}
1466
1467	#[test]
1468	fn test_genesis_decoding() {
1469		// We should disallow decoding a Vtxo<Bare> as a Vtxo<Full> since it's nonsensical and will
1470		// only lead to confusing errors, such as when validating a VTXO.
1471		fn check<P: Policy + ProtocolEncoding + Clone + std::fmt::Debug>(
1472			vtxo: &Vtxo<Full, P>,
1473		) where
1474			Vtxo<Full, P>: PartialEq,
1475		{
1476			let full_bytes = vtxo.serialize();
1477			let bare_bytes = vtxo.as_bare_vtxo().unwrap().serialize();
1478
1479			// We should support the following:
1480			// - Full -> Full
1481			// - Full -> Bare
1482			// - Bare -> Bare
1483			// We should disallow Bare -> Full.
1484			let full_to_full = Vtxo::<Full>::deserialize(&full_bytes).expect("works");
1485			let full_to_bare = Vtxo::<Bare>::deserialize(&full_bytes).expect("works");
1486			let bare_to_bare = Vtxo::<Bare>::deserialize(&bare_bytes).expect("works");
1487			Vtxo::<Full>::deserialize(&bare_bytes).expect_err("bare to full fails");
1488
1489			assert_eq!(full_to_full.serialize(), full_bytes);
1490			assert_eq!(full_to_bare.serialize(), bare_bytes);
1491			assert_eq!(bare_to_bare.serialize(), bare_bytes);
1492		}
1493
1494		let v = &*VTXO_VECTORS;
1495		check(&v.board_vtxo);
1496		check(&v.arkoor_htlc_out_vtxo);
1497		check(&v.arkoor2_vtxo);
1498		check(&v.round1_vtxo);
1499		check(&v.round2_vtxo);
1500		check(&v.arkoor3_vtxo);
1501	}
1502
1503	mod genesis_transition_encoding {
1504		use bitcoin::hashes::{sha256, Hash};
1505		use bitcoin::secp256k1::{Keypair, PublicKey};
1506		use bitcoin::taproot::TapTweakHash;
1507		use std::str::FromStr;
1508
1509		use crate::encode::ProtocolEncoding;
1510		use crate::test_util::encoding_roundtrip;
1511		use super::genesis::{
1512			GenesisTransition, CosignedGenesis, HashLockedCosignedGenesis, ArkoorGenesis,
1513		};
1514		use super::MaybePreimage;
1515
1516		fn test_pubkey() -> PublicKey {
1517			Keypair::from_str(
1518				"916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92"
1519			).unwrap().public_key()
1520		}
1521
1522		fn test_signature() -> bitcoin::secp256k1::schnorr::Signature {
1523			"cc8b93e9f6fbc2506bb85ae8bbb530b178daac49704f5ce2e3ab69c266fd5932\
1524			 0b28d028eef212e3b9fdc42cfd2e0760a0359d3ea7d2e9e8cfe2040e3f1b71ea"
1525				.parse().unwrap()
1526		}
1527
1528		#[test]
1529		fn cosigned_with_signature() {
1530			let transition = GenesisTransition::Cosigned(CosignedGenesis {
1531				pubkeys: vec![test_pubkey()],
1532				signature: Some(test_signature()),
1533			});
1534			encoding_roundtrip(&transition);
1535		}
1536
1537		#[test]
1538		fn cosigned_without_signature() {
1539			let transition = GenesisTransition::Cosigned(CosignedGenesis {
1540				pubkeys: vec![test_pubkey()],
1541				signature: None,
1542			});
1543			encoding_roundtrip(&transition);
1544		}
1545
1546		#[test]
1547		fn cosigned_empty_pubkeys_rejected() {
1548			let mut buf = Vec::new();
1549			buf.push(super::GENESIS_TRANSITION_TYPE_COSIGNED);
1550			buf.push(0x00); // LengthPrefixedVector length = 0
1551			buf.push(0x00); // Option::<Signature> = None
1552			let err = GenesisTransition::deserialize(&mut buf.as_slice())
1553				.expect_err("empty pubkeys must be rejected");
1554			assert!(format!("{err}").contains("empty pubkey list"), "got: {err}");
1555		}
1556
1557		#[test]
1558		fn cosigned_multiple_pubkeys() {
1559			let pk1 = test_pubkey();
1560			let pk2 = Keypair::from_str(
1561				"fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879"
1562			).unwrap().public_key();
1563
1564			let transition = GenesisTransition::Cosigned(CosignedGenesis {
1565				pubkeys: vec![pk1, pk2],
1566				signature: Some(test_signature()),
1567			});
1568			encoding_roundtrip(&transition);
1569		}
1570
1571		#[test]
1572		fn hash_locked_cosigned_with_preimage() {
1573			let preimage = [0x42u8; 32];
1574			let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1575				user_pubkey: test_pubkey(),
1576				signature: Some(test_signature()),
1577				unlock: MaybePreimage::Preimage(preimage),
1578			});
1579			encoding_roundtrip(&transition);
1580		}
1581
1582		#[test]
1583		fn hash_locked_cosigned_with_hash() {
1584			let hash = sha256::Hash::hash(b"test preimage");
1585			let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1586				user_pubkey: test_pubkey(),
1587				signature: Some(test_signature()),
1588				unlock: MaybePreimage::Hash(hash),
1589			});
1590			encoding_roundtrip(&transition);
1591		}
1592
1593		#[test]
1594		fn hash_locked_cosigned_without_signature() {
1595			let preimage = [0x42u8; 32];
1596			let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1597				user_pubkey: test_pubkey(),
1598				signature: None,
1599				unlock: MaybePreimage::Preimage(preimage),
1600			});
1601			encoding_roundtrip(&transition);
1602		}
1603
1604		#[test]
1605		fn arkoor_with_signature() {
1606			let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1607			let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1608				client_cosigners: vec![test_pubkey()],
1609				tap_tweak,
1610				signature: Some(test_signature()),
1611			});
1612			encoding_roundtrip(&transition);
1613		}
1614
1615		#[test]
1616		fn arkoor_without_signature() {
1617			let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1618			let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1619				client_cosigners: vec![test_pubkey()],
1620				tap_tweak,
1621				signature: None,
1622			});
1623			encoding_roundtrip(&transition);
1624		}
1625	}
1626}