Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP 484 Type Hints for pyUmbral #189

Merged
merged 5 commits into from
Jul 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,4 @@ pytest.ini
/tests/metrics/.benchmarks/
tests/metrics/histograms/
.circleci/execute_build.sh
/monkeytype.sqlite3
7 changes: 4 additions & 3 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[mypy]
python_version=3.6
verbosity=1
verbosity=0
ignore_missing_imports=True
[mypy-umbral.*]
disallow_untyped_defs=True
disallow_untyped_defs=False
check_untyped_defs=False
disallow_untyped_calls=True
disallow_untyped_calls=False
[mypy-umbral.openssl]
disallow_untyped_defs=False
62 changes: 62 additions & 0 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from collections import namedtuple

import pytest

from umbral import keys
from umbral.curvebn import CurveBN
from umbral.point import Point

MockKeyPair = namedtuple('TestKeyPair', 'priv pub')


parameters = [
# (N, M)
(1, 1),
(6, 1),
(6, 4),
(6, 6),
(50, 30)
]

wrong_parameters = [
# (N, M)
(-1, -1), (-1, 0), (-1, 5),
(0, -1), (0, 0), (0, 5),
(1, -1), (1, 0), (1, 5),
(5, -1), (5, 0), (5, 10)
]


@pytest.fixture(scope='function')
def alices_keys():
delegating_priv = keys.UmbralPrivateKey.gen_key()
signing_priv = keys.UmbralPrivateKey.gen_key()
return delegating_priv, signing_priv


@pytest.fixture(scope='function')
def bobs_keys():
priv = keys.UmbralPrivateKey.gen_key()
pub = priv.get_pubkey()
return MockKeyPair(priv, pub)


@pytest.fixture()
def random_ec_point1():
yield Point.gen_rand()


@pytest.fixture()
def random_ec_point2():
yield Point.gen_rand()


@pytest.fixture()
def random_ec_curvebn1():
yield CurveBN.gen_rand()


@pytest.fixture()
def random_ec_curvebn2():
yield CurveBN.gen_rand()

27 changes: 14 additions & 13 deletions umbral/_pre.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
from typing import Optional

from umbral.curvebn import CurveBN
from umbral.config import default_params
from umbral.keys import UmbralPublicKey
from umbral.params import UmbralParameters


def prove_cfrag_correctness(cfrag: "CapsuleFrag",
kfrag: "KFrag",
capsule: "Capsule",
metadata: bytes = None
) -> "CorrectnessProof":
def prove_cfrag_correctness(cfrag: 'CapsuleFrag',
kfrag: 'KFrag',
capsule: 'Capsule',
metadata: Optional[bytes] = None
) -> 'CorrectnessProof':

params = capsule._umbral_params

rk = kfrag._bn_key
t = CurveBN.gen_rand(params.curve)
####
## Here are the formulaic constituents shared with `assess_cfrag_correctness`.
# Here are the formulaic constituents shared with `assess_cfrag_correctness`.
####
e = capsule._point_e
v = capsule._point_v
Expand Down Expand Up @@ -45,7 +46,7 @@ def prove_cfrag_correctness(cfrag: "CapsuleFrag",
raise capsule.NotValid("Capsule verification failed.")


def assess_cfrag_correctness(cfrag, capsule: "Capsule"):
def assess_cfrag_correctness(cfrag: 'CapsuleFrag', capsule: 'Capsule') -> bool:

correctness_keys = capsule.get_correctness_keys()

Expand All @@ -62,7 +63,7 @@ def assess_cfrag_correctness(cfrag, capsule: "Capsule"):
params = capsule._umbral_params

####
## Here are the formulaic constituents shared with `prove_cfrag_correctness`.
# Here are the formulaic constituents shared with `prove_cfrag_correctness`.
####
e = capsule._point_e
v = capsule._point_v
Expand Down Expand Up @@ -110,11 +111,11 @@ def assess_cfrag_correctness(cfrag, capsule: "Capsule"):
& correct_rk_commitment


def verify_kfrag(kfrag,
def verify_kfrag(kfrag: 'KFrag',
delegating_pubkey: UmbralPublicKey,
signing_pubkey,
signing_pubkey: UmbralPublicKey,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was deliberately left without type annotation, since strictly speaking, it shouldn't be an UmbralPublicKey. Umbral keys should only be used for encryption & delegation. The problem is that our current implementation of Signature uses, temporarily, UmbralPublicKey. AFAIK, that's something that will change after testnet, because it has a low priority.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what shall we do in the meantime?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree - a big part of the point was to make this swappable. It's not obvious to me what the "correct" type is here; situations like this make me questions the underlying wisdom of the type hint system, because what we're attempting to do here is, IMO, in the best spirit of the dynamic / duck-typed tradition of python.

Copy link
Member Author

@KPrasch KPrasch Jul 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep - looks like we need to implement a custom subprotocol: https://mypy.readthedocs.io/en/latest/protocols.html#simple-user-defined-protocols

receiving_pubkey: UmbralPublicKey
):
) -> bool:


params = delegating_pubkey.params
Expand Down
14 changes: 8 additions & 6 deletions umbral/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Optional, Type
from warnings import warn

from umbral.curve import Curve, SECP256K1
from umbral.params import UmbralParameters


class _CONFIG:
Expand All @@ -18,19 +20,19 @@ def __set_curve_by_default(cls):
cls.set_curve(cls.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER)

@classmethod
def params(cls):
def params(cls) -> UmbralParameters:
if not cls.__params:
cls.__set_curve_by_default()
return cls.__params

@classmethod
def curve(cls):
def curve(cls) -> Type[Curve]:
if not cls.__curve:
cls.__set_curve_by_default()
return cls.__curve

@classmethod
def set_curve(cls, curve: Curve=None):
def set_curve(cls, curve: Optional[Curve] = None) -> None:
if cls.__curve:
raise cls.UmbralConfigurationError(
"You can only set the default curve once. Do it once and then leave it alone.")
Expand All @@ -42,13 +44,13 @@ def set_curve(cls, curve: Curve=None):
cls.__params = UmbralParameters(curve)


def set_default_curve(curve: Curve=None):
def set_default_curve(curve: Optional[Curve] = None) -> None:
return _CONFIG.set_curve(curve)


def default_curve():
def default_curve() -> Type[Curve]:
return _CONFIG.curve()


def default_params():
def default_params() -> UmbralParameters:
return _CONFIG.params()
36 changes: 17 additions & 19 deletions umbral/curvebn.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import os

from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.primitives import hashes

from umbral import openssl
from umbral.config import default_curve, default_params
from umbral.config import default_curve
from umbral.curve import Curve
from umbral.utils import get_field_order_size_in_bytes
from umbral.params import UmbralParameters
from umbral.utils import get_field_order_size_in_bytes


class CurveBN(object):
Expand All @@ -27,7 +25,7 @@ def __init__(self, bignum, curve: Curve):
self.curve = curve

@classmethod
def expected_bytes_length(cls, curve: Curve=None):
def expected_bytes_length(cls, curve: Curve=None) -> int:
"""
Returns the size (in bytes) of a CurveBN given the curve.
If no curve is provided, it uses the default.
Expand All @@ -36,7 +34,7 @@ def expected_bytes_length(cls, curve: Curve=None):
return get_field_order_size_in_bytes(curve)

@classmethod
def gen_rand(cls, curve: Curve=None):
def gen_rand(cls, curve: Curve=None) -> 'CurveBN':
"""
Returns a CurveBN object with a cryptographically secure OpenSSL BIGNUM
based on the given curve.
Expand All @@ -56,7 +54,7 @@ def gen_rand(cls, curve: Curve=None):
return cls(new_rand_bn, curve)

@classmethod
def from_int(cls, num, curve: Curve=None):
def from_int(cls, num: int, curve: Curve=None) -> 'CurveBN':
"""
Returns a CurveBN object from a given integer on a curve.
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
Expand All @@ -67,7 +65,7 @@ def from_int(cls, num, curve: Curve=None):
return cls(conv_bn, curve)

@classmethod
def hash(cls, *crypto_items, params: UmbralParameters):
def hash(cls, *crypto_items, params: UmbralParameters) -> 'CurveBN':
# TODO: Clean this in an upcoming cleanup of pyUmbral
blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend)
for item in crypto_items:
Expand Down Expand Up @@ -101,7 +99,7 @@ def hash(cls, *crypto_items, params: UmbralParameters):
return cls(bignum, params.curve)

@classmethod
def from_bytes(cls, data, curve: Curve=None):
def from_bytes(cls, data: bytes, curve: Curve=None) -> 'CurveBN':
"""
Returns a CurveBN object from the given byte data that's within the size
of the provided curve's order.
Expand All @@ -112,20 +110,20 @@ def from_bytes(cls, data, curve: Curve=None):
num = int.from_bytes(data, 'big')
return cls.from_int(num, curve)

def to_bytes(self):
def to_bytes(self) -> bytes:
"""
Returns the CurveBN as bytes.
"""
size = backend._lib.BN_num_bytes(self.curve.order)
return int.to_bytes(int(self), size, 'big')

def __int__(self):
def __int__(self) -> int:
"""
Converts the CurveBN to a Python int.
"""
return backend._bn_to_int(self.bignum)

def __eq__(self, other):
def __eq__(self, other) -> bool:
"""
Compares the two BIGNUMS or int.
"""
Expand All @@ -137,7 +135,7 @@ def __eq__(self, other):
# -1 less than, 0 is equal to, 1 is greater than
return not bool(backend._lib.BN_cmp(self.bignum, other.bignum))

def __pow__(self, other):
def __pow__(self, other) -> 'CurveBN':
"""
Performs a BN_mod_exp on two BIGNUMS.

Expand All @@ -157,7 +155,7 @@ def __pow__(self, other):

return CurveBN(power, self.curve)

def __mul__(self, other):
def __mul__(self, other) -> 'CurveBN':
"""
Performs a BN_mod_mul between two BIGNUMS.
"""
Expand All @@ -173,7 +171,7 @@ def __mul__(self, other):

return CurveBN(product, self.curve)

def __truediv__(self, other):
def __truediv__(self, other) -> 'CurveBN':
"""
Performs a BN_div on two BIGNUMs (modulo the order of the curve).

Expand All @@ -193,7 +191,7 @@ def __truediv__(self, other):

return CurveBN(product, self.curve)

def __add__(self, other):
def __add__(self, other) -> 'CurveBN':
"""
Performs a BN_mod_add on two BIGNUMs.
"""
Expand All @@ -206,7 +204,7 @@ def __add__(self, other):

return CurveBN(op_sum, self.curve)

def __sub__(self, other):
def __sub__(self, other) -> 'CurveBN':
"""
Performs a BN_mod_sub on two BIGNUMS.
"""
Expand All @@ -219,7 +217,7 @@ def __sub__(self, other):

return CurveBN(diff, self.curve)

def __invert__(self):
def __invert__(self) -> 'CurveBN':
"""
Performs a BN_mod_inverse.

Expand All @@ -235,7 +233,7 @@ def __invert__(self):

return CurveBN(inv, self.curve)

def __mod__(self, other):
def __mod__(self, other) -> 'CurveBN':
"""
Performs a BN_nnmod on two BIGNUMS.
"""
Expand Down
8 changes: 5 additions & 3 deletions umbral/dem.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import os
from typing import Optional

from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305


Expand All @@ -7,7 +9,7 @@


class UmbralDEM(object):
def __init__(self, symm_key: bytes):
def __init__(self, symm_key: bytes) -> None:
"""
Initializes an UmbralDEM object. Requires a key to perform
ChaCha20-Poly1305.
Expand All @@ -19,7 +21,7 @@ def __init__(self, symm_key: bytes):

self.cipher = ChaCha20Poly1305(symm_key)

def encrypt(self, data: bytes, authenticated_data: bytes=None):
def encrypt(self, data: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
"""
Encrypts data using ChaCha20-Poly1305 with optional authenticated data.
"""
Expand All @@ -28,7 +30,7 @@ def encrypt(self, data: bytes, authenticated_data: bytes=None):
# Ciphertext will be a 12 byte nonce, the ciphertext, and a 16 byte tag.
return nonce + enc_data

def decrypt(self, ciphertext: bytes, authenticated_data: bytes=None):
def decrypt(self, ciphertext: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
"""
Decrypts data using ChaCha20-Poly1305 and validates the provided
authenticated data.
Expand Down
Loading