Change sign_message::sign to take &PrivateKey
What changed, and why it matters
This commit is a routine API refactor in the rust-bitcoin library. It changes the message-signing function so it accepts a higher-level PrivateKey reference instead of a low-level secp256k1 secret key, and moves the underlying signing logic into a new PrivateKey method. There is no indication this fixes a security bug; it is a design cleanup to hide internal cryptographic types from users and prepare for future API changes.
No security action required. Treat as a normal API refactor. Downstream users calling sign_message::sign will need to pass &PrivateKey instead of SecretKey when upgrading.
Security signals we found
No security-relevant behavior change: same ECDSA recoverable signing operation, same message hash construction.
API hardening: hides secp256k1::SecretKey from a public function signature and moves toward reference-based APIs, which can reduce accidental key copying.
No bounds, overflow, or memory-safety changes visible in the diff.
No mention of vulnerability, CVE, bug, or security fix in commit message or code comments.
Evidence from the diff
The patch refactors sign_message::sign to take &PrivateKey rather than secp256k1::SecretKey, and introduces PrivateKey::raw_ecdsa_sign_recoverable. The new method wraps secp256k1::ecdsa::RecoverableSignature::sign_ecdsa_recoverable and preserves the key’s compressed flag. Tests are updated to use PrivateKey::generate() and CompressedPublicKey. The cryptographic operation (ECDSA recoverable signature over the Bitcoin message hash) remains unchanged; only the public surface and ownership semantics differ.
Changed components
bitcoin/src/sign_message.rsbitcoin/src/crypto/key.rsInspect captured patch +30 / −15
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 05df49a7..64a61dfd 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -25,6 +25,8 @@ use crate::prelude::{DisplayHex, String, Vec};
use crate::script::{self, WitnessScriptBuf};
#[cfg(feature = "serde")]
use crate::serde::{Deserialize, Deserializer, Serialize, Serializer};
+#[cfg(feature = "secp-recovery")]
+use crate::sign_message::MessageSignature;
use crate::taproot::{TapNodeHash, TapTweakHash};
#[rustfmt::skip] // Keep public re-exports separate.
@@ -965,6 +967,25 @@ impl PrivateKey {
false => Self::from_secp_uncompressed(self.as_inner().negate()),
}
}
+
+ /// 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]
+ #[cfg(feature = "secp-recovery")]
+ pub 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(),
+ )
+ }
}
/// A Bitcoin ECDSA private key with known network for WIF.
diff --git a/bitcoin/src/sign_message.rs b/bitcoin/src/sign_message.rs
index 3e02b134..ab2265fa 100644
--- a/bitcoin/src/sign_message.rs
+++ b/bitcoin/src/sign_message.rs
@@ -6,10 +6,10 @@
//! library is used with the `secp-recovery` feature.
use hashes::{sha256d, HashEngine};
-#[cfg(feature = "secp-recovery")]
-use secp256k1::SecretKey;
use crate::consensus::encode::WriteExt;
+#[cfg(feature = "secp-recovery")]
+use crate::PrivateKey;
#[rustfmt::skip]
#[doc(inline)]
@@ -210,13 +210,10 @@ pub fn signed_msg_hash(msg: impl AsRef<[u8]>) -> sha256d::Hash {
/// Sign message using Bitcoin's message signing format.
#[cfg(feature = "secp-recovery")]
-pub fn sign(msg: impl AsRef<[u8]>, privkey: SecretKey) -> MessageSignature {
- use secp256k1::ecdsa::RecoverableSignature;
-
+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());
- let secp_sig = RecoverableSignature::sign_ecdsa_recoverable(msg_to_sign, &privkey);
- MessageSignature { signature: secp_sig, compressed: true }
+ privkey.raw_ecdsa_sign_recoverable(msg_to_sign)
}
#[cfg(test)]
@@ -237,18 +234,15 @@ mod tests {
#[test]
#[cfg(all(feature = "secp-recovery", feature = "base64", feature = "rand", feature = "std"))]
fn message_signature() {
- use secp256k1::ecdsa::RecoverableSignature;
-
- use crate::{Address, AddressType, Network, NetworkKind};
+ use crate::{Address, AddressType, CompressedPublicKey, Network, NetworkKind, PrivateKey};
let message = "rust-bitcoin MessageSignature test";
let msg_hash = super::signed_msg_hash(message);
let msg = secp256k1::Message::from_digest(msg_hash.to_byte_array());
- let privkey = secp256k1::SecretKey::new(&mut secp256k1::rand::rng());
- let secp_sig = RecoverableSignature::sign_ecdsa_recoverable(msg, &privkey);
- let signature = super::MessageSignature { signature: secp_sig, compressed: true };
+ let privkey = PrivateKey::generate();
+ let signature = privkey.raw_ecdsa_sign_recoverable(msg);
- assert_eq!(signature.to_string(), super::sign(message, privkey).to_string());
+ assert_eq!(signature.to_string(), super::sign(message, &privkey).to_string());
assert_eq!(signature.to_base64(), signature.to_string());
let signature2 = &signature.to_string().parse::<super::MessageSignature>().unwrap();
let pubkey = signature2
@@ -272,7 +266,7 @@ mod tests {
let p2pkh = Address::p2pkh(pubkey, Network::Bitcoin);
assert_eq!(signature2.is_signed_by_address(&p2pkh, msg_hash), Ok(true));
- assert_eq!(pubkey.to_inner(), secp256k1::PublicKey::from_secret_key(&privkey));
+ assert_eq!(pubkey, CompressedPublicKey::from_private_key(privkey).unwrap());
let signature_base64 = signature.to_base64();
let signature_round_trip =
super::MessageSignature::from_base64(&signature_base64).expect("message signature");
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.