Skip to main content

ark/tree/
signed.rs

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
27/// Hash to lock hArk VTXOs from users before forfeits
28pub type UnlockHash = sha256::Hash;
29
30/// Preimage to unlock hArk VTXOs
31pub type UnlockPreimage = [u8; 32];
32
33/// The version of the hashlock clauses used in the leaves of a VTXO tree.
34///
35/// Trees created before the `PROTOCOL_VERSION_HASHLOCK_CLAUSES` protocol
36/// version used the v0 unlock scripts.
37///
38/// We forgot to properly encode the difference in the trees, so version
39/// used is detected at runtime and then stored inside the tree.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub enum HashlockVersion {
42	V0,
43	V1,
44}
45
46/// The upper bound witness weight to spend a node transaction.
47pub const NODE_SPEND_WEIGHT: Weight = Weight::from_wu(140);
48
49/// The expiry clause hidden in the node taproot as only script.
50pub fn expiry_clause(server_pubkey: PublicKey, expiry_height: BlockHeight) -> ScriptBuf {
51	TimelockSignClause { pubkey: server_pubkey, timelock_height: expiry_height }.tapscript()
52}
53
54/// The hash-based unlock clause that requires a signature and a preimage
55///
56/// It is used hidden in the leaf taproot as only script or used in the forfeit output.
57pub fn unlock_clause(pubkey: XOnlyPublicKey, unlock_hash: UnlockHash) -> ScriptBuf {
58	scripts::hash_and_sign(unlock_hash, pubkey)
59}
60
61/// The taproot of the leaf policy, i.e. of the output that is spent by the leaf tx
62///
63/// This output is guarded by user+server key and a hash preimage.
64///
65/// The internal key is set to the MuSig of user's VTXO key + server pubkey,
66/// but the keyspend clause is currently not used in the protocol.
67pub 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
76/// The hash-based unlock clause that requires a signature and a preimage
77///
78/// It is used hidden in the leaf taproot as only script or used in the forfeit output.
79pub fn unlock_clause_v0(pubkey: XOnlyPublicKey, unlock_hash: UnlockHash) -> ScriptBuf {
80	scripts::hash_and_sign_v0(unlock_hash, pubkey)
81}
82
83/// The taproot of the leaf policy, i.e. of the output that is spent by the leaf tx
84///
85/// This output is guarded by user+server key and a hash preimage.
86///
87/// The internal key is set to the MuSig of user's VTXO key + server pubkey,
88/// but the keyspend clause is currently not used in the protocol.
89pub 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
98/// The taproot spend info of an output that is spent by an internal node tx
99pub 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	/// The actual VTXO request.
112	pub vtxo: VtxoRequest,
113
114	/// The public key used by the client to cosign the internal txs of the tree
115	///
116	/// Only interactive participants have a cosign key here.
117	///
118	/// The client SHOULD forget this key after signing the transaction tree.
119	/// Non-interactive participants don't have a cosign pubkey.
120	pub cosign_pubkey: Option<PublicKey>,
121
122	/// The unlock hash used to lock the VTXO before forfeits are signed
123	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/// All the information that uniquely specifies a VTXO tree before it has been signed.
148#[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	/// The hashlock clause version used in the leaf outputs.
156	/// Detected at decoding time because we forgot to properly encode this info.
157	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	/// Get the leaf index of the given leaf spec.
204	pub fn leaf_idx_of(&self, leaf_spec: &VtxoLeafSpec) -> Option<usize> {
205		self.vtxos.iter().position(|e| e == leaf_spec)
206	}
207
208	/// Get the leaf index of each of the given vtxo requests of the
209	/// participation with the given unlock hash.
210	///
211	/// Each leaf is assigned to at most one request, so identical requests
212	/// within a participation are bound to distinct leaves. Leaves belonging
213	/// to other participations (different unlock hash) are ignored.
214	///
215	/// Returns [None] when a request can't be matched to an unused leaf.
216	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	/// Calculate the total value needed in the tree.
232	///
233	/// This accounts for
234	/// - all vtxos getting their value
235	pub fn total_required_value(&self) -> Amount {
236		self.vtxos.iter().map(|d| d.vtxo.amount).sum::<Amount>()
237	}
238
239	/// Calculate the taproot spend info for a leaf node
240	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	/// Calculate the taproot spend info for internal nodes
256	pub fn internal_taproot(&self, agg_pk: XOnlyPublicKey) -> taproot::TaprootSpendInfo {
257		cosign_taproot(agg_pk, self.server_pubkey, self.expiry_height)
258	}
259
260	/// The cosign pubkey used on the vtxo output of the tx funding the tree
261	///
262	/// In Ark rounds this will be the round funding tx scriptPubkey.
263	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	/// The scriptPubkey used on the vtxo output of the tx funding the tree
271	///
272	/// In Ark rounds this will be the round funding tx scriptPubkey.
273	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	/// The output of the tx funding the tree
279	///
280	/// In Ark rounds this will be the round funding tx output.
281	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	/// Create a node tx
289	///
290	/// The children are an iterator over the next tx, its cosign pubkey
291	/// and the unlock hash if the child is a leaf.
292	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(), // we will fill this later
301				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		// We collect fees in rounds by the difference in value between the inputs and outputs which
334		// is enforced by the round payment validation code. Therefore, we can leave fees set to
335		// zero for the leaf tx.
336		vtxo::create_exit_tx(OutPoint::null(), txout, None, Amount::ZERO)
337	}
338
339	/// Calculate all the aggregate cosign pubkeys by aggregating the leaf and server pubkeys.
340	///
341	/// Pubkeys expected and returned ordered from leaves to root.
342	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	/// Return unsigned transactions for all nodes from leaves to root.
361	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		// set the prevouts
389		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	/// Return all final transactions for all nodes from leaves to root
402	///
403	/// Internal transactions are signed, leaf txs not.
404	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	/// Calculate all the aggregate cosign nonces by aggregating the leaf and server nonces.
417	///
418	/// Nonces expected and returned for all internal nodes ordered from leaves to root.
419	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					// note that we skip some nonces for some leaves that are at the edges
434					// and skip some levels
435					.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	/// Convert this spec into an unsigned tree by providing the
447	/// root outpoint and the nodes' aggregate nonces.
448	///
449	/// Nonces expected ordered from leaves to root.
450	pub fn into_unsigned_tree(
451		self,
452		utxo: OutPoint,
453	) -> UnsignedVtxoTree {
454		UnsignedVtxoTree::new(self, utxo)
455	}
456}
457
458/// A VTXO tree ready to be signed.
459///
460/// This type contains various cached values required to sign the tree.
461#[derive(Debug, Clone)]
462pub struct UnsignedVtxoTree {
463	pub spec: VtxoTreeSpec,
464	pub utxo: OutPoint,
465
466	// the following fields are calculated from the above
467
468	/// Aggregate pubkeys for the inputs to all nodes, leaves to root.
469	pub cosign_agg_pks: Vec<PublicKey>,
470	/// Transactions for all nodes, leaves to root.
471	pub txs: Vec<Transaction>,
472	/// Sighashes for the only input of the tx for all internal nodes,
473	/// leaves to root.
474	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, // input idx is always 0
504				&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	/// The number of leaves that have a cosign pubkey
517	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	/// Generate partial musig signatures for the nodes in the tree branch of the given
532	/// vtxo request.
533	///
534	/// Note that the signatures are indexed by their place in the tree and thus do not
535	/// necessarily match up with the indices in the secret nonces vector.
536	///
537	/// Aggregate nonces expected for all nodes, ordered from leaves to root.
538	/// Secret nonces expected for branch, ordered from leaf to root.
539	///
540	/// Returns [None] if the vtxo request is not part of the tree.
541	/// Returned signatures over the branch from leaf to root.
542	//TODO(stevenroose) streamline indices of nonces and sigs
543	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		// skip the leaf
561		for node in self.tree.iter_branch(leaf_idx).skip(1) {
562			// Since we can skip a level, we sometimes have to skip a nonce.
563			// NB We can't just use the index into the sec_nonces vector, because
564			// musig requires us to use the owned SecNonce type to prevent footgun
565			// by reusing secret nonces.
566			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	/// Generate partial musig signatures for all internal nodes in the tree.
596	///
597	/// Nonces expected for all internal nodes, ordered from leaves to root.
598	///
599	/// Returns [None] if the vtxo request is not part of the tree.
600	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	/// Verify partial cosign signature of a single internal node
632	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	/// Verify the partial cosign signatures from one of the leaves.
664	///
665	/// Nonces and partial signatures expected for all internal nodes,
666	/// ordered from leaves to root.
667	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		// skip the leaf of the branch we verify
680		let internal_branch = self.tree.iter_branch(leaf_idx).skip(1);
681
682		// quickly check if the number of sigs is sane
683		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	/// Verify the partial cosign signatures for all nodes.
711	///
712	/// Nonces and partial signatures expected for all internal nodes,
713	/// ordered from leaves to root.
714	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	/// Combine all partial cosign signatures.
733	///
734	/// Nonces expected for all internal nodes, ordered from leaves to root.
735	///
736	/// Branch signatures expected for internal nodes in branch ordered from leaf to root.
737	///
738	/// Server signatures expected for all internal nodes ordered from leaves to root,
739	/// in the same order as `global_cosign_pubkeys`.
740	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		// to ease implementation, we're reconstructing the part sigs map with dequeues
747		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				// NB if the called didn't order part sigs identically as global_cosign_pubkeys,
759				// this pubkey indication is actually wrong..
760				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			// add global signers
782			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	/// Verify the signatures of all the internal node txs.
800	///
801	/// Signatures expected for all internal nodes, ordered from leaves to root.
802	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	/// Convert into a [SignedVtxoTreeSpec] by providing the signatures.
819	///
820	/// Signatures expected for all internal nodes, ordered from leaves to root.
821	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/// Error returned from cosigning a VTXO tree.
834#[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/// All the information needed to uniquely specify a fully signed VTXO tree.
862#[derive(Debug, Clone, PartialEq)]
863pub struct SignedVtxoTreeSpec {
864	pub spec: VtxoTreeSpec,
865	pub utxo: OutPoint,
866	/// The signatures for the internal txs, from leaves to root.
867	pub cosign_sigs: Vec<schnorr::Signature>,
868}
869
870impl SignedVtxoTreeSpec {
871	/// Signatures expected for internal nodes ordered from leaves to root.
872	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	/// Construct the exit branch starting from the root ending in the leaf.
885	///
886	/// Panics if `leaf_idx` is out of range.
887	///
888	/// This call is quite inefficient and if you want to make repeated calls,
889	/// it is advised to use [CachedSignedVtxoTree::exit_branch] instead.
890	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	/// Get all final txs in this tree, starting with the leaves, towards the root
901	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	/// Detect the hashlock version this tree was signed with.
913	///
914	///	We forgot to properly encode this, so it detects it by looking at the
915	///	first signature and seeing which version it is valid for.
916	///
917	///	Falls back to v1 for empty trees, they shouldn't exist.
918	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	/// Return this tree with its hashlock version detected from the cosign
939	/// signatures.
940	///
941	/// Use this before rebuilding transactions or VTXOs from a stored tree
942	/// that may predate the v1 hashlock clauses; see
943	/// [Self::detect_hashlock_version].
944	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/// The hashlock version of a tree couldn't be determined because its cosign
951/// signatures don't verify for any version. This means the tree is corrupt.
952#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
953#[error("tree cosign signatures don't verify for any hashlock version")]
954pub struct HashlockVersionDetectError;
955
956/// A fully signed VTXO tree, with all the transaction cached.
957///
958/// This is useful for cheap extraction of VTXO branches.
959pub struct CachedSignedVtxoTree {
960	pub spec: SignedVtxoTreeSpec,
961	/// All signed txs in this tree, starting with the leaves, towards the root.
962	pub txs: Vec<Transaction>,
963}
964
965impl CachedSignedVtxoTree {
966	/// Construct the exit branch starting from the root ending in the leaf.
967	///
968	/// Panics if `leaf_idx` is out of range.
969	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	/// Get all final txs in this tree, starting with the leaves, towards the root.
987	///
988	/// The leaf transactions are unsigned and the node transactions are signed.
989	/// This is equivalent to `unsigned_leaf_txs` chained with `signed_node_txs`
990	pub fn all_final_txs(&self) -> &[Transaction] {
991		&self.txs
992	}
993
994	/// Returns all leaf transactions
995	///
996	/// These transactions aren't signed (yet)
997	pub fn unsigned_leaf_txs(&self) -> &[Transaction] {
998		&self.txs[..self.nb_leaves()]
999	}
1000
1001	/// Returns all internal node transactions
1002	///
1003	/// These transactions are fully signed
1004	pub fn internal_node_txs(&self) -> &[Transaction] {
1005		&self.txs[self.nb_leaves()..]
1006	}
1007
1008	/// Build the genesis item for the given node tx and its output idx
1009	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) // Exclude this output
1016			.filter(|(_, out)| !out.is_p2a_fee_anchor())    // Exclude the fee-anchor
1017			.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	/// Construct the server vtxo at the given node index.
1055	///
1056	/// The index corresponds to the prevout of self.txs[index]
1057	///
1058	/// Panics if `node_idx` is out of range.
1059	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	/// Construct the VTXO at the given leaf index.
1110	///
1111	/// Panics if `leaf_idx` is out of range.
1112	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	/// Construct all internal ServerVtxos, each paired with the txid
1144	/// of the transaction that spends it.
1145	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	/// Construct all individual vtxos from this round.
1154	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
1164/// Calculate the scriptspend sighash of a hArk leaf transaction
1165pub 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, // input idx is always 0
1179		&sighash::Prevouts::All(&[prev_txout]),
1180		leaf_hash,
1181		TapSighashType::Default,
1182	).expect("sighash error")
1183}
1184
1185/// Calculate the scriptspend sighash of a hArk leaf transaction
1186pub 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, // input idx is always 0
1200		&sighash::Prevouts::All(&[prev_txout]),
1201		leaf_hash,
1202		TapSighashType::Default,
1203	).expect("sighash error")
1204}
1205
1206/// The VTXO can't take part in the hArk leaf cosign flow because its
1207/// last genesis transition is not hash-locked.
1208#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
1209#[error("VTXO {0} is not a hArk leaf VTXO")]
1210pub struct NotHarkLeafError(VtxoId);
1211
1212/// Create the leaf tx sighash from an existing VTXO
1213///
1214/// This is used after the interactive part of the round is finished by
1215/// both user and server to cosign the leaf input script-spend before
1216/// exchanging forfeit signatures for the unlock preimage.
1217fn 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	// we need the penultimate TxOut and last tx
1224	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		// we don't know when we're penultimate, update txout
1229		// each time except last
1230		if peekable_iter.peek().is_some() {
1231			preleaf_txout = item.tx.output[item.output_idx].clone();
1232		}
1233
1234		// then only take the last tx
1235		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	/// Create a new [LeafVtxoCosignRequest] for the given VTXO
1276	///
1277	/// Returns an error if this VTXO is not a hArk leaf VTXO.
1278	///
1279	/// Panics if the chain_anchor tx is incorrect.
1280	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	/// Finalize the VTXO using the response from the server
1294	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	/// Cosign a [LeafVtxoCosignRequest]
1335	///
1336	/// Returns an error if the VTXO is not a hArk leaf VTXO.
1337	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	//! This module allows a single party to construct his own signed
1358	//! VTXO tree, to then request signatures from the server.
1359	//!
1360	//! This is not used for rounds, where the tree is created with
1361	//! many users at once.
1362
1363	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			/// Just a trait to seal the BuilderState trait
1380			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		/// A marker trait used as a generic for [super::SignedTreeBuilder]
1388		pub trait BuilderState: sealed::Sealed {}
1389
1390		/// The user is preparing the funding tx
1391		pub struct Preparing;
1392		impl BuilderState for Preparing {}
1393
1394		/// The UTXO that will be used to fund the tree is known, so the
1395		/// user's signing nonces can be generated
1396		pub struct CanGenerateNonces;
1397		impl BuilderState for CanGenerateNonces {}
1398
1399		/// All the information for the server to cosign the tree is known
1400		pub struct ServerCanCosign;
1401		impl BuilderState for ServerCanCosign {}
1402
1403		/// The user is ready to build the tree as soon as it has
1404		/// a cosign response from the server
1405		pub struct CanFinish;
1406		impl BuilderState for CanFinish {}
1407
1408		/// Trait to capture all states that have sufficient information
1409		/// for either party to create signatures
1410		pub trait CanSign: BuilderState {}
1411		impl CanSign for ServerCanCosign {}
1412		impl CanSign for CanFinish {}
1413	}
1414
1415	/// Just an enum to hold either a tree spec or an unsigned tree
1416	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	/// A builder for a single party to construct a VTXO tree
1438	///
1439	/// For more information, see the module documentation.
1440	pub struct SignedTreeBuilder<S: state::BuilderState> {
1441		pub expiry_height: BlockHeight,
1442		pub server_pubkey: PublicKey,
1443		pub exit_delta: BlockDelta,
1444		/// The cosign pubkey used to cosign all nodes in the tree
1445		pub cosign_pubkey: PublicKey,
1446		/// The unlock hash used to unlock all VTXOs in the tree
1447		pub unlock_preimage: UnlockPreimage,
1448
1449		tree: BuilderTree,
1450
1451		/// users public nonces, leaves to the root
1452		user_pub_nonces: Vec<musig::PublicNonce>,
1453		/// users secret nonces, leaves to the root
1454		/// this field is empty on the server side
1455		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		/// The total value required for the tree to be funded
1468		pub fn total_required_value(&self) -> Amount {
1469			self.tree_spec().total_required_value()
1470		}
1471
1472		/// The scriptPubkey to send the board funds to
1473		pub fn funding_script_pubkey(&self) -> ScriptBuf {
1474			self.tree_spec().funding_tx_script_pubkey()
1475		}
1476
1477		/// The TxOut to create in the funding tx
1478		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		/// Get the user's public nonces
1489		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		/// Construct the spec to be used in [SignedTreeBuilder]
1500		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				// NB we place server last because then it looks closer like
1525				// a regular user-signed tree which Vtxo::validate relies on
1526				vec![cosign_pubkey, server_cosign_pubkey],
1527			))
1528		}
1529
1530		/// Create a new [SignedTreeBuilder]
1531		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		/// Set the utxo from which the tree will be created
1560		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, // should not happen
1564			};
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		/// Generate user nonces
1582		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	/// Holds the cosignature information of the server
1612	#[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		/// Create a new [SignedTreeBuilder] for the server to cosign
1620		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		/// The server cosigns the tree nodes
1655		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		/// Validate the server's partial signatures
1681		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
1738/// The serialization version of [VtxoTreeSpec].
1739const 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			// not part of the encoding; see the field docs for how to handle
1776			// trees that might predate the v1 clauses
1777			hashlock_version: HashlockVersion::V1,
1778		})
1779	}
1780}
1781
1782/// The serialization version of [SignedVtxoTreeSpec].
1783const SIGNED_VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1784
1785/// The serialization version of [SignedVtxoTreeSpec] with u32 as signature count
1786const 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		// skip the root
1851		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		// check the root
1864		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			// Assert it's a valid chain.
1950			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		// we test different number of nodes
1988		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			// finalize vtxos and check
2022			for mut vtxo in tree.output_vtxos() {
2023				{
2024					// check that with just the preimage, the VTXO is not valid
2025					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				// We still miss some signatures. But the tree should be vaild
2036				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				// Verify transactions with consensus checks
2055				let mut prev_txout = funding_tx.output[vtxo.chain_anchor().vout as usize].clone();
2056
2057				// Verify that all vtxo.transactions() are consensus valid
2058				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				// Verify the policies
2065				if idx < nb_vtxos as usize {
2066					// All leafs have the HarkLeafPolicy
2067					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		// 6 leaves make a tree with two internal nodes, so detection has to
2088		// work on a first internal node that isn't the root
2089		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		// the version changes the leaf taproots and thus the vtxo ids,
2104		// so building with the wrong version yields the wrong vtxos
2105		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		// detection recovers the version, also after an encoding roundtrip,
2111		// which drops it
2112		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		// a tree with an invalid first cosign signature can't be detected
2123		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		// detection only checks the first signature, it's not an integrity
2133		// check, so a corrupt later signature doesn't affect it
2134		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		// the v0 leaves carry the v0 policy and genesis transition and can
2139		// complete the post-round cosign and unlock flow
2140		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		// a board VTXO's last genesis transition is not hash-locked
2164		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		// Test with Some(cosign_pubkey)
2183		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		// Test with None cosign_pubkey
2194		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		// another participation's leaf with an identical request first,
2226		// then two identical leaves of the same participation
2227		let spec = VtxoTreeSpec::new(
2228			vec![leaf(hash2), leaf(hash1), leaf(hash1)], pk, 100_000, 2016, vec![],
2229		);
2230
2231		// identical requests are bound to the distinct leaves of their
2232		// own participation
2233		assert_eq!(spec.leaf_idxs_for_participation(hash1, [&req, &req]), Some(vec![1, 2]));
2234
2235		// a third identical request has no leaf left to bind to
2236		assert_eq!(spec.leaf_idxs_for_participation(hash1, [&req, &req, &req]), None);
2237
2238		// a request that isn't in the tree doesn't match
2239		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		// Build a valid spec and verify it roundtrips.
2257		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		// Empty the vtxos and verify decode rejects it.
2269		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		//! check the backwards compatibility of parsing SignedVtxoTreeSpec with u32 as size prefix
2280		let hex = "0102888a0100035b0ef8c9bd756af433edc3129975888f6f18b8185b2afbaabc8bb3029a00cf81e0070102f8112234026e68b1e4d1565540d7b791ced1b64c5f30525cbe14f21dd7aa8c78030003c48b53afac0d2d5169cd6848f03f67a21db6f506f8b8fc2dbef2552b6a7dc111a08601000000000002c6c80e198e170ca6f8fa17810d8ee23c7c0d85c5d2febc95c3e24b1878ca733fbfd032abc31b253f5063521fd5b4c431f2cdd3fee1b4ec00a9b00f69d3b033e7000296dfec4c92e831ffe3619285646a46c545577f19dbc27c2fc0950bffb0f4a362a08601000000000003850a7d2bf22e6ba669695410a8b03c5800a0d4c2bec814b9eb21b0cddd2af5c935395dea8bd6dcac26a8a417b553b18d13027c23e8016c3466b81e70832254360002415f712d6e551b715542422b975a2ae7c635b44a1e3747d5a8674231fa10a841a08601000000000002a3d4de26a87f8bb9d5c0b9f1e3409a635b35f656575d8ad60a6a2294ac4e50b87c0cc2177dfce6432efa42ca6c04c0b774dbb3c5ca2573cd893443e10e393bfd0100000000000000000000000000000000000000000000000000000000000000010000000400000002cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb02cc1980aba21f65a51342cbaa3beb69d930eac87fe3086c45df4e642f2dd07cd19e0e4c7b9584b9e952eb4fb02e7f43364ee9bbfc667236aa166fd10a97dcbb";
2281		let ret = SignedVtxoTreeSpec::deserialize_hex(hex).unwrap();
2282		assert_eq!(ret.cosign_sigs.len(), 4);
2283	}
2284}