From: Vihiga Tyonum Date: Sat, 4 Jul 2026 11:01:20 +0000 (+0100) Subject: ref(compile):compile tr desc with rand unspend key X-Git-Url: http://internal-gitweb-vhost/blockdata/script/encode/-script/display/struct.ScriptHash.html?a=commitdiff_plain;h=249948eac1ceb7b83b76d03751669ed0dbde7a35;p=bdk-cli ref(compile):compile tr desc with rand unspend key - add compile taproot descriptor with randomized unspendable internal key --- diff --git a/src/handlers/descriptor.rs b/src/handlers/descriptor.rs index 1396c88..1c949cd 100644 --- a/src/handlers/descriptor.rs +++ b/src/handlers/descriptor.rs @@ -15,7 +15,11 @@ use clap::Parser; #[cfg(feature = "compiler")] use { bdk_wallet::{ - bitcoin::XOnlyPublicKey, + bitcoin::{ + XOnlyPublicKey, + key::{Parity, rand}, + secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}, + }, miniscript::{Descriptor, Miniscript, descriptor::TapTree, policy::Concrete}, }, std::{str::FromStr, sync::Arc}, @@ -89,15 +93,32 @@ impl AppCommand> for CompileCommand { .compile() .map_err(|e| Error::Generic(e.to_string()))?; + let mut r = None; + let descriptor = match self.script_type.as_str() { "sh" => Descriptor::new_sh(legacy_policy), "wsh" => Descriptor::new_wsh(segwit_policy), "sh-wsh" => Descriptor::new_sh_wsh(segwit_policy), "tr" => { - let xonly_public_key = XOnlyPublicKey::from_str(NUMS_UNSPENDABLE_KEY_HEX) + // Use a randomized unspendable internal key (H + rG) instead of a fixed NUMS + // point. This improves privacy by preventing observers from determining whether + // key-path spending is disabled. `r` is returned so the user can verify the key + // is derived from the NUMS point. See BIP-341. + let secp = Secp256k1::new(); + let r_secret = SecretKey::new(&mut rand::thread_rng()); + r = Some(r_secret.display_secret().to_string()); + + let nums_key = XOnlyPublicKey::from_str(NUMS_UNSPENDABLE_KEY_HEX) .map_err(|e| Error::Generic(format!("Invalid NUMS key: {e}")))?; + let nums_point = PublicKey::from_x_only_public_key(nums_key, Parity::Even); + + let internal_key_point = nums_point + .add_exp_tweak(&secp, &Scalar::from(r_secret)) + .map_err(|e| Error::Generic(format!("Failed to tweak NUMS key: {e}")))?; + let (xonly_internal_key, _) = internal_key_point.x_only_public_key(); + let tree = TapTree::Leaf(Arc::new(taproot_policy)); - Descriptor::new_tr(xonly_public_key.to_string(), Some(tree)) + Descriptor::new_tr(xonly_internal_key.to_string(), Some(tree)) } _ => { return Err(Error::Generic( @@ -113,6 +134,7 @@ impl AppCommand> for CompileCommand { public_descriptors: None, private_descriptors: None, fingerprint: None, + r, }) } } diff --git a/src/utils/descriptors.rs b/src/utils/descriptors.rs index 04391e5..44e795f 100644 --- a/src/utils/descriptors.rs +++ b/src/utils/descriptors.rs @@ -102,6 +102,7 @@ fn generate_private_descriptors( }), mnemonic: None, fingerprint: Some(fingerprint.to_string()), + r: None, }) } @@ -139,6 +140,7 @@ pub fn generate_public_descriptors( private_descriptors: None, mnemonic: None, fingerprint: Some(fingerprint.to_string()), + r: None, }) } diff --git a/src/utils/types.rs b/src/utils/types.rs index a8e2998..6f8cf09 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -170,6 +170,10 @@ pub struct DescriptorResult { #[serde(skip_serializing_if = "Option::is_none")] pub fingerprint: Option, + + /// Randomness factor `r` used to derive the taproot unspendable internal key (H + rG). + #[serde(skip_serializing_if = "Option::is_none")] + pub r: Option, } #[derive(Serialize)]