def on_auth_received(self, nick, offer, commitment, cr, amount, kphex):
     if self.authmal:
         jmprint("Counterparty commitment rejected maliciously", "debug")
         return (False, )
     return super(DeterministicMaliciousYieldGenerator,
                  self).on_auth_received(nick, offer, commitment, cr,
                                         amount, kphex)
 def on_tx_received(self, nick, txhex, offerinfo):
     if self.txmal:
         if random.randint(1, 100) < self.mfrac:
             jmprint("Counterparty tx rejected maliciously", "debug")
             return (False, "malicious tx rejection")
     return super(MaliciousYieldGenerator,
                  self).on_tx_received(nick, txhex, offerinfo)
示例#3
0
def get_addr_and_fund(yg):
    """ This function allows us to create
    and publish a fidelity bond for a particular
    yield generator object after the wallet has reached
    a synced state and is therefore ready to serve up
    timelock addresses. We create the TL address, fund it,
    refresh the wallet and then republish our offers, which
    will also publish the new FB.
    """
    if not yg.wallet_service.synced:
        return
    if yg.wallet_service.timelock_funded:
        return
    addr = wallet_gettimelockaddress(yg.wallet_service.wallet, "2021-11")
    print("Got timelockaddress: {}".format(addr))

    # pay into it; amount is randomized for now.
    # Note that grab_coins already mines 1 block.
    fb_amt = random.randint(1, 5)
    jm_single().bc_interface.grab_coins(addr, fb_amt)

    # we no longer have to run this loop (TODO kill with nonlocal)
    yg.wallet_service.timelock_funded = True

    # force wallet to check for the new coins so the new
    # yg offers will include them:
    yg.wallet_service.transaction_monitor()

    # publish a new offer:
    yg.offerlist = yg.create_my_orders()
    yg.fidelity_bond = yg.get_fidelity_bond_template()
    jmprint('updated offerlist={}'.format(yg.offerlist))
def test_start_payjoin_server(setup_payjoin_server):
    # set up the wallet that the server owns, and the wallet for
    # the sender too (print the seed):
    if jm_single().config.get("POLICY", "native") == "true":
        walletclass = SegwitWallet
    else:
        walletclass = SegwitLegacyWallet

    wallet_services = make_wallets(2,
                                   wallet_structures=[[1, 3, 0, 0, 0]] * 2,
                                   mean_amt=2,
                                   walletclass=walletclass)
    #the server bot uses the first wallet, the sender the second
    server_wallet_service = wallet_services[0]['wallet']
    jmprint("\n\nTaker wallet seed : " + wallet_services[1]['seed'])
    jmprint("\n")
    server_wallet_service.sync_wallet(fast=True)
    
    site = Site(PayjoinServer(server_wallet_service))
    # TODO for now, just sticking with TLS test as non-encrypted
    # is unlikely to be used, but add that option.
    reactor.listenSSL(8080, site, contextFactory=get_ssl_context())
    #endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
    #endpoint.listen(site)
    reactor.run()
示例#5
0
 def on_auth_received(self, nick, offer, commitment, cr, amount, kphex):
     if self.authmal:
         if random.randint(1, 100) < self.mfrac:
             jmprint("Counterparty commitment rejected maliciously", "debug")
             return (False,)
     return super(MaliciousYieldGenerator, self).on_auth_received(nick,
                                 offer, commitment, cr, amount, kphex)
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()
        txid, n = u.split(':')
        assert len(txid) == 64
        assert len(n) in range(1, 4)
        n = int(n)
        assert n in range(256)
    except:
        #not sending data to stdout in case privkey info
        jmprint("Failed to parse utxo information for utxo", "error")
        raise
    try:
        hexpriv = btc.from_wif_privkey(priv, vbyte=get_p2pk_vbyte())
    except:
        jmprint(
            "failed to parse privkey, make sure it's WIF compressed format.",
            "error")
        raise
    return u, priv
示例#7
0
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 dummy_taker_finished(res, fromtx=False,
                            waittime=0.0, txdetails=None):
     jmprint("Taker is finished")
     # check that the funds have arrived.
     mbal = mgr.daemon.services["wallet"].get_balance_by_mixdepth()[4]
     assert mbal == cj_amount
     jmprint("Funds: {} sats successfully arrived into mixdepth 4.".format(cj_amount))
     stop_reactor()
示例#9
0
 def start_snicker_server_and_tor(self):
     """ Packages the startup of the receiver side.
     """
     self.server = SNICKERServer()
     self.site = Site(self.server)
     self.site.displayTracebacks = False
     jmprint("Attempting to start onion service on port: " +
             str(self.port) + " ...")
     self.start_tor()
 def estimate_fee_per_kb(self, N):
     if super(ElectrumInterface, self).fee_per_kb_has_been_manually_set(N):
         return int(random.uniform(N * float(0.8), N * float(1.2)))
     fee_info = self.get_from_electrum('blockchain.estimatefee',
                                       N,
                                       blocking=True)
     jmprint('got fee info result: ' + str(fee_info), "debug")
     fee = fee_info.get('result')
     fee_per_kb_sat = int(float(fee) * 100000000)
     return fee_per_kb_sat
    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
