Skip to content
Snippets Groups Projects
Commit 3e396f89 authored by Josué Ortega's avatar Josué Ortega :fish_cake:
Browse files

New upstream version 0.18.0~b1

parent f8e50c3f
No related branches found
No related tags found
No related merge requests found
Showing with 2580 additions and 114 deletions
......@@ -259,17 +259,17 @@ jobs:
MERGE_BASE=$(git merge-base origin/$BASE_REF HEAD)
echo "MERGE_BASE:" $MERGE_BASE
git checkout $MERGE_BASE
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa/test*.py
instrumental -t ecdsa -i '.*test_.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa/test*.py
instrumental -f .instrumental.cov -s
instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental
git checkout $GITHUB_SHA
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa/test*.py
instrumental -t ecdsa -i '.*test_.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa/test*.py
instrumental -f .instrumental.cov -sr
instrumental -f .instrumental.cov -s | python diff-instrumental.py --read .diff-instrumental --fail-under 70 --max-difference -0.1
- name: instrumental test coverage on push
if: ${{ contains(matrix.opt-deps, 'instrumental') && !github.event.pull_request }}
run: |
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa
instrumental -t ecdsa -i '.*test_.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa
instrumental -f .instrumental.cov -s
# just log the values when merging
instrumental -f .instrumental.cov -s | python diff-instrumental.py
......
*.py[cod]
MANIFEST
htmlcov
# C extensions
*.so
......
* Relase 0.17.0 (27 May 2021)
* Release 0.18.0-beta1 (03 Aug 2021)
New features:
* Support for EdDSA (Ed25519, Ed448) signature creation and verification.
* Support for Ed25519 and Ed448 in PKCS#8 and public key files.
New API:
* CurveEdTw class to represent the Twisted Edwards curve parameters.
* PointEdwards class to represent points on Twisted Edwards curve and
provide point arithmetic on it.
* Release 0.17.0 (27 May 2021)
New API:
* Keys that use explicit curve parameters can now be read and written.
......@@ -38,13 +49,13 @@ Bug fix:
`PointJacobi`, `VerifyingKey`, and `SigningKey` so that it behaves
consistently and in the expected way both in Python 2 and Python 3.
* Implement lock-less algorithm inside PointJacobi for keeping shared state
so that when calculation is aborted with KeyboardInterrupt, the state doesn't
become corrupted (this fixes the occasional breakage of ecdsa in interactive
shells).
so that when a calculation is aborted with KeyboardInterrupt, the state
doesn't become corrupted (this fixes the occasional breakage of ecdsa in
interactive shells).
New features:
* The `speed.py` script now provides performance for signature verification
without use of precomputation.
without the use of precomputation.
* New curves supported: secp112r1, secp112r2, secp128r1, secp160r1.
Performance:
......@@ -89,11 +100,11 @@ Support for reading and writing private keys in PKCS#8 format.
New API:
`to_pem` and `to_der` now accept new parameter, `format`, to specify
the format of the encoded files, either the dafault, legacy "ssleay", or
the format of the encoded files, either the default, legacy `ssleay`, or
the new `pkcs8` to use PKCS#8. Note that only unencrypted PKCS#8 files are
supported.
Add `allow_truncate` to `verify` in `VerifyingKey`, it defaults to True,
when specified as False, use of large hashes smaller than curves will be
when specified as False, the use of large hashes smaller than curves will be
disallowed (as it was in 0.14.1 and earlier).
Bug fix:
......@@ -108,7 +119,7 @@ as such.
Maintenance:
Ensure that version checks will work with Python 4.
Format the source with black.
Fix uses of `assert_` in test suite.
Fix uses of `assert_` in the test suite.
Use newer Ubuntu in Travis to test against OpenSSL 1.1.1 (and thus
test the interoperability of ECDH code in Travis).
......@@ -131,24 +142,24 @@ New API:
`ecdsa.ecdh` module and `ECDH` class.
`PointJacobi` added.
`VerifyingKey.verify_digest`, `SigningKey.sign_digest` and
`SigningKey.sign_digest_deterministic` methods now accept `allow_truncate`
argument to enable use of hashes larger than the curve order.
`SigningKey.sign_digest_deterministic` methods now accept the `allow_truncate`
argument to enable the use of hashes larger than the curve order.
`VerifyingKey` `from_pem` and `from_der` now accept `hashfunc` parameter
like other `from*` methods.
`VerifyingKey` has `precompute` method now.
`VerifyingKey` has the `precompute` method now.
`VerifyingKey.from_public_point` may now not perform validation of public
point when `validate_point=False` argument is passed to method.
point when `validate_point=False` argument is passed to the method.
`CurveFp` constructor now accepts the `h` parameter - the cofactor of the
elliptic curve, it's used for selection of algorithm of public point
elliptic curve, it's used for the selection of algorithm of public point
verification.
Performance:
`randrange` now will now perform much fewer calls to system random number
`randrange` now will perform much fewer calls to the system random number
generator.
`PointJacobi` introduced and used as the underlying implementation; speeds up
the library by a factor of about 20.
Library has now optional dependencies on `gmpy` and `gmpy2`. When they are
availbale, the elliptic curve calculations will be about 3 times faster.
The library has now optional dependencies on `gmpy` and `gmpy2`. When they are
available, the elliptic curve calculations will be about 3 times faster.
Maintenance:
expected minimum version of `six` module (1.9.0) is now specified explicitly
......@@ -173,13 +184,13 @@ Make `SigningKey.sign_digest_deterministic` use default object hashfunc when
none was provided.
`encode_integer` now works for large integers.
Make `encode_oid` and `remove_object` correctly handle OBJECT IDENTIFIERs
with large second subidentifier and padding in encoded subidentifiers.
with large second sub-identifier and padding in encoded sub-identifiers.
New features:
Deterministic signature methods now accept `extra_entropy` parameter to further
randomise the selection of `k` (the nonce) for signature, as specified in
RFC6979.
Recovery of public key from signature is now supported.
Deterministic signature methods now accept the `extra_entropy` parameter to
further randomise the selection of `k` (the nonce) for signature, as specified
in RFC6979.
Recovery of the public key from signature is now supported.
Support for SEC1/X9.62 formatted keys, all three encodings are supported:
"uncompressed", "compressed" and "hybrid". Both string, and PEM/DER will
automatically accept them, if the size of the key matches the curve.
......@@ -204,7 +215,7 @@ added.
`VerifyingKey`: `__repr__` is now supported
Deprecations:
Python 2.5 is not supported any more - dead code removal.
Python 2.5 is not supported anymore - dead code removal.
`from ecdsa.keys import *` will now import only objects defined in that module.
Trying to decode a malformed point using `VerifyingKey.from_string`
will rise now the `MalformedPointError` exception (that inherits from
......@@ -223,10 +234,10 @@ modular_exp: will emit `DeprecationWarning`
Hardening:
Deterministic signatures now verify that the signature won't leak private
key through very unlikely selection of `k` value (the nonce).
key through a very unlikely selection of `k` value (the nonce).
Nonce bit size hiding was added (hardening against Minerva attack). Please
note that it DOES NOT make library secure against side channel attacks (timing
attacks).
note that it DOES NOT make the library secure against side-channel attacks
(timing attacks).
Performance:
The public key in key generation is not verified twice now, making key
......@@ -311,14 +322,15 @@ hashfunc=sha256 in each time they call sign() or verify().
Fix test failure against OpenSSL-1.0.0 (previous versions only worked against
openssl-0.9.8 or earlier). Increase python requirement to py2.5 or later
(still no py3 compatibility, but work is underway). Replace use of obsolete
(still no py3 compatibility, but work is underway). Replace the use of obsolete
'sha' library with modern 'hashlib'. Clean up unit test runner (stop using
subprocesses).
* Release 0.6 (15 Oct 2010)
Small packaging changes: extract version number from git, add 'setup.py test'
command, set exit code correctly on test failure. Fix pyflakes warnings.
Small packaging changes: extract the version number from git, add
'setup.py test' command, set exit code correctly on test failure. Fix pyflakes
warnings.
* Release 0.5 (27 Apr 2010)
......
Metadata-Version: 2.1
Name: ecdsa
Version: 0.17.0
Version: 0.18.0b1
Summary: ECDSA cryptographic signature library (pure python)
Home-page: http://github.com/tlsfuzzer/python-ecdsa
Author: Brian Warner
......@@ -47,7 +47,7 @@ Description: # Pure-Python ECDSA and ECDH
## Dependencies
This library uses only Python and the 'six' package. It is compatible with
Python 2.6, 2.7 and 3.3+. It also supports execution on the alternative
Python 2.6, 2.7, and 3.3+. It also supports execution on alternative
implementations like pypy and pypy3.
If `gmpy2` or `gmpy` is installed, they will be used for faster arithmetic.
......@@ -86,7 +86,7 @@ Description: # Pure-Python ECDSA and ECDH
The following table shows how long this library takes to generate keypairs
(`keygen`), to sign data (`sign`), to verify those signatures (`verify`),
to derive a shared secret (`ecdh`), and
to verify the signatures with no key specific precomputation (`no PC verify`).
to verify the signatures with no key-specific precomputation (`no PC verify`).
All those values are in seconds.
For convenience, the inverses of those values are also provided:
how many keys per second can be generated (`keygen/s`), how many signatures
......@@ -95,7 +95,7 @@ Description: # Pure-Python ECDSA and ECDH
(`ecdh/s`), and how many signatures with no key specific
precomputation can be verified per second (`no PC verify/s`). The size of raw
signature (generally the smallest
way a signature can be encoded) is also provided in the `siglen` column.
the way a signature can be encoded) is also provided in the `siglen` column.
Use `tox -e speed` to generate this table on your own computer.
On an Intel Core i7 4790K @ 4.0GHz I'm getting the following performance:
......@@ -184,8 +184,8 @@ Description: # Pure-Python ECDSA and ECDH
(there's also `gmpy` version, execute it using `tox -e speedgmpy`)
For comparison, a highly optimised implementation (including curve-specific
assembly for some curves), like the one in OpenSSL 1.1.1d, provides following
performance numbers on the same machine.
assembly for some curves), like the one in OpenSSL 1.1.1d, provides the
following performance numbers on the same machine.
Run `openssl speed ecdsa` and `openssl speed ecdh` to reproduce it:
```
sign verify sign/s verify/s
......@@ -255,7 +255,7 @@ Description: # Pure-Python ECDSA and ECDH
a wrapper. The primary use-case of this library is as a portable library for
interoperability testing and as a teaching tool.
**This library does not protect against side channel attacks.**
**This library does not protect against side-channel attacks.**
Do not allow attackers to measure how long it takes you to generate a keypair
or sign a message. Do not allow attackers to run code on the same physical
......@@ -270,7 +270,7 @@ Description: # Pure-Python ECDSA and ECDH
reconstruct the private key**.
Please also note that any Pure-python cryptographic library will be vulnerable
to the same side channel attacks. This is because Python does not provide
to the same side-channel attacks. This is because Python does not provide
side-channel secure primitives (with the exception of
[`hmac.compare_digest()`][3]), making side-channel secure programming
impossible.
......@@ -371,7 +371,7 @@ Description: # Pure-Python ECDSA and ECDH
There are a couple of different ways to compute a signature. Fundamentally,
ECDSA takes a number that represents the data being signed, and returns a
pair of numbers that represent the signature. The `hashfunc=` argument to
`sk.sign()` and `vk.verify()` is used to turn an arbitrary string into
`sk.sign()` and `vk.verify()` is used to turn an arbitrary string into a
fixed-length digest, which is then turned into a number that ECDSA can sign,
and both sign and verify must use the same approach. The default value is
`hashlib.sha1`, but if you use NIST256p or a longer curve, you can use
......
......@@ -39,7 +39,7 @@ curves over prime fields.
## Dependencies
This library uses only Python and the 'six' package. It is compatible with
Python 2.6, 2.7 and 3.3+. It also supports execution on the alternative
Python 2.6, 2.7, and 3.3+. It also supports execution on alternative
implementations like pypy and pypy3.
If `gmpy2` or `gmpy` is installed, they will be used for faster arithmetic.
......@@ -78,7 +78,7 @@ pip install ecdsa[gmpy]
The following table shows how long this library takes to generate keypairs
(`keygen`), to sign data (`sign`), to verify those signatures (`verify`),
to derive a shared secret (`ecdh`), and
to verify the signatures with no key specific precomputation (`no PC verify`).
to verify the signatures with no key-specific precomputation (`no PC verify`).
All those values are in seconds.
For convenience, the inverses of those values are also provided:
how many keys per second can be generated (`keygen/s`), how many signatures
......@@ -87,7 +87,7 @@ per second (`verify/s`), how many shared secrets can be derived per second
(`ecdh/s`), and how many signatures with no key specific
precomputation can be verified per second (`no PC verify/s`). The size of raw
signature (generally the smallest
way a signature can be encoded) is also provided in the `siglen` column.
the way a signature can be encoded) is also provided in the `siglen` column.
Use `tox -e speed` to generate this table on your own computer.
On an Intel Core i7 4790K @ 4.0GHz I'm getting the following performance:
......@@ -176,8 +176,8 @@ On the same machine I'm getting the following performance with `gmpy2`:
(there's also `gmpy` version, execute it using `tox -e speedgmpy`)
For comparison, a highly optimised implementation (including curve-specific
assembly for some curves), like the one in OpenSSL 1.1.1d, provides following
performance numbers on the same machine.
assembly for some curves), like the one in OpenSSL 1.1.1d, provides the
following performance numbers on the same machine.
Run `openssl speed ecdsa` and `openssl speed ecdh` to reproduce it:
```
sign verify sign/s verify/s
......@@ -247,7 +247,7 @@ OpenSSL. [pyca/cryptography](https://cryptography.io) is one example of such
a wrapper. The primary use-case of this library is as a portable library for
interoperability testing and as a teaching tool.
**This library does not protect against side channel attacks.**
**This library does not protect against side-channel attacks.**
Do not allow attackers to measure how long it takes you to generate a keypair
or sign a message. Do not allow attackers to run code on the same physical
......@@ -262,7 +262,7 @@ operation with a private key will be sufficient to completely
reconstruct the private key**.
Please also note that any Pure-python cryptographic library will be vulnerable
to the same side channel attacks. This is because Python does not provide
to the same side-channel attacks. This is because Python does not provide
side-channel secure primitives (with the exception of
[`hmac.compare_digest()`][3]), making side-channel secure programming
impossible.
......@@ -363,7 +363,7 @@ vk2 = VerifyingKey.from_pem(vk_pem)
There are a couple of different ways to compute a signature. Fundamentally,
ECDSA takes a number that represents the data being signed, and returns a
pair of numbers that represent the signature. The `hashfunc=` argument to
`sk.sign()` and `vk.verify()` is used to turn an arbitrary string into
`sk.sign()` and `vk.verify()` is used to turn an arbitrary string into a
fixed-length digest, which is then turned into a number that ECDSA can sign,
and both sign and verify must use the same approach. The default value is
`hashlib.sha1`, but if you use NIST256p or a longer curve, you can use
......
......@@ -98,6 +98,8 @@ print(
)
for curve in [i.name for i in curves]:
if curve == "Ed25519" or curve == "Ed448":
continue
S1 = "from ecdsa import SigningKey, ECDH, {0}".format(curve)
S2 = "our = SigningKey.generate({0})".format(curve)
S3 = "remote = SigningKey.generate({0}).verifying_key".format(curve)
......
Metadata-Version: 2.1
Name: ecdsa
Version: 0.17.0
Version: 0.18.0b1
Summary: ECDSA cryptographic signature library (pure python)
Home-page: http://github.com/tlsfuzzer/python-ecdsa
Author: Brian Warner
......@@ -47,7 +47,7 @@ Description: # Pure-Python ECDSA and ECDH
## Dependencies
This library uses only Python and the 'six' package. It is compatible with
Python 2.6, 2.7 and 3.3+. It also supports execution on the alternative
Python 2.6, 2.7, and 3.3+. It also supports execution on alternative
implementations like pypy and pypy3.
If `gmpy2` or `gmpy` is installed, they will be used for faster arithmetic.
......@@ -86,7 +86,7 @@ Description: # Pure-Python ECDSA and ECDH
The following table shows how long this library takes to generate keypairs
(`keygen`), to sign data (`sign`), to verify those signatures (`verify`),
to derive a shared secret (`ecdh`), and
to verify the signatures with no key specific precomputation (`no PC verify`).
to verify the signatures with no key-specific precomputation (`no PC verify`).
All those values are in seconds.
For convenience, the inverses of those values are also provided:
how many keys per second can be generated (`keygen/s`), how many signatures
......@@ -95,7 +95,7 @@ Description: # Pure-Python ECDSA and ECDH
(`ecdh/s`), and how many signatures with no key specific
precomputation can be verified per second (`no PC verify/s`). The size of raw
signature (generally the smallest
way a signature can be encoded) is also provided in the `siglen` column.
the way a signature can be encoded) is also provided in the `siglen` column.
Use `tox -e speed` to generate this table on your own computer.
On an Intel Core i7 4790K @ 4.0GHz I'm getting the following performance:
......@@ -184,8 +184,8 @@ Description: # Pure-Python ECDSA and ECDH
(there's also `gmpy` version, execute it using `tox -e speedgmpy`)
For comparison, a highly optimised implementation (including curve-specific
assembly for some curves), like the one in OpenSSL 1.1.1d, provides following
performance numbers on the same machine.
assembly for some curves), like the one in OpenSSL 1.1.1d, provides the
following performance numbers on the same machine.
Run `openssl speed ecdsa` and `openssl speed ecdh` to reproduce it:
```
sign verify sign/s verify/s
......@@ -255,7 +255,7 @@ Description: # Pure-Python ECDSA and ECDH
a wrapper. The primary use-case of this library is as a portable library for
interoperability testing and as a teaching tool.
**This library does not protect against side channel attacks.**
**This library does not protect against side-channel attacks.**
Do not allow attackers to measure how long it takes you to generate a keypair
or sign a message. Do not allow attackers to run code on the same physical
......@@ -270,7 +270,7 @@ Description: # Pure-Python ECDSA and ECDH
reconstruct the private key**.
Please also note that any Pure-python cryptographic library will be vulnerable
to the same side channel attacks. This is because Python does not provide
to the same side-channel attacks. This is because Python does not provide
side-channel secure primitives (with the exception of
[`hmac.compare_digest()`][3]), making side-channel secure programming
impossible.
......@@ -371,7 +371,7 @@ Description: # Pure-Python ECDSA and ECDH
There are a couple of different ways to compute a signature. Fundamentally,
ECDSA takes a number that represents the data being signed, and returns a
pair of numbers that represent the signature. The `hashfunc=` argument to
`sk.sign()` and `vk.verify()` is used to turn an arbitrary string into
`sk.sign()` and `vk.verify()` is used to turn an arbitrary string into a
fixed-length digest, which is then turned into a number that ECDSA can sign,
and both sign and verify must use the same approach. The default value is
`hashlib.sha1`, but if you use NIST256p or a longer curve, you can use
......
......@@ -21,11 +21,13 @@ versioneer.py
src/ecdsa/__init__.py
src/ecdsa/_compat.py
src/ecdsa/_rwlock.py
src/ecdsa/_sha3.py
src/ecdsa/_version.py
src/ecdsa/curves.py
src/ecdsa/der.py
src/ecdsa/ecdh.py
src/ecdsa/ecdsa.py
src/ecdsa/eddsa.py
src/ecdsa/ellipticcurve.py
src/ecdsa/errors.py
src/ecdsa/keys.py
......@@ -35,6 +37,7 @@ src/ecdsa/test_curves.py
src/ecdsa/test_der.py
src/ecdsa/test_ecdh.py
src/ecdsa/test_ecdsa.py
src/ecdsa/test_eddsa.py
src/ecdsa/test_ellipticcurve.py
src/ecdsa/test_jacobi.py
src/ecdsa/test_keys.py
......@@ -42,6 +45,7 @@ src/ecdsa/test_malformed_sigs.py
src/ecdsa/test_numbertheory.py
src/ecdsa/test_pyecdsa.py
src/ecdsa/test_rw_lock.py
src/ecdsa/test_sha3.py
src/ecdsa/util.py
src/ecdsa.egg-info/PKG-INFO
src/ecdsa.egg-info/SOURCES.txt
......
......@@ -26,6 +26,8 @@ from .curves import (
SECP112r2,
SECP128r1,
SECP160r1,
Ed25519,
Ed448,
)
from .ecdh import (
ECDH,
......@@ -83,6 +85,8 @@ _hush_pyflakes = [
SECP112r2,
SECP128r1,
SECP160r1,
Ed25519,
Ed448,
six.b(""),
]
del _hush_pyflakes
......@@ -3,6 +3,7 @@ Common functions for providing cross-python version compatibility.
"""
import sys
import re
import binascii
from six import integer_types
......@@ -15,6 +16,7 @@ def str_idx_as_int(string, index):
if sys.version_info < (3, 0): # pragma: no branch
import platform
def normalise_bytes(buffer_object):
"""Cast the input into array of bytes."""
......@@ -24,22 +26,70 @@ if sys.version_info < (3, 0): # pragma: no branch
def hmac_compat(ret):
return ret
if sys.version_info < (2, 7) or sys.version_info < ( # pragma: no branch
2,
7,
4,
):
if (
sys.version_info < (2, 7)
or sys.version_info < (2, 7, 4)
or platform.system() == "Java"
): # pragma: no branch
def remove_whitespace(text):
"""Removes all whitespace from passed in string"""
return re.sub(r"\s+", "", text)
def compat26_str(val):
return str(val)
def bit_length(val):
if val == 0:
return 0
return len(bin(val)) - 2
else:
def remove_whitespace(text):
"""Removes all whitespace from passed in string"""
return re.sub(r"\s+", "", text, flags=re.UNICODE)
def compat26_str(val):
return val
def bit_length(val):
"""Return number of bits necessary to represent an integer."""
return val.bit_length()
def b2a_hex(val):
return binascii.b2a_hex(compat26_str(val))
def a2b_hex(val):
try:
return bytearray(binascii.a2b_hex(val))
except Exception as e:
raise ValueError("base16 error: %s" % e)
def bytes_to_int(val, byteorder):
"""Convert bytes to an int."""
if not val:
return 0
if byteorder == "big":
return int(b2a_hex(val), 16)
if byteorder == "little":
return int(b2a_hex(val[::-1]), 16)
raise ValueError("Only 'big' and 'little' endian supported")
def int_to_bytes(val, length=None, byteorder="big"):
"""Return number converted to bytes"""
if length is None:
length = byte_length(val)
if byteorder == "big":
return bytearray(
(val >> i) & 0xFF for i in reversed(range(0, length * 8, 8))
)
if byteorder == "little":
return bytearray(
(val >> i) & 0xFF for i in range(0, length * 8, 8)
)
raise ValueError("Only 'big' or 'little' endian supported")
else:
if sys.version_info < (3, 4): # pragma: no branch
......@@ -55,6 +105,9 @@ else:
def hmac_compat(data):
return data
def compat26_str(val):
return val
def normalise_bytes(buffer_object):
"""Cast the input into array of bytes."""
return memoryview(buffer_object).cast("B")
......@@ -62,3 +115,34 @@ else:
def remove_whitespace(text):
"""Removes all whitespace from passed in string"""
return re.sub(r"\s+", "", text, flags=re.UNICODE)
def a2b_hex(val):
try:
return bytearray(binascii.a2b_hex(bytearray(val, "ascii")))
except Exception as e:
raise ValueError("base16 error: %s" % e)
# pylint: disable=invalid-name
# pylint is stupid here and deson't notice it's a function, not
# constant
bytes_to_int = int.from_bytes
# pylint: enable=invalid-name
def bit_length(val):
"""Return number of bits necessary to represent an integer."""
return val.bit_length()
def int_to_bytes(val, length=None, byteorder="big"):
"""Convert integer to bytes."""
if length is None:
length = byte_length(val)
# for gmpy we need to convert back to native int
if type(val) != int:
val = int(val)
return bytearray(val.to_bytes(length=length, byteorder=byteorder))
def byte_length(val):
"""Return number of bytes necessary to represent an integer."""
length = bit_length(val)
return (length + 7) // 8
"""
Implementation of the SHAKE-256 algorithm for Ed448
"""
try:
import hashlib
hashlib.new("shake256").digest(64)
def shake_256(msg, outlen):
return hashlib.new("shake256", msg).digest(outlen)
except (TypeError, ValueError):
from ._compat import bytes_to_int, int_to_bytes
# From little endian.
def _from_le(s):
return bytes_to_int(s, byteorder="little")
# Rotate a word x by b places to the left.
def _rol(x, b):
return ((x << b) | (x >> (64 - b))) & (2 ** 64 - 1)
# Do the SHA-3 state transform on state s.
def _sha3_transform(s):
ROTATIONS = [
0,
1,
62,
28,
27,
36,
44,
6,
55,
20,
3,
10,
43,
25,
39,
41,
45,
15,
21,
8,
18,
2,
61,
56,
14,
]
PERMUTATION = [
1,
6,
9,
22,
14,
20,
2,
12,
13,
19,
23,
15,
4,
24,
21,
8,
16,
5,
3,
18,
17,
11,
7,
10,
]
RC = [
0x0000000000000001,
0x0000000000008082,
0x800000000000808A,
0x8000000080008000,
0x000000000000808B,
0x0000000080000001,
0x8000000080008081,
0x8000000000008009,
0x000000000000008A,
0x0000000000000088,
0x0000000080008009,
0x000000008000000A,
0x000000008000808B,
0x800000000000008B,
0x8000000000008089,
0x8000000000008003,
0x8000000000008002,
0x8000000000000080,
0x000000000000800A,
0x800000008000000A,
0x8000000080008081,
0x8000000000008080,
0x0000000080000001,
0x8000000080008008,
]
for rnd in range(0, 24):
# AddColumnParity (Theta)
c = [0] * 5
d = [0] * 5
for i in range(0, 25):
c[i % 5] ^= s[i]
for i in range(0, 5):
d[i] = c[(i + 4) % 5] ^ _rol(c[(i + 1) % 5], 1)
for i in range(0, 25):
s[i] ^= d[i % 5]
# RotateWords (Rho)
for i in range(0, 25):
s[i] = _rol(s[i], ROTATIONS[i])
# PermuteWords (Pi)
t = s[PERMUTATION[0]]
for i in range(0, len(PERMUTATION) - 1):
s[PERMUTATION[i]] = s[PERMUTATION[i + 1]]
s[PERMUTATION[-1]] = t
# NonlinearMixRows (Chi)
for i in range(0, 25, 5):
t = [
s[i],
s[i + 1],
s[i + 2],
s[i + 3],
s[i + 4],
s[i],
s[i + 1],
]
for j in range(0, 5):
s[i + j] = t[j] ^ ((~t[j + 1]) & (t[j + 2]))
# AddRoundConstant (Iota)
s[0] ^= RC[rnd]
# Reinterpret octet array b to word array and XOR it to state s.
def _reinterpret_to_words_and_xor(s, b):
for j in range(0, len(b) // 8):
s[j] ^= _from_le(b[8 * j : 8 * j + 8])
# Reinterpret word array w to octet array and return it.
def _reinterpret_to_octets(w):
mp = bytearray()
for j in range(0, len(w)):
mp += int_to_bytes(w[j], 8, byteorder="little")
return mp
def _sha3_raw(msg, r_w, o_p, e_b):
"""Semi-generic SHA-3 implementation"""
r_b = 8 * r_w
s = [0] * 25
# Handle whole blocks.
idx = 0
blocks = len(msg) // r_b
for i in range(0, blocks):
_reinterpret_to_words_and_xor(s, msg[idx : idx + r_b])
idx += r_b
_sha3_transform(s)
# Handle last block padding.
m = bytearray(msg[idx:])
m.append(o_p)
while len(m) < r_b:
m.append(0)
m[len(m) - 1] |= 128
# Handle padded last block.
_reinterpret_to_words_and_xor(s, m)
_sha3_transform(s)
# Output.
out = bytearray()
while len(out) < e_b:
out += _reinterpret_to_octets(s[:r_w])
_sha3_transform(s)
return out[:e_b]
def shake_256(msg, outlen):
return _sha3_raw(msg, 17, 31, outlen)
......@@ -8,11 +8,11 @@ import json
version_json = '''
{
"date": "2021-05-27T20:07:15+0200",
"date": "2021-08-03T14:09:39+0200",
"dirty": false,
"error": null,
"full-revisionid": "5aa87c52c25a476f691b570be3577ff71cd01982",
"version": "0.17.0"
"full-revisionid": "c8802e5c4f20557b674ef3d724985d40b5ff0537",
"version": "0.18.0b1"
}
''' # END VERSION_JSON
......
from __future__ import division
from six import PY2
from . import der, ecdsa, ellipticcurve
from . import der, ecdsa, ellipticcurve, eddsa
from .util import orderlen, number_to_string, string_to_number
from ._compat import normalise_bytes
from ._compat import normalise_bytes, bit_length
# orderlen was defined in this module previously, so keep it in __all__,
......@@ -33,6 +33,8 @@ __all__ = [
"BRAINPOOLP512r1",
"PRIME_FIELD_OID",
"CHARACTERISTIC_TWO_FIELD_OID",
"Ed25519",
"Ed448",
]
......@@ -51,8 +53,16 @@ class Curve:
self.curve = curve
self.generator = generator
self.order = generator.order()
self.baselen = orderlen(self.order)
self.verifying_key_length = 2 * orderlen(curve.p())
if isinstance(curve, ellipticcurve.CurveEdTw):
# EdDSA keys are special in that both private and public
# are the same size (as it's defined only with compressed points)
# +1 for the sign bit and then round up
self.baselen = (bit_length(curve.p()) + 1 + 7) // 8
self.verifying_key_length = self.baselen
else:
self.baselen = orderlen(self.order)
self.verifying_key_length = 2 * orderlen(curve.p())
self.signature_length = 2 * self.baselen
self.oid = oid
if oid:
......@@ -90,6 +100,11 @@ class Curve:
else:
encoding = "explicit"
if encoding not in ("named_curve", "explicit"):
raise ValueError(
"Only 'named_curve' and 'explicit' encodings supported"
)
if encoding == "named_curve":
if not self.oid:
raise UnknownCurveError(
......@@ -97,6 +112,11 @@ class Curve:
"associated curve OID"
)
return der.encode_oid(*self.oid)
elif isinstance(self.curve, ellipticcurve.CurveEdTw):
assert encoding == "explicit"
raise UnknownCurveError(
"Twisted Edwards curves don't support explicit encoding"
)
# encode the ECParameters sequence
curve_p = self.curve.p()
......@@ -408,6 +428,16 @@ BRAINPOOLP512r1 = Curve(
)
Ed25519 = Curve(
"Ed25519", eddsa.curve_ed25519, eddsa.generator_ed25519, (1, 3, 101, 112),
)
Ed448 = Curve(
"Ed448", eddsa.curve_ed448, eddsa.generator_ed448, (1, 3, 101, 113),
)
# no order in particular, but keep previously added curves first
curves = [
NIST192p,
......@@ -427,6 +457,8 @@ curves = [
SECP112r2,
SECP128r1,
SECP160r1,
Ed25519,
Ed448,
]
......
"""Implementation of Edwards Digital Signature Algorithm."""
import hashlib
from ._sha3 import shake_256
from . import ellipticcurve
from ._compat import (
remove_whitespace,
bit_length,
bytes_to_int,
int_to_bytes,
compat26_str,
)
# edwards25519, defined in RFC7748
_p = 2 ** 255 - 19
_a = -1
_d = int(
remove_whitespace(
"370957059346694393431380835087545651895421138798432190163887855330"
"85940283555"
)
)
_h = 8
_Gx = int(
remove_whitespace(
"151122213495354007725011514095885315114540126930418572060461132"
"83949847762202"
)
)
_Gy = int(
remove_whitespace(
"463168356949264781694283940034751631413079938662562256157830336"
"03165251855960"
)
)
_r = 2 ** 252 + 0x14DEF9DEA2F79CD65812631A5CF5D3ED
def _sha512(data):
return hashlib.new("sha512", compat26_str(data)).digest()
curve_ed25519 = ellipticcurve.CurveEdTw(_p, _a, _d, _h, _sha512)
generator_ed25519 = ellipticcurve.PointEdwards(
curve_ed25519, _Gx, _Gy, 1, _Gx * _Gy % _p, _r
)
# edwards448, defined in RFC7748
_p = 2 ** 448 - 2 ** 224 - 1
_a = 1
_d = -39081 % _p
_h = 4
_Gx = int(
remove_whitespace(
"224580040295924300187604334099896036246789641632564134246125461"
"686950415467406032909029192869357953282578032075146446173674602635"
"247710"
)
)
_Gy = int(
remove_whitespace(
"298819210078481492676017930443930673437544040154080242095928241"
"372331506189835876003536878655418784733982303233503462500531545062"
"832660"
)
)
_r = 2 ** 446 - 0x8335DC163BB124B65129C96FDE933D8D723A70AADC873D6D54A7BB0D
def _shake256(data):
return shake_256(data, 114)
curve_ed448 = ellipticcurve.CurveEdTw(_p, _a, _d, _h, _shake256)
generator_ed448 = ellipticcurve.PointEdwards(
curve_ed448, _Gx, _Gy, 1, _Gx * _Gy % _p, _r
)
class PublicKey(object):
"""Public key for the Edwards Digital Signature Algorithm."""
def __init__(self, generator, public_key, public_point=None):
self.generator = generator
self.curve = generator.curve()
self.__encoded = public_key
# plus one for the sign bit and round up
self.baselen = (bit_length(self.curve.p()) + 1 + 7) // 8
if len(public_key) != self.baselen:
raise ValueError(
"Incorrect size of the public key, expected: {0} bytes".format(
self.baselen
)
)
if public_point:
self.__point = public_point
else:
self.__point = ellipticcurve.PointEdwards.from_bytes(
self.curve, public_key
)
def __eq__(self, other):
if isinstance(other, PublicKey):
return (
self.curve == other.curve and self.__encoded == other.__encoded
)
return NotImplemented
def __ne__(self, other):
return not self == other
@property
def point(self):
return self.__point
def public_point(self):
return self.__point
def public_key(self):
return self.__encoded
def verify(self, data, signature):
"""Verify a Pure EdDSA signature over data."""
data = compat26_str(data)
if len(signature) != 2 * self.baselen:
raise ValueError(
"Invalid signature length, expected: {0} bytes".format(
2 * self.baselen
)
)
R = ellipticcurve.PointEdwards.from_bytes(
self.curve, signature[: self.baselen]
)
S = bytes_to_int(signature[self.baselen :], "little")
if S >= self.generator.order():
raise ValueError("Invalid signature")
dom = bytearray()
if self.curve == curve_ed448:
dom = bytearray(b"SigEd448" + b"\x00\x00")
k = bytes_to_int(
self.curve.hash_func(dom + R.to_bytes() + self.__encoded + data),
"little",
)
if self.generator * S != self.__point * k + R:
raise ValueError("Invalid signature")
return True
class PrivateKey(object):
"""Private key for the Edwards Digital Signature Algorithm."""
def __init__(self, generator, private_key):
self.generator = generator
self.curve = generator.curve()
# plus one for the sign bit and round up
self.baselen = (bit_length(self.curve.p()) + 1 + 7) // 8
if len(private_key) != self.baselen:
raise ValueError(
"Incorrect size of private key, expected: {0} bytes".format(
self.baselen
)
)
self.__private_key = bytes(private_key)
self.__h = bytearray(self.curve.hash_func(private_key))
self.__public_key = None
a = self.__h[: self.baselen]
a = self._key_prune(a)
scalar = bytes_to_int(a, "little")
self.__s = scalar
@property
def private_key(self):
return self.__private_key
def __eq__(self, other):
if isinstance(other, PrivateKey):
return (
self.curve == other.curve
and self.__private_key == other.__private_key
)
return NotImplemented
def __ne__(self, other):
return not self == other
def _key_prune(self, key):
# make sure the key is not in a small subgroup
h = self.curve.cofactor()
if h == 4:
h_log = 2
elif h == 8:
h_log = 3
else:
raise ValueError("Only cofactor 4 and 8 curves supported")
key[0] &= ~((1 << h_log) - 1)
# ensure the highest bit is set but no higher
l = bit_length(self.curve.p())
if l % 8 == 0:
key[-1] = 0
key[-2] |= 0x80
else:
key[-1] = key[-1] & (1 << (l % 8)) - 1 | 1 << (l % 8) - 1
return key
def public_key(self):
"""Generate the public key based on the included private key"""
if self.__public_key:
return self.__public_key
public_point = self.generator * self.__s
self.__public_key = PublicKey(
self.generator, public_point.to_bytes(), public_point
)
return self.__public_key
def sign(self, data):
"""Perform a Pure EdDSA signature over data."""
data = compat26_str(data)
A = self.public_key().public_key()
prefix = self.__h[self.baselen :]
dom = bytearray()
if self.curve == curve_ed448:
dom = bytearray(b"SigEd448" + b"\x00\x00")
r = bytes_to_int(self.curve.hash_func(dom + prefix + data), "little")
R = (self.generator * r).to_bytes()
k = bytes_to_int(self.curve.hash_func(dom + R + A + data), "little")
k %= self.generator.order()
S = (r + k * self.__s) % self.generator.order()
return R + int_to_bytes(S, self.baselen, "little")
......@@ -49,14 +49,14 @@ except ImportError: # pragma: no branch
from six import python_2_unicode_compatible
from . import numbertheory
from ._compat import normalise_bytes
from ._compat import normalise_bytes, int_to_bytes, bit_length, bytes_to_int
from .errors import MalformedPointError
from .util import orderlen, string_to_number, number_to_string
@python_2_unicode_compatible
class CurveFp(object):
"""Elliptic Curve over the field of integers modulo a prime."""
"""Short Weierstrass Elliptic Curve over a prime field."""
if GMPY: # pragma: no branch
......@@ -141,6 +141,86 @@ class CurveFp(object):
)
class CurveEdTw(object):
"""Parameters for a Twisted Edwards Elliptic Curve"""
if GMPY: # pragma: no branch
def __init__(self, p, a, d, h=None, hash_func=None):
"""
The curve of points satisfying a*x^2 + y^2 = 1 + d*x^2*y^2 (mod p).
h is the cofactor of the curve.
hash_func is the hash function associated with the curve
(like SHA-512 for Ed25519)
"""
self.__p = mpz(p)
self.__a = mpz(a)
self.__d = mpz(d)
self.__h = h
self.__hash_func = hash_func
else:
def __init__(self, p, a, d, h=None, hash_func=None):
"""
The curve of points satisfying a*x^2 + y^2 = 1 + d*x^2*y^2 (mod p).
h is the cofactor of the curve.
hash_func is the hash function associated with the curve
(like SHA-512 for Ed25519)
"""
self.__p = p
self.__a = a
self.__d = d
self.__h = h
self.__hash_func = hash_func
def __eq__(self, other):
"""Returns True if other is an identical curve."""
if isinstance(other, CurveEdTw):
p = self.__p
return (
self.__p == other.__p
and self.__a % p == other.__a % p
and self.__d % p == other.__d % p
)
return NotImplemented
def __ne__(self, other):
"""Return False if the other is an identical curve, True otherwise."""
return not self == other
def __hash__(self):
return hash((self.__p, self.__a, self.__d))
def contains_point(self, x, y):
"""Is the point (x, y) on this curve?"""
return (
self.__a * x * x + y * y - 1 - self.__d * x * x * y * y
) % self.__p == 0
def p(self):
return self.__p
def a(self):
return self.__a
def d(self):
return self.__d
def hash_func(self, data):
return self.__hash_func(data)
def cofactor(self):
return self.__h
def __str__(self):
return "CurveEdTw(p={0}, a={1}, d={2}, h={3})".format(
self.__p, self.__a, self.__d, self.__h,
)
class AbstractPoint(object):
"""Class for common methods of elliptic curve points."""
......@@ -207,6 +287,41 @@ class AbstractPoint(object):
return x, y
@classmethod
def _from_edwards(cls, curve, data):
"""Decode a point on an Edwards curve."""
data = bytearray(data)
p = curve.p()
# add 1 for the sign bit and then round up
exp_len = (bit_length(p) + 1 + 7) // 8
if len(data) != exp_len:
raise MalformedPointError("Point length doesn't match the curve.")
x_0 = (data[-1] & 0x80) >> 7
data[-1] &= 0x80 - 1
y = bytes_to_int(data, "little")
if GMPY:
y = mpz(y)
x2 = (
(y * y - 1)
* numbertheory.inverse_mod(curve.d() * y * y - curve.a(), p)
% p
)
try:
x = numbertheory.square_root_mod_prime(x2, p)
except numbertheory.SquareRootError as e:
raise MalformedPointError(
"Encoding does not correspond to a point on curve", e
)
if x % 2 != x_0:
x = -x % p
return x, y
@classmethod
def from_bytes(
cls, curve, data, validate_encoding=True, valid_encodings=None
......@@ -254,6 +369,10 @@ class AbstractPoint(object):
"supported."
)
data = normalise_bytes(data)
if isinstance(curve, CurveEdTw):
return cls._from_edwards(curve, data)
key_len = len(data)
raw_encoding_length = 2 * orderlen(curve.p())
if key_len == raw_encoding_length and "raw" in valid_encodings:
......@@ -310,6 +429,18 @@ class AbstractPoint(object):
return b"\x07" + raw_enc
return b"\x06" + raw_enc
def _edwards_encode(self):
"""Encode the point according to RFC8032 encoding."""
self.scale()
x, y, p = self.x(), self.y(), self.curve().p()
# add 1 for the sign bit and then round up
enc_len = (bit_length(p) + 1 + 7) // 8
y_str = int_to_bytes(y, enc_len, "little")
if x % 2:
y_str[-1] |= 0x80
return y_str
def to_bytes(self, encoding="raw"):
"""
Convert the point to a byte string.
......@@ -318,11 +449,17 @@ class AbstractPoint(object):
by `encoding="raw"`. It can also output points in :term:`uncompressed`,
:term:`compressed`, and :term:`hybrid` formats.
For points on Edwards curves `encoding` is ignored and only the
encoding defined in RFC 8032 is supported.
:return: :term:`raw encoding` of a public on the curve
:rtype: bytes
"""
assert encoding in ("raw", "uncompressed", "compressed", "hybrid")
if encoding == "raw":
curve = self.curve()
if isinstance(curve, CurveEdTw):
return self._edwards_encode()
elif encoding == "raw":
return self._raw_encode()
elif encoding == "uncompressed":
return b"\x04" + self._raw_encode()
......@@ -331,10 +468,26 @@ class AbstractPoint(object):
else:
return self._compressed_encode()
@staticmethod
def _naf(mult):
"""Calculate non-adjacent form of number."""
ret = []
while mult:
if mult % 2:
nd = mult % 4
if nd >= 2:
nd -= 4
ret.append(nd)
mult -= nd
else:
ret.append(0)
mult //= 2
return ret
class PointJacobi(AbstractPoint):
"""
Point on an elliptic curve. Uses Jacobi coordinates.
Point on a short Weierstrass elliptic curve. Uses Jacobi coordinates.
In Jacobian coordinates, there are three parameters, X, Y and Z.
They correspond to affine parameters 'x' and 'y' like so:
......@@ -773,22 +926,6 @@ class PointJacobi(AbstractPoint):
return INFINITY
return PointJacobi(self.__curve, X3, Y3, Z3, self.__order)
@staticmethod
def _naf(mult):
"""Calculate non-adjacent form of number."""
ret = []
while mult:
if mult % 2:
nd = mult % 4
if nd >= 2:
nd -= 4
ret.append(nd)
mult -= nd
else:
ret.append(0)
mult //= 2
return ret
def __mul__(self, other):
"""Multiply point by an integer."""
if not self.__coords[1] or not other:
......@@ -927,8 +1064,8 @@ class PointJacobi(AbstractPoint):
class Point(AbstractPoint):
"""A point on an elliptic curve. Altering x and y is forbidden,
but they can be read by the x() and y() methods."""
"""A point on a short Weierstrass elliptic curve. Altering x and y is
forbidden, but they can be read by the x() and y() methods."""
def __init__(self, curve, x, y, order=None):
"""curve, x, y, order; order (optional) is the order of this point."""
......@@ -1124,5 +1261,255 @@ class Point(AbstractPoint):
return self.__order
class PointEdwards(AbstractPoint):
"""Point on Twisted Edwards curve.
Internally represents the coordinates on the curve using four parameters,
X, Y, Z, T. They correspond to affine parameters 'x' and 'y' like so:
x = X / Z
y = Y / Z
x*y = T / Z
"""
def __init__(self, curve, x, y, z, t, order=None):
"""
Initialise a point that uses the extended coordinates interanlly.
"""
super(PointEdwards, self).__init__()
self.__curve = curve
if GMPY: # pragma: no branch
self.__coords = (mpz(x), mpz(y), mpz(z), mpz(t))
self.__order = order and mpz(order)
else: # pragma: no branch
self.__coords = (x, y, z, t)
self.__order = order
@classmethod
def from_bytes(
cls,
curve,
data,
validate_encoding=None,
valid_encodings=None,
order=None,
generator=False,
):
"""
Initialise the object from byte encoding of a point.
`validate_encoding` and `valid_encodings` are provided for
compatibility with Weierstrass curves, they are ignored for Edwards
points.
:param data: single point encoding of the public key
:type data: :term:`bytes-like object`
:param curve: the curve on which the public key is expected to lay
:type curve: ecdsa.ellipticcurve.CurveEdTw
:param None validate_encoding: Ignored, encoding is always validated
:param None valid_encodings: Ignored, there is just one encoding
supported
:param int order: the point order, must be non zero when using
generator=True
:param bool generator: Ignored, may be used in the future
to precompute point multiplication table.
:raises MalformedPointError: if the public point does not lay on the
curve or the encoding is invalid
:return: Initialised point on an Edwards curve
:rtype: PointEdwards
"""
coord_x, coord_y = super(PointEdwards, cls).from_bytes(
curve, data, validate_encoding, valid_encodings
)
return PointEdwards(
curve, coord_x, coord_y, 1, coord_x * coord_y, order
)
def x(self):
"""Return affine x coordinate."""
X1, _, Z1, _ = self.__coords
if Z1 == 1:
return X1
p = self.__curve.p()
z_inv = numbertheory.inverse_mod(Z1, p)
return X1 * z_inv % p
def y(self):
"""Return affine y coordinate."""
_, Y1, Z1, _ = self.__coords
if Z1 == 1:
return Y1
p = self.__curve.p()
z_inv = numbertheory.inverse_mod(Z1, p)
return Y1 * z_inv % p
def curve(self):
"""Return the curve of the point."""
return self.__curve
def order(self):
return self.__order
def scale(self):
"""
Return point scaled so that z == 1.
Modifies point in place, returns self.
"""
X1, Y1, Z1, _ = self.__coords
if Z1 == 1:
return self
p = self.__curve.p()
z_inv = numbertheory.inverse_mod(Z1, p)
x = X1 * z_inv % p
y = Y1 * z_inv % p
t = x * y % p
self.__coords = (x, y, 1, t)
return self
def __eq__(self, other):
"""Compare for equality two points with each-other.
Note: only points on the same curve can be equal.
"""
x1, y1, z1, t1 = self.__coords
if other is INFINITY:
return not x1 or not t1
if isinstance(other, PointEdwards):
x2, y2, z2, t2 = other.__coords
else:
return NotImplemented
if self.__curve != other.curve():
return False
p = self.__curve.p()
# cross multiply to eliminate divisions
xn1 = x1 * z2 % p
xn2 = x2 * z1 % p
yn1 = y1 * z2 % p
yn2 = y2 * z1 % p
return xn1 == xn2 and yn1 == yn2
def __ne__(self, other):
"""Compare for inequality two points with each-other."""
return not self == other
def _add(self, X1, Y1, Z1, T1, X2, Y2, Z2, T2, p, a):
"""add two points, assume sane parameters."""
# after add-2008-hwcd-2
# from https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html
# NOTE: there are more efficient formulas for Z1 or Z2 == 1
A = X1 * X2 % p
B = Y1 * Y2 % p
C = Z1 * T2 % p
D = T1 * Z2 % p
E = D + C
F = ((X1 - Y1) * (X2 + Y2) + B - A) % p
G = B + a * A
H = D - C
if not H:
return self._double(X1, Y1, Z1, T1, p, a)
X3 = E * F % p
Y3 = G * H % p
T3 = E * H % p
Z3 = F * G % p
return X3, Y3, Z3, T3
def __add__(self, other):
"""Add point to another."""
if other == INFINITY:
return self
if (
not isinstance(other, PointEdwards)
or self.__curve != other.__curve
):
raise ValueError("The other point is on a different curve.")
p, a = self.__curve.p(), self.__curve.a()
X1, Y1, Z1, T1 = self.__coords
X2, Y2, Z2, T2 = other.__coords
X3, Y3, Z3, T3 = self._add(X1, Y1, Z1, T1, X2, Y2, Z2, T2, p, a)
if not X3 or not T3:
return INFINITY
return PointEdwards(self.__curve, X3, Y3, Z3, T3, self.__order)
def __radd__(self, other):
"""Add other to self."""
return self + other
def _double(self, X1, Y1, Z1, T1, p, a):
"""Double the point, assume sane parameters."""
# after "dbl-2008-hwcd"
# from https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html
# NOTE: there are more efficient formulas for Z1 == 1
A = X1 * X1 % p
B = Y1 * Y1 % p
C = 2 * Z1 * Z1 % p
D = a * A % p
E = ((X1 + Y1) * (X1 + Y1) - A - B) % p
G = D + B
F = G - C
H = D - B
X3 = E * F % p
Y3 = G * H % p
T3 = E * H % p
Z3 = F * G % p
return X3, Y3, Z3, T3
def double(self):
"""Return point added to itself."""
X1, Y1, Z1, T1 = self.__coords
if not X1 or not T1:
return INFINITY
p, a = self.__curve.p(), self.__curve.a()
X3, Y3, Z3, T3 = self._double(X1, Y1, Z1, T1, p, a)
if not X3 or not T3:
return INFINITY
return PointEdwards(self.__curve, X3, Y3, Z3, T3, self.__order)
def __rmul__(self, other):
"""Multiply point by an integer."""
return self * other
def __mul__(self, other):
"""Multiply point by an integer."""
X2, Y2, Z2, T2 = self.__coords
if not X2 or not T2 or not other:
return INFINITY
if other == 1:
return self
if self.__order:
# order*2 as a protection for Minerva
other = other % (self.__order * 2)
X3, Y3, Z3, T3 = 0, 1, 1, 0 # INFINITY in extended coordinates
p, a = self.__curve.p(), self.__curve.a()
_double = self._double
_add = self._add
for i in reversed(self._naf(other)):
X3, Y3, Z3, T3 = _double(X3, Y3, Z3, T3, p, a)
if i < 0:
X3, Y3, Z3, T3 = _add(X3, Y3, Z3, T3, -X2, Y2, Z2, -T2, p, a)
elif i > 0:
X3, Y3, Z3, T3 = _add(X3, Y3, Z3, T3, X2, Y2, Z2, T2, p, a)
if not X3 or not T3:
return INFINITY
return PointEdwards(self.__curve, X3, Y3, Z3, T3, self.__order)
# This one point is the Point At Infinity for all purposes:
INFINITY = Point(None, None, None)
This diff is collapsed.
......@@ -6,7 +6,7 @@ except ImportError:
import base64
import pytest
from .curves import Curve, NIST256p, curves, UnknownCurveError, PRIME_FIELD_OID
from .ellipticcurve import CurveFp, PointJacobi
from .ellipticcurve import CurveFp, PointJacobi, CurveEdTw
from . import der
from .util import number_to_string
......@@ -109,6 +109,12 @@ class TestParameterEncoding(unittest.TestCase):
self.assertEqual(encoded, bytes(base64.b64decode(self.base64_params)))
def test_encoding_to_unsupported_type(self):
with self.assertRaises(ValueError) as e:
NIST256p.to_der("unsupported")
self.assertIn("Only 'named_curve'", str(e.exception))
def test_encoding_to_explicit_compressed_params(self):
encoded = NIST256p.to_der("explicit", "compressed")
......@@ -291,9 +297,13 @@ def test_curve_params_encode_decode_named(curve):
@pytest.mark.parametrize("curve", curves, ids=[i.name for i in curves])
def test_curve_params_encode_decode_explicit(curve):
ret = Curve.from_der(curve.to_der("explicit"))
if isinstance(curve.curve, CurveEdTw):
with pytest.raises(UnknownCurveError):
curve.to_der("explicit")
else:
ret = Curve.from_der(curve.to_der("explicit"))
assert curve == ret
assert curve == ret
@pytest.mark.parametrize("curve", curves, ids=[i.name for i in curves])
......@@ -305,6 +315,10 @@ def test_curve_params_encode_decode_default(curve):
@pytest.mark.parametrize("curve", curves, ids=[i.name for i in curves])
def test_curve_params_encode_decode_explicit_compressed(curve):
ret = Curve.from_der(curve.to_der("explicit", "compressed"))
if isinstance(curve.curve, CurveEdTw):
with pytest.raises(UnknownCurveError):
curve.to_der("explicit", "compressed")
else:
ret = Curve.from_der(curve.to_der("explicit", "compressed"))
assert curve == ret
assert curve == ret
......@@ -26,12 +26,15 @@ from .ecdh import (
NoCurveError,
)
from .keys import SigningKey, VerifyingKey
from .ellipticcurve import CurveEdTw
@pytest.mark.parametrize(
"vcurve", curves, ids=[curve.name for curve in curves]
"vcurve", curves, ids=[curve.name for curve in curves],
)
def test_ecdh_each(vcurve):
if isinstance(vcurve.curve, CurveEdTw):
pytest.skip("ECDH is not supported for Edwards curves")
ecdh1 = ECDH(curve=vcurve)
ecdh2 = ECDH(curve=vcurve)
......@@ -362,9 +365,12 @@ OPENSSL_SUPPORTED_CURVES = set(
@pytest.mark.parametrize(
"vcurve", curves, ids=[curve.name for curve in curves]
"vcurve", curves, ids=[curve.name for curve in curves],
)
def test_ecdh_with_openssl(vcurve):
if isinstance(vcurve.curve, CurveEdTw):
pytest.skip("Edwards curves are not supported for ECDH")
assert vcurve.openssl_name
if vcurve.openssl_name not in OPENSSL_SUPPORTED_CURVES:
......
This diff is collapsed.
......@@ -14,7 +14,13 @@ import pytest
import hashlib
from .keys import VerifyingKey, SigningKey, MalformedPointError
from .der import unpem, UnexpectedDER
from .der import (
unpem,
UnexpectedDER,
encode_sequence,
encode_oid,
encode_bitstring,
)
from .util import (
sigencode_string,
sigencode_der,
......@@ -23,7 +29,7 @@ from .util import (
sigdecode_der,
sigdecode_strings,
)
from .curves import NIST256p, Curve, BRAINPOOLP160r1
from .curves import NIST256p, Curve, BRAINPOOLP160r1, Ed25519, Ed448
from .ellipticcurve import Point, PointJacobi, CurveFp, INFINITY
from .ecdsa import generator_brainpoolp160r1
......@@ -268,6 +274,195 @@ class TestVerifyingKeyFromDer(unittest.TestCase):
self.assertEqual(vk, self.vk)
def test_ed25519_VerifyingKey_repr__(self):
sk = SigningKey.from_string(Ed25519.generator.to_bytes(), Ed25519)
string = repr(sk.verifying_key)
self.assertEqual(
"VerifyingKey.from_string("
"bytearray(b'K\\x0c\\xfbZH\\x8e\\x8c\\x8c\\x07\\xee\\xda\\xfb"
"\\xe1\\x97\\xcd\\x90\\x18\\x02\\x15h]\\xfe\\xbe\\xcbB\\xba\\xe6r"
"\\x10\\xae\\xf1P'), Ed25519, None)",
string,
)
def test_edwards_from_public_point(self):
point = Ed25519.generator
with self.assertRaises(ValueError) as e:
VerifyingKey.from_public_point(point, Ed25519)
self.assertIn("incompatible with Edwards", str(e.exception))
def test_edwards_precompute_no_side_effect(self):
sk = SigningKey.from_string(Ed25519.generator.to_bytes(), Ed25519)
vk = sk.verifying_key
vk2 = VerifyingKey.from_string(vk.to_string(), Ed25519)
vk.precompute()
self.assertEqual(vk, vk2)
def test_parse_malfomed_eddsa_der_pubkey(self):
der_str = encode_sequence(
encode_sequence(encode_oid(*Ed25519.oid)),
encode_bitstring(bytes(Ed25519.generator.to_bytes()), 0),
encode_bitstring(b"\x00", 0),
)
with self.assertRaises(UnexpectedDER) as e:
VerifyingKey.from_der(der_str)
self.assertIn("trailing junk afer public key", str(e.exception))
def test_edwards_from_public_key_recovery(self):
with self.assertRaises(ValueError) as e:
VerifyingKey.from_public_key_recovery(b"", b"", Ed25519)
self.assertIn("unsupported for Edwards", str(e.exception))
def test_edwards_from_public_key_recovery_with_digest(self):
with self.assertRaises(ValueError) as e:
VerifyingKey.from_public_key_recovery_with_digest(
b"", b"", Ed25519
)
self.assertIn("unsupported for Edwards", str(e.exception))
def test_load_ed25519_from_pem(self):
vk_pem = (
"-----BEGIN PUBLIC KEY-----\n"
"MCowBQYDK2VwAyEAIwBQ0NZkIiiO41WJfm5BV42u3kQm7lYnvIXmCy8qy2U=\n"
"-----END PUBLIC KEY-----\n"
)
vk = VerifyingKey.from_pem(vk_pem)
vk_str = (
b"\x23\x00\x50\xd0\xd6\x64\x22\x28\x8e\xe3\x55\x89\x7e\x6e\x41\x57"
b"\x8d\xae\xde\x44\x26\xee\x56\x27\xbc\x85\xe6\x0b\x2f\x2a\xcb\x65"
)
vk_2 = VerifyingKey.from_string(vk_str, Ed25519)
self.assertEqual(vk, vk_2)
def test_export_ed255_to_pem(self):
vk_str = (
b"\x23\x00\x50\xd0\xd6\x64\x22\x28\x8e\xe3\x55\x89\x7e\x6e\x41\x57"
b"\x8d\xae\xde\x44\x26\xee\x56\x27\xbc\x85\xe6\x0b\x2f\x2a\xcb\x65"
)
vk = VerifyingKey.from_string(vk_str, Ed25519)
vk_pem = (
b"-----BEGIN PUBLIC KEY-----\n"
b"MCowBQYDK2VwAyEAIwBQ0NZkIiiO41WJfm5BV42u3kQm7lYnvIXmCy8qy2U=\n"
b"-----END PUBLIC KEY-----\n"
)
self.assertEqual(vk_pem, vk.to_pem())
def test_ed25519_export_import(self):
sk = SigningKey.generate(Ed25519)
vk = sk.verifying_key
vk2 = VerifyingKey.from_pem(vk.to_pem())
self.assertEqual(vk, vk2)
def test_ed25519_sig_verify(self):
vk_pem = (
"-----BEGIN PUBLIC KEY-----\n"
"MCowBQYDK2VwAyEAIwBQ0NZkIiiO41WJfm5BV42u3kQm7lYnvIXmCy8qy2U=\n"
"-----END PUBLIC KEY-----\n"
)
vk = VerifyingKey.from_pem(vk_pem)
data = b"data\n"
# signature created by OpenSSL 3.0.0 beta1
sig = (
b"\x64\x47\xab\x6a\x33\xcd\x79\x45\xad\x98\x11\x6c\xb9\xf2\x20\xeb"
b"\x90\xd6\x50\xe3\xc7\x8f\x9f\x60\x10\xec\x75\xe0\x2f\x27\xd3\x96"
b"\xda\xe8\x58\x7f\xe0\xfe\x46\x5c\x81\xef\x50\xec\x29\x9f\xae\xd5"
b"\xad\x46\x3c\x91\x68\x83\x4d\xea\x8d\xa8\x19\x04\x04\x79\x03\x0b"
)
self.assertTrue(vk.verify(sig, data))
def test_ed448_from_pem(self):
pem_str = (
"-----BEGIN PUBLIC KEY-----\n"
"MEMwBQYDK2VxAzoAeQtetSu7CMEzE+XWB10Bg47LCA0giNikOxHzdp+tZ/eK/En0\n"
"dTdYD2ll94g58MhSnBiBQB9A1MMA\n"
"-----END PUBLIC KEY-----\n"
)
vk = VerifyingKey.from_pem(pem_str)
vk_str = (
b"\x79\x0b\x5e\xb5\x2b\xbb\x08\xc1\x33\x13\xe5\xd6\x07\x5d\x01\x83"
b"\x8e\xcb\x08\x0d\x20\x88\xd8\xa4\x3b\x11\xf3\x76\x9f\xad\x67\xf7"
b"\x8a\xfc\x49\xf4\x75\x37\x58\x0f\x69\x65\xf7\x88\x39\xf0\xc8\x52"
b"\x9c\x18\x81\x40\x1f\x40\xd4\xc3\x00"
)
vk2 = VerifyingKey.from_string(vk_str, Ed448)
self.assertEqual(vk, vk2)
def test_ed448_to_pem(self):
vk_str = (
b"\x79\x0b\x5e\xb5\x2b\xbb\x08\xc1\x33\x13\xe5\xd6\x07\x5d\x01\x83"
b"\x8e\xcb\x08\x0d\x20\x88\xd8\xa4\x3b\x11\xf3\x76\x9f\xad\x67\xf7"
b"\x8a\xfc\x49\xf4\x75\x37\x58\x0f\x69\x65\xf7\x88\x39\xf0\xc8\x52"
b"\x9c\x18\x81\x40\x1f\x40\xd4\xc3\x00"
)
vk = VerifyingKey.from_string(vk_str, Ed448)
vk_pem = (
b"-----BEGIN PUBLIC KEY-----\n"
b"MEMwBQYDK2VxAzoAeQtetSu7CMEzE+XWB10Bg47LCA0giNikOxHzdp+tZ/eK/En0\n"
b"dTdYD2ll94g58MhSnBiBQB9A1MMA\n"
b"-----END PUBLIC KEY-----\n"
)
self.assertEqual(vk_pem, vk.to_pem())
def test_ed448_export_import(self):
sk = SigningKey.generate(Ed448)
vk = sk.verifying_key
vk2 = VerifyingKey.from_pem(vk.to_pem())
self.assertEqual(vk, vk2)
def test_ed448_sig_verify(self):
pem_str = (
"-----BEGIN PUBLIC KEY-----\n"
"MEMwBQYDK2VxAzoAeQtetSu7CMEzE+XWB10Bg47LCA0giNikOxHzdp+tZ/eK/En0\n"
"dTdYD2ll94g58MhSnBiBQB9A1MMA\n"
"-----END PUBLIC KEY-----\n"
)
vk = VerifyingKey.from_pem(pem_str)
data = b"data\n"
# signature created by OpenSSL 3.0.0 beta1
sig = (
b"\x68\xed\x2c\x70\x35\x22\xca\x1c\x35\x03\xf3\xaa\x51\x33\x3d\x00"
b"\xc0\xae\xb0\x54\xc5\xdc\x7f\x6f\x30\x57\xb4\x1d\xcb\xe9\xec\xfa"
b"\xc8\x45\x3e\x51\xc1\xcb\x60\x02\x6a\xd0\x43\x11\x0b\x5f\x9b\xfa"
b"\x32\x88\xb2\x38\x6b\xed\xac\x09\x00\x78\xb1\x7b\x5d\x7e\xf8\x16"
b"\x31\xdd\x1b\x3f\x98\xa0\xce\x19\xe7\xd8\x1c\x9f\x30\xac\x2f\xd4"
b"\x1e\x55\xbf\x21\x98\xf6\x4c\x8c\xbe\x81\xa5\x2d\x80\x4c\x62\x53"
b"\x91\xd5\xee\x03\x30\xc6\x17\x66\x4b\x9e\x0c\x8d\x40\xd0\xad\xae"
b"\x0a\x00"
)
self.assertTrue(vk.verify(sig, data))
class TestSigningKey(unittest.TestCase):
"""
......@@ -387,6 +582,88 @@ class TestSigningKey(unittest.TestCase):
def test_inequality_on_signing_keys_not_implemented(self):
self.assertNotEqual(self.sk1, None)
def test_ed25519_from_pem(self):
pem_str = (
"-----BEGIN PRIVATE KEY-----\n"
"MC4CAQAwBQYDK2VwBCIEIDS6x9FO1PG8T4xIPg8Zd0z8uL6sVGZFEZrX17gHC/XU\n"
"-----END PRIVATE KEY-----\n"
)
sk = SigningKey.from_pem(pem_str)
sk_str = SigningKey.from_string(
b"\x34\xBA\xC7\xD1\x4E\xD4\xF1\xBC\x4F\x8C\x48\x3E\x0F\x19\x77\x4C"
b"\xFC\xB8\xBE\xAC\x54\x66\x45\x11\x9A\xD7\xD7\xB8\x07\x0B\xF5\xD4",
Ed25519,
)
self.assertEqual(sk, sk_str)
def test_ed25519_to_pem(self):
sk = SigningKey.from_string(
b"\x34\xBA\xC7\xD1\x4E\xD4\xF1\xBC\x4F\x8C\x48\x3E\x0F\x19\x77\x4C"
b"\xFC\xB8\xBE\xAC\x54\x66\x45\x11\x9A\xD7\xD7\xB8\x07\x0B\xF5\xD4",
Ed25519,
)
pem_str = (
b"-----BEGIN PRIVATE KEY-----\n"
b"MC4CAQAwBQYDK2VwBCIEIDS6x9FO1PG8T4xIPg8Zd0z8uL6sVGZFEZrX17gHC/XU\n"
b"-----END PRIVATE KEY-----\n"
)
self.assertEqual(sk.to_pem(format="pkcs8"), pem_str)
def test_ed25519_to_and_from_pem(self):
sk = SigningKey.generate(Ed25519)
decoded = SigningKey.from_pem(sk.to_pem(format="pkcs8"))
self.assertEqual(sk, decoded)
def test_ed448_from_pem(self):
pem_str = (
"-----BEGIN PRIVATE KEY-----\n"
"MEcCAQAwBQYDK2VxBDsEOTyFuXqFLXgJlV8uDqcOw9nG4IqzLiZ/i5NfBDoHPzmP\n"
"OP0JMYaLGlTzwovmvCDJ2zLaezu9NLz9aQ==\n"
"-----END PRIVATE KEY-----\n"
)
sk = SigningKey.from_pem(pem_str)
sk_str = SigningKey.from_string(
b"\x3C\x85\xB9\x7A\x85\x2D\x78\x09\x95\x5F\x2E\x0E\xA7\x0E\xC3\xD9"
b"\xC6\xE0\x8A\xB3\x2E\x26\x7F\x8B\x93\x5F\x04\x3A\x07\x3F\x39\x8F"
b"\x38\xFD\x09\x31\x86\x8B\x1A\x54\xF3\xC2\x8B\xE6\xBC\x20\xC9\xDB"
b"\x32\xDA\x7B\x3B\xBD\x34\xBC\xFD\x69",
Ed448,
)
self.assertEqual(sk, sk_str)
def test_ed448_to_pem(self):
sk = SigningKey.from_string(
b"\x3C\x85\xB9\x7A\x85\x2D\x78\x09\x95\x5F\x2E\x0E\xA7\x0E\xC3\xD9"
b"\xC6\xE0\x8A\xB3\x2E\x26\x7F\x8B\x93\x5F\x04\x3A\x07\x3F\x39\x8F"
b"\x38\xFD\x09\x31\x86\x8B\x1A\x54\xF3\xC2\x8B\xE6\xBC\x20\xC9\xDB"
b"\x32\xDA\x7B\x3B\xBD\x34\xBC\xFD\x69",
Ed448,
)
pem_str = (
b"-----BEGIN PRIVATE KEY-----\n"
b"MEcCAQAwBQYDK2VxBDsEOTyFuXqFLXgJlV8uDqcOw9nG4IqzLiZ/i5NfBDoHPzmP\n"
b"OP0JMYaLGlTzwovmvCDJ2zLaezu9NLz9aQ==\n"
b"-----END PRIVATE KEY-----\n"
)
self.assertEqual(sk.to_pem(format="pkcs8"), pem_str)
def test_ed448_encode_decode(self):
sk = SigningKey.generate(Ed448)
decoded = SigningKey.from_pem(sk.to_pem(format="pkcs8"))
self.assertEqual(decoded, sk)
class TestTrivialCurve(unittest.TestCase):
@classmethod
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment