예제 #1
0
def main():
    import bitcoin as btc
    import common
    import binascii, os
    from optparse import OptionParser

    common.nickname = random_nick()  #watcher' +binascii.hexlify(os.urandom(4))
    common.load_program_config()

    parser = OptionParser(
        usage='usage: %prog [options]',
        description='Runs a webservice which shows the orderbook.')
    parser.add_option('-H',
                      '--host',
                      action='store',
                      type='string',
                      dest='host',
                      default='localhost',
                      help='hostname or IP to bind to, default=localhost')
    parser.add_option('-p',
                      '--port',
                      action='store',
                      type='int',
                      dest='port',
                      help='port to listen on, default=62601',
                      default=62601)
    (options, args) = parser.parse_args()

    hostport = (options.host, options.port)

    irc = IRCMessageChannel(common.nickname)
    taker = GUITaker(irc, hostport)
    print('starting irc')

    irc.run()
예제 #2
0
def main():
	parser = OptionParser(usage='usage: %prog [options] [wallet file / fromaccount] [amount] [destaddr]',
		description='Sends a single payment from a given mixing depth of your ' +
			'wallet to an given address using coinjoin and then switches off. Also sends from bitcoinqt. ' +
			'Setting amount to zero will do a sweep, where the entire mix depth is emptied')
	parser.add_option('-f', '--txfee', action='store', type='int', dest='txfee',
		default=10000, help='miner fee contribution, in satoshis, default=10000')
	parser.add_option('-w', '--wait-time', action='store', type='float', dest='waittime',
		help='wait time in seconds to allow orders to arrive, default=5', default=5)
	parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount',
		help='how many makers to coinjoin with, default=2', default=2)
	parser.add_option('-C','--choose-cheapest', action='store_true', dest='choosecheapest', default=False,
		help='override weightened offers picking and choose cheapest')
	parser.add_option('-P','--pick-orders', action='store_true', dest='pickorders', default=False,
		help='manually pick which orders to take. doesn\'t work while sweeping.')
	parser.add_option('-m', '--mixdepth', action='store', type='int', dest='mixdepth',
		help='mixing depth to spend from, default=0', default=0)
	parser.add_option('--yes', action='store_true', dest='answeryes', default=False,
		help='answer yes to everything')
	parser.add_option('--rpcwallet', action='store_true', dest='userpcwallet', default=False,
		help='Use the Bitcoin Core wallet through json rpc, instead of the internal joinmarket ' +
			'wallet. Requires blockchain_source=json-rpc')
	(options, args) = parser.parse_args()

	if len(args) < 3:
		parser.error('Needs a wallet, amount and destination address')
		sys.exit(0)
	wallet_name = args[0]
	amount = int(args[1])
	destaddr = args[2]
	
	load_program_config()
	addr_valid, errormsg = validate_address(destaddr)
	if not addr_valid:
		print 'ERROR: Address invalid. ' + errormsg
		return
	
	common.nickname = random_nick()
	debug('starting sendpayment')

	if not options.userpcwallet:
		wallet = Wallet(wallet_name, options.mixdepth + 1)
	else:
		wallet = BitcoinCoreWallet(fromaccount = wallet_name)
	common.bc_interface.sync_wallet(wallet)

	irc = IRCMessageChannel(common.nickname)
	taker = SendPayment(irc, wallet, destaddr, amount, options.makercount, options.txfee,
		options.waittime, options.mixdepth, options.answeryes, options.choosecheapest, options.pickorders)
	try:
		debug('starting irc')
		irc.run()
	except:
		debug('CRASHING, DUMPING EVERYTHING')
		debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed'])
		debug_dump_object(taker)
		import traceback
		debug(traceback.format_exc())
예제 #3
0
def main():
	parser = OptionParser(usage='usage: %prog [options] [wallet file / fromaccount] [amount] [destaddr]',
		description='Sends a single payment from the zero mixing depth of your ' +
			'wallet to an given address using coinjoin and then switches off. ' +
			'Setting amount to zero will do a sweep, where the entire mix depth is emptied')
	parser.add_option('-f', '--txfee', action='store', type='int', dest='txfee',
		default=10000, help='miner fee contribution, in satoshis, default=10000')
	parser.add_option('-w', '--wait-time', action='store', type='float', dest='waittime',
		help='wait time in seconds to allow orders to arrive, default=5', default=5)
	parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount',
		help='how many makers to coinjoin with, default=2', default=2)
	parser.add_option('-C','--choose-cheapest', action='store_true', dest='choosecheapest', default=False,
		help='override weightened offers picking and choose cheapest')
	parser.add_option('-P','--pick-orders', action='store_true', dest='pickorders', default=False,
		help='manually pick which orders to take. doesn\'t work while sweeping.')
	parser.add_option('-m', '--mixdepth', action='store', type='int', dest='mixdepth',
		help='mixing depth to spend from, default=0', default=0)
	parser.add_option('--yes', action='store_true', dest='answeryes', default=False,
		help='answer yes to everything')
	parser.add_option('--rpcwallet', action='store_true', dest='userpcwallet', default=False,
		help='Use the Bitcoin Core wallet through json rpc, instead of the internal joinmarket ' +
			'wallet. Requires blockchain_source=json-rpc')
	(options, args) = parser.parse_args()

	if len(args) < 3:
		parser.error('Needs a wallet, amount and destination address')
		sys.exit(0)
	wallet_name = args[0]
	amount = int(args[1])
	destaddr = args[2]
	
	load_program_config()
	addr_valid, errormsg = validate_address(destaddr)
	if not addr_valid:
		print 'ERROR: Address invalid. ' + errormsg
		return
	
	common.nickname = random_nick()
	debug('starting sendpayment')

	if not options.userpcwallet:
		wallet = Wallet(wallet_name, options.mixdepth + 1)
	else:
		wallet = BitcoinCoreWallet(fromaccount = wallet_name)
	common.bc_interface.sync_wallet(wallet)

	irc = IRCMessageChannel(common.nickname)
	taker = SendPayment(irc, wallet, destaddr, amount, options.makercount, options.txfee,
		options.waittime, options.mixdepth, options.answeryes, options.choosecheapest, options.pickorders)
	try:
		debug('starting irc')
		irc.run()
	except:
		debug('CRASHING, DUMPING EVERYTHING')
		debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed'])
		debug_dump_object(taker)
		import traceback
		debug(traceback.format_exc())
