Introduce From conversion for PrivateKey -> Keypair
What changed, and why it matters
This commit is a routine API cleanup in the rust-bitcoin library. It adds new helper methods so that developers can create cryptographic keypairs directly from PrivateKey objects instead of reaching into the underlying secp256k1 secret key. The change does not fix a bug, close a security hole, or alter cryptographic behavior; it only makes the public API more convenient and less error-prone.
No security action required. Treat as a normal refactoring/API-improvement commit. Reviewers may verify that the new From conversion preserves network/compression metadata correctly for downstream callers, but the diff shows no security defect.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces Keypair::from_private_key, Keypair::to_private_key, and an impl From
Changed components
bitcoin/src/crypto/key.rsbitcoin/src/bip32.rsbitcoin/src/crypto/sighash.rsbitcoin/src/psbt/mod.rsbitcoin/examples/sign-tx-taproot.rsbitcoin/examples/taproot-psbt.rsbitcoin/tests/psbt-sign-taproot.rsInspect captured patch +32 / −35
diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs
index 67f77de5..cc7961d3 100644
--- a/bitcoin/examples/sign-tx-taproot.rs
+++ b/bitcoin/examples/sign-tx-taproot.rs
@@ -3,9 +3,8 @@
//! Demonstrate creating a transaction that spends to and from p2tr outputs.
use bitcoin::ext::*;
-use bitcoin::key::{Keypair, TapTweak, TweakedKeypair, UntweakedPublicKey};
+use bitcoin::key::{Keypair, PrivateKey, TapTweak, TweakedKeypair, UntweakedPublicKey};
use bitcoin::locktime::absolute;
-use bitcoin::secp256k1::{rand, SecretKey};
use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType};
use bitcoin::{
transaction, Address, Amount, Network, OutPoint, ScriptPubKeyBuf, ScriptSigBuf, Sequence,
@@ -84,8 +83,8 @@ fn main() {
///
/// In a real application these would be actual secrets.
fn senders_keys() -> Keypair {
- let sk = SecretKey::new(&mut rand::rng());
- Keypair::from_secret_key(&sk)
+ let sk = PrivateKey::generate();
+ Keypair::from_private_key(&sk)
}
/// A dummy address for the receiver.
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index 54c2e7e7..4028af37 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -80,7 +80,7 @@ use std::collections::BTreeMap;
use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpriv, Xpub};
use bitcoin::consensus::encode;
use bitcoin::ext::*;
-use bitcoin::key::{Keypair, TapTweak, XOnlyPublicKey};
+use bitcoin::key::{Keypair, PrivateKey, TapTweak, XOnlyPublicKey};
use bitcoin::opcodes::all::{OP_CHECKSIG, OP_CLTV, OP_DROP};
use bitcoin::psbt::{self, Input, Output, Psbt, PsbtSighashType};
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
@@ -293,7 +293,7 @@ fn generate_bip86_key_spend_tx(
let secret_key = master_xpriv.derive_xpriv(derivation_path)?.to_private_key();
sign_psbt_taproot(
- secret_key.as_inner(),
+ &secret_key,
input.tap_internal_key.unwrap(),
None,
input,
@@ -532,7 +532,7 @@ impl BenefactorWallet {
.expect("derivation path is short")
.to_private_key();
sign_psbt_taproot(
- secret_key.as_inner(),
+ &secret_key,
spend_info.internal_key(),
None,
input,
@@ -662,7 +662,7 @@ impl BeneficiaryWallet {
sighash_type,
)?;
sign_psbt_taproot(
- secret_key.as_inner(),
+ &secret_key,
*x_only_pubkey,
Some(*lh),
&mut psbt.inputs[0],
@@ -742,14 +742,14 @@ impl BeneficiaryWallet {
// Calling this with `leaf_hash` = `None` will sign for key-spend
fn sign_psbt_taproot(
- secret_key: &secp256k1::SecretKey,
+ secret_key: &PrivateKey,
pubkey: XOnlyPublicKey,
leaf_hash: Option<TapLeafHash>,
psbt_input: &mut psbt::Input,
hash: TapSighash,
sighash_type: TapSighashType,
) {
- let keypair = Keypair::from_secret_key(secret_key);
+ let keypair = Keypair::from_private_key(secret_key);
let keypair = match leaf_hash {
None => keypair.tap_tweak(psbt_input.tap_merkle_root).into_keypair(),
Some(_) => keypair, // no tweak for script spend
diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs
index bd73e714..176984a4 100644
--- a/bitcoin/src/bip32.rs
+++ b/bitcoin/src/bip32.rs
@@ -742,7 +742,7 @@ impl Xpriv {
/// Constructs a new BIP-0340 keypair for Schnorr signatures and Taproot use matching the internal
/// secret key representation.
- pub fn to_keypair(self) -> Keypair { Keypair::from_secret_key(&self.private_key) }
+ pub fn to_keypair(self) -> Keypair { Keypair::from_private_key(&self.to_private_key()) }
/// Derives an extended private key from a path.
///
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 13b4ed63..81c50f83 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -408,18 +408,16 @@ impl Keypair {
Self::from_secp(kp)
}
- /// Constructs a [`Keypair`] directly from a secp256k1 secret key.
+ /// Constructs a [`Keypair`] from a [`PrivateKey`].
#[inline]
- pub fn from_secret_key(sk: &secp256k1::SecretKey) -> Self {
- Self::from(secp256k1::Keypair::from_secret_key(sk))
+ pub fn from_private_key(pk: &PrivateKey) -> Self {
+ Self::from(secp256k1::Keypair::from_secret_key(pk.as_inner()))
}
- /// Returns the [`secp256k1::SecretKey`] for this [`Keypair`].
- ///
- /// This is equivalent to using [`secp256k1::SecretKey::from_keypair`] on the inner value.
+ /// Returns a compressed [`PrivateKey`] for this [`Keypair`].
#[inline]
- pub fn to_secret_key(&self) -> secp256k1::SecretKey {
- secp256k1::SecretKey::from_keypair(self.as_inner())
+ pub fn to_private_key(&self) -> PrivateKey {
+ PrivateKey::from_secp(secp256k1::SecretKey::from_keypair(self.as_inner()))
}
/// Returns the secret bytes for this [`Keypair`].
@@ -482,6 +480,10 @@ impl From<Keypair> for secp256k1::PublicKey {
fn from(kp: Keypair) -> Self { kp.to_public_key().to_inner() }
}
+impl From<PrivateKey> for Keypair {
+ fn from(pk: PrivateKey) -> Self { Self::from_private_key(&pk) }
+}
+
impl PublicKey {
/// Constructs a new compressed ECDSA public key from the provided generic secp256k1 public key.
#[deprecated(since = "TBD", note = "use `from_secp` instead")]
@@ -2298,7 +2300,7 @@ mod tests {
)
.unwrap();
let sk = PrivateKey::from_secret_bytes(bytes).unwrap();
- Keypair::from_secret_key(sk.as_inner())
+ Keypair::from_private_key(&sk)
};
// Use secp256k1::DisplaySecret, since no key type implements Display
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 0c3ed66e..afe0eb78 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -1889,7 +1889,7 @@ mod tests {
use crate::consensus::serde as con_serde;
use crate::crypto::key::XOnlyPublicKey;
- use crate::key::{Keypair, TapTweak};
+ use crate::key::{Keypair, PrivateKey, TapTweak};
use crate::taproot::TapNodeHash;
#[derive(serde::Deserialize)]
@@ -1998,7 +1998,7 @@ mod tests {
for mut inp in key_path.input_spending {
let tx_ind = inp.given.txin_index;
- let internal_priv_key = inp.given.internal_privkey;
+ let internal_priv_key = PrivateKey::from_secp(inp.given.internal_privkey);
let merkle_root = inp.given.merkle_root;
let hash_ty = inp.given.hash_type;
@@ -2013,7 +2013,7 @@ mod tests {
};
// tests
- let keypair = Keypair::from_secret_key(&internal_priv_key);
+ let keypair = Keypair::from_private_key(&internal_priv_key);
let internal_key = XOnlyPublicKey::from_keypair(&keypair);
let tweaked_keypair = keypair.tap_tweak(merkle_root);
let mut sig_msg = Vec::new();
@@ -2042,8 +2042,8 @@ mod tests {
assert_eq!(expected_hash_ty, hash_ty);
assert_eq!(expected_key_spend_sig, key_spend_sig);
- let tweaked_priv_key = tweaked_keypair.to_secret_key();
- assert_eq!(expected.tweaked_privkey, tweaked_priv_key);
+ let tweaked_priv_key = tweaked_keypair.to_private_key();
+ assert_eq!(PrivateKey::from_secp(expected.tweaked_privkey), tweaked_priv_key);
}
}
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index b9f065f9..57faed64 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -429,7 +429,7 @@ impl Psbt {
// According to BIP-0371, we also need to consider the condition leaf_hashes.is_empty() for a more accurate determination.
if internal_key == xonly && leaf_hashes.is_empty() && input.tap_key_sig.is_none() {
let (sighash, sighash_type) = self.sighash_taproot(input_index, cache, None)?;
- let key_pair = Keypair::from_secret_key(sk.as_inner())
+ let key_pair = Keypair::from_private_key(&sk)
.tap_tweak(input.tap_merkle_root)
.into_keypair();
@@ -451,7 +451,7 @@ impl Psbt {
.collect::<Vec<_>>();
if !leaf_hashes.is_empty() {
- let key_pair = Keypair::from_secret_key(sk.as_inner());
+ let key_pair = Keypair::from_private_key(&sk);
for lh in leaf_hashes {
let (sighash, sighash_type) =
diff --git a/bitcoin/tests/psbt-sign-taproot.rs b/bitcoin/tests/psbt-sign-taproot.rs
index cc72fce1..388db889 100644
--- a/bitcoin/tests/psbt-sign-taproot.rs
+++ b/bitcoin/tests/psbt-sign-taproot.rs
@@ -82,10 +82,8 @@ fn psbt_sign_taproot() {
//
// Step 2: sign psbt.
//
- let keystore = Keystore {
- mfp: mfp.parse::<Fingerprint>().unwrap(),
- sk: PrivateKey::from_secp(kp.to_secret_key()),
- };
+ let keystore =
+ Keystore { mfp: mfp.parse::<Fingerprint>().unwrap(), sk: kp.to_private_key() };
let _ = psbt_key_path_spend.sign(&keystore);
let sig = "92864dc9e56b6260ecbd54ec16b94bb597a2e6be7cca0de89d75e17921e0e1528cba32dd04217175c237e1835b5db1c8b384401718514f9443dce933c6ba9c87";
@@ -112,10 +110,8 @@ fn psbt_sign_taproot() {
let x_only_pubkey = kp.to_x_only_public_key().with_parity(secp256k1::Parity::Even);
let signing_key_path = sk_path[1].1;
- let keystore = Keystore {
- mfp: mfp.parse::<Fingerprint>().unwrap(),
- sk: PrivateKey::from_secp(kp.to_secret_key()),
- };
+ let keystore =
+ Keystore { mfp: mfp.parse::<Fingerprint>().unwrap(), sk: kp.to_private_key() };
//
// Step 1: create psbt for script path spend.
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.