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