Merge rust-bitcoin/rust-bitcoin#1769: Implement computing SHA256 in const context

42d3ae05be Implement computing SHA256 in const context (Martin Habovstiak)

Pull request description:

  Computing hashes in const fn is useful for esily creating tags for `sha256t`. This adds `const fn` implementation for `sha256::Hash` and the algorithm for computing midstate of tagged hash in `const` context.

  Part of #1427

  I'd like to make sha256t evocative of the output after this which would also do the thing we want to do with #1427 (awkward language to bypass GH automatically closing it...) I'm thinking of this:

  ```rust
  sha256t_newtype_hash! {
  // optional attrs
  /*pub*/ struct TagName = "tag_str"; // weird syntax but I guess OK-ish?

  // optional attrs
  /*pub*/ struct HashName(_); // allowed to elide the type because it's implied; maybe also allow writing it?
  }
  ```

  What do you think? I was also thinking about this alternative:

  ```rust
  sha256t_newtype_hash! {
  // optional attrs
  /*pub*/ struct TagName;

  const TAG: Tag = from_str("tag_str"); // allows for having from_raw([...]) or even from_hex([...])

  // optional attrs
  /*pub*/ struct HashName(_); // allowed to elide the type because it's implied; maybe also allow writing it?
  }
  ```

  Which looks more natural but maybe too much boilerplate? Which one do you like? Any better ideas? Heads up sanket1729

  Also FYI tcharding this is the kind of post-1.48 change I was thinking about. :)

ACKs for top commit:
  apoelstra:
    ACK 42d3ae05be
  tcharding:
    ACK 42d3ae05be

Tree-SHA512: 437f3160e53104fcdf94c6e09ca6722ea2fe1032429b4701e578dded665cb9cab45cc68ee718b8925075c05dff231c0b898dc03949ef57b831666c68e38950a6
This commit is contained in:
Andrew Poelstra 2023-04-03 14:42:57 +00:00
commit cd9f706cb1
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 219 additions and 0 deletions

View File

