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> {
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 + 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 += 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
518impl<P: Policy> Vtxo<Full, P> {
519 pub fn exit_depth(&self) -> u16 {
521 self.genesis.items.len() as u16
522 }
523
524 pub fn past_arkoor_pubkeys(&self) -> Vec<Vec<PublicKey>> {
532 self.genesis.items.iter().filter_map(|g| {
533 match &g.transition {
534 GenesisTransition::Arkoor(inner) => Some(inner.client_cosigners().collect()),
537 _ => None,
538 }
539 }).collect()
540 }
541
542 pub fn has_all_witnesses(&self) -> bool {
547 self.genesis.items.iter().all(|g| g.transition.has_all_witnesses())
548 }
549
550 pub fn is_standard(&self) -> bool {
557 self.txout().is_standard() && self.genesis.items.iter()
558 .all(|i| i.other_outputs.iter().all(|o| o.is_standard()))
559 }
560
561 pub fn unlock_hash(&self) -> Option<UnlockHash> {
563 match self.genesis.items.last()?.transition {
564 GenesisTransition::HashLockedCosigned(ref inner) => Some(inner.unlock.hash()),
565 _ => None,
566 }
567 }
568
569 pub fn provide_unlock_signature(&mut self, signature: schnorr::Signature) -> bool {
573 match self.genesis.items.last_mut().map(|g| &mut g.transition) {
574 Some(GenesisTransition::HashLockedCosigned(inner)) => {
575 inner.signature.replace(signature);
576 true
577 },
578 _ => false,
579 }
580 }
581
582 pub fn provide_unlock_preimage(&mut self, preimage: UnlockPreimage) -> bool {
586 match self.genesis.items.last_mut().map(|g| &mut g.transition) {
587 Some(GenesisTransition::HashLockedCosigned(ref mut inner)) => {
588 if inner.unlock.hash() == UnlockHash::hash(&preimage) {
589 inner.unlock = MaybePreimage::Preimage(preimage);
590 true
591 } else {
592 false
593 }
594 },
595 _ => false,
596 }
597 }
598
599 pub fn transactions(&self) -> VtxoTxIter<'_, P> {
601 VtxoTxIter::new(self)
602 }
603
604 pub fn validate(
609 &self,
610 chain_anchor_tx: &Transaction,
611 ) -> Result<(), VtxoValidationError> {
612 self::validation::validate(self, chain_anchor_tx)
613 }
614
615 pub fn validate_unsigned(
617 &self,
618 chain_anchor_tx: &Transaction,
619 ) -> Result<(), VtxoValidationError> {
620 self::validation::validate_unsigned(self, chain_anchor_tx)
621 }
622
623 pub(crate) fn chain_anchor_amount(&self) -> Option<Amount> {
627 self.amount.checked_add(self.genesis.items.iter().try_fold(Amount::ZERO, |sum, i| {
628 i.other_output_sum().and_then(|amt| sum.checked_add(amt))
629 })?)
630 }
631}
632
633impl<G> Vtxo<G, VtxoPolicy> {
634 pub fn user_pubkey(&self) -> PublicKey {
636 self.policy.user_pubkey()
637 }
638
639 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
643 self.policy.arkoor_pubkey()
644 }
645}
646
647impl Vtxo<Full, VtxoPolicy> {
648 #[cfg(any(test, feature = "test-util"))]
650 pub fn finalize_hark_leaf(
651 &mut self,
652 user_key: &bitcoin::secp256k1::Keypair,
653 server_key: &bitcoin::secp256k1::Keypair,
654 chain_anchor: &Transaction,
655 unlock_preimage: UnlockPreimage,
656 ) {
657 use crate::tree::signed::{LeafVtxoCosignContext, LeafVtxoCosignResponse};
658
659 let (ctx, req) = LeafVtxoCosignContext::new(self, chain_anchor, user_key);
661 let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key);
662 assert!(ctx.finalize(self, cosign));
663 assert!(self.provide_unlock_preimage(unlock_preimage));
665 }
666}
667
668impl<G> Vtxo<G, ServerVtxoPolicy> {
669 pub fn try_into_user_vtxo(self) -> Result<Vtxo<G, VtxoPolicy>, ServerVtxo<G>> {
673 if let Some(p) = self.policy.clone().into_user_policy() {
674 Ok(Vtxo {
675 policy: p,
676 amount: self.amount,
677 expiry_height: self.expiry_height,
678 server_pubkey: self.server_pubkey,
679 exit_delta: self.exit_delta,
680 anchor_point: self.anchor_point,
681 genesis: self.genesis,
682 point: self.point,
683 })
684 } else {
685 Err(self)
686 }
687 }
688}
689
690impl<G, P: Policy> PartialEq for Vtxo<G, P> {
691 fn eq(&self, other: &Self) -> bool {
692 PartialEq::eq(&self.id(), &other.id())
693 }
694}
695
696impl<G, P: Policy> Eq for Vtxo<G, P> {}
697
698impl<G, P: Policy> PartialOrd for Vtxo<G, P> {
699 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
700 PartialOrd::partial_cmp(&self.id(), &other.id())
701 }
702}
703
704impl<G, P: Policy> Ord for Vtxo<G, P> {
705 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
706 Ord::cmp(&self.id(), &other.id())
707 }
708}
709
710impl<G, P: Policy> std::hash::Hash for Vtxo<G, P> {
711 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
712 std::hash::Hash::hash(&self.id(), state)
713 }
714}
715
716impl<G, P: Policy> AsRef<Vtxo<G, P>> for Vtxo<G, P> {
717 fn as_ref(&self) -> &Vtxo<G, P> {
718 self
719 }
720}
721
722impl<G> From<Vtxo<G>> for ServerVtxo<G> {
723 fn from(vtxo: Vtxo<G>) -> ServerVtxo<G> {
724 ServerVtxo {
725 policy: vtxo.policy.into(),
726 amount: vtxo.amount,
727 expiry_height: vtxo.expiry_height,
728 server_pubkey: vtxo.server_pubkey,
729 exit_delta: vtxo.exit_delta,
730 anchor_point: vtxo.anchor_point,
731 genesis: vtxo.genesis,
732 point: vtxo.point,
733 }
734 }
735}
736
737pub trait VtxoRef<P: Policy = VtxoPolicy> {
739 fn vtxo_id(&self) -> VtxoId;
741
742 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { None }
744
745 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { None }
747
748 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> where Self: Sized;
750}
751
752impl<P: Policy> VtxoRef<P> for VtxoId {
753 fn vtxo_id(&self) -> VtxoId { *self }
754 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
755}
756
757impl<'a, P: Policy> VtxoRef<P> for &'a VtxoId {
758 fn vtxo_id(&self) -> VtxoId { **self }
759 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
760}
761
762impl<P: Policy> VtxoRef<P> for Vtxo<Bare, P> {
763 fn vtxo_id(&self) -> VtxoId { self.id() }
764 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(self)) }
765 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
766}
767
768impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Bare, P> {
769 fn vtxo_id(&self) -> VtxoId { self.id() }
770 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(*self)) }
771 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
772}
773
774impl<P: Policy> VtxoRef<P> for Vtxo<Full, P> {
775 fn vtxo_id(&self) -> VtxoId { self.id() }
776 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
777 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(self) }
778 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self) }
779}
780
781impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Full, P> {
782 fn vtxo_id(&self) -> VtxoId { self.id() }
783 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
784 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(*self) }
785 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self.clone()) }
786}
787
788const VTXO_POLICY_PUBKEY: u8 = 0x00;
790
791const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
793
794const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
796
797const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
799
800const VTXO_POLICY_EXPIRY: u8 = 0x04;
802
803const VTXO_POLICY_HARK_LEAF: u8 = 0x05;
805
806const VTXO_POLICY_HARK_FORFEIT: u8 = 0x06;
808
809const VTXO_POLICY_SERVER_OWNED: u8 = 0x07;
811
812impl ProtocolEncoding for VtxoPolicy {
813 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
814 match self {
815 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
816 w.emit_u8(VTXO_POLICY_PUBKEY)?;
817 user_pubkey.encode(w)?;
818 },
819 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
820 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
821 user_pubkey.encode(w)?;
822 payment_hash.to_sha256_hash().encode(w)?;
823 w.emit_u32(*htlc_expiry)?;
824 },
825 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
826 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
827 }) => {
828 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
829 user_pubkey.encode(w)?;
830 payment_hash.to_sha256_hash().encode(w)?;
831 w.emit_u32(*htlc_expiry)?;
832 w.emit_u16(*htlc_expiry_delta)?;
833 },
834 }
835 Ok(())
836 }
837
838 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
839 let type_byte = r.read_u8()?;
840 decode_vtxo_policy(type_byte, r)
841 }
842}
843
844fn decode_vtxo_policy<R: io::Read + ?Sized>(
848 type_byte: u8,
849 r: &mut R,
850) -> Result<VtxoPolicy, ProtocolDecodingError> {
851 match type_byte {
852 VTXO_POLICY_PUBKEY => {
853 let user_pubkey = PublicKey::decode(r)?;
854 Ok(VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
855 },
856 VTXO_POLICY_SERVER_HTLC_SEND => {
857 let user_pubkey = PublicKey::decode(r)?;
858 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
859 let htlc_expiry = r.read_u32()?;
860 Ok(VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
861 },
862 VTXO_POLICY_SERVER_HTLC_RECV => {
863 let user_pubkey = PublicKey::decode(r)?;
864 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
865 let htlc_expiry = r.read_u32()?;
866 let htlc_expiry_delta = r.read_u16()?;
867 Ok(VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
868 },
869
870 v => Err(ProtocolDecodingError::invalid(format_args!(
875 "invalid VtxoPolicy type byte: {v:#x}",
876 ))),
877 }
878}
879
880impl ProtocolEncoding for ServerVtxoPolicy {
881 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
882 match self {
883 Self::User(p) => p.encode(w)?,
884 Self::ServerOwned => {
885 w.emit_u8(VTXO_POLICY_SERVER_OWNED)?;
886 },
887 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
888 w.emit_u8(VTXO_POLICY_CHECKPOINT)?;
889 user_pubkey.encode(w)?;
890 },
891 Self::Expiry(ExpiryVtxoPolicy { internal_key }) => {
892 w.emit_u8(VTXO_POLICY_EXPIRY)?;
893 internal_key.encode(w)?;
894 },
895 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }) => {
896 w.emit_u8(VTXO_POLICY_HARK_LEAF)?;
897 user_pubkey.encode(w)?;
898 unlock_hash.encode(w)?;
899 },
900 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }) => {
901 w.emit_u8(VTXO_POLICY_HARK_FORFEIT)?;
902 user_pubkey.encode(w)?;
903 unlock_hash.encode(w)?;
904 },
905 }
906 Ok(())
907 }
908
909 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
910 let type_byte = r.read_u8()?;
911 match type_byte {
912 VTXO_POLICY_PUBKEY | VTXO_POLICY_SERVER_HTLC_SEND | VTXO_POLICY_SERVER_HTLC_RECV => {
913 Ok(Self::User(decode_vtxo_policy(type_byte, r)?))
914 },
915 VTXO_POLICY_SERVER_OWNED => Ok(Self::ServerOwned),
916 VTXO_POLICY_CHECKPOINT => {
917 let user_pubkey = PublicKey::decode(r)?;
918 Ok(Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }))
919 },
920 VTXO_POLICY_EXPIRY => {
921 let internal_key = XOnlyPublicKey::decode(r)?;
922 Ok(Self::Expiry(ExpiryVtxoPolicy { internal_key }))
923 },
924 VTXO_POLICY_HARK_LEAF => {
925 let user_pubkey = PublicKey::decode(r)?;
926 let unlock_hash = sha256::Hash::decode(r)?;
927 Ok(Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }))
928 },
929 VTXO_POLICY_HARK_FORFEIT => {
930 let user_pubkey = PublicKey::decode(r)?;
931 let unlock_hash = sha256::Hash::decode(r)?;
932 Ok(Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }))
933 },
934 v => Err(ProtocolDecodingError::invalid(format_args!(
935 "invalid ServerVtxoPolicy type byte: {v:#x}",
936 ))),
937 }
938 }
939}
940
941const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
943
944const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
946
947const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
949
950impl ProtocolEncoding for GenesisTransition {
951 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
952 match self {
953 Self::Cosigned(t) => {
954 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
955 LengthPrefixedVector::new(&t.pubkeys).encode(w)?;
956 t.signature.encode(w)?;
957 },
958 Self::HashLockedCosigned(t) => {
959 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
960 t.user_pubkey.encode(w)?;
961 t.signature.encode(w)?;
962 match t.unlock {
963 MaybePreimage::Preimage(p) => {
964 w.emit_u8(0)?;
965 w.emit_slice(&p[..])?;
966 },
967 MaybePreimage::Hash(h) => {
968 w.emit_u8(1)?;
969 w.emit_slice(&h[..])?;
970 },
971 }
972 },
973 Self::Arkoor(t) => {
974 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
975 LengthPrefixedVector::new(&t.client_cosigners).encode(w)?;
976 t.tap_tweak.encode(w)?;
977 t.signature.encode(w)?;
978 },
979 }
980 Ok(())
981 }
982
983 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
984 match r.read_u8()? {
985 GENESIS_TRANSITION_TYPE_COSIGNED => {
986 let pubkeys = LengthPrefixedVector::decode(r)?.into_inner();
987 let signature = Option::<schnorr::Signature>::decode(r)?;
988 Ok(Self::new_cosigned(pubkeys, signature))
989 },
990 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
991 let user_pubkey = PublicKey::decode(r)?;
992 let signature = Option::<schnorr::Signature>::decode(r)?;
993 let unlock = match r.read_u8()? {
994 0 => MaybePreimage::Preimage(r.read_byte_array()?),
995 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
996 v => return Err(ProtocolDecodingError::invalid(format_args!(
997 "invalid MaybePreimage type byte: {v:#x}",
998 ))),
999 };
1000 Ok(Self::new_hash_locked_cosigned(user_pubkey, signature, unlock))
1001 },
1002 GENESIS_TRANSITION_TYPE_ARKOOR => {
1003 let cosigners = LengthPrefixedVector::decode(r)?.into_inner();
1004 let taptweak = TapTweakHash::decode(r)?;
1005 let signature = Option::<schnorr::Signature>::decode(r)?;
1006 Ok(Self::new_arkoor(cosigners, taptweak, signature))
1007 },
1008 v => Err(ProtocolDecodingError::invalid(format_args!(
1009 "invalid GenesisTransistion type byte: {v:#x}",
1010 ))),
1011 }
1012 }
1013}
1014
1015trait VtxoVersionedEncoding: Sized {
1018 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, version: u16) -> Result<(), io::Error>;
1019
1020 fn decode<R: io::Read + ?Sized>(
1021 r: &mut R,
1022 version: u16,
1023 ) -> Result<Self, ProtocolDecodingError>;
1024}
1025
1026impl VtxoVersionedEncoding for Bare {
1027 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1028 w.emit_compact_size(0u64)?;
1029 Ok(())
1030 }
1031
1032 fn decode<R: io::Read + ?Sized>(
1033 r: &mut R,
1034 version: u16,
1035 ) -> Result<Self, ProtocolDecodingError> {
1036 let _full = Full::decode(r, version)?;
1039
1040 Ok(Bare)
1041 }
1042}
1043
1044impl VtxoVersionedEncoding for Full {
1045 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1046 w.emit_compact_size(self.items.len() as u64)?;
1047 for item in &self.items {
1048 item.transition.encode(w)?;
1049 let nb_outputs = item.other_outputs.len() + 1;
1050 w.emit_u8(nb_outputs.try_into()
1051 .map_err(|_| io::Error::other("too many outputs on genesis transaction"))?)?;
1052 w.emit_u8(item.output_idx)?;
1053 for txout in &item.other_outputs {
1054 txout.encode(w)?;
1055 }
1056 w.emit_u64(item.fee_amount.to_sat())?;
1057 }
1058 Ok(())
1059 }
1060
1061 fn decode<R: io::Read + ?Sized>(
1062 r: &mut R,
1063 version: u16,
1064 ) -> Result<Self, ProtocolDecodingError> {
1065 let nb_genesis_items = r.read_compact_size()? as usize;
1066 OversizedVectorError::check::<GenesisItem>(nb_genesis_items)?;
1067 let mut genesis = Vec::with_capacity(nb_genesis_items);
1068 for _ in 0..nb_genesis_items {
1069 let transition = GenesisTransition::decode(r)?;
1070 let nb_outputs = r.read_u8()? as usize;
1071 let output_idx = r.read_u8()?;
1072 let nb_other = nb_outputs.checked_sub(1)
1073 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1074 let mut other_outputs = Vec::with_capacity(nb_other);
1075 for _ in 0..nb_other {
1076 other_outputs.push(TxOut::decode(r)?);
1077 }
1078 let fee_amount = if version == VTXO_NO_FEE_AMOUNT_VERSION {
1079 Amount::ZERO
1081 } else {
1082 Amount::from_sat(r.read_u64()?)
1083 };
1084 genesis.push(GenesisItem { transition, output_idx, other_outputs, fee_amount });
1085 }
1086 Ok(Full { items: genesis })
1087 }
1088}
1089
1090impl<G: VtxoVersionedEncoding, P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<G, P> {
1091 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1092 let version = VTXO_ENCODING_VERSION;
1093 w.emit_u16(version)?;
1094 w.emit_u64(self.amount.to_sat())?;
1095 w.emit_u32(self.expiry_height)?;
1096 self.server_pubkey.encode(w)?;
1097 w.emit_u16(self.exit_delta)?;
1098 self.anchor_point.encode(w)?;
1099
1100 self.genesis.encode(w, version)?;
1101
1102 self.policy.encode(w)?;
1103 self.point.encode(w)?;
1104 Ok(())
1105 }
1106
1107 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1108 let version = r.read_u16()?;
1109 if version != VTXO_ENCODING_VERSION && version != VTXO_NO_FEE_AMOUNT_VERSION {
1110 return Err(ProtocolDecodingError::invalid(format_args!(
1111 "invalid Vtxo encoding version byte: {version:#x}",
1112 )));
1113 }
1114
1115 let amount = Amount::from_sat(r.read_u64()?);
1116 let expiry_height = r.read_u32()?;
1117 if LockTime::from_height(expiry_height).is_err() {
1120 return Err(ProtocolDecodingError::invalid(format_args!(
1121 "expiry_height {expiry_height} is not a valid block height \
1122 (must be below consensus LOCK_TIME_THRESHOLD)"
1123 )));
1124 }
1125 let server_pubkey = PublicKey::decode(r)?;
1126 let exit_delta = r.read_u16()?;
1127 let anchor_point = OutPoint::decode(r)?;
1128
1129 let genesis = VtxoVersionedEncoding::decode(r, version)?;
1130
1131 let policy = P::decode(r)?;
1132 let point = OutPoint::decode(r)?;
1133
1134 Ok(Self {
1135 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy, point,
1136 })
1137 }
1138}
1139
1140
1141#[cfg(test)]
1142mod test {
1143 use bitcoin::consensus::encode::serialize_hex;
1144 use bitcoin::hex::DisplayHex;
1145
1146 use crate::test_util::encoding_roundtrip;
1147 use crate::test_util::dummy::{DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1148 use crate::test_util::vectors::{
1149 generate_vtxo_vectors, VTXO_VECTORS, VTXO_NO_FEE_AMOUNT_VERSION_HEXES,
1150 };
1151
1152 use super::*;
1153
1154 #[test]
1155 fn test_generate_vtxo_vectors() {
1156 let g = generate_vtxo_vectors();
1157 println!("\n\ngenerated:");
1160 println!(" anchor_tx: {}", serialize_hex(&g.anchor_tx));
1161 println!(" board_vtxo: {}", g.board_vtxo.serialize().as_hex().to_string());
1162 println!(" arkoor_htlc_out_vtxo: {}", g.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1163 println!(" arkoor2_vtxo: {}", g.arkoor2_vtxo.serialize().as_hex().to_string());
1164 println!(" round_tx: {}", serialize_hex(&g.round_tx));
1165 println!(" round1_vtxo: {}", g.round1_vtxo.serialize().as_hex().to_string());
1166 println!(" round2_vtxo: {}", g.round2_vtxo.serialize().as_hex().to_string());
1167 println!(" arkoor3_vtxo: {}", g.arkoor3_vtxo.serialize().as_hex().to_string());
1168
1169
1170 let v = &*VTXO_VECTORS;
1171 println!("\n\nstatic:");
1172 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1173 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1174 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1175 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1176 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1177 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1178 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1179 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1180
1181 assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1182 assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1183 assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1184 assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1185 assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1186 assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1187 assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1188 assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1189
1190 assert_eq!(g, *v);
1192 }
1193
1194 #[test]
1195 fn test_vtxo_no_fee_amount_version_upgrade() {
1196 let hexes = &*VTXO_NO_FEE_AMOUNT_VERSION_HEXES;
1197 let v = hexes.deserialize_test_vectors();
1198
1199 v.validate_vtxos();
1201
1202 let board_hex = v.board_vtxo.serialize().as_hex().to_string();
1204 let arkoor_htlc_out_vtxo_hex = v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string();
1205 let arkoor2_vtxo_hex = v.arkoor2_vtxo.serialize().as_hex().to_string();
1206 let round1_vtxo_hex = v.round1_vtxo.serialize().as_hex().to_string();
1207 let round2_vtxo_hex = v.round2_vtxo.serialize().as_hex().to_string();
1208 let arkoor3_vtxo_hex = v.arkoor3_vtxo.serialize().as_hex().to_string();
1209 assert_ne!(board_hex, hexes.board_vtxo);
1210 assert_ne!(arkoor_htlc_out_vtxo_hex, hexes.arkoor_htlc_out_vtxo);
1211 assert_ne!(arkoor2_vtxo_hex, hexes.arkoor2_vtxo);
1212 assert_ne!(round1_vtxo_hex, hexes.round1_vtxo);
1213 assert_ne!(round2_vtxo_hex, hexes.round2_vtxo);
1214 assert_ne!(arkoor3_vtxo_hex, hexes.arkoor3_vtxo);
1215
1216 let board_vtxo = Vtxo::<Full>::deserialize_hex(&board_hex).unwrap();
1222 assert_eq!(board_vtxo.serialize().as_hex().to_string(), board_hex);
1223 let arkoor_htlc_out_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor_htlc_out_vtxo_hex).unwrap();
1224 assert_eq!(arkoor_htlc_out_vtxo.serialize().as_hex().to_string(), arkoor_htlc_out_vtxo_hex);
1225 let arkoor2_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor2_vtxo_hex).unwrap();
1226 assert_eq!(arkoor2_vtxo.serialize().as_hex().to_string(), arkoor2_vtxo_hex);
1227 let round1_vtxo = Vtxo::<Full>::deserialize_hex(&round1_vtxo_hex).unwrap();
1228 assert_eq!(round1_vtxo.serialize().as_hex().to_string(), round1_vtxo_hex);
1229 let round2_vtxo = Vtxo::<Full>::deserialize_hex(&round2_vtxo_hex).unwrap();
1230 assert_eq!(round2_vtxo.serialize().as_hex().to_string(), round2_vtxo_hex);
1231 let arkoor3_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor3_vtxo_hex).unwrap();
1232 assert_eq!(arkoor3_vtxo.serialize().as_hex().to_string(), arkoor3_vtxo_hex);
1233 }
1234
1235 #[test]
1236 fn exit_depth() {
1237 let vtxos = &*VTXO_VECTORS;
1238 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1240
1241 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1243
1244 assert_eq!(
1246 vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1247 1 + 1 + 1 ,
1248 );
1249 assert_eq!(
1250 vtxos.arkoor2_vtxo.exit_depth(),
1251 1 + 2 + 2 ,
1252 );
1253 assert_eq!(
1254 vtxos.arkoor3_vtxo.exit_depth(),
1255 3 + 1 + 1 ,
1256 );
1257 }
1258
1259 #[test]
1260 fn test_genesis_length_257() {
1261 let vtxo: Vtxo<Full> = Vtxo {
1262 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1263 amount: Amount::from_sat(10_000),
1264 expiry_height: 101_010,
1265 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1266 exit_delta: 2016,
1267 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1268 genesis: Full {
1269 items: (0..257).map(|_| {
1270 GenesisItem {
1271 transition: GenesisTransition::new_cosigned(
1272 vec![DUMMY_USER_KEY.public_key()],
1273 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1274 ),
1275 output_idx: 0,
1276 other_outputs: vec![],
1277 fee_amount: Amount::ZERO,
1278 }
1279 }).collect(),
1280 },
1281 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1282 };
1283 assert_eq!(vtxo.genesis.items.len(), 257);
1284 encoding_roundtrip(&vtxo);
1285 }
1286
1287 mod genesis_transition_encoding {
1288 use bitcoin::hashes::{sha256, Hash};
1289 use bitcoin::secp256k1::{Keypair, PublicKey};
1290 use bitcoin::taproot::TapTweakHash;
1291 use std::str::FromStr;
1292
1293 use crate::test_util::encoding_roundtrip;
1294 use super::genesis::{
1295 GenesisTransition, CosignedGenesis, HashLockedCosignedGenesis, ArkoorGenesis,
1296 };
1297 use super::MaybePreimage;
1298
1299 fn test_pubkey() -> PublicKey {
1300 Keypair::from_str(
1301 "916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92"
1302 ).unwrap().public_key()
1303 }
1304
1305 fn test_signature() -> bitcoin::secp256k1::schnorr::Signature {
1306 "cc8b93e9f6fbc2506bb85ae8bbb530b178daac49704f5ce2e3ab69c266fd5932\
1307 0b28d028eef212e3b9fdc42cfd2e0760a0359d3ea7d2e9e8cfe2040e3f1b71ea"
1308 .parse().unwrap()
1309 }
1310
1311 #[test]
1312 fn cosigned_with_signature() {
1313 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1314 pubkeys: vec![test_pubkey()],
1315 signature: Some(test_signature()),
1316 });
1317 encoding_roundtrip(&transition);
1318 }
1319
1320 #[test]
1321 fn cosigned_without_signature() {
1322 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1323 pubkeys: vec![test_pubkey()],
1324 signature: None,
1325 });
1326 encoding_roundtrip(&transition);
1327 }
1328
1329 #[test]
1330 fn cosigned_multiple_pubkeys() {
1331 let pk1 = test_pubkey();
1332 let pk2 = Keypair::from_str(
1333 "fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879"
1334 ).unwrap().public_key();
1335
1336 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1337 pubkeys: vec![pk1, pk2],
1338 signature: Some(test_signature()),
1339 });
1340 encoding_roundtrip(&transition);
1341 }
1342
1343 #[test]
1344 fn hash_locked_cosigned_with_preimage() {
1345 let preimage = [0x42u8; 32];
1346 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1347 user_pubkey: test_pubkey(),
1348 signature: Some(test_signature()),
1349 unlock: MaybePreimage::Preimage(preimage),
1350 });
1351 encoding_roundtrip(&transition);
1352 }
1353
1354 #[test]
1355 fn hash_locked_cosigned_with_hash() {
1356 let hash = sha256::Hash::hash(b"test preimage");
1357 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1358 user_pubkey: test_pubkey(),
1359 signature: Some(test_signature()),
1360 unlock: MaybePreimage::Hash(hash),
1361 });
1362 encoding_roundtrip(&transition);
1363 }
1364
1365 #[test]
1366 fn hash_locked_cosigned_without_signature() {
1367 let preimage = [0x42u8; 32];
1368 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1369 user_pubkey: test_pubkey(),
1370 signature: None,
1371 unlock: MaybePreimage::Preimage(preimage),
1372 });
1373 encoding_roundtrip(&transition);
1374 }
1375
1376 #[test]
1377 fn arkoor_with_signature() {
1378 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1379 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1380 client_cosigners: vec![test_pubkey()],
1381 tap_tweak,
1382 signature: Some(test_signature()),
1383 });
1384 encoding_roundtrip(&transition);
1385 }
1386
1387 #[test]
1388 fn arkoor_without_signature() {
1389 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1390 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1391 client_cosigners: vec![test_pubkey()],
1392 tap_tweak,
1393 signature: None,
1394 });
1395 encoding_roundtrip(&transition);
1396 }
1397 }
1398}