Skip to main content

RIPEMD160

RIPEMD160 is a cryptographic one-way hash function producing a 20-byte digest, designed as an alternative to SHA-1.

Ethereum Context

Mainnet algorithm - Available as EVM precompile at address 0x03 for Bitcoin address compatibility. Rarely used in practice but required for Bitcoin-Ethereum bridges.

Overview

RIPEMD160 (RACE Integrity Primitives Evaluation Message Digest 160-bit) is a cryptographic hash function that produces a 20-byte (160-bit) digest from arbitrary-length input data. Developed in 1996 as an alternative to MD5 and SHA-1, RIPEMD160 is part of the RIPEMD family designed by the COSIC research group. While largely superseded by SHA-256 and SHA-3 for general cryptography, RIPEMD160 remains important in blockchain technology:
  • Bitcoin addresses: Combined with SHA256 for address generation (Base58Check encoding)
  • Ethereum precompile: Address 0x03 for Bitcoin-Ethereum interoperability
  • Address derivation: Creates shorter address representations (20 bytes vs 32 bytes)
  • Legacy compatibility: Maintained for Bitcoin protocol compatibility
The shorter 160-bit output (compared to 256-bit SHA256) provides compact addresses while maintaining sufficient security for address collision resistance (~80-bit security level).

Implementations

  • Pure Zig: Custom RIPEMD160 implementation following Bitcoin Core reference
    • WARNING: Zig implementation is UNAUDITED custom crypto code
    • Uses constant-time operations to resist timing attacks
  • TypeScript: Uses @noble/hashes legacy module for JavaScript environments
  • WASM: Available via ripemd160.wasm.ts for browser environments
  • C FFI fallback: For platforms without native Zig support
RIPEMD160 is primarily maintained for Bitcoin compatibility. For new applications requiring 160-bit hashes, consider Blake2b with 20-byte output, which offers better performance and security margins.

Quick Start

import * as RIPEMD160 from 'voltaire/crypto/RIPEMD160';

// Hash string data
const message = "Hello, Bitcoin!";
const hash = RIPEMD160.hashString(message);
// Uint8Array(20) [RIPEMD160 hash]

// Hash bytes
const data = new Uint8Array([1, 2, 3, 4, 5]);
const bytesHash = RIPEMD160.hash(data);
// Uint8Array(20)

// Constructor pattern - auto-detects type
const autoHash = RIPEMD160.from("hello");
// Uint8Array(20)
View Example: hash-string.ts

API Reference

RIPEMD160.hash(data: Uint8Array | string): Uint8Array

Compute RIPEMD160 hash of byte array or string. Accepts both Uint8Array and string inputs. Strings are UTF-8 encoded before hashing. Parameters:
  • data: Input data to hash (Uint8Array or string)
Returns: Uint8Array - 20-byte hash Example:
import * as RIPEMD160 from 'voltaire/crypto/RIPEMD160';

// Hash bytes
const hash1 = RIPEMD160.hash(new Uint8Array([1, 2, 3]));
console.log(hash1.length); // 20

// Hash string
const hash2 = RIPEMD160.hash('hello');
console.log(hash2.length); // 20
View Example: hash-bytes.ts

RIPEMD160.hashString(str: string): Uint8Array

Compute RIPEMD160 hash of UTF-8 string. Parameters:
  • str: Input string
Returns: Uint8Array - 20-byte hash Example:
const hash = RIPEMD160.hashString('message digest');
console.log(hash.length); // 20
View Example: hash-string.ts

RIPEMD160.hashHex(hex: string): Uint8Array

Compute RIPEMD160 hash of hex string. Parameters:
  • hex: Hex string (with or without 0x prefix)
Returns: Uint8Array - 20-byte hash Example:
const hash = RIPEMD160.hashHex("0xdeadbeef");
console.log(hash.length); // 20
View Example: hash-hex.ts

RIPEMD160.from(input: Uint8Array | string): Uint8Array

Constructor pattern - auto-detects input type and hashes accordingly. Parameters:
  • input: Data to hash (Uint8Array or string)
Returns: Uint8Array - 20-byte hash Example:
const hash1 = RIPEMD160.from("hello");
const hash2 = RIPEMD160.from(new Uint8Array([1, 2, 3]));
View Example: constructor-pattern.ts

