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