1
2
3use std::{cmp, fmt, io, iter};
4use std::collections::{HashMap, VecDeque};
5
6use bitcoin::hashes::{sha256, Hash};
7use bitcoin::{
8 taproot, Amount, OutPoint, ScriptBuf, Sequence, TapLeafHash, Transaction, TxIn, TxOut, 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, Vtxo, VtxoId, VtxoPolicy, VtxoRequest, SECP};
18use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
19use crate::tree::{self, Tree};
20use crate::vtxo::{self, GenesisItem, GenesisTransition, MaybePreimage};
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 {
42 scripts::hash_and_sign(unlock_hash, pubkey)
43}
44
45pub fn leaf_cosign_taproot(
52 user_pubkey: PublicKey,
53 server_pubkey: PublicKey,
54 expiry_height: BlockHeight,
55 unlock_hash: UnlockHash,
56) -> taproot::TaprootSpendInfo {
57 let agg_pk = musig::combine_keys([user_pubkey, server_pubkey]);
58 taproot::TaprootBuilder::new()
59 .add_leaf(1, expiry_clause(server_pubkey, expiry_height)).unwrap()
60 .add_leaf(1, unlock_clause(agg_pk, unlock_hash)).unwrap()
61 .finalize(&SECP, agg_pk).unwrap()
62}
63
64pub fn cosign_taproot(
66 agg_pk: XOnlyPublicKey,
67 server_pubkey: PublicKey,
68 expiry_height: BlockHeight,
69) -> taproot::TaprootSpendInfo {
70 taproot::TaprootBuilder::new()
71 .add_leaf(0, expiry_clause(server_pubkey, expiry_height)).unwrap()
72 .finalize(&SECP, agg_pk).unwrap()
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
76pub struct VtxoLeafSpec {
77 pub vtxo: VtxoRequest,
79
80 pub cosign_pubkey: Option<PublicKey>,
87
88 pub unlock_hash: UnlockHash,
90}
91
92impl ProtocolEncoding for VtxoLeafSpec {
93 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
94 self.vtxo.policy.encode(w)?;
95 w.emit_u64(self.vtxo.amount.to_sat())?;
96 self.cosign_pubkey.encode(w)?;
97 self.unlock_hash.encode(w)?;
98 Ok(())
99 }
100
101 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
102 Ok(VtxoLeafSpec {
103 vtxo: VtxoRequest {
104 policy: VtxoPolicy::decode(r)?,
105 amount: Amount::from_sat(r.read_u64()?),
106 },
107 cosign_pubkey: Option::<PublicKey>::decode(r)?,
108 unlock_hash: sha256::Hash::decode(r)?,
109 })
110 }
111}
112
113#[derive(Debug, Clone, Eq, PartialEq)]
115pub struct VtxoTreeSpec {
116 pub vtxos: Vec<VtxoLeafSpec>,
117 pub expiry_height: BlockHeight,
118 pub server_pubkey: PublicKey,
119 pub exit_delta: BlockDelta,
120 pub global_cosign_pubkeys: Vec<PublicKey>,
121}
122
123#[derive(Clone, Copy)]
124enum ChildSpec<'a> {
125 Leaf {
126 spec: &'a VtxoLeafSpec,
127 },
128 Internal {
129 output_value: Amount,
130 agg_pk: XOnlyPublicKey,
131 },
132}
133
134impl VtxoTreeSpec {
135 pub fn new(
136 vtxos: Vec<VtxoLeafSpec>,
137 server_pubkey: PublicKey,
138 expiry_height: BlockHeight,
139 exit_delta: BlockDelta,
140 global_cosign_pubkeys: Vec<PublicKey>,
141 ) -> VtxoTreeSpec {
142 assert_ne!(vtxos.len(), 0);
143 VtxoTreeSpec { vtxos, server_pubkey, expiry_height, exit_delta, global_cosign_pubkeys }
144 }
145
146 pub fn nb_leaves(&self) -> usize {
147 self.vtxos.len()
148 }
149
150 pub fn nb_nodes(&self) -> usize {
151 Tree::nb_nodes_for_leaves(self.nb_leaves())
152 }
153
154 pub fn nb_internal_nodes(&self) -> usize {
155 Tree::nb_nodes_for_leaves(self.nb_leaves()).checked_sub(self.nb_leaves())
156 .expect("tree can't have less nodes than leaves")
157 }
158
159 pub fn iter_vtxos(&self) -> impl Iterator<Item = &VtxoLeafSpec> {
160 self.vtxos.iter()
161 }
162
163 pub fn leaf_idx_of(&self, leaf_spec: &VtxoLeafSpec) -> Option<usize> {
165 self.vtxos.iter().position(|e| e == leaf_spec)
166 }
167
168 pub fn leaf_idx_of_req(&self, vtxo_request: &VtxoRequest) -> Option<usize> {
173 self.vtxos.iter().position(|e| e.vtxo == *vtxo_request)
174 }
175
176 pub fn total_required_value(&self) -> Amount {
181 self.vtxos.iter().map(|d| d.vtxo.amount).sum::<Amount>()
182 }
183
184 pub fn leaf_taproot(
186 &self,
187 user_pubkey: PublicKey,
188 unlock_hash: UnlockHash,
189 ) -> taproot::TaprootSpendInfo {
190 leaf_cosign_taproot(user_pubkey, self.server_pubkey, self.expiry_height, unlock_hash)
191 }
192
193 pub fn internal_taproot(&self, agg_pk: XOnlyPublicKey) -> taproot::TaprootSpendInfo {
195 cosign_taproot(agg_pk, self.server_pubkey, self.expiry_height)
196 }
197
198 pub fn funding_tx_cosign_pubkey(&self) -> XOnlyPublicKey {
202 let keys = self.vtxos.iter()
203 .filter_map(|v| v.cosign_pubkey)
204 .chain(self.global_cosign_pubkeys.iter().copied());
205 musig::combine_keys(keys)
206 }
207
208 pub fn funding_tx_script_pubkey(&self) -> ScriptBuf {
212 let agg_pk = self.funding_tx_cosign_pubkey();
213 self.internal_taproot(agg_pk).script_pubkey()
214 }
215
216 pub fn funding_tx_txout(&self) -> TxOut {
220 TxOut {
221 script_pubkey: self.funding_tx_script_pubkey(),
222 value: self.total_required_value(),
223 }
224 }
225
226 fn node_tx<'a>(
231 &self,
232 children: impl Iterator<Item = ChildSpec<'a>>,
233 ) -> Transaction {
234 Transaction {
235 version: bitcoin::transaction::Version(3),
236 lock_time: bitcoin::absolute::LockTime::ZERO,
237 input: vec![TxIn {
238 previous_output: OutPoint::null(), sequence: Sequence::ZERO,
240 script_sig: ScriptBuf::new(),
241 witness: Witness::new(),
242 }],
243 output: children.map(|child| match child {
244 ChildSpec::Leaf { spec } => {
245 let taproot = self.leaf_taproot(
246 spec.vtxo.policy.user_pubkey(),
247 spec.unlock_hash,
248 );
249 TxOut {
250 script_pubkey: taproot.script_pubkey(),
251 value: spec.vtxo.amount,
252 }
253 },
254 ChildSpec::Internal { output_value, agg_pk } => {
255 let taproot = self.internal_taproot(agg_pk);
256 TxOut {
257 script_pubkey: taproot.script_pubkey(),
258 value: output_value,
259 }
260 },
261 }).chain(Some(fee::fee_anchor())).collect(),
262 }
263 }
264
265 fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
266 let txout = TxOut {
267 value: vtxo.amount,
268 script_pubkey: vtxo.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height),
269 };
270
271 vtxo::create_exit_tx(OutPoint::null(), txout, None)
272 }
273
274 pub fn cosign_agg_pks(&self)
278 -> impl Iterator<Item = XOnlyPublicKey> + iter::DoubleEndedIterator + iter::ExactSizeIterator + '_
279 {
280 Tree::new(self.nb_leaves()).into_iter().map(|node| {
281 if node.is_leaf() {
282 musig::combine_keys([
283 self.vtxos[node.idx()].vtxo.policy.user_pubkey(),
284 self.server_pubkey,
285 ])
286 } else {
287 musig::combine_keys(
288 node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey)
289 .chain(self.global_cosign_pubkeys.iter().copied())
290 )
291 }
292 })
293 }
294
295 pub fn unsigned_transactions(&self, utxo: OutPoint) -> Vec<Transaction> {
297 let tree = Tree::new(self.nb_leaves());
298
299 let cosign_agg_pks = self.cosign_agg_pks().collect::<Vec<_>>();
300
301 let mut txs = Vec::<Transaction>::with_capacity(tree.nb_nodes());
302 for node in tree.iter() {
303 let tx = if node.is_leaf() {
304 self.leaf_tx(&self.vtxos[node.idx()].vtxo).clone()
305 } else {
306 let mut buf = [None; tree::RADIX];
307 for (idx, child) in node.children().enumerate() {
308 let child = if let Some(spec) = self.vtxos.get(child) {
309 ChildSpec::Leaf { spec }
310 } else {
311 ChildSpec::Internal {
312 output_value: txs[child].output_value(),
313 agg_pk: cosign_agg_pks[child],
314 }
315 };
316 buf[idx] = Some(child);
317 }
318 self.node_tx(buf.iter().filter_map(|x| *x))
319 };
320 txs.push(tx.clone());
321 };
322
323 txs.last_mut().unwrap().input[0].previous_output = utxo;
325 for node in tree.iter().rev() {
326 let txid = txs[node.idx()].compute_txid();
327 for (i, child) in node.children().enumerate() {
328 let point = OutPoint::new(txid, i as u32);
329 txs[child].input[0].previous_output = point;
330 }
331 }
332
333 txs
334 }
335
336 pub fn final_transactions(
340 &self,
341 utxo: OutPoint,
342 internal_signatures: &[schnorr::Signature],
343 ) -> Vec<Transaction> {
344 let mut txs = self.unsigned_transactions(utxo);
345 for (tx, sig) in txs.iter_mut().skip(self.nb_leaves()).zip(internal_signatures) {
346 tx.input[0].witness.push(&sig[..]);
347 }
348 txs
349 }
350
351 pub fn calculate_cosign_agg_nonces(
355 &self,
356 leaf_cosign_nonces: &HashMap<PublicKey, Vec<PublicNonce>>,
357 global_signer_cosign_nonces: &[impl AsRef<[PublicNonce]>],
358 ) -> Result<Vec<AggregatedNonce>, String> {
359 if global_signer_cosign_nonces.len() != self.global_cosign_pubkeys.len() {
360 return Err("missing global signer nonces".into());
361 }
362
363 Tree::new(self.nb_leaves()).iter_internal().enumerate().map(|(idx, node)| {
364 let mut nonces = Vec::new();
365 for pk in node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey) {
366 nonces.push(leaf_cosign_nonces.get(&pk)
367 .ok_or_else(|| format!("missing nonces for leaf pk {}", pk))?
368 .get(node.internal_level())
371 .ok_or_else(|| format!("not enough nonces for leaf_pk {}", pk))?
372 );
373 }
374 for glob in global_signer_cosign_nonces {
375 nonces.push(glob.as_ref().get(idx).ok_or("not enough global cosign nonces")?);
376 }
377 Ok(musig::nonce_agg(&nonces))
378 }).collect()
379 }
380
381 pub fn into_unsigned_tree(
386 self,
387 utxo: OutPoint,
388 ) -> UnsignedVtxoTree {
389 UnsignedVtxoTree::new(self, utxo)
390 }
391}
392
393#[derive(Debug, Clone)]
397pub struct UnsignedVtxoTree {
398 pub spec: VtxoTreeSpec,
399 pub utxo: OutPoint,
400
401 pub cosign_agg_pks: Vec<XOnlyPublicKey>,
405 pub txs: Vec<Transaction>,
407 pub internal_sighashes: Vec<TapSighash>,
410
411 tree: Tree,
412}
413
414impl UnsignedVtxoTree {
415 pub fn new(
416 spec: VtxoTreeSpec,
417 utxo: OutPoint,
418 ) -> UnsignedVtxoTree {
419 let tree = Tree::new(spec.nb_leaves());
420
421 let cosign_agg_pks = spec.cosign_agg_pks().collect::<Vec<_>>();
422 let txs = spec.unsigned_transactions(utxo);
423
424 let root_txout = spec.funding_tx_txout();
425 let internal_sighashes = tree.iter_internal().map(|node| {
426 let prev = if let Some((parent, sibling_idx))
427 = tree.parent_idx_of_with_sibling_idx(node.idx())
428 {
429 assert!(!node.is_root());
430 &txs[parent].output[sibling_idx]
431 } else {
432 assert!(node.is_root());
433 &root_txout
434 };
435
436 let mut shc = SighashCache::new(&txs[node.idx()]);
437 shc.taproot_key_spend_signature_hash(
438 0, &sighash::Prevouts::All(&[prev]),
440 TapSighashType::Default,
441 ).expect("sighash error")
442 }).collect();
443
444 UnsignedVtxoTree { spec, utxo, txs, internal_sighashes, cosign_agg_pks, tree }
445 }
446
447 pub fn nb_leaves(&self) -> usize {
448 self.tree.nb_leaves()
449 }
450
451 pub fn nb_cosigned_leaves(&self) -> usize {
453 self.spec.vtxos.iter()
454 .filter(|v| v.cosign_pubkey.is_some())
455 .count()
456 }
457
458 pub fn nb_nodes(&self) -> usize {
459 self.tree.nb_nodes()
460 }
461
462 pub fn nb_internal_nodes(&self) -> usize {
463 self.tree.nb_internal_nodes()
464 }
465
466 pub fn cosign_branch(
479 &self,
480 cosign_agg_nonces: &[AggregatedNonce],
481 leaf_idx: usize,
482 cosign_key: &Keypair,
483 cosign_sec_nonces: Vec<SecretNonce>,
484 ) -> Result<Vec<PartialSignature>, IncorrectSigningKeyError> {
485 let req = self.spec.vtxos.get(leaf_idx).expect("leaf idx out of bounds");
486 if Some(cosign_key.public_key()) != req.cosign_pubkey {
487 return Err(IncorrectSigningKeyError {
488 required: req.cosign_pubkey,
489 provided: cosign_key.public_key(),
490 });
491 }
492
493 let mut nonce_iter = cosign_sec_nonces.into_iter().enumerate();
494 let mut ret = Vec::with_capacity(self.tree.root().level() + 1);
495 for node in self.tree.iter_branch(leaf_idx).skip(1) {
497 let sec_nonce = loop {
502 let next = nonce_iter.next().expect("level overflow");
503 if next.0 == node.internal_level() {
504 break next.1;
505 }
506 };
507
508 let cosign_pubkeys = node.leaves()
509 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
510 .chain(self.spec.global_cosign_pubkeys.iter().copied());
511 let sighash = self.internal_sighashes[node.internal_idx()];
512
513 let agg_pk = self.cosign_agg_pks[node.idx()];
514 let tweak = self.spec.internal_taproot(agg_pk).tap_tweak().to_byte_array();
515 let sig = musig::partial_sign(
516 cosign_pubkeys,
517 cosign_agg_nonces[node.internal_idx()],
518 &cosign_key,
519 sec_nonce,
520 sighash.to_byte_array(),
521 Some(tweak),
522 None,
523 ).0;
524 ret.push(sig);
525 }
526
527 Ok(ret)
528 }
529
530 pub fn cosign_tree(
536 &self,
537 cosign_agg_nonces: &[AggregatedNonce],
538 keypair: &Keypair,
539 cosign_sec_nonces: Vec<SecretNonce>,
540 ) -> Vec<PartialSignature> {
541 debug_assert_eq!(cosign_agg_nonces.len(), self.nb_internal_nodes());
542 debug_assert_eq!(cosign_sec_nonces.len(), self.nb_internal_nodes());
543
544 let nonces = cosign_sec_nonces.into_iter().zip(cosign_agg_nonces);
545 self.tree.iter_internal().zip(nonces).map(|(node, (sec_nonce, agg_nonce))| {
546 let sighash = self.internal_sighashes[node.internal_idx()];
547
548 let cosign_pubkeys = node.leaves()
549 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
550 .chain(self.spec.global_cosign_pubkeys.iter().copied());
551 let agg_pk = self.cosign_agg_pks[node.idx()];
552 debug_assert_eq!(agg_pk, musig::combine_keys(cosign_pubkeys.clone()));
553 let taproot = self.spec.internal_taproot(agg_pk);
554 musig::partial_sign(
555 cosign_pubkeys,
556 *agg_nonce,
557 &keypair,
558 sec_nonce,
559 sighash.to_byte_array(),
560 Some(taproot.tap_tweak().to_byte_array()),
561 None,
562 ).0
563 }).collect()
564 }
565
566 fn verify_internal_node_cosign_partial_sig(
568 &self,
569 node: &tree::Node,
570 pk: PublicKey,
571 agg_nonces: &[AggregatedNonce],
572 part_sig: PartialSignature,
573 pub_nonce: PublicNonce,
574 ) -> Result<(), CosignSignatureError> {
575 debug_assert!(!node.is_leaf());
576
577 let sighash = self.internal_sighashes[node.internal_idx()];
578
579 let key_agg = {
580 let cosign_pubkeys = node.leaves()
581 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
582 .chain(self.spec.global_cosign_pubkeys.iter().copied());
583 let agg_pk = self.cosign_agg_pks[node.idx()];
584 let taproot = self.spec.internal_taproot(agg_pk);
585 let taptweak = taproot.tap_tweak().to_byte_array();
586 musig::tweaked_key_agg(cosign_pubkeys, taptweak).0
587 };
588 let agg_nonce = agg_nonces.get(node.internal_idx())
589 .ok_or(CosignSignatureError::NotEnoughNonces)?;
590 let session = musig::Session::new(&key_agg, *agg_nonce, &sighash.to_byte_array());
591 let ok = session.partial_verify(&key_agg, &part_sig, &pub_nonce, musig::pubkey_to(pk));
592 if !ok {
593 return Err(CosignSignatureError::invalid_sig(pk));
594 }
595 Ok(())
596 }
597
598 pub fn verify_branch_cosign_partial_sigs(
603 &self,
604 cosign_agg_nonces: &[AggregatedNonce],
605 request: &VtxoLeafSpec,
606 cosign_pub_nonces: &[PublicNonce],
607 cosign_part_sigs: &[PartialSignature],
608 ) -> Result<(), String> {
609 assert_eq!(cosign_agg_nonces.len(), self.nb_internal_nodes());
610
611 let cosign_pubkey = request.cosign_pubkey.ok_or("no cosign pubkey for request")?;
612 let leaf_idx = self.spec.leaf_idx_of(request).ok_or("request not in tree")?;
613
614 let internal_branch = self.tree.iter_branch(leaf_idx).skip(1);
616
617 match internal_branch.clone().count().cmp(&cosign_part_sigs.len()) {
619 cmp::Ordering::Less => return Err("too few partial signatures".into()),
620 cmp::Ordering::Greater => return Err("too many partial signatures".into()),
621 cmp::Ordering::Equal => {},
622 }
623
624 let mut part_sigs_iter = cosign_part_sigs.iter();
625 let mut pub_nonce_iter = cosign_pub_nonces.iter().enumerate();
626 for node in internal_branch {
627 let pub_nonce = loop {
628 let next = pub_nonce_iter.next().ok_or("not enough pub nonces")?;
629 if next.0 == node.internal_level() {
630 break next.1;
631 }
632 };
633 self.verify_internal_node_cosign_partial_sig(
634 node,
635 cosign_pubkey,
636 cosign_agg_nonces,
637 part_sigs_iter.next().ok_or("not enough sigs")?.clone(),
638 *pub_nonce,
639 ).map_err(|e| format!("part sig verification failed: {}", e))?;
640 }
641
642 Ok(())
643 }
644
645 pub fn verify_global_cosign_partial_sigs(
650 &self,
651 pk: PublicKey,
652 agg_nonces: &[AggregatedNonce],
653 pub_nonces: &[PublicNonce],
654 part_sigs: &[PartialSignature],
655 ) -> Result<(), CosignSignatureError> {
656 for node in self.tree.iter_internal() {
657 let sigs = *part_sigs.get(node.internal_idx())
658 .ok_or_else(|| CosignSignatureError::missing_sig(pk))?;
659 let nonces = *pub_nonces.get(node.internal_idx())
660 .ok_or_else(|| CosignSignatureError::NotEnoughNonces)?;
661 self.verify_internal_node_cosign_partial_sig(node, pk, agg_nonces, sigs, nonces)?;
662 }
663
664 Ok(())
665 }
666
667 pub fn combine_partial_signatures(
676 &self,
677 cosign_agg_nonces: &[AggregatedNonce],
678 branch_part_sigs: &HashMap<PublicKey, Vec<PartialSignature>>,
679 global_signer_part_sigs: &[impl AsRef<[PartialSignature]>],
680 ) -> Result<Vec<schnorr::Signature>, CosignSignatureError> {
681 let mut leaf_part_sigs = branch_part_sigs.iter()
683 .map(|(pk, sigs)| (pk, sigs.iter().collect()))
684 .collect::<HashMap<_, VecDeque<_>>>();
685
686 if global_signer_part_sigs.len() != self.spec.global_cosign_pubkeys.len() {
687 return Err(CosignSignatureError::Invalid(
688 "invalid nb of global cosigner partial signatures",
689 ));
690 }
691 for (pk, sigs) in self.spec.global_cosign_pubkeys.iter().zip(global_signer_part_sigs) {
692 if sigs.as_ref().len() != self.nb_internal_nodes() {
693 return Err(CosignSignatureError::MissingSignature { pk: *pk });
696 }
697 }
698
699 let max_level = match self.tree.root().is_leaf() {
700 true => 0,
701 false => self.tree.root().internal_level(),
702 };
703 self.tree.iter_internal().map(|node| {
704 let mut cosign_pks = Vec::with_capacity(max_level + 1);
705 let mut part_sigs = Vec::with_capacity(max_level + 1);
706 for leaf in node.leaves() {
707 if let Some(cosign_pk) = self.spec.vtxos[leaf].cosign_pubkey {
708 let part_sig = leaf_part_sigs.get_mut(&cosign_pk)
709 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?
710 .pop_front()
711 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?;
712 cosign_pks.push(cosign_pk);
713 part_sigs.push(part_sig);
714 }
715 }
716 cosign_pks.extend(&self.spec.global_cosign_pubkeys);
718 for sigs in global_signer_part_sigs {
719 part_sigs.push(sigs.as_ref().get(node.internal_idx()).expect("checked before"));
720 }
721
722 let agg_pk = self.cosign_agg_pks[node.idx()];
723 let taproot = self.spec.internal_taproot(agg_pk);
724 let agg_nonce = *cosign_agg_nonces.get(node.internal_idx())
725 .ok_or(CosignSignatureError::NotEnoughNonces)?;
726 let sighash = self.internal_sighashes[node.internal_idx()].to_byte_array();
727 let tweak = taproot.tap_tweak().to_byte_array();
728 Ok(musig::combine_partial_signatures(
729 cosign_pks, agg_nonce, sighash, Some(tweak), &part_sigs,
730 ))
731 }).collect()
732 }
733
734 pub fn verify_cosign_sigs(
738 &self,
739 signatures: &[schnorr::Signature],
740 ) -> Result<(), XOnlyPublicKey> {
741 for node in self.tree.iter_internal() {
742 let sighash = self.internal_sighashes[node.internal_idx()];
743 let agg_pk = &self.cosign_agg_pks[node.idx()];
744 let pk = self.spec.internal_taproot(*agg_pk).output_key().to_x_only_public_key();
745 let sig = signatures.get(node.internal_idx()).ok_or_else(|| pk)?;
746 if SECP.verify_schnorr(sig, &sighash.into(), &pk).is_err() {
747 return Err(pk);
748 }
749 }
750 Ok(())
751 }
752
753 pub fn into_signed_tree(
757 self,
758 signatures: Vec<schnorr::Signature>,
759 ) -> SignedVtxoTreeSpec {
760 SignedVtxoTreeSpec {
761 spec: self.spec,
762 utxo: self.utxo,
763 cosign_sigs: signatures,
764 }
765 }
766}
767
768#[derive(PartialEq, Eq, thiserror::Error)]
770pub enum CosignSignatureError {
771 #[error("missing cosign signature from pubkey {pk}")]
772 MissingSignature { pk: PublicKey },
773 #[error("invalid cosign signature from pubkey {pk}")]
774 InvalidSignature { pk: PublicKey },
775 #[error("not enough nonces")]
776 NotEnoughNonces,
777 #[error("invalid cosign signatures: {0}")]
778 Invalid(&'static str),
779}
780
781impl fmt::Debug for CosignSignatureError {
782 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
783 fmt::Display::fmt(self, f)
784 }
785}
786
787impl CosignSignatureError {
788 fn missing_sig(cosign_pk: PublicKey) -> CosignSignatureError {
789 CosignSignatureError::MissingSignature { pk: cosign_pk }
790 }
791 fn invalid_sig(cosign_pk: PublicKey) -> CosignSignatureError {
792 CosignSignatureError::InvalidSignature { pk: cosign_pk }
793 }
794}
795
796#[derive(Debug, Clone, PartialEq)]
798pub struct SignedVtxoTreeSpec {
799 pub spec: VtxoTreeSpec,
800 pub utxo: OutPoint,
801 pub cosign_sigs: Vec<schnorr::Signature>,
803}
804
805impl SignedVtxoTreeSpec {
806 pub fn new(
808 spec: VtxoTreeSpec,
809 utxo: OutPoint,
810 cosign_signatures: Vec<schnorr::Signature>,
811 ) -> SignedVtxoTreeSpec {
812 SignedVtxoTreeSpec { spec, utxo, cosign_sigs: cosign_signatures }
813 }
814
815 pub fn nb_leaves(&self) -> usize {
816 self.spec.nb_leaves()
817 }
818
819 pub fn exit_branch(&self, leaf_idx: usize) -> Vec<Transaction> {
826 let txs = self.all_final_txs();
827 let tree = Tree::new(self.spec.nb_leaves());
828 let mut ret = tree.iter_branch(leaf_idx)
829 .map(|n| txs[n.idx()].clone())
830 .collect::<Vec<_>>();
831 ret.reverse();
832 ret
833 }
834
835 pub fn all_final_txs(&self) -> Vec<Transaction> {
837 self.spec.final_transactions(self.utxo, &self.cosign_sigs)
838 }
839
840 pub fn into_cached_tree(self) -> CachedSignedVtxoTree {
841 CachedSignedVtxoTree {
842 txs: self.all_final_txs(),
843 spec: self,
844 }
845 }
846}
847
848pub struct CachedSignedVtxoTree {
852 pub spec: SignedVtxoTreeSpec,
853 pub txs: Vec<Transaction>,
855}
856
857impl CachedSignedVtxoTree {
858 pub fn exit_branch(&self, leaf_idx: usize) -> Vec<&Transaction> {
862 let tree = Tree::new(self.spec.spec.nb_leaves());
863 let mut ret = tree.iter_branch(leaf_idx)
864 .map(|n| &self.txs[n.idx()])
865 .collect::<Vec<_>>();
866 ret.reverse();
867 ret
868 }
869
870 pub fn nb_leaves(&self) -> usize {
871 self.spec.nb_leaves()
872 }
873
874 pub fn all_final_txs(&self) -> &[Transaction] {
876 &self.txs
877 }
878
879 pub fn build_vtxo(&self, leaf_idx: usize) -> Vtxo {
883 let req = self.spec.spec.vtxos.get(leaf_idx).expect("index is not a leaf");
884 let genesis = {
885 let mut genesis = Vec::new();
886
887 let tree = Tree::new(self.spec.spec.nb_leaves());
888 let mut branch = tree.iter_branch(leaf_idx);
889
890 let leaf_node = branch.next().unwrap();
892 genesis.push(GenesisItem {
893 transition: GenesisTransition::new_hash_locked_cosigned(
894 req.vtxo.policy.user_pubkey(),
895 None,
896 MaybePreimage::Hash(req.unlock_hash)),
897 output_idx: 0,
898 other_outputs: vec![],
899 });
900
901 let mut last_node = leaf_node.idx();
903 for node in branch {
904 let pubkeys = node.leaves()
905 .filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
906 .chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
907 .collect();
908 let sig = self.spec.cosign_sigs.get(node.internal_idx())
909 .expect("enough sigs for all nodes");
910
911 let transition = GenesisTransition::new_cosigned(pubkeys, Some(*sig));
912
913 let output_idx = node.children().position(|child_idx| last_node == child_idx)
914 .expect("last node should be our child") as u8;
915 let other_outputs = self.txs.get(node.idx()).expect("we have all txs")
916 .output.iter()
917 .enumerate()
918 .filter(|(i, o)| !o.is_p2a_fee_anchor() && *i != output_idx as usize)
919 .map(|(_i, o)| o).cloned().collect();
920 genesis.push(GenesisItem { transition, output_idx, other_outputs });
921 last_node = node.idx();
922 }
923 genesis.reverse();
924
925
926 genesis
927 };
928
929 Vtxo {
930 amount: req.vtxo.amount,
931 expiry_height: self.spec.spec.expiry_height,
932 server_pubkey: self.spec.spec.server_pubkey,
933 exit_delta: self.spec.spec.exit_delta,
934 anchor_point: self.spec.utxo,
935 genesis: genesis,
936 policy: req.vtxo.policy.clone(),
937 point: {
938 let leaf_tx = self.txs.get(leaf_idx).expect("leaf idx exists");
939 OutPoint::new(leaf_tx.compute_txid(), 0)
940 },
941 }
942 }
943
944 pub fn all_vtxos(&self) -> impl Iterator<Item = Vtxo> + ExactSizeIterator + '_ {
946 (0..self.nb_leaves()).map(|idx| self.build_vtxo(idx))
947 }
948}
949
950pub fn hashlocked_leaf_sighash(
952 leaf_tx: &Transaction,
953 user_pubkey: PublicKey,
954 server_pubkey: PublicKey,
955 unlock_hash: UnlockHash,
956 prev_txout: &TxOut,
957) -> TapSighash {
958 let agg_pk = musig::combine_keys([user_pubkey, server_pubkey]);
959 let clause = unlock_clause(agg_pk, unlock_hash);
960 let leaf_hash = TapLeafHash::from_script(&clause, bitcoin::taproot::LeafVersion::TapScript);
961 let mut shc = SighashCache::new(leaf_tx);
962 shc.taproot_script_spend_signature_hash(
963 0, &sighash::Prevouts::All(&[prev_txout]),
965 leaf_hash,
966 TapSighashType::Default,
967 ).expect("sighash error")
968}
969
970fn hashlocked_leaf_sighash_from_vtxo(
976 vtxo: &Vtxo,
977 chain_anchor: &Transaction,
978) -> TapSighash {
979 assert_eq!(chain_anchor.compute_txid(), vtxo.chain_anchor().txid);
980 let last_genesis = vtxo.genesis.last().expect("at least one genesis item");
981 let (user_pubkey, unlock_hash) = match &last_genesis.transition {
982 GenesisTransition::HashLockedCosigned(inner) => {
983 (inner.user_pubkey, inner.unlock.hash())
984 },
985 _ => panic!("VTXO is not a HashLockedCosigned VTXO")
986 };
987 debug_assert_eq!(user_pubkey, vtxo.user_pubkey());
988
989 let mut preleaf_txout = chain_anchor.output[vtxo.chain_anchor().vout as usize].clone();
991 let mut leaf_tx = None;
992 let mut peekable_iter = vtxo.transactions().peekable();
993 while let Some(item) = peekable_iter.next() {
994 if peekable_iter.peek().is_some() {
997 preleaf_txout = item.tx.output[item.output_idx].clone();
998 }
999
1000 if peekable_iter.peek().is_none() {
1002 leaf_tx = Some(item.tx);
1003 }
1004 }
1005 let leaf_tx = leaf_tx.expect("at least one tx");
1006 hashlocked_leaf_sighash(
1007 &leaf_tx, user_pubkey, vtxo.server_pubkey(), unlock_hash, &preleaf_txout,
1008 )
1009}
1010
1011#[derive(Debug)]
1012pub struct LeafVtxoCosignRequest {
1013 pub vtxo_id: VtxoId,
1014 pub pub_nonce: musig::PublicNonce,
1015}
1016
1017pub struct LeafVtxoCosignContext<'a> {
1018 key: &'a Keypair,
1019 pub_nonce: musig::PublicNonce,
1020 sec_nonce: musig::SecretNonce,
1021 sighash: TapSighash,
1022}
1023
1024impl<'a> LeafVtxoCosignContext<'a> {
1025 pub fn new(
1030 vtxo: &Vtxo,
1031 chain_anchor: &Transaction,
1032 key: &'a Keypair,
1033 ) -> (Self, LeafVtxoCosignRequest) {
1034 let sighash = hashlocked_leaf_sighash_from_vtxo(&vtxo, chain_anchor);
1035 let (sec_nonce, pub_nonce) = musig::nonce_pair_with_msg(key, &sighash.to_byte_array());
1036 let vtxo_id = vtxo.id();
1037 let req = LeafVtxoCosignRequest { vtxo_id, pub_nonce };
1038 let ret = Self { key, pub_nonce, sec_nonce, sighash };
1039 (ret, req)
1040 }
1041
1042 pub fn finalize(
1044 self,
1045 vtxo: &mut Vtxo,
1046 response: LeafVtxoCosignResponse,
1047 ) -> bool {
1048 let agg_nonce = musig::nonce_agg(&[&self.pub_nonce, &response.public_nonce]);
1049 let (_part_sig, final_sig) = musig::partial_sign(
1050 [vtxo.user_pubkey(), vtxo.server_pubkey()],
1051 agg_nonce,
1052 self.key,
1053 self.sec_nonce,
1054 self.sighash.to_byte_array(),
1055 None,
1056 Some(&[&response.partial_signature]),
1057 );
1058 let final_sig = final_sig.expect("has other sigs");
1059
1060 let pubkey = musig::combine_keys([vtxo.user_pubkey(), vtxo.server_pubkey()]);
1061 debug_assert_eq!(pubkey, leaf_cosign_taproot(
1062 vtxo.user_pubkey(),
1063 vtxo.server_pubkey(),
1064 vtxo.expiry_height(),
1065 vtxo.unlock_hash().expect("checked is hark vtxo"),
1066 ).internal_key());
1067 if SECP.verify_schnorr(&final_sig, &self.sighash.into(), &pubkey).is_err() {
1068 return false;
1069 }
1070
1071 vtxo.provide_unlock_signature(final_sig)
1072 }
1073}
1074
1075#[derive(Debug)]
1076pub struct LeafVtxoCosignResponse {
1077 pub public_nonce: musig::PublicNonce,
1078 pub partial_signature: musig::PartialSignature,
1079}
1080
1081impl LeafVtxoCosignResponse {
1082 pub fn new_cosign(
1084 request: &LeafVtxoCosignRequest,
1085 vtxo: &Vtxo,
1086 chain_anchor: &Transaction,
1087 server_key: &Keypair,
1088 ) -> Self {
1089 debug_assert_eq!(server_key.public_key(), vtxo.server_pubkey());
1090 let sighash = hashlocked_leaf_sighash_from_vtxo(&vtxo, chain_anchor);
1091 let (public_nonce, partial_signature) = musig::deterministic_partial_sign(
1092 server_key,
1093 [vtxo.user_pubkey()],
1094 &[&request.pub_nonce],
1095 sighash.to_byte_array(),
1096 None,
1097 );
1098 Self { public_nonce, partial_signature }
1099 }
1100}
1101
1102pub mod builder {
1103 use std::collections::HashMap;
1110 use std::marker::PhantomData;
1111
1112 use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut};
1113 use bitcoin::hashes::{sha256, Hash};
1114 use bitcoin::secp256k1::{Keypair, PublicKey};
1115 use bitcoin_ext::{BlockDelta, BlockHeight};
1116
1117 use crate::tree::signed::{UnlockHash, UnlockPreimage, VtxoLeafSpec};
1118 use crate::{musig, VtxoRequest};
1119 use crate::error::IncorrectSigningKeyError;
1120
1121 use super::{CosignSignatureError, SignedVtxoTreeSpec, UnsignedVtxoTree, VtxoTreeSpec};
1122
1123 pub mod state {
1124 mod sealed {
1125 pub trait Sealed {}
1127 impl Sealed for super::Preparing {}
1128 impl Sealed for super::CanGenerateNonces {}
1129 impl Sealed for super::ServerCanCosign {}
1130 impl Sealed for super::CanFinish {}
1131 }
1132
1133 pub trait BuilderState: sealed::Sealed {}
1135
1136 pub struct Preparing;
1138 impl BuilderState for Preparing {}
1139
1140 pub struct CanGenerateNonces;
1143 impl BuilderState for CanGenerateNonces {}
1144
1145 pub struct ServerCanCosign;
1147 impl BuilderState for ServerCanCosign {}
1148
1149 pub struct CanFinish;
1152 impl BuilderState for CanFinish {}
1153
1154 pub trait CanSign: BuilderState {}
1157 impl CanSign for ServerCanCosign {}
1158 impl CanSign for CanFinish {}
1159 }
1160
1161 enum BuilderTree {
1163 Spec(VtxoTreeSpec),
1164 Unsigned(UnsignedVtxoTree),
1165 }
1166
1167 impl BuilderTree {
1168 fn unsigned_tree(&self) -> Option<&UnsignedVtxoTree> {
1169 match self {
1170 BuilderTree::Spec(_) => None,
1171 BuilderTree::Unsigned(t) => Some(t),
1172 }
1173 }
1174
1175 fn into_unsigned_tree(self) -> Option<UnsignedVtxoTree> {
1176 match self {
1177 BuilderTree::Spec(_) => None,
1178 BuilderTree::Unsigned(t) => Some(t),
1179 }
1180 }
1181 }
1182
1183 pub struct SignedTreeBuilder<S: state::BuilderState> {
1187 pub expiry_height: BlockHeight,
1188 pub server_pubkey: PublicKey,
1189 pub exit_delta: BlockDelta,
1190 pub cosign_pubkey: PublicKey,
1192 pub unlock_preimage: UnlockPreimage,
1194
1195 tree: BuilderTree,
1196
1197 user_pub_nonces: Vec<musig::PublicNonce>,
1199 user_sec_nonces: Option<Vec<musig::SecretNonce>>,
1202 _state: PhantomData<S>,
1203 }
1204
1205 impl<T: state::BuilderState> SignedTreeBuilder<T> {
1206 fn tree_spec(&self) -> &VtxoTreeSpec {
1207 match self.tree {
1208 BuilderTree::Spec(ref s) => s,
1209 BuilderTree::Unsigned(ref t) => &t.spec,
1210 }
1211 }
1212
1213 pub fn total_required_value(&self) -> Amount {
1215 self.tree_spec().total_required_value()
1216 }
1217
1218 pub fn funding_script_pubkey(&self) -> ScriptBuf {
1220 self.tree_spec().funding_tx_script_pubkey()
1221 }
1222
1223 pub fn funding_txout(&self) -> TxOut {
1225 let spec = self.tree_spec();
1226 TxOut {
1227 value: spec.total_required_value(),
1228 script_pubkey: spec.funding_tx_script_pubkey(),
1229 }
1230 }
1231 }
1232
1233 impl<T: state::CanSign> SignedTreeBuilder<T> {
1234 pub fn user_pub_nonces(&self) -> &[musig::PublicNonce] {
1236 &self.user_pub_nonces
1237 }
1238 }
1239
1240 #[derive(Debug, thiserror::Error)]
1241 #[error("signed VTXO tree builder error: {0}")]
1242 pub struct SignedTreeBuilderError(&'static str);
1243
1244 impl SignedTreeBuilder<state::Preparing> {
1245 pub fn construct_tree_spec(
1247 vtxos: impl IntoIterator<Item = VtxoRequest>,
1248 cosign_pubkey: PublicKey,
1249 unlock_hash: UnlockHash,
1250 expiry_height: BlockHeight,
1251 server_pubkey: PublicKey,
1252 server_cosign_pubkey: PublicKey,
1253 exit_delta: BlockDelta,
1254 ) -> Result<VtxoTreeSpec, SignedTreeBuilderError> {
1255 let reqs = vtxos.into_iter()
1256 .map(|vtxo| VtxoLeafSpec {
1257 vtxo: vtxo,
1258 cosign_pubkey: None,
1259 unlock_hash: unlock_hash,
1260 })
1261 .collect::<Vec<_>>();
1262 if reqs.len() < 2 {
1263 return Err(SignedTreeBuilderError("need to have at least 2 VTXOs in tree"));
1264 }
1265 Ok(VtxoTreeSpec::new(
1266 reqs,
1267 server_pubkey,
1268 expiry_height,
1269 exit_delta,
1270 vec![cosign_pubkey, server_cosign_pubkey],
1273 ))
1274 }
1275
1276 pub fn new(
1278 vtxos: impl IntoIterator<Item = VtxoRequest>,
1279 cosign_pubkey: PublicKey,
1280 unlock_preimage: UnlockPreimage,
1281 expiry_height: BlockHeight,
1282 server_pubkey: PublicKey,
1283 server_cosign_pubkey: PublicKey,
1284 exit_delta: BlockDelta,
1285 ) -> Result<SignedTreeBuilder<state::Preparing>, SignedTreeBuilderError> {
1286 let tree = Self::construct_tree_spec(
1287 vtxos,
1288 cosign_pubkey,
1289 sha256::Hash::hash(&unlock_preimage),
1290 expiry_height,
1291 server_pubkey,
1292 server_cosign_pubkey,
1293 exit_delta,
1294 )?;
1295
1296 Ok(SignedTreeBuilder {
1297 expiry_height, server_pubkey, exit_delta, cosign_pubkey, unlock_preimage,
1298 tree: BuilderTree::Spec(tree),
1299 user_pub_nonces: Vec::new(),
1300 user_sec_nonces: None,
1301 _state: PhantomData,
1302 })
1303 }
1304
1305 pub fn set_utxo(self, utxo: OutPoint) -> SignedTreeBuilder<state::CanGenerateNonces> {
1307 let unsigned_tree = match self.tree {
1308 BuilderTree::Spec(s) => s.into_unsigned_tree(utxo),
1309 BuilderTree::Unsigned(t) => t, };
1311 SignedTreeBuilder {
1312 tree: BuilderTree::Unsigned(unsigned_tree),
1313
1314 expiry_height: self.expiry_height,
1315 server_pubkey: self.server_pubkey,
1316 exit_delta: self.exit_delta,
1317 cosign_pubkey: self.cosign_pubkey,
1318 unlock_preimage: self.unlock_preimage,
1319 user_pub_nonces: self.user_pub_nonces,
1320 user_sec_nonces: self.user_sec_nonces,
1321 _state: PhantomData,
1322 }
1323 }
1324 }
1325
1326 impl SignedTreeBuilder<state::CanGenerateNonces> {
1327 pub fn generate_user_nonces(
1329 self,
1330 cosign_key: &Keypair,
1331 ) -> SignedTreeBuilder<state::CanFinish> {
1332 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1333
1334 let mut cosign_sec_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1335 let mut cosign_pub_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1336 for sh in &unsigned_tree.internal_sighashes {
1337 let pair = musig::nonce_pair_with_msg(&cosign_key, &sh.to_byte_array());
1338 cosign_sec_nonces.push(pair.0);
1339 cosign_pub_nonces.push(pair.1);
1340 }
1341
1342 SignedTreeBuilder {
1343 user_pub_nonces: cosign_pub_nonces,
1344 user_sec_nonces: Some(cosign_sec_nonces),
1345
1346 expiry_height: self.expiry_height,
1347 server_pubkey: self.server_pubkey,
1348 exit_delta: self.exit_delta,
1349 cosign_pubkey: self.cosign_pubkey,
1350 unlock_preimage: self.unlock_preimage,
1351 tree: self.tree,
1352 _state: PhantomData,
1353 }
1354 }
1355 }
1356
1357 #[derive(Debug, Clone)]
1359 pub struct SignedTreeCosignResponse {
1360 pub pub_nonces: Vec<musig::PublicNonce>,
1361 pub partial_signatures: Vec<musig::PartialSignature>,
1362 }
1363
1364 impl SignedTreeBuilder<state::ServerCanCosign> {
1365 pub fn new_for_cosign(
1367 vtxos: impl IntoIterator<Item = VtxoRequest>,
1368 cosign_pubkey: PublicKey,
1369 unlock_preimage: UnlockPreimage,
1370 expiry_height: BlockHeight,
1371 server_pubkey: PublicKey,
1372 server_cosign_pubkey: PublicKey,
1373 exit_delta: BlockDelta,
1374 utxo: OutPoint,
1375 user_pub_nonces: Vec<musig::PublicNonce>,
1376 ) -> Result<SignedTreeBuilder<state::ServerCanCosign>, SignedTreeBuilderError> {
1377 let unsigned_tree = SignedTreeBuilder::construct_tree_spec(
1378 vtxos,
1379 cosign_pubkey,
1380 sha256::Hash::hash(&unlock_preimage),
1381 expiry_height,
1382 server_pubkey,
1383 server_cosign_pubkey,
1384 exit_delta,
1385 )?.into_unsigned_tree(utxo);
1386
1387 Ok(SignedTreeBuilder {
1388 expiry_height,
1389 server_pubkey,
1390 exit_delta,
1391 cosign_pubkey,
1392 unlock_preimage,
1393 user_pub_nonces,
1394 tree: BuilderTree::Unsigned(unsigned_tree),
1395 user_sec_nonces: None,
1396 _state: PhantomData,
1397 })
1398 }
1399
1400 pub fn server_cosign(&self, server_cosign_key: &Keypair) -> SignedTreeCosignResponse {
1402 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1403
1404 let mut sec_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1405 let mut pub_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1406 for sh in &unsigned_tree.internal_sighashes {
1407 let pair = musig::nonce_pair_with_msg(&server_cosign_key, &sh.to_byte_array());
1408 sec_nonces.push(pair.0);
1409 pub_nonces.push(pair.1);
1410 }
1411
1412 let agg_nonces = self.user_pub_nonces().iter().zip(&pub_nonces)
1413 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1414 .collect::<Vec<_>>();
1415
1416 let sigs = unsigned_tree.cosign_tree(&agg_nonces, &server_cosign_key, sec_nonces);
1417
1418 SignedTreeCosignResponse {
1419 pub_nonces,
1420 partial_signatures: sigs,
1421 }
1422 }
1423 }
1424
1425 impl SignedTreeBuilder<state::CanFinish> {
1426 pub fn verify_cosign_response(
1428 &self,
1429 server_cosign: &SignedTreeCosignResponse,
1430 ) -> Result<(), CosignSignatureError> {
1431 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1432
1433 let agg_nonces = self.user_pub_nonces().iter()
1434 .zip(&server_cosign.pub_nonces)
1435 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1436 .collect::<Vec<_>>();
1437
1438 unsigned_tree.verify_global_cosign_partial_sigs(
1439 *unsigned_tree.spec.global_cosign_pubkeys.get(1).expect("state invariant"),
1440 &agg_nonces,
1441 &server_cosign.pub_nonces,
1442 &server_cosign.partial_signatures,
1443 )
1444 }
1445
1446 pub fn build_tree(
1447 self,
1448 server_cosign: &SignedTreeCosignResponse,
1449 cosign_key: &Keypair,
1450 ) -> Result<SignedVtxoTreeSpec, IncorrectSigningKeyError> {
1451 if cosign_key.public_key() != self.cosign_pubkey {
1452 return Err(IncorrectSigningKeyError {
1453 required: Some(self.cosign_pubkey),
1454 provided: cosign_key.public_key(),
1455 });
1456 }
1457
1458 let agg_nonces = self.user_pub_nonces().iter().zip(&server_cosign.pub_nonces)
1459 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1460 .collect::<Vec<_>>();
1461
1462 let unsigned_tree = self.tree.into_unsigned_tree().expect("state invariant");
1463 let sec_nonces = self.user_sec_nonces.expect("state invariant");
1464 let partial_sigs = unsigned_tree.cosign_tree(&agg_nonces, cosign_key, sec_nonces);
1465
1466 debug_assert!(unsigned_tree.verify_global_cosign_partial_sigs(
1467 self.cosign_pubkey,
1468 &agg_nonces,
1469 &self.user_pub_nonces,
1470 &partial_sigs,
1471 ).is_ok(), "produced invalid partial signatures");
1472
1473 let sigs = unsigned_tree.combine_partial_signatures(
1474 &agg_nonces,
1475 &HashMap::new(),
1476 &[&server_cosign.partial_signatures, &partial_sigs],
1477 ).expect("should work with correct cosign signatures");
1478
1479 Ok(unsigned_tree.into_signed_tree(sigs))
1480 }
1481 }
1482}
1483
1484const VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1486
1487impl ProtocolEncoding for VtxoTreeSpec {
1488 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1489 w.emit_u8(VTXO_TREE_SPEC_VERSION)?;
1490 w.emit_u32(self.expiry_height)?;
1491 self.server_pubkey.encode(w)?;
1492 w.emit_u16(self.exit_delta)?;
1493 w.emit_compact_size(self.global_cosign_pubkeys.len() as u64)?;
1494 for pk in &self.global_cosign_pubkeys {
1495 pk.encode(w)?;
1496 }
1497
1498 w.emit_compact_size(self.vtxos.len() as u64)?;
1499 for vtxo in &self.vtxos {
1500 vtxo.encode(w)?;
1501 }
1502 Ok(())
1503 }
1504
1505 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, crate::encode::ProtocolDecodingError> {
1506 let version = r.read_u8()?;
1507
1508 if version != VTXO_TREE_SPEC_VERSION {
1509 return Err(ProtocolDecodingError::invalid(format_args!(
1510 "invalid VtxoTreeSpec encoding version byte: {version:#x}",
1511 )));
1512 }
1513
1514 let expiry_height = r.read_u32()?;
1515 let server_pubkey = PublicKey::decode(r)?;
1516 let exit_delta = r.read_u16()?;
1517 let nb_global_signers = r.read_compact_size()?;
1518 let mut global_cosign_pubkeys = Vec::with_capacity(nb_global_signers as usize);
1519 for _ in 0..nb_global_signers {
1520 global_cosign_pubkeys.push(PublicKey::decode(r)?);
1521 }
1522
1523 let nb_vtxos = r.read_compact_size()?;
1524 let mut vtxos = Vec::with_capacity(nb_vtxos as usize);
1525 for _ in 0..nb_vtxos {
1526 vtxos.push(VtxoLeafSpec::decode(r)?);
1527 }
1528
1529 Ok(VtxoTreeSpec { vtxos, expiry_height, server_pubkey, exit_delta, global_cosign_pubkeys })
1530 }
1531}
1532
1533const SIGNED_VTXO_TREE_SPEC_VERSION: u8 = 0x01;
1535
1536impl ProtocolEncoding for SignedVtxoTreeSpec {
1537 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1538 w.emit_u8(SIGNED_VTXO_TREE_SPEC_VERSION)?;
1539 self.spec.encode(w)?;
1540 self.utxo.encode(w)?;
1541 w.emit_u32(self.cosign_sigs.len() as u32)?;
1542 for sig in &self.cosign_sigs {
1543 sig.encode(w)?;
1544 }
1545 Ok(())
1546 }
1547
1548 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, crate::encode::ProtocolDecodingError> {
1549 let version = r.read_u8()?;
1550 if version != SIGNED_VTXO_TREE_SPEC_VERSION {
1551 return Err(ProtocolDecodingError::invalid(format_args!(
1552 "invalid SignedVtxoTreeSpec encoding version byte: {version:#x}",
1553 )));
1554 }
1555 let spec = VtxoTreeSpec::decode(r)?;
1556 let utxo = OutPoint::decode(r)?;
1557 let nb_cosign_sigs = r.read_u32()?;
1558 let mut cosign_sigs = Vec::with_capacity(nb_cosign_sigs as usize);
1559 for _ in 0..nb_cosign_sigs {
1560 cosign_sigs.push(schnorr::Signature::decode(r)?);
1561 }
1562 Ok(SignedVtxoTreeSpec { spec, utxo, cosign_sigs })
1563 }
1564}
1565
1566
1567#[cfg(test)]
1568mod test {
1569 use std::iter;
1570 use std::collections::HashMap;
1571 use std::str::FromStr;
1572
1573 use bitcoin::hashes::{siphash24, sha256, Hash, HashEngine};
1574 use bitcoin::key::rand::Rng;
1575 use bitcoin::secp256k1::{self, rand, Keypair};
1576 use bitcoin::{absolute, transaction};
1577 use rand::SeedableRng;
1578
1579 use crate::encode;
1580 use crate::test_util::{encoding_roundtrip, json_roundtrip};
1581 use crate::tree::signed::builder::SignedTreeBuilder;
1582 use crate::vtxo::policy::VtxoPolicy;
1583
1584 use super::*;
1585
1586 fn test_tree_amounts(
1587 tree: &UnsignedVtxoTree,
1588 root_value: Amount,
1589 ) {
1590 let map = tree.txs.iter().map(|tx| (tx.compute_txid(), tx)).collect::<HashMap<_, _>>();
1591
1592 for (idx, tx) in tree.txs.iter().take(tree.txs.len() - 1).enumerate() {
1594 println!("tx #{idx}: {}", bitcoin::consensus::encode::serialize_hex(tx));
1595 let input = tx.input.iter().map(|i| {
1596 let prev = i.previous_output;
1597 map.get(&prev.txid).expect(&format!("tx {} not found", prev.txid))
1598 .output[prev.vout as usize].value
1599 }).sum::<Amount>();
1600 let output = tx.output_value();
1601 assert!(input >= output);
1602 assert_eq!(input, output);
1603 }
1604
1605 let root = tree.txs.last().unwrap();
1607 assert_eq!(root_value, root.output_value());
1608 }
1609
1610 #[test]
1611 fn vtxo_tree() {
1612 let secp = secp256k1::Secp256k1::new();
1613 let mut rand = rand::rngs::StdRng::seed_from_u64(42);
1614 let random_sig = {
1615 let key = Keypair::new(&secp, &mut rand);
1616 let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1617 let msg = secp256k1::Message::from_digest(sha.to_byte_array());
1618 secp.sign_schnorr(&msg, &key)
1619 };
1620
1621 let server_key = Keypair::new(&secp, &mut rand);
1622 let server_cosign_key = Keypair::new(&secp, &mut rand);
1623
1624 struct Req {
1625 key: Keypair,
1626 cosign_key: Keypair,
1627 amount: Amount,
1628 hash: sha256::Hash,
1629 }
1630 impl Req {
1631 fn to_vtxo(&self) -> VtxoLeafSpec {
1632 VtxoLeafSpec {
1633 vtxo: VtxoRequest {
1634 amount: self.amount,
1635 policy: VtxoPolicy::new_pubkey(self.key.public_key()),
1636 },
1637 cosign_pubkey: Some(self.cosign_key.public_key()),
1638 unlock_hash: self.hash,
1639 }
1640 }
1641 }
1642
1643 let nb_leaves = 27;
1644 let reqs = iter::repeat_with(|| Req {
1645 key: Keypair::new(&secp, &mut rand),
1646 cosign_key: Keypair::new(&secp, &mut rand),
1647 amount: Amount::from_sat(100_000),
1648 hash: sha256::Hash::from_byte_array(rand.r#gen()),
1649 }).take(nb_leaves).collect::<Vec<_>>();
1650 let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
1651
1652 let spec = VtxoTreeSpec::new(
1653 reqs.iter().map(|r| r.to_vtxo()).collect(),
1654 server_key.public_key(),
1655 101_000,
1656 2016,
1657 vec![server_cosign_key.public_key()],
1658 );
1659 assert_eq!(spec.nb_leaves(), nb_leaves);
1660 assert_eq!(spec.total_required_value().to_sat(), 2700000);
1661 let nb_nodes = spec.nb_nodes();
1662
1663 encoding_roundtrip(&spec);
1664
1665 let unsigned = spec.into_unsigned_tree(point);
1666
1667 test_tree_amounts(&unsigned, unsigned.spec.total_required_value());
1668
1669 let sighashes_hash = {
1670 let mut eng = siphash24::Hash::engine();
1671 unsigned.internal_sighashes.iter().for_each(|h| eng.input(&h[..]));
1672 siphash24::Hash::from_engine(eng)
1673 };
1674 assert_eq!(sighashes_hash.to_string(), "b83a4fe5937a7404");
1675
1676 let signed = unsigned.into_signed_tree(vec![random_sig; nb_nodes]);
1677
1678 encoding_roundtrip(&signed);
1679
1680 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1681 struct JsonSignedVtxoTreeSpec {
1682 #[serde(with = "encode::serde")]
1683 pub spec: SignedVtxoTreeSpec,
1684 }
1685
1686 json_roundtrip(&JsonSignedVtxoTreeSpec { spec: signed.clone() });
1687
1688 for l in 0..nb_leaves {
1689 let exit = signed.exit_branch(l);
1690
1691 let mut iter = exit.iter().enumerate().peekable();
1693 while let Some((i, cur)) = iter.next() {
1694 if let Some((_, next)) = iter.peek() {
1695 assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
1696 }
1697 }
1698 }
1699
1700 let cached = signed.into_cached_tree();
1701 for vtxo in cached.all_vtxos() {
1702 encoding_roundtrip(&vtxo);
1703 }
1704 }
1705
1706 #[test]
1707 fn test_tree_builder() {
1708 let expiry = 100_000;
1709 let exit_delta = 24;
1710
1711 let vtxo_key = Keypair::from_str("985247fb0ef008f8043b6be28add87710d42d482433ef287235bfe041ee6cc11").unwrap();
1712 let policy = VtxoPolicy::new_pubkey(vtxo_key.public_key());
1713 let user_cosign_key = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1714 let user_cosign_pubkey = user_cosign_key.public_key();
1715 println!("user_cosign_pubkey: {}", user_cosign_pubkey);
1716
1717 let server_key = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1718 let server_pubkey = server_key.public_key();
1719 println!("server_pubkey: {}", server_pubkey);
1720
1721 let server_cosign_key = Keypair::from_str("52a506fbae3b725749d2486afd4761841ec685b841c2967e30f24182c4b02eed").unwrap();
1722 let server_cosign_pubkey = server_cosign_key.public_key();
1723 println!("server_cosign_pubkey: {}", server_cosign_pubkey);
1724
1725 let unlock_preimage = rand::random::<UnlockPreimage>();
1726 let unlock_hash = sha256::Hash::hash(&unlock_preimage);
1727 println!("unlock_hash: {}", unlock_hash);
1728
1729 for nb_vtxos in [2, 3, 4, 5, 10, 50] {
1731 println!("building tree with {} vtxos", nb_vtxos);
1732 let vtxos = (0..nb_vtxos).map(|i| VtxoRequest {
1733 amount: Amount::from_sat(1000 * (i + 1)),
1734 policy: policy.clone(),
1735 }).collect::<Vec<_>>();
1736
1737 let builder = SignedTreeBuilder::new(
1738 vtxos.iter().cloned(), user_cosign_pubkey, unlock_preimage, expiry, server_pubkey,
1739 server_cosign_pubkey, exit_delta,
1740 ).unwrap();
1741
1742 let funding_tx = Transaction {
1743 version: transaction::Version::TWO,
1744 lock_time: absolute::LockTime::ZERO,
1745 input: vec![],
1746 output: vec![builder.funding_txout()],
1747 };
1748 let utxo = OutPoint::new(funding_tx.compute_txid(), 0);
1749 let builder = builder.set_utxo(utxo).generate_user_nonces(&user_cosign_key);
1750 let user_pub_nonces = builder.user_pub_nonces().to_vec();
1751
1752 let cosign = {
1753 let builder = SignedTreeBuilder::new_for_cosign(
1754 vtxos.iter().cloned(), user_cosign_pubkey, unlock_preimage, expiry, server_pubkey,
1755 server_cosign_pubkey, exit_delta, utxo, user_pub_nonces,
1756 ).unwrap();
1757 builder.server_cosign(&server_cosign_key)
1758 };
1759
1760 builder.verify_cosign_response(&cosign).unwrap();
1761 let tree = builder.build_tree(&cosign, &user_cosign_key).unwrap().into_cached_tree();
1762
1763 for mut vtxo in tree.all_vtxos() {
1765 {
1766 let mut with_preimage = vtxo.clone();
1768 assert!(with_preimage.provide_unlock_preimage(unlock_preimage));
1769 assert!(with_preimage.validate(&funding_tx).is_err());
1770 }
1771
1772 let (ctx, req) = LeafVtxoCosignContext::new(&vtxo, &funding_tx, &vtxo_key);
1773 let cosign = LeafVtxoCosignResponse::new_cosign(&req, &vtxo, &funding_tx, &server_key);
1774 assert!(ctx.finalize(&mut vtxo, cosign));
1775
1776 assert!(vtxo.validate(&funding_tx).is_err());
1778
1779 assert!(vtxo.provide_unlock_preimage(unlock_preimage));
1780
1781 println!("vtxo debug: {:#?}", vtxo);
1782 println!("vtxo hex: {}", vtxo.serialize_hex());
1783 vtxo.validate(&funding_tx).expect("should be value");
1784 }
1785 }
1786 }
1787
1788 #[test]
1789 fn vtxo_leaf_spec_encoding() {
1790 let pk1: PublicKey = "020aceb65eed0ee5c512d3718e6f4bd868a7efb58ede7899ffd9bcba09555d4eb8".parse().unwrap();
1791 let pk2: PublicKey = "02e4ed0ca35c3b8a2ff675b9b23f4961964b57e130afa607e32a83d2d9a510622b".parse().unwrap();
1792 let hash = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1793
1794 let spec_with_cosign = VtxoLeafSpec {
1796 vtxo: VtxoRequest {
1797 amount: Amount::from_sat(100_000),
1798 policy: VtxoPolicy::new_pubkey(pk1),
1799 },
1800 cosign_pubkey: Some(pk2),
1801 unlock_hash: hash,
1802 };
1803 encoding_roundtrip(&spec_with_cosign);
1804
1805 let spec_without_cosign = VtxoLeafSpec {
1807 vtxo: VtxoRequest {
1808 amount: Amount::from_sat(200_000),
1809 policy: VtxoPolicy::new_pubkey(pk1),
1810 },
1811 cosign_pubkey: None,
1812 unlock_hash: hash,
1813 };
1814 encoding_roundtrip(&spec_without_cosign);
1815 }
1816}