Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic segwit support #184

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 192 additions & 10 deletions bitcoin/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ def json_changebase(obj, changer):
# Transaction serialization and deserialization


def deserialize(tx):
def deserialize(tx, segwit=False):
# segwit: specifies if the serialization is for a SegWit transaction.
if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
#tx = bytes(bytearray.fromhex(tx))
return json_changebase(deserialize(binascii.unhexlify(tx)),
return json_changebase(deserialize(binascii.unhexlify(tx), segwit),
lambda x: safe_hexlify(x))
# http://stackoverflow.com/questions/4851463/python-closure-write-to-variable-in-parent-scope
# Python's scoping rules are demented, requiring me to make pos an object
Expand All @@ -72,8 +73,18 @@ def read_var_string():
size = read_var_int()
return read_bytes(size)

def read_segwit_string():
size = read_var_int()
return num_to_var_int(size)+read_bytes(size)

obj = {"ins": [], "outs": []}
if segwit:
obj = {"ins": [], "outs": [], "witness": []}
obj["version"] = read_as_int(4)
if segwit:
# The next two bytes are marker and flag:
obj["marker"] = read_var_int()
obj["flag"] = read_var_int()
ins = read_var_int()
for i in range(ins):
obj["ins"].append({
Expand All @@ -90,18 +101,33 @@ def read_var_string():
"value": read_as_int(8),
"script": read_var_string()
})
if segwit:
# We know that there are ins witness fields:
for w in range(ins):
sc = ''
witems = read_var_int() # The number of witness items for this input
for i in range(witems): # If witems == 0, then this is NOT evaluated (there are not witness items!)
sc += read_segwit_string() # Concatenate witness items
obj["witness"].append({ # Add witness data
"number": witems,
"scriptCode": sc
})
obj["locktime"] = read_as_int(4)
return obj

def serialize(txobj):
def serialize(txobj, segwit=False):
# segwit: specifies if the serialization is for a SegWit transaction.
#if isinstance(txobj, bytes):
# txobj = bytes_to_hex_string(txobj)
o = []
if json_is_base(txobj, 16):
json_changedbase = json_changebase(txobj, lambda x: binascii.unhexlify(x))
hexlified = safe_hexlify(serialize(json_changedbase))
hexlified = safe_hexlify(serialize(json_changedbase, segwit))
return hexlified
o.append(encode(txobj["version"], 256, 4)[::-1])
if segwit:
o.append(encode(txobj["marker"], 256, 1)[::-1])
o.append(encode(txobj["flag"], 256, 1)[::-1])
o.append(num_to_var_int(len(txobj["ins"])))
for inp in txobj["ins"]:
o.append(inp["outpoint"]["hash"][::-1])
Expand All @@ -112,6 +138,9 @@ def serialize(txobj):
for out in txobj["outs"]:
o.append(encode(out["value"], 256, 8)[::-1])
o.append(num_to_var_int(len(out["script"]))+out["script"])
if segwit:
for wit in txobj["witness"]:
o.append(num_to_var_int(wit["number"])+(wit["scriptCode"] if wit["scriptCode"] or is_python2 else bytes()))
o.append(encode(txobj["locktime"], 256, 4)[::-1])

return ''.join(o) if is_python2 else reduce(lambda x,y: x+y, o, bytes())
Expand All @@ -126,10 +155,13 @@ def serialize(txobj):
SIGHASH_ANYONECANPAY = 0x81


def signature_form(tx, i, script, hashcode=SIGHASH_ALL):
def signature_form(tx, i, script, hashcode=SIGHASH_ALL, segwit=False):
# segwit defines the structure of tx
# if amount: # amount is only different from 0 if it is a segwit input being signed
# return signature_form_segwit(tx, i, script, amount, hashcode)
i, hashcode = int(i), int(hashcode)
if isinstance(tx, string_or_bytes_types):
return serialize(signature_form(deserialize(tx), i, script, hashcode))
return serialize(signature_form(deserialize(tx, segwit), i, script, hashcode))
newtx = copy.deepcopy(tx)
for inp in newtx["ins"]:
inp["script"] = ""
Expand All @@ -147,6 +179,89 @@ def signature_form(tx, i, script, hashcode=SIGHASH_ALL):
pass
return newtx


def serialize_signature_form_segwit(txobj):
#
o = []
if json_is_base(txobj, 16):
json_changedbase = json_changebase(txobj, lambda x: binascii.unhexlify(x))
hexlified = safe_hexlify(serialize_signature_form_segwit(json_changedbase))
return hexlified
o.append(encode(txobj["version"], 256, 4)[::-1])
o.append(txobj["hashPrevouts"][::-1])
o.append(txobj["hashSequence"][::-1])
o.append(txobj["outpoint"]["hash"][::-1])
o.append(encode(txobj["outpoint"]["index"], 256, 4)[::-1])
o.append(num_to_var_int(len(txobj["scriptCode"]))+(txobj["scriptCode"] if txobj["scriptCode"] or is_python2 else bytes()))
o.append(encode(txobj["value"], 256, 8)[::-1])
o.append(encode(txobj["sequence"], 256, 4)[::-1])
o.append(txobj["hashOutputs"][::-1])
o.append(encode(txobj["locktime"], 256, 4)[::-1])
# No need to append with hashcode, as this is done with the function txhash() when using hashcode in argument.

return ''.join(o) if is_python2 else reduce(lambda x,y: x+y, o, bytes())


def mk_hashInputs(inp, hashcode=SIGHASH_ALL): # For segwit: Used to calculate the hashPrevouts and hashSequences to be signed
obj = {}
ss_pre, ss_seq = (), ()
for i in inp:
prevhsh = changebase(i["outpoint"]["hash"], 16, 256)[::-1]
previnx = encode(i["outpoint"]["index"], 256, 4)[::-1]
ss_pre += (prevhsh+previnx,) # tuple of all input outpoints
ss_seq += (encode(i["sequence"], 256, 4)[::-1],) # tuple of all input sequences
obj["hashPrevouts"] = (txhash(''.join(ss_pre)) if hashcode != SIGHASH_ANYONECANPAY else encode(0, 256, 32)) # double-SHA256 of concatenation of all input outpoints
obj["hashSequence"] = (txhash(''.join(ss_seq)) if hashcode == SIGHASH_ALL else encode(0, 256, 32)) # double-SHA256 of concatenation of all input sequences

return obj


def mk_hashOutputs(out, hashcode=SIGHASH_ALL): # For segwit: Used to calculate the hashOutputs to be signed
obj = {}
ss_out = ()
if hashcode!=SIGHASH_SINGLE:
for o in out:
outamount = encode(o["value"], 256, 8)[::-1]
outamount = changebase(outamount,256,16,16)
outscript = (num_to_var_int(len(o["script"])/2)+(changebase(o["script"], 16, 256) if o["script"] or is_python2 else bytes())) # FOR SEGWIT OUTPUTS
outscript = changebase(outscript, 256, 16)
ss_out += (outamount+outscript,) # tuple of all outputs
else: # if hashcode == SIGHASH_SINGLE
single_outamount = encode(o["outs"]["value"], 256, 8)[::-1]
single_outamount = changebase(single_outamount, 256, 16)
single_outscript = (num_to_var_int(len(o["outs"]["script"])/2)+(changebase(o["outs"]["script"], 16, 256) if o["outs"]["script"] or is_python2 else bytes()))
single_outscript = changebase(single_outscript, 256, 16)
single_ss_out = (single_outamount + single_outscript)
obj["hashOutputs"] = (txhash(single_ss_out) if hashcode == SIGHASH_SINGLE else (encode(0, 256, 32) if hashcode == SIGHASH_NONE else txhash(''.join(ss_out)))) # double-SHA256 of concatenation of all outputs

return obj


def signature_form_segwit(tx, i, scriptCode, amount, hashcode=SIGHASH_ALL):
#
if not amount: # amount is only different from 0 if it is a segwit input being signed
return signature_form(tx, i, scriptCode, hashcode, True)
i, hashcode = int(i), int(hashcode)
if isinstance(tx, string_or_bytes_types):
return serialize_signature_form_segwit(signature_form_segwit(deserialize(tx, True), i, scriptCode, amount, hashcode))
newtx = {}
newtx["version"] = tx["version"]
hashIn = mk_hashInputs(tx["ins"], hashcode)
newtx["hashPrevouts"] = hashIn["hashPrevouts"]
newtx["hashSequence"] = hashIn["hashSequence"]
newtx["outpoint"]={}
newtx["outpoint"]["hash"] = tx["ins"][i]["outpoint"]["hash"]
newtx["outpoint"]["index"] = tx["ins"][i]["outpoint"]["index"]
newtx["scriptCode"] = scriptCode
newtx["value"] = amount
newtx["sequence"] = tx["ins"][i]["sequence"]
hashOut = (mk_hashOutputs(tx["outs"][i], hashcode) if hashcode==SIGHASH_SINGLE else mk_hashOutputs(tx["outs"], hashcode))
newtx["hashOutputs"] = hashOut["hashOutputs"]
newtx["locktime"] = tx["locktime"]
# No need to append with hashcode, as this is done with the function txhash() when using hashcode in argument.

return newtx

# Making the actual signatures


Expand All @@ -173,7 +288,7 @@ def is_bip66(sig):
#0x30 [total-len] 0x02 [R-len] [R] 0x02 [S-len] [S] [sighash]
sig = bytearray.fromhex(sig) if re.match('^[0-9a-fA-F]*$', sig) else bytearray(sig)
if (sig[0] == 0x30) and (sig[1] == len(sig)-2): # check if sighash is missing
sig.extend(b"\1") # add SIGHASH_ALL for testing
sig.extend(b"\1") # add SIGHASH_ALL for testing
#assert (sig[-1] & 124 == 0) and (not not sig[-1]), "Bad SIGHASH value"

if len(sig) < 9 or len(sig) > 73: return False
Expand Down Expand Up @@ -234,6 +349,30 @@ def mk_pubkey_script(addr):
def mk_scripthash_script(addr):
return 'a914' + b58check_to_hex(addr) + '87'


def pubkey_to_hash(pubkey):
if isinstance(pubkey, (list, tuple)):
pubkey = encode_pubkey(pubkey, 'bin')
if len(pubkey) in [66, 130]:
return safe_hexlify(bin_hash160(binascii.unhexlify(pubkey)))
return safe_hexlify(bin_hash160(pubkey))


def hex_to_hash160(s_hex):
return safe_hexlify(bin_hash160(binascii.unhexlify(s_hex)))


def mk_p2wpkh_script(pubkey):
return 'a914'+ hex_to_hash160(mk_p2wpkh_redeemscript(pubkey)[2:]) + '87'


def mk_p2wpkh_redeemscript(pubkey):
return '160014' + pubkey_to_hash(pubkey)


def mk_p2wpkh_scriptcode(pubkey): # ScriptCode to be signed
return '76a914' + pubkey_to_hash(pubkey) + '88ac'

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be nice to make sure that the pubkey being passed to the functions mk_p2wpkh_script(), mk_p2wpkh_redeemscript() and mk_p2wpkh_scriptcode() is compressed, since with Segwit compressed-public-keys are mandatory.

# Address representation to output script


Expand Down Expand Up @@ -373,6 +512,44 @@ def sign(tx, i, priv, hashcode=SIGHASH_ALL):
return serialize(txobj)


def sign_segwit_struct(tx, i, priv, hashcode=SIGHASH_ALL, segwit=False, amount=0):
# tx must be a segwit transaction-structure
# segwit defines if the input to be signed comes from a P2WPKH-Segwit address; otherwise from standard P2PKH input
# if segwit: amount specifies the tx-output value, which the P2WPKH-input being signed spends (will be retrieved automatically if not defined)
from bitcoin.bci import history
from __builtin__ import str
i = int(i)
if (not is_python2 and isinstance(re, bytes)) or not re.match('^[0-9a-fA-F]*$', tx):
return binascii.unhexlify(sign_segwit_struct(safe_hexlify(tx), i, priv, hashcode, segwit))
if len(priv) <= 33:
priv = safe_hexlify(priv)
pub = compress(privkey_to_pubkey(priv)) # Mandatory to used compressed public keys with segwit
address = (script_to_address(mk_p2wpkh_script(pub)) if segwit else pubkey_to_address(pub))
txobj = deserialize(tx, True)
if segwit and amount <= 0:
all_history = history(address)
tospend = ':'.join((txobj["ins"][i]["outpoint"]["hash"], str(txobj["ins"][i]["outpoint"]["index"])))
for a in all_history:
if tospend in a["output"]:
amount = a["value"]
else:
raise Exception("Could not find the output to spend from 'address'.")
elif not segwit:
amount = 0
signing_tx = signature_form_segwit(tx, i, (mk_p2wpkh_scriptcode(pub) if segwit else mk_pubkey_script(address)), amount, hashcode) # TODO: this works for P2WPKH; but also P2WSH?
sig = ecdsa_tx_sign(signing_tx, priv, hashcode)
if segwit:
txobj["ins"][i]["script"] = mk_p2wpkh_redeemscript(pub)
txobj["witness"][i]["number"] = 2
txobj["witness"][i]["scriptCode"] = serialize_script([sig, pub])
else:
txobj["ins"][i]["script"] = serialize_script([sig, pub])
txobj["witness"][i]["number"] = 0
txobj["witness"][i]["scriptCode"] = ''

return serialize(txobj, True)


def signall(tx, priv):
# if priv is a dictionary, assume format is
# { 'txinhash:txinidx' : privkey }
Expand Down Expand Up @@ -420,15 +597,20 @@ def is_inp(arg):


def mktx(*args):
# [in0, in1...],[out0, out1...] or in0, in1 ... out0 out1 ...
# [in0, in1...],[out0, out1...],[segwit] or in0, in1 ... out0 out1 ... segwit
ins, outs = [], []
segwit = False # Create legacy transaction-structure as default
for arg in args:
if isinstance(arg, list):
if isinstance(arg, bool):
segwit = arg # Creates segwit transaction-structure if the last argument is "True"
elif isinstance(arg, list):
for a in arg: (ins if is_inp(a) else outs).append(a)
else:
(ins if is_inp(arg) else outs).append(arg)

txobj = {"locktime": 0, "version": 1, "ins": [], "outs": []}
if segwit:
txobj = {"locktime": 0, "version": 1, "ins": [], "outs": [], "marker": 0, "flag": 1, "witness": []}
for i in ins:
if isinstance(i, dict) and "outpoint" in i:
txobj["ins"].append(i)
Expand Down Expand Up @@ -461,7 +643,7 @@ def mktx(*args):
outobj["value"] = o["value"]
txobj["outs"].append(outobj)

return serialize(txobj)
return serialize(txobj, segwit)


def select(unspent, value):
Expand Down