예제 #4
0
def main():
	parser = OptionParser(usage='usage: %prog [options] [wallet file / seed] [amount] [destaddr]',
		description='Sends a payment from your wallet to an given address' +
			' using coinjoin. First acts as a maker, announcing an order and ' +
			'waiting for someone to fill it. After a set period of time, gives' +
			' up waiting and acts as a taker and coinjoins any remaining coins')
	parser.add_option('-f', '--txfee', action='store', type='int', dest='txfee',
		default=10000, help='miner fee contribution, in satoshis, default=10000')
	parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount',
		help='how many makers to coinjoin with when taking liquidity, default=2', default=2)
	parser.add_option('-w', '--wait-time', action='store', type='float', dest='waittime',
		help='wait time in hours as a maker before becoming a taker, default=8', default=8)
	parser.add_option('-c', '--cjfee', action='store', type='int', dest='cjfee',
		help='coinjoin fee asked for when being a maker, in satoshis per order filled, default=50000', default=50000)
	parser.add_option('-m', '--mixdepth', action='store', type='int', dest='mixdepth',
		help='mixing depth to spend from, default=0', default=0)
	(options, args) = parser.parse_args()

	if len(args) < 3:
		parser.error('Needs a seed, amount and destination address')
		sys.exit(0)
	seed = args[0]
	amount = int(args[1])
	destaddr = args[2]
	
	load_program_config()
	addr_valid, errormsg = validate_address(destaddr)
	if not addr_valid:
		print 'ERROR: Address invalid. ' + errormsg
		return
	
	waittime = timedelta(hours=options.waittime).total_seconds()
	print 'txfee=%d cjfee=%d waittime=%s makercount=%d' % (options.txfee, options.cjfee,
		str(timedelta(hours=options.waittime)), options.makercount)

	wallet = Wallet(seed, options.mixdepth + 1)
	common.bc_interface.sync_wallet(wallet)

	available_balance = wallet.get_balance_by_mixdepth()[options.mixdepth]
	if available_balance < amount:
		print 'not enough money at mixdepth=%d, exiting' % (options.mixdepth)
		return

	common.nickname = random_nick()
	debug('Running patient sender of a payment')

	irc = IRCMessageChannel(common.nickname)
	bot = PatientSendPayment(irc, wallet, destaddr, amount, options.makercount,
		options.txfee, options.cjfee, waittime, options.mixdepth)
	try:
		irc.run()
	except:
		debug('CRASHING, DUMPING EVERYTHING')
		debug_dump_object(wallet, ['addr_cache', 'keys', 'seed'])
		debug_dump_object(taker)
		import traceback
		traceback.print_exc()
예제 #5
0
def main():
	import bitcoin as btc
	import common
	import binascii, os
	common.nickname =random_nick() #watcher' +binascii.hexlify(os.urandom(4))
	common.load_program_config()

	irc = IRCMessageChannel(common.nickname)
	taker = GUITaker(irc)
	print 'starting irc'
	irc.run()
예제 #6
0
def main():
    import bitcoin as btc
    import common
    import binascii, os
    common.nickname = random_nick()  #watcher' +binascii.hexlify(os.urandom(4))
    common.load_program_config()

    irc = IRCMessageChannel(common.nickname)
    taker = GUITaker(irc)
    print 'starting irc'
    irc.run()
예제 #7
0
def main():
	parser = OptionParser(usage='usage: %prog [options] [wallet file / seed] [amount] [destaddr]',
		description='Sends a single payment from the zero mixing depth of your ' +
			'wallet to an given address using coinjoin and then switches off. ' +
			'Setting amount to zero will do a sweep, where the entire mix depth is emptied')
	parser.add_option('-f', '--txfee', action='store', type='int', dest='txfee',
		default=10000, help='miner fee contribution, in satoshis, default=10000')
	parser.add_option('-w', '--wait-time', action='store', type='float', dest='waittime',
		help='wait time in seconds to allow orders to arrive, default=5', default=5)
	parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount',
		help='how many makers to coinjoin with, default=2', default=2)
	parser.add_option('-m', '--mixdepth', action='store', type='int', dest='mixdepth',
		help='mixing depth to spend from, default=0', default=0)
	parser.add_option('--yes', action='store_true', dest='answeryes', default=False,
		help='answer yes to everything')
	(options, args) = parser.parse_args()

	if len(args) < 3:
		parser.error('Needs a seed, amount and destination address')
		sys.exit(0)
	seed = args[0]
	amount = int(args[1])
	destaddr = args[2]
	
	load_program_config()
	addr_valid, errormsg = validate_address(destaddr)
	if not addr_valid:
		print 'ERROR: Address invalid. ' + errormsg
		return
	
	common.nickname = random_nick()
	debug('starting sendpayment')
	import binascii, os

	wallet = Wallet(seed, options.mixdepth + 1)
	common.bc_interface.sync_wallet(wallet)
	wallet.print_debug_wallet_info()

	irc = IRCMessageChannel(common.nickname)
	taker = SendPayment(irc, wallet, destaddr, amount, options.makercount, options.txfee,
		options.waittime, options.mixdepth, options.answeryes)
	try:
		debug('starting irc')
		irc.run()
	except:
		debug('CRASHING, DUMPING EVERYTHING')
		debug('wallet seed = ' + seed)
		debug_dump_object(wallet, ['addr_cache', 'keys', 'seed'])
		debug_dump_object(taker)
		import traceback
		debug(traceback.format_exc())
예제 #8
0
def main():
	import bitcoin as btc
	import common
	import binascii, os
	from optparse import OptionParser

	common.nickname =random_nick() #watcher' +binascii.hexlify(os.urandom(4))
	common.load_program_config()

	parser = OptionParser(usage='usage: %prog [options]',
		description='Runs a webservice which shows the orderbook.')
	parser.add_option('-H', '--host', action='store', type='string', dest='host',
		default='localhost', help='hostname or IP to bind to, default=localhost')
	parser.add_option('-p', '--port', action='store', type='int', dest='port',
		help='port to listen on, default=62601', default=62601)
	(options, args) = parser.parse_args()

	hostport = (options.host, options.port)

	irc = IRCMessageChannel(common.nickname)
	taker = GUITaker(irc, hostport)
	print('starting irc')

	irc.run()
