Example #1
0
def test_imported_privkey(setup_wallets):
    for n in ["mainnet", "testnet"]:
        privkey = "7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb401"
        jm_single().config.set("BLOCKCHAIN", "network", n)
        password = "******"
        password_key = bitcoin.bin_dbl_sha256(password)
        wifprivkey = bitcoin.wif_compressed_privkey(privkey, get_p2pk_vbyte())
        #mainnet is "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi"
        #to verify use from_wif_privkey and privkey_to_address
        if n == "mainnet":
            iaddr = "1LDsjB43N2NAQ1Vbc2xyHca4iBBciN8iwC"
        else:
            iaddr = "mzjq2E92B3oRB7yDKbwM7XnPaAnKfRERw2"
        privkey_bin = bitcoin.from_wif_privkey(
            wifprivkey, vbyte=get_p2pk_vbyte()).decode('hex')[:-1]
        encrypted_privkey = encryptData(password_key, privkey_bin)
        encrypted_privkey_bad = encryptData(password_key, privkey_bin[:6])
        walletdir = "wallets"
        testwalletname = "test" + n
        pathtowallet = os.path.join(walletdir, testwalletname)
        seed = bitcoin.sha256("\xaa" * 64)[:32]
        encrypted_seed = encryptData(password_key, seed.decode('hex'))
        timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
        for ep in [encrypted_privkey, encrypted_privkey_bad]:
            walletfilejson = {
                'creator':
                'joinmarket project',
                'creation_time':
                timestamp,
                'encrypted_seed':
                encrypted_seed.encode('hex'),
                'network':
                n,
                'index_cache': [[0, 0]] * 5,
                'imported_keys': [{
                    'encrypted_privkey': ep.encode('hex'),
                    'mixdepth': 0
                }]
            }
            walletfile = json.dumps(walletfilejson)
            if not os.path.exists(walletdir):
                os.makedirs(walletdir)
            with open(pathtowallet, "wb") as f:
                f.write(walletfile)
            if ep == encrypted_privkey_bad:
                with pytest.raises(Exception) as e_info:
                    Wallet(testwalletname, password, 5, 6, False, False)
                continue
            newwallet = Wallet(testwalletname, password, 5, 6, False, False)
            assert newwallet.seed == seed
            #test accessing the key from the addr
            assert newwallet.get_key_from_addr(
                iaddr) == bitcoin.from_wif_privkey(wifprivkey,
                                                   vbyte=get_p2pk_vbyte())
            if n == "testnet":
                jm_single().bc_interface.sync_wallet(newwallet)
    load_program_config()
def make_wallets(n,
                 wallet_structures=None,
                 mean_amt=1,
                 sdev_amt=0,
                 start_index=0,
                 fixed_seeds=None,
                 test_wallet=False,
                 passwords=None):
    '''n: number of wallets to be created
       wallet_structure: array of n arrays , each subarray
       specifying the number of addresses to be populated with coins
       at each depth (for now, this will only populate coins into 'receive' addresses)
       mean_amt: the number of coins (in btc units) in each address as above
       sdev_amt: if randomness in amouts is desired, specify here.
       Returns: a dict of dicts of form {0:{'seed':seed,'wallet':Wallet object},1:..,}
       Default Wallet constructor is joinmarket.Wallet, else use TestWallet,
       which takes a password parameter as in the list passwords.
       '''
    if len(wallet_structures) != n:
        raise Exception("Number of wallets doesn't match wallet structures")
    if not fixed_seeds:
        seeds = chunks(binascii.hexlify(os.urandom(15 * n)), 15 * 2)
    else:
        seeds = fixed_seeds
    wallets = {}
    for i in range(n):
        if test_wallet:
            w = Wallet(seeds[i], passwords[i], max_mix_depth=5)
        else:
            w = Wallet(seeds[i], None, max_mix_depth=5)
        wallets[i + start_index] = {'seed': seeds[i], 'wallet': w}
        for j in range(5):
            for k in range(wallet_structures[i][j]):
                deviation = sdev_amt * random.random()
                amt = mean_amt - sdev_amt / 2.0 + deviation
                if amt < 0: amt = 0.001
                amt = float(Decimal(amt).quantize(Decimal(10)**-8))
                jm_single().bc_interface.grab_coins(
                    wallets[i + start_index]['wallet'].get_external_addr(j),
                    amt)
            #reset the index so the coins can be seen if running in same script
            wallets[
                i +
                start_index]['wallet'].index[j][0] -= wallet_structures[i][j]
    return wallets
def create_default_testnet_wallet():
    walletdir = "wallets"
    testwalletname = "testwallet.json"
    pathtowallet = os.path.join(walletdir, testwalletname)
    if os.path.exists(pathtowallet):
        os.remove(pathtowallet)
    seed = "hello"
    return (walletdir, pathtowallet, testwalletname,
            Wallet(seed,
                   None,
                   5,
                   6,
                   extend_mixdepth=False,
                   storepassword=False))
def test_blockr_sync(setup_blockr, net, seed, gaplimit, showprivkey, method):
    jm_single().config.set("BLOCKCHAIN", "network", net)
    wallet = Wallet(seed, None, max_mix_depth=5)
    sync_wallet(wallet)

    #copy pasted from wallet-tool; some boiled down form of
    #this should really be in wallet.py in the joinmarket module.
    def cus_print(s):
        print s

    total_balance = 0
    for m in range(wallet.max_mix_depth):
        cus_print('mixing depth %d m/0/%d/' % (m, m))
        balance_depth = 0
        for forchange in [0, 1]:
            cus_print(' ' + ('external' if forchange == 0 else 'internal') +
                      ' addresses m/0/%d/%d/' % (m, forchange))

            for k in range(wallet.index[m][forchange] + gaplimit):
                addr = wallet.get_addr(m, forchange, k)
                balance = 0.0
                for addrvalue in wallet.unspent.values():
                    if addr == addrvalue['address']:
                        balance += addrvalue['value']
                balance_depth += balance
                used = ('used' if k < wallet.index[m][forchange] else ' new')
                if showprivkey:
                    privkey = btc.wif_compressed_privkey(
                        wallet.get_key(m, forchange, k), get_p2pk_vbyte())
                else:
                    privkey = ''
                if (method == 'displayall' or balance > 0
                        or (used == ' new' and forchange == 0)):
                    cus_print(
                        '  m/0/%d/%d/%03d %-35s%s %.8f btc %s' %
                        (m, forchange, k, addr, used, balance / 1e8, privkey))
        total_balance += balance_depth
        print('for mixdepth=%d balance=%.8fbtc' % (m, balance_depth / 1e8))
    assert total_balance == 96085297
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) 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 -u -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 raw_input(
                    "You have chosen to delete commitments, other arguments "
                    "will be ignored; continue? (y/n)") != 'y':
                print "Quitting"
                sys.exit(0)
        c, e = get_podle_commitments()
        print pformat(e)
        if raw_input(
                "You will remove the above commitments; are you sure? (y/n): "
        ) != 'y':
            print "Quitting"
            sys.exit(0)
        update_commitments(external_to_remove=e)
        print "Commitments deleted."
        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:
        while True:
            pwd = get_password("Enter wallet decryption passphrase: ")
            try:
                wallet = Wallet(options.loadwallet, pwd, options.maxmixdepth,
                                options.gaplimit)
            except WalletError:
                print("Wrong password, try again.")
                continue
            except Exception as e:
                print("Failed to load wallet, error message: " + repr(e))
                sys.exit(0)
            break
        sync_wallet(wallet, fast=options.fastsync)
        unsp = {}
        for u, av in wallet.unspent.iteritems():
            addr = av['address']
            key = wallet.get_key_from_addr(addr)
            wifkey = btc.wif_compressed_privkey(key, vbyte=get_p2pk_vbyte())
            unsp[u] = {
                'address': av['address'],
                'value': av['value'],
                'privkey': wifkey
            }
        for u, pva in unsp.iteritems():
            utxo_data.append((u, pva['privkey']))
    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):
            print "File: " + options.in_json + " not found."
            sys.exit(0)
        with open(options.in_json, "rb") as f:
            try:
                utxo_json = json.loads(f.read())
            except:
                print "Failed to read json from " + options.in_json
                sys.exit(0)
        for u, pva in utxo_json.iteritems():
            utxo_data.append((u, pva['privkey']))
    elif len(args) == 1:
        u = args[0]
        priv = raw_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:
        if not validate_utxo_data(utxo_data):
            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)
