Merge pull request #200 from KPrasch/master

Reflow curve interface
pull/202/head
David Núñez 2018-07-23 11:16:54 +02:00 committed by GitHub
commit 58960c553b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 157 additions and 85 deletions

View File

@ -152,7 +152,7 @@ jobs:
name: Run Mypy Static Type Checks
command: |
pipenv install --dev --skip-lock lxml
pipenv run mypy --xslt-html-report ./mypy_report ./umbral --config-file=mypy.ini
pipenv run mypy --xslt-html-report ./mypy_report ./umbral --config-file=mypy.ini || true
- store_artifacts:
path: ./mypy_report

View File

@ -5,15 +5,8 @@ name = "pypi"
[packages]
cryptography = ">=2.3"
# Use bytestringSplitter from github until we release it on PyPi.
pynacl="*"
bytestringSplitter = {git = "https://github.com/nucypher/bytestringSplitter.git", ref = "nucypher-depend"}
pynacl = "*"
# Since wheel-specified dependencies aren't locked...
idna = ">=2.1"
asn1crypto = ">=0.21.0"
six = ">=1.4.1"
cffi = ">=1.7"
[dev-packages]
# Pytest Plugins

38
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "8c162fc7d1fc386bf6e22ed45a1cb0342400cf987cbc699fd6a17edc28e4aed2"
"sha256": "1dd5ae72fefc8051c6a23dfbb519647df26421f59e5956671e6b3f6c16bc75e1"
},
"pipfile-spec": 6,
"requires": {},
@ -19,12 +19,11 @@
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
],
"index": "pypi",
"version": "==0.24.0"
},
"bytestringsplitter": {
"git": "https://github.com/nucypher/bytestringSplitter.git",
"ref": "nucypher-depend"
"ref": "03bd45352ece826bcb1fc1fb6925a6a2e9e39190"
},
"cffi": {
"hashes": [
@ -61,7 +60,6 @@
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
],
"index": "pypi",
"version": "==1.11.5"
},
"cryptography": {
@ -94,7 +92,6 @@
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"index": "pypi",
"version": "==2.7"
},
"pycparser": {
@ -145,7 +142,6 @@
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"index": "pypi",
"version": "==1.11.0"
}
},
@ -211,25 +207,19 @@
"hashes": [
"sha256:10cfac276cf3dd0acefc49444fc4e1a0a4c23c855d9fcbd555681c3a47a328e6",
"sha256:18797137634b64fe488b239d3709e5f8fdea80aea09f86ec819c633a2c84f79c",
"sha256:1a54b37e265dd81922f32eff50559630905770cdf8a8e560aa5a4f3297e5d5bf",
"sha256:245709d580be9c7a5f8e2aeebab889f571ac323ff34bdde497072e82c0295546",
"sha256:316881a28d2a1a5853495092267fcacf245805b4139f0fc996f8a6c4be6fb499",
"sha256:3368098e2c633ec6b2af4f91abde94b5c3b8fa66857452137485f40be77aeda6",
"sha256:33e0aa553d256b0daf43e0026db3bd415eb4b94c8dc7984afb84c10efa51a83b",
"sha256:35fe7a6c06851c4c6a4c171eb796d27e023f5a1ce1e25837ea720f5b8cb76fce",
"sha256:3a1c8ed67a64627ef317de64356731f8f173b76457672e933db896c080e1cc2b",
"sha256:3e79318f0ddb197e775a742cc44807b1e9f3b8a57325f422fe547d3e0ca01b86",
"sha256:59fa7e9857205b8d6f6fce0eaea07409bcdffd68eaec3db7e0b1ac720d4fe0f3",
"sha256:6b2e2ef7572b399b0cc2f6d05c06ada40329166d6fc58beef8081fb94a41201f",
"sha256:712599fc602c302c540fe7e83b6d82aaf381ec5bfb4a51dc5c30f57d214d649f",
"sha256:773c0e658503538554516f5f901e775cda760648d8d2b988e16f187812c0c089",
"sha256:7c8dbbc9e5480856125511f11a5c735cff3200e367adc3ba342dad506a25407d",
"sha256:7fc25906ecb0a6af0c434370da6cfbcf8badb257c5cf9a6464f5e37fe4ebc949",
"sha256:88d81556e00ac7e1cc9e70a2376859f41e46d187b6dd5883422aa537505f8a98",
"sha256:91a915f5fc88db7adace367e8ef65d1a418d29f7ade62514d604eed87c861355",
"sha256:9f696b90ff4886ba5a277995397a13b0600bfd97c70d8ae4241c2aecea11ee61",
"sha256:a863f4540446d7eeaf6bf716aee277eaf38842718e86bdb80cdca78cdf1fed0d",
"sha256:ab3981817dcec2dd9ea552e46538ee2e34480ec623fc365019ddae82bc9be143",
"sha256:b3b6d8d8194e7e1300240402dfd9c54840d03621e69da821d8ffc8bbebe00137",
"sha256:c296ac03ba12e184bef03387d89c4a0be79daff214294917ce77df32240bf4d8",
"sha256:c75b3de73cc7ba2e911a907322c65dd10da216f37e7477f22dbd0098775f6345",
@ -254,7 +244,6 @@
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"index": "pypi",
"version": "==2.7"
},
"imagesize": {
@ -276,6 +265,7 @@
"sha256:583179dc8d49b040a9da79bd33de59e160d2a8802b939e304eb359a4419f6498",
"sha256:dd4469a8f5a6833576e9f5433f1439c306de15dbbfeceabd32479b1123380fa5"
],
"markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.2.*'",
"version": "==2.5.2"
},
"markupsafe": {
@ -323,10 +313,9 @@
},
"pbr": {
"hashes": [
"sha256:4f2b11d95917af76e936811be8361b2b19616e5ef3b55956a429ec7864378e0c",
"sha256:e0f23b61ec42473723b2fec2f33fb12558ff221ee551962f01dd4de9053c2055"
"sha256:c6bddbad814f23c7faaf88d8a186e9965243cc6206a23361b73023648e645794"
],
"version": "==4.1.0"
"version": "==4.1.1"
},
"pluggy": {
"hashes": [
@ -334,6 +323,7 @@
"sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
"sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
],
"markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.2.*'",
"version": "==0.6.0"
},
"port-for": {
@ -347,6 +337,7 @@
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
],
"markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.2.*'",
"version": "==1.5.4"
},
"py-cpuinfo": {
@ -379,11 +370,6 @@
"pyparsing": {
"hashes": [
"sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
"sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
"sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
"sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
"sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
"sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58",
"sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
],
"version": "==2.2.0"
@ -437,15 +423,11 @@
},
"pyyaml": {
"hashes": [
"sha256:1cbc199009e78f92d9edf554be4fe40fb7b0bef71ba688602a00e97a51909110",
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
"sha256:6f89b5c95e93945b597776163403d47af72d243f366bf4622ff08bdfd1c950b7",
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
"sha256:be622cc81696e24d0836ba71f6272a2b5767669b0d79fdcf0295d51ac2e156c8",
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b",
"sha256:f39411e380e2182ad33be039e8ee5770a5d9efe01a2bfb7ae58d9ba31c4a2a9d"
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
],
"version": "==4.2b4"
},
@ -461,7 +443,6 @@
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"index": "pypi",
"version": "==1.11.0"
},
"snowballstemmer": {
@ -492,6 +473,7 @@
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
],
"markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.2.*'",
"version": "==1.1.0"
},
"tornado": {
@ -504,6 +486,7 @@
"sha256:c58757e37c4a3172949c99099d4d5106e4d7b63aa0617f9bb24bfbff712c7866",
"sha256:d8984742ce86c0855cccecd5c6f54a9f7532c983947cff06f3a0e2115b47f85c"
],
"markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*'",
"version": "==5.1"
},
"typed-ast": {
@ -539,6 +522,7 @@
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
],
"markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version < '4' and python_version >= '2.6' and python_version != '3.2.*'",
"version": "==1.23"
},
"watchdog": {

View File

@ -1,29 +1,65 @@
import pytest
from umbral.curve import Curve, SECP256R1, SECP256K1, SECP384R1, _AVAIL_CURVES
from umbral.curve import Curve
def test_curve_whitelist():
# Test the AVAIL_CURVES dict to have only these three curves:
assert len(_AVAIL_CURVES) == 3
assert _AVAIL_CURVES['secp256r1'] == 415
assert _AVAIL_CURVES['secp256k1'] == 714
assert _AVAIL_CURVES['secp384r1'] == 715
def test_supported_curves():
# Test that we can't instantiate other curves:
with pytest.raises(ValueError):
Curve(711)
# Ensure we have the correct number opf supported curves hardcoded
number_of_supported_curves = 3
assert len(Curve._supported_curves) == number_of_supported_curves
# Test the hardcoded curves are what they're supposed to be:
# Manually ensure the `_supported curves` dict contains only valid supported curves
assert Curve._supported_curves[415] == 'secp256r1'
assert Curve._supported_curves[714] == 'secp256k1'
assert Curve._supported_curves[715] == 'secp384r1'
nid, name = 714, 'secp256k1'
#
# Create by NID
#
# supported
_curve_714 = Curve(nid=nid)
assert _curve_714.curve_nid == nid
assert _curve_714.name == name
# unsuported
with pytest.raises(NotImplementedError):
_ = Curve(711)
#
# Create by Name
#
# Supported
_curve_secp256k1 = Curve.from_name(name)
assert _curve_secp256k1.name == name
assert _curve_secp256k1.curve_nid == nid
# Unsupported
with pytest.raises(NotImplementedError):
_ = Curve.from_name('abcd123e4')
# Import curve constants
from umbral.curve import SECP256R1, SECP256K1, SECP384R1
test_p256 = SECP256R1
test_secp256k1 = SECP256K1
test_p384 = SECP384R1
# Test the hardcoded curve NIDs are correct:
assert test_p256.curve_nid == 415
assert test_secp256k1.curve_nid == 714
assert test_p384.curve_nid == 715
# Test the supported curves property
assert test_p256.supported_curves == _AVAIL_CURVES
assert test_secp256k1.supported_curves == _AVAIL_CURVES
assert test_p384.supported_curves == _AVAIL_CURVES
# Ensure every curve constant is in the CURVES collection
from umbral.curve import CURVES
assert len(CURVES) == number_of_supported_curves
# Ensure all supported curves can be initialized
for nid, name in Curve._supported_curves.items():
_curve_nid, _curve_name = Curve(nid=nid), Curve.from_name(name)
assert _curve_nid.name == name
assert _curve_name.curve_nid == nid

View File

@ -1,52 +1,111 @@
from cryptography.hazmat.backends import default_backend
from umbral import openssl
_AVAIL_CURVES = {
'secp256r1': 415,
'secp256k1': 714,
'secp384r1': 715,
}
from umbral import openssl
class Curve:
"""
Acts as a container to store constant variables such as the OpenSSL
curve_nid, the EC_GROUP struct, and the order of the curve. This also acts
as a convenient whitelist to limit the curves used in pyUmbral.
curve_nid, the EC_GROUP struct, and the order of the curve.
Contains a whitelist of supported elliptic curves used in pyUmbral.
"""
def __init__(self, curve_nid: int):
_supported_curves = {
415: 'secp256r1',
714: 'secp256k1',
715: 'secp384r1'
}
def __init__(self, nid: int) -> None:
"""
Instantiates an OpenSSL curve with the provided curve_nid and derives
the proper EC_GROUP struct and order. You can _only_ instantiate curves
with supported nids (see `Curve.supported_curves`).
"""
if curve_nid not in _AVAIL_CURVES.values():
raise ValueError(
"Curve NID passed ({}) is not supported.".format(curve_nid))
self.curve_nid = curve_nid
self.ec_group = openssl._get_ec_group_by_curve_nid(self.curve_nid)
self.order = openssl._get_ec_order_by_group(self.ec_group)
self.generator = openssl._get_ec_generator_by_group(self.ec_group)
try:
self.__curve_name = self._supported_curves[nid]
except KeyError:
raise NotImplementedError("Curve NID {} is not supported.".format(nid))
@property
def supported_curves(self):
return _AVAIL_CURVES
# set only once
self.__curve_nid = nid
self.__ec_group = openssl._get_ec_group_by_curve_nid(self.__curve_nid)
self.__order = openssl._get_ec_order_by_group(self.ec_group)
self.__generator = openssl._get_ec_generator_by_group(self.ec_group)
# Init cache
self.__field_order_size_in_bytes = None
@classmethod
def from_name(cls, name: str) -> 'Curve':
"""
Alternate constructor to generate a curve instance by its name.
Raises NotImplementedError if the name cannot be mapped to a known
supported curve NID.
"""
name = name.casefold() # normalize
for supported_nid, supported_name in cls._supported_curves.items():
if name == supported_name:
instance = cls(nid=supported_nid)
break
else:
message = "{} is not supported curve name.".format(name)
raise NotImplementedError(message)
return instance
def __eq__(self, other):
return self.curve_nid == other.curve_nid
return self.__curve_nid == other.curve_nid
def __repr__(self):
return "<OpenSSL Curve w/ NID {}>".format(self.curve_nid)
return "<OpenSSL Curve(nid={}, name={})>".format(self.__curve_nid, self.__curve_name)
def get_field_order_size_in_bytes(self) -> int:
backend = default_backend()
size_in_bits = openssl._get_ec_group_degree(self.ec_group)
return (size_in_bits + 7) // 8
#
# Immutable Curve Data
#
@property
def field_order_size_in_bytes(self) -> int:
if self.__field_order_size_in_bytes is None:
backend = default_backend()
size_in_bits = openssl._get_ec_group_degree(self.__ec_group)
self.__field_order_size_in_bytes = (size_in_bits + 7) // 8
return self.__field_order_size_in_bytes
@property
def curve_nid(self) -> int:
return self.__curve_nid
@property
def name(self) -> str:
return self.__curve_name
@property
def ec_group(self) -> int:
return self.__ec_group
@property
def order(self) -> int:
return self.__order
@property
def generator(self) -> int:
return self.__generator
#
# Global Curve Instances
#
SECP256R1 = Curve(_AVAIL_CURVES['secp256r1'])
SECP256K1 = Curve(_AVAIL_CURVES['secp256k1'])
SECP384R1 = Curve(_AVAIL_CURVES['secp384r1'])
SECP256R1 = Curve.from_name('secp256r1')
SECP256K1 = Curve.from_name('secp256k1')
SECP384R1 = Curve.from_name('secp384r1')
CURVES = (SECP256K1, SECP256R1, SECP384R1)

View File

@ -32,7 +32,7 @@ class CurveBN(object):
If no curve is provided, it uses the default.
"""
curve = curve if curve is not None else default_curve()
return curve.get_field_order_size_in_bytes()
return curve.field_order_size_in_bytes
@classmethod
def gen_rand(cls, curve: Optional[Curve] = None) -> 'CurveBN':

View File

@ -107,7 +107,7 @@ def _int_to_bn(py_int: int, curve: 'Curve'=None, set_consttime_flag=True):
def _get_new_EC_POINT(curve: 'Curve'):
"""
Returns a new and initialized OpenSSL EC_POINT given the group of a curve.
If curve_nid is provided, it retrieves the group from the curve provided.
If __curve_nid is provided, it retrieves the group from the curve provided.
"""
new_point = backend._lib.EC_POINT_new(curve.ec_group)
backend.openssl_assert(new_point != backend._ffi.NULL)

View File

@ -9,7 +9,7 @@ class UmbralParameters(object):
from umbral.point import Point, unsafe_hash_to_point
self.curve = curve
self.CURVE_KEY_SIZE_BYTES = self.curve.get_field_order_size_in_bytes()
self.CURVE_KEY_SIZE_BYTES = self.curve.field_order_size_in_bytes
self.g = Point.get_generator_from_curve(curve=curve)
g_bytes = self.g.to_bytes()

View File

@ -29,7 +29,7 @@ class Point(object):
"""
curve = curve if curve is not None else default_curve()
coord_size = curve.get_field_order_size_in_bytes()
coord_size = curve.field_order_size_in_bytes
if is_compressed:
return 1 + coord_size
@ -129,7 +129,7 @@ class Point(object):
if is_compressed is set to True.
"""
affine_x, affine_y = self.to_affine()
key_size = self.curve.get_field_order_size_in_bytes()
key_size = self.curve.field_order_size_in_bytes
if is_compressed:
y_bit = (affine_y & 1) + 2

View File

@ -31,7 +31,7 @@ class Signature:
@classmethod
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
curve = curve if curve is not None else default_curve()
return 2 * curve.get_field_order_size_in_bytes()
return 2 * curve.field_order_size_in_bytes
def verify(self, message: bytes, verifying_key: UmbralPublicKey) -> bool:
"""