예제 #9
0
def main():
	parser = OptionParser(usage='usage: %prog [options] [wallet file / seed] [destaddr...]',
		description='Sends bitcoins to many different addresses using coinjoin in'
			' an attempt to break the link between them. Sending to multiple '
			' addresses is highly recommended for privacy. This tumbler can'
			' be configured to ask for more address mid-run, giving the user'
			' a chance to click `Generate New Deposit Address` on whatever service'
			' they are using.')
	parser.add_option('-m', '--mixdepthsource', type='int', dest='mixdepthsrc',
		help='mixing depth to spend from, default=0', default=0)
	parser.add_option('-f', '--txfee', type='int', dest='txfee',
		default=10000, help='miner fee contribution, in satoshis, default=10000')
	parser.add_option('-x', '--maxcjfee', type='float', dest='maxcjfee',
		default=0.03, help='maximum coinjoin fee the tumbler is willing to pay to a single market maker. default=0.03 (3%)')
	parser.add_option('-a', '--addrask', type='int', dest='addrask',
		default=2, help='How many more addresses to ask for in the terminal. Should '
			'be similar to --txcountparams. default=2')
	parser.add_option('-N', '--makercountrange', type='float', nargs=2, action='store',
		dest='makercountrange',
		help='Input the range of makers to use. e.g. 3-5 will random use between '
		'3 and 5 makers inclusive, default=3 4', default=(3, 1))
	parser.add_option('-M', '--mixdepthcount', type='int', dest='mixdepthcount',
		help='How many mixing depths to mix through', default=4)
	parser.add_option('-c', '--txcountparams', type='float', nargs=2, dest='txcountparams', default=(5, 1),
		help='The number of transactions to take coins from one mixing depth to the next, it is'
		' randomly chosen following a normal distribution. Should be similar to --addrask. '
		'This option controlls the parameters of that normal curve. (mean, standard deviation). default=(3, 1)')
	parser.add_option('--amountpower', type='float', dest='amountpower', default=100.0,
		help='The output amounts follow a power law distribution, this is the power, default=100.0')
	parser.add_option('-l', '--timelambda', type='float', dest='timelambda', default=20,
		help='Average the number of minutes to wait between transactions. Randomly chosen '
		' following an exponential distribution, which describes the time between uncorrelated'
		' events. default=20')
	parser.add_option('-w', '--wait-time', action='store', type='float', dest='waittime',
		help='wait time in seconds to allow orders to arrive, default=5', default=5)
	parser.add_option('-s', '--mincjamount', type='float', dest='mincjamount', default=0.0001,
		help='minimum coinjoin amount in transaction')
	(options, args) = parser.parse_args()
	#TODO somehow implement a lower limit

	if len(args) < 1:
		parser.error('Needs a seed')
		sys.exit(0)
	seed = args[0]
	destaddrs = args[1:]
	
	common.load_program_config()
	for addr in destaddrs:
		addr_valid, errormsg = validate_address(addr)
		if not addr_valid:
			print 'ERROR: Address ' + addr + ' invalid. ' + errormsg
			return
	
	if len(destaddrs) + options.addrask <= 1:
		print '='*50
		print 'WARNING: You are only using one destination address'
		print 'this is very bad for privacy'
		print '='*50

	print str(options)
	tx_list = generate_tumbler_tx(destaddrs, options)
	if not tx_list:
		return

	tx_list2 = copy.deepcopy(tx_list)
	tx_dict = {}
	for tx in tx_list2:
		srcmixdepth = tx['srcmixdepth']
		tx.pop('srcmixdepth')
		if srcmixdepth not in tx_dict:
			tx_dict[srcmixdepth] = []
		tx_dict[srcmixdepth].append(tx)
	dbg_tx_list = []
	for srcmixdepth, txlist in tx_dict.iteritems():
		dbg_tx_list.append({'srcmixdepth': srcmixdepth, 'tx': txlist})
	print 'tumbler transaction list'
	pprint(dbg_tx_list)

	total_wait = sum([tx['wait'] for tx in tx_list])
	print 'waits in total for ' + str(len(tx_list)) + ' blocks and ' + str(total_wait) + ' minutes'
	total_block_and_wait = len(tx_list)*10 + total_wait
	print('estimated time taken ' + str(total_block_and_wait) +
		' minutes or ' + str(round(total_block_and_wait/60.0, 2)) + ' hours')

	ret = raw_input('tumble with these tx? (y/n):')
	if ret[0] != 'y':
		return

	#a couple of modes
	#im-running-from-the-nsa, takes about 80 hours, costs a lot
	#python tumbler.py -a 10 -N 10 5 -c 10 5 -l 50 -M 10 seed 1xxx
	#
	#quick and cheap, takes about 90 minutes
	#python tumbler.py -N 2 1 -c 3 0.001 -l 10 -M 3 -a 1 seed 1xxx
	#
	#default, good enough for most, takes about 5 hours
	#python tumbler.py seed 1xxx
	#
	#for quick testing
	#python tumbler.py -N 2 1 -c 3 0.001 -l 0.1 -M 3 -a 0 seed 1xxx 1yyy
	wallet = Wallet(seed, max_mix_depth = options.mixdepthsrc + options.mixdepthcount)
	common.bc_interface.sync_wallet(wallet)

	common.nickname = random_nick()
	debug('starting tumbler')
	irc = IRCMessageChannel(common.nickname)
	tumbler = Tumbler(irc, wallet, tx_list, options.txfee, options.maxcjfee, options.mincjamount)
	try:
		debug('connecting to irc')
		irc.run()
	except:
		debug('CRASHING, DUMPING EVERYTHING')
		debug_dump_object(wallet, ['addr_cache', 'keys', 'seed'])
		debug_dump_object(tumbler)
		import traceback
		debug(traceback.format_exc())
예제 #10
0
import time, os, binascii, sys, datetime
import pprint
data_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, os.path.join(data_dir, 'lib'))

from maker import *
from irc import IRCMessageChannel, random_nick
import bitcoin as btc
import common, blockchaininterface

from socket import gethostname

txfee = 1000
cjfee = '0.002' # 0.2% fee
nickname = random_nick()
nickserv_password = ''
minsize = int(1.2 * txfee / float(cjfee)) #minimum size is such that you always net profit at least 20% of the miner fee
mix_levels = 5



#is a maker for the purposes of generating a yield from held
# bitcoins without ruining privacy for the taker, the taker could easily check
# the history of the utxos this bot sends, so theres not much incentive
# to ruin the privacy for barely any more yield
#sell-side algorithm:
#add up the value of each utxo for each mixing depth,
# announce a relative-fee order of the highest balance
#spent from utxos that try to make the highest balance even higher
# so try to keep coins concentrated in one mixing depth
예제 #11
0
def main():
	parser = OptionParser(usage='usage: %prog [options] [wallet file] [destaddr(s)...]',
		description='Sends bitcoins to many different addresses using coinjoin in'
			' an attempt to break the link between them. Sending to multiple '
			' addresses is highly recommended for privacy. This tumbler can'
			' be configured to ask for more address mid-run, giving the user'
			' a chance to click `Generate New Deposit Address` on whatever service'
			' they are using.')
	parser.add_option('-m', '--mixdepthsource', type='int', dest='mixdepthsrc',
		help='mixing depth to spend from, default=0', default=0)
	parser.add_option('-f', '--txfee', type='int', dest='txfee',
		default=10000, help='miner fee contribution, in satoshis, default=10000')
	parser.add_option('-x', '--maxcjfee', type='float', dest='maxcjfee', nargs=2,
		default=(0.01, 10000), help='maximum coinjoin fee and bitcoin value the tumbler is '
		'willing to pay to a single market maker. Both values need to be exceeded, so if '
		'the fee is 30% but only 500satoshi is paid the tx will go ahead. default=0.01, 10000 (1%, 10000satoshi)')
	parser.add_option('-a', '--addrcount', type='int', dest='addrcount',
		default=3, help='How many destination addresses in total should be used. If not enough are given'
			' as command line arguments, the script will ask for more, default=3')
	parser.add_option('-N', '--makercountrange', type='float', nargs=2, action='store',
		dest='makercountrange',
		help='Input the mean and spread of number of makers to use. e.g. 3 1.5 will be a normal distribution '
		'with mean 3 and standard deveation 1.5 inclusive, default=3 1.5', default=(3, 1.5))
	parser.add_option('-M', '--mixdepthcount', type='int', dest='mixdepthcount',
		help='How many mixing depths to mix through', default=4)
	parser.add_option('-c', '--txcountparams', type='float', nargs=2, dest='txcountparams', default=(4, 1),
		help='The number of transactions to take coins from one mixing depth to the next, it is'
		' randomly chosen following a normal distribution. Should be similar to --addrask. '
		'This option controlls the parameters of that normal curve. (mean, standard deviation). default=(4, 1)')
	parser.add_option('--amountpower', type='float', dest='amountpower', default=100.0,
		help='The output amounts follow a power law distribution, this is the power, default=100.0')
	parser.add_option('-l', '--timelambda', type='float', dest='timelambda', default=20,
		help='Average the number of minutes to wait between transactions. Randomly chosen '
		' following an exponential distribution, which describes the time between uncorrelated'
		' events. default=20')
	parser.add_option('-w', '--wait-time', action='store', type='float', dest='waittime',
		help='wait time in seconds to allow orders to arrive, default=5', default=5)
	parser.add_option('-s', '--mincjamount', type='int', dest='mincjamount', default=100000,
		help='minimum coinjoin amount in transaction in satoshi, default 100k')
	(options, args) = parser.parse_args()
	#TODO somehow implement a lower limit

	if len(args) < 1:
		parser.error('Needs a wallet file')
		sys.exit(0)
	wallet_file = args[0]
	destaddrs = args[1:]
	
	common.load_program_config()
	for addr in destaddrs:
		addr_valid, errormsg = validate_address(addr)
		if not addr_valid:
			print 'ERROR: Address ' + addr + ' invalid. ' + errormsg
			return

	if len(destaddrs) > options.addrcount:
		options.addrcount = len(destaddrs)
	if options.addrcount+1 > options.mixdepthcount:
		print 'not enough mixing depths to pay to all destination addresses, increasing mixdepthcount'
		options.mixdepthcount = options.addrcount+1
	if options.addrcount <= 1:
		print '='*50
		print 'WARNING: You are only using one destination address'
		print 'this is very bad for privacy'
		print '='*50

	print str(options)
	tx_list = generate_tumbler_tx(destaddrs, options)
	if not tx_list:
		return

	tx_list2 = copy.deepcopy(tx_list)
	tx_dict = {}
	for tx in tx_list2:
		srcmixdepth = tx['srcmixdepth']
		tx.pop('srcmixdepth')
		if srcmixdepth not in tx_dict:
			tx_dict[srcmixdepth] = []
		tx_dict[srcmixdepth].append(tx)
	dbg_tx_list = []
	for srcmixdepth, txlist in tx_dict.iteritems():
		dbg_tx_list.append({'srcmixdepth': srcmixdepth, 'tx': txlist})
	debug('tumbler transaction list')
	pprint(dbg_tx_list)

	total_wait = sum([tx['wait'] for tx in tx_list])
	print 'creates ' + str(len(tx_list)) + ' transactions in total'
	print 'waits in total for ' + str(len(tx_list)) + ' blocks and ' + str(total_wait) + ' minutes'
	total_block_and_wait = len(tx_list)*10 + total_wait
	print('estimated time taken ' + str(total_block_and_wait) +
		' minutes or ' + str(round(total_block_and_wait/60.0, 2)) + ' hours')

	ret = raw_input('tumble with these tx? (y/n):')
	if ret[0] != 'y':
		return

	#NOTE: possibly out of date documentation
	#a couple of modes
	#im-running-from-the-nsa, takes about 80 hours, costs a lot
	#python tumbler.py -a 10 -N 10 5 -c 10 5 -l 50 -M 10 wallet_file 1xxx
	#
	#quick and cheap, takes about 90 minutes
	#python tumbler.py -N 2 1 -c 3 0.001 -l 10 -M 3 -a 1 wallet_file 1xxx
	#
	#default, good enough for most, takes about 5 hours
	#python tumbler.py wallet_file 1xxx
	#
	#for quick testing
	#python tumbler.py -N 2 1 -c 3 0.001 -l 0.1 -M 3 -a 0 wallet_file 1xxx 1yyy
	wallet = Wallet(wallet_file, max_mix_depth = options.mixdepthsrc + options.mixdepthcount)
	common.bc_interface.sync_wallet(wallet)

        #check if there are actually any coins at the mixdepthsrc level
        #if not, allow user to start at minimum used level
        used_depths = [k for k,v in  wallet.get_utxos_by_mixdepth().iteritems() if v != {}]
        if options.mixdepthsrc not in used_depths:
                ret = raw_input("no coins in chosen src level. Use lowest possible level? (y/n):")
                if ret[0] !='y':
                        return
                options.mixdepthsrc = sorted(used_depths.keys())[0]
                print "starting with depth: " + str(options.mixdepthsrc)
                wallet = Wallet(wallet_file,max_mix_depth = options.mixdepthsrc + options.mixdepthcount)
                common.bc_interface.sync_wallet(wallet)
                
        

	common.nickname = random_nick()
	debug('starting tumbler')
	irc = IRCMessageChannel(common.nickname)
	tumbler = Tumbler(irc, wallet, tx_list, options.txfee, options.maxcjfee, options.mincjamount)
	try:
		debug('connecting to irc')
		irc.run()
	except:
		debug('CRASHING, DUMPING EVERYTHING')
		debug_dump_object(wallet, ['addr_cache', 'keys', 'seed'])
		debug_dump_object(tumbler)
		debug_dump_object(tumbler.cjtx)
		import traceback
		debug(traceback.format_exc())
