Created
August 31, 2017 16:14
-
-
Save wozz/f81eec454fadb019f18b9693874e4e8a to your computer and use it in GitHub Desktop.
joinmarket bitcoin cash tool
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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