use zeroize::Zeroizing;
use crate::stego::crypto::{
derive_h264_mvd_structural_key, derive_per_gop_seed_from_master,
derive_structural_key,
};
use crate::stego::error::StegoError;
use super::EmbedDomain;
pub struct CabacStegoMasterKeys {
coeff_master: Zeroizing<[u8; 64]>,
mvd_master: Zeroizing<[u8; 64]>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct DomainSeeds {
pub perm_seed: [u8; 32],
pub hhat_seed: [u8; 32],
}
impl DomainSeeds {
pub fn hhat_seed_ref(&self) -> &[u8; 32] {
&self.hhat_seed
}
}
impl CabacStegoMasterKeys {
pub fn derive(passphrase: &str) -> Result<Self, StegoError> {
let coeff_master = derive_structural_key(passphrase)?;
let mvd_master = derive_h264_mvd_structural_key(passphrase)?;
Ok(Self { coeff_master, mvd_master })
}
pub fn per_gop_seeds(&self, domain: EmbedDomain, gop_idx: u32) -> DomainSeeds {
let (master_perm_src, master_hhat_src, perm_label, hhat_label) = match domain {
EmbedDomain::CoeffSignBypass => (
self.coeff_master_perm(),
self.coeff_master_hhat(),
b"cabac-coeff-sign-perm" as &[u8],
b"cabac-coeff-sign-hhat" as &[u8],
),
EmbedDomain::CoeffSuffixLsb => (
self.coeff_master_perm(),
self.coeff_master_hhat(),
b"cabac-coeff-suffix-perm" as &[u8],
b"cabac-coeff-suffix-hhat" as &[u8],
),
EmbedDomain::MvdSignBypass => (
self.mvd_master_perm(),
self.mvd_master_hhat(),
b"cabac-mvd-sign-perm" as &[u8],
b"cabac-mvd-sign-hhat" as &[u8],
),
EmbedDomain::MvdSuffixLsb => (
self.mvd_master_perm(),
self.mvd_master_hhat(),
b"cabac-mvd-suffix-perm" as &[u8],
b"cabac-mvd-suffix-hhat" as &[u8],
),
};
DomainSeeds {
perm_seed: derive_per_gop_seed_from_master(master_perm_src, gop_idx, perm_label),
hhat_seed: derive_per_gop_seed_from_master(master_hhat_src, gop_idx, hhat_label),
}
}
fn coeff_master_perm(&self) -> &[u8; 32] {
Self::first_half(&self.coeff_master)
}
fn coeff_master_hhat(&self) -> &[u8; 32] {
Self::second_half(&self.coeff_master)
}
fn mvd_master_perm(&self) -> &[u8; 32] {
Self::first_half(&self.mvd_master)
}
fn mvd_master_hhat(&self) -> &[u8; 32] {
Self::second_half(&self.mvd_master)
}
fn first_half(buf: &[u8; 64]) -> &[u8; 32] {
let first: &[u8] = &buf[..32];
first.try_into().expect("32-byte slice")
}
fn second_half(buf: &[u8; 64]) -> &[u8; 32] {
let second: &[u8] = &buf[32..];
second.try_into().expect("32-byte slice")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derive_succeeds_with_any_nonempty_passphrase() {
let keys = CabacStegoMasterKeys::derive("test-passphrase").unwrap();
let _ = keys.per_gop_seeds(EmbedDomain::CoeffSignBypass, 0);
}
#[test]
fn same_passphrase_produces_same_seeds() {
let a = CabacStegoMasterKeys::derive("same").unwrap();
let b = CabacStegoMasterKeys::derive("same").unwrap();
for &domain in &[
EmbedDomain::CoeffSignBypass,
EmbedDomain::CoeffSuffixLsb,
EmbedDomain::MvdSignBypass,
EmbedDomain::MvdSuffixLsb,
] {
for gop in [0u32, 1, 5, 100] {
assert_eq!(
a.per_gop_seeds(domain, gop),
b.per_gop_seeds(domain, gop),
"determinism: {domain:?} gop={gop}",
);
}
}
}
#[test]
fn different_passphrase_produces_different_seeds() {
let a = CabacStegoMasterKeys::derive("alpha").unwrap();
let b = CabacStegoMasterKeys::derive("beta").unwrap();
let sa = a.per_gop_seeds(EmbedDomain::CoeffSignBypass, 0);
let sb = b.per_gop_seeds(EmbedDomain::CoeffSignBypass, 0);
assert_ne!(sa, sb);
}
#[test]
fn four_domains_produce_distinct_seeds_at_same_gop() {
let keys = CabacStegoMasterKeys::derive("isolation").unwrap();
let cs = keys.per_gop_seeds(EmbedDomain::CoeffSignBypass, 0);
let cl = keys.per_gop_seeds(EmbedDomain::CoeffSuffixLsb, 0);
let ms = keys.per_gop_seeds(EmbedDomain::MvdSignBypass, 0);
let ml = keys.per_gop_seeds(EmbedDomain::MvdSuffixLsb, 0);
let all = [cs, cl, ms, ml];
for i in 0..all.len() {
for j in (i + 1)..all.len() {
assert_ne!(all[i], all[j], "domain seeds {i} and {j} collided");
}
}
}
#[test]
fn perm_and_hhat_seeds_are_distinct() {
let keys = CabacStegoMasterKeys::derive("perm-vs-hhat").unwrap();
let s = keys.per_gop_seeds(EmbedDomain::CoeffSignBypass, 0);
assert_ne!(
s.perm_seed, s.hhat_seed,
"perm and hhat seeds must be uncorrelated within one domain",
);
}
#[test]
fn different_gops_produce_different_seeds() {
let keys = CabacStegoMasterKeys::derive("gop-isolation").unwrap();
let s0 = keys.per_gop_seeds(EmbedDomain::CoeffSignBypass, 0);
let s1 = keys.per_gop_seeds(EmbedDomain::CoeffSignBypass, 1);
assert_ne!(s0, s1);
}
#[test]
fn coeff_and_mvd_masters_are_uncorrelated() {
let keys = CabacStegoMasterKeys::derive("master-split").unwrap();
let coeff = keys.per_gop_seeds(EmbedDomain::CoeffSignBypass, 0);
let mvd = keys.per_gop_seeds(EmbedDomain::MvdSignBypass, 0);
assert_ne!(coeff.perm_seed, mvd.perm_seed);
assert_ne!(coeff.hhat_seed, mvd.hhat_seed);
}
}