Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor library organization #15

Merged
merged 2 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benches/cpa.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use muscat::cpa::{self, Cpa, CpaProcessor};
use muscat::cpa_normal;
use muscat::distinguishers::cpa::{self, Cpa, CpaProcessor};
use muscat::distinguishers::cpa_normal;
use muscat::leakage::{hw, sbox};
use ndarray::{Array2, ArrayView1, Axis};
use ndarray_rand::rand::{rngs::StdRng, SeedableRng};
Expand Down
2 changes: 1 addition & 1 deletion benches/dpa.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use muscat::dpa::{dpa, Dpa, DpaProcessor};
use muscat::distinguishers::dpa::{dpa, Dpa, DpaProcessor};
use muscat::leakage::sbox;
use ndarray::{Array1, Array2};
use ndarray_rand::rand::{rngs::StdRng, SeedableRng};
Expand Down
2 changes: 1 addition & 1 deletion benches/snr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use muscat::processors::{snr, Snr};
use muscat::leakage_detection::{snr, Snr};
use ndarray::{Array1, Array2};
use ndarray_rand::rand::{rngs::StdRng, SeedableRng};
use ndarray_rand::rand_distr::Uniform;
Expand Down
2 changes: 1 addition & 1 deletion examples/cpa.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use indicatif::ProgressIterator;
use muscat::cpa_normal::CpaProcessor;
use muscat::distinguishers::cpa_normal::CpaProcessor;
use muscat::leakage::{hw, sbox};
use muscat::util::{progress_bar, read_array2_from_npy_file, save_array2};
use ndarray::*;
Expand Down
2 changes: 1 addition & 1 deletion examples/cpa_partioned.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use indicatif::ProgressIterator;
use muscat::cpa::CpaProcessor;
use muscat::distinguishers::cpa::CpaProcessor;
use muscat::leakage::{hw, sbox};
use muscat::util::{progress_bar, read_array2_from_npy_file, save_array};
use rayon::prelude::{ParallelBridge, ParallelIterator};
Expand Down
2 changes: 1 addition & 1 deletion examples/dpa.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use indicatif::ProgressIterator;
use muscat::dpa::DpaProcessor;
use muscat::distinguishers::dpa::DpaProcessor;
use muscat::leakage::sbox;
use muscat::util::read_array2_from_npy_file;
use ndarray::{s, Array1};
Expand Down
2 changes: 1 addition & 1 deletion examples/rank.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use indicatif::ProgressIterator;
use muscat::cpa::CpaProcessor;
use muscat::distinguishers::cpa::CpaProcessor;
use muscat::leakage::{hw, sbox};
use muscat::util::{progress_bar, read_array2_from_npy_file, save_array};
use ndarray::s;
Expand Down
2 changes: 1 addition & 1 deletion examples/snr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use indicatif::ProgressIterator;
use muscat::processors::Snr;
use muscat::leakage_detection::Snr;
use muscat::quicklog::{BatchIter, Log};
use muscat::util::{progress_bar, save_array};
use rayon::prelude::{ParallelBridge, ParallelIterator};
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/cpa_normal.rs → src/distinguishers/cpa_normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
use rayon::iter::{ParallelBridge, ParallelIterator};
use std::{iter::zip, ops::Add};

use crate::cpa::Cpa;
use crate::distinguishers::cpa::Cpa;

/// Compute the [`Cpa`] of the given traces using [`CpaProcessor`].
///
Expand Down
File renamed without changes.
3 changes: 3 additions & 0 deletions src/distinguishers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod cpa;
pub mod cpa_normal;
pub mod dpa;
263 changes: 263 additions & 0 deletions src/leakage_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
//! Leakage detection methods
use crate::processors::MeanVar;
use ndarray::{s, Array1, Array2, ArrayView1, ArrayView2, Axis};
use rayon::iter::{ParallelBridge, ParallelIterator};
use std::ops::Add;

/// Computes the SNR of the given traces.
///
/// `get_class` is a function returning the class of the given trace by index.
///
/// # Panics
/// Panic if `chunk_size` is 0.
pub fn snr<T, F>(
leakages: ArrayView2<T>,
classes: usize,
get_class: F,
chunk_size: usize,
) -> Array1<f64>
where
T: Into<i64> + Copy + Sync,
F: Fn(usize) -> usize + Sync,
{
assert!(chunk_size > 0);

// From benchmarks fold + reduce_with is faster than map + reduce/reduce_with and fold + reduce
leakages
.axis_chunks_iter(Axis(0), chunk_size)
.enumerate()
.par_bridge()
.fold(
|| Snr::new(leakages.shape()[1], classes),
|mut snr, (chunk_idx, leakages_chunk)| {
for i in 0..leakages_chunk.shape()[0] {
snr.process(leakages_chunk.row(i), get_class(chunk_idx + i));
}
snr
},
)
.reduce_with(|a, b| a + b)
.unwrap()
.snr()
}

/// Processes traces to calculate the Signal-to-Noise Ratio.
#[derive(Debug, Clone)]
pub struct Snr {
mean_var: MeanVar,
/// Sum of traces per class
classes_sum: Array2<i64>,
/// Counts the number of traces per class
classes_count: Array1<usize>,
}