예제 #12
0
def main():
    parser = OptionParser(
        usage=
        'usage: %prog [options] [auth utxo] [cjamount] [cjaddr] [changeaddr] [utxos..]',
        description=
        'Creates an unsigned coinjoin transaction. Outputs a partially signed transaction '
        +
        'hex string. The user must sign their inputs independently and broadcast them. The JoinMarket'
        +
        ' protocol requires the taker to have a single p2pk UTXO input to use to authenticate the '
        +
        ' encrypted messages. For this reason you must pass auth utxo and the corresponding private key'
    )
    #for cjamount=0 do a sweep, and ignore change address
    parser.add_option(
        '-f',
        '--txfee',
        action='store',
        type='int',
        dest='txfee',
        default=10000,
        help='miner fee contribution, in satoshis, default=10000')
    parser.add_option(
        '-w',
        '--wait-time',
        action='store',
        type='float',
        dest='waittime',
        help='wait time in seconds to allow orders to arrive, default=5',
        default=5)
    parser.add_option('-N',
                      '--makercount',
                      action='store',
                      type='int',
                      dest='makercount',
                      help='how many makers to coinjoin with, default=2',
                      default=2)
    parser.add_option(
        '-C',
        '--choose-cheapest',
        action='store_true',
        dest='choosecheapest',
        default=False,
        help='override weightened offers picking and choose cheapest')
    parser.add_option(
        '-P',
        '--pick-orders',
        action='store_true',
        dest='pickorders',
        default=False,
        help='manually pick which orders to take. doesn\'t work while sweeping.'
    )
    parser.add_option('--yes',
                      action='store_true',
                      dest='answeryes',
                      default=False,
                      help='answer yes to everything')
    #TODO implement
    #parser.add_option('-n', '--no-network', action='store_true', dest='nonetwork', default=False,
    #	help='dont query the blockchain interface, instead user must supply value of UTXOs on ' +
    #		' command line in the format txid:output/value-in-satoshi')
    (options, args) = parser.parse_args()

    if len(args) < 3:
        parser.error('Needs a wallet, amount and destination address')
        sys.exit(0)
    auth_utxo = args[0]
    cjamount = int(args[1])
    destaddr = args[2]
    changeaddr = args[3]
    cold_utxos = args[4:]

    common.load_program_config()
    addr_valid1, errormsg1 = validate_address(destaddr)
    #if amount = 0 dont bother checking changeaddr so user can write any junk
    if cjamount != 0:
        addr_valid2, errormsg2 = validate_address(changeaddr)
    else:
        addr_valid2 = True
    if not addr_valid1 or not addr_valid2:
        if not addr_valid1:
            print 'ERROR: Address invalid. ' + errormsg1
        else:
            print 'ERROR: Address invalid. ' + errormsg2
        return

    all_utxos = [auth_utxo] + cold_utxos
    query_result = common.bc_interface.query_utxo_set(all_utxos)
    if None in query_result:
        print query_result
    utxo_data = {}
    for utxo, data in zip(all_utxos, query_result):
        utxo_data[utxo] = {'address': data['address'], 'value': data['value']}
    auth_privkey = raw_input('input private key for ' +
                             utxo_data[auth_utxo]['address'] + ' :')
    if utxo_data[auth_utxo]['address'] != btc.privtoaddr(
            auth_privkey, common.get_p2pk_vbyte()):
        print 'ERROR: privkey does not match auth utxo'
        return

    chooseOrdersFunc = None
    if options.pickorders and amount != 0:  #cant use for sweeping
        chooseOrdersFunc = pick_order
    elif options.choosecheapest:
        chooseOrdersFunc = cheapest_order_choose
    else:  #choose randomly (weighted)
        chooseOrdersFunc = weighted_order_choose

    common.nickname = random_nick()
    debug('starting sendpayment')

    class UnsignedTXWallet(common.AbstractWallet):
        def get_key_from_addr(self, addr):
            debug('getting privkey of ' + addr)
            if btc.privtoaddr(auth_privkey, common.get_p2pk_vbyte()) != addr:
                raise RuntimeError('privkey doesnt match given address')
            return auth_privkey

    wallet = UnsignedTXWallet()
    irc = IRCMessageChannel(common.nickname)
    taker = CreateUnsignedTx(irc, wallet, auth_utxo, cjamount, destaddr,
                             changeaddr, utxo_data, options, chooseOrdersFunc)
    try:
        debug('starting irc')
        irc.run()
    except:
        debug('CRASHING, DUMPING EVERYTHING')
        debug_dump_object(wallet,
                          ['addr_cache', 'keys', 'wallet_name', 'seed'])
        debug_dump_object(taker)
        import traceback
        debug(traceback.format_exc())
예제 #13
0
def main():
    parser = OptionParser(
        usage="usage: %prog [options] [wallet file / fromaccount] [amount] [destaddr]",
        description="Sends a single payment from a given mixing depth of your "
        + "wallet to an given address using coinjoin and then switches off. Also sends from bitcoinqt. "
        + "Setting amount to zero will do a sweep, where the entire mix depth is emptied",
    )
    parser.add_option(
        "-f",
        "--txfee",
        action="store",
        type="int",
        dest="txfee",
        default=30000,
        help="total miner fee in satoshis, default=30000",
    )
    parser.add_option(
        "-w",
        "--wait-time",
        action="store",
        type="float",
        dest="waittime",
        help="wait time in seconds to allow orders to arrive, default=5",
        default=5,
    )
    parser.add_option(
        "-N",
        "--makercount",
        action="store",
        type="int",
        dest="makercount",
        help="how many makers to coinjoin with, default=2",
        default=2,
    )
    parser.add_option(
        "-C",
        "--choose-cheapest",
        action="store_true",
        dest="choosecheapest",
        default=False,
        help="override weightened offers picking and choose cheapest",
    )
    parser.add_option(
        "-P",
        "--pick-orders",
        action="store_true",
        dest="pickorders",
        default=False,
        help="manually pick which orders to take. doesn't work while sweeping.",
    )
    parser.add_option(
        "-m",
        "--mixdepth",
        action="store",
        type="int",
        dest="mixdepth",
        help="mixing depth to spend from, default=0",
        default=0,
    )
    parser.add_option("--yes", action="store_true", dest="answeryes", default=False, help="answer yes to everything")
    parser.add_option(
        "--rpcwallet",
        action="store_true",
        dest="userpcwallet",
        default=False,
        help="Use the Bitcoin Core wallet through json rpc, instead of the internal joinmarket "
        + "wallet. Requires blockchain_source=json-rpc",
    )
    (options, args) = parser.parse_args()

    if len(args) < 3:
        parser.error("Needs a wallet, amount and destination address")
        sys.exit(0)
    wallet_name = args[0]
    amount = int(args[1])
    destaddr = args[2]

    load_program_config()
    addr_valid, errormsg = validate_address(destaddr)
    if not addr_valid:
        print "ERROR: Address invalid. " + errormsg
        return

    chooseOrdersFunc = None
    if options.pickorders and amount != 0:  # cant use for sweeping
        chooseOrdersFunc = pick_order
    elif options.choosecheapest:
        chooseOrdersFunc = cheapest_order_choose
    else:  # choose randomly (weighted)
        chooseOrdersFunc = weighted_order_choose

    common.nickname = random_nick()
    debug("starting sendpayment")

    if not options.userpcwallet:
        wallet = Wallet(wallet_name, options.mixdepth + 1)
    else:
        wallet = BitcoinCoreWallet(fromaccount=wallet_name)
    common.bc_interface.sync_wallet(wallet)

    irc = IRCMessageChannel(common.nickname)
    taker = SendPayment(
        irc,
        wallet,
        destaddr,
        amount,
        options.makercount,
        options.txfee,
        options.waittime,
        options.mixdepth,
        options.answeryes,
        chooseOrdersFunc,
    )
    try:
        debug("starting irc")
        irc.run()
    except:
        debug("CRASHING, DUMPING EVERYTHING")
        debug_dump_object(wallet, ["addr_cache", "keys", "wallet_name", "seed"])
        debug_dump_object(taker)
        import traceback

        debug(traceback.format_exc())
예제 #14
0
def main():
    parser = OptionParser(
        usage='usage: %prog [options] [wallet file] [destaddr(s)...]',
        description=
        'Sends bitcoins to many different addresses using coinjoin in'
        ' an attempt to break the link between them. Sending to multiple '
        ' addresses is highly recommended for privacy. This tumbler can'
        ' be configured to ask for more address mid-run, giving the user'
        ' a chance to click `Generate New Deposit Address` on whatever service'
        ' they are using.')
    parser.add_option('-m',
                      '--mixdepthsource',
                      type='int',
                      dest='mixdepthsrc',
                      help='mixing depth to spend from, default=0',
                      default=0)
    parser.add_option(
        '-f',
        '--txfee',
        type='int',
        dest='txfee',
        default=10000,
        help='miner fee contribution, in satoshis, default=10000')
    parser.add_option(
        '-x',
        '--maxcjfee',
        type='float',
        dest='maxcjfee',
        default=0.01,
        help=
        'maximum coinjoin fee the tumbler is willing to pay to a single market maker. default=0.01 (1%)'
    )
    parser.add_option(
        '-a',
        '--addrask',
        type='int',
        dest='addrask',
        default=2,
        help='How many more addresses to ask for in the terminal. Should '
        'be similar to --txcountparams. default=2')
    parser.add_option(
        '-N',
        '--makercountrange',
        type='float',
        nargs=2,
        action='store',
        dest='makercountrange',
        help=
        'Input the mean and spread of number of makers to use. e.g. 3 1.5 will be a normal distribution '
        'with mean 3 and standard deveation 1.5 inclusive, default=3 1.5',
        default=(3, 1.5))
    parser.add_option('-M',
                      '--mixdepthcount',
                      type='int',
                      dest='mixdepthcount',
                      help='How many mixing depths to mix through',
                      default=4)
    parser.add_option(
        '-c',
        '--txcountparams',
        type='float',
        nargs=2,
        dest='txcountparams',
        default=(5, 1),
        help=
        'The number of transactions to take coins from one mixing depth to the next, it is'
        ' randomly chosen following a normal distribution. Should be similar to --addrask. '
        'This option controlls the parameters of that normal curve. (mean, standard deviation). default=(3, 1)'
    )
    parser.add_option(
        '--amountpower',
        type='float',
        dest='amountpower',
        default=100.0,
        help=
        'The output amounts follow a power law distribution, this is the power, default=100.0'
    )
    parser.add_option(
        '-l',
        '--timelambda',
        type='float',
        dest='timelambda',
        default=20,
        help=
        'Average the number of minutes to wait between transactions. Randomly chosen '
        ' following an exponential distribution, which describes the time between uncorrelated'
        ' events. default=20')
    parser.add_option(
        '-w',
        '--wait-time',
        action='store',
        type='float',
        dest='waittime',
        help='wait time in seconds to allow orders to arrive, default=5',
        default=5)
    parser.add_option(
        '-s',
        '--mincjamount',
        type='int',
        dest='mincjamount',
        default=100000,
        help='minimum coinjoin amount in transaction in satoshi, default 100k')
    (options, args) = parser.parse_args()
    #TODO somehow implement a lower limit

    if len(args) < 1:
        parser.error('Needs a wallet file')
        sys.exit(0)
    wallet_file = args[0]
    destaddrs = args[1:]

    common.load_program_config()
    for addr in destaddrs:
        addr_valid, errormsg = validate_address(addr)
        if not addr_valid:
            print 'ERROR: Address ' + addr + ' invalid. ' + errormsg
            return

    if len(destaddrs) + options.addrask <= 1:
        print '=' * 50
        print 'WARNING: You are only using one destination address'
        print 'this is very bad for privacy'
        print '=' * 50

    print str(options)
    tx_list = generate_tumbler_tx(destaddrs, options)
    if not tx_list:
        return

    tx_list2 = copy.deepcopy(tx_list)
    tx_dict = {}
    for tx in tx_list2:
        srcmixdepth = tx['srcmixdepth']
        tx.pop('srcmixdepth')
        if srcmixdepth not in tx_dict:
            tx_dict[srcmixdepth] = []
        tx_dict[srcmixdepth].append(tx)
    dbg_tx_list = []
    for srcmixdepth, txlist in tx_dict.iteritems():
        dbg_tx_list.append({'srcmixdepth': srcmixdepth, 'tx': txlist})
    debug('tumbler transaction list')
    pprint(dbg_tx_list)

    total_wait = sum([tx['wait'] for tx in tx_list])
    print 'waits in total for ' + str(
        len(tx_list)) + ' blocks and ' + str(total_wait) + ' minutes'
    total_block_and_wait = len(tx_list) * 10 + total_wait
    print('estimated time taken ' + str(total_block_and_wait) +
          ' minutes or ' + str(round(total_block_and_wait / 60.0, 2)) +
          ' hours')

    ret = raw_input('tumble with these tx? (y/n):')
    if ret[0] != 'y':
        return

    #NOTE: possibly out of date documentation
    #a couple of modes
    #im-running-from-the-nsa, takes about 80 hours, costs a lot
    #python tumbler.py -a 10 -N 10 5 -c 10 5 -l 50 -M 10 wallet_file 1xxx
    #
    #quick and cheap, takes about 90 minutes
    #python tumbler.py -N 2 1 -c 3 0.001 -l 10 -M 3 -a 1 wallet_file 1xxx
    #
    #default, good enough for most, takes about 5 hours
    #python tumbler.py wallet_file 1xxx
    #
    #for quick testing
    #python tumbler.py -N 2 1 -c 3 0.001 -l 0.1 -M 3 -a 0 wallet_file 1xxx 1yyy
    wallet = Wallet(wallet_file,
                    max_mix_depth=options.mixdepthsrc + options.mixdepthcount)
    common.bc_interface.sync_wallet(wallet)

    common.nickname = random_nick()
    debug('starting tumbler')
    irc = IRCMessageChannel(common.nickname)
    tumbler = Tumbler(irc, wallet, tx_list, options.txfee, options.maxcjfee,
                      options.mincjamount)
    try:
        debug('connecting to irc')
        irc.run()
    except:
        debug('CRASHING, DUMPING EVERYTHING')
        debug_dump_object(wallet, ['addr_cache', 'keys', 'seed'])
        debug_dump_object(tumbler)
        import traceback
        debug(traceback.format_exc())
