Crates.io | confidential-script-lib |
lib.rs | confidential-script-lib |
version | 0.2.2 |
created_at | 2025-08-12 15:08:08.294775+00 |
updated_at | 2025-08-29 20:31:49.293943+00 |
description | Emulate Bitcoin script by converting script-path spends to key-path spends |
homepage | https://github.com/joshdoman/confidential-script-lib |
repository | https://github.com/joshdoman/confidential-script-lib |
max_upload_size | |
id | 1792103 |
size | 83,154 |
confidential-script-lib
is a Rust library that emulates 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.
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.
The library operates on a two-step process: emulation and signing.
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).
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.
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.
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.
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.
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.
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.
This library is intended to be used within a Nitro Enclave, integrated with KMS such that any AWS account can provision an identical enclave with the same master private key. For maximum security, the KMS key should be created with an irrevocable policy that makes it un-deletable and accessible only to enclaves running a specific, verified image. Crucially, it should allow requests from any AWS account that meets this strict criteria, so that usage is permissionless.
To generate the master secret, an enclave should generate a random secret and use GenerateDataKey
to encrypt it using KMS. To provision a different enclave with the secret, the user should provide the enclave the encrypted encryption key and the encrypted secret. The enclave can then decrypt the encryption key with KMS using Decrypt
and subequently decrypt the secret.
Finally, the enclave should be able to expose the master public key, so that users can independently derive the on-chain address they should send funds to.
/// Trait to abstract the behavior of the bitcoin script verifier, allowing
/// users to provide their own verifier.
pub trait Verifier {
/// Verify a bitcoin script, mirroring the API of `bitcoinkernel::verify`.
///
/// # Arguments
/// * `script_pubkey` - The script public key to verify.
/// * `amount` - The amount of the input being spent.
/// * `tx_to` - The transaction containing the script.
/// * `input_index` - The index of the input to verify.
/// * `spent_outputs` - The outputs being spent by the transaction.
/// * `tx_weight` - The weight of the transaction.
///
/// # Errors
/// Returns `Error` if verification fails.
fn verify(
&self,
script_pubkey: &[u8],
amount: Option<i64>,
tx_to: &[u8],
input_index: u32,
spent_outputs: &[TxOut],
tx_weight: Weight,
) -> Result<(), Error>;
}
/// The default `Verifier` implementation that uses `bitcoinkernel`.
pub struct DefaultVerifier;
/// Verifies an emulated Bitcoin script and signs the corresponding transaction.
///
/// This function performs script verification using bitcoinkernel, verifying an
/// emulated P2TR input. If successful, it derives an XOnlyPublicKey from the
/// parent key and the emulated merkle root, which is then tweaked with an optional
/// backup merkle root to derive the actual spent UTXO, which is then key-path signed
/// with `SIGHASH_DEFAULT`.
///
/// If the emulated script-path spend includes a data-carrying annex (begins with 0x50
/// followed by 0x00), the annex is included in the key-path spend. Otherwise, the annex
/// is dropped.
///
/// # Arguments
/// * `verifier` - The verifier to use for script validation
/// * `input_index` - Index of the input to verify and sign (0-based)
/// * `emulated_tx_to` - Serialized transaction to verify and sign
/// * `actual_spent_outputs` - Actual outputs being spent
/// * `aux_rand` - Auxiliary random data for signing
/// * `parent_key` - Parent secret key used to derive child key for signing
/// * `backup_merkle_root` - Optional merkle root for backup script path spending
///
/// # Errors
/// Returns error if verification fails, key derivation fails, or signing fails
pub fn verify_and_sign<V: Verifier>(
verifier: &V,
input_index: u32,
emulated_tx_to: &[u8],
actual_spent_outputs: &[TxOut],
aux_rand: &[u8; 32],
parent_key: SecretKey,
backup_merkle_root: Option<TapNodeHash>,
) -> Result<Transaction, Error>;
/// Generates P2TR address from a parent public key and the emulated merkle root,
/// with an optional backup merkle root.
///
/// # Arguments
/// * `parent_key` - The parent public key
/// * `emulated_merkle_root` - The merkle root of the emulated input
/// * `backup_merkle_root` - Optional merkle root for backup script path spending
/// * `network` - The network to generate the address for
///
/// # Errors
/// Returns an error if key derivation fails
fn generate_address(
parent_key: PublicKey,
emulated_merkle_root: TapNodeHash,
backup_merkle_root: Option<TapNodeHash>,
network: Network,
) -> Result<Address, secp256k1::Error>;
The default Verifier
implementation is based on bitcoinkernel
, which is an optional feature but required to run the included tests.
Use the following command to run the test suite:
cargo test --features bitcoinkernel
Or run:
cargo test --all-features
This project is licensed under the CC0-1.0 License.
Joshua Doman joshsdoman@gmail.com