aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJ08nY2020-06-13 01:59:22 +0200
committerJ08nY2020-06-13 01:59:22 +0200
commit4e17dfdb12707c814add7851c81eda4edb3dacde (patch)
tree1f59c7adad2cc58e3a53d995b029c5a76f591411
parent23b3638a496637c1810fb5a2bd610b63b1a72521 (diff)
downloadpyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.tar.gz
pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.tar.bz2
pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.zip
Add docs and tests to the ECTester target.
-rw-r--r--docs/conf.py4
-rw-r--r--pyecsca/ec/curve.py49
-rw-r--r--pyecsca/ec/mod.py78
-rw-r--r--pyecsca/sca/target/ISO7816.py2
-rw-r--r--pyecsca/sca/target/__init__.py2
-rw-r--r--pyecsca/sca/target/ectester.py272
-rw-r--r--test/ec/test_curve.py13
-rw-r--r--test/ec/test_mod.py18
-rw-r--r--test/sca/test_target.py288
9 files changed, 687 insertions, 39 deletions
diff --git a/docs/conf.py b/docs/conf.py
index f84364b..7f4489b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -207,4 +207,6 @@ autodoc_default_options = {
"undoc-members": True,
"inherited-members": True,
"show-inheritance": True
-} \ No newline at end of file
+}
+
+nbsphinx_allow_errors = True
diff --git a/pyecsca/ec/curve.py b/pyecsca/ec/curve.py
index e8ae66c..a05ff3f 100644
--- a/pyecsca/ec/curve.py
+++ b/pyecsca/ec/curve.py
@@ -6,8 +6,8 @@ from public import public
from .coordinates import CoordinateModel, AffineCoordinateModel
from .mod import Mod
-from .model import CurveModel
-from .point import Point
+from .model import CurveModel, ShortWeierstrassModel
+from .point import Point, InfinityPoint
@public
@@ -93,17 +93,62 @@ class EllipticCurve(object):
@property
def neutral_is_affine(self):
+ """Whether the neurtal point is an affine point."""
return bool(self.model.base_neutral)
def is_neutral(self, point: Point) -> bool:
+ """Check whether the point is the neutral point."""
return self.neutral == point
def is_on_curve(self, point: Point) -> bool:
+ """Check whether the point is on the curve."""
if point.coordinate_model.curve_model != self.model:
return False
+ if self.is_neutral(point):
+ return True
loc = {**self.parameters, **point.to_affine().coords}
return eval(compile(self.model.equation, "", mode="eval"), loc)
+ def to_affine(self) -> "EllipticCurve":
+ """Convert this curve into the affine coordinate model, if possible."""
+ coord_model = AffineCoordinateModel(self.model)
+ return EllipticCurve(self.model, coord_model, self.prime, self.neutral.to_affine(), self.parameters)
+
+ def decode_point(self, encoded: bytes) -> Point:
+ """Decode a point encoded as a sequence of bytes (ANSI X9.62)."""
+ if encoded[0] == 0x00 and len(encoded) == 1:
+ return InfinityPoint(self.coordinate_model)
+ coord_len = (self.prime.bit_length() + 7) // 8
+ if encoded[0] in (0x04, 0x06):
+ data = encoded[1:]
+ if len(data) != coord_len * len(self.coordinate_model.variables):
+ raise ValueError("Encoded point has bad length")
+ coords = {}
+ for var in sorted(self.coordinate_model.variables):
+ coords[var] = Mod(int.from_bytes(data[:coord_len], "big"), self.prime)
+ data = data[coord_len:]
+ return Point(self.coordinate_model, **coords)
+ elif encoded[0] in (0x02, 0x03):
+ if isinstance(self.coordinate_model, AffineCoordinateModel) and isinstance(self.model, ShortWeierstrassModel):
+ data = encoded[1:]
+ if len(data) != coord_len:
+ raise ValueError("Encoded point has bad length")
+ x = Mod(int.from_bytes(data, "big"), self.prime)
+ rhs = x**3 + self.parameters["a"] * x + self.parameters["b"]
+ if not rhs.is_residue():
+ raise ValueError("Point not on curve")
+ sqrt = rhs.sqrt()
+ yp = encoded[0] & 0x01
+ if int(sqrt) & 0x01 == yp:
+ y = sqrt
+ else:
+ y = -sqrt
+ return Point(self.coordinate_model, x=x, y=y)
+ else:
+ raise NotImplementedError
+ else:
+ raise ValueError(f"Wrong encoding type: {hex(encoded[0])}, should be one of 0x04, 0x06, 0x02, 0x03 or 0x00")
+
def __eq__(self, other):
if not isinstance(other, EllipticCurve):
return False
diff --git a/pyecsca/ec/mod.py b/pyecsca/ec/mod.py
index ec5dfe6..421f521 100644
--- a/pyecsca/ec/mod.py
+++ b/pyecsca/ec/mod.py
@@ -1,5 +1,6 @@
+import random
import secrets
-from functools import wraps
+from functools import wraps, lru_cache
from public import public
@@ -39,6 +40,34 @@ def extgcd(a, b):
return x2, y2, a
+@public
+@lru_cache
+def miller_rabin(n: int, rounds: int = 50) -> bool:
+ """Miller-Rabin probabilistic primality test."""
+ if n == 2 or n == 3:
+ return True
+
+ if n % 2 == 0:
+ return False
+
+ r, s = 0, n - 1
+ while s % 2 == 0:
+ r += 1
+ s //= 2
+ for _ in range(rounds):
+ a = random.randrange(2, n - 1)
+ x = pow(a, s, n)
+ if x == 1 or x == n - 1:
+ continue
+ for _ in range(r - 1):
+ x = pow(x, 2, n)
+ if x == n - 1:
+ break
+ else:
+ return False
+ return True
+
+
def check(func):
@wraps(func)
def method(self, other):
@@ -99,6 +128,53 @@ class Mod(object):
def __invert__(self):
return self.inverse()
+ def is_residue(self):
+ """Whether this element is a quadratic residue (only implemented for prime modulus)."""
+ if not miller_rabin(self.n):
+ raise NotImplementedError
+ if self.x == 0:
+ return True
+ if self.n == 2:
+ return self.x in (0, 1)
+ legendre = self ** ((self.n - 1) // 2)
+ return legendre == 1
+
+ def sqrt(self):
+ """
+ The modular square root of this element (only implemented for prime modulus).
+
+ Uses the `Tonelli-Shanks <https://en.wikipedia.org/wiki/Tonelli–Shanks_algorithm>`_ algorithm.
+ """
+ if not miller_rabin(self.n):
+ raise NotImplementedError
+ q = self.n - 1
+ s = 0
+ while q % 2 == 0:
+ q //= 2
+ s += 1
+
+ z = 2
+ while Mod(z, self.n).is_residue():
+ z += 1
+
+ m = s
+ c = Mod(z, self.n) ** q
+ t = self ** q
+ r_exp = (q + 1) // 2
+ r = self ** r_exp
+
+ while t != 1:
+ i = 1
+ while not (t ** (2**i)) == 1:
+ i += 1
+ two_exp = m - (i + 1)
+ b = c ** int(Mod(2, self.n)**two_exp)
+ m = int(Mod(i, self.n))
+ c = b ** 2
+ t *= c
+ r *= b
+ return r
+
@check
def __mul__(self, other):
return Mod((self.x * other.x) % self.n, self.n)
diff --git a/pyecsca/sca/target/ISO7816.py b/pyecsca/sca/target/ISO7816.py
index 8d5385a..3cf712f 100644
--- a/pyecsca/sca/target/ISO7816.py
+++ b/pyecsca/sca/target/ISO7816.py
@@ -48,7 +48,7 @@ class CommandAPDU(object): # pragma: no cover
@dataclass
class ResponseAPDU(object):
"""A response APDU that can be received from an ISO7816-4 target."""
- data: Optional[bytes]
+ data: bytes
sw: int
diff --git a/pyecsca/sca/target/__init__.py b/pyecsca/sca/target/__init__.py
index a30ea25..f1555dd 100644
--- a/pyecsca/sca/target/__init__.py
+++ b/pyecsca/sca/target/__init__.py
@@ -18,7 +18,7 @@ except ImportError: # pragma: no cover
pass
try:
- import pyscard
+ import smartcard
has_pyscard = True
except ImportError: # pragma: no cover
diff --git a/pyecsca/sca/target/ectester.py b/pyecsca/sca/target/ectester.py
index 31ff2a2..e69695a 100644
--- a/pyecsca/sca/target/ectester.py
+++ b/pyecsca/sca/target/ectester.py
@@ -1,16 +1,20 @@
from abc import ABC
+from binascii import hexlify
from enum import IntEnum, IntFlag
from functools import reduce
from math import ceil, log
from operator import or_
-from typing import Optional, Mapping, List
+from typing import Optional, Mapping, List, Union
from public import public
from smartcard.CardConnection import CardConnection
from smartcard.Exceptions import CardConnectionException
+from .ISO7816 import CommandAPDU, ResponseAPDU, ISO7816
from .PCSC import PCSCTarget
-from .. import CommandAPDU, ResponseAPDU, ISO7816
+from ...ec.model import ShortWeierstrassModel
+from ...ec.params import DomainParameters
+from ...ec.point import Point
class ShiftableFlag(IntFlag):
@@ -28,6 +32,16 @@ class ShiftableFlag(IntFlag):
return e
raise ValueError
+ def __iter__(self):
+ val = int(self)
+ for e in self.__class__:
+ i = int(e)
+ if i & val == i:
+ while i % 2 == 0 and i != 0:
+ i //= 2
+ if i == 1:
+ yield e
+
@public
class KeypairEnum(ShiftableFlag):
@@ -226,50 +240,69 @@ class Response(ABC):
self.params[i] = resp.data[offset:offset + param_len]
offset += param_len
+ def __repr__(self):
+ return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error})"
+
+@public
class AllocateKaResponse(Response):
+ """A response to the KeyAgreement allocation command."""
def __init__(self, resp: ResponseAPDU):
super().__init__(resp, 1, 0)
+@public
class AllocateSigResponse(Response):
+ """A response to the Signature allocation command."""
def __init__(self, resp: ResponseAPDU):
super().__init__(resp, 1, 0)
+@public
class AllocateResponse(Response):
+ """A response to the KeyPair allocation command."""
def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
+@public
class ClearResponse(Response):
+ """A response to the Clear key command."""
def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
+@public
class SetResponse(Response):
+ """A response to the Set command."""
def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
+@public
class TransformResponse(Response):
+ """A response to the Transform command."""
def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
+@public
class GenerateResponse(Response):
+ """A response to the Generate command."""
def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum):
super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
+@public
class ExportResponse(Response):
+ """A response to the Export command, contains the exported parameters/values."""
keypair: KeypairEnum
key: KeyEnum
parameters: ParameterEnum
@@ -288,7 +321,7 @@ class ExportResponse(Response):
param_count += 1
if param == ParameterEnum.K:
break
- param << 1
+ param <<= 1
other = 0
other += 1 if key & KeyEnum.PUBLIC and params & ParameterEnum.W else 0
other += 1 if key & KeyEnum.PRIVATE and params & ParameterEnum.S else 0
@@ -325,45 +358,63 @@ class ExportResponse(Response):
return self.params[index]
return None
+ def __repr__(self):
+ return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, " \
+ f"keypair={self.keypair.name}, key={self.key.name}, params={self.parameters.name})"
+
+@public
class ECDHResponse(Response):
+ """A response to the ECDH and ECDH_direct KeyAgreement commands."""
def __init__(self, resp: ResponseAPDU, export: bool):
super().__init__(resp, 1, 1 if export else 0)
@property
def secret(self):
- if len(self.params) == 0:
+ if len(self.params) != 0:
return self.params[0]
return None
+ def __repr__(self):
+ return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, secret={hexlify(self.secret).decode() if self.secret else ''})"
+
+@public
class ECDSAResponse(Response):
+ """A response to the ECDSA and ECDSA sign and ECDSA verify commands."""
def __init__(self, resp: ResponseAPDU, export: bool):
super().__init__(resp, 1, 1 if export else 0)
@property
def signature(self):
- if len(self.params) == 0:
+ if len(self.params) != 0:
return self.params[0]
return None
+ def __repr__(self):
+ return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, sig={hexlify(self.signature).decode() if self.signature else ''})"
+
+@public
class CleanupResponse(Response):
+ """A response to the Cleanup command."""
def __init__(self, resp: ResponseAPDU):
super().__init__(resp, 1, 0)
+@public
class RunModeResponse(Response):
+ """A response to the Set run mode command."""
def __init__(self, resp: ResponseAPDU):
super().__init__(resp, 1, 0)
class InfoResponse(Response):
- sw: int
+ """A response to the Info command, contains all information about the applet version/environment."""
version: str
base: AppletBaseEnum
system_version: float
@@ -376,9 +427,7 @@ class InfoResponse(Response):
def __init__(self, resp: ResponseAPDU):
super().__init__(resp, 1, 0)
- offset = 0
- self.sw = int.from_bytes(resp.data[offset:offset + 2], "big")
- offset += 2
+ offset = 2
version_len = int.from_bytes(resp.data[offset:offset + 2], "big")
offset += 2
self.version = resp.data[offset:offset + version_len].decode()
@@ -402,9 +451,18 @@ class InfoResponse(Response):
self.apdu_len = int.from_bytes(resp.data[offset:offset + 2], "big")
offset += 2
+ def __repr__(self):
+ return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, " \
+ f"success={self.success}, error={self.error}, version={self.version}, base={self.base.name}, system_version={self.system_version}, " \
+ f"object_deletion_supported={self.object_deletion_supported}, buf_len={self.buf_len}, ram1_len={self.ram1_len}, ram2_len={self.ram2_len}, apdu_len={self.apdu_len})"
+
@public
class ECTesterTarget(PCSCTarget):
+ """
+ A smartcard target which communicates with the `ECTester <https://github.com/crocs-muni/ECTester>`_
+ applet on smartcards of the JavaCard platform using PCSC.
+ """
CLA_ECTESTER = 0xb0
AID_PREFIX = bytes([0x45, 0x43, 0x54, 0x65, 0x73, 0x74, 0x65, 0x72])
AID_CURRENT_VERSION = bytes([0x30, 0x33, 0x33]) # Version v0.3.3
@@ -442,6 +500,7 @@ class ECTesterTarget(PCSCTarget):
return resp
def select_applet(self, latest_version: bytes = AID_CURRENT_VERSION):
+ """Select the *ECTester* applet, with a specified version or older."""
version_bytes = bytearray(latest_version)
for i in range(10):
aid_222 = self.AID_PREFIX + version_bytes + self.AID_SUFFIX_222
@@ -469,31 +528,104 @@ class ECTesterTarget(PCSCTarget):
return False
return True
- def allocate_ka(self, ka_type: KeyAgreementEnum):
+ @staticmethod
+ def encode_parameters(params: ParameterEnum, obj: Union[DomainParameters, Point, int]) -> \
+ Mapping[ParameterEnum, bytes]:
+ """Encode values from `obj` into the byte parameters that the **ECTester** applet expects."""
+
+ def convert_int(obj: int) -> bytes:
+ ilen = (obj.bit_length() + 7) // 8
+ return obj.to_bytes(ilen, "big")
+
+ def convert_point(obj: Point) -> bytes:
+ return bytes(obj)
+
+ result = {}
+ if isinstance(obj, DomainParameters) and isinstance(obj.curve.model, ShortWeierstrassModel):
+ for param in params & ParameterEnum.DOMAIN_FP:
+ if param == ParameterEnum.G:
+ result[param] = convert_point(obj.generator.to_affine())
+ elif param == ParameterEnum.FP:
+ result[param] = convert_int(obj.curve.prime)
+ elif param == ParameterEnum.A:
+ result[param] = convert_int(obj.curve.parameters["a"].x)
+ elif param == ParameterEnum.B:
+ result[param] = convert_int(obj.curve.parameters["b"].x)
+ elif param == ParameterEnum.R:
+ result[param] = convert_int(obj.order)
+ elif param == ParameterEnum.K:
+ result[param] = convert_int(obj.cofactor)
+ elif isinstance(obj, Point):
+ for param in params & (ParameterEnum.G | ParameterEnum.W):
+ result[param] = convert_point(obj)
+ elif isinstance(obj, int):
+ for param in params & ((ParameterEnum.DOMAIN_FP ^ ParameterEnum.G) | ParameterEnum.S):
+ result[param] = convert_int(obj)
+ else:
+ raise TypeError
+ return result
+
+ def allocate_ka(self, ka_type: KeyAgreementEnum) -> AllocateKaResponse:
+ """
+ Send the Allocate KeyAgreement command.
+
+ :param ka_type: Which KeyAgreement type to allocate.
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE_KA, 0, 0,
bytes([ka_type])))
return AllocateKaResponse(resp)
- def allocate_sig(self, sig_type: SignatureEnum):
+ def allocate_sig(self, sig_type: SignatureEnum) -> AllocateSigResponse:
+ """
+ Send the Allocate Signature command.
+
+ :param sig_type: Which Signature type to allocate.
+ :return: The response.
+ """
resp = self.send_apdu(CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE_SIG, 0, 0,
bytes([sig_type])))
return AllocateSigResponse(resp)
def allocate(self, keypair: KeypairEnum, builder: KeyBuildEnum, key_length: int,
- key_class: KeyClassEnum):
+ key_class: KeyClassEnum) -> AllocateResponse:
+ """
+ Send the Allocate KeyPair command.
+
+ :param keypair: Which keypair to allocate.
+ :param builder: Which builder to use to allocate the keypair.
+ :param key_length: Bit-size of the allocated keypair.
+ :param key_class: Type of the allocated keypair.
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE, keypair, builder,
key_length.to_bytes(2, "big") + bytes([key_class])))
return AllocateResponse(resp, keypair)
- def clear(self, keypair: KeypairEnum):
+ def clear(self, keypair: KeypairEnum) -> ClearResponse:
+ """
+ Send the Clear key command.
+
+ :param keypair: Which keypair to clear.
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEAR, keypair, 0, None))
return ClearResponse(resp, keypair)
def set(self, keypair: KeypairEnum, curve: CurveEnum, params: ParameterEnum,
- values: Optional[Mapping[ParameterEnum, bytes]] = None):
+ values: Optional[Mapping[ParameterEnum, bytes]] = None) -> SetResponse:
+ """
+ Send the Set command.
+
+ :param keypair: Which keypair to set values on.
+ :param curve: Which pre-set curve to use to set values, or default or external.
+ :param params: Which parameters to set on the keypair.
+ :param values: External values to set on the keypair.
+ :return: The response.
+ """
if curve == CurveEnum.external and values is not None:
if params != reduce(or_, values.keys()):
raise ValueError("Params and values need to have the same keys.")
@@ -517,25 +649,58 @@ class ECTesterTarget(PCSCTarget):
return SetResponse(resp, keypair)
def transform(self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum,
- transformation: TransformationEnum):
+ transformation: TransformationEnum) -> TransformResponse:
+ """
+ Send the Transform command.
+
+ :param keypair: Which keypair to transform.
+ :param key: Which key to apply the transform to.
+ :param params: Which parameters to transform.
+ :param transformation: What transformation to apply.
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_TRANSFORM, keypair, key,
params.to_bytes(2, "big") + transformation.to_bytes(2, "big")))
return TransformResponse(resp, keypair)
- def generate(self, keypair: KeypairEnum):
+ def generate(self, keypair: KeypairEnum) -> GenerateResponse:
+ """
+ Send the Generate command.
+
+ :param keypair: Which keypair to generate.
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GENERATE, keypair, 0, None))
return GenerateResponse(resp, keypair)
- def export(self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum):
+ def export(self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum) -> ExportResponse:
+ """
+ Send the Export command.
+
+ :param keypair: Which keypair to export from.
+ :param key: Which key to export from.
+ :param params: Which parameters to export.
+ :return: The response, containing the exported parameters.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_EXPORT, keypair, key,
params.to_bytes(2, "big")))
return ExportResponse(resp, keypair, key, params)
def ecdh(self, pubkey: KeypairEnum, privkey: KeypairEnum, export: bool,
- transformation: TransformationEnum, ka_type: KeyAgreementEnum):
+ transformation: TransformationEnum, ka_type: KeyAgreementEnum) -> ECDHResponse:
+ """
+ Send the ECDH command.
+
+ :param pubkey: Which keypair to use the pubkey from, in the key-agreement.
+ :param privkey: Which keypair to use the privkey from, in the key-agreement.
+ :param export: Whether to export the shared secret.
+ :param transformation: The transformation to apply to the pubkey before key-agreement.
+ :param ka_type: The key-agreement type to use.
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDH, pubkey, privkey,
bytes([ExportEnum.from_bool(export)]) + transformation.to_bytes(
@@ -543,7 +708,17 @@ class ECTesterTarget(PCSCTarget):
return ECDHResponse(resp, export)
def ecdh_direct(self, privkey: KeypairEnum, export: bool, transformation: TransformationEnum,
- ka_type: KeyAgreementEnum, pubkey: bytes):
+ ka_type: KeyAgreementEnum, pubkey: bytes) -> ECDHResponse:
+ """
+ Send the ECDH direct command.
+
+ :param privkey: Which keypair to use the privkey from, in the key-agreement.
+ :param export: Whether to export the shared secret.
+ :param transformation: The transformation to apply to the pubkey before key-agreement.
+ :param ka_type: The key-agreement type to use.
+ :param pubkey: The raw bytes that will be used as a pubkey in the key-agreement.
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDH_DIRECT, privkey,
ExportEnum.from_bool(export),
@@ -551,16 +726,33 @@ class ECTesterTarget(PCSCTarget):
pubkey).to_bytes(2, "big") + pubkey))
return ECDHResponse(resp, export)
- def ecdsa(self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, data: bytes):
+ def ecdsa(self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum,
+ data: bytes) -> ECDSAResponse:
+ """
+ Send the ECDSA command.
+
+ :param keypair: The keypair to use.
+ :param export: Whether to export the signature.
+ :param sig_type: The Signature type to use.
+ :param data: The data to sign and verify.
+ :return: The response.
+ """
resp = self.send_apdu(CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDSA, keypair,
ExportEnum.from_bool(export),
bytes([sig_type]) + len(data).to_bytes(2, "big") + data))
return ECDSAResponse(resp, export)
def ecdsa_sign(self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum,
- data: Optional[bytes] = None):
- if not data:
- data = bytes()
+ data: bytes) -> ECDSAResponse:
+ """
+ Send the ECDSA sign command.
+
+ :param keypair: The keypair to use to sign.
+ :param export: Whether to export the signature.
+ :param sig_type: The Signature type to use.
+ :param data: The data to sign.
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDSA_SIGN, keypair,
ExportEnum.from_bool(export),
@@ -568,26 +760,48 @@ class ECTesterTarget(PCSCTarget):
return ECDSAResponse(resp, export)
def ecdsa_verify(self, keypair: KeypairEnum, sig_type: SignatureEnum, sig: bytes,
- data: Optional[bytes] = None):
- if not data:
- data = bytes()
+ data: bytes) -> ECDSAResponse:
+ """
+ Send the ECDSA verify command.
+
+ :param keypair: The keypair to use to verify.
+ :param sig_type: The Signature type to use.
+ :param sig: The signature to verify.
+ :param data: The data.
+ :return: The response.
+ """
resp = self.send_apdu(CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDSA_VERIFY,
keypair, sig_type,
len(data).to_bytes(2, "big") + data + len(sig).to_bytes(2,
"big") + sig))
return ECDSAResponse(resp, False)
- def cleanup(self):
+ def cleanup(self) -> CleanupResponse:
+ """
+ Send the Cleanup command.
+
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEANUP, 0, 0, None))
return CleanupResponse(resp)
- def info(self):
+ def info(self) -> InfoResponse:
+ """
+ Send the Info command.
+
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GET_INFO, 0, 0, None))
return InfoResponse(resp)
- def run_mode(self, run_mode: RunModeEnum):
+ def run_mode(self, run_mode: RunModeEnum) -> RunModeResponse:
+ """
+ Send the Run mode command.
+
+ :return: The response.
+ """
resp = self.send_apdu(
CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_SET_DRY_RUN_MODE, run_mode, 0,
None))
diff --git a/test/ec/test_curve.py b/test/ec/test_curve.py
index 91d9f16..1b22279 100644
--- a/test/ec/test_curve.py
+++ b/test/ec/test_curve.py
@@ -1,5 +1,7 @@
+from binascii import unhexlify
from unittest import TestCase
+from pyecsca.ec.coordinates import AffineCoordinateModel
from pyecsca.ec.curve import EllipticCurve
from pyecsca.ec.params import get_params
from pyecsca.ec.mod import Mod
@@ -76,3 +78,14 @@ class CurveTests(TestCase):
self.assertEqual(self.secp128r1.curve, self.secp128r1.curve)
self.assertNotEqual(self.secp128r1.curve, self.curve25519.curve)
self.assertNotEqual(self.secp128r1.curve, None)
+
+ def test_decode(self):
+ affine_curve = self.secp128r1.curve.to_affine()
+ affine_point = self.secp128r1.generator.to_affine()
+ decoded = affine_curve.decode_point(bytes(affine_point))
+ self.assertEqual(decoded, affine_point)
+
+ affine_compressed_bytes = unhexlify("03161ff7528b899b2d0c28607ca52c5b86")
+ decoded_compressed = affine_curve.decode_point(affine_compressed_bytes)
+ self.assertEqual(decoded_compressed, affine_point)
+
diff --git a/test/ec/test_mod.py b/test/ec/test_mod.py
index 59b716e..59c8e24 100644
--- a/test/ec/test_mod.py
+++ b/test/ec/test_mod.py
@@ -1,6 +1,6 @@
from unittest import TestCase
-from pyecsca.ec.mod import Mod, gcd, extgcd, Undefined
+from pyecsca.ec.mod import Mod, gcd, extgcd, Undefined, miller_rabin
class ModTests(TestCase):
@@ -10,6 +10,22 @@ class ModTests(TestCase):
self.assertEqual(extgcd(15, 0), (1, 0, 15))
self.assertEqual(extgcd(15, 20), (-1, 1, 5))
+ def test_miller_rabin(self):
+ self.assertTrue(miller_rabin(2))
+ self.assertTrue(miller_rabin(3))
+ self.assertTrue(miller_rabin(5))
+ self.assertFalse(miller_rabin(8))
+ self.assertTrue(miller_rabin(0xe807561107ccf8fa82af74fd492543a918ca2e9c13750233a9))
+ self.assertFalse(miller_rabin(0x6f6889deb08da211927370810f026eb4c17b17755f72ea005))
+
+ def test_is_residue(self):
+ self.assertTrue(Mod(4, 11).is_residue())
+ self.assertFalse(Mod(11, 31).is_residue())
+
+ def test_sqrt(self):
+ p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
+ self.assertIn(Mod(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc, p).sqrt(), (0x9add512515b70d9ec471151c1dec46625cd18b37bde7ca7fb2c8b31d7033599d, 0x6522aed9ea48f2623b8eeae3e213b99da32e74c9421835804d374ce28fcca662))
+
def test_wrong_mod(self):
a = Mod(5, 7)
b = Mod(4, 11)
diff --git a/test/sca/test_target.py b/test/sca/test_target.py
index 25d506a..5519502 100644
--- a/test/sca/test_target.py
+++ b/test/sca/test_target.py
@@ -1,12 +1,30 @@
-from unittest import TestCase
+from copy import copy
from os.path import realpath, dirname, join
+from typing import Optional
+from unittest import TestCase, SkipTest
+from pyecsca.ec.key_agreement import ECDH_SHA1
+from pyecsca.ec.key_generation import KeyGeneration
+from pyecsca.ec.mod import Mod
+from pyecsca.ec.mult import LTRMultiplier
+from pyecsca.ec.params import DomainParameters, get_params
+from pyecsca.ec.point import Point
+from pyecsca.ec.signature import SignatureResult, ECDSA_SHA1
+from pyecsca.sca.target import BinaryTarget, SimpleSerialTarget, SimpleSerialMessage, has_pyscard
+from pyecsca.sca.target.ectester import (KeyAgreementEnum, SignatureEnum, KeypairEnum, KeyBuildEnum,
+ KeyClassEnum, CurveEnum, ParameterEnum, RunModeEnum,
+ KeyEnum, TransformationEnum)
+
+if has_pyscard:
+ from pyecsca.sca.target.ectester import ECTesterTarget
+else:
+ ECTesterTarget = None
-from pyecsca.sca.target import BinaryTarget, SimpleSerialTarget, SimpleSerialMessage
class TestTarget(SimpleSerialTarget, BinaryTarget):
pass
+
class BinaryTargetTests(TestCase):
def test_basic_target(self):
@@ -17,4 +35,268 @@ class BinaryTargetTests(TestCase):
self.assertIn("r", resp)
self.assertIn("z", resp)
self.assertEqual(resp["r"].data, "01020304")
- target.disconnect() \ No newline at end of file
+ target.disconnect()
+
+
+class ECTesterTargetTests(TestCase):
+ reader: Optional[str] = None
+ target: Optional[ECTesterTarget] = None
+ secp256r1: DomainParameters
+ secp256r1_projective: DomainParameters
+
+ @classmethod
+ def setUpClass(cls):
+ if not has_pyscard:
+ return
+ from smartcard.System import readers
+ rs = readers()
+ if not rs:
+ return
+ cls.reader = rs[0]
+ cls.secp256r1 = get_params("secg", "secp256r1", "affine")
+ cls.secp256r1_projective = get_params("secg", "secp256r1", "projective")
+
+ def setUp(self):
+ if not ECTesterTargetTests.reader:
+ raise SkipTest("No smartcard readers.")
+ self.target = ECTesterTarget(ECTesterTargetTests.reader)
+ self.target.connect()
+ if not self.target.select_applet():
+ self.target.disconnect()
+ raise SkipTest("No applet in reader: {}".format(ECTesterTargetTests.reader))
+
+ def tearDown(self):
+ self.target.cleanup()
+ self.target.disconnect()
+
+ def test_allocate(self):
+ ka_resp = self.target.allocate_ka(KeyAgreementEnum.ALG_EC_SVDP_DH)
+ self.assertTrue(ka_resp.success)
+ sig_resp = self.target.allocate_sig(SignatureEnum.ALG_ECDSA_SHA)
+ self.assertTrue(sig_resp.success)
+ key_resp = self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.assertTrue(key_resp.success)
+
+ def test_set(self):
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ set_resp = self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1,
+ ParameterEnum.DOMAIN_FP)
+ self.assertTrue(set_resp.success)
+
+ def test_set_explicit(self):
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ values = ECTesterTarget.encode_parameters(ParameterEnum.DOMAIN_FP, self.secp256r1)
+ set_resp = self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.external,
+ ParameterEnum.DOMAIN_FP, values)
+ self.assertTrue(set_resp.success)
+
+ def test_generate(self):
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ generate_resp = self.target.generate(KeypairEnum.KEYPAIR_LOCAL)
+ self.assertTrue(generate_resp.success)
+
+ def test_clear(self):
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ clear_resp = self.target.clear(KeypairEnum.KEYPAIR_LOCAL)
+ self.assertTrue(clear_resp.success)
+
+ def test_cleanup(self):
+ cleanup_resp = self.target.cleanup()
+ self.assertTrue(cleanup_resp.success)
+
+ def test_info(self):
+ info_resp = self.target.info()
+ self.assertTrue(info_resp.success)
+
+ def test_dry_run(self):
+ dry_run_resp = self.target.run_mode(RunModeEnum.MODE_DRY_RUN)
+ self.assertTrue(dry_run_resp.success)
+ allocate_resp = self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR,
+ 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.assertTrue(allocate_resp.success)
+ dry_run_resp = self.target.run_mode(RunModeEnum.MODE_NORMAL)
+ self.assertTrue(dry_run_resp.success)
+
+ def test_export(self):
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ self.target.generate(KeypairEnum.KEYPAIR_LOCAL)
+ export_public_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC,
+ ParameterEnum.W)
+ self.assertTrue(export_public_resp.success)
+ pubkey_bytes = export_public_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W)
+ pubkey = self.secp256r1.curve.decode_point(pubkey_bytes)
+ export_privkey_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE,
+ ParameterEnum.S)
+ self.assertTrue(export_privkey_resp.success)
+ privkey = int.from_bytes(
+ export_privkey_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), "big")
+ self.assertEqual(pubkey,
+ self.secp256r1.curve.affine_multiply(self.secp256r1.generator, privkey))
+
+ def test_export_curve(self):
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ export_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC,
+ ParameterEnum.DOMAIN_FP)
+ self.assertTrue(export_resp.success)
+
+ def test_transform(self):
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ self.target.generate(KeypairEnum.KEYPAIR_LOCAL)
+ export_privkey_resp1 = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE,
+ ParameterEnum.S)
+ privkey = int.from_bytes(
+ export_privkey_resp1.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), "big")
+ transform_resp = self.target.transform(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE,
+ ParameterEnum.S, TransformationEnum.INCREMENT)
+ self.assertTrue(transform_resp.success)
+ export_privkey_resp2 = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE,
+ ParameterEnum.S)
+ privkey_new = int.from_bytes(
+ export_privkey_resp2.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), "big")
+ self.assertEqual(privkey + 1, privkey_new)
+
+ def test_ecdh(self):
+ self.target.allocate_ka(KeyAgreementEnum.ALG_EC_SVDP_DH)
+ self.target.allocate(KeypairEnum.KEYPAIR_BOTH, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_BOTH, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ self.target.generate(KeypairEnum.KEYPAIR_BOTH)
+ ecdh_resp = self.target.ecdh(KeypairEnum.KEYPAIR_LOCAL, KeypairEnum.KEYPAIR_REMOTE, True,
+ TransformationEnum.NONE, KeyAgreementEnum.ALG_EC_SVDP_DH)
+ self.assertTrue(ecdh_resp.success)
+ export_public_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC,
+ ParameterEnum.W)
+ pubkey_bytes = export_public_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W)
+ pubkey = self.secp256r1.curve.decode_point(pubkey_bytes)
+ export_privkey_resp = self.target.export(KeypairEnum.KEYPAIR_REMOTE, KeyEnum.PRIVATE,
+ ParameterEnum.S)
+ privkey = Mod(int.from_bytes(
+ export_privkey_resp.get_param(KeypairEnum.KEYPAIR_REMOTE, ParameterEnum.S), "big"),
+ self.secp256r1.curve.prime)
+ pubkey_projective = Point.from_affine(self.secp256r1_projective.curve.coordinate_model,
+ pubkey)
+ mult = LTRMultiplier(
+ self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"],
+ self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"])
+ ecdh = ECDH_SHA1(mult, self.secp256r1_projective, pubkey_projective, privkey)
+ expected = ecdh.perform()
+ self.assertEqual(ecdh_resp.secret, expected)
+
+ def test_ecdh_raw(self):
+ self.target.allocate_ka(KeyAgreementEnum.ALG_EC_SVDP_DH)
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ self.target.generate(KeypairEnum.KEYPAIR_LOCAL)
+ mult = LTRMultiplier(
+ self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"],
+ self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"])
+ keygen = KeyGeneration(copy(mult), self.secp256r1_projective)
+ priv, pubkey_projective = keygen.generate()
+
+ ecdh_resp = self.target.ecdh_direct(KeypairEnum.KEYPAIR_LOCAL, True,
+ TransformationEnum.NONE,
+ KeyAgreementEnum.ALG_EC_SVDP_DH,
+ bytes(pubkey_projective.to_affine()))
+ self.assertTrue(ecdh_resp.success)
+ export_privkey_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PRIVATE,
+ ParameterEnum.S)
+ privkey = Mod(int.from_bytes(
+ export_privkey_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.S), "big"),
+ self.secp256r1.curve.prime)
+
+ ecdh = ECDH_SHA1(copy(mult), self.secp256r1_projective, pubkey_projective, privkey)
+ expected = ecdh.perform()
+ self.assertEqual(ecdh_resp.secret, expected)
+
+ def test_ecdsa(self):
+ self.target.allocate_sig(SignatureEnum.ALG_ECDSA_SHA)
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ self.target.generate(KeypairEnum.KEYPAIR_LOCAL)
+ data = "Some text over here.".encode()
+ ecdsa_resp = self.target.ecdsa(KeypairEnum.KEYPAIR_LOCAL, True, SignatureEnum.ALG_ECDSA_SHA,
+ data)
+ self.assertTrue(ecdsa_resp.success)
+ export_public_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC,
+ ParameterEnum.W)
+ pubkey_bytes = export_public_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W)
+ pubkey = self.secp256r1.curve.decode_point(pubkey_bytes)
+ pubkey_projective = Point.from_affine(self.secp256r1_projective.curve.coordinate_model,
+ pubkey)
+
+ sig = SignatureResult.from_DER(ecdsa_resp.signature)
+ mult = LTRMultiplier(
+ self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"],
+ self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"])
+ ecdsa = ECDSA_SHA1(copy(mult), self.secp256r1_projective,
+ self.secp256r1_projective.curve.coordinate_model.formulas[
+ "add-2016-rcb"],
+ pubkey_projective)
+ self.assertTrue(ecdsa.verify_data(sig, data))
+
+ def test_ecdsa_sign(self):
+ self.target.allocate_sig(SignatureEnum.ALG_ECDSA_SHA)
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ self.target.generate(KeypairEnum.KEYPAIR_LOCAL)
+ data = "Some text over here.".encode()
+ ecdsa_resp = self.target.ecdsa_sign(KeypairEnum.KEYPAIR_LOCAL, True,
+ SignatureEnum.ALG_ECDSA_SHA, data)
+ self.assertTrue(ecdsa_resp.success)
+ export_public_resp = self.target.export(KeypairEnum.KEYPAIR_LOCAL, KeyEnum.PUBLIC,
+ ParameterEnum.W)
+ pubkey_bytes = export_public_resp.get_param(KeypairEnum.KEYPAIR_LOCAL, ParameterEnum.W)
+ pubkey = self.secp256r1.curve.decode_point(pubkey_bytes)
+ pubkey_projective = Point.from_affine(self.secp256r1_projective.curve.coordinate_model,
+ pubkey)
+
+ sig = SignatureResult.from_DER(ecdsa_resp.signature)
+ mult = LTRMultiplier(
+ self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"],
+ self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"])
+ ecdsa = ECDSA_SHA1(copy(mult), self.secp256r1_projective,
+ self.secp256r1_projective.curve.coordinate_model.formulas[
+ "add-2016-rcb"],
+ pubkey_projective)
+ self.assertTrue(ecdsa.verify_data(sig, data))
+
+ def test_ecdsa_verify(self):
+ self.target.allocate_sig(SignatureEnum.ALG_ECDSA_SHA)
+ self.target.allocate(KeypairEnum.KEYPAIR_LOCAL, KeyBuildEnum.BUILD_KEYPAIR, 256,
+ KeyClassEnum.ALG_EC_FP)
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.secp256r1, ParameterEnum.DOMAIN_FP)
+ mult = LTRMultiplier(
+ self.secp256r1_projective.curve.coordinate_model.formulas["add-2016-rcb"],
+ self.secp256r1_projective.curve.coordinate_model.formulas["dbl-2016-rcb"])
+ keygen = KeyGeneration(copy(mult), self.secp256r1_projective)
+ priv, pubkey_projective = keygen.generate()
+ self.target.set(KeypairEnum.KEYPAIR_LOCAL, CurveEnum.external, ParameterEnum.W,
+ ECTesterTarget.encode_parameters(ParameterEnum.W,
+ pubkey_projective.to_affine()))
+ ecdsa = ECDSA_SHA1(copy(mult), self.secp256r1_projective,
+ self.secp256r1_projective.curve.coordinate_model.formulas[
+ "add-2016-rcb"],
+ pubkey_projective,
+ priv)
+ data = "Some text over here.".encode()
+ sig = ecdsa.sign_data(data)
+
+ ecdsa_resp = self.target.ecdsa_verify(KeypairEnum.KEYPAIR_LOCAL,
+ SignatureEnum.ALG_ECDSA_SHA, sig.to_DER(), data)
+ self.assertTrue(ecdsa_resp.success)