def sign(utxo, priv, destaddrs, utxo_address_type): """Sign a tx sending the amount amt, from utxo utxo, equally to each of addresses in list destaddrs, after fees; the purpose is to create multiple utxos. utxo_address_type must be one of p2sh-p2wpkh/p2wpkh/p2pkh. """ results = validate_utxo_data([(utxo, priv)], retrieve=True, utxo_address_type=utxo_address_type) if not results: return False assert results[0][0] == utxo amt = results[0][1] ins = [utxo] estfee = estimate_tx_fee(1, len(destaddrs), txtype=utxo_address_type) outs = [] share = int((amt - estfee) / len(destaddrs)) fee = amt - share*len(destaddrs) assert fee >= estfee log.info("Using fee: " + str(fee)) for i, addr in enumerate(destaddrs): outs.append({'address': addr, 'value': share}) tx = btc.make_shuffled_tx(ins, outs, version=2, locktime=compute_tx_locktime()) amtforsign = amt if utxo_address_type != "p2pkh" else None rawpriv, _ = BTCEngine.wif_to_privkey(priv) if utxo_address_type == "p2wpkh": native = utxo_address_type else: native = False success, msg = btc.sign(tx, 0, rawpriv, amount=amtforsign, native=native) assert success, msg return tx
def sign(utxo, priv, destaddrs, segwit=True): """Sign a tx sending the amount amt, from utxo utxo, equally to each of addresses in list destaddrs, after fees; the purpose is to create a large number of utxos. If segwit=True the (single) utxo is assumed to be of type segwit p2sh/p2wpkh. """ results = validate_utxo_data([(utxo, priv)], retrieve=True, segwit=segwit) if not results: return False assert results[0][0] == utxo amt = results[0][1] ins = [utxo] # TODO extend to other utxo types txtype = 'p2sh-p2wpkh' if segwit else 'p2pkh' estfee = estimate_tx_fee(1, len(destaddrs), txtype=txtype) outs = [] share = int((amt - estfee) / len(destaddrs)) fee = amt - share * len(destaddrs) assert fee >= estfee log.info("Using fee: " + str(fee)) for i, addr in enumerate(destaddrs): outs.append({'address': addr, 'value': share}) tx = btc.mktx(ins, outs) amtforsign = amt if segwit else None rawpriv, _ = BTCEngine.wif_to_privkey(priv) success, msg = btc.sign(tx, 0, rawpriv, amount=amtforsign) assert success, msg return tx
def get_utxo_info(upriv, utxo_binary=False): """Verify that the input string parses correctly as (utxo, priv) and return that. If `utxo_binary` is true, the first element of that return tuple is the standard internal form (txid-in-binary, index-as-int). """ try: u, priv = upriv.split(',') u = u.strip() priv = priv.strip() success, utxo_bin = utxostr_to_utxo(u) assert success, utxo except: #not sending data to stdout in case privkey info jmprint("Failed to parse utxo information for utxo", "error") raise try: # see note below for why keytype is ignored, and note that # this calls read_privkey to validate. raw, _ = BTCEngine.wif_to_privkey(priv) except: jmprint("failed to parse privkey, make sure it's WIF compressed format.", "error") raise utxo_to_return = utxo_bin if utxo_binary else u return utxo_to_return, priv
def generate_single_podle_sig(u, priv, i): """Make a podle entry for key priv at index i, using a dummy utxo value. This calls the underlying 'raw' code based on the class PoDLE, not the library 'generate_podle' which intelligently searches and updates commitments. """ # Convert priv from wif; note that wallet type # isn't relevant since we only work with pubkeys in PoDLE: rawpriv, _ = BTCEngine.wif_to_privkey(priv) podle = PoDLE(u, rawpriv) r = podle.generate_podle(i) return (r['P'], r['P2'], r['sig'], r['e'], r['commit'])
def generate_single_podle_sig(u, priv, i): """Make a podle entry for key priv at index i, using a dummy utxo value. This calls the underlying 'raw' code based on the class PoDLE, not the library 'generate_podle' which intelligently searches and updates commitments. """ #Convert priv from wif; require P2SH-P2WPKH keys rawpriv, keytype = BTCEngine.wif_to_privkey(priv) assert keytype == BTC_P2SH_P2WPKH podle = PoDLE(u, rawpriv) r = podle.generate_podle(i) return (r['P'], r['P2'], r['sig'], r['e'], r['commit'])
def validate_utxo_data(utxo_datas, retrieve=False, utxo_address_type="p2wpkh"): """For each (utxo, privkey), first convert the privkey and convert to address, then use the blockchain instance to look up the utxo and check that its address field matches. If retrieve is True, return the set of utxos and their values. If segwit is true, assumes a p2sh wrapped p2wpkh, i.e. native segwit is NOT currently supported here. If segwit is false, p2pkh is assumed. """ results = [] for u, priv in utxo_datas: success, utxostr = utxo_to_utxostr(u) if not success: jmprint("Invalid utxo format: " + str(u), "error") sys.exit(EXIT_FAILURE) jmprint('validating this utxo: ' + utxostr, "info") # as noted in `ImportWalletMixin` code comments, there is not # yet a functional auto-detection of key type from WIF, so the # second argument is ignored; we assume p2sh-p2wpkh if segwit=True, # p2pkh if segwit=False, and p2wpkh if segwit="native" (slightly # ugly, just done for backwards compat.). if utxo_address_type == "p2wpkh": engine = BTC_P2WPKH elif utxo_address_type == "p2sh-p2wpkh": engine = BTC_P2SH_P2WPKH elif utxo_address_type == "p2pkh": engine = BTC_P2PKH else: raise Exception("Invalid argument: " + str(utxo_address_type)) rawpriv, _ = BTCEngine.wif_to_privkey(priv) addr = engine.privkey_to_address(rawpriv) jmprint('claimed address: ' + addr, "info") res = jm_single().bc_interface.query_utxo_set([u]) if len(res) != 1 or None in res: jmprint("utxo not found on blockchain: " + utxostr, "error") return False if res[0]['address'] != addr: jmprint( "privkey corresponds to the wrong address for utxo: " + utxostr, "error") jmprint( "blockchain returned address: {}".format(res[0]['address']), "error") jmprint("your privkey gave this address: " + addr, "error") return False if retrieve: results.append((u, res[0]['value'])) jmprint('all utxos validated OK', "success") if retrieve: return results return True
def test_wif_privkeys_invalid(setup_keys): #first try to create wif privkey from key of wrong length bad_privs = [b'\x01\x02' * 17] #some silly private key but > 33 bytes #next try to create wif with correct length but wrong compression byte bad_privs.append(b'\x07' * 32 + b'\x02') for priv in bad_privs: with pytest.raises(Exception) as e_info: fake_wif = BTCEngine.privkey_to_wif(priv) #Create a wif with wrong length bad_wif1 = btc.bin_to_b58check(b'\x01\x02' * 34, b'\x80') #Create a wif with wrong compression byte bad_wif2 = btc.bin_to_b58check(b'\x07' * 33, b'\x80') for bw in [bad_wif1, bad_wif2]: with pytest.raises(Exception) as e_info: fake_priv, keytype = BTCEngine.wif_to_privkey(bw) #Some invalid b58 from bitcoin repo; #none of these are valid as any kind of key or address with open(os.path.join(testdir, "base58_keys_invalid.json"), "r") as f: json_data = f.read() invalid_key_list = json.loads(json_data) for k in invalid_key_list: bad_key = k[0] for netval in ["mainnet", "testnet"]: #if using pytest -s ; sanity check to see what's actually being tested print('testing this key: ' + bad_key) #should throw exception with pytest.raises(Exception) as e_info: from_wif_key, keytype = BTCEngine.wif_to_privkey(bad_key) #in case the b58 check encoding is valid, we should #also check if the leading version byte is in the #expected set, and throw an error if not. if chr(btc.get_version_byte(bad_key)) not in b'\x80\xef': raise Exception("Invalid version byte")
def validate_utxo_data(utxo_datas, retrieve=False, utxo_address_type="p2wpkh"): """For each (utxo, privkey), first convert the privkey and convert to address, then use the blockchain instance to look up the utxo and check that its address field matches. If retrieve is True, return the set of utxos and their values. """ results = [] for u, priv in utxo_datas: success, utxostr = utxo_to_utxostr(u) if not success: jmprint("Invalid utxo format: " + str(u), "error") sys.exit(EXIT_FAILURE) jmprint('validating this utxo: ' + utxostr, "info") # as noted in `ImportWalletMixin` code comments, there is not # yet a functional auto-detection of key type from WIF, hence # the need for this additional switch: if utxo_address_type == "p2wpkh": engine = BTC_P2WPKH elif utxo_address_type == "p2sh-p2wpkh": engine = BTC_P2SH_P2WPKH elif utxo_address_type == "p2pkh": engine = BTC_P2PKH else: raise Exception("Invalid argument: " + str(utxo_address_type)) rawpriv, _ = BTCEngine.wif_to_privkey(priv) addr = engine.privkey_to_address(rawpriv) jmprint('claimed address: ' + addr, "info") res = jm_single().bc_interface.query_utxo_set([u]) if len(res) != 1 or None in res: jmprint("utxo not found on blockchain: " + utxostr, "error") return False returned_addr = engine.script_to_address(res[0]['script']) if returned_addr != addr: return print_failed_addr_match(utxostr, addr, returned_addr) if retrieve: results.append((u, res[0]['value'])) jmprint('all utxos validated OK', "success") if retrieve: return results return True
def test_wif_privkeys_valid(setup_keys): with open(os.path.join(testdir, "base58_keys_valid.json"), "r") as f: json_data = f.read() valid_keys_list = json.loads(json_data) for a in valid_keys_list: key, hex_key, prop_dict = a if prop_dict["isPrivkey"]: netval = "testnet" if prop_dict["isTestnet"] else "mainnet" jm_single().config.set("BLOCKCHAIN", "network", netval) print('testing this key: ' + key) assert btc.get_version_byte( key) in b'\x80\xef', "not valid network byte" comp = prop_dict["isCompressed"] if not comp: # we only handle compressed keys continue from_wif_key, keytype = BTCEngine.wif_to_privkey(key) expected_key = hextobin(hex_key) + b"\x01" assert from_wif_key == expected_key, "Incorrect key decoding: " + \ str(from_wif_key) + ", should be: " + str(expected_key) jm_single().config.set("BLOCKCHAIN", "network", "testnet")
def validate_utxo_data(utxo_datas, retrieve=False, segwit=False): """For each txid: N, privkey, first convert the privkey and convert to address, then use the blockchain instance to look up the utxo and check that its address field matches. If retrieve is True, return the set of utxos and their values. If segwit is true, assumes a p2sh wrapped p2wpkh, i.e. native segwit is NOT currently supported here. If segwit is false, p2pkh is assumed. """ results = [] for u, priv in utxo_datas: jmprint('validating this utxo: ' + str(u), "info") # as noted in `ImportWalletMixin` code comments, there is not # yet a functional auto-detection of key type from WIF, so the # second argument is ignored; we assume p2sh-p2wpkh if segwit, # else we assume p2pkh. engine = BTC_P2SH_P2WPKH if segwit else BTC_P2PKH rawpriv, _ = BTCEngine.wif_to_privkey(priv) addr = engine.privkey_to_address(rawpriv) jmprint('claimed address: ' + addr, "info") res = jm_single().bc_interface.query_utxo_set([u]) if len(res) != 1 or None in res: jmprint("utxo not found on blockchain: " + str(u), "error") return False if res[0]['address'] != addr: jmprint( "privkey corresponds to the wrong address for utxo: " + str(u), "error") jmprint( "blockchain returned address: {}".format(res[0]['address']), "error") jmprint("your privkey gave this address: " + addr, "error") return False if retrieve: results.append((u, res[0]['value'])) jmprint('all utxos validated OK', "success") if retrieve: return results return True
def new_wallet_from_data(data, file_name): print("Creating new wallet file.") new_pw = cli_get_wallet_passphrase_check() if new_pw is False: return False storage = Storage(file_name, create=True, password=new_pw) wallet_cls = get_wallet_cls(wtype=get_configured_wallet_type(False)) kwdata = { 'entropy': data['entropy'], 'timestamp': data.get('creation_time'), 'max_mixdepth': get_max_mixdepth(data) } if 'entropy_ext' in data: kwdata['entropy_extension'] = data['entropy_ext'] wallet_cls.initialize(storage, data['network'], **kwdata) wallet = wallet_cls(storage) if 'index_cache' in data: for md, indices in enumerate(data['index_cache']): wallet.set_next_index(md, BaseWallet.ADDRESS_TYPE_EXTERNAL, indices[0], force=True) wallet.set_next_index(md, BaseWallet.ADDRESS_TYPE_INTERNAL, indices[1], force=True) if 'imported' in data: for md in data['imported']: for privkey in data['imported'][md]: privkey += b'\x01' wif = BTCEngine.privkey_to_wif(privkey) wallet.import_private_key(md, wif) wallet.save() wallet.close() return True
def get_utxo_info(upriv): """Verify that the input string parses correctly as (utxo, priv) and return that. """ try: u, priv = upriv.split(',') u = u.strip() priv = priv.strip() success, utxo = utxostr_to_utxo(u) assert success, utxo except: #not sending data to stdout in case privkey info jmprint("Failed to parse utxo information for utxo", "error") raise try: # see note below for why keytype is ignored, and note that # this calls read_privkey to validate. raw, _ = BTCEngine.wif_to_privkey(priv) except: jmprint( "failed to parse privkey, make sure it's WIF compressed format.", "error") raise return u, priv