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