Return 0 on Target::from_compact overflow
What changed, and why it matters
This commit fixes a bug in how rust-bitcoin converts Bitcoin 'compact' difficulty targets into full numeric Target values. Previously, if the compact value encoded a number too large to fit in a valid Target, the code would silently produce a truncated/wrapped result instead of treating it as invalid. The fix makes overflow behave like an invalid negative mantissa: return zero. This aligns the library with Bitcoin Core's consensus behavior, reducing the risk of consensus divergence or incorrect difficulty calculations.
Review callers of Target::from_compact to ensure they handle Target::ZERO as an error/invalid case. Backport this fix to maintained release branches. Consider adding unit tests covering the new overflow branches and comparing outputs against Bitcoin Core's SetCompact for known edge-case nBits values. No immediate emergency response is indicated unless this function is used directly in consensus validation without additional checks.
Security signals we found
Consensus-critical code path modified
Overflow/wraparound in cryptographic/numeric conversion
Alignment with Bitcoin Core behavior (SetCompact overflow flag)
Silent truncation could lead to incorrect target/difficulty values
No explicit CVE or vendor security advisory in commit
Evidence from the diff
Target::from_compact in units/src/pow.rs decodes Bitcoin nBits-style compact representations. Before the patch, only a negative signed mantissa (mant > 0x7F_FFFF) caused the function to return Target::ZERO. Overflow cases—where mantissa * 2^(8*(expt-3)) exceeds the 256-bit Target space—were not detected, so the U256 left shift would silently wrap/truncate. The patch adds overflow detection for exponent/mantissa combinations that would exceed 32 bytes and treats them the same as negative mantissas, returning Target::ZERO. This matches Bitcoin Core’s SetCompact overflow semantics.
Changed components
units/src/pow.rsTarget::from_compactCompact target decoding used in difficulty retargeting and block validationInspect captured patch +7 / −3
diff --git a/units/src/pow.rs b/units/src/pow.rs
index 46b99d14..f5d79c31 100644
--- a/units/src/pow.rs
+++ b/units/src/pow.rs
@@ -178,17 +178,21 @@ impl Target {
// OpenSSL, which satoshi put into consensus code, so we're stuck
// with it. The exponent needs to have 3 subtracted from it, hence
// this goofy decoding code. 3 is due to 3 bytes in the mantissa.
+ let unshifted_expt = bits >> 24;
let (mant, expt) = {
- let unshifted_expt = bits >> 24;
if unshifted_expt <= 3 {
((bits & 0xFF_FFFF) >> (8 * (3 - unshifted_expt as usize)), 0)
} else {
(bits & 0xFF_FFFF, 8 * ((bits >> 24) - 3))
}
};
+ let overflow = mant != 0
+ && (unshifted_expt > 34
+ || (mant > 0xFF && unshifted_expt > 33)
+ || (mant > 0xFFFF && unshifted_expt > 32));
- // The mantissa is signed but may not be negative.
- if mant > 0x7F_FFFF {
+ // The mantissa is signed but may not be negative or overflow.
+ if mant > 0x7F_FFFF || overflow {
Self::ZERO
} else {
Self(U256::from(mant) << expt)
Why this scored 59/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.