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/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/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..b9d8a89 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,45 @@ 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, + corr: Array2, + 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 +94,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 +101,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 +117,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 +152,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 +162,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 +189,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 +218,7 @@ impl Cpa { } } -impl Add for Cpa { +impl Add for CpaProcessor { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -227,9 +237,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,