1
0
mirror of https://github.com/bigchaindb/bigchaindb.git synced 2024-06-28 00:27:45 +02:00

Merge pull request #152 from bigchaindb/feat/137/crypto-ed25519-compatible-signing-scheme

Switch to ED25519 signing scheme
This commit is contained in:
Dimitri De Jonghe 2016-04-07 17:10:50 +02:00
commit 3a057755f5
11 changed files with 56 additions and 233 deletions

View File

@ -3,7 +3,7 @@ import copy
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:
@ -11,7 +11,7 @@ def e(key, default=None, conv=None):
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)

View File

@ -27,8 +27,8 @@ class Client:
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

View File

@ -2,7 +2,7 @@ from abc import ABCMeta, abstractmethod
import bigchaindb.exceptions as exceptions
from bigchaindb import util
from bigchaindb.crypto import hash_data, PublicKey
from bigchaindb import crypto
class AbstractConsensusRules(metaclass=ABCMeta):
@ -156,7 +156,7 @@ class BaseConsensusRules(AbstractConsensusRules):
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()
@ -185,7 +185,7 @@ class BaseConsensusRules(AbstractConsensusRules):
"""
# 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()

View File

@ -41,8 +41,8 @@ class Bigchain(object):
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.
"""
@ -298,7 +298,7 @@ class Bigchain(object):
# 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,
@ -419,7 +419,7 @@ class Bigchain(object):
}
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,

View File

@ -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

View File

@ -6,7 +6,7 @@ from datetime import datetime
import bigchaindb
from bigchaindb import exceptions
from bigchaindb.crypto import PrivateKey, PublicKey, hash_data
from bigchaindb import crypto
class ProcessGroup(object):
@ -109,7 +109,7 @@ 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
@ -117,7 +117,7 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
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
@ -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 = {
@ -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
@ -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()
@ -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)

View File

@ -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

View File

