Skip to content

Commit

Permalink
Merge pull request #28 from bigchaindb/fix-encoding-problem
Browse files Browse the repository at this point in the history
Fix encoding problem
  • Loading branch information
Rodolphe Marques authored Oct 4, 2016
2 parents a2c8390 + 994f88e commit 1b0fc30
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 258 deletions.
288 changes: 90 additions & 198 deletions cryptoconditions/crypto.py
Original file line number Diff line number Diff line change
@@ -1,269 +1,161 @@
# 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:
"""
import nacl.signing
import nacl.encoding
import nacl.exceptions

@abstractmethod
def get_verifying_key(self):
"""
Get the associated verifying key
from cryptoconditions import exceptions

Returns:
A VerifyingKey object
"""

@abstractmethod
def to_ascii(self, prefix, encoding):
"""
Encode the external value
Args:
prefix:
encoding:
"""
class Base58Encoder(object):

@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


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:
"""
def encode(data):
return base58.b58encode(data).encode()

@staticmethod
@abstractmethod
def encode(public_value):
"""
Encode the public key to base58 represented by the internal values
Args:
public_value
"""

@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 'bytes':
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
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'.
"""
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
"""
vk = super().get_verifying_key()
return Ed25519VerifyingKey(base58.b58encode(vk.to_bytes()))

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
Ed25519VerifyingKey
"""
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)
return Ed25519VerifyingKey(self.verify_key.encode(encoder=Base58Encoder))

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
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`.
"""
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)
raw_signature = super().sign(data).signature
return _get_nacl_encoder(encoding).encode(raw_signature)

@staticmethod
def encode(private_base64):
def encode(self, encoding='base58'):
"""
Encode the base64 number private_base64 to base58
Encode the private key
Args:
private_base64:
"""
return base58.b58encode(base64.b64decode(private_base64)).encode('ascii')
encoding(str): {'bytes'|'hex'|'base16'|'base32'|'base58'|'base64'}. Encoding in which the private
key should be returned. Defaults to 'base58'.
@staticmethod
def decode(key):
Returns:
The private key encoded with `encoding`.
"""
Decode the base58 private_value to base64
return super().encode(encoder=_get_nacl_encoder(encoding))

Args:
key:
"""
return base64.b64encode(base58.b58decode(key))
@classmethod
def generate(cls):
return cls(nacl.signing.SigningKey.generate().encode(encoder=Base58Encoder))


class Ed25519VerifyingKey(ed25519.VerifyingKey, VerifyingKey):
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
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'.
"""
public_base64 = self.decode(key)
super().__init__(public_base64, encoding='base64')
super().__init__(key, encoder=_get_nacl_encoder(encoding))

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
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
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:
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, raw_signature)
except nacl.exceptions.BadSignatureError:
return False

return True

def to_ascii(self, prefix="", encoding='base58'):
def encode(self, encoding='base58'):
"""
convert external value to ascii with specified encoding
Encode the public key
Args:
prefix (str):
encoding (str): {'base58'|'base64'|'base32'|'base16'|'hex'}
encoding(str): {'bytes'|'hex'|'base16'|'base32'|'base58'|'base64'}. Encoding in which the public
key should be returned. Defaults to 'base58'.
Returns:
bytes: encoded string
The public key encoded with `encoding`.
"""
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)
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, vk = ed25519.create_keypair()
sk = Ed25519SigningKey.generate()
# Private key
private_value_base58 = Ed25519SigningKey(base58.b58encode(sk.to_bytes())).to_ascii()
private_value_base58 = sk.encode(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')

return private_value_base58, public_value_compressed_base58

Expand Down
4 changes: 4 additions & 0 deletions cryptoconditions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Loading

0 comments on commit 1b0fc30

Please sign in to comment.