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::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> {
122 if b.len() == 36 {
123 let mut ret = [0u8; 36];
124 ret[..].copy_from_slice(&b[0..36]);
125 Ok(Self(ret))
126 } else {
127 Err(VtxoIdParseError)
128 }
129 }
130
131 pub fn utxo(self) -> OutPoint {
132 let vout = [self.0[32], self.0[33], self.0[34], self.0[35]];
133 OutPoint::new(Txid::from_slice(&self.0[0..32]).unwrap(), u32::from_le_bytes(vout))
134 }
135
136 pub fn to_bytes(self) -> [u8; 36] {
137 self.0
138 }
139}
140
141impl From<OutPoint> for VtxoId {
142 fn from(p: OutPoint) -> VtxoId {
143 let mut ret = [0u8; 36];
144 ret[0..32].copy_from_slice(&p.txid[..]);
145 ret[32..].copy_from_slice(&p.vout.to_le_bytes());
146 VtxoId(ret)
147 }
148}
149
150impl AsRef<[u8]> for VtxoId {
151 fn as_ref(&self) -> &[u8] {
152 &self.0
153 }
154}
155
156impl fmt::Display for VtxoId {
157 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158 fmt::Display::fmt(&self.utxo(), f)
159 }
160}
161
162impl fmt::Debug for VtxoId {
163 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164 fmt::Display::fmt(self, f)
165 }
166}
167
168impl FromStr for VtxoId {
169 type Err = VtxoIdParseError;
170 fn from_str(s: &str) -> Result<Self, Self::Err> {
171 Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
172 }
173}
174
175impl serde::Serialize for VtxoId {
176 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
177 if s.is_human_readable() {
178 s.collect_str(self)
179 } else {
180 s.serialize_bytes(self.as_ref())
181 }
182 }
183}
184
185impl<'de> serde::Deserialize<'de> for VtxoId {
186 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
187 struct Visitor;
188 impl<'de> serde::de::Visitor<'de> for Visitor {
189 type Value = VtxoId;
190 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 write!(f, "a VtxoId")
192 }
193 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
194 VtxoId::from_slice(v).map_err(serde::de::Error::custom)
195 }
196 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
197 VtxoId::from_str(v).map_err(serde::de::Error::custom)
198 }
199 }
200 if d.is_human_readable() {
201 d.deserialize_str(Visitor)
202 } else {
203 d.deserialize_bytes(Visitor)
204 }
205 }
206}
207
208impl ProtocolEncoding for VtxoId {
209 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
210 w.emit_slice(&self.0)
211 }
212 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
213 let array: [u8; 36] = r.read_byte_array()
214 .map_err(|_| ProtocolDecodingError::invalid("invalid vtxo id. Expected 36 bytes"))?;
215
216 Ok(VtxoId(array))
217 }
218}
219
220pub(crate) fn exit_clause(
222 user_pubkey: PublicKey,
223 exit_delta: BlockDelta,
224) -> ScriptBuf {
225 scripts::delayed_sign(exit_delta, user_pubkey.x_only_public_key().0)
226}
227
228pub fn create_exit_tx(
233 prevout: OutPoint,
234 output: TxOut,
235 signature: Option<&schnorr::Signature>,
236 fee: Amount,
237) -> Transaction {
238 Transaction {
239 version: bitcoin::transaction::Version(3),
240 lock_time: LockTime::ZERO,
241 input: vec![TxIn {
242 previous_output: prevout,
243 script_sig: ScriptBuf::new(),
244 sequence: Sequence::ZERO,
245 witness: {
246 let mut ret = Witness::new();
247 if let Some(sig) = signature {
248 ret.push(&sig[..]);
249 }
250 ret
251 },
252 }],
253 output: vec![output, fee::fee_anchor_with_amount(fee)],
254 }
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261pub(crate) enum MaybePreimage {
262 Preimage([u8; 32]),
263 Hash(sha256::Hash),
264}
265
266impl MaybePreimage {
267 pub fn hash(&self) -> sha256::Hash {
269 match self {
270 Self::Preimage(p) => sha256::Hash::hash(p),
271 Self::Hash(h) => *h,
272 }
273 }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
279pub struct VtxoTxIterItem {
280 pub tx: Transaction,
282 pub output_idx: usize,
284}
285
286pub struct VtxoTxIter<'a, P: Policy = VtxoPolicy> {
288 vtxo: &'a Vtxo<Full, P>,
289
290 prev: OutPoint,
291 genesis_idx: usize,
292 current_amount: Amount,
293}
294
295impl<'a, P: Policy> VtxoTxIter<'a, P> {
296 fn new(vtxo: &'a Vtxo<Full, P>) -> VtxoTxIter<'a, P> {
297 let onchain_amount = vtxo.chain_anchor_amount()
299 .expect("This should only fail if the VTXO is invalid.");
300 VtxoTxIter {
301 prev: vtxo.anchor_point,
302 vtxo: vtxo,
303 genesis_idx: 0,
304 current_amount: onchain_amount,
305 }
306 }
307}
308
309impl<'a, P: Policy> Iterator for VtxoTxIter<'a, P> {
310 type Item = VtxoTxIterItem;
311
312 fn next(&mut self) -> Option<Self::Item> {
313 let item = self.vtxo.genesis.items.get(self.genesis_idx)?;
314 let next_amount = self.current_amount.checked_sub(
315 item.other_output_sum().expect("we calculated this amount beforehand")
316 ).expect("we calculated this amount beforehand");
317
318 let next_output = if let Some(item) = self.vtxo.genesis.items.get(self.genesis_idx + 1) {
319 item.transition.input_txout(
320 next_amount,
321 self.vtxo.server_pubkey,
322 self.vtxo.expiry_height,
323 self.vtxo.exit_delta,
324 )
325 } else {
326 self.vtxo.policy.txout(
328 self.vtxo.amount,
329 self.vtxo.server_pubkey,
330 self.vtxo.exit_delta,
331 self.vtxo.expiry_height,
332 )
333 };
334
335 let tx = item.tx(self.prev, next_output, self.vtxo.server_pubkey, self.vtxo.expiry_height);
336 self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
337 self.genesis_idx += 1;
338 self.current_amount = next_amount;
339 let output_idx = item.output_idx as usize;
340 Some(VtxoTxIterItem { tx, output_idx })
341 }
342
343 fn size_hint(&self) -> (usize, Option<usize>) {
344 let len = self.vtxo.genesis.items.len().saturating_sub(self.genesis_idx);
345 (len, Some(len))
346 }
347}
348
349impl<'a, P: Policy> ExactSizeIterator for VtxoTxIter<'a, P> {}
350impl<'a, P: Policy> FusedIterator for VtxoTxIter<'a, P> {}
351
352#[derive(Debug, Clone)]
354pub struct Bare;
355
356#[derive(Debug, Clone)]
358pub struct Full {
359 pub(crate) items: Vec<genesis::GenesisItem>,
360}
361
362#[derive(Debug, Clone)]
376pub struct Vtxo<G = Full, P = VtxoPolicy> {
377 pub(crate) policy: P,
378 pub(crate) amount: Amount,
379 pub(crate) expiry_height: BlockHeight,
380
381 pub(crate) server_pubkey: PublicKey,
382 pub(crate) exit_delta: BlockDelta,
383
384 pub(crate) anchor_point: OutPoint,
385 pub(crate) genesis: G,
387
388 pub(crate) point: OutPoint,
395}
396
397impl<G, P: Policy> Vtxo<G, P> {
398 pub fn id(&self) -> VtxoId {
402 self.point.into()
403 }
404
405 pub fn point(&self) -> OutPoint {
409 self.point
410 }
411
412 pub fn amount(&self) -> Amount {
414 self.amount
415 }
416
417 pub fn chain_anchor(&self) -> OutPoint {
421 self.anchor_point
422 }
423
424 pub fn policy(&self) -> &P {
426 &self.policy
427 }
428
429 pub fn policy_type(&self) -> VtxoPolicyKind {
431 self.policy.policy_type()
432 }
433
434 pub fn expiry_height(&self) -> BlockHeight {
436 self.expiry_height
437 }
438
439 pub fn server_pubkey(&self) -> PublicKey {
441 self.server_pubkey
442 }
443
444 pub fn exit_delta(&self) -> BlockDelta {
446 self.exit_delta
447 }
448
449 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
451 self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
452 }
453
454 pub fn output_script_pubkey(&self) -> ScriptBuf {
456 self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
457 }
458
459 pub fn txout(&self) -> TxOut {
461 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
462 }
463
464 pub fn to_bare(&self) -> Vtxo<Bare, P> {
466 Vtxo {
467 point: self.point,
468 policy: self.policy.clone(),
469 amount: self.amount,
470 expiry_height: self.expiry_height,
471 server_pubkey: self.server_pubkey,
472 exit_delta: self.exit_delta,
473 anchor_point: self.anchor_point,
474 genesis: Bare,
475 }
476 }
477
478 pub fn into_bare(self) -> Vtxo<Bare, P> {
480 Vtxo {
481 point: self.point,
482 policy: self.policy,
483 amount: self.amount,
484 expiry_height: self.expiry_height,
485 server_pubkey: self.server_pubkey,
486 exit_delta: self.exit_delta,
487 anchor_point: self.anchor_point,
488 genesis: Bare,
489 }
490 }
491}
492
493impl<P: Policy> Vtxo<Bare, P> {
494 pub fn new(
496 point: OutPoint,
497 policy: P,
498 amount: Amount,
499 expiry_height: BlockHeight,
500 server_pubkey: PublicKey,
501 exit_delta: BlockDelta,
502 anchor_point: OutPoint,
503 ) -> Self {
504 Vtxo { point, policy, amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis: Bare }
505 }
506}
507
508impl<P: Policy> Vtxo<Full, P> {
509 pub fn exit_depth(&self) -> u16 {
511 self.genesis.items.len() as u16
512 }
513
514 pub fn past_arkoor_pubkeys(&self) -> Vec<Vec<PublicKey>> {
522 self.genesis.items.iter().filter_map(|g| {
523 match &g.transition {
524 GenesisTransition::Arkoor(inner) => Some(inner.client_cosigners().collect()),
527 _ => None,
528 }
529 }).collect()
530 }
531
532 pub fn has_all_witnesses(&self) -> bool {
537 self.genesis.items.iter().all(|g| g.transition.has_all_witnesses())
538 }
539
540 pub fn is_standard(&self) -> bool {
547 self.txout().is_standard() && self.genesis.items.iter()
548 .all(|i| i.other_outputs.iter().all(|o| o.is_standard()))
549 }
550
551 pub fn unlock_hash(&self) -> Option<UnlockHash> {
553 match self.genesis.items.last()?.transition {
554 GenesisTransition::HashLockedCosigned(ref inner) => Some(inner.unlock.hash()),
555 _ => None,
556 }
557 }
558
559 pub fn provide_unlock_signature(&mut self, signature: schnorr::Signature) -> bool {
563 match self.genesis.items.last_mut().map(|g| &mut g.transition) {
564 Some(GenesisTransition::HashLockedCosigned(inner)) => {
565 inner.signature.replace(signature);
566 true
567 },
568 _ => false,
569 }
570 }
571
572 pub fn provide_unlock_preimage(&mut self, preimage: UnlockPreimage) -> bool {
576 match self.genesis.items.last_mut().map(|g| &mut g.transition) {
577 Some(GenesisTransition::HashLockedCosigned(ref mut inner)) => {
578 if inner.unlock.hash() == UnlockHash::hash(&preimage) {
579 inner.unlock = MaybePreimage::Preimage(preimage);
580 true
581 } else {
582 false
583 }
584 },
585 _ => false,
586 }
587 }
588
589 pub fn transactions(&self) -> VtxoTxIter<'_, P> {
591 VtxoTxIter::new(self)
592 }
593
594 pub fn validate(
599 &self,
600 chain_anchor_tx: &Transaction,
601 ) -> Result<(), VtxoValidationError> {
602 self::validation::validate(self, chain_anchor_tx)
603 }
604
605 pub fn validate_unsigned(
607 &self,
608 chain_anchor_tx: &Transaction,
609 ) -> Result<(), VtxoValidationError> {
610 self::validation::validate_unsigned(self, chain_anchor_tx)
611 }
612
613 pub(crate) fn chain_anchor_amount(&self) -> Option<Amount> {
617 self.amount.checked_add(self.genesis.items.iter().try_fold(Amount::ZERO, |sum, i| {
618 i.other_output_sum().and_then(|amt| sum.checked_add(amt))
619 })?)
620 }
621}
622
623impl<G> Vtxo<G, VtxoPolicy> {
624 pub fn user_pubkey(&self) -> PublicKey {
626 self.policy.user_pubkey()
627 }
628
629 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
633 self.policy.arkoor_pubkey()
634 }
635}
636
637impl Vtxo<Full, VtxoPolicy> {
638 #[cfg(any(test, feature = "test-util"))]
640 pub fn finalize_hark_leaf(
641 &mut self,
642 user_key: &bitcoin::secp256k1::Keypair,
643 server_key: &bitcoin::secp256k1::Keypair,
644 chain_anchor: &Transaction,
645 unlock_preimage: UnlockPreimage,
646 ) {
647 use crate::tree::signed::{LeafVtxoCosignContext, LeafVtxoCosignResponse};
648
649 let (ctx, req) = LeafVtxoCosignContext::new(self, chain_anchor, user_key);
651 let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key);
652 assert!(ctx.finalize(self, cosign));
653 assert!(self.provide_unlock_preimage(unlock_preimage));
655 }
656}
657
658impl<G> Vtxo<G, ServerVtxoPolicy> {
659 pub fn try_into_user_vtxo(self) -> Result<Vtxo<G, VtxoPolicy>, ServerVtxo<G>> {
663 if let Some(p) = self.policy.clone().into_user_policy() {
664 Ok(Vtxo {
665 policy: p,
666 amount: self.amount,
667 expiry_height: self.expiry_height,
668 server_pubkey: self.server_pubkey,
669 exit_delta: self.exit_delta,
670 anchor_point: self.anchor_point,
671 genesis: self.genesis,
672 point: self.point,
673 })
674 } else {
675 Err(self)
676 }
677 }
678}
679
680impl<G, P: Policy> PartialEq for Vtxo<G, P> {
681 fn eq(&self, other: &Self) -> bool {
682 PartialEq::eq(&self.id(), &other.id())
683 }
684}
685
686impl<G, P: Policy> Eq for Vtxo<G, P> {}
687
688impl<G, P: Policy> PartialOrd for Vtxo<G, P> {
689 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
690 PartialOrd::partial_cmp(&self.id(), &other.id())
691 }
692}
693
694impl<G, P: Policy> Ord for Vtxo<G, P> {
695 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
696 Ord::cmp(&self.id(), &other.id())
697 }
698}
699
700impl<G, P: Policy> std::hash::Hash for Vtxo<G, P> {
701 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
702 std::hash::Hash::hash(&self.id(), state)
703 }
704}
705
706impl<G, P: Policy> AsRef<Vtxo<G, P>> for Vtxo<G, P> {
707 fn as_ref(&self) -> &Vtxo<G, P> {
708 self
709 }
710}
711
712impl<G> From<Vtxo<G>> for ServerVtxo<G> {
713 fn from(vtxo: Vtxo<G>) -> ServerVtxo<G> {
714 ServerVtxo {
715 policy: vtxo.policy.into(),
716 amount: vtxo.amount,
717 expiry_height: vtxo.expiry_height,
718 server_pubkey: vtxo.server_pubkey,
719 exit_delta: vtxo.exit_delta,
720 anchor_point: vtxo.anchor_point,
721 genesis: vtxo.genesis,
722 point: vtxo.point,
723 }
724 }
725}
726
727pub trait VtxoRef<P: Policy = VtxoPolicy> {
729 fn vtxo_id(&self) -> VtxoId;
731
732 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { None }
734
735 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { None }
737
738 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> where Self: Sized;
740}
741
742impl<P: Policy> VtxoRef<P> for VtxoId {
743 fn vtxo_id(&self) -> VtxoId { *self }
744 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
745}
746
747impl<'a, P: Policy> VtxoRef<P> for &'a VtxoId {
748 fn vtxo_id(&self) -> VtxoId { **self }
749 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
750}
751
752impl<P: Policy> VtxoRef<P> for Vtxo<Bare, P> {
753 fn vtxo_id(&self) -> VtxoId { self.id() }
754 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(self)) }
755 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
756}
757
758impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Bare, P> {
759 fn vtxo_id(&self) -> VtxoId { self.id() }
760 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(*self)) }
761 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
762}
763
764impl<P: Policy> VtxoRef<P> for Vtxo<Full, P> {
765 fn vtxo_id(&self) -> VtxoId { self.id() }
766 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
767 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(self) }
768 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self) }
769}
770
771impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Full, P> {
772 fn vtxo_id(&self) -> VtxoId { self.id() }
773 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
774 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(*self) }
775 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self.clone()) }
776}
777
778const VTXO_POLICY_PUBKEY: u8 = 0x00;
780
781const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
783
784const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
786
787const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
789
790const VTXO_POLICY_EXPIRY: u8 = 0x04;
792
793const VTXO_POLICY_HARK_LEAF: u8 = 0x05;
795
796const VTXO_POLICY_HARK_FORFEIT: u8 = 0x06;
798
799const VTXO_POLICY_SERVER_OWNED: u8 = 0x07;
801
802impl ProtocolEncoding for VtxoPolicy {
803 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
804 match self {
805 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
806 w.emit_u8(VTXO_POLICY_PUBKEY)?;
807 user_pubkey.encode(w)?;
808 },
809 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
810 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
811 user_pubkey.encode(w)?;
812 payment_hash.to_sha256_hash().encode(w)?;
813 w.emit_u32(*htlc_expiry)?;
814 },
815 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
816 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
817 }) => {
818 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
819 user_pubkey.encode(w)?;
820 payment_hash.to_sha256_hash().encode(w)?;
821 w.emit_u32(*htlc_expiry)?;
822 w.emit_u16(*htlc_expiry_delta)?;
823 },
824 }
825 Ok(())
826 }
827
828 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
829 let type_byte = r.read_u8()?;
830 decode_vtxo_policy(type_byte, r)
831 }
832}
833
834fn decode_vtxo_policy<R: io::Read + ?Sized>(
838 type_byte: u8,
839 r: &mut R,
840) -> Result<VtxoPolicy, ProtocolDecodingError> {
841 match type_byte {
842 VTXO_POLICY_PUBKEY => {
843 let user_pubkey = PublicKey::decode(r)?;
844 Ok(VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
845 },
846 VTXO_POLICY_SERVER_HTLC_SEND => {
847 let user_pubkey = PublicKey::decode(r)?;
848 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
849 let htlc_expiry = r.read_u32()?;
850 Ok(VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
851 },
852 VTXO_POLICY_SERVER_HTLC_RECV => {
853 let user_pubkey = PublicKey::decode(r)?;
854 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
855 let htlc_expiry = r.read_u32()?;
856 let htlc_expiry_delta = r.read_u16()?;
857 Ok(VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
858 },
859
860 v => Err(ProtocolDecodingError::invalid(format_args!(
865 "invalid VtxoPolicy type byte: {v:#x}",
866 ))),
867 }
868}
869
870impl ProtocolEncoding for ServerVtxoPolicy {
871 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
872 match self {
873 Self::User(p) => p.encode(w)?,
874 Self::ServerOwned => {
875 w.emit_u8(VTXO_POLICY_SERVER_OWNED)?;
876 },
877 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
878 w.emit_u8(VTXO_POLICY_CHECKPOINT)?;
879 user_pubkey.encode(w)?;
880 },
881 Self::Expiry(ExpiryVtxoPolicy { internal_key }) => {
882 w.emit_u8(VTXO_POLICY_EXPIRY)?;
883 internal_key.encode(w)?;
884 },
885 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }) => {
886 w.emit_u8(VTXO_POLICY_HARK_LEAF)?;
887 user_pubkey.encode(w)?;
888 unlock_hash.encode(w)?;
889 },
890 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }) => {
891 w.emit_u8(VTXO_POLICY_HARK_FORFEIT)?;
892 user_pubkey.encode(w)?;
893 unlock_hash.encode(w)?;
894 },
895 }
896 Ok(())
897 }
898
899 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
900 let type_byte = r.read_u8()?;
901 match type_byte {
902 VTXO_POLICY_PUBKEY | VTXO_POLICY_SERVER_HTLC_SEND | VTXO_POLICY_SERVER_HTLC_RECV => {
903 Ok(Self::User(decode_vtxo_policy(type_byte, r)?))
904 },
905 VTXO_POLICY_SERVER_OWNED => Ok(Self::ServerOwned),
906 VTXO_POLICY_CHECKPOINT => {
907 let user_pubkey = PublicKey::decode(r)?;
908 Ok(Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }))
909 },
910 VTXO_POLICY_EXPIRY => {
911 let internal_key = XOnlyPublicKey::decode(r)?;
912 Ok(Self::Expiry(ExpiryVtxoPolicy { internal_key }))
913 },
914 VTXO_POLICY_HARK_LEAF => {
915 let user_pubkey = PublicKey::decode(r)?;
916 let unlock_hash = sha256::Hash::decode(r)?;
917 Ok(Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }))
918 },
919 VTXO_POLICY_HARK_FORFEIT => {
920 let user_pubkey = PublicKey::decode(r)?;
921 let unlock_hash = sha256::Hash::decode(r)?;
922 Ok(Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }))
923 },
924 v => Err(ProtocolDecodingError::invalid(format_args!(
925 "invalid ServerVtxoPolicy type byte: {v:#x}",
926 ))),
927 }
928 }
929}
930
931const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
933
934const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
936
937const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
939
940impl ProtocolEncoding for GenesisTransition {
941 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
942 match self {
943 Self::Cosigned(t) => {
944 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
945 LengthPrefixedVector::new(&t.pubkeys).encode(w)?;
946 t.signature.encode(w)?;
947 },
948 Self::HashLockedCosigned(t) => {
949 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
950 t.user_pubkey.encode(w)?;
951 t.signature.encode(w)?;
952 match t.unlock {
953 MaybePreimage::Preimage(p) => {
954 w.emit_u8(0)?;
955 w.emit_slice(&p[..])?;
956 },
957 MaybePreimage::Hash(h) => {
958 w.emit_u8(1)?;
959 w.emit_slice(&h[..])?;
960 },
961 }
962 },
963 Self::Arkoor(t) => {
964 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
965 LengthPrefixedVector::new(&t.client_cosigners).encode(w)?;
966 t.tap_tweak.encode(w)?;
967 t.signature.encode(w)?;
968 },
969 }
970 Ok(())
971 }
972
973 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
974 match r.read_u8()? {
975 GENESIS_TRANSITION_TYPE_COSIGNED => {
976 let pubkeys = LengthPrefixedVector::decode(r)?.into_inner();
977 let signature = Option::<schnorr::Signature>::decode(r)?;
978 Ok(Self::new_cosigned(pubkeys, signature))
979 },
980 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
981 let user_pubkey = PublicKey::decode(r)?;
982 let signature = Option::<schnorr::Signature>::decode(r)?;
983 let unlock = match r.read_u8()? {
984 0 => MaybePreimage::Preimage(r.read_byte_array()?),
985 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
986 v => return Err(ProtocolDecodingError::invalid(format_args!(
987 "invalid MaybePreimage type byte: {v:#x}",
988 ))),
989 };
990 Ok(Self::new_hash_locked_cosigned(user_pubkey, signature, unlock))
991 },
992 GENESIS_TRANSITION_TYPE_ARKOOR => {
993 let cosigners = LengthPrefixedVector::decode(r)?.into_inner();
994 let taptweak = TapTweakHash::decode(r)?;
995 let signature = Option::<schnorr::Signature>::decode(r)?;
996 Ok(Self::new_arkoor(cosigners, taptweak, signature))
997 },
998 v => Err(ProtocolDecodingError::invalid(format_args!(
999 "invalid GenesisTransistion type byte: {v:#x}",
1000 ))),
1001 }
1002 }
1003}
1004
1005trait VtxoVersionedEncoding: Sized {
1008 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, version: u16) -> Result<(), io::Error>;
1009
1010 fn decode<R: io::Read + ?Sized>(
1011 r: &mut R,
1012 version: u16,
1013 ) -> Result<Self, ProtocolDecodingError>;
1014}
1015
1016impl VtxoVersionedEncoding for Bare {
1017 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1018 w.emit_compact_size(0u64)?;
1019 Ok(())
1020 }
1021
1022 fn decode<R: io::Read + ?Sized>(
1023 r: &mut R,
1024 version: u16,
1025 ) -> Result<Self, ProtocolDecodingError> {
1026 let _full = Full::decode(r, version)?;
1029
1030 Ok(Bare)
1031 }
1032}
1033
1034impl VtxoVersionedEncoding for Full {
1035 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1036 w.emit_compact_size(self.items.len() as u64)?;
1037 for item in &self.items {
1038 item.transition.encode(w)?;
1039 let nb_outputs = item.other_outputs.len() + 1;
1040 w.emit_u8(nb_outputs.try_into()
1041 .map_err(|_| io::Error::other("too many outputs on genesis transaction"))?)?;
1042 w.emit_u8(item.output_idx)?;
1043 for txout in &item.other_outputs {
1044 txout.encode(w)?;
1045 }
1046 w.emit_u64(item.fee_amount.to_sat())?;
1047 }
1048 Ok(())
1049 }
1050
1051 fn decode<R: io::Read + ?Sized>(
1052 r: &mut R,
1053 version: u16,
1054 ) -> Result<Self, ProtocolDecodingError> {
1055 let nb_genesis_items = r.read_compact_size()? as usize;
1056 OversizedVectorError::check::<GenesisItem>(nb_genesis_items)?;
1057 let mut genesis = Vec::with_capacity(nb_genesis_items);
1058 for _ in 0..nb_genesis_items {
1059 let transition = GenesisTransition::decode(r)?;
1060 let nb_outputs = r.read_u8()? as usize;
1061 let output_idx = r.read_u8()?;
1062 let nb_other = nb_outputs.checked_sub(1)
1063 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1064 let mut other_outputs = Vec::with_capacity(nb_other);
1065 for _ in 0..nb_other {
1066 other_outputs.push(TxOut::decode(r)?);
1067 }
1068 let fee_amount = if version == VTXO_NO_FEE_AMOUNT_VERSION {
1069 Amount::ZERO
1071 } else {
1072 Amount::from_sat(r.read_u64()?)
1073 };
1074 genesis.push(GenesisItem { transition, output_idx, other_outputs, fee_amount });
1075 }
1076 Ok(Full { items: genesis })
1077 }
1078}
1079
1080impl<G: VtxoVersionedEncoding, P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<G, P> {
1081 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1082 let version = VTXO_ENCODING_VERSION;
1083 w.emit_u16(version)?;
1084 w.emit_u64(self.amount.to_sat())?;
1085 w.emit_u32(self.expiry_height)?;
1086 self.server_pubkey.encode(w)?;
1087 w.emit_u16(self.exit_delta)?;
1088 self.anchor_point.encode(w)?;
1089
1090 self.genesis.encode(w, version)?;
1091
1092 self.policy.encode(w)?;
1093 self.point.encode(w)?;
1094 Ok(())
1095 }
1096
1097 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1098 let version = r.read_u16()?;
1099 if version != VTXO_ENCODING_VERSION && version != VTXO_NO_FEE_AMOUNT_VERSION {
1100 return Err(ProtocolDecodingError::invalid(format_args!(
1101 "invalid Vtxo encoding version byte: {version:#x}",
1102 )));
1103 }
1104
1105 let amount = Amount::from_sat(r.read_u64()?);
1106 let expiry_height = r.read_u32()?;
1107 let server_pubkey = PublicKey::decode(r)?;
1108 let exit_delta = r.read_u16()?;
1109 let anchor_point = OutPoint::decode(r)?;
1110
1111 let genesis = VtxoVersionedEncoding::decode(r, version)?;
1112
1113 let policy = P::decode(r)?;
1114 let point = OutPoint::decode(r)?;
1115
1116 Ok(Self {
1117 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy, point,
1118 })
1119 }
1120}
1121
1122
1123#[cfg(test)]
1124mod test {
1125 use bitcoin::consensus::encode::serialize_hex;
1126 use bitcoin::hex::DisplayHex;
1127
1128 use crate::test_util::encoding_roundtrip;
1129 use crate::test_util::dummy::{DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1130 use crate::test_util::vectors::{
1131 generate_vtxo_vectors, VTXO_VECTORS, VTXO_NO_FEE_AMOUNT_VERSION_HEXES,
1132 };
1133
1134 use super::*;
1135
1136 #[test]
1137 fn test_generate_vtxo_vectors() {
1138 let g = generate_vtxo_vectors();
1139 println!("\n\ngenerated:");
1142 println!(" anchor_tx: {}", serialize_hex(&g.anchor_tx));
1143 println!(" board_vtxo: {}", g.board_vtxo.serialize().as_hex().to_string());
1144 println!(" arkoor_htlc_out_vtxo: {}", g.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1145 println!(" arkoor2_vtxo: {}", g.arkoor2_vtxo.serialize().as_hex().to_string());
1146 println!(" round_tx: {}", serialize_hex(&g.round_tx));
1147 println!(" round1_vtxo: {}", g.round1_vtxo.serialize().as_hex().to_string());
1148 println!(" round2_vtxo: {}", g.round2_vtxo.serialize().as_hex().to_string());
1149 println!(" arkoor3_vtxo: {}", g.arkoor3_vtxo.serialize().as_hex().to_string());
1150
1151
1152 let v = &*VTXO_VECTORS;
1153 println!("\n\nstatic:");
1154 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1155 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1156 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1157 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1158 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1159 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1160 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1161 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1162
1163 assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1164 assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1165 assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1166 assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1167 assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1168 assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1169 assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1170 assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1171
1172 assert_eq!(g, *v);
1174 }
1175
1176 #[test]
1177 fn test_vtxo_no_fee_amount_version_upgrade() {
1178 let hexes = &*VTXO_NO_FEE_AMOUNT_VERSION_HEXES;
1179 let v = hexes.deserialize_test_vectors();
1180
1181 v.validate_vtxos();
1183
1184 let board_hex = v.board_vtxo.serialize().as_hex().to_string();
1186 let arkoor_htlc_out_vtxo_hex = v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string();
1187 let arkoor2_vtxo_hex = v.arkoor2_vtxo.serialize().as_hex().to_string();
1188 let round1_vtxo_hex = v.round1_vtxo.serialize().as_hex().to_string();
1189 let round2_vtxo_hex = v.round2_vtxo.serialize().as_hex().to_string();
1190 let arkoor3_vtxo_hex = v.arkoor3_vtxo.serialize().as_hex().to_string();
1191 assert_ne!(board_hex, hexes.board_vtxo);
1192 assert_ne!(arkoor_htlc_out_vtxo_hex, hexes.arkoor_htlc_out_vtxo);
1193 assert_ne!(arkoor2_vtxo_hex, hexes.arkoor2_vtxo);
1194 assert_ne!(round1_vtxo_hex, hexes.round1_vtxo);
1195 assert_ne!(round2_vtxo_hex, hexes.round2_vtxo);
1196 assert_ne!(arkoor3_vtxo_hex, hexes.arkoor3_vtxo);
1197
1198 let board_vtxo = Vtxo::<Full>::deserialize_hex(&board_hex).unwrap();
1204 assert_eq!(board_vtxo.serialize().as_hex().to_string(), board_hex);
1205 let arkoor_htlc_out_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor_htlc_out_vtxo_hex).unwrap();
1206 assert_eq!(arkoor_htlc_out_vtxo.serialize().as_hex().to_string(), arkoor_htlc_out_vtxo_hex);
1207 let arkoor2_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor2_vtxo_hex).unwrap();
1208 assert_eq!(arkoor2_vtxo.serialize().as_hex().to_string(), arkoor2_vtxo_hex);
1209 let round1_vtxo = Vtxo::<Full>::deserialize_hex(&round1_vtxo_hex).unwrap();
1210 assert_eq!(round1_vtxo.serialize().as_hex().to_string(), round1_vtxo_hex);
1211 let round2_vtxo = Vtxo::<Full>::deserialize_hex(&round2_vtxo_hex).unwrap();
1212 assert_eq!(round2_vtxo.serialize().as_hex().to_string(), round2_vtxo_hex);
1213 let arkoor3_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor3_vtxo_hex).unwrap();
1214 assert_eq!(arkoor3_vtxo.serialize().as_hex().to_string(), arkoor3_vtxo_hex);
1215 }
1216
1217 #[test]
1218 fn exit_depth() {
1219 let vtxos = &*VTXO_VECTORS;
1220 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1222
1223 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1225
1226 assert_eq!(
1228 vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1229 1 + 1 + 1 ,
1230 );
1231 assert_eq!(
1232 vtxos.arkoor2_vtxo.exit_depth(),
1233 1 + 2 + 2 ,
1234 );
1235 assert_eq!(
1236 vtxos.arkoor3_vtxo.exit_depth(),
1237 3 + 1 + 1 ,
1238 );
1239 }
1240
1241 #[test]
1242 fn test_genesis_length_257() {
1243 let vtxo: Vtxo<Full> = Vtxo {
1244 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1245 amount: Amount::from_sat(10_000),
1246 expiry_height: 101_010,
1247 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1248 exit_delta: 2016,
1249 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1250 genesis: Full {
1251 items: (0..257).map(|_| {
1252 GenesisItem {
1253 transition: GenesisTransition::new_cosigned(
1254 vec![DUMMY_USER_KEY.public_key()],
1255 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1256 ),
1257 output_idx: 0,
1258 other_outputs: vec![],
1259 fee_amount: Amount::ZERO,
1260 }
1261 }).collect(),
1262 },
1263 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1264 };
1265 assert_eq!(vtxo.genesis.items.len(), 257);
1266 encoding_roundtrip(&vtxo);
1267 }
1268
1269 mod genesis_transition_encoding {
1270 use bitcoin::hashes::{sha256, Hash};
1271 use bitcoin::secp256k1::{Keypair, PublicKey};
1272 use bitcoin::taproot::TapTweakHash;
1273 use std::str::FromStr;
1274
1275 use crate::test_util::encoding_roundtrip;
1276 use super::genesis::{
1277 GenesisTransition, CosignedGenesis, HashLockedCosignedGenesis, ArkoorGenesis,
1278 };
1279 use super::MaybePreimage;
1280
1281 fn test_pubkey() -> PublicKey {
1282 Keypair::from_str(
1283 "916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92"
1284 ).unwrap().public_key()
1285 }
1286
1287 fn test_signature() -> bitcoin::secp256k1::schnorr::Signature {
1288 "cc8b93e9f6fbc2506bb85ae8bbb530b178daac49704f5ce2e3ab69c266fd5932\
1289 0b28d028eef212e3b9fdc42cfd2e0760a0359d3ea7d2e9e8cfe2040e3f1b71ea"
1290 .parse().unwrap()
1291 }
1292
1293 #[test]
1294 fn cosigned_with_signature() {
1295 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1296 pubkeys: vec![test_pubkey()],
1297 signature: Some(test_signature()),
1298 });
1299 encoding_roundtrip(&transition);
1300 }
1301
1302 #[test]
1303 fn cosigned_without_signature() {
1304 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1305 pubkeys: vec![test_pubkey()],
1306 signature: None,
1307 });
1308 encoding_roundtrip(&transition);
1309 }
1310
1311 #[test]
1312 fn cosigned_multiple_pubkeys() {
1313 let pk1 = test_pubkey();
1314 let pk2 = Keypair::from_str(
1315 "fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879"
1316 ).unwrap().public_key();
1317
1318 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1319 pubkeys: vec![pk1, pk2],
1320 signature: Some(test_signature()),
1321 });
1322 encoding_roundtrip(&transition);
1323 }
1324
1325 #[test]
1326 fn hash_locked_cosigned_with_preimage() {
1327 let preimage = [0x42u8; 32];
1328 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1329 user_pubkey: test_pubkey(),
1330 signature: Some(test_signature()),
1331 unlock: MaybePreimage::Preimage(preimage),
1332 });
1333 encoding_roundtrip(&transition);
1334 }
1335
1336 #[test]
1337 fn hash_locked_cosigned_with_hash() {
1338 let hash = sha256::Hash::hash(b"test preimage");
1339 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1340 user_pubkey: test_pubkey(),
1341 signature: Some(test_signature()),
1342 unlock: MaybePreimage::Hash(hash),
1343 });
1344 encoding_roundtrip(&transition);
1345 }
1346
1347 #[test]
1348 fn hash_locked_cosigned_without_signature() {
1349 let preimage = [0x42u8; 32];
1350 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1351 user_pubkey: test_pubkey(),
1352 signature: None,
1353 unlock: MaybePreimage::Preimage(preimage),
1354 });
1355 encoding_roundtrip(&transition);
1356 }
1357
1358 #[test]
1359 fn arkoor_with_signature() {
1360 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1361 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1362 client_cosigners: vec![test_pubkey()],
1363 tap_tweak,
1364 signature: Some(test_signature()),
1365 });
1366 encoding_roundtrip(&transition);
1367 }
1368
1369 #[test]
1370 fn arkoor_without_signature() {
1371 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1372 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1373 client_cosigners: vec![test_pubkey()],
1374 tap_tweak,
1375 signature: None,
1376 });
1377 encoding_roundtrip(&transition);
1378 }
1379 }
1380}