1pub mod policy;
57pub mod raw;
58pub(crate) mod genesis;
59mod validation;
60
61pub use self::validation::VtxoValidationError;
62pub use self::policy::{Policy, VtxoPolicy, VtxoPolicyKind, ServerVtxoPolicy};
63pub(crate) use self::genesis::{GenesisItem, GenesisTransition};
64
65pub use self::policy::{
66 PubkeyVtxoPolicy, CheckpointVtxoPolicy, ExpiryVtxoPolicy, HarkLeafVtxoPolicy,
67 ServerHtlcRecvVtxoPolicy, ServerHtlcSendVtxoPolicy
68};
69pub use self::policy::clause::{
70 VtxoClause, DelayedSignClause, DelayedTimelockSignClause, HashDelaySignClause,
71 TapScriptClause,
72};
73
74pub type ServerVtxo<G = Bare> = Vtxo<G, ServerVtxoPolicy>;
76
77use std::borrow::Cow;
78use std::iter::FusedIterator;
79use std::{fmt, io};
80use std::str::FromStr;
81
82use bitcoin::{
83 taproot, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness
84};
85use bitcoin::absolute::LockTime;
86use bitcoin::hashes::{sha256, Hash};
87use bitcoin::secp256k1::{schnorr, PublicKey, XOnlyPublicKey};
88use bitcoin::taproot::TapTweakHash;
89
90use bitcoin_ext::{fee, BlockDelta, BlockHeight, NonStandardOutput, TxOutExt, P2TR_DUST, P2TR_DUST_SAT};
91
92use crate::vtxo::policy::{check_block_delta, check_block_height, HarkForfeitVtxoPolicy};
93use crate::scripts;
94use crate::encode::{
95 LengthPrefixedVector, MAX_VEC_SIZE, OversizedVectorError, ProtocolDecodingError,
96 ProtocolEncoding, ReadExt, WriteExt,
97};
98use crate::lightning::PaymentHash;
99use crate::tree::signed::{UnlockHash, UnlockPreimage};
100
101pub const VTXO_DUST_SAT: u64 = P2TR_DUST_SAT;
103pub const VTXO_DUST: Amount = P2TR_DUST;
105
106pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
108
109const VTXO_ENCODING_VERSION: u16 = 2;
111const VTXO_NO_FEE_AMOUNT_VERSION: u16 = 1;
113
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
116#[error("failed to parse vtxo id, must be 36 bytes")]
117pub struct VtxoIdParseError;
118
119#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
132pub enum VtxoStandardnessError {
133 #[error("the VTXO's own output is below the dust limit for its script type")]
136 Dusty,
137
138 #[error("dust sibling output at genesis item {item_idx}/{item_count}, output {output_idx}")]
164 DustSibling {
165 item_idx: usize,
166 item_count: usize,
167 output_idx: usize,
168 },
169
170 #[error("the VTXO's own output uses a non-standard script type")]
173 Script,
174
175 #[error("non-standard script in sibling output at genesis item {item_idx}/{item_count}, output {output_idx}")]
179 ScriptSibling {
180 item_idx: usize,
181 item_count: usize,
182 output_idx: usize,
183 },
184}
185
186#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
187pub struct VtxoId([u8; 36]);
188
189impl VtxoId {
190 pub const ENCODE_SIZE: usize = 36;
192
193 pub fn from_slice(b: &[u8]) -> Result<VtxoId, VtxoIdParseError> {
195 if b.len() == 36 {
196 let mut ret = [0u8; 36];
197 ret[..].copy_from_slice(&b[0..36]);
198 Ok(Self(ret))
199 } else {
200 Err(VtxoIdParseError)
201 }
202 }
203
204 pub fn to_point(&self) -> OutPoint {
206 let txid = Txid::from_byte_array(self.0[0..32].try_into().expect("32 bytes"));
207 let vout_bytes = [self.0[32], self.0[33], self.0[34], self.0[35]];
208 let vout = u32::from_le_bytes(vout_bytes);
209 OutPoint::new(txid, vout)
210 }
211
212 #[deprecated(since = "0.1.3", note = "use to_point instead")]
213 pub fn utxo(self) -> OutPoint {
214 self.to_point()
215 }
216
217 pub fn to_bytes(self) -> [u8; 36] {
219 self.0
220 }
221}
222
223impl From<OutPoint> for VtxoId {
224 fn from(p: OutPoint) -> VtxoId {
225 let mut ret = [0u8; 36];
226 ret[0..32].copy_from_slice(&p.txid[..]);
227 ret[32..].copy_from_slice(&p.vout.to_le_bytes());
228 VtxoId(ret)
229 }
230}
231
232impl AsRef<[u8]> for VtxoId {
233 fn as_ref(&self) -> &[u8] {
234 &self.0
235 }
236}
237
238impl fmt::Display for VtxoId {
239 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
240 fmt::Display::fmt(&self.to_point(), f)
241 }
242}
243
244impl fmt::Debug for VtxoId {
245 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246 fmt::Display::fmt(self, f)
247 }
248}
249
250impl FromStr for VtxoId {
251 type Err = VtxoIdParseError;
252 fn from_str(s: &str) -> Result<Self, Self::Err> {
253 Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
254 }
255}
256
257impl serde::Serialize for VtxoId {
258 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
259 if s.is_human_readable() {
260 s.collect_str(self)
261 } else {
262 s.serialize_bytes(self.as_ref())
263 }
264 }
265}
266
267impl<'de> serde::Deserialize<'de> for VtxoId {
268 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
269 struct Visitor;
270 impl<'de> serde::de::Visitor<'de> for Visitor {
271 type Value = VtxoId;
272 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 write!(f, "a VtxoId")
274 }
275 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
276 VtxoId::from_slice(v).map_err(serde::de::Error::custom)
277 }
278 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
279 VtxoId::from_str(v).map_err(serde::de::Error::custom)
280 }
281 }
282 if d.is_human_readable() {
283 d.deserialize_str(Visitor)
284 } else {
285 d.deserialize_bytes(Visitor)
286 }
287 }
288}
289
290impl ProtocolEncoding for VtxoId {
291 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
292 w.emit_slice(&self.0)
293 }
294 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
295 let array: [u8; 36] = r.read_byte_array()
296 .map_err(|_| ProtocolDecodingError::invalid("invalid vtxo id. Expected 36 bytes"))?;
297
298 Ok(VtxoId(array))
299 }
300}
301
302pub(crate) fn exit_clause(
304 user_pubkey: PublicKey,
305 exit_delta: BlockDelta,
306) -> ScriptBuf {
307 scripts::delayed_sign(exit_delta, user_pubkey.x_only_public_key().0)
308}
309
310pub fn create_exit_tx(
315 prevout: OutPoint,
316 output: TxOut,
317 signature: Option<&schnorr::Signature>,
318 fee: Amount,
319) -> Transaction {
320 Transaction {
321 version: bitcoin::transaction::Version(3),
322 lock_time: LockTime::ZERO,
323 input: vec![TxIn {
324 previous_output: prevout,
325 script_sig: ScriptBuf::new(),
326 sequence: Sequence::ZERO,
327 witness: {
328 let mut ret = Witness::new();
329 if let Some(sig) = signature {
330 ret.push(&sig[..]);
331 }
332 ret
333 },
334 }],
335 output: vec![output, fee::fee_anchor_with_amount(fee)],
336 }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub(crate) enum MaybePreimage {
344 Preimage([u8; 32]),
345 Hash(sha256::Hash),
346}
347
348impl MaybePreimage {
349 pub fn hash(&self) -> sha256::Hash {
351 match self {
352 Self::Preimage(p) => sha256::Hash::hash(p),
353 Self::Hash(h) => *h,
354 }
355 }
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
361pub struct VtxoTxIterItem {
362 pub tx: Transaction,
364 pub output_idx: usize,
366}
367
368pub struct VtxoTxIter<'a, P: Policy = VtxoPolicy> {
370 vtxo: &'a Vtxo<Full, P>,
371
372 prev: OutPoint,
373 genesis_idx: usize,
374 current_amount: Amount,
375}
376
377impl<'a, P: Policy> VtxoTxIter<'a, P> {
378 fn new(vtxo: &'a Vtxo<Full, P>) -> VtxoTxIter<'a, P> {
379 let onchain_amount = vtxo.chain_anchor_amount()
381 .expect("This should only fail if the VTXO is invalid.");
382 VtxoTxIter {
383 prev: vtxo.anchor_point,
384 vtxo: vtxo,
385 genesis_idx: 0,
386 current_amount: onchain_amount,
387 }
388 }
389}
390
391impl<'a, P: Policy> Iterator for VtxoTxIter<'a, P> {
392 type Item = VtxoTxIterItem;
393
394 fn next(&mut self) -> Option<Self::Item> {
395 let item = self.vtxo.genesis.items.get(self.genesis_idx)?;
396 let next_amount = self.current_amount.checked_sub(
397 item.other_output_sum().expect("we calculated this amount beforehand")
398 ).expect("we calculated this amount beforehand");
399
400 let next_output = if let Some(item) = self.vtxo.genesis.items.get(self.genesis_idx.saturating_add(1)) {
401 item.transition.input_txout(
402 next_amount,
403 self.vtxo.server_pubkey,
404 self.vtxo.expiry_height,
405 self.vtxo.exit_delta,
406 )
407 } else {
408 self.vtxo.policy.txout(
410 self.vtxo.amount,
411 self.vtxo.server_pubkey,
412 self.vtxo.exit_delta,
413 self.vtxo.expiry_height,
414 )
415 };
416
417 let tx = item.tx(self.prev, next_output, self.vtxo.server_pubkey, self.vtxo.expiry_height);
418 self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
419 self.genesis_idx = self.genesis_idx.saturating_add(1);
420 self.current_amount = next_amount;
421 let output_idx = item.output_idx as usize;
422 Some(VtxoTxIterItem { tx, output_idx })
423 }
424
425 fn size_hint(&self) -> (usize, Option<usize>) {
426 let len = self.vtxo.genesis.items.len().saturating_sub(self.genesis_idx);
427 (len, Some(len))
428 }
429}
430
431impl<'a, P: Policy> ExactSizeIterator for VtxoTxIter<'a, P> {}
432impl<'a, P: Policy> FusedIterator for VtxoTxIter<'a, P> {}
433
434#[derive(Debug, Clone)]
436pub struct Bare;
437
438#[derive(Debug, Clone)]
440pub struct Full {
441 pub(crate) items: Vec<genesis::GenesisItem>,
442}
443
444#[derive(Debug, Clone)]
458pub struct Vtxo<G = Full, P = VtxoPolicy> {
459 pub(crate) policy: P,
460 pub(crate) amount: Amount,
461 pub(crate) expiry_height: BlockHeight,
462
463 pub(crate) server_pubkey: PublicKey,
464 pub(crate) exit_delta: BlockDelta,
465
466 pub(crate) anchor_point: OutPoint,
467 pub(crate) genesis: G,
469
470 pub(crate) point: OutPoint,
477}
478
479impl<G, P: Policy> Vtxo<G, P> {
480 pub fn id(&self) -> VtxoId {
484 self.point.into()
485 }
486
487 pub fn point(&self) -> OutPoint {
491 self.point
492 }
493
494 pub fn amount(&self) -> Amount {
496 self.amount
497 }
498
499 pub fn chain_anchor(&self) -> OutPoint {
503 self.anchor_point
504 }
505
506 pub fn policy(&self) -> &P {
508 &self.policy
509 }
510
511 pub fn policy_type(&self) -> VtxoPolicyKind {
513 self.policy.policy_type()
514 }
515
516 pub fn expiry_height(&self) -> BlockHeight {
518 self.expiry_height
519 }
520
521 pub fn server_pubkey(&self) -> PublicKey {
523 self.server_pubkey
524 }
525
526 pub fn exit_delta(&self) -> BlockDelta {
528 self.exit_delta
529 }
530
531 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
533 self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
534 }
535
536 pub fn output_script_pubkey(&self) -> ScriptBuf {
538 self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
539 }
540
541 pub fn txout(&self) -> TxOut {
543 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
544 }
545
546 pub fn to_bare(&self) -> Vtxo<Bare, P> {
548 Vtxo {
549 point: self.point,
550 policy: self.policy.clone(),
551 amount: self.amount,
552 expiry_height: self.expiry_height,
553 server_pubkey: self.server_pubkey,
554 exit_delta: self.exit_delta,
555 anchor_point: self.anchor_point,
556 genesis: Bare,
557 }
558 }
559
560 pub fn into_bare(self) -> Vtxo<Bare, P> {
562 Vtxo {
563 point: self.point,
564 policy: self.policy,
565 amount: self.amount,
566 expiry_height: self.expiry_height,
567 server_pubkey: self.server_pubkey,
568 exit_delta: self.exit_delta,
569 anchor_point: self.anchor_point,
570 genesis: Bare,
571 }
572 }
573}
574
575impl<P: Policy> Vtxo<Bare, P> {
576 pub fn new(
578 point: OutPoint,
579 policy: P,
580 amount: Amount,
581 expiry_height: BlockHeight,
582 server_pubkey: PublicKey,
583 exit_delta: BlockDelta,
584 anchor_point: OutPoint,
585 ) -> Self {
586 Vtxo { point, policy, amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis: Bare }
587 }
588
589 pub fn with_genesis(self, genesis: Full) -> Result<Vtxo<Full, P>, VtxoValidationError> {
604 if self.point() != self.chain_anchor() {
607 if genesis.items.is_empty() {
608 return Err(VtxoValidationError::MissingGenesisItems);
609 }
610 }
611 else {
612 if !genesis.items.is_empty() {
613 return Err(VtxoValidationError::UnexpectedGenesisItems);
614 }
615 }
616 Ok(Vtxo {
617 policy: self.policy,
618 amount: self.amount,
619 expiry_height: self.expiry_height,
620 server_pubkey: self.server_pubkey,
621 exit_delta: self.exit_delta,
622 anchor_point: self.anchor_point,
623 genesis,
624 point: self.point,
625 })
626 }
627}
628
629const _: () = assert!(
633 MAX_VEC_SIZE / core::mem::size_of::<GenesisItem>() <= u16::MAX as usize,
634 "genesis decode cap must keep items.len() within u16 for Vtxo::exit_depth",
635);
636
637impl<P: Policy> Vtxo<Full, P> {
638 pub fn exit_depth(&self) -> u16 {
640 u16::try_from(self.genesis.items.len())
644 .expect("genesis item count fits in u16")
645 }
646
647 pub fn past_arkoor_pubkeys(&self) -> Vec<Vec<PublicKey>> {
655 self.genesis.items.iter().filter_map(|g| {
656 match &g.transition {
657 GenesisTransition::Arkoor(inner) => Some(inner.client_cosigners().collect()),
660 _ => None,
661 }
662 }).collect()
663 }
664
665 pub fn has_all_witnesses(&self) -> bool {
670 self.genesis.items.iter().all(|g| g.transition.has_all_witnesses())
671 }
672
673 pub fn is_standard(&self) -> bool {
683 self.check_standard().is_ok()
684 }
685
686 pub fn check_standard(&self) -> Result<(), VtxoStandardnessError> {
693 if let Err(kind) = self.txout().check_standard() {
694 return Err(match kind {
695 NonStandardOutput::Dust => VtxoStandardnessError::Dusty,
696 NonStandardOutput::Script => VtxoStandardnessError::Script,
697 });
698 }
699 let item_count = self.genesis.items.len();
700 for (item_idx, item) in self.genesis.items.iter().enumerate() {
701 for (output_idx, out) in item.other_outputs.iter().enumerate() {
702 if let Err(kind) = out.check_standard() {
703 return Err(match kind {
704 NonStandardOutput::Dust => VtxoStandardnessError::DustSibling {
705 item_idx, item_count, output_idx,
706 },
707 NonStandardOutput::Script => VtxoStandardnessError::ScriptSibling {
708 item_idx, item_count, output_idx,
709 },
710 });
711 }
712 }
713 }
714 Ok(())
715 }
716
717 pub fn unlock_hash(&self) -> Option<UnlockHash> {
719 match self.genesis.items.last()?.transition {
720 GenesisTransition::HashLockedCosigned(ref inner) => Some(inner.unlock.hash()),
721 _ => None,
722 }
723 }
724
725 pub fn provide_unlock_signature(&mut self, signature: schnorr::Signature) -> bool {
729 match self.genesis.items.last_mut().map(|g| &mut g.transition) {
730 Some(GenesisTransition::HashLockedCosigned(inner)) => {
731 inner.signature.replace(signature);
732 true
733 },
734 _ => false,
735 }
736 }
737
738 pub fn provide_unlock_preimage(&mut self, preimage: UnlockPreimage) -> bool {
742 match self.genesis.items.last_mut().map(|g| &mut g.transition) {
743 Some(GenesisTransition::HashLockedCosigned(ref mut inner)) => {
744 if inner.unlock.hash() == UnlockHash::hash(&preimage) {
745 inner.unlock = MaybePreimage::Preimage(preimage);
746 true
747 } else {
748 false
749 }
750 },
751 _ => false,
752 }
753 }
754
755 pub fn transactions(&self) -> VtxoTxIter<'_, P> {
757 VtxoTxIter::new(self)
758 }
759
760 pub fn encode_genesis<W: io::Write + ?Sized>(
767 &self,
768 w: &mut W,
769 ) -> Result<(), io::Error> {
770 Full::encode(&self.genesis, w, VTXO_ENCODING_VERSION)
771 }
772
773 pub fn deserialize_with_genesis(
776 mut vtxo_bytes: &[u8],
777 mut genesis_bytes: &[u8],
778 ) -> Result<Self, ProtocolDecodingError>
779 where
780 P: ProtocolEncoding,
781 {
782 let (vtxo, version) = vtxo_decode_inner::<Bare, P, _>(&mut vtxo_bytes)?;
783 let genesis = Full::decode(&mut genesis_bytes, version)?;
784 vtxo.with_genesis(genesis)
785 .map_err(|e| ProtocolDecodingError::invalid_err(
786 e, "unable to decode VTXO with genesis",
787 ))
788 }
789
790 pub fn serialize_genesis(&self) -> Vec<u8> {
792 let mut out = Vec::new();
793 self.encode_genesis(&mut out).expect("writing to a Vec doesn't fail");
794 out
795 }
796
797 pub fn validate(
802 &self,
803 chain_anchor_tx: &Transaction,
804 ) -> Result<(), VtxoValidationError> {
805 self::validation::validate(self, chain_anchor_tx)
806 }
807
808 pub fn validate_unsigned(
810 &self,
811 chain_anchor_tx: &Transaction,
812 ) -> Result<(), VtxoValidationError> {
813 self::validation::validate_unsigned(self, chain_anchor_tx)
814 }
815
816 pub(crate) fn chain_anchor_amount(&self) -> Option<Amount> {
820 self.amount.checked_add(self.genesis.items.iter().try_fold(Amount::ZERO, |sum, i| {
821 i.other_output_sum().and_then(|amt| sum.checked_add(amt))
822 })?)
823 }
824
825 pub fn ancestor_ids(&self) -> Vec<VtxoId> {
834 let items = self.transactions().collect::<Vec<_>>();
835 let ancestor_count = items.len().saturating_sub(1);
837 items.iter()
838 .take(ancestor_count)
839 .map(|item| OutPoint::new(item.tx.compute_txid(), item.output_idx as u32).into())
840 .collect()
841 }
842}
843
844impl<G> Vtxo<G, VtxoPolicy> {
845 pub fn user_pubkey(&self) -> PublicKey {
847 self.policy.user_pubkey()
848 }
849
850 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
854 self.policy.arkoor_pubkey()
855 }
856}
857
858impl Vtxo<Full, VtxoPolicy> {
859 #[cfg(any(test, feature = "test-util"))]
861 pub fn finalize_hark_leaf(
862 &mut self,
863 user_key: &bitcoin::secp256k1::Keypair,
864 server_key: &bitcoin::secp256k1::Keypair,
865 chain_anchor: &Transaction,
866 unlock_preimage: UnlockPreimage,
867 ) {
868 use crate::tree::signed::{LeafVtxoCosignContext, LeafVtxoCosignResponse};
869
870 let (ctx, req) = LeafVtxoCosignContext::new(self, chain_anchor, user_key);
872 let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key);
873 assert!(ctx.finalize(self, cosign));
874 assert!(self.provide_unlock_preimage(unlock_preimage));
876 }
877}
878
879impl<G> Vtxo<G, ServerVtxoPolicy> {
880 pub fn try_into_user_vtxo(self) -> Result<Vtxo<G, VtxoPolicy>, ServerVtxo<G>> {
884 if let Some(p) = self.policy.clone().into_user_policy() {
885 Ok(Vtxo {
886 policy: p,
887 amount: self.amount,
888 expiry_height: self.expiry_height,
889 server_pubkey: self.server_pubkey,
890 exit_delta: self.exit_delta,
891 anchor_point: self.anchor_point,
892 genesis: self.genesis,
893 point: self.point,
894 })
895 } else {
896 Err(self)
897 }
898 }
899}
900
901impl<G, P: Policy> PartialEq for Vtxo<G, P> {
902 fn eq(&self, other: &Self) -> bool {
903 PartialEq::eq(&self.id(), &other.id())
904 }
905}
906
907impl<G, P: Policy> Eq for Vtxo<G, P> {}
908
909impl<G, P: Policy> PartialOrd for Vtxo<G, P> {
910 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
911 PartialOrd::partial_cmp(&self.id(), &other.id())
912 }
913}
914
915impl<G, P: Policy> Ord for Vtxo<G, P> {
916 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
917 Ord::cmp(&self.id(), &other.id())
918 }
919}
920
921impl<G, P: Policy> std::hash::Hash for Vtxo<G, P> {
922 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
923 std::hash::Hash::hash(&self.id(), state)
924 }
925}
926
927impl<G, P: Policy> AsRef<Vtxo<G, P>> for Vtxo<G, P> {
928 fn as_ref(&self) -> &Vtxo<G, P> {
929 self
930 }
931}
932
933impl<G> From<Vtxo<G>> for ServerVtxo<G> {
934 fn from(vtxo: Vtxo<G>) -> ServerVtxo<G> {
935 ServerVtxo {
936 policy: vtxo.policy.into(),
937 amount: vtxo.amount,
938 expiry_height: vtxo.expiry_height,
939 server_pubkey: vtxo.server_pubkey,
940 exit_delta: vtxo.exit_delta,
941 anchor_point: vtxo.anchor_point,
942 genesis: vtxo.genesis,
943 point: vtxo.point,
944 }
945 }
946}
947
948pub trait VtxoRef<P: Policy = VtxoPolicy> {
950 fn vtxo_id(&self) -> VtxoId;
952
953 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { None }
955
956 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { None }
958
959 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> where Self: Sized;
961}
962
963impl<P: Policy> VtxoRef<P> for VtxoId {
964 fn vtxo_id(&self) -> VtxoId { *self }
965 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
966}
967
968impl<'a, P: Policy> VtxoRef<P> for &'a VtxoId {
969 fn vtxo_id(&self) -> VtxoId { **self }
970 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
971}
972
973impl<P: Policy> VtxoRef<P> for Vtxo<Bare, P> {
974 fn vtxo_id(&self) -> VtxoId { self.id() }
975 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(self)) }
976 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
977}
978
979impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Bare, P> {
980 fn vtxo_id(&self) -> VtxoId { self.id() }
981 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Borrowed(*self)) }
982 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { None }
983}
984
985impl<P: Policy> VtxoRef<P> for Vtxo<Full, P> {
986 fn vtxo_id(&self) -> VtxoId { self.id() }
987 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
988 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(self) }
989 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self) }
990}
991
992impl<'a, P: Policy> VtxoRef<P> for &'a Vtxo<Full, P> {
993 fn vtxo_id(&self) -> VtxoId { self.id() }
994 fn as_bare_vtxo(&self) -> Option<Cow<'_, Vtxo<Bare, P>>> { Some(Cow::Owned(self.to_bare())) }
995 fn as_full_vtxo(&self) -> Option<&Vtxo<Full, P>> { Some(*self) }
996 fn into_full_vtxo(self) -> Option<Vtxo<Full, P>> { Some(self.clone()) }
997}
998
999const VTXO_POLICY_PUBKEY: u8 = 0x00;
1001
1002const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
1004
1005const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
1007
1008const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
1010
1011const VTXO_POLICY_EXPIRY: u8 = 0x04;
1013
1014const VTXO_POLICY_HARK_LEAF: u8 = 0x05;
1016
1017const VTXO_POLICY_HARK_FORFEIT: u8 = 0x06;
1019
1020const VTXO_POLICY_SERVER_OWNED: u8 = 0x07;
1022
1023impl ProtocolEncoding for VtxoPolicy {
1024 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1025 match self {
1026 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
1027 w.emit_u8(VTXO_POLICY_PUBKEY)?;
1028 user_pubkey.encode(w)?;
1029 },
1030 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
1031 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
1032 user_pubkey.encode(w)?;
1033 payment_hash.to_sha256_hash().encode(w)?;
1034 w.emit_u32(*htlc_expiry)?;
1035 },
1036 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1037 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1038 }) => {
1039 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
1040 user_pubkey.encode(w)?;
1041 payment_hash.to_sha256_hash().encode(w)?;
1042 w.emit_u32(*htlc_expiry)?;
1043 w.emit_u16(*htlc_expiry_delta)?;
1044 },
1045 }
1046 Ok(())
1047 }
1048
1049 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1050 let type_byte = r.read_u8()?;
1051 decode_vtxo_policy(type_byte, r)
1052 }
1053}
1054
1055fn decode_vtxo_policy<R: io::Read + ?Sized>(
1059 type_byte: u8,
1060 r: &mut R,
1061) -> Result<VtxoPolicy, ProtocolDecodingError> {
1062 match type_byte {
1063 VTXO_POLICY_PUBKEY => {
1064 let user_pubkey = PublicKey::decode(r)?;
1065 Ok(VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
1066 },
1067 VTXO_POLICY_SERVER_HTLC_SEND => {
1068 let user_pubkey = PublicKey::decode(r)?;
1069 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1070 let htlc_expiry = check_block_height(r.read_u32()?)
1071 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1072 Ok(VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
1073 },
1074 VTXO_POLICY_SERVER_HTLC_RECV => {
1075 let user_pubkey = PublicKey::decode(r)?;
1076 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1077 let htlc_expiry = check_block_height(r.read_u32()?)
1078 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry"))?;
1079 let htlc_expiry_delta = check_block_delta(r.read_u16()?)
1080 .map_err(|e| ProtocolDecodingError::invalid_err(e, "htlc_expiry_delta"))?;
1081 Ok(VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
1082 },
1083
1084 v => Err(ProtocolDecodingError::invalid(format_args!(
1089 "invalid VtxoPolicy type byte: {v:#x}",
1090 ))),
1091 }
1092}
1093
1094impl ProtocolEncoding for ServerVtxoPolicy {
1095 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1096 match self {
1097 Self::User(p) => p.encode(w)?,
1098 Self::ServerOwned => {
1099 w.emit_u8(VTXO_POLICY_SERVER_OWNED)?;
1100 },
1101 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
1102 w.emit_u8(VTXO_POLICY_CHECKPOINT)?;
1103 user_pubkey.encode(w)?;
1104 },
1105 Self::Expiry(ExpiryVtxoPolicy { internal_key }) => {
1106 w.emit_u8(VTXO_POLICY_EXPIRY)?;
1107 internal_key.encode(w)?;
1108 },
1109 Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }) => {
1110 w.emit_u8(VTXO_POLICY_HARK_LEAF)?;
1111 user_pubkey.encode(w)?;
1112 unlock_hash.encode(w)?;
1113 },
1114 Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }) => {
1115 w.emit_u8(VTXO_POLICY_HARK_FORFEIT)?;
1116 user_pubkey.encode(w)?;
1117 unlock_hash.encode(w)?;
1118 },
1119 }
1120 Ok(())
1121 }
1122
1123 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1124 let type_byte = r.read_u8()?;
1125 match type_byte {
1126 VTXO_POLICY_PUBKEY | VTXO_POLICY_SERVER_HTLC_SEND | VTXO_POLICY_SERVER_HTLC_RECV => {
1127 Ok(Self::User(decode_vtxo_policy(type_byte, r)?))
1128 },
1129 VTXO_POLICY_SERVER_OWNED => Ok(Self::ServerOwned),
1130 VTXO_POLICY_CHECKPOINT => {
1131 let user_pubkey = PublicKey::decode(r)?;
1132 Ok(Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }))
1133 },
1134 VTXO_POLICY_EXPIRY => {
1135 let internal_key = XOnlyPublicKey::decode(r)?;
1136 Ok(Self::Expiry(ExpiryVtxoPolicy { internal_key }))
1137 },
1138 VTXO_POLICY_HARK_LEAF => {
1139 let user_pubkey = PublicKey::decode(r)?;
1140 let unlock_hash = sha256::Hash::decode(r)?;
1141 Ok(Self::HarkLeaf(HarkLeafVtxoPolicy { user_pubkey, unlock_hash }))
1142 },
1143 VTXO_POLICY_HARK_FORFEIT => {
1144 let user_pubkey = PublicKey::decode(r)?;
1145 let unlock_hash = sha256::Hash::decode(r)?;
1146 Ok(Self::HarkForfeit(HarkForfeitVtxoPolicy { user_pubkey, unlock_hash }))
1147 },
1148 v => Err(ProtocolDecodingError::invalid(format_args!(
1149 "invalid ServerVtxoPolicy type byte: {v:#x}",
1150 ))),
1151 }
1152 }
1153}
1154
1155const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
1157
1158const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
1160
1161const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
1163
1164impl ProtocolEncoding for GenesisTransition {
1165 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1166 match self {
1167 Self::Cosigned(t) => {
1168 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
1169 LengthPrefixedVector::new(&t.pubkeys).encode(w)?;
1170 t.signature.encode(w)?;
1171 },
1172 Self::HashLockedCosigned(t) => {
1173 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
1174 t.user_pubkey.encode(w)?;
1175 t.signature.encode(w)?;
1176 match t.unlock {
1177 MaybePreimage::Preimage(p) => {
1178 w.emit_u8(0)?;
1179 w.emit_slice(&p[..])?;
1180 },
1181 MaybePreimage::Hash(h) => {
1182 w.emit_u8(1)?;
1183 w.emit_slice(&h[..])?;
1184 },
1185 }
1186 },
1187 Self::Arkoor(t) => {
1188 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
1189 LengthPrefixedVector::new(&t.client_cosigners).encode(w)?;
1190 t.tap_tweak.encode(w)?;
1191 t.signature.encode(w)?;
1192 },
1193 }
1194 Ok(())
1195 }
1196
1197 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1198 match r.read_u8()? {
1199 GENESIS_TRANSITION_TYPE_COSIGNED => {
1200 let pubkeys: Vec<PublicKey> = LengthPrefixedVector::decode(r)?.into_inner();
1201 if pubkeys.is_empty() {
1202 return Err(ProtocolDecodingError::invalid(
1203 "cosigned genesis transition with empty pubkey list",
1204 ));
1205 }
1206 let signature = Option::<schnorr::Signature>::decode(r)?;
1207 Ok(Self::new_cosigned(pubkeys, signature))
1208 },
1209 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
1210 let user_pubkey = PublicKey::decode(r)?;
1211 let signature = Option::<schnorr::Signature>::decode(r)?;
1212 let unlock = match r.read_u8()? {
1213 0 => MaybePreimage::Preimage(r.read_byte_array()?),
1214 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
1215 v => return Err(ProtocolDecodingError::invalid(format_args!(
1216 "invalid MaybePreimage type byte: {v:#x}",
1217 ))),
1218 };
1219 Ok(Self::new_hash_locked_cosigned(user_pubkey, signature, unlock))
1220 },
1221 GENESIS_TRANSITION_TYPE_ARKOOR => {
1222 let cosigners = LengthPrefixedVector::decode(r)?.into_inner();
1223 let taptweak = TapTweakHash::decode(r)?;
1224 if bitcoin::secp256k1::Scalar::from_be_bytes(taptweak.to_byte_array()).is_err() {
1225 return Err(ProtocolDecodingError::invalid(
1226 "arkoor genesis tap tweak is not a valid secp256k1 scalar",
1227 ));
1228 }
1229 let signature = Option::<schnorr::Signature>::decode(r)?;
1230 Ok(Self::new_arkoor(cosigners, taptweak, signature))
1231 },
1232 v => Err(ProtocolDecodingError::invalid(format_args!(
1233 "invalid GenesisTransistion type byte: {v:#x}",
1234 ))),
1235 }
1236 }
1237}
1238
1239trait VtxoVersionedEncoding: Sized {
1242 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, version: u16) -> Result<(), io::Error>;
1243
1244 fn decode<R: io::Read + ?Sized>(
1245 r: &mut R,
1246 version: u16,
1247 ) -> Result<Self, ProtocolDecodingError>;
1248}
1249
1250impl VtxoVersionedEncoding for Bare {
1251 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1252 w.emit_compact_size(0u64)?;
1253 Ok(())
1254 }
1255
1256 fn decode<R: io::Read + ?Sized>(
1257 r: &mut R,
1258 version: u16,
1259 ) -> Result<Self, ProtocolDecodingError> {
1260 let _full = Full::decode(r, version)?;
1263
1264 Ok(Bare)
1265 }
1266}
1267
1268impl VtxoVersionedEncoding for Full {
1269 fn encode<W: io::Write + ?Sized>(&self, w: &mut W, _version: u16) -> Result<(), io::Error> {
1270 w.emit_compact_size(self.items.len() as u64)?;
1271 for item in &self.items {
1272 item.transition.encode(w)?;
1273 let nb_outputs = item.other_outputs.len().saturating_add(1);
1274 w.emit_u8(nb_outputs.try_into()
1275 .map_err(|_| io::Error::other("too many outputs on genesis transaction"))?)?;
1276 w.emit_u8(item.output_idx)?;
1277 for txout in &item.other_outputs {
1278 txout.encode(w)?;
1279 }
1280 w.emit_u64(item.fee_amount.to_sat())?;
1281 }
1282 Ok(())
1283 }
1284
1285 fn decode<R: io::Read + ?Sized>(
1286 r: &mut R,
1287 version: u16,
1288 ) -> Result<Self, ProtocolDecodingError> {
1289 let nb_genesis_items = r.read_compact_size()? as usize;
1290 OversizedVectorError::check::<GenesisItem>(nb_genesis_items)?;
1291 let mut genesis = Vec::with_capacity(nb_genesis_items);
1292 for _ in 0..nb_genesis_items {
1293 let transition = GenesisTransition::decode(r)?;
1294 let nb_outputs = r.read_u8()? as usize;
1295 let output_idx = r.read_u8()?;
1296 let nb_other = nb_outputs.checked_sub(1)
1297 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1298 if output_idx as usize >= nb_outputs {
1304 return Err(ProtocolDecodingError::invalid(
1305 "genesis item output_idx out of range (>= nb_outputs)",
1306 ));
1307 }
1308 let mut other_outputs = Vec::with_capacity(nb_other);
1309 for _ in 0..nb_other {
1310 other_outputs.push(TxOut::decode(r)?);
1311 }
1312 let fee_amount = if version == VTXO_NO_FEE_AMOUNT_VERSION {
1313 Amount::ZERO
1315 } else {
1316 Amount::from_sat(r.read_u64()?)
1317 };
1318 genesis.push(GenesisItem { transition, output_idx, other_outputs, fee_amount });
1319 }
1320 Ok(Full { items: genesis })
1321 }
1322}
1323
1324impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<Bare, P> {
1325 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1326 vtxo_encode_inner(&self, w)
1327 }
1328
1329 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1330 Ok(vtxo_decode_inner(r)?.0)
1331 }
1332}
1333
1334impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<Full, P> {
1335 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1336 vtxo_encode_inner(&self, w)
1337 }
1338
1339 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1340 let (vtxo, _) = vtxo_decode_inner::<Full, P, _>(r)?;
1343 if vtxo.point() != vtxo.chain_anchor() {
1344 if vtxo.genesis.items.is_empty() {
1345 return Err(ProtocolDecodingError::invalid_err(
1346 VtxoValidationError::MissingGenesisItems,
1347 format!("VTXO {} has no genesis item data", vtxo.id()),
1348 ));
1349 }
1350 } else {
1351 if !vtxo.genesis.items.is_empty() {
1352 return Err(ProtocolDecodingError::invalid_err(
1353 VtxoValidationError::UnexpectedGenesisItems,
1354 format!("decoded genesis item data when there shouldn't be any for VTXO {}", vtxo.id()),
1355 ));
1356 }
1357 }
1358 Ok(vtxo)
1359 }
1360}
1361
1362fn vtxo_encode_inner<G, P, W>(vtxo: &Vtxo<G, P>, w: &mut W) -> Result<(), io::Error>
1363where
1364 G: VtxoVersionedEncoding,
1365 P: Policy + ProtocolEncoding,
1366 W: io::Write + ?Sized,
1367{
1368 let version = VTXO_ENCODING_VERSION;
1369 w.emit_u16(version)?;
1370 w.emit_u64(vtxo.amount.to_sat())?;
1371 w.emit_u32(vtxo.expiry_height)?;
1372 vtxo.server_pubkey.encode(w)?;
1373 w.emit_u16(vtxo.exit_delta)?;
1374 vtxo.anchor_point.encode(w)?;
1375
1376 vtxo.genesis.encode(w, version)?;
1377
1378 vtxo.policy.encode(w)?;
1379 vtxo.point.encode(w)?;
1380 Ok(())
1381}
1382
1383fn vtxo_decode_inner<G, P, R>(r: &mut R) -> Result<(Vtxo<G, P>, u16), ProtocolDecodingError>
1384where
1385 G: VtxoVersionedEncoding,
1386 P: Policy + ProtocolEncoding,
1387 R: io::Read + ?Sized,
1388{
1389 let version = r.read_u16()?;
1390 if version != VTXO_ENCODING_VERSION && version != VTXO_NO_FEE_AMOUNT_VERSION {
1391 return Err(ProtocolDecodingError::invalid(format_args!(
1392 "invalid Vtxo encoding version byte: {version:#x}",
1393 )));
1394 }
1395
1396 let amount = Amount::from_sat(r.read_u64()?);
1397 let expiry_height = check_block_height(r.read_u32()?)
1398 .map_err(|e| ProtocolDecodingError::invalid_err(e, "expiry_height"))?;
1399 let server_pubkey = PublicKey::decode(r)?;
1400 let exit_delta = check_block_delta(r.read_u16()?)
1401 .map_err(|e| ProtocolDecodingError::invalid_err(e, "exit_delta"))?;
1402 let anchor_point = OutPoint::decode(r)?;
1403
1404 let genesis = VtxoVersionedEncoding::decode(r, version)?;
1405
1406 let policy = P::decode(r)?;
1407 let point = OutPoint::decode(r)?;
1408 let vtxo = Vtxo {
1409 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy, point,
1410 };
1411 Ok((vtxo, version))
1412}
1413
1414#[cfg(test)]
1415mod test {
1416 use bitcoin::consensus::encode::serialize_hex;
1417 use bitcoin::hex::DisplayHex;
1418
1419 use crate::test_util::encoding_roundtrip;
1420 use crate::test_util::dummy::{DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1421 use crate::test_util::vectors::{
1422 generate_vtxo_vectors, VTXO_VECTORS, VTXO_NO_FEE_AMOUNT_VERSION_HEXES,
1423 };
1424
1425 use super::*;
1426
1427 #[test]
1428 fn test_generate_vtxo_vectors() {
1429 let g = generate_vtxo_vectors();
1430 println!("\n\ngenerated:");
1433 println!(" anchor_tx: {}", serialize_hex(&g.anchor_tx));
1434 println!(" board_vtxo: {}", g.board_vtxo.serialize().as_hex().to_string());
1435 println!(" arkoor_htlc_out_vtxo: {}", g.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1436 println!(" arkoor2_vtxo: {}", g.arkoor2_vtxo.serialize().as_hex().to_string());
1437 println!(" round_tx: {}", serialize_hex(&g.round_tx));
1438 println!(" round1_vtxo: {}", g.round1_vtxo.serialize().as_hex().to_string());
1439 println!(" round2_vtxo: {}", g.round2_vtxo.serialize().as_hex().to_string());
1440 println!(" arkoor3_vtxo: {}", g.arkoor3_vtxo.serialize().as_hex().to_string());
1441
1442
1443 let v = &*VTXO_VECTORS;
1444 println!("\n\nstatic:");
1445 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1446 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1447 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1448 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1449 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1450 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1451 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1452 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1453
1454 assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1455 assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1456 assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1457 assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1458 assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1459 assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1460 assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1461 assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1462
1463 assert_eq!(g, *v);
1465 }
1466
1467 #[test]
1468 fn test_vtxo_no_fee_amount_version_upgrade() {
1469 let hexes = &*VTXO_NO_FEE_AMOUNT_VERSION_HEXES;
1470 let v = hexes.deserialize_test_vectors();
1471
1472 v.validate_vtxos();
1474
1475 let board_hex = v.board_vtxo.serialize().as_hex().to_string();
1477 let arkoor_htlc_out_vtxo_hex = v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string();
1478 let arkoor2_vtxo_hex = v.arkoor2_vtxo.serialize().as_hex().to_string();
1479 let round1_vtxo_hex = v.round1_vtxo.serialize().as_hex().to_string();
1480 let round2_vtxo_hex = v.round2_vtxo.serialize().as_hex().to_string();
1481 let arkoor3_vtxo_hex = v.arkoor3_vtxo.serialize().as_hex().to_string();
1482 assert_ne!(board_hex, hexes.board_vtxo);
1483 assert_ne!(arkoor_htlc_out_vtxo_hex, hexes.arkoor_htlc_out_vtxo);
1484 assert_ne!(arkoor2_vtxo_hex, hexes.arkoor2_vtxo);
1485 assert_ne!(round1_vtxo_hex, hexes.round1_vtxo);
1486 assert_ne!(round2_vtxo_hex, hexes.round2_vtxo);
1487 assert_ne!(arkoor3_vtxo_hex, hexes.arkoor3_vtxo);
1488
1489 let board_vtxo = Vtxo::<Full>::deserialize_hex(&board_hex).unwrap();
1495 assert_eq!(board_vtxo.serialize().as_hex().to_string(), board_hex);
1496 let arkoor_htlc_out_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor_htlc_out_vtxo_hex).unwrap();
1497 assert_eq!(arkoor_htlc_out_vtxo.serialize().as_hex().to_string(), arkoor_htlc_out_vtxo_hex);
1498 let arkoor2_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor2_vtxo_hex).unwrap();
1499 assert_eq!(arkoor2_vtxo.serialize().as_hex().to_string(), arkoor2_vtxo_hex);
1500 let round1_vtxo = Vtxo::<Full>::deserialize_hex(&round1_vtxo_hex).unwrap();
1501 assert_eq!(round1_vtxo.serialize().as_hex().to_string(), round1_vtxo_hex);
1502 let round2_vtxo = Vtxo::<Full>::deserialize_hex(&round2_vtxo_hex).unwrap();
1503 assert_eq!(round2_vtxo.serialize().as_hex().to_string(), round2_vtxo_hex);
1504 let arkoor3_vtxo = Vtxo::<Full>::deserialize_hex(&arkoor3_vtxo_hex).unwrap();
1505 assert_eq!(arkoor3_vtxo.serialize().as_hex().to_string(), arkoor3_vtxo_hex);
1506 }
1507
1508 #[test]
1509 fn exit_depth() {
1510 let vtxos = &*VTXO_VECTORS;
1511 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1513
1514 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1516
1517 assert_eq!(
1519 vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1520 1 + 1 + 1 ,
1521 );
1522 assert_eq!(
1523 vtxos.arkoor2_vtxo.exit_depth(),
1524 1 + 2 + 2 ,
1525 );
1526 assert_eq!(
1527 vtxos.arkoor3_vtxo.exit_depth(),
1528 3 + 1 + 1 ,
1529 );
1530 }
1531
1532 #[test]
1533 fn ancestor_ids() {
1534 let v = &*VTXO_VECTORS;
1535
1536 assert_eq!(v.board_vtxo.exit_depth(), 1, "board is a single-tx chain anchor");
1539 assert!(v.board_vtxo.ancestor_ids().is_empty(),
1540 "a chain-anchor VTXO has no ancestors");
1541
1542 for vtxo in [
1547 &v.board_vtxo, &v.arkoor_htlc_out_vtxo, &v.arkoor2_vtxo,
1548 &v.round1_vtxo, &v.round2_vtxo, &v.arkoor3_vtxo,
1549 ] {
1550 let ancestors = vtxo.ancestor_ids();
1551
1552 assert_eq!(ancestors.len(), vtxo.exit_depth() as usize - 1,
1553 "ancestor_ids is the whole genesis chain except the VTXO itself");
1554 assert!(!ancestors.contains(&vtxo.id()),
1555 "ancestor_ids must never contain the VTXO's own id");
1556
1557 let last = vtxo.transactions().last().expect("a VTXO has >=1 transaction");
1558 let last_id: VtxoId = OutPoint::new(last.tx.compute_txid(), last.output_idx as u32).into();
1559 assert_eq!(last_id, vtxo.id(),
1560 "the final genesis tx must produce the VTXO itself");
1561 }
1562
1563 assert!(v.arkoor_htlc_out_vtxo.ancestor_ids().contains(&v.board_vtxo.id()),
1569 "a single-hop arkoor lists the board it spent as an ancestor");
1570
1571 let anc2 = v.arkoor2_vtxo.ancestor_ids();
1573 let board_pos = anc2.iter().position(|id| *id == v.board_vtxo.id())
1574 .expect("arkoor2 must list the board ancestor");
1575 let arkoor1_pos = anc2.iter().position(|id| *id == v.arkoor_htlc_out_vtxo.id())
1576 .expect("arkoor2 must list the arkoor1 ancestor");
1577 assert!(board_pos < arkoor1_pos,
1578 "ancestors are ordered from chain anchor down to the immediate parent");
1579
1580 let mut parent_chain = v.arkoor_htlc_out_vtxo.ancestor_ids();
1583 parent_chain.push(v.arkoor_htlc_out_vtxo.id());
1584 assert!(v.arkoor2_vtxo.ancestor_ids().starts_with(&parent_chain),
1585 "a child's ancestors extend its parent's full genesis chain");
1586
1587 assert!(v.arkoor3_vtxo.ancestor_ids().contains(&v.round2_vtxo.id()),
1589 "an arkoor spending a round output lists it as an ancestor");
1590 }
1591
1592 #[test]
1593 fn test_split_genesis_roundtrip() {
1594 fn check<P: Policy + ProtocolEncoding + Clone + std::fmt::Debug>(
1598 vtxo: &Vtxo<Full, P>,
1599 ) where
1600 Vtxo<Full, P>: PartialEq,
1601 {
1602 let original = vtxo.serialize();
1603
1604 let bare_bytes = vtxo.to_bare().serialize();
1605 let genesis_bytes = vtxo.serialize_genesis();
1606
1607 let bare = Vtxo::<Bare, P>::deserialize(&bare_bytes)
1608 .expect("bare deserialize");
1609 let genesis = Full::decode(&mut &genesis_bytes[..], VTXO_ENCODING_VERSION)
1610 .expect("decode_genesis");
1611 let reassembled = bare.with_genesis(genesis)
1612 .expect("reassemble");
1613
1614 assert_eq!(*vtxo, reassembled, "reassembled vtxo differs from original");
1615 assert_eq!(reassembled.serialize(), original,
1616 "reassembled bytes differ from original");
1617 }
1618
1619 let v = &*VTXO_VECTORS;
1620 check(&v.board_vtxo);
1621 check(&v.arkoor_htlc_out_vtxo);
1622 check(&v.arkoor2_vtxo);
1623 check(&v.round1_vtxo);
1624 check(&v.round2_vtxo);
1625 check(&v.arkoor3_vtxo);
1626
1627 let big: Vtxo<Full> = Vtxo {
1629 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1630 amount: Amount::from_sat(10_000),
1631 expiry_height: 101_010,
1632 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1633 exit_delta: 2016,
1634 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1635 genesis: Full {
1636 items: vec![GenesisItem {
1637 transition: GenesisTransition::new_cosigned(
1638 vec![DUMMY_USER_KEY.public_key()],
1639 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1640 ),
1641 output_idx: 0,
1642 other_outputs: vec![],
1643 fee_amount: Amount::ZERO,
1644 }; 257],
1645 },
1646 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1647 };
1648 check(&big);
1649 }
1650
1651 #[test]
1652 fn test_genesis_length_257() {
1653 let vtxo: Vtxo<Full> = Vtxo {
1654 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1655 amount: Amount::from_sat(10_000),
1656 expiry_height: 101_010,
1657 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1658 exit_delta: 2016,
1659 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1660 genesis: Full {
1661 items: vec![GenesisItem {
1662 transition: GenesisTransition::new_cosigned(
1663 vec![DUMMY_USER_KEY.public_key()],
1664 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1665 ),
1666 output_idx: 0,
1667 other_outputs: vec![],
1668 fee_amount: Amount::ZERO,
1669 }; 257],
1670 },
1671 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1672 };
1673 assert_eq!(vtxo.genesis.items.len(), 257);
1674 encoding_roundtrip(&vtxo);
1675 }
1676
1677 #[test]
1678 fn test_genesis_decoding() {
1679 fn check<P: Policy + ProtocolEncoding + Clone + std::fmt::Debug>(
1682 vtxo: &Vtxo<Full, P>,
1683 ) where
1684 Vtxo<Full, P>: PartialEq,
1685 {
1686 let full_bytes = vtxo.serialize();
1687 let bare_bytes = vtxo.as_bare_vtxo().unwrap().serialize();
1688
1689 let full_to_full = Vtxo::<Full>::deserialize(&full_bytes).expect("works");
1695 let full_to_bare = Vtxo::<Bare>::deserialize(&full_bytes).expect("works");
1696 let bare_to_bare = Vtxo::<Bare>::deserialize(&bare_bytes).expect("works");
1697 Vtxo::<Full>::deserialize(&bare_bytes).expect_err("bare to full fails");
1698
1699 assert_eq!(full_to_full.serialize(), full_bytes);
1700 assert_eq!(full_to_bare.serialize(), bare_bytes);
1701 assert_eq!(bare_to_bare.serialize(), bare_bytes);
1702 }
1703
1704 let v = &*VTXO_VECTORS;
1705 check(&v.board_vtxo);
1706 check(&v.arkoor_htlc_out_vtxo);
1707 check(&v.arkoor2_vtxo);
1708 check(&v.round1_vtxo);
1709 check(&v.round2_vtxo);
1710 check(&v.arkoor3_vtxo);
1711 }
1712
1713 fn dummy_vtxo_with(amount: Amount, other_outputs: Vec<TxOut>) -> Vtxo<Full> {
1718 Vtxo {
1719 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1720 amount,
1721 expiry_height: 101_010,
1722 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1723 exit_delta: 2016,
1724 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1725 genesis: Full {
1726 items: vec![GenesisItem {
1727 transition: GenesisTransition::new_cosigned(
1728 vec![DUMMY_USER_KEY.public_key()],
1729 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1730 ),
1731 output_idx: 0,
1732 other_outputs,
1733 fee_amount: Amount::ZERO,
1734 }],
1735 },
1736 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1737 }
1738 }
1739
1740 fn dummy_p2tr_script() -> ScriptBuf {
1742 VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key())
1743 .script_pubkey(DUMMY_SERVER_KEY.public_key(), 2016, 101_010)
1744 }
1745
1746 #[test]
1747 fn check_standard_accepts_real_vtxos() {
1748 let v = &*VTXO_VECTORS;
1751 assert_eq!(v.board_vtxo.check_standard(), Ok(()));
1752 assert_eq!(v.arkoor_htlc_out_vtxo.check_standard(), Ok(()));
1753 assert_eq!(v.arkoor2_vtxo.check_standard(), Ok(()));
1754 assert_eq!(v.round1_vtxo.check_standard(), Ok(()));
1755 assert_eq!(v.round2_vtxo.check_standard(), Ok(()));
1756 assert_eq!(v.arkoor3_vtxo.check_standard(), Ok(()));
1757 assert!(v.board_vtxo.is_standard());
1758 }
1759
1760 #[test]
1761 fn check_standard_dusty_own_output() {
1762 let vtxo = dummy_vtxo_with(Amount::from_sat(100), vec![]);
1765 assert_eq!(vtxo.check_standard(), Err(VtxoStandardnessError::Dusty));
1766 assert!(!vtxo.is_standard());
1767 }
1768
1769 #[test]
1770 fn check_standard_dust_sibling() {
1771 let dust = TxOut {
1775 value: Amount::from_sat(100),
1776 script_pubkey: dummy_p2tr_script(),
1777 };
1778 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![dust]);
1779 assert_eq!(
1780 vtxo.check_standard(),
1781 Err(VtxoStandardnessError::DustSibling {
1782 item_idx: 0,
1783 item_count: 1,
1784 output_idx: 0,
1785 }),
1786 );
1787 }
1788
1789 #[test]
1790 fn check_standard_script_sibling() {
1791 let bad = TxOut {
1795 value: Amount::from_sat(10_000),
1796 script_pubkey: ScriptBuf::from_bytes(vec![0xab, 0xcd]),
1797 };
1798 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![bad]);
1799 assert_eq!(
1800 vtxo.check_standard(),
1801 Err(VtxoStandardnessError::ScriptSibling {
1802 item_idx: 0,
1803 item_count: 1,
1804 output_idx: 0,
1805 }),
1806 );
1807 }
1808
1809 #[test]
1810 fn check_standard_dust_takes_priority_over_later_script_sibling() {
1811 let dust = TxOut {
1814 value: Amount::from_sat(100),
1815 script_pubkey: dummy_p2tr_script(),
1816 };
1817 let bad = TxOut {
1818 value: Amount::from_sat(10_000),
1819 script_pubkey: ScriptBuf::from_bytes(vec![0xab, 0xcd]),
1820 };
1821 let vtxo = dummy_vtxo_with(Amount::from_sat(10_000), vec![dust, bad]);
1822 assert_eq!(
1823 vtxo.check_standard(),
1824 Err(VtxoStandardnessError::DustSibling {
1825 item_idx: 0,
1826 item_count: 1,
1827 output_idx: 0,
1828 }),
1829 );
1830 }
1831
1832 mod genesis_transition_encoding {
1833 use bitcoin::hashes::{sha256, Hash};
1834 use bitcoin::secp256k1::{Keypair, PublicKey};
1835 use bitcoin::taproot::TapTweakHash;
1836 use std::str::FromStr;
1837
1838 use crate::encode::ProtocolEncoding;
1839 use crate::test_util::encoding_roundtrip;
1840 use super::genesis::{
1841 GenesisTransition, CosignedGenesis, HashLockedCosignedGenesis, ArkoorGenesis,
1842 };
1843 use super::MaybePreimage;
1844
1845 fn test_pubkey() -> PublicKey {
1846 Keypair::from_str(
1847 "916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92"
1848 ).unwrap().public_key()
1849 }
1850
1851 fn test_signature() -> bitcoin::secp256k1::schnorr::Signature {
1852 "cc8b93e9f6fbc2506bb85ae8bbb530b178daac49704f5ce2e3ab69c266fd5932\
1853 0b28d028eef212e3b9fdc42cfd2e0760a0359d3ea7d2e9e8cfe2040e3f1b71ea"
1854 .parse().unwrap()
1855 }
1856
1857 #[test]
1858 fn cosigned_with_signature() {
1859 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1860 pubkeys: vec![test_pubkey()],
1861 signature: Some(test_signature()),
1862 });
1863 encoding_roundtrip(&transition);
1864 }
1865
1866 #[test]
1867 fn cosigned_without_signature() {
1868 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1869 pubkeys: vec![test_pubkey()],
1870 signature: None,
1871 });
1872 encoding_roundtrip(&transition);
1873 }
1874
1875 #[test]
1876 fn cosigned_empty_pubkeys_rejected() {
1877 let mut buf = Vec::new();
1878 buf.push(super::GENESIS_TRANSITION_TYPE_COSIGNED);
1879 buf.push(0x00); buf.push(0x00); let err = GenesisTransition::deserialize(&mut buf.as_slice())
1882 .expect_err("empty pubkeys must be rejected");
1883 assert!(format!("{err}").contains("empty pubkey list"), "got: {err}");
1884 }
1885
1886 #[test]
1887 fn cosigned_multiple_pubkeys() {
1888 let pk1 = test_pubkey();
1889 let pk2 = Keypair::from_str(
1890 "fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879"
1891 ).unwrap().public_key();
1892
1893 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1894 pubkeys: vec![pk1, pk2],
1895 signature: Some(test_signature()),
1896 });
1897 encoding_roundtrip(&transition);
1898 }
1899
1900 #[test]
1901 fn hash_locked_cosigned_with_preimage() {
1902 let preimage = [0x42u8; 32];
1903 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1904 user_pubkey: test_pubkey(),
1905 signature: Some(test_signature()),
1906 unlock: MaybePreimage::Preimage(preimage),
1907 });
1908 encoding_roundtrip(&transition);
1909 }
1910
1911 #[test]
1912 fn hash_locked_cosigned_with_hash() {
1913 let hash = sha256::Hash::hash(b"test preimage");
1914 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1915 user_pubkey: test_pubkey(),
1916 signature: Some(test_signature()),
1917 unlock: MaybePreimage::Hash(hash),
1918 });
1919 encoding_roundtrip(&transition);
1920 }
1921
1922 #[test]
1923 fn hash_locked_cosigned_without_signature() {
1924 let preimage = [0x42u8; 32];
1925 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1926 user_pubkey: test_pubkey(),
1927 signature: None,
1928 unlock: MaybePreimage::Preimage(preimage),
1929 });
1930 encoding_roundtrip(&transition);
1931 }
1932
1933 #[test]
1934 fn arkoor_with_signature() {
1935 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1936 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1937 client_cosigners: vec![test_pubkey()],
1938 tap_tweak,
1939 signature: Some(test_signature()),
1940 });
1941 encoding_roundtrip(&transition);
1942 }
1943
1944 #[test]
1945 fn arkoor_without_signature() {
1946 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1947 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1948 client_cosigners: vec![test_pubkey()],
1949 tap_tweak,
1950 signature: None,
1951 });
1952 encoding_roundtrip(&transition);
1953 }
1954
1955 #[test]
1956 fn arkoor_out_of_range_tweak_rejected() {
1957 let valid = GenesisTransition::Arkoor(ArkoorGenesis {
1961 client_cosigners: vec![test_pubkey()],
1962 tap_tweak: TapTweakHash::from_slice(&[0xabu8; 32]).unwrap(),
1963 signature: None,
1964 });
1965 let mut bytes = valid.serialize();
1966 let n = bytes.len();
1969 for b in &mut bytes[n - 96 .. n - 64] {
1970 *b = 0xff;
1971 }
1972 let err = GenesisTransition::deserialize(&mut bytes.as_slice())
1973 .expect_err("out-of-range tap tweak must be rejected");
1974 assert!(
1975 format!("{err}").contains("not a valid secp256k1 scalar"),
1976 "got: {err}",
1977 );
1978 }
1979 }
1980}