Skip to main content

cryptography/hash/
ripemd160.rs

1//! RIPEMD-160 from Dobbertin/Bosselaers/Preneel.
2//!
3//! RIPEMD-160 is retained for interoperability with legacy ecosystems
4//! (for example, Bitcoin address derivation). It should not be used as the
5//! first choice for new protocol designs.
6
7use super::Digest;
8
9// Initial chaining value from the RIPEMD-160 specification.
10const IV: [u32; 5] = [
11    0x6745_2301,
12    0xefcd_ab89,
13    0x98ba_dcfe,
14    0x1032_5476,
15    0xc3d2_e1f0,
16];
17
18// Left-line message word order (r_j) for rounds 0..79.
19const RL: [usize; 80] = [
20    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5,
21    2, 14, 11, 8, 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4,
22    13, 3, 7, 15, 14, 5, 6, 2, 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13,
23];
24
25// Right-line message word order (r'_j) for rounds 0..79.
26const RR: [usize; 80] = [
27    5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12,
28    4, 9, 1, 2, 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5,
29    12, 2, 13, 9, 7, 10, 14, 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11,
30];
31
32// Left-line rotation counts (s_j) for rounds 0..79.
33const SL: [u32; 80] = [
34    11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15,
35    9, 11, 7, 13, 12, 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14,
36    15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6,
37];
38
39// Right-line rotation counts (s'_j) for rounds 0..79.
40const SR: [u32; 80] = [
41    8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12,
42    7, 6, 15, 13, 11, 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14,
43    6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11,
44];
45
46#[inline]
47fn f(j: usize, x: u32, y: u32, z: u32) -> u32 {
48    // Five Boolean functions used over 16-round phases.
49    match j {
50        0..=15 => x ^ y ^ z,
51        16..=31 => (x & y) | (!x & z),
52        32..=47 => (x | !y) ^ z,
53        48..=63 => (x & z) | (y & !z),
54        _ => x ^ (y | !z),
55    }
56}
57
58#[inline]
59fn k_left(j: usize) -> u32 {
60    // Left-line additive constants K_j (spec Table 2).
61    match j {
62        0..=15 => 0x0000_0000,
63        16..=31 => 0x5a82_7999,
64        32..=47 => 0x6ed9_eba1,
65        48..=63 => 0x8f1b_bcdc,
66        _ => 0xa953_fd4e,
67    }
68}
69
70#[inline]
71fn k_right(j: usize) -> u32 {
72    // Right-line additive constants K'_j (spec Table 2).
73    match j {
74        0..=15 => 0x50a2_8be6,
75        16..=31 => 0x5c4d_d124,
76        32..=47 => 0x6d70_3ef3,
77        48..=63 => 0x7a6d_76e9,
78        _ => 0x0000_0000,
79    }
80}
81
82#[inline]
83fn compress(state: &mut [u32; 5], block: &[u8; 64]) {
84    let mut words = [0u32; 16];
85    for (i, chunk) in block.chunks_exact(4).enumerate() {
86        words[i] = u32::from_le_bytes(chunk.try_into().expect("4-byte chunk"));
87    }
88
89    // RIPEMD-160 runs two parallel 80-round lines (left and right) over the
90    // same message block with different schedules/constants, then mixes them.
91    let mut al = state[0];
92    let mut bl = state[1];
93    let mut cl = state[2];
94    let mut dl = state[3];
95    let mut el = state[4];
96
97    let mut ar = state[0];
98    let mut br = state[1];
99    let mut cr = state[2];
100    let mut dr = state[3];
101    let mut er = state[4];
102
103    for j in 0..80 {
104        let tl = al
105            .wrapping_add(f(j, bl, cl, dl))
106            .wrapping_add(words[RL[j]])
107            .wrapping_add(k_left(j))
108            .rotate_left(SL[j])
109            .wrapping_add(el);
110        al = el;
111        el = dl;
112        dl = cl.rotate_left(10);
113        cl = bl;
114        bl = tl;
115
116        let tr = ar
117            .wrapping_add(f(79 - j, br, cr, dr))
118            .wrapping_add(words[RR[j]])
119            .wrapping_add(k_right(j))
120            .rotate_left(SR[j])
121            .wrapping_add(er);
122        ar = er;
123        er = dr;
124        dr = cr.rotate_left(10);
125        cr = br;
126        br = tr;
127    }
128
129    // Feed-forward merge step from the specification: cross-couple both lines
130    // back into the 5-word chaining state.
131    let t = state[1].wrapping_add(cl).wrapping_add(dr);
132    state[1] = state[2].wrapping_add(dl).wrapping_add(er);
133    state[2] = state[3].wrapping_add(el).wrapping_add(ar);
134    state[3] = state[4].wrapping_add(al).wrapping_add(br);
135    state[4] = state[0].wrapping_add(bl).wrapping_add(cr);
136    state[0] = t;
137}
138
139#[derive(Clone)]
140pub struct Ripemd160 {
141    state: [u32; 5],
142    block: [u8; 64],
143    pos: usize,
144    bit_len: u64,
145}
146
147impl Default for Ripemd160 {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153impl Ripemd160 {
154    pub const BLOCK_LEN: usize = 64;
155    pub const OUTPUT_LEN: usize = 20;
156
157    #[must_use]
158    pub fn new() -> Self {
159        Self {
160            state: IV,
161            block: [0u8; 64],
162            pos: 0,
163            bit_len: 0,
164        }
165    }
166
167    pub fn update(&mut self, mut data: &[u8]) {
168        while !data.is_empty() {
169            let take = (64 - self.pos).min(data.len());
170            self.block[self.pos..self.pos + take].copy_from_slice(&data[..take]);
171            self.pos += take;
172            data = &data[take..];
173
174            if self.pos == 64 {
175                compress(&mut self.state, &self.block);
176                self.block = [0u8; 64];
177                self.pos = 0;
178                self.bit_len = self.bit_len.wrapping_add(512);
179            }
180        }
181    }
182
183    #[must_use]
184    pub fn finalize(mut self) -> [u8; 20] {
185        self.bit_len = self.bit_len.wrapping_add((self.pos as u64) * 8);
186
187        self.block[self.pos] = 0x80;
188        self.pos += 1;
189
190        if self.pos > 56 {
191            self.block[self.pos..].fill(0);
192            compress(&mut self.state, &self.block);
193            self.block = [0u8; 64];
194            self.pos = 0;
195        }
196
197        self.block[self.pos..56].fill(0);
198        self.block[56..].copy_from_slice(&self.bit_len.to_le_bytes());
199        compress(&mut self.state, &self.block);
200
201        let mut out = [0u8; 20];
202        for (chunk, word) in out.chunks_exact_mut(4).zip(self.state.iter()) {
203            chunk.copy_from_slice(&word.to_le_bytes());
204        }
205        out
206    }
207
208    #[must_use]
209    pub fn digest(data: &[u8]) -> [u8; 20] {
210        let mut h = Self::new();
211        h.update(data);
212        h.finalize()
213    }
214
215    fn finalize_into_reset(&mut self, out: &mut [u8; 20]) {
216        self.bit_len = self.bit_len.wrapping_add((self.pos as u64) * 8);
217
218        self.block[self.pos] = 0x80;
219        self.pos += 1;
220
221        if self.pos > 56 {
222            self.block[self.pos..].fill(0);
223            compress(&mut self.state, &self.block);
224            self.block = [0u8; 64];
225            self.pos = 0;
226        }
227
228        self.block[self.pos..56].fill(0);
229        self.block[56..].copy_from_slice(&self.bit_len.to_le_bytes());
230        compress(&mut self.state, &self.block);
231
232        for (chunk, word) in out.chunks_exact_mut(4).zip(self.state.iter()) {
233            chunk.copy_from_slice(&word.to_le_bytes());
234        }
235
236        self.zeroize();
237    }
238}
239
240impl Digest for Ripemd160 {
241    const BLOCK_LEN: usize = 64;
242    const OUTPUT_LEN: usize = 20;
243
244    fn new() -> Self {
245        Self::new()
246    }
247
248    fn update(&mut self, data: &[u8]) {
249        self.update(data);
250    }
251
252    fn finalize_into(self, out: &mut [u8]) {
253        assert_eq!(out.len(), 20, "wrong digest length");
254        out.copy_from_slice(&self.finalize());
255    }
256
257    fn finalize_reset(&mut self, out: &mut [u8]) {
258        let out: &mut [u8; 20] = out.try_into().expect("wrong digest length");
259        self.finalize_into_reset(out);
260    }
261
262    fn zeroize(&mut self) {
263        crate::ct::zeroize_slice(self.state.as_mut_slice());
264        crate::ct::zeroize_slice(self.block.as_mut_slice());
265        self.pos = 0;
266        self.bit_len = 0;
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    fn hex(bytes: &[u8]) -> String {
275        let mut out = String::with_capacity(bytes.len() * 2);
276        for b in bytes {
277            use core::fmt::Write;
278            let _ = write!(&mut out, "{b:02x}");
279        }
280        out
281    }
282
283    #[test]
284    fn ripemd160_empty() {
285        assert_eq!(
286            hex(&Ripemd160::digest(b"")),
287            "9c1185a5c5e9fc54612808977ee8f548b2258d31"
288        );
289    }
290
291    #[test]
292    fn ripemd160_abc() {
293        assert_eq!(
294            hex(&Ripemd160::digest(b"abc")),
295            "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"
296        );
297    }
298
299    #[test]
300    fn ripemd160_message_digest() {
301        assert_eq!(
302            hex(&Ripemd160::digest(b"message digest")),
303            "5d0689ef49d2fae572b881b123a85ffa21595f36"
304        );
305    }
306
307    #[test]
308    fn ripemd160_matches_openssl() {
309        let msg = b"The quick brown fox jumps over the lazy dog";
310        let Some(expected) = crate::test_utils::run_openssl(&["dgst", "-ripemd160", "-binary"], msg) else {
311            return;
312        };
313        assert_eq!(Ripemd160::digest(msg).as_slice(), expected.as_slice());
314    }
315}