impl Snr {
/// Creates a new SNR processor.
///
/// # Arguments
///
/// * `size` - Size of the input traces
/// * `classes` - Number of classes
pub fn new(size: usize, num_classes: usize) -> Self {
Self {
mean_var: MeanVar::new(size),
classes_sum: Array2::zeros((num_classes, size)),
classes_count: Array1::zeros(num_classes),
}
}

/// Processes an input trace to update internal accumulators.
///
/// # Panics
/// Panics in debug if the length of the trace is different from the size of [`Snr`].
pub fn process<T: Into<i64> + Copy>(&mut self, trace: ArrayView1<T>, class: usize) {
debug_assert!(trace.len() == self.size());

self.mean_var.process(trace);

for i in 0..self.size() {
self.classes_sum[[class, i]] += trace[i].into();
}

self.classes_count[class] += 1;
}

/// Returns the Signal-to-Noise Ratio of the traces.
/// SNR = V[E[L|X]] / E[V[L|X]]
pub fn snr(&self) -> Array1<f64> {
let size = self.size();

let mut acc: Array1<f64> = Array1::zeros(size);
for class in 0..self.num_classes() {
if self.classes_count[class] == 0 {
continue;
}

let class_sum = self.classes_sum.slice(s![class, ..]);
for i in 0..size {
acc[i] += (class_sum[i] as f64).powi(2) / (self.classes_count[class] as f64);
}
}

let var = self.mean_var.var();
let mean = self.mean_var.mean();
// V[E[L|X]]
let velx = (acc / self.mean_var.count() as f64) - mean.mapv(|x| x.powi(2));
1f64 / (var / velx - 1f64)
}

/// Returns the trace size handled
pub fn size(&self) -> usize {
self.classes_sum.shape()[1]
}

/// Returns the number of classes handled.
pub fn num_classes(&self) -> usize {
self.classes_count.len()
}

/// Determine if two [`Snr`] are compatible for addition.
///
/// If they were created with the same parameters, they are compatible.
fn is_compatible_with(&self, other: &Self) -> bool {
self.size() == other.size() && self.num_classes() == other.num_classes()
}
}

impl Add for Snr {
type Output = Self;

/// Merge computations of two [`Snr`]. Processors need to be compatible to be merged
/// together, otherwise it can panic or yield incoherent result (see
/// [`Snr::is_compatible_with`]).
///
/// # Panics
/// Panics in debug if the processors are not compatible.
fn add(self, rhs: Self) -> Self::Output {
debug_assert!(self.is_compatible_with(&rhs));

Self {
mean_var: self.mean_var + rhs.mean_var,
classes_sum: self.classes_sum + rhs.classes_sum,
classes_count: self.classes_count + rhs.classes_count,
}
}
}

/// Processes traces to calculate Welch's T-Test.
#[derive(Debug)]
pub struct TTest {
mean_var_1: MeanVar,
mean_var_2: MeanVar,
}

impl TTest {
/// Creates a new Welch's T-Test processor.
///
/// # Arguments
///
/// * `size` - Number of samples per trace
pub fn new(size: usize) -> Self {
Self {
mean_var_1: MeanVar::new(size),
mean_var_2: MeanVar::new(size),
}
}

/// Processes an input trace to update internal accumulators.
///
/// # Arguments
///
/// * `trace` - Input trace.
/// * `class` - Indicates to which of the two partitions the given trace belongs.
///
/// # Panics
/// Panics in debug if `trace.len() != self.size()`.
pub fn process<T: Into<i64> + Copy>(&mut self, trace: ArrayView1<T>, class: bool) {
debug_assert!(trace.len() == self.size());

if class {
self.mean_var_2.process(trace);
} else {
self.mean_var_1.process(trace);
}
}

/// Calculate and returns Welch's T-Test result.
pub fn ttest(&self) -> Array1<f64> {
// E(X1) - E(X2)
let q = self.mean_var_1.mean() - self.mean_var_2.mean();

// √(σ1²/N1 + σ2²/N2)
let d = ((self.mean_var_1.var() / self.mean_var_1.count() as f64)
+ (self.mean_var_2.var() / self.mean_var_2.count() as f64))
.mapv(f64::sqrt);
q / d
}

/// Returns the trace size handled.
pub fn size(&self) -> usize {
self.mean_var_1.size()
}

/// Determine if two [`TTest`] are compatible for addition.
///
/// If they were created with the same parameters, they are compatible.
fn is_compatible_with(&self, other: &Self) -> bool {
self.size() == other.size()
}
}

impl Add for TTest {
type Output = Self;

/// Merge computations of two [`TTest`]. Processors need to be compatible to be merged
/// together, otherwise it can panic or yield incoherent result (see
/// [`TTest::is_compatible_with`]).
///
/// # Panics
/// Panics in debug if the processors are not compatible.
fn add(self, rhs: Self) -> Self::Output {
debug_assert!(self.is_compatible_with(&rhs));

Self {
mean_var_1: self.mean_var_1 + rhs.mean_var_1,
mean_var_2: self.mean_var_2 + rhs.mean_var_2,
}
}
}

#[cfg(test)]
mod tests {
use super::TTest;
use ndarray::array;

#[test]
fn test_ttest() {
let mut processor = TTest::new(4);
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],
];
for (i, trace) in traces.iter().enumerate() {
processor.process(trace.view(), i % 3 == 0);
}
assert_eq!(
processor.ttest(),
array![
-1.0910344547297484,
-5.524921845887032,
0.29385284736362266,
0.23308466737856662
]
);
}
}
5 changes: 2 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
pub mod cpa;
pub mod cpa_normal;
pub mod dpa;
pub mod distinguishers;
pub mod leakage;
pub mod leakage_detection;
pub mod preprocessors;
pub mod processors;
pub mod trace;
Expand Down
Loading