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