示例#12
0
 def display_rescan_message_and_system_exit(self, restart_cb):
     #TODO using system exit here should be avoided as it makes the code
     # harder to understand and reason about
     #theres also a sys.exit() in BitcoinCoreInterface.import_addresses()
     #perhaps have sys.exit() placed inside the restart_cb that only
     # CLI scripts will use
     if self.bci.__class__ == BitcoinCoreInterface:
         #Exit conditions cannot be included in tests
         restart_msg = (
             "Use `bitcoin-cli rescanblockchain` if you're "
             "recovering an existing wallet from backup seed\n"
             "Otherwise just restart this joinmarket application.")
         if restart_cb:
             restart_cb(restart_msg)
         else:
             jmprint(restart_msg, "important")
             sys.exit(EXIT_SUCCESS)
def test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, mean_amt,
                   malicious, deterministic):
    """Set up some wallets, for the ygs and 1 sp.
    Then start the ygs in background and publish
    the seed of the sp wallet for easy import into -qt
    """
    if jm_single().config.get("POLICY", "native") == "true":
        walletclass = SegwitWallet
    else:
        # TODO add Legacy
        walletclass = SegwitLegacyWallet

    wallet_services = make_wallets(num_ygs + 1,
                                   wallet_structures=wallet_structures,
                                   mean_amt=mean_amt,
                                   walletclass=walletclass)
    #the sendpayment bot uses the last wallet in the list
    wallet_service = wallet_services[num_ygs]['wallet']
    jmprint("\n\nTaker wallet seed : " + wallet_services[num_ygs]['seed'])
    # for manual audit if necessary, show the maker's wallet seeds
    # also (note this audit should be automated in future, see
    # test_full_coinjoin.py in this directory)
    jmprint("\n\nMaker wallet seeds: ")
    for i in range(num_ygs):
        jmprint("Maker seed: " + wallet_services[i]['seed'])
    jmprint("\n")
    wallet_service.sync_wallet(fast=True)
    txfee = 1000
    cjfee_a = 4200
    cjfee_r = '0.001'
    ordertype = 'swreloffer'
    minsize = 100000
    ygclass = YieldGeneratorBasic
    if malicious:
        if deterministic:
            ygclass = DeterministicMaliciousYieldGenerator
        else:
            ygclass = MaliciousYieldGenerator
    for i in range(num_ygs):

        cfg = [txfee, cjfee_a, cjfee_r, ordertype, minsize]
        wallet_service_yg = wallet_services[i]["wallet"]
        wallet_service_yg.startService()
        yg = ygclass(wallet_service_yg, cfg)
        if malicious:
            yg.set_maliciousness(malicious, mtype="tx")
        clientfactory = JMClientProtocolFactory(yg, proto_type="MAKER")
        nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
        daemon = True if nodaemon == 1 else False
        rs = True if i == num_ygs - 1 else False
        start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                      jm_single().config.getint("DAEMON", "daemon_port"),
                      clientfactory,
                      daemon=daemon,
                      rs=rs)
示例#14
0
def update_commitments(commitment=None,
                       external_to_remove=None,
                       external_to_add=None):
    """Optionally add the commitment commitment to the list of 'used',
    and optionally remove the available external commitment
    whose key value is the utxo in external_to_remove,
    persist updated entries to disk.
    """
    c = {}
    if os.path.isfile(PODLE_COMMIT_FILE):
        with open(PODLE_COMMIT_FILE, "rb") as f:
            try:
                c = json.loads(f.read().decode('utf-8'))
            except ValueError:  #pragma: no cover
                #Exit conditions cannot be included in tests.
                jmprint(
                    "the file: " + PODLE_COMMIT_FILE + " is not valid json.",
                    "error")
                sys.exit(0)

    if 'used' in c:
        commitments = c['used']
    else:
        commitments = []
    if 'external' in c:
        external = c['external']
    else:
        external = {}
    if commitment:
        commitments.append(commitment)
        #remove repeats
        commitments = list(set(commitments))
    if external_to_remove:
        external = {
            k: v
            for k, v in external.items() if k not in external_to_remove
        }
    if external_to_add:
        external.update(external_to_add)
    to_write = {}
    to_write['used'] = commitments
    to_write['external'] = external
    with open(PODLE_COMMIT_FILE, "wb") as f:
        f.write(json.dumps(to_write, indent=4).encode('utf-8'))
示例#15
0
def read_from_podle_file():
    """ Returns used commitment list and external commitments dict
    struct currently stored in PODLE_COMMIT_FILE.
    """
    if os.path.isfile(PODLE_COMMIT_FILE):
        with open(PODLE_COMMIT_FILE, "rb") as f:
            try:
                c = json.loads(f.read().decode('utf-8'))
            except ValueError: #pragma: no cover
                #Exit conditions cannot be included in tests.
                jmprint("the file: " + PODLE_COMMIT_FILE + " is not valid json.",
                        "error")
                sys.exit(EXIT_FAILURE)
            if 'used' not in c.keys() or 'external' not in c.keys():
                raise PoDLEError("Incorrectly formatted file: " + PODLE_COMMIT_FILE)

        used = [hextobin(x) for x in c["used"]]
        external = external_dict_from_file(c["external"])
        return (used, external)
    return ([], {})
