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