From a4bbffa544eeeb1b9bcfe98ab7355c6d9a58a693 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Thu, 14 Apr 2016 16:13:57 +0200 Subject: [PATCH] Refactor sign_tx. Fix tests The implicit condition of create transactions is now handled by `get_fulfillment_message` instead of `sign_tx` --- bigchaindb/exceptions.py | 3 ++- bigchaindb/util.py | 42 +++++++++++++++++++++-------------- tests/db/test_bigchain_api.py | 27 +++++++++++----------- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/bigchaindb/exceptions.py b/bigchaindb/exceptions.py index 0baa4ad2..f0de0bc5 100644 --- a/bigchaindb/exceptions.py +++ b/bigchaindb/exceptions.py @@ -28,4 +28,5 @@ class DatabaseDoesNotExist(Exception): class KeypairNotFoundException(Exception): """Raised if operation cannot proceed because the keypair was not given""" - +class KeypairMismatchException(Exception): + """Raised if the private key(s) provided for signing don't match any of the curret owner(s)""" diff --git a/bigchaindb/util.py b/bigchaindb/util.py index 1a2b62a4..9ad285f9 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -248,36 +248,38 @@ def sign_tx(transaction, sks): if not isinstance(sks, list): sks = [sks] - if len(sks) == 1: - sk = crypto.SigningKey(sks[0]) - else: - # create a mapping between sk and vk so that we can match the private key to the current_owners - key_pairs = {} - for sk in sks: - signing_key = crypto.SigningKey(sk) - vk = signing_key.get_verifying_key().to_ascii().decode() - key_pairs[vk] = signing_key + # create a mapping between sk and vk so that we can match the private key to the current_owners + key_pairs = {} + for sk in sks: + signing_key = crypto.SigningKey(sk) + vk = signing_key.get_verifying_key().to_ascii().decode() + key_pairs[vk] = signing_key tx = copy.deepcopy(transaction) for fulfillment in tx['transaction']['fulfillments']: fulfillment_message = get_fulfillment_message(transaction, fulfillment) - if tx['transaction']['operation'] in ['CREATE', 'GENESIS']: - # sign the fulfillment message - parsed_fulfillment = Ed25519Fulfillment(public_key=sk.get_verifying_key()) - else: - parsed_fulfillment = Fulfillment.from_json(fulfillment_message['condition']['condition']['details']) + parsed_fulfillment = Fulfillment.from_json(fulfillment_message['condition']['condition']['details']) # single current owner if isinstance(parsed_fulfillment, Ed25519Fulfillment): - parsed_fulfillment.sign(serialize(fulfillment_message), sk) + current_owner = fulfillment['current_owners'][0] + try: + parsed_fulfillment.sign(serialize(fulfillment_message), key_pairs[current_owner]) + except KeyError: + raise exceptions.KeypairMismatchException('Public key {} is not a pair to any of the private keys' + .format(current_owner)) # multiple current owners elif isinstance(parsed_fulfillment, ThresholdSha256Fulfillment): # replace the fulfillments with the signed fulfillments parsed_fulfillment.subconditions = [] for current_owner in fulfillment['current_owners']: subfulfillment = get_subcondition_from_vk(fulfillment_message['condition'], current_owner) - subfulfillment.sign(serialize(fulfillment_message), key_pairs[current_owner]) + try: + subfulfillment.sign(serialize(fulfillment_message), key_pairs[current_owner]) + except KeyError: + raise exceptions.KeypairMismatchException('Public key {} is not a pair to any of the private keys' + .format(current_owner)) parsed_fulfillment.add_subfulfillment(subfulfillment) signed_fulfillment = parsed_fulfillment.serialize_uri() @@ -351,12 +353,18 @@ def get_fulfillment_message(transaction, fulfillment): 'condition': None, }) - # if not a `CREATE` transaction + # if `TRANSFER` transaction if fulfillment['input']: # get previous condition previous_tx = b.get_transaction(fulfillment['input']['txid']) conditions = sorted(previous_tx['transaction']['conditions'], key=lambda d: d['cid']) fulfillment_message['condition'] = conditions[fulfillment['input']['cid']] + # if `CREATE` transaction + # there is no previous transaction so we need to create one on the fly + else: + current_owner = transaction['transaction']['fulfillments'][0]['current_owners'][0] + condition = json.loads(Ed25519Fulfillment(public_key=current_owner).serialize_json()) + fulfillment_message['condition'] = {'condition': {'details': condition}} return fulfillment_message diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 8d9c3988..9c6b84f9 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -41,7 +41,7 @@ class TestBigchainApi(object): input_tx = b.get_owned_ids(user_vk).pop() assert b.verify_signature(b.get_transaction(input_tx['txid'])) == True - tx = b.create_transaction(b.me, user_sk, input_tx, 'TRANSFER') + tx = b.create_transaction(user_vk, b.me, input_tx, 'TRANSFER') assert sorted(tx) == sorted(['id', 'transaction', 'version']) assert sorted(tx['transaction']) == sorted(['conditions', 'data', 'fulfillments', 'operation', 'timestamp']) @@ -260,14 +260,6 @@ class TestTransactionValidation(object): assert excinfo.value.args[0] == 'Only federation nodes can use the operation `CREATE`' assert b.is_valid_transaction(tx) is False - tx_signed = b.sign_transaction(tx, b.me_private) - - with pytest.raises(exceptions.OperationError) as excinfo: - b.validate_transaction(tx_signed) - - assert excinfo.value.args[0] == 'Only federation nodes can use the operation `CREATE`' - assert b.is_valid_transaction(tx_signed) is False - def test_non_create_operation_no_inputs(self, b, user_vk): tx = b.create_transaction(user_vk, user_vk, None, 'TRANSFER') with pytest.raises(ValueError) as excinfo: @@ -326,16 +318,25 @@ class TestTransactionValidation(object): assert b.is_valid_transaction(tx_valid) is False @pytest.mark.usefixtures('inputs') - def test_wrong_signature(self, b, user_vk): + def test_wrong_signature(self, b, user_sk, user_vk): input_valid = b.get_owned_ids(user_vk).pop() tx_valid = b.create_transaction(user_vk, user_vk, input_valid, 'TRANSFER') wrong_private_key = '4fyvJe1aw2qHZ4UNRYftXK7JU7zy9bCqoU5ps6Ne3xrY' - tx_invalid_signed = b.sign_transaction(tx_valid, wrong_private_key) + with pytest.raises(exceptions.KeypairMismatchException): + tx_invalid_signed = b.sign_transaction(tx_valid, wrong_private_key) + + # create a correctly signed transaction and change the signature + tx_signed = b.sign_transaction(tx_valid, user_sk) + fulfillment = tx_signed['transaction']['fulfillments'][0]['fulfillment'] + changed_fulfillment = Ed25519Fulfillment().from_uri(fulfillment) + changed_fulfillment.signature = b'0' * 64 + tx_signed['transaction']['fulfillments'][0]['fulfillment'] = changed_fulfillment.serialize_uri() + with pytest.raises(exceptions.InvalidSignature): - b.validate_transaction(tx_invalid_signed) - assert b.is_valid_transaction(tx_invalid_signed) is False + b.validate_transaction(tx_signed) + assert b.is_valid_transaction(tx_signed) is False def test_valid_create_transaction(self, b, user_vk): tx = b.create_transaction(b.me, user_vk, None, 'CREATE')