Add Ed25519 keys and EdDSA signatures

This commit is contained in:
Helder Eijs 2022-04-15 00:15:48 +02:00
parent db731bb3a7
commit 764c1e81b9
73 changed files with 6840 additions and 629 deletions

View file

@ -33,7 +33,10 @@ class MockLib(object):
ec_ws_new_context = lambda *x: 0
ec_free_context = lambda *x: None
ec_ws_new_point = lambda *x: 0
ec_free_point = lambda *x: None
ec_ws_free_point = lambda *x: None
ed25519_new_point = lambda *x: 0
ed25519_free_point = lambda *x: None
ed25519_new_point = lambda *x: 0
_raw_api.load_pycryptodome_raw_lib = lambda name, cdec: MockLib()

28
Doc/reqs.txt Normal file
View file

@ -0,0 +1,28 @@
alabaster==0.7.12
Babel==2.9.1
certifi==2021.5.30
chardet==4.0.0
commonmark==0.8.1
docutils==0.17.1
future==0.18.2
idna==2.10
imagesize==1.2.0
Jinja2==2.11.3
MarkupSafe==1.1.1
mock==1.0.1
packaging==20.9
Pillow
Pygments==2.5.2
pyparsing==2.4.7
pytz==2021.1
readthedocs-sphinx-ext==2.1.4
recommonmark==0.5.0
requests==2.26.0
six==1.16.0
snowballstemmer==2.1.0
Sphinx==1.8.5
sphinx-rtd-theme==0.4.3
sphinxcontrib-websupport==1.1.2
#typing==3.10.0.0
typing
urllib3==1.26.7

View file

@ -17,7 +17,7 @@ using widely supported formats like PEM or DER.
.. _curve_names:
.. csv-table::
:header: Curve, Possible identifiers
:header: Curve, Strings accepted for the ``curve`` API parameter
:widths: 20, 80
"NIST P-192", "``'NIST P-192'``, ``'p192'``, ``'P-192'``, ``'prime192v1'``, ``'secp192r1'``"
@ -25,9 +25,12 @@ using widely supported formats like PEM or DER.
"NIST P-256", "``'NIST P-256'``, ``'p256'``, ``'P-256'``, ``'prime256v1'``, ``'secp256r1'``"
"NIST P-384", "``'NIST P-384'``, ``'p384'``, ``'P-384'``, ``'prime384v1'``, ``'secp384r1'``"
"NIST P-521", "``'NIST P-521'``, ``'p521'``, ``'P-521'``, ``'prime521v1'``, ``'secp521r1'``"
"Ed25519", "``'ed25519'``, ``'Ed25519'``"
For more information about each NIST curve see `FIPS 186-4`_, Section D.1.2.
The Ed25519 curve is defined in RFC8032_.
The following example demonstrates how to generate a new ECC key, export it,
and subsequently reload it back into the application::
@ -42,11 +45,12 @@ and subsequently reload it back into the application::
>>> f = open('myprivatekey.pem','rt')
>>> key = ECC.import_key(f.read())
The ECC key can be used to perform or verify ECDSA signatures, using the module
:mod:`Crypto.Signature.DSS`.
The ECC key can be used to perform or verify ECDSA signatures, using the modules
:mod:`Crypto.Signature.DSS` (NIST curves only) or :mod:`Crypto.Signature.eddsa` (Ed25519 curve only).
.. _ECC: http://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/
.. _`FIPS 186-4`: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
.. _RFC8032: https://datatracker.ietf.org/doc/html/rfc8032
.. automodule:: Crypto.PublicKey.ECC
:members:

View file

@ -43,5 +43,7 @@ Available mechanisms
* :doc:`pkcs1_pss`
* :doc:`eddsa`
* :doc:`dsa`

View file

@ -92,7 +92,7 @@ def wrap(private_key, key_oid, passphrase=None, protection=None,
| | value is 1. |
+------------------+-----------------------------------------------+
key_params (DER object):
key_params (DER object or Null):
The algorithm parameters associated to the private key.
It is required for algorithms like DSA, but not for others like RSA.
@ -106,9 +106,6 @@ def wrap(private_key, key_oid, passphrase=None, protection=None,
The PKCS#8-wrapped private key (possibly encrypted), as a byte string.
"""
if key_params is None:
key_params = DerNull()
#
# PrivateKeyInfo ::= SEQUENCE {
# version Version,
@ -117,12 +114,14 @@ def wrap(private_key, key_oid, passphrase=None, protection=None,
# attributes [0] IMPLICIT Attributes OPTIONAL
# }
#
if key_params is None:
algorithm = DerSequence([DerObjectId(key_oid)])
else:
algorithm = DerSequence([DerObjectId(key_oid), key_params])
pk_info = DerSequence([
0,
DerSequence([
DerObjectId(key_oid),
key_params
]),
algorithm,
DerOctetString(private_key)
])
pk_info_der = pk_info.encode()
@ -185,11 +184,12 @@ def unwrap(p8_private_key, passphrase=None):
if not found:
raise ValueError("Error decoding PKCS#8 (%s)" % error_str)
pk_info = DerSequence().decode(p8_private_key, nr_elements=(2, 3, 4))
pk_info = DerSequence().decode(p8_private_key, nr_elements=(2, 3, 4, 5))
if len(pk_info) == 2 and not passphrase:
raise ValueError("Not a valid clear PKCS#8 structure "
"(maybe it is encrypted?)")
# RFC5208, PKCS#8, version is v1(0)
#
# PrivateKeyInfo ::= SEQUENCE {
# version Version,
@ -197,22 +197,27 @@ def unwrap(p8_private_key, passphrase=None):
# privateKey PrivateKey,
# attributes [0] IMPLICIT Attributes OPTIONAL
# }
# Version ::= INTEGER
if pk_info[0] != 0:
raise ValueError("Not a valid PrivateKeyInfo SEQUENCE")
# PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
#
# EncryptedPrivateKeyInfo ::= SEQUENCE {
# encryptionAlgorithm EncryptionAlgorithmIdentifier,
# encryptedData EncryptedData
# RFC5915, Asymmetric Key Package, version is v2(1)
#
# OneAsymmetricKey ::= SEQUENCE {
# version Version,
# privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
# privateKey PrivateKey,
# attributes [0] Attributes OPTIONAL,
# ...,
# [[2: publicKey [1] PublicKey OPTIONAL ]],
# ...
# }
# EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
# AlgorithmIdentifier ::= SEQUENCE {
# algorithm OBJECT IDENTIFIER,
# parameters ANY DEFINED BY algorithm OPTIONAL
# }
if pk_info[0] == 0:
if len(pk_info) not in (3, 4):
raise ValueError("Not a valid PrivateKeyInfo SEQUENCE")
elif pk_info[0] == 1:
if len(pk_info) not in (3, 4, 5):
raise ValueError("Not a valid PrivateKeyInfo SEQUENCE")
else:
raise ValueError("Not a valid PrivateKeyInfo SEQUENCE")
algo = DerSequence().decode(pk_info[1], nr_elements=(1, 2))
algo_oid = DerObjectId().decode(algo[0]).value
@ -225,7 +230,9 @@ def unwrap(p8_private_key, passphrase=None):
except:
algo_params = algo[1]
# EncryptedData ::= OCTET STRING
# PrivateKey ::= OCTET STRING
private_key = DerOctetString().decode(pk_info[2]).payload
# We ignore attributes and (for v2 only) publickey
return (algo_oid, private_key, algo_params)

View file

@ -51,12 +51,12 @@ class IntegerBase(ABC):
pass
@abc.abstractmethod
def to_bytes(self, block_size=0):
def to_bytes(self, block_size=0, byteorder='big'):
pass
@staticmethod
@abc.abstractmethod
def from_bytes(byte_string):
def from_bytes(byte_string, byteorder='big'):
pass
# Relations

View file

@ -7,9 +7,9 @@ class IntegerBase:
def __int__(self) -> int: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def to_bytes(self, block_size: Optional[int]=0) -> bytes: ...
def to_bytes(self, block_size: Optional[int]=0, byteorder: str= ...) -> bytes: ...
@staticmethod
def from_bytes(byte_string: bytes) -> IntegerBase: ...
def from_bytes(byte_string: bytes, byteorder: Optional[str] = ...) -> IntegerBase: ...
def __eq__(self, term: object) -> bool: ...
def __ne__(self, term: object) -> bool: ...
def __lt__(self, term: Union[IntegerBase, int]) -> bool: ...

View file

@ -57,7 +57,14 @@ implementation = {"library": "custom", "api": backend}
class IntegerCustom(IntegerNative):
@staticmethod
def from_bytes(byte_string):
def from_bytes(byte_string, byteorder='big'):
if byteorder == 'big':
pass
elif byteorder == 'little':
byte_string = bytearray(byte_string)
byte_string.reverse()
else:
raise ValueError("Incorrect byteorder")
return IntegerCustom(bytes_to_long(byte_string))
def inplace_pow(self, exponent, modulus=None):

View file

@ -35,7 +35,7 @@ from Crypto.Util.py3compat import tobytes, is_native_int
from Crypto.Util._raw_api import (backend, load_lib,
get_raw_buffer, get_c_string,
null_pointer, create_string_buffer,
c_ulong, c_size_t)
c_ulong, c_size_t, c_uint8_ptr)
from ._IntegerBase import IntegerBase
@ -225,7 +225,7 @@ class IntegerGMP(IntegerBase):
def __index__(self):
return int(self)
def to_bytes(self, block_size=0):
def to_bytes(self, block_size=0, byteorder='big'):
"""Convert the number into a byte string.
This method encodes the number in network order and prepends
@ -236,6 +236,8 @@ class IntegerGMP(IntegerBase):
block_size : integer
The exact size the output byte string must have.
If zero, the string has the minimal length.
byteorder : string
'big' for big-endian integers (default), 'little' for litte-endian.
:Returns:
A byte string.
:Raise ValueError:
@ -252,6 +254,7 @@ class IntegerGMP(IntegerBase):
" of prescribed length")
buf = create_string_buffer(buf_len)
_gmp.mpz_export(
buf,
null_pointer, # Ignore countp
@ -261,20 +264,39 @@ class IntegerGMP(IntegerBase):
c_size_t(0), # No nails
self._mpz_p)
return b'\x00' * max(0, block_size - buf_len) + get_raw_buffer(buf)
result = b'\x00' * max(0, block_size - buf_len) + get_raw_buffer(buf)
if byteorder == 'big':
pass
elif byteorder == 'little':
result = bytearray(result)
result.reverse()
result = bytes(result)
else:
raise ValueError("Incorrect byteorder")
return result
@staticmethod
def from_bytes(byte_string):
def from_bytes(byte_string, byteorder='big'):
"""Convert a byte string into a number.
:Parameters:
byte_string : byte string
The input number, encoded in network order.
It can only be non-negative.
byteorder : string
'big' for big-endian integers (default), 'little' for litte-endian.
:Return:
The ``Integer`` object carrying the same value as the input.
"""
result = IntegerGMP(0)
if byteorder == 'big':
pass
elif byteorder == 'little':
byte_string = bytearray(byte_string)
byte_string.reverse()
else:
raise ValueError("Incorrect byteorder")
_gmp.mpz_import(
result._mpz_p,
c_size_t(len(byte_string)), # Amount of words to read
@ -282,7 +304,7 @@ class IntegerGMP(IntegerBase):
c_size_t(1), # Each word is 1 byte long
0, # Endianess within a word - not relevant
c_size_t(0), # No nails
byte_string)
c_uint8_ptr(byte_string))
return result
# Relations

View file

@ -62,16 +62,31 @@ class IntegerNative(IntegerBase):
def __index__(self):
return int(self._value)
def to_bytes(self, block_size=0):
def to_bytes(self, block_size=0, byteorder='big'):
if self._value < 0:
raise ValueError("Conversion only valid for non-negative numbers")
result = long_to_bytes(self._value, block_size)
if len(result) > block_size > 0:
raise ValueError("Value too large to encode")
if byteorder == 'big':
pass
elif byteorder == 'little':
result = bytearray(result)
result.reverse()
result = bytes(result)
else:
raise ValueError("Incorrect byteorder")
return result
@classmethod
def from_bytes(cls, byte_string):
def from_bytes(cls, byte_string, byteorder='big'):
if byteorder == 'big':
pass
elif byteorder == 'little':
byte_string = bytearray(byte_string)
byte_string.reverse()
else:
raise ValueError("Incorrect byteorder")
return cls(bytes_to_long(byte_string))
# Relations

View file

