muscat 0.3.0

Multithreaded Side Channel Attacks Tool
Documentation
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
use itertools::Itertools;
use ndarray::{Array1, ArrayView1, s};
use num_traits::{AsPrimitive, One, Zero};
use std::{
    cmp::Ordering,
    ops::{Div, Range},
};

use crate::{Sample, processors::MeanVar};

/// Computes the centered product of "order" leakage samples
/// Used particularly when performing high-order SCA
#[derive(Debug)]
pub struct CenteredProduct {
    /// Sum of traces
    acc: Array1<i64>,
    /// Number of traces processed
    count: usize,
    /// Mean of traces
    mean: Array1<f32>,
    /// Indices of samples to combine
    intervals: Vec<Range<i32>>,
    /// Boolean to ensure that finalize function happened before apply
    processed: bool,
}

impl CenteredProduct {
    /// Creates a new CenteredProduct processor.
    ///
    /// # Arguments
    ///
    /// * `size` - Number of samples per trace
    /// * `intervals` - Intervals to combine
    pub fn new(size: usize, intervals: Vec<Range<i32>>) -> Self {
        Self {
            acc: Array1::zeros(size),
            count: 0,
            intervals,
            processed: false,
            mean: Array1::zeros(size),
        }
    }

    /// Processes an input trace to update internal accumulators.
    pub fn process<T: Into<i64> + Copy>(&mut self, trace: ArrayView1<T>) {
        let size = self.acc.len();
        for i in 0..size {
            let x = trace[i].into();
            self.acc[i] += x;
        }
        self.count += 1
    }

    /// Compute the mean
    pub fn finalize(&mut self) {
        if self.count != 0 {
            self.mean = self.acc.mapv(|x| x as f32 / self.count as f32)
        }
        self.processed = true
    }

    /// Apply the processing to an input trace
    /// The centered product substract the mean of the traces and then perform products between every input time samples
    pub fn apply<T: Into<f32> + Copy>(&self, trace: ArrayView1<T>) -> Array1<f32> {
        // First we substract the mean trace
        let centered_trace: Array1<f32> = trace.mapv(|x| x.into()) - &self.mean;
        let length_out_trace: usize = self.intervals.iter().map(|x| x.len()).product();

        let mut centered_product_trace = Array1::ones(length_out_trace);

        // Then we do the products
        let multi_prod = (0..self.intervals.len())
            .map(|i| self.intervals[i].clone())
            .multi_cartesian_product(); //NOTE/TODO: maybe this can go in the struct parameters, which could improve performances

        for (idx, combination) in multi_prod.enumerate() {
            for i in combination {
                centered_product_trace[idx] *= centered_trace[i as usize] as f32;
            }
        }

        centered_product_trace
    }
}

/// Elevates parts of a trace to a certain power
#[derive(Debug)]
pub struct Power {
    intervals: Vec<Range<i32>>,
    power: i32,
}

impl Power {
    /// Creates a new Power processor.
    ///
    /// # Arguments
    ///
    /// * `intervals` - Intervals to elevate to the power
    /// * `power` - Power to elevate
    pub fn new(intervals: Vec<Range<i32>>, power: i32) -> Self {
        Self { intervals, power }
    }

    /// Processes an input trace
    pub fn process<T: Into<i64> + Copy>(&self, trace: ArrayView1<T>) -> Array1<f32> {
        // Concatenate the slices specified by the ranges
        let result: Array1<_> = self
            .intervals
            .iter()
            .flat_map(|range| trace.slice(s![range.clone()]).to_owned())
            .map(|val| val.into() as f32)
            .collect();

        result.mapv(|result| result.powi(self.power))
    }
}

/// Standardization of the traces by removing the mean and scaling to unit variance
pub struct StandardScaler<T>
where
    T: Sample,
{
    /// meanVar processor
    meanvar: MeanVar<T>,
    /// mean
    mean: Array1<f32>,
    /// std
    std: Array1<f32>,
}

impl<T> StandardScaler<T>
where
    T: Sample + Copy,
{
    pub fn new(size: usize) -> Self {
        Self {
            meanvar: MeanVar::new(size),
            mean: Array1::zeros(size),
            std: Array1::zeros(size),
        }
    }

    /// Processes an input trace to update internal accumulators.
    pub fn process(&mut self, trace: ArrayView1<T>) {
        self.meanvar.process(trace);
    }

    /// Compute mean and var
    pub fn finalize(&mut self) {
        self.mean = self.meanvar.mean();
        self.std = self.meanvar.var().mapv(f32::sqrt);
    }

    /// Apply the processing to an input trace
    pub fn apply(&self, trace: ArrayView1<T>) -> Array1<f32> {
        (trace.mapv(|x| <T as Sample>::Container::from(x).as_()) - &self.mean) / &self.std
    }
}

