1use super::input::UtxoVout;
14use super::txhandler::DEFAULT_SEQUENCE;
15use crate::builder;
16use crate::builder::address::create_taproot_address;
17use crate::builder::script::{TimelockScript, WinternitzCommit};
18use crate::builder::transaction::creator::KickoffWinternitzKeys;
19use crate::builder::transaction::input::SpendableTxIn;
20use crate::builder::transaction::output::UnspentTxOut;
21use crate::builder::transaction::txhandler::TxHandler;
22use crate::builder::transaction::*;
23use crate::config::protocol::ProtocolParamset;
24use crate::constants::MIN_TAPROOT_AMOUNT;
25use crate::errors::BridgeError;
26use crate::rpc::clementine::NumberedSignatureKind;
27use bitcoin::Sequence;
28use bitcoin::{Amount, OutPoint, TxOut, XOnlyPublicKey};
29use std::sync::Arc;
30
31pub enum RoundTxInput {
32 Prevout(Box<SpendableTxIn>),
33 Collateral(OutPoint, Amount),
34}
35
36pub fn create_round_txhandler(
59 operator_xonly_pk: XOnlyPublicKey,
60 txin: RoundTxInput,
61 pubkeys: &[bitvm::signatures::winternitz::PublicKey],
62 paramset: &'static ProtocolParamset,
63) -> Result<TxHandler, BridgeError> {
64 let mut builder = TxHandlerBuilder::new(TransactionType::Round).with_version(NON_STANDARD_V3);
65 let input_amount;
66 match txin {
67 RoundTxInput::Prevout(prevout) => {
68 input_amount = prevout.get_prevout().value;
69 builder = builder.add_input(
70 NormalSignatureKind::OperatorSighashDefault,
71 *prevout,
72 SpendPath::KeySpend,
73 Sequence::from_height(paramset.operator_reimburse_timelock),
74 );
75 }
76 RoundTxInput::Collateral(outpoint, amount) => {
77 let (op_address, op_spend) =
78 create_taproot_address(&[], Some(operator_xonly_pk), paramset.network);
79 input_amount = amount;
80 builder = builder.add_input(
81 NormalSignatureKind::OperatorSighashDefault,
82 SpendableTxIn::new(
83 outpoint,
84 TxOut {
85 value: input_amount,
86 script_pubkey: op_address.script_pubkey(),
87 },
88 vec![],
89 Some(op_spend.clone()),
90 ),
91 SpendPath::KeySpend,
92 DEFAULT_SEQUENCE,
93 );
94 }
95 }
96
97 let timeout_block_count_locked_script =
100 Arc::new(TimelockScript::new(Some(operator_xonly_pk), 1));
101
102 let total_required = (paramset.kickoff_amount + paramset.default_utxo_amount())
103 .checked_mul(paramset.num_kickoffs_per_round as u64)
104 .and_then(|kickoff_total| kickoff_total.checked_add(paramset.anchor_amount()))
105 .ok_or_else(|| {
106 BridgeError::ArithmeticOverflow("Total required amount calculation overflow")
107 })?;
108
109 let remaining_amount = input_amount.checked_sub(total_required).ok_or_else(|| {
110 BridgeError::InsufficientFunds("Input amount insufficient for required outputs")
111 })?;
112
113 builder = builder.add_output(UnspentTxOut::from_scripts(
114 remaining_amount,
115 vec![],
116 Some(operator_xonly_pk),
117 paramset.network,
118 ));
119
120 for pubkey in pubkeys.iter().take(paramset.num_kickoffs_per_round) {
122 let blockhash_commit = Arc::new(WinternitzCommit::new(
123 vec![(pubkey.clone(), paramset.kickoff_blockhash_commit_length)],
124 operator_xonly_pk,
125 paramset.winternitz_log_d,
126 ));
127 builder = builder.add_output(UnspentTxOut::from_scripts(
128 paramset.kickoff_amount,
129 vec![blockhash_commit, timeout_block_count_locked_script.clone()],
130 None,
131 paramset.network,
132 ));
133 }
134 for _ in 0..paramset.num_kickoffs_per_round {
136 builder = builder.add_output(UnspentTxOut::from_scripts(
137 paramset.default_utxo_amount(),
138 vec![],
139 Some(operator_xonly_pk),
140 paramset.network,
141 ));
142 }
143 Ok(builder
144 .add_output(UnspentTxOut::from_partial(
145 builder::transaction::anchor_output(paramset.anchor_amount()),
146 ))
147 .finalize())
148}
149
150pub fn create_assert_timeout_txhandlers(
171 kickoff_txhandler: &TxHandler,
172 round_txhandler: &TxHandler,
173 num_asserts: usize,
174 paramset: &'static ProtocolParamset,
175) -> Result<Vec<TxHandler>, BridgeError> {
176 let mut txhandlers = Vec::new();
177 for idx in 0..num_asserts {
178 txhandlers.push(
179 TxHandlerBuilder::new(TransactionType::AssertTimeout(idx))
180 .with_version(NON_STANDARD_V3)
181 .add_input(
182 (NumberedSignatureKind::AssertTimeout1, idx as i32),
183 kickoff_txhandler.get_spendable_output(UtxoVout::Assert(idx))?,
184 SpendPath::ScriptSpend(0),
185 Sequence::from_height(paramset.assert_timeout_timelock),
186 )
187 .add_input(
188 (NumberedSignatureKind::AssertTimeout2, idx as i32),
189 kickoff_txhandler.get_spendable_output(UtxoVout::KickoffFinalizer)?,
190 SpendPath::ScriptSpend(0),
191 DEFAULT_SEQUENCE,
192 )
193 .add_input(
194 (NumberedSignatureKind::AssertTimeout3, idx as i32),
195 round_txhandler.get_spendable_output(UtxoVout::CollateralInRound)?,
196 SpendPath::KeySpend,
197 DEFAULT_SEQUENCE,
198 )
199 .add_output(UnspentTxOut::from_partial(
200 builder::transaction::anchor_output(paramset.anchor_amount()),
201 ))
202 .finalize(),
203 );
204 }
205 Ok(txhandlers)
206}
207
208pub fn create_round_nth_txhandler(
221 operator_xonly_pk: XOnlyPublicKey,
222 input_outpoint: OutPoint,
223 input_amount: Amount,
224 index: RoundIndex,
225 pubkeys: &KickoffWinternitzKeys,
226 paramset: &'static ProtocolParamset,
227) -> Result<(TxHandler, TxHandler), BridgeError> {
228 if index == RoundIndex::Collateral
233 || index.to_index() > RoundIndex::Round(paramset.num_round_txs).to_index()
234 {
235 return Err(TxError::InvalidRoundIndex(index).into());
236 }
237
238 let mut round_txhandler = create_round_txhandler(
240 operator_xonly_pk,
241 RoundTxInput::Collateral(input_outpoint, input_amount),
242 pubkeys.get_keys_for_round(RoundIndex::Round(0))?,
243 paramset,
244 )?;
245 let mut ready_to_reimburse_txhandler =
246 create_ready_to_reimburse_txhandler(&round_txhandler, operator_xonly_pk, paramset)?;
247
248 let round_idx = match index {
250 RoundIndex::Collateral => 0, RoundIndex::Round(idx) => idx,
252 };
253 for round_idx in RoundIndex::iter_rounds_range(1, round_idx + 1) {
255 round_txhandler = create_round_txhandler(
256 operator_xonly_pk,
257 RoundTxInput::Prevout(Box::new(
258 ready_to_reimburse_txhandler
259 .get_spendable_output(UtxoVout::CollateralInReadyToReimburse)?,
260 )),
261 pubkeys.get_keys_for_round(round_idx)?,
262 paramset,
263 )?;
264 ready_to_reimburse_txhandler =
265 create_ready_to_reimburse_txhandler(&round_txhandler, operator_xonly_pk, paramset)?;
266 }
267 Ok((round_txhandler, ready_to_reimburse_txhandler))
268}
269
270pub fn create_ready_to_reimburse_txhandler(
287 round_txhandler: &TxHandler,
288 operator_xonly_pk: XOnlyPublicKey,
289 paramset: &'static ProtocolParamset,
290) -> Result<TxHandler, BridgeError> {
291 let prevout = round_txhandler.get_spendable_output(UtxoVout::CollateralInRound)?;
292 let prev_value = prevout.get_prevout().value;
293
294 Ok(TxHandlerBuilder::new(TransactionType::ReadyToReimburse)
295 .with_version(NON_STANDARD_V3)
296 .add_input(
297 NormalSignatureKind::OperatorSighashDefault,
298 prevout,
299 SpendPath::KeySpend,
300 DEFAULT_SEQUENCE,
301 )
302 .add_output(UnspentTxOut::from_scripts(
303 prev_value.checked_sub(paramset.anchor_amount()).ok_or(
304 BridgeError::ArithmeticOverflow(
305 "Insufficient funds while creating ready to reimburse tx",
306 ),
307 )?,
308 vec![],
309 Some(operator_xonly_pk),
310 paramset.network,
311 ))
312 .add_output(UnspentTxOut::from_partial(
313 builder::transaction::anchor_output(paramset.anchor_amount()),
314 ))
315 .finalize())
316}
317
318pub fn create_unspent_kickoff_txhandlers(
336 round_txhandler: &TxHandler,
337 ready_to_reimburse_txhandler: &TxHandler,
338 paramset: &'static ProtocolParamset,
339) -> Result<Vec<TxHandler>, BridgeError> {
340 let mut txhandlers = Vec::new();
341 for idx in 0..paramset.num_kickoffs_per_round {
342 txhandlers.push(
343 TxHandlerBuilder::new(TransactionType::UnspentKickoff(idx))
344 .with_version(NON_STANDARD_V3)
345 .add_input(
346 (NumberedSignatureKind::UnspentKickoff1, idx as i32),
347 ready_to_reimburse_txhandler
348 .get_spendable_output(UtxoVout::CollateralInReadyToReimburse)?,
349 SpendPath::KeySpend,
350 DEFAULT_SEQUENCE,
351 )
352 .add_input(
353 (NumberedSignatureKind::UnspentKickoff2, idx as i32),
354 round_txhandler.get_spendable_output(UtxoVout::Kickoff(idx))?,
355 SpendPath::ScriptSpend(1),
356 Sequence::from_height(1),
357 )
358 .add_output(UnspentTxOut::from_partial(
359 builder::transaction::anchor_output(paramset.anchor_amount()),
360 ))
361 .finalize(),
362 );
363 }
364 Ok(txhandlers)
365}
366
367pub fn create_burn_unused_kickoff_connectors_txhandler(
384 round_txhandler: &TxHandler,
385 unused_kickoff_connectors_indices: &[usize],
386 change_address: &Address,
387 paramset: &'static ProtocolParamset,
388) -> Result<TxHandler, BridgeError> {
389 if unused_kickoff_connectors_indices.is_empty() {
390 return Err(eyre::eyre!(
391 "create_burn_unused_kickoff_connectors_txhandler called with no unused kickoff connectors"
392 )
393 .into());
394 }
395 let mut tx_handler_builder =
396 TxHandlerBuilder::new(TransactionType::BurnUnusedKickoffConnectors)
397 .with_version(NON_STANDARD_V3);
398 let mut input_amount = Amount::ZERO;
399 for &idx in unused_kickoff_connectors_indices {
400 let txin = round_txhandler.get_spendable_output(UtxoVout::Kickoff(idx))?;
401 input_amount = input_amount.checked_add(txin.get_prevout().value).ok_or(
402 BridgeError::ArithmeticOverflow("Amount overflow in burn unused kickoff connectors tx"),
403 )?;
404 tx_handler_builder = tx_handler_builder.add_input(
405 NormalSignatureKind::OperatorSighashDefault,
406 txin,
407 SpendPath::ScriptSpend(1),
408 Sequence::from_height(1),
409 );
410 }
411 if !paramset.bridge_nonstandard && input_amount >= paramset.anchor_amount() + MIN_TAPROOT_AMOUNT
412 {
413 tx_handler_builder = tx_handler_builder.add_output(UnspentTxOut::from_partial(TxOut {
416 value: input_amount - paramset.anchor_amount(),
418 script_pubkey: change_address.script_pubkey(),
419 }));
420 }
421 tx_handler_builder = tx_handler_builder.add_output(UnspentTxOut::from_partial(
422 builder::transaction::anchor_output(paramset.anchor_amount()),
423 ));
424 Ok(tx_handler_builder.finalize())
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use crate::config::protocol::REGTEST_PARAMSET;
431 use std::str::FromStr;
432
433 #[tokio::test]
434 async fn test_create_round_nth_txhandler_and_round_txhandlers() {
435 let op_xonly_pk = XOnlyPublicKey::from_str(
437 "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
438 )
439 .expect("this key is valid");
440 let paramset = ®TEST_PARAMSET;
441 let input_outpoint = OutPoint::new(bitcoin::Txid::all_zeros(), 0);
442 let input_amount = Amount::from_sat(10000000000);
443 let pubkeys = KickoffWinternitzKeys::new(
444 vec![
445 vec![[0u8; 20]; 43];
446 (paramset.num_round_txs + 1) * paramset.num_kickoffs_per_round
447 ],
448 paramset.num_kickoffs_per_round,
449 paramset.num_round_txs,
450 )
451 .unwrap();
452
453 let mut round_tx_input = RoundTxInput::Collateral(input_outpoint, input_amount);
454
455 for i in 0..paramset.num_round_txs {
456 let (round_nth_txhandler, ready_to_reimburse_nth_txhandler) =
457 create_round_nth_txhandler(
458 op_xonly_pk,
459 input_outpoint,
460 input_amount,
461 RoundIndex::Round(i),
462 &pubkeys,
463 paramset,
464 )
465 .unwrap();
466
467 let round_txhandler = create_round_txhandler(
468 op_xonly_pk,
469 round_tx_input,
470 pubkeys.get_keys_for_round(RoundIndex::Round(i)).unwrap(),
471 paramset,
472 )
473 .unwrap();
474
475 let ready_to_reimburse_txhandler =
476 create_ready_to_reimburse_txhandler(&round_txhandler, op_xonly_pk, paramset)
477 .unwrap();
478
479 assert_eq!(round_nth_txhandler.get_txid(), round_txhandler.get_txid());
480 assert_eq!(
481 ready_to_reimburse_nth_txhandler.get_txid(),
482 ready_to_reimburse_txhandler.get_txid()
483 );
484
485 let prev_ready_to_reimburse_txhandler = ready_to_reimburse_txhandler;
486 round_tx_input = RoundTxInput::Prevout(Box::new(
487 prev_ready_to_reimburse_txhandler
488 .get_spendable_output(UtxoVout::CollateralInReadyToReimburse)
489 .unwrap(),
490 ));
491 }
492 }
493}