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_idxs_for_participation<'a>(
217 &self,
218 unlock_hash: UnlockHash,
219 vtxo_requests: impl IntoIterator<Item = &'a VtxoRequest>,
220 ) -> Option<Vec<usize>> {
221 let mut ret = Vec::new();
222 for req in vtxo_requests {
223 let (idx, _) = self.vtxos.iter().enumerate().find(|(i, e)| {
224 !ret.contains(i) && e.unlock_hash == unlock_hash && e.vtxo == *req
225 })?;
226 ret.push(idx);
227 }
228 Some(ret)
229 }
230
231 pub fn total_required_value(&self) -> Amount {
236 self.vtxos.iter().map(|d| d.vtxo.amount).sum::<Amount>()
237 }
238
239 pub fn leaf_taproot(
241 &self,
242 user_pubkey: PublicKey,
243 unlock_hash: UnlockHash,
244 ) -> taproot::TaprootSpendInfo {
245 match self.hashlock_version {
246 HashlockVersion::V1 => leaf_cosign_taproot(
247 user_pubkey, self.server_pubkey, self.expiry_height, unlock_hash,
248 ),
249 HashlockVersion::V0 => leaf_cosign_taproot_v0(
250 user_pubkey, self.server_pubkey, self.expiry_height, unlock_hash,
251 ),
252 }
253 }
254
255 pub fn internal_taproot(&self, agg_pk: XOnlyPublicKey) -> taproot::TaprootSpendInfo {
257 cosign_taproot(agg_pk, self.server_pubkey, self.expiry_height)
258 }
259
260 pub fn funding_tx_cosign_pubkey(&self) -> XOnlyPublicKey {
264 let keys = self.vtxos.iter()
265 .filter_map(|v| v.cosign_pubkey)
266 .chain(self.global_cosign_pubkeys.iter().copied());
267 musig::combine_keys(keys).x_only_public_key().0
268 }
269
270 pub fn funding_tx_script_pubkey(&self) -> ScriptBuf {
274 let agg_pk = self.funding_tx_cosign_pubkey();
275 self.internal_taproot(agg_pk).script_pubkey()
276 }
277
278 pub fn funding_tx_txout(&self) -> TxOut {
282 TxOut {
283 script_pubkey: self.funding_tx_script_pubkey(),
284 value: self.total_required_value(),
285 }
286 }
287
288 fn node_tx<'a>(
293 &self,
294 children: impl Iterator<Item = ChildSpec<'a>>,
295 ) -> Transaction {
296 Transaction {
297 version: bitcoin::transaction::Version(3),
298 lock_time: bitcoin::absolute::LockTime::ZERO,
299 input: vec![TxIn {
300 previous_output: OutPoint::null(), sequence: Sequence::ZERO,
302 script_sig: ScriptBuf::new(),
303 witness: Witness::new(),
304 }],
305 output: children.map(|child| match child {
306 ChildSpec::Leaf { spec } => {
307 let taproot = self.leaf_taproot(
308 spec.vtxo.policy.user_pubkey(),
309 spec.unlock_hash,
310 );
311 TxOut {
312 script_pubkey: taproot.script_pubkey(),
313 value: spec.vtxo.amount,
314 }
315 },
316 ChildSpec::Internal { output_value, agg_pk } => {
317 let taproot = self.internal_taproot(agg_pk.x_only_public_key().0);
318 TxOut {
319 script_pubkey: taproot.script_pubkey(),
320 value: output_value,
321 }
322 },
323 }).chain(Some(fee::fee_anchor())).collect(),
324 }
325 }
326
327 fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
328 let txout = TxOut {
329 value: vtxo.amount,
330 script_pubkey: vtxo.policy.script_pubkey(self.server_pubkey, self.exit_delta, self.expiry_height),
331 };
332
333 vtxo::create_exit_tx(OutPoint::null(), txout, None, Amount::ZERO)
337 }
338
339 pub fn cosign_agg_pks(&self)
343 -> impl Iterator<Item = PublicKey> + iter::DoubleEndedIterator + iter::ExactSizeIterator + '_
344 {
345 Tree::new(self.nb_leaves()).into_iter().map(|node| {
346 if node.is_leaf() {
347 musig::combine_keys([
348 self.vtxos[node.idx()].vtxo.policy.user_pubkey(),
349 self.server_pubkey,
350 ])
351 } else {
352 musig::combine_keys(
353 node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey)
354 .chain(self.global_cosign_pubkeys.iter().copied())
355 )
356 }
357 })
358 }
359
360 pub fn unsigned_transactions(&self, utxo: OutPoint) -> Vec<Transaction> {
362 let tree = Tree::new(self.nb_leaves());
363
364 let cosign_agg_pks = self.cosign_agg_pks().collect::<Vec<_>>();
365
366 let mut txs = Vec::<Transaction>::with_capacity(tree.nb_nodes());
367 for node in tree.iter() {
368 let tx = if node.is_leaf() {
369 self.leaf_tx(&self.vtxos[node.idx()].vtxo).clone()
370 } else {
371 let mut buf = [None; tree::RADIX];
372 for (idx, child) in node.children().enumerate() {
373 let child = if let Some(spec) = self.vtxos.get(child) {
374 ChildSpec::Leaf { spec }
375 } else {
376 ChildSpec::Internal {
377 output_value: txs[child].output_value(),
378 agg_pk: cosign_agg_pks[child],
379 }
380 };
381 buf[idx] = Some(child);
382 }
383 self.node_tx(buf.iter().filter_map(|x| *x))
384 };
385 txs.push(tx.clone());
386 };
387
388 txs.last_mut().unwrap().input[0].previous_output = utxo;
390 for node in tree.iter().rev() {
391 let txid = txs[node.idx()].compute_txid();
392 for (i, child) in node.children().enumerate() {
393 let point = OutPoint::new(txid, u32::try_from(i).expect("tree child index fits in u32"));
394 txs[child].input[0].previous_output = point;
395 }
396 }
397
398 txs
399 }
400
401 pub fn final_transactions(
405 &self,
406 utxo: OutPoint,
407 internal_signatures: &[schnorr::Signature],
408 ) -> Vec<Transaction> {
409 let mut txs = self.unsigned_transactions(utxo);
410 for (tx, sig) in txs.iter_mut().skip(self.nb_leaves()).zip(internal_signatures) {
411 tx.input[0].witness.push(&sig[..]);
412 }
413 txs
414 }
415
416 pub fn calculate_cosign_agg_nonces(
420 &self,
421 leaf_cosign_nonces: &HashMap<PublicKey, Vec<PublicNonce>>,
422 global_signer_cosign_nonces: &[impl AsRef<[PublicNonce]>],
423 ) -> Result<Vec<AggregatedNonce>, String> {
424 if global_signer_cosign_nonces.len() != self.global_cosign_pubkeys.len() {
425 return Err("missing global signer nonces".into());
426 }
427
428 Tree::new(self.nb_leaves()).iter_internal().enumerate().map(|(idx, node)| {
429 let mut nonces = Vec::new();
430 for pk in node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey) {
431 nonces.push(leaf_cosign_nonces.get(&pk)
432 .ok_or_else(|| format!("missing nonces for leaf pk {}", pk))?
433 .get(node.internal_level())
436 .ok_or_else(|| format!("not enough nonces for leaf_pk {}", pk))?
437 );
438 }
439 for glob in global_signer_cosign_nonces {
440 nonces.push(glob.as_ref().get(idx).ok_or("not enough global cosign nonces")?);
441 }
442 Ok(musig::nonce_agg(&nonces))
443 }).collect()
444 }
445
446 pub fn into_unsigned_tree(
451 self,
452 utxo: OutPoint,
453 ) -> UnsignedVtxoTree {
454 UnsignedVtxoTree::new(self, utxo)
455 }
456}
457
458#[derive(Debug, Clone)]
462pub struct UnsignedVtxoTree {
463 pub spec: VtxoTreeSpec,
464 pub utxo: OutPoint,
465
466 pub cosign_agg_pks: Vec<PublicKey>,
470 pub txs: Vec<Transaction>,
472 pub internal_sighashes: Vec<TapSighash>,
475
476 tree: Tree,
477}
478
479impl UnsignedVtxoTree {
480 pub fn new(
481 spec: VtxoTreeSpec,
482 utxo: OutPoint,
483 ) -> UnsignedVtxoTree {
484 let tree = Tree::new(spec.nb_leaves());
485
486 let cosign_agg_pks = spec.cosign_agg_pks().collect::<Vec<_>>();
487 let txs = spec.unsigned_transactions(utxo);
488
489 let root_txout = spec.funding_tx_txout();
490 let internal_sighashes = tree.iter_internal().map(|node| {
491 let prev = if let Some((parent, sibling_idx))
492 = tree.parent_idx_of_with_sibling_idx(node.idx())
493 {
494 assert!(!node.is_root());
495 &txs[parent].output[sibling_idx]
496 } else {
497 assert!(node.is_root());
498 &root_txout
499 };
500
501 let mut shc = SighashCache::new(&txs[node.idx()]);
502 shc.taproot_key_spend_signature_hash(
503 0, &sighash::Prevouts::All(&[prev]),
505 TapSighashType::Default,
506 ).expect("sighash error")
507 }).collect();
508
509 UnsignedVtxoTree { spec, utxo, txs, internal_sighashes, cosign_agg_pks, tree }
510 }
511
512 pub fn nb_leaves(&self) -> usize {
513 self.tree.nb_leaves()
514 }
515
516 pub fn nb_cosigned_leaves(&self) -> usize {
518 self.spec.vtxos.iter()
519 .filter(|v| v.cosign_pubkey.is_some())
520 .count()
521 }
522
523 pub fn nb_nodes(&self) -> usize {
524 self.tree.nb_nodes()
525 }
526
527 pub fn nb_internal_nodes(&self) -> usize {
528 self.tree.nb_internal_nodes()
529 }
530
531 pub fn cosign_branch(
544 &self,
545 cosign_agg_nonces: &[AggregatedNonce],
546 leaf_idx: usize,
547 cosign_key: &Keypair,
548 cosign_sec_nonces: Vec<SecretNonce>,
549 ) -> Result<Vec<PartialSignature>, IncorrectSigningKeyError> {
550 let req = self.spec.vtxos.get(leaf_idx).expect("leaf idx out of bounds");
551 if Some(cosign_key.public_key()) != req.cosign_pubkey {
552 return Err(IncorrectSigningKeyError {
553 required: req.cosign_pubkey,
554 provided: cosign_key.public_key(),
555 });
556 }
557
558 let mut nonce_iter = cosign_sec_nonces.into_iter().enumerate();
559 let mut ret = Vec::with_capacity(self.tree.root().level().saturating_add(1));
560 for node in self.tree.iter_branch(leaf_idx).skip(1) {
562 let sec_nonce = loop {
567 let next = nonce_iter.next().expect("level overflow");
568 if next.0 == node.internal_level() {
569 break next.1;
570 }
571 };
572
573 let cosign_pubkeys = node.leaves()
574 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
575 .chain(self.spec.global_cosign_pubkeys.iter().copied());
576 let sighash = self.internal_sighashes[node.internal_idx()];
577
578 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
579 let tweak = self.spec.internal_taproot(agg_pk).tap_tweak().to_byte_array();
580 let sig = musig::partial_sign(
581 cosign_pubkeys,
582 cosign_agg_nonces[node.internal_idx()],
583 &cosign_key,
584 sec_nonce,
585 sighash.to_byte_array(),
586 Some(tweak),
587 None,
588 ).0;
589 ret.push(sig);
590 }
591
592 Ok(ret)
593 }
594
595 pub fn cosign_tree(
601 &self,
602 cosign_agg_nonces: &[AggregatedNonce],
603 keypair: &Keypair,
604 cosign_sec_nonces: Vec<SecretNonce>,
605 ) -> Vec<PartialSignature> {
606 debug_assert_eq!(cosign_agg_nonces.len(), self.nb_internal_nodes());
607 debug_assert_eq!(cosign_sec_nonces.len(), self.nb_internal_nodes());
608
609 let nonces = cosign_sec_nonces.into_iter().zip(cosign_agg_nonces);
610 self.tree.iter_internal().zip(nonces).map(|(node, (sec_nonce, agg_nonce))| {
611 let sighash = self.internal_sighashes[node.internal_idx()];
612
613 let cosign_pubkeys = node.leaves()
614 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
615 .chain(self.spec.global_cosign_pubkeys.iter().copied());
616 let agg_pk = self.cosign_agg_pks[node.idx()];
617 debug_assert_eq!(agg_pk, musig::combine_keys(cosign_pubkeys.clone()));
618 let taproot = self.spec.internal_taproot(agg_pk.x_only_public_key().0);
619 musig::partial_sign(
620 cosign_pubkeys,
621 *agg_nonce,
622 &keypair,
623 sec_nonce,
624 sighash.to_byte_array(),
625 Some(taproot.tap_tweak().to_byte_array()),
626 None,
627 ).0
628 }).collect()
629 }
630
631 fn verify_internal_node_cosign_partial_sig(
633 &self,
634 node: &tree::Node,
635 pk: PublicKey,
636 agg_nonces: &[AggregatedNonce],
637 part_sig: PartialSignature,
638 pub_nonce: PublicNonce,
639 ) -> Result<(), CosignSignatureError> {
640 debug_assert!(!node.is_leaf());
641
642 let sighash = self.internal_sighashes[node.internal_idx()];
643
644 let key_agg = {
645 let cosign_pubkeys = node.leaves()
646 .filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
647 .chain(self.spec.global_cosign_pubkeys.iter().copied());
648 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
649 let taproot = self.spec.internal_taproot(agg_pk);
650 let taptweak = taproot.tap_tweak().to_byte_array();
651 musig::tweaked_key_agg(cosign_pubkeys, taptweak).0
652 };
653 let agg_nonce = agg_nonces.get(node.internal_idx())
654 .ok_or(CosignSignatureError::NotEnoughNonces)?;
655 let session = musig::Session::new(&key_agg, *agg_nonce, &sighash.to_byte_array());
656 let ok = session.partial_verify(&key_agg, &part_sig, &pub_nonce, musig::pubkey_to(pk));
657 if !ok {
658 return Err(CosignSignatureError::invalid_sig(pk));
659 }
660 Ok(())
661 }
662
663 pub fn verify_branch_cosign_partial_sigs(
668 &self,
669 cosign_agg_nonces: &[AggregatedNonce],
670 request: &VtxoLeafSpec,
671 cosign_pub_nonces: &[PublicNonce],
672 cosign_part_sigs: &[PartialSignature],
673 ) -> Result<(), String> {
674 assert_eq!(cosign_agg_nonces.len(), self.nb_internal_nodes());
675
676 let cosign_pubkey = request.cosign_pubkey.ok_or("no cosign pubkey for request")?;
677 let leaf_idx = self.spec.leaf_idx_of(request).ok_or("request not in tree")?;
678
679 let internal_branch = self.tree.iter_branch(leaf_idx).skip(1);
681
682 match internal_branch.clone().count().cmp(&cosign_part_sigs.len()) {
684 cmp::Ordering::Less => return Err("too few partial signatures".into()),
685 cmp::Ordering::Greater => return Err("too many partial signatures".into()),
686 cmp::Ordering::Equal => {},
687 }
688
689 let mut part_sigs_iter = cosign_part_sigs.iter();
690 let mut pub_nonce_iter = cosign_pub_nonces.iter().enumerate();
691 for node in internal_branch {
692 let pub_nonce = loop {
693 let next = pub_nonce_iter.next().ok_or("not enough pub nonces")?;
694 if next.0 == node.internal_level() {
695 break next.1;
696 }
697 };
698 self.verify_internal_node_cosign_partial_sig(
699 node,
700 cosign_pubkey,
701 cosign_agg_nonces,
702 part_sigs_iter.next().ok_or("not enough sigs")?.clone(),
703 *pub_nonce,
704 ).map_err(|e| format!("part sig verification failed: {}", e))?;
705 }
706
707 Ok(())
708 }
709
710 pub fn verify_global_cosign_partial_sigs(
715 &self,
716 pk: PublicKey,
717 agg_nonces: &[AggregatedNonce],
718 pub_nonces: &[PublicNonce],
719 part_sigs: &[PartialSignature],
720 ) -> Result<(), CosignSignatureError> {
721 for node in self.tree.iter_internal() {
722 let sigs = *part_sigs.get(node.internal_idx())
723 .ok_or_else(|| CosignSignatureError::missing_sig(pk))?;
724 let nonces = *pub_nonces.get(node.internal_idx())
725 .ok_or_else(|| CosignSignatureError::NotEnoughNonces)?;
726 self.verify_internal_node_cosign_partial_sig(node, pk, agg_nonces, sigs, nonces)?;
727 }
728
729 Ok(())
730 }
731
732 pub fn combine_partial_signatures(
741 &self,
742 cosign_agg_nonces: &[AggregatedNonce],
743 branch_part_sigs: &HashMap<PublicKey, Vec<PartialSignature>>,
744 global_signer_part_sigs: &[impl AsRef<[PartialSignature]>],
745 ) -> Result<Vec<schnorr::Signature>, CosignSignatureError> {
746 let mut leaf_part_sigs = branch_part_sigs.iter()
748 .map(|(pk, sigs)| (pk, sigs.iter().collect()))
749 .collect::<HashMap<_, VecDeque<_>>>();
750
751 if global_signer_part_sigs.len() != self.spec.global_cosign_pubkeys.len() {
752 return Err(CosignSignatureError::Invalid(
753 "invalid nb of global cosigner partial signatures",
754 ));
755 }
756 for (pk, sigs) in self.spec.global_cosign_pubkeys.iter().zip(global_signer_part_sigs) {
757 if sigs.as_ref().len() != self.nb_internal_nodes() {
758 return Err(CosignSignatureError::MissingSignature { pk: *pk });
761 }
762 }
763
764 let max_level = match self.tree.root().is_leaf() {
765 true => 0,
766 false => self.tree.root().internal_level(),
767 };
768 self.tree.iter_internal().map(|node| {
769 let mut cosign_pks = Vec::with_capacity(max_level.saturating_add(1));
770 let mut part_sigs = Vec::with_capacity(max_level.saturating_add(1));
771 for leaf in node.leaves() {
772 if let Some(cosign_pk) = self.spec.vtxos[leaf].cosign_pubkey {
773 let part_sig = leaf_part_sigs.get_mut(&cosign_pk)
774 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?
775 .pop_front()
776 .ok_or(CosignSignatureError::missing_sig(cosign_pk))?;
777 cosign_pks.push(cosign_pk);
778 part_sigs.push(part_sig);
779 }
780 }
781 cosign_pks.extend(&self.spec.global_cosign_pubkeys);
783 for sigs in global_signer_part_sigs {
784 part_sigs.push(sigs.as_ref().get(node.internal_idx()).expect("checked before"));
785 }
786
787 let agg_pk = self.cosign_agg_pks[node.idx()].x_only_public_key().0;
788 let taproot = self.spec.internal_taproot(agg_pk);
789 let agg_nonce = *cosign_agg_nonces.get(node.internal_idx())
790 .ok_or(CosignSignatureError::NotEnoughNonces)?;
791 let sighash = self.internal_sighashes[node.internal_idx()].to_byte_array();
792 let tweak = taproot.tap_tweak().to_byte_array();
793 Ok(musig::combine_partial_signatures(
794 cosign_pks, agg_nonce, sighash, Some(tweak), &part_sigs,
795 ))
796 }).collect()
797 }
798
799 pub fn verify_cosign_sigs(
803 &self,
804 signatures: &[schnorr::Signature],
805 ) -> Result<(), XOnlyPublicKey> {
806 for node in self.tree.iter_internal() {
807 let sighash = self.internal_sighashes[node.internal_idx()];
808 let agg_pk = &self.cosign_agg_pks[node.idx()].x_only_public_key().0;
809 let pk = self.spec.internal_taproot(*agg_pk).output_key().to_x_only_public_key();
810 let sig = signatures.get(node.internal_idx()).ok_or_else(|| pk)?;
811 if SECP.verify_schnorr(sig, &sighash.into(), &pk).is_err() {
812 return Err(pk);
813 }
814 }
815 Ok(())
816 }
817
818 pub fn into_signed_tree(
822 self,
823 signatures: Vec<schnorr::Signature>,
824 ) -> SignedVtxoTreeSpec {
825 SignedVtxoTreeSpec {
826 spec: self.spec,
827 utxo: self.utxo,
828 cosign_sigs: signatures,
829 }
830 }
831}
832
833#[derive(PartialEq, Eq, thiserror::Error)]
835pub enum CosignSignatureError {
836 #[error("missing cosign signature from pubkey {pk}")]
837 MissingSignature { pk: PublicKey },
838 #[error("invalid cosign signature from pubkey {pk}")]
839 InvalidSignature { pk: PublicKey },
840 #[error("not enough nonces")]
841 NotEnoughNonces,
842 #[error("invalid cosign signatures: {0}")]
843 Invalid(&'static str),
844}
845
846impl fmt::Debug for CosignSignatureError {
847 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
848 fmt::Display::fmt(self, f)
849 }
850}
851
852impl CosignSignatureError {
853 fn missing_sig(cosign_pk: PublicKey) -> CosignSignatureError {
854 CosignSignatureError::MissingSignature { pk: cosign_pk }
855 }
856 fn invalid_sig(cosign_pk: PublicKey) -> CosignSignatureError {
857 CosignSignatureError::InvalidSignature { pk: cosign_pk }
858 }
859}
860
861#[derive(Debug, Clone, PartialEq)]
863pub struct SignedVtxoTreeSpec {
864 pub spec: VtxoTreeSpec,
865 pub utxo: OutPoint,
866 pub cosign_sigs: Vec<schnorr::Signature>,
868}
869
870impl SignedVtxoTreeSpec {
871 pub fn new(
873 spec: VtxoTreeSpec,
874 utxo: OutPoint,
875 cosign_signatures: Vec<schnorr::Signature>,
876 ) -> SignedVtxoTreeSpec {
877 SignedVtxoTreeSpec { spec, utxo, cosign_sigs: cosign_signatures }
878 }
879
880 pub fn nb_leaves(&self) -> usize {
881 self.spec.nb_leaves()
882 }
883
884 pub fn exit_branch(&self, leaf_idx: usize) -> Vec<Transaction> {
891 let txs = self.all_final_txs();
892 let tree = Tree::new(self.spec.nb_leaves());
893 let mut ret = tree.iter_branch(leaf_idx)
894 .map(|n| txs[n.idx()].clone())
895 .collect::<Vec<_>>();
896 ret.reverse();
897 ret
898 }
899
900 pub fn all_final_txs(&self) -> Vec<Transaction> {
902 self.spec.final_transactions(self.utxo, &self.cosign_sigs)
903 }
904
905 pub fn into_cached_tree(self) -> CachedSignedVtxoTree {
906 CachedSignedVtxoTree {
907 txs: self.all_final_txs(),
908 spec: self,
909 }
910 }
911
912 pub fn detect_hashlock_version(&self) -> Result<HashlockVersion, HashlockVersionDetectError> {
919 if self.spec.nb_leaves() == 1 {
920 return Ok(HashlockVersion::V1);
921 }
922 let sig = self.cosign_sigs.first().ok_or(HashlockVersionDetectError)?;
923
924 for version in [HashlockVersion::V1, HashlockVersion::V0] {
925 let mut spec = self.spec.clone();
926 spec.hashlock_version = version;
927 let unsigned = spec.into_unsigned_tree(self.utxo);
928 let sighash = unsigned.internal_sighashes[0];
929 let agg_pk = unsigned.cosign_agg_pks[unsigned.nb_leaves()].x_only_public_key().0;
930 let pk = unsigned.spec.internal_taproot(agg_pk).output_key().to_x_only_public_key();
931 if SECP.verify_schnorr(sig, &sighash.into(), &pk).is_ok() {
932 return Ok(version);
933 }
934 }
935 Err(HashlockVersionDetectError)
936 }
937
938 pub fn with_detected_hashlock_version(mut self) -> Result<Self, HashlockVersionDetectError> {
945 self.spec.hashlock_version = self.detect_hashlock_version()?;
946 Ok(self)
947 }
948}
949
950#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
953#[error("tree cosign signatures don't verify for any hashlock version")]
954pub struct HashlockVersionDetectError;
955
956pub struct CachedSignedVtxoTree {
960 pub spec: SignedVtxoTreeSpec,
961 pub txs: Vec<Transaction>,
963}
964
965impl CachedSignedVtxoTree {
966 pub fn exit_branch(&self, leaf_idx: usize) -> Vec<&Transaction> {
970 let tree = Tree::new(self.spec.spec.nb_leaves());
971 let mut ret = tree.iter_branch(leaf_idx)
972 .map(|n| &self.txs[n.idx()])
973 .collect::<Vec<_>>();
974 ret.reverse();
975 ret
976 }
977
978 pub fn nb_leaves(&self) -> usize {
979 self.spec.nb_leaves()
980 }
981
982 pub fn nb_nodes(&self) -> usize {
983 Tree::nb_nodes_for_leaves(self.spec.nb_leaves())
984 }
985
986 pub fn all_final_txs(&self) -> &[Transaction] {
991 &self.txs
992 }
993
994 pub fn unsigned_leaf_txs(&self) -> &[Transaction] {
998 &self.txs[..self.nb_leaves()]
999 }
1000
1001 pub fn internal_node_txs(&self) -> &[Transaction] {
1005 &self.txs[self.nb_leaves()..]
1006 }
1007
1008 fn build_genesis_item_at<'a>(&self, tree: &Tree, node_idx: usize, output_idx: u8) -> GenesisItem {
1010 debug_assert_eq!(self.nb_leaves(), tree.nb_leaves(), "tree corresponds to self");
1011 debug_assert!(node_idx < tree.nb_nodes(), "Node index is in tree");
1012
1013 let other_outputs = self.txs.get(node_idx).expect("Each node has a tx")
1014 .output.iter().enumerate()
1015 .filter(|(i, _)| *i != output_idx as usize) .filter(|(_, out)| !out.is_p2a_fee_anchor()) .map(|(_, out)| out)
1018 .cloned()
1019 .collect();
1020
1021 let node = tree.node_at(node_idx);
1022
1023 let transition = if node.is_leaf() {
1024 debug_assert_eq!(output_idx, 0, "Leafs have a single output");
1025 let req = self.spec.spec.vtxos.get(node_idx).expect("Every leaf has a spec");
1026 match self.spec.spec.hashlock_version {
1027 HashlockVersion::V1 => GenesisTransition::new_hash_locked_cosigned(
1028 req.vtxo.policy.user_pubkey(),
1029 None,
1030 MaybePreimage::Hash(req.unlock_hash),
1031 ),
1032 HashlockVersion::V0 => GenesisTransition::new_hash_locked_cosigned_v0(
1033 req.vtxo.policy.user_pubkey(),
1034 None,
1035 MaybePreimage::Hash(req.unlock_hash),
1036 ),
1037 }
1038 } else {
1039 let pubkeys = node.leaves()
1040 .filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
1041 .chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
1042 .collect();
1043
1044 let sig = self.spec.cosign_sigs.get(node.internal_idx())
1045 .expect("enough sigs for all nodes");
1046
1047 GenesisTransition::new_cosigned(pubkeys, Some(*sig))
1048 };
1049
1050 let fee_amount = Amount::ZERO;
1051 GenesisItem {transition, output_idx, other_outputs, fee_amount }
1052 }
1053
1054 fn build_internal_vtxo(&self, node_idx: usize) -> ServerVtxo<Full> {
1060 let tree = Tree::new(self.spec.spec.nb_leaves());
1061 assert!(node_idx < tree.nb_nodes(), "node_idx out of range");
1062
1063 let mut genesis = tree.iter_branch_with_output(node_idx)
1064 .map(|(idx, child_idx)| self.build_genesis_item_at(&tree, idx, u8::try_from(child_idx).expect("tree child index fits in u8")))
1065 .collect::<Vec<_>>();
1066 genesis.reverse();
1067
1068 let node = tree.node_at(node_idx);
1069 let spec = &self.spec.spec;
1070 let (point, amount) = match tree.parent_idx_of_with_sibling_idx(node_idx) {
1071 None => (self.spec.utxo, spec.total_required_value()),
1072 Some((parent_idx, child_idx)) => {
1073 let parent_tx = self.txs.get(parent_idx).expect("parent tx exists");
1074 let point = OutPoint::new(parent_tx.compute_txid(), u32::try_from(child_idx).expect("tree child index fits in u32"));
1075 (point, parent_tx.output[child_idx].value)
1076 }
1077 };
1078
1079 let policy = if node.is_leaf() {
1080 let req = spec.vtxos.get(node_idx).expect("one vtxo request for every leaf");
1081 match spec.hashlock_version {
1082 HashlockVersion::V1 => ServerVtxoPolicy::new_hark_leaf(
1083 req.vtxo.policy.user_pubkey(), req.unlock_hash,
1084 ),
1085 HashlockVersion::V0 => ServerVtxoPolicy::new_hark_leaf_v0(
1086 req.vtxo.policy.user_pubkey(), req.unlock_hash,
1087 ),
1088 }
1089 } else {
1090 let agg_pk = musig::combine_keys(
1091 node.leaves().filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
1092 .chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
1093 );
1094 ServerVtxoPolicy::new_expiry(agg_pk.x_only_public_key().0)
1095 };
1096
1097 ServerVtxo {
1098 policy,
1099 amount,
1100 expiry_height: self.spec.spec.expiry_height,
1101 server_pubkey: self.spec.spec.server_pubkey,
1102 exit_delta: self.spec.spec.exit_delta,
1103 anchor_point: self.spec.utxo,
1104 genesis: Full { items: genesis },
1105 point,
1106 }
1107 }
1108
1109 pub fn build_vtxo(&self, leaf_idx: usize) -> Vtxo<Full> {
1113 let req = self.spec.spec.vtxos.get(leaf_idx).expect("index is not a leaf");
1114
1115 let genesis = {
1116 let tree = Tree::new(self.spec.spec.nb_leaves());
1117 let leaf = self.build_genesis_item_at(&tree, leaf_idx, 0);
1118 let internal = tree.iter_branch_with_output(leaf_idx)
1119 .map(|(node_idx, child_idx)| {
1120 self.build_genesis_item_at(&tree, node_idx, u8::try_from(child_idx).expect("tree child index fits in u8"))
1121 });
1122
1123 let mut genesis = [leaf].into_iter().chain(internal).collect::<Vec<_>>();
1124 genesis.reverse();
1125 genesis
1126 };
1127
1128 Vtxo {
1129 amount: req.vtxo.amount,
1130 expiry_height: self.spec.spec.expiry_height,
1131 server_pubkey: self.spec.spec.server_pubkey,
1132 exit_delta: self.spec.spec.exit_delta,
1133 anchor_point: self.spec.utxo,
1134 genesis: Full { items: genesis },
1135 policy: req.vtxo.policy.clone(),
1136 point: {
1137 let leaf_tx = self.txs.get(leaf_idx).expect("leaf idx exists");
1138 OutPoint::new(leaf_tx.compute_txid(), 0)
1139 },
1140 }
1141 }
1142
1143 pub fn internal_vtxos(&self) -> impl Iterator<Item = (ServerVtxo<Full>, Txid)> + '_ {
1146 (0..self.nb_nodes()).map(|idx| {
1147 let vtxo = self.build_internal_vtxo(idx);
1148 let spending_txid = self.txs[idx].compute_txid();
1149 (vtxo, spending_txid)
1150 })
1151 }
1152
1153 pub fn output_vtxos(&self) -> impl Iterator<Item = Vtxo<Full>> + ExactSizeIterator + '_ {
1155 (0..self.nb_leaves()).map(|idx| self.build_vtxo(idx))
1156 }
1157
1158 pub fn spend_info(&self) -> impl Iterator<Item = (VtxoId, Txid)> + '_ {
1159 self.internal_vtxos()
1160 .map(|(vtxo, spending_txid)| (vtxo.id(), spending_txid))
1161 }
1162}
1163
1164pub fn hashlocked_leaf_sighash(
1166 leaf_tx: &Transaction,
1167 user_pubkey: PublicKey,
1168 server_pubkey: PublicKey,
1169 unlock_hash: UnlockHash,
1170 prev_txout: &TxOut,
1171) -> TapSighash {
1172 let agg_pk = musig::combine_keys([user_pubkey, server_pubkey])
1173 .x_only_public_key().0;
1174 let clause = unlock_clause(agg_pk, unlock_hash);
1175 let leaf_hash = TapLeafHash::from_script(&clause, bitcoin::taproot::LeafVersion::TapScript);
1176 let mut shc = SighashCache::new(leaf_tx);
1177 shc.taproot_script_spend_signature_hash(
1178 0, &sighash::Prevouts::All(&[prev_txout]),
1180 leaf_hash,
1181 TapSighashType::Default,
1182 ).expect("sighash error")
1183}
1184
1185pub fn hashlocked_leaf_sighash_v0(
1187 leaf_tx: &Transaction,
1188 user_pubkey: PublicKey,
1189 server_pubkey: PublicKey,
1190 unlock_hash: UnlockHash,
1191 prev_txout: &TxOut,
1192) -> TapSighash {
1193 let agg_pk = musig::combine_keys([user_pubkey, server_pubkey])
1194 .x_only_public_key().0;
1195 let clause = unlock_clause_v0(agg_pk, unlock_hash);
1196 let leaf_hash = TapLeafHash::from_script(&clause, bitcoin::taproot::LeafVersion::TapScript);
1197 let mut shc = SighashCache::new(leaf_tx);
1198 shc.taproot_script_spend_signature_hash(
1199 0, &sighash::Prevouts::All(&[prev_txout]),
1201 leaf_hash,
1202 TapSighashType::Default,
1203 ).expect("sighash error")
1204}
1205
1206#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
1209#[error("VTXO {0} is not a hArk leaf VTXO")]
1210pub struct NotHarkLeafError(VtxoId);
1211
1212fn hashlocked_leaf_sighash_from_vtxo(
1218 vtxo: &Vtxo<Full>,
1219 chain_anchor: &Transaction,
1220) -> Result<TapSighash, NotHarkLeafError> {
1221 assert_eq!(chain_anchor.compute_txid(), vtxo.chain_anchor().txid);
1222
1223 let mut preleaf_txout = chain_anchor.output[vtxo.chain_anchor().vout as usize].clone();
1225 let mut leaf_tx = None;
1226 let mut peekable_iter = vtxo.transactions().peekable();
1227 while let Some(item) = peekable_iter.next() {
1228 if peekable_iter.peek().is_some() {
1231 preleaf_txout = item.tx.output[item.output_idx].clone();
1232 }
1233
1234 if peekable_iter.peek().is_none() {
1236 leaf_tx = Some(item.tx);
1237 }
1238 }
1239 let leaf_tx = leaf_tx.expect("at least one tx");
1240
1241 let last_genesis = vtxo.genesis.items.last().expect("at least one genesis item");
1242 match &last_genesis.transition {
1243 GenesisTransition::HashLockedCosigned(inner) => {
1244 debug_assert_eq!(inner.user_pubkey, vtxo.user_pubkey());
1245 Ok(hashlocked_leaf_sighash(
1246 &leaf_tx, inner.user_pubkey, vtxo.server_pubkey(), inner.unlock.hash(),
1247 &preleaf_txout,
1248 ))
1249 },
1250 GenesisTransition::HashLockedCosigned_v0(inner) => {
1251 debug_assert_eq!(inner.user_pubkey, vtxo.user_pubkey());
1252 Ok(hashlocked_leaf_sighash_v0(
1253 &leaf_tx, inner.user_pubkey, vtxo.server_pubkey(), inner.unlock.hash(),
1254 &preleaf_txout,
1255 ))
1256 },
1257 _ => Err(NotHarkLeafError(vtxo.id())),
1258 }
1259}
1260
1261#[derive(Debug)]
1262pub struct LeafVtxoCosignRequest {
1263 pub vtxo_id: VtxoId,
1264 pub pub_nonce: musig::PublicNonce,
1265}
1266
1267pub struct LeafVtxoCosignContext<'a> {
1268 key: &'a Keypair,
1269 pub_nonce: musig::PublicNonce,
1270 sec_nonce: musig::SecretNonce,
1271 sighash: TapSighash,
1272}
1273
1274impl<'a> LeafVtxoCosignContext<'a> {
1275 pub fn new(
1281 vtxo: &Vtxo<Full>,
1282 chain_anchor: &Transaction,
1283 key: &'a Keypair,
1284 ) -> Result<(Self, LeafVtxoCosignRequest), NotHarkLeafError> {
1285 let sighash = hashlocked_leaf_sighash_from_vtxo(&vtxo, chain_anchor)?;
1286 let (sec_nonce, pub_nonce) = musig::nonce_pair_with_msg(key, &sighash.to_byte_array());
1287 let vtxo_id = vtxo.id();
1288 let req = LeafVtxoCosignRequest { vtxo_id, pub_nonce };
1289 let ret = Self { key, pub_nonce, sec_nonce, sighash };
1290 Ok((ret, req))
1291 }
1292
1293 pub fn finalize(
1295 self,
1296 vtxo: &mut Vtxo<Full>,
1297 response: LeafVtxoCosignResponse,
1298 ) -> bool {
1299 let agg_nonce = musig::nonce_agg(&[&self.pub_nonce, &response.public_nonce]);
1300 let (_part_sig, final_sig) = musig::partial_sign(
1301 [vtxo.user_pubkey(), vtxo.server_pubkey()],
1302 agg_nonce,
1303 self.key,
1304 self.sec_nonce,
1305 self.sighash.to_byte_array(),
1306 None,
1307 Some(&[&response.partial_signature]),
1308 );
1309 let final_sig = final_sig.expect("has other sigs");
1310
1311 let pubkey = musig::combine_keys([vtxo.user_pubkey(), vtxo.server_pubkey()])
1312 .x_only_public_key().0;
1313 debug_assert_eq!(pubkey, leaf_cosign_taproot(
1314 vtxo.user_pubkey(),
1315 vtxo.server_pubkey(),
1316 vtxo.expiry_height(),
1317 vtxo.unlock_hash().expect("checked is hark vtxo"),
1318 ).internal_key());
1319 if SECP.verify_schnorr(&final_sig, &self.sighash.into(), &pubkey).is_err() {
1320 return false;
1321 }
1322
1323 vtxo.provide_unlock_signature(final_sig)
1324 }
1325}
1326
1327#[derive(Debug)]
1328pub struct LeafVtxoCosignResponse {
1329 pub public_nonce: musig::PublicNonce,
1330 pub partial_signature: musig::PartialSignature,
1331}
1332
1333impl LeafVtxoCosignResponse {
1334 pub fn new_cosign(
1338 request: &LeafVtxoCosignRequest,
1339 vtxo: &Vtxo<Full>,
1340 chain_anchor: &Transaction,
1341 server_key: &Keypair,
1342 ) -> Result<Self, NotHarkLeafError> {
1343 debug_assert_eq!(server_key.public_key(), vtxo.server_pubkey());
1344 let sighash = hashlocked_leaf_sighash_from_vtxo(&vtxo, chain_anchor)?;
1345 let (public_nonce, partial_signature) = musig::deterministic_partial_sign(
1346 server_key,
1347 [vtxo.user_pubkey()],
1348 &[&request.pub_nonce],
1349 sighash.to_byte_array(),
1350 None,
1351 );
1352 Ok(Self { public_nonce, partial_signature })
1353 }
1354}
1355
1356pub mod builder {
1357 use std::collections::HashMap;
1364 use std::marker::PhantomData;
1365
1366 use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut};
1367 use bitcoin::hashes::{sha256, Hash};
1368 use bitcoin::secp256k1::{Keypair, PublicKey};
1369 use bitcoin_ext::{BlockDelta, BlockHeight};
1370
1371 use crate::tree::signed::{UnlockHash, UnlockPreimage, VtxoLeafSpec};
1372 use crate::{musig, VtxoRequest};
1373 use crate::error::IncorrectSigningKeyError;
1374
1375 use super::{CosignSignatureError, SignedVtxoTreeSpec, UnsignedVtxoTree, VtxoTreeSpec};
1376
1377 pub mod state {
1378 mod sealed {
1379 pub trait Sealed {}
1381 impl Sealed for super::Preparing {}
1382 impl Sealed for super::CanGenerateNonces {}
1383 impl Sealed for super::ServerCanCosign {}
1384 impl Sealed for super::CanFinish {}
1385 }
1386
1387 pub trait BuilderState: sealed::Sealed {}
1389
1390 pub struct Preparing;
1392 impl BuilderState for Preparing {}
1393
1394 pub struct CanGenerateNonces;
1397 impl BuilderState for CanGenerateNonces {}
1398
1399 pub struct ServerCanCosign;
1401 impl BuilderState for ServerCanCosign {}
1402
1403 pub struct CanFinish;
1406 impl BuilderState for CanFinish {}
1407
1408 pub trait CanSign: BuilderState {}
1411 impl CanSign for ServerCanCosign {}
1412 impl CanSign for CanFinish {}
1413 }
1414
1415 enum BuilderTree {
1417 Spec(VtxoTreeSpec),
1418 Unsigned(UnsignedVtxoTree),
1419 }
1420
1421 impl BuilderTree {
1422 fn unsigned_tree(&self) -> Option<&UnsignedVtxoTree> {
1423 match self {
1424 BuilderTree::Spec(_) => None,
1425 BuilderTree::Unsigned(t) => Some(t),
1426 }
1427 }
1428
1429 fn into_unsigned_tree(self) -> Option<UnsignedVtxoTree> {
1430 match self {
1431 BuilderTree::Spec(_) => None,
1432 BuilderTree::Unsigned(t) => Some(t),
1433 }
1434 }
1435 }
1436
1437 pub struct SignedTreeBuilder<S: state::BuilderState> {
1441 pub expiry_height: BlockHeight,
1442 pub server_pubkey: PublicKey,
1443 pub exit_delta: BlockDelta,
1444 pub cosign_pubkey: PublicKey,
1446 pub unlock_preimage: UnlockPreimage,
1448
1449 tree: BuilderTree,
1450
1451 user_pub_nonces: Vec<musig::PublicNonce>,
1453 user_sec_nonces: Option<Vec<musig::SecretNonce>>,
1456 _state: PhantomData<S>,
1457 }
1458
1459 impl<T: state::BuilderState> SignedTreeBuilder<T> {
1460 fn tree_spec(&self) -> &VtxoTreeSpec {
1461 match self.tree {
1462 BuilderTree::Spec(ref s) => s,
1463 BuilderTree::Unsigned(ref t) => &t.spec,
1464 }
1465 }
1466
1467 pub fn total_required_value(&self) -> Amount {
1469 self.tree_spec().total_required_value()
1470 }
1471
1472 pub fn funding_script_pubkey(&self) -> ScriptBuf {
1474 self.tree_spec().funding_tx_script_pubkey()
1475 }
1476
1477 pub fn funding_txout(&self) -> TxOut {
1479 let spec = self.tree_spec();
1480 TxOut {
1481 value: spec.total_required_value(),
1482 script_pubkey: spec.funding_tx_script_pubkey(),
1483 }
1484 }
1485 }
1486
1487 impl<T: state::CanSign> SignedTreeBuilder<T> {
1488 pub fn user_pub_nonces(&self) -> &[musig::PublicNonce] {
1490 &self.user_pub_nonces
1491 }
1492 }
1493
1494 #[derive(Debug, thiserror::Error)]
1495 #[error("signed VTXO tree builder error: {0}")]
1496 pub struct SignedTreeBuilderError(&'static str);
1497
1498 impl SignedTreeBuilder<state::Preparing> {
1499 pub fn construct_tree_spec(
1501 vtxos: impl IntoIterator<Item = VtxoRequest>,
1502 cosign_pubkey: PublicKey,
1503 unlock_hash: UnlockHash,
1504 expiry_height: BlockHeight,
1505 server_pubkey: PublicKey,
1506 server_cosign_pubkey: PublicKey,
1507 exit_delta: BlockDelta,
1508 ) -> Result<VtxoTreeSpec, SignedTreeBuilderError> {
1509 let reqs = vtxos.into_iter()
1510 .map(|vtxo| VtxoLeafSpec {
1511 vtxo: vtxo,
1512 cosign_pubkey: None,
1513 unlock_hash: unlock_hash,
1514 })
1515 .collect::<Vec<_>>();
1516 if reqs.len() < 2 {
1517 return Err(SignedTreeBuilderError("need to have at least 2 VTXOs in tree"));
1518 }
1519 Ok(VtxoTreeSpec::new(
1520 reqs,
1521 server_pubkey,
1522 expiry_height,
1523 exit_delta,
1524 vec![cosign_pubkey, server_cosign_pubkey],
1527 ))
1528 }
1529
1530 pub fn new(
1532 vtxos: impl IntoIterator<Item = VtxoRequest>,
1533 cosign_pubkey: PublicKey,
1534 unlock_preimage: UnlockPreimage,
1535 expiry_height: BlockHeight,
1536 server_pubkey: PublicKey,
1537 server_cosign_pubkey: PublicKey,
1538 exit_delta: BlockDelta,
1539 ) -> Result<SignedTreeBuilder<state::Preparing>, SignedTreeBuilderError> {
1540 let tree = Self::construct_tree_spec(
1541 vtxos,
1542 cosign_pubkey,
1543 sha256::Hash::hash(&unlock_preimage),
1544 expiry_height,
1545 server_pubkey,
1546 server_cosign_pubkey,
1547 exit_delta,
1548 )?;
1549
1550 Ok(SignedTreeBuilder {
1551 expiry_height, server_pubkey, exit_delta, cosign_pubkey, unlock_preimage,
1552 tree: BuilderTree::Spec(tree),
1553 user_pub_nonces: Vec::new(),
1554 user_sec_nonces: None,
1555 _state: PhantomData,
1556 })
1557 }
1558
1559 pub fn set_utxo(self, utxo: OutPoint) -> SignedTreeBuilder<state::CanGenerateNonces> {
1561 let unsigned_tree = match self.tree {
1562 BuilderTree::Spec(s) => s.into_unsigned_tree(utxo),
1563 BuilderTree::Unsigned(t) => t, };
1565 SignedTreeBuilder {
1566 tree: BuilderTree::Unsigned(unsigned_tree),
1567
1568 expiry_height: self.expiry_height,
1569 server_pubkey: self.server_pubkey,
1570 exit_delta: self.exit_delta,
1571 cosign_pubkey: self.cosign_pubkey,
1572 unlock_preimage: self.unlock_preimage,
1573 user_pub_nonces: self.user_pub_nonces,
1574 user_sec_nonces: self.user_sec_nonces,
1575 _state: PhantomData,
1576 }
1577 }
1578 }
1579
1580 impl SignedTreeBuilder<state::CanGenerateNonces> {
1581 pub fn generate_user_nonces(
1583 self,
1584 cosign_key: &Keypair,
1585 ) -> SignedTreeBuilder<state::CanFinish> {
1586 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1587
1588 let mut cosign_sec_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1589 let mut cosign_pub_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1590 for sh in &unsigned_tree.internal_sighashes {
1591 let pair = musig::nonce_pair_with_msg(&cosign_key, &sh.to_byte_array());
1592 cosign_sec_nonces.push(pair.0);
1593 cosign_pub_nonces.push(pair.1);
1594 }
1595
1596 SignedTreeBuilder {
1597 user_pub_nonces: cosign_pub_nonces,
1598 user_sec_nonces: Some(cosign_sec_nonces),
1599
1600 expiry_height: self.expiry_height,
1601 server_pubkey: self.server_pubkey,
1602 exit_delta: self.exit_delta,
1603 cosign_pubkey: self.cosign_pubkey,
1604 unlock_preimage: self.unlock_preimage,
1605 tree: self.tree,
1606 _state: PhantomData,
1607 }
1608 }
1609 }
1610
1611 #[derive(Debug, Clone)]
1613 pub struct SignedTreeCosignResponse {
1614 pub pub_nonces: Vec<musig::PublicNonce>,
1615 pub partial_signatures: Vec<musig::PartialSignature>,
1616 }
1617
1618 impl SignedTreeBuilder<state::ServerCanCosign> {
1619 pub fn new_for_cosign(
1621 vtxos: impl IntoIterator<Item = VtxoRequest>,
1622 cosign_pubkey: PublicKey,
1623 unlock_preimage: UnlockPreimage,
1624 expiry_height: BlockHeight,
1625 server_pubkey: PublicKey,
1626 server_cosign_pubkey: PublicKey,
1627 exit_delta: BlockDelta,
1628 utxo: OutPoint,
1629 user_pub_nonces: Vec<musig::PublicNonce>,
1630 ) -> Result<SignedTreeBuilder<state::ServerCanCosign>, SignedTreeBuilderError> {
1631 let unsigned_tree = SignedTreeBuilder::construct_tree_spec(
1632 vtxos,
1633 cosign_pubkey,
1634 sha256::Hash::hash(&unlock_preimage),
1635 expiry_height,
1636 server_pubkey,
1637 server_cosign_pubkey,
1638 exit_delta,
1639 )?.into_unsigned_tree(utxo);
1640
1641 Ok(SignedTreeBuilder {
1642 expiry_height,
1643 server_pubkey,
1644 exit_delta,
1645 cosign_pubkey,
1646 unlock_preimage,
1647 user_pub_nonces,
1648 tree: BuilderTree::Unsigned(unsigned_tree),
1649 user_sec_nonces: None,
1650 _state: PhantomData,
1651 })
1652 }
1653
1654 pub fn server_cosign(&self, server_cosign_key: &Keypair) -> SignedTreeCosignResponse {
1656 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1657
1658 let mut sec_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1659 let mut pub_nonces = Vec::with_capacity(unsigned_tree.internal_sighashes.len());
1660 for sh in &unsigned_tree.internal_sighashes {
1661 let pair = musig::nonce_pair_with_msg(&server_cosign_key, &sh.to_byte_array());
1662 sec_nonces.push(pair.0);
1663 pub_nonces.push(pair.1);
1664 }
1665
1666 let agg_nonces = self.user_pub_nonces().iter().zip(&pub_nonces)
1667 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1668 .collect::<Vec<_>>();
1669
1670 let sigs = unsigned_tree.cosign_tree(&agg_nonces, &server_cosign_key, sec_nonces);
1671
1672 SignedTreeCosignResponse {
1673 pub_nonces,
1674 partial_signatures: sigs,
1675 }
1676 }
1677 }
1678
1679 impl SignedTreeBuilder<state::CanFinish> {
1680 pub fn verify_cosign_response(
1682 &self,
1683 server_cosign: &SignedTreeCosignResponse,
1684 ) -> Result<(), CosignSignatureError> {
1685 let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1686
1687 let agg_nonces = self.user_pub_nonces().iter()
1688 .zip(&server_cosign.pub_nonces)
1689 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1690 .collect::<Vec<_>>();
1691
1692 unsigned_tree.verify_global_cosign_partial_sigs(
1693 *unsigned_tree.spec.global_cosign_pubkeys.get(1).expect("state invariant"),
1694 &agg_nonces,
1695 &server_cosign.pub_nonces,
1696 &server_cosign.partial_signatures,
1697 )
1698 }
1699
1700 pub fn build_tree(
1701 self,
1702 server_cosign: &SignedTreeCosignResponse,
1703 cosign_key: &Keypair,
1704 ) -> Result<SignedVtxoTreeSpec, IncorrectSigningKeyError> {
1705 if cosign_key.public_key() != self.cosign_pubkey {
1706 return Err(IncorrectSigningKeyError {
1707 required: Some(self.cosign_pubkey),
1708 provided: cosign_key.public_key(),
1709 });
1710 }
1711
1712 let agg_nonces = self.user_pub_nonces().iter().zip(&server_cosign.pub_nonces)
1713 .map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1714 .collect::<Vec<_>>();
1715
1716 let unsigned_tree = self.tree.into_unsigned_tree().expect("state invariant");
1717 let sec_nonces = self.user_sec_nonces.expect("state invariant");
1718 let partial_sigs = unsigned_tree.cosign_tree(&agg_nonces, cosign_key, sec_nonces);
1719
1720 debug_assert!(unsigned_tree.verify_global_cosign_partial_sigs(
1721 self.cosign_pubkey,
1722 &agg_nonces,
1723 &self.user_pub_nonces,
1724 &partial_sigs,
1725 ).is_ok(), "produced invalid partial signatures");
1726
1727 let sigs = unsigned_tree.combine_partial_signatures(
1728 &agg_nonces,
1729 &HashMap::new(),
1730 &[&server_cosign.partial_signatures, &partial_sigs],
1731 ).expect("should work with correct cosign signatures");
1732
1733 Ok(unsigned_tree.into_signed_tree(sigs))
1734 }
1735 }
1736}
1737
1738const VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1740
1741impl ProtocolEncoding for VtxoTreeSpec {
1742 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1743 w.emit_u8(VTXO_TREE_SPEC_VERSION)?;
1744 w.emit_u32(self.expiry_height)?;
1745 self.server_pubkey.encode(w)?;
1746 w.emit_u16(self.exit_delta)?;
1747 LengthPrefixedVector::new(&self.global_cosign_pubkeys).encode(w)?;
1748 LengthPrefixedVector::new(&self.vtxos).encode(w)?;
1749 Ok(())
1750 }
1751
1752 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1753 let version = r.read_u8()?;
1754
1755 if version != VTXO_TREE_SPEC_VERSION {
1756 return Err(ProtocolDecodingError::invalid(format_args!(
1757 "invalid VtxoTreeSpec encoding version byte: {version:#x}",
1758 )));
1759 }
1760
1761 let expiry_height = check_block_height(r.read_u32()?)
1762 .map_err(|e| ProtocolDecodingError::invalid_err(e, "expiry_height"))?;
1763 let server_pubkey = PublicKey::decode(r)?;
1764 let exit_delta = check_block_delta(r.read_u16()?)
1765 .map_err(|e| ProtocolDecodingError::invalid_err(e, "exit_delta"))?;
1766 let global_cosign_pubkeys = LengthPrefixedVector::decode(r)?.into_inner();
1767 let vtxos = LengthPrefixedVector::decode(r)?.into_inner();
1768 if vtxos.is_empty() {
1769 return Err(ProtocolDecodingError::invalid(
1770 "vtxo tree spec must have at least one leaf",
1771 ));
1772 }
1773 Ok(VtxoTreeSpec {
1774 vtxos, expiry_height, server_pubkey, exit_delta, global_cosign_pubkeys,
1775 hashlock_version: HashlockVersion::V1,
1778 })
1779 }
1780}
1781
1782const SIGNED_VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1784
1785const SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE: u8 = 0x01;
1787
1788impl ProtocolEncoding for SignedVtxoTreeSpec {
1789 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1790 w.emit_u8(SIGNED_VTXO_TREE_SPEC_VERSION)?;
1791 self.spec.encode(w)?;
1792 self.utxo.encode(w)?;
1793 LengthPrefixedVector::new(&self.cosign_sigs).encode(w)?;
1794 Ok(())
1795 }
1796
1797 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
1798 let version = r.read_u8()?;
1799 if version != SIGNED_VTXO_TREE_SPEC_VERSION
1800 && version != SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE
1801 {
1802 return Err(ProtocolDecodingError::invalid(format_args!(
1803 "invalid SignedVtxoTreeSpec encoding version byte: {version:#x}",
1804 )));
1805 }
1806 let spec = VtxoTreeSpec::decode(r)?;
1807 let utxo = OutPoint::decode(r)?;
1808 let cosign_sigs = if version == SIGNED_VTXO_TREE_SPEC_VERSION_U32_SIZE {
1809 let nb_cosign_sigs = r.read_u32()?;
1810 OversizedVectorError::check::<schnorr::Signature>(nb_cosign_sigs as usize)?;
1811 let mut cosign_sigs = Vec::with_capacity(nb_cosign_sigs as usize);
1812 for _ in 0..nb_cosign_sigs {
1813 cosign_sigs.push(schnorr::Signature::decode(r)?);
1814 }
1815 cosign_sigs
1816 } else {
1817 LengthPrefixedVector::decode(r)?.into_inner()
1818 };
1819 Ok(SignedVtxoTreeSpec { spec, utxo, cosign_sigs })
1820 }
1821}
1822
1823
1824#[cfg(test)]
1825mod test {
1826 use std::iter;
1827 use std::collections::HashMap;
1828 use std::str::FromStr;
1829
1830 use bitcoin::hashes::{siphash24, sha256, Hash, HashEngine};
1831 use bitcoin::key::rand::Rng;
1832 use bitcoin::secp256k1::{self, rand, Keypair};
1833 use bitcoin::{absolute, transaction};
1834 use rand::SeedableRng;
1835
1836 use crate::encode;
1837 use crate::test_util::{encoding_roundtrip, json_roundtrip};
1838 use crate::test_util::dummy::{DummyTestVtxoSpec, DUMMY_SERVER_KEY, DUMMY_USER_KEY};
1839 use crate::tree::signed::builder::SignedTreeBuilder;
1840 use crate::vtxo::policy::{ServerVtxoPolicy, VtxoPolicy};
1841
1842 use super::*;
1843
1844 fn test_tree_amounts(
1845 tree: &UnsignedVtxoTree,
1846 root_value: Amount,
1847 ) {
1848 let map = tree.txs.iter().map(|tx| (tx.compute_txid(), tx)).collect::<HashMap<_, _>>();
1849
1850 for (idx, tx) in tree.txs.iter().take(tree.txs.len().saturating_sub(1)).enumerate() {
1852 println!("tx #{idx}: {}", bitcoin::consensus::encode::serialize_hex(tx));
1853 let input = tx.input.iter().map(|i| {
1854 let prev = i.previous_output;
1855 map.get(&prev.txid).expect(&format!("tx {} not found", prev.txid))
1856 .output[prev.vout as usize].value
1857 }).sum::<Amount>();
1858 let output = tx.output_value();
1859 assert!(input >= output);
1860 assert_eq!(input, output);
1861 }
1862
1863 let root = tree.txs.last().unwrap();
1865 assert_eq!(root_value, root.output_value());
1866 }
1867
1868 #[test]
1869 fn vtxo_tree() {
1870 let secp = secp256k1::Secp256k1::new();
1871 let mut rand = rand::rngs::StdRng::seed_from_u64(42);
1872 let random_sig = {
1873 let key = Keypair::new(&secp, &mut rand);
1874 let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1875 let msg = secp256k1::Message::from_digest(sha.to_byte_array());
1876 secp.sign_schnorr(&msg, &key)
1877 };
1878
1879 let server_key = Keypair::new(&secp, &mut rand);
1880 let server_cosign_key = Keypair::new(&secp, &mut rand);
1881
1882 struct Req {
1883 key: Keypair,
1884 cosign_key: Keypair,
1885 amount: Amount,
1886 hash: sha256::Hash,
1887 }
1888 impl Req {
1889 fn to_vtxo(&self) -> VtxoLeafSpec {
1890 VtxoLeafSpec {
1891 vtxo: VtxoRequest {
1892 amount: self.amount,
1893 policy: VtxoPolicy::new_pubkey(self.key.public_key()),
1894 },
1895 cosign_pubkey: Some(self.cosign_key.public_key()),
1896 unlock_hash: self.hash,
1897 }
1898 }
1899 }
1900
1901 let nb_leaves = 27;
1902 let reqs = iter::repeat_with(|| Req {
1903 key: Keypair::new(&secp, &mut rand),
1904 cosign_key: Keypair::new(&secp, &mut rand),
1905 amount: Amount::from_sat(100_000),
1906 hash: sha256::Hash::from_byte_array(rand.r#gen()),
1907 }).take(nb_leaves).collect::<Vec<_>>();
1908 let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
1909
1910 let spec = VtxoTreeSpec::new(
1911 reqs.iter().map(|r| r.to_vtxo()).collect(),
1912 server_key.public_key(),
1913 101_000,
1914 2016,
1915 vec![server_cosign_key.public_key()],
1916 );
1917 assert_eq!(spec.nb_leaves(), nb_leaves);
1918 assert_eq!(spec.total_required_value().to_sat(), 2700000);
1919 let nb_nodes = spec.nb_nodes();
1920
1921 encoding_roundtrip(&spec);
1922
1923 let unsigned = spec.into_unsigned_tree(point);
1924
1925 test_tree_amounts(&unsigned, unsigned.spec.total_required_value());
1926
1927 let sighashes_hash = {
1928 let mut eng = siphash24::Hash::engine();
1929 unsigned.internal_sighashes.iter().for_each(|h| eng.input(&h[..]));
1930 siphash24::Hash::from_engine(eng)
1931 };
1932 assert_eq!(sighashes_hash.to_string(), "78ae911f557c0b86");
1933
1934 let signed = unsigned.into_signed_tree(vec![random_sig; nb_nodes]);
1935
1936 encoding_roundtrip(&signed);
1937
1938 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1939 struct JsonSignedVtxoTreeSpec {
1940 #[serde(with = "encode::serde")]
1941 pub spec: SignedVtxoTreeSpec,
1942 }
1943
1944 json_roundtrip(&JsonSignedVtxoTreeSpec { spec: signed.clone() });
1945
1946 for l in 0..nb_leaves {
1947 let exit = signed.exit_branch(l);
1948
1949 let mut iter = exit.iter().enumerate().peekable();
1951 while let Some((i, cur)) = iter.next() {
1952 if let Some((_, next)) = iter.peek() {
1953 assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
1954 }
1955 }
1956 }
1957
1958 let cached = signed.into_cached_tree();
1959 for vtxo in cached.output_vtxos() {
1960 encoding_roundtrip(&vtxo);
1961 }
1962 }
1963
1964 #[test]
1965 fn test_tree_builder() {
1966 let expiry = 100_000;
1967 let exit_delta = 24;
1968
1969 let vtxo_key = Keypair::from_str("985247fb0ef008f8043b6be28add87710d42d482433ef287235bfe041ee6cc11").unwrap();
1970 let policy = VtxoPolicy::new_pubkey(vtxo_key.public_key());
1971 let user_cosign_key = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1972 let user_cosign_pubkey = user_cosign_key.public_key();
1973 println!("user_cosign_pubkey: {}", user_cosign_pubkey);
1974
1975 let server_key = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1976 let server_pubkey = server_key.public_key();
1977 println!("server_pubkey: {}", server_pubkey);
1978
1979 let server_cosign_key = Keypair::from_str("52a506fbae3b725749d2486afd4761841ec685b841c2967e30f24182c4b02eed").unwrap();
1980 let server_cosign_pubkey = server_cosign_key.public_key();
1981 println!("server_cosign_pubkey: {}", server_cosign_pubkey);
1982
1983 let unlock_preimage = rand::random::<UnlockPreimage>();
1984 let unlock_hash = sha256::Hash::hash(&unlock_preimage);
1985 println!("unlock_hash: {}", unlock_hash);
1986
1987 for nb_vtxos in [2, 3, 4, 5, 10, 50] {
1989 println!("building tree with {} vtxos", nb_vtxos);
1990 let vtxos = (0..nb_vtxos).map(|i| VtxoRequest {
1991 amount: Amount::from_sat(1000 * (i + 1)),
1992 policy: policy.clone(),
1993 }).collect::<Vec<_>>();
1994
1995 let builder = SignedTreeBuilder::new(
1996 vtxos.iter().cloned(), user_cosign_pubkey, unlock_preimage, expiry, server_pubkey,
1997 server_cosign_pubkey, exit_delta,
1998 ).unwrap();
1999
2000 let funding_tx = Transaction {
2001 version: transaction::Version::TWO,
2002 lock_time: absolute::LockTime::ZERO,
2003 input: vec![],
2004 output: vec![builder.funding_txout()],
2005 };
2006 let utxo = OutPoint::new(funding_tx.compute_txid(), 0);
2007 let builder = builder.set_utxo(utxo).generate_user_nonces(&user_cosign_key);
2008 let user_pub_nonces = builder.user_pub_nonces().to_vec();
2009
2010 let cosign = {
2011 let builder = SignedTreeBuilder::new_for_cosign(
2012 vtxos.iter().cloned(), user_cosign_pubkey, unlock_preimage, expiry, server_pubkey,
2013 server_cosign_pubkey, exit_delta, utxo, user_pub_nonces,
2014 ).unwrap();
2015 builder.server_cosign(&server_cosign_key)
2016 };
2017
2018 builder.verify_cosign_response(&cosign).unwrap();
2019 let tree = builder.build_tree(&cosign, &user_cosign_key).unwrap().into_cached_tree();
2020
2021 for mut vtxo in tree.output_vtxos() {
2023 {
2024 let mut with_preimage = vtxo.clone();
2026 assert!(with_preimage.provide_unlock_preimage(unlock_preimage));
2027 assert!(with_preimage.validate(&funding_tx).is_err());
2028 }
2029
2030 let (ctx, req) = LeafVtxoCosignContext::new(&vtxo, &funding_tx, &vtxo_key).unwrap();
2031 let cosign = LeafVtxoCosignResponse::new_cosign(&req, &vtxo, &funding_tx, &server_key).unwrap();
2032 assert!(ctx.finalize(&mut vtxo, cosign));
2033
2034
2035 assert!(!vtxo.has_all_witnesses());
2037 vtxo.validate_unsigned(&funding_tx).expect("Tree is valid if we ignore sigs");
2038 vtxo.validate(&funding_tx).expect_err("The signature check must fail");
2039
2040 assert!(vtxo.provide_unlock_preimage(unlock_preimage));
2041
2042 println!("vtxo debug: {:#?}", vtxo);
2043 println!("vtxo hex: {}", vtxo.serialize_hex());
2044 assert!(vtxo.has_all_witnesses());
2045 vtxo.validate(&funding_tx).expect("should be value");
2046
2047 vtxo.invalidate_final_sig();
2048 assert!(vtxo.has_all_witnesses(), "still has all witnesses, just an invalid one");
2049 vtxo.validate_unsigned(&funding_tx).expect("unsigned still valid");
2050 vtxo.validate(&funding_tx).expect_err("but not fully valid anymore");
2051 }
2052
2053 for (idx, (vtxo, _spending_txid)) in tree.internal_vtxos().enumerate() {
2054 let mut prev_txout = funding_tx.output[vtxo.chain_anchor().vout as usize].clone();
2056
2057 for item in vtxo.transactions() {
2059 crate::test_util::verify_tx(&[prev_txout], 0, &item.tx)
2060 .expect("Invalid transaction");
2061 prev_txout = item.tx.output[item.output_idx].clone();
2062 }
2063
2064 if idx < nb_vtxos as usize {
2066 matches!(vtxo.policy(), ServerVtxoPolicy::HarkLeaf(_));
2068 } else {
2069 matches!(vtxo.policy(), ServerVtxoPolicy::Expiry(_));
2070 }
2071 }
2072 }
2073 }
2074
2075 #[test]
2076 fn tree_hashlock_version_detection() {
2077 let secp = secp256k1::Secp256k1::new();
2078 let mut rand = rand::rngs::StdRng::seed_from_u64(43);
2079
2080 let user_key = Keypair::new(&secp, &mut rand);
2081 let user_cosign_key = Keypair::new(&secp, &mut rand);
2082 let server_key = Keypair::new(&secp, &mut rand);
2083 let server_cosign_key = Keypair::new(&secp, &mut rand);
2084 let preimage: UnlockPreimage = rand.r#gen();
2085 let unlock_hash = sha256::Hash::hash(&preimage);
2086
2087 let outputs = (1..=6u64).map(|i| VtxoRequest {
2090 amount: Amount::from_sat(10_000 * i),
2091 policy: VtxoPolicy::new_pubkey(user_key.public_key()),
2092 }).collect::<Vec<_>>();
2093
2094 let (v1_tree, _) = crate::test_util::build_signed_tree(
2095 HashlockVersion::V1, outputs.iter().cloned(),
2096 &user_cosign_key, &server_key, &server_cosign_key, unlock_hash,
2097 );
2098 let (v0_tree, v0_funding_tx) = crate::test_util::build_signed_tree(
2099 HashlockVersion::V0, outputs.iter().cloned(),
2100 &user_cosign_key, &server_key, &server_cosign_key, unlock_hash,
2101 );
2102
2103 assert_ne!(
2106 v1_tree.clone().into_cached_tree().build_vtxo(0).id(),
2107 v0_tree.clone().into_cached_tree().build_vtxo(0).id(),
2108 );
2109
2110 for (tree, version) in [(&v1_tree, HashlockVersion::V1), (&v0_tree, HashlockVersion::V0)] {
2113 assert_eq!(tree.detect_hashlock_version(), Ok(version));
2114
2115 let decoded = SignedVtxoTreeSpec::deserialize(&tree.serialize()).unwrap();
2116 assert_eq!(decoded.spec.hashlock_version, HashlockVersion::V1);
2117 let restored = decoded.with_detected_hashlock_version().unwrap();
2118 assert_eq!(restored.spec.hashlock_version, version);
2119 assert_eq!(&restored.spec, &tree.spec);
2120 }
2121
2122 let garbage_sig = {
2124 let sha = sha256::Hash::hash(b"not a tree sighash");
2125 let msg = secp256k1::Message::from_digest(sha.to_byte_array());
2126 secp.sign_schnorr(&msg, &Keypair::new(&secp, &mut rand))
2127 };
2128 let mut broken = v0_tree.clone();
2129 broken.cosign_sigs[0] = garbage_sig;
2130 assert_eq!(broken.detect_hashlock_version(), Err(HashlockVersionDetectError));
2131
2132 let mut half_broken = v0_tree.clone();
2135 half_broken.cosign_sigs[1] = garbage_sig;
2136 assert_eq!(half_broken.detect_hashlock_version(), Ok(HashlockVersion::V0));
2137
2138 let cached = v0_tree.into_cached_tree();
2141 for (idx, (vtxo, _)) in cached.internal_vtxos().enumerate() {
2142 if idx < cached.nb_leaves() {
2143 assert!(matches!(vtxo.policy(), ServerVtxoPolicy::HarkLeaf_v0(_)));
2144 }
2145 }
2146 for mut vtxo in cached.output_vtxos() {
2147 assert_eq!(vtxo.unlock_hash(), Some(unlock_hash));
2148 assert!(matches!(
2149 vtxo.genesis.items.last().unwrap().transition,
2150 GenesisTransition::HashLockedCosigned_v0(_),
2151 ));
2152
2153 let (ctx, req) = LeafVtxoCosignContext::new(&vtxo, &v0_funding_tx, &user_key).unwrap();
2154 let resp = LeafVtxoCosignResponse::new_cosign(&req, &vtxo, &v0_funding_tx, &server_key).unwrap();
2155 assert!(ctx.finalize(&mut vtxo, resp));
2156 assert!(vtxo.provide_unlock_preimage(preimage));
2157 vtxo.validate(&v0_funding_tx).expect("unlocked v0 leaf vtxo should be valid");
2158 }
2159 }
2160
2161 #[test]
2162 fn leaf_cosign_rejects_non_hark_vtxo() {
2163 let (funding_tx, vtxo) = DummyTestVtxoSpec::default().build();
2165
2166 let err = LeafVtxoCosignContext::new(&vtxo, &funding_tx, &DUMMY_USER_KEY).err().unwrap();
2167 assert_eq!(err, NotHarkLeafError(vtxo.id()));
2168
2169 let (_sec_nonce, pub_nonce) = musig::nonce_pair(&DUMMY_USER_KEY);
2170 let req = LeafVtxoCosignRequest { vtxo_id: vtxo.id(), pub_nonce };
2171 let err = LeafVtxoCosignResponse::new_cosign(&req, &vtxo, &funding_tx, &DUMMY_SERVER_KEY)
2172 .unwrap_err();
2173 assert_eq!(err, NotHarkLeafError(vtxo.id()));
2174 }
2175
2176 #[test]
2177 fn vtxo_leaf_spec_encoding() {
2178 let pk1: PublicKey = "020aceb65eed0ee5c512d3718e6f4bd868a7efb58ede7899ffd9bcba09555d4eb8".parse().unwrap();
2179 let pk2: PublicKey = "02e4ed0ca35c3b8a2ff675b9b23f4961964b57e130afa607e32a83d2d9a510622b".parse().unwrap();
2180 let hash = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
2181
2182 let spec_with_cosign = VtxoLeafSpec {
2184 vtxo: VtxoRequest {
2185 amount: Amount::from_sat(100_000),
2186 policy: VtxoPolicy::new_pubkey(pk1),
2187 },
2188 cosign_pubkey: Some(pk2),
2189 unlock_hash: hash,
2190 };
2191 encoding_roundtrip(&spec_with_cosign);
2192
2193 let spec_without_cosign = VtxoLeafSpec {
2195 vtxo: VtxoRequest {
2196 amount: Amount::from_sat(200_000),
2197 policy: VtxoPolicy::new_pubkey(pk1),
2198 },
2199 cosign_pubkey: None,
2200 unlock_hash: hash,
2201 };
2202 encoding_roundtrip(&spec_without_cosign);
2203 }
2204
2205 #[test]
2206 fn leaf_idxs_for_participation_binds_duplicates_to_distinct_leaves() {
2207 let pk: PublicKey = "020aceb65eed0ee5c512d3718e6f4bd868a7efb58ede7899ffd9bcba09555d4eb8"
2208 .parse().unwrap();
2209 let hash1 = sha256::Hash::from_str(
2210 "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
2211 ).unwrap();
2212 let hash2 = sha256::Hash::from_str(
2213 "dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986",
2214 ).unwrap();
2215
2216 let req = VtxoRequest {
2217 amount: Amount::from_sat(50_000),
2218 policy: VtxoPolicy::new_pubkey(pk),
2219 };
2220 let leaf = |unlock_hash| VtxoLeafSpec {
2221 vtxo: req.clone(),
2222 cosign_pubkey: None,
2223 unlock_hash,
2224 };
2225 let spec = VtxoTreeSpec::new(
2228 vec![leaf(hash2), leaf(hash1), leaf(hash1)], pk, 100_000, 2016, vec![],
2229 );
2230
2231 assert_eq!(spec.leaf_idxs_for_participation(hash1, [&req, &req]), Some(vec![1, 2]));
2234
2235 assert_eq!(spec.leaf_idxs_for_participation(hash1, [&req, &req, &req]), None);
2237
2238 let other = VtxoRequest {
2240 amount: Amount::from_sat(60_000),
2241 policy: VtxoPolicy::new_pubkey(pk),
2242 };
2243 assert_eq!(spec.leaf_idxs_for_participation(hash1, [&other]), None);
2244 }
2245
2246 #[test]
2247 fn vtxo_tree_spec_rejects_empty_vtxos() {
2248 let pk1: PublicKey = "020aceb65eed0ee5c512d3718e6f4bd868a7efb58ede7899ffd9bcba09555d4eb8"
2249 .parse().unwrap();
2250 let pk2: PublicKey = "02e4ed0ca35c3b8a2ff675b9b23f4961964b57e130afa607e32a83d2d9a510622b"
2251 .parse().unwrap();
2252 let hash = sha256::Hash::from_str(
2253 "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
2254 ).unwrap();
2255
2256 let leaf = VtxoLeafSpec {
2258 vtxo: VtxoRequest {
2259 amount: Amount::from_sat(100_000),
2260 policy: VtxoPolicy::new_pubkey(pk1),
2261 },
2262 cosign_pubkey: Some(pk2),
2263 unlock_hash: hash,
2264 };
2265 let spec = VtxoTreeSpec::new(vec![leaf], pk1, 100_000, 2016, vec![]);
2266 encoding_roundtrip(&spec);
2267
2268 let empty_spec = VtxoTreeSpec { vtxos: vec![], ..spec };
2270 let buf = empty_spec.serialize();
2271 let err = VtxoTreeSpec::deserialize(&buf)
2272 .expect_err("decoding a VtxoTreeSpec with zero vtxos must fail");
2273 let msg = err.to_string();
2274 assert!(msg.contains("at least one leaf"), "unexpected error message: {msg}");
2275 }
2276
2277 #[test]
2278 fn test_compat_u32_size_signed_spec() {
2279 let hex = "0102888a0100035b0ef8c9bd756af433edc3129975888f6f18b8185b2afbaabc8bb3029a00cf81e0070102f8112234026e68b1e4d1565540d7b791ced1b64c5f30525cbe14f21dd7aa8c78030003c48b53afac0d2d5169cd6848f03f67a21db6f506f8b8fc2dbef2552b6a7dc111a08601000000000002c6c80e198e170ca6f8fa17810d8ee23c7c0d85c5d2febc95c3e24b1878ca733fbfd032abc31b253f5063521fd5b4c431f2cdd3fee1b4ec00a9b00f69d3b033e7000296dfec4c92e831ffe3619285646a46c545577f19dbc27c2fc0950bffb0f4a362a08601000000000003850a7d2bf22e6ba669695410a8b03c5800a0d4c2bec814b9eb21b0cddd2af5c935395dea8bd6dcac26a8a417b553b18d13027c23e8016c3466b81e70832254360002415f712d6e551b715542422b975a2ae7c635b44a1e3747d5a8674231fa10a841a08601000000000002a3d4de26a87f8bb9d5c0b9f1e3409a635b35f656575d8ad60a6a2294ac4e50b87c0cc2177dfce6432efa42ca6c04c0b774dbb3c5ca2573cd893443e10e393bfd0100000000000000000000000000000000000000000000000000000000000000010000000400000002cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb";
2281 let ret = SignedVtxoTreeSpec::deserialize_hex(hex).unwrap();
2282 assert_eq!(ret.cosign_sigs.len(), 4);
2283 }
2284}