bigchain/core: multisig

interledger: user, escrow, connector
This commit is contained in:
diminator 2016-02-24 19:38:52 +01:00
parent d7145b04bf
commit 30a68e483f
7 changed files with 220 additions and 20 deletions

View File

@ -68,7 +68,7 @@ class Bigchain(object):
def reconnect(self):
return r.connect(host=self.host, port=self.port, db=self.dbname)
def create_transaction(self, current_owner, new_owner, tx_input, operation, payload=None):
def create_transaction(self, current_owners, new_owners, tx_input, operation, payload=None):
"""Create a new transaction
A transaction in the bigchain is a transfer of a digital asset between two entities represented
@ -84,8 +84,8 @@ class Bigchain(object):
`TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities.
Args:
current_owner (str): base58 encoded public key of the current owner of the asset.
new_owner (str): base58 encoded public key of the new owner of the digital asset.
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.
@ -115,8 +115,8 @@ class Bigchain(object):
}
tx = {
'current_owner': current_owner,
'new_owner': new_owner,
'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(),
@ -135,7 +135,7 @@ class Bigchain(object):
return transaction
def sign_transaction(self, transaction, private_key):
def sign_transaction(self, transaction, private_key, public_key=None):
"""Sign a transaction
A transaction signed with the `current_owner` corresponding private key.
@ -143,15 +143,28 @@ class Bigchain(object):
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.
"""
private_key = PrivateKey(private_key)
signature = private_key.sign(self.serialize(transaction))
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({'signature': signature})
signed_transaction.update({'signatures': signatures_updated})
return signed_transaction
def verify_signature(self, signed_transaction):
@ -172,10 +185,23 @@ class Bigchain(object):
if 'assignee' in data:
data.pop('assignee')
signature = data.pop('signature')
public_key_base58 = signed_transaction['transaction']['current_owner']
signatures = data.pop('signatures')
for public_key_base58 in signed_transaction['transaction']['current_owners']:
public_key = PublicKey(public_key_base58)
return public_key.verify(self.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(self.serialize(data), signature):
return False
return True
def write_transaction(self, signed_transaction):
"""Write the transaction to bigchain.
@ -184,7 +210,7 @@ class Bigchain(object):
it has been validated by the nodes of the federation.
Args:
singed_transaction (dict): transaction with the `signature` included.
signed_transaction (dict): transaction with the `signature` included.
Returns:
dict: database response
@ -299,9 +325,10 @@ class Bigchain(object):
list: list of `txids` currently owned by `owner`
"""
# TODO: fix for multisig. new_owners is a list!
response = r.table('bigchain')\
.concat_map(lambda doc: doc['block']['transactions'])\
.filter({'transaction': {'new_owner': owner}})\
.filter({'transaction': {'new_owners': owner if isinstance(owner, list) else [owner]}})\
.pluck('id')['id']\
.run(self.conn)
owned = []
@ -336,7 +363,7 @@ class Bigchain(object):
if transaction['transaction']['operation'] == 'CREATE':
if transaction['transaction']['input']:
raise ValueError('A CREATE operation has no inputs')
if transaction['transaction']['current_owner'] not in self.federation_nodes + [self.me]:
if not(set(transaction['transaction']['current_owners']) <= set(self.federation_nodes + [self.me])):
raise exceptions.OperationError('Only federation nodes can use the operation `CREATE`')
else:
@ -349,9 +376,9 @@ class Bigchain(object):
raise exceptions.TransactionDoesNotExist('input `{}` does not exist in the bigchain'.format(
transaction['transaction']['input']))
if tx_input['transaction']['new_owner'] != transaction['transaction']['current_owner']:
if tx_input['transaction']['new_owners'] != transaction['transaction']['current_owners']:
raise exceptions.TransactionOwnerError('current_owner `{}` does not own the input `{}`'.format(
transaction['transaction']['current_owner'], transaction['transaction']['input']))
transaction['transaction']['current_owners'], transaction['transaction']['input']))
# check if the input was already spent
spent = self.get_spent(tx_input['id'])
@ -496,13 +523,11 @@ class Bigchain(object):
# 2. create the block with one transaction
# 3. write the block to the bigchain
blocks_count = r.table('bigchain').count().run(self.conn)
if blocks_count:
raise GenesisBlockAlreadyExistsError('Cannot create the Genesis block')
payload = {'message': 'Hello World from the Bigchain'}
transaction = self.create_transaction(self.me, self.me, None, 'GENESIS', payload=payload)
transaction_signed = self.sign_transaction(transaction, self.me_private)

0
interledger/__init__.py Normal file
View File

120
interledger/core.py Normal file
View File

@ -0,0 +1,120 @@
class User:
def __init__(self, ledger):
self.ledger = ledger
self.private, self.public = ledger.generate_keys()
self.assets = []
def create_asset(self):
tx = self.ledger.create_transaction(self.ledger.me, self.public, None, 'CREATE')
tx_signed = self.ledger.sign_transaction(tx, self.ledger.me_private)
self.ledger.validate_transaction(tx_signed)
self.ledger.write_transaction(tx_signed)
self.assets.append(tx_signed)
def create_assets(self, amount=1):
for i in range(amount):
self.create_asset()
class Escrow(User):
def __init__(self, ledger=None, current_owner=None, new_owner=None,
asset_id=None, condition_func=None, payload=None):
User.__init__(self, ledger)
self.condition_func = condition_func if condition_func else lambda proof: True
self.new_owner = new_owner
tx = self.ledger.create_transaction(current_owner,
[current_owner, self.public],
asset_id,
'TRANSFER',
payload)
self.assets = tx
def release(self, receipt=None):
if not self.validate(receipt):
raise Exception
tx = self.ledger.create_transaction(self.assets['transaction']['new_owners'],
self.new_owner,
self.assets['id'],
'TRANSFER',
self.assets['transaction']['data']['payload'])
return self.ledger.sign_transaction(tx, self.private, self.public)
def validate(self, receipt):
return self.condition_func(receipt)
class LedgerConnection(User):
def __init__(self, ledger):
self._escrow = None
User.__init__(self, ledger)
def escrow(self, current_owner=None, new_owner=None, condition_func=None, asset_id=None, payload=None):
self._escrow = Escrow(ledger=self.ledger,
current_owner=current_owner if current_owner else self.public,
new_owner=new_owner if new_owner else self.public,
asset_id=asset_id if asset_id else self.assets[0]['id'],
condition_func=condition_func,
payload=payload)
if not current_owner:
tx_connector_signed = self.ledger.sign_transaction(self._escrow.assets, self.private)
self.ledger.validate_transaction(tx_connector_signed)
self._escrow.assets = tx_connector_signed
self.ledger.write_transaction(tx_connector_signed)
def release(self, condition):
return self._escrow.release(condition)
class Connector:
def __init__(self, ledger=None):
self.ledger_connections = []
if ledger:
self.add_ledger(ledger)
def public(self, ledger=None):
return self.get_ledger_connection(ledger).public if self.get_ledger_connection(ledger) else None
def private(self, ledger=None):
return self.get_ledger_connection(ledger).private if self.get_ledger_connection(ledger) else None
def get_assets(self, ledger=None):
ledger_connection = self.get_ledger_connection(ledger)
return ledger_connection.assets if ledger_connection else None
def create_assets(self, amount=1, ledger=None):
ledger_connection = self.get_ledger_connection(ledger)
if ledger_connection:
ledger_connection.create_assets(amount)
def get_ledger_connection(self, ledger=None):
if not ledger:
return self.ledger_connections[0]
# TODO: yield
ledger_connection = [l for l in self.ledger_connections if l.ledger == ledger]
return ledger_connection[0] if ledger_connection else None
def add_ledger(self, ledger):
if self.can_add_ledger_connection(ledger):
self.ledger_connections.append(LedgerConnection(ledger))
def can_add_ledger_connection(self, ledger):
return False if self.get_ledger_connection(ledger) else True
def connect(self, user_from=None, ledger_from=None, user_to=None, ledger_to=None,
condition_func=None, asset_id=None, payload=None):
connection_from = self.get_ledger_connection(ledger_from)
connection_to = self.get_ledger_connection(ledger_to)
connection_from.escrow(current_owner=user_from,
condition_func=condition_func,
asset_id=asset_id,
payload=payload)
connection_to.escrow(new_owner=user_to,
condition_func=condition_func,
payload=payload)
return connection_from._escrow.assets
def release(self, ledger=None, receipt=None):
connection = self.get_ledger_connection(ledger)
return connection.release(receipt)

View File

View File

@ -0,0 +1 @@
{"database": {"host": "localhost", "name": "bigchain", "port": 28015}, "keyring": [], "keypair": {"public": "jhpCiHPiMmHGFQvjqn1LdLddYhYFd63ywUbi3tFCW31f", "private": "5vbka7oLMf2UTNV1sq3nyKJXTERCPWvD8Pf4KnGYNBtA"}}

View File

@ -0,0 +1 @@
{"keyring": [], "database": {"host": "localhost", "port": 28015, "name": "megachain"}, "keypair": {"private": "DGKiLSdLsZofmzZMv3B9KZVj79wrq4PZ3PsMmk8Hu8KK", "public": "gMGJ9j15qyBVC1t8ceiDgJLLaRBgvrFchyzB1GgWk3Fa"}}

View File

@ -0,0 +1,53 @@
import json
from time import sleep
from bigchaindb import Bigchain
from interledger.core import User, Connector
config_bigchain = json.load(open('interledger/tests/bigchain.json', 'r'))
config_megachain = json.load(open('interledger/tests/megachain.json', 'r'))
bigchain = Bigchain(dbname=config_bigchain['database']['name'],
public_key=config_bigchain['keypair']['public'],
private_key=config_bigchain['keypair']['private'])
megachain = Bigchain(dbname=config_megachain['database']['name'],
public_key=config_megachain['keypair']['public'],
private_key=config_megachain['keypair']['private'])
alice = User(bigchain)
bob = User(megachain)
connector = Connector(bigchain)
connector.add_ledger(megachain)
# create assets
alice.create_assets(amount=2)
connector.create_assets(amount=2, ledger=bigchain)
connector.create_assets(amount=2, ledger=megachain)
sleep(6)
# transfer asset to escrow
tx_alice = connector.connect(user_from=alice.public,
ledger_from=alice.ledger,
user_to=bob.public,
ledger_to=bob.ledger,
condition_func=lambda proof: True,
asset_id=alice.assets[0]['id'],
payload={'what': 'ever'})
tx_alice_signed = alice.ledger.sign_transaction(tx_alice, alice.private)
alice.ledger.validate_transaction(tx_alice_signed)
alice.ledger.write_transaction(tx_alice_signed)
sleep(6)
# release asset from escrow
tx_bob = connector.release(ledger=bob.ledger, receipt=None)
tx_bob_signed = bob.ledger.sign_transaction(tx_bob, connector.private(bob.ledger), connector.public(bob.ledger))
bob.ledger.validate_transaction(tx_bob_signed)
bob.ledger.write_transaction(tx_bob_signed)
tx_connector = connector.release(ledger=alice.ledger, receipt=None)
tx_connector_signed = alice.ledger.sign_transaction(tx_connector, alice.private, alice.public)
alice.ledger.validate_transaction(tx_connector_signed)
alice.ledger.write_transaction(tx_connector_signed)