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 let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key);
882 assert!(ctx.finalize(self, cosign));
883 assert!(self.provide_unlock_preimage(unlock_preimage));
885 }
886}
887
888impl<G> Vtxo<G, ServerVtxoPolicy> {
889 pub fn try_into_user_vtxo(self) -> Result<Vtxo<G, VtxoPolicy>, ServerVtxo<G>> {
893 if let Some(p) = self.policy.clone().into_user_policy() {
894 Ok(Vtxo {
895 policy: p,
896 amount: self.amount,
897 expiry_height: self.expiry_height,
898 server_pubkey: self.server_pubkey,
899 exit_delta: self.exit_delta,
900 anchor_point: self.anchor_point,
901 genesis: self.genesis,
902 point: self.point,
903 })
904 } else {
905 Err(self)
906 }
907 }
908}
909
910impl<G, P: Policy> PartialEq for Vtxo<G, P> {
911 fn eq(&self, other: &Self) -> bool {
912 PartialEq::eq(&self.id(), &other.id())
913 }
914}
915
916impl<G, P: Policy> Eq for Vtxo<G, P> {}
917
918impl<G, P: Policy> PartialOrd for Vtxo<G, P> {
919 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
920 PartialOrd::partial_cmp(&self.id(), &other.id())
921 }
922}
923
924impl<G, P: Policy> Ord for Vtxo<G, P> {
925 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
926 Ord::cmp(&self.id(), &other.id())
927 }
928}
929
930impl<G, P: Policy> std::hash::Hash for Vtxo<G, P> {
931 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
932 std::hash::Hash::hash(&self.id(), state)
933 }
934}
935
936impl<G, P: Policy> AsRef<Vtxo<G, P>> for Vtxo<G, P> {
937 fn as_ref(&self) -> &Vtxo<G, P> {
938 self
939 }
940}
941
942impl<G> From<Vtxo<G>> for ServerVtxo<G> {
943 fn from(vtxo: Vtxo<G>) -> ServerVtxo<G> {
944 ServerVtxo {
945 policy: vtxo.policy.into(),
946 amount: vtxo.amount,
947 expiry_height: vtxo.expiry_height,
948 server_pubkey: vtxo.server_pubkey,
949 exit_delta: vtxo.exit_delta,
950 anchor_point: vtxo.anchor_point,
951 genesis: vtxo.genesis,
952 point: vtxo.point,
953 }
954 }
955}
956
957pub trait VtxoRef<P: Policy = VtxoPolicy> {
959 fn vtxo_id(&self) -> VtxoId;
961
962 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { None }
964
965 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { None }
967
968 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> where Self: Sized;
970}
971
972impl<P: Policy> VtxoRef<P> for VtxoId {
973 fn vtxo_id(&self) -> VtxoId { *self }
974 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
975}
976
977impl<'a, P: Policy> VtxoRef<P> for &'a VtxoId {
978 fn vtxo_id(&self) -> VtxoId { **self }
979 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
980}
981
982impl<P: Policy> VtxoRef<P> for Vtxo<Bare, P> {
983 fn vtxo_id(&self) -> VtxoId { self.id() }
984 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(self)) }
985 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
986}
987
988impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Bare, P> {
989 fn vtxo_id(&self) -> VtxoId { self.id() }
990 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(*self)) }
991 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
992}
993
994impl<P: Policy> VtxoRef<P> for Vtxo<Full, P> {
995 fn vtxo_id(&self) -> VtxoId { self.id() }
996 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
997 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(self) }
998 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self) }
999}
1000
1001impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Full, P> {
1002 fn vtxo_id(&self) -> VtxoId { self.id() }
1003 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
1004 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(*self) }
1005 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self.clone()) }
1006}
1007
1008const VTXO_POLICY_PUBKEY: u8 = 0x00;
1010
1011const VTXO_POLICY_SERVER_HTLC_SEND_V0: u8 = 0x01;
1013
1014const VTXO_POLICY_SERVER_HTLC_RECV_V0: u8 = 0x02;
1016
1017const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
1019
1020const VTXO_POLICY_EXPIRY: u8 = 0x04;
1022
1023const VTXO_POLICY_HARK_LEAF_V0: u8 = 0x05;
1025
1026const VTXO_POLICY_HARK_FORFEIT_V0: u8 = 0x06;
1028
1029const VTXO_POLICY_SERVER_OWNED: u8 = 0x07;
1031
1032const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x08;
1034
1035const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x09;
1037
1038const VTXO_POLICY_HARK_LEAF: u8 = 0x0a;
1040
1041const VTXO_POLICY_HARK_FORFEIT: u8 = 0x0b;
1043
1044impl ProtocolEncoding for VtxoPolicy {
1045 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1046 match self {
1047 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
1048 w.emit_u8(VTXO_POLICY_PUBKEY)?;
1049 user_pubkey.encode(w)?;
1050 },
1051 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
1052 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
1053 user_pubkey.encode(w)?;
1054 payment_hash.to_sha256_hash().encode(w)?;
1055 w.emit_u32(*htlc_expiry)?;
1056 },
1057 Self::ServerHtlcSend_v0(ServerHtlcSend_v0_VtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
1058 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND_V0)?;
1059 user_pubkey.encode(w)?;
1060 payment_hash.to_sha256_hash().encode(w)?;
1061 w.emit_u32(*htlc_expiry)?;
1062 },
1063 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1064 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1065 }) => {
1066 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
1067 user_pubkey.encode(w)?;
1068 payment_hash.to_sha256_hash().encode(w)?;
1069 w.emit_u32(*htlc_expiry)?;
1070 w.emit_u16(*htlc_expiry_delta)?;
1071 },
1072 Self::ServerHtlcRecv_v0(ServerHtlcRecv_v0_VtxoPolicy {
1073 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1074 }) => {
1075 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV_V0)?;
1076 user_pubkey.encode(w)?;
1077 payment_hash.to_sha256_hash().encode(w)?;
1078 w.emit_u32(*htlc_expiry)?;
1079 w.emit_u16(*htlc_expiry_delta)?;
1080 },
1081 }
1082 Ok(())
1083 }
1084
1085 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1086 let type_byte = r.read_u8()?;
1087 decode_vtxo_policy(type_byte, r)
1088 }
1089}
1090
1091fn decode_vtxo_policy<R: io::Read + ?Sized>(
1095 type_byte: u8,
1096 r: &mut R,
1097) -> Result<VtxoPolicy, ProtocolDecodingError> {
1098 match type_byte {
1099 VTXO_POLICY_PUBKEY => {
1100 let user_pubkey = PublicKey::decode(r)?;
1101 Ok(VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
1102 },
1103 VTXO_POLICY_SERVER_HTLC_SEND => {
1104 let user_pubkey = PublicKey::decode(r)?;
1105 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1106 let htlc_expiry = check_block_height(r.read_u32()?)
1107 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1108 Ok(VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy {
1109 user_pubkey, payment_hash, htlc_expiry,
1110 }))
1111 },
1112 VTXO_POLICY_SERVER_HTLC_SEND_V0 => {
1113 let user_pubkey = PublicKey::decode(r)?;
1114 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1115 let htlc_expiry = check_block_height(r.read_u32()?)
1116 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1117 Ok(VtxoPolicy::ServerHtlcSend_v0(ServerHtlcSend_v0_VtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
1118 },
1119 VTXO_POLICY_SERVER_HTLC_RECV => {
1120 let user_pubkey = PublicKey::decode(r)?;
1121 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1122 let htlc_expiry = check_block_height(r.read_u32()?)
1123 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1124 let htlc_expiry_delta = check_block_delta(r.read_u16()?)
1125 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry_delta"))?;
1126 Ok(VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1127 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1128 }))
1129 },
1130 VTXO_POLICY_SERVER_HTLC_RECV_V0 => {
1131 let user_pubkey = PublicKey::decode(r)?;
1132 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1133 let htlc_expiry = check_block_height(r.read_u32()?)
1134 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1135 let htlc_expiry_delta = check_block_delta(r.read_u16()?)
1136 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry_delta"))?;
1137 Ok(VtxoPolicy::ServerHtlcRecv_v0(ServerHtlcRecv_v0_VtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
1138 },
1139
1140 v => Err(ProtocolDecodingError::invalid(format_args!(
1145 "invalid VtxoPolicy type byte: {v:#x}",
1146 ))),
1147 }
1148}
1149
1150impl ProtocolEncoding for ServerVtxoPolicy {
1151 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1152 match self {
1153 Self::User(p) => p.encode(w)?,
1154 Self::ServerOwned => {
1155 w.emit_u8(VTXO_POLICY_SERVER_OWNED)?;
1156 },
1157 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
1158 w.emit_u8(VTXO_POLICY_CHECKPOINT)?;
1159 user_pubkey.encode(w)?;
1160 },
1161 Self::Expiry(ExpiryVtxoPolicy { internal_key }) => {
1162 w.emit_u8(VTXO_POLICY_EXPIRY)?;
1163 internal_key.encode(w)?;
1164 },
1165 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }) => {
1166 w.emit_u8(VTXO_POLICY_HARK_LEAF)?;
1167 user_pubkey.encode(w)?;
1168 unlock_hash.encode(w)?;
1169 },
1170 Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, unlock_hash }) => {
1171 w.emit_u8(VTXO_POLICY_HARK_LEAF_V0)?;
1172 user_pubkey.encode(w)?;
1173 unlock_hash.encode(w)?;
1174 },
1175 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }) => {
1176 w.emit_u8(VTXO_POLICY_HARK_FORFEIT)?;
1177 user_pubkey.encode(w)?;
1178 unlock_hash.encode(w)?;
1179 },
1180 Self::HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy { user_pubkey, unlock_hash }) => {
1181 w.emit_u8(VTXO_POLICY_HARK_FORFEIT_V0)?;
1182 user_pubkey.encode(w)?;
1183 unlock_hash.encode(w)?;
1184 },
1185 }
1186 Ok(())
1187 }
1188
1189 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1190 let type_byte = r.read_u8()?;
1191 match type_byte {
1192 VTXO_POLICY_PUBKEY | VTXO_POLICY_SERVER_HTLC_SEND | VTXO_POLICY_SERVER_HTLC_RECV
1193 | VTXO_POLICY_SERVER_HTLC_SEND_V0 | VTXO_POLICY_SERVER_HTLC_RECV_V0 =>
1194 {
1195 Ok(Self::User(decode_vtxo_policy(type_byte, r)?))
1196 },
1197 VTXO_POLICY_SERVER_OWNED => Ok(Self::ServerOwned),
1198 VTXO_POLICY_CHECKPOINT => {
1199 let user_pubkey = PublicKey::decode(r)?;
1200 Ok(Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }))
1201 },
1202 VTXO_POLICY_EXPIRY => {
1203 let internal_key = XOnlyPublicKey::decode(r)?;
1204 Ok(Self::Expiry(ExpiryVtxoPolicy { internal_key }))
1205 },
1206 VTXO_POLICY_HARK_LEAF => {
1207 let user_pubkey = PublicKey::decode(r)?;
1208 let unlock_hash = sha256::Hash::decode(r)?;
1209 Ok(Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }))
1210 },
1211 VTXO_POLICY_HARK_LEAF_V0 => {
1212 let user_pubkey = PublicKey::decode(r)?;
1213 let unlock_hash = sha256::Hash::decode(r)?;
1214 Ok(Self::HarkLeaf_v0(HarkLeaf_v0_VtxoPolicy { user_pubkey, unlock_hash }))
1215 },
1216 VTXO_POLICY_HARK_FORFEIT => {
1217 let user_pubkey = PublicKey::decode(r)?;
1218 let unlock_hash = sha256::Hash::decode(r)?;
1219 Ok(Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }))
1220 },
1221 VTXO_POLICY_HARK_FORFEIT_V0 => {
1222 let user_pubkey = PublicKey::decode(r)?;
1223 let unlock_hash = sha256::Hash::decode(r)?;
1224 Ok(Self::HarkForfeit_v0(HarkForfeit_v0_VtxoPolicy { user_pubkey, unlock_hash }))
1225 },
1226 v => Err(ProtocolDecodingError::invalid(format_args!(
1227 "invalid ServerVtxoPolicy type byte: {v:#x}",
1228 ))),
1229 }
1230 }
1231}
1232
1233const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
1235
1236const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
1238
1239const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED_V0: u8 = 3;
1241
1242const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 4;
1244
1245impl ProtocolEncoding for GenesisTransition {
1246 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1247 match self {
1248 Self::Cosigned(t) => {
1249 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
1250 LengthPrefixedVector::new(&t.pubkeys).encode(w)?;
1251 t.signature.encode(w)?;
1252 },
1253 Self::HashLockedCosigned(t) => {
1254 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
1255 t.user_pubkey.encode(w)?;
1256 t.signature.encode(w)?;
1257 match t.unlock {
1258 MaybePreimage::Preimage(p) => {
1259 w.emit_u8(0)?;
1260 w.emit_slice(&p[..])?;
1261 },
1262 MaybePreimage::Hash(h) => {
1263 w.emit_u8(1)?;
1264 w.emit_slice(&h[..])?;
1265 },
1266 }
1267 },
1268 Self::HashLockedCosigned_v0(t) => {
1269 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED_V0)?;
1270 t.user_pubkey.encode(w)?;
1271 t.signature.encode(w)?;
1272 match t.unlock {
1273 MaybePreimage::Preimage(p) => {
1274 w.emit_u8(0)?;
1275 w.emit_slice(&p[..])?;
1276 },
1277 MaybePreimage::Hash(h) => {
1278 w.emit_u8(1)?;
1279 w.emit_slice(&h[..])?;
1280 },
1281 }
1282 },
1283 Self::Arkoor(t) => {
1284 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
1285 LengthPrefixedVector::new(&t.client_cosigners).encode(w)?;
1286 t.tap_tweak.encode(w)?;
1287 t.signature.encode(w)?;
1288 },
1289 }
1290 Ok(())
1291 }
1292
1293 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1294 match r.read_u8()? {
1295 GENESIS_TRANSITION_TYPE_COSIGNED => {
1296 let pubkeys: Vec<PublicKey> = LengthPrefixedVector::decode(r)?.into_inner();
1297 if pubkeys.is_empty() {
1298 return Err(ProtocolDecodingError::invalid(
1299 "cosigned genesis transition with empty pubkey list",
1300 ));
1301 }
1302 let signature = Option::<schnorr::Signature>::decode(r)?;
1303 Ok(Self::new_cosigned(pubkeys, signature))
1304 },
1305 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
1306 let user_pubkey = PublicKey::decode(r)?;
1307 let signature = Option::<schnorr::Signature>::decode(r)?;
1308 let unlock = match r.read_u8()? {
1309 0 => MaybePreimage::Preimage(r.read_byte_array()?),
1310 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
1311 v => return Err(ProtocolDecodingError::invalid(format_args!(
1312 "invalid MaybePreimage type byte: {v:#x}",
1313 ))),
1314 };
1315 Ok(Self::HashLockedCosigned(genesis::HashLockedCosignedGenesis {
1316 user_pubkey, signature, unlock,
1317 }))
1318 },
1319 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED_V0 => {
1320 let user_pubkey = PublicKey::decode(r)?;
1321 let signature = Option::<schnorr::Signature>::decode(r)?;
1322 let unlock = match r.read_u8()? {
1323 0 => MaybePreimage::Preimage(r.read_byte_array()?),
1324 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
1325 v => return Err(ProtocolDecodingError::invalid(format_args!(
1326 "invalid MaybePreimage type byte: {v:#x}",
1327 ))),
1328 };
1329 Ok(Self::HashLockedCosigned_v0(genesis::HashLockedCosignedGenesis_v0 {
1330 user_pubkey, signature, unlock,
1331 }))
1332 },
1333 GENESIS_TRANSITION_TYPE_ARKOOR => {
1334 let cosigners = LengthPrefixedVector::decode(r)?.into_inner();
1335 let taptweak = TapTweakHash::decode(r)?;
1336 if bitcoin::secp256k1::Scalar::from_be_bytes(taptweak.to_byte_array()).is_err() {
1337 return Err(ProtocolDecodingError::invalid(
1338 "arkoor genesis tap tweak is not a valid secp256k1 scalar",
1339 ));
1340 }
1341 let signature = Option::<schnorr::Signature>::decode(r)?;
1342 Ok(Self::new_arkoor(cosigners, taptweak, signature))
1343 },
1344 v => Err(ProtocolDecodingError::invalid(format_args!(
1345 "invalid GenesisTransistion type byte: {v:#x}",
1346 ))),
1347 }
1348 }
1349}
1350
1351trait VtxoVersionedEncoding: Sized {
1354 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, version: u16) -> Result<(), io::Error>;
1355
1356 fn decode<R: io::Read + ?Sized>(
1357 r: &mut R,
1358 version: u16,
1359 ) -> Result<Self, ProtocolDecodingError>;
1360}
1361
1362impl VtxoVersionedEncoding for Bare {
1363 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1364 w.emit_compact_size(0u64)?;
1365 Ok(())
1366 }
1367
1368 fn decode<R: io::Read + ?Sized>(
1369 r: &mut R,
1370 version: u16,
1371 ) -> Result<Self, ProtocolDecodingError> {
1372 let _full = Full::decode(r, version)?;
1375
1376 Ok(Bare)
1377 }
1378}
1379
1380impl VtxoVersionedEncoding for Full {
1381 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1382 w.emit_compact_size(self.items.len() as u64)?;
1383 for item in &self.items {
1384 item.transition.encode(w)?;
1385 let nb_outputs = item.other_outputs.len().saturating_add(1);
1386 w.emit_u8(nb_outputs.try_into()
1387 .map_err(|_| io::Error::other("too many outputs on genesis transaction"))?)?;
1388 w.emit_u8(item.output_idx)?;
1389 for txout in &item.other_outputs {
1390 txout.encode(w)?;
1391 }
1392 w.emit_u64(item.fee_amount.to_sat())?;
1393 }
1394 Ok(())
1395 }
1396
1397 fn decode<R: io::Read + ?Sized>(
1398 r: &mut R,
1399 version: u16,
1400 ) -> Result<Self, ProtocolDecodingError> {
1401 let nb_genesis_items = r.read_compact_size()? as usize;
1402 OversizedVectorError::check::<GenesisItem>(nb_genesis_items)?;
1403 let mut genesis = Vec::with_capacity(nb_genesis_items);
1404 for _ in 0..nb_genesis_items {
1405 let transition = GenesisTransition::decode(r)?;
1406 let nb_outputs = r.read_u8()? as usize;
1407 let output_idx = r.read_u8()?;
1408 let nb_other = nb_outputs.checked_sub(1)
1409 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1410 if output_idx as usize >= nb_outputs {
1416 return Err(ProtocolDecodingError::invalid(
1417 "genesis item output_idx out of range (>= nb_outputs)",
1418 ));
1419 }
1420 let mut other_outputs = Vec::with_capacity(nb_other);
1421 for _ in 0..nb_other {
1422 other_outputs.push(TxOut::decode(r)?);
1423 }
1424 let fee_amount = if version == VTXO_NO_FEE_AMOUNT_VERSION {
1425 Amount::ZERO
1427 } else {
1428 Amount::from_sat(r.read_u64()?)
1429 };
1430 genesis.push(GenesisItem { transition, output_idx, other_outputs, fee_amount });
1431 }
1432 Ok(Full { items: genesis })
1433 }
1434}
1435
1436impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<Bare, P> {
1437 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1438 vtxo_encode_inner(&self, w)
1439 }
1440
1441 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1442 Ok(vtxo_decode_inner(r)?.0)
1443 }
1444}
1445
1446impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<Full, P> {
1447 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1448 vtxo_encode_inner(&self, w)
1449 }
1450
1451 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1452 let (vtxo, _) = vtxo_decode_inner::<Full, P, _>(r)?;
1455 if vtxo.point() != vtxo.chain_anchor() {
1456 if vtxo.genesis.items.is_empty() {
1457 return Err(ProtocolDecodingError::invalid_err(
1458 VtxoValidationError::MissingGenesisItems,
1459 format!("VTXO {} has no genesis item data", vtxo.id()),
1460 ));
1461 }
1462 } else {
1463 if !vtxo.genesis.items.is_empty() {
1464 return Err(ProtocolDecodingError::invalid_err(
1465 VtxoValidationError::UnexpectedGenesisItems,
1466 format!("decoded genesis item data when there shouldn't be any for VTXO {}", vtxo.id()),
1467 ));
1468 }
1469 }
1470 Ok(vtxo)
1471 }
1472}
1473
1474fn vtxo_encode_inner<G, P, W>(vtxo: &Vtxo<G, P>, w: &mut W) -> Result<(), io::Error>
1475where
1476 G: VtxoVersionedEncoding,
1477 P: Policy + ProtocolEncoding,
1478 W: io::Write + ?Sized,
1479{
1480 let version = VTXO_ENCODING_VERSION;
1481 w.emit_u16(version)?;
1482 w.emit_u64(vtxo.amount.to_sat())?;
1483 w.emit_u32(vtxo.expiry_height)?;
1484 vtxo.server_pubkey.encode(w)?;
1485 w.emit_u16(vtxo.exit_delta)?;
1486 vtxo.anchor_point.encode(w)?;
1487
1488 vtxo.genesis.encode(w, version)?;
1489
1490 vtxo.policy.encode(w)?;
1491 vtxo.point.encode(w)?;
1492 Ok(())
1493}
1494
1495fn vtxo_decode_inner<G, P, R>(r: &mut R) -> Result<(Vtxo<G, P>, u16), ProtocolDecodingError>
1496where
1497 G: VtxoVersionedEncoding,
1498 P: Policy + ProtocolEncoding,
1499 R: io::Read + ?Sized,
1500{
1501 let version = r.read_u16()?;
1502 if version != VTXO_ENCODING_VERSION && version != VTXO_NO_FEE_AMOUNT_VERSION {
1503 return Err(ProtocolDecodingError::invalid(format_args!(
1504 "invalid Vtxo encoding version byte: {version:#x}",
1505 )));
1506 }
1507
1508 let amount = Amount::from_sat(r.read_u64()?);
1509 let expiry_height = check_block_height(r.read_u32()?)
1510 .map_err(|e| ProtocolDecodingError::invalid_err(e, "expiry_height"))?;
1511 let server_pubkey = PublicKey::decode(r)?;
1512 let exit_delta = check_block_delta(r.read_u16()?)
1513 .map_err(|e| ProtocolDecodingError::invalid_err(e, "exit_delta"))?;
1514 let anchor_point = OutPoint::decode(r)?;
1515
1516 let genesis = VtxoVersionedEncoding::decode(r, version)?;
1517
1518 let policy = P::decode(r)?;
1519 let point = OutPoint::decode(r)?;
1520 let vtxo = Vtxo {
1521 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy, point,
1522 };
1523 Ok((vtxo, version))
1524}
1525
1526#[cfg(test)]
1527mod test {
1528 use bitcoin::consensus::encode::serialize_hex;
1529 use bitcoin::hex::DisplayHex;
1530
1531 use crate::test_util::encoding_roundtrip;
1532 use crate::test_util::dummy::{DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1533 use crate::test_util::vectors::{
1534 generate_vtxo_vectors, VTXO_VECTORS, VTXO_NO_FEE_AMOUNT_VERSION_HEXES,
1535 };
1536
1537 use super::*;
1538
1539 #[test]
1540 fn test_generate_vtxo_vectors() {
1541 let g = generate_vtxo_vectors();
1542 println!("\n\ngenerated:");
1545 println!(" anchor_tx: {}", serialize_hex(&g.anchor_tx));
1546 println!(" board_vtxo: {}", g.board_vtxo.serialize().as_hex().to_string());
1547 println!(" arkoor_htlc_out_vtxo: {}", g.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1548 println!(" arkoor2_vtxo: {}", g.arkoor2_vtxo.serialize().as_hex().to_string());
1549 println!(" round_tx: {}", serialize_hex(&g.round_tx));
1550 println!(" round1_vtxo: {}", g.round1_vtxo.serialize().as_hex().to_string());
1551 println!(" round2_vtxo: {}", g.round2_vtxo.serialize().as_hex().to_string());
1552 println!(" arkoor3_vtxo: {}", g.arkoor3_vtxo.serialize().as_hex().to_string());
1553
1554
1555 let v = &*VTXO_VECTORS;
1556 println!("\n\nstatic:");
1557 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1558 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1559 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1560 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1561 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1562 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1563 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1564 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1565
1566 assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1567 assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1568 assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1569 assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1570 assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1571 assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1572 assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1573 assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1574
1575 assert_eq!(g, *v);
1577 }
1578
1579 #[test]
1580 fn test_vtxo_no_fee_amount_version_upgrade() {
1581 let hexes = &*VTXO_NO_FEE_AMOUNT_VERSION_HEXES;
1582 let v = hexes.deserialize_test_vectors();
1583
1584 v.validate_vtxos();
1586
1587 let board_hex = v.board_vtxo.serialize().as_hex().to_string();
1589 let arkoor_htlc_out_vtxo_hex = v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string();
1590 let arkoor2_vtxo_hex = v.arkoor2_vtxo.serialize().as_hex().to_string();
1591 let round1_vtxo_hex = v.round1_vtxo.serialize().as_hex().to_string();
1592 let round2_vtxo_hex = v.round2_vtxo.serialize().as_hex().to_string();
1593 let arkoor3_vtxo_hex = v.arkoor3_vtxo.serialize().as_hex().to_string();
1594 assert_ne!(board_hex, hexes.board_vtxo);
1595 assert_ne!(arkoor_htlc_out_vtxo_hex, hexes.arkoor_htlc_out_vtxo);
1596 assert_ne!(arkoor2_vtxo_hex, hexes.arkoor2_vtxo);
1597 assert_ne!(round1_vtxo_hex, hexes.round1_vtxo);
1598 assert_ne!(round2_vtxo_hex, hexes.round2_vtxo);
1599 assert_ne!(arkoor3_vtxo_hex, hexes.arkoor3_vtxo);
1600
1601 let board_vtxo = Vtxo::<Full>::deserialize_hex(&board_hex).unwrap();
1607 assert_eq!(board_vtxo.serialize().as_hex().to_string(), board_hex);
1608 let arkoor_htlc_out_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor_htlc_out_vtxo_hex).unwrap();
1609 assert_eq!(arkoor_htlc_out_vtxo.serialize().as_hex().to_string(), arkoor_htlc_out_vtxo_hex);
1610 let arkoor2_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor2_vtxo_hex).unwrap();
1611 assert_eq!(arkoor2_vtxo.serialize().as_hex().to_string(), arkoor2_vtxo_hex);
1612 let round1_vtxo = Vtxo::<Full>::deserialize_hex(&round1_vtxo_hex).unwrap();
1613 assert_eq!(round1_vtxo.serialize().as_hex().to_string(), round1_vtxo_hex);
1614 let round2_vtxo = Vtxo::<Full>::deserialize_hex(&round2_vtxo_hex).unwrap();
1615 assert_eq!(round2_vtxo.serialize().as_hex().to_string(), round2_vtxo_hex);
1616 let arkoor3_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor3_vtxo_hex).unwrap();
1617 assert_eq!(arkoor3_vtxo.serialize().as_hex().to_string(), arkoor3_vtxo_hex);
1618 }
1619
1620 #[test]
1621 fn exit_depth() {
1622 let vtxos = &*VTXO_VECTORS;
1623 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1625
1626 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1628
1629 assert_eq!(
1631 vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1632 1 + 1 + 1 ,
1633 );
1634 assert_eq!(
1635 vtxos.arkoor2_vtxo.exit_depth(),
1636 1 + 2 + 2 ,
1637 );
1638 assert_eq!(
1639 vtxos.arkoor3_vtxo.exit_depth(),
1640 3 + 1 + 1 ,
1641 );
1642 }
1643
1644 #[test]
1645 fn ancestor_ids() {
1646 let v = &*VTXO_VECTORS;
1647
1648 assert_eq!(v.board_vtxo.exit_depth(), 1, "board is a single-tx chain anchor");
1651 assert!(v.board_vtxo.ancestor_ids().is_empty(),
1652 "a chain-anchor VTXO has no ancestors");
1653
1654 for vtxo in [
1659 &v.board_vtxo, &v.arkoor_htlc_out_vtxo, &v.arkoor2_vtxo,
1660 &v.round1_vtxo, &v.round2_vtxo, &v.arkoor3_vtxo,
1661 ] {
1662 let ancestors = vtxo.ancestor_ids();
1663
1664 assert_eq!(ancestors.len(), vtxo.exit_depth() as usize - 1,
1665 "ancestor_ids is the whole genesis chain except the VTXO itself");
1666 assert!(!ancestors.contains(&vtxo.id()),
1667 "ancestor_ids must never contain the VTXO's own id");
1668
1669 let last = vtxo.transactions().last().expect("a VTXO has >=1 transaction");
1670 let last_id: VtxoId = OutPoint::new(last.tx.compute_txid(), last.output_idx as u32).into();
1671 assert_eq!(last_id, vtxo.id(),
1672 "the final genesis tx must produce the VTXO itself");
1673 }
1674
1675 assert!(v.arkoor_htlc_out_vtxo.ancestor_ids().contains(&v.board_vtxo.id()),
1681 "a single-hop arkoor lists the board it spent as an ancestor");
1682
1683 let anc2 = v.arkoor2_vtxo.ancestor_ids();
1685 let board_pos = anc2.iter().position(|id| *id == v.board_vtxo.id())
1686 .expect("arkoor2 must list the board ancestor");
1687 let arkoor1_pos = anc2.iter().position(|id| *id == v.arkoor_htlc_out_vtxo.id())
1688 .expect("arkoor2 must list the arkoor1 ancestor");
1689 assert!(board_pos < arkoor1_pos,
1690 "ancestors are ordered from chain anchor down to the immediate parent");
1691
1692 let mut parent_chain = v.arkoor_htlc_out_vtxo.ancestor_ids();
1695 parent_chain.push(v.arkoor_htlc_out_vtxo.id());
1696 assert!(v.arkoor2_vtxo.ancestor_ids().starts_with(&parent_chain),
1697 "a child's ancestors extend its parent's full genesis chain");
1698
1699 assert!(v.arkoor3_vtxo.ancestor_ids().contains(&v.round2_vtxo.id()),
1701 "an arkoor spending a round output lists it as an ancestor");
1702 }
1703
1704 #[test]
1705 fn test_split_genesis_roundtrip() {
1706 fn check<P: Policy + ProtocolEncoding + Clone + std::fmt::Debug>(
1710 vtxo: &Vtxo<Full, P>,
1711 ) where
1712 Vtxo<Full, P>: PartialEq,
1713 {
1714 let original = vtxo.serialize();
1715
1716 let bare_bytes = vtxo.to_bare().serialize();
1717 let genesis_bytes = vtxo.serialize_genesis();
1718
1719 let bare = Vtxo::<Bare, P>::deserialize(&bare_bytes)
1720 .expect("bare deserialize");
1721 let genesis = Full::decode(&mut &genesis_bytes[..], VTXO_ENCODING_VERSION)
1722 .expect("decode_genesis");
1723 let reassembled = bare.with_genesis(genesis)
1724 .expect("reassemble");
1725
1726 assert_eq!(*vtxo, reassembled, "reassembled vtxo differs from original");
1727 assert_eq!(reassembled.serialize(), original,
1728 "reassembled bytes differ from original");
1729 }
1730
1731 let v = &*VTXO_VECTORS;
1732 check(&v.board_vtxo);
1733 check(&v.arkoor_htlc_out_vtxo);
1734 check(&v.arkoor2_vtxo);
1735 check(&v.round1_vtxo);
1736 check(&v.round2_vtxo);
1737 check(&v.arkoor3_vtxo);
1738
1739 let big: Vtxo<Full> = Vtxo {
1741 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1742 amount: Amount::from_sat(10_000),
1743 expiry_height: 101_010,
1744 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1745 exit_delta: 2016,
1746 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1747 genesis: Full {
1748 items: vec![GenesisItem {
1749 transition: GenesisTransition::new_cosigned(
1750 vec![DUMMY_USER_KEY.public_key()],
1751 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1752 ),
1753 output_idx: 0,
1754 other_outputs: vec![],
1755 fee_amount: Amount::ZERO,
1756 }; 257],
1757 },
1758 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1759 };
1760 check(&big);
1761 }
1762
1763 #[test]
1764 fn test_genesis_length_257() {
1765 let vtxo: Vtxo<Full> = Vtxo {
1766 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1767 amount: Amount::from_sat(10_000),
1768 expiry_height: 101_010,
1769 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1770 exit_delta: 2016,
1771 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1772 genesis: Full {
1773 items: vec![GenesisItem {
1774 transition: GenesisTransition::new_cosigned(
1775 vec![DUMMY_USER_KEY.public_key()],
1776 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1777 ),
1778 output_idx: 0,
1779 other_outputs: vec![],
1780 fee_amount: Amount::ZERO,
1781 }; 257],
1782 },
1783 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1784 };
1785 assert_eq!(vtxo.genesis.items.len(), 257);
1786 encoding_roundtrip(&vtxo);
1787 }
1788
1789 #[test]
1790 fn test_genesis_decoding() {
1791 fn check<P: Policy + ProtocolEncoding + Clone + std::fmt::Debug>(
1794 vtxo: &Vtxo<Full, P>,
1795 ) where
1796 Vtxo<Full, P>: PartialEq,
1797 {
1798 let full_bytes = vtxo.serialize();
1799 let bare_bytes = vtxo.as_bare_vtxo().unwrap().serialize();
1800
1801 let full_to_full = Vtxo::<Full>::deserialize(&full_bytes).expect("works");
1807 let full_to_bare = Vtxo::<Bare>::deserialize(&full_bytes).expect("works");
1808 let bare_to_bare = Vtxo::<Bare>::deserialize(&bare_bytes).expect("works");
1809 Vtxo::<Full>::deserialize(&bare_bytes).expect_err("bare to full fails");
1810
1811 assert_eq!(full_to_full.serialize(), full_bytes);
1812 assert_eq!(full_to_bare.serialize(), bare_bytes);
1813 assert_eq!(bare_to_bare.serialize(), bare_bytes);
1814 }
1815
1816 let v = &*VTXO_VECTORS;
1817 check(&v.board_vtxo);
1818 check(&v.arkoor_htlc_out_vtxo);
1819 check(&v.arkoor2_vtxo);
1820 check(&v.round1_vtxo);
1821 check(&v.round2_vtxo);
1822 check(&v.arkoor3_vtxo);
1823 }
1824
1825 fn dummy_vtxo_with(amount: Amount, other_outputs: Vec<TxOut>) -> Vtxo<Full> {
1830 Vtxo {
1831 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1832 amount,
1833 expiry_height: 101_010,
1834 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1835 exit_delta: 2016,
1836 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1837 genesis: Full {
1838 items: vec![GenesisItem {
1839 transition: GenesisTransition::new_cosigned(
1840 vec![DUMMY_USER_KEY.public_key()],
1841 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1842 ),
1843 output_idx: 0,
1844 other_outputs,
1845 fee_amount: Amount::ZERO,
1846 }],
1847 },
1848 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1849 }
1850 }
1851
1852 fn dummy_p2tr_script() -> ScriptBuf {
1854 VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key())
1855 .script_pubkey(DUMMY_SERVER_KEY.public_key(), 2016, 101_010)
1856 }
1857
1858 #[test]
1859 fn check_standard_accepts_real_vtxos() {
1860 let v = &*VTXO_VECTORS;
1863 assert_eq!(v.board_vtxo.check_standard(), Ok(()));
1864 assert_eq!(v.arkoor_htlc_out_vtxo.check_standard(), Ok(()));
1865 assert_eq!(v.arkoor2_vtxo.check_standard(), Ok(()));
1866 assert_eq!(v.round1_vtxo.check_standard(), Ok(()));
1867 assert_eq!(v.round2_vtxo.check_standard(), Ok(()));
1868 assert_eq!(v.arkoor3_vtxo.check_standard(), Ok(()));
1869 assert!(v.board_vtxo.is_standard());
1870 }
1871
1872 #[test]
1873 fn check_standard_dusty_own_output() {
1874 let vtxo = dummy_vtxo_with(Amount::from_sat(100), vec![]);
1877 assert_eq!(vtxo.check_standard(), Err(VtxoStandardnessError::Dusty));
1878 assert!(!vtxo.is_standard());
1879 }
1880
1881 #[test]
1882 fn check_standard_dust_sibling() {
1883 let dust = TxOut {
1887 value: Amount::from_sat(100),
1888 script_pubkey: dummy_p2tr_script(),
1889 };
1890 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![dust]);
1891 assert_eq!(
1892 vtxo.check_standard(),
1893 Err(VtxoStandardnessError::DustSibling {
1894 item_idx: 0,
1895 item_count: 1,
1896 output_idx: 0,
1897 }),
1898 );
1899 }
1900
1901 #[test]
1902 fn check_standard_script_sibling() {
1903 let bad = TxOut {
1907 value: Amount::from_sat(10_000),
1908 script_pubkey: ScriptBuf::from_bytes(vec![0xab, 0xcd]),
1909 };
1910 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![bad]);
1911 assert_eq!(
1912 vtxo.check_standard(),
1913 Err(VtxoStandardnessError::ScriptSibling {
1914 item_idx: 0,
1915 item_count: 1,
1916 output_idx: 0,
1917 }),
1918 );
1919 }
1920
1921 #[test]
1922 fn check_standard_dust_takes_priority_over_later_script_sibling() {
1923 let dust = TxOut {
1926 value: Amount::from_sat(100),
1927 script_pubkey: dummy_p2tr_script(),
1928 };
1929 let bad = TxOut {
1930 value: Amount::from_sat(10_000),
1931 script_pubkey: ScriptBuf::from_bytes(vec![0xab, 0xcd]),
1932 };
1933 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![dust, bad]);
1934 assert_eq!(
1935 vtxo.check_standard(),
1936 Err(VtxoStandardnessError::DustSibling {
1937 item_idx: 0,
1938 item_count: 1,
1939 output_idx: 0,
1940 }),
1941 );
1942 }
1943
1944 mod genesis_transition_encoding {
1945 use bitcoin::hashes::{sha256, Hash};
1946 use bitcoin::secp256k1::{Keypair, PublicKey};
1947 use bitcoin::taproot::TapTweakHash;
1948 use std::str::FromStr;
1949
1950 use crate::encode::ProtocolEncoding;
1951 use crate::test_util::encoding_roundtrip;
1952 use super::genesis::{
1953 GenesisTransition, CosignedGenesis, HashLockedCosignedGenesis_v0, ArkoorGenesis,
1954 };
1955 use super::MaybePreimage;
1956
1957 fn test_pubkey() -> PublicKey {
1958 Keypair::from_str(
1959 "916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92"
1960 ).unwrap().public_key()
1961 }
1962
1963 fn test_signature() -> bitcoin::secp256k1::schnorr::Signature {
1964 "cc8b93e9f6fbc2506bb85ae8bbb530b178daac49704f5ce2e3ab69c266fd5932\
1965 0b28d028eef212e3b9fdc42cfd2e0760a0359d3ea7d2e9e8cfe2040e3f1b71ea"
1966 .parse().unwrap()
1967 }
1968
1969 #[test]
1970 fn cosigned_with_signature() {
1971 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1972 pubkeys: vec![test_pubkey()],
1973 signature: Some(test_signature()),
1974 });
1975 encoding_roundtrip(&transition);
1976 }
1977
1978 #[test]
1979 fn cosigned_without_signature() {
1980 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1981 pubkeys: vec![test_pubkey()],
1982 signature: None,
1983 });
1984 encoding_roundtrip(&transition);
1985 }
1986
1987 #[test]
1988 fn cosigned_empty_pubkeys_rejected() {
1989 let mut buf = Vec::new();
1990 buf.push(super::GENESIS_TRANSITION_TYPE_COSIGNED);
1991 buf.push(0x00); buf.push(0x00); let err = GenesisTransition::deserialize(&mut buf.as_slice())
1994 .expect_err("empty pubkeys must be rejected");
1995 assert!(format!("{err}").contains("empty pubkey list"), "got: {err}");
1996 }
1997
1998 #[test]
1999 fn cosigned_multiple_pubkeys() {
2000 let pk1 = test_pubkey();
2001 let pk2 = Keypair::from_str(
2002 "fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879"
2003 ).unwrap().public_key();
2004
2005 let transition = GenesisTransition::Cosigned(CosignedGenesis {
2006 pubkeys: vec![pk1, pk2],
2007 signature: Some(test_signature()),
2008 });
2009 encoding_roundtrip(&transition);
2010 }
2011
2012 #[test]
2013 fn hash_locked_cosigned_with_preimage() {
2014 let preimage = [0x42u8; 32];
2015 let transition = GenesisTransition::HashLockedCosigned_v0(HashLockedCosignedGenesis_v0 {
2016 user_pubkey: test_pubkey(),
2017 signature: Some(test_signature()),
2018 unlock: MaybePreimage::Preimage(preimage),
2019 });
2020 encoding_roundtrip(&transition);
2021 }
2022
2023 #[test]
2024 fn hash_locked_cosigned_with_hash() {
2025 let hash = sha256::Hash::hash(b"test preimage");
2026 let transition = GenesisTransition::HashLockedCosigned_v0(HashLockedCosignedGenesis_v0 {
2027 user_pubkey: test_pubkey(),
2028 signature: Some(test_signature()),
2029 unlock: MaybePreimage::Hash(hash),
2030 });
2031 encoding_roundtrip(&transition);
2032 }
2033
2034 #[test]
2035 fn hash_locked_cosigned_without_signature() {
2036 let preimage = [0x42u8; 32];
2037 let transition = GenesisTransition::HashLockedCosigned_v0(HashLockedCosignedGenesis_v0 {
2038 user_pubkey: test_pubkey(),
2039 signature: None,
2040 unlock: MaybePreimage::Preimage(preimage),
2041 });
2042 encoding_roundtrip(&transition);
2043 }
2044
2045 #[test]
2046 fn arkoor_with_signature() {
2047 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
2048 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
2049 client_cosigners: vec![test_pubkey()],
2050 tap_tweak,
2051 signature: Some(test_signature()),
2052 });
2053 encoding_roundtrip(&transition);
2054 }
2055
2056 #[test]
2057 fn arkoor_without_signature() {
2058 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
2059 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
2060 client_cosigners: vec![test_pubkey()],
2061 tap_tweak,
2062 signature: None,
2063 });
2064 encoding_roundtrip(&transition);
2065 }
2066
2067 #[test]
2068 fn arkoor_out_of_range_tweak_rejected() {
2069 let valid = GenesisTransition::Arkoor(ArkoorGenesis {
2073 client_cosigners: vec![test_pubkey()],
2074 tap_tweak: TapTweakHash::from_slice(&[0xabu8; 32]).unwrap(),
2075 signature: None,
2076 });
2077 let mut bytes = valid.serialize();
2078 let n = bytes.len();
2081 for b in &mut bytes[n - 96 .. n - 64] {
2082 *b = 0xff;
2083 }
2084 let err = GenesisTransition::deserialize(&mut bytes.as_slice())
2085 .expect_err("out-of-range tap tweak must be rejected");
2086 assert!(
2087 format!("{err}").contains("not a valid secp256k1 scalar"),
2088 "got: {err}",
2089 );
2090 }
2091 }
2092}