What changed, and why it matters
This change removes the ability to accidentally print a Bitcoin private key using Rust's standard '{}' formatting. Before, code like println!("{}", private_key) would expose the secret key. Now developers must explicitly call .to_wif() or similar methods, making secret leakage less likely through casual logging or error messages. It is a defensive hardening improvement, not a fix for an active exploit.
Review downstream code for any reliance on PrivateKey implementing Display; replace println!/format!/{}/to_string() usage with explicit to_wif() where intentional secret output is required. Consider this a breaking API change requiring a version bump and release notes.
Security signals we found
Removal of Display impl for secret-bearing type
Prevention of accidental secret exposure via standard formatting macros
Defense-in-depth key-handling hardening
No cryptographic bug or memory-safety flaw fixed
Evidence from the diff
The commit removes the fmt::Display implementation for PrivateKey in rust-bitcoin. Previously, Display delegated to fmt_wif, so any format! / print! / log! call with {} would serialize the secret WIF. The patch keeps FromStr and serde::Serialize (now explicitly calling to_wif()), and rewrites internal callers and tests to use to_wif(). This is a deliberate API change to make exposing secret key material opt-in rather than default.
Changed components
bitcoin/src/crypto/key.rsbitcoin/embedded/src/main.rsbitcoin/examples/create-p2wpkh-address.rsInspect captured patch +8 / −10
diff --git a/bitcoin/embedded/src/main.rs b/bitcoin/embedded/src/main.rs
index 97d97118..f27a1127 100644
--- a/bitcoin/embedded/src/main.rs
+++ b/bitcoin/embedded/src/main.rs
@@ -30,7 +30,7 @@ fn main() -> ! {
// Load a private key
let raw = "L1HKVVLHXiUhecWnwFYF6L3shkf1E12HUmuZTESvBXUdx3yqVP1D";
let pk = PrivateKey::from_wif(raw).unwrap();
- hprintln!("Seed WIF: {}", pk).unwrap();
+ hprintln!("Seed WIF: {}", pk.to_wif()).unwrap();
// Derive address
let pubkey = pk.public_key().try_into().unwrap();
diff --git a/bitcoin/examples/create-p2wpkh-address.rs b/bitcoin/examples/create-p2wpkh-address.rs
index 94a42180..c33684f5 100644
--- a/bitcoin/examples/create-p2wpkh-address.rs
+++ b/bitcoin/examples/create-p2wpkh-address.rs
@@ -16,6 +16,6 @@ fn main() {
// Create a Bitcoin P2WPKH address.
let address = Address::p2wpkh(public_key, Network::Bitcoin);
- println!("Private Key: {private_key}");
+ println!("Private Key: {}", private_key.to_wif());
println!("Address: {address}");
}
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 8b54f624..6924741f 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -6,7 +6,7 @@
//! (de)serialized.
use core::convert::Infallible;
-use core::fmt::{self, Write as _};
+use core::fmt;
use core::str::FromStr;
use hashes::hash160;
@@ -968,7 +968,7 @@ impl PrivateKey {
#[allow(clippy::missing_panics_doc)]
pub fn to_wif(self) -> String {
let mut buf = String::new();
- buf.write_fmt(format_args!("{}", self)).unwrap();
+ let _ = self.fmt_wif(&mut buf);
buf.shrink_to_fit();
buf
}
@@ -1031,10 +1031,8 @@ impl PrivateKey {
}
}
-impl fmt::Display for PrivateKey {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_wif(f) }
-}
-
+// [`PrivateKey`] intentionally has a `FromStr` without a reciprocal `Display`.
+// Parsing from a WIF string should be convenient, printing secret data should not.
impl FromStr for PrivateKey {
type Err = FromWifError;
fn from_str(s: &str) -> Result<Self, FromWifError> { Self::from_wif(s) }
@@ -1043,7 +1041,7 @@ impl FromStr for PrivateKey {
#[cfg(feature = "serde")]
impl serde::Serialize for PrivateKey {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
- s.collect_str(self)
+ s.serialize_str(&self.to_wif())
}
}
@@ -1748,7 +1746,7 @@ mod tests {
assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx");
// test string conversion
- assert_eq!(&sk.to_string(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
+ assert_eq!(&sk.to_wif(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
let sk_str =
"cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy".parse::<PrivateKey>().unwrap();
assert_eq!(&sk.to_wif(), &sk_str.to_wif());
Why this scored 38/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.