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}