# Report: 48-bit truncated RIPEMD-160 collision (λ=24)

## Answer

```json
{"algo":"ripemd160","lambda":24,"inputA":"idmd-13458001","inputB":"idmd-21777228"}
```

| Input (UTF-8, no trailing newline) | RIPEMD-160 digest |
|---|---|
| `idmd-13458001` | `9ff5cb7e7844`b4e20774f357c3ce88d721e7832f |
| `idmd-21777228` | `9ff5cb7e7844`b94fb8896a518dd50687714b3a45 |

The first 48 bits (12 hex digits, MSB first) are `9ff5cb7e7844` for both. The inputs are different.

## Evidence (facts, reproducible)

1. **Search.** A Node.js (`crypto.createHash('ripemd160')`, OpenSSL-backed) birthday search hashed the
   strings `idmd-0`, `idmd-1`, … and stored each 48-bit prefix in a hash table. The first repeated
   prefix appeared at index 21,777,228, matching index 13,458,001. That took 21,777,229 evaluations
   (about 2^24.4) and about 66 s of wall time.
2. **Independent check.** I recomputed both digests with the OpenSSL CLI:
   ```
   $ printf %s idmd-13458001 | openssl dgst -ripemd160
   RIPEMD-160(stdin)= 9ff5cb7e7844b4e20774f357c3ce88d721e7832f
   $ printf %s idmd-21777228 | openssl dgst -ripemd160
   RIPEMD-160(stdin)= 9ff5cb7e7844b94fb8896a518dd50687714b3a45
   ```
   The OpenSSL output matches the Node.js search output exactly.

## Inferences

- The work done is consistent with the birthday bound. For a 48-bit output, about
  sqrt(π/2 · 2^48) ≈ 2.1·10^7 evaluations are expected, and this search used 2.18·10^7.
- The collision is generic. It comes only from truncation and does not exploit any weakness in
  RIPEMD-160's structure.

## Uncertainty / unanswered questions

- I assume the verifier treats inputs that lack a `0x` prefix as UTF-8 bytes, with no added
  newline or normalization, as the task format states. If it hashed the strings any other way,
  the digests would be different.
- I did not run the "SIMD" verifier myself. The only checks were with Node.js crypto and OpenSSL 
  (both use OpenSSL's RIPEMD-160).
