Keccak256 Optimizer Result Reuse
Overview
The Solidity optimizer in versions before 0.8.3 treated two keccak256(ptr, L1) and keccak256(ptr, L2) calls as equivalent when L1 and L2 rounded up to the same 32-byte word count. A later hash over a different byte length could be replaced with the earlier result, producing the wrong digest. The bug triggers only inside inline assembly where the two hashes share a memory pointer and have no intervening jumps or calls — the window the optimizer uses for common-subexpression elimination. It was found through differential fuzzing of the legacy and Yul pipelines and fixed in 0.8.3.
Every version prior to 0.8.3 is affected when the optimizer is enabled. Sigvex reports this from recovered compiler metadata together with a bytecode signal. A single hash cannot be optimized into reuse of a prior result, so the contract must contain at least two KECCAK256 operations before a finding is considered.
The two-hash floor alone is near-universal on older mapping-heavy contracts, so it is too weak on its own to justify a strong finding. When Sigvex can structurally confirm the precise pattern — a pair of hashes sharing a memory offset, with constant length operands that differ yet round to the same word count and no control transfer between them — the finding fires at full confidence. When only the weak count floor is met, the finding is emitted at a low confidence to flag the affected version without overstating the evidence.
Why This Is an Issue
A reused hash returns a digest for the wrong input length. Where keccak256 backs storage-slot derivation, mapping keys, commitment schemes, or signature pre-images, a wrong digest can route a write to the wrong slot, accept a forged commitment, or break a uniqueness assumption. Because the substitution happens silently in optimized output, source review of the contract will not reveal it.
How to Resolve
Recompile with a fixed compiler and redeploy.
// Affected: optimizer-enabled build on a compiler before 0.8.3
pragma solidity 0.8.2;
contract Hasher {
function digestTwice(bytes memory input) external pure returns (bytes32 a, bytes32 b) {
assembly {
a := keccak256(add(input, 0x20), 20) // L1 = 20 -> 1 word
b := keccak256(add(input, 0x20), 32) // L2 = 32 -> 1 word; may reuse a
}
}
}
// Fixed: compile with 0.8.3 or later
pragma solidity 0.8.3;
For a manual audit on an unfixable build, search inline assembly for a keccak256(ptr, L1) followed (with no jump or call in between) by keccak256(ptr, L2) where L1 and L2 differ but round to the same 32-byte multiple.
Detection Methodology
The detector first checks recovered compiler metadata: the version must be below the fix, and a finding is suppressed when the optimizer is known to be disabled. It then scans the recovered instructions for at least two hash operations. For full confidence, it walks each function’s basic blocks looking for two hash operations in the same block that share a memory-offset operand, whose length operands both resolve through constant definitions to different values that round up to the same 32-byte word count, with no jump or call between them. Any control transfer or external call resets the window. Length operands that do not resolve to a constant never satisfy the pattern, which keeps unresolved sizes from producing a strong finding. All operand resolution works from recovered constants in the instruction stream; no source is consulted.
Sample Sigvex Output
{
"detector_id": "solc-2021-03-keccak-optimizer",
"severity": "medium",
"confidence": 0.8,
"description": "solc-2021-03-keccak-optimizer: Contract was compiled with Solidity 0.8.2, which is in the affected range. Precise same-pointer / rounded-length keccak reuse pattern structurally confirmed.",
"location": {
"function": "digestTwice",
"offset": 308
}
}
Limitations
False positives arise when the version is affected but the structural pattern cannot be confirmed: such findings are deliberately emitted at low confidence because the contract may simply hash twice for unrelated reasons. Confidence is further reduced when compiler metadata is unreliable or the optimizer setting is unknown. False negatives occur when the offending hashes are split across basic blocks, when their length operands are not recoverable constants, or when compiler metadata is missing.
Related Detectors
- Outdated Compiler — general detection of compiler versions with known bugs.
- Hash Collision — hash-related correctness and collision issues at the contract level.