Skip to main content

ark/vtxo/policy/
signing.rs

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