def get_address_generator(script_pre, script_post, vbyte): counter = 0 while True: script = script_pre + struct.pack(b'=LQQ', 0, 0, counter) + script_post addr = btc.script_to_address(script, vbyte) yield addr, binascii.hexlify(script).decode('ascii') counter += 1
def add_tx_notify(self, txd, unconfirmfun, confirmfun, spentfun, notifyaddr, timeoutfun=None): if not self.notifythread: self.notifythread = BitcoinCoreNotifyThread(self) self.notifythread.start() one_addr_imported = False for outs in txd['outs']: addr = btc.script_to_address(outs['script'], get_p2pk_vbyte()) if self.rpc('getaccount', [addr]) != '': one_addr_imported = True break if not one_addr_imported: self.rpc('importaddress', [notifyaddr, 'joinmarket-notify', False]) tx_output_set = set([(sv['script'], sv['value']) for sv in txd['outs']]) self.txnotify_fun.append( (btc.txhash(btc.serialize(txd)), tx_output_set, unconfirmfun, confirmfun, spentfun, timeoutfun, False)) #create unconfirm timeout here, create confirm timeout in the other thread if timeoutfun: threading.Timer(cs_single().config.getint('TIMEOUT', 'unconfirm_timeout_sec'), bitcoincore_timeout_callback, args=(False, tx_output_set, self.txnotify_fun, timeoutfun)).start()
def test_create_p2sh_output_tx(setup_tx_creation, nw, wallet_structures, mean_amt, sdev_amt, amount, pubs, k): wallets = make_wallets(nw, wallet_structures, mean_amt, sdev_amt) for w in wallets.values(): sync_wallet(w['wallet'], fast=True) for k, w in enumerate(wallets.values()): wallet = w['wallet'] ins_full = wallet.select_utxos(0, amount) script = bitcoin.mk_multisig_script(pubs, k) output_addr = bitcoin.script_to_address(script, vbyte=196) txid = make_sign_and_push(ins_full, wallet, amount, output_addr=output_addr) assert txid
def tx_watcher(self, txd, unconfirmfun, confirmfun, spentfun, c, n): """Called at a polling interval, checks if the given deserialized transaction (which must be fully signed) is (a) broadcast, (b) confirmed and (c) spent from. (c, n ignored in electrum version, just supports registering first confirmation). TODO: There is no handling of conflicts here. """ txid = btc.txhash(btc.serialize(txd)) wl = self.tx_watcher_loops[txid] #first check if in mempool (unconfirmed) #choose an output address for the query. Filter out #p2pkh addresses, assume p2sh (thus would fail to find tx on #some nonstandard script type) addr = None for i in range(len(txd['outs'])): if not btc.is_p2pkh_script(txd['outs'][i]['script']): addr = btc.script_to_address(txd['outs'][i]['script'], get_p2sh_vbyte()) break if not addr: log.error("Failed to find any p2sh output, cannot be a standard " "joinmarket transaction, fatal error!") reactor.stop() return unconftxs_res = self.get_from_electrum( 'blockchain.address.get_mempool', addr, blocking=True).get('result') unconftxs = [str(t['tx_hash']) for t in unconftxs_res] if not wl[1] and txid in unconftxs: jmprint("Tx: " + str(txid) + " seen on network.", "info") unconfirmfun(txd, txid) wl[1] = True return conftx = self.get_from_electrum('blockchain.address.listunspent', addr, blocking=True).get('result') conftxs = [str(t['tx_hash']) for t in conftx] if not wl[2] and len(conftxs) and txid in conftxs: jmprint("Tx: " + str(txid) + " is confirmed.", "info") confirmfun(txd, txid, 1) wl[2] = True #Note we do not stop the monitoring loop when #confirmations occur, since we are also monitoring for spending. return if not spentfun or wl[3]: return
def test_spend_freeze_script(setup_tx_creation): ensure_bip65_activated() wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet'] wallet_service.sync_wallet(fast=True) mediantime = jm_single().bc_interface.rpc("getblockchaininfo", [])["mediantime"] timeoffset_success_tests = [(2, False), (-60*60*24*30, True), (60*60*24*30, False)] for timeoffset, required_success in timeoffset_success_tests: #generate keypair priv = "aa"*32 + "01" pub = unhexlify(bitcoin.privkey_to_pubkey(priv)) addr_locktime = mediantime + timeoffset redeem_script = bitcoin.mk_freeze_script(pub, addr_locktime) script_pub_key = bitcoin.redeem_script_to_p2wsh_script(redeem_script) regtest_vbyte = 100 addr = bitcoin.script_to_address(script_pub_key, vbyte=regtest_vbyte) #fund frozen funds address amount = 100000000 funding_ins_full = wallet_service.select_utxos(0, amount) funding_txid = make_sign_and_push(funding_ins_full, wallet_service, amount, output_addr=addr) assert funding_txid #spend frozen funds frozen_in = funding_txid + ":0" output_addr = wallet_service.get_internal_addr(1) miner_fee = 5000 outs = [{'value': amount - miner_fee, 'address': output_addr}] tx = bitcoin.mktx([frozen_in], outs, locktime=addr_locktime+1) i = 0 sig = bitcoin.get_p2sh_signature(tx, i, redeem_script, priv, amount) assert bitcoin.verify_tx_input(tx, i, script_pub_key, sig, pub, scriptCode=redeem_script, amount=amount) tx = bitcoin.apply_freeze_signature(tx, i, redeem_script, sig) push_success = jm_single().bc_interface.pushtx(tx) assert push_success == required_success
def test_script_to_address(setup_tx_creation): sample_script = "a914307f099a3bfedec9a09682238db491bade1b467f87" assert bitcoin.script_to_address( sample_script, vbyte=5) == "367SYUMqo1Fi4tQsycnmCtB6Ces1Z7EZLH" assert bitcoin.script_to_address( sample_script, vbyte=196) == "2MwfecDHsQTm4Gg3RekQdpqAMR15BJrjfRF"
def add_tx_notify(self, txd, unconfirmfun, confirmfun, notifyaddr, wallet_name=None, timeoutfun=None, spentfun=None, txid_flag=True, n=0, c=1, vb=None): """Given a deserialized transaction txd, callback functions for broadcast and confirmation of the transaction, an address to import, and a callback function for timeout, set up a polling loop to check for events on the transaction. Also optionally set to trigger "confirmed" callback on number of confirmations c. Also checks for spending (if spentfun is not None) of the outpoint n. If txid_flag is True, we create a watcher loop on the txid (hence only really usable in a segwit context, and only on fully formed transactions), else we create a watcher loop on the output set of the transaction (taken from the outs field of the txd). """ if not vb: vb = get_p2pk_vbyte() if isinstance(self, BitcoinCoreInterface) or isinstance( self, RegtestBitcoinCoreInterface): #This code ensures that a walletnotify is triggered, by #ensuring that at least one of the output addresses is #imported into the wallet (note the sweep special case, where #none of the output addresses belong to me). one_addr_imported = False for outs in txd['outs']: addr = btc.script_to_address(outs['script'], vb) try: if self.is_address_imported(addr): one_addr_imported = True break except JsonRpcError as e: log.debug("Failed to getaccount for address: " + addr) log.debug("This is normal for bech32 addresses.") continue if not one_addr_imported: try: self.rpc('importaddress', [notifyaddr, 'joinmarket-notify', False]) except JsonRpcError as e: #In edge case of address already controlled #by another account, warn but do not quit in middle of tx. #Can occur if destination is owned in Core wallet. if e.code == -4 and e.message == "The wallet already " + \ "contains the private key for this address or script": log.warn("WARNING: Failed to import address: " + notifyaddr) #No other error should be possible else: raise #Warning! In case of txid_flag false, this is *not* a valid txid, #but only a hash of an incomplete transaction serialization. txid = btc.txhash(btc.serialize(txd)) if not txid_flag: tx_output_set = set([(sv['script'], sv['value']) for sv in txd['outs']]) loop = task.LoopingCall(self.outputs_watcher, wallet_name, notifyaddr, tx_output_set, unconfirmfun, confirmfun, timeoutfun) log.debug("Created watcher loop for address: " + notifyaddr) loopkey = notifyaddr else: loop = task.LoopingCall(self.tx_watcher, txd, unconfirmfun, confirmfun, spentfun, c, n) log.debug("Created watcher loop for txid: " + txid) loopkey = txid self.tx_watcher_loops[loopkey] = [loop, False, False, False] #Hardcoded polling interval, but in any case it can be very short. loop.start(5.0) #Give up on un-broadcast transactions and broadcast but not confirmed #transactions as per settings in the config. reactor.callLater( float(jm_single().config.get("TIMEOUT", "unconfirm_timeout_sec")), self.tx_network_timeout, loopkey) confirm_timeout_sec = int(jm_single().config.get( "TIMEOUT", "confirm_timeout_hours")) * 3600 reactor.callLater(confirm_timeout_sec, self.tx_timeout, txd, loopkey, timeoutfun)
def script_to_address(cls, script): return btc.script_to_address(script, vbyte=cls.VBYTE)
def pubkey_to_address(cls, pubkey): script = cls.pubkey_to_script(pubkey) return btc.script_to_address(script, cls.VBYTE)
def scan_for_coinjoins(privkey, amount, filename): """Given a file which contains encrypted coinjoin proposals, and a private key for a pubkey with a known utxo existing which we can spend, scan the entries in the file, all assumed to be ECIES encrypted to a pubkey, for one which is encrypted to *this* pubkey, if found, output the retrieved partially signed transaction, and destination key, address to a list which is returned to the caller. Only if the retrieved coinjoin transaction passes basic checks on validity in terms of amount paid, is it returned. This is an elementary implementation that will obviously fail any performance test (i.e. moderately large lists). Note that the tweaked output address must be of type p2sh/p2wpkh. """ try: with open(filename, "rb") as f: msgs = f.readlines() except: print("Failed to read from file: ", filename) return valid_coinjoins = [] for msg in msgs: try: decrypted_msg = decrypt_message(msg, privkey) tweak, tx = deserialize_coinjoin_proposal(decrypted_msg) except: print("Could not decrypt message, skipping") continue if not tweak: print("Could not decrypt message, reason: " + str(tx)) continue #We analyse the content of the transaction to check if it follows #our requirements try: deserialized_tx = btc.deserialize(tx) except: print("Proposed transaction is not correctly formatted, skipping.") continue #construct our receiving address according to the tweak pubkey = btc.privkey_to_pubkey(privkey) tweak, destnpt, my_destn_addr = create_recipient_address(pubkey, tweak=tweak, segwit=True) #add_privkeys requires both inputs to be compressed (or un-) consistently. tweak_priv = tweak + "01" my_destn_privkey = btc.add_privkeys(tweak_priv, privkey, True) my_output_index = -1 for i, o in enumerate(deserialized_tx['outs']): addr = btc.script_to_address(o['script'], get_p2sh_vbyte()) if addr == my_destn_addr: print('found our output address: ', my_destn_addr) my_output_index = i break if my_output_index == -1: print("Proposal doesn't contain our output address, rejecting") continue my_output_amount = deserialized_tx['outs'][i]['value'] required_amount = amount - 2 * estimate_tx_fee(3, 3, 'p2sh-p2wpkh') if my_output_amount < required_amount: print("Proposal pays too little, difference is: ", required_amount - my_output_amount) continue #now we know output is acceptable to us, we should check that the #ctrprty input is signed and the other input is ours, but will do this #later; if it's not, it just won't work so NBD for now. valid_coinjoins.append((my_destn_addr, my_destn_privkey, tx)) return valid_coinjoins
def privkey_to_address(cls, privkey): script = cls.key_to_script(privkey) return btc.script_to_address(script, cls.VBYTE)