confidential_script_lib/
lib.rs

1// Written in 2025 by Joshua Doman <joshsdoman@gmail.com>
2// SPDX-License-Identifier: CC0-1.0
3
4//! # Confidential Script Library
5//!
6//! Emulate Bitcoin script by converting valid script-path spends to key-path spends. Intended for use within a Trusted Execution Environment (TEE), the library validates unlocking conditions and then authorizes the transaction using a deterministically derived private key.
7//!
8//! This approach enables confidential execution of complex script, including opcodes not yet supported by the Bitcoin protocol. The actual on-chain footprint is a minimal key-path spend, preserving privacy and efficiency.
9//!
10//! ## Overview
11//!
12//! The library operates on a two-step process: emulation and signing.
13//!
14//! 1.  **Emulation**: A transaction is constructed using an input spending a *real* `previous_outpoint` with a witness that is a script-path spend from an *emulated* P2TR `script_pubkey`. The library validates this emulated witness using a `Verifier`, which matches the API of `rust-bitcoinkernel`. If compiled with the `bitcoinkernel` feature, users can use the actual kernel as the default verifier, or they can provide an alternative verifier that enforces a different set of rules (ex: a fork of `bitcoinkernel` that supports Simplicity).
15//!
16//! 2.  **Signing**: If the transaction is valid, the library uses the provided parent private key and the merkle root of the *emulated* script path spend to derive a child private key, which corresponds to the internal public key of the *actual* UTXO being spent. The library then updates the transaction with a key-path spend signed with this child key.
17//!
18//! To facilitate offline generation of the real `script_pubkey`, the child key is derived from the parent key using a non-hardened HMAC-SHA512 derivation scheme. This lets users generate addresses using the parent _public_ key, while the parent private key is secured elsewhere.
19//!
20//! This library is intended to be run within a TEE, which is securely provisioned with the parent private key. This decouples script execution from on-chain settlement, keeping execution private and enabling new functionality with minimal trust assumptions.
21//!
22//! ## Failsafe Mechanism: Backup Script Path
23//!
24//! To prevent funds from being irrecoverably locked if the TEE becomes unavailable, the library allows for the inclusion of an optional `backup_merkle_root` when creating the actual on-chain address. This backup merkle root defines the alternative spending paths that are available independently of the TEE.
25//!
26//! A common use case for this feature is to include a timelocked recovery script (e.g., using `OP_CHECKSEQUENCEVERIFY`). If the primary TEE-based execution path becomes unavailable for any reason, the owner can wait for the timelock to expire and then recover the funds using a pre-defined backup script. This provides a crucial failsafe, ensuring that users retain ultimate control over their assets.
27//!
28//! ## Extensibility for Proposed Soft Forks
29//!
30//! This library can be used to emulate proposed upgrades, such as new opcodes like `OP_CAT` or `OP_CTV` or new scripting languages like Simplicity. It accepts any verifier that adheres to the `rust-bitcoinkernel` API, allowing developers to experiment with new functionality by forking the kernel, without waiting for a soft fork to gain adoption on mainnet.
31//!
32
33// Coding conventions
34#![deny(unsafe_code)]
35#![deny(non_upper_case_globals)]
36#![deny(non_camel_case_types)]
37#![deny(non_snake_case)]
38#![deny(unused_mut)]
39#![deny(dead_code)]
40#![deny(unused_imports)]
41#![deny(missing_docs)]
42
43#[cfg(not(any(feature = "std")))]
44compile_error!("`std` must be enabled");
45
46use bitcoin::{
47    Address, Network, ScriptBuf, TapNodeHash, TapSighashType, TapTweakHash, Transaction, TxIn,
48    TxOut, Witness, XOnlyPublicKey,
49    consensus::deserialize,
50    hashes::Hash,
51    key::Secp256k1,
52    secp256k1,
53    secp256k1::{Keypair, Message, PublicKey, Scalar, SecretKey, constants::CURVE_ORDER},
54    sighash::{Prevouts, SighashCache},
55    taproot::{ControlBlock, Signature},
56};
57#[cfg(feature = "bitcoinkernel")]
58use bitcoinkernel::{KernelError, verify};
59use hmac::{Hmac, Mac};
60use num_bigint::BigUint;
61use sha2::Sha512;
62use std::fmt;
63
64/// Comprehensive error type for verify_and_sign operations
65#[derive(Debug)]
66pub enum Error {
67    /// Verification failed
68    VerificationFailed(String),
69    /// Wrapped secp256k1 errors from cryptographic operations
70    Secp256k1(secp256k1::Error),
71    /// Input is not a script path spend (missing taproot control block)
72    NotScriptPathSpend,
73    /// Invalid control block format or size
74    InvalidControlBlock,
75    /// Invalid amount
76    InvalidAmount(bitcoin_units::amount::OutOfRangeError),
77    /// Deserialization failed
78    DeserializationFailed(bitcoin::consensus::encode::Error),
79    /// Unable to calculate sighash
80    InvalidSighash,
81    /// Input index out of bounds
82    InputIndexOutOfBounds,
83}
84
85/// Trait to abstract the behavior of the bitcoin script verifier, allowing
86/// users to provide their own verifier.
87pub trait Verifier {
88    /// Verify a bitcoin script, mirroring the API of `bitcoinkernel::verify`.
89    ///
90    /// # Arguments
91    /// * `script_pubkey` - The script public key to verify.
92    /// * `amount` - The amount of the input being spent.
93    /// * `tx_to` - The transaction containing the script.
94    /// * `input_index` - The index of the input to verify.
95    /// * `flags` - Script verification flags.
96    /// * `spent_outputs` - The outputs being spent by the transaction.
97    ///
98    /// # Errors
99    /// Returns `Error` if verification fails.
100    fn verify(
101        &self,
102        script_pubkey: &[u8],
103        amount: Option<i64>,
104        tx_to: &[u8],
105        input_index: u32,
106        flags: Option<u32>,
107        spent_outputs: &[TxOut],
108    ) -> Result<(), Error>;
109}
110
111/// The default `Verifier` implementation that uses `bitcoinkernel`.
112#[cfg(feature = "bitcoinkernel")]
113pub struct DefaultVerifier;
114
115#[cfg(feature = "bitcoinkernel")]
116impl Verifier for DefaultVerifier {
117    fn verify(
118        &self,
119        script_pubkey: &[u8],
120        amount: Option<i64>,
121        tx_to: &[u8],
122        input_index: u32,
123        flags: Option<u32>,
124        spent_outputs: &[TxOut],
125    ) -> Result<(), Error> {
126        let mut outputs = Vec::new();
127        for txout in spent_outputs {
128            let amount = txout.value.to_signed()?.to_sat();
129            let script = bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes())?;
130            outputs.push(bitcoinkernel::TxOut::new(&script, amount));
131        }
132
133        verify(
134            &bitcoinkernel::ScriptPubkey::try_from(script_pubkey)?,
135            amount,
136            &bitcoinkernel::Transaction::try_from(tx_to)?,
137            input_index,
138            flags,
139            &outputs,
140        )?;
141
142        Ok(())
143    }
144}
145
146/// Verifies an emulated Bitcoin script and signs the corresponding transaction.
147///
148/// This function performs script verification using bitcoinkernel, verifying an
149/// emulated P2TR input. If successful, it derives an XOnlyPublicKey from the
150/// parent key and the emulated merkle root, which is then tweaked with an optional
151/// backup merkle root to derive the actual spent UTXO, which is then key path signed
152/// with `SIGHASH_DEFAULT`.
153///
154/// # Arguments
155/// * `verifier` - The verifier to use for script validation
156/// * `input_index` - Index of the input to verify and sign (0-based)
157/// * `emulated_tx_to` - Serialized transaction to verify and sign
158/// * `actual_spent_outputs` - Actual outputs being spent
159/// * `aux_rand` - Auxiliary random data for signing
160/// * `parent_key` - Parent secret key used to derive child key for signing
161/// * `backup_merkle_root` - Optional merkle root for backup script path spending
162///
163/// # Errors
164/// Returns error if verification fails, key derivation fails, or signing fails
165pub fn verify_and_sign<V: Verifier>(
166    verifier: &V,
167    input_index: u32,
168    emulated_tx_to: &[u8],
169    actual_spent_outputs: &[TxOut],
170    aux_rand: &[u8; 32],
171    parent_key: SecretKey,
172    backup_merkle_root: Option<TapNodeHash>,
173) -> Result<Transaction, Error> {
174    // Must be able to deserialize transaction
175    let mut tx: Transaction = deserialize(emulated_tx_to)?;
176
177    // Input index must be in bounds
178    if input_index as usize >= tx.input.len() {
179        return Err(Error::InputIndexOutOfBounds);
180    }
181
182    // Get the input amount
183    let amount = actual_spent_outputs[input_index as usize]
184        .value
185        .to_signed()?
186        .to_sat();
187
188    // Must be script path spend
189    let input = tx.input[input_index as usize].clone();
190    let (Some(control_block), Some(tapleaf)) = (
191        input.witness.taproot_control_block(),
192        input.witness.taproot_leaf_script(),
193    ) else {
194        return Err(Error::NotScriptPathSpend);
195    };
196    let Ok(control_block) = ControlBlock::decode(control_block) else {
197        return Err(Error::NotScriptPathSpend);
198    };
199
200    // Calculate merkle root
201    let mut merkle_root = TapNodeHash::from_script(tapleaf.script, tapleaf.version);
202    for elem in &control_block.merkle_branch {
203        merkle_root = TapNodeHash::from_node_hashes(merkle_root, *elem);
204    }
205
206    // Create emulated script pubkey
207    let secp = Secp256k1::new();
208    let address = Address::p2tr(
209        &secp,
210        control_block.internal_key,
211        Some(merkle_root),
212        Network::Bitcoin,
213    );
214
215    // Must satisfy verifier
216    verifier.verify(
217        address.script_pubkey().as_bytes(),
218        Some(amount),
219        emulated_tx_to,
220        input_index,
221        None,
222        actual_spent_outputs,
223    )?;
224
225    // Get actual internal key and child key to be tweaked for signing
226    let child_key = derive_child_secret_key(parent_key, merkle_root.to_byte_array())?;
227    let (internal_key, parity) = child_key.public_key(&secp).x_only_public_key();
228    let child_key_for_tweak = if parity == secp256k1::Parity::Odd {
229        child_key.negate()
230    } else {
231        child_key
232    };
233
234    // Update input at this index
235    tx.input[input_index as usize] = TxIn {
236        previous_output: input.previous_output,
237        script_sig: ScriptBuf::new(),
238        sequence: input.sequence, // Keep the same sequence
239        witness: Witness::new(),
240    };
241
242    // Create sighash for the input
243    let mut sighash_cache = SighashCache::new(&tx);
244    let sighash_bytes = sighash_cache
245        .taproot_key_spend_signature_hash(
246            input_index as usize,
247            &Prevouts::All(actual_spent_outputs),
248            TapSighashType::Default,
249        )
250        .map_err(|_| Error::InvalidSighash)?;
251    let mut sighash = [0u8; 32];
252    sighash.copy_from_slice(sighash_bytes.as_byte_array());
253
254    // Calculate the taproot tweaked private key for keypath spending
255    let tweak = TapTweakHash::from_key_and_tweak(internal_key, backup_merkle_root);
256    let tweaked_secret_key = child_key_for_tweak.add_tweak(&tweak.to_scalar())?;
257    let tweaked_keypair = Keypair::from_secret_key(&secp, &tweaked_secret_key);
258
259    // Sign the sighash
260    let message = Message::from_digest(sighash);
261    let signature = secp.sign_schnorr_with_aux_rand(&message, &tweaked_keypair, aux_rand);
262
263    // Create taproot signature (schnorr signature + sighash type)
264    let tap_signature = Signature {
265        signature,
266        sighash_type: TapSighashType::Default,
267    };
268
269    // Create witness for keypath spend
270    let mut witness = Witness::new();
271    witness.push(tap_signature.to_vec());
272    tx.input[input_index as usize].witness = witness;
273
274    Ok(tx)
275}
276
277/// Generates P2TR address from a parent public key and the emulated merkle root,
278/// with an optional backup merkle root.
279///
280/// # Arguments
281/// * `parent_key` - The parent public key
282/// * `emulated_merkle_root` - The merkle root of the emulated input
283/// * `backup_merkle_root` - Optional merkle root for backup script path spending
284/// * `network` - The network to generate the address for
285///
286/// # Errors
287/// Returns an error if key derivation fails
288pub fn generate_address(
289    parent_key: PublicKey,
290    emulated_merkle_root: TapNodeHash,
291    backup_merkle_root: Option<TapNodeHash>,
292    network: Network,
293) -> Result<Address, secp256k1::Error> {
294    let secp = Secp256k1::new();
295    let child_key = derive_child_public_key(parent_key, emulated_merkle_root.to_byte_array())?;
296    let internal_key = XOnlyPublicKey::from(child_key);
297    let address = Address::p2tr(&secp, internal_key, backup_merkle_root, network);
298
299    Ok(address)
300}
301
302/// Derives a child secret key from a parent secret key and emulated merkle root
303/// using HMAC-SHA512 based key derivation (non-hardened derivation).
304fn derive_child_secret_key(
305    parent_key: SecretKey,
306    emulated_merkle_root: [u8; 32],
307) -> Result<SecretKey, secp256k1::Error> {
308    let secp = Secp256k1::new();
309
310    // Derive parent public key from parent secret
311    let parent_public = parent_key.public_key(&secp);
312
313    // Create HMAC-SHA512 with parent public key and merkle root
314    let mut mac = Hmac::<Sha512>::new_from_slice(&parent_public.serialize())
315        .expect("PublicKey serialization should always be non-empty");
316    mac.update(&emulated_merkle_root);
317    let hmac_result = mac.finalize().into_bytes();
318
319    // Use first 32 bytes for key material
320    let mut key_material = [0u8; 32];
321    key_material.copy_from_slice(&hmac_result[..32]);
322    let scalar = reduce_mod_order(&key_material);
323
324    // Add the key material to parent private key
325    parent_key.add_tweak(&scalar)
326}
327
328/// Derives a child public key from a parent public key and emulated merkle root
329/// This allows public key derivation without access to private keys.
330fn derive_child_public_key(
331    parent_public: PublicKey,
332    emulated_merkle_root: [u8; 32],
333) -> Result<PublicKey, secp256k1::Error> {
334    let secp = Secp256k1::new();
335
336    // Create HMAC-SHA512 with parent public key as key
337    let mut mac = Hmac::<Sha512>::new_from_slice(&parent_public.serialize())
338        .expect("PublicKey serialization should always be non-empty");
339    mac.update(&emulated_merkle_root);
340    let hmac_result = mac.finalize().into_bytes();
341
342    // Use first 32 bytes as scalar for point multiplication
343    let mut key_material = [0u8; 32];
344    key_material.copy_from_slice(&hmac_result[..32]);
345    let scalar = reduce_mod_order(&key_material);
346
347    // Add scalar * G to parent public key
348    parent_public.add_exp_tweak(&secp, &scalar)
349}
350
351/// Safely reduces a 32-byte array modulo the secp256k1 curve order
352fn reduce_mod_order(bytes: &[u8; 32]) -> Scalar {
353    // Keep trying to create a scalar until we get a valid one
354    // In practice, this loop will almost always execute only once
355    let mut attempt = *bytes;
356    loop {
357        match Scalar::from_be_bytes(attempt) {
358            Ok(scalar) => return scalar,
359            Err(_) => {
360                // If the value is too large, subtract the curve order
361                // This is equivalent to modular reduction
362                attempt = subtract_curve_order(&attempt);
363            }
364        }
365    }
366}
367
368/// Subtract the secp256k1 curve order from a 32-byte big-endian number
369fn subtract_curve_order(bytes: &[u8; 32]) -> [u8; 32] {
370    let value = BigUint::from_bytes_be(bytes);
371    let order = BigUint::from_bytes_be(&CURVE_ORDER);
372    let reduced = value % order;
373
374    let mut result = [0u8; 32];
375    let reduced_bytes = reduced.to_bytes_be();
376    let offset = 32 - reduced_bytes.len();
377    result[offset..].copy_from_slice(&reduced_bytes);
378    result
379}
380
381impl fmt::Display for Error {
382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383        match self {
384            Error::VerificationFailed(e) => {
385                write!(f, "Verification failed: {e}")
386            }
387            Error::Secp256k1(e) => {
388                write!(f, "Secp256k1 cryptographic operation failed: {e}")
389            }
390            Error::NotScriptPathSpend => {
391                write!(
392                    f,
393                    "Input is not a script path spend (missing taproot control block)"
394                )
395            }
396            Error::InvalidAmount(e) => {
397                write!(f, "Invalid amount: {e}")
398            }
399            Error::InvalidControlBlock => {
400                write!(f, "Input has invalid control block")
401            }
402            Error::DeserializationFailed(e) => {
403                write!(f, "Failed to deserialize: {e}")
404            }
405            Error::InvalidSighash => {
406                write!(f, "Unable to calculate sighash for input")
407            }
408            Error::InputIndexOutOfBounds => {
409                write!(f, "Input index out of bounds")
410            }
411        }
412    }
413}
414
415#[cfg(feature = "bitcoinkernel")]
416impl From<KernelError> for Error {
417    fn from(error: KernelError) -> Self {
418        Error::VerificationFailed(error.to_string())
419    }
420}
421
422impl From<secp256k1::Error> for Error {
423    fn from(error: secp256k1::Error) -> Self {
424        Error::Secp256k1(error)
425    }
426}
427
428impl From<bitcoin::consensus::encode::Error> for Error {
429    fn from(error: bitcoin::consensus::encode::Error) -> Self {
430        Error::DeserializationFailed(error)
431    }
432}
433
434impl From<bitcoin_units::amount::OutOfRangeError> for Error {
435    fn from(error: bitcoin_units::amount::OutOfRangeError) -> Self {
436        Error::InvalidAmount(error)
437    }
438}
439
440#[cfg(test)]
441#[cfg(feature = "bitcoinkernel")]
442mod kernel_tests {
443    use super::*;
444    use bitcoin::{
445        Address, Amount, Network, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut, Txid,
446        Witness,
447        consensus::encode::serialize,
448        hashes::Hash,
449        key::UntweakedPublicKey,
450        taproot::{LeafVersion, TaprootBuilder},
451    };
452
453    fn create_test_transaction_single_input() -> Transaction {
454        Transaction {
455            version: bitcoin::transaction::Version::TWO,
456            lock_time: bitcoin::locktime::absolute::LockTime::ZERO,
457            input: vec![TxIn {
458                previous_output: OutPoint::null(),
459                script_sig: ScriptBuf::new(),
460                sequence: bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME,
461                witness: Witness::new(),
462            }],
463            output: vec![TxOut {
464                value: Amount::from_sat(100000),
465                script_pubkey: ScriptBuf::new_op_return([]),
466            }],
467        }
468    }
469
470    fn create_test_transaction_multi_input() -> Transaction {
471        Transaction {
472            version: bitcoin::transaction::Version::TWO,
473            lock_time: bitcoin::locktime::absolute::LockTime::ZERO,
474            input: vec![
475                TxIn {
476                    previous_output: OutPoint::null(),
477                    script_sig: ScriptBuf::new(),
478                    sequence: bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME,
479                    witness: Witness::new(),
480                },
481                TxIn {
482                    previous_output: OutPoint::new(Txid::all_zeros(), 1),
483                    script_sig: ScriptBuf::new(),
484                    sequence: bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME,
485                    witness: Witness::new(),
486                },
487            ],
488            output: vec![TxOut {
489                value: Amount::from_sat(100000),
490                script_pubkey: ScriptBuf::new_op_return([]),
491            }],
492        }
493    }
494
495    #[test]
496    fn test_unable_to_deserialize_tx() {
497        let result = verify_and_sign(
498            &DefaultVerifier,
499            0,
500            &[],
501            &[],
502            &[1u8; 32],
503            SecretKey::from_slice(&[1u8; 32]).unwrap(),
504            None,
505        );
506
507        assert!(matches!(result, Err(Error::DeserializationFailed(_))));
508    }
509
510    #[test]
511    fn test_input_index_out_of_bounds() {
512        let txout = TxOut {
513            value: Amount::from_sat(100000),
514            script_pubkey: ScriptBuf::new_op_return([]),
515        };
516        let result = verify_and_sign(
517            &DefaultVerifier,
518            1,
519            &serialize(&create_test_transaction_single_input()),
520            std::slice::from_ref(&txout),
521            &[1u8; 32],
522            SecretKey::from_slice(&[1u8; 32]).unwrap(),
523            None,
524        );
525
526        assert!(matches!(result, Err(Error::InputIndexOutOfBounds)));
527    }
528
529    #[test]
530    fn test_verify_and_sign_single_input_single_leaf() {
531        let secp = Secp256k1::new();
532
533        // 1. Create a dummy internal key
534        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
535        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
536
537        // 2. Create OP_TRUE script leaf
538        let op_true_script = Script::builder()
539            .push_opcode(bitcoin::opcodes::OP_TRUE)
540            .into_script();
541
542        // 3. Build the taproot tree with single OP_TRUE leaf
543        let taproot_builder = TaprootBuilder::new()
544            .add_leaf(0, op_true_script.clone())
545            .unwrap();
546        let taproot_spend_info = taproot_builder.finalize(&secp, internal_key).unwrap();
547
548        // 4. Get the control block for our OP_TRUE leaf
549        let control_block = taproot_spend_info
550            .control_block(&(op_true_script.clone(), LeafVersion::TapScript))
551            .unwrap();
552
553        // 5. Create the witness stack for script path spending
554        let mut witness = Witness::new();
555        witness.push(op_true_script.as_bytes());
556        witness.push(control_block.serialize());
557
558        // 6. Create emulated transaction
559        let mut emulated_tx = create_test_transaction_single_input();
560        emulated_tx.input[0].witness = witness;
561
562        // 7. Create actual child secret
563        let aux_rand = [1u8; 32];
564        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
565        let child_secret = derive_child_secret_key(
566            parent_secret,
567            taproot_spend_info.merkle_root().unwrap().to_byte_array(),
568        )
569        .unwrap();
570
571        // 8. Create actual P2TR outputs
572        let actual_internal_key = XOnlyPublicKey::from(child_secret.public_key(&secp));
573        let actual_address = Address::p2tr(&secp, actual_internal_key, None, Network::Bitcoin);
574        let actual_spent_outputs = [TxOut {
575            value: Amount::from_sat(100_000),
576            script_pubkey: actual_address.script_pubkey(),
577        }];
578
579        // 9. Verify and sign actual transaction
580        let actual_tx = verify_and_sign(
581            &DefaultVerifier,
582            0,
583            &serialize(&emulated_tx),
584            &actual_spent_outputs,
585            &aux_rand,
586            parent_secret,
587            None,
588        )
589        .unwrap();
590
591        let mut actual_outputs = Vec::new();
592        for txout in actual_spent_outputs {
593            let amount = txout.value.to_signed().unwrap().to_sat();
594            let script =
595                bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes()).unwrap();
596            actual_outputs.push(bitcoinkernel::TxOut::new(&script, amount));
597        }
598
599        // 10. Verify the actual transaction was properly signed
600        let verify_result = bitcoinkernel::verify(
601            &bitcoinkernel::ScriptPubkey::try_from(actual_address.script_pubkey().as_bytes())
602                .unwrap(),
603            Some(100_000),
604            &bitcoinkernel::Transaction::try_from(serialize(&actual_tx).as_slice()).unwrap(),
605            0,
606            None,
607            &actual_outputs,
608        );
609
610        assert!(verify_result.is_ok());
611        assert_eq!(actual_tx.input[0].witness.len(), 1)
612    }
613
614    #[test]
615    fn test_verify_and_sign_single_input_multiple_leaves() {
616        let secp = Secp256k1::new();
617
618        // 1. Create a dummy internal key
619        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
620        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
621
622        // 2. Create script leaves
623        let op_true_script = Script::builder()
624            .push_opcode(bitcoin::opcodes::OP_TRUE)
625            .into_script();
626        let op_false_script = Script::builder()
627            .push_opcode(bitcoin::opcodes::OP_FALSE)
628            .into_script();
629
630        // 3. Build the taproot tree with two leaves
631        let taproot_builder = TaprootBuilder::new()
632            .add_leaf(1, op_true_script.clone())
633            .unwrap()
634            .add_leaf(1, op_false_script.clone())
635            .unwrap();
636        let taproot_spend_info = taproot_builder.finalize(&secp, internal_key).unwrap();
637
638        // 4. Get the control block for our OP_TRUE leaf
639        let control_block = taproot_spend_info
640            .control_block(&(op_true_script.clone(), LeafVersion::TapScript))
641            .unwrap();
642
643        // 5. Create the witness stack for script path spending
644        let mut witness = Witness::new();
645        witness.push(op_true_script.as_bytes());
646        witness.push(control_block.serialize());
647
648        // 6. Create emulated transaction
649        let mut emulated_tx = create_test_transaction_single_input();
650        emulated_tx.input[0].witness = witness;
651
652        // 7. Create actual child secret
653        let aux_rand = [1u8; 32];
654        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
655        let child_secret = derive_child_secret_key(
656            parent_secret,
657            taproot_spend_info.merkle_root().unwrap().to_byte_array(),
658        )
659        .unwrap();
660
661        // 8. Create actual P2TR outputs
662        let actual_internal_key = XOnlyPublicKey::from(child_secret.public_key(&secp));
663        let actual_address = Address::p2tr(&secp, actual_internal_key, None, Network::Bitcoin);
664        let actual_spent_outputs = [TxOut {
665            value: Amount::from_sat(100_000),
666            script_pubkey: actual_address.script_pubkey(),
667        }];
668
669        // 9. Verify and sign actual transaction
670        let actual_tx = verify_and_sign(
671            &DefaultVerifier,
672            0,
673            &serialize(&emulated_tx),
674            &actual_spent_outputs,
675            &aux_rand,
676            parent_secret,
677            None,
678        )
679        .unwrap();
680
681        let mut actual_outputs = Vec::new();
682        for txout in actual_spent_outputs {
683            let amount = txout.value.to_signed().unwrap().to_sat();
684            let script =
685                bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes()).unwrap();
686            actual_outputs.push(bitcoinkernel::TxOut::new(&script, amount));
687        }
688
689        // 10. Verify the actual transaction was properly signed
690        let verify_result = bitcoinkernel::verify(
691            &bitcoinkernel::ScriptPubkey::try_from(actual_address.script_pubkey().as_bytes())
692                .unwrap(),
693            Some(100_000),
694            &bitcoinkernel::Transaction::try_from(serialize(&actual_tx).as_slice()).unwrap(),
695            0,
696            None,
697            &actual_outputs,
698        );
699
700        assert!(verify_result.is_ok());
701        assert_eq!(actual_tx.input[0].witness.len(), 1)
702    }
703
704    #[test]
705    fn test_verify_and_sign_single_input_with_backup() {
706        let secp = Secp256k1::new();
707
708        // 1. Create a dummy internal key
709        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
710        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
711
712        // 2. Create OP_TRUE script leaf
713        let op_true_script = Script::builder()
714            .push_opcode(bitcoin::opcodes::OP_TRUE)
715            .into_script();
716
717        // 3. Build the taproot tree with single OP_TRUE leaf
718        let taproot_builder = TaprootBuilder::new()
719            .add_leaf(0, op_true_script.clone())
720            .unwrap();
721        let taproot_spend_info = taproot_builder.finalize(&secp, internal_key).unwrap();
722
723        // 4. Get the control block for our OP_TRUE leaf
724        let control_block = taproot_spend_info
725            .control_block(&(op_true_script.clone(), LeafVersion::TapScript))
726            .unwrap();
727
728        // 5. Create the witness stack for script path spending
729        let mut witness = Witness::new();
730        witness.push(op_true_script.as_bytes());
731        witness.push(control_block.serialize());
732
733        // 6. Create emulated transaction
734        let mut emulated_tx = create_test_transaction_single_input();
735        emulated_tx.input[0].witness = witness;
736
737        // 7. Create actual child secret
738        let aux_rand = [1u8; 32];
739        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
740        let child_secret = derive_child_secret_key(
741            parent_secret,
742            taproot_spend_info.merkle_root().unwrap().to_byte_array(),
743        )
744        .unwrap();
745
746        // 8. Create actual P2TR outputs
747        let actual_backup_merkle_root = taproot_spend_info.merkle_root();
748        let actual_internal_key = XOnlyPublicKey::from(child_secret.public_key(&secp));
749        let actual_address = Address::p2tr(
750            &secp,
751            actual_internal_key,
752            actual_backup_merkle_root,
753            Network::Bitcoin,
754        );
755        let actual_spent_outputs = [TxOut {
756            value: Amount::from_sat(100_000),
757            script_pubkey: actual_address.script_pubkey(),
758        }];
759
760        // 9. Verify and sign actual transaction
761        let actual_tx = verify_and_sign(
762            &DefaultVerifier,
763            0,
764            &serialize(&emulated_tx),
765            &actual_spent_outputs,
766            &aux_rand,
767            parent_secret,
768            actual_backup_merkle_root,
769        )
770        .unwrap();
771
772        let mut actual_outputs = Vec::new();
773        for txout in actual_spent_outputs {
774            let amount = txout.value.to_signed().unwrap().to_sat();
775            let script =
776                bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes()).unwrap();
777            actual_outputs.push(bitcoinkernel::TxOut::new(&script, amount));
778        }
779
780        // 10. Verify the actual transaction was properly signed
781        let verify_result = bitcoinkernel::verify(
782            &bitcoinkernel::ScriptPubkey::try_from(actual_address.script_pubkey().as_bytes())
783                .unwrap(),
784            Some(100_000),
785            &bitcoinkernel::Transaction::try_from(serialize(&actual_tx).as_slice()).unwrap(),
786            0,
787            None,
788            &actual_outputs,
789        );
790
791        assert!(verify_result.is_ok());
792        assert_eq!(actual_tx.input[0].witness.len(), 1)
793    }
794
795    #[test]
796    fn test_verify_and_sign_multi_input_tx() {
797        let secp = Secp256k1::new();
798
799        // 1. Create a dummy internal key
800        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
801        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
802
803        // 2. Create OP_TRUE script leaf
804        let op_true_script = Script::builder()
805            .push_opcode(bitcoin::opcodes::OP_TRUE)
806            .into_script();
807
808        // 3. Build the taproot tree with single OP_TRUE leaf
809        let taproot_builder = TaprootBuilder::new()
810            .add_leaf(0, op_true_script.clone())
811            .unwrap();
812        let taproot_spend_info = taproot_builder.finalize(&secp, internal_key).unwrap();
813
814        // 4. Get the control block for our OP_TRUE leaf
815        let control_block = taproot_spend_info
816            .control_block(&(op_true_script.clone(), LeafVersion::TapScript))
817            .unwrap();
818
819        // 5. Create the witness stack for script path spending
820        let mut witness = Witness::new();
821        witness.push(op_true_script.as_bytes());
822        witness.push(control_block.serialize());
823
824        // 6. Create emulated transaction
825        let mut emulated_tx = create_test_transaction_multi_input();
826        emulated_tx.input[1].witness = witness;
827
828        // 7. Create actual child secret
829        let aux_rand = [1u8; 32];
830        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
831        let child_secret = derive_child_secret_key(
832            parent_secret,
833            taproot_spend_info.merkle_root().unwrap().to_byte_array(),
834        )
835        .unwrap();
836
837        // 8. Create actual P2TR outputs
838        let actual_internal_key = XOnlyPublicKey::from(child_secret.public_key(&secp));
839        let actual_address = Address::p2tr(&secp, actual_internal_key, None, Network::Bitcoin);
840        let actual_spent_outputs = [
841            TxOut {
842                value: Amount::from_sat(200_000),
843                script_pubkey: actual_address.script_pubkey(),
844            },
845            TxOut {
846                value: Amount::from_sat(100_000),
847                script_pubkey: actual_address.script_pubkey(),
848            },
849        ];
850
851        // 9. Verify and sign actual transaction
852        let actual_tx = verify_and_sign(
853            &DefaultVerifier,
854            1,
855            &serialize(&emulated_tx),
856            &actual_spent_outputs,
857            &aux_rand,
858            parent_secret,
859            None,
860        )
861        .unwrap();
862
863        let mut actual_outputs = Vec::new();
864        for txout in actual_spent_outputs {
865            let amount = txout.value.to_signed().unwrap().to_sat();
866            let script =
867                bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes()).unwrap();
868            actual_outputs.push(bitcoinkernel::TxOut::new(&script, amount));
869        }
870
871        // 10. Verify the actual transaction was properly signed
872        let verify_result = bitcoinkernel::verify(
873            &bitcoinkernel::ScriptPubkey::try_from(actual_address.script_pubkey().as_bytes())
874                .unwrap(),
875            Some(100_000),
876            &bitcoinkernel::Transaction::try_from(serialize(&actual_tx).as_slice()).unwrap(),
877            1,
878            None,
879            &actual_outputs,
880        );
881
882        assert!(verify_result.is_ok());
883        assert_eq!(actual_tx.input[1].witness.len(), 1)
884    }
885}
886
887#[cfg(test)]
888mod non_kernel_tests {
889    use super::*;
890    use bitcoin::{
891        Script,
892        key::{Secp256k1, UntweakedPublicKey},
893        taproot::TaprootBuilder,
894    };
895
896    #[test]
897    fn test_generate_address() {
898        let secp = Secp256k1::new();
899
900        // 1. Create emulated script
901        let emulated_script = Script::builder()
902            .push_opcode(bitcoin::opcodes::OP_TRUE)
903            .into_script();
904
905        // 2. Build the taproot tree and create emulated merkle root
906        let taproot_builder = TaprootBuilder::new()
907            .add_leaf(0, emulated_script.clone())
908            .unwrap();
909        let dummy_internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
910        let dummy_internal_key = UntweakedPublicKey::from(dummy_internal_secret.public_key(&secp));
911        let taproot_spend_info = taproot_builder.finalize(&secp, dummy_internal_key).unwrap();
912        let emulated_merkle_root = taproot_spend_info.merkle_root().unwrap();
913
914        // 3. Create backup merkle root
915        let backup_merkle_root = emulated_merkle_root;
916
917        // 4. Generate an on-chain address
918        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
919        let master_public_key: PublicKey = internal_secret.public_key(&secp);
920        let onchain_address = generate_address(
921            master_public_key,
922            emulated_merkle_root,
923            Some(backup_merkle_root),
924            Network::Bitcoin,
925        );
926
927        assert!(onchain_address.is_ok());
928    }
929
930    #[test]
931    fn test_public_private_key_derivation_consistency() {
932        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
933        let parent_public = parent_secret.public_key(&Secp256k1::new());
934        let merkle_root = [42u8; 32];
935
936        let child_secret = derive_child_secret_key(parent_secret, merkle_root).unwrap();
937        let child_public_from_secret = child_secret.public_key(&Secp256k1::new());
938        let child_public_direct = derive_child_public_key(parent_public, merkle_root).unwrap();
939
940        assert_eq!(child_public_from_secret, child_public_direct);
941    }
942
943    #[test]
944    fn test_curve_order_reduction() {
945        let max_bytes = [0xFF; 32];
946        let reduced = reduce_mod_order(&max_bytes);
947        // Should not panic and should be valid scalar
948        #[allow(clippy::useless_conversion)]
949        let _ = Scalar::from(reduced);
950    }
951}