示例#16
0
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
示例#17
0
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
            randomize_cjfee = int(random.uniform(float(self.cjfee_a) * (1 - float(self.cjfee_factor)),
                                                 float(self.cjfee_a) * (1 + float(self.cjfee_factor))))
            randomize_cjfee = randomize_cjfee + randomize_txfee
        else:
            randomize_cjfee = random.uniform(float(f) * (1 - float(self.cjfee_factor)),
                                             float(f) * (1 + float(self.cjfee_factor)))
            randomize_cjfee = "{0:.6f}".format(randomize_cjfee)  # round to 6 decimals

        order = {'oid': 0,
                 'ordertype': self.ordertype,
                 'minsize': randomize_minsize,
                 'maxsize': randomize_maxsize,
                 'txfee': randomize_txfee,
                 'cjfee': str(randomize_cjfee)}

        # sanity check
        assert order['minsize'] >= 0
        assert order['maxsize'] > 0
        assert order['minsize'] <= order['maxsize']
        if order['ordertype'] in ['swreloffer', 'sw0reloffer']:
            while order['txfee'] >= (float(order['cjfee']) * order['minsize']):
                order['txfee'] = int(order['txfee'] / 2)
                jlog.info('Warning: too high txfee to be profitable, halfing it to: ' + str(order['txfee']))

        return [order]


if __name__ == "__main__":
    ygmain(YieldGeneratorPrivacyEnhanced, nickserv_password='')
    jmprint('done', "success")
示例#19
0
def main():
    parser = OptionParser(
        usage=
        'usage: %prog [options] walletname hex-tx input-index output-index net-transfer',
        description=description)
    add_base_options(parser)
    parser.add_option('-m',
                      '--mixdepth',
                      action='store',
                      type='int',
                      dest='mixdepth',
                      help='mixdepth/account to spend from, default=0',
                      default=0)
    parser.add_option('-g',
                      '--gap-limit',
                      action='store',
                      type='int',
                      dest='gaplimit',
                      default=6,
                      help='gap limit for Joinmarket wallet, default 6.')
    parser.add_option(
        '-n',
        '--no-upload',
        action='store_true',
        dest='no_upload',
        default=False,
        help="if set, we don't upload the new proposal to the servers")
    parser.add_option(
        '-f',
        '--txfee',
        action='store',
        type='int',
        dest='txfee',
        default=-1,
        help='Bitcoin miner tx_fee to use for transaction(s). A number higher '
        'than 1000 is used as "satoshi per KB" tx fee. A number lower than that '
        'uses the dynamic fee estimation of your blockchain provider as '
        'confirmation target. This temporarily overrides the "tx_fees" setting '
        'in your joinmarket.cfg. Works the same way as described in it. Check '
        'it for examples.')
    parser.add_option('-a',
                      '--amtmixdepths',
                      action='store',
                      type='int',
                      dest='amtmixdepths',
                      help='number of mixdepths in wallet, default 5',
                      default=5)
    (options, args) = parser.parse_args()
    snicker_plugin = JMPluginService("SNICKER")
    load_program_config(config_path=options.datadir,
                        plugin_services=[snicker_plugin])
    if len(args) != 5:
        jmprint("Invalid arguments, see --help")
        sys.exit(EXIT_ARGERROR)
    wallet_name, hextx, input_index, output_index, net_transfer = args
    input_index, output_index, net_transfer = [
        int(x) for x in [input_index, output_index, net_transfer]
    ]
    check_regtest()

    # If tx_fees are set manually by CLI argument, override joinmarket.cfg:
    if int(options.txfee) > 0:
        jm_single().config.set("POLICY", "tx_fees", str(options.txfee))
    max_mix_depth = max([options.mixdepth, options.amtmixdepths - 1])
    wallet_path = get_wallet_path(wallet_name, None)
    wallet = open_test_wallet_maybe(
        wallet_path,
        wallet_name,
        max_mix_depth,
        wallet_password_stdin=options.wallet_password_stdin,
        gap_limit=options.gaplimit)
    wallet_service = WalletService(wallet)
    if wallet_service.rpc_error:
        sys.exit(EXIT_FAILURE)
    snicker_plugin.start_plugin_logging(wallet_service)
    # in this script, we need the wallet synced before
    # logic processing for some paths, so do it now:
    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=not options.recoversync)
    # the sync call here will now be a no-op:
    wallet_service.startService()

    # now that the wallet is available, we can construct a proposal
    # before encrypting it:
    originating_tx = btc.CMutableTransaction.deserialize(hextobin(hextx))
    txid1 = originating_tx.GetTxid()[::-1]
    # the proposer wallet needs to choose a single utxo, from his selected
    # mixdepth, that is bigger than the output amount of tx1 at the given
    # index.
    fee_est = estimate_tx_fee(2, 3, txtype=wallet_service.get_txtype())
    amt_required = originating_tx.vout[output_index].nValue + fee_est

    prop_utxo_dict = wallet_service.select_utxos(options.mixdepth,
                                                 amt_required)
    prop_utxos = list(prop_utxo_dict)
    prop_utxo_vals = [prop_utxo_dict[x] for x in prop_utxos]
    # get the private key for that utxo
    priv = wallet_service.get_key_from_addr(
        wallet_service.script_to_addr(prop_utxo_vals[0]['script']))
    # construct the arguments for the snicker proposal:
    our_input_utxos = [
        btc.CMutableTxOut(x['value'], x['script']) for x in prop_utxo_vals
    ]

    # destination must be a different mixdepth:
    prop_destn_spk = wallet_service.get_new_script(
        (options.mixdepth + 1) % (wallet_service.mixdepth + 1), 1)
    change_spk = wallet_service.get_new_script(options.mixdepth, 1)
    their_input = (txid1, output_index)
    # we also need to extract the pubkey of the chosen input from
    # the witness; we vary this depending on our wallet type:
    pubkey, msg = btc.extract_pubkey_from_witness(originating_tx, input_index)
    if not pubkey:
        log.error("Failed to extract pubkey from transaction: {}".format(msg))
        sys.exit(EXIT_FAILURE)
    encrypted_proposal = wallet_service.create_snicker_proposal(
        prop_utxos,
        their_input,
        our_input_utxos,
        originating_tx.vout[output_index],
        net_transfer,
        fee_est,
        priv,
        pubkey,
        prop_destn_spk,
        change_spk,
        version_byte=1) + b"," + bintohex(pubkey).encode('utf-8')
    if options.no_upload:
        jmprint(encrypted_proposal.decode("utf-8"))
        sys.exit(EXIT_SUCCESS)

    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = True if nodaemon == 1 else False
    snicker_client = SNICKERPostingClient([encrypted_proposal])
    servers = jm_single().config.get("SNICKER", "servers").split(",")
    snicker_pf = SNICKERClientProtocolFactory(snicker_client, servers)
    start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                  jm_single().config.getint("DAEMON", "daemon_port"),
                  None,
                  snickerfactory=snicker_pf,
                  daemon=daemon)
