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

Simplified API #159

Merged
merged 11 commits into from
Jun 6, 2018
Merged

Simplified API #159

merged 11 commits into from
Jun 6, 2018

Conversation

cygnusv
Copy link
Member

@cygnusv cygnusv commented May 31, 2018

What this does:

This PR provides a slightly simplified API for pyUmbral. In particular, removes all redundant arguments from public and private methods that could be obtained by other means (primarily, from a neighboring argument), such as params and correctness keys. The result is not only that the interface is simplified, but also that there is less surface for misuse.

  • Simplified public API for umbral.pre and umbral.fragments:
    • Methods now only take Umbral keys as arguments, rather than primitive types (Point, CurveBN).
    • Remove unnecessary arguments from public facing and internal methods when they can be extracted from a Capsule, UmbralPublicKey or UmbralPrivateKey.
    • Adapts the test suite to new simplified API
  • Fixes a bug in Capsule.attach_cfrag() that allowed to attach incorrect CFrag
  • Adds a getter in Capsule for correctness keys
  • Removes pre.CHACHA20_KEY_SIZE constant and uses dem.DEM_KEYSIZE instead
  • Add __eq__ method to UmbralParameters

@codecov
Copy link

codecov bot commented May 31, 2018

Codecov Report

Merging #159 into master will decrease coverage by 0.57%.
The diff coverage is 93.75%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #159      +/-   ##
==========================================
- Coverage    96.1%   95.53%   -0.58%     
==========================================
  Files          12       12              
  Lines        1129     1141      +12     
==========================================
+ Hits         1085     1090       +5     
- Misses         44       51       +7
Impacted Files Coverage Δ
umbral/params.py 100% <100%> (ø) ⬆️
umbral/fragments.py 93.96% <100%> (-0.21%) ⬇️
umbral/keys.py 95.86% <100%> (-0.12%) ⬇️
umbral/point.py 95.89% <100%> (+0.71%) ⬆️
umbral/curvebn.py 97.16% <100%> (-0.71%) ⬇️
umbral/pre.py 96.61% <90.9%> (-1.5%) ⬇️
umbral/_pre.py 96.2% <93.75%> (-0.82%) ⬇️
umbral/signing.py 89.28% <0%> (-3.58%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee35a52...fa4375d. Read the comment docs.

umbral/pre.py Outdated

components = splitter(capsule_bytes)
return cls(*components)
return cls(*components, params=params)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh right, that's obviously needed. It's a little unnerving that, without this, we didn't have a failing test.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, that's why I'm leaning towards removing redundant params as argument in some places (assuming there's a capsule or other structure already holding params) and making it non-optional in others (e.g., in Umbral key generation).

