Skip to content

Commit 9b497dd

Browse files
authored
sha2: add soft-compact backend (#686)
The new backend results in a much smaller binary code (e.g. for SHA-256 on `thumbv7em-none-eabi` we get [~150 instructions][0] vs [~1680 instructions][1], for SHA-512 it's [~330][2] vs [~7950][3]) while being somewhat slower on non-embedded CPUs (e.g. for SHA-256 on my x86-64 laptop with `opt-level=s` it results in 289 MB/s vs 315 MB/s). [0]: https://rust.godbolt.org/z/c5cT5chGz [1]: https://rust.godbolt.org/z/rWMTqaxdc [2]: https://rust.godbolt.org/z/EKoxxdqrv [3]: https://rust.godbolt.org/z/Td81PxsdW
1 parent ec8989e commit 9b497dd

File tree

7 files changed

+153
-12
lines changed

7 files changed

+153
-12
lines changed

.github/workflows/sha2.yml

+15
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ jobs:
7979
- run: cargo test --all-features
8080
env:
8181
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft"
82+
- run: cargo test --all-features
83+
env:
84+
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft-compact"
8285

8386
# macOS tests
8487
macos:
@@ -102,6 +105,9 @@ jobs:
102105
- run: cargo test --all-features
103106
env:
104107
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft"
108+
- run: cargo test --all-features
109+
env:
110+
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft-compact"
105111

106112
# Windows tests
107113
windows:
@@ -126,6 +132,9 @@ jobs:
126132
- run: cargo test --all-features
127133
env:
128134
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft"
135+
- run: cargo test --all-features
136+
env:
137+
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft-compact"
129138

130139
# Cross-compiled tests
131140
cross:
@@ -174,6 +183,9 @@ jobs:
174183
- run: cross test --package sha2 --all-features --target riscv64gc-unknown-linux-gnu
175184
env:
176185
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft" -C target-feature=+zknh,+zbkb
186+
- run: cross test --package sha2 --all-features --target riscv64gc-unknown-linux-gnu
187+
env:
188+
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft-compact" -C target-feature=+zknh,+zbkb
177189
- run: cross test --package sha2 --all-features --target riscv64gc-unknown-linux-gnu
178190
env:
179191
RUSTFLAGS: -Dwarnings --cfg sha2_backend="riscv-zknh" -C target-feature=+zknh,+zbkb
@@ -193,6 +205,9 @@ jobs:
193205
- run: cargo build --all-features --target riscv32gc-unknown-linux-gnu -Z build-std
194206
env:
195207
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft" -C target-feature=+zknh,+zbkb
208+
- run: cargo build --all-features --target riscv32gc-unknown-linux-gnu -Z build-std
209+
env:
210+
RUSTFLAGS: -Dwarnings --cfg sha2_backend="soft-compact" -C target-feature=+zknh,+zbkb
196211
- run: cargo build --all-features --target riscv32gc-unknown-linux-gnu -Z build-std
197212
env:
198213
RUSTFLAGS: -Dwarnings --cfg sha2_backend="riscv-zknh" -C target-feature=+zknh,+zbkb

sha2/CHANGELOG.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Added
1515
- RISC-V scalar crypto extension support gated behind `sha2_backend = "riscv-zknh"` or
1616
`sha2_backend = "riscv-zknh-compact"` configuration flags ([#614])
17+
- `sha2_backend = "soft"` configuration flag ([#615])
18+
- `sha2_backend = "soft-compact"` configuration flag ([#686])
1719

1820
### Removed
1921
- `asm`, `asm-aarch64`, `loongarch64_asm`, and `compress` crate features ([#542])
20-
- `soft` crate feature. Replaced with `sha2_backend = "soft"` configuration flag ([#615])
22+
- `soft` crate feature ([#615])
23+
- `force-soft-compact` crate feature ([#686])
2124

2225
[#542]: https://github.com/RustCrypto/hashes/pull/542
2326
[#614]: https://github.com/RustCrypto/hashes/pull/614
2427
[#615]: https://github.com/RustCrypto/hashes/pull/615
2528
[#652]: https://github.com/RustCrypto/hashes/pull/652
29+
[#686]: https://github.com/RustCrypto/hashes/pull/686
30+
31+
## 0.10.9 (2025-04-30)
32+
### Added
33+
- `force-soft-compact` crate feature to enable compact software backend (backport of [#686]) [#687]
34+
35+
[#687]: https://github.com/RustCrypto/hashes/pull/687
2636

2737
## 0.10.8 (2023-09-26)
2838
### Added

sha2/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ oid = ["digest/oid"]
3636
[lints.rust.unexpected_cfgs]
3737
level = "warn"
3838
check-cfg = [
39-
'cfg(sha2_backend, values("soft", "riscv-zknh", "riscv-zknh-compact"))',
39+
'cfg(sha2_backend, values("soft", "soft-compact", "riscv-zknh", "riscv-zknh-compact"))',
4040
]
4141

4242
[package.metadata.docs.rs]

sha2/src/sha256.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ cfg_if::cfg_if! {
22
if #[cfg(sha2_backend = "soft")] {
33
mod soft;
44
use soft::compress;
5+
} else if #[cfg(sha2_backend = "soft-compact")] {
6+
mod soft_compact;
7+
use soft_compact::compress;
58
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
69
mod soft;
710
mod x86_shani;
@@ -39,11 +42,10 @@ cfg_if::cfg_if! {
3942
#[inline(always)]
4043
#[allow(dead_code)]
4144
fn to_u32s(block: &[u8; 64]) -> [u32; 16] {
42-
let mut res = [0u32; 16];
43-
for (src, dst) in block.chunks_exact(4).zip(res.iter_mut()) {
44-
*dst = u32::from_be_bytes(src.try_into().unwrap());
45-
}
46-
res
45+
core::array::from_fn(|i| {
46+
let chunk = block[4 * i..][..4].try_into().unwrap();
47+
u32::from_be_bytes(chunk)
48+
})
4749
}
4850

4951
/// Raw SHA-256 compression function.

sha2/src/sha256/soft_compact.rs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use crate::consts::K32;
2+
3+
fn compress_u32(state: &mut [u32; 8], block: [u32; 16]) {
4+
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
5+
6+
let mut w = [0; 64];
7+
w[..16].copy_from_slice(&block);
8+
9+
for i in 16..64 {
10+
let w15 = w[i - 15];
11+
let s0 = (w15.rotate_right(7)) ^ (w15.rotate_right(18)) ^ (w15 >> 3);
12+
let w2 = w[i - 2];
13+
let s1 = (w2.rotate_right(17)) ^ (w2.rotate_right(19)) ^ (w2 >> 10);
14+
w[i] = w[i - 16]
15+
.wrapping_add(s0)
16+
.wrapping_add(w[i - 7])
17+
.wrapping_add(s1);
18+
}
19+
20+
for i in 0..64 {
21+
let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
22+
let ch = (e & f) ^ ((!e) & g);
23+
let t1 = s1
24+
.wrapping_add(ch)
25+
.wrapping_add(K32[i])
26+
.wrapping_add(w[i])
27+
.wrapping_add(h);
28+
let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
29+
let maj = (a & b) ^ (a & c) ^ (b & c);
30+
let t2 = s0.wrapping_add(maj);
31+
32+
h = g;
33+
g = f;
34+
f = e;
35+
e = d.wrapping_add(t1);
36+
d = c;
37+
c = b;
38+
b = a;
39+
a = t1.wrapping_add(t2);
40+
}
41+
42+
state[0] = state[0].wrapping_add(a);
43+
state[1] = state[1].wrapping_add(b);
44+
state[2] = state[2].wrapping_add(c);
45+
state[3] = state[3].wrapping_add(d);
46+
state[4] = state[4].wrapping_add(e);
47+
state[5] = state[5].wrapping_add(f);
48+
state[6] = state[6].wrapping_add(g);
49+
state[7] = state[7].wrapping_add(h);
50+
}
51+
52+
pub fn compress(state: &mut [u32; 8], blocks: &[[u8; 64]]) {
53+
for block in blocks.iter() {
54+
compress_u32(state, super::to_u32s(block));
55+
}
56+
}

sha2/src/sha512.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ cfg_if::cfg_if! {
22
if #[cfg(sha2_backend = "soft")] {
33
mod soft;
44
use soft::compress;
5+
} else if #[cfg(sha2_backend = "soft-compact")] {
6+
mod soft_compact;
7+
use soft_compact::compress;
58
} else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
69
mod soft;
710
mod x86_avx2;
@@ -39,11 +42,10 @@ cfg_if::cfg_if! {
3942
#[inline(always)]
4043
#[allow(dead_code)]
4144
fn to_u64s(block: &[u8; 128]) -> [u64; 16] {
42-
let mut res = [0u64; 16];
43-
for (src, dst) in block.chunks_exact(8).zip(res.iter_mut()) {
44-
*dst = u64::from_be_bytes(src.try_into().unwrap());
45-
}
46-
res
45+
core::array::from_fn(|i| {
46+
let chunk = block[8 * i..][..8].try_into().unwrap();
47+
u64::from_be_bytes(chunk)
48+
})
4749
}
4850

4951
/// Raw SHA-512 compression function.

sha2/src/sha512/soft_compact.rs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use crate::consts::K64;
2+
3+
fn compress_u64(state: &mut [u64; 8], block: [u64; 16]) {
4+
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
5+
6+
let mut w = [0; 80];
7+
w[..16].copy_from_slice(&block);
8+
9+
for i in 16..80 {
10+
let w15 = w[i - 15];
11+
let s0 = (w15.rotate_right(1)) ^ (w15.rotate_right(8)) ^ (w15 >> 7);
12+
let w2 = w[i - 2];
13+
let s1 = (w2.rotate_right(19)) ^ (w2.rotate_right(61)) ^ (w2 >> 6);
14+
w[i] = w[i - 16]
15+
.wrapping_add(s0)
16+
.wrapping_add(w[i - 7])
17+
.wrapping_add(s1);
18+
}
19+
20+
for i in 0..80 {
21+
let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
22+
let ch = (e & f) ^ ((!e) & g);
23+
let t1 = s1
24+
.wrapping_add(ch)
25+
.wrapping_add(K64[i])
26+
.wrapping_add(w[i])
27+
.wrapping_add(h);
28+
let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
29+
let maj = (a & b) ^ (a & c) ^ (b & c);
30+
let t2 = s0.wrapping_add(maj);
31+
32+
h = g;
33+
g = f;
34+
f = e;
35+
e = d.wrapping_add(t1);
36+
d = c;
37+
c = b;
38+
b = a;
39+
a = t1.wrapping_add(t2);
40+
}
41+
42+
state[0] = state[0].wrapping_add(a);
43+
state[1] = state[1].wrapping_add(b);
44+
state[2] = state[2].wrapping_add(c);
45+
state[3] = state[3].wrapping_add(d);
46+
state[4] = state[4].wrapping_add(e);
47+
state[5] = state[5].wrapping_add(f);
48+
state[6] = state[6].wrapping_add(g);
49+
state[7] = state[7].wrapping_add(h);
50+
}
51+
52+
pub fn compress(state: &mut [u64; 8], blocks: &[[u8; 128]]) {
53+
for block in blocks.iter() {
54+
compress_u64(state, super::to_u64s(block));
55+
}
56+
}

0 commit comments

Comments
 (0)