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:
parent
2a518d62e6
commit
c11772a768
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue