clementine_core/builder/transaction/
input.rs1use crate::bitvm_client;
8use crate::builder::script::SpendableScript;
9use crate::builder::sighash::TapTweakData;
10use crate::builder::{address::create_taproot_address, script::SpendPath};
11use crate::config::protocol::ProtocolParamset;
12use crate::rpc::clementine::tagged_signature::SignatureId;
13use bitcoin::{
14 taproot::{LeafVersion, TaprootSpendInfo},
15 Amount, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Witness, WitnessProgram, XOnlyPublicKey,
16};
17use std::sync::Arc;
18use thiserror::Error;
19
20pub type BlockHeight = u16;
21
22#[derive(Debug, Clone)]
23pub struct SpendableTxIn {
25 previous_outpoint: OutPoint,
27 prevout: TxOut, scripts: Vec<Arc<dyn SpendableScript>>,
30 spendinfo: Option<TaprootSpendInfo>,
32}
33
34#[derive(Clone, Debug, Error, PartialEq)]
35pub enum SpendableTxInError {
37 #[error(
38 "The taproot spend info contains an incomplete merkle proof map. Some scripts are missing."
39 )]
40 IncompleteMerkleProofMap,
41
42 #[error("The script_pubkey of the previous output does not match the expected script_pubkey for the taproot spending information.")]
43 IncorrectScriptPubkey,
44
45 #[error("Error creating a spendable txin: {0}")]
46 Error(String),
47}
48
49#[derive(Debug, Clone, Copy)]
50pub enum UtxoVout {
53 Assert(usize),
55 WatchtowerChallenge(usize),
57 WatchtowerChallengeAck(usize),
59 Challenge,
61 KickoffFinalizer,
63 ReimburseInKickoff,
65 Disprove,
67 LatestBlockhash,
69 DepositInMove,
71 ReimburseInRound(usize, &'static ProtocolParamset),
73 Kickoff(usize),
75 CollateralInRound,
77 CollateralInReadyToReimburse,
79}
80
81impl UtxoVout {
82 pub fn get_vout(self) -> u32 {
84 match self {
85 UtxoVout::Assert(idx) => idx as u32 + 5,
86 UtxoVout::WatchtowerChallenge(idx) => {
87 (2 * idx + 5 + bitvm_client::ClementineBitVMPublicKeys::number_of_assert_txs())
88 as u32
89 }
90 UtxoVout::WatchtowerChallengeAck(idx) => {
91 (2 * idx + 6 + bitvm_client::ClementineBitVMPublicKeys::number_of_assert_txs())
92 as u32
93 }
94 UtxoVout::Challenge => 0,
95 UtxoVout::KickoffFinalizer => 1,
96 UtxoVout::ReimburseInKickoff => 2,
97 UtxoVout::Disprove => 3,
98 UtxoVout::LatestBlockhash => 4,
99 UtxoVout::ReimburseInRound(idx, paramset) => {
100 (paramset.num_kickoffs_per_round + idx + 1) as u32
101 }
102 UtxoVout::Kickoff(idx) => idx as u32 + 1,
103 UtxoVout::DepositInMove => 0,
104 UtxoVout::CollateralInRound => 0,
105 UtxoVout::CollateralInReadyToReimburse => 0,
106 }
107 }
108}
109
110impl SpendableTxIn {
111 pub fn get_prevout(&self) -> &TxOut {
113 &self.prevout
114 }
115
116 pub fn get_prev_outpoint(&self) -> &OutPoint {
118 &self.previous_outpoint
119 }
120
121 pub fn new_partial(previous_output: OutPoint, prevout: TxOut) -> SpendableTxIn {
123 Self::new(previous_output, prevout, vec![], None)
124 }
125
126 pub fn from_scripts(
140 previous_output: OutPoint,
141 value: Amount,
142 scripts: Vec<Arc<dyn SpendableScript>>,
143 key_path: Option<XOnlyPublicKey>,
144 network: bitcoin::Network,
145 ) -> SpendableTxIn {
146 let script_bufs: Vec<ScriptBuf> = scripts
147 .iter()
148 .map(|script| script.clone().to_script_buf())
149 .collect();
150 let (addr, spend_info) = create_taproot_address(&script_bufs, key_path, network);
151 Self::new(
152 previous_output,
153 TxOut {
154 value,
155 script_pubkey: addr.script_pubkey(),
156 },
157 scripts,
158 Some(spend_info),
159 )
160 }
161
162 #[inline(always)]
164 pub fn new(
165 previous_output: OutPoint,
166 prevout: TxOut,
167 scripts: Vec<Arc<dyn SpendableScript>>,
168 spendinfo: Option<TaprootSpendInfo>,
169 ) -> SpendableTxIn {
170 if cfg!(debug_assertions) {
171 return Self::from_checked(previous_output, prevout, scripts, spendinfo)
172 .expect("failed to construct a spendabletxin in debug mode");
173 }
174
175 Self::from_unchecked(previous_output, prevout, scripts, spendinfo)
176 }
177
178 pub fn get_scripts(&self) -> &Vec<Arc<dyn SpendableScript>> {
180 &self.scripts
181 }
182
183 pub fn get_spend_info(&self) -> &Option<TaprootSpendInfo> {
185 &self.spendinfo
186 }
187
188 pub fn set_spend_info(&mut self, spendinfo: Option<TaprootSpendInfo>) {
190 self.spendinfo = spendinfo;
191 #[cfg(debug_assertions)]
192 self.check().expect("spendinfo is invalid in debug mode");
193 }
194
195 fn check(&self) -> Result<(), SpendableTxInError> {
197 use SpendableTxInError::*;
198 let Some(spendinfo) = self.spendinfo.as_ref() else {
199 return Ok(());
200 };
201
202 let (prevout, scripts) = (&self.prevout, &self.scripts);
203
204 if ScriptBuf::new_witness_program(&WitnessProgram::p2tr_tweaked(spendinfo.output_key()))
205 != prevout.script_pubkey
206 {
207 return Err(IncorrectScriptPubkey);
208 }
209 let script_bufs: Vec<ScriptBuf> = scripts
210 .iter()
211 .map(|script| script.to_script_buf())
212 .collect();
213 if script_bufs.into_iter().any(|script| {
214 spendinfo
215 .script_map()
216 .get(&(script, LeafVersion::TapScript))
217 .is_none()
218 }) {
219 return Err(IncompleteMerkleProofMap);
220 }
221 Ok(())
222 }
223
224 fn from_checked(
226 previous_output: OutPoint,
227 prevout: TxOut,
228 scripts: Vec<Arc<dyn SpendableScript>>,
229 spendinfo: Option<TaprootSpendInfo>,
230 ) -> Result<SpendableTxIn, SpendableTxInError> {
231 let this = Self::from_unchecked(previous_output, prevout, scripts, spendinfo);
232 this.check()?;
233 Ok(this)
234 }
235
236 fn from_unchecked(
238 previous_outpoint: OutPoint,
239 prevout: TxOut,
240 scripts: Vec<Arc<dyn SpendableScript>>,
241 spendinfo: Option<TaprootSpendInfo>,
242 ) -> SpendableTxIn {
243 SpendableTxIn {
244 previous_outpoint,
245 prevout,
246 scripts,
247 spendinfo,
248 }
249 }
250}
251
252#[allow(dead_code)]
253#[derive(Debug, Clone)]
254pub struct SpentTxIn {
256 spendable: SpendableTxIn,
257 sequence: Sequence,
262 witness: Option<Witness>,
267 spend_path: SpendPath,
269 input_id: SignatureId,
271}
272
273impl SpentTxIn {
274 pub fn from_spendable(
276 input_id: SignatureId,
277 spendable: SpendableTxIn,
278 spend_path: SpendPath,
279 sequence: Sequence,
280 witness: Option<Witness>,
281 ) -> SpentTxIn {
282 SpentTxIn {
283 spendable,
284 sequence,
285 witness,
286 spend_path,
287 input_id,
288 }
289 }
290
291 pub fn get_spendable(&self) -> &SpendableTxIn {
293 &self.spendable
294 }
295
296 pub fn get_spend_path(&self) -> SpendPath {
298 self.spend_path
299 }
300
301 pub fn get_tweak_data(&self) -> TapTweakData {
303 match self.spend_path {
304 SpendPath::ScriptSpend(_) => TapTweakData::ScriptPath,
305 SpendPath::KeySpend => {
306 let spendinfo = self.spendable.get_spend_info();
307 match spendinfo {
308 Some(spendinfo) => TapTweakData::KeyPath(spendinfo.merkle_root()),
309 None => TapTweakData::Unknown,
310 }
311 }
312 SpendPath::Unknown => TapTweakData::Unknown,
313 }
314 }
315
316 pub fn get_witness(&self) -> &Option<Witness> {
318 &self.witness
319 }
320
321 pub fn get_signature_id(&self) -> SignatureId {
323 self.input_id
324 }
325
326 pub fn set_witness(&mut self, witness: Witness) {
328 self.witness = Some(witness);
329 }
330
331 pub fn to_txin(&self) -> TxIn {
341 TxIn {
342 previous_output: self.spendable.previous_outpoint,
343 sequence: self.sequence,
344 script_sig: ScriptBuf::default(),
345 witness: self.witness.clone().unwrap_or_default(),
346 }
347 }
348}