From f2c5f1955766953ddb8f76896a9edf83546198b4 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Fri, 11 Aug 2023 21:31:20 +0200 Subject: [PATCH] Introduce the `small-hash` feature for `bitcoin_hashes` When enabled this feature swaps the hash implementation of sha512, sha256 and ripemd160 for a smaller (but also slower) one. On embedded processors (Cortex-M4) it can lead to up to a 52% size reduction, from around 37KiB for just the `process_block` methods of the three hash functions to 17.8KiB. --- clippy.toml | 1 + hashes/Cargo.toml | 2 + hashes/src/ripemd160.rs | 25 ++++++++++++ hashes/src/sha256.rs | 86 ++++++++++++++++++++++++++++++++--------- hashes/src/sha512.rs | 83 ++++++++++++++++++++++++++++++--------- 5 files changed, 159 insertions(+), 38 deletions(-) diff --git a/clippy.toml b/clippy.toml index 11d46a73..6fbdb48f 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,2 @@ msrv = "1.48.0" +too-many-arguments-threshold = 13 diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml index 30313e14..44a2351f 100644 --- a/hashes/Cargo.toml +++ b/hashes/Cargo.toml @@ -19,6 +19,8 @@ alloc = ["internals/alloc", "hex/alloc"] serde-std = ["serde/std"] # If you want I/O you must enable either "std" or "core2". core2 = ["actual-core2", "hex/core2"] +# Smaller (but slower) implementation of sha256, sha512 and ripemd160 +small-hash = [] [package.metadata.docs.rs] all-features = true diff --git a/hashes/src/ripemd160.rs b/hashes/src/ripemd160.rs index f3b306a1..d97b3bba 100644 --- a/hashes/src/ripemd160.rs +++ b/hashes/src/ripemd160.rs @@ -90,6 +90,31 @@ impl crate::HashEngine for HashEngine { engine_input_impl!(); } +#[cfg(feature = "small-hash")] +#[macro_use] +mod small_hash { + #[rustfmt::skip] + pub(super) fn round(a: u32, _b: u32, c: u32, _d: u32, e: u32, + x: u32, bits: u32, add: u32, round: u32, + ) -> (u32, u32) { + let a = a.wrapping_add(round).wrapping_add(x).wrapping_add(add); + let a = a.rotate_left(bits).wrapping_add(e); + let c = c.rotate_left(10); + + (a, c) + } + + macro_rules! round( + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, + $x:expr, $bits:expr, $add:expr, $round:expr) => ({ + let updates = small_hash::round($a, $b, $c, $d, $e, $x, $bits, $add, $round); + $a = updates.0; + $c = updates.1; + }); + ); +} + +#[cfg(not(feature = "small-hash"))] macro_rules! round( ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $x:expr, $bits:expr, $add:expr, $round:expr) => ({ diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs index 5bafe883..339972f6 100644 --- a/hashes/src/sha256.rs +++ b/hashes/src/sha256.rs @@ -186,27 +186,75 @@ impl hex::FromHex for Midstate { } } -macro_rules! Ch( ($x:expr, $y:expr, $z:expr) => ($z ^ ($x & ($y ^ $z))) ); -macro_rules! Maj( ($x:expr, $y:expr, $z:expr) => (($x & $y) | ($z & ($x | $y))) ); -macro_rules! Sigma0( ($x:expr) => ($x.rotate_left(30) ^ $x.rotate_left(19) ^ $x.rotate_left(10)) ); -macro_rules! Sigma1( ($x:expr) => ( $x.rotate_left(26) ^ $x.rotate_left(21) ^ $x.rotate_left(7)) ); -macro_rules! sigma0( ($x:expr) => ($x.rotate_left(25) ^ $x.rotate_left(14) ^ ($x >> 3)) ); -macro_rules! sigma1( ($x:expr) => ($x.rotate_left(15) ^ $x.rotate_left(13) ^ ($x >> 10)) ); +#[allow(non_snake_case)] +const fn Ch(x: u32, y: u32, z: u32) -> u32 { z ^ (x & (y ^ z)) } +#[allow(non_snake_case)] +const fn Maj(x: u32, y: u32, z: u32) -> u32 { (x & y) | (z & (x | y)) } +#[allow(non_snake_case)] +const fn Sigma0(x: u32) -> u32 { x.rotate_left(30) ^ x.rotate_left(19) ^ x.rotate_left(10) } +#[allow(non_snake_case)] +const fn Sigma1(x: u32) -> u32 { x.rotate_left(26) ^ x.rotate_left(21) ^ x.rotate_left(7) } +const fn sigma0(x: u32) -> u32 { x.rotate_left(25) ^ x.rotate_left(14) ^ (x >> 3) } +const fn sigma1(x: u32) -> u32 { x.rotate_left(15) ^ x.rotate_left(13) ^ (x >> 10) } -macro_rules! round( - // first round - ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr) => ( - let t1 = $h.wrapping_add(Sigma1!($e)).wrapping_add(Ch!($e, $f, $g)).wrapping_add($k).wrapping_add($w); - let t2 = Sigma0!($a).wrapping_add(Maj!($a, $b, $c)); - $d = $d.wrapping_add(t1); - $h = t1.wrapping_add(t2); +#[cfg(feature = "small-hash")] +#[macro_use] +mod small_hash { + use super::*; + + #[rustfmt::skip] + pub(super) const fn round(a: u32, b: u32, c: u32, d: u32, e: u32, + f: u32, g: u32, h: u32, k: u32, w: u32) -> (u32, u32) { + let t1 = + h.wrapping_add(Sigma1(e)).wrapping_add(Ch(e, f, g)).wrapping_add(k).wrapping_add(w); + let t2 = Sigma0(a).wrapping_add(Maj(a, b, c)); + (d.wrapping_add(t1), t1.wrapping_add(t2)) + } + #[rustfmt::skip] + pub(super) const fn later_round(a: u32, b: u32, c: u32, d: u32, e: u32, + f: u32, g: u32, h: u32, k: u32, w: u32, + w1: u32, w2: u32, w3: u32, + ) -> (u32, u32, u32) { + let w = w.wrapping_add(sigma1(w1)).wrapping_add(w2).wrapping_add(sigma0(w3)); + let (d, h) = round(a, b, c, d, e, f, g, h, k, w); + (d, h, w) + } + + macro_rules! round( + // first round + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr) => ( + let updates = small_hash::round($a, $b, $c, $d, $e, $f, $g, $h, $k, $w); + $d = updates.0; + $h = updates.1; + ); + // later rounds we reassign $w before doing the first-round computation + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr, $w1:expr, $w2:expr, $w3:expr) => ( + let updates = small_hash::later_round($a, $b, $c, $d, $e, $f, $g, $h, $k, $w, $w1, $w2, $w3); + $d = updates.0; + $h = updates.1; + $w = updates.2; + ) ); - // later rounds we reassign $w before doing the first-round computation - ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr, $w1:expr, $w2:expr, $w3:expr) => ( - $w = $w.wrapping_add(sigma1!($w1)).wrapping_add($w2).wrapping_add(sigma0!($w3)); - round!($a, $b, $c, $d, $e, $f, $g, $h, $k, $w); - ) -); +} + +#[cfg(not(feature = "small-hash"))] +#[macro_use] +mod fast_hash { + macro_rules! round( + // first round + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr) => ( + let t1 = $h.wrapping_add(Sigma1($e)).wrapping_add(Ch($e, $f, $g)).wrapping_add($k).wrapping_add($w); + let t2 = Sigma0($a).wrapping_add(Maj($a, $b, $c)); + $d = $d.wrapping_add(t1); + $h = t1.wrapping_add(t2); + ); + // later rounds we reassign $w before doing the first-round computation + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr, $w1:expr, $w2:expr, $w3:expr) => ( + $w = $w.wrapping_add(sigma1($w1)).wrapping_add($w2).wrapping_add(sigma0($w3)); + round!($a, $b, $c, $d, $e, $f, $g, $h, $k, $w); + ) + ); +} impl Midstate { #[allow(clippy::identity_op)] // more readble diff --git a/hashes/src/sha512.rs b/hashes/src/sha512.rs index 034b3a1a..85a8aa4c 100644 --- a/hashes/src/sha512.rs +++ b/hashes/src/sha512.rs @@ -123,27 +123,72 @@ pub(crate) fn from_engine(e: HashEngine) -> Hash { Hash(hash) } -macro_rules! Ch( ($x:expr, $y:expr, $z:expr) => ($z ^ ($x & ($y ^ $z))) ); -macro_rules! Maj( ($x:expr, $y:expr, $z:expr) => (($x & $y) | ($z & ($x | $y))) ); -macro_rules! Sigma0( ($x:expr) => ($x.rotate_left(36) ^ $x.rotate_left(30) ^ $x.rotate_left(25)) ); -macro_rules! Sigma1( ($x:expr) => ($x.rotate_left(50) ^ $x.rotate_left(46) ^ $x.rotate_left(23)) ); -macro_rules! sigma0( ($x:expr) => ($x.rotate_left(63) ^ $x.rotate_left(56) ^ ($x >> 7)) ); -macro_rules! sigma1( ($x:expr) => ($x.rotate_left(45) ^ $x.rotate_left(3) ^ ($x >> 6)) ); +#[allow(non_snake_case)] +fn Ch(x: u64, y: u64, z: u64) -> u64 { z ^ (x & (y ^ z)) } +#[allow(non_snake_case)] +fn Maj(x: u64, y: u64, z: u64) -> u64 { (x & y) | (z & (x | y)) } +#[allow(non_snake_case)] +fn Sigma0(x: u64) -> u64 { x.rotate_left(36) ^ x.rotate_left(30) ^ x.rotate_left(25) } +#[allow(non_snake_case)] +fn Sigma1(x: u64) -> u64 { x.rotate_left(50) ^ x.rotate_left(46) ^ x.rotate_left(23) } +fn sigma0(x: u64) -> u64 { x.rotate_left(63) ^ x.rotate_left(56) ^ (x >> 7) } +fn sigma1(x: u64) -> u64 { x.rotate_left(45) ^ x.rotate_left(3) ^ (x >> 6) } -macro_rules! round( - // first round - ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr) => ( - let t1 = $h.wrapping_add(Sigma1!($e)).wrapping_add(Ch!($e, $f, $g)).wrapping_add($k).wrapping_add($w); - let t2 = Sigma0!($a).wrapping_add(Maj!($a, $b, $c)); - $d = $d.wrapping_add(t1); - $h = t1.wrapping_add(t2); +#[cfg(feature = "small-hash")] +#[macro_use] +mod small_hash { + use super::*; + + #[rustfmt::skip] + pub(super) fn round(a: u64, b: u64, c: u64, d: &mut u64, e: u64, + f: u64, g: u64, h: &mut u64, k: u64, w: u64, + ) { + let t1 = + h.wrapping_add(Sigma1(e)).wrapping_add(Ch(e, f, g)).wrapping_add(k).wrapping_add(w); + let t2 = Sigma0(a).wrapping_add(Maj(a, b, c)); + *d = d.wrapping_add(t1); + *h = t1.wrapping_add(t2); + } + #[rustfmt::skip] + pub(super) fn later_round(a: u64, b: u64, c: u64, d: &mut u64, e: u64, + f: u64, g: u64, h: &mut u64, k: u64, w: u64, + w1: u64, w2: u64, w3: u64, + ) -> u64 { + let w = w.wrapping_add(sigma1(w1)).wrapping_add(w2).wrapping_add(sigma0(w3)); + round(a, b, c, d, e, f, g, h, k, w); + w + } + + macro_rules! round( + // first round + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr) => ( + small_hash::round($a, $b, $c, &mut $d, $e, $f, $g, &mut $h, $k, $w) + ); + // later rounds we reassign $w before doing the first-round computation + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr, $w1:expr, $w2:expr, $w3:expr) => ( + $w = small_hash::later_round($a, $b, $c, &mut $d, $e, $f, $g, &mut $h, $k, $w, $w1, $w2, $w3) + ) ); - // later rounds we reassign $w before doing the first-round computation - ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr, $w1:expr, $w2:expr, $w3:expr) => ( - $w = $w.wrapping_add(sigma1!($w1)).wrapping_add($w2).wrapping_add(sigma0!($w3)); - round!($a, $b, $c, $d, $e, $f, $g, $h, $k, $w); - ) -); +} + +#[cfg(not(feature = "small-hash"))] +#[macro_use] +mod fast_hash { + macro_rules! round( + // first round + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr) => ( + let t1 = $h.wrapping_add(Sigma1($e)).wrapping_add(Ch($e, $f, $g)).wrapping_add($k).wrapping_add($w); + let t2 = Sigma0($a).wrapping_add(Maj($a, $b, $c)); + $d = $d.wrapping_add(t1); + $h = t1.wrapping_add(t2); + ); + // later rounds we reassign $w before doing the first-round computation + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr, $w1:expr, $w2:expr, $w3:expr) => ( + $w = $w.wrapping_add(sigma1($w1)).wrapping_add($w2).wrapping_add(sigma0($w3)); + round!($a, $b, $c, $d, $e, $f, $g, $h, $k, $w); + ) + ); +} impl HashEngine { // Algorithm copied from libsecp256k1