clementine_core/builder/transaction/
challenge.rs

1//! # Challenge Transaction Logic
2//!
3//! This module provides functions for constructing and challenge related transactions in the protocol.
4//! The transactions are: Challenge, ChallengeTimeout, OperatorChallengeNack, OperatorChallengeAck, Disprove.
5
6use crate::builder;
7use crate::builder::script::SpendPath;
8use crate::builder::transaction::output::UnspentTxOut;
9use crate::builder::transaction::txhandler::{TxHandler, DEFAULT_SEQUENCE};
10use crate::builder::transaction::*;
11use crate::config::protocol::ProtocolParamset;
12use crate::constants::{MIN_TAPROOT_AMOUNT, NON_STANDARD_V3};
13use crate::rpc::clementine::{NormalSignatureKind, NumberedSignatureKind};
14use bitcoin::script::PushBytesBuf;
15use bitcoin::{Sequence, TxOut, WitnessVersion};
16use clementine_errors::{BridgeError, TransactionType, TxError};
17use clementine_primitives::EVMAddress;
18use eyre::Context;
19
20use self::input::UtxoVout;
21
22/// Creates a [`TxHandler`] for the `watchtower_challenge_tx`.
23///
24/// This transaction is sent by a watchtower to submit a challenge proof (e.g., a Groth16 proof with public inputs).
25/// The proof data is encoded as a series of Taproot outputs and a final OP_RETURN output.
26/// Currently a watchtower challenge is in total 144 bytes, 32 + 32 + 80 bytes.
27///
28/// # Inputs
29/// 1. KickoffTx: WatchtowerChallenge utxo (for the given watchtower)
30///
31/// # Outputs
32/// 1. First output, first 32 bytes of challenge data encoded directly in scriptpubkey.
33/// 2. Second output, next 32 bytes of challenge data encoded directly in scriptpubkey.
34/// 3. OP_RETURN output, containing the last 80 bytes of challenge data.
35///
36/// # Arguments
37///
38/// * `kickoff_txhandler` - The kickoff transaction handler the watchtower challenge belongs to.
39/// * `watchtower_idx` - The index of the watchtower in the deposit submitting the challenge.
40/// * `commit_data` - The challenge proof data to be included in the transaction.
41/// * `paramset` - Protocol parameter set.
42///
43/// # Returns
44///
45/// A [`TxHandler`] for the watchtower challenge transaction, or a [`BridgeError`] if construction fails.
46pub fn create_watchtower_challenge_txhandler(
47    kickoff_txhandler: &TxHandler,
48    watchtower_idx: usize,
49    commit_data: &[u8],
50    paramset: &'static ProtocolParamset,
51    #[cfg(test)] test_params: &crate::config::TestParams,
52) -> Result<TxHandler, BridgeError> {
53    if commit_data.len() != paramset.watchtower_challenge_bytes {
54        return Err(TxError::IncorrectWatchtowerChallengeDataLength.into());
55    }
56    let mut builder = TxHandlerBuilder::new(TransactionType::WatchtowerChallenge(watchtower_idx))
57        .with_version(Version::TWO)
58        .add_input(
59            (
60                NumberedSignatureKind::WatchtowerChallenge,
61                watchtower_idx as i32,
62            ),
63            kickoff_txhandler
64                .get_spendable_output(UtxoVout::WatchtowerChallenge(watchtower_idx))?,
65            SpendPath::KeySpend,
66            DEFAULT_SEQUENCE,
67        );
68    let mut current_idx = 0;
69    while current_idx + 80 < paramset.watchtower_challenge_bytes {
70        // encode next 32 bytes of data as script pubkey of taproot utxo
71        let data = PushBytesBuf::try_from(commit_data[current_idx..current_idx + 32].to_vec())
72            .wrap_err("Failed to create pushbytesbuf for watchtower challenge op_return: {}")?;
73
74        let data_encoded_scriptbuf = Builder::new()
75            .push_opcode(WitnessVersion::V1.into())
76            .push_slice(data)
77            .into_script();
78
79        builder = builder.add_output(UnspentTxOut::from_partial(TxOut {
80            value: MIN_TAPROOT_AMOUNT,
81            script_pubkey: data_encoded_scriptbuf,
82        }));
83        current_idx += 32;
84    }
85
86    // add the remaining data as an op_return output
87    if current_idx < paramset.watchtower_challenge_bytes {
88        let remaining_data = PushBytesBuf::try_from(commit_data[current_idx..].to_vec())
89            .wrap_err("Failed to create pushbytesbuf for watchtower challenge op_return")?;
90        builder = builder.add_output(UnspentTxOut::from_partial(op_return_txout(remaining_data)));
91    }
92
93    #[cfg(test)]
94    {
95        builder = test_params.maybe_add_large_test_outputs(builder)?;
96    }
97
98    Ok(builder.finalize())
99}
100
101/// Creates a [`TxHandler`] for the `watchtower_challenge_timeout_tx`.
102///
103/// This transaction is sent by an operator if a watchtower does not submit a challenge in time, allowing the operator to claim a timeout.
104/// This way, operators do not need to reveal their preimage, and do not need to use the watchtowers longest chain proof in their
105/// bridge proof.
106///
107/// # Inputs
108/// 1. KickoffTx: WatchtowerChallenge utxo (for the given watchtower)
109/// 2. KickoffTx: WatchtowerChallengeAck utxo (for the given watchtower)
110///
111/// # Outputs
112/// 1. Anchor output for CPFP
113///
114/// # Arguments
115///
116/// * `kickoff_txhandler` - The kickoff transaction handler the watchtower challenge timeout belongs to.
117/// * `watchtower_idx` - The index of the watchtower in the deposit submitting the challenge.
118/// * `paramset` - Protocol parameter set.
119///
120/// # Returns
121///
122/// A [`TxHandler`] for the watchtower challenge timeout transaction, or a [`BridgeError`] if construction fails.
123pub fn create_watchtower_challenge_timeout_txhandler(
124    kickoff_txhandler: &TxHandler,
125    watchtower_idx: usize,
126    paramset: &'static ProtocolParamset,
127) -> Result<TxHandler, BridgeError> {
128    let watchtower_challenge_vout = UtxoVout::WatchtowerChallenge(watchtower_idx);
129    let challenge_ack_vout = UtxoVout::WatchtowerChallengeAck(watchtower_idx);
130    Ok(
131        TxHandlerBuilder::new(TransactionType::WatchtowerChallengeTimeout(watchtower_idx))
132            .with_version(NON_STANDARD_V3)
133            .add_input(
134                (
135                    NumberedSignatureKind::WatchtowerChallengeTimeout1,
136                    watchtower_idx as i32,
137                ),
138                kickoff_txhandler.get_spendable_output(watchtower_challenge_vout)?,
139                SpendPath::ScriptSpend(0),
140                Sequence::from_height(paramset.watchtower_challenge_timeout_timelock),
141            )
142            .add_input(
143                (
144                    NumberedSignatureKind::WatchtowerChallengeTimeout2,
145                    watchtower_idx as i32,
146                ),
147                kickoff_txhandler.get_spendable_output(challenge_ack_vout)?,
148                SpendPath::ScriptSpend(1),
149                Sequence::from_height(paramset.watchtower_challenge_timeout_timelock),
150            )
151            .add_output(UnspentTxOut::from_partial(
152                builder::transaction::anchor_output(paramset.anchor_amount()),
153            ))
154            .finalize(),
155    )
156}
157
158/// Creates a [`TxHandler`] for the `OperatorChallengeNack` transaction.
159///
160/// This transaction is used to force an operator to reveal a preimage for a watchtower challenge. If a watchtower sends a watchtower challenge,
161/// but the operator does not reveal the preimage by sending an OperatorChallengeAck, after a specified number of time (defined in paramset),
162/// the N-of-N can spend the output, burning the operator's collateral.
163///
164/// # Inputs
165/// 1. KickoffTx: WatchtowerChallengeAck utxo (for the given watchtower)
166/// 2. KickoffTx: KickoffFinalizer utxo
167/// 3. RoundTx: BurnConnector utxo
168///
169/// # Outputs
170/// 1. Anchor output for CPFP
171///
172/// # Arguments
173///
174/// * `kickoff_txhandler` - The kickoff transaction handler the operator challenge nack belongs to.
175/// * `watchtower_idx` - The index of the watchtower in the deposit corresponding to the watchtower challenge related to the operator challenge nack.
176/// * `round_txhandler` - The round transaction handler for the current round the kickoff belongs to.
177/// * `paramset` - Protocol parameter set.
178///
179/// # Returns
180///
181/// A [`TxHandler`] for the operator challenge NACK transaction, or a [`BridgeError`] if construction fails.
182pub fn create_operator_challenge_nack_txhandler(
183    kickoff_txhandler: &TxHandler,
184    watchtower_idx: usize,
185    round_txhandler: &TxHandler,
186    paramset: &'static ProtocolParamset,
187) -> Result<TxHandler, BridgeError> {
188    Ok(
189        TxHandlerBuilder::new(TransactionType::OperatorChallengeNack(watchtower_idx))
190            .with_version(NON_STANDARD_V3)
191            .add_input(
192                (
193                    NumberedSignatureKind::OperatorChallengeNack1,
194                    watchtower_idx as i32,
195                ),
196                kickoff_txhandler
197                    .get_spendable_output(UtxoVout::WatchtowerChallengeAck(watchtower_idx))?,
198                SpendPath::ScriptSpend(0),
199                Sequence::from_height(paramset.operator_challenge_nack_timelock),
200            )
201            .add_input(
202                (
203                    NumberedSignatureKind::OperatorChallengeNack2,
204                    watchtower_idx as i32,
205                ),
206                kickoff_txhandler.get_spendable_output(UtxoVout::KickoffFinalizer)?,
207                SpendPath::ScriptSpend(0),
208                DEFAULT_SEQUENCE,
209            )
210            .add_input(
211                (
212                    NumberedSignatureKind::OperatorChallengeNack3,
213                    watchtower_idx as i32,
214                ),
215                round_txhandler.get_spendable_output(UtxoVout::CollateralInRound)?,
216                SpendPath::KeySpend,
217                DEFAULT_SEQUENCE,
218            )
219            .add_output(UnspentTxOut::from_partial(
220                builder::transaction::anchor_output(paramset.anchor_amount()),
221            ))
222            .finalize(),
223    )
224}
225
226/// Creates a [`TxHandler`] for the OperatorChallengeAck transaction.
227///
228/// This transaction is used by an operator to acknowledge a watchtower challenge and reveal the required preimage, if a watchtower challenge is sent.
229///
230/// # Inputs
231/// 1. KickoffTx: WatchtowerChallengeAck utxo (for the given watchtower)
232///
233/// # Outputs
234/// 1. Anchor output for CPFP
235/// 2. Dummy OP_RETURN output (to pad the size of the transaction, as it is too small otherwise)
236///
237/// # Arguments
238///
239/// * `kickoff_txhandler` - The kickoff transaction handler the operator challenge ack belongs to.
240/// * `watchtower_idx` - The index of the watchtower that sent the challenge.
241/// * `paramset` - Protocol parameter set.
242///
243/// # Returns
244///
245/// A [`TxHandler`] for the operator challenge ACK transaction, or a [`BridgeError`] if construction fails.
246pub fn create_operator_challenge_ack_txhandler(
247    kickoff_txhandler: &TxHandler,
248    watchtower_idx: usize,
249    paramset: &'static ProtocolParamset,
250) -> Result<TxHandler, BridgeError> {
251    Ok(
252        TxHandlerBuilder::new(TransactionType::OperatorChallengeAck(watchtower_idx))
253            .with_version(NON_STANDARD_V3)
254            .add_input(
255                NormalSignatureKind::OperatorChallengeAck1,
256                kickoff_txhandler
257                    .get_spendable_output(UtxoVout::WatchtowerChallengeAck(watchtower_idx))?,
258                SpendPath::ScriptSpend(2),
259                DEFAULT_SEQUENCE,
260            )
261            .add_output(UnspentTxOut::from_partial(
262                builder::transaction::anchor_output(paramset.anchor_amount()),
263            ))
264            .add_output(UnspentTxOut::from_partial(op_return_txout(b"PADDING")))
265            .finalize(),
266    )
267}
268
269/// Creates a [`TxHandler`] for the `disprove_tx`.
270///
271/// This transaction is sent by N-of-N to penalize a malicious operator by burning their collateral (burn connector).
272/// This is done either with the additional disprove script created by BitVM, in case the public inputs of the bridge proof the operator
273/// sent are not correct/do not match previous data, or if the Groth16 verification of the proof is incorrect using BitVM disprove scripts.
274///
275/// # Inputs
276/// 1. KickoffTx: Disprove utxo
277/// 2. RoundTx: BurnConnector utxo
278///
279/// # Outputs
280/// 1. Anchor output for CPFP
281///
282/// # Arguments
283///
284/// * `kickoff_txhandler` - The kickoff transaction handler the disprove belongs to.
285/// * `round_txhandler` - The round transaction handler to the current round the kickoff belongs to.
286///
287/// # Returns
288///
289/// A [`TxHandler`] for the disprove transaction, or a [`BridgeError`] if construction fails.
290pub fn create_disprove_txhandler(
291    kickoff_txhandler: &TxHandler,
292    round_txhandler: &TxHandler,
293) -> Result<TxHandler, BridgeError> {
294    Ok(TxHandlerBuilder::new(TransactionType::Disprove)
295        .with_version(Version::TWO)
296        .add_input(
297            NormalSignatureKind::NoSignature,
298            kickoff_txhandler.get_spendable_output(UtxoVout::Disprove)?,
299            SpendPath::Unknown,
300            DEFAULT_SEQUENCE,
301        )
302        .add_input(
303            NormalSignatureKind::Disprove2,
304            round_txhandler.get_spendable_output(UtxoVout::CollateralInRound)?,
305            SpendPath::KeySpend,
306            DEFAULT_SEQUENCE,
307        )
308        .add_output(UnspentTxOut::from_partial(
309            builder::transaction::non_ephemeral_anchor_output(), // must be non-ephemeral, because tx is v2
310        ))
311        .finalize())
312}
313
314/// Creates a [`TxHandler`] for the `challenge` transaction.
315///
316/// This transaction is used to reimburse an operator for a valid challenge, intended to cover their costs for sending asserts transactions,
317/// and potentially cover their opportunity cost as their reimbursements are delayed due to the challenge. This cost of a challenge is also
318/// used to disincentivize sending challenges for kickoffs that are correct. In case the challenge is correct and operator is proved to be
319/// malicious, the challenge cost will be reimbursed using the operator's collateral that's locked in Citrea.
320///
321/// # Inputs
322/// 1. KickoffTx: Challenge utxo
323///
324/// # Outputs
325/// 1. Operator reimbursement output
326/// 2. OP_RETURN output (containing EVM address of the challenger, for reimbursement if the challenge is correct)
327///
328/// # Arguments
329///
330/// * `kickoff_txhandler` - The kickoff transaction handler that the challenge belongs to.
331/// * `operator_reimbursement_address` - The address to reimburse the operator to cover their costs.
332/// * `challenger_evm_address` - The EVM address of the challenger, for reimbursement if the challenge is correct.
333/// * `paramset` - Protocol parameter set.
334///
335/// # Returns
336///
337/// A [`TxHandler`] for the challenge transaction, or a [`BridgeError`] if construction fails.
338pub fn create_challenge_txhandler(
339    kickoff_txhandler: &TxHandler,
340    operator_reimbursement_address: &bitcoin::Address,
341    challenger_evm_address: Option<EVMAddress>,
342    paramset: &'static ProtocolParamset,
343) -> Result<TxHandler, BridgeError> {
344    let mut builder = TxHandlerBuilder::new(TransactionType::Challenge)
345        .with_version(NON_STANDARD_V3)
346        .add_input(
347            NormalSignatureKind::Challenge,
348            kickoff_txhandler.get_spendable_output(UtxoVout::Challenge)?,
349            SpendPath::ScriptSpend(0),
350            DEFAULT_SEQUENCE,
351        )
352        .add_output(UnspentTxOut::from_partial(TxOut {
353            value: paramset.operator_challenge_amount,
354            script_pubkey: operator_reimbursement_address.script_pubkey(),
355        }));
356
357    if let Some(challenger_evm_address) = challenger_evm_address {
358        builder = builder.add_output(UnspentTxOut::from_partial(op_return_txout(
359            challenger_evm_address.0,
360        )));
361    }
362
363    Ok(builder.finalize())
364}
365
366/// Creates a [`TxHandler`] for the `challenge_timeout` transaction.
367///
368/// This transaction is used to finalize a kickoff if no challenge is submitted in time, allowing the operator to proceed faster to the next round, thus getting their reimbursement, as the next round will generate the reimbursement connectors of the current round.
369///
370/// # Inputs
371/// 1. KickoffTx: Challenge utxo
372/// 2. KickoffTx: KickoffFinalizer utxo
373///
374/// # Outputs
375/// 1. Anchor output for CPFP
376///
377/// # Arguments
378///
379/// * `kickoff_txhandler` - The kickoff transaction handler the challenge timeout belongs to.
380/// * `paramset` - Protocol parameter set.
381///
382/// # Returns
383///
384/// A [`TxHandler`] for the challenge timeout transaction, or a [`BridgeError`] if construction fails.
385pub fn create_challenge_timeout_txhandler(
386    kickoff_txhandler: &TxHandler,
387    paramset: &'static ProtocolParamset,
388) -> Result<TxHandler, BridgeError> {
389    Ok(TxHandlerBuilder::new(TransactionType::ChallengeTimeout)
390        .with_version(NON_STANDARD_V3)
391        .add_input(
392            NormalSignatureKind::OperatorSighashDefault,
393            kickoff_txhandler.get_spendable_output(UtxoVout::Challenge)?,
394            SpendPath::ScriptSpend(1),
395            Sequence::from_height(paramset.operator_challenge_timeout_timelock),
396        )
397        .add_input(
398            NormalSignatureKind::ChallengeTimeout2,
399            kickoff_txhandler.get_spendable_output(UtxoVout::KickoffFinalizer)?,
400            SpendPath::ScriptSpend(0),
401            DEFAULT_SEQUENCE,
402        )
403        .add_output(UnspentTxOut::from_partial(
404            builder::transaction::anchor_output(paramset.anchor_amount()),
405        ))
406        .finalize())
407}