示例#20
0
 def default_info_callback(self, msg):
     jmprint(msg)
示例#21
0
 def default_user_info_callback(self, msg):
     """ Info level message print to command line.
     """
     jmprint(msg)
def main():
    parser = OptionParser(
        usage=
        'usage: %prog [options] [txid:n]',
        description="Adds one or more utxos to the list that can be used to make "
                    "commitments for anti-snooping. Note that this utxo, and its "
                    "PUBkey, will be revealed to makers, so consider the privacy "
                    "implication. "
                    
                    "It may be useful to those who are having trouble making "
                    "coinjoins due to several unsuccessful attempts (especially "
                    "if your joinmarket wallet is new). "
                    
                    "'Utxo' means unspent transaction output, it must not "
                    "already be spent. "
                    "The options -w, -r and -R offer ways to load these utxos "
                    "from a file or wallet. "
                    "If you enter a single utxo without these options, you will be "
                    "prompted to enter the private key here - it must be in "
                    "WIF compressed format. "

                    "BE CAREFUL about handling private keys! "
                    "Don't do this in insecure environments. "
                    
                    "Also note this ONLY works for standard (p2pkh or p2sh-p2wpkh) utxos."
    )
    parser.add_option(
        '-r',
        '--read-from-file',
        action='store',
        type='str',
        dest='in_file',
        help='name of plain text csv file containing utxos, one per line, format: '
        'txid:N, WIF-compressed-privkey'
    )
    parser.add_option(
        '-R',
        '--read-from-json',
        action='store',
        type='str',
        dest='in_json',
        help='name of json formatted file containing utxos with private keys, as '
        'output from "python wallet-tool.py -p walletname showutxos"'
        )
    parser.add_option(
        '-w',
        '--load-wallet',
        action='store',
        type='str',
        dest='loadwallet',
        help='name of wallet from which to load utxos and use as commitments.'
        )
    parser.add_option(
        '-g',
        '--gap-limit',
        action='store',
        type='int',
        dest='gaplimit',
        default = 6,
        help='Only to be used with -w; gap limit for Joinmarket wallet, default 6.'
    )
    parser.add_option(
        '-M',
        '--max-mixdepth',
        action='store',
        type='int',
        dest='maxmixdepth',
        default=5,
        help='Only to be used with -w; number of mixdepths for wallet, default 5.'
    )
    parser.add_option(
        '-d',
        '--delete-external',
        action='store_true',
        dest='delete_ext',
        help='deletes the current list of external commitment utxos',
        default=False
        )
    parser.add_option(
        '-v',
        '--validate-utxos',
        action='store_true',
        dest='validate',
        help='validate the utxos and pubkeys provided against the blockchain',
        default=False
    )
    parser.add_option(
        '-o',
        '--validate-only',
        action='store_true',
        dest='vonly',
        help='only validate the provided utxos (file or command line), not add',
        default=False
    )
    parser.add_option('--fast',
                      action='store_true',
                      dest='fastsync',
                      default=False,
                      help=('choose to do fast wallet sync, only for Core and '
                      'only for previously synced wallet'))
    (options, args) = parser.parse_args()
    load_program_config()
    #TODO; sort out "commit file location" global so this script can
    #run without this hardcoding:
    utxo_data = []
    if options.delete_ext:
        other = options.in_file or options.in_json or options.loadwallet
        if len(args) > 0 or other:
            if input("You have chosen to delete commitments, other arguments "
                         "will be ignored; continue? (y/n)") != 'y':
                jmprint("Quitting", "warning")
                sys.exit(0)
        c, e = get_podle_commitments()
        jmprint(pformat(e), "info")
        if input(
            "You will remove the above commitments; are you sure? (y/n): ") != 'y':
            jmprint("Quitting", "warning")
            sys.exit(0)
        update_commitments(external_to_remove=e)
        jmprint("Commitments deleted.", "important")
        sys.exit(0)

    #Three options (-w, -r, -R) for loading utxo and privkey pairs from a wallet,
    #csv file or json file.
    if options.loadwallet:
        wallet_path = get_wallet_path(options.loadwallet, None)
        wallet = open_wallet(wallet_path, gap_limit=options.gaplimit)
        while not jm_single().bc_interface.wallet_synced:
            sync_wallet(wallet, fast=options.fastsync)

        # minor note: adding a utxo from an external wallet for commitments, we
        # default to not allowing disabled utxos to avoid a privacy leak, so the
        # user would have to explicitly enable.
        for md, utxos in wallet.get_utxos_by_mixdepth_().items():
            for (txid, index), utxo in utxos.items():
                txhex = binascii.hexlify(txid).decode('ascii') + ':' + str(index)
                wif = wallet.get_wif_path(utxo['path'])
                utxo_data.append((txhex, wif))

    elif options.in_file:
        with open(options.in_file, "rb") as f:
            utxo_info = f.readlines()
        for ul in utxo_info:
            ul = ul.rstrip()
            if ul:
                u, priv = get_utxo_info(ul)
                if not u:
                    quit(parser, "Failed to parse utxo info: " + str(ul))
                utxo_data.append((u, priv))
    elif options.in_json:
        if not os.path.isfile(options.in_json):
            jmprint("File: " + options.in_json + " not found.", "error")
            sys.exit(0)
        with open(options.in_json, "rb") as f:
            try:
                utxo_json = json.loads(f.read())
            except:
                jmprint("Failed to read json from " + options.in_json, "error")
                sys.exit(0)
        for u, pva in iteritems(utxo_json):
            utxo_data.append((u, pva['privkey']))
    elif len(args) == 1:
        u = args[0]
        priv = input(
            'input private key for ' + u + ', in WIF compressed format : ')
        u, priv = get_utxo_info(','.join([u, priv]))
        if not u:
            quit(parser, "Failed to parse utxo info: " + u)
        utxo_data.append((u, priv))
    else:
        quit(parser, 'Invalid syntax')
    if options.validate or options.vonly:
        sw = False if jm_single().config.get("POLICY", "segwit") == "false" else True
        if not validate_utxo_data(utxo_data, segwit=sw):
            quit(parser, "Utxos did not validate, quitting")
    if options.vonly:
        sys.exit(0)
    
    #We are adding utxos to the external list
    assert len(utxo_data)
    add_ext_commitments(utxo_data)
 def render_POST(self, request):
     """ The sender will use POST to send the initial
     payment transaction.
     """
     jmprint("The server got this POST request: ")
     print(request)
     print(request.method)
     print(request.uri)
     print(request.args)
     print(request.path)
     print(request.content)
     proposed_tx = request.content
     assert isinstance(proposed_tx, BytesIO)
     payment_psbt_base64 = proposed_tx.read()
     payment_psbt = btc.PartiallySignedTransaction.from_base64(
         payment_psbt_base64)
     all_receiver_utxos = self.wallet_service.get_all_utxos()
     # TODO is there a less verbose way to get any 2 utxos from the dict?
     receiver_utxos_keys = list(all_receiver_utxos.keys())[:2]
     receiver_utxos = {k: v for k, v in all_receiver_utxos.items(
         ) if k in receiver_utxos_keys}
 
     # receiver will do other checks as discussed above, including payment
     # amount; as discussed above, this is out of the scope of this PSBT test.
 
     # construct unsigned tx for payjoin-psbt:
     payjoin_tx_inputs = [(x.prevout.hash[::-1],
                 x.prevout.n) for x in payment_psbt.unsigned_tx.vin]
     payjoin_tx_inputs.extend(receiver_utxos.keys())
     # find payment output and change output
     pay_out = None
     change_out = None
     for o in payment_psbt.unsigned_tx.vout:
         jm_out_fmt = {"value": o.nValue,
         "address": str(btc.CCoinAddress.from_scriptPubKey(
         o.scriptPubKey))}
         if o.nValue == payment_amt:
             assert pay_out is None
             pay_out = jm_out_fmt
         else:
             assert change_out is None
             change_out = jm_out_fmt
 
     # we now know there were two outputs and know which is payment.
     # bump payment output with our input:
     outs = [pay_out, change_out]
     our_inputs_val = sum([v["value"] for _, v in receiver_utxos.items()])
     pay_out["value"] += our_inputs_val
     print("we bumped the payment output value by: ", our_inputs_val)
     print("It is now: ", pay_out["value"])
     unsigned_payjoin_tx = btc.make_shuffled_tx(payjoin_tx_inputs, outs,
                                 version=payment_psbt.unsigned_tx.nVersion,
                                 locktime=payment_psbt.unsigned_tx.nLockTime)
     print("we created this unsigned tx: ")
     print(btc.human_readable_transaction(unsigned_payjoin_tx))
     # to create the PSBT we need the spent_outs for each input,
     # in the right order:
     spent_outs = []
     for i, inp in enumerate(unsigned_payjoin_tx.vin):
         input_found = False
         for j, inp2 in enumerate(payment_psbt.unsigned_tx.vin):
             if inp.prevout == inp2.prevout:
                 spent_outs.append(payment_psbt.inputs[j].utxo)
                 input_found = True
                 break
         if input_found:
             continue
         # if we got here this input is ours, we must find
         # it from our original utxo choice list:
         for ru in receiver_utxos.keys():
             if (inp.prevout.hash[::-1], inp.prevout.n) == ru:
                 spent_outs.append(
                     self.wallet_service.witness_utxos_to_psbt_utxos(
                         {ru: receiver_utxos[ru]})[0])
                 input_found = True
                 break
         # there should be no other inputs:
         assert input_found
 
     r_payjoin_psbt = self.wallet_service.create_psbt_from_tx(unsigned_payjoin_tx,
                                                   spent_outs=spent_outs)
     print("Receiver created payjoin PSBT:\n{}".format(
         self.wallet_service.human_readable_psbt(r_payjoin_psbt)))
 
     signresultandpsbt, err = self.wallet_service.sign_psbt(r_payjoin_psbt.serialize(),
                                                 with_sign_result=True)
     assert not err, err
     signresult, receiver_signed_psbt = signresultandpsbt
     assert signresult.num_inputs_final == len(receiver_utxos)
     assert not signresult.is_final
 
     print("Receiver signing successful. Payjoin PSBT is now:\n{}".format(
         self.wallet_service.human_readable_psbt(receiver_signed_psbt)))
     content = receiver_signed_psbt.to_base64()
     request.setHeader(b"content-length", ("%d" % len(content)).encode("ascii"))
     return content.encode("ascii")
