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

Fix encoding problem #28

Merged
merged 5 commits into from
Oct 4, 2016
Merged

Fix encoding problem #28

merged 5 commits into from
Oct 4, 2016

Conversation

r-marques
Copy link
Contributor

@r-marques r-marques commented Sep 28, 2016

resolves #22

This pull request replaces the ed25519 python library with pynacl.
pynacl is a python binding for libsodium and is under active development.

Besides replacing the libraries, also change the way encoding is handled. Now instead of the encode, to_ascii, and to_bytes methods we have only encode and we can pass the encoding that we want. Supported encoders are hex, bytes, base16, base32, base58, base64

TODO:

  • Fix the docstrings and documentation

@codecov-io
Copy link

codecov-io commented Sep 28, 2016

Current coverage is 85.94% (diff: 94.11%)

Merging #28 into master will decrease coverage by 0.48%

@@             master        #28   diff @@
==========================================
  Files            17         17          
  Lines           818        804    -14   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
- Hits            707        691    -16   
- Misses          111        113     +2   
  Partials          0          0          

Powered by Codecov. Last update a2c8390...994f88e

@@ -3,13 +3,13 @@

# ED25519
VK_HEX_ILP = b'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf'
VK_B64_ILP = b'7Bcrk61eVjv0kyxw4SRQNMNUZ+8u/U1k6/gZaDRn4r8'
VK_B64_ILP = b'7Bcrk61eVjv0kyxw4SRQNMNUZ+8u/U1k6/gZaDRn4r8='
Copy link

Choose a reason for hiding this comment

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

Curious as to why the = was added?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There was some unnecessary adding and removing of padding

"""
Sign data with private key

Args:
data (str, bytes): data to sign
prefix:
encoding (str): base64, hex
Copy link

Choose a reason for hiding this comment

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

Probably should be encoding (str): {'base58'|'base64'|'base32'|'base16'|'hex'}?

"""
PrivateKey instance
"""

def __init__(self, key):
def __init__(self, key, encoding='base58'):
"""
Instantiate the private key with the private_value encoded in base58
Copy link

Choose a reason for hiding this comment

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

These docs should probably also be updated.

class Ed25519VerifyingKey(ed25519.VerifyingKey, VerifyingKey):

def __init__(self, key):
def __init__(self, key, encoding='base58'):
"""
Instantiate the public key with the compressed public value encoded in base58
Copy link

Choose a reason for hiding this comment

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

Should probably update docs.

"""
Verify if the signature signs the data with this verifying key

Args:
data (bytes|str): data to be signed
data (bytes|str): data verify
Copy link

Choose a reason for hiding this comment

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

Small typo: data to verify

# The reason for using raw_signatures here is because the verify method of pynacl expects the message
# and the signature to have the same encoding. Basically pynacl does:
# encoder.decode(signature + message)
raw_signature = _get_nacl_encoder(encoding).decode(signature)
Copy link

Choose a reason for hiding this comment

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

I don't think the encoder throws; can we put this above the try?


# Public key
public_value_compressed_base58 = Ed25519VerifyingKey(base58.b58encode(vk.to_bytes())).to_ascii()
public_value_compressed_base58 = sk.verify_key.encode(encoder=Base58Encoder)
Copy link

@sohkai sohkai Sep 29, 2016

Choose a reason for hiding this comment

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

In regards to #23, we would want this to be .decode()ed before it gets returned. This seems to only be used outside of the library, and so decoding it into a string is easier for others to use. A named tuple would also be nice here 😉.

We can address #23 in a later PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No sure if I want to do that inside this library.
If I do it for keys I also need to do it for signatures.
I feel like its more consistent if the this library uses only byte strings throughout and we can decode / encode what we need in the bigchaindb wrappers

Copy link

Choose a reason for hiding this comment

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

The dict of fullfillments also decodes the key (although the signature isn't).

Perhaps this is something about external/internal handling; to me this method is only applicable to external users so it should be decoded. I agree that internally, we should just use byte strings.

raise exceptions.UnknownEncodingError("Unknown or unsupported encoding")


class Ed25519SigningKey(nacl.signing.SigningKey):
Copy link

Choose a reason for hiding this comment

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

Given that we don't use the generate() classmethod (and it probably doesn't work?), perhaps composition here would be preferable to inheritance?

Copy link
Contributor

Choose a reason for hiding this comment

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

composition is a bit messy in python, as you need to explicitly implement each method

Copy link

Choose a reason for hiding this comment

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

But... you realize we've already re-implemented each method anyway? And if we hadn't, the non-overridden functions would've likely been broken if accessed from an instance of this class (e.g. generate before being re-implemented)?

composition is a bit messy in python

This is simply not true 😄.

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 simply not true .

http://blog.thedigitalcatonline.com/blog/2014/08/20/python-3-oop-part-3-delegation-composition-and-inheritance/

you'll need to reimplement each method as you are wrapping the object in an attribute... messy/inefficient
Haven't encountered a good example of composition in python

