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};
64pub use self::genesis::TransitionKind;
65
66pub use self::policy::{
67 PubkeyVtxoPolicy, CheckpointVtxoPolicy, ExpiryVtxoPolicy, HarkLeafVtxoPolicy,
68 HarkLeaf_v0_VtxoPolicy,
69 ServerHtlcRecv_v0_VtxoPolicy, ServerHtlcSend_v0_VtxoPolicy, ServerHtlcRecvVtxoPolicy,
70 ServerHtlcSendVtxoPolicy,
71};
72pub use self::policy::clause::{
73 VtxoClause, DelayedSignClause, DelayedTimelockSignClause, HashDelaySignClause,
74 HashDelaySignClause_v0, TapScriptClause,
75};
76
77pub type ServerVtxo<G = Bare> = Vtxo<G, ServerVtxoPolicy>;
79
80use std::borrow::Cow;
81use std::iter::FusedIterator;
82use std::{fmt, io};
83use std::str::FromStr;
84
85use bitcoin::{
86 taproot, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness
87};
88use bitcoin::absolute::LockTime;
89use bitcoin::hashes::{sha256, Hash};
90use bitcoin::secp256k1::{schnorr, PublicKey, XOnlyPublicKey};
91use bitcoin::taproot::TapTweakHash;
92
93use bitcoin_ext::{fee, BlockDelta, BlockHeight, NonStandardOutput, TxOutExt, P2TR_DUST, P2TR_DUST_SAT};
94
95use crate::vtxo::policy::{
96 check_block_delta, check_block_height, HarkForfeitVtxoPolicy, HarkForfeit_v0_VtxoPolicy,
97};
98use crate::encode::{
99 LengthPrefixedVector, MAX_VEC_SIZE, OversizedVectorError, ProtocolDecodingError,
100 ProtocolEncoding, ReadExt, WriteExt,
101};
102use crate::lightning::PaymentHash;
103use crate::tree::signed::{UnlockHash, UnlockPreimage};
104
105pub const VTXO_DUST_SAT: u64 = P2TR_DUST_SAT;
107pub const VTXO_DUST: Amount = P2TR_DUST;
109
110pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
112
113const VTXO_ENCODING_VERSION: u16 = 2;
115const VTXO_NO_FEE_AMOUNT_VERSION: u16 = 1;
117
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
120#[error("failed to parse vtxo id, must be 36 bytes")]
121pub struct VtxoIdParseError;
122
123#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
136pub enum VtxoStandardnessError {
137 #[error("the VTXO's own output is below the dust limit for its script type")]
140 Dusty,
141
142 #[error("dust sibling output at genesis item {item_idx}/{item_count}, output {output_idx}")]
168 DustSibling {
169 item_idx: usize,
170 item_count: usize,
171 output_idx: usize,
172 },
173
174 #[error("the VTXO's own output uses a non-standard script type")]
177 Script,
178
179 #[error("non-standard script in sibling output at genesis item {item_idx}/{item_count}, output {output_idx}")]
183 ScriptSibling {
184 item_idx: usize,
185 item_count: usize,
186 output_idx: usize,
187 },
188}
189
190#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
191pub struct VtxoId([u8; 36]);
192
193impl VtxoId {
194 pub const ENCODE_SIZE: usize = 36;
196
197 pub fn from_slice(b: &[u8]) -> Result<VtxoId, VtxoIdParseError> {
199 if b.len() == 36 {
200 let mut ret = [0u8; 36];
201 ret[..].copy_from_slice(&b[0..36]);
202 Ok(Self(ret))
203 } else {
204 Err(VtxoIdParseError)
205 }
206 }
207
208 pub fn to_point(&self) -> OutPoint {
210 let txid = Txid::from_byte_array(self.0[0..32].try_into().expect("32 bytes"));
211 let vout_bytes = [self.0[32], self.0[33], self.0[34], self.0[35]];
212 let vout = u32::from_le_bytes(vout_bytes);
213 OutPoint::new(txid, vout)
214 }
215
216 #[deprecated(since = "0.1.3", note = "use to_point instead")]
217 pub fn utxo(self) -> OutPoint {
218 self.to_point()
219 }
220
221 pub fn to_bytes(self) -> [u8; 36] {
223 self.0
224 }
225}
226
227impl From<OutPoint> for VtxoId {
228 fn from(p: OutPoint) -> VtxoId {
229 let mut ret = [0u8; 36];
230 ret[0..32].copy_from_slice(&p.txid[..]);
231 ret[32..].copy_from_slice(&p.vout.to_le_bytes());
232 VtxoId(ret)
233 }
234}
235
236impl AsRef<[u8]> for VtxoId {
237 fn as_ref(&self) -> &[u8] {
238 &self.0
239 }
240}
241
242impl fmt::Display for VtxoId {
243 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244 fmt::Display::fmt(&self.to_point(), f)
245 }
246}
247
248impl fmt::Debug for VtxoId {
249 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
250 fmt::Display::fmt(self, f)
251 }
252}
253
254impl FromStr for VtxoId {
255 type Err = VtxoIdParseError;
256 fn from_str(s: &str) -> Result<Self, Self::Err> {
257 Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
258 }
259}
260
261impl serde::Serialize for VtxoId {
262 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
263 if s.is_human_readable() {
264 s.collect_str(self)
265 } else {
266 s.serialize_bytes(self.as_ref())
267 }
268 }
269}
270
271impl<'de> serde::Deserialize<'de> for VtxoId {
272 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
273 struct Visitor;
274 impl<'de> serde::de::Visitor<'de> for Visitor {
275 type Value = VtxoId;
276 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 write!(f, "a VtxoId")
278 }
279 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
280 VtxoId::from_slice(v).map_err(serde::de::Error::custom)
281 }
282 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
283 VtxoId::from_str(v).map_err(serde::de::Error::custom)
284 }
285 }
286 if d.is_human_readable() {
287 d.deserialize_str(Visitor)
288 } else {
289 d.deserialize_bytes(Visitor)
290 }
291 }
292}
293
294impl ProtocolEncoding for VtxoId {
295 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
296 w.emit_slice(&self.0)
297 }
298 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
299 let array: [u8; 36] = r.read_byte_array()
300 .map_err(|_| ProtocolDecodingError::invalid("invalid vtxo id. Expected 36 bytes"))?;
301
302 Ok(VtxoId(array))
303 }
304}
305
306pub fn create_exit_tx(
311 prevout: OutPoint,
312 output: TxOut,
313 signature: Option<&schnorr::Signature>,
314 fee: Amount,
315) -> Transaction {
316 Transaction {
317 version: bitcoin::transaction::Version(3),
318 lock_time: LockTime::ZERO,
319 input: vec![TxIn {
320 previous_output: prevout,
321 script_sig: ScriptBuf::new(),
322 sequence: Sequence::ZERO,
323 witness: {
324 let mut ret = Witness::new();
325 if let Some(sig) = signature {
326 ret.push(&sig[..]);
327 }
328 ret
329 },
330 }],
331 output: vec![output, fee::fee_anchor_with_amount(fee)],
332 }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339pub(crate) enum MaybePreimage {
340 Preimage([u8; 32]),
341 Hash(sha256::Hash),
342}
343
344impl MaybePreimage {
345 pub fn hash(&self) -> sha256::Hash {
347 match self {
348 Self::Preimage(p) => sha256::Hash::hash(p),
349 Self::Hash(h) => *h,
350 }
351 }
352}
353
354#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
357pub struct VtxoTxIterItem {
358 pub tx: Transaction,
360 pub output_idx: usize,
362}
363
364pub struct VtxoTxIter<'a, P: Policy = VtxoPolicy> {
366 vtxo: &'a Vtxo<Full, P>,
367
368 prev: OutPoint,
369 genesis_idx: usize,
370 current_amount: Amount,
371}
372
373impl<'a, P: Policy> VtxoTxIter<'a, P> {
374 fn new(vtxo: &'a Vtxo<Full, P>) -> VtxoTxIter<'a, P> {
375 let onchain_amount = vtxo.chain_anchor_amount()
377 .expect("This should only fail if the VTXO is invalid.");
378 VtxoTxIter {
379 prev: vtxo.anchor_point,
380 vtxo: vtxo,
381 genesis_idx: 0,
382 current_amount: onchain_amount,
383 }
384 }
385}
386
387impl<'a, P: Policy> Iterator for VtxoTxIter<'a, P> {
388 type Item = VtxoTxIterItem;
389
390 fn next(&mut self) -> Option<Self::Item> {
391 let item = self.vtxo.genesis.items.get(self.genesis_idx)?;
392 let next_amount = self.current_amount.checked_sub(
393 item.other_output_sum().expect("we calculated this amount beforehand")
394 ).expect("we calculated this amount beforehand");
395
396 let next_output = if let Some(item) = self.vtxo.genesis.items.get(self.genesis_idx.saturating_add(1)) {
397 item.transition.input_txout(
398 next_amount,
399 self.vtxo.server_pubkey,
400 self.vtxo.expiry_height,
401 self.vtxo.exit_delta,
402 )
403 } else {
404 self.vtxo.policy.txout(
406 self.vtxo.amount,
407 self.vtxo.server_pubkey,
408 self.vtxo.exit_delta,
409 self.vtxo.expiry_height,
410 )
411 };
412
413 let tx = item.tx(self.prev, next_output, self.vtxo.server_pubkey, self.vtxo.expiry_height);
414 self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
415 self.genesis_idx = self.genesis_idx.saturating_add(1);
416 self.current_amount = next_amount;
417 let output_idx = item.output_idx as usize;
418 Some(VtxoTxIterItem { tx, output_idx })
419 }
420
421 fn size_hint(&self) -> (usize, Option<usize>) {
422 let len = self.vtxo.genesis.items.len().saturating_sub(self.genesis_idx);
423 (len, Some(len))
424 }
425}
426
427impl<'a, P: Policy> ExactSizeIterator for VtxoTxIter<'a, P> {}
428impl<'a, P: Policy> FusedIterator for VtxoTxIter<'a, P> {}
429
430#[derive(Debug, Clone)]
432pub struct Bare;
433
434#[derive(Debug, Clone)]
436pub struct Full {
437 pub(crate) items: Vec<genesis::GenesisItem>,
438}
439
440#[derive(Debug, Clone)]
454pub struct Vtxo<G = Full, P = VtxoPolicy> {
455 pub(crate) policy: P,
456 pub(crate) amount: Amount,
457 pub(crate) expiry_height: BlockHeight,
458
459 pub(crate) server_pubkey: PublicKey,
460 pub(crate) exit_delta: BlockDelta,
461
462 pub(crate) anchor_point: OutPoint,
463 pub(crate) genesis: G,
465
466 pub(crate) point: OutPoint,
473}
474
475impl<G, P: Policy> Vtxo<G, P> {
476 pub fn id(&self) -> VtxoId {
480 self.point.into()
481 }
482
483 pub fn point(&self) -> OutPoint {
487 self.point
488 }
489
490 pub fn amount(&self) -> Amount {
492 self.amount
493 }
494
495 pub fn chain_anchor(&self) -> OutPoint {
499 self.anchor_point
500 }
501
502 pub fn policy(&self) -> &P {
504 &self.policy
505 }
506
507 pub fn policy_type(&self) -> VtxoPolicyKind {
509 self.policy.policy_type()
510 }
511
512 pub fn expiry_height(&self) -> BlockHeight {
514 self.expiry_height
515 }
516
517 pub fn server_pubkey(&self) -> PublicKey {
519 self.server_pubkey
520 }
521
522 pub fn exit_delta(&self) -> BlockDelta {
524 self.exit_delta
525 }
526
527 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
529 self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
530 }
531
532 pub fn output_script_pubkey(&self) -> ScriptBuf {
534 self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
535 }
536
537 pub fn txout(&self) -> TxOut {
539 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
540 }
541
542 pub fn to_bare(&self) -> Vtxo<Bare, P> {
544 Vtxo {
545 point: self.point,
546 policy: self.policy.clone(),
547 amount: self.amount,
548 expiry_height: self.expiry_height,
549 server_pubkey: self.server_pubkey,
550 exit_delta: self.exit_delta,
551 anchor_point: self.anchor_point,
552 genesis: Bare,
553 }
554 }
555
556 pub fn into_bare(self) -> Vtxo<Bare, P> {
558 Vtxo {
559 point: self.point,
560 policy: self.policy,
561 amount: self.amount,
562 expiry_height: self.expiry_height,
563 server_pubkey: self.server_pubkey,
564 exit_delta: self.exit_delta,
565 anchor_point: self.anchor_point,
566 genesis: Bare,
567 }
568 }
569}
570
571impl<P: Policy> Vtxo<Bare, P> {
572 pub fn new(
574 point: OutPoint,
575 policy: P,
576 amount: Amount,
577 expiry_height: BlockHeight,
578 server_pubkey: PublicKey,
579 exit_delta: BlockDelta,
580 anchor_point: OutPoint,
581 ) -> Self {
582 Vtxo { point, policy, amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis: Bare }
583 }
584
585 pub fn with_genesis(self, genesis: Full) -> Result<Vtxo<Full, P>, VtxoValidationError> {
600 if self.point() != self.chain_anchor() {
603 if genesis.items.is_empty() {
604 return Err(VtxoValidationError::MissingGenesisItems);
605 }
606 }
607 else {
608 if !genesis.items.is_empty() {
609 return Err(VtxoValidationError::UnexpectedGenesisItems);
610 }
611 }
612 Ok(Vtxo {
613 policy: self.policy,
614 amount: self.amount,
615 expiry_height: self.expiry_height,
616 server_pubkey: self.server_pubkey,
617 exit_delta: self.exit_delta,
618 anchor_point: self.anchor_point,
619 genesis,
620 point: self.point,
621 })
622 }
623}
624
625const _: () = assert!(
629 MAX_VEC_SIZE / core::mem::size_of::<GenesisItem>() <= u16::MAX as usize,
630 "genesis decode cap must keep items.len() within u16 for Vtxo::exit_depth",
631);
632
633impl<P: Policy> Vtxo<Full, P> {
634 pub fn exit_depth(&self) -> u16 {
636 u16::try_from(self.genesis.items.len())
640 .expect("genesis item count fits in u16")
641 }
642
643 pub fn past_arkoor_pubkeys(&self) -> Vec<Vec<PublicKey>> {
651 self.genesis.items.iter().filter_map(|g| {
652 match &g.transition {
653 GenesisTransition::Arkoor(inner) => Some(inner.client_cosigners().collect()),
656 _ => None,
657 }
658 }).collect()
659 }
660
661 pub fn has_all_witnesses(&self) -> bool {
666 self.genesis.items.iter().all(|g| g.transition.has_all_witnesses())
667 }
668
669 pub fn is_standard(&self) -> bool {
679 self.check_standard().is_ok()
680 }
681
682 pub fn check_standard(&self) -> Result<(), VtxoStandardnessError> {
689 if let Err(kind) = self.txout().check_standard() {
690 return Err(match kind {
691 NonStandardOutput::Dust => VtxoStandardnessError::Dusty,
692 NonStandardOutput::Script => VtxoStandardnessError::Script,
693 });
694 }
695 let item_count = self.genesis.items.len();
696 for (item_idx, item) in self.genesis.items.iter().enumerate() {
697 for (output_idx, out) in item.other_outputs.iter().enumerate() {
698 if let Err(kind) = out.check_standard() {
699 return Err(match kind {
700 NonStandardOutput::Dust => VtxoStandardnessError::DustSibling {
701 item_idx, item_count, output_idx,
702 },
703 NonStandardOutput::Script => VtxoStandardnessError::ScriptSibling {
704 item_idx, item_count, output_idx,
705 },
706 });
707 }
708 }
709 }
710 Ok(())
711 }
712
713 pub fn unlock_hash(&self) -> Option<UnlockHash> {
715 match self.genesis.items.last()?.transition {
716 GenesisTransition::HashLockedCosigned(ref inner) => Some(inner.unlock.hash()),
717 GenesisTransition::HashLockedCosigned_v0(ref inner) => Some(inner.unlock.hash()),
718 _ => None,
719 }
720 }
721
722 pub fn provide_unlock_signature(&mut self, signature: schnorr::Signature) -> bool {
726 match self.genesis.items.last_mut().map(|g| &mut g.transition) {
727 Some(GenesisTransition::HashLockedCosigned(inner)) => {
728 inner.signature.replace(signature);
729 true
730 },
731 Some(GenesisTransition::HashLockedCosigned_v0(inner)) => {
732 inner.signature.replace(signature);
733 true
734 },
735 _ => false,
736 }
737 }
738
739 pub fn provide_unlock_preimage(&mut self, preimage: UnlockPreimage) -> bool {
743 match self.genesis.items.last_mut().map(|g| &mut g.transition) {
744 Some(GenesisTransition::HashLockedCosigned(ref mut inner)) => {
745 if inner.unlock.hash() == UnlockHash::hash(&preimage) {
746 inner.unlock = MaybePreimage::Preimage(preimage);
747 true
748 } else {
749 false
750 }
751 },
752 Some(GenesisTransition::HashLockedCosigned_v0(ref mut inner)) => {
753 if inner.unlock.hash() == UnlockHash::hash(&preimage) {
754 inner.unlock = MaybePreimage::Preimage(preimage);
755 true
756 } else {
757 false
758 }
759 },
760 _ => false,
761 }
762 }
763
764 pub fn transactions(&self) -> VtxoTxIter<'_, P> {
766 VtxoTxIter::new(self)
767 }
768
769 pub fn encode_genesis<W: io::Write + ?Sized>(
776 &self,
777 w: &mut W,
778 ) -> Result<(), io::Error> {
779 Full::encode(&self.genesis, w, VTXO_ENCODING_VERSION)
780 }
781
782 pub fn deserialize_with_genesis(
785 mut vtxo_bytes: &[u8],
786 mut genesis_bytes: &[u8],
787 ) -> Result<Self, ProtocolDecodingError>
788 where
789 P: ProtocolEncoding,
790 {
791 let (vtxo, version) = vtxo_decode_inner::<Bare, P, _>(&mut vtxo_bytes)?;
792 let genesis = Full::decode(&mut genesis_bytes, version)?;
793 vtxo.with_genesis(genesis)
794 .map_err(|e| ProtocolDecodingError::invalid_err(
795 e, "unable to decode VTXO with genesis",
796 ))
797 }
798
799 pub fn serialize_genesis(&self) -> Vec<u8> {
801 let mut out = Vec::new();
802 self.encode_genesis(&mut out).expect("writing to a Vec doesn't fail");
803 out
804 }
805
806 pub fn validate(
811 &self,
812 chain_anchor_tx: &Transaction,
813 ) -> Result<(), VtxoValidationError> {
814 self::validation::validate(self, chain_anchor_tx)
815 }
816
817 pub fn validate_unsigned(
819 &self,
820 chain_anchor_tx: &Transaction,
821 ) -> Result<(), VtxoValidationError> {
822 self::validation::validate_unsigned(self, chain_anchor_tx)
823 }
824
825 pub(crate) fn chain_anchor_amount(&self) -> Option<Amount> {
829 self.amount.checked_add(self.genesis.items.iter().try_fold(Amount::ZERO, |sum, i| {
830 i.other_output_sum().and_then(|amt| sum.checked_add(amt))
831 })?)
832 }
833
834 pub fn ancestor_ids(&self) -> Vec<VtxoId> {
843 let items = self.transactions().collect::<Vec<_>>();
844 let ancestor_count = items.len().saturating_sub(1);
846 items.iter()
847 .take(ancestor_count)
848 .map(|item| OutPoint::new(item.tx.compute_txid(), item.output_idx as u32).into())
849 .collect()
850 }
851}
852
853impl<G> Vtxo<G, VtxoPolicy> {
854 pub fn user_pubkey(&self) -> PublicKey {
856 self.policy.user_pubkey()
857 }
858
859 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
863 self.policy.arkoor_pubkey()
864 }
865}
866
867impl Vtxo<Full, VtxoPolicy> {
868 #[cfg(any(test, feature = "test-util"))]
870 pub fn finalize_hark_leaf(
871 &mut self,
872 user_key: &bitcoin::secp256k1::Keypair,
873 server_key: &bitcoin::secp256k1::Keypair,
874 chain_anchor: &Transaction,
875 unlock_preimage: UnlockPreimage,
876 ) {
877 use crate::tree::signed::{LeafVtxoCosignContext, LeafVtxoCosignResponse};
878
879 let (ctx, req) = LeafVtxoCosignContext::new(self, chain_anchor, user_key)
881 .expect("not a hArk leaf VTXO");
882 let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key)
883 .expect("not a hArk leaf VTXO");
884 assert!(ctx.finalize(self, cosign));
885 assert!(self.provide_unlock_preimage(unlock_preimage));
887 }
888}
889
890impl<G> Vtxo<G, ServerVtxoPolicy> {
891 pub fn try_into_user_vtxo(self) -> Result<Vtxo<G, VtxoPolicy>, ServerVtxo<G>> {
895 if let Some(p) = self.policy.clone().into_user_policy() {
896 Ok(Vtxo {
897 policy: p,
898 amount: self.amount,
899 expiry_height: self.expiry_height,
900 server_pubkey: self.server_pubkey,
901 exit_delta: self.exit_delta,
902 anchor_point: self.anchor_point,
903 genesis: self.genesis,
904 point: self.point,
905 })
906 } else {
907 Err(self)
908 }
909 }
910}
911
912impl<G, P: Policy> PartialEq for Vtxo<G, P> {
913 fn eq(&self, other: &Self) -> bool {
914 PartialEq::eq(&self.id(), &other.id())
915 }
916}
917
918impl<G, P: Policy> Eq for Vtxo<G, P> {}
919
920impl<G, P: Policy> PartialOrd for Vtxo<G, P> {
921 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
922 PartialOrd::partial_cmp(&self.id(), &other.id())
923 }
924}
925
926impl<G, P: Policy> Ord for Vtxo<G, P> {
927 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
928 Ord::cmp(&self.id(), &other.id())
929 }
930}
931
932impl<G, P: Policy> std::hash::Hash for Vtxo<G, P> {
933 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
934 std::hash::Hash::hash(&self.id(), state)
935 }
936}
937
938impl<G, P: Policy> AsRef<Vtxo<G, P>> for Vtxo<G, P> {
939 fn as_ref(&self) -> &Vtxo<G, P> {
940 self
941 }
942}
943
944impl<G> From<Vtxo<G>> for ServerVtxo<G> {
945 fn from(vtxo: Vtxo<G>) -> ServerVtxo<G> {
946 ServerVtxo {
947 policy: vtxo.policy.into(),
948 amount: vtxo.amount,
949 expiry_height: vtxo.expiry_height,
950 server_pubkey: vtxo.server_pubkey,
951 exit_delta: vtxo.exit_delta,
952 anchor_point: vtxo.anchor_point,
953 genesis: vtxo.genesis,
954 point: vtxo.point,
955 }
956 }
957}
958
959pub trait VtxoRef<P: Policy = VtxoPolicy> {
961 fn vtxo_id(&self) -> VtxoId;
963
964 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { None }
966
967 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { None }
969
970 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> where Self: Sized;
972}
973
974impl<P: Policy> VtxoRef<P> for VtxoId {
975 fn vtxo_id(&self) -> VtxoId { *self }
976 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
977}
978
979impl<'a, P: Policy> VtxoRef<P> for &'a VtxoId {
980 fn vtxo_id(&self) -> VtxoId { **self }
981 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
982}
983
984impl<P: Policy> VtxoRef<P> for Vtxo<Bare, P> {
985 fn vtxo_id(&self) -> VtxoId { self.id() }
986 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(self)) }
987 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
988}
989
990impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Bare, P> {
991 fn vtxo_id(&self) -> VtxoId { self.id() }
992 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(*self)) }
993 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
994}
995
996impl<P: Policy> VtxoRef<P> for Vtxo<Full, P> {
997 fn vtxo_id(&self) -> VtxoId { self.id() }
998 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
999 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(self) }
1000 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self) }
1001}
1002
1003impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Full, P> {
1004 fn vtxo_id(&self) -> VtxoId { self.id() }
1005 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
1006 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(*self) }
1007 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self.clone()) }
1008}
1009
1010const VTXO_POLICY_PUBKEY: u8 = 0x00;
1012
1013const VTXO_POLICY_SERVER_HTLC_SEND_V0: u8 = 0x01;
1015
1016const VTXO_POLICY_SERVER_HTLC_RECV_V0: u8 = 0x02;
1018
1019const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
1021
1022const VTXO_POLICY_EXPIRY: u8 = 0x04;
1024
1025const VTXO_POLICY_HARK_LEAF_V0: u8 = 0x05;
1027
1028const VTXO_POLICY_HARK_FORFEIT_V0: u8 = 0x06;
1030
1031const VTXO_POLICY_SERVER_OWNED: u8 = 0x07;
1033
1034const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x08;
1036
1037const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x09;
1039
1040const VTXO_POLICY_HARK_LEAF: u8 = 0x0a;
1042
1043const VTXO_POLICY_HARK_FORFEIT: u8 = 0x0b;
1045
1046impl ProtocolEncoding for VtxoPolicy {
1047 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1048 match self {
1049 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
1050 w.emit_u8(VTXO_POLICY_PUBKEY)?;
1051 user_pubkey.encode(w)?;
1052 },
1053 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
1054 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
1055 user_pubkey.encode(w)?;
1056 payment_hash.to_sha256_hash().encode(w)?;
1057 w.emit_u32(*htlc_expiry)?;
1058 },
1059 Self::ServerHtlcSend_v0(ServerHtlcSend_v0_VtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
1060 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND_V0)?;
1061 user_pubkey.encode(w)?;
1062 payment_hash.to_sha256_hash().encode(w)?;
1063 w.emit_u32(*htlc_expiry)?;
1064 },
1065 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1066 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1067 }) => {
1068 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
1069 user_pubkey.encode(w)?;
1070 payment_hash.to_sha256_hash().encode(w)?;
1071 w.emit_u32(*htlc_expiry)?;
1072 w.emit_u16(*htlc_expiry_delta)?;
1073 },
1074 Self::ServerHtlcRecv_v0(ServerHtlcRecv_v0_VtxoPolicy {
1075 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1076 }) => {
1077 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV_V0)?;
1078 user_pubkey.encode(w)?;
1079 payment_hash.to_sha256_hash().encode(w)?;
1080 w.emit_u32(*htlc_expiry)?;
1081 w.emit_u16(*htlc_expiry_delta)?;
1082 },
1083 }
1084 Ok(())
1085 }
1086
1087 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1088 let type_byte = r.read_u8()?;
1089 decode_vtxo_policy(type_byte, r)
1090 }
1091}
1092
1093fn decode_vtxo_policy<R: io::Read + ?Sized>(
1097 type_byte: u8,
1098 r: &mut R,
1099) -> Result<VtxoPolicy, ProtocolDecodingError> {
1100 match type_byte {
1101 VTXO_POLICY_PUBKEY => {
1102 let user_pubkey = PublicKey::decode(r)?;
1103 Ok(VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
1104 },
1105 VTXO_POLICY_SERVER_HTLC_SEND => {
1106 let user_pubkey = PublicKey::decode(r)?;
1107 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1108 let htlc_expiry = check_block_height(r.read_u32()?)
1109 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1110 Ok(VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy {
1111 user_pubkey, payment_hash, htlc_expiry,
1112 }))
1113 },
1114 VTXO_POLICY_SERVER_HTLC_SEND_V0 => {
1115 let user_pubkey = PublicKey::decode(r)?;
1116 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1117 let htlc_expiry = check_block_height(r.read_u32()?)
1118 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1119 Ok(VtxoPolicy::ServerHtlcSend_v0(ServerHtlcSend_v0_VtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
1120 },
1121 VTXO_POLICY_SERVER_HTLC_RECV => {
1122 let user_pubkey = PublicKey::decode(r)?;
1123 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1124 let htlc_expiry = check_block_height(r.read_u32()?)
1125 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1126 let htlc_expiry_delta = check_block_delta(r.read_u16()?)
1127 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry_delta"))?;
1128 Ok(VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1129 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1130 }))
1131 },
1132 VTXO_POLICY_SERVER_HTLC_RECV_V0 => {
1133 let user_pubkey = PublicKey::decode(r)?;
1134 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1135 let htlc_expiry = check_block_height(r.read_u32()?)
1136 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1137 let htlc_expiry_delta = check_block_delta(r.read_u16()?)
1138 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry_delta"))?;
1139 Ok(VtxoPolicy::ServerHtlcRecv_v0(ServerHtlcRecv_v0_VtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
1140 },
1141
1142 v => Err(ProtocolDecodingError::invalid(format_args!(
1147 "invalid VtxoPolicy type byte: {v:#x}",
1148 ))),
1149 }
1150}
1151
1152impl ProtocolEncoding for ServerVtxoPolicy {
1153 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1154 match self {
1155 Self::User(p) => p.encode(w)?,
1156 Self::ServerOwned => {
1157 w.emit_u8(VTXO_POLICY_SERVER_OWNED)?;
1158 },
1159 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
1160 w.emit_u8(VTXO_POLICY_CHECKPOINT)?;
1161 user_pubkey.encode(w)?;
1162 },
1163 Self::Expiry(ExpiryVtxoPolicy { internal_key }) => {
1164 w.emit_u8(VTXO_POLICY_EXPIRY)?;
1165 internal_key.encode(w)?;
1166 },
1167 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }) => {
1168 w.emit_u8(VTXO_POLICY_HARK_LEAF)?;
1169 user_pubkey.encode(w)?;
1170 unlock_hash.encode(w)?;
1171 },
1172 Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, unlock_hash }) => {
1173 w.emit_u8(VTXO_POLICY_HARK_LEAF_V0)?;
1174 user_pubkey.encode(w)?;
1175 unlock_hash.encode(w)?;
1176 },
1177 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }) => {
1178 w.emit_u8(VTXO_POLICY_HARK_FORFEIT)?;
1179 user_pubkey.encode(w)?;
1180 unlock_hash.encode(w)?;
1181 },
1182 Self::HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy { user_pubkey, unlock_hash }) => {
1183 w.emit_u8(VTXO_POLICY_HARK_FORFEIT_V0)?;
1184 user_pubkey.encode(w)?;
1185 unlock_hash.encode(w)?;
1186 },
1187 }
1188 Ok(())
1189 }
1190
1191 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1192 let type_byte = r.read_u8()?;
1193 match type_byte {
1194 VTXO_POLICY_PUBKEY | VTXO_POLICY_SERVER_HTLC_SEND | VTXO_POLICY_SERVER_HTLC_RECV
1195 | VTXO_POLICY_SERVER_HTLC_SEND_V0 | VTXO_POLICY_SERVER_HTLC_RECV_V0 =>
1196 {
1197 Ok(Self::User(decode_vtxo_policy(type_byte, r)?))
1198 },
1199 VTXO_POLICY_SERVER_OWNED => Ok(Self::ServerOwned),
1200 VTXO_POLICY_CHECKPOINT => {
1201 let user_pubkey = PublicKey::decode(r)?;
1202 Ok(Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }))
1203 },
1204 VTXO_POLICY_EXPIRY => {
1205 let internal_key = XOnlyPublicKey::decode(r)?;
1206 Ok(Self::Expiry(ExpiryVtxoPolicy { internal_key }))
1207 },
1208 VTXO_POLICY_HARK_LEAF => {
1209 let user_pubkey = PublicKey::decode(r)?;
1210 let unlock_hash = sha256::Hash::decode(r)?;
1211 Ok(Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }))
1212 },
1213 VTXO_POLICY_HARK_LEAF_V0 => {
1214 let user_pubkey = PublicKey::decode(r)?;
1215 let unlock_hash = sha256::Hash::decode(r)?;
1216 Ok(Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, unlock_hash }))
1217 },
1218 VTXO_POLICY_HARK_FORFEIT => {
1219 let user_pubkey = PublicKey::decode(r)?;
1220 let unlock_hash = sha256::Hash::decode(r)?;
1221 Ok(Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }))
1222 },
1223 VTXO_POLICY_HARK_FORFEIT_V0 => {
1224 let user_pubkey = PublicKey::decode(r)?;
1225 let unlock_hash = sha256::Hash::decode(r)?;
1226 Ok(Self::HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy { user_pubkey, unlock_hash }))
1227 },
1228 v => Err(ProtocolDecodingError::invalid(format_args!(
1229 "invalid ServerVtxoPolicy type byte: {v:#x}",
1230 ))),
1231 }
1232 }
1233}
1234
1235const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
1237
1238const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
1240
1241const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED_V0: u8 = 3;
1243
1244const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 4;
1246
1247impl ProtocolEncoding for GenesisTransition {
1248 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1249 match self {
1250 Self::Cosigned(t) => {
1251 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
1252 LengthPrefixedVector::new(&t.pubkeys).encode(w)?;
1253 t.signature.encode(w)?;
1254 },
1255 Self::HashLockedCosigned(t) => {
1256 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
1257 t.user_pubkey.encode(w)?;
1258 t.signature.encode(w)?;
1259 match t.unlock {
1260 MaybePreimage::Preimage(p) => {
1261 w.emit_u8(0)?;
1262 w.emit_slice(&p[..])?;
1263 },
1264 MaybePreimage::Hash(h) => {
1265 w.emit_u8(1)?;
1266 w.emit_slice(&h[..])?;
1267 },
1268 }
1269 },
1270 Self::HashLockedCosigned_v0(t) => {
1271 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED_V0)?;
1272 t.user_pubkey.encode(w)?;
1273 t.signature.encode(w)?;
1274 match t.unlock {
1275 MaybePreimage::Preimage(p) => {
1276 w.emit_u8(0)?;
1277 w.emit_slice(&p[..])?;
1278 },
1279 MaybePreimage::Hash(h) => {
1280 w.emit_u8(1)?;
1281 w.emit_slice(&h[..])?;
1282 },
1283 }
1284 },
1285 Self::Arkoor(t) => {
1286 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
1287 LengthPrefixedVector::new(&t.client_cosigners).encode(w)?;
1288 t.tap_tweak.encode(w)?;
1289 t.signature.encode(w)?;
1290 },
1291 }
1292 Ok(())
1293 }
1294
1295 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1296 match r.read_u8()? {
1297 GENESIS_TRANSITION_TYPE_COSIGNED => {
1298 let pubkeys: Vec<PublicKey> = LengthPrefixedVector::decode(r)?.into_inner();
1299 if pubkeys.is_empty() {
1300 return Err(ProtocolDecodingError::invalid(
1301 "cosigned genesis transition with empty pubkey list",
1302 ));
1303 }
1304 let signature = Option::<schnorr::Signature>::decode(r)?;
1305 Ok(Self::new_cosigned(pubkeys, signature))
1306 },
1307 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
1308 let user_pubkey = PublicKey::decode(r)?;
1309 let signature = Option::<schnorr::Signature>::decode(r)?;
1310 let unlock = match r.read_u8()? {
1311 0 => MaybePreimage::Preimage(r.read_byte_array()?),
1312 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
1313 v => return Err(ProtocolDecodingError::invalid(format_args!(
1314 "invalid MaybePreimage type byte: {v:#x}",
1315 ))),
1316 };
1317 Ok(Self::HashLockedCosigned(genesis::HashLockedCosignedGenesis {
1318 user_pubkey, signature, unlock,
1319 }))
1320 },
1321 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED_V0 => {
1322 let user_pubkey = PublicKey::decode(r)?;
1323 let signature = Option::<schnorr::Signature>::decode(r)?;
1324 let unlock = match r.read_u8()? {
1325 0 => MaybePreimage::Preimage(r.read_byte_array()?),
1326 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
1327 v => return Err(ProtocolDecodingError::invalid(format_args!(
1328 "invalid MaybePreimage type byte: {v:#x}",
1329 ))),
1330 };
1331 Ok(Self::HashLockedCosigned_v0(genesis::HashLockedCosignedGenesis_v0 {
1332 user_pubkey, signature, unlock,
1333 }))
1334 },
1335 GENESIS_TRANSITION_TYPE_ARKOOR => {
1336 let cosigners = LengthPrefixedVector::decode(r)?.into_inner();
1337 let taptweak = TapTweakHash::decode(r)?;
1338 if bitcoin::secp256k1::Scalar::from_be_bytes(taptweak.to_byte_array()).is_err() {
1339 return Err(ProtocolDecodingError::invalid(
1340 "arkoor genesis tap tweak is not a valid secp256k1 scalar",
1341 ));
1342 }
1343 let signature = Option::<schnorr::Signature>::decode(r)?;
1344 Ok(Self::new_arkoor(cosigners, taptweak, signature))
1345 },
1346 v => Err(ProtocolDecodingError::invalid(format_args!(
1347 "invalid GenesisTransistion type byte: {v:#x}",
1348 ))),
1349 }
1350 }
1351}
1352
1353trait VtxoVersionedEncoding: Sized {
1356 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, version: u16) -> Result<(), io::Error>;
1357
1358 fn decode<R: io::Read + ?Sized>(
1359 r: &mut R,
1360 version: u16,
1361 ) -> Result<Self, ProtocolDecodingError>;
1362}
1363
1364impl VtxoVersionedEncoding for Bare {
1365 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1366 w.emit_compact_size(0u64)?;
1367 Ok(())
1368 }
1369
1370 fn decode<R: io::Read + ?Sized>(
1371 r: &mut R,
1372 version: u16,
1373 ) -> Result<Self, ProtocolDecodingError> {
1374 let _full = Full::decode(r, version)?;
1377
1378 Ok(Bare)
1379 }
1380}
1381
1382impl VtxoVersionedEncoding for Full {
1383 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1384 w.emit_compact_size(self.items.len() as u64)?;
1385 for item in &self.items {
1386 item.transition.encode(w)?;
1387 let nb_outputs = item.other_outputs.len().saturating_add(1);
1388 w.emit_u8(nb_outputs.try_into()
1389 .map_err(|_| io::Error::other("too many outputs on genesis transaction"))?)?;
1390 w.emit_u8(item.output_idx)?;
1391 for txout in &item.other_outputs {
1392 txout.encode(w)?;
1393 }
1394 w.emit_u64(item.fee_amount.to_sat())?;
1395 }
1396 Ok(())
1397 }
1398
1399 fn decode<R: io::Read + ?Sized>(
1400 r: &mut R,
1401 version: u16,
1402 ) -> Result<Self, ProtocolDecodingError> {
1403 let nb_genesis_items = r.read_compact_size()? as usize;
1404 OversizedVectorError::check::<GenesisItem>(nb_genesis_items)?;
1405 let mut genesis = Vec::with_capacity(nb_genesis_items);
1406 for _ in 0..nb_genesis_items {
1407 let transition = GenesisTransition::decode(r)?;
1408 let nb_outputs = r.read_u8()? as usize;
1409 let output_idx = r.read_u8()?;
1410 let nb_other = nb_outputs.checked_sub(1)
1411 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1412 if output_idx as usize >= nb_outputs {
1418 return Err(ProtocolDecodingError::invalid(
1419 "genesis item output_idx out of range (>= nb_outputs)",
1420 ));
1421 }
1422 let mut other_outputs = Vec::with_capacity(nb_other);
1423 for _ in 0..nb_other {
1424 other_outputs.push(TxOut::decode(r)?);
1425 }
1426 let fee_amount = if version == VTXO_NO_FEE_AMOUNT_VERSION {
1427 Amount::ZERO
1429 } else {
1430 Amount::from_sat(r.read_u64()?)
1431 };
1432 genesis.push(GenesisItem { transition, output_idx, other_outputs, fee_amount });
1433 }
1434 Ok(Full { items: genesis })
1435 }
1436}
1437
1438impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<Bare, P> {
1439 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1440 vtxo_encode_inner(&self, w)
1441 }
1442
1443 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1444 Ok(vtxo_decode_inner(r)?.0)
1445 }
1446}
1447
1448impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<Full, P> {
1449 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1450 vtxo_encode_inner(&self, w)
1451 }
1452
1453 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1454 let (vtxo, _) = vtxo_decode_inner::<Full, P, _>(r)?;
1457 if vtxo.point() != vtxo.chain_anchor() {
1458 if vtxo.genesis.items.is_empty() {
1459 return Err(ProtocolDecodingError::invalid_err(
1460 VtxoValidationError::MissingGenesisItems,
1461 format!("VTXO {} has no genesis item data", vtxo.id()),
1462 ));
1463 }
1464 } else {
1465 if !vtxo.genesis.items.is_empty() {
1466 return Err(ProtocolDecodingError::invalid_err(
1467 VtxoValidationError::UnexpectedGenesisItems,
1468 format!("decoded genesis item data when there shouldn't be any for VTXO {}", vtxo.id()),
1469 ));
1470 }
1471 }
1472 Ok(vtxo)
1473 }
1474}
1475
1476fn vtxo_encode_inner<G, P, W>(vtxo: &Vtxo<G, P>, w: &mut W) -> Result<(), io::Error>
1477where
1478 G: VtxoVersionedEncoding,
1479 P: Policy + ProtocolEncoding,
1480 W: io::Write + ?Sized,
1481{
1482 let version = VTXO_ENCODING_VERSION;
1483 w.emit_u16(version)?;
1484 w.emit_u64(vtxo.amount.to_sat())?;
1485 w.emit_u32(vtxo.expiry_height)?;
1486 vtxo.server_pubkey.encode(w)?;
1487 w.emit_u16(vtxo.exit_delta)?;
1488 vtxo.anchor_point.encode(w)?;
1489
1490 vtxo.genesis.encode(w, version)?;
1491
1492 vtxo.policy.encode(w)?;
1493 vtxo.point.encode(w)?;
1494 Ok(())
1495}
1496
1497fn vtxo_decode_inner<G, P, R>(r: &mut R) -> Result<(Vtxo<G, P>, u16), ProtocolDecodingError>
1498where
1499 G: VtxoVersionedEncoding,
1500 P: Policy + ProtocolEncoding,
1501 R: io::Read + ?Sized,
1502{
1503 let version = r.read_u16()?;
1504 if version != VTXO_ENCODING_VERSION && version != VTXO_NO_FEE_AMOUNT_VERSION {
1505 return Err(ProtocolDecodingError::invalid(format_args!(
1506 "invalid Vtxo encoding version byte: {version:#x}",
1507 )));
1508 }
1509
1510 let amount = Amount::from_sat(r.read_u64()?);
1511 let expiry_height = check_block_height(r.read_u32()?)
1512 .map_err(|e| ProtocolDecodingError::invalid_err(e, "expiry_height"))?;
1513 let server_pubkey = PublicKey::decode(r)?;
1514 let exit_delta = check_block_delta(r.read_u16()?)
1515 .map_err(|e| ProtocolDecodingError::invalid_err(e, "exit_delta"))?;
1516 let anchor_point = OutPoint::decode(r)?;
1517
1518 let genesis = VtxoVersionedEncoding::decode(r, version)?;
1519
1520 let policy = P::decode(r)?;
1521 let point = OutPoint::decode(r)?;
1522 let vtxo = Vtxo {
1523 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy, point,
1524 };
1525 Ok((vtxo, version))
1526}
1527
1528#[cfg(test)]
1529mod test {
1530 use bitcoin::consensus::encode::serialize_hex;
1531 use bitcoin::hex::DisplayHex;
1532
1533 use crate::test_util::encoding_roundtrip;
1534 use crate::test_util::dummy::{DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1535 use crate::test_util::vectors::{
1536 generate_vtxo_vectors, VTXO_VECTORS, VTXO_NO_FEE_AMOUNT_VERSION_HEXES,
1537 };
1538
1539 use super::*;
1540
1541 #[test]
1542 fn test_generate_vtxo_vectors() {
1543 let g = generate_vtxo_vectors();
1544 println!("\n\ngenerated:");
1547 println!(" anchor_tx: {}", serialize_hex(&g.anchor_tx));
1548 println!(" board_vtxo: {}", g.board_vtxo.serialize().as_hex().to_string());
1549 println!(" arkoor_htlc_out_vtxo: {}", g.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1550 println!(" arkoor2_vtxo: {}", g.arkoor2_vtxo.serialize().as_hex().to_string());
1551 println!(" round_tx: {}", serialize_hex(&g.round_tx));
1552 println!(" round1_vtxo: {}", g.round1_vtxo.serialize().as_hex().to_string());
1553 println!(" round2_vtxo: {}", g.round2_vtxo.serialize().as_hex().to_string());
1554 println!(" arkoor3_vtxo: {}", g.arkoor3_vtxo.serialize().as_hex().to_string());
1555
1556
1557 let v = &*VTXO_VECTORS;
1558 println!("\n\nstatic:");
1559 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1560 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1561 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1562 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1563 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1564 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1565 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1566 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1567
1568 assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1569 assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1570 assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1571 assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1572 assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1573 assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1574 assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1575 assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1576
1577 assert_eq!(g, *v);
1579 }
1580
1581 #[test]
1582 fn test_vtxo_no_fee_amount_version_upgrade() {
1583 let hexes = &*VTXO_NO_FEE_AMOUNT_VERSION_HEXES;
1584 let v = hexes.deserialize_test_vectors();
1585
1586 v.validate_vtxos();
1588
1589 let board_hex = v.board_vtxo.serialize().as_hex().to_string();
1591 let arkoor_htlc_out_vtxo_hex = v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string();
1592 let arkoor2_vtxo_hex = v.arkoor2_vtxo.serialize().as_hex().to_string();
1593 let round1_vtxo_hex = v.round1_vtxo.serialize().as_hex().to_string();
1594 let round2_vtxo_hex = v.round2_vtxo.serialize().as_hex().to_string();
1595 let arkoor3_vtxo_hex = v.arkoor3_vtxo.serialize().as_hex().to_string();
1596 assert_ne!(board_hex, hexes.board_vtxo);
1597 assert_ne!(arkoor_htlc_out_vtxo_hex, hexes.arkoor_htlc_out_vtxo);
1598 assert_ne!(arkoor2_vtxo_hex, hexes.arkoor2_vtxo);
1599 assert_ne!(round1_vtxo_hex, hexes.round1_vtxo);
1600 assert_ne!(round2_vtxo_hex, hexes.round2_vtxo);
1601 assert_ne!(arkoor3_vtxo_hex, hexes.arkoor3_vtxo);
1602
1603 let board_vtxo = Vtxo::<Full>::deserialize_hex(&board_hex).unwrap();
1609 assert_eq!(board_vtxo.serialize().as_hex().to_string(), board_hex);
1610 let arkoor_htlc_out_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor_htlc_out_vtxo_hex).unwrap();
1611 assert_eq!(arkoor_htlc_out_vtxo.serialize().as_hex().to_string(), arkoor_htlc_out_vtxo_hex);
1612 let arkoor2_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor2_vtxo_hex).unwrap();
1613 assert_eq!(arkoor2_vtxo.serialize().as_hex().to_string(), arkoor2_vtxo_hex);
1614 let round1_vtxo = Vtxo::<Full>::deserialize_hex(&round1_vtxo_hex).unwrap();
1615 assert_eq!(round1_vtxo.serialize().as_hex().to_string(), round1_vtxo_hex);
1616 let round2_vtxo = Vtxo::<Full>::deserialize_hex(&round2_vtxo_hex).unwrap();
1617 assert_eq!(round2_vtxo.serialize().as_hex().to_string(), round2_vtxo_hex);
1618 let arkoor3_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor3_vtxo_hex).unwrap();
1619 assert_eq!(arkoor3_vtxo.serialize().as_hex().to_string(), arkoor3_vtxo_hex);
1620 }
1621
1622 #[test]
1623 fn exit_depth() {
1624 let vtxos = &*VTXO_VECTORS;
1625 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1627
1628 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1630
1631 assert_eq!(
1633 vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1634 1 + 1 + 1 ,
1635 );
1636 assert_eq!(
1637 vtxos.arkoor2_vtxo.exit_depth(),
1638 1 + 2 + 2 ,
1639 );
1640 assert_eq!(
1641 vtxos.arkoor3_vtxo.exit_depth(),
1642 3 + 1 + 1 ,
1643 );
1644 }
1645
1646 #[test]
1647 fn ancestor_ids() {
1648 let v = &*VTXO_VECTORS;
1649
1650 assert_eq!(v.board_vtxo.exit_depth(), 1, "board is a single-tx chain anchor");
1653 assert!(v.board_vtxo.ancestor_ids().is_empty(),
1654 "a chain-anchor VTXO has no ancestors");
1655
1656 for vtxo in [
1661 &v.board_vtxo, &v.arkoor_htlc_out_vtxo, &v.arkoor2_vtxo,
1662 &v.round1_vtxo, &v.round2_vtxo, &v.arkoor3_vtxo,
1663 ] {
1664 let ancestors = vtxo.ancestor_ids();
1665
1666 assert_eq!(ancestors.len(), vtxo.exit_depth() as usize - 1,
1667 "ancestor_ids is the whole genesis chain except the VTXO itself");
1668 assert!(!ancestors.contains(&vtxo.id()),
1669 "ancestor_ids must never contain the VTXO's own id");
1670
1671 let last = vtxo.transactions().last().expect("a VTXO has >=1 transaction");
1672 let last_id: VtxoId = OutPoint::new(last.tx.compute_txid(), last.output_idx as u32).into();
1673 assert_eq!(last_id, vtxo.id(),
1674 "the final genesis tx must produce the VTXO itself");
1675 }
1676
1677 assert!(v.arkoor_htlc_out_vtxo.ancestor_ids().contains(&v.board_vtxo.id()),
1683 "a single-hop arkoor lists the board it spent as an ancestor");
1684
1685 let anc2 = v.arkoor2_vtxo.ancestor_ids();
1687 let board_pos = anc2.iter().position(|id| *id == v.board_vtxo.id())
1688 .expect("arkoor2 must list the board ancestor");
1689 let arkoor1_pos = anc2.iter().position(|id| *id == v.arkoor_htlc_out_vtxo.id())
1690 .expect("arkoor2 must list the arkoor1 ancestor");
1691 assert!(board_pos < arkoor1_pos,
1692 "ancestors are ordered from chain anchor down to the immediate parent");
1693
1694 let mut parent_chain = v.arkoor_htlc_out_vtxo.ancestor_ids();
1697 parent_chain.push(v.arkoor_htlc_out_vtxo.id());
1698 assert!(v.arkoor2_vtxo.ancestor_ids().starts_with(&parent_chain),
1699 "a child's ancestors extend its parent's full genesis chain");
1700
1701 assert!(v.arkoor3_vtxo.ancestor_ids().contains(&v.round2_vtxo.id()),
1703 "an arkoor spending a round output lists it as an ancestor");
1704 }
1705
1706 #[test]
1707 fn test_split_genesis_roundtrip() {
1708 fn check<P: Policy + ProtocolEncoding + Clone + std::fmt::Debug>(
1712 vtxo: &Vtxo<Full, P>,
1713 ) where
1714 Vtxo<Full, P>: PartialEq,
1715 {
1716 let original = vtxo.serialize();
1717
1718 let bare_bytes = vtxo.to_bare().serialize();
1719 let genesis_bytes = vtxo.serialize_genesis();
1720
1721 let bare = Vtxo::<Bare, P>::deserialize(&bare_bytes)
1722 .expect("bare deserialize");
1723 let genesis = Full::decode(&mut &genesis_bytes[..], VTXO_ENCODING_VERSION)
1724 .expect("decode_genesis");
1725 let reassembled = bare.with_genesis(genesis)
1726 .expect("reassemble");
1727
1728 assert_eq!(*vtxo, reassembled, "reassembled vtxo differs from original");
1729 assert_eq!(reassembled.serialize(), original,
1730 "reassembled bytes differ from original");
1731 }
1732
1733 let v = &*VTXO_VECTORS;
1734 check(&v.board_vtxo);
1735 check(&v.arkoor_htlc_out_vtxo);
1736 check(&v.arkoor2_vtxo);
1737 check(&v.round1_vtxo);
1738 check(&v.round2_vtxo);
1739 check(&v.arkoor3_vtxo);
1740
1741 let big: Vtxo<Full> = Vtxo {
1743 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1744 amount: Amount::from_sat(10_000),
1745 expiry_height: 101_010,
1746 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1747 exit_delta: 2016,
1748 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1749 genesis: Full {
1750 items: vec![GenesisItem {
1751 transition: GenesisTransition::new_cosigned(
1752 vec![DUMMY_USER_KEY.public_key()],
1753 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1754 ),
1755 output_idx: 0,
1756 other_outputs: vec![],
1757 fee_amount: Amount::ZERO,
1758 }; 257],
1759 },
1760 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1761 };
1762 check(&big);
1763 }
1764
1765 #[test]
1766 fn test_genesis_length_257() {
1767 let vtxo: Vtxo<Full> = Vtxo {
1768 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1769 amount: Amount::from_sat(10_000),
1770 expiry_height: 101_010,
1771 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1772 exit_delta: 2016,
1773 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1774 genesis: Full {
1775 items: vec![GenesisItem {
1776 transition: GenesisTransition::new_cosigned(
1777 vec![DUMMY_USER_KEY.public_key()],
1778 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1779 ),
1780 output_idx: 0,
1781 other_outputs: vec![],
1782 fee_amount: Amount::ZERO,
1783 }; 257],
1784 },
1785 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1786 };
1787 assert_eq!(vtxo.genesis.items.len(), 257);
1788 encoding_roundtrip(&vtxo);
1789 }
1790
1791 #[test]
1792 fn test_genesis_decoding() {
1793 fn check<P: Policy + ProtocolEncoding + Clone + std::fmt::Debug>(
1796 vtxo: &Vtxo<Full, P>,
1797 ) where
1798 Vtxo<Full, P>: PartialEq,
1799 {
1800 let full_bytes = vtxo.serialize();
1801 let bare_bytes = vtxo.as_bare_vtxo().unwrap().serialize();
1802
1803 let full_to_full = Vtxo::<Full>::deserialize(&full_bytes).expect("works");
1809 let full_to_bare = Vtxo::<Bare>::deserialize(&full_bytes).expect("works");
1810 let bare_to_bare = Vtxo::<Bare>::deserialize(&bare_bytes).expect("works");
1811 Vtxo::<Full>::deserialize(&bare_bytes).expect_err("bare to full fails");
1812
1813 assert_eq!(full_to_full.serialize(), full_bytes);
1814 assert_eq!(full_to_bare.serialize(), bare_bytes);
1815 assert_eq!(bare_to_bare.serialize(), bare_bytes);
1816 }
1817
1818 let v = &*VTXO_VECTORS;
1819 check(&v.board_vtxo);
1820 check(&v.arkoor_htlc_out_vtxo);
1821 check(&v.arkoor2_vtxo);
1822 check(&v.round1_vtxo);
1823 check(&v.round2_vtxo);
1824 check(&v.arkoor3_vtxo);
1825 }
1826
1827 fn dummy_vtxo_with(amount: Amount, other_outputs: Vec<TxOut>) -> Vtxo<Full> {
1832 Vtxo {
1833 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1834 amount,
1835 expiry_height: 101_010,
1836 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1837 exit_delta: 2016,
1838 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1839 genesis: Full {
1840 items: vec![GenesisItem {
1841 transition: GenesisTransition::new_cosigned(
1842 vec![DUMMY_USER_KEY.public_key()],
1843 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1844 ),
1845 output_idx: 0,
1846 other_outputs,
1847 fee_amount: Amount::ZERO,
1848 }],
1849 },
1850 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1851 }
1852 }
1853
1854 fn dummy_p2tr_script() -> ScriptBuf {
1856 VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key())
1857 .script_pubkey(DUMMY_SERVER_KEY.public_key(), 2016, 101_010)
1858 }
1859
1860 #[test]
1861 fn check_standard_accepts_real_vtxos() {
1862 let v = &*VTXO_VECTORS;
1865 assert_eq!(v.board_vtxo.check_standard(), Ok(()));
1866 assert_eq!(v.arkoor_htlc_out_vtxo.check_standard(), Ok(()));
1867 assert_eq!(v.arkoor2_vtxo.check_standard(), Ok(()));
1868 assert_eq!(v.round1_vtxo.check_standard(), Ok(()));
1869 assert_eq!(v.round2_vtxo.check_standard(), Ok(()));
1870 assert_eq!(v.arkoor3_vtxo.check_standard(), Ok(()));
1871 assert!(v.board_vtxo.is_standard());
1872 }
1873
1874 #[test]
1875 fn check_standard_dusty_own_output() {
1876 let vtxo = dummy_vtxo_with(Amount::from_sat(100), vec![]);
1879 assert_eq!(vtxo.check_standard(), Err(VtxoStandardnessError::Dusty));
1880 assert!(!vtxo.is_standard());
1881 }
1882
1883 #[test]
1884 fn check_standard_dust_sibling() {
1885 let dust = TxOut {
1889 value: Amount::from_sat(100),
1890 script_pubkey: dummy_p2tr_script(),
1891 };
1892 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![dust]);
1893 assert_eq!(
1894 vtxo.check_standard(),
1895 Err(VtxoStandardnessError::DustSibling {
1896 item_idx: 0,
1897 item_count: 1,
1898 output_idx: 0,
1899 }),
1900 );
1901 }
1902
1903 #[test]
1904 fn check_standard_script_sibling() {
1905 let bad = TxOut {
1909 value: Amount::from_sat(10_000),
1910 script_pubkey: ScriptBuf::from_bytes(vec![0xab, 0xcd]),
1911 };
1912 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![bad]);
1913 assert_eq!(
1914 vtxo.check_standard(),
1915 Err(VtxoStandardnessError::ScriptSibling {
1916 item_idx: 0,
1917 item_count: 1,
1918 output_idx: 0,
1919 }),
1920 );
1921 }
1922
1923 #[test]
1924 fn check_standard_dust_takes_priority_over_later_script_sibling() {
1925 let dust = TxOut {
1928 value: Amount::from_sat(100),
1929 script_pubkey: dummy_p2tr_script(),
1930 };
1931 let bad = TxOut {
1932 value: Amount::from_sat(10_000),
1933 script_pubkey: ScriptBuf::from_bytes(vec![0xab, 0xcd]),
1934 };
1935 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![dust, bad]);
1936 assert_eq!(
1937 vtxo.check_standard(),
1938 Err(VtxoStandardnessError::DustSibling {
1939 item_idx: 0,
1940 item_count: 1,
1941 output_idx: 0,
1942 }),
1943 );
1944 }
1945
1946 mod genesis_transition_encoding {
1947 use bitcoin::hashes::{sha256, Hash};
1948 use bitcoin::secp256k1::{Keypair, PublicKey};
1949 use bitcoin::taproot::TapTweakHash;
1950 use std::str::FromStr;
1951
1952 use crate::encode::ProtocolEncoding;
1953 use crate::test_util::encoding_roundtrip;
1954 use super::genesis::{
1955 GenesisTransition, CosignedGenesis, HashLockedCosignedGenesis_v0, ArkoorGenesis,
1956 };
1957 use super::MaybePreimage;
1958
1959 fn test_pubkey() -> PublicKey {
1960 Keypair::from_str(
1961 "916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92"
1962 ).unwrap().public_key()
1963 }
1964
1965 fn test_signature() -> bitcoin::secp256k1::schnorr::Signature {
1966 "cc8b93e9f6fbc2506bb85ae8bbb530b178daac49704f5ce2e3ab69c266fd5932\
1967 0b28d028eef212e3b9fdc42cfd2e0760a0359d3ea7d2e9e8cfe2040e3f1b71ea"
1968 .parse().unwrap()
1969 }
1970
1971 #[test]
1972 fn cosigned_with_signature() {
1973 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1974 pubkeys: vec![test_pubkey()],
1975 signature: Some(test_signature()),
1976 });
1977 encoding_roundtrip(&transition);
1978 }
1979
1980 #[test]
1981 fn cosigned_without_signature() {
1982 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1983 pubkeys: vec![test_pubkey()],
1984 signature: None,
1985 });
1986 encoding_roundtrip(&transition);
1987 }
1988
1989 #[test]
1990 fn cosigned_empty_pubkeys_rejected() {
1991 let mut buf = Vec::new();
1992 buf.push(super::GENESIS_TRANSITION_TYPE_COSIGNED);
1993 buf.push(0x00); buf.push(0x00); let err = GenesisTransition::deserialize(&mut buf.as_slice())
1996 .expect_err("empty pubkeys must be rejected");
1997 assert!(format!("{err}").contains("empty pubkey list"), "got: {err}");
1998 }
1999
2000 #[test]
2001 fn cosigned_multiple_pubkeys() {
2002 let pk1 = test_pubkey();
2003 let pk2 = Keypair::from_str(
2004 "fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879"
2005 ).unwrap().public_key();
2006
2007 let transition = GenesisTransition::Cosigned(CosignedGenesis {
2008 pubkeys: vec![pk1, pk2],
2009 signature: Some(test_signature()),
2010 });
2011 encoding_roundtrip(&transition);
2012 }
2013
2014 #[test]
2015 fn hash_locked_cosigned_with_preimage() {
2016 let preimage = [0x42u8; 32];
2017 let transition = GenesisTransition::HashLockedCosigned_v0(HashLockedCosignedGenesis_v0 {
2018 user_pubkey: test_pubkey(),
2019 signature: Some(test_signature()),
2020 unlock: MaybePreimage::Preimage(preimage),
2021 });
2022 encoding_roundtrip(&transition);
2023 }
2024
2025 #[test]
2026 fn hash_locked_cosigned_with_hash() {
2027 let hash = sha256::Hash::hash(b"test preimage");
2028 let transition = GenesisTransition::HashLockedCosigned_v0(HashLockedCosignedGenesis_v0 {
2029 user_pubkey: test_pubkey(),
2030 signature: Some(test_signature()),
2031 unlock: MaybePreimage::Hash(hash),
2032 });
2033 encoding_roundtrip(&transition);
2034 }
2035
2036 #[test]
2037 fn hash_locked_cosigned_without_signature() {
2038 let preimage = [0x42u8; 32];
2039 let transition = GenesisTransition::HashLockedCosigned_v0(HashLockedCosignedGenesis_v0 {
2040 user_pubkey: test_pubkey(),
2041 signature: None,
2042 unlock: MaybePreimage::Preimage(preimage),
2043 });
2044 encoding_roundtrip(&transition);
2045 }
2046
2047 #[test]
2048 fn arkoor_with_signature() {
2049 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
2050 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
2051 client_cosigners: vec![test_pubkey()],
2052 tap_tweak,
2053 signature: Some(test_signature()),
2054 });
2055 encoding_roundtrip(&transition);
2056 }
2057
2058 #[test]
2059 fn arkoor_without_signature() {
2060 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
2061 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
2062 client_cosigners: vec![test_pubkey()],
2063 tap_tweak,
2064 signature: None,
2065 });
2066 encoding_roundtrip(&transition);
2067 }
2068
2069 #[test]
2070 fn arkoor_out_of_range_tweak_rejected() {
2071 let valid = GenesisTransition::Arkoor(ArkoorGenesis {
2075 client_cosigners: vec![test_pubkey()],
2076 tap_tweak: TapTweakHash::from_slice(&[0xabu8; 32]).unwrap(),
2077 signature: None,
2078 });
2079 let mut bytes = valid.serialize();
2080 let n = bytes.len();
2083 for b in &mut bytes[n - 96 .. n - 64] {
2084 *b = 0xff;
2085 }
2086 let err = GenesisTransition::deserialize(&mut bytes.as_slice())
2087 .expect_err("out-of-range tap tweak must be rejected");
2088 assert!(
2089 format!("{err}").contains("not a valid secp256k1 scalar"),
2090 "got: {err}",
2091 );
2092 }
2093 }
2094}