fix: validate max_fee_per_gas zero prefix in EIP-1559
What changed, and why it matters
This commit fixes a typo-like bug in the BitBox02 hardware wallet's Ethereum transaction signing code. The firmware was supposed to reject a 'max_fee_per_gas' value that has an unnecessary leading zero byte, but it was accidentally checking 'gas_limit' instead. Leading-zero values can cause the device and a connected computer to compute different transaction hashes, which could let an attacker trick the user into signing one transaction while the computer broadcasts a different one. The fix corrects the field check and adds tests for both fee fields.
Users should update BitBox02 firmware once a release containing this commit is available. Until then, be cautious when signing EIP-1559 transactions and verify the transaction details shown by the host wallet match the device screen. Developers should review other RLP numeric field validations for similar copy-paste field-name errors.
Security signals we found
Incorrect field validated in EIP-1559 transaction parser
Leading-zero canonicalization check bypassed for max_fee_per_gas
Potential transaction-hash mismatch between host and device
Unit tests added for both fee-field leading-zero rejections
Evidence from the diff
In the EIP-1559 signing path, the firmware validates RLP-encoded numeric fields by rejecting values with a leading 0x00 byte. The original code intended to check max_fee_per_gas but instead checked gas_limit. Because the transaction hash displayed/confirmed on the device is computed from the RLP-serialized fields, a malicious host could supply a max_fee_per_gas with a leading zero and produce a different hash on the device than what the host later broadcasts. This is a canonicalization/signature-malleability-style issue. The patch changes the guard to max_fee_per_gas and adds unit tests covering both max_fee_per_gas and max_priority_fee_per_gas leading-zero cases.
Changed components
src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rsBitBox02 Ethereum EIP-1559 transaction signingInspect captured patch +27 / −1
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
index b9b2590..3babf0c 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
@@ -346,7 +346,7 @@ pub async fn _process(
if let [0, ..] = &eip1559.max_priority_fee_per_gas[..] {
return Err(Error::InvalidInput);
}
- if let [0, ..] = &eip1559.gas_limit[..] {
+ if let [0, ..] = &eip1559.max_fee_per_gas[..] {
return Err(Error::InvalidInput);
}
if eip1559.max_priority_fee_per_gas.len() > 16 || eip1559.max_fee_per_gas.len() > 16 {
@@ -1215,6 +1215,32 @@ mod tests {
Err(Error::InvalidInput)
);
}
+
+ {
+ // max_fee_per_gas with leading zero byte
+ let mut invalid_request = valid_request.clone();
+ invalid_request.max_fee_per_gas = b"\x00\x01\x65\xa0\xbc\x00".to_vec();
+ assert_eq!(
+ block_on(process(
+ &mut TestingHal::new(),
+ &Transaction::Eip1559(&invalid_request)
+ )),
+ Err(Error::InvalidInput)
+ );
+ }
+
+ {
+ // max_priority_fee_per_gas with leading zero byte
+ let mut invalid_request = valid_request.clone();
+ invalid_request.max_priority_fee_per_gas = b"\x00\x3b\x9a\xca\x00".to_vec();
+ assert_eq!(
+ block_on(process(
+ &mut TestingHal::new(),
+ &Transaction::Eip1559(&invalid_request)
+ )),
+ Err(Error::InvalidInput)
+ );
+ }
}
/// Unknown chain ID (network params not hardcoded in in the firmware).
Why this scored 49/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.