1use super::challenge::create_watchtower_challenge_txhandler;
6use super::{ContractContext, TxHandlerCache};
7use crate::actor::{Actor, TweakCache, WinternitzDerivationPath};
8use crate::bitvm_client::ClementineBitVMPublicKeys;
9use crate::builder;
10use crate::builder::transaction::creator::ReimburseDbCache;
11use crate::citrea::CitreaClientT;
12use crate::config::protocol::ProtocolParamset;
13use crate::config::BridgeConfig;
14use crate::database::{Database, DatabaseTransaction};
15use crate::deposit::KickoffData;
16use crate::operator::Operator;
17use crate::utils::{Last20Bytes, RbfSigningInfo};
18use crate::verifier::Verifier;
19use bitcoin::hashes::Hash;
20use bitcoin::{BlockHash, OutPoint, Transaction, XOnlyPublicKey};
21use clementine_errors::BridgeError;
22use clementine_errors::{TransactionType, TxError};
23use clementine_primitives::RoundIndex;
24use eyre::Context;
25use rand_chacha::rand_core::SeedableRng;
26use rand_chacha::ChaCha12Rng;
27use secp256k1::rand::seq::SliceRandom;
28
29#[derive(Debug, Clone)]
31pub struct TransactionRequestData {
32 pub deposit_outpoint: OutPoint,
33 pub kickoff_data: KickoffData,
34}
35
36pub fn get_kickoff_utxos_to_sign(
53 paramset: &'static ProtocolParamset,
54 op_xonly_pk: XOnlyPublicKey,
55 deposit_blockhash: BlockHash,
56 deposit_outpoint: bitcoin::OutPoint,
57) -> Vec<usize> {
58 let deposit_data = [
59 op_xonly_pk.serialize().to_vec(),
60 deposit_blockhash.to_byte_array().to_vec(),
61 deposit_outpoint.txid.to_byte_array().to_vec(),
62 deposit_outpoint.vout.to_le_bytes().to_vec(),
63 ]
64 .concat();
65
66 let seed = bitcoin::hashes::sha256d::Hash::hash(&deposit_data).to_byte_array();
67 let mut rng = ChaCha12Rng::from_seed(seed);
68
69 let mut numbers: Vec<usize> = (0..paramset.num_kickoffs_per_round).collect();
70 numbers.shuffle(&mut rng);
71
72 numbers
73 .into_iter()
74 .take(paramset.num_signed_kickoffs)
75 .collect()
76}
77
78pub async fn create_and_sign_txs(
92 db: Database,
93 signer: &Actor,
94 config: BridgeConfig,
95 context: ContractContext,
96 block_hash: Option<[u8; 20]>, dbtx: Option<DatabaseTransaction<'_>>,
98) -> Result<Vec<(TransactionType, Transaction)>, BridgeError> {
99 let txhandlers = builder::transaction::create_txhandlers(
100 match context.is_context_for_kickoff() {
101 true => TransactionType::AllNeededForDeposit,
102 false => TransactionType::Round,
104 },
105 context.clone(),
106 &mut TxHandlerCache::new(),
107 &mut match context.is_context_for_kickoff() {
108 true => ReimburseDbCache::new_for_deposit(
109 db.clone(),
110 context.operator_xonly_pk,
111 context
112 .deposit_data
113 .as_ref()
114 .expect("Already checked existence of deposit data")
115 .get_deposit_outpoint(),
116 config.protocol_paramset(),
117 dbtx,
118 ),
119 false => ReimburseDbCache::new_for_rounds(
120 db.clone(),
121 context.operator_xonly_pk,
122 config.protocol_paramset(),
123 dbtx,
124 ),
125 },
126 )
127 .await?;
128
129 let mut signatures = Vec::new();
130
131 if context.is_context_for_kickoff() {
132 let deposit_sigs_query = db
134 .get_deposit_signatures(
135 None,
136 context
137 .deposit_data
138 .as_ref()
139 .expect("Should have deposit data at this point")
140 .get_deposit_outpoint(),
141 context.operator_xonly_pk,
142 context.round_idx,
143 context
144 .kickoff_idx
145 .expect("Already checked existence of kickoff idx") as usize,
146 )
147 .await?;
148 signatures.extend(deposit_sigs_query.unwrap_or_default());
149 }
150
151 let setup_sigs_query = db
153 .get_unspent_kickoff_sigs(None, context.operator_xonly_pk, context.round_idx)
154 .await?;
155
156 signatures.extend(setup_sigs_query.unwrap_or_default());
157
158 let mut signed_txs = Vec::with_capacity(txhandlers.len());
159 let mut tweak_cache = TweakCache::default();
160
161 for (tx_type, mut txhandler) in txhandlers.into_iter() {
162 let _ = signer
163 .tx_sign_and_fill_sigs(&mut txhandler, &signatures, Some(&mut tweak_cache))
164 .wrap_err(format!(
165 "Couldn't sign transaction {tx_type:?} in create_and_sign_txs for context {context:?}"
166 ));
167
168 if let TransactionType::OperatorChallengeAck(watchtower_idx) = tx_type {
169 let path = WinternitzDerivationPath::ChallengeAckHash(
170 watchtower_idx as u32,
171 context
172 .deposit_data
173 .as_ref()
174 .expect("Should have deposit data at this point")
175 .get_deposit_outpoint(),
176 config.protocol_paramset(),
177 );
178 let preimage = signer.generate_preimage_from_path(path)?;
179 let _ = signer.tx_sign_preimage(&mut txhandler, preimage);
180 }
181
182 if let TransactionType::Kickoff = tx_type {
183 if let Some(block_hash) = block_hash {
184 let path = WinternitzDerivationPath::Kickoff(
186 context.round_idx,
187 context
188 .kickoff_idx
189 .expect("Should have kickoff idx at this point"),
190 config.protocol_paramset(),
191 );
192 signer.tx_sign_winternitz(&mut txhandler, &[(block_hash.to_vec(), path)])?;
193 }
194 }
196
197 let checked_txhandler = txhandler.promote();
198
199 match checked_txhandler {
200 Ok(checked_txhandler) => {
201 signed_txs.push((tx_type, checked_txhandler.get_cached_tx().clone()));
202 }
203 Err(e) => {
204 tracing::debug!(
205 "Couldn't sign transaction {:?} in create_and_sign_all_txs: {:?}.
206 This might be normal if the transaction is not needed to be/cannot be signed.",
207 tx_type,
208 e
209 );
210 }
211 }
212 }
213
214 Ok(signed_txs)
215}
216
217impl<C> Verifier<C>
218where
219 C: CitreaClientT,
220{
221 pub async fn create_watchtower_challenge(
233 &self,
234 transaction_data: TransactionRequestData,
235 commit_data: &[u8],
236 dbtx: Option<DatabaseTransaction<'_>>,
237 ) -> Result<(TransactionType, Transaction, RbfSigningInfo), BridgeError> {
238 if commit_data.len() != self.config.protocol_paramset().watchtower_challenge_bytes {
239 return Err(TxError::IncorrectWatchtowerChallengeDataLength.into());
240 }
241
242 let deposit_data = self
243 .db
244 .get_deposit_data(None, transaction_data.deposit_outpoint)
245 .await?
246 .ok_or(BridgeError::DepositNotFound(
247 transaction_data.deposit_outpoint,
248 ))?
249 .1;
250
251 let context = ContractContext::new_context_with_signer(
252 transaction_data.kickoff_data,
253 deposit_data.clone(),
254 self.config.protocol_paramset(),
255 self.signer.clone(),
256 );
257
258 let mut txhandlers = builder::transaction::create_txhandlers(
259 TransactionType::AllNeededForDeposit,
260 context,
261 &mut TxHandlerCache::new(),
262 &mut ReimburseDbCache::new_for_deposit(
263 self.db.clone(),
264 transaction_data.kickoff_data.operator_xonly_pk,
265 transaction_data.deposit_outpoint,
266 self.config.protocol_paramset(),
267 dbtx,
268 ),
269 )
270 .await?;
271
272 let kickoff_txhandler = txhandlers
273 .remove(&TransactionType::Kickoff)
274 .ok_or(TxError::TxHandlerNotFound(TransactionType::Kickoff))?;
275
276 let watchtower_index = deposit_data.get_watchtower_index(&self.signer.xonly_public_key)?;
277
278 let watchtower_challenge_txhandler = create_watchtower_challenge_txhandler(
279 &kickoff_txhandler,
280 watchtower_index,
281 commit_data,
282 self.config.protocol_paramset(),
283 #[cfg(test)]
284 &self.config.test_params,
285 )?;
286
287 let merkle_root = watchtower_challenge_txhandler.get_merkle_root_of_txin(0)?;
288
289 #[cfg(test)]
292 let (annex, additional_taproot_output_count) = {
293 let mut annex: Option<Vec<u8>> = None;
294 let mut additional_taproot_output_count = None;
295
296 if self.config.test_params.use_small_annex {
297 annex = Some(vec![80u8; 10000]);
298 } else if self.config.test_params.use_large_annex {
299 annex = Some(vec![80u8; 3990000]);
300 } else if self.config.test_params.use_large_annex_and_output {
301 annex = Some(vec![80u8; 3000000]);
302 additional_taproot_output_count = Some(2300);
303 } else if self.config.test_params.use_large_output {
304 additional_taproot_output_count = Some(2300);
305 }
306 (annex, additional_taproot_output_count)
307 };
308
309 #[cfg(not(test))]
310 let (annex, additional_taproot_output_count) = (None, None);
311
312 Ok((
313 TransactionType::WatchtowerChallenge(watchtower_index),
314 watchtower_challenge_txhandler.get_cached_tx().clone(),
315 RbfSigningInfo {
316 vout: 0,
317 tweak_merkle_root: merkle_root,
318 annex,
319 additional_taproot_output_count,
320 },
321 ))
322 }
323
324 pub async fn create_and_sign_unspent_kickoff_connector_txs(
336 &self,
337 round_idx: RoundIndex,
338 operator_xonly_pk: XOnlyPublicKey,
339 mut dbtx: Option<DatabaseTransaction<'_>>,
340 ) -> Result<Vec<(TransactionType, Transaction)>, BridgeError> {
341 let context = ContractContext::new_context_for_round(
342 operator_xonly_pk,
343 round_idx,
344 self.config.protocol_paramset(),
345 );
346
347 let txhandlers = builder::transaction::create_txhandlers(
348 TransactionType::UnspentKickoff(0),
349 context,
350 &mut TxHandlerCache::new(),
351 &mut ReimburseDbCache::new_for_rounds(
352 self.db.clone(),
353 operator_xonly_pk,
354 self.config.protocol_paramset(),
355 dbtx.as_deref_mut(),
356 ),
357 )
358 .await?;
359
360 let unspent_kickoff_sigs = self
362 .db
363 .get_unspent_kickoff_sigs(dbtx, operator_xonly_pk, round_idx)
364 .await?
365 .ok_or(eyre::eyre!(
366 "No unspent kickoff signatures found for operator {:?} and round {:?}",
367 operator_xonly_pk,
368 round_idx
369 ))?;
370
371 let mut signed_txs = Vec::with_capacity(txhandlers.len());
372 let mut tweak_cache = TweakCache::default();
373
374 for (tx_type, mut txhandler) in txhandlers.into_iter() {
375 if !matches!(tx_type, TransactionType::UnspentKickoff(_)) {
376 continue;
378 }
379 let res = self.signer
380 .tx_sign_and_fill_sigs(
381 &mut txhandler,
382 &unspent_kickoff_sigs,
383 Some(&mut tweak_cache),
384 )
385 .wrap_err(format!(
386 "Couldn't sign transaction {tx_type:?} in create_and_sign_unspent_kickoff_connector_txs for round {round_idx:?} and operator {operator_xonly_pk:?}",
387 ));
388
389 let checked_txhandler = txhandler.promote();
390
391 match checked_txhandler {
392 Ok(checked_txhandler) => {
393 signed_txs.push((tx_type, checked_txhandler.get_cached_tx().clone()));
394 }
395 Err(e) => {
396 tracing::trace!(
397 "Couldn't sign transaction {:?} in create_and_sign_unspent_kickoff_connector_txs: {:?}: {:?}",
398 tx_type,
399 e,
400 res.err()
401 );
402 }
403 }
404 }
405
406 Ok(signed_txs)
407 }
408}
409
410impl<C> Operator<C>
411where
412 C: CitreaClientT,
413{
414 pub async fn create_assert_commitment_txs(
425 &self,
426 assert_data: TransactionRequestData,
427 commit_data: Vec<Vec<Vec<u8>>>,
428 dbtx: Option<DatabaseTransaction<'_>>,
429 ) -> Result<Vec<(TransactionType, Transaction)>, BridgeError> {
430 let deposit_data = self
431 .db
432 .get_deposit_data(None, assert_data.deposit_outpoint)
433 .await?
434 .ok_or(BridgeError::DepositNotFound(assert_data.deposit_outpoint))?
435 .1;
436
437 let context = ContractContext::new_context_with_signer(
438 assert_data.kickoff_data,
439 deposit_data.clone(),
440 self.config.protocol_paramset(),
441 self.signer.clone(),
442 );
443
444 let mut txhandlers = builder::transaction::create_txhandlers(
445 TransactionType::MiniAssert(0),
446 context,
447 &mut TxHandlerCache::new(),
448 &mut ReimburseDbCache::new_for_deposit(
449 self.db.clone(),
450 self.signer.xonly_public_key,
451 assert_data.deposit_outpoint,
452 self.config.protocol_paramset(),
453 dbtx,
454 ),
455 )
456 .await?;
457
458 let mut signed_txhandlers = Vec::new();
459
460 for idx in 0..ClementineBitVMPublicKeys::number_of_assert_txs() {
461 let mut mini_assert_txhandler = txhandlers
462 .remove(&TransactionType::MiniAssert(idx))
463 .ok_or(TxError::TxHandlerNotFound(TransactionType::MiniAssert(idx)))?;
464 let derivations = ClementineBitVMPublicKeys::get_assert_derivations(
465 idx,
466 assert_data.deposit_outpoint,
467 self.config.protocol_paramset(),
468 );
469 let winternitz_data: Vec<(Vec<u8>, WinternitzDerivationPath)> = derivations
472 .iter()
473 .zip(commit_data[idx].iter())
474 .map(|(derivation, commit_data)| match derivation {
475 WinternitzDerivationPath::BitvmAssert(_len, _, _, _, _) => {
476 (commit_data.clone(), derivation.clone())
477 }
478 _ => unreachable!(),
479 })
480 .collect();
481 self.signer
482 .tx_sign_winternitz(&mut mini_assert_txhandler, &winternitz_data)?;
483 signed_txhandlers.push(mini_assert_txhandler.promote()?);
484 }
485
486 Ok(signed_txhandlers
487 .into_iter()
488 .map(|txhandler| {
489 (
490 txhandler.get_transaction_type(),
491 txhandler.get_cached_tx().clone(),
492 )
493 })
494 .collect())
495 }
496
497 pub async fn create_latest_blockhash_tx(
508 &self,
509 assert_data: TransactionRequestData,
510 block_hash: BlockHash,
511 dbtx: Option<DatabaseTransaction<'_>>,
512 ) -> Result<(TransactionType, Transaction), BridgeError> {
513 let deposit_data = self
514 .db
515 .get_deposit_data(None, assert_data.deposit_outpoint)
516 .await?
517 .ok_or(BridgeError::DepositNotFound(assert_data.deposit_outpoint))?
518 .1;
519
520 let context = ContractContext::new_context_with_signer(
521 assert_data.kickoff_data,
522 deposit_data,
523 self.config.protocol_paramset(),
524 self.signer.clone(),
525 );
526
527 let mut txhandlers = builder::transaction::create_txhandlers(
528 TransactionType::LatestBlockhash,
529 context,
530 &mut TxHandlerCache::new(),
531 &mut ReimburseDbCache::new_for_deposit(
532 self.db.clone(),
533 assert_data.kickoff_data.operator_xonly_pk,
534 assert_data.deposit_outpoint,
535 self.config.protocol_paramset(),
536 dbtx,
537 ),
538 )
539 .await?;
540
541 let mut latest_blockhash_txhandler =
542 txhandlers
543 .remove(&TransactionType::LatestBlockhash)
544 .ok_or(TxError::TxHandlerNotFound(TransactionType::LatestBlockhash))?;
545
546 let block_hash: [u8; 32] = {
547 let raw = block_hash.to_byte_array();
548
549 #[cfg(test)]
550 {
551 self.config.test_params.maybe_disrupt_block_hash(raw)
552 }
553
554 #[cfg(not(test))]
555 {
556 raw
557 }
558 };
559
560 let block_hash_last_20 = block_hash.last_20_bytes().to_vec();
562
563 tracing::info!(
564 "Creating latest blockhash tx with block hash's last 20 bytes: {:?}",
565 block_hash_last_20
566 );
567 self.signer.tx_sign_winternitz(
568 &mut latest_blockhash_txhandler,
569 &[(
570 block_hash_last_20,
571 ClementineBitVMPublicKeys::get_latest_blockhash_derivation(
572 assert_data.deposit_outpoint,
573 self.config.protocol_paramset(),
574 ),
575 )],
576 )?;
577
578 let latest_blockhash_txhandler = latest_blockhash_txhandler.promote()?;
579
580 tracing::info!(
582 "Latest blockhash tx created with block hash witness: {:?}",
583 latest_blockhash_txhandler.get_cached_tx().input
584 );
585
586 Ok((
587 latest_blockhash_txhandler.get_transaction_type(),
588 latest_blockhash_txhandler.get_cached_tx().to_owned(),
589 ))
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use std::str::FromStr;
596
597 use crate::test::common::create_test_config_with_thread_name;
598
599 use super::*;
600
601 #[tokio::test]
602 async fn test_get_kickoff_utxos_to_sign_consistency() {
606 let config = create_test_config_with_thread_name().await;
607 let mut paramset = config.protocol_paramset().clone();
608 paramset.num_kickoffs_per_round = 2000;
609 paramset.num_signed_kickoffs = 20;
610 let paramset_ref: &'static ProtocolParamset = Box::leak(Box::new(paramset));
611 let op_xonly_pk = XOnlyPublicKey::from_str(
612 "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
613 )
614 .unwrap();
615 let deposit_blockhash =
616 BlockHash::from_str("0000000000000000000000000000000000000000000000000000000000000000")
617 .unwrap();
618 let deposit_outpoint = OutPoint::from_str(
619 "0000000000000000000000000000000000000000000000000000000000000000:0",
620 )
621 .unwrap();
622 let utxos_to_sign = get_kickoff_utxos_to_sign(
623 paramset_ref,
624 op_xonly_pk,
625 deposit_blockhash,
626 deposit_outpoint,
627 );
628 assert_eq!(utxos_to_sign.len(), 20);
629 assert_eq!(
630 utxos_to_sign,
631 vec![
632 1124, 447, 224, 1664, 1673, 1920, 713, 125, 1936, 1150, 1079, 1922, 596, 984, 567,
633 1134, 530, 539, 700, 1864
634 ]
635 );
636
637 let deposit_blockhash =
639 BlockHash::from_str("1100000000000000000000000000000000000000000000000000000000000000")
640 .unwrap();
641 let utxos_to_sign = get_kickoff_utxos_to_sign(
642 paramset_ref,
643 op_xonly_pk,
644 deposit_blockhash,
645 deposit_outpoint,
646 );
647
648 assert_eq!(utxos_to_sign.len(), 20);
649 assert_eq!(
650 utxos_to_sign,
651 vec![
652 1454, 26, 157, 1900, 451, 1796, 881, 544, 23, 1080, 1112, 1503, 1233, 1583, 1054,
653 603, 329, 1635, 213, 1331
654 ]
655 );
656 }
657}