ark/vtxo/policy/
signing.rs

1
2use std::borrow::Borrow;
3
4use bitcoin::hashes::Hash;
5use bitcoin::{TapSighash, Transaction, TxOut, Witness, sighash, taproot};
6
7use crate::Vtxo;
8use crate::vtxo::policy::clause::VtxoClause;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
11#[error("the vtxo has no clause signable by the provided signer")]
12pub struct CannotSignVtxoError;
13
14/// A trait to implement a signer for a [Vtxo].
15#[async_trait::async_trait]
16pub trait VtxoSigner {
17	/// Returns the witness for a [VtxoClause] if it is signable, otherwise [None].
18	async fn witness(
19		&self,
20		clause: &VtxoClause,
21		control_block: &taproot::ControlBlock,
22		sighash: TapSighash,
23	) -> Option<Witness>;
24
25	/// Returns true if the clause is signable, otherwise false.
26	async fn can_sign(&self, clause: &VtxoClause, vtxo: &Vtxo) -> bool {
27		// NB: We won't use the witness after this, so we can use all zeros
28		let sighash = TapSighash::all_zeros();
29		let cb = clause.control_block(vtxo);
30		self.witness(clause, &cb, sighash).await.is_some()
31	}
32
33	/// Returns the first signable clause from [Vtxo]'s policy.
34	/// If no clause is signable, returns [None].
35	async fn find_signable_clause(&self, vtxo: &Vtxo) -> Option<VtxoClause> {
36		let exit_delta = vtxo.exit_delta();
37		let expiry_height = vtxo.expiry_height();
38		let server_pubkey = vtxo.server_pubkey();
39
40		let clauses = vtxo.policy().clauses(exit_delta, expiry_height, server_pubkey);
41
42		for clause in clauses {
43			if self.can_sign(&clause, vtxo).await {
44				return Some(clause);
45			}
46		}
47
48		None
49	}
50
51	/// Return the full witness for a [Vtxo] using the first signable clause.
52	///
53	/// # Errors
54	///
55	/// Returns [CannotSignVtxoError] if no clause is signable.
56	async fn sign_input(
57		&self,
58		vtxo: &Vtxo,
59		input_idx: usize,
60		sighash_cache: &mut sighash::SighashCache<impl Borrow<Transaction> + Send + Sync>,
61		prevouts: &sighash::Prevouts<impl Borrow<TxOut> + Send + Sync>,
62	) -> Result<Witness, CannotSignVtxoError> {
63		let clause = self.find_signable_clause(vtxo).await
64			.ok_or(CannotSignVtxoError)?;
65		self.sign_input_with_clause(vtxo, &clause, input_idx, sighash_cache, prevouts).await
66	}
67
68	/// Return the full witness for a [Vtxo] using the specified clause.
69	///
70	/// # Errors
71	///
72	/// Returns [CannotSignVtxoError] if the clause is not signable.
73	async fn sign_input_with_clause(
74		&self,
75		vtxo: &Vtxo,
76		clause: &VtxoClause,
77		input_idx: usize,
78		sighash_cache: &mut sighash::SighashCache<impl Borrow<Transaction> + Send + Sync>,
79		prevouts: &sighash::Prevouts<impl Borrow<TxOut> + Send + Sync>,
80	) -> Result<Witness, CannotSignVtxoError> {
81		let cb = clause.control_block(vtxo);
82
83		let exit_script = clause.tapscript();
84		let leaf_hash = taproot::TapLeafHash::from_script(
85			&exit_script,
86			taproot::LeafVersion::TapScript,
87		);
88
89		let sighash = sighash_cache.taproot_script_spend_signature_hash(
90			input_idx, &prevouts, leaf_hash, sighash::TapSighashType::Default,
91		).expect("all prevouts provided");
92
93		let witness = self.witness(clause, &cb, sighash).await
94			.ok_or(CannotSignVtxoError)?;
95
96		debug_assert_eq!(
97			witness.size(), clause.witness_size(vtxo),
98			"actual witness size ({}) does not match expected ({})",
99			witness.size(), clause.witness_size(vtxo)
100		);
101
102		Ok(witness)
103	}
104}