1
2
3use std::{cmp, fmt, io, iter};
4use std::collections::{HashMap, VecDeque};
5
6use bitcoin::hashes::{sha256, Hash};
7use bitcoin::{
8 taproot, Amount, OutPoint, ScriptBuf, Sequence, TapLeafHash, Transaction, TxIn, TxOut, Txid, Weight, Witness
9};
10use bitcoin::secp256k1::{schnorr, Keypair, PublicKey, XOnlyPublicKey};
11use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
12use secp256k1_musig::musig::{AggregatedNonce, PartialSignature, PublicNonce, SecretNonce};
13
14use bitcoin_ext::{fee, BlockDelta, BlockHeight, TaprootSpendInfoExt, TransactionExt, TxOutExt};
15
16use crate::{musig, scripts, ServerVtxoPolicy, Vtxo, VtxoId, VtxoPolicy, VtxoRequest, SECP};
17use crate::encode::{
18 LengthPrefixedVector, OversizedVectorError, ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt
19};
20use crate::error::IncorrectSigningKeyError;
21use crate::tree::{self, Tree};
22use crate::vtxo::{self, Full, GenesisItem, GenesisTransition, HarkLeafVtxoPolicy, MaybePreimage, ServerVtxo, TapScriptClause};
23use crate::vtxo::policy::{check_block_delta, check_block_height, HarkLeaf_v0_VtxoPolicy};
24use crate::vtxo::policy::clause::TimelockSignClause;
25
26
27pub type UnlockHash = sha256::Hash;
29
30pub type UnlockPreimage = [u8; 32];
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub enum HashlockVersion {
42 V0,
43 V1,
44}
45
46pub const NODE_SPEND_WEIGHT: Weight = Weight::from_wu(140);
48
49pub fn expiry_clause(server_pubkey: PublicKey, expiry_height: BlockHeight) -> ScriptBuf {
51 TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }.tapscript()
52}
53
54pub fn unlock_clause(pubkey: XOnlyPublicKey, unlock_hash: UnlockHash) -> ScriptBuf {
58 scripts::hash_and_sign(unlock_hash, pubkey)
59}
60
61pub fn leaf_cosign_taproot(
68 user_pubkey: PublicKey,
69 server_pubkey: PublicKey,
70 expiry_height: BlockHeight,
71 unlock_hash: UnlockHash,
72) -> taproot::TaprootSpendInfo {
73 HarkLeafVtxoPolicy { user_pubkey, unlock_hash }.taproot(server_pubkey, expiry_height)
74}
75
76pub fn unlock_clause_v0(pubkey: XOnlyPublicKey, unlock_hash: UnlockHash) -> ScriptBuf {
80 scripts::hash_and_sign_v0(unlock_hash, pubkey)
81}
82
83pub fn leaf_cosign_taproot_v0(
90 user_pubkey: PublicKey,
91 server_pubkey: PublicKey,
92 expiry_height: BlockHeight,
93 unlock_hash: UnlockHash,
94) -> taproot::TaprootSpendInfo {
95 HarkLeaf_v0_VtxoPolicy { user_pubkey, unlock_hash }.taproot(server_pubkey, expiry_height)
96}
97
98pub fn cosign_taproot(
100 agg_pk: XOnlyPublicKey,
101 server_pubkey: PublicKey,
102 expiry_height: BlockHeight,
103) -> taproot::TaprootSpendInfo {
104 taproot::TaprootBuilder::new()
105 .add_leaf(0, expiry_clause(server_pubkey, expiry_height)).unwrap()
106 .finalize(&SECP, agg_pk).unwrap()
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
110pub struct VtxoLeafSpec {
111 pub vtxo: VtxoRequest,
113
114 pub cosign_pubkey: Option<PublicKey>,
121
122 pub unlock_hash: UnlockHash,
124}
125
126impl ProtocolEncoding for VtxoLeafSpec {
127 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
128 self.vtxo.policy.encode(w)?;
129 w.emit_u64(self.vtxo.amount.to_sat())?;
130 self.cosign_pubkey.encode(w)?;
131 self.unlock_hash.encode(w)?;
132 Ok(())
133 }
134
135 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
136 Ok(VtxoLeafSpec {
137 vtxo: VtxoRequest {
138 policy: VtxoPolicy::decode(r)?,
139 amount: Amount::from_sat(r.read_u64()?),
140 },
141 cosign_pubkey: Option::<PublicKey>::decode(r)?,
142 unlock_hash: sha256::Hash::decode(r)?,
143 })
144 }
145}
146
147#[derive(Debug, Clone, Eq, PartialEq)]
149pub struct VtxoTreeSpec {
150 pub vtxos: Vec<VtxoLeafSpec>,
151 pub expiry_height: BlockHeight,
152 pub server_pubkey: PublicKey,
153 pub exit_delta: BlockDelta,
154 pub global_cosign_pubkeys: Vec<PublicKey>,
155 pub hashlock_version: HashlockVersion,
158}
159
160#[derive(Clone, Copy)]
161enum ChildSpec<'a> {
162 Leaf {
163 spec: &'a VtxoLeafSpec,
164 },
165 Internal {
166 output_value: Amount,
167 agg_pk: PublicKey,
168 },
169}
170
171impl VtxoTreeSpec {
172 pub fn new(
173 vtxos: Vec<VtxoLeafSpec>,
174 server_pubkey: PublicKey,
175 expiry_height: BlockHeight,
176 exit_delta: BlockDelta,
177 global_cosign_pubkeys: Vec<PublicKey>,
178 ) -> VtxoTreeSpec {
179 assert_ne!(vtxos.len(), 0);
180 VtxoTreeSpec {
181 vtxos, server_pubkey, expiry_height, exit_delta, global_cosign_pubkeys,
182 hashlock_version: HashlockVersion::V1,
183 }
184 }
185
186 pub fn nb_leaves(&self) -> usize {
187 self.vtxos.len()
188 }
189
190 pub fn nb_nodes(&self) -> usize {
191 Tree::nb_nodes_for_leaves(self.nb_leaves())
192 }
193
194 pub fn nb_internal_nodes(&self) -> usize {
195 Tree::nb_nodes_for_leaves(self.nb_leaves()).checked_sub(self.nb_leaves())
196 .expect("tree can't have less nodes than leaves")
197 }
198
199 pub fn iter_vtxos(&self) -> impl Iterator<Item = &VtxoLeafSpec> {
200 self.vtxos.iter()
201 }
202
203 pub fn leaf_idx_of(&self, leaf_spec: &VtxoLeafSpec) -> Option<usize> {
205 self.vtxos.iter().position(|e| e == leaf_spec)
206 }
207
208 pub fn leaf_idx_of_req(&self, vtxo_request: &VtxoRequest) -> Option<usize> {
213 self.vtxos.iter().position(|e| e.vtxo == *vtxo_request)
214 }
215
216 pub fn total_required_value(&self) -> Amount {
221 self.vtxos.iter().map(|d| d.vtxo.amount).sum::<Amount>()
222 }
223
224 pub fn leaf_taproot(
226 &self,
227 user_pubkey: PublicKey,
228 unlock_hash: UnlockHash,
229 ) -> taproot::TaprootSpendInfo {
230 match self.hashlock_version {
231 HashlockVersion::V1 => leaf_cosign_taproot(
232 user_pubkey, self.server_pubkey, self.expiry_height, unlock_hash,
233 ),
234 HashlockVersion::V0 => leaf_cosign_taproot_v0(
235 user_pubkey, self.server_pubkey, self.expiry_height, unlock_hash,
236 ),
237 }
238 }
239
240 pub fn internal_taproot(&self, agg_pk: XOnlyPublicKey) -> taproot::TaprootSpendInfo {
242 cosign_taproot(agg_pk, self.server_pubkey, self.expiry_height)
243 }
244
245 pub fn funding_tx_cosign_pubkey(&self) -> XOnlyPublicKey {
249 let keys = self.vtxos.iter()
250 .filter_map(|v| v.cosign_pubkey)
251 .chain(self.global_cosign_pubkeys.iter().copied());
252 musig::combine_keys(keys).x_only_public_key().0
253 }
254
255 pub fn funding_tx_script_pubkey(&self) -> ScriptBuf {
259 let agg_pk = self.funding_tx_cosign_pubkey();
260 self.internal_taproot(agg_pk).script_pubkey()
261 }
262
263 pub fn funding_tx_txout(&self) -> TxOut {
267 TxOut {
268 script_pubkey: self.funding_tx_script_pubkey(),
269 value: self.total_required_value(),
270 }
271 }
272
273 fn node_tx<'a>(
278 &self,
279 children: impl Iterator<Item = ChildSpec<'a>>,
280 ) -> Transaction {
281 Transaction {
282 version: bitcoin::transaction::Version(3),
283 lock_time: bitcoin::absolute::LockTime::ZERO,
284 input: vec![TxIn {
285 previous_output: OutPoint::null(), sequence: Sequence::ZERO,
287 script_sig: ScriptBuf::new(),
288 witness: Witness::new(),
289 }],
290 output: children.map(|child| match child {
291 ChildSpec::Leaf { spec } => {
292 let taproot = self.leaf_taproot(
293 spec.vtxo.policy.user_pubkey(),
294 spec.unlock_hash,
295 );
296 TxOut {
297 script_pubkey: taproot.script_pubkey(),
298 value: spec.vtxo.amount,
299 }
300 },
301 ChildSpec::Internal { output_value, agg_pk } => {
302 let taproot = self.internal_taproot(agg_pk.x_only_public_key().0);
303 TxOut {
304 script_pubkey: taproot.script_pubkey(),
305 value: output_value,
306 }
307 },
308 }).chain(Some(fee::fee_anchor())).collect(),
309 }
310 }
311
312 fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
313 let txout = TxOut {
314 value: vtxo.amount,
315 script_pubkey: vtxo.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height),
316 };
317
318 vtxo::create_exit_tx(OutPoint::null(), txout, None, Amount::ZERO)
322 }
323
324 pub fn cosign_agg_pks(&self)
328 -> impl Iterator<Item = PublicKey> + iter::DoubleEndedIterator + iter::ExactSizeIterator + '_
329 {
330 Tree::new(self.nb_leaves()).into_iter().map(|node| {
331 if node.is_leaf() {
332 musig::combine_keys([
333 self.vtxos[node.idx()].vtxo.policy.user_pubkey(),
334 self.server_pubkey,
335 ])
336 } else {
337 musig::combine_keys(
338 node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey)
339 .chain(self.global_cosign_pubkeys.iter().copied())
340 )
341 }
342 })
343 }
344
345 pub fn unsigned_transactions(&self, utxo: OutPoint) -> Vec<Transaction> {
347 let tree = Tree::new(self.nb_leaves());
348
349 let cosign_agg_pks = self.cosign_agg_pks().collect::<Vec<_>>();
350
351 let mut txs = Vec::<Transaction>::with_capacity(tree.nb_nodes());
352 for node in tree.iter() {
353 let tx = if node.is_leaf() {
354 self.leaf_tx(&self.vtxos[node.idx()].vtxo).clone()
355 } else {
356 let mut buf = [None; tree::RADIX];
357 for (idx, child) in node.children().enumerate() {
358 let child = if let Some(spec) = self.vtxos.get(child) {
359 ChildSpec::Leaf { spec }
360 } else {
361 ChildSpec::Internal {
362 output_value: txs[child].output_value(),
363 agg_pk: cosign_agg_pks[child],
364 }
365 };
366 buf[idx] = Some(child);
367 }
368 self.node_tx(buf.iter().filter_map(|x| *x))
369 };
370 txs.push(tx.clone());
371 };
372
373 txs.last_mut().unwrap().input[0].previous_output = utxo;
375 for node in tree.iter().rev() {
376 let txid = txs[node.idx()].compute_txid();
377 for (i, child) in node.children().enumerate() {
378 let point = OutPoint::new(txid, u32::try_from(i).expect("tree child index fits in u32"));
379 txs[child].input[0].previous_output = point;
380 }
381 }
382
383 txs
384 }
385
386 pub fn final_transactions(
390 &self,
391 utxo: OutPoint,
392 internal_signatures: &[schnorr::Signature],
393 ) -> Vec<Transaction> {
394 let mut txs = self.unsigned_transactions(utxo);
395 for (tx, sig) in txs.iter_mut().skip(self.nb_leaves()).zip(internal_signatures) {
396 tx.input[0].witness.push(&sig[..]);
397 }
398 txs
399 }
400
401 pub fn calculate_cosign_agg_nonces(
405 &self,
406 leaf_cosign_nonces: &HashMap<PublicKey, Vec<PublicNonce>>,
407 global_signer_cosign_nonces: &[impl AsRef<[PublicNonce]>],
408 ) -> Result<Vec<AggregatedNonce>, String> {
409 if global_signer_cosign_nonces.len() != self.global_cosign_pubkeys.len() {
410 return Err("missing global signer nonces".into());
411 }
412
413 Tree::new(self.nb_leaves()).iter_internal().enumerate().map(|(idx, node)| {
414 let mut nonces = Vec::new();
415 for pk in node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey) {
416 nonces.push(leaf_cosign_nonces.get(&pk)
417 .ok_or_else(|| format!("missing nonces for leaf pk {}", pk))?
418 .get(node.internal_level())
421 .ok_or_else(|| format!("not enough nonces for leaf_pk {}", pk))?
422 );
423 }
424 for glob in global_signer_cosign_nonces {
425 nonces.push(glob.as_ref().get(idx).ok_or("not enough global cosign nonces")?);
426 }
427 Ok(musig::nonce_agg(&nonces))
428 }).collect()
429 }
430
431 pub fn into_unsigned_tree(
436 self,
437 utxo: OutPoint,
438 ) -> UnsignedVtxoTree {
439 UnsignedVtxoTree::new(self, utxo)
440 }
441}
442
443#[derive(Debug, Clone)]
447pub struct UnsignedVtxoTree {
448 pub spec: VtxoTreeSpec,
449 pub utxo: OutPoint,
450
451 pub cosign_agg_pks: Vec<PublicKey>,
455 pub txs: Vec<Transaction>,
457 pub internal_sighashes: Vec<TapSighash>,
460
461 tree: Tree,
462}
463
464impl UnsignedVtxoTree {
465 pub fn new(
466 spec: VtxoTreeSpec,
467 utxo: OutPoint,
468 ) -> UnsignedVtxoTree {
469 let tree = Tree::new(spec.nb_leaves());
470
471 let cosign_agg_pks = spec.cosign_agg_pks().collect::<Vec<_>>();
472 let txs = spec.unsigned_transactions(utxo);
473
474 let root_txout = spec.funding_tx_txout();
475 let internal_sighashes = tree.iter_internal().map(|node| {
476 let prev = if let Some((parent, sibling_idx))
477 = tree.parent_idx_of_with_sibling_idx(node.idx())
478 {
479 assert!(!node.is_root());
480 &txs[parent].output[sibling_idx]
481 } else {
482 assert!(node.is_root());
483 &root_txout
484 };
485
486 let mut shc = SighashCache::new(&txs[node.idx()]);
487 shc.taproot_key_spend_signature_hash(
488 0, &sighash::Prevouts::All(&[prev]),
490 TapSighashType::Default,
491 ).expect("sighash error")
492 }).collect();
493
494 UnsignedVtxoTree { spec, utxo, txs, internal_sighashes, cosign_agg_pks, tree }
495 }
496
497 pub fn nb_leaves(&self) -> usize {
498 self.tree.nb_leaves()
499 }
500
501 pub fn nb_cosigned_leaves(&self) -> usize {
503 self.spec.vtxos.iter()
504 .filter(|v| v.cosign_pubkey.is_some())
505 .count()
506 }
507
508 pub fn nb_nodes(&self) -> usize {
509 self.tree.nb_nodes()
510 }
511
512 pub fn nb_internal_nodes(&self) -> usize {
513 self.tree.nb_internal_nodes()
514 }
515
516 pub fn cosign_branch(
529 &self,
530 cosign_agg_nonces: &[AggregatedNonce],
531 leaf_idx: usize,
532 cosign_key: &Keypair,
533 cosign_sec_nonces: Vec<SecretNonce>,
534 ) -> Result<Vec<PartialSignature>, IncorrectSigningKeyError> {
535 let req = self.spec.vtxos.get(leaf_idx).expect("leaf idx out of bounds");
536 if Some(cosign_key.public_key()) != req.cosign_pubkey {
537 return Err(IncorrectSigningKeyError {
538 required: req.cosign_pubkey,
539 provided: cosign_key.public_key(),
540 });
541 }
542
543 let mut nonce_iter = cosign_sec_nonces.into_iter().enumerate();
544 let mut ret = Vec::with_capacity(self.tree.root().level().saturating_add(1));
545 for node in self.tree.iter_branch(leaf_idx).skip(1) {
547 let sec_nonce = loop {
552 let next = nonce_iter.next().expect("level overflow");
553 if next.0 == node.internal_level() {
554 break next.1;
555 }
556 };
557
558 let cosign_pubkeys = node.leaves()
559 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
560 .chain(self.spec.global_cosign_pubkeys.iter().copied());
561 let sighash = self.internal_sighashes[node.internal_idx()];
562
563 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
564 let tweak = self.spec.internal_taproot(agg_pk).tap_tweak().to_byte_array();
565 let sig = musig::partial_sign(
566 cosign_pubkeys,
567 cosign_agg_nonces[node.internal_idx()],
568 &cosign_key,
569 sec_nonce,
570 sighash.to_byte_array(),
571 Some(tweak),
572 None,
573 ).0;
574 ret.push(sig);
575 }
576
577 Ok(ret)
578 }
579
580 pub fn cosign_tree(
586 &self,
587 cosign_agg_nonces: &[AggregatedNonce],
588 keypair: &Keypair,
589 cosign_sec_nonces: Vec<SecretNonce>,
590 ) -> Vec<PartialSignature> {
591 debug_assert_eq!(cosign_agg_nonces.len(), self.nb_internal_nodes());
592 debug_assert_eq!(cosign_sec_nonces.len(), self.nb_internal_nodes());
593
594 let nonces = cosign_sec_nonces.into_iter().zip(cosign_agg_nonces);
595 self.tree.iter_internal().zip(nonces).map(|(node, (sec_nonce, agg_nonce))| {
596 let sighash = self.internal_sighashes[node.internal_idx()];
597
598 let cosign_pubkeys = node.leaves()
599 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
600 .chain(self.spec.global_cosign_pubkeys.iter().copied());
601 let agg_pk = self.cosign_agg_pks[node.idx()];
602 debug_assert_eq!(agg_pk, musig::combine_keys(cosign_pubkeys.clone()));
603 let taproot = self.spec.internal_taproot(agg_pk.x_only_public_key().0);
604 musig::partial_sign(
605 cosign_pubkeys,
606 *agg_nonce,
607 &keypair,
608 sec_nonce,
609 sighash.to_byte_array(),
610 Some(taproot.tap_tweak().to_byte_array()),
611 None,
612 ).0
613 }).collect()
614 }
615
616 fn verify_internal_node_cosign_partial_sig(
618 &self,
619 node: &tree::Node,
620 pk: PublicKey,
621 agg_nonces: &[AggregatedNonce],
622 part_sig: PartialSignature,
623 pub_nonce: PublicNonce,
624 ) -> Result<(), CosignSignatureError> {
625 debug_assert!(!node.is_leaf());
626
627 let sighash = self.internal_sighashes[node.internal_idx()];
628
629 let key_agg = {
630 let cosign_pubkeys = node.leaves()
631 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
632 .chain(self.spec.global_cosign_pubkeys.iter().copied());
633 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
634 let taproot = self.spec.internal_taproot(agg_pk);
635 let taptweak = taproot.tap_tweak().to_byte_array();
636 musig::tweaked_key_agg(cosign_pubkeys, taptweak).0
637 };
638 let agg_nonce = agg_nonces.get(node.internal_idx())
639 .ok_or(CosignSignatureError::NotEnoughNonces)?;
640 let session = musig::Session::new(&key_agg, *agg_nonce, &sighash.to_byte_array());
641 let ok = session.partial_verify(&key_agg, &part_sig, &pub_nonce, musig::pubkey_to(pk));
642 if !ok {
643 return Err(CosignSignatureError::invalid_sig(pk));
644 }
645 Ok(())
646 }
647
648 pub fn verify_branch_cosign_partial_sigs(
653 &self,
654 cosign_agg_nonces: &[AggregatedNonce],
655 request: &VtxoLeafSpec,
656 cosign_pub_nonces: &[PublicNonce],
657 cosign_part_sigs: &[PartialSignature],
658 ) -> Result<(), String> {
659 assert_eq!(cosign_agg_nonces.len(), self.nb_internal_nodes());
660
661 let cosign_pubkey = request.cosign_pubkey.ok_or("no cosign pubkey for request")?;
662 let leaf_idx = self.spec.leaf_idx_of(request).ok_or("request not in tree")?;
663
664 let internal_branch = self.tree.iter_branch(leaf_idx).skip(1);
666
667 match internal_branch.clone().count().cmp(&cosign_part_sigs.len()) {
669 cmp::Ordering::Less => return Err("too few partial signatures".into()),
670 cmp::Ordering::Greater => return Err("too many partial signatures".into()),
671 cmp::Ordering::Equal => {},
672 }
673
674 let mut part_sigs_iter = cosign_part_sigs.iter();
675 let mut pub_nonce_iter = cosign_pub_nonces.iter().enumerate();
676 for node in internal_branch {
677 let pub_nonce = loop {
678 let next = pub_nonce_iter.next().ok_or("not enough pub nonces")?;
679 if next.0 == node.internal_level() {
680 break next.1;
681 }
682 };
683 self.verify_internal_node_cosign_partial_sig(
684 node,
685 cosign_pubkey,
686 cosign_agg_nonces,
687 part_sigs_iter.next().ok_or("not enough sigs")?.clone(),
688 *pub_nonce,
689 ).map_err(|e| format!("part sig verification failed: {}", e))?;
690 }
691
692 Ok(())
693 }
694
695 pub fn verify_global_cosign_partial_sigs(
700 &self,
701 pk: PublicKey,
702 agg_nonces: &[AggregatedNonce],
703 pub_nonces: &[PublicNonce],
704 part_sigs: &[PartialSignature],
705 ) -> Result<(), CosignSignatureError> {
706 for node in self.tree.iter_internal() {
707 let sigs = *part_sigs.get(node.internal_idx())
708 .ok_or_else(|| CosignSignatureError::missing_sig(pk))?;
709 let nonces = *pub_nonces.get(node.internal_idx())
710 .ok_or_else(|| CosignSignatureError::NotEnoughNonces)?;
711 self.verify_internal_node_cosign_partial_sig(node, pk, agg_nonces, sigs, nonces)?;
712 }
713
714 Ok(())
715 }
716
717 pub fn combine_partial_signatures(
726 &self,
727 cosign_agg_nonces: &[AggregatedNonce],
728 branch_part_sigs: &HashMap<PublicKey, Vec<PartialSignature>>,
729 global_signer_part_sigs: &[impl AsRef<[PartialSignature]>],
730 ) -> Result<Vec<schnorr::Signature>, CosignSignatureError> {
731 let mut leaf_part_sigs = branch_part_sigs.iter()
733 .map(|(pk, sigs)| (pk, sigs.iter().collect()))
734 .collect::<HashMap<_, VecDeque<_>>>();
735
736 if global_signer_part_sigs.len() != self.spec.global_cosign_pubkeys.len() {
737 return Err(CosignSignatureError::Invalid(
738 "invalid nb of global cosigner partial signatures",
739 ));
740 }
741 for (pk, sigs) in self.spec.global_cosign_pubkeys.iter().zip(global_signer_part_sigs) {
742 if sigs.as_ref().len() != self.nb_internal_nodes() {
743 return Err(CosignSignatureError::MissingSignature { pk: *pk });
746 }
747 }
748
749 let max_level = match self.tree.root().is_leaf() {
750 true => 0,
751 false => self.tree.root().internal_level(),
752 };
753 self.tree.iter_internal().map(|node| {
754 let mut cosign_pks = Vec::with_capacity(max_level.saturating_add(1));
755 let mut part_sigs = Vec::with_capacity(max_level.saturating_add(1));
756 for leaf in node.leaves() {
757 if let Some(cosign_pk) = self.spec.vtxos[leaf].cosign_pubkey {
758 let part_sig = leaf_part_sigs.get_mut(&cosign_pk)
759 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?
760 .pop_front()
761 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?;
762 cosign_pks.push(cosign_pk);
763 part_sigs.push(part_sig);
764 }
765 }
766 cosign_pks.extend(&self.spec.global_cosign_pubkeys);
768 for sigs in global_signer_part_sigs {
769 part_sigs.push(sigs.as_ref().get(node.internal_idx()).expect("checked before"));
770 }
771
772 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
773 let taproot = self.spec.internal_taproot(agg_pk);
774 let agg_nonce = *cosign_agg_nonces.get(node.internal_idx())
775 .ok_or(CosignSignatureError::NotEnoughNonces)?;
776 let sighash = self.internal_sighashes[node.internal_idx()].to_byte_array();
777 let tweak = taproot.tap_tweak().to_byte_array();
778 Ok(musig::combine_partial_signatures(
779 cosign_pks, agg_nonce, sighash, Some(tweak), &part_sigs,
780 ))
781 }).collect()
782 }
783
784 pub fn verify_cosign_sigs(
788 &self,
789 signatures: &[schnorr::Signature],
790 ) -> Result<(), XOnlyPublicKey> {
791 for node in self.tree.iter_internal() {
792 let sighash = self.internal_sighashes[node.internal_idx()];
793 let agg_pk = &self.cosign_agg_pks[node.idx()].x_only_public_key().0;
794 let pk = self.spec.internal_taproot(*agg_pk).output_key().to_x_only_public_key();
795 let sig = signatures.get(node.internal_idx()).ok_or_else(|| pk)?;
796 if SECP.verify_schnorr(sig, &sighash.into(), &pk).is_err() {
797 return Err(pk);
798 }
799 }
800 Ok(())
801 }
802
803 pub fn into_signed_tree(
807 self,
808 signatures: Vec<schnorr::Signature>,
809 ) -> SignedVtxoTreeSpec {
810 SignedVtxoTreeSpec {
811 spec: self.spec,
812 utxo: self.utxo,
813 cosign_sigs: signatures,
814 }
815 }
816}
817
818#[derive(PartialEq, Eq, thiserror::Error)]
820pub enum CosignSignatureError {
821 #[error("missing cosign signature from pubkey {pk}")]
822 MissingSignature { pk: PublicKey },
823 #[error("invalid cosign signature from pubkey {pk}")]
824 InvalidSignature { pk: PublicKey },
825 #[error("not enough nonces")]
826 NotEnoughNonces,
827 #[error("invalid cosign signatures: {0}")]
828 Invalid(&'static str),
829}
830
831impl fmt::Debug for CosignSignatureError {
832 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
833 fmt::Display::fmt(self, f)
834 }
835}
836
837impl CosignSignatureError {
838 fn missing_sig(cosign_pk: PublicKey) -> CosignSignatureError {
839 CosignSignatureError::MissingSignature { pk: cosign_pk }
840 }
841 fn invalid_sig(cosign_pk: PublicKey) -> CosignSignatureError {
842 CosignSignatureError::InvalidSignature { pk: cosign_pk }
843 }
844}
845
846#[derive(Debug, Clone, PartialEq)]
848pub struct SignedVtxoTreeSpec {
849 pub spec: VtxoTreeSpec,
850 pub utxo: OutPoint,
851 pub cosign_sigs: Vec<schnorr::Signature>,
853}
854
855impl SignedVtxoTreeSpec {
856 pub fn new(
858 spec: VtxoTreeSpec,
859 utxo: OutPoint,
860 cosign_signatures: Vec<schnorr::Signature>,
861 ) -> SignedVtxoTreeSpec {
862 SignedVtxoTreeSpec { spec, utxo, cosign_sigs: cosign_signatures }
863 }
864
865 pub fn nb_leaves(&self) -> usize {
866 self.spec.nb_leaves()
867 }
868
869 pub fn exit_branch(&self, leaf_idx: usize) -> Vec<Transaction> {
876 let txs = self.all_final_txs();
877 let tree = Tree::new(self.spec.nb_leaves());
878 let mut ret = tree.iter_branch(leaf_idx)
879 .map(|n| txs[n.idx()].clone())
880 .collect::<Vec<_>>();
881 ret.reverse();
882 ret
883 }
884
885 pub fn all_final_txs(&self) -> Vec<Transaction> {
887 self.spec.final_transactions(self.utxo, &self.cosign_sigs)
888 }
889
890 pub fn into_cached_tree(self) -> CachedSignedVtxoTree {
891 CachedSignedVtxoTree {
892 txs: self.all_final_txs(),
893 spec: self,
894 }
895 }
896
897 pub fn detect_hashlock_version(&self) -> Result<HashlockVersion, HashlockVersionDetectError> {
904 if self.spec.nb_leaves() == 1 {
905 return Ok(HashlockVersion::V1);
906 }
907 let sig = self.cosign_sigs.first().ok_or(HashlockVersionDetectError)?;
908
909 for version in [HashlockVersion::V1, HashlockVersion::V0] {
910 let mut spec = self.spec.clone();
911 spec.hashlock_version = version;
912 let unsigned = spec.into_unsigned_tree(self.utxo);
913 let sighash = unsigned.internal_sighashes[0];
914 let agg_pk = unsigned.cosign_agg_pks[unsigned.nb_leaves()].x_only_public_key().0;
915 let pk = unsigned.spec.internal_taproot(agg_pk).output_key().to_x_only_public_key();
916 if SECP.verify_schnorr(sig, &sighash.into(), &pk).is_ok() {
917 return Ok(version);
918 }
919 }
920 Err(HashlockVersionDetectError)
921 }
922
923 pub fn with_detected_hashlock_version(mut self) -> Result<Self, HashlockVersionDetectError> {
930 self.spec.hashlock_version = self.detect_hashlock_version()?;
931 Ok(self)
932 }
933}
934
935#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
938#[error("tree cosign signatures don't verify for any hashlock version")]
939pub struct HashlockVersionDetectError;
940
941pub struct CachedSignedVtxoTree {
945 pub spec: SignedVtxoTreeSpec,
946 pub txs: Vec<Transaction>,
948}
949
950impl CachedSignedVtxoTree {
951 pub fn exit_branch(&self, leaf_idx: usize) -> Vec<&Transaction> {
955 let tree = Tree::new(self.spec.spec.nb_leaves());
956 let mut ret = tree.iter_branch(leaf_idx)
957 .map(|n| &self.txs[n.idx()])
958 .collect::<Vec<_>>();
959 ret.reverse();
960 ret
961 }
962
963 pub fn nb_leaves(&self) -> usize {
964 self.spec.nb_leaves()
965 }
966
967 pub fn nb_nodes(&self) -> usize {
968 Tree::nb_nodes_for_leaves(self.spec.nb_leaves())
969 }
970
971 pub fn all_final_txs(&self) -> &[Transaction] {
976 &self.txs
977 }
978
979 pub fn unsigned_leaf_txs(&self) -> &[Transaction] {
983 &self.txs[..self.nb_leaves()]
984 }
985
986 pub fn internal_node_txs(&self) -> &[Transaction] {
990 &self.txs[self.nb_leaves()..]
991 }
992
993 fn build_genesis_item_at<'a>(&self, tree: &Tree, node_idx: usize, output_idx: u8) -> GenesisItem {
995 debug_assert_eq!(self.nb_leaves(), tree.nb_leaves(), "tree corresponds to self");
996 debug_assert!(node_idx < tree.nb_nodes(), "Node index is in tree");
997
998 let other_outputs = self.txs.get(node_idx).expect("Each node has a tx")
999 .output.iter().enumerate()
1000 .filter(|(i, _)| *i != output_idx as usize) .filter(|(_, out)| !out.is_p2a_fee_anchor()) .map(|(_, out)| out)
1003 .cloned()
1004 .collect();
1005
1006 let node = tree.node_at(node_idx);
1007
1008 let transition = if node.is_leaf() {
1009 debug_assert_eq!(output_idx, 0, "Leafs have a single output");
1010 let req = self.spec.spec.vtxos.get(node_idx).expect("Every leaf has a spec");
1011 match self.spec.spec.hashlock_version {
1012 HashlockVersion::V1 => GenesisTransition::new_hash_locked_cosigned(
1013 req.vtxo.policy.user_pubkey(),
1014 None,
1015 MaybePreimage::Hash(req.unlock_hash),
1016 ),
1017 HashlockVersion::V0 => GenesisTransition::new_hash_locked_cosigned_v0(
1018 req.vtxo.policy.user_pubkey(),
1019 None,
1020 MaybePreimage::Hash(req.unlock_hash),
1021 ),
1022 }
1023 } else {
1024 let pubkeys = node.leaves()
1025 .filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
1026 .chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
1027 .collect();
1028
1029 let sig = self.spec.cosign_sigs.get(node.internal_idx())
1030 .expect("enough sigs for all nodes");
1031
1032 GenesisTransition::new_cosigned(pubkeys, Some(*sig))
1033 };
1034
1035 let fee_amount = Amount::ZERO;
1036 GenesisItem {transition, output_idx, other_outputs, fee_amount }
1037 }
1038
1039 fn build_internal_vtxo(&self, node_idx: usize) -> ServerVtxo<Full> {
1045 let tree = Tree::new(self.spec.spec.nb_leaves());
1046 assert!(node_idx < tree.nb_nodes(), "node_idx out of range");
1047
1048 let mut genesis = tree.iter_branch_with_output(node_idx)
1049 .map(|(idx, child_idx)| self.build_genesis_item_at(&tree, idx, u8::try_from(child_idx).expect("tree child index fits in u8")))
1050 .collect::<Vec<_>>();
1051 genesis.reverse();
1052
1053 let node = tree.node_at(node_idx);
1054 let spec = &self.spec.spec;
1055 let (point, amount) = match tree.parent_idx_of_with_sibling_idx(node_idx) {
1056 None => (self.spec.utxo, spec.total_required_value()),
1057 Some((parent_idx, child_idx)) => {
1058 let parent_tx = self.txs.get(parent_idx).expect("parent tx exists");
1059 let point = OutPoint::new(parent_tx.compute_txid(), u32::try_from(child_idx).expect("tree child index fits in u32"));
1060 (point, parent_tx.output[child_idx].value)
1061 }
1062 };
1063
1064 let policy = if node.is_leaf() {
1065 let req = spec.vtxos.get(node_idx).expect("one vtxo request for every leaf");
1066 match spec.hashlock_version {
1067 HashlockVersion::V1 => ServerVtxoPolicy::new_hark_leaf(
1068 req.vtxo.policy.user_pubkey(), req.unlock_hash,
1069 ),
1070 HashlockVersion::V0 => ServerVtxoPolicy::new_hark_leaf_v0(
1071 req.vtxo.policy.user_pubkey(), req.unlock_hash,
1072 ),
1073 }
1074 } else {
1075 let agg_pk = musig::combine_keys(
1076 node.leaves().filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
1077 .chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
1078 );
1079 ServerVtxoPolicy::new_expiry(agg_pk.x_only_public_key().0)
1080 };
1081
1082 ServerVtxo {
1083 policy,
1084 amount,
1085 expiry_height: self.spec.spec.expiry_height,
1086 server_pubkey: self.spec.spec.server_pubkey,
1087 exit_delta: self.spec.spec.exit_delta,
1088 anchor_point: self.spec.utxo,
1089 genesis: Full { items: genesis },
1090 point,
1091 }
1092 }
1093
1094 pub fn build_vtxo(&self, leaf_idx: usize) -> Vtxo<Full> {
1098 let req = self.spec.spec.vtxos.get(leaf_idx).expect("index is not a leaf");
1099
1100 let genesis = {
1101 let tree = Tree::new(self.spec.spec.nb_leaves());
1102 let leaf = self.build_genesis_item_at(&tree, leaf_idx, 0);
1103 let internal = tree.iter_branch_with_output(leaf_idx)
1104 .map(|(node_idx, child_idx)| {
1105 self.build_genesis_item_at(&tree, node_idx, u8::try_from(child_idx).expect("tree child index fits in u8"))
1106 });
1107
1108 let mut genesis = [leaf].into_iter().chain(internal).collect::<Vec<_>>();
1109 genesis.reverse();
1110 genesis
1111 };
1112
1113 Vtxo {
1114 amount: req.vtxo.amount,
1115 expiry_height: self.spec.spec.expiry_height,
1116 server_pubkey: self.spec.spec.server_pubkey,
1117 exit_delta: self.spec.spec.exit_delta,
1118 anchor_point: self.spec.utxo,
1119 genesis: Full { items: genesis },
1120 policy: req.vtxo.policy.clone(),
1121 point: {
1122 let leaf_tx = self.txs.get(leaf_idx).expect("leaf idx exists");
1123 OutPoint::new(leaf_tx.compute_txid(), 0)
1124 },
1125 }
1126 }
1127
1128 pub fn internal_vtxos(&self) -> impl Iterator<Item = (ServerVtxo<Full>, Txid)> + '_ {
1131 (0..self.nb_nodes()).map(|idx| {
1132 let vtxo = self.build_internal_vtxo(idx);
1133 let spending_txid = self.txs[idx].compute_txid();
1134 (vtxo, spending_txid)
1135 })
1136 }
1137
1138 pub fn output_vtxos(&self) -> impl Iterator<Item = Vtxo<Full>> + ExactSizeIterator + '_ {
1140 (0..self.nb_leaves()).map(|idx| self.build_vtxo(idx))
1141 }
1142
1143 pub fn spend_info(&self) -> impl Iterator<Item = (VtxoId, Txid)> + '_ {
1144 self.internal_vtxos()
1145 .map(|(vtxo, spending_txid)| (vtxo.id(), spending_txid))
1146 }
1147}
1148
1149pub fn hashlocked_leaf_sighash(
1151 leaf_tx: &Transaction,
1152 user_pubkey: PublicKey,
1153 server_pubkey: PublicKey,
1154 unlock_hash: UnlockHash,
1155 prev_txout: &TxOut,
1156) -> TapSighash {
1157 let agg_pk = musig::combine_keys([user_pubkey, server_pubkey])
1158 .x_only_public_key().0;
1159 let clause = unlock_clause(agg_pk, unlock_hash);
1160 let leaf_hash = TapLeafHash::from_script(&clause, bitcoin::taproot::LeafVersion::TapScript);
1161 let mut shc = SighashCache::new(leaf_tx);
1162 shc.taproot_script_spend_signature_hash(
1163 0, &sighash::Prevouts::All(&[prev_txout]),
1165 leaf_hash,
1166 TapSighashType::Default,
1167 ).expect("sighash error")
1168}
1169
1170pub fn hashlocked_leaf_sighash_v0(
1172 leaf_tx: &Transaction,
1173 user_pubkey: PublicKey,
1174 server_pubkey: PublicKey,
1175 unlock_hash: UnlockHash,
1176 prev_txout: &TxOut,
1177) -> TapSighash {
1178 let agg_pk = musig::combine_keys([user_pubkey, server_pubkey])
1179 .x_only_public_key().0;
1180 let clause = unlock_clause_v0(agg_pk, unlock_hash);
1181 let leaf_hash = TapLeafHash::from_script(&clause, bitcoin::taproot::LeafVersion::TapScript);
1182 let mut shc = SighashCache::new(leaf_tx);
1183 shc.taproot_script_spend_signature_hash(
1184 0, &sighash::Prevouts::All(&[prev_txout]),
1186 leaf_hash,
1187 TapSighashType::Default,
1188 ).expect("sighash error")
1189}
1190
1191fn hashlocked_leaf_sighash_from_vtxo(
1197 vtxo: &Vtxo<Full>,
1198 chain_anchor: &Transaction,
1199) -> TapSighash {
1200 assert_eq!(chain_anchor.compute_txid(), vtxo.chain_anchor().txid);
1201
1202 let mut preleaf_txout = chain_anchor.output[vtxo.chain_anchor().vout as usize].clone();
1204 let mut leaf_tx = None;
1205 let mut peekable_iter = vtxo.transactions().peekable();
1206 while let Some(item) = peekable_iter.next() {
1207 if peekable_iter.peek().is_some() {
1210 preleaf_txout = item.tx.output[item.output_idx].clone();
1211 }
1212
1213 if peekable_iter.peek().is_none() {
1215 leaf_tx = Some(item.tx);
1216 }
1217 }
1218 let leaf_tx = leaf_tx.expect("at least one tx");
1219
1220 let last_genesis = vtxo.genesis.items.last().expect("at least one genesis item");
1221 match &last_genesis.transition {
1222 GenesisTransition::HashLockedCosigned(inner) => {
1223 debug_assert_eq!(inner.user_pubkey, vtxo.user_pubkey());
1224 hashlocked_leaf_sighash(
1225 &leaf_tx, inner.user_pubkey, vtxo.server_pubkey(), inner.unlock.hash(),
1226 &preleaf_txout,
1227 )
1228 },
1229 GenesisTransition::HashLockedCosigned_v0(inner) => {
1230 debug_assert_eq!(inner.user_pubkey, vtxo.user_pubkey());
1231 hashlocked_leaf_sighash_v0(
1232 &leaf_tx, inner.user_pubkey, vtxo.server_pubkey(), inner.unlock.hash(),
1233 &preleaf_txout,
1234 )
1235 },
1236 _ => panic!("VTXO is not a HashLockedCosigned VTXO"),
1237 }
1238}
1239
1240#[derive(Debug)]
1241pub struct LeafVtxoCosignRequest {
1242 pub vtxo_id: VtxoId,
1243 pub pub_nonce: musig::PublicNonce,
1244}
1245
1246pub struct LeafVtxoCosignContext<'a> {
1247 key: &'a Keypair,
1248 pub_nonce: musig::PublicNonce,
1249 sec_nonce: musig::SecretNonce,
1250 sighash: TapSighash,
1251}
1252
1253impl<'a> LeafVtxoCosignContext<'a> {
1254 pub fn new(
1259 vtxo: &Vtxo<Full>,
1260 chain_anchor: &Transaction,
1261 key: &'a Keypair,
1262 ) -> (Self, LeafVtxoCosignRequest) {
1263 let sighash = hashlocked_leaf_sighash_from_vtxo(&vtxo, chain_anchor);
1264 let (sec_nonce, pub_nonce) = musig::nonce_pair_with_msg(key, &sighash.to_byte_array());
1265 let vtxo_id = vtxo.id();
1266 let req = LeafVtxoCosignRequest { vtxo_id, pub_nonce };
1267 let ret = Self { key, pub_nonce, sec_nonce, sighash };
1268 (ret, req)
1269 }
1270
1271 pub fn finalize(
1273 self,
1274 vtxo: &mut Vtxo<Full>,
1275 response: LeafVtxoCosignResponse,
1276 ) -> bool {
1277 let agg_nonce = musig::nonce_agg(&[&self.pub_nonce, &response.public_nonce]);
1278 let (_part_sig, final_sig) = musig::partial_sign(
1279 [vtxo.user_pubkey(), vtxo.server_pubkey()],
1280 agg_nonce,
1281 self.key,
1282 self.sec_nonce,
1283 self.sighash.to_byte_array(),
1284 None,
1285 Some(&[&response.partial_signature]),
1286 );
1287 let final_sig = final_sig.expect("has other sigs");
1288
1289 let pubkey = musig::combine_keys([vtxo.user_pubkey(), vtxo.server_pubkey()])
1290 .x_only_public_key().0;
1291 debug_assert_eq!(pubkey, leaf_cosign_taproot(
1292 vtxo.user_pubkey(),
1293 vtxo.server_pubkey(),
1294 vtxo.expiry_height(),
1295 vtxo.unlock_hash().expect("checked is hark vtxo"),
1296 ).internal_key());
1297 if SECP.verify_schnorr(&final_sig, &self.sighash.into(), &pubkey).is_err() {
1298 return false;
1299 }
1300
1301 vtxo.provide_unlock_signature(final_sig)
1302 }
1303}
1304
1305#[derive(Debug)]
1306pub struct LeafVtxoCosignResponse {
1307 pub public_nonce: musig::PublicNonce,
1308 pub partial_signature: musig::PartialSignature,
1309}
1310
1311impl LeafVtxoCosignResponse {
1312 pub fn new_cosign(
1314 request: &LeafVtxoCosignRequest,
1315 vtxo: &Vtxo<Full>,
1316 chain_anchor: &Transaction,
1317 server_key: &Keypair,
1318 ) -> Self {
1319 debug_assert_eq!(server_key.public_key(), vtxo.server_pubkey());
1320 let sighash = hashlocked_leaf_sighash_from_vtxo(&vtxo, chain_anchor);
1321 let (public_nonce, partial_signature) = musig::deterministic_partial_sign(
1322 server_key,
1323 [vtxo.user_pubkey()],
1324 &[&request.pub_nonce],
1325 sighash.to_byte_array(),
1326 None,
1327 );
1328 Self { public_nonce, partial_signature }
1329 }
1330}
1331
1332pub mod builder {
1333 use std::collections::HashMap;
1340 use std::marker::PhantomData;
1341
1342 use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut};
1343 use bitcoin::hashes::{sha256, Hash};
1344 use bitcoin::secp256k1::{Keypair, PublicKey};
1345 use bitcoin_ext::{BlockDelta, BlockHeight};
1346
1347 use crate::tree::signed::{UnlockHash, UnlockPreimage, VtxoLeafSpec};
1348 use crate::{musig, VtxoRequest};
1349 use crate::error::IncorrectSigningKeyError;
1350
1351 use super::{CosignSignatureError, SignedVtxoTreeSpec, UnsignedVtxoTree, VtxoTreeSpec};
1352
1353 pub mod state {
1354 mod sealed {
1355 pub trait Sealed {}
1357 impl Sealed for super::Preparing {}
1358 impl Sealed for super::CanGenerateNonces {}
1359 impl Sealed for super::ServerCanCosign {}
1360 impl Sealed for super::CanFinish {}
1361 }
1362
1363 pub trait BuilderState: sealed::Sealed {}
1365
1366 pub struct Preparing;
1368 impl BuilderState for Preparing {}
1369
1370 pub struct CanGenerateNonces;
1373 impl BuilderState for CanGenerateNonces {}
1374
1375 pub struct ServerCanCosign;
1377 impl BuilderState for ServerCanCosign {}
1378
1379 pub struct CanFinish;
1382 impl BuilderState for CanFinish {}
1383
1384 pub trait CanSign: BuilderState {}
1387 impl CanSign for ServerCanCosign {}
1388 impl CanSign for CanFinish {}
1389 }
1390
1391 enum BuilderTree {
1393 Spec(VtxoTreeSpec),
1394 Unsigned(UnsignedVtxoTree),
1395 }
1396
1397 impl BuilderTree {
1398 fn unsigned_tree(&self) -> Option<&UnsignedVtxoTree> {
1399 match self {
1400 BuilderTree::Spec(_) => None,
1401 BuilderTree::Unsigned(t) => Some(t),
1402 }
1403 }
1404
1405 fn into_unsigned_tree(self) -> Option<UnsignedVtxoTree> {
1406 match self {
1407 BuilderTree::Spec(_) => None,
1408 BuilderTree::Unsigned(t) => Some(t),
1409 }
1410 }
1411 }
1412
1413 pub struct SignedTreeBuilder<S: state::BuilderState> {
1417 pub expiry_height: BlockHeight,
1418 pub server_pubkey: PublicKey,
1419 pub exit_delta: BlockDelta,
1420 pub cosign_pubkey: PublicKey,
1422 pub unlock_preimage: UnlockPreimage,
1424
1425 tree: BuilderTree,
1426
1427 user_pub_nonces: Vec<musig::PublicNonce>,
1429 user_sec_nonces: Option<Vec<musig::SecretNonce>>,
1432 _state: PhantomData<S>,
1433 }
1434
1435 impl<T: state::BuilderState> SignedTreeBuilder<T> {
1436 fn tree_spec(&self) -> &VtxoTreeSpec {
1437 match self.tree {
1438 BuilderTree::Spec(ref s) => s,
1439 BuilderTree::Unsigned(ref t) => &t.spec,
1440 }
1441 }
1442
1443 pub fn total_required_value(&self) -> Amount {
1445 self.tree_spec().total_required_value()
1446 }
1447
1448 pub fn funding_script_pubkey(&self) -> ScriptBuf {
1450 self.tree_spec().funding_tx_script_pubkey()
1451 }
1452
1453 pub fn funding_txout(&self) -> TxOut {
1455 let spec = self.tree_spec();
1456 TxOut {
1457 value: spec.total_required_value(),
1458 script_pubkey: spec.funding_tx_script_pubkey(),
1459 }
1460 }
1461 }
1462
1463 impl<T: state::CanSign> SignedTreeBuilder<T> {
1464 pub fn user_pub_nonces(&self) -> &[musig::PublicNonce] {
1466 &self.user_pub_nonces
1467 }
1468 }
1469
1470 #[derive(Debug, thiserror::Error)]
1471 #[error("signed VTXO tree builder error: {0}")]
1472 pub struct SignedTreeBuilderError(&'static str);
1473
1474 impl SignedTreeBuilder<state::Preparing> {
1475 pub fn construct_tree_spec(
1477 vtxos: impl IntoIterator<Item = VtxoRequest>,
1478 cosign_pubkey: PublicKey,
1479 unlock_hash: UnlockHash,
1480 expiry_height: BlockHeight,
1481 server_pubkey: PublicKey,
1482 server_cosign_pubkey: PublicKey,
1483 exit_delta: BlockDelta,
1484 ) -> Result<VtxoTreeSpec, SignedTreeBuilderError> {
1485 let reqs = vtxos.into_iter()
1486 .map(|vtxo| VtxoLeafSpec {
1487 vtxo: vtxo,
1488 cosign_pubkey: None,
1489 unlock_hash: unlock_hash,
1490 })
1491 .collect::<Vec<_>>();
1492 if reqs.len() < 2 {
1493 return Err(SignedTreeBuilderError("need to have at least 2 VTXOs in tree"));
1494 }
1495 Ok(VtxoTreeSpec::new(
1496 reqs,
1497 server_pubkey,
1498 expiry_height,
1499 exit_delta,
1500 vec![cosign_pubkey, server_cosign_pubkey],
1503 ))
1504 }
1505
1506 pub fn new(
1508 vtxos: impl IntoIterator<Item = VtxoRequest>,
1509 cosign_pubkey: PublicKey,
1510 unlock_preimage: UnlockPreimage,
1511 expiry_height: BlockHeight,
1512 server_pubkey: PublicKey,
1513 server_cosign_pubkey: PublicKey,
1514 exit_delta: BlockDelta,
1515 ) -> Result<SignedTreeBuilder<state::Preparing>, SignedTreeBuilderError> {
1516 let tree = Self::construct_tree_spec(
1517 vtxos,
1518 cosign_pubkey,
1519 sha256::Hash::hash(&unlock_preimage),
1520 expiry_height,
1521 server_pubkey,
1522 server_cosign_pubkey,
1523 exit_delta,
1524 )?;
1525
1526 Ok(SignedTreeBuilder {
1527 expiry_height, server_pubkey, exit_delta, cosign_pubkey, unlock_preimage,
1528 tree: BuilderTree::Spec(tree),
1529 user_pub_nonces: Vec::new(),
1530 user_sec_nonces: None,
1531 _state: PhantomData,
1532 })
1533 }
1534
1535 pub fn set_utxo(self, utxo: OutPoint) -> SignedTreeBuilder<state::CanGenerateNonces> {
1537 let unsigned_tree = match self.tree {
1538 BuilderTree::Spec(s) => s.into_unsigned_tree(utxo),
1539 BuilderTree::Unsigned(t) => t, };
1541 SignedTreeBuilder {
1542 tree: BuilderTree::Unsigned(unsigned_tree),
1543
1544 expiry_height: self.expiry_height,
1545 server_pubkey: self.server_pubkey,
1546 exit_delta: self.exit_delta,
1547 cosign_pubkey: self.cosign_pubkey,
1548 unlock_preimage: self.unlock_preimage,
1549 user_pub_nonces: self.user_pub_nonces,
1550 user_sec_nonces: self.user_sec_nonces,
1551 _state: PhantomData,
1552 }
1553 }
1554 }
1555
1556 impl SignedTreeBuilder<state::CanGenerateNonces> {
1557 pub fn generate_user_nonces(
1559 self,
1560 cosign_key: &Keypair,
1561 ) -> SignedTreeBuilder<state::CanFinish> {
1562 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1563
1564 let mut cosign_sec_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1565 let mut cosign_pub_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1566 for sh in &unsigned_tree.internal_sighashes {
1567 let pair = musig::nonce_pair_with_msg(&cosign_key, &sh.to_byte_array());
1568 cosign_sec_nonces.push(pair.0);
1569 cosign_pub_nonces.push(pair.1);
1570 }
1571
1572 SignedTreeBuilder {
1573 user_pub_nonces: cosign_pub_nonces,
1574 user_sec_nonces: Some(cosign_sec_nonces),
1575
1576 expiry_height: self.expiry_height,
1577 server_pubkey: self.server_pubkey,
1578 exit_delta: self.exit_delta,
1579 cosign_pubkey: self.cosign_pubkey,
1580 unlock_preimage: self.unlock_preimage,
1581 tree: self.tree,
1582 _state: PhantomData,
1583 }
1584 }
1585 }
1586
1587 #[derive(Debug, Clone)]
1589 pub struct SignedTreeCosignResponse {
1590 pub pub_nonces: Vec<musig::PublicNonce>,
1591 pub partial_signatures: Vec<musig::PartialSignature>,
1592 }
1593
1594 impl SignedTreeBuilder<state::ServerCanCosign> {
1595 pub fn new_for_cosign(
1597 vtxos: impl IntoIterator<Item = VtxoRequest>,
1598 cosign_pubkey: PublicKey,
1599 unlock_preimage: UnlockPreimage,
1600 expiry_height: BlockHeight,
1601 server_pubkey: PublicKey,
1602 server_cosign_pubkey: PublicKey,
1603 exit_delta: BlockDelta,
1604 utxo: OutPoint,
1605 user_pub_nonces: Vec<musig::PublicNonce>,
1606 ) -> Result<SignedTreeBuilder<state::ServerCanCosign>, SignedTreeBuilderError> {
1607 let unsigned_tree = SignedTreeBuilder::construct_tree_spec(
1608 vtxos,
1609 cosign_pubkey,
1610 sha256::Hash::hash(&unlock_preimage),
1611 expiry_height,
1612 server_pubkey,
1613 server_cosign_pubkey,
1614 exit_delta,
1615 )?.into_unsigned_tree(utxo);
1616
1617 Ok(SignedTreeBuilder {
1618 expiry_height,
1619 server_pubkey,
1620 exit_delta,
1621 cosign_pubkey,
1622 unlock_preimage,
1623 user_pub_nonces,
1624 tree: BuilderTree::Unsigned(unsigned_tree),
1625 user_sec_nonces: None,
1626 _state: PhantomData,
1627 })
1628 }
1629
1630 pub fn server_cosign(&self, server_cosign_key: &Keypair) -> SignedTreeCosignResponse {
1632 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1633
1634 let mut sec_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1635 let mut pub_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1636 for sh in &unsigned_tree.internal_sighashes {
1637 let pair = musig::nonce_pair_with_msg(&server_cosign_key, &sh.to_byte_array());
1638 sec_nonces.push(pair.0);
1639 pub_nonces.push(pair.1);
1640 }
1641
1642 let agg_nonces = self.user_pub_nonces().iter().zip(&pub_nonces)
1643 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1644 .collect::<Vec<_>>();
1645
1646 let sigs = unsigned_tree.cosign_tree(&agg_nonces, &server_cosign_key, sec_nonces);
1647
1648 SignedTreeCosignResponse {
1649 pub_nonces,
1650 partial_signatures: sigs,
1651 }
1652 }
1653 }
1654
1655 impl SignedTreeBuilder<state::CanFinish> {
1656 pub fn verify_cosign_response(
1658 &self,
1659 server_cosign: &SignedTreeCosignResponse,
1660 ) -> Result<(), CosignSignatureError> {
1661 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1662
1663 let agg_nonces = self.user_pub_nonces().iter()
1664 .zip(&server_cosign.pub_nonces)
1665 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1666 .collect::<Vec<_>>();
1667
1668 unsigned_tree.verify_global_cosign_partial_sigs(
1669 *unsigned_tree.spec.global_cosign_pubkeys.get(1).expect("state invariant"),
1670 &agg_nonces,
1671 &server_cosign.pub_nonces,
1672 &server_cosign.partial_signatures,
1673 )
1674 }
1675
1676 pub fn build_tree(
1677 self,
1678 server_cosign: &SignedTreeCosignResponse,
1679 cosign_key: &Keypair,
1680 ) -> Result<SignedVtxoTreeSpec, IncorrectSigningKeyError> {
1681 if cosign_key.public_key() != self.cosign_pubkey {
1682 return Err(IncorrectSigningKeyError {
1683 required: Some(self.cosign_pubkey),
1684 provided: cosign_key.public_key(),
1685 });
1686 }
1687
1688 let agg_nonces = self.user_pub_nonces().iter().zip(&server_cosign.pub_nonces)
1689 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1690 .collect::<Vec<_>>();
1691
1692 let unsigned_tree = self.tree.into_unsigned_tree().expect("state invariant");
1693 let sec_nonces = self.user_sec_nonces.expect("state invariant");
1694 let partial_sigs = unsigned_tree.cosign_tree(&agg_nonces, cosign_key, sec_nonces);
1695
1696 debug_assert!(unsigned_tree.verify_global_cosign_partial_sigs(
1697 self.cosign_pubkey,
1698 &agg_nonces,
1699 &self.user_pub_nonces,
1700 &partial_sigs,
1701 ).is_ok(), "produced invalid partial signatures");
1702
1703 let sigs = unsigned_tree.combine_partial_signatures(
1704 &agg_nonces,
1705 &HashMap::new(),
1706 &[&server_cosign.partial_signatures, &partial_sigs],
1707 ).expect("should work with correct cosign signatures");
1708
1709 Ok(unsigned_tree.into_signed_tree(sigs))
1710 }
1711 }
1712}
1713
1714const VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1716
1717impl ProtocolEncoding for VtxoTreeSpec {
1718 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1719 w.emit_u8(VTXO_TREE_SPEC_VERSION)?;
1720 w.emit_u32(self.expiry_height)?;
1721 self.server_pubkey.encode(w)?;
1722 w.emit_u16(self.exit_delta)?;
1723 LengthPrefixedVector::new(&self.global_cosign_pubkeys).encode(w)?;
1724 LengthPrefixedVector::new(&self.vtxos).encode(w)?;
1725 Ok(())
1726 }
1727
1728 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1729 let version = r.read_u8()?;
1730
1731 if version != VTXO_TREE_SPEC_VERSION {
1732 return Err(ProtocolDecodingError::invalid(format_args!(
1733 "invalid VtxoTreeSpec encoding version byte: {version:#x}",
1734 )));
1735 }
1736
1737 let expiry_height = check_block_height(r.read_u32()?)
1738 .map_err(|e| ProtocolDecodingError::invalid_err(e, "expiry_height"))?;
1739 let server_pubkey = PublicKey::decode(r)?;
1740 let exit_delta = check_block_delta(r.read_u16()?)
1741 .map_err(|e| ProtocolDecodingError::invalid_err(e, "exit_delta"))?;
1742 let global_cosign_pubkeys = LengthPrefixedVector::decode(r)?.into_inner();
1743 let vtxos = LengthPrefixedVector::decode(r)?.into_inner();
1744 if vtxos.is_empty() {
1745 return Err(ProtocolDecodingError::invalid(
1746 "vtxo tree spec must have at least one leaf",
1747 ));
1748 }
1749 Ok(VtxoTreeSpec {
1750 vtxos, expiry_height, server_pubkey, exit_delta, global_cosign_pubkeys,
1751 hashlock_version: HashlockVersion::V1,
1754 })
1755 }
1756}
1757
1758const SIGNED_VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1760
1761const SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE: u8 = 0x01;
1763
1764impl ProtocolEncoding for SignedVtxoTreeSpec {
1765 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1766 w.emit_u8(SIGNED_VTXO_TREE_SPEC_VERSION)?;
1767 self.spec.encode(w)?;
1768 self.utxo.encode(w)?;
1769 LengthPrefixedVector::new(&self.cosign_sigs).encode(w)?;
1770 Ok(())
1771 }
1772
1773 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1774 let version = r.read_u8()?;
1775 if version != SIGNED_VTXO_TREE_SPEC_VERSION
1776 && version != SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE
1777 {
1778 return Err(ProtocolDecodingError::invalid(format_args!(
1779 "invalid SignedVtxoTreeSpec encoding version byte: {version:#x}",
1780 )));
1781 }
1782 let spec = VtxoTreeSpec::decode(r)?;
1783 let utxo = OutPoint::decode(r)?;
1784 let cosign_sigs = if version == SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE {
1785 let nb_cosign_sigs = r.read_u32()?;
1786 OversizedVectorError::check::<schnorr::Signature>(nb_cosign_sigs as usize)?;
1787 let mut cosign_sigs = Vec::with_capacity(nb_cosign_sigs as usize);
1788 for _ in 0..nb_cosign_sigs {
1789 cosign_sigs.push(schnorr::Signature::decode(r)?);
1790 }
1791 cosign_sigs
1792 } else {
1793 LengthPrefixedVector::decode(r)?.into_inner()
1794 };
1795 Ok(SignedVtxoTreeSpec { spec, utxo, cosign_sigs })
1796 }
1797}
1798
1799
1800#[cfg(test)]
1801mod test {
1802 use std::iter;
1803 use std::collections::HashMap;
1804 use std::str::FromStr;
1805
1806 use bitcoin::hashes::{siphash24, sha256, Hash, HashEngine};
1807 use bitcoin::key::rand::Rng;
1808 use bitcoin::secp256k1::{self, rand, Keypair};
1809 use bitcoin::{absolute, transaction};
1810 use rand::SeedableRng;
1811
1812 use crate::encode;
1813 use crate::test_util::{encoding_roundtrip, json_roundtrip};
1814 use crate::tree::signed::builder::SignedTreeBuilder;
1815 use crate::vtxo::policy::{ServerVtxoPolicy, VtxoPolicy};
1816
1817 use super::*;
1818
1819 fn test_tree_amounts(
1820 tree: &UnsignedVtxoTree,
1821 root_value: Amount,
1822 ) {
1823 let map = tree.txs.iter().map(|tx| (tx.compute_txid(), tx)).collect::<HashMap<_, _>>();
1824
1825 for (idx, tx) in tree.txs.iter().take(tree.txs.len().saturating_sub(1)).enumerate() {
1827 println!("tx #{idx}: {}", bitcoin::consensus::encode::serialize_hex(tx));
1828 let input = tx.input.iter().map(|i| {
1829 let prev = i.previous_output;
1830 map.get(&prev.txid).expect(&format!("tx {} not found", prev.txid))
1831 .output[prev.vout as usize].value
1832 }).sum::<Amount>();
1833 let output = tx.output_value();
1834 assert!(input >= output);
1835 assert_eq!(input, output);
1836 }
1837
1838 let root = tree.txs.last().unwrap();
1840 assert_eq!(root_value, root.output_value());
1841 }
1842
1843 #[test]
1844 fn vtxo_tree() {
1845 let secp = secp256k1::Secp256k1::new();
1846 let mut rand = rand::rngs::StdRng::seed_from_u64(42);
1847 let random_sig = {
1848 let key = Keypair::new(&secp, &mut rand);
1849 let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1850 let msg = secp256k1::Message::from_digest(sha.to_byte_array());
1851 secp.sign_schnorr(&msg, &key)
1852 };
1853
1854 let server_key = Keypair::new(&secp, &mut rand);
1855 let server_cosign_key = Keypair::new(&secp, &mut rand);
1856
1857 struct Req {
1858 key: Keypair,
1859 cosign_key: Keypair,
1860 amount: Amount,
1861 hash: sha256::Hash,
1862 }
1863 impl Req {
1864 fn to_vtxo(&self) -> VtxoLeafSpec {
1865 VtxoLeafSpec {
1866 vtxo: VtxoRequest {
1867 amount: self.amount,
1868 policy: VtxoPolicy::new_pubkey(self.key.public_key()),
1869 },
1870 cosign_pubkey: Some(self.cosign_key.public_key()),
1871 unlock_hash: self.hash,
1872 }
1873 }
1874 }
1875
1876 let nb_leaves = 27;
1877 let reqs = iter::repeat_with(|| Req {
1878 key: Keypair::new(&secp, &mut rand),
1879 cosign_key: Keypair::new(&secp, &mut rand),
1880 amount: Amount::from_sat(100_000),
1881 hash: sha256::Hash::from_byte_array(rand.r#gen()),
1882 }).take(nb_leaves).collect::<Vec<_>>();
1883 let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
1884
1885 let spec = VtxoTreeSpec::new(
1886 reqs.iter().map(|r| r.to_vtxo()).collect(),
1887 server_key.public_key(),
1888 101_000,
1889 2016,
1890 vec![server_cosign_key.public_key()],
1891 );
1892 assert_eq!(spec.nb_leaves(), nb_leaves);
1893 assert_eq!(spec.total_required_value().to_sat(), 2700000);
1894 let nb_nodes = spec.nb_nodes();
1895
1896 encoding_roundtrip(&spec);
1897
1898 let unsigned = spec.into_unsigned_tree(point);
1899
1900 test_tree_amounts(&unsigned, unsigned.spec.total_required_value());
1901
1902 let sighashes_hash = {
1903 let mut eng = siphash24::Hash::engine();
1904 unsigned.internal_sighashes.iter().for_each(|h| eng.input(&h[..]));
1905 siphash24::Hash::from_engine(eng)
1906 };
1907 assert_eq!(sighashes_hash.to_string(), "78ae911f557c0b86");
1908
1909 let signed = unsigned.into_signed_tree(vec![random_sig; nb_nodes]);
1910
1911 encoding_roundtrip(&signed);
1912
1913 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1914 struct JsonSignedVtxoTreeSpec {
1915 #[serde(with = "encode::serde")]
1916 pub spec: SignedVtxoTreeSpec,
1917 }
1918
1919 json_roundtrip(&JsonSignedVtxoTreeSpec { spec: signed.clone() });
1920
1921 for l in 0..nb_leaves {
1922 let exit = signed.exit_branch(l);
1923
1924 let mut iter = exit.iter().enumerate().peekable();
1926 while let Some((i, cur)) = iter.next() {
1927 if let Some((_, next)) = iter.peek() {
1928 assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
1929 }
1930 }
1931 }
1932
1933 let cached = signed.into_cached_tree();
1934 for vtxo in cached.output_vtxos() {
1935 encoding_roundtrip(&vtxo);
1936 }
1937 }
1938
1939 #[test]
1940 fn test_tree_builder() {
1941 let expiry = 100_000;
1942 let exit_delta = 24;
1943
1944 let vtxo_key = Keypair::from_str("985247fb0ef008f8043b6be28add87710d42d482433ef287235bfe041ee6cc11").unwrap();
1945 let policy = VtxoPolicy::new_pubkey(vtxo_key.public_key());
1946 let user_cosign_key = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1947 let user_cosign_pubkey = user_cosign_key.public_key();
1948 println!("user_cosign_pubkey: {}", user_cosign_pubkey);
1949
1950 let server_key = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1951 let server_pubkey = server_key.public_key();
1952 println!("server_pubkey: {}", server_pubkey);
1953
1954 let server_cosign_key = Keypair::from_str("52a506fbae3b725749d2486afd4761841ec685b841c2967e30f24182c4b02eed").unwrap();
1955 let server_cosign_pubkey = server_cosign_key.public_key();
1956 println!("server_cosign_pubkey: {}", server_cosign_pubkey);
1957
1958 let unlock_preimage = rand::random::<UnlockPreimage>();
1959 let unlock_hash = sha256::Hash::hash(&unlock_preimage);
1960 println!("unlock_hash: {}", unlock_hash);
1961
1962 for nb_vtxos in [2, 3, 4, 5, 10, 50] {
1964 println!("building tree with {} vtxos", nb_vtxos);
1965 let vtxos = (0..nb_vtxos).map(|i| VtxoRequest {
1966 amount: Amount::from_sat(1000 * (i + 1)),
1967 policy: policy.clone(),
1968 }).collect::<Vec<_>>();
1969
1970 let builder = SignedTreeBuilder::new(
1971 vtxos.iter().cloned(), user_cosign_pubkey, unlock_preimage, expiry, server_pubkey,
1972 server_cosign_pubkey, exit_delta,
1973 ).unwrap();
1974
1975 let funding_tx = Transaction {
1976 version: transaction::Version::TWO,
1977 lock_time: absolute::LockTime::ZERO,
1978 input: vec![],
1979 output: vec![builder.funding_txout()],
1980 };
1981 let utxo = OutPoint::new(funding_tx.compute_txid(), 0);
1982 let builder = builder.set_utxo(utxo).generate_user_nonces(&user_cosign_key);
1983 let user_pub_nonces = builder.user_pub_nonces().to_vec();
1984
1985 let cosign = {
1986 let builder = SignedTreeBuilder::new_for_cosign(
1987 vtxos.iter().cloned(), user_cosign_pubkey, unlock_preimage, expiry, server_pubkey,
1988 server_cosign_pubkey, exit_delta, utxo, user_pub_nonces,
1989 ).unwrap();
1990 builder.server_cosign(&server_cosign_key)
1991 };
1992
1993 builder.verify_cosign_response(&cosign).unwrap();
1994 let tree = builder.build_tree(&cosign, &user_cosign_key).unwrap().into_cached_tree();
1995
1996 for mut vtxo in tree.output_vtxos() {
1998 {
1999 let mut with_preimage = vtxo.clone();
2001 assert!(with_preimage.provide_unlock_preimage(unlock_preimage));
2002 assert!(with_preimage.validate(&funding_tx).is_err());
2003 }
2004
2005 let (ctx, req) = LeafVtxoCosignContext::new(&vtxo, &funding_tx, &vtxo_key);
2006 let cosign = LeafVtxoCosignResponse::new_cosign(&req, &vtxo, &funding_tx, &server_key);
2007 assert!(ctx.finalize(&mut vtxo, cosign));
2008
2009
2010 assert!(!vtxo.has_all_witnesses());
2012 vtxo.validate_unsigned(&funding_tx).expect("Tree is valid if we ignore sigs");
2013 vtxo.validate(&funding_tx).expect_err("The signature check must fail");
2014
2015 assert!(vtxo.provide_unlock_preimage(unlock_preimage));
2016
2017 println!("vtxo debug: {:#?}", vtxo);
2018 println!("vtxo hex: {}", vtxo.serialize_hex());
2019 assert!(vtxo.has_all_witnesses());
2020 vtxo.validate(&funding_tx).expect("should be value");
2021
2022 vtxo.invalidate_final_sig();
2023 assert!(vtxo.has_all_witnesses(), "still has all witnesses, just an invalid one");
2024 vtxo.validate_unsigned(&funding_tx).expect("unsigned still valid");
2025 vtxo.validate(&funding_tx).expect_err("but not fully valid anymore");
2026 }
2027
2028 for (idx, (vtxo, _spending_txid)) in tree.internal_vtxos().enumerate() {
2029 let mut prev_txout = funding_tx.output[vtxo.chain_anchor().vout as usize].clone();
2031
2032 for item in vtxo.transactions() {
2034 crate::test_util::verify_tx(&[prev_txout], 0, &item.tx)
2035 .expect("Invalid transaction");
2036 prev_txout = item.tx.output[item.output_idx].clone();
2037 }
2038
2039 if idx < nb_vtxos as usize {
2041 matches!(vtxo.policy(), ServerVtxoPolicy::HarkLeaf(_));
2043 } else {
2044 matches!(vtxo.policy(), ServerVtxoPolicy::Expiry(_));
2045 }
2046 }
2047 }
2048 }
2049
2050 #[test]
2051 fn tree_hashlock_version_detection() {
2052 let secp = secp256k1::Secp256k1::new();
2053 let mut rand = rand::rngs::StdRng::seed_from_u64(43);
2054
2055 let user_key = Keypair::new(&secp, &mut rand);
2056 let user_cosign_key = Keypair::new(&secp, &mut rand);
2057 let server_key = Keypair::new(&secp, &mut rand);
2058 let server_cosign_key = Keypair::new(&secp, &mut rand);
2059 let preimage: UnlockPreimage = rand.r#gen();
2060 let unlock_hash = sha256::Hash::hash(&preimage);
2061
2062 let outputs = (1..=6u64).map(|i| VtxoRequest {
2065 amount: Amount::from_sat(10_000 * i),
2066 policy: VtxoPolicy::new_pubkey(user_key.public_key()),
2067 }).collect::<Vec<_>>();
2068
2069 let (v1_tree, _) = crate::test_util::build_signed_tree(
2070 HashlockVersion::V1, outputs.iter().cloned(),
2071 &user_cosign_key, &server_key, &server_cosign_key, unlock_hash,
2072 );
2073 let (v0_tree, v0_funding_tx) = crate::test_util::build_signed_tree(
2074 HashlockVersion::V0, outputs.iter().cloned(),
2075 &user_cosign_key, &server_key, &server_cosign_key, unlock_hash,
2076 );
2077
2078 assert_ne!(
2081 v1_tree.clone().into_cached_tree().build_vtxo(0).id(),
2082 v0_tree.clone().into_cached_tree().build_vtxo(0).id(),
2083 );
2084
2085 for (tree, version) in [(&v1_tree, HashlockVersion::V1), (&v0_tree, HashlockVersion::V0)] {
2088 assert_eq!(tree.detect_hashlock_version(), Ok(version));
2089
2090 let decoded = SignedVtxoTreeSpec::deserialize(&tree.serialize()).unwrap();
2091 assert_eq!(decoded.spec.hashlock_version, HashlockVersion::V1);
2092 let restored = decoded.with_detected_hashlock_version().unwrap();
2093 assert_eq!(restored.spec.hashlock_version, version);
2094 assert_eq!(&restored.spec, &tree.spec);
2095 }
2096
2097 let garbage_sig = {
2099 let sha = sha256::Hash::hash(b"not a tree sighash");
2100 let msg = secp256k1::Message::from_digest(sha.to_byte_array());
2101 secp.sign_schnorr(&msg, &Keypair::new(&secp, &mut rand))
2102 };
2103 let mut broken = v0_tree.clone();
2104 broken.cosign_sigs[0] = garbage_sig;
2105 assert_eq!(broken.detect_hashlock_version(), Err(HashlockVersionDetectError));
2106
2107 let mut half_broken = v0_tree.clone();
2110 half_broken.cosign_sigs[1] = garbage_sig;
2111 assert_eq!(half_broken.detect_hashlock_version(), Ok(HashlockVersion::V0));
2112
2113 let cached = v0_tree.into_cached_tree();
2116 for (idx, (vtxo, _)) in cached.internal_vtxos().enumerate() {
2117 if idx < cached.nb_leaves() {
2118 assert!(matches!(vtxo.policy(), ServerVtxoPolicy::HarkLeaf_v0(_)));
2119 }
2120 }
2121 for mut vtxo in cached.output_vtxos() {
2122 assert_eq!(vtxo.unlock_hash(), Some(unlock_hash));
2123 assert!(matches!(
2124 vtxo.genesis.items.last().unwrap().transition,
2125 GenesisTransition::HashLockedCosigned_v0(_),
2126 ));
2127
2128 let (ctx, req) = LeafVtxoCosignContext::new(&vtxo, &v0_funding_tx, &user_key);
2129 let resp = LeafVtxoCosignResponse::new_cosign(&req, &vtxo, &v0_funding_tx, &server_key);
2130 assert!(ctx.finalize(&mut vtxo, resp));
2131 assert!(vtxo.provide_unlock_preimage(preimage));
2132 vtxo.validate(&v0_funding_tx).expect("unlocked v0 leaf vtxo should be valid");
2133 }
2134 }
2135
2136 #[test]
2137 fn vtxo_leaf_spec_encoding() {
2138 let pk1: PublicKey = "020aceb65eed0ee5c512d3718e6f4bd868a7efb58ede7899ffd9bcba09555d4eb8".parse().unwrap();
2139 let pk2: PublicKey = "02e4ed0ca35c3b8a2ff675b9b23f4961964b57e130afa607e32a83d2d9a510622b".parse().unwrap();
2140 let hash = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
2141
2142 let spec_with_cosign = VtxoLeafSpec {
2144 vtxo: VtxoRequest {
2145 amount: Amount::from_sat(100_000),
2146 policy: VtxoPolicy::new_pubkey(pk1),
2147 },
2148 cosign_pubkey: Some(pk2),
2149 unlock_hash: hash,
2150 };
2151 encoding_roundtrip(&spec_with_cosign);
2152
2153 let spec_without_cosign = VtxoLeafSpec {
2155 vtxo: VtxoRequest {
2156 amount: Amount::from_sat(200_000),
2157 policy: VtxoPolicy::new_pubkey(pk1),
2158 },
2159 cosign_pubkey: None,
2160 unlock_hash: hash,
2161 };
2162 encoding_roundtrip(&spec_without_cosign);
2163 }
2164
2165 #[test]
2166 fn vtxo_tree_spec_rejects_empty_vtxos() {
2167 let pk1: PublicKey = "020aceb65eed0ee5c512d3718e6f4bd868a7efb58ede7899ffd9bcba09555d4eb8"
2168 .parse().unwrap();
2169 let pk2: PublicKey = "02e4ed0ca35c3b8a2ff675b9b23f4961964b57e130afa607e32a83d2d9a510622b"
2170 .parse().unwrap();
2171 let hash = sha256::Hash::from_str(
2172 "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
2173 ).unwrap();
2174
2175 let leaf = VtxoLeafSpec {
2177 vtxo: VtxoRequest {
2178 amount: Amount::from_sat(100_000),
2179 policy: VtxoPolicy::new_pubkey(pk1),
2180 },
2181 cosign_pubkey: Some(pk2),
2182 unlock_hash: hash,
2183 };
2184 let spec = VtxoTreeSpec::new(vec![leaf], pk1, 100_000, 2016, vec![]);
2185 encoding_roundtrip(&spec);
2186
2187 let empty_spec = VtxoTreeSpec { vtxos: vec![], ..spec };
2189 let buf = empty_spec.serialize();
2190 let err = VtxoTreeSpec::deserialize(&buf)
2191 .expect_err("decoding a VtxoTreeSpec with zero vtxos must fail");
2192 let msg = err.to_string();
2193 assert!(msg.contains("at least one leaf"), "unexpected error message: {msg}");
2194 }
2195
2196 #[test]
2197 fn test_compat_u32_size_signed_spec() {
2198 let hex = "0102888a0100035b0ef8c9bd756af433edc3129975888f6f18b8185b2afbaabc8bb3029a00cf81e0070102f8112234026e68b1e4d1565540d7b791ced1b64c5f30525cbe14f21dd7aa8c78030003c48b53afac0d2d5169cd6848f03f67a21db6f506f8b8fc2dbef2552b6a7dc111a08601000000000002c6c80e198e170ca6f8fa17810d8ee23c7c0d85c5d2febc95c3e24b1878ca733fbfd032abc31b253f5063521fd5b4c431f2cdd3fee1b4ec00a9b00f69d3b033e7000296dfec4c92e831ffe3619285646a46c545577f19dbc27c2fc0950bffb0f4a362a08601000000000003850a7d2bf22e6ba669695410a8b03c5800a0d4c2bec814b9eb21b0cddd2af5c935395dea8bd6dcac26a8a417b553b18d13027c23e8016c3466b81e70832254360002415f712d6e551b715542422b975a2ae7c635b44a1e3747d5a8674231fa10a841a08601000000000002a3d4de26a87f8bb9d5c0b9f1e3409a635b35f656575d8ad60a6a2294ac4e50b87c0cc2177dfce6432efa42ca6c04c0b774dbb3c5ca2573cd893443e10e393bfd0100000000000000000000000000000000000000000000000000000000000000010000000400000002cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb";
2200 let ret = SignedVtxoTreeSpec::deserialize_hex(hex).unwrap();
2201 assert_eq!(ret.cosign_sigs.len(), 4);
2202 }
2203}