@ -71,7 +71,7 @@ setup(
'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',

View File

@ -19,14 +19,14 @@ CONFIG = {
'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

View File

@ -8,7 +8,7 @@ import rethinkdb as r
import bigchaindb
from bigchaindb import util
from bigchaindb import exceptions
from bigchaindb.crypto import PrivateKey, PublicKey, generate_key_pair, hash_data
from bigchaindb import crypto
from bigchaindb.voter import Voter
from bigchaindb.block import Block
@ -44,7 +44,7 @@ class TestBigchainApi(object):
'operation': 'd',
'timestamp': tx['transaction']['timestamp'],
'data': {
'hash': hash_data(util.serialize(payload)),
'hash': crypto.hash_data(util.serialize(payload)),
'payload': payload
}
}
@ -52,7 +52,7 @@ class TestBigchainApi(object):
# assert tx_hash == tx_calculated_hash
def test_transaction_signature(self, b):
sk, vk = generate_key_pair()
sk, vk = crypto.generate_key_pair()
tx = b.create_transaction(vk, 'b', 'c', 'd')
tx_signed = b.sign_transaction(tx, sk)
@ -108,7 +108,7 @@ class TestBigchainApi(object):
def test_assign_transaction_multiple_nodes(self, b, user_public_key, user_private_key):
# create 5 federation nodes
for _ in range(5):
b.federation_nodes.append(generate_key_pair()[1])
b.federation_nodes.append(crypto.generate_key_pair()[1])
# test assignee for several transactions
for _ in range(20):
@ -185,11 +185,11 @@ class TestBigchainApi(object):
def test_create_new_block(self, b):
new_block = b.create_block([])
block_hash = hash_data(util.serialize(new_block['block']))
block_hash = crypto.hash_data(util.serialize(new_block['block']))
assert new_block['block']['voters'] == [b.me]
assert new_block['block']['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(new_block['block']), new_block['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(new_block['block']), new_block['signature']) is True
assert new_block['id'] == block_hash
assert new_block['votes'] == []
@ -371,8 +371,8 @@ class TestBlockValidation(object):
}
block_data = util.serialize(block)
block_hash = hash_data(block_data)
block_signature = PrivateKey(b.me_private).sign(block_data)
block_hash = crypto.hash_data(block_data)
block_signature = crypto.SigningKey(b.me_private).sign(block_data)
block = {
'id': block_hash,
@ -408,45 +408,6 @@ class TestBlockValidation(object):
assert b.is_valid_block(block)
class TestBigchainCrypto(object):
PRIVATE_VALUE = 64328150571824492670917070117568709277186368319388887463636481841106388379832
PUBLIC_VALUE_X = 48388170575736684074633245566225141536152842355597159440179742847497614196929
PUBLIC_VALUE_Y = 65233479152484407841598798165960909560839872511163322973341535484598825150846
PRIVATE_VALUE_B58 = 'AaAp4xBavbe6VGeQF2mWdSKNM1r6HfR2Z1tAY6aUkwdq'
PUBLIC_VALUE_COMPRESSED_B58 = 'ifEi3UuTDT4CqUUKiS5omgeDodhu2aRFHVp6LoahbEVe'
def test_private_key_encode(self):
private_value_base58 = PrivateKey.encode(self.PRIVATE_VALUE)
assert private_value_base58 == self.PRIVATE_VALUE_B58
def test_private_key_decode(self):
private_value = PrivateKey.decode(self.PRIVATE_VALUE_B58)
assert private_value == self.PRIVATE_VALUE
def test_public_key_encode(self):
public_value_compressed_base58 = PublicKey.encode(self.PUBLIC_VALUE_X, self.PUBLIC_VALUE_Y)
assert public_value_compressed_base58 == self.PUBLIC_VALUE_COMPRESSED_B58
def test_public_key_decode(self):
public_value_x, public_value_y = PublicKey.decode(self.PUBLIC_VALUE_COMPRESSED_B58)
assert public_value_x == self.PUBLIC_VALUE_X
assert public_value_y == self.PUBLIC_VALUE_Y
def test_sign_verify(self):
message = 'Hello World!'
public_key = PublicKey(self.PUBLIC_VALUE_COMPRESSED_B58)
private_key = PrivateKey(self.PRIVATE_VALUE_B58)
assert public_key.verify(message, private_key.sign(message)) is True
def test_generate_key_pair(self):
private_value_base58, public_value_compressed_base58 = generate_key_pair()
assert PrivateKey.encode(
PrivateKey.decode(private_value_base58)) == private_value_base58
assert PublicKey.encode(
*PublicKey.decode(public_value_compressed_base58)) == public_value_compressed_base58
class TestBigchainVoter(object):
def test_valid_block_voting(self, b):
@ -483,7 +444,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is True
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_invalid_block_voting(self, b, user_public_key):
# create queue and voter
@ -524,7 +485,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is False
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_vote_creation_valid(self, b):
# create valid block
@ -538,7 +499,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is True
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_vote_creation_invalid(self, b):
# create valid block
@ -552,7 +513,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is False
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
class TestBigchainBlock(object):

View File

@ -6,7 +6,7 @@ import multiprocessing as mp
from bigchaindb import util
from bigchaindb.voter import Voter, BlockStream
from bigchaindb.crypto import PublicKey, generate_key_pair
from bigchaindb import crypto
class TestBigchainVoter(object):
@ -45,7 +45,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is True
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_valid_block_voting_with_create_transaction(self, b):
q_new_block = mp.Queue()
@ -53,7 +53,7 @@ class TestBigchainVoter(object):
genesis = b.create_genesis_block()
# create a `CREATE` transaction
test_user_priv, test_user_pub = generate_key_pair()
test_user_priv, test_user_pub = crypto.generate_key_pair()
tx = b.create_transaction(b.me, test_user_pub, None, 'CREATE')
tx_signed = b.sign_transaction(tx, b.me_private)
assert b.is_valid_transaction(tx_signed)
@ -87,7 +87,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is True
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_valid_block_voting_with_transfer_transactions(self, b):
q_new_block = mp.Queue()
@ -95,7 +95,7 @@ class TestBigchainVoter(object):
b.create_genesis_block()
# create a `CREATE` transaction
test_user_priv, test_user_pub = generate_key_pair()
test_user_priv, test_user_pub = crypto.generate_key_pair()
tx = b.create_transaction(b.me, test_user_pub, None, 'CREATE')
tx_signed = b.sign_transaction(tx, b.me_private)
assert b.is_valid_transaction(tx_signed)
@ -124,7 +124,7 @@ class TestBigchainVoter(object):
assert len(blocks[1]['votes']) == 1
# create a `TRANSFER` transaction
test_user2_priv, test_user2_pub = generate_key_pair()
test_user2_priv, test_user2_pub = crypto.generate_key_pair()
tx2 = b.create_transaction(test_user_pub, test_user2_pub, tx['id'], 'TRANSFER')
tx2_signed = b.sign_transaction(tx2, test_user_priv)
assert b.is_valid_transaction(tx2_signed)
@ -158,7 +158,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is True
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_invalid_block_voting(self, b, user_public_key):
# create queue and voter
@ -197,7 +197,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is False
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_vote_creation_valid(self, b):
# create valid block
@ -211,7 +211,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is True
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_vote_creation_invalid(self, b):
# create valid block
@ -225,7 +225,7 @@ class TestBigchainVoter(object):
assert vote['vote']['is_block_valid'] is False
assert vote['vote']['invalid_reason'] is None
assert vote['node_pubkey'] == b.me
assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
assert crypto.VerifyingKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True
def test_voter_considers_unvoted_blocks_when_single_node(self, b):
# simulate a voter going donw in a single node environment
@ -301,7 +301,7 @@ class TestBlockStream(object):
def test_if_federation_size_is_greater_than_one_ignore_past_blocks(self, b):
for _ in range(5):
b.federation_nodes.append(generate_key_pair()[1])
b.federation_nodes.append(crypto.generate_key_pair()[1])
new_blocks = mp.Queue()
bs = BlockStream(new_blocks)
block_1 = b.create_block([])