pub use dtw::dist;

/// Align traces using elastic alignment [1]. Elastic alignment is a dynamic alignment algorithm
/// based on FastDTW.
///
/// # Examples
/// ```rust
/// use muscat::preprocessors::{ElasticAlignment, dist::euclidean_distance};
/// use ndarray::array;
///
/// let reference_trace = array![77, 117, 5, 51, 91, -12, -33];
/// let trace_to_align = array![77, 117, 13, 15, 5, 51, 91];
/// let elastic_alignment = ElasticAlignment::new(reference_trace, 1, euclidean_distance);
///
/// let aligned_trace = elastic_alignment.align(trace_to_align.view());
/// ```
///
/// # References
/// [1] van Woudenberg, J.G.J., Witteman, M.F., Bakker, B. (2011). Improving Differential Power
/// Analysis by Elastic Alignment. In: Kiayias, A. (eds) Topics in Cryptology – CT-RSA 2011. CT-RSA
/// 2011. Lecture Notes in Computer Science, vol 6558. Springer, Berlin, Heidelberg.
/// https://doi.org/10.1007/978-3-642-19074-2_8
pub struct ElasticAlignment<T, D>
where
    D: Fn(T, T) -> T,
{
    reference_trace: Array1<T>,
    /// FastDTW radius
    radius: usize,
    /// Distance function
    dist: D,
}

impl<T, D> ElasticAlignment<T, D>
where
    T: dtw::Average + dtw::SumContainer + Copy + Zero + 'static,
    T::Container: Zero + One + Div<Output = T::Container> + AsPrimitive<T>,
    D: Fn(T, T) -> T,
{
    /// Creates a new [`ElasticAlignment`] with the given reference trace and distance function.
    pub fn new(reference_trace: Array1<T>, radius: usize, dist: D) -> Self {
        Self {
            reference_trace,
            radius,
            dist,
        }
    }

    /// Align given trace using elastic alignment (see [`ElasticAlignment`]).
    pub fn align_with_cmp<C>(&self, trace: ArrayView1<T>, cmp: &C) -> Array1<T>
    where
        C: Fn(&T::Container, &T::Container) -> Ordering,
    {
        let warp_path = dtw::fast_dtw_with_cmp(
            self.reference_trace.as_slice().unwrap(),
            trace.as_slice().unwrap(),
            self.radius,
            &self.dist,
            cmp,
        );

        let mut aligned_trace = Array1::zeros([trace.len()]);
        let mut k = 0;
        for j in 0..trace.len() {
            let mut count = T::Container::zero();
            let mut sum = T::Container::zero();

            while k < warp_path.len() && warp_path[k].0 == j {
                count = count + T::Container::one();
                sum = sum + T::Container::from(trace[warp_path[k].1]);
                k += 1;
            }

            aligned_trace[j] = (sum / count).as_();
        }

        aligned_trace
    }
}

impl<T, D> ElasticAlignment<T, D>
where
    T: dtw::Average + dtw::SumContainer + Copy + Zero + 'static,
    T::Container: Zero + One + Div<Output = T::Container> + AsPrimitive<T> + Ord,
    D: Fn(T, T) -> T,
{
    /// Align given trace using elastic alignment (see [`ElasticAlignment`]).
    ///
    /// NOTE: See [`ElasticAlignment::align_with_cmp`] for type than do not implement [`Ord`].
    pub fn align(&self, trace: ArrayView1<T>) -> Array1<T> {
        self.align_with_cmp(trace, &T::Container::cmp)
    }
}

#[cfg(test)]
mod tests {
    use crate::preprocessors::{CenteredProduct, ElasticAlignment, Power, StandardScaler, dist};
    use ndarray::array;

    fn round_to_2_digits(x: f32) -> f32 {
        (x * 100f32).round() / 100f32
    }

