clementine_core/builder/transaction/
input.rs

1//! # Transaction Input Types and Utilities
2//!
3//! This module defines types and utilities for representing and handling transaction inputs used in [`super::TxHandler`].
4//! It provides abstractions for spendable inputs, input errors, correctness checks, supporting Taproot and script path spends.
5//!
6
7use 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)]
23/// Represents a spendable transaction input, including previous output, scripts, and Taproot spend info.
24pub struct SpendableTxIn {
25    /// The reference to the previous output that is being used as an input.
26    previous_outpoint: OutPoint,
27    prevout: TxOut, // locking script (taproot => op_1 op_pushbytes_32 tweaked pk)
28    /// Scripts associated with this input (for script path spends).
29    scripts: Vec<Arc<dyn SpendableScript>>,
30    /// Optional Taproot spend info for this input.
31    spendinfo: Option<TaprootSpendInfo>,
32}
33
34#[derive(Clone, Debug, Error, PartialEq)]
35/// Error type for spendable input construction and validation.
36pub 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)]
50/// Enumerates protocol-specific UTXO output indices for transaction construction.
51/// Used to identify the vout of specific UTXOs in protocol transactions.
52pub enum UtxoVout {
53    /// The vout of the assert utxo in KickoffTx
54    Assert(usize),
55    /// The vout of the watchtower challenge utxo in KickoffTx
56    WatchtowerChallenge(usize),
57    /// The vout of the watchtower challenge ack utxo in KickoffTx
58    WatchtowerChallengeAck(usize),
59    /// The vout of the challenge utxo in KickoffTx
60    Challenge,
61    /// The vout of the kickoff finalizer utxo in KickoffTx
62    KickoffFinalizer,
63    /// The vout of the reimburse utxo in KickoffTx
64    ReimburseInKickoff,
65    /// The vout of the disprove utxo in KickoffTx
66    Disprove,
67    /// The vout of the latest blockhash utxo in KickoffTx
68    LatestBlockhash,
69    /// The vout of the deposited btc utxo in MoveTx
70    DepositInMove,
71    /// The vout of the reimburse connector utxo in RoundTx
72    ReimburseInRound(usize, &'static ProtocolParamset),
73    /// The vout of the kickoff utxo in RoundTx
74    Kickoff(usize),
75    /// The vout of the collateral utxo in RoundTx
76    CollateralInRound,
77    /// The vout of the collateral utxo in ReadyToReimburseTx
78    CollateralInReadyToReimburse,
79}
80
81impl UtxoVout {
82    /// Returns the vout index for this UTXO in the corresponding transaction.
83    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    /// Returns a reference to the previous output (TxOut) for this input.
112    pub fn get_prevout(&self) -> &TxOut {
113        &self.prevout
114    }
115
116    /// Returns a reference to the previous outpoint (OutPoint) for this input.
117    pub fn get_prev_outpoint(&self) -> &OutPoint {
118        &self.previous_outpoint
119    }
120
121    /// Creates a new [`SpendableTxIn`] with only a previous output and TxOut (no scripts or spend info).
122    pub fn new_partial(previous_output: OutPoint, prevout: TxOut) -> SpendableTxIn {
123        Self::new(previous_output, prevout, vec![], None)
124    }
125
126    /// Constructs a [`SpendableTxIn`] from scripts, value, and the internal key. Giving None for the internal key will create the tx
127    /// with an unspendable internal key.
128    ///
129    /// # Arguments
130    /// * `previous_output` - The outpoint being spent.
131    /// * `value` - The value of the previous output.
132    /// * `scripts` - Scripts for script path spends.
133    /// * `key_path` - The internal key for key path spends.
134    /// * `network` - Bitcoin network.
135    ///
136    /// # Returns
137    ///
138    /// A new [`SpendableTxIn`] with the specified parameters.
139    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    /// Creates a new [`SpendableTxIn`] from all fields.
163    #[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    /// Returns a reference to the scripts for this input.
179    pub fn get_scripts(&self) -> &Vec<Arc<dyn SpendableScript>> {
180        &self.scripts
181    }
182
183    /// Returns a reference to the Taproot spend info for this input, if any.
184    pub fn get_spend_info(&self) -> &Option<TaprootSpendInfo> {
185        &self.spendinfo
186    }
187
188    /// Sets the Taproot spend info for this input.
189    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    /// Checks the validity of the spendable input, ensuring script pubkey and merkle proof map are correct.
196    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    /// Creates a [`SpendableTxIn`] with validation if the given input is valid (used in debug mode for testing).
225    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    /// Creates a [`SpendableTxIn`] without validation (used in release mode).
237    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)]
254/// Represents a fully specified transaction input, including sequence, witness, spend path, and signature ID.
255pub struct SpentTxIn {
256    spendable: SpendableTxIn,
257    /// The sequence number, which suggests to miners which of two
258    /// conflicting transactions should be preferred, or 0xFFFFFFFF
259    /// to ignore this feature. This is generally never used since
260    /// the miner behavior cannot be enforced.
261    sequence: Sequence,
262    /// Witness data used to spend this TxIn. Can be None if the
263    /// transaction that this TxIn is in has not been signed yet.
264    ///
265    /// Has to be Some(_) when the transaction is signed.
266    witness: Option<Witness>,
267    /// Spend path for this input (key or script path).
268    spend_path: SpendPath,
269    /// Signature ID for this input, which signature in the protocol this input needs.
270    input_id: SignatureId,
271}
272
273impl SpentTxIn {
274    /// Constructs a [`SpentTxIn`] from a spendable input and associated metadata.
275    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    /// Returns a reference to the underlying [`SpendableTxIn`].
292    pub fn get_spendable(&self) -> &SpendableTxIn {
293        &self.spendable
294    }
295
296    /// Returns the spend path for this input.
297    pub fn get_spend_path(&self) -> SpendPath {
298        self.spend_path
299    }
300
301    /// Returns the Taproot tweak data for this input, based on the spend path and spend info.
302    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    /// Returns a reference to the witness data for this input, if any.
317    pub fn get_witness(&self) -> &Option<Witness> {
318        &self.witness
319    }
320
321    /// Returns the signature ID for this input.
322    pub fn get_signature_id(&self) -> SignatureId {
323        self.input_id
324    }
325
326    /// Sets the witness data for this input.
327    pub fn set_witness(&mut self, witness: Witness) {
328        self.witness = Some(witness);
329    }
330
331    // pub fn get_sequence(&self) -> Sequence {
332    //     self.sequence
333    // }
334
335    // pub fn set_sequence(&mut self, sequence: Sequence) {
336    //     self.sequence = sequence;
337    // }
338
339    /// Converts this [`SpentTxIn`] into a Bitcoin [`TxIn`] for inclusion in a Bitcoin transaction.
340    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}