From 3950dde249bd3111cde0a6341308b55c9f2ecd0e Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 14 Sep 2016 14:00:53 +0200 Subject: [PATCH 1/5] initial implentation of the switch to nacl --- cryptoconditions/crypto.py | 249 +++++++-------------------------- cryptoconditions/exceptions.py | 4 + setup.py | 2 +- tests/test_crypto.py | 22 ++- 4 files changed, 73 insertions(+), 204 deletions(-) diff --git a/cryptoconditions/crypto.py b/cryptoconditions/crypto.py index 46338ee..9362c13 100644 --- a/cryptoconditions/crypto.py +++ b/cryptoconditions/crypto.py @@ -1,269 +1,116 @@ # Separate all crypto code so that we can easily test several implementations -from abc import ABCMeta, abstractmethod import base64 - import base58 -import ed25519 - - -class SigningKey(metaclass=ABCMeta): - """ - SigningKey instance - """ - - @abstractmethod - def sign(self, data): - """ - Sign data with private key - - Args: - data: - """ - - @abstractmethod - def get_verifying_key(self): - """ - Get the associated verifying key - - Returns: - A VerifyingKey object - """ - - @abstractmethod - def to_ascii(self, prefix, encoding): - """ - Encode the external value - - Args: - prefix: - encoding: - """ - - @staticmethod - @abstractmethod - def encode(private_value): - """ - Encode the internal private_value to base58 - - Args: - private_value: - """ - - @staticmethod - @abstractmethod - def decode(private_base58): - """ - Decode the base58 private value to internal value - - Args: - private_base58 (base58): - """ - raise NotImplementedError +import nacl.signing +import nacl.encoding +import nacl.exceptions +from cryptoconditions import exceptions -class VerifyingKey(metaclass=ABCMeta): - @abstractmethod - def verify(self, data, signature): - """ - Check the if the signature matches the data and this verifyingkey - - Args: - data: - signature: - - Returns: - boolean: - """ - - @abstractmethod - def to_ascii(self, prefix, encoding): - """ - Encode the external value - - Args: - prefix: - encoding: - """ +class Base58Encoder(object): @staticmethod - @abstractmethod - def encode(public_value): - """ - Encode the public key to base58 represented by the internal values - - Args: - public_value - """ + def encode(data): + return base58.b58encode(data) @staticmethod - @abstractmethod - def decode(public_base58): - """ - Decode the base58 public_value to internal value - - Args: - public_base58 (base58): - """ - - -class Ed25519SigningKey(ed25519.SigningKey, SigningKey): + def decode(data): + return base58.b58decode(data) + + +def _get_nacl_encoder(encoding): + if encoding == 'base58': + return Base58Encoder + elif encoding == 'base64': + return nacl.encoding.Base64Encoder + elif encoding == 'base32': + return nacl.encoding.Base32Encoder + elif encoding == 'base16': + return nacl.encoding.Base16Encoder + elif encoding == 'hex': + return nacl.encoding.HexEncoder + elif encoding is None: + return nacl.encoding.RawEncoder + else: + raise exceptions.UnknownEncodingError("Unknown or unsupported encoding") + + +class Ed25519SigningKey(nacl.signing.SigningKey): """ PrivateKey instance """ - def __init__(self, key): + def __init__(self, key, encoding='base58'): """ Instantiate the private key with the private_value encoded in base58 Args: key (base58): base58 encoded private key """ - private_base64 = self.decode(key) - super().__init__(private_base64, encoding='base64') + super().__init__(key, encoder=_get_nacl_encoder(encoding)) def get_verifying_key(self): """ Get the corresponding VerifyingKey Returns: - VerifyingKey + Ed25519VerifyingKey """ - vk = super().get_verifying_key() - return Ed25519VerifyingKey(base58.b58encode(vk.to_bytes())) + return Ed25519VerifyingKey(self.verify_key.encode(encoder=Base58Encoder)) - def to_ascii(self, prefix="", encoding='base58'): - """ - convert external value to ascii with specified encoding - - Args: - prefix (str): - encoding (str): {'base58'|'base64'|'base32'|'base16'|'hex'} - - Returns: - bytes: encoded string - """ - if encoding == 'base58': - return base58.b58encode(self.to_seed()).encode('ascii').decode('ascii').rstrip("=").encode('ascii') - else: - return super().to_ascii(prefix=prefix, encoding=encoding) - - def sign(self, data, prefix="", encoding="base58"): + def sign(self, data, encoding="base58"): """ Sign data with private key Args: data (str, bytes): data to sign - prefix: encoding (str): base64, hex """ - if not isinstance(data, bytes): - data = data.encode('ascii') - if encoding == 'base58': - signature = super().sign(data, prefix="", encoding='base64') - return base58.b58encode(base64.b64decode(base64_add_padding(signature))) - else: - return super().sign(data, prefix="", encoding=encoding) - - @staticmethod - def encode(private_base64): - """ - Encode the base64 number private_base64 to base58 - - Args: - private_base64: - """ - return base58.b58encode(base64.b64decode(private_base64)).encode('ascii') + return super().sign(encoder=_get_nacl_encoder(encoding)) - @staticmethod - def decode(key): - """ - Decode the base58 private_value to base64 - - Args: - key: - """ - return base64.b64encode(base58.b58decode(key)) + def encode(self, encoding='base58'): + return super().encode(encoder=_get_nacl_encoder(encoding)) -class Ed25519VerifyingKey(ed25519.VerifyingKey, VerifyingKey): +class Ed25519VerifyingKey(nacl.signing.VerifyKey): def __init__(self, key): """ Instantiate the public key with the compressed public value encoded in base58 """ - public_base64 = self.decode(key) - super().__init__(public_base64, encoding='base64') + super().__init__(key, encoder=Base58Encoder) - def verify(self, data, signature, prefix="", encoding='base58'): + def verify(self, data, signature, encoding='base58'): """ Verify if the signature signs the data with this verifying key Args: - data (bytes|str): data to be signed + data (bytes|str): data verify signature (bytes|str): {base64|base32|base16|hex|bytes} signature to be verified - prefix: see super encoding: {base64|base32|base16|hex|bytes} encoding of the signature """ try: - if not isinstance(data, bytes): - data = data.encode('ascii') - if encoding == 'base58': - super().verify(base58.b58decode(signature), data, prefix=prefix) - else: - super().verify(signature, data, prefix=prefix, encoding=encoding) - except ed25519.BadSignatureError: + super().verify(data, signature, encoder=_get_nacl_encoder(encoding)) + except nacl.exceptions.BadSignatureError: return False return True - def to_ascii(self, prefix="", encoding='base58'): - """ - convert external value to ascii with specified encoding - - Args: - prefix (str): - encoding (str): {'base58'|'base64'|'base32'|'base16'|'hex'} - - Returns: - bytes: encoded string - """ - if encoding == 'base58': - return base58.b58encode(self.vk_s).encode('ascii').decode('ascii').rstrip("=").encode() - else: - return super().to_ascii(prefix=prefix, encoding=encoding) - - @staticmethod - def encode(public_base64): - """ - Encode the public key represented by base64 to base58 - - Args: - public_base64 - """ - return Ed25519SigningKey.encode(public_base64) - - @staticmethod - def decode(public_base58): - """ - Decode the base58 public_value to base64 - - Args: - public_base58 - """ - return Ed25519SigningKey.decode(public_base58) + def encode(self, encoding='base58'): + return super().encode(encoder=_get_nacl_encoder(encoding)) 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() # Private key - private_value_base58 = Ed25519SigningKey(base58.b58encode(sk.to_bytes())).to_ascii() + private_value_base58 = sk.encode(encoder=Base58Encoder) # Public key - public_value_compressed_base58 = Ed25519VerifyingKey(base58.b58encode(vk.to_bytes())).to_ascii() + public_value_compressed_base58 = sk.verify_key.encode(encoder=Base58Encoder) return private_value_base58, public_value_compressed_base58 diff --git a/cryptoconditions/exceptions.py b/cryptoconditions/exceptions.py index 6a67bea..2185ed1 100644 --- a/cryptoconditions/exceptions.py +++ b/cryptoconditions/exceptions.py @@ -12,3 +12,7 @@ class UnsupportedTypeError(Exception): class ValidationError(Exception): """Raised when a validation errors out""" + + +class UnknownEncodingError(Exception): + """Raised when an unknown or unsuported encoding is used""" diff --git a/setup.py b/setup.py index f386d35..dcb64f7 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ install_requires=[ 'base58==0.2.2', - 'ed25519', + 'PyNaCl==1.0.1', ], setup_requires=['pytest-runner'], tests_require=tests_require, diff --git a/tests/test_crypto.py b/tests/test_crypto.py index f1b575b..d42eab1 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -11,12 +11,13 @@ class TestBigchainCryptoED25519(object): def test_signing_key_encode(self, sk_ilp): - private_value_base58 = SigningKey.encode(base64_add_padding(sk_ilp['b64'])) + sk = SigningKey(base64_add_padding(sk_ilp['b64']), encoding='base64') + private_value_base58 = sk.encode(encoding="base58") assert private_value_base58 == sk_ilp['b58'] def test_signing_key_init(self, sk_ilp): sk = SigningKey(sk_ilp['b58']) - assert sk.to_ascii(encoding='base64') == sk_ilp['b64'] + assert sk.encode(encoding='base64') == sk_ilp['b64'] assert sk.to_seed() == sk_ilp['byt'] def test_signing_key_decode(self, sk_ilp): @@ -99,3 +100,20 @@ def test_generate_sign_verify(self, vk_ilp): VerifyingKey.encode( base64_add_padding(vk_ilp[2]['b64']))) assert vk.verify(message, sk.sign(message)) is False + + def test_weak_public_keys(self): + """reproduce the problem in https://github.com/bigchaindb/bigchaindb/issues/617 + + This problem is due to weak keys, specially in this case the key and signature + when decoded from base58 correspond to a key and a signature that are zero. + In this case its possible to come up with messages that would verify. + + Libraries like libsodium check for these weak keys and return a BadSignature error + if weak keys are being used. + + More details here: https://github.com/jedisct1/libsodium/issues/112 + """ + vk_b58 = VerifyingKey('1' * 32) + message = 'age=33&name=luo&title=architecture' + signature = '1' * 64 + assert vk_b58.verify(message, signature) == False From 0d72c9903957f0c7a67f92c7c3112d5cedc7d145 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 28 Sep 2016 17:01:55 +0200 Subject: [PATCH 2/5] Better handling of encoding Fixed tests --- cryptoconditions/crypto.py | 17 +++-- cryptoconditions/types/ed25519.py | 12 ++-- cryptoconditions/types/threshold_sha256.py | 2 +- tests/conftest.py | 6 +- tests/test_crypto.py | 73 ++++++++++------------ tests/test_fulfillment.py | 19 +++--- 6 files changed, 65 insertions(+), 64 deletions(-) diff --git a/cryptoconditions/crypto.py b/cryptoconditions/crypto.py index 9362c13..be2f6a9 100644 --- a/cryptoconditions/crypto.py +++ b/cryptoconditions/crypto.py @@ -12,7 +12,7 @@ class Base58Encoder(object): @staticmethod def encode(data): - return base58.b58encode(data) + return base58.b58encode(data).encode() @staticmethod def decode(data): @@ -30,7 +30,7 @@ def _get_nacl_encoder(encoding): return nacl.encoding.Base16Encoder elif encoding == 'hex': return nacl.encoding.HexEncoder - elif encoding is None: + elif encoding is 'bytes': return nacl.encoding.RawEncoder else: raise exceptions.UnknownEncodingError("Unknown or unsupported encoding") @@ -67,7 +67,8 @@ def sign(self, data, encoding="base58"): data (str, bytes): data to sign encoding (str): base64, hex """ - return super().sign(encoder=_get_nacl_encoder(encoding)) + raw_signature = super().sign(data).signature + return _get_nacl_encoder(encoding).encode(raw_signature) def encode(self, encoding='base58'): return super().encode(encoder=_get_nacl_encoder(encoding)) @@ -75,11 +76,11 @@ def encode(self, encoding='base58'): class Ed25519VerifyingKey(nacl.signing.VerifyKey): - def __init__(self, key): + def __init__(self, key, encoding='base58'): """ Instantiate the public key with the compressed public value encoded in base58 """ - super().__init__(key, encoder=Base58Encoder) + super().__init__(key, encoder=_get_nacl_encoder(encoding)) def verify(self, data, signature, encoding='base58'): """ @@ -91,7 +92,11 @@ def verify(self, data, signature, encoding='base58'): encoding: {base64|base32|base16|hex|bytes} encoding of the signature """ try: - super().verify(data, signature, encoder=_get_nacl_encoder(encoding)) + # 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) + super().verify(data, raw_signature) except nacl.exceptions.BadSignatureError: return False diff --git a/cryptoconditions/types/ed25519.py b/cryptoconditions/types/ed25519.py index ddcf8fa..60d7b66 100644 --- a/cryptoconditions/types/ed25519.py +++ b/cryptoconditions/types/ed25519.py @@ -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'))) self.public_key = vk @@ -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') def generate_hash(self): """ @@ -77,7 +77,7 @@ def generate_hash(self): """ if not self.public_key: raise ValueError('Requires a public publicKey') - return self.public_key.to_bytes() + return self.public_key.encode(encoding='bytes') def parse_payload(self, reader, *args): """ @@ -104,7 +104,7 @@ def write_payload(self, writer): Args: writer (Writer): Subject for writing the fulfillment payload. """ - writer.write_octet_string(self.public_key.to_bytes(), Ed25519Fulfillment.PUBKEY_LENGTH) + writer.write_octet_string(self.public_key.encode(encoding='bytes'), Ed25519Fulfillment.PUBKEY_LENGTH) writer.write_octet_string(self.signature, Ed25519Fulfillment.SIGNATURE_LENGTH) return writer @@ -122,7 +122,7 @@ def to_dict(self): 'type': 'fulfillment', 'type_id': self.TYPE_ID, 'bitmask': self.bitmask, - 'public_key': self.public_key.to_ascii(encoding='base58').decode(), + 'public_key': self.public_key.encode(encoding='base58').decode(), 'signature': base58.b58encode(self.signature) if self.signature else None } @@ -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') diff --git a/cryptoconditions/types/threshold_sha256.py b/cryptoconditions/types/threshold_sha256.py index aae4e02..742cde7 100644 --- a/cryptoconditions/types/threshold_sha256.py +++ b/cryptoconditions/types/threshold_sha256.py @@ -164,7 +164,7 @@ def get_subcondition_from_vk(self, vk): conditions = [] for c in self.subconditions: - if isinstance(c['body'], Ed25519Fulfillment) and c['body'].public_key.to_ascii(encoding='base58') == vk: + if isinstance(c['body'], Ed25519Fulfillment) and c['body'].public_key.encode(encoding='base58') == vk: conditions.append(c['body']) elif isinstance(c['body'], ThresholdSha256Fulfillment): result = c['body'].get_subcondition_from_vk(vk) diff --git a/tests/conftest.py b/tests/conftest.py index f492b57..e4eb9fb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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=' VK_B58_ILP = b'Gtbi6WQDB6wUePiZm8aYs5XZ5pUqx9jMMLvRVHPESTjU' VK_BYT_ILP = b'\xec\x17+\x93\xad^V;\xf4\x93,p\xe1$P4\xc3Tg\xef.\xfdMd\xeb\xf8\x19h4g\xe2\xbf' SK_HEX_ILP = b'833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42' -SK_B64_ILP = b'gz/mJAkje51i7HdYdSCRHpp1nOwdGXVbfakBuW3KPUI' +SK_B64_ILP = b'gz/mJAkje51i7HdYdSCRHpp1nOwdGXVbfakBuW3KPUI=' SK_B58_ILP = b'9qLvREC54mhKYivr88VpckyVWdAFmifJpGjbvV5AiTRs' SK_BYT_ILP = b'\x83?\xe6$\t#{\x9db\xecwXu \x91\x1e\x9au\x9c\xec\x1d\x19u[}\xa9\x01\xb9m\xca=B' @@ -18,7 +18,7 @@ VK_HEX_ILP_2 = b'a614d63a28be3e8e45ea99638d22abc0430e4112a28b3f601a617f9c7f445021' VK_B58_ILP_2 = b'CBK79fAE8AVxwprPumyLrW5JfHaDRAReSpmsg92FV3GL' -VK_B64_ILP_2 = b'phTWOii+Po5F6pljjSKrwEMOQRKiiz9gGmF/nH9EUCE' +VK_B64_ILP_2 = b'phTWOii+Po5F6pljjSKrwEMOQRKiiz9gGmF/nH9EUCE=' MSG_SHA_ILP = 'claZQU7qkFz7smkAVtQp9ekUCc5LgoeN9W3RItIzykNEDbGSvzeHvOk9v/vrPpm+XWx5VFjd/sVbM2SLnCpxLw==' SIG_B64_ILP = 'ZAg/R++Z3yhggpW8iviqdDwhhV0nK6et/Nn66Hcds9rSk3f4JDsNHws1dMsbqY6KKuToxwvDuDmzW3a8JVqwBg==' diff --git a/tests/test_crypto.py b/tests/test_crypto.py index d42eab1..8c8daf0 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -11,94 +11,89 @@ class TestBigchainCryptoED25519(object): def test_signing_key_encode(self, sk_ilp): - sk = SigningKey(base64_add_padding(sk_ilp['b64']), encoding='base64') + sk = SigningKey(sk_ilp['b64'], encoding='base64') private_value_base58 = sk.encode(encoding="base58") assert private_value_base58 == sk_ilp['b58'] def test_signing_key_init(self, sk_ilp): sk = SigningKey(sk_ilp['b58']) assert sk.encode(encoding='base64') == sk_ilp['b64'] - assert sk.to_seed() == sk_ilp['byt'] + assert sk.encode(encoding='bytes') == sk_ilp['byt'] def test_signing_key_decode(self, sk_ilp): - private_value = SigningKey.decode(sk_ilp['b58']) - assert private_value == base64_add_padding(sk_ilp['b64']) + sk = SigningKey(sk_ilp['b58']) + private_value = sk.encode(encoding='base64') + assert private_value == sk_ilp['b64'] def test_verifying_key_encode(self, vk_ilp): - public_value_base58 = VerifyingKey.encode(base64_add_padding(vk_ilp['b64'])) + vk = VerifyingKey(vk_ilp['b64'], encoding='base64') + public_value_base58 = vk.encode(encoding='base58') assert public_value_base58 == vk_ilp['b58'] def test_verifying_key_init(self, vk_ilp): vk = VerifyingKey(vk_ilp['b58']) - assert vk.to_ascii(encoding='base64') == vk_ilp['b64'] - assert vk.to_bytes() == vk_ilp['byt'] + assert vk.encode(encoding='base64') == vk_ilp['b64'] + assert vk.encode(encoding='bytes') == vk_ilp['byt'] def test_verifying_key_decode(self, vk_ilp): - public_value = VerifyingKey.decode(vk_ilp['b58']) - assert public_value == base64_add_padding(vk_ilp['b64']) + vk = VerifyingKey(vk_ilp['b58']) + public_value = vk.encode(encoding='base64') + assert public_value == vk_ilp['b64'] def test_sign_verify(self, sk_ilp, vk_ilp): - message = 'Hello World!' + message = b'Hello World!' sk = SigningKey(sk_ilp['b58']) vk = VerifyingKey(vk_ilp['b58']) assert vk.verify(message, sk.sign(message)) is True - assert vk.verify(message, sk.sign(message + 'dummy')) is False - assert vk.verify(message + 'dummy', sk.sign(message)) is False - vk = VerifyingKey( - VerifyingKey.encode( - base64_add_padding(vk_ilp[2]['b64']))) + assert vk.verify(message, sk.sign(message + b'dummy')) is False + assert vk.verify(message + b'dummy', sk.sign(message)) is False + vk = VerifyingKey(vk_ilp[2]['b64'], encoding='base64') assert vk.verify(message, sk.sign(message)) is False - def test_to_ascii(self, sk_ilp, vk_ilp): + def test_to_bytes(self, sk_ilp, vk_ilp): sk = SigningKey(sk_ilp['b58']) - assert sk.to_ascii(encoding='base58') == sk_ilp['b58'] - assert sk.to_ascii(encoding='base64') == sk_ilp['b64'] + assert sk.encode(encoding='base58') == sk_ilp['b58'] + assert sk.encode(encoding='base64') == sk_ilp['b64'] vk = VerifyingKey(vk_ilp['b58']) - assert vk.to_ascii(encoding='base58') == vk_ilp['b58'] - assert vk.to_ascii(encoding='base64') == vk_ilp['b64'] + assert vk.encode(encoding='base58') == vk_ilp['b58'] + assert vk.encode(encoding='base64') == vk_ilp['b64'] def test_get_verifying_key(self, sk_ilp, vk_ilp): sk = SigningKey(sk_ilp['b58']) vk = VerifyingKey(vk_ilp['b58']) vk_from_sk = sk.get_verifying_key() - assert vk.to_bytes() == vk_from_sk.to_bytes() + assert vk.encode(encoding='bytes') == vk_from_sk.encode(encoding='bytes') def test_valid_condition_valid_signature_ilp(self, vk_ilp, signature): - vk = VerifyingKey( - VerifyingKey.encode( - base64_add_padding(vk_ilp[2]['b64']))) + vk = VerifyingKey(vk_ilp[2]['b64'], encoding='base64') msg = base64.b64decode(signature['msg']) assert vk.verify(msg, signature['sig'], encoding='base64') is True assert vk.verify(msg, binascii.hexlify(base64.b64decode(signature['sig'])), encoding='hex') is True - assert vk.verify(msg, base64.b64decode(signature['sig']), encoding=None) is True + assert vk.verify(msg, base64.b64decode(signature['sig']), encoding='bytes') is True def test_valid_condition_invalid_signature_ilp(self, vk_ilp, signature): - vk = VerifyingKey( - VerifyingKey.encode( - base64_add_padding(vk_ilp[2]['b64']))) + vk = VerifyingKey(vk_ilp[2]['b64'], encoding='base64') msg = base64.b64decode(signature['msg']) assert vk.verify(msg, signature['msg'], encoding='base64') is False assert vk.verify(msg, binascii.hexlify(base64.b64decode(signature['msg'])), encoding='hex') is False - assert vk.verify(msg, base64.b64decode(signature['msg']), encoding=None) is False + assert vk.verify(msg, base64.b64decode(signature['msg']), encoding='bytes') is False def test_generate_key_pair(self): sk_b58, vk_b58 = ed25519_generate_key_pair() assert len(base58.b58decode(sk_b58)) == 32 assert len(base58.b58decode(vk_b58)) == 32 - assert SigningKey.encode(SigningKey.decode(sk_b58)) == sk_b58 - assert VerifyingKey.encode(VerifyingKey.decode(vk_b58)) == vk_b58 + assert SigningKey(sk_b58).encode() == sk_b58 + assert VerifyingKey(vk_b58).encode() == vk_b58 def test_generate_sign_verify(self, vk_ilp): sk_b58, vk_b58 = ed25519_generate_key_pair() sk = SigningKey(sk_b58) vk = VerifyingKey(vk_b58) - message = 'Hello World!' + message = b'Hello World!' assert vk.verify(message, sk.sign(message)) is True - assert vk.verify(message, sk.sign(message + 'dummy')) is False - assert vk.verify(message + 'dummy', sk.sign(message)) is False - vk = VerifyingKey( - VerifyingKey.encode( - base64_add_padding(vk_ilp[2]['b64']))) + assert vk.verify(message, sk.sign(message + b'dummy')) is False + assert vk.verify(message + b'dummy', sk.sign(message)) is False + vk = VerifyingKey(vk_ilp[2]['b64'], encoding='base64') assert vk.verify(message, sk.sign(message)) is False def test_weak_public_keys(self): @@ -114,6 +109,6 @@ def test_weak_public_keys(self): More details here: https://github.com/jedisct1/libsodium/issues/112 """ vk_b58 = VerifyingKey('1' * 32) - message = 'age=33&name=luo&title=architecture' - signature = '1' * 64 + message = b'age=33&name=luo&title=architecture' + signature = b'1' * 64 assert vk_b58.verify(message, signature) == False diff --git a/tests/test_fulfillment.py b/tests/test_fulfillment.py index 10d75dc..0bbf386 100644 --- a/tests/test_fulfillment.py +++ b/tests/test_fulfillment.py @@ -18,7 +18,7 @@ Ed25519VerifyingKey as VerifyingKey from cryptoconditions.types.timeout import timestamp -MESSAGE = 'Hello World! Conditions are here!' +MESSAGE = b'Hello World! Conditions are here!' class TestSha256Condition: @@ -79,12 +79,12 @@ def test_condition_from_fulfillment(self): class TestEd25519Sha256Fulfillment: def test_ilp_keys(self, sk_ilp, vk_ilp): sk = SigningKey(sk_ilp['b58']) - assert sk.to_ascii(encoding='base64') == sk_ilp['b64'] - assert binascii.hexlify(sk.to_bytes()[:32]) == sk_ilp['hex'] + assert sk.encode(encoding='base64') == sk_ilp['b64'] + assert binascii.hexlify(sk.encode(encoding='bytes')[:32]) == sk_ilp['hex'] vk = VerifyingKey(vk_ilp['b58']) - assert vk.to_ascii(encoding='base64') == vk_ilp['b64'] - assert binascii.hexlify(vk.to_bytes()) == vk_ilp['hex'] + assert vk.encode(encoding='base64') == vk_ilp['b64'] + assert binascii.hexlify(vk.encode(encoding='bytes')) == vk_ilp['hex'] def test_create(self, vk_ilp): fulfillment1 = Ed25519Fulfillment(public_key=vk_ilp['b58']) @@ -173,7 +173,7 @@ def test_deserialize_fulfillment(self, vk_ilp, fulfillment_ed25519): assert fulfillment.serialize_uri() == fulfillment_ed25519['fulfillment_uri'] assert fulfillment.condition.serialize_uri() == fulfillment_ed25519['condition_uri'] assert binascii.hexlify(fulfillment.condition.hash) == fulfillment_ed25519['condition_hash'] - assert fulfillment.public_key.to_ascii(encoding='hex') == vk_ilp['hex'] + assert fulfillment.public_key.encode(encoding='hex') == vk_ilp['hex'] assert fulfillment.validate(MESSAGE) def test_deserialize_fulfillment_2(self, vk_ilp, fulfillment_ed25519_2): @@ -183,7 +183,7 @@ def test_deserialize_fulfillment_2(self, vk_ilp, fulfillment_ed25519_2): assert fulfillment.serialize_uri() == fulfillment_ed25519_2['fulfillment_uri'] assert fulfillment.condition.serialize_uri() == fulfillment_ed25519_2['condition_uri'] assert binascii.hexlify(fulfillment.condition.hash) == fulfillment_ed25519_2['condition_hash'] - assert fulfillment.public_key.to_ascii(encoding='hex') == vk_ilp[2]['hex'] + assert fulfillment.public_key.encode(encoding='hex') == vk_ilp[2]['hex'] assert fulfillment.validate(MESSAGE) def test_serialize_deserialize_fulfillment(self, sk_ilp, vk_ilp): @@ -199,7 +199,8 @@ def test_serialize_deserialize_fulfillment(self, sk_ilp, vk_ilp): assert isinstance(deserialized_fulfillment, Ed25519Fulfillment) assert deserialized_fulfillment.serialize_uri() == fulfillment.serialize_uri() assert deserialized_fulfillment.condition.serialize_uri() == fulfillment.condition.serialize_uri() - assert deserialized_fulfillment.public_key.to_bytes() == fulfillment.public_key.to_bytes() + assert deserialized_fulfillment.public_key.encode(encoding='bytes') == \ + fulfillment.public_key.encode(encoding='bytes') assert deserialized_fulfillment.validate(MESSAGE) @@ -438,7 +439,7 @@ def test_fulfillment_nested_and_or(self, assert deserialized_fulfillment.serialize_uri() == fulfillment_uri assert deserialized_fulfillment.validate(MESSAGE) assert deserialized_condition.serialize_uri() == condition_uri - vk = ilp_fulfillment_ed.public_key.to_ascii(encoding='base58') + vk = ilp_fulfillment_ed.public_key.encode(encoding='base58') assert len(fulfillment.get_subcondition_from_vk(vk)) == 2 assert len(deserialized_fulfillment.get_subcondition_from_vk(vk)) == 1 From fbc298f05aa440fba89bbfdaffe333747b23d794 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Thu, 29 Sep 2016 13:43:15 +0200 Subject: [PATCH 3/5] Fixed docstrings --- cryptoconditions/crypto.py | 64 +++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/cryptoconditions/crypto.py b/cryptoconditions/crypto.py index be2f6a9..c35f245 100644 --- a/cryptoconditions/crypto.py +++ b/cryptoconditions/crypto.py @@ -43,10 +43,12 @@ class Ed25519SigningKey(nacl.signing.SigningKey): def __init__(self, key, encoding='base58'): """ - Instantiate the private key with the private_value encoded in base58 + Instantiate the private key with the private value. Args: - key (base58): base58 encoded private key + key (str): encoded private value. + encoding(str): {'bytes'|'hex'|'base16'|'base32'|'base58'|'base64'}. Encoding of the private + value. Defaults to 'base58'. """ super().__init__(key, encoder=_get_nacl_encoder(encoding)) @@ -59,18 +61,32 @@ def get_verifying_key(self): """ return Ed25519VerifyingKey(self.verify_key.encode(encoder=Base58Encoder)) - def sign(self, data, encoding="base58"): + def sign(self, data, encoding='base58'): """ Sign data with private key Args: - data (str, bytes): data to sign - encoding (str): base64, hex + data (bytes): data to sign. + encoding(str): {'bytes'|'hex'|'base16'|'base32'|'base58'|'base64'}. Encoding in which to return the + signature. Defaults to 'base58'. + + Returns: + The signature encoded in `encoding`. """ raw_signature = super().sign(data).signature return _get_nacl_encoder(encoding).encode(raw_signature) def encode(self, encoding='base58'): + """ + Encode the private key + + Args: + encoding(str): {'bytes'|'hex'|'base16'|'base32'|'base58'|'base64'}. Encoding in which the private + key should be returned. Defaults to 'base58'. + + Returns: + The private key encoded with `encoding`. + """ return super().encode(encoder=_get_nacl_encoder(encoding)) @@ -78,7 +94,12 @@ class Ed25519VerifyingKey(nacl.signing.VerifyKey): def __init__(self, key, encoding='base58'): """ - Instantiate the public key with the compressed public value encoded in base58 + Instantiate the public key with the public value. + + Args: + key (str): encoded compressed value. + encoding(str): {'bytes'|'hex'|'base16'|'base32'|'base58'|'base64'}. Encoding of the public key. + Defaults to 'base58'. """ super().__init__(key, encoder=_get_nacl_encoder(encoding)) @@ -87,15 +108,17 @@ def verify(self, data, signature, encoding='base58'): Verify if the signature signs the data with this verifying key Args: - data (bytes|str): data verify - signature (bytes|str): {base64|base32|base16|hex|bytes} signature to be verified - encoding: {base64|base32|base16|hex|bytes} encoding of the signature + data (bytes): data to verify. + signature (bytes): {base64|base32|base16|hex|bytes} signature to be verified + encoding(str): {'bytes'|'hex'|'base16'|'base32'|'base58'|'base64'}. Encoding of the signature. + Defaults to 'base58'. """ + + # 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) try: - # 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) super().verify(data, raw_signature) except nacl.exceptions.BadSignatureError: return False @@ -103,12 +126,25 @@ def verify(self, data, signature, encoding='base58'): return True def encode(self, encoding='base58'): + """ + Encode the public key + + Args: + encoding(str): {'bytes'|'hex'|'base16'|'base32'|'base58'|'base64'}. Encoding in which the public + key should be returned. Defaults to 'base58'. + + Returns: + The public key encoded with `encoding`. + """ return super().encode(encoder=_get_nacl_encoder(encoding)) def ed25519_generate_key_pair(): """ - Generate a new key pair and return the pair encoded in base58 + Generate a new key pair. + + Returns: + A tuple of (private_key, public_key) encoded in base58. """ sk = nacl.signing.SigningKey.generate() # Private key From a5b7cee91ee909e7a13a0c28f9972072243eef00 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Thu, 29 Sep 2016 14:38:49 +0200 Subject: [PATCH 4/5] Expose generate classmethod --- cryptoconditions/crypto.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cryptoconditions/crypto.py b/cryptoconditions/crypto.py index c35f245..80d1f4f 100644 --- a/cryptoconditions/crypto.py +++ b/cryptoconditions/crypto.py @@ -89,6 +89,10 @@ def encode(self, encoding='base58'): """ return super().encode(encoder=_get_nacl_encoder(encoding)) + @classmethod + def generate(cls): + return cls(nacl.signing.SigningKey.generate().encode(encoder=Base58Encoder)) + class Ed25519VerifyingKey(nacl.signing.VerifyKey): @@ -146,12 +150,12 @@ def ed25519_generate_key_pair(): Returns: A tuple of (private_key, public_key) encoded in base58. """ - sk = nacl.signing.SigningKey.generate() + sk = Ed25519SigningKey.generate() # Private key - private_value_base58 = sk.encode(encoder=Base58Encoder) + private_value_base58 = sk.encode(encoding='base58') # Public key - public_value_compressed_base58 = sk.verify_key.encode(encoder=Base58Encoder) + public_value_compressed_base58 = sk.get_verifying_key().encode(encoding='base58') return private_value_base58, public_value_compressed_base58 From 994f88e72995e74e30f627381f326a944c7f1e86 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Fri, 30 Sep 2016 15:29:01 +0200 Subject: [PATCH 5/5] fixed docstring --- cryptoconditions/types/ed25519.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cryptoconditions/types/ed25519.py b/cryptoconditions/types/ed25519.py index 60d7b66..b0a5148 100644 --- a/cryptoconditions/types/ed25519.py +++ b/cryptoconditions/types/ed25519.py @@ -53,11 +53,11 @@ def sign(self, message, private_key): prefix and suffix and create a signature using the provided Ed25519 private key. Args: - message (string): message to be signed - private_key (string) Ed25519 private key + message (bytes): message to be signed + private_key (:obj:`Ed25519SigningKey`) Ed25519 private key """ sk = private_key - vk = VerifyingKey(base58.b58encode(sk.get_verifying_key().encode(encoding='bytes'))) + vk = sk.get_verifying_key() self.public_key = vk