Accept flexible input types for Taproot-related functions

Refactor Taproot functions to accept any type implementing `Into<XOnlyPublicKey>`,
instead of requiring `XOnlyPublicKey` directly. This improves ergonomics when working
with compatible types, avoiding unnecessary `.into()` conversions at call sites.
This commit is contained in:
Erick Cestari 2025-04-28 14:28:11 -03:00
parent 2a518d62e6
commit c11772a768
No known key found for this signature in database
GPG Key ID: D7D17E26F2FC3F3C
14 changed files with 65 additions and 46 deletions

View File

@ -26,7 +26,7 @@ fn main() {
// Get an unspent output that is locked to the key above that we control.
// In a real application these would come from the chain.
let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&secp, internal_key.into());
let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&secp, internal_key);
// Get an address to send to.
let address = receivers_address();
@ -45,7 +45,7 @@ fn main() {
// The change output is locked to a key controlled by us.
let change = TxOut {
value: CHANGE_AMOUNT,
script_pubkey: ScriptBuf::new_p2tr(&secp, internal_key.into(), None), // Change comes back to us.
script_pubkey: ScriptBuf::new_p2tr(&secp, internal_key, None), // Change comes back to us.
};
// The transaction we want to sign and broadcast.
@ -113,10 +113,11 @@ fn receivers_address() -> Address {
///
/// This output is locked to keys that we control, in a real application this would be a valid
/// output taken from a transaction that appears in the chain.
fn dummy_unspent_transaction_output<C: Verification>(
fn dummy_unspent_transaction_output<C: Verification, K: Into<UntweakedPublicKey>>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
) -> (OutPoint, TxOut) {
let internal_key = internal_key.into();
let script_pubkey = ScriptBuf::new_p2tr(secp, internal_key, None);
let out_point = OutPoint {

View File

@ -83,11 +83,12 @@ fn get_internal_address_xpriv<C: Signing>(
}
// Get the Taproot Key Origin.
fn get_tap_key_origin(
x_only_key: UntweakedPublicKey,
fn get_tap_key_origin<K: Into<UntweakedPublicKey> + std::cmp::Ord>(
x_only_key: K,
master_fingerprint: Fingerprint,
path: DerivationPath,
) -> BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, (Fingerprint, DerivationPath))> {
let x_only_key = x_only_key.into();
let mut map = BTreeMap::new();
map.insert(x_only_key, (vec![], (master_fingerprint, path)));
map
@ -151,12 +152,12 @@ fn main() {
// Get the Tap Key Origins
// Map of tap root X-only keys to origin info and leaf hashes contained in it.
let origin_input_1 = get_tap_key_origin(
pk_input_1.into(),
pk_input_1,
MASTER_FINGERPRINT.parse::<Fingerprint>().unwrap(),
"m/86'/0'/0'/0/0".parse::<DerivationPath>().unwrap(),
);
let origin_input_2 = get_tap_key_origin(
pk_input_2.into(),
pk_input_2,
MASTER_FINGERPRINT.parse::<Fingerprint>().unwrap(),
"m/86'/0'/0'/1/0".parse::<DerivationPath>().unwrap(),
);
@ -187,7 +188,7 @@ fn main() {
// The change output is locked to a key controlled by us.
let change = TxOut {
value: CHANGE_AMOUNT,
script_pubkey: ScriptBuf::new_p2tr(&secp, pk_change.into(), None), // Change comes back to us.
script_pubkey: ScriptBuf::new_p2tr(&secp, pk_change, None), // Change comes back to us.
};
// The transaction we want to sign and broadcast.

View File

@ -404,7 +404,7 @@ impl BenefactorWallet {
let taproot_spend_info = TaprootBuilder::new()
.add_leaf(0, script.clone())?
.finalize(&self.secp, internal_keypair.x_only_public_key().0.into())
.finalize(&self.secp, internal_keypair.x_only_public_key().0)
.expect("should be finalizable");
self.current_spend_info = Some(taproot_spend_info.clone());
let script_pubkey = ScriptBuf::new_p2tr(
@ -502,7 +502,7 @@ impl BenefactorWallet {
let taproot_spend_info = TaprootBuilder::new()
.add_leaf(0, script.clone())?
.finalize(&self.secp, new_internal_keypair.x_only_public_key().0.into())
.finalize(&self.secp, new_internal_keypair.x_only_public_key().0)
.expect("should be finalizable");
self.current_spend_info = Some(taproot_spend_info.clone());
let prevout_script_pubkey = input.witness_utxo.as_ref().unwrap().script_pubkey.clone();

View File

@ -59,6 +59,7 @@ use crate::constants::{
};
use crate::crypto::key::{
CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey,
XOnlyPublicKey,
};
use crate::network::{Network, NetworkKind, Params};
use crate::prelude::{String, ToOwned};
@ -69,7 +70,6 @@ use crate::script::{
WitnessScriptSizeError,
};
use crate::taproot::TapNodeHash;
use crate::XOnlyPublicKey;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
@ -573,12 +573,13 @@ impl Address {
}
/// Constructs a new pay-to-Taproot (P2TR) [`Address`] from an untweaked key.
pub fn p2tr<C: Verification>(
pub fn p2tr<C: Verification, K: Into<UntweakedPublicKey>>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
merkle_root: Option<TapNodeHash>,
hrp: impl Into<KnownHrp>,
) -> Address {
let internal_key = internal_key.into();
let program = WitnessProgram::p2tr(secp, internal_key, merkle_root);
Address::from_witness_program(program, hrp)
}

View File

@ -49,11 +49,12 @@ define_extension_trait! {
/// Computes P2TR output with a given internal key and a single script spending path equal to
/// the current script, assuming that the script is a Tapscript.
fn to_p2tr<C: Verification>(
fn to_p2tr<C: Verification, K: Into<UntweakedPublicKey>>(
&self,
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
) -> ScriptBuf {
let internal_key = internal_key.into();
let leaf_hash = self.tapscript_leaf_hash();
let merkle_root = TapNodeHash::from(leaf_hash);
ScriptBuf::new_p2tr(secp, internal_key, Some(merkle_root))
@ -157,11 +158,12 @@ define_extension_trait! {
/// Generates P2TR for script spending path using an internal public key and some optional
/// script tree Merkle root.
fn new_p2tr<C: Verification>(
fn new_p2tr<C: Verification, K: Into<UntweakedPublicKey>>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
merkle_root: Option<TapNodeHash>,
) -> Self {
let internal_key = internal_key.into();
let (output_key, _) = internal_key.tap_tweak(secp, merkle_root);
// output key is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv1)
new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize())

View File

@ -15,11 +15,10 @@ use internals::array::ArrayExt;
use internals::write_err;
use secp256k1::Secp256k1;
use crate::crypto::key::{CompressedPublicKey, Keypair, PrivateKey};
use crate::crypto::key::{CompressedPublicKey, Keypair, PrivateKey, XOnlyPublicKey};
use crate::internal_macros::{impl_array_newtype, impl_array_newtype_stringify};
use crate::network::NetworkKind;
use crate::prelude::{String, Vec};
use crate::XOnlyPublicKey;
/// Version bytes for extended public keys on the Bitcoin network.
const VERSION_BYTES_MAINNET_PUBLIC: [u8; 4] = [0x04, 0x88, 0xB2, 0x1E];

View File

@ -95,11 +95,12 @@ impl WitnessProgram {
///
/// This function applies BIP341 key-tweaking to the untweaked
/// key using the merkle root, if it's present.
pub fn p2tr<C: Verification>(
pub fn p2tr<C: Verification, K: Into<UntweakedPublicKey>>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
merkle_root: Option<TapNodeHash>,
) -> Self {
let internal_key = internal_key.into();
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
let pubkey = output_key.to_inner().serialize();
WitnessProgram::new_p2tr(pubkey)

View File

@ -129,6 +129,16 @@ impl From<secp256k1::PublicKey> for XOnlyPublicKey {
fn from(pk: secp256k1::PublicKey) -> XOnlyPublicKey { XOnlyPublicKey::new(pk) }
}
impl fmt::LowerHex for XOnlyPublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0, f) }
}
// Allocate for serialized size
impl_to_hex_from_lower_hex!(XOnlyPublicKey, |_| constants::SCHNORR_PUBLIC_KEY_SIZE * 2);
impl fmt::Display for XOnlyPublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
/// A Bitcoin ECDSA public key.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PublicKey {
@ -842,13 +852,13 @@ pub type UntweakedPublicKey = XOnlyPublicKey;
pub struct TweakedPublicKey(XOnlyPublicKey);
impl fmt::LowerHex for TweakedPublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0 .0, f) }
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0, f) }
}
// Allocate for serialized size
impl_to_hex_from_lower_hex!(TweakedPublicKey, |_| constants::SCHNORR_PUBLIC_KEY_SIZE * 2);
impl fmt::Display for TweakedPublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0 .0, f) }
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
/// Untweaked BIP-340 key pair.

View File

@ -1881,8 +1881,8 @@ mod tests {
use secp256k1::SecretKey;
use crate::consensus::serde as con_serde;
use crate::crypto::key::XOnlyPublicKey;
use crate::taproot::{TapNodeHash, TapTweakHash};
use crate::XOnlyPublicKey;
#[derive(serde::Deserialize)]
struct UtxoSpent {

View File

@ -6,7 +6,7 @@ use core::str::FromStr;
use hashes::{hash160, ripemd160, sha256, sha256d};
use crate::bip32::KeySource;
use crate::crypto::key::PublicKey;
use crate::crypto::key::{PublicKey, XOnlyPublicKey};
use crate::crypto::{ecdsa, taproot};
use crate::prelude::{btree_map, BTreeMap, Borrow, Box, ToOwned, Vec};
use crate::psbt::map::Map;
@ -20,7 +20,6 @@ use crate::sighash::{
use crate::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapNodeHash};
use crate::transaction::{Transaction, TxOut};
use crate::witness::Witness;
use crate::XOnlyPublicKey;
/// Type: Non-Witness UTXO PSBT_IN_NON_WITNESS_UTXO = 0x00
const PSBT_IN_NON_WITNESS_UTXO: u64 = 0x00;

View File

@ -1,12 +1,12 @@
// SPDX-License-Identifier: CC0-1.0
use crate::bip32::KeySource;
use crate::crypto::key::XOnlyPublicKey;
use crate::prelude::{btree_map, BTreeMap, Vec};
use crate::psbt::map::Map;
use crate::psbt::{raw, Error};
use crate::script::ScriptBuf;
use crate::taproot::{TapLeafHash, TapTree};
use crate::XOnlyPublicKey;
/// Type: Redeem ScriptBuf PSBT_OUT_REDEEM_SCRIPT = 0x00
const PSBT_OUT_REDEEM_SCRIPT: u64 = 0x00;

View File

@ -13,7 +13,7 @@ use internals::slice::SliceExt;
use super::map::{Input, Map, Output, PsbtSighashType};
use crate::bip32::{ChildNumber, Fingerprint, KeySource};
use crate::consensus::encode::{self, deserialize_partial, serialize, Decodable, Encodable};
use crate::crypto::key::PublicKey;
use crate::crypto::key::{PublicKey, XOnlyPublicKey};
use crate::crypto::{ecdsa, taproot};
use crate::io::Write;
use crate::prelude::{DisplayHex, String, Vec};
@ -24,7 +24,6 @@ use crate::taproot::{
};
use crate::transaction::{Transaction, TxOut};
use crate::witness::Witness;
use crate::XOnlyPublicKey;
/// A trait for serializing a value as raw data for insertion into PSBT
/// key-value maps.

View File

@ -97,10 +97,11 @@ impl From<TapLeafHash> for TapNodeHash {
impl TapTweakHash {
/// Constructs a new BIP341 [`TapTweakHash`] from key and tweak. Produces `H_taptweak(P||R)` where
/// `P` is the internal key and `R` is the Merkle root.
pub fn from_key_and_tweak(
internal_key: UntweakedPublicKey,
pub fn from_key_and_tweak<K: Into<UntweakedPublicKey>>(
internal_key: K,
merkle_root: Option<TapNodeHash>,
) -> TapTweakHash {
let internal_key = internal_key.into();
let mut eng = sha256t::Hash::<TapTweakTag>::engine();
// always hash the key
eng.input(&internal_key.serialize());
@ -248,14 +249,15 @@ impl TaprootSpendInfo {
/// weights of satisfaction for that script.
///
/// See [`TaprootBuilder::with_huffman_tree`] for more detailed documentation.
pub fn with_huffman_tree<C, I>(
pub fn with_huffman_tree<C, I, K>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
script_weights: I,
) -> Result<Self, TaprootBuilderError>
where
I: IntoIterator<Item = (u32, ScriptBuf)>,
C: secp256k1::Verification,
K: Into<UntweakedPublicKey>,
{
let builder = TaprootBuilder::with_huffman_tree(script_weights)?;
Ok(builder.finalize(secp, internal_key).expect("Huffman tree is always complete"))
@ -272,11 +274,12 @@ impl TaprootSpendInfo {
///
/// Refer to BIP 341 footnote ('Why should the output key always have a Taproot commitment, even
/// if there is no script path?') for more details.
pub fn new_key_spend<C: secp256k1::Verification>(
pub fn new_key_spend<C: secp256k1::Verification, K: Into<UntweakedPublicKey>>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
merkle_root: Option<TapNodeHash>,
) -> Self {
let internal_key = internal_key.into();
let (output_key, parity) = internal_key.tap_tweak(secp, merkle_root);
Self {
internal_key,
@ -312,9 +315,9 @@ impl TaprootSpendInfo {
///
/// This is useful when you want to manually build a Taproot tree without using
/// [`TaprootBuilder`].
pub fn from_node_info<C: secp256k1::Verification>(
pub fn from_node_info<C: secp256k1::Verification, K: Into<UntweakedPublicKey>>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
node: NodeInfo,
) -> TaprootSpendInfo {
// Create as if it is a key spend path with the given Merkle root
@ -583,11 +586,12 @@ impl TaprootBuilder {
///
/// Returns the unmodified builder as Err if the builder is not finalizable.
/// See also [`TaprootBuilder::is_finalizable`]
pub fn finalize<C: secp256k1::Verification>(
pub fn finalize<C: secp256k1::Verification, K: Into<XOnlyPublicKey>>(
mut self,
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
internal_key: K,
) -> Result<TaprootSpendInfo, TaprootBuilder> {
let internal_key = internal_key.into();
match self.branch.len() {
0 => Ok(TaprootSpendInfo::new_key_spend(secp, internal_key, None)),
1 =>

View File

@ -66,7 +66,7 @@ fn psbt_sign_taproot() {
let internal_key = kp.x_only_public_key().0; // Ignore the parity.
let tree =
create_taproot_tree(secp, script1.clone(), script2.clone(), script3.clone(), internal_key.into());
create_taproot_tree(secp, script1.clone(), script2.clone(), script3.clone(), internal_key);
let address = create_p2tr_address(tree.clone());
assert_eq!(
@ -131,7 +131,7 @@ fn psbt_sign_taproot() {
address,
to_address,
tree.clone(),
x_only_pubkey.into(),
x_only_pubkey,
signing_key_path,
script2.clone(),
);
@ -176,13 +176,14 @@ fn create_basic_single_sig_script(secp: &Secp256k1<secp256k1::All>, sk: &str) ->
.into_script()
}
fn create_taproot_tree(
fn create_taproot_tree<K: Into<XOnlyPublicKey>>(
secp: &Secp256k1<secp256k1::All>,
script1: ScriptBuf,
script2: ScriptBuf,
script3: ScriptBuf,
internal_key: XOnlyPublicKey,
internal_key: K,
) -> TaprootSpendInfo {
let internal_key = internal_key.into();
let builder = TaprootBuilder::new();
let builder = builder.add_leaf(2, script1).unwrap();
let builder = builder.add_leaf(2, script2).unwrap();
@ -267,14 +268,15 @@ fn finalize_psbt_for_key_path_spend(mut psbt: Psbt) -> Psbt {
psbt
}
fn create_psbt_for_taproot_script_path_spend(
fn create_psbt_for_taproot_script_path_spend<K: Into<XOnlyPublicKey>>(
from_address: Address,
to_address: Address,
tree: TaprootSpendInfo,
x_only_pubkey_of_signing_key: XOnlyPublicKey,
x_only_pubkey_of_signing_key: K,
signing_key_path: &str,
use_script: ScriptBuf,
) -> Psbt {
let x_only_pubkey_of_signing_key = x_only_pubkey_of_signing_key.into();
let utxo_value = 6280;
let send_value = 6000;
let mfp = "73c5da0a";