clementine_core/builder/
script.rs

1//! # Bitcoin Script Construction
2//!
3//! This module provides a collection of builders for creating various Bitcoin
4//! scripts utilized within the Clementine bridge. It defines a `SpendableScript`
5//! trait, implemented by specific script structures (e.g., `CheckSig`,
6//! `WinternitzCommit`, `TimelockScript`, `BaseDepositScript`) to standardize
7//! script generation and witness creation.
8//!
9//! Each script builder offers:
10//! - A constructor to initialize the script with its specific parameters.
11//! - A method to convert the script structure into a `bitcoin::ScriptBuf`.
12//! - A method to generate the corresponding `bitcoin::Witness` required to spend
13//!   an output locked with this script.
14//!
15//! The module also includes `ScriptKind`, an enum to differentiate between various
16//! spendable script types, facilitating dynamic dispatch and script management.
17//! Helper functions like `extract_winternitz_commits` are provided for parsing
18//! specific data committed using witnernitz keys from witness.
19
20#![allow(dead_code)]
21
22use crate::actor::WinternitzDerivationPath;
23use crate::config::protocol::ProtocolParamset;
24use crate::deposit::SecurityCouncil;
25use bitcoin::hashes::Hash;
26use bitcoin::opcodes::OP_TRUE;
27use bitcoin::{
28    opcodes::{all::*, OP_FALSE},
29    script::Builder,
30    ScriptBuf, XOnlyPublicKey,
31};
32use bitcoin::{taproot, Txid, Witness};
33use bitvm::signatures::winternitz::{Parameters, PublicKey, SecretKey};
34use clementine_primitives::EVMAddress;
35use eyre::{Context, Result};
36use std::any::Any;
37use std::fmt::Debug;
38
39#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
40pub enum SpendPath {
41    ScriptSpend(usize),
42    KeySpend,
43    Unknown,
44}
45
46/// Converts a minimal serialized u32 (trailing zeros removed) to full 4 byte representation
47fn from_minimal_to_u32_le_bytes(minimal: &[u8]) -> Result<[u8; 4]> {
48    if minimal.len() > 4 {
49        return Err(eyre::eyre!("u32 bytes length is greater than 4"));
50    }
51    let mut bytes = [0u8; 4];
52    bytes[..minimal.len()].copy_from_slice(minimal);
53    Ok(bytes)
54}
55
56/// Extracts the committed data from the witness.
57/// Note: The function is hardcoded for winternitz_log_d = 4 currently, will not work for others.
58pub fn extract_winternitz_commits(
59    witness: Witness,
60    wt_derive_paths: &[WinternitzDerivationPath],
61    paramset: &'static ProtocolParamset,
62) -> Result<Vec<Vec<u8>>> {
63    if paramset.winternitz_log_d != 4 {
64        return Err(eyre::eyre!("Only winternitz_log_d = 4 is supported"));
65    }
66    let mut commits: Vec<Vec<u8>> = Vec::new();
67    let mut cur_witness_iter = witness.into_iter().skip(1);
68
69    for wt_path in wt_derive_paths.iter().rev() {
70        let wt_params = wt_path.get_params();
71        let message_digits =
72            (wt_params.message_byte_len() * 8).div_ceil(paramset.winternitz_log_d) as usize;
73        let checksum_digits = wt_params.total_digit_len() as usize - message_digits;
74
75        let mut elements: Vec<&[u8]> = cur_witness_iter
76            .by_ref()
77            .skip(1)
78            .step_by(2)
79            .take(message_digits)
80            .collect();
81        elements.reverse();
82
83        // advance iterator to skip checksum digits at the end
84        cur_witness_iter.by_ref().nth(checksum_digits * 2 - 1);
85
86        commits.push(
87            elements
88                .chunks_exact(2)
89                .map(|digits| {
90                    let first_digit = u32::from_le_bytes(from_minimal_to_u32_le_bytes(digits[0])?);
91                    let second_digit = u32::from_le_bytes(from_minimal_to_u32_le_bytes(digits[1])?);
92
93                    let first_u8 = u8::try_from(first_digit)
94                        .wrap_err("Failed to convert first digit to u8")?;
95                    let second_u8 = u8::try_from(second_digit)
96                        .wrap_err("Failed to convert second digit to u8")?;
97
98                    Ok(second_u8 * (1 << paramset.winternitz_log_d) + first_u8)
99                })
100                .collect::<Result<Vec<_>>>()?,
101        );
102    }
103    commits.reverse();
104    Ok(commits)
105}
106
107/// Extracts the committed data from the witness.
108/// Note: The function is hardcoded for winternitz_log_d = 4 currently, will not work for others.
109pub fn extract_winternitz_commits_with_sigs(
110    witness: Witness,
111    wt_derive_paths: &[WinternitzDerivationPath],
112    paramset: &'static ProtocolParamset,
113) -> Result<Vec<Vec<Vec<u8>>>> {
114    if paramset.winternitz_log_d != 4 {
115        return Err(eyre::eyre!("Only winternitz_log_d = 4 is supported"));
116    }
117    // Structure: [commit][signature_sequence][element]
118    // - commit: one signed message
119    // - signature_sequence: alternating signature elements and signed characters, ending with a checksum
120    // - element: raw bytes of either a signature part, signed character, or checksum
121    let mut commits_with_sig: Vec<Vec<Vec<u8>>> = Vec::new();
122    let mut cur_witness_iter = witness.into_iter().skip(1);
123
124    for wt_path in wt_derive_paths.iter().rev() {
125        let wt_params = wt_path.get_params();
126        let message_digits =
127            (wt_params.message_byte_len() * 8).div_ceil(paramset.winternitz_log_d) as usize;
128        let checksum_digits = wt_params.total_digit_len() as usize - message_digits;
129
130        let elements: Vec<Vec<u8>> = cur_witness_iter
131            .by_ref()
132            .take((message_digits + checksum_digits) * 2)
133            .map(|x| x.to_vec())
134            .collect();
135
136        commits_with_sig.push(elements);
137    }
138
139    Ok(commits_with_sig)
140}
141
142/// A trait that marks all script types. Each script has a `generate_script_inputs` (eg. [`WinternitzCommit::generate_script_inputs`]) function that
143/// generates the witness for the script using various arguments. A `dyn SpendableScript` is cast into a concrete [`ScriptKind`] to
144/// generate a witness, the trait object can be used to generate the script_buf.
145///
146/// We store `Arc<dyn SpendableScript>`s inside a [`super::transaction::TxHandler`] input, and we cast them into a [`ScriptKind`] when signing.
147///
148/// When creating a new Script, make sure you add it to the [`ScriptKind`] enum and add a test for it below.
149/// Otherwise, it will not be spendable.
150pub trait SpendableScript: Send + Sync + 'static + std::any::Any {
151    fn as_any(&self) -> &dyn Any;
152
153    fn kind(&self) -> ScriptKind;
154
155    fn to_script_buf(&self) -> ScriptBuf;
156}
157
158impl Debug for dyn SpendableScript {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        write!(f, "SpendableScript")
161    }
162}
163
164/// Struct for scripts that do not conform to any other type of SpendableScripts
165#[derive(Debug, Clone)]
166pub struct OtherSpendable(ScriptBuf);
167
168impl From<ScriptBuf> for OtherSpendable {
169    fn from(script: ScriptBuf) -> Self {
170        Self(script)
171    }
172}
173
174impl SpendableScript for OtherSpendable {
175    fn as_any(&self) -> &dyn Any {
176        self
177    }
178
179    fn kind(&self) -> ScriptKind {
180        ScriptKind::Other(self)
181    }
182
183    fn to_script_buf(&self) -> ScriptBuf {
184        self.0.clone()
185    }
186}
187
188impl OtherSpendable {
189    fn as_script(&self) -> &ScriptBuf {
190        &self.0
191    }
192
193    fn generate_script_inputs(&self, witness: Witness) -> Witness {
194        witness
195    }
196
197    pub fn new(script: ScriptBuf) -> Self {
198        Self(script)
199    }
200}
201
202/// Struct for scripts that only includes a CHECKSIG
203#[derive(Debug, Clone)]
204pub struct CheckSig(pub(crate) XOnlyPublicKey);
205impl SpendableScript for CheckSig {
206    fn as_any(&self) -> &dyn Any {
207        self
208    }
209
210    fn kind(&self) -> ScriptKind {
211        ScriptKind::CheckSig(self)
212    }
213
214    fn to_script_buf(&self) -> ScriptBuf {
215        Builder::new()
216            .push_x_only_key(&self.0)
217            .push_opcode(OP_CHECKSIG)
218            .into_script()
219    }
220}
221
222impl CheckSig {
223    pub fn generate_script_inputs(&self, signature: &taproot::Signature) -> Witness {
224        Witness::from_slice(&[signature.serialize()])
225    }
226
227    pub fn new(xonly_pk: XOnlyPublicKey) -> Self {
228        Self(xonly_pk)
229    }
230}
231
232#[derive(Clone)]
233pub struct Multisig {
234    pubkeys: Vec<XOnlyPublicKey>,
235    threshold: u32,
236}
237
238impl SpendableScript for Multisig {
239    fn as_any(&self) -> &dyn Any {
240        self
241    }
242
243    fn kind(&self) -> ScriptKind {
244        ScriptKind::ManualSpend(self)
245    }
246
247    fn to_script_buf(&self) -> ScriptBuf {
248        let mut script_builder = Builder::new()
249            .push_x_only_key(&self.pubkeys[0])
250            .push_opcode(OP_CHECKSIG);
251        for pubkey in self.pubkeys.iter().skip(1) {
252            script_builder = script_builder.push_x_only_key(pubkey);
253            script_builder = script_builder.push_opcode(OP_CHECKSIGADD);
254        }
255        script_builder = script_builder.push_int(self.threshold as i64);
256        script_builder = script_builder.push_opcode(OP_NUMEQUAL);
257        script_builder.into_script()
258    }
259}
260
261impl Multisig {
262    pub fn new(pubkeys: Vec<XOnlyPublicKey>, threshold: u32) -> Self {
263        Self { pubkeys, threshold }
264    }
265
266    pub fn from_security_council(security_council: SecurityCouncil) -> Self {
267        Self {
268            pubkeys: security_council.pks,
269            threshold: security_council.threshold,
270        }
271    }
272
273    pub fn generate_script_inputs(
274        &self,
275        signatures: &[Option<taproot::Signature>],
276    ) -> eyre::Result<Witness> {
277        let mut witness = Witness::new();
278
279        for signature in signatures.iter().rev() {
280            match signature {
281                Some(sig) => witness.push(sig.serialize()),
282                None => witness.push([]),
283            }
284        }
285        Ok(witness)
286    }
287}
288
289/// Struct for scripts that commit to a message using Winternitz keys
290/// Contains the Winternitz PK, CheckSig PK, message length respectively
291/// can contain multiple different Winternitz public keys for different messages
292#[derive(Clone)]
293pub struct WinternitzCommit {
294    commitments: Vec<(PublicKey, u32)>,
295    pub(crate) checksig_pubkey: XOnlyPublicKey,
296    log_d: u32,
297}
298
299impl SpendableScript for WinternitzCommit {
300    fn as_any(&self) -> &dyn Any {
301        self
302    }
303
304    fn kind(&self) -> ScriptKind {
305        ScriptKind::WinternitzCommit(self)
306    }
307
308    fn to_script_buf(&self) -> ScriptBuf {
309        let mut total_script = ScriptBuf::new();
310        for (index, (pubkey, _size)) in self.commitments.iter().enumerate() {
311            let params = self.get_params(index);
312            let a = bitvm::signatures::signing_winternitz::WINTERNITZ_MESSAGE_VERIFIER
313                .checksig_verify_and_clear_stack(&params, pubkey);
314            total_script.extend(a.compile().instructions().map(|x| x.expect("just created")));
315        }
316
317        total_script.push_slice(self.checksig_pubkey.serialize());
318        total_script.push_opcode(OP_CHECKSIG);
319        total_script
320    }
321}
322
323impl WinternitzCommit {
324    pub fn get_params(&self, index: usize) -> Parameters {
325        Parameters::new(self.commitments[index].1, self.log_d)
326    }
327
328    pub fn generate_script_inputs(
329        &self,
330        commit_data: &[(Vec<u8>, SecretKey)],
331        signature: &taproot::Signature,
332    ) -> Witness {
333        let mut witness = Witness::new();
334        witness.push(signature.serialize());
335        for (index, (data, secret_key)) in commit_data.iter().enumerate().rev() {
336            #[cfg(debug_assertions)]
337            {
338                let pk = bitvm::signatures::winternitz::generate_public_key(
339                    &self.get_params(index),
340                    secret_key,
341                );
342                if pk != self.commitments[index].0 {
343                    tracing::error!(
344                        "Winternitz public key mismatch len: {}",
345                        self.commitments[index].1
346                    );
347                }
348            }
349            bitvm::signatures::signing_winternitz::WINTERNITZ_MESSAGE_VERIFIER
350                .sign(&self.get_params(index), secret_key, data)
351                .into_iter()
352                .for_each(|x| witness.push(x));
353        }
354        witness
355    }
356
357    /// commitments is a Vec of winternitz public key and message length tuple
358    pub fn new(
359        commitments: Vec<(PublicKey, u32)>,
360        checksig_pubkey: XOnlyPublicKey,
361        log_d: u32,
362    ) -> Self {
363        Self {
364            commitments,
365            checksig_pubkey,
366            log_d,
367        }
368    }
369}
370
371/// Struct for scripts that include a relative timelock (by block count) and optionally a CHECKSIG if a pubkey is provided.
372/// Generates a relative timelock script with a given [`XOnlyPublicKey`] that CHECKSIG checks the signature against.
373///
374/// ATTENTION: If you want to spend a UTXO using timelock script, the
375/// condition is that (`# in the script`) ≤ (`# in the sequence of the tx`)
376/// ≤ (`# of blocks mined after UTXO appears on the chain`). However, this is not mandatory.
377/// One can spend an output delayed for some number of blocks just by using the nSequence field
378/// of the input inside the transaction. For more, see:
379///
380/// - [BIP-0068](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki)
381/// - [BIP-0112](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki)
382#[derive(Debug, Clone)]
383pub struct TimelockScript(pub(crate) Option<XOnlyPublicKey>, u16);
384
385impl SpendableScript for TimelockScript {
386    fn as_any(&self) -> &dyn Any {
387        self
388    }
389
390    fn kind(&self) -> ScriptKind {
391        ScriptKind::TimelockScript(self)
392    }
393
394    fn to_script_buf(&self) -> ScriptBuf {
395        let script_builder = Builder::new()
396            .push_int(self.1 as i64)
397            .push_opcode(OP_CSV)
398            .push_opcode(OP_DROP);
399
400        if let Some(xonly_pk) = self.0 {
401            script_builder
402                .push_x_only_key(&xonly_pk)
403                .push_opcode(OP_CHECKSIG)
404        } else {
405            script_builder.push_opcode(OP_TRUE)
406        }
407        .into_script()
408    }
409}
410
411impl TimelockScript {
412    pub fn generate_script_inputs(&self, signature: Option<&taproot::Signature>) -> Witness {
413        match signature {
414            Some(sig) => Witness::from_slice(&[sig.serialize()]),
415            None => Witness::default(),
416        }
417    }
418
419    pub fn new(xonly_pk: Option<XOnlyPublicKey>, block_count: u16) -> Self {
420        Self(xonly_pk, block_count)
421    }
422}
423
424/// Struct for scripts that reveal a preimage of a OP_HASH160 and verify it against the given hash in the script.
425pub struct PreimageRevealScript(pub(crate) XOnlyPublicKey, [u8; 20]);
426
427impl SpendableScript for PreimageRevealScript {
428    fn as_any(&self) -> &dyn Any {
429        self
430    }
431
432    fn kind(&self) -> ScriptKind {
433        ScriptKind::PreimageRevealScript(self)
434    }
435
436    fn to_script_buf(&self) -> ScriptBuf {
437        Builder::new()
438            .push_opcode(OP_SIZE)
439            .push_int(20)
440            .push_opcode(OP_EQUALVERIFY)
441            .push_opcode(OP_HASH160)
442            .push_slice(self.1)
443            .push_opcode(OP_EQUALVERIFY)
444            .push_x_only_key(&self.0)
445            .push_opcode(OP_CHECKSIG)
446            .into_script()
447    }
448}
449
450impl PreimageRevealScript {
451    pub fn generate_script_inputs(
452        &self,
453        preimage: impl AsRef<[u8]>,
454        signature: &taproot::Signature,
455    ) -> Witness {
456        let mut witness = Witness::new();
457        #[cfg(debug_assertions)]
458        assert_eq!(
459            bitcoin::hashes::hash160::Hash::hash(preimage.as_ref()),
460            bitcoin::hashes::hash160::Hash::from_byte_array(self.1),
461            "Preimage does not match"
462        );
463
464        witness.push(signature.serialize());
465        witness.push(preimage.as_ref());
466        witness
467    }
468
469    pub fn new(xonly_pk: XOnlyPublicKey, hash: [u8; 20]) -> Self {
470        Self(xonly_pk, hash)
471    }
472}
473
474/// Struct for deposit script that commits Citrea address to be deposited into onchain.
475#[derive(Debug, Clone)]
476pub struct BaseDepositScript(pub(crate) XOnlyPublicKey, EVMAddress);
477
478impl SpendableScript for BaseDepositScript {
479    fn as_any(&self) -> &dyn Any {
480        self
481    }
482
483    fn kind(&self) -> ScriptKind {
484        ScriptKind::BaseDepositScript(self)
485    }
486
487    fn to_script_buf(&self) -> ScriptBuf {
488        let citrea: [u8; 6] = "citrea".as_bytes().try_into().expect("length == 6");
489
490        Builder::new()
491            .push_x_only_key(&self.0)
492            .push_opcode(OP_CHECKSIG)
493            .push_opcode(OP_FALSE)
494            .push_opcode(OP_IF)
495            .push_slice(citrea)
496            .push_slice(self.1 .0)
497            .push_opcode(OP_ENDIF)
498            .into_script()
499    }
500}
501
502impl BaseDepositScript {
503    pub fn generate_script_inputs(&self, signature: &taproot::Signature) -> Witness {
504        Witness::from_slice(&[signature.serialize()])
505    }
506
507    pub fn new(nofn_xonly_pk: XOnlyPublicKey, evm_address: EVMAddress) -> Self {
508        Self(nofn_xonly_pk, evm_address)
509    }
510}
511
512/// Struct for deposit script that replaces an old move tx with a replacement deposit (to update bridge design on chain)
513/// It commits to the old move txid inside the script.
514#[derive(Debug, Clone)]
515pub struct ReplacementDepositScript(pub(crate) XOnlyPublicKey, Txid);
516
517impl SpendableScript for ReplacementDepositScript {
518    fn as_any(&self) -> &dyn Any {
519        self
520    }
521
522    fn kind(&self) -> ScriptKind {
523        ScriptKind::ReplacementDepositScript(self)
524    }
525
526    fn to_script_buf(&self) -> ScriptBuf {
527        let citrea_replace: [u8; 13] = "citreaReplace".as_bytes().try_into().expect("length == 13");
528
529        Builder::new()
530            .push_x_only_key(&self.0)
531            .push_opcode(OP_CHECKSIG)
532            .push_opcode(OP_FALSE)
533            .push_opcode(OP_IF)
534            .push_slice(citrea_replace)
535            .push_slice(self.1.as_byte_array())
536            .push_opcode(OP_ENDIF)
537            .into_script()
538    }
539}
540
541impl ReplacementDepositScript {
542    pub fn generate_script_inputs(&self, signature: &taproot::Signature) -> Witness {
543        Witness::from_slice(&[signature.serialize()])
544    }
545
546    pub fn new(nofn_xonly_pk: XOnlyPublicKey, old_move_txid: Txid) -> Self {
547        Self(nofn_xonly_pk, old_move_txid)
548    }
549}
550
551#[derive(Clone)]
552pub enum ScriptKind<'a> {
553    CheckSig(&'a CheckSig),
554    WinternitzCommit(&'a WinternitzCommit),
555    TimelockScript(&'a TimelockScript),
556    PreimageRevealScript(&'a PreimageRevealScript),
557    BaseDepositScript(&'a BaseDepositScript),
558    ReplacementDepositScript(&'a ReplacementDepositScript),
559    Other(&'a OtherSpendable),
560    ManualSpend(&'a Multisig),
561}
562
563#[cfg(test)]
564fn get_script_from_arr<T: SpendableScript>(
565    arr: &Vec<Box<dyn SpendableScript>>,
566) -> Option<(usize, &T)> {
567    arr.iter()
568        .enumerate()
569        .find_map(|(i, x)| x.as_any().downcast_ref::<T>().map(|x| (i, x)))
570}
571#[cfg(test)]
572mod tests {
573    use super::*;
574    use crate::actor::{Actor, WinternitzDerivationPath};
575    use crate::bitvm_client::{self, UNSPENDABLE_XONLY_PUBKEY};
576    use crate::builder::address::create_taproot_address;
577    use crate::config::protocol::ProtocolParamsetName;
578    use crate::extended_bitcoin_rpc::ExtendedBitcoinRpc;
579    use bitcoin::hashes::Hash;
580    use bitcoin::secp256k1::rand::{self, Rng};
581    use bitcoin::secp256k1::{PublicKey, SecretKey};
582    use bitcoincore_rpc::RpcApi;
583    use clementine_primitives::RoundIndex;
584    use std::sync::Arc;
585
586    // Create some dummy values for testing.
587    // Note: These values are not cryptographically secure and are only used for tests.
588    fn dummy_xonly() -> XOnlyPublicKey {
589        // 32 bytes array filled with 0x03.
590        *bitvm_client::UNSPENDABLE_XONLY_PUBKEY
591    }
592
593    fn dummy_scriptbuf() -> ScriptBuf {
594        ScriptBuf::from_hex("51").expect("valid hex")
595    }
596
597    fn dummy_pubkey() -> PublicKey {
598        bitvm_client::UNSPENDABLE_XONLY_PUBKEY.public_key(bitcoin::secp256k1::Parity::Even)
599    }
600
601    fn dummy_params() -> Parameters {
602        Parameters::new(32, 4)
603    }
604
605    fn dummy_evm_address() -> EVMAddress {
606        // For testing purposes, we use a dummy 20-byte array.
607        EVMAddress([0u8; 20])
608    }
609
610    #[test]
611    fn test_dynamic_casting_extended() {
612        // Build a collection of SpendableScript implementations.
613        let scripts: Vec<Box<dyn SpendableScript>> = vec![
614            Box::new(OtherSpendable::new(dummy_scriptbuf())),
615            Box::new(CheckSig::new(dummy_xonly())),
616            Box::new(WinternitzCommit::new(
617                vec![(vec![[0u8; 20]; 32], 32)],
618                dummy_xonly(),
619                4,
620            )),
621            Box::new(TimelockScript::new(Some(dummy_xonly()), 10)),
622            Box::new(PreimageRevealScript::new(dummy_xonly(), [0; 20])),
623            Box::new(BaseDepositScript::new(dummy_xonly(), dummy_evm_address())),
624        ];
625
626        // helper closures that return Option<(usize, &T)> using get_script_from_arr.
627        let checksig = get_script_from_arr::<CheckSig>(&scripts);
628        let winternitz = get_script_from_arr::<WinternitzCommit>(&scripts);
629        let timelock = get_script_from_arr::<TimelockScript>(&scripts);
630        let preimage = get_script_from_arr::<PreimageRevealScript>(&scripts);
631        let deposit = get_script_from_arr::<BaseDepositScript>(&scripts);
632        let others = get_script_from_arr::<OtherSpendable>(&scripts);
633
634        assert!(checksig.is_some(), "CheckSig not found");
635        assert!(winternitz.is_some(), "WinternitzCommit not found");
636        assert!(timelock.is_some(), "TimelockScript not found");
637        assert!(preimage.is_some(), "PreimageRevealScript not found");
638        assert!(deposit.is_some(), "DepositScript not found");
639        assert!(others.is_some(), "OtherSpendable not found");
640
641        // Print found items.
642        let checksig_val = checksig.unwrap().1;
643        println!("CheckSig: {checksig_val:?}");
644        // println!("WinternitzCommit: {:?}", winternitz.unwrap().1);
645        let timelock_val = timelock.unwrap().1;
646        println!("TimelockScript: {timelock_val:?}");
647        // println!("PreimageRevealScript: {:?}", preimage.unwrap().1);
648        // println!("DepositScript: {:?}", deposit.unwrap().1);
649        let others_val = others.unwrap().1;
650        println!("OtherSpendable: {others_val:?}");
651    }
652
653    #[test]
654    fn test_dynamic_casting() {
655        use crate::bitvm_client;
656        let scripts: Vec<Box<dyn SpendableScript>> = vec![
657            Box::new(OtherSpendable(ScriptBuf::from_hex("51").expect(""))),
658            Box::new(CheckSig(*bitvm_client::UNSPENDABLE_XONLY_PUBKEY)),
659        ];
660
661        let otherspendable = scripts
662            .first()
663            .expect("")
664            .as_any()
665            .downcast_ref::<OtherSpendable>()
666            .expect("");
667
668        let checksig = get_script_from_arr::<CheckSig>(&scripts).expect("");
669        println!("{otherspendable:?}");
670        println!("{checksig:?}");
671    }
672
673    #[test]
674    fn test_scriptkind_completeness() {
675        let script_variants: Vec<(&str, Arc<dyn SpendableScript>)> = vec![
676            ("CheckSig", Arc::new(CheckSig::new(dummy_xonly()))),
677            (
678                "WinternitzCommit",
679                Arc::new(WinternitzCommit::new(
680                    vec![(vec![[0u8; 20]; 32], 32)],
681                    dummy_xonly(),
682                    4,
683                )),
684            ),
685            (
686                "TimelockScript",
687                Arc::new(TimelockScript::new(Some(dummy_xonly()), 15)),
688            ),
689            (
690                "PreimageRevealScript",
691                Arc::new(PreimageRevealScript::new(dummy_xonly(), [1; 20])),
692            ),
693            (
694                "BaseDepositScript",
695                Arc::new(BaseDepositScript::new(dummy_xonly(), dummy_evm_address())),
696            ),
697            (
698                "ReplacementDepositScript",
699                Arc::new(ReplacementDepositScript::new(
700                    dummy_xonly(),
701                    Txid::all_zeros(),
702                )),
703            ),
704            ("Other", Arc::new(OtherSpendable::new(dummy_scriptbuf()))),
705        ];
706
707        for (expected, script) in script_variants {
708            let kind = script.kind();
709            match (expected, kind) {
710                ("CheckSig", ScriptKind::CheckSig(_)) => (),
711                ("WinternitzCommit", ScriptKind::WinternitzCommit(_)) => (),
712                ("TimelockScript", ScriptKind::TimelockScript(_)) => (),
713                ("PreimageRevealScript", ScriptKind::PreimageRevealScript(_)) => (),
714                ("BaseDepositScript", ScriptKind::BaseDepositScript(_)) => (),
715                ("ReplacementDepositScript", ScriptKind::ReplacementDepositScript(_)) => (),
716                ("Other", ScriptKind::Other(_)) => (),
717                (s, _) => panic!("ScriptKind conversion not comprehensive for variant: {s}"),
718            }
719        }
720    }
721    // Tests for the spendability of all scripts
722    use crate::bitvm_client::SECP;
723    use crate::builder;
724    use crate::builder::transaction::input::SpendableTxIn;
725    use crate::builder::transaction::output::UnspentTxOut;
726    use crate::builder::transaction::{TxHandlerBuilder, DEFAULT_SEQUENCE};
727    use bitcoin::{Amount, OutPoint, Sequence, TxOut, Txid};
728    use clementine_primitives::TransactionType;
729
730    async fn create_taproot_test_tx(
731        rpc: &ExtendedBitcoinRpc,
732        scripts: Vec<Arc<dyn SpendableScript>>,
733        spend_path: SpendPath,
734        amount: Amount,
735    ) -> (TxHandlerBuilder, bitcoin::Address) {
736        let (address, taproot_spend_info) = builder::address::create_taproot_address(
737            &scripts
738                .iter()
739                .map(|s| s.to_script_buf())
740                .collect::<Vec<_>>(),
741            None,
742            bitcoin::Network::Regtest,
743        );
744
745        let outpoint = rpc.send_to_address(&address, amount).await.unwrap();
746        let sequence = if let SpendPath::ScriptSpend(idx) = spend_path {
747            if let Some(script) = scripts.get(idx) {
748                match script.kind() {
749                    ScriptKind::TimelockScript(&TimelockScript(_, seq)) => {
750                        Sequence::from_height(seq)
751                    }
752                    _ => DEFAULT_SEQUENCE,
753                }
754            } else {
755                DEFAULT_SEQUENCE
756            }
757        } else {
758            DEFAULT_SEQUENCE
759        };
760        let mut builder = TxHandlerBuilder::new(TransactionType::Dummy);
761        builder = builder.add_input(
762            crate::rpc::clementine::NormalSignatureKind::OperatorSighashDefault,
763            SpendableTxIn::new(
764                outpoint,
765                TxOut {
766                    value: amount,
767                    script_pubkey: address.script_pubkey(),
768                },
769                scripts.clone(),
770                Some(taproot_spend_info.clone()),
771            ),
772            spend_path,
773            sequence,
774        );
775
776        builder = builder.add_output(UnspentTxOut::new(
777            TxOut {
778                value: amount - Amount::from_sat(5000), // Subtract fee
779                script_pubkey: address.script_pubkey(),
780            },
781            scripts,
782            Some(taproot_spend_info),
783        ));
784
785        (builder, address)
786    }
787
788    use crate::test::common::*;
789
790    #[tokio::test]
791
792    async fn test_checksig_spendable() {
793        let mut config = create_test_config_with_thread_name().await;
794        let regtest = create_regtest_rpc(&mut config).await;
795        let rpc = regtest.rpc().clone();
796
797        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
798        let xonly_pk = kp.public_key().x_only_public_key().0;
799
800        let scripts: Vec<Arc<dyn SpendableScript>> = vec![Arc::new(CheckSig::new(xonly_pk))];
801        let (builder, _) = create_taproot_test_tx(
802            &rpc,
803            scripts,
804            SpendPath::ScriptSpend(0),
805            Amount::from_sat(10_000),
806        )
807        .await;
808        let mut tx = builder.finalize();
809
810        // Should be able to sign with the key
811        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
812
813        signer
814            .tx_sign_and_fill_sigs(&mut tx, &[], None)
815            .expect("should be able to sign checksig");
816        let tx = tx
817            .promote()
818            .expect("the transaction should be fully signed");
819
820        rpc.send_raw_transaction(tx.get_cached_tx())
821            .await
822            .expect("bitcoin RPC did not accept transaction");
823    }
824
825    #[tokio::test]
826    async fn test_winternitz_commit_spendable() {
827        let mut config = create_test_config_with_thread_name().await;
828        let regtest = create_regtest_rpc(&mut config).await;
829        let rpc = regtest.rpc();
830
831        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
832        let xonly_pk = kp.public_key().x_only_public_key().0;
833
834        let deposit_outpoint = OutPoint {
835            txid: Txid::all_zeros(),
836            vout: 0,
837        };
838
839        let derivation = WinternitzDerivationPath::BitvmAssert(
840            64,
841            3,
842            0,
843            deposit_outpoint,
844            ProtocolParamsetName::Regtest.into(),
845        );
846
847        let derivation2 = WinternitzDerivationPath::BitvmAssert(
848            64,
849            2,
850            0,
851            deposit_outpoint,
852            ProtocolParamsetName::Regtest.into(),
853        );
854
855        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
856
857        let script: Arc<dyn SpendableScript> = Arc::new(WinternitzCommit::new(
858            vec![
859                (
860                    signer
861                        .derive_winternitz_pk(derivation.clone())
862                        .expect("failed to derive Winternitz public key"),
863                    64,
864                ),
865                (
866                    signer
867                        .derive_winternitz_pk(derivation2.clone())
868                        .expect("failed to derive Winternitz public key"),
869                    64,
870                ),
871            ],
872            xonly_pk,
873            4,
874        ));
875
876        let scripts = vec![script];
877        let (builder, _) = create_taproot_test_tx(
878            rpc,
879            scripts,
880            SpendPath::ScriptSpend(0),
881            Amount::from_sat(10_000),
882        )
883        .await;
884        let mut tx = builder.finalize();
885
886        signer
887            .tx_sign_winternitz(
888                &mut tx,
889                &[
890                    (vec![0; 32], derivation.clone()),
891                    (vec![0; 32], derivation2.clone()),
892                ],
893            )
894            .expect("failed to partially sign commitments");
895
896        let tx = tx
897            .promote()
898            .expect("the transaction should be fully signed");
899
900        rpc.send_raw_transaction(tx.get_cached_tx())
901            .await
902            .expect("bitcoin RPC did not accept transaction");
903    }
904
905    #[tokio::test]
906    async fn test_timelock_script_spendable() {
907        let mut config = create_test_config_with_thread_name().await;
908        let regtest = create_regtest_rpc(&mut config).await;
909        let rpc = regtest.rpc();
910
911        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
912        let xonly_pk = kp.public_key().x_only_public_key().0;
913
914        let scripts: Vec<Arc<dyn SpendableScript>> =
915            vec![Arc::new(TimelockScript::new(Some(xonly_pk), 15))];
916        let (builder, _) = create_taproot_test_tx(
917            rpc,
918            scripts,
919            SpendPath::ScriptSpend(0),
920            Amount::from_sat(10_000),
921        )
922        .await;
923
924        let mut tx = builder.finalize();
925
926        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
927
928        signer
929            .tx_sign_and_fill_sigs(&mut tx, &[], None)
930            .expect("should be able to sign timelock");
931
932        rpc.send_raw_transaction(tx.get_cached_tx())
933            .await
934            .expect_err("should not pass without 15 blocks");
935
936        rpc.mine_blocks(15).await.expect("failed to mine blocks");
937
938        rpc.send_raw_transaction(tx.get_cached_tx())
939            .await
940            .expect("should pass after 15 blocks");
941    }
942
943    #[tokio::test]
944    async fn test_preimage_reveal_script_spendable() {
945        let mut config = create_test_config_with_thread_name().await;
946        let regtest = create_regtest_rpc(&mut config).await;
947        let rpc = regtest.rpc().clone();
948        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
949        let xonly_pk = kp.public_key().x_only_public_key().0;
950
951        let preimage = [1; 20];
952        let hash = bitcoin::hashes::hash160::Hash::hash(&preimage);
953        let script: Arc<dyn SpendableScript> =
954            Arc::new(PreimageRevealScript::new(xonly_pk, hash.to_byte_array()));
955        let scripts = vec![script];
956        let (builder, _) = create_taproot_test_tx(
957            &rpc,
958            scripts,
959            SpendPath::ScriptSpend(0),
960            Amount::from_sat(10_000),
961        )
962        .await;
963        let mut tx = builder.finalize();
964
965        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
966
967        signer
968            .tx_sign_preimage(&mut tx, preimage)
969            .expect("failed to sign preimage reveal");
970
971        let final_tx = tx
972            .promote()
973            .expect("the transaction should be fully signed");
974
975        rpc.send_raw_transaction(final_tx.get_cached_tx())
976            .await
977            .expect("bitcoin RPC did not accept transaction");
978
979        // preimage lengths other than 20 bytes should fail
980
981        let preimage = [1; 21];
982        let hash = bitcoin::hashes::hash160::Hash::hash(&preimage);
983        let script: Arc<dyn SpendableScript> =
984            Arc::new(PreimageRevealScript::new(xonly_pk, hash.to_byte_array()));
985        let scripts = vec![script];
986        let (builder, _) = create_taproot_test_tx(
987            &rpc,
988            scripts,
989            SpendPath::ScriptSpend(0),
990            Amount::from_sat(10_000),
991        )
992        .await;
993        let mut tx = builder.finalize();
994
995        signer
996            .tx_sign_preimage(&mut tx, preimage)
997            .expect("failed to sign preimage reveal");
998
999        let final_tx = tx
1000            .promote()
1001            .expect("the transaction should be fully signed");
1002
1003        assert!(rpc
1004            .send_raw_transaction(final_tx.get_cached_tx())
1005            .await
1006            .is_err());
1007    }
1008
1009    #[tokio::test]
1010    async fn test_base_deposit_script_spendable() {
1011        let mut config = create_test_config_with_thread_name().await;
1012        let regtest = create_regtest_rpc(&mut config).await;
1013        let rpc = regtest.rpc().clone();
1014
1015        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
1016        let xonly_pk = kp.public_key().x_only_public_key().0;
1017
1018        let script: Arc<dyn SpendableScript> =
1019            Arc::new(BaseDepositScript::new(xonly_pk, EVMAddress([2; 20])));
1020        let scripts = vec![script];
1021        let (builder, _) = create_taproot_test_tx(
1022            &rpc,
1023            scripts,
1024            SpendPath::ScriptSpend(0),
1025            Amount::from_sat(10_000),
1026        )
1027        .await;
1028        let mut tx = builder.finalize();
1029
1030        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
1031
1032        signer
1033            .tx_sign_and_fill_sigs(&mut tx, &[], None)
1034            .expect("should be able to sign base deposit");
1035
1036        rpc.send_raw_transaction(tx.get_cached_tx())
1037            .await
1038            .expect("bitcoin RPC did not accept transaction");
1039    }
1040
1041    #[tokio::test]
1042    async fn test_replacement_deposit_script_spendable() {
1043        let mut config = create_test_config_with_thread_name().await;
1044        let regtest = create_regtest_rpc(&mut config).await;
1045        let rpc = regtest.rpc().clone();
1046
1047        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
1048        let xonly_pk = kp.public_key().x_only_public_key().0;
1049
1050        let script: Arc<dyn SpendableScript> =
1051            Arc::new(ReplacementDepositScript::new(xonly_pk, Txid::all_zeros()));
1052        let scripts = vec![script];
1053        let (builder, _) = create_taproot_test_tx(
1054            &rpc,
1055            scripts,
1056            SpendPath::ScriptSpend(0),
1057            Amount::from_sat(10_000),
1058        )
1059        .await;
1060        let mut tx = builder.finalize();
1061
1062        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
1063
1064        signer
1065            .tx_sign_and_fill_sigs(&mut tx, &[], None)
1066            .expect("should be able to sign replacement deposit");
1067
1068        rpc.send_raw_transaction(tx.get_cached_tx())
1069            .await
1070            .expect("bitcoin RPC did not accept transaction");
1071    }
1072
1073    #[tokio::test]
1074    async fn test_extract_commit_data() {
1075        let config = create_test_config_with_thread_name().await;
1076        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
1077
1078        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
1079
1080        let kickoff =
1081            WinternitzDerivationPath::Kickoff(RoundIndex::Round(0), 0, config.protocol_paramset());
1082        let bitvm_assert = WinternitzDerivationPath::BitvmAssert(
1083            64,
1084            3,
1085            0,
1086            OutPoint {
1087                txid: Txid::all_zeros(),
1088                vout: 0,
1089            },
1090            config.protocol_paramset(),
1091        );
1092        let commit_script = WinternitzCommit::new(
1093            vec![
1094                (
1095                    signer
1096                        .derive_winternitz_pk(kickoff.clone())
1097                        .expect("failed to derive Winternitz public key"),
1098                    40,
1099                ),
1100                (
1101                    signer
1102                        .derive_winternitz_pk(bitvm_assert.clone())
1103                        .expect("failed to derive Winternitz public key"),
1104                    64,
1105                ),
1106            ],
1107            signer.xonly_public_key,
1108            4,
1109        );
1110        let signature = taproot::Signature::from_slice(&[1u8; 64]).expect("valid signature");
1111        let kickoff_blockhash: Vec<u8> = (0..20u8).collect();
1112        let assert_commit_data: Vec<u8> = (0..32u8).collect();
1113        let witness = commit_script.generate_script_inputs(
1114            &[
1115                (
1116                    kickoff_blockhash.clone(),
1117                    signer.get_derived_winternitz_sk(kickoff.clone()).unwrap(),
1118                ),
1119                (
1120                    assert_commit_data.clone(),
1121                    signer
1122                        .get_derived_winternitz_sk(bitvm_assert.clone())
1123                        .unwrap(),
1124                ),
1125            ],
1126            &signature,
1127        );
1128        let extracted = extract_winternitz_commits(
1129            witness,
1130            &[kickoff, bitvm_assert],
1131            config.protocol_paramset(),
1132        )
1133        .unwrap();
1134        tracing::info!("{:?}", extracted);
1135        assert_eq!(extracted[0], kickoff_blockhash);
1136        assert_eq!(extracted[1], assert_commit_data);
1137    }
1138
1139    #[tokio::test]
1140    async fn test_multisig_matches_descriptor() {
1141        let mut config = create_test_config_with_thread_name().await;
1142        let regtest = create_regtest_rpc(&mut config).await;
1143        let rpc = regtest.rpc().clone();
1144
1145        // select a random number of public keys
1146        let num_pks = rand::thread_rng().gen_range(1..=10);
1147        let threshold = rand::thread_rng().gen_range(1..=num_pks);
1148
1149        let mut pks = Vec::new();
1150        for _ in 0..num_pks {
1151            let secret_key = SecretKey::new(&mut rand::thread_rng());
1152            let kp = bitcoin::secp256k1::Keypair::from_secret_key(&*SECP, &secret_key);
1153            pks.push(kp.public_key().x_only_public_key().0);
1154        }
1155
1156        let unspendable_xonly_pk_str = (*UNSPENDABLE_XONLY_PUBKEY).to_string();
1157        let descriptor = format!(
1158            "tr({},multi_a({},{}))",
1159            unspendable_xonly_pk_str,
1160            threshold,
1161            pks.iter()
1162                .map(|pk| pk.to_string())
1163                .collect::<Vec<String>>()
1164                .join(",")
1165        );
1166
1167        let descriptor_info = rpc.get_descriptor_info(&descriptor).await.expect("");
1168
1169        let descriptor = descriptor_info.descriptor;
1170
1171        let addresses = rpc.derive_addresses(&descriptor, None).await.expect("");
1172
1173        tracing::info!("{:?}", addresses);
1174
1175        let multisig_address = addresses[0].clone().assume_checked();
1176
1177        let multisig = Multisig::new(pks, threshold);
1178
1179        let (addr, _) = create_taproot_address(
1180            &[multisig.to_script_buf()],
1181            None,
1182            config.protocol_paramset().network,
1183        );
1184
1185        // println!("addr: {:?}", addr);
1186        // println!("multisig_address: {:?}", multisig_address);
1187
1188        assert_eq!(addr, multisig_address);
1189    }
1190}