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