1pub mod policy;
57pub(crate) mod genesis;
58mod validation;
59
60pub use self::validation::VtxoValidationError;
61pub use self::policy::{Policy, VtxoPolicy, VtxoPolicyKind, ServerVtxoPolicy};
62pub(crate) use self::genesis::{GenesisItem, GenesisTransition};
63
64pub use self::policy::{
65 PubkeyVtxoPolicy, CheckpointVtxoPolicy, ExpiryVtxoPolicy,
66 ServerHtlcRecvVtxoPolicy, ServerHtlcSendVtxoPolicy
67};
68pub use self::policy::clause::{
69 VtxoClause, DelayedSignClause, DelayedTimelockSignClause, HashDelaySignClause,
70 TapScriptClause,
71};
72
73pub type ServerVtxo = Vtxo<ServerVtxoPolicy>;
75
76use std::iter::FusedIterator;
77use std::{fmt, io};
78use std::str::FromStr;
79
80use bitcoin::{
81 taproot, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness
82};
83use bitcoin::absolute::LockTime;
84use bitcoin::hashes::{sha256, Hash};
85use bitcoin::secp256k1::{schnorr, PublicKey, XOnlyPublicKey};
86use bitcoin::taproot::TapTweakHash;
87
88use bitcoin_ext::{fee, BlockDelta, BlockHeight, TxOutExt};
89
90use crate::{musig, scripts};
91use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
92use crate::lightning::PaymentHash;
93use crate::tree::signed::{UnlockHash, UnlockPreimage};
94
95pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
97
98const VTXO_ENCODING_VERSION: u16 = 1;
100
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
103#[error("failed to parse vtxo id, must be 36 bytes")]
104pub struct VtxoIdParseError;
105
106#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
107pub struct VtxoId([u8; 36]);
108
109impl VtxoId {
110 pub const ENCODE_SIZE: usize = 36;
112
113 pub fn from_slice(b: &[u8]) -> Result<VtxoId, VtxoIdParseError> {
114 if b.len() == 36 {
115 let mut ret = [0u8; 36];
116 ret[..].copy_from_slice(&b[0..36]);
117 Ok(Self(ret))
118 } else {
119 Err(VtxoIdParseError)
120 }
121 }
122
123 pub fn utxo(self) -> OutPoint {
124 let vout = [self.0[32], self.0[33], self.0[34], self.0[35]];
125 OutPoint::new(Txid::from_slice(&self.0[0..32]).unwrap(), u32::from_le_bytes(vout))
126 }
127
128 pub fn to_bytes(self) -> [u8; 36] {
129 self.0
130 }
131}
132
133impl From<OutPoint> for VtxoId {
134 fn from(p: OutPoint) -> VtxoId {
135 let mut ret = [0u8; 36];
136 ret[0..32].copy_from_slice(&p.txid[..]);
137 ret[32..].copy_from_slice(&p.vout.to_le_bytes());
138 VtxoId(ret)
139 }
140}
141
142impl AsRef<[u8]> for VtxoId {
143 fn as_ref(&self) -> &[u8] {
144 &self.0
145 }
146}
147
148impl fmt::Display for VtxoId {
149 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150 fmt::Display::fmt(&self.utxo(), f)
151 }
152}
153
154impl fmt::Debug for VtxoId {
155 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156 fmt::Display::fmt(self, f)
157 }
158}
159
160impl FromStr for VtxoId {
161 type Err = VtxoIdParseError;
162 fn from_str(s: &str) -> Result<Self, Self::Err> {
163 Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
164 }
165}
166
167impl serde::Serialize for VtxoId {
168 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
169 if s.is_human_readable() {
170 s.collect_str(self)
171 } else {
172 s.serialize_bytes(self.as_ref())
173 }
174 }
175}
176
177impl<'de> serde::Deserialize<'de> for VtxoId {
178 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
179 struct Visitor;
180 impl<'de> serde::de::Visitor<'de> for Visitor {
181 type Value = VtxoId;
182 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 write!(f, "a VtxoId")
184 }
185 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
186 VtxoId::from_slice(v).map_err(serde::de::Error::custom)
187 }
188 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
189 VtxoId::from_str(v).map_err(serde::de::Error::custom)
190 }
191 }
192 if d.is_human_readable() {
193 d.deserialize_str(Visitor)
194 } else {
195 d.deserialize_bytes(Visitor)
196 }
197 }
198}
199
200impl ProtocolEncoding for VtxoId {
201 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
202 w.emit_slice(&self.0)
203 }
204 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
205 let array: [u8; 36] = r.read_byte_array()
206 .map_err(|_| ProtocolDecodingError::invalid("invalid vtxo id. Expected 36 bytes"))?;
207
208 Ok(VtxoId(array))
209 }
210}
211
212pub(crate) fn exit_clause(
214 user_pubkey: PublicKey,
215 exit_delta: BlockDelta,
216) -> ScriptBuf {
217 scripts::delayed_sign(exit_delta, user_pubkey.x_only_public_key().0)
218}
219
220pub fn create_exit_tx(
225 prevout: OutPoint,
226 output: TxOut,
227 signature: Option<&schnorr::Signature>,
228) -> Transaction {
229 Transaction {
230 version: bitcoin::transaction::Version(3),
231 lock_time: LockTime::ZERO,
232 input: vec![TxIn {
233 previous_output: prevout,
234 script_sig: ScriptBuf::new(),
235 sequence: Sequence::ZERO,
236 witness: {
237 let mut ret = Witness::new();
238 if let Some(sig) = signature {
239 ret.push(&sig[..]);
240 }
241 ret
242 },
243 }],
244 output: vec![output, fee::fee_anchor()],
245 }
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub(crate) enum MaybePreimage {
253 Preimage([u8; 32]),
254 Hash(sha256::Hash),
255}
256
257impl MaybePreimage {
258 pub fn hash(&self) -> sha256::Hash {
260 match self {
261 Self::Preimage(p) => sha256::Hash::hash(p),
262 Self::Hash(h) => *h,
263 }
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
270pub struct VtxoTxIterItem {
271 pub tx: Transaction,
273 pub output_idx: usize,
275}
276
277pub struct VtxoTxIter<'a, P: Policy = VtxoPolicy> {
279 vtxo: &'a Vtxo<P>,
280
281 prev: OutPoint,
282 genesis_idx: usize,
283 current_amount: Amount,
284}
285
286impl<'a, P: Policy> VtxoTxIter<'a, P> {
287 fn new(vtxo: &'a Vtxo<P>) -> VtxoTxIter<'a, P> {
288 let onchain_amount = vtxo.amount() + vtxo.genesis.iter().map(|i| {
290 i.other_outputs.iter().map(|o| o.value).sum()
291 }).sum();
292 VtxoTxIter {
293 prev: vtxo.anchor_point,
294 vtxo: vtxo,
295 genesis_idx: 0,
296 current_amount: onchain_amount,
297 }
298 }
299}
300
301impl<'a, P: Policy> Iterator for VtxoTxIter<'a, P> {
302 type Item = VtxoTxIterItem;
303
304 fn next(&mut self) -> Option<Self::Item> {
305 let item = self.vtxo.genesis.get(self.genesis_idx)?;
306 let next_amount = self.current_amount.checked_sub(
307 item.other_outputs.iter().map(|o| o.value).sum()
308 ).expect("we calculated this amount beforehand");
309
310 let next_output = if let Some(item) = self.vtxo.genesis.get(self.genesis_idx + 1) {
311 item.transition.input_txout(
312 next_amount,
313 self.vtxo.server_pubkey,
314 self.vtxo.expiry_height,
315 self.vtxo.exit_delta,
316 )
317 } else {
318 self.vtxo.policy.txout(
320 self.vtxo.amount,
321 self.vtxo.server_pubkey,
322 self.vtxo.exit_delta,
323 self.vtxo.expiry_height,
324 )
325 };
326
327 let tx = item.tx(self.prev, next_output, self.vtxo.server_pubkey, self.vtxo.expiry_height);
328 self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
329 self.genesis_idx += 1;
330 self.current_amount = next_amount;
331 let output_idx = item.output_idx as usize;
332 Some(VtxoTxIterItem { tx, output_idx })
333 }
334
335 fn size_hint(&self) -> (usize, Option<usize>) {
336 let len = self.vtxo.genesis.len().saturating_sub(self.genesis_idx);
337 (len, Some(len))
338 }
339}
340
341impl<'a, P: Policy> ExactSizeIterator for VtxoTxIter<'a, P> {}
342impl<'a, P: Policy> FusedIterator for VtxoTxIter<'a, P> {}
343
344
345#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
347pub struct VtxoSpec<P = VtxoPolicy> {
348 pub policy: P,
349 pub amount: Amount,
350 pub expiry_height: BlockHeight,
351 pub server_pubkey: PublicKey,
352 pub exit_delta: BlockDelta,
353}
354
355impl<P: Policy> VtxoSpec<P> {
356 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
358 self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
359 }
360
361 pub fn output_script_pubkey(&self) -> ScriptBuf {
363 self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
364 }
365
366 pub fn txout(&self) -> TxOut {
368 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
369 }
370}
371
372#[derive(Debug, Clone)]
386pub struct Vtxo<P = VtxoPolicy> {
387 pub(crate) policy: P,
388 pub(crate) amount: Amount,
389 pub(crate) expiry_height: BlockHeight,
390
391 pub(crate) server_pubkey: PublicKey,
392 pub(crate) exit_delta: BlockDelta,
393
394 pub(crate) anchor_point: OutPoint,
395 pub(crate) genesis: Vec<genesis::GenesisItem>,
396
397 pub(crate) point: OutPoint,
404}
405
406impl<P: Policy> Vtxo<P> {
407 pub fn id(&self) -> VtxoId {
411 self.point.into()
412 }
413
414 pub fn point(&self) -> OutPoint {
418 self.point
419 }
420
421 pub fn amount(&self) -> Amount {
423 self.amount
424 }
425
426 pub fn chain_anchor(&self) -> OutPoint {
430 self.anchor_point
431 }
432
433 pub fn policy(&self) -> &P {
435 &self.policy
436 }
437
438 pub fn policy_type(&self) -> VtxoPolicyKind {
440 self.policy.policy_type()
441 }
442
443 pub fn expiry_height(&self) -> BlockHeight {
445 self.expiry_height
446 }
447
448 pub fn server_pubkey(&self) -> PublicKey {
450 self.server_pubkey
451 }
452
453 pub fn exit_delta(&self) -> BlockDelta {
455 self.exit_delta
456 }
457
458 pub fn exit_depth(&self) -> u16 {
460 self.genesis.len() as u16
461 }
462
463 pub fn past_arkoor_pubkeys(&self) -> Vec<Vec<PublicKey>> {
471 self.genesis.iter().filter_map(|g| {
472 match &g.transition {
473 GenesisTransition::Arkoor(inner) => Some(inner.client_cosigners().collect()),
476 _ => None,
477 }
478 }).collect()
479 }
480
481 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
483 self.policy.taproot(self.server_pubkey, self.exit_delta, self.expiry_height)
484 }
485
486 pub fn output_script_pubkey(&self) -> ScriptBuf {
488 self.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height)
489 }
490
491 pub fn txout(&self) -> TxOut {
493 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta, self.expiry_height)
494 }
495
496 pub fn is_fully_signed(&self) -> bool {
501 self.genesis.iter().all(|g| g.transition.is_fully_signed())
502 }
503
504 pub fn is_standard(&self) -> bool {
510 self.txout().is_standard() && self.genesis.iter()
511 .all(|i| i.other_outputs.iter().all(|o| o.is_standard()))
512 }
513
514 pub fn unlock_hash(&self) -> Option<UnlockHash> {
516 match self.genesis.last()?.transition {
517 GenesisTransition::HashLockedCosigned(ref inner) => Some(inner.unlock.hash()),
518 _ => None,
519 }
520 }
521
522 pub fn provide_unlock_signature(&mut self, signature: schnorr::Signature) -> bool {
526 match self.genesis.last_mut().map(|g| &mut g.transition) {
527 Some(GenesisTransition::HashLockedCosigned(inner)) => {
528 inner.signature.replace(signature);
529 true
530 },
531 _ => false,
532 }
533 }
534
535 pub fn provide_unlock_preimage(&mut self, preimage: UnlockPreimage) -> bool {
539 match self.genesis.last_mut().map(|g| &mut g.transition) {
540 Some(GenesisTransition::HashLockedCosigned(ref mut inner)) => {
541 if inner.unlock.hash() == UnlockHash::hash(&preimage) {
542 inner.unlock = MaybePreimage::Preimage(preimage);
543 true
544 } else {
545 false
546 }
547 },
548 _ => false,
549 }
550 }
551
552 pub fn spec(&self) -> VtxoSpec<P> {
554 VtxoSpec {
555 policy: self.policy.clone(),
556 amount: self.amount,
557 expiry_height: self.expiry_height,
558 server_pubkey: self.server_pubkey,
559 exit_delta: self.exit_delta,
560 }
561 }
562
563 pub fn transactions(&self) -> VtxoTxIter<'_, P> {
565 VtxoTxIter::new(self)
566 }
567
568 pub fn validate(
573 &self,
574 chain_anchor_tx: &Transaction,
575 ) -> Result<(), VtxoValidationError> {
576 self::validation::validate(self, chain_anchor_tx)
577 }
578
579 pub fn validate_unsigned(
581 &self,
582 chain_anchor_tx: &Transaction,
583 ) -> Result<(), VtxoValidationError> {
584 self::validation::validate_unsigned(self, chain_anchor_tx)
585 }
586}
587
588impl Vtxo {
589 pub fn user_pubkey(&self) -> PublicKey {
591 self.policy.user_pubkey()
592 }
593
594 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
598 self.policy.arkoor_pubkey()
599 }
600
601 pub(crate) fn forfeit_agg_pubkey(&self) -> XOnlyPublicKey {
604 let ret = musig::combine_keys([self.user_pubkey(), self.server_pubkey()]);
605 debug_assert_eq!(ret, self.output_taproot().internal_key());
606 ret
607 }
608
609 #[cfg(any(test, feature = "test-util"))]
611 pub fn finalize_hark_leaf(
612 &mut self,
613 user_key: &bitcoin::secp256k1::Keypair,
614 server_key: &bitcoin::secp256k1::Keypair,
615 chain_anchor: &Transaction,
616 unlock_preimage: UnlockPreimage,
617 ) {
618 use crate::tree::signed::{LeafVtxoCosignContext, LeafVtxoCosignResponse};
619
620 let (ctx, req) = LeafVtxoCosignContext::new(self, chain_anchor, user_key);
622 let cosign = LeafVtxoCosignResponse::new_cosign(&req, self, chain_anchor, server_key);
623 assert!(ctx.finalize(self, cosign));
624 assert!(self.provide_unlock_preimage(unlock_preimage));
626 }
627}
628
629impl Vtxo<ServerVtxoPolicy> {
630 pub fn try_into_user_vtxo(self) -> Result<Vtxo, ServerVtxo> {
634 if let Some(p) = self.policy.clone().into_user_policy() {
635 Ok(Vtxo {
636 policy: p,
637 amount: self.amount,
638 expiry_height: self.expiry_height,
639 server_pubkey: self.server_pubkey,
640 exit_delta: self.exit_delta,
641 anchor_point: self.anchor_point,
642 genesis: self.genesis,
643 point: self.point,
644 })
645 } else {
646 Err(self)
647 }
648 }
649}
650
651impl<P: Policy> PartialEq for Vtxo<P> {
652 fn eq(&self, other: &Self) -> bool {
653 PartialEq::eq(&self.id(), &other.id())
654 }
655}
656
657impl<P: Policy> Eq for Vtxo<P> {}
658
659impl<P: Policy> PartialOrd for Vtxo<P> {
660 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
661 PartialOrd::partial_cmp(&self.id(), &other.id())
662 }
663}
664
665impl<P: Policy> Ord for Vtxo<P> {
666 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
667 Ord::cmp(&self.id(), &other.id())
668 }
669}
670
671impl<P: Policy> std::hash::Hash for Vtxo<P> {
672 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
673 std::hash::Hash::hash(&self.id(), state)
674 }
675}
676
677impl AsRef<Vtxo> for Vtxo {
678 fn as_ref(&self) -> &Vtxo {
679 self
680 }
681}
682
683impl From<Vtxo> for ServerVtxo {
684 fn from(vtxo: Vtxo) -> ServerVtxo {
685 ServerVtxo {
686 policy: vtxo.policy.into(),
687 amount: vtxo.amount,
688 expiry_height: vtxo.expiry_height,
689 server_pubkey: vtxo.server_pubkey,
690 exit_delta: vtxo.exit_delta,
691 anchor_point: vtxo.anchor_point,
692 genesis: vtxo.genesis,
693 point: vtxo.point,
694 }
695 }
696}
697
698pub trait VtxoRef {
700 fn vtxo_id(&self) -> VtxoId;
702
703 fn vtxo(&self) -> Option<&Vtxo>;
705}
706
707impl VtxoRef for VtxoId {
708 fn vtxo_id(&self) -> VtxoId { *self }
709 fn vtxo(&self) -> Option<&Vtxo> { None }
710}
711
712impl<'a> VtxoRef for &'a VtxoId {
713 fn vtxo_id(&self) -> VtxoId { **self }
714 fn vtxo(&self) -> Option<&Vtxo> { None }
715}
716
717impl VtxoRef for Vtxo {
718 fn vtxo_id(&self) -> VtxoId { self.id() }
719 fn vtxo(&self) -> Option<&Vtxo> { Some(self) }
720}
721
722impl<'a> VtxoRef for &'a Vtxo {
723 fn vtxo_id(&self) -> VtxoId { self.id() }
724 fn vtxo(&self) -> Option<&Vtxo> { Some(*self) }
725}
726
727const VTXO_POLICY_PUBKEY: u8 = 0x00;
729
730const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
732
733const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
735
736const VTXO_POLICY_CHECKPOINT: u8 = 0x03;
738
739const VTXO_POLICY_EXPIRY: u8 = 0x04;
741
742impl ProtocolEncoding for VtxoPolicy {
743 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
744 match self {
745 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
746 w.emit_u8(VTXO_POLICY_PUBKEY)?;
747 user_pubkey.encode(w)?;
748 },
749 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
750 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
751 user_pubkey.encode(w)?;
752 payment_hash.to_sha256_hash().encode(w)?;
753 w.emit_u32(*htlc_expiry)?;
754 },
755 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
756 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
757 }) => {
758 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
759 user_pubkey.encode(w)?;
760 payment_hash.to_sha256_hash().encode(w)?;
761 w.emit_u32(*htlc_expiry)?;
762 w.emit_u16(*htlc_expiry_delta)?;
763 },
764 }
765 Ok(())
766 }
767
768 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
769 let type_byte = r.read_u8()?;
770 decode_vtxo_policy(type_byte, r)
771 }
772}
773
774fn decode_vtxo_policy<R: io::Read + ?Sized>(
778 type_byte: u8,
779 r: &mut R,
780) -> Result<VtxoPolicy, ProtocolDecodingError> {
781 match type_byte {
782 VTXO_POLICY_PUBKEY => {
783 let user_pubkey = PublicKey::decode(r)?;
784 Ok(VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
785 },
786 VTXO_POLICY_SERVER_HTLC_SEND => {
787 let user_pubkey = PublicKey::decode(r)?;
788 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
789 let htlc_expiry = r.read_u32()?;
790 Ok(VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
791 },
792 VTXO_POLICY_SERVER_HTLC_RECV => {
793 let user_pubkey = PublicKey::decode(r)?;
794 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
795 let htlc_expiry = r.read_u32()?;
796 let htlc_expiry_delta = r.read_u16()?;
797 Ok(VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
798 },
799 v => Err(ProtocolDecodingError::invalid(format_args!(
800 "invalid VtxoPolicy type byte: {v:#x}",
801 ))),
802 }
803}
804
805impl ProtocolEncoding for ServerVtxoPolicy {
806 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
807 match self {
808 Self::User(p) => p.encode(w)?,
809 Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }) => {
810 w.emit_u8(VTXO_POLICY_CHECKPOINT)?;
811 user_pubkey.encode(w)?;
812 },
813 Self::Expiry(ExpiryVtxoPolicy { internal_key }) => {
814 w.emit_u8(VTXO_POLICY_EXPIRY)?;
815 internal_key.encode(w)?;
816 },
817 }
818 Ok(())
819 }
820
821 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
822 let type_byte = r.read_u8()?;
823 match type_byte {
824 VTXO_POLICY_PUBKEY | VTXO_POLICY_SERVER_HTLC_SEND | VTXO_POLICY_SERVER_HTLC_RECV => {
825 Ok(Self::User(decode_vtxo_policy(type_byte, r)?))
826 },
827 VTXO_POLICY_CHECKPOINT => {
828 let user_pubkey = PublicKey::decode(r)?;
829 Ok(Self::Checkpoint(CheckpointVtxoPolicy { user_pubkey }))
830 },
831 VTXO_POLICY_EXPIRY => {
832 let internal_key = XOnlyPublicKey::decode(r)?;
833 Ok(Self::Expiry(ExpiryVtxoPolicy { internal_key }))
834 },
835 v => Err(ProtocolDecodingError::invalid(format_args!(
836 "invalid ServerVtxoPolicy type byte: {v:#x}",
837 ))),
838 }
839 }
840}
841
842const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
844
845const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
847
848const GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED: u8 = 3;
850
851impl ProtocolEncoding for GenesisTransition {
852 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
853 match self {
854 Self::Cosigned(t) => {
855 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
856 w.emit_compact_size(t.pubkeys.len() as u64)?;
857 for pk in t.pubkeys.iter() {
858 pk.encode(w)?;
859 }
860 t.signature.encode(w)?;
861 },
862 Self::HashLockedCosigned(t) => {
863 w.emit_u8(GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED)?;
864 t.user_pubkey.encode(w)?;
865 t.signature.encode(w)?;
866 match t.unlock {
867 MaybePreimage::Preimage(p) => {
868 w.emit_u8(0)?;
869 w.emit_slice(&p[..])?;
870 },
871 MaybePreimage::Hash(h) => {
872 w.emit_u8(1)?;
873 w.emit_slice(&h[..])?;
874 },
875 }
876 },
877 Self::Arkoor(t) => {
878 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
879 w.emit_compact_size(t.client_cosigners.len() as u64)?;
880 for cosigner in &t.client_cosigners {
881 cosigner.encode(w)?;
882 };
883 t.tap_tweak.encode(w)?;
884 t.signature.encode(w)?;
885 },
886 }
887 Ok(())
888 }
889
890 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
891 match r.read_u8()? {
892 GENESIS_TRANSITION_TYPE_COSIGNED => {
893 let nb_pubkeys = r.read_compact_size()? as usize;
894 let mut pubkeys = Vec::with_capacity(nb_pubkeys);
895 for _ in 0..nb_pubkeys {
896 pubkeys.push(PublicKey::decode(r)?);
897 }
898 let signature = Option::<schnorr::Signature>::decode(r)?;
899 Ok(Self::new_cosigned(pubkeys, signature))
900 },
901 GENESIS_TRANSITION_TYPE_HASH_LOCKED_COSIGNED => {
902 let user_pubkey = PublicKey::decode(r)?;
903 let signature = Option::<schnorr::Signature>::decode(r)?;
904 let unlock = match r.read_u8()? {
905 0 => MaybePreimage::Preimage(r.read_byte_array()?),
906 1 => MaybePreimage::Hash(ProtocolEncoding::decode(r)?),
907 v => return Err(ProtocolDecodingError::invalid(format_args!(
908 "invalid MaybePreimage type byte: {v:#x}",
909 ))),
910 };
911 Ok(Self::new_hash_locked_cosigned(user_pubkey, signature, unlock))
912 },
913 GENESIS_TRANSITION_TYPE_ARKOOR => {
914 let nb_cosigners = r.read_compact_size()? as usize;
915 let mut cosigners = Vec::with_capacity(nb_cosigners);
916 for _ in 0..nb_cosigners {
917 cosigners.push(PublicKey::decode(r)?);
918 }
919 let taptweak = TapTweakHash::decode(r)?;
920 let signature = Option::<schnorr::Signature>::decode(r)?;
921 Ok(Self::new_arkoor(cosigners, taptweak, signature))
922 },
923 v => Err(ProtocolDecodingError::invalid(format_args!(
924 "invalid GenesisTransistion type byte: {v:#x}",
925 ))),
926 }
927 }
928}
929
930impl<P: Policy + ProtocolEncoding> ProtocolEncoding for Vtxo<P> {
931 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
932 w.emit_u16(VTXO_ENCODING_VERSION)?;
933 w.emit_u64(self.amount.to_sat())?;
934 w.emit_u32(self.expiry_height)?;
935 self.server_pubkey.encode(w)?;
936 w.emit_u16(self.exit_delta)?;
937 self.anchor_point.encode(w)?;
938
939 w.emit_compact_size(self.genesis.len() as u64)?;
940 for item in &self.genesis {
941 item.transition.encode(w)?;
942 let nb_outputs = item.other_outputs.len() + 1;
943 w.emit_u8(nb_outputs.try_into()
944 .map_err(|_| io::Error::other("too many outputs on genesis transaction"))?)?;
945 w.emit_u8(item.output_idx)?;
946 for txout in &item.other_outputs {
947 txout.encode(w)?;
948 }
949 }
950
951 self.policy.encode(w)?;
952 self.point.encode(w)?;
953 Ok(())
954 }
955
956 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
957 let version = r.read_u16()?;
958 if version != VTXO_ENCODING_VERSION {
959 return Err(ProtocolDecodingError::invalid(format_args!(
960 "invalid Vtxo encoding version byte: {version:#x}",
961 )));
962 }
963
964 let amount = Amount::from_sat(r.read_u64()?);
965 let expiry_height = r.read_u32()?;
966 let server_pubkey = PublicKey::decode(r)?;
967 let exit_delta = r.read_u16()?;
968 let anchor_point = OutPoint::decode(r)?;
969
970 let nb_genesis_items = r.read_compact_size()? as usize;
971 let mut genesis = Vec::with_capacity(nb_genesis_items);
972 for _ in 0..nb_genesis_items {
973 let transition = GenesisTransition::decode(r)?;
974 let nb_outputs = r.read_u8()? as usize;
975 let output_idx = r.read_u8()?;
976 let nb_other = nb_outputs.checked_sub(1)
977 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
978 let mut other_outputs = Vec::with_capacity(nb_other);
979 for _ in 0..nb_other {
980 other_outputs.push(TxOut::decode(r)?);
981 }
982 genesis.push(GenesisItem { transition, output_idx, other_outputs });
983 }
984
985 let policy = P::decode(r)?;
986 let point = OutPoint::decode(r)?;
987
988 Ok(Self {
989 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy, point,
990 })
991 }
992}
993
994
995#[cfg(test)]
996mod test {
997 use bitcoin::consensus::encode::serialize_hex;
998 use bitcoin::hex::DisplayHex;
999
1000 use crate::test_util::encoding_roundtrip;
1001 use crate::test_util::dummy::{DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1002 use crate::test_util::vectors::{generate_vtxo_vectors, VTXO_VECTORS};
1003
1004 use super::*;
1005
1006 #[test]
1007 fn test_generate_vtxo_vectors() {
1008 let g = generate_vtxo_vectors();
1009 println!("\n\ngenerated:");
1012 println!(" anchor_tx: {}", serialize_hex(&g.anchor_tx));
1013 println!(" board_vtxo: {}", g.board_vtxo.serialize().as_hex().to_string());
1014 println!(" arkoor_htlc_out_vtxo: {}", g.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1015 println!(" arkoor2_vtxo: {}", g.arkoor2_vtxo.serialize().as_hex().to_string());
1016 println!(" round_tx: {}", serialize_hex(&g.round_tx));
1017 println!(" round1_vtxo: {}", g.round1_vtxo.serialize().as_hex().to_string());
1018 println!(" round2_vtxo: {}", g.round2_vtxo.serialize().as_hex().to_string());
1019 println!(" arkoor3_vtxo: {}", g.arkoor3_vtxo.serialize().as_hex().to_string());
1020
1021
1022 let v = &*VTXO_VECTORS;
1023 println!("\n\nstatic:");
1024 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1025 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1026 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1027 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1028 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1029 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1030 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1031 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1032
1033 assert_eq!(g.anchor_tx, v.anchor_tx, "anchor_tx does not match");
1034 assert_eq!(g.board_vtxo, v.board_vtxo, "board_vtxo does not match");
1035 assert_eq!(g.arkoor_htlc_out_vtxo, v.arkoor_htlc_out_vtxo, "arkoor_htlc_out_vtxo does not match");
1036 assert_eq!(g.arkoor2_vtxo, v.arkoor2_vtxo, "arkoor2_vtxo does not match");
1037 assert_eq!(g.round_tx, v.round_tx, "round_tx does not match");
1038 assert_eq!(g.round1_vtxo, v.round1_vtxo, "round1_vtxo does not match");
1039 assert_eq!(g.round2_vtxo, v.round2_vtxo, "round2_vtxo does not match");
1040 assert_eq!(g.arkoor3_vtxo, v.arkoor3_vtxo, "arkoor3_vtxo does not match");
1041
1042 assert_eq!(g, *v);
1044 }
1045
1046 #[test]
1047 fn exit_depth() {
1048 let vtxos = &*VTXO_VECTORS;
1049 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1051
1052 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1054
1055 assert_eq!(
1057 vtxos.arkoor_htlc_out_vtxo.exit_depth(),
1058 1 + 1 + 1 ,
1059 );
1060 assert_eq!(
1061 vtxos.arkoor2_vtxo.exit_depth(),
1062 1 + 2 + 2 ,
1063 );
1064 assert_eq!(
1065 vtxos.arkoor3_vtxo.exit_depth(),
1066 3 + 1 + 1 ,
1067 );
1068 }
1069
1070 #[test]
1071 fn test_genesis_length_257() {
1072 let vtxo = Vtxo {
1073 policy: VtxoPolicy::new_pubkey(DUMMY_USER_KEY.public_key()),
1074 amount: Amount::from_sat(10_000),
1075 expiry_height: 101_010,
1076 server_pubkey: DUMMY_SERVER_KEY.public_key(),
1077 exit_delta: 2016,
1078 anchor_point: OutPoint::new(Txid::from_slice(&[1u8; 32]).unwrap(), 1),
1079 genesis: (0..257).map(|_| {
1080 GenesisItem {
1081 transition: GenesisTransition::new_cosigned(
1082 vec![DUMMY_USER_KEY.public_key()],
1083 Some(schnorr::Signature::from_slice(&[2u8; 64]).unwrap()),
1084 ),
1085 output_idx: 0,
1086 other_outputs: vec![],
1087 }
1088 }).collect(),
1089 point: OutPoint::new(Txid::from_slice(&[3u8; 32]).unwrap(), 3),
1090 };
1091 assert_eq!(vtxo.genesis.len(), 257);
1092 encoding_roundtrip(&vtxo);
1093 }
1094
1095 mod genesis_transition_encoding {
1096 use bitcoin::hashes::{sha256, Hash};
1097 use bitcoin::secp256k1::{Keypair, PublicKey};
1098 use bitcoin::taproot::TapTweakHash;
1099 use std::str::FromStr;
1100
1101 use crate::test_util::encoding_roundtrip;
1102 use super::genesis::{
1103 GenesisTransition, CosignedGenesis, HashLockedCosignedGenesis, ArkoorGenesis,
1104 };
1105 use super::MaybePreimage;
1106
1107 fn test_pubkey() -> PublicKey {
1108 Keypair::from_str(
1109 "916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92"
1110 ).unwrap().public_key()
1111 }
1112
1113 fn test_signature() -> bitcoin::secp256k1::schnorr::Signature {
1114 "cc8b93e9f6fbc2506bb85ae8bbb530b178daac49704f5ce2e3ab69c266fd5932\
1115 0b28d028eef212e3b9fdc42cfd2e0760a0359d3ea7d2e9e8cfe2040e3f1b71ea"
1116 .parse().unwrap()
1117 }
1118
1119 #[test]
1120 fn cosigned_with_signature() {
1121 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1122 pubkeys: vec![test_pubkey()],
1123 signature: Some(test_signature()),
1124 });
1125 encoding_roundtrip(&transition);
1126 }
1127
1128 #[test]
1129 fn cosigned_without_signature() {
1130 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1131 pubkeys: vec![test_pubkey()],
1132 signature: None,
1133 });
1134 encoding_roundtrip(&transition);
1135 }
1136
1137 #[test]
1138 fn cosigned_multiple_pubkeys() {
1139 let pk1 = test_pubkey();
1140 let pk2 = Keypair::from_str(
1141 "fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879"
1142 ).unwrap().public_key();
1143
1144 let transition = GenesisTransition::Cosigned(CosignedGenesis {
1145 pubkeys: vec![pk1, pk2],
1146 signature: Some(test_signature()),
1147 });
1148 encoding_roundtrip(&transition);
1149 }
1150
1151 #[test]
1152 fn hash_locked_cosigned_with_preimage() {
1153 let preimage = [0x42u8; 32];
1154 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1155 user_pubkey: test_pubkey(),
1156 signature: Some(test_signature()),
1157 unlock: MaybePreimage::Preimage(preimage),
1158 });
1159 encoding_roundtrip(&transition);
1160 }
1161
1162 #[test]
1163 fn hash_locked_cosigned_with_hash() {
1164 let hash = sha256::Hash::hash(b"test preimage");
1165 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1166 user_pubkey: test_pubkey(),
1167 signature: Some(test_signature()),
1168 unlock: MaybePreimage::Hash(hash),
1169 });
1170 encoding_roundtrip(&transition);
1171 }
1172
1173 #[test]
1174 fn hash_locked_cosigned_without_signature() {
1175 let preimage = [0x42u8; 32];
1176 let transition = GenesisTransition::HashLockedCosigned(HashLockedCosignedGenesis {
1177 user_pubkey: test_pubkey(),
1178 signature: None,
1179 unlock: MaybePreimage::Preimage(preimage),
1180 });
1181 encoding_roundtrip(&transition);
1182 }
1183
1184 #[test]
1185 fn arkoor_with_signature() {
1186 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1187 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1188 client_cosigners: vec![test_pubkey()],
1189 tap_tweak,
1190 signature: Some(test_signature()),
1191 });
1192 encoding_roundtrip(&transition);
1193 }
1194
1195 #[test]
1196 fn arkoor_without_signature() {
1197 let tap_tweak = TapTweakHash::from_slice(&[0xabu8; 32]).unwrap();
1198 let transition = GenesisTransition::Arkoor(ArkoorGenesis {
1199 client_cosigners: vec![test_pubkey()],
1200 tap_tweak,
1201 signature: None,
1202 });
1203 encoding_roundtrip(&transition);
1204 }
1205 }
1206}