Skip to content

Commit 1f74571

Browse files
committed
Merge rust-bitcoin#4230: Fix BIP32 validation for private keys and master key constraints (rust-bitcoin#4195)
8f74b82 Add validation for private key format and master key constraints (Erick Cestari) Pull request description: This PR addresses issue rust-bitcoin#4195 by adding proper validation when decoding extended private keys: ### Changes - Add validation to ensure byte 45 is zero as required by BIP-32 specification for private keys - For master keys (depth=0), add validation to ensure parent fingerprint is zero - For master keys (depth=0), add validation to ensure child number is zero - Add corresponding error types to handle these validation failures - Add unit tests to verify each validation rule ### Validation Rationale These checks improve security by rejecting malformed extended keys that could potentially lead to unexpected behavior. As noted in the issue discussion, these validations are explicitly required by the BIP-32 specification. ### Testing Added three new unit tests to verify each validation rule: - test_reject_xpriv_with_non_zero_byte_at_index_45 - test_reject_xpriv_with_zero_depth_and_non_zero_index - test_reject_xpriv_with_zero_depth_and_non_zero_parent_fingerprint Fixes rust-bitcoin#4195 ACKs for top commit: jrakibi: ACK 8f74b82 tcharding: ACK 8f74b82 apoelstra: ACK 8f74b82; successfully ran local tests Tree-SHA512: 6a013e4917f83cfd7e39a2a18f7491853d791ab1d981a99eeea6204e1dab723fed7a168ff2a89e8850d512c3c381bfa1afef7fa32e5a0d246d949a46b01a3023
2 parents b18aa6f + 8f74b82 commit 1f74571

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

bitcoin/src/bip32.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,12 @@ pub enum Error {
518518
InvalidPublicKeyHexLength(usize),
519519
/// Base58 decoded data was an invalid length.
520520
InvalidBase58PayloadLength(InvalidBase58PayloadLengthError),
521+
/// Invalid private key prefix (byte 45 must be 0)
522+
InvalidPrivateKeyPrefix,
523+
/// Non-zero parent fingerprint for a master key (depth 0)
524+
NonZeroParentFingerprintForMasterKey,
525+
/// Non-zero child number for a master key (depth 0)
526+
NonZeroChildNumberForMasterKey,
521527
}
522528

523529
impl From<Infallible> for Error {
@@ -544,6 +550,11 @@ impl fmt::Display for Error {
544550
InvalidPublicKeyHexLength(got) =>
545551
write!(f, "PublicKey hex should be 66 or 130 digits long, got: {}", got),
546552
InvalidBase58PayloadLength(ref e) => write_err!(f, "base58 payload"; e),
553+
InvalidPrivateKeyPrefix =>
554+
f.write_str("invalid private key prefix, byte 45 must be 0 as required by BIP-32"),
555+
NonZeroParentFingerprintForMasterKey =>
556+
f.write_str("non-zero parent fingerprint in master key"),
557+
NonZeroChildNumberForMasterKey => f.write_str("non-zero child number in master key"),
547558
}
548559
}
549560
}
@@ -565,6 +576,9 @@ impl std::error::Error for Error {
565576
| UnknownVersion(_)
566577
| WrongExtendedKeyLength(_)
567578
| InvalidPublicKeyHexLength(_) => None,
579+
InvalidPrivateKeyPrefix => None,
580+
NonZeroParentFingerprintForMasterKey => None,
581+
NonZeroChildNumberForMasterKey => None,
568582
}
569583
}
570584
}
@@ -699,6 +713,23 @@ impl Xpriv {
699713
return Err(Error::UnknownVersion([b0, b1, b2, b3]));
700714
};
701715

716+
// Check that byte 45 is 0 as required by BIP-32 for private keys
717+
if data[45] != 0 {
718+
return Err(Error::InvalidPrivateKeyPrefix);
719+
}
720+
721+
// For master keys (depth=0), both parent fingerprint and child number must be zero
722+
let depth = data[4];
723+
if depth == 0 {
724+
if !data[5..9].iter().all(|&b| b == 0) {
725+
return Err(Error::NonZeroParentFingerprintForMasterKey);
726+
}
727+
728+
if !data[9..13].iter().all(|&b| b == 0) {
729+
return Err(Error::NonZeroChildNumberForMasterKey);
730+
}
731+
}
732+
702733
Ok(Xpriv {
703734
network,
704735
depth: data[4],
@@ -1235,6 +1266,44 @@ mod tests {
12351266
"xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y");
12361267
}
12371268

1269+
#[test]
1270+
fn test_reject_xpriv_with_non_zero_byte_at_index_45() {
1271+
let mut xpriv = base58::decode_check("xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9").unwrap();
1272+
1273+
// Modify byte at index 45 to be non-zero (e.g., 1)
1274+
xpriv[45] = 1;
1275+
1276+
let result = Xpriv::decode(&xpriv);
1277+
assert!(result.is_err());
1278+
1279+
match result {
1280+
Err(Error::InvalidPrivateKeyPrefix) => {}
1281+
_ => panic!("Expected InvalidPrivateKeyPrefix error, got {:?}", result),
1282+
}
1283+
}
1284+
1285+
#[test]
1286+
fn test_reject_xpriv_with_zero_depth_and_non_zero_index() {
1287+
let result = "xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN".parse::<Xpriv>();
1288+
assert!(result.is_err());
1289+
1290+
match result {
1291+
Err(Error::NonZeroChildNumberForMasterKey) => {}
1292+
_ => panic!("Expected NonZeroChildNumberForMasterKey error, got {:?}", result),
1293+
}
1294+
}
1295+
1296+
#[test]
1297+
fn test_reject_xpriv_with_zero_depth_and_non_zero_parent_fingerprint() {
1298+
let result = "xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv".parse::<Xpriv>();
1299+
assert!(result.is_err());
1300+
1301+
match result {
1302+
Err(Error::NonZeroParentFingerprintForMasterKey) => {}
1303+
_ => panic!("Expected NonZeroParentFingerprintForMasterKey error, got {:?}", result),
1304+
}
1305+
}
1306+
12381307
#[test]
12391308
#[cfg(feature = "serde")]
12401309
pub fn encode_decode_childnumber() {

0 commit comments

Comments
 (0)