use super::keccak::{Keccak, KeccakReader};
fn left_encode(buf: &mut [u8; 9], x: u64) -> usize {
let mut len = 1usize;
let mut v = x;
while {
v >>= 8;
v
} != 0
{
len += 1;
}
buf[0] = len as u8;
for (i, slot) in buf[1..1 + len].iter_mut().enumerate() {
*slot = (x >> (8 * (len - 1 - i))) as u8;
}
1 + len
}
fn right_encode(buf: &mut [u8; 9], x: u64) -> usize {
let mut len = 1usize;
let mut v = x;
while {
v >>= 8;
v
} != 0
{
len += 1;
}
for (i, slot) in buf[..len].iter_mut().enumerate() {
*slot = (x >> (8 * (len - 1 - i))) as u8;
}
buf[len] = len as u8;
len + 1
}
struct Absorb<'a> {
k: &'a mut Keccak,
count: usize,
}
impl Absorb<'_> {
fn raw(&mut self, data: &[u8]) {
self.k.update(data);
self.count += data.len();
}
fn left_encode(&mut self, x: u64) {
let mut b = [0u8; 9];
let n = left_encode(&mut b, x);
self.raw(&b[..n]);
}
fn encode_string(&mut self, s: &[u8]) {
self.left_encode(8 * s.len() as u64);
self.raw(s);
}
fn bytepad_to(&mut self, rate: usize) {
let pad = (rate - self.count % rate) % rate;
let zeros = [0u8; 168];
self.k.update(&zeros[..pad]);
self.count += pad;
}
}
fn cshake_init(rate: usize, name: &[u8], custom: &[u8]) -> (Keccak, u8) {
let mut k = Keccak::new(rate);
if name.is_empty() && custom.is_empty() {
return (k, 0x1F);
}
let mut a = Absorb {
k: &mut k,
count: 0,
};
a.left_encode(rate as u64); a.encode_string(name);
a.encode_string(custom);
a.bytepad_to(rate);
(k, 0x04)
}
fn kmac_init(rate: usize, key: &[u8], custom: &[u8]) -> Keccak {
let (mut k, _) = cshake_init(rate, b"KMAC", custom);
let mut a = Absorb {
k: &mut k,
count: 0,
};
a.left_encode(rate as u64); a.encode_string(key);
a.bytepad_to(rate);
k
}
macro_rules! cshake {
($name:ident, $rate:expr, $doc:literal) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name {
keccak: Keccak,
pad: u8,
}
impl $name {
pub fn new(name: &[u8], custom: &[u8]) -> Self {
let (keccak, pad) = cshake_init($rate, name, custom);
$name { keccak, pad }
}
pub fn update(&mut self, data: &[u8]) {
self.keccak.update(data);
}
pub fn finalize_xof(self) -> KeccakReader {
KeccakReader::new(self.keccak, self.pad)
}
pub fn finalize_into(self, out: &mut [u8]) {
use super::XofReader;
self.finalize_xof().read(out);
}
}
};
}
cshake!(CShake128, 168, "cSHAKE128 (customizable SHAKE128).");
cshake!(CShake256, 136, "cSHAKE256 (customizable SHAKE256).");
macro_rules! kmac {
($name:ident, $rate:expr, $doc:literal) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name {
keccak: Keccak,
}
impl $name {
pub fn new(key: &[u8], custom: &[u8]) -> Self {
$name {
keccak: kmac_init($rate, key, custom),
}
}
pub fn update(&mut self, data: &[u8]) {
self.keccak.update(data);
}
pub fn finalize_into(mut self, out: &mut [u8]) {
let mut b = [0u8; 9];
let n = right_encode(&mut b, 8 * out.len() as u64);
self.keccak.update(&b[..n]);
self.keccak.finalize(0x04);
self.keccak.squeeze(out);
}
pub fn verify(self, expected: &[u8]) -> crate::ct::Choice {
super::Mac::verify(self, expected)
}
}
impl super::Mac for $name {
fn update(&mut self, data: &[u8]) {
$name::update(self, data);
}
fn finalize_into(self, out: &mut [u8]) {
$name::finalize_into(self, out);
}
}
impl Drop for $name {
fn drop(&mut self) {
self.keccak.zeroize();
}
}
};
}
kmac!(Kmac128, 168, "KMAC128 (keyed MAC, SP 800-185).");
kmac!(Kmac256, 136, "KMAC256 (keyed MAC, SP 800-185).");
macro_rules! kmac_xof {
($name:ident, $rate:expr, $doc:literal) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name {
keccak: Keccak,
}
impl $name {
pub fn new(key: &[u8], custom: &[u8]) -> Self {
$name {
keccak: kmac_init($rate, key, custom),
}
}
pub fn update(&mut self, data: &[u8]) {
self.keccak.update(data);
}
pub fn finalize_xof(mut self) -> KeccakReader {
let mut b = [0u8; 9];
let n = right_encode(&mut b, 0);
self.keccak.update(&b[..n]);
KeccakReader::new(self.keccak, 0x04)
}
pub fn finalize_into(self, out: &mut [u8]) {
use super::XofReader;
self.finalize_xof().read(out);
}
}
};
}
kmac_xof!(KmacXof128, 168, "KMAC128 in XOF mode.");
kmac_xof!(KmacXof256, 136, "KMAC256 in XOF mode.");
macro_rules! tuplehash {
($name:ident, $rate:expr, $doc:literal) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name {
keccak: Keccak,
}
impl $name {
pub fn new(custom: &[u8]) -> Self {
let (keccak, _) = cshake_init($rate, b"TupleHash", custom);
$name { keccak }
}
pub fn update(&mut self, element: &[u8]) {
let mut b = [0u8; 9];
let n = left_encode(&mut b, 8 * element.len() as u64);
self.keccak.update(&b[..n]);
self.keccak.update(element);
}
pub fn finalize_into(mut self, out: &mut [u8]) {
let mut b = [0u8; 9];
let n = right_encode(&mut b, 8 * out.len() as u64);
self.keccak.update(&b[..n]);
self.keccak.finalize(0x04);
self.keccak.squeeze(out);
}
pub fn finalize_xof(mut self) -> KeccakReader {
let mut b = [0u8; 9];
let n = right_encode(&mut b, 0);
self.keccak.update(&b[..n]);
KeccakReader::new(self.keccak, 0x04)
}
}
};
}
tuplehash!(
TupleHash128,
168,
"TupleHash128 (hashing of a tuple of strings)."
);
tuplehash!(
TupleHash256,
136,
"TupleHash256 (hashing of a tuple of strings)."
);
macro_rules! parallelhash {
($name:ident, $rate:expr, $inner_rate:expr, $cv:expr, $doc:literal) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name<const B: usize> {
outer: Keccak,
leaf: [u8; B],
leaf_len: usize,
blocks: u64,
}
impl<const B: usize> $name<B> {
pub fn new(custom: &[u8]) -> Self {
let (mut outer, _) = cshake_init($rate, b"ParallelHash", custom);
let mut b = [0u8; 9];
let n = left_encode(&mut b, B as u64);
outer.update(&b[..n]);
$name {
outer,
leaf: [0u8; B],
leaf_len: 0,
blocks: 0,
}
}
fn absorb_leaf(&mut self) {
let mut cv = [0u8; $cv];
let mut k = Keccak::new($inner_rate);
k.update(&self.leaf[..self.leaf_len]);
k.finalize(0x1F);
k.squeeze(&mut cv);
self.outer.update(&cv);
self.blocks += 1;
self.leaf_len = 0;
}
pub fn update(&mut self, mut data: &[u8]) {
while !data.is_empty() {
let take = (B - self.leaf_len).min(data.len());
self.leaf[self.leaf_len..self.leaf_len + take].copy_from_slice(&data[..take]);
self.leaf_len += take;
data = &data[take..];
if self.leaf_len == B {
self.absorb_leaf();
}
}
}
fn absorb_tail(&mut self) {
if self.leaf_len > 0 {
self.absorb_leaf();
}
let mut b = [0u8; 9];
let n = right_encode(&mut b, self.blocks);
self.outer.update(&b[..n]);
}
pub fn finalize_into(mut self, out: &mut [u8]) {
self.absorb_tail();
let mut b = [0u8; 9];
let n = right_encode(&mut b, 8 * out.len() as u64);
self.outer.update(&b[..n]);
self.outer.finalize(0x04);
self.outer.squeeze(out);
}
pub fn finalize_xof(mut self) -> KeccakReader {
self.absorb_tail();
let mut b = [0u8; 9];
let n = right_encode(&mut b, 0);
self.outer.update(&b[..n]);
KeccakReader::new(self.outer, 0x04)
}
}
};
}
parallelhash!(
ParallelHash128,
168,
168,
32,
"ParallelHash128 with a const-generic block size `B`."
);
parallelhash!(
ParallelHash256,
136,
136,
64,
"ParallelHash256 with a const-generic block size `B`."
);
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::from_hex;
fn sample_key() -> [u8; 32] {
let mut k = [0u8; 32];
for (i, b) in k.iter_mut().enumerate() {
*b = 0x40 + i as u8;
}
k
}
#[test]
fn kmac_nist_samples() {
let key = sample_key();
let data = [0x00u8, 0x01, 0x02, 0x03];
let mut out = [0u8; 32];
let mut m = Kmac128::new(&key, b"");
m.update(&data);
m.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<32>("e5780b0d3ea6f7d3a429c5706aa43a00fadbd7d49628839e3187243f456ee14e")
);
let mut out = [0u8; 32];
let mut m = Kmac128::new(&key, b"My Tagged Application");
m.update(&data);
m.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<32>("3b1fba963cd8b0b59e8c1a6d71888b7143651af8ba0a7070c0979e2811324aa5")
);
let mut out = [0u8; 64];
let mut m = Kmac256::new(&key, b"My Tagged Application");
m.update(&data);
m.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<64>(
"20c570c31346f703c9ac36c61c03cb64c3970d0cfc787e9b79599d273a68d2f7f69d4cc3de9d104a351689f27cf6f5951f0103f33f4f24871024d9c27773a8dd"
)
);
}
#[test]
fn cshake128_sample() {
let mut out = [0u8; 32];
let mut c = CShake128::new(b"", b"Email Signature");
c.update(&[0x00, 0x01, 0x02, 0x03]);
c.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<32>("c1c36925b6409a04f1b504fcbca9d82b4017277cb5ed2b2065fc1d3814d5aaf5")
);
}
#[test]
fn kmac_verify_constant_time() {
let key = sample_key();
let data = [0x00u8, 0x01, 0x02, 0x03];
let mut tag = [0u8; 32];
let mut m = Kmac128::new(&key, b"");
m.update(&data);
m.finalize_into(&mut tag);
let mut m = Kmac128::new(&key, b"");
m.update(&data);
assert!(bool::from(m.verify(&tag)));
let mut bad = tag;
bad[0] ^= 1;
let mut m = Kmac128::new(&key, b"");
m.update(&data);
assert!(!bool::from(m.verify(&bad)));
let mut m = Kmac128::new(&key, b"");
m.update(&data);
assert!(!bool::from(m.verify(&tag[..31])));
}
#[test]
fn kmacxof_incremental_matches() {
use super::super::XofReader;
let key = sample_key();
let mut x = KmacXof128::new(&key, b"App");
x.update(b"streamed message");
let mut reader = x.finalize_xof();
let mut a = [0u8; 40];
reader.read(&mut a[..5]);
reader.read(&mut a[5..]);
let mut b = [0u8; 40];
let mut x2 = KmacXof128::new(&key, b"App");
x2.update(b"streamed message");
x2.finalize_into(&mut b);
assert_eq!(a, b);
}
#[test]
fn tuplehash_nist_samples() {
let x1 = [0x00u8, 0x01, 0x02];
let x2 = [0x10u8, 0x11, 0x12, 0x13, 0x14, 0x15];
let x3 = [0x20u8, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28];
let mut out = [0u8; 32];
let mut t = TupleHash128::new(b"");
t.update(&x1);
t.update(&x2);
t.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<32>("c5d8786c1afb9b82111ab34b65b2c0048fa64e6d48e263264ce1707d3ffc8ed1")
);
let mut out = [0u8; 32];
let mut t = TupleHash128::new(b"My Tuple App");
t.update(&x1);
t.update(&x2);
t.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<32>("75cdb20ff4db1154e841d758e24160c54bae86eb8c13e7f5f40eb35588e96dfb")
);
let mut out = [0u8; 32];
let mut t = TupleHash128::new(b"My Tuple App");
t.update(&x1);
t.update(&x2);
t.update(&x3);
t.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<32>("e60f202c89a2631eda8d4c588ca5fd07f39e5151998deccf973adb3804bb6e84")
);
let mut out = [0u8; 64];
let mut t = TupleHash256::new(b"");
t.update(&x1);
t.update(&x2);
t.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<64>(
"cfb7058caca5e668f81a12a20a2195ce97a925f1dba3e7449a56f82201ec607311ac2696b1ab5ea2352df1423bde7bd4bb78c9aed1a853c78672f9eb23bbe194"
)
);
}
#[test]
fn parallelhash_nist_samples() {
let x = [
0x00u8, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
0x16, 0x17, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
];
let mut out = [0u8; 32];
let mut p = ParallelHash128::<8>::new(b"");
p.update(&x);
p.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<32>("ba8dc1d1d979331d3f813603c67f72609ab5e44b94a0b8f9af46514454a2b4f5")
);
let mut out = [0u8; 32];
let mut p = ParallelHash128::<8>::new(b"Parallel Data");
p.update(&x);
p.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<32>("fc484dcb3f84dceedc353438151bee58157d6efed0445a81f165e495795b7206")
);
let mut out = [0u8; 64];
let mut p = ParallelHash256::<8>::new(b"");
p.update(&x);
p.finalize_into(&mut out);
assert_eq!(
out,
from_hex::<64>(
"bc1ef124da34495e948ead207dd9842235da432d2bbc54b4c110e64c451105531b7f2a3e0ce055c02805e7c2de1fb746af97a1dd01f43b824e31b87612410429"
)
);
}
#[test]
fn parallelhash_streaming_matches() {
let data: [u8; 100] = core::array::from_fn(|i| i as u8);
let mut a = [0u8; 32];
let mut p = ParallelHash128::<16>::new(b"ctx");
p.update(&data);
p.finalize_into(&mut a);
let mut b = [0u8; 32];
let mut p = ParallelHash128::<16>::new(b"ctx");
for c in data.chunks(7) {
p.update(c);
}
p.finalize_into(&mut b);
assert_eq!(a, b);
}
}