@@ -132,6 +132,8 @@ def _set_cfrag_correctness_key(self, key_type, key: UmbralPublicKey):
if current_key is None:
if key is None:
raise TypeError("The {} key is not set and you didn't pass one.".format(key_type))
elif self._umbral_params != key.params:
raise TypeError("You are trying to set a key with different UmbralParameters.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Come on man, this is what segfaults are for.

@cygnusv cygnusv changed the title [WIP] When possible, take params from Capsule [WIP] Simplified API Jun 4, 2018
@cygnusv cygnusv changed the title [WIP] Simplified API Simplified API Jun 4, 2018
@@ -28,43 +35,52 @@ def test_capsule_creation(alices_keys):


def test_capsule_equality():
one_capsule = Capsule(point_e=Point.gen_rand(),
params = default_params()
Copy link
Contributor

Choose a reason for hiding this comment

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

We pass this in a ton. Should we make this a pytest fixture?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we should. Perhaps in another PR?

@@ -39,26 +40,29 @@ def test_activated_capsule_serialization(alices_keys, bobs_keys):
delegating_pubkey = delegating_privkey.get_pubkey()
signer_alice = Signer(signing_privkey)

receiving_privkey, receiving_pubkey = bobs_keys
params = delegating_privkey.params
Copy link
Contributor

Choose a reason for hiding this comment

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

This is an interesting paradigm where each key and capsule can have its own params.

This might be a great pattern in the future, but we should be careful with it for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed on both points 100%. Interesting but be careful.



@pytest.mark.parametrize("N, M", parameters)
def test_lifecycle_with_serialization(N, M, curve=default_curve()):
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Looks really good.

@@ -148,7 +150,7 @@ def to_bytes(self) -> bytes:

return result

def _bn_keytes__(self):
def _bytes__(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be __bytes__ :)

Copy link
Member Author

Choose a reason for hiding this comment

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

✔️

umbral/keys.py Outdated
@@ -117,6 +114,12 @@ def to_bytes(self, password: bytes=None, _scrypt_cost: int=20,

return umbral_privkey

def __bytes__(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't offer __bytes__ on UmbralPrivateKey to prevent accidental misuse.

I think this should be removed.

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 with @tuxxy here. What's the use case?

Copy link
Member Author

Choose a reason for hiding this comment

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

None. It was a silly move 🤦‍♂️ ... Fixed.

@@ -82,8 +79,8 @@ def from_bytes(cls, key_bytes: bytes, params: UmbralParameters=None,
bn_key = CurveBN.from_bytes(key_bytes, params.curve)
return cls(bn_key, params)

def to_bytes(self, password: bytes=None, _scrypt_cost: int=20,
encoder: Callable=None):
def to_bytes(self, password: bytes = None, _scrypt_cost: int = 20,
Copy link
Contributor

Choose a reason for hiding this comment

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

In instances where you set a default value with type in the function like the _scrypt_cost variable here, the equal sign should have no spaces such that:

_scrypt_cost: int=20

In cases where there is no type, such as None, it should have a space:

password: bytes = None

https://www.python.org/dev/peps/pep-0008/#other-recommendations

I'm not necessarily a huge fan of this PEP8 rule, as such I will leave it up to everyone else to decide how to do this. Generally, I'm in favor of not doing this as it just isn't necessary.

Copy link
Contributor

Choose a reason for hiding this comment

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

I used to think this was a really dumb style, but then I came around to it. The thing is: in kwargs, x=y looks like assignment. With the space around it, your eyes learn to treat the space as a type hint instead of argument assignment.

Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 But it's not a type hint? It's a default assignment if the argument is not passed?

Copy link
Contributor

Choose a reason for hiding this comment

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

Eh? It is a type hint. Without the space, it looks like you're assigning the value None to the name bytes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I see! Gotcha.

@@ -21,7 +20,7 @@ class Signature:
between (r, s) and DER formatting.
"""

def __init__(self, r: int, s: int):
def __init__(self, r: CurveBN, s: CurveBN):
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're going to use CurveBN here, this should be in a separate PR. I'm not quite sure what the best options here are yet and I want to think about it more.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, agreed.


def __bytes__(self):
return self.r.to_bytes(32, "big") + self.s.to_bytes(32, "big")
return self.r.to_bytes() + self.s.to_bytes()
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@jMyles jMyles left a comment

Choose a reason for hiding this comment

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

One of the best PRs in the history of this project. Bravo.

There are some outstanding concerns, though, so I'll wait a bit to approve.

@@ -28,43 +35,52 @@ def test_capsule_creation(alices_keys):


def test_capsule_equality():
one_capsule = Capsule(point_e=Point.gen_rand(),
params = default_params()
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes.

one_capsule = Capsule(point_e=Point.gen_rand(),
params = default_params()

one_capsule = Capsule(params,
Copy link
Contributor

Choose a reason for hiding this comment

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

Wow - big change.

This is pretty cool I think.

But I'm left wondering: if Capsule is going to require params, does it perhaps make sense to make it private? Is there really ever a public reason for someone to init a Capsule?

delegating_privkey, _signing_privkey = alices_keys

sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey().point_key)
sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh hallelujah. Holy shit what a relief.

assert len(sym_key) == 32

# The symmetric key sym_key is perhaps used for block cipher here in a real-world scenario.
sym_key_2 = pre._decapsulate_original(delegating_privkey.bn_key, capsule)
sym_key_2 = pre._decapsulate_original(delegating_privkey, capsule)
Copy link
Contributor

Choose a reason for hiding this comment

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

oh my goodness this is seriously amazing. This is how pyUmbral has always wanted to read. (Although again, as I outlined in #161, I might advocate even going all the way down to just delegating_key or delegating_secret and dropping the "priv" idea).

@@ -39,26 +40,29 @@ def test_activated_capsule_serialization(alices_keys, bobs_keys):
delegating_pubkey = delegating_privkey.get_pubkey()
signer_alice = Signer(signing_privkey)

receiving_privkey, receiving_pubkey = bobs_keys
params = delegating_privkey.params
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed on both points 100%. Interesting but be careful.

* Instead, takes params from Capsules or UmbralPublicKeys
* Makes params required in some places (Capsule.init, Capsule.from_bytes, etc)
* Removes pre.CHACHA20_KEY_SIZE constant and use dem.DEM_KEYSIZE instead
* Functions in `pre` now only take Umbral keys as arguments, rather than primitive types (Point, CurveBN)
* Remove unnecessary arguments from public facing and internal methods when they can be extracted from a Capsule, UmbralPublicKey or UmbralPrivateKey
* Adds a getter in Capsule for correctness keys
* Adapts the test suite to new simplified API
Copy link
Contributor

@tuxxy tuxxy left a comment

Choose a reason for hiding this comment

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

🐧

@cygnusv cygnusv merged commit 9e3a037 into nucypher:master Jun 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants