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 script is not P2TR
72    NotTaprootSpend,
73    /// Input is not a script path spend (missing taproot control block)
74    NotScriptPathSpend,
75    /// Invalid control block format or size
76    InvalidControlBlock,
77    /// Invalid amount
78    InvalidAmount(bitcoin_units::amount::OutOfRangeError),
79    /// Deserialization failed
80    DeserializationFailed(bitcoin::consensus::encode::Error),
81    /// Unable to calculate sighash
82    InvalidSighash,
83    /// Mismatch between emulated and actual spent outputs count
84    SpentOutputsMismatch,
85    /// Input index out of bounds
86    InputIndexOutOfBounds,
87}
88
89/// Trait to abstract the behavior of the bitcoin script verifier, allowing
90/// users to provide their own verifier.
91pub trait Verifier {
92    /// Verify a bitcoin script, mirroring the API of `bitcoinkernel::verify`.
93    ///
94    /// # Arguments
95    /// * `script_pubkey` - The script public key to verify.
96    /// * `amount` - The amount of the input being spent.
97    /// * `tx_to` - The transaction containing the script.
98    /// * `input_index` - The index of the input to verify.
99    /// * `flags` - Script verification flags.
100    /// * `spent_outputs` - The outputs being spent by the transaction.
101    ///
102    /// # Errors
103    /// Returns `KernelError` if verification fails.
104    fn verify(
105        &self,
106        script_pubkey: &[u8],
107        amount: Option<i64>,
108        tx_to: &[u8],
109        input_index: u32,
110        flags: Option<u32>,
111        spent_outputs: &[TxOut],
112    ) -> Result<(), Error>;
113}
114
115/// The default `Verifier` implementation that uses `bitcoinkernel`.
116#[cfg(feature = "bitcoinkernel")]
117pub struct DefaultVerifier;
118
119#[cfg(feature = "bitcoinkernel")]
120impl Verifier for DefaultVerifier {
121    fn verify(
122        &self,
123        script_pubkey: &[u8],
124        amount: Option<i64>,
125        tx_to: &[u8],
126        input_index: u32,
127        flags: Option<u32>,
128        spent_outputs: &[TxOut],
129    ) -> Result<(), Error> {
130        let mut outputs = Vec::new();
131        for txout in spent_outputs {
132            let amount = txout.value.to_signed()?.to_sat();
133            let script = bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes())?;
134            outputs.push(bitcoinkernel::TxOut::new(&script, amount));
135        }
136
137        verify(
138            &bitcoinkernel::ScriptPubkey::try_from(script_pubkey)?,
139            amount,
140            &bitcoinkernel::Transaction::try_from(tx_to)?,
141            input_index,
142            flags,
143            &outputs,
144        )?;
145
146        Ok(())
147    }
148}
149
150/// Verifies an emulated Bitcoin script and signs the corresponding transaction.
151///
152/// This function performs script verification using bitcoinkernel, verifying an
153/// emulated P2TR input. If successful, it derives an XOnlyPublicKey from the
154/// parent key and the emulated merkle root, which is then tweaked with an optional
155/// backup merkle root to derive the actual spent UTXO, which is then key path signed
156/// with `SIGHASH_DEFAULT`.
157///
158/// # Arguments
159/// * `verifier` - The verifier to use for script validation
160/// * `emulated_script_pubkey` - The P2TR script to verify against
161/// * `amount` - The amount for the input
162/// * `emulated_tx_to` - Serialized transaction to verify and sign
163/// * `input_index` - Index of the input to verify and sign (0-based)
164/// * `emulated_spent_outputs` - Outputs being spent in the emulated transaction
165/// * `actual_spent_outputs` - Actual outputs for signature generation
166/// * `aux_rand` - Auxiliary random data for signing
167/// * `parent_key` - Parent secret key used to derive child key for signing
168/// * `backup_merkle_root` - Optional merkle root for backup script path spending
169///
170/// # Errors
171/// Returns error if verification fails, key derivation fails, or signing fails
172#[allow(clippy::too_many_arguments)]
173pub fn verify_and_sign<V: Verifier>(
174    verifier: &V,
175    emulated_script_pubkey: &[u8],
176    amount: i64,
177    emulated_tx_to: &[u8],
178    input_index: u32,
179    emulated_spent_outputs: &[TxOut],
180    actual_spent_outputs: &[TxOut],
181    aux_rand: &[u8; 32],
182    parent_key: SecretKey,
183    backup_merkle_root: Option<TapNodeHash>,
184) -> Result<Transaction, Error> {
185    // Must be taproot spend
186    if !ScriptBuf::from(emulated_script_pubkey.to_vec()).is_p2tr() {
187        return Err(Error::NotTaprootSpend);
188    }
189
190    // Must be able to deserialize transaction
191    let mut tx: Transaction = deserialize(emulated_tx_to)?;
192
193    // Emulate and actual spent outputs must match input count and have same values
194    if tx.input.len() != emulated_spent_outputs.len()
195        || tx.input.len() != actual_spent_outputs.len()
196        || emulated_spent_outputs
197            .iter()
198            .zip(actual_spent_outputs.iter())
199            .any(|(e, a)| e.value != a.value)
200    {
201        return Err(Error::SpentOutputsMismatch);
202    }
203
204    // Input index must be in bounds
205    if input_index as usize >= tx.input.len() {
206        return Err(Error::InputIndexOutOfBounds);
207    }
208
209    // Must be script path spend
210    let input = tx.input[input_index as usize].clone();
211    let (Some(control_block), Some(tapleaf)) = (
212        input.witness.taproot_control_block(),
213        input.witness.taproot_leaf_script(),
214    ) else {
215        return Err(Error::NotScriptPathSpend);
216    };
217    let Ok(control_block) = ControlBlock::decode(control_block) else {
218        return Err(Error::NotScriptPathSpend);
219    };
220
221    // Must satisfy verifier
222    verifier.verify(
223        emulated_script_pubkey,
224        Some(amount),
225        emulated_tx_to,
226        input_index,
227        None,
228        emulated_spent_outputs,
229    )?;
230
231    // Calculate merkle root
232    let mut curr_hash = TapNodeHash::from_script(tapleaf.script, tapleaf.version);
233    for elem in &control_block.merkle_branch {
234        curr_hash = TapNodeHash::from_node_hashes(curr_hash, *elem);
235    }
236    let merkle_root = curr_hash.to_byte_array();
237
238    // Get actual internal key and child key to be tweaked for signing
239    let secp = Secp256k1::new();
240    let child_key = derive_child_secret_key(parent_key, merkle_root)?;
241    let (internal_key, parity) = child_key.public_key(&secp).x_only_public_key();
242    let child_key_for_tweak = if parity == secp256k1::Parity::Odd {
243        child_key.negate()
244    } else {
245        child_key
246    };
247
248    // Update input at this index
249    tx.input[input_index as usize] = TxIn {
250        previous_output: input.previous_output,
251        script_sig: ScriptBuf::new(),
252        sequence: input.sequence, // Keep the same sequence
253        witness: Witness::new(),
254    };
255
256    // Create sighash for the input
257    let mut sighash_cache = SighashCache::new(&tx);
258    let sighash_bytes = sighash_cache
259        .taproot_key_spend_signature_hash(
260            input_index as usize,
261            &Prevouts::All(actual_spent_outputs),
262            TapSighashType::Default,
263        )
264        .map_err(|_| Error::InvalidSighash)?;
265    let mut sighash = [0u8; 32];
266    sighash.copy_from_slice(sighash_bytes.as_byte_array());
267
268    // Calculate the taproot tweaked private key for keypath spending
269    let tweak = TapTweakHash::from_key_and_tweak(internal_key, backup_merkle_root);
270    let tweaked_secret_key = child_key_for_tweak.add_tweak(&tweak.to_scalar())?;
271    let tweaked_keypair = Keypair::from_secret_key(&secp, &tweaked_secret_key);
272
273    // Sign the sighash
274    let message = Message::from_digest(sighash);
275    let signature = secp.sign_schnorr_with_aux_rand(&message, &tweaked_keypair, aux_rand);
276
277    // Create taproot signature (schnorr signature + sighash type)
278    let tap_signature = Signature {
279        signature,
280        sighash_type: TapSighashType::Default,
281    };
282
283    // Create witness for keypath spend
284    let mut witness = Witness::new();
285    witness.push(tap_signature.to_vec());
286    tx.input[input_index as usize].witness = witness;
287
288    Ok(tx)
289}
290
291/// Generates P2TR address from a parent public key and the emulated merkle root,
292/// with an optional backup merkle root.
293///
294/// # Arguments
295/// * `parent_key` - The parent public key
296/// * `emulated_merkle_root` - The merkle root of the emulated input
297/// * `backup_merkle_root` - Optional merkle root for backup script path spending
298/// * `network` - The network to generate the address for
299///
300/// # Errors
301/// Returns an error if key derivation fails
302pub fn generate_address(
303    parent_key: PublicKey,
304    emulated_merkle_root: TapNodeHash,
305    backup_merkle_root: Option<TapNodeHash>,
306    network: Network,
307) -> Result<Address, secp256k1::Error> {
308    let secp = Secp256k1::new();
309    let child_key = derive_child_public_key(parent_key, emulated_merkle_root.to_byte_array())?;
310    let internal_key = XOnlyPublicKey::from(child_key);
311    let address = Address::p2tr(&secp, internal_key, backup_merkle_root, network);
312
313    Ok(address)
314}
315
316/// Derives a child secret key from a parent secret key and emulated merkle root
317/// using HMAC-SHA512 based key derivation (non-hardened derivation).
318fn derive_child_secret_key(
319    parent_key: SecretKey,
320    emulated_merkle_root: [u8; 32],
321) -> Result<SecretKey, secp256k1::Error> {
322    let secp = Secp256k1::new();
323
324    // Derive parent public key from parent secret
325    let parent_public = parent_key.public_key(&secp);
326
327    // Create HMAC-SHA512 with parent public key and merkle root
328    let mut mac = Hmac::<Sha512>::new_from_slice(&parent_public.serialize())
329        .expect("PublicKey serialization should always be non-empty");
330    mac.update(&emulated_merkle_root);
331    let hmac_result = mac.finalize().into_bytes();
332
333    // Use first 32 bytes for key material
334    let mut key_material = [0u8; 32];
335    key_material.copy_from_slice(&hmac_result[..32]);
336    let scalar = reduce_mod_order(&key_material);
337
338    // Add the key material to parent private key
339    parent_key.add_tweak(&scalar)
340}
341
342/// Derives a child public key from a parent public key and emulated merkle root
343/// This allows public key derivation without access to private keys.
344fn derive_child_public_key(
345    parent_public: PublicKey,
346    emulated_merkle_root: [u8; 32],
347) -> Result<PublicKey, secp256k1::Error> {
348    let secp = Secp256k1::new();
349
350    // Create HMAC-SHA512 with parent public key as key
351    let mut mac = Hmac::<Sha512>::new_from_slice(&parent_public.serialize())
352        .expect("PublicKey serialization should always be non-empty");
353    mac.update(&emulated_merkle_root);
354    let hmac_result = mac.finalize().into_bytes();
355
356    // Use first 32 bytes as scalar for point multiplication
357    let mut key_material = [0u8; 32];
358    key_material.copy_from_slice(&hmac_result[..32]);
359    let scalar = reduce_mod_order(&key_material);
360
361    // Add scalar * G to parent public key
362    parent_public.add_exp_tweak(&secp, &scalar)
363}
364
365/// Safely reduces a 32-byte array modulo the secp256k1 curve order
366fn reduce_mod_order(bytes: &[u8; 32]) -> Scalar {
367    // Keep trying to create a scalar until we get a valid one
368    // In practice, this loop will almost always execute only once
369    let mut attempt = *bytes;
370    loop {
371        match Scalar::from_be_bytes(attempt) {
372            Ok(scalar) => return scalar,
373            Err(_) => {
374                // If the value is too large, subtract the curve order
375                // This is equivalent to modular reduction
376                attempt = subtract_curve_order(&attempt);
377            }
378        }
379    }
380}
381
382/// Subtract the secp256k1 curve order from a 32-byte big-endian number
383fn subtract_curve_order(bytes: &[u8; 32]) -> [u8; 32] {
384    let value = BigUint::from_bytes_be(bytes);
385    let order = BigUint::from_bytes_be(&CURVE_ORDER);
386    let reduced = value % order;
387
388    let mut result = [0u8; 32];
389    let reduced_bytes = reduced.to_bytes_be();
390    let offset = 32 - reduced_bytes.len();
391    result[offset..].copy_from_slice(&reduced_bytes);
392    result
393}
394
395impl fmt::Display for Error {
396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397        match self {
398            Error::VerificationFailed(e) => {
399                write!(f, "Verification failed: {e}")
400            }
401            Error::Secp256k1(e) => {
402                write!(f, "Secp256k1 cryptographic operation failed: {e}")
403            }
404            Error::NotTaprootSpend => {
405                write!(f, "Input is not a taproot spend")
406            }
407            Error::NotScriptPathSpend => {
408                write!(
409                    f,
410                    "Input is not a script path spend (missing taproot control block)"
411                )
412            }
413            Error::InvalidAmount(e) => {
414                write!(f, "Invalid amount: {e}")
415            }
416            Error::InvalidControlBlock => {
417                write!(f, "Input has invalid control block")
418            }
419            Error::DeserializationFailed(e) => {
420                write!(f, "Failed to deserialize: {e}")
421            }
422            Error::InvalidSighash => {
423                write!(f, "Unable to calculate sighash for input")
424            }
425            Error::SpentOutputsMismatch => {
426                write!(
427                    f,
428                    "Mismatch between number of emulated and actual spent outputs"
429                )
430            }
431            Error::InputIndexOutOfBounds => {
432                write!(f, "Input index out of bounds")
433            }
434        }
435    }
436}
437
438#[cfg(feature = "bitcoinkernel")]
439impl From<KernelError> for Error {
440    fn from(error: KernelError) -> Self {
441        Error::VerificationFailed(error.to_string())
442    }
443}
444
445impl From<secp256k1::Error> for Error {
446    fn from(error: secp256k1::Error) -> Self {
447        Error::Secp256k1(error)
448    }
449}
450
451impl From<bitcoin::consensus::encode::Error> for Error {
452    fn from(error: bitcoin::consensus::encode::Error) -> Self {
453        Error::DeserializationFailed(error)
454    }
455}
456
457impl From<bitcoin_units::amount::OutOfRangeError> for Error {
458    fn from(error: bitcoin_units::amount::OutOfRangeError) -> Self {
459        Error::InvalidAmount(error)
460    }
461}
462
463#[cfg(test)]
464#[cfg(feature = "bitcoinkernel")]
465mod kernel_tests {
466    use super::*;
467    use bitcoin::{
468        Address, Amount, Network, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut, Txid,
469        Witness,
470        consensus::encode::serialize,
471        hashes::Hash,
472        key::UntweakedPublicKey,
473        taproot::{LeafVersion, TaprootBuilder},
474    };
475
476    fn create_test_transaction_single_input() -> Transaction {
477        Transaction {
478            version: bitcoin::transaction::Version::TWO,
479            lock_time: bitcoin::locktime::absolute::LockTime::ZERO,
480            input: vec![TxIn {
481                previous_output: OutPoint::null(),
482                script_sig: ScriptBuf::new(),
483                sequence: bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME,
484                witness: Witness::new(),
485            }],
486            output: vec![TxOut {
487                value: Amount::from_sat(100000),
488                script_pubkey: ScriptBuf::new_op_return([]),
489            }],
490        }
491    }
492
493    fn create_test_transaction_multi_input() -> Transaction {
494        Transaction {
495            version: bitcoin::transaction::Version::TWO,
496            lock_time: bitcoin::locktime::absolute::LockTime::ZERO,
497            input: vec![
498                TxIn {
499                    previous_output: OutPoint::null(),
500                    script_sig: ScriptBuf::new(),
501                    sequence: bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME,
502                    witness: Witness::new(),
503                },
504                TxIn {
505                    previous_output: OutPoint::new(Txid::all_zeros(), 1),
506                    script_sig: ScriptBuf::new(),
507                    sequence: bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME,
508                    witness: Witness::new(),
509                },
510            ],
511            output: vec![TxOut {
512                value: Amount::from_sat(100000),
513                script_pubkey: ScriptBuf::new_op_return([]),
514            }],
515        }
516    }
517
518    #[test]
519    fn test_not_taproot_input() {
520        let result = verify_and_sign(
521            &DefaultVerifier,
522            ScriptBuf::new_op_return([]).as_bytes(),
523            0,
524            &[],
525            0,
526            &[],
527            &[],
528            &[1u8; 32],
529            SecretKey::from_slice(&[1u8; 32]).unwrap(),
530            None,
531        );
532
533        assert!(matches!(result, Err(Error::NotTaprootSpend)));
534    }
535
536    #[test]
537    fn test_unable_to_deserialize_tx() {
538        let secp = Secp256k1::new();
539        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
540        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
541        let address = Address::p2tr(&secp, internal_key, None, Network::Bitcoin);
542
543        let result = verify_and_sign(
544            &DefaultVerifier,
545            address.script_pubkey().as_bytes(),
546            0,
547            &[],
548            0,
549            &[],
550            &[],
551            &[1u8; 32],
552            SecretKey::from_slice(&[1u8; 32]).unwrap(),
553            None,
554        );
555
556        assert!(matches!(result, Err(Error::DeserializationFailed(_))));
557    }
558
559    #[test]
560    fn test_spent_output_mismatch() {
561        let secp = Secp256k1::new();
562        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
563        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
564        let address = Address::p2tr(&secp, internal_key, None, Network::Bitcoin);
565
566        let result = verify_and_sign(
567            &DefaultVerifier,
568            address.script_pubkey().as_bytes(),
569            0,
570            &serialize(&create_test_transaction_single_input()),
571            0,
572            &[],
573            &[],
574            &[1u8; 32],
575            SecretKey::from_slice(&[1u8; 32]).unwrap(),
576            None,
577        );
578
579        assert!(matches!(result, Err(Error::SpentOutputsMismatch)));
580
581        let txout = TxOut {
582            value: Amount::from_sat(100000),
583            script_pubkey: ScriptBuf::new_op_return([]),
584        };
585        let result = verify_and_sign(
586            &DefaultVerifier,
587            address.script_pubkey().as_bytes(),
588            0,
589            &serialize(&create_test_transaction_single_input()),
590            0,
591            std::slice::from_ref(&txout),
592            &[],
593            &[1u8; 32],
594            SecretKey::from_slice(&[1u8; 32]).unwrap(),
595            None,
596        );
597
598        assert!(matches!(result, Err(Error::SpentOutputsMismatch)));
599
600        let result = verify_and_sign(
601            &DefaultVerifier,
602            address.script_pubkey().as_bytes(),
603            0,
604            &serialize(&create_test_transaction_single_input()),
605            0,
606            &[txout.clone(), txout.clone()],
607            &[txout.clone(), txout.clone()],
608            &[1u8; 32],
609            SecretKey::from_slice(&[1u8; 32]).unwrap(),
610            None,
611        );
612
613        assert!(matches!(result, Err(Error::SpentOutputsMismatch)));
614
615        let result = verify_and_sign(
616            &DefaultVerifier,
617            address.script_pubkey().as_bytes(),
618            0,
619            &serialize(&create_test_transaction_single_input()),
620            0,
621            std::slice::from_ref(&txout),
622            &[TxOut {
623                value: Amount::from_sat(200000),
624                script_pubkey: ScriptBuf::new_op_return([]),
625            }],
626            &[1u8; 32],
627            SecretKey::from_slice(&[1u8; 32]).unwrap(),
628            None,
629        );
630
631        assert!(matches!(result, Err(Error::SpentOutputsMismatch)));
632    }
633
634    #[test]
635    fn test_input_index_out_of_bounds() {
636        let secp = Secp256k1::new();
637        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
638        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
639        let address = Address::p2tr(&secp, internal_key, None, Network::Bitcoin);
640
641        let txout = TxOut {
642            value: Amount::from_sat(100000),
643            script_pubkey: ScriptBuf::new_op_return([]),
644        };
645        let result = verify_and_sign(
646            &DefaultVerifier,
647            address.script_pubkey().as_bytes(),
648            0,
649            &serialize(&create_test_transaction_single_input()),
650            1,
651            std::slice::from_ref(&txout),
652            std::slice::from_ref(&txout),
653            &[1u8; 32],
654            SecretKey::from_slice(&[1u8; 32]).unwrap(),
655            None,
656        );
657
658        assert!(matches!(result, Err(Error::InputIndexOutOfBounds)));
659    }
660
661    #[test]
662    fn test_verify_and_sign_single_input_single_leaf() {
663        let secp = Secp256k1::new();
664
665        // 1. Create a dummy internal key
666        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
667        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
668
669        // 2. Create OP_TRUE script leaf
670        let op_true_script = Script::builder()
671            .push_opcode(bitcoin::opcodes::OP_TRUE)
672            .into_script();
673
674        // 3. Build the taproot tree with single OP_TRUE leaf
675        let taproot_builder = TaprootBuilder::new()
676            .add_leaf(0, op_true_script.clone())
677            .unwrap();
678        let taproot_spend_info = taproot_builder.finalize(&secp, internal_key).unwrap();
679
680        // 4. Create the emulated P2TR outputs
681        let emulated_address = Address::p2tr(
682            &secp,
683            internal_key,
684            taproot_spend_info.merkle_root(),
685            Network::Bitcoin,
686        );
687        let emulated_spent_outputs = [TxOut {
688            value: Amount::from_sat(100_000),
689            script_pubkey: emulated_address.script_pubkey(),
690        }];
691
692        // 5. Get the control block for our OP_TRUE leaf
693        let control_block = taproot_spend_info
694            .control_block(&(op_true_script.clone(), LeafVersion::TapScript))
695            .unwrap();
696
697        // 6. Create the witness stack for script path spending
698        let mut witness = Witness::new();
699        witness.push(op_true_script.as_bytes());
700        witness.push(control_block.serialize());
701
702        // 7. Create emulated transaction
703        let mut emulated_tx = create_test_transaction_single_input();
704        emulated_tx.input[0].witness = witness;
705
706        // 8. Create actual child secret
707        let aux_rand = [1u8; 32];
708        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
709        let child_secret = derive_child_secret_key(
710            parent_secret,
711            taproot_spend_info.merkle_root().unwrap().to_byte_array(),
712        )
713        .unwrap();
714
715        // 9. Create actual P2TR outputs
716        let actual_internal_key = XOnlyPublicKey::from(child_secret.public_key(&secp));
717        let actual_address = Address::p2tr(&secp, actual_internal_key, None, Network::Bitcoin);
718        let mut actual_spent_outputs = emulated_spent_outputs.clone();
719        actual_spent_outputs[0].script_pubkey = actual_address.script_pubkey();
720
721        // 10. Verify and sign actual transaction
722        let actual_tx = verify_and_sign(
723            &DefaultVerifier,
724            emulated_address.script_pubkey().as_bytes(),
725            100_000,
726            &serialize(&emulated_tx),
727            0,
728            &emulated_spent_outputs,
729            &actual_spent_outputs,
730            &aux_rand,
731            parent_secret,
732            None,
733        )
734        .unwrap();
735
736        let mut actual_outputs = Vec::new();
737        for txout in actual_spent_outputs {
738            let amount = txout.value.to_signed().unwrap().to_sat();
739            let script =
740                bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes()).unwrap();
741            actual_outputs.push(bitcoinkernel::TxOut::new(&script, amount));
742        }
743
744        // 11. Verify the actual transaction was properly signed
745        let verify_result = bitcoinkernel::verify(
746            &bitcoinkernel::ScriptPubkey::try_from(actual_address.script_pubkey().as_bytes())
747                .unwrap(),
748            Some(100_000),
749            &bitcoinkernel::Transaction::try_from(serialize(&actual_tx).as_slice()).unwrap(),
750            0,
751            None,
752            &actual_outputs,
753        );
754
755        assert!(verify_result.is_ok());
756        assert_eq!(actual_tx.input[0].witness.len(), 1)
757    }
758
759    #[test]
760    fn test_verify_and_sign_single_input_multiple_leaves() {
761        let secp = Secp256k1::new();
762
763        // 1. Create a dummy internal key
764        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
765        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
766
767        // 2. Create script leaves
768        let op_true_script = Script::builder()
769            .push_opcode(bitcoin::opcodes::OP_TRUE)
770            .into_script();
771        let op_false_script = Script::builder()
772            .push_opcode(bitcoin::opcodes::OP_FALSE)
773            .into_script();
774
775        // 3. Build the taproot tree with two leaves
776        let taproot_builder = TaprootBuilder::new()
777            .add_leaf(1, op_true_script.clone())
778            .unwrap()
779            .add_leaf(1, op_false_script.clone())
780            .unwrap();
781        let taproot_spend_info = taproot_builder.finalize(&secp, internal_key).unwrap();
782
783        // 4. Create the emulated P2TR outputs
784        let emulated_address = Address::p2tr(
785            &secp,
786            internal_key,
787            taproot_spend_info.merkle_root(),
788            Network::Bitcoin,
789        );
790        let emulated_spent_outputs = [TxOut {
791            value: Amount::from_sat(100_000),
792            script_pubkey: emulated_address.script_pubkey(),
793        }];
794
795        // 5. Get the control block for our OP_TRUE leaf
796        let control_block = taproot_spend_info
797            .control_block(&(op_true_script.clone(), LeafVersion::TapScript))
798            .unwrap();
799
800        // 6. Create the witness stack for script path spending
801        let mut witness = Witness::new();
802        witness.push(op_true_script.as_bytes());
803        witness.push(control_block.serialize());
804
805        // 7. Create emulated transaction
806        let mut emulated_tx = create_test_transaction_single_input();
807        emulated_tx.input[0].witness = witness;
808
809        // 8. Create actual child secret
810        let aux_rand = [1u8; 32];
811        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
812        let child_secret = derive_child_secret_key(
813            parent_secret,
814            taproot_spend_info.merkle_root().unwrap().to_byte_array(),
815        )
816        .unwrap();
817
818        // 9. Create actual P2TR outputs
819        let actual_internal_key = XOnlyPublicKey::from(child_secret.public_key(&secp));
820        let actual_address = Address::p2tr(&secp, actual_internal_key, None, Network::Bitcoin);
821        let mut actual_spent_outputs = emulated_spent_outputs.clone();
822        actual_spent_outputs[0].script_pubkey = actual_address.script_pubkey();
823
824        // 10. Verify and sign actual transaction
825        let actual_tx = verify_and_sign(
826            &DefaultVerifier,
827            emulated_address.script_pubkey().as_bytes(),
828            100_000,
829            &serialize(&emulated_tx),
830            0,
831            &emulated_spent_outputs,
832            &actual_spent_outputs,
833            &aux_rand,
834            parent_secret,
835            None,
836        )
837        .unwrap();
838
839        let mut actual_outputs = Vec::new();
840        for txout in actual_spent_outputs {
841            let amount = txout.value.to_signed().unwrap().to_sat();
842            let script =
843                bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes()).unwrap();
844            actual_outputs.push(bitcoinkernel::TxOut::new(&script, amount));
845        }
846
847        // 11. Verify the actual transaction was properly signed
848        let verify_result = bitcoinkernel::verify(
849            &bitcoinkernel::ScriptPubkey::try_from(actual_address.script_pubkey().as_bytes())
850                .unwrap(),
851            Some(100_000),
852            &bitcoinkernel::Transaction::try_from(serialize(&actual_tx).as_slice()).unwrap(),
853            0,
854            None,
855            &actual_outputs,
856        );
857
858        assert!(verify_result.is_ok());
859        assert_eq!(actual_tx.input[0].witness.len(), 1)
860    }
861
862    #[test]
863    fn test_verify_and_sign_single_input_with_backup() {
864        let secp = Secp256k1::new();
865
866        // 1. Create a dummy internal key
867        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
868        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
869
870        // 2. Create OP_TRUE script leaf
871        let op_true_script = Script::builder()
872            .push_opcode(bitcoin::opcodes::OP_TRUE)
873            .into_script();
874
875        // 3. Build the taproot tree with single OP_TRUE leaf
876        let taproot_builder = TaprootBuilder::new()
877            .add_leaf(0, op_true_script.clone())
878            .unwrap();
879        let taproot_spend_info = taproot_builder.finalize(&secp, internal_key).unwrap();
880
881        // 4. Create the emulated P2TR outputs
882        let emulated_address = Address::p2tr(
883            &secp,
884            internal_key,
885            taproot_spend_info.merkle_root(),
886            Network::Bitcoin,
887        );
888        let emulated_spent_outputs = [TxOut {
889            value: Amount::from_sat(100_000),
890            script_pubkey: emulated_address.script_pubkey(),
891        }];
892
893        // 5. Get the control block for our OP_TRUE leaf
894        let control_block = taproot_spend_info
895            .control_block(&(op_true_script.clone(), LeafVersion::TapScript))
896            .unwrap();
897
898        // 6. Create the witness stack for script path spending
899        let mut witness = Witness::new();
900        witness.push(op_true_script.as_bytes());
901        witness.push(control_block.serialize());
902
903        // 7. Create emulated transaction
904        let mut emulated_tx = create_test_transaction_single_input();
905        emulated_tx.input[0].witness = witness;
906
907        // 8. Create actual child secret
908        let aux_rand = [1u8; 32];
909        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
910        let child_secret = derive_child_secret_key(
911            parent_secret,
912            taproot_spend_info.merkle_root().unwrap().to_byte_array(),
913        )
914        .unwrap();
915
916        // 9. Create actual P2TR outputs
917        let actual_backup_merkle_root = taproot_spend_info.merkle_root();
918        let actual_internal_key = XOnlyPublicKey::from(child_secret.public_key(&secp));
919        let actual_address = Address::p2tr(
920            &secp,
921            actual_internal_key,
922            actual_backup_merkle_root,
923            Network::Bitcoin,
924        );
925        let mut actual_spent_outputs = emulated_spent_outputs.clone();
926        actual_spent_outputs[0].script_pubkey = actual_address.script_pubkey();
927
928        // 10. Verify and sign actual transaction
929        let actual_tx = verify_and_sign(
930            &DefaultVerifier,
931            emulated_address.script_pubkey().as_bytes(),
932            100_000,
933            &serialize(&emulated_tx),
934            0,
935            &emulated_spent_outputs,
936            &actual_spent_outputs,
937            &aux_rand,
938            parent_secret,
939            actual_backup_merkle_root,
940        )
941        .unwrap();
942
943        let mut actual_outputs = Vec::new();
944        for txout in actual_spent_outputs {
945            let amount = txout.value.to_signed().unwrap().to_sat();
946            let script =
947                bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes()).unwrap();
948            actual_outputs.push(bitcoinkernel::TxOut::new(&script, amount));
949        }
950
951        // 11. Verify the actual transaction was properly signed
952        let verify_result = bitcoinkernel::verify(
953            &bitcoinkernel::ScriptPubkey::try_from(actual_address.script_pubkey().as_bytes())
954                .unwrap(),
955            Some(100_000),
956            &bitcoinkernel::Transaction::try_from(serialize(&actual_tx).as_slice()).unwrap(),
957            0,
958            None,
959            &actual_outputs,
960        );
961
962        assert!(verify_result.is_ok());
963        assert_eq!(actual_tx.input[0].witness.len(), 1)
964    }
965
966    #[test]
967    fn test_verify_and_sign_multi_input_tx() {
968        let secp = Secp256k1::new();
969
970        // 1. Create a dummy internal key
971        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
972        let internal_key = UntweakedPublicKey::from(internal_secret.public_key(&secp));
973
974        // 2. Create OP_TRUE script leaf
975        let op_true_script = Script::builder()
976            .push_opcode(bitcoin::opcodes::OP_TRUE)
977            .into_script();
978
979        // 3. Build the taproot tree with single OP_TRUE leaf
980        let taproot_builder = TaprootBuilder::new()
981            .add_leaf(0, op_true_script.clone())
982            .unwrap();
983        let taproot_spend_info = taproot_builder.finalize(&secp, internal_key).unwrap();
984
985        // 4. Create the emulated P2TR outputs
986        let emulated_address = Address::p2tr(
987            &secp,
988            internal_key,
989            taproot_spend_info.merkle_root(),
990            Network::Bitcoin,
991        );
992        let emulated_spent_outputs = [
993            TxOut {
994                value: Amount::from_sat(200_000),
995                script_pubkey: emulated_address.script_pubkey(),
996            },
997            TxOut {
998                value: Amount::from_sat(100_000),
999                script_pubkey: emulated_address.script_pubkey(),
1000            },
1001        ];
1002
1003        // 5. Get the control block for our OP_TRUE leaf
1004        let control_block = taproot_spend_info
1005            .control_block(&(op_true_script.clone(), LeafVersion::TapScript))
1006            .unwrap();
1007
1008        // 6. Create the witness stack for script path spending
1009        let mut witness = Witness::new();
1010        witness.push(op_true_script.as_bytes());
1011        witness.push(control_block.serialize());
1012
1013        // 7. Create emulated transaction
1014        let mut emulated_tx = create_test_transaction_multi_input();
1015        emulated_tx.input[1].witness = witness;
1016
1017        // 8. Create actual child secret
1018        let aux_rand = [1u8; 32];
1019        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
1020        let child_secret = derive_child_secret_key(
1021            parent_secret,
1022            taproot_spend_info.merkle_root().unwrap().to_byte_array(),
1023        )
1024        .unwrap();
1025
1026        // 9. Create actual P2TR outputs
1027        let actual_internal_key = XOnlyPublicKey::from(child_secret.public_key(&secp));
1028        let actual_address = Address::p2tr(&secp, actual_internal_key, None, Network::Bitcoin);
1029        let mut actual_spent_outputs = emulated_spent_outputs.clone();
1030        actual_spent_outputs[1].script_pubkey = actual_address.script_pubkey();
1031
1032        // 10. Verify and sign actual transaction
1033        let actual_tx = verify_and_sign(
1034            &DefaultVerifier,
1035            emulated_address.script_pubkey().as_bytes(),
1036            100_000,
1037            &serialize(&emulated_tx),
1038            1,
1039            &emulated_spent_outputs,
1040            &actual_spent_outputs,
1041            &aux_rand,
1042            parent_secret,
1043            None,
1044        )
1045        .unwrap();
1046
1047        let mut actual_outputs = Vec::new();
1048        for txout in actual_spent_outputs {
1049            let amount = txout.value.to_signed().unwrap().to_sat();
1050            let script =
1051                bitcoinkernel::ScriptPubkey::try_from(txout.script_pubkey.as_bytes()).unwrap();
1052            actual_outputs.push(bitcoinkernel::TxOut::new(&script, amount));
1053        }
1054
1055        // 11. Verify the actual transaction was properly signed
1056        let verify_result = bitcoinkernel::verify(
1057            &bitcoinkernel::ScriptPubkey::try_from(actual_address.script_pubkey().as_bytes())
1058                .unwrap(),
1059            Some(100_000),
1060            &bitcoinkernel::Transaction::try_from(serialize(&actual_tx).as_slice()).unwrap(),
1061            1,
1062            None,
1063            &actual_outputs,
1064        );
1065
1066        assert!(verify_result.is_ok());
1067        assert_eq!(actual_tx.input[1].witness.len(), 1)
1068    }
1069}
1070
1071#[cfg(test)]
1072mod non_kernel_tests {
1073    use super::*;
1074    use bitcoin::{
1075        Script,
1076        key::{Secp256k1, UntweakedPublicKey},
1077        taproot::TaprootBuilder,
1078    };
1079
1080    #[test]
1081    fn test_generate_address() {
1082        let secp = Secp256k1::new();
1083
1084        // 1. Create emulated script
1085        let emulated_script = Script::builder()
1086            .push_opcode(bitcoin::opcodes::OP_TRUE)
1087            .into_script();
1088
1089        // 2. Build the taproot tree and create emulated merkle root
1090        let taproot_builder = TaprootBuilder::new()
1091            .add_leaf(0, emulated_script.clone())
1092            .unwrap();
1093        let dummy_internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
1094        let dummy_internal_key = UntweakedPublicKey::from(dummy_internal_secret.public_key(&secp));
1095        let taproot_spend_info = taproot_builder.finalize(&secp, dummy_internal_key).unwrap();
1096        let emulated_merkle_root = taproot_spend_info.merkle_root().unwrap();
1097
1098        // 3. Create backup merkle root
1099        let backup_merkle_root = emulated_merkle_root;
1100
1101        // 4. Generate an on-chain address
1102        let internal_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
1103        let master_public_key: PublicKey = internal_secret.public_key(&secp);
1104        let onchain_address = generate_address(
1105            master_public_key,
1106            emulated_merkle_root,
1107            Some(backup_merkle_root),
1108            Network::Bitcoin,
1109        );
1110
1111        assert!(onchain_address.is_ok());
1112    }
1113
1114    #[test]
1115    fn test_public_private_key_derivation_consistency() {
1116        let parent_secret = SecretKey::from_slice(&[1u8; 32]).unwrap();
1117        let parent_public = parent_secret.public_key(&Secp256k1::new());
1118        let merkle_root = [42u8; 32];
1119
1120        let child_secret = derive_child_secret_key(parent_secret, merkle_root).unwrap();
1121        let child_public_from_secret = child_secret.public_key(&Secp256k1::new());
1122        let child_public_direct = derive_child_public_key(parent_public, merkle_root).unwrap();
1123
1124        assert_eq!(child_public_from_secret, child_public_direct);
1125    }
1126
1127    #[test]
1128    fn test_curve_order_reduction() {
1129        let max_bytes = [0xFF; 32];
1130        let reduced = reduce_mod_order(&max_bytes);
1131        // Should not panic and should be valid scalar
1132        #[allow(clippy::useless_conversion)]
1133        let _ = Scalar::from(reduced);
1134    }
1135}