mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
fix merge + tests
This commit is contained in:
parent
03a25bd245
commit
4039310d43
@ -72,106 +72,17 @@ class Bigchain(object):
|
|||||||
"""Create a new transaction
|
"""Create a new transaction
|
||||||
|
|
||||||
Refer to the documentation of ``bigchaindb.util.create_tx``
|
Refer to the documentation of ``bigchaindb.util.create_tx``
|
||||||
A transaction in the bigchain is a transfer of a digital asset between two entities represented
|
|
||||||
by public keys.
|
|
||||||
|
|
||||||
Currently the bigchain supports two types of operations:
|
|
||||||
|
|
||||||
`CREATE` - Only federation nodes are allowed to use this operation. In a create operation
|
|
||||||
a federation node creates a digital asset in the bigchain and assigns that asset to a public
|
|
||||||
key. The owner of the private key can then decided to transfer this digital asset by using the
|
|
||||||
`transaction id` of the transaction as an input in a `TRANSFER` transaction.
|
|
||||||
|
|
||||||
`TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
current_owners (list): base58 encoded public keys of all current owners of the asset.
|
|
||||||
new_owners (list): base58 encoded public keys of all new owners of the digital asset.
|
|
||||||
tx_input (str): id of the transaction to use as input.
|
|
||||||
operation (str): Either `CREATE` or `TRANSFER` operation.
|
|
||||||
payload (Optional[dict]): dictionary with information about asset.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: unsigned transaction.
|
|
||||||
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
TypeError: if the optional ``payload`` argument is not a ``dict``.
|
|
||||||
"""
|
"""
|
||||||
data = None
|
|
||||||
if payload is not None:
|
|
||||||
if isinstance(payload, dict):
|
|
||||||
hash_payload = hash_data(self.serialize(payload))
|
|
||||||
data = {
|
|
||||||
'hash': hash_payload,
|
|
||||||
'payload': payload
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise TypeError('`payload` must be an dict instance')
|
|
||||||
|
|
||||||
hash_payload = hash_data(self.serialize(payload))
|
return util.create_tx(current_owners, new_owners, tx_input, operation, payload)
|
||||||
data = {
|
|
||||||
'hash': hash_payload,
|
|
||||||
'payload': payload
|
|
||||||
}
|
|
||||||
|
|
||||||
tx = {
|
|
||||||
'current_owners': current_owners if isinstance(current_owners, list) else [current_owners],
|
|
||||||
'new_owners': new_owners if isinstance(new_owners, list) else [new_owners],
|
|
||||||
'input': tx_input,
|
|
||||||
'operation': operation,
|
|
||||||
'timestamp': self.timestamp(),
|
|
||||||
'data': data
|
|
||||||
}
|
|
||||||
|
|
||||||
# serialize and convert to bytes
|
|
||||||
tx_serialized = self.serialize(tx)
|
|
||||||
tx_hash = hash_data(tx_serialized)
|
|
||||||
|
|
||||||
# create the transaction
|
|
||||||
transaction = {
|
|
||||||
'id': tx_hash,
|
|
||||||
'transaction': tx
|
|
||||||
}
|
|
||||||
|
|
||||||
return transaction
|
|
||||||
return util.create_tx(current_owner, new_owner, tx_input, operation, payload)
|
|
||||||
|
|
||||||
def sign_transaction(self, transaction, private_key, public_key=None):
|
def sign_transaction(self, transaction, private_key, public_key=None):
|
||||||
"""Sign a transaction
|
"""Sign a transaction
|
||||||
|
|
||||||
Refer to the documentation of ``bigchaindb.util.sign_tx``
|
Refer to the documentation of ``bigchaindb.util.sign_tx``
|
||||||
A transaction signed with the `current_owner` corresponding private key.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
transaction (dict): transaction to sign.
|
|
||||||
private_key (str): base58 encoded private key to create a signature of the transaction.
|
|
||||||
public_key (str): (optional) base58 encoded public key to identify each signature of a multisig transaction.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: transaction with the `signature` field included.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# return util.sign_tx(transaction, private_key)
|
return util.sign_tx(transaction, private_key, public_key)
|
||||||
private_key = PrivateKey(private_key)
|
|
||||||
if len(transaction['transaction']['current_owners']) == 1:
|
|
||||||
signatures_updated = private_key.sign(self.serialize(transaction))
|
|
||||||
else:
|
|
||||||
# multisig, sign for each input and store {pub_key: signature_for_priv_key}
|
|
||||||
if public_key is None:
|
|
||||||
raise ValueError('public_key must be provided for signing multisig transactions')
|
|
||||||
transaction_without_signatures = transaction.copy()
|
|
||||||
signatures = transaction_without_signatures.pop('signatures') \
|
|
||||||
if 'signatures' in transaction_without_signatures else []
|
|
||||||
signatures_updated = signatures.copy()
|
|
||||||
signatures_updated = [s for s in signatures_updated if not s['public_key'] == public_key]
|
|
||||||
signatures_updated.append({'public_key': public_key,
|
|
||||||
'signature': private_key.sign(self.serialize(transaction_without_signatures))})
|
|
||||||
|
|
||||||
signed_transaction = transaction.copy()
|
|
||||||
signed_transaction.update({'signatures': signatures_updated})
|
|
||||||
return signed_transaction
|
|
||||||
|
|
||||||
def verify_signature(self, signed_transaction):
|
def verify_signature(self, signed_transaction):
|
||||||
"""Verify the signature of a transaction.
|
"""Verify the signature of a transaction.
|
||||||
@ -179,29 +90,7 @@ class Bigchain(object):
|
|||||||
Refer to the documentation of ``bigchaindb.crypto.verify_signature``
|
Refer to the documentation of ``bigchaindb.crypto.verify_signature``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = signed_transaction.copy()
|
return util.verify_signature(signed_transaction)
|
||||||
|
|
||||||
# if assignee field in the transaction, remove it
|
|
||||||
if 'assignee' in data:
|
|
||||||
data.pop('assignee')
|
|
||||||
|
|
||||||
signatures = data.pop('signatures')
|
|
||||||
for public_key_base58 in signed_transaction['transaction']['current_owners']:
|
|
||||||
public_key = PublicKey(public_key_base58)
|
|
||||||
|
|
||||||
if isinstance(signatures, list):
|
|
||||||
try:
|
|
||||||
signature = [s['signature'] for s in signatures if s['public_key'] == public_key_base58]
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
if not len(signature) == 1:
|
|
||||||
return False
|
|
||||||
signature = signature[0]
|
|
||||||
else:
|
|
||||||
signature = signatures
|
|
||||||
if not public_key.verify(self.serialize(data), signature):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
@monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate'])
|
@monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate'])
|
||||||
def write_transaction(self, signed_transaction, durability='soft'):
|
def write_transaction(self, signed_transaction, durability='soft'):
|
||||||
|
|||||||
@ -76,7 +76,7 @@ def timestamp():
|
|||||||
return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6)
|
return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6)
|
||||||
|
|
||||||
|
|
||||||
def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
|
def create_tx(current_owners, new_owners, tx_input, operation, payload=None):
|
||||||
"""Create a new transaction
|
"""Create a new transaction
|
||||||
|
|
||||||
A transaction in the bigchain is a transfer of a digital asset between two entities represented
|
A transaction in the bigchain is a transfer of a digital asset between two entities represented
|
||||||
@ -92,8 +92,8 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
|
|||||||
`TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities.
|
`TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
current_owner (str): base58 encoded public key of the current owner of the asset.
|
current_owners (list): base58 encoded public keys of all current owners of the asset.
|
||||||
new_owner (str): base58 encoded public key of the new owner of the digital asset.
|
new_owners (list): base58 encoded public keys of all new owners of the digital asset.
|
||||||
tx_input (str): id of the transaction to use as input.
|
tx_input (str): id of the transaction to use as input.
|
||||||
operation (str): Either `CREATE` or `TRANSFER` operation.
|
operation (str): Either `CREATE` or `TRANSFER` operation.
|
||||||
payload (Optional[dict]): dictionary with information about asset.
|
payload (Optional[dict]): dictionary with information about asset.
|
||||||
@ -124,8 +124,8 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx = {
|
tx = {
|
||||||
'current_owner': current_owner,
|
'current_owners': current_owners if isinstance(current_owners, list) else [current_owners],
|
||||||
'new_owner': new_owner,
|
'new_owners': new_owners if isinstance(new_owners, list) else [new_owners],
|
||||||
'input': tx_input,
|
'input': tx_input,
|
||||||
'operation': operation,
|
'operation': operation,
|
||||||
'timestamp': timestamp(),
|
'timestamp': timestamp(),
|
||||||
@ -145,7 +145,7 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
|
|||||||
return transaction
|
return transaction
|
||||||
|
|
||||||
|
|
||||||
def sign_tx(transaction, private_key):
|
def sign_tx(transaction, private_key, public_key=None):
|
||||||
"""Sign a transaction
|
"""Sign a transaction
|
||||||
|
|
||||||
A transaction signed with the `current_owner` corresponding private key.
|
A transaction signed with the `current_owner` corresponding private key.
|
||||||
@ -153,15 +153,29 @@ def sign_tx(transaction, private_key):
|
|||||||
Args:
|
Args:
|
||||||
transaction (dict): transaction to sign.
|
transaction (dict): transaction to sign.
|
||||||
private_key (str): base58 encoded private key to create a signature of the transaction.
|
private_key (str): base58 encoded private key to create a signature of the transaction.
|
||||||
|
public_key (str): (optional) base58 encoded public key to identify each signature of a multisig transaction.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: transaction with the `signature` field included.
|
dict: transaction with the `signature` field included.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
private_key = PrivateKey(private_key)
|
private_key = PrivateKey(private_key)
|
||||||
signature = private_key.sign(serialize(transaction))
|
if len(transaction['transaction']['current_owners']) == 1:
|
||||||
|
signatures_updated = private_key.sign(serialize(transaction))
|
||||||
|
else:
|
||||||
|
# multisig, sign for each input and store {pub_key: signature_for_priv_key}
|
||||||
|
if public_key is None:
|
||||||
|
raise ValueError('public_key must be provided for signing multisig transactions')
|
||||||
|
transaction_without_signatures = transaction.copy()
|
||||||
|
signatures = transaction_without_signatures.pop('signatures') \
|
||||||
|
if 'signatures' in transaction_without_signatures else []
|
||||||
|
signatures_updated = signatures.copy()
|
||||||
|
signatures_updated = [s for s in signatures_updated if not s['public_key'] == public_key]
|
||||||
|
signatures_updated.append({'public_key': public_key,
|
||||||
|
'signature': private_key.sign(serialize(transaction_without_signatures))})
|
||||||
|
|
||||||
signed_transaction = transaction.copy()
|
signed_transaction = transaction.copy()
|
||||||
signed_transaction.update({'signature': signature})
|
signed_transaction.update({'signatures': signatures_updated})
|
||||||
return signed_transaction
|
return signed_transaction
|
||||||
|
|
||||||
|
|
||||||
@ -199,10 +213,23 @@ def verify_signature(signed_transaction):
|
|||||||
if 'assignee' in data:
|
if 'assignee' in data:
|
||||||
data.pop('assignee')
|
data.pop('assignee')
|
||||||
|
|
||||||
signature = data.pop('signature')
|
signatures = data.pop('signatures')
|
||||||
public_key_base58 = signed_transaction['transaction']['current_owner']
|
for public_key_base58 in signed_transaction['transaction']['current_owners']:
|
||||||
public_key = PublicKey(public_key_base58)
|
public_key = PublicKey(public_key_base58)
|
||||||
return public_key.verify(serialize(data), signature)
|
|
||||||
|
if isinstance(signatures, list):
|
||||||
|
try:
|
||||||
|
signature = [s['signature'] for s in signatures if s['public_key'] == public_key_base58]
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
if not len(signature) == 1:
|
||||||
|
return False
|
||||||
|
signature = signature[0]
|
||||||
|
else:
|
||||||
|
signature = signatures
|
||||||
|
if not public_key.verify(serialize(data), signature):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def transform_create(tx):
|
def transform_create(tx):
|
||||||
@ -215,6 +242,6 @@ def transform_create(tx):
|
|||||||
payload = None
|
payload = None
|
||||||
if transaction['data'] and 'payload' in transaction['data']:
|
if transaction['data'] and 'payload' in transaction['data']:
|
||||||
payload = transaction['data']['payload']
|
payload = transaction['data']['payload']
|
||||||
new_tx = create_tx(b.me, transaction['current_owner'], None, 'CREATE', payload=payload)
|
new_tx = create_tx(b.me, transaction['current_owners'], None, 'CREATE', payload=payload)
|
||||||
return new_tx
|
return new_tx
|
||||||
|
|
||||||
|
|||||||
@ -50,8 +50,8 @@ class TestBigchainApi(object):
|
|||||||
payload = {'cats': 'are awesome'}
|
payload = {'cats': 'are awesome'}
|
||||||
tx = b.create_transaction('a', 'b', 'c', 'd', payload)
|
tx = b.create_transaction('a', 'b', 'c', 'd', payload)
|
||||||
tx_calculated = {
|
tx_calculated = {
|
||||||
'current_owner': 'a',
|
'current_owners': ['a'],
|
||||||
'new_owner': 'b',
|
'new_owners': ['b'],
|
||||||
'input': 'c',
|
'input': 'c',
|
||||||
'operation': 'd',
|
'operation': 'd',
|
||||||
'timestamp': tx['transaction']['timestamp'],
|
'timestamp': tx['transaction']['timestamp'],
|
||||||
@ -67,7 +67,8 @@ class TestBigchainApi(object):
|
|||||||
sk, vk = generate_key_pair()
|
sk, vk = generate_key_pair()
|
||||||
tx = b.create_transaction(vk, 'b', 'c', 'd')
|
tx = b.create_transaction(vk, 'b', 'c', 'd')
|
||||||
|
|
||||||
assert b.verify_signature(tx) is False
|
with pytest.raises(KeyError) as excinfo:
|
||||||
|
b.verify_signature(tx)
|
||||||
tx_signed = b.sign_transaction(tx, sk)
|
tx_signed = b.sign_transaction(tx, sk)
|
||||||
|
|
||||||
assert 'signatures' in tx_signed
|
assert 'signatures' in tx_signed
|
||||||
@ -77,13 +78,18 @@ class TestBigchainApi(object):
|
|||||||
num_current_owners = 42
|
num_current_owners = 42
|
||||||
sk, vk = [], []
|
sk, vk = [], []
|
||||||
for _ in range(num_current_owners):
|
for _ in range(num_current_owners):
|
||||||
sk_, vk_ = b.generate_keys()
|
sk_, vk_ = generate_key_pair()
|
||||||
sk.append(sk_)
|
sk.append(sk_)
|
||||||
vk.append(vk_)
|
vk.append(vk_)
|
||||||
tx = b.create_transaction(vk, 'b', 'c', 'd')
|
tx = b.create_transaction(vk, 'b', 'c', 'd')
|
||||||
tx_signed = tx
|
tx_signed = tx
|
||||||
|
|
||||||
|
with pytest.raises(KeyError) as excinfo:
|
||||||
|
b.verify_signature(tx_signed)
|
||||||
|
|
||||||
for i in range(num_current_owners):
|
for i in range(num_current_owners):
|
||||||
assert b.verify_signature(tx_signed) is False
|
if i > 0:
|
||||||
|
assert b.verify_signature(tx_signed) is False
|
||||||
tx_signed = b.sign_transaction(tx_signed, sk[i], vk[i])
|
tx_signed = b.sign_transaction(tx_signed, sk[i], vk[i])
|
||||||
|
|
||||||
assert 'signatures' in tx_signed
|
assert 'signatures' in tx_signed
|
||||||
|
|||||||
@ -39,8 +39,8 @@ def test_client_can_create_assets(mock_requests_post, client):
|
|||||||
# `current_owner` will be overwritten with the public key of the node in the federation
|
# `current_owner` will be overwritten with the public key of the node in the federation
|
||||||
# that will create the real transaction. `signature` will be overwritten with the new signature.
|
# that will create the real transaction. `signature` will be overwritten with the new signature.
|
||||||
# Note that this scenario is ignored by this test.
|
# Note that this scenario is ignored by this test.
|
||||||
assert tx['transaction']['current_owner'] == client.public_key
|
assert tx['transaction']['current_owners'] == [client.public_key]
|
||||||
assert tx['transaction']['new_owner'] == client.public_key
|
assert tx['transaction']['new_owners'] == [client.public_key]
|
||||||
assert tx['transaction']['input'] == None
|
assert tx['transaction']['input'] == None
|
||||||
|
|
||||||
assert util.verify_signature(tx)
|
assert util.verify_signature(tx)
|
||||||
@ -51,8 +51,8 @@ def test_client_can_transfer_assets(mock_requests_post, client):
|
|||||||
|
|
||||||
tx = client.transfer('a', 123)
|
tx = client.transfer('a', 123)
|
||||||
|
|
||||||
assert tx['transaction']['current_owner'] == client.public_key
|
assert tx['transaction']['current_owners'] == [client.public_key]
|
||||||
assert tx['transaction']['new_owner'] == 'a'
|
assert tx['transaction']['new_owners'] == ['a']
|
||||||
assert tx['transaction']['input'] == 123
|
assert tx['transaction']['input'] == 123
|
||||||
|
|
||||||
assert util.verify_signature(tx)
|
assert util.verify_signature(tx)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ def test_transform_create(b, user_private_key, user_public_key):
|
|||||||
tx = util.transform_create(tx)
|
tx = util.transform_create(tx)
|
||||||
tx = util.sign_tx(tx, b.me_private)
|
tx = util.sign_tx(tx, b.me_private)
|
||||||
|
|
||||||
assert tx['transaction']['current_owner'] == b.me
|
assert tx['transaction']['current_owners'] == [b.me]
|
||||||
assert tx['transaction']['new_owner'] == user_public_key
|
assert tx['transaction']['new_owners'] == [user_public_key]
|
||||||
assert util.verify_signature(tx)
|
assert util.verify_signature(tx)
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,8 @@ def test_post_create_transaction_endpoint(b, client):
|
|||||||
tx = util.create_and_sign_tx(keypair[0], keypair[1], keypair[1], None, 'CREATE')
|
tx = util.create_and_sign_tx(keypair[0], keypair[1], keypair[1], None, 'CREATE')
|
||||||
|
|
||||||
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||||
assert res.json['transaction']['current_owner'] == b.me
|
assert res.json['transaction']['current_owners'] == [b.me]
|
||||||
assert res.json['transaction']['new_owner'] == keypair[1]
|
assert res.json['transaction']['new_owners'] == [keypair[1]]
|
||||||
|
|
||||||
|
|
||||||
def test_post_transfer_transaction_endpoint(b, client):
|
def test_post_transfer_transaction_endpoint(b, client):
|
||||||
@ -37,6 +37,6 @@ def test_post_transfer_transaction_endpoint(b, client):
|
|||||||
transfer = util.create_and_sign_tx(from_keypair[0], from_keypair[1], to_keypair[1], tx_id)
|
transfer = util.create_and_sign_tx(from_keypair[0], from_keypair[1], to_keypair[1], tx_id)
|
||||||
res = client.post(TX_ENDPOINT, data=json.dumps(transfer))
|
res = client.post(TX_ENDPOINT, data=json.dumps(transfer))
|
||||||
|
|
||||||
assert res.json['transaction']['current_owner'] == from_keypair[1]
|
assert res.json['transaction']['current_owners'] == [from_keypair[1]]
|
||||||
assert res.json['transaction']['new_owner'] == to_keypair[1]
|
assert res.json['transaction']['new_owners'] == [to_keypair[1]]
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user