Precompile Summary
| Property | Value |
|---|---|
| Address | 0x0000000000000000000000000000000000000003 (0x03) |
| Name | RIPEMD160 |
| Gas | 600 base + 120 per 32-byte word of input (rounded up: ceiling of input length ÷ 32, in words) |
| Input | Arbitrary-length byte data |
| Output | 32 bytes: 20-byte RIPEMD-160 digest right-aligned (12 zero bytes on the left) |
| Behavior | Computes the RIPEMD-160 hash of the input. The 160-bit digest occupies the low-order 20 bytes of the 32-byte return buffer. The precompile exists primarily for Bitcoin address derivation compatibility: P2PKH-style addresses use RIPEMD160(SHA256(pubkey)) (often written as HASH160). |
Threat Surface
The RIPEMD160 precompile is a narrow but security-sensitive primitive: it is the on-chain hook for HASH160-style workflows, especially cross-chain and Bitcoin-facing verification. Most Ethereum-native contracts prefer Keccak256 or SHA256; RIPEMD160 therefore sees far less routine use than hashing precompiles priced for common paths. That low volume increases the risk that integrators misunderstand the 32-byte return layout (hash in the last 20 bytes, not the first), or that they mis-compose RIPEMD160 with SHA256 when reproducing Bitcoin address checks.
Cryptographically, RIPEMD-160 is a 160-bit hash: classical birthday bounds imply roughly 80-bit collision resistance, weaker than 256-bit hashes used elsewhere on Ethereum. Progress in cryptanalysis (including partial-round results) does not by itself break typical preimage-style uses in bridges, but it matters for long-lived commitments or designs that assume “hash strength” comparable to SHA-256. Economically, gas pricing is roughly an order of magnitude higher per word than SHA256, reflecting historical DoS concerns and implementation cost; that pushes usage off-chain and concentrates risk in verification and parsing code paths rather than bulk hashing.
Smart Contract Threats
T1: Weakening Cryptographic Strength (Medium)
- RIPEMD-160 outputs 160 bits, yielding about 80-bit collision resistance under generic birthday attacks—materially weaker than SHA-256-class hashes.
- Academic analysis has advanced on reduced-round variants (e.g., collision attacks on a subset of rounds as of recent literature); full-round practical collisions are not assumed here, but the trajectory favors conservative assumptions for new designs.
- The algorithm has a smaller active research footprint than SHA-2/SHA-3 families, so unexpected issues may receive slower scrutiny than mainstream hashes.
- Long-lived on-chain commitments (multi-year anchors, timelocked proofs) that rely on collision resistance of RIPEMD160 alone may age poorly relative to 256-bit alternatives.
Why it matters: If a protocol treats RIPEMD160 as interchangeable with SHA-256 for security margins, it may accept weaker collision bounds and mis-estimate risk for protocols that bind to hashes for extended periods.
T2: Output Padding Confusion (Medium)
- The precompile returns 32 bytes with the 20-byte digest in the low-order (rightmost) bytes and 12 zero bytes in the high-order (leftmost) bytes.
- Developers may incorrectly read the first 20 bytes (all zero for typical digests that do not overflow into the high word) or apply
bytes20casts that take high-order bytes—whereas the digest lives in the low-order bytes. - Patterns familiar from
keccak256/sha256typed casts in Solidity do not transfer directly: e.g., mental models based on “first bytes of the hash” will silently yield wrong addresses or roots.
Why it matters: Incorrect slicing produces valid-looking 20-byte values that are not the RIPEMD160 digest, enabling authorization bypasses, wrong address derivations, or failed verifications that appear flaky rather than obviously wrong.
T3: Gas Cost Disproportionality (Low)
- Pricing is 600 + 120×words versus SHA256 at 60 + 12×words (same per-word granularity), so RIPEMD160 is much more expensive for large inputs.
- The disparity reflects intentional throttling of costly legacy hash paths and historical lessons from underpriced precompiles.
- High cost encourages off-chain hashing and on-chain verification of compact proofs, shifting complexity to boundary code where mistakes in format or composition are common.
Why it matters: Integrators may avoid the precompile and instead verify custom formats, or may batch work in ways that obscure bugs; the threat is less “DoS via RIPEMD160” than inconsistent verification across layers.
T4: Bitcoin Address Verification Edge Cases (Medium)
- Bitcoin P2PKH-style addresses depend on
RIPEMD160(SHA256(pubkey)), not RIPEMD160 alone. - Bridges and SPV-style verifiers must compose SHA256 (0x02) then RIPEMD160 (0x03) in the correct order and on the correct canonical encoding of the public key (compressed vs uncompressed, prefix bytes, witness programs for other address types).
- Any mistake in that pipeline yields plausible-looking 20-byte hashes that do not match a legitimate Bitcoin address for the same key.
Why it matters: Cross-chain systems often trust “this hash matches Bitcoin address X”; a subtle composition bug becomes a consensus or custody bug between chains, not a simple UI error.
Protocol-Level Threats
P1: Low Usage and Under-Tested Client Paths (Medium)
Relative to ECDSA recovery and SHA256, RIPEMD160 is invoked infrequently on many networks. Rare code paths in client implementations historically correlate with longer-lived defects (incorrect edge-case handling, platform-specific bugs). Low usage also means fewer battle-tested integration examples in the wild.
P2: Historical Gas Underpricing and Shanghai-Style DoS Lessons (Low)
2016 Shanghai attacks exploited underpriced expensive operations; subsequent repricing and precompile cost adjustments (including relatively high RIPEMD160 gas) were part of aligning metered cost with real computation. RIPEMD160’s pricing is partly a deliberate disincentive to lean on this path for bulk hashing on L1.
Edge Cases
| Case | Behavior / note |
|---|---|
| Empty input | Returns the RIPEMD160 of the empty string, left-padded to 32 bytes. Example full return value: 0x0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31 (digest 9c1185…d31 in the last 20 bytes). |
| Left padding | Always 12 high-order zero bytes before the 20-byte digest in the 32-byte return buffer. |
| Very large inputs | Gas scales linearly with the ceiling of (input length in bytes ÷ 32); large calldata payloads become prohibitively expensive, encouraging off-chain hashing. |
| Digest alignment | Treat the return value as a 32-byte blob; the digest is bytes 12..31 (zero-based), i.e. the last 20 bytes—not the first 20. |
| Composition with SHA256 | Order matters: Bitcoin HASH160 is RIPEMD160(SHA256(x)), not the reverse. |
Real-World Exploits
Exploit 1: No Major Public Exploit Targeting 0x03 Specifically
There is no widely cited incident where attackers compromised Ethereum consensus or a major bridge solely by abusing the RIPEMD160 precompile contract. Risk materializes more often as integration bugs (wrong bytes, wrong hash order) or client implementation defects (address those like any cryptographic primitive via audits and fuzzing). CVE-class bugs in specific clients remain a generic supply-chain concern rather than a unique RIPEMD160 narrative.
Bitcoin SPV bridges, tBTC, RenBTC, WBTC-related verification flows, and similar designs depend on correct HASH160 and preimage reasoning. Failures observed in the wild tend to be categorized as bridge logic bugs or oracle issues rather than “RIPEMD160 broken,” but the same verification surface is in scope when those systems touch the EVM precompile.
Attack Scenarios
A — Incorrect output parsing (wrong 20 bytes)
// ANTI-PATTERN: treats high-order bytes as the "hash"
// For 0x03, the digest is in the LOW 20 bytes; high bytes are often zero.
function wrongDigest(bytes memory input) internal view returns (bytes20 bad) {
bytes memory out = new bytes(32);
assembly {
let success := staticcall(gas(), 0x03, add(input, 0x20), mload(input), add(out, 0x20), 32)
if iszero(success) { revert(0, 0) }
}
// Incorrect: first 20 bytes are padding zeros for typical outputs
assembly {
bad := mload(add(out, 0x20))
}
}B — Bitcoin address verification bypass via hash composition errors
// ANTI-PATTERN: reverses HASH160 order (must be RIPEMD160(SHA256(pubkey)))
function wrongHash160(bytes memory pubkey) internal view returns (bytes memory) {
bytes memory h1 = new bytes(32);
bytes memory h2 = new bytes(32);
// First RIPEMD160(pubkey) — NOT Bitcoin HASH160
assembly {
let ok := staticcall(gas(), 0x03, add(pubkey, 0x20), mload(pubkey), add(h1, 0x20), 32)
if iszero(ok) { revert(0, 0) }
}
// Then SHA256(ripemd160_out) — reversed composition
assembly {
let ok := staticcall(gas(), 0x02, add(h1, 0x20), 32, add(h2, 0x20), 32)
if iszero(ok) { revert(0, 0) }
}
return h2;
}Mitigations
| Mitigation | Applies to |
|---|---|
Extract digest as output[12:32] (last 20 bytes of the 32-byte return) | T2, edge cases |
| Centralize HASH160 in one audited library function: SHA256(0x02) → RIPEMD160(0x03) on the correct pubkey encoding | T4 |
| Prefer stronger hashes (e.g., SHA-256, Keccak256) for new Ethereum-native commitments unless Bitcoin compatibility forces RIPEMD160 | T1 |
| Property tests / vectors for empty input, max-length samples, and known Bitcoin test vectors | T2, T4, P1 |
| Fuzz client and Solidity boundaries for precompile calls | P1 |
| Document security margin (160-bit hash, ~80-bit collision strength) for reviewers | T1 |
Compiler / EIP-Based Protections
- No Solidity built-in wraps 0x03; developers must use
staticcallor vetted libraries—there is no compiler-enforced correct slicing. - EIPs define precompile addresses and semantics; gas costs are protocol-level throttles (see T3, P2), not proof of correctness.
- Best practice: use OpenZeppelin or other maintained
hash160-style helpers where available, and never copy-paste byte-slice logic without tests against known vectors.
Severity Summary
| ID | Title | Severity |
|---|---|---|
| T1 | Weakening cryptographic strength | Medium |
| T2 | Output padding confusion | Medium |
| T3 | Gas cost disproportionality | Low |
| T4 | Bitcoin address verification composition | Medium |
| P1 | Low usage / under-tested client paths | Medium |
| P2 | Historical gas underpricing context | Low |
Related Precompiles
| Address | Name | Relationship |
|---|---|---|
| 0x01 | ECRECOVER | Used with pubkey recovery flows that may feed into HASH160-style checks |
| 0x02 | SHA256 | Required first step for Bitcoin HASH160: RIPEMD160(SHA256(pubkey)) |
| 0x04 | IDENTITY | Passthrough precompile; sometimes used in generic copying patterns near other precompile plumbing |