Skip to main content

logicaffeine_base/
sha_ops.rs

1//! The four Intel SHA-1 (SHA-NI) operations, in SOFTWARE — the exact bit-for-bit semantics of
2//! `sha1rnds4` / `sha1msg1` / `sha1msg2` / `sha1nexte`. These are the *spec* the tree-walker runs so
3//! that SHA-1 written in LOGOS over these ops produces the identical result whether it is interpreted
4//! (software here) or AOT-compiled to the real hardware instruction (`core::arch::x86_64`). Same idea
5//! as the scalar-lane spec behind `Lanes8Word32` → AVX2.
6//!
7//! A 128-bit value is a `[u32; 4]` in LANE order: index `i` is lanes bits `[32i+31 : 32i]`, so index 0
8//! is the low dword (`SRC[31:0]`) and index 3 is the high dword (`SRC[127:96]`) — matching how
9//! `_mm_loadu_si128`/`_mm_storeu_si128` move an array to/from an `__m128i`. The tests below load the
10//! same array into an `__m128i` and assert the software op equals the hardware intrinsic on random
11//! inputs, so the spec is validated against silicon, not just against itself.
12
13#[inline]
14fn ch(b: u32, c: u32, d: u32) -> u32 {
15    (b & c) | (!b & d)
16}
17#[inline]
18fn parity(b: u32, c: u32, d: u32) -> u32 {
19    b ^ c ^ d
20}
21#[inline]
22fn maj(b: u32, c: u32, d: u32) -> u32 {
23    (b & c) | (b & d) | (c & d)
24}
25
26/// `sha1rnds4(abcd, msg, func)` — four rounds of SHA-1. `abcd` is the working state (A in lane 3 …
27/// D in lane 0), `msg` the four message dwords with the round's E already folded into its high dword
28/// (via [`sha1nexte`] / the initial add), and `func` ∈ 0..=3 selects the round function + constant
29/// (0 = Ch/K0, 1 = Parity/K1, 2 = Maj/K2, 3 = Parity/K3). Returns the new state.
30pub fn sha1rnds4(abcd: [u32; 4], msg: [u32; 4], func: u32) -> [u32; 4] {
31    let (k, f): (u32, fn(u32, u32, u32) -> u32) = match func & 3 {
32        0 => (0x5A82_7999, ch),
33        1 => (0x6ED9_EBA1, parity),
34        2 => (0x8F1B_BCDC, maj),
35        _ => (0xCA62_C1D6, parity),
36    };
37    // A = SRC1[127:96] = lane 3, … D = SRC1[31:0] = lane 0. Message W0 = SRC2[127:96] = lane 3.
38    let mut a = abcd[3];
39    let mut b = abcd[2];
40    let mut c = abcd[1];
41    let mut d = abcd[0];
42    let w = [msg[3], msg[2], msg[1], msg[0]];
43    // Round 0's E is folded into W0 (msg high dword); subsequent rounds carry E through the state.
44    let mut e = 0u32;
45    for &wi in &w {
46        let t = f(b, c, d)
47            .wrapping_add(a.rotate_left(5))
48            .wrapping_add(wi)
49            .wrapping_add(k)
50            .wrapping_add(e);
51        e = d;
52        d = c;
53        c = b.rotate_left(30);
54        b = a;
55        a = t;
56    }
57    // DEST[127:96] = A (lane 3) … DEST[31:0] = D (lane 0).
58    [d, c, b, a]
59}
60
61/// `sha1msg1(a, b)` — the first half of the message-schedule recurrence (the XOR mixing before the
62/// rotate). `dest = { W2^W0, W3^W1, W4^W2, W5^W3 }` per Intel, in lane order.
63pub fn sha1msg1(a: [u32; 4], b: [u32; 4]) -> [u32; 4] {
64    // W0=a[3],W1=a[2],W2=a[1],W3=a[0]; W4=b[3],W5=b[2].
65    [
66        b[2] ^ a[0], // DEST[31:0]  = W5 ^ W3
67        b[3] ^ a[1], // DEST[63:32] = W4 ^ W2
68        a[0] ^ a[2], // DEST[95:64] = W3 ^ W1
69        a[1] ^ a[3], // DEST[127:96]= W2 ^ W0
70    ]
71}
72
73/// `sha1msg2(a, b)` — completes the schedule: the final XOR with the previous words and the ROL-1,
74/// including the intra-vector dependency of W19 on W16.
75pub fn sha1msg2(a: [u32; 4], b: [u32; 4]) -> [u32; 4] {
76    // W13=b[2],W14=b[1],W15=b[0].
77    let w16 = (a[3] ^ b[2]).rotate_left(1); // DEST[127:96]
78    let w17 = (a[2] ^ b[1]).rotate_left(1); // DEST[95:64]
79    let w18 = (a[1] ^ b[0]).rotate_left(1); // DEST[63:32]
80    let w19 = (a[0] ^ w16).rotate_left(1); // DEST[31:0], depends on W16
81    [w19, w18, w17, w16]
82}
83
84/// `sha1nexte(a, b)` — fold the next E (the previous block-round's A, rotated) into the high dword of
85/// the next message group. `dest[127:96] = b[127:96] + (a[127:96] ROL 30)`; the low 96 bits pass `b`.
86pub fn sha1nexte(a: [u32; 4], b: [u32; 4]) -> [u32; 4] {
87    [b[0], b[1], b[2], b[3].wrapping_add(a[3].rotate_left(30))]
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[cfg(target_arch = "x86_64")]
95    fn hw_available() -> bool {
96        std::is_x86_feature_detected!("sha")
97            && std::is_x86_feature_detected!("ssse3")
98            && std::is_x86_feature_detected!("sse4.1")
99    }
100
101    /// Drive random inputs through both the software op and the real intrinsic; they must be equal —
102    /// this is the proof that the software spec IS the hardware.
103    #[cfg(target_arch = "x86_64")]
104    #[test]
105    fn software_sha_ops_equal_the_hardware_intrinsics() {
106        if !hw_available() {
107            eprintln!("skipping: no SHA-NI on this CPU");
108            return;
109        }
110        use core::arch::x86_64::*;
111
112        #[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
113        unsafe fn check(a: [u32; 4], b: [u32; 4]) {
114            let va = _mm_loadu_si128(a.as_ptr() as *const __m128i);
115            let vb = _mm_loadu_si128(b.as_ptr() as *const __m128i);
116            let store = |v: __m128i| {
117                let mut o = [0u32; 4];
118                _mm_storeu_si128(o.as_mut_ptr() as *mut __m128i, v);
119                o
120            };
121            assert_eq!(store(_mm_sha1msg1_epu32(va, vb)), sha1msg1(a, b), "sha1msg1");
122            assert_eq!(store(_mm_sha1msg2_epu32(va, vb)), sha1msg2(a, b), "sha1msg2");
123            assert_eq!(store(_mm_sha1nexte_epu32(va, vb)), sha1nexte(a, b), "sha1nexte");
124            assert_eq!(store(_mm_sha1rnds4_epu32(va, vb, 0)), sha1rnds4(a, b, 0), "rnds4 f0");
125            assert_eq!(store(_mm_sha1rnds4_epu32(va, vb, 1)), sha1rnds4(a, b, 1), "rnds4 f1");
126            assert_eq!(store(_mm_sha1rnds4_epu32(va, vb, 2)), sha1rnds4(a, b, 2), "rnds4 f2");
127            assert_eq!(store(_mm_sha1rnds4_epu32(va, vb, 3)), sha1rnds4(a, b, 3), "rnds4 f3");
128        }
129
130        let mut s: u64 = 0x0123_4567_89ab_cdef;
131        let mut next = || {
132            s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
133            (s >> 32) as u32
134        };
135        for _ in 0..20_000 {
136            let a = [next(), next(), next(), next()];
137            let b = [next(), next(), next(), next()];
138            unsafe { check(a, b) };
139        }
140    }
141}