Skip to content

Instantly share code, notes, and snippets.

@wozz
Created August 31, 2017 16:14
Show Gist options
  • Save wozz/f81eec454fadb019f18b9693874e4e8a to your computer and use it in GitHub Desktop.
Save wozz/f81eec454fadb019f18b9693874e4e8a to your computer and use it in GitHub Desktop.
joinmarket bitcoin cash tool
# this tool can be used to create sweep transactions for bitcoin cash
# it's designed to send all coins from one mixdepth at a time to a single
# address with a small fee.
# bitcoin cash donations: 128Q5Ro2c9Enb5DhG7wSBmws8nCtC7e5x8
# sample run command:
# python bcash-tool.py -m 1 -g 10 -a 128Q5Ro2c9Enb5DhG7wSBmws8nCtC7e5x8 -f 3000 wallet.json
# this tool does not broadcast the signed transaction, it only prints it to the terminal
# once you are satisfied with the transaction, you can broadcast here:
# https://bch-bitcore2.trezor.io/tx/send
# created at commit 66bd67a58a7fc242393a767edd478dd6258ec0ff of joinmarket
from __future__ import absolute_import, print_function
import sys
from optparse import OptionParser
import binascii
import re
from joinmarket import load_program_config, Wallet, jm_single
import bitcoin as btc
SIGHASH_BCASH_FORK = 0x41
description = (
'Allows extracting bitcoin cash from joinmarket wallet')
parser = OptionParser(usage='usage: %prog [options] [wallet file]',
description=description)
parser.add_option('-m',
'--mixdepth',
action='store',
type='int',
dest='mixdepth',
help='mix depth to sweep from the wallet')
parser.add_option('-g',
'--gap-limit',
type="int",
action='store',
dest='gaplimit',
help='gap limit for wallet, default=6',
default=6)
parser.add_option('-a',
'--addr',
type='str',
action='store',
dest='addr',
help='destination bitcoin cash address to send funds')
parser.add_option('-f',
'--fee',
type='int',
action='store',
dest='fee',
help='fee to use in satoshis, default=1500',
default=1500)
parser.add_option('-i',
'--ignoresecp',
type='int',
action='store',
dest='ignore',
help='set once secp libs are moved',
default=0)
(options, args) = parser.parse_args()
if len(args) < 1:
parser.error('Needs a wallet file')
sys.exit(0)
load_program_config()
# helpers
def serialize_outpoint(txin):
return txin["outpoint"]["hash"][::-1] + btc.encode(txin["outpoint"]["index"], 256, 4)[::-1]
def serialize_output(txo):
return btc.encode(txo["value"], 256, 8)[::-1] + btc.num_to_var_int(len(txo["script"])) + txo["script"]
def bcash_preimage(tx, i, value):
deser = btc.deserialize(tx)
version = btc.encode(deser["version"], 256, 4)[::-1]
hashType = btc.encode(0x41, 256, 4)[::-1]
nLockTime = btc.encode(0, 256, 4)[::-1]
prevouts = ("".join(serialize_outpoint(txin) for txin in deser["ins"]))
hashPrevouts = btc.bin_dbl_sha256(prevouts)
seqser = ("".join(btc.encode(x["sequence"], 256, 4)[::-1] for x in deser["ins"]))
hashSequence = btc.bin_dbl_sha256(seqser)
outputs = ("".join(serialize_output(o) for o in deser["outs"]))
hashOutputs = btc.bin_dbl_sha256(outputs)
txin = deser["ins"][i]
outpoint = serialize_outpoint(txin)
scriptCode = (btc.num_to_var_int(len(txin["script"])) + txin["script"])
amount = btc.encode(value, 256, 8)[::-1]
nSequence = btc.encode(txin["sequence"], 256, 4)[::-1]
return version + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLockTime + hashType
def txhash(tx, hashcode=None, i=0, value=0):
if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
tx = btc.changebase(tx, 16, 256)
p = bcash_preimage(tx, i, value)
return btc.dbl_sha256(p)
def bin_txhash(tx, hashcode=None, i=0, value=0):
return binascii.unhexlify(txhash(tx, hashcode, i, value))
def ecdsa_tx_sign(tx, priv, hashcode=SIGHASH_BCASH_FORK, i=0, value=0):
rawsig = ''
if btc.secp_present and not options.ignore:
print("try again without secp libs:\nmkdir backup\nmv bitcoin/secp256k1* backup/\n\nThen rerun with --ignoresecp=1 flag added")
sys.exit(1)
else:
rawsig = btc.ecdsa_raw_sign(bin_txhash(tx, hashcode, i, value), priv)
return btc.der_encode_sig(*rawsig) + btc.encode(hashcode, 16, 2)
def sign(tx, i, priv, hashcode=SIGHASH_BCASH_FORK, value=0):
pub = btc.privkey_to_pubkey(priv)
address = btc.pubkey_to_address(pub)
signing_tx = btc.signature_form(tx, i, btc.mk_pubkey_script(address), hashcode)
sig = ecdsa_tx_sign(signing_tx, priv, hashcode, i=i, value=value)
txobj = btc.deserialize(tx)
txobj["ins"][i]["script"] = btc.serialize_script([sig, pub])
return btc.serialize(txobj)
####
seed = args[0]
wallet = Wallet(seed,
options.mixdepth,
options.gaplimit,
extend_mixdepth=False,
storepassword=False)
if 'listunspent_args' not in jm_single().config.options('POLICY'):
jm_single().config.set('POLICY','listunspent_args', '[0]')
jm_single().bc_interface.sync_wallet(wallet)
utxo_list = wallet.get_utxos_by_mixdepth()
fee = options.fee
txins = []
txouts = []
total = 0
for utxo in utxo_list[options.mixdepth-1]:
txins.append({
"output": utxo
})
total = total + utxo_list[options.mixdepth-1][utxo]["value"]
total = total - fee
txouts = [
{'value': total, "address": options.addr}
]
tx = btc.deserialize(btc.mktx(txins, txouts))
sertx = btc.serialize(tx)
for index, ins in enumerate(tx['ins']):
utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index'])
addr = utxo_list[options.mixdepth-1][utxo]['address']
inval = utxo_list[options.mixdepth-1][utxo]['value']
signed_tx = sign(sertx, index, wallet.get_key_from_addr(addr), hashcode=SIGHASH_BCASH_FORK, value=inval)
sertx = signed_tx
tx = btc.deserialize(sertx)
print(tx)
print("===========================")
print(sertx)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment