def from_dict(cls, block_body): """Transform a Python dictionary to a Block object. Args: block_body (dict): A block dictionary to be transformed. Returns: :class:`~Block` Raises: InvalidHash: If the block's id is not corresponding to its data. """ # Validate block id block = block_body['block'] block_serialized = serialize(block) block_id = hash_data(block_serialized) if block_id != block_body['id']: raise InvalidHash() transactions = [ Transaction.from_dict(tx) for tx in block['transactions'] ] signature = block_body.get('signature') return cls(transactions, block['node_pubkey'], block['timestamp'], block['voters'], signature)
def test_sign_block(self, b, alice): from bigchaindb.common.crypto import PrivateKey, PublicKey from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [ Transaction.create([alice.public_key], [([alice.public_key], 1)]) ] timestamp = gen_timestamp() voters = ['Qaaa', 'Qbbb'] expected_block = { 'timestamp': timestamp, 'transactions': [tx.to_dict() for tx in transactions], 'node_pubkey': alice.public_key, 'voters': voters, } expected_block_serialized = serialize(expected_block).encode() expected = PrivateKey( alice.private_key).sign(expected_block_serialized) block = Block(transactions, alice.public_key, timestamp, voters) block = block.sign(alice.private_key) assert block.signature == expected.decode() public_key = PublicKey(alice.public_key) assert public_key.verify(expected_block_serialized, block.signature)
def test_valid_block_voting_sequential(b, genesis_block, monkeypatch): from bigchaindb.backend import query from bigchaindb.common import crypto, utils from bigchaindb.pipelines import vote monkeypatch.setattr('time.time', lambda: 1111111111) vote_obj = vote.Vote() block = dummy_block(b).to_dict() txs = block['block']['transactions'] for tx, block_id, num_tx in vote_obj.ungroup(block['id'], txs): last_vote = vote_obj.vote(*vote_obj.validate_tx(tx, block_id, num_tx)) vote_obj.write_vote(*last_vote) vote_rs = query.get_votes_by_block_id_and_voter(b.connection, block_id, b.me) vote_doc = vote_rs.next() assert vote_doc['vote'] == { 'voting_for_block': block['id'], 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '1111111111' } serialized_vote = utils.serialize(vote_doc['vote']).encode() assert vote_doc['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify(serialized_vote, vote_doc['signature']) is True
def vote(self, block_id, previous_block_id, decision, invalid_reason=None): """Create a signed vote for a block given the :attr:`previous_block_id` and the :attr:`decision` (valid/invalid). Args: block_id (str): The id of the block to vote on. previous_block_id (str): The id of the previous block. decision (bool): Whether the block is valid or invalid. invalid_reason (Optional[str]): Reason the block is invalid """ if block_id == previous_block_id: raise exceptions.CyclicBlockchainError() vote = { 'voting_for_block': block_id, 'previous_block': previous_block_id, 'is_block_valid': decision, 'invalid_reason': invalid_reason, 'timestamp': gen_timestamp() } vote_data = serialize(vote) signature = crypto.PrivateKey(self.me_private).sign(vote_data.encode()) vote_signed = { 'node_pubkey': self.me, 'signature': signature.decode(), 'vote': vote } return vote_signed
def test_block_invalid_signature(self, b, alice): from bigchaindb.common.crypto import hash_data from bigchaindb.common.exceptions import InvalidSignature from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transaction = Transaction.create([alice.public_key], [([alice.public_key], 1)]) transaction.sign([alice.private_key]) timestamp = gen_timestamp() block = { 'timestamp': timestamp, 'transactions': [transaction.to_dict()], 'node_pubkey': alice.public_key, } block_body = { 'id': hash_data(serialize(block)), 'block': block, 'signature': 'an invalid signature', } with raises(InvalidSignature): Block.from_dict(block_body).validate(b)
def test_block_deserialization(self, b): from bigchaindb.common.crypto import hash_data from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transaction = Transaction.create([b.me], [([b.me], 1)]) transaction.sign([b.me_private]) timestamp = gen_timestamp() voters = ['Qaaa', 'Qbbb'] expected = Block([transaction], b.me, timestamp, voters) block = { 'timestamp': timestamp, 'transactions': [transaction.to_dict()], 'node_pubkey': b.me, 'voters': voters, } block_body = { 'id': hash_data(serialize(block)), 'block': block, 'signature': None, } assert expected == Block.from_dict(block_body)
def to_dict(self): """Transform the Block to a Python dictionary. Returns: dict: The Block as a dict. Raises: OperationError: If the Block doesn't contain any transactions. """ if len(self.transactions) == 0: raise OperationError('Empty block creation is not allowed') block = { 'timestamp': self.timestamp, 'transactions': [tx.to_dict() for tx in self.transactions], 'node_pubkey': self.node_pubkey, 'voters': self.voters, } block_serialized = serialize(block) block_id = hash_data(block_serialized) return { 'id': block_id, 'block': block, 'signature': self.signature, }
def test_block_serialization(self, b, alice): from bigchaindb.common.crypto import hash_data from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [ Transaction.create([alice.public_key], [([alice.public_key], 1)]) ] timestamp = gen_timestamp() voters = ['Qaaa', 'Qbbb'] expected_block = { 'timestamp': timestamp, 'transactions': [tx.to_dict() for tx in transactions], 'node_pubkey': alice.public_key, 'voters': voters, } expected = { 'id': hash_data(serialize(expected_block)), 'block': expected_block, 'signature': None, } block = Block(transactions, alice.public_key, timestamp, voters) assert block.to_dict() == expected
def verify_vote_signature(cls, vote): """Verify the signature of a vote """ signature = vote.get('signature') pk_base58 = vote.get('node_pubkey') if not (type(signature) == str and type(pk_base58) == str): raise ValueError('Malformed vote: %s' % vote) public_key = PublicKey(pk_base58) body = serialize(vote['vote']).encode() return public_key.verify(body, signature)
def test_transaction_unicode(b, alice): from bigchaindb.common.utils import serialize from bigchaindb.models import Transaction # http://www.fileformat.info/info/unicode/char/1f37a/index.htm beer_python = {'beer': '\N{BEER MUG}'} beer_json = '{"beer":"\N{BEER MUG}"}' tx = (Transaction.create([alice.public_key], [([alice.public_key], 100)], beer_python)).sign([alice.private_key]) block = b.create_block([tx]) b.write_block(block) assert b.get_block(block.id) == block.to_dict() assert block.validate(b) == block assert beer_json in serialize(block.to_dict())
def sign(self, private_key): """Create a signature for the Block and overwrite `self.signature`. Args: private_key (str): A private key corresponding to `self.node_pubkey`. Returns: :class:`~.Block` """ block_body = self.to_dict() block_serialized = serialize(block_body['block']) private_key = PrivateKey(private_key) self.signature = private_key.sign(block_serialized.encode()).decode() return self
def test_transaction_unicode(b, alice): import copy from bigchaindb.common.utils import serialize from bigchaindb.models import Transaction # http://www.fileformat.info/info/unicode/char/1f37a/index.htm beer_python = {'beer': '\N{BEER MUG}'} beer_json = '{"beer":"\N{BEER MUG}"}' tx = (Transaction.create([alice.public_key], [([alice.public_key], 100)], beer_python)).sign([alice.private_key]) tx_1 = copy.deepcopy(tx) b.store_bulk_transactions([tx]) assert beer_json in serialize(tx_1.to_dict())
def is_signature_valid(self): """Check the validity of a Block's signature. Returns: bool: Stating the validity of the Block's signature. """ block = self.to_dict()['block'] # cc only accepts bytestring messages block_serialized = serialize(block).encode() public_key = PublicKey(block['node_pubkey']) try: # NOTE: CC throws a `ValueError` on some wrong signatures # https://github.com/bigchaindb/cryptoconditions/issues/27 return public_key.verify(block_serialized, self.signature) except (ValueError, AttributeError): return False
def from_dict(cls, block_body): """Transform a Python dictionary to a Block object. Args: block_body (dict): A block dictionary to be transformed. Returns: :class:`~Block` Raises: InvalidHash: If the block's id is not corresponding to its data. InvalidSignature: If the block's signature is not corresponding to it's data or `node_pubkey`. """ # TODO: Reuse `is_signature_valid` method here. block = block_body['block'] block_serialized = serialize(block) block_id = hash_data(block_serialized) public_key = PublicKey(block['node_pubkey']) try: signature = block_body['signature'] except KeyError: signature = None if block_id != block_body['id']: raise InvalidHash() if signature is not None: # NOTE: CC throws a `ValueError` on some wrong signatures # https://github.com/bigchaindb/cryptoconditions/issues/27 try: signature_valid = public_key\ .verify(block_serialized.encode(), signature) except ValueError: signature_valid = False if signature_valid is False: raise InvalidSignature('Invalid block signature') transactions = [ Transaction.from_dict(tx) for tx in block['transactions'] ] return cls(transactions, block['node_pubkey'], block['timestamp'], block['voters'], signature)
def test_vote_creation_invalid(b): from bigchaindb.common import crypto from bigchaindb.common.utils import serialize # create valid block block = dummy_block(b) # retrieve vote vote = b.vote(block.id, DUMMY_SHA3, False) # assert vote is correct assert vote['vote']['voting_for_block'] == block.id assert vote['vote']['previous_block'] == DUMMY_SHA3 assert vote['vote']['is_block_valid'] is False assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify( serialize(vote['vote']).encode(), vote['signature']) is True
def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, user2_output, user_priv): from bigchaindb.common.transaction import Transaction from bigchaindb.common.utils import serialize from .utils import validate_transaction_model expected = { 'outputs': [user2_output.to_dict()], 'metadata': None, 'asset': { 'id': tx.id, }, 'inputs': [{ 'owners_before': [user_pub], 'fulfillment': None, 'fulfills': { 'transaction_id': tx.id, 'output_index': 0 } }], 'operation': 'TRANSFER', 'version': Transaction.VERSION } inputs = tx.to_inputs([0]) transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset_id=tx.id) transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.to_dict() expected_input = deepcopy(inputs[0]) expected['id'] = transfer_tx['id'] expected_input.fulfillment.sign( serialize(expected).encode(), b58decode(user_priv)) expected_ffill = expected_input.fulfillment.serialize_uri() transfer_ffill = transfer_tx['inputs'][0]['fulfillment'] assert transfer_ffill == expected_ffill transfer_tx = Transaction.from_dict(transfer_tx) assert transfer_tx.inputs_valid([tx.outputs[0]]) is True validate_transaction_model(transfer_tx)
def test_valid_block_voting_with_create_transaction(b, genesis_block, monkeypatch): from bigchaindb.backend import query from bigchaindb.common import crypto, utils from bigchaindb.models import Transaction from bigchaindb.pipelines import vote # create a `CREATE` transaction test_user_priv, test_user_pub = crypto.generate_key_pair() tx = Transaction.create([b.me], [([test_user_pub], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1111111111) block = b.create_block([tx]) block_dict = decouple_assets(b, block) inpipe = Pipe() outpipe = Pipe() vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) inpipe.put(block_dict) vote_pipeline.start() vote_out = outpipe.get() vote_pipeline.terminate() vote_rs = query.get_votes_by_block_id_and_voter(b.connection, block.id, b.me) vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == { 'voting_for_block': block.id, 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '1111111111' } serialized_vote = utils.serialize(vote_doc['vote']).encode() assert vote_doc['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify(serialized_vote, vote_doc['signature']) is True
def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk, genesis_block): from bigchaindb.backend import query from bigchaindb.common import crypto, utils from bigchaindb.models import Transaction from bigchaindb.pipelines import vote inpipe = Pipe() outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) # NOTE: `tx` is invalid, because its content is not corresponding to its id tx = Transaction.create([b.me], [([b.me], 1)]) tx = tx.sign([b.me_private]) block = b.create_block([tx]).to_dict() block['block']['transactions'][0]['id'] = 'an invalid tx id' inpipe.put(block) vote_pipeline.start() vote_out = outpipe.get() vote_pipeline.terminate() vote_rs = query.get_votes_by_block_id_and_voter(b.connection, block['id'], b.me) vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == { 'voting_for_block': block['id'], 'previous_block': genesis_block.id, 'is_block_valid': False, 'invalid_reason': None, 'timestamp': '1111111111' } serialized_vote = utils.serialize(vote_doc['vote']).encode() assert vote_doc['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify(serialized_vote, vote_doc['signature']) is True
def test_get_last_voted_block_cyclic_blockchain(self, b, monkeypatch, alice): from bigchaindb.common.crypto import PrivateKey from bigchaindb.common.exceptions import CyclicBlockchainError from bigchaindb.common.utils import serialize from bigchaindb.models import Transaction tx = Transaction.create([alice.public_key], [([alice.public_key], 1)]) tx = tx.sign([alice.private_key]) monkeypatch.setattr('time.time', lambda: 1) block1 = b.create_block([tx]) b.write_block(block1) # Manipulate vote to create a cyclic Blockchain vote = b.vote(block1.id, b.get_last_voted_block().id, True) vote['vote']['previous_block'] = block1.id vote_data = serialize(vote['vote']) vote['signature'] = PrivateKey(alice.private_key).sign(vote_data.encode()) b.write_vote(vote) with pytest.raises(CyclicBlockchainError): b.get_last_voted_block()
def verify_vote_signature(voters, signed_vote): """Verify the signature of a vote A valid vote should have been signed by a voter's private key. Args: voters (list): voters of the block that is under election signed_vote (dict): a vote with the `signature` included. Returns: bool: True if the signature is correct, False otherwise. """ signature = signed_vote['signature'] pk_base58 = signed_vote['node_pubkey'] # immediately return False if the voter is not in the block voter list if pk_base58 not in voters: return False public_key = crypto.PublicKey(pk_base58) return public_key.verify(serialize(signed_vote['vote']).encode(), signature)
def test_valid_block_voting_multiprocessing(b, genesis_block, monkeypatch): from bigchaindb.backend import query from bigchaindb.common import crypto, utils from bigchaindb.pipelines import vote inpipe = Pipe() outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) block = dummy_block(b) block_dict = decouple_assets(b, block) inpipe.put(block_dict) vote_pipeline.start() vote_out = outpipe.get() vote_pipeline.terminate() vote_rs = query.get_votes_by_block_id_and_voter(b.connection, block.id, b.me) vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == { 'voting_for_block': block.id, 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '1111111111' } serialized_vote = utils.serialize(vote_doc['vote']).encode() assert vote_doc['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify(serialized_vote, vote_doc['signature']) is True
def test_block_deserialization(self, b, alice): from bigchaindb.common.crypto import hash_data from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transaction = Transaction.create([alice.public_key], [([alice.public_key], 1)]) transaction.sign([alice.private_key]) timestamp = gen_timestamp() expected = Block([transaction], alice.public_key, timestamp) block = { 'timestamp': timestamp, 'transactions': [transaction.to_dict()], 'node_pubkey': alice.public_key, } block_body = { 'id': hash_data(serialize(block)), 'block': block, 'signature': None, } assert expected == Block.from_dict(block_body)
def test_block_invalid_signature(self, b): from bigchaindb.common.crypto import hash_data from bigchaindb.common.exceptions import InvalidSignature from bigchaindb.common.utils import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [([b.me], 1)])] timestamp = gen_timestamp() block = { 'timestamp': timestamp, 'transactions': [tx.to_dict() for tx in transactions], 'node_pubkey': b.me, 'voters': list(b.federation), } block_body = { 'id': hash_data(serialize(block)), 'block': block, 'signature': 'an invalid signature', } with raises(InvalidSignature): Block.from_dict(block_body).validate(b)
def test_invalid_block_voting(monkeypatch, b, user_pk, genesis_block): from bigchaindb.backend import query from bigchaindb.common import crypto, utils from bigchaindb.pipelines import vote inpipe = Pipe() outpipe = Pipe() monkeypatch.setattr('time.time', lambda: 1111111111) vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) block = dummy_block(b).to_dict() block['block']['id'] = 'this-is-not-a-valid-hash' inpipe.put(block) vote_pipeline.start() vote_out = outpipe.get() vote_pipeline.terminate() vote_rs = query.get_votes_by_block_id_and_voter(b.connection, block['id'], b.me) vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == { 'voting_for_block': block['id'], 'previous_block': genesis_block.id, 'is_block_valid': False, 'invalid_reason': None, 'timestamp': '1111111111' } serialized_vote = utils.serialize(vote_doc['vote']).encode() assert vote_doc['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify(serialized_vote, vote_doc['signature']) is True
def _to_str(value): return serialize(value)
def test_valid_block_voting_with_transfer_transactions(monkeypatch, b, genesis_block): from bigchaindb.backend import query from bigchaindb.common import crypto, utils from bigchaindb.models import Transaction from bigchaindb.pipelines import vote # create a `CREATE` transaction test_user_priv, test_user_pub = crypto.generate_key_pair() tx = Transaction.create([b.me], [([test_user_pub], 1)]) tx = tx.sign([b.me_private]) monkeypatch.setattr('time.time', lambda: 1000000000) block = b.create_block([tx]) b.write_block(block) # create a `TRANSFER` transaction test_user2_priv, test_user2_pub = crypto.generate_key_pair() tx2 = Transaction.transfer(tx.to_inputs(), [([test_user2_pub], 1)], asset_id=tx.id) tx2 = tx2.sign([test_user_priv]) monkeypatch.setattr('time.time', lambda: 2000000000) block2 = b.create_block([tx2]) b.write_block(block2) inpipe = Pipe() outpipe = Pipe() vote_pipeline = vote.create_pipeline() vote_pipeline.setup(indata=inpipe, outdata=outpipe) vote_pipeline.start() inpipe.put(block.to_dict()) time.sleep(1) inpipe.put(block2.to_dict()) vote_out = outpipe.get() vote2_out = outpipe.get() vote_pipeline.terminate() vote_rs = query.get_votes_by_block_id_and_voter(b.connection, block.id, b.me) vote_doc = vote_rs.next() assert vote_out['vote'] == vote_doc['vote'] assert vote_doc['vote'] == { 'voting_for_block': block.id, 'previous_block': genesis_block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '2000000000' } serialized_vote = utils.serialize(vote_doc['vote']).encode() assert vote_doc['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify(serialized_vote, vote_doc['signature']) is True vote2_rs = query.get_votes_by_block_id_and_voter(b.connection, block2.id, b.me) vote2_doc = vote2_rs.next() assert vote2_out['vote'] == vote2_doc['vote'] assert vote2_doc['vote'] == { 'voting_for_block': block2.id, 'previous_block': block.id, 'is_block_valid': True, 'invalid_reason': None, 'timestamp': '2000000000' } serialized_vote2 = utils.serialize(vote2_doc['vote']).encode() assert vote2_doc['node_pubkey'] == b.me assert crypto.PublicKey(b.me).verify(serialized_vote2, vote2_doc['signature']) is True
def to_str(self): return serialize(self.to_dict())
def write_block(connection, block_dict): return connection.run( r.table('bigchain') .insert(r.json(serialize(block_dict)), durability=WRITE_DURABILITY))