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