use crate::Schedule;
use crate::ubc_check::RecompressFrom;
pub(crate) const K: [u32; 4] = [0x5A82_7999, 0x6ED9_EBA1, 0x8F1B_BCDC, 0xCA62_C1D6];
#[inline(always)]
pub(crate) fn ch(b: u32, c: u32, d: u32) -> u32 {
d ^ (b & (c ^ d))
}
#[inline(always)]
pub(crate) fn parity(b: u32, c: u32, d: u32) -> u32 {
b ^ c ^ d
}
#[inline(always)]
pub(crate) fn maj(b: u32, c: u32, d: u32) -> u32 {
(b & c) | (d & (b | c))
}
macro_rules! step {
($f:ident, $k:expr, $a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $w:expr) => {{
$e = $e.wrapping_add(
$a.rotate_left(5)
.wrapping_add($f($b, $c, $d))
.wrapping_add($k)
.wrapping_add($w),
);
$b = $b.rotate_left(30);
}};
}
macro_rules! unstep {
($f:ident, $k:expr, $a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $w:expr) => {{
$c = $c.rotate_right(30);
$a = $a.wrapping_sub(
$b.rotate_left(5)
.wrapping_add($f($c, $d, $e))
.wrapping_add($k)
.wrapping_add($w),
);
}};
}
macro_rules! five {
($f:ident, $k:expr, $a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $w:expr, $t:expr) => {{
step!($f, $k, $a, $b, $c, $d, $e, $w.at($t));
step!($f, $k, $e, $a, $b, $c, $d, $w.at($t + 1));
step!($f, $k, $d, $e, $a, $b, $c, $w.at($t + 2));
step!($f, $k, $c, $d, $e, $a, $b, $w.at($t + 3));
step!($f, $k, $b, $c, $d, $e, $a, $w.at($t + 4));
}};
}
macro_rules! unfive {
($f:ident, $k:expr, $a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $w:expr, $t:expr) => {{
unstep!($f, $k, $a, $b, $c, $d, $e, $w.at($t + 4));
unstep!($f, $k, $b, $c, $d, $e, $a, $w.at($t + 3));
unstep!($f, $k, $c, $d, $e, $a, $b, $w.at($t + 2));
unstep!($f, $k, $d, $e, $a, $b, $c, $w.at($t + 1));
unstep!($f, $k, $e, $a, $b, $c, $d, $w.at($t));
}};
}
pub(crate) trait Words {
fn at(&self, t: usize) -> u32;
}
impl Words for Schedule {
#[inline(always)]
fn at(&self, t: usize) -> u32 {
self[t]
}
}
impl Words for [u32; 16] {
#[inline(always)]
fn at(&self, t: usize) -> u32 {
self[t]
}
}
struct Xor<'a> {
m1: &'a Schedule,
dm: &'a [u32; 80],
}
impl Words for Xor<'_> {
#[inline(always)]
fn at(&self, t: usize) -> u32 {
self.m1[t] ^ self.dm[t]
}
}
macro_rules! step_expand {
($f:ident, $k:expr, $a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $w:expr, $t:expr) => {{
let word = ($w[$t - 3] ^ $w[$t - 8] ^ $w[$t - 14] ^ $w[$t - 16]).rotate_left(1);
$w[$t] = word;
step!($f, $k, $a, $b, $c, $d, $e, word);
}};
}
macro_rules! step_load {
($f:ident, $k:expr, $a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $w:expr, $m:expr, $t:expr) => {{
let word = $m[$t];
$w[$t] = word;
step!($f, $k, $a, $b, $c, $d, $e, word);
}};
}
macro_rules! five_load {
($f:ident, $k:expr, $a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $w:expr, $m:expr, $t:expr) => {{
step_load!($f, $k, $a, $b, $c, $d, $e, $w, $m, $t);
step_load!($f, $k, $e, $a, $b, $c, $d, $w, $m, $t + 1);
step_load!($f, $k, $d, $e, $a, $b, $c, $w, $m, $t + 2);
step_load!($f, $k, $c, $d, $e, $a, $b, $w, $m, $t + 3);
step_load!($f, $k, $b, $c, $d, $e, $a, $w, $m, $t + 4);
}};
}
macro_rules! five_expand {
($f:ident, $k:expr, $a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $w:expr, $t:expr) => {{
step_expand!($f, $k, $a, $b, $c, $d, $e, $w, $t);
step_expand!($f, $k, $e, $a, $b, $c, $d, $w, $t + 1);
step_expand!($f, $k, $d, $e, $a, $b, $c, $w, $t + 2);
step_expand!($f, $k, $c, $d, $e, $a, $b, $w, $t + 3);
step_expand!($f, $k, $b, $c, $d, $e, $a, $w, $t + 4);
}};
}
#[allow(
unused_imports,
reason = "only the `x86` compression without SHA-NI uses it outside this file"
)]
pub(crate) use five;
pub(crate) use {five_expand, five_load, step, step_expand, step_load};
#[inline(always)]
pub(crate) fn add(left: &mut [u32; 5], right: [u32; 5]) {
for (l, r) in left.iter_mut().zip(right) {
*l = l.wrapping_add(r);
}
}
pub(crate) fn compression_w(ihv: &mut [u32; 5], w: &Schedule) {
let [mut a, mut b, mut c, mut d, mut e] = *ihv;
five!(ch, K[0], a, b, c, d, e, w, 0);
five!(ch, K[0], a, b, c, d, e, w, 5);
five!(ch, K[0], a, b, c, d, e, w, 10);
five!(ch, K[0], a, b, c, d, e, w, 15);
five!(parity, K[1], a, b, c, d, e, w, 20);
five!(parity, K[1], a, b, c, d, e, w, 25);
five!(parity, K[1], a, b, c, d, e, w, 30);
five!(parity, K[1], a, b, c, d, e, w, 35);
five!(maj, K[2], a, b, c, d, e, w, 40);
five!(maj, K[2], a, b, c, d, e, w, 45);
five!(maj, K[2], a, b, c, d, e, w, 50);
five!(maj, K[2], a, b, c, d, e, w, 55);
five!(parity, K[3], a, b, c, d, e, w, 60);
five!(parity, K[3], a, b, c, d, e, w, 65);
five!(parity, K[3], a, b, c, d, e, w, 70);
five!(parity, K[3], a, b, c, d, e, w, 75);
add(ihv, [a, b, c, d, e]);
}
#[allow(dead_code, reason = "a target with no SHA-1 instructions never asks")]
pub(crate) fn states_from_60_64(
w: &Schedule,
need_58: bool,
state_58: &mut [u32; 5],
state_65: &mut [u32; 5],
) {
let [a, mut b, c, d, mut e] = *state_65;
step!(parity, K[3], a, b, c, d, e, w[64]);
*state_65 = [e, a, b, c, d];
if need_58 {
let [mut a, mut b, mut c, mut d, e] = *state_58;
unstep!(maj, K[2], a, b, c, d, e, w[59]);
unstep!(maj, K[2], b, c, d, e, a, w[58]);
*state_58 = [c, d, e, a, b];
}
}
#[cfg(all(test, feature = "std"))]
pub(crate) fn state_at(ihv: &[u32; 5], w: &Schedule, t: usize) -> [u32; 5] {
let [mut a, mut b, mut c, mut d, mut e] = *ihv;
for s in 0..t {
let f = match s / 20 {
0 => ch(b, c, d),
2 => maj(b, c, d),
_ => parity(b, c, d),
};
let next = a
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(e)
.wrapping_add(K[s / 20])
.wrapping_add(w[s]);
(a, b, c, d, e) = (next, a, b.rotate_left(30), c, d);
}
[a, b, c, d, e]
}
pub(crate) fn recompression_step(
step: RecompressFrom,
ihvin: &mut [u32; 5],
ihvout: &mut [u32; 5],
m1: &Schedule,
dm: &[u32; 80],
state: &[u32; 5],
) {
let me2 = &Xor { m1, dm };
let [mut b0, mut b1, mut b2, mut b3, mut b4] = *state;
let [mut f0, mut f1, mut f2, mut f3, mut f4] = *state;
match step {
RecompressFrom::Step58 => {
unstep!(maj, K[2], b0, b1, b2, b3, b4, me2.at(57));
unstep!(maj, K[2], b1, b2, b3, b4, b0, me2.at(56));
unstep!(maj, K[2], b2, b3, b4, b0, b1, me2.at(55));
step!(maj, K[2], f0, f1, f2, f3, f4, me2.at(58));
step!(maj, K[2], f4, f0, f1, f2, f3, me2.at(59));
unfive!(maj, K[2], b3, b4, b0, b1, b2, me2, 50);
five!(parity, K[3], f3, f4, f0, f1, f2, me2, 60);
unfive!(maj, K[2], b3, b4, b0, b1, b2, me2, 45);
five!(parity, K[3], f3, f4, f0, f1, f2, me2, 65);
unfive!(maj, K[2], b3, b4, b0, b1, b2, me2, 40);
five!(parity, K[3], f3, f4, f0, f1, f2, me2, 70);
unfive!(parity, K[1], b3, b4, b0, b1, b2, me2, 35);
five!(parity, K[3], f3, f4, f0, f1, f2, me2, 75);
unfive!(parity, K[1], b3, b4, b0, b1, b2, me2, 30);
unfive!(parity, K[1], b3, b4, b0, b1, b2, me2, 25);
unfive!(parity, K[1], b3, b4, b0, b1, b2, me2, 20);
unfive!(ch, K[0], b3, b4, b0, b1, b2, me2, 15);
unfive!(ch, K[0], b3, b4, b0, b1, b2, me2, 10);
unfive!(ch, K[0], b3, b4, b0, b1, b2, me2, 5);
unfive!(ch, K[0], b3, b4, b0, b1, b2, me2, 0);
*ihvin = [b3, b4, b0, b1, b2];
*ihvout = *ihvin;
add(ihvout, [f3, f4, f0, f1, f2]);
}
RecompressFrom::Step65 => {
unfive!(parity, K[3], b0, b1, b2, b3, b4, me2, 60);
five!(parity, K[3], f0, f1, f2, f3, f4, me2, 65);
unfive!(maj, K[2], b0, b1, b2, b3, b4, me2, 55);
five!(parity, K[3], f0, f1, f2, f3, f4, me2, 70);
unfive!(maj, K[2], b0, b1, b2, b3, b4, me2, 50);
five!(parity, K[3], f0, f1, f2, f3, f4, me2, 75);
unfive!(maj, K[2], b0, b1, b2, b3, b4, me2, 45);
unfive!(maj, K[2], b0, b1, b2, b3, b4, me2, 40);
unfive!(parity, K[1], b0, b1, b2, b3, b4, me2, 35);
unfive!(parity, K[1], b0, b1, b2, b3, b4, me2, 30);
unfive!(parity, K[1], b0, b1, b2, b3, b4, me2, 25);
unfive!(parity, K[1], b0, b1, b2, b3, b4, me2, 20);
unfive!(ch, K[0], b0, b1, b2, b3, b4, me2, 15);
unfive!(ch, K[0], b0, b1, b2, b3, b4, me2, 10);
unfive!(ch, K[0], b0, b1, b2, b3, b4, me2, 5);
unfive!(ch, K[0], b0, b1, b2, b3, b4, me2, 0);
*ihvin = [b0, b1, b2, b3, b4];
*ihvout = *ihvin;
add(ihvout, [f0, f1, f2, f3, f4]);
}
}
}
#[allow(dead_code, reason = "a target with no SHA-1 instructions never asks")]
pub(crate) fn partner_boundary(
step: RecompressFrom,
m1: &Schedule,
dm: &[u32; 80],
state: &[u32; 5],
) -> [u32; 5] {
let me2 = &Xor { m1, dm };
let [mut a, mut b, mut c, mut d, mut e] = *state;
match step {
RecompressFrom::Step58 => {
step!(maj, K[2], a, b, c, d, e, me2.at(58));
step!(maj, K[2], e, a, b, c, d, me2.at(59));
[d, e, a, b, c]
}
RecompressFrom::Step65 => {
unstep!(parity, K[3], a, b, c, d, e, me2.at(64));
[b, c, d, e, a]
}
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::ubc_check::SHA1_DVS;
use quickcheck::QuickCheck;
#[test]
fn the_recompression_agrees_with_the_compression() {
fn prop(w: [u32; 80], state: [u32; 5]) -> bool {
let m1 = Schedule::from_words(w);
SHA1_DVS.iter().all(|dv| {
let (mut ihvin, mut ihvout) = ([0u32; 5], [0u32; 5]);
recompression_step(
dv.recompress_from,
&mut ihvin,
&mut ihvout,
&m1,
&dv.dm,
&state,
);
let mut me2 = Schedule::zeroed();
for t in 0..80 {
me2[t] = m1[t] ^ dv.dm[t];
}
let mut replayed = ihvin;
compression_w(&mut replayed, &me2);
replayed == ihvout
})
}
QuickCheck::new()
.tests(500)
.quickcheck(prop as fn([u32; 80], [u32; 5]) -> bool);
}
#[test]
fn the_partner_boundary_is_on_the_partners_way() {
fn prop(w: [u32; 80], state: [u32; 5]) -> bool {
let m1 = Schedule::from_words(w);
SHA1_DVS.iter().all(|dv| {
let (mut ihvin, mut ihvout) = ([0u32; 5], [0u32; 5]);
recompression_step(
dv.recompress_from,
&mut ihvin,
&mut ihvout,
&m1,
&dv.dm,
&state,
);
let mut me2 = Schedule::zeroed();
for t in 0..80 {
me2[t] = m1[t] ^ dv.dm[t];
}
let at = match dv.recompress_from {
RecompressFrom::Step58 => 60,
RecompressFrom::Step65 => 64,
};
partner_boundary(dv.recompress_from, &m1, &dv.dm, &state)
== state_at(&ihvin, &me2, at)
})
}
QuickCheck::new()
.tests(500)
.quickcheck(prop as fn([u32; 80], [u32; 5]) -> bool);
}
}