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;
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(
232 &self,
233 transaction_data: TransactionRequestData,
234 commit_data: &[u8],
235 dbtx: Option<DatabaseTransaction<'_>>,
236 ) -> Result<(TransactionType, Transaction), 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 mut 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 self.signer
287 .tx_sign_and_fill_sigs(&mut watchtower_challenge_txhandler, &[], None)?;
288
289 Ok((
290 TransactionType::WatchtowerChallenge(watchtower_index),
291 watchtower_challenge_txhandler.get_cached_tx().clone(),
292 ))
293 }
294
295 pub async fn create_and_sign_unspent_kickoff_connector_txs(
307 &self,
308 round_idx: RoundIndex,
309 operator_xonly_pk: XOnlyPublicKey,
310 mut dbtx: Option<DatabaseTransaction<'_>>,
311 ) -> Result<Vec<(TransactionType, Transaction)>, BridgeError> {
312 let context = ContractContext::new_context_for_round(
313 operator_xonly_pk,
314 round_idx,
315 self.config.protocol_paramset(),
316 );
317
318 let txhandlers = builder::transaction::create_txhandlers(
319 TransactionType::UnspentKickoff(0),
320 context,
321 &mut TxHandlerCache::new(),
322 &mut ReimburseDbCache::new_for_rounds(
323 self.db.clone(),
324 operator_xonly_pk,
325 self.config.protocol_paramset(),
326 dbtx.as_deref_mut(),
327 ),
328 )
329 .await?;
330
331 let unspent_kickoff_sigs = self
333 .db
334 .get_unspent_kickoff_sigs(dbtx, operator_xonly_pk, round_idx)
335 .await?
336 .ok_or(eyre::eyre!(
337 "No unspent kickoff signatures found for operator {:?} and round {:?}",
338 operator_xonly_pk,
339 round_idx
340 ))?;
341
342 let mut signed_txs = Vec::with_capacity(txhandlers.len());
343 let mut tweak_cache = TweakCache::default();
344
345 for (tx_type, mut txhandler) in txhandlers.into_iter() {
346 if !matches!(tx_type, TransactionType::UnspentKickoff(_)) {
347 continue;
349 }
350 let res = self.signer
351 .tx_sign_and_fill_sigs(
352 &mut txhandler,
353 &unspent_kickoff_sigs,
354 Some(&mut tweak_cache),
355 )
356 .wrap_err(format!(
357 "Couldn't sign transaction {tx_type:?} in create_and_sign_unspent_kickoff_connector_txs for round {round_idx:?} and operator {operator_xonly_pk:?}",
358 ));
359
360 let checked_txhandler = txhandler.promote();
361
362 match checked_txhandler {
363 Ok(checked_txhandler) => {
364 signed_txs.push((tx_type, checked_txhandler.get_cached_tx().clone()));
365 }
366 Err(e) => {
367 tracing::trace!(
368 "Couldn't sign transaction {:?} in create_and_sign_unspent_kickoff_connector_txs: {:?}: {:?}",
369 tx_type,
370 e,
371 res.err()
372 );
373 }
374 }
375 }
376
377 Ok(signed_txs)
378 }
379}
380
381impl<C> Operator<C>
382where
383 C: CitreaClientT,
384{
385 pub async fn create_assert_commitment_txs(
396 &self,
397 assert_data: TransactionRequestData,
398 commit_data: Vec<Vec<Vec<u8>>>,
399 dbtx: Option<DatabaseTransaction<'_>>,
400 ) -> Result<Vec<(TransactionType, Transaction)>, BridgeError> {
401 let deposit_data = self
402 .db
403 .get_deposit_data(None, assert_data.deposit_outpoint)
404 .await?
405 .ok_or(BridgeError::DepositNotFound(assert_data.deposit_outpoint))?
406 .1;
407
408 let context = ContractContext::new_context_with_signer(
409 assert_data.kickoff_data,
410 deposit_data.clone(),
411 self.config.protocol_paramset(),
412 self.signer.clone(),
413 );
414
415 let mut txhandlers = builder::transaction::create_txhandlers(
416 TransactionType::MiniAssert(0),
417 context,
418 &mut TxHandlerCache::new(),
419 &mut ReimburseDbCache::new_for_deposit(
420 self.db.clone(),
421 self.signer.xonly_public_key,
422 assert_data.deposit_outpoint,
423 self.config.protocol_paramset(),
424 dbtx,
425 ),
426 )
427 .await?;
428
429 let mut signed_txhandlers = Vec::new();
430
431 for idx in 0..ClementineBitVMPublicKeys::number_of_assert_txs() {
432 let mut mini_assert_txhandler = txhandlers
433 .remove(&TransactionType::MiniAssert(idx))
434 .ok_or(TxError::TxHandlerNotFound(TransactionType::MiniAssert(idx)))?;
435 let derivations = ClementineBitVMPublicKeys::get_assert_derivations(
436 idx,
437 assert_data.deposit_outpoint,
438 self.config.protocol_paramset(),
439 );
440 let winternitz_data: Vec<(Vec<u8>, WinternitzDerivationPath)> = derivations
443 .iter()
444 .zip(commit_data[idx].iter())
445 .map(|(derivation, commit_data)| match derivation {
446 WinternitzDerivationPath::BitvmAssert(_len, _, _, _, _) => {
447 (commit_data.clone(), derivation.clone())
448 }
449 _ => unreachable!(),
450 })
451 .collect();
452 self.signer
453 .tx_sign_winternitz(&mut mini_assert_txhandler, &winternitz_data)?;
454 signed_txhandlers.push(mini_assert_txhandler.promote()?);
455 }
456
457 Ok(signed_txhandlers
458 .into_iter()
459 .map(|txhandler| {
460 (
461 txhandler.get_transaction_type(),
462 txhandler.get_cached_tx().clone(),
463 )
464 })
465 .collect())
466 }
467
468 pub async fn create_latest_blockhash_tx(
479 &self,
480 assert_data: TransactionRequestData,
481 block_hash: BlockHash,
482 dbtx: Option<DatabaseTransaction<'_>>,
483 ) -> Result<(TransactionType, Transaction), BridgeError> {
484 let deposit_data = self
485 .db
486 .get_deposit_data(None, assert_data.deposit_outpoint)
487 .await?
488 .ok_or(BridgeError::DepositNotFound(assert_data.deposit_outpoint))?
489 .1;
490
491 let context = ContractContext::new_context_with_signer(
492 assert_data.kickoff_data,
493 deposit_data,
494 self.config.protocol_paramset(),
495 self.signer.clone(),
496 );
497
498 let mut txhandlers = builder::transaction::create_txhandlers(
499 TransactionType::LatestBlockhash,
500 context,
501 &mut TxHandlerCache::new(),
502 &mut ReimburseDbCache::new_for_deposit(
503 self.db.clone(),
504 assert_data.kickoff_data.operator_xonly_pk,
505 assert_data.deposit_outpoint,
506 self.config.protocol_paramset(),
507 dbtx,
508 ),
509 )
510 .await?;
511
512 let mut latest_blockhash_txhandler =
513 txhandlers
514 .remove(&TransactionType::LatestBlockhash)
515 .ok_or(TxError::TxHandlerNotFound(TransactionType::LatestBlockhash))?;
516
517 let block_hash: [u8; 32] = {
518 let raw = block_hash.to_byte_array();
519
520 #[cfg(test)]
521 {
522 self.config.test_params.maybe_disrupt_block_hash(raw)
523 }
524
525 #[cfg(not(test))]
526 {
527 raw
528 }
529 };
530
531 let block_hash_last_20 = block_hash.last_20_bytes().to_vec();
533
534 tracing::info!(
535 "Creating latest blockhash tx with block hash's last 20 bytes: {:?}",
536 block_hash_last_20
537 );
538 self.signer.tx_sign_winternitz(
539 &mut latest_blockhash_txhandler,
540 &[(
541 block_hash_last_20,
542 ClementineBitVMPublicKeys::get_latest_blockhash_derivation(
543 assert_data.deposit_outpoint,
544 self.config.protocol_paramset(),
545 ),
546 )],
547 )?;
548
549 let latest_blockhash_txhandler = latest_blockhash_txhandler.promote()?;
550
551 tracing::info!(
553 "Latest blockhash tx created with block hash witness: {:?}",
554 latest_blockhash_txhandler.get_cached_tx().input
555 );
556
557 Ok((
558 latest_blockhash_txhandler.get_transaction_type(),
559 latest_blockhash_txhandler.get_cached_tx().to_owned(),
560 ))
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use std::str::FromStr;
567
568 use crate::test::common::create_test_config_with_thread_name;
569
570 use super::*;
571
572 #[tokio::test]
573 async fn test_get_kickoff_utxos_to_sign_consistency() {
577 let config = create_test_config_with_thread_name().await;
578 let mut paramset = config.protocol_paramset().clone();
579 paramset.num_kickoffs_per_round = 2000;
580 paramset.num_signed_kickoffs = 20;
581 let paramset_ref: &'static ProtocolParamset = Box::leak(Box::new(paramset));
582 let op_xonly_pk = XOnlyPublicKey::from_str(
583 "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
584 )
585 .unwrap();
586 let deposit_blockhash =
587 BlockHash::from_str("0000000000000000000000000000000000000000000000000000000000000000")
588 .unwrap();
589 let deposit_outpoint = OutPoint::from_str(
590 "0000000000000000000000000000000000000000000000000000000000000000:0",
591 )
592 .unwrap();
593 let utxos_to_sign = get_kickoff_utxos_to_sign(
594 paramset_ref,
595 op_xonly_pk,
596 deposit_blockhash,
597 deposit_outpoint,
598 );
599 assert_eq!(utxos_to_sign.len(), 20);
600 assert_eq!(
601 utxos_to_sign,
602 vec![
603 1124, 447, 224, 1664, 1673, 1920, 713, 125, 1936, 1150, 1079, 1922, 596, 984, 567,
604 1134, 530, 539, 700, 1864
605 ]
606 );
607
608 let deposit_blockhash =
610 BlockHash::from_str("1100000000000000000000000000000000000000000000000000000000000000")
611 .unwrap();
612 let utxos_to_sign = get_kickoff_utxos_to_sign(
613 paramset_ref,
614 op_xonly_pk,
615 deposit_blockhash,
616 deposit_outpoint,
617 );
618
619 assert_eq!(utxos_to_sign.len(), 20);
620 assert_eq!(
621 utxos_to_sign,
622 vec![
623 1454, 26, 157, 1900, 451, 1796, 881, 544, 23, 1080, 1112, 1503, 1233, 1583, 1054,
624 603, 329, 1635, 213, 1331
625 ]
626 );
627 }
628}