diff --git a/examples/cpa.rs b/examples/cpa.rs index 814363e..a4520cf 100644 --- a/examples/cpa.rs +++ b/examples/cpa.rs @@ -1,63 +1,105 @@ -// use simple_bar::ProgressBar; +use cpa::cpa_normal::*; +use cpa::leakage::{hw, sbox}; +use cpa::tools::{progress_bar, read_array_2_from_npy_file, write_array}; use indicatif::ProgressIterator; -use muscat::cpa::*; -use muscat::leakage::{hw, sbox}; -use muscat::util::{progress_bar, read_array_2_from_npy_file, save_array}; use ndarray::*; -use rayon::prelude::{ParallelBridge, ParallelIterator}; -use std::time::Instant; +use rayon::iter::{ParallelBridge, ParallelIterator}; +use std::time::{self}; -// traces format -type FormatTraces = i16; -type FormatMetadata = i32; // leakage model -pub fn leakage_model(value: usize, guess: usize) -> usize { - hw(sbox((value ^ guess) as u8) as usize) +pub fn leakage_model(value: ArrayView1, guess: usize) -> usize { + hw(sbox((value[1] ^ guess) as u8) as usize) } -// multi-threading cpa + +// traces format +type FormatTraces = f64; +type FormatMetadata = u8; + +#[allow(dead_code)] fn cpa() { - let size: usize = 5000; // Number of samples + let start_sample: usize = 0; + let end_sample: usize = 5000; + let size: usize = end_sample - start_sample; // Number of samples + let patch: usize = 500; let guess_range = 256; // 2**(key length) - let target_byte = 1; - let folder = String::from("../../data"); // Directory of leakages and metadata - let nfiles = 5; // Number of files in the directory. TBD: Automating this value - - /* Parallel operation using multi-threading on patches */ - let mut cpa = (0..nfiles) - .into_iter() - .progress_with(progress_bar(nfiles)) - .map(|n| { - let dir_l = format!("{folder}/l{n}.npy"); - let dir_p = format!("{folder}/p{n}.npy"); - let leakages: Array2 = read_array_2_from_npy_file(&dir_l); - let plaintext: Array2 = read_array_2_from_npy_file(&dir_p); - (leakages, plaintext) - }) - .into_iter() + let folder = String::from("../data/cw"); + let dir_l = format!("{folder}/leakages.npy"); + let dir_p = format!("{folder}/plaintexts.npy"); + let leakages: Array2 = read_array_2_from_npy_file::(&dir_l); + let plaintext: Array2 = read_array_2_from_npy_file::(&dir_p); + let len_traces = leakages.shape()[0]; + let mut cpa_parallel = ((0..len_traces).step_by(patch)).progress_with(progress_bar(len_traces)) + .map(|row| row) .par_bridge() - .map(|patch| { - let mut c: Cpa = Cpa::new(size, guess_range, target_byte, leakage_model); - let len_leakage = patch.0.shape()[0]; - for i in 0..len_leakage { - c.update( - patch.0.row(i).map(|x| *x as usize), - patch.1.row(i).map(|y| *y as usize), - ); - } - c + .map(|row_number| { + let mut cpa = Cpa::new(size, patch, guess_range, leakage_model); + let range_rows = row_number..row_number + patch; + let range_samples = start_sample..end_sample; + let sample_traces = leakages + .slice(s![range_rows.clone(), range_samples]) + .map(|l| *l as f32); + let sample_metadata: ArrayBase, Dim<[usize; 2]>> = + plaintext.slice(s![range_rows, ..]).map(|p| *p as usize); + cpa.update(sample_traces, sample_metadata); + cpa }) .reduce( - || Cpa::new(size, guess_range, target_byte, leakage_model), - |a: Cpa, b| a + b, + || Cpa::new(size, patch, guess_range, leakage_model), + |x, y| x + y, ); + cpa_parallel.finalize(); + println!("Guessed key = {}", cpa_parallel.pass_guess()); + write_array("results/corr.npy", cpa_parallel.pass_corr_array().view()) +} + + + +#[allow(dead_code)] +fn success() { + let start_sample: usize = 0; + let end_sample: usize = 5000; + let size: usize = end_sample - start_sample; // Number of samples + let patch: usize = 500; + let guess_range = 256; // 2**(key length) + let folder = String::from("../data/log_584012"); // "../../../intenship/scripts/log_584012" + let nfiles = 13; // Number of files in the directory. TBD: Automating this value + let rank_traces: usize = 1000; + let mut cpa = Cpa::new(size, patch, guess_range, leakage_model); + cpa.success_traces(rank_traces); + for i in (0..nfiles).progress() { + let dir_l = format!("{folder}/l/{i}.npy"); + let dir_p = format!("{folder}/p/{i}.npy"); + let leakages: Array2 = read_array_2_from_npy_file::(&dir_l); + let plaintext: Array2 = + read_array_2_from_npy_file::(&dir_p); + let len_leakages = leakages.shape()[0]; + for row in (0..len_leakages).step_by(patch) { + let range_samples = start_sample..end_sample; + let range_rows: std::ops::Range = row..row + patch; + let range_metadat = 0..plaintext.shape()[1]; + let sample_traces = leakages + .slice(s![range_rows.clone(), range_samples]).map(|l| *l as f32); + let sample_metadata: Array2= plaintext + .slice(s![range_rows, range_metadat]).to_owned(); + cpa.update_success(sample_traces, sample_metadata); + } + } cpa.finalize(); println!("Guessed key = {}", cpa.pass_guess()); // save corr key curves in npy - save_array("../results/corr.npy", &cpa.pass_corr_array()); + write_array("results/success.npy", cpa.pass_rank().view()); } -fn main() { + + +fn main(){ + let t = time::Instant::now(); cpa(); + println!("{:?}", t.elapsed()); } + + + + diff --git a/examples/cpa_partioned.rs b/examples/cpa_partioned.rs new file mode 100644 index 0000000..814363e --- /dev/null +++ b/examples/cpa_partioned.rs @@ -0,0 +1,63 @@ +// use simple_bar::ProgressBar; +use indicatif::ProgressIterator; +use muscat::cpa::*; +use muscat::leakage::{hw, sbox}; +use muscat::util::{progress_bar, read_array_2_from_npy_file, save_array}; +use ndarray::*; +use rayon::prelude::{ParallelBridge, ParallelIterator}; +use std::time::Instant; + +// traces format +type FormatTraces = i16; +type FormatMetadata = i32; + +// leakage model +pub fn leakage_model(value: usize, guess: usize) -> usize { + hw(sbox((value ^ guess) as u8) as usize) +} + +// multi-threading cpa +fn cpa() { + let size: usize = 5000; // Number of samples + let guess_range = 256; // 2**(key length) + let target_byte = 1; + let folder = String::from("../../data"); // Directory of leakages and metadata + let nfiles = 5; // Number of files in the directory. TBD: Automating this value + + /* Parallel operation using multi-threading on patches */ + let mut cpa = (0..nfiles) + .into_iter() + .progress_with(progress_bar(nfiles)) + .map(|n| { + let dir_l = format!("{folder}/l{n}.npy"); + let dir_p = format!("{folder}/p{n}.npy"); + let leakages: Array2 = read_array_2_from_npy_file(&dir_l); + let plaintext: Array2 = read_array_2_from_npy_file(&dir_p); + (leakages, plaintext) + }) + .into_iter() + .par_bridge() + .map(|patch| { + let mut c: Cpa = Cpa::new(size, guess_range, target_byte, leakage_model); + let len_leakage = patch.0.shape()[0]; + for i in 0..len_leakage { + c.update( + patch.0.row(i).map(|x| *x as usize), + patch.1.row(i).map(|y| *y as usize), + ); + } + c + }) + .reduce( + || Cpa::new(size, guess_range, target_byte, leakage_model), + |a: Cpa, b| a + b, + ); + cpa.finalize(); + println!("Guessed key = {}", cpa.pass_guess()); + // save corr key curves in npy + save_array("../results/corr.npy", &cpa.pass_corr_array()); +} + +fn main() { + cpa(); +} diff --git a/src/cpa_normal.rs b/src/cpa_normal.rs new file mode 100644 index 0000000..100b9a9 --- /dev/null +++ b/src/cpa_normal.rs @@ -0,0 +1,206 @@ +use ndarray::{concatenate, Array1, Array2, ArrayView1, ArrayView2, Axis}; +use std::ops::Add; +pub struct Cpa { + /* List of internal class variables */ + sum_leakages: Array1, + sum2_leakages: Array1, + sum_keys: Array1, + sum2_keys: Array1, + values: Array2, + len_leakages: usize, + guess_range: i32, + cov: Array2, + corr: Array2, + max_corr: Array2, + rank_slice: Array2, + leakage_func: fn(ArrayView1, usize) -> usize, + len_samples: usize, + chunk: usize, + rank_traces: usize, // Number of traces to calculate succes rate +} + +/* This class implements the CPA algorithm shown in: +https://www.iacr.org/archive/ches2004/31560016/31560016.pdf */ + +impl Cpa { + pub fn new( + size: usize, + patch: usize, + guess_range: i32, + f: fn(ArrayView1, usize) -> usize, + ) -> Self { + Self { + len_samples: size, + chunk: patch, + guess_range: guess_range, + sum_leakages: Array1::zeros(size), + sum2_leakages: Array1::zeros(size), + sum_keys: Array1::zeros(guess_range as usize), + sum2_keys: Array1::zeros(guess_range as usize), + values: Array2::zeros((patch, guess_range as usize)), + cov: Array2::zeros((guess_range as usize, size)), + corr: Array2::zeros((guess_range as usize, size)), + max_corr: Array2::zeros((guess_range as usize, 1)), + rank_slice: Array2::zeros((guess_range as usize, 1)), + leakage_func: f, + len_leakages: 0, + rank_traces: 0, + } + } + + pub fn update( + &mut self, + trace_patch: Array2, + plaintext_patch: Array2, + ) where + f32: From, usize:From + { + /* This function updates the internal arrays of the CPA + It accepts trace_patch and plaintext_patch to update them*/ + let tmp_traces = trace_patch.map(|t| f32::from(*t)); + let metadat = plaintext_patch.map(|m| usize::from(*m)); + self.len_leakages += self.chunk; + self.update_values(&metadat, &tmp_traces, self.guess_range); + self.update_key_leakages(tmp_traces, self.guess_range); + } + + pub fn update_values( + /* This function generates the values and cov arrays */ + &mut self, + metadata: &Array2, + _trace: &Array2, + _guess_range: i32, + ) { + for row in 0..self.chunk { + for guess in 0.._guess_range { + let pass_to_leakage: ArrayView1 = metadata.row(row); + self.values[[row, guess as usize]] = + (self.leakage_func)(pass_to_leakage, guess as usize) as f32; + } + } + + self.cov = self.cov.clone() + self.values.t().dot(_trace); + } + + pub fn update_key_leakages(&mut self, _trace: Array2, _guess_range: i32) { + for i in 0..self.len_samples { + self.sum_leakages[i] += _trace.column(i).sum(); // _trace[i] as usize; + self.sum2_leakages[i] += _trace.column(i).dot(&_trace.column(i)); // (_trace[i] * _trace[i]) as usize; + } + + for guess in 0.._guess_range { + self.sum_keys[guess as usize] += self.values.column(guess as usize).sum(); //self.values[guess as usize] as usize; + self.sum2_keys[guess as usize] += self + .values + .column(guess as usize) + .dot(&self.values.column(guess as usize)); + // (self.values[guess as usize] * self.values[guess as usize]) as usize; + } + } + + pub fn update_success(&mut self, trace_patch: Array2, plaintext_patch: Array2) where f32: From, usize:From { + /* This function updates the main arrays of the CPA for the success rate*/ + self.update(trace_patch, plaintext_patch); + if self.len_leakages % self.rank_traces == 0 { + self.finalize(); + if self.len_leakages == self.rank_traces { + self.rank_slice = self.max_corr.clone(); + } else { + self.rank_slice = concatenate![Axis(1), self.rank_slice, self.max_corr]; + } + } + } + + pub fn finalize(&mut self) { + /* This function finalizes the calculation after + feeding all stored acc arrays */ + let cov_n: Array2 = self.cov.clone() / self.len_leakages as f32; + let avg_keys: Array1 = self.sum_keys.clone()/ self.len_leakages as f32; + let std_key: Array1 = self.sum2_keys.clone() / self.len_leakages as f32; + let avg_leakages: Array1 = self.sum_leakages.clone() / self.len_leakages as f32; + let std_leakages: Array1 = self.sum2_leakages.clone() / self.len_leakages as f32; + + for i in 0..self.guess_range as usize { + for x in 0..self.len_samples { + let numerator: f32 = cov_n[[i, x]] + - (avg_keys[i] * avg_leakages[x]); + + let denominator_1: f32 = std_key[i] + - (avg_keys[i] * avg_keys[i] ); + + let denominator_2: f32 = std_leakages[x] + - (avg_leakages[x] * avg_leakages[x]); + if numerator != 0.0{ + self.corr[[i as usize, x]] = f32::abs(numerator / f32::sqrt(denominator_1 * denominator_2)); + + } + } + } + self.select_max(); + } + + pub fn select_max(&mut self) { + for i in 0..self.guess_range { + let row = self.corr.row(i as usize); + // Calculating the max value in the row + let max_value = row + .into_iter() + .reduce(|a, b| { + let mut tmp = a; + if tmp < b { + tmp = b; + } + tmp + }) + .unwrap(); + self.max_corr[[i as usize, 0]] = *max_value; + } + } + + 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) -> Array2 { + self.corr.clone() + } + + pub fn pass_guess(&self) -> i32 { + let mut init_value: f32 = 0.0; + let mut guess: i32 = 0; + for i in 0..self.guess_range { + if self.max_corr[[i as usize, 0]] > init_value { + init_value = self.max_corr[[i as usize, 0]]; + guess = i; + } + } + guess + } +} + +impl Add for Cpa { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + Self { + sum_leakages: self.sum_leakages + rhs.sum_leakages, + sum2_leakages: self.sum2_leakages + rhs.sum2_leakages, + sum_keys: self.sum_keys + rhs.sum_keys, + sum2_keys: self.sum2_keys + rhs.sum2_keys, + values: self.values + rhs.values, + len_leakages: self.len_leakages + rhs.len_leakages, + guess_range: rhs.guess_range, + chunk: rhs.chunk, + cov: self.cov + rhs.cov, + corr: self.corr + rhs.corr, + max_corr: self.max_corr, + rank_slice: self.rank_slice, + len_samples: rhs.len_samples, + leakage_func: self.leakage_func, + rank_traces: self.rank_traces, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 47aa3e3..e0f6c69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,5 @@ pub mod processors; pub mod quicklog; pub mod trace; pub mod util; -pub mod preprocessors; \ No newline at end of file +pub mod preprocessors; +use cpa_normal; \ No newline at end of file