electrum-personal-server

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

secp256k1_transaction.py (14427B)


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