def address_to_scriptpubkey(address): # Raise ValueError if we cannot identify the address. get_version(address) try: version = b58decode_check(address)[:1] except ValueError: witver, data = segwit_decode(address) return segwit_scriptpubkey(witver, data) if version == MAIN_PUBKEY_HASH or version == TEST_PUBKEY_HASH: return OP_DUP + OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash( address) + OP_EQUALVERIFY + OP_CHECKSIG elif version == MAIN_SCRIPT_HASH or version == TEST_SCRIPT_HASH: return OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash( address) + OP_EQUAL
def _validate_outputs(self): """Validates output addresses.""" if not self.outputs: raise EmptyOutputs() # Sanity check: If spending from main-/testnet, then all output addresses must also be for main-/testnet. for dest in self.outputs.keys(): if dest[0] not in supported_out_prefixes: raise NotSupportedOutputAddress() try: vs = get_version(dest) except ValueError as err: raise InvalidOutputAddress(dest, str(err)) else: if vs and vs != self.requested_net: raise NetworkMismatchOutputAddress( self.source_address, self.source_net, dest, vs, self.requested_net, ) try: self.outputs[dest] = int(self.outputs[dest]) if self.outputs[dest] < DUST_THRESHOLD: raise ValueError( "Output amount is lower that dust threshold.") except ValueError as err: raise InvalidOutputAmount(self.outputs[dest], DUST_THRESHOLD, str(err))
def sanitize_tx_data(unspents, outputs, fee, leftover, combine=True, message=None, compressed=True, min_change=0, version='main'): """ sanitize_tx_data() """ outputs = outputs.copy() for i, output in enumerate(outputs): dest, amount, currency = output outputs[i] = (dest, currency_to_satoshi_cached(amount, currency)) if not unspents: raise ValueError('Transactions must have at least one unspent.') # Temporary storage so all outputs precede messages. messages = [] if message: message_chunks = chunk_data(message.encode('utf-8'), MESSAGE_LIMIT) for message in message_chunks: messages.append((message, 0)) # Include return address in output count. # Calculate output size as a list (including return address). output_size = [len(address_to_scriptpubkey(o[0])) + 9 for o in outputs] output_size.append(len(messages) * (MESSAGE_LIMIT + 9)) output_size.append(len(address_to_scriptpubkey(leftover)) + 9) sum_outputs = sum(out[1] for out in outputs) # Use Branch-and-Bound for coin selection: unspents[:], remaining = select_coins(sum_outputs, fee, output_size, min_change=min_change, consolidate=combine, unspents=unspents) if remaining > 0: outputs.append((leftover, remaining)) # Sanity check: If spending from main-/testnet, then all output addresses must also be for main-/testnet. for output in outputs: dest, amount = output vs = get_version(dest) if vs and vs != version: raise ValueError('Cannot send to ' + vs + 'net address when ' 'spending from a ' + version + 'net address.') outputs.extend(messages) return unspents, outputs
def _validate_source_address(self): """Validates source_address attr.""" if not self.source_address: raise EmptySourceAddress() try: self.source_net = get_version(self.source_address) except ValueError as err: raise InvalidSourceAddress(self.source_address, str(err)) else: if self.source_net != self.requested_net: raise NetworkMismatchSourceAddress(self.source_address, self.source_net, self.requested_net) if self.source_address[0] not in supported_in_prefixes: raise NotSupportedSourceAddress()
def get_withdrawal(): address = request.args.get('address', 0, type=str) amount = request.args.get('amount', 0, type=float) withdrawal = 'None' try: check = get_version(address) if check == 'test': if type(amount) is not float: withdrawal = 'error-amount' else: user = current_user balances = Balance.query.filter_by(user_id=user.id).first() latest_transfers = Transfer.query.filter_by(user_id=user.id).all() db_txs = [] for transfer in latest_transfers: db_txs.append(transfer.tx_id) new_balance_btc = balances.balance_btc - amount key = PrivateKeyTestnet('cPvDDZ8XREaxWnZNaZ2V8FeUrZYvwHk8kAMHVaFbFte1QZUuuTur') address_balance = key.get_balance() txid = key.send([(address, amount, 'btc')]) if txid not in db_txs: db.session.delete(balances) db.session.commit() transfer = Transfer(tx_type='withdrawal', amount=amount, currency='BTC', tx_id=txid, user_id=user.id) new_balances = Balance(balance_btc=new_balance_btc, balance_usd=balances.balance_usd, user_id=user.id) db.session.add_all([transfer, new_balances]) db.session.commit() print(("\nTransfer: {}\nNew balances: {}\n").format(transfer, new_balances)) withdrawal = txid else: withdrawal = 'error-main' except ValueError: withdrawal = 'error-address' if withdrawal in ['error-main', 'error-address']: if type(amount) is not float: withdrawal = 'error-both' return jsonify(result=withdrawal)
def test_invalid(self): with pytest.raises(ValueError): get_version('dg2dNAjuezub6iJVPNML5pW5ZQvtA9ocL')
def test_testnet(self): assert get_version(BITCOIN_ADDRESS_TEST) == 'test' assert get_version(BITCOIN_ADDRESS_TEST_COMPRESSED) == 'test'
def test_mainnet(self): assert get_version(BITCOIN_ADDRESS) == 'main' assert get_version(BITCOIN_ADDRESS_COMPRESSED) == 'main'
def sanitize_tx_data(unspents, outputs, fee, leftover, combine=True, message=None, compressed=True, version='main'): """ sanitize_tx_data() fee is in satoshis per byte. """ outputs = outputs.copy() for i, output in enumerate(outputs): dest, amount, currency = output # Sanity check: If spending from main-/testnet, then all output addresses must also be for main-/testnet. if amount: # ``dest`` could be a text to be stored in the blockchain; but only if ``amount`` is exactly zero. vs = get_version(dest) if vs and vs != version: raise ValueError('Cannot send to ' + vs + 'net address when spending from a ' + version + 'net address.') outputs[i] = (dest, currency_to_satoshi_cached(amount, currency)) if not unspents: raise ValueError('Transactions must have at least one unspent.') # Temporary storage so all outputs precede messages. messages = [] if message: message_chunks = chunk_data(message.encode('utf-8'), MESSAGE_LIMIT) for message in message_chunks: messages.append((message, 0)) # Include return address in fee estimate. total_in = 0 num_outputs = len(outputs) + len(messages) + 1 sum_outputs = sum(out[1] for out in outputs) if combine: # calculated_fee is in total satoshis. calculated_fee = estimate_tx_fee(len(unspents), num_outputs, fee, compressed) total_out = sum_outputs + calculated_fee unspents = unspents.copy() total_in += sum(unspent.amount for unspent in unspents) else: unspents = sorted(unspents, key=lambda x: x.amount) index = 0 for index, unspent in enumerate(unspents): total_in += unspent.amount calculated_fee = estimate_tx_fee(len(unspents[:index + 1]), num_outputs, fee, compressed) total_out = sum_outputs + calculated_fee if total_in >= total_out: break unspents[:] = unspents[:index + 1] remaining = total_in - total_out if remaining > 0: outputs.append((leftover, remaining)) elif remaining < 0: raise InsufficientFunds('Balance {} is less than {} (including ' 'fee).'.format(total_in, total_out)) outputs.extend(messages) return unspents, outputs
def test_testnet_pay2sh(self): assert get_version(BITCOIN_ADDRESS_TEST_PAY2SH) == 'test'
def test_mainnet_pay2sh(self): assert get_version(BITCOIN_ADDRESS_PAY2SH) == 'main'
def test_testnet_pay2sh(self): with pytest.raises(ValueError): get_version(BITCOIN_ADDRESS_TEST_PAY2SH)
def is_valid_address(bitcoin_address: str) -> bool: try: return get_version(bitcoin_address) == settings.btc_network except ValueError: return False