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.
This commit is contained in:
Alekos Filini 2023-08-11 21:31:20 +02:00
parent 29af523beb
commit f2c5f19557
No known key found for this signature in database
GPG Key ID: 431401E4A4530061
5 changed files with 159 additions and 38 deletions

View File

@ -1 +1,2 @@
msrv = "1.48.0" msrv = "1.48.0"
too-many-arguments-threshold = 13

View File

@ -19,6 +19,8 @@ alloc = ["internals/alloc", "hex/alloc"]
serde-std = ["serde/std"] serde-std = ["serde/std"]
# If you want I/O you must enable either "std" or "core2". # If you want I/O you must enable either "std" or "core2".
core2 = ["actual-core2", "hex/core2"] core2 = ["actual-core2", "hex/core2"]
# Smaller (but slower) implementation of sha256, sha512 and ripemd160
small-hash = []
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -90,6 +90,31 @@ impl crate::HashEngine for HashEngine {
engine_input_impl!(); 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( macro_rules! round(
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr,
$x:expr, $bits:expr, $add:expr, $round:expr) => ({ $x:expr, $bits:expr, $add:expr, $round:expr) => ({

View File

@ -186,27 +186,75 @@ impl hex::FromHex for Midstate {
} }
} }
macro_rules! Ch( ($x:expr, $y:expr, $z:expr) => ($z ^ ($x & ($y ^ $z))) ); #[allow(non_snake_case)]
macro_rules! Maj( ($x:expr, $y:expr, $z:expr) => (($x & $y) | ($z & ($x | $y))) ); const fn Ch(x: u32, y: u32, z: u32) -> u32 { z ^ (x & (y ^ z)) }
macro_rules! Sigma0( ($x:expr) => ($x.rotate_left(30) ^ $x.rotate_left(19) ^ $x.rotate_left(10)) ); #[allow(non_snake_case)]
macro_rules! Sigma1( ($x:expr) => ( $x.rotate_left(26) ^ $x.rotate_left(21) ^ $x.rotate_left(7)) ); const fn Maj(x: u32, y: u32, z: u32) -> u32 { (x & y) | (z & (x | y)) }
macro_rules! sigma0( ($x:expr) => ($x.rotate_left(25) ^ $x.rotate_left(14) ^ ($x >> 3)) ); #[allow(non_snake_case)]
macro_rules! sigma1( ($x:expr) => ($x.rotate_left(15) ^ $x.rotate_left(13) ^ ($x >> 10)) ); 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( #[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 // first round
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr) => ( ($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 updates = small_hash::round($a, $b, $c, $d, $e, $f, $g, $h, $k, $w);
let t2 = Sigma0!($a).wrapping_add(Maj!($a, $b, $c)); $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;
)
);
}
#[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); $d = $d.wrapping_add(t1);
$h = t1.wrapping_add(t2); $h = t1.wrapping_add(t2);
); );
// later rounds we reassign $w before doing the first-round computation // 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) => ( ($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)); $w = $w.wrapping_add(sigma1($w1)).wrapping_add($w2).wrapping_add(sigma0($w3));
round!($a, $b, $c, $d, $e, $f, $g, $h, $k, $w); round!($a, $b, $c, $d, $e, $f, $g, $h, $k, $w);
) )
); );
}
impl Midstate { impl Midstate {
#[allow(clippy::identity_op)] // more readble #[allow(clippy::identity_op)] // more readble

View File

@ -123,27 +123,72 @@ pub(crate) fn from_engine(e: HashEngine) -> Hash {
Hash(hash) Hash(hash)
} }
macro_rules! Ch( ($x:expr, $y:expr, $z:expr) => ($z ^ ($x & ($y ^ $z))) ); #[allow(non_snake_case)]
macro_rules! Maj( ($x:expr, $y:expr, $z:expr) => (($x & $y) | ($z & ($x | $y))) ); fn Ch(x: u64, y: u64, z: u64) -> u64 { z ^ (x & (y ^ z)) }
macro_rules! Sigma0( ($x:expr) => ($x.rotate_left(36) ^ $x.rotate_left(30) ^ $x.rotate_left(25)) ); #[allow(non_snake_case)]
macro_rules! Sigma1( ($x:expr) => ($x.rotate_left(50) ^ $x.rotate_left(46) ^ $x.rotate_left(23)) ); fn Maj(x: u64, y: u64, z: u64) -> u64 { (x & y) | (z & (x | y)) }
macro_rules! sigma0( ($x:expr) => ($x.rotate_left(63) ^ $x.rotate_left(56) ^ ($x >> 7)) ); #[allow(non_snake_case)]
macro_rules! sigma1( ($x:expr) => ($x.rotate_left(45) ^ $x.rotate_left(3) ^ ($x >> 6)) ); 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( #[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 // first round
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $k:expr, $w:expr) => ( ($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); small_hash::round($a, $b, $c, &mut $d, $e, $f, $g, &mut $h, $k, $w)
let t2 = Sigma0!($a).wrapping_add(Maj!($a, $b, $c)); );
// 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)
)
);
}
#[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); $d = $d.wrapping_add(t1);
$h = t1.wrapping_add(t2); $h = t1.wrapping_add(t2);
); );
// later rounds we reassign $w before doing the first-round computation // 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) => ( ($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)); $w = $w.wrapping_add(sigma1($w1)).wrapping_add($w2).wrapping_add(sigma0($w3));
round!($a, $b, $c, $d, $e, $f, $g, $h, $k, $w); round!($a, $b, $c, $d, $e, $f, $g, $h, $k, $w);
) )
); );
}
impl HashEngine { impl HashEngine {
// Algorithm copied from libsecp256k1 // Algorithm copied from libsecp256k1