1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! The ChaCha20 block function. Defined in RFC 8439 Section 2.3.
//!
//! <https://tools.ietf.org/html/rfc8439#section-2.3>

use salsa20_core::{CONSTANTS, IV_WORDS, KEY_WORDS, STATE_WORDS};

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub(crate) mod sse2;

/// The ChaCha20 block function
///
/// While ChaCha20 is a stream cipher, not a block cipher, its core
/// primitive is a function which acts on a 512-bit block
// TODO(tarcieri): zeroize? need to make sure we're actually copying first
pub(crate) struct Block {
    /// Internal state of the block function
    state: [u32; STATE_WORDS],
}

impl Block {
    /// Generate a block
    pub(crate) fn generate(
        key: &[u32; KEY_WORDS],
        iv: [u32; IV_WORDS],
        counter: u64,
    ) -> [u32; STATE_WORDS] {
        let mut block = Self {
            state: [
                CONSTANTS[0],
                CONSTANTS[1],
                CONSTANTS[2],
                CONSTANTS[3],
                key[0],
                key[1],
                key[2],
                key[3],
                key[4],
                key[5],
                key[6],
                key[7],
                (counter & 0xffff_ffff) as u32,
                ((counter >> 32) & 0xffff_ffff) as u32,
                iv[0],
                iv[1],
            ],
        };

        block.rounds();
        block.finish(key, iv, counter)
    }

    /// Run the 20 rounds (i.e. 10 double rounds) of ChaCha20
    #[inline]
    fn rounds(&mut self) {
        self.double_round();
        self.double_round();
        self.double_round();
        self.double_round();
        self.double_round();

        self.double_round();
        self.double_round();
        self.double_round();
        self.double_round();
        self.double_round();
    }

    /// Double round function
    #[inline]
    fn double_round(&mut self) {
        let state = &mut self.state;
        quarter_round(0, 4, 8, 12, state);
        quarter_round(1, 5, 9, 13, state);
        quarter_round(2, 6, 10, 14, state);
        quarter_round(3, 7, 11, 15, state);
        quarter_round(0, 5, 10, 15, state);
        quarter_round(1, 6, 11, 12, state);
        quarter_round(2, 7, 8, 13, state);
        quarter_round(3, 4, 9, 14, state);
    }

    /// Finish computing a block
    #[inline]
    fn finish(
        self,
        key: &[u32; KEY_WORDS],
        iv: [u32; IV_WORDS],
        counter: u64,
    ) -> [u32; STATE_WORDS] {
        let mut state = self.state;

        state[0] = state[0].wrapping_add(CONSTANTS[0]);
        state[1] = state[1].wrapping_add(CONSTANTS[1]);
        state[2] = state[2].wrapping_add(CONSTANTS[2]);
        state[3] = state[3].wrapping_add(CONSTANTS[3]);
        state[4] = state[4].wrapping_add(key[0]);
        state[5] = state[5].wrapping_add(key[1]);
        state[6] = state[6].wrapping_add(key[2]);
        state[7] = state[7].wrapping_add(key[3]);
        state[8] = state[8].wrapping_add(key[4]);
        state[9] = state[9].wrapping_add(key[5]);
        state[10] = state[10].wrapping_add(key[6]);
        state[11] = state[11].wrapping_add(key[7]);
        state[12] = state[12].wrapping_add((counter & 0xffff_ffff) as u32);
        state[13] = state[13].wrapping_add(((counter >> 32) & 0xffff_ffff) as u32);
        state[14] = state[14].wrapping_add(iv[0]);
        state[15] = state[15].wrapping_add(iv[1]);

        state
    }
}

/// The ChaCha20 quarter round function
#[inline]
pub(crate) fn quarter_round(a: usize, b: usize, c: usize, d: usize, state: &mut [u32; 16]) {
    state[a] = state[a].wrapping_add(state[b]);
    state[d] ^= state[a];
    state[d] = state[d].rotate_left(16);

    state[c] = state[c].wrapping_add(state[d]);
    state[b] ^= state[c];
    state[b] = state[b].rotate_left(12);

    state[a] = state[a].wrapping_add(state[b]);
    state[d] ^= state[a];
    state[d] = state[d].rotate_left(8);

    state[c] = state[c].wrapping_add(state[d]);
    state[b] ^= state[c];
    state[b] = state[b].rotate_left(7);
}