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_ws_new_context = lambda *x: 0
ec_free_context = lambda *x: None ec_free_context = lambda *x: None
ec_ws_new_point = lambda *x: 0 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() _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: .. _curve_names:
.. csv-table:: .. csv-table::
:header: Curve, Possible identifiers :header: Curve, Strings accepted for the ``curve`` API parameter
:widths: 20, 80 :widths: 20, 80
"NIST P-192", "``'NIST P-192'``, ``'p192'``, ``'P-192'``, ``'prime192v1'``, ``'secp192r1'``" "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-256", "``'NIST P-256'``, ``'p256'``, ``'P-256'``, ``'prime256v1'``, ``'secp256r1'``"
"NIST P-384", "``'NIST P-384'``, ``'p384'``, ``'P-384'``, ``'prime384v1'``, ``'secp384r1'``" "NIST P-384", "``'NIST P-384'``, ``'p384'``, ``'P-384'``, ``'prime384v1'``, ``'secp384r1'``"
"NIST P-521", "``'NIST P-521'``, ``'p521'``, ``'P-521'``, ``'prime521v1'``, ``'secp521r1'``" "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. 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, The following example demonstrates how to generate a new ECC key, export it,
and subsequently reload it back into the application:: 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') >>> f = open('myprivatekey.pem','rt')
>>> key = ECC.import_key(f.read()) >>> key = ECC.import_key(f.read())
The ECC key can be used to perform or verify ECDSA signatures, using the module The ECC key can be used to perform or verify ECDSA signatures, using the modules
:mod:`Crypto.Signature.DSS`. :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/ .. _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 .. _`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 .. automodule:: Crypto.PublicKey.ECC
:members: :members:

View file

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

View file

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

View file

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

View file

@ -7,9 +7,9 @@ class IntegerBase:
def __int__(self) -> int: ... def __int__(self) -> int: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(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 @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 __eq__(self, term: object) -> bool: ...
def __ne__(self, term: object) -> bool: ... def __ne__(self, term: object) -> bool: ...
def __lt__(self, term: Union[IntegerBase, int]) -> bool: ... def __lt__(self, term: Union[IntegerBase, int]) -> bool: ...

View file

@ -57,7 +57,14 @@ implementation = {"library": "custom", "api": backend}
class IntegerCustom(IntegerNative): class IntegerCustom(IntegerNative):
@staticmethod @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)) return IntegerCustom(bytes_to_long(byte_string))
def inplace_pow(self, exponent, modulus=None): 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, from Crypto.Util._raw_api import (backend, load_lib,
get_raw_buffer, get_c_string, get_raw_buffer, get_c_string,
null_pointer, create_string_buffer, null_pointer, create_string_buffer,
c_ulong, c_size_t) c_ulong, c_size_t, c_uint8_ptr)
from ._IntegerBase import IntegerBase from ._IntegerBase import IntegerBase
@ -225,7 +225,7 @@ class IntegerGMP(IntegerBase):
def __index__(self): def __index__(self):
return int(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. """Convert the number into a byte string.
This method encodes the number in network order and prepends This method encodes the number in network order and prepends
@ -236,6 +236,8 @@ class IntegerGMP(IntegerBase):
block_size : integer block_size : integer
The exact size the output byte string must have. The exact size the output byte string must have.
If zero, the string has the minimal length. If zero, the string has the minimal length.
byteorder : string
'big' for big-endian integers (default), 'little' for litte-endian.
:Returns: :Returns:
A byte string. A byte string.
:Raise ValueError: :Raise ValueError:
@ -252,6 +254,7 @@ class IntegerGMP(IntegerBase):
" of prescribed length") " of prescribed length")
buf = create_string_buffer(buf_len) buf = create_string_buffer(buf_len)
_gmp.mpz_export( _gmp.mpz_export(
buf, buf,
null_pointer, # Ignore countp null_pointer, # Ignore countp
@ -261,20 +264,39 @@ class IntegerGMP(IntegerBase):
c_size_t(0), # No nails c_size_t(0), # No nails
self._mpz_p) 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 @staticmethod
def from_bytes(byte_string): def from_bytes(byte_string, byteorder='big'):
"""Convert a byte string into a number. """Convert a byte string into a number.
:Parameters: :Parameters:
byte_string : byte string byte_string : byte string
The input number, encoded in network order. The input number, encoded in network order.
It can only be non-negative. It can only be non-negative.
byteorder : string
'big' for big-endian integers (default), 'little' for litte-endian.
:Return: :Return:
The ``Integer`` object carrying the same value as the input. The ``Integer`` object carrying the same value as the input.
""" """
result = IntegerGMP(0) 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( _gmp.mpz_import(
result._mpz_p, result._mpz_p,
c_size_t(len(byte_string)), # Amount of words to read 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 c_size_t(1), # Each word is 1 byte long
0, # Endianess within a word - not relevant 0, # Endianess within a word - not relevant
c_size_t(0), # No nails c_size_t(0), # No nails
byte_string) c_uint8_ptr(byte_string))
return result return result
# Relations # Relations

View file

@ -62,16 +62,31 @@ class IntegerNative(IntegerBase):
def __index__(self): def __index__(self):
return int(self._value) return int(self._value)
def to_bytes(self, block_size=0): def to_bytes(self, block_size=0, byteorder='big'):
if self._value < 0: if self._value < 0:
raise ValueError("Conversion only valid for non-negative numbers") raise ValueError("Conversion only valid for non-negative numbers")
result = long_to_bytes(self._value, block_size) result = long_to_bytes(self._value, block_size)
if len(result) > block_size > 0: if len(result) > block_size > 0:
raise ValueError("Value too large to encode") 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 return result
@classmethod @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)) return cls(bytes_to_long(byte_string))
# Relations # Relations

View file

@ -42,13 +42,15 @@ from Crypto.Math.Numbers import Integer
from Crypto.Util.asn1 import (DerObjectId, DerOctetString, DerSequence, from Crypto.Util.asn1 import (DerObjectId, DerOctetString, DerSequence,
DerBitString) 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, from Crypto.PublicKey import (_expand_subject_public_key_info,
_create_subject_public_key_info, _create_subject_public_key_info,
_extract_subject_public_key_info) _extract_subject_public_key_info)
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer, from Crypto.Hash import SHA512
SmartPointer, c_size_t, c_uint8_ptr,
c_ulonglong)
from Crypto.Random import get_random_bytes from Crypto.Random import get_random_bytes
from Crypto.Random.random import getrandbits from Crypto.Random.random import getrandbits
@ -69,7 +71,7 @@ int ec_ws_new_point(EcPoint **pecp,
const uint8_t *y, const uint8_t *y,
size_t len, size_t len,
const EcContext *ec_ctx); 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, int ec_ws_get_xy(uint8_t *x,
uint8_t *y, uint8_t *y,
size_t len, size_t len,
@ -81,14 +83,51 @@ int ec_ws_scalar(EcPoint *ecp,
size_t len, size_t len,
uint64_t seed); uint64_t seed);
int ec_ws_clone(EcPoint **pecp2, const EcPoint *ecp); 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_cmp(const EcPoint *ecp1, const EcPoint *ecp2);
int ec_ws_neg(EcPoint *p); 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 = {} _curves = {}
@ -129,7 +168,8 @@ def init_p192():
"1.2.840.10045.3.1.1", # ANSI X9.62 / SEC2 "1.2.840.10045.3.1.1", # ANSI X9.62 / SEC2
context, context,
"NIST P-192", "NIST P-192",
"ecdsa-sha2-nistp192") "ecdsa-sha2-nistp192",
"p192")
global p192_names global p192_names
_curves.update(dict.fromkeys(p192_names, p192)) _curves.update(dict.fromkeys(p192_names, p192))
@ -175,7 +215,8 @@ def init_p224():
"1.3.132.0.33", # SEC 2 "1.3.132.0.33", # SEC 2
context, context,
"NIST P-224", "NIST P-224",
"ecdsa-sha2-nistp224") "ecdsa-sha2-nistp224",
"p224")
global p224_names global p224_names
_curves.update(dict.fromkeys(p224_names, p224)) _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 "1.2.840.10045.3.1.7", # ANSI X9.62 / SEC2
context, context,
"NIST P-256", "NIST P-256",
"ecdsa-sha2-nistp256") "ecdsa-sha2-nistp256",
"p256")
global p256_names global p256_names
_curves.update(dict.fromkeys(p256_names, p256)) _curves.update(dict.fromkeys(p256_names, p256))
@ -267,7 +309,8 @@ def init_p384():
"1.3.132.0.34", # SEC 2 "1.3.132.0.34", # SEC 2
context, context,
"NIST P-384", "NIST P-384",
"ecdsa-sha2-nistp384") "ecdsa-sha2-nistp384",
"p384")
global p384_names global p384_names
_curves.update(dict.fromkeys(p384_names, p384)) _curves.update(dict.fromkeys(p384_names, p384))
@ -313,7 +356,8 @@ def init_p521():
"1.3.132.0.35", # SEC 2 "1.3.132.0.35", # SEC 2
context, context,
"NIST P-521", "NIST P-521",
"ecdsa-sha2-nistp521") "ecdsa-sha2-nistp521",
"p521")
global p521_names global p521_names
_curves.update(dict.fromkeys(p521_names, p521)) _curves.update(dict.fromkeys(p521_names, p521))
@ -322,6 +366,35 @@ init_p521()
del 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): class UnsupportedEccFeature(ValueError):
pass pass
@ -334,7 +407,7 @@ class EccPoint(object):
* Adding two points: ``R = S + T`` * Adding two points: ``R = S + T``
* In-place addition: ``S += T`` * In-place addition: ``S += T``
* Negating a point: ``R = -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`` * Multiplying a point by a scalar: ``R = S*k``
* In-place multiplication by a scalar: ``T *= 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 :ivar y: The affine Y-coordinate of the ECC point
:vartype y: integer :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"): def __init__(self, x, y, curve="p256"):
@ -356,19 +429,26 @@ class EccPoint(object):
self._curve_name = curve self._curve_name = curve
modulus_bytes = self.size_in_bytes() modulus_bytes = self.size_in_bytes()
context = self._curve.context
xb = long_to_bytes(x, modulus_bytes) xb = long_to_bytes(x, modulus_bytes)
yb = long_to_bytes(y, modulus_bytes) yb = long_to_bytes(y, modulus_bytes)
if len(xb) != modulus_bytes or len(yb) != modulus_bytes: if len(xb) != modulus_bytes or len(yb) != modulus_bytes:
raise ValueError("Incorrect coordinate length") raise ValueError("Incorrect coordinate length")
new_point = lib_func(self, "new_point")
free_func = lib_func(self, "free_point")
self._point = VoidPointer() 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(xb),
c_uint8_ptr(yb), c_uint8_ptr(yb),
c_size_t(modulus_bytes), c_size_t(modulus_bytes),
context.get()) context)
if result: if result:
if result == 15: if result == 15:
raise ValueError("The EC point does not belong to the curve") 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) # Ensure that object disposal of this Python object will (eventually)
# free the memory allocated by the raw library for the EC point # free the memory allocated by the raw library for the EC point
self._point = SmartPointer(self._point.get(), self._point = SmartPointer(self._point.get(), free_func)
_ec_lib.ec_free_point)
def set(self, point): def set(self, point):
clone = lib_func(self, "clone")
free_func = lib_func(self, "free_point")
self._point = VoidPointer() self._point = VoidPointer()
result = _ec_lib.ec_ws_clone(self._point.address_of(), result = clone(self._point.address_of(),
point._point.get()) point._point.get())
if result: if result:
raise ValueError("Error %d while cloning an EC point" % result) raise ValueError("Error %d while cloning an EC point" % result)
self._point = SmartPointer(self._point.get(), self._point = SmartPointer(self._point.get(), free_func)
_ec_lib.ec_free_point)
return self return self
def __eq__(self, point): 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): def __neg__(self):
neg_func = lib_func(self, "neg")
np = self.copy() np = self.copy()
result = _ec_lib.ec_ws_neg(np._point.get()) result = neg_func(np._point.get())
if result: if result:
raise ValueError("Error %d while inverting an EC point" % result) raise ValueError("Error %d while inverting an EC point" % result)
return np return np
@ -408,10 +496,16 @@ class EccPoint(object):
def is_point_at_infinity(self): def is_point_at_infinity(self):
"""``True`` if this is the point-at-infinity.""" """``True`` if this is the point-at-infinity."""
if self._curve.desc == "Ed25519":
return self.x == 0
else:
return self.xy == (0, 0) return self.xy == (0, 0)
def point_at_infinity(self): def point_at_infinity(self):
"""Return the point-at-infinity for the curve this point is on.""" """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) return EccPoint(0, 0, self._curve_name)
@property @property
@ -427,7 +521,8 @@ class EccPoint(object):
modulus_bytes = self.size_in_bytes() modulus_bytes = self.size_in_bytes()
xb = bytearray(modulus_bytes) xb = bytearray(modulus_bytes)
yb = 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_uint8_ptr(yb),
c_size_t(modulus_bytes), c_size_t(modulus_bytes),
self._point.get()) self._point.get())
@ -451,7 +546,8 @@ class EccPoint(object):
:class:`EccPoint` : this same object (to enable chaining) :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: if result:
raise ValueError("Error %d while doubling an EC point" % result) raise ValueError("Error %d while doubling an EC point" % result)
return self return self
@ -459,7 +555,8 @@ class EccPoint(object):
def __iadd__(self, point): def __iadd__(self, point):
"""Add a second point to this one""" """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:
if result == 16: if result == 16:
raise ValueError("EC points are not on the same curve") raise ValueError("EC points are not on the same curve")
@ -476,10 +573,11 @@ class EccPoint(object):
def __imul__(self, scalar): def __imul__(self, scalar):
"""Multiply this point by a scalar""" """Multiply this point by a scalar"""
scalar_func = lib_func(self, "scalar")
if scalar < 0: if scalar < 0:
raise ValueError("Scalar multiplication is only defined for non-negative integers") raise ValueError("Scalar multiplication is only defined for non-negative integers")
sb = long_to_bytes(scalar) 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_uint8_ptr(sb),
c_size_t(len(sb)), c_size_t(len(sb)),
c_ulonglong(getrandbits(64))) c_ulonglong(getrandbits(64)))
@ -524,6 +622,11 @@ p521 = _curves['p521']._replace(G=p521_G)
_curves.update(dict.fromkeys(p521_names, p521)) _curves.update(dict.fromkeys(p521_names, p521))
del p521_G, p521, p521_names 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): class EccKey(object):
r"""Class defining an ECC key. 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`. :ivar curve: The name of the ECC as defined in :numref:`curve_names`.
:vartype curve: string :vartype curve: string
:ivar pointQ: an ECC point representating the public component :ivar point: an ECC point representating the public component
:vartype pointQ: :class:`EccPoint` :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 :vartype d: integer
:ivar seed: A 32-byte seed representating the private component.
Only for Ed25519 curves.
:vartype seed: bytes
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -547,32 +655,64 @@ class EccKey(object):
curve : string curve : string
It must be *"p256"*, *"P-256"*, *"prime256v1"* or *"secp256r1"*. It must be *"p256"*, *"P-256"*, *"prime256v1"* or *"secp256r1"*.
d : integer 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 point : EccPoint
Mandatory for a public key. If provided for a private key, Mandatory for a public key. If provided for a private key,
the implementation will NOT check whether it matches ``d``. the implementation will NOT check whether it matches ``d``.
Only one parameter among ``d``, ``seed`` or ``point`` may be used.
""" """
kwargs_ = dict(kwargs) kwargs_ = dict(kwargs)
curve_name = kwargs_.pop("curve", None) curve_name = kwargs_.pop("curve", None)
self._d = kwargs_.pop("d", None) self._d = kwargs_.pop("d", None)
self._seed = kwargs_.pop("seed", None)
self._point = kwargs_.pop("point", None) self._point = kwargs_.pop("point", None)
if curve_name is None and self._point:
curve_name = self._point._curve_name
if kwargs_: if kwargs_:
raise TypeError("Unknown parameters: " + str(kwargs_)) raise TypeError("Unknown parameters: " + str(kwargs_))
if curve_name not in _curves: 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 = _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: if self._point is None:
raise ValueError("Either private or public ECC component must be specified, not both") raise ValueError("At lest one between parameters 'point', 'd' or 'seed' must be specified")
else: 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) self._d = Integer(self._d)
if not 1 <= self._d < self._curve.order: if not 1 <= self._d < self._curve.order:
raise ValueError("Invalid ECC private component") raise ValueError("Parameter d must be an integer smaller than the curve order")
else:
self.curve = self._curve.desc 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): def __eq__(self, other):
if other.has_private() != self.has_private(): if other.has_private() != self.has_private():
@ -582,6 +722,9 @@ class EccKey(object):
def __repr__(self): def __repr__(self):
if self.has_private(): if self.has_private():
if self._curve == "Ed25519":
extra = ", seed=%s" % self._seed.hex()
else:
extra = ", d=%d" % int(self._d) extra = ", d=%d" % int(self._d)
else: else:
extra = "" extra = ""
@ -593,6 +736,7 @@ class EccKey(object):
return self._d is not None return self._d is not None
# ECDSA
def _sign(self, z, k): def _sign(self, z, k):
assert 0 < k < self._curve.order assert 0 < k < self._curve.order
@ -607,6 +751,7 @@ class EccKey(object):
s = inv_blind_k * (blind * z + blind_d * r) % order s = inv_blind_k * (blind * z + blind_d * r) % order
return (r, s) return (r, s)
# ECDSA
def _verify(self, z, rs): def _verify(self, z, rs):
order = self._curve.order order = self._curve.order
sinv = rs[1].inverse(order) sinv = rs[1].inverse(order)
@ -620,6 +765,12 @@ class EccKey(object):
raise ValueError("This is not a private ECC key") raise ValueError("This is not a private ECC key")
return self._d 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 @property
def pointQ(self): def pointQ(self):
if self._point is None: if self._point is None:
@ -636,6 +787,9 @@ class EccKey(object):
return EccKey(curve=self._curve.desc, point=self.pointQ) return EccKey(curve=self._curve.desc, point=self.pointQ)
def _export_SEC1(self, compress): 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 # See 2.2 in RFC5480 and 2.3.3 in SEC1
# #
# The first byte is: # The first byte is:
@ -660,15 +814,28 @@ class EccKey(object):
self.pointQ.y.to_bytes(modulus_bytes)) self.pointQ.y.to_bytes(modulus_bytes))
return public_key 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): 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) public_key = self._export_SEC1(compress)
unrestricted_oid = "1.2.840.10045.2.1" params = DerObjectId(self._curve.oid)
return _create_subject_public_key_info(unrestricted_oid,
public_key,
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() assert self.has_private()
@ -701,11 +868,18 @@ class EccKey(object):
if kwargs.get('passphrase', None) is not None and 'protection' not in kwargs: if kwargs.get('passphrase', None) is not None and 'protection' not in kwargs:
raise ValueError("At least the 'protection' parameter should be present") raise ValueError("At least the 'protection' parameter should be present")
unrestricted_oid = "1.2.840.10045.2.1" if self._curve.name == "ed25519":
private_key = self._export_private_der(include_ec_params=False) 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, result = PKCS8.wrap(private_key,
unrestricted_oid, oid,
key_params=DerObjectId(self._curve.oid), key_params=params,
**kwargs) **kwargs)
return result return result
@ -718,7 +892,7 @@ class EccKey(object):
def _export_private_pem(self, passphrase, **kwargs): def _export_private_pem(self, passphrase, **kwargs):
from Crypto.IO import PEM 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) return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs)
def _export_private_clear_pkcs8_in_clear_pem(self): def _export_private_clear_pkcs8_in_clear_pem(self):
@ -741,6 +915,11 @@ class EccKey(object):
raise ValueError("Cannot export OpenSSH private keys") raise ValueError("Cannot export OpenSSH private keys")
desc = self._curve.openssh 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() modulus_bytes = self.pointQ.size_in_bytes()
if compress: if compress:
@ -754,6 +933,7 @@ class EccKey(object):
middle = desc.split("-")[2] middle = desc.split("-")[2]
comps = (tobytes(desc), tobytes(middle), public_key) comps = (tobytes(desc), tobytes(middle), public_key)
blob = b"".join([struct.pack(">I", len(x)) + x for x in comps]) blob = b"".join([struct.pack(">I", len(x)) + x for x in comps])
return desc + " " + tostr(binascii.b2a_base64(blob)) return desc + " " + tostr(binascii.b2a_base64(blob))
@ -784,9 +964,7 @@ class EccKey(object):
Only relevant for private keys. Only relevant for private keys.
If ``True`` (default and recommended), the `PKCS#8`_ representation If ``True`` (default and recommended), the `PKCS#8`_ representation
will be used. will be used. Ignored for EdDSA curves, for which PKCS#8 is always used.
If ``False``, the much weaker `PEM encryption`_ mechanism will be used.
protection (string): protection (string):
When a private key is exported with password-protection 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. If ``False`` (default), the method returns the full public key.
This parameter is ignored for EdDSA curves, as compression is
mandatory.
.. warning:: .. warning::
If you don't provide a passphrase, the private key will be If you don't provide a passphrase, the private key will be
exported in the clear! exported in the clear!
@ -811,10 +992,8 @@ class EccKey(object):
.. _PEM: http://www.ietf.org/rfc/rfc1421.txt .. _PEM: http://www.ietf.org/rfc/rfc1421.txt
.. _`PEM encryption`: http://www.ietf.org/rfc/rfc1423.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 .. _OpenSSH: http://www.openssh.com/txt/rfc5656.txt
.. _RFC5480: https://tools.ietf.org/html/rfc5480 .. _RFC5480: https://tools.ietf.org/html/rfc5480
.. _RFC5915: http://www.ietf.org/rfc/rfc5915.txt
.. _SEC1: https://www.secg.org/sec1-v2.pdf .. _SEC1: https://www.secg.org/sec1-v2.pdf
Returns: Returns:
@ -836,6 +1015,10 @@ class EccKey(object):
if not passphrase: if not passphrase:
raise ValueError("Empty passphrase") raise ValueError("Empty passphrase")
use_pkcs8 = args.pop("use_pkcs8", True) 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 ext_format == "PEM":
if use_pkcs8: if use_pkcs8:
if passphrase: if passphrase:
@ -851,7 +1034,7 @@ class EccKey(object):
if use_pkcs8: if use_pkcs8:
return self._export_pkcs8(passphrase=passphrase, **args) return self._export_pkcs8(passphrase=passphrase, **args)
else: else:
return self._export_private_der() return self._export_rfc5915_private_der()
else: else:
raise ValueError("Private keys cannot be exported " raise ValueError("Private keys cannot be exported "
"in the '%s' format" % ext_format) "in the '%s' format" % ext_format)
@ -887,30 +1070,43 @@ def generate(**kwargs):
if kwargs: if kwargs:
raise TypeError("Unknown parameters: " + str(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, d = Integer.random_range(min_inclusive=1,
max_exclusive=curve.order, max_exclusive=curve.order,
randfunc=randfunc) randfunc=randfunc)
new_key = EccKey(curve=curve_name, d=d)
return EccKey(curve=curve_name, d=d) return new_key
def construct(**kwargs): def construct(**kwargs):
"""Build a new ECC key (private or public) starting """Build a new ECC key (private or public) starting
from some base components. 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): 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): 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): 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): 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: Returns:
:class:`EccKey` : a new ECC key object :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 # ValueError is raised if the point is not on the curve
kwargs["point"] = EccPoint(point_x, point_y, curve_name) kwargs["point"] = EccPoint(point_x, point_y, curve_name)
new_key = EccKey(**kwargs)
# Validate that the private key matches the public one # Validate that the private key matches the public one
d = kwargs.get("d", None) # because EccKey will not do that automatically
if d is not None and "point" in kwargs: if new_key.has_private() and 'point' in kwargs:
pub_key = curve.G * d pub_key = curve.G * new_key.d
if pub_key.xy != (point_x, point_y): if pub_key.xy != (point_x, point_y):
raise ValueError("Private and public ECC keys do not match") 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): 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 # Parse the generic subjectPublicKeyInfo structure
oid, ec_point, params = _expand_subject_public_key_info(encoded) oid, ec_point, params = _expand_subject_public_key_info(encoded)
# ec_point must be an encoded OCTET STRING nist_p_oids = (
# params is encoded ECParameters "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 if oid in nist_p_oids:
# distiction for now. # See RFC5480
# 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")
# Parameters are mandatory and encoded as ECParameters
# ECParameters ::= CHOICE { # ECParameters ::= CHOICE {
# namedCurve OBJECT IDENTIFIER # namedCurve OBJECT IDENTIFIER
# -- implicitCurve NULL # -- implicitCurve NULL
# -- specifiedCurve SpecifiedECDomain # -- specifiedCurve SpecifiedECDomain
# } # }
#
# implicitCurve and specifiedCurve are not supported (as per RFC) # 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 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) 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 # 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): def _import_pkcs8(encoded, passphrase):
from Crypto.IO import PKCS8 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) algo_oid, private_key, params = PKCS8.unwrap(encoded, passphrase)
# We accept id-ecPublicKey, id-ecDH, id-ecMQV without making any nist_p_oids = (
# distiction for now. "1.2.840.10045.2.1", # id-ecPublicKey (unrestricted)
unrestricted_oid = "1.2.840.10045.2.1" "1.3.132.1.12", # id-ecDH
ecdh_oid = "1.3.132.1.12" "1.3.132.1.13" # id-ecMQV
ecmqv_oid = "1.3.132.1.13" )
eddsa_oids = (
if algo_oid not in (unrestricted_oid, ecdh_oid, ecmqv_oid): "1.3.101.112" # id-Ed25519
raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % algo_oid) )
if algo_oid in nist_p_oids:
curve_oid = DerObjectId().decode(params).value curve_oid = DerObjectId().decode(params).value
return _import_rfc5915_der(private_key, passphrase, curve_oid)
return _import_private_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): def _import_x509_cert(encoded, *kwargs):
@ -1132,7 +1342,7 @@ def _import_der(encoded, passphrase):
pass pass
try: try:
return _import_private_der(encoded, passphrase) return _import_rfc5915_der(encoded, passphrase)
except UnsupportedEccFeature as err: except UnsupportedEccFeature as err:
raise err raise err
except (ValueError, TypeError, IndexError): except (ValueError, TypeError, IndexError):
@ -1149,7 +1359,12 @@ def _import_der(encoded, passphrase):
def _import_openssh_public(encoded): 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 = [] keyparts = []
while len(keystring) > 4: while len(keystring) > 4:
@ -1157,14 +1372,32 @@ def _import_openssh_public(encoded):
keyparts.append(keystring[4:4 + lk]) keyparts.append(keystring[4:4 + lk])
keystring = keystring[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(): for curve_name, curve in _curves.items():
middle = tobytes(curve.openssh.split("-")[2]) middle = tobytes(curve.openssh.split("-")[2])
if keyparts[1] == middle: if keyparts[1] == middle:
break break
else: 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): 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, from ._openssh import (import_openssh_private_generic,
read_bytes, read_string, check_padding) 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) # https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-04
if name not in _curves: if key_type.startswith("ecdsa-sha2"):
raise UnsupportedEccFeature("Unsupported ECC curve %s" % name)
curve = _curves[name] 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 modulus_bytes = (curve.modulus_bits + 7) // 8
public_key, decrypted = read_bytes(decrypted) 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_x = Integer.from_bytes(public_key[1:1+modulus_bytes])
point_y = Integer.from_bytes(public_key[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) private_key, decrypted = read_bytes(decrypted)
d = Integer.from_bytes(private_key) 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 _, padded = read_string(decrypted) # Comment
check_padding(padded) 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): def import_key(encoded, passphrase=None, curve_name=None):
@ -1206,19 +1502,22 @@ def import_key(encoded, passphrase=None, curve_name=None):
Args: Args:
encoded (bytes or multi-line string): encoded (bytes or multi-line string):
The ECC key to import. 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) - X.509 certificate: binary (DER) or ASCII (PEM).
- An X.509 ``subjectPublicKeyInfo``, binary (DER) or ASCII (PEM) - X.509 ``subjectPublicKeyInfo``: binary (DER) or ASCII (PEM).
- A SEC1_ (or X9.62) byte string. You must also provide the - SEC1_ (or X9.62), as ``bytes``. NIST P curves only. You must also provide the ``curve_name``.
``curve_name``. - OpenSSH line, defined in RFC5656_ and RFC8709_ (ASCII).
- An OpenSSH line (e.g. the content of ``~/.ssh/id_ecdsa``, 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`_) - A binary ``ECPrivateKey`` structure, as defined in `RFC5915`_ (DER).
- In ASCII format (PEM or `OpenSSH 6.5+`_) 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. 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): passphrase (byte string):
The passphrase to use for decrypting a private key. 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. This parameter is ignored if the key in input is not encrypted.
curve_name (string): 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`. 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: Returns:
:class:`EccKey` : a new ECC key object :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 ValueError: when the given key cannot be parsed (possibly because
the pass phrase is wrong). the pass phrase is wrong).
.. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt .. _RFC1421: https://datatracker.ietf.org/doc/html/rfc1421
.. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt .. _RFC1423: https://datatracker.ietf.org/doc/html/rfc1423
.. _RFC5915: http://www.ietf.org/rfc/rfc5915.txt .. _RFC5915: https://datatracker.ietf.org/doc/html/rfc5915
.. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt .. _RFC5656: https://datatracker.ietf.org/doc/html/rfc5656
.. _`OpenSSH 6.5+`: https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf .. _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 .. _SEC1: https://www.secg.org/sec1-v2.pdf
""" """
@ -1285,7 +1592,7 @@ def import_key(encoded, passphrase=None, curve_name=None):
return result return result
# OpenSSH # OpenSSH
if encoded.startswith(b'ecdsa-sha2-'): if encoded.startswith(b'ecdsa-sha2-') or encoded.startswith(b'ssh-ed25519'):
return _import_openssh_public(encoded) return _import_openssh_public(encoded)
# DER # DER

