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, Transaction, TxIn, TxOut, 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::error::IncorrectSigningKeyError;
17use crate::{musig, scripts, SECP, SignedVtxoRequest, Vtxo, VtxoPolicy, VtxoRequest};
18use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
19use crate::tree::{self, Tree};
20use crate::vtxo::{self, GenesisItem, GenesisTransition};
21
22
23pub type UnlockHash = sha256::Hash;
25
26pub type UnlockPreimage = [u8; 32];
28
29pub const NODE_SPEND_WEIGHT: Weight = Weight::from_wu(140);
31
32pub fn expiry_clause(server_pubkey: PublicKey, expiry_height: BlockHeight) -> ScriptBuf {
34 let pk = server_pubkey.x_only_public_key().0;
35 scripts::timelock_sign(expiry_height, pk)
36}
37
38pub fn unlock_clause(pubkey: XOnlyPublicKey, unlock_hash: UnlockHash) -> ScriptBuf {
40 scripts::hash_and_sign(unlock_hash, pubkey)
41}
42
43pub fn leaf_cosign_taproot(
45 agg_pk: XOnlyPublicKey,
46 server_pubkey: PublicKey,
47 expiry_height: BlockHeight,
48 unlock_hash: UnlockHash,
49) -> taproot::TaprootSpendInfo {
50 taproot::TaprootBuilder::new()
51 .add_leaf(1, expiry_clause(server_pubkey, expiry_height)).unwrap()
52 .add_leaf(1, unlock_clause(agg_pk, unlock_hash)).unwrap()
53 .finalize(&SECP, agg_pk).unwrap()
54}
55
56pub fn cosign_taproot(
57 agg_pk: XOnlyPublicKey,
58 server_pubkey: PublicKey,
59 expiry_height: BlockHeight,
60) -> taproot::TaprootSpendInfo {
61 taproot::TaprootBuilder::new()
62 .add_leaf(0, expiry_clause(server_pubkey, expiry_height)).unwrap()
63 .finalize(&SECP, agg_pk).unwrap()
64}
65
66#[derive(Debug, Clone, Eq, PartialEq)]
68pub struct VtxoTreeSpec {
69 pub vtxos: Vec<SignedVtxoRequest>,
70 pub expiry_height: BlockHeight,
71 pub server_pubkey: PublicKey,
72 pub exit_delta: BlockDelta,
73 pub global_cosign_pubkeys: Vec<PublicKey>,
74}
75
76impl VtxoTreeSpec {
77 pub fn new(
78 vtxos: Vec<SignedVtxoRequest>,
79 server_pubkey: PublicKey,
80 expiry_height: BlockHeight,
81 exit_delta: BlockDelta,
82 global_cosign_pubkeys: Vec<PublicKey>,
83 ) -> VtxoTreeSpec {
84 assert_ne!(vtxos.len(), 0);
85 VtxoTreeSpec { vtxos, server_pubkey, expiry_height, exit_delta, global_cosign_pubkeys }
86 }
87
88 pub fn nb_leaves(&self) -> usize {
89 self.vtxos.len()
90 }
91
92 pub fn nb_nodes(&self) -> usize {
93 Tree::nb_nodes_for_leaves(self.nb_leaves())
94 }
95
96 pub fn iter_vtxos(&self) -> impl Iterator<Item = &SignedVtxoRequest> {
97 self.vtxos.iter()
98 }
99
100 pub fn leaf_idx_of(&self, vtxo_request: &SignedVtxoRequest) -> Option<usize> {
102 self.vtxos.iter().position(|e| e == vtxo_request)
103 }
104
105 pub fn total_required_value(&self) -> Amount {
110 self.vtxos.iter().map(|d| d.vtxo.amount).sum::<Amount>()
111 }
112
113 pub fn cosign_taproot(&self, agg_pk: XOnlyPublicKey) -> taproot::TaprootSpendInfo {
115 cosign_taproot(agg_pk, self.server_pubkey, self.expiry_height)
116 }
117
118 pub fn funding_tx_cosign_pubkey(&self) -> XOnlyPublicKey {
122 let keys = self.vtxos.iter()
123 .filter_map(|v| v.cosign_pubkey)
124 .chain(self.global_cosign_pubkeys.iter().copied());
125 musig::combine_keys(keys)
126 }
127
128 pub fn funding_tx_script_pubkey(&self) -> ScriptBuf {
132 let agg_pk = self.funding_tx_cosign_pubkey();
133 self.cosign_taproot(agg_pk).script_pubkey()
134 }
135
136 pub fn funding_tx_txout(&self) -> TxOut {
140 TxOut {
141 script_pubkey: self.funding_tx_script_pubkey(),
142 value: self.total_required_value(),
143 }
144 }
145
146 fn node_tx<'a>(&self, children: impl Iterator<Item=(&'a Transaction, &'a XOnlyPublicKey)>) -> Transaction {
147 Transaction {
148 version: bitcoin::transaction::Version(3),
149 lock_time: bitcoin::absolute::LockTime::ZERO,
150 input: vec![TxIn {
151 previous_output: OutPoint::null(), sequence: Sequence::ZERO,
153 script_sig: ScriptBuf::new(),
154 witness: Witness::new(),
155 }],
156 output: children.map(|(tx, agg_pk)| {
157 let taproot = self.cosign_taproot(*agg_pk);
158 TxOut {
159 script_pubkey: taproot.script_pubkey(),
160 value: tx.output_value(),
161 }
162 }).chain(Some(fee::fee_anchor())).collect(),
163 }
164 }
165
166 fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
167 let txout = TxOut {
168 value: vtxo.amount,
169 script_pubkey: vtxo.policy.script_pubkey(self.server_pubkey, self.exit_delta),
170 };
171
172 vtxo::create_exit_tx(OutPoint::null(), txout, None)
173 }
174
175 pub fn cosign_agg_pks(&self)
179 -> impl Iterator<Item = XOnlyPublicKey> + iter::DoubleEndedIterator + iter::ExactSizeIterator + '_
180 {
181 Tree::new(self.nb_leaves()).into_iter().map(|node| {
182 musig::combine_keys(
183 node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey)
184 .chain(self.global_cosign_pubkeys.iter().copied())
185 )
186 })
187 }
188
189 pub fn unsigned_transactions(&self, utxo: OutPoint) -> Vec<Transaction> {
191 let tree = Tree::new(self.nb_leaves());
192
193 let cosign_agg_pks = self.cosign_agg_pks().collect::<Vec<_>>();
194
195 let mut txs = Vec::with_capacity(tree.nb_nodes());
196 for node in tree.iter() {
197 let tx = if node.is_leaf() {
198 self.leaf_tx(&self.vtxos[node.idx()].vtxo).clone()
199 } else {
200 let mut buf = [None; tree::RADIX];
201 for (idx, child) in node.children().enumerate() {
202 buf[idx] = Some((&txs[child], &cosign_agg_pks[child]));
203 }
204 self.node_tx(buf.iter().filter_map(|x| *x))
205 };
206 txs.push(tx.clone());
207 };
208
209 txs.last_mut().unwrap().input[0].previous_output = utxo;
211 for node in tree.iter().rev() {
212 let txid = txs[node.idx()].compute_txid();
213 for (i, child) in node.children().enumerate() {
214 let point = OutPoint::new(txid, i as u32);
215 txs[child].input[0].previous_output = point;
216 }
217 }
218
219 txs
220 }
221
222 pub fn signed_transactions(
224 &self,
225 utxo: OutPoint,
226 signatures: &[schnorr::Signature],
227 ) -> Vec<Transaction> {
228 let mut txs = self.unsigned_transactions(utxo);
229 for (tx, sig) in txs.iter_mut().zip(signatures) {
230 tx.input[0].witness.push(&sig[..]);
231 }
232 txs
233 }
234
235 pub fn calculate_cosign_agg_nonces(
239 &self,
240 leaf_cosign_nonces: &HashMap<PublicKey, Vec<PublicNonce>>,
241 global_signer_cosign_nonces: &[impl AsRef<[PublicNonce]>],
242 ) -> Result<Vec<AggregatedNonce>, String> {
243 if global_signer_cosign_nonces.len() != self.global_cosign_pubkeys.len() {
244 return Err("missing global signer nonces".into());
245 }
246
247 Tree::new(self.nb_leaves()).iter().enumerate().map(|(idx, node)| {
248 let mut nonces = Vec::new();
249 for pk in node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey) {
250 nonces.push(leaf_cosign_nonces.get(&pk)
251 .ok_or_else(|| format!("missing nonces for leaf pk {}", pk))?
252 .get(node.level())
255 .ok_or_else(|| format!("not enough nonces for leaf_pk {}", pk))?
256 );
257 }
258 for glob in global_signer_cosign_nonces {
259 nonces.push(glob.as_ref().get(idx).ok_or("not enough global cosign nonces")?);
260 }
261 Ok(musig::nonce_agg(&nonces))
262 }).collect()
263 }
264
265 pub fn into_unsigned_tree(
270 self,
271 utxo: OutPoint,
272 ) -> UnsignedVtxoTree {
273 UnsignedVtxoTree::new(self, utxo)
274 }
275}
276
277#[derive(Debug, Clone)]
281pub struct UnsignedVtxoTree {
282 pub spec: VtxoTreeSpec,
283 pub utxo: OutPoint,
284
285 pub cosign_agg_pks: Vec<XOnlyPublicKey>,
289 pub txs: Vec<Transaction>,
291 pub sighashes: Vec<TapSighash>,
293
294 tree: Tree,
295}
296
297impl UnsignedVtxoTree {
298 pub fn new(
299 spec: VtxoTreeSpec,
300 utxo: OutPoint,
301 ) -> UnsignedVtxoTree {
302 let tree = Tree::new(spec.nb_leaves());
303
304 let cosign_agg_pks = spec.cosign_agg_pks().collect::<Vec<_>>();
305 let txs = spec.unsigned_transactions(utxo);
306
307 let root_txout = spec.funding_tx_txout();
308 let sighashes = tree.iter().map(|node| {
309 let prev = if let Some((parent, sibling_idx)) = tree.parent_idx_of_with_sibling_idx(node.idx()) {
310 assert!(!node.is_root());
311 &txs[parent].output[sibling_idx]
312 } else {
313 assert!(node.is_root());
314 &root_txout
315 };
316 SighashCache::new(&txs[node.idx()]).taproot_key_spend_signature_hash(
317 0, &sighash::Prevouts::All(&[prev]),
319 TapSighashType::Default,
320 ).expect("sighash error")
321 }).collect();
322
323 UnsignedVtxoTree { spec, utxo, txs, sighashes, cosign_agg_pks, tree }
324 }
325
326 pub fn nb_leaves(&self) -> usize {
327 self.tree.nb_leaves()
328 }
329
330 pub fn nb_nodes(&self) -> usize {
331 self.tree.nb_nodes()
332 }
333
334 pub fn cosign_branch(
347 &self,
348 cosign_agg_nonces: &[AggregatedNonce],
349 leaf_idx: usize,
350 cosign_key: &Keypair,
351 cosign_sec_nonces: Vec<SecretNonce>,
352 ) -> Result<Vec<PartialSignature>, IncorrectSigningKeyError> {
353 let req = self.spec.vtxos.get(leaf_idx).expect("leaf idx out of bounds");
354 if Some(cosign_key.public_key()) != req.cosign_pubkey {
355 return Err(IncorrectSigningKeyError {
356 required: req.cosign_pubkey,
357 provided: cosign_key.public_key(),
358 });
359 }
360
361 let mut nonce_iter = cosign_sec_nonces.into_iter().enumerate();
362 let mut ret = Vec::with_capacity(self.tree.root().level() + 1);
363 for node in self.tree.iter_branch(leaf_idx) {
364 let sec_nonce = loop {
369 let next = nonce_iter.next().expect("level overflow");
370 if next.0 == node.level() {
371 break next.1;
372 }
373 };
374
375 let cosign_pubkeys = node.leaves().filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
376 .chain(self.spec.global_cosign_pubkeys.iter().copied());
377 let sighash = self.sighashes[node.idx()];
378
379 let agg_pk = self.cosign_agg_pks[node.idx()];
380 let sig = musig::partial_sign(
381 cosign_pubkeys,
382 cosign_agg_nonces[node.idx()],
383 &cosign_key,
384 sec_nonce,
385 sighash.to_byte_array(),
386 Some(self.spec.cosign_taproot(agg_pk).tap_tweak().to_byte_array()),
387 None,
388 ).0;
389 ret.push(sig);
390 }
391
392 Ok(ret)
393 }
394
395 pub fn cosign_tree(
401 &self,
402 cosign_agg_nonces: &[AggregatedNonce],
403 keypair: &Keypair,
404 cosign_sec_nonces: Vec<SecretNonce>,
405 ) -> Vec<PartialSignature> {
406 assert_eq!(cosign_agg_nonces.len(), self.nb_nodes());
407 assert_eq!(cosign_sec_nonces.len(), self.nb_nodes());
408
409 self.tree.iter().zip(cosign_sec_nonces.into_iter()).map(|(node, sec_nonce)| {
410 let cosign_pubkeys = node.leaves().filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
411 .chain(self.spec.global_cosign_pubkeys.iter().copied());
412 let sighash = self.sighashes[node.idx()];
413
414 let agg_pk = self.cosign_agg_pks[node.idx()];
415 debug_assert_eq!(agg_pk, musig::combine_keys(cosign_pubkeys.clone()));
416 musig::partial_sign(
417 cosign_pubkeys,
418 cosign_agg_nonces[node.idx()],
419 &keypair,
420 sec_nonce,
421 sighash.to_byte_array(),
422 Some(self.spec.cosign_taproot(agg_pk).tap_tweak().to_byte_array()),
423 None,
424 ).0
425 }).collect()
426 }
427
428 fn verify_node_cosign_partial_sig(
430 &self,
431 node: &tree::Node,
432 pk: PublicKey,
433 agg_nonces: &[AggregatedNonce],
434 part_sig: PartialSignature,
435 pub_nonce: PublicNonce,
436 ) -> Result<(), CosignSignatureError> {
437 let cosign_pubkeys = node.leaves().filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
438 .chain(self.spec.global_cosign_pubkeys.iter().copied());
439 let sighash = self.sighashes[node.idx()];
440
441 let taptweak = self.spec.cosign_taproot(self.cosign_agg_pks[node.idx()]).tap_tweak();
442 let key_agg = musig::tweaked_key_agg(cosign_pubkeys, taptweak.to_byte_array()).0;
443 let session = musig::Session::new(
444 &key_agg,
445 *agg_nonces.get(node.idx()).ok_or(CosignSignatureError::NotEnoughNonces)?,
446 &sighash.to_byte_array(),
447 );
448 let ok = session.partial_verify(&key_agg, &part_sig, &pub_nonce, musig::pubkey_to(pk));
449 if !ok {
450 return Err(CosignSignatureError::invalid_sig(pk));
451 }
452 Ok(())
453 }
454
455 pub fn verify_branch_cosign_partial_sigs(
459 &self,
460 cosign_agg_nonces: &[AggregatedNonce],
461 request: &SignedVtxoRequest,
462 cosign_pub_nonces: &[PublicNonce],
463 cosign_part_sigs: &[PartialSignature],
464 ) -> Result<(), String> {
465 assert_eq!(cosign_agg_nonces.len(), self.nb_nodes());
466
467 let cosign_pubkey = request.cosign_pubkey.ok_or("no cosign pubkey for request")?;
468 let leaf_idx = self.spec.leaf_idx_of(request).ok_or("request not in tree")?;
469 match self.tree.iter_branch(leaf_idx).count().cmp(&cosign_part_sigs.len()) {
471 cmp::Ordering::Less => return Err("too few partial signatures".into()),
472 cmp::Ordering::Greater => return Err("too many partial signatures".into()),
473 cmp::Ordering::Equal => {},
474 }
475
476 let mut part_sigs_iter = cosign_part_sigs.iter();
477 let mut pub_nonce_iter = cosign_pub_nonces.iter().enumerate();
478 for node in self.tree.iter_branch(leaf_idx) {
479 let pub_nonce = loop {
480 let next = pub_nonce_iter.next().ok_or("not enough pub nonces")?;
481 if next.0 == node.level() {
482 break next.1;
483 }
484 };
485 self.verify_node_cosign_partial_sig(
486 node,
487 cosign_pubkey,
488 cosign_agg_nonces,
489 part_sigs_iter.next().ok_or("not enough sigs")?.clone(),
490 *pub_nonce,
491 ).map_err(|e| format!("part sig verification failed: {}", e))?;
492 }
493
494 Ok(())
495 }
496
497 pub fn verify_global_cosign_partial_sigs(
501 &self,
502 pk: PublicKey,
503 agg_nonces: &[AggregatedNonce],
504 pub_nonces: &[PublicNonce],
505 part_sigs: &[PartialSignature],
506 ) -> Result<(), CosignSignatureError> {
507 for node in self.tree.iter() {
508 self.verify_node_cosign_partial_sig(
509 node,
510 pk,
511 agg_nonces,
512 *part_sigs.get(node.idx()).ok_or_else(|| CosignSignatureError::missing_sig(pk))?,
513 *pub_nonces.get(node.idx()).ok_or_else(|| CosignSignatureError::NotEnoughNonces)?,
514 )?;
515 }
516
517 Ok(())
518 }
519
520 pub fn combine_partial_signatures(
526 &self,
527 cosign_agg_nonces: &[AggregatedNonce],
528 leaf_part_sigs: &HashMap<PublicKey, Vec<PartialSignature>>,
529 global_signer_part_sigs: &[impl AsRef<[PartialSignature]>],
530 ) -> Result<Vec<schnorr::Signature>, CosignSignatureError> {
531 let mut leaf_part_sigs = leaf_part_sigs.iter()
533 .map(|(pk, sigs)| (pk, sigs.iter().collect()))
534 .collect::<HashMap<_, VecDeque<_>>>();
535
536 if global_signer_part_sigs.len() != self.spec.global_cosign_pubkeys.len() {
537 return Err(CosignSignatureError::Invalid(
538 "invalid nb of global cosigner partial signatures",
539 ));
540 }
541 for (pk, sigs) in self.spec.global_cosign_pubkeys.iter().zip(global_signer_part_sigs) {
542 if sigs.as_ref().len() != self.nb_nodes() {
543 return Err(CosignSignatureError::MissingSignature { pk: *pk });
544 }
545 }
546
547 let max_level = self.tree.root().level();
548 self.tree.iter().enumerate().map(|(idx, node)| {
549 let mut cosign_pks = Vec::with_capacity(max_level + 1);
550 let mut part_sigs = Vec::with_capacity(max_level + 1);
551 for leaf in node.leaves() {
552 if let Some(cosign_pk) = self.spec.vtxos[leaf].cosign_pubkey {
553 let part_sig = leaf_part_sigs.get_mut(&cosign_pk)
554 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?
555 .pop_front()
556 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?;
557 cosign_pks.push(cosign_pk);
558 part_sigs.push(part_sig);
559 }
560 }
561 cosign_pks.extend(&self.spec.global_cosign_pubkeys);
563 for sigs in global_signer_part_sigs {
564 part_sigs.push(sigs.as_ref().get(idx).expect("checked before"));
565 }
566
567 let agg_pk = self.cosign_agg_pks[node.idx()];
568 Ok(musig::combine_partial_signatures(
569 cosign_pks,
570 *cosign_agg_nonces.get(node.idx()).ok_or(CosignSignatureError::NotEnoughNonces)?,
571 self.sighashes[node.idx()].to_byte_array(),
572 Some(self.spec.cosign_taproot(agg_pk).tap_tweak().to_byte_array()),
573 &part_sigs
574 ))
575 }).collect()
576 }
577
578 pub fn verify_cosign_sigs(
582 &self,
583 signatures: &[schnorr::Signature],
584 ) -> Result<(), XOnlyPublicKey> {
585 for node in self.tree.iter() {
586 let sighash = self.sighashes[node.idx()];
587 let agg_pk = &self.cosign_agg_pks[node.idx()];
588 let pk = self.spec.cosign_taproot(*agg_pk).output_key().to_x_only_public_key();
589 let sig = signatures.get(node.idx()).ok_or_else(|| pk)?;
590 if SECP.verify_schnorr(sig, &sighash.into(), &pk).is_err() {
591 return Err(pk);
592 }
593 }
594 Ok(())
595 }
596
597 pub fn into_signed_tree(
601 self,
602 signatures: Vec<schnorr::Signature>,
603 ) -> SignedVtxoTreeSpec {
604 SignedVtxoTreeSpec {
605 spec: self.spec,
606 utxo: self.utxo,
607 cosign_sigs: signatures,
608 }
609 }
610}
611
612#[derive(PartialEq, Eq, thiserror::Error)]
614pub enum CosignSignatureError {
615 #[error("missing cosign signature from pubkey {pk}")]
616 MissingSignature { pk: PublicKey },
617 #[error("invalid cosign signature from pubkey {pk}")]
618 InvalidSignature { pk: PublicKey },
619 #[error("not enough nonces")]
620 NotEnoughNonces,
621 #[error("invalid cosign signatures: {0}")]
622 Invalid(&'static str),
623}
624
625impl fmt::Debug for CosignSignatureError {
626 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
627 fmt::Display::fmt(self, f)
628 }
629}
630
631impl CosignSignatureError {
632 fn missing_sig(cosign_pk: PublicKey) -> CosignSignatureError {
633 CosignSignatureError::MissingSignature { pk: cosign_pk }
634 }
635 fn invalid_sig(cosign_pk: PublicKey) -> CosignSignatureError {
636 CosignSignatureError::InvalidSignature { pk: cosign_pk }
637 }
638}
639
640#[derive(Debug, Clone, PartialEq)]
642pub struct SignedVtxoTreeSpec {
643 pub spec: VtxoTreeSpec,
644 pub utxo: OutPoint,
645 pub cosign_sigs: Vec<schnorr::Signature>,
647}
648
649impl SignedVtxoTreeSpec {
650 pub fn new(
652 spec: VtxoTreeSpec,
653 utxo: OutPoint,
654 signatures: Vec<schnorr::Signature>,
655 ) -> SignedVtxoTreeSpec {
656 SignedVtxoTreeSpec { spec, utxo, cosign_sigs: signatures }
657 }
658
659 pub fn nb_leaves(&self) -> usize {
660 self.spec.nb_leaves()
661 }
662
663 pub fn exit_branch(&self, leaf_idx: usize) -> Option<Vec<Transaction>> {
665 let txs = self.all_signed_txs();
666
667 if leaf_idx >= self.spec.nb_leaves() {
668 return None;
669 }
670
671 let tree = Tree::new(self.spec.nb_leaves());
672 let mut ret = tree.iter_branch(leaf_idx)
673 .map(|n| txs[n.idx()].clone())
674 .collect::<Vec<_>>();
675 ret.reverse();
676 Some(ret)
677 }
678
679 pub fn all_signed_txs(&self) -> Vec<Transaction> {
681 self.spec.signed_transactions(self.utxo, &self.cosign_sigs)
682 }
683
684 pub fn into_cached_tree(self) -> CachedSignedVtxoTree {
685 CachedSignedVtxoTree {
686 txs: self.all_signed_txs(),
687 spec: self,
688 }
689 }
690}
691
692pub struct CachedSignedVtxoTree {
696 pub spec: SignedVtxoTreeSpec,
697 pub txs: Vec<Transaction>,
699}
700
701impl CachedSignedVtxoTree {
702 pub fn exit_branch(&self, leaf_idx: usize) -> Option<Vec<&Transaction>> {
704 if leaf_idx >= self.spec.spec.nb_leaves() {
705 return None;
706 }
707
708 let tree = Tree::new(self.spec.spec.nb_leaves());
709 let mut ret = tree.iter_branch(leaf_idx)
710 .map(|n| &self.txs[n.idx()])
711 .collect::<Vec<_>>();
712 ret.reverse();
713 Some(ret)
714 }
715
716 pub fn nb_leaves(&self) -> usize {
717 self.spec.nb_leaves()
718 }
719
720 pub fn all_signed_txs(&self) -> &[Transaction] {
722 &self.txs
723 }
724
725 pub fn build_vtxo(&self, leaf_idx: usize) -> Option<Vtxo> {
727 let req = self.spec.spec.vtxos.get(leaf_idx)?;
728 let genesis = {
729 let mut genesis = Vec::new();
730
731 let mut last_node = None;
732 let tree = Tree::new(self.spec.spec.nb_leaves());
733 for node in tree.iter_branch(leaf_idx) {
734 let transition = GenesisTransition::Cosigned {
735 pubkeys: node.leaves().filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
736 .chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
737 .collect(),
738 signature: self.spec.cosign_sigs.get(node.idx()).cloned()
739 .expect("enough sigs for all nodes"),
740 };
741 let output_idx = {
742 if let Some(last) = last_node {
743 node.children().position(|child_idx| last == child_idx)
744 .expect("last node should be our child") as u8
745 } else {
746 0
748 }
749 };
750 let other_outputs = self.txs.get(node.idx()).expect("we have all txs")
751 .output.iter().enumerate()
752 .filter(|(i, o)| !o.is_p2a_fee_anchor() && *i != output_idx as usize)
753 .map(|(_i, o)| o).cloned().collect();
754 genesis.push(GenesisItem { transition, output_idx, other_outputs });
755 last_node = Some(node.idx());
756 }
757 genesis.reverse();
758 genesis
759 };
760
761 Some(Vtxo {
762 amount: req.vtxo.amount,
763 expiry_height: self.spec.spec.expiry_height,
764 server_pubkey: self.spec.spec.server_pubkey,
765 exit_delta: self.spec.spec.exit_delta,
766 anchor_point: self.spec.utxo,
767 genesis: genesis,
768 policy: req.vtxo.policy.clone(),
769 point: {
770 let leaf_tx = self.txs.get(leaf_idx).expect("leaf idx exists");
771 OutPoint::new(leaf_tx.compute_txid(), 0)
772 },
773 })
774 }
775
776 pub fn all_vtxos(&self) -> impl Iterator<Item = Vtxo> + ExactSizeIterator + '_ {
780 (0..self.nb_leaves()).map(|idx| self.build_vtxo(idx).unwrap())
781 }
782}
783
784pub mod builder {
785 use std::collections::HashMap;
792 use std::marker::PhantomData;
793
794 use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut};
795 use bitcoin::hashes::Hash;
796 use bitcoin::secp256k1::{Keypair, PublicKey};
797 use bitcoin_ext::{BlockDelta, BlockHeight};
798
799 use crate::{musig, SignedVtxoRequest, VtxoRequest};
800 use crate::error::IncorrectSigningKeyError;
801
802 use super::{CosignSignatureError, SignedVtxoTreeSpec, UnsignedVtxoTree, VtxoTreeSpec};
803
804 pub mod state {
805 mod sealed {
806 pub trait Sealed {}
808 impl Sealed for super::Preparing {}
809 impl Sealed for super::CanGenerateNonces {}
810 impl Sealed for super::ServerCanCosign {}
811 impl Sealed for super::CanFinish {}
812 }
813
814 pub trait BuilderState: sealed::Sealed {}
816
817 pub struct Preparing;
819 impl BuilderState for Preparing {}
820
821 pub struct CanGenerateNonces;
824 impl BuilderState for CanGenerateNonces {}
825
826 pub struct ServerCanCosign;
828 impl BuilderState for ServerCanCosign {}
829
830 pub struct CanFinish;
833 impl BuilderState for CanFinish {}
834
835 pub trait CanSign: BuilderState {}
838 impl CanSign for ServerCanCosign {}
839 impl CanSign for CanFinish {}
840 }
841
842 enum BuilderTree {
844 Spec(VtxoTreeSpec),
845 Unsigned(UnsignedVtxoTree),
846 }
847
848 impl BuilderTree {
849 fn unsigned_tree(&self) -> Option<&UnsignedVtxoTree> {
850 match self {
851 BuilderTree::Spec(_) => None,
852 BuilderTree::Unsigned(t) => Some(t),
853 }
854 }
855 fn into_unsigned_tree(self) -> Option<UnsignedVtxoTree> {
856 match self {
857 BuilderTree::Spec(_) => None,
858 BuilderTree::Unsigned(t) => Some(t),
859 }
860 }
861 }
862
863 pub struct SignedTreeBuilder<S: state::BuilderState> {
867 pub expiry_height: BlockHeight,
868 pub server_pubkey: PublicKey,
869 pub exit_delta: BlockDelta,
870 pub cosign_pubkey: PublicKey,
872
873 tree: BuilderTree,
874
875 user_pub_nonces: Vec<musig::PublicNonce>,
877 user_sec_nonces: Option<Vec<musig::SecretNonce>>,
880 _state: PhantomData<S>,
881 }
882
883 impl<T: state::BuilderState> SignedTreeBuilder<T> {
884 fn tree_spec(&self) -> &VtxoTreeSpec {
885 match self.tree {
886 BuilderTree::Spec(ref s) => s,
887 BuilderTree::Unsigned(ref t) => &t.spec,
888 }
889 }
890
891 pub fn total_required_value(&self) -> Amount {
893 self.tree_spec().total_required_value()
894 }
895
896 pub fn funding_script_pubkey(&self) -> ScriptBuf {
898 self.tree_spec().funding_tx_script_pubkey()
899 }
900
901 pub fn funding_txout(&self) -> TxOut {
903 let spec = self.tree_spec();
904 TxOut {
905 value: spec.total_required_value(),
906 script_pubkey: spec.funding_tx_script_pubkey(),
907 }
908 }
909 }
910
911 impl<T: state::CanSign> SignedTreeBuilder<T> {
912 pub fn user_pub_nonces(&self) -> &[musig::PublicNonce] {
914 assert!(!self.user_pub_nonces.is_empty(), "state invariant");
915 &self.user_pub_nonces
916 }
917 }
918
919 impl SignedTreeBuilder<state::Preparing> {
920 pub fn construct_tree_spec(
922 vtxos: impl IntoIterator<Item = VtxoRequest>,
923 cosign_pubkey: PublicKey,
924 expiry_height: BlockHeight,
925 server_pubkey: PublicKey,
926 server_cosign_pubkey: PublicKey,
927 exit_delta: BlockDelta,
928 ) -> VtxoTreeSpec {
929 let reqs = vtxos.into_iter()
930 .map(|vtxo| SignedVtxoRequest { cosign_pubkey: None, vtxo })
931 .collect::<Vec<_>>();
932 VtxoTreeSpec::new(
933 reqs,
934 server_pubkey,
935 expiry_height,
936 exit_delta,
937 vec![cosign_pubkey, server_cosign_pubkey],
940 )
941 }
942
943 pub fn new(
945 vtxos: impl IntoIterator<Item = VtxoRequest>,
946 cosign_pubkey: PublicKey,
947 expiry_height: BlockHeight,
948 server_pubkey: PublicKey,
949 server_cosign_pubkey: PublicKey,
950 exit_delta: BlockDelta,
951 ) -> SignedTreeBuilder<state::Preparing> {
952 let tree = Self::construct_tree_spec(
953 vtxos,
954 cosign_pubkey,
955 expiry_height,
956 server_pubkey,
957 server_cosign_pubkey,
958 exit_delta,
959 );
960
961 SignedTreeBuilder {
962 expiry_height, server_pubkey, exit_delta, cosign_pubkey,
963 tree: BuilderTree::Spec(tree),
964 user_pub_nonces: Vec::new(),
965 user_sec_nonces: None,
966 _state: PhantomData,
967 }
968 }
969
970 pub fn set_utxo(self, utxo: OutPoint) -> SignedTreeBuilder<state::CanGenerateNonces> {
972 let unsigned_tree = match self.tree {
973 BuilderTree::Spec(s) => s.into_unsigned_tree(utxo),
974 BuilderTree::Unsigned(t) => t, };
976 SignedTreeBuilder {
977 tree: BuilderTree::Unsigned(unsigned_tree),
978
979 expiry_height: self.expiry_height,
980 server_pubkey: self.server_pubkey,
981 exit_delta: self.exit_delta,
982 cosign_pubkey: self.cosign_pubkey,
983 user_pub_nonces: self.user_pub_nonces,
984 user_sec_nonces: self.user_sec_nonces,
985 _state: PhantomData,
986 }
987 }
988 }
989
990 impl SignedTreeBuilder<state::CanGenerateNonces> {
991 pub fn generate_user_nonces(
993 self,
994 cosign_key: &Keypair,
995 ) -> SignedTreeBuilder<state::CanFinish> {
996 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
997
998 let mut cosign_sec_nonces = Vec::with_capacity(unsigned_tree.sighashes.len());
999 let mut cosign_pub_nonces = Vec::with_capacity(unsigned_tree.sighashes.len());
1000 for sh in &unsigned_tree.sighashes {
1001 let pair = musig::nonce_pair_with_msg(&cosign_key, &sh.to_byte_array());
1002 cosign_sec_nonces.push(pair.0);
1003 cosign_pub_nonces.push(pair.1);
1004 }
1005
1006 SignedTreeBuilder {
1007 user_pub_nonces: cosign_pub_nonces,
1008 user_sec_nonces: Some(cosign_sec_nonces),
1009
1010 expiry_height: self.expiry_height,
1011 server_pubkey: self.server_pubkey,
1012 exit_delta: self.exit_delta,
1013 cosign_pubkey: self.cosign_pubkey,
1014 tree: self.tree,
1015 _state: PhantomData,
1016 }
1017 }
1018 }
1019
1020 #[derive(Debug, Clone)]
1022 pub struct SignedTreeCosignResponse {
1023 pub pub_nonces: Vec<musig::PublicNonce>,
1024 pub partial_signatures: Vec<musig::PartialSignature>,
1025 }
1026
1027 impl SignedTreeBuilder<state::ServerCanCosign> {
1028 pub fn new_for_cosign(
1030 vtxos: impl IntoIterator<Item = VtxoRequest>,
1031 cosign_pubkey: PublicKey,
1032 expiry_height: BlockHeight,
1033 server_pubkey: PublicKey,
1034 server_cosign_pubkey: PublicKey,
1035 exit_delta: BlockDelta,
1036 utxo: OutPoint,
1037 user_pub_nonces: Vec<musig::PublicNonce>,
1038 ) -> SignedTreeBuilder<state::ServerCanCosign> {
1039 let unsigned_tree = SignedTreeBuilder::construct_tree_spec(
1040 vtxos,
1041 cosign_pubkey,
1042 expiry_height,
1043 server_pubkey,
1044 server_cosign_pubkey,
1045 exit_delta,
1046 ).into_unsigned_tree(utxo);
1047
1048 SignedTreeBuilder {
1049 expiry_height, server_pubkey, exit_delta, cosign_pubkey, user_pub_nonces,
1050 tree: BuilderTree::Unsigned(unsigned_tree),
1051 user_sec_nonces: None,
1052 _state: PhantomData,
1053 }
1054 }
1055
1056 pub fn server_cosign(&self, server_cosign_key: &Keypair) -> SignedTreeCosignResponse {
1058 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1059
1060 let mut sec_nonces = Vec::with_capacity(unsigned_tree.sighashes.len());
1061 let mut pub_nonces = Vec::with_capacity(unsigned_tree.sighashes.len());
1062 for sh in &unsigned_tree.sighashes {
1063 let pair = musig::nonce_pair_with_msg(&server_cosign_key, &sh.to_byte_array());
1064 sec_nonces.push(pair.0);
1065 pub_nonces.push(pair.1);
1066 }
1067
1068 let agg_nonces = self.user_pub_nonces().iter().zip(&pub_nonces)
1069 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1070 .collect::<Vec<_>>();
1071
1072 let sigs = unsigned_tree.cosign_tree(&agg_nonces, &server_cosign_key, sec_nonces);
1073
1074 SignedTreeCosignResponse {
1075 pub_nonces,
1076 partial_signatures: sigs,
1077 }
1078 }
1079 }
1080
1081 impl SignedTreeBuilder<state::CanFinish> {
1082 pub fn verify_cosign_response(
1084 &self,
1085 server_cosign: &SignedTreeCosignResponse,
1086 ) -> Result<(), CosignSignatureError> {
1087 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1088
1089 let agg_nonces = self.user_pub_nonces().iter()
1090 .zip(&server_cosign.pub_nonces)
1091 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1092 .collect::<Vec<_>>();
1093
1094 unsigned_tree.verify_global_cosign_partial_sigs(
1095 *unsigned_tree.spec.global_cosign_pubkeys.get(1).expect("state invariant"),
1096 &agg_nonces,
1097 &server_cosign.pub_nonces,
1098 &server_cosign.partial_signatures,
1099 )
1100 }
1101
1102 pub fn build_tree(
1103 self,
1104 server_cosign: &SignedTreeCosignResponse,
1105 cosign_key: &Keypair,
1106 ) -> Result<SignedVtxoTreeSpec, IncorrectSigningKeyError> {
1107 if cosign_key.public_key() != self.cosign_pubkey {
1108 return Err(IncorrectSigningKeyError {
1109 required: Some(self.cosign_pubkey),
1110 provided: cosign_key.public_key(),
1111 });
1112 }
1113
1114 let agg_nonces = self.user_pub_nonces().iter().zip(&server_cosign.pub_nonces)
1115 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1116 .collect::<Vec<_>>();
1117
1118 let unsigned_tree = self.tree.into_unsigned_tree().expect("state invariant");
1119 let sec_nonces = self.user_sec_nonces.expect("state invariant");
1120 let partial_sigs = unsigned_tree.cosign_tree(&agg_nonces, cosign_key, sec_nonces);
1121
1122 debug_assert!(unsigned_tree.verify_global_cosign_partial_sigs(
1123 self.cosign_pubkey,
1124 &agg_nonces,
1125 &self.user_pub_nonces,
1126 &partial_sigs,
1127 ).is_ok(), "produced invalid partial signatures");
1128
1129 let sigs = unsigned_tree.combine_partial_signatures(
1130 &agg_nonces,
1131 &HashMap::new(),
1132 &[&server_cosign.partial_signatures, &partial_sigs],
1133 ).expect("should work with correct cosign signatures");
1134
1135 Ok(unsigned_tree.into_signed_tree(sigs))
1136 }
1137 }
1138}
1139
1140const VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1142
1143impl ProtocolEncoding for VtxoTreeSpec {
1144 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1145 w.emit_u8(VTXO_TREE_SPEC_VERSION)?;
1146 w.emit_u32(self.expiry_height)?;
1147 self.server_pubkey.encode(w)?;
1148 w.emit_u16(self.exit_delta)?;
1149 w.emit_compact_size(self.global_cosign_pubkeys.len() as u64)?;
1150 for pk in &self.global_cosign_pubkeys {
1151 pk.encode(w)?;
1152 }
1153
1154 w.emit_compact_size(self.vtxos.len() as u64)?;
1155 for vtxo in &self.vtxos {
1156 vtxo.vtxo.policy.encode(w)?;
1157 w.emit_u64(vtxo.vtxo.amount.to_sat())?;
1158 if let Some(pk) = vtxo.cosign_pubkey {
1159 pk.encode(w)?;
1160 } else {
1161 w.emit_u8(0)?;
1162 }
1163 }
1164 Ok(())
1165 }
1166
1167 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, crate::encode::ProtocolDecodingError> {
1168 let version = r.read_u8()?;
1169
1170 if version == 0x01 {
1172 let expiry_height = r.read_u32()?;
1173 let server_pubkey = PublicKey::decode(r)?;
1174 let exit_delta = r.read_u16()?;
1175 let server_cosign_pk = PublicKey::decode(r)?;
1176
1177 let nb_vtxos = r.read_u32()?;
1178 let mut vtxos = Vec::with_capacity(nb_vtxos as usize);
1179 for _ in 0..nb_vtxos {
1180 let output = VtxoPolicy::decode(r)?;
1181 let amount = Amount::from_sat(r.read_u64()?);
1182 let cosign_pk = PublicKey::decode(r)?;
1183 vtxos.push(SignedVtxoRequest {
1184 vtxo: VtxoRequest { policy: output, amount },
1185 cosign_pubkey: Some(cosign_pk),
1186 });
1187 }
1188
1189 return Ok(VtxoTreeSpec {
1190 vtxos, expiry_height, server_pubkey, exit_delta,
1191 global_cosign_pubkeys: vec![server_cosign_pk],
1192 });
1193 }
1194
1195 if version != VTXO_TREE_SPEC_VERSION {
1196 return Err(ProtocolDecodingError::invalid(format_args!(
1197 "invalid VtxoTreeSpec encoding version byte: {version:#x}",
1198 )));
1199 }
1200
1201 let expiry_height = r.read_u32()?;
1202 let server_pubkey = PublicKey::decode(r)?;
1203 let exit_delta = r.read_u16()?;
1204 let nb_global_signers = r.read_compact_size()?;
1205 let mut global_cosign_pubkeys = Vec::with_capacity(nb_global_signers as usize);
1206 for _ in 0..nb_global_signers {
1207 global_cosign_pubkeys.push(PublicKey::decode(r)?);
1208 }
1209
1210 let nb_vtxos = r.read_compact_size()?;
1211 let mut vtxos = Vec::with_capacity(nb_vtxos as usize);
1212 for _ in 0..nb_vtxos {
1213 let output = VtxoPolicy::decode(r)?;
1214 let amount = Amount::from_sat(r.read_u64()?);
1215 let cosign_pubkey = Option::<PublicKey>::decode(r)?;
1216 vtxos.push(SignedVtxoRequest { vtxo: VtxoRequest { policy: output, amount }, cosign_pubkey });
1217 }
1218
1219 Ok(VtxoTreeSpec { vtxos, expiry_height, server_pubkey, exit_delta, global_cosign_pubkeys })
1220 }
1221}
1222
1223const SIGNED_VTXO_TREE_SPEC_VERSION: u8 = 0x01;
1225
1226impl ProtocolEncoding for SignedVtxoTreeSpec {
1227 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1228 w.emit_u8(SIGNED_VTXO_TREE_SPEC_VERSION)?;
1229 self.spec.encode(w)?;
1230 self.utxo.encode(w)?;
1231 w.emit_u32(self.cosign_sigs.len() as u32)?;
1232 for sig in &self.cosign_sigs {
1233 sig.encode(w)?;
1234 }
1235 Ok(())
1236 }
1237
1238 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, crate::encode::ProtocolDecodingError> {
1239 let version = r.read_u8()?;
1240 if version != SIGNED_VTXO_TREE_SPEC_VERSION {
1241 return Err(ProtocolDecodingError::invalid(format_args!(
1242 "invalid SignedVtxoTreeSpec encoding version byte: {version:#x}",
1243 )));
1244 }
1245 let spec = VtxoTreeSpec::decode(r)?;
1246 let utxo = OutPoint::decode(r)?;
1247 let nb_cosign_sigs = r.read_u32()?;
1248 let mut cosign_sigs = Vec::with_capacity(nb_cosign_sigs as usize);
1249 for _ in 0..nb_cosign_sigs {
1250 cosign_sigs.push(schnorr::Signature::decode(r)?);
1251 }
1252 Ok(SignedVtxoTreeSpec { spec, utxo, cosign_sigs })
1253 }
1254}
1255
1256
1257#[cfg(test)]
1258mod test {
1259 use std::iter;
1260 use std::collections::HashMap;
1261 use std::str::FromStr;
1262
1263 use bitcoin::hashes::{siphash24, sha256, Hash, HashEngine};
1264 use bitcoin::secp256k1::{self, rand, Keypair};
1265 use bitcoin::{absolute, transaction};
1266 use rand::SeedableRng;
1267
1268 use crate::encode;
1269 use crate::encode::test::{encoding_roundtrip, json_roundtrip};
1270 use crate::vtxo::{ValidationResult, VtxoPolicy};
1271 use crate::tree::signed::builder::SignedTreeBuilder;
1272
1273 use super::*;
1274
1275 fn test_tree_amounts(
1276 tree: &UnsignedVtxoTree,
1277 root_value: Amount,
1278 ) {
1279 let map = tree.txs.iter().map(|tx| (tx.compute_txid(), tx)).collect::<HashMap<_, _>>();
1280
1281 for (idx, tx) in tree.txs.iter().take(tree.txs.len() - 1).enumerate() {
1283 println!("tx #{idx}: {}", bitcoin::consensus::encode::serialize_hex(tx));
1284 let input = tx.input.iter().map(|i| {
1285 let prev = i.previous_output;
1286 map.get(&prev.txid).expect(&format!("tx {} not found", prev.txid))
1287 .output[prev.vout as usize].value
1288 }).sum::<Amount>();
1289 let output = tx.output_value();
1290 assert!(input >= output);
1291 assert_eq!(input, output);
1292 }
1293
1294 let root = tree.txs.last().unwrap();
1296 assert_eq!(root_value, root.output_value());
1297 }
1298
1299 #[test]
1300 fn vtxo_tree() {
1301 let secp = secp256k1::Secp256k1::new();
1302 let mut rand = rand::rngs::StdRng::seed_from_u64(42);
1303 let random_sig = {
1304 let key = Keypair::new(&secp, &mut rand);
1305 let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1306 let msg = secp256k1::Message::from_digest(sha.to_byte_array());
1307 secp.sign_schnorr(&msg, &key)
1308 };
1309
1310 let server_key = Keypair::new(&secp, &mut rand);
1311 let server_cosign_key = Keypair::new(&secp, &mut rand);
1312
1313 struct Req {
1314 key: Keypair,
1315 cosign_key: Keypair,
1316 amount: Amount,
1317 }
1318 impl Req {
1319 fn to_vtxo(&self) -> SignedVtxoRequest {
1320 SignedVtxoRequest {
1321 vtxo: VtxoRequest {
1322 amount: self.amount,
1323 policy: VtxoPolicy::new_pubkey(self.key.public_key()),
1324 },
1325 cosign_pubkey: Some(self.cosign_key.public_key()),
1326 }
1327 }
1328 }
1329
1330 let nb_leaves = 27;
1331 let reqs = iter::repeat_with(|| Req {
1332 key: Keypair::new(&secp, &mut rand),
1333 cosign_key: Keypair::new(&secp, &mut rand),
1334 amount: Amount::from_sat(100_000),
1335 }).take(nb_leaves).collect::<Vec<_>>();
1336 let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
1337
1338 let spec = VtxoTreeSpec::new(
1339 reqs.iter().map(|r| r.to_vtxo()).collect(),
1340 server_key.public_key(),
1341 101_000,
1342 2016,
1343 vec![server_cosign_key.public_key()],
1344 );
1345 assert_eq!(spec.nb_leaves(), nb_leaves);
1346 assert_eq!(spec.total_required_value().to_sat(), 2700000);
1347 let nb_nodes = spec.nb_nodes();
1348
1349 encoding_roundtrip(&spec);
1350
1351 let unsigned = spec.into_unsigned_tree(point);
1352
1353 test_tree_amounts(&unsigned, unsigned.spec.total_required_value());
1354
1355 let sighashes_hash = {
1356 let mut eng = siphash24::Hash::engine();
1357 unsigned.sighashes.iter().for_each(|h| eng.input(&h[..]));
1358 siphash24::Hash::from_engine(eng)
1359 };
1360 assert_eq!(sighashes_hash.to_string(), "44c13179cd19569f");
1361
1362 let signed = unsigned.into_signed_tree(vec![random_sig; nb_nodes]);
1363
1364 encoding_roundtrip(&signed);
1365
1366 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1367 struct JsonSignedVtxoTreeSpec {
1368 #[serde(with = "encode::serde")]
1369 pub spec: SignedVtxoTreeSpec,
1370 }
1371
1372 json_roundtrip(&JsonSignedVtxoTreeSpec { spec: signed.clone() });
1373
1374 for l in 0..nb_leaves {
1375 let exit = signed.exit_branch(l).unwrap();
1376
1377 let mut iter = exit.iter().enumerate().peekable();
1379 while let Some((i, cur)) = iter.next() {
1380 if let Some((_, next)) = iter.peek() {
1381 assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
1382 }
1383 }
1384 }
1385
1386 let cached = signed.into_cached_tree();
1387 for vtxo in cached.all_vtxos() {
1388 encoding_roundtrip(&vtxo);
1389 }
1390 }
1391
1392 #[test]
1393 fn test_tree_builder() {
1394 let expiry = 100_000;
1395 let exit_delta = 24;
1396
1397 let vtxo_pubkey = "035e160cd261ac8ffcd2866a5aab2116bc90fbefdb1d739531e121eee612583802".parse().unwrap();
1398 let policy = VtxoPolicy::new_pubkey(vtxo_pubkey);
1399 let vtxos = (1..50).map(|i| VtxoRequest {
1400 amount: Amount::from_sat(1000 * i),
1401 policy: policy.clone(),
1402 }).collect::<Vec<_>>();
1403
1404 let user_cosign_key = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1405 let user_cosign_pubkey = user_cosign_key.public_key();
1406 println!("cosign_pubkey: {}", user_cosign_pubkey);
1407
1408 let server_key = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1409 let server_pubkey = server_key.public_key();
1410 println!("server_pubkey: {}", server_pubkey);
1411
1412 let server_cosign_key = Keypair::from_str("52a506fbae3b725749d2486afd4761841ec685b841c2967e30f24182c4b02eed").unwrap();
1413 let server_cosign_pubkey = server_cosign_key.public_key();
1414 println!("server_cosign_pubkey: {}", server_cosign_pubkey);
1415
1416 let builder = SignedTreeBuilder::new(
1417 vtxos.iter().cloned(), user_cosign_pubkey, expiry, server_pubkey, server_cosign_pubkey,
1418 exit_delta,
1419 );
1420 assert_eq!(builder.total_required_value(), Amount::from_sat(1_225_000));
1421 assert_eq!(builder.funding_script_pubkey().to_hex_string(), "5120ca542aaf6c76c4b4c7822d73d91551ef42482098f3675d915d61782448b2ac5b");
1422
1423 let funding_tx = Transaction {
1424 version: transaction::Version::TWO,
1425 lock_time: absolute::LockTime::ZERO,
1426 input: vec![],
1427 output: vec![builder.funding_txout()],
1428 };
1429 let utxo = OutPoint::new(funding_tx.compute_txid(), 0);
1430 assert_eq!(utxo.to_string(), "49b930a56b7eb510813600b54d01e6017cbb41895e137892c0b41e6399779ef7:0");
1431 let builder = builder.set_utxo(utxo).generate_user_nonces(&user_cosign_key);
1432 let user_pub_nonces = builder.user_pub_nonces().to_vec();
1433
1434 let cosign = {
1435 let builder = SignedTreeBuilder::new_for_cosign(
1436 vtxos.iter().cloned(), user_cosign_pubkey, expiry, server_pubkey,
1437 server_cosign_pubkey, exit_delta, utxo, user_pub_nonces,
1438 );
1439 builder.server_cosign(&server_cosign_key)
1440 };
1441
1442 builder.verify_cosign_response(&cosign).unwrap();
1443 let tree = builder.build_tree(&cosign, &user_cosign_key).unwrap().into_cached_tree();
1444
1445 for vtxo in tree.all_vtxos() {
1447 assert_eq!(vtxo.validate(&funding_tx).unwrap(), ValidationResult::Cosigned);
1448 }
1449 }
1450}