def __init__(self, private={}, public={}, private_seeds={}): # It is possible to distinguish between private and public seeds # based on the string content. Consider modifying this function # to take merely one dict of seeds. Trees should still be stored # separately. self.trees = {} self.private_trees = {} self.public_trees = {} def treegen(value, entropy=False): if entropy: # this method also takes a netcode parameter, but we don't care # what network pycoin thinks this node is, because we only use it # for key derivation. return BIP32Node.from_master_secret(unhexlify(value)) else: # this method will infer a network from the header bytes. We # don't care right now for the same reason as above, but we will # if Gem's API stops returning 'xpub' as the pubkey header bytes # because if pycoin doesn't recognize a header it will error. return BIP32Node.from_hwif(value) for name, seed in iteritems(private): tree = treegen(seed) self.private_trees[name] = self.trees[name] = tree for name, seed in iteritems(private_seeds): tree = treegen(seed, True) self.private_trees[name] = self.trees[name] = tree for name, seed in iteritems(public): tree = BIP32Node.from_hwif(seed) self.public_trees[name] = self.trees[name] = tree
def bip32node_for_slug(self, slug): c = self._exec_sql("select id, as_text from BIP32Key where slug=?", slug) r = c.fetchone() if r is None: return None bip32_node = BIP32Node.from_hwif(r[1]) bip32_node.id = r[0] return bip32_node
def create_new_address(self): n = n_addresses.incr() bip32node = BIP32Node.from_hwif(xpub.get()) subkey = bip32node.subkey(0).subkey(n) # match electrum path new_address = subkey.address() addr_to_uid[new_address] = self.uid uid_to_addr[self.uid] = new_address all_addresses.add(new_address) return True
def treegen(value, entropy=False): if entropy: # this method also takes a netcode parameter, but we don't care # what network pycoin thinks this node is, because we only use it # for key derivation. return BIP32Node.from_master_secret(unhexlify(value)) else: # this method will infer a network from the header bytes. We # don't care right now for the same reason as above, but we will # if Gem's API stops returning 'xpub' as the pubkey header bytes # because if pycoin doesn't recognize a header it will error. return BIP32Node.from_hwif(value)
def evaluate_key_input(self, txt): self.ext_key_widget.clear() self.subkey_widget.clear() txt = str(txt) if not txt: return self.invalid_key_label.setVisible(False) # Variable substitution. elif txt.startswith('$'): return try: key = BIP32Node.from_hwif(txt) except Exception as e: self.invalid_key_label.setVisible(True) else: self.invalid_key_label.setVisible(False) self.ext_key_widget.set_key(key) self.derive_child() finally: self.ext_key_widget.mapper.setCurrentIndex(0)
def createTransaction(self, address, amount, keychain, fee="standard", confirms=0): unspents_result = yield self.unspents() spendables = [] p2sh = [] chain_paths = [] # Strip leading / keychain_path = keychain['path'][1:] for unspent in unspents_result["unspents"]: if unspent["confirmations"] < confirms: continue p2sh.append(h2b(unspent["redeemScript"])) spendable = Spendable(unspent["value"], h2b(unspent["script"]), h2b_rev(unspent["tx_hash"]), unspent["tx_output_n"]) spendables.append(spendable) chain_paths.append(keychain_path + unspent['chainPath']) p2sh_lookup = build_p2sh_lookup(p2sh) address_result = yield self.createAddress(1) change = address_result["address"] tx = tx_utils.create_tx(spendables, [(address, amount), change], fee) # address_keys = [BIP32Node.from_hwif(keychain["xprv"]).subkey_for_path("0/0/0/0"), # BIP32Node.from_hwif(keychain["xprv"]).subkey_for_path(address_result['path'])] spendable_keys = [BIP32Node.from_hwif(keychain["xprv"]).subkey_for_path(path) for path in chain_paths] # all_keys = address_keys + spendable_keys hash160_lookup = build_hash160_lookup([key.secret_exponent() for key in spendable_keys]) pprint(tx) tx.sign(hash160_lookup=hash160_lookup, p2sh_lookup=p2sh_lookup) pprint(tx) returnValue({'tx': tx.as_hex(), 'fee': tx.fee()})
def derive_child(self): strkey = str(self.key_edit.text()) subkey_path = str(self.subkey_path.text()) subkey_path = subkey_path.replace("'", "H").replace("h", "H") # Don't clear if the user is typing a path. if subkey_path.endswith('/'): return self.subkey_widget.clear() if not strkey or not subkey_path: return try: ext_key = BIP32Node.from_hwif(strkey) result = ext_key.subkey_for_path(subkey_path) except Exception as e: self.invalid_subkey_label.setText(str(e)) self.invalid_subkey_label.setVisible(True) else: self.invalid_subkey_label.setVisible(False) self.subkey_widget.set_key(result) finally: self.subkey_widget.mapper.setCurrentIndex(0)
def main(): if len(sys.argv) != 2: print("usage: %s bip32_key_file" % sys.argv[0]) sys.exit(-1) with open(sys.argv[1], "r") as f: hwif = f.readline().strip() # turn the bip32 text into a BIP32Node object BIP32_KEY = BIP32Node.from_hwif(hwif) # create three sec_keys (these are public keys, streamed using the SEC format) SEC_0 = BIP32_KEY.subkey_for_path("0/0/0").sec() SEC_1 = BIP32_KEY.subkey_for_path("0/1/0").sec() SEC_2 = BIP32_KEY.subkey_for_path("0/2/0").sec() public_key_sec_list = [SEC_0, SEC_1, SEC_2] # create the 2-of-3 multisig script # any 2 signatures can release the funds pay_to_multisig_script = ScriptMultisig(2, public_key_sec_list).script() # create a "2-of-3" multisig address_for_multisig the_address = address_for_pay_to_script(pay_to_multisig_script) print("Here is your pay 2-of-3 address: %s" % the_address) print("Here is the pay 2-of-3 script: %s" % b2h(pay_to_multisig_script)) print("The hex script should go into p2sh_lookup.hex") base_dir = os.path.dirname(sys.argv[1]) print("The three WIFs are written into %s as wif0, wif1 and wif2" % base_dir) for i in range(3): wif = BIP32_KEY.subkey_for_path("0/%d/0" % i).wif() with open(os.path.join(base_dir, "wif%d" % i), "w") as f: f.write(wif)
def handle(self, *args, **options): # Maintain global metadata about the orders being settled global_settled_order_count = 0 now = datetime.datetime.now(datetime.timezone.utc) # Add a unique id allowing us to trace through this pass at settling identifiers = { 'trace_id': reporting.utils.generate_trace_id(), } errors = [] # @TODO: grab a lock to ensure only one process can settle at a time # https://github.com/ .. /issues/134 with connection.cursor() as cursor: cursor.execute('DROP TABLE IF EXISTS settle_temptrades') cursor.execute(''' CREATE TABLE settle_temptrades ( id BIGSERIAL PRIMARY KEY NOT NULL, currency CHARACTER VARYING(16), volume BIGINT, fee BIGINT, wallet_id UUID REFERENCES wallet_wallet (id), trade_id BIGINT REFERENCES trade_trade (id), side BOOLEAN );''') # Step 1: # Loop through each coin type, settle money out: for coin_type in settings.COINS.keys(): print("Settling %s (%s)..." % (settings.COINS[coin_type]['name'], coin_type)) coin_details = { 'type': coin_type, 'name': settings.COINS[coin_type]['name'], } reporting.utils.audit(message="initiating settling", details={ 'identifiers': identifiers, 'coin_details': coin_details, }) # Start by finding trades where the base currency for the sell # order side has an appropriate base currency. # @TODO: do we need to review sell_order_settled_in trades too? trades_out_sells = trade.models.Trade.objects.filter(sell_order__base_currency=coin_type) \ .filter(models.Q(sell_order_settled_out=trade.models.SETTLED_NONE) | models.Q(sell_order_settled_out=trade.models.SETTLED_VALID)) \ .order_by('created') for unsettled_trade in trades_out_sells: global_settled_order_count += 1 from_user = unsettled_trade.sell_order.wallet.user.get() order_fee = order.utils.get_fee( order=unsettled_trade.sell_order, volume=unsettled_trade.base_volume) # Collect the temporary data we build for settling, and # write it to the audit logs. to_settle = [] # Record the money-out (sell) from this trade new_settle = TempTrades( currency=coin_type, volume=unsettled_trade.base_volume * -1, fee=0, # no fee for money-out side=False, # sell order wallet=unsettled_trade.sell_order.wallet, trade=unsettled_trade, ) new_settle.save() to_settle.append(model_to_dict(new_settle)) # Record the money-in (buy) from this trade to_user = unsettled_trade.buy_order.wallet.user.get() money_in_wallet = wallet.models.Wallet.objects.get( user=to_user, currencycode=coin_type, ) new_settle = TempTrades( currency=coin_type, volume=unsettled_trade.base_volume - order_fee, fee=order_fee, side=True, # buy order wallet=money_in_wallet, trade=unsettled_trade, ) new_settle.save() to_settle.append(model_to_dict(new_settle)) # Be sure order and trade have sane timestamps errors = self.validate_timestamps( identifiers=identifiers, now=now, unsettled_trade=unsettled_trade, errors=errors) from_details = { 'user_id': from_user.id, 'wallet_id': unsettled_trade.sell_order.wallet.id, 'order_id': unsettled_trade.sell_order.id, 'trade_id': unsettled_trade.id, 'order_type': 'sell', 'cryptopair': unsettled_trade.cryptopair, 'pair_side': 'base', 'volume': unsettled_trade.base_volume, 'fee': order_fee, 'order_created': unsettled_trade.sell_order.created, 'trade_created': unsettled_trade.created, 'now': now, } to_details = { 'user_id': to_user.id, 'wallet_id': unsettled_trade.buy_order.wallet.id, 'order_id': unsettled_trade.buy_order.id, 'trade_id': unsettled_trade.id, 'order_type': 'buy', 'cryptopair': unsettled_trade.cryptopair, 'pair_side': 'base', 'volume': unsettled_trade.base_volume - order_fee, 'order_created': unsettled_trade.buy_order.created, 'trade_created': unsettled_trade.created, 'now': now, } reporting.utils.audit(message="settling details", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'from_details': from_details, 'to_details': to_details, 'to_settle': to_settle, 'errors': errors, }) # Next, find trades where the base currency for the buy order side # has an appropriate quote currency. # @TODO do we need to review buy_order_settled_in trades too? trades_out_buys = trade.models.Trade.objects.filter(buy_order__quote_currency=coin_type) \ .filter(models.Q(buy_order_settled_out=trade.models.SETTLED_NONE) | models.Q(buy_order_settled_out=trade.models.SETTLED_VALID)) \ .order_by('created') for unsettled_trade in trades_out_buys: global_settled_order_count += 1 from_user = unsettled_trade.buy_order.wallet.user.get() order_fee = order.utils.get_fee( order=unsettled_trade.buy_order, volume=unsettled_trade.volume, ) # Collect the temporary data we build for settling, and # write it to the audit logs. to_settle = [] # Record the money-out (buy) from this trade new_settle = TempTrades( currency=coin_type, volume=unsettled_trade.volume * -1, fee=0, # no fee for money-out side=True, # buy order wallet=unsettled_trade.buy_order.wallet, trade=unsettled_trade, ) new_settle.save() to_settle.append(model_to_dict(new_settle)) # Record the money-in (sell) from this trade to_user = unsettled_trade.sell_order.wallet.user.get() money_in_wallet = wallet.models.Wallet.objects.get( user=to_user, currencycode=coin_type, ) new_settle = TempTrades( currency=coin_type, volume=unsettled_trade.volume - order_fee, fee=order_fee, side=False, # sell order wallet=money_in_wallet, trade=unsettled_trade, ) new_settle.save() to_settle.append(model_to_dict(new_settle)) # Be sure order and trade have sane timestamps errors = self.validate_timestamps( identifiers=identifiers, now=now, unsettled_trade=unsettled_trade, errors=errors) from_details = { 'user_id': from_user.id, 'wallet_id': unsettled_trade.buy_order.wallet.id, 'order_id': unsettled_trade.buy_order.id, 'trade_id': unsettled_trade.id, 'order_type': 'buy', 'cryptopair': unsettled_trade.cryptopair, 'pair_side': 'quote', 'volume': unsettled_trade.volume, 'fee': order_fee, 'order_created': unsettled_trade.buy_order.created, 'trade_created': unsettled_trade.created, 'now': now, } to_details = { 'user_id': to_user.id, 'wallet_id': unsettled_trade.sell_order.wallet.id, 'order_id': unsettled_trade.sell_order.id, 'trade_id': unsettled_trade.id, 'order_type': 'sell', 'pair_side': 'quote', 'volume': unsettled_trade.volume - order_fee, } reporting.utils.audit(message="settling details", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'from_details': from_details, 'to_details': to_details, 'to_settle': to_settle, 'errors': errors, }) tmp_trades = TempTrades.objects.values('wallet') \ .filter(currency=coin_type) \ .annotate(total_volume=models.Sum('volume'), total_fee=models.Sum('fee')) for tmp_trade in tmp_trades: user_wallet = wallet.models.Wallet.objects.get( id=tmp_trade['wallet']) balances = wallet.utils.get_balances( identifiers=identifiers, user_wallet=user_wallet, ) # Confirm there's sufficient funds for this trade. if tmp_trade['total_volume'] < 0: balance_in = 0 balance_out = tmp_trade['total_volume'] * -1 #print("validating transfer of {} {} out of wallet {}".format(balance_out, user_wallet.currencycode, user_wallet.id)) if balances['blockchain'] < balance_out: trades = self.get_trades_from_wallet( user_wallet=user_wallet) for error_trade in trades: self.mark_as_error( identifiers=identifiers, unsettled_trade=error_trade.trade, order_side=error_trade.side, ) errors.append( "%d balance insufficient for %d trades" % (balances['blockchain'], balance_out)) print("ERROR: insufficient funds") #if tmp_trade['total_fee'] > 0: #print("validating transfer of {} {} fee to exchange".format(tmp_trade['total_fee'], user_wallet.currencycode)) else: balance_out = 0 balance_in = tmp_trade['total_volume'] #print("validating transfer of {} {} into wallet {}".format(balance_in, user_wallet.currencycode, user_wallet.id)) #print("validating transfer of {} {} fee to exchange".format(tmp_trade['total_fee'], user_wallet.currencycode)) coin_details = { 'type': user_wallet.currencycode, 'name': settings.COINS[user_wallet.currencycode]['name'], } reporting.utils.audit( message="settling balance confirmation", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'aggregate_settle': tmp_trade, 'funds_out': { 'currency_code': user_wallet.currencycode, 'volume': balance_out, }, 'funds_in': { 'currency_code': user_wallet.currencycode, 'volume': balance_in, }, 'balances': balances, 'errors': errors, }) # Auditing completed, mark trades as pending indicating we're ready # to actually settle. Any trades that have previously been marked # with an error will stay as an error. all_trades_to_settle = TempTrades.objects.filter() for trade_to_settle in all_trades_to_settle: # This only succeeds if the trade doesn't have an error if trade_to_settle.volume > 0: order_direction = 'in' else: order_direction = 'out' self.mark_as_valid(identifiers=identifiers, unsettled_trade=trade_to_settle.trade, order_side=trade_to_settle.side, order_direction=order_direction) # Be sure there are no trades in an ERROR state. error_trades = trade.models.Trade.objects.filter( models.Q(buy_order_settled_in=trade.models.SETTLED_ERROR) | models.Q(buy_order_settled_out=trade.models.SETTLED_ERROR) | models.Q(sell_order_settled_in=trade.models.SETTLED_ERROR) | models.Q(sell_order_settled_out=trade.models.SETTLED_ERROR)) error_count = 0 for error_trade in error_trades: error_count += 1 if error_count > 0: print("ERROR: aborting settling due to errors (%d)".format( error_count)) return (-1) else: print("no errors detected: creating blockchain transactions.") # Finally, create the actual blockchain transactions for coin_type in settings.COINS.keys(): trades_to_settle = TempTrades.objects \ .filter(currency=coin_type) if len(trades_to_settle): print( "\nGenerating {} transaction(s)...".format(coin_type)) # @TODO: keep track of transaction size, split into multiple # transactions if necessary. validation = { 'exchange': 0, } audit_validation = { 'exchange': 0, } total = 0 for trade_to_settle in trades_to_settle: if trade_to_settle.wallet.id in validation: validation[trade_to_settle.wallet. id] += trade_to_settle.volume audit_validation[trade_to_settle.wallet.id. hex] += trade_to_settle.volume else: validation[trade_to_settle.wallet. id] = trade_to_settle.volume audit_validation[trade_to_settle.wallet.id. hex] = trade_to_settle.volume validation['exchange'] += trade_to_settle.fee audit_validation['exchange'] += trade_to_settle.fee total += trade_to_settle.volume + trade_to_settle.fee # funds in plus funds out must be zero assert (total == 0) reporting.utils.audit( message="settling trades balance validation", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'validation': audit_validation, 'validation_total': total, 'errors': errors, }) # The funds going into blockchain wallets: vin = {} # The funds coming out of blockchain wallets: vout = [] addresses_with_unspent = {} funds_in = 0 funds_out = 0 #pprint(validation) for wallet_id in validation: # @TODO: use a valid address if wallet_id == 'exchange': exchange_address = address.utils.get_new_exchange_address( self, currencycode=coin_type) vin[exchange_address] = validation[wallet_id] continue user_wallet = wallet.models.Wallet.objects.get( id=wallet_id) # Handle funds-in if validation[wallet_id] > 0: # Generate a new address from this user's wallet new_address, index = address.utils.get_new_address( user_wallet=user_wallet, is_change=False) # @TODO: make it configurable which address gets used -- for now, we use p2pkh vin[new_address['p2pkh']] = validation[wallet_id] reporting.utils.audit( message="settling funds in new address", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'wallet_id': wallet_id, 'wallet_funds_in': validation[wallet_id], 'address': new_address['p2pkh'], }) else: value = validation[wallet_id] * -1 unspent, addresses, subtotal, success = wallet.utils.get_unspent_equal_or_greater( user_wallet, value) if (success == False): # @TODO # Something has gone terribly wrong: we already validated we had sufficient funds print("ERROR: ALERT ALERT") return -1 funds_out += subtotal # We'll use this when we sign the transaction addresses_with_unspent[wallet_id] = addresses reporting.utils.audit( message="settling funds out loading unspent", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'wallet_id': wallet_id, 'wallet_funds_out': value, 'unspent': { 'vout': unspent, 'addresses': list(addresses), 'value': subtotal, } }) # Assemble the vout array for detail in unspent: vout.append(detail) # If the unspent has more funds than needed, send the change back to the user's wallet if subtotal > value: new_address, index = address.utils.get_new_address( user_wallet=user_wallet, is_change=True) vin[new_address['p2pkh']] = subtotal - value #print(" + CHANGE: {}".format(subtotal - value)) reporting.utils.audit( message="settling returning change to user", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'wallet_id': wallet_id, 'wallet_id': wallet_id, 'wallet_funds_out': value, 'unspent': { 'vout': unspent, 'addresses': list(addresses), 'value': subtotal, }, 'change': { 'address': new_address['p2pkh'], 'value': subtotal - value, } }) # Validate our vin and vout for receive_address in vin: assert (vin[receive_address] > 0) funds_in += vin[receive_address] assert (funds_in == funds_out) #print(" o vin: {}".format(vin)) #print(" o vout: {}".format(vout)) # @TODO: exchange configuration for how quickly we settle # @TODO: exchange configuratoin for how we settle fee, valid = wallet.utils.calculate_fee( wallet=user_wallet, vout=vout, vin=vin, number_of_blocks=18, estimate_mode='CONSERVATIVE') reporting.utils.audit(message="settling calculating fees", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'funds_in': funds_in, 'funds_out': funds_out, 'vin': vin, 'vout': vout, 'exchange_fee': validation['exchange'], 'network_fee': fee, 'errors': errors, }) if fee > validation['exchange']: print(" ! ERROR: Unable to settle {}, our fee of {} is less than network fee of {} ... skipping" \ .format(coin_type, validation['exchange'], fee)) # @TODO don't record this as settled, next time we try # to settle it should still need to be settled reporting.utils.audit( message="settling insufficient exchange fee", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'funds_in': funds_in, 'funds_out': funds_out, 'vin': vin, 'vout': vout, 'exchange_fee': validation['exchange'], 'network_fee': fee, 'errors': errors, }) continue # Subtract network fee from our profits vin[exchange_address] -= fee #print(" - subtracting network fee of {}, leaving {} for exchange fee".format(fee, vin[exchange_address])) # Create actual transaction final_vin = {} for to_address in vin: final_vin[ to_address] = wallet.utils.convert_to_decimal( vin[to_address]) #print("final_vin: {}".format(final_vin)) reporting.utils.audit( message="settling finalizing transaction", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'final_vin': final_vin, 'vout': vout, 'final_exchange_fee': validation['exchange'], 'network_fee': fee, 'errors': errors, }) raw_tx = wallet.utils.create_raw_transaction( currencycode=coin_type, input=vout, output=final_vin) #print("raw_tx: {}".format(raw_tx)) reporting.utils.audit(message="settling raw transaction", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'vin': final_vin, 'vout': vout, 'exchange_fee': validation['exchange'], 'network_fee': fee, 'raw_transaction': raw_tx, 'errors': errors, }) # @TODO Sign the raw transaction private_keys = [] # Load wallet's private key, effectively unlocking it #print("addresses_with_unspent: {}".format(addresses_with_unspent)) for wallet_id in validation: # exchange only receives money, skip if wallet_id == 'exchange': continue # Only get private keys if the user is sending funds if validation[wallet_id] < 0: # @TODO use remote secrets database for storing private keys user_wallet = wallet.models.Wallet.objects.get( id=wallet_id) unlocked_account = BIP32Node.from_hwif( user_wallet.private_key) # Get WIF for all addresses we're sending from for source_address in addresses_with_unspent[ wallet_id]: #print("to_address: %s" % to_address) address_object = Address.objects.get( wallet=wallet_id, p2pkh=source_address) #print("source_address({}) index({}) is_change({})".format(source_address, address_object.index, address_object.is_change)) if address_object.is_change: is_change = 1 else: is_change = 0 unlocked_address = unlocked_account.subkey_for_path( "%d/%s" % (is_change, address_object.index)) private_keys.append(unlocked_address.wif()) #print(private_keys) if coin_type in ['BTC', 'XTN']: # Starting with 0.17, bitcoind replaces the old sign RPC with a new one signed_tx = wallet.utils.sign_raw_transaction_with_key( currencycode=coin_type, raw_tx=raw_tx, private_keys=private_keys) else: signed_tx = wallet.utils.sign_raw_transaction( currencycode=coin_type, raw_tx=raw_tx, output=[], private_keys=private_keys) #print("signed_tx: {}".format(signed_tx)) reporting.utils.audit( message="settling signed transaction", details={ 'identifiers': identifiers, 'coin_details': coin_details, 'vin': final_vin, 'vout': vout, 'exchange_fee': validation['exchange'], 'network_fee': fee, 'raw_transaction': raw_tx, 'signed_transaction': signed_tx, 'errors': errors, }) try: if signed_tx['complete'] is False: print("ERROR: failed to sign transaction") print(signed_tx) else: # @TODO Send the transaction to the blockchain: # - submit tx to blockchain, audit resulting txid # Update database: these trades are now pending inclusion on # the blockchain. for trade_to_settle in trades_to_settle: if trade_to_settle.volume > 0: order_direction = 'in' else: order_direction = 'out' self.mark_as_pending( identifiers=identifiers, unsettled_trade=trade_to_settle.trade, order_side=trade_to_settle.side, order_direction=order_direction) except: print( "ERROR: failed to sign transaction - no response from RPC call" ) cursor.execute('DROP TABLE settle_temptrades') self.stdout.write( self.style.SUCCESS('Successfully settled %d orders' % global_settled_order_count)) reporting.utils.audit(message="completed settling all coins", details={ 'identifiers': identifiers, 'global_settled_order_count': global_settled_order_count, })
def coerce_base58(v): key = BIP32Node.from_hwif(v) return key
def get_wallet_addresses(currencycode, mnemonic_seed, salt): m = Mnemonic('english') salted_mnemonic = m.to_seed(mnemonic_seed, passphrase=salt) try: path = [ "44H", # purpose (bip44) "%sH" % settings.COINS[currencycode]['bip44_index'], # coin type "%sH" % settings.COINS[currencycode] ['account_index'], # support multiple testnets ] except Exception as e: print("Unexpected error, invalid currencycode?: {}".format(e)) return None, None root_wallet = BIP32Node.from_master_secret(master_secret=salted_mnemonic, netcode=currencycode) coin_account_key = root_wallet.subkey_for_path("/".join(path)) coin_account_private_key = coin_account_key.wallet_key(as_private=True) external_addresses = [] index = 0 addresses_since_found_on_blockchain = 0 look_for_address = True while look_for_address: coin_wallet = BIP32Node.from_hwif(coin_account_private_key) subkey = coin_wallet.subkey_for_path("0/%d" % index) new_address = subkey.bitcoin_address() external_addresses.append(new_address) if blockchain.utils.is_address_on_blockchain( currencycode=currencycode, address_to_check=new_address, confirmations=1): addresses_since_found_on_blockchain = 0 else: addresses_since_found_on_blockchain += 1 index += 1 if addresses_since_found_on_blockchain >= 20: look_for_address = False change_addresses = [] index = 0 addresses_since_found_on_blockchain = 0 look_for_address = True while look_for_address: coin_wallet = BIP32Node.from_hwif(coin_account_private_key) subkey = coin_wallet.subkey_for_path("1/%d" % index) new_address = subkey.bitcoin_address() change_addresses.append(new_address) if blockchain.utils.is_address_on_blockchain( currencycode=currencycode, address_to_check=new_address, confirmations=1): addresses_since_found_on_blockchain = 0 else: addresses_since_found_on_blockchain += 1 index += 1 if addresses_since_found_on_blockchain >= 20: look_for_address = False return external_addresses, change_addresses
import sys import subprocess from pycoin.key import Key from pycoin.key.electrum import ElectrumWallet from pycoin.key.validate import netcode_and_type_for_data, netcode_and_type_for_text, is_address_valid, is_wif_valid, is_public_bip32_valid from pycoin.key.BIP32Node import BIP32Node mpk = sys.argv[1] index = int(sys.argv[2]) #print(is_public_bip32_valid(MPK)) wallet = BIP32Node.from_hwif(mpk) key = wallet.subkey(0) subkey = key.subkey(index) calculated_address = subkey.address() print("%s" % calculated_address) sys.exit(calculated_address)
def send(self, wallet_id, passcode, address, amount, message='', fee=10000): """ Send bitcoins to address :param wallet_id: bitgo wallet id :param address: bitcoin address :param amount: btc amount in satoshis :return: boolean """ wallet = self.get_wallet(wallet_id) if not wallet['spendingAccount']: raise NotSpendableWallet() if not wallet['isActive']: raise NotActiveWallet() if amount < 10000: raise Exception('amount to small') if wallet['confirmedBalance'] < amount: raise NotEnoughFunds('Not enough funds: balance %s amount %s' % (wallet['confirmedBalance'], amount) ) change_address = self.create_address(wallet_id, chain=1) usableKeychain = False spendables = [] chain_paths = [] p2sh = [] payables = [(address, amount)] keychain_path = "" for keychain in wallet['private']['keychains']: keychain_path = keychain['path'][1:] keychain = self.get_keychain(keychain['xpub']) if 'encryptedXprv' not in keychain: continue usableKeychain = True break if not usableKeychain: raise BitGoError("didn't found a spendable keychain") data = json.loads(keychain['encryptedXprv']) #add base64 paddings for k in ['iv', 'salt', 'ct']: data[k] = data[k] + "==" cipher = sjcl.SJCL() xprv = cipher.decrypt(data, passcode) unspents = self.get_unspents(wallet_id) total_value = 0 for d in unspents['unspents'][::-1]: path = keychain_path + d['chainPath'] chain_paths.append(path) p2sh.append(h2b(d["redeemScript"])) spendables.append(Spendable(d["value"], h2b(d["script"]), h2b_rev(d["tx_hash"]), d["tx_output_n"])) total_value += d['value'] if total_value > amount: break if total_value > (amount + fee): #add a change address #TODO: create address payables.append(change_address) p2sh_lookup = build_p2sh_lookup(p2sh) spendable_keys = [] priv_key = BIP32Node.from_hwif(xprv) spendable_keys = [priv_key.subkey_for_path(path) for path in chain_paths] hash160_lookup = build_hash160_lookup([key.secret_exponent() for key in spendable_keys]) tx = create_tx(spendables, payables) tx.sign(hash160_lookup=hash160_lookup, p2sh_lookup=p2sh_lookup) r = requests.post(self.url + '/tx/send', { 'tx': tx.as_hex(), 'message': message }, headers={ 'Authorization': 'Bearer %s' % self.access_token, }) return r.json()
def test_bitcoind_cosigning(dev, bitcoind, start_sign, end_sign, import_ms_wallet, clear_ms, explora, try_sign, need_keypress, addr_style): # Make a P2SH wallet with local bitcoind as a co-signer (and simulator) # - send an receive various # - following text of <https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md> # - the constructed multisig walelt will only work for a single pubkey on core side # - before starting this test, have some funds already deposited to bitcoind testnet wallet from pycoin.encoding import sec_to_public_pair from binascii import a2b_hex import re if addr_style == 'legacy': addr_fmt = AF_P2SH elif addr_style == 'p2sh-segwit': addr_fmt = AF_P2WSH_P2SH elif addr_style == 'bech32': addr_fmt = AF_P2WSH try: addr, = bitcoind.getaddressesbylabel("sim-cosign").keys() except: addr = bitcoind.getnewaddress("sim-cosign") info = bitcoind.getaddressinfo(addr) #pprint(info) assert info['address'] == addr bc_xfp = swab32(int(info['hdmasterfingerprint'], 16)) bc_deriv = info['hdkeypath'] # example: "m/0'/0'/3'" bc_pubkey = info['pubkey'] # 02f75ae81199559c4aa... pp = sec_to_public_pair(a2b_hex(bc_pubkey)) # No means to export XPUB from bitcoind! Still. In 2019. # - this fake will only work for for one pubkey value, the first/topmost node = BIP32Node('XTN', b'\x23'*32, depth=len(bc_deriv.split('/'))-1, parent_fingerprint=a2b_hex('%08x' % bc_xfp), public_pair=pp) keys = [ (bc_xfp, None, node), (1130956047, None, BIP32Node.from_hwif('tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n')), # simulator: m/45' ] M,N=2,2 clear_ms() import_ms_wallet(M, N, keys=keys, accept=1, name="core-cosign") cc_deriv = "m/45'/55" cc_pubkey = B2A(BIP32Node.from_hwif(simulator_fixed_xprv).subkey_for_path(cc_deriv[2:]).sec()) # NOTE: bitcoind doesn't seem to implement pubkey sorting. We have to do it. resp = bitcoind.addmultisigaddress(M, list(sorted([cc_pubkey, bc_pubkey])), 'shared-addr-'+addr_style, addr_style) ms_addr = resp['address'] bc_redeem = a2b_hex(resp['redeemScript']) assert bc_redeem[0] == 0x52 def mapper(cosigner_idx): return list(str2ipath(cc_deriv if cosigner_idx else bc_deriv)) scr, pubkeys, xfp_paths = make_redeem(M, keys, mapper) assert scr == bc_redeem # check Coldcard calcs right address to match got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address( M, xfp_paths, scr, addr_fmt=addr_fmt), timeout=None) assert got_addr == ms_addr time.sleep(.1) need_keypress('x') # clear screen / start over print(f"Will be signing an input from {ms_addr}") if xfp2str(bc_xfp) in ('5380D0ED', 'EDD08053'): # my own expected values assert ms_addr in ( '2NDT3ymKZc8iMfbWqsNd1kmZckcuhixT5U4', '2N1hZJ5mazTX524GQTPKkCT4UFZn5Fqwdz6', 'tb1qpcv2rkc003p5v8lrglrr6lhz2jg8g4qa9vgtrgkt0p5rteae5xtqn6njw9') # Need some UTXO to sign # # - but bitcoind can't give me that (using listunspent) because it's only a watched addr?? # did_fund = False while 1: rr = explora('address', ms_addr, 'utxo') pprint(rr) avail = [] amt = 0 for i in rr: txn = i['txid'] vout = i['vout'] avail.append( (txn, vout) ) amt += i['value'] # just use first UTXO available; save other for later tests break else: # doesn't need to confirm, but does need to reach public testnet/blockstream assert not amt and not avail if not did_fund: print(f"Sending some XTN to {ms_addr} (wait)") bitcoind.sendtoaddress(ms_addr, 0.0001, 'fund testing') did_fund = True else: print(f"Still waiting ...") time.sleep(2) if amt: break ret_addr = bitcoind.getrawchangeaddress() ''' If you get insufficent funds, even tho we provide the UTXO (!!), do this: bitcoin-cli importaddress "2NDT3ymKZc8iMfbWqsNd1kmZckcuhixT5U4" true true Better method: always fund addresses for testing here from same wallet (ie. got from non-multisig to multisig on same bitcoin-qt instance). -> Now doing that, automated, above. ''' resp = bitcoind.walletcreatefundedpsbt([dict(txid=t, vout=o) for t,o in avail], [{ret_addr: amt/1E8}], 0, {'subtractFeeFromOutputs': [0], 'includeWatching': True}, True) assert resp['changepos'] == -1 psbt = b64decode(resp['psbt']) open('debug/funded.psbt', 'wb').write(psbt) # patch up the PSBT a little ... bitcoind doesn't know the path for the CC's key ex = BasicPSBT().parse(psbt) cxpk = a2b_hex(cc_pubkey) for i in ex.inputs: assert cxpk in i.bip32_paths, 'input not to be signed by CC?' i.bip32_paths[cxpk] = pack('<3I', keys[1][0], *str2ipath(cc_deriv)) psbt = ex.as_bytes() open('debug/patched.psbt', 'wb').write(psbt) _, updated = try_sign(psbt, finalize=False) open('debug/cc-updated.psbt', 'wb').write(updated) # have bitcoind do the rest of the signing rr = bitcoind.walletprocesspsbt(b64encode(updated).decode('ascii')) pprint(rr) open('debug/bc-processed.psbt', 'wt').write(rr['psbt']) assert rr['complete'] # finalize and send rr = bitcoind.finalizepsbt(rr['psbt'], True) open('debug/bc-final-txn.txn', 'wt').write(rr['hex']) assert rr['complete'] txn_id = bitcoind.sendrawtransaction(rr['hex']) print(txn_id)
def decode_key(dct): if 'hwif' in dct: return BIP32Node.from_hwif(dct['hwif']) return dct
def send(self, wallet_id, passcode, address, amount, message='', fee=None, fan_unspend=10): """ Send bitcoins to address :param wallet_id: bitgo wallet id :param address: bitcoin address :param amount: btc amount in satoshis :param split: create new outputs if needed :return: boolean """ MINIMAL_FEE = 20000 MINIMAL_SPLIT = 10000000 MIN_UNSPENTS_FAN = 5 wallet = self.get_wallet(wallet_id) if not wallet['spendingAccount']: raise NotSpendableWallet() if not wallet['isActive']: raise NotActiveWallet() if amount < 10000: raise Exception('amount to small') if wallet['confirmedBalance'] < amount: raise NotEnoughFunds('Not enough funds: balance %s amount %s' % (wallet['confirmedBalance'], amount) ) change_address = self.create_address(wallet_id, chain=1) usableKeychain = False spendables = [] chain_paths = [] p2sh = [] payables = [(address, amount)] keychain_path = "" for keychain in wallet['private']['keychains']: keychain_path = keychain['path'][1:] keychain = self.get_keychain(keychain['xpub']) if 'encryptedXprv' not in keychain: continue usableKeychain = True break if not usableKeychain: raise BitGoError("didn't found a spendable keychain") data = json.loads(keychain['encryptedXprv']) #add base64 paddings for k in ['iv', 'salt', 'ct']: data[k] = data[k] + "==" cipher = sjcl.SJCL() xprv = cipher.decrypt(data, passcode) unspents = self.get_unspents(wallet_id)['unspents'] order_unspents = sorted(unspents, key=lambda k: k['confirmations'], reverse=True) total_value = 0 for d in order_unspents: path = keychain_path + d['chainPath'] chain_paths.append(path) p2sh.append(h2b(d["redeemScript"])) spendables.append(Spendable(d["value"], h2b(d["script"]), h2b_rev(d["tx_hash"]), d["tx_output_n"])) total_value += d['value'] if total_value > amount: break # make many outputs? if len(order_unspents) < MIN_UNSPENTS_FAN and (total_value > (amount + MINIMAL_SPLIT)) and fan_unspend > 0: fee = self.calculate_fee(len(spendables), fan_unspend) value = (total_value - amount - fee) / fan_unspend for i in range(fan_unspend): payables.append((change_address, value)) elif total_value > (amount + MINIMAL_FEE): # add a change address if fee is None: fee = self.calculate_fee(len(spendables), 2) value = total_value - amount - fee if value > 10000: #avoid dust payables.append((change_address, value)) p2sh_lookup = build_p2sh_lookup(p2sh) spendable_keys = [] priv_key = BIP32Node.from_hwif(xprv) spendable_keys = [priv_key.subkey_for_path(path) for path in chain_paths] hash160_lookup = build_hash160_lookup([key.secret_exponent() for key in spendable_keys]) tx = create_tx(spendables, payables) tx.sign(hash160_lookup=hash160_lookup, p2sh_lookup=p2sh_lookup) r = requests.post(self.url + '/tx/send', { 'tx': tx.as_hex(), 'message': message }, headers={ 'Authorization': 'Bearer %s' % self.access_token, }) return r.json()
def get_new_address(user_wallet, is_change=False, get_all_addresses=False): # Be sure this is a valid currency supported by the exchange if user_wallet.currencycode in settings.COINS.keys(): all_addresses = [] last_active_address = 0 # Use the wallet's public_key to generate addresses. wallet_public_key = BIP32Node.from_hwif(user_wallet.public_key) # External address if not is_change: wallet_base = wallet_public_key.subkey(i=0, is_hardened=False, as_private=False) index = user_wallet.last_external_index # Change address else: wallet_base = wallet_public_key.subkey(i=1, is_hardened=False, as_private=False) index = user_wallet.last_change_index if get_all_addresses: index = 0 # If index is non-zero, then increment by one to generate a new unused # index. if index > 0: index += 1 # Look for an unused address. # @TODO: https://github.com/ .. /issues/6 search through a gap of 20 searching = True while searching: # Create addresses following BIP44 # https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki address_key = wallet_base.subkey(i=index, is_hardened=False, as_private=False) new_address = { 'index': index, 'p2pkh': address_key.bitcoin_address() } # Generate bech32 for Bitcoin and Bitcoin Testnet # @TODO get working with LTC https://github.com/richardkiss/pycoin/issues/323 if user_wallet.currencycode in ['BTC', 'XTN']: try: script = ScriptPayToAddressWit( b'\0', address_key.hash160(use_uncompressed=False)).script() new_address['p2sh_p2wpkh'] = address_for_pay_to_script( script, netcode=user_wallet.currencycode) new_address['bech32'] = address_for_pay_to_script_wit( script, netcode=user_wallet.currencycode) except Exception as e: #print(e) new_address['p2sh_p2wpkh'] = None new_address['bech32'] = None else: new_address['p2sh_p2wpkh'] = None new_address['bech32'] = None #print("coin: %s, p2pkh: %s, p2sh_p2wpkh: %s, bech32: %s" % (user_wallet.currencycode, p2pkh, p2sh_p2wpkh, bech32)) if address_is_used(new_address, user_wallet): index += 1 if get_all_addresses: all_addresses.append(new_address) else: last_active_address += 1 if get_all_addresses and last_active_address > 20: searching = False else: searching = False #print("%s address: %s" % (user_wallet.currencycode, p2pkh)) if get_all_addresses: return all_addresses, index else: return new_address, index else: return False, False
def main(): parser = argparse.ArgumentParser(description="Process blinktrade withdrawals requests") parser.add_argument('-c', "--config", action="store", dest="config", help='Configuration file', type=str) arguments = parser.parse_args() candidates = [ os.path.join(site_config_dir('blinktrade'), 'blinktrade_withdrawer.ini'), os.path.expanduser('~/.blinktrade/blinktrade_withdrawer.ini')] if arguments.config: candidates.append(arguments.config) config = ConfigParser.SafeConfigParser() config.read( candidates ) password = getpass.getpass('password: '******'ws': should_connect_on_ssl = False blinktrade_port = 80 db_engine = config.get("database", "sqlalchemy_engine") + ':///' +\ os.path.expanduser(config.get("database", "sqlalchemy_connection_string")) engine = create_engine(db_engine, echo=config.getboolean('database', 'sqlalchmey_verbose')) Base.metadata.create_all(engine) factory = BlinkTradeClientFactory(blinktrade_url.geturl()) factory.db_session = scoped_session(sessionmaker(bind=engine)) factory.verbose = config.getboolean("blinktrade", "verbose") factory.blinktrade_broker_id = config.get("blinktrade", "broker_id") factory.blinktrade_user = config.get("blinktrade", "api_key") factory.blinktrade_password = decrypt(password, unhexlify(config.get("blinktrade", "api_password"))) factory.currencies = json.loads(config.get("blinktrade", "currencies")) factory.methods = json.loads(config.get("blinktrade", "methods")) factory.blocked_accounts = json.loads(config.get("blinktrade", "blocked_accounts")) factory.mandrill_api = mandrill_api if config.has_section('blockchain_info'): from blockchain_info import BlockchainInfoWithdrawalProtocol factory.blockchain_guid = decrypt(password, unhexlify(config.get("blockchain_info", "guid"))) factory.blockchain_main_password = decrypt(password, unhexlify(config.get("blockchain_info", "main_password"))) factory.blockchain_second_password = decrypt(password, unhexlify(config.get("blockchain_info", "second_password"))) factory.blockchain_api_key = config.get("blockchain_info", "api_key") factory.from_address = config.get("blockchain_info", "from_address") factory.note = config.get("blockchain_info", "note") factory.protocol = BlockchainInfoWithdrawalProtocol if config.has_section('blocktrail'): import blocktrail from mnemonic.mnemonic import Mnemonic from pycoin.key.BIP32Node import BIP32Node is_testnet = False if config.get("blocktrail", "testnet") == '1': is_testnet = True client = blocktrail.APIClient(api_key=config.get("blocktrail", "api_key"), api_secret=decrypt(password, unhexlify(config.get("blocktrail", "api_secret"))), network='BTC', testnet=is_testnet) data = client.get_wallet(config.get("blocktrail", "wallet_identifier")) primary_seed = Mnemonic.to_seed(data['primary_mnemonic'], decrypt(password, unhexlify(config.get("blocktrail", "wallet_passphrase")))) primary_private_key = BIP32Node.from_master_secret(primary_seed, netcode='XTN' if client.testnet else 'BTC') backup_public_key = BIP32Node.from_hwif(data['backup_public_key'][0]) checksum = client.create_checksum(primary_private_key) if checksum != data['checksum']: raise Exception("Checksum [%s] does not match expected checksum [%s], " \ "most likely due to incorrect password" % (checksum, data['checksum'])) blocktrail_public_keys = {} for v,k in data['blocktrail_public_keys']: if k in blocktrail_public_keys: blocktrail_public_keys[k].append(v) else: blocktrail_public_keys[k] = [v] key_index = data['key_index'] wallet = blocktrail.wallet.Wallet(client=client, identifier= config.get("blocktrail", "wallet_identifier"), primary_mnemonic=data['primary_mnemonic'], primary_private_key=primary_private_key, backup_public_key=backup_public_key, blocktrail_public_keys=blocktrail_public_keys, key_index=key_index, testnet=client.testnet) from blocktrail_protocol import BlocktrailWithdrawalProtocol factory.blocktrail_wallet = wallet factory.blocktrail_change_address = config.get("blocktrail", "change_address") factory.protocol = BlocktrailWithdrawalProtocol if config.has_section('mailer'): from mailer_protocol import MailerWithdrawalProtocol factory.mandrill_apikey = config.get("mailer", "mandrill_apikey") factory.mandrill_template_name = config.get("mailer", "template_name") factory.mandrill_from_email = config.get("mailer", "from_email") factory.mandrill_from_name = config.get("mailer", "from_name") factory.mandrill_to_email = config.get("mailer", "to_email") factory.mandrill_to_name = config.get("mailer", "to_name") factory.mandrill_website = config.get("mailer", "website") factory.protocol = MailerWithdrawalProtocol if should_connect_on_ssl: reactor.connectSSL( blinktrade_url.netloc , blinktrade_port , factory, ssl.ClientContextFactory() ) else: reactor.connectTCP(blinktrade_url.netloc , blinktrade_port , factory ) reactor.run()
def test_sign_p2sh_example(set_master_key, sim_execfile, start_sign, end_sign, decode_psbt_with_bitcoind, offer_ms_import, need_keypress, clear_ms): # Use the private key given in BIP 174 and do similar signing # as the examples. # PROBLEM: we can't handle this, since we don't allow same cosigner key to be used # more than once and that check happens after we decide we can sign an input, and yet # no way to provide the right set of keys needed since 4 in total, etc, etc. # - code below nearly works tho raise pytest.skip('difficult example') # expect xfp=4F6A0CD9 exk = 'tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF' set_master_key(exk) # Peeked at PSBT to know the full, deep hardened path we'll need. # in1: 0'/0'/0' and 0'/0'/1' # in2: 0'/0'/3' and 0'/0'/2' config = "name: p2sh-example\npolicy: 2 of 2\n\n" n1 = BIP32Node.from_hwif(exk).subkey_for_path("0'/0'").hwif() n2 = BIP32Node.from_hwif(exk).subkey_for_path("0'/0'").hwif() xfp = '4F6A0CD9' config += f'{xfp}: {n1}\n{xfp}: {n2}\n' clear_ms() offer_ms_import(config) time.sleep(.1) need_keypress('y') psbt = a2b_hex(open('data/worked-unsigned.psbt', 'rb').read()) # PROBLEM: revised BIP174 has p2sh multisig cases which we don't support yet. # - it has two signatures from same key on same input # - that's a rare case and not worth supporting in the firmware # - but we can do it in two passes # - the MS wallet is also hard, since dup xfp (same actual key) ... altho can # provide different subkeys start_sign(psbt) part_signed = end_sign(True) open('debug/ex-signed-part.psbt', 'wb').write(part_signed) b4 = BasicPSBT().parse(psbt) aft = BasicPSBT().parse(part_signed) assert b4 != aft, "(partial) signing didn't change anything?" # NOTE: cannot handle combining multisig txn yet, so cannot finalize on-device start_sign(part_signed, finalize=False) signed = end_sign(True, finalize=False) open('debug/ex-signed.psbt', 'wb').write(signed) aft2 = BasicPSBT().parse(signed) decode = decode_psbt_with_bitcoind(signed) pprint(decode) mx_expect = BasicPSBT().parse( a2b_hex(open('data/worked-combined.psbt', 'rb').read())) assert aft2 == mx_expect expect = a2b_hex(open('data/worked-combined.psbt', 'rb').read()) decode_ex = decode_psbt_with_bitcoind(expect) # NOTE: because we are using RFC6979, the exact bytes of the signatures should match for i in range(2): assert decode['inputs'][i]['partial_signatures'] == \ decode_ex['inputs'][i]['partial_signatures'] if 0: import json, decimal def EncodeDecimal(o): if isinstance(o, decimal.Decimal): return float(round(o, 8)) raise TypeError json.dump(decode, open('debug/core-decode.json', 'wt'), indent=2, default=EncodeDecimal)
def send(self, wallet_id, passcode, address, amount, message='', fee=10000): """ Send bitcoins to address :param wallet_id: bitgo wallet id :param address: bitcoin address :param amount: btc amount in satoshis :return: boolean """ wallet = self.get_wallet(wallet_id) if not wallet['spendingAccount']: raise NotSpendableWallet() if not wallet['isActive']: raise NotActiveWallet() if amount < 10000: raise Exception('amount to small') if wallet['confirmedBalance'] < amount: raise NotEnoughFunds('Not enough funds: balance %s amount %s' % (wallet['confirmedBalance'], amount)) change_address = self.create_address(wallet_id, chain=1) usableKeychain = False spendables = [] chain_paths = [] p2sh = [] payables = [(address, amount)] keychain_path = "" for keychain in wallet['private']['keychains']: keychain_path = keychain['path'][1:] keychain = self.get_keychain(keychain['xpub']) if 'encryptedXprv' not in keychain: continue usableKeychain = True break if not usableKeychain: raise BitGoError("didn't found a spendable keychain") data = json.loads(keychain['encryptedXprv']) #add base64 paddings for k in ['iv', 'salt', 'ct']: data[k] = data[k] + "==" cipher = sjcl.SJCL() xprv = cipher.decrypt(data, passcode) unspents = self.get_unspents(wallet_id) total_value = 0 for d in unspents['unspents'][::-1]: path = keychain_path + d['chainPath'] chain_paths.append(path) p2sh.append(h2b(d["redeemScript"])) spendables.append( Spendable(d["value"], h2b(d["script"]), h2b_rev(d["tx_hash"]), d["tx_output_n"])) total_value += d['value'] if total_value > amount: break if total_value > (amount + fee): #add a change address #TODO: create address payables.append(change_address) p2sh_lookup = build_p2sh_lookup(p2sh) spendable_keys = [] priv_key = BIP32Node.from_hwif(xprv) spendable_keys = [ priv_key.subkey_for_path(path) for path in chain_paths ] hash160_lookup = build_hash160_lookup( [key.secret_exponent() for key in spendable_keys]) tx = create_tx(spendables, payables) tx.sign(hash160_lookup=hash160_lookup, p2sh_lookup=p2sh_lookup) r = requests.post(self.url + '/tx/send', { 'tx': tx.as_hex(), 'message': message }, headers={ 'Authorization': 'Bearer %s' % self.access_token, }) return r.json()
def test_bip_vectors(mode, index, entropy, expect, set_encoded_secret, dev, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path, settings_set, sim_eval, sim_exec, reset_seed_words): set_encoded_secret(a2b_hex(EXAMPLE_XPRV)) settings_set('chain', 'BTC') goto_home() pick_menu_item('Advanced') pick_menu_item('Derive Entropy') time.sleep(0.1) title, story = cap_story() assert 'seed value' in story assert 'other wallet systems' in story need_keypress('y') time.sleep(0.1) pick_menu_item(mode) if index is not None: time.sleep(0.1) for n in str(index): need_keypress(n) need_keypress('y') time.sleep(0.1) title, story = cap_story() assert f'Path Used (index={index}):' in story assert "m/83696968'/" in story assert f"/{index}'" in story if entropy is not None: assert f"Raw Entropy:\n{entropy}" in story do_import = False if 'words' in mode: num_words = int(mode.split()[0]) assert f'Seed words ({num_words}):' in story assert f"m/83696968'/39'/0'/{num_words}'/{index}'" in story assert '\n 1: ' in story assert f'\n{num_words}: ' in story got = [ ln[4:] for ln in story.split('\n') if len(ln) > 5 and ln[2] == ':' ] assert ' '.join(got) == expect do_import = 'words' elif 'XPRV' in mode: assert 'Derived XPRV:' in story assert f"m/83696968'/32'/{index}'" in story assert expect in story do_import = 'xprv' elif 'WIF' in mode: assert 'WIF (privkey)' in story assert f"m/83696968'/2'/{index}'" in story assert expect in story elif 'bytes hex' in mode: width = int(mode.split('-')[0]) assert width in {32, 64} assert f'Hex ({width} bytes):' in story assert f"m/83696968'/128169'/{width}'/{index}'" in story assert expect in story else: raise ValueError(mode) # write to SD msg = story.split('Press', 1)[0] if 1: assert 'Press 1 to save' in story need_keypress('1') time.sleep(0.1) title, story = cap_story() assert title == 'Saved' fname = story.split('\n')[-1] need_keypress('y') time.sleep(0.1) title, story = cap_story() assert story.startswith(msg) path = microsd_path(fname) assert path.endswith('.txt') txt = open(path, 'rt').read() assert txt.strip() == msg.strip() if do_import: assert '2 to switch to derived secret' in story try: time.sleep(0.1) need_keypress('2') time.sleep(0.1) title, story = cap_story() assert 'New master key in effect' in story encoded = sim_exec( 'from pincodes import pa; RV.write(repr(pa.fetch()))') print(encoded) assert 'Error' not in encoded encoded = eval(encoded) assert len(encoded) == 72 marker = encoded[0] if do_import == 'words': assert marker & 0x80 == 0x80 width = ((marker & 0x3) + 2) * 8 assert width in {16, 24, 32} assert encoded[1:1 + width] == a2b_hex(entropy) elif do_import == 'xprv': assert marker == 0x01 node = BIP32Node.from_hwif(expect) ch, pk = encoded[1:33], encoded[33:65] assert node.chain_code() == ch assert node.secret_exponent() == int(B2A(pk), 16) finally: # required cleanup reset_seed_words() else: assert '3 to view as QR code' in story need_keypress('x')
def test_path_index(mode, pattern, index, set_encoded_secret, dev, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, cap_screen_qr, qr_quality_check): # Uses any key on Simulator; just checking for operation + entropy level goto_home() pick_menu_item('Advanced') pick_menu_item('Derive Entropy') time.sleep(0.1) title, story = cap_story() assert 'seed value' in story assert 'other wallet systems' in story need_keypress('y') time.sleep(0.1) pick_menu_item(mode) if index is not None: time.sleep(0.1) for n in str(index): need_keypress(n) need_keypress('y') time.sleep(0.1) title, story = cap_story() assert f'Path Used (index={index}):' in story assert "m/83696968'/" in story assert f"/{index}'" in story got = re.findall(pattern, story)[0] assert len(set(got)) >= 12 global HISTORY assert got not in HISTORY HISTORY.add(got) if 'words' in mode: exp = Mnemonic('english').to_mnemonic(a2b_hex(got)).split() assert '\n'.join(f'{n+1:2d}: {w}' for n, w in enumerate(exp)) in story elif 'XPRV' in mode: node = BIP32Node.from_hwif(got) assert str(b2a_hex(node.chain_code()), 'ascii') in story assert hex(node.secret_exponent())[2:] in story elif 'WIF' in mode: key = Key.from_text(got) assert hex(key.secret_exponent())[2:] in story if index == 0: assert '3 to view as QR code' in story need_keypress('3') qr = cap_screen_qr().decode('ascii') if 'words' in mode: gw = qr.lower().split() assert gw == [i[0:4] for i in exp] elif 'hex' in mode: assert qr.lower() == got elif 'XPRV' in mode: assert qr == got elif 'WIF' in mode: assert qr == got
def post(self, request, format=None): # Verify that a wallet_id has been passed in wallet_id, success = wallet.utils.get_wallet_id(request) if success is not True: # If status code is set, then wallet_id is a JSON-formatted error: abort! return Response(wallet_id, status=success) # Be sure the wallet exists and the user has access to it. user_wallet, success = wallet.utils.get_wallet( user_id=self.request.user.id, wallet_id=wallet_id) if success is not True: return Response(user_wallet, status=success) # Be sure wallet contains supported currency. if user_wallet.currencycode not in settings.COINS.keys(): status_code = status.HTTP_400_BAD_REQUEST data = { "status": "invalid data", "code": status_code, "debug": { "request": self.request.data, "symbol": user_wallet.currencycode, }, "data": {}, } return Response(data, status=status_code) passphrase, success = wallet.utils.get_passphrase(request) if success != True: # If status code is set, then passphrase is a JSON-formatted error: abort! return Response(passphrase, status=success) if not wallet.utils.valid_passphrase(self.request.user, passphrase): status_code = status.HTTP_400_BAD_REQUEST data = { "status": "invalid passphrase", "code": status_code, "debug": { "passphrase": self.request.user.passphrase, "request": self.request.data, }, "data": {}, } return Response(data, status=status_code) output, output_total, success = wallet.utils.get_output( request, user_wallet=user_wallet) if success != True: # If status code is set, then output is a JSON-formatted error: abort! return Response(output, status=success) number_of_blocks, estimate_mode, success = wallet.utils.get_priority( request) if success != True: # If status code is set, then number_of_blocks is a JSON-formatted error: abort! return Response(number_of_blocks, status=success) # STEP 1: find enough unspent to cover desired transaction total_to_send = output_total unspent, addresses, unspent_total, success = wallet.utils.get_unspent_equal_or_greater( user_wallet, total_to_send) if success is not True: # If success isn't true, unspent contains an error: abort return Response(unspent, status=success) # STEP 2: calculate fee and change # https://bitcoin.org/en/developer-reference#estimatefee fee, valid = wallet.utils.calculate_fee( wallet=user_wallet, vout=unspent, vin=output, number_of_blocks=number_of_blocks, estimate_mode=estimate_mode) if not valid: status_code = status.HTTP_500_INTERNAL_SERVER_ERROR data = { "status": "fee error", "code": status_code, "debug": { "request": self.request.data, "fee": fee, "unspent": unspent, "output": output, "number_of_blocks": number_of_blocks, "estimate_mode": estimate_mode, }, "data": {}, } return Response(data, status=status_code) # Check if our unspent can cover our desired transaction plus the calculated fee, # if not re-request unspent this time including enough for the fee. if (output_total + fee) > unspent_total: print("fee grew, re-request unspent") # TODO: loop in case this requires more unspent and therefor a larger fee unspent, addresses, unspent_total, success = wallet.utils.get_unspent_equal_or_greater( user_wallet, total_to_send + fee) if success is not True: # If success isn't true, unspent contains an error: abort return Response(unspent, status=success) change = unspent_total - (total_to_send + fee) #print("addresses: %s, unspent: %s, change: %s" % (addresses, unspent, change)) # STEP 3: create a raw transaction including unspent and destination address final_output = {} for out_address in output: # Build an array of outputs we are sending final_output[out_address] = wallet.utils.convert_to_decimal( output[out_address]) # An address may have multiple outputs, use a set to only get each address once new_change_address, change_index = address.utils.get_new_address( user_wallet=user_wallet, is_change=True) final_output[new_change_address[ 'p2pkh']] = wallet.utils.convert_to_decimal(change) #pprint(final_output) raw_tx = wallet.utils.create_raw_transaction( currencycode=user_wallet.currencycode, input=unspent, output=final_output) #print("raw_tx: %s" % raw_tx) # STEP 4: sign the raw transaction # @TODO request private key from secrets database -- limit each wallet to only 1 private key private_keys = [] # Load wallet's private key, effectively unlocking it unlocked_account = BIP32Node.from_hwif(user_wallet.private_key) # Get WIF for all addresses we're sending from for to_address in addresses: #print("to_address: %s" % to_address) loaded_address = Address.objects.filter(wallet=user_wallet.id, p2pkh=to_address) for la in loaded_address: if la.is_change: is_change = 1 else: is_change = 0 unlocked_address = unlocked_account.subkey_for_path( "%d/%s" % (is_change, la.index)) #print("wif: %s subkey path: %d/%s" % (unlocked_address.wif(), is_change, la.index)) private_keys.append(unlocked_address.wif()) #print("private_keys:") #pprint(private_keys) if user_wallet.currencycode in ['BTC', 'XTN']: # Starting with 0.17, bitcoind replaces the old sign RPC with a new one signed_tx = wallet.utils.sign_raw_transaction_with_key( currencycode=user_wallet.currencycode, raw_tx=raw_tx, private_keys=private_keys) #print("signed_tx: %s" % signed_tx) else: signed_tx = wallet.utils.sign_raw_transaction( currencycode=user_wallet.currencycode, raw_tx=raw_tx, output=[], private_keys=private_keys) #print("signed_tx: %s" % signed_tx) try: if signed_tx['complete'] is False: ''' response (decoded): {'result': {'hex': '02000000011db9fe34f5a8c4854c68ca73faa92b41f479df0a562d76c3e69fefdf325855100000000000ffffffff02c4090000000000001976a914df75177fb70c628dc9387456a36c5c7f8f472f4488acdb9ee60e000000001976a914bb48756d3b4ab3d383713af776d16abef9243b3d88ac00000000', 'complete': False, 'errors': [{'txid': '10555832dfef9fe6c3762d560adf79f4412ba9fa73ca684c85c4a8f534feb91d', 'vout': 0, 'witness': [], 'scriptSig': '', 'sequence': 4294967295, 'error': 'Unable to sign input, invalid stack size (possibly missing key)'}]}, 'error': None, 'id': 51929} ''' status_code = status.HTTP_500_INTERNAL_SERVER_ERROR data = { "status": "invalid transaction", "code": status_code, "debug": { "request": self.request.data, "fee": fee, "signed_tx": signed_tx, "output": final_output, "spent": unspent, }, "data": {}, } return Response(data, status=status_code) except Exception as e: status_code = status.HTTP_500_INTERNAL_SERVER_ERROR data = { "status": "signing error", "code": status_code, "debug": { "request": self.request.data, "fee": fee, "signed_tx": signed_tx, "output": final_output, "unspent": unspent, "exception": str(e), }, "data": {}, } return Response(data, status=status_code) # STEP 5: send the signed transaction txid = wallet.utils.send_signed_transaction( currencycode=user_wallet.currencycode, signed_tx=signed_tx['hex']) if txid: # funds were sent, add the change address to our wallet wallet.utils.add_addresses_to_wallet( wallet_id=user_wallet.id, label='change', p2pkh=new_change_address['p2pkh'], p2sh_p2wpkh=None, bech32=None, index=change_index, is_change=True) status_message = "funds sent" else: status_message = "no funds sent" status_code = status.HTTP_200_OK data = { "status": status_message, "code": status_code, "debug": {}, "data": { "txid": txid, "fee": fee, "output": final_output, "spent": unspent, "change_address": new_change_address['p2pkh'], }, } return Response(data, status=status_code)
def main(): parser = argparse.ArgumentParser( description="Process blinktrade withdrawals requests") parser.add_argument('-c', "--config", action="store", dest="config", help='Configuration file', type=str) arguments = parser.parse_args() candidates = [ os.path.join(site_config_dir('blinktrade'), 'blinktrade_withdrawer.ini'), os.path.expanduser('~/.blinktrade/blinktrade_withdrawer.ini') ] if arguments.config: candidates.append(arguments.config) config = ConfigParser.SafeConfigParser() config.read(candidates) password = getpass.getpass('password: '******'ws': should_connect_on_ssl = False blinktrade_port = 80 db_engine = config.get("database", "sqlalchemy_engine") + ':///' +\ os.path.expanduser(config.get("database", "sqlalchemy_connection_string")) engine = create_engine(db_engine, echo=config.getboolean('database', 'sqlalchmey_verbose')) Base.metadata.create_all(engine) factory = BlinkTradeClientFactory(blinktrade_url.geturl()) factory.db_session = scoped_session(sessionmaker(bind=engine)) factory.verbose = config.getboolean("blinktrade", "verbose") factory.blinktrade_broker_id = config.get("blinktrade", "broker_id") factory.blinktrade_user = config.get("blinktrade", "api_key") factory.blinktrade_password = decrypt( password, unhexlify(config.get("blinktrade", "api_password"))) factory.currencies = json.loads(config.get("blinktrade", "currencies")) factory.methods = json.loads(config.get("blinktrade", "methods")) factory.blocked_accounts = json.loads( config.get("blinktrade", "blocked_accounts")) factory.mandrill_api = mandrill_api if config.has_section('blockchain_info'): from blockchain_info import BlockchainInfoWithdrawalProtocol factory.blockchain_guid = decrypt( password, unhexlify(config.get("blockchain_info", "guid"))) factory.blockchain_main_password = decrypt( password, unhexlify(config.get("blockchain_info", "main_password"))) factory.blockchain_second_password = decrypt( password, unhexlify(config.get("blockchain_info", "second_password"))) factory.blockchain_api_key = config.get("blockchain_info", "api_key") factory.from_address = config.get("blockchain_info", "from_address") factory.note = config.get("blockchain_info", "note") factory.protocol = BlockchainInfoWithdrawalProtocol if config.has_section('blocktrail'): import blocktrail from mnemonic.mnemonic import Mnemonic from pycoin.key.BIP32Node import BIP32Node is_testnet = False if config.get("blocktrail", "testnet") == '1': is_testnet = True client = blocktrail.APIClient( api_key=config.get("blocktrail", "api_key"), api_secret=decrypt( password, unhexlify(config.get("blocktrail", "api_secret"))), network='BTC', testnet=is_testnet) data = client.get_wallet(config.get("blocktrail", "wallet_identifier")) primary_seed = Mnemonic.to_seed( data['primary_mnemonic'], decrypt(password, unhexlify(config.get("blocktrail", "wallet_passphrase")))) primary_private_key = BIP32Node.from_master_secret( primary_seed, netcode='XTN' if client.testnet else 'BTC') backup_public_key = BIP32Node.from_hwif(data['backup_public_key'][0]) checksum = client.create_checksum(primary_private_key) if checksum != data['checksum']: raise Exception("Checksum [%s] does not match expected checksum [%s], " \ "most likely due to incorrect password" % (checksum, data['checksum'])) blocktrail_public_keys = {} for v, k in data['blocktrail_public_keys']: if k in blocktrail_public_keys: blocktrail_public_keys[k].append(v) else: blocktrail_public_keys[k] = [v] key_index = data['key_index'] wallet = blocktrail.wallet.Wallet( client=client, identifier=config.get("blocktrail", "wallet_identifier"), primary_mnemonic=data['primary_mnemonic'], primary_private_key=primary_private_key, backup_public_key=backup_public_key, blocktrail_public_keys=blocktrail_public_keys, key_index=key_index, testnet=client.testnet) from blocktrail_protocol import BlocktrailWithdrawalProtocol factory.blocktrail_wallet = wallet factory.blocktrail_change_address = config.get("blocktrail", "change_address") factory.protocol = BlocktrailWithdrawalProtocol if config.has_section('mailer'): from mailer_protocol import MailerWithdrawalProtocol factory.mandrill_apikey = config.get("mailer", "mandrill_apikey") factory.mandrill_template_name = config.get("mailer", "template_name") factory.mandrill_from_email = config.get("mailer", "from_email") factory.mandrill_from_name = config.get("mailer", "from_name") factory.mandrill_to_email = config.get("mailer", "to_email") factory.mandrill_to_name = config.get("mailer", "to_name") factory.mandrill_website = config.get("mailer", "website") factory.protocol = MailerWithdrawalProtocol if should_connect_on_ssl: reactor.connectSSL(blinktrade_url.netloc, blinktrade_port, factory, ssl.ClientContextFactory()) else: reactor.connectTCP(blinktrade_url.netloc, blinktrade_port, factory) reactor.run()
def doit(M, addr_fmt=None, do_import=True): passwords = ['Me', 'Myself', 'And I', ''] if 0: # WORKING, but slow .. and it's constant data keys = [] for pw in passwords: xfp = set_bip39_pw(pw) sk = dev.send_recv(CCProtocolPacker.get_xpub("m/45'")) node = BIP32Node.from_wallet_key(sk) keys.append((xfp, None, node)) assert len(set(x for x, _, _ in keys)) == 4, keys pprint(keys) else: # Much, FASTER! assert dev.is_simulator keys = [ (3503269483, None, BIP32Node.from_hwif( 'tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9' )), (2389277556, None, BIP32Node.from_hwif( 'tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc' )), (3190206587, None, BIP32Node.from_hwif( 'tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa' )), (1130956047, None, BIP32Node.from_hwif( 'tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n' )), ] if do_import: # render as a file for import config = f"name: Myself-{M}\npolicy: {M} / 4\n\n" if addr_fmt: config += f'format: {addr_fmt.upper()}\n' config += '\n'.join('%s: %s' % (xfp2str(xfp), sk.hwif()) for xfp, _, sk in keys) #print(config) title, story = offer_ms_import(config) #print(story) # dont care if update or create; accept it. time.sleep(.1) need_keypress('y') def select_wallet(idx): # select to specific pw xfp = set_bip39_pw(passwords[idx]) assert xfp == keys[idx][0] return (keys, select_wallet)