1#![no_std]
2#![doc = include_str!("../README.md")]
3#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
5 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
6)]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
9
10pub use cipher;
11
12use cipher::{
13 Block, BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure,
14 StreamCipherCore, StreamCipherCoreWrapper, StreamCipherSeekCore,
15 array::{Array, typenum::Unsigned},
16 consts::{U4, U6, U8, U10, U24, U32, U64},
17};
18use core::marker::PhantomData;
19
20#[cfg(feature = "zeroize")]
21use cipher::zeroize::{Zeroize, ZeroizeOnDrop};
22
23mod backends;
24mod xsalsa;
25
26pub use xsalsa::{XSalsa8, XSalsa12, XSalsa20, XSalsaCore, hsalsa};
27
28pub type Salsa8 = StreamCipherCoreWrapper<SalsaCore<U4>>;
31
32pub type Salsa12 = StreamCipherCoreWrapper<SalsaCore<U6>>;
35
36pub type Salsa20 = StreamCipherCoreWrapper<SalsaCore<U10>>;
39
40pub type Key = Array<u8, U32>;
42
43pub type Nonce = Array<u8, U8>;
45
46pub type XNonce = Array<u8, U24>;
48
49const STATE_WORDS: usize = 16;
51
52const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
54
55pub struct SalsaCore<R: Unsigned> {
57 state: [u32; STATE_WORDS],
59 rounds: PhantomData<R>,
61}
62
63impl<R: Unsigned> SalsaCore<R> {
64 pub fn from_raw_state(state: [u32; STATE_WORDS]) -> Self {
69 Self {
70 state,
71 rounds: PhantomData,
72 }
73 }
74}
75
76impl<R: Unsigned> KeySizeUser for SalsaCore<R> {
77 type KeySize = U32;
78}
79
80impl<R: Unsigned> IvSizeUser for SalsaCore<R> {
81 type IvSize = U8;
82}
83
84impl<R: Unsigned> BlockSizeUser for SalsaCore<R> {
85 type BlockSize = U64;
86}
87
88impl<R: Unsigned> KeyIvInit for SalsaCore<R> {
89 fn new(key: &Key, iv: &Nonce) -> Self {
90 let mut state = [0u32; STATE_WORDS];
91 state[0] = CONSTANTS[0];
92
93 for (i, chunk) in key[..16].chunks(4).enumerate() {
94 state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
95 }
96
97 state[5] = CONSTANTS[1];
98
99 for (i, chunk) in iv.chunks(4).enumerate() {
100 state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
101 }
102
103 state[8] = 0;
104 state[9] = 0;
105 state[10] = CONSTANTS[2];
106
107 for (i, chunk) in key[16..].chunks(4).enumerate() {
108 state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
109 }
110
111 state[15] = CONSTANTS[3];
112
113 Self {
114 state,
115 rounds: PhantomData,
116 }
117 }
118}
119
120impl<R: Unsigned> StreamCipherCore for SalsaCore<R> {
121 #[inline(always)]
122 fn remaining_blocks(&self) -> Option<usize> {
123 let rem = u64::MAX - self.get_block_pos();
124 rem.try_into().ok()
125 }
126 fn process_with_backend(&mut self, f: impl StreamCipherClosure<BlockSize = Self::BlockSize>) {
127 f.call(&mut backends::soft::Backend(self));
128 }
129}
130
131impl<R: Unsigned> StreamCipherSeekCore for SalsaCore<R> {
132 type Counter = u64;
133
134 #[inline(always)]
135 fn get_block_pos(&self) -> u64 {
136 (self.state[8] as u64) + ((self.state[9] as u64) << 32)
137 }
138
139 #[inline(always)]
140 fn set_block_pos(&mut self, pos: u64) {
141 self.state[8] = (pos & 0xffff_ffff) as u32;
142 self.state[9] = ((pos >> 32) & 0xffff_ffff) as u32;
143 }
144}
145
146#[cfg(feature = "zeroize")]
147impl<R: Unsigned> Drop for SalsaCore<R> {
148 fn drop(&mut self) {
149 self.state.zeroize();
150 }
151}
152
153#[cfg(feature = "zeroize")]
154impl<R: Unsigned> ZeroizeOnDrop for SalsaCore<R> {}