aboutsummaryrefslogtreecommitdiff
path: root/pyecsca/sca/attack/CPA.py
blob: efa406ce4f2ee350553fae891569417cd0a786d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
from public import public
from scipy.stats import pearsonr
import numpy as np
from numpy.typing import NDArray

from pyecsca.ec.mult import ScalarMultiplier
from pyecsca.ec.point import Point
from pyecsca.ec.context import DefaultContext, local
from pyecsca.ec.params import DomainParameters
from pyecsca.ec.mod import Mod
from pyecsca.sca.trace import Trace
from pyecsca.sca.trace.plot import plot_trace
from pyecsca.sca.attack.leakage_model import LeakageModel


@public
class CPA:
    traces: NDArray
    points: list[Point]
    mult: ScalarMultiplier
    params: DomainParameters
    leakage_model: LeakageModel
    correlations: dict[str, list[list[float]]]

    def __init__(
        self,
        points: list[Point],
        traces: list[Trace],
        leakage_model: LeakageModel,
        mult: ScalarMultiplier,
        params: DomainParameters,
    ):
        """
        :param points: Points on which scalar multiplication with secret scalar was performed
        :param traces: Power traces corresponding to the scalar multiplication for each of the points
        :param mult: Scalar multiplier used
        :param params: Domain parameters used
        """
        self.points = points
        self.traces = np.array([trace.samples for trace in traces]).transpose()
        self.mult = mult
        self.params = params
        self.leakage_model = leakage_model
        self.correlations = {"guess_one": [], "guess_zero": []}

    def compute_intermediate_value(
        self, guessed_scalar: int, target_bit: int, point: Point
    ) -> Mod:
        with local(DefaultContext()) as ctx:
            self.mult.init(self.params, point)
            self.mult.multiply(guessed_scalar)
        action_index = -1
        for bit in bin(guessed_scalar)[2 : target_bit + 2]:
            if bit == "1":
                action_index += 2
            elif bit == "0":
                action_index += 1
        result = ctx.actions[0].get_by_index([action_index]).action
        return result.output_points[0].X

    def compute_correlation_trace(
        self, guessed_scalar: int, target_bit: int
    ) -> list[float]:
        correlation_trace = []
        intermediate_values = []
        for i in range(len(self.points)):
            intermediate_value = self.compute_intermediate_value(
                guessed_scalar, target_bit, self.points[i]
            )
            intermediate_values.append(self.leakage_model(intermediate_value))
        for trace in self.traces:
            correlation_trace.append(pearsonr(intermediate_values, trace)[0])
        return correlation_trace

    def plot_correlations(self, ct):
        return plot_trace(Trace(np.array(ct))).opts(width=950, height=600)

    def recover_bit(
        self,
        recovered_scalar: int,
        target_bit: int,
        scalar_bit_length: int,
        real_pub_key: Point,
    ) -> int:
        if target_bit == scalar_bit_length - 1:
            self.mult.init(self.params, self.params.generator)
            if real_pub_key == self.mult.multiply(recovered_scalar):
                return recovered_scalar
            return recovered_scalar | 1
        mask = 1 << (scalar_bit_length - target_bit - 1)
        guessed_scalar_0 = recovered_scalar
        guessed_scalar_1 = recovered_scalar | mask
        correlation_trace_0 = self.compute_correlation_trace(
            guessed_scalar_0, target_bit
        )
        correlation_trace_1 = self.compute_correlation_trace(
            guessed_scalar_1, target_bit
        )
        self.correlations["guess_zero"].append(correlation_trace_0)
        self.correlations["guess_one"].append(correlation_trace_1)
        if np.nanmax(np.abs(correlation_trace_0)) > np.nanmax(
            np.abs(correlation_trace_1)
        ):
            return guessed_scalar_0
        return guessed_scalar_1

    def perform(self, scalar_bit_length: int, real_pub_key: Point) -> int:
        recovered_scalar = 1 << (scalar_bit_length - 1)
        for target_bit in range(1, scalar_bit_length):
            recovered_scalar = self.recover_bit(
                recovered_scalar, target_bit, scalar_bit_length, real_pub_key
            )
        return recovered_scalar