1pub 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
74pub 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
101pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
103
104const VTXO_ENCODING_VERSION: u16 = 2;
106const 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 pub const ENCODE_SIZE: usize = 36;
120
121 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 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 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
230pub(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
238pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub(crate) enum MaybePreimage {
272 Preimage([u8; 32]),
273 Hash(sha256::Hash),
274}
275
276impl MaybePreimage {
277 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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
289pub struct VtxoTxIterItem {
290 pub tx: Transaction,
292 pub output_idx: usize,
294}
295
296pub 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 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 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#[derive(Debug, Clone)]
364pub struct Bare;
365
366#[derive(Debug, Clone)]
368pub struct Full {
369 pub(crate) items: Vec<genesis::GenesisItem>,
370}
371
372#[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 pub(crate) genesis: G,
397
398 pub(crate) point: OutPoint,
405}
406
407impl<G, P: Policy> Vtxo<G, P> {
408 pub fn id(&self) -> VtxoId {
412 self.point.into()
413 }
414
415 pub fn point(&self) -> OutPoint {
419 self.point
420 }
421
422 pub fn amount(&self) -> Amount {
424 self.amount
425 }
426
427 pub fn chain_anchor(&self) -> OutPoint {
431 self.anchor_point
432 }
433
434 pub fn policy(&self) -> &P {
436 &self.policy
437 }
438
439 pub fn policy_type(&self) -> VtxoPolicyKind {
441 self.policy.policy_type()
442 }
443
444 pub fn expiry_height(&self) -> BlockHeight {
446 self.expiry_height
447 }
448
449 pub fn server_pubkey(&self) -> PublicKey {
451 self.server_pubkey
452 }
453
454 pub fn exit_delta(&self) -> BlockDelta {
456 self.exit_delta
457 }
458
459 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
461 self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
462 }
463
464 pub fn output_script_pubkey(&self) -> ScriptBuf {
466 self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
467 }
468
469 pub fn txout(&self) -> TxOut {
471 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
472 }
473
474 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 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 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 pub fn with_genesis(self, genesis: Full) -> Result<Vtxo<Full, P>, VtxoValidationError> {
532 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 pub fn exit_depth(&self) -> u16 {
560 self.genesis.items.len() as u16
561 }
562
563 pub fn past_arkoor_pubkeys(&self) -> Vec<Vec<PublicKey>> {
571 self.genesis.items.iter().filter_map(|g| {
572 match &g.transition {
573 GenesisTransition::Arkoor(inner) => Some(inner.client_cosigners().collect()),
576 _ => None,
577 }
578 }).collect()
579 }
580
581 pub fn has_all_witnesses(&self) -> bool {
586 self.genesis.items.iter().all(|g| g.transition.has_all_witnesses())
587 }
588
589 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 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 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 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 pub fn transactions(&self) -> VtxoTxIter<'_, P> {
640 VtxoTxIter::new(self)
641 }
642
643 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 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 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 pub fn validate(
685 &self,
686 chain_anchor_tx: &Transaction,
687 ) -> Result<(), VtxoValidationError> {
688 self::validation::validate(self, chain_anchor_tx)
689 }
690
691 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 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 pub fn user_pubkey(&self) -> PublicKey {
712 self.policy.user_pubkey()
713 }
714
715 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
719 self.policy.arkoor_pubkey()
720 }
721}
722
723impl Vtxo<Full, VtxoPolicy> {
724 #[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 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 assert!(self.provide_unlock_preimage(unlock_preimage));
741 }
742}
743
744impl<G> Vtxo<G, ServerVtxoPolicy> {
745 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
813pub trait VtxoRef<P: Policy = VtxoPolicy> {
815 fn vtxo_id(&self) -> VtxoId;
817
818 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { None }
820
821 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { None }
823
824 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
864const VTXO_POLICY_PUBKEY: u8 = 0x00;
866
867const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
869
870const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
872
873const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
875
876const VTXO_POLICY_EXPIRY: u8 = 0x04;
878
879const VTXO_POLICY_HARK_LEAF: u8 = 0x05;
881
882const VTXO_POLICY_HARK_FORFEIT: u8 = 0x06;
884
885const 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
920fn 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 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
1020const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
1022
1023const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
1025
1026const 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
1099trait 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 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 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 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 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 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 v.validate_vtxos();
1324
1325 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 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 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1363
1364 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1366
1367 assert_eq!(
1369 vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1370 1 + 1 + 1 ,
1371 );
1372 assert_eq!(
1373 vtxos.arkoor2_vtxo.exit_depth(),
1374 1 + 2 + 2 ,
1375 );
1376 assert_eq!(
1377 vtxos.arkoor3_vtxo.exit_depth(),
1378 3 + 1 + 1 ,
1379 );
1380 }
1381
1382 #[test]
1383 fn test_split_genesis_roundtrip() {
1384 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 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 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 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); buf.push(0x00); 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}