Example #6
0
def main_cs(test_data=None):
    #twisted logging (TODO disable for non-debug runs)
    if test_data:
        wallet_name, args, options, use_ssl, alt_class, alt_c_class = test_data
    else:
        log.startLogging(sys.stdout)
        #Joinmarket wallet
        parser = get_coinswap_parser()
        (options, args) = parser.parse_args()
        load_coinswap_config()
        wallet_name = args[0]
    #depth 0: spend in, depth 1: receive out, depth 2: for backout transactions.
    max_mix_depth = 3
    if not os.path.exists(os.path.join('wallets', wallet_name)):
        wallet = Wallet(wallet_name, None, max_mix_depth, 6)
    else:
        while True:
            try:
                pwd = get_password("Enter wallet decryption passphrase: ")
                wallet = Wallet(wallet_name, pwd, max_mix_depth, 6)
            except WalletError:
                print("Wrong password, try again.")
                continue
            except Exception as e:
                print("Failed to load wallet, error message: " + repr(e))
                sys.exit(0)
            break
    #for testing main script (not test framework), need funds.
    if not test_data and isinstance(cs_single().bc_interface,
                                    RegtestBitcoinCoreInterface):
        cs_single().bc_interface.grab_coins(wallet.get_new_addr(0, 0), 2.0)
        time.sleep(3)
    sync_wallet(wallet, fast=options.fastsync)
    wallet.used_coins = None
    if options.serve:
        #sanity check that client params were not provided:
        if len(args) > 1:
            print("Extra parameters provided for running as server. "
                  "Are you sure you didn't want to run as client?")
            sys.exit(0)
        if not test_data:
            main_server(options, wallet)
        else:
            main_server(options, wallet, {
                'use_ssl': use_ssl,
                'alt_c_class': alt_c_class
            })
            return wallet.get_balance_by_mixdepth()
        return
    tx01_amount = int(args[1])
    #Reset the targetting for backout transactions
    oldtarget = cs_single().config.get("POLICY", "tx_fees")
    newtarget = cs_single().config.getint("POLICY", "backout_fee_target")
    multiplier = float(cs_single().config.get("POLICY",
                                              "backout_fee_multiplier"))
    cs_single().config.set("POLICY", "tx_fees", str(newtarget))
    tx23fee = estimate_tx_fee((1, 2, 2), 1, txtype='p2shMofN')
    tx23fee = int(multiplier * tx23fee)
    tx24_recipient_amount = tx01_amount - tx23fee
    tx35_recipient_amount = tx01_amount - tx23fee
    cs_single().config.set("POLICY", "tx_fees", oldtarget)
    #to allow testing of confirm/unconfirm callback for multiple txs
    if isinstance(cs_single().bc_interface, RegtestBitcoinCoreInterface):
        cs_single().bc_interface.tick_forward_chain_interval = 2
        cs_single().bc_interface.simulating = True
        cs_single().config.set("BLOCKCHAIN", "notify_port", "62652")
        cs_single().config.set("BLOCKCHAIN", "rpc_host", "127.0.0.2")

    #if restart option selected, read state and backout
    if options.recover:
        session_id = options.recover
        alice = CoinSwapAlice(wallet, 'alicestate')
        alice.bbmb = wallet.get_balance_by_mixdepth(verbose=False)
        alice.load(sessionid=session_id)
        alice.backout("Recovering from shutdown")
        reactor.run()
        return
    if len(args) > 2:
        tx5address = args[2]
        if not validate_address(tx5address):
            print("Invalid address: ", tx5address)
            sys.exit(0)
    else:
        #Our destination address should be in a separate mixdepth
        tx5address = wallet.get_new_addr(1, 1)
    #instantiate the parameters, but don't yet have the ephemeral pubkeys
    #or destination addresses.
    cpp = CoinSwapPublicParameters(tx01_amount, tx24_recipient_amount,
                                   tx35_recipient_amount)
    #Alice must set the unique identifier for this run.
    cpp.set_session_id()
    cpp.set_tx5_address(tx5address)
    testing_mode = True if test_data else False
    aliceclass = alt_class if test_data and alt_class else CoinSwapAlice
    alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode)
    scheme, server, port = options.serverport.split(":")
    print("got this scheme, server, port: ", scheme, server, port)
    if scheme == "https":
        usessl = True
    elif scheme == "http":
        usessl = False
    else:
        print("Invalid server string: ", options.serverport)
        sys.exit(0)
    if not server[:2] == "//":
        print("Invalid server string: ", options.serverport)
    alice_client = CoinSwapJSONRPCClient(server[2:], port, alice.sm.tick,
                                         alice.backout, usessl)
    alice.set_jsonrpc_client(alice_client)
    reactor.callWhenRunning(alice.sm.tick)
    if not test_data:
        reactor.run()
    if test_data:
        return alice
def test_wallet_create(setup_wallets, includecache, wrongnet, storepwd,
                       extendmd, pwdnumtries):
    walletdir, pathtowallet, testwalletname, wallet = create_default_testnet_wallet(
    )
    assert wallet.get_key(
        4, 1,
        17) == "1289ca322f96673acef83f396a9735840e3ab69f0459cf9bfa8d9985a876534401"
    assert wallet.get_addr(2, 0, 5) == "myWPu9QJWHGE79XAmuKkwKgNk8vsr5evpk"
    jm_single().bc_interface.wallet_synced = True
    assert wallet.get_new_addr(1, 0) == "mi88ZgDGPmarzcsU6S437h9CY9BLmgH5M6"
    assert wallet.get_external_addr(3) == "mvChQuChnXVhqvH67wfMxrodPQ7xccdVJU"
    addr3internal = wallet.get_internal_addr(3)
    assert addr3internal == "mv26o79Bauf2miJMoxoSu1vXmfXnk85YPQ"
    assert wallet.get_key_from_addr(
        addr3internal) == "2a283c9a2168a25509e2fb944939637228c50c8b4fecd9024650316c4584246501"
    dummyaddr = "mvw1NazKDRbeNufFANqpYNAANafsMC2zVU"
    assert not wallet.get_key_from_addr(dummyaddr)
    #Make a new Wallet(), and prepare a testnet wallet file for this wallet

    password = "******"
    password_key = bitcoin.bin_dbl_sha256(password)
    seed = bitcoin.sha256("\xaa" * 64)[:32]
    encrypted_seed = encryptData(password_key, seed.decode('hex'))
    timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
    net = get_network() if not wrongnet else 'mainnnet'
    walletfilejson = {'creator': 'joinmarket project',
                      'creation_time': timestamp,
                      'encrypted_seed': encrypted_seed.encode('hex'),
                      'network': net}
    if includecache:
        mmd = wallet.max_mix_depth if not extendmd else wallet.max_mix_depth + 5
        print("using mmd: " + str(mmd))
        walletfilejson.update({'index_cache': [[0, 0]] * mmd})
    walletfile = json.dumps(walletfilejson)
    if not os.path.exists(walletdir):
        os.makedirs(walletdir)
    with open(pathtowallet, "wb") as f:
        f.write(walletfile)
    if wrongnet:
        with pytest.raises(ValueError) as e_info:
            Wallet(testwalletname,
                   password,
                   5,
                   6,
                   extend_mixdepth=extendmd,
                   storepassword=storepwd)
        return
    from string import ascii_letters
    for i in range(
            pwdnumtries):  #multiple tries to ensure pkcs7 error is triggered
        with pytest.raises(WalletError) as e_info:
            wrongpwd = "".join([random.choice(ascii_letters) for _ in range(20)
                               ])
            Wallet(testwalletname,
                   wrongpwd,
                   5,
                   6,
                   extend_mixdepth=extendmd,
                   storepassword=storepwd)

    with pytest.raises(WalletError) as e_info:
        Wallet(testwalletname,
               None,
               5,
               6,
               extend_mixdepth=extendmd,
               storepassword=storepwd)
    newwallet = Wallet(testwalletname,
                       password,
                       5,
                       6,
                       extend_mixdepth=extendmd,
                       storepassword=storepwd)
    assert newwallet.seed == seed
    #now we have a functional wallet + file, update the cache; first try
    #with failed paths
    oldpath = newwallet.path
    newwallet.path = None
    newwallet.update_cache_index()
    newwallet.path = "fake-path-definitely-doesnt-exist"
    newwallet.update_cache_index()
    #with real path
    newwallet.path = oldpath
    newwallet.index = [[1, 1]] * 5
    newwallet.update_cache_index()

    #ensure we cannot find a mainnet wallet from seed
    seed = "goodbye"
    jm_single().config.set("BLOCKCHAIN", "network", "mainnet")
    with pytest.raises(IOError) as e_info:
        Wallet(seed, 5, 6, False, False)
    load_program_config()
def main():
    parser = get_sendpayment_parser()
    (options, args) = parser.parse_args()
    load_program_config()

    if options.schedule == '' and len(args) < 3:
        parser.error('Needs a wallet, amount and destination address')
        sys.exit(0)

    #without schedule file option, use the arguments to create a schedule
    #of a single transaction
    sweeping = False
    if options.schedule == '':
        #note that sendpayment doesn't support fractional amounts, fractions throw
        #here.
        amount = int(args[1])
        if amount == 0:
            sweeping = True
        destaddr = args[2]
        mixdepth = options.mixdepth
        addr_valid, errormsg = validate_address(destaddr)
        if not addr_valid:
            print('ERROR: Address invalid. ' + errormsg)
            return
        schedule = [[
            options.mixdepth, amount, options.makercount, destaddr, 0.0, 0
        ]]
    else:
        result, schedule = get_schedule(options.schedule)
        if not result:
            log.info(
                "Failed to load schedule file, quitting. Check the syntax.")
            log.info("Error was: " + str(schedule))
            sys.exit(0)
        mixdepth = 0
        for s in schedule:
            if s[1] == 0:
                sweeping = True
            #only used for checking the maximum mixdepth required
            mixdepth = max([mixdepth, s[0]])

    wallet_name = args[0]

    #to allow testing of confirm/unconfirm callback for multiple txs
    if isinstance(jm_single().bc_interface, RegtestBitcoinCoreInterface):
        jm_single().bc_interface.tick_forward_chain_interval = 10
        jm_single().maker_timeout_sec = 5

    chooseOrdersFunc = None
    if options.pickorders:
        chooseOrdersFunc = pick_order
        if sweeping:
            print('WARNING: You may have to pick offers multiple times')
            print('WARNING: due to manual offer picking while sweeping')
    elif options.choosecheapest:
        chooseOrdersFunc = cheapest_order_choose
    else:  # choose randomly (weighted)
        chooseOrdersFunc = weighted_order_choose

    # Dynamically estimate a realistic fee if it currently is the default value.
    # At this point we do not know even the number of our own inputs, so
    # we guess conservatively with 2 inputs and 2 outputs each
    if options.txfee == -1:
        options.txfee = max(options.txfee, estimate_tx_fee(2, 2))
        log.debug("Estimated miner/tx fee for each cj participant: " +
                  str(options.txfee))
    assert (options.txfee >= 0)

    log.debug('starting sendpayment')

    if not options.userpcwallet:
        max_mix_depth = max([mixdepth, options.amtmixdepths])
        if not os.path.exists(os.path.join('wallets', wallet_name)):
            wallet = Wallet(wallet_name, None, max_mix_depth, options.gaplimit)
        else:
            while True:
                try:
                    pwd = get_password("Enter wallet decryption passphrase: ")
                    wallet = Wallet(wallet_name, pwd, max_mix_depth,
                                    options.gaplimit)
                except WalletError:
                    print("Wrong password, try again.")
                    continue
                except Exception as e:
                    print("Failed to load wallet, error message: " + repr(e))
                    sys.exit(0)
                break
    else:
        wallet = BitcoinCoreWallet(fromaccount=wallet_name)
    sync_wallet(wallet, fast=options.fastsync)

    if options.makercount == 0:
        if isinstance(wallet, BitcoinCoreWallet):
            raise NotImplementedError(
                "Direct send only supported for JM wallets")
        direct_send(wallet, amount, mixdepth, destaddr, options.answeryes)
        return

    def filter_orders_callback(orders_fees, cjamount):
        orders, total_cj_fee = orders_fees
        log.info("Chose these orders: " + pprint.pformat(orders))
        log.info('total cj fee = ' + str(total_cj_fee))
        total_fee_pc = 1.0 * total_cj_fee / cjamount
        log.info('total coinjoin fee = ' +
                 str(float('%.3g' % (100.0 * total_fee_pc))) + '%')
        WARNING_THRESHOLD = 0.02  # 2%
        if total_fee_pc > WARNING_THRESHOLD:
            log.info('\n'.join(['=' * 60] * 3))
            log.info('WARNING   ' * 6)
            log.info('\n'.join(['=' * 60] * 1))
            log.info(
                'OFFERED COINJOIN FEE IS UNUSUALLY HIGH. DOUBLE/TRIPLE CHECK.')
            log.info('\n'.join(['=' * 60] * 1))
            log.info('WARNING   ' * 6)
            log.info('\n'.join(['=' * 60] * 3))
        if not options.answeryes:
            if raw_input('send with these orders? (y/n):')[0] != 'y':
                return False
        return True

    def taker_finished(res, fromtx=False, waittime=0.0, txdetails=None):
        if fromtx == "unconfirmed":
            #If final entry, stop *here*, don't wait for confirmation
            if taker.schedule_index + 1 == len(taker.schedule):
                reactor.stop()
            return
        if fromtx:
            if res:
                txd, txid = txdetails
                taker.wallet.remove_old_utxos(txd)
                taker.wallet.add_new_utxos(txd, txid)
                reactor.callLater(waittime * 60,
                                  clientfactory.getClient().clientStart)
            else:
                #a transaction failed; just stop
                reactor.stop()
        else:
            if not res:
                log.info("Did not complete successfully, shutting down")
            #Should usually be unreachable, unless conf received out of order;
            #because we should stop on 'unconfirmed' for last (see above)
            else:
                log.info("All transactions completed correctly")
            reactor.stop()

    taker = Taker(wallet,
                  schedule,
                  order_chooser=chooseOrdersFunc,
                  callbacks=(filter_orders_callback, None, taker_finished))
    clientfactory = JMTakerClientProtocolFactory(taker)
    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = True if nodaemon == 1 else False
    start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                  jm_single().config.getint("DAEMON", "daemon_port"),
                  clientfactory,
                  daemon=daemon)
def test_wallet_create(setup_wallets, includecache, wrongnet, storepwd,
                       extendmd, pwdnumtries):
    walletdir, pathtowallet, testwalletname, wallet = create_default_testnet_wallet(
    )
    assert wallet.get_key(
        4, 1,
        17) == "96095d7542e4e832c476b9df7e49ca9e5be61ad3bb8c8a3bdd8e141e2f4caf9101"
    assert wallet.get_addr(2, 0, 5) == "2NBUxbEQrGPKrYCV6d4o7Y4AtJ34Uy6gZZg"
    jm_single().bc_interface.wallet_synced = True
    assert wallet.get_new_addr(1, 0) == "2Mz817RE6zqywgkG2h9cATUoiXwnFSxufk2"
    assert wallet.get_external_addr(3) == "2N3gn65WXEzbLnjk5FLDZPc1pL6ebvZAmoA"
    addr3internal = wallet.get_internal_addr(3)
    assert addr3internal == "2N5NMTYogAyrGhDtWBnVQUp1kgwwFzcf7UM"
    assert wallet.get_key_from_addr(
        addr3internal) == "089a7173314d29f99e02a37e36da517ce41537a317c83284db1f33dda0af0cc201"
    dummyaddr = "mvw1NazKDRbeNufFANqpYNAANafsMC2zVU"
    assert not wallet.get_key_from_addr(dummyaddr)
    #Make a new Wallet(), and prepare a testnet wallet file for this wallet

    password = "******"
    password_key = bitcoin.bin_dbl_sha256(password)
    seed = bitcoin.sha256("\xaa" * 64)[:32]
    encrypted_seed = encryptData(password_key, seed.decode('hex'))
    timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
    net = get_network() if not wrongnet else 'mainnnet'
    walletfilejson = {'creator': 'joinmarket project',
                      'creation_time': timestamp,
                      'encrypted_seed': encrypted_seed.encode('hex'),
                      'network': net}
    if includecache:
        mmd = wallet.max_mix_depth if not extendmd else wallet.max_mix_depth + 5
        print("using mmd: " + str(mmd))
        walletfilejson.update({'index_cache': [[0, 0]] * mmd})
    walletfile = json.dumps(walletfilejson)
    if not os.path.exists(walletdir):
        os.makedirs(walletdir)
    with open(pathtowallet, "wb") as f:
        f.write(walletfile)
    if wrongnet:
        with pytest.raises(ValueError) as e_info:
            SegwitWallet(testwalletname,
                   password,
                   5,
                   6,
                   extend_mixdepth=extendmd,
                   storepassword=storepwd)
        return
    from string import ascii_letters
    for i in range(
            pwdnumtries):  #multiple tries to ensure pkcs7 error is triggered
        with pytest.raises(WalletError) as e_info:
            wrongpwd = "".join([random.choice(ascii_letters) for _ in range(20)
                               ])
            SegwitWallet(testwalletname,
                   wrongpwd,
                   5,
                   6,
                   extend_mixdepth=extendmd,
                   storepassword=storepwd)

    with pytest.raises(WalletError) as e_info:
        SegwitWallet(testwalletname,
               None,
               5,
               6,
               extend_mixdepth=extendmd,
               storepassword=storepwd)
    newwallet = SegwitWallet(testwalletname,
                       password,
                       5,
                       6,
                       extend_mixdepth=extendmd,
                       storepassword=storepwd)
    assert newwallet.seed == wallet.wallet_data_to_seed(seed)
    #now we have a functional wallet + file, update the cache; first try
    #with failed paths
    oldpath = newwallet.path
    newwallet.path = None
    newwallet.update_cache_index()
    newwallet.path = "fake-path-definitely-doesnt-exist"
    newwallet.update_cache_index()
    #with real path
    newwallet.path = oldpath
    newwallet.index = [[1, 1]] * 5
    newwallet.update_cache_index()

    #ensure we cannot find a mainnet wallet from seed
    seed = "goodbye"
    jm_single().config.set("BLOCKCHAIN", "network", "mainnet")
    with pytest.raises(IOError) as e_info:
        Wallet(seed, 5, 6, False, False)
    load_program_config()
Example #10
0
def main():
    tumble_log = get_tumble_log(logsdir)
    (options, args) = get_tumbler_parser().parse_args()
    options = vars(options)
    if len(args) < 1:
        parser.error('Needs a wallet file')
        sys.exit(0)
    load_program_config()
    #Load the wallet
    wallet_name = args[0]
    max_mix_depth = options['mixdepthsrc'] + options['mixdepthcount']
    if not os.path.exists(os.path.join('wallets', wallet_name)):
        wallet = Wallet(wallet_name, None, max_mix_depth)
    else:
        while True:
            try:
                pwd = get_password("Enter wallet decryption passphrase: ")
                wallet = Wallet(wallet_name, pwd, max_mix_depth)
            except WalletError:
                print("Wrong password, try again.")
                continue
            except Exception as e:
                print("Failed to load wallet, error message: " + repr(e))
                sys.exit(0)
            break
    sync_wallet(wallet, fast=options['fastsync'])

    #Parse options and generate schedule
    #Output information to log files
    jm_single().mincjamount = options['mincjamount']
    destaddrs = args[1:]
    print(destaddrs)
    #If the --restart flag is set we read the schedule
    #from the file, and filter out entries that are
    #already complete
    if options['restart']:
        res, schedule = get_schedule(os.path.join(logsdir,
                                                  options['schedulefile']))
        if not res:
            print("Failed to load schedule, name: " + str(
                options['schedulefile']))
            print("Error was: " + str(schedule))
            sys.exit(0)
        #This removes all entries that are marked as done
        schedule = [s for s in schedule if s[5] != 1]
        if isinstance(schedule[0][5], str) and len(schedule[0][5]) == 64:
            #ensure last transaction is confirmed before restart
            tumble_log.info("WAITING TO RESTART...")
            txid = schedule[0][5]
            restart_waiter(txid + ":0") #add 0 index because all have it
            #remove the already-done entry (this connects to the other TODO,
            #probably better *not* to truncate the done-already txs from file,
            #but simplest for now.
            schedule = schedule[1:]
        elif schedule[0][5] != 0:
            print("Error: first schedule entry is invalid.")
            sys.exit(0)
        with open(os.path.join(logsdir, options['schedulefile']), "wb") as f:
                    f.write(schedule_to_text(schedule))
        tumble_log.info("TUMBLE RESTARTING")
    else:
        #Create a new schedule from scratch
        schedule = get_tumble_schedule(options, destaddrs)
        tumble_log.info("TUMBLE STARTING")
        with open(os.path.join(logsdir, options['schedulefile']), "wb") as f:
            f.write(schedule_to_text(schedule))
        print("Schedule written to logs/" + options['schedulefile'])
    tumble_log.info("With this schedule: ")
    tumble_log.info(pprint.pformat(schedule))

    print("Progress logging to logs/TUMBLE.log")

    def filter_orders_callback(orders_fees, cjamount):
        """Decide whether to accept fees
        """
        return tumbler_filter_orders_callback(orders_fees, cjamount, taker, options)

    def taker_finished(res, fromtx=False, waittime=0.0, txdetails=None):
        """on_finished_callback for tumbler; processing is almost entirely
        deferred to generic taker_finished in tumbler_support module, except
        here reactor signalling.
        """
        sfile = os.path.join(logsdir, options['schedulefile'])
        tumbler_taker_finished_update(taker, sfile, tumble_log, options,
                                      res, fromtx, waittime, txdetails)
        if not fromtx:
            reactor.stop()
        elif fromtx != "unconfirmed":
            reactor.callLater(waittime*60, clientfactory.getClient().clientStart)

    #to allow testing of confirm/unconfirm callback for multiple txs
    if isinstance(jm_single().bc_interface, RegtestBitcoinCoreInterface):
        jm_single().bc_interface.tick_forward_chain_interval = 10
        jm_single().maker_timeout_sec = 5

    #instantiate Taker with given schedule and run
    taker = Taker(wallet,
                  schedule,
                  order_chooser=weighted_order_choose,
                  callbacks=(filter_orders_callback, None, taker_finished),
                  tdestaddrs=destaddrs)
    clientfactory = JMTakerClientProtocolFactory(taker)
    nodaemon = jm_single().config.getint("DAEMON", "no_daemon")
    daemon = True if nodaemon == 1 else False
    start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
                  jm_single().config.getint("DAEMON", "daemon_port"),
                  clientfactory, daemon=daemon)
