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