@ -42,13 +42,15 @@ from Crypto.Math.Numbers import Integer
from Crypto.Util.asn1 import (DerObjectId, DerOctetString, DerSequence,
DerBitString)
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
SmartPointer, c_size_t, c_uint8_ptr,
c_ulonglong, null_pointer)
from Crypto.PublicKey import (_expand_subject_public_key_info,
_create_subject_public_key_info,
_extract_subject_public_key_info)
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
SmartPointer, c_size_t, c_uint8_ptr,
c_ulonglong)
from Crypto.Hash import SHA512
from Crypto.Random import get_random_bytes
from Crypto.Random.random import getrandbits
@ -69,7 +71,7 @@ int ec_ws_new_point(EcPoint **pecp,
const uint8_t *y,
size_t len,
const EcContext *ec_ctx);
void ec_free_point(EcPoint *ecp);
void ec_ws_free_point(EcPoint *ecp);
int ec_ws_get_xy(uint8_t *x,
uint8_t *y,
size_t len,
@ -81,14 +83,51 @@ int ec_ws_scalar(EcPoint *ecp,
size_t len,
uint64_t seed);
int ec_ws_clone(EcPoint **pecp2, const EcPoint *ecp);
int ec_ws_copy(EcPoint *ecp1, const EcPoint *ecp2);
int ec_ws_cmp(const EcPoint *ecp1, const EcPoint *ecp2);
int ec_ws_neg(EcPoint *p);
int ec_ws_normalize(EcPoint *ecp);
int ec_ws_is_pai(EcPoint *ecp);
""")
_Curve = namedtuple("_Curve", "p b order Gx Gy G modulus_bits oid context desc openssh")
_ed25519_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._ed25519", """
typedef void Point;
int ed25519_new_point(Point **out,
uint8_t x[32], uint8_t y[32],
size_t modsize, void *context);
int ed25519_clone(Point **P, const Point *Q);
void ed25519_free_point(Point *p);
int ed25519_cmp(const Point *p1, const Point *p2);
int ed25519_neg(Point *p);
int ed25519_get_xy(uint8_t *xb, uint8_t *yb, size_t modsize, Point *p);
int ed25519_double(Point *p);
int ed25519_add(Point *P1, const Point *P2);
int ed25519_scalar(Point *P, uint8_t *scalar, size_t scalar_len, uint64_t seed);
""")
def lib_func(ecc_obj, func_name):
if ecc_obj._curve.desc == "Ed25519":
result = getattr(_ed25519_lib, "ed25519_" + func_name)
else:
result = getattr(_ec_lib, "ec_ws_" + func_name)
return result
#
# _curves is a database of curve parameters. Items are indexed by their
# human-friendly name, suchas "P-256". Each item has the following fields:
# - p: the prime number that defines the finite field for all modulo operations
# - b: the constant in the Short Weierstrass curve equation
# - order: the number of elements in the group with the generator below
# - Gx the affine coordinate X of the generator point
# - Gy the affine coordinate Y of the generator point
# - G the generator, as an EccPoint object
# - modulus_bits the minimum number of bits for encoding the modulus p
# - oid an ASCII string with the registered ASN.1 Object ID
# - context a raw pointer to memory holding a context for all curve operations (can be NULL)
# - desc an ASCII string describing the curve
# - openssh the ASCII string used in OpenSSH id files for public keys on this curve
# - name the ASCII string which is also a valid key in _curves
_Curve = namedtuple("_Curve", "p b order Gx Gy G modulus_bits oid context desc openssh name")
_curves = {}
@ -129,7 +168,8 @@ def init_p192():
"1.2.840.10045.3.1.1", # ANSI X9.62 / SEC2
context,
"NIST P-192",
"ecdsa-sha2-nistp192")
"ecdsa-sha2-nistp192",
"p192")
global p192_names
_curves.update(dict.fromkeys(p192_names, p192))
@ -175,7 +215,8 @@ def init_p224():
"1.3.132.0.33", # SEC 2
context,
"NIST P-224",
"ecdsa-sha2-nistp224")
"ecdsa-sha2-nistp224",
"p224")
global p224_names
_curves.update(dict.fromkeys(p224_names, p224))
@ -221,7 +262,8 @@ def init_p256():
"1.2.840.10045.3.1.7", # ANSI X9.62 / SEC2
context,
"NIST P-256",
"ecdsa-sha2-nistp256")
"ecdsa-sha2-nistp256",
"p256")
global p256_names
_curves.update(dict.fromkeys(p256_names, p256))
@ -267,7 +309,8 @@ def init_p384():
"1.3.132.0.34", # SEC 2
context,
"NIST P-384",
"ecdsa-sha2-nistp384")
"ecdsa-sha2-nistp384",
"p384")
global p384_names
_curves.update(dict.fromkeys(p384_names, p384))
@ -313,7 +356,8 @@ def init_p521():
"1.3.132.0.35", # SEC 2
context,
"NIST P-521",
"ecdsa-sha2-nistp521")
"ecdsa-sha2-nistp521",
"p521")
global p521_names
_curves.update(dict.fromkeys(p521_names, p521))
@ -322,6 +366,35 @@ init_p521()
del init_p521
ed25519_names = ["ed25519", "Ed25519"]
def init_ed25519():
p = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed # 2**255 - 19
order = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed
Gx = 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a
Gy = 0x6666666666666666666666666666666666666666666666666666666666666658
ed25519 = _Curve(Integer(p),
None,
Integer(order),
Integer(Gx),
Integer(Gy),
None,
255,
"1.3.101.112", # RFC8410
None,
"Ed25519", # Used throughout; do not change
"ssh-ed25519",
"ed25519")
global ed25519_names
_curves.update(dict.fromkeys(ed25519_names, ed25519))
init_ed25519()
del init_ed25519
class UnsupportedEccFeature(ValueError):
pass
@ -334,7 +407,7 @@ class EccPoint(object):
* Adding two points: ``R = S + T``
* In-place addition: ``S += T``
* Negating a point: ``R = -T``
* Comparing two points: ``if S == T: ...``
* Comparing two points: ``if S == T: ...`` or ``if S != T: ...``
* Multiplying a point by a scalar: ``R = S*k``
* In-place multiplication by a scalar: ``T *= k``
@ -344,7 +417,7 @@ class EccPoint(object):
:ivar y: The affine Y-coordinate of the ECC point
:vartype y: integer
:ivar xy: The tuple with X- and Y- coordinates
:ivar xy: The tuple with affine X- and Y- coordinates
"""
def __init__(self, x, y, curve="p256"):
@ -356,19 +429,26 @@ class EccPoint(object):
self._curve_name = curve
modulus_bytes = self.size_in_bytes()
context = self._curve.context
xb = long_to_bytes(x, modulus_bytes)
yb = long_to_bytes(y, modulus_bytes)
if len(xb) != modulus_bytes or len(yb) != modulus_bytes:
raise ValueError("Incorrect coordinate length")
new_point = lib_func(self, "new_point")
free_func = lib_func(self, "free_point")
self._point = VoidPointer()
result = _ec_lib.ec_ws_new_point(self._point.address_of(),
try:
context = self._curve.context.get()
except AttributeError:
context = null_pointer
result = new_point(self._point.address_of(),
c_uint8_ptr(xb),
c_uint8_ptr(yb),
c_size_t(modulus_bytes),
context.get())
context)
if result:
if result == 15:
raise ValueError("The EC point does not belong to the curve")
@ -376,26 +456,34 @@ class EccPoint(object):
# Ensure that object disposal of this Python object will (eventually)
# free the memory allocated by the raw library for the EC point
self._point = SmartPointer(self._point.get(),
_ec_lib.ec_free_point)
self._point = SmartPointer(self._point.get(), free_func)
def set(self, point):
clone = lib_func(self, "clone")
free_func = lib_func(self, "free_point")
self._point = VoidPointer()
result = _ec_lib.ec_ws_clone(self._point.address_of(),
result = clone(self._point.address_of(),
point._point.get())
if result:
raise ValueError("Error %d while cloning an EC point" % result)
self._point = SmartPointer(self._point.get(),
_ec_lib.ec_free_point)
self._point = SmartPointer(self._point.get(), free_func)
return self
def __eq__(self, point):
return 0 == _ec_lib.ec_ws_cmp(self._point.get(), point._point.get())
cmp_func = lib_func(self, "cmp")
return 0 == cmp_func(self._point.get(), point._point.get())
# Only needed for Python 2
def __ne__(self, point):
return not self == point
def __neg__(self):
neg_func = lib_func(self, "neg")
np = self.copy()
result = _ec_lib.ec_ws_neg(np._point.get())
result = neg_func(np._point.get())
if result:
raise ValueError("Error %d while inverting an EC point" % result)
return np
@ -408,10 +496,16 @@ class EccPoint(object):
def is_point_at_infinity(self):
"""``True`` if this is the point-at-infinity."""
if self._curve.desc == "Ed25519":
return self.x == 0
else:
return self.xy == (0, 0)
def point_at_infinity(self):
"""Return the point-at-infinity for the curve this point is on."""
if self._curve.desc == "Ed25519":
return EccPoint(0, 1, self._curve_name)
else:
return EccPoint(0, 0, self._curve_name)
@property
@ -427,7 +521,8 @@ class EccPoint(object):
modulus_bytes = self.size_in_bytes()
xb = bytearray(modulus_bytes)
yb = bytearray(modulus_bytes)
result = _ec_lib.ec_ws_get_xy(c_uint8_ptr(xb),
get_xy = lib_func(self, "get_xy")
result = get_xy(c_uint8_ptr(xb),
c_uint8_ptr(yb),
c_size_t(modulus_bytes),
self._point.get())
@ -451,7 +546,8 @@ class EccPoint(object):
:class:`EccPoint` : this same object (to enable chaining)
"""
result = _ec_lib.ec_ws_double(self._point.get())
double_func = lib_func(self, "double")
result = double_func(self._point.get())
if result:
raise ValueError("Error %d while doubling an EC point" % result)
return self
@ -459,7 +555,8 @@ class EccPoint(object):
def __iadd__(self, point):
"""Add a second point to this one"""
result = _ec_lib.ec_ws_add(self._point.get(), point._point.get())
add_func = lib_func(self, "add")
result = add_func(self._point.get(), point._point.get())
if result:
if result == 16:
raise ValueError("EC points are not on the same curve")
@ -476,10 +573,11 @@ class EccPoint(object):
def __imul__(self, scalar):
"""Multiply this point by a scalar"""
scalar_func = lib_func(self, "scalar")
if scalar < 0:
raise ValueError("Scalar multiplication is only defined for non-negative integers")
sb = long_to_bytes(scalar)
result = _ec_lib.ec_ws_scalar(self._point.get(),
result = scalar_func(self._point.get(),
c_uint8_ptr(sb),
c_size_t(len(sb)),
c_ulonglong(getrandbits(64)))
@ -524,6 +622,11 @@ p521 = _curves['p521']._replace(G=p521_G)
_curves.update(dict.fromkeys(p521_names, p521))
del p521_G, p521, p521_names
ed25519_G = EccPoint(_curves['Ed25519'].Gx, _curves['Ed25519'].Gy, "Ed25519")
ed25519 = _curves['Ed25519']._replace(G=ed25519_G)
_curves.update(dict.fromkeys(ed25519_names, ed25519))
del ed25519_G, ed25519, ed25519_names
class EccKey(object):
r"""Class defining an ECC key.
@ -533,11 +636,16 @@ class EccKey(object):
:ivar curve: The name of the ECC as defined in :numref:`curve_names`.
:vartype curve: string
:ivar pointQ: an ECC point representating the public component
:vartype pointQ: :class:`EccPoint`
:ivar point: an ECC point representating the public component
:vartype point: :class:`EccPoint`
:ivar d: A scalar representating the private component
:ivar d: A scalar representating the private component.
Only for NIST P curves.
:vartype d: integer
:ivar seed: A 32-byte seed representating the private component.
Only for Ed25519 curves.
:vartype seed: bytes
"""
def __init__(self, **kwargs):
@ -547,32 +655,64 @@ class EccKey(object):
curve : string
It must be *"p256"*, *"P-256"*, *"prime256v1"* or *"secp256r1"*.
d : integer
Only for a private key. It must be in the range ``[1..order-1]``.
Mandatory for a private key one NIST P curves.
It must be in the range ``[1..order-1]``.
seed : bytes
Mandatory for a private key on the Ed25519 curve.
It must be 32 bytes long.
point : EccPoint
Mandatory for a public key. If provided for a private key,
the implementation will NOT check whether it matches ``d``.
Only one parameter among ``d``, ``seed`` or ``point`` may be used.
"""
kwargs_ = dict(kwargs)
curve_name = kwargs_.pop("curve", None)
self._d = kwargs_.pop("d", None)
self._seed = kwargs_.pop("seed", None)
self._point = kwargs_.pop("point", None)
if curve_name is None and self._point:
curve_name = self._point._curve_name
if kwargs_:
raise TypeError("Unknown parameters: " + str(kwargs_))
if curve_name not in _curves:
raise ValueError("Unsupported curve (%s)", curve_name)
raise ValueError("Unsupported curve (%s)" % curve_name)
self._curve = _curves[curve_name]
self.curve = curve_name
if self._d is None:
count = int(self._d is not None) + int(self._seed is not None)
if count == 0:
if self._point is None:
raise ValueError("Either private or public ECC component must be specified, not both")
else:
raise ValueError("At lest one between parameters 'point', 'd' or 'seed' must be specified")
return
if count == 2:
raise ValueError("Parameters d and seed are mutually exclusive")
# NIST P curves work with d, Ed25519 works with seed
if self._curve.desc != "Ed25519":
if self._seed is not None:
raise ValueError("Parameter 'seed' can only be used with Ed25519")
self._d = Integer(self._d)
if not 1 <= self._d < self._curve.order:
raise ValueError("Invalid ECC private component")
self.curve = self._curve.desc
raise ValueError("Parameter d must be an integer smaller than the curve order")
else:
if self._d is not None:
raise ValueError("Parameter d can only be used with NIST P curves")
# RFC 8032, 5.1.5
if len(self._seed) != 32:
raise ValueError("Parameter seed must be 32 bytes long")
seed_hash = SHA512.new(self._seed).digest() # h
tmp = bytearray(seed_hash[:32])
tmp[0] &= 0xF8
tmp[31] = (tmp[31] & 0x7F) | 0x40
tmp.reverse()
self._d = Integer.from_bytes(tmp)
self._prefix = seed_hash[32:]
def __eq__(self, other):
if other.has_private() != self.has_private():
@ -582,6 +722,9 @@ class EccKey(object):
def __repr__(self):
if self.has_private():
if self._curve == "Ed25519":
extra = ", seed=%s" % self._seed.hex()
else:
extra = ", d=%d" % int(self._d)
else:
extra = ""
@ -593,6 +736,7 @@ class EccKey(object):
return self._d is not None
# ECDSA
def _sign(self, z, k):
assert 0 < k < self._curve.order
@ -607,6 +751,7 @@ class EccKey(object):
s = inv_blind_k * (blind * z + blind_d * r) % order
return (r, s)
# ECDSA
def _verify(self, z, rs):
order = self._curve.order
sinv = rs[1].inverse(order)
@ -620,6 +765,12 @@ class EccKey(object):
raise ValueError("This is not a private ECC key")
return self._d
@property
def seed(self):
if not self.has_private():
raise ValueError("This is not a private ECC key")
return self._seed
@property
def pointQ(self):
if self._point is None:
@ -636,6 +787,9 @@ class EccKey(object):
return EccKey(curve=self._curve.desc, point=self.pointQ)
def _export_SEC1(self, compress):
if self._curve.name == "ed25519":
raise ValueError("SEC1 format is unsupported for EdDSA curves")
# See 2.2 in RFC5480 and 2.3.3 in SEC1
#
# The first byte is:
@ -660,15 +814,28 @@ class EccKey(object):
self.pointQ.y.to_bytes(modulus_bytes))
return public_key
def _export_ed25519(self):
x, y = self.pointQ.xy
result = bytearray(y.to_bytes(32))
result.reverse() # Little-endian
result[31] = ((x & 1) << 7) | result[31]
return bytes(result)
def _export_subjectPublicKeyInfo(self, compress):
if self._curve.name == "ed25519":
oid = "1.3.101.112"
public_key = self._export_ed25519()
params = None
else:
oid = "1.2.840.10045.2.1" # unrestricted
public_key = self._export_SEC1(compress)
unrestricted_oid = "1.2.840.10045.2.1"
return _create_subject_public_key_info(unrestricted_oid,
public_key,
DerObjectId(self._curve.oid))
params = DerObjectId(self._curve.oid)
def _export_private_der(self, include_ec_params=True):
return _create_subject_public_key_info(oid,
public_key,
params)
def _export_rfc5915_private_der(self, include_ec_params=True):
assert self.has_private()
@ -701,11 +868,18 @@ class EccKey(object):
if kwargs.get('passphrase', None) is not None and 'protection' not in kwargs:
raise ValueError("At least the 'protection' parameter should be present")
unrestricted_oid = "1.2.840.10045.2.1"
private_key = self._export_private_der(include_ec_params=False)
if self._curve.name == "ed25519":
oid = "1.3.101.112" # id-Ed25519
private_key = DerOctetString(self._seed).encode()
params = None
else:
oid = "1.2.840.10045.2.1" # unrestricted
private_key = self._export_rfc5915_private_der(include_ec_params=False)
params = DerObjectId(self._curve.oid)
result = PKCS8.wrap(private_key,
unrestricted_oid,
key_params=DerObjectId(self._curve.oid),
oid,
key_params=params,
**kwargs)
return result
@ -718,7 +892,7 @@ class EccKey(object):
def _export_private_pem(self, passphrase, **kwargs):
from Crypto.IO import PEM
encoded_der = self._export_private_der()
encoded_der = self._export_rfc5915_private_der()
return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs)
def _export_private_clear_pkcs8_in_clear_pem(self):
@ -741,6 +915,11 @@ class EccKey(object):
raise ValueError("Cannot export OpenSSH private keys")
desc = self._curve.openssh
if desc == "ssh-ed25519":
public_key = self._export_ed25519()
comps = (tobytes(desc), tobytes(public_key))
else:
modulus_bytes = self.pointQ.size_in_bytes()
if compress:
@ -754,6 +933,7 @@ class EccKey(object):
middle = desc.split("-")[2]
comps = (tobytes(desc), tobytes(middle), public_key)
blob = b"".join([struct.pack(">I", len(x)) + x for x in comps])
return desc + " " + tostr(binascii.b2a_base64(blob))
@ -784,9 +964,7 @@ class EccKey(object):
Only relevant for private keys.
If ``True`` (default and recommended), the `PKCS#8`_ representation
will be used.
If ``False``, the much weaker `PEM encryption`_ mechanism will be used.
will be used. Ignored for EdDSA curves, for which PKCS#8 is always used.
protection (string):
When a private key is exported with password-protection
@ -800,6 +978,9 @@ class EccKey(object):
If ``False`` (default), the method returns the full public key.
This parameter is ignored for EdDSA curves, as compression is
mandatory.
.. warning::
If you don't provide a passphrase, the private key will be
exported in the clear!
@ -811,10 +992,8 @@ class EccKey(object):
.. _PEM: http://www.ietf.org/rfc/rfc1421.txt
.. _`PEM encryption`: http://www.ietf.org/rfc/rfc1423.txt
.. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
.. _OpenSSH: http://www.openssh.com/txt/rfc5656.txt
.. _RFC5480: https://tools.ietf.org/html/rfc5480
.. _RFC5915: http://www.ietf.org/rfc/rfc5915.txt
.. _SEC1: https://www.secg.org/sec1-v2.pdf
Returns:
@ -836,6 +1015,10 @@ class EccKey(object):
if not passphrase:
raise ValueError("Empty passphrase")
use_pkcs8 = args.pop("use_pkcs8", True)
if not use_pkcs8 and self._curve.name == "ed25519":
raise ValueError("'pkcs8' must be True for EdDSA curves")
if ext_format == "PEM":
if use_pkcs8:
if passphrase:
@ -851,7 +1034,7 @@ class EccKey(object):
if use_pkcs8:
return self._export_pkcs8(passphrase=passphrase, **args)
else:
return self._export_private_der()
return self._export_rfc5915_private_der()
else:
raise ValueError("Private keys cannot be exported "
"in the '%s' format" % ext_format)
@ -887,30 +1070,43 @@ def generate(**kwargs):
if kwargs:
raise TypeError("Unknown parameters: " + str(kwargs))
if _curves[curve_name].desc == "Ed25519":
seed = randfunc(32)
new_key = EccKey(curve=curve_name, seed=seed)
else:
d = Integer.random_range(min_inclusive=1,
max_exclusive=curve.order,
randfunc=randfunc)
new_key = EccKey(curve=curve_name, d=d)
return EccKey(curve=curve_name, d=d)
return new_key
def construct(**kwargs):
"""Build a new ECC key (private or public) starting
from some base components.
Args:
In most cases, you will already have an existing key
which you can read in with :func:`import_key` instead
of this function.
Args:
curve (string):
Mandatory. It must be a curve name defined in :numref:`curve_names`.
Mandatory. The name of the elliptic curve, from the list :numref:`curve_names`.
d (integer):
Only for a private key. It must be in the range ``[1..order-1]``.
Mandatory for a private key on a NIST P-curve (e.g., P-256):
the integer in the range ``[1..order-1]`` that represents the key.
seed (bytes):
Mandatory for a private key on curve Ed25519: the 32 bytes that
represent the private key.
point_x (integer):
Mandatory for a public key. X coordinate (affine) of the ECC point.
Mandatory for a public key: the X coordinate (affine) of the ECC point.
point_y (integer):
Mandatory for a public key. Y coordinate (affine) of the ECC point.
Mandatory for a public key: the Y coordinate (affine) of the ECC point.
Returns:
:class:`EccKey` : a new ECC key object
@ -928,14 +1124,16 @@ def construct(**kwargs):
# ValueError is raised if the point is not on the curve
kwargs["point"] = EccPoint(point_x, point_y, curve_name)
new_key = EccKey(**kwargs)
# Validate that the private key matches the public one
d = kwargs.get("d", None)
if d is not None and "point" in kwargs:
pub_key = curve.G * d
# because EccKey will not do that automatically
if new_key.has_private() and 'point' in kwargs:
pub_key = curve.G * new_key.d
if pub_key.xy != (point_x, point_y):
raise ValueError("Private and public ECC keys do not match")
return EccKey(**kwargs)
return new_key
def _import_public_der(ec_point, curve_oid=None, curve_name=None):
@ -1002,38 +1200,49 @@ def _import_subjectPublicKeyInfo(encoded, *kwargs):
# Parse the generic subjectPublicKeyInfo structure
oid, ec_point, params = _expand_subject_public_key_info(encoded)
# ec_point must be an encoded OCTET STRING
# params is encoded ECParameters
nist_p_oids = (
"1.2.840.10045.2.1", # id-ecPublicKey (unrestricted)
"1.3.132.1.12", # id-ecDH
"1.3.132.1.13" # id-ecMQV
)
eddsa_oids = (
"1.3.101.112" # id-Ed25519
)
# We accept id-ecPublicKey, id-ecDH, id-ecMQV without making any
# distiction for now.
# Restrictions can be captured in the key usage certificate
# extension
unrestricted_oid = "1.2.840.10045.2.1"
ecdh_oid = "1.3.132.1.12"
ecmqv_oid = "1.3.132.1.13"
if oid not in (unrestricted_oid, ecdh_oid, ecmqv_oid):
raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % oid)
# Parameters are mandatory for all three types
if not params:
raise ValueError("Missing ECC parameters")
if oid in nist_p_oids:
# See RFC5480
# Parameters are mandatory and encoded as ECParameters
# ECParameters ::= CHOICE {
# namedCurve OBJECT IDENTIFIER
# -- implicitCurve NULL
# -- specifiedCurve SpecifiedECDomain
# }
#
# implicitCurve and specifiedCurve are not supported (as per RFC)
if not params:
raise ValueError("Missing ECC parameters for ECC OID %s" % oid)
try:
curve_oid = DerObjectId().decode(params).value
except ValueError:
raise ValueError("Error decoding namedCurve")
# ECPoint ::= OCTET STRING
return _import_public_der(ec_point, curve_oid=curve_oid)
elif oid in eddsa_oids:
# See RFC8410
def _import_private_der(encoded, passphrase, curve_oid=None):
# Parameters must be absent
if params:
raise ValueError("Unexpected ECC parameters for ECC OID %s" % oid)
x, y = _import_ed25519_public_key(ec_point)
return construct(point_x=x, point_y=y, curve='Ed25519')
else:
raise UnsupportedEccFeature("Unsupported ECC OID: %s" % oid)
def _import_rfc5915_der(encoded, passphrase, curve_oid=None):
# See RFC5915 https://tools.ietf.org/html/rfc5915
#
@ -1086,27 +1295,28 @@ def _import_private_der(encoded, passphrase, curve_oid=None):
def _import_pkcs8(encoded, passphrase):
from Crypto.IO import PKCS8
# From RFC5915, Section 1:
#
# Distributing an EC private key with PKCS#8 [RFC5208] involves including:
# a) id-ecPublicKey, id-ecDH, or id-ecMQV (from [RFC5480]) with the
# namedCurve as the parameters in the privateKeyAlgorithm field; and
# b) ECPrivateKey in the PrivateKey field, which is an OCTET STRING.
algo_oid, private_key, params = PKCS8.unwrap(encoded, passphrase)
# We accept id-ecPublicKey, id-ecDH, id-ecMQV without making any
# distiction for now.
unrestricted_oid = "1.2.840.10045.2.1"
ecdh_oid = "1.3.132.1.12"
ecmqv_oid = "1.3.132.1.13"
if algo_oid not in (unrestricted_oid, ecdh_oid, ecmqv_oid):
raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % algo_oid)
nist_p_oids = (
"1.2.840.10045.2.1", # id-ecPublicKey (unrestricted)
"1.3.132.1.12", # id-ecDH
"1.3.132.1.13" # id-ecMQV
)
eddsa_oids = (
"1.3.101.112" # id-Ed25519
)
if algo_oid in nist_p_oids:
curve_oid = DerObjectId().decode(params).value
return _import_private_der(private_key, passphrase, curve_oid)
return _import_rfc5915_der(private_key, passphrase, curve_oid)
elif algo_oid in eddsa_oids:
if params is not None:
raise ValueError("EdDSA ECC private key must not have parameters")
curve_oid = None
seed = DerOctetString().decode(private_key).payload
return construct(curve="Ed25519", seed=seed)
else:
raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % algo_oid)
def _import_x509_cert(encoded, *kwargs):
@ -1132,7 +1342,7 @@ def _import_der(encoded, passphrase):
pass
try:
return _import_private_der(encoded, passphrase)
return _import_rfc5915_der(encoded, passphrase)
except UnsupportedEccFeature as err:
raise err
except (ValueError, TypeError, IndexError):
@ -1149,7 +1359,12 @@ def _import_der(encoded, passphrase):
def _import_openssh_public(encoded):
keystring = binascii.a2b_base64(encoded.split(b' ')[1])
parts = encoded.split(b' ')
if len(parts) not in (2, 3):
raise ValueError("Not an openssh public key")
try:
keystring = binascii.a2b_base64(parts[1])
keyparts = []
while len(keystring) > 4:
@ -1157,14 +1372,32 @@ def _import_openssh_public(encoded):
keyparts.append(keystring[4:4 + lk])
keystring = keystring[4 + lk:]
if parts[0] != keyparts[0]:
raise ValueError("Mismatch in openssh public key")
# NIST P curves
if parts[0].startswith(b"ecdsa-sha2-"):
for curve_name, curve in _curves.items():
middle = tobytes(curve.openssh.split("-")[2])
if keyparts[1] == middle:
break
else:
raise ValueError("Unsupported ECC curve")
raise ValueError("Unsupported ECC curve: " + middle)
return _import_public_der(keyparts[2], curve_oid=curve.oid)
ecc_key = _import_public_der(keyparts[2], curve_oid=curve.oid)
# EdDSA
elif parts[0] == b"ssh-ed25519":
x, y = _import_ed25519_public_key(keyparts[1])
ecc_key = construct(curve="Ed25519", point_x=x, point_y=y)
else:
raise ValueError("Unsupported SSH key type: " + parts[0])
except (IndexError, TypeError, binascii.Error):
raise ValueError("Error parsing SSH key type: " + parts[0])
return ecc_key
def _import_openssh_private_ecc(data, password):
@ -1172,12 +1405,15 @@ def _import_openssh_private_ecc(data, password):
from ._openssh import (import_openssh_private_generic,
read_bytes, read_string, check_padding)
ssh_name, decrypted = import_openssh_private_generic(data, password)
key_type, decrypted = import_openssh_private_generic(data, password)
name, decrypted = read_string(decrypted)
if name not in _curves:
raise UnsupportedEccFeature("Unsupported ECC curve %s" % name)
curve = _curves[name]
# https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-04
if key_type.startswith("ecdsa-sha2"):
ecdsa_curve_name, decrypted = read_string(decrypted)
if ecdsa_curve_name not in _curves:
raise UnsupportedEccFeature("Unsupported ECC curve %s" % ecdsa_curve_name)
curve = _curves[ecdsa_curve_name]
modulus_bytes = (curve.modulus_bits + 7) // 8
public_key, decrypted = read_bytes(decrypted)
@ -1189,15 +1425,75 @@ def _import_openssh_private_ecc(data, password):
point_x = Integer.from_bytes(public_key[1:1+modulus_bytes])
point_y = Integer.from_bytes(public_key[1+modulus_bytes:])
point = EccPoint(point_x, point_y, curve=name)
point = EccPoint(point_x, point_y, curve=ecdsa_curve_name)
private_key, decrypted = read_bytes(decrypted)
d = Integer.from_bytes(private_key)
params = {'d': d, 'curve': ecdsa_curve_name}
elif key_type == "ssh-ed25519":
public_key, decrypted = read_bytes(decrypted)
point_x, point_y = _import_ed25519_public_key(public_key)
private_public_key, decrypted = read_bytes(decrypted)
seed = private_public_key[:32]
params = {'seed': seed, 'curve': 'Ed25519'}
else:
raise ValueError("Unsupport SSH agent key type:" + key_type)
_, padded = read_string(decrypted) # Comment
check_padding(padded)
return EccKey(curve=name, d=d, point=point)
return construct(point_x=point_x, point_y=point_y, **params)
def _import_ed25519_public_key(encoded):
"""Import an EdDSA ECC public key, encoded as raw bytes as described
in RFC8032_.
Args:
encoded (bytes):
The EdDSA public key to import. It must be 32 bytes long for Ed25519.
Returns:
:class:`EccKey` : a new ECC key object
Raises:
ValueError: when the given key cannot be parsed.
.. _RFC8032: https://datatracker.ietf.org/doc/html/rfc8032
"""
if len(encoded) != 32:
raise ValueError("Incorrect length. Only Ed25519 public keys are supported.")
p = Integer(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed) # 2**255 - 19
d = 37095705934669439343138083508754565189542113879843219016388785533085940283555
y = bytearray(encoded)
x_lsb = y[31] >> 7
y[31] &= 0x7F
y.reverse()
point_y = Integer.from_bytes(y)
if point_y >= p:
raise ValueError("Invalid Ed25519 key (y)")
if point_y == 1:
return 0, 1
u = (point_y**2 - 1) % p
v = ((point_y**2 % p) * d + 1) % p
v_inv = v.inverse(p)
x2 = (u * v_inv) % p
try:
point_x = Integer._tonelli_shanks(x2, p)
if (point_x & 1) != x_lsb:
point_x = p - point_x
return point_x, point_y
except ValueError:
raise ValueError("Invalid Ed25519 public key")
def import_key(encoded, passphrase=None, curve_name=None):
@ -1206,19 +1502,22 @@ def import_key(encoded, passphrase=None, curve_name=None):
Args:
encoded (bytes or multi-line string):
The ECC key to import.
The function will try to automatically detect the right format.
An ECC **public** key can be:
Supported formats for an ECC **public** key:
- An X.509 certificate, binary (DER) or ASCII (PEM)
- An X.509 ``subjectPublicKeyInfo``, binary (DER) or ASCII (PEM)
- A SEC1_ (or X9.62) byte string. You must also provide the
``curve_name``.
- An OpenSSH line (e.g. the content of ``~/.ssh/id_ecdsa``, ASCII)
- X.509 certificate: binary (DER) or ASCII (PEM).
- X.509 ``subjectPublicKeyInfo``: binary (DER) or ASCII (PEM).
- SEC1_ (or X9.62), as ``bytes``. NIST P curves only. You must also provide the ``curve_name``.
- OpenSSH line, defined in RFC5656_ and RFC8709_ (ASCII).
This is normally the content of files like ``~/.ssh/id_ecdsa.pub``.
An ECC **private** key can be:
Supported formats for an ECC **private** key:
- In binary format (DER, see section 3 of `RFC5915`_ or `PKCS#8`_)
- In ASCII format (PEM or `OpenSSH 6.5+`_)
- A binary ``ECPrivateKey`` structure, as defined in `RFC5915`_ (DER).
NIST P curves only.
- A `PKCS#8`_ structure (or the more recent Asymmetric Key Package, RFC5958_): binary (DER) or ASCII (PEM).
- `OpenSSH 6.5`_ and newer versions (ASCII).
Private keys can be in the clear or password-protected.
@ -1226,13 +1525,18 @@ def import_key(encoded, passphrase=None, curve_name=None):
passphrase (byte string):
The passphrase to use for decrypting a private key.
Encryption may be applied protected at the PEM level or at the PKCS#8 level.
Encryption may be applied protected at the PEM level (not recommended)
or at the PKCS#8 level (recommended).
This parameter is ignored if the key in input is not encrypted.
curve_name (string):
For a SEC1 byte string only. This is the name of the ECC curve,
For a SEC1 encoding only. This is the name of the ECC curve,
as defined in :numref:`curve_names`.
To import EdDSA private and public keys, when encoded as raw ``bytes``,
use :func:`Crypto.Signature.eddsa.import_public_key`
and :func:`Crypto.Signature.eddsa.import_private_key`.
Returns:
:class:`EccKey` : a new ECC key object
@ -1240,11 +1544,14 @@ def import_key(encoded, passphrase=None, curve_name=None):
ValueError: when the given key cannot be parsed (possibly because
the pass phrase is wrong).
.. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt
.. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt
.. _RFC5915: http://www.ietf.org/rfc/rfc5915.txt
.. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
.. _`OpenSSH 6.5+`: https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf
.. _RFC1421: https://datatracker.ietf.org/doc/html/rfc1421
.. _RFC1423: https://datatracker.ietf.org/doc/html/rfc1423
.. _RFC5915: https://datatracker.ietf.org/doc/html/rfc5915
.. _RFC5656: https://datatracker.ietf.org/doc/html/rfc5656
.. _RFC8709: https://datatracker.ietf.org/doc/html/rfc8709
.. _RFC5958: https://datatracker.ietf.org/doc/html/rfc5958
.. _`PKCS#8`: https://datatracker.ietf.org/doc/html/rfc5208
.. _`OpenSSH 6.5`: https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf
.. _SEC1: https://www.secg.org/sec1-v2.pdf
"""
@ -1285,7 +1592,7 @@ def import_key(encoded, passphrase=None, curve_name=None):
return result
# OpenSSH
if encoded.startswith(b'ecdsa-sha2-'):
if encoded.startswith(b'ecdsa-sha2-') or encoded.startswith(b'ssh-ed25519'):
return _import_openssh_public(encoded)
# DER

View file

@ -62,3 +62,4 @@ def construct(**kwargs: Union[str, int]) -> EccKey: ...
def import_key(encoded: Union[bytes, str],
passphrase: Optional[str]=None,
curve_name:Optional[str]=None) -> EccKey: ...
def _import_ed25519_public_key(encoded: bytes) -> EccKey: ...

View file

@ -37,7 +37,7 @@ import struct
from Crypto import Random
from Crypto.Util.py3compat import tobytes, bord, tostr
from Crypto.Util.asn1 import DerSequence
from Crypto.Util.asn1 import DerSequence, DerNull
from Crypto.Math.Numbers import Integer
from Crypto.Math.Primality import (test_probable_prime,
@ -339,19 +339,22 @@ class RsaKey(object):
if format == 'PEM' and protection is None:
key_type = 'PRIVATE KEY'
binary_key = PKCS8.wrap(binary_key, oid, None)
binary_key = PKCS8.wrap(binary_key, oid, None,
key_params=DerNull())
else:
key_type = 'ENCRYPTED PRIVATE KEY'
if not protection:
protection = 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'
binary_key = PKCS8.wrap(binary_key, oid,
passphrase, protection)
passphrase, protection,
key_params=DerNull())
passphrase = None
else:
key_type = "PUBLIC KEY"
binary_key = _create_subject_public_key_info(oid,
DerSequence([self.n,
self.e])
self.e]),
DerNull()
)
if format == 'DER':

View file

