use sha3::digest::{ExtendableOutput, Update, XofReader};
use sha3::{Digest, Sha3_256, Sha3_512, Shake256};
use zeroize::Zeroizing;
use crate::params::{SALT_BYTES, SEED_BYTES, SHARED_KEY_BYTES};
const DOMAIN_XOF: u8 = 0x01; const DOMAIN_H: u8 = 0x01; const DOMAIN_I: u8 = 0x02; const DOMAIN_G: u8 = 0x00; const DOMAIN_J: u8 = 0x03;
pub const DIGEST_BYTES: usize = 32;
pub const THETA_BYTES: usize = 32;
pub type EkHash = [u8; DIGEST_BYTES];
pub type Theta = Zeroizing<[u8; THETA_BYTES]>;
pub type SharedKey = [u8; SHARED_KEY_BYTES];
fn sha3_256_ds(parts: &[&[u8]], domain: u8) -> [u8; 32] {
let mut h = Sha3_256::default();
for &p in parts {
Digest::update(&mut h, p);
}
Digest::update(&mut h, [domain]);
let digest = Digest::finalize(h);
let mut out = [0u8; 32];
out.copy_from_slice(&digest);
out
}
fn sha3_512_ds(parts: &[&[u8]], domain: u8) -> Zeroizing<[u8; 64]> {
let mut h = Sha3_512::default();
for &p in parts {
Digest::update(&mut h, p);
}
Digest::update(&mut h, [domain]);
let digest = Digest::finalize(h);
let mut out = Zeroizing::new([0u8; 64]);
out.copy_from_slice(&digest);
out
}
pub fn h_ek(ek: &[u8]) -> EkHash {
sha3_256_ds(&[ek], DOMAIN_H)
}
pub fn g(ek_hash: &EkHash, m: &[u8], salt: &[u8; SALT_BYTES]) -> (SharedKey, Theta) {
let digest = sha3_512_ds(&[ek_hash, m, salt], DOMAIN_G);
let mut k = [0u8; SHARED_KEY_BYTES];
k.copy_from_slice(&digest[..SHARED_KEY_BYTES]);
let mut theta = Zeroizing::new([0u8; THETA_BYTES]);
theta.copy_from_slice(&digest[SHARED_KEY_BYTES..SHARED_KEY_BYTES + THETA_BYTES]);
(k, theta)
}
pub fn j(ek_hash: &EkHash, sigma: &[u8; SEED_BYTES], c: &[u8]) -> SharedKey {
sha3_256_ds(&[ek_hash, sigma, c], DOMAIN_J)
}
pub fn xof(seed: &[u8]) -> impl XofReader {
let mut x = Shake256::default();
Update::update(&mut x, seed);
Update::update(&mut x, &[DOMAIN_XOF]);
x.finalize_xof()
}
pub fn i_pke_seed(
seed_pke: &[u8; SEED_BYTES],
) -> (Zeroizing<[u8; SEED_BYTES]>, [u8; SEED_BYTES]) {
let digest = sha3_512_ds(&[seed_pke], DOMAIN_I);
let mut seed_dk = Zeroizing::new([0u8; SEED_BYTES]);
seed_dk.copy_from_slice(&digest[..SEED_BYTES]);
let mut seed_ek = [0u8; SEED_BYTES];
seed_ek.copy_from_slice(&digest[SEED_BYTES..2 * SEED_BYTES]);
(seed_dk, seed_ek)
}
#[cfg(test)]
mod tests {
use super::*;
use sha3::digest::{ExtendableOutput, Update, XofReader};
use sha3::{Digest, Sha3_256, Sha3_512, Shake256};
#[test]
fn sha3_256_empty_vector() {
assert_eq!(
hex::encode(Sha3_256::digest(b"")),
"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"
);
}
#[test]
fn sha3_512_empty_vector() {
assert_eq!(
hex::encode(Sha3_512::digest(b"")),
"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a6\
15b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26"
);
}
#[test]
fn xof_appends_domain_byte() {
let seed = b"abc";
let mut got = [0u8; 32];
xof(seed).read(&mut got);
let mut h = Shake256::default();
Update::update(&mut h, seed);
Update::update(&mut h, &[0x01u8]);
let mut want = [0u8; 32];
h.finalize_xof().read(&mut want);
assert_eq!(got, want);
let mut h2 = Shake256::default();
Update::update(&mut h2, seed);
let mut plain = [0u8; 32];
h2.finalize_xof().read(&mut plain);
assert_ne!(got, plain);
}
#[test]
fn h_ek_appends_domain_byte() {
let ek = [0xABu8; 100];
let mut buf = ek.to_vec();
buf.push(0x01);
assert_eq!(hex::encode(h_ek(&ek)), hex::encode(Sha3_256::digest(&buf)));
}
#[test]
fn g_matches_spec_construction() {
let ek_hash = [9u8; 32];
let m = [7u8; 16];
let salt = [5u8; 16];
let (k, theta) = g(&ek_hash, &m, &salt);
let mut buf = Vec::new();
buf.extend_from_slice(&ek_hash);
buf.extend_from_slice(&m);
buf.extend_from_slice(&salt);
buf.push(0x00);
let expected = Sha3_512::digest(&buf);
assert_eq!(&k[..], &expected[..32], "K must be the first 32 bytes");
assert_eq!(theta.as_slice(), &expected[32..64], "θ must be the last 32 bytes");
}
#[test]
fn g_deterministic_and_split_distinct() {
let (k1, t1) = g(&[1u8; 32], &[2u8; 16], &[3u8; 16]);
let (k2, t2) = g(&[1u8; 32], &[2u8; 16], &[3u8; 16]);
assert_eq!(k1, k2);
assert_eq!(*t1, *t2);
assert_ne!(&k1[..], t1.as_slice());
}
#[test]
fn g_sensitive_to_each_input() {
let (bk, bt) = g(&[0u8; 32], &[0u8; 16], &[0u8; 16]);
let mut ek = [0u8; 32];
ek[0] = 1;
let (k_ek, t_ek) = g(&ek, &[0u8; 16], &[0u8; 16]);
let mut m = [0u8; 16];
m[0] = 1;
let (k_m, t_m) = g(&[0u8; 32], &m, &[0u8; 16]);
let mut salt = [0u8; 16];
salt[0] = 1;
let (k_s, t_s) = g(&[0u8; 32], &[0u8; 16], &salt);
for (k, t) in [(k_ek, t_ek), (k_m, t_m), (k_s, t_s)] {
assert_ne!(bk, k);
assert_ne!(*bt, *t);
}
}
#[test]
fn j_matches_spec_construction() {
let ek_hash = [0x44u8; 32];
let sigma = [0x11u8; 32];
let c = [0x22u8; 64];
let mut buf = Vec::new();
buf.extend_from_slice(&ek_hash);
buf.extend_from_slice(&sigma);
buf.extend_from_slice(&c);
buf.push(0x03);
assert_eq!(hex::encode(j(&ek_hash, &sigma, &c)), hex::encode(Sha3_256::digest(&buf)));
}
#[test]
fn j_sensitive_to_each_input() {
let ek_hash = [0x44u8; 32];
let sigma = [0x11u8; 32];
let c = [0x22u8; 64];
let base = j(&ek_hash, &sigma, &c);
let mut ek2 = ek_hash;
ek2[0] ^= 1;
assert_ne!(base, j(&ek2, &sigma, &c));
let mut sigma2 = sigma;
sigma2[0] ^= 1;
assert_ne!(base, j(&ek_hash, &sigma2, &c));
let mut c2 = c;
c2[0] ^= 1;
assert_ne!(base, j(&ek_hash, &sigma, &c2));
}
#[test]
fn i_pke_seed_matches_spec_construction() {
let seed = [0x5Au8; SEED_BYTES];
let (dk, ek) = i_pke_seed(&seed);
let mut buf = seed.to_vec();
buf.push(0x02);
let full = Sha3_512::digest(&buf);
assert_eq!(dk.as_slice(), &full[..SEED_BYTES]);
assert_eq!(&ek[..], &full[SEED_BYTES..]);
assert_ne!(dk.as_slice(), &ek[..]);
}
#[test]
fn i_pke_seed_deterministic() {
let seed = [0xC3u8; SEED_BYTES];
let (dk1, ek1) = i_pke_seed(&seed);
let (dk2, ek2) = i_pke_seed(&seed);
assert_eq!(dk1.as_slice(), dk2.as_slice());
assert_eq!(ek1, ek2);
}
}