What changed, and why it matters
This commit adds a new helper type for storing Bitcoin legacy public keys in their serialized form without needing a heap allocation. It is a routine internal API improvement and does not fix any security bug or change how keys are validated.
No security action needed. Treat as a normal feature/refactoring commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces SerializedLegacyPublicKey, a small stack-allocated wrapper around ArrayVec<u8, 65> that can hold either a 33-byte compressed or 65-byte uncompressed legacy public key. It implements Deref, AsRef, Borrow, and conversion to PushBytes so the serialized key can be used in scripts. The existing LegacyPublicKey::to_bytes() method is changed from returning Vec<u8> to returning this new type, and its deprecation attribute is removed. A round-trip unit test is added. No cryptographic validation logic is modified.
Changed components
bitcoin/src/crypto/key.rsInspect captured patch +86 / −3
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 6f1c51c3..c819d317 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -5,6 +5,7 @@
//! This module provides keys used in Bitcoin that can be roundtrip
//! (de)serialized.
+use core::borrow::Borrow;
use core::fmt;
use core::str::FromStr;
@@ -21,7 +22,7 @@ use crate::hex::{self, DecodeFixedLengthBytesError};
use crate::internal_macros::impl_asref_push_bytes;
use crate::network::NetworkKind;
use crate::prelude::{DisplayHex, String, Vec};
-use crate::script::{self, WitnessScriptBuf};
+use crate::script::{self, PushBytes, WitnessScriptBuf};
#[cfg(feature = "serde")]
use crate::serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "secp-recovery")]
@@ -30,6 +31,7 @@ use crate::taproot::{TapNodeHash, TapTweakHash};
#[rustfmt::skip] // Keep public re-exports separate.
pub use secp256k1::{constants, Parity, Verification};
+pub use serialized_legacy_public_key::SerializedLegacyPublicKey;
pub use encapsulate::{
FullPublicKey, Keypair, LegacyPublicKey, PrivateKey, SerializedXOnlyPublicKey, TweakedKeypair,
TweakedPublicKey, XOnlyPublicKey,
@@ -280,6 +282,64 @@ mod encapsulate {
}
}
+mod serialized_legacy_public_key {
+ use internals::array_vec::ArrayVec;
+ use crate::script::PushBytes;
+
+ /// A serialized form of `LegacyPublicKey`.
+ ///
+ /// This type contains the legacy public key in serialized as either compressed or
+ /// uncompressed. The type implements the standard conversion traits so it behaves a lot like
+ /// an array. In addition, the type implements `AsRef<PushBytes>`, so you can pass it into
+ /// script.
+ #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+ pub struct SerializedLegacyPublicKey(ArrayVec<u8, 65>);
+
+ impl SerializedLegacyPublicKey {
+ pub(crate) fn new_compressed(compressed: &[u8; 33]) -> Self {
+ Self(ArrayVec::from_slice(compressed))
+ }
+
+ pub(crate) fn new_uncompressed(uncompressed: &[u8; 65]) -> Self {
+ Self(ArrayVec::from_slice(uncompressed))
+ }
+ }
+
+ // Keep the proof close to the type definition
+ impl core::borrow::Borrow<PushBytes> for SerializedLegacyPublicKey {
+ fn borrow(&self) -> &PushBytes {
+ <&PushBytes>::try_from(&*self.0).expect("65 <= u32::MAX")
+ }
+ }
+}
+
+impl core::ops::Deref for SerializedLegacyPublicKey {
+ type Target = [u8];
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ <Self as Borrow<PushBytes>>::borrow(self).as_bytes()
+ }
+}
+
+impl AsRef<PushBytes> for SerializedLegacyPublicKey {
+ fn as_ref(&self) -> &PushBytes {
+ self.borrow()
+ }
+}
+
+impl AsRef<[u8]> for SerializedLegacyPublicKey {
+ fn as_ref(&self) -> &[u8] {
+ self
+ }
+}
+
+impl Borrow<[u8]> for SerializedLegacyPublicKey {
+ fn borrow(&self) -> &[u8] {
+ self
+ }
+}
+
impl XOnlyPublicKey {
/// Constructs an x-only public key from a keypair.
///
@@ -650,8 +710,13 @@ impl LegacyPublicKey {
}
/// Serializes the public key to bytes.
- #[deprecated(since = "TBD", note = "use to_vec instead")]
- pub fn to_bytes(self) -> Vec<u8> { self.to_vec() }
+ pub fn to_bytes(self) -> SerializedLegacyPublicKey {
+ if self.compressed() {
+ SerializedLegacyPublicKey::new_compressed(&self.serialize_compressed())
+ } else {
+ SerializedLegacyPublicKey::new_uncompressed(&self.serialize_uncompressed())
+ }
+ }
/// Serializes the public key to bytes.
#[allow(clippy::missing_panics_doc)]
@@ -2453,4 +2518,22 @@ mod tests {
secp256k1::SecretKey::from_secret_bytes(bitcoin_key.to_secret_bytes()).unwrap();
assert_eq!(PrivateKey::from_secp(secp_key), bitcoin_key);
}
+
+ #[test]
+ #[cfg(feature = "rand")]
+ #[cfg(feature = "std")]
+ fn serialized_legacy_public_key_roundtrip() {
+ let key = Keypair::generate().to_public_key();
+ assert!(key.compressed());
+ let serialized = &key.to_bytes();
+ assert_eq!(serialized.len(), 33);
+ let deser = LegacyPublicKey::from_slice(serialized).unwrap();
+ assert_eq!(deser, key);
+
+ let key = LegacyPublicKey::from_secp_uncompressed(key.to_inner());
+ let serialized = &key.to_bytes();
+ assert_eq!(serialized.len(), 65);
+ let deser = LegacyPublicKey::from_slice(serialized).unwrap();
+ assert_eq!(deser, key);
+ }
}
Why this scored 15/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.