def test_generate_block(): signing_key, account_number = create_account() encoded_account_number = encode_verify_key(verify_key=account_number) transactions = [{ 'amount': 1, 'recipient': random_encoded_account_number(), }, { 'amount': 1, 'recipient': random_encoded_account_number(), }, { 'amount': 5, 'recipient': random_encoded_account_number(), }] block = generate_block(account_number=account_number, balance_lock=encoded_account_number, signing_key=signing_key, transactions=transactions) assert block['account_number'] == encoded_account_number assert block['message']['balance_key'] == encoded_account_number assert len(block['message']['txs']) == 3 assert len(block['signature']) == SIGNATURE_LENGTH # Verify that signature is valid and the message has not been modified verify_signature(message=sort_and_encode(block['message']), signature=block['signature'], verify_key=block['account_number'])
def verify_request_signature(*, request, signed_data_key): """ Verify the request signature signed_data - block or message """ node_identifier = request.data.get('node_identifier') signature = request.data.get('signature') signed_data = request.data.get(signed_data_key) for field in ['node_identifier', 'signature', signed_data_key]: if not request.data.get(field): return request, {ERROR: f'{field} required'} error = None try: verify_signature(message=sort_and_encode(signed_data), signature=signature, verify_key=node_identifier) except BadSignatureError as e: logger.exception(e) error = {ERROR: BAD_SIGNATURE} except Exception as e: logger.exception(e) error = {ERROR: UNKNOWN} return request, error
def validate(self, data): """ Validate signature Validate Tx recipients are unique Validate account_number (the sender) is not included as a Tx recipient """ account_number = data['account_number'] message = data['message'] txs = message['txs'] signature = data['signature'] verify_signature(message=sort_and_encode(message), signature=signature, verify_key=account_number) recipient_list = [tx['recipient'] for tx in txs] recipient_set = set(recipient_list) if len(recipient_list) != len(recipient_set): raise serializers.ValidationError('Tx recipients must be unique') if account_number in recipient_set: raise serializers.ValidationError( 'Block account_number not allowed as Tx recipient') validate_keys(self, data) return data
def send_signed_block(*, block, ip_address, port, protocol, url_path): """ Sign block and send to recipient """ signing_key = get_signing_key() node_identifier = get_verify_key(signing_key=signing_key) node_identifier = encode_verify_key(verify_key=node_identifier) message = sort_and_encode(block) signed_block = { 'block': block, 'node_identifier': node_identifier, 'signature': generate_signature(message=message, signing_key=signing_key) } node_address = format_address(ip_address=ip_address, port=port, protocol=protocol) url = f'{node_address}{url_path}' try: post(url=url, body=signed_block) except Exception as e: request_new_primary_validator() logger.exception(e)
def inner(request, *args, **kwargs): message = request.data.get('message') node_identifier = request.data.get('node_identifier') signature = request.data.get('signature') self_configuration = get_self_configuration( exception_class=RuntimeError) if node_identifier != self_configuration.node_identifier: return Response(status=status.HTTP_401_UNAUTHORIZED) try: verify_signature(message=sort_and_encode(message), signature=signature, verify_key=node_identifier) except BadSignatureError as e: logger.exception(e) return Response({ERROR: BAD_SIGNATURE}, status=status.HTTP_401_UNAUTHORIZED) except Exception as e: logger.exception(e) return Response({ERROR: UNKNOWN}, status=status.HTTP_401_UNAUTHORIZED) return func(request, *args, **kwargs)
def send_signed_block(*, block, ip_address, port, protocol, url_path): """ Sign block and send to recipient """ network_signing_key = get_environment_variable('NETWORK_SIGNING_KEY') signing_key = SigningKey(network_signing_key, encoder=HexEncoder) node_identifier = get_verify_key(signing_key=signing_key) node_identifier = encode_verify_key(verify_key=node_identifier) message = sort_and_encode(block) signed_block = { 'block': block, 'node_identifier': node_identifier, 'signature': generate_signature(message=message, signing_key=signing_key) } node_address = format_address(ip_address=ip_address, port=port, protocol=protocol) url = f'{node_address}{url_path}' try: post(url=url, body=signed_block) except Exception as e: logger.exception(e)
def populate_confirmation_block_queue(*, address, error_handler, initial_block_identifier): """ Fetch confirmation blocks from primary validator starting with initial_block_identifier Add all confirmation blocks to confirmation block queue """ block_identifier = initial_block_identifier results = get_confirmation_block_chain_segment( address=address, block_identifier=block_identifier) error = False while results and not error: confirmation_block = get_confirmation_block_from_results( block_identifier=block_identifier, results=results) while confirmation_block: message = confirmation_block['message'] try: verify_signature( message=sort_and_encode(message), signature=confirmation_block['signature'], verify_key=confirmation_block['node_identifier']) except BadSignatureError as e: error_handler(e) error = True break except Exception as e: error_handler(e) error = True break serializer = ConfirmationBlockSerializerCreate(data=message) if serializer.is_valid(): _bid = serializer.save() print(_bid) else: error_handler(serializer.errors) error = True break block_identifier = get_message_hash(message=message) confirmation_block = get_confirmation_block_from_results( block_identifier=block_identifier, results=results) if error: break results = get_confirmation_block_chain_segment( address=address, block_identifier=block_identifier)
def generate_signed_request(*, data, nid_signing_key): """Generate and return signed request""" node_identifier = get_verify_key(signing_key=nid_signing_key) signature = generate_signature( message=sort_and_encode(data), signing_key=nid_signing_key ) return { 'message': data, 'node_identifier': encode_verify_key(verify_key=node_identifier), 'signature': signature }
def signed_block(block_data, bank_signing_key): yield { 'block': block_data, 'node_identifier': encode_verify_key(verify_key=get_verify_key( signing_key=bank_signing_key, ), ), 'signature': generate_signature( message=sort_and_encode(block_data), signing_key=bank_signing_key, ), }
def is_block_valid(*, block): """ For given block verify: - signature - account balance exists - amount sent does not exceed account balance - balance key matches balance lock Return boolean indicating validity, senders account balance """ account_number = block.get('account_number') message = block.get('message') signature = block.get('signature') try: verify_signature(message=sort_and_encode(message), signature=signature, verify_key=account_number) except BadSignatureError: return False, None except Exception as e: capture_exception(e) logger.exception(e) return False, None account_balance = get_account_balance(account_number=account_number) account_balance_lock = get_account_balance_lock( account_number=account_number) if account_balance is None: logger.error(f'Account balance for {account_number} not found') return False, None total_amount_valid, error = is_total_amount_valid( block=block, account_balance=account_balance) if not total_amount_valid: logger.error(error) return False, None balance_key = message.get('balance_key') if balance_key != account_balance_lock: logger.error( f'Balance key of {balance_key} does not match balance lock of {account_balance_lock}' ) return False, None return True, account_balance
def validate(self, data): """Validate signature, unique Tx recipients, unique Tx fees and account_number not included as a Tx recipient""" account_number = data['account_number'] message = data['message'] txs = message['txs'] signature = data['signature'] verify_signature(message=sort_and_encode(message), signature=signature, verify_key=account_number) recipient_list = [tx['recipient'] for tx in txs] recipient_set = set(recipient_list) if len(recipient_list) != len(recipient_set): raise serializers.ValidationError('Tx recipients must be unique') if account_number in recipient_set: raise serializers.ValidationError( 'Block account_number not allowed as Tx recipient') bank_fee_exists = False primary_validator_fee_exists = False for tx in txs: fee = tx.get('fee', None) if fee is None: continue if fee == BANK: if bank_fee_exists: raise serializers.ValidationError( 'Multiple bank fees not allowed') else: bank_fee_exists = True if fee == PRIMARY_VALIDATOR: if primary_validator_fee_exists: raise serializers.ValidationError( 'Multiple primary validator fees not allowed') else: primary_validator_fee_exists = True validate_keys(self, data) return data
def generate_block(*, account_number, balance_lock, signing_key, transactions): """Generate block""" message = { 'balance_key': balance_lock, 'txs': sorted(transactions, key=itemgetter('recipient')) } signature = generate_signature(message=sort_and_encode(message), signing_key=signing_key) block = { 'account_number': encode_verify_key(verify_key=account_number), 'message': message, 'signature': signature } return block
def run(send_to_pv=False): """ Create block used for: - POST /bank_blocks - Bank > PV """ treasury_signing_key = read_signing_key_file( os.path.join(SIGNING_KEY_DIR, 'treasury')) account_number = get_verify_key(signing_key=treasury_signing_key) balance_lock = get_account_balance_lock( account_number=TREASURY_ACCOUNT_NUMBER, live_pv=True) transactions = [{ 'amount': BANK_TX_FEE, 'recipient': BANK_ACCOUNT_NUMBER, }, { 'amount': PV_TX_FEE, 'recipient': PV_ACCOUNT_NUMBER, }, { 'amount': 1.0, 'recipient': BUCKY_ACCOUNT_NUMBER, }] block = generate_block(account_number=account_number, balance_lock=balance_lock, signing_key=treasury_signing_key, transactions=transactions) bank_nid_sk = read_signing_key_file( os.path.join(SIGNING_KEY_DIR, 'bank_nid')) bank_nid = get_verify_key(signing_key=bank_nid_sk) bank_nid = encode_verify_key(verify_key=bank_nid) message = sort_and_encode(block) signed_block = { 'block': block, 'node_identifier': bank_nid, 'signature': generate_signature(message=message, signing_key=bank_nid_sk) } write_json(os.path.join(BLOCKS_DIR, 'bank-blocks-request.json'), signed_block) if send_to_pv: send_request_to_pv(signed_block)
def validate(self, data): """ Validate block signature Note: when building the block, message is pulled from 'initial_data' since 'data' has already been processed by the MessageSerializer converting all amounts to DecimalField (which are not JSON serializable) """ block = { 'account_number': data['account_number'], 'message': self.initial_data['message'], 'signature': data['signature'] } verify_signature( message=sort_and_encode(block['message']), signature=block['signature'], verify_key=block['account_number'] ) return block
def get_message_hash(*, message): """Return has of given message""" return sha3(sort_and_encode(message)).digest().hex()