Type Definition

export type Ripemd160Hash = Uint8Array & {
  readonly [brand]: "Ripemd160Hash";
};

Constants

RIPEMD160.SIZE  // 20 - Output size in bytes (160 bits)

Test Vectors

Official RIPEMD160 test vectors:
import * as RIPEMD160 from 'voltaire/crypto/RIPEMD160';

// Empty string
RIPEMD160.hashString("")
// Uint8Array(20) [
//   0x9c, 0x11, 0x85, 0xa5, 0xc5, 0xe9, 0xfc, 0x54,
//   0x61, 0x28, 0x08, 0x97, 0x7e, 0xe8, 0xf5, 0x48,
//   0xb2, 0x25, 0x8d, 0x31
// ]

// "a"
RIPEMD160.hashString("a")
// Uint8Array(20) [
//   0x0b, 0xdc, 0x9d, 0x2d, 0x25, 0x6b, 0x3e, 0xe9,
//   0xda, 0xae, 0x34, 0x7b, 0xe6, 0xf4, 0xdc, 0x83,
//   0x5a, 0x46, 0x7f, 0xfe
// ]

// "abc"
RIPEMD160.hashString("abc")
// Uint8Array(20) [
//   0x8e, 0xb2, 0x08, 0xf7, 0xe0, 0x5d, 0x98, 0x7a,
//   0x9b, 0x04, 0x4a, 0x8e, 0x98, 0xc6, 0xb0, 0x87,
//   0xf1, 0x5a, 0x0b, 0xfc
// ]

// "message digest"
RIPEMD160.hashString("message digest")
// Uint8Array(20) [
//   0x5d, 0x06, 0x89, 0xef, 0x49, 0xd2, 0xfa, 0xe5,
//   0x72, 0xb8, 0x81, 0xb1, 0x23, 0xa8, 0x5f, 0xfa,
//   0x21, 0x59, 0x5f, 0x36
// ]

// "abcdefghijklmnopqrstuvwxyz"
RIPEMD160.hashString("abcdefghijklmnopqrstuvwxyz")
// Uint8Array(20) [
//   0xf7, 0x1c, 0x27, 0x10, 0x9c, 0x69, 0x2c, 0x1b,
//   0x56, 0xbb, 0xdc, 0xeb, 0x5b, 0x9d, 0x28, 0x65,
//   0xb3, 0x70, 0x8d, 0xbc
// ]