View file

@ -62,3 +62,4 @@ def construct(**kwargs: Union[str, int]) -> EccKey: ...
def import_key(encoded: Union[bytes, str], def import_key(encoded: Union[bytes, str],
passphrase: Optional[str]=None, passphrase: Optional[str]=None,
curve_name:Optional[str]=None) -> EccKey: ... 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 import Random
from Crypto.Util.py3compat import tobytes, bord, tostr 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.Numbers import Integer
from Crypto.Math.Primality import (test_probable_prime, from Crypto.Math.Primality import (test_probable_prime,
@ -339,19 +339,22 @@ class RsaKey(object):
if format == 'PEM' and protection is None: if format == 'PEM' and protection is None:
key_type = 'PRIVATE KEY' key_type = 'PRIVATE KEY'
binary_key = PKCS8.wrap(binary_key, oid, None) binary_key = PKCS8.wrap(binary_key, oid, None,
key_params=DerNull())
else: else:
key_type = 'ENCRYPTED PRIVATE KEY' key_type = 'ENCRYPTED PRIVATE KEY'
if not protection: if not protection:
protection = 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC' protection = 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'
binary_key = PKCS8.wrap(binary_key, oid, binary_key = PKCS8.wrap(binary_key, oid,
passphrase, protection) passphrase, protection,
key_params=DerNull())
passphrase = None passphrase = None
else: else:
key_type = "PUBLIC KEY" key_type = "PUBLIC KEY"
binary_key = _create_subject_public_key_info(oid, binary_key = _create_subject_public_key_info(oid,
DerSequence([self.n, DerSequence([self.n,
self.e]) self.e]),
DerNull()
) )
if format == 'DER': if format == 'DER':

View file

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

View file

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

View file

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

View file

@ -24,31 +24,29 @@
"""Self-test for public-key crypto""" """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={}): def get_tests(config={}):
tests = [] tests = []
from Crypto.SelfTest.PublicKey import test_DSA; tests += test_DSA.get_tests(config=config) tests += test_DSA.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_RSA; tests += test_RSA.get_tests(config=config) tests += test_RSA.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_ECC; tests += test_ECC.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)
tests +=test_import_DSA.get_tests(config=config)
from Crypto.SelfTest.PublicKey import test_import_RSA
tests += test_import_RSA.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) 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 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 import unittest
from binascii import unhexlify
from Crypto.SelfTest.st_common import list_test_cases from Crypto.SelfTest.st_common import list_test_cases
from Crypto.SelfTest.loader import load_test_vectors 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.x, pointRx)
self.assertEqual(pointR.y, pointRy) self.assertEqual(pointR.y, pointRy)
def test_joing_scalar_multiply(self): def test_joint_scalar_multiply(self):
d = 0xa78a236d60baec0c5dd41b33a542463a8255391af64c74ee d = 0xa78a236d60baec0c5dd41b33a542463a8255391af64c74ee
e = 0xc4be3d53ec3089e71e4de8ceab7cce889bc393cd85b972bc e = 0xc4be3d53ec3089e71e4de8ceab7cce889bc393cd85b972bc
pointRx = 0x019f64eed8fa9b72b7dfea82c17c9bfa60ecb9e1778b5bde pointRx = 0x019f64eed8fa9b72b7dfea82c17c9bfa60ecb9e1778b5bde
@ -993,6 +995,9 @@ class TestEccKey_P256(unittest.TestCase):
key = EccKey(curve="secp256r1", d=1) key = EccKey(curve="secp256r1", d=1)
key = EccKey(curve="prime256v1", 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): def test_public_key(self):
point = EccPoint(_curves['p256'].Gx, _curves['p256'].Gy) 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), 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)) 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 # Create reference key pair
# ref_private, ref_public = create_ref_keys_p521() # 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') key = ECC.import_key(key_file, curve_name='P192')
self.assertEqual(self.ref_public, key) 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_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) self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None) 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') key = ECC.import_key(key_file, curve_name='P224')
self.assertEqual(self.ref_public, key) 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_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) self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None) 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') key = ECC.import_key(key_file, curve_name='P256')
self.assertEqual(self.ref_public, key) 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_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) self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None) 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') key = ECC.import_key(key_file, curve_name='P384')
self.assertEqual(self.ref_public, key) 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_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) self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None) 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') key = ECC.import_key(key_file, curve_name='P521')
self.assertEqual(self.ref_public, key) 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_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) self.assertEqual(self.ref_private, key)
key = ECC._import_der(key_file, None) 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) value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded) 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") 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) self.assertEqual(key_file, encoded)
# --- # ---
@ -1094,10 +1102,10 @@ class TestExport_P224(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref) value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded) 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") 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) self.assertEqual(key_file, encoded)
# --- # ---
@ -1348,10 +1356,10 @@ class TestExport_P256(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref) value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded) 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") 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) self.assertEqual(key_file, encoded)
# --- # ---
@ -1627,10 +1635,10 @@ class TestExport_P384(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref) value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded) 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") 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) self.assertEqual(key_file, encoded)
# --- # ---
@ -1911,10 +1919,10 @@ class TestExport_P521(unittest.TestCase):
value = extract_bitstring_from_spki(key_file_compressed_ref) value = extract_bitstring_from_spki(key_file_compressed_ref)
self.assertEqual(value, encoded) 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") 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) self.assertEqual(key_file, encoded)
# --- # ---
@ -2156,6 +2164,231 @@ vv6oYkMIIi7r5oQWAiQDrR2mlrrFDL9V7GH/r8SWQw==
self.assertEqual(low16, 0x9643) 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={}): def get_tests(config={}):
tests = [] tests = []
tests += list_test_cases(TestImport) tests += list_test_cases(TestImport)
@ -2165,12 +2398,15 @@ def get_tests(config={}):
tests += list_test_cases(TestImport_P256) tests += list_test_cases(TestImport_P256)
tests += list_test_cases(TestImport_P384) tests += list_test_cases(TestImport_P384)
tests += list_test_cases(TestImport_P521) tests += list_test_cases(TestImport_P521)
tests += list_test_cases(TestImport_Ed25519)
tests += list_test_cases(TestExport_P192) tests += list_test_cases(TestExport_P192)
tests += list_test_cases(TestExport_P224) tests += list_test_cases(TestExport_P224)
tests += list_test_cases(TestExport_P256) tests += list_test_cases(TestExport_P256)
tests += list_test_cases(TestExport_P384) tests += list_test_cases(TestExport_P384)
tests += list_test_cases(TestExport_P521) tests += list_test_cases(TestExport_P521)
tests += list_test_cases(TestExport_Ed25519)
except MissingTestVectorException: except MissingTestVectorException:
pass pass
return tests return tests

View file

@ -22,18 +22,20 @@
"""Self-test for signature modules""" """Self-test for signature modules"""
import os import unittest
from . import test_pkcs1_15, test_pss, test_dss, test_eddsa
def get_tests(config={}): def get_tests(config={}):
tests = [] tests = []
from . import test_pkcs1_15; tests += test_pkcs1_15.get_tests(config=config) tests += test_pkcs1_15.get_tests(config=config)
from . import test_pss; tests += test_pss.get_tests(config=config) tests += test_pss.get_tests(config=config)
from . import test_dss; tests += test_dss.get_tests(config=config) tests += test_dss.get_tests(config=config)
tests += test_eddsa.get_tests(config=config)
return tests 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.sign, hash_obj)
self.assertRaises(ValueError, signer.verify, hash_obj, b"\x00" * 40) 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): def test_sign_verify(self):
"""Verify public/private method""" """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): if isinstance(key, EccKey):
order = key._curve.order order = key._curve.order
private_key_attr = 'd' private_key_attr = 'd'
if key._curve.name == "ed25519":
raise ValueError("ECC key is not on a NIST P curve")
elif isinstance(key, DsaKey): elif isinstance(key, DsaKey):
order = Integer(key.q) order = Integer(key.q)
private_key_attr = 'x' private_key_attr = 'x'

View file

@ -33,4 +33,4 @@
A collection of standardized protocols to carry out digital signatures. 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_native_int(s: Any) -> bool: ...
def is_string(x: Any) -> bool: ... def is_string(x: Any) -> bool: ...
def is_bytes(x: Any) -> bool: ...
def BytesIO(b: bytes) -> IO[bytes]: ... def BytesIO(b: bytes) -> IO[bytes]: ...
def StringIO(s: str) -> IO[str]: ... def StringIO(s: str) -> IO[str]: ...

View file

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

View file

@ -443,6 +443,14 @@ ext_modules = [
'src/p521_table.c'], 'src/p521_table.c'],
py_limited_api=True, 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 # Math
Extension("Crypto.Math._modexp", Extension("Crypto.Math._modexp",

View file

@ -28,5 +28,8 @@ p384_table.c: make_ecc_table.py
p521_table.c: make_ecc_table.py p521_table.c: make_ecc_table.py
python make_ecc_table.py p521 4 p521_table python make_ecc_table.py p521 4 p521_table
x25519: x25519.c multiply_64.c
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ -DSYS_BITS=64 -DPROFILE
clean:: 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, const uint8_t *y,
size_t len, size_t len,
const EcContext *ec_ctx); 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, EXPORT_SYM int ec_ws_get_xy(uint8_t *x,
uint8_t *y, uint8_t *y,
size_t len, size_t len,

View file

@ -36,6 +36,9 @@
#include "multiply.h" #include "multiply.h"
#include "mont.h" #include "mont.h"
#include "ec.h" #include "ec.h"
#include "p256_table.h"
#include "p384_table.h"
#include "p521_table.h"
#include "p256_table.h" #include "p256_table.h"
#include "p384_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. * Create a new EC point on the given EC curve.
* *
* @param pecp The memory area where the pointer to the newly allocated EC * @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 x The X-coordinate (affine, big-endian)
* @param y The Y-coordinate (affine, big-endian) * @param y The Y-coordinate (affine, big-endian)
* @param len The length of x and y in bytes * @param len The length of x and y in bytes
@ -1062,7 +1065,7 @@ cleanup:
return res; return res;
} }
EXPORT_SYM void ec_free_point(EcPoint *ecp) EXPORT_SYM void ec_ws_free_point(EcPoint *ecp)
{ {
if (NULL == ecp) if (NULL == ecp)
return; return;
@ -1172,42 +1175,6 @@ EXPORT_SYM int ec_ws_add(EcPoint *ecpa, EcPoint *ecpb)
return 0; 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 * Blind the scalar factor to be used in an EC multiplication
* *
@ -1473,22 +1440,6 @@ cleanup:
return res; 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 * Compare two EC points and return 0 if they match
*/ */
@ -1550,3 +1501,18 @@ EXPORT_SYM int ec_ws_neg(EcPoint *p)
return 0; 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"); printf("\n");
#endif #endif
ec_free_point(gp); ec_ws_free_point(gp);
ec_free_point(ecp); ec_ws_free_point(ecp);
ec_free_context(ec_ctx); ec_free_context(ec_ctx);
return 0; return 0;

View file

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

View file

@ -122,8 +122,8 @@ int main(void)
printf("\n"); printf("\n");
#endif #endif
ec_free_point(gp); ec_ws_free_point(gp);
ec_free_point(ecp); ec_ws_free_point(ecp);
ec_free_context(ec_ctx); ec_free_context(ec_ctx);
return 0; 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
#endif #endif
static inline unsigned is_odd(uint64_t x) #include "bignum.c"
{
return 1 == (x & 1);
}
static inline unsigned is_even(uint64_t x)
{
return !is_odd(x);
}
/** /**
* Compute the inverse modulo 2 of a 64-bit odd integer. * Compute the inverse modulo 2 of a 64-bit odd integer.
@ -88,61 +80,6 @@ STATIC uint64_t inverse64(uint64_t a)
return x; 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. * 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. * 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 **/ /** Divide by R and possibly subtract n **/
sub(t2, &t[nw], n, nw); sub(t2, &t[nw], n, nw);
cond = (unsigned)(t[2*nw] | (uint64_t)ge(&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) 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 **/ /** Divide by R and possibly subtract n **/
sub(t2, &t[nw], n, WORDS_64); sub(t2, &t[nw], n, WORDS_64);
cond = (unsigned)(t[PREDIV_WORDS_64-1] | (uint64_t)ge(&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 WORDS_64
#undef PREDIV_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 **/ /** Divide by R and possibly subtract n **/
sub(t2, &t[WORDS_64], n, WORDS_64); sub(t2, &t[WORDS_64], n, WORDS_64);
cond = (unsigned)(t[PREDIV_WORDS_64-1] | (uint64_t)ge(&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 WORDS_64
#undef PREDIV_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) 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) if (NULL == out || NULL == a || NULL == b || NULL == tmp || NULL == ctx)
return ERR_NULL; return ERR_NULL;
scratchpad = tmp + ctx->words; return sub_mod(out, a, b, ctx->modulus, tmp, tmp + ctx->words, 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;
} }
/* /*

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\ 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_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)) ifneq (,$(filter $(shell uname -m),x86_64 i386 i686))
CPPFLAGS += -DHAVE_X86INTRIN_H -DUSE_SSE2 -DHAVE_WMMINTRIN_H -DHAVE_TMMINTRIN_H CPPFLAGS += -DHAVE_X86INTRIN_H -DUSE_SSE2 -DHAVE_WMMINTRIN_H -DHAVE_TMMINTRIN_H
@ -33,7 +34,7 @@ build:
all: ${$TGTS} all: ${$TGTS}
clean: clean:
rm -fr build common.pyc rm -fr build common.pyc __pycache__
# utils # utils
@ -44,7 +45,6 @@ build/modexp_utils.o: ../modexp_utils.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $^ $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $^
# ECC # ECC
TABLES = build/p256_table.o build/p384_table.o build/p521_table.o TABLES = build/p256_table.o build/p384_table.o build/p521_table.o
build/p256_table.o: ../p256_table.c 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 # pkcs1
build/test_pkcs1: test_pkcs1.c ../common.h ../pkcs1_decode.c build/test_pkcs1: test_pkcs1.c ../common.h ../pkcs1_decode.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $^ $(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); res = ec_ws_new_point(&ecp, Gx, Gy, 32, ec_ctx);
assert(res == 0); assert(res == 0);
ec_free_point(ecp); ec_ws_free_point(ecp);
res = ec_ws_new_point(&ecp, zero, zero, 32, ec_ctx); res = ec_ws_new_point(&ecp, zero, zero, 32, ec_ctx);
assert(res == 0); assert(res == 0);
ec_free_point(ecp); ec_ws_free_point(ecp);
ec_free_context(ec_ctx); 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(bufx, Gx, 32));
assert(0 == memcmp(bufy, Gy, 32)); assert(0 == memcmp(bufy, Gy, 32));
ec_free_point(ecp); ec_ws_free_point(ecp);
ec_free_context(ec_ctx); 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(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)); 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); 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(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)); 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); 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(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)); 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_ws_free_point(ecp);
ec_free_point(ecp2); ec_ws_free_point(ecp2);
ec_free_context(ec_ctx); 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(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)); 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); 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(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)); 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); 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); 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); 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); 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) void test_ge(void)
{ {
@ -371,7 +371,7 @@ void test_mont_set(void)
mont_context_free(ctx); mont_context_free(ctx);
} }
void test_mont_select() void test_mod_select()
{ {
int res; int res;
MontContext *ctx; MontContext *ctx;
@ -387,17 +387,17 @@ void test_mont_select()
mont_context_init(&ctx, modulusA, 16); mont_context_init(&ctx, modulusA, 16);
memset(c, 0, sizeof c); 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(res == 0);
assert(memcmp(a, c, sizeof c) == 0); assert(memcmp(a, c, sizeof c) == 0);
memset(c, 0, sizeof c); 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(res == 0);
assert(memcmp(a, c, sizeof c) == 0); assert(memcmp(a, c, sizeof c) == 0);
memset(c, 0, sizeof c); 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(res == 0);
assert(memcmp(b, c, sizeof c) == 0); assert(memcmp(b, c, sizeof c) == 0);
@ -408,12 +408,12 @@ void test_mont_select()
mont_context_init(&ctx, modulusB, 17); mont_context_init(&ctx, modulusB, 17);
memset(f, 0, sizeof f); 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(res == 0);
assert(memcmp(d, f, sizeof f) == 0); assert(memcmp(d, f, sizeof f) == 0);
memset(f, 0, sizeof f); 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(res == 0);
assert(memcmp(e, f, sizeof f) == 0); assert(memcmp(e, f, sizeof f) == 0);
} }
@ -429,6 +429,6 @@ int main(void) {
test_mont_sub(); test_mont_sub();
test_mont_inv_prime(); test_mont_inv_prime();
test_mont_set(); test_mont_set();
test_mont_select(); test_mod_select();
return 0; 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"