Loading .recipe/meta.yaml +1 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ requirements: - numpy - estraces - psutil - numba about: home: https://gitlab.com/eshard/scared license: GNU LGPL V3 Loading scared/models.py +70 −15 Original line number Diff line number Diff line import numpy as _np from ._utils import _is_bytes_array import abc import numba _HW_LUT = _np.array([0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8], dtype='uint32') @numba.vectorize([numba.uint32(numba.uint8)]) def _fhw8(x): return _HW_LUT[x] @numba.vectorize([numba.uint32(numba.uint16)]) def _fhw16(x): return _HW_LUT[x & 0x00ff] + _HW_LUT[x >> 8] @numba.vectorize([numba.uint32(numba.uint32)]) def _fhw32(x): r = 0 for _ in range(4): r += _HW_LUT[x & 0x000000ff] x >>= 8 return r @numba.vectorize([numba.uint32(numba.uint64)]) def _fhw64(x): r = 0 for _ in range(8): r += _HW_LUT[x & 0x00000000000000ff] x >>= 8 return r _hw_functions_list = dict([(1, _fhw8), (2, _fhw16), (4, _fhw32), (8, _fhw64)]) class Model(abc.ABC): Loading Loading @@ -102,7 +142,7 @@ class Monobit(Model): class HammingWeight(Model): """Hamming weight leakage model for bytes arrays. """Hamming weight leakage model for unsigned integer arrays. Instances of this class are callables which takes a data numpy array as input and returns the Hamming Weight values computed on the last dimension of the array, and on a number of words Loading @@ -110,9 +150,10 @@ class HammingWeight(Model): Attributes: nb_words (int, default=1): number of words on which to compute the hamming weight. expected_dtype(numpy.dtype, default='uint8'): expected dtype of input data. Args: data (numpy.ndarray): a bytes ndarray (of uint8 dtype). data (numpy.ndarray): a unsigned integer ndarray. Returns: (numpy.ndarray) an ndarray with hamming weight computed on last dimension. Loading @@ -120,33 +161,47 @@ class HammingWeight(Model): """ def __init__(self, nb_words=1): def __init__(self, nb_words=1, expected_dtype='uint8'): if not isinstance(nb_words, int): raise TypeError(f'nb_words should be an integer, not {nb_words}.') if nb_words <= 0: raise ValueError(f'nb_words must be strictly greater than 0, not {nb_words}.') try: expected_dtype = _np.dtype(expected_dtype) except TypeError: raise ValueError(f'{expected_dtype} is not a valid dtype.') if expected_dtype.kind != 'u': raise ValueError(f'`expected_dtype` should be an unsigned integer dtype, not {expected_dtype}).') self.nb_words = nb_words self.expected_dtype = expected_dtype def _compute(self, data, axis): _is_bytes_array(data) if data.dtype.kind != 'u': raise ValueError(f'HammingWeight should take unsigned integer data as input, not {data.dtype}).') if data.dtype != self.expected_dtype: raise ValueError(f'Expected dtype for HammingWeight input data is {self.expected_dtype}, not {data.dtype}.') if data.shape[axis] < self.nb_words: raise ValueError(f'data should have at least {self.nb_words} as dimension with index {axis}, not {data.shape[axis]}.') result_data = _hw_functions_list[data.dtype.itemsize](data) if self.nb_words > 1: final_w_dimension = data.shape[axis] // self.nb_words final_shape = [d if i != axis else final_w_dimension for i, d in enumerate(data.shape)] result = _np.zeros(final_shape, dtype='uint32').swapaxes(0, axis) data = _np.swapaxes(data, 0, axis) result_data = _np.swapaxes(result_data, 0, axis) for i in range(result.shape[0]): slices = data[i * self.nb_words: (i + 1) * self.nb_words] result[i] = _np.sum(_np.unpackbits(slices, axis=0), axis=0) slices = result_data[i * self.nb_words: (i + 1) * self.nb_words] result[i] = _np.sum(slices, axis=0) result_data = result result_data = _np.swapaxes(result, 0, axis) result = _np.swapaxes(result, 0, axis) return result return result_data @property def max_data_value(self): return self.nb_words * 8 return self.nb_words * self.expected_dtype.itemsize * 8 def __str__(self): return f'Hamming Weight on {self.nb_words} word(s).' setup.cfg +1 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ install_requires = numpy<1.17 estraces psutil numba tests_require = pytest pycrypto Loading tests/test_models.py +65 −0 Original line number Diff line number Diff line Loading @@ -233,3 +233,68 @@ def test_model_accepts_axis_parameters_with_default_value(): [1, 5, 9, 13]], dtype='uint32').swapaxes(0, 1) res = scared.HammingWeight(2)(data, axis=0) assert np.array_equal(expected, res) def test_hamming_weight_uint8(): a = np.array([[3, 28, 89, 100, 89, 158, 199], [183, 46, 190, 184, 144, 163, 165], [80, 122, 200, 112, 86, 135, 243]], dtype='uint8') b = np.array([[2, 3, 4, 3, 4, 5, 5], [6, 4, 6, 4, 2, 4, 4], [2, 5, 3, 3, 4, 4, 6]]) hw = scared.HammingWeight() res = hw(a) assert np.array_equal(res, b) def test_hamming_weight_uint16(): a = np.array([[49492, 35012, 17375, 58517, 7925, 37294, 1960], [12319, 21194, 3592, 62643, 7298, 52388, 17174], [5624, 52639, 16319, 28373, 20979, 33310, 30228]], dtype='uint16') b = np.array([[6, 5, 10, 8, 10, 8, 6], [7, 7, 4, 10, 5, 7, 6], [8, 11, 13, 10, 9, 6, 7]]) hw = scared.HammingWeight(expected_dtype='uint16') res = hw(a) assert np.array_equal(res, b) def test_hamming_weight_uint32(): a = np.array([[3011812195, 566284227, 3180952330, 3224490982, 1398648567, 2997601074, 3466990530], [1745373437, 2305307499, 528675825, 3245007040, 3385056644, 1402421254, 890085974], [3910116154, 3106231214, 503027565, 263222377, 3958549359, 3780560129, 2475275928]], dtype='uint32') b = np.array([[16, 14, 18, 15, 21, 16, 15], [13, 15, 18, 12, 16, 12, 14], [17, 17, 21, 15, 22, 13, 15]]) hw = scared.HammingWeight(expected_dtype='uint32') res = hw(a) assert np.array_equal(res, b) def test_hamming_weight_uint64(): a = np.array([[1110681567975564723, 2603173294400796779, 8824234896931680599, 2581838972723594911, 6596729868911047227, 2074859436293001592, 11731185813118878444], [16879640904508054626, 16622460785862736051, 15934166478821968156, 6382170377471969988, 14708844167980688964, 5124927357537709470, 8409390396293004342], [2595719322730764045, 6531922580352310653, 3061678657169528223, 8881635347030906723, 4337229908681420868, 4353603953276958515, 6752076961829864544]], dtype='uint64') b = np.array([[33, 27, 40, 32, 35, 28, 31], [30, 33, 34, 26, 25, 37, 25], [31, 33, 31, 30, 31, 38, 30]]) hw = scared.HammingWeight(expected_dtype='uint64') res = hw(a) assert np.array_equal(res, b) def test_hamming_weight_wrong_expected_dtype(): with pytest.raises(ValueError): scared.HammingWeight(expected_dtype='coucou') def test_hamming_weight_uint16_with_wong_expected_dtype(): a = np.array([[49492, 35012, 17375, 58517, 7925, 37294, 1960], [12319, 21194, 3592, 62643, 7298, 52388, 17174], [5624, 52639, 16319, 28373, 20979, 33310, 30228]], dtype='uint16') hw = scared.HammingWeight() with pytest.raises(ValueError): hw(a) Loading
.recipe/meta.yaml +1 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ requirements: - numpy - estraces - psutil - numba about: home: https://gitlab.com/eshard/scared license: GNU LGPL V3 Loading
scared/models.py +70 −15 Original line number Diff line number Diff line import numpy as _np from ._utils import _is_bytes_array import abc import numba _HW_LUT = _np.array([0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8], dtype='uint32') @numba.vectorize([numba.uint32(numba.uint8)]) def _fhw8(x): return _HW_LUT[x] @numba.vectorize([numba.uint32(numba.uint16)]) def _fhw16(x): return _HW_LUT[x & 0x00ff] + _HW_LUT[x >> 8] @numba.vectorize([numba.uint32(numba.uint32)]) def _fhw32(x): r = 0 for _ in range(4): r += _HW_LUT[x & 0x000000ff] x >>= 8 return r @numba.vectorize([numba.uint32(numba.uint64)]) def _fhw64(x): r = 0 for _ in range(8): r += _HW_LUT[x & 0x00000000000000ff] x >>= 8 return r _hw_functions_list = dict([(1, _fhw8), (2, _fhw16), (4, _fhw32), (8, _fhw64)]) class Model(abc.ABC): Loading Loading @@ -102,7 +142,7 @@ class Monobit(Model): class HammingWeight(Model): """Hamming weight leakage model for bytes arrays. """Hamming weight leakage model for unsigned integer arrays. Instances of this class are callables which takes a data numpy array as input and returns the Hamming Weight values computed on the last dimension of the array, and on a number of words Loading @@ -110,9 +150,10 @@ class HammingWeight(Model): Attributes: nb_words (int, default=1): number of words on which to compute the hamming weight. expected_dtype(numpy.dtype, default='uint8'): expected dtype of input data. Args: data (numpy.ndarray): a bytes ndarray (of uint8 dtype). data (numpy.ndarray): a unsigned integer ndarray. Returns: (numpy.ndarray) an ndarray with hamming weight computed on last dimension. Loading @@ -120,33 +161,47 @@ class HammingWeight(Model): """ def __init__(self, nb_words=1): def __init__(self, nb_words=1, expected_dtype='uint8'): if not isinstance(nb_words, int): raise TypeError(f'nb_words should be an integer, not {nb_words}.') if nb_words <= 0: raise ValueError(f'nb_words must be strictly greater than 0, not {nb_words}.') try: expected_dtype = _np.dtype(expected_dtype) except TypeError: raise ValueError(f'{expected_dtype} is not a valid dtype.') if expected_dtype.kind != 'u': raise ValueError(f'`expected_dtype` should be an unsigned integer dtype, not {expected_dtype}).') self.nb_words = nb_words self.expected_dtype = expected_dtype def _compute(self, data, axis): _is_bytes_array(data) if data.dtype.kind != 'u': raise ValueError(f'HammingWeight should take unsigned integer data as input, not {data.dtype}).') if data.dtype != self.expected_dtype: raise ValueError(f'Expected dtype for HammingWeight input data is {self.expected_dtype}, not {data.dtype}.') if data.shape[axis] < self.nb_words: raise ValueError(f'data should have at least {self.nb_words} as dimension with index {axis}, not {data.shape[axis]}.') result_data = _hw_functions_list[data.dtype.itemsize](data) if self.nb_words > 1: final_w_dimension = data.shape[axis] // self.nb_words final_shape = [d if i != axis else final_w_dimension for i, d in enumerate(data.shape)] result = _np.zeros(final_shape, dtype='uint32').swapaxes(0, axis) data = _np.swapaxes(data, 0, axis) result_data = _np.swapaxes(result_data, 0, axis) for i in range(result.shape[0]): slices = data[i * self.nb_words: (i + 1) * self.nb_words] result[i] = _np.sum(_np.unpackbits(slices, axis=0), axis=0) slices = result_data[i * self.nb_words: (i + 1) * self.nb_words] result[i] = _np.sum(slices, axis=0) result_data = result result_data = _np.swapaxes(result, 0, axis) result = _np.swapaxes(result, 0, axis) return result return result_data @property def max_data_value(self): return self.nb_words * 8 return self.nb_words * self.expected_dtype.itemsize * 8 def __str__(self): return f'Hamming Weight on {self.nb_words} word(s).'
setup.cfg +1 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ install_requires = numpy<1.17 estraces psutil numba tests_require = pytest pycrypto Loading
tests/test_models.py +65 −0 Original line number Diff line number Diff line Loading @@ -233,3 +233,68 @@ def test_model_accepts_axis_parameters_with_default_value(): [1, 5, 9, 13]], dtype='uint32').swapaxes(0, 1) res = scared.HammingWeight(2)(data, axis=0) assert np.array_equal(expected, res) def test_hamming_weight_uint8(): a = np.array([[3, 28, 89, 100, 89, 158, 199], [183, 46, 190, 184, 144, 163, 165], [80, 122, 200, 112, 86, 135, 243]], dtype='uint8') b = np.array([[2, 3, 4, 3, 4, 5, 5], [6, 4, 6, 4, 2, 4, 4], [2, 5, 3, 3, 4, 4, 6]]) hw = scared.HammingWeight() res = hw(a) assert np.array_equal(res, b) def test_hamming_weight_uint16(): a = np.array([[49492, 35012, 17375, 58517, 7925, 37294, 1960], [12319, 21194, 3592, 62643, 7298, 52388, 17174], [5624, 52639, 16319, 28373, 20979, 33310, 30228]], dtype='uint16') b = np.array([[6, 5, 10, 8, 10, 8, 6], [7, 7, 4, 10, 5, 7, 6], [8, 11, 13, 10, 9, 6, 7]]) hw = scared.HammingWeight(expected_dtype='uint16') res = hw(a) assert np.array_equal(res, b) def test_hamming_weight_uint32(): a = np.array([[3011812195, 566284227, 3180952330, 3224490982, 1398648567, 2997601074, 3466990530], [1745373437, 2305307499, 528675825, 3245007040, 3385056644, 1402421254, 890085974], [3910116154, 3106231214, 503027565, 263222377, 3958549359, 3780560129, 2475275928]], dtype='uint32') b = np.array([[16, 14, 18, 15, 21, 16, 15], [13, 15, 18, 12, 16, 12, 14], [17, 17, 21, 15, 22, 13, 15]]) hw = scared.HammingWeight(expected_dtype='uint32') res = hw(a) assert np.array_equal(res, b) def test_hamming_weight_uint64(): a = np.array([[1110681567975564723, 2603173294400796779, 8824234896931680599, 2581838972723594911, 6596729868911047227, 2074859436293001592, 11731185813118878444], [16879640904508054626, 16622460785862736051, 15934166478821968156, 6382170377471969988, 14708844167980688964, 5124927357537709470, 8409390396293004342], [2595719322730764045, 6531922580352310653, 3061678657169528223, 8881635347030906723, 4337229908681420868, 4353603953276958515, 6752076961829864544]], dtype='uint64') b = np.array([[33, 27, 40, 32, 35, 28, 31], [30, 33, 34, 26, 25, 37, 25], [31, 33, 31, 30, 31, 38, 30]]) hw = scared.HammingWeight(expected_dtype='uint64') res = hw(a) assert np.array_equal(res, b) def test_hamming_weight_wrong_expected_dtype(): with pytest.raises(ValueError): scared.HammingWeight(expected_dtype='coucou') def test_hamming_weight_uint16_with_wong_expected_dtype(): a = np.array([[49492, 35012, 17375, 58517, 7925, 37294, 1960], [12319, 21194, 3592, 62643, 7298, 52388, 17174], [5624, 52639, 16319, 28373, 20979, 33310, 30228]], dtype='uint16') hw = scared.HammingWeight() with pytest.raises(ValueError): hw(a)