Remove public access to all fields of PublicKey and PrivateKey
What changed, and why it matters
This commit hides the internal fields of two key types (PublicKey and PrivateKey) so outside code can no longer read or change them directly. Instead, it provides read-only accessor methods. This is a normal defensive-coding change that improves encapsulation and makes future misuse less likely, but the commit itself does not fix a known active bug or vulnerability.
Treat as a routine API-cleanup / hardening commit. Review downstream consumers for breaking changes due to removed public fields, but no urgent security response is indicated.
Security signals we found
Reduction of public mutable surface on cryptographic key types
Encapsulation hardening to prevent external mutation of key metadata
No direct fix for a reported vulnerability or incident
Evidence from the diff
The patch changes compressed and network fields from pub to private in PublicKey and PrivateKey, and adds compressed(), network() getters. All in-crate direct field accesses are replaced with accessor calls. One test that previously mutated pk.compressed = true now reconstructs the key via PublicKey::from_secp(pk.to_inner()). This is an API-hardening refactor rather than a patch for an exploitable flaw.
Changed components
bitcoin/src/crypto/key.rs (PublicKey, PrivateKey structs)bitcoin/src/blockdata/script/builder.rsbitcoin/src/psbt/mod.rsInspect captured patch +34 / −22
diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs
index c977cfc9..e640827b 100644
--- a/bitcoin/src/blockdata/script/builder.rs
+++ b/bitcoin/src/blockdata/script/builder.rs
@@ -137,7 +137,7 @@ impl<T> Builder<T> {
/// Adds instructions to push a public key onto the stack.
pub fn push_key(self, key: PublicKey) -> Self {
- if key.compressed {
+ if key.compressed() {
self.push_slice(key.to_inner().serialize())
} else {
self.push_slice(key.to_inner().serialize_uncompressed())
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index ec3aaf64..3576c7f2 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -404,7 +404,7 @@ impl From<Keypair> for secp256k1::PublicKey {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PublicKey {
/// Whether this public key should be serialized as compressed.
- pub compressed: bool,
+ compressed: bool,
/// The actual ECDSA key.
inner: secp256k1::PublicKey,
}
@@ -438,8 +438,12 @@ impl PublicKey {
#[inline]
pub fn to_inner(self) -> secp256k1::PublicKey { self.inner }
+ /// Returns whether this public key should be serialized as compressed.
+ #[inline]
+ pub fn compressed(&self) -> bool { self.compressed }
+
fn with_serialized<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
- if self.compressed {
+ if self.compressed() {
f(&self.to_inner().serialize())
} else {
f(&self.to_inner().serialize_uncompressed())
@@ -453,7 +457,7 @@ impl PublicKey {
/// Returns bitcoin 160-bit hash of the public key for witness program
pub fn wpubkey_hash(&self) -> Result<WPubkeyHash, UncompressedPublicKeyError> {
- if self.compressed {
+ if self.compressed() {
Ok(WPubkeyHash::from_byte_array(
hash160::Hash::hash(&self.to_inner().serialize()).to_byte_array(),
))
@@ -761,7 +765,7 @@ impl TryFrom<PublicKey> for CompressedPublicKey {
type Error = UncompressedPublicKeyError;
fn try_from(value: PublicKey) -> Result<Self, Self::Error> {
- if value.compressed {
+ if value.compressed() {
Ok(Self::from_secp(value.to_inner()))
} else {
Err(UncompressedPublicKeyError)
@@ -797,9 +801,9 @@ impl From<&CompressedPublicKey> for WPubkeyHash {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct PrivateKey {
/// Whether this private key should be serialized as compressed.
- pub compressed: bool,
+ compressed: bool,
/// The network kind on which this key should be used.
- pub network: NetworkKind,
+ network: NetworkKind,
/// The actual ECDSA key.
inner: secp256k1::SecretKey,
}
@@ -829,9 +833,17 @@ impl PrivateKey {
#[inline]
pub fn as_inner(&self) -> &secp256k1::SecretKey { &self.inner }
+ /// Returns whether this private key should be serialized as compressed.
+ #[inline]
+ pub fn compressed(&self) -> bool { self.compressed }
+
+ /// Returns the [`NetworkKind`] of this key.
+ #[inline]
+ pub fn network(&self) -> NetworkKind { self.network }
+
/// Constructs a new public key from this private key.
pub fn public_key(&self) -> PublicKey {
- match self.compressed {
+ match self.compressed() {
true => PublicKey::from_secp(secp256k1::PublicKey::from_secret_key(self.as_inner())),
false => PublicKey::from_secp_uncompressed(secp256k1::PublicKey::from_secret_key(self.as_inner())),
}
@@ -866,10 +878,10 @@ impl PrivateKey {
#[rustfmt::skip]
pub fn fmt_wif(&self, fmt: &mut dyn fmt::Write) -> fmt::Result {
let mut ret = [0; 34];
- ret[0] = if self.network.is_mainnet() { 128 } else { 239 };
+ ret[0] = if self.network().is_mainnet() { 128 } else { 239 };
ret[1..33].copy_from_slice(&self.as_inner()[..]);
- let privkey = if self.compressed {
+ let privkey = if self.compressed() {
ret[33] = 1;
base58::encode_check(&ret[..])
} else {
@@ -924,9 +936,9 @@ impl PrivateKey {
/// with specific public key formats and BIP-0340 requirements.
#[inline]
pub fn negate(&self) -> Self {
- match self.compressed {
- true => Self::from_secp(self.as_inner().negate(), self.network),
- false => Self::from_secp_uncompressed(self.as_inner().negate(), self.network),
+ match self.compressed() {
+ true => Self::from_secp(self.as_inner().negate(), self.network()),
+ false => Self::from_secp_uncompressed(self.as_inner().negate(), self.network()),
}
}
}
@@ -1638,11 +1650,11 @@ mod tests {
// testnet compressed
let sk =
PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
- assert_eq!(sk.network, NetworkKind::Test);
- assert!(sk.compressed);
+ assert_eq!(sk.network(), NetworkKind::Test);
+ assert!(sk.compressed());
assert_eq!(&sk.to_wif(), "cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy");
- let pk = Address::p2pkh(sk.public_key(), sk.network);
+ let pk = Address::p2pkh(sk.public_key(), sk.network());
assert_eq!(&pk.to_string(), "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx");
// test string conversion
@@ -1654,18 +1666,18 @@ mod tests {
// mainnet uncompressed
let sk =
PrivateKey::from_wif("5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3").unwrap();
- assert_eq!(sk.network, NetworkKind::Main);
- assert!(!sk.compressed);
+ assert_eq!(sk.network(), NetworkKind::Main);
+ assert!(!sk.compressed());
assert_eq!(&sk.to_wif(), "5JYkZjmN7PVMjJUfJWfRFwtuXTGB439XV6faajeHPAM9Z2PT2R3");
let mut pk = sk.public_key();
- assert!(!pk.compressed);
+ assert!(!pk.compressed());
assert_eq!(&pk.to_string(), "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133");
assert_eq!(pk, "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133"
.parse::<PublicKey>().unwrap());
- let addr = Address::p2pkh(pk, sk.network);
+ let addr = Address::p2pkh(pk, sk.network());
assert_eq!(&addr.to_string(), "1GhQvF6dL8xa6wBxLnWmHcQsurx9RxiMc8");
- pk.compressed = true;
+ pk = PublicKey::from_secp(pk.to_inner());
assert_eq!(
&pk.to_string(),
"032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af"
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index f61dd98f..d1f365cc 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -1396,7 +1396,7 @@ mod tests {
let psbt = hex_psbt("70736274ff01003302000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff000000000000420204bb0d5d0cca36e7b9c80f63bc04c1240babb83bcd2803ef7ac8b6e2af594291daec281e856c98d210c5ab14dfd5828761f8ee7d5f45ca21ad3e4c4b41b747a3a047304402204f67e2afb76142d44fae58a2495d33a3419daa26cd0db8d04f3452b63289ac0f022010762a9fb67e94cc5cad9026f6dc99ff7f070f4278d30fbc7d0c869dd38c7fe70100").unwrap();
assert!(psbt.inputs[0].partial_sigs.len() == 1);
let pk = psbt.inputs[0].partial_sigs.iter().next().unwrap().0;
- assert!(!pk.compressed);
+ assert!(!pk.compressed());
}
#[test]
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.