def make_sign_and_push(ins_full,
                       wallet,
                       amount,
                       output_addr=None,
                       change_addr=None,
                       hashcode=btc.SIGHASH_ALL,
                       estimate_fee = False):
    """Utility function for easily building transactions
    from wallets
    """
    total = sum(x['value'] for x in ins_full.values())
    ins = list(ins_full.keys())
    #random output address and change addr
    output_addr = wallet.get_new_addr(1, 1) if not output_addr else output_addr
    change_addr = wallet.get_new_addr(1, 0) if not change_addr else change_addr
    fee_est = estimate_tx_fee(len(ins), 2) if estimate_fee else 10000
    outs = [{'value': amount,
             'address': output_addr}, {'value': total - amount - fee_est,
                                       'address': change_addr}]

    de_tx = btc.deserialize(btc.mktx(ins, outs))
    scripts = {}
    for index, ins in enumerate(de_tx['ins']):
        utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index'])
        script = wallet.addr_to_script(ins_full[utxo]['address'])
        scripts[index] = (script, ins_full[utxo]['value'])
    binarize_tx(de_tx)
    de_tx = wallet.sign_tx(de_tx, scripts, hashcode=hashcode)
    #pushtx returns False on any error
    tx = binascii.hexlify(btc.serialize(de_tx)).decode('ascii')
    push_succeed = jm_single().bc_interface.pushtx(tx)
    if push_succeed:
        return btc.txhash(tx)
    else:
        return False
예제 #2
0
    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()
예제 #3
0
 def sign_transaction(cls, tx, index, privkey, *args, **kwargs):
     hashcode = kwargs.get('hashcode') or btc.SIGHASH_ALL
     return btc.sign(btc.serialize(tx),
                     index,
                     privkey,
                     hashcode=hashcode,
                     amount=None,
                     native=False)
예제 #4
0
def make_sign_and_push(ins_full,
                       wallet_service,
                       amount,
                       output_addr=None,
                       change_addr=None,
                       hashcode=btc.SIGHASH_ALL,
                       estimate_fee=False):
    """Utility function for easily building transactions
    from wallets
    """
    assert isinstance(wallet_service, WalletService)
    total = sum(x['value'] for x in ins_full.values())
    ins = list(ins_full.keys())
    #random output address and change addr
    output_addr = wallet_service.get_new_addr(
        1, 1) if not output_addr else output_addr
    change_addr = wallet_service.get_new_addr(
        0, 1) if not change_addr else change_addr
    fee_est = estimate_tx_fee(len(ins), 2) if estimate_fee else 10000
    outs = [{
        'value': amount,
        'address': output_addr
    }, {
        'value': total - amount - fee_est,
        'address': change_addr
    }]

    de_tx = btc.deserialize(btc.mktx(ins, outs))
    scripts = {}
    for index, ins in enumerate(de_tx['ins']):
        utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index'])
        script = wallet_service.addr_to_script(ins_full[utxo]['address'])
        scripts[index] = (script, ins_full[utxo]['value'])
    binarize_tx(de_tx)
    de_tx = wallet_service.sign_tx(de_tx, scripts, hashcode=hashcode)
    #pushtx returns False on any error
    push_succeed = jm_single().bc_interface.pushtx(btc.serialize(de_tx))
    if push_succeed:
        txid = btc.txhash(btc.serialize(de_tx))
        # in normal operation this happens automatically
        # but in some tests there is no monitoring loop:
        wallet_service.process_new_tx(de_tx, txid)
        return txid
    else:
        return False
예제 #5
0
def test_segwit_valid_txs(setup_segwit):
    with open("test/tx_segwit_valid.json", "r") as f:
        json_data = f.read()
    valid_txs = json.loads(json_data)
    for j in valid_txs:
        if len(j) < 2:
            continue
        deserialized_tx = btc.deserialize(str(j[1]))
        print pformat(deserialized_tx)
        assert btc.serialize(deserialized_tx) == str(j[1])
 def outputs_watcher(self, wallet_name, notifyaddr, tx_output_set,
                     unconfirmfun, confirmfun, timeoutfun):
     """Given a key for the watcher loop (notifyaddr), a wallet name (label),
     a set of outputs, and unconfirm, confirm and timeout callbacks,
     check to see if a transaction matching that output set has appeared in
     the wallet. Call the callbacks and update the watcher loop state.
     End the loop when the confirmation has been seen (no spent monitoring here).
     """
     wl = self.tx_watcher_loops[notifyaddr]
     txlist = self.rpc("listtransactions", ["*", 100, 0, True])
     for tx in txlist[::-1]:
         #changed syntax in 0.14.0; allow both syntaxes
         try:
             res = self.rpc("gettransaction", [tx["txid"], True])
         except:
             try:
                 res = self.rpc("gettransaction", [tx["txid"], 1])
             except JsonRpcError as e:
                 #This should never happen (gettransaction is a wallet rpc).
                 log.warn("Failed gettransaction call; JsonRpcError")
                 res = None
             except Exception as e:
                 log.warn("Failed gettransaction call; unexpected error:")
                 log.warn(str(e))
                 res = None
         if not res:
             continue
         if "confirmations" not in res:
             log.debug("Malformed gettx result: " + str(res))
             return
         txd = self.get_deser_from_gettransaction(res)
         if txd is None:
             continue
         txos = set([(sv['script'], sv['value']) for sv in txd['outs']])
         if not txos == tx_output_set:
             continue
         #Here we have found a matching transaction in the wallet.
         real_txid = btc.txhash(btc.serialize(txd))
         if not wl[1] and res["confirmations"] == 0:
             log.debug("Tx: " + str(real_txid) + " seen on network.")
             unconfirmfun(txd, real_txid)
             wl[1] = True
             return
         if not wl[2] and res["confirmations"] > 0:
             log.debug("Tx: " + str(real_txid) + " has " +
                       str(res["confirmations"]) + " confirmations.")
             confirmfun(txd, real_txid, res["confirmations"])
             wl[2] = True
             wl[0].stop()
             return
         if res["confirmations"] < 0:
             log.debug("Tx: " + str(real_txid) +
                       " has a conflict. Abandoning.")
             wl[0].stop()
             return
예제 #7
0
 def sign_transaction(cls,
                      tx,
                      index,
                      privkey,
                      amount,
                      hashcode=btc.SIGHASH_ALL,
                      **kwargs):
     assert amount is not None
     return btc.sign(btc.serialize(tx),
                     index,
                     privkey,
                     hashcode=hashcode,
                     amount=amount,
                     native=True)
