Skip to content

Commit

Permalink
Merge pull request #152 from bigchaindb/feat/137/crypto-ed25519-compa…
Browse files Browse the repository at this point in the history
…tible-signing-scheme

Switch to ED25519 signing scheme
  • Loading branch information
diminator committed Apr 7, 2016
2 parents aebe179 + 12ce460 commit 3a05775
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 233 deletions.
4 changes: 2 additions & 2 deletions bigchaindb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@


def e(key, default=None, conv=None):
'''Get the environment variable `key`, fallback to `default`
"""Get the environment variable `key`, fallback to `default`
if nothing is found.
Keyword arguments:
key -- the key to look for in the environment
default -- the default value if nothing is found (default: None)
conv -- a callable used to convert the value (default: use the type of the
default value)
'''
"""

val = os.environ.get(key, default)

Expand Down
4 changes: 2 additions & 2 deletions bigchaindb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def __init__(self, public_key=None, private_key=None, api_endpoint=None,
3. Reading them from the `config.json` file.
Args:
public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve.
private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve.
public_key (str): the base58 encoded public key for the ED25519 curve.
private_key (str): the base58 encoded private key for the ED25519 curve.
api_endpoint (str): a URL where rethinkdb is running.
format: scheme://hostname:port
consensus_plugin (str): the registered name of your installed
Expand Down
6 changes: 3 additions & 3 deletions bigchaindb/consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import bigchaindb.exceptions as exceptions
from bigchaindb import util
from bigchaindb.crypto import hash_data, PublicKey
from bigchaindb import crypto


class AbstractConsensusRules(metaclass=ABCMeta):
Expand Down Expand Up @@ -156,7 +156,7 @@ def validate_transaction(bigchain, transaction):
transaction['transaction']['input']))

# Check hash of the transaction
calculated_hash = hash_data(util.serialize(
calculated_hash = crypto.hash_data(util.serialize(
transaction['transaction']))
if calculated_hash != transaction['id']:
raise exceptions.InvalidHash()
Expand Down Expand Up @@ -185,7 +185,7 @@ def validate_block(bigchain, block):
"""

# Check if current hash is correct
calculated_hash = hash_data(util.serialize(block['block']))
calculated_hash = crypto.hash_data(util.serialize(block['block']))
if calculated_hash != block['id']:
raise exceptions.InvalidHash()