示例#24
0
#!/usr/bin/env python3
from jmbase import jmprint
from jmclient import wallet_tool_main

if __name__ == "__main__":
    jmprint(wallet_tool_main("wallets"), "success")
def tumbler_taker_finished_update(taker,
                                  schedulefile,
                                  tumble_log,
                                  options,
                                  res,
                                  fromtx=False,
                                  waittime=0.0,
                                  txdetails=None):
    """on_finished_callback processing for tumbler.
    Note that this is *not* the full callback, but provides common
    processing across command line and other GUI versions.
    """

    if fromtx == "unconfirmed":
        #unconfirmed event means transaction has been propagated,
        #we update state to prevent accidentally re-creating it in
        #any crash/restart condition
        unconf_update(taker, schedulefile, tumble_log, True)
        return

    if fromtx:
        if res:
            #this has no effect except in the rare case that confirmation
            #is immediate; also it does not repeat the log entry.
            unconf_update(taker, schedulefile, tumble_log, False)
            #note that Qt does not yet support 'addrask', so this is only
            #for command line script TODO
            if taker.schedule[taker.schedule_index + 1][3] == 'addrask':
                jm_single().debug_silence[0] = True
                jmprint('\n'.join(['=' * 60] * 3))
                jmprint(
                    'Tumbler requires more addresses to stop amount correlation'
                )
                jmprint(
                    'Obtain a new destination address from your bitcoin recipient'
                )
                jmprint(
                    ' for example click the button that gives a new deposit address'
                )
                jmprint('\n'.join(['=' * 60] * 1))
                while True:
                    destaddr = input('insert new address: ')
                    addr_valid, errormsg = validate_address(destaddr)
                    if addr_valid:
                        break
                    jmprint(
                        'Address ' + destaddr + ' invalid. ' + errormsg +
                        ' try again', "warning")
                jm_single().debug_silence[0] = False
                taker.schedule[taker.schedule_index + 1][3] = destaddr
                taker.tdestaddrs.append(destaddr)

            waiting_message = "Waiting for: " + str(waittime) + " minutes."
            tumble_log.info(waiting_message)
            log.info(waiting_message)
        else:
            # a transaction failed, either because insufficient makers
            # (acording to minimum_makers) responded in Phase 1, or not all
            # makers responded in Phase 2, or the tx was a mempool conflict.
            # If the tx was a mempool conflict, we should restart with random
            # maker choice as usual. If someone didn't respond, we'll try to
            # repeat without the troublemakers.
            log.info("Schedule entry: " + str(
                taker.schedule[taker.schedule_index]) + \
                     " failed after timeout, trying again")
            taker.add_ignored_makers(taker.nonrespondants)
            #Is the failure in Phase 2?
            if not taker.latest_tx is None:
                if len(taker.nonrespondants) == 0:
                    # transaction was created validly but conflicted in the
                    # mempool; just try again without honest settings;
                    # i.e. fallback to same as Phase 1 failure.
                    log.info("Invalid transaction; possible mempool conflict.")
                else:
                    #Now we have to set the specific group we want to use, and hopefully
                    #they will respond again as they showed honesty last time.
                    #Note that we must wipe the list first; other honest makers needn't
                    #have the right settings (e.g. max cjamount), so can't be carried
                    #over from earlier transactions.
                    taker.honest_makers = []
                    taker.add_honest_makers(
                        list(
                            set(taker.maker_utxo_data.keys()).
                            symmetric_difference(set(taker.nonrespondants))))
                    #If insufficient makers were honest, we can only tweak the schedule.
                    #If enough were, we prefer to restart with them only:
                    log.info("Inside a Phase 2 failure; number of honest "
                             "respondants was: " +
                             str(len(taker.honest_makers)))
                    log.info("They were: " + str(taker.honest_makers))
                    if len(taker.honest_makers) >= jm_single().config.getint(
                            "POLICY", "minimum_makers"):
                        tumble_log.info(
                            "Transaction attempt failed, attempting to "
                            "restart with subset.")
                        tumble_log.info(
                            "The paramaters of the failed attempt: ")
                        tumble_log.info(
                            str(taker.schedule[taker.schedule_index]))
                        #we must reset the number of counterparties, as well as fix who they
                        #are; this is because the number is used to e.g. calculate fees.
                        #cleanest way is to reset the number in the schedule before restart.
                        taker.schedule[taker.schedule_index][2] = len(
                            taker.honest_makers)
                        retry_str = "Retrying with: " + str(taker.schedule[
                            taker.schedule_index][2]) + " counterparties."
                        tumble_log.info(retry_str)
                        log.info(retry_str)
                        taker.set_honest_only(True)
                        taker.schedule_index -= 1
                        return

            #There were not enough honest counterparties.
            #Tumbler is aggressive in trying to complete; we tweak the schedule
            #from this point in the mixdepth, then try again.
            tumble_log.info("Transaction attempt failed, tweaking schedule"
                            " and trying again.")
            tumble_log.info("The paramaters of the failed attempt: ")
            tumble_log.info(str(taker.schedule[taker.schedule_index]))
            taker.schedule_index -= 1
            taker.schedule = tweak_tumble_schedule(options, taker.schedule,
                                                   taker.schedule_index,
                                                   taker.tdestaddrs)
            tumble_log.info("We tweaked the schedule, the new schedule is:")
            tumble_log.info(pprint.pformat(taker.schedule))
    else:
        if not res:
            failure_msg = "Did not complete successfully, shutting down"
            tumble_log.info(failure_msg)
            log.info(failure_msg)
        else:
            log.info("All transactions completed correctly")
            tumble_log.info("Completed successfully the last entry:")
            #Whether sweep or not, the amt is not in satoshis; use taker data
            hramt = taker.cjamount
            tumble_log.info(
                human_readable_schedule_entry(
                    taker.schedule[taker.schedule_index], hramt))
            #copy of above, TODO refactor out
            taker.schedule[taker.schedule_index][5] = 1
            with open(schedulefile, "wb") as f:
                f.write(schedule_to_text(taker.schedule))
 def on_tx_received(self, nick, txhex, offerinfo):
     if self.txmal:
         jmprint("Counterparty tx rejected maliciously", "debug")
         return (False, "malicious tx rejection")
     return super(DeterministicMaliciousYieldGenerator,
                  self).on_tx_received(nick, txhex, offerinfo)
