use crate::Schedule;
const DV_I_43_0_BIT: u32 = 1 << 0;
const DV_I_44_0_BIT: u32 = 1 << 1;
const DV_I_45_0_BIT: u32 = 1 << 2;
const DV_I_46_0_BIT: u32 = 1 << 3;
const DV_I_46_2_BIT: u32 = 1 << 4;
const DV_I_47_0_BIT: u32 = 1 << 5;
const DV_I_47_2_BIT: u32 = 1 << 6;
const DV_I_48_0_BIT: u32 = 1 << 7;
const DV_I_48_2_BIT: u32 = 1 << 8;
const DV_I_49_0_BIT: u32 = 1 << 9;
const DV_I_49_2_BIT: u32 = 1 << 10;
const DV_I_50_0_BIT: u32 = 1 << 11;
const DV_I_50_2_BIT: u32 = 1 << 12;
const DV_I_51_0_BIT: u32 = 1 << 13;
const DV_I_51_2_BIT: u32 = 1 << 14;
const DV_I_52_0_BIT: u32 = 1 << 15;
const DV_II_45_0_BIT: u32 = 1 << 16;
const DV_II_46_0_BIT: u32 = 1 << 17;
const DV_II_46_2_BIT: u32 = 1 << 18;
const DV_II_47_0_BIT: u32 = 1 << 19;
const DV_II_48_0_BIT: u32 = 1 << 20;
const DV_II_49_0_BIT: u32 = 1 << 21;
const DV_II_49_2_BIT: u32 = 1 << 22;
const DV_II_50_0_BIT: u32 = 1 << 23;
const DV_II_50_2_BIT: u32 = 1 << 24;
const DV_II_51_0_BIT: u32 = 1 << 25;
const DV_II_51_2_BIT: u32 = 1 << 26;
const DV_II_52_0_BIT: u32 = 1 << 27;
const DV_II_53_0_BIT: u32 = 1 << 28;
const DV_II_54_0_BIT: u32 = 1 << 29;
const DV_II_55_0_BIT: u32 = 1 << 30;
const DV_II_56_0_BIT: u32 = 1 << 31;
#[derive(Clone, Copy)]
pub(crate) struct Info {
pub(crate) recompress_from: RecompressFrom,
pub(crate) mask_bit: i32,
pub(crate) dm: [u32; 80],
}
#[derive(Copy, Clone)]
#[repr(u32)]
pub(crate) enum RecompressFrom {
Step58 = 58,
Step65 = 65,
}
#[derive(Clone, Copy, PartialEq)]
enum DvType {
I,
II,
}
const TYPE_I_SEED: [u32; 16] = [
0x04000010, 0xe8000000, 0x0800000c, 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014,
0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000,
];
const TYPE_II_SEED: [u32; 16] = [
0x2600001a, 0x00000010, 0x0400001c, 0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004,
0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004,
];
const TYPE_I_K: u32 = 52;
const TYPE_II_K: u32 = 56;
const fn expand(seed: [u32; 16]) -> [u32; 96] {
let mut w = [0u32; 96];
let mut t = 0;
while t < 16 {
w[t] = seed[t];
t += 1;
}
while t < 96 {
w[t] = (w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]).rotate_left(1);
t += 1;
}
w
}
const TYPE_I_DM: [u32; 96] = expand(TYPE_I_SEED);
const TYPE_II_DM: [u32; 96] = expand(TYPE_II_SEED);
const fn message_difference(family: DvType, k: u32, b: u32) -> [u32; 80] {
let (base, base_k) = match family {
DvType::I => (&TYPE_I_DM, TYPE_I_K),
DvType::II => (&TYPE_II_DM, TYPE_II_K),
};
let offset = (base_k - k) as usize;
let mut dm = [0u32; 80];
let mut i = 0;
while i < 80 {
dm[i] = base[i + offset].rotate_left(b);
i += 1;
}
dm
}
const DVS: [(DvType, u32, u32, RecompressFrom); 32] = [
(DvType::I, 43, 0, RecompressFrom::Step58),
(DvType::I, 44, 0, RecompressFrom::Step58),
(DvType::I, 45, 0, RecompressFrom::Step58),
(DvType::I, 46, 0, RecompressFrom::Step58),
(DvType::I, 46, 2, RecompressFrom::Step58),
(DvType::I, 47, 0, RecompressFrom::Step58),
(DvType::I, 47, 2, RecompressFrom::Step58),
(DvType::I, 48, 0, RecompressFrom::Step58),
(DvType::I, 48, 2, RecompressFrom::Step58),
(DvType::I, 49, 0, RecompressFrom::Step58),
(DvType::I, 49, 2, RecompressFrom::Step58),
(DvType::I, 50, 0, RecompressFrom::Step65),
(DvType::I, 50, 2, RecompressFrom::Step65),
(DvType::I, 51, 0, RecompressFrom::Step65),
(DvType::I, 51, 2, RecompressFrom::Step65),
(DvType::I, 52, 0, RecompressFrom::Step65),
(DvType::II, 45, 0, RecompressFrom::Step58),
(DvType::II, 46, 0, RecompressFrom::Step58),
(DvType::II, 46, 2, RecompressFrom::Step58),
(DvType::II, 47, 0, RecompressFrom::Step58),
(DvType::II, 48, 0, RecompressFrom::Step58),
(DvType::II, 49, 0, RecompressFrom::Step58),
(DvType::II, 49, 2, RecompressFrom::Step58),
(DvType::II, 50, 0, RecompressFrom::Step65),
(DvType::II, 50, 2, RecompressFrom::Step65),
(DvType::II, 51, 0, RecompressFrom::Step65),
(DvType::II, 51, 2, RecompressFrom::Step65),
(DvType::II, 52, 0, RecompressFrom::Step65),
(DvType::II, 53, 0, RecompressFrom::Step65),
(DvType::II, 54, 0, RecompressFrom::Step65),
(DvType::II, 55, 0, RecompressFrom::Step65),
(DvType::II, 56, 0, RecompressFrom::Step65),
];
pub(crate) const SHA1_DVS: [Info; 32] = build_dvs();
pub(crate) const STEP58_MASK: u32 = build_step58_mask();
const fn build_step58_mask() -> u32 {
let mut mask = 0;
let mut i = 0;
while i < DVS.len() {
if DVS[i].3 as u32 == RecompressFrom::Step58 as u32 {
mask |= 1 << i;
}
i += 1;
}
mask
}
const fn build_dvs() -> [Info; 32] {
let mut out = [Info {
recompress_from: RecompressFrom::Step58,
mask_bit: 0,
dm: [0u32; 80],
}; 32];
let mut i = 0;
while i < out.len() {
let (family, k, b, recompress_from) = DVS[i];
out[i] = Info {
recompress_from,
mask_bit: i as i32,
dm: message_difference(family, k, b),
};
i += 1;
}
out
}
#[cfg(test)]
mod conditions;
mod scalar;
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
mod neon;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2"
))]
mod sse2;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2",
any(feature = "std", target_feature = "avx2")
))]
mod avx2;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2",
any(feature = "std", target_feature = "avx2")
))]
#[inline(always)]
fn has_avx2() -> bool {
#[cfg(feature = "std")]
{
std::arch::is_x86_feature_detected!("avx2")
}
#[cfg(not(feature = "std"))]
{
cfg!(target_feature = "avx2")
}
}
macro_rules! dispatch {
($scalar_only:expr, $pick:ident) => {{
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
if !$scalar_only {
return $pick!(neon);
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2"
))]
if !$scalar_only {
#[cfg(any(feature = "std", target_feature = "avx2"))]
if has_avx2() {
return $pick!(avx2);
}
return $pick!(sse2);
}
#[cfg(not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2"
)
)))]
let _ = $scalar_only;
$pick!(scalar)
}};
}
#[inline]
pub(crate) fn ubc_check(w: &Schedule, scalar_only: bool) -> u32 {
macro_rules! run {
(neon) => {{
unsafe { neon::check(w) }
}};
(avx2) => {{
unsafe { avx2::check(w) }
}};
(sse2) => {{
unsafe { sse2::check(w) }
}};
(scalar) => {
scalar::check(w)
};
}
dispatch!(scalar_only, run)
}
#[cfg(test)]
mod tests {
use super::*;
fn schedules(n: usize, mut f: impl FnMut(&Schedule)) {
let mut seed = 0x1234_5678_9abc_def0u64;
for _ in 0..n {
let m = core::array::from_fn(|_| {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed as u32
});
f(&Schedule::expand(&m));
}
}
fn diverging_form(w: &Schedule) -> Option<&'static str> {
#[allow(unused_variables)]
let want = scalar::check(w);
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
if unsafe { neon::check(w) } != want {
return Some("neon");
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2"
))]
if unsafe { sse2::check(w) } != want {
return Some("sse2");
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2",
any(feature = "std", target_feature = "avx2")
))]
if has_avx2() {
if unsafe { avx2::check(w) } != want {
return Some("avx2");
}
}
None
}
#[cfg(feature = "std")]
fn selected_form() -> &'static str {
macro_rules! name {
($form:ident) => {
stringify!($form)
};
}
dispatch!(false, name)
}
#[cfg(feature = "std")]
#[test]
fn the_expected_implementation_was_selected() {
let expected = std::env::var("SHA1DC_EXPECT_UBC_CHECK").unwrap_or_default();
let expected = expected.trim();
if expected.is_empty() {
return; }
assert_eq!(
selected_form(),
expected,
"this job was meant to exercise a different implementation of `ubc_check`"
);
}
#[test]
fn dv_table_matches_c() {
const EXPECTED: u64 = 0xab2c_9b22_b0b0_b952;
let mut checksum = 0xcbf2_9ce4_8422_2325u64;
let mut feed = |v: u32| {
for byte in v.to_le_bytes() {
checksum ^= u64::from(byte);
checksum = checksum.wrapping_mul(0x100_0000_01b3);
}
};
for dv in &SHA1_DVS {
feed(dv.recompress_from as u32);
feed(dv.mask_bit as u32);
for word in dv.dm {
feed(word);
}
}
assert_eq!(checksum, EXPECTED, "DV table diverged from the C original");
}
#[test]
fn matches_c_reference() {
const C_NONZERO: u32 = 4767;
const C_CHECKSUM: u64 = 0x8c03_7397_6647_17a3;
let mut nonzero = 0u32;
let mut checksum = 0xcbf2_9ce4_8422_2325u64;
schedules(100_000, |w| {
let mask = ubc_check(w, false);
if mask != 0 {
nonzero += 1;
}
for byte in mask.to_le_bytes() {
checksum ^= u64::from(byte);
checksum = checksum.wrapping_mul(0x100_0000_01b3);
}
});
assert_eq!(nonzero, C_NONZERO, "flagged-block count diverged from C");
assert_eq!(checksum, C_CHECKSUM, "mask stream diverged from C");
}
#[cfg(feature = "std")]
mod properties {
use super::*;
use quickcheck::QuickCheck;
#[test]
fn forms_agree_on_arbitrary_words() {
fn prop(w: [u32; 80]) -> bool {
diverging_form(&Schedule::from_words(w)).is_none()
}
QuickCheck::new()
.tests(2_000)
.quickcheck(prop as fn([u32; 80]) -> bool);
}
#[test]
fn forms_agree_on_expanded_schedules() {
fn prop(m: [u32; 16]) -> bool {
diverging_form(&Schedule::expand(&m)).is_none()
}
QuickCheck::new()
.tests(20_000)
.quickcheck(prop as fn([u32; 16]) -> bool);
}
#[test]
fn scalar_only_gives_the_same_mask() {
fn prop(m: [u32; 16]) -> bool {
let w = Schedule::expand(&m);
ubc_check(&w, true) == ubc_check(&w, false)
}
QuickCheck::new()
.tests(2_000)
.quickcheck(prop as fn([u32; 16]) -> bool);
}
}
mod witness {
use super::conditions::CONDITIONS;
type Form = [u64; 8];
pub(super) type Forms = [[Form; 32]; 80];
fn xor(a: &Form, b: &Form) -> Form {
core::array::from_fn(|k| a[k] ^ b[k])
}
fn odd(a: &Form, x: &Form) -> u32 {
(0..8).fold(0, |p, k| p ^ (a[k] & x[k]).count_ones()) & 1
}
fn get(f: &Form, i: usize) -> u32 {
(f[i / 64] >> (i % 64)) as u32 & 1
}
pub(super) fn forms() -> Forms {
let mut f: Forms = [[[0; 8]; 32]; 80];
for (t, word) in f.iter_mut().enumerate().take(16) {
for (b, form) in word.iter_mut().enumerate() {
let unknown = t * 32 + b;
form[unknown / 64] = 1 << (unknown % 64);
}
}
for t in 16..80 {
for b in 0..32 {
let s = (b + 31) % 32;
f[t][b] = core::array::from_fn(|k| {
f[t - 3][s][k] ^ f[t - 8][s][k] ^ f[t - 14][s][k] ^ f[t - 16][s][k]
});
}
}
f
}
pub(super) fn message(forms: &Forms, dv: usize, seed: &mut u64) -> [u32; 16] {
let mut rows = [([0u64; 8], 0u32); 16];
let mut n = 0;
for &(i, a, j, b, c, dvs) in &CONDITIONS {
if dvs >> dv & 1 == 1 {
rows[n] = (
xor(
&forms[i as usize][a as usize],
&forms[j as usize][b as usize],
),
u32::from(c),
);
n += 1;
}
}
let mut pivots = [0usize; 16];
let mut rank = 0;
for col in 0..512 {
let Some(found) = (rank..n).find(|&k| get(&rows[k].0, col) == 1) else {
continue;
};
rows.swap(found, rank);
for k in 0..n {
if k != rank && get(&rows[k].0, col) == 1 {
let (coeff, rhs) = rows[rank];
rows[k].0 = xor(&rows[k].0, &coeff);
rows[k].1 ^= rhs;
}
}
pivots[rank] = col;
rank += 1;
if rank == n {
break;
}
}
for row in rows.iter().take(n).skip(rank) {
assert_eq!(row.1, 0, "conditions for DV {dv} are inconsistent");
}
let mut x = [0u64; 8];
for word in x.iter_mut() {
*seed ^= *seed << 13;
*seed ^= *seed >> 7;
*seed ^= *seed << 17;
*word = *seed;
}
for &col in pivots.iter().take(rank) {
x[col / 64] &= !(1 << (col % 64));
}
for k in 0..rank {
if odd(&rows[k].0, &x) != rows[k].1 {
let col = pivots[k];
x[col / 64] |= 1 << (col % 64);
}
}
core::array::from_fn(|t| (0..32).fold(0u32, |acc, b| acc | (get(&x, t * 32 + b) << b)))
}
}
#[test]
fn every_form_matches_scalar_where_the_tail_runs() {
const PER_DV: usize = 64;
let forms = witness::forms();
let mut seed = 0x243f_6a88_85a3_08d3u64;
let mut seen = 0u32;
for dv in 0..32 {
for _ in 0..PER_DV {
let w = Schedule::expand(&witness::message(&forms, dv, &mut seed));
let mask = ubc_check(&w, true);
assert_ne!(mask >> dv & 1, 0, "the witness for DV {dv} did not survive");
seen |= mask;
if let Some(form) = diverging_form(&w) {
panic!("{form} diverged on a witness for DV {dv}");
}
}
}
assert_eq!(seen, u32::MAX, "a DV was not covered");
}
}