electrum-personal-server

Maximally lightweight electrum server for a single user
git clone https://git.parazyd.org/electrum-personal-server
Log | Files | Refs | README

transaction.py (15760B)


      1 #!/usr/bin/python
      2 import binascii, re, json, copy, sys
      3 from electrumpersonalserver.bitcoin.main import *
      4 from _functools import reduce
      5 
      6 ### Hex to bin converter and vice versa for objects
      7 
      8 
      9 def json_is_base(obj, base):
     10     if not is_python2 and isinstance(obj, bytes):
     11         return False
     12 
     13     alpha = get_code_string(base)
     14     if isinstance(obj, string_types):
     15         for i in range(len(obj)):
     16             if alpha.find(obj[i]) == -1:
     17                 return False
     18         return True
     19     elif isinstance(obj, int_types) or obj is None:
     20         return True
     21     elif isinstance(obj, list):
     22         for i in range(len(obj)):
     23             if not json_is_base(obj[i], base):
     24                 return False
     25         return True
     26     else:
     27         for x in obj:
     28             if not json_is_base(obj[x], base):
     29                 return False
     30         return True
     31 
     32 
     33 def json_changebase(obj, changer):
     34     if isinstance(obj, string_or_bytes_types):
     35         return changer(obj)
     36     elif isinstance(obj, int_types) or obj is None:
     37         return obj
     38     elif isinstance(obj, list):
     39         return [json_changebase(x, changer) for x in obj]
     40     return dict((x, json_changebase(obj[x], changer)) for x in obj)
     41 
     42 # Transaction serialization and deserialization
     43 
     44 
     45 def deserialize(tx):
     46     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
     47         #tx = bytes(bytearray.fromhex(tx))
     48         return json_changebase(
     49             deserialize(binascii.unhexlify(tx)), lambda x: safe_hexlify(x))
     50     # http://stackoverflow.com/questions/4851463/python-closure-write-to-variable-in-parent-scope
     51     # Python's scoping rules are demented, requiring me to make pos an object
     52     # so that it is call-by-reference
     53     pos = [0]
     54 
     55     def read_as_int(bytez):
     56         pos[0] += bytez
     57         return decode(tx[pos[0] - bytez:pos[0]][::-1], 256)
     58 
     59     def read_var_int():
     60         pos[0] += 1
     61 
     62         val = from_byte_to_int(tx[pos[0] - 1])
     63         if val < 253:
     64             return val
     65         return read_as_int(pow(2, val - 252))
     66 
     67     def read_bytes(bytez):
     68         pos[0] += bytez
     69         return tx[pos[0] - bytez:pos[0]]
     70 
     71     def read_var_string():
     72         size = read_var_int()
     73         return read_bytes(size)
     74 
     75     obj = {"ins": [], "outs": []}
     76     obj["version"] = read_as_int(4)
     77     ins = read_var_int()
     78     for i in range(ins):
     79         obj["ins"].append({
     80             "outpoint": {
     81                 "hash": read_bytes(32)[::-1],
     82                 "index": read_as_int(4)
     83             },
     84             "script": read_var_string(),
     85             "sequence": read_as_int(4)
     86         })
     87     outs = read_var_int()
     88     for i in range(outs):
     89         obj["outs"].append({
     90             "value": read_as_int(8),
     91             "script": read_var_string()
     92         })
     93     obj["locktime"] = read_as_int(4)
     94     return obj
     95 
     96 
     97 def serialize(txobj):
     98     #if isinstance(txobj, bytes):
     99     #    txobj = bytes_to_hex_string(txobj)
    100     o = []
    101     if json_is_base(txobj, 16):
    102         json_changedbase = json_changebase(txobj,
    103                                            lambda x: binascii.unhexlify(x))
    104         hexlified = safe_hexlify(serialize(json_changedbase))
    105         return hexlified
    106     o.append(encode(txobj["version"], 256, 4)[::-1])
    107     o.append(num_to_var_int(len(txobj["ins"])))
    108     for inp in txobj["ins"]:
    109         o.append(inp["outpoint"]["hash"][::-1])
    110         o.append(encode(inp["outpoint"]["index"], 256, 4)[::-1])
    111         o.append(num_to_var_int(len(inp["script"])) + (inp["script"] if inp[
    112             "script"] or is_python2 else bytes()))
    113         o.append(encode(inp["sequence"], 256, 4)[::-1])
    114     o.append(num_to_var_int(len(txobj["outs"])))
    115     for out in txobj["outs"]:
    116         o.append(encode(out["value"], 256, 8)[::-1])
    117         o.append(num_to_var_int(len(out["script"])) + out["script"])
    118     o.append(encode(txobj["locktime"], 256, 4)[::-1])
    119 
    120     return ''.join(o) if is_python2 else reduce(lambda x, y: x + y, o, bytes())
    121 
    122 # Hashing transactions for signing
    123 
    124 SIGHASH_ALL = 1
    125 SIGHASH_NONE = 2
    126 SIGHASH_SINGLE = 3
    127 # this works like SIGHASH_ANYONECANPAY | SIGHASH_ALL, might as well make it explicit while
    128 # we fix the constant
    129 SIGHASH_ANYONECANPAY = 0x81
    130 
    131 
    132 def signature_form(tx, i, script, hashcode=SIGHASH_ALL):
    133     i, hashcode = int(i), int(hashcode)
    134     if isinstance(tx, string_or_bytes_types):
    135         return serialize(signature_form(deserialize(tx), i, script, hashcode))
    136     newtx = copy.deepcopy(tx)
    137     for inp in newtx["ins"]:
    138         inp["script"] = ""
    139     newtx["ins"][i]["script"] = script
    140     if hashcode == SIGHASH_NONE:
    141         newtx["outs"] = []
    142     elif hashcode == SIGHASH_SINGLE:
    143         newtx["outs"] = newtx["outs"][:len(newtx["ins"])]
    144         for out in range(len(newtx["ins"]) - 1):
    145             out.value = 2**64 - 1
    146             out.script = ""
    147     elif hashcode == SIGHASH_ANYONECANPAY:
    148         newtx["ins"] = [newtx["ins"][i]]
    149     else:
    150         pass
    151     return newtx
    152 
    153 # Making the actual signatures
    154 
    155 
    156 def der_encode_sig(v, r, s):
    157     """Takes (vbyte, r, s) as ints and returns hex der encode sig"""
    158     #See https://github.com/vbuterin/pybitcointools/issues/89
    159     #See https://github.com/simcity4242/pybitcointools/
    160     s = N - s if s > N // 2 else s  # BIP62 low s
    161     b1, b2 = encode(r, 256), encode(s, 256)
    162     if bytearray(b1)[
    163             0] & 0x80:  # add null bytes if leading byte interpreted as negative
    164         b1 = b'\x00' + b1
    165     if bytearray(b2)[0] & 0x80:
    166         b2 = b'\x00' + b2
    167     left = b'\x02' + encode(len(b1), 256, 1) + b1
    168     right = b'\x02' + encode(len(b2), 256, 1) + b2
    169     return safe_hexlify(b'\x30' + encode(
    170         len(left + right), 256, 1) + left + right)
    171 
    172 
    173 def der_decode_sig(sig):
    174     leftlen = decode(sig[6:8], 16) * 2
    175     left = sig[8:8 + leftlen]
    176     rightlen = decode(sig[10 + leftlen:12 + leftlen], 16) * 2
    177     right = sig[12 + leftlen:12 + leftlen + rightlen]
    178     return (None, decode(left, 16), decode(right, 16))
    179 
    180 
    181 def txhash(tx, hashcode=None):
    182     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
    183         tx = changebase(tx, 16, 256)
    184     if hashcode:
    185         return dbl_sha256(from_string_to_bytes(tx) + encode(
    186             int(hashcode), 256, 4)[::-1])
    187     else:
    188         return safe_hexlify(bin_dbl_sha256(tx)[::-1])
    189 
    190 
    191 def bin_txhash(tx, hashcode=None):
    192     return binascii.unhexlify(txhash(tx, hashcode))
    193 
    194 
    195 def ecdsa_tx_sign(tx, priv, hashcode=SIGHASH_ALL):
    196     rawsig = ecdsa_raw_sign(bin_txhash(tx, hashcode), priv)
    197     return der_encode_sig(*rawsig) + encode(hashcode, 16, 2)
    198 
    199 
    200 def ecdsa_tx_verify(tx, sig, pub, hashcode=SIGHASH_ALL):
    201     return ecdsa_raw_verify(bin_txhash(tx, hashcode), der_decode_sig(sig), pub)
    202 
    203 # Scripts
    204 
    205 def mk_pubkey_script(addr):
    206     # Keep the auxiliary functions around for altcoins' sake
    207     return '76a914' + b58check_to_hex(addr) + '88ac'
    208 
    209 
    210 def mk_scripthash_script(addr):
    211     return 'a914' + b58check_to_hex(addr) + '87'
    212 
    213 # Address representation to output script
    214 
    215 
    216 def address_to_script(addr):
    217     if addr[0] == '3' or addr[0] == '2':
    218         return mk_scripthash_script(addr)
    219     else:
    220         return mk_pubkey_script(addr)
    221 
    222 # Output script to address representation
    223 
    224 
    225 def script_to_address(script, vbyte=0):
    226     if re.match('^[0-9a-fA-F]*$', script):
    227         script = binascii.unhexlify(script)
    228     if script[:3] == b'\x76\xa9\x14' and script[-2:] == b'\x88\xac' and len(
    229             script) == 25:
    230         return bin_to_b58check(script[3:-2], vbyte)  # pubkey hash addresses
    231     else:
    232         if vbyte in [111, 196]:
    233             # Testnet
    234             scripthash_byte = 196
    235         else:
    236             scripthash_byte = 5
    237         # BIP0016 scripthash addresses
    238         return bin_to_b58check(script[2:-1], scripthash_byte)
    239 
    240 
    241 def p2sh_scriptaddr(script, magicbyte=5):
    242     if re.match('^[0-9a-fA-F]*$', script):
    243         script = binascii.unhexlify(script)
    244     return hex_to_b58check(hash160(script), magicbyte)
    245 
    246 
    247 scriptaddr = p2sh_scriptaddr
    248 
    249 
    250 def deserialize_script(script):
    251     if isinstance(script, str) and re.match('^[0-9a-fA-F]*$', script):
    252         return json_changebase(
    253             deserialize_script(binascii.unhexlify(script)),
    254             lambda x: safe_hexlify(x))
    255     out, pos = [], 0
    256     while pos < len(script):
    257         code = from_byte_to_int(script[pos])
    258         if code == 0:
    259             out.append(None)
    260             pos += 1
    261         elif code <= 75:
    262             out.append(script[pos + 1:pos + 1 + code])
    263             pos += 1 + code
    264         elif code <= 78:
    265             szsz = pow(2, code - 76)
    266             sz = decode(script[pos + szsz:pos:-1], 256)
    267             out.append(script[pos + 1 + szsz:pos + 1 + szsz + sz])
    268             pos += 1 + szsz + sz
    269         elif code <= 96:
    270             out.append(code - 80)
    271             pos += 1
    272         else:
    273             out.append(code)
    274             pos += 1
    275     return out
    276 
    277 
    278 def serialize_script_unit(unit):
    279     if isinstance(unit, int):
    280         if unit < 16:
    281             return from_int_to_byte(unit + 80)
    282         else:
    283             return bytes([unit])
    284     elif unit is None:
    285         return b'\x00'
    286     else:
    287         if len(unit) <= 75:
    288             return from_int_to_byte(len(unit)) + unit
    289         elif len(unit) < 256:
    290             return from_int_to_byte(76) + from_int_to_byte(len(unit)) + unit
    291         elif len(unit) < 65536:
    292             return from_int_to_byte(77) + encode(len(unit), 256, 2)[::-1] + unit
    293         else:
    294             return from_int_to_byte(78) + encode(len(unit), 256, 4)[::-1] + unit
    295 
    296 
    297 if is_python2:
    298 
    299     def serialize_script(script):
    300         if json_is_base(script, 16):
    301             return binascii.hexlify(serialize_script(json_changebase(
    302                 script, lambda x: binascii.unhexlify(x))))
    303         return ''.join(map(serialize_script_unit, script))
    304 else:
    305 
    306     def serialize_script(script):
    307         if json_is_base(script, 16):
    308             return safe_hexlify(serialize_script(json_changebase(
    309                 script, lambda x: binascii.unhexlify(x))))
    310 
    311         result = bytes()
    312         for b in map(serialize_script_unit, script):
    313             result += b if isinstance(b, bytes) else bytes(b, 'utf-8')
    314         return result
    315 
    316 
    317 def mk_multisig_script(*args):  # [pubs],k or pub1,pub2...pub[n],k
    318     if isinstance(args[0], list):
    319         pubs, k = args[0], int(args[1])
    320     else:
    321         pubs = list(filter(lambda x: len(str(x)) >= 32, args))
    322         k = int(args[len(pubs)])
    323     return serialize_script([k] + pubs + [len(pubs)]) + 'ae'
    324 
    325 # Signing and verifying
    326 
    327 
    328 def verify_tx_input(tx, i, script, sig, pub):
    329     if re.match('^[0-9a-fA-F]*$', tx):
    330         tx = binascii.unhexlify(tx)
    331     if re.match('^[0-9a-fA-F]*$', script):
    332         script = binascii.unhexlify(script)
    333     if not re.match('^[0-9a-fA-F]*$', sig):
    334         sig = safe_hexlify(sig)
    335     hashcode = decode(sig[-2:], 16)
    336     modtx = signature_form(tx, int(i), script, hashcode)
    337     return ecdsa_tx_verify(modtx, sig, pub, hashcode)
    338 
    339 
    340 def sign(tx, i, priv, hashcode=SIGHASH_ALL):
    341     i = int(i)
    342     if (not is_python2 and isinstance(re, bytes)) or not re.match(
    343             '^[0-9a-fA-F]*$', tx):
    344         return binascii.unhexlify(sign(safe_hexlify(tx), i, priv))
    345     if len(priv) <= 33:
    346         priv = safe_hexlify(priv)
    347     pub = privkey_to_pubkey(priv)
    348     address = pubkey_to_address(pub)
    349     signing_tx = signature_form(tx, i, mk_pubkey_script(address), hashcode)
    350     sig = ecdsa_tx_sign(signing_tx, priv, hashcode)
    351     txobj = deserialize(tx)
    352     txobj["ins"][i]["script"] = serialize_script([sig, pub])
    353     return serialize(txobj)
    354 
    355 
    356 def signall(tx, priv):
    357     # if priv is a dictionary, assume format is
    358     # { 'txinhash:txinidx' : privkey }
    359     if isinstance(priv, dict):
    360         for e, i in enumerate(deserialize(tx)["ins"]):
    361             k = priv["%s:%d" % (i["outpoint"]["hash"], i["outpoint"]["index"])]
    362             tx = sign(tx, e, k)
    363     else:
    364         for i in range(len(deserialize(tx)["ins"])):
    365             tx = sign(tx, i, priv)
    366     return tx
    367 
    368 
    369 def multisign(tx, i, script, pk, hashcode=SIGHASH_ALL):
    370     if re.match('^[0-9a-fA-F]*$', tx):
    371         tx = binascii.unhexlify(tx)
    372     if re.match('^[0-9a-fA-F]*$', script):
    373         script = binascii.unhexlify(script)
    374     modtx = signature_form(tx, i, script, hashcode)
    375     return ecdsa_tx_sign(modtx, pk, hashcode)
    376 
    377 
    378 def apply_multisignatures(*args):
    379     # tx,i,script,sigs OR tx,i,script,sig1,sig2...,sig[n]
    380     tx, i, script = args[0], int(args[1]), args[2]
    381     sigs = args[3] if isinstance(args[3], list) else list(args[3:])
    382 
    383     if isinstance(script, str) and re.match('^[0-9a-fA-F]*$', script):
    384         script = binascii.unhexlify(script)
    385     sigs = [binascii.unhexlify(x) if x[:2] == '30' else x for x in sigs]
    386     if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
    387         return safe_hexlify(apply_multisignatures(
    388             binascii.unhexlify(tx), i, script, sigs))
    389 
    390     txobj = deserialize(tx)
    391     txobj["ins"][i]["script"] = serialize_script([None] + sigs + [script])
    392     return serialize(txobj)
    393 
    394 
    395 def is_inp(arg):
    396     return len(arg) > 64 or "output" in arg or "outpoint" in arg
    397 
    398 
    399 def mktx(*args):
    400     # [in0, in1...],[out0, out1...] or in0, in1 ... out0 out1 ...
    401     ins, outs = [], []
    402     for arg in args:
    403         if isinstance(arg, list):
    404             for a in arg:
    405                 (ins if is_inp(a) else outs).append(a)
    406         else:
    407             (ins if is_inp(arg) else outs).append(arg)
    408 
    409     txobj = {"locktime": 0, "version": 1, "ins": [], "outs": []}
    410     for i in ins:
    411         if isinstance(i, dict) and "outpoint" in i:
    412             txobj["ins"].append(i)
    413         else:
    414             if isinstance(i, dict) and "output" in i:
    415                 i = i["output"]
    416             txobj["ins"].append({
    417                 "outpoint": {"hash": i[:64],
    418                              "index": int(i[65:])},
    419                 "script": "",
    420                 "sequence": 4294967295
    421             })
    422     for o in outs:
    423         if isinstance(o, string_or_bytes_types):
    424             addr = o[:o.find(':')]
    425             val = int(o[o.find(':') + 1:])
    426             o = {}
    427             if re.match('^[0-9a-fA-F]*$', addr):
    428                 o["script"] = addr
    429             else:
    430                 o["address"] = addr
    431             o["value"] = val
    432 
    433         outobj = {}
    434         if "address" in o:
    435             outobj["script"] = address_to_script(o["address"])
    436         elif "script" in o:
    437             outobj["script"] = o["script"]
    438         else:
    439             raise Exception("Could not find 'address' or 'script' in output.")
    440         outobj["value"] = o["value"]
    441         txobj["outs"].append(outobj)
    442 
    443     return serialize(txobj)
    444 
    445 
    446 def select(unspent, value):
    447     value = int(value)
    448     high = [u for u in unspent if u["value"] >= value]
    449     high.sort(key=lambda u: u["value"])
    450     low = [u for u in unspent if u["value"] < value]
    451     low.sort(key=lambda u: -u["value"])
    452     if len(high):
    453         return [high[0]]
    454     i, tv = 0, 0
    455     while tv < value and i < len(low):
    456         tv += low[i]["value"]
    457         i += 1
    458     if tv < value:
    459         raise Exception("Not enough funds")
    460     return low[:i]
    461 
    462 # Only takes inputs of the form { "output": blah, "value": foo }
    463 
    464 
    465 def mksend(*args):
    466     argz, change, fee = args[:-2], args[-2], int(args[-1])
    467     ins, outs = [], []
    468     for arg in argz:
    469         if isinstance(arg, list):
    470             for a in arg:
    471                 (ins if is_inp(a) else outs).append(a)
    472         else:
    473             (ins if is_inp(arg) else outs).append(arg)
    474 
    475     isum = sum([i["value"] for i in ins])
    476     osum, outputs2 = 0, []
    477     for o in outs:
    478         if isinstance(o, string_types):
    479             o2 = {"address": o[:o.find(':')], "value": int(o[o.find(':') + 1:])}
    480         else:
    481             o2 = o
    482         outputs2.append(o2)
    483         osum += o2["value"]
    484 
    485     if isum < osum + fee:
    486         raise Exception("Not enough money")
    487     elif isum > osum + fee + 5430:
    488         outputs2 += [{"address": change, "value": isum - osum - fee}]
    489 
    490     return mktx(ins, outputs2)