예제 #15
0
def main():
    parser = OptionParser(
        usage='usage: %prog [options] [wallet file / seed] [amount] [destaddr]',
        description='Sends a single payment from the zero mixing depth of your '
        + 'wallet to an given address using coinjoin and then switches off. ' +
        'Setting amount to zero will do a sweep, where the entire mix depth is emptied'
    )
    parser.add_option(
        '-f',
        '--txfee',
        action='store',
        type='int',
        dest='txfee',
        default=10000,
        help='miner fee contribution, in satoshis, default=10000')
    parser.add_option(
        '-w',
        '--wait-time',
        action='store',
        type='float',
        dest='waittime',
        help='wait time in seconds to allow orders to arrive, default=5',
        default=5)
    parser.add_option('-N',
                      '--makercount',
                      action='store',
                      type='int',
                      dest='makercount',
                      help='how many makers to coinjoin with, default=2',
                      default=2)
    parser.add_option('-m',
                      '--mixdepth',
                      action='store',
                      type='int',
                      dest='mixdepth',
                      help='mixing depth to spend from, default=0',
                      default=0)
    parser.add_option('--yes',
                      action='store_true',
                      dest='answeryes',
                      default=False,
                      help='answer yes to everything')
    (options, args) = parser.parse_args()

    if len(args) < 3:
        parser.error('Needs a seed, amount and destination address')
        sys.exit(0)
    seed = args[0]
    amount = int(args[1])
    destaddr = args[2]

    load_program_config()
    addr_valid, errormsg = validate_address(destaddr)
    if not addr_valid:
        print 'ERROR: Address invalid. ' + errormsg
        return

    common.nickname = random_nick()
    debug('starting sendpayment')
    import binascii, os

    wallet = Wallet(seed, options.mixdepth + 1)
    common.bc_interface.sync_wallet(wallet)
    wallet.print_debug_wallet_info()

    irc = IRCMessageChannel(common.nickname)
    taker = SendPayment(irc, wallet, destaddr, amount, options.makercount,
                        options.txfee, options.waittime, options.mixdepth,
                        options.answeryes)
    try:
        debug('starting irc')
        irc.run()
    except:
        debug('CRASHING, DUMPING EVERYTHING')
        debug('wallet seed = ' + seed)
        debug_dump_object(wallet, ['addr_cache', 'keys', 'seed'])
        debug_dump_object(taker)
        import traceback
        debug(traceback.format_exc())
예제 #16
0
def main():
	parser = OptionParser(usage='usage: %prog [options] [auth utxo] [cjamount] [cjaddr] [changeaddr] [utxos..]',
		description='Creates an unsigned coinjoin transaction. Outputs a partially signed transaction ' +
			'hex string. The user must sign their inputs independently and broadcast them. The JoinMarket' +
			' protocol requires the taker to have a single p2pk UTXO input to use to authenticate the ' +
			' encrypted messages. For this reason you must pass auth utxo and the corresponding private key')
	#for cjamount=0 do a sweep, and ignore change address
	parser.add_option('-f', '--txfee', action='store', type='int', dest='txfee',
		default=10000, help='total miner fee in satoshis, default=10000')
	parser.add_option('-w', '--wait-time', action='store', type='float', dest='waittime',
		help='wait time in seconds to allow orders to arrive, default=5', default=5)
	parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount',
		help='how many makers to coinjoin with, default=2', default=2)
	parser.add_option('-C','--choose-cheapest', action='store_true', dest='choosecheapest', default=False,
		help='override weightened offers picking and choose cheapest')
	parser.add_option('-P','--pick-orders', action='store_true', dest='pickorders', default=False,
		help='manually pick which orders to take. doesn\'t work while sweeping.')
	parser.add_option('--yes', action='store_true', dest='answeryes', default=False,
		help='answer yes to everything')
	#TODO implement
	#parser.add_option('-n', '--no-network', action='store_true', dest='nonetwork', default=False,
	#	help='dont query the blockchain interface, instead user must supply value of UTXOs on ' +
	#		' command line in the format txid:output/value-in-satoshi')
	(options, args) = parser.parse_args()

	if len(args) < 3:
		parser.error('Needs a wallet, amount and destination address')
		sys.exit(0)
	auth_utxo = args[0]
	cjamount = int(args[1])
	destaddr = args[2]
	changeaddr = args[3]
	cold_utxos = args[4:]

	common.load_program_config()
	addr_valid1, errormsg1 = validate_address(destaddr)
	#if amount = 0 dont bother checking changeaddr so user can write any junk
	if cjamount != 0:
		addr_valid2, errormsg2 = validate_address(changeaddr)
	else:
		addr_valid2 = True
	if not addr_valid1 or not addr_valid2:
		if not addr_valid1:
			print 'ERROR: Address invalid. ' + errormsg1
		else:
			print 'ERROR: Address invalid. ' + errormsg2
		return

	all_utxos = [auth_utxo] + cold_utxos
	query_result = common.bc_interface.query_utxo_set(all_utxos)
	if None in query_result:
		print query_result
	utxo_data = {}
	for utxo, data in zip(all_utxos, query_result):
		utxo_data[utxo] = {'address': data['address'], 'value': data['value']}
	auth_privkey = raw_input('input private key for ' + utxo_data[auth_utxo]['address'] + ' :')
	if utxo_data[auth_utxo]['address'] != btc.privtoaddr(auth_privkey, common.get_p2pk_vbyte()):
		print 'ERROR: privkey does not match auth utxo'
		return

	chooseOrdersFunc = None
	if options.pickorders and amount != 0: #cant use for sweeping
		chooseOrdersFunc = pick_order
	elif options.choosecheapest:
		chooseOrdersFunc = cheapest_order_choose
	else: #choose randomly (weighted)
		chooseOrdersFunc = weighted_order_choose
	
	common.nickname = random_nick()
	debug('starting sendpayment')

	class UnsignedTXWallet(common.AbstractWallet):
		def get_key_from_addr(self, addr):
			debug('getting privkey of ' + addr)
			if btc.privtoaddr(auth_privkey, common.get_p2pk_vbyte()) != addr:
				raise RuntimeError('privkey doesnt match given address')
			return auth_privkey

	wallet = UnsignedTXWallet()
	irc = IRCMessageChannel(common.nickname)
	taker = CreateUnsignedTx(irc, wallet, auth_utxo, cjamount, destaddr,
		changeaddr, utxo_data, options, chooseOrdersFunc)
	try:
		debug('starting irc')
		irc.run()
	except:
		debug('CRASHING, DUMPING EVERYTHING')
		debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed'])
		debug_dump_object(taker)
		import traceback
		debug(traceback.format_exc())
