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 crate::EVMAddress;
26use bitcoin::hashes::Hash;
27use bitcoin::opcodes::OP_TRUE;
28use bitcoin::{
29    opcodes::{all::*, OP_FALSE},
30    script::Builder,
31    ScriptBuf, XOnlyPublicKey,
32};
33use bitcoin::{taproot, Txid, Witness};
34use bitvm::signatures::winternitz::{Parameters, PublicKey, SecretKey};
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 crate::operator::RoundIndex;
580    use bitcoin::hashes::Hash;
581    use bitcoin::secp256k1::rand::{self, Rng};
582    use bitcoin::secp256k1::{PublicKey, SecretKey};
583    use bitcoincore_rpc::RpcApi;
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::{TransactionType, TxHandlerBuilder, DEFAULT_SEQUENCE};
727    use bitcoin::{Amount, OutPoint, Sequence, TxOut, Txid};
728
729    async fn create_taproot_test_tx(
730        rpc: &ExtendedBitcoinRpc,
731        scripts: Vec<Arc<dyn SpendableScript>>,
732        spend_path: SpendPath,
733        amount: Amount,
734    ) -> (TxHandlerBuilder, bitcoin::Address) {
735        let (address, taproot_spend_info) = builder::address::create_taproot_address(
736            &scripts
737                .iter()
738                .map(|s| s.to_script_buf())
739                .collect::<Vec<_>>(),
740            None,
741            bitcoin::Network::Regtest,
742        );
743
744        let outpoint = rpc.send_to_address(&address, amount).await.unwrap();
745        let sequence = if let SpendPath::ScriptSpend(idx) = spend_path {
746            if let Some(script) = scripts.get(idx) {
747                match script.kind() {
748                    ScriptKind::TimelockScript(&TimelockScript(_, seq)) => {
749                        Sequence::from_height(seq)
750                    }
751                    _ => DEFAULT_SEQUENCE,
752                }
753            } else {
754                DEFAULT_SEQUENCE
755            }
756        } else {
757            DEFAULT_SEQUENCE
758        };
759        let mut builder = TxHandlerBuilder::new(TransactionType::Dummy);
760        builder = builder.add_input(
761            crate::rpc::clementine::NormalSignatureKind::OperatorSighashDefault,
762            SpendableTxIn::new(
763                outpoint,
764                TxOut {
765                    value: amount,
766                    script_pubkey: address.script_pubkey(),
767                },
768                scripts.clone(),
769                Some(taproot_spend_info.clone()),
770            ),
771            spend_path,
772            sequence,
773        );
774
775        builder = builder.add_output(UnspentTxOut::new(
776            TxOut {
777                value: amount - Amount::from_sat(5000), // Subtract fee
778                script_pubkey: address.script_pubkey(),
779            },
780            scripts,
781            Some(taproot_spend_info),
782        ));
783
784        (builder, address)
785    }
786
787    use crate::test::common::*;
788
789    #[tokio::test]
790
791    async fn test_checksig_spendable() {
792        let mut config = create_test_config_with_thread_name().await;
793        let regtest = create_regtest_rpc(&mut config).await;
794        let rpc = regtest.rpc().clone();
795
796        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
797        let xonly_pk = kp.public_key().x_only_public_key().0;
798
799        let scripts: Vec<Arc<dyn SpendableScript>> = vec![Arc::new(CheckSig::new(xonly_pk))];
800        let (builder, _) = create_taproot_test_tx(
801            &rpc,
802            scripts,
803            SpendPath::ScriptSpend(0),
804            Amount::from_sat(10_000),
805        )
806        .await;
807        let mut tx = builder.finalize();
808
809        // Should be able to sign with the key
810        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
811
812        signer
813            .tx_sign_and_fill_sigs(&mut tx, &[], None)
814            .expect("should be able to sign checksig");
815        let tx = tx
816            .promote()
817            .expect("the transaction should be fully signed");
818
819        rpc.send_raw_transaction(tx.get_cached_tx())
820            .await
821            .expect("bitcoin RPC did not accept transaction");
822    }
823
824    #[tokio::test]
825    async fn test_winternitz_commit_spendable() {
826        let mut config = create_test_config_with_thread_name().await;
827        let regtest = create_regtest_rpc(&mut config).await;
828        let rpc = regtest.rpc();
829
830        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
831        let xonly_pk = kp.public_key().x_only_public_key().0;
832
833        let deposit_outpoint = OutPoint {
834            txid: Txid::all_zeros(),
835            vout: 0,
836        };
837
838        let derivation = WinternitzDerivationPath::BitvmAssert(
839            64,
840            3,
841            0,
842            deposit_outpoint,
843            ProtocolParamsetName::Regtest.into(),
844        );
845
846        let derivation2 = WinternitzDerivationPath::BitvmAssert(
847            64,
848            2,
849            0,
850            deposit_outpoint,
851            ProtocolParamsetName::Regtest.into(),
852        );
853
854        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
855
856        let script: Arc<dyn SpendableScript> = Arc::new(WinternitzCommit::new(
857            vec![
858                (
859                    signer
860                        .derive_winternitz_pk(derivation.clone())
861                        .expect("failed to derive Winternitz public key"),
862                    64,
863                ),
864                (
865                    signer
866                        .derive_winternitz_pk(derivation2.clone())
867                        .expect("failed to derive Winternitz public key"),
868                    64,
869                ),
870            ],
871            xonly_pk,
872            4,
873        ));
874
875        let scripts = vec![script];
876        let (builder, _) = create_taproot_test_tx(
877            rpc,
878            scripts,
879            SpendPath::ScriptSpend(0),
880            Amount::from_sat(10_000),
881        )
882        .await;
883        let mut tx = builder.finalize();
884
885        signer
886            .tx_sign_winternitz(
887                &mut tx,
888                &[
889                    (vec![0; 32], derivation.clone()),
890                    (vec![0; 32], derivation2.clone()),
891                ],
892            )
893            .expect("failed to partially sign commitments");
894
895        let tx = tx
896            .promote()
897            .expect("the transaction should be fully signed");
898
899        rpc.send_raw_transaction(tx.get_cached_tx())
900            .await
901            .expect("bitcoin RPC did not accept transaction");
902    }
903
904    #[tokio::test]
905    async fn test_timelock_script_spendable() {
906        let mut config = create_test_config_with_thread_name().await;
907        let regtest = create_regtest_rpc(&mut config).await;
908        let rpc = regtest.rpc();
909
910        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
911        let xonly_pk = kp.public_key().x_only_public_key().0;
912
913        let scripts: Vec<Arc<dyn SpendableScript>> =
914            vec![Arc::new(TimelockScript::new(Some(xonly_pk), 15))];
915        let (builder, _) = create_taproot_test_tx(
916            rpc,
917            scripts,
918            SpendPath::ScriptSpend(0),
919            Amount::from_sat(10_000),
920        )
921        .await;
922
923        let mut tx = builder.finalize();
924
925        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
926
927        signer
928            .tx_sign_and_fill_sigs(&mut tx, &[], None)
929            .expect("should be able to sign timelock");
930
931        rpc.send_raw_transaction(tx.get_cached_tx())
932            .await
933            .expect_err("should not pass without 15 blocks");
934
935        rpc.mine_blocks(15).await.expect("failed to mine blocks");
936
937        rpc.send_raw_transaction(tx.get_cached_tx())
938            .await
939            .expect("should pass after 15 blocks");
940    }
941
942    #[tokio::test]
943    async fn test_preimage_reveal_script_spendable() {
944        let mut config = create_test_config_with_thread_name().await;
945        let regtest = create_regtest_rpc(&mut config).await;
946        let rpc = regtest.rpc().clone();
947        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
948        let xonly_pk = kp.public_key().x_only_public_key().0;
949
950        let preimage = [1; 20];
951        let hash = bitcoin::hashes::hash160::Hash::hash(&preimage);
952        let script: Arc<dyn SpendableScript> =
953            Arc::new(PreimageRevealScript::new(xonly_pk, hash.to_byte_array()));
954        let scripts = vec![script];
955        let (builder, _) = create_taproot_test_tx(
956            &rpc,
957            scripts,
958            SpendPath::ScriptSpend(0),
959            Amount::from_sat(10_000),
960        )
961        .await;
962        let mut tx = builder.finalize();
963
964        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
965
966        signer
967            .tx_sign_preimage(&mut tx, preimage)
968            .expect("failed to sign preimage reveal");
969
970        let final_tx = tx
971            .promote()
972            .expect("the transaction should be fully signed");
973
974        rpc.send_raw_transaction(final_tx.get_cached_tx())
975            .await
976            .expect("bitcoin RPC did not accept transaction");
977
978        // preimage lengths other than 20 bytes should fail
979
980        let preimage = [1; 21];
981        let hash = bitcoin::hashes::hash160::Hash::hash(&preimage);
982        let script: Arc<dyn SpendableScript> =
983            Arc::new(PreimageRevealScript::new(xonly_pk, hash.to_byte_array()));
984        let scripts = vec![script];
985        let (builder, _) = create_taproot_test_tx(
986            &rpc,
987            scripts,
988            SpendPath::ScriptSpend(0),
989            Amount::from_sat(10_000),
990        )
991        .await;
992        let mut tx = builder.finalize();
993
994        signer
995            .tx_sign_preimage(&mut tx, preimage)
996            .expect("failed to sign preimage reveal");
997
998        let final_tx = tx
999            .promote()
1000            .expect("the transaction should be fully signed");
1001
1002        assert!(rpc
1003            .send_raw_transaction(final_tx.get_cached_tx())
1004            .await
1005            .is_err());
1006    }
1007
1008    #[tokio::test]
1009    async fn test_base_deposit_script_spendable() {
1010        let mut config = create_test_config_with_thread_name().await;
1011        let regtest = create_regtest_rpc(&mut config).await;
1012        let rpc = regtest.rpc().clone();
1013
1014        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
1015        let xonly_pk = kp.public_key().x_only_public_key().0;
1016
1017        let script: Arc<dyn SpendableScript> =
1018            Arc::new(BaseDepositScript::new(xonly_pk, EVMAddress([2; 20])));
1019        let scripts = vec![script];
1020        let (builder, _) = create_taproot_test_tx(
1021            &rpc,
1022            scripts,
1023            SpendPath::ScriptSpend(0),
1024            Amount::from_sat(10_000),
1025        )
1026        .await;
1027        let mut tx = builder.finalize();
1028
1029        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
1030
1031        signer
1032            .tx_sign_and_fill_sigs(&mut tx, &[], None)
1033            .expect("should be able to sign base deposit");
1034
1035        rpc.send_raw_transaction(tx.get_cached_tx())
1036            .await
1037            .expect("bitcoin RPC did not accept transaction");
1038    }
1039
1040    #[tokio::test]
1041    async fn test_replacement_deposit_script_spendable() {
1042        let mut config = create_test_config_with_thread_name().await;
1043        let regtest = create_regtest_rpc(&mut config).await;
1044        let rpc = regtest.rpc().clone();
1045
1046        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
1047        let xonly_pk = kp.public_key().x_only_public_key().0;
1048
1049        let script: Arc<dyn SpendableScript> =
1050            Arc::new(ReplacementDepositScript::new(xonly_pk, Txid::all_zeros()));
1051        let scripts = vec![script];
1052        let (builder, _) = create_taproot_test_tx(
1053            &rpc,
1054            scripts,
1055            SpendPath::ScriptSpend(0),
1056            Amount::from_sat(10_000),
1057        )
1058        .await;
1059        let mut tx = builder.finalize();
1060
1061        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
1062
1063        signer
1064            .tx_sign_and_fill_sigs(&mut tx, &[], None)
1065            .expect("should be able to sign replacement deposit");
1066
1067        rpc.send_raw_transaction(tx.get_cached_tx())
1068            .await
1069            .expect("bitcoin RPC did not accept transaction");
1070    }
1071
1072    #[tokio::test]
1073    async fn test_extract_commit_data() {
1074        let config = create_test_config_with_thread_name().await;
1075        let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng());
1076
1077        let signer = Actor::new(kp.secret_key(), bitcoin::Network::Regtest);
1078
1079        let kickoff =
1080            WinternitzDerivationPath::Kickoff(RoundIndex::Round(0), 0, config.protocol_paramset());
1081        let bitvm_assert = WinternitzDerivationPath::BitvmAssert(
1082            64,
1083            3,
1084            0,
1085            OutPoint {
1086                txid: Txid::all_zeros(),
1087                vout: 0,
1088            },
1089            config.protocol_paramset(),
1090        );
1091        let commit_script = WinternitzCommit::new(
1092            vec![
1093                (
1094                    signer
1095                        .derive_winternitz_pk(kickoff.clone())
1096                        .expect("failed to derive Winternitz public key"),
1097                    40,
1098                ),
1099                (
1100                    signer
1101                        .derive_winternitz_pk(bitvm_assert.clone())
1102                        .expect("failed to derive Winternitz public key"),
1103                    64,
1104                ),
1105            ],
1106            signer.xonly_public_key,
1107            4,
1108        );
1109        let signature = taproot::Signature::from_slice(&[1u8; 64]).expect("valid signature");
1110        let kickoff_blockhash: Vec<u8> = (0..20u8).collect();
1111        let assert_commit_data: Vec<u8> = (0..32u8).collect();
1112        let witness = commit_script.generate_script_inputs(
1113            &[
1114                (
1115                    kickoff_blockhash.clone(),
1116                    signer.get_derived_winternitz_sk(kickoff.clone()).unwrap(),
1117                ),
1118                (
1119                    assert_commit_data.clone(),
1120                    signer
1121                        .get_derived_winternitz_sk(bitvm_assert.clone())
1122                        .unwrap(),
1123                ),
1124            ],
1125            &signature,
1126        );
1127        let extracted = extract_winternitz_commits(
1128            witness,
1129            &[kickoff, bitvm_assert],
1130            config.protocol_paramset(),
1131        )
1132        .unwrap();
1133        tracing::info!("{:?}", extracted);
1134        assert_eq!(extracted[0], kickoff_blockhash);
1135        assert_eq!(extracted[1], assert_commit_data);
1136    }
1137
1138    #[tokio::test]
1139    async fn test_multisig_matches_descriptor() {
1140        let mut config = create_test_config_with_thread_name().await;
1141        let regtest = create_regtest_rpc(&mut config).await;
1142        let rpc = regtest.rpc().clone();
1143
1144        // select a random number of public keys
1145        let num_pks = rand::thread_rng().gen_range(1..=10);
1146        let threshold = rand::thread_rng().gen_range(1..=num_pks);
1147
1148        let mut pks = Vec::new();
1149        for _ in 0..num_pks {
1150            let secret_key = SecretKey::new(&mut rand::thread_rng());
1151            let kp = bitcoin::secp256k1::Keypair::from_secret_key(&*SECP, &secret_key);
1152            pks.push(kp.public_key().x_only_public_key().0);
1153        }
1154
1155        let unspendable_xonly_pk_str = (*UNSPENDABLE_XONLY_PUBKEY).to_string();
1156        let descriptor = format!(
1157            "tr({},multi_a({},{}))",
1158            unspendable_xonly_pk_str,
1159            threshold,
1160            pks.iter()
1161                .map(|pk| pk.to_string())
1162                .collect::<Vec<String>>()
1163                .join(",")
1164        );
1165
1166        let descriptor_info = rpc.get_descriptor_info(&descriptor).await.expect("");
1167
1168        let descriptor = descriptor_info.descriptor;
1169
1170        let addresses = rpc.derive_addresses(&descriptor, None).await.expect("");
1171
1172        tracing::info!("{:?}", addresses);
1173
1174        let multisig_address = addresses[0].clone().assume_checked();
1175
1176        let multisig = Multisig::new(pks, threshold);
1177
1178        let (addr, _) = create_taproot_address(
1179            &[multisig.to_script_buf()],
1180            None,
1181            config.protocol_paramset().network,
1182        );
1183
1184        // println!("addr: {:?}", addr);
1185        // println!("multisig_address: {:?}", multisig_address);
1186
1187        assert_eq!(addr, multisig_address);
1188    }
1189}