Example #11
0
def main_cs(test_data=None):
    #twisted logging (TODO disable for non-debug runs)
    if test_data:
        wallet_name, args, options, use_ssl, alt_class, alt_c_class, fail_alice_state, fail_carol_state = test_data
        server, port, usessl = parse_server_string(options.serverport)
    else:
        parser = get_coinswap_parser()
        (options, args) = parser.parse_args()
        #Will only be used by client
        server, port, usessl = parse_server_string(options.serverport)
        if options.checkonly:
            #no need for any more data; just query
            alice_client = CoinSwapJSONRPCClient(server[2:], port, usessl=usessl)
            reactor.callWhenRunning(alice_client.send_poll_unsigned,
                                    "status", print_status)
            reactor.run()
            return
        log.startLogging(sys.stdout)
        load_coinswap_config()
        wallet_name = args[0]
    #depth 0: spend in, depth 1: receive out, depth 2: for backout transactions.
    max_mix_depth = 3
    wallet_dir = os.path.join(cs_single().homedir, 'wallets')
    if not os.path.exists(os.path.join(wallet_dir, wallet_name)):
        wallet = Wallet(wallet_name, None, max_mix_depth, 6,
                        wallet_dir=wallet_dir)
    else:
        while True:
            try:
                pwd = get_password("Enter wallet decryption passphrase: ")
                wallet = Wallet(wallet_name, pwd, max_mix_depth, 6,
                                wallet_dir=wallet_dir)
            except WalletError:
                print("Wrong password, try again.")
                continue
            except Exception as e:
                print("Failed to load wallet, error message: " + repr(e))
                sys.exit(0)
            break
    #for testing main script (not test framework), need funds.
    if not test_data and isinstance(
        cs_single().bc_interface, RegtestBitcoinCoreInterface):
        cs_single().bc_interface.grab_coins(wallet.get_new_addr(0, 0, True), 2.0)
        wallet.index[0][0] -= 1
        time.sleep(3)
    sync_wallet(wallet, fast=options.fastsync)
    if test_data:
        cs_single().bc_interface.wallet_synced = True
    wallet.used_coins = None
    if options.serve:
        #sanity check that client params were not provided:
        if len(args) > 1:
            print("Extra parameters provided for running as server. "
                  "Are you sure you didn't want to run as client?")
            sys.exit(0)
        if not test_data:
            main_server(options, wallet)
        else:
            main_server(options, wallet, {'use_ssl': use_ssl,
                                          'alt_c_class': alt_c_class,
                                          'fail_carol_state': fail_carol_state})
            return wallet.get_balance_by_mixdepth()
        return
    if not options.recover:
        target_amount = int(args[1])
        #Reset the targetting for backout transactions
        #TODO must be removed/changed for updated fees handling
        oldtarget = cs_single().config.get("POLICY", "tx_fees")
        newtarget = cs_single().config.getint("POLICY", "backout_fee_target")
        multiplier = float(cs_single().config.get("POLICY", "backout_fee_multiplier"))
        cs_single().config.set("POLICY", "tx_fees", str(newtarget))
        tx23fee = estimate_tx_fee((1, 2, 2), 1, txtype='p2shMofN')
        tx23fee = int(multiplier * tx23fee)
        tx24_recipient_amount = target_amount - tx23fee
        tx35_recipient_amount = target_amount - tx23fee
        cs_single().config.set("POLICY", "tx_fees", oldtarget)
    #to allow testing of confirm/unconfirm callback for multiple txs
    if isinstance(cs_single().bc_interface, RegtestBitcoinCoreInterface):
        cs_single().bc_interface.tick_forward_chain_interval = 2
        cs_single().bc_interface.simulating = True
        cs_single().config.set("BLOCKCHAIN", "notify_port", "62652")
        cs_single().config.set("BLOCKCHAIN", "rpc_host", "127.0.0.2")
    
    #if restart option selected, read state and backout
    if options.recover:
        session_id = options.recover
        alice = CoinSwapAlice(wallet, 'alicestate')
        alice.bbmb = wallet.get_balance_by_mixdepth(verbose=False)
        alice.load(sessionid=session_id)
        alice.backout("Recovering from shutdown")
        reactor.run()
        return
    if len(args) > 2:
        tx5address = args[2]
        if not validate_address(tx5address):
            print("Invalid address: ", tx5address)
            sys.exit(0)
    else:
        #Our destination address should be in a separate mixdepth
        tx5address = wallet.get_new_addr(1, 1, True)
    #instantiate the parameters, but don't yet have the ephemeral pubkeys
    #or destination addresses.
    #TODO figure out best estimate incl. priority
    btcfee_est = estimate_tx_fee((1, 2, 2), 1, txtype='p2shMofN')
    cpp = CoinSwapPublicParameters(base_amount=target_amount, bitcoin_fee=btcfee_est)
    cpp.set_addr_data(addr5=tx5address)
    testing_mode = True if test_data else False
    aliceclass = alt_class if test_data and alt_class else CoinSwapAlice
    if test_data and fail_alice_state:
        alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode,
                           fail_state=fail_alice_state)
    else:
        alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode)

    alice_client = CoinSwapJSONRPCClient(server[2:], port,
                                         alice.sm.tick, alice.backout, usessl)
    alice.set_jsonrpc_client(alice_client)
    reactor.callWhenRunning(alice_client.send_poll_unsigned, "status",
                            alice.check_server_status)
    if not test_data:
        reactor.run()
    if test_data:
        return alice
Example #12
0
noscan_methods = ['showseed', 'importprivkey', 'dumpprivkey']

if len(args) < 1:
    parser.error('Needs a wallet file or method')
    sys.exit(0)

load_coinswap_config()

if args[0] in noseed_methods:
    method = args[0]
else:
    seed = args[0]
    method = ('display' if len(args) == 1 else args[1].lower())
    if not os.path.exists(os.path.join('wallets', seed)):
        wallet = Wallet(seed, None, options.maxmixdepth,
                        options.gaplimit, extend_mixdepth= not maxmixdepth_configured,
                        storepassword=(method == 'importprivkey'))
    else:
        while True:
            try:
                pwd = get_password("Enter wallet decryption passphrase: ")
                wallet = Wallet(seed, pwd,
                        options.maxmixdepth,
                        options.gaplimit,
                        extend_mixdepth=not maxmixdepth_configured,
                        storepassword=(method == 'importprivkey'))
            except WalletError:
                print("Wrong password, try again.")
                continue
            except Exception as e:
                print("Failed to load wallet, error message: " + repr(e))