예제 #17
0
def main():
    parser = OptionParser(
        usage="usage: %prog [options] [wallet file] [destaddr(s)...]",
        description="Sends bitcoins to many different addresses using coinjoin in"
        " an attempt to break the link between them. Sending to multiple "
        " addresses is highly recommended for privacy. This tumbler can"
        " be configured to ask for more address mid-run, giving the user"
        " a chance to click `Generate New Deposit Address` on whatever service"
        " they are using.",
    )
    parser.add_option(
        "-m",
        "--mixdepthsource",
        type="int",
        dest="mixdepthsrc",
        help="mixing depth to spend from, default=0",
        default=0,
    )
    parser.add_option(
        "-f",
        "--txfee",
        type="int",
        dest="txfee",
        default=10000,
        help="miner fee contribution, in satoshis, default=10000",
    )
    parser.add_option(
        "-x",
        "--maxcjfee",
        type="float",
        dest="maxcjfee",
        default=0.01,
        help="maximum coinjoin fee the tumbler is willing to pay to a single market maker. default=0.01 (1%)",
    )
    parser.add_option(
        "-a",
        "--addrask",
        type="int",
        dest="addrask",
        default=2,
        help="How many more addresses to ask for in the terminal. Should " "be similar to --txcountparams. default=2",
    )
    parser.add_option(
        "-N",
        "--makercountrange",
        type="float",
        nargs=2,
        action="store",
        dest="makercountrange",
        help="Input the mean and spread of number of makers to use. e.g. 3 1.5 will be a normal distribution "
        "with mean 3 and standard deveation 1.5 inclusive, default=3 1.5",
        default=(3, 1.5),
    )
    parser.add_option(
        "-M",
        "--mixdepthcount",
        type="int",
        dest="mixdepthcount",
        help="How many mixing depths to mix through",
        default=4,
    )
    parser.add_option(
        "-c",
        "--txcountparams",
        type="float",
        nargs=2,
        dest="txcountparams",
        default=(5, 1),
        help="The number of transactions to take coins from one mixing depth to the next, it is"
        " randomly chosen following a normal distribution. Should be similar to --addrask. "
        "This option controlls the parameters of that normal curve. (mean, standard deviation). default=(3, 1)",
    )
    parser.add_option(
        "--amountpower",
        type="float",
        dest="amountpower",
        default=100.0,
        help="The output amounts follow a power law distribution, this is the power, default=100.0",
    )
    parser.add_option(
        "-l",
        "--timelambda",
        type="float",
        dest="timelambda",
        default=20,
        help="Average the number of minutes to wait between transactions. Randomly chosen "
        " following an exponential distribution, which describes the time between uncorrelated"
        " events. default=20",
    )
    parser.add_option(
        "-w",
        "--wait-time",
        action="store",
        type="float",
        dest="waittime",
        help="wait time in seconds to allow orders to arrive, default=5",
        default=5,
    )
    parser.add_option(
        "-s",
        "--mincjamount",
        type="int",
        dest="mincjamount",
        default=100000,
        help="minimum coinjoin amount in transaction in satoshi, default 100k",
    )
    (options, args) = parser.parse_args()
    # TODO somehow implement a lower limit

    if len(args) < 1:
        parser.error("Needs a wallet file")
        sys.exit(0)
    wallet_file = args[0]
    destaddrs = args[1:]

    common.load_program_config()
    for addr in destaddrs:
        addr_valid, errormsg = validate_address(addr)
        if not addr_valid:
            print "ERROR: Address " + addr + " invalid. " + errormsg
            return

    if len(destaddrs) + options.addrask <= 1:
        print "=" * 50
        print "WARNING: You are only using one destination address"
        print "this is very bad for privacy"
        print "=" * 50

    print str(options)
    tx_list = generate_tumbler_tx(destaddrs, options)
    if not tx_list:
        return

    tx_list2 = copy.deepcopy(tx_list)
    tx_dict = {}
    for tx in tx_list2:
        srcmixdepth = tx["srcmixdepth"]
        tx.pop("srcmixdepth")
        if srcmixdepth not in tx_dict:
            tx_dict[srcmixdepth] = []
        tx_dict[srcmixdepth].append(tx)
    dbg_tx_list = []
    for srcmixdepth, txlist in tx_dict.iteritems():
        dbg_tx_list.append({"srcmixdepth": srcmixdepth, "tx": txlist})
    debug("tumbler transaction list")
    pprint(dbg_tx_list)

    total_wait = sum([tx["wait"] for tx in tx_list])
    print "waits in total for " + str(len(tx_list)) + " blocks and " + str(total_wait) + " minutes"
    total_block_and_wait = len(tx_list) * 10 + total_wait
    print (
        "estimated time taken "
        + str(total_block_and_wait)
        + " minutes or "
        + str(round(total_block_and_wait / 60.0, 2))
        + " hours"
    )

    ret = raw_input("tumble with these tx? (y/n):")
    if ret[0] != "y":
        return

        # NOTE: possibly out of date documentation
        # a couple of modes
        # im-running-from-the-nsa, takes about 80 hours, costs a lot
        # python tumbler.py -a 10 -N 10 5 -c 10 5 -l 50 -M 10 wallet_file 1xxx
        #
        # quick and cheap, takes about 90 minutes
        # python tumbler.py -N 2 1 -c 3 0.001 -l 10 -M 3 -a 1 wallet_file 1xxx
        #
        # default, good enough for most, takes about 5 hours
        # python tumbler.py wallet_file 1xxx
        #
        # for quick testing
        # python tumbler.py -N 2 1 -c 3 0.001 -l 0.1 -M 3 -a 0 wallet_file 1xxx 1yyy
    wallet = Wallet(wallet_file, max_mix_depth=options.mixdepthsrc + options.mixdepthcount)
    common.bc_interface.sync_wallet(wallet)

    common.nickname = random_nick()
    debug("starting tumbler")
    irc = IRCMessageChannel(common.nickname)
    tumbler = Tumbler(irc, wallet, tx_list, options.txfee, options.maxcjfee, options.mincjamount)
    try:
        debug("connecting to irc")
        irc.run()
    except:
        debug("CRASHING, DUMPING EVERYTHING")
        debug_dump_object(wallet, ["addr_cache", "keys", "seed"])
        debug_dump_object(tumbler)
        import traceback

        debug(traceback.format_exc())