mirror of
https://github.com/HelloZeroNet/ZeroNet.git
synced 2023-12-14 04:33:03 +01:00
514 lines
17 KiB
Python
514 lines
17 KiB
Python
#!/usr/bin/python
|
|
import binascii, re, json, copy, sys
|
|
from .main import *
|
|
from _functools import reduce
|
|
|
|
### Hex to bin converter and vice versa for objects
|
|
|
|
|
|
def json_is_base(obj, base):
|
|
if not is_python2 and isinstance(obj, bytes):
|
|
return False
|
|
|
|
alpha = get_code_string(base)
|
|
if isinstance(obj, string_types):
|
|
for i in range(len(obj)):
|
|
if alpha.find(obj[i]) == -1:
|
|
return False
|
|
return True
|
|
elif isinstance(obj, int_types) or obj is None:
|
|
return True
|
|
elif isinstance(obj, list):
|
|
for i in range(len(obj)):
|
|
if not json_is_base(obj[i], base):
|
|
return False
|
|
return True
|
|
else:
|
|
for x in obj:
|
|
if not json_is_base(obj[x], base):
|
|
return False
|
|
return True
|
|
|
|
|
|
def json_changebase(obj, changer):
|
|
if isinstance(obj, string_or_bytes_types):
|
|
return changer(obj)
|
|
elif isinstance(obj, int_types) or obj is None:
|
|
return obj
|
|
elif isinstance(obj, list):
|
|
return [json_changebase(x, changer) for x in obj]
|
|
return dict((x, json_changebase(obj[x], changer)) for x in obj)
|
|
|
|
# Transaction serialization and deserialization
|
|
|
|
|
|
def deserialize(tx):
|
|
if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
|
|
#tx = bytes(bytearray.fromhex(tx))
|
|
return json_changebase(deserialize(binascii.unhexlify(tx)),
|
|
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
|
|
# so that it is call-by-reference
|
|
pos = [0]
|
|
|
|
def read_as_int(bytez):
|
|
pos[0] += bytez
|
|
return decode(tx[pos[0]-bytez:pos[0]][::-1], 256)
|
|
|
|
def read_var_int():
|
|
pos[0] += 1
|
|
|
|
val = from_byte_to_int(tx[pos[0]-1])
|
|
if val < 253:
|
|
return val
|
|
return read_as_int(pow(2, val - 252))
|
|
|
|
def read_bytes(bytez):
|
|
pos[0] += bytez
|
|
return tx[pos[0]-bytez:pos[0]]
|
|
|
|
def read_var_string():
|
|
size = read_var_int()
|
|
return read_bytes(size)
|
|
|
|
obj = {"ins": [], "outs": []}
|
|
obj["version"] = read_as_int(4)
|
|
ins = read_var_int()
|
|
for i in range(ins):
|
|
obj["ins"].append({
|
|
"outpoint": {
|
|
"hash": read_bytes(32)[::-1],
|
|
"index": read_as_int(4)
|
|
},
|
|
"script": read_var_string(),
|
|
"sequence": read_as_int(4)
|
|
})
|
|
outs = read_var_int()
|
|
for i in range(outs):
|
|
obj["outs"].append({
|
|
"value": read_as_int(8),
|
|
"script": read_var_string()
|
|
})
|
|
obj["locktime"] = read_as_int(4)
|
|
return obj
|
|
|
|
def serialize(txobj):
|
|
#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))
|
|
return hexlified
|
|
o.append(encode(txobj["version"], 256, 4)[::-1])
|
|
o.append(num_to_var_int(len(txobj["ins"])))
|
|
for inp in txobj["ins"]:
|
|
o.append(inp["outpoint"]["hash"][::-1])
|
|
o.append(encode(inp["outpoint"]["index"], 256, 4)[::-1])
|
|
o.append(num_to_var_int(len(inp["script"]))+(inp["script"] if inp["script"] or is_python2 else bytes()))
|
|
o.append(encode(inp["sequence"], 256, 4)[::-1])
|
|
o.append(num_to_var_int(len(txobj["outs"])))
|
|
for out in txobj["outs"]:
|
|
o.append(encode(out["value"], 256, 8)[::-1])
|
|
o.append(num_to_var_int(len(out["script"]))+out["script"])
|
|
o.append(encode(txobj["locktime"], 256, 4)[::-1])
|
|
|
|
return ''.join(o) if is_python2 else reduce(lambda x,y: x+y, o, bytes())
|
|
|
|
# Hashing transactions for signing
|
|
|
|
SIGHASH_ALL = 1
|
|
SIGHASH_NONE = 2
|
|
SIGHASH_SINGLE = 3
|
|
# this works like SIGHASH_ANYONECANPAY | SIGHASH_ALL, might as well make it explicit while
|
|
# we fix the constant
|
|
SIGHASH_ANYONECANPAY = 0x81
|
|
|
|
|
|
def signature_form(tx, i, script, hashcode=SIGHASH_ALL):
|
|
i, hashcode = int(i), int(hashcode)
|
|
if isinstance(tx, string_or_bytes_types):
|
|
return serialize(signature_form(deserialize(tx), i, script, hashcode))
|
|
newtx = copy.deepcopy(tx)
|
|
for inp in newtx["ins"]:
|
|
inp["script"] = ""
|
|
newtx["ins"][i]["script"] = script
|
|
if hashcode == SIGHASH_NONE:
|
|
newtx["outs"] = []
|
|
elif hashcode == SIGHASH_SINGLE:
|
|
newtx["outs"] = newtx["outs"][:len(newtx["ins"])]
|
|
for out in newtx["outs"][:len(newtx["ins"]) - 1]:
|
|
out['value'] = 2**64 - 1
|
|
out['script'] = ""
|
|
elif hashcode == SIGHASH_ANYONECANPAY:
|
|
newtx["ins"] = [newtx["ins"][i]]
|
|
else:
|
|
pass
|
|
return newtx
|
|
|
|
# Making the actual signatures
|
|
|
|
|
|
def der_encode_sig(v, r, s):
|
|
b1, b2 = safe_hexlify(encode(r, 256)), safe_hexlify(encode(s, 256))
|
|
if len(b1) and b1[0] in '89abcdef':
|
|
b1 = '00' + b1
|
|
if len(b2) and b2[0] in '89abcdef':
|
|
b2 = '00' + b2
|
|
left = '02'+encode(len(b1)//2, 16, 2)+b1
|
|
right = '02'+encode(len(b2)//2, 16, 2)+b2
|
|
return '30'+encode(len(left+right)//2, 16, 2)+left+right
|
|
|
|
def der_decode_sig(sig):
|
|
leftlen = decode(sig[6:8], 16)*2
|
|
left = sig[8:8+leftlen]
|
|
rightlen = decode(sig[10+leftlen:12+leftlen], 16)*2
|
|
right = sig[12+leftlen:12+leftlen+rightlen]
|
|
return (None, decode(left, 16), decode(right, 16))
|
|
|
|
def is_bip66(sig):
|
|
"""Checks hex DER sig for BIP66 consistency"""
|
|
#https://raw.githubusercontent.com/bitcoin/bips/master/bip-0066.mediawiki
|
|
#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
|
|
#assert (sig[-1] & 124 == 0) and (not not sig[-1]), "Bad SIGHASH value"
|
|
|
|
if len(sig) < 9 or len(sig) > 73: return False
|
|
if (sig[0] != 0x30): return False
|
|
if (sig[1] != len(sig)-3): return False
|
|
rlen = sig[3]
|
|
if (5+rlen >= len(sig)): return False
|
|
slen = sig[5+rlen]
|
|
if (rlen + slen + 7 != len(sig)): return False
|
|
if (sig[2] != 0x02): return False
|
|
if (rlen == 0): return False
|
|
if (sig[4] & 0x80): return False
|
|
if (rlen > 1 and (sig[4] == 0x00) and not (sig[5] & 0x80)): return False
|
|
if (sig[4+rlen] != 0x02): return False
|
|
if (slen == 0): return False
|
|
if (sig[rlen+6] & 0x80): return False
|
|
if (slen > 1 and (sig[6+rlen] == 0x00) and not (sig[7+rlen] & 0x80)):
|
|
return False
|
|
return True
|
|
|
|
def txhash(tx, hashcode=None):
|
|
if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
|
|
tx = changebase(tx, 16, 256)
|
|
if hashcode:
|
|
return dbl_sha256(from_string_to_bytes(tx) + encode(int(hashcode), 256, 4)[::-1])
|
|
else:
|
|
return safe_hexlify(bin_dbl_sha256(tx)[::-1])
|
|
|
|
|
|
def bin_txhash(tx, hashcode=None):
|
|
return binascii.unhexlify(txhash(tx, hashcode))
|
|
|
|
|
|
def ecdsa_tx_sign(tx, priv, hashcode=SIGHASH_ALL):
|
|
rawsig = ecdsa_raw_sign(bin_txhash(tx, hashcode), priv)
|
|
return der_encode_sig(*rawsig)+encode(hashcode, 16, 2)
|
|
|
|
|
|
def ecdsa_tx_verify(tx, sig, pub, hashcode=SIGHASH_ALL):
|
|
return ecdsa_raw_verify(bin_txhash(tx, hashcode), der_decode_sig(sig), pub)
|
|
|
|
|
|
def ecdsa_tx_recover(tx, sig, hashcode=SIGHASH_ALL):
|
|
z = bin_txhash(tx, hashcode)
|
|
_, r, s = der_decode_sig(sig)
|
|
left = ecdsa_raw_recover(z, (0, r, s))
|
|
right = ecdsa_raw_recover(z, (1, r, s))
|
|
return (encode_pubkey(left, 'hex'), encode_pubkey(right, 'hex'))
|
|
|
|
# Scripts
|
|
|
|
|
|
def mk_pubkey_script(addr):
|
|
# Keep the auxiliary functions around for altcoins' sake
|
|
return '76a914' + b58check_to_hex(addr) + '88ac'
|
|
|
|
|
|
def mk_scripthash_script(addr):
|
|
return 'a914' + b58check_to_hex(addr) + '87'
|
|
|
|
# Address representation to output script
|
|
|
|
|
|
def address_to_script(addr):
|
|
if addr[0] == '3' or addr[0] == '2':
|
|
return mk_scripthash_script(addr)
|
|
else:
|
|
return mk_pubkey_script(addr)
|
|
|
|
# Output script to address representation
|
|
|
|
|
|
def script_to_address(script, vbyte=0):
|
|
if re.match('^[0-9a-fA-F]*$', script):
|
|
script = binascii.unhexlify(script)
|
|
if script[:3] == b'\x76\xa9\x14' and script[-2:] == b'\x88\xac' and len(script) == 25:
|
|
return bin_to_b58check(script[3:-2], vbyte) # pubkey hash addresses
|
|
else:
|
|
if vbyte in [111, 196]:
|
|
# Testnet
|
|
scripthash_byte = 196
|
|
elif vbyte == 0:
|
|
# Mainnet
|
|
scripthash_byte = 5
|
|
else:
|
|
scripthash_byte = vbyte
|
|
# BIP0016 scripthash addresses
|
|
return bin_to_b58check(script[2:-1], scripthash_byte)
|
|
|
|
|
|
def p2sh_scriptaddr(script, magicbyte=5):
|
|
if re.match('^[0-9a-fA-F]*$', script):
|
|
script = binascii.unhexlify(script)
|
|
return hex_to_b58check(hash160(script), magicbyte)
|
|
scriptaddr = p2sh_scriptaddr
|
|
|
|
|
|
def deserialize_script(script):
|
|
if isinstance(script, str) and re.match('^[0-9a-fA-F]*$', script):
|
|
return json_changebase(deserialize_script(binascii.unhexlify(script)),
|
|
lambda x: safe_hexlify(x))
|
|
out, pos = [], 0
|
|
while pos < len(script):
|
|
code = from_byte_to_int(script[pos])
|
|
if code == 0:
|
|
out.append(None)
|
|
pos += 1
|
|
elif code <= 75:
|
|
out.append(script[pos+1:pos+1+code])
|
|
pos += 1 + code
|
|
elif code <= 78:
|
|
szsz = pow(2, code - 76)
|
|
sz = decode(script[pos+szsz: pos:-1], 256)
|
|
out.append(script[pos + 1 + szsz:pos + 1 + szsz + sz])
|
|
pos += 1 + szsz + sz
|
|
elif code <= 96:
|
|
out.append(code - 80)
|
|
pos += 1
|
|
else:
|
|
out.append(code)
|
|
pos += 1
|
|
return out
|
|
|
|
|
|
def serialize_script_unit(unit):
|
|
if isinstance(unit, int):
|
|
if unit < 16:
|
|
return from_int_to_byte(unit + 80)
|
|
else:
|
|
return from_int_to_byte(unit)
|
|
elif unit is None:
|
|
return b'\x00'
|
|
else:
|
|
if len(unit) <= 75:
|
|
return from_int_to_byte(len(unit))+unit
|
|
elif len(unit) < 256:
|
|
return from_int_to_byte(76)+from_int_to_byte(len(unit))+unit
|
|
elif len(unit) < 65536:
|
|
return from_int_to_byte(77)+encode(len(unit), 256, 2)[::-1]+unit
|
|
else:
|
|
return from_int_to_byte(78)+encode(len(unit), 256, 4)[::-1]+unit
|
|
|
|
|
|
if is_python2:
|
|
def serialize_script(script):
|
|
if json_is_base(script, 16):
|
|
return binascii.hexlify(serialize_script(json_changebase(script,
|
|
lambda x: binascii.unhexlify(x))))
|
|
return ''.join(map(serialize_script_unit, script))
|
|
else:
|
|
def serialize_script(script):
|
|
if json_is_base(script, 16):
|
|
return safe_hexlify(serialize_script(json_changebase(script,
|
|
lambda x: binascii.unhexlify(x))))
|
|
|
|
result = bytes()
|
|
for b in map(serialize_script_unit, script):
|
|
result += b if isinstance(b, bytes) else bytes(b, 'utf-8')
|
|
return result
|
|
|
|
|
|
def mk_multisig_script(*args): # [pubs],k or pub1,pub2...pub[n],k
|
|
if isinstance(args[0], list):
|
|
pubs, k = args[0], int(args[1])
|
|
else:
|
|
pubs = list(filter(lambda x: len(str(x)) >= 32, args))
|
|
k = int(args[len(pubs)])
|
|
return serialize_script([k]+pubs+[len(pubs)]+[0xae])
|
|
|
|
# Signing and verifying
|
|
|
|
|
|
def verify_tx_input(tx, i, script, sig, pub):
|
|
if re.match('^[0-9a-fA-F]*$', tx):
|
|
tx = binascii.unhexlify(tx)
|
|
if re.match('^[0-9a-fA-F]*$', script):
|
|
script = binascii.unhexlify(script)
|
|
if not re.match('^[0-9a-fA-F]*$', sig):
|
|
sig = safe_hexlify(sig)
|
|
hashcode = decode(sig[-2:], 16)
|
|
modtx = signature_form(tx, int(i), script, hashcode)
|
|
return ecdsa_tx_verify(modtx, sig, pub, hashcode)
|
|
|
|
|
|
def sign(tx, i, priv, hashcode=SIGHASH_ALL):
|
|
i = int(i)
|
|
if (not is_python2 and isinstance(re, bytes)) or not re.match('^[0-9a-fA-F]*$', tx):
|
|
return binascii.unhexlify(sign(safe_hexlify(tx), i, priv))
|
|
if len(priv) <= 33:
|
|
priv = safe_hexlify(priv)
|
|
pub = privkey_to_pubkey(priv)
|
|
address = pubkey_to_address(pub)
|
|
signing_tx = signature_form(tx, i, mk_pubkey_script(address), hashcode)
|
|
sig = ecdsa_tx_sign(signing_tx, priv, hashcode)
|
|
txobj = deserialize(tx)
|
|
txobj["ins"][i]["script"] = serialize_script([sig, pub])
|
|
return serialize(txobj)
|
|
|
|
|
|
def signall(tx, priv):
|
|
# if priv is a dictionary, assume format is
|
|
# { 'txinhash:txinidx' : privkey }
|
|
if isinstance(priv, dict):
|
|
for e, i in enumerate(deserialize(tx)["ins"]):
|
|
k = priv["%s:%d" % (i["outpoint"]["hash"], i["outpoint"]["index"])]
|
|
tx = sign(tx, e, k)
|
|
else:
|
|
for i in range(len(deserialize(tx)["ins"])):
|
|
tx = sign(tx, i, priv)
|
|
return tx
|
|
|
|
|
|
def multisign(tx, i, script, pk, hashcode=SIGHASH_ALL):
|
|
if re.match('^[0-9a-fA-F]*$', tx):
|
|
tx = binascii.unhexlify(tx)
|
|
if re.match('^[0-9a-fA-F]*$', script):
|
|
script = binascii.unhexlify(script)
|
|
modtx = signature_form(tx, i, script, hashcode)
|
|
return ecdsa_tx_sign(modtx, pk, hashcode)
|
|
|
|
|
|
def apply_multisignatures(*args):
|
|
# tx,i,script,sigs OR tx,i,script,sig1,sig2...,sig[n]
|
|
tx, i, script = args[0], int(args[1]), args[2]
|
|
sigs = args[3] if isinstance(args[3], list) else list(args[3:])
|
|
|
|
if isinstance(script, str) and re.match('^[0-9a-fA-F]*$', script):
|
|
script = binascii.unhexlify(script)
|
|
sigs = [binascii.unhexlify(x) if x[:2] == '30' else x for x in sigs]
|
|
if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
|
|
return safe_hexlify(apply_multisignatures(binascii.unhexlify(tx), i, script, sigs))
|
|
|
|
# Not pushing empty elements on the top of the stack if passing no
|
|
# script (in case of bare multisig inputs there is no script)
|
|
script_blob = [] if script.__len__() == 0 else [script]
|
|
|
|
txobj = deserialize(tx)
|
|
txobj["ins"][i]["script"] = serialize_script([None]+sigs+script_blob)
|
|
return serialize(txobj)
|
|
|
|
|
|
def is_inp(arg):
|
|
return len(arg) > 64 or "output" in arg or "outpoint" in arg
|
|
|
|
|
|
def mktx(*args):
|
|
# [in0, in1...],[out0, out1...] or in0, in1 ... out0 out1 ...
|
|
ins, outs = [], []
|
|
for arg in args:
|
|
if 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": []}
|
|
for i in ins:
|
|
if isinstance(i, dict) and "outpoint" in i:
|
|
txobj["ins"].append(i)
|
|
else:
|
|
if isinstance(i, dict) and "output" in i:
|
|
i = i["output"]
|
|
txobj["ins"].append({
|
|
"outpoint": {"hash": i[:64], "index": int(i[65:])},
|
|
"script": "",
|
|
"sequence": 4294967295
|
|
})
|
|
for o in outs:
|
|
if isinstance(o, string_or_bytes_types):
|
|
addr = o[:o.find(':')]
|
|
val = int(o[o.find(':')+1:])
|
|
o = {}
|
|
if re.match('^[0-9a-fA-F]*$', addr):
|
|
o["script"] = addr
|
|
else:
|
|
o["address"] = addr
|
|
o["value"] = val
|
|
|
|
outobj = {}
|
|
if "address" in o:
|
|
outobj["script"] = address_to_script(o["address"])
|
|
elif "script" in o:
|
|
outobj["script"] = o["script"]
|
|
else:
|
|
raise Exception("Could not find 'address' or 'script' in output.")
|
|
outobj["value"] = o["value"]
|
|
txobj["outs"].append(outobj)
|
|
|
|
return serialize(txobj)
|
|
|
|
|
|
def select(unspent, value):
|
|
value = int(value)
|
|
high = [u for u in unspent if u["value"] >= value]
|
|
high.sort(key=lambda u: u["value"])
|
|
low = [u for u in unspent if u["value"] < value]
|
|
low.sort(key=lambda u: -u["value"])
|
|
if len(high):
|
|
return [high[0]]
|
|
i, tv = 0, 0
|
|
while tv < value and i < len(low):
|
|
tv += low[i]["value"]
|
|
i += 1
|
|
if tv < value:
|
|
raise Exception("Not enough funds")
|
|
return low[:i]
|
|
|
|
# Only takes inputs of the form { "output": blah, "value": foo }
|
|
|
|
|
|
def mksend(*args):
|
|
argz, change, fee = args[:-2], args[-2], int(args[-1])
|
|
ins, outs = [], []
|
|
for arg in argz:
|
|
if 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)
|
|
|
|
isum = sum([i["value"] for i in ins])
|
|
osum, outputs2 = 0, []
|
|
for o in outs:
|
|
if isinstance(o, string_types):
|
|
o2 = {
|
|
"address": o[:o.find(':')],
|
|
"value": int(o[o.find(':')+1:])
|
|
}
|
|
else:
|
|
o2 = o
|
|
outputs2.append(o2)
|
|
osum += o2["value"]
|
|
|
|
if isum < osum+fee:
|
|
raise Exception("Not enough money")
|
|
elif isum > osum+fee+5430:
|
|
outputs2 += [{"address": change, "value": isum-osum-fee}]
|
|
|
|
return mktx(ins, outputs2)
|