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}