mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
refactor input_condition and get_fulfillment_message
test malleability
This commit is contained in:
parent
0c9777686d
commit
7630ec7fc6
@ -330,7 +330,10 @@ def sign_tx(transaction, signing_keys):
|
|||||||
|
|
||||||
for fulfillment in tx['transaction']['fulfillments']:
|
for fulfillment in tx['transaction']['fulfillments']:
|
||||||
fulfillment_message = get_fulfillment_message(transaction, fulfillment)
|
fulfillment_message = get_fulfillment_message(transaction, fulfillment)
|
||||||
parsed_fulfillment = cc.Fulfillment.from_json(fulfillment_message['input_condition']['condition']['details'])
|
# TODO: avoid instantiation, pass as argument!
|
||||||
|
bigchain = bigchaindb.Bigchain()
|
||||||
|
input_condition = get_input_condition(bigchain, fulfillment)
|
||||||
|
parsed_fulfillment = cc.Fulfillment.from_json(input_condition['condition']['details'])
|
||||||
# for the case in which the type of fulfillment is not covered by this method
|
# for the case in which the type of fulfillment is not covered by this method
|
||||||
parsed_fulfillment_signed = parsed_fulfillment
|
parsed_fulfillment_signed = parsed_fulfillment
|
||||||
|
|
||||||
@ -436,19 +439,23 @@ def validate_fulfillments(signed_transaction):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if the signature is correct, False otherwise.
|
bool: True if the signature is correct, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for fulfillment in signed_transaction['transaction']['fulfillments']:
|
for fulfillment in signed_transaction['transaction']['fulfillments']:
|
||||||
fulfillment_message = get_fulfillment_message(signed_transaction, fulfillment)
|
fulfillment_message = get_fulfillment_message(signed_transaction, fulfillment)
|
||||||
try:
|
try:
|
||||||
parsed_fulfillment = cc.Fulfillment.from_uri(fulfillment['fulfillment'])
|
parsed_fulfillment = cc.Fulfillment.from_uri(fulfillment['fulfillment'])
|
||||||
except (TypeError, ValueError, ParsingError):
|
except (TypeError, ValueError, ParsingError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
is_valid = parsed_fulfillment.validate(serialize(fulfillment_message))
|
is_valid = parsed_fulfillment.validate(serialize(fulfillment_message))
|
||||||
|
|
||||||
# if transaction has an input (i.e. not a `CREATE` transaction)
|
# if transaction has an input (i.e. not a `CREATE` transaction)
|
||||||
if fulfillment['input']:
|
if fulfillment['input']:
|
||||||
|
# TODO: avoid instantiation, pass as argument!
|
||||||
|
bigchain = bigchaindb.Bigchain()
|
||||||
|
input_condition = get_input_condition(bigchain, fulfillment)
|
||||||
is_valid &= parsed_fulfillment.condition.serialize_uri() == \
|
is_valid &= parsed_fulfillment.condition.serialize_uri() == \
|
||||||
fulfillment_message['input_condition']['condition']['uri']
|
input_condition['condition']['uri']
|
||||||
|
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -466,8 +473,6 @@ def get_fulfillment_message(transaction, fulfillment, serialized=False):
|
|||||||
Returns:
|
Returns:
|
||||||
str|dict: fulfillment message
|
str|dict: fulfillment message
|
||||||
"""
|
"""
|
||||||
b = bigchaindb.Bigchain()
|
|
||||||
|
|
||||||
# data to sign contains common transaction data
|
# data to sign contains common transaction data
|
||||||
fulfillment_message = {
|
fulfillment_message = {
|
||||||
'operation': transaction['transaction']['operation'],
|
'operation': transaction['transaction']['operation'],
|
||||||
@ -479,28 +484,42 @@ def get_fulfillment_message(transaction, fulfillment, serialized=False):
|
|||||||
# and the condition which needs to be retrieved from the output of a previous transaction
|
# and the condition which needs to be retrieved from the output of a previous transaction
|
||||||
# or created on the fly it this is a `CREATE` transaction
|
# or created on the fly it this is a `CREATE` transaction
|
||||||
fulfillment_message.update({
|
fulfillment_message.update({
|
||||||
'input': fulfillment['input'],
|
'fulfillment': copy.deepcopy(fulfillment),
|
||||||
'input_condition': None,
|
'condition': transaction['transaction']['conditions'][fulfillment['fid']]
|
||||||
'output_condition': transaction['transaction']['conditions'][fulfillment['fid']]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# if `TRANSFER` transaction
|
# remove any fulfillment, as a fulfillment cannot sign itself
|
||||||
if fulfillment['input']:
|
fulfillment_message['fulfillment']['fulfillment'] = None
|
||||||
# get previous condition
|
|
||||||
previous_tx = b.get_transaction(fulfillment['input']['txid'])
|
|
||||||
conditions = sorted(previous_tx['transaction']['conditions'], key=lambda d: d['cid'])
|
|
||||||
fulfillment_message['input_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(cc.Ed25519Fulfillment(public_key=current_owner).serialize_json())
|
|
||||||
fulfillment_message['input_condition'] = {'condition': {'details': condition}}
|
|
||||||
if serialized:
|
if serialized:
|
||||||
return serialize(fulfillment_message)
|
return serialize(fulfillment_message)
|
||||||
return fulfillment_message
|
return fulfillment_message
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_condition(bigchain, fulfillment):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bigchain:
|
||||||
|
fulfillment:
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
input_tx = fulfillment['input']
|
||||||
|
# if `TRANSFER` transaction
|
||||||
|
if input_tx:
|
||||||
|
# get previous condition
|
||||||
|
previous_tx = bigchain.get_transaction(input_tx['txid'])
|
||||||
|
conditions = sorted(previous_tx['transaction']['conditions'], key=lambda d: d['cid'])
|
||||||
|
return conditions[input_tx['cid']]
|
||||||
|
|
||||||
|
# if `CREATE` transaction
|
||||||
|
# there is no previous transaction so we need to create one on the fly
|
||||||
|
else:
|
||||||
|
current_owner = fulfillment['current_owners'][0]
|
||||||
|
condition = json.loads(cc.Ed25519Fulfillment(public_key=current_owner).serialize_json())
|
||||||
|
return {'condition': {'details': condition}}
|
||||||
|
|
||||||
|
|
||||||
def get_hash_data(transaction):
|
def get_hash_data(transaction):
|
||||||
""" Get the hashed data that (should) correspond to the `transaction['id']`
|
""" Get the hashed data that (should) correspond to the `transaction['id']`
|
||||||
|
|
||||||
|
@ -1208,18 +1208,18 @@ class TestFulfillmentMessage(object):
|
|||||||
original_fulfillment = tx['transaction']['fulfillments'][0]
|
original_fulfillment = tx['transaction']['fulfillments'][0]
|
||||||
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
|
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
|
||||||
|
|
||||||
assert sorted(fulfillment_message) == ['data', 'id', 'input', 'input_condition',
|
assert sorted(fulfillment_message) == \
|
||||||
'operation', 'output_condition', 'timestamp', 'version']
|
['condition', 'data', 'fulfillment', 'id', 'operation', 'timestamp', 'version']
|
||||||
|
|
||||||
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
|
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
|
||||||
assert fulfillment_message['id'] == tx['id']
|
assert fulfillment_message['id'] == tx['id']
|
||||||
assert fulfillment_message['input'] == original_fulfillment['input']
|
assert fulfillment_message['condition'] == tx['transaction']['conditions'][0]
|
||||||
assert fulfillment_message['input_condition']['condition']['details']['public_key'] == \
|
assert fulfillment_message['fulfillment']['current_owners'] == original_fulfillment['current_owners']
|
||||||
original_fulfillment['current_owners'][0]
|
assert fulfillment_message['fulfillment']['fid'] == original_fulfillment['fid']
|
||||||
|
assert fulfillment_message['fulfillment']['input'] == original_fulfillment['input']
|
||||||
assert fulfillment_message['operation'] == tx['transaction']['operation']
|
assert fulfillment_message['operation'] == tx['transaction']['operation']
|
||||||
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
|
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
|
||||||
assert fulfillment_message['version'] == tx['version']
|
assert fulfillment_message['version'] == tx['version']
|
||||||
assert fulfillment_message['output_condition'] == tx['transaction']['conditions'][0]
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
@pytest.mark.usefixtures('inputs')
|
||||||
def test_fulfillment_message_transfer(self, b, user_vk):
|
def test_fulfillment_message_transfer(self, b, user_vk):
|
||||||
@ -1231,17 +1231,18 @@ class TestFulfillmentMessage(object):
|
|||||||
original_fulfillment = tx['transaction']['fulfillments'][0]
|
original_fulfillment = tx['transaction']['fulfillments'][0]
|
||||||
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
|
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
|
||||||
|
|
||||||
assert sorted(fulfillment_message) == ['data', 'id', 'input', 'input_condition',
|
assert sorted(fulfillment_message) == \
|
||||||
'operation', 'output_condition', 'timestamp', 'version']
|
['condition', 'data', 'fulfillment', 'id', 'operation', 'timestamp', 'version']
|
||||||
|
|
||||||
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
|
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
|
||||||
assert fulfillment_message['id'] == tx['id']
|
assert fulfillment_message['id'] == tx['id']
|
||||||
assert fulfillment_message['input'] == original_fulfillment['input']
|
assert fulfillment_message['condition'] == tx['transaction']['conditions'][0]
|
||||||
assert fulfillment_message['input_condition']['new_owners'] == original_fulfillment['current_owners']
|
assert fulfillment_message['fulfillment']['current_owners'] == original_fulfillment['current_owners']
|
||||||
|
assert fulfillment_message['fulfillment']['fid'] == original_fulfillment['fid']
|
||||||
|
assert fulfillment_message['fulfillment']['input'] == original_fulfillment['input']
|
||||||
assert fulfillment_message['operation'] == tx['transaction']['operation']
|
assert fulfillment_message['operation'] == tx['transaction']['operation']
|
||||||
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
|
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
|
||||||
assert fulfillment_message['version'] == tx['version']
|
assert fulfillment_message['version'] == tx['version']
|
||||||
assert fulfillment_message['output_condition'] == tx['transaction']['conditions'][0]
|
|
||||||
|
|
||||||
def test_fulfillment_message_multiple_current_owners_multiple_new_owners_multiple_inputs(self, b, user_vk):
|
def test_fulfillment_message_multiple_current_owners_multiple_new_owners_multiple_inputs(self, b, user_vk):
|
||||||
# create a new users
|
# create a new users
|
||||||
@ -1268,18 +1269,96 @@ class TestFulfillmentMessage(object):
|
|||||||
for original_fulfillment in tx['transaction']['fulfillments']:
|
for original_fulfillment in tx['transaction']['fulfillments']:
|
||||||
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
|
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
|
||||||
|
|
||||||
assert sorted(fulfillment_message) == ['data', 'id', 'input', 'input_condition',
|
assert sorted(fulfillment_message) == \
|
||||||
'operation', 'output_condition', 'timestamp', 'version']
|
['condition', 'data', 'fulfillment', 'id', 'operation', 'timestamp', 'version']
|
||||||
|
|
||||||
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
|
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
|
||||||
assert fulfillment_message['id'] == tx['id']
|
assert fulfillment_message['id'] == tx['id']
|
||||||
assert fulfillment_message['input'] == original_fulfillment['input']
|
assert fulfillment_message['condition'] == tx['transaction']['conditions'][original_fulfillment['fid']]
|
||||||
assert fulfillment_message['input_condition']['new_owners'] == original_fulfillment['current_owners']
|
assert fulfillment_message['fulfillment']['current_owners'] == original_fulfillment['current_owners']
|
||||||
|
assert fulfillment_message['fulfillment']['fid'] == original_fulfillment['fid']
|
||||||
|
assert fulfillment_message['fulfillment']['input'] == original_fulfillment['input']
|
||||||
assert fulfillment_message['operation'] == tx['transaction']['operation']
|
assert fulfillment_message['operation'] == tx['transaction']['operation']
|
||||||
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
|
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
|
||||||
assert fulfillment_message['version'] == tx['version']
|
assert fulfillment_message['version'] == tx['version']
|
||||||
assert fulfillment_message['output_condition'] == \
|
|
||||||
tx['transaction']['conditions'][original_fulfillment['fid']]
|
|
||||||
|
class TestTransactionMalleability(object):
|
||||||
|
@pytest.mark.usefixtures('inputs')
|
||||||
|
def test_create_transaction_transfer(self, b, user_vk, user_sk):
|
||||||
|
input_tx = b.get_owned_ids(user_vk).pop()
|
||||||
|
assert b.validate_fulfillments(b.get_transaction(input_tx['txid'])) is True
|
||||||
|
|
||||||
|
tx = b.create_transaction(user_vk, b.me, input_tx, 'TRANSFER')
|
||||||
|
|
||||||
|
tx_signed = b.sign_transaction(tx, user_sk)
|
||||||
|
|
||||||
|
assert b.validate_fulfillments(tx_signed) is True
|
||||||
|
assert b.is_valid_transaction(tx_signed) == tx_signed
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['id'] = 'dsdasd'
|
||||||
|
assert b.validate_fulfillments(tx_changed) is False
|
||||||
|
assert b.is_valid_transaction(tx_changed) is False
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['version'] = '0'
|
||||||
|
assert b.validate_fulfillments(tx_changed) is False
|
||||||
|
assert b.is_valid_transaction(tx_changed) is False
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['transaction']['operation'] = 'CREATE'
|
||||||
|
assert b.validate_fulfillments(tx_changed) is False
|
||||||
|
assert b.is_valid_transaction(tx_changed) is False
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['transaction']['timestamp'] = '1463033192.123456'
|
||||||
|
assert b.validate_fulfillments(tx_changed) is False
|
||||||
|
assert b.is_valid_transaction(tx_changed) is False
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['transaction']['data'] = {
|
||||||
|
"hash": "872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8",
|
||||||
|
"payload": {
|
||||||
|
"msg": "Hello BigchainDB!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert b.validate_fulfillments(tx_changed) == False
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['transaction']['fulfillments'] = [
|
||||||
|
{
|
||||||
|
"current_owners": [
|
||||||
|
"AFbofwJYEB7Cx2fgrPrCJzbdDVRzRKysoGXt4DsvuTGN"
|
||||||
|
],
|
||||||
|
"fid": 0,
|
||||||
|
"fulfillment": "cf:4:iXaq3UbandDj4DgBhFDcfHjkm2639RwgLmwAHUmuDFMfMEKMZ71eQw2qCMK951kBaNNJel_FCDuYnacn_MsWzYXOUJs6DGW3lYfXI_d55xuqpH2BenvRWKNp98tRRr4B",
|
||||||
|
"input": None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
assert b.validate_fulfillments(tx_changed) is False
|
||||||
|
assert b.is_valid_transaction(tx_changed) is False
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['transaction']['fulfillments'][0]['fid'] = 1
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
assert b.validate_fulfillments(tx_changed) is False
|
||||||
|
assert b.is_valid_transaction(tx_changed) is False
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['transaction']['fulfillments'][0]['current_owners'] = [
|
||||||
|
"AFbofwJYEB7Cx2fgrPrCJzbdDVRzRKysoGXt4DsvuTGN"]
|
||||||
|
assert b.validate_fulfillments(tx_changed) is False
|
||||||
|
assert b.is_valid_transaction(tx_changed) is False
|
||||||
|
|
||||||
|
tx_changed = copy.deepcopy(tx_signed)
|
||||||
|
tx_changed['transaction']['fulfillments'][0]['input'] = {
|
||||||
|
"cid": 0,
|
||||||
|
"txid": "3055348675fc6f23b75f13c55db6d112b66eee068e99d30a802883d3b1784203"
|
||||||
|
}
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
assert b.validate_fulfillments(tx_changed) is False
|
||||||
|
assert b.is_valid_transaction(tx_changed) is False
|
||||||
|
|
||||||
|
|
||||||
class TestCryptoconditions(object):
|
class TestCryptoconditions(object):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user