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, 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: u16,
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: u16,
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::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: u16,
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: u16,
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: u16,
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 (cosign_sec_nonces, cosign_pub_nonces) = unsigned_tree.sighashes.iter().map(|sh| {
975 musig::nonce_pair_with_msg(cosign_key, &sh.to_byte_array())
976 }).collect::<(Vec<_>, Vec<_>)>();
977
978 SignedTreeBuilder {
979 user_pub_nonces: cosign_pub_nonces,
980 user_sec_nonces: Some(cosign_sec_nonces),
981
982 expiry_height: self.expiry_height,
983 server_pubkey: self.server_pubkey,
984 exit_delta: self.exit_delta,
985 cosign_pubkey: self.cosign_pubkey,
986 tree: self.tree,
987 _state: PhantomData,
988 }
989 }
990 }
991
992 #[derive(Debug, Clone)]
994 pub struct SignedTreeCosignResponse {
995 pub pub_nonces: Vec<musig::PublicNonce>,
996 pub partial_signatures: Vec<musig::PartialSignature>,
997 }
998
999 impl SignedTreeBuilder<state::ServerCanCosign> {
1000 pub fn new_for_cosign(
1002 vtxos: impl IntoIterator<Item = VtxoRequest>,
1003 cosign_pubkey: PublicKey,
1004 expiry_height: BlockHeight,
1005 server_pubkey: PublicKey,
1006 server_cosign_pubkey: PublicKey,
1007 exit_delta: u16,
1008 utxo: OutPoint,
1009 user_pub_nonces: Vec<musig::PublicNonce>,
1010 ) -> SignedTreeBuilder<state::ServerCanCosign> {
1011 let unsigned_tree = SignedTreeBuilder::construct_tree_spec(
1012 vtxos,
1013 cosign_pubkey,
1014 expiry_height,
1015 server_pubkey,
1016 server_cosign_pubkey,
1017 exit_delta,
1018 ).into_unsigned_tree(utxo);
1019
1020 SignedTreeBuilder {
1021 expiry_height, server_pubkey, exit_delta, cosign_pubkey, user_pub_nonces,
1022 tree: BuilderTree::Unsigned(unsigned_tree),
1023 user_sec_nonces: None,
1024 _state: PhantomData,
1025 }
1026 }
1027
1028 pub fn server_cosign(&self, server_cosign_key: &Keypair) -> SignedTreeCosignResponse {
1030 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1031
1032 let (sec_nonces, pub_nonces) = unsigned_tree.sighashes.iter().map(|sh| {
1033 musig::nonce_pair_with_msg(&server_cosign_key, &sh.to_byte_array())
1034 }).collect::<(Vec<_>, Vec<_>)>();
1035
1036 let agg_nonces = self.user_pub_nonces().iter().zip(&pub_nonces)
1037 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1038 .collect::<Vec<_>>();
1039
1040 let sigs = unsigned_tree.cosign_tree(&agg_nonces, &server_cosign_key, sec_nonces);
1041
1042 SignedTreeCosignResponse {
1043 pub_nonces,
1044 partial_signatures: sigs,
1045 }
1046 }
1047 }
1048
1049 impl SignedTreeBuilder<state::CanFinish> {
1050 pub fn verify_cosign_response(
1052 &self,
1053 server_cosign: &SignedTreeCosignResponse,
1054 ) -> Result<(), CosignSignatureError> {
1055 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1056
1057 let agg_nonces = self.user_pub_nonces().iter()
1058 .zip(&server_cosign.pub_nonces)
1059 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1060 .collect::<Vec<_>>();
1061
1062 unsigned_tree.verify_global_cosign_partial_sigs(
1063 *unsigned_tree.spec.global_cosign_pubkeys.get(1).expect("state invariant"),
1064 &agg_nonces,
1065 &server_cosign.pub_nonces,
1066 &server_cosign.partial_signatures,
1067 )
1068 }
1069
1070 pub fn build_tree(
1071 self,
1072 server_cosign: &SignedTreeCosignResponse,
1073 cosign_key: &Keypair,
1074 ) -> Result<SignedVtxoTreeSpec, IncorrectSigningKeyError> {
1075 if cosign_key.public_key() != self.cosign_pubkey {
1076 return Err(IncorrectSigningKeyError {
1077 required: Some(self.cosign_pubkey),
1078 provided: cosign_key.public_key(),
1079 });
1080 }
1081
1082 let agg_nonces = self.user_pub_nonces().iter().zip(&server_cosign.pub_nonces)
1083 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1084 .collect::<Vec<_>>();
1085
1086 let unsigned_tree = self.tree.into_unsigned_tree().expect("state invariant");
1087 let sec_nonces = self.user_sec_nonces.expect("state invariant");
1088 let partial_sigs = unsigned_tree.cosign_tree(&agg_nonces, cosign_key, sec_nonces);
1089
1090 debug_assert!(unsigned_tree.verify_global_cosign_partial_sigs(
1091 self.cosign_pubkey,
1092 &agg_nonces,
1093 &self.user_pub_nonces,
1094 &partial_sigs,
1095 ).is_ok(), "produced invalid partial signatures");
1096
1097 let sigs = unsigned_tree.combine_partial_signatures(
1098 &agg_nonces,
1099 &HashMap::new(),
1100 &[&server_cosign.partial_signatures, &partial_sigs],
1101 ).expect("should work with correct cosign signatures");
1102
1103 Ok(unsigned_tree.into_signed_tree(sigs))
1104 }
1105 }
1106}
1107
1108const VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1110
1111impl ProtocolEncoding for VtxoTreeSpec {
1112 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1113 w.emit_u8(VTXO_TREE_SPEC_VERSION)?;
1114 w.emit_u32(self.expiry_height)?;
1115 self.server_pubkey.encode(w)?;
1116 w.emit_u16(self.exit_delta)?;
1117 w.emit_compact_size(self.global_cosign_pubkeys.len() as u64)?;
1118 for pk in &self.global_cosign_pubkeys {
1119 pk.encode(w)?;
1120 }
1121
1122 w.emit_compact_size(self.vtxos.len() as u64)?;
1123 for vtxo in &self.vtxos {
1124 vtxo.vtxo.policy.encode(w)?;
1125 w.emit_u64(vtxo.vtxo.amount.to_sat())?;
1126 if let Some(pk) = vtxo.cosign_pubkey {
1127 pk.encode(w)?;
1128 } else {
1129 w.emit_u8(0)?;
1130 }
1131 }
1132 Ok(())
1133 }
1134
1135 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, crate::encode::ProtocolDecodingError> {
1136 let version = r.read_u8()?;
1137
1138 if version == 0x01 {
1140 let expiry_height = r.read_u32()?;
1141 let server_pubkey = PublicKey::decode(r)?;
1142 let exit_delta = r.read_u16()?;
1143 let server_cosign_pk = PublicKey::decode(r)?;
1144
1145 let nb_vtxos = r.read_u32()?;
1146 let mut vtxos = Vec::with_capacity(nb_vtxos as usize);
1147 for _ in 0..nb_vtxos {
1148 let output = VtxoPolicy::decode(r)?;
1149 let amount = Amount::from_sat(r.read_u64()?);
1150 let cosign_pk = PublicKey::decode(r)?;
1151 vtxos.push(SignedVtxoRequest {
1152 vtxo: VtxoRequest { policy: output, amount },
1153 cosign_pubkey: Some(cosign_pk),
1154 });
1155 }
1156
1157 return Ok(VtxoTreeSpec {
1158 vtxos, expiry_height, server_pubkey, exit_delta,
1159 global_cosign_pubkeys: vec![server_cosign_pk],
1160 });
1161 }
1162
1163 if version != VTXO_TREE_SPEC_VERSION {
1164 return Err(ProtocolDecodingError::invalid(format_args!(
1165 "invalid VtxoTreeSpec encoding version byte: {version:#x}",
1166 )));
1167 }
1168
1169 let expiry_height = r.read_u32()?;
1170 let server_pubkey = PublicKey::decode(r)?;
1171 let exit_delta = r.read_u16()?;
1172 let nb_global_signers = r.read_compact_size()?;
1173 let mut global_cosign_pubkeys = Vec::with_capacity(nb_global_signers as usize);
1174 for _ in 0..nb_global_signers {
1175 global_cosign_pubkeys.push(PublicKey::decode(r)?);
1176 }
1177
1178 let nb_vtxos = r.read_compact_size()?;
1179 let mut vtxos = Vec::with_capacity(nb_vtxos as usize);
1180 for _ in 0..nb_vtxos {
1181 let output = VtxoPolicy::decode(r)?;
1182 let amount = Amount::from_sat(r.read_u64()?);
1183 let cosign_pubkey = Option::<PublicKey>::decode(r)?;
1184 vtxos.push(SignedVtxoRequest { vtxo: VtxoRequest { policy: output, amount }, cosign_pubkey });
1185 }
1186
1187 Ok(VtxoTreeSpec { vtxos, expiry_height, server_pubkey, exit_delta, global_cosign_pubkeys })
1188 }
1189}
1190
1191const SIGNED_VTXO_TREE_SPEC_VERSION: u8 = 0x01;
1193
1194impl ProtocolEncoding for SignedVtxoTreeSpec {
1195 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1196 w.emit_u8(SIGNED_VTXO_TREE_SPEC_VERSION)?;
1197 self.spec.encode(w)?;
1198 self.utxo.encode(w)?;
1199 w.emit_u32(self.cosign_sigs.len() as u32)?;
1200 for sig in &self.cosign_sigs {
1201 sig.encode(w)?;
1202 }
1203 Ok(())
1204 }
1205
1206 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, crate::encode::ProtocolDecodingError> {
1207 let version = r.read_u8()?;
1208 if version != SIGNED_VTXO_TREE_SPEC_VERSION {
1209 return Err(ProtocolDecodingError::invalid(format_args!(
1210 "invalid SignedVtxoTreeSpec encoding version byte: {version:#x}",
1211 )));
1212 }
1213 let spec = VtxoTreeSpec::decode(r)?;
1214 let utxo = OutPoint::decode(r)?;
1215 let nb_cosign_sigs = r.read_u32()?;
1216 let mut cosign_sigs = Vec::with_capacity(nb_cosign_sigs as usize);
1217 for _ in 0..nb_cosign_sigs {
1218 cosign_sigs.push(schnorr::Signature::decode(r)?);
1219 }
1220 Ok(SignedVtxoTreeSpec { spec, utxo, cosign_sigs })
1221 }
1222}
1223
1224
1225#[cfg(test)]
1226mod test {
1227 use std::iter;
1228 use std::collections::HashMap;
1229 use std::str::FromStr;
1230
1231 use bitcoin::hashes::{siphash24, sha256, Hash, HashEngine};
1232 use bitcoin::secp256k1::{self, rand, Keypair};
1233 use bitcoin::{absolute, transaction};
1234 use rand::SeedableRng;
1235
1236 use crate::encode;
1237 use crate::encode::test::{encoding_roundtrip, json_roundtrip};
1238 use crate::vtxo::{Validation, VtxoPolicy};
1239 use crate::tree::signed::builder::SignedTreeBuilder;
1240
1241 use super::*;
1242
1243 fn test_tree_amounts(
1244 tree: &UnsignedVtxoTree,
1245 root_value: Amount,
1246 ) {
1247 let map = tree.txs.iter().map(|tx| (tx.compute_txid(), tx)).collect::<HashMap<_, _>>();
1248
1249 for (idx, tx) in tree.txs.iter().take(tree.txs.len() - 1).enumerate() {
1251 println!("tx #{idx}: {}", bitcoin::consensus::encode::serialize_hex(tx));
1252 let input = tx.input.iter().map(|i| {
1253 let prev = i.previous_output;
1254 map.get(&prev.txid).expect(&format!("tx {} not found", prev.txid))
1255 .output[prev.vout as usize].value
1256 }).sum::<Amount>();
1257 let output = tx.output_value();
1258 assert!(input >= output);
1259 assert_eq!(input, output);
1260 }
1261
1262 let root = tree.txs.last().unwrap();
1264 assert_eq!(root_value, root.output_value());
1265 }
1266
1267 #[test]
1268 fn vtxo_tree() {
1269 let secp = secp256k1::Secp256k1::new();
1270 let mut rand = rand::rngs::StdRng::seed_from_u64(42);
1271 let random_sig = {
1272 let key = Keypair::new(&secp, &mut rand);
1273 let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1274 let msg = secp256k1::Message::from_digest(sha.to_byte_array());
1275 secp.sign_schnorr(&msg, &key)
1276 };
1277
1278 let server_key = Keypair::new(&secp, &mut rand);
1279 let server_cosign_key = Keypair::new(&secp, &mut rand);
1280
1281 struct Req {
1282 key: Keypair,
1283 cosign_key: Keypair,
1284 amount: Amount,
1285 }
1286 impl Req {
1287 fn to_vtxo(&self) -> SignedVtxoRequest {
1288 SignedVtxoRequest {
1289 vtxo: VtxoRequest {
1290 amount: self.amount,
1291 policy: VtxoPolicy::new_pubkey(self.key.public_key()),
1292 },
1293 cosign_pubkey: Some(self.cosign_key.public_key()),
1294 }
1295 }
1296 }
1297
1298 let nb_leaves = 27;
1299 let reqs = iter::repeat_with(|| Req {
1300 key: Keypair::new(&secp, &mut rand),
1301 cosign_key: Keypair::new(&secp, &mut rand),
1302 amount: Amount::from_sat(100_000),
1303 }).take(nb_leaves).collect::<Vec<_>>();
1304 let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
1305
1306 let spec = VtxoTreeSpec::new(
1307 reqs.iter().map(|r| r.to_vtxo()).collect(),
1308 server_key.public_key(),
1309 101_000,
1310 2016,
1311 vec![server_cosign_key.public_key()],
1312 );
1313 assert_eq!(spec.nb_leaves(), nb_leaves);
1314 assert_eq!(spec.total_required_value().to_sat(), 2700000);
1315 let nb_nodes = spec.nb_nodes();
1316
1317 encoding_roundtrip(&spec);
1318
1319 let unsigned = spec.into_unsigned_tree(point);
1320
1321 test_tree_amounts(&unsigned, unsigned.spec.total_required_value());
1322
1323 let sighashes_hash = {
1324 let mut eng = siphash24::Hash::engine();
1325 unsigned.sighashes.iter().for_each(|h| eng.input(&h[..]));
1326 siphash24::Hash::from_engine(eng)
1327 };
1328 assert_eq!(sighashes_hash.to_string(), "44c13179cd19569f");
1329
1330 let signed = unsigned.into_signed_tree(vec![random_sig; nb_nodes]);
1331
1332 encoding_roundtrip(&signed);
1333
1334 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1335 struct JsonSignedVtxoTreeSpec {
1336 #[serde(with = "encode::serde")]
1337 pub spec: SignedVtxoTreeSpec,
1338 }
1339
1340 json_roundtrip(&JsonSignedVtxoTreeSpec { spec: signed.clone() });
1341
1342 for l in 0..nb_leaves {
1343 let exit = signed.exit_branch(l).unwrap();
1344
1345 let mut iter = exit.iter().enumerate().peekable();
1347 while let Some((i, cur)) = iter.next() {
1348 if let Some((_, next)) = iter.peek() {
1349 assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
1350 }
1351 }
1352 }
1353
1354 let cached = signed.into_cached_tree();
1355 for vtxo in cached.all_vtxos() {
1356 encoding_roundtrip(&vtxo);
1357 }
1358 }
1359
1360 #[test]
1361 fn test_tree_builder() {
1362 let expiry = 100_000;
1363 let exit_delta = 24;
1364
1365 let vtxo_pubkey = "035e160cd261ac8ffcd2866a5aab2116bc90fbefdb1d739531e121eee612583802".parse().unwrap();
1366 let policy = VtxoPolicy::new_pubkey(vtxo_pubkey);
1367 let vtxos = (1..50).map(|i| VtxoRequest {
1368 amount: Amount::from_sat(1000 * i),
1369 policy: policy.clone(),
1370 }).collect::<Vec<_>>();
1371
1372 let user_cosign_key = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1373 let user_cosign_pubkey = user_cosign_key.public_key();
1374 println!("cosign_pubkey: {}", user_cosign_pubkey);
1375
1376 let server_key = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1377 let server_pubkey = server_key.public_key();
1378 println!("server_pubkey: {}", server_pubkey);
1379
1380 let server_cosign_key = Keypair::from_str("52a506fbae3b725749d2486afd4761841ec685b841c2967e30f24182c4b02eed").unwrap();
1381 let server_cosign_pubkey = server_cosign_key.public_key();
1382 println!("server_cosign_pubkey: {}", server_cosign_pubkey);
1383
1384 let builder = SignedTreeBuilder::new(
1385 vtxos.iter().cloned(), user_cosign_pubkey, expiry, server_pubkey, server_cosign_pubkey,
1386 exit_delta,
1387 );
1388 assert_eq!(builder.total_required_value(), Amount::from_sat(1_225_000));
1389 assert_eq!(builder.funding_script_pubkey().to_hex_string(), "5120ca542aaf6c76c4b4c7822d73d91551ef42482098f3675d915d61782448b2ac5b");
1390
1391 let funding_tx = Transaction {
1392 version: transaction::Version::TWO,
1393 lock_time: absolute::LockTime::ZERO,
1394 input: vec![],
1395 output: vec![builder.funding_txout()],
1396 };
1397 let utxo = OutPoint::new(funding_tx.compute_txid(), 0);
1398 assert_eq!(utxo.to_string(), "49b930a56b7eb510813600b54d01e6017cbb41895e137892c0b41e6399779ef7:0");
1399 let builder = builder.set_utxo(utxo).generate_user_nonces(&user_cosign_key);
1400 let user_pub_nonces = builder.user_pub_nonces().to_vec();
1401
1402 let cosign = {
1403 let builder = SignedTreeBuilder::new_for_cosign(
1404 vtxos.iter().cloned(), user_cosign_pubkey, expiry, server_pubkey,
1405 server_cosign_pubkey, exit_delta, utxo, user_pub_nonces,
1406 );
1407 builder.server_cosign(&server_cosign_key)
1408 };
1409
1410 builder.verify_cosign_response(&cosign).unwrap();
1411 let tree = builder.build_tree(&cosign, &user_cosign_key).unwrap().into_cached_tree();
1412
1413 for vtxo in tree.all_vtxos() {
1415 match vtxo.validate(&funding_tx).unwrap() {
1416 Validation::Trusted { cosign_pubkey } => {
1417 assert_eq!(cosign_pubkey, user_cosign_pubkey);
1418 },
1419 Validation::Arkoor => panic!("should be trusted"),
1420 }
1421 }
1422 }
1423}