Skip to main content

dvb_csa/
key.rs

1//! Control word and key schedule — the 8-byte key and its derivations.
2//!
3//! DVB-CSA2 keys the block cipher and stream cipher from the same 8-byte
4//! control word:
5//!
6//! - **Block key schedule** (`expand_block`): produces 56 round-key bytes via
7//!   the KPERM permutation.
8//! - **Stream cipher seed** (`expand_stream`): produces a nibble-swapped copy
9//!   of the control word for LFSR initialization.
10use super::tables::KPERM;
11
12/// An 8-byte DVB-CSA control word.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct ControlWord(pub [u8; 8]);
15
16impl ControlWord {
17    /// Create a `ControlWord` from 8 bytes.
18    pub const fn from_bytes(bytes: [u8; 8]) -> Self {
19        Self(bytes)
20    }
21
22    /// Expand the control word into 56 block-cipher round-key bytes.
23    pub fn expand_block(&self) -> [u8; 56] {
24        let cw_u64 = u64::from_le_bytes(self.0);
25
26        let mut k = [0u64; 7];
27        k[6] = cw_u64;
28        for i in (1..=6).rev() {
29            k[i - 1] = key_permute(k[i]);
30        }
31
32        let mut sch = [0u8; 56];
33        for i in 0..7 {
34            let ki = k[i];
35            for j in 0..8 {
36                sch[i * 8 + j] = ((ki >> (j * 8)) as u8) ^ (i as u8);
37            }
38        }
39        sch
40    }
41
42    /// Expand to the nibble-swapped stream-cipher seed (cws).
43    ///
44    /// Each byte has its high and low nibbles swapped:
45    /// `cws[i] = (cw[i] >> 4) | (cw[i] << 4)`
46    pub fn expand_stream(&self) -> [u8; 8] {
47        let mut cws = [0u8; 8];
48        for (i, out) in cws.iter_mut().enumerate() {
49            *out = self.0[i].rotate_left(4);
50        }
51        cws
52    }
53}
54
55fn key_permute(k: u64) -> u64 {
56    let bytes = k.to_le_bytes();
57    let mut result = 0u64;
58    for (i, &b) in bytes.iter().enumerate() {
59        result |= KPERM[i][b as usize];
60    }
61    result
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn nibble_swap_symmetry() {
70        let cw = ControlWord::from_bytes([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]);
71        let cws = cw.expand_stream();
72        assert_eq!(cws[0], 0x21);
73        assert_eq!(cws[1], 0x43);
74        assert_eq!(cws[7], 0x0f);
75    }
76
77    #[test]
78    fn vector_12_key_schedule() {
79        // Quick sanity: vector 12 CW produces the correct encrypt output
80        let cw = ControlWord::from_bytes([0x55, 0xfd, 0x78, 0x15, 0x27, 0xec, 0xa2, 0x29]);
81        let sch = cw.expand_block();
82        // Just verify first and last round key bytes are non-zero
83        assert!(sch.iter().any(|&b| b != 0));
84    }
85}