Replace uses of `chunks_exact` with `as_chunks`
In the past we've been using `chunks_exact` because const generics were unstable but then, when they were stabilized we didn't use `as_chunks` (or `array_chunks`) since they were unstable. But the instability was only because Rust devs don't know how to handle `0` being passed in. The function is perfectly implementable on stable. (With a tiny, easy-to-understand `unsafe` block.) `core` doesn't want to make a decision for all other crates yet but we can make it for our own crates because we know that we simply never pass zero. (And even if we did, we could just change the decision.) It also turns out there's a hack to simulate `const {}` block in our MSRV, so we can make compilation fail early. This commit adds an extension trait to internals to provide the methods, so we no longer have to use `chunks_exact`. It also cleans up the code quite nicely.
This commit is contained in:
parent
a27a6b9592
commit
a013700527
|
@ -146,6 +146,7 @@ dependencies = [
|
||||||
name = "bitcoin_hashes"
|
name = "bitcoin_hashes"
|
||||||
version = "0.16.0"
|
version = "0.16.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitcoin-internals",
|
||||||
"hex-conservative 0.3.0",
|
"hex-conservative 0.3.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -145,6 +145,7 @@ dependencies = [
|
||||||
name = "bitcoin_hashes"
|
name = "bitcoin_hashes"
|
||||||
version = "0.16.0"
|
version = "0.16.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitcoin-internals",
|
||||||
"hex-conservative 0.3.0",
|
"hex-conservative 0.3.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -45,18 +45,17 @@ impl TaprootMerkleBranch {
|
||||||
/// The function returns an error if the number of bytes is not an integer multiple of 32 or
|
/// The function returns an error if the number of bytes is not an integer multiple of 32 or
|
||||||
/// if the number of hashes exceeds 128.
|
/// if the number of hashes exceeds 128.
|
||||||
pub fn decode(sl: &[u8]) -> Result<Self, TaprootError> {
|
pub fn decode(sl: &[u8]) -> Result<Self, TaprootError> {
|
||||||
if sl.len() % TAPROOT_CONTROL_NODE_SIZE != 0 {
|
use internals::slice::SliceExt;
|
||||||
|
let (node_hashes, remainder) = sl.bitcoin_as_chunks::<TAPROOT_CONTROL_NODE_SIZE>();
|
||||||
|
if !remainder.is_empty() {
|
||||||
Err(InvalidMerkleBranchSizeError(sl.len()).into())
|
Err(InvalidMerkleBranchSizeError(sl.len()).into())
|
||||||
} else if sl.len() > TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT {
|
} else if node_hashes.len() > TAPROOT_CONTROL_MAX_NODE_COUNT {
|
||||||
Err(InvalidMerkleTreeDepthError(sl.len() / TAPROOT_CONTROL_NODE_SIZE).into())
|
Err(InvalidMerkleTreeDepthError(sl.len() / TAPROOT_CONTROL_NODE_SIZE).into())
|
||||||
} else {
|
} else {
|
||||||
let inner = sl
|
let inner = node_hashes
|
||||||
.chunks_exact(TAPROOT_CONTROL_NODE_SIZE)
|
.iter()
|
||||||
.map(|chunk| {
|
.copied()
|
||||||
let bytes = <[u8; 32]>::try_from(chunk)
|
.map(TapNodeHash::from_byte_array)
|
||||||
.expect("chunks_exact always returns the correct size");
|
|
||||||
TapNodeHash::from_byte_array(bytes)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(TaprootMerkleBranch(inner))
|
Ok(TaprootMerkleBranch(inner))
|
||||||
|
|
|
@ -22,7 +22,7 @@ serde = ["dep:serde", "hex"]
|
||||||
small-hash = []
|
small-hash = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
internals = { package = "bitcoin-internals", version = "0.4.0" }
|
||||||
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true }
|
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true }
|
||||||
serde = { version = "1.0", default-features = false, optional = true }
|
serde = { version = "1.0", default-features = false, optional = true }
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// SPDX-License-Identifier: CC0-1.0
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
use internals::slice::SliceExt;
|
||||||
use super::{HashEngine, BLOCK_SIZE};
|
use super::{HashEngine, BLOCK_SIZE};
|
||||||
|
|
||||||
#[cfg(feature = "small-hash")]
|
#[cfg(feature = "small-hash")]
|
||||||
|
@ -132,8 +133,8 @@ impl HashEngine {
|
||||||
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
|
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
|
||||||
|
|
||||||
let mut w = [0u32; 16];
|
let mut w = [0u32; 16];
|
||||||
for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.chunks_exact(4)) {
|
for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.bitcoin_as_chunks().0) {
|
||||||
*w_val = u32::from_le_bytes(buff_bytes.try_into().expect("4 byte slice"))
|
*w_val = u32::from_le_bytes(*buff_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
process_block!(self.h, w,
|
process_block!(self.h, w,
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
//! RIPEMD160 implementation.
|
//! RIPEMD160 implementation.
|
||||||
|
|
||||||
|
use internals::slice::SliceExt;
|
||||||
|
|
||||||
#[cfg(bench)]
|
#[cfg(bench)]
|
||||||
mod benches;
|
mod benches;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
@ -68,8 +70,8 @@ impl HashEngine {
|
||||||
#[cfg(not(hashes_fuzz))]
|
#[cfg(not(hashes_fuzz))]
|
||||||
fn midstate(&self) -> [u8; 20] {
|
fn midstate(&self) -> [u8; 20] {
|
||||||
let mut ret = [0; 20];
|
let mut ret = [0; 20];
|
||||||
for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) {
|
for (val, ret_bytes) in self.h.iter().zip(ret.bitcoin_as_chunks_mut().0) {
|
||||||
ret_bytes.copy_from_slice(&(*val).to_le_bytes());
|
*ret_bytes = val.to_le_bytes();
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// SPDX-License-Identifier: CC0-1.0
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
use internals::slice::SliceExt;
|
||||||
use super::{HashEngine, BLOCK_SIZE};
|
use super::{HashEngine, BLOCK_SIZE};
|
||||||
|
|
||||||
impl HashEngine {
|
impl HashEngine {
|
||||||
|
@ -8,8 +9,8 @@ impl HashEngine {
|
||||||
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
|
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
|
||||||
|
|
||||||
let mut w = [0u32; 80];
|
let mut w = [0u32; 80];
|
||||||
for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.chunks_exact(4)) {
|
for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.bitcoin_as_chunks().0) {
|
||||||
*w_val = u32::from_be_bytes(buff_bytes.try_into().expect("4 bytes slice"))
|
*w_val = u32::from_be_bytes(*buff_bytes)
|
||||||
}
|
}
|
||||||
for i in 16..80 {
|
for i in 16..80 {
|
||||||
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
|
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
//! SHA1 implementation.
|
//! SHA1 implementation.
|
||||||
|
|
||||||
|
use internals::slice::SliceExt;
|
||||||
|
|
||||||
#[cfg(bench)]
|
#[cfg(bench)]
|
||||||
mod benches;
|
mod benches;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
@ -60,8 +62,8 @@ impl HashEngine {
|
||||||
#[cfg(not(hashes_fuzz))]
|
#[cfg(not(hashes_fuzz))]
|
||||||
pub(crate) fn midstate(&self) -> [u8; 20] {
|
pub(crate) fn midstate(&self) -> [u8; 20] {
|
||||||
let mut ret = [0; 20];
|
let mut ret = [0; 20];
|
||||||
for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) {
|
for (val, ret_bytes) in self.h.iter().zip(ret.bitcoin_as_chunks_mut().0) {
|
||||||
ret_bytes.copy_from_slice(&val.to_be_bytes())
|
*ret_bytes = val.to_be_bytes();
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use core::arch::x86::*;
|
||||||
#[cfg(all(feature = "std", target_arch = "x86_64"))]
|
#[cfg(all(feature = "std", target_arch = "x86_64"))]
|
||||||
use core::arch::x86_64::*;
|
use core::arch::x86_64::*;
|
||||||
|
|
||||||
|
use internals::slice::SliceExt;
|
||||||
use super::{HashEngine, Midstate, BLOCK_SIZE};
|
use super::{HashEngine, Midstate, BLOCK_SIZE};
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -526,8 +527,8 @@ impl HashEngine {
|
||||||
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
|
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
|
||||||
|
|
||||||
let mut w = [0u32; 16];
|
let mut w = [0u32; 16];
|
||||||
for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.chunks_exact(4)) {
|
for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.bitcoin_as_chunks().0) {
|
||||||
*w_val = u32::from_be_bytes(buff_bytes.try_into().expect("4 byte slice"));
|
*w_val = u32::from_be_bytes(*buff_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut a = self.h[0];
|
let mut a = self.h[0];
|
||||||
|
|
|
@ -9,6 +9,7 @@ mod crypto;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use core::{cmp, convert, fmt};
|
use core::{cmp, convert, fmt};
|
||||||
|
use internals::slice::SliceExt;
|
||||||
|
|
||||||
use crate::{incomplete_block_len, sha256d, HashEngine as _};
|
use crate::{incomplete_block_len, sha256d, HashEngine as _};
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
|
@ -79,8 +80,8 @@ impl HashEngine {
|
||||||
/// Please see docs on [`Midstate`] before using this function.
|
/// Please see docs on [`Midstate`] before using this function.
|
||||||
pub fn from_midstate(midstate: Midstate) -> HashEngine {
|
pub fn from_midstate(midstate: Midstate) -> HashEngine {
|
||||||
let mut ret = [0; 8];
|
let mut ret = [0; 8];
|
||||||
for (ret_val, midstate_bytes) in ret.iter_mut().zip(midstate.as_ref().chunks_exact(4)) {
|
for (ret_val, midstate_bytes) in ret.iter_mut().zip(midstate.as_ref().bitcoin_as_chunks().0) {
|
||||||
*ret_val = u32::from_be_bytes(midstate_bytes.try_into().expect("4 byte slice"));
|
*ret_val = u32::from_be_bytes(*midstate_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
HashEngine { buffer: [0; BLOCK_SIZE], h: ret, bytes_hashed: midstate.bytes_hashed }
|
HashEngine { buffer: [0; BLOCK_SIZE], h: ret, bytes_hashed: midstate.bytes_hashed }
|
||||||
|
@ -108,8 +109,8 @@ impl HashEngine {
|
||||||
#[cfg(not(hashes_fuzz))]
|
#[cfg(not(hashes_fuzz))]
|
||||||
fn midstate_unchecked(&self) -> Midstate {
|
fn midstate_unchecked(&self) -> Midstate {
|
||||||
let mut ret = [0; 32];
|
let mut ret = [0; 32];
|
||||||
for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(4)) {
|
for (val, ret_bytes) in self.h.iter().zip(ret.bitcoin_as_chunks_mut::<4>().0) {
|
||||||
ret_bytes.copy_from_slice(&val.to_be_bytes());
|
*ret_bytes = val.to_be_bytes();
|
||||||
}
|
}
|
||||||
Midstate { bytes: ret, bytes_hashed: self.bytes_hashed }
|
Midstate { bytes: ret, bytes_hashed: self.bytes_hashed }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// SPDX-License-Identifier: CC0-1.0
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
use internals::slice::SliceExt;
|
||||||
use super::{HashEngine, BLOCK_SIZE};
|
use super::{HashEngine, BLOCK_SIZE};
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -75,8 +76,8 @@ impl HashEngine {
|
||||||
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
|
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
|
||||||
|
|
||||||
let mut w = [0u64; 16];
|
let mut w = [0u64; 16];
|
||||||
for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.chunks_exact(8)) {
|
for (w_val, buff_bytes) in w.iter_mut().zip(self.buffer.bitcoin_as_chunks().0) {
|
||||||
*w_val = u64::from_be_bytes(buff_bytes.try_into().expect("8 byte slice"));
|
*w_val = u64::from_be_bytes(*buff_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut a = self.h[0];
|
let mut a = self.h[0];
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
//! SHA512 implementation.
|
//! SHA512 implementation.
|
||||||
|
|
||||||
|
use internals::slice::SliceExt;
|
||||||
|
|
||||||
#[cfg(bench)]
|
#[cfg(bench)]
|
||||||
mod benches;
|
mod benches;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
@ -79,8 +81,8 @@ impl HashEngine {
|
||||||
#[cfg(not(hashes_fuzz))]
|
#[cfg(not(hashes_fuzz))]
|
||||||
pub(crate) fn midstate(&self) -> [u8; 64] {
|
pub(crate) fn midstate(&self) -> [u8; 64] {
|
||||||
let mut ret = [0; 64];
|
let mut ret = [0; 64];
|
||||||
for (val, ret_bytes) in self.h.iter().zip(ret.chunks_exact_mut(8)) {
|
for (val, ret_bytes) in self.h.iter().zip(ret.bitcoin_as_chunks_mut().0) {
|
||||||
ret_bytes.copy_from_slice(&val.to_be_bytes());
|
*ret_bytes = val.to_be_bytes();
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ pub mod error;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
mod parse;
|
mod parse;
|
||||||
pub mod script;
|
pub mod script;
|
||||||
|
pub mod slice;
|
||||||
pub mod wrap_debug;
|
pub mod wrap_debug;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
//! Contains extensions related to slices.
|
||||||
|
|
||||||
|
/// Extension trait for slice.
|
||||||
|
pub trait SliceExt {
|
||||||
|
/// The item type the slice is storing.
|
||||||
|
type Item;
|
||||||
|
|
||||||
|
/// Splits up the slice into a slice of arrays and a remainder.
|
||||||
|
///
|
||||||
|
/// Note that `N` must not be zero:
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// let slice = [1, 2, 3];
|
||||||
|
/// let fail = slice.as_chunks::<0>();
|
||||||
|
/// ```
|
||||||
|
fn bitcoin_as_chunks<const N: usize>(&self) -> (&[[Self::Item; N]], &[Self::Item]);
|
||||||
|
|
||||||
|
/// Splits up the slice into a slice of arrays and a remainder.
|
||||||
|
///
|
||||||
|
/// Note that `N` must not be zero:
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// let mut slice = [1, 2, 3];
|
||||||
|
/// let fail = slice.as_chunks_mut::<0>();
|
||||||
|
/// ```
|
||||||
|
fn bitcoin_as_chunks_mut<const N: usize>(&mut self) -> (&mut [[Self::Item; N]], &mut [Self::Item]);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SliceExt for [T] {
|
||||||
|
type Item = T;
|
||||||
|
|
||||||
|
fn bitcoin_as_chunks<const N: usize>(&self) -> (&[[Self::Item; N]], &[Self::Item]) {
|
||||||
|
#[allow(clippy::let_unit_value)]
|
||||||
|
let _ = Hack::<N>::IS_NONZERO;
|
||||||
|
|
||||||
|
let chunks_count = self.len() / N;
|
||||||
|
let total_left_len = chunks_count * N;
|
||||||
|
let (left, right) = self.split_at(total_left_len);
|
||||||
|
// SAFETY: we've obtained the pointer from a slice that's still live
|
||||||
|
// we're merely casting, so no aliasing issues here
|
||||||
|
// arrays of T have same alignment as T
|
||||||
|
// the resulting slice points within the obtained slice as was computed above
|
||||||
|
let left = unsafe { core::slice::from_raw_parts(left.as_ptr().cast::<[Self::Item; N]>(), chunks_count) };
|
||||||
|
(left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bitcoin_as_chunks_mut<const N: usize>(&mut self) -> (&mut [[Self::Item; N]], &mut [Self::Item]) {
|
||||||
|
#[allow(clippy::let_unit_value)]
|
||||||
|
let _ = Hack::<N>::IS_NONZERO;
|
||||||
|
|
||||||
|
let chunks_count = self.len() / N;
|
||||||
|
let total_left_len = chunks_count * N;
|
||||||
|
let (left, right) = self.split_at_mut(total_left_len);
|
||||||
|
// SAFETY: we've obtained the pointer from a slice that's still live
|
||||||
|
// we're merely casting, so no aliasing issues here
|
||||||
|
// arrays of T have same alignment as T
|
||||||
|
// the resulting slice points within the obtained slice as was computed above
|
||||||
|
let left = unsafe { core::slice::from_raw_parts_mut(left.as_mut_ptr().cast::<[Self::Item; N]>(), chunks_count) };
|
||||||
|
(left, right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Hack<const N: usize>;
|
||||||
|
|
||||||
|
impl<const N: usize> Hack<N> {
|
||||||
|
const IS_NONZERO: () = { assert!(N != 0); };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::SliceExt;
|
||||||
|
|
||||||
|
// some comparisons require type annotations
|
||||||
|
const EMPTY: &[i32] = &[];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_to_one() {
|
||||||
|
let slice = [1];
|
||||||
|
let (left, right) = slice.bitcoin_as_chunks::<1>();
|
||||||
|
assert_eq!(left, &[[1]]);
|
||||||
|
assert_eq!(right, EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_to_two() {
|
||||||
|
const EMPTY_LEFT: &[[i32; 2]] = &[];
|
||||||
|
|
||||||
|
let slice = [1i32];
|
||||||
|
let (left, right) = slice.bitcoin_as_chunks::<2>();
|
||||||
|
assert_eq!(left, EMPTY_LEFT);
|
||||||
|
assert_eq!(right, &[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_to_one() {
|
||||||
|
let slice = [1, 2];
|
||||||
|
let (left, right) = slice.bitcoin_as_chunks::<1>();
|
||||||
|
assert_eq!(left, &[[1], [2]]);
|
||||||
|
assert_eq!(right, EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_to_two() {
|
||||||
|
let slice = [1, 2];
|
||||||
|
let (left, right) = slice.bitcoin_as_chunks::<2>();
|
||||||
|
assert_eq!(left, &[[1, 2]]);
|
||||||
|
assert_eq!(right, EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn three_to_two() {
|
||||||
|
let slice = [1, 2, 3];
|
||||||
|
let (left, right) = slice.bitcoin_as_chunks::<2>();
|
||||||
|
assert_eq!(left, &[[1, 2]]);
|
||||||
|
assert_eq!(right, &[3]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue