ttransaction.py - electrum-personal-server - Maximally lightweight electrum server for a single user
HTML git clone https://git.parazyd.org/electrum-personal-server
DIR Log
DIR Files
DIR Refs
DIR README
---
ttransaction.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)