示例#27
0
                                    if not success:
                                        jmprint(
                                            "Failed to import SNICKER key: {}".
                                            format(msg), "error")
                                        return False
                                    else:
                                        jmprint("... success.")
                                    # we want the blockheight to track where the next-round rescan
                                    # must start from
                                    current_block_heights.add(
                                        wallet_service.
                                        get_transaction_block_height(tx))
                                    # add this transaction to the next round.
                                    new_txs.append(tx)
    if len(new_txs) == 0:
        return True
    seed_transactions.extend(new_txs)
    earliest_new_blockheight = min(current_block_heights)
    jmprint("New SNICKER addresses were imported to the Core wallet; "
            "do rescanblockchain again, starting from block {}, before "
            "restarting this script.".format(earliest_new_blockheight))
    return False


if __name__ == "__main__":
    res = main()
    if not res:
        jmprint("Script finished, recovery is NOT complete.", level="warning")
    else:
        jmprint("Script finished, recovery is complete.")
示例#28
0
def main():
    parser = OptionParser(usage='usage: %prog [options] walletname',
                          description=description)
    parser.add_option('-m',
                      '--mixdepth',
                      action='store',
                      type='int',
                      dest='mixdepth',
                      default=0,
                      help="mixdepth to source coins from")
    parser.add_option('-a',
                      '--amtmixdepths',
                      action='store',
                      type='int',
                      dest='amtmixdepths',
                      help='number of mixdepths in wallet, default 5',
                      default=5)
    parser.add_option('-g',
                      '--gap-limit',
                      type="int",
                      action='store',
                      dest='gaplimit',
                      help='gap limit for wallet, default=6',
                      default=6)
    add_base_options(parser)
    (options, args) = parser.parse_args()
    load_program_config(config_path=options.datadir)
    check_regtest()
    if len(args) != 1:
        log.error("Invalid arguments, see --help")
        sys.exit(EXIT_ARGERROR)
    wallet_name = args[0]
    wallet_path = get_wallet_path(wallet_name, None)
    max_mix_depth = max([options.mixdepth, options.amtmixdepths - 1])
    wallet = open_test_wallet_maybe(
        wallet_path,
        wallet_name,
        max_mix_depth,
        wallet_password_stdin=options.wallet_password_stdin,
        gap_limit=options.gaplimit)
    wallet_service = WalletService(wallet)

    # step 1: do a full recovery style sync. this will pick up
    # all addresses that we expect to match transactions against,
    # from a blank slate Core wallet that originally had no imports.
    if not options.recoversync:
        jmprint("Recovery sync was not set, but using it anyway.")
    while not wallet_service.synced:
        wallet_service.sync_wallet(fast=False)
    # Note that the user may be interrupted above by the rescan
    # request; this is as for normal scripts; after the rescan is done
    # (usually, only once, but, this *IS* needed here, unlike a normal
    # wallet generation event), we just try again.

    # Now all address from HD are imported, we need to grab
    # all the transactions for those addresses; this includes txs
    # that *spend* as well as receive our coins, so will include
    # "first-out" SNICKER txs as well as ordinary spends and JM coinjoins.
    seed_transactions = wallet_service.get_all_transactions()

    # Search for SNICKER txs and add them if they match.
    # We proceed recursively; we find all one-out matches, then
    # all 2-out matches, until we find no new ones and stop.

    if len(seed_transactions) == 0:
        jmprint("No transactions were found for this wallet. Did you rescan?")
        return False

    new_txs = []
    current_block_heights = set()
    for tx in seed_transactions:
        if btc.is_snicker_tx(tx):
            jmprint("Found a snicker tx: {}".format(
                bintohex(tx.GetTxid()[::-1])))
            equal_outs = btc.get_equal_outs(tx)
            if not equal_outs:
                continue
            if all([
                    wallet_service.is_known_script(x.scriptPubKey) == False
                    for x in [a[1] for a in equal_outs]
            ]):
                # it is now *very* likely that one of the two equal
                # outputs is our SNICKER custom output
                # script; notice that in this case, the transaction *must*
                # have spent our inputs, since it didn't recognize ownership
                # of either coinjoin output (and if it did recognize the change,
                # it would have recognized the cj output also).
                # We try to regenerate one of the outputs, but warn if
                # we can't.
                my_indices = get_pubs_and_indices_of_inputs(tx,
                                                            wallet_service,
                                                            ours=True)
                for mypub, mi in my_indices:
                    for eo in equal_outs:
                        for (other_pub, i) in get_pubs_and_indices_of_inputs(
                                tx, wallet_service, ours=False):
                            for (our_pub,
                                 j) in get_pubs_and_indices_of_ancestor_inputs(
                                     tx.vin[mi], wallet_service, ours=True):
                                our_spk = wallet_service.pubkey_to_script(
                                    our_pub)
                                our_priv = wallet_service.get_key_from_addr(
                                    wallet_service.script_to_addr(our_spk))
                                tweak_bytes = btc.ecdh(our_priv[:-1],
                                                       other_pub)
                                tweaked_pub = btc.snicker_pubkey_tweak(
                                    our_pub, tweak_bytes)
                                tweaked_spk = wallet_service.pubkey_to_script(
                                    tweaked_pub)
                                if tweaked_spk == eo[1].scriptPubKey:
                                    # TODO wallet.script_to_addr has a dubious assertion, that's why
                                    # we use btc method directly:
                                    address_found = str(
                                        btc.CCoinAddress.from_scriptPubKey(
                                            btc.CScript(tweaked_spk)))
                                    #address_found = wallet_service.script_to_addr(tweaked_spk)
                                    jmprint(
                                        "Found a new SNICKER output belonging to us."
                                    )
                                    jmprint(
                                        "Output address {} in the following transaction:"
                                        .format(address_found))
                                    jmprint(btc.human_readable_transaction(tx))
                                    jmprint(
                                        "Importing the address into the joinmarket wallet..."
                                    )
                                    # NB for a recovery we accept putting any imported keys all into
                                    # the same mixdepth (0); TODO investigate correcting this, it will
                                    # be a little complicated.
                                    success, msg = wallet_service.check_tweak_matches_and_import(
                                        wallet_service.script_to_addr(our_spk),
                                        tweak_bytes, tweaked_pub,
                                        wallet_service.mixdepth)
                                    if not success:
                                        jmprint(
                                            "Failed to import SNICKER key: {}".
                                            format(msg), "error")
                                        return False
                                    else:
                                        jmprint("... success.")
                                    # we want the blockheight to track where the next-round rescan
                                    # must start from
                                    current_block_heights.add(
                                        wallet_service.
                                        get_transaction_block_height(tx))
                                    # add this transaction to the next round.
                                    new_txs.append(tx)
    if len(new_txs) == 0:
        return True
    seed_transactions.extend(new_txs)
    earliest_new_blockheight = min(current_block_heights)
    jmprint("New SNICKER addresses were imported to the Core wallet; "
            "do rescanblockchain again, starting from block {}, before "
            "restarting this script.".format(earliest_new_blockheight))
    return False
    def outputs_watcher(self, wallet_name, notifyaddr, tx_output_set,
                        unconfirmfun, confirmfun, timeoutfun):
        """Given a key for the watcher loop (notifyaddr), a wallet name (account),
        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]
        jmprint('txoutset=' + pprint.pformat(tx_output_set), "debug")
        unconftx = self.get_from_electrum('blockchain.address.get_mempool',
                                          notifyaddr,
                                          blocking=True).get('result')
        unconftxs = set([str(t['tx_hash']) for t in unconftx])
        if len(unconftxs):
            txdatas = []
            for txid in unconftxs:
                txdatas.append({
                    'id':
                    txid,
                    'hex':
                    str(
                        self.get_from_electrum('blockchain.transaction.get',
                                               txid,
                                               blocking=True).get('result'))
                })
            unconfirmed_txid = None
            for txdata in txdatas:
                txhex = txdata['hex']
                outs = set([(sv['script'], sv['value'])
                            for sv in btc.deserialize(txhex)['outs']])
                jmprint('unconfirm query outs = ' + str(outs), "debug")
                if outs == tx_output_set:
                    unconfirmed_txid = txdata['id']
                    unconfirmed_txhex = txhex
                    break
            #call unconf callback if it was found in the mempool
            if unconfirmed_txid and not wl[1]:
                jmprint("Tx: " + str(unconfirmed_txid) + " seen on network.",
                        "info")
                unconfirmfun(btc.deserialize(unconfirmed_txhex),
                             unconfirmed_txid)
                wl[1] = True
                return

        conftx = self.get_from_electrum('blockchain.address.listunspent',
                                        notifyaddr,
                                        blocking=True).get('result')
        conftxs = set([str(t['tx_hash']) for t in conftx])
        if len(conftxs):
            txdatas = []
            for txid in conftxs:
                txdata = str(
                    self.get_from_electrum('blockchain.transaction.get',
                                           txid,
                                           blocking=True).get('result'))
                txdatas.append({'hex': txdata, 'id': txid})
            confirmed_txid = None
            for txdata in txdatas:
                txhex = txdata['hex']
                outs = set([(sv['script'], sv['value'])
                            for sv in btc.deserialize(txhex)['outs']])
                jmprint('confirm query outs = ' + str(outs), "info")
                if outs == tx_output_set:
                    confirmed_txid = txdata['id']
                    confirmed_txhex = txhex
                    break
            if confirmed_txid and not wl[2]:
                confirmfun(btc.deserialize(confirmed_txhex), confirmed_txid, 1)
                wl[2] = True
                wl[0].stop()
                return
 def clientConnectionFailed(self, connector, reason):
     jmprint('connection failed', "warning")
     self.bci.start_electrum_proto(None)