@ -114,6 +114,11 @@ impl Hash {
pub fn hash_again(&self) -> sha256d::Hash { pub fn hash_again(&self) -> sha256d::Hash {
crate::Hash::from_byte_array(<Self as crate::Hash>::hash(&self.0).0) crate::Hash::from_byte_array(<Self as crate::Hash>::hash(&self.0).0)
} }
/// Computes hash from `bytes` in `const` context.
///
/// Warning: this function is inefficient. It should be only used in `const` context.
pub const fn const_hash(bytes: &[u8]) -> Self { Hash(Midstate::const_hash(bytes, true).0) }
} }
/// Output of the SHA256 hash function. /// Output of the SHA256 hash function.
@ -161,6 +166,23 @@ impl Midstate {
/// Unwraps the [`Midstate`] and returns the underlying byte array. /// Unwraps the [`Midstate`] and returns the underlying byte array.
pub fn to_byte_array(self) -> [u8; 32] { self.0 } pub fn to_byte_array(self) -> [u8; 32] { self.0 }
/// Creates midstate for tagged hashes.
///
/// Warning: this function is inefficient. It should be only used in `const` context.
///
/// Computes non-finalized hash of `sha256(tag) || sha256(tag)` for use in
/// [`sha256t`](super::sha256t). It's provided for use with [`sha256t`](crate::sha256t).
pub const fn hash_tag(tag: &[u8]) -> Self {
let hash = Hash::const_hash(tag);
let mut buf = [0u8; 64];
let mut i = 0usize;
while i < buf.len() {
buf[i] = hash.0[i % hash.0.len()];
i += 1;
}
Self::const_hash(&buf, false)
}
} }
impl hex::FromHex for Midstate { impl hex::FromHex for Midstate {
@ -195,6 +217,174 @@ macro_rules! round(
) )
); );
impl Midstate {
#[allow(clippy::identity_op)] // more readble
const fn read_u32(bytes: &[u8], index: usize) -> u32 {
((bytes[index + 0] as u32) << 24)
| ((bytes[index + 1] as u32) << 16)
| ((bytes[index + 2] as u32) << 8)
| ((bytes[index + 3] as u32) << 0)
}
const fn copy_w(bytes: &[u8], index: usize) -> [u32; 16] {
let mut w = [0u32; 16];
let mut i = 0;
while i < 16 {
w[i] = Self::read_u32(bytes, index + i * 4);
i += 1;
}
w
}
const fn const_hash(bytes: &[u8], finalize: bool) -> Self {
let mut state = [
0x6a09e667u32,
0xbb67ae85,
0x3c6ef372,
0xa54ff53a,
0x510e527f,
0x9b05688c,
0x1f83d9ab,
0x5be0cd19,
];
let num_chunks = (bytes.len() + 9 + 63) / 64;
let mut chunk = 0;
#[allow(clippy::precedence)]
while chunk < num_chunks {
if !finalize && chunk + 1 == num_chunks {
break;
}
let mut w = if chunk * 64 + 64 <= bytes.len() {
Self::copy_w(bytes, chunk * 64)
} else {
let mut buf = [0; 64];
let mut i = 0;
let offset = chunk * 64;
while offset + i < bytes.len() {
buf[i] = bytes[offset + i];
i += 1;
}
if (bytes.len() % 64 <= 64 - 9) || (chunk + 2 == num_chunks) {
buf[i] = 0x80;
}
#[allow(clippy::identity_op)] // more readble
#[allow(clippy::erasing_op)]
if chunk + 1 == num_chunks {
let bit_len = bytes.len() as u64 * 8;
buf[64 - 8] = ((bit_len >> 8 * 7) & 0xFF) as u8;
buf[64 - 7] = ((bit_len >> 8 * 6) & 0xFF) as u8;
buf[64 - 6] = ((bit_len >> 8 * 5) & 0xFF) as u8;
buf[64 - 5] = ((bit_len >> 8 * 4) & 0xFF) as u8;
buf[64 - 4] = ((bit_len >> 8 * 3) & 0xFF) as u8;
buf[64 - 3] = ((bit_len >> 8 * 2) & 0xFF) as u8;
buf[64 - 2] = ((bit_len >> 8 * 1) & 0xFF) as u8;
buf[64 - 1] = ((bit_len >> 8 * 0) & 0xFF) as u8;
}
Self::copy_w(&buf, 0)
};
chunk += 1;
let mut a = state[0];
let mut b = state[1];
let mut c = state[2];
let mut d = state[3];
let mut e = state[4];
let mut f = state[5];
let mut g = state[6];
let mut h = state[7];
round!(a, b, c, d, e, f, g, h, 0x428a2f98, w[0]);
round!(h, a, b, c, d, e, f, g, 0x71374491, w[1]);
round!(g, h, a, b, c, d, e, f, 0xb5c0fbcf, w[2]);
round!(f, g, h, a, b, c, d, e, 0xe9b5dba5, w[3]);
round!(e, f, g, h, a, b, c, d, 0x3956c25b, w[4]);
round!(d, e, f, g, h, a, b, c, 0x59f111f1, w[5]);
round!(c, d, e, f, g, h, a, b, 0x923f82a4, w[6]);
round!(b, c, d, e, f, g, h, a, 0xab1c5ed5, w[7]);
round!(a, b, c, d, e, f, g, h, 0xd807aa98, w[8]);
round!(h, a, b, c, d, e, f, g, 0x12835b01, w[9]);
round!(g, h, a, b, c, d, e, f, 0x243185be, w[10]);
round!(f, g, h, a, b, c, d, e, 0x550c7dc3, w[11]);
round!(e, f, g, h, a, b, c, d, 0x72be5d74, w[12]);
round!(d, e, f, g, h, a, b, c, 0x80deb1fe, w[13]);
round!(c, d, e, f, g, h, a, b, 0x9bdc06a7, w[14]);
round!(b, c, d, e, f, g, h, a, 0xc19bf174, w[15]);
round!(a, b, c, d, e, f, g, h, 0xe49b69c1, w[0], w[14], w[9], w[1]);
round!(h, a, b, c, d, e, f, g, 0xefbe4786, w[1], w[15], w[10], w[2]);
round!(g, h, a, b, c, d, e, f, 0x0fc19dc6, w[2], w[0], w[11], w[3]);
round!(f, g, h, a, b, c, d, e, 0x240ca1cc, w[3], w[1], w[12], w[4]);
round!(e, f, g, h, a, b, c, d, 0x2de92c6f, w[4], w[2], w[13], w[5]);
round!(d, e, f, g, h, a, b, c, 0x4a7484aa, w[5], w[3], w[14], w[6]);
round!(c, d, e, f, g, h, a, b, 0x5cb0a9dc, w[6], w[4], w[15], w[7]);
round!(b, c, d, e, f, g, h, a, 0x76f988da, w[7], w[5], w[0], w[8]);
round!(a, b, c, d, e, f, g, h, 0x983e5152, w[8], w[6], w[1], w[9]);
round!(h, a, b, c, d, e, f, g, 0xa831c66d, w[9], w[7], w[2], w[10]);
round!(g, h, a, b, c, d, e, f, 0xb00327c8, w[10], w[8], w[3], w[11]);
round!(f, g, h, a, b, c, d, e, 0xbf597fc7, w[11], w[9], w[4], w[12]);
round!(e, f, g, h, a, b, c, d, 0xc6e00bf3, w[12], w[10], w[5], w[13]);
round!(d, e, f, g, h, a, b, c, 0xd5a79147, w[13], w[11], w[6], w[14]);
round!(c, d, e, f, g, h, a, b, 0x06ca6351, w[14], w[12], w[7], w[15]);
round!(b, c, d, e, f, g, h, a, 0x14292967, w[15], w[13], w[8], w[0]);
round!(a, b, c, d, e, f, g, h, 0x27b70a85, w[0], w[14], w[9], w[1]);
round!(h, a, b, c, d, e, f, g, 0x2e1b2138, w[1], w[15], w[10], w[2]);
round!(g, h, a, b, c, d, e, f, 0x4d2c6dfc, w[2], w[0], w[11], w[3]);
round!(f, g, h, a, b, c, d, e, 0x53380d13, w[3], w[1], w[12], w[4]);
round!(e, f, g, h, a, b, c, d, 0x650a7354, w[4], w[2], w[13], w[5]);
round!(d, e, f, g, h, a, b, c, 0x766a0abb, w[5], w[3], w[14], w[6]);
round!(c, d, e, f, g, h, a, b, 0x81c2c92e, w[6], w[4], w[15], w[7]);
round!(b, c, d, e, f, g, h, a, 0x92722c85, w[7], w[5], w[0], w[8]);
round!(a, b, c, d, e, f, g, h, 0xa2bfe8a1, w[8], w[6], w[1], w[9]);
round!(h, a, b, c, d, e, f, g, 0xa81a664b, w[9], w[7], w[2], w[10]);
round!(g, h, a, b, c, d, e, f, 0xc24b8b70, w[10], w[8], w[3], w[11]);
round!(f, g, h, a, b, c, d, e, 0xc76c51a3, w[11], w[9], w[4], w[12]);
round!(e, f, g, h, a, b, c, d, 0xd192e819, w[12], w[10], w[5], w[13]);
round!(d, e, f, g, h, a, b, c, 0xd6990624, w[13], w[11], w[6], w[14]);
round!(c, d, e, f, g, h, a, b, 0xf40e3585, w[14], w[12], w[7], w[15]);
round!(b, c, d, e, f, g, h, a, 0x106aa070, w[15], w[13], w[8], w[0]);
round!(a, b, c, d, e, f, g, h, 0x19a4c116, w[0], w[14], w[9], w[1]);
round!(h, a, b, c, d, e, f, g, 0x1e376c08, w[1], w[15], w[10], w[2]);
round!(g, h, a, b, c, d, e, f, 0x2748774c, w[2], w[0], w[11], w[3]);
round!(f, g, h, a, b, c, d, e, 0x34b0bcb5, w[3], w[1], w[12], w[4]);
round!(e, f, g, h, a, b, c, d, 0x391c0cb3, w[4], w[2], w[13], w[5]);
round!(d, e, f, g, h, a, b, c, 0x4ed8aa4a, w[5], w[3], w[14], w[6]);
round!(c, d, e, f, g, h, a, b, 0x5b9cca4f, w[6], w[4], w[15], w[7]);
round!(b, c, d, e, f, g, h, a, 0x682e6ff3, w[7], w[5], w[0], w[8]);
round!(a, b, c, d, e, f, g, h, 0x748f82ee, w[8], w[6], w[1], w[9]);
round!(h, a, b, c, d, e, f, g, 0x78a5636f, w[9], w[7], w[2], w[10]);
round!(g, h, a, b, c, d, e, f, 0x84c87814, w[10], w[8], w[3], w[11]);
round!(f, g, h, a, b, c, d, e, 0x8cc70208, w[11], w[9], w[4], w[12]);
round!(e, f, g, h, a, b, c, d, 0x90befffa, w[12], w[10], w[5], w[13]);
round!(d, e, f, g, h, a, b, c, 0xa4506ceb, w[13], w[11], w[6], w[14]);
round!(c, d, e, f, g, h, a, b, 0xbef9a3f7, w[14], w[12], w[7], w[15]);
round!(b, c, d, e, f, g, h, a, 0xc67178f2, w[15], w[13], w[8], w[0]);
state[0] = state[0].wrapping_add(a);
state[1] = state[1].wrapping_add(b);
state[2] = state[2].wrapping_add(c);
state[3] = state[3].wrapping_add(d);
state[4] = state[4].wrapping_add(e);
state[5] = state[5].wrapping_add(f);
state[6] = state[6].wrapping_add(g);
state[7] = state[7].wrapping_add(h);
}
let mut output = [0u8; 32];
let mut i = 0;
#[allow(clippy::identity_op)] // more readble
while i < 8 {
output[i * 4 + 0] = (state[i + 0] >> 24) as u8;
output[i * 4 + 1] = (state[i + 0] >> 16) as u8;
output[i * 4 + 2] = (state[i + 0] >> 8) as u8;
output[i * 4 + 3] = (state[i + 0] >> 0) as u8;
i += 1;
}
Midstate(output)
}
}
impl HashEngine { impl HashEngine {
/// Create a new [`HashEngine`] from a [`Midstate`]. /// Create a new [`HashEngine`] from a [`Midstate`].
/// ///
@ -456,6 +646,35 @@ mod tests {
assert_eq!(hash, sha256::Hash(HASH_EXPECTED)); assert_eq!(hash, sha256::Hash(HASH_EXPECTED));
} }
#[test]
fn const_hash() {
assert_eq!(super::Hash::hash(&[]), super::Hash::const_hash(&[]));
let mut bytes = Vec::new();
for i in 0..256 {
bytes.push(i as u8);
assert_eq!(
super::Hash::hash(&bytes),
super::Hash::const_hash(&bytes),
"hashes don't match for length {}",
i + 1
);
}
}
#[test]
fn const_midstate() {
use super::Midstate;
assert_eq!(
Midstate::hash_tag(b"TapLeaf"),
Midstate([
156, 224, 228, 230, 124, 17, 108, 57, 56, 179, 202, 242, 195, 15, 80, 137, 211,
243, 147, 108, 71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201,
])
)
}
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[test] #[test]
fn sha256_serde() { fn sha256_serde() {