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