1use crate::bitvm_client;
24use crate::builder::transaction::deposit_signature_owner::EntityType;
25use crate::builder::transaction::sign::get_kickoff_utxos_to_sign;
26use crate::builder::transaction::{
27 create_txhandlers, ContractContext, ReimburseDbCache, TxHandlerCache,
28};
29use crate::config::BridgeConfig;
30use crate::database::Database;
31use crate::deposit::{DepositData, KickoffData};
32use crate::rpc::clementine::tagged_signature::SignatureId;
33use crate::rpc::clementine::NormalSignatureKind;
34use async_stream::try_stream;
35use bitcoin::hashes::Hash;
36use bitcoin::{TapSighash, XOnlyPublicKey};
37use clementine_errors::BridgeError;
38use clementine_errors::TransactionType;
39use clementine_primitives::RoundIndex;
40use futures_core::stream::Stream;
41
42impl BridgeConfig {
43 pub fn get_num_required_nofn_sigs(&self, deposit_data: &DepositData) -> usize {
51 deposit_data.get_num_operators()
52 * self.protocol_paramset().num_round_txs
53 * self.protocol_paramset().num_signed_kickoffs
54 * self.get_num_required_nofn_sigs_per_kickoff(deposit_data)
55 }
56
57 pub fn get_num_required_operator_sigs(&self, deposit_data: &DepositData) -> usize {
65 self.protocol_paramset().num_round_txs
66 * self.protocol_paramset().num_signed_kickoffs
67 * self.get_num_required_operator_sigs_per_kickoff(deposit_data)
68 }
69
70 pub fn get_num_required_nofn_sigs_per_kickoff(&self, deposit_data: &DepositData) -> usize {
78 7 + 4 * deposit_data.get_num_verifiers()
79 + bitvm_client::ClementineBitVMPublicKeys::number_of_assert_txs() * 2
80 }
81
82 pub fn get_num_required_operator_sigs_per_kickoff(&self, deposit_data: &DepositData) -> usize {
90 4 + bitvm_client::ClementineBitVMPublicKeys::number_of_assert_txs()
91 + deposit_data.get_num_verifiers()
92 }
93
94 pub fn get_num_kickoff_winternitz_pks(&self) -> usize {
99 self.protocol_paramset().num_kickoffs_per_round
100 * (self.protocol_paramset().num_round_txs + 1) }
102
103 pub fn get_num_unspent_kickoff_sigs(&self) -> usize {
108 self.protocol_paramset().num_round_txs * self.protocol_paramset().num_kickoffs_per_round * 2
109 }
110
111 pub fn get_num_challenge_ack_hashes(&self, deposit_data: &DepositData) -> usize {
119 deposit_data.get_num_watchtowers()
120 }
121
122 }
127
128#[derive(Copy, Clone, Debug)]
130pub struct PartialSignatureInfo {
131 pub operator_idx: usize,
132 pub round_idx: RoundIndex,
133 pub kickoff_utxo_idx: usize,
134}
135
136pub use clementine_utils::TapTweakData;
138
139#[derive(Copy, Clone, Debug)]
146pub struct SignatureInfo {
147 pub operator_idx: usize,
148 pub round_idx: RoundIndex,
149 pub kickoff_utxo_idx: usize,
150 pub signature_id: SignatureId,
151 pub tweak_data: TapTweakData,
152 pub kickoff_txid: Option<bitcoin::Txid>,
153}
154
155impl PartialSignatureInfo {
156 pub fn new(
157 operator_idx: usize,
158 round_idx: RoundIndex,
159 kickoff_utxo_idx: usize,
160 ) -> PartialSignatureInfo {
161 PartialSignatureInfo {
162 operator_idx,
163 round_idx,
164 kickoff_utxo_idx,
165 }
166 }
167 pub fn complete(&self, signature_id: SignatureId, spend_data: TapTweakData) -> SignatureInfo {
169 SignatureInfo {
170 operator_idx: self.operator_idx,
171 round_idx: self.round_idx,
172 kickoff_utxo_idx: self.kickoff_utxo_idx,
173 signature_id,
174 tweak_data: spend_data,
175 kickoff_txid: None,
176 }
177 }
178 pub fn complete_with_kickoff_txid(&self, kickoff_txid: bitcoin::Txid) -> SignatureInfo {
180 SignatureInfo {
181 operator_idx: self.operator_idx,
182 round_idx: self.round_idx,
183 kickoff_utxo_idx: self.kickoff_utxo_idx,
184 signature_id: NormalSignatureKind::YieldKickoffTxid.into(),
185 tweak_data: TapTweakData::ScriptPath,
186 kickoff_txid: Some(kickoff_txid),
187 }
188 }
189}
190
191pub fn create_nofn_sighash_stream(
207 db: Database,
208 config: BridgeConfig,
209 deposit_data: DepositData,
210 deposit_blockhash: bitcoin::BlockHash,
211 yield_kickoff_txid: bool,
212) -> impl Stream<Item = Result<(TapSighash, SignatureInfo), BridgeError>> {
213 try_stream! {
214 let paramset = config.protocol_paramset();
215
216 let operators = deposit_data.get_operators();
217
218 for (operator_idx, op_xonly_pk) in
219 operators.iter().enumerate()
220 {
221
222 let utxo_idxs = get_kickoff_utxos_to_sign(
223 config.protocol_paramset(),
224 *op_xonly_pk,
225 deposit_blockhash,
226 deposit_data.get_deposit_outpoint(),
227 );
228 let mut tx_db_data = ReimburseDbCache::new_for_deposit(db.clone(), *op_xonly_pk, deposit_data.get_deposit_outpoint(), config.protocol_paramset(), None);
230
231 let mut txhandler_cache = TxHandlerCache::new();
232
233 for round_idx in RoundIndex::iter_rounds(paramset.num_round_txs) {
234 for &kickoff_idx in &utxo_idxs {
236 let partial = PartialSignatureInfo::new(operator_idx, round_idx, kickoff_idx);
237
238 let context = ContractContext::new_context_for_kickoff(
239 KickoffData {
240 operator_xonly_pk: *op_xonly_pk,
241 round_idx,
242 kickoff_idx: kickoff_idx as u32,
243 },
244 deposit_data.clone(),
245 config.protocol_paramset(),
246 );
247
248 let mut txhandlers = create_txhandlers(
249 TransactionType::AllNeededForDeposit,
250 context,
251 &mut txhandler_cache,
252 &mut tx_db_data,
253 ).await?;
254
255 let mut sum = 0;
256 let mut kickoff_txid = None;
257 for (tx_type, txhandler) in txhandlers.iter() {
258 let sighashes = txhandler.calculate_shared_txins_sighash(EntityType::VerifierDeposit, partial)?;
259 sum += sighashes.len();
260 for sighash in sighashes {
261 yield sighash;
262 }
263 if tx_type == &TransactionType::Kickoff {
264 kickoff_txid = Some(txhandler.get_txid());
265 }
266 }
267
268 match (yield_kickoff_txid, kickoff_txid) {
269 (true, Some(kickoff_txid)) => {
270 yield (TapSighash::all_zeros(), partial.complete_with_kickoff_txid(*kickoff_txid));
271 }
272 (true, None) => {
273 Err(eyre::eyre!("Kickoff txid not found in sighash stream"))?;
274 }
275 _ => {}
276 }
277
278
279 if sum != config.get_num_required_nofn_sigs_per_kickoff(&deposit_data) {
280 Err(eyre::eyre!("NofN sighash count does not match: expected {0}, got {1}", config.get_num_required_nofn_sigs_per_kickoff(&deposit_data), sum))?;
281 }
282 txhandler_cache.store_for_next_kickoff(&mut txhandlers)?;
284 }
285 txhandler_cache.store_for_next_round()?;
287 }
288 }
289 }
290}
291
292pub fn create_operator_sighash_stream(
309 db: Database,
310 operator_xonly_pk: XOnlyPublicKey,
311 config: BridgeConfig,
312 deposit_data: DepositData,
313 deposit_blockhash: bitcoin::BlockHash,
314) -> impl Stream<Item = Result<(TapSighash, SignatureInfo), BridgeError>> {
315 try_stream! {
316 let mut tx_db_data = ReimburseDbCache::new_for_deposit(db.clone(), operator_xonly_pk, deposit_data.get_deposit_outpoint(), config.protocol_paramset(), None);
317
318 let operator = db.get_operator(None, operator_xonly_pk).await?;
319
320 let operator = match operator {
321 Some(operator) => operator,
322 None => Err(BridgeError::OperatorNotFound(operator_xonly_pk))?,
323 };
324
325 let utxo_idxs = get_kickoff_utxos_to_sign(
326 config.protocol_paramset(),
327 operator.xonly_pk,
328 deposit_blockhash,
329 deposit_data.get_deposit_outpoint(),
330 );
331
332 let paramset = config.protocol_paramset();
333 let mut txhandler_cache = TxHandlerCache::new();
334 let operator_idx = deposit_data.get_operator_index(operator_xonly_pk)?;
335
336 for round_idx in RoundIndex::iter_rounds(paramset.num_round_txs) {
338 for &kickoff_idx in &utxo_idxs {
339 let partial = PartialSignatureInfo::new(operator_idx, round_idx, kickoff_idx);
340
341 let context = ContractContext::new_context_for_kickoff(
342 KickoffData {
343 operator_xonly_pk,
344 round_idx,
345 kickoff_idx: kickoff_idx as u32,
346 },
347 deposit_data.clone(),
348 config.protocol_paramset(),
349 );
350
351 let mut txhandlers = create_txhandlers(
352 TransactionType::AllNeededForDeposit,
353 context,
354 &mut txhandler_cache,
355 &mut tx_db_data,
356 ).await?;
357
358 let mut sum = 0;
359 for (_, txhandler) in txhandlers.iter() {
360 let sighashes = txhandler.calculate_shared_txins_sighash(EntityType::OperatorDeposit, partial)?;
361 sum += sighashes.len();
362 for sighash in sighashes {
363 yield sighash;
364 }
365 }
366 if sum != config.get_num_required_operator_sigs_per_kickoff(&deposit_data) {
367 Err(eyre::eyre!("Operator sighash count does not match: expected {0}, got {1}", config.get_num_required_operator_sigs_per_kickoff(&deposit_data), sum))?;
368 }
369 txhandler_cache.store_for_next_kickoff(&mut txhandlers)?;
371 }
372 txhandler_cache.store_for_next_round()?;
374 }
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381 use crate::{
382 bitvm_client::SECP,
383 builder::transaction::sign::TransactionRequestData,
384 config::protocol::ProtocolParamset,
385 deposit::{Actors, DepositInfo, OperatorData},
386 extended_bitcoin_rpc::ExtendedBitcoinRpc,
387 rpc::clementine::{
388 clementine_operator_client::ClementineOperatorClient, TransactionRequest,
389 },
390 test::common::{
391 citrea::MockCitreaClient, create_actors, create_regtest_rpc,
392 create_test_config_with_thread_name, run_single_deposit,
393 tx_utils::get_tx_from_signed_txs_with_type,
394 },
395 };
396 use bincode;
397 use bitcoin::hashes::sha256;
398 use bitcoin::secp256k1::PublicKey;
399 use bitcoin::{Block, BlockHash, OutPoint, Txid};
400 use bitcoincore_rpc::RpcApi;
401 use futures_util::stream::TryStreamExt;
402 use std::fs::File;
403
404 #[cfg(debug_assertions)]
405 pub const DEPOSIT_STATE_FILE_PATH_DEBUG: &str = "src/test/data/deposit_state_debug.bincode";
406 #[cfg(not(debug_assertions))]
407 pub const DEPOSIT_STATE_FILE_PATH_RELEASE: &str = "src/test/data/deposit_state_release.bincode";
408
409 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
420 struct DepositChainState {
421 blocks: Vec<Block>,
422 deposit_info: DepositInfo,
423 deposit_blockhash: BlockHash,
424 move_txid: Txid,
425 operator_data: OperatorData,
426 round_tx_txid_hash: sha256::Hash,
427 nofn_sighash_hash: sha256::Hash,
428 operator_sighash_hash: sha256::Hash,
429 }
430
431 #[cfg(feature = "automation")]
435 #[tokio::test]
436 #[ignore = "Run this to generate fresh deposit state data, in case any breaking change occurs to deposits"]
437 async fn generate_deposit_state() {
438 let mut config = create_test_config_with_thread_name().await;
439 config.test_params.all_operators_secret_keys.truncate(1);
441
442 let regtest = create_regtest_rpc(&mut config).await;
443 let rpc = regtest.rpc().clone();
444
445 let actors = create_actors::<MockCitreaClient>(&config).await;
446 let (deposit_info, move_txid, deposit_blockhash, verifiers_public_keys) =
447 run_single_deposit::<MockCitreaClient>(&mut config, rpc.clone(), None, &actors, None)
448 .await
449 .unwrap();
450
451 let height = rpc.get_current_chain_height().await.unwrap();
453 let mut blocks = Vec::new();
454 for i in 1..=height {
455 let (blockhash, _) = rpc.get_block_info_by_height(i as u64).await.unwrap();
456 let block = rpc.get_block(&blockhash).await.unwrap();
457 blocks.push(block);
458 }
459
460 let op0_config = BridgeConfig {
461 secret_key: config.test_params.all_verifiers_secret_keys[0],
462 db_name: config.db_name + "0",
463 ..config
464 };
465
466 let operators_xonly_pks = op0_config
467 .test_params
468 .all_operators_secret_keys
469 .iter()
470 .map(|sk| sk.x_only_public_key(&SECP).0)
471 .collect::<Vec<_>>();
472
473 let op0_xonly_pk = operators_xonly_pks[0];
474
475 let db = Database::new(&op0_config).await.unwrap();
476 let operator_data = db.get_operator(None, op0_xonly_pk).await.unwrap().unwrap();
477
478 let (nofn_sighash_hash, operator_sighash_hash) = calculate_hash_of_sighashes(
479 deposit_info.clone(),
480 verifiers_public_keys,
481 operators_xonly_pks.clone(),
482 op0_config.clone(),
483 deposit_blockhash,
484 )
485 .await;
486
487 let operator = actors.get_operator_client_by_index(0);
488
489 let round_tx_txid_hash = compute_hash_of_round_txs(
490 operator,
491 deposit_info.deposit_outpoint,
492 operators_xonly_pks[0],
493 deposit_blockhash,
494 op0_config.protocol_paramset(),
495 )
496 .await;
497
498 let deposit_state = DepositChainState {
499 blocks,
500 deposit_blockhash,
501 move_txid,
502 deposit_info,
503 operator_data,
504 round_tx_txid_hash,
505 nofn_sighash_hash,
506 operator_sighash_hash,
507 };
508
509 #[cfg(debug_assertions)]
510 let file_path = DEPOSIT_STATE_FILE_PATH_DEBUG;
511 #[cfg(not(debug_assertions))]
512 let file_path = DEPOSIT_STATE_FILE_PATH_RELEASE;
513
514 let file = File::create(file_path).unwrap();
516 bincode::serialize_into(file, &deposit_state).unwrap();
517 }
518
519 async fn load_deposit_state(rpc: &ExtendedBitcoinRpc) -> DepositChainState {
520 tracing::debug!(
521 "Current chain height: {}",
522 rpc.get_current_chain_height().await.unwrap()
523 );
524 #[cfg(debug_assertions)]
525 let file_path = DEPOSIT_STATE_FILE_PATH_DEBUG;
526 #[cfg(not(debug_assertions))]
527 let file_path = DEPOSIT_STATE_FILE_PATH_RELEASE;
528
529 let file = File::open(file_path).unwrap();
530 let deposit_state: DepositChainState = bincode::deserialize_from(file).unwrap();
531
532 for block in &deposit_state.blocks {
534 rpc.submit_block(block).await.unwrap();
535 }
536 deposit_state
537 }
538
539 async fn compute_hash_of_round_txs(
541 mut operator: ClementineOperatorClient<tonic::transport::Channel>,
542 deposit_outpoint: OutPoint,
543 operator_xonly_pk: XOnlyPublicKey,
544 deposit_blockhash: bitcoin::BlockHash,
545 paramset: &'static ProtocolParamset,
546 ) -> sha256::Hash {
547 let kickoff_utxo = get_kickoff_utxos_to_sign(
548 paramset,
549 operator_xonly_pk,
550 deposit_blockhash,
551 deposit_outpoint,
552 )[0];
553
554 let mut all_round_txids = Vec::new();
555 for i in 0..paramset.num_round_txs {
556 let tx_req = TransactionRequestData {
557 deposit_outpoint,
558 kickoff_data: KickoffData {
559 operator_xonly_pk,
560 round_idx: RoundIndex::Round(i),
561 kickoff_idx: kickoff_utxo as u32,
562 },
563 };
564 let signed_txs = operator
565 .internal_create_signed_txs(TransactionRequest::from(tx_req))
566 .await
567 .unwrap()
568 .into_inner();
569 let round_tx =
570 get_tx_from_signed_txs_with_type(&signed_txs, TransactionType::Round).unwrap();
571 all_round_txids.push(round_tx.compute_txid());
572 }
573
574 sha256::Hash::hash(&all_round_txids.concat())
575 }
576
577 async fn calculate_hash_of_sighashes(
579 deposit_info: DepositInfo,
580 verifiers_public_keys: Vec<PublicKey>,
581 operators_xonly_pks: Vec<XOnlyPublicKey>,
582 op0_config: BridgeConfig,
583 deposit_blockhash: bitcoin::BlockHash,
584 ) -> (sha256::Hash, sha256::Hash) {
585 let deposit_data = DepositData {
586 nofn_xonly_pk: None,
587 deposit: deposit_info,
588 actors: Actors {
589 verifiers: verifiers_public_keys,
590 watchtowers: vec![],
591 operators: operators_xonly_pks.clone(),
592 },
593 security_council: op0_config.security_council.clone(),
594 };
595
596 let db = Database::new(&op0_config).await.unwrap();
597
598 let sighash_stream = create_nofn_sighash_stream(
599 db.clone(),
600 op0_config.clone(),
601 deposit_data.clone(),
602 deposit_blockhash,
603 true,
604 );
605
606 let nofn_sighashes: Vec<_> = sighash_stream.try_collect().await.unwrap();
607 let nofn_sighashes = nofn_sighashes
608 .into_iter()
609 .map(|(sighash, _info)| sighash.to_byte_array())
610 .collect::<Vec<_>>();
611
612 let operator_streams = create_operator_sighash_stream(
613 db.clone(),
614 operators_xonly_pks[0],
615 op0_config.clone(),
616 deposit_data.clone(),
617 deposit_blockhash,
618 );
619
620 let operator_sighashes: Vec<_> = operator_streams.try_collect().await.unwrap();
621 let operator_sighashes = operator_sighashes
622 .into_iter()
623 .map(|(sighash, _info)| sighash.to_byte_array())
624 .collect::<Vec<_>>();
625
626 let nofn_hash = sha256::Hash::hash(&nofn_sighashes.concat());
628 let operator_hash = sha256::Hash::hash(&operator_sighashes.concat());
629
630 (nofn_hash, operator_hash)
631 }
632
633 #[cfg(feature = "automation")]
652 #[tokio::test]
653 async fn test_bridge_contract_change() {
654 let mut config = create_test_config_with_thread_name().await;
655 config.test_params.all_operators_secret_keys.truncate(1);
657
658 config.test_params.generate_to_address = false;
660
661 let regtest = create_regtest_rpc(&mut config).await;
662 let rpc = regtest.rpc().clone();
663
664 let deposit_state = load_deposit_state(&rpc).await;
665
666 config.operator_reimbursement_address = Some(
668 deposit_state
669 .operator_data
670 .reimburse_addr
671 .as_unchecked()
672 .to_owned(),
673 );
674 config.operator_collateral_funding_outpoint =
675 Some(deposit_state.operator_data.collateral_funding_outpoint);
676
677 let address = rpc
680 .get_new_address(None, None)
681 .await
682 .expect("Failed to get new address");
683
684 rpc.generate_to_address(105, address.assume_checked_ref())
685 .await
686 .expect("Failed to generate blocks");
687
688 let actors = create_actors::<MockCitreaClient>(&config).await;
689 let (deposit_info, move_txid, deposit_blockhash, verifiers_public_keys) =
690 run_single_deposit::<MockCitreaClient>(
691 &mut config,
692 rpc.clone(),
693 None,
694 &actors,
695 Some(deposit_state.deposit_info.deposit_outpoint),
696 )
697 .await
698 .unwrap();
699
700 assert_eq!(move_txid, deposit_state.move_txid);
703 assert_eq!(deposit_blockhash, deposit_state.deposit_blockhash);
704 assert_eq!(deposit_info, deposit_state.deposit_info);
705
706 let op0_config = BridgeConfig {
707 secret_key: config.test_params.all_verifiers_secret_keys[0],
708 db_name: config.db_name.clone() + "0",
709 ..config.clone()
710 };
711
712 let operators_xonly_pks = op0_config
713 .test_params
714 .all_operators_secret_keys
715 .iter()
716 .map(|sk| sk.x_only_public_key(&SECP).0)
717 .collect::<Vec<_>>();
718
719 let operator = actors.get_operator_client_by_index(0);
720
721 let round_tx_hash = compute_hash_of_round_txs(
722 operator,
723 deposit_info.deposit_outpoint,
724 operators_xonly_pks[0],
725 deposit_blockhash,
726 op0_config.protocol_paramset(),
727 )
728 .await;
729
730 assert_eq!(
732 round_tx_hash, deposit_state.round_tx_txid_hash,
733 "Round tx hash does not match the previous values, round txs are changed"
734 );
735
736 let (nofn_hash, operator_hash) = calculate_hash_of_sighashes(
737 deposit_info,
738 verifiers_public_keys,
739 operators_xonly_pks,
740 op0_config,
741 deposit_blockhash,
742 )
743 .await;
744
745 assert_eq!(
747 nofn_hash, deposit_state.nofn_sighash_hash,
748 "NofN sighashes do not match the previous values"
749 );
750 assert_eq!(
751 operator_hash, deposit_state.operator_sighash_hash,
752 "Operator sighashes do not match the previous values"
753 );
754 }
755}