Merge rust-bitcoin/rust-bitcoin#4410: Wrap secp256k1::XOnlyPublicKey to improve error handling
c11772a768
Accept flexible input types for Taproot-related functions (Erick Cestari)2a518d62e6
Wrap secp256k1::XOnlyPublicKey to improve error handling (Erick Cestari) Pull request description: This PR addresses issue #4361 by creating a wrapper type for XOnlyPublicKey instead of directly re-exporting it from the secp256k1 library. ### Key Changes 1. Created a new `XOnlyPublicKey` struct that wraps `secp256k1::XOnlyPublicKey` 2. Implemented custom error types: - `ParseXOnlyPublicKeyError` for handling parsing errors - `TweakXOnlyPublicKeyError` for tweaking an `XOnlyPublicKey` 3. Updated all imports and usage throughout the codebase 4. Implemented necessary traits and methods for compatibility Closes #4361 ACKs for top commit: apoelstra: ACK c11772a768eefd89dcc0e3b1a369d535c191f94a; successfully ran local tests tcharding: ACKc11772a768
Tree-SHA512: c8da3486e7ffcab6c24cc08f9b2f964dd9158449ef2bd720e54d56176bc7027052314ea23cac3f673d217fa785238ea8a9b5323ba57f02199f20e56df5893965
This commit is contained in:
commit
7d4b40dfd4
|
@ -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
|
/// 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.
|
/// 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>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
) -> (OutPoint, TxOut) {
|
) -> (OutPoint, TxOut) {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
let script_pubkey = ScriptBuf::new_p2tr(secp, internal_key, None);
|
let script_pubkey = ScriptBuf::new_p2tr(secp, internal_key, None);
|
||||||
|
|
||||||
let out_point = OutPoint {
|
let out_point = OutPoint {
|
||||||
|
|
|
@ -85,11 +85,12 @@ fn get_internal_address_xpriv<C: Signing>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the Taproot Key Origin.
|
// Get the Taproot Key Origin.
|
||||||
fn get_tap_key_origin(
|
fn get_tap_key_origin<K: Into<UntweakedPublicKey> + std::cmp::Ord>(
|
||||||
x_only_key: UntweakedPublicKey,
|
x_only_key: K,
|
||||||
master_fingerprint: Fingerprint,
|
master_fingerprint: Fingerprint,
|
||||||
path: DerivationPath,
|
path: DerivationPath,
|
||||||
) -> BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, (Fingerprint, DerivationPath))> {
|
) -> BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, (Fingerprint, DerivationPath))> {
|
||||||
|
let x_only_key = x_only_key.into();
|
||||||
let mut map = BTreeMap::new();
|
let mut map = BTreeMap::new();
|
||||||
map.insert(x_only_key, (vec![], (master_fingerprint, path)));
|
map.insert(x_only_key, (vec![], (master_fingerprint, path)));
|
||||||
map
|
map
|
||||||
|
@ -212,14 +213,14 @@ fn main() {
|
||||||
Input {
|
Input {
|
||||||
witness_utxo: Some(utxos[0].clone()),
|
witness_utxo: Some(utxos[0].clone()),
|
||||||
tap_key_origins: origins[0].clone(),
|
tap_key_origins: origins[0].clone(),
|
||||||
tap_internal_key: Some(pk_input_1),
|
tap_internal_key: Some(pk_input_1.into()),
|
||||||
sighash_type: Some(ty),
|
sighash_type: Some(ty),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Input {
|
Input {
|
||||||
witness_utxo: Some(utxos[1].clone()),
|
witness_utxo: Some(utxos[1].clone()),
|
||||||
tap_key_origins: origins[1].clone(),
|
tap_key_origins: origins[1].clone(),
|
||||||
tap_internal_key: Some(pk_input_2),
|
tap_internal_key: Some(pk_input_2.into()),
|
||||||
sighash_type: Some(ty),
|
sighash_type: Some(ty),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
|
@ -444,7 +444,7 @@ impl BenefactorWallet {
|
||||||
(vec![leaf_hash], (self.beneficiary_xpub.fingerprint(), derivation_path.clone())),
|
(vec![leaf_hash], (self.beneficiary_xpub.fingerprint(), derivation_path.clone())),
|
||||||
);
|
);
|
||||||
origins.insert(
|
origins.insert(
|
||||||
internal_keypair.x_only_public_key().0,
|
internal_keypair.x_only_public_key().0.into(),
|
||||||
(vec![], (self.master_xpriv.fingerprint(&self.secp), derivation_path)),
|
(vec![], (self.master_xpriv.fingerprint(&self.secp), derivation_path)),
|
||||||
);
|
);
|
||||||
let ty = "SIGHASH_ALL".parse::<PsbtSighashType>()?;
|
let ty = "SIGHASH_ALL".parse::<PsbtSighashType>()?;
|
||||||
|
@ -459,7 +459,7 @@ impl BenefactorWallet {
|
||||||
tap_key_origins: origins,
|
tap_key_origins: origins,
|
||||||
tap_merkle_root: taproot_spend_info.merkle_root(),
|
tap_merkle_root: taproot_spend_info.merkle_root(),
|
||||||
sighash_type: Some(ty),
|
sighash_type: Some(ty),
|
||||||
tap_internal_key: Some(internal_keypair.x_only_public_key().0),
|
tap_internal_key: Some(internal_keypair.x_only_public_key().0.into()),
|
||||||
tap_scripts,
|
tap_scripts,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -612,7 +612,7 @@ impl BenefactorWallet {
|
||||||
tap_key_origins: origins,
|
tap_key_origins: origins,
|
||||||
tap_merkle_root: taproot_spend_info.merkle_root(),
|
tap_merkle_root: taproot_spend_info.merkle_root(),
|
||||||
sighash_type: Some(ty),
|
sighash_type: Some(ty),
|
||||||
tap_internal_key: Some(new_internal_keypair.x_only_public_key().0),
|
tap_internal_key: Some(new_internal_keypair.x_only_public_key().0.into()),
|
||||||
tap_scripts,
|
tap_scripts,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,7 +50,7 @@ use bech32::primitives::gf32::Fe32;
|
||||||
use bech32::primitives::hrp::Hrp;
|
use bech32::primitives::hrp::Hrp;
|
||||||
use hashes::{hash160, HashEngine};
|
use hashes::{hash160, HashEngine};
|
||||||
use internals::array::ArrayExt;
|
use internals::array::ArrayExt;
|
||||||
use secp256k1::{Secp256k1, Verification, XOnlyPublicKey};
|
use secp256k1::{Secp256k1, Verification};
|
||||||
|
|
||||||
use crate::address::script_pubkey::ScriptBufExt as _;
|
use crate::address::script_pubkey::ScriptBufExt as _;
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
|
@ -59,6 +59,7 @@ use crate::constants::{
|
||||||
};
|
};
|
||||||
use crate::crypto::key::{
|
use crate::crypto::key::{
|
||||||
CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey,
|
CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey,
|
||||||
|
XOnlyPublicKey,
|
||||||
};
|
};
|
||||||
use crate::network::{Network, NetworkKind, Params};
|
use crate::network::{Network, NetworkKind, Params};
|
||||||
use crate::prelude::{String, ToOwned};
|
use crate::prelude::{String, ToOwned};
|
||||||
|
@ -572,12 +573,13 @@ impl Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new pay-to-Taproot (P2TR) [`Address`] from an untweaked key.
|
/// 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>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
merkle_root: Option<TapNodeHash>,
|
merkle_root: Option<TapNodeHash>,
|
||||||
hrp: impl Into<KnownHrp>,
|
hrp: impl Into<KnownHrp>,
|
||||||
) -> Address {
|
) -> Address {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
let program = WitnessProgram::p2tr(secp, internal_key, merkle_root);
|
let program = WitnessProgram::p2tr(secp, internal_key, merkle_root);
|
||||||
Address::from_witness_program(program, hrp)
|
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
|
/// 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.
|
/// the current script, assuming that the script is a Tapscript.
|
||||||
fn to_p2tr<C: Verification>(
|
fn to_p2tr<C: Verification, K: Into<UntweakedPublicKey>>(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
) -> ScriptBuf {
|
) -> ScriptBuf {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
let leaf_hash = self.tapscript_leaf_hash();
|
let leaf_hash = self.tapscript_leaf_hash();
|
||||||
let merkle_root = TapNodeHash::from(leaf_hash);
|
let merkle_root = TapNodeHash::from(leaf_hash);
|
||||||
ScriptBuf::new_p2tr(secp, internal_key, Some(merkle_root))
|
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
|
/// Generates P2TR for script spending path using an internal public key and some optional
|
||||||
/// script tree Merkle root.
|
/// script tree Merkle root.
|
||||||
fn new_p2tr<C: Verification>(
|
fn new_p2tr<C: Verification, K: Into<UntweakedPublicKey>>(
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
merkle_root: Option<TapNodeHash>,
|
merkle_root: Option<TapNodeHash>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
let (output_key, _) = internal_key.tap_tweak(secp, merkle_root);
|
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)
|
// 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())
|
new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize())
|
||||||
|
|
|
@ -13,9 +13,9 @@ use core::{fmt, slice};
|
||||||
use hashes::{hash160, hash_newtype, sha512, Hash, HashEngine, Hmac, HmacEngine};
|
use hashes::{hash160, hash_newtype, sha512, Hash, HashEngine, Hmac, HmacEngine};
|
||||||
use internals::array::ArrayExt;
|
use internals::array::ArrayExt;
|
||||||
use internals::write_err;
|
use internals::write_err;
|
||||||
use secp256k1::{Secp256k1, XOnlyPublicKey};
|
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::internal_macros::{impl_array_newtype, impl_array_newtype_stringify};
|
||||||
use crate::network::NetworkKind;
|
use crate::network::NetworkKind;
|
||||||
use crate::prelude::{String, Vec};
|
use crate::prelude::{String, Vec};
|
||||||
|
|
|
@ -95,11 +95,12 @@ impl WitnessProgram {
|
||||||
///
|
///
|
||||||
/// This function applies BIP341 key-tweaking to the untweaked
|
/// This function applies BIP341 key-tweaking to the untweaked
|
||||||
/// key using the merkle root, if it's present.
|
/// 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>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
merkle_root: Option<TapNodeHash>,
|
merkle_root: Option<TapNodeHash>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
|
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
|
||||||
let pubkey = output_key.as_x_only_public_key().serialize();
|
let pubkey = output_key.as_x_only_public_key().serialize();
|
||||||
WitnessProgram::new_p2tr(pubkey)
|
WitnessProgram::new_p2tr(pubkey)
|
||||||
|
|
|
@ -25,11 +25,120 @@ use crate::script::{self, ScriptBuf};
|
||||||
use crate::taproot::{TapNodeHash, TapTweakHash};
|
use crate::taproot::{TapNodeHash, TapTweakHash};
|
||||||
|
|
||||||
#[rustfmt::skip] // Keep public re-exports separate.
|
#[rustfmt::skip] // Keep public re-exports separate.
|
||||||
pub use secp256k1::{constants, Keypair, Parity, Secp256k1, Verification, XOnlyPublicKey};
|
pub use secp256k1::{constants, Keypair, Parity, Secp256k1, Verification};
|
||||||
#[cfg(feature = "rand-std")]
|
#[cfg(feature = "rand-std")]
|
||||||
pub use secp256k1::rand;
|
pub use secp256k1::rand;
|
||||||
pub use serialized_x_only::SerializedXOnlyPublicKey;
|
pub use serialized_x_only::SerializedXOnlyPublicKey;
|
||||||
|
|
||||||
|
/// A Bitcoin Schnorr X-only public key used for BIP340 signatures.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct XOnlyPublicKey(secp256k1::XOnlyPublicKey);
|
||||||
|
|
||||||
|
impl XOnlyPublicKey {
|
||||||
|
/// Constructs a new x-only public key from the provided generic Secp256k1 x-only public key.
|
||||||
|
pub fn new(key: impl Into<secp256k1::XOnlyPublicKey>) -> XOnlyPublicKey {
|
||||||
|
XOnlyPublicKey(key.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an x-only public key from a keypair.
|
||||||
|
///
|
||||||
|
/// Returns the x-only public key and the parity of the full public key.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_keypair(keypair: &Keypair) -> (XOnlyPublicKey, Parity) {
|
||||||
|
let (xonly, parity) = secp256k1::XOnlyPublicKey::from_keypair(keypair);
|
||||||
|
(XOnlyPublicKey::new(xonly), parity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an x-only public key from a 32-byte x-coordinate.
|
||||||
|
///
|
||||||
|
/// Returns an error if the provided bytes don't represent a valid secp256k1 point x-coordinate.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_byte_array(
|
||||||
|
data: &[u8; constants::SCHNORR_PUBLIC_KEY_SIZE],
|
||||||
|
) -> Result<XOnlyPublicKey, ParseXOnlyPublicKeyError> {
|
||||||
|
secp256k1::XOnlyPublicKey::from_byte_array(data)
|
||||||
|
.map(XOnlyPublicKey::new)
|
||||||
|
.map_err(|_| ParseXOnlyPublicKeyError::InvalidXCoordinate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes the x-only public key as a byte-encoded x coordinate value (32 bytes).
|
||||||
|
#[inline]
|
||||||
|
pub fn serialize(&self) -> [u8; constants::SCHNORR_PUBLIC_KEY_SIZE] { self.0.serialize() }
|
||||||
|
|
||||||
|
/// Converts this x-only public key to a full public key given the parity.
|
||||||
|
#[inline]
|
||||||
|
pub fn public_key(&self, parity: Parity) -> PublicKey { self.0.public_key(parity).into() }
|
||||||
|
|
||||||
|
/// Verifies that a tweak produced by [`XOnlyPublicKey::add_tweak`] was computed correctly.
|
||||||
|
///
|
||||||
|
/// Should be called on the original untweaked key. Takes the tweaked key and output parity from
|
||||||
|
/// [`XOnlyPublicKey::add_tweak`] as input.
|
||||||
|
#[inline]
|
||||||
|
pub fn tweak_add_check<V: Verification>(
|
||||||
|
&self,
|
||||||
|
secp: &Secp256k1<V>,
|
||||||
|
tweaked_key: &Self,
|
||||||
|
tweaked_parity: Parity,
|
||||||
|
tweak: secp256k1::Scalar,
|
||||||
|
) -> bool {
|
||||||
|
self.0.tweak_add_check(secp, &tweaked_key.0, tweaked_parity, tweak)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tweaks an [`XOnlyPublicKey`] by adding the generator multiplied with the given tweak to it.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The newly tweaked key plus an opaque type representing the parity of the tweaked key, this
|
||||||
|
/// should be provided to `tweak_add_check` which can be used to verify a tweak more efficiently
|
||||||
|
/// than regenerating it and checking equality.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If the resulting key would be invalid.
|
||||||
|
#[inline]
|
||||||
|
pub fn add_tweak<V: Verification>(
|
||||||
|
&self,
|
||||||
|
secp: &Secp256k1<V>,
|
||||||
|
tweak: &secp256k1::Scalar,
|
||||||
|
) -> Result<(XOnlyPublicKey, Parity), TweakXOnlyPublicKeyError> {
|
||||||
|
match self.0.add_tweak(secp, tweak) {
|
||||||
|
Ok((xonly, parity)) => Ok((XOnlyPublicKey(xonly), parity)),
|
||||||
|
Err(secp256k1::Error::InvalidTweak) => Err(TweakXOnlyPublicKeyError::BadTweak),
|
||||||
|
Err(secp256k1::Error::InvalidParityValue(_)) =>
|
||||||
|
Err(TweakXOnlyPublicKeyError::ParityError),
|
||||||
|
Err(_) => Err(TweakXOnlyPublicKeyError::ResultKeyInvalid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for XOnlyPublicKey {
|
||||||
|
type Err = ParseXOnlyPublicKeyError;
|
||||||
|
fn from_str(s: &str) -> Result<XOnlyPublicKey, ParseXOnlyPublicKeyError> {
|
||||||
|
secp256k1::XOnlyPublicKey::from_str(s)
|
||||||
|
.map(XOnlyPublicKey::from)
|
||||||
|
.map_err(|_| ParseXOnlyPublicKeyError::InvalidXCoordinate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<secp256k1::XOnlyPublicKey> for XOnlyPublicKey {
|
||||||
|
fn from(pk: secp256k1::XOnlyPublicKey) -> XOnlyPublicKey { XOnlyPublicKey::new(pk) }
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
/// A Bitcoin ECDSA public key.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct PublicKey {
|
pub struct PublicKey {
|
||||||
|
@ -222,7 +331,7 @@ impl From<secp256k1::PublicKey> for PublicKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PublicKey> for XOnlyPublicKey {
|
impl From<PublicKey> for XOnlyPublicKey {
|
||||||
fn from(pk: PublicKey) -> XOnlyPublicKey { pk.inner.into() }
|
fn from(pk: PublicKey) -> XOnlyPublicKey { XOnlyPublicKey::new(pk.inner) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An opaque return type for PublicKey::to_sort_key.
|
/// An opaque return type for PublicKey::to_sort_key.
|
||||||
|
@ -874,7 +983,7 @@ impl TweakedPublicKey {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_keypair(keypair: TweakedKeypair) -> Self {
|
pub fn from_keypair(keypair: TweakedKeypair) -> Self {
|
||||||
let (xonly, _parity) = keypair.0.x_only_public_key();
|
let (xonly, _parity) = keypair.0.x_only_public_key();
|
||||||
TweakedPublicKey(xonly)
|
TweakedPublicKey(xonly.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new [`TweakedPublicKey`] from a [`XOnlyPublicKey`]. No tweak is applied, consider
|
/// Constructs a new [`TweakedPublicKey`] from a [`XOnlyPublicKey`]. No tweak is applied, consider
|
||||||
|
@ -933,7 +1042,7 @@ impl TweakedKeypair {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn public_parts(&self) -> (TweakedPublicKey, Parity) {
|
pub fn public_parts(&self) -> (TweakedPublicKey, Parity) {
|
||||||
let (xonly, parity) = self.0.x_only_public_key();
|
let (xonly, parity) = self.0.x_only_public_key();
|
||||||
(TweakedPublicKey(xonly), parity)
|
(TweakedPublicKey(xonly.into()), parity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1265,7 +1374,7 @@ mod serialized_x_only {
|
||||||
|
|
||||||
impl SerializedXOnlyPublicKey {
|
impl SerializedXOnlyPublicKey {
|
||||||
/// Returns `XOnlyPublicKey` if the bytes are valid.
|
/// Returns `XOnlyPublicKey` if the bytes are valid.
|
||||||
pub fn to_validated(self) -> Result<XOnlyPublicKey, secp256k1::Error> {
|
pub fn to_validated(self) -> Result<XOnlyPublicKey, ParseXOnlyPublicKeyError> {
|
||||||
XOnlyPublicKey::from_byte_array(self.as_byte_array())
|
XOnlyPublicKey::from_byte_array(self.as_byte_array())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1284,6 +1393,48 @@ impl fmt::Debug for SerializedXOnlyPublicKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error that can occur when parsing an [`XOnlyPublicKey`] from bytes.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ParseXOnlyPublicKeyError {
|
||||||
|
/// The provided bytes do not represent a valid secp256k1 point x-coordinate.
|
||||||
|
InvalidXCoordinate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseXOnlyPublicKeyError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::InvalidXCoordinate => write!(f, "Invalid X coordinate for secp256k1 point"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for ParseXOnlyPublicKeyError {}
|
||||||
|
|
||||||
|
/// Error that can occur when tweaking an [`XOnlyPublicKey`].
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum TweakXOnlyPublicKeyError {
|
||||||
|
/// The tweak value was invalid.
|
||||||
|
BadTweak,
|
||||||
|
/// The resulting public key would be invalid.
|
||||||
|
ResultKeyInvalid,
|
||||||
|
/// Invalid parity value encountered during the operation.
|
||||||
|
ParityError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TweakXOnlyPublicKeyError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::BadTweak => write!(f, "Invalid tweak value"),
|
||||||
|
Self::ResultKeyInvalid => write!(f, "Resulting public key would be invalid"),
|
||||||
|
Self::ParityError => write!(f, "Invalid parity value encountered"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for TweakXOnlyPublicKeyError {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1878,9 +1878,10 @@ mod tests {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
use secp256k1::{SecretKey, XOnlyPublicKey};
|
use secp256k1::SecretKey;
|
||||||
|
|
||||||
use crate::consensus::serde as con_serde;
|
use crate::consensus::serde as con_serde;
|
||||||
|
use crate::crypto::key::XOnlyPublicKey;
|
||||||
use crate::taproot::{TapNodeHash, TapTweakHash};
|
use crate::taproot::{TapNodeHash, TapTweakHash};
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
|
|
|
@ -4,10 +4,9 @@ use core::fmt;
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
|
|
||||||
use hashes::{hash160, ripemd160, sha256, sha256d};
|
use hashes::{hash160, ripemd160, sha256, sha256d};
|
||||||
use secp256k1::XOnlyPublicKey;
|
|
||||||
|
|
||||||
use crate::bip32::KeySource;
|
use crate::bip32::KeySource;
|
||||||
use crate::crypto::key::PublicKey;
|
use crate::crypto::key::{PublicKey, XOnlyPublicKey};
|
||||||
use crate::crypto::{ecdsa, taproot};
|
use crate::crypto::{ecdsa, taproot};
|
||||||
use crate::prelude::{btree_map, BTreeMap, Borrow, Box, ToOwned, Vec};
|
use crate::prelude::{btree_map, BTreeMap, Borrow, Box, ToOwned, Vec};
|
||||||
use crate::psbt::map::Map;
|
use crate::psbt::map::Map;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
// SPDX-License-Identifier: CC0-1.0
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
use secp256k1::XOnlyPublicKey;
|
|
||||||
|
|
||||||
use crate::bip32::KeySource;
|
use crate::bip32::KeySource;
|
||||||
|
use crate::crypto::key::XOnlyPublicKey;
|
||||||
use crate::prelude::{btree_map, BTreeMap, Vec};
|
use crate::prelude::{btree_map, BTreeMap, Vec};
|
||||||
use crate::psbt::map::Map;
|
use crate::psbt::map::Map;
|
||||||
use crate::psbt::{raw, Error};
|
use crate::psbt::{raw, Error};
|
||||||
|
|
|
@ -850,14 +850,14 @@ impl GetKey for $map<PublicKey, PrivateKey> {
|
||||||
match key_request {
|
match key_request {
|
||||||
KeyRequest::Pubkey(pk) => Ok(self.get(&pk).cloned()),
|
KeyRequest::Pubkey(pk) => Ok(self.get(&pk).cloned()),
|
||||||
KeyRequest::XOnlyPubkey(xonly) => {
|
KeyRequest::XOnlyPubkey(xonly) => {
|
||||||
let pubkey_even = PublicKey::new(xonly.public_key(secp256k1::Parity::Even));
|
let pubkey_even = xonly.public_key(secp256k1::Parity::Even);
|
||||||
let key = self.get(&pubkey_even).cloned();
|
let key = self.get(&pubkey_even).cloned();
|
||||||
|
|
||||||
if key.is_some() {
|
if key.is_some() {
|
||||||
return Ok(key);
|
return Ok(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pubkey_odd = PublicKey::new(xonly.public_key(secp256k1::Parity::Odd));
|
let pubkey_odd = xonly.public_key(secp256k1::Parity::Odd);
|
||||||
if let Some(priv_key) = self.get(&pubkey_odd).copied() {
|
if let Some(priv_key) = self.get(&pubkey_odd).copied() {
|
||||||
let negated_priv_key = priv_key.negate();
|
let negated_priv_key = priv_key.negate();
|
||||||
return Ok(Some(negated_priv_key));
|
return Ok(Some(negated_priv_key));
|
||||||
|
@ -2267,7 +2267,7 @@ mod tests {
|
||||||
|
|
||||||
pubkey_map.insert(pk, priv_key);
|
pubkey_map.insert(pk, priv_key);
|
||||||
|
|
||||||
let req_result = pubkey_map.get_key(&KeyRequest::XOnlyPubkey(xonly), &secp).unwrap();
|
let req_result = pubkey_map.get_key(&KeyRequest::XOnlyPubkey(xonly.into()), &secp).unwrap();
|
||||||
|
|
||||||
let retrieved_key = req_result.unwrap();
|
let retrieved_key = req_result.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,11 @@ use hashes::{hash160, ripemd160, sha256, sha256d};
|
||||||
use internals::compact_size;
|
use internals::compact_size;
|
||||||
#[allow(unused)] // MSRV polyfill
|
#[allow(unused)] // MSRV polyfill
|
||||||
use internals::slice::SliceExt;
|
use internals::slice::SliceExt;
|
||||||
use secp256k1::XOnlyPublicKey;
|
|
||||||
|
|
||||||
use super::map::{Input, Map, Output, PsbtSighashType};
|
use super::map::{Input, Map, Output, PsbtSighashType};
|
||||||
use crate::bip32::{ChildNumber, Fingerprint, KeySource};
|
use crate::bip32::{ChildNumber, Fingerprint, KeySource};
|
||||||
use crate::consensus::encode::{self, deserialize_partial, serialize, Decodable, Encodable};
|
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::crypto::{ecdsa, taproot};
|
||||||
use crate::io::Write;
|
use crate::io::Write;
|
||||||
use crate::prelude::{DisplayHex, String, Vec};
|
use crate::prelude::{DisplayHex, String, Vec};
|
||||||
|
|
|
@ -25,6 +25,7 @@ use crate::consensus::Encodable;
|
||||||
use crate::crypto::key::{
|
use crate::crypto::key::{
|
||||||
SerializedXOnlyPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey,
|
SerializedXOnlyPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey,
|
||||||
};
|
};
|
||||||
|
use crate::key::ParseXOnlyPublicKeyError;
|
||||||
use crate::prelude::{BTreeMap, BTreeSet, BinaryHeap, Vec};
|
use crate::prelude::{BTreeMap, BTreeSet, BinaryHeap, Vec};
|
||||||
use crate::{Script, ScriptBuf};
|
use crate::{Script, ScriptBuf};
|
||||||
|
|
||||||
|
@ -96,10 +97,11 @@ impl From<TapLeafHash> for TapNodeHash {
|
||||||
impl TapTweakHash {
|
impl TapTweakHash {
|
||||||
/// Constructs a new BIP341 [`TapTweakHash`] from key and tweak. Produces `H_taptweak(P||R)` where
|
/// 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.
|
/// `P` is the internal key and `R` is the Merkle root.
|
||||||
pub fn from_key_and_tweak(
|
pub fn from_key_and_tweak<K: Into<UntweakedPublicKey>>(
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
merkle_root: Option<TapNodeHash>,
|
merkle_root: Option<TapNodeHash>,
|
||||||
) -> TapTweakHash {
|
) -> TapTweakHash {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
let mut eng = sha256t::Hash::<TapTweakTag>::engine();
|
let mut eng = sha256t::Hash::<TapTweakTag>::engine();
|
||||||
// always hash the key
|
// always hash the key
|
||||||
eng.input(&internal_key.serialize());
|
eng.input(&internal_key.serialize());
|
||||||
|
@ -247,14 +249,15 @@ impl TaprootSpendInfo {
|
||||||
/// weights of satisfaction for that script.
|
/// weights of satisfaction for that script.
|
||||||
///
|
///
|
||||||
/// See [`TaprootBuilder::with_huffman_tree`] for more detailed documentation.
|
/// 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>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
script_weights: I,
|
script_weights: I,
|
||||||
) -> Result<Self, TaprootBuilderError>
|
) -> Result<Self, TaprootBuilderError>
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = (u32, ScriptBuf)>,
|
I: IntoIterator<Item = (u32, ScriptBuf)>,
|
||||||
C: secp256k1::Verification,
|
C: secp256k1::Verification,
|
||||||
|
K: Into<UntweakedPublicKey>,
|
||||||
{
|
{
|
||||||
let builder = TaprootBuilder::with_huffman_tree(script_weights)?;
|
let builder = TaprootBuilder::with_huffman_tree(script_weights)?;
|
||||||
Ok(builder.finalize(secp, internal_key).expect("Huffman tree is always complete"))
|
Ok(builder.finalize(secp, internal_key).expect("Huffman tree is always complete"))
|
||||||
|
@ -271,11 +274,12 @@ impl TaprootSpendInfo {
|
||||||
///
|
///
|
||||||
/// Refer to BIP 341 footnote ('Why should the output key always have a Taproot commitment, even
|
/// 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.
|
/// 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>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
merkle_root: Option<TapNodeHash>,
|
merkle_root: Option<TapNodeHash>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
let (output_key, parity) = internal_key.tap_tweak(secp, merkle_root);
|
let (output_key, parity) = internal_key.tap_tweak(secp, merkle_root);
|
||||||
Self {
|
Self {
|
||||||
internal_key,
|
internal_key,
|
||||||
|
@ -311,9 +315,9 @@ impl TaprootSpendInfo {
|
||||||
///
|
///
|
||||||
/// This is useful when you want to manually build a Taproot tree without using
|
/// This is useful when you want to manually build a Taproot tree without using
|
||||||
/// [`TaprootBuilder`].
|
/// [`TaprootBuilder`].
|
||||||
pub fn from_node_info<C: secp256k1::Verification>(
|
pub fn from_node_info<C: secp256k1::Verification, K: Into<UntweakedPublicKey>>(
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
node: NodeInfo,
|
node: NodeInfo,
|
||||||
) -> TaprootSpendInfo {
|
) -> TaprootSpendInfo {
|
||||||
// Create as if it is a key spend path with the given Merkle root
|
// Create as if it is a key spend path with the given Merkle root
|
||||||
|
@ -582,11 +586,12 @@ impl TaprootBuilder {
|
||||||
///
|
///
|
||||||
/// Returns the unmodified builder as Err if the builder is not finalizable.
|
/// Returns the unmodified builder as Err if the builder is not finalizable.
|
||||||
/// See also [`TaprootBuilder::is_finalizable`]
|
/// See also [`TaprootBuilder::is_finalizable`]
|
||||||
pub fn finalize<C: secp256k1::Verification>(
|
pub fn finalize<C: secp256k1::Verification, K: Into<XOnlyPublicKey>>(
|
||||||
mut self,
|
mut self,
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: K,
|
||||||
) -> Result<TaprootSpendInfo, TaprootBuilder> {
|
) -> Result<TaprootSpendInfo, TaprootBuilder> {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
match self.branch.len() {
|
match self.branch.len() {
|
||||||
0 => Ok(TaprootSpendInfo::new_key_spend(secp, internal_key, None)),
|
0 => Ok(TaprootSpendInfo::new_key_spend(secp, internal_key, None)),
|
||||||
1 =>
|
1 =>
|
||||||
|
@ -1523,7 +1528,7 @@ pub enum TaprootError {
|
||||||
/// Invalid control block size.
|
/// Invalid control block size.
|
||||||
InvalidControlBlockSize(InvalidControlBlockSizeError),
|
InvalidControlBlockSize(InvalidControlBlockSizeError),
|
||||||
/// Invalid Taproot internal key.
|
/// Invalid Taproot internal key.
|
||||||
InvalidInternalKey(secp256k1::Error),
|
InvalidInternalKey(ParseXOnlyPublicKeyError),
|
||||||
/// Invalid control block hex
|
/// Invalid control block hex
|
||||||
InvalidControlBlockHex(HexToBytesError),
|
InvalidControlBlockHex(HexToBytesError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo};
|
||||||
use bitcoin::transaction::Version;
|
use bitcoin::transaction::Version;
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
absolute, script, Address, Network, OutPoint, PrivateKey, Psbt, ScriptBuf, Sequence,
|
absolute, script, Address, Network, OutPoint, PrivateKey, Psbt, ScriptBuf, Sequence,
|
||||||
Transaction, TxIn, TxOut, Witness,
|
Transaction, TxIn, TxOut, Witness, XOnlyPublicKey,
|
||||||
};
|
};
|
||||||
use secp256k1::{Keypair, Secp256k1, Signing, XOnlyPublicKey};
|
use secp256k1::{Keypair, Secp256k1, Signing};
|
||||||
use units::Amount;
|
use units::Amount;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -146,7 +146,7 @@ fn psbt_sign_taproot() {
|
||||||
sig,
|
sig,
|
||||||
psbt_script_path_spend.inputs[0]
|
psbt_script_path_spend.inputs[0]
|
||||||
.tap_script_sigs
|
.tap_script_sigs
|
||||||
.get(&(x_only_pubkey, script2.clone().tapscript_leaf_hash()))
|
.get(&(x_only_pubkey.into(), script2.clone().tapscript_leaf_hash()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.signature
|
.signature
|
||||||
.to_string()
|
.to_string()
|
||||||
|
@ -176,13 +176,14 @@ fn create_basic_single_sig_script(secp: &Secp256k1<secp256k1::All>, sk: &str) ->
|
||||||
.into_script()
|
.into_script()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_taproot_tree(
|
fn create_taproot_tree<K: Into<XOnlyPublicKey>>(
|
||||||
secp: &Secp256k1<secp256k1::All>,
|
secp: &Secp256k1<secp256k1::All>,
|
||||||
script1: ScriptBuf,
|
script1: ScriptBuf,
|
||||||
script2: ScriptBuf,
|
script2: ScriptBuf,
|
||||||
script3: ScriptBuf,
|
script3: ScriptBuf,
|
||||||
internal_key: XOnlyPublicKey,
|
internal_key: K,
|
||||||
) -> TaprootSpendInfo {
|
) -> TaprootSpendInfo {
|
||||||
|
let internal_key = internal_key.into();
|
||||||
let builder = TaprootBuilder::new();
|
let builder = TaprootBuilder::new();
|
||||||
let builder = builder.add_leaf(2, script1).unwrap();
|
let builder = builder.add_leaf(2, script1).unwrap();
|
||||||
let builder = builder.add_leaf(2, script2).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
|
psbt
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_psbt_for_taproot_script_path_spend(
|
fn create_psbt_for_taproot_script_path_spend<K: Into<XOnlyPublicKey>>(
|
||||||
from_address: Address,
|
from_address: Address,
|
||||||
to_address: Address,
|
to_address: Address,
|
||||||
tree: TaprootSpendInfo,
|
tree: TaprootSpendInfo,
|
||||||
x_only_pubkey_of_signing_key: XOnlyPublicKey,
|
x_only_pubkey_of_signing_key: K,
|
||||||
signing_key_path: &str,
|
signing_key_path: &str,
|
||||||
use_script: ScriptBuf,
|
use_script: ScriptBuf,
|
||||||
) -> Psbt {
|
) -> Psbt {
|
||||||
|
let x_only_pubkey_of_signing_key = x_only_pubkey_of_signing_key.into();
|
||||||
let utxo_value = 6280;
|
let utxo_value = 6280;
|
||||||
let send_value = 6000;
|
let send_value = 6000;
|
||||||
let mfp = "73c5da0a";
|
let mfp = "73c5da0a";
|
||||||
|
|
Loading…
Reference in New Issue