1use crate::actor::WinternitzDerivationPath;
2use crate::builder::address::taproot_builder_with_scripts;
3use crate::builder::script::{SpendableScript, WinternitzCommit};
4
5use crate::config::protocol::ProtocolParamset;
6use crate::constants::MAX_SCRIPT_REPLACEMENT_OPERATIONS;
7use crate::errors::BridgeError;
8use bitcoin::{self};
9use bitcoin::{ScriptBuf, XOnlyPublicKey};
10
11use bitvm::chunk::api::{
12 api_generate_full_tapscripts, api_generate_partial_script, Assertions, NUM_HASH, NUM_PUBS,
13 NUM_U256,
14};
15
16use bitvm::signatures::{Wots, Wots20};
17use borsh::{BorshDeserialize, BorshSerialize};
18use bridge_circuit_host::utils::{get_verifying_key, is_dev_mode};
19use eyre::Context;
20use sha2::{Digest, Sha256};
21use std::fs;
22use tokio::sync::Mutex;
23
24use once_cell::sync::OnceCell;
25use std::str::FromStr;
26use std::sync::{Arc, LazyLock};
27use std::time::Instant;
28
29pub static REPLACE_SCRIPTS_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
32
33lazy_static::lazy_static! {
34 pub static ref SECP: bitcoin::secp256k1::Secp256k1<bitcoin::secp256k1::All> = bitcoin::secp256k1::Secp256k1::new();
36}
37
38lazy_static::lazy_static! {
39 pub static ref UNSPENDABLE_XONLY_PUBKEY: bitcoin::secp256k1::XOnlyPublicKey =
52 XOnlyPublicKey::from_str("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0").expect("this key is valid");
53}
54
55pub static BITVM_CACHE: OnceCell<BitvmCacheWithMetadata> = OnceCell::new();
61
62pub struct BitvmCacheWithMetadata {
63 pub bitvm_cache: BitvmCache,
64 pub sha256_bitvm_cache: [u8; 32],
65}
66
67fn get_cache_path() -> String {
69 std::env::var("BITVM_CACHE_PATH").unwrap_or_else(|_| {
70 if is_dev_mode() {
71 "bitvm_cache_dev.bin".to_string()
72 } else {
73 "bitvm_cache.bin".to_string()
74 }
75 })
76}
77
78pub fn load_or_generate_bitvm_cache() -> Result<BitvmCacheWithMetadata, BridgeError> {
79 let start = Instant::now();
80
81 let cache_path = get_cache_path();
82
83 let bitvm_cache = {
84 tracing::debug!("Attempting to load BitVM cache from file: {}", cache_path);
85
86 match BitvmCache::load_from_file(&cache_path) {
87 Ok(cache) => {
88 tracing::debug!("Loaded BitVM cache from file");
89
90 cache
91 }
92 Err(_) => {
93 tracing::info!("No BitVM cache found, generating fresh data");
94
95 let fresh_data = generate_fresh_data();
96
97 if let Err(e) = fresh_data.save_to_file(&cache_path) {
98 tracing::error!(
99 "Failed to save freshly generated BitVM cache to file: {}",
100 e
101 );
102 }
103 fresh_data
104 }
105 }
106 };
107
108 tracing::debug!("BitVM initialization took: {:?}", start.elapsed());
109
110 let sha256_start = Instant::now();
112 let sha256_bitvm_cache = bitvm_cache.calculate_sha256()?;
113 tracing::info!(
114 "Time taken to calculate SHA256 of bitvm cache: {:?}",
115 sha256_start.elapsed()
116 );
117 tracing::info!("SHA256 of bitvm cache: {:?}", sha256_bitvm_cache);
118
119 Ok(BitvmCacheWithMetadata {
120 bitvm_cache,
121 sha256_bitvm_cache,
122 })
123}
124
125#[derive(BorshSerialize, BorshDeserialize, Debug)]
126pub struct BitvmCache {
127 pub disprove_scripts: Vec<Vec<u8>>,
128 pub replacement_places: Box<ClementineBitVMReplacementData>,
129}
130
131impl BitvmCache {
132 fn save_to_file(&self, path: &str) -> Result<(), BridgeError> {
133 let serialized = borsh::to_vec(self).map_err(|e| {
134 tracing::error!("Failed to serialize BitVM cache: {}", e);
135 BridgeError::ConfigError("Failed to serialize BitVM cache".to_string())
136 })?;
137
138 fs::write(path, serialized).map_err(|e| {
139 tracing::error!("Failed to save BitVM cache: {}", e);
140 BridgeError::ConfigError("Failed to save BitVM cache".to_string())
141 })
142 }
143
144 fn load_from_file(path: &str) -> Result<Self, BridgeError> {
145 let bytes = fs::read(path).map_err(|e| {
146 tracing::error!("Failed to read BitVM cache: {}", e);
147 BridgeError::ConfigError("No BitVM cache found".to_string())
148 })?;
149
150 tracing::info!("Loaded BitVM cache from file, read {} bytes", bytes.len());
151
152 Self::try_from_slice(&bytes).map_err(|e| {
153 tracing::error!("Failed to deserialize BitVM cache: {}", e);
154 BridgeError::ConfigError("Failed to deserialize BitVM cache".to_string())
155 })
156 }
157
158 fn calculate_sha256(&self) -> Result<[u8; 32], BridgeError> {
159 let mut hasher = Sha256::new();
160 hasher.update(
161 borsh::to_vec(&self)
162 .wrap_err("Failed to serialize BitVM cache while calculating sha256")?,
163 );
164 Ok(hasher.finalize().into())
165 }
166}
167
168fn generate_fresh_data() -> BitvmCache {
169 let vk = get_verifying_key();
170
171 let dummy_pks = ClementineBitVMPublicKeys::create_replacable();
172
173 let partial_scripts = api_generate_partial_script(&vk);
174
175 let scripts = partial_scripts
176 .iter()
177 .map(|s| s.clone().to_bytes())
178 .collect::<Vec<_>>();
179
180 for (script_idx, script) in scripts.iter().enumerate() {
181 let mut pos = 0;
182 while pos + 20 <= script.len() {
183 if script[pos + 4..pos + 20] == [255u8; 16] {
185 panic!("Dummy value found in script {script_idx}");
186 }
187 pos += 1;
188 }
189 }
190
191 let disprove_scripts = api_generate_full_tapscripts(dummy_pks.bitvm_pks, &partial_scripts);
192
193 let scripts: Vec<Vec<u8>> = disprove_scripts
194 .iter()
195 .map(|s| s.clone().to_bytes())
196 .collect();
197
198 let mut replacement_places: ClementineBitVMReplacementData = Default::default();
200 for (script_idx, script) in scripts.iter().enumerate() {
202 let mut pos = 0;
203 while pos + 20 <= script.len() {
204 if script[pos + 4..pos + 20] == [255u8; 16] {
206 let pk_type_idx = script[pos];
207 let pk_idx = u16::from_be_bytes([script[pos + 1], script[pos + 2]]);
208 let digit_idx = script[pos + 3];
209
210 match pk_type_idx {
211 0 => {
212 replacement_places.payout_tx_blockhash_pk[digit_idx as usize]
213 .push((script_idx, pos));
214 }
215 1 => {
216 replacement_places.latest_blockhash_pk[digit_idx as usize]
217 .push((script_idx, pos));
218 }
219 2 => {
220 replacement_places.challenge_sending_watchtowers_pk[digit_idx as usize]
221 .push((script_idx, pos));
222 }
223 3 => {
224 replacement_places.bitvm_pks.0[pk_idx as usize][digit_idx as usize]
225 .push((script_idx, pos));
226 }
227 4 => {
228 replacement_places.bitvm_pks.1[pk_idx as usize][digit_idx as usize]
229 .push((script_idx, pos));
230 }
231 5 => {
232 replacement_places.bitvm_pks.2[pk_idx as usize][digit_idx as usize]
233 .push((script_idx, pos));
234 }
235 _ => {
236 panic!("Invalid pk type index: {pk_type_idx}");
237 }
238 }
239 pos += 20;
240 } else {
241 pos += 1;
242 }
243 }
244 }
245
246 BitvmCache {
247 disprove_scripts: scripts,
248 replacement_places: Box::new(replacement_places),
249 }
250}
251
252#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
253pub struct ClementineBitVMPublicKeys {
254 pub combined_method_id_constant: [u8; 32],
255 pub deposit_constant: [u8; 32],
256 pub payout_tx_blockhash_pk: <Wots20 as Wots>::PublicKey,
257 pub latest_blockhash_pk: <Wots20 as Wots>::PublicKey,
258 pub challenge_sending_watchtowers_pk: <Wots20 as Wots>::PublicKey,
259 pub bitvm_pks: bitvm::chunk::api::PublicKeys,
260}
261
262#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
263#[allow(clippy::type_complexity)]
264pub struct ClementineBitVMReplacementData {
265 pub payout_tx_blockhash_pk: [Vec<(usize, usize)>; 43],
266 pub latest_blockhash_pk: [Vec<(usize, usize)>; 43],
267 pub challenge_sending_watchtowers_pk: [Vec<(usize, usize)>; 43],
268 pub bitvm_pks: (
269 [[Vec<(usize, usize)>; 67]; NUM_PUBS],
270 [[Vec<(usize, usize)>; 67]; NUM_U256],
271 [[Vec<(usize, usize)>; 35]; NUM_HASH],
272 ),
273}
274
275impl Default for ClementineBitVMReplacementData {
276 fn default() -> Self {
277 Self {
278 payout_tx_blockhash_pk: std::array::from_fn(|_| Vec::new()),
279 latest_blockhash_pk: std::array::from_fn(|_| Vec::new()),
280 challenge_sending_watchtowers_pk: std::array::from_fn(|_| Vec::new()),
281 bitvm_pks: (
282 std::array::from_fn(|_| std::array::from_fn(|_| Vec::new())),
283 std::array::from_fn(|_| std::array::from_fn(|_| Vec::new())),
284 std::array::from_fn(|_| std::array::from_fn(|_| Vec::new())),
285 ),
286 }
287 }
288}
289
290impl ClementineBitVMPublicKeys {
291 pub fn get_replacable_value(pk_type_idx: u8, pk_idx: u16, digit_idx: u8) -> [u8; 20] {
292 let mut dummy_value = [255u8; 20];
293 dummy_value[0] = pk_type_idx;
294 dummy_value[1] = pk_idx.to_be_bytes()[0];
295 dummy_value[2] = pk_idx.to_be_bytes()[1];
296 dummy_value[3] = digit_idx;
297 dummy_value
298 }
299
300 pub fn get_replacable_wpks<const DIGIT_LEN: usize>(
301 pk_type_idx: u8,
302 pk_idx: u16,
303 ) -> [[u8; 20]; DIGIT_LEN] {
304 (0..DIGIT_LEN as u8)
305 .map(|digit_idx| Self::get_replacable_value(pk_type_idx, pk_idx, digit_idx))
306 .collect::<Vec<_>>()
307 .try_into()
308 .expect("Should be able to convert to array")
309 }
310
311 pub fn get_multiple_replacable_wpks<const DIGIT_LEN: usize, const PK_LEN: usize>(
312 pk_type_idx: u8,
313 ) -> [[[u8; 20]; DIGIT_LEN]; PK_LEN] {
314 (0..PK_LEN as u16)
315 .map(|pk_idx| Self::get_replacable_wpks(pk_type_idx, pk_idx))
316 .collect::<Vec<_>>()
317 .try_into()
318 .expect("Should be able to convert to array")
319 }
320
321 pub fn create_replacable() -> Self {
322 let combined_method_id_constant = [255u8; 32];
323 let deposit_constant = [255u8; 32];
324 let payout_tx_blockhash_pk = Self::get_replacable_wpks(0, 0);
325 let latest_blockhash_pk = Self::get_replacable_wpks(1, 0);
326 let challenge_sending_watchtowers_pk = Self::get_replacable_wpks(2, 0);
327 let bitvm_part_1 = Self::get_multiple_replacable_wpks(3);
328 let bitvm_part_2 = Self::get_multiple_replacable_wpks(4);
329 let bitvm_part_3 = Self::get_multiple_replacable_wpks(5);
330 let bitvm_pks = (bitvm_part_1, bitvm_part_2, bitvm_part_3);
331 Self {
332 combined_method_id_constant,
333 deposit_constant,
334 payout_tx_blockhash_pk,
335 latest_blockhash_pk,
336 challenge_sending_watchtowers_pk,
337 bitvm_pks,
338 }
339 }
340
341 pub fn from_flattened_vec(flattened_wpks: &[Vec<[u8; 20]>]) -> Self {
342 let combined_method_id_constant = [255u8; 32];
344 let deposit_constant = [255u8; 32];
345
346 let payout_tx_blockhash_pk = Self::vec_to_array::<43>(&flattened_wpks[0]);
348
349 let latest_blockhash_pk = Self::vec_to_array::<43>(&flattened_wpks[1]);
351
352 let challenge_sending_watchtowers_pk = Self::vec_to_array::<43>(&flattened_wpks[2]);
354
355 let bitvm_pks_1 =
357 Self::vec_slice_to_nested_array::<67, NUM_PUBS>(&flattened_wpks[3..3 + NUM_PUBS]);
358
359 let bitvm_pks_2 = Self::vec_slice_to_nested_array::<67, NUM_U256>(
360 &flattened_wpks[3 + NUM_PUBS..3 + NUM_PUBS + NUM_U256],
361 );
362
363 let bitvm_pks_3 = Self::vec_slice_to_nested_array::<35, NUM_HASH>(
364 &flattened_wpks[3 + NUM_PUBS + NUM_U256..3 + NUM_PUBS + NUM_U256 + NUM_HASH],
365 );
366
367 Self {
368 combined_method_id_constant,
369 deposit_constant,
370 payout_tx_blockhash_pk,
371 latest_blockhash_pk,
372 challenge_sending_watchtowers_pk,
373 bitvm_pks: (bitvm_pks_1, bitvm_pks_2, bitvm_pks_3),
374 }
375 }
376
377 pub fn to_flattened_vec(&self) -> Vec<Vec<[u8; 20]>> {
378 let mut flattened_wpks = Vec::new();
379
380 flattened_wpks.push(self.payout_tx_blockhash_pk.to_vec());
382 flattened_wpks.push(self.latest_blockhash_pk.to_vec());
383 flattened_wpks.push(self.challenge_sending_watchtowers_pk.to_vec());
384
385 for arr in &self.bitvm_pks.0 {
387 flattened_wpks.push(arr.to_vec());
388 }
389
390 for arr in &self.bitvm_pks.1 {
391 flattened_wpks.push(arr.to_vec());
392 }
393
394 for arr in &self.bitvm_pks.2 {
395 flattened_wpks.push(arr.to_vec());
396 }
397
398 flattened_wpks
399 }
400
401 pub fn vec_to_array<const N: usize>(vec: &[[u8; 20]]) -> [[u8; 20]; N] {
403 let mut result = [[255u8; 20]; N];
404 for (i, item) in vec.iter().enumerate() {
405 if i < N {
406 result[i] = *item;
407 }
408 }
409 result
410 }
411
412 pub fn vec_slice_to_nested_array<const INNER_LEN: usize, const OUTER_LEN: usize>(
414 slice: &[Vec<[u8; 20]>],
415 ) -> [[[u8; 20]; INNER_LEN]; OUTER_LEN] {
416 let mut result = [[[255u8; 20]; INNER_LEN]; OUTER_LEN];
417 for (i, vec) in slice.iter().enumerate() {
418 if i < OUTER_LEN {
419 result[i] = Self::vec_to_array::<INNER_LEN>(vec);
420 }
421 }
422 result
423 }
424
425 pub const fn number_of_assert_txs() -> usize {
426 36
427 }
428
429 pub const fn number_of_flattened_wpks() -> usize {
430 381
431 }
432
433 pub fn get_assert_scripts(
434 &self,
435 xonly_public_key: XOnlyPublicKey,
436 ) -> Vec<std::sync::Arc<dyn SpendableScript>> {
437 let mut scripts = Vec::new();
438 let first_script: Arc<dyn SpendableScript> = Arc::new(WinternitzCommit::new(
439 vec![
440 (self.challenge_sending_watchtowers_pk.to_vec(), 40),
441 (self.bitvm_pks.0[0].to_vec(), 64),
442 (self.bitvm_pks.1[NUM_U256 - 4].to_vec(), 64),
443 (self.bitvm_pks.1[NUM_U256 - 3].to_vec(), 64),
444 (self.bitvm_pks.1[NUM_U256 - 2].to_vec(), 64),
445 (self.bitvm_pks.1[NUM_U256 - 1].to_vec(), 64),
446 ],
447 xonly_public_key,
448 4,
449 ));
450 scripts.push(first_script);
451 let k1 = 5;
453 for i in (0..NUM_U256 - 4).step_by(k1) {
454 let last_idx = std::cmp::min(i + k1, NUM_U256);
455 let script: Arc<dyn SpendableScript> = Arc::new(WinternitzCommit::new(
456 self.bitvm_pks.1[i..last_idx]
457 .iter()
458 .map(|x| (x.to_vec(), 64))
459 .collect::<Vec<_>>(),
460 xonly_public_key,
461 4,
462 ));
463 scripts.push(script);
464 }
465 let k2 = 11;
466 for i in (0..NUM_HASH).step_by(k2) {
468 let last_idx = std::cmp::min(i + k2, NUM_HASH);
469 let script: Arc<dyn SpendableScript> = Arc::new(WinternitzCommit::new(
470 self.bitvm_pks.2[i..last_idx]
471 .iter()
472 .map(|x| (x.to_vec(), 32))
473 .collect::<Vec<_>>(),
474 xonly_public_key,
475 4,
476 ));
477 scripts.push(script);
478 }
479 scripts
480 }
481
482 pub fn get_assert_commit_data(
483 asserts: Assertions,
484 challenge_sending_watchtowers: &[u8; 20],
485 ) -> Vec<Vec<Vec<u8>>> {
486 let mut commit_data = Vec::new();
487 tracing::info!(
488 "Getting assert commit data, challenge_sending_watchtowers: {:?}",
489 challenge_sending_watchtowers
490 );
491 commit_data.push(vec![
492 challenge_sending_watchtowers.to_vec(),
493 asserts.0[0].to_vec(),
494 asserts.1[NUM_U256 - 4].to_vec(),
495 asserts.1[NUM_U256 - 3].to_vec(),
496 asserts.1[NUM_U256 - 2].to_vec(),
497 asserts.1[NUM_U256 - 1].to_vec(),
498 ]);
499 let k1 = 5;
500 for i in (0..NUM_U256 - 4).step_by(k1) {
501 let last_idx = std::cmp::min(i + k1, NUM_U256);
502 commit_data.push(
503 asserts.1[i..last_idx]
504 .iter()
505 .map(|x| x.to_vec())
506 .collect::<Vec<_>>(),
507 );
508 }
509 let k2 = 11;
510 for i in (0..NUM_HASH).step_by(k2) {
511 let last_idx = std::cmp::min(i + k2, NUM_HASH);
512 commit_data.push(
513 asserts.2[i..last_idx]
514 .iter()
515 .map(|x| x.to_vec())
516 .collect::<Vec<_>>(),
517 );
518 }
519 commit_data
520 }
521
522 pub fn get_latest_blockhash_derivation(
523 deposit_outpoint: bitcoin::OutPoint,
524 paramset: &'static ProtocolParamset,
525 ) -> WinternitzDerivationPath {
526 WinternitzDerivationPath::BitvmAssert(20 * 2, 1, 0, deposit_outpoint, paramset)
527 }
528
529 pub fn get_payout_tx_blockhash_derivation(
530 deposit_outpoint: bitcoin::OutPoint,
531 paramset: &'static ProtocolParamset,
532 ) -> WinternitzDerivationPath {
533 WinternitzDerivationPath::BitvmAssert(20 * 2, 0, 0, deposit_outpoint, paramset)
534 }
535
536 pub fn get_challenge_sending_watchtowers_derivation(
537 deposit_outpoint: bitcoin::OutPoint,
538 paramset: &'static ProtocolParamset,
539 ) -> WinternitzDerivationPath {
540 WinternitzDerivationPath::BitvmAssert(20 * 2, 2, 0, deposit_outpoint, paramset)
541 }
542
543 pub fn mini_assert_derivations_0(
544 deposit_outpoint: bitcoin::OutPoint,
545 paramset: &'static ProtocolParamset,
546 ) -> Vec<WinternitzDerivationPath> {
547 vec![
548 Self::get_challenge_sending_watchtowers_derivation(deposit_outpoint, paramset), WinternitzDerivationPath::BitvmAssert(32 * 2, 3, 0, deposit_outpoint, paramset), WinternitzDerivationPath::BitvmAssert(
552 32 * 2,
553 4,
554 (NUM_U256 - 4) as u32,
555 deposit_outpoint,
556 paramset,
557 ),
558 WinternitzDerivationPath::BitvmAssert(
560 32 * 2,
561 4,
562 (NUM_U256 - 3) as u32,
563 deposit_outpoint,
564 paramset,
565 ),
566 WinternitzDerivationPath::BitvmAssert(
568 32 * 2,
569 4,
570 (NUM_U256 - 2) as u32,
571 deposit_outpoint,
572 paramset,
573 ),
574 WinternitzDerivationPath::BitvmAssert(
576 32 * 2,
577 4,
578 (NUM_U256 - 1) as u32,
579 deposit_outpoint,
580 paramset,
581 ),
582 ]
583 }
584
585 pub fn get_assert_derivations(
586 mini_assert_idx: usize,
587 deposit_outpoint: bitcoin::OutPoint,
588 paramset: &'static ProtocolParamset,
589 ) -> Vec<WinternitzDerivationPath> {
590 if mini_assert_idx == 0 {
591 Self::mini_assert_derivations_0(deposit_outpoint, paramset)
592 } else if (1..=2).contains(&mini_assert_idx) {
593 let derivations: u32 = (mini_assert_idx as u32 - 1) * 5;
596
597 let mut derivations_vec = vec![];
598 for i in 0..5 {
599 if derivations + i < NUM_U256 as u32 - 4 {
600 derivations_vec.push(WinternitzDerivationPath::BitvmAssert(
601 32 * 2,
602 4,
603 derivations + i,
604 deposit_outpoint,
605 paramset,
606 ));
607 }
608 }
609 derivations_vec
610 } else {
611 let derivations: u32 = (mini_assert_idx as u32 - 3) * 11;
612 let mut derivations_vec = vec![];
613 for i in 0..11 {
614 if derivations + i < NUM_HASH as u32 {
615 derivations_vec.push(WinternitzDerivationPath::BitvmAssert(
616 16 * 2,
617 5,
618 derivations + i,
619 deposit_outpoint,
620 paramset,
621 ));
622 }
623 }
624 derivations_vec
625 }
626 }
627
628 pub fn get_assert_taproot_leaf_hashes(
629 &self,
630 xonly_public_key: XOnlyPublicKey,
631 ) -> Vec<bitcoin::TapNodeHash> {
632 let assert_scripts = self.get_assert_scripts(xonly_public_key);
633 assert_scripts
634 .into_iter()
635 .map(|script| {
636 let taproot_builder = taproot_builder_with_scripts(&[script.to_script_buf()]);
637 taproot_builder
638 .try_into_taptree()
639 .expect("taproot builder always builds a full taptree")
640 .root_hash()
641 })
642 .collect::<Vec<_>>()
643 }
644
645 pub fn get_g16_verifier_disprove_scripts(&self) -> Result<Vec<ScriptBuf>, BridgeError> {
646 if cfg!(debug_assertions) {
647 Ok(vec![ScriptBuf::from_bytes(vec![0x51])]) } else {
649 Ok(replace_disprove_scripts(self)?)
650 }
651 }
652}
653
654pub fn replace_disprove_scripts(
655 pks: &ClementineBitVMPublicKeys,
656) -> Result<Vec<ScriptBuf>, BridgeError> {
657 let start = Instant::now();
658 tracing::info!("Starting script replacement");
659
660 let cache = &BITVM_CACHE
661 .get_or_try_init(load_or_generate_bitvm_cache)?
662 .bitvm_cache;
663 let replacement_places = &cache.replacement_places;
664
665 let estimated_operations = calculate_replacement_operations(replacement_places);
667 tracing::info!(
668 "Estimated operations for script replacement: {}",
669 estimated_operations
670 );
671 if estimated_operations > MAX_SCRIPT_REPLACEMENT_OPERATIONS {
672 tracing::warn!(
673 "Rejecting script replacement: estimated {} operations exceeds limit of {}",
674 estimated_operations,
675 MAX_SCRIPT_REPLACEMENT_OPERATIONS
676 );
677 return Err(BridgeError::BitvmReplacementResourceExhaustion(
678 estimated_operations,
679 ));
680 }
681
682 tracing::info!("Estimated operations: {}", estimated_operations);
683
684 let mut result: Vec<Vec<u8>> = cache.disprove_scripts.clone();
685
686 for (digit, places) in replacement_places.payout_tx_blockhash_pk.iter().enumerate() {
687 for (script_idx, pos) in places.iter() {
688 result[*script_idx][*pos..*pos + 20]
689 .copy_from_slice(&pks.payout_tx_blockhash_pk[digit]);
690 }
691 }
692
693 for (digit, places) in replacement_places.latest_blockhash_pk.iter().enumerate() {
694 for (script_idx, pos) in places.iter() {
695 result[*script_idx][*pos..*pos + 20].copy_from_slice(&pks.latest_blockhash_pk[digit]);
696 }
697 }
698
699 for (digit, places) in replacement_places
700 .challenge_sending_watchtowers_pk
701 .iter()
702 .enumerate()
703 {
704 for (script_idx, pos) in places.iter() {
705 result[*script_idx][*pos..*pos + 20]
706 .copy_from_slice(&pks.challenge_sending_watchtowers_pk[digit]);
707 }
708 }
709
710 for (digit, places) in replacement_places.bitvm_pks.0.iter().enumerate() {
711 for (pk_idx, places) in places.iter().enumerate() {
712 for (script_idx, pos) in places.iter() {
713 result[*script_idx][*pos..*pos + 20]
714 .copy_from_slice(&pks.bitvm_pks.0[digit][pk_idx]);
715 }
716 }
717 }
718
719 for (digit, places) in replacement_places.bitvm_pks.1.iter().enumerate() {
720 for (pk_idx, places) in places.iter().enumerate() {
721 for (script_idx, pos) in places.iter() {
722 result[*script_idx][*pos..*pos + 20]
723 .copy_from_slice(&pks.bitvm_pks.1[digit][pk_idx]);
724 }
725 }
726 }
727
728 for (digit, places) in replacement_places.bitvm_pks.2.iter().enumerate() {
729 for (pk_idx, places) in places.iter().enumerate() {
730 for (script_idx, pos) in places.iter() {
731 result[*script_idx][*pos..*pos + 20]
732 .copy_from_slice(&pks.bitvm_pks.2[digit][pk_idx]);
733 }
734 }
735 }
736
737 let result: Vec<ScriptBuf> = result.into_iter().map(ScriptBuf::from_bytes).collect();
738
739 let elapsed = start.elapsed();
740 tracing::info!(
741 "Script replacement completed in {:?} with {} operations",
742 elapsed,
743 estimated_operations
744 );
745
746 Ok(result)
747}
748
749fn calculate_replacement_operations(replacement_places: &ClementineBitVMReplacementData) -> usize {
751 let mut total_operations = 0;
752
753 for places in &replacement_places.payout_tx_blockhash_pk {
755 total_operations += places.len();
756 }
757
758 for places in &replacement_places.latest_blockhash_pk {
760 total_operations += places.len();
761 }
762
763 for places in &replacement_places.challenge_sending_watchtowers_pk {
765 total_operations += places.len();
766 }
767
768 for digit_places in &replacement_places.bitvm_pks.0 {
770 for places in digit_places {
771 total_operations += places.len();
772 }
773 }
774
775 for digit_places in &replacement_places.bitvm_pks.1 {
776 for places in digit_places {
777 total_operations += places.len();
778 }
779 }
780
781 for digit_places in &replacement_places.bitvm_pks.2 {
782 for places in digit_places {
783 total_operations += places.len();
784 }
785 }
786
787 total_operations
788}
789
790#[cfg(test)]
791mod tests {
792 use bitcoin::secp256k1::rand::thread_rng;
793 use bitcoin::{hashes::Hash, Txid};
794
795 use super::*;
796 use crate::{actor::Actor, test::common::create_test_config_with_thread_name};
797
798 #[test]
799 fn test_to_flattened_vec() {
800 let bitvm_pks = ClementineBitVMPublicKeys::create_replacable();
801 let flattened_vec = bitvm_pks.to_flattened_vec();
802 let from_vec_to_array = ClementineBitVMPublicKeys::from_flattened_vec(&flattened_vec);
803 assert_eq!(bitvm_pks, from_vec_to_array);
804 }
805
806 #[tokio::test]
807 async fn test_vec_to_array_with_actor_values() {
808 let config = create_test_config_with_thread_name().await;
809
810 let sk = bitcoin::secp256k1::SecretKey::new(&mut thread_rng());
811 let signer = Actor::new(sk, config.protocol_paramset().network);
812 let deposit_outpoint = bitcoin::OutPoint {
813 txid: Txid::all_zeros(),
814 vout: 0,
815 };
816 let bitvm_pks = signer
817 .generate_bitvm_pks_for_deposit(deposit_outpoint, config.protocol_paramset())
818 .unwrap();
819
820 let flattened_vec = bitvm_pks.to_flattened_vec();
821 let from_vec_to_array = ClementineBitVMPublicKeys::from_flattened_vec(&flattened_vec);
822 assert_eq!(bitvm_pks, from_vec_to_array);
823 }
824
825 #[tokio::test]
826 #[ignore = "This test is too slow to run on every commit"]
827 async fn test_generate_fresh_data() {
828 let bitvm_cache = generate_fresh_data();
829 bitvm_cache
830 .save_to_file("bitvm_cache_new.bin")
831 .expect("Failed to save BitVM cache");
832 }
833
834 #[tokio::test]
835 async fn test_bitvm_cache_matches_generated_data() {
836 if cfg!(debug_assertions) {
837 return;
839 }
840 let bitvm_cache = BitvmCache::load_from_file(&get_cache_path()).unwrap();
841 let sha256_bitvm_cache = bitvm_cache.calculate_sha256().unwrap();
842 let generated_data = generate_fresh_data();
843 let fresh_data_sha256 = generated_data.calculate_sha256().unwrap();
844 assert_eq!(sha256_bitvm_cache, fresh_data_sha256);
845 }
846}