diff --git a/benches/cpa.rs b/benches/cpa.rs index 126b36f..5dd2845 100644 --- a/benches/cpa.rs +++ b/benches/cpa.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use muscat::cpa::{self, Cpa}; +use muscat::cpa::{self, Cpa, CpaProcessor}; use muscat::cpa_normal; use muscat::leakage::{hw, sbox}; use ndarray::{Array2, ArrayView1, Axis}; @@ -13,7 +13,7 @@ pub fn leakage_model(value: usize, guess: usize) -> usize { } fn cpa_sequential(leakages: &Array2, plaintexts: &Array2) -> Cpa { - let mut cpa = Cpa::new(leakages.shape()[1], 256, 0, leakage_model); + let mut cpa = CpaProcessor::new(leakages.shape()[1], 256, 0, leakage_model); for i in 0..leakages.shape()[0] { cpa.update( @@ -22,9 +22,7 @@ fn cpa_sequential(leakages: &Array2, plaintexts: &Array2) -> Cpa { ); } - cpa.finalize(); - - cpa + cpa.finalize() } pub fn leakage_model_normal(value: ArrayView1, guess: usize) -> usize { @@ -34,7 +32,8 @@ pub fn leakage_model_normal(value: ArrayView1, guess: usize) -> usize { fn cpa_normal_sequential(leakages: &Array2, plaintexts: &Array2) -> cpa_normal::Cpa { let chunk_size = 500; - let mut cpa = cpa_normal::Cpa::new(leakages.shape()[1], chunk_size, 256, leakage_model_normal); + let mut cpa = + cpa_normal::CpaProcessor::new(leakages.shape()[1], chunk_size, 256, leakage_model_normal); for (leakages_chunk, plaintexts_chunk) in zip( leakages.axis_chunks_iter(Axis(0), chunk_size), @@ -43,9 +42,7 @@ fn cpa_normal_sequential(leakages: &Array2, plaintexts: &Array2) -> cpa cpa.update(leakages_chunk.map(|&x| x as f32).view(), plaintexts_chunk); } - cpa.finalize(); - - cpa + cpa.finalize() } fn bench_cpa(c: &mut Criterion) { diff --git a/benches/dpa.rs b/benches/dpa.rs index 55a9e07..9aad207 100644 --- a/benches/dpa.rs +++ b/benches/dpa.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use muscat::dpa::{dpa, Dpa}; +use muscat::dpa::{dpa, Dpa, DpaProcessor}; use muscat::leakage::sbox; use ndarray::{Array1, Array2}; use ndarray_rand::rand::{rngs::StdRng, SeedableRng}; @@ -10,19 +10,17 @@ fn selection_function(metadata: Array1, guess: usize) -> usize { sbox(metadata[1] ^ guess as u8).into() } -fn dpa_sequential(leakages: &Array2, plaintexts: &Array2) -> Dpa> { - let mut dpa = Dpa::new(leakages.shape()[1], 256, selection_function); +fn dpa_sequential(leakages: &Array2, plaintexts: &Array2) -> Dpa { + let mut dpa = DpaProcessor::new(leakages.shape()[1], 256, selection_function); for i in 0..leakages.shape()[0] { dpa.update(leakages.row(i), plaintexts.row(i).to_owned()); } - dpa.finalize(); - - dpa + dpa.finalize() } -fn dpa_parallel(leakages: &Array2, plaintexts: &Array2) -> Dpa> { +fn dpa_parallel(leakages: &Array2, plaintexts: &Array2) -> Dpa { dpa( leakages.view(), plaintexts diff --git a/examples/cpa.rs b/examples/cpa.rs index 73f1dde..17eaa08 100644 --- a/examples/cpa.rs +++ b/examples/cpa.rs @@ -1,6 +1,6 @@ use anyhow::Result; use indicatif::ProgressIterator; -use muscat::cpa_normal::*; +use muscat::cpa_normal::CpaProcessor; use muscat::leakage::{hw, sbox}; use muscat::util::{progress_bar, read_array2_from_npy_file, save_array2}; use ndarray::*; @@ -30,11 +30,11 @@ fn cpa() -> Result<()> { let plaintext = read_array2_from_npy_file::(&dir_p)?; let len_traces = leakages.shape()[0]; - let mut cpa_parallel = ((0..len_traces).step_by(batch)) + let cpa_parallel = ((0..len_traces).step_by(batch)) .progress_with(progress_bar(len_traces)) .par_bridge() .map(|row_number| { - let mut cpa = Cpa::new(size, batch, guess_range, leakage_model); + let mut cpa = CpaProcessor::new(size, batch, guess_range, leakage_model); let range_rows = row_number..row_number + batch; let range_samples = start_sample..end_sample; let sample_traces = leakages @@ -45,14 +45,14 @@ fn cpa() -> Result<()> { cpa }) .reduce( - || Cpa::new(size, batch, guess_range, leakage_model), + || CpaProcessor::new(size, batch, guess_range, leakage_model), |x, y| x + y, ); - cpa_parallel.finalize(); - println!("Guessed key = {}", cpa_parallel.pass_guess()); + let cpa = cpa_parallel.finalize(); + println!("Guessed key = {}", cpa.pass_guess()); - save_array2("results/corr.npy", cpa_parallel.pass_corr_array().view())?; + save_array2("results/corr.npy", cpa.pass_corr_array().view())?; Ok(()) } @@ -68,7 +68,7 @@ fn success() -> Result<()> { let nfiles = 13; // Number of files in the directory. TBD: Automating this value let rank_traces = 1000; - let mut cpa = Cpa::new(size, batch, guess_range, leakage_model); + let mut cpa = CpaProcessor::new(size, batch, guess_range, leakage_model); cpa.success_traces(rank_traces); for i in (0..nfiles).progress() { @@ -89,7 +89,7 @@ fn success() -> Result<()> { } } - cpa.finalize(); + let cpa = cpa.finalize(); println!("Guessed key = {}", cpa.pass_guess()); // save corr key curves in npy diff --git a/examples/cpa_partioned.rs b/examples/cpa_partioned.rs index 6903c25..e1ad877 100644 --- a/examples/cpa_partioned.rs +++ b/examples/cpa_partioned.rs @@ -1,6 +1,6 @@ use anyhow::Result; use indicatif::ProgressIterator; -use muscat::cpa::*; +use muscat::cpa::CpaProcessor; use muscat::leakage::{hw, sbox}; use muscat::util::{progress_bar, read_array2_from_npy_file, save_array}; use rayon::prelude::{ParallelBridge, ParallelIterator}; @@ -23,7 +23,7 @@ fn cpa() -> Result<()> { let nfiles = 5; // Number of files in the directory. TBD: Automating this value /* Parallel operation using multi-threading on batches */ - let mut cpa = (0..nfiles) + let cpa = (0..nfiles) .progress_with(progress_bar(nfiles)) .map(|n| { let dir_l = format!("{folder}/l{n}.npy"); @@ -34,7 +34,7 @@ fn cpa() -> Result<()> { }) .par_bridge() .map(|batch| { - let mut c = Cpa::new(size, guess_range, target_byte, leakage_model); + let mut c = CpaProcessor::new(size, guess_range, target_byte, leakage_model); let len_leakage = batch.0.shape()[0]; for i in 0..len_leakage { c.update( @@ -45,15 +45,15 @@ fn cpa() -> Result<()> { c }) .reduce( - || Cpa::new(size, guess_range, target_byte, leakage_model), - |a: Cpa, b| a + b, + || CpaProcessor::new(size, guess_range, target_byte, leakage_model), + |a: CpaProcessor, b| a + b, ); - cpa.finalize(); - println!("Guessed key = {}", cpa.pass_guess()); + let cpa_result = cpa.finalize(); + println!("Guessed key = {}", cpa_result.pass_guess()); // save corr key curves in npy - save_array("../results/corr.npy", &cpa.pass_corr_array())?; + save_array("../results/corr.npy", &cpa_result.pass_corr_array())?; Ok(()) } diff --git a/examples/dpa.rs b/examples/dpa.rs index d80029c..370c829 100644 --- a/examples/dpa.rs +++ b/examples/dpa.rs @@ -1,6 +1,6 @@ use anyhow::Result; use indicatif::ProgressIterator; -use muscat::dpa::Dpa; +use muscat::dpa::DpaProcessor; use muscat::leakage::sbox; use muscat::util::read_array2_from_npy_file; use ndarray::{s, Array1, Array2}; @@ -26,7 +26,7 @@ fn dpa() -> Result<()> { let leakages: Array2 = read_array2_from_npy_file::(&dir_l)?; let plaintext: Array2 = read_array2_from_npy_file::(&dir_p)?; let len_traces = 20000; //leakages.shape()[0]; - let mut dpa = Dpa::new(size, guess_range, leakage_model); + let mut dpa = DpaProcessor::new(size, guess_range, leakage_model); for i in (0..len_traces).progress() { let tmp_trace = leakages .row(i) @@ -35,7 +35,7 @@ fn dpa() -> Result<()> { let tmp_metadata = plaintext.row(i); dpa.update(tmp_trace.view(), tmp_metadata.to_owned()); } - dpa.finalize(); + let dpa = dpa.finalize(); println!("Guessed key = {:02x}", dpa.pass_guess()); // let corr = dpa.pass_corr_array(); @@ -54,7 +54,7 @@ fn dpa_success() -> Result<()> { let leakages: Array2 = read_array2_from_npy_file::(&dir_l)?; let plaintext: Array2 = read_array2_from_npy_file::(&dir_p)?; let len_traces = leakages.shape()[0]; - let mut dpa = Dpa::new(size, guess_range, leakage_model); + let mut dpa = DpaProcessor::new(size, guess_range, leakage_model); let rank_traces: usize = 100; dpa.assign_rank_traces(rank_traces); @@ -67,6 +67,7 @@ fn dpa_success() -> Result<()> { dpa.update_success(tmp_trace.view(), tmp_metadata); } + let dpa = dpa.finalize(); println!("Guessed key = {:02x}", dpa.pass_guess()); // let succss = dpa.pass_rank().to_owned(); @@ -96,7 +97,7 @@ fn dpa_parallel() -> Result<()> { let tmp_metadata = plaintext .slice(s![range_rows..range_rows + batch, ..]) .to_owned(); - let mut dpa_inner = Dpa::new(size, guess_range, leakage_model); + let mut dpa_inner = DpaProcessor::new(size, guess_range, leakage_model); for i in 0..batch { let trace = tmp_leakages.row(i); let metadata = tmp_metadata.row(i).to_owned(); @@ -104,9 +105,12 @@ fn dpa_parallel() -> Result<()> { } dpa_inner }) - .reduce(|| Dpa::new(size, guess_range, leakage_model), |x, y| x + y); + .reduce( + || DpaProcessor::new(size, guess_range, leakage_model), + |x, y| x + y, + ); - dpa.finalize(); + let dpa = dpa.finalize(); println!("{:2x}", dpa.pass_guess()); // let corr = dpa.pass_corr_array(); diff --git a/examples/rank.rs b/examples/rank.rs index 2dd7dc7..a8b3cfd 100644 --- a/examples/rank.rs +++ b/examples/rank.rs @@ -1,9 +1,9 @@ use anyhow::Result; use indicatif::ProgressIterator; -use muscat::cpa::*; +use muscat::cpa::CpaProcessor; use muscat::leakage::{hw, sbox}; use muscat::util::{progress_bar, read_array2_from_npy_file, save_array}; -use ndarray::*; +use ndarray::s; use rayon::prelude::{ParallelBridge, ParallelIterator}; // traces format @@ -22,7 +22,7 @@ fn rank() -> Result<()> { let folder = String::from("../../data"); let nfiles = 5; let chunk = 3000; - let mut rank = Cpa::new(size, guess_range, target_byte, leakage_model); + let mut rank = CpaProcessor::new(size, guess_range, target_byte, leakage_model); for file in (0..nfiles).progress_with(progress_bar(nfiles)) { let dir_l = format!("{folder}/l{file}.npy"); let dir_p = format!("{folder}/p{file}.npy"); @@ -38,8 +38,8 @@ fn rank() -> Result<()> { let x = (0..chunk) .par_bridge() .fold( - || Cpa::new(size, guess_range, target_byte, leakage_model), - |mut r: Cpa, n| { + || CpaProcessor::new(size, guess_range, target_byte, leakage_model), + |mut r: CpaProcessor, n| { r.update( l_sample.row(n).map(|l| *l as usize).view(), p_sample.row(n).map(|p| *p as usize).view(), @@ -48,14 +48,15 @@ fn rank() -> Result<()> { }, ) .reduce( - || Cpa::new(size, guess_range, target_byte, leakage_model), + || CpaProcessor::new(size, guess_range, target_byte, leakage_model), |lhs, rhs| lhs + rhs, ); rank = rank + x; - rank.finalize(); } } + let rank = rank.finalize(); + // save rank key curves in npy save_array("../results/rank.npy", &rank.pass_rank())?; diff --git a/src/cpa.rs b/src/cpa.rs index a28ba8a..e3ccd50 100644 --- a/src/cpa.rs +++ b/src/cpa.rs @@ -6,7 +6,7 @@ use rayon::{ }; use std::{iter::zip, ops::Add}; -/// Computes the [`Cpa`] of the given traces. +/// Computes the [`Cpa`] of the given traces using [`CpaProcessor`]. /// /// # Panics /// - Panic if `leakages.shape()[0] != plaintexts.shape()[0]` @@ -25,13 +25,14 @@ where assert_eq!(leakages.shape()[0], plaintexts.shape()[0]); assert!(chunk_size > 0); - let mut cpa = zip( + zip( leakages.axis_chunks_iter(Axis(0), chunk_size), plaintexts.axis_chunks_iter(Axis(0), chunk_size), ) .par_bridge() .map(|(leakages_chunk, plaintexts_chunk)| { - let mut cpa = Cpa::new(leakages.shape()[1], guess_range, target_byte, leakage_func); + let mut cpa = + CpaProcessor::new(leakages.shape()[1], guess_range, target_byte, leakage_func); for i in 0..leakages_chunk.shape()[0] { cpa.update(leakages_chunk.row(i), plaintexts_chunk.row(i)); @@ -40,16 +41,47 @@ where cpa }) .reduce( - || Cpa::new(leakages.shape()[1], guess_range, target_byte, leakage_func), + || CpaProcessor::new(leakages.shape()[1], guess_range, target_byte, leakage_func), |a, b| a + b, - ); + ) + .finalize() +} + +pub struct Cpa { + /// Guess range upper excluded bound + guess_range: usize, + /// Pearson correlation coefficients + corr: Array2, + /// Max pearson correlation coefficients + max_corr: Array1, + rank_slice: Array2, +} + +impl Cpa { + pub fn pass_rank(&self) -> ArrayView2 { + self.rank_slice.view() + } - cpa.finalize(); + pub fn pass_corr_array(&self) -> ArrayView2 { + self.corr.view() + } + + pub fn pass_guess(&self) -> usize { + let mut init_value = 0.0; + let mut guess = 0; + + for i in 0..self.guess_range { + if self.max_corr[i] > init_value { + init_value = self.max_corr[i]; + guess = i; + } + } - cpa + guess + } } -pub struct Cpa { +pub struct CpaProcessor { /// Number of samples per trace len_samples: usize, target_byte: usize, @@ -64,9 +96,6 @@ pub struct Cpa { /// Sum of square of traces per key guess guess_sum_squares_leakages: Array1, a_l: Array2, - corr: Array2, - max_corr: Array1, - rank_slice: Array2, /// Leakage model leakage_func: fn(usize, usize) -> usize, /// Number of traces processed @@ -74,7 +103,7 @@ pub struct Cpa { } /* This class implements the CPA shown in this paper: https://eprint.iacr.org/2013/794.pdf */ -impl Cpa { +impl CpaProcessor { pub fn new( size: usize, guess_range: usize, @@ -90,9 +119,6 @@ impl Cpa { guess_sum_leakages: Array1::zeros(guess_range), guess_sum_squares_leakages: Array1::zeros(guess_range), a_l: Array2::zeros((guess_range, size)), - corr: Array2::zeros((guess_range, size)), - max_corr: Array1::zeros(guess_range), - rank_slice: Array2::zeros((guess_range, 1)), leakage_func, len_leakages: 0, } @@ -128,10 +154,9 @@ impl Cpa { self.len_leakages += 1; } - pub fn finalize(&mut self) { + pub fn finalize(&self) -> Cpa { /* This function finalizes the calculation after feeding the overall traces */ - let mut p = Array2::zeros((self.guess_range, self.guess_range)); for guess in 0..self.guess_range { for x in 0..self.guess_range { @@ -139,6 +164,7 @@ impl Cpa { } } + let mut corr = Array2::zeros((self.guess_range, self.len_samples)); for guess in 0..self.guess_range { let mean_key = self.guess_sum_leakages[guess] as f32 / self.len_leakages as f32; let mean_squares_key = @@ -165,42 +191,28 @@ impl Cpa { #[allow(clippy::needless_range_loop)] for u in 0..self.len_samples { - self.corr[[guess, u]] = tmp[u]; + corr[[guess, u]] = tmp[u]; } } - self.max_corr = max_per_row(self.corr.view()); + let max_corr = max_per_row(corr.view()); - self.rank_slice = concatenate![ + let mut rank_slice = Array2::zeros((self.guess_range, 1)); + rank_slice = concatenate![ Axis(1), - self.rank_slice, - self.max_corr + rank_slice, + max_corr .clone() - .into_shape((self.max_corr.shape()[0], 1)) + .into_shape((max_corr.shape()[0], 1)) .unwrap() ]; - } - pub fn pass_rank(&self) -> ArrayView2 { - self.rank_slice.view() - } - - pub fn pass_corr_array(&self) -> ArrayView2 { - self.corr.view() - } - - pub fn pass_guess(&self) -> usize { - let mut init_value = 0.0; - let mut guess = 0; - - for i in 0..self.guess_range { - if self.max_corr[i] > init_value { - init_value = self.max_corr[i]; - guess = i; - } + Cpa { + guess_range: self.guess_range, + corr, + max_corr, + rank_slice, } - - guess } fn sum_mult(&self, a: ArrayView1, b: ArrayView1) -> i32 { @@ -208,7 +220,7 @@ impl Cpa { } } -impl Add for Cpa { +impl Add for CpaProcessor { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -227,9 +239,6 @@ impl Add for Cpa { guess_sum_squares_leakages: self.guess_sum_squares_leakages + rhs.guess_sum_squares_leakages, a_l: self.a_l + rhs.a_l, - corr: self.corr + rhs.corr, - max_corr: self.max_corr, - rank_slice: self.rank_slice, leakage_func: self.leakage_func, len_leakages: self.len_leakages + rhs.len_leakages, } diff --git a/src/cpa_normal.rs b/src/cpa_normal.rs index 98a7a60..f08ba5f 100644 --- a/src/cpa_normal.rs +++ b/src/cpa_normal.rs @@ -4,7 +4,7 @@ use std::{iter::zip, ops::Add}; use crate::util::max_per_row; -/// Computes the [`Cpa`] of the given traces. +/// Computes the [`Cpa`] of the given traces using [`CpaProcessor`]. /// /// # Panics /// - Panic if `leakages.shape()[0] != plaintexts.shape()[0]` @@ -23,27 +23,56 @@ where assert_eq!(leakages.shape()[0], plaintexts.shape()[0]); assert!(chunk_size > 0); - let mut cpa = zip( + zip( leakages.axis_chunks_iter(Axis(0), chunk_size), plaintexts.axis_chunks_iter(Axis(0), chunk_size), ) .par_bridge() .map(|(leakages_chunk, plaintexts_chunk)| { - let mut cpa = Cpa::new(leakages.shape()[1], chunk_size, guess_range, leakage_func); + let mut cpa = CpaProcessor::new(leakages.shape()[1], chunk_size, guess_range, leakage_func); cpa.update(leakages_chunk, plaintexts_chunk); cpa }) .reduce( - || Cpa::new(leakages.shape()[1], chunk_size, guess_range, leakage_func), + || CpaProcessor::new(leakages.shape()[1], chunk_size, guess_range, leakage_func), |x, y| x + y, - ); + ) + .finalize() +} - cpa.finalize(); +pub struct Cpa { + /// Guess range upper excluded bound + guess_range: usize, + corr: Array2, + max_corr: Array1, + rank_slice: Array2, +} - cpa +impl Cpa { + pub fn pass_rank(&self) -> ArrayView2 { + self.rank_slice.view() + } + + pub fn pass_corr_array(&self) -> ArrayView2 { + self.corr.view() + } + + pub fn pass_guess(&self) -> usize { + let mut init_value = 0.0; + let mut guess = 0; + + for i in 0..self.guess_range { + if self.max_corr[i] > init_value { + init_value = self.max_corr[i]; + guess = i; + } + } + + guess + } } -pub struct Cpa { +pub struct CpaProcessor { /// Number of samples per trace len_samples: usize, /// Guess range upper excluded bound @@ -58,9 +87,6 @@ pub struct Cpa { guess_sum2_leakages: Array1, values: Array2, cov: Array2, - corr: Array2, - max_corr: Array1, - rank_slice: Array2, rank_traces: usize, // Number of traces to calculate succes rate /// Batch size batch_size: usize, @@ -73,7 +99,7 @@ pub struct Cpa { /* This class implements the CPA algorithm shown in: https://www.iacr.org/archive/ches2004/31560016/31560016.pdf */ -impl Cpa { +impl CpaProcessor { pub fn new( size: usize, batch_size: usize, @@ -89,9 +115,6 @@ impl Cpa { guess_sum2_leakages: Array1::zeros(guess_range), values: Array2::zeros((batch_size, guess_range)), cov: Array2::zeros((guess_range, size)), - corr: Array2::zeros((guess_range, size)), - max_corr: Array1::zeros(guess_range), - rank_slice: Array2::zeros((guess_range, 1)), rank_traces: 0, batch_size, leakage_func, @@ -156,34 +179,44 @@ impl Cpa { &mut self, trace_batch: ArrayView2, plaintext_batch: ArrayView2, - ) where + ) -> Option + where T: Into + Copy, U: Into + Copy, { /* This function updates the main arrays of the CPA for the success rate*/ self.update(trace_batch, plaintext_batch); + + // WARN: if self.rank_traces == 0 this function will panic (division by zero) + // WARN: if self.rank_traces is not divisible by self.batch_size this branch will never be + // taken if self.len_leakages % self.rank_traces == 0 { - self.finalize(); + let mut cpa = self.finalize(); + if self.len_leakages == self.rank_traces { - self.rank_slice = self + cpa.rank_slice = cpa .max_corr .clone() - .into_shape((self.max_corr.shape()[0], 1)) + .into_shape((cpa.max_corr.shape()[0], 1)) .unwrap(); } else { - self.rank_slice = concatenate![ + cpa.rank_slice = concatenate![ Axis(1), - self.rank_slice, - self.max_corr + cpa.rank_slice, + cpa.max_corr .clone() - .into_shape((self.max_corr.shape()[0], 1)) + .into_shape((cpa.max_corr.shape()[0], 1)) .unwrap() ]; } + + Some(cpa) + } else { + None } } - pub fn finalize(&mut self) { + pub fn finalize(&self) -> Cpa { /* This function finalizes the calculation after feeding all stored acc arrays */ let cov_n = self.cov.clone() / self.len_leakages as f32; @@ -192,6 +225,7 @@ impl Cpa { let avg_leakages = self.sum_leakages.clone() / self.len_leakages as f32; let std_leakages = self.sum2_leakages.clone() / self.len_leakages as f32; + let mut corr = Array2::zeros((self.guess_range, self.len_samples)); for i in 0..self.guess_range { for x in 0..self.len_samples { let numerator = cov_n[[i, x]] - (avg_keys[i] * avg_leakages[x]); @@ -200,43 +234,27 @@ impl Cpa { let denominator_2 = std_leakages[x] - (avg_leakages[x] * avg_leakages[x]); if numerator != 0.0 { - self.corr[[i, x]] = - f32::abs(numerator / f32::sqrt(denominator_1 * denominator_2)); + corr[[i, x]] = f32::abs(numerator / f32::sqrt(denominator_1 * denominator_2)); } } } - self.max_corr = max_per_row(self.corr.view()); + let max_corr = max_per_row(corr.view()); + + Cpa { + guess_range: self.guess_range, + corr, + max_corr, + rank_slice: Array2::zeros((self.guess_range, 1)), + } } pub fn success_traces(&mut self, traces_no: usize) { self.rank_traces = traces_no; } - - pub fn pass_rank(&self) -> ArrayView2 { - self.rank_slice.view() - } - - pub fn pass_corr_array(&self) -> ArrayView2 { - self.corr.view() - } - - pub fn pass_guess(&self) -> usize { - let mut init_value = 0.0; - let mut guess = 0; - - for i in 0..self.guess_range { - if self.max_corr[i] > init_value { - init_value = self.max_corr[i]; - guess = i; - } - } - - guess - } } -impl Add for Cpa { +impl Add for CpaProcessor { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -253,9 +271,6 @@ impl Add for Cpa { guess_sum2_leakages: self.guess_sum2_leakages + rhs.guess_sum2_leakages, values: self.values + rhs.values, cov: self.cov + rhs.cov, - corr: self.corr + rhs.corr, - max_corr: self.max_corr, - rank_slice: self.rank_slice, rank_traces: self.rank_traces, batch_size: self.batch_size, leakage_func: self.leakage_func, diff --git a/src/dpa.rs b/src/dpa.rs index 88da218..c2832f2 100644 --- a/src/dpa.rs +++ b/src/dpa.rs @@ -10,18 +10,18 @@ pub fn dpa( guess_range: usize, leakage_func: fn(M, usize) -> usize, chunk_size: usize, -) -> Dpa +) -> Dpa where T: Into + Copy + Sync, M: Clone + Sync, { - let mut dpa = zip( + zip( leakages.axis_chunks_iter(Axis(0), chunk_size), metadata.axis_chunks_iter(Axis(0), chunk_size), ) .par_bridge() .map(|(leakages_chunk, metadata_chunk)| { - let mut dpa = Dpa::new(leakages.shape()[1], guess_range, leakage_func); + let mut dpa = DpaProcessor::new(leakages.shape()[1], guess_range, leakage_func); for i in 0..leakages_chunk.shape()[0] { dpa.update(leakages_chunk.row(i), metadata_chunk[i].clone()); @@ -30,16 +30,40 @@ where dpa }) .reduce( - || Dpa::new(leakages.shape()[1], guess_range, leakage_func), + || DpaProcessor::new(leakages.shape()[1], guess_range, leakage_func), |a, b| a + b, - ); + ) + .finalize() +} + +pub struct Dpa { + /// Guess range upper excluded bound + guess_range: usize, + corr: Array2, + max_corr: Array1, +} - dpa.finalize(); +impl Dpa { + pub fn pass_corr_array(&self) -> ArrayView2 { + self.corr.view() + } - dpa + pub fn pass_guess(&self) -> usize { + let mut init_value = 0.0; + let mut guess = 0; + + for i in 0..self.guess_range { + if self.max_corr[i] > init_value { + init_value = self.max_corr[i]; + guess = i; + } + } + + guess + } } -pub struct Dpa { +pub struct DpaProcessor { /// Number of samples per trace len_samples: usize, /// Guess range upper excluded bound @@ -52,8 +76,6 @@ pub struct Dpa { count_0: Array1, /// Number of traces processed for which the selection function equals 1 count_1: Array1, - corr: Array2, - max_corr: Array1, rank_slice: Array2, rank_traces: usize, // Number of traces to calculate succes rate /// Selection function @@ -66,7 +88,7 @@ pub struct Dpa { https://paulkocher.com/doc/DifferentialPowerAnalysis.pdf https://web.mit.edu/6.857/OldStuff/Fall03/ref/kocher-DPATechInfo.pdf */ -impl Dpa { +impl DpaProcessor { pub fn new(size: usize, guess_range: usize, f: fn(M, usize) -> usize) -> Self { Self { len_samples: size, @@ -75,8 +97,6 @@ impl Dpa { sum_1: Array2::zeros((guess_range, size)), count_0: Array1::zeros(guess_range), count_1: Array1::zeros(guess_range), - corr: Array2::zeros((guess_range, size)), - max_corr: Array1::zeros(guess_range), rank_slice: Array2::zeros((guess_range, 1)), rank_traces: 0, leakage_func: f, @@ -125,21 +145,21 @@ impl Dpa { self.update(trace_batch, plaintext_batch); if self.len_leakages % self.rank_traces == 0 { - self.finalize(); + let dpa = self.finalize(); if self.len_leakages == self.rank_traces { - self.rank_slice = self + self.rank_slice = dpa .max_corr .clone() - .into_shape((self.max_corr.shape()[0], 1)) + .into_shape((dpa.max_corr.shape()[0], 1)) .unwrap(); } else { self.rank_slice = concatenate![ Axis(1), self.rank_slice, - self.max_corr + dpa.max_corr .clone() - .into_shape((self.max_corr.shape()[0], 1)) + .into_shape((dpa.max_corr.shape()[0], 1)) .unwrap() ]; } @@ -150,9 +170,8 @@ impl Dpa { self.rank_traces = value; } - pub fn finalize(&mut self) { - /* This function finalizes the calculation after - feeding all stored acc arrays */ + pub fn finalize(&mut self) -> Dpa { + /* This function finalizes the calculation after feeding all stored acc arrays */ let mut tmp_avg_0 = Array2::zeros((self.guess_range, self.len_samples)); let mut tmp_avg_1 = Array2::zeros((self.guess_range, self.len_samples)); @@ -162,11 +181,15 @@ impl Dpa { tmp_avg_0.row_mut(row).assign(&tmp_row_0); tmp_avg_1.row_mut(row).assign(&tmp_row_1); } - let diff = tmp_avg_0.clone() - tmp_avg_1; - self.corr = diff.map(|e| f32::abs(*e)); + let corr = (tmp_avg_0 - tmp_avg_1).map(|e| f32::abs(*e)); + let max_corr = max_per_row(corr.view()); - self.max_corr = max_per_row(self.corr.view()); + Dpa { + guess_range: self.guess_range, + corr, + max_corr, + } } pub fn success_traces(&mut self, traces_no: usize) { @@ -176,27 +199,9 @@ impl Dpa { pub fn pass_rank(&self) -> ArrayView2 { self.rank_slice.view() } - - pub fn pass_corr_array(&self) -> ArrayView2 { - self.corr.view() - } - - pub fn pass_guess(&self) -> usize { - let mut init_value = 0.0; - let mut guess = 0; - - for i in 0..self.guess_range { - if self.max_corr[i] > init_value { - init_value = self.max_corr[i]; - guess = i; - } - } - - guess - } } -impl Add for Dpa { +impl Add for DpaProcessor { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -211,8 +216,6 @@ impl Add for Dpa { sum_1: self.sum_1 + rhs.sum_1, count_0: self.count_0 + rhs.count_0, count_1: self.count_1 + rhs.count_1, - corr: self.corr + rhs.corr, - max_corr: self.max_corr, rank_slice: self.rank_slice, rank_traces: self.rank_traces, leakage_func: self.leakage_func,