예제 #8
0
def test_signing_simple(setup_wallet, wallet_cls, type_check):
    jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
    storage = VolatileStorage()
    wallet_cls.initialize(storage, get_network())
    wallet = wallet_cls(storage)
    utxo = fund_wallet_addr(wallet, wallet.get_internal_addr(0))
    tx = btc.deserialize(btc.mktx(['{}:{}'.format(hexlify(utxo[0]).decode('ascii'), utxo[1])],
                                  ['00'*17 + ':' + str(10**8 - 9000)]))
    binarize_tx(tx)
    script = wallet.get_script(0, 1, 0)
    wallet.sign_tx(tx, {0: (script, 10**8)})
    type_check(tx)
    txout = jm_single().bc_interface.pushtx(hexlify(btc.serialize(tx)).decode('ascii'))
    assert txout
예제 #9
0
def test_signing_simple(setup_wallet, wallet_cls, type_check):
    jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
    storage = VolatileStorage()
    wallet_cls.initialize(storage, get_network())
    wallet = wallet_cls(storage)
    utxo = fund_wallet_addr(wallet, wallet.get_internal_addr(0))
    # The dummy output is constructed as an unspendable p2sh:
    tx = btc.deserialize(btc.mktx(['{}:{}'.format(
        hexlify(utxo[0]).decode('ascii'), utxo[1])],
        [btc.p2sh_scriptaddr(b"\x00",magicbyte=196) + ':' + str(10**8 - 9000)]))
    script = wallet.get_script(0, 1, 0)
    tx = wallet.sign_tx(tx, {0: (script, 10**8)})
    type_check(tx)
    txout = jm_single().bc_interface.pushtx(btc.serialize(tx))
    assert txout
    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
예제 #11
0
def test_signing_imported(setup_wallet, wif, keytype, type_check):
    jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
    storage = VolatileStorage()
    SegwitLegacyWallet.initialize(storage, get_network())
    wallet = SegwitLegacyWallet(storage)

    MIXDEPTH = 0
    path = wallet.import_private_key(MIXDEPTH, wif, keytype)
    utxo = fund_wallet_addr(wallet, wallet.get_addr_path(path))
    tx = btc.deserialize(
        btc.mktx(['{}:{}'.format(hexlify(utxo[0]).decode('ascii'), utxo[1])],
                 ['00' * 17 + ':' + str(10**8 - 9000)]))
    script = wallet.get_script_path(path)
    tx = wallet.sign_tx(tx, {0: (script, 10**8)})
    type_check(tx)
    txout = jm_single().bc_interface.pushtx(btc.serialize(tx))
    assert txout
예제 #12
0
def test_signing_simple(setup_wallet, wallet_cls, type_check):
    jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
    storage = VolatileStorage()
    wallet_cls.initialize(storage, get_network())
    wallet = wallet_cls(storage)
    utxo = fund_wallet_addr(wallet, wallet.get_internal_addr(0))
    # The dummy output is of length 25 bytes, because, for SegwitWallet, we else
    # trigger the tx-size-small DOS limit in Bitcoin Core (82 bytes is the
    # smallest "normal" transaction size (non-segwit size, ie no witness)
    tx = btc.deserialize(
        btc.mktx(['{}:{}'.format(hexlify(utxo[0]).decode('ascii'), utxo[1])],
                 ['00' * 25 + ':' + str(10**8 - 9000)]))
    script = wallet.get_script(0, 1, 0)
    tx = wallet.sign_tx(tx, {0: (script, 10**8)})
    type_check(tx)
    txout = jm_single().bc_interface.pushtx(btc.serialize(tx))
    assert txout
예제 #13
0
    def sign_transaction(cls,
                         tx,
                         index,
                         privkey_locktime,
                         amount,
                         hashcode=btc.SIGHASH_ALL,
                         **kwargs):
        assert amount is not None

        privkey, locktime = privkey_locktime
        privkey = hexlify(privkey).decode()
        pubkey = btc.privkey_to_pubkey(privkey)
        pubkey = unhexlify(pubkey)
        redeem_script = cls.pubkey_to_script_code((pubkey, locktime))
        tx = btc.serialize(tx)
        sig = btc.get_p2sh_signature(tx, index, redeem_script, privkey, amount)
        return btc.apply_freeze_signature(tx, index, redeem_script, sig)
def test_serialization_roundtrip2():
    #Data extracted from:
    #https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_valid.json
    #These are a variety of rather strange edge case transactions, which are
    #still valid.
    #Note that of course this is only a serialization, not validity test, so
    #only currently of very limited significance
    with open(os.path.join(testdir, "tx_valid.json"), "r") as f:
        json_data = f.read()
    valid_txs = json.loads(json_data)
    for j in valid_txs:
        #ignore comment entries
        if len(j) < 2:
            continue
        print j
        deserialized = btc.deserialize(str(j[0]))
        print deserialized
        assert j[0] == btc.serialize(deserialized)
예제 #15
0
def test_signing_imported(setup_wallet, wif, keytype, type_check):
    jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
    storage = VolatileStorage()
    SegwitLegacyWallet.initialize(storage, get_network())
    wallet = SegwitLegacyWallet(storage)

    MIXDEPTH = 0
    path = wallet.import_private_key(MIXDEPTH, wif, keytype)
    utxo = fund_wallet_addr(wallet, wallet.get_address_from_path(path))
    # The dummy output is constructed as an unspendable p2sh:
    tx = btc.deserialize(btc.mktx(['{}:{}'.format(
        hexlify(utxo[0]).decode('ascii'), utxo[1])],
        [btc.p2sh_scriptaddr(b"\x00",magicbyte=196) + ':' + str(10**8 - 9000)]))
    script = wallet.get_script_from_path(path)
    tx = wallet.sign_tx(tx, {0: (script, 10**8)})
    type_check(tx)
    txout = jm_single().bc_interface.pushtx(btc.serialize(tx))
    assert txout
예제 #16
0
def test_timelocked_output_signing(setup_wallet):
    jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
    ensure_bip65_activated()
    storage = VolatileStorage()
    SegwitLegacyWalletFidelityBonds.initialize(storage, get_network())
    wallet = SegwitLegacyWalletFidelityBonds(storage)

    index = 0
    timenumber = 0
    script = wallet.get_script_and_update_map(
        FidelityBondMixin.FIDELITY_BOND_MIXDEPTH,
        FidelityBondMixin.BIP32_TIMELOCK_ID, index, timenumber)
    utxo = fund_wallet_addr(wallet, wallet.script_to_addr(script))
    timestamp = wallet._time_number_to_timestamp(timenumber)

    tx = btc.deserialize(btc.mktx(['{}:{}'.format(
        hexlify(utxo[0]).decode('ascii'), utxo[1])],
        [btc.p2sh_scriptaddr(b"\x00",magicbyte=196) + ':' + str(10**8 - 9000)],
        locktime=timestamp+1))
    tx = wallet.sign_tx(tx, {0: (script, 10**8)})
    txout = jm_single().bc_interface.pushtx(btc.serialize(tx))
    assert txout