Expand Down
8 changes: 4 additions & 4 deletions bigchaindb/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def __init__(self, host=None, port=None, dbname=None,
host (str): hostname where the rethinkdb is running.
port (int): port in which rethinkb is running (usually 28015).
dbname (str): the name of the database to connect to (usually bigchain).
public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve.
private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve.
public_key (str): the base58 encoded public key for the ED25519 curve.
private_key (str): the base58 encoded private key for the ED25519 curve.
keyring (list[str]): list of base58 encoded public keys of the federation nodes.
"""

Expand Down Expand Up @@ -298,7 +298,7 @@ def create_block(self, validated_transactions):
# Calculate the hash of the new block
block_data = util.serialize(block)
block_hash = crypto.hash_data(block_data)
block_signature = crypto.PrivateKey(self.me_private).sign(block_data)
block_signature = crypto.SigningKey(self.me_private).sign(block_data).decode()

block = {
'id': block_hash,
Expand Down Expand Up @@ -419,7 +419,7 @@ def vote(self, block, previous_block_id, decision, invalid_reason=None):
}

vote_data = util.serialize(vote)
signature = crypto.PrivateKey(self.me_private).sign(vote_data)
signature = crypto.SigningKey(self.me_private).sign(vote_data).decode()

vote_signed = {
'node_pubkey': self.me,
Expand Down
152 changes: 7 additions & 145 deletions bigchaindb/crypto.py
Original file line number Diff line number Diff line change
@@ -1,155 +1,17 @@
# Separate all crypto code so that we can easily test several implementations

import binascii
import base58

import sha3
import bitcoin

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature


class PrivateKey(object):
"""
PrivateKey instance
"""

def __init__(self, key):
"""
Instantiate the private key with the private_value encoded in base58
"""
private_value = self.decode(key)
private_numbers = self._private_value_to_cryptography_private_numbers(private_value)
self.private_key = self._cryptography_private_key_from_private_numbers(private_numbers)

def sign(self, data):
"""
Sign data with private key
"""
signer = self.private_key.signer(ec.ECDSA(hashes.SHA256()))
signer.update(data.encode('utf-8'))
signature = signer.finalize()
return binascii.hexlify(signature).decode('utf-8')


@staticmethod
def encode(private_value):
"""
Encode the decimal number private_value to base58
"""
private_value_hex = bitcoin.encode_privkey(private_value, 'hex')
private_value_base58 = base58.b58encode(bytes.fromhex(private_value_hex))
return private_value_base58

@staticmethod
def decode(key):
"""
Decode the base58 private_value to decimale
"""
private_value_hex = binascii.hexlify(base58.b58decode(key))
private_value = bitcoin.decode_privkey(private_value_hex)
return private_value

def _private_value_to_public_values(self, private_value):
"""
Return the public values from the private value
"""
public_value_x, public_value_y = bitcoin.privkey_to_pubkey(private_value)
return (public_value_x, public_value_y)

def _private_value_to_cryptography_private_numbers(self, private_value):
"""
Return an instance of cryptography PrivateNumbers from the decimal private_value
"""
public_value_x, public_value_y = self._private_value_to_public_values(private_value)
public_numbers = PublicKey._public_values_to_cryptography_public_numbers(public_value_x, public_value_y)
private_numbers = ec.EllipticCurvePrivateNumbers(private_value, public_numbers)
return private_numbers

@staticmethod
def _cryptography_private_key_from_private_numbers(private_numbers):
"""
Return an instace of cryptography PrivateKey from a cryptography instance of PrivateNumbers
"""
return private_numbers.private_key(default_backend())


class PublicKey(object):

def __init__(self, key):
"""
Instantiate the public key with the compressed public value encoded in base58
"""
public_value_x, public_value_y = self.decode(key)
public_numbers = self._public_values_to_cryptography_public_numbers(public_value_x, public_value_y)
self.public_key = self._criptography_public_key_from_public_numbers(public_numbers)

def verify(self, data, signature):
verifier = self.public_key.verifier(binascii.unhexlify(signature), ec.ECDSA(hashes.SHA256()))
verifier.update(data.encode('utf-8'))
try:
verifier.verify()
except InvalidSignature:
return False

return True

@staticmethod
def encode(public_value_x, public_value_y):
"""
Encode the public key represented by the decimal values x and y to base58
"""
public_value_compressed_hex = bitcoin.encode_pubkey([public_value_x, public_value_y], 'hex_compressed')
public_value_compressed_base58 = base58.b58encode(bytes.fromhex(public_value_compressed_hex))
return public_value_compressed_base58

@staticmethod
def decode(public_value_compressed_base58):
"""
Decode the base58 public_value to the decimal x and y values
"""
public_value_compressed_hex = binascii.hexlify(base58.b58decode(public_value_compressed_base58))
public_value_x, public_value_y = bitcoin.decode_pubkey(public_value_compressed_hex.decode())
return (public_value_x, public_value_y)

@staticmethod
def _public_values_to_cryptography_public_numbers(public_value_x, public_value_y):
"""
Return an instance of cryptography PublicNumbers from the decimal x and y values
"""
public_numbers = ec.EllipticCurvePublicNumbers(public_value_x, public_value_y, ec.SECP256K1())
return public_numbers

def _criptography_public_key_from_public_numbers(self, public_numbers):
"""
Return an instance of cryptography PublicKey from a cryptography instance of PublicNumbers
"""
return public_numbers.public_key(default_backend())


def generate_key_pair():
"""
Generate a new key pair and return the pair encoded in base58
"""
# Private key
private_key = ec.generate_private_key(ec.SECP256K1, default_backend())
private_value = private_key.private_numbers().private_value
private_value_base58 = PrivateKey.encode(private_value)

# Public key
public_key = private_key.public_key()
public_value_x, public_value_y = public_key.public_numbers().x, public_key.public_numbers().y
public_value_compressed_base58 = PublicKey.encode(public_value_x, public_value_y)

return (private_value_base58, public_value_compressed_base58)
from cryptoconditions import ed25519


def hash_data(data):
"""Hash the provided data using SHA3-256"""

return sha3.sha3_256(data.encode()).hexdigest()


def generate_key_pair():
sk, pk = ed25519.ed25519_generate_key_pair()
return sk.decode(), pk.decode()

SigningKey = ed25519.Ed25519SigningKey
VerifyingKey = ed25519.Ed25519VerifyingKey
16 changes: 8 additions & 8 deletions bigchaindb/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import bigchaindb
from bigchaindb import exceptions
from bigchaindb.crypto import PrivateKey, PublicKey, hash_data
from bigchaindb import crypto


class ProcessGroup(object):
Expand Down Expand Up @@ -109,15 +109,15 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
data = None
if payload is not None:
if isinstance(payload, dict):
hash_payload = hash_data(serialize(payload))
hash_payload = crypto.hash_data(serialize(payload))
data = {
'hash': hash_payload,
'payload': payload
}
else:
raise TypeError('`payload` must be an dict instance')

hash_payload = hash_data(serialize(payload))
hash_payload = crypto.hash_data(serialize(payload))
data = {
'hash': hash_payload,
'payload': payload
Expand All @@ -134,7 +134,7 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):

# serialize and convert to bytes
tx_serialized = serialize(tx)
tx_hash = hash_data(tx_serialized)
tx_hash = crypto.hash_data(tx_serialized)

# create the transaction
transaction = {
Expand All @@ -158,10 +158,10 @@ def sign_tx(transaction, private_key):
dict: transaction with the `signature` field included.
"""
private_key = PrivateKey(private_key)
private_key = crypto.SigningKey(private_key)
signature = private_key.sign(serialize(transaction))
signed_transaction = transaction.copy()
signed_transaction.update({'signature': signature})
signed_transaction.update({'signature': signature.decode()})
return signed_transaction


Expand All @@ -172,7 +172,7 @@ def create_and_sign_tx(private_key, current_owner, new_owner, tx_input, operatio

def check_hash_and_signature(transaction):
# Check hash of the transaction
calculated_hash = hash_data(serialize(transaction['transaction']))
calculated_hash = crypto.hash_data(serialize(transaction['transaction']))
if calculated_hash != transaction['id']:
raise exceptions.InvalidHash()

Expand Down Expand Up @@ -201,7 +201,7 @@ def verify_signature(signed_transaction):

signature = data.pop('signature')
public_key_base58 = signed_transaction['transaction']['current_owner']
public_key = PublicKey(public_key_base58)
public_key = crypto.VerifyingKey(public_key_base58)
return public_key.verify(serialize(data), signature)


Expand Down
4 changes: 2 additions & 2 deletions docs/source/cryptography.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ tx_hash = hashlib.sha3_256(data).hexdigest()

## Signature algorithm and keys

The signature algorithm used by BigchainDB is ECDSA with the secp256k1 curve
using the python [cryptography](https://cryptography.io/en/latest/) module.
The signature algorithm used by BigchainDB is [ED25519](https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-04)
using the python [ed25519](https://github.com/warner/python-ed25519) module, overloaded by the [cryptoconditions library](https://github.com/bigchaindb/cryptoconditions).

The private key is the base58 encoded hexadecimal representation of private number.
The public key is the base58 encoded hexadecimal representation of the
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
'rethinkdb==2.2.0.post4',
'pysha3==0.3',
'pytz==2015.7',
'cryptography==1.2.3',
'cryptoconditions==0.1.1',
'statsd==3.2.1',
'python-rapidjson==0.0.6',
'logstats==0.2.1',
Expand Down
8 changes: 4 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
'name': DB_NAME
},
'keypair': {
'private': '3i2FDXp87N9ExXSvWxqBAw9EgzoxxGTQNKbtxmWBpTyL',
'public': '29Tw3ozmSRtN8XNofvsu5RdoQRk9gAonfpkFvRZDmhTPo'
'private': '31Lb1ZGKTyHnmVK3LUMrAUrPNfd4sE2YyBt3UA4A25aA',
'public': '4XYfCbabAWVUCbjTmRTFEu2sc3dFEdkse4r6X498B1s8'
}
}

# Test user. inputs will be created for this user. Cryptography Keys
USER_PRIVATE_KEY = 'GmRZxQdQv7tooMijXytQkexKuFN6mJocciJarAmMwTX2'
USER_PUBLIC_KEY = 'r3cEu8GNoz8rYpNJ61k7GqfR8VEvdUbtyHce8u1kaYwh'
USER_PRIVATE_KEY = '8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie'
USER_PUBLIC_KEY = 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE'


@pytest.fixture
Expand Down
Loading

0 comments on commit 3a05775

Please sign in to comment.