1mod validation;
58pub use self::validation::{ValidationResult, VtxoValidationError};
59
60use std::collections::HashSet;
61use std::iter::FusedIterator;
62use std::{fmt, io};
63use std::str::FromStr;
64
65use bitcoin::{
66 taproot, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness
67};
68use bitcoin::absolute::LockTime;
69use bitcoin::hashes::{sha256, Hash};
70use bitcoin::secp256k1::{schnorr, PublicKey};
71
72use bitcoin_ext::{fee, BlockDelta, BlockHeight, TaprootSpendInfoExt};
73
74use crate::{musig, scripts, SECP};
75use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
76use crate::lightning::{server_htlc_receive_taproot, server_htlc_send_taproot, PaymentHash};
77use crate::tree::signed::cosign_taproot;
78
79
80
81pub const EXIT_TX_WEIGHT: Weight = Weight::from_vb_unchecked(124);
83
84const VTXO_CLAIM_INPUT_WEIGHT: Weight = Weight::from_wu(138);
86
87const VTXO_ENCODING_VERSION: u16 = 1;
89
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
92#[error("failed to parse vtxo id, must be 36 bytes")]
93pub struct VtxoIdParseError;
94
95#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
96pub struct VtxoId([u8; 36]);
97
98impl VtxoId {
99 pub const ENCODE_SIZE: usize = 36;
101
102 pub fn from_slice(b: &[u8]) -> Result<VtxoId, VtxoIdParseError> {
103 if b.len() == 36 {
104 let mut ret = [0u8; 36];
105 ret[..].copy_from_slice(&b[0..36]);
106 Ok(Self(ret))
107 } else {
108 Err(VtxoIdParseError)
109 }
110 }
111
112 pub fn utxo(self) -> OutPoint {
113 let vout = [self.0[32], self.0[33], self.0[34], self.0[35]];
114 OutPoint::new(Txid::from_slice(&self.0[0..32]).unwrap(), u32::from_le_bytes(vout))
115 }
116
117 pub fn to_bytes(self) -> [u8; 36] {
118 self.0
119 }
120}
121
122impl From<OutPoint> for VtxoId {
123 fn from(p: OutPoint) -> VtxoId {
124 let mut ret = [0u8; 36];
125 ret[0..32].copy_from_slice(&p.txid[..]);
126 ret[32..].copy_from_slice(&p.vout.to_le_bytes());
127 VtxoId(ret)
128 }
129}
130
131impl AsRef<[u8]> for VtxoId {
132 fn as_ref(&self) -> &[u8] {
133 &self.0
134 }
135}
136
137impl fmt::Display for VtxoId {
138 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 fmt::Display::fmt(&self.utxo(), f)
140 }
141}
142
143impl fmt::Debug for VtxoId {
144 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145 fmt::Display::fmt(self, f)
146 }
147}
148
149impl FromStr for VtxoId {
150 type Err = VtxoIdParseError;
151 fn from_str(s: &str) -> Result<Self, Self::Err> {
152 Ok(OutPoint::from_str(s).map_err(|_| VtxoIdParseError)?.into())
153 }
154}
155
156impl serde::Serialize for VtxoId {
157 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
158 if s.is_human_readable() {
159 s.collect_str(self)
160 } else {
161 s.serialize_bytes(self.as_ref())
162 }
163 }
164}
165
166impl<'de> serde::Deserialize<'de> for VtxoId {
167 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
168 struct Visitor;
169 impl<'de> serde::de::Visitor<'de> for Visitor {
170 type Value = VtxoId;
171 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 write!(f, "a VtxoId")
173 }
174 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
175 VtxoId::from_slice(v).map_err(serde::de::Error::custom)
176 }
177 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
178 VtxoId::from_str(v).map_err(serde::de::Error::custom)
179 }
180 }
181 if d.is_human_readable() {
182 d.deserialize_str(Visitor)
183 } else {
184 d.deserialize_bytes(Visitor)
185 }
186 }
187}
188
189fn exit_clause(
191 user_pubkey: PublicKey,
192 exit_delta: BlockDelta,
193) -> ScriptBuf {
194 scripts::delayed_sign(exit_delta, user_pubkey.x_only_public_key().0)
195}
196
197pub fn exit_taproot(
199 user_pubkey: PublicKey,
200 server_pubkey: PublicKey,
201 exit_delta: BlockDelta,
202) -> taproot::TaprootSpendInfo {
203 let combined_pk = musig::combine_keys([user_pubkey, server_pubkey]);
204 taproot::TaprootBuilder::new()
205 .add_leaf(0, exit_clause(user_pubkey, exit_delta)).unwrap()
206 .finalize(&SECP, combined_pk).unwrap()
207}
208
209pub fn create_exit_tx(
214 prevout: OutPoint,
215 output: TxOut,
216 signature: Option<&schnorr::Signature>,
217) -> Transaction {
218 Transaction {
219 version: bitcoin::transaction::Version(3),
220 lock_time: LockTime::ZERO,
221 input: vec![TxIn {
222 previous_output: prevout,
223 script_sig: ScriptBuf::new(),
224 sequence: Sequence::ZERO,
225 witness: {
226 let mut ret = Witness::new();
227 if let Some(sig) = signature {
228 ret.push(&sig[..]);
229 }
230 ret
231 },
232 }],
233 output: vec![output, fee::fee_anchor()],
234 }
235}
236
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
240pub enum VtxoPolicyKind {
241 Pubkey,
243 ServerHtlcSend,
245 ServerHtlcRecv,
247}
248
249impl fmt::Display for VtxoPolicyKind {
250 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251 match self {
252 Self::Pubkey => f.write_str("pubkey"),
253 Self::ServerHtlcSend => f.write_str("server-htlc-send"),
254 Self::ServerHtlcRecv => f.write_str("server-htlc-receive"),
255 }
256 }
257}
258
259impl FromStr for VtxoPolicyKind {
260 type Err = String;
261 fn from_str(s: &str) -> Result<Self, Self::Err> {
262 Ok(match s {
263 "pubkey" => Self::Pubkey,
264 "server-htlc-send" => Self::ServerHtlcSend,
265 "server-htlc-receive" => Self::ServerHtlcRecv,
266 _ => return Err(format!("unknown VtxoPolicyType: {}", s)),
267 })
268 }
269}
270
271impl serde::Serialize for VtxoPolicyKind {
272 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
273 s.collect_str(self)
274 }
275}
276
277impl<'de> serde::Deserialize<'de> for VtxoPolicyKind {
278 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
279 struct Visitor;
280 impl<'de> serde::de::Visitor<'de> for Visitor {
281 type Value = VtxoPolicyKind;
282 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 write!(f, "a VtxoPolicyType")
284 }
285 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
286 VtxoPolicyKind::from_str(v).map_err(serde::de::Error::custom)
287 }
288 }
289 d.deserialize_str(Visitor)
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
294pub struct PubkeyVtxoPolicy {
295 pub user_pubkey: PublicKey,
296}
297
298impl From<PubkeyVtxoPolicy> for VtxoPolicy {
299 fn from(policy: PubkeyVtxoPolicy) -> Self {
300 Self::Pubkey(policy)
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
305pub struct ServerHtlcSendVtxoPolicy {
306 pub user_pubkey: PublicKey,
307 pub payment_hash: PaymentHash,
308 pub htlc_expiry: BlockHeight,
309}
310
311impl From<ServerHtlcSendVtxoPolicy> for VtxoPolicy {
312 fn from(policy: ServerHtlcSendVtxoPolicy) -> Self {
313 Self::ServerHtlcSend(policy)
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
318pub struct ServerHtlcRecvVtxoPolicy {
319 pub user_pubkey: PublicKey,
320 pub payment_hash: PaymentHash,
321 pub htlc_expiry_delta: BlockDelta,
322 pub htlc_expiry: BlockHeight,
323}
324
325impl From<ServerHtlcRecvVtxoPolicy> for VtxoPolicy {
326 fn from(policy: ServerHtlcRecvVtxoPolicy) -> Self {
327 Self::ServerHtlcRecv(policy)
328 }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
333pub enum VtxoPolicy {
334 Pubkey(PubkeyVtxoPolicy),
342 ServerHtlcSend(ServerHtlcSendVtxoPolicy),
344 ServerHtlcRecv(ServerHtlcRecvVtxoPolicy),
346}
347
348impl VtxoPolicy {
349 pub fn new_pubkey(user_pubkey: PublicKey) -> Self {
350 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey })
351 }
352
353 pub fn new_server_htlc_send(
354 user_pubkey: PublicKey,
355 payment_hash: PaymentHash,
356 htlc_expiry: BlockHeight,
357 ) -> Self {
358 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry })
359 }
360
361 pub fn new_server_htlc_recv(
370 user_pubkey: PublicKey,
371 payment_hash: PaymentHash,
372 htlc_expiry: BlockHeight,
373 htlc_expiry_delta: BlockDelta,
374 ) -> Self {
375 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta })
376 }
377
378 pub fn as_pubkey(&self) -> Option<&PubkeyVtxoPolicy> {
379 match self {
380 Self::Pubkey(v) => Some(v),
381 _ => None,
382 }
383 }
384
385 pub fn as_server_htlc_send(&self) -> Option<&ServerHtlcSendVtxoPolicy> {
386 match self {
387 Self::ServerHtlcSend(v) => Some(v),
388 _ => None,
389 }
390 }
391
392 pub fn as_server_htlc_recv(&self) -> Option<&ServerHtlcRecvVtxoPolicy> {
393 match self {
394 Self::ServerHtlcRecv(v) => Some(v),
395 _ => None,
396 }
397 }
398
399 pub fn policy_type(&self) -> VtxoPolicyKind {
401 match self {
402 Self::Pubkey { .. } => VtxoPolicyKind::Pubkey,
403 Self::ServerHtlcSend { .. } => VtxoPolicyKind::ServerHtlcSend,
404 Self::ServerHtlcRecv { .. } => VtxoPolicyKind::ServerHtlcRecv,
405 }
406 }
407
408 pub fn is_arkoor_compatible(&self) -> bool {
410 match self {
411 Self::Pubkey { .. } => true,
412 Self::ServerHtlcSend { .. } => false,
413 Self::ServerHtlcRecv { .. } => false,
414 }
415 }
416
417 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
420 match self {
421 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => Some(*user_pubkey),
422 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
423 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, .. }) => Some(*user_pubkey),
424 }
425 }
426
427 pub fn user_pubkey(&self) -> PublicKey {
429 match self {
430 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => *user_pubkey,
431 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
432 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, .. }) => *user_pubkey,
433 }
434 }
435
436 pub(crate) fn taproot(
437 &self,
438 server_pubkey: PublicKey,
439 exit_delta: BlockDelta,
440 ) -> taproot::TaprootSpendInfo {
441 match self {
442 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
443 exit_taproot(*user_pubkey, server_pubkey, exit_delta)
444 },
445 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
446 server_htlc_send_taproot(*payment_hash, server_pubkey, *user_pubkey, exit_delta, *htlc_expiry)
447 },
448 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
449 user_pubkey, payment_hash, htlc_expiry_delta, htlc_expiry
450 }) => {
451 server_htlc_receive_taproot(*payment_hash, server_pubkey, *user_pubkey, exit_delta, *htlc_expiry_delta, *htlc_expiry)
452 },
453 }
454 }
455
456 pub fn user_exit_clause(&self, exit_delta: BlockDelta) -> ScriptBuf {
462 match self {
463 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
464 exit_clause(*user_pubkey, exit_delta)
465 },
466 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, htlc_expiry, .. }) => {
467 scripts::delay_timelock_sign(
468 2 * exit_delta, *htlc_expiry, user_pubkey.x_only_public_key().0,
469 )
470 },
471 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
472 user_pubkey, payment_hash, htlc_expiry_delta, ..
473 }) => {
474 scripts::hash_delay_sign(
475 payment_hash.to_sha256_hash(),
476 exit_delta + *htlc_expiry_delta,
477 user_pubkey.x_only_public_key().0,
478 )
479 },
480 }
481 }
482
483 pub(crate) fn script_pubkey(&self, server_pubkey: PublicKey, exit_delta: BlockDelta) -> ScriptBuf {
484 self.taproot(server_pubkey, exit_delta).script_pubkey()
485 }
486
487 pub(crate) fn txout(&self, amount: Amount, server_pubkey: PublicKey, exit_delta: BlockDelta) -> TxOut {
488 TxOut {
489 value: amount,
490 script_pubkey: self.script_pubkey(server_pubkey, exit_delta),
491 }
492 }
493}
494
495#[derive(Debug, Clone, PartialEq, Eq)]
499pub(crate) enum GenesisTransition {
500 Cosigned {
505 pubkeys: Vec<PublicKey>,
510 signature: schnorr::Signature,
511 },
512 Arkoor {
514 policy: VtxoPolicy,
515 signature: Option<schnorr::Signature>,
516 },
517}
518
519impl GenesisTransition {
520 fn input_taproot(
522 &self,
523 server_pubkey: PublicKey,
524 expiry_height: BlockHeight,
525 exit_delta: BlockDelta,
526 ) -> taproot::TaprootSpendInfo {
527 match self {
528 Self::Cosigned { pubkeys, .. } => {
529 let agg_pk = musig::combine_keys(pubkeys.iter().copied());
530 cosign_taproot(agg_pk, server_pubkey, expiry_height)
531 },
532 Self::Arkoor { policy, .. } => policy.taproot(server_pubkey, exit_delta),
533 }
534 }
535
536 fn input_txout(
538 &self,
539 amount: Amount,
540 server_pubkey: PublicKey,
541 expiry_height: BlockHeight,
542 exit_delta: BlockDelta,
543 ) -> TxOut {
544 let taproot = self.input_taproot(server_pubkey, expiry_height, exit_delta);
545 TxOut {
546 value: amount,
547 script_pubkey: taproot.script_pubkey(),
548 }
549 }
550
551 fn witness(&self) -> Witness {
553 match self {
554 Self::Cosigned { signature, .. } => Witness::from_slice(&[&signature[..]]),
555 Self::Arkoor { signature: Some(sig), .. } => Witness::from_slice(&[&sig[..]]),
556 Self::Arkoor { signature: None, .. } => Witness::new(),
557 }
558 }
559
560 fn has_exit(&self) -> bool {
562 match self {
563 Self::Cosigned { .. } => false,
564 Self::Arkoor { .. } => true,
565 }
566 }
567
568 fn is_arkoor(&self) -> bool {
570 match self {
571 Self::Cosigned { .. } => false,
572 Self::Arkoor { .. } => true,
573 }
574 }
575}
576
577#[derive(Debug, Clone, PartialEq, Eq)]
581pub(crate) struct GenesisItem {
582 pub(crate) transition: GenesisTransition,
584 pub(crate) output_idx: u8,
586 pub(crate) other_outputs: Vec<TxOut>,
589}
590
591impl GenesisItem {
592 fn tx(&self, prev: OutPoint, next: TxOut) -> Transaction {
594 Transaction {
595 version: bitcoin::transaction::Version(3),
596 lock_time: bitcoin::absolute::LockTime::ZERO,
597 input: vec![TxIn {
598 previous_output: prev,
599 script_sig: ScriptBuf::new(),
600 sequence: Sequence::ZERO,
601 witness: self.transition.witness(),
602 }],
603 output: {
604 let mut out = Vec::with_capacity(self.other_outputs.len() + 2);
605 out.extend(self.other_outputs.iter().take(self.output_idx as usize).cloned());
606 out.push(next);
607 out.extend(self.other_outputs.iter().skip(self.output_idx as usize).cloned());
608 out.push(fee::fee_anchor());
609 out
610 },
611 }
612 }
613}
614
615#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
618pub struct VtxoTxIterItem {
619 pub tx: Transaction,
621 pub is_exit: bool,
623}
624
625pub struct VtxoTxIter<'a> {
635 vtxo: &'a Vtxo,
636
637 prev: OutPoint,
638 genesis_idx: usize,
639 current_amount: Amount,
640 exit: bool,
643 done: bool,
644}
645
646impl<'a> VtxoTxIter<'a> {
647 fn new(vtxo: &'a Vtxo) -> VtxoTxIter<'a> {
648 let onchain_amount = vtxo.amount() + vtxo.genesis.iter().map(|i| {
650 i.other_outputs.iter().map(|o| o.value).sum()
651 }).sum();
652 VtxoTxIter {
653 prev: vtxo.anchor_point,
654 vtxo: vtxo,
655 genesis_idx: 0,
656 current_amount: onchain_amount,
657 exit: false,
658 done: false,
659 }
660 }
661
662 pub fn first_exit(mut self) -> Option<Transaction> {
663 let mut current = self.next();
664 while !self.exit {
665 current = self.next();
666 }
667
668 current.map(|c| c.tx)
669 }
670}
671
672impl<'a> Iterator for VtxoTxIter<'a> {
673 type Item = VtxoTxIterItem;
674
675 fn next(&mut self) -> Option<Self::Item> {
676 if self.done {
677 return None;
678 }
679
680 let item = self.vtxo.genesis.get(self.genesis_idx).expect("broken impl");
681 let next_amount = self.current_amount.checked_sub(
682 item.other_outputs.iter().map(|o| o.value).sum()
683 ).expect("we calculated this amount beforehand");
684
685 let next_output = if let Some(item) = self.vtxo.genesis.get(self.genesis_idx + 1) {
686 self.exit = self.exit || item.transition.has_exit();
687 item.transition.input_txout(
688 next_amount,
689 self.vtxo.server_pubkey,
690 self.vtxo.expiry_height,
691 self.vtxo.exit_delta,
692 )
693 } else {
694 self.done = true;
696 self.exit = true;
697 self.vtxo.policy.txout(self.vtxo.amount, self.vtxo.server_pubkey, self.vtxo.exit_delta)
698 };
699
700 let tx = item.tx(self.prev, next_output);
701 self.prev = OutPoint::new(tx.compute_txid(), item.output_idx as u32);
702 self.genesis_idx += 1;
703 self.current_amount = next_amount;
704 Some(VtxoTxIterItem { tx, is_exit: self.exit })
705 }
706
707 fn size_hint(&self) -> (usize, Option<usize>) {
708 let len = self.vtxo.genesis.len().saturating_sub(self.genesis_idx);
709 (len, Some(len))
710 }
711}
712
713impl<'a> ExactSizeIterator for VtxoTxIter<'a> {}
714impl<'a> FusedIterator for VtxoTxIter<'a> {}
715
716
717#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
719pub struct VtxoSpec {
720 pub policy: VtxoPolicy,
721 pub amount: Amount,
722 pub expiry_height: BlockHeight,
723 pub server_pubkey: PublicKey,
724 pub exit_delta: BlockDelta,
725}
726
727impl VtxoSpec {
728 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
730 self.policy.taproot(self.server_pubkey, self.exit_delta)
731 }
732
733 pub fn output_script_pubkey(&self) -> ScriptBuf {
735 self.policy.script_pubkey(self.server_pubkey, self.exit_delta)
736 }
737
738 pub fn txout(&self) -> TxOut {
740 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta)
741 }
742}
743
744#[derive(Debug, Clone)]
758pub struct Vtxo {
759 pub(crate) policy: VtxoPolicy,
760 pub(crate) amount: Amount,
761 pub(crate) expiry_height: BlockHeight,
762
763 pub(crate) server_pubkey: PublicKey,
764 pub(crate) exit_delta: BlockDelta,
765
766 pub(crate) anchor_point: OutPoint,
767 pub(crate) genesis: Vec<GenesisItem>,
768
769 pub(crate) point: OutPoint,
776}
777
778impl Vtxo {
779 pub fn id(&self) -> VtxoId {
783 self.point.into()
784 }
785
786 pub fn spec(&self) -> VtxoSpec {
788 VtxoSpec {
789 policy: self.policy.clone(),
790 amount: self.amount,
791 expiry_height: self.expiry_height,
792 server_pubkey: self.server_pubkey,
793 exit_delta: self.exit_delta,
794 }
795 }
796
797 pub fn point(&self) -> OutPoint {
801 self.point
802 }
803
804 pub fn amount(&self) -> Amount {
806 self.amount
807 }
808
809 pub fn chain_anchor(&self) -> OutPoint {
813 self.anchor_point
814 }
815
816 pub fn policy(&self) -> &VtxoPolicy {
818 &self.policy
819 }
820
821 pub fn policy_type(&self) -> VtxoPolicyKind {
823 self.policy.policy_type()
824 }
825
826 pub fn expiry_height(&self) -> BlockHeight {
828 self.expiry_height
829 }
830
831 pub fn server_pubkey(&self) -> PublicKey {
833 self.server_pubkey
834 }
835
836 pub fn exit_delta(&self) -> BlockDelta {
838 self.exit_delta
839 }
840
841 pub fn exit_depth(&self) -> u16 {
843 self.genesis.len() as u16
844 }
845
846 pub fn arkoor_depth(&self) -> u16 {
848 self.genesis.iter().rev().take_while(|item| item.transition.is_arkoor()).count() as u16
851 }
852
853 pub fn server_htlc_out_payment_hash(&self) -> Option<PaymentHash> {
855 match self.policy {
856 VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy { payment_hash, .. }) => Some(payment_hash),
857 VtxoPolicy::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { payment_hash, .. }) => Some(payment_hash),
858 VtxoPolicy::Pubkey { .. } => None,
859 }
860 }
861
862 pub fn is_arkoor_compatible(&self) -> bool {
864 self.genesis.iter().all(|i| match i.transition {
865 GenesisTransition::Cosigned { .. } => true,
866 GenesisTransition::Arkoor { ref policy, .. } => policy.is_arkoor_compatible(),
867 }) && self.policy.is_arkoor_compatible()
868 }
869
870 pub fn arkoor_pubkey(&self) -> Option<PublicKey> {
873 self.policy.arkoor_pubkey()
874 }
875
876 pub fn past_arkoor_pubkeys(&self) -> impl Iterator<Item = PublicKey> + '_ {
881 self.genesis.iter().filter_map(|g| {
882 match g.transition {
883 GenesisTransition::Arkoor { ref policy, .. } => policy.arkoor_pubkey(),
886 _ => None,
887 }
888 })
889 }
890
891 pub fn user_pubkey(&self) -> PublicKey {
893 self.policy.user_pubkey()
894 }
895
896 pub fn output_taproot(&self) -> taproot::TaprootSpendInfo {
898 self.policy.taproot(self.server_pubkey, self.exit_delta)
899 }
900
901 pub fn output_script_pubkey(&self) -> ScriptBuf {
903 self.policy.script_pubkey(self.server_pubkey, self.exit_delta)
904 }
905
906 pub fn txout(&self) -> TxOut {
908 self.policy.txout(self.amount, self.server_pubkey, self.exit_delta)
909 }
910
911 pub fn is_arkoor(&self) -> bool {
914 self.genesis.iter().any(|t| t.transition.has_exit())
915 }
916
917 pub fn transactions(&self) -> VtxoTxIter<'_> {
919 VtxoTxIter::new(self)
920 }
921
922 pub fn claim_satisfaction_weight(&self) -> Weight {
925 match self.policy {
926 VtxoPolicy::Pubkey { .. } => VTXO_CLAIM_INPUT_WEIGHT,
927 VtxoPolicy::ServerHtlcSend { .. } => VTXO_CLAIM_INPUT_WEIGHT,
931 VtxoPolicy::ServerHtlcRecv { .. } => VTXO_CLAIM_INPUT_WEIGHT,
932 }
933 }
934
935 pub fn round_cosign_pubkeys(&self) -> Vec<PublicKey> {
938 let mut ret = Option::<Vec<PublicKey>>::None;
939
940 for item in self.genesis.iter().rev() {
944 match &item.transition {
945 GenesisTransition::Cosigned { pubkeys, .. } => {
946 if let Some(ref mut keys) = ret {
947 keys.retain(|p| pubkeys.contains(p));
948 if keys.is_empty() {
949 break;
950 }
951 } else {
952 ret = Some(pubkeys.clone());
954 }
955 },
956 GenesisTransition::Arkoor { .. } => {},
957 }
958 }
959
960 ret.unwrap_or_default()
961 }
962
963 pub fn arkoor_pubkeys(&self) -> HashSet<PublicKey> {
966 self.genesis.iter().filter_map(|i| match &i.transition {
967 GenesisTransition::Arkoor { policy, .. } => policy.arkoor_pubkey(),
968 GenesisTransition::Cosigned { .. } => None,
969 }).collect()
970 }
971
972 pub fn validate(
977 &self,
978 chain_anchor_tx: &Transaction,
979 ) -> Result<ValidationResult, VtxoValidationError> {
980 self::validation::validate(&self, chain_anchor_tx)
981 }
982}
983
984impl PartialEq for Vtxo {
985 fn eq(&self, other: &Self) -> bool {
986 PartialEq::eq(&self.id(), &other.id())
987 }
988}
989
990impl Eq for Vtxo {}
991
992impl PartialOrd for Vtxo {
993 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
994 PartialOrd::partial_cmp(&self.id(), &other.id())
995 }
996}
997
998impl Ord for Vtxo {
999 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1000 Ord::cmp(&self.id(), &other.id())
1001 }
1002}
1003
1004impl std::hash::Hash for Vtxo {
1005 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1006 std::hash::Hash::hash(&self.id(), state)
1007 }
1008}
1009
1010pub trait VtxoRef {
1012 fn vtxo_id(&self) -> VtxoId;
1014
1015 fn vtxo(&self) -> Option<&Vtxo>;
1017}
1018
1019impl VtxoRef for VtxoId {
1020 fn vtxo_id(&self) -> VtxoId { *self }
1021 fn vtxo(&self) -> Option<&Vtxo> { None }
1022}
1023
1024impl<'a> VtxoRef for &'a VtxoId {
1025 fn vtxo_id(&self) -> VtxoId { **self }
1026 fn vtxo(&self) -> Option<&Vtxo> { None }
1027}
1028
1029impl VtxoRef for Vtxo {
1030 fn vtxo_id(&self) -> VtxoId { self.id() }
1031 fn vtxo(&self) -> Option<&Vtxo> { Some(self) }
1032}
1033
1034impl<'a> VtxoRef for &'a Vtxo {
1035 fn vtxo_id(&self) -> VtxoId { self.id() }
1036 fn vtxo(&self) -> Option<&Vtxo> { Some(*self) }
1037}
1038
1039const VTXO_POLICY_PUBKEY: u8 = 0x00;
1041
1042const VTXO_POLICY_SERVER_HTLC_SEND: u8 = 0x01;
1044
1045const VTXO_POLICY_SERVER_HTLC_RECV: u8 = 0x02;
1047
1048impl ProtocolEncoding for VtxoPolicy {
1049 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1050 match self {
1051 Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }) => {
1052 w.emit_u8(VTXO_POLICY_PUBKEY)?;
1053 user_pubkey.encode(w)?;
1054 },
1055 Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }) => {
1056 w.emit_u8(VTXO_POLICY_SERVER_HTLC_SEND)?;
1057 user_pubkey.encode(w)?;
1058 payment_hash.to_sha256_hash().encode(w)?;
1059 w.emit_u32(*htlc_expiry)?;
1060 },
1061 Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy {
1062 user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta,
1063 }) => {
1064 w.emit_u8(VTXO_POLICY_SERVER_HTLC_RECV)?;
1065 user_pubkey.encode(w)?;
1066 payment_hash.to_sha256_hash().encode(w)?;
1067 w.emit_u32(*htlc_expiry)?;
1068 w.emit_u16(*htlc_expiry_delta)?;
1069 },
1070 }
1071 Ok(())
1072 }
1073
1074 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1075 match r.read_u8()? {
1076 VTXO_POLICY_PUBKEY => {
1077 let user_pubkey = PublicKey::decode(r)?;
1078 Ok(Self::Pubkey(PubkeyVtxoPolicy { user_pubkey }))
1079 },
1080 VTXO_POLICY_SERVER_HTLC_SEND => {
1081 let user_pubkey = PublicKey::decode(r)?;
1082 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1083 let htlc_expiry = r.read_u32()?;
1084 Ok(Self::ServerHtlcSend(ServerHtlcSendVtxoPolicy { user_pubkey, payment_hash, htlc_expiry }))
1085 },
1086 VTXO_POLICY_SERVER_HTLC_RECV => {
1087 let user_pubkey = PublicKey::decode(r)?;
1088 let payment_hash = PaymentHash::from(sha256::Hash::decode(r)?.to_byte_array());
1089 let htlc_expiry = r.read_u32()?;
1090 let htlc_expiry_delta = r.read_u16()?;
1091 Ok(Self::ServerHtlcRecv(ServerHtlcRecvVtxoPolicy { user_pubkey, payment_hash, htlc_expiry, htlc_expiry_delta }))
1092 },
1093 v => Err(ProtocolDecodingError::invalid(format_args!(
1094 "invalid VtxoType type byte: {v:#x}",
1095 ))),
1096 }
1097 }
1098}
1099
1100const GENESIS_TRANSITION_TYPE_COSIGNED: u8 = 1;
1102
1103const GENESIS_TRANSITION_TYPE_ARKOOR: u8 = 2;
1105
1106impl ProtocolEncoding for GenesisTransition {
1107 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1108 match self {
1109 Self::Cosigned { pubkeys, signature } => {
1110 w.emit_u8(GENESIS_TRANSITION_TYPE_COSIGNED)?;
1111 w.emit_u16(pubkeys.len().try_into().expect("cosign pubkey length overflow"))?;
1112 for pk in pubkeys {
1113 pk.encode(w)?;
1114 }
1115 signature.encode(w)?;
1116 },
1117 Self::Arkoor { policy, signature } => {
1118 w.emit_u8(GENESIS_TRANSITION_TYPE_ARKOOR)?;
1119 policy.encode(w)?;
1120 signature.encode(w)?;
1121 },
1122 }
1123 Ok(())
1124 }
1125
1126 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1127 match r.read_u8()? {
1128 GENESIS_TRANSITION_TYPE_COSIGNED => {
1129 let nb_pubkeys = r.read_u16()? as usize;
1130 let mut pubkeys = Vec::with_capacity(nb_pubkeys);
1131 for _ in 0..nb_pubkeys {
1132 pubkeys.push(PublicKey::decode(r)?);
1133 }
1134 let signature = schnorr::Signature::decode(r)?;
1135 Ok(Self::Cosigned { pubkeys, signature })
1136 },
1137 GENESIS_TRANSITION_TYPE_ARKOOR => {
1138 let policy = VtxoPolicy::decode(r)?;
1139 let signature = Option::<schnorr::Signature>::decode(r)?;
1140 Ok(Self::Arkoor { policy, signature })
1141 },
1142 v => Err(ProtocolDecodingError::invalid(format_args!(
1143 "invalid GenesisTransistion type byte: {v:#x}",
1144 ))),
1145 }
1146 }
1147}
1148
1149impl ProtocolEncoding for Vtxo {
1150 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1151 w.emit_u16(VTXO_ENCODING_VERSION)?;
1152 w.emit_u64(self.amount.to_sat())?;
1153 w.emit_u32(self.expiry_height)?;
1154 self.server_pubkey.encode(w)?;
1155 w.emit_u16(self.exit_delta)?;
1156 self.anchor_point.encode(w)?;
1157
1158 w.emit_u8(self.genesis.len().try_into().expect("genesis length overflow"))?;
1159 for item in &self.genesis {
1160 item.transition.encode(w)?;
1161 let nb_outputs = item.other_outputs.len() + 1;
1162 w.emit_u8(nb_outputs.try_into().expect("genesis item output length overflow"))?;
1163 w.emit_u8(item.output_idx)?;
1164 for txout in &item.other_outputs {
1165 txout.encode(w)?;
1166 }
1167 }
1168
1169 self.policy.encode(w)?;
1170 self.point.encode(w)?;
1171 Ok(())
1172 }
1173
1174 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1175 let version = r.read_u16()?;
1176 if version != VTXO_ENCODING_VERSION {
1177 return Err(ProtocolDecodingError::invalid(format_args!(
1178 "invalid Vtxo encoding version byte: {version:#x}",
1179 )));
1180 }
1181
1182 let amount = Amount::from_sat(r.read_u64()?);
1183 let expiry_height = r.read_u32()?;
1184 let server_pubkey = PublicKey::decode(r)?;
1185 let exit_delta = r.read_u16()?;
1186 let anchor_point = OutPoint::decode(r)?;
1187
1188 let nb_genesis_items = r.read_u8()? as usize;
1189 let mut genesis = Vec::with_capacity(nb_genesis_items);
1190 for _ in 0..nb_genesis_items {
1191 let transition = GenesisTransition::decode(r)?;
1192 let nb_outputs = r.read_u8()? as usize;
1193 let output_idx = r.read_u8()?;
1194 let nb_other = nb_outputs.checked_sub(1)
1195 .ok_or_else(|| ProtocolDecodingError::invalid("genesis item with 0 outputs"))?;
1196 let mut other_outputs = Vec::with_capacity(nb_other);
1197 for _ in 0..nb_other {
1198 other_outputs.push(TxOut::decode(r)?);
1199 }
1200 genesis.push(GenesisItem { transition, output_idx, other_outputs });
1201 }
1202
1203 let output = VtxoPolicy::decode(r)?;
1204 let point = OutPoint::decode(r)?;
1205
1206 Ok(Self {
1207 amount, expiry_height, server_pubkey, exit_delta, anchor_point, genesis, policy: output, point,
1208 })
1209 }
1210}
1211
1212
1213#[cfg(any(test, feature = "test-util"))]
1214pub mod test {
1215 use std::iter;
1216 use std::collections::HashMap;
1217
1218 use bitcoin::consensus::encode::{deserialize_hex, serialize_hex};
1219 use bitcoin::hex::DisplayHex;
1220 use bitcoin::secp256k1::Keypair;
1221 use bitcoin::transaction::Version;
1222
1223 use crate::arkoor::ArkoorBuilder;
1224 use crate::board::BoardBuilder;
1225 use crate::encode::test::encoding_roundtrip;
1226 use crate::tree::signed::VtxoTreeSpec;
1227 use crate::{SECP, SignedVtxoRequest, VtxoRequest};
1228
1229 use super::*;
1230
1231 #[allow(unused)]
1232 #[macro_export]
1233 macro_rules! assert_eq_vtxos {
1234 ($v1:expr, $v2:expr) => {
1235 let v1 = &$v1;
1236 let v2 = &$v2;
1237 assert_eq!(
1238 v1.serialize().as_hex().to_string(),
1239 v2.serialize().as_hex().to_string(),
1240 "vtxo {} != {}", v1.id(), v2.id(),
1241 );
1242 };
1243 }
1244
1245 #[derive(Debug, PartialEq, Eq)]
1246 pub struct VtxoTestVectors {
1247 pub anchor_tx: Transaction,
1248 pub board_vtxo: Vtxo,
1249
1250 pub arkoor_htlc_out_vtxo: Vtxo,
1251 pub arkoor2_vtxo: Vtxo,
1252
1253 pub round_tx: Transaction,
1254 pub round1_vtxo: Vtxo,
1255 pub round2_vtxo: Vtxo,
1256
1257 pub arkoor3_vtxo: Vtxo,
1258 }
1259
1260 #[allow(unused)] fn generate_vtxo_vectors() -> VtxoTestVectors {
1262 let expiry_height = 101_010;
1263 let exit_delta = 2016;
1264 let server_pubkey = Keypair::from_str("916da686cedaee9a9bfb731b77439f2a3f1df8664e16488fba46b8d2bfe15e92").unwrap();
1265 let board_user_key = Keypair::from_str("fab9e598081a3e74b2233d470c4ad87bcc285b6912ed929568e62ac0e9409879").unwrap();
1266 let amount = Amount::from_sat(10_000);
1267 let builder = BoardBuilder::new(
1268 board_user_key.public_key(),
1269 expiry_height,
1270 server_pubkey.public_key(),
1271 exit_delta,
1272 );
1273 let anchor_tx = Transaction {
1274 version: Version::TWO,
1275 lock_time: LockTime::ZERO,
1276 input: vec![TxIn {
1277 previous_output: OutPoint::null(),
1278 script_sig: ScriptBuf::new(),
1279 sequence: Sequence::ZERO,
1280 witness: Witness::new(),
1281 }],
1282 output: vec![TxOut {
1283 value: Amount::from_sat(10_000),
1284 script_pubkey: builder.funding_script_pubkey(),
1285 }],
1286 };
1287 println!("chain anchor tx: {}", serialize_hex(&anchor_tx));
1288 let anchor_point = OutPoint::new(anchor_tx.compute_txid(), 0);
1289 let builder = builder.set_funding_details(amount, anchor_point)
1290 .generate_user_nonces();
1291
1292 let board_cosign = {
1293 BoardBuilder::new_for_cosign(
1294 builder.user_pubkey,
1295 builder.expiry_height,
1296 builder.server_pubkey,
1297 builder.exit_delta,
1298 amount,
1299 anchor_point,
1300 *builder.user_pub_nonce(),
1301 ).server_cosign(&server_pubkey)
1302 };
1303
1304 assert!(builder.verify_cosign_response(&board_cosign));
1305 let board_vtxo = builder.build_vtxo(&board_cosign, &board_user_key).unwrap();
1306 encoding_roundtrip(&board_vtxo);
1307 println!("board vtxo: {}", board_vtxo.serialize().as_hex());
1308
1309 let arkoor_htlc_out_user_key = Keypair::from_str("33b6f3ede430a1a53229f55da7117242d8392cbfc64a57249ba70731dba71408").unwrap();
1312 let payment_hash = PaymentHash::from(sha256::Hash::hash("arkoor1".as_bytes()).to_byte_array());
1313 let arkoor1out1 = VtxoRequest {
1314 amount: Amount::from_sat(9000),
1315 policy: VtxoPolicy::ServerHtlcSend(ServerHtlcSendVtxoPolicy {
1316 user_pubkey: arkoor_htlc_out_user_key.public_key(),
1317 payment_hash,
1318 htlc_expiry: expiry_height - 1000,
1319 }),
1320 };
1321 let arkoor1out2 = VtxoRequest {
1322 amount: Amount::from_sat(1000),
1323 policy: VtxoPolicy::new_pubkey("0229b7de0ce4d573192d002a6f9fd1109e00f7bae52bf10780d6f6e73e12a8390f".parse().unwrap()),
1324 };
1325 let outputs = [&arkoor1out1, &arkoor1out2];
1326 let (sec_nonce, pub_nonce) = musig::nonce_pair(&board_user_key);
1327 let builder = ArkoorBuilder::new(&board_vtxo, &pub_nonce, &outputs).unwrap();
1328 let cosign = builder.server_cosign(&server_pubkey);
1329 assert!(builder.verify_cosign_response(&cosign));
1330 let [arkoor_htlc_out_vtxo, change] = builder.build_vtxos(
1331 sec_nonce, &board_user_key, &cosign,
1332 ).unwrap().try_into().unwrap();
1333 encoding_roundtrip(&arkoor_htlc_out_vtxo);
1334 encoding_roundtrip(&change);
1335 println!("arkoor1_vtxo: {}", arkoor_htlc_out_vtxo.serialize().as_hex());
1336
1337 let arkoor2_user_key = Keypair::from_str("fcc43a4f03356092a945ca1d7218503156bed3f94c2fa224578ce5b158fbf5a6").unwrap();
1340 let arkoor2out1 = VtxoRequest {
1341 amount: Amount::from_sat(8000),
1342 policy: VtxoPolicy::new_pubkey(arkoor2_user_key.public_key()),
1343 };
1344 let arkoor2out2 = VtxoRequest {
1345 amount: Amount::from_sat(1000),
1346 policy: VtxoPolicy::new_pubkey("037039dc4f4b16e78059d2d56eb98d181cb1bdff2675694d39d92c4a2ea08ced88".parse().unwrap()),
1347 };
1348 let outputs = [&arkoor2out1, &arkoor2out2];
1349 let (sec_nonce, pub_nonce) = musig::nonce_pair(&arkoor_htlc_out_user_key);
1350 let builder = ArkoorBuilder::new(&arkoor_htlc_out_vtxo, &pub_nonce, &outputs).unwrap();
1351 let arkoor2_cosign = builder.server_cosign(&server_pubkey);
1352 assert!(builder.verify_cosign_response(&arkoor2_cosign));
1353 let [arkoor2_vtxo, change] = builder.build_vtxos(
1354 sec_nonce, &arkoor_htlc_out_user_key, &arkoor2_cosign,
1355 ).unwrap().try_into().unwrap();
1356 encoding_roundtrip(&arkoor2_vtxo);
1357 encoding_roundtrip(&change);
1358 println!("arkoor2_vtxo: {}", arkoor2_vtxo.serialize().as_hex());
1359
1360 let round1_user_key = Keypair::from_str("0a832e9574070c94b5b078600a18639321c880c830c5ba2f2a96850c7dcc4725").unwrap();
1364 let round1_cosign_key = Keypair::from_str("e14bfc3199842c76816eec1d93c9da00b850c4ed19e414e246d07e845e465a2b").unwrap();
1365 println!("round1_cosign_key: {}", round1_cosign_key.public_key());
1366 let round1_req = SignedVtxoRequest {
1367 vtxo: VtxoRequest {
1368 amount: Amount::from_sat(10_000),
1369 policy: VtxoPolicy::new_pubkey(round1_user_key.public_key()),
1370 },
1371 cosign_pubkey: Some(round1_cosign_key.public_key()),
1372 };
1373 let round1_nonces = iter::repeat_with(|| musig::nonce_pair(&round1_cosign_key)).take(5).collect::<Vec<_>>();
1374
1375 let round2_user_key = Keypair::from_str("c0b645b01cac427717a18b30c7c9238dee2b3885f659930144fbe05061ad6166").unwrap();
1376 let round2_cosign_key = Keypair::from_str("628789cd7b7e02766d184ecfecc433798c9640349e41822df7996c66a56fc633").unwrap();
1377 println!("round2_cosign_key: {}", round2_cosign_key.public_key());
1378 let round2_payment_hash = PaymentHash::from(sha256::Hash::hash("round2".as_bytes()).to_byte_array());
1379 let round2_req = SignedVtxoRequest {
1380 vtxo: VtxoRequest {
1381 amount: Amount::from_sat(10_000),
1382 policy: VtxoPolicy::new_server_htlc_recv(
1383 round2_user_key.public_key(),
1384 round2_payment_hash,
1385 expiry_height - 2000,
1386 40,
1387 ),
1388 },
1389 cosign_pubkey: Some(round2_cosign_key.public_key()),
1390 };
1391 let round2_nonces = iter::repeat_with(|| musig::nonce_pair(&round2_cosign_key)).take(5).collect::<Vec<_>>();
1392
1393 let others = [
1394 "93b376f64ada74f0fbf940be86f888459ac94655dc6a7805cc790b3c95a2a612",
1395 "00add86ff531ef53f877780622f0b376669ec6ad7e090131820ff7007e79f529",
1396 "775b836f2acf53de4ff9beeba2a17d5475e9b027d82fece72033ef06b954c7cd",
1397 "395c2c210481990a5d12d33dca37995e235a34b717c89647a33907c62e32dc09",
1398 "8f02f2a7aa1746bbcc92bba607b7166b6a77e9d0efd9d09dae7c2dc3addbdef1",
1399 ];
1400 let mut other_reqs = Vec::new();
1401 let mut other_nonces = Vec::new();
1402 for k in others {
1403 let user_key = Keypair::from_str(k).unwrap();
1404 let cosign_key = Keypair::from_seckey_slice(&SECP, &sha256::Hash::hash(k.as_bytes())[..]).unwrap();
1405 other_reqs.push(SignedVtxoRequest {
1406 vtxo: VtxoRequest {
1407 amount: Amount::from_sat(5_000),
1408 policy: VtxoPolicy::new_pubkey(user_key.public_key()),
1409 },
1410 cosign_pubkey: Some(cosign_key.public_key()),
1411 });
1412 other_nonces.push(iter::repeat_with(|| musig::nonce_pair(&cosign_key)).take(5).collect::<Vec<_>>());
1413 }
1414
1415 let server_cosign_key = Keypair::from_str("4371a4a7989b89ebe1b2582db4cd658cb95070977e6f10601ddc1e9b53edee79").unwrap();
1416 let spec = VtxoTreeSpec::new(
1417 [&round1_req, &round2_req].into_iter().chain(other_reqs.iter()).cloned().collect(),
1418 server_pubkey.public_key(),
1419 expiry_height,
1420 exit_delta,
1421 vec![server_cosign_key.public_key()],
1422 );
1423 let round_tx = Transaction {
1424 version: Version::TWO,
1425 lock_time: LockTime::ZERO,
1426 input: vec![TxIn {
1427 previous_output: OutPoint::null(),
1428 script_sig: ScriptBuf::new(),
1429 sequence: Sequence::ZERO,
1430 witness: Witness::new(),
1431 }],
1432 output: vec![TxOut {
1433 value: Amount::from_sat(45_000),
1434 script_pubkey: spec.funding_tx_script_pubkey(),
1435 }],
1436 };
1437 println!("round tx: {}", serialize_hex(&round_tx));
1438 let all_nonces = {
1439 let mut map = HashMap::new();
1440 map.insert(round1_cosign_key.public_key(), round1_nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1441 map.insert(round2_cosign_key.public_key(), round2_nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1442 for (req, nonces) in other_reqs.iter().zip(other_nonces.iter()) {
1443 map.insert(req.cosign_pubkey.unwrap(), nonces.iter().map(|n| n.1).collect::<Vec<_>>());
1444 }
1445 map
1446 };
1447 let (server_cosign_sec_nonces, server_cosign_pub_nonces) = iter::repeat_with(|| {
1448 musig::nonce_pair(&server_cosign_key)
1449 }).take(spec.nb_nodes()).unzip::<_, _, Vec<_>, Vec<_>>();
1450 let cosign_agg_nonces = spec.calculate_cosign_agg_nonces(&all_nonces, &[&server_cosign_pub_nonces]).unwrap();
1451 let root_point = OutPoint::new(round_tx.compute_txid(), 0);
1452 let tree = spec.into_unsigned_tree(root_point);
1453 let part_sigs = {
1454 let mut map = HashMap::new();
1455 map.insert(round1_cosign_key.public_key(), {
1456 let secs = round1_nonces.into_iter().map(|(s, _)| s).collect();
1457 let r = tree.cosign_branch(&cosign_agg_nonces, 0, &round1_cosign_key, secs).unwrap();
1458 r
1459 });
1460 map.insert(round2_cosign_key.public_key(), {
1461 let secs = round2_nonces.into_iter().map(|(s, _)| s).collect();
1462 tree.cosign_branch(&cosign_agg_nonces, 1, &round2_cosign_key, secs).unwrap()
1463 });
1464 for (i, (req, nonces)) in other_reqs.iter().zip(other_nonces.into_iter()).enumerate() {
1465 let cosign_key = Keypair::from_seckey_slice(
1466 &SECP, &sha256::Hash::hash(others[i].as_bytes())[..],
1467 ).unwrap();
1468 map.insert(req.cosign_pubkey.unwrap(), {
1469 let secs = nonces.into_iter().map(|(s, _)| s).collect();
1470 tree.cosign_branch(&cosign_agg_nonces, 2 + i, &cosign_key, secs).unwrap()
1471 });
1472 }
1473 map
1474 };
1475 let server_cosign_sigs = tree.cosign_tree(
1476 &cosign_agg_nonces, &server_cosign_key, server_cosign_sec_nonces,
1477 );
1478 let cosign_sigs = tree.combine_partial_signatures(&cosign_agg_nonces, &part_sigs, &[&server_cosign_sigs]).unwrap();
1479 assert!(tree.verify_cosign_sigs(&cosign_sigs).is_ok());
1480 let signed = tree.into_signed_tree(cosign_sigs).into_cached_tree();
1481 let mut vtxo_iter = signed.all_vtxos();
1483 let round1_vtxo = vtxo_iter.next().unwrap();
1484 encoding_roundtrip(&round1_vtxo);
1485 println!("round1_vtxo: {}", round1_vtxo.serialize().as_hex());
1486 let round2_vtxo = vtxo_iter.next().unwrap();
1487 encoding_roundtrip(&round2_vtxo);
1488 println!("round2_vtxo: {}", round2_vtxo.serialize().as_hex());
1489
1490 let arkoor3_user_key = Keypair::from_str("ad12595bdbdab56cb61d1f60ccc46ff96b11c5d6fe06ae7ba03d3a5f4347440f").unwrap();
1493 let arkoor3out = VtxoRequest {
1494 amount: Amount::from_sat(10_000),
1495 policy: VtxoPolicy::Pubkey(PubkeyVtxoPolicy { user_pubkey: arkoor3_user_key.public_key() }),
1496 };
1497 let outputs = [&arkoor3out];
1498 let (sec_nonce, pub_nonce) = musig::nonce_pair(&round2_user_key);
1499 let builder = ArkoorBuilder::new(&round2_vtxo, &pub_nonce, &outputs).unwrap();
1500 let arkoor3_cosign = builder.server_cosign(&server_pubkey);
1501 assert!(builder.verify_cosign_response(&arkoor3_cosign));
1502 let [arkoor3_vtxo] = builder.build_vtxos(
1503 sec_nonce, &round2_user_key, &arkoor3_cosign,
1504 ).unwrap().try_into().unwrap();
1505 encoding_roundtrip(&arkoor3_vtxo);
1506 println!("arkoor3_vtxo: {}", arkoor3_vtxo.serialize().as_hex());
1507
1508 VtxoTestVectors {
1509 anchor_tx,
1510 board_vtxo,
1511 arkoor_htlc_out_vtxo,
1512 arkoor2_vtxo,
1513 round_tx,
1514 round1_vtxo,
1515 round2_vtxo,
1516 arkoor3_vtxo,
1517 }
1518 }
1519
1520 lazy_static! {
1521 pub static ref VTXO_VECTORS: VtxoTestVectors = VtxoTestVectors {
1523 anchor_tx: deserialize_hex("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0000000000011027000000000000225120652675904a84ea02e24b57b3d547203d2ce71526113d35bf4d02e0b4efbe9a2d00000000").unwrap(),
1524 board_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000001010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a010000030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4c99b744ad009b7070f330794bf003fa8e5cd46ea1a6eb854aaf469385e3080000000000").unwrap(),
1525 arkoor_htlc_out_vtxo: ProtocolEncoding::deserialize_hex("01002823000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000002010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a01000200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4a4402956ec89190685f761fbe77018d1727c01d62f877cc5737d3f951725a0629410c1a0ead31328f24bb54c856d6efb3205032c1fdf723bf2fed7a7c6dfbc90200e8030000000000002251209b987ec3c169c70d1ed6aef420a4858e3e3ec9d8404358787d4e06ba926a4ae40103eb4570ae385202d4a48f06bdb14126910b90c07f8e42d7dc5e28a860c085e73712358912c950a9a7d04bb9011ee9f6a16b6127a5aab7415803d48c0225f620f5aa860100c692b81703c12cac1e8d69b86fa9f0e2f167168d96ae1045ef8d9192bc4a6e4c00000000").unwrap(),
1526 arkoor2_vtxo: ProtocolEncoding::deserialize_hex("0100401f000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007ed4d23932a2625a78fe5c75bded751da3a99e23a297a527c01bd7bc8372128f20000000003010200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee0365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b624ca17311d98cad1de3dbf28029c44d06da19e3101f9d688e51d5b8ac450a7eb6476c3f8ca9ba3a828150fb92791328480e313ce2b0ea8789e1aba4998455377a01000200030a752219f1b94bbdf8994a0a980cdda08c2ad094cb29dd834878db6dee1612ee4a4402956ec89190685f761fbe77018d1727c01d62f877cc5737d3f951725a0629410c1a0ead31328f24bb54c856d6efb3205032c1fdf723bf2fed7a7c6dfbc90200e8030000000000002251209b987ec3c169c70d1ed6aef420a4858e3e3ec9d8404358787d4e06ba926a4ae4020103eb4570ae385202d4a48f06bdb14126910b90c07f8e42d7dc5e28a860c085e73712358912c950a9a7d04bb9011ee9f6a16b6127a5aab7415803d48c0225f620f5aa860100ade724357d339cd6ffdd606fdd58d19540757673920512a5c01f6f9591adff3713240032fefacef370a91d268456484a460bbf992dea6872b5a751619f95560c0200e80300000000000022512018d297ade3cfbb7080b65e21af238ac88c15e38734f5f462530c34a225e80ca9000265cca13271cafd0b90c440e722a1937b7c4faf4ccd7dee0548d152c24ce4b2a8d4f7d410cf052720ffc5ce4668c4371448ffe98b7037f7c42aa943d717fcd67700000000").unwrap(),
1527 round_tx: deserialize_hex("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff000000000001c8af000000000000225120649657f65947abfd83ff629ad8a851c795f419ed4d52a2748d3f868cc3e6c94d00000000").unwrap(),
1528 round1_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000003010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427431fbf80b38758fed9f56d58a7b9f1b64d25c6219591637932f9939678e45d845cd725141a30d0b1ff28e56f7fdc838630b166449b2ad8953538006baf77a1294104038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427436f5c1527ec051272529b96c08fc51711565a71174fa762dfb0353d39f8d93f64215368326730353875cabd078be4badf8b82d1f5a6b0ca05aed5e49cc117e8ae04001027000000000000225120b54cbc99321d02aa2114fabb39dc5e8f346e88296dcec79b1b3c0849caba3d6f881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427437a733b847aed8ceda6460c70a55c80778115c536cfca265559d2f6890be7bd3ab55ef96405f2f136b185893b5d73696f866808229b1506c98b06d992a81b618c0100000374a3ec37cc4ccd29717388e6dc24f2aa366632f1a36a49e73cd7671b231792988588da5d9b08f1767aab3b3a78b6cd27deb937193e153300bcf84b3eeaaef07200000000").unwrap(),
1529 round2_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000003010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743fd28289b9179bcd986f615bfe559c17fe868b34739b9856cffc2b7d5e8f11c952579af829442116177efc4ea7867ab69376215c6eb30c9f2f79e97e1c06cf2f604038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743273b637dbe9a823fe1e04d0360d887914b83a9266e09ae0b893004fc633552038b01b2fa4f4db5dd811a87e42a4718adba852b9af5536109a07dfe81a1067a6b040110270000000000002251202df700706227474e89354e4ad3ac28f007952140fff42a5a0f0675bdff87f6b3881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc4024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743f622e425138d7c93cc09185f8328d8e113af4511c6b11053da214c65afc17f9aedca7bc667eafae74b9e232cbab6031b457c14886a3cf5573343654283b712cd0100020256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecd9ea50d885c3f66d40d27e779648ba8dc730629663f65a3e6f7749b4a35b6dfecc28201002800396ada529b0608572b3b0b6095394574c55f5b3e6911320608d35cc1cc200dab00000000").unwrap(),
1530 arkoor3_vtxo: ProtocolEncoding::deserialize_hex("01001027000000000000928a01000365a81233741893bbe2461b8d479dadc5880594fe6f7479180d5843820af72b62e007a3c23c49874159964c52b95021596d5a22e8f8b6bc7c16aa8303c24498d3d5ab0000000004010800039e8a040d9c1fba5a7b0db8485d8f167f8d2590afd8595f9eb9ba7a769347ba2602bd0ad185b18089d37d20dd784b99003914faadcc59f37bbf3273a3b5cd22ed5002568a3a6d25000fc942f0443dc76be4ef688e8c8dc055591de1f2cc1c847b1ed3036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427431fbf80b38758fed9f56d58a7b9f1b64d25c6219591637932f9939678e45d845cd725141a30d0b1ff28e56f7fdc838630b166449b2ad8953538006baf77a1294104038813000000000000225120c6b818ec8692762e68c7bd4c6d4ccebf4c764deb670a2b39daa0d05f53a1c07f8813000000000000225120b1daa25905430275c3b86e002bc586337fc4315e0c2a585969cf0ae60fad2f268813000000000000225120e790cc3be3288cd57290afd4cc977f4aca98023f0cfbad671768b25376e8a5c8010500036e64c16a01e3d18908a15b55251a5db74ff2c1142c02abd048222893b2a18a16024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc403932b1b0224e375471c938ea84bd17df7d8295941b2db68a6623610dd959b2eb303d215f5e88aefa7a2a9794771878f3d6caf20e884c1e0ce923cfa9dea261bb4e2024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b2427436f5c1527ec051272529b96c08fc51711565a71174fa762dfb0353d39f8d93f64215368326730353875cabd078be4badf8b82d1f5a6b0ca05aed5e49cc117e8ae040110270000000000002251202df700706227474e89354e4ad3ac28f007952140fff42a5a0f0675bdff87f6b3881300000000000022512058460fc9dbe1e0acb12eeabd9423161ce27fbb50dccab1179f2d843f455354a78813000000000000225120f6dbe7d3ee38ca1eb90721b3ae9d26f456e9e8b305451bede118d42807a471ef010200024ac23c8d3f6e0b1116e34f68ee7c9e4c19eba30213e8fae0925d70faf8d1ecc4024482039ebf3624525061f88aa01d4a02b3f2224eee8b4060ae0cbf036b242743b6c685b3ee849d2400f96fb5968ddd472e18c54b8910a8f952d5b127494cbd51556346bbb0a44d7f5f5150310535b4b2f2aa4412d7b88af929dae5a9652f7643010002020256fda20ffb102f6cf8590d27433ce036d29927fb35324d15d9915df888f16ecd9ea50d885c3f66d40d27e779648ba8dc730629663f65a3e6f7749b4a35b6dfecc282010028003e6785232b2247cb57de941a898729170b3a50e1bed843700f5dfaeb90ee8e531aa4e35b2bf72ceb2de6bfd2d859be86bdbe5f75fbc90a17ab9c2babb40fc83501000002ed1334f116cea9128e1f59f1d5a431cb4f338f0998e2b32f654c310bf7831f97ff70cc93c752b2cdfa42fef244be8915b087a7e13d9cf6cb24b6443b6a8b87dc00000000").unwrap(),
1531 };
1532 }
1533
1534 #[test]
1535 fn test_generate_vtxo_vectors() {
1536 let g = generate_vtxo_vectors();
1537 let v = &*VTXO_VECTORS;
1540 println!("\n\nstatic:");
1541 println!(" anchor_tx: {}", serialize_hex(&v.anchor_tx));
1542 println!(" board_vtxo: {}", v.board_vtxo.serialize().as_hex().to_string());
1543 println!(" arkoor_htlc_out_vtxo: {}", v.arkoor_htlc_out_vtxo.serialize().as_hex().to_string());
1544 println!(" arkoor2_vtxo: {}", v.arkoor2_vtxo.serialize().as_hex().to_string());
1545 println!(" round_tx: {}", serialize_hex(&v.round_tx));
1546 println!(" round1_vtxo: {}", v.round1_vtxo.serialize().as_hex().to_string());
1547 println!(" round2_vtxo: {}", v.round2_vtxo.serialize().as_hex().to_string());
1548 println!(" arkoor3_vtxo: {}", v.arkoor3_vtxo.serialize().as_hex().to_string());
1549
1550 assert_eq!(g, *v);
1552 }
1553
1554 #[test]
1555 fn arkoor_depth() {
1556 let vtxos = &*VTXO_VECTORS;
1557 assert_eq!(vtxos.board_vtxo.arkoor_depth(), 0);
1559
1560 assert_eq!(vtxos.round1_vtxo.arkoor_depth(), 0);
1562
1563 assert_eq!(vtxos.arkoor_htlc_out_vtxo.arkoor_depth(), 1);
1565 assert_eq!(vtxos.arkoor2_vtxo.arkoor_depth(), 2);
1566 assert_eq!(vtxos.arkoor3_vtxo.arkoor_depth(), 1);
1567 }
1568
1569 #[test]
1570 fn exit_depth() {
1571 let vtxos = &*VTXO_VECTORS;
1572 assert_eq!(vtxos.board_vtxo.exit_depth(), 1 );
1574
1575 assert_eq!(vtxos.round1_vtxo.exit_depth(), 3 );
1577
1578 assert_eq!(vtxos.arkoor_htlc_out_vtxo.exit_depth(), 1 + 1 );
1580 assert_eq!(vtxos.arkoor2_vtxo.exit_depth(), 1 + 2 );
1581 assert_eq!(vtxos.arkoor3_vtxo.exit_depth(), 3 + 1 );
1582 }
1583}