Commit 771d2218 authored by Guillaume B's avatar Guillaume B Committed by Rémi Huguet
Browse files

feat:HammingWeight computation optimization, about 15 time faster, and accept...

feat:HammingWeight computation optimization, about 15 time faster, and accept all unsigned integer data types
parent e33f680c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ requirements:
    - numpy
    - estraces
    - psutil
    - numba
about:
  home: https://gitlab.com/eshard/scared
  license: GNU LGPL V3
+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):
@@ -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
@@ -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.
@@ -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).'
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ install_requires =
  numpy<1.17
  estraces
  psutil
  numba
tests_require =
  pytest
  pycrypto
+65 −0
Original line number Diff line number Diff line
@@ -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)