// "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
RIPEMD160.hashString("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
// Uint8Array(20) [
//   0x12, 0xa0, 0x53, 0x38, 0x4a, 0x9c, 0x0c, 0x88,
//   0xe4, 0x05, 0xa0, 0x6c, 0x27, 0xdc, 0xf4, 0x9a,
//   0xda, 0x62, 0xeb, 0x2b
// ]

// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
RIPEMD160.hashString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// Uint8Array(20) [
//   0xb0, 0xe2, 0x0b, 0x6e, 0x31, 0x16, 0x64, 0x02,
//   0x86, 0xed, 0x3a, 0x87, 0xa5, 0x71, 0x30, 0x79,
//   0xb2, 0x1f, 0x51, 0x89
// ]

// Eight repetitions of "1234567890"
RIPEMD160.hashString("12345678901234567890123456789012345678901234567890123456789012345678901234567890")
// Uint8Array(20) [
//   0x9b, 0x75, 0x2e, 0x45, 0x57, 0x3d, 0x4b, 0x39,
//   0xf4, 0xdb, 0xd3, 0x32, 0x3c, 0xab, 0x82, 0xbf,
//   0x63, 0x32, 0x6b, 0xfb
// ]
View Example: hash-string.ts

Security Considerations

Collision Resistance

RIPEMD160 provides ~80-bit security against collision attacks due to its 160-bit output size. This is considered adequate for Bitcoin addresses where collision resistance prevents address conflicts.

Preimage Resistance

Finding a specific input that produces a given RIPEMD160 hash requires ~2^160 operations, which remains computationally infeasible.

Birthday Paradox

The 160-bit output means collisions become probable after ~2^80 random inputs (birthday bound). This is acceptable for address generation where inputs are not randomly chosen, but insufficient for applications requiring strong collision resistance.

Bitcoin Context

In Bitcoin, RIPEMD160 is never used alone for security-critical operations:
  • Always combined with SHA256 (double hashing)
  • Address collisions require breaking both SHA256 and RIPEMD160
  • Compact 20-byte addresses reduce blockchain storage

Known Vulnerabilities

  • No practical collision or preimage attacks exist as of 2025
  • RIPEMD128 (128-bit variant) has theoretical weaknesses, but RIPEMD160 remains secure
  • Primarily replaced by SHA-256/SHA-3 for new applications due to larger security margin
RIPEMD160’s 160-bit output provides only 80-bit collision security. For new applications, use SHA256 (256-bit) or Blake2b which offer stronger security margins and better performance.

Performance

Implementation

  • TypeScript: Uses @noble/hashes pure TypeScript implementation from legacy.js
  • Zig/Native: Custom RIPEMD160 implementation following Bitcoin Core reference
    • WARNING: Zig implementation is UNAUDITED custom crypto code
    • Uses constant-time operations to resist timing attacks
    • Pure software implementation (no hardware acceleration available)
  • WASM: Available via ripemd160.wasm.ts for browser environments

Benchmarks

Typical performance (varies by platform):
  • Native (Zig): ~150-250 MB/s
  • WASM: ~80-150 MB/s
  • Pure JS: ~50-100 MB/s

Performance vs Other Hashes

Algorithm          Software Speed    Hardware Accel
---------          --------------    --------------
RIPEMD160          ~200 MB/s         N/A (no accel)
SHA256             ~500 MB/s         ~2500 MB/s
Keccak256          ~350 MB/s         N/A
Blake2b            ~700 MB/s         N/A
Key insight: RIPEMD160 is slower than modern alternatives and lacks hardware acceleration. Only use for Bitcoin compatibility. For new applications, Blake2b offers 3x better performance with stronger security margins.

Implementation Details

TypeScript Implementation

Uses @noble/hashes legacy module:
import { ripemd160 } from "@noble/hashes/legacy.js";

export function hash(data: Uint8Array | string): Uint8Array {
  if (typeof data === "string") {
    const encoder = new TextEncoder();
    return ripemd160(encoder.encode(data));
  }
  return ripemd160(data);
}

WASM

Available via ripemd160.wasm.ts for browser environments. Compiled from Zig with wasm32-wasi target.
import { Ripemd160Wasm } from '@tevm/voltaire/crypto/ripemd160.wasm';
await Ripemd160Wasm.load();
const hash = Ripemd160Wasm.hash(data);

Use Cases

Bitcoin P2PKH Address

Pay-to-PubKey-Hash (most common Bitcoin address):
import * as SHA256 from 'voltaire/crypto/SHA256';
import * as RIPEMD160 from 'voltaire/crypto/RIPEMD160';

function createPubKeyHash(publicKey: Uint8Array): Uint8Array {
  // Bitcoin uses SHA256 followed by RIPEMD160
  const sha256Hash = SHA256.hash(publicKey);
  const pubKeyHash = RIPEMD160.hash(sha256Hash);
  return pubKeyHash; // 20 bytes
}

// Then add version byte (0x00 for mainnet) and checksum for Base58Check
View Example: bitcoin-address.ts

Bitcoin P2SH Address

Pay-to-Script-Hash addresses:
import * as SHA256 from 'voltaire/crypto/SHA256';
import * as RIPEMD160 from 'voltaire/crypto/RIPEMD160';

function createScriptHash(redeemScript: Uint8Array): Uint8Array {
  const sha256Hash = SHA256.hash(redeemScript);
  const scriptHash = RIPEMD160.hash(sha256Hash);
  return scriptHash; // 20 bytes
}
// Version byte 0x05 for P2SH mainnet addresses

Why Bitcoin Uses Both SHA256 and RIPEMD160

  1. Redundancy: If one algorithm is broken, the other provides backup security
  2. Compact addresses: RIPEMD160’s 20-byte output reduces address size
  3. Historical: Design decision made in 2009 when both were considered secure
  4. No single point of failure: Requires breaking both algorithms for address collision
  • New cryptocurrencies: Use SHA256, Keccak256, or Blake2b instead
  • General hashing: SHA256 provides better security margins
  • Password hashing: Use proper password hash functions (Argon2, bcrypt, scrypt)
  • File integrity: SHA256 is more widely supported and faster on modern hardware