Merge pull request #1225 from bigchaindb/sign-tx-body

Sign whole transaction body instead of partial transaction
This commit is contained in:
libscott 2017-04-24 14:27:31 +02:00 committed by GitHub
commit bd057f63ca
3 changed files with 38 additions and 58 deletions

View File

@ -686,22 +686,16 @@ 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):
self.inputs[i] = self._sign_input(input_, tx_serialized, 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 +706,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 +738,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 +782,8 @@ class Transaction(object):
# cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings
for subffill in subffills:
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 +836,17 @@ 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 """
return self._input_valid(self.inputs[i], self.operation,
tx_serialized, output_condition_uri)
partial_transactions = map(gen_tx, self.inputs,
self.outputs, output_condition_uris)
return all(partial_transactions)
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):

View File

@ -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. The computed signature goes into creating the ``fulfillment`` string of the input.

View File

@ -510,7 +510,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 = str(tx).encode()
expected.fulfillment.sign(message, PrivateKey(user_priv))
tx.sign([user_priv])
assert tx.inputs[0].to_dict()['fulfillment'] == \
@ -527,7 +528,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)
@ -538,13 +538,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)
@ -576,10 +574,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 = 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])