@ -60,16 +60,15 @@ def _expand_subject_public_key_info(encoded):
return algo_oid.value, spk, algo_params
def _create_subject_public_key_info(algo_oid, secret_key, params=None):
def _create_subject_public_key_info(algo_oid, public_key, params):
if params is None:
params = DerNull()
algorithm = DerSequence([DerObjectId(algo_oid)])
else:
algorithm = DerSequence([DerObjectId(algo_oid), params])
spki = DerSequence([
DerSequence([
DerObjectId(algo_oid),
params]),
DerBitString(secret_key)
spki = DerSequence([algorithm,
DerBitString(public_key)
])
return spki.encode()

View file

@ -39,6 +39,8 @@ from binascii import unhexlify
from Crypto.Util.py3compat import *
from Crypto.IO import PKCS8
from Crypto.Util.asn1 import DerNull
oid_key = '1.2.840.113549.1.1.1'
# Original RSA key (in DER format)
@ -407,7 +409,7 @@ class PKCS8_Decrypt(unittest.TestCase):
b("TestTest"),
protection=t[0],
prot_params=params,
key_params=None,
key_params=DerNull(),
randfunc=rng)
self.assertEqual(wrapped, t[4])

View file

@ -114,27 +114,42 @@ class TestIntegerBase(unittest.TestCase):
v1 = Integer(0x17)
self.assertEqual(b("\x17"), v1.to_bytes())
v2 = Integer(0xFFFF)
self.assertEqual(b("\xFF\xFF"), v2.to_bytes())
self.assertEqual(b("\x00\xFF\xFF"), v2.to_bytes(3))
v2 = Integer(0xFFFE)
self.assertEqual(b("\xFF\xFE"), v2.to_bytes())
self.assertEqual(b("\x00\xFF\xFE"), v2.to_bytes(3))
self.assertRaises(ValueError, v2.to_bytes, 1)
self.assertEqual(b("\xFE\xFF"), v2.to_bytes(byteorder='little'))
self.assertEqual(b("\xFE\xFF\x00"), v2.to_bytes(3, byteorder='little'))
v3 = Integer(-90)
self.assertRaises(ValueError, v3.to_bytes)
self.assertRaises(ValueError, v3.to_bytes, byteorder='bittle')
def test_conversion_from_bytes(self):
Integer = self.Integer
v1 = Integer.from_bytes(b("\x00"))
v1 = Integer.from_bytes(b"\x00")
self.assertTrue(isinstance(v1, Integer))
self.assertEqual(0, v1)
v2 = Integer.from_bytes(b("\x00\x00"))
self.assertEqual(0, v2)
v2 = Integer.from_bytes(b"\x00\x01")
self.assertEqual(1, v2)
v3 = Integer.from_bytes(b("\xFF\xFF"))
v3 = Integer.from_bytes(b"\xFF\xFF")
self.assertEqual(0xFFFF, v3)
v4 = Integer.from_bytes(b"\x00\x01", 'big')
self.assertEqual(1, v4)
v5 = Integer.from_bytes(b"\x00\x01", byteorder='big')
self.assertEqual(1, v5)
v6 = Integer.from_bytes(b"\x00\x01", byteorder='little')
self.assertEqual(0x0100, v6)
self.assertRaises(ValueError, Integer.from_bytes, b'\x09', 'bittle')
def test_inequality(self):
# Test Integer!=Integer and Integer!=int
v1, v2, v3, v4 = self.Integers(89, 89, 90, -8)

View file

@ -24,31 +24,29 @@
"""Self-test for public-key crypto"""
__revision__ = "$Id$"
import unittest
from Crypto.SelfTest.PublicKey import (test_DSA, test_RSA,
test_ECC_NIST, test_ECC_25519,
test_import_DSA, test_import_RSA,
test_import_ECC, test_ElGamal)
import os
def get_tests(config={}):
tests = []
from Crypto.SelfTest.PublicKey import test_DSA; tests += test_DSA.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_RSA; tests += test_RSA.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_ECC; tests += test_ECC.get_tests(config=config)
tests += test_DSA.get_tests(config=config)
tests += test_RSA.get_tests(config=config)
tests += test_ECC_NIST.get_tests(config=config)
tests += test_ECC_25519.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_import_DSA
tests +=test_import_DSA.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_import_RSA
tests += test_import_DSA.get_tests(config=config)
tests += test_import_RSA.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_import_ECC
tests += test_import_ECC.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_ElGamal; tests += test_ElGamal.get_tests(config=config)
tests += test_ElGamal.get_tests(config=config)
return tests
if __name__ == '__main__':
import unittest
suite = lambda: unittest.TestSuite(get_tests())
unittest.main(defaultTest='suite')
# vim:set ts=4 sw=4 sts=4 expandtab:
if __name__ == '__main__':
def suite():
return unittest.TestSuite(get_tests())
unittest.main(defaultTest='suite')

View file

@ -0,0 +1,327 @@
# ===================================================================
#
# Copyright (c) 2022, Legrandin <helderijs@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ===================================================================
import unittest
from binascii import unhexlify
from Crypto.SelfTest.st_common import list_test_cases
from Crypto.SelfTest.loader import load_test_vectors
from Crypto.PublicKey import ECC
from Crypto.PublicKey.ECC import EccPoint, _curves, EccKey
from Crypto.Math.Numbers import Integer
from Crypto.Hash import SHAKE128
class TestEccPoint_Ed25519(unittest.TestCase):
Gxy = {"x": 15112221349535400772501151409588531511454012693041857206046113283949847762202,
"y": 46316835694926478169428394003475163141307993866256225615783033603165251855960}
G2xy = {"x": 24727413235106541002554574571675588834622768167397638456726423682521233608206,
"y": 15549675580280190176352668710449542251549572066445060580507079593062643049417}
G3xy = {"x": 46896733464454938657123544595386787789046198280132665686241321779790909858396,
"y": 8324843778533443976490377120369201138301417226297555316741202210403726505172}
pointG = EccPoint(Gxy['x'], Gxy['y'], curve="Ed25519")
pointG2 = EccPoint(G2xy['x'], G2xy['y'], curve="Ed25519")
pointG3 = EccPoint(G3xy['x'], G3xy['y'], curve="Ed25519")
def test_init_xy(self):
EccPoint(self.Gxy['x'], self.Gxy['y'], curve="Ed25519")
# Neutral point
pai = EccPoint(0, 1, curve="Ed25519")
self.assertEqual(pai.x, 0)
self.assertEqual(pai.y, 1)
self.assertEqual(pai.xy, (0, 1))
# G
bp = self.pointG.copy()
self.assertEqual(bp.x, 15112221349535400772501151409588531511454012693041857206046113283949847762202)
self.assertEqual(bp.y, 46316835694926478169428394003475163141307993866256225615783033603165251855960)
self.assertEqual(bp.xy, (bp.x, bp.y))
# 2G
bp2 = self.pointG2.copy()
self.assertEqual(bp2.x, 24727413235106541002554574571675588834622768167397638456726423682521233608206)
self.assertEqual(bp2.y, 15549675580280190176352668710449542251549572066445060580507079593062643049417)
self.assertEqual(bp2.xy, (bp2.x, bp2.y))
# 5G
EccPoint(x=33467004535436536005251147249499675200073690106659565782908757308821616914995,
y=43097193783671926753355113395909008640284023746042808659097434958891230611693,
curve="Ed25519")
# Catch if point is not on the curve
self.assertRaises(ValueError, EccPoint, 34, 35, curve="Ed25519")
def test_set(self):
pointW = EccPoint(0, 1, curve="Ed25519")
pointW.set(self.pointG)
self.assertEqual(pointW.x, self.pointG.x)
self.assertEqual(pointW.y, self.pointG.y)
def test_copy(self):
pointW = self.pointG.copy()
self.assertEqual(pointW.x, self.pointG.x)
self.assertEqual(pointW.y, self.pointG.y)
def test_equal(self):
pointH = self.pointG.copy()
pointI = self.pointG2.copy()
self.assertEqual(self.pointG, pointH)
self.assertNotEqual(self.pointG, pointI)
def test_pai(self):
pai = EccPoint(0, 1, curve="Ed25519")
self.failUnless(pai.is_point_at_infinity())
self.assertEqual(pai, pai.point_at_infinity())
def test_negate(self):
negG = -self.pointG
sum = self.pointG + negG
self.failUnless(sum.is_point_at_infinity())
def test_addition(self):
self.assertEqual(self.pointG + self.pointG2, self.pointG3)
self.assertEqual(self.pointG2 + self.pointG, self.pointG3)
self.assertEqual(self.pointG2 + self.pointG.point_at_infinity(), self.pointG2)
self.assertEqual(self.pointG.point_at_infinity() + self.pointG2, self.pointG2)
G5 = self.pointG2 + self.pointG3
self.assertEqual(G5.x, 33467004535436536005251147249499675200073690106659565782908757308821616914995)
self.assertEqual(G5.y, 43097193783671926753355113395909008640284023746042808659097434958891230611693)
def test_inplace_addition(self):
pointH = self.pointG.copy()
pointH += self.pointG
self.assertEqual(pointH, self.pointG2)
pointH += self.pointG
self.assertEqual(pointH, self.pointG3)
pointH += self.pointG.point_at_infinity()
self.assertEqual(pointH, self.pointG3)
def test_doubling(self):
pointH = self.pointG.copy()
pointH.double()
self.assertEqual(pointH.x, self.pointG2.x)
self.assertEqual(pointH.y, self.pointG2.y)
# 2*0
pai = self.pointG.point_at_infinity()
pointR = pai.copy()
pointR.double()
self.assertEqual(pointR, pai)
def test_scalar_multiply(self):
d = 0
pointH = d * self.pointG
self.assertEqual(pointH.x, 0)
self.assertEqual(pointH.y, 1)
d = 1
pointH = d * self.pointG
self.assertEqual(pointH.x, self.pointG.x)
self.assertEqual(pointH.y, self.pointG.y)
d = 2
pointH = d * self.pointG
self.assertEqual(pointH.x, self.pointG2.x)
self.assertEqual(pointH.y, self.pointG2.y)
d = 3
pointH = d * self.pointG
self.assertEqual(pointH.x, self.pointG3.x)
self.assertEqual(pointH.y, self.pointG3.y)
d = 4
pointH = d * self.pointG
self.assertEqual(pointH.x, 14582954232372986451776170844943001818709880559417862259286374126315108956272)
self.assertEqual(pointH.y, 32483318716863467900234833297694612235682047836132991208333042722294373421359)
d = 5
pointH = d * self.pointG
self.assertEqual(pointH.x, 33467004535436536005251147249499675200073690106659565782908757308821616914995)
self.assertEqual(pointH.y, 43097193783671926753355113395909008640284023746042808659097434958891230611693)
d = 10
pointH = d * self.pointG
self.assertEqual(pointH.x, 43500613248243327786121022071801015118933854441360174117148262713429272820047)
self.assertEqual(pointH.y, 45005105423099817237495816771148012388779685712352441364231470781391834741548)
d = 20
pointH = d * self.pointG
self.assertEqual(pointH.x, 46694936775300686710656303283485882876784402425210400817529601134760286812591)
self.assertEqual(pointH.y, 8786390172762935853260670851718824721296437982862763585171334833968259029560)
d = 255
pointH = d * self.pointG
self.assertEqual(pointH.x, 36843863416400016952258312492144504209624961884991522125275155377549541182230)
self.assertEqual(pointH.y, 22327030283879720808995671630924669697661065034121040761798775626517750047180)
d = 256
pointH = d * self.pointG
self.assertEqual(pointH.x, 42740085206947573681423002599456489563927820004573071834350074001818321593686)
self.assertEqual(pointH.y, 6935684722522267618220753829624209639984359598320562595061366101608187623111)
def test_sizes(self):
self.assertEqual(self.pointG.size_in_bits(), 255)
self.assertEqual(self.pointG.size_in_bytes(), 32)
class TestEccKey_Ed25519(unittest.TestCase):
def test_private_key(self):
seed = unhexlify("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
Px = 38815646466658113194383306759739515082307681141926459231621296960732224964046
Py = 11903303657706407974989296177215005343713679411332034699907763981919547054807
key = EccKey(curve="Ed25519", seed=seed)
self.assertEqual(key.seed, seed)
self.assertEqual(key.d, 36144925721603087658594284515452164870581325872720374094707712194495455132720)
self.assertTrue(key.has_private())
self.assertEqual(key.pointQ.x, Px)
self.assertEqual(key.pointQ.y, Py)
point = EccPoint(Px, Py, "ed25519")
key = EccKey(curve="Ed25519", seed=seed, point=point)
self.assertEqual(key.d, 36144925721603087658594284515452164870581325872720374094707712194495455132720)
self.assertTrue(key.has_private())
self.assertEqual(key.pointQ, point)
# Other names
key = EccKey(curve="ed25519", seed=seed)
# Must not accept d parameter
self.assertRaises(ValueError, EccKey, curve="ed25519", d=1)
def test_public_key(self):
point = EccPoint(_curves['ed25519'].Gx, _curves['ed25519'].Gy, curve='ed25519')
key = EccKey(curve="ed25519", point=point)
self.assertFalse(key.has_private())
self.assertEqual(key.pointQ, point)
def test_public_key_derived(self):
priv_key = EccKey(curve="ed25519", seed=b'H'*32)
pub_key = priv_key.public_key()
self.assertFalse(pub_key.has_private())
self.assertEqual(priv_key.pointQ, pub_key.pointQ)
def test_invalid_seed(self):
self.assertRaises(ValueError, lambda: EccKey(curve="ed25519", seed=b'H' * 31))
def test_equality(self):
private_key = ECC.construct(seed=b'H'*32, curve="Ed25519")
private_key2 = ECC.construct(seed=b'H'*32, curve="ed25519")
private_key3 = ECC.construct(seed=b'C'*32, curve="Ed25519")
public_key = private_key.public_key()
public_key2 = private_key2.public_key()
public_key3 = private_key3.public_key()
self.assertEqual(private_key, private_key2)
self.assertNotEqual(private_key, private_key3)
self.assertEqual(public_key, public_key2)
self.assertNotEqual(public_key, public_key3)
self.assertNotEqual(public_key, private_key)
class TestEccModule_Ed25519(unittest.TestCase):
def test_generate(self):
key = ECC.generate(curve="Ed25519")
self.assertTrue(key.has_private())
point = EccPoint(_curves['Ed25519'].Gx, _curves['Ed25519'].Gy, curve="Ed25519") * key.d
self.assertEqual(key.pointQ, point)
# Always random
key2 = ECC.generate(curve="Ed25519")
self.assertNotEqual(key, key2)
# Other names
ECC.generate(curve="Ed25519")
# Random source
key1 = ECC.generate(curve="Ed25519", randfunc=SHAKE128.new().read)
key2 = ECC.generate(curve="Ed25519", randfunc=SHAKE128.new().read)
self.assertEqual(key1, key2)
def test_construct(self):
seed = unhexlify("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
Px = 38815646466658113194383306759739515082307681141926459231621296960732224964046
Py = 11903303657706407974989296177215005343713679411332034699907763981919547054807
d = 36144925721603087658594284515452164870581325872720374094707712194495455132720
point = EccPoint(Px, Py, curve="Ed25519")
# Private key only
key = ECC.construct(curve="Ed25519", seed=seed)
self.assertEqual(key.pointQ, point)
self.assertTrue(key.has_private())
# Public key only
key = ECC.construct(curve="Ed25519", point_x=Px, point_y=Py)
self.assertEqual(key.pointQ, point)
self.assertFalse(key.has_private())
# Private and public key
key = ECC.construct(curve="Ed25519", seed=seed, point_x=Px, point_y=Py)
self.assertEqual(key.pointQ, point)
self.assertTrue(key.has_private())
# Other names
key = ECC.construct(curve="ed25519", seed=seed)
def test_negative_construct(self):
coord = dict(point_x=10, point_y=4)
coordG = dict(point_x=_curves['ed25519'].Gx, point_y=_curves['ed25519'].Gy)
self.assertRaises(ValueError, ECC.construct, curve="Ed25519", **coord)
self.assertRaises(ValueError, ECC.construct, curve="Ed25519", d=2, **coordG)
self.assertRaises(ValueError, ECC.construct, curve="Ed25519", seed=b'H'*31)
def get_tests(config={}):
tests = []
tests += list_test_cases(TestEccPoint_Ed25519)
tests += list_test_cases(TestEccKey_Ed25519)
tests += list_test_cases(TestEccModule_Ed25519)
return tests
if __name__ == '__main__':
def suite():
return unittest.TestSuite(get_tests())
unittest.main(defaultTest='suite')

View file

@ -29,6 +29,8 @@
# ===================================================================
import unittest
from binascii import unhexlify
from Crypto.SelfTest.st_common import list_test_cases
from Crypto.SelfTest.loader import load_test_vectors
@ -190,7 +192,7 @@ class TestEccPoint_NIST_P192(unittest.TestCase):
self.assertEqual(pointR.x, pointRx)
self.assertEqual(pointR.y, pointRy)
def test_joing_scalar_multiply(self):
def test_joint_scalar_multiply(self):
d = 0xa78a236d60baec0c5dd41b33a542463a8255391af64c74ee
e = 0xc4be3d53ec3089e71e4de8ceab7cce889bc393cd85b972bc
pointRx = 0x019f64eed8fa9b72b7dfea82c17c9bfa60ecb9e1778b5bde
@ -993,6 +995,9 @@ class TestEccKey_P256(unittest.TestCase):
key = EccKey(curve="secp256r1", d=1)
key = EccKey(curve="prime256v1", d=1)
# Must not accept d parameter
self.assertRaises(ValueError, EccKey, curve="p256", seed=b'H'*32)
def test_public_key(self):
point = EccPoint(_curves['p256'].Gx, _curves['p256'].Gy)

View file

@ -148,6 +148,14 @@ def create_ref_keys_p521():
return (ECC.construct(curve="P-521", d=private_key_d),
ECC.construct(curve="P-521", point_x=public_key_x, point_y=public_key_y))
def create_ref_keys_ed25519():
key_lines = load_file("ecc_ed25519.txt").splitlines()
seed = compact(key_lines[5:8])
key = ECC.construct(curve="Ed25519", seed=seed)
return (key, key.public_key())
# Create reference key pair
# ref_private, ref_public = create_ref_keys_p521()
@ -199,10 +207,10 @@ class TestImport_P192(unittest.TestCase):
key = ECC.import_key(key_file, curve_name='P192')
self.assertEqual(self.ref_public, key)
def test_import_private_der(self):
def test_import_rfc5915_der(self):
key_file = load_file("ecc_p192_private.der")
key = ECC._import_private_der(key_file, None)
key = ECC._import_rfc5915_der(key_file, None)
self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None)
@ -309,10 +317,10 @@ class TestImport_P224(unittest.TestCase):
key = ECC.import_key(key_file, curve_name='P224')
self.assertEqual(self.ref_public, key)
def test_import_private_der(self):
def test_import_rfc5915_der(self):
key_file = load_file("ecc_p224_private.der")
key = ECC._import_private_der(key_file, None)
key = ECC._import_rfc5915_der(key_file, None)
self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None)
@ -419,10 +427,10 @@ class TestImport_P256(unittest.TestCase):
key = ECC.import_key(key_file, curve_name='P256')
self.assertEqual(self.ref_public, key)
def test_import_private_der(self):
def test_import_rfc5915_der(self):
key_file = load_file("ecc_p256_private.der")
key = ECC._import_private_der(key_file, None)
key = ECC._import_rfc5915_der(key_file, None)
self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None)
@ -559,10 +567,10 @@ class TestImport_P384(unittest.TestCase):
key = ECC.import_key(key_file, curve_name='P384')
self.assertEqual(self.ref_public, key)
def test_import_private_der(self):
def test_import_rfc5915_der(self):
key_file = load_file("ecc_p384_private.der")
key = ECC._import_private_der(key_file, None)
key = ECC._import_rfc5915_der(key_file, None)
self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None)
@ -694,10 +702,10 @@ class TestImport_P521(unittest.TestCase):
key = ECC.import_key(key_file, curve_name='P521')
self.assertEqual(self.ref_public, key)
def test_import_private_der(self):
def test_import_rfc5915_der(self):
key_file = load_file("ecc_p521_private.der")
key = ECC._import_private_der(key_file, None)
key = ECC._import_rfc5915_der(key_file, None)
self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None)
@ -840,10 +848,10 @@ class TestExport_P192(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded)
def test_export_private_der(self):
def test_export_rfc5915_private_der(self):
key_file = load_file("ecc_p192_private.der")
encoded = self.ref_private._export_private_der()
encoded = self.ref_private._export_rfc5915_private_der()
self.assertEqual(key_file, encoded)
# ---
@ -1094,10 +1102,10 @@ class TestExport_P224(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded)
def test_export_private_der(self):
def test_export_rfc5915_private_der(self):
key_file = load_file("ecc_p224_private.der")
encoded = self.ref_private._export_private_der()
encoded = self.ref_private._export_rfc5915_private_der()
self.assertEqual(key_file, encoded)
# ---
@ -1348,10 +1356,10 @@ class TestExport_P256(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded)
def test_export_private_der(self):
def test_export_rfc5915_private_der(self):
key_file = load_file("ecc_p256_private.der")
encoded = self.ref_private._export_private_der()
encoded = self.ref_private._export_rfc5915_private_der()
self.assertEqual(key_file, encoded)
# ---
@ -1627,10 +1635,10 @@ class TestExport_P384(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded)
def test_export_private_der(self):
def test_export_rfc5915_private_der(self):
key_file = load_file("ecc_p384_private.der")
encoded = self.ref_private._export_private_der()
encoded = self.ref_private._export_rfc5915_private_der()
self.assertEqual(key_file, encoded)
# ---
@ -1911,10 +1919,10 @@ class TestExport_P521(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded)
def test_export_private_der(self):
def test_export_rfc5915_private_der(self):
key_file = load_file("ecc_p521_private.der")
encoded = self.ref_private._export_private_der()
encoded = self.ref_private._export_rfc5915_private_der()
self.assertEqual(key_file, encoded)
# ---
@ -2156,6 +2164,231 @@ vv6oYkMIIi7r5oQWAiQDrR2mlrrFDL9V7GH/r8SWQw==
self.assertEqual(low16, 0x9643)
class TestImport_Ed25519(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(TestImport_Ed25519, self).__init__(*args, **kwargs)
self.ref_private, self.ref_public = create_ref_keys_ed25519()
def test_import_public_der(self):
key_file = load_file("ecc_ed25519_public.der")
key = ECC._import_subjectPublicKeyInfo(key_file)
self.assertEqual(self.ref_public, key)
key = ECC._import_der(key_file, None)
self.assertEqual(self.ref_public, key)
key = ECC.import_key(key_file)
self.assertEqual(self.ref_public, key)
def test_import_pkcs8_der(self):
key_file = load_file("ecc_ed25519_private.der")
key = ECC._import_der(key_file, None)
self.assertEqual(self.ref_private, key)
key = ECC.import_key(key_file)
self.assertEqual(self.ref_private, key)
def test_import_private_pkcs8_encrypted_1(self):
key_file = load_file("ecc_ed25519_private_p8.der")
key = ECC._import_der(key_file, "secret")
self.assertEqual(self.ref_private, key)
key = ECC.import_key(key_file, "secret")
self.assertEqual(self.ref_private, key)
def test_import_private_pkcs8_encrypted_2(self):
key_file = load_file("ecc_ed25519_private_p8.pem")
key = ECC.import_key(key_file, "secret")
self.assertEqual(self.ref_private, key)
def test_import_x509_der(self):
key_file = load_file("ecc_ed25519_x509.der")
key = ECC._import_der(key_file, None)
self.assertEqual(self.ref_public, key)
key = ECC.import_key(key_file)
self.assertEqual(self.ref_public, key)
def test_import_public_pem(self):
key_file = load_file("ecc_ed25519_public.pem")
key = ECC.import_key(key_file)
self.assertEqual(self.ref_public, key)
def test_import_private_pem(self):
key_file = load_file("ecc_ed25519_private.pem")
key = ECC.import_key(key_file)
self.assertEqual(self.ref_private, key)
def test_import_private_pem_encrypted(self):
for algo in "des3", "aes128", "aes192", "aes256":
key_file = load_file("ecc_ed25519_private_enc_%s.pem" % algo)
key = ECC.import_key(key_file, "secret")
self.assertEqual(self.ref_private, key)
key = ECC.import_key(tostr(key_file), b"secret")
self.assertEqual(self.ref_private, key)
def test_import_x509_pem(self):
key_file = load_file("ecc_ed25519_x509.pem")
key = ECC.import_key(key_file)
self.assertEqual(self.ref_public, key)
def test_import_openssh_public(self):
key_file = load_file("ecc_ed25519_public_openssh.txt")
key = ECC._import_openssh_public(key_file)
self.failIf(key.has_private())
key = ECC.import_key(key_file)
self.failIf(key.has_private())
def test_import_openssh_private_clear(self):
key_file = load_file("ecc_ed25519_private_openssh.pem")
key = ECC.import_key(key_file)
def test_import_openssh_private_password(self):
key_file = load_file("ecc_p521_private_openssh_pwd.pem")
key = ECC.import_key(key_file, b"password")
class TestExport_Ed25519(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(TestExport_Ed25519, self).__init__(*args, **kwargs)
self.ref_private, self.ref_public = create_ref_keys_ed25519()
def test_export_public_der(self):
key_file = load_file("ecc_ed25519_public.der")
encoded = self.ref_public._export_subjectPublicKeyInfo(True)
self.assertEqual(key_file, encoded)
encoded = self.ref_public.export_key(format="DER")
self.assertEqual(key_file, encoded)
encoded = self.ref_public.export_key(format="DER", compress=False)
self.assertEqual(key_file, encoded)
def test_export_public_sec1(self):
self.assertRaises(ValueError, self.ref_public.export_key, format="SEC1")
def test_export_private_pkcs8_clear(self):
key_file = load_file("ecc_ed25519_private.der")
encoded = self.ref_private._export_pkcs8()
self.assertEqual(key_file, encoded)
# ---
encoded = self.ref_private.export_key(format="DER")
self.assertEqual(key_file, encoded)
self.assertRaises(ValueError, self.ref_private.export_key,
format="DER", use_pkcs8=False)
def test_export_private_pkcs8_encrypted(self):
encoded = self.ref_private._export_pkcs8(passphrase="secret",
protection="PBKDF2WithHMAC-SHA1AndAES128-CBC")
# This should prove that the output is password-protected
self.assertRaises(ValueError, ECC._import_pkcs8, encoded, None)
decoded = ECC._import_pkcs8(encoded, "secret")
self.assertEqual(self.ref_private, decoded)
# ---
encoded = self.ref_private.export_key(format="DER",
passphrase="secret",
protection="PBKDF2WithHMAC-SHA1AndAES128-CBC")
decoded = ECC.import_key(encoded, "secret")
self.assertEqual(self.ref_private, decoded)
def test_export_public_pem(self):
key_file_ref = load_file("ecc_ed25519_public.pem", "rt").strip()
key_file = self.ref_public.export_key(format="PEM").strip()
self.assertEqual(key_file_ref, key_file)
def test_export_private_pem_clear(self):
key_file = load_file("ecc_ed25519_private.pem", "rt").strip()
encoded = self.ref_private.export_key(format="PEM").strip()
self.assertEqual(key_file, encoded)
def test_export_private_pem_encrypted(self):
encoded = self.ref_private.export_key(format="PEM",
passphrase=b"secret",
protection="PBKDF2WithHMAC-SHA1AndAES128-CBC")
# This should prove that the output is password-protected
self.assertRaises(ValueError, ECC.import_key, encoded)
assert "ENCRYPTED PRIVATE KEY" in encoded
decoded = ECC.import_key(encoded, "secret")
self.assertEqual(self.ref_private, decoded)
def test_export_openssh(self):
key_file = load_file("ecc_ed25519_public_openssh.txt", "rt")
public_key = ECC.import_key(key_file)
key_file = " ".join(key_file.split(' ')[:2]) # remove comment
encoded = public_key._export_openssh(False)
self.assertEqual(key_file, encoded.strip())
encoded = public_key.export_key(format="OpenSSH")
self.assertEqual(key_file, encoded.strip())
def test_prng(self):
# Test that password-protected containers use the provided PRNG
encoded1 = self.ref_private.export_key(format="PEM",
passphrase="secret",
protection="PBKDF2WithHMAC-SHA1AndAES128-CBC",
randfunc=get_fixed_prng())
encoded2 = self.ref_private.export_key(format="PEM",
passphrase="secret",
protection="PBKDF2WithHMAC-SHA1AndAES128-CBC",
randfunc=get_fixed_prng())
self.assertEqual(encoded1, encoded2)
def test_byte_or_string_passphrase(self):
encoded1 = self.ref_private.export_key(format="PEM",
passphrase="secret",
protection="PBKDF2WithHMAC-SHA1AndAES128-CBC",
randfunc=get_fixed_prng())
encoded2 = self.ref_private.export_key(format="PEM",
passphrase=b"secret",
protection="PBKDF2WithHMAC-SHA1AndAES128-CBC",
randfunc=get_fixed_prng())
self.assertEqual(encoded1, encoded2)
def test_error_params1(self):
# Unknown format
self.assertRaises(ValueError, self.ref_private.export_key, format="XXX")
# Missing 'protection' parameter when PKCS#8 is used
self.assertRaises(ValueError, self.ref_private.export_key, format="PEM",
passphrase="secret")
# Empty password
self.assertRaises(ValueError, self.ref_private.export_key, format="PEM",
passphrase="", use_pkcs8=False)
self.assertRaises(ValueError, self.ref_private.export_key, format="PEM",
passphrase="",
protection="PBKDF2WithHMAC-SHA1AndAES128-CBC")
# No private keys with OpenSSH
self.assertRaises(ValueError, self.ref_private.export_key, format="OpenSSH",
passphrase="secret")
def get_tests(config={}):
tests = []
tests += list_test_cases(TestImport)
@ -2165,12 +2398,15 @@ def get_tests(config={}):
tests += list_test_cases(TestImport_P256)
tests += list_test_cases(TestImport_P384)
tests += list_test_cases(TestImport_P521)
tests += list_test_cases(TestImport_Ed25519)
tests += list_test_cases(TestExport_P192)
tests += list_test_cases(TestExport_P224)
tests += list_test_cases(TestExport_P256)
tests += list_test_cases(TestExport_P384)
tests += list_test_cases(TestExport_P521)
tests += list_test_cases(TestExport_Ed25519)
except MissingTestVectorException:
pass
return tests

View file

@ -22,18 +22,20 @@
"""Self-test for signature modules"""
import os
import unittest
from . import test_pkcs1_15, test_pss, test_dss, test_eddsa
def get_tests(config={}):
tests = []
from . import test_pkcs1_15; tests += test_pkcs1_15.get_tests(config=config)
from . import test_pss; tests += test_pss.get_tests(config=config)
from . import test_dss; tests += test_dss.get_tests(config=config)
tests += test_pkcs1_15.get_tests(config=config)
tests += test_pss.get_tests(config=config)
tests += test_dss.get_tests(config=config)
tests += test_eddsa.get_tests(config=config)
return tests
if __name__ == '__main__':
import unittest
suite = lambda: unittest.TestSuite(get_tests())
unittest.main(defaultTest='suite')
# vim:set ts=4 sw=4 sts=4 expandtab:
if __name__ == '__main__':
def suite():
return unittest.TestSuite(get_tests())
unittest.main(defaultTest='suite')

View file

@ -251,6 +251,10 @@ class FIPS_ECDSA_Tests(unittest.TestCase):
self.assertRaises(ValueError, signer.sign, hash_obj)
self.assertRaises(ValueError, signer.verify, hash_obj, b"\x00" * 40)
def test_negative_eddsa_key(self):
key = ECC.generate(curve="ed25519")
self.assertRaises(ValueError, DSS.new, key, 'fips-186-3')
def test_sign_verify(self):
"""Verify public/private method"""

View file

@ -0,0 +1,355 @@
#
# Copyright (c) 2022, Legrandin <helderijs@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ===================================================================
import unittest
from binascii import unhexlify
from Crypto.PublicKey import ECC
from Crypto.Signature import eddsa
from Crypto.Hash import SHA512
from Crypto.SelfTest.st_common import list_test_cases
from Crypto.SelfTest.loader import load_test_vectors_wycheproof
from Crypto.Util.number import bytes_to_long
rfc8032_tv_str = (
# 7.1 Ed25519
(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
"",
None,
"",
"e5564300c360ac729086e2cc806e828a"
"84877f1eb8e5d974d873e06522490155"
"5fb8821590a33bacc61e39701cf9b46b"
"d25bf5f0595bbe24655141438e7a100b"
),
(
"4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
"3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
"72",
None,
"",
"92a009a9f0d4cab8720e820b5f642540"
"a2b27b5416503f8fb3762223ebdb69da"
"085ac1e43e15996e458f3613d0f11d8c"
"387b2eaeb4302aeeb00d291612bb0c00"
),
(
"c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
"fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
"af82",
None,
"",
"6291d657deec24024827e69c3abe01a3"
"0ce548a284743a445e3680d7db5ac3ac"
"18ff9b538d16f290ae67f760984dc659"
"4a7c15e9716ed28dc027beceea1ec40a"
),
(
"f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5",
"278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e",
"08b8b2b733424243760fe426a4b54908"
"632110a66c2f6591eabd3345e3e4eb98"
"fa6e264bf09efe12ee50f8f54e9f77b1"
"e355f6c50544e23fb1433ddf73be84d8"
"79de7c0046dc4996d9e773f4bc9efe57"
"38829adb26c81b37c93a1b270b20329d"
"658675fc6ea534e0810a4432826bf58c"
"941efb65d57a338bbd2e26640f89ffbc"
"1a858efcb8550ee3a5e1998bd177e93a"
"7363c344fe6b199ee5d02e82d522c4fe"
"ba15452f80288a821a579116ec6dad2b"
"3b310da903401aa62100ab5d1a36553e"
"06203b33890cc9b832f79ef80560ccb9"
"a39ce767967ed628c6ad573cb116dbef"
"efd75499da96bd68a8a97b928a8bbc10"
"3b6621fcde2beca1231d206be6cd9ec7"
"aff6f6c94fcd7204ed3455c68c83f4a4"
"1da4af2b74ef5c53f1d8ac70bdcb7ed1"
"85ce81bd84359d44254d95629e9855a9"
"4a7c1958d1f8ada5d0532ed8a5aa3fb2"
"d17ba70eb6248e594e1a2297acbbb39d"
"502f1a8c6eb6f1ce22b3de1a1f40cc24"
"554119a831a9aad6079cad88425de6bd"
"e1a9187ebb6092cf67bf2b13fd65f270"
"88d78b7e883c8759d2c4f5c65adb7553"
"878ad575f9fad878e80a0c9ba63bcbcc"
"2732e69485bbc9c90bfbd62481d9089b"
"eccf80cfe2df16a2cf65bd92dd597b07"
"07e0917af48bbb75fed413d238f5555a"
"7a569d80c3414a8d0859dc65a46128ba"
"b27af87a71314f318c782b23ebfe808b"
"82b0ce26401d2e22f04d83d1255dc51a"
"ddd3b75a2b1ae0784504df543af8969b"
"e3ea7082ff7fc9888c144da2af58429e"
"c96031dbcad3dad9af0dcbaaaf268cb8"
"fcffead94f3c7ca495e056a9b47acdb7"
"51fb73e666c6c655ade8297297d07ad1"
"ba5e43f1bca32301651339e22904cc8c"
"42f58c30c04aafdb038dda0847dd988d"
"cda6f3bfd15c4b4c4525004aa06eeff8"
"ca61783aacec57fb3d1f92b0fe2fd1a8"
"5f6724517b65e614ad6808d6f6ee34df"
"f7310fdc82aebfd904b01e1dc54b2927"
"094b2db68d6f903b68401adebf5a7e08"
"d78ff4ef5d63653a65040cf9bfd4aca7"
"984a74d37145986780fc0b16ac451649"
"de6188a7dbdf191f64b5fc5e2ab47b57"
"f7f7276cd419c17a3ca8e1b939ae49e4"
"88acba6b965610b5480109c8b17b80e1"
"b7b750dfc7598d5d5011fd2dcc5600a3"
"2ef5b52a1ecc820e308aa342721aac09"
"43bf6686b64b2579376504ccc493d97e"
"6aed3fb0f9cd71a43dd497f01f17c0e2"
"cb3797aa2a2f256656168e6c496afc5f"
"b93246f6b1116398a346f1a641f3b041"
"e989f7914f90cc2c7fff357876e506b5"
"0d334ba77c225bc307ba537152f3f161"
"0e4eafe595f6d9d90d11faa933a15ef1"
"369546868a7f3a45a96768d40fd9d034"
"12c091c6315cf4fde7cb68606937380d"
"b2eaaa707b4c4185c32eddcdd306705e"
"4dc1ffc872eeee475a64dfac86aba41c"
"0618983f8741c5ef68d3a101e8a3b8ca"
"c60c905c15fc910840b94c00a0b9d0",
None,
"",
"0aab4c900501b3e24d7cdf4663326a3a"
"87df5e4843b2cbdb67cbf6e460fec350"
"aa5371b1508f9f4528ecea23c436d94b"
"5e8fcd4f681e30a6ac00a9704a188a03"
),
# 7.2 Ed25519ctx
(
"0305334e381af78f141cb666f6199f57"
"bc3495335a256a95bd2a55bf546663f6",
"dfc9425e4f968f7f0c29f0259cf5f9ae"
"d6851c2bb4ad8bfb860cfee0ab248292",
"f726936d19c800494e3fdaff20b276a8",
None,
"666f6f",
"55a4cc2f70a54e04288c5f4cd1e45a7b"
"b520b36292911876cada7323198dd87a"
"8b36950b95130022907a7fb7c4e9b2d5"
"f6cca685a587b4b21f4b888e4e7edb0d"
),
(
"0305334e381af78f141cb666f6199f57"
"bc3495335a256a95bd2a55bf546663f6",
"dfc9425e4f968f7f0c29f0259cf5f9ae"
"d6851c2bb4ad8bfb860cfee0ab248292",
"f726936d19c800494e3fdaff20b276a8",
None,
"626172",
"fc60d5872fc46b3aa69f8b5b4351d580"
"8f92bcc044606db097abab6dbcb1aee3"
"216c48e8b3b66431b5b186d1d28f8ee1"
"5a5ca2df6668346291c2043d4eb3e90d"
),
(
"0305334e381af78f141cb666f6199f57"
"bc3495335a256a95bd2a55bf546663f6",
"dfc9425e4f968f7f0c29f0259cf5f9ae"
"d6851c2bb4ad8bfb860cfee0ab248292",
"508e9e6882b979fea900f62adceaca35",
None,
"666f6f",
"8b70c1cc8310e1de20ac53ce28ae6e72"
"07f33c3295e03bb5c0732a1d20dc6490"
"8922a8b052cf99b7c4fe107a5abb5b2c"
"4085ae75890d02df26269d8945f84b0b"
),
(
"ab9c2853ce297ddab85c993b3ae14bca"
"d39b2c682beabc27d6d4eb20711d6560",
"0f1d1274943b91415889152e893d80e9"
"3275a1fc0b65fd71b4b0dda10ad7d772",
"f726936d19c800494e3fdaff20b276a8",
None,
"666f6f",
"21655b5f1aa965996b3f97b3c849eafb"
"a922a0a62992f73b3d1b73106a84ad85"
"e9b86a7b6005ea868337ff2d20a7f5fb"
"d4cd10b0be49a68da2b2e0dc0ad8960f"
),
# 7.3 Ed25519ph
(
"833fe62409237b9d62ec77587520911e"
"9a759cec1d19755b7da901b96dca3d42",
"ec172b93ad5e563bf4932c70e1245034"
"c35467ef2efd4d64ebf819683467e2bf",
"616263",
SHA512,
"",
"98a70222f0b8121aa9d30f813d683f80"
"9e462b469c7ff87639499bb94e6dae41"
"31f85042463c2a355a2003d062adf5aa"
"a10b8c61e636062aaad11c2a26083406"
)
)
rfc8032_tv_bytes = []
for tv_str in rfc8032_tv_str:
rfc8032_tv_bytes.append([unhexlify(i) if isinstance(i, str) else i for i in tv_str])
class TestEdDSA(unittest.TestCase):
def test_sign(self):
for sk, _, msg, hashmod, ctx, exp_signature in rfc8032_tv_bytes:
key = eddsa.import_private_key(sk)
signer = eddsa.new(key, 'rfc8032', context=ctx)
if hashmod is None:
# PureEdDSA
signature = signer.sign(msg)
else:
# HashEdDSA
hashobj = hashmod.new(msg)
signature = signer.sign(hashobj)
self.assertEqual(exp_signature, signature)
def test_verify(self):
for _, pk, msg, hashmod, ctx, exp_signature in rfc8032_tv_bytes:
key = eddsa.import_public_key(pk)
verifier = eddsa.new(key, 'rfc8032', context=ctx)
if hashmod is None:
# PureEdDSA
verifier.verify(msg, exp_signature)
else:
# HashEdDSA
hashobj = hashmod.new(msg)
verifier.verify(hashobj, exp_signature)
def test_negative(self):
key = ECC.generate(curve="ed25519")
self.assertRaises(ValueError, eddsa.new, key, 'rfc9999')
nist_key = ECC.generate(curve="p256")
self.assertRaises(ValueError, eddsa.new, nist_key, 'rfc8032')
class TestExport_Ed25519(unittest.TestCase):
def test_raw(self):
key = ECC.generate(curve="Ed25519")
x, y = key.pointQ.xy
raw = bytearray(key._export_ed25519())
sign_x = raw[31] >> 7
raw[31] &= 0x7F
yt = bytes_to_long(raw[::-1])
self.assertEqual(y, yt)
self.assertEqual(x & 1, sign_x)
key = ECC.construct(point_x=0, point_y=1, curve="Ed25519")
out = key._export_ed25519()
self.assertEqual(b'\x01' + b'\x00' * 31, out)
class TestImport_Ed25519(unittest.TestCase):
def test_raw(self):
Px = 24407857220263921307776619664228778204996144802740950419837658238229122415920
Py = 56480760040633817885061096979765646085062883740629155052073094891081309750690
encoded = b'\xa2\x05\xd6\x00\xe1 \xe1\xc0\xff\x96\xee?V\x8e\xba/\xd3\x89\x06\xd7\xc4c\xe8$\xc2d\xd7a1\xfa\xde|'
key = eddsa.import_public_key(encoded)
self.assertEqual(Py, key.pointQ.y)
self.assertEqual(Px, key.pointQ.x)
encoded = b'\x01' + b'\x00' * 31
key = eddsa.import_public_key(encoded)
self.assertEqual(1, key.pointQ.y)
self.assertEqual(0, key.pointQ.x)
class TestVectorsEdDSAWycheproof(unittest.TestCase):
def add_tests(self, filename):
def pk(group):
elem = group['key']['pk']
return unhexlify(elem)
def sk(group):
elem = group['key']['sk']
return unhexlify(elem)
result = load_test_vectors_wycheproof(("Signature", "wycheproof"),
filename,
"Wycheproof ECDSA signature (%s)"
% filename,
group_tag={'pk': pk, 'sk': sk})
self.tv += result
def setUp(self):
self.tv = []
self.add_tests("eddsa_test.json")
def test_sign(self, tv):
if not tv.valid:
return
self._id = "Wycheproof EdDSA Sign Test #%d (%s, %s)" % (tv.id, tv.comment, tv.filename)
key = eddsa.import_private_key(tv.sk)
signer = eddsa.new(key, 'rfc8032')
signature = signer.sign(tv.msg)
self.assertEqual(signature, tv.sig)
def test_verify(self, tv):
self._id = "Wycheproof EdDSA Verify Test #%d (%s, %s)" % (tv.id, tv.comment, tv.filename)
key = eddsa.import_public_key(tv.pk)
verifier = eddsa.new(key, 'rfc8032')
try:
verifier.verify(tv.msg, tv.sig)
except ValueError:
assert not tv.valid
else:
assert tv.valid
def runTest(self):
for tv in self.tv:
self.test_sign(tv)
self.test_verify(tv)
def get_tests(config={}):
tests = []
tests += list_test_cases(TestExport_Ed25519)
tests += list_test_cases(TestImport_Ed25519)
tests += list_test_cases(TestEdDSA)
tests += [TestVectorsEdDSAWycheproof()]
return tests
if __name__ == '__main__':
def suite():
return unittest.TestSuite(get_tests())
unittest.main(defaultTest='suite')

View file

@ -379,6 +379,8 @@ def new(key, mode, encoding='binary', randfunc=None):
if isinstance(key, EccKey):
order = key._curve.order
private_key_attr = 'd'
if key._curve.name == "ed25519":
raise ValueError("ECC key is not on a NIST P curve")
elif isinstance(key, DsaKey):
order = Integer(key.q)
private_key_attr = 'x'

View file

@ -33,4 +33,4 @@
A collection of standardized protocols to carry out digital signatures.
"""
__all__ = ['PKCS1_v1_5', 'PKCS1_PSS', 'DSS', 'pkcs1_15', 'pss']
__all__ = ['PKCS1_v1_5', 'PKCS1_PSS', 'DSS', 'pkcs1_15', 'pss', 'eddsa']

View file

@ -0,0 +1,229 @@
# ===================================================================
#
# Copyright (c) 2022, Legrandin <helderijs@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ===================================================================
from Crypto.Math.Numbers import Integer
from Crypto.Hash import SHA512
from Crypto.Util.py3compat import bchr, is_bytes
from Crypto.PublicKey.ECC import EccKey, construct, _import_ed25519_public_key
def import_public_key(encoded):
"""Import an EdDSA ECC public key, when encoded as raw ``bytes`` as described
in RFC8032.
Args:
encoded (bytes):
The EdDSA public key to import.
It must be 32 bytes long for Ed25519.
Returns:
:class:`Crypto.PublicKey.EccKey` : a new ECC key object.
Raises:
ValueError: when the given key cannot be parsed.
"""
x, y = _import_ed25519_public_key(encoded)
return construct(curve="Ed25519", point_x=x, point_y=y)
def import_private_key(encoded):
"""Import an EdDSA ECC private key, when encoded as raw ``bytes`` as described
in RFC8032.
Args:
encoded (bytes):
The EdDSA private key to import. It must be 32 bytes long for Ed25519.
Returns:
:class:`Crypto.PublicKey.EccKey` : a new ECC key object.
Raises:
ValueError: when the given key cannot be parsed.
"""
if len(encoded) != 32:
raise ValueError("Incorrect length. Only Ed25519 private keys are supported.")
# Note that the private key is truly a sequence of random bytes,
# so we cannot check its correctness in any way.
return construct(seed=encoded, curve="ed25519")
class EdDSASigScheme(object):
"""An EdDSA signature object.
Do not instantiate directly.
Use :func:`Crypto.Signature.eddsa.new`.
"""
def __init__(self, key, context):
"""Create a new EdDSA object.
Do not instantiate this object directly,
use `Crypto.Signature.DSS.new` instead.
"""
self._key = key
self._context = context
self._A = key._export_ed25519()
self._order = key._curve.order
def can_sign(self):
"""Return ``True`` if this signature object can be used
for signing messages."""
return self._key.has_private()
def sign(self, msg_or_hash):
"""Compute the EdDSA signature of a message.
Args:
msg_or_hash (bytes or a :class:`Crypto.Hash.SHA512` object):
The message to verify (*PureEdDSA*) or
the hash that was carried out over the message (*HashEdDSA*).
:return: The signature as ``bytes``
:raise TypeError: if the EdDSA key has no private half
"""
if not self._key.has_private():
raise TypeError("Private key is needed to sign")
ph = isinstance(msg_or_hash, SHA512.SHA512Hash)
if not (ph or is_bytes(msg_or_hash)):
raise TypeError("'msg_or_hash' must be bytes of a SHA-512 hash")
if self._context or ph:
flag = int(ph)
dom2 = b'SigEd25519 no Ed25519 collisions' + bchr(flag) + \
bchr(len(self._context)) + self._context
else:
dom2 = b''
PHM = msg_or_hash.digest() if ph else msg_or_hash
# See RFC 8032, section 5.1.6
# Step 2
r_hash = SHA512.new(dom2 + self._key._prefix + PHM).digest()
r = Integer.from_bytes(r_hash, 'little') % self._order
# Step 3
R_pk = EccKey(point=r * self._key._curve.G)._export_ed25519()
# Step 4
k_hash = SHA512.new(dom2 + R_pk + self._A + PHM).digest()
k = Integer.from_bytes(k_hash, 'little') % self._order
# Step 5
s = (r + k * self._key.d) % self._order
return R_pk + s.to_bytes(32, 'little')
def verify(self, msg_or_hash, signature):
"""Check if an EdDSA signature is authentic.
Args:
msg_or_hash (bytes or a :class:`Crypto.Hash.SHA512` object):
The message to verify (*PureEdDSA*) or
the hash that was carried out over the message (*HashEdDSA*).
signature (``bytes``):
The signature that needs to be validated.
:raise ValueError: if the signature is not authentic
"""
if len(signature) != 64:
raise ValueError("The signature is not authentic (length)")
ph = isinstance(msg_or_hash, SHA512.SHA512Hash)
if not (ph or is_bytes(msg_or_hash)):
raise TypeError("'msg_or_hash' must be bytes of a SHA-512 hash")
if self._context or ph:
flag = int(ph)
dom2 = b'SigEd25519 no Ed25519 collisions' + bchr(flag) + \
bchr(len(self._context)) + self._context
else:
dom2 = b''
PHM = msg_or_hash.digest() if ph else msg_or_hash
# Step 1
try:
R = import_public_key(signature[:32]).pointQ
except ValueError:
raise ValueError("The signature is not authentic (R)")
s = Integer.from_bytes(signature[32:], 'little')
if s > self._order:
raise ValueError("The signature is not authentic (S)")
# Step 2
k_hash = SHA512.new(dom2 + signature[:32] + self._A + PHM).digest()
k = Integer.from_bytes(k_hash, 'little') % self._order
# Step 3
point1 = s * 8 * self._key._curve.G
# OPTIMIZE: with double-scalar multiplication, with no SCA
# countermeasures because it is public values
point2 = 8 * R + k * 8 * self._key.pointQ
if point1 != point2:
raise ValueError("The signature is not authentic")
def new(key, mode, context=None):
"""Create a signature object :class:`EdDSASigScheme` that
can perform or verify an EdDSA signature.
Args:
key (:class:`Crypto.PublicKey.ECC` on curve ``Ed25519``):
The key to use for computing the signature (*private* keys only)
or for verifying one.
mode (string):
This parameter must be ``'rfc8032'``.
context (bytes):
Up to 255 bytes of `context <https://datatracker.ietf.org/doc/html/rfc8032#page-41>`_,
which is a constant byte string to segregate different protocols or
uses of the same key.
Do not specify a context, if you want a pure Ed25519 signature.
"""
if not isinstance(key, EccKey) or key._curve.name != 'ed25519':
raise ValueError("EdDSA can only be used with Ed25519 keys")
if mode != 'rfc8032':
raise ValueError("Mode must be 'rfc8032'")
if context is None:
context = b''
elif len(context) > 255:
raise ValueError("Context for EdDSA must not be longer than 255 bytes")
return EdDSASigScheme(key, context)

View file

@ -0,0 +1,18 @@
from typing import Union, Optional
from typing_extensions import Protocol
from Crypto.PublicKey.ECC import EccKey
class Hash(Protocol):
def digest(self) -> bytes: ...
def import_public_key(encoded: bytes) -> EccKey: ...
def import_private_key(encoded: bytes) -> EccKey: ...
class EdDSASigScheme(object):
def __init__(self, key: EccKey, context: bytes) -> None: ...
def can_sign(self) -> bool: ...
def sign(self, msg_or_hash: Union[bytes, Hash]) -> bytes: ...
def verify(self, msg_or_hash: Union[bytes, Hash], signature: bytes) -> None: ...
def new(key: EccKey, mode: bytes, context: Optional[bytes]=None) -> EdDSASigScheme: ...

View file

@ -13,6 +13,7 @@ def bytestring(x: Any) -> bool: ...
def is_native_int(s: Any) -> bool: ...
def is_string(x: Any) -> bool: ...
def is_bytes(x: Any) -> bool: ...
def BytesIO(b: bytes) -> IO[bytes]: ...
def StringIO(s: str) -> IO[str]: ...

View file

@ -1,6 +1,6 @@
__all__ = ['Cipher', 'Hash', 'Protocol', 'PublicKey', 'Util', 'Signature',
'IO', 'Math']
version_info = (3, 15, '0', 'dev')
version_info = (3, 15, '0dev')
__version__ = ".".join([str(x) for x in version_info])

View file

@ -443,6 +443,14 @@ ext_modules = [
'src/p521_table.c'],
py_limited_api=True,
),
Extension("Crypto.PublicKey._x255219",
include_dirs=['src/'],
sources=['src/x25519.c']
),
Extension("Crypto.PublicKey._ed25519",
include_dirs=['src/'],
sources=['src/ed25519.c']
),
# Math
Extension("Crypto.Math._modexp",

View file

@ -28,5 +28,8 @@ p384_table.c: make_ecc_table.py
p521_table.c: make_ecc_table.py
python make_ecc_table.py p521 4 p521_table
x25519: x25519.c multiply_64.c
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ -DSYS_BITS=64 -DPROFILE
clean::
rm -f ec_ws_p256 ec_ws_p384 ec_ws_p521 mont.o modexp
rm -f ec_ws_p256 ec_ws_p384 ec_ws_p521 mont.o modexp x25519

282
src/bignum.c Normal file
View file

@ -0,0 +1,282 @@
#ifndef __BIGNUM_C
#define __BIGNUM_C
#include "common.h"
#include "multiply.h"
#if defined(USE_SSE2)
#if defined(HAVE_INTRIN_H)
#include <intrin.h>
#elif defined(HAVE_X86INTRIN_H)
#include <x86intrin.h>
#elif defined(HAVE_EMMINTRIN_H)
#include <xmmintrin.h>
#include <emmintrin.h>
#endif
#endif
static inline unsigned is_odd(uint64_t x)
{
return 1 == (x & 1);
}
static inline unsigned is_even(uint64_t x)
{
return !is_odd(x);
}
/**
* Check if a multi-word integer x is greater than or equal to y.
*
* @param x The first term
* @param y The second term
* @param nw The number of words that make up x and y
* @return 1 if x>=y, 0 if x<y
*/
STATIC int ge(const uint64_t *x, const uint64_t *y, size_t nw)
{
unsigned mask = (unsigned)-1;
unsigned result = 0;
size_t i, j;
i = nw - 1;
for (j=0; j<nw; j++, i--) {
unsigned greater, lower;
greater = x[i] > y[i];
lower = x[i] < y[i];
result |= mask & (greater | (lower << 1));
mask &= (greater ^ lower) - 1;
}
return result<2;
}
/*
* Subtract a multi-word integer b from a.
*
* @param out The location where the multi-word result is stored
* @param a Number to subtract from
* @param b Number to subtract
* @param nw The number of words of both a and b
* @result 0 if there is no borrow, 1 otherwise
*/
STATIC unsigned sub(uint64_t *out, const uint64_t *a, const uint64_t *b, size_t nw)
{
size_t i;
unsigned borrow1 , borrow2;
borrow2 = 0;
for (i=0; i<nw; i++) {
borrow1 = b[i] > a[i];
out[i] = a[i] - b[i];
borrow1 |= borrow2 > out[i];
out[i] -= borrow2;
borrow2 = borrow1;
}
return borrow2;
}
/*
* Multiply a multi-word integer a by a 64-bit scalar k and
* then add the result to the multi-word integer t.
*
* @param t The multi-word integer accumulator
* @param tw The number of words of t
* @param a The multi-word integer to multiply with the scalar
* @param aw The number of words of a
* @param k The 64-bit scalar multiplier
*/
STATIC void addmul(uint64_t *t, size_t tw, const uint64_t *a, size_t aw, uint64_t k)
{
size_t i;
uint64_t carry;
carry = 0;
for (i=0; i<aw; i++) {
uint64_t prod_lo, prod_hi;
DP_MULT(a[i], k, prod_lo, prod_hi);
prod_lo += carry;
prod_hi += prod_lo < carry;
t[i] += prod_lo;
prod_hi += t[i] < prod_lo;
carry = prod_hi;
}
for (; carry; i++) {
t[i] += carry;
carry = t[i] < carry;
}
assert(i <= tw);
}
/**
* Multiply two multi-word integers.
*
* @param t The location where the result is stored. It is twice as big as
* either a (or b). It is an array of 2*nw words).
* @param scratchpad Temporary area. It is an array of 3*nw words.
* @param a The first term, array of nw words.
* @param b The second term, array of nw words.
* @param nw The number of words of both a and b.
*
*/
STATIC void product(uint64_t *t, uint64_t *scratchpad, const uint64_t *a, const uint64_t *b, size_t nw)
{
size_t i;
memset(t, 0, 2*sizeof(uint64_t)*nw);
for (i=0; i<(nw ^ (nw & 1)); i+=2) {
addmul128(&t[i], scratchpad, a, b[i], b[i+1], 2*nw-i, nw);
}
if (is_odd(nw)) {
addmul(&t[nw-1], nw+2, a, nw, b[nw-1]);
}
}
/*
* Select a number out of two, in constant time.
*
* @param out The location where the multi-word result is stored
* @param a The first choice, selected if cond is true (non-zero)
* @param b The second choice, selected if cond is false (zero)
* @param cond The flag that drives the selection
* @param words The number of words of a, b, and out
* @return 0 for success, the appropriate code otherwise.
*/
STATIC FUNC_SSE2 int mod_select(uint64_t *out, const uint64_t *a, const uint64_t *b, unsigned cond, size_t words)
{
uint64_t mask;
#if defined(USE_SSE2)
unsigned pairs, i;
__m128i r0, r1, r2, r3, r4, r5;
pairs = (unsigned)words / 2;
mask = (uint64_t)((cond != 0) - 1); /* 0 for a, 1s for b */
#if SYSBITS == 64
r0 = _mm_set1_epi64x(mask);
#else
r0 = _mm_loadl_epi64((__m128i*)&mask);
r0 = _mm_unpacklo_epi64(r0, r0);
#endif
for (i=0; i<pairs; i++, a+=2, b+=2, out+=2) {
r1 = _mm_loadu_si128((__m128i const*)b);
r2 = _mm_loadu_si128((__m128i const*)a);
r3 = _mm_and_si128(r0, r1);
r4 = _mm_andnot_si128(r0, r2);
r5 = _mm_or_si128(r3, r4);
_mm_storeu_si128((__m128i*)out, r5);
}
if (words & 1) {
*out = (*b & mask) ^ (*a & ~mask);
}
#else
unsigned i;
mask = (uint64_t)((cond != 0) - 1);
for (i=0; i<words; i++) {
*out++ = (*b++ & mask) ^ (*a++ & ~mask);
}
#endif
return 0;
}
/*
* Add two multi-word numbers with modulo arithmetic.
*
* @param out The locaton where the multi-word result (nw words) is stored
* @param a The first term (nw words)
* @param b The second term (nw words)
* @param modulus The modulus (nw words)
* @param tmp1 A temporary area (nw words)
* @param tmp2 A temporary area (nw words)
* @param nw The number of 64-bit words in all parameters
*/
STATIC void add_mod(uint64_t* out, const uint64_t* a, const uint64_t* b, const uint64_t *modulus, uint64_t *tmp1, uint64_t *tmp2, size_t nw)
{
unsigned i;
unsigned carry, borrow1, borrow2;
/*
* Compute sum in tmp1[], and subtract modulus[]
* from tmp1[] into tmp2[].
*/
borrow2 = 0;
for (i=0, carry=0; i<nw; i++) {
tmp1[i] = a[i] + carry;
carry = tmp1[i] < carry;
tmp1[i] += b[i];
carry += tmp1[i] < b[i];
borrow1 = modulus[i] > tmp1[i];
tmp2[i] = tmp1[i] - modulus[i];
borrow1 |= borrow2 > tmp2[i];
tmp2[i] -= borrow2;
borrow2 = borrow1;
}
/*
* If there is no borrow or if there is carry,
* tmp1[] is larger than modulus, so we must return tmp2[].
*/
mod_select(out, tmp2, tmp1, carry | (borrow2 ^ 1), nw);
}
/*
* Subtract two multi-word numbers with modulo arithmetic.
*
* @param out The locaton where the multi-word result (nw words) is stored
* @param a The number it will be subtracted from (nw words)
* @param b The number to subtract (nw wordS)
* @param modulus The modulus (nw words)
* @param tmp1 A temporary area (nw words)
* @param tmp2 A temporary area (nw words)
* @param nw The number of 64-bit words in all parameters
* @return 0 for success, the relevant error code otherwise
*/
int sub_mod(uint64_t *out, const uint64_t *a, const uint64_t *b, const uint64_t *modulus, uint64_t *tmp1, uint64_t *tmp2, size_t nw)
{
unsigned i;
unsigned carry, borrow1 , borrow2;
/*
* Compute difference in tmp1[], and add modulus[]
* to tmp1[] into tmp2[].
*/
borrow2 = 0;
carry = 0;
for (i=0; i<nw; i++) {
borrow1 = b[i] > a[i];
tmp1[i] = a[i] - b[i];
borrow1 |= borrow2 > tmp1[i];
tmp1[i] -= borrow2;
borrow2 = borrow1;
tmp2[i] = tmp1[i] + carry;
carry = tmp2[i] < carry;
tmp2[i] += modulus[i];
carry += tmp2[i] < modulus[i];
}
/*
* If there is no borrow, tmp[] is smaller than modulus.
*/
mod_select(out, tmp2, tmp1, borrow2, nw);
return 0;
}
#endif /* __BIGNUM_C **/

View file

@ -42,7 +42,7 @@ EXPORT_SYM int ec_ws_new_point(EcPoint **pecp,
const uint8_t *y,
size_t len,
const EcContext *ec_ctx);
EXPORT_SYM void ec_free_point(EcPoint *ecp);
EXPORT_SYM void ec_ws_free_point(EcPoint *ecp);
EXPORT_SYM int ec_ws_get_xy(uint8_t *x,
uint8_t *y,
size_t len,

View file

@ -36,6 +36,9 @@
#include "multiply.h"
#include "mont.h"
#include "ec.h"
#include "p256_table.h"
#include "p384_table.h"
#include "p521_table.h"
#include "p256_table.h"
#include "p384_table.h"
@ -986,7 +989,7 @@ EXPORT_SYM void ec_free_context(EcContext *ec_ctx)
* Create a new EC point on the given EC curve.
*
* @param pecp The memory area where the pointer to the newly allocated EC
* point will be stored. Use ec_free_point() for deallocating it.
* point will be stored. Use ec_ws_free_point() for deallocating it.
* @param x The X-coordinate (affine, big-endian)
* @param y The Y-coordinate (affine, big-endian)
* @param len The length of x and y in bytes
@ -1062,7 +1065,7 @@ cleanup:
return res;
}
EXPORT_SYM void ec_free_point(EcPoint *ecp)
EXPORT_SYM void ec_ws_free_point(EcPoint *ecp)
{
if (NULL == ecp)
return;
@ -1172,42 +1175,6 @@ EXPORT_SYM int ec_ws_add(EcPoint *ecpa, EcPoint *ecpb)
return 0;
}
/*
* Normalize the projective representation of a point
* so that Z=1 or Z=0.
*/
EXPORT_SYM int ec_ws_normalize(EcPoint *ecp)
{
MontContext *ctx;
Workplace *wp = NULL;
if (NULL == ecp)
return ERR_NULL;
ctx = ecp->ec_ctx->mont_ctx;
wp = new_workplace(ctx);
if (NULL == wp)
return ERR_MEMORY;
if (!mont_is_zero(ecp->z, ctx)) {
ec_projective_to_affine(ecp->x, ecp->y,
ecp->x, ecp->y, ecp->z,
wp, ctx);
mont_set(ecp->z, 1, ctx);
}
free_workplace(wp);
return 0;
}
EXPORT_SYM int ec_ws_is_pai(EcPoint *ecp)
{
if (NULL == ecp)
return FALSE;
return mont_is_zero(ecp->z, ecp->ec_ctx->mont_ctx);
}
/*
* Blind the scalar factor to be used in an EC multiplication
*
@ -1473,22 +1440,6 @@ cleanup:
return res;
}
EXPORT_SYM int ec_ws_copy(EcPoint *ecp1, const EcPoint *ecp2)
{
MontContext *ctx;
if (NULL == ecp1 || NULL == ecp2)
return ERR_NULL;
ctx = ecp2->ec_ctx->mont_ctx;
ecp1->ec_ctx = ecp2->ec_ctx;
mont_copy(ecp1->x, ecp2->x, ctx);
mont_copy(ecp1->y, ecp2->y, ctx);
mont_copy(ecp1->z, ecp2->z, ctx);
return 0;
}
/*
* Compare two EC points and return 0 if they match
*/
@ -1550,3 +1501,18 @@ EXPORT_SYM int ec_ws_neg(EcPoint *p)
return 0;
}
EXPORT_SYM int ec_ws_copy(EcPoint *ecp1, const EcPoint *ecp2)
{
MontContext *ctx;
if (NULL == ecp1 || NULL == ecp2)
return ERR_NULL;
ctx = ecp2->ec_ctx->mont_ctx;
ecp1->ec_ctx = ecp2->ec_ctx;
mont_copy(ecp1->x, ecp2->x, ctx);
mont_copy(ecp1->y, ecp2->y, ctx);
mont_copy(ecp1->z, ecp2->z, ctx);
return 0;
}

View file

@ -116,8 +116,8 @@ int main(void)
printf("\n");
#endif
ec_free_point(gp);
ec_free_point(ecp);
ec_ws_free_point(gp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
return 0;

View file

@ -118,8 +118,8 @@ int main(void)
printf("\n");
#endif
ec_free_point(gp);
ec_free_point(ecp);
ec_ws_free_point(gp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
return 0;

View file

@ -122,8 +122,8 @@ int main(void)
printf("\n");
#endif
ec_free_point(gp);
ec_free_point(ecp);
ec_ws_free_point(gp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
return 0;

308
src/ed25519.c Normal file
View file

@ -0,0 +1,308 @@
/*
* Twisted Edward curve with equation:
*
* ax² + y² = 1 + dx²y²
*
* where a = -1 and d = - 121665/121666 over the prime field 2² - 19.
*
* Points (x, y) can be represented as extended homogeneous coordinates
* (X, Y, Z, T) with x = X/Z, y = Y/Z, and x*y = T/Z for a non-zero Z.
*
* The PAI (or neutral point) is (0, Z, Z, 0) or equivalently (0, 1).
* The point (x, y) can be obtained by normalizing to (x, y, 1, x*y).
*/
#include "common.h"
#include "endianess.h"
#include "mod25519.c"
typedef struct Point {
uint32_t X[10];
uint32_t Y[10];
uint32_t Z[10];
uint32_t T[10];
} Point;
/*
* P3 can be P1 or P2
*/
STATIC void ed25519_add_internal(Point *P3, const Point *P1, const Point *P2)
{
uint32_t A[10], B[10], C[10], D[10];
/* d = 37095705934669439343138083508754565189542113879843219016388785533085940283555 */
/* k = 2 * d (mod 2²⁵⁵ - 19) */
static const uint32_t k[10] = { 0x2B2F159, 0x1A6E509, 0x22ADD7A, 0xD4141D, 0x38052,
0xF3D130, 0x3407977, 0x19CE331, 0x1C56DFF, 0x901B67 };
/* https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3 */
sub32(A, P1->Y, P1->X); /* (Y1-X1) each limb < 2²⁷ */
sub32(B, P2->Y, P2->X); /* (Y2-X2) < 2²⁷ */
mul_25519(A, A, B); /* A < 2²⁶ */
add32(B, P1->Y, P1->X); /* (Y1+X1) < 2²⁷ */
add32(C, P2->Y, P2->X); /* (Y2+X2) < 2²⁷ */
mul_25519(B, B, C); /* B < 2²⁶ */
mul_25519(C, P1->T, P2->T); /* T1*T2 < 2²⁶ */
mul_25519(C, C, k); /* C < 2²⁶ */
mul_25519(D, P1->Z, P2->Z); /* Z1*Z2 < 2²⁶ */
add_25519(D, D, D); /* D < 2²⁶ */
sub32(P3->T, B, A); /* E=B-A < 2²⁷ */
sub32(P3->Z, D, C); /* F=D-C < 2²⁷ */
add32(D, D, C); /* G=D+C < 2²⁷ */
add32(B, B, A); /* H=B+A < 2²⁷ */
mul_25519(P3->X, P3->T, P3->Z); /* X3=E*F < 2²⁶ */
mul_25519(P3->Y, D, B); /* Y3=G*H < 2²⁶ */
mul_25519(P3->T, P3->T, B); /* T3=E*H < 2²⁶ */
mul_25519(P3->Z, P3->Z, D); /* Z3=F*G < 2²⁶ */
}
STATIC void ed25519_double_internal(Point *P3, const Point *P1)
{
uint32_t A[10], B[10], C[10], D[10];
mul_25519(A, P1->X, P1->X); /* X1^2 each limb < 2²⁶ */
mul_25519(B, P1->Y, P1->Y); /* Y1^2 < 2²⁶ */
mul_25519(C, P1->Z, P1->Z); /* Z1^2 < 2²⁶ */
add_25519(C, C, C); /* C=2*Z1^2 < 2²⁶ */
add32(D, A, B); /* H=A+B < 2²⁷ */
add32(P3->T, P1->X, P1->Y); /* X1+Y1 < 2²⁷ */
mul_25519(P3->T, P3->T, P3->T); /* (X1+Y1)^2 < 2²⁶ */
sub32(P3->T, D, P3->T); /* E=H-(X1+Y1)^2 < 2²⁷ */
sub32(P3->Z, A, B); /* G=A-B < 2²⁷ */
add_25519(A, C, P3->Z); /* F=C+G < 2²⁶ */
mul_25519(P3->X, P3->T, A); /* X3=E*F < 2²⁶ */
mul_25519(P3->Y, P3->Z, D); /* Y3=G*H < 2²⁶ */
mul_25519(P3->T, P3->T, D); /* T3=E*H < 2²⁶ */
mul_25519(P3->Z, A, P3->Z); /* Z3=F*G < 2²⁶ */
}
#if 0
static void print_point_le8(const Point *p)
{
uint8_t bin[32];
unsigned i;
uint32_t tmp[32];
uint32_t invz[32];
invert_25519(invz, p->Z);
mul_25519(tmp, p->X, invz);
convert_le25p5_to_le8(bin, tmp);
printf("X=");
for (i=0; i<32; i++)
printf("%02X", bin[i]);
printf("\n");
invert_25519(invz, p->Z);
mul_25519(tmp, p->Y, invz);
convert_le25p5_to_le8(bin, tmp);
printf("Y=");
for (i=0; i<32; i++)
printf("%02X", bin[i]);
printf("\n");
}
#endif
/*
* Scalar multiplication Q = k*B
*
* @param[out] xout The X-coordinate of the resulting point Q.
* @param[out] yout The Y-coordinate of the resulting point Q.
* @param[in] k The scalar encoded in little-endian mode.
* @param[in] len Length of the scalar in bytes.
* @param[in] xin The X-coordinate of the input point B.
* @param[in] yin The Y-coordinate of the input point B.
*/
STATIC void ed25519_scalar_internal(Point *Pout,
const uint8_t *k, size_t len,
const Point *Pin)
{
Point R0, R1;
unsigned bit_idx, swap;
size_t scan;
/* Point R0 */
memset(&R0, 0, sizeof R0);
R0.Y[0] = R0.Z[0] = 1;
/* Point R1 */
R1 = *Pin;
/* https://eprint.iacr.org/2020/956.pdf */
/* OPTIMIZE: with pre-computed tables in case of fixed-point multiplication */
/* Scan all bits from MSB to LSB */
bit_idx = 7;
swap = 0;
scan = 0;
while (scan<len) {
unsigned bit;
bit = (k[scan] >> bit_idx) & 1;
swap ^= bit;
cswap(R0.X, R0.Y, R1.X, R1.Y, swap);
cswap(R0.Z, R0.T, R1.Z, R1.T, swap);
ed25519_add_internal(&R1, &R0, &R1); /* R1 <-- R0 + R1 */
ed25519_double_internal(&R0, &R0); /* R0 <-- 2R0 */
swap = bit;
if (bit_idx-- == 0) {
bit_idx = 7;
scan++;
}
}
cswap(R0.X, R0.Y, R1.X, R1.Y, swap);
cswap(R0.Z, R0.T, R1.Z, R1.T, swap);
*Pout = R0;
}
/* ---- */
int ed25519_new_point(Point **out,
uint8_t x[32], uint8_t y[32],
size_t modsize, void *context)
{
uint32_t A[10], B[10], C[10];
const char d[] = "52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3";
if ((NULL == out) || (NULL == x) || (NULL == y))
return ERR_NULL;
if (modsize != 32)
return ERR_MODULUS;
*out = calloc(1, sizeof(Point));
if (NULL == *out)
return ERR_MEMORY;
convert_be8_to_le25p5((*out)->X, x);
convert_be8_to_le25p5((*out)->Y, y);
(*out)->Z[0] = 1;
mul_25519((*out)->T, (*out)->X, (*out)->Y);
/** Verify that the point is on the Ed25519 curve **/
mul_25519(A, (*out)->X, (*out)->X); /* x² */
mul_25519(B, (*out)->Y, (*out)->Y); /* y² */
convert_behex_to_le25p5(C, d); /* d */
mul_25519(C, C, B); /* dy² */
mul_25519(C, C, A); /* dx²y² */
add_25519(C, C, A); /* dx²y² - ax² */
memset(A, 0, sizeof A);
A[0] = 1;
add_25519(C, C, A); /* 1 + dx²y² - ax² */
reduce_25519_le25p5(B);
reduce_25519_le25p5(C);
if (0 != memcmp(B, C, sizeof B)) {
free(*out);
*out = NULL;
return ERR_EC_POINT;
}
return 0;
}
int ed25519_clone(Point **P, const Point *Q)
{
if ((NULL == P) || (NULL == Q))
return ERR_NULL;
*P = calloc(1, sizeof(Point));
if (NULL == *P)
return ERR_MEMORY;
**P = *Q;
return 0;
}
void ed25519_free_point(Point *p)
{
if (p)
free(p);
}
int ed25519_cmp(const Point *p1, const Point *p2)
{
uint32_t tmp[10];
uint8_t bin1[32], bin2[32];
int res = 0;
mul_25519(tmp, p1->X, p2->Z);
convert_le25p5_to_le8(bin1, tmp);
mul_25519(tmp, p2->X, p1->Z);
convert_le25p5_to_le8(bin2, tmp);
for (unsigned i=0; i<sizeof bin1; i++) {
res |= bin1[i] != bin2[i];
}
mul_25519(tmp, p1->Y, p2->Z);
convert_le25p5_to_le8(bin1, tmp);
mul_25519(tmp, p2->Y, p1->Z);
convert_le25p5_to_le8(bin2, tmp);
for (unsigned i=0; i<sizeof bin1; i++) {
res |= bin1[i] != bin2[i];
}
return res;
}
int ed25519_neg(Point *p)
{
const uint32_t zero[10] = { 0 };
sub32(p->X, zero, p->X);
reduce_25519_le25p5(p->X);
return 0;
}
int ed25519_get_xy(uint8_t *xb, uint8_t *yb, size_t modsize, Point *p)
{
uint32_t invz[10], tmp[10];
if ((NULL == xb) || (NULL == yb) || (NULL == p))
return ERR_NULL;
if (modsize != 32)
return ERR_MODULUS;
invert_25519(invz, p->Z);
mul_25519(tmp, p->X, invz);
convert_le25p5_to_be8(xb, tmp);
mul_25519(tmp, p->Y, invz);
convert_le25p5_to_be8(yb, tmp);
return 0;
}
int ed25519_double(Point *p)
{
if (NULL == p)
return ERR_NULL;
ed25519_double_internal(p, p);
return 0;
}
int ed25519_add(Point *P1, const Point *P2)
{
if ((NULL == P1) || (NULL == P2))
return ERR_NULL;
ed25519_add_internal(P1, P1, P2);
return 0;
}
int ed25519_scalar(Point *P, uint8_t *scalar, size_t scalar_len, uint64_t seed)
{
if ((NULL == P) || (NULL == scalar))
return ERR_NULL;
ed25519_scalar_internal(P, scalar, scalar_len, P);
return 0;
}

111
src/make_p256_table.c Normal file
View file

@ -0,0 +1,111 @@
#include "common.h"
#include "ec.h"
#include "endianess.h"
#define BITS 256
#define BYTES BITS/8
#define WORDS BITS/64
static void print_64bit_array(uint64_t *x, unsigned len)
{
unsigned i, j;
for (i=0; i<len; i++) {
printf("0x");
for (j=0; j<8; j++) {
printf("%02X", (uint8_t)(x[i] >> ((7-j)*8)));
}
printf("ULL");
if (i!=(len-1))
printf(",");
}
}
int main(void)
{
const uint8_t p256_mod[32] = "\xff\xff\xff\xff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
const uint8_t b[32] = "\x5a\xc6\x35\xd8\xaa\x3a\x93\xe7\xb3\xeb\xbd\x55\x76\x98\x86\xbc\x65\x1d\x06\xb0\xcc\x53\xb0\xf6\x3b\xce\x3c\x3e\x27\xd2\x60\x4b";
const uint8_t order[32] = "\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xbc\xe6\xfa\xad\xa7\x17\x9e\x84\xf3\xb9\xca\xc2\xfc\x63\x25\x51";
const uint8_t p256_Gx[32] = "\x6b\x17\xd1\xf2\xe1\x2c\x42\x47\xf8\xbc\xe6\xe5\x63\xa4\x40\xf2\x77\x03\x7d\x81\x2d\xeb\x33\xa0\xf4\xa1\x39\x45\xd8\x98\xc2\x96";
const uint8_t p256_Gy[32] = "\x4f\xe3\x42\xe2\xfe\x1a\x7f\x9b\x8e\xe7\xeb\x4a\x7c\x0f\x9e\x16\x2b\xce\x33\x57\x6b\x31\x5e\xce\xcb\xb6\x40\x68\x37\xbf\x51\xf5";
uint8_t xz[32] = { 0 }, yz[32] = { 0 };
EcContext *ec_ctx;
EcPoint *g = NULL;
EcPoint **window = NULL;
int i, j;
unsigned n_tables, points_per_table, window_size;
ec_ws_new_context(&ec_ctx, p256_mod, b, order, 32, 0);
ec_ws_new_point(&g, p256_Gx, p256_Gy, 32, ec_ctx);
/** TODO: accept this as input **/
window_size = 5;
points_per_table = 1U << window_size;
n_tables = (256+window_size-1)/window_size;
/** Create table with points 0, G, 2G, 3G, .. (2**window_size-1)G **/
window = (EcPoint**)calloc(points_per_table, sizeof(EcPoint*));
ec_ws_new_point(&window[0], xz, yz, 32, ec_ctx);
for (i=1; i<points_per_table; i++) {
ec_ws_clone(&window[i], window[i-1]);
ec_ws_add(window[i], g);
}
printf("/* This file was automatically generated, do not edit */\n");
printf("#include \"common.h\"\n");
printf("#include \"p256_table.h\"\n");
printf("const unsigned p256_n_tables = %d;\n", n_tables);
printf("const unsigned p256_window_size = %d;\n", window_size);
printf("const unsigned p256_points_per_table = %d;\n", points_per_table);
printf("/* Affine coordinates in Montgomery form */\n");
printf("/* Table size: %u kbytes */\n", (unsigned)(n_tables*points_per_table*2*WORDS*sizeof(uint64_t)));
printf("const uint64_t p256_tables[%d][%d][2][4] = {\n", n_tables, points_per_table);
for (i=0; i<n_tables; i++) {
printf(" { /* Table #%u */\n", i);
for (j=0; j<points_per_table; j++) {
uint64_t xw[4], yw[4];
if (j == 0) {
memcpy(xw, xz, sizeof xw);
memcpy(yw, yz, sizeof yw);
} else {
ec_ws_normalize(window[j]);
memcpy(xw, window[j]->x, sizeof xw);
memcpy(yw, window[j]->y, sizeof yw);
}
printf(" { /* Point #%d */\n", j);
printf(" { ");
print_64bit_array(xw, 4);
printf(" },\n");
printf(" { ");
print_64bit_array(yw, 4);
printf(" }\n");
printf(" }%s\n", j==points_per_table-1 ? "" : ",");
}
printf(" }%s\n", i==n_tables-1 ? "" : ",");
/* Move from G to G*2^{w} */
for (j=0; j<window_size; j++)
ec_ws_double(g);
for (j=1; j<points_per_table; j++) {
ec_ws_copy(window[j], window[j-1]);
ec_ws_add(window[j], g);
}
}
printf("};\n");
for (i=0; i<points_per_table; i++) {
ec_ws_free_point(window[i]);
}
free(window);
ec_ws_free_point(g);
ec_free_context(ec_ctx);
return 0;
}

111
src/make_p384_table.c Normal file
View file

@ -0,0 +1,111 @@
#include "common.h"
#include "ec.h"
#include "endianess.h"
#define BITS 384
#define BYTES BITS/8
#define WORDS BITS/64
static void print_64bit_array(uint64_t *x, unsigned len)
{
unsigned i, j;
for (i=0; i<len; i++) {
printf("0x");
for (j=0; j<8; j++) {
printf("%02X", (uint8_t)(x[i] >> ((7-j)*8)));
}
printf("ULL");
if (i!=(len-1))
printf(",");
}
}
int main(void)
{
const uint8_t p384_mod[BYTES] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff";
const uint8_t b[BYTES] = "\xb3\x31\x2f\xa7\xe2\x3e\xe7\xe4\x98\x8e\x05\x6b\xe3\xf8\x2d\x19\x18\x1d\x9c\x6e\xfe\x81\x41\x12\x03\x14\x08\x8f\x50\x13\x87\x5a\xc6\x56\x39\x8d\x8a\x2e\xd1\x9d\x2a\x85\xc8\xed\xd3\xec\x2a\xef";
const uint8_t order[BYTES] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc7\x63\x4d\x81\xf4\x37\x2d\xdf\x58\x1a\x0d\xb2\x48\xb0\xa7\x7a\xec\xec\x19\x6a\xcc\xc5\x29\x73";
const uint8_t p384_Gx[BYTES] = "\xaa\x87\xca\x22\xbe\x8b\x05\x37\x8e\xb1\xc7\x1e\xf3\x20\xad\x74\x6e\x1d\x3b\x62\x8b\xa7\x9b\x98\x59\xf7\x41\xe0\x82\x54\x2a\x38\x55\x02\xf2\x5d\xbf\x55\x29\x6c\x3a\x54\x5e\x38\x72\x76\x0a\xb7";
const uint8_t p384_Gy[BYTES] = "\x36\x17\xde\x4a\x96\x26\x2c\x6f\x5d\x9e\x98\xbf\x92\x92\xdc\x29\xf8\xf4\x1d\xbd\x28\x9a\x14\x7c\xe9\xda\x31\x13\xb5\xf0\xb8\xc0\x0a\x60\xb1\xce\x1d\x7e\x81\x9d\x7a\x43\x1d\x7c\x90\xea\x0e\x5f";
uint8_t xz[BYTES] = { 0 }, yz[BYTES] = { 0 };
EcContext *ec_ctx;
EcPoint *g = NULL;
EcPoint **window = NULL;
int i, j;
unsigned n_tables, points_per_table, window_size;
ec_ws_new_context(&ec_ctx, p384_mod, b, order, BYTES, 0);
ec_ws_new_point(&g, p384_Gx, p384_Gy, BYTES, ec_ctx);
/** TODO: accept this as input **/
window_size = 5;
points_per_table = 1U << window_size;
n_tables = (BITS+window_size-1)/window_size;
/** Create table with points 0, G, 2G, 3G, .. (2**window_size-1)G **/
window = (EcPoint**)calloc(points_per_table, sizeof(EcPoint*));
ec_ws_new_point(&window[0], xz, yz, BYTES, ec_ctx);
for (i=1; i<points_per_table; i++) {
ec_ws_clone(&window[i], window[i-1]);
ec_ws_add(window[i], g);
}
printf("/* This file was automatically generated, do not edit */\n");
printf("#include \"common.h\"\n");
printf("#include \"p384_table.h\"\n");
printf("const unsigned p384_n_tables = %d;\n", n_tables);
printf("const unsigned p384_window_size = %d;\n", window_size);
printf("const unsigned p384_points_per_table = %d;\n", points_per_table);
printf("/* Affine coordinates in Montgomery form */\n");
printf("/* Table size: %u kbytes */\n", (unsigned)(n_tables*points_per_table*2*WORDS*sizeof(uint64_t)));
printf("const uint64_t p384_tables[%d][%d][2][%d] = {\n", n_tables, points_per_table, WORDS);
for (i=0; i<n_tables; i++) {
printf(" { /* Table #%u */\n", i);
for (j=0; j<points_per_table; j++) {
uint64_t xw[WORDS], yw[WORDS];
if (j == 0) {
memcpy(xw, xz, sizeof xw);
memcpy(yw, yz, sizeof yw);
} else {
ec_ws_normalize(window[j]);
memcpy(xw, window[j]->x, sizeof xw);
memcpy(yw, window[j]->y, sizeof yw);
}
printf(" { /* Point #%d */\n", j);
printf(" { ");
print_64bit_array(xw, 6);
printf(" },\n");
printf(" { ");
print_64bit_array(yw, 6);
printf(" }\n");
printf(" }%s\n", j==points_per_table-1 ? "" : ",");
}
printf(" }%s\n", i==n_tables-1 ? "" : ",");
/* Move from G to G*2^{w} */
for (j=0; j<window_size; j++)
ec_ws_double(g);
for (j=1; j<points_per_table; j++) {
ec_ws_copy(window[j], window[j-1]);
ec_ws_add(window[j], g);
}
}
printf("};\n");
for (i=0; i<points_per_table; i++) {
ec_ws_free_point(window[i]);
}
free(window);
ec_ws_free_point(g);
ec_free_context(ec_ctx);
return 0;
}

111
src/make_p521_table.c Normal file
View file

@ -0,0 +1,111 @@
#include "common.h"
#include "ec.h"
#include "endianess.h"
#define BITS 521
#define BYTES 66
#define WORDS 9
static void print_64bit_array(uint64_t *x, unsigned len)
{
unsigned i, j;
for (i=0; i<len; i++) {
printf("0x");
for (j=0; j<8; j++) {
printf("%02X", (uint8_t)(x[i] >> ((7-j)*8)));
}
printf("ULL");
if (i!=(len-1))
printf(",");
}
}
int main(void)
{
const uint8_t p521_mod[BYTES] = "\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
const uint8_t b[BYTES] = "\x00\x51\x95\x3e\xb9\x61\x8e\x1c\x9a\x1f\x92\x9a\x21\xa0\xb6\x85\x40\xee\xa2\xda\x72\x5b\x99\xb3\x15\xf3\xb8\xb4\x89\x91\x8e\xf1\x09\xe1\x56\x19\x39\x51\xec\x7e\x93\x7b\x16\x52\xc0\xbd\x3b\xb1\xbf\x07\x35\x73\xdf\x88\x3d\x2c\x34\xf1\xef\x45\x1f\xd4\x6b\x50\x3f\x00";
const uint8_t order[BYTES] = "\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfa\x51\x86\x87\x83\xbf\x2f\x96\x6b\x7f\xcc\x01\x48\xf7\x09\xa5\xd0\x3b\xb5\xc9\xb8\x89\x9c\x47\xae\xbb\x6f\xb7\x1e\x91\x38\x64\x09";
const uint8_t p521_Gx[BYTES] = "\x00\xc6\x85\x8e\x06\xb7\x04\x04\xe9\xcd\x9e\x3e\xcb\x66\x23\x95\xb4\x42\x9c\x64\x81\x39\x05\x3f\xb5\x21\xf8\x28\xaf\x60\x6b\x4d\x3d\xba\xa1\x4b\x5e\x77\xef\xe7\x59\x28\xfe\x1d\xc1\x27\xa2\xff\xa8\xde\x33\x48\xb3\xc1\x85\x6a\x42\x9b\xf9\x7e\x7e\x31\xc2\xe5\xbd\x66";
const uint8_t p521_Gy[BYTES] = "\x01\x18\x39\x29\x6a\x78\x9a\x3b\xc0\x04\x5c\x8a\x5f\xb4\x2c\x7d\x1b\xd9\x98\xf5\x44\x49\x57\x9b\x44\x68\x17\xaf\xbd\x17\x27\x3e\x66\x2c\x97\xee\x72\x99\x5e\xf4\x26\x40\xc5\x50\xb9\x01\x3f\xad\x07\x61\x35\x3c\x70\x86\xa2\x72\xc2\x40\x88\xbe\x94\x76\x9f\xd1\x66\x50";
uint8_t xz[WORDS*8] = { 0 }, yz[WORDS*8] = { 0 };
EcContext *ec_ctx;
EcPoint *g = NULL;
EcPoint **window = NULL;
int i, j;
unsigned n_tables, points_per_table, window_size;
ec_ws_new_context(&ec_ctx, p521_mod, b, order, BYTES, 0);
ec_ws_new_point(&g, p521_Gx, p521_Gy, BYTES, ec_ctx);
/** TODO: accept this as input **/
window_size = 4;
points_per_table = 1U << window_size;
n_tables = (BITS+window_size-1)/window_size;
/** Create table with points 0, G, 2G, 3G, .. (2**window_size-1)G **/
window = (EcPoint**)calloc(points_per_table, sizeof(EcPoint*));
ec_ws_new_point(&window[0], xz, yz, sizeof xz, ec_ctx);
for (i=1; i<points_per_table; i++) {
ec_ws_clone(&window[i], window[i-1]);
ec_ws_add(window[i], g);
}
printf("/* This file was automatically generated, do not edit */\n");
printf("#include \"common.h\"\n");
printf("#include \"p521_table.h\"\n");
printf("const unsigned p521_n_tables = %d;\n", n_tables);
printf("const unsigned p521_window_size = %d;\n", window_size);
printf("const unsigned p521_points_per_table = %d;\n", points_per_table);
printf("/* Affine coordinates in plain form (not Montgomery) */\n");
printf("/* Table size: %u kbytes */\n", (unsigned)(n_tables*points_per_table*2*WORDS*sizeof(uint64_t)));
printf("const uint64_t p521_tables[%d][%d][2][%d] = {\n", n_tables, points_per_table, WORDS);
for (i=0; i<n_tables; i++) {
printf(" { /* Table #%u */\n", i);
for (j=0; j<points_per_table; j++) {
uint64_t xw[WORDS], yw[WORDS];
if (j == 0) {
memcpy(xw, xz, sizeof xw);
memcpy(yw, yz, sizeof yw);
} else {
ec_ws_normalize(window[j]);
memcpy(xw, window[j]->x, sizeof xw);
memcpy(yw, window[j]->y, sizeof yw);
}
printf(" { /* Point #%d */\n", j);
printf(" { ");
print_64bit_array(xw, WORDS);
printf(" },\n");
printf(" { ");
print_64bit_array(yw, WORDS);
printf(" }\n");
printf(" }%s\n", j==points_per_table-1 ? "" : ",");
}
printf(" }%s\n", i==n_tables-1 ? "" : ",");
/* Move from G to G*2^{w} */
for (j=0; j<window_size; j++)
ec_ws_double(g);
for (j=1; j<points_per_table; j++) {
ec_ws_copy(window[j], window[j-1]);
ec_ws_add(window[j], g);
}
}
printf("};\n");
for (i=0; i<points_per_table; i++) {
ec_ws_free_point(window[i]);
}
free(window);
ec_ws_free_point(g);
ec_free_context(ec_ctx);
return 0;
}

581
src/mod25519.c Normal file
View file

@ -0,0 +1,581 @@
#ifndef __MOD25519_C
#define __MOD25519_C
#include "common.h"
#include "endianess.h"
#include "bignum.c"
/*
* Operations over the field of integers modulo 2² - 19
*/
#if defined(MASK26) || defined(MASK25) || defined(MASK13) || defined(MASK12)
#error Necessary macros are already defined
#endif
#define MASK26 ((1ULL<<26)-1)
#define MASK25 ((1ULL<<25)-1)
#define MASK13 ((1ULL<<13)-1)
#define MASK12 ((1ULL<<12)-1)
STATIC void reduce_25519_le64(uint64_t x[4]);
/*
* Convert a 256-bit number in radix 2 and little-endian mode,
* to mixed radix 2²/2², also little-endian.
*/
STATIC void convert_le64_to_le25p5(uint32_t out[10], const uint64_t in[4])
{
/** MSB of in[3] will be ignored */
out[0] = in[0] & MASK26; /* Fill 26 bits: 38 bits left in in[0] */
out[1] = (in[0] >> 26) & MASK25; /* Fill 25 bits: 13 bits left in in[0] */
out[2] = (uint32_t)((in[0] >> 51) | (in[1] & MASK13) << 13); /* Fill 26 bits: 51 bits left in in[1] */
out[3] = (in[1] >> 13) & MASK25; /* Fill 25 bits: 26 bits left in in[1] */
out[4] = (uint32_t)(in[1] >> 38); /* Fill 26 bits: no bits left in in[1] */
out[5] = in[2] & MASK25; /* Fill 25 bits: 39 bits left in in[2] */
out[6] = (in[2] >> 25) & MASK26; /* Fill 26 bits: 13 bits left in in[2] */
out[7] = (uint32_t)((in[2] >> 51) | (in[3] & MASK12) << 13); /* Fill 25 bits: 52 bits left in in[3] */
out[8] = (in[3] >> 12) & MASK26; /* Fill 26 bits: 26 bits left in in[3] */
out[9] = (uint32_t)(in[3] >> 38); /* Fill 26 bits in theory, 25 in practice */
}
/*
* Convert a 256-bit number in mixed radix 2²/2² and little-endian mode,
* to radix 2, also little-endian mode.
*
* The input limbs must fulfill the conditions:
* in[i] < 2² for even i (0, 2, 4, etc)
* in[j] < 2² for odd j<9 (1, 3, 5, etc)
* in[9] < 2²
*/
STATIC void convert_le25p5_to_le64(uint64_t out[4], const uint32_t in[10])
{
/** We assume that the 6 or 7 upper bits of in[] words is set to 0 */
assert(in[0] >> 26 == 0);
assert(in[1] >> 25 == 0);
assert(in[2] >> 26 == 0);
assert(in[3] >> 25 == 0);
assert(in[4] >> 26 == 0);
assert(in[5] >> 25 == 0);
assert(in[6] >> 26 == 0);
assert(in[7] >> 25 == 0);
assert(in[8] >> 26 == 0);
assert(in[9] >> 26 == 0);
out[0] = in[0] | (uint64_t)in[1] << 26 | ((uint64_t)in[2] & MASK13) << 51; /* 64 = 26 + 25 + 13: 13 bits left in in[2] */
out[1] = in[2] >> 13 | (uint64_t)in[3] << 13 | (uint64_t)in[4] << 38; /* 64 = 13 + 25 + 26: no bits left in[4] */
out[2] = in[5] | (uint64_t)in[6] << 25 | ((uint64_t)in[7] & MASK13) << 51; /* 64 = 25 + 26 + 13: 12 bits left in in[7] */
out[3] = in[7] >> 13 | (uint64_t)in[8] << 12 | (uint64_t)in[9] << 38; /* 64 = 12 + 26 + 26(!) */
}
/*
* Deserialize a 255-bit little-endian integer
* to mixed radix 2²/2², also little-endian.
*/
STATIC void convert_le8_to_le25p5(uint32_t out[10], const uint8_t in[32])
{
uint64_t in64[4];
unsigned i;
for (i=0; i<4; i++) {
in64[i] = LOAD_U64_LITTLE(&in[i*8]);
}
convert_le64_to_le25p5(out, in64);
}
/*
* Serialize a 256-bit mixed-radix 2²/2² little-endian integer
* into a little-endian byte string.
*
* The input limbs must fulfill the conditions:
* in[i] < 2² for even i (0, 2, 4, etc)
* in[j] < 2² for odd j<9 (1, 3, 5, etc)
* in[9] < 2²
*/
STATIC void convert_le25p5_to_le8(uint8_t out[32], const uint32_t in[10])
{
uint64_t out64[4];
unsigned i;
convert_le25p5_to_le64(out64, in);
reduce_25519_le64(out64);
for (i=0; i<4; i++) {
STORE_U64_LITTLE(&out[i*8], out64[i]);
}
}
/*
* Deserialize a 255-bit big-endian integer
* to mixed radix 2²/2² little-endian.
*/
STATIC void convert_be8_to_le25p5(uint32_t out[10], const uint8_t in[32])
{
uint64_t in64[4];
unsigned i;
for (i=0; i<4; i++) {
in64[3-i] = LOAD_U64_BIG(&in[i*8]);
}
convert_le64_to_le25p5(out, in64);
}
/*
* Serialize a 256-bit mixed-radix 2²/2² little-endian integer
* into a big-endian byte string.
*
* The input limbs must fulfill the conditions:
* in[i] < 2² for even i (0, 2, 4, etc)
* in[j] < 2² for odd j<9 (1, 3, 5, etc)
* in[9] < 2²
*/
STATIC void convert_le25p5_to_be8(uint8_t out[32], const uint32_t in[10])
{
uint64_t out64[4];
unsigned i;
convert_le25p5_to_le64(out64, in);
reduce_25519_le64(out64);
for (i=0; i<4; i++) {
STORE_U64_BIG(&out[i*8], out64[3-i]);
}
}
static int hex2bin(char in)
{
if ((in >= '0') && (in <= '9'))
return in - '0';
if ((in >= 'A') && (in <= 'F'))
return in - 'A' + 10;
if ((in >= 'a') && (in <= 'f'))
return in - 'a' + 10;
return -1;
}
static char bin2hex(uint8_t b)
{
if (b < 10)
return '0' + b;
return 'a' + b - 10;
}
/*
* Convert a big-endian hexadecimal number (up to 256 bits, and as a zero-terminated ASCII string)
* to mixed radix 2²/2², also little-endian.
*/
STATIC int convert_behex_to_le25p5(uint32_t out[10], const char *in)
{
size_t len;
uint8_t bin[32];
unsigned i;
if (in == NULL)
return ERR_NULL;
len = strlen(in);
if (len > 64)
return ERR_MAX_DATA;
if (len & 1)
return ERR_BLOCK_SIZE;
memset(bin, 0, sizeof bin);
for (i=0; i<len; i+=2) {
int c1, c2;
c1 = hex2bin(in[len-1-i]);
c2 = hex2bin(in[len-1-i-1]);
if ((c1 < 0) || (c2 < 0))
return ERR_VALUE;
bin[i/2] = c2 * 16 + c1;
}
convert_le8_to_le25p5(out, bin);
return 0;
}
/*
* Convert a number in mixed radix 2²/2², little-endian form
* into a new ASCII hex, zero-terminated string. The caller
* is responsible for deallocating the string.
*/
STATIC int convert_le25p5_to_behex(char **out, uint32_t in[10])
{
uint8_t bin[32];
unsigned i;
if (NULL == out)
return ERR_NULL;
convert_le25p5_to_le8(bin, in);
*out = calloc(64 + 1, 1);
for (i=0; i<32; i++) {
*(*out+64-1-i*2) = bin2hex(bin[i] & 0xF);
*(*out+64-2-i*2) = bin2hex(bin[i] >> 4);
}
return 0;
}
#if 0
static void print_le25p5(char *str, const uint32_t n[10])
{
uint8_t bin[32];
convert_le25p5_to_le8(bin, n);
printf("%s", str);
for (unsigned i=0; i<32; i++) {
printf("%02X", bin[i]);
}
printf("\n");
}
#endif
/*
* Reduce a 256-bit number (2 radix, litte-endian) modulo 2² - 19.
*/
STATIC void reduce_25519_le64(uint64_t x[4])
{
unsigned borrow;
uint64_t tmp1[4], tmp2[4];
static const uint64_t modulus[4] = { 0xffffffffffffffedULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0x7fffffffffffffffULL };
borrow = sub(tmp1, x, modulus, 4);
mod_select(tmp2, x, tmp1, borrow, 4);
borrow = sub(tmp1, tmp2, modulus, 4);
mod_select(x, tmp2, tmp1, borrow, 4);
}
/*
* Multiply f[] and g[] modulo 2² - 19.
*
* The inputs f[] and g[] are encoded in mixed radix 2²/2² with limbs < 2².
*
* The result out[] is encoded in also mixed radix 2²/2² such that:
* f[i] < 2² for even i
* f[j] < 2² for odd j<9
* f[9] < 2²
*/
STATIC void mul_25519(uint32_t out[10], const uint32_t f[10], const uint32_t g[10])
{
uint64_t h0, h1, h2, h3, h4, h5, h6, h7, h8, h9;
uint64_t f0, f1, f2, f3, f4, f5, f6, f7, f8, f9;
uint64_t f1_38, f2_19, f3_19, f4_19, f5_19, f6_19, f7_19, f8_19, f9_19;
uint64_t g0, g1, g2, g3, g4, g5, g6, g7, g8, g9;
uint64_t carry;
f0 = f[0]; f1 = f[1]; f2 = f[2]; f3 = f[3]; f4 = f[4]; f5 = f[5]; f6 = f[6]; f7 = f[7]; f8 = f[8]; f9 = f[9];
g0 = g[0]; g1 = g[1]; g2 = g[2]; g3 = g[3]; g4 = g[4]; g5 = g[5]; g6 = g[6]; g7 = g[7]; g8 = g[8]; g9 = g[9];
f1_38 = (uint64_t)38*f[1];
f2_19 = (uint64_t)19*f[2];
f3_19 = (uint64_t)19*f[3];
f4_19 = (uint64_t)19*f[4];
f5_19 = (uint64_t)19*f[5];
f6_19 = (uint64_t)19*f[6];
f7_19 = (uint64_t)19*f[7];
f8_19 = (uint64_t)19*f[8];
f9_19 = (uint64_t)19*f[9];
/** input terms can the result of at most 4 additions **/
h0 = f0*g0 + f1_38*g9 + f2_19*g8 + 2*f3_19*g7 + f4_19*g6 +
2*f5_19*g5 + f6_19*g4 + 2*f7_19*g3 + f8_19*g2 + 2*f9_19*g1;
h1 = f0*g1 + f1*g0 + f2_19*g9 + f3_19*g8 + f4_19*g7 +
f5_19*g6 + f6_19*g5 + f7_19*g4 + f8_19*g3 + f9_19*g2;
h2 = f0*g2 + 2*f1*g1 + f2*g0 + 2*f3_19*g9 + f4_19*g8 +
2*f5_19*g7 + f6_19*g6 + 2*f7_19*g5 + f8_19*g4 + 2*f9_19*g3;
h3 = f0*g3 + f1*g2 + f2*g1 + f3*g0 + f4_19*g9 +
f5_19*g8 + f6_19*g7 + f7_19*g6 + f8_19*g5 + f9_19*g4;
h4 = f0*g4 + 2*f1*g3 + f2*g2 + 2*f3*g1 + f4*g0 +
2*f5_19*g9 + f6_19*g8 + 2*f7_19*g7 + f8_19*g6 + 2*f9_19*g5;
h5 = f0*g5 + f1*g4 + f2*g3 + f3*g2 + f4*g1 +
f5*g0 + f6_19*g9 + f7_19*g8 + f8_19*g7 + f9_19*g6;
h6 = f0*g6 + 2*f1*g5 + f2*g4 + 2*f3*g3 + f4*g2 +
2*f5*g1 + f6*g0 + 2*f7_19*g9 + f8_19*g8 + 2*f9_19*g7;
h7 = f0*g7 + f1*g6 + f2*g5 + f3*g4 + f4*g3 +
f5*g2 + f6*g1 + f7*g0 + f8_19*g9 + f9_19*g8;
h8 = f0*g8 + 2*f1*g7 + f2*g6 + 2*f3*g5 + f4*g4 +
2*f5*g3 + f6*g2 + 2*f7*g1 + f8*g0 + 2*f9_19*g9;
h9 = f0*g9 + f1*g8 + f2*g7 + f3*g6 + f4*g5 +
f5*g4 + f6*g3 + f7*g2 + f8*g1 + f9*g0;
/* h0..h9 < 2⁶³ */
carry = h8 >> 26;
h8 &= MASK26;
/* carry < 2³⁷ */
h9 += carry;
carry = (h9 >> 25)*19;
h9 &= MASK25;
/* carry < 2⁴⁴ */
h0 += carry;
carry = h0 >> 26;
h0 &= MASK26;
/* carry < 2³⁸ */
h1 += carry;
carry = h1 >> 25;
h1 &= MASK25;
/* carry < 2³⁹ */
h2 += carry;
carry = h2 >> 26;
h2 &= MASK26;
/* carry < 2³⁸ */
h3 += carry;
carry = h3 >> 25;
h3 &= MASK25;
/* carry < 2³⁹ */
h4 += carry;
carry = h4 >> 26;
h4 &= MASK26;
/* carry < 2³⁸ */
h5 += carry;
carry = h5 >> 25;
h5 &= MASK25;
/* carry < 2³⁹ */
h6 += carry;
carry = h6 >> 26;
h6 &= MASK26;
/* carry < 2³⁸ */
h7 += carry;
carry = h7 >> 25;
h7 &= MASK25;
/* carry < 2³⁹ */
h8 += carry;
carry = h8 >> 26;
h8 &= MASK26;
/* carry < 2¹⁴ */
h9 += carry;
/* h9 < 2²⁶ */
out[0] = (uint32_t)h0;
out[1] = (uint32_t)h1;
out[2] = (uint32_t)h2;
out[3] = (uint32_t)h3;
out[4] = (uint32_t)h4;
out[5] = (uint32_t)h5;
out[6] = (uint32_t)h6;
out[7] = (uint32_t)h7;
out[8] = (uint32_t)h8;
out[9] = (uint32_t)h9;
}
/*
* Compute addition for mixed-radix 2²/2².
* If the biggest input limb fits into x bits (x<32), the biggest output limb will fit into (x+1) bits.
*/
STATIC void add32(uint32_t out[10], const uint32_t a[10], const uint32_t b[10])
{
unsigned i;
for (i=0; i<10; i++) {
out[i] = a[i] + b[i];
}
}
/*
* Carry out subtraction a[] - b[] for mixed-radix 2²/2² where all limbs are < 2².
* Output limbs are < 2².
*/
STATIC void sub32(uint32_t out[10], const uint32_t a[10], const uint32_t b[10])
{
/*
* We pre-sum a number which is >= 2²-1 for each limb, and which is congruent to zero modulo 2²-19
*/
static const uint32_t modulus_32[10] = { 0x7ffffda, 0x3fffffe, 0x7fffffe, 0x3fffffe, 0x7fffffe, 0x3fffffe, 0x7fffffe, 0x3fffffe, 0x7fffffe, 0x3fffffe };
unsigned i;
for (i=0; i<10; i++) {
out[i] = modulus_32[i] + a[i] - b[i];
}
}
/*
* Swap arguments a/c and b/d when condition is NOT ZERO.
* If the condition IS ZERO, no swapping takes place.
*/
STATIC void cswap(uint32_t a[10], uint32_t b[10], uint32_t c[10], uint32_t d[10], unsigned swap)
{
uint32_t mask, i, e, f;
mask = (uint32_t)(0 - (swap!=0)); /* 0 if swap is 0, all 1s if swap is !=0 */
for (i=0; i<10; i++) {
e = mask & (a[i] ^ c[i]);
a[i] ^= e;
c[i] ^= e;
f = mask & (b[i] ^ d[i]);
b[i] ^= f;
d[i] ^= f;
}
}
/*
* Compute x¹ in prime field 2² - 19
*
* The input x[] is encoded in mixed radix 2²/2² with limbs < 2².
*
* The result out[] is encoded in also mixed radix 2²/2² such that:
* out[i] < 2² for even i
* out[j] < 2² for odd j<9
* out[9] < 2²
*/
STATIC void invert_25519(uint32_t out[10], const uint32_t x[10])
{
uint32_t a[10], x1[10], x3p0[10], x3p1p0[10], x5m0[10];
uint32_t x10m0[10], x20m0[10], x50m0[10], x100m0[10];
unsigned i;
#define sqr_25519(a,b) mul_25519(a,b,b)
sqr_25519(x1, x); /* 2¹ */
sqr_25519(a, x1); /* 2² */
sqr_25519(a, a); /* 2³ */
mul_25519(x3p0, a, x); /* 2³ + 2⁰ */
mul_25519(x3p1p0, x3p0, x1);/* 2³ + 2¹ + 2⁰ = 11 */
sqr_25519(a, x3p1p0); /* 2⁴ + 2² + 2¹ */
mul_25519(x5m0, a, x3p0); /* 2⁴ + 2³ + 2² + 2¹ + 2⁰ = 2⁵ - 2⁰ */
sqr_25519(a, x5m0); /* 2⁶ - 2¹ */
sqr_25519(a, a); /* 2⁷ - 2² */
sqr_25519(a, a); /* 2⁸ - 2³ */
sqr_25519(a, a); /* 2⁹ - 2⁴ */
sqr_25519(a, a); /* 2¹⁰ - 2⁵ */
mul_25519(a, a, x5m0); /* 2¹⁰ - 2⁰ */
memcpy(x10m0, a, sizeof a);
for (i=0; i<10; i++) {
sqr_25519(a, a);
} /* 2²⁰ - 2¹⁰ */
mul_25519(a, a, x10m0); /* 2²⁰ - 2⁰ */
memcpy(x20m0, a, sizeof a);
for (i=0; i<20; i++) {
sqr_25519(a, a);
} /* 2⁴⁰ - 2²⁰ */
mul_25519(a, a, x20m0); /* 2⁴⁰ - 2⁰ */
for (i=0; i<10; i++) {
sqr_25519(a, a);
} /* 2⁵⁰ - 2¹⁰ */
mul_25519(a, a, x10m0); /* 2⁵⁰ - 2⁰ */
memcpy(x50m0, a, sizeof a);
for (i=0; i<50; i++) {
sqr_25519(a, a);
} /* 2¹⁰⁰ - 2⁵⁰ */
mul_25519(a, a, x50m0); /* 2¹⁰⁰ - 2⁰ */
memcpy(x100m0, a, sizeof a);
for (i=0; i<100; i++) {
sqr_25519(a, a);
} /* 2²⁰⁰ - 2¹⁰⁰ */
mul_25519(a, a, x100m0); /* 2²⁰⁰ - 2⁰ */
for (i=0; i<50; i++) {
sqr_25519(a, a);
} /* 2²⁵⁰ - 2⁵⁰ */
mul_25519(a, a, x50m0); /* 2²⁵⁰ - 2⁰ */
sqr_25519(a, a); /* 2²⁵¹ - 2¹ */
sqr_25519(a, a); /* 2²⁵² - 2² */
sqr_25519(a, a); /* 2²⁵³ - 2³ */
sqr_25519(a, a); /* 2²⁵⁴ - 2⁴ */
sqr_25519(a, a); /* 2²⁵⁵ - 2⁵ = 2²⁵⁵ - 32 */
mul_25519(out, a, x3p1p0); /* 2²⁵⁵ - 21 */
}
/*
* Add f[] and g[] modulo 2² - 19.
*
* f[] and g[] are encoded in radix 2²/2² and each limb is < 2².
*
* The result out[] is encoded in also radix 2²/2² such that:
* out[i] < 2² for even i
* out[j] < 2² for odd j<9
* out[9] < 2²
*/
STATIC void add_25519(uint32_t out[10], const uint32_t f[10], const uint32_t g[10])
{
uint64_t h0, h1, h2, h3, h4, h5, h6, h7, h8, h9;
uint64_t carry;
h0 = f[0] + g[0];
h1 = f[1] + g[1];
h2 = f[2] + g[2];
h3 = f[3] + g[3];
h4 = f[4] + g[4];
h5 = f[5] + g[5];
h6 = f[6] + g[6];
h7 = f[7] + g[7];
h8 = f[8] + g[8];
h9 = f[9] + g[9];
/* h0..h9 < 2²⁸ */
carry = h8 >> 26;
h8 &= MASK26;
/* carry < 2² */
h9 += carry;
carry = (h9 >> 25)*19;
h9 &= MASK25;
/* carry < 2⁹ */
h0 += carry;
carry = h0 >> 26;
h0 &= MASK26;
/* carry < 2³ */
h1 += carry;
carry = h1 >> 25;
h1 &= MASK25;
/* carry < 2⁴ */
h2 += carry;
carry = h2 >> 26;
h2 &= MASK26;
/* carry < 2³ */
h3 += carry;
carry = h3 >> 25;
h3 &= MASK25;
/* carry < 2⁴ */
h4 += carry;
carry = h4 >> 26;
h4 &= MASK26;
/* carry < 2³ */
h5 += carry;
carry = h5 >> 25;
h5 &= MASK25;
/* carry < 2⁴ */
h6 += carry;
carry = h6 >> 26;
h6 &= MASK26;
/* carry < 2³ */
h7 += carry;
carry = h7 >> 25;
h7 &= MASK25;
/* carry < 2⁴ */
h8 += carry;
carry = h8 >> 26;
h8 &= MASK26;
/* carry < 2¹ */
h9 += carry;
/* h9 < 2²⁶ */
out[0] = (uint32_t)h0;
out[1] = (uint32_t)h1;
out[2] = (uint32_t)h2;
out[3] = (uint32_t)h3;
out[4] = (uint32_t)h4;
out[5] = (uint32_t)h5;
out[6] = (uint32_t)h6;
out[7] = (uint32_t)h7;
out[8] = (uint32_t)h8;
out[9] = (uint32_t)h9;
}
/*
* Reduce a 256-bit number (mixed radix 2²/², litte-endian) modulo 2² - 19.
*
* Each limb of x[] in input is < 2² (for instance, as result of sub32).
*
* The result x[] in output is such that:
* x[i] < 2² for even i
* x[j] < 2² for odd j<9
* x[9] < 2²
*/
STATIC void reduce_25519_le25p5(uint32_t x[10])
{
uint32_t zero[10] = { 0 };
add_25519(x, x, zero);
assert((x[0] >> 26) == 0);
}
#endif /* __MOD25519_C */

View file

@ -58,15 +58,7 @@
#endif
#endif
static inline unsigned is_odd(uint64_t x)
{
return 1 == (x & 1);
}
static inline unsigned is_even(uint64_t x)
{
return !is_odd(x);
}
#include "bignum.c"
/**
* Compute the inverse modulo 2 of a 64-bit odd integer.
@ -88,61 +80,6 @@ STATIC uint64_t inverse64(uint64_t a)
return x;
}
/**
* Check if a multi-word integer x is greater than or equal to y.
*
* @param x The first term
* @param y The second term
* @param nw The number of words that make up x and y
* @return 1 if x>=y, 0 if x<y
*/
STATIC int ge(const uint64_t *x, const uint64_t *y, size_t nw)
{
unsigned mask = (unsigned)-1;
unsigned result = 0;
size_t i, j;
i = nw - 1;
for (j=0; j<nw; j++, i--) {
unsigned greater, lower;
greater = x[i] > y[i];
lower = x[i] < y[i];
result |= mask & (greater | (lower << 1));
mask &= (greater ^ lower) - 1;
}
return result<2;
}
/*
* Subtract a multi-word integer b from a.
*
* @param out The location where the multi-word result is stored
* @param a Number to subtract from
* @param b Number to subtract
* @param nw The number of words of both a and b
* @result 0 if there is no borrow, 1 otherwise
*/
STATIC unsigned sub(uint64_t *out, const uint64_t *a, const uint64_t *b, size_t nw)
{
size_t i;
unsigned borrow1 , borrow2;
borrow2 = 0;
for (i=0; i<nw; i++) {
borrow1 = b[i] > a[i];
out[i] = a[i] - b[i];
borrow1 |= borrow2 > out[i];
out[i] -= borrow2;
borrow2 = borrow1;
}
return borrow2;
}
/*
* Compute R² mod N, where R is the smallest power of 2 larger than N.
*
@ -183,161 +120,6 @@ STATIC void rsquare(uint64_t *r2_mod_n, uint64_t *n, size_t nw)
}
}
/*
* Multiply a multi-word integer a by a 64-bit scalar k and
* then add the result to the multi-word integer t.
*
* @param t The multi-word integer accumulator
* @param tw The number of words of t
* @param a The multi-word integer to multiply with the scalar
* @param aw The number of words of a
* @param k The 64-bit scalar multiplier
*/
STATIC void addmul(uint64_t *t, size_t tw, const uint64_t *a, size_t aw, uint64_t k)
{
size_t i;
uint64_t carry;
carry = 0;
for (i=0; i<aw; i++) {
uint64_t prod_lo, prod_hi;
DP_MULT(a[i], k, prod_lo, prod_hi);
prod_lo += carry;
prod_hi += prod_lo < carry;
t[i] += prod_lo;
prod_hi += t[i] < prod_lo;
carry = prod_hi;
}
for (; carry; i++) {
t[i] += carry;
carry = t[i] < carry;
}
assert(i <= tw);
}
/**
* Multiply two multi-word integers.
*
* @param t The location where the result is stored. It is twice as big as
* either a (or b). It is an array of 2*nw words).
* @param scratchpad Temporary area. It is an array of 3*nw words.
* @param a The first term, array of nw words.
* @param b The second term, array of nw words.
* @param nw The number of words of both a and b.
*
*/
STATIC void product(uint64_t *t, uint64_t *scratchpad, const uint64_t *a, const uint64_t *b, size_t nw)
{
size_t i;
memset(t, 0, 2*sizeof(uint64_t)*nw);
for (i=0; i<(nw ^ (nw & 1)); i+=2) {
addmul128(&t[i], scratchpad, a, b[i], b[i+1], 2*nw-i, nw);
}
if (is_odd(nw)) {
addmul(&t[nw-1], nw+2, a, nw, b[nw-1]);
}
}
/*
* Select a number out of two, in constant time.
*
* @param out The location where the multi-word result is stored
* @param a The first choice, selected if cond is true (non-zero)
* @param b The second choice, selected if cond is false (zero)
* @param cond The flag that drives the selection
* @param words The number of words of a, b, and out
* @return 0 for success, the appropriate code otherwise.
*/
STATIC FUNC_SSE2 int mont_select(uint64_t *out, const uint64_t *a, const uint64_t *b, unsigned cond, size_t words)
{
uint64_t mask;
#if defined(USE_SSE2)
unsigned pairs, i;
__m128i r0, r1, r2, r3, r4, r5;
pairs = (unsigned)words / 2;
mask = (uint64_t)((cond != 0) - 1); /* 0 for a, 1s for b */
#if SYSBITS == 64
r0 = _mm_set1_epi64x(mask);
#else
r0 = _mm_loadl_epi64((__m128i*)&mask);
r0 = _mm_unpacklo_epi64(r0, r0);
#endif
for (i=0; i<pairs; i++, a+=2, b+=2, out+=2) {
r1 = _mm_loadu_si128((__m128i const*)b);
r2 = _mm_loadu_si128((__m128i const*)a);
r3 = _mm_and_si128(r0, r1);
r4 = _mm_andnot_si128(r0, r2);
r5 = _mm_or_si128(r3, r4);
_mm_storeu_si128((__m128i*)out, r5);
}
if (words & 1) {
*out = (*b & mask) ^ (*a & ~mask);
}
#else
unsigned i;
mask = (uint64_t)((cond != 0) - 1);
for (i=0; i<words; i++) {
*out++ = (*b++ & mask) ^ (*a++ & ~mask);
}
#endif
return 0;
}
/*
* Add two multi-word numbers with modulo arithmetic.
*
* @param out The locaton where the multi-word result (nw words) is stored
* @param a The first term (nw words)
* @param b The second term (nw words)
* @param modulus The modulus (nw words)
* @param tmp1 A temporary area (nw words)
* @param tmp2 A temporary area (nw words)
* @param nw The number of 64-bit words in all parameters
*/
void add_mod(uint64_t* out, const uint64_t* a, const uint64_t* b, const uint64_t *modulus, uint64_t *tmp1, uint64_t *tmp2, size_t nw)
{
unsigned i;
unsigned carry, borrow1, borrow2;
/*
* Compute sum in tmp1[], and subtract modulus[]
* from tmp1[] into tmp2[].
*/
borrow2 = 0;
for (i=0, carry=0; i<nw; i++) {
tmp1[i] = a[i] + carry;
carry = tmp1[i] < carry;
tmp1[i] += b[i];
carry += tmp1[i] < b[i];
borrow1 = modulus[i] > tmp1[i];
tmp2[i] = tmp1[i] - modulus[i];
borrow1 |= borrow2 > tmp2[i];
tmp2[i] -= borrow2;
borrow2 = borrow1;
}
/*
* If there is no borrow or if there is carry,
* tmp1[] is larger than modulus, so we must return tmp2[].
*/
mont_select(out, tmp2, tmp1, carry | (borrow2 ^ 1), nw);
}
/*
* Montgomery modular multiplication, that is a*b*R mod N.
*
@ -413,7 +195,7 @@ STATIC void mont_mult_generic(uint64_t *out, const uint64_t *a, const uint64_t *
/** Divide by R and possibly subtract n **/
sub(t2, &t[nw], n, nw);
cond = (unsigned)(t[2*nw] | (uint64_t)ge(&t[nw], n, nw));
mont_select(out, t2, &t[nw], cond, (unsigned)nw);
mod_select(out, t2, &t[nw], cond, (unsigned)nw);
}
STATIC void mont_mult_p256(uint64_t *out, const uint64_t *a, const uint64_t *b, const uint64_t *n, uint64_t m0, uint64_t *tmp, size_t nw)
@ -552,7 +334,7 @@ STATIC void mont_mult_p256(uint64_t *out, const uint64_t *a, const uint64_t *b,
/** Divide by R and possibly subtract n **/
sub(t2, &t[nw], n, WORDS_64);
cond = (unsigned)(t[PREDIV_WORDS_64-1] | (uint64_t)ge(&t[WORDS_64], n, WORDS_64));
mont_select(out, t2, &t[WORDS_64], cond, WORDS_64);
mod_select(out, t2, &t[WORDS_64], cond, WORDS_64);
#undef WORDS_64
#undef PREDIV_WORDS_64
@ -735,7 +517,7 @@ STATIC void mont_mult_p384(uint64_t *out, const uint64_t *a, const uint64_t *b,
/** Divide by R and possibly subtract n **/
sub(t2, &t[WORDS_64], n, WORDS_64);
cond = (unsigned)(t[PREDIV_WORDS_64-1] | (uint64_t)ge(&t[WORDS_64], n, WORDS_64));
mont_select(out, t2, &t[WORDS_64], cond, WORDS_64);
mod_select(out, t2, &t[WORDS_64], cond, WORDS_64);
#undef WORDS_64
#undef PREDIV_WORDS_64
@ -1044,40 +826,10 @@ int mont_mult(uint64_t* out, const uint64_t* a, const uint64_t *b, uint64_t *tmp
*/
int mont_sub(uint64_t *out, const uint64_t *a, const uint64_t *b, uint64_t *tmp, const MontContext *ctx)
{
unsigned i;
unsigned carry, borrow1 , borrow2;
uint64_t *scratchpad;
if (NULL == out || NULL == a || NULL == b || NULL == tmp || NULL == ctx)
return ERR_NULL;
scratchpad = tmp + ctx->words;
/*
* Compute difference in tmp[], and add modulus[]
* to tmp[] into scratchpad[].
*/
borrow2 = 0;
carry = 0;
for (i=0; i<ctx->words; i++) {
borrow1 = b[i] > a[i];
tmp[i] = a[i] - b[i];
borrow1 |= borrow2 > tmp[i];
tmp[i] -= borrow2;
borrow2 = borrow1;
scratchpad[i] = tmp[i] + carry;
carry = scratchpad[i] < carry;
scratchpad[i] += ctx->modulus[i];
carry += scratchpad[i] < ctx->modulus[i];
}
/*
* If there is no borrow, tmp[] is smaller than modulus.
*/
mont_select(out, scratchpad, tmp, borrow2, ctx->words);
return 0;
return sub_mod(out, a, b, ctx->modulus, tmp, tmp + ctx->words, ctx->words);
}
/*

View file

@ -6,7 +6,8 @@ CFLAGS += -O3 -g -fstrict-aliasing -Wall -Werror
TAPPS:=tests_addmul128_32 tests_addmul128_64 tests_square_32 tests_square_64 tests_product tests_addmul test_endianess\
test_poly1305_reduce test_poly1305_load_r test_poly1305_load_m test_poly1305_multiply test_poly1305_accumulate\
test_mont tests_mont_mult tests_ec_ws_64 tests_ec_ws_32 test_pkcs1
test_mont tests_mont_mult tests_ec_ws_64 tests_ec_ws_32 test_pkcs1\
test_mod25519 test_x25519 tests_mul_25519 tests_ladder_step test_ed25519
ifneq (,$(filter $(shell uname -m),x86_64 i386 i686))
CPPFLAGS += -DHAVE_X86INTRIN_H -DUSE_SSE2 -DHAVE_WMMINTRIN_H -DHAVE_TMMINTRIN_H
@ -33,7 +34,7 @@ build:
all: ${$TGTS}
clean:
rm -fr build common.pyc
rm -fr build common.pyc __pycache__
# utils
@ -44,7 +45,6 @@ build/modexp_utils.o: ../modexp_utils.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $^
# ECC
TABLES = build/p256_table.o build/p384_table.o build/p521_table.o
build/p256_table.o: ../p256_table.c
@ -164,3 +164,25 @@ build/tests_mont_mult: build/tests_mont_mult.c build/mont_32.o $(UTILS)
# pkcs1
build/test_pkcs1: test_pkcs1.c ../common.h ../pkcs1_decode.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $^
# Curve25519
build/test_mod25519: test_mod25519.c ../mod25519.c ../multiply_32.c
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^
build/tests_mul_25519.c: make_tests_mul_25519.py
$(PYTHON) $^ > $@
build/tests_mul_25519: build/tests_mul_25519.c ../mod25519.c ../multiply_32.c
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^
build/test_x25519: test_x25519.c ../x25519.c ../multiply_32.c
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^
build/tests_ladder_step.c: make_tests_ladder_step.py
$(PYTHON) $^ > $@
build/tests_ladder_step: build/tests_ladder_step.c ../x25519.c ../multiply_32.c
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^
build/test_ed25519: test_ed25519.c ../ed25519.c ../multiply_32.c
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^

View file

@ -0,0 +1,182 @@
"""Make unit test for ladder_step() in x25519.c"""
from common import counter, make_main, split64, bin2int
from hashlib import sha256
import struct
def ref(x2, z2, x3, z3, x1):
mod = 2**255 - 19
x4 = (x2**2 - z2**2)**2
z4 = 4*x2*z2*(x2**2 + 486662*x2*z2 + z2**2)
x5 = 4*((x2*x3 - z2*z3)**2)
z5 = 4*((x2*z3 - z2*x3)**2)*x1
return x4 % mod, z4 % mod, x5 % mod, z5 % mod
def make_test_max():
v = ["0x%08X" % (2**26-1)] * 10
modulus = 2**255 - 19
base = [0, 26, 51, 77, 102, 128, 153, 179, 204, 230]
n = 0
for i in range(10):
n += (2**26 - 1) * (2**(base[i]))
n %= modulus
x2_out, z2_out, x3_out, z3_out = ref(n, n, n, n, n)
# Output
words = split64(x2_out)
x2outx = words + ["0"] * (4 - len(words))
words = split64(z2_out)
z2outx = words + ["0"] * (4 - len(words))
words = split64(x3_out)
x3outx = words + ["0"] * (4 - len(words))
words = split64(z3_out)
z3outx = words + ["0"] * (4 - len(words))
print("")
print("void test_%d() {" % next(counter))
print(" uint32_t x2[10] = { " + ",".join(v) + " };")
print(" uint32_t z2[10] = { " + ",".join(v) + " };")
print(" uint32_t x3[10] = { " + ",".join(v) + " };")
print(" uint32_t z3[10] = { " + ",".join(v) + " };")
print(" uint32_t xp[10] = { " + ",".join(v) + " };")
print(" const uint64_t x2_out_ref[4] = {" + ", ".join(x2outx) + "};")
print(" const uint64_t z2_out_ref[4] = {" + ", ".join(z2outx) + "};")
print(" const uint64_t x3_out_ref[4] = {" + ", ".join(x3outx) + "};")
print(" const uint64_t z3_out_ref[4] = {" + ", ".join(z3outx) + "};")
print(" uint64_t x2_out[4] = { 0 };")
print(" uint64_t z2_out[4] = { 0 };")
print(" uint64_t x3_out[4] = { 0 };")
print(" uint64_t z3_out[4] = { 0 };")
print("")
print(" ladder_step(x2, z2, x3, z3, xp);")
print(" convert_le25p5_to_le64(x2_out, x2);")
print(" convert_le25p5_to_le64(z2_out, z2);")
print(" convert_le25p5_to_le64(x3_out, x3);")
print(" convert_le25p5_to_le64(z3_out, z3);")
print(" reduce_25519_le64(x2_out);")
print(" reduce_25519_le64(z2_out);")
print(" reduce_25519_le64(x3_out);")
print(" reduce_25519_le64(z3_out);")
print("")
print(" assert(0 == memcmp(x2_out, x2_out_ref, sizeof x2_out));")
print(" assert(0 == memcmp(z2_out, z2_out_ref, sizeof z2_out));")
print(" assert(0 == memcmp(x3_out, x3_out_ref, sizeof x3_out));")
print(" assert(0 == memcmp(z3_out, z3_out_ref, sizeof z3_out));")
print("}")
def make_test(x2, z2, x3, z3, xp):
x2_out, z2_out, x3_out, z3_out = ref(x2, z2, x3, z3, xp)
# Input
words = split64(x2)
x2x = words + ["0"] * (4 - len(words))
words = split64(z2)
z2x = words + ["0"] * (4 - len(words))
words = split64(x3)
x3x = words + ["0"] * (4 - len(words))
words = split64(z3)
z3x = words + ["0"] * (4 - len(words))
words = split64(xp)
xpx = words + ["0"] * (4 - len(words))
# Output
words = split64(x2_out)
x2outx = words + ["0"] * (4 - len(words))
words = split64(z2_out)
z2outx = words + ["0"] * (4 - len(words))
words = split64(x3_out)
x3outx = words + ["0"] * (4 - len(words))
words = split64(z3_out)
z3outx = words + ["0"] * (4 - len(words))
print("")
print("void test_%d() {" % next(counter))
print(" const uint64_t x2_in[4] = {" + ", ".join(x2x) + "};")
print(" const uint64_t z2_in[4] = {" + ", ".join(z2x) + "};")
print(" const uint64_t x3_in[4] = {" + ", ".join(x3x) + "};")
print(" const uint64_t z3_in[4] = {" + ", ".join(z3x) + "};")
print(" const uint64_t xp_in[4] = {" + ", ".join(xpx) + "};")
print(" const uint64_t x2_out_ref[4] = {" + ", ".join(x2outx) + "};")
print(" const uint64_t z2_out_ref[4] = {" + ", ".join(z2outx) + "};")
print(" const uint64_t x3_out_ref[4] = {" + ", ".join(x3outx) + "};")
print(" const uint64_t z3_out_ref[4] = {" + ", ".join(z3outx) + "};")
print(" uint32_t x2[10] = { 0 };")
print(" uint32_t z2[10] = { 0 };")
print(" uint32_t x3[10] = { 0 };")
print(" uint32_t z3[10] = { 0 };")
print(" uint32_t xp[10] = { 0 };")
print(" uint64_t x2_out[4] = { 0 };")
print(" uint64_t z2_out[4] = { 0 };")
print(" uint64_t x3_out[4] = { 0 };")
print(" uint64_t z3_out[4] = { 0 };")
print("")
print(" convert_le64_to_le25p5(x2, x2_in);")
print(" convert_le64_to_le25p5(z2, z2_in);")
print(" convert_le64_to_le25p5(x3, x3_in);")
print(" convert_le64_to_le25p5(z3, z3_in);")
print(" convert_le64_to_le25p5(xp, xp_in);")
print(" ladder_step(x2, z2, x3, z3, xp);")
print(" convert_le25p5_to_le64(x2_out, x2);")
print(" convert_le25p5_to_le64(z2_out, z2);")
print(" convert_le25p5_to_le64(x3_out, x3);")
print(" convert_le25p5_to_le64(z3_out, z3);")
print(" reduce_25519_le64(x2_out);")
print(" reduce_25519_le64(z2_out);")
print(" reduce_25519_le64(x3_out);")
print(" reduce_25519_le64(z3_out);")
print("")
print(" assert(0 == memcmp(x2_out, x2_out_ref, sizeof x2_out));")
print(" assert(0 == memcmp(z2_out, z2_out_ref, sizeof z2_out));")
print(" assert(0 == memcmp(x3_out, x3_out_ref, sizeof x3_out));")
print(" assert(0 == memcmp(z3_out, z3_out_ref, sizeof z3_out));")
print("}")
def make_limb(seed):
result = bin2int(sha256(struct.pack(">I", seed)).digest()) & ((2**255)-1)
return result
print("#include <assert.h>")
print("#include <string.h>")
print("#include <stdint.h>")
print("#include <stdio.h>")
print("void convert_le25p5_to_le64(uint64_t out[4], const uint32_t in[10]);")
print("void convert_le64_to_le25p5(uint32_t out[10], const uint64_t in[4]);")
print("void reduce_25519_le64(uint64_t x[4]);")
print("void ladder_step(uint32_t x2[10], uint32_t z2[10], uint32_t x3[10], uint32_t z3[10], const uint32_t xp[10]);")
make_test_max()
make_test(0, 0, 0, 0, 0)
make_test(1, 1, 1, 1, 1)
make_test(6000, 10, 1000, 19999, 18888)
for x in range(50):
x2 = make_limb(1000 + x)
z2 = make_limb(2000 + x)
x3 = make_limb(3000 + x)
z3 = make_limb(4000 + x)
xp = make_limb(5000 + x)
make_test(x2, z2, x3, z3, xp)
make_main()

View file

@ -0,0 +1,107 @@
"""Make unit test for mul_25519() in x25519.c"""
from common import counter, make_main, split64, bin2int
from hashlib import sha256
import struct
def make_test(f, g):
assert(len(f) == 10)
assert(len(g) == 10)
for i in range(10):
assert(f[i] < 2**27)
assert(g[i] < 2**27)
fx = ["0x%08X" % x for x in f]
gx = ["0x%08X" % x for x in g]
max26 = hex(2**26 - 1)
max25 = hex(2**25 - 1)
modulus = 2**255 - 19
base = [0, 26, 51, 77, 102, 128, 153, 179, 204, 230]
fv = 0
gv = 0
for i in range(10):
fv += f[i] * (2**(base[i]))
gv += g[i] * (2**(base[i]))
canonical = (fv * gv) % modulus
results = [canonical, canonical + modulus]
if canonical < 38:
results.append(canonical + modulus * 2)
# Turn results[] into arrays of 64-bit words
results_hex = []
for result in results:
words = split64(result)
words = words + ["0"] * (4 - len(words))
results_hex.append(words)
print("")
print("void test_%d() {" % next(counter))
print(" const uint32_t f[10] = {" + ", ".join(fx) + "};")
print(" const uint32_t g[10] = {" + ", ".join(gx) + "};")
print(" uint32_t out[10];")
print(" uint64_t out64[4];")
print(" uint64_t exp[%d][4] = {" % len(results))
print(" { " + ",".join(results_hex[0]) + " },")
print(" { " + ",".join(results_hex[1]) + " }")
if len(results_hex) == 3:
print(" ,{ " + ",".join(results_hex[2]) + " }")
print(" };")
print(" unsigned match;")
print("")
print(" mul_25519(out, f, g);")
print(" assert(out[0] <= " + max26 + ");")
print(" assert(out[1] <= " + max25 + ");")
print(" assert(out[2] <= " + max26 + ");")
print(" assert(out[3] <= " + max25 + ");")
print(" assert(out[4] <= " + max26 + ");")
print(" assert(out[5] <= " + max25 + ");")
print(" assert(out[6] <= " + max26 + ");")
print(" assert(out[7] <= " + max25 + ");")
print(" assert(out[8] <= " + max26 + ");")
print(" assert(out[9] <= " + max26 + ");")
print(" convert_le25p5_to_le64(out64, out);")
print(" match = 0;")
print(" match |= !memcmp(exp[0], out64, 32);")
print(" match |= !memcmp(exp[1], out64, 32);")
if len(results_hex) == 3:
print(" match |= !memcmp(exp[2], out64, 32);")
print(" assert(match);")
print("}")
def make_limb(seed):
result = bin2int(sha256(struct.pack(">I", seed)).digest()) & ((2**27)-1)
return result
print("#include <assert.h>")
print("#include <string.h>")
print("#include <stdint.h>")
print("#include <stdio.h>")
print("void convert_le25p5_to_le64(uint64_t out[4], const uint32_t in[10]);")
print("void mul_25519(uint32_t out[10], const uint32_t f[10], const uint32_t g[10]);")
modulus = [0x3ffffed, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff]
modulus_m1 = [0x3ffffec, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff]
modulus_m2 = [0x3ffffeb, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff]
modulus_m40 = [0x3ffffc5, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff]
make_test([0]*10, [0]*10)
make_test([1] + [0]*9, [1] + [0]*9)
make_test([30] + [0]*9, [30] + [0]*9)
make_test([0x7ffffed] + [0]*9, [0x7ffffed] + [0]*9)
make_test(modulus, modulus)
make_test(modulus_m1, modulus_m1)
make_test(modulus_m2, modulus_m2)
make_test(modulus, modulus_m2)
make_test(modulus_m40, modulus_m40)
for x in range(100):
f = [make_limb(1000*x + y) for y in range(10)]
g = [make_limb(2000*x + y) for y in range(10)]
make_test(f, g)
make_main()

View file

@ -572,11 +572,11 @@ void test_ec_ws_new_point(void)
res = ec_ws_new_point(&ecp, Gx, Gy, 32, ec_ctx);
assert(res == 0);
ec_free_point(ecp);
ec_ws_free_point(ecp);
res = ec_ws_new_point(&ecp, zero, zero, 32, ec_ctx);
assert(res == 0);
ec_free_point(ecp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
}
@ -614,7 +614,7 @@ void test_ec_ws_get_xy(void)
assert(0 == memcmp(bufx, Gx, 32));
assert(0 == memcmp(bufy, Gy, 32));
ec_free_point(ecp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
}
@ -642,7 +642,7 @@ void test_ec_ws_double_p256(void)
assert(0 == memcmp(bufx, "\x7c\xf2\x7b\x18\x8d\x03\x4f\x7e\x8a\x52\x38\x03\x04\xb5\x1a\xc3\xc0\x89\x69\xe2\x77\xf2\x1b\x35\xa6\x0b\x48\xfc\x47\x66\x99\x78", 32));
assert(0 == memcmp(bufy, "\x07\x77\x55\x10\xdb\x8e\xd0\x40\x29\x3d\x9a\xc6\x9f\x74\x30\xdb\xba\x7d\xad\xe6\x3c\xe9\x82\x29\x9e\x04\xb7\x9d\x22\x78\x73\xd1", 32));
ec_free_point(ecp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
}
@ -670,7 +670,7 @@ void test_ec_ws_double_p521(void)
assert(0 == memcmp(bufx, "\x01\x28\x79\x44\x2F\x24\x50\xC1\x19\xE7\x11\x9A\x5F\x73\x8B\xE1\xF1\xEB\xA9\xE9\xD7\xC6\xCF\x41\xB3\x25\xD9\xCE\x6D\x64\x31\x06\xE9\xD6\x11\x24\xA9\x1A\x96\xBC\xF2\x01\x30\x5A\x9D\xEE\x55\xFA\x79\x13\x6D\xC7\x00\x83\x1E\x54\xC3\xCA\x4F\xF2\x64\x6B\xD3\xC3\x6B\xC6", 66));
assert(0 == memcmp(bufy, "\x01\x98\x64\xA8\xB8\x85\x5C\x24\x79\xCB\xEF\xE3\x75\xAE\x55\x3E\x23\x93\x27\x1E\xD3\x6F\xAD\xFC\x44\x94\xFC\x05\x83\xF6\xBD\x03\x59\x88\x96\xF3\x98\x54\xAB\xEA\xE5\xF9\xA6\x51\x5A\x02\x1E\x2C\x0E\xEF\x13\x9E\x71\xDE\x61\x01\x43\xF5\x33\x82\xF4\x10\x4D\xCC\xB5\x43", 66));
ec_free_point(ecp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
}
@ -702,8 +702,8 @@ void test_ec_ws_add(void)
assert(0 == memcmp(bufx, "\x5e\xcb\xe4\xd1\xa6\x33\x0a\x44\xc8\xf7\xef\x95\x1d\x4b\xf1\x65\xe6\xc6\xb7\x21\xef\xad\xa9\x85\xfb\x41\x66\x1b\xc6\xe7\xfd\x6c", 32));
assert(0 == memcmp(bufy, "\x87\x34\x64\x0c\x49\x98\xff\x7e\x37\x4b\x06\xce\x1a\x64\xa2\xec\xd8\x2a\xb0\x36\x38\x4f\xb8\x3d\x9a\x79\xb1\x27\xa2\x7d\x50\x32", 32));
ec_free_point(ecp);
ec_free_point(ecp2);
ec_ws_free_point(ecp);
ec_ws_free_point(ecp2);
ec_free_context(ec_ctx);
}
@ -733,7 +733,7 @@ void test_ec_ws_scalar(void)
assert(0 == memcmp(bufx, "\xf2\x49\x10\x4d\x0e\x6f\x8f\x29\xe6\x01\x62\x77\x78\x0c\xda\x84\xdc\x84\xb8\x3b\xc3\xd8\x99\xdf\xb7\x36\xca\x08\x31\xfb\xe8\xcf", 32));
assert(0 == memcmp(bufy, "\xb5\x7e\x12\xfc\xdb\x03\x1f\x59\xca\xb8\x1b\x1c\x6b\x1e\x1c\x07\xe4\x51\x2e\x52\xce\x83\x2f\x1a\x0c\xed\xef\xff\x8b\x43\x40\xe9", 32));
ec_free_point(ecp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
}
@ -761,7 +761,7 @@ void test_ec_ws_neg(void)
assert(0 == memcmp(bufx, "\x6b\x17\xd1\xf2\xe1\x2c\x42\x47\xf8\xbc\xe6\xe5\x63\xa4\x40\xf2\x77\x03\x7d\x81\x2d\xeb\x33\xa0\xf4\xa1\x39\x45\xd8\x98\xc2\x96", 32));
assert(0 == memcmp(bufy, "\xb0\x1c\xbd\x1c\x01\xe5\x80\x65\x71\x18\x14\xb5\x83\xf0\x61\xe9\xd4\x31\xcc\xa9\x94\xce\xa1\x31\x34\x49\xbf\x97\xc8\x40\xae\x0a", 32));
ec_free_point(ecp);
ec_ws_free_point(ecp);
ec_free_context(ec_ctx);
}

169
src/test/test_ed25519.c Normal file
View file

@ -0,0 +1,169 @@
#include "endianess.h"
#include <assert.h>
typedef struct Point {
uint32_t X[10];
uint32_t Y[10];
uint32_t Z[10];
uint32_t T[10];
} Point;
void ed25519_add_internal(Point *P3, const Point *P1, const Point *P2);
void ed25519_double_internal(Point *P3, const Point *P1);
void mul_25519(uint32_t out[10], const uint32_t f[10], const uint32_t g[10]);
void invert_25519(uint32_t out[10], const uint32_t x[10]);
int convert_behex_to_le25p5(uint32_t out[10], const char *in);
int convert_le25p5_to_behex(char **out, uint32_t in[10]);
void convert_le25p5_to_le8(uint8_t out[32], const uint32_t in[10]);
void convert_le8_to_le25p5(uint32_t out[10], const uint8_t in[32]);
void ed25519_scalar_internal(Point *Pout,
const uint8_t *k, size_t len,
const Point *Pin);
void test_point_add(void)
{
char Gx_hex[] = "216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a";
char Gy_hex[] = "6666666666666666666666666666666666666666666666666666666666666658";
char G2x_hex[] = "36ab384c9f5a046c3d043b7d1833e7ac080d8e4515d7a45f83c5a14e2843ce0e";
char G2y_hex[] = "2260cdf3092329c21da25ee8c9a21f5697390f51643851560e5f46ae6af8a3c9";
Point G, G2;
uint32_t invz[10];
char *c;
memset(&G, 0, sizeof G);
convert_behex_to_le25p5(G.X, Gx_hex);
convert_behex_to_le25p5(G.Y, Gy_hex);
G.Z[0] = 1;
mul_25519(G.T, G.X, G.Y);
ed25519_add_internal(&G2, &G, &G);
invert_25519(invz, G2.Z);
/* Check X */
mul_25519(G2.X, G2.X, invz);
convert_le25p5_to_behex(&c, G2.X);
assert(0 == strcmp(c, G2x_hex));
free(c);
/* Check Y */
mul_25519(G2.Y, G2.Y, invz);
convert_le25p5_to_behex(&c, G2.Y);
assert(0 == strcmp(c, G2y_hex));
free(c);
}
void test_point_double(void)
{
char Gx_hex[] = "216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a";
char Gy_hex[] = "6666666666666666666666666666666666666666666666666666666666666658";
char G2x_hex[] = "36ab384c9f5a046c3d043b7d1833e7ac080d8e4515d7a45f83c5a14e2843ce0e";
char G2y_hex[] = "2260cdf3092329c21da25ee8c9a21f5697390f51643851560e5f46ae6af8a3c9";
Point G, G2;
uint32_t invz[10];
char *c;
memset(&G, 0, sizeof G);
convert_behex_to_le25p5(G.X, Gx_hex);
convert_behex_to_le25p5(G.Y, Gy_hex);
G.Z[0] = 1;
mul_25519(G.T, G.X, G.Y);
ed25519_double_internal(&G2, &G);
invert_25519(invz, G2.Z);
/* Check X */
mul_25519(G2.X, G2.X, invz);
convert_le25p5_to_behex(&c, G2.X);
assert(0 == strcmp(c, G2x_hex));
free(c);
/* Check Y */
mul_25519(G2.Y, G2.Y, invz);
convert_le25p5_to_behex(&c, G2.Y);
assert(0 == strcmp(c, G2y_hex));
free(c);
}
void from_affine(Point *P, const uint8_t x[32], const uint8_t y[32])
{
memset(P, 0, sizeof *P);
convert_le8_to_le25p5(P->X, x);
convert_le8_to_le25p5(P->Y, y);
P->Z[0] = 1;
mul_25519(P->T, P->X, P->Y);
}
void to_affine(uint8_t x[32], uint8_t y[32], const Point *P)
{
uint32_t invz[10];
uint32_t tmp[10];
invert_25519(invz, P->Z);
mul_25519(tmp, P->X, invz);
convert_le25p5_to_le8(x, tmp);
mul_25519(tmp, P->Y, invz);
convert_le25p5_to_le8(y, tmp);
}
void test_scalar_mult(void)
{
uint8_t xout[32], yout[32];
uint8_t G0x[32] = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
uint8_t G0y[32] = "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
uint8_t Gx[32] = "\x1a\xd5\x25\x8f\x60\x2d\x56\xc9\xb2\xa7\x25\x95\x60\xc7\x2c\x69\x5c\xdc\xd6\xfd\x31\xe2\xa4\xc0\xfe\x53\x6e\xcd\xd3\x36\x69\x21";
uint8_t Gy[32] = "\x58\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66";
uint8_t G2x[32] = "\x0e\xce\x43\x28\x4e\xa1\xc5\x83\x5f\xa4\xd7\x15\x45\x8e\x0d\x08\xac\xe7\x33\x18\x7d\x3b\x04\x3d\x6c\x04\x5a\x9f\x4c\x38\xab\x36";
uint8_t G2y[32] = "\xc9\xa3\xf8\x6a\xae\x46\x5f\x0e\x56\x51\x38\x64\x51\x0f\x39\x97\x56\x1f\xa2\xc9\xe8\x5e\xa2\x1d\xc2\x29\x23\x09\xf3\xcd\x60\x22";
uint8_t G5y[32] = "\xED\xC8\x76\xD6\x83\x1F\xD2\x10\x5D\x0B\x43\x89\xCA\x2E\x28\x31\x66\x46\x92\x89\x14\x6E\x2C\xE0\x6F\xAE\xFE\x98\xB2\x25\x48\x5F";
uint8_t Gry[32] = "\xC9\x72\x8D\x51\x1D\xF5\xB3\x05\x12\xD4\x81\xCC\x41\xDE\x72\x0E\x73\x90\xF1\x53\xFE\xF6\xF0\x59\xDC\xF4\xB8\xAF\xEE\x92\x77\x16";
Point P, Q;
/* 0 */
from_affine(&P, Gx, Gy);
ed25519_scalar_internal(&Q, (uint8_t*)"\x00", 1, &P);
to_affine(xout, yout, &Q);
assert(0 == memcmp(G0x, xout, sizeof G0x));
assert(0 == memcmp(G0y, yout, sizeof G0y));
/* 1 */
from_affine(&P, Gx, Gy);
ed25519_scalar_internal(&Q, (uint8_t*)"\x01", 1, &P);
to_affine(xout, yout, &Q);
assert(0 == memcmp(Gx, xout, sizeof Gx));
assert(0 == memcmp(Gy, yout, sizeof Gy));
/* 2 */
from_affine(&P, Gx, Gy);
ed25519_scalar_internal(&Q, (uint8_t*)"\x02", 1, &P);
to_affine(xout, yout, &Q);
assert(0 == memcmp(G2x, xout, sizeof G2x));
assert(0 == memcmp(G2y, yout, sizeof G2y));
/* 5 */
from_affine(&P, Gx, Gy);
ed25519_scalar_internal(&Q, (uint8_t*)"\x05", 1, &P);
to_affine(xout, yout, &Q);
assert(0 == memcmp(G5y, yout, sizeof G5y));
/* random */
uint8_t r[32] = "\x08\x68\xba\x7a\x34\x73\x4f\x3e\x93\xdd\x24\x26\x32\x7f\x0f\x34\x14\x5c\xd9\x43\x02\xe4\xd5\xdd\x95\x00\xee\x1b\x57\x11\x39\xdd";
from_affine(&P, Gx, Gy);
ed25519_scalar_internal(&Q, r, 32, &P);
to_affine(xout, yout, &Q);
assert(0 == memcmp(Gry, yout, sizeof Gry));
}
int main(void)
{
test_point_add();
test_point_double();
test_scalar_mult();
return 0;
}

374
src/test/test_mod25519.c Normal file
View file

@ -0,0 +1,374 @@
#include "endianess.h"
#include <assert.h>
/** Test for multiplication are in a separate unit **/
void convert_le64_to_le25p5(uint32_t out[9], const uint64_t in[4]);
void convert_le25p5_to_le64(uint64_t out[4], const uint32_t in[9]);
int convert_behex_to_le25p5(uint32_t out[10], const char *in);
int convert_le25p5_to_behex(char **out, uint32_t in[10]);
void reduce_25519_le64(uint64_t x[4]);
void cswap(uint32_t a[10], uint32_t b[10], uint32_t c[10], uint32_t d[10], unsigned cond);
void invert_25519(uint32_t out[10], const uint32_t x[10]);
void add_25519(uint32_t out[10], const uint32_t f[10], const uint32_t g[10]);
static const uint64_t modulus[4] = { 0xffffffffffffffedULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0x7fffffffffffffffULL };
static const uint64_t modulus2[4] = { 0xffffffffffffffdaULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL };
static const uint64_t hundhund[4] = { 0xe08063f1e8753fb4ULL, 0x29e492f797f6605cULL, 0x1f6de7b30d1327efULL, 0x534a930de945ebf3ULL };
static const uint32_t modulus_32[10] = { 0x3ffffed, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff };
static const uint32_t hundhund_32[10] = { 0x753fb4, 0x18fc7a, 0xb9c10, 0x1bcbfb3, 0xa7924b, 0x11327ef, 0x2f3d986, 0x17e63ed, 0xde945e, 0x14d2a4c };
void test_le64_tole25p5(void)
{
uint32_t out[10];
uint64_t in[4];
memset(out, 0xAA, sizeof out);
memcpy(in, modulus, sizeof modulus);
convert_le64_to_le25p5(out, in);
assert(out[0] == 0x3ffffed);
assert(out[1] == 0x1ffffff);
assert(out[2] == 0x3ffffff);
assert(out[3] == 0x1ffffff);
assert(out[4] == 0x3ffffff);
assert(out[5] == 0x1ffffff);
assert(out[6] == 0x3ffffff);
assert(out[7] == 0x1ffffff);
assert(out[8] == 0x3ffffff);
assert(out[9] == 0x1ffffff);
memset(out, 0xAA, sizeof out);
memcpy(in, hundhund, sizeof hundhund);
convert_le64_to_le25p5(out, in);
assert(out[0] == 0x753fb4);
assert(out[1] == 0x18fc7a);
assert(out[2] == 0xb9c10);
assert(out[3] == 0x1bcbfb3);
assert(out[4] == 0xa7924b);
assert(out[5] == 0x11327ef);
assert(out[6] == 0x2f3d986);
assert(out[7] == 0x17e63ed);
assert(out[8] == 0xde945e);
assert(out[9] == 0x14d2a4c);
in[0] = 0xAAAAAAAAAAAAAAAA;
in[1] = 0xBBBBBBBBBBBBBBBB;
in[2] = 0xCCCCCCCCCCCCCCCC;
in[3] = 0xDDDDDDDDDDDDDDDD;
convert_le64_to_le25p5(out, in);
assert(out[0] == 0x2aaaaaa);
assert(out[1] == 0xaaaaaa);
assert(out[2] == 0x3777555);
assert(out[3] == 0x1dddddd);
assert(out[4] == 0x2eeeeee);
assert(out[5] == 0xcccccc);
assert(out[6] == 0x2666666);
assert(out[7] == 0x1bbb999);
assert(out[8] == 0x1dddddd);
assert(out[9] == 0x3777777);
}
void test_le25p5_to_le64(void)
{
uint64_t out[4];
memset(out, 0xAA, sizeof out);
convert_le25p5_to_le64(out, modulus_32);
assert(out[0] == modulus[0]);
assert(out[1] == modulus[1]);
assert(out[2] == modulus[2]);
assert(out[3] == modulus[3]);
memset(out, 0xAA, sizeof out);
convert_le25p5_to_le64(out, hundhund_32);
assert(out[0] == hundhund[0]);
assert(out[1] == hundhund[1]);
assert(out[2] == hundhund[2]);
}
void test_behex_tole25p5(void)
{
uint32_t out[10];
char in1[] = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed";
char in2[] = "534a930de945ebf31f6de7b30d1327ef29e492f797f6605ce08063f1e8753fb4";
char in3[] = "DDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAA";
char in4[] = "AA";
int ret;
memset(out, 0xAA, sizeof out);
ret = convert_behex_to_le25p5(out, in1);
assert(ret == 0);
assert(out[0] == 0x3ffffed);
assert(out[1] == 0x1ffffff);
assert(out[2] == 0x3ffffff);
assert(out[3] == 0x1ffffff);
assert(out[4] == 0x3ffffff);
assert(out[5] == 0x1ffffff);
assert(out[6] == 0x3ffffff);
assert(out[7] == 0x1ffffff);
assert(out[8] == 0x3ffffff);
assert(out[9] == 0x1ffffff);
memset(out, 0xAA, sizeof out);
ret = convert_behex_to_le25p5(out, in2);
assert(ret == 0);
assert(out[0] == 0x753fb4);
assert(out[1] == 0x18fc7a);
assert(out[2] == 0xb9c10);
assert(out[3] == 0x1bcbfb3);
assert(out[4] == 0xa7924b);
assert(out[5] == 0x11327ef);
assert(out[6] == 0x2f3d986);
assert(out[7] == 0x17e63ed);
assert(out[8] == 0xde945e);
assert(out[9] == 0x14d2a4c);
memset(out, 0xAA, sizeof out);
ret = convert_behex_to_le25p5(out, in3);
assert(ret == 0);
assert(out[0] == 0x2aaaaaa);
assert(out[1] == 0xaaaaaa);
assert(out[2] == 0x3777555);
assert(out[3] == 0x1dddddd);
assert(out[4] == 0x2eeeeee);
assert(out[5] == 0xcccccc);
assert(out[6] == 0x2666666);
assert(out[7] == 0x1bbb999);
assert(out[8] == 0x1dddddd);
assert(out[9] == 0x3777777);
memset(out, 0xAA, sizeof out);
ret = convert_behex_to_le25p5(out, in4);
assert(ret == 0);
assert(out[0] == 0xaa);
assert(out[1] == 0);
assert(out[2] == 0);
assert(out[3] == 0);
assert(out[4] == 0);
assert(out[5] == 0);
assert(out[6] == 0);
assert(out[7] == 0);
assert(out[8] == 0);
/* Negative test cases */
ret = convert_behex_to_le25p5(out, "A");
assert(ret != 0);
ret = convert_behex_to_le25p5(out, NULL);
assert(ret != 0);
ret = convert_behex_to_le25p5(out, "te");
assert(ret != 0);
}
void test_tole25p5_to_behex(void)
{
char *out;
uint32_t in[10];
int res;
in[0] = 0x3ffffed;
in[1] = 0x1ffffff;
in[2] = 0x3ffffff;
in[3] = 0x1ffffff;
in[4] = 0x3ffffff;
in[5] = 0x1ffffff;
in[6] = 0x3ffffff;
in[7] = 0x1ffffff;
in[8] = 0x3ffffff;
in[9] = 0x1ffffff;
convert_le25p5_to_behex(&out, in);
res = strcmp(out, "0000000000000000000000000000000000000000000000000000000000000000");
assert(res == 0);
}
void test_reduce(void)
{
uint64_t x[4];
/** 0 **/
x[0] = x[1] = x[2] = x[3] = 0;
reduce_25519_le64(x);
assert(x[0] == 0);
assert(x[1] == 0);
assert(x[2] == 0);
assert(x[3] == 0);
memcpy(x, modulus, sizeof x);
reduce_25519_le64(x);
assert(x[0] == 0);
assert(x[1] == 0);
assert(x[2] == 0);
assert(x[3] == 0);
memcpy(x, modulus2, sizeof x);
reduce_25519_le64(x);
assert(x[0] == 0);
assert(x[1] == 0);
assert(x[2] == 0);
assert(x[3] == 0);
/** 1 **/
x[0] = 1;
x[1] = x[2] = x[3] = 0;
reduce_25519_le64(x);
assert(x[0] == 1);
assert(x[1] == 0);
assert(x[2] == 0);
assert(x[3] == 0);
memcpy(x, modulus, sizeof x);
x[0]++;
reduce_25519_le64(x);
assert(x[0] == 1);
assert(x[1] == 0);
assert(x[2] == 0);
assert(x[3] == 0);
memcpy(x, modulus2, sizeof x);
x[0]++;
reduce_25519_le64(x);
assert(x[0] == 1);
assert(x[1] == 0);
assert(x[2] == 0);
assert(x[3] == 0);
/** 38 **/
x[0] = 38;
x[1] = x[2] = x[3] = 0;
reduce_25519_le64(x);
assert(x[0] == 38);
assert(x[1] == 0);
assert(x[2] == 0);
assert(x[3] == 0);
x[0] = 0x13;
x[1] = x[2] = 0;
x[3] = 0x8000000000000000ULL;
reduce_25519_le64(x);
assert(x[0] == 38);
assert(x[1] == 0);
assert(x[2] == 0);
assert(x[3] == 0);
}
int check(uint32_t x[10], uint32_t s)
{
unsigned i;
for (i=0; i<10; i++)
if (x[i] != s) {
return 0;
}
return 1;
}
void test_cswap(void)
{
uint32_t a[10];
uint32_t b[10];
uint32_t c[10];
uint32_t d[10];
memset(a, 0xAA, sizeof a);
memset(b, 0x55, sizeof b);
memset(c, 0x77, sizeof c);
memset(d, 0x11, sizeof d);
cswap(a, b, c, d, 0);
assert(check(a, 0xAAAAAAAA));
assert(check(b, 0x55555555));
assert(check(c, 0x77777777));
assert(check(d, 0x11111111));
cswap(a, b, c, d, 1);
assert(check(a, 0x77777777));
assert(check(b, 0x11111111));
assert(check(c, 0xAAAAAAAA));
assert(check(d, 0x55555555));
}
void test_invert(void)
{
uint64_t in[4];
uint32_t x[10];
uint64_t out[4];
in[0] = 1;
in[1] = in[2] = in[3] = 0;
convert_le64_to_le25p5(x, in);
invert_25519(x, x);
convert_le25p5_to_le64(out, x);
reduce_25519_le64(out);
assert(out[0] == 1);
assert(out[1] == 0);
assert(out[2] == 0);
assert(out[3] == 0);
in[0] = 2;
in[1] = in[2] = in[3] = 0;
convert_le64_to_le25p5(x, in);
invert_25519(x, x);
convert_le25p5_to_le64(out, x);
reduce_25519_le64(out);
assert(out[0] == 0xfffffffffffffff7);
assert(out[1] == 0xffffffffffffffff);
assert(out[2] == 0xffffffffffffffff);
assert(out[3] == 0x3fffffffffffffff);
in[0] = 0xAAAAAAAAAAAAAAAA;
in[1] = 0xBBBBBBBBBBBBBBBB;
in[2] = 0xCCCCCCCCCCCCCCCC;
in[3] = 0xDDDDDDDDDDDDDDDD;
convert_le64_to_le25p5(x, in);
invert_25519(x, x);
convert_le25p5_to_le64(out, x);
reduce_25519_le64(out);
assert(out[0] == 0x6cf8847ba332c4c7);
assert(out[1] == 0x984028b39c0b5e92);
assert(out[2] == 0x2404af2276fdd005);
assert(out[3] == 0x22336ebc77628108);
}
void test_add(void)
{
uint64_t in[4];
uint32_t x[10];
uint64_t out[4];
in[0] = 0xFFFFFFFFFFFFFFFF;
in[1] = 0xFFFFFFFFFFFFFFFF;
in[2] = 0xFFFFFFFFFFFFFFFF;
in[3] = 0xFFFFFFFFFFFFFFFF;
convert_le64_to_le25p5(x, in);
/* x[8] and x[9] have 26 bits set */
add_25519(x, x, x);
convert_le25p5_to_le64(out, x);
assert(out[0] == 0x0000000000000037);
assert(out[1] == 0x0000000000000000);
assert(out[2] == 0x0000000000000000);
assert(out[3] == 0x8000000000000000);
}
int main(void)
{
test_le64_tole25p5();
test_le25p5_to_le64();
test_behex_tole25p5();
test_tole25p5_to_behex();
test_reduce();
test_cswap();
test_invert();
test_add();
return 0;
}

View file

@ -5,7 +5,7 @@
int ge(const uint64_t *x, const uint64_t *y, size_t nw);
unsigned sub(uint64_t *out, const uint64_t *a, const uint64_t *b, size_t nw);
void rsquare(uint64_t *r2, uint64_t *n, size_t nw);
int mont_select(uint64_t *out, const uint64_t *a, const uint64_t *b, unsigned cond, size_t words);
int mod_select(uint64_t *out, const uint64_t *a, const uint64_t *b, unsigned cond, unsigned words);
void test_ge(void)
{
@ -371,7 +371,7 @@ void test_mont_set(void)
mont_context_free(ctx);
}
void test_mont_select()
void test_mod_select()
{
int res;
MontContext *ctx;
@ -387,17 +387,17 @@ void test_mont_select()
mont_context_init(&ctx, modulusA, 16);
memset(c, 0, sizeof c);
res = mont_select(c, a, b, 1, ctx->words);
res = mod_select(c, a, b, 1, ctx->words);
assert(res == 0);
assert(memcmp(a, c, sizeof c) == 0);
memset(c, 0, sizeof c);
res = mont_select(c, a, b, 10, ctx->words);
res = mod_select(c, a, b, 10, ctx->words);
assert(res == 0);
assert(memcmp(a, c, sizeof c) == 0);
memset(c, 0, sizeof c);
res = mont_select(c, a, b, 0, ctx->words);
res = mod_select(c, a, b, 0, ctx->words);
assert(res == 0);
assert(memcmp(b, c, sizeof c) == 0);
@ -408,12 +408,12 @@ void test_mont_select()
mont_context_init(&ctx, modulusB, 17);
memset(f, 0, sizeof f);
res = mont_select(f, d, e, 1, ctx->words);
res = mod_select(f, d, e, 1, ctx->words);
assert(res == 0);
assert(memcmp(d, f, sizeof f) == 0);
memset(f, 0, sizeof f);
res = mont_select(f, d, e, 0, ctx->words);
res = mod_select(f, d, e, 0, ctx->words);
assert(res == 0);
assert(memcmp(e, f, sizeof f) == 0);
}
@ -429,6 +429,6 @@ int main(void) {
test_mont_sub();
test_mont_inv_prime();
test_mont_set();
test_mont_select();
test_mod_select();
return 0;
}

24
src/test/test_x25519.c Normal file
View file

@ -0,0 +1,24 @@
#include "endianess.h"
#include <assert.h>
void ladder(uint8_t shared_secret[32], const uint8_t *k, size_t len, const uint8_t pubkey[32]);
void test_ladder(void)
{
uint8_t scalar[32] = "\xa5\x46\xe3\x6b\xf0\x52\x7c\x9d\x3b\x16\x15\x4b\x82\x46\x5e\xdd\x62\x14\x4c\x0a\xc1\xfc\x5a\x18\x50\x6a\x22\x44\xba\x44\x9a\xc4";
uint8_t pubkey[32] = "\xe6\xdb\x68\x67\x58\x30\x30\xdb\x35\x94\xc1\xa4\x24\xb1\x5f\x7c\x72\x66\x24\xec\x26\xb3\x35\x3b\x10\xa9\x03\xa6\xd0\xab\x1c\x4c";
uint8_t expout[32] = "\xc3\xda\x55\x37\x9d\xe9\xc6\x90\x8e\x94\xea\x4d\xf2\x8d\x08\x4f\x32\xec\xcf\x03\x49\x1c\x71\xf7\x54\xb4\x07\x55\x77\xa2\x85\x52";
uint8_t out[32];
scalar[0] &= 248;
scalar[31] &= 127;
scalar[31] |= 64;
ladder(out, scalar, 32, pubkey);
assert(0 == memcmp(out, expout, 32));
}
int main(void)
{
test_ladder();
return 0;
}

133
src/x25519.c Normal file
View file

@ -0,0 +1,133 @@
#include "common.h"
#include "endianess.h"
FAKE_INIT(x25519)
/*
* Fast variable-base scalar multiplication for the Montgomery curve Curve25519
*
* y² = x³ + 486662x² + x
*
* over the prime field 2² - 19.
*/
#include "mod25519.c"
/*
* Execute the step in the Montgomery ladder.
*
* x2/z2 is updated with the doubling of P
* x3/z3 is updated with the sum P+P
*
* @param[in,out] x2 The projective X-coordinate of P (< 2²)
* @param[in,out] z2 The projective Z-coordinate of P (< 2²)
* @param[in,out] x3 The projective X-coordinate of P (< 2²)
* @param[in,out] z3 The projective Z-coordinate of P (< 2²)
* @param[in] xp The affine X-coordinate of P-P (< 2²)
*/
STATIC void ladder_step(uint32_t x2[10], uint32_t z2[10], uint32_t x3[10], uint32_t z3[10], const uint32_t xp[10])
{
uint32_t t0[10], t1[10];
static const uint32_t nr_121666[10] = { 121666 };
/* https://www.hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#ladder-mladd-1987-m */
sub32(t0, x3, z3); /* t0 = D < 2^28 */
sub32(t1, x2, z2); /* t1 = B < 2^28 */
add32(x2, x2, z2); /* x2 = A < 2^27 */
add32(z2, x3, z3); /* z2 = C < 2^27 */
mul_25519(z3, t0, x2); /* z3 = DA < 2^26 */
mul_25519(z2, z2, t1); /* z2 = CB < 2^26 */
add32(x3, z3, z2); /* x3 = DA+CB < 2^27 */
sub32(z2, z3, z2); /* z2 = DA-CB < 2^28 */
mul_25519(x3, x3, x3); /* x3 = X5 < 2^26 */
mul_25519(z2, z2, z2); /* z2 = (DA-CB)² < 2^26 */
mul_25519(t0, t1, t1); /* t0 = BB < 2^26 */
mul_25519(t1, x2, x2); /* t1 = AA < 2^26 */
sub32(x2, t1, t0); /* x2 = E < 2^28 */
mul_25519(z3, xp, z2); /* z3 = Z5 < 2^26 */
mul_25519(z2, x2, nr_121666); /* z2 = a24*E < 2^26 */
add32(z2, t0, z2); /* z2 = BB+a24*E < 2^27 */
mul_25519(z2, x2, z2); /* z2 = Z4 < 2^26 */
mul_25519(x2, t1, t0); /* x2 = X4 < 2^26 */
}
/*
* Variable-base scalar multiplication on Curve25519.
*
* @param[out] ssecret The X-coordinate of the resulting point.
* @param[in] k The scalar encoded in little-endian mode.
* It must have been already clamped.
* @param[in] len Length of the scalar in bytes.
* @param[in] pubkey The X-coordinate of the point to multiply, encoded in
* little-endian mode.
*/
void ladder(uint8_t ssecret[32], const uint8_t *k, size_t len, const uint8_t pubkey[32])
{
uint32_t R0x[10] = { 1 };
uint32_t R0z[10] = { 0 };
uint32_t R1x[10];
uint32_t R1z[10] = { 1 };
uint32_t xp[10];
uint32_t invz[10];
uint32_t affx[10];
uint64_t tmp_64[4];
unsigned bit_idx, swap;
unsigned i;
for (i=0; i<4; i++) {
tmp_64[i] = LOAD_U64_LITTLE(&pubkey[i*8]);
}
convert_le64_to_le25p5(xp, tmp_64);
memcpy(R1x, xp, sizeof R1x);
bit_idx = 7;
swap = 0;
/* https://eprint.iacr.org/2020/956.pdf */
while (len>0) {
unsigned bit;
bit = (k[len-1] >> bit_idx) & 1;
swap ^= bit;
cswap(R0x, R0z, R1x, R1z, swap);
ladder_step(R0x, R0z, R1x, R1z, xp);
swap = bit;
if (bit_idx-- == 0) {
bit_idx = 7;
len--;
}
}
cswap(R0x, R0z, R1x, R1z, swap);
invert_25519(invz, R0z);
mul_25519(affx, R0x, invz);
convert_le25p5_to_le64(tmp_64, affx);
reduce_25519_le64(tmp_64);
for (i=0; i<4; i++) {
STORE_U64_LITTLE(&ssecret[i*8], tmp_64[i]);
}
}
#ifdef PROFILE
int main(void)
{
uint8_t pubkey[32];
uint8_t secret[32];
uint8_t out[32];
unsigned i;
secret[0] = pubkey[0] = 0xAA;
for (i=1; i<32; i++) {
secret[i] = pubkey[i] = (uint8_t)((secret[i-1] << 1) | (secret[i-1] >> 7));
}
for (i=0; i<10000; i++) {
ladder(out, secret, sizeof secret, pubkey);
}
}
#endif

View file

@ -0,0 +1,12 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIAX0BvLDTI71TpzUoZ+rzXFdgm0WVN+ndcu2UuG4lz47
-----END PRIVATE KEY-----
ED25519 Private-Key:
priv:
05:f4:06:f2:c3:4c:8e:f5:4e:9c:d4:a1:9f:ab:cd:
71:5d:82:6d:16:54:df:a7:75:cb:b6:52:e1:b8:97:
3e:3b
pub:
bc:85:b8:cf:58:5d:20:a4:de:47:e8:4d:1c:b6:18:
3f:63:d9:ba:96:22:3f:cb:c8:86:e3:63:ff:de:a2:
0c:ff

View file

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIAX0BvLDTI71TpzUoZ+rzXFdgm0WVN+ndcu2UuG4lz47
-----END PRIVATE KEY-----

View file

@ -0,0 +1,6 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAiwCrCpOhjU8wICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEOk0cNotyf4edYcdUnl6GNAEQFEf
NUiZ3jfT8HYD0FqIjwZ9UL5T5rUmM34LCqipAuSPu+f23i2OuFlWIvfXtF4Qy79v
ysv4OQa6DTDGopAHaL4=
-----END ENCRYPTED PRIVATE KEY-----

View file

@ -0,0 +1,6 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAiX7Eh6OAEHXAICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEARYEEJpUDxSZpCzf7OI60qPsYU0EQG0u
aMz2AkSXtRU3+ATTEDtb4slSdtivYBkUCfcAw8/d0PZN56wjPStu585VgRTlGTXW
Ty3Qe6eABSTTtEfBAhU=
-----END ENCRYPTED PRIVATE KEY-----

View file

@ -0,0 +1,6 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAh8KD2HGLrgSAICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEENKrZJ2LKxhTgbSfXCWBKuMEQFiS
O8+y8UlvtuAAHhDd0TBu7sGVG2M4tzpvhnMV82QXfCRDkJAUqQNtXQBLWjdO44Ui
EaYvk2g9GUDQVDfx25E=
-----END ENCRYPTED PRIVATE KEY-----

View file

@ -0,0 +1,5 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGKME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAjS0vLA/pEM8AICCAAw
DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIdHVzfnJXMDwEOFN1knabShDOAvnI
M8NhzBnlqPf0YK3HeJ4JZY0jAfLBGyKafLPA94+KbGBGRLB+bFIrVRpBrkN4
-----END ENCRYPTED PRIVATE KEY-----

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACB5VzUBr/gix80anEf1muNYHTnCByK7eInlDM5QrqilUAAAAIiXPO6Alzzu
gAAAAAtzc2gtZWQyNTUxOQAAACB5VzUBr/gix80anEf1muNYHTnCByK7eInlDM5QrqilUA
AAAEBjRlj3swElydFqbfy2FD2dgmwGMDLyhkPJ6HesGcJC4XlXNQGv+CLHzRqcR/Wa41gd
OcIHIrt4ieUMzlCuqKVQAAAABHRlc3QB
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDjWCfp8z
xIlHc7MwYdCIthAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAICHNC69L0IPdGK1V
hVBpRNoxcBOoDKZpGZ98pFEBo1wNAAAAkKTQjCRhdNO9Zv027lR5uG3cGePQK+98oRA6Wy
wcrAmwahxGoBo8y49yPMCIt0e9KmDY7+Utw0FBfdldKuYzRV732kd7bQgr0z13sc+70oix
rT8S6ufYMZHyT5sRvjaM8jlM6nuyBix4xLc7pjyNqojZfLnoELNWQ9hrN6DK8C4yQ2rUwU
UtOxazNEKJBnmLqg==
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1,6 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAh2gZRW4+DZnQICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEENs1bbPFWltnsWl6mwI1bf8EQH0K
0QOWF+Y2c24tojkepMuuKq3JRht2vCY/bxaJO74lk0p0DLsa8kIxUfd6oDA0+Mf2
K8LxR/qXZCIMRYzRyjA=
-----END ENCRYPTED PRIVATE KEY-----

View file

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAvIW4z1hdIKTeR+hNHLYYP2PZupYiP8vIhuNj/96iDP8=
-----END PUBLIC KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHlXNQGv+CLHzRqcR/Wa41gdOcIHIrt4ieUMzlCuqKVQ test

View file

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBWzCCAQ2gAwIBAgIUCvoAagsRlhJ+PYMH2nrNafycn1UwBQYDK2VwMCMxCzAJ
BgNVBAYTAkdCMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0yMjA1MDcxODMxMzFa
Fw0yMzA1MDcxODMxMzFaMCMxCzAJBgNVBAYTAkdCMRQwEgYDVQQDDAtleGFtcGxl
LmNvbTAqMAUGAytlcAMhALyFuM9YXSCk3kfoTRy2GD9j2bqWIj/LyIbjY//eogz/
o1MwUTAdBgNVHQ4EFgQU8Yevx2AVIt0UoJy8mJSdH7lBHF0wHwYDVR0jBBgwFoAU
8Yevx2AVIt0UoJy8mJSdH7lBHF0wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQB/
vYInhoHQyJYoNuPWHQt0AkZwHTZNNFBSz/+Vty2SfkTZgsyKtqNdeVlc1VfVDjU7
Sa3zerNfRQGz/qMLtKkP
-----END CERTIFICATE-----

View file

@ -0,0 +1,39 @@
#!/bin/sh
# Note that openssl's ec command does not support ed25519; you must use the (gen)pkey command
set -e
set -x
openssl version | tee openssl_version_ed25519.txt
# Private key (PKCS#8)
openssl genpkey -algorithm ed25519 -outform PEM -out ecc_ed25519_private.pem
openssl pkey -inform PEM -in ecc_ed25519_private.pem -outform DER -out ecc_ed25519_private.der
openssl pkey -in ecc_ed25519_private.pem -text -out ecc_ed25519.txt
# Encrypted private key
# Traditional format (PEM enveloped) is unsupported for ed25519, so we only use encrypted PKCS#8
openssl pkcs8 -in ecc_ed25519_private.der -inform DER -passout 'pass:secret' -out ecc_ed25519_private_p8.der -outform DER -topk8
openssl pkcs8 -in ecc_ed25519_private.der -inform DER -passout 'pass:secret' -out ecc_ed25519_private_p8.pem -outform PEM -topk8
openssl pkey -in ecc_ed25519_private.pem -des3 -out ecc_ed25519_private_enc_des3.pem -passout 'pass:secret' -outform PEM
openssl pkey -in ecc_ed25519_private.pem -aes128 -out ecc_ed25519_private_enc_aes128.pem -passout 'pass:secret' -outform PEM
openssl pkey -in ecc_ed25519_private.pem -aes192 -out ecc_ed25519_private_enc_aes192.pem -passout 'pass:secret' -outform PEM
openssl pkey -in ecc_ed25519_private.pem -aes256 -out ecc_ed25519_private_enc_aes256.pem -passout 'pass:secret' -outform PEM
# GCM is not supported by openssl in this case...
#openssl pkey -in ecc_ed25519_private.pem -aes-256-gcm -out ecc_ed25519_private_enc_aes256_gcm.pem -passout 'pass:secret' -outform PEM
# Public key
openssl pkey -in ecc_ed25519_private.pem -pubout -out ecc_ed25519_public.pem
openssl pkey -pubin -in ecc_ed25519_public.pem -outform DER -out ecc_ed25519_public.der
# X.509 cert
openssl req -new -key ecc_ed25519_private.pem -days 365 -x509 -out ecc_ed25519_x509.pem -subj '/C=GB/CN=example.com'
openssl x509 -in ecc_ed25519_x509.pem -out ecc_ed25519_x509.der -outform DER
# OpenSSH - also the .pem.pub files are created
# Unfortunately, it does not seem possible to reuse ecc_ed25519_private.pem, we need to regenerate
ssh-keygen -t ed25519 -f ecc_ed25519_private_openssh.pem -P "" -C test
mv ecc_ed25519_private_openssh.pem.pub ecc_ed25519_public_openssh.txt
ssh-keygen -t ed25519 -f ecc_ed25519_private_openssh_pwd.pem -P "password" -C test
rm ecc_ed25519_private_openssh_pwd.pem.pub

View file

@ -0,0 +1 @@
OpenSSL 1.1.1n FIPS 15 Mar 2022

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
__version__ = "1.0.7"
__version__ = "1.0.8"