Copy link
Contributor

Choose a reason for hiding this comment

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

BTW: in this case it seems not worth spending effort in discussing the most elegant pattern... bigger fish



def ed25519_generate_key_pair():
"""
Generate a new key pair and return the pair encoded in base58
"""
sk, vk = ed25519.create_keypair()
sk = nacl.signing.SigningKey.generate()
Copy link

Choose a reason for hiding this comment

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

Does seems slightly awkward that we just defined our own version of SigningKey and then use the pynacl one here. Maybe we should expose generate in our class and use it here?

@sohkai
Copy link

sohkai commented Sep 29, 2016

Really like how much nicer the crypto module is now :)!

So glad we don't have things like this base58.b58encode(self.to_seed()).encode('ascii').decode('ascii').rstrip("=").encode('ascii') running around 😉.

@@ -154,4 +154,4 @@ def validate(self, message=None, **kwargs):
if not (message and self.signature):
return False

return self.public_key.verify(message, self.signature, encoding=None)
return self.public_key.verify(message, self.signature, encoding='bytes')
Copy link

@sohkai sohkai Sep 29, 2016

Choose a reason for hiding this comment

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

Should probably update the docstring to specify that message has to be a bytestring.

@@ -66,7 +66,7 @@ def sign(self, message, private_key):
# .update(Buffer.concat([this.messagePrefix, this.message]))
# .digest()

self.signature = sk.sign(message, encoding=None)
self.signature = sk.sign(message, encoding='bytes')
Copy link

Choose a reason for hiding this comment

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

Should probably update the docstring to specify that message has to be a bytestring

Copy link
Contributor

Choose a reason for hiding this comment

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

make sure it is in line with the cc spec. Buffer means bytes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This didn't change anything. encoding='bytes' is that same as the old 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.

from the docstring: message (string): message to be signed
needs to be:

message (bytes): message to be signed

Copy link
Contributor

@diminator diminator left a comment

Choose a reason for hiding this comment

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

Great stuff - minor changes

raise exceptions.UnknownEncodingError("Unknown or unsupported encoding")


class Ed25519SigningKey(nacl.signing.SigningKey):
Copy link
Contributor

Choose a reason for hiding this comment

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

composition is a bit messy in python, as you need to explicitly implement each method


Args:
key (base58): base58 encoded private key
key (str): encoded private value.
Copy link
Contributor

Choose a reason for hiding this comment

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

str or bytes?

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe also typecheck on either

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It works well we both. I guess the encoders handle both cases.
But to be consistent I will put bytes

Instantiate the public key with the public value.

Args:
key (str): encoded compressed value.
Copy link
Contributor

Choose a reason for hiding this comment

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

str or bytes? typecheck?

# Private key
private_value_base58 = Ed25519SigningKey(base58.b58encode(sk.to_bytes())).to_ascii()
private_value_base58 = sk.encode(encoding='base58')
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to signing_key_base58 or sk_base58
or better: add encoding as a kwarg: ed25519_generate_key_pair(encoding='base58')


# Public key
public_value_compressed_base58 = Ed25519VerifyingKey(base58.b58encode(vk.to_bytes())).to_ascii()
public_value_compressed_base58 = sk.get_verifying_key().encode(encoding='base58')
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to vk_base58 or verifying_key_base58
consider encoding as kwarg

@@ -57,7 +57,7 @@ def sign(self, message, private_key):
private_key (string) Ed25519 private key
"""
sk = private_key
vk = VerifyingKey(base58.b58encode(sk.get_verifying_key().to_bytes()))
vk = VerifyingKey(base58.b58encode(sk.get_verifying_key().encode(encoding='bytes')))
Copy link
Contributor

Choose a reason for hiding this comment

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

vk = sk.get_verifying_key().encode(encoding='base58') ?

Copy link
Contributor

@diminator diminator Sep 29, 2016

Choose a reason for hiding this comment

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

Does get_verifying_key come from nacl inheritance and runs a nacl.signing.VerifyKey?
maybe we can override that method to return a Ed25519VerifyingKey?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

get_verifying_key was already part of the Ed25519SigningKey api. Didn't want to change it since its also used in the rest of the cc code.

Copy link
Contributor

@diminator diminator Sep 30, 2016

Choose a reason for hiding this comment

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

ok, but you can get rid of the base58.b58encode

@@ -66,7 +66,7 @@ def sign(self, message, private_key):
# .update(Buffer.concat([this.messagePrefix, this.message]))
# .digest()

self.signature = sk.sign(message, encoding=None)
self.signature = sk.sign(message, encoding='bytes')
Copy link
Contributor

Choose a reason for hiding this comment

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

make sure it is in line with the cc spec. Buffer means bytes

@diminator
Copy link
Contributor

But... you realize we've already re-implemented each method anyway?

agreed

@diminator
Copy link
Contributor

👍 mergeable!

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.

Investigate possible encoding problem
4 participants