1
2
3use std::{cmp, fmt, io, iter};
4use std::collections::{HashMap, VecDeque};
5
6use bitcoin::hashes::{sha256, Hash};
7use bitcoin::{
8 taproot, Amount, OutPoint, ScriptBuf, Sequence, TapLeafHash, Transaction, TxIn, TxOut, Txid, Weight, Witness
9};
10use bitcoin::secp256k1::{schnorr, Keypair, PublicKey, XOnlyPublicKey};
11use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
12use secp256k1_musig::musig::{AggregatedNonce, PartialSignature, PublicNonce, SecretNonce};
13
14use bitcoin_ext::{fee, BlockDelta, BlockHeight, TaprootSpendInfoExt, TransactionExt, TxOutExt};
15
16use crate::{musig, scripts, ServerVtxoPolicy, Vtxo, VtxoId, VtxoPolicy, VtxoRequest, SECP};
17use crate::encode::{
18 LengthPrefixedVector, OversizedVectorError, ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt
19};
20use crate::error::IncorrectSigningKeyError;
21use crate::tree::{self, Tree};
22use crate::vtxo::{self, Full, GenesisItem, GenesisTransition, MaybePreimage, ServerVtxo};
23use crate::vtxo::policy::{check_block_delta, check_block_height};
24
25
26pub type UnlockHash = sha256::Hash;
28
29pub type UnlockPreimage = [u8; 32];
31
32pub const NODE_SPEND_WEIGHT: Weight = Weight::from_wu(140);
34
35pub fn expiry_clause(server_pubkey: PublicKey, expiry_height: BlockHeight) -> ScriptBuf {
37 let pk = server_pubkey.x_only_public_key().0;
38 scripts::timelock_sign(expiry_height, pk)
39}
40
41pub fn unlock_clause(pubkey: XOnlyPublicKey, unlock_hash: UnlockHash) -> ScriptBuf {
45 scripts::hash_and_sign(unlock_hash, pubkey)
46}
47
48pub fn leaf_cosign_taproot(
55 user_pubkey: PublicKey,
56 server_pubkey: PublicKey,
57 expiry_height: BlockHeight,
58 unlock_hash: UnlockHash,
59) -> taproot::TaprootSpendInfo {
60 let agg_pk = musig::combine_keys([user_pubkey, server_pubkey])
61 .x_only_public_key().0;
62 taproot::TaprootBuilder::new()
63 .add_leaf(1, expiry_clause(server_pubkey, expiry_height)).unwrap()
64 .add_leaf(1, unlock_clause(agg_pk, unlock_hash)).unwrap()
65 .finalize(&SECP, agg_pk).unwrap()
66}
67
68pub fn cosign_taproot(
70 agg_pk: XOnlyPublicKey,
71 server_pubkey: PublicKey,
72 expiry_height: BlockHeight,
73) -> taproot::TaprootSpendInfo {
74 taproot::TaprootBuilder::new()
75 .add_leaf(0, expiry_clause(server_pubkey, expiry_height)).unwrap()
76 .finalize(&SECP, agg_pk).unwrap()
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub struct VtxoLeafSpec {
81 pub vtxo: VtxoRequest,
83
84 pub cosign_pubkey: Option<PublicKey>,
91
92 pub unlock_hash: UnlockHash,
94}
95
96impl ProtocolEncoding for VtxoLeafSpec {
97 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
98 self.vtxo.policy.encode(w)?;
99 w.emit_u64(self.vtxo.amount.to_sat())?;
100 self.cosign_pubkey.encode(w)?;
101 self.unlock_hash.encode(w)?;
102 Ok(())
103 }
104
105 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
106 Ok(VtxoLeafSpec {
107 vtxo: VtxoRequest {
108 policy: VtxoPolicy::decode(r)?,
109 amount: Amount::from_sat(r.read_u64()?),
110 },
111 cosign_pubkey: Option::<PublicKey>::decode(r)?,
112 unlock_hash: sha256::Hash::decode(r)?,
113 })
114 }
115}
116
117#[derive(Debug, Clone, Eq, PartialEq)]
119pub struct VtxoTreeSpec {
120 pub vtxos: Vec<VtxoLeafSpec>,
121 pub expiry_height: BlockHeight,
122 pub server_pubkey: PublicKey,
123 pub exit_delta: BlockDelta,
124 pub global_cosign_pubkeys: Vec<PublicKey>,
125}
126
127#[derive(Clone, Copy)]
128enum ChildSpec<'a> {
129 Leaf {
130 spec: &'a VtxoLeafSpec,
131 },
132 Internal {
133 output_value: Amount,
134 agg_pk: PublicKey,
135 },
136}
137
138impl VtxoTreeSpec {
139 pub fn new(
140 vtxos: Vec<VtxoLeafSpec>,
141 server_pubkey: PublicKey,
142 expiry_height: BlockHeight,
143 exit_delta: BlockDelta,
144 global_cosign_pubkeys: Vec<PublicKey>,
145 ) -> VtxoTreeSpec {
146 assert_ne!(vtxos.len(), 0);
147 VtxoTreeSpec { vtxos, server_pubkey, expiry_height, exit_delta, global_cosign_pubkeys }
148 }
149
150 pub fn nb_leaves(&self) -> usize {
151 self.vtxos.len()
152 }
153
154 pub fn nb_nodes(&self) -> usize {
155 Tree::nb_nodes_for_leaves(self.nb_leaves())
156 }
157
158 pub fn nb_internal_nodes(&self) -> usize {
159 Tree::nb_nodes_for_leaves(self.nb_leaves()).checked_sub(self.nb_leaves())
160 .expect("tree can't have less nodes than leaves")
161 }
162
163 pub fn iter_vtxos(&self) -> impl Iterator<Item = &VtxoLeafSpec> {
164 self.vtxos.iter()
165 }
166
167 pub fn leaf_idx_of(&self, leaf_spec: &VtxoLeafSpec) -> Option<usize> {
169 self.vtxos.iter().position(|e| e == leaf_spec)
170 }
171
172 pub fn leaf_idx_of_req(&self, vtxo_request: &VtxoRequest) -> Option<usize> {
177 self.vtxos.iter().position(|e| e.vtxo == *vtxo_request)
178 }
179
180 pub fn total_required_value(&self) -> Amount {
185 self.vtxos.iter().map(|d| d.vtxo.amount).sum::<Amount>()
186 }
187
188 pub fn leaf_taproot(
190 &self,
191 user_pubkey: PublicKey,
192 unlock_hash: UnlockHash,
193 ) -> taproot::TaprootSpendInfo {
194 leaf_cosign_taproot(user_pubkey, self.server_pubkey, self.expiry_height, unlock_hash)
195 }
196
197 pub fn internal_taproot(&self, agg_pk: XOnlyPublicKey) -> taproot::TaprootSpendInfo {
199 cosign_taproot(agg_pk, self.server_pubkey, self.expiry_height)
200 }
201
202 pub fn funding_tx_cosign_pubkey(&self) -> XOnlyPublicKey {
206 let keys = self.vtxos.iter()
207 .filter_map(|v| v.cosign_pubkey)
208 .chain(self.global_cosign_pubkeys.iter().copied());
209 musig::combine_keys(keys).x_only_public_key().0
210 }
211
212 pub fn funding_tx_script_pubkey(&self) -> ScriptBuf {
216 let agg_pk = self.funding_tx_cosign_pubkey();
217 self.internal_taproot(agg_pk).script_pubkey()
218 }
219
220 pub fn funding_tx_txout(&self) -> TxOut {
224 TxOut {
225 script_pubkey: self.funding_tx_script_pubkey(),
226 value: self.total_required_value(),
227 }
228 }
229
230 fn node_tx<'a>(
235 &self,
236 children: impl Iterator<Item = ChildSpec<'a>>,
237 ) -> Transaction {
238 Transaction {
239 version: bitcoin::transaction::Version(3),
240 lock_time: bitcoin::absolute::LockTime::ZERO,
241 input: vec![TxIn {
242 previous_output: OutPoint::null(), sequence: Sequence::ZERO,
244 script_sig: ScriptBuf::new(),
245 witness: Witness::new(),
246 }],
247 output: children.map(|child| match child {
248 ChildSpec::Leaf { spec } => {
249 let taproot = self.leaf_taproot(
250 spec.vtxo.policy.user_pubkey(),
251 spec.unlock_hash,
252 );
253 TxOut {
254 script_pubkey: taproot.script_pubkey(),
255 value: spec.vtxo.amount,
256 }
257 },
258 ChildSpec::Internal { output_value, agg_pk } => {
259 let taproot = self.internal_taproot(agg_pk.x_only_public_key().0);
260 TxOut {
261 script_pubkey: taproot.script_pubkey(),
262 value: output_value,
263 }
264 },
265 }).chain(Some(fee::fee_anchor())).collect(),
266 }
267 }
268
269 fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
270 let txout = TxOut {
271 value: vtxo.amount,
272 script_pubkey: vtxo.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height),
273 };
274
275 vtxo::create_exit_tx(OutPoint::null(), txout, None, Amount::ZERO)
279 }
280
281 pub fn cosign_agg_pks(&self)
285 -> impl Iterator<Item = PublicKey> + iter::DoubleEndedIterator + iter::ExactSizeIterator + '_
286 {
287 Tree::new(self.nb_leaves()).into_iter().map(|node| {
288 if node.is_leaf() {
289 musig::combine_keys([
290 self.vtxos[node.idx()].vtxo.policy.user_pubkey(),
291 self.server_pubkey,
292 ])
293 } else {
294 musig::combine_keys(
295 node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey)
296 .chain(self.global_cosign_pubkeys.iter().copied())
297 )
298 }
299 })
300 }
301
302 pub fn unsigned_transactions(&self, utxo: OutPoint) -> Vec<Transaction> {
304 let tree = Tree::new(self.nb_leaves());
305
306 let cosign_agg_pks = self.cosign_agg_pks().collect::<Vec<_>>();
307
308 let mut txs = Vec::<Transaction>::with_capacity(tree.nb_nodes());
309 for node in tree.iter() {
310 let tx = if node.is_leaf() {
311 self.leaf_tx(&self.vtxos[node.idx()].vtxo).clone()
312 } else {
313 let mut buf = [None; tree::RADIX];
314 for (idx, child) in node.children().enumerate() {
315 let child = if let Some(spec) = self.vtxos.get(child) {
316 ChildSpec::Leaf { spec }
317 } else {
318 ChildSpec::Internal {
319 output_value: txs[child].output_value(),
320 agg_pk: cosign_agg_pks[child],
321 }
322 };
323 buf[idx] = Some(child);
324 }
325 self.node_tx(buf.iter().filter_map(|x| *x))
326 };
327 txs.push(tx.clone());
328 };
329
330 txs.last_mut().unwrap().input[0].previous_output = utxo;
332 for node in tree.iter().rev() {
333 let txid = txs[node.idx()].compute_txid();
334 for (i, child) in node.children().enumerate() {
335 let point = OutPoint::new(txid, i as u32);
336 txs[child].input[0].previous_output = point;
337 }
338 }
339
340 txs
341 }
342
343 pub fn final_transactions(
347 &self,
348 utxo: OutPoint,
349 internal_signatures: &[schnorr::Signature],
350 ) -> Vec<Transaction> {
351 let mut txs = self.unsigned_transactions(utxo);
352 for (tx, sig) in txs.iter_mut().skip(self.nb_leaves()).zip(internal_signatures) {
353 tx.input[0].witness.push(&sig[..]);
354 }
355 txs
356 }
357
358 pub fn calculate_cosign_agg_nonces(
362 &self,
363 leaf_cosign_nonces: &HashMap<PublicKey, Vec<PublicNonce>>,
364 global_signer_cosign_nonces: &[impl AsRef<[PublicNonce]>],
365 ) -> Result<Vec<AggregatedNonce>, String> {
366 if global_signer_cosign_nonces.len() != self.global_cosign_pubkeys.len() {
367 return Err("missing global signer nonces".into());
368 }
369
370 Tree::new(self.nb_leaves()).iter_internal().enumerate().map(|(idx, node)| {
371 let mut nonces = Vec::new();
372 for pk in node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey) {
373 nonces.push(leaf_cosign_nonces.get(&pk)
374 .ok_or_else(|| format!("missing nonces for leaf pk {}", pk))?
375 .get(node.internal_level())
378 .ok_or_else(|| format!("not enough nonces for leaf_pk {}", pk))?
379 );
380 }
381 for glob in global_signer_cosign_nonces {
382 nonces.push(glob.as_ref().get(idx).ok_or("not enough global cosign nonces")?);
383 }
384 Ok(musig::nonce_agg(&nonces))
385 }).collect()
386 }
387
388 pub fn into_unsigned_tree(
393 self,
394 utxo: OutPoint,
395 ) -> UnsignedVtxoTree {
396 UnsignedVtxoTree::new(self, utxo)
397 }
398}
399
400#[derive(Debug, Clone)]
404pub struct UnsignedVtxoTree {
405 pub spec: VtxoTreeSpec,
406 pub utxo: OutPoint,
407
408 pub cosign_agg_pks: Vec<PublicKey>,
412 pub txs: Vec<Transaction>,
414 pub internal_sighashes: Vec<TapSighash>,
417
418 tree: Tree,
419}
420
421impl UnsignedVtxoTree {
422 pub fn new(
423 spec: VtxoTreeSpec,
424 utxo: OutPoint,
425 ) -> UnsignedVtxoTree {
426 let tree = Tree::new(spec.nb_leaves());
427
428 let cosign_agg_pks = spec.cosign_agg_pks().collect::<Vec<_>>();
429 let txs = spec.unsigned_transactions(utxo);
430
431 let root_txout = spec.funding_tx_txout();
432 let internal_sighashes = tree.iter_internal().map(|node| {
433 let prev = if let Some((parent, sibling_idx))
434 = tree.parent_idx_of_with_sibling_idx(node.idx())
435 {
436 assert!(!node.is_root());
437 &txs[parent].output[sibling_idx]
438 } else {
439 assert!(node.is_root());
440 &root_txout
441 };
442
443 let mut shc = SighashCache::new(&txs[node.idx()]);
444 shc.taproot_key_spend_signature_hash(
445 0, &sighash::Prevouts::All(&[prev]),
447 TapSighashType::Default,
448 ).expect("sighash error")
449 }).collect();
450
451 UnsignedVtxoTree { spec, utxo, txs, internal_sighashes, cosign_agg_pks, tree }
452 }
453
454 pub fn nb_leaves(&self) -> usize {
455 self.tree.nb_leaves()
456 }
457
458 pub fn nb_cosigned_leaves(&self) -> usize {
460 self.spec.vtxos.iter()
461 .filter(|v| v.cosign_pubkey.is_some())
462 .count()
463 }
464
465 pub fn nb_nodes(&self) -> usize {
466 self.tree.nb_nodes()
467 }
468
469 pub fn nb_internal_nodes(&self) -> usize {
470 self.tree.nb_internal_nodes()
471 }
472
473 pub fn cosign_branch(
486 &self,
487 cosign_agg_nonces: &[AggregatedNonce],
488 leaf_idx: usize,
489 cosign_key: &Keypair,
490 cosign_sec_nonces: Vec<SecretNonce>,
491 ) -> Result<Vec<PartialSignature>, IncorrectSigningKeyError> {
492 let req = self.spec.vtxos.get(leaf_idx).expect("leaf idx out of bounds");
493 if Some(cosign_key.public_key()) != req.cosign_pubkey {
494 return Err(IncorrectSigningKeyError {
495 required: req.cosign_pubkey,
496 provided: cosign_key.public_key(),
497 });
498 }
499
500 let mut nonce_iter = cosign_sec_nonces.into_iter().enumerate();
501 let mut ret = Vec::with_capacity(self.tree.root().level().saturating_add(1));
502 for node in self.tree.iter_branch(leaf_idx).skip(1) {
504 let sec_nonce = loop {
509 let next = nonce_iter.next().expect("level overflow");
510 if next.0 == node.internal_level() {
511 break next.1;
512 }
513 };
514
515 let cosign_pubkeys = node.leaves()
516 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
517 .chain(self.spec.global_cosign_pubkeys.iter().copied());
518 let sighash = self.internal_sighashes[node.internal_idx()];
519
520 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
521 let tweak = self.spec.internal_taproot(agg_pk).tap_tweak().to_byte_array();
522 let sig = musig::partial_sign(
523 cosign_pubkeys,
524 cosign_agg_nonces[node.internal_idx()],
525 &cosign_key,
526 sec_nonce,
527 sighash.to_byte_array(),
528 Some(tweak),
529 None,
530 ).0;
531 ret.push(sig);
532 }
533
534 Ok(ret)
535 }
536
537 pub fn cosign_tree(
543 &self,
544 cosign_agg_nonces: &[AggregatedNonce],
545 keypair: &Keypair,
546 cosign_sec_nonces: Vec<SecretNonce>,
547 ) -> Vec<PartialSignature> {
548 debug_assert_eq!(cosign_agg_nonces.len(), self.nb_internal_nodes());
549 debug_assert_eq!(cosign_sec_nonces.len(), self.nb_internal_nodes());
550
551 let nonces = cosign_sec_nonces.into_iter().zip(cosign_agg_nonces);
552 self.tree.iter_internal().zip(nonces).map(|(node, (sec_nonce, agg_nonce))| {
553 let sighash = self.internal_sighashes[node.internal_idx()];
554
555 let cosign_pubkeys = node.leaves()
556 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
557 .chain(self.spec.global_cosign_pubkeys.iter().copied());
558 let agg_pk = self.cosign_agg_pks[node.idx()];
559 debug_assert_eq!(agg_pk, musig::combine_keys(cosign_pubkeys.clone()));
560 let taproot = self.spec.internal_taproot(agg_pk.x_only_public_key().0);
561 musig::partial_sign(
562 cosign_pubkeys,
563 *agg_nonce,
564 &keypair,
565 sec_nonce,
566 sighash.to_byte_array(),
567 Some(taproot.tap_tweak().to_byte_array()),
568 None,
569 ).0
570 }).collect()
571 }
572
573 fn verify_internal_node_cosign_partial_sig(
575 &self,
576 node: &tree::Node,
577 pk: PublicKey,
578 agg_nonces: &[AggregatedNonce],
579 part_sig: PartialSignature,
580 pub_nonce: PublicNonce,
581 ) -> Result<(), CosignSignatureError> {
582 debug_assert!(!node.is_leaf());
583
584 let sighash = self.internal_sighashes[node.internal_idx()];
585
586 let key_agg = {
587 let cosign_pubkeys = node.leaves()
588 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
589 .chain(self.spec.global_cosign_pubkeys.iter().copied());
590 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
591 let taproot = self.spec.internal_taproot(agg_pk);
592 let taptweak = taproot.tap_tweak().to_byte_array();
593 musig::tweaked_key_agg(cosign_pubkeys, taptweak).0
594 };
595 let agg_nonce = agg_nonces.get(node.internal_idx())
596 .ok_or(CosignSignatureError::NotEnoughNonces)?;
597 let session = musig::Session::new(&key_agg, *agg_nonce, &sighash.to_byte_array());
598 let ok = session.partial_verify(&key_agg, &part_sig, &pub_nonce, musig::pubkey_to(pk));
599 if !ok {
600 return Err(CosignSignatureError::invalid_sig(pk));
601 }
602 Ok(())
603 }
604
605 pub fn verify_branch_cosign_partial_sigs(
610 &self,
611 cosign_agg_nonces: &[AggregatedNonce],
612 request: &VtxoLeafSpec,
613 cosign_pub_nonces: &[PublicNonce],
614 cosign_part_sigs: &[PartialSignature],
615 ) -> Result<(), String> {
616 assert_eq!(cosign_agg_nonces.len(), self.nb_internal_nodes());
617
618 let cosign_pubkey = request.cosign_pubkey.ok_or("no cosign pubkey for request")?;
619 let leaf_idx = self.spec.leaf_idx_of(request).ok_or("request not in tree")?;
620
621 let internal_branch = self.tree.iter_branch(leaf_idx).skip(1);
623
624 match internal_branch.clone().count().cmp(&cosign_part_sigs.len()) {
626 cmp::Ordering::Less => return Err("too few partial signatures".into()),
627 cmp::Ordering::Greater => return Err("too many partial signatures".into()),
628 cmp::Ordering::Equal => {},
629 }
630
631 let mut part_sigs_iter = cosign_part_sigs.iter();
632 let mut pub_nonce_iter = cosign_pub_nonces.iter().enumerate();
633 for node in internal_branch {
634 let pub_nonce = loop {
635 let next = pub_nonce_iter.next().ok_or("not enough pub nonces")?;
636 if next.0 == node.internal_level() {
637 break next.1;
638 }
639 };
640 self.verify_internal_node_cosign_partial_sig(
641 node,
642 cosign_pubkey,
643 cosign_agg_nonces,
644 part_sigs_iter.next().ok_or("not enough sigs")?.clone(),
645 *pub_nonce,
646 ).map_err(|e| format!("part sig verification failed: {}", e))?;
647 }
648
649 Ok(())
650 }
651
652 pub fn verify_global_cosign_partial_sigs(
657 &self,
658 pk: PublicKey,
659 agg_nonces: &[AggregatedNonce],
660 pub_nonces: &[PublicNonce],
661 part_sigs: &[PartialSignature],
662 ) -> Result<(), CosignSignatureError> {
663 for node in self.tree.iter_internal() {
664 let sigs = *part_sigs.get(node.internal_idx())
665 .ok_or_else(|| CosignSignatureError::missing_sig(pk))?;
666 let nonces = *pub_nonces.get(node.internal_idx())
667 .ok_or_else(|| CosignSignatureError::NotEnoughNonces)?;
668 self.verify_internal_node_cosign_partial_sig(node, pk, agg_nonces, sigs, nonces)?;
669 }
670
671 Ok(())
672 }
673
674 pub fn combine_partial_signatures(
683 &self,
684 cosign_agg_nonces: &[AggregatedNonce],
685 branch_part_sigs: &HashMap<PublicKey, Vec<PartialSignature>>,
686 global_signer_part_sigs: &[impl AsRef<[PartialSignature]>],
687 ) -> Result<Vec<schnorr::Signature>, CosignSignatureError> {
688 let mut leaf_part_sigs = branch_part_sigs.iter()
690 .map(|(pk, sigs)| (pk, sigs.iter().collect()))
691 .collect::<HashMap<_, VecDeque<_>>>();
692
693 if global_signer_part_sigs.len() != self.spec.global_cosign_pubkeys.len() {
694 return Err(CosignSignatureError::Invalid(
695 "invalid nb of global cosigner partial signatures",
696 ));
697 }
698 for (pk, sigs) in self.spec.global_cosign_pubkeys.iter().zip(global_signer_part_sigs) {
699 if sigs.as_ref().len() != self.nb_internal_nodes() {
700 return Err(CosignSignatureError::MissingSignature { pk: *pk });
703 }
704 }
705
706 let max_level = match self.tree.root().is_leaf() {
707 true => 0,
708 false => self.tree.root().internal_level(),
709 };
710 self.tree.iter_internal().map(|node| {
711 let mut cosign_pks = Vec::with_capacity(max_level.saturating_add(1));
712 let mut part_sigs = Vec::with_capacity(max_level.saturating_add(1));
713 for leaf in node.leaves() {
714 if let Some(cosign_pk) = self.spec.vtxos[leaf].cosign_pubkey {
715 let part_sig = leaf_part_sigs.get_mut(&cosign_pk)
716 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?
717 .pop_front()
718 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?;
719 cosign_pks.push(cosign_pk);
720 part_sigs.push(part_sig);
721 }
722 }
723 cosign_pks.extend(&self.spec.global_cosign_pubkeys);
725 for sigs in global_signer_part_sigs {
726 part_sigs.push(sigs.as_ref().get(node.internal_idx()).expect("checked before"));
727 }
728
729 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
730 let taproot = self.spec.internal_taproot(agg_pk);
731 let agg_nonce = *cosign_agg_nonces.get(node.internal_idx())
732 .ok_or(CosignSignatureError::NotEnoughNonces)?;
733 let sighash = self.internal_sighashes[node.internal_idx()].to_byte_array();
734 let tweak = taproot.tap_tweak().to_byte_array();
735 Ok(musig::combine_partial_signatures(
736 cosign_pks, agg_nonce, sighash, Some(tweak), &part_sigs,
737 ))
738 }).collect()
739 }
740
741 pub fn verify_cosign_sigs(
745 &self,
746 signatures: &[schnorr::Signature],
747 ) -> Result<(), XOnlyPublicKey> {
748 for node in self.tree.iter_internal() {
749 let sighash = self.internal_sighashes[node.internal_idx()];
750 let agg_pk = &self.cosign_agg_pks[node.idx()].x_only_public_key().0;
751 let pk = self.spec.internal_taproot(*agg_pk).output_key().to_x_only_public_key();
752 let sig = signatures.get(node.internal_idx()).ok_or_else(|| pk)?;
753 if SECP.verify_schnorr(sig, &sighash.into(), &pk).is_err() {
754 return Err(pk);
755 }
756 }
757 Ok(())
758 }
759
760 pub fn into_signed_tree(
764 self,
765 signatures: Vec<schnorr::Signature>,
766 ) -> SignedVtxoTreeSpec {
767 SignedVtxoTreeSpec {
768 spec: self.spec,
769 utxo: self.utxo,
770 cosign_sigs: signatures,
771 }
772 }
773}
774
775#[derive(PartialEq, Eq, thiserror::Error)]
777pub enum CosignSignatureError {
778 #[error("missing cosign signature from pubkey {pk}")]
779 MissingSignature { pk: PublicKey },
780 #[error("invalid cosign signature from pubkey {pk}")]
781 InvalidSignature { pk: PublicKey },
782 #[error("not enough nonces")]
783 NotEnoughNonces,
784 #[error("invalid cosign signatures: {0}")]
785 Invalid(&'static str),
786}
787
788impl fmt::Debug for CosignSignatureError {
789 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
790 fmt::Display::fmt(self, f)
791 }
792}
793
794impl CosignSignatureError {
795 fn missing_sig(cosign_pk: PublicKey) -> CosignSignatureError {
796 CosignSignatureError::MissingSignature { pk: cosign_pk }
797 }
798 fn invalid_sig(cosign_pk: PublicKey) -> CosignSignatureError {
799 CosignSignatureError::InvalidSignature { pk: cosign_pk }
800 }
801}
802
803#[derive(Debug, Clone, PartialEq)]
805pub struct SignedVtxoTreeSpec {
806 pub spec: VtxoTreeSpec,
807 pub utxo: OutPoint,
808 pub cosign_sigs: Vec<schnorr::Signature>,
810}
811
812impl SignedVtxoTreeSpec {
813 pub fn new(
815 spec: VtxoTreeSpec,
816 utxo: OutPoint,
817 cosign_signatures: Vec<schnorr::Signature>,
818 ) -> SignedVtxoTreeSpec {
819 SignedVtxoTreeSpec { spec, utxo, cosign_sigs: cosign_signatures }
820 }
821
822 pub fn nb_leaves(&self) -> usize {
823 self.spec.nb_leaves()
824 }
825
826 pub fn exit_branch(&self, leaf_idx: usize) -> Vec<Transaction> {
833 let txs = self.all_final_txs();
834 let tree = Tree::new(self.spec.nb_leaves());
835 let mut ret = tree.iter_branch(leaf_idx)
836 .map(|n| txs[n.idx()].clone())
837 .collect::<Vec<_>>();
838 ret.reverse();
839 ret
840 }
841
842 pub fn all_final_txs(&self) -> Vec<Transaction> {
844 self.spec.final_transactions(self.utxo, &self.cosign_sigs)
845 }
846
847 pub fn into_cached_tree(self) -> CachedSignedVtxoTree {
848 CachedSignedVtxoTree {
849 txs: self.all_final_txs(),
850 spec: self,
851 }
852 }
853}
854
855pub struct CachedSignedVtxoTree {
859 pub spec: SignedVtxoTreeSpec,
860 pub txs: Vec<Transaction>,
862}
863
864impl CachedSignedVtxoTree {
865 pub fn exit_branch(&self, leaf_idx: usize) -> Vec<&Transaction> {
869 let tree = Tree::new(self.spec.spec.nb_leaves());
870 let mut ret = tree.iter_branch(leaf_idx)
871 .map(|n| &self.txs[n.idx()])
872 .collect::<Vec<_>>();
873 ret.reverse();
874 ret
875 }
876
877 pub fn nb_leaves(&self) -> usize {
878 self.spec.nb_leaves()
879 }
880
881 pub fn nb_nodes(&self) -> usize {
882 Tree::nb_nodes_for_leaves(self.spec.nb_leaves())
883 }
884
885 pub fn all_final_txs(&self) -> &[Transaction] {
890 &self.txs
891 }
892
893 pub fn unsigned_leaf_txs(&self) -> &[Transaction] {
897 &self.txs[..self.nb_leaves()]
898 }
899
900 pub fn internal_node_txs(&self) -> &[Transaction] {
904 &self.txs[self.nb_leaves()..]
905 }
906
907 fn build_genesis_item_at<'a>(&self, tree: &Tree, node_idx: usize, output_idx: u8) -> GenesisItem {
909 debug_assert_eq!(self.nb_leaves(), tree.nb_leaves(), "tree corresponds to self");
910 debug_assert!(node_idx < tree.nb_nodes(), "Node index is in tree");
911
912 let other_outputs = self.txs.get(node_idx).expect("Each node has a tx")
913 .output.iter().enumerate()
914 .filter(|(i, _)| *i != output_idx as usize) .filter(|(_, out)| !out.is_p2a_fee_anchor()) .map(|(_, out)| out)
917 .cloned()
918 .collect();
919
920 let node = tree.node_at(node_idx);
921
922 let transition = if node.is_leaf() {
923 debug_assert_eq!(output_idx, 0, "Leafs have a single output");
924 let req = self.spec.spec.vtxos.get(node_idx).expect("Every leaf has a spec");
925 GenesisTransition::new_hash_locked_cosigned(
926 req.vtxo.policy.user_pubkey(),
927 None,
928 MaybePreimage::Hash(req.unlock_hash),
929 )
930 } else {
931 let pubkeys = node.leaves()
932 .filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
933 .chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
934 .collect();
935
936 let sig = self.spec.cosign_sigs.get(node.internal_idx())
937 .expect("enough sigs for all nodes");
938
939 GenesisTransition::new_cosigned(pubkeys, Some(*sig))
940 };
941
942 let fee_amount = Amount::ZERO;
943 GenesisItem {transition, output_idx, other_outputs, fee_amount }
944 }
945
946 fn build_internal_vtxo(&self, node_idx: usize) -> ServerVtxo<Full> {
952 let tree = Tree::new(self.spec.spec.nb_leaves());
953 assert!(node_idx < tree.nb_nodes(), "node_idx out of range");
954
955 let mut genesis = tree.iter_branch_with_output(node_idx)
956 .map(|(idx, child_idx)| self.build_genesis_item_at(&tree, idx, child_idx as u8))
957 .collect::<Vec<_>>();
958 genesis.reverse();
959
960 let node = tree.node_at(node_idx);
961 let spec = &self.spec.spec;
962 let (point, amount) = match tree.parent_idx_of_with_sibling_idx(node_idx) {
963 None => (self.spec.utxo, spec.total_required_value()),
964 Some((parent_idx, child_idx)) => {
965 let parent_tx = self.txs.get(parent_idx).expect("parent tx exists");
966 let point = OutPoint::new(parent_tx.compute_txid(), child_idx as u32);
967 (point, parent_tx.output[child_idx].value)
968 }
969 };
970
971 let policy = if node.is_leaf() {
972 let req = spec.vtxos.get(node_idx).expect("one vtxo request for every leaf");
973 ServerVtxoPolicy::new_hark_leaf(req.vtxo.policy.user_pubkey(), req.unlock_hash)
974 } else {
975 let agg_pk = musig::combine_keys(
976 node.leaves().filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
977 .chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
978 );
979 ServerVtxoPolicy::new_expiry(agg_pk.x_only_public_key().0)
980 };
981
982 ServerVtxo {
983 policy,
984 amount,
985 expiry_height: self.spec.spec.expiry_height,
986 server_pubkey: self.spec.spec.server_pubkey,
987 exit_delta: self.spec.spec.exit_delta,
988 anchor_point: self.spec.utxo,
989 genesis: Full { items: genesis },
990 point,
991 }
992 }
993
994 pub fn build_vtxo(&self, leaf_idx: usize) -> Vtxo<Full> {
998 let req = self.spec.spec.vtxos.get(leaf_idx).expect("index is not a leaf");
999
1000 let genesis = {
1001 let tree = Tree::new(self.spec.spec.nb_leaves());
1002 let leaf = self.build_genesis_item_at(&tree, leaf_idx, 0);
1003 let internal = tree.iter_branch_with_output(leaf_idx)
1004 .map(|(node_idx, child_idx)| {
1005 self.build_genesis_item_at(&tree, node_idx, child_idx as u8)
1006 });
1007
1008 let mut genesis = [leaf].into_iter().chain(internal).collect::<Vec<_>>();
1009 genesis.reverse();
1010 genesis
1011 };
1012
1013 Vtxo {
1014 amount: req.vtxo.amount,
1015 expiry_height: self.spec.spec.expiry_height,
1016 server_pubkey: self.spec.spec.server_pubkey,
1017 exit_delta: self.spec.spec.exit_delta,
1018 anchor_point: self.spec.utxo,
1019 genesis: Full { items: genesis },
1020 policy: req.vtxo.policy.clone(),
1021 point: {
1022 let leaf_tx = self.txs.get(leaf_idx).expect("leaf idx exists");
1023 OutPoint::new(leaf_tx.compute_txid(), 0)
1024 },
1025 }
1026 }
1027
1028 pub fn internal_vtxos(&self) -> impl Iterator<Item = (ServerVtxo<Full>, Txid)> + '_ {
1031 (0..self.nb_nodes()).map(|idx| {
1032 let vtxo = self.build_internal_vtxo(idx);
1033 let spending_txid = self.txs[idx].compute_txid();
1034 (vtxo, spending_txid)
1035 })
1036 }
1037
1038 pub fn output_vtxos(&self) -> impl Iterator<Item = Vtxo<Full>> + ExactSizeIterator + '_ {
1040 (0..self.nb_leaves()).map(|idx| self.build_vtxo(idx))
1041 }
1042
1043 pub fn spend_info(&self) -> impl Iterator<Item = (VtxoId, Txid)> + '_ {
1044 self.internal_vtxos()
1045 .map(|(vtxo, spending_txid)| (vtxo.id(), spending_txid))
1046 }
1047}
1048
1049pub fn hashlocked_leaf_sighash(
1051 leaf_tx: &Transaction,
1052 user_pubkey: PublicKey,
1053 server_pubkey: PublicKey,
1054 unlock_hash: UnlockHash,
1055 prev_txout: &TxOut,
1056) -> TapSighash {
1057 let agg_pk = musig::combine_keys([user_pubkey, server_pubkey])
1058 .x_only_public_key().0;
1059 let clause = unlock_clause(agg_pk, unlock_hash);
1060 let leaf_hash = TapLeafHash::from_script(&clause, bitcoin::taproot::LeafVersion::TapScript);
1061 let mut shc = SighashCache::new(leaf_tx);
1062 shc.taproot_script_spend_signature_hash(
1063 0, &sighash::Prevouts::All(&[prev_txout]),
1065 leaf_hash,
1066 TapSighashType::Default,
1067 ).expect("sighash error")
1068}
1069
1070fn hashlocked_leaf_sighash_from_vtxo(
1076 vtxo: &Vtxo<Full>,
1077 chain_anchor: &Transaction,
1078) -> TapSighash {
1079 assert_eq!(chain_anchor.compute_txid(), vtxo.chain_anchor().txid);
1080 let last_genesis = vtxo.genesis.items.last().expect("at least one genesis item");
1081 let (user_pubkey, unlock_hash) = match &last_genesis.transition {
1082 GenesisTransition::HashLockedCosigned(inner) => {
1083 (inner.user_pubkey, inner.unlock.hash())
1084 },
1085 _ => panic!("VTXO is not a HashLockedCosigned VTXO")
1086 };
1087 debug_assert_eq!(user_pubkey, vtxo.user_pubkey());
1088
1089 let mut preleaf_txout = chain_anchor.output[vtxo.chain_anchor().vout as usize].clone();
1091 let mut leaf_tx = None;
1092 let mut peekable_iter = vtxo.transactions().peekable();
1093 while let Some(item) = peekable_iter.next() {
1094 if peekable_iter.peek().is_some() {
1097 preleaf_txout = item.tx.output[item.output_idx].clone();
1098 }
1099
1100 if peekable_iter.peek().is_none() {
1102 leaf_tx = Some(item.tx);
1103 }
1104 }
1105 let leaf_tx = leaf_tx.expect("at least one tx");
1106 hashlocked_leaf_sighash(
1107 &leaf_tx, user_pubkey, vtxo.server_pubkey(), unlock_hash, &preleaf_txout,
1108 )
1109}
1110
1111#[derive(Debug)]
1112pub struct LeafVtxoCosignRequest {
1113 pub vtxo_id: VtxoId,
1114 pub pub_nonce: musig::PublicNonce,
1115}
1116
1117pub struct LeafVtxoCosignContext<'a> {
1118 key: &'a Keypair,
1119 pub_nonce: musig::PublicNonce,
1120 sec_nonce: musig::SecretNonce,
1121 sighash: TapSighash,
1122}
1123
1124impl<'a> LeafVtxoCosignContext<'a> {
1125 pub fn new(
1130 vtxo: &Vtxo<Full>,
1131 chain_anchor: &Transaction,
1132 key: &'a Keypair,
1133 ) -> (Self, LeafVtxoCosignRequest) {
1134 let sighash = hashlocked_leaf_sighash_from_vtxo(&vtxo, chain_anchor);
1135 let (sec_nonce, pub_nonce) = musig::nonce_pair_with_msg(key, &sighash.to_byte_array());
1136 let vtxo_id = vtxo.id();
1137 let req = LeafVtxoCosignRequest { vtxo_id, pub_nonce };
1138 let ret = Self { key, pub_nonce, sec_nonce, sighash };
1139 (ret, req)
1140 }
1141
1142 pub fn finalize(
1144 self,
1145 vtxo: &mut Vtxo<Full>,
1146 response: LeafVtxoCosignResponse,
1147 ) -> bool {
1148 let agg_nonce = musig::nonce_agg(&[&self.pub_nonce, &response.public_nonce]);
1149 let (_part_sig, final_sig) = musig::partial_sign(
1150 [vtxo.user_pubkey(), vtxo.server_pubkey()],
1151 agg_nonce,
1152 self.key,
1153 self.sec_nonce,
1154 self.sighash.to_byte_array(),
1155 None,
1156 Some(&[&response.partial_signature]),
1157 );
1158 let final_sig = final_sig.expect("has other sigs");
1159
1160 let pubkey = musig::combine_keys([vtxo.user_pubkey(), vtxo.server_pubkey()])
1161 .x_only_public_key().0;
1162 debug_assert_eq!(pubkey, leaf_cosign_taproot(
1163 vtxo.user_pubkey(),
1164 vtxo.server_pubkey(),
1165 vtxo.expiry_height(),
1166 vtxo.unlock_hash().expect("checked is hark vtxo"),
1167 ).internal_key());
1168 if SECP.verify_schnorr(&final_sig, &self.sighash.into(), &pubkey).is_err() {
1169 return false;
1170 }
1171
1172 vtxo.provide_unlock_signature(final_sig)
1173 }
1174}
1175
1176#[derive(Debug)]
1177pub struct LeafVtxoCosignResponse {
1178 pub public_nonce: musig::PublicNonce,
1179 pub partial_signature: musig::PartialSignature,
1180}
1181
1182impl LeafVtxoCosignResponse {
1183 pub fn new_cosign(
1185 request: &LeafVtxoCosignRequest,
1186 vtxo: &Vtxo<Full>,
1187 chain_anchor: &Transaction,
1188 server_key: &Keypair,
1189 ) -> Self {
1190 debug_assert_eq!(server_key.public_key(), vtxo.server_pubkey());
1191 let sighash = hashlocked_leaf_sighash_from_vtxo(&vtxo, chain_anchor);
1192 let (public_nonce, partial_signature) = musig::deterministic_partial_sign(
1193 server_key,
1194 [vtxo.user_pubkey()],
1195 &[&request.pub_nonce],
1196 sighash.to_byte_array(),
1197 None,
1198 );
1199 Self { public_nonce, partial_signature }
1200 }
1201}
1202
1203pub mod builder {
1204 use std::collections::HashMap;
1211 use std::marker::PhantomData;
1212
1213 use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut};
1214 use bitcoin::hashes::{sha256, Hash};
1215 use bitcoin::secp256k1::{Keypair, PublicKey};
1216 use bitcoin_ext::{BlockDelta, BlockHeight};
1217
1218 use crate::tree::signed::{UnlockHash, UnlockPreimage, VtxoLeafSpec};
1219 use crate::{musig, VtxoRequest};
1220 use crate::error::IncorrectSigningKeyError;
1221
1222 use super::{CosignSignatureError, SignedVtxoTreeSpec, UnsignedVtxoTree, VtxoTreeSpec};
1223
1224 pub mod state {
1225 mod sealed {
1226 pub trait Sealed {}
1228 impl Sealed for super::Preparing {}
1229 impl Sealed for super::CanGenerateNonces {}
1230 impl Sealed for super::ServerCanCosign {}
1231 impl Sealed for super::CanFinish {}
1232 }
1233
1234 pub trait BuilderState: sealed::Sealed {}
1236
1237 pub struct Preparing;
1239 impl BuilderState for Preparing {}
1240
1241 pub struct CanGenerateNonces;
1244 impl BuilderState for CanGenerateNonces {}
1245
1246 pub struct ServerCanCosign;
1248 impl BuilderState for ServerCanCosign {}
1249
1250 pub struct CanFinish;
1253 impl BuilderState for CanFinish {}
1254
1255 pub trait CanSign: BuilderState {}
1258 impl CanSign for ServerCanCosign {}
1259 impl CanSign for CanFinish {}
1260 }
1261
1262 enum BuilderTree {
1264 Spec(VtxoTreeSpec),
1265 Unsigned(UnsignedVtxoTree),
1266 }
1267
1268 impl BuilderTree {
1269 fn unsigned_tree(&self) -> Option<&UnsignedVtxoTree> {
1270 match self {
1271 BuilderTree::Spec(_) => None,
1272 BuilderTree::Unsigned(t) => Some(t),
1273 }
1274 }
1275
1276 fn into_unsigned_tree(self) -> Option<UnsignedVtxoTree> {
1277 match self {
1278 BuilderTree::Spec(_) => None,
1279 BuilderTree::Unsigned(t) => Some(t),
1280 }
1281 }
1282 }
1283
1284 pub struct SignedTreeBuilder<S: state::BuilderState> {
1288 pub expiry_height: BlockHeight,
1289 pub server_pubkey: PublicKey,
1290 pub exit_delta: BlockDelta,
1291 pub cosign_pubkey: PublicKey,
1293 pub unlock_preimage: UnlockPreimage,
1295
1296 tree: BuilderTree,
1297
1298 user_pub_nonces: Vec<musig::PublicNonce>,
1300 user_sec_nonces: Option<Vec<musig::SecretNonce>>,
1303 _state: PhantomData<S>,
1304 }
1305
1306 impl<T: state::BuilderState> SignedTreeBuilder<T> {
1307 fn tree_spec(&self) -> &VtxoTreeSpec {
1308 match self.tree {
1309 BuilderTree::Spec(ref s) => s,
1310 BuilderTree::Unsigned(ref t) => &t.spec,
1311 }
1312 }
1313
1314 pub fn total_required_value(&self) -> Amount {
1316 self.tree_spec().total_required_value()
1317 }
1318
1319 pub fn funding_script_pubkey(&self) -> ScriptBuf {
1321 self.tree_spec().funding_tx_script_pubkey()
1322 }
1323
1324 pub fn funding_txout(&self) -> TxOut {
1326 let spec = self.tree_spec();
1327 TxOut {
1328 value: spec.total_required_value(),
1329 script_pubkey: spec.funding_tx_script_pubkey(),
1330 }
1331 }
1332 }
1333
1334 impl<T: state::CanSign> SignedTreeBuilder<T> {
1335 pub fn user_pub_nonces(&self) -> &[musig::PublicNonce] {
1337 &self.user_pub_nonces
1338 }
1339 }
1340
1341 #[derive(Debug, thiserror::Error)]
1342 #[error("signed VTXO tree builder error: {0}")]
1343 pub struct SignedTreeBuilderError(&'static str);
1344
1345 impl SignedTreeBuilder<state::Preparing> {
1346 pub fn construct_tree_spec(
1348 vtxos: impl IntoIterator<Item = VtxoRequest>,
1349 cosign_pubkey: PublicKey,
1350 unlock_hash: UnlockHash,
1351 expiry_height: BlockHeight,
1352 server_pubkey: PublicKey,
1353 server_cosign_pubkey: PublicKey,
1354 exit_delta: BlockDelta,
1355 ) -> Result<VtxoTreeSpec, SignedTreeBuilderError> {
1356 let reqs = vtxos.into_iter()
1357 .map(|vtxo| VtxoLeafSpec {
1358 vtxo: vtxo,
1359 cosign_pubkey: None,
1360 unlock_hash: unlock_hash,
1361 })
1362 .collect::<Vec<_>>();
1363 if reqs.len() < 2 {
1364 return Err(SignedTreeBuilderError("need to have at least 2 VTXOs in tree"));
1365 }
1366 Ok(VtxoTreeSpec::new(
1367 reqs,
1368 server_pubkey,
1369 expiry_height,
1370 exit_delta,
1371 vec![cosign_pubkey, server_cosign_pubkey],
1374 ))
1375 }
1376
1377 pub fn new(
1379 vtxos: impl IntoIterator<Item = VtxoRequest>,
1380 cosign_pubkey: PublicKey,
1381 unlock_preimage: UnlockPreimage,
1382 expiry_height: BlockHeight,
1383 server_pubkey: PublicKey,
1384 server_cosign_pubkey: PublicKey,
1385 exit_delta: BlockDelta,
1386 ) -> Result<SignedTreeBuilder<state::Preparing>, SignedTreeBuilderError> {
1387 let tree = Self::construct_tree_spec(
1388 vtxos,
1389 cosign_pubkey,
1390 sha256::Hash::hash(&unlock_preimage),
1391 expiry_height,
1392 server_pubkey,
1393 server_cosign_pubkey,
1394 exit_delta,
1395 )?;
1396
1397 Ok(SignedTreeBuilder {
1398 expiry_height, server_pubkey, exit_delta, cosign_pubkey, unlock_preimage,
1399 tree: BuilderTree::Spec(tree),
1400 user_pub_nonces: Vec::new(),
1401 user_sec_nonces: None,
1402 _state: PhantomData,
1403 })
1404 }
1405
1406 pub fn set_utxo(self, utxo: OutPoint) -> SignedTreeBuilder<state::CanGenerateNonces> {
1408 let unsigned_tree = match self.tree {
1409 BuilderTree::Spec(s) => s.into_unsigned_tree(utxo),
1410 BuilderTree::Unsigned(t) => t, };
1412 SignedTreeBuilder {
1413 tree: BuilderTree::Unsigned(unsigned_tree),
1414
1415 expiry_height: self.expiry_height,
1416 server_pubkey: self.server_pubkey,
1417 exit_delta: self.exit_delta,
1418 cosign_pubkey: self.cosign_pubkey,
1419 unlock_preimage: self.unlock_preimage,
1420 user_pub_nonces: self.user_pub_nonces,
1421 user_sec_nonces: self.user_sec_nonces,
1422 _state: PhantomData,
1423 }
1424 }
1425 }
1426
1427 impl SignedTreeBuilder<state::CanGenerateNonces> {
1428 pub fn generate_user_nonces(
1430 self,
1431 cosign_key: &Keypair,
1432 ) -> SignedTreeBuilder<state::CanFinish> {
1433 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1434
1435 let mut cosign_sec_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1436 let mut cosign_pub_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1437 for sh in &unsigned_tree.internal_sighashes {
1438 let pair = musig::nonce_pair_with_msg(&cosign_key, &sh.to_byte_array());
1439 cosign_sec_nonces.push(pair.0);
1440 cosign_pub_nonces.push(pair.1);
1441 }
1442
1443 SignedTreeBuilder {
1444 user_pub_nonces: cosign_pub_nonces,
1445 user_sec_nonces: Some(cosign_sec_nonces),
1446
1447 expiry_height: self.expiry_height,
1448 server_pubkey: self.server_pubkey,
1449 exit_delta: self.exit_delta,
1450 cosign_pubkey: self.cosign_pubkey,
1451 unlock_preimage: self.unlock_preimage,
1452 tree: self.tree,
1453 _state: PhantomData,
1454 }
1455 }
1456 }
1457
1458 #[derive(Debug, Clone)]
1460 pub struct SignedTreeCosignResponse {
1461 pub pub_nonces: Vec<musig::PublicNonce>,
1462 pub partial_signatures: Vec<musig::PartialSignature>,
1463 }
1464
1465 impl SignedTreeBuilder<state::ServerCanCosign> {
1466 pub fn new_for_cosign(
1468 vtxos: impl IntoIterator<Item = VtxoRequest>,
1469 cosign_pubkey: PublicKey,
1470 unlock_preimage: UnlockPreimage,
1471 expiry_height: BlockHeight,
1472 server_pubkey: PublicKey,
1473 server_cosign_pubkey: PublicKey,
1474 exit_delta: BlockDelta,
1475 utxo: OutPoint,
1476 user_pub_nonces: Vec<musig::PublicNonce>,
1477 ) -> Result<SignedTreeBuilder<state::ServerCanCosign>, SignedTreeBuilderError> {
1478 let unsigned_tree = SignedTreeBuilder::construct_tree_spec(
1479 vtxos,
1480 cosign_pubkey,
1481 sha256::Hash::hash(&unlock_preimage),
1482 expiry_height,
1483 server_pubkey,
1484 server_cosign_pubkey,
1485 exit_delta,
1486 )?.into_unsigned_tree(utxo);
1487
1488 Ok(SignedTreeBuilder {
1489 expiry_height,
1490 server_pubkey,
1491 exit_delta,
1492 cosign_pubkey,
1493 unlock_preimage,
1494 user_pub_nonces,
1495 tree: BuilderTree::Unsigned(unsigned_tree),
1496 user_sec_nonces: None,
1497 _state: PhantomData,
1498 })
1499 }
1500
1501 pub fn server_cosign(&self, server_cosign_key: &Keypair) -> SignedTreeCosignResponse {
1503 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1504
1505 let mut sec_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1506 let mut pub_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1507 for sh in &unsigned_tree.internal_sighashes {
1508 let pair = musig::nonce_pair_with_msg(&server_cosign_key, &sh.to_byte_array());
1509 sec_nonces.push(pair.0);
1510 pub_nonces.push(pair.1);
1511 }
1512
1513 let agg_nonces = self.user_pub_nonces().iter().zip(&pub_nonces)
1514 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1515 .collect::<Vec<_>>();
1516
1517 let sigs = unsigned_tree.cosign_tree(&agg_nonces, &server_cosign_key, sec_nonces);
1518
1519 SignedTreeCosignResponse {
1520 pub_nonces,
1521 partial_signatures: sigs,
1522 }
1523 }
1524 }
1525
1526 impl SignedTreeBuilder<state::CanFinish> {
1527 pub fn verify_cosign_response(
1529 &self,
1530 server_cosign: &SignedTreeCosignResponse,
1531 ) -> Result<(), CosignSignatureError> {
1532 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1533
1534 let agg_nonces = self.user_pub_nonces().iter()
1535 .zip(&server_cosign.pub_nonces)
1536 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1537 .collect::<Vec<_>>();
1538
1539 unsigned_tree.verify_global_cosign_partial_sigs(
1540 *unsigned_tree.spec.global_cosign_pubkeys.get(1).expect("state invariant"),
1541 &agg_nonces,
1542 &server_cosign.pub_nonces,
1543 &server_cosign.partial_signatures,
1544 )
1545 }
1546
1547 pub fn build_tree(
1548 self,
1549 server_cosign: &SignedTreeCosignResponse,
1550 cosign_key: &Keypair,
1551 ) -> Result<SignedVtxoTreeSpec, IncorrectSigningKeyError> {
1552 if cosign_key.public_key() != self.cosign_pubkey {
1553 return Err(IncorrectSigningKeyError {
1554 required: Some(self.cosign_pubkey),
1555 provided: cosign_key.public_key(),
1556 });
1557 }
1558
1559 let agg_nonces = self.user_pub_nonces().iter().zip(&server_cosign.pub_nonces)
1560 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1561 .collect::<Vec<_>>();
1562
1563 let unsigned_tree = self.tree.into_unsigned_tree().expect("state invariant");
1564 let sec_nonces = self.user_sec_nonces.expect("state invariant");
1565 let partial_sigs = unsigned_tree.cosign_tree(&agg_nonces, cosign_key, sec_nonces);
1566
1567 debug_assert!(unsigned_tree.verify_global_cosign_partial_sigs(
1568 self.cosign_pubkey,
1569 &agg_nonces,
1570 &self.user_pub_nonces,
1571 &partial_sigs,
1572 ).is_ok(), "produced invalid partial signatures");
1573
1574 let sigs = unsigned_tree.combine_partial_signatures(
1575 &agg_nonces,
1576 &HashMap::new(),
1577 &[&server_cosign.partial_signatures, &partial_sigs],
1578 ).expect("should work with correct cosign signatures");
1579
1580 Ok(unsigned_tree.into_signed_tree(sigs))
1581 }
1582 }
1583}
1584
1585const VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1587
1588impl ProtocolEncoding for VtxoTreeSpec {
1589 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1590 w.emit_u8(VTXO_TREE_SPEC_VERSION)?;
1591 w.emit_u32(self.expiry_height)?;
1592 self.server_pubkey.encode(w)?;
1593 w.emit_u16(self.exit_delta)?;
1594 LengthPrefixedVector::new(&self.global_cosign_pubkeys).encode(w)?;
1595 LengthPrefixedVector::new(&self.vtxos).encode(w)?;
1596 Ok(())
1597 }
1598
1599 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1600 let version = r.read_u8()?;
1601
1602 if version != VTXO_TREE_SPEC_VERSION {
1603 return Err(ProtocolDecodingError::invalid(format_args!(
1604 "invalid VtxoTreeSpec encoding version byte: {version:#x}",
1605 )));
1606 }
1607
1608 let expiry_height = check_block_height(r.read_u32()?)
1609 .map_err(|e| ProtocolDecodingError::invalid_err(e, "expiry_height"))?;
1610 let server_pubkey = PublicKey::decode(r)?;
1611 let exit_delta = check_block_delta(r.read_u16()?)
1612 .map_err(|e| ProtocolDecodingError::invalid_err(e, "exit_delta"))?;
1613 let global_cosign_pubkeys = LengthPrefixedVector::decode(r)?.into_inner();
1614 let vtxos = LengthPrefixedVector::decode(r)?.into_inner();
1615 if vtxos.is_empty() {
1616 return Err(ProtocolDecodingError::invalid(
1617 "vtxo tree spec must have at least one leaf",
1618 ));
1619 }
1620 Ok(VtxoTreeSpec { vtxos, expiry_height, server_pubkey, exit_delta, global_cosign_pubkeys })
1621 }
1622}
1623
1624const SIGNED_VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1626
1627const SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE: u8 = 0x01;
1629
1630impl ProtocolEncoding for SignedVtxoTreeSpec {
1631 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1632 w.emit_u8(SIGNED_VTXO_TREE_SPEC_VERSION)?;
1633 self.spec.encode(w)?;
1634 self.utxo.encode(w)?;
1635 LengthPrefixedVector::new(&self.cosign_sigs).encode(w)?;
1636 Ok(())
1637 }
1638
1639 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1640 let version = r.read_u8()?;
1641 if version != SIGNED_VTXO_TREE_SPEC_VERSION
1642 && version != SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE
1643 {
1644 return Err(ProtocolDecodingError::invalid(format_args!(
1645 "invalid SignedVtxoTreeSpec encoding version byte: {version:#x}",
1646 )));
1647 }
1648 let spec = VtxoTreeSpec::decode(r)?;
1649 let utxo = OutPoint::decode(r)?;
1650 let cosign_sigs = if version == SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE {
1651 let nb_cosign_sigs = r.read_u32()?;
1652 OversizedVectorError::check::<schnorr::Signature>(nb_cosign_sigs as usize)?;
1653 let mut cosign_sigs = Vec::with_capacity(nb_cosign_sigs as usize);
1654 for _ in 0..nb_cosign_sigs {
1655 cosign_sigs.push(schnorr::Signature::decode(r)?);
1656 }
1657 cosign_sigs
1658 } else {
1659 LengthPrefixedVector::decode(r)?.into_inner()
1660 };
1661 Ok(SignedVtxoTreeSpec { spec, utxo, cosign_sigs })
1662 }
1663}
1664
1665
1666#[cfg(test)]
1667mod test {
1668 use std::iter;
1669 use std::collections::HashMap;
1670 use std::str::FromStr;
1671
1672 use bitcoin::hashes::{siphash24, sha256, Hash, HashEngine};
1673 use bitcoin::key::rand::Rng;
1674 use bitcoin::secp256k1::{self, rand, Keypair};
1675 use bitcoin::{absolute, transaction};
1676 use rand::SeedableRng;
1677
1678 use crate::encode;
1679 use crate::test_util::{encoding_roundtrip, json_roundtrip};
1680 use crate::tree::signed::builder::SignedTreeBuilder;
1681 use crate::vtxo::policy::{ServerVtxoPolicy, VtxoPolicy};
1682
1683 use super::*;
1684
1685 fn test_tree_amounts(
1686 tree: &UnsignedVtxoTree,
1687 root_value: Amount,
1688 ) {
1689 let map = tree.txs.iter().map(|tx| (tx.compute_txid(), tx)).collect::<HashMap<_, _>>();
1690
1691 for (idx, tx) in tree.txs.iter().take(tree.txs.len().saturating_sub(1)).enumerate() {
1693 println!("tx #{idx}: {}", bitcoin::consensus::encode::serialize_hex(tx));
1694 let input = tx.input.iter().map(|i| {
1695 let prev = i.previous_output;
1696 map.get(&prev.txid).expect(&format!("tx {} not found", prev.txid))
1697 .output[prev.vout as usize].value
1698 }).sum::<Amount>();
1699 let output = tx.output_value();
1700 assert!(input >= output);
1701 assert_eq!(input, output);
1702 }
1703
1704 let root = tree.txs.last().unwrap();
1706 assert_eq!(root_value, root.output_value());
1707 }
1708
1709 #[test]
1710 fn vtxo_tree() {
1711 let secp = secp256k1::Secp256k1::new();
1712 let mut rand = rand::rngs::StdRng::seed_from_u64(42);
1713 let random_sig = {
1714 let key = Keypair::new(&secp, &mut rand);
1715 let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1716 let msg = secp256k1::Message::from_digest(sha.to_byte_array());
1717 secp.sign_schnorr(&msg, &key)
1718 };
1719
1720 let server_key = Keypair::new(&secp, &mut rand);
1721 let server_cosign_key = Keypair::new(&secp, &mut rand);
1722
1723 struct Req {
1724 key: Keypair,
1725 cosign_key: Keypair,
1726 amount: Amount,
1727 hash: sha256::Hash,
1728 }
1729 impl Req {
1730 fn to_vtxo(&self) -> VtxoLeafSpec {
1731 VtxoLeafSpec {
1732 vtxo: VtxoRequest {
1733 amount: self.amount,
1734 policy: VtxoPolicy::new_pubkey(self.key.public_key()),
1735 },
1736 cosign_pubkey: Some(self.cosign_key.public_key()),
1737 unlock_hash: self.hash,
1738 }
1739 }
1740 }
1741
1742 let nb_leaves = 27;
1743 let reqs = iter::repeat_with(|| Req {
1744 key: Keypair::new(&secp, &mut rand),
1745 cosign_key: Keypair::new(&secp, &mut rand),
1746 amount: Amount::from_sat(100_000),
1747 hash: sha256::Hash::from_byte_array(rand.r#gen()),
1748 }).take(nb_leaves).collect::<Vec<_>>();
1749 let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
1750
1751 let spec = VtxoTreeSpec::new(
1752 reqs.iter().map(|r| r.to_vtxo()).collect(),
1753 server_key.public_key(),
1754 101_000,
1755 2016,
1756 vec![server_cosign_key.public_key()],
1757 );
1758 assert_eq!(spec.nb_leaves(), nb_leaves);
1759 assert_eq!(spec.total_required_value().to_sat(), 2700000);
1760 let nb_nodes = spec.nb_nodes();
1761
1762 encoding_roundtrip(&spec);
1763
1764 let unsigned = spec.into_unsigned_tree(point);
1765
1766 test_tree_amounts(&unsigned, unsigned.spec.total_required_value());
1767
1768 let sighashes_hash = {
1769 let mut eng = siphash24::Hash::engine();
1770 unsigned.internal_sighashes.iter().for_each(|h| eng.input(&h[..]));
1771 siphash24::Hash::from_engine(eng)
1772 };
1773 assert_eq!(sighashes_hash.to_string(), "b83a4fe5937a7404");
1774
1775 let signed = unsigned.into_signed_tree(vec![random_sig; nb_nodes]);
1776
1777 encoding_roundtrip(&signed);
1778
1779 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1780 struct JsonSignedVtxoTreeSpec {
1781 #[serde(with = "encode::serde")]
1782 pub spec: SignedVtxoTreeSpec,
1783 }
1784
1785 json_roundtrip(&JsonSignedVtxoTreeSpec { spec: signed.clone() });
1786
1787 for l in 0..nb_leaves {
1788 let exit = signed.exit_branch(l);
1789
1790 let mut iter = exit.iter().enumerate().peekable();
1792 while let Some((i, cur)) = iter.next() {
1793 if let Some((_, next)) = iter.peek() {
1794 assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
1795 }
1796 }
1797 }
1798
1799 let cached = signed.into_cached_tree();
1800 for vtxo in cached.output_vtxos() {
1801 encoding_roundtrip(&vtxo);
1802 }
1803 }
1804
1805 #[test]
1806 fn test_tree_builder() {
1807 let expiry = 100_000;
1808 let exit_delta = 24;
1809
1810 let vtxo_key = Keypair::from_str("985247fb0ef008f8043b6be28add87710d42d482433ef287235bfe041ee6cc11").unwrap();
1811 let policy = VtxoPolicy::new_pubkey(vtxo_key.public_key());
1812 let user_cosign_key = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1813 let user_cosign_pubkey = user_cosign_key.public_key();
1814 println!("user_cosign_pubkey: {}", user_cosign_pubkey);
1815
1816 let server_key = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1817 let server_pubkey = server_key.public_key();
1818 println!("server_pubkey: {}", server_pubkey);
1819
1820 let server_cosign_key = Keypair::from_str("52a506fbae3b725749d2486afd4761841ec685b841c2967e30f24182c4b02eed").unwrap();
1821 let server_cosign_pubkey = server_cosign_key.public_key();
1822 println!("server_cosign_pubkey: {}", server_cosign_pubkey);
1823
1824 let unlock_preimage = rand::random::<UnlockPreimage>();
1825 let unlock_hash = sha256::Hash::hash(&unlock_preimage);
1826 println!("unlock_hash: {}", unlock_hash);
1827
1828 for nb_vtxos in [2, 3, 4, 5, 10, 50] {
1830 println!("building tree with {} vtxos", nb_vtxos);
1831 let vtxos = (0..nb_vtxos).map(|i| VtxoRequest {
1832 amount: Amount::from_sat(1000 * (i + 1)),
1833 policy: policy.clone(),
1834 }).collect::<Vec<_>>();
1835
1836 let builder = SignedTreeBuilder::new(
1837 vtxos.iter().cloned(), user_cosign_pubkey, unlock_preimage, expiry, server_pubkey,
1838 server_cosign_pubkey, exit_delta,
1839 ).unwrap();
1840
1841 let funding_tx = Transaction {
1842 version: transaction::Version::TWO,
1843 lock_time: absolute::LockTime::ZERO,
1844 input: vec![],
1845 output: vec![builder.funding_txout()],
1846 };
1847 let utxo = OutPoint::new(funding_tx.compute_txid(), 0);
1848 let builder = builder.set_utxo(utxo).generate_user_nonces(&user_cosign_key);
1849 let user_pub_nonces = builder.user_pub_nonces().to_vec();
1850
1851 let cosign = {
1852 let builder = SignedTreeBuilder::new_for_cosign(
1853 vtxos.iter().cloned(), user_cosign_pubkey, unlock_preimage, expiry, server_pubkey,
1854 server_cosign_pubkey, exit_delta, utxo, user_pub_nonces,
1855 ).unwrap();
1856 builder.server_cosign(&server_cosign_key)
1857 };
1858
1859 builder.verify_cosign_response(&cosign).unwrap();
1860 let tree = builder.build_tree(&cosign, &user_cosign_key).unwrap().into_cached_tree();
1861
1862 for mut vtxo in tree.output_vtxos() {
1864 {
1865 let mut with_preimage = vtxo.clone();
1867 assert!(with_preimage.provide_unlock_preimage(unlock_preimage));
1868 assert!(with_preimage.validate(&funding_tx).is_err());
1869 }
1870
1871 let (ctx, req) = LeafVtxoCosignContext::new(&vtxo, &funding_tx, &vtxo_key);
1872 let cosign = LeafVtxoCosignResponse::new_cosign(&req, &vtxo, &funding_tx, &server_key);
1873 assert!(ctx.finalize(&mut vtxo, cosign));
1874
1875
1876 assert!(!vtxo.has_all_witnesses());
1878 vtxo.validate_unsigned(&funding_tx).expect("Tree is valid if we ignore sigs");
1879 vtxo.validate(&funding_tx).expect_err("The signature check must fail");
1880
1881 assert!(vtxo.provide_unlock_preimage(unlock_preimage));
1882
1883 println!("vtxo debug: {:#?}", vtxo);
1884 println!("vtxo hex: {}", vtxo.serialize_hex());
1885 assert!(vtxo.has_all_witnesses());
1886 vtxo.validate(&funding_tx).expect("should be value");
1887
1888 vtxo.invalidate_final_sig();
1889 assert!(vtxo.has_all_witnesses(), "still has all witnesses, just an invalid one");
1890 vtxo.validate_unsigned(&funding_tx).expect("unsigned still valid");
1891 vtxo.validate(&funding_tx).expect_err("but not fully valid anymore");
1892 }
1893
1894 for (idx, (vtxo, _spending_txid)) in tree.internal_vtxos().enumerate() {
1895 let mut prev_txout = funding_tx.output[vtxo.chain_anchor().vout as usize].clone();
1897
1898 for item in vtxo.transactions() {
1900 crate::test_util::verify_tx(&[prev_txout], 0, &item.tx)
1901 .expect("Invalid transaction");
1902 prev_txout = item.tx.output[item.output_idx].clone();
1903 }
1904
1905 if idx < nb_vtxos as usize {
1907 matches!(vtxo.policy(), ServerVtxoPolicy::HarkLeaf(_));
1909 } else {
1910 matches!(vtxo.policy(), ServerVtxoPolicy::Expiry(_));
1911 }
1912 }
1913 }
1914 }
1915
1916 #[test]
1917 fn vtxo_leaf_spec_encoding() {
1918 let pk1: PublicKey = "020aceb65eed0ee5c512d3718e6f4bd868a7efb58ede7899ffd9bcba09555d4eb8".parse().unwrap();
1919 let pk2: PublicKey = "02e4ed0ca35c3b8a2ff675b9b23f4961964b57e130afa607e32a83d2d9a510622b".parse().unwrap();
1920 let hash = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1921
1922 let spec_with_cosign = VtxoLeafSpec {
1924 vtxo: VtxoRequest {
1925 amount: Amount::from_sat(100_000),
1926 policy: VtxoPolicy::new_pubkey(pk1),
1927 },
1928 cosign_pubkey: Some(pk2),
1929 unlock_hash: hash,
1930 };
1931 encoding_roundtrip(&spec_with_cosign);
1932
1933 let spec_without_cosign = VtxoLeafSpec {
1935 vtxo: VtxoRequest {
1936 amount: Amount::from_sat(200_000),
1937 policy: VtxoPolicy::new_pubkey(pk1),
1938 },
1939 cosign_pubkey: None,
1940 unlock_hash: hash,
1941 };
1942 encoding_roundtrip(&spec_without_cosign);
1943 }
1944
1945 #[test]
1946 fn vtxo_tree_spec_rejects_empty_vtxos() {
1947 let pk1: PublicKey = "020aceb65eed0ee5c512d3718e6f4bd868a7efb58ede7899ffd9bcba09555d4eb8"
1948 .parse().unwrap();
1949 let pk2: PublicKey = "02e4ed0ca35c3b8a2ff675b9b23f4961964b57e130afa607e32a83d2d9a510622b"
1950 .parse().unwrap();
1951 let hash = sha256::Hash::from_str(
1952 "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
1953 ).unwrap();
1954
1955 let leaf = VtxoLeafSpec {
1957 vtxo: VtxoRequest {
1958 amount: Amount::from_sat(100_000),
1959 policy: VtxoPolicy::new_pubkey(pk1),
1960 },
1961 cosign_pubkey: Some(pk2),
1962 unlock_hash: hash,
1963 };
1964 let spec = VtxoTreeSpec::new(vec![leaf], pk1, 100_000, 2016, vec![]);
1965 encoding_roundtrip(&spec);
1966
1967 let empty_spec = VtxoTreeSpec { vtxos: vec![], ..spec };
1969 let buf = empty_spec.serialize();
1970 let err = VtxoTreeSpec::deserialize(&buf)
1971 .expect_err("decoding a VtxoTreeSpec with zero vtxos must fail");
1972 let msg = err.to_string();
1973 assert!(msg.contains("at least one leaf"), "unexpected error message: {msg}");
1974 }
1975
1976 #[test]
1977 fn test_compat_u32_size_signed_spec() {
1978 let hex = "0102888a0100035b0ef8c9bd756af433edc3129975888f6f18b8185b2afbaabc8bb3029a00cf81e0070102f8112234026e68b1e4d1565540d7b791ced1b64c5f30525cbe14f21dd7aa8c78030003c48b53afac0d2d5169cd6848f03f67a21db6f506f8b8fc2dbef2552b6a7dc111a08601000000000002c6c80e198e170ca6f8fa17810d8ee23c7c0d85c5d2febc95c3e24b1878ca733fbfd032abc31b253f5063521fd5b4c431f2cdd3fee1b4ec00a9b00f69d3b033e7000296dfec4c92e831ffe3619285646a46c545577f19dbc27c2fc0950bffb0f4a362a08601000000000003850a7d2bf22e6ba669695410a8b03c5800a0d4c2bec814b9eb21b0cddd2af5c935395dea8bd6dcac26a8a417b553b18d13027c23e8016c3466b81e70832254360002415f712d6e551b715542422b975a2ae7c635b44a1e3747d5a8674231fa10a841a08601000000000002a3d4de26a87f8bb9d5c0b9f1e3409a635b35f656575d8ad60a6a2294ac4e50b87c0cc2177dfce6432efa42ca6c04c0b774dbb3c5ca2573cd893443e10e393bfd0100000000000000000000000000000000000000000000000000000000000000010000000400000002cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb";
1980 let ret = SignedVtxoTreeSpec::deserialize_hex(hex).unwrap();
1981 assert_eq!(ret.cosign_sigs.len(), 4);
1982 }
1983}