Merge rust-bitcoin/rust-bitcoin#4050: Validate compressed WIF keys

00cd247bc4 Validate compressed WIF keys (ndungudedan)

Pull request description:

  For private WIF keys corresponding to a compressed address, the last byte of the key needs to be 0x01, but the API doesn't enforce this when using PrivateKey::from_wif(). So, invalid keys can be accepted.

  Thus we check if the last byte is equivalent to 0x01 if the key's length is 34 (which indicates it's
  compressed).

  resolves #3773

ACKs for top commit:
  Kixunil:
    ACK 00cd247bc4
  tcharding:
    ACK 00cd247bc4
  apoelstra:
    ACK 00cd247bc49451fe8c2cbf537ed042b0bb5c3340; successfully ran local tests

Tree-SHA512: 8d2cfc13f713dd4ae47d4f55d87f783167b5b3755f4f902cd009ebe0c5da19b0d3bf31393570a804e22010685bf97381922d21c53a71a9c144aa53ff7aac2167
This commit is contained in:
merge-script 2025-02-14 18:00:11 +00:00
commit 1a610cb36f
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 43 additions and 1 deletions

View File

@ -497,7 +497,12 @@ impl PrivateKey {
let compressed = match data.len() { let compressed = match data.len() {
33 => false, 33 => false,
34 => true, 34 => {
if data[33] != 1 {
return Err(InvalidWifCompressionFlagError{ invalid: data[33] }.into());
}
true
},
length => { length => {
return Err(InvalidBase58PayloadLengthError { length }.into()); return Err(InvalidBase58PayloadLengthError { length }.into());
} }
@ -963,6 +968,8 @@ pub enum FromWifError {
InvalidAddressVersion(InvalidAddressVersionError), InvalidAddressVersion(InvalidAddressVersionError),
/// A secp256k1 error. /// A secp256k1 error.
Secp256k1(secp256k1::Error), Secp256k1(secp256k1::Error),
/// Invalid WIF compression flag.
InvalidWifCompressionFlag(InvalidWifCompressionFlagError),
} }
impl From<Infallible> for FromWifError { impl From<Infallible> for FromWifError {
@ -980,6 +987,8 @@ impl fmt::Display for FromWifError {
InvalidAddressVersion(ref e) => InvalidAddressVersion(ref e) =>
write_err!(f, "decoded base58 data contained an invalid address version btye"; e), write_err!(f, "decoded base58 data contained an invalid address version btye"; e),
Secp256k1(ref e) => write_err!(f, "private key validation failed"; e), Secp256k1(ref e) => write_err!(f, "private key validation failed"; e),
InvalidWifCompressionFlag(ref e) =>
write_err!(f, "invalid WIF compression flag";e),
} }
} }
} }
@ -994,6 +1003,7 @@ impl std::error::Error for FromWifError {
InvalidBase58PayloadLength(ref e) => Some(e), InvalidBase58PayloadLength(ref e) => Some(e),
InvalidAddressVersion(ref e) => Some(e), InvalidAddressVersion(ref e) => Some(e),
Secp256k1(ref e) => Some(e), Secp256k1(ref e) => Some(e),
InvalidWifCompressionFlag(ref e) => Some(e),
} }
} }
} }
@ -1016,6 +1026,12 @@ impl From<InvalidAddressVersionError> for FromWifError {
fn from(e: InvalidAddressVersionError) -> FromWifError { Self::InvalidAddressVersion(e) } fn from(e: InvalidAddressVersionError) -> FromWifError { Self::InvalidAddressVersion(e) }
} }
impl From<InvalidWifCompressionFlagError> for FromWifError {
fn from(e: InvalidWifCompressionFlagError) -> FromWifError {
Self::InvalidWifCompressionFlag(e)
}
}
/// Error returned while constructing public key from string. /// Error returned while constructing public key from string.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParsePublicKeyError { pub enum ParsePublicKeyError {
@ -1161,6 +1177,27 @@ impl fmt::Display for InvalidAddressVersionError {
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl std::error::Error for InvalidAddressVersionError {} impl std::error::Error for InvalidAddressVersionError {}
/// Invalid compression flag for a WIF key
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidWifCompressionFlagError{
/// The invalid compression flag.
pub(crate) invalid: u8,
}
impl InvalidWifCompressionFlagError {
/// Returns the invalid compression flag.
pub fn invalid_compression_flag(&self) -> u8 { self.invalid }
}
impl fmt::Display for InvalidWifCompressionFlagError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid WIF compression flag. Expected a 0x01 byte at the end of the key but found: {}", self.invalid)
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidWifCompressionFlagError {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -1168,6 +1205,11 @@ mod tests {
#[test] #[test]
fn key_derivation() { fn key_derivation() {
// mainnet compressed WIF with invalid compression flag.
let sk =
PrivateKey::from_wif("L2x4uC2YgfFWZm9tF4pjDnVR6nJkheizFhEr2KvDNnTEmEqVzPJY");
assert!(matches!(sk, Err(FromWifError::InvalidWifCompressionFlag(InvalidWifCompressionFlagError { invalid: 49 }))));
// testnet compressed // testnet compressed
let sk = let sk =
PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap(); PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();