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