Introduce from_secp constructors for PublicKey and PrivateKey
What changed, and why it matters
This is a routine code cleanup in the rust-bitcoin library. It introduces new constructor functions named from_secp for creating public and private keys, and replaces direct internal field access with calls to those constructors. The old constructors are kept but marked as deprecated. There is no security bug being fixed here; the change is about improving code structure and preparing to hide internal details in the future.
No security action required. Treat as a normal API refactor; downstream users may eventually migrate from deprecated new() constructors to from_secp() variants.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors PublicKey and PrivateKey construction in rust-bitcoin. It adds PublicKey::from_secp, PublicKey::from_secp_uncompressed, PrivateKey::from_secp, and PrivateKey::from_secp_uncompressed. Existing PublicKey::new / PublicKey::new_uncompressed / PrivateKey::new are retained as deprecated wrappers. All internal struct-literal instantiations are replaced with calls to the new constructors. This is an encapsulation refactor with no functional or cryptographic change visible in the diff.
Changed components
bitcoin/src/crypto/key.rsbitcoin/src/bip32.rsbitcoin/src/psbt/mod.rsbitcoin/src/sign_message.rsbitcoin/src/address/mod.rsbitcoin/examples/create-p2wpkh-address.rsbitcoin/examples/sign-tx-segwit-v0.rsbitcoin/tests/psbt-sign-taproot.rsInspect captured patch +57 / −35
diff --git a/bitcoin/examples/create-p2wpkh-address.rs b/bitcoin/examples/create-p2wpkh-address.rs
index a036bc53..94a42180 100644
--- a/bitcoin/examples/create-p2wpkh-address.rs
+++ b/bitcoin/examples/create-p2wpkh-address.rs
@@ -8,7 +8,7 @@ fn main() {
let (secret_key, public_key) = secp256k1::generate_keypair(&mut rand::rng());
// Create a Bitcoin private key to be used on the Bitcoin mainnet.
- let private_key = PrivateKey::new(secret_key, Network::Bitcoin);
+ let private_key = PrivateKey::from_secp(secret_key, Network::Bitcoin);
// Create a compressed Bitcoin public key from the secp256k1 public key.
let public_key = CompressedPublicKey::from_secp(public_key);
diff --git a/bitcoin/examples/sign-tx-segwit-v0.rs b/bitcoin/examples/sign-tx-segwit-v0.rs
index 5f64df68..c71621c8 100644
--- a/bitcoin/examples/sign-tx-segwit-v0.rs
+++ b/bitcoin/examples/sign-tx-segwit-v0.rs
@@ -87,7 +87,7 @@ fn main() {
/// In a real application these would be actual secrets.
fn senders_keys() -> (SecretKey, WPubkeyHash) {
let sk = SecretKey::new(&mut rand::rng());
- let pk = bitcoin::PublicKey::new(sk.public_key());
+ let pk = bitcoin::PublicKey::from_secp(sk.public_key());
let wpkh = pk.wpubkey_hash().expect("key is compressed");
(sk, wpkh)
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index 11bf1acd..e911a0ca 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -15,7 +15,7 @@
//!
//! // Generate random key pair.
//! let (_sk, pk) = secp256k1::generate_keypair(&mut rand::rng());
-//! let public_key = PublicKey::new(pk); // Or `PublicKey::from(pk)`.
+//! let public_key = PublicKey::from_secp(pk); // Or `PublicKey::from(pk)`.
//!
//! // Generate a mainnet pay-to-pubkey-hash address.
//! let address = Address::p2pkh(&public_key, Network::Bitcoin);
diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs
index c39c6dbe..4ee7ce4e 100644
--- a/bitcoin/src/bip32.rs
+++ b/bitcoin/src/bip32.rs
@@ -734,7 +734,7 @@ impl Xpriv {
/// Constructs a new ECDSA compressed private key matching internal secret key representation.
pub fn to_private_key(self) -> PrivateKey {
- PrivateKey { compressed: true, network: self.network, inner: self.private_key }
+ PrivateKey::from_secp(self.private_key, self.network)
}
/// Constructs a new extended public key from this extended private key.
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 43685bad..7671e1f4 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -411,13 +411,26 @@ pub struct PublicKey {
impl PublicKey {
/// Constructs a new compressed ECDSA public key from the provided generic secp256k1 public key.
+ #[deprecated(since = "TBD", note = "use `from_secp` instead")]
pub fn new(key: impl Into<secp256k1::PublicKey>) -> Self {
- Self { compressed: true, inner: key.into() }
+ Self::from_secp(key)
}
/// Constructs a new uncompressed (legacy) ECDSA public key from the provided generic secp256k1
/// public key.
+ #[deprecated(since = "TBD", note = "use `from_secp_uncompressed` instead")]
pub fn new_uncompressed(key: impl Into<secp256k1::PublicKey>) -> Self {
+ Self::from_secp_uncompressed(key)
+ }
+
+ /// Constructs a new compressed ECDSA public key from the provided secp256k1 public key.
+ pub fn from_secp(key: impl Into<secp256k1::PublicKey>) -> Self {
+ Self { compressed: true, inner: key.into() }
+ }
+
+ /// Constructs a new uncompressed (legacy) ECDSA public key from the provided secp256k1 public
+ /// key.
+ pub fn from_secp_uncompressed(key: impl Into<secp256k1::PublicKey>) -> Self {
Self { compressed: false, inner: key.into() }
}
@@ -569,7 +582,10 @@ impl PublicKey {
return Err(FromSliceError::InvalidKeyPrefix(data[0]));
}
- Ok(Self { compressed, inner: secp256k1::PublicKey::from_slice(data)? })
+ Ok(match compressed {
+ true => Self::from_secp(secp256k1::PublicKey::from_slice(data)?),
+ false => Self::from_secp_uncompressed(secp256k1::PublicKey::from_slice(data)?),
+ })
}
/// Computes the public key as supposed to be used with this secret.
@@ -577,7 +593,7 @@ impl PublicKey {
/// Extracts the public key from a Keypair
pub fn from_keypair(pair: &Keypair) -> Self {
- Self::new(secp256k1::PublicKey::from_keypair(&pair.to_inner()))
+ Self::from_secp(secp256k1::PublicKey::from_keypair(&pair.to_inner()))
}
/// Checks that `sig` is a valid ECDSA signature for `msg` using this public key.
@@ -591,7 +607,7 @@ impl PublicKey {
}
impl From<secp256k1::PublicKey> for PublicKey {
- fn from(pk: secp256k1::PublicKey) -> Self { Self::new(pk) }
+ fn from(pk: secp256k1::PublicKey) -> Self { Self::from_secp(pk) }
}
impl From<PublicKey> for XOnlyPublicKey {
@@ -755,7 +771,7 @@ impl TryFrom<PublicKey> for CompressedPublicKey {
}
impl From<CompressedPublicKey> for PublicKey {
- fn from(value: CompressedPublicKey) -> Self { Self::new(value.to_inner()) }
+ fn from(value: CompressedPublicKey) -> Self { Self::from_secp(value.to_inner()) }
}
impl From<CompressedPublicKey> for XOnlyPublicKey {
@@ -795,25 +811,26 @@ impl PrivateKey {
#[cfg(all(feature = "rand", feature = "std"))]
pub fn generate(network: impl Into<NetworkKind>) -> Self {
let secret_key = secp256k1::SecretKey::new(&mut rand::rng());
- Self::new(secret_key, network.into())
+ Self::from_secp(secret_key, network.into())
}
- /// Constructs a new compressed ECDSA private key from the provided generic secp256k1 private key
- /// and the specified network.
- pub fn new(key: secp256k1::SecretKey, network: impl Into<NetworkKind>) -> Self {
+
+ /// Constructs a new compressed ECDSA private key from the provided secp256k1 private
+ /// key and the specified network.
+ pub fn from_secp(key: secp256k1::SecretKey, network: impl Into<NetworkKind>) -> Self {
Self { compressed: true, network: network.into(), inner: key }
}
- /// Constructs a new uncompressed (legacy) ECDSA private key from the provided generic secp256k1
+ /// Constructs a new uncompressed (legacy) ECDSA private key from the provided secp256k1
/// private key and the specified network.
- pub fn new_uncompressed(key: secp256k1::SecretKey, network: impl Into<NetworkKind>) -> Self {
+ pub fn from_secp_uncompressed(key: secp256k1::SecretKey, network: impl Into<NetworkKind>) -> Self {
Self { compressed: false, network: network.into(), inner: key }
}
/// Constructs a new public key from this private key.
pub fn public_key(&self) -> PublicKey {
- PublicKey {
- compressed: self.compressed,
- inner: secp256k1::PublicKey::from_secret_key(&self.inner),
+ match self.compressed {
+ true => PublicKey::from_secp(secp256k1::PublicKey::from_secret_key(&self.inner)),
+ false => PublicKey::from_secp_uncompressed(secp256k1::PublicKey::from_secret_key(&self.inner)),
}
}
@@ -829,7 +846,7 @@ impl PrivateKey {
data: [u8; 32],
network: impl Into<NetworkKind>,
) -> Result<Self, secp256k1::Error> {
- Ok(Self::new(secp256k1::SecretKey::from_secret_bytes(data)?, network))
+ Ok(Self::from_secp(secp256k1::SecretKey::from_secret_bytes(data)?, network))
}
/// Deserializes a private key from a slice.
@@ -891,7 +908,10 @@ impl PrivateKey {
}
};
- Ok(Self { compressed, network, inner: secp256k1::SecretKey::from_secret_bytes(*key)? })
+ Ok(match compressed {
+ true => Self::from_secp(secp256k1::SecretKey::from_secret_bytes(*key)?, network),
+ false => Self::from_secp_uncompressed(secp256k1::SecretKey::from_secret_bytes(*key)?, network),
+ })
}
/// Returns a new private key with the negated secret value.
@@ -901,7 +921,10 @@ impl PrivateKey {
/// with specific public key formats and BIP-0340 requirements.
#[inline]
pub fn negate(&self) -> Self {
- Self { compressed: self.compressed, network: self.network, inner: self.inner.negate() }
+ match self.compressed {
+ true => Self::from_secp(self.inner.negate(), self.network),
+ false => Self::from_secp_uncompressed(self.inner.negate(), self.network),
+ }
}
}
@@ -1711,7 +1734,7 @@ mod tests {
let sk = KEY_WIF.parse::<PrivateKey>().unwrap();
let pk = PublicKey::from_private_key(sk);
- let pk_u = PublicKey { inner: pk.inner, compressed: false };
+ let pk_u = PublicKey::from_secp_uncompressed(pk.inner);
assert_tokens(&sk, &[Token::BorrowedStr(KEY_WIF)]);
assert_tokens(&pk.compact(), &[Token::BorrowedBytes(&PK_BYTES[..])]);
@@ -1774,7 +1797,7 @@ mod tests {
let key1 = "02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8"
.parse::<PublicKey>()
.unwrap();
- let key2 = PublicKey { inner: key1.inner, compressed: false };
+ let key2 = PublicKey::from_secp_uncompressed(key1.inner);
let arrayvec1 = ArrayVec::from_slice(
&<[u8; 33]>::from_hex(
"02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8",
@@ -1907,8 +1930,8 @@ mod tests {
let kp = Keypair::generate(&mut rand::rng());
- let _ = PublicKey::new(kp);
- let _ = PublicKey::new_uncompressed(kp);
+ let _ = PublicKey::from_secp(kp);
+ let _ = PublicKey::from_secp_uncompressed(kp);
}
#[test]
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 76cf7d57..3ace7e38 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -356,7 +356,7 @@ impl Psbt {
for (pk, key_source) in input.bip32_derivation.iter() {
let sk = if let Ok(Some(sk)) = k.get_key(&KeyRequest::Bip32(key_source.clone())) {
sk
- } else if let Ok(Some(sk)) = k.get_key(&KeyRequest::Pubkey(PublicKey::new(*pk))) {
+ } else if let Ok(Some(sk)) = k.get_key(&KeyRequest::Pubkey(PublicKey::from_secp(*pk))) {
sk
} else {
continue;
@@ -2349,7 +2349,7 @@ mod tests {
use secp256k1::rand;
let sk = SecretKey::new(&mut rand::rng());
- let priv_key = PrivateKey::new(sk, NetworkKind::Test);
+ let priv_key = PrivateKey::from_secp(sk, NetworkKind::Test);
let pk = PublicKey::from_private_key(priv_key);
(priv_key, pk)
@@ -2378,11 +2378,7 @@ mod tests {
let mut pubkey_map: HashMap<PublicKey, PrivateKey> = HashMap::new();
if parity == secp256k1::Parity::Even {
- priv_key = PrivateKey {
- compressed: priv_key.compressed,
- network: priv_key.network,
- inner: priv_key.inner.negate(),
- };
+ priv_key = priv_key.negate();
pk = priv_key.public_key();
}
diff --git a/bitcoin/src/sign_message.rs b/bitcoin/src/sign_message.rs
index a3ed532b..565fb278 100644
--- a/bitcoin/src/sign_message.rs
+++ b/bitcoin/src/sign_message.rs
@@ -133,7 +133,10 @@ mod message_signing {
) -> Result<PublicKey, MessageSignatureError> {
let msg = secp256k1::Message::from_digest(msg_hash.to_byte_array());
let pubkey = self.signature.recover_ecdsa(msg)?;
- Ok(PublicKey { inner: pubkey, compressed: self.compressed })
+ Ok(match self.compressed {
+ true => PublicKey::from_secp(pubkey),
+ false => PublicKey::from_secp_uncompressed(pubkey),
+ })
}
/// Verifies that the signature signs the message and was signed by the given address.
diff --git a/bitcoin/tests/psbt-sign-taproot.rs b/bitcoin/tests/psbt-sign-taproot.rs
index 1e409e97..e61d85c6 100644
--- a/bitcoin/tests/psbt-sign-taproot.rs
+++ b/bitcoin/tests/psbt-sign-taproot.rs
@@ -84,7 +84,7 @@ fn psbt_sign_taproot() {
//
let keystore = Keystore {
mfp: mfp.parse::<Fingerprint>().unwrap(),
- sk: PrivateKey::new(kp.to_secret_key(), Network::Testnet(bitcoin::TestnetVersion::V3)),
+ sk: PrivateKey::from_secp(kp.to_secret_key(), Network::Testnet(bitcoin::TestnetVersion::V3)),
};
let _ = psbt_key_path_spend.sign(&keystore);
@@ -114,7 +114,7 @@ fn psbt_sign_taproot() {
let keystore = Keystore {
mfp: mfp.parse::<Fingerprint>().unwrap(),
- sk: PrivateKey::new(kp.to_secret_key(), Network::Testnet(bitcoin::TestnetVersion::V3)),
+ sk: PrivateKey::from_secp(kp.to_secret_key(), Network::Testnet(bitcoin::TestnetVersion::V3)),
};
//
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.