#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
mod avx2;
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
mod avx512;
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
mod avx512x2;
mod batch;
mod chain;
mod core;
mod lanes;
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
mod neon;
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
mod neon_sha2;
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
mod shani;
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
mod simd128;
pub use batch::Message;
use {
batch::{drive, drive_pairs, drive_slices},
lanes::Scalar,
};
pub fn lane_width() -> usize {
dispatch::lane_width()
}
pub fn backend() -> &'static str {
dispatch::backend()
}
pub fn hash_many(msgs: &[&[u8]], out: &mut [[u8; 32]]) {
dispatch::hash_slices(&[], msgs, out);
}
pub fn hash_many_prefixed(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
dispatch::hash_slices(prefix, bodies, out);
}
pub fn hash_pairs(prefix: &[u8], left: &[&[u8]], right: &[&[u8]], out: &mut [[u8; 32]]) {
dispatch::hash_pairs(prefix, left, right, out);
}
pub fn hash_messages(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
dispatch::hash(msgs, out);
}
pub fn hash_chain(seed: &[u8; 32], n: u64) -> [u8; 32] {
chain::hash_chain(seed, n)
}
pub fn hash_chains(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
assert_eq!(seeds.len(), lens.len());
assert_eq!(seeds.len(), out.len());
chain::hash_chains(seeds, lens, out);
}
pub fn chain_backend() -> &'static str {
chain::backend()
}
#[doc(hidden)]
pub mod backends {
use super::*;
pub(crate) fn scalar1(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
batch::hash_lanes::<Scalar<1>, 1>(msgs, out)
}
pub(crate) fn scalar8(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
batch::hash_lanes::<Scalar<8>, 8>(msgs, out)
}
pub fn portable8(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
unsafe { drive(8, scalar8, msgs, out) }
}
pub fn portable8_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
unsafe { drive_slices(8, scalar8, prefix, bodies, out) }
}
pub fn serial(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
unsafe { drive(1, scalar1, msgs, out) }
}
pub fn serial_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
unsafe { drive_slices(1, scalar1, prefix, bodies, out) }
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
pub fn neon4(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
unsafe { drive(4, crate::neon::group, msgs, out) }
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
pub fn neon4_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
unsafe { drive_slices(4, crate::neon::group, prefix, bodies, out) }
}
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
pub fn simd128_4(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
unsafe { drive(4, crate::simd128::group, msgs, out) }
}
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
pub fn simd128_4_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
unsafe { drive_slices(4, crate::simd128::group, prefix, bodies, out) }
}
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
pub fn simd128_8_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
unsafe { drive_slices(8, crate::simd128::group8, prefix, bodies, out) }
}
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
pub fn simd128_16_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
unsafe { drive_slices(16, crate::simd128::group16, prefix, bodies, out) }
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
pub unsafe fn neon_sha2x4(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
drive(
crate::neon_sha2::STREAMS,
crate::neon_sha2::group,
msgs,
out,
)
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
pub unsafe fn neon_sha2x4_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
drive_slices(
crate::neon_sha2::STREAMS,
crate::neon_sha2::group,
prefix,
bodies,
out,
)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn shani_x4(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
drive(crate::shani::STREAMS, crate::shani::group, msgs, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn shani_x4_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
drive_slices(
crate::shani::STREAMS,
crate::shani::group,
prefix,
bodies,
out,
)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx2_8(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
drive(8, crate::avx2::group, msgs, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx2_8_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
drive_slices(8, crate::avx2::group, prefix, bodies, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx512_16(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
drive(16, crate::avx512::group, msgs, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx512_16_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
drive_slices(16, crate::avx512::group, prefix, bodies, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx512_16x2(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
drive(crate::avx512x2::WIDTH, crate::avx512x2::group2, msgs, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx512_16x2_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
drive_slices(
crate::avx512x2::WIDTH,
crate::avx512x2::group2,
prefix,
bodies,
out,
)
}
pub fn chain_portable(seed: &[u8; 32], n: u64) -> [u8; 32] {
crate::chain::portable(seed, n)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn chain_shani(seed: &[u8; 32], n: u64) -> [u8; 32] {
crate::shani::chain(seed, n)
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
pub unsafe fn chain_neon_sha2(seed: &[u8; 32], n: u64) -> [u8; 32] {
crate::neon_sha2::chain(seed, n)
}
pub mod chains {
use crate::chain::run_scheduled;
pub fn portable1(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
unsafe { run_scheduled(1, crate::chain::steps_scalar1, seeds, lens, out) }
}
pub fn portable8(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
unsafe { run_scheduled(8, crate::chain::steps_scalar8, seeds, lens, out) }
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
pub unsafe fn neon_sha2x4(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
run_scheduled(4, crate::neon_sha2::steps4, seeds, lens, out)
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
pub unsafe fn neon_sha2x8(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
run_scheduled(8, crate::neon_sha2::steps8, seeds, lens, out)
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
pub unsafe fn neon4(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
run_scheduled(4, crate::neon::steps, seeds, lens, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn shani_x4(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
run_scheduled(4, crate::shani::steps4, seeds, lens, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx2_8(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
run_scheduled(8, crate::avx2::steps, seeds, lens, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx512_16(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
run_scheduled(16, crate::avx512::steps, seeds, lens, out)
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
pub unsafe fn avx512_16x2(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
run_scheduled(32, crate::avx512x2::steps2, seeds, lens, out)
}
}
}
mod dispatch {
use super::*;
#[allow(dead_code)]
const PORTABLE: Kernel = if cfg!(target_arch = "aarch64") {
Kernel::Portable8
} else {
Kernel::Portable1
};
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Kernel {
Portable1,
Portable8,
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Neon4,
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
NeonSha2x4,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
ShaNiX4,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Avx2_8,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Avx512_16,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Avx512_16x2,
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
Simd128_4,
}
impl Kernel {
pub(crate) fn name(self) -> &'static str {
match self {
Kernel::Portable1 => "portable-1",
Kernel::Portable8 => "portable-8",
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::Neon4 => "neon-4",
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2x4 => "neon-sha2-x4",
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNiX4 => "shani-x4",
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx2_8 => "avx2-8",
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx512_16 => "avx512-16",
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx512_16x2 => "avx512-2x16",
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
Kernel::Simd128_4 => "simd128-4",
}
}
pub(crate) fn width(self) -> usize {
match self {
Kernel::Portable1 => 1,
Kernel::Portable8 => 8,
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::Neon4 => 4,
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2x4 => crate::neon_sha2::STREAMS,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNiX4 => crate::shani::STREAMS,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx2_8 => 8,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx512_16 => 16,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx512_16x2 => crate::avx512x2::WIDTH,
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
Kernel::Simd128_4 => 4,
}
}
}
#[cfg(all(
target_arch = "x86_64",
not(any(feature = "scalar", feature = "avx2", feature = "avx512"))
))]
fn amd_family() -> u32 {
use std::sync::OnceLock;
static FAMILY: OnceLock<u32> = OnceLock::new();
*FAMILY.get_or_init(|| {
use std::arch::x86_64::__cpuid;
#[allow(unused_unsafe)]
let v = unsafe { __cpuid(0) };
if (v.ebx, v.edx, v.ecx) != (0x6874_7541, 0x6974_6e65, 0x444d_4163) {
return 0;
}
#[allow(unused_unsafe)]
let eax = unsafe { __cpuid(1) }.eax;
((eax >> 8) & 0xf) + ((eax >> 20) & 0xff)
})
}
#[inline]
#[allow(clippy::needless_return)]
pub(crate) fn select() -> Kernel {
#[cfg(feature = "scalar")]
return PORTABLE;
#[cfg(all(target_arch = "x86_64", feature = "avx512", not(feature = "scalar")))]
return Kernel::Avx512_16;
#[cfg(all(target_arch = "x86_64", feature = "avx2", not(feature = "scalar")))]
return Kernel::Avx2_8;
#[cfg(all(target_arch = "aarch64", feature = "neon", not(feature = "scalar")))]
return Kernel::Neon4;
#[cfg(not(any(
feature = "scalar",
all(target_arch = "x86_64", any(feature = "avx2", feature = "avx512")),
all(target_arch = "aarch64", feature = "neon"),
)))]
{
#[cfg(target_arch = "x86_64")]
{
use std::sync::OnceLock;
static KERNEL: OnceLock<Kernel> = OnceLock::new();
return *KERNEL.get_or_init(|| {
let sha = have_shani();
if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512bw") {
let fam = amd_family();
if fam == 0x1a {
return Kernel::Avx512_16x2;
}
if !(sha && fam == 0x19) {
return Kernel::Avx512_16;
}
}
if sha {
return Kernel::ShaNiX4;
}
if is_x86_feature_detected!("avx2") {
return Kernel::Avx2_8;
}
PORTABLE
});
}
#[cfg(target_arch = "aarch64")]
{
if std::arch::is_aarch64_feature_detected!("sha2") {
return Kernel::NeonSha2x4;
}
Kernel::Neon4
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
{
Kernel::Simd128_4
}
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
{
PORTABLE
}
}
}
}
pub(crate) fn hash(msgs: &[Message<'_>], out: &mut [[u8; 32]]) {
match select() {
Kernel::Portable1 => backends::serial(msgs, out),
Kernel::Portable8 => backends::portable8(msgs, out),
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::Neon4 => backends::neon4(msgs, out),
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2x4 => unsafe { backends::neon_sha2x4(msgs, out) },
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNiX4 => unsafe { backends::shani_x4(msgs, out) },
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx2_8 => unsafe { backends::avx2_8(msgs, out) },
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
k @ (Kernel::Avx512_16 | Kernel::Avx512_16x2) => unsafe {
let (w, group) = avx512_parts(k);
let cut = avx512_tail_cut(msgs.len());
let (head, tail) = msgs.split_at(cut);
let (out_head, out_tail) = out.split_at_mut(cut);
drive(w, group, head, out_head);
if !tail.is_empty() {
backends::shani_x4(tail, out_tail);
}
},
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
Kernel::Simd128_4 => backends::simd128_4(msgs, out),
}
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
#[inline]
pub(crate) fn have_shani() -> bool {
is_x86_feature_detected!("sha")
&& is_x86_feature_detected!("ssse3")
&& is_x86_feature_detected!("sse4.1")
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
#[inline]
fn avx512_tail_cut(len: usize) -> usize {
let rem = len % 16;
if (1..=12).contains(&rem) && have_shani() {
len - rem
} else {
len
}
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
fn avx512_parts(k: Kernel) -> (usize, batch::GroupFn) {
match k {
Kernel::Avx512_16x2 => (
crate::avx512x2::WIDTH,
crate::avx512x2::group2 as batch::GroupFn,
),
_ => (16, crate::avx512::group as batch::GroupFn),
}
}
pub(crate) fn hash_slices(prefix: &[u8], bodies: &[&[u8]], out: &mut [[u8; 32]]) {
match select() {
Kernel::Portable1 => backends::serial_slices(prefix, bodies, out),
Kernel::Portable8 => backends::portable8_slices(prefix, bodies, out),
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::Neon4 => backends::neon4_slices(prefix, bodies, out),
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2x4 => unsafe { backends::neon_sha2x4_slices(prefix, bodies, out) },
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNiX4 => unsafe { backends::shani_x4_slices(prefix, bodies, out) },
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx2_8 => unsafe { backends::avx2_8_slices(prefix, bodies, out) },
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
k @ (Kernel::Avx512_16 | Kernel::Avx512_16x2) => unsafe {
let (w, group) = avx512_parts(k);
let cut = avx512_tail_cut(bodies.len());
let (head, tail) = bodies.split_at(cut);
let (out_head, out_tail) = out.split_at_mut(cut);
drive_slices(w, group, prefix, head, out_head);
if !tail.is_empty() {
backends::shani_x4_slices(prefix, tail, out_tail);
}
},
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
Kernel::Simd128_4 => backends::simd128_4_slices(prefix, bodies, out),
}
}
pub(crate) fn hash_pairs(prefix: &[u8], left: &[&[u8]], right: &[&[u8]], out: &mut [[u8; 32]]) {
let k = select();
let width = k.width();
match k {
Kernel::Portable1 => unsafe {
drive_pairs(width, backends::scalar1, prefix, left, right, out)
},
Kernel::Portable8 => unsafe {
drive_pairs(width, backends::scalar8, prefix, left, right, out)
},
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::Neon4 => unsafe {
drive_pairs(width, crate::neon::group, prefix, left, right, out)
},
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2x4 => unsafe {
drive_pairs(width, crate::neon_sha2::group, prefix, left, right, out)
},
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNiX4 => unsafe {
drive_pairs(width, crate::shani::group, prefix, left, right, out)
},
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx2_8 => unsafe {
drive_pairs(width, crate::avx2::group, prefix, left, right, out)
},
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
k @ (Kernel::Avx512_16 | Kernel::Avx512_16x2) => unsafe {
let (w, group) = avx512_parts(k);
let cut = avx512_tail_cut(left.len());
drive_pairs(
w,
group,
prefix,
&left[..cut],
&right[..cut],
&mut out[..cut],
);
if cut < left.len() {
drive_pairs(
crate::shani::STREAMS,
crate::shani::group,
prefix,
&left[cut..],
&right[cut..],
&mut out[cut..],
);
}
},
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
Kernel::Simd128_4 => unsafe {
drive_pairs(width, crate::simd128::group, prefix, left, right, out)
},
}
}
pub(crate) fn lane_width() -> usize {
select().width()
}
pub(crate) fn backend() -> &'static str {
select().name()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_message() {
let mut out = [[0u8; 32]; 1];
hash_many(&[b""], &mut out);
let expect = hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
assert_eq!(out[0], expect);
}
#[test]
fn abc() {
let mut out = [[0u8; 32]; 1];
hash_many(&[b"abc"], &mut out);
let expect = hex("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
assert_eq!(out[0], expect);
}
#[test]
fn prefix_matches_concatenation() {
let prefix = b"\x00SOLANA_MERKLE_SHREDS_LEAF";
let bodies: Vec<Vec<u8>> = (0..8usize).map(|i| vec![i as u8; 100 + i * 37]).collect();
let body_refs: Vec<&[u8]> = bodies.iter().map(|b| b.as_slice()).collect();
let mut via_prefix = vec![[0u8; 32]; bodies.len()];
hash_many_prefixed(prefix, &body_refs, &mut via_prefix);
let joined: Vec<Vec<u8>> = bodies
.iter()
.map(|b| [prefix.as_slice(), b].concat())
.collect();
let joined_refs: Vec<&[u8]> = joined.iter().map(|b| b.as_slice()).collect();
let mut via_concat = vec![[0u8; 32]; bodies.len()];
hash_many(&joined_refs, &mut via_concat);
assert_eq!(via_prefix, via_concat);
}
fn hex(s: &str) -> [u8; 32] {
let mut out = [0u8; 32];
for (i, b) in out.iter_mut().enumerate() {
*b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).unwrap();
}
out
}
}