Remove PrivateKeyExt and make PrivateKey::as_inner private
What changed, and why it matters
This commit is a routine API cleanup, not a security fix. It removes a helper trait called PrivateKeyExt and hides a low-level accessor method (as_inner) that exposes the underlying secret key object. The same signing behavior is preserved inside the crate, just with less public surface area. There is no patch for an active vulnerability.
No immediate action required. Treat as a normal API-breaking refactor. Downstream users relying on PrivateKeyExt or PrivateKey::as_inner will need to update their code; consider noting the API change in release notes.
Security signals we found
Reduction of public API surface for secret-key material
Removal of extension trait that exposed raw ECDSA recoverable signing
as_inner accessor narrowed from pub to pub(super)
No mention of vulnerability, CVE, bug bounty, or security advisory in commit message
Evidence from the diff
The change removes the public PrivateKeyExt extension trait and its raw_ecdsa_sign_recoverable method, rewrites the two call sites in sign_message.rs to construct recoverable signatures directly from PrivateKey::to_secret_bytes, and narrows PrivateKey::as_inner from pub to pub(super). This reduces the public API surface and prevents external crates from obtaining a reference to the inner secp256k1::SecretKey. It is a hardening/refactoring commit rather than a fix for a disclosed bug.
Changed components
bitcoin/src/crypto/key.rsbitcoin/src/lib.rsbitcoin/src/sign_message.rscrypto/src/key.rsInspect captured patch +14 / −34
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index c5539de1..9173d295 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -7,8 +7,6 @@
use crate::internal_macros::define_extension_trait;
use crate::script::{self, WitnessScriptBuf};
-#[cfg(feature = "secp-recovery")]
-use crate::sign_message::MessageSignature;
#[rustfmt::skip] // Keep public re-exports separate.
pub use secp256k1::{constants, Parity};
@@ -68,35 +66,10 @@ define_extension_trait! {
}
}
-#[cfg(feature = "secp-recovery")]
-define_extension_trait! {
- /// Extension functionality for the [`PrivateKey`] type.
- pub trait PrivateKeyExt impl for PrivateKey {
- /// ECDSA signs a [`Message`] with this private key.
- ///
- /// This produces an ECDSA signature with a recovery ID for pubkey recovery.
- /// See [`RecoverableSignature::sign_ecdsa_recoverable`] for details.
- ///
- /// [`Message`]: secp256k1::Message
- /// [`RecoverableSignature::sign_ecdsa_recoverable`]: secp256k1::ecdsa::RecoverableSignature::sign_ecdsa_recoverable
- #[inline]
- fn raw_ecdsa_sign_recoverable(
- &self,
- msg: impl Into<secp256k1::Message>,
- ) -> MessageSignature {
- MessageSignature::new(
- secp256k1::ecdsa::RecoverableSignature::sign_ecdsa_recoverable(msg, self.as_inner()),
- self.compressed(),
- )
- }
- }
-}
-
mod sealed {
pub trait Sealed {}
impl Sealed for super::FullPublicKey {}
impl Sealed for super::LegacyPublicKey {}
- impl Sealed for super::PrivateKey {}
}
#[cfg(test)]
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index 0aa6ae55..65b7910e 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -113,8 +113,6 @@ pub mod ext {
};
#[cfg(feature = "bitcoinconsensus")]
pub use crate::consensus_validation::{ScriptPubKeyExt as _, TransactionExt as _};
- #[cfg(feature = "secp-recovery")]
- pub use crate::key::PrivateKeyExt as _;
}
pub mod address;
pub mod bip158;
diff --git a/bitcoin/src/sign_message.rs b/bitcoin/src/sign_message.rs
index 82d1b5e1..604fc85e 100644
--- a/bitcoin/src/sign_message.rs
+++ b/bitcoin/src/sign_message.rs
@@ -8,8 +8,6 @@
use encoding::CompactSizeEncoder;
use hashes::{sha256d, HashEngine};
-#[cfg(feature = "secp-recovery")]
-use crate::key::PrivateKeyExt as _;
#[cfg(feature = "secp-recovery")]
use crate::PrivateKey;
@@ -179,7 +177,13 @@ pub fn signed_msg_hash(msg: impl AsRef<[u8]>) -> sha256d::Hash {
pub fn sign(msg: impl AsRef<[u8]>, privkey: &PrivateKey) -> MessageSignature {
let msg_hash = signed_msg_hash(msg);
let msg_to_sign = secp256k1::Message::from_digest(msg_hash.to_byte_array());
- privkey.raw_ecdsa_sign_recoverable(msg_to_sign)
+
+ let secp_key = secp256k1::SecretKey::from_secret_bytes(privkey.to_secret_bytes())
+ .expect("to_secret_bytes yields underlying valid secp bytes");
+ MessageSignature::new(
+ secp256k1::ecdsa::RecoverableSignature::sign_ecdsa_recoverable(msg_to_sign, &secp_key),
+ privkey.compressed(),
+ )
}
/// Error types for message signing.
@@ -267,7 +271,12 @@ mod tests {
let msg_hash = super::signed_msg_hash(message);
let msg = secp256k1::Message::from_digest(msg_hash.to_byte_array());
let privkey = PrivateKey::generate();
- let signature = privkey.raw_ecdsa_sign_recoverable(msg);
+ let secp_key = secp256k1::SecretKey::from_secret_bytes(privkey.to_secret_bytes())
+ .expect("to_secret_bytes yields underlying valid secp bytes");
+ let signature = MessageSignature::new(
+ secp256k1::ecdsa::RecoverableSignature::sign_ecdsa_recoverable(msg, &secp_key),
+ privkey.compressed(),
+ );
assert_eq!(signature.to_string(), super::sign(message, &privkey).to_string());
assert_eq!(signature.to_base64(), signature.to_string());
diff --git a/crypto/src/key.rs b/crypto/src/key.rs
index 0416ead8..6d7ddd2a 100644
--- a/crypto/src/key.rs
+++ b/crypto/src/key.rs
@@ -206,7 +206,7 @@ mod private_key {
/// Returns a reference to the inner secp256k1 secret key.
#[inline]
- pub fn as_inner(&self) -> &secp256k1::SecretKey { &self.inner }
+ pub(super) fn as_inner(&self) -> &secp256k1::SecretKey { &self.inner }
/// Returns whether this private key should be serialized as compressed.
#[inline]
Why this scored 19/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.