mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
sign whole transaction body instead of partial transaction
This commit is contained in:
parent
4a5a5566e7
commit
bae05e80a0
@ -686,22 +686,17 @@ class Transaction(object):
|
||||
key_pairs = {gen_public_key(PrivateKey(private_key)):
|
||||
PrivateKey(private_key) for private_key in private_keys}
|
||||
|
||||
for index, input_ in enumerate(self.inputs):
|
||||
# NOTE: We clone the current transaction but only add the output
|
||||
# and input we're currently working on plus all
|
||||
# previously signed ones.
|
||||
tx_partial = Transaction(self.operation, self.asset, [input_],
|
||||
self.outputs, self.metadata,
|
||||
self.version)
|
||||
|
||||
tx_partial_dict = tx_partial.to_dict()
|
||||
tx_partial_dict = Transaction._remove_signatures(tx_partial_dict)
|
||||
tx_serialized = Transaction._to_str(tx_partial_dict)
|
||||
self._sign_input(input_, index, tx_serialized, key_pairs)
|
||||
tx_dict = self.to_dict()
|
||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||
tx_serialized = Transaction._to_str(tx_dict)
|
||||
for i, input_ in enumerate(self.inputs):
|
||||
message = '%s:%s' % (i, tx_serialized)
|
||||
self.inputs[i] = self._sign_input(input_, message, key_pairs)
|
||||
return self
|
||||
|
||||
def _sign_input(self, input_, index, tx_serialized, key_pairs):
|
||||
"""Signs a single Input with a partial Transaction as message.
|
||||
@classmethod
|
||||
def _sign_input(cls, input_, message, key_pairs):
|
||||
"""Signs a single Input.
|
||||
|
||||
Note:
|
||||
This method works only for the following Cryptoconditions
|
||||
@ -712,31 +707,27 @@ class Transaction(object):
|
||||
Args:
|
||||
input_ (:class:`~bigchaindb.common.transaction.
|
||||
Input`) The Input to be signed.
|
||||
index (int): The index of the input to be signed.
|
||||
tx_serialized (str): The Transaction to be used as message.
|
||||
message (str): The message to be signed
|
||||
key_pairs (dict): The keys to sign the Transaction with.
|
||||
"""
|
||||
if isinstance(input_.fulfillment, Ed25519Fulfillment):
|
||||
self._sign_simple_signature_fulfillment(input_, index,
|
||||
tx_serialized, key_pairs)
|
||||
return cls._sign_simple_signature_fulfillment(input_, message,
|
||||
key_pairs)
|
||||
elif isinstance(input_.fulfillment, ThresholdSha256Fulfillment):
|
||||
self._sign_threshold_signature_fulfillment(input_, index,
|
||||
tx_serialized,
|
||||
key_pairs)
|
||||
return cls._sign_threshold_signature_fulfillment(input_, message,
|
||||
key_pairs)
|
||||
else:
|
||||
raise ValueError("Fulfillment couldn't be matched to "
|
||||
'Cryptocondition fulfillment type.')
|
||||
|
||||
def _sign_simple_signature_fulfillment(self, input_, index,
|
||||
tx_serialized, key_pairs):
|
||||
@classmethod
|
||||
def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs):
|
||||
"""Signs a Ed25519Fulfillment.
|
||||
|
||||
Args:
|
||||
input_ (:class:`~bigchaindb.common.transaction.
|
||||
Input`) The input to be signed.
|
||||
index (int): The index of the input to be
|
||||
signed.
|
||||
tx_serialized (str): The Transaction to be used as message.
|
||||
message (str): The message to be signed
|
||||
key_pairs (dict): The keys to sign the Transaction with.
|
||||
"""
|
||||
# NOTE: To eliminate the dangers of accidentally signing a condition by
|
||||
@ -748,23 +739,21 @@ class Transaction(object):
|
||||
try:
|
||||
# cryptoconditions makes no assumptions of the encoding of the
|
||||
# message to sign or verify. It only accepts bytestrings
|
||||
input_.fulfillment.sign(tx_serialized.encode(), key_pairs[public_key])
|
||||
input_.fulfillment.sign(message.encode(), key_pairs[public_key])
|
||||
except KeyError:
|
||||
raise KeypairMismatchException('Public key {} is not a pair to '
|
||||
'any of the private keys'
|
||||
.format(public_key))
|
||||
self.inputs[index] = input_
|
||||
return input_
|
||||
|
||||
def _sign_threshold_signature_fulfillment(self, input_, index,
|
||||
tx_serialized, key_pairs):
|
||||
@classmethod
|
||||
def _sign_threshold_signature_fulfillment(cls, input_, message, key_pairs):
|
||||
"""Signs a ThresholdSha256Fulfillment.
|
||||
|
||||
Args:
|
||||
input_ (:class:`~bigchaindb.common.transaction.
|
||||
Input`) The Input to be signed.
|
||||
index (int): The index of the Input to be
|
||||
signed.
|
||||
tx_serialized (str): The Transaction to be used as message.
|
||||
message (str): The message to be signed
|
||||
key_pairs (dict): The keys to sign the Transaction with.
|
||||
"""
|
||||
input_ = deepcopy(input_)
|
||||
@ -794,8 +783,8 @@ class Transaction(object):
|
||||
|
||||
# cryptoconditions makes no assumptions of the encoding of the
|
||||
# message to sign or verify. It only accepts bytestrings
|
||||
subffill.sign(tx_serialized.encode(), private_key)
|
||||
self.inputs[index] = input_
|
||||
subffill.sign(message.encode(), private_key)
|
||||
return input_
|
||||
|
||||
def inputs_valid(self, outputs=None):
|
||||
"""Validates the Inputs in the Transaction against given
|
||||
@ -848,24 +837,19 @@ class Transaction(object):
|
||||
raise ValueError('Inputs and '
|
||||
'output_condition_uris must have the same count')
|
||||
|
||||
def gen_tx(input_, output, output_condition_uri=None):
|
||||
"""Splits multiple IO Transactions into partial single IO
|
||||
Transactions.
|
||||
"""
|
||||
tx = Transaction(self.operation, self.asset, [input_],
|
||||
self.outputs, self.metadata, self.version)
|
||||
tx_dict = tx.to_dict()
|
||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||
tx_serialized = Transaction._to_str(tx_dict)
|
||||
tx_dict = self.to_dict()
|
||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||
tx_serialized = Transaction._to_str(tx_dict)
|
||||
|
||||
return self.__class__._input_valid(input_,
|
||||
self.operation,
|
||||
tx_serialized,
|
||||
output_condition_uri)
|
||||
def validate(i, output_condition_uri=None):
|
||||
""" Validate input against output condition URI """
|
||||
message = '%s:%s' % (i, tx_serialized)
|
||||
|
||||
partial_transactions = map(gen_tx, self.inputs,
|
||||
self.outputs, output_condition_uris)
|
||||
return all(partial_transactions)
|
||||
return self._input_valid(self.inputs[i], self.operation, message,
|
||||
output_condition_uri)
|
||||
|
||||
return all(validate(i, cond)
|
||||
for i, cond in enumerate(output_condition_uris))
|
||||
|
||||
@staticmethod
|
||||
def _input_valid(input_, operation, tx_serialized, output_condition_uri=None):
|
||||
|
@ -49,4 +49,4 @@ Here's some explanation of the contents of a :ref:`transaction <transaction>`:
|
||||
|
||||
Later, when we get to the models for the block and the vote, we'll see that both include a signature (from the node which created it). You may wonder why transactions don't have signatures... The answer is that they do! They're just hidden inside the ``fulfillment`` string of each input. A creation transaction is signed by whoever created it. A transfer transaction is signed by whoever currently controls or owns it.
|
||||
|
||||
What gets signed? For each input in the transaction, the "fullfillment message" that gets signed includes the ``operation``, ``data``, ``version``, ``id``, corresponding ``condition``, and the fulfillment itself, except with its fulfillment string set to ``null``. The computed signature goes into creating the ``fulfillment`` string of the input.
|
||||
What gets signed? For each input in the transaction, the "fullfillment message" that gets signed includes the JSON serialized body of the transaction, minus any fulfillment strings, and with "n:" prepended where n is the index of the input being signed. The computed signature goes into creating the ``fulfillment`` string of the input.
|
||||
|
@ -496,7 +496,8 @@ def test_validate_tx_simple_create_signature(user_input, user_output, user_priv,
|
||||
|
||||
tx = Transaction(Transaction.CREATE, asset_definition, [user_input], [user_output])
|
||||
expected = deepcopy(user_output)
|
||||
expected.fulfillment.sign(str(tx).encode(), PrivateKey(user_priv))
|
||||
message = ('0:' + str(tx)).encode()
|
||||
expected.fulfillment.sign(message, PrivateKey(user_priv))
|
||||
tx.sign([user_priv])
|
||||
|
||||
assert tx.inputs[0].to_dict()['fulfillment'] == \
|
||||
@ -513,7 +514,6 @@ def test_invoke_simple_signature_fulfillment_with_invalid_params(utx,
|
||||
with raises(KeypairMismatchException):
|
||||
invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'}
|
||||
utx._sign_simple_signature_fulfillment(user_input,
|
||||
0,
|
||||
'somemessage',
|
||||
invalid_key_pair)
|
||||
|
||||
@ -524,13 +524,11 @@ def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_input,
|
||||
|
||||
with raises(KeypairMismatchException):
|
||||
utx._sign_threshold_signature_fulfillment(user_user2_threshold_input,
|
||||
0,
|
||||
'somemessage',
|
||||
{user3_pub: user3_priv})
|
||||
with raises(KeypairMismatchException):
|
||||
user_user2_threshold_input.owners_before = ['somewrongvalue']
|
||||
utx._sign_threshold_signature_fulfillment(user_user2_threshold_input,
|
||||
0,
|
||||
'somemessage',
|
||||
None)
|
||||
|
||||
@ -560,13 +558,11 @@ def test_validate_multiple_inputs(user_input, user_output, user_priv,
|
||||
|
||||
expected_first = deepcopy(tx)
|
||||
expected_second = deepcopy(tx)
|
||||
expected_first.inputs = [expected_first.inputs[0]]
|
||||
expected_second.inputs = [expected_second.inputs[1]]
|
||||
|
||||
expected_first_bytes = str(expected_first).encode()
|
||||
expected_first_bytes = ('0:' + str(tx)).encode()
|
||||
expected_first.inputs[0].fulfillment.sign(expected_first_bytes,
|
||||
PrivateKey(user_priv))
|
||||
expected_second_bytes = str(expected_second).encode()
|
||||
expected_second_bytes = ('1:' + str(tx)).encode()
|
||||
expected_second.inputs[0].fulfillment.sign(expected_second_bytes,
|
||||
PrivateKey(user_priv))
|
||||
tx.sign([user_priv])
|
||||
@ -596,10 +592,11 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_input,
|
||||
tx = Transaction(Transaction.CREATE, asset_definition,
|
||||
[user_user2_threshold_input],
|
||||
[user_user2_threshold_output])
|
||||
message = ('0:' + str(tx)).encode()
|
||||
expected = deepcopy(user_user2_threshold_output)
|
||||
expected.fulfillment.subconditions[0]['body'].sign(str(tx).encode(),
|
||||
expected.fulfillment.subconditions[0]['body'].sign(message,
|
||||
PrivateKey(user_priv))
|
||||
expected.fulfillment.subconditions[1]['body'].sign(str(tx).encode(),
|
||||
expected.fulfillment.subconditions[1]['body'].sign(message,
|
||||
PrivateKey(user2_priv))
|
||||
tx.sign([user_priv, user2_priv])
|
||||
|
||||
@ -861,7 +858,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
|
||||
|
||||
expected_input = deepcopy(inputs[0])
|
||||
expected['id'] = transfer_tx['id']
|
||||
expected_input.fulfillment.sign(serialize(expected).encode(),
|
||||
expected_input.fulfillment.sign(('0:' + serialize(expected)).encode(),
|
||||
PrivateKey(user_priv))
|
||||
expected_ffill = expected_input.fulfillment.serialize_uri()
|
||||
transfer_ffill = transfer_tx['inputs'][0]['fulfillment']
|
||||
|
Loading…
x
Reference in New Issue
Block a user