Skip to main content

arcana/hash/
ripemd160.rs

1//! RIPEMD-160 hash function.
2//!
3//! 160-bit output, 512-bit (64-byte) blocks. Two parallel computation lines
4//! (left and right), each with 5 rounds of 16 operations (80 total per line).
5
6use crate::Hasher;
7
8const H0: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
9
10// Left round constants
11const KL: [u32; 5] = [0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E];
12
13// Right round constants
14const KR: [u32; 5] = [0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000];
15
16// Left message word selection
17const RL: [usize; 80] = [
18    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, 2, 14, 11, 8, 3, 10,
19    14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, 5, 9, 7,
20    12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13,
21];
22
23// Right message word selection
24const RR: [usize; 80] = [
25    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, 4, 9, 1, 2, 15, 5,
26    1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, 15, 10, 4,
27    1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11,
28];
29
30// Left shift amounts
31const SL: [u32; 80] = [
32    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, 9, 11, 7, 13, 12, 11,
33    13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 9, 15,
34    5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6,
35];
36
37// Right shift amounts
38const SR: [u32; 80] = [
39    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, 7, 6, 15, 13, 11, 9,
40    7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 8, 5,
41    12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11,
42];
43
44#[inline(always)]
45fn f(j: usize, x: u32, y: u32, z: u32) -> u32 {
46    match j {
47        0..=15 => x ^ y ^ z,
48        16..=31 => (x & y) | ((!x) & z),
49        32..=47 => (x | (!y)) ^ z,
50        48..=63 => (x & z) | (y & (!z)),
51        _ => x ^ (y | (!z)),
52    }
53}
54
55fn compress(state: &mut [u32; 5], block: &[u8]) {
56    let mut x = [0u32; 16];
57    for i in 0..16 {
58        x[i] = u32::from_le_bytes([block[4 * i], block[4 * i + 1], block[4 * i + 2], block[4 * i + 3]]);
59    }
60
61    let mut al = state[0];
62    let mut bl = state[1];
63    let mut cl = state[2];
64    let mut dl = state[3];
65    let mut el = state[4];
66
67    let mut ar = state[0];
68    let mut br = state[1];
69    let mut cr = state[2];
70    let mut dr = state[3];
71    let mut er = state[4];
72
73    for j in 0..80 {
74        let round = j / 16;
75
76        // Left line
77        let t = al
78            .wrapping_add(f(j, bl, cl, dl))
79            .wrapping_add(x[RL[j]])
80            .wrapping_add(KL[round])
81            .rotate_left(SL[j])
82            .wrapping_add(el);
83        al = el;
84        el = dl;
85        dl = cl.rotate_left(10);
86        cl = bl;
87        bl = t;
88
89        // Right line
90        let t = ar
91            .wrapping_add(f(79 - j, br, cr, dr))
92            .wrapping_add(x[RR[j]])
93            .wrapping_add(KR[round])
94            .rotate_left(SR[j])
95            .wrapping_add(er);
96        ar = er;
97        er = dr;
98        dr = cr.rotate_left(10);
99        cr = br;
100        br = t;
101    }
102
103    let t = state[1].wrapping_add(cl).wrapping_add(dr);
104    state[1] = state[2].wrapping_add(dl).wrapping_add(er);
105    state[2] = state[3].wrapping_add(el).wrapping_add(ar);
106    state[3] = state[4].wrapping_add(al).wrapping_add(br);
107    state[4] = state[0].wrapping_add(bl).wrapping_add(cr);
108    state[0] = t;
109}
110
111/// RIPEMD-160 hasher (ISO/IEC 10118-3). 160-bit output, 64-byte blocks.
112///
113/// Designed by Dobbertin / Bosselaers / Preneel as a counterpart to
114/// MD5 / SHA-1, RIPEMD-160 is best known today as the second hash in
115/// the Bitcoin address derivation chain (`RIPEMD-160(SHA-256(pk))`)
116/// and in some 2000s X.509 certificates from European CAs that
117/// preferred non-NSA-designed hashes. Considered safe but rarely
118/// used outside these legacy niches.
119#[derive(Clone)]
120pub struct Ripemd160 {
121    state: [u32; 5],
122    buf: [u8; 64],
123    buf_len: usize,
124    total_len: u64,
125}
126
127impl Hasher for Ripemd160 {
128    const OUTPUT_LEN: usize = 20;
129    const BLOCK_LEN: usize = 64;
130
131    fn new() -> Self {
132        Self {
133            state: H0,
134            buf: [0u8; 64],
135            buf_len: 0,
136            total_len: 0,
137        }
138    }
139
140    fn update(&mut self, data: &[u8]) {
141        let mut pos = 0;
142        self.total_len += data.len() as u64;
143
144        if self.buf_len > 0 {
145            let need = 64 - self.buf_len;
146            let take = need.min(data.len());
147            self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&data[..take]);
148            self.buf_len += take;
149            pos = take;
150            if self.buf_len == 64 {
151                let block = self.buf;
152                compress(&mut self.state, &block);
153                self.buf_len = 0;
154            }
155        }
156
157        while pos + 64 <= data.len() {
158            compress(&mut self.state, &data[pos..pos + 64]);
159            pos += 64;
160        }
161
162        if pos < data.len() {
163            let remaining = data.len() - pos;
164            self.buf[..remaining].copy_from_slice(&data[pos..]);
165            self.buf_len = remaining;
166        }
167    }
168
169    fn finalize(self) -> Vec<u8> {
170        let mut out = vec![0u8; 20];
171        self.finalize_into(&mut out);
172        out
173    }
174
175    fn finalize_into(mut self, out: &mut [u8]) {
176        let bit_len = self.total_len * 8;
177        let mut pad = [0u8; 72];
178        pad[0] = 0x80;
179        let pad_len = if self.buf_len < 56 {
180            56 - self.buf_len
181        } else {
182            120 - self.buf_len
183        };
184        self.update(&pad[..pad_len]);
185        // RIPEMD-160 uses little-endian length
186        self.update(&bit_len.to_le_bytes());
187
188        for (i, word) in self.state.iter().enumerate() {
189            let bytes = word.to_le_bytes();
190            let start = i * 4;
191            if start + 4 <= out.len() {
192                out[start..start + 4].copy_from_slice(&bytes);
193            } else if start < out.len() {
194                let end = out.len() - start;
195                out[start..].copy_from_slice(&bytes[..end]);
196            }
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use crate::Hasher;
205
206    #[test]
207    fn test_ripemd160_empty() {
208        let digest = Ripemd160::hash(b"");
209        let expected: [u8; 20] = [
210            0x9c, 0x11, 0x85, 0xa5, 0xc5, 0xe9, 0xfc, 0x54, 0x61, 0x28, 0x08, 0x97, 0x7e, 0xe8, 0xf5, 0x48, 0xb2, 0x25,
211            0x8d, 0x31,
212        ];
213        assert_eq!(&digest[..], &expected[..]);
214    }
215
216    #[test]
217    fn test_ripemd160_abc() {
218        let digest = Ripemd160::hash(b"abc");
219        let expected: [u8; 20] = [
220            0x8e, 0xb2, 0x08, 0xf7, 0xe0, 0x5d, 0x98, 0x7a, 0x9b, 0x04, 0x4a, 0x8e, 0x98, 0xc6, 0xb0, 0x87, 0xf1, 0x5a,
221            0x0b, 0xfc,
222        ];
223        assert_eq!(&digest[..], &expected[..]);
224    }
225}