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