ark/tree/
signed.rs

1
2
3use std::{cmp, fmt, io, iter};
4use std::collections::{HashMap, VecDeque};
5
6use bitcoin::hashes::Hash;
7use bitcoin::{
8	taproot, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Weight, Witness,
9};
10use bitcoin::secp256k1::{schnorr, Keypair, PublicKey, XOnlyPublicKey};
11use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
12use secp256k1_musig::musig::{AggregatedNonce, PartialSignature, PublicNonce, SecretNonce};
13
14use bitcoin_ext::{fee, BlockDelta, BlockHeight, TaprootSpendInfoExt, TransactionExt, TxOutExt};
15
16use crate::error::IncorrectSigningKeyError;
17use crate::{musig, scripts, SECP, SignedVtxoRequest, Vtxo, VtxoPolicy, VtxoRequest};
18use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
19use crate::tree::{self, Tree};
20use crate::vtxo::{self, GenesisItem, GenesisTransition};
21
22
23/// The upper bound witness weight to spend a node transaction.
24pub const NODE_SPEND_WEIGHT: Weight = Weight::from_wu(140);
25
26/// The expiry clause hidden in the node taproot as only script.
27pub fn expiry_clause(server_pubkey: PublicKey, expiry_height: BlockHeight) -> ScriptBuf {
28	let pk = server_pubkey.x_only_public_key().0;
29	scripts::timelock_sign(expiry_height, pk)
30}
31
32pub fn cosign_taproot(
33	agg_pk: XOnlyPublicKey,
34	server_pubkey: PublicKey,
35	expiry_height: BlockHeight,
36) -> taproot::TaprootSpendInfo {
37	taproot::TaprootBuilder::new()
38		.add_leaf(0, expiry_clause(server_pubkey, expiry_height)).unwrap()
39		.finalize(&SECP, agg_pk).unwrap()
40}
41
42/// All the information that uniquely specifies a VTXO tree before it has been signed.
43#[derive(Debug, Clone, Eq, PartialEq)]
44pub struct VtxoTreeSpec {
45	pub vtxos: Vec<SignedVtxoRequest>,
46	pub expiry_height: BlockHeight,
47	pub server_pubkey: PublicKey,
48	pub exit_delta: BlockDelta,
49	pub global_cosign_pubkeys: Vec<PublicKey>,
50}
51
52impl VtxoTreeSpec {
53	pub fn new(
54		vtxos: Vec<SignedVtxoRequest>,
55		server_pubkey: PublicKey,
56		expiry_height: BlockHeight,
57		exit_delta: BlockDelta,
58		global_cosign_pubkeys: Vec<PublicKey>,
59	) -> VtxoTreeSpec {
60		assert_ne!(vtxos.len(), 0);
61		VtxoTreeSpec { vtxos, server_pubkey, expiry_height, exit_delta, global_cosign_pubkeys }
62	}
63
64	pub fn nb_leaves(&self) -> usize {
65		self.vtxos.len()
66	}
67
68	pub fn nb_nodes(&self) -> usize {
69		Tree::nb_nodes_for_leaves(self.nb_leaves())
70	}
71
72	pub fn iter_vtxos(&self) -> impl Iterator<Item = &SignedVtxoRequest> {
73		self.vtxos.iter()
74	}
75
76	/// Get the leaf index of the given vtxo request.
77	pub fn leaf_idx_of(&self, vtxo_request: &SignedVtxoRequest) -> Option<usize> {
78		self.vtxos.iter().position(|e| e == vtxo_request)
79	}
80
81	/// Calculate the total value needed in the tree.
82	///
83	/// This accounts for
84	/// - all vtxos getting their value
85	pub fn total_required_value(&self) -> Amount {
86		self.vtxos.iter().map(|d| d.vtxo.amount).sum::<Amount>()
87	}
88
89	/// Calculate the cosign taproot at a given node.
90	pub fn cosign_taproot(&self, agg_pk: XOnlyPublicKey) -> taproot::TaprootSpendInfo {
91		cosign_taproot(agg_pk, self.server_pubkey, self.expiry_height)
92	}
93
94	/// The cosign pubkey used on the vtxo output of the tx funding the tree
95	///
96	/// In Ark rounds this will be the round tx scriptPubkey.
97	pub fn funding_tx_cosign_pubkey(&self) -> XOnlyPublicKey {
98		let keys = self.vtxos.iter()
99			.filter_map(|v| v.cosign_pubkey)
100			.chain(self.global_cosign_pubkeys.iter().copied());
101		musig::combine_keys(keys)
102	}
103
104	/// The scriptPubkey used on the vtxo output of the tx funding the tree
105	///
106	/// In Ark rounds this will be the round tx scriptPubkey.
107	pub fn funding_tx_script_pubkey(&self) -> ScriptBuf {
108		let agg_pk = self.funding_tx_cosign_pubkey();
109		self.cosign_taproot(agg_pk).script_pubkey()
110	}
111
112	/// The output of the tx funding the tree
113	///
114	/// In Ark rounds this will be the round tx scriptPubkey.
115	pub fn funding_tx_txout(&self) -> TxOut {
116		TxOut {
117			script_pubkey: self.funding_tx_script_pubkey(),
118			value: self.total_required_value(),
119		}
120	}
121
122	fn node_tx<'a>(&self, children: impl Iterator<Item=(&'a Transaction, &'a XOnlyPublicKey)>) -> Transaction {
123		Transaction {
124			version: bitcoin::transaction::Version(3),
125			lock_time: bitcoin::absolute::LockTime::ZERO,
126			input: vec![TxIn {
127				previous_output: OutPoint::null(), // we will fill this later
128				sequence: Sequence::ZERO,
129				script_sig: ScriptBuf::new(),
130				witness: Witness::new(),
131			}],
132			output: children.map(|(tx, agg_pk)| {
133				let taproot = self.cosign_taproot(*agg_pk);
134				TxOut {
135					script_pubkey: taproot.script_pubkey(),
136					value: tx.output_value(),
137				}
138			}).chain(Some(fee::fee_anchor())).collect(),
139		}
140	}
141
142	fn leaf_tx(&self, vtxo: &VtxoRequest) -> Transaction {
143		let txout = TxOut {
144			value: vtxo.amount,
145			script_pubkey: vtxo.policy.script_pubkey(self.server_pubkey, self.exit_delta),
146		};
147
148		vtxo::create_exit_tx(OutPoint::null(), txout, None)
149	}
150
151	/// Calculate all the aggregate cosign pubkeys by aggregating the leaf and server pubkeys.
152	///
153	/// Pubkeys expected and returned ordered from leaves to root.
154	pub fn cosign_agg_pks(&self)
155		-> impl Iterator<Item = XOnlyPublicKey> + iter::DoubleEndedIterator + iter::ExactSizeIterator + '_
156	{
157		Tree::new(self.nb_leaves()).into_iter().map(|node| {
158			musig::combine_keys(
159				node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey)
160					.chain(self.global_cosign_pubkeys.iter().copied())
161			)
162		})
163	}
164
165	/// Return unsigned transactions for all nodes from leaves to root.
166	pub fn unsigned_transactions(&self, utxo: OutPoint) -> Vec<Transaction> {
167		let tree = Tree::new(self.nb_leaves());
168
169		let cosign_agg_pks = self.cosign_agg_pks().collect::<Vec<_>>();
170
171		let mut txs = Vec::with_capacity(tree.nb_nodes());
172		for node in tree.iter() {
173			let tx = if node.is_leaf() {
174				self.leaf_tx(&self.vtxos[node.idx()].vtxo).clone()
175			} else {
176				let mut buf = [None; tree::RADIX];
177				for (idx, child) in node.children().enumerate() {
178					buf[idx] = Some((&txs[child], &cosign_agg_pks[child]));
179				}
180				self.node_tx(buf.iter().filter_map(|x| *x))
181			};
182			txs.push(tx.clone());
183		};
184
185		// set the prevouts
186		txs.last_mut().unwrap().input[0].previous_output = utxo;
187		for node in tree.iter().rev() {
188			let txid = txs[node.idx()].compute_txid();
189			for (i, child) in node.children().enumerate() {
190				let point = OutPoint::new(txid, i as u32);
191				txs[child].input[0].previous_output = point;
192			}
193		}
194
195		txs
196	}
197
198	/// Return all signed transactions for all nodes from leaves to root.
199	pub fn signed_transactions(
200		&self,
201		utxo: OutPoint,
202		signatures: &[schnorr::Signature],
203	) -> Vec<Transaction> {
204		let mut txs = self.unsigned_transactions(utxo);
205		for (tx, sig) in txs.iter_mut().zip(signatures) {
206			tx.input[0].witness.push(&sig[..]);
207		}
208		txs
209	}
210
211	/// Calculate all the aggregate cosign nonces by aggregating the leaf and server nonces.
212	///
213	/// Nonces expected and returned ordered from leaves to root.
214	pub fn calculate_cosign_agg_nonces(
215		&self,
216		leaf_cosign_nonces: &HashMap<PublicKey, Vec<PublicNonce>>,
217		global_signer_cosign_nonces: &[impl AsRef<[PublicNonce]>],
218	) -> Result<Vec<AggregatedNonce>, String> {
219		if global_signer_cosign_nonces.len() != self.global_cosign_pubkeys.len() {
220			return Err("missing global signer nonces".into());
221		}
222
223		Tree::new(self.nb_leaves()).iter().enumerate().map(|(idx, node)| {
224			let mut nonces = Vec::new();
225			for pk in node.leaves().filter_map(|i| self.vtxos[i].cosign_pubkey) {
226				nonces.push(leaf_cosign_nonces.get(&pk)
227					.ok_or_else(|| format!("missing nonces for leaf pk {}", pk))?
228					// note that we skip some nonces for some leaves that are at the edges
229					// and skip some levels
230					.get(node.level())
231					.ok_or_else(|| format!("not enough nonces for leaf_pk {}", pk))?
232				);
233			}
234			for glob in global_signer_cosign_nonces {
235				nonces.push(glob.as_ref().get(idx).ok_or("not enough global cosign nonces")?);
236			}
237			Ok(musig::nonce_agg(&nonces))
238		}).collect()
239	}
240
241	/// Convert this spec into an unsigned tree by providing the
242	/// root outpoint and the nodes' aggregate nonces.
243	///
244	/// Nonces expected ordered from leaves to root.
245	pub fn into_unsigned_tree(
246		self,
247		utxo: OutPoint,
248	) -> UnsignedVtxoTree {
249		UnsignedVtxoTree::new(self, utxo)
250	}
251}
252
253/// A VTXO tree ready to be signed.
254///
255/// This type contains various cached values required to sign the tree.
256#[derive(Debug, Clone)]
257pub struct UnsignedVtxoTree {
258	pub spec: VtxoTreeSpec,
259	pub utxo: OutPoint,
260
261	// the following fields are calculated from the above
262
263	/// Aggregate pubkeys for the inputs to all nodes, leaves to root.
264	pub cosign_agg_pks: Vec<XOnlyPublicKey>,
265	/// Transactions for all nodes, leaves to root.
266	pub txs: Vec<Transaction>,
267	/// Sighashes for the only input of the tx for all nodes, leaves to root.
268	pub sighashes: Vec<TapSighash>,
269
270	tree: Tree,
271}
272
273impl UnsignedVtxoTree {
274	pub fn new(
275		spec: VtxoTreeSpec,
276		utxo: OutPoint,
277	) -> UnsignedVtxoTree {
278		let tree = Tree::new(spec.nb_leaves());
279
280		let cosign_agg_pks = spec.cosign_agg_pks().collect::<Vec<_>>();
281		let txs = spec.unsigned_transactions(utxo);
282
283		let root_txout = spec.funding_tx_txout();
284		let sighashes = tree.iter().map(|node| {
285			let prev = if let Some((parent, sibling_idx)) = tree.parent_idx_of_with_sibling_idx(node.idx()) {
286				assert!(!node.is_root());
287				&txs[parent].output[sibling_idx]
288			} else {
289				assert!(node.is_root());
290				&root_txout
291			};
292			SighashCache::new(&txs[node.idx()]).taproot_key_spend_signature_hash(
293				0, // input idx is always 0
294				&sighash::Prevouts::All(&[prev]),
295				TapSighashType::Default,
296			).expect("sighash error")
297		}).collect();
298
299		UnsignedVtxoTree { spec, utxo, txs, sighashes, cosign_agg_pks, tree }
300	}
301
302	pub fn nb_leaves(&self) -> usize {
303		self.tree.nb_leaves()
304	}
305
306	pub fn nb_nodes(&self) -> usize {
307		self.tree.nb_nodes()
308	}
309
310	/// Generate partial musig signatures for the nodes in the tree branch of the given
311	/// vtxo request.
312	///
313	/// Note that the signatures are indexed by their place in the tree and thus do not
314	/// necessarily match up with the indices in the secret nonces vector.
315	///
316	/// Aggregate nonces expected for all nodes, ordered from leaves to root.
317	/// Secret nonces expected for branch, ordered from leaf to root.
318	///
319	/// Returns [None] if the vtxo request is not part of the tree.
320	/// Returned signatures over the branch from leaf to root.
321	//TODO(stevenroose) streamline indices of nonces and sigs
322	pub fn cosign_branch(
323		&self,
324		cosign_agg_nonces: &[AggregatedNonce],
325		leaf_idx: usize,
326		cosign_key: &Keypair,
327		cosign_sec_nonces: Vec<SecretNonce>,
328	) -> Result<Vec<PartialSignature>, IncorrectSigningKeyError> {
329		let req = self.spec.vtxos.get(leaf_idx).expect("leaf idx out of bounds");
330		if Some(cosign_key.public_key()) != req.cosign_pubkey {
331			return Err(IncorrectSigningKeyError {
332				required: req.cosign_pubkey,
333				provided: cosign_key.public_key(),
334			});
335		}
336
337		let mut nonce_iter = cosign_sec_nonces.into_iter().enumerate();
338		let mut ret = Vec::with_capacity(self.tree.root().level() + 1);
339		for node in self.tree.iter_branch(leaf_idx) {
340			// Since we can skip a level, we sometimes have to skip a nonce.
341			// NB We can't just use the index into the sec_nonces vector, because
342			// musig requires us to use the owned SecNonce type to prevent footgun
343			// by reusing secret nonces.
344			let sec_nonce = loop {
345				let next = nonce_iter.next().expect("level overflow");
346				if next.0 == node.level() {
347					break next.1;
348				}
349			};
350
351			let cosign_pubkeys = node.leaves().filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
352				.chain(self.spec.global_cosign_pubkeys.iter().copied());
353			let sighash = self.sighashes[node.idx()];
354
355			let agg_pk = self.cosign_agg_pks[node.idx()];
356			let sig = musig::partial_sign(
357				cosign_pubkeys,
358				cosign_agg_nonces[node.idx()],
359				&cosign_key,
360				sec_nonce,
361				sighash.to_byte_array(),
362				Some(self.spec.cosign_taproot(agg_pk).tap_tweak().to_byte_array()),
363				None,
364			).0;
365			ret.push(sig);
366		}
367
368		Ok(ret)
369	}
370
371	/// Generate partial musig signatures for all nodes in the tree.
372	///
373	/// Nonces expected for all nodes, ordered from leaves to root.
374	///
375	/// Returns [None] if the vtxo request is not part of the tree.
376	pub fn cosign_tree(
377		&self,
378		cosign_agg_nonces: &[AggregatedNonce],
379		keypair: &Keypair,
380		cosign_sec_nonces: Vec<SecretNonce>,
381	) -> Vec<PartialSignature> {
382		assert_eq!(cosign_agg_nonces.len(), self.nb_nodes());
383		assert_eq!(cosign_sec_nonces.len(), self.nb_nodes());
384
385		self.tree.iter().zip(cosign_sec_nonces.into_iter()).map(|(node, sec_nonce)| {
386			let cosign_pubkeys = node.leaves().filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
387				.chain(self.spec.global_cosign_pubkeys.iter().copied());
388			let sighash = self.sighashes[node.idx()];
389
390			let agg_pk = self.cosign_agg_pks[node.idx()];
391			debug_assert_eq!(agg_pk, musig::combine_keys(cosign_pubkeys.clone()));
392			musig::partial_sign(
393				cosign_pubkeys,
394				cosign_agg_nonces[node.idx()],
395				&keypair,
396				sec_nonce,
397				sighash.to_byte_array(),
398				Some(self.spec.cosign_taproot(agg_pk).tap_tweak().to_byte_array()),
399				None,
400			).0
401		}).collect()
402	}
403
404	/// Verify partial cosign signature of a single node.
405	fn verify_node_cosign_partial_sig(
406		&self,
407		node: &tree::Node,
408		pk: PublicKey,
409		agg_nonces: &[AggregatedNonce],
410		part_sig: PartialSignature,
411		pub_nonce: PublicNonce,
412	) -> Result<(), CosignSignatureError> {
413		let cosign_pubkeys = node.leaves().filter_map(|i| self.spec.vtxos[i].cosign_pubkey)
414			.chain(self.spec.global_cosign_pubkeys.iter().copied());
415		let sighash = self.sighashes[node.idx()];
416
417		let taptweak = self.spec.cosign_taproot(self.cosign_agg_pks[node.idx()]).tap_tweak();
418		let key_agg = musig::tweaked_key_agg(cosign_pubkeys, taptweak.to_byte_array()).0;
419		let session = musig::Session::new(
420			&key_agg,
421			*agg_nonces.get(node.idx()).ok_or(CosignSignatureError::NotEnoughNonces)?,
422			&sighash.to_byte_array(),
423		);
424		let ok = session.partial_verify(&key_agg, &part_sig, &pub_nonce, musig::pubkey_to(pk));
425		if !ok {
426			return Err(CosignSignatureError::invalid_sig(pk));
427		}
428		Ok(())
429	}
430
431	/// Verify the partial cosign signatures from one of the leaves.
432	///
433	/// Nonces and partial signatures expected ordered from leaves to root.
434	pub fn verify_branch_cosign_partial_sigs(
435		&self,
436		cosign_agg_nonces: &[AggregatedNonce],
437		request: &SignedVtxoRequest,
438		cosign_pub_nonces: &[PublicNonce],
439		cosign_part_sigs: &[PartialSignature],
440	) -> Result<(), String> {
441		assert_eq!(cosign_agg_nonces.len(), self.nb_nodes());
442
443		let cosign_pubkey = request.cosign_pubkey.ok_or("no cosign pubkey for request")?;
444		let leaf_idx = self.spec.leaf_idx_of(request).ok_or("request not in tree")?;
445		// quickly check if the number of sigs is sane
446		match self.tree.iter_branch(leaf_idx).count().cmp(&cosign_part_sigs.len()) {
447			cmp::Ordering::Less => return Err("too few partial signatures".into()),
448			cmp::Ordering::Greater => return Err("too many partial signatures".into()),
449			cmp::Ordering::Equal => {},
450		}
451
452		let mut part_sigs_iter = cosign_part_sigs.iter();
453		let mut pub_nonce_iter = cosign_pub_nonces.iter().enumerate();
454		for node in self.tree.iter_branch(leaf_idx) {
455			let pub_nonce = loop {
456				let next = pub_nonce_iter.next().ok_or("not enough pub nonces")?;
457				if next.0 == node.level() {
458					break next.1;
459				}
460			};
461			self.verify_node_cosign_partial_sig(
462				node,
463				cosign_pubkey,
464				cosign_agg_nonces,
465				part_sigs_iter.next().ok_or("not enough sigs")?.clone(),
466				*pub_nonce,
467			).map_err(|e| format!("part sig verification failed: {}", e))?;
468		}
469
470		Ok(())
471	}
472
473	/// Verify the partial cosign signatures for all nodes.
474	///
475	/// Nonces and partial signatures expected ordered from leaves to root.
476	pub fn verify_global_cosign_partial_sigs(
477		&self,
478		pk: PublicKey,
479		agg_nonces: &[AggregatedNonce],
480		pub_nonces: &[PublicNonce],
481		part_sigs: &[PartialSignature],
482	) -> Result<(), CosignSignatureError> {
483		for node in self.tree.iter() {
484			self.verify_node_cosign_partial_sig(
485				node,
486				pk,
487				agg_nonces,
488				*part_sigs.get(node.idx()).ok_or_else(|| CosignSignatureError::missing_sig(pk))?,
489				*pub_nonces.get(node.idx()).ok_or_else(|| CosignSignatureError::NotEnoughNonces)?,
490			)?;
491		}
492
493		Ok(())
494	}
495
496	/// Combine all partial cosign signatures.
497	///
498	/// Nonces expected ordered from leaves to root.
499	/// Leaf signatures expected over leaf branch ordered from leaf to root.
500	/// server signatures expected ordered from leaves to root.
501	pub fn combine_partial_signatures(
502		&self,
503		cosign_agg_nonces: &[AggregatedNonce],
504		leaf_part_sigs: &HashMap<PublicKey, Vec<PartialSignature>>,
505		global_signer_part_sigs: &[impl AsRef<[PartialSignature]>],
506	) -> Result<Vec<schnorr::Signature>, CosignSignatureError> {
507		// to ease implementation, we're reconstructing the part sigs map with dequeues
508		let mut leaf_part_sigs = leaf_part_sigs.iter()
509			.map(|(pk, sigs)| (pk, sigs.iter().collect()))
510			.collect::<HashMap<_, VecDeque<_>>>();
511
512		if global_signer_part_sigs.len() != self.spec.global_cosign_pubkeys.len() {
513			return Err(CosignSignatureError::Invalid(
514				"invalid nb of global cosigner partial signatures",
515			));
516		}
517		for (pk, sigs) in self.spec.global_cosign_pubkeys.iter().zip(global_signer_part_sigs) {
518			if sigs.as_ref().len() != self.nb_nodes() {
519				return Err(CosignSignatureError::MissingSignature { pk: *pk });
520			}
521		}
522
523		let max_level = self.tree.root().level();
524		self.tree.iter().enumerate().map(|(idx, node)| {
525			let mut cosign_pks = Vec::with_capacity(max_level + 1);
526			let mut part_sigs = Vec::with_capacity(max_level + 1);
527			for leaf in node.leaves() {
528				if let Some(cosign_pk) = self.spec.vtxos[leaf].cosign_pubkey {
529					let part_sig = leaf_part_sigs.get_mut(&cosign_pk)
530						.ok_or(CosignSignatureError::missing_sig(cosign_pk))?
531						.pop_front()
532						.ok_or(CosignSignatureError::missing_sig(cosign_pk))?;
533					cosign_pks.push(cosign_pk);
534					part_sigs.push(part_sig);
535				}
536			}
537			// add global signers
538			cosign_pks.extend(&self.spec.global_cosign_pubkeys);
539			for sigs in global_signer_part_sigs {
540				part_sigs.push(sigs.as_ref().get(idx).expect("checked before"));
541			}
542
543			let agg_pk = self.cosign_agg_pks[node.idx()];
544			Ok(musig::combine_partial_signatures(
545				cosign_pks,
546				*cosign_agg_nonces.get(node.idx()).ok_or(CosignSignatureError::NotEnoughNonces)?,
547				self.sighashes[node.idx()].to_byte_array(),
548				Some(self.spec.cosign_taproot(agg_pk).tap_tweak().to_byte_array()),
549				&part_sigs
550			))
551		}).collect()
552	}
553
554	/// Verify the signatures of all the node txs.
555	///
556	/// Signatures expected ordered from leaves to root.
557	pub fn verify_cosign_sigs(
558		&self,
559		signatures: &[schnorr::Signature],
560	) -> Result<(), XOnlyPublicKey> {
561		for node in self.tree.iter() {
562			let sighash = self.sighashes[node.idx()];
563			let agg_pk = &self.cosign_agg_pks[node.idx()];
564			let pk = self.spec.cosign_taproot(*agg_pk).output_key().to_x_only_public_key();
565			let sig = signatures.get(node.idx()).ok_or_else(|| pk)?;
566			if SECP.verify_schnorr(sig, &sighash.into(), &pk).is_err() {
567				return Err(pk);
568			}
569		}
570		Ok(())
571	}
572
573	/// Convert into a [SignedVtxoTreeSpec] by providing the signatures.
574	///
575	/// Signatures expected ordered from leaves to root.
576	pub fn into_signed_tree(
577		self,
578		signatures: Vec<schnorr::Signature>,
579	) -> SignedVtxoTreeSpec {
580		SignedVtxoTreeSpec {
581			spec: self.spec,
582			utxo: self.utxo,
583			cosign_sigs: signatures,
584		}
585	}
586}
587
588/// Error returned from cosigning a VTXO tree.
589#[derive(PartialEq, Eq, thiserror::Error)]
590pub enum CosignSignatureError {
591	#[error("missing cosign signature from pubkey {pk}")]
592	MissingSignature { pk: PublicKey },
593	#[error("invalid cosign signature from pubkey {pk}")]
594	InvalidSignature { pk: PublicKey },
595	#[error("not enough nonces")]
596	NotEnoughNonces,
597	#[error("invalid cosign signatures: {0}")]
598	Invalid(&'static str),
599}
600
601impl fmt::Debug for CosignSignatureError {
602	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
603	    fmt::Display::fmt(self, f)
604	}
605}
606
607impl CosignSignatureError {
608	fn missing_sig(cosign_pk: PublicKey) -> CosignSignatureError {
609		CosignSignatureError::MissingSignature { pk: cosign_pk }
610	}
611	fn invalid_sig(cosign_pk: PublicKey) -> CosignSignatureError {
612		CosignSignatureError::InvalidSignature { pk: cosign_pk }
613	}
614}
615
616/// All the information needed to uniquely specify a fully signed VTXO tree.
617#[derive(Debug, Clone, PartialEq)]
618pub struct SignedVtxoTreeSpec {
619	pub spec: VtxoTreeSpec,
620	pub utxo: OutPoint,
621	/// The signatures for the txs from leaves to root.
622	pub cosign_sigs: Vec<schnorr::Signature>,
623}
624
625impl SignedVtxoTreeSpec {
626	/// Signatures expected ordered from leaves to root.
627	pub fn new(
628		spec: VtxoTreeSpec,
629		utxo: OutPoint,
630		signatures: Vec<schnorr::Signature>,
631	) -> SignedVtxoTreeSpec {
632		SignedVtxoTreeSpec { spec, utxo, cosign_sigs: signatures }
633	}
634
635	pub fn nb_leaves(&self) -> usize {
636		self.spec.nb_leaves()
637	}
638
639	/// Construct the exit branch starting from the root ending in the leaf.
640	pub fn exit_branch(&self, leaf_idx: usize) -> Option<Vec<Transaction>> {
641		let txs = self.all_signed_txs();
642
643		if leaf_idx >= self.spec.nb_leaves() {
644			return None;
645		}
646
647		let tree = Tree::new(self.spec.nb_leaves());
648		let mut ret = tree.iter_branch(leaf_idx)
649			.map(|n| txs[n.idx()].clone())
650			.collect::<Vec<_>>();
651		ret.reverse();
652		Some(ret)
653	}
654
655	/// Get all signed txs in this tree, starting with the leaves, towards the root.
656	pub fn all_signed_txs(&self) -> Vec<Transaction> {
657		self.spec.signed_transactions(self.utxo, &self.cosign_sigs)
658	}
659
660	pub fn into_cached_tree(self) -> CachedSignedVtxoTree {
661		CachedSignedVtxoTree {
662			txs: self.all_signed_txs(),
663			spec: self,
664		}
665	}
666}
667
668/// A fully signed VTXO tree, with all the transaction cached.
669///
670/// This is useful for cheap extraction of VTXO branches.
671pub struct CachedSignedVtxoTree {
672	pub spec: SignedVtxoTreeSpec,
673	/// All signed txs in this tree, starting with the leaves, towards the root.
674	pub txs: Vec<Transaction>,
675}
676
677impl CachedSignedVtxoTree {
678	/// Construct the exit branch starting from the root ending in the leaf.
679	pub fn exit_branch(&self, leaf_idx: usize) -> Option<Vec<&Transaction>> {
680		if leaf_idx >= self.spec.spec.nb_leaves() {
681			return None;
682		}
683
684		let tree = Tree::new(self.spec.spec.nb_leaves());
685		let mut ret = tree.iter_branch(leaf_idx)
686			.map(|n| &self.txs[n.idx()])
687			.collect::<Vec<_>>();
688		ret.reverse();
689		Some(ret)
690	}
691
692	pub fn nb_leaves(&self) -> usize {
693		self.spec.nb_leaves()
694	}
695
696	/// Get all signed txs in this tree, starting with the leaves, towards the root.
697	pub fn all_signed_txs(&self) -> &[Transaction] {
698		&self.txs
699	}
700
701	/// Construct the VTXO at the given leaf index.
702	pub fn build_vtxo(&self, leaf_idx: usize) -> Option<Vtxo> {
703		let req = self.spec.spec.vtxos.get(leaf_idx)?;
704		let genesis = {
705			let mut genesis = Vec::new();
706
707			let mut last_node = None;
708			let tree = Tree::new(self.spec.spec.nb_leaves());
709			for node in tree.iter_branch(leaf_idx) {
710				let transition = GenesisTransition::Cosigned {
711					pubkeys: node.leaves().filter_map(|i| self.spec.spec.vtxos[i].cosign_pubkey)
712						.chain(self.spec.spec.global_cosign_pubkeys.iter().copied())
713						.collect(),
714					signature: self.spec.cosign_sigs.get(node.idx()).cloned()
715						.expect("enough sigs for all nodes"),
716				};
717				let output_idx = {
718					if let Some(last) = last_node {
719						node.children().position(|child_idx| last == child_idx)
720							.expect("last node should be our child") as u8
721					} else {
722						// we start with the leaf, so this is the exit tx
723						0
724					}
725				};
726				let other_outputs = self.txs.get(node.idx()).expect("we have all txs")
727					.output.iter().enumerate()
728					.filter(|(i, o)| !o.is_p2a_fee_anchor() && *i != output_idx as usize)
729					.map(|(_i, o)| o).cloned().collect();
730				genesis.push(GenesisItem { transition, output_idx, other_outputs });
731				last_node = Some(node.idx());
732			}
733			genesis.reverse();
734			genesis
735		};
736
737		Some(Vtxo {
738			amount: req.vtxo.amount,
739			expiry_height: self.spec.spec.expiry_height,
740			server_pubkey: self.spec.spec.server_pubkey,
741			exit_delta: self.spec.spec.exit_delta,
742			anchor_point: self.spec.utxo,
743			genesis: genesis,
744			policy: req.vtxo.policy.clone(),
745			point: {
746				let leaf_tx = self.txs.get(leaf_idx).expect("leaf idx exists");
747				OutPoint::new(leaf_tx.compute_txid(), 0)
748			},
749		})
750	}
751
752	/// Construct all individual vtxos from this round.
753	///
754	/// This call is pretty wasteful.
755	pub fn all_vtxos(&self) -> impl Iterator<Item = Vtxo> + ExactSizeIterator + '_ {
756		(0..self.nb_leaves()).map(|idx| self.build_vtxo(idx).unwrap())
757	}
758}
759
760pub mod builder {
761	//! This module allows a single party to construct his own signed
762	//! VTXO tree, to then request signatures from the server.
763	//!
764	//! This is not used for rounds, where the tree is created with
765	//! many users at once.
766
767	use std::collections::HashMap;
768	use std::marker::PhantomData;
769
770	use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut};
771	use bitcoin::hashes::Hash;
772	use bitcoin::secp256k1::{Keypair, PublicKey};
773	use bitcoin_ext::{BlockDelta, BlockHeight};
774
775	use crate::{musig, SignedVtxoRequest, VtxoRequest};
776	use crate::error::IncorrectSigningKeyError;
777
778	use super::{CosignSignatureError, SignedVtxoTreeSpec, UnsignedVtxoTree, VtxoTreeSpec};
779
780	pub mod state {
781		mod sealed {
782			/// Just a trait to seal the BuilderState trait
783			pub trait Sealed {}
784			impl Sealed for super::Preparing {}
785			impl Sealed for super::CanGenerateNonces {}
786			impl Sealed for super::ServerCanCosign {}
787			impl Sealed for super::CanFinish {}
788		}
789
790		/// A marker trait used as a generic for [super::SignedTreeBuilder]
791		pub trait BuilderState: sealed::Sealed {}
792
793		/// The user is preparing the funding tx
794		pub struct Preparing;
795		impl BuilderState for Preparing {}
796
797		/// The UTXO that will be used to fund the tree is known, so the
798		/// user's signing nonces can be generated
799		pub struct CanGenerateNonces;
800		impl BuilderState for CanGenerateNonces {}
801
802		/// All the information for the server to cosign the tree is known
803		pub struct ServerCanCosign;
804		impl BuilderState for ServerCanCosign {}
805
806		/// The user is ready to build the tree as soon as it has
807		/// a cosign response from the server
808		pub struct CanFinish;
809		impl BuilderState for CanFinish {}
810
811		/// Trait to capture all states that have sufficient information
812		/// for either party to create signatures
813		pub trait CanSign: BuilderState {}
814		impl CanSign for ServerCanCosign {}
815		impl CanSign for CanFinish {}
816	}
817
818	/// Just an enum to hold either a tree spec or an unsigned tree
819	enum BuilderTree {
820		Spec(VtxoTreeSpec),
821		Unsigned(UnsignedVtxoTree),
822	}
823
824	impl BuilderTree {
825		fn unsigned_tree(&self) -> Option<&UnsignedVtxoTree> {
826			match self {
827				BuilderTree::Spec(_) => None,
828				BuilderTree::Unsigned(t) => Some(t),
829			}
830		}
831		fn into_unsigned_tree(self) -> Option<UnsignedVtxoTree> {
832			match self {
833				BuilderTree::Spec(_) => None,
834				BuilderTree::Unsigned(t) => Some(t),
835			}
836		}
837	}
838
839	/// A builder for a single party to construct a VTXO tree
840	///
841	/// For more information, see the module documentation.
842	pub struct SignedTreeBuilder<S: state::BuilderState> {
843		pub expiry_height: BlockHeight,
844		pub server_pubkey: PublicKey,
845		pub exit_delta: BlockDelta,
846		/// The cosign pubkey used to cosign all nodes in the tree
847		pub cosign_pubkey: PublicKey,
848
849		tree: BuilderTree,
850
851		/// users public nonces, leaves to the root
852		user_pub_nonces: Vec<musig::PublicNonce>,
853		/// users secret nonces, leaves to the root
854		/// this field is empty on the server side
855		user_sec_nonces: Option<Vec<musig::SecretNonce>>,
856		_state: PhantomData<S>,
857	}
858
859	impl<T: state::BuilderState> SignedTreeBuilder<T> {
860		fn tree_spec(&self) -> &VtxoTreeSpec {
861			match self.tree {
862				BuilderTree::Spec(ref s) => s,
863				BuilderTree::Unsigned(ref t) => &t.spec,
864			}
865		}
866
867		/// The total value required for the tree to be funded
868		pub fn total_required_value(&self) -> Amount {
869			self.tree_spec().total_required_value()
870		}
871
872		/// The scriptPubkey to send the board funds to
873		pub fn funding_script_pubkey(&self) -> ScriptBuf {
874			self.tree_spec().funding_tx_script_pubkey()
875		}
876
877		/// The TxOut to create in the funding tx
878		pub fn funding_txout(&self) -> TxOut {
879			let spec = self.tree_spec();
880			TxOut {
881				value: spec.total_required_value(),
882				script_pubkey: spec.funding_tx_script_pubkey(),
883			}
884		}
885	}
886
887	impl<T: state::CanSign> SignedTreeBuilder<T> {
888		/// Get the user's public nonces
889		pub fn user_pub_nonces(&self) -> &[musig::PublicNonce] {
890			assert!(!self.user_pub_nonces.is_empty(), "state invariant");
891			&self.user_pub_nonces
892		}
893	}
894
895	impl SignedTreeBuilder<state::Preparing> {
896		/// Construct the spec to be used in [SignedTreeBuilder]
897		pub fn construct_tree_spec(
898			vtxos: impl IntoIterator<Item = VtxoRequest>,
899			cosign_pubkey: PublicKey,
900			expiry_height: BlockHeight,
901			server_pubkey: PublicKey,
902			server_cosign_pubkey: PublicKey,
903			exit_delta: BlockDelta,
904		) -> VtxoTreeSpec {
905			let reqs = vtxos.into_iter()
906				.map(|vtxo| SignedVtxoRequest { cosign_pubkey: None, vtxo })
907				.collect::<Vec<_>>();
908			VtxoTreeSpec::new(
909				reqs,
910				server_pubkey,
911				expiry_height,
912				exit_delta,
913				// NB we place server last because then it looks closer like
914				// a regular user-signed tree which Vtxo::validate relies on
915				vec![cosign_pubkey, server_cosign_pubkey],
916			)
917		}
918
919		/// Create a new [SignedTreeBuilder]
920		pub fn new(
921			vtxos: impl IntoIterator<Item = VtxoRequest>,
922			cosign_pubkey: PublicKey,
923			expiry_height: BlockHeight,
924			server_pubkey: PublicKey,
925			server_cosign_pubkey: PublicKey,
926			exit_delta: BlockDelta,
927		) -> SignedTreeBuilder<state::Preparing> {
928			let tree = Self::construct_tree_spec(
929				vtxos,
930				cosign_pubkey,
931				expiry_height,
932				server_pubkey,
933				server_cosign_pubkey,
934				exit_delta,
935			);
936
937			SignedTreeBuilder {
938				expiry_height, server_pubkey, exit_delta, cosign_pubkey,
939				tree: BuilderTree::Spec(tree),
940				user_pub_nonces: Vec::new(),
941				user_sec_nonces: None,
942				_state: PhantomData,
943			}
944		}
945
946		/// Set the utxo from which the tree will be created
947		pub fn set_utxo(self, utxo: OutPoint) -> SignedTreeBuilder<state::CanGenerateNonces> {
948			let unsigned_tree = match self.tree {
949				BuilderTree::Spec(s) => s.into_unsigned_tree(utxo),
950				BuilderTree::Unsigned(t) => t, // should not happen
951			};
952			SignedTreeBuilder {
953				tree: BuilderTree::Unsigned(unsigned_tree),
954
955				expiry_height: self.expiry_height,
956				server_pubkey: self.server_pubkey,
957				exit_delta: self.exit_delta,
958				cosign_pubkey: self.cosign_pubkey,
959				user_pub_nonces: self.user_pub_nonces,
960				user_sec_nonces: self.user_sec_nonces,
961				_state: PhantomData,
962			}
963		}
964	}
965
966	impl SignedTreeBuilder<state::CanGenerateNonces> {
967		/// Generate user nonces
968		pub fn generate_user_nonces(
969			self,
970			cosign_key: &Keypair,
971		) -> SignedTreeBuilder<state::CanFinish> {
972			let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
973
974			let mut cosign_sec_nonces = Vec::with_capacity(unsigned_tree.sighashes.len());
975			let mut cosign_pub_nonces = Vec::with_capacity(unsigned_tree.sighashes.len());
976			for sh in &unsigned_tree.sighashes {
977				let pair = musig::nonce_pair_with_msg(&cosign_key, &sh.to_byte_array());
978				cosign_sec_nonces.push(pair.0);
979				cosign_pub_nonces.push(pair.1);
980			}
981
982			SignedTreeBuilder {
983				user_pub_nonces: cosign_pub_nonces,
984				user_sec_nonces: Some(cosign_sec_nonces),
985
986				expiry_height: self.expiry_height,
987				server_pubkey: self.server_pubkey,
988				exit_delta: self.exit_delta,
989				cosign_pubkey: self.cosign_pubkey,
990				tree: self.tree,
991				_state: PhantomData,
992			}
993		}
994	}
995
996	/// Holds the cosignature information of the server
997	#[derive(Debug, Clone)]
998	pub struct SignedTreeCosignResponse {
999		pub pub_nonces: Vec<musig::PublicNonce>,
1000		pub partial_signatures: Vec<musig::PartialSignature>,
1001	}
1002
1003	impl SignedTreeBuilder<state::ServerCanCosign> {
1004		/// Create a new [SignedTreeBuilder] for the server to cosign
1005		pub fn new_for_cosign(
1006			vtxos: impl IntoIterator<Item = VtxoRequest>,
1007			cosign_pubkey: PublicKey,
1008			expiry_height: BlockHeight,
1009			server_pubkey: PublicKey,
1010			server_cosign_pubkey: PublicKey,
1011			exit_delta: BlockDelta,
1012			utxo: OutPoint,
1013			user_pub_nonces: Vec<musig::PublicNonce>,
1014		) -> SignedTreeBuilder<state::ServerCanCosign> {
1015			let unsigned_tree = SignedTreeBuilder::construct_tree_spec(
1016				vtxos,
1017				cosign_pubkey,
1018				expiry_height,
1019				server_pubkey,
1020				server_cosign_pubkey,
1021				exit_delta,
1022			).into_unsigned_tree(utxo);
1023
1024			SignedTreeBuilder {
1025				expiry_height, server_pubkey, exit_delta, cosign_pubkey, user_pub_nonces,
1026				tree: BuilderTree::Unsigned(unsigned_tree),
1027				user_sec_nonces: None,
1028				_state: PhantomData,
1029			}
1030		}
1031
1032		/// The server cosigns the tree nodes
1033		pub fn server_cosign(&self, server_cosign_key: &Keypair) -> SignedTreeCosignResponse {
1034			let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1035
1036			let mut sec_nonces = Vec::with_capacity(unsigned_tree.sighashes.len());
1037			let mut pub_nonces = Vec::with_capacity(unsigned_tree.sighashes.len());
1038			for sh in &unsigned_tree.sighashes {
1039				let pair = musig::nonce_pair_with_msg(&server_cosign_key, &sh.to_byte_array());
1040				sec_nonces.push(pair.0);
1041				pub_nonces.push(pair.1);
1042			}
1043
1044			let agg_nonces = self.user_pub_nonces().iter().zip(&pub_nonces)
1045				.map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1046				.collect::<Vec<_>>();
1047
1048			let sigs = unsigned_tree.cosign_tree(&agg_nonces, &server_cosign_key, sec_nonces);
1049
1050			SignedTreeCosignResponse {
1051				pub_nonces,
1052				partial_signatures: sigs,
1053			}
1054		}
1055	}
1056
1057	impl SignedTreeBuilder<state::CanFinish> {
1058		/// Validate the server's partial signatures
1059		pub fn verify_cosign_response(
1060			&self,
1061			server_cosign: &SignedTreeCosignResponse,
1062		) -> Result<(), CosignSignatureError> {
1063			let unsigned_tree = self.tree.unsigned_tree().expect("state invariant");
1064
1065			let agg_nonces = self.user_pub_nonces().iter()
1066				.zip(&server_cosign.pub_nonces)
1067				.map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1068				.collect::<Vec<_>>();
1069
1070			unsigned_tree.verify_global_cosign_partial_sigs(
1071				*unsigned_tree.spec.global_cosign_pubkeys.get(1).expect("state invariant"),
1072				&agg_nonces,
1073				&server_cosign.pub_nonces,
1074				&server_cosign.partial_signatures,
1075			)
1076		}
1077
1078		pub fn build_tree(
1079			self,
1080			server_cosign: &SignedTreeCosignResponse,
1081			cosign_key: &Keypair,
1082		) -> Result<SignedVtxoTreeSpec, IncorrectSigningKeyError> {
1083			if cosign_key.public_key() != self.cosign_pubkey {
1084				return Err(IncorrectSigningKeyError {
1085					required: Some(self.cosign_pubkey),
1086					provided: cosign_key.public_key(),
1087				});
1088			}
1089
1090			let agg_nonces = self.user_pub_nonces().iter().zip(&server_cosign.pub_nonces)
1091				.map(|(u, s)| musig::AggregatedNonce::new(&[u, s]))
1092				.collect::<Vec<_>>();
1093
1094			let unsigned_tree = self.tree.into_unsigned_tree().expect("state invariant");
1095			let sec_nonces = self.user_sec_nonces.expect("state invariant");
1096			let partial_sigs = unsigned_tree.cosign_tree(&agg_nonces, cosign_key, sec_nonces);
1097
1098			debug_assert!(unsigned_tree.verify_global_cosign_partial_sigs(
1099				self.cosign_pubkey,
1100				&agg_nonces,
1101				&self.user_pub_nonces,
1102				&partial_sigs,
1103			).is_ok(), "produced invalid partial signatures");
1104
1105			let sigs = unsigned_tree.combine_partial_signatures(
1106				&agg_nonces,
1107				&HashMap::new(),
1108				&[&server_cosign.partial_signatures, &partial_sigs],
1109			).expect("should work with correct cosign signatures");
1110
1111			Ok(unsigned_tree.into_signed_tree(sigs))
1112		}
1113	}
1114}
1115
1116/// The serialization version of [VtxoTreeSpec].
1117const VTXO_TREE_SPEC_VERSION: u8 = 0x02;
1118
1119impl ProtocolEncoding for VtxoTreeSpec {
1120	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1121		w.emit_u8(VTXO_TREE_SPEC_VERSION)?;
1122		w.emit_u32(self.expiry_height)?;
1123		self.server_pubkey.encode(w)?;
1124		w.emit_u16(self.exit_delta)?;
1125		w.emit_compact_size(self.global_cosign_pubkeys.len() as u64)?;
1126		for pk in &self.global_cosign_pubkeys {
1127			pk.encode(w)?;
1128		}
1129
1130		w.emit_compact_size(self.vtxos.len() as u64)?;
1131		for vtxo in &self.vtxos {
1132			vtxo.vtxo.policy.encode(w)?;
1133			w.emit_u64(vtxo.vtxo.amount.to_sat())?;
1134			if let Some(pk) = vtxo.cosign_pubkey {
1135				pk.encode(w)?;
1136			} else {
1137				w.emit_u8(0)?;
1138			}
1139		}
1140		Ok(())
1141	}
1142
1143	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, crate::encode::ProtocolDecodingError> {
1144		let version = r.read_u8()?;
1145
1146		// be compatible with old version
1147		if version == 0x01 {
1148			let expiry_height = r.read_u32()?;
1149			let server_pubkey = PublicKey::decode(r)?;
1150			let exit_delta = r.read_u16()?;
1151			let server_cosign_pk = PublicKey::decode(r)?;
1152
1153			let nb_vtxos = r.read_u32()?;
1154			let mut vtxos = Vec::with_capacity(nb_vtxos as usize);
1155			for _ in 0..nb_vtxos {
1156				let output = VtxoPolicy::decode(r)?;
1157				let amount = Amount::from_sat(r.read_u64()?);
1158				let cosign_pk = PublicKey::decode(r)?;
1159				vtxos.push(SignedVtxoRequest {
1160					vtxo: VtxoRequest { policy: output, amount },
1161					cosign_pubkey: Some(cosign_pk),
1162				});
1163			}
1164
1165			return Ok(VtxoTreeSpec {
1166				vtxos, expiry_height, server_pubkey, exit_delta,
1167				global_cosign_pubkeys: vec![server_cosign_pk],
1168			});
1169		}
1170
1171		if version != VTXO_TREE_SPEC_VERSION {
1172			return Err(ProtocolDecodingError::invalid(format_args!(
1173				"invalid VtxoTreeSpec encoding version byte: {version:#x}",
1174			)));
1175		}
1176
1177		let expiry_height = r.read_u32()?;
1178		let server_pubkey = PublicKey::decode(r)?;
1179		let exit_delta = r.read_u16()?;
1180		let nb_global_signers = r.read_compact_size()?;
1181		let mut global_cosign_pubkeys = Vec::with_capacity(nb_global_signers as usize);
1182		for _ in 0..nb_global_signers {
1183			global_cosign_pubkeys.push(PublicKey::decode(r)?);
1184		}
1185
1186		let nb_vtxos = r.read_compact_size()?;
1187		let mut vtxos = Vec::with_capacity(nb_vtxos as usize);
1188		for _ in 0..nb_vtxos {
1189			let output = VtxoPolicy::decode(r)?;
1190			let amount = Amount::from_sat(r.read_u64()?);
1191			let cosign_pubkey = Option::<PublicKey>::decode(r)?;
1192			vtxos.push(SignedVtxoRequest { vtxo: VtxoRequest { policy: output, amount }, cosign_pubkey });
1193		}
1194
1195		Ok(VtxoTreeSpec { vtxos, expiry_height, server_pubkey, exit_delta, global_cosign_pubkeys })
1196	}
1197}
1198
1199/// The serialization version of [SignedVtxoTreeSpec].
1200const SIGNED_VTXO_TREE_SPEC_VERSION: u8 = 0x01;
1201
1202impl ProtocolEncoding for SignedVtxoTreeSpec {
1203	fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
1204		w.emit_u8(SIGNED_VTXO_TREE_SPEC_VERSION)?;
1205		self.spec.encode(w)?;
1206		self.utxo.encode(w)?;
1207		w.emit_u32(self.cosign_sigs.len() as u32)?;
1208		for sig in &self.cosign_sigs {
1209			sig.encode(w)?;
1210		}
1211		Ok(())
1212	}
1213
1214	fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, crate::encode::ProtocolDecodingError> {
1215		let version = r.read_u8()?;
1216		if version != SIGNED_VTXO_TREE_SPEC_VERSION {
1217			return Err(ProtocolDecodingError::invalid(format_args!(
1218				"invalid SignedVtxoTreeSpec encoding version byte: {version:#x}",
1219			)));
1220		}
1221		let spec = VtxoTreeSpec::decode(r)?;
1222		let utxo = OutPoint::decode(r)?;
1223		let nb_cosign_sigs = r.read_u32()?;
1224		let mut cosign_sigs = Vec::with_capacity(nb_cosign_sigs as usize);
1225		for _ in 0..nb_cosign_sigs {
1226			cosign_sigs.push(schnorr::Signature::decode(r)?);
1227		}
1228		Ok(SignedVtxoTreeSpec { spec, utxo, cosign_sigs })
1229	}
1230}
1231
1232
1233#[cfg(test)]
1234mod test {
1235	use std::iter;
1236	use std::collections::HashMap;
1237	use std::str::FromStr;
1238
1239	use bitcoin::hashes::{siphash24, sha256, Hash, HashEngine};
1240	use bitcoin::secp256k1::{self, rand, Keypair};
1241	use bitcoin::{absolute, transaction};
1242	use rand::SeedableRng;
1243
1244	use crate::encode;
1245	use crate::encode::test::{encoding_roundtrip, json_roundtrip};
1246	use crate::vtxo::{ValidationResult, VtxoPolicy};
1247	use crate::tree::signed::builder::SignedTreeBuilder;
1248
1249	use super::*;
1250
1251	fn test_tree_amounts(
1252		tree: &UnsignedVtxoTree,
1253		root_value: Amount,
1254	) {
1255		let map = tree.txs.iter().map(|tx| (tx.compute_txid(), tx)).collect::<HashMap<_, _>>();
1256
1257		// skip the root
1258		for (idx, tx) in tree.txs.iter().take(tree.txs.len() - 1).enumerate() {
1259			println!("tx #{idx}: {}", bitcoin::consensus::encode::serialize_hex(tx));
1260			let input = tx.input.iter().map(|i| {
1261				let prev = i.previous_output;
1262				map.get(&prev.txid).expect(&format!("tx {} not found", prev.txid))
1263					.output[prev.vout as usize].value
1264			}).sum::<Amount>();
1265			let output = tx.output_value();
1266			assert!(input >= output);
1267			assert_eq!(input, output);
1268		}
1269
1270		// check the root
1271		let root = tree.txs.last().unwrap();
1272		assert_eq!(root_value, root.output_value());
1273	}
1274
1275	#[test]
1276	fn vtxo_tree() {
1277		let secp = secp256k1::Secp256k1::new();
1278		let mut rand = rand::rngs::StdRng::seed_from_u64(42);
1279		let random_sig = {
1280			let key = Keypair::new(&secp, &mut rand);
1281			let sha = sha256::Hash::from_str("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap();
1282			let msg = secp256k1::Message::from_digest(sha.to_byte_array());
1283			secp.sign_schnorr(&msg, &key)
1284		};
1285
1286		let server_key = Keypair::new(&secp, &mut rand);
1287		let server_cosign_key = Keypair::new(&secp, &mut rand);
1288
1289		struct Req {
1290			key: Keypair,
1291			cosign_key: Keypair,
1292			amount: Amount,
1293		}
1294		impl Req {
1295			fn to_vtxo(&self) -> SignedVtxoRequest {
1296				SignedVtxoRequest {
1297					vtxo: VtxoRequest {
1298						amount: self.amount,
1299						policy: VtxoPolicy::new_pubkey(self.key.public_key()),
1300					},
1301					cosign_pubkey: Some(self.cosign_key.public_key()),
1302				}
1303			}
1304		}
1305
1306		let nb_leaves = 27;
1307		let reqs = iter::repeat_with(|| Req {
1308			key: Keypair::new(&secp, &mut rand),
1309			cosign_key:  Keypair::new(&secp, &mut rand),
1310			amount: Amount::from_sat(100_000),
1311		}).take(nb_leaves).collect::<Vec<_>>();
1312		let point = "0000000000000000000000000000000000000000000000000000000000000001:1".parse().unwrap();
1313
1314		let spec = VtxoTreeSpec::new(
1315			reqs.iter().map(|r| r.to_vtxo()).collect(),
1316			server_key.public_key(),
1317			101_000,
1318			2016,
1319			vec![server_cosign_key.public_key()],
1320		);
1321		assert_eq!(spec.nb_leaves(), nb_leaves);
1322		assert_eq!(spec.total_required_value().to_sat(), 2700000);
1323		let nb_nodes = spec.nb_nodes();
1324
1325		encoding_roundtrip(&spec);
1326
1327		let unsigned = spec.into_unsigned_tree(point);
1328
1329		test_tree_amounts(&unsigned, unsigned.spec.total_required_value());
1330
1331		let sighashes_hash = {
1332			let mut eng = siphash24::Hash::engine();
1333			unsigned.sighashes.iter().for_each(|h| eng.input(&h[..]));
1334			siphash24::Hash::from_engine(eng)
1335		};
1336		assert_eq!(sighashes_hash.to_string(), "44c13179cd19569f");
1337
1338		let signed = unsigned.into_signed_tree(vec![random_sig; nb_nodes]);
1339
1340		encoding_roundtrip(&signed);
1341
1342		#[derive(Debug, PartialEq, Serialize, Deserialize)]
1343		struct JsonSignedVtxoTreeSpec {
1344			#[serde(with = "encode::serde")]
1345			pub spec: SignedVtxoTreeSpec,
1346		}
1347
1348		json_roundtrip(&JsonSignedVtxoTreeSpec { spec: signed.clone() });
1349
1350		for l in 0..nb_leaves {
1351			let exit = signed.exit_branch(l).unwrap();
1352
1353			// Assert it's a valid chain.
1354			let mut iter = exit.iter().enumerate().peekable();
1355			while let Some((i, cur)) = iter.next() {
1356				if let Some((_, next)) = iter.peek() {
1357					assert_eq!(next.input[0].previous_output.txid, cur.compute_txid(), "{}", i);
1358				}
1359			}
1360		}
1361
1362		let cached = signed.into_cached_tree();
1363		for vtxo in cached.all_vtxos() {
1364			encoding_roundtrip(&vtxo);
1365		}
1366	}
1367
1368	#[test]
1369	fn test_tree_builder() {
1370		let expiry = 100_000;
1371		let exit_delta = 24;
1372
1373		let vtxo_pubkey = "035e160cd261ac8ffcd2866a5aab2116bc90fbefdb1d739531e121eee612583802".parse().unwrap();
1374		let policy = VtxoPolicy::new_pubkey(vtxo_pubkey);
1375		let vtxos = (1..50).map(|i| VtxoRequest {
1376			amount: Amount::from_sat(1000 * i),
1377			policy: policy.clone(),
1378		}).collect::<Vec<_>>();
1379
1380		let user_cosign_key = Keypair::from_str("5255d132d6ec7d4fc2a41c8f0018bb14343489ddd0344025cc60c7aa2b3fda6a").unwrap();
1381		let user_cosign_pubkey = user_cosign_key.public_key();
1382		println!("cosign_pubkey: {}", user_cosign_pubkey);
1383
1384		let server_key = Keypair::from_str("1fb316e653eec61de11c6b794636d230379509389215df1ceb520b65313e5426").unwrap();
1385		let server_pubkey = server_key.public_key();
1386		println!("server_pubkey: {}", server_pubkey);
1387
1388		let server_cosign_key = Keypair::from_str("52a506fbae3b725749d2486afd4761841ec685b841c2967e30f24182c4b02eed").unwrap();
1389		let server_cosign_pubkey = server_cosign_key.public_key();
1390		println!("server_cosign_pubkey: {}", server_cosign_pubkey);
1391
1392		let builder = SignedTreeBuilder::new(
1393			vtxos.iter().cloned(), user_cosign_pubkey, expiry, server_pubkey, server_cosign_pubkey,
1394			exit_delta,
1395		);
1396		assert_eq!(builder.total_required_value(), Amount::from_sat(1_225_000));
1397		assert_eq!(builder.funding_script_pubkey().to_hex_string(), "5120ca542aaf6c76c4b4c7822d73d91551ef42482098f3675d915d61782448b2ac5b");
1398
1399		let funding_tx = Transaction {
1400			version: transaction::Version::TWO,
1401			lock_time: absolute::LockTime::ZERO,
1402			input: vec![],
1403			output: vec![builder.funding_txout()],
1404		};
1405		let utxo = OutPoint::new(funding_tx.compute_txid(), 0);
1406		assert_eq!(utxo.to_string(), "49b930a56b7eb510813600b54d01e6017cbb41895e137892c0b41e6399779ef7:0");
1407		let builder = builder.set_utxo(utxo).generate_user_nonces(&user_cosign_key);
1408		let user_pub_nonces = builder.user_pub_nonces().to_vec();
1409
1410		let cosign = {
1411			let builder = SignedTreeBuilder::new_for_cosign(
1412				vtxos.iter().cloned(), user_cosign_pubkey, expiry, server_pubkey,
1413				server_cosign_pubkey, exit_delta, utxo, user_pub_nonces,
1414			);
1415			builder.server_cosign(&server_cosign_key)
1416		};
1417
1418		builder.verify_cosign_response(&cosign).unwrap();
1419		let tree = builder.build_tree(&cosign, &user_cosign_key).unwrap().into_cached_tree();
1420
1421		// check that vtxos are valid
1422		for vtxo in tree.all_vtxos() {
1423			assert_eq!(vtxo.validate(&funding_tx).unwrap(), ValidationResult::Cosigned);
1424		}
1425	}
1426}