    #[test]
    fn test_centered_product() {
        let mut processor = CenteredProduct::new(5, vec![0..2, 3..5]);
        processor.process(array![0i16, 1i16, 2i16, -3i16, -4i16].view());
        processor.finalize();
        assert_eq!(
            processor.apply(array![0i16, 1i16, 2i16, -3i16, -4i16].view()),
            array![0f32, 0f32, 0f32, 0f32]
        );
        let traces = [
            array![77, 137, 51, 91],
            array![72, 61, 91, 83],
            array![39, 49, 52, 23],
            array![26, 114, 63, 45],
            array![30, 8, 97, 91],
            array![13, 68, 7, 45],
            array![17, 181, 60, 34],
            array![43, 88, 76, 78],
            array![0, 36, 35, 0],
            array![93, 191, 49, 26],
        ];

        let mut processor2 = CenteredProduct::new(4, vec![0..1, 1..2, 2..4]);
        for t in traces.iter() {
            processor2.process(t.view());
        }
        processor2.finalize();

        let expected_results = [
            array![-11169.72, 61984.08],
            array![-32942.77, -31440.83],
            array![-540.46, -2533.96],
            array![-1521.45, 2049.30],
            array![36499.88, 36969.02],
            array![-36199.24, -4675.44],
            array![-3999.12, 37044.47],
            array![-189.74, -279.84],
            array![-54268.83, -121223.88],
            array![-46231.64, -130058.23],
        ];

        for (i, t) in traces.iter().enumerate() {
            assert_eq!(
                processor2
                    .apply(t.mapv(|x| x as f32).view())
                    .mapv(round_to_2_digits),
                expected_results[i]
            );
        }
    }

    #[test]
    fn test_power() {
        let t = array![-1, 2, -3, 4, -5, 6];
        let processor1 = Power {
            intervals: vec![0..2, 4..6],
            power: 1,
        };
        let processor2 = Power {
            intervals: vec![0..2, 4..6],
            power: 2,
        };
        let processor3 = Power {
            intervals: vec![0..2, 4..6],
            power: 3,
        };
        let expected_results = [
            array![-1.0, 2.0, -5.0, 6.0],
            array![1.0, 4.0, 25.0, 36.0],
            array![-1.0, 8.0, -125.0, 216.0],
        ];

        assert_eq!(
            processor1.process(t.view()).mapv(round_to_2_digits),
            expected_results[0]
        );
        assert_eq!(
            processor2.process(t.view()).mapv(round_to_2_digits),
            expected_results[1]
        );
        assert_eq!(
            processor3.process(t.view()).mapv(round_to_2_digits),
            expected_results[2]
        );
    }

    #[test]
    fn test_standard_scaler() {
        let traces = [
            array![77, 137, 51, 91],
            array![72, 61, 91, 83],
            array![39, 49, 52, 23],
            array![26, 114, 63, 45],
            array![30, 8, 97, 91],
            array![13, 68, 7, 45],
            array![17, 181, 60, 34],
            array![43, 88, 76, 78],
            array![0, 36, 35, 0],
            array![93, 191, 49, 26],
        ];

        let mut processor = StandardScaler::new(4);
        for t in traces.iter() {
            processor.process(t.view());
        }
        processor.finalize();

        let expected_results = [
            array![1.25, 0.75, -0.28, 1.29],
            array![1.07, -0.56, 1.32, 1.03],
            array![-0.07, -0.76, -0.24, -0.94],
            array![-0.52, 0.36, 0.20, -0.22],
            array![-0.38, -1.47, 1.55, 1.29],
            array![-0.97, -0.44, -2.04, -0.22],
            array![-0.83, 1.51, 0.08, -0.58],
            array![0.07, -0.09, 0.72, 0.86],
            array![-1.42, -0.99, -0.92, -1.69],
            array![1.80, 1.68, -0.36, -0.84],
        ];

        for (i, t) in traces.iter().enumerate() {
            assert_eq!(
                processor.apply(t.view()).mapv(round_to_2_digits),
                expected_results[i]
            );
        }
    }

    #[test]
    fn test_elastic_align() {
        let reference_trace = array![77, 117, 5, 51, 91, -12, -33];
        let trace = array![77, 117, 13, 15, 5, 51, 91];

        let elastic_alignment =
            ElasticAlignment::new(reference_trace.clone(), 1, dist::euclidean_distance);
        assert_eq!(
            elastic_alignment.align(trace.view()),
            array![77, 117, 11, 51, 51, 51, 91]
        );
    }

    #[test]
    fn test_elastic_align_same_trace() {
        let reference_trace = array![77, 117, 5, 51, 91, -12, -33];

        let elastic_alignment =
            ElasticAlignment::new(reference_trace.clone(), 1, dist::euclidean_distance);
        assert_eq!(
            elastic_alignment.align(reference_trace.view()),
            reference_trace
        );
    }
}