예제 #17
0
 def attach_signatures(self):
     """Once all signatures are available,
     they can be attached to construct a "fully_signed_tx"
     form of the transaction ready for broadcast (as distinct
     from the "base_form" without any signatures attached).
     """
     assert self.fully_signed()
     self.fully_signed_tx = copy.deepcopy(self.base_form)
     for idx in range(len(self.ins)):
         tp = self.template.ins[idx].spk_type
         assert tp in ["NN", "p2sh-p2wpkh"]
         if tp == "NN":
             self.fully_signed_tx = btc.apply_p2wsh_multisignatures(
                 self.fully_signed_tx, idx,
                 self.signing_redeem_scripts[idx], self.signatures[idx])
         else:
             k = self.keys["ins"][idx][self.keys["ins"][idx].keys()[0]]
             dtx = btc.deserialize(self.fully_signed_tx)
             dtx["ins"][idx][
                 "script"] = "16" + btc.pubkey_to_p2sh_p2wpkh_script(k)
             dtx["ins"][idx]["txinwitness"] = [self.signatures[idx][0], k]
             self.fully_signed_tx = btc.serialize(dtx)
예제 #18
0
    def on_JM_TX_RECEIVED(self, nick, txhex, offer):
        # "none" flags p2ep protocol; pass through to the generic
        # on_tx handler for that:
        if offer == "none":
            return self.on_p2ep_tx_received(nick, txhex)

        offer = json.loads(offer)
        retval = self.client.on_tx_received(nick, txhex, offer)
        if not retval[0]:
            jlog.info("Maker refuses to continue on receipt of tx")
        else:
            sigs = retval[1]
            self.finalized_offers[nick] = offer
            tx = btc.deserialize(txhex)
            self.finalized_offers[nick]["txd"] = tx
            txid = btc.txhash(btc.serialize(tx))
            # we index the callback by the out-set of the transaction,
            # because the txid is not known until all scriptSigs collected
            # (hence this is required for Makers, but not Takers).
            # For more info see WalletService.transaction_monitor():
            txinfo = tuple((x["script"], x["value"]) for x in tx["outs"])
            self.client.wallet_service.register_callbacks(
                [self.unconfirm_callback], txinfo, "unconfirmed")
            self.client.wallet_service.register_callbacks(
                [self.confirm_callback], txinfo, "confirmed")

            task.deferLater(
                reactor,
                float(jm_single().config.getint("TIMEOUT",
                                                "unconfirm_timeout_sec")),
                self.client.wallet_service.check_callback_called, txinfo,
                self.unconfirm_callback, "unconfirmed",
                "transaction with outputs: " + str(txinfo) + " not broadcast.")

            d = self.callRemote(commands.JMTXSigs,
                                nick=nick,
                                sigs=json.dumps(sigs))
            self.defaultCallbacks(d)
        return {"accepted": True}
예제 #19
0
 def mktx(self):
     """First, construct input and output lists
     as for a normal transaction construction,
     using the OCCTemplateTx corresponding inputs
     and outputs as information.
     To do this completely requires txids for all inputs.
     Thus, this must be called for this OCCTx *after*
     it has been called for all parent txs.
     We ensure that the txid for this Tx is set here,
     and is attached to all the Outpoint objects for its
     outputs.
     """
     self.build_ins_from_template()
     self.build_outs_from_template()
     assert all([self.ins, self.outs])
     self.base_form = btc.mktx([x[0] for x in self.ins], self.outs)
     dtx = btc.deserialize(self.base_form)
     if self.locktime:
         dtx["ins"][0]["sequence"] = 0
         dtx["locktime"] = self.locktime
     #To set the txid, it's required that we set the
     #scriptSig and scriptPubkey objects. We don't yet
     #need to flag it segwit (we're not yet attaching
     #signatures) since we want txid not wtxid and the
     #former doesn't use segwit formatting anyway.
     for i, inp in enumerate(dtx["ins"]):
         sti = self.template.ins[i]
         if sti.spk_type == "p2sh-p2wpkh":
             inp["script"] = "16" + btc.pubkey_to_p2sh_p2wpkh_script(
                 self.keys["ins"][i][sti.counterparty])
         elif sti.spk_type == "NN":
             inp["script"] = ""
     self.txid = btc.txhash(btc.serialize(dtx))
     #by setting the txid of the outpoints, we allow child
     #transactions to know the outpoint references for their inputs.
     for to in self.template.outs:
         to.txid = self.txid
예제 #20
0
def direct_send(wallet,
                amount,
                mixdepth,
                destaddr,
                answeryes=False,
                accept_callback=None,
                info_callback=None):
    """Send coins directly from one mixdepth to one destination address;
    does not need IRC. Sweep as for normal sendpayment (set amount=0).
    If answeryes is True, callback/command line query is not performed.
    If accept_callback is None, command line input for acceptance is assumed,
    else this callback is called:
    accept_callback:
    ====
    args:
    deserialized tx, destination address, amount in satoshis, fee in satoshis
    returns:
    True if accepted, False if not
    ====
    The info_callback takes one parameter, the information message (when tx is
    pushed), and returns nothing.

    This function returns:
    The txid if transaction is pushed, False otherwise
    """
    #Sanity checks
    assert validate_address(destaddr)[0]
    assert isinstance(mixdepth, numbers.Integral)
    assert mixdepth >= 0
    assert isinstance(amount, numbers.Integral)
    assert amount >= 0
    assert isinstance(wallet, BaseWallet)

    from pprint import pformat
    txtype = wallet.get_txtype()
    if amount == 0:
        utxos = wallet.get_utxos_by_mixdepth()[mixdepth]
        if utxos == {}:
            log.error("There are no utxos in mixdepth: " + str(mixdepth) +
                      ", quitting.")
            return
        total_inputs_val = sum([va['value'] for u, va in iteritems(utxos)])
        fee_est = estimate_tx_fee(len(utxos), 1, txtype=txtype)
        outs = [{"address": destaddr, "value": total_inputs_val - fee_est}]
    else:
        #8 inputs to be conservative
        initial_fee_est = estimate_tx_fee(8, 2, txtype=txtype)
        utxos = wallet.select_utxos(mixdepth, amount + initial_fee_est)
        if len(utxos) < 8:
            fee_est = estimate_tx_fee(len(utxos), 2, txtype=txtype)
        else:
            fee_est = initial_fee_est
        total_inputs_val = sum([va['value'] for u, va in iteritems(utxos)])
        changeval = total_inputs_val - fee_est - amount
        outs = [{"value": amount, "address": destaddr}]
        change_addr = wallet.get_internal_addr(mixdepth)
        import_new_addresses(wallet, [change_addr])
        outs.append({"value": changeval, "address": change_addr})

    #Now ready to construct transaction
    log.info("Using a fee of : " + str(fee_est) + " satoshis.")
    if amount != 0:
        log.info("Using a change value of: " + str(changeval) + " satoshis.")
    txsigned = sign_tx(wallet, mktx(list(utxos.keys()), outs), utxos)
    log.info("Got signed transaction:\n")
    log.info(pformat(txsigned))
    tx = serialize(txsigned)
    log.info("In serialized form (for copy-paste):")
    log.info(tx)
    actual_amount = amount if amount != 0 else total_inputs_val - fee_est
    log.info("Sends: " + str(actual_amount) + " satoshis to address: " +
             destaddr)
    if not answeryes:
        if not accept_callback:
            if input(
                    'Would you like to push to the network? (y/n):')[0] != 'y':
                log.info(
                    "You chose not to broadcast the transaction, quitting.")
                return False
        else:
            accepted = accept_callback(pformat(txsigned), destaddr,
                                       actual_amount, fee_est)
            if not accepted:
                return False
    jm_single().bc_interface.pushtx(tx)
    txid = txhash(tx)
    successmsg = "Transaction sent: " + txid
    cb = log.info if not info_callback else info_callback
    cb(successmsg)
    return txid
예제 #21
0
def main():

    # sets up grpc connection to lnd
    channel = get_secure_channel()

    # note that the 'admin' macaroon already has the required
    # permissions for the walletkit request, so we don't need
    # that third macaroon.
    macaroon, signer_macaroon = get_macaroons(["admin", "signer"])

    # the main stub allows access to the default rpc commands:
    stub = lnrpc.LightningStub(channel)
    # the signer stub allows us to access the rpc for signing
    # transactions on our coins:
    stub_signer = signrpc.SignerStub(channel)
    # we also need a stub for the walletkit rpc to extract
    # public keys for addresses holding coins:
    stub_walletkit = walletrpc.WalletKitStub(channel)

    # Here we start the process to sign a custom tx.
    # 1. List unspent coins, get most recent ones (just an example).
    # 2. Get the pubkeys of those addresses.
    # 3. Get the next unused address in the wallet as destination.
    # 4. Build a transaction, (in future: optionally taking extra
    #    inputs and outputs from elsewhere).
    # 5. Use signOutputRaw rpc to sign the new transaction.
    # 6. Use the walletkit PublishTransaction to publish.

    # Just an example of retrieving basic info, not necessary:
    # Retrieve and display the wallet balance
    response = stub.WalletBalance(ln.WalletBalanceRequest(),
                                  metadata=[('macaroon', macaroon)])
    print("Current on-chain wallet balance: ", response.total_balance)

    inputs = get_our_coins(stub, macaroon) + get_other_coins()

    for inp in inputs:
        # Attach auxiliary data needed to the inputs, for signing.

        # Get the public key of an address
        inp["pubkey"] = stub_walletkit.KeyForAddress(
            walletkit.KeyForAddressRequest(addr_in=inp["utxo"].address),
            metadata=[('macaroon', macaroon)]).raw_key_bytes

        # this data (known as scriptCode in BIP143 parlance)
        # is the pubkeyhash script for this p2wpkh, as is needed
        # to construct the signature hash.
        # **NOTE** This code currently works with bech32 only.
        # TODO update to allow p2sh-p2wpkh in wallet coins, also.
        inp["script"] = btc.pubkey_to_p2pkh_script(inp["pubkey"])

    # We need an output address for the transaction, this is taken from the
    # standard wallet 'new address' request (type 0 is bech32 p2wpkh):
    request = ln.NewAddressRequest(type=0, )
    response = stub.NewAddress(request, metadata=[('macaroon', macaroon)])
    output_address = response.address
    print("Generated new address: ", output_address)

    # Build the raw unsigned transaction
    tx_ins = []
    output_amt = 0
    for inp in inputs:
        tx_ins.append(inp["utxo"].outpoint.txid_str + ":" +
                      str(inp["utxo"].outpoint.output_index))
        output_amt += inp["utxo"].amount_sat

    fee_est = estimate_tx_fee(2, 1, "p2wpkh", 6, stub, macaroon)

    output = {"address": output_address, "value": output_amt - fee_est}
    tx_unsigned = btc.mktx(tx_ins, [output], version=2)
    print(btc.deserialize(tx_unsigned))

    # use SignOutputRaw to sign each input (currently, they are all ours).
    raw_sigs = {}
    for i, inp in enumerate(inputs):
        # KeyDescriptors must contain at least one of the pubkey and the HD path,
        # here we use the latter:
        kd = signer.KeyDescriptor(raw_key_bytes=inp["pubkey"])
        # specify the utxo information for this input into a TxOut:
        sdout = signer.TxOut(value=inp["utxo"].amount_sat,
                             pk_script=unhexlify(inp["utxo"].pk_script))
        # we must pass a list of SignDescriptors; we could batch all into
        # one grpc call if we preferred. The witnessscript field is
        # constructed above as the "script" field in the input dict.
        sds = [
            signer.SignDescriptor(key_desc=kd,
                                  input_index=i,
                                  output=sdout,
                                  witness_script=inp["script"],
                                  sighash=1)
        ]
        req = signer.SignReq(raw_tx_bytes=unhexlify(tx_unsigned),
                             sign_descs=sds)
        # here we make the actual signing request to lnd over grpc:
        response = stub_signer.SignOutputRaw(req,
                                             metadata=[('macaroon',
                                                        signer_macaroon)])
        # note that btcwallet's sign function does not return the sighash byte,
        # it must be added manually:
        raw_sigs[i] = response.raw_sigs[0] + sighash_all_bytes

    # insert the signatures into the relevant inputs in the deserialized tx
    tx_unsigned_deser = btc.deserialize(tx_unsigned)
    for i in range(len(inputs)):
        tx_unsigned_deser["ins"][i]["txinwitness"] = [
            btc.safe_hexlify(raw_sigs[i]),
            btc.safe_hexlify(inputs[i]["pubkey"])
        ]
    print("Signed transaction: \n", tx_unsigned_deser)
    hextx = btc.serialize(tx_unsigned_deser)
    print("Serialized: ", hextx)
    print("You can broadcast this externally e.g. via Bitcoin Core")
예제 #22
0
    def on_tx_received(self, nick, txhex):
        """ Called when the sender-counterparty has sent a transaction proposal.
        1. First we check for the expected destination and amount (this is
           sufficient to identify our cp, as this info was presumably passed
           out of band, as for any normal payment).
        2. Then we verify the validity of the proposed non-coinjoin
           transaction; if not, reject, otherwise store this as a
           fallback transaction in case the protocol doesn't complete.
        3. Next, we select utxos from our wallet, to add into the
           payment transaction as input. Try to select so as to not
           trigger the UIH2 condition, but continue (and inform user)
           even if we can't (if we can't select any coins, broadcast the
           non-coinjoin payment, if the user agrees).
           Proceeding with payjoin:
        4. We update the output amount at the destination address.
        5. We modify the change amount in the original proposal (which
           will be the only other output other than the destination),
           reducing it to account for the increased transaction fee
           caused by our additional proposed input(s).
        6. Finally we sign our own input utxo(s) and re-serialize the
           tx, allowing it to be sent back to the counterparty.
        7. If the transaction is not fully signed and broadcast within
           the time unconfirm_timeout_sec as specified in the joinmarket.cfg,
           we broadcast the non-coinjoin fallback tx instead.
        """
        try:
            tx = btc.deserialize(txhex)
        except (IndexError, SerializationError,
                SerializationTruncationError) as e:
            return (False, 'malformed txhex. ' + repr(e))
        self.user_info('obtained proposed fallback (non-coinjoin) ' +\
                       'transaction from sender:\n' + pprint.pformat(tx))

        if len(tx["outs"]) != 2:
            return (False,
                    "Transaction has more than 2 outputs; not supported.")
        dest_found = False
        destination_index = -1
        change_index = -1
        proposed_change_value = 0
        for index, out in enumerate(tx["outs"]):
            if out["script"] == btc.address_to_script(self.destination_addr):
                # we found the expected destination; is the amount correct?
                if not out["value"] == self.receiving_amount:
                    return (False,
                            "Wrong payout value in proposal from sender.")
                dest_found = True
                destination_index = index
            else:
                change_found = True
                proposed_change_out = out["script"]
                proposed_change_value = out["value"]
                change_index = index

        if not dest_found:
            return (False, "Our expected destination address was not found.")

        # Verify valid input utxos provided and check their value.
        # batch retrieval of utxo data
        utxo = {}
        ctr = 0
        for index, ins in enumerate(tx['ins']):
            utxo_for_checking = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            utxo[ctr] = [index, utxo_for_checking]
            ctr += 1

        utxo_data = jm_single().bc_interface.query_utxo_set(
            [x[1] for x in utxo.values()])

        total_sender_input = 0
        for i, u in iteritems(utxo):
            if utxo_data[i] is None:
                return (False, "Proposed transaction contains invalid utxos")
            total_sender_input += utxo_data[i]["value"]

        # Check that the transaction *as proposed* balances; check that the
        # included fee is within 0.3-3x our own current estimates, if not user
        # must decide.
        btc_fee = total_sender_input - self.receiving_amount - proposed_change_value
        self.user_info("Network transaction fee of fallback tx is: " +
                       str(btc_fee) + " satoshis.")
        fee_est = estimate_tx_fee(len(tx['ins']),
                                  len(tx['outs']),
                                  txtype=self.wallet.get_txtype())
        fee_ok = False
        if btc_fee > 0.3 * fee_est and btc_fee < 3 * fee_est:
            fee_ok = True
        else:
            if self.user_check("Is this transaction fee acceptable? (y/n):"):
                fee_ok = True
        if not fee_ok:
            return (False,
                    "Proposed transaction fee not accepted due to tx fee: " +
                    str(btc_fee))

        # This direct rpc call currently assumes Core 0.17, so not using now.
        # It has the advantage of (a) being simpler and (b) allowing for any
        # non standard coins.
        #
        #res = jm_single().bc_interface.rpc('testmempoolaccept', [txhex])
        #print("Got this result from rpc call: ", res)
        #if not res["accepted"]:
        #    return (False, "Proposed transaction was rejected from mempool.")

        # Manual verification of the transaction signatures. Passing this
        # test does imply that the transaction is valid (unless there is
        # a double spend during the process), but is restricted to standard
        # types: p2pkh, p2wpkh, p2sh-p2wpkh only. Double spend is not counted
        # as a risk as this is a payment.
        for i, u in iteritems(utxo):
            if "txinwitness" in tx["ins"][u[0]]:
                ver_amt = utxo_data[i]["value"]
                try:
                    ver_sig, ver_pub = tx["ins"][u[0]]["txinwitness"]
                except Exception as e:
                    self.user_info("Segwit error: " + repr(e))
                    return (False, "Segwit input not of expected type, "
                            "either p2sh-p2wpkh or p2wpkh")
                # note that the scriptCode is the same whether nested or not
                # also note that the scriptCode has to be inferred if we are
                # only given a transaction serialization.
                scriptCode = "76a914" + btc.hash160(
                    unhexlify(ver_pub)) + "88ac"
            else:
                scriptCode = None
                ver_amt = None
                scriptSig = btc.deserialize_script(tx["ins"][u[0]]["script"])
                if len(scriptSig) != 2:
                    return (
                        False,
                        "Proposed transaction contains unsupported input type")
                ver_sig, ver_pub = scriptSig
            if not btc.verify_tx_input(txhex,
                                       u[0],
                                       utxo_data[i]['script'],
                                       ver_sig,
                                       ver_pub,
                                       scriptCode=scriptCode,
                                       amount=ver_amt):
                return (False, "Proposed transaction is not correctly signed.")

        # At this point we are satisfied with the proposal. Record the fallback
        # in case the sender disappears and the payjoin tx doesn't happen:
        self.user_info(
            "We'll use this serialized transaction to broadcast if your"
            " counterparty fails to broadcast the payjoin version:")
        self.user_info(txhex)
        # Keep a local copy for broadcast fallback:
        self.fallback_tx = txhex

        # Now we add our own inputs:
        # See the gist comment here:
        # https://gist.github.com/AdamISZ/4551b947789d3216bacfcb7af25e029e#gistcomment-2799709
        # which sets out the decision Bob must make.
        # In cases where Bob can add any amount, he selects one utxo
        # to keep it simple.
        # In cases where he must choose at least X, he selects one utxo
        # which provides X if possible, otherwise defaults to a normal
        # selection algorithm.
        # In those cases where he must choose X but X is unavailable,
        # he selects all coins, and proceeds anyway with payjoin, since
        # it has other advantages (CIOH and utxo defrag).
        my_utxos = {}
        largest_out = max(self.receiving_amount, proposed_change_value)
        max_sender_amt = max([u['value'] for u in utxo_data])
        not_uih2 = False
        if max_sender_amt < largest_out:
            # just select one coin.
            # have some reasonable lower limit but otherwise choose
            # randomly; note that this is actually a great way of
            # sweeping dust ...
            self.user_info("Choosing one coin at random")
            try:
                my_utxos = self.wallet.select_utxos(self.mixdepth,
                                                    jm_single().DUST_THRESHOLD,
                                                    select_fn=select_one_utxo)
            except:
                return self.no_coins_fallback()
            not_uih2 = True
        else:
            # get an approximate required amount assuming 4 inputs, which is
            # fairly conservative (but guess by necessity).
            fee_for_select = estimate_tx_fee(len(tx['ins']) + 4,
                                             2,
                                             txtype=self.wallet.get_txtype())
            approx_sum = max_sender_amt - self.receiving_amount + fee_for_select
            try:
                my_utxos = self.wallet.select_utxos(self.mixdepth, approx_sum)
                not_uih2 = True
            except Exception:
                # TODO probably not logical to always sweep here.
                self.user_info("Sweeping all coins in this mixdepth.")
                my_utxos = self.wallet.get_utxos_by_mixdepth()[self.mixdepth]
                if my_utxos == {}:
                    return self.no_coins_fallback()
        if not_uih2:
            self.user_info("The proposed tx does not trigger UIH2, which "
                           "means it is indistinguishable from a normal "
                           "payment. This is the ideal case. Continuing..")
        else:
            self.user_info("The proposed tx does trigger UIH2, which it makes "
                           "it somewhat distinguishable from a normal payment,"
                           " but proceeding with payjoin..")

        my_total_in = sum([va['value'] for va in my_utxos.values()])
        self.user_info("We selected inputs worth: " + str(my_total_in))
        # adjust the output amount at the destination based on our contribution
        new_destination_amount = self.receiving_amount + my_total_in
        # estimate the required fee for the new version of the transaction
        total_ins = len(tx["ins"]) + len(my_utxos.keys())
        est_fee = estimate_tx_fee(total_ins,
                                  2,
                                  txtype=self.wallet.get_txtype())
        self.user_info("We estimated a fee of: " + str(est_fee))
        new_change_amount = total_sender_input + my_total_in - \
            new_destination_amount - est_fee
        self.user_info("We calculated a new change amount of: " +
                       str(new_change_amount))
        self.user_info("We calculated a new destination amount of: " +
                       str(new_destination_amount))
        # now reconstruct the transaction with the new inputs and the
        # amount-changed outputs
        new_outs = [{
            "address": self.destination_addr,
            "value": new_destination_amount
        }]
        if new_change_amount >= jm_single().BITCOIN_DUST_THRESHOLD:
            new_outs.append({
                "script": proposed_change_out,
                "value": new_change_amount
            })
        new_ins = [x[1] for x in utxo.values()]
        new_ins.extend(my_utxos.keys())
        # set locktime for best anonset (Core, Electrum) - most recent block.
        # this call should never fail so no catch here.
        currentblock = jm_single().bc_interface.rpc("getblockchaininfo",
                                                    [])["blocks"]
        new_tx = make_shuffled_tx(new_ins, new_outs, False, 2, currentblock)
        new_tx_deser = btc.deserialize(new_tx)

        # sign our inputs before transfer
        our_inputs = {}
        for index, ins in enumerate(new_tx_deser['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            if utxo not in my_utxos:
                continue
            script = self.wallet.addr_to_script(my_utxos[utxo]['address'])
            amount = my_utxos[utxo]['value']
            our_inputs[index] = (script, amount)

        txs = self.wallet.sign_tx(btc.deserialize(new_tx), our_inputs)
        jm_single().bc_interface.add_tx_notify(
            txs,
            self.on_tx_unconfirmed,
            self.on_tx_confirmed,
            self.destination_addr,
            wallet_name=jm_single().bc_interface.get_wallet_name(self.wallet),
            txid_flag=False,
            vb=self.wallet._ENGINE.VBYTE)
        # The blockchain interface just abandons monitoring if the transaction
        # is not broadcast before the configured timeout; we want to take
        # action in this case, so we add an additional callback to the reactor:
        reactor.callLater(
            jm_single().config.getint("TIMEOUT", "unconfirm_timeout_sec"),
            self.broadcast_fallback)
        return (True, nick, btc.serialize(txs))
예제 #23
0
def test_spend_p2sh_p2wpkh_multi(setup_segwit, wallet_structure, in_amt, amount,
                                 segwit_amt, segwit_ins, o_ins):
    """Creates a wallet from which non-segwit inputs/
    outputs can be created, constructs one or more
    p2wpkh in p2sh spendable utxos (by paying into the
    corresponding address) and tests spending them
    in combination.
    wallet_structure is in accordance with commontest.make_wallets, see docs there
    in_amt is the amount to pay into each address into the wallet (non-segwit adds)
    amount (in satoshis) is how much we will pay to the output address
    segwit_amt in BTC is the amount we will fund each new segwit address with
    segwit_ins is a list of input indices (where to place the funding segwit utxos)
    other_ins is a list of input indices (where to place the funding non-sw utxos)
    """
    MIXDEPTH = 0

    # set up wallets and inputs
    nsw_wallet = make_wallets(1, wallet_structure, in_amt,
                              walletclass=LegacyWallet)[0]['wallet']
    jm_single().bc_interface.sync_wallet(nsw_wallet, fast=True)
    sw_wallet = make_wallets(1, [[len(segwit_ins), 0, 0, 0, 0]], segwit_amt)[0]['wallet']
    jm_single().bc_interface.sync_wallet(sw_wallet, fast=True)

    nsw_utxos = nsw_wallet.get_utxos_by_mixdepth_()[MIXDEPTH]
    sw_utxos = sw_wallet.get_utxos_by_mixdepth_()[MIXDEPTH]
    assert len(o_ins) <= len(nsw_utxos), "sync failed"
    assert len(segwit_ins) <= len(sw_utxos), "sync failed"

    total_amt_in_sat = 0

    nsw_ins = {}
    for nsw_in_index in o_ins:
        total_amt_in_sat += in_amt * 10**8
        nsw_ins[nsw_in_index] = nsw_utxos.popitem()

    sw_ins = {}
    for sw_in_index in segwit_ins:
        total_amt_in_sat += int(segwit_amt * 10**8)
        sw_ins[sw_in_index] = sw_utxos.popitem()

    all_ins = {}
    all_ins.update(nsw_ins)
    all_ins.update(sw_ins)

    # sanity checks
    assert len(all_ins) == len(nsw_ins) + len(sw_ins), \
        "test broken, duplicate index"
    for k in all_ins:
        assert 0 <= k < len(all_ins), "test broken, missing input index"

    # FIXME: encoding mess, mktx should accept binary input formats
    tx_ins = []
    for i, (txid, data) in sorted(all_ins.items(), key=lambda x: x[0]):
        tx_ins.append('{}:{}'.format(binascii.hexlify(txid[0]), txid[1]))

    # create outputs
    FEE = 50000
    assert FEE < total_amt_in_sat - amount, "test broken, not enough funds"

    cj_script = nsw_wallet.get_new_script(MIXDEPTH + 1, True)
    change_script = nsw_wallet.get_new_script(MIXDEPTH, True)
    change_amt = total_amt_in_sat - amount - FEE

    tx_outs = [
        {'script': binascii.hexlify(cj_script),
         'value': amount},
        {'script': binascii.hexlify(change_script),
         'value': change_amt}]
    tx = btc.deserialize(btc.mktx(tx_ins, tx_outs))
    binarize_tx(tx)

    # import new addresses to bitcoind
    jm_single().bc_interface.import_addresses(
        [nsw_wallet.script_to_addr(x)
         for x in [cj_script, change_script]],
        jm_single().bc_interface.get_wallet_name(nsw_wallet))

    # sign tx
    scripts = {}
    for nsw_in_index in o_ins:
        inp = nsw_ins[nsw_in_index][1]
        scripts[nsw_in_index] = (inp['script'], inp['value'])
    nsw_wallet.sign_tx(tx, scripts)

    scripts = {}
    for sw_in_index in segwit_ins:
        inp = sw_ins[sw_in_index][1]
        scripts[sw_in_index] = (inp['script'], inp['value'])
    sw_wallet.sign_tx(tx, scripts)

    print(tx)

    # push and verify
    txid = jm_single().bc_interface.pushtx(binascii.hexlify(btc.serialize(tx)))
    assert txid

    balances = jm_single().bc_interface.get_received_by_addr(
        [nsw_wallet.script_to_addr(cj_script),
         nsw_wallet.script_to_addr(change_script)], None)['data']
    assert balances[0]['balance'] == amount
    assert balances[1]['balance'] == change_amt
예제 #24
0
def direct_send(wallet_service, amount, mixdepth, destination, answeryes=False,
                accept_callback=None, info_callback=None):
    """Send coins directly from one mixdepth to one destination address;
    does not need IRC. Sweep as for normal sendpayment (set amount=0).
    If answeryes is True, callback/command line query is not performed.
    If accept_callback is None, command line input for acceptance is assumed,
    else this callback is called:
    accept_callback:
    ====
    args:
    deserialized tx, destination address, amount in satoshis, fee in satoshis
    returns:
    True if accepted, False if not
    ====
    The info_callback takes one parameter, the information message (when tx is
    pushed), and returns nothing.

    This function returns:
    The txid if transaction is pushed, False otherwise
    """
    #Sanity checks
    assert validate_address(destination)[0] or is_burn_destination(destination)
    assert isinstance(mixdepth, numbers.Integral)
    assert mixdepth >= 0
    assert isinstance(amount, numbers.Integral)
    assert amount >=0
    assert isinstance(wallet_service.wallet, BaseWallet)

    if is_burn_destination(destination):
        #Additional checks
        if not isinstance(wallet_service.wallet, FidelityBondMixin):
            log.error("Only fidelity bond wallets can burn coins")
            return
        if answeryes:
            log.error("Burning coins not allowed without asking for confirmation")
            return
        if mixdepth != FidelityBondMixin.FIDELITY_BOND_MIXDEPTH:
            log.error("Burning coins only allowed from mixdepth " + str(
                FidelityBondMixin.FIDELITY_BOND_MIXDEPTH))
            return
        if amount != 0:
            log.error("Only sweeping allowed when burning coins, to keep the tx " +
                "small. Tip: use the coin control feature to freeze utxos")
            return

    from pprint import pformat
    txtype = wallet_service.get_txtype()
    if amount == 0:
        utxos = wallet_service.get_utxos_by_mixdepth()[mixdepth]
        if utxos == {}:
            log.error(
                "There are no available utxos in mixdepth: " + str(mixdepth) + ", quitting.")
            return

        total_inputs_val = sum([va['value'] for u, va in iteritems(utxos)])

        if is_burn_destination(destination):
            if len(utxos) > 1:
                log.error("Only one input allowed when burning coins, to keep "
                    + "the tx small. Tip: use the coin control feature to freeze utxos")
                return
            address_type = FidelityBondMixin.BIP32_BURN_ID
            index = wallet_service.wallet.get_next_unused_index(mixdepth, address_type)
            path = wallet_service.wallet.get_path(mixdepth, address_type, index)
            privkey, engine = wallet_service.wallet._get_key_from_path(path)
            pubkey = engine.privkey_to_pubkey(privkey)
            pubkeyhash = bin_hash160(pubkey)

            #size of burn output is slightly different from regular outputs
            burn_script = mk_burn_script(pubkeyhash) #in hex
            fee_est = estimate_tx_fee(len(utxos), 0, txtype=txtype, extra_bytes=len(burn_script)/2)

            outs = [{"script": burn_script, "value": total_inputs_val - fee_est}]
            destination = "BURNER OUTPUT embedding pubkey at " \
                + wallet_service.wallet.get_path_repr(path) \
                + "\n\nWARNING: This transaction if broadcasted will PERMANENTLY DESTROY your bitcoins\n"
        else:
            #regular send (non-burn)
            fee_est = estimate_tx_fee(len(utxos), 1, txtype=txtype)
            outs = [{"address": destination, "value": total_inputs_val - fee_est}]
    else:
        #8 inputs to be conservative
        initial_fee_est = estimate_tx_fee(8,2, txtype=txtype)
        utxos = wallet_service.select_utxos(mixdepth, amount + initial_fee_est)
        if len(utxos) < 8:
            fee_est = estimate_tx_fee(len(utxos), 2, txtype=txtype)
        else:
            fee_est = initial_fee_est
        total_inputs_val = sum([va['value'] for u, va in iteritems(utxos)])
        changeval = total_inputs_val - fee_est - amount
        outs = [{"value": amount, "address": destination}]
        change_addr = wallet_service.get_internal_addr(mixdepth)
        outs.append({"value": changeval, "address": change_addr})

    #compute transaction locktime, has special case for spending timelocked coins
    tx_locktime = compute_tx_locktime()
    if mixdepth == FidelityBondMixin.FIDELITY_BOND_MIXDEPTH and \
            isinstance(wallet_service.wallet, FidelityBondMixin):
        for outpoint, utxo in utxos.items():
            path = wallet_service.script_to_path(
                wallet_service.addr_to_script(utxo["address"]))
            if not FidelityBondMixin.is_timelocked_path(path):
                continue
            path_locktime = path[-1]
            tx_locktime = max(tx_locktime, path_locktime+1)
            #compute_tx_locktime() gives a locktime in terms of block height
            #timelocked addresses use unix time instead
            #OP_CHECKLOCKTIMEVERIFY can only compare like with like, so we
            #must use unix time as the transaction locktime

    #Now ready to construct transaction
    log.info("Using a fee of : " + amount_to_str(fee_est) + ".")
    if amount != 0:
        log.info("Using a change value of: " + amount_to_str(changeval) + ".")
    txsigned = sign_tx(wallet_service, make_shuffled_tx(
        list(utxos.keys()), outs, False, 2, tx_locktime), utxos)
    log.info("Got signed transaction:\n")
    log.info(pformat(txsigned))
    tx = serialize(txsigned)
    log.info("In serialized form (for copy-paste):")
    log.info(tx)
    actual_amount = amount if amount != 0 else total_inputs_val - fee_est
    log.info("Sends: " + amount_to_str(actual_amount) + " to destination: " + destination)
    if not answeryes:
        if not accept_callback:
            if input('Would you like to push to the network? (y/n):')[0] != 'y':
                log.info("You chose not to broadcast the transaction, quitting.")
                return False
        else:
            accepted = accept_callback(pformat(txsigned), destination, actual_amount,
                                       fee_est)
            if not accepted:
                return False
    jm_single().bc_interface.pushtx(tx)
    txid = txhash(tx)
    successmsg = "Transaction sent: " + txid
    cb = log.info if not info_callback else info_callback
    cb(successmsg)
    return txid
예제 #25
0
def graft_onto_single_acp(wallet, txhex, amount, destaddr):
    """Given a serialized txhex which is checked to be of
    form single|acp (one in, one out), a destination address
    and an amount to spend, grafts in this in-out pair (at index zero)
    to our own transaction spending amount amount to destination destaddr,
    and uses a user-specified transaction fee (normal joinmarket
    configuration), and sanity checks that the bump value is not
    greater than user specified bump option.
    Returned: serialized txhex of fully signed transaction.
    """
    d = btc.deserialize(txhex)
    if len(d['ins']) != 1 or len(d['outs']) != 1:
        return (False, "Proposed tx should have 1 in 1 out, has: " +
                ','.join([str(len(d[x])) for x in ['ins', 'outs']]))
    #most important part: check provider hasn't bumped more than options.bump:
    other_utxo_in = d['ins'][0]['outpoint']['hash'] + ":" + str(
        d['ins'][0]['outpoint']['index'])
    res = jm_single().bc_interface.query_utxo_set(other_utxo_in)
    assert len(res) == 1
    if not res[0]:
        return (False, "Utxo provided by counterparty not found.")
    excess = d['outs'][0]['value'] - res[0]["value"]
    if not excess <= options.bump:
        return (False,
                "Counterparty claims too much excess value: " + str(excess))
    #Last sanity check - ensure that it's single|acp, else we're wasting our time
    try:
        if 'txinwitness' in d['ins'][0]:
            sig, pub = d['ins'][0]['txinwitness']
        else:
            sig, pub = btc.deserialize_script(d['ins'][0]['script'])
        assert sig[-2:] == "83"
    except Exception as e:
        return (
            False, "The transaction's signature does not parse as signed with "
            "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, for p2pkh or p2sh-p2wpkh, or "
            "is otherwise invalid, and so is not valid for this function.\n" +
            repr(e))
    #source inputs for our own chosen spending amount:
    try:
        input_utxos = wallet.select_utxos(options.mixdepth, amount)
    except Exception as e:
        return (False, "Unable to select sufficient coins from mixdepth: " +
                str(options.mixdepth))
    total_selected = sum([x['value'] for x in input_utxos.values()])
    fee = estimate_tx_fee(len(input_utxos) + 1, 3, txtype='p2sh-p2wpkh')
    change_amount = total_selected - amount - excess - fee
    changeaddr = wallet.get_new_addr(options.mixdepth, 1)
    #Build new transaction and, graft in signature
    ins = [other_utxo_in] + input_utxos.keys()
    outs = [
        d['outs'][0], {
            'address': destaddr,
            'value': amount
        }, {
            'address': changeaddr,
            'value': change_amount
        }
    ]
    fulltx = btc.mktx(ins, outs)
    df = btc.deserialize(fulltx)
    #put back in original signature
    df['ins'][0]['script'] = d['ins'][0]['script']
    if 'txinwitness' in d['ins'][0]:
        df['ins'][0]['txinwitness'] = d['ins'][0]['txinwitness']
    fulltx = btc.serialize(df)
    for i, iu in enumerate(input_utxos):
        priv, inamt = get_privkey_amount_from_utxo(wallet, iu)
        print("Signing index: ", i + 1, " with privkey: ", priv,
              " and amount: ", inamt, " for utxo: ", iu)
        fulltx = btc.sign(fulltx, i + 1, priv, amount=inamt)
    return (True, fulltx)
def test_serialization_roundtrip(tx_type, tx_id, tx_hex):
    assert tx_hex == btc.serialize(btc.deserialize(tx_hex))
 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 at index n, and notifies confirmation if number
     of confs = c.
     TODO: Deal with conflicts correctly. Here just abandons monitoring.
     """
     txid = btc.txhash(btc.serialize(txd))
     wl = self.tx_watcher_loops[txid]
     try:
         res = self.rpc('gettransaction', [txid, True])
     except JsonRpcError as e:
         return
     if not res:
         return
     if "confirmations" not in res:
         log.debug("Malformed gettx result: " + str(res))
         return
     if not wl[1] and res["confirmations"] == 0:
         log.debug("Tx: " + str(txid) + " seen on network.")
         unconfirmfun(txd, txid)
         wl[1] = True
         return
     if not wl[2] and res["confirmations"] > 0:
         log.debug("Tx: " + str(txid) + " has " +
                   str(res["confirmations"]) + " confirmations.")
         confirmfun(txd, txid, res["confirmations"])
         if c <= res["confirmations"]:
             wl[2] = True
             #Note we do not stop the monitoring loop when
             #confirmations occur, since we are also monitoring for spending.
         return
     if res["confirmations"] < 0:
         log.debug("Tx: " + str(txid) + " has a conflict. Abandoning.")
         wl[0].stop()
         return
     if not spentfun or wl[3]:
         return
     #To trigger the spent callback, we check if this utxo outpoint appears in
     #listunspent output with 0 or more confirmations. Note that this requires
     #we have added the destination address to the watch-only wallet, otherwise
     #that outpoint will not be returned by listunspent.
     res2 = self.rpc('listunspent', [0, 999999])
     if not res2:
         return
     txunspent = False
     for r in res2:
         if "txid" not in r:
             continue
         if txid == r["txid"] and n == r["vout"]:
             txunspent = True
             break
     if not txunspent:
         #We need to find the transaction which spent this one;
         #assuming the address was added to the wallet, then this
         #transaction must be in the recent list retrieved via listunspent.
         #For each one, use gettransaction to check its inputs.
         #This is a bit expensive, but should only occur once.
         txlist = self.rpc("listtransactions", ["*", 1000, 0, True])
         for tx in txlist[::-1]:
             #changed syntax in 0.14.0; allow both syntaxes
             try:
                 res = self.rpc("gettransaction", [tx["txid"], True])
             except:
                 try:
                     res = self.rpc("gettransaction", [tx["txid"], 1])
                 except:
                     #This should never happen (gettransaction is a wallet rpc).
                     log.info("Failed any gettransaction call")
                     res = None
             if not res:
                 continue
             deser = self.get_deser_from_gettransaction(res)
             if deser is None:
                 continue
             for vin in deser["ins"]:
                 if not "outpoint" in vin:
                     #coinbases
                     continue
                 if vin["outpoint"]["hash"] == txid and vin["outpoint"][
                         "index"] == n:
                     #recover the deserialized form of the spending transaction.
                     log.info("We found a spending transaction: " + \
                                btc.txhash(binascii.unhexlify(res["hex"])))
                     res2 = self.rpc("gettransaction", [tx["txid"], True])
                     spending_deser = self.get_deser_from_gettransaction(
                         res2)
                     if not spending_deser:
                         log.info(
                             "ERROR: could not deserialize spending tx.")
                         #Should never happen, it's a parsing bug.
                         #No point continuing to monitor, we just hope we
                         #can extract the secret by scanning blocks.
                         wl[3] = True
                         return
                     spentfun(spending_deser, vin["outpoint"]["hash"])
                     wl[3] = True
                     return
    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)