salsa20/
xsalsa.rs

1//! XSalsa20 is an extended nonce variant of Salsa20
2
3use super::{quarter_round, Key, Nonce, SalsaCore, Unsigned, XNonce, CONSTANTS};
4use cipher::{
5    consts::{U10, U16, U24, U32, U4, U6, U64},
6    generic_array::GenericArray,
7    BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherCore, StreamCipherCoreWrapper,
8    StreamCipherSeekCore, StreamClosure,
9};
10
11#[cfg(feature = "zeroize")]
12use cipher::zeroize::ZeroizeOnDrop;
13
14/// XSalsa20 is a Salsa20 variant with an extended 192-bit (24-byte) nonce.
15///
16/// Based on the paper "Extending the Salsa20 Nonce":
17///
18/// <https://cr.yp.to/snuffle/xsalsa-20081128.pdf>
19pub type XSalsa20 = StreamCipherCoreWrapper<XSalsaCore<U10>>;
20/// XSalsa12 stream cipher (reduced-round variant of [`XSalsa20`] with 12 rounds)
21pub type XSalsa12 = StreamCipherCoreWrapper<XSalsaCore<U6>>;
22/// XSalsa8 stream cipher (reduced-round variant of [`XSalsa20`] with 8 rounds)
23pub type XSalsa8 = StreamCipherCoreWrapper<XSalsaCore<U4>>;
24
25/// The XSalsa core function.
26pub struct XSalsaCore<R: Unsigned>(SalsaCore<R>);
27
28impl<R: Unsigned> KeySizeUser for XSalsaCore<R> {
29    type KeySize = U32;
30}
31
32impl<R: Unsigned> IvSizeUser for XSalsaCore<R> {
33    type IvSize = U24;
34}
35
36impl<R: Unsigned> BlockSizeUser for XSalsaCore<R> {
37    type BlockSize = U64;
38}
39
40impl<R: Unsigned> KeyIvInit for XSalsaCore<R> {
41    #[inline]
42    fn new(key: &Key, iv: &XNonce) -> Self {
43        let subkey = hsalsa::<R>(key, iv[..16].as_ref().into());
44        let mut padded_iv = Nonce::default();
45        padded_iv.copy_from_slice(&iv[16..]);
46        XSalsaCore(SalsaCore::new(&subkey, &padded_iv))
47    }
48}
49
50impl<R: Unsigned> StreamCipherCore for XSalsaCore<R> {
51    #[inline(always)]
52    fn remaining_blocks(&self) -> Option<usize> {
53        self.0.remaining_blocks()
54    }
55
56    #[inline(always)]
57    fn process_with_backend(&mut self, f: impl StreamClosure<BlockSize = Self::BlockSize>) {
58        self.0.process_with_backend(f);
59    }
60}
61
62impl<R: Unsigned> StreamCipherSeekCore for XSalsaCore<R> {
63    type Counter = u64;
64
65    #[inline(always)]
66    fn get_block_pos(&self) -> u64 {
67        self.0.get_block_pos()
68    }
69
70    #[inline(always)]
71    fn set_block_pos(&mut self, pos: u64) {
72        self.0.set_block_pos(pos);
73    }
74}
75
76#[cfg(feature = "zeroize")]
77#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
78impl<R: Unsigned> ZeroizeOnDrop for XSalsaCore<R> {}
79
80/// The HSalsa20 function defined in the paper "Extending the Salsa20 nonce"
81///
82/// <https://cr.yp.to/snuffle/xsalsa-20110204.pdf>
83///
84/// HSalsa20 takes 512-bits of input:
85///
86/// - Constants (`u32` x 4)
87/// - Key (`u32` x 8)
88/// - Nonce (`u32` x 4)
89///
90/// It produces 256-bits of output suitable for use as a Salsa20 key
91pub fn hsalsa<R: Unsigned>(key: &Key, input: &GenericArray<u8, U16>) -> GenericArray<u8, U32> {
92    #[inline(always)]
93    fn to_u32(chunk: &[u8]) -> u32 {
94        u32::from_le_bytes(chunk.try_into().unwrap())
95    }
96
97    let mut state = [0u32; 16];
98    state[0] = CONSTANTS[0];
99    state[1..5]
100        .iter_mut()
101        .zip(key[0..16].chunks_exact(4))
102        .for_each(|(v, chunk)| *v = to_u32(chunk));
103    state[5] = CONSTANTS[1];
104    state[6..10]
105        .iter_mut()
106        .zip(input.chunks_exact(4))
107        .for_each(|(v, chunk)| *v = to_u32(chunk));
108    state[10] = CONSTANTS[2];
109    state[11..15]
110        .iter_mut()
111        .zip(key[16..].chunks_exact(4))
112        .for_each(|(v, chunk)| *v = to_u32(chunk));
113    state[15] = CONSTANTS[3];
114
115    // 20 rounds consisting of 10 column rounds and 10 diagonal rounds
116    for _ in 0..R::USIZE {
117        // column rounds
118        quarter_round(0, 4, 8, 12, &mut state);
119        quarter_round(5, 9, 13, 1, &mut state);
120        quarter_round(10, 14, 2, 6, &mut state);
121        quarter_round(15, 3, 7, 11, &mut state);
122
123        // diagonal rounds
124        quarter_round(0, 1, 2, 3, &mut state);
125        quarter_round(5, 6, 7, 4, &mut state);
126        quarter_round(10, 11, 8, 9, &mut state);
127        quarter_round(15, 12, 13, 14, &mut state);
128    }
129
130    let mut output = GenericArray::default();
131    let key_idx: [usize; 8] = [0, 5, 10, 15, 6, 7, 8, 9];
132
133    for (i, chunk) in output.chunks_exact_mut(4).enumerate() {
134        chunk.copy_from_slice(&state[key_idx[i]].to_le_bytes());
135    }
136
137    output
138}