use crate::sha3::Sponge;
use ic_core::traits::Algorithm;
pub const MAX_ENCODE: usize = 9;
pub fn left_encode(x: u64, buf: &mut [u8; MAX_ENCODE]) -> usize {
let n = value_bytes(x);
buf[0] = n as u8;
for i in 0..n {
buf[1 + i] = (x >> (8 * (n - 1 - i))) as u8;
}
n + 1
}
pub fn right_encode(x: u64, buf: &mut [u8; MAX_ENCODE]) -> usize {
let n = value_bytes(x);
for (i, slot) in buf.iter_mut().take(n).enumerate() {
*slot = (x >> (8 * (n - 1 - i))) as u8;
}
buf[n] = n as u8;
n + 1
}
fn value_bytes(x: u64) -> usize {
if x == 0 {
1
} else {
8 - (x.leading_zeros() / 8) as usize
}
}
pub(crate) struct CountingAbsorb<'a> {
sponge: &'a mut Sponge,
written: usize,
}
impl<'a> CountingAbsorb<'a> {
pub(crate) fn new(sponge: &'a mut Sponge) -> Self {
Self { sponge, written: 0 }
}
pub(crate) fn feed(&mut self, data: &[u8]) {
self.sponge.absorb(data);
self.written += data.len();
}
pub(crate) fn feed_encoded_string(&mut self, s: &[u8]) {
let mut buf = [0u8; MAX_ENCODE];
let n = left_encode((s.len() as u64) * 8, &mut buf);
self.feed(&buf[..n]);
self.feed(s);
}
pub(crate) fn finish_bytepad(self, w: usize) {
let remainder = self.written % w;
if remainder != 0 {
let zeros = [0u8; 168];
let mut left = w - remainder;
while left > 0 {
let chunk = core::cmp::min(left, zeros.len());
self.sponge.absorb(&zeros[..chunk]);
left -= chunk;
}
}
}
}
macro_rules! cshake {
($name:ident, $id:literal, $disp:literal, $rate:literal, $doc:literal) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name {
sponge: Sponge,
}
impl Algorithm for $name {
const ID: &'static str = $id;
const NAME: &'static str = $disp;
}
impl $name {
pub const RATE: usize = $rate;
pub fn new(n: &[u8], s: &[u8]) -> Self {
if n.is_empty() && s.is_empty() {
return Self {
sponge: Sponge::new($rate, 0x1f),
};
}
let mut sponge = Sponge::new($rate, 0x04);
let mut prefix = CountingAbsorb::new(&mut sponge);
let mut buf = [0u8; MAX_ENCODE];
let used = left_encode($rate as u64, &mut buf);
prefix.feed(&buf[..used]);
prefix.feed_encoded_string(n);
prefix.feed_encoded_string(s);
prefix.finish_bytepad($rate);
Self { sponge }
}
pub fn update(&mut self, data: &[u8]) {
self.sponge.absorb(data);
}
pub fn finalize_xof(mut self, out: &mut [u8]) {
self.sponge.finish();
self.sponge.squeeze(out);
}
pub fn xof(n: &[u8], s: &[u8], data: &[u8], out: &mut [u8]) {
let mut x = Self::new(n, s);
x.update(data);
x.finalize_xof(out);
}
pub fn absorb_bytepadded_string(&mut self, s: &[u8]) {
let mut pad = CountingAbsorb::new(&mut self.sponge);
let mut buf = [0u8; MAX_ENCODE];
let used = left_encode($rate as u64, &mut buf);
pad.feed(&buf[..used]);
pad.feed_encoded_string(s);
pad.finish_bytepad($rate);
}
}
};
}
macro_rules! cshake_self_test {
($name:ident, $shake:ty, $id:literal) => {
impl ic_core::traits::SelfTest for $name {
fn self_test() -> ic_core::Result<()> {
let mut plain = [0u8; 32];
let mut shake = [0u8; 32];
Self::xof(b"", b"", b"abc", &mut plain);
<$shake>::xof(b"abc", &mut shake);
ic_core::ensure!(ic_core::ct::verify(&plain, &shake), SelfTestFailed, $id);
let mut customized = [0u8; 32];
Self::xof(b"", b"self-test", b"abc", &mut customized);
ic_core::ensure!(
!ic_core::ct::verify(&plain, &customized),
SelfTestFailed,
$id
);
Ok(())
}
}
};
}
cshake!(
CShake128,
"cshake128",
"cSHAKE128",
168,
"SP 800-185 cSHAKE128: SHAKE128 with a customization string."
);
cshake!(
CShake256,
"cshake256",
"cSHAKE256",
136,
"SP 800-185 cSHAKE256: SHAKE256 with a customization string."
);
cshake_self_test!(CShake128, crate::Shake128, "cshake128");
cshake_self_test!(CShake256, crate::Shake256, "cshake256");
macro_rules! tuple_hash {
($name:ident, $cshake:ty, $id:literal, $disp:literal, $doc:literal) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name {
inner: $cshake,
}
impl Algorithm for $name {
const ID: &'static str = $id;
const NAME: &'static str = $disp;
}
impl $name {
pub fn new(custom: &[u8]) -> Self {
Self {
inner: <$cshake>::new(b"TupleHash", custom),
}
}
pub fn update(&mut self, element: &[u8]) {
let mut buf = [0u8; MAX_ENCODE];
let used = left_encode((element.len() as u64) * 8, &mut buf);
self.inner.update(&buf[..used]);
self.inner.update(element);
}
pub fn finalize(mut self, out: &mut [u8]) {
let mut buf = [0u8; MAX_ENCODE];
let used = right_encode((out.len() as u64) * 8, &mut buf);
self.inner.update(&buf[..used]);
self.inner.finalize_xof(out);
}
pub fn finalize_xof(mut self, out: &mut [u8]) {
let mut buf = [0u8; MAX_ENCODE];
let used = right_encode(0, &mut buf);
self.inner.update(&buf[..used]);
self.inner.finalize_xof(out);
}
pub fn hash(custom: &[u8], elements: &[&[u8]], out: &mut [u8]) {
let mut t = Self::new(custom);
for element in elements {
t.update(element);
}
t.finalize(out);
}
pub fn hash_xof(custom: &[u8], elements: &[&[u8]], out: &mut [u8]) {
let mut t = Self::new(custom);
for element in elements {
t.update(element);
}
t.finalize_xof(out);
}
}
impl ic_core::traits::SelfTest for $name {
fn self_test() -> ic_core::Result<()> {
let mut a = [0u8; 32];
let mut b = [0u8; 32];
Self::hash(b"self-test", &[b"abc", b"d"], &mut a);
Self::hash(b"self-test", &[b"ab", b"cd"], &mut b);
ic_core::ensure!(!ic_core::ct::verify(&a, &b), SelfTestFailed, $id);
let mut again = [0u8; 32];
Self::hash(b"self-test", &[b"abc", b"d"], &mut again);
ic_core::ensure!(ic_core::ct::verify(&a, &again), SelfTestFailed, $id);
Ok(())
}
}
};
}
tuple_hash!(
TupleHash128,
CShake128,
"tuplehash128",
"TupleHash128",
"SP 800-185 TupleHash128: hashes a *sequence* of strings unambiguously.\n\
\n\
Hashing `a || b` cannot distinguish `(\"abc\", \"d\")` from `(\"ab\", \"cd\")`,\n\
and a protocol that concatenates fields before hashing them has a\n\
forgery waiting in it. TupleHash length-prefixes every element, so\n\
distinct tuples always hash distinctly."
);
tuple_hash!(
TupleHash256,
CShake256,
"tuplehash256",
"TupleHash256",
"SP 800-185 TupleHash256, at the 256-bit security level."
);
macro_rules! parallel_hash {
($name:ident, $cshake:ty, $chain:literal, $id:literal, $disp:literal, $doc:literal) => {
#[doc = $doc]
pub struct $name;
impl Algorithm for $name {
const ID: &'static str = $id;
const NAME: &'static str = $disp;
}
impl $name {
pub const CHAINING_LEN: usize = $chain;
pub fn hash(custom: &[u8], block_size: usize, data: &[u8], out: &mut [u8]) {
Self::run(custom, block_size, data, out, false)
}
pub fn hash_xof(custom: &[u8], block_size: usize, data: &[u8], out: &mut [u8]) {
Self::run(custom, block_size, data, out, true)
}
fn run(custom: &[u8], block_size: usize, data: &[u8], out: &mut [u8], xof: bool) {
assert!(block_size > 0, "parallelhash block size must be positive");
let mut outer = <$cshake>::new(b"ParallelHash", custom);
let mut buf = [0u8; MAX_ENCODE];
let used = left_encode(block_size as u64, &mut buf);
outer.update(&buf[..used]);
let mut blocks = 0u64;
for block in data.chunks(block_size) {
let mut chain = [0u8; $chain];
<$cshake>::xof(b"", b"", block, &mut chain);
outer.update(&chain);
blocks += 1;
}
let used = right_encode(blocks, &mut buf);
outer.update(&buf[..used]);
let used = right_encode(if xof { 0 } else { (out.len() as u64) * 8 }, &mut buf);
outer.update(&buf[..used]);
outer.finalize_xof(out);
}
}
impl ic_core::traits::SelfTest for $name {
fn self_test() -> ic_core::Result<()> {
let data = [0x5au8; 200];
let mut a = [0u8; 32];
let mut b = [0u8; 32];
Self::hash(b"self-test", 16, &data, &mut a);
Self::hash(b"self-test", 32, &data, &mut b);
ic_core::ensure!(!ic_core::ct::verify(&a, &b), SelfTestFailed, $id);
let mut again = [0u8; 32];
Self::hash(b"self-test", 16, &data, &mut again);
ic_core::ensure!(ic_core::ct::verify(&a, &again), SelfTestFailed, $id);
Ok(())
}
}
};
}
parallel_hash!(
ParallelHash128,
CShake128,
32,
"parallelhash128",
"ParallelHash128",
"SP 800-185 ParallelHash128: hashes fixed-size blocks independently, then\n\
hashes their digests.\n\
\n\
The structure is designed so the per-block work can be spread across\n\
cores. This implementation does them in order — the workspace has no\n\
threading and `no_std` targets have no threads to spread onto — so what\n\
it buys here is interoperability with implementations that do, not\n\
speed. The output is identical either way."
);
parallel_hash!(
ParallelHash256,
CShake256,
64,
"parallelhash256",
"ParallelHash256",
"SP 800-185 ParallelHash256, at the 256-bit security level."
);
#[cfg(test)]
mod tests {
use super::*;
use crate::{Sha3_256, Shake128, Shake256};
use ic_core::codec::hex;
use ic_core::traits::Digest;
fn reference_left_encode(x: u64) -> Vec<u8> {
let mut bytes = x.to_be_bytes().to_vec();
while bytes.len() > 1 && bytes[0] == 0 {
bytes.remove(0);
}
let mut out = vec![bytes.len() as u8];
out.extend_from_slice(&bytes);
out
}
fn reference_right_encode(x: u64) -> Vec<u8> {
let mut bytes = x.to_be_bytes().to_vec();
while bytes.len() > 1 && bytes[0] == 0 {
bytes.remove(0);
}
let n = bytes.len() as u8;
bytes.push(n);
bytes
}
#[test]
fn the_encodings_match_an_independent_construction() {
for x in [
0u64,
1,
2,
127,
128,
255,
256,
65535,
65536,
1 << 24,
u32::MAX as u64,
u64::MAX,
] {
let mut buf = [0u8; MAX_ENCODE];
let n = left_encode(x, &mut buf);
assert_eq!(&buf[..n], &reference_left_encode(x)[..], "left_encode({x})");
let n = right_encode(x, &mut buf);
assert_eq!(
&buf[..n],
&reference_right_encode(x)[..],
"right_encode({x})"
);
}
}
#[test]
fn the_encodings_match_the_published_examples() {
let mut buf = [0u8; MAX_ENCODE];
let n = left_encode(0, &mut buf);
assert_eq!(&buf[..n], &[0x01, 0x00]);
let n = right_encode(0, &mut buf);
assert_eq!(&buf[..n], &[0x00, 0x01]);
let n = left_encode(1, &mut buf);
assert_eq!(&buf[..n], &[0x01, 0x01]);
let n = right_encode(1, &mut buf);
assert_eq!(&buf[..n], &[0x01, 0x01]);
let n = left_encode(256, &mut buf);
assert_eq!(&buf[..n], &[0x02, 0x01, 0x00]);
let n = right_encode(256, &mut buf);
assert_eq!(&buf[..n], &[0x01, 0x00, 0x02]);
}
#[allow(clippy::needless_range_loop)]
fn reference_keccak(lanes: &mut [[u64; 5]; 5]) {
const RC: [u64; 24] = [
0x0000000000000001,
0x0000000000008082,
0x800000000000808a,
0x8000000080008000,
0x000000000000808b,
0x0000000080000001,
0x8000000080008081,
0x8000000000008009,
0x000000000000008a,
0x0000000000000088,
0x0000000080008009,
0x000000008000000a,
0x000000008000808b,
0x800000000000008b,
0x8000000000008089,
0x8000000000008003,
0x8000000000008002,
0x8000000000000080,
0x000000000000800a,
0x800000008000000a,
0x8000000080008081,
0x8000000000008080,
0x0000000080000001,
0x8000000080008008,
];
const R: [[u32; 5]; 5] = [
[0, 36, 3, 41, 18],
[1, 44, 10, 45, 2],
[62, 6, 43, 15, 61],
[28, 55, 25, 21, 56],
[27, 20, 39, 8, 14],
];
for rc in RC {
let mut c = [0u64; 5];
for (x, cx) in c.iter_mut().enumerate() {
*cx = lanes[x][0] ^ lanes[x][1] ^ lanes[x][2] ^ lanes[x][3] ^ lanes[x][4];
}
let mut d = [0u64; 5];
for x in 0..5 {
d[x] = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
}
for x in 0..5 {
for y in 0..5 {
lanes[x][y] ^= d[x];
}
}
let mut b = [[0u64; 5]; 5];
for x in 0..5 {
for y in 0..5 {
b[y][(2 * x + 3 * y) % 5] = lanes[x][y].rotate_left(R[x][y]);
}
}
for x in 0..5 {
for y in 0..5 {
lanes[x][y] = b[x][y] ^ ((!b[(x + 1) % 5][y]) & b[(x + 2) % 5][y]);
}
}
lanes[0][0] ^= rc;
}
}
fn reference_sponge(rate: usize, pad: u8, input: &[u8], out: &mut [u8]) {
let mut lanes = [[0u64; 5]; 5];
let put = |lanes: &mut [[u64; 5]; 5], i: usize, byte: u8| {
let lane = i / 8;
lanes[lane % 5][lane / 5] ^= (byte as u64) << (8 * (i % 8));
};
let get = |lanes: &[[u64; 5]; 5], i: usize| -> u8 {
let lane = i / 8;
(lanes[lane % 5][lane / 5] >> (8 * (i % 8))) as u8
};
let mut padded = input.to_vec();
padded.push(pad);
while padded.len() % rate != 0 {
padded.push(0);
}
let last = padded.len() - 1;
padded[last] |= 0x80;
for block in padded.chunks(rate) {
for (i, byte) in block.iter().enumerate() {
put(&mut lanes, i, *byte);
}
reference_keccak(&mut lanes);
}
let mut produced = 0;
while produced < out.len() {
let take = core::cmp::min(rate, out.len() - produced);
for i in 0..take {
out[produced + i] = get(&lanes, i);
}
produced += take;
if produced < out.len() {
reference_keccak(&mut lanes);
}
}
}
#[test]
fn the_reference_keccak_reproduces_sha3() {
let mut got = [0u8; 32];
reference_sponge(136, 0x06, b"abc", &mut got);
assert_eq!(
hex(&got),
"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
);
assert_eq!(hex(Sha3_256::digest(b"abc").as_ref()), hex(&got));
}
#[test]
fn empty_customization_is_plain_shake() {
for len in [1usize, 16, 32, 168, 169, 512] {
let mut a = vec![0u8; len];
let mut b = vec![0u8; len];
CShake128::xof(b"", b"", b"the quick brown fox", &mut a);
Shake128::xof(b"the quick brown fox", &mut b);
assert_eq!(a, b, "cSHAKE128 with no customization, {len} bytes");
CShake256::xof(b"", b"", b"the quick brown fox", &mut a);
Shake256::xof(b"the quick brown fox", &mut b);
assert_eq!(a, b, "cSHAKE256 with no customization, {len} bytes");
}
}
#[test]
fn the_customized_path_matches_the_reference_keccak() {
let cases: &[(&[u8], &[u8], &[u8])] = &[
(b"", b"Email Signature", b"\x00\x01\x02\x03"),
(b"KMAC", b"", b"hello"),
(b"KMAC", b"My Tagged Application", b""),
(b"N", b"S", &[0x5a; 200]),
];
for (n, s, data) in cases {
for (rate, is_128) in [(168usize, true), (136, false)] {
let mut prefix = reference_left_encode(rate as u64);
prefix.extend_from_slice(&reference_left_encode((n.len() as u64) * 8));
prefix.extend_from_slice(n);
prefix.extend_from_slice(&reference_left_encode((s.len() as u64) * 8));
prefix.extend_from_slice(s);
while prefix.len() % rate != 0 {
prefix.push(0);
}
prefix.extend_from_slice(data);
let mut want = [0u8; 64];
reference_sponge(rate, 0x04, &prefix, &mut want);
let mut got = [0u8; 64];
if is_128 {
CShake128::xof(n, s, data, &mut got);
} else {
CShake256::xof(n, s, data, &mut got);
}
assert_eq!(
hex(&got),
hex(&want),
"cSHAKE rate {rate}, N={n:?}, S={s:?}"
);
}
}
}
#[test]
fn customization_changes_the_output() {
let mut a = [0u8; 32];
let mut b = [0u8; 32];
let mut c = [0u8; 32];
CShake128::xof(b"", b"one", b"message", &mut a);
CShake128::xof(b"", b"two", b"message", &mut b);
CShake128::xof(b"", b"", b"message", &mut c);
assert_ne!(a, b, "S is bound into the output");
assert_ne!(a, c, "and distinguishes customized from plain");
}
#[test]
fn streaming_matches_the_one_shot() {
let data = [0x37u8; 500];
let mut one = [0u8; 64];
CShake128::xof(b"KMAC", b"S", &data, &mut one);
let mut x = CShake128::new(b"KMAC", b"S");
for chunk in data.chunks(7) {
x.update(chunk);
}
let mut streamed = [0u8; 64];
x.finalize_xof(&mut streamed);
assert_eq!(one, streamed);
}
}
#[cfg(test)]
mod tuple_parallel_tests {
use super::*;
use ic_core::traits::SelfTest;
fn enc(x: u64) -> Vec<u8> {
let mut bytes = x.to_be_bytes().to_vec();
while bytes.len() > 1 && bytes[0] == 0 {
bytes.remove(0);
}
let mut v = vec![bytes.len() as u8];
v.extend_from_slice(&bytes);
v
}
fn renc(x: u64) -> Vec<u8> {
let mut bytes = x.to_be_bytes().to_vec();
while bytes.len() > 1 && bytes[0] == 0 {
bytes.remove(0);
}
let n = bytes.len() as u8;
bytes.push(n);
bytes
}
fn reference_tuple_hash(
wide: bool,
custom: &[u8],
elements: &[&[u8]],
out: &mut [u8],
xof: bool,
) {
let mut z = Vec::new();
for e in elements {
z.extend_from_slice(&enc((e.len() as u64) * 8));
z.extend_from_slice(e);
}
z.extend_from_slice(&renc(if xof { 0 } else { (out.len() as u64) * 8 }));
if wide {
CShake256::xof(b"TupleHash", custom, &z, out);
} else {
CShake128::xof(b"TupleHash", custom, &z, out);
}
}
fn reference_parallel_hash(
wide: bool,
custom: &[u8],
block_size: usize,
data: &[u8],
out: &mut [u8],
xof: bool,
) {
let chain_len = if wide { 64 } else { 32 };
let mut z = enc(block_size as u64);
let mut n = 0u64;
for block in data.chunks(block_size) {
let mut chain = vec![0u8; chain_len];
if wide {
CShake256::xof(b"", b"", block, &mut chain);
} else {
CShake128::xof(b"", b"", block, &mut chain);
}
z.extend_from_slice(&chain);
n += 1;
}
z.extend_from_slice(&renc(n));
z.extend_from_slice(&renc(if xof { 0 } else { (out.len() as u64) * 8 }));
if wide {
CShake256::xof(b"ParallelHash", custom, &z, out);
} else {
CShake128::xof(b"ParallelHash", custom, &z, out);
}
}
#[test]
fn tuple_hash_matches_an_independent_construction() {
let cases: &[(&[u8], &[&[u8]])] = &[
(b"", &[]),
(b"", &[b"abc"]),
(b"My Tupled App", &[b"abc", b"d"]),
(b"", &[b"", b"", b""]),
(b"S", &[&[0x5au8; 300][..], b"x", &[0u8; 168][..]]),
];
for (custom, elements) in cases {
for len in [16usize, 32, 64] {
let mut want = vec![0u8; len];
let mut got = vec![0u8; len];
reference_tuple_hash(false, custom, elements, &mut want, false);
TupleHash128::hash(custom, elements, &mut got);
assert_eq!(got, want, "TupleHash128 fixed, {len} bytes");
reference_tuple_hash(true, custom, elements, &mut want, false);
TupleHash256::hash(custom, elements, &mut got);
assert_eq!(got, want, "TupleHash256 fixed, {len} bytes");
reference_tuple_hash(false, custom, elements, &mut want, true);
TupleHash128::hash_xof(custom, elements, &mut got);
assert_eq!(got, want, "TupleHash128 xof, {len} bytes");
}
}
}
#[test]
fn tuples_that_concatenate_alike_hash_differently() {
let splits: &[&[&[u8]]] = &[
&[b"abc", b"d"],
&[b"ab", b"cd"],
&[b"a", b"bcd"],
&[b"abcd"],
&[b"abcd", b""],
&[b"", b"abcd"],
];
let mut seen: Vec<[u8; 32]> = Vec::new();
for elements in splits {
let mut out = [0u8; 32];
TupleHash128::hash(b"", elements, &mut out);
assert!(
!seen.contains(&out),
"two different tuples collided: {elements:?}"
);
seen.push(out);
}
}
#[test]
fn tuple_hash_streams() {
let mut one = [0u8; 32];
TupleHash128::hash(b"S", &[b"alpha", b"beta", b"gamma"], &mut one);
let mut t = TupleHash128::new(b"S");
t.update(b"alpha");
t.update(b"beta");
t.update(b"gamma");
let mut streamed = [0u8; 32];
t.finalize(&mut streamed);
assert_eq!(one, streamed);
}
#[test]
fn parallel_hash_matches_an_independent_construction() {
let data = [0x37u8; 500];
for block_size in [1usize, 8, 32, 137, 500, 1024] {
for len in [16usize, 32, 64] {
let mut want = vec![0u8; len];
let mut got = vec![0u8; len];
reference_parallel_hash(false, b"S", block_size, &data, &mut want, false);
ParallelHash128::hash(b"S", block_size, &data, &mut got);
assert_eq!(got, want, "ParallelHash128 B={block_size}, {len} bytes");
reference_parallel_hash(true, b"", block_size, &data, &mut want, false);
ParallelHash256::hash(b"", block_size, &data, &mut got);
assert_eq!(got, want, "ParallelHash256 B={block_size}, {len} bytes");
reference_parallel_hash(false, b"S", block_size, &data, &mut want, true);
ParallelHash128::hash_xof(b"S", block_size, &data, &mut got);
assert_eq!(got, want, "ParallelHash128 xof B={block_size}");
}
}
}
#[test]
fn parallel_hash_handles_an_empty_input() {
let mut want = [0u8; 32];
let mut got = [0u8; 32];
reference_parallel_hash(false, b"", 64, b"", &mut want, false);
ParallelHash128::hash(b"", 64, b"", &mut got);
assert_eq!(got, want);
}
#[test]
fn the_block_size_changes_the_result() {
let data = [0xa1u8; 256];
let mut a = [0u8; 32];
let mut b = [0u8; 32];
ParallelHash128::hash(b"", 32, &data, &mut a);
ParallelHash128::hash(b"", 64, &data, &mut b);
assert_ne!(a, b, "B is bound into the output");
}
#[test]
fn output_length_is_bound_in_for_both() {
let mut short = [0u8; 32];
let mut long = [0u8; 64];
TupleHash128::hash(b"", &[b"x"], &mut short);
TupleHash128::hash(b"", &[b"x"], &mut long);
assert_ne!(short[..], long[..32], "TupleHash binds L");
ParallelHash128::hash(b"", 32, b"x", &mut short);
ParallelHash128::hash(b"", 32, b"x", &mut long);
assert_ne!(short[..], long[..32], "ParallelHash binds L");
TupleHash128::hash_xof(b"", &[b"x"], &mut short);
TupleHash128::hash_xof(b"", &[b"x"], &mut long);
assert_eq!(short[..], long[..32], "the xof form is a stream");
}
#[test]
fn every_self_test_passes() {
TupleHash128::self_test().unwrap();
TupleHash256::self_test().unwrap();
ParallelHash128::self_test().unwrap();
ParallelHash256::self_test().unwrap();
}
}