From a64e094b026a55ca6a7cb1eebef765b7f4686ea0 Mon Sep 17 00:00:00 2001 From: Steve132 Date: Tue, 24 Nov 2015 13:37:24 -0500 Subject: [PATCH 01/14] Begin making class-organization around blockchain networks --- bitcoin/__init__.py | 1 + bitcoin/bci.py | 172 +++++++++++++++++++++------------------ bitcoin/deterministic.py | 3 + 3 files changed, 97 insertions(+), 79 deletions(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index a772073f..7d44152b 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -7,3 +7,4 @@ from bitcoin.composite import * from bitcoin.stealth import * from bitcoin.blocks import * +from bitcoin.mnemonic import * \ No newline at end of file diff --git a/bitcoin/bci.py b/bitcoin/bci.py index 1a6c7337..44d36dbc 100644 --- a/bitcoin/bci.py +++ b/bitcoin/bci.py @@ -40,89 +40,103 @@ def parse_addr_args(*args): return network, addr_args -# Gets the unspent outputs of one or more addresses -def bci_unspent(*args): - network, addrs = parse_addr_args(*args) - u = [] - for a in addrs: - try: - data = make_request('https://blockchain.info/unspent?active='+a) - except Exception as e: - if str(e) == 'No free outputs to spend': - continue - else: - raise Exception(e) - try: - jsonobj = json.loads(data.decode("utf-8")) - for o in jsonobj["unspent_outputs"]: - h = o['tx_hash'].decode('hex')[::-1].encode('hex') - u.append({ - "output": h+':'+str(o['tx_output_n']), - "value": o['value'] - }) - except: - raise Exception("Failed to decode data: "+data) - return u - +class BlockchainInterface(object): + pass + +class BlockchainInfo(BlockchainInterface): + @classmethod + def unspent(cls,*args): + network, addrs = parse_addr_args(*args) + u = [] + for a in addrs: + try: + data = make_request('https://blockchain.info/unspent?active='+a) + except Exception as e: + if str(e) == 'No free outputs to spend': + continue + else: + raise Exception(e) + try: + jsonobj = json.loads(data.decode("utf-8")) + for o in jsonobj["unspent_outputs"]: + h = o['tx_hash'].decode('hex')[::-1].encode('hex') + u.append({ + "output": h+':'+str(o['tx_output_n']), + "value": o['value'] + }) + except: + raise Exception("Failed to decode data: "+data) + return u +def bci_unspent(*args): + return BlockchainInfo.unspent(*args) + +class Blockr(BlockchainInterface): + @classmethod + def unspent(cls,*args): + # Valid input formats: blockr_unspent([addr1, addr2,addr3]) + # blockr_unspent(addr1, addr2, addr3) + # blockr_unspent([addr1, addr2, addr3], network) + # blockr_unspent(addr1, addr2, addr3, network) + # Where network is 'btc' or 'testnet' + network, addr_args = parse_addr_args(*args) + + if network == 'testnet': + blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' + elif network == 'btc': + blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' + else: + raise Exception( + 'Unsupported network {0} for blockr_unspent'.format(network)) + + if len(addr_args) == 0: + return [] + elif isinstance(addr_args[0], list): + addrs = addr_args[0] + else: + addrs = addr_args + res = make_request(blockr_url+','.join(addrs)) + data = json.loads(res.decode("utf-8"))['data'] + o = [] + if 'unspent' in data: + data = [data] + for dat in data: + for u in dat['unspent']: + o.append({ + "output": u['tx']+':'+str(u['n']), + "value": int(u['amount'].replace('.', '')) + }) + return o + def blockr_unspent(*args): - # Valid input formats: blockr_unspent([addr1, addr2,addr3]) - # blockr_unspent(addr1, addr2, addr3) - # blockr_unspent([addr1, addr2, addr3], network) - # blockr_unspent(addr1, addr2, addr3, network) - # Where network is 'btc' or 'testnet' - network, addr_args = parse_addr_args(*args) - - if network == 'testnet': - blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' - elif network == 'btc': - blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' - else: - raise Exception( - 'Unsupported network {0} for blockr_unspent'.format(network)) - - if len(addr_args) == 0: - return [] - elif isinstance(addr_args[0], list): - addrs = addr_args[0] - else: - addrs = addr_args - res = make_request(blockr_url+','.join(addrs)) - data = json.loads(res.decode("utf-8"))['data'] - o = [] - if 'unspent' in data: - data = [data] - for dat in data: - for u in dat['unspent']: - o.append({ - "output": u['tx']+':'+str(u['n']), - "value": int(u['amount'].replace('.', '')) - }) - return o - + return Blockr.unspent(*args) + +class HelloBlock(BlockchainInterface): + @classmethod + def unspent(*args): + network, addrs = parse_addr_args(*args) + if network == 'testnet': + url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' + elif network == 'btc': + url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' + o = [] + for addr in addrs: + for offset in xrange(0, 10**9, 500): + res = make_request(url % (addr, offset)) + data = json.loads(res.decode("utf-8"))["data"] + if not len(data["unspents"]): + break + elif offset: + sys.stderr.write("Getting more unspents: %d\n" % offset) + for dat in data["unspents"]: + o.append({ + "output": dat["txHash"]+':'+str(dat["index"]), + "value": dat["value"], + }) + return o def helloblock_unspent(*args): - network, addrs = parse_addr_args(*args) - if network == 'testnet': - url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' - elif network == 'btc': - url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' - o = [] - for addr in addrs: - for offset in xrange(0, 10**9, 500): - res = make_request(url % (addr, offset)) - data = json.loads(res.decode("utf-8"))["data"] - if not len(data["unspents"]): - break - elif offset: - sys.stderr.write("Getting more unspents: %d\n" % offset) - for dat in data["unspents"]: - o.append({ - "output": dat["txHash"]+':'+str(dat["index"]), - "value": dat["value"], - }) - return o - + return HelloBlock.unspent(*args) unspent_getters = { 'bci': bci_unspent, diff --git a/bitcoin/deterministic.py b/bitcoin/deterministic.py index 36b1b63e..6d038ccb 100644 --- a/bitcoin/deterministic.py +++ b/bitcoin/deterministic.py @@ -197,3 +197,6 @@ def bip32_descend(*args): for p in path: key = bip32_ckd(key, p) return bip32_extract_key(key) + +def bip32_harden(x): + return 2**31+x \ No newline at end of file From 26f3cda65e8a0ac195c75a82fce9a1425d51e02c Mon Sep 17 00:00:00 2001 From: Steve132 Date: Tue, 24 Nov 2015 13:54:30 -0500 Subject: [PATCH 02/14] Revert "Begin making class-organization around blockchain networks" This reverts commit a64e094b026a55ca6a7cb1eebef765b7f4686ea0. --- bitcoin/__init__.py | 1 - bitcoin/bci.py | 172 ++++++++++++++++++--------------------- bitcoin/deterministic.py | 3 - 3 files changed, 79 insertions(+), 97 deletions(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 7d44152b..a772073f 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -7,4 +7,3 @@ from bitcoin.composite import * from bitcoin.stealth import * from bitcoin.blocks import * -from bitcoin.mnemonic import * \ No newline at end of file diff --git a/bitcoin/bci.py b/bitcoin/bci.py index 44d36dbc..1a6c7337 100644 --- a/bitcoin/bci.py +++ b/bitcoin/bci.py @@ -40,103 +40,89 @@ def parse_addr_args(*args): return network, addr_args -class BlockchainInterface(object): - pass - -class BlockchainInfo(BlockchainInterface): - @classmethod - def unspent(cls,*args): - network, addrs = parse_addr_args(*args) - u = [] - for a in addrs: - try: - data = make_request('https://blockchain.info/unspent?active='+a) - except Exception as e: - if str(e) == 'No free outputs to spend': - continue - else: - raise Exception(e) - try: - jsonobj = json.loads(data.decode("utf-8")) - for o in jsonobj["unspent_outputs"]: - h = o['tx_hash'].decode('hex')[::-1].encode('hex') - u.append({ - "output": h+':'+str(o['tx_output_n']), - "value": o['value'] - }) - except: - raise Exception("Failed to decode data: "+data) - return u - +# Gets the unspent outputs of one or more addresses def bci_unspent(*args): - return BlockchainInfo.unspent(*args) - -class Blockr(BlockchainInterface): - @classmethod - def unspent(cls,*args): - # Valid input formats: blockr_unspent([addr1, addr2,addr3]) - # blockr_unspent(addr1, addr2, addr3) - # blockr_unspent([addr1, addr2, addr3], network) - # blockr_unspent(addr1, addr2, addr3, network) - # Where network is 'btc' or 'testnet' - network, addr_args = parse_addr_args(*args) - - if network == 'testnet': - blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' - elif network == 'btc': - blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' - else: - raise Exception( - 'Unsupported network {0} for blockr_unspent'.format(network)) - - if len(addr_args) == 0: - return [] - elif isinstance(addr_args[0], list): - addrs = addr_args[0] - else: - addrs = addr_args - res = make_request(blockr_url+','.join(addrs)) - data = json.loads(res.decode("utf-8"))['data'] - o = [] - if 'unspent' in data: - data = [data] - for dat in data: - for u in dat['unspent']: - o.append({ - "output": u['tx']+':'+str(u['n']), - "value": int(u['amount'].replace('.', '')) - }) - return o - + network, addrs = parse_addr_args(*args) + u = [] + for a in addrs: + try: + data = make_request('https://blockchain.info/unspent?active='+a) + except Exception as e: + if str(e) == 'No free outputs to spend': + continue + else: + raise Exception(e) + try: + jsonobj = json.loads(data.decode("utf-8")) + for o in jsonobj["unspent_outputs"]: + h = o['tx_hash'].decode('hex')[::-1].encode('hex') + u.append({ + "output": h+':'+str(o['tx_output_n']), + "value": o['value'] + }) + except: + raise Exception("Failed to decode data: "+data) + return u + + def blockr_unspent(*args): - return Blockr.unspent(*args) - -class HelloBlock(BlockchainInterface): - @classmethod - def unspent(*args): - network, addrs = parse_addr_args(*args) - if network == 'testnet': - url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' - elif network == 'btc': - url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' - o = [] - for addr in addrs: - for offset in xrange(0, 10**9, 500): - res = make_request(url % (addr, offset)) - data = json.loads(res.decode("utf-8"))["data"] - if not len(data["unspents"]): - break - elif offset: - sys.stderr.write("Getting more unspents: %d\n" % offset) - for dat in data["unspents"]: - o.append({ - "output": dat["txHash"]+':'+str(dat["index"]), - "value": dat["value"], - }) - return o + # Valid input formats: blockr_unspent([addr1, addr2,addr3]) + # blockr_unspent(addr1, addr2, addr3) + # blockr_unspent([addr1, addr2, addr3], network) + # blockr_unspent(addr1, addr2, addr3, network) + # Where network is 'btc' or 'testnet' + network, addr_args = parse_addr_args(*args) + + if network == 'testnet': + blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' + elif network == 'btc': + blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' + else: + raise Exception( + 'Unsupported network {0} for blockr_unspent'.format(network)) + + if len(addr_args) == 0: + return [] + elif isinstance(addr_args[0], list): + addrs = addr_args[0] + else: + addrs = addr_args + res = make_request(blockr_url+','.join(addrs)) + data = json.loads(res.decode("utf-8"))['data'] + o = [] + if 'unspent' in data: + data = [data] + for dat in data: + for u in dat['unspent']: + o.append({ + "output": u['tx']+':'+str(u['n']), + "value": int(u['amount'].replace('.', '')) + }) + return o + def helloblock_unspent(*args): - return HelloBlock.unspent(*args) + network, addrs = parse_addr_args(*args) + if network == 'testnet': + url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' + elif network == 'btc': + url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' + o = [] + for addr in addrs: + for offset in xrange(0, 10**9, 500): + res = make_request(url % (addr, offset)) + data = json.loads(res.decode("utf-8"))["data"] + if not len(data["unspents"]): + break + elif offset: + sys.stderr.write("Getting more unspents: %d\n" % offset) + for dat in data["unspents"]: + o.append({ + "output": dat["txHash"]+':'+str(dat["index"]), + "value": dat["value"], + }) + return o + unspent_getters = { 'bci': bci_unspent, diff --git a/bitcoin/deterministic.py b/bitcoin/deterministic.py index 6d038ccb..36b1b63e 100644 --- a/bitcoin/deterministic.py +++ b/bitcoin/deterministic.py @@ -197,6 +197,3 @@ def bip32_descend(*args): for p in path: key = bip32_ckd(key, p) return bip32_extract_key(key) - -def bip32_harden(x): - return 2**31+x \ No newline at end of file From b41b22483e23853e7ab59c568c132fd84c8a0483 Mon Sep 17 00:00:00 2001 From: Steve132 Date: Tue, 24 Nov 2015 13:58:03 -0500 Subject: [PATCH 03/14] Add mnemonic to __init__ and also add simple harden method for bip32 support --- bitcoin/__init__.py | 1 + bitcoin/deterministic.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index a772073f..7d44152b 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -7,3 +7,4 @@ from bitcoin.composite import * from bitcoin.stealth import * from bitcoin.blocks import * +from bitcoin.mnemonic import * \ No newline at end of file diff --git a/bitcoin/deterministic.py b/bitcoin/deterministic.py index 36b1b63e..3d0252e0 100644 --- a/bitcoin/deterministic.py +++ b/bitcoin/deterministic.py @@ -197,3 +197,7 @@ def bip32_descend(*args): for p in path: key = bip32_ckd(key, p) return bip32_extract_key(key) + +#explicit harden method. +def bip32_harden(x): + return (1 << 31) + x \ No newline at end of file From 16432dc2715663d96f32f0eacf2dabf3234c2d74 Mon Sep 17 00:00:00 2001 From: Steve132 Date: Wed, 25 Nov 2015 16:59:53 -0500 Subject: [PATCH 04/14] Refactor bci to classmethods --- bitcoin/bci.py | 473 +++++++++++++++++++++++++--------------------- hd_brainwallet.py | 40 ++++ testhd.py | 15 ++ 3 files changed, 311 insertions(+), 217 deletions(-) create mode 100644 hd_brainwallet.py create mode 100644 testhd.py diff --git a/bitcoin/bci.py b/bitcoin/bci.py index 31f09bc0..593e6105 100644 --- a/bitcoin/bci.py +++ b/bitcoin/bci.py @@ -95,90 +95,223 @@ def parse_addr_args(*args): network = set_network(addr_args) return addr_args, network # note params are "reversed" now +class BlockchainInterface(object): + pass + +class BlockchainInfo(BlockchainInterface): + @classmethod + def unspent(cls,*args): + addrs, network = parse_addr_args(*args) + u = [] + for a in addrs: + try: + data = make_request('https://blockchain.info/unspent?active='+a) + except Exception as e: + if str(e) == 'No free outputs to spend': + continue + else: + raise Exception(e) + try: + jsonobj = json.loads(data.decode("utf-8")) + for o in jsonobj["unspent_outputs"]: + h = o['tx_hash'].decode('hex')[::-1].encode('hex') + u.append({ + "output": h+':'+str(o['tx_output_n']), + "value": o['value'] + }) + except: + raise Exception("Failed to decode data: "+data) + return u + + # Pushes a transaction to the network using https://blockchain.info/pushtx + @classmethod + def pushtx(cls,tx,network='btc'): + if(network != 'btc'): + raise Exception('Unsupported network {0} for BlockchainInfo'.format(network)) + + if not re.match('^[0-9a-fA-F]*$', tx): + tx = tx.encode('hex') + return make_request('https://blockchain.info/pushtx', 'tx='+tx) + + @classmethod + def fetchtx(cls,txhash,network='btc'): + if(network != 'btc'): + raise Exception('Unsupported network {0} for BlockchainInfo'.format(network)) + if isinstance(txhash, list): + return [cls.fetchtx(h,network) for h in txhash] + if not re.match('^[0-9a-fA-F]*$', txhash): + txhash = txhash.encode('hex') + data = make_request('https://blockchain.info/rawtx/'+txhash+'?format=hex') + return data + +class Blockr(BlockchainInterface): + @classmethod + def unspent(cls,*args): + network, addr_args = parse_addr_args(*args) + + if network == 'testnet': + blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' + elif network == 'btc': + blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' + else: + raise Exception( + 'Unsupported network {0} for blockr_unspent'.format(network)) -# Gets the unspent outputs of one or more addresses -def bci_unspent(*args): - addrs, network = parse_addr_args(*args) - u = [] - for a in addrs: - try: - data = make_request('https://blockchain.info/unspent?active='+a) - except Exception as e: - if str(e) == 'No free outputs to spend': - continue - else: - raise Exception(e) - try: - jsonobj = json.loads(data.decode("utf-8")) - for o in jsonobj["unspent_outputs"]: - h = o['tx_hash'].decode('hex')[::-1].encode('hex') - u.append({ - "output": h+':'+str(o['tx_output_n']), - "value": o['value'] + if len(addr_args) == 0: + return [] + elif isinstance(addr_args[0], list): + addrs = addr_args[0] + else: + addrs = addr_args + res = make_request(blockr_url+','.join(addrs)) + data = json.loads(res.decode("utf-8"))['data'] + o = [] + if 'unspent' in data: + data = [data] + for dat in data: + for u in dat['unspent']: + o.append({ + "output": u['tx']+':'+str(u['n']), + "value": int(u['amount'].replace('.', '')) }) - except: - raise Exception("Failed to decode data: "+data) - return u - - -def blockr_unspent(*args): - # Valid input formats: blockr_unspent([addr1, addr2,addr3]) - # blockr_unspent(addr1, addr2, addr3) - # blockr_unspent([addr1, addr2, addr3], network) - # blockr_unspent(addr1, addr2, addr3, network) - # Where network is 'btc' or 'testnet' - network, addr_args = parse_addr_args(*args) - - if network == 'testnet': - blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' - elif network == 'btc': - blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' - else: - raise Exception( - 'Unsupported network {0} for blockr_unspent'.format(network)) - - if len(addr_args) == 0: - return [] - elif isinstance(addr_args[0], list): - addrs = addr_args[0] - else: - addrs = addr_args - res = make_request(blockr_url+','.join(addrs)) - data = json.loads(res.decode("utf-8"))['data'] - o = [] - if 'unspent' in data: - data = [data] - for dat in data: - for u in dat['unspent']: - o.append({ - "output": u['tx']+':'+str(u['n']), - "value": int(u['amount'].replace('.', '')) + return o + + @classmethod + def pushtx(tx,network='btc'): + if network == 'testnet': + blockr_url = 'http://tbtc.blockr.io/api/v1/tx/push' + elif network == 'btc': + blockr_url = 'http://btc.blockr.io/api/v1/tx/push' + else: + raise Exception( + 'Unsupported network {0} for blockr_pushtx'.format(network)) + + if not re.match('^[0-9a-fA-F]*$', tx): + tx = tx.encode('hex') + return make_request(blockr_url, '{"hex":"%s"}' % tx) + + @classmethod + def blockr_fetchtx(cls,txhash, network='btc'): + if network == 'testnet': + blockr_url = 'http://tbtc.blockr.io/api/v1/tx/raw/' + elif network == 'btc': + blockr_url = 'http://btc.blockr.io/api/v1/tx/raw/' + else: + raise Exception( + 'Unsupported network {0} for blockr_fetchtx'.format(network)) + if isinstance(txhash, list): + txhash = ','.join([x.encode('hex') if not re.match('^[0-9a-fA-F]*$', x) + else x for x in txhash]) + jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) + return [d['tx']['hex'] for d in jsondata['data']] + else: + if not re.match('^[0-9a-fA-F]*$', txhash): + txhash = txhash.encode('hex') + jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) + return jsondata['data']['tx']['hex'] + +class HelloBlock(BlockchainInterface): + @classmethod + def unspent(cls,*args): + addrs, network = parse_addr_args(*args) + if network == 'testnet': + url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' + elif network == 'btc': + url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' + o = [] + for addr in addrs: + for offset in xrange(0, 10**9, 500): + res = make_request(url % (addr, offset)) + data = json.loads(res.decode("utf-8"))["data"] + if not len(data["unspents"]): + break + elif offset: + sys.stderr.write("Getting more unspents: %d\n" % offset) + for dat in data["unspents"]: + o.append({ + "output": dat["txHash"]+':'+str(dat["index"]), + "value": dat["value"], + }) + return o + + @classmethod + def pushtx(cls,tx,network='btc'): + if(network == 'testnet'): + url='https://testnet.helloblock.io/v1/transactions' + else: + url='https://mainnet.helloblock.io/v1/transactions' + + if not re.match('^[0-9a-fA-F]*$', tx): + tx = tx.encode('hex') + return make_request(url,'rawTxHex='+tx) + + @classmethod + def fetchtx(cls,txhash,network='btc'): + if isinstance(txhash, list): + return [helloblock_fetchtx(h) for h in txhash] + if not re.match('^[0-9a-fA-F]*$', txhash): + txhash = txhash.encode('hex') + if network == 'testnet': + url = 'https://testnet.helloblock.io/v1/transactions/' + elif network == 'btc': + url = 'https://mainnet.helloblock.io/v1/transactions/' + else: + raise Exception( + 'Unsupported network {0} for helloblock_fetchtx'.format(network)) + data = json.loads(make_request(url + txhash).decode("utf-8"))["data"]["transaction"] + o = { + "locktime": data["locktime"], + "version": data["version"], + "ins": [], + "outs": [] + } + for inp in data["inputs"]: + o["ins"].append({ + "script": inp["scriptSig"], + "outpoint": { + "index": inp["prevTxoutIndex"], + "hash": inp["prevTxHash"], + }, + "sequence": 4294967295 }) - return o - + for outp in data["outputs"]: + o["outs"].append({ + "value": outp["value"], + "script": outp["scriptPubKey"] + }) + from bitcoin.transaction import serialize + from bitcoin.transaction import txhash as TXHASH + tx = serialize(o) + assert TXHASH(tx) == txhash + return tx + +class Eligius(BlockchainInterface): + @classmethod + def pushtx(cls,tx,network='btc'): + if(network != 'btc'): + raise Exception( + 'Unsupported network {0} for Eligius.'.format(network)) + + if not re.match('^[0-9a-fA-F]*$', tx): + tx = tx.encode('hex') + s = make_request( + 'http://eligius.st/~wizkid057/newstats/pushtxn.php', + 'transaction='+tx+'&send=Push') + strings = re.findall('string[^"]*"[^"]*"', s) + for string in strings: + quote = re.findall('"[^"]*"', string)[0] + if len(quote) >= 5: + return quote[1:-1] +# Gets the unspent outputs of one or more addresses +def bci_unspent(*args): + return BlockchainInfo.unspent(*args) + +def blockr_unspent(*args): + return Blockr.unspent(args) + def helloblock_unspent(*args): - addrs, network = parse_addr_args(*args) - if network == 'testnet': - url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' - elif network == 'btc': - url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' - o = [] - for addr in addrs: - for offset in xrange(0, 10**9, 500): - res = make_request(url % (addr, offset)) - data = json.loads(res.decode("utf-8"))["data"] - if not len(data["unspents"]): - break - elif offset: - sys.stderr.write("Getting more unspents: %d\n" % offset) - for dat in data["unspents"]: - o.append({ - "output": dat["txHash"]+':'+str(dat["index"]), - "value": dat["value"], - }) - return o - + return HelloBlock.unspent(*args) unspent_getters = { 'bci': bci_unspent, @@ -186,11 +319,53 @@ def helloblock_unspent(*args): 'helloblock': helloblock_unspent } - def unspent(*args, **kwargs): f = unspent_getters.get(kwargs.get('source', ''), bci_unspent) return f(*args) + + # Pushes a transaction to the network using https://blockchain.info/pushtx +def bci_pushtx(tx,network='btc'): + return BlockchainInfo.pushtx(tx,network) + +def blockr_pushtx(tx, network='btc'): + return Blockr.pushtx(tx,network) + +def helloblock_pushtx(tx,network='btc'): + return HelloBlock.pushtx(tx,network) + +def eligius_pushtx(tx,network='btc'): + return Eligius.pushtx(tx,network) + +pushtx_getters = { + 'bci': bci_pushtx, + 'blockr': blockr_pushtx, + 'helloblock': helloblock_pushtx +} + +def pushtx(*args, **kwargs): + f = pushtx_getters.get(kwargs.get('source', ''), bci_pushtx) + return f(*args) + +# Gets a specific transaction +def bci_fetchtx(txhash,network='btc'): + return BlockchainInfo.fetchtx(txhash,network) + +def blockr_fetchtx(txhash, network='btc'): + return Blockr.fetchtx(txhash,network) + +def helloblock_fetchtx(txhash, network='btc'): + return HelloBlock.fetchtx(txhash,network) + + +fetchtx_getters = { + 'bci': bci_fetchtx, + 'blockr': blockr_fetchtx, + 'helloblock': helloblock_fetchtx +} +def fetchtx(*args, **kwargs): + f = fetchtx_getters.get(kwargs.get('source', ''), bci_fetchtx) + return f(*args) # Gets the transaction output history of a given set of addresses, # including whether or not they have been spent @@ -250,60 +425,7 @@ def history(*args): if outs.get(key): outs[key]["spend"] = tx["hash"]+':'+str(i) return [outs[k] for k in outs] - - -# Pushes a transaction to the network using https://blockchain.info/pushtx -def bci_pushtx(tx): - if not re.match('^[0-9a-fA-F]*$', tx): - tx = tx.encode('hex') - return make_request('https://blockchain.info/pushtx', 'tx='+tx) - - -def eligius_pushtx(tx): - if not re.match('^[0-9a-fA-F]*$', tx): - tx = tx.encode('hex') - s = make_request( - 'http://eligius.st/~wizkid057/newstats/pushtxn.php', - 'transaction='+tx+'&send=Push') - strings = re.findall('string[^"]*"[^"]*"', s) - for string in strings: - quote = re.findall('"[^"]*"', string)[0] - if len(quote) >= 5: - return quote[1:-1] - - -def blockr_pushtx(tx, network='btc'): - if network == 'testnet': - blockr_url = 'http://tbtc.blockr.io/api/v1/tx/push' - elif network == 'btc': - blockr_url = 'http://btc.blockr.io/api/v1/tx/push' - else: - raise Exception( - 'Unsupported network {0} for blockr_pushtx'.format(network)) - - if not re.match('^[0-9a-fA-F]*$', tx): - tx = tx.encode('hex') - return make_request(blockr_url, '{"hex":"%s"}' % tx) - - -def helloblock_pushtx(tx): - if not re.match('^[0-9a-fA-F]*$', tx): - tx = tx.encode('hex') - return make_request('https://mainnet.helloblock.io/v1/transactions', - 'rawTxHex='+tx) - -pushtx_getters = { - 'bci': bci_pushtx, - 'blockr': blockr_pushtx, - 'helloblock': helloblock_pushtx -} - - -def pushtx(*args, **kwargs): - f = pushtx_getters.get(kwargs.get('source', ''), bci_pushtx) - return f(*args) - - + def last_block_height(network='btc'): if network == 'testnet': data = make_request('http://tbtc.blockr.io/api/v1/block/info/last') @@ -314,89 +436,6 @@ def last_block_height(network='btc'): jsonobj = json.loads(data.decode("utf-8")) return jsonobj["height"] - -# Gets a specific transaction -def bci_fetchtx(txhash): - if isinstance(txhash, list): - return [bci_fetchtx(h) for h in txhash] - if not re.match('^[0-9a-fA-F]*$', txhash): - txhash = txhash.encode('hex') - data = make_request('https://blockchain.info/rawtx/'+txhash+'?format=hex') - return data - - -def blockr_fetchtx(txhash, network='btc'): - if network == 'testnet': - blockr_url = 'http://tbtc.blockr.io/api/v1/tx/raw/' - elif network == 'btc': - blockr_url = 'http://btc.blockr.io/api/v1/tx/raw/' - else: - raise Exception( - 'Unsupported network {0} for blockr_fetchtx'.format(network)) - if isinstance(txhash, list): - txhash = ','.join([x.encode('hex') if not re.match('^[0-9a-fA-F]*$', x) - else x for x in txhash]) - jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) - return [d['tx']['hex'] for d in jsondata['data']] - else: - if not re.match('^[0-9a-fA-F]*$', txhash): - txhash = txhash.encode('hex') - jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) - return jsondata['data']['tx']['hex'] - - -def helloblock_fetchtx(txhash, network='btc'): - if isinstance(txhash, list): - return [helloblock_fetchtx(h) for h in txhash] - if not re.match('^[0-9a-fA-F]*$', txhash): - txhash = txhash.encode('hex') - if network == 'testnet': - url = 'https://testnet.helloblock.io/v1/transactions/' - elif network == 'btc': - url = 'https://mainnet.helloblock.io/v1/transactions/' - else: - raise Exception( - 'Unsupported network {0} for helloblock_fetchtx'.format(network)) - data = json.loads(make_request(url + txhash).decode("utf-8"))["data"]["transaction"] - o = { - "locktime": data["locktime"], - "version": data["version"], - "ins": [], - "outs": [] - } - for inp in data["inputs"]: - o["ins"].append({ - "script": inp["scriptSig"], - "outpoint": { - "index": inp["prevTxoutIndex"], - "hash": inp["prevTxHash"], - }, - "sequence": 4294967295 - }) - for outp in data["outputs"]: - o["outs"].append({ - "value": outp["value"], - "script": outp["scriptPubKey"] - }) - from bitcoin.transaction import serialize - from bitcoin.transaction import txhash as TXHASH - tx = serialize(o) - assert TXHASH(tx) == txhash - return tx - - -fetchtx_getters = { - 'bci': bci_fetchtx, - 'blockr': blockr_fetchtx, - 'helloblock': helloblock_fetchtx -} - - -def fetchtx(*args, **kwargs): - f = fetchtx_getters.get(kwargs.get('source', ''), bci_fetchtx) - return f(*args) - - def firstbits(address): if len(address) >= 25: return make_request('https://blockchain.info/q/getfirstbits/'+address) diff --git a/hd_brainwallet.py b/hd_brainwallet.py new file mode 100644 index 00000000..bc79077d --- /dev/null +++ b/hd_brainwallet.py @@ -0,0 +1,40 @@ +import bitcoin +import sys +import argparse + + +is_offline = False +def test_offline(): + raw_input("Make sure you are offline, alone, and your screen is not visible. [OK]") + print("Testing if you are online...") + try: + urllib2.urlopen("https://google.com",timeout=2.0) + raw_input("You lied about being offline!") + return False + except: + return True + + +def offlineonly(f): + def wrapper(): + if(not is_offline): + q=raw_input('Warning! You are not in offline mode! You should immediately quit before executing this function! [Y/n]') + if(q.lower()!='n'): + quit() + return f() + return wrapper + +@offlineonly +def get_password(): + mnemonic=raw_input('Type your password mnemonic, one word at a time:\n') + return mnemonic + + +if __name__=="__main__": + + + + if(args.is_offline): + is_offline=test_offline() + + \ No newline at end of file diff --git a/testhd.py b/testhd.py new file mode 100644 index 00000000..9633acea --- /dev/null +++ b/testhd.py @@ -0,0 +1,15 @@ +import bitcoin +from bitcoin.deterministic import bip32_harden as h + +mnemonic='saddle observe obtain scare burger nerve electric alone minute east walnut motor omit coyote time' +seed=bitcoin.mnemonic_to_seed(mnemonic) +mpriv=bitcoin.bip32_master_key(seed) + +accountroot=mpriv +accountroot=bitcoin.bip32_ckd(accountroot,h(44)) +accountroot=bitcoin.bip32_ckd(accountroot,h(0)) +accountroot=bitcoin.bip32_ckd(accountroot,h(0)) + +for i in range(19): + dkey=bitcoin.bip32_descend(accountroot,0,i) + print(bitcoin.privtoaddr(dkey)) \ No newline at end of file From 5f12e81385685312415f59d29212a94995efcc67 Mon Sep 17 00:00:00 2001 From: Steve132 Date: Thu, 10 Dec 2015 17:59:26 -0500 Subject: [PATCH 05/14] Began commit --- bitcoin/bci.py | 402 ++++++++++++++++++++++++++++--------------------- 1 file changed, 234 insertions(+), 168 deletions(-) diff --git a/bitcoin/bci.py b/bitcoin/bci.py index 593e6105..7cdbca13 100644 --- a/bitcoin/bci.py +++ b/bitcoin/bci.py @@ -95,9 +95,29 @@ def parse_addr_args(*args): network = set_network(addr_args) return addr_args, network # note params are "reversed" now + +class _BlockchainInterfaceSet(object): + interfaces=[] + + def __getattr__(cls,key): + sort(cls.interfaces,key=lambda x: return x.priority) + for c in cls.interfaces: + if(hasattr(c,key) and c.valid): + return getattr(c,key) + class BlockchainInterface(object): - pass - + __metaclass__=_BlockchainInterfaceSet + + +_prioritycounter=0 +def blockchain_interface_impl(cls): + cls.valid=True + cls.priority=_prioritycounter + _prioritycounter+=1 + _BlockchainInterfaceSet.append(cls) + return cls + +@blockchain_interface_impl class BlockchainInfo(BlockchainInterface): @classmethod def unspent(cls,*args): @@ -143,7 +163,113 @@ def fetchtx(cls,txhash,network='btc'): txhash = txhash.encode('hex') data = make_request('https://blockchain.info/rawtx/'+txhash+'?format=hex') return data + + @classmethod + def history(cls,*args):# Valid input formats: history([addr1, addr2,addr3]) + # history(addr1, addr2, addr3) + if len(args) == 0: + return [] + elif isinstance(args[0], list): + addrs = args[0] + else: + addrs = args + + txs = [] + for addr in addrs: + offset = 0 + while 1: + gathered = False + while not gathered: + try: + data = make_request( + 'https://blockchain.info/address/%s?format=json&offset=%s' % + (addr, offset)) + gathered = True + except Exception as e: + try: + sys.stderr.write(e.read().strip()) + except: + sys.stderr.write(str(e)) + gathered = False + try: + jsonobj = json.loads(data.decode("utf-8")) + except: + raise Exception("Failed to decode data: "+data) + txs.extend(jsonobj["txs"]) + if len(jsonobj["txs"]) < 50: + break + offset += 50 + sys.stderr.write("Fetching more transactions... "+str(offset)+'\n') + outs = {} + for tx in txs: + for o in tx["out"]: + if o.get('addr', None) in addrs: + key = str(tx["tx_index"])+':'+str(o["n"]) + outs[key] = { + "address": o["addr"], + "value": o["value"], + "output": tx["hash"]+':'+str(o["n"]), + "block_height": tx.get("block_height", None) + } + for tx in txs: + for i, inp in enumerate(tx["inputs"]): + if "prev_out" in inp: + if inp["prev_out"].get("addr", None) in addrs: + key = str(inp["prev_out"]["tx_index"]) + \ + ':'+str(inp["prev_out"]["n"]) + if outs.get(key): + outs[key]["spend"] = tx["hash"]+':'+str(i) + return [outs[k] for k in outs] + + @classmethod + def firstbits(cls,address): + if len(address) >= 25: + return make_request('https://blockchain.info/q/getfirstbits/'+address) + else: + return make_request( + 'https://blockchain.info/q/resolvefirstbits/'+address) + @classmethod + def get_block_at_height(cls,height): + j = json.loads(make_request("https://blockchain.info/block-height/" + + str(height)+"?format=json").decode("utf-8")) + for b in j['blocks']: + if b['main_chain'] is True: + return b + raise Exception("Block at this height not found") + @classmethod + def _get_block(cls,inp): + if len(str(inp)) < 64: + return get_block_at_height(inp) + else: + return json.loads(make_request( + 'https://blockchain.info/rawblock/'+inp).decode("utf-8")) + + @classmethod + def get_block_header_data(cls,inp,network='btc'): + j = cls._get_block(inp) + return { + 'version': j['ver'], + 'hash': j['hash'], + 'prevhash': j['prev_block'], + 'timestamp': j['time'], + 'merkle_root': j['mrkl_root'], + 'bits': j['bits'], + 'nonce': j['nonce'], + } + + @classmethod + def get_txs_in_block(cls,inp): + j = cls._get_block(inp) + hashes = [t['hash'] for t in j['tx']] + return hashes + + @classmethod + def get_block_height(cls,txhash): + j = json.loads(make_request('https://blockchain.info/rawtx/'+txhash).decode("utf-8")) + return j['block_height'] + +@blockchain_interface_impl class Blockr(BlockchainInterface): @classmethod def unspent(cls,*args): @@ -209,7 +335,51 @@ def blockr_fetchtx(cls,txhash, network='btc'): txhash = txhash.encode('hex') jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) return jsondata['data']['tx']['hex'] + + @classmethod + def blockr_get_block_header_data(cls,height, network='btc'): + if network == 'testnet': + blockr_url = "http://tbtc.blockr.io/api/v1/block/raw/" + elif network == 'btc': + blockr_url = "http://btc.blockr.io/api/v1/block/raw/" + else: + raise Exception( + 'Unsupported network {0} for blockr_get_block_header_data'.format(network)) + k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) + j = k['data'] + return { + 'version': j['version'], + 'hash': j['hash'], + 'prevhash': j['previousblockhash'], + 'timestamp': j['time'], + 'merkle_root': j['merkleroot'], + 'bits': int(j['bits'], 16), + 'nonce': j['nonce'], + } + + @classmethod + def get_block_timestamp(cls,height, network='btc'): + if network == 'testnet': + blockr_url = "http://tbtc.blockr.io/api/v1/block/info/" + elif network == 'btc': + blockr_url = "http://btc.blockr.io/api/v1/block/info/" + else: + raise Exception( + 'Unsupported network {0} for get_block_timestamp'.format(network)) + + import time, calendar + if isinstance(height, list): + k = json.loads(make_request(blockr_url + ','.join([str(x) for x in height])).decode("utf-8")) + o = {x['nb']: calendar.timegm(time.strptime(x['time_utc'], + "%Y-%m-%dT%H:%M:%SZ")) for x in k['data']} + return [o[x] for x in height] + else: + k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) + j = k['data']['time_utc'] + return calendar.timegm(time.strptime(j, "%Y-%m-%dT%H:%M:%SZ")) + +@blockchain_interface_impl class HelloBlock(BlockchainInterface): @classmethod def unspent(cls,*args): @@ -285,6 +455,7 @@ def fetchtx(cls,txhash,network='btc'): assert TXHASH(tx) == txhash return tx +@blockchain_interface_impl class Eligius(BlockchainInterface): @classmethod def pushtx(cls,tx,network='btc'): @@ -302,7 +473,40 @@ def pushtx(cls,tx,network='btc'): quote = re.findall('"[^"]*"', string)[0] if len(quote) >= 5: return quote[1:-1] - + +@blockchain_interface_impl +class BlockCypher(BlockchainInterface): + @classmethod + def get_tx_composite(cls,inputs, outputs, output_value, change_address=None, network=None): + """mktx using blockcypher API""" + inputs = [inputs] if not isinstance(inputs, list) else inputs + outputs = [outputs] if not isinstance(outputs, list) else outputs + network = set_network(change_address or inputs) if not network else network.lower() + url = "http://api.blockcypher.com/v1/btc/{network}/txs/new?includeToSignTx=true".format( + network=('test3' if network=='testnet' else 'main')) + is_address = lambda a: bool(re.match("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", a)) + if any([is_address(x) for x in inputs]): + inputs_type = 'addresses' # also accepts UTXOs, only addresses supported presently + if any([is_address(x) for x in outputs]): + outputs_type = 'addresses' # TODO: add UTXO support + data = { + 'inputs': [{inputs_type: inputs}], + 'confirmations': 0, + 'preference': 'high', + 'outputs': [{outputs_type: outputs, "value": output_value}] + } + if change_address: + data["change_address"] = change_address # + jdata = json.loads(make_request(url, data)) + hash, txh = jdata.get("tosign")[0], jdata.get("tosign_tx")[0] + assert bin_dbl_sha256(txh.decode('hex')).encode('hex') == hash, "checksum mismatch %s" % hash + return txh.encode("utf-8") + + +###########LEGACY API################# +###########LEGACY API################# +###########LEGACY API################# + # Gets the unspent outputs of one or more addresses def bci_unspent(*args): return BlockchainInfo.unspent(*args) @@ -370,62 +574,22 @@ def fetchtx(*args, **kwargs): # Gets the transaction output history of a given set of addresses, # including whether or not they have been spent def history(*args): - # Valid input formats: history([addr1, addr2,addr3]) - # history(addr1, addr2, addr3) - if len(args) == 0: - return [] - elif isinstance(args[0], list): - addrs = args[0] - else: - addrs = args - - txs = [] - for addr in addrs: - offset = 0 - while 1: - gathered = False - while not gathered: - try: - data = make_request( - 'https://blockchain.info/address/%s?format=json&offset=%s' % - (addr, offset)) - gathered = True - except Exception as e: - try: - sys.stderr.write(e.read().strip()) - except: - sys.stderr.write(str(e)) - gathered = False - try: - jsonobj = json.loads(data.decode("utf-8")) - except: - raise Exception("Failed to decode data: "+data) - txs.extend(jsonobj["txs"]) - if len(jsonobj["txs"]) < 50: - break - offset += 50 - sys.stderr.write("Fetching more transactions... "+str(offset)+'\n') - outs = {} - for tx in txs: - for o in tx["out"]: - if o.get('addr', None) in addrs: - key = str(tx["tx_index"])+':'+str(o["n"]) - outs[key] = { - "address": o["addr"], - "value": o["value"], - "output": tx["hash"]+':'+str(o["n"]), - "block_height": tx.get("block_height", None) - } - for tx in txs: - for i, inp in enumerate(tx["inputs"]): - if "prev_out" in inp: - if inp["prev_out"].get("addr", None) in addrs: - key = str(inp["prev_out"]["tx_index"]) + \ - ':'+str(inp["prev_out"]["n"]) - if outs.get(key): - outs[key]["spend"] = tx["hash"]+':'+str(i) - return [outs[k] for k in outs] + return BlockchainInfo.history(*args) +def firstbits(address): + return BlockchainInfo.firstbits(address) + + +def get_block_at_height(height): + return BlockchainInfo.get_block_at_height(height) + +#def _get_block(inp): +# if len(str(inp)) < 64: +# return get_block_at_height(inp) +# else: +# return json.loads(make_request( +# 'https://blockchain.info/rawblock/'+inp).decode("utf-8")) + def last_block_height(network='btc'): if network == 'testnet': data = make_request('http://tbtc.blockr.io/api/v1/block/info/last') @@ -435,133 +599,35 @@ def last_block_height(network='btc'): data = make_request('https://blockchain.info/latestblock') jsonobj = json.loads(data.decode("utf-8")) return jsonobj["height"] - -def firstbits(address): - if len(address) >= 25: - return make_request('https://blockchain.info/q/getfirstbits/'+address) - else: - return make_request( - 'https://blockchain.info/q/resolvefirstbits/'+address) - - -def get_block_at_height(height): - j = json.loads(make_request("https://blockchain.info/block-height/" + - str(height)+"?format=json").decode("utf-8")) - for b in j['blocks']: - if b['main_chain'] is True: - return b - raise Exception("Block at this height not found") - - -def _get_block(inp): - if len(str(inp)) < 64: - return get_block_at_height(inp) - else: - return json.loads(make_request( - 'https://blockchain.info/rawblock/'+inp).decode("utf-8")) - - -def bci_get_block_header_data(inp): - j = _get_block(inp) - return { - 'version': j['ver'], - 'hash': j['hash'], - 'prevhash': j['prev_block'], - 'timestamp': j['time'], - 'merkle_root': j['mrkl_root'], - 'bits': j['bits'], - 'nonce': j['nonce'], - } - + + +def bci_get_block_header_data(inp, network='btc'): + return BlockchainInfo.get_block_header_data(inp,network) + def blockr_get_block_header_data(height, network='btc'): - if network == 'testnet': - blockr_url = "http://tbtc.blockr.io/api/v1/block/raw/" - elif network == 'btc': - blockr_url = "http://btc.blockr.io/api/v1/block/raw/" - else: - raise Exception( - 'Unsupported network {0} for blockr_get_block_header_data'.format(network)) - - k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) - j = k['data'] - return { - 'version': j['version'], - 'hash': j['hash'], - 'prevhash': j['previousblockhash'], - 'timestamp': j['time'], - 'merkle_root': j['merkleroot'], - 'bits': int(j['bits'], 16), - 'nonce': j['nonce'], - } - - -def get_block_timestamp(height, network='btc'): - if network == 'testnet': - blockr_url = "http://tbtc.blockr.io/api/v1/block/info/" - elif network == 'btc': - blockr_url = "http://btc.blockr.io/api/v1/block/info/" - else: - raise Exception( - 'Unsupported network {0} for get_block_timestamp'.format(network)) - - import time, calendar - if isinstance(height, list): - k = json.loads(make_request(blockr_url + ','.join([str(x) for x in height])).decode("utf-8")) - o = {x['nb']: calendar.timegm(time.strptime(x['time_utc'], - "%Y-%m-%dT%H:%M:%SZ")) for x in k['data']} - return [o[x] for x in height] - else: - k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) - j = k['data']['time_utc'] - return calendar.timegm(time.strptime(j, "%Y-%m-%dT%H:%M:%SZ")) - + return Blockr.get_block_header_data(height,network) block_header_data_getters = { 'bci': bci_get_block_header_data, 'blockr': blockr_get_block_header_data } - def get_block_header_data(inp, **kwargs): f = block_header_data_getters.get(kwargs.get('source', ''), bci_get_block_header_data) return f(inp, **kwargs) - +def get_block_timestamp(height, network='btc'): + return Blockr.get_block_timestamp(height,network) + def get_txs_in_block(inp): - j = _get_block(inp) - hashes = [t['hash'] for t in j['tx']] - return hashes - + return BlockchainInfo.get_txs_in_block(inp) def get_block_height(txhash): - j = json.loads(make_request('https://blockchain.info/rawtx/'+txhash).decode("utf-8")) - return j['block_height'] + return BlockchainInfo.get_block_height(txhash) # fromAddr, toAddr, 12345, changeAddress def get_tx_composite(inputs, outputs, output_value, change_address=None, network=None): - """mktx using blockcypher API""" - inputs = [inputs] if not isinstance(inputs, list) else inputs - outputs = [outputs] if not isinstance(outputs, list) else outputs - network = set_network(change_address or inputs) if not network else network.lower() - url = "http://api.blockcypher.com/v1/btc/{network}/txs/new?includeToSignTx=true".format( - network=('test3' if network=='testnet' else 'main')) - is_address = lambda a: bool(re.match("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", a)) - if any([is_address(x) for x in inputs]): - inputs_type = 'addresses' # also accepts UTXOs, only addresses supported presently - if any([is_address(x) for x in outputs]): - outputs_type = 'addresses' # TODO: add UTXO support - data = { - 'inputs': [{inputs_type: inputs}], - 'confirmations': 0, - 'preference': 'high', - 'outputs': [{outputs_type: outputs, "value": output_value}] - } - if change_address: - data["change_address"] = change_address # - jdata = json.loads(make_request(url, data)) - hash, txh = jdata.get("tosign")[0], jdata.get("tosign_tx")[0] - assert bin_dbl_sha256(txh.decode('hex')).encode('hex') == hash, "checksum mismatch %s" % hash - return txh.encode("utf-8") + return BlockCypher.get_tx_composite(inputs,outputs,output_value,change_address,network) blockcypher_mktx = get_tx_composite From fd27269fd780a76aa03de3938f54617ea86891ac Mon Sep 17 00:00:00 2001 From: Steve132 Date: Fri, 11 Dec 2015 14:21:14 -0500 Subject: [PATCH 06/14] Fixing bug with is_testnet --- bitcoin/bci.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bitcoin/bci.py b/bitcoin/bci.py index 7cdbca13..39059027 100644 --- a/bitcoin/bci.py +++ b/bitcoin/bci.py @@ -33,18 +33,8 @@ def is_testnet(inp): if not inp or (inp.lower() in ("btc", "testnet")): pass - ## ADDRESSES - if inp[0] in "123mn": - if re.match("^[2mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): - return True - elif re.match("^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): - return False - else: - #sys.stderr.write("Bad address format %s") - return None - ## TXID - elif re.match('^[0-9a-fA-F]{64}$', inp): + if re.match('^[0-9a-fA-F]{64}$', inp): base_url = "http://api.blockcypher.com/v1/btc/{network}/txs/{txid}?includesHex=false" try: # try testnet fetchtx @@ -56,10 +46,20 @@ def is_testnet(inp): return False sys.stderr.write("TxID %s has no match for testnet or mainnet (Bad TxID)") return None + ## ADDRESSES + elif inp[0] in "123mn": + if re.match("^[2mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): + return True + elif re.match("^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): + return False + else: + #sys.stderr.write("Bad address format %s") + return None else: raise TypeError("{0} is unknown input".format(inp)) + def set_network(*args): '''Decides if args for unspent/fetchtx/pushtx are mainnet or testnet''' r = [] From 4bdfa6f6e2ebe289a459e8b0d14753609b07629d Mon Sep 17 00:00:00 2001 From: Steven Braeger Date: Wed, 20 Jan 2016 02:54:59 -0500 Subject: [PATCH 07/14] Made all changes to fix bugs in mnemonic. Added user-friendly bip44 wallet support to deterministic. HDBrainwallet patch --- bitcoin/bci.py | 12 +++-- bitcoin/deterministic.py | 24 +++++++--- bitcoin/mnemonic.py | 37 +++++---------- hd_brainwallet.py | 97 ++++++++++++++++++++++++++++++++-------- 4 files changed, 118 insertions(+), 52 deletions(-) diff --git a/bitcoin/bci.py b/bitcoin/bci.py index 39059027..7d34b748 100644 --- a/bitcoin/bci.py +++ b/bitcoin/bci.py @@ -100,21 +100,23 @@ class _BlockchainInterfaceSet(object): interfaces=[] def __getattr__(cls,key): - sort(cls.interfaces,key=lambda x: return x.priority) + sort(cls.interfaces,key=lambda x: x.priority) for c in cls.interfaces: if(hasattr(c,key) and c.valid): return getattr(c,key) class BlockchainInterface(object): - __metaclass__=_BlockchainInterfaceSet + pass +# __metaclass__=_BlockchainInterfaceSet _prioritycounter=0 def blockchain_interface_impl(cls): + global _prioritycounter cls.valid=True cls.priority=_prioritycounter _prioritycounter+=1 - _BlockchainInterfaceSet.append(cls) + _BlockchainInterfaceSet.interfaces.append(cls) return cls @blockchain_interface_impl @@ -142,6 +144,10 @@ def unspent(cls,*args): except: raise Exception("Failed to decode data: "+data) return u + + @classmethod + def unspent_xpub(cls,*args): + pass # Pushes a transaction to the network using https://blockchain.info/pushtx @classmethod diff --git a/bitcoin/deterministic.py b/bitcoin/deterministic.py index 2797ac77..de43a5c9 100644 --- a/bitcoin/deterministic.py +++ b/bitcoin/deterministic.py @@ -189,17 +189,31 @@ def coinvault_priv_to_bip32(*args): return bip32_serialize((MAINNET_PRIVATE, 0, b'\x00'*4, 0, I2, I3+b'\x01')) -def bip32_descend(*args): +def bip32_path(*args): if len(args) == 2 and isinstance(args[1], list): key, path = args else: key, path = args[0], map(int, args[1:]) - for p in path: - key = bip32_ckd(key, p) - return bip32_extract_key(key) + + return reduce(bip32_ckd,path,key) + +def bip32_descend(*args): + return bip32_extract_key(bip32_path(*args)) #explicit harden method. def bip32_harden(x): return (1 << 31) + x - \ No newline at end of file +def hd_lookup(root,account=None,index=None,change=0,coin=0): + depth=bip32_deserialize(root)[1] + if(depth==0): #if root is master (has a depth=0) + if(index): + return bip32_path(root,bip32_harden(44),bip32_harden(coin),bip32_harden(account),change,index) + else: + return bip32_path(root,bip32_harden(44),bip32_harden(coin),bip32_harden(account)) + elif(depth==3): + return bip32_path(root,change,index) + else: + raise Exception("%s does not appear to be a bip44-compatible xpubkey!" % (root)) + + diff --git a/bitcoin/mnemonic.py b/bitcoin/mnemonic.py index 14399d31..84cd21e1 100644 --- a/bitcoin/mnemonic.py +++ b/bitcoin/mnemonic.py @@ -4,11 +4,10 @@ import random from bisect import bisect_left -wordlist_english=list(open(os.path.join(os.path.dirname(os.path.realpath(__file__)),'english.txt'),'r')) +wordlist_english=[w.strip() for w in open(os.path.join(os.path.dirname(os.path.realpath(__file__)),'english.txt'),'r')] def eint_to_bytes(entint,entbits): - a=hex(entint)[2:].rstrip('L').zfill(32) - print(a) + a=hex(entint)[2:].rstrip('L').zfill(entbits//4) return binascii.unhexlify(a) def mnemonic_int_to_words(mint,mint_num_words,wordlist=wordlist_english): @@ -33,31 +32,15 @@ def entropy_to_words(entbytes,wordlist=wordlist_english): return mnemonic_int_to_words(mint,mint_num_words,wordlist) -def words_bisect(word,wordlist=wordlist_english): - lo=bisect_left(wordlist,word) - hi=len(wordlist)-bisect_left(wordlist[:lo:-1],word) - - return lo,hi - def words_split(wordstr,wordlist=wordlist_english): - def popword(wordstr,wordlist): - for fwl in range(1,9): - w=wordstr[:fwl].strip() - lo,hi=words_bisect(w,wordlist) - if(hi-lo == 1): - return w,wordstr[fwl:].lstrip() - wordlist=wordlist[lo:hi] - raise Exception("Wordstr %s not found in list" %(w)) - - words=[] - tail=wordstr - while(len(tail)): - head,tail=popword(tail,wordlist) - words.append(head) + words=wordstr.split() + for w in words: + if(w not in wordlist): + raise Exception("Word %s not in wordlist" % (w)) return words def words_to_mnemonic_int(words,wordlist=wordlist_english): - if(instance(words,str)): + if(isinstance(words,str)): words=words_split(words,wordlist) return sum([wordlist.index(w) << (11*x) for x,w in enumerate(words[::-1])]) @@ -71,8 +54,9 @@ def words_verify(words,wordlist=wordlist_english): entropy_bits=mint_bits-cs_bits eint=mint >> cs_bits csint=mint & ((1 << cs_bits)-1) - ebytes=_eint_to_bytes(eint,entropy_bits) - return csint == entropy_cs(ebytes) + ebytes=eint_to_bytes(eint,entropy_bits) + ecsint,ecsint_size=entropy_cs(ebytes) + return csint == ecsint def mnemonic_to_seed(mnemonic_phrase,passphrase=u''): try: @@ -119,6 +103,7 @@ def words_mine(prefix,entbits,satisfunction,wordlist=wordlist_english,randombits for v in testvectors['english']: ebytes=binascii.unhexlify(v[0]) w=' '.join(entropy_to_words(ebytes)) + passed=words_verify(w) seed=mnemonic_to_seed(w,passphrase='TREZOR') passed = passed and w==v[1] passed = passed and binascii.hexlify(seed)==v[2] diff --git a/hd_brainwallet.py b/hd_brainwallet.py index bc79077d..48151fe6 100644 --- a/hd_brainwallet.py +++ b/hd_brainwallet.py @@ -1,40 +1,101 @@ import bitcoin import sys import argparse +import urllib2 +import binascii +require_offline=False +running_offline=None + +def user_input(s,expectednotchar=None): + sys.stderr.write(s) + q=raw_input() + if(expectednotchar and (q[0].lower() not in expectednotchar)): + quit() + return q -is_offline = False def test_offline(): - raw_input("Make sure you are offline, alone, and your screen is not visible. [OK]") - print("Testing if you are online...") - try: - urllib2.urlopen("https://google.com",timeout=2.0) - raw_input("You lied about being offline!") - return False - except: - return True + global running_offline + if(running_offline is None): + user_input("Make sure you are offline, alone, and your screen is not visible. [OK]") + print("Testing if you are online...") + try: + result=urllib2.urlopen("https://google.com",timeout=3.0).read() + user_input("You lied about being offline! [OK]") + running_offline=False + return False + except Exception as e: + print(e) + running_offline=True + return True + else: + return running_offline def offlineonly(f): - def wrapper(): - if(not is_offline): - q=raw_input('Warning! You are not in offline mode! You should immediately quit before executing this function! [Y/n]') - if(q.lower()!='n'): - quit() + def wrapper(): + global require_offline + if(require_offline): + if(not test_offline()): + user_input('Warning! You are not in offline mode! You should immediately quit before executing this function! Do you want to do so now? [Y/n]','n') return f() return wrapper @offlineonly def get_password(): - mnemonic=raw_input('Type your password mnemonic, one word at a time:\n') + mnemonic=user_input('Type your password mnemonic, one word at a time:\n') return mnemonic +def get_master_key(): + words=' '.join(get_password().split()) + try: + a=bitcoin.words_verify(words) + except Exception as e: + print(e) + a=False + + if(not a): + q=user_input("Warning! Mnemonic does not verify as a string of bip39 english space-seperated words! continue? [y/N]",'y') + + seed=bitcoin.mnemonic_to_seed(words) + master_key=bitcoin.bip32_master_key(seed) + return master_key + +def send(args): + if(len(args.outputs) % 2 != 0): + raise Exception("When sending, there must be an even number of arguments for the outputs (address,price)") + + +def pubkey(args): + master_key=get_master_key() + + if(args.root or (args.account and args.account < 0)): + #print("The following is your master root extended public key:") + print(bitcoin.bip32_privtopub(master_key)) + else: + account_privkey=bitcoin.hd_lookup(master_key,account=args.account) + #print("The following is the extended public key for account #%d:" % (args.account)) + print(bitcoin.bip32_privtopub(account_privkey)) + if __name__=="__main__": + aparser=argparse.ArgumentParser() + subaparsers=aparser.add_subparsers() + aparse_send=subaparsers.add_parser('send',help="[online] Get the unspents and generate an unsigned transaction to some outputs") + aparse_send.add_argument('--xpub','-p',required=True,help="The xpubkey for the hdwallet account") + aparse_send.add_argument('--fee','-f',default=-1,type=float,help="The fee to use") + aparse_send.add_argument('outputs',help="The outputs, two at a time in format...e.g. 1L3qUmg3GeuGrGvi1JxT2jMhAdV76qVj7V 1.032",nargs='+') + aparse_send.set_defaults(func=send) + + aparse_pubkey=subaparsers.add_parser('pubkey',help='[offline] Get the extended HD pubkey for a particular account') + aparse_pubkey_accountgroup=aparse_pubkey.add_mutually_exclusive_group(required=True) + aparse_pubkey_accountgroup.add_argument('--account','-a',type=int,help="The number of the hd wallet account to export the pubkey for.") + aparse_pubkey_accountgroup.add_argument('--root','-r',action='store_true',help="The exported wallet account pubkey is the master extended pubkey.") + aparse_pubkey.set_defaults(func=pubkey) + args=aparser.parse_args() + args.func(args) + - if(args.is_offline): - is_offline=test_offline() - \ No newline at end of file From 757fa467c3309c3842034247c1769b8671ce391b Mon Sep 17 00:00:00 2001 From: Steven Braeger Date: Wed, 20 Jan 2016 12:24:41 -0500 Subject: [PATCH 08/14] Adding hdbrainwallet stuff --- bitcoin/bci.py | 24 ++++++++++++++++- bitcoin/deterministic.py | 10 ++++++- bitcoin/transaction.py | 4 ++- hd_brainwallet.py | 57 +++++++++++++++++++++++++++++++++++----- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/bitcoin/bci.py b/bitcoin/bci.py index 7d34b748..470e9768 100644 --- a/bitcoin/bci.py +++ b/bitcoin/bci.py @@ -147,7 +147,29 @@ def unspent(cls,*args): @classmethod def unspent_xpub(cls,*args): - pass + u = [] + for a in args: + try: + data = make_request('https://blockchain.info/unspent?active='+a) + except Exception as e: + if str(e) == 'No free outputs to spend': + continue + else: + raise Exception(e) + try: + jsonobj = json.loads(data.strip().decode("utf-8")) + for o in jsonobj["unspent_outputs"]: + h = o['tx_hash'].decode('hex')[::-1].encode('hex') + u.append({ + "output": h+':'+str(o['tx_output_n']), + "value": o['value'], + "xpub": o['xpub'] + }) + except Exception as e: + print(e) + raise Exception("Failed to decode data: "+data) + return u + # Pushes a transaction to the network using https://blockchain.info/pushtx @classmethod diff --git a/bitcoin/deterministic.py b/bitcoin/deterministic.py index de43a5c9..99cfec64 100644 --- a/bitcoin/deterministic.py +++ b/bitcoin/deterministic.py @@ -204,6 +204,14 @@ def bip32_descend(*args): def bip32_harden(x): return (1 << 31) + x +def bip32_path_from_string(pathstring): #can only handle private key style paths not others. + def process(x): + nhx=x.strip("'") + return bip32_harden(int(nhx)) if nhx != x else int(nhx) + pe=pathstring.split("/") + return [process(x) for x in pe[1:]] + + def hd_lookup(root,account=None,index=None,change=0,coin=0): depth=bip32_deserialize(root)[1] if(depth==0): #if root is master (has a depth=0) @@ -213,7 +221,7 @@ def hd_lookup(root,account=None,index=None,change=0,coin=0): return bip32_path(root,bip32_harden(44),bip32_harden(coin),bip32_harden(account)) elif(depth==3): return bip32_path(root,change,index) - else: + else: raise Exception("%s does not appear to be a bip44-compatible xpubkey!" % (root)) diff --git a/bitcoin/transaction.py b/bitcoin/transaction.py index 9ca546d6..bcd37d5d 100644 --- a/bitcoin/transaction.py +++ b/bitcoin/transaction.py @@ -482,7 +482,6 @@ def select(unspent, value): # 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 = [], [] @@ -506,6 +505,9 @@ def mksend(*args): outputs2.append(o2) osum += o2["value"] +def estimate_fee(tx): + pass + if isum < osum+fee: raise Exception("Not enough money") elif isum > osum+fee+5430: diff --git a/hd_brainwallet.py b/hd_brainwallet.py index 48151fe6..fec77fdf 100644 --- a/hd_brainwallet.py +++ b/hd_brainwallet.py @@ -2,7 +2,7 @@ import sys import argparse import urllib2 -import binascii +import json require_offline=False running_offline=None @@ -61,12 +61,17 @@ def get_master_key(): master_key=bitcoin.bip32_master_key(seed) return master_key - -def send(args): - if(len(args.outputs) % 2 != 0): - raise Exception("When sending, there must be an even number of arguments for the outputs (address,price)") +def sign(args): + master_key=get_master_key() + account_privkey=bitcoin.hd_lookup(master_key,account=args.account) + input_transaction=json.load(args.input_transaction) + #compute the largest change address and largest address in the account (use xpub and bitcoin.bip32_string_to_path) + #compute all the underlying addresses and pkeys into a string hash + #decide on the change address + #build the transaction + #sign the transaction + #print the hex - def pubkey(args): master_key=get_master_key() @@ -77,6 +82,41 @@ def pubkey(args): account_privkey=bitcoin.hd_lookup(master_key,account=args.account) #print("The following is the extended public key for account #%d:" % (args.account)) print(bitcoin.bip32_privtopub(account_privkey)) + +def send(args): + if(len(args.outputs) % 2 != 0): + raise Exception("When sending, there must be an even number of arguments for the outputs (address,price)") + unspents=bitcoin.BlockchainInfo.unspent_xpub(args.xpub) + def btctosatoshi(vs): + return int(float(vs)*100000000.0) + fee=btctosatoshi(args.fee) + if(fee < 0): + fee=int(-0.0001*100000000) #todo do something to estimated fee...make it negative or something though + outaddrval=[(args.outputs[2*i],btctosatoshi(args.outputs[2*i+1])) for i in range(len(args.outputs)//2)] + outtotalval=sum([o[1] for o in outaddrval]) + unspenttotalval=sum([u['value'] for u in unspents]) + if(outtotalval+abs(fee) >= unspenttotalval): + raise Exception("There is unlikely to be enough unspent outputs to cover the transaction and fees") + out={} + out['unspents']=unspents + out['fee']=fee #negative if estimated + out['outputs']=outaddrval + + json.dump(out,sys.stdout) + +def address(args): + if(not args.index): + unspents=bitcoin.BlockchainInfo.unspent_xpub(args.xpub) + index=0 + for u in unspents: + upath=u['xpub']['path'] + cdex=bitcoin.bip32_path_from_string(upath)[-1] + index=max(cdex,index) + index+=1 + else: + index=args.index + address=bitcoin.pubtoaddr(bitcoin.bip32_descend(args.xpub,0,index)) + print(address) if __name__=="__main__": aparser=argparse.ArgumentParser() @@ -93,6 +133,11 @@ def pubkey(args): aparse_pubkey_accountgroup.add_argument('--root','-r',action='store_true',help="The exported wallet account pubkey is the master extended pubkey.") aparse_pubkey.set_defaults(func=pubkey) + aparse_address=subaparsers.add_parser('address',help='[online or offline] Get an address for an account') + aparse_address.add_argument('--xpub','-p',required=True,help="The xpubkey for the hdwallet account") + aparse_address.add_argument('--index','-i','--address',type=int,help='The index of the address to get from the account') + aparse_address.set_defaults(func=address) + args=aparser.parse_args() args.func(args) From 4b0db9395db685acd8400ec7ea17be1380641f71 Mon Sep 17 00:00:00 2001 From: Steve132 Date: Fri, 22 Jan 2016 14:15:00 -0500 Subject: [PATCH 09/14] Added secure random generator and helper function to correctly get entropy --- bitcoin/mnemonic.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/bitcoin/mnemonic.py b/bitcoin/mnemonic.py index 84cd21e1..9e6e97f3 100644 --- a/bitcoin/mnemonic.py +++ b/bitcoin/mnemonic.py @@ -1,7 +1,7 @@ import hashlib import os.path import binascii -import random +from os import urandom as secure_random_bytes from bisect import bisect_left wordlist_english=[w.strip() for w in open(os.path.join(os.path.dirname(os.path.realpath(__file__)),'english.txt'),'r')] @@ -82,7 +82,18 @@ def pbkdf2_hmac_sha256(password,salt,iters=2048): return pbkdf2_hmac_sha256(password=mnemonic_phrase,salt='mnemonic'+passphrase) -def words_mine(prefix,entbits,satisfunction,wordlist=wordlist_english,randombits=random.getrandbits): +def _getrandbits(num_bits): + bytes=(num_bits >> 3) + (1 if (num_bits & 0x7) else 0) + rint=int(binascii.hexlify(secure_random_bytes(bytes)),16) + return rint & ((1 << num_bits)-1) + +def words_generate(num_bits_entropy=128,num_words=None,randombits=_getrandbits,wordlist=wordlist_english): + if(num_words is not None and num_words % 3 == 0): + num_bits_entropy=(32*11*num_words) // 33 + rint=randombits(num_bits_entropy) + return entropy_to_words(eint_to_bytes(rint,num_bits_entropy),wordlist) + +def words_mine(prefix,entbits,satisfunction,wordlist=wordlist_english,randombits=_getrandbits): prefix_bits=len(prefix)*11 mine_bits=entbits-prefix_bits pint=words_to_mnemonic_int(prefix,wordlist) From 59c7976ec793cebe7984b74ff356c61a634a578d Mon Sep 17 00:00:00 2001 From: Steve132 Date: Fri, 22 Jan 2016 17:22:34 -0500 Subject: [PATCH 10/14] Fixed mining algorithm --- bitcoin/mnemonic.py | 179 +++++++++++++++++++++++++++++--------------- 1 file changed, 118 insertions(+), 61 deletions(-) diff --git a/bitcoin/mnemonic.py b/bitcoin/mnemonic.py index 9e6e97f3..17506c83 100644 --- a/bitcoin/mnemonic.py +++ b/bitcoin/mnemonic.py @@ -3,61 +3,72 @@ import binascii from os import urandom as secure_random_bytes from bisect import bisect_left +import re -wordlist_english=[w.strip() for w in open(os.path.join(os.path.dirname(os.path.realpath(__file__)),'english.txt'),'r')] +wordlist_english = [w.strip() for w in open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'english.txt'), 'r')] -def eint_to_bytes(entint,entbits): - a=hex(entint)[2:].rstrip('L').zfill(entbits//4) - return binascii.unhexlify(a) +def eint_to_bytes(entint, entbits): + a = hex(entint)[2:].rstrip('L').zfill(entbits // 4) + return binascii.unhexlify(a) -def mnemonic_int_to_words(mint,mint_num_words,wordlist=wordlist_english): - backwords=[wordlist[(mint >> (11*x)) & 0x7FF].strip() for x in range(mint_num_words)] + +def mnemonic_int_to_words(mint, mint_num_words, wordlist=wordlist_english): + backwords = [wordlist[(mint >> (11 * x)) & 0x7FF].strip() for x in range(mint_num_words)] return backwords[::-1] - + + def entropy_cs(entbytes): - entropy_size=8*len(entbytes) - checksum_size=entropy_size//32 - hd=hashlib.sha256(entbytes).hexdigest() - csint=int(hd,16) >> (256-checksum_size) - return csint,checksum_size - -def entropy_to_words(entbytes,wordlist=wordlist_english): - if(len(entbytes) < 4 or len(entbytes) % 4 != 0): - raise ValueError("The size of the entropy must be a multiple of 4 bytes (multiple of 32 bits)") - entropy_size=8*len(entbytes) - csint,checksum_size = entropy_cs(entbytes) - entint=int(binascii.hexlify(entbytes),16) - mint=(entint << checksum_size) | csint - mint_num_words=(entropy_size+checksum_size)//11 - - return mnemonic_int_to_words(mint,mint_num_words,wordlist) - -def words_split(wordstr,wordlist=wordlist_english): - words=wordstr.split() - for w in words: - if(w not in wordlist): - raise Exception("Word %s not in wordlist" % (w)) - return words - -def words_to_mnemonic_int(words,wordlist=wordlist_english): - if(isinstance(words,str)): - words=words_split(words,wordlist) - return sum([wordlist.index(w) << (11*x) for x,w in enumerate(words[::-1])]) - -def words_verify(words,wordlist=wordlist_english): - if(isinstance(words,str)): - words=words_split(words,wordlist) - - mint = words_to_mnemonic_int(words,wordlist) - mint_bits=len(words)*11 - cs_bits=mint_bits//32 - entropy_bits=mint_bits-cs_bits - eint=mint >> cs_bits - csint=mint & ((1 << cs_bits)-1) - ebytes=eint_to_bytes(eint,entropy_bits) - ecsint,ecsint_size=entropy_cs(ebytes) + entropy_size = 8 * len(entbytes) + checksum_size = entropy_size // 32 + hd = hashlib.sha256(entbytes).hexdigest() + csint = int(hd, 16) >> (256 - checksum_size) + return csint, checksum_size + + +def entropy_to_words(entbytes, wordlist=wordlist_english): + if(len(entbytes) < 4 or len(entbytes) % 4 != 0): + raise ValueError("The size of the entropy must be a multiple of 4 bytes (multiple of 32 bits)") + entropy_size = 8 * len(entbytes) + csint, checksum_size = entropy_cs(entbytes) + entint = int(binascii.hexlify(entbytes), 16) + mint = (entint << checksum_size) | csint + mint_num_words = (entropy_size + checksum_size) // 11 + + return mnemonic_int_to_words(mint, mint_num_words, wordlist) + + +def words_split(wordstr, wordlist=wordlist_english): + words = wordstr.split() + for w in words: + if(w not in wordlist): + raise Exception("Word %s not in wordlist" % (w)) + return words + + +def words_to_mnemonic_int(words, wordlist=wordlist_english): + if(isinstance(words, str)): + words = words_split(words, wordlist) + return sum([wordlist.index(w) << (11 * x) for x, w in enumerate(words[::-1])]) + + +def _mint_verify(mint, mint_bits): + cs_bits = mint_bits // 32 + entropy_bits = mint_bits - cs_bits + eint = mint >> cs_bits + csint = mint & ((1 << cs_bits) - 1) + ebytes = eint_to_bytes(eint, entropy_bits) + ecsint, ecsint_size = entropy_cs(ebytes) return csint == ecsint + +def words_verify(words, wordlist=wordlist_english): + if(isinstance(words, str)): + words = words_split(words, wordlist) + + mint=words_to_mnemonic_int(words, wordlist) + mint_bits=len(words)*11 + return _mint_verify(mint,mint_bits) + def mnemonic_to_seed(mnemonic_phrase,passphrase=u''): try: from hashlib import pbkdf2_hmac @@ -93,19 +104,65 @@ def words_generate(num_bits_entropy=128,num_words=None,randombits=_getrandbits,w rint=randombits(num_bits_entropy) return entropy_to_words(eint_to_bytes(rint,num_bits_entropy),wordlist) -def words_mine(prefix,entbits,satisfunction,wordlist=wordlist_english,randombits=_getrandbits): - prefix_bits=len(prefix)*11 - mine_bits=entbits-prefix_bits - pint=words_to_mnemonic_int(prefix,wordlist) - pint<<=mine_bits - dint=randombits(mine_bits) - count=0 - while(not satisfunction(entropy_to_words(eint_to_bytes(pint+dint,entbits)))): - dint=randombits(mine_bits) - if((count & 0xFFFF) == 0): - print("Searched %f percent of the space" % (float(count)/float(1 << mine_bits))) - - return entropy_to_words(eint_to_bytes(pint+dint,entbits)) + +#pattern syntax: a word is (\w*)(?::(\w*))?...but split it first +def words_mine(pattern,valid=lambda x : True ,wordlist=wordlist_english,randombits=_getrandbits): + if(isinstance(pattern,str)): + pattern=pattern.split() + if(len(pattern) % 3 != 0): + raise RuntimeError("The number of words in the pattern must be a multiple of 3") + + rec=re.compile(r'(\w*)(?::(\w*))?') + ranges=[] + + for word in pattern: + m=rec.match(word) + if(not m): + raise RuntimeError("Could not parse pattern word %s" % (word)) + lower=0 + upper=1 << 11 + lw,uw=m.group(1,2) + if(lw): + if(lw != ""): + try: + lower=int(lw) & 0x7FF + except: + lower=next((index for index,value in enumerate(wordlist) if lw <= value)) + if(uw): + if(uw != ""): + try: + upper=int(uw) & 0x7FF + except: + upper=next((index for index,value in enumerate(wordlist) if uw <= value)) + elif(lw): + upper=next((index for index,value in enumerate(wordlist[::-1]) if lw >= value[:len(lw)])) + upper=2048-upper + ranges.append((lower,upper)) + + total_randomness=reduce(lambda a,b: a*(b[1]-b[0]),ranges,1) + + mint_bits=len(pattern)*11 + def gen_mint(rangess,randombitss): + mint=0 + for r in rangess: + mint<<=11 + mint|=r[0] + randombitss(11) % (r[1]-r[0]) + return mint + + tint=gen_mint(ranges,randombits) + count=0 + + while(not _mint_verify(tint,mint_bits) or not valid(mnemonic_int_to_words(tint,len(pattern),wordlist))): + tint=gen_mint(ranges,randombits) + + count+=1 + if((count & 0xFFFF) == 0): + print("Searched %f percent of the space" % (100.0*float(count)/float(total_randomness))) + + return mnemonic_int_to_words(tint,len(pattern),wordlist) + + + if __name__=="__main__": import json From f17b6f4046a01166cbfb17d19074879b2ed72c51 Mon Sep 17 00:00:00 2001 From: Steven Braeger Date: Sat, 23 Jan 2016 21:00:39 -0500 Subject: [PATCH 11/14] Included all standard wordlists. Implemented efficient lookup patch for all wordlists --- bitcoin/english.txt | 2048 ------------------------------------ bitcoin/mnemonic.py | 200 ++-- bitcoin/wordlists.json.bz2 | Bin 0 -> 35558 bytes hd_brainwallet.py | 8 + 4 files changed, 121 insertions(+), 2135 deletions(-) delete mode 100644 bitcoin/english.txt create mode 100644 bitcoin/wordlists.json.bz2 diff --git a/bitcoin/english.txt b/bitcoin/english.txt deleted file mode 100644 index 942040ed..00000000 --- a/bitcoin/english.txt +++ /dev/null @@ -1,2048 +0,0 @@ -abandon -ability -able -about -above -absent -absorb -abstract -absurd -abuse -access -accident -account -accuse -achieve -acid -acoustic -acquire -across -act -action -actor -actress -actual -adapt -add -addict -address -adjust -admit -adult -advance -advice -aerobic -affair -afford -afraid -again -age -agent -agree -ahead -aim -air -airport -aisle -alarm -album -alcohol -alert -alien -all -alley -allow -almost -alone -alpha -already -also -alter -always -amateur -amazing -among -amount -amused -analyst -anchor -ancient -anger -angle -angry -animal -ankle -announce -annual -another -answer -antenna -antique -anxiety -any -apart -apology -appear -apple -approve -april -arch -arctic -area -arena -argue -arm -armed -armor -army -around -arrange -arrest -arrive -arrow -art -artefact -artist -artwork -ask -aspect -assault -asset -assist -assume -asthma -athlete -atom -attack -attend -attitude -attract -auction -audit -august -aunt -author -auto -autumn -average -avocado -avoid -awake -aware -away -awesome -awful -awkward -axis -baby -bachelor -bacon -badge -bag -balance -balcony -ball -bamboo -banana -banner -bar -barely -bargain -barrel -base -basic -basket -battle -beach -bean -beauty -because -become -beef -before -begin -behave -behind -believe -below -belt -bench -benefit -best -betray -better -between -beyond -bicycle -bid -bike -bind -biology -bird -birth -bitter -black -blade -blame -blanket -blast -bleak -bless -blind -blood -blossom -blouse -blue -blur -blush -board -boat -body -boil -bomb -bone -bonus -book -boost -border -boring -borrow -boss -bottom -bounce -box -boy -bracket -brain -brand -brass -brave -bread -breeze -brick -bridge -brief -bright -bring -brisk -broccoli -broken -bronze -broom -brother -brown -brush -bubble -buddy -budget -buffalo -build -bulb -bulk -bullet -bundle -bunker -burden -burger -burst -bus -business -busy -butter -buyer -buzz -cabbage -cabin -cable -cactus -cage -cake -call -calm -camera -camp -can -canal -cancel -candy -cannon -canoe -canvas -canyon -capable -capital -captain -car -carbon -card -cargo -carpet -carry -cart -case -cash -casino -castle -casual -cat -catalog -catch -category -cattle -caught -cause -caution -cave -ceiling -celery -cement -census -century -cereal -certain -chair -chalk -champion -change -chaos -chapter -charge -chase -chat -cheap -check -cheese -chef -cherry -chest -chicken -chief -child -chimney -choice -choose -chronic -chuckle -chunk -churn -cigar -cinnamon -circle -citizen -city -civil -claim -clap -clarify -claw -clay -clean -clerk -clever -click -client -cliff -climb -clinic -clip -clock -clog -close -cloth -cloud -clown -club -clump -cluster -clutch -coach -coast -coconut -code -coffee -coil -coin -collect -color -column -combine -come -comfort -comic -common -company -concert -conduct -confirm -congress -connect -consider -control -convince -cook -cool -copper -copy -coral -core -corn -correct -cost -cotton -couch -country -couple -course -cousin -cover -coyote -crack -cradle -craft -cram -crane -crash -crater -crawl -crazy -cream -credit -creek -crew -cricket -crime -crisp -critic -crop -cross -crouch -crowd -crucial -cruel -cruise -crumble -crunch -crush -cry -crystal -cube -culture -cup -cupboard -curious -current -curtain -curve -cushion -custom -cute -cycle -dad -damage -damp -dance -danger -daring -dash -daughter -dawn -day -deal -debate -debris -decade -december -decide -decline -decorate -decrease -deer -defense -define -defy -degree -delay -deliver -demand -demise -denial -dentist -deny -depart -depend -deposit -depth -deputy -derive -describe -desert -design -desk -despair -destroy -detail -detect -develop -device -devote -diagram -dial -diamond -diary -dice -diesel -diet -differ -digital -dignity -dilemma -dinner -dinosaur -direct -dirt -disagree -discover -disease -dish -dismiss -disorder -display -distance -divert -divide -divorce -dizzy -doctor -document -dog -doll -dolphin -domain -donate -donkey -donor -door -dose -double -dove -draft -dragon -drama -drastic -draw -dream -dress -drift -drill -drink -drip -drive -drop -drum -dry -duck -dumb -dune -during -dust -dutch -duty -dwarf -dynamic -eager -eagle -early -earn -earth -easily -east -easy -echo -ecology -economy -edge -edit -educate -effort -egg -eight -either -elbow -elder -electric -elegant -element -elephant -elevator -elite -else -embark -embody -embrace -emerge -emotion -employ -empower -empty -enable -enact -end -endless -endorse -enemy -energy -enforce -engage -engine -enhance -enjoy -enlist -enough -enrich -enroll -ensure -enter -entire -entry -envelope -episode -equal -equip -era -erase -erode -erosion -error -erupt -escape -essay -essence -estate -eternal -ethics -evidence -evil -evoke -evolve -exact -example -excess -exchange -excite -exclude -excuse -execute -exercise -exhaust -exhibit -exile -exist -exit -exotic -expand -expect -expire -explain -expose -express -extend -extra -eye -eyebrow -fabric -face -faculty -fade -faint -faith -fall -false -fame -family -famous -fan -fancy -fantasy -farm -fashion -fat -fatal -father -fatigue -fault -favorite -feature -february -federal -fee -feed -feel -female -fence -festival -fetch -fever -few -fiber -fiction -field -figure -file -film -filter -final -find -fine -finger -finish -fire -firm -first -fiscal -fish -fit -fitness -fix -flag -flame -flash -flat -flavor -flee -flight -flip -float -flock -floor -flower -fluid -flush -fly -foam -focus -fog -foil -fold -follow -food -foot -force -forest -forget -fork -fortune -forum -forward -fossil -foster -found -fox -fragile -frame -frequent -fresh -friend -fringe -frog -front -frost -frown -frozen -fruit -fuel -fun -funny -furnace -fury -future -gadget -gain -galaxy -gallery -game -gap -garage -garbage -garden -garlic -garment -gas -gasp -gate -gather -gauge -gaze -general -genius -genre -gentle -genuine -gesture -ghost -giant -gift -giggle -ginger -giraffe -girl -give -glad -glance -glare -glass -glide -glimpse -globe -gloom -glory -glove -glow -glue -goat -goddess -gold -good -goose -gorilla -gospel -gossip -govern -gown -grab -grace -grain -grant -grape -grass -gravity -great -green -grid -grief -grit -grocery -group -grow -grunt -guard -guess -guide -guilt -guitar -gun -gym -habit -hair -half -hammer -hamster -hand -happy -harbor -hard -harsh -harvest -hat -have -hawk -hazard -head -health -heart -heavy -hedgehog -height -hello -helmet -help -hen -hero -hidden -high -hill -hint -hip -hire -history -hobby -hockey -hold -hole -holiday -hollow -home -honey -hood -hope -horn -horror -horse -hospital -host -hotel -hour -hover -hub -huge -human -humble -humor -hundred -hungry -hunt -hurdle -hurry -hurt -husband -hybrid -ice -icon -idea -identify -idle -ignore -ill -illegal -illness -image -imitate -immense -immune -impact -impose -improve -impulse -inch -include -income -increase -index -indicate -indoor -industry -infant -inflict -inform -inhale -inherit -initial -inject -injury -inmate -inner -innocent -input -inquiry -insane -insect -inside -inspire -install -intact -interest -into -invest -invite -involve -iron -island -isolate -issue -item -ivory -jacket -jaguar -jar -jazz -jealous -jeans -jelly -jewel -job -join -joke -journey -joy -judge -juice -jump -jungle -junior -junk -just -kangaroo -keen -keep -ketchup -key -kick -kid -kidney -kind -kingdom -kiss -kit -kitchen -kite -kitten -kiwi -knee -knife -knock -know -lab -label -labor -ladder -lady -lake -lamp -language -laptop -large -later -latin -laugh -laundry -lava -law -lawn -lawsuit -layer -lazy -leader -leaf -learn -leave -lecture -left -leg -legal -legend -leisure -lemon -lend -length -lens -leopard -lesson -letter -level -liar -liberty -library -license -life -lift -light -like -limb -limit -link -lion -liquid -list -little -live -lizard -load -loan -lobster -local -lock -logic -lonely -long -loop -lottery -loud -lounge -love -loyal -lucky -luggage -lumber -lunar -lunch -luxury -lyrics -machine -mad -magic -magnet -maid -mail -main -major -make -mammal -man -manage -mandate -mango -mansion -manual -maple -marble -march -margin -marine -market -marriage -mask -mass -master -match -material -math -matrix -matter -maximum -maze -meadow -mean -measure -meat -mechanic -medal -media -melody -melt -member -memory -mention -menu -mercy -merge -merit -merry -mesh -message -metal -method -middle -midnight -milk -million -mimic -mind -minimum -minor -minute -miracle -mirror -misery -miss -mistake -mix -mixed -mixture -mobile -model -modify -mom -moment -monitor -monkey -monster -month -moon -moral -more -morning -mosquito -mother -motion -motor -mountain -mouse -move -movie -much -muffin -mule -multiply -muscle -museum -mushroom -music -must -mutual -myself -mystery -myth -naive -name -napkin -narrow -nasty -nation -nature -near -neck -need -negative -neglect -neither -nephew -nerve -nest -net -network -neutral -never -news -next -nice -night -noble -noise -nominee -noodle -normal -north -nose -notable -note -nothing -notice -novel -now -nuclear -number -nurse -nut -oak -obey -object -oblige -obscure -observe -obtain -obvious -occur -ocean -october -odor -off -offer -office -often -oil -okay -old -olive -olympic -omit -once -one -onion -online -only -open -opera -opinion -oppose -option -orange -orbit -orchard -order -ordinary -organ -orient -original -orphan -ostrich -other -outdoor -outer -output -outside -oval -oven -over -own -owner -oxygen -oyster -ozone -pact -paddle -page -pair -palace -palm -panda -panel -panic -panther -paper -parade -parent -park -parrot -party -pass -patch -path -patient -patrol -pattern -pause -pave -payment -peace -peanut -pear -peasant -pelican -pen -penalty -pencil -people -pepper -perfect -permit -person -pet -phone -photo -phrase -physical -piano -picnic -picture -piece -pig -pigeon -pill -pilot -pink -pioneer -pipe -pistol -pitch -pizza -place -planet -plastic -plate -play -please -pledge -pluck -plug -plunge -poem -poet -point -polar -pole -police -pond -pony -pool -popular -portion -position -possible -post -potato -pottery -poverty -powder -power -practice -praise -predict -prefer -prepare -present -pretty -prevent -price -pride -primary -print -priority -prison -private -prize -problem -process -produce -profit -program -project -promote -proof -property -prosper -protect -proud -provide -public -pudding -pull -pulp -pulse -pumpkin -punch -pupil -puppy -purchase -purity -purpose -purse -push -put -puzzle -pyramid -quality -quantum -quarter -question -quick -quit -quiz -quote -rabbit -raccoon -race -rack -radar -radio -rail -rain -raise -rally -ramp -ranch -random -range -rapid -rare -rate -rather -raven -raw -razor -ready -real -reason -rebel -rebuild -recall -receive -recipe -record -recycle -reduce -reflect -reform -refuse -region -regret -regular -reject -relax -release -relief -rely -remain -remember -remind -remove -render -renew -rent -reopen -repair -repeat -replace -report -require -rescue -resemble -resist -resource -response -result -retire -retreat -return -reunion -reveal -review -reward -rhythm -rib -ribbon -rice -rich -ride -ridge -rifle -right -rigid -ring -riot -ripple -risk -ritual -rival -river -road -roast -robot -robust -rocket -romance -roof -rookie -room -rose -rotate -rough -round -route -royal -rubber -rude -rug -rule -run -runway -rural -sad -saddle -sadness -safe -sail -salad -salmon -salon -salt -salute -same -sample -sand -satisfy -satoshi -sauce -sausage -save -say -scale -scan -scare -scatter -scene -scheme -school -science -scissors -scorpion -scout -scrap -screen -script -scrub -sea -search -season -seat -second -secret -section -security -seed -seek -segment -select -sell -seminar -senior -sense -sentence -series -service -session -settle -setup -seven -shadow -shaft -shallow -share -shed -shell -sheriff -shield -shift -shine -ship -shiver -shock -shoe -shoot -shop -short -shoulder -shove -shrimp -shrug -shuffle -shy -sibling -sick -side -siege -sight -sign -silent -silk -silly -silver -similar -simple -since -sing -siren -sister -situate -six -size -skate -sketch -ski -skill -skin -skirt -skull -slab -slam -sleep -slender -slice -slide -slight -slim -slogan -slot -slow -slush -small -smart -smile -smoke -smooth -snack -snake -snap -sniff -snow -soap -soccer -social -sock -soda -soft -solar -soldier -solid -solution -solve -someone -song -soon -sorry -sort -soul -sound -soup -source -south -space -spare -spatial -spawn -speak -special -speed -spell -spend -sphere -spice -spider -spike -spin -spirit -split -spoil -sponsor -spoon -sport -spot -spray -spread -spring -spy -square -squeeze -squirrel -stable -stadium -staff -stage -stairs -stamp -stand -start -state -stay -steak -steel -stem -step -stereo -stick -still -sting -stock -stomach -stone -stool -story -stove -strategy -street -strike -strong -struggle -student -stuff -stumble -style -subject -submit -subway -success -such -sudden -suffer -sugar -suggest -suit -summer -sun -sunny -sunset -super -supply -supreme -sure -surface -surge -surprise -surround -survey -suspect -sustain -swallow -swamp -swap -swarm -swear -sweet -swift -swim -swing -switch -sword -symbol -symptom -syrup -system -table -tackle -tag -tail -talent -talk -tank -tape -target -task -taste -tattoo -taxi -teach -team -tell -ten -tenant -tennis -tent -term -test -text -thank -that -theme -then -theory -there -they -thing -this -thought -three -thrive -throw -thumb -thunder -ticket -tide -tiger -tilt -timber -time -tiny -tip -tired -tissue -title -toast -tobacco -today -toddler -toe -together -toilet -token -tomato -tomorrow -tone -tongue -tonight -tool -tooth -top -topic -topple -torch -tornado -tortoise -toss -total -tourist -toward -tower -town -toy -track -trade -traffic -tragic -train -transfer -trap -trash -travel -tray -treat -tree -trend -trial -tribe -trick -trigger -trim -trip -trophy -trouble -truck -true -truly -trumpet -trust -truth -try -tube -tuition -tumble -tuna -tunnel -turkey -turn -turtle -twelve -twenty -twice -twin -twist -two -type -typical -ugly -umbrella -unable -unaware -uncle -uncover -under -undo -unfair -unfold -unhappy -uniform -unique -unit -universe -unknown -unlock -until -unusual -unveil -update -upgrade -uphold -upon -upper -upset -urban -urge -usage -use -used -useful -useless -usual -utility -vacant -vacuum -vague -valid -valley -valve -van -vanish -vapor -various -vast -vault -vehicle -velvet -vendor -venture -venue -verb -verify -version -very -vessel -veteran -viable -vibrant -vicious -victory -video -view -village -vintage -violin -virtual -virus -visa -visit -visual -vital -vivid -vocal -voice -void -volcano -volume -vote -voyage -wage -wagon -wait -walk -wall -walnut -want -warfare -warm -warrior -wash -wasp -waste -water -wave -way -wealth -weapon -wear -weasel -weather -web -wedding -weekend -weird -welcome -west -wet -whale -what -wheat -wheel -when -where -whip -whisper -wide -width -wife -wild -will -win -window -wine -wing -wink -winner -winter -wire -wisdom -wise -wish -witness -wolf -woman -wonder -wood -wool -word -work -world -worry -worth -wrap -wreck -wrestle -wrist -write -wrong -yard -year -yellow -you -young -youth -zebra -zero -zone -zoo diff --git a/bitcoin/mnemonic.py b/bitcoin/mnemonic.py index 17506c83..2f9d885f 100644 --- a/bitcoin/mnemonic.py +++ b/bitcoin/mnemonic.py @@ -4,15 +4,35 @@ from os import urandom as secure_random_bytes from bisect import bisect_left import re +import collections +import json +import bz2 + +#this type has list semantics but is optimized for O(1) search using a hashtable for speed +class Wordlist(list): + def __init__(self,*collection,**kwargs): + list.__init__(self,*collection,**kwargs) + self.lookup=dict((value,index) for index,value in enumerate(self)) + + def index(self,x): + try: + return self.lookup[x] + except KeyError as ke: + raise IndexError("'%s' is not in list" % (ke.args)) + + def __contains__(self,x): + return x in self.lookup -wordlist_english = [w.strip() for w in open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'english.txt'), 'r')] + +_wordlists_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'wordlists.json.bz2') +wordlists = dict(((k,Wordlist(v)) for k,v in json.load(bz2.BZ2File(_wordlists_path,'r')).iteritems())) def eint_to_bytes(entint, entbits): - a = hex(entint)[2:].rstrip('L').zfill(entbits // 4) - return binascii.unhexlify(a) + a = hex(entint)[2:].rstrip('L').zfill(entbits // 4) + return binascii.unhexlify(a) -def mnemonic_int_to_words(mint, mint_num_words, wordlist=wordlist_english): +def mnemonic_int_to_words(mint, mint_num_words, wordlist=wordlists["english"]): backwords = [wordlist[(mint >> (11 * x)) & 0x7FF].strip() for x in range(mint_num_words)] return backwords[::-1] @@ -25,33 +45,33 @@ def entropy_cs(entbytes): return csint, checksum_size -def entropy_to_words(entbytes, wordlist=wordlist_english): - if(len(entbytes) < 4 or len(entbytes) % 4 != 0): - raise ValueError("The size of the entropy must be a multiple of 4 bytes (multiple of 32 bits)") - entropy_size = 8 * len(entbytes) - csint, checksum_size = entropy_cs(entbytes) - entint = int(binascii.hexlify(entbytes), 16) - mint = (entint << checksum_size) | csint - mint_num_words = (entropy_size + checksum_size) // 11 +def entropy_to_words(entbytes, wordlist=wordlists["english"]): + if(len(entbytes) < 4 or len(entbytes) % 4 != 0): + raise ValueError("The size of the entropy must be a multiple of 4 bytes (multiple of 32 bits)") + entropy_size = 8 * len(entbytes) + csint, checksum_size = entropy_cs(entbytes) + entint = int(binascii.hexlify(entbytes), 16) + mint = (entint << checksum_size) | csint + mint_num_words = (entropy_size + checksum_size) // 11 - return mnemonic_int_to_words(mint, mint_num_words, wordlist) + return mnemonic_int_to_words(mint, mint_num_words, wordlist) -def words_split(wordstr, wordlist=wordlist_english): - words = wordstr.split() - for w in words: - if(w not in wordlist): - raise Exception("Word %s not in wordlist" % (w)) - return words +def words_split(wordstr, wordlist=wordlists["english"]): + words = wordstr.split() + for w in words: + if(w not in wordlist): + raise Exception("Word %s not in wordlist" % (w)) + return words -def words_to_mnemonic_int(words, wordlist=wordlist_english): - if(isinstance(words, str)): +def words_to_mnemonic_int(words, wordlist=wordlists["english"]): + if(isinstance(words, basestring)): words = words_split(words, wordlist) return sum([wordlist.index(w) << (11 * x) for x, w in enumerate(words[::-1])]) -def _mint_verify(mint, mint_bits): +def mnemonic_int_verify(mint, mint_bits): cs_bits = mint_bits // 32 entropy_bits = mint_bits - cs_bits eint = mint >> cs_bits @@ -61,13 +81,13 @@ def _mint_verify(mint, mint_bits): return csint == ecsint -def words_verify(words, wordlist=wordlist_english): - if(isinstance(words, str)): - words = words_split(words, wordlist) - - mint=words_to_mnemonic_int(words, wordlist) - mint_bits=len(words)*11 - return _mint_verify(mint,mint_bits) +def words_verify(words, wordlist=wordlists["english"]): + if(isinstance(words, basestring)): + words = words_split(words, wordlist) + + mint=words_to_mnemonic_int(words, wordlist) + mint_bits=len(words)*11 + return mnemonic_int_verify(mint,mint_bits) def mnemonic_to_seed(mnemonic_phrase,passphrase=u''): try: @@ -94,75 +114,81 @@ def pbkdf2_hmac_sha256(password,salt,iters=2048): return pbkdf2_hmac_sha256(password=mnemonic_phrase,salt='mnemonic'+passphrase) def _getrandbits(num_bits): - bytes=(num_bits >> 3) + (1 if (num_bits & 0x7) else 0) - rint=int(binascii.hexlify(secure_random_bytes(bytes)),16) - return rint & ((1 << num_bits)-1) - -def words_generate(num_bits_entropy=128,num_words=None,randombits=_getrandbits,wordlist=wordlist_english): - if(num_words is not None and num_words % 3 == 0): - num_bits_entropy=(32*11*num_words) // 33 - rint=randombits(num_bits_entropy) - return entropy_to_words(eint_to_bytes(rint,num_bits_entropy),wordlist) + bytes=(num_bits >> 3) + (1 if (num_bits & 0x7) else 0) + rint=int(binascii.hexlify(secure_random_bytes(bytes)),16) + return rint & ((1 << num_bits)-1) + +def words_generate(num_bits_entropy=128,num_words=None,randombits=_getrandbits,wordlist=wordlists["english"]): + if(num_words is not None and num_words % 3 == 0): + num_bits_entropy=(32*11*num_words) // 33 + rint=randombits(num_bits_entropy) + return entropy_to_words(eint_to_bytes(rint,num_bits_entropy),wordlist) #pattern syntax: a word is (\w*)(?::(\w*))?...but split it first -def words_mine(pattern,valid=lambda x : True ,wordlist=wordlist_english,randombits=_getrandbits): - if(isinstance(pattern,str)): - pattern=pattern.split() - if(len(pattern) % 3 != 0): - raise RuntimeError("The number of words in the pattern must be a multiple of 3") +def words_mine(pattern,valid=lambda x : True ,wordlist=wordlists["english"],randombits=_getrandbits): + if(isinstance(pattern,basestring)): + pattern=pattern.split() + if(len(pattern) % 3 != 0): + raise RuntimeError("The number of words in the pattern must be a multiple of 3") - rec=re.compile(r'(\w*)(?::(\w*))?') - ranges=[] - - for word in pattern: - m=rec.match(word) - if(not m): - raise RuntimeError("Could not parse pattern word %s" % (word)) - lower=0 - upper=1 << 11 - lw,uw=m.group(1,2) - if(lw): - if(lw != ""): - try: - lower=int(lw) & 0x7FF - except: - lower=next((index for index,value in enumerate(wordlist) if lw <= value)) - if(uw): - if(uw != ""): - try: - upper=int(uw) & 0x7FF - except: - upper=next((index for index,value in enumerate(wordlist) if uw <= value)) - elif(lw): - upper=next((index for index,value in enumerate(wordlist[::-1]) if lw >= value[:len(lw)])) - upper=2048-upper - ranges.append((lower,upper)) + rec=re.compile(r'(\w*)(?::(\w*))?') + ranges=[] + + for word in pattern: + m=rec.match(word) + if(not m): + raise RuntimeError("Could not parse pattern word %s" % (word)) + lower=0 + upper=1 << 11 + lw,uw=m.group(1,2) + if(lw): + if(lw != ""): + try: + lower=int(lw) & 0x7FF + except: + lower=next((index for index,value in enumerate(wordlist) if lw <= value)) + if(uw): + if(uw != ""): + try: + upper=int(uw) & 0x7FF + except: + upper=next((index for index,value in enumerate(wordlist) if uw <= value)) + elif(lw): + upper=next((index for index,value in enumerate(wordlist[::-1]) if lw >= value[:len(lw)])) + upper=2048-upper + ranges.append((lower,upper)) - total_randomness=reduce(lambda a,b: a*(b[1]-b[0]),ranges,1) + total_randomness=reduce(lambda a,b: a*(b[1]-b[0]),ranges,1) - mint_bits=len(pattern)*11 - def gen_mint(rangess,randombitss): - mint=0 - for r in rangess: - mint<<=11 - mint|=r[0] + randombitss(11) % (r[1]-r[0]) - return mint + mint_bits=len(pattern)*11 + def gen_mint(rangess,randombitss): + mint=0 + for r in rangess: + mint<<=11 + mint|=r[0] + randombitss(11) % (r[1]-r[0]) + return mint - tint=gen_mint(ranges,randombits) - count=0 + tint=gen_mint(ranges,randombits) + count=0 - while(not _mint_verify(tint,mint_bits) or not valid(mnemonic_int_to_words(tint,len(pattern),wordlist))): - tint=gen_mint(ranges,randombits) - - count+=1 - if((count & 0xFFFF) == 0): - print("Searched %f percent of the space" % (100.0*float(count)/float(total_randomness))) + while(not mnemonic_int_verify(tint,mint_bits) or not valid(mnemonic_int_to_words(tint,len(pattern),wordlist))): + tint=gen_mint(ranges,randombits) - return mnemonic_int_to_words(tint,len(pattern),wordlist) + count+=1 + if((count & 0xFFFF) == 0): + print("Searched %f percent of the space" % (100.0*float(count)/float(total_randomness))) + + return mnemonic_int_to_words(tint,len(pattern),wordlist) - +def _build_mnemonic_file(): + import urllib2 + mnemonic_languages=["english","japanese","spanish","french","chinese_simplified","chinese_traditional"] + wordlists={} + for la in mnemonic_languages: + wordlists[la]=urllib2.urlopen("https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/%s.txt" % (la)).read().split() + json.dump(wordlists,bz2.BZ2File('wordlists.json.bz2','w')) if __name__=="__main__": import json diff --git a/bitcoin/wordlists.json.bz2 b/bitcoin/wordlists.json.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..b259725859a3450dae075d7356d43a51ff7665ec GIT binary patch literal 35558 zcmV(rK<>XnT4*^jL0KkKS>U{5KLI|^TYx|SPy~PQ01m(Z|N3CKe(ow1Qm9o*K)!iZ zZK@I#R4ZE9m9t0DR z%JMiM9RZ%O&Bt^O0ERW^uL0rdhKIe5LYIpulgJI*Y}m18w)O3ffRIq`za^h5@_pn$?t*A5t9d=BUeNs*yIxD?yO9!cB~owYLq>(v+z+C4GtSPzRvJ?*;~d zJt3hB2?{w1dUcIs+ zl1i4wySp6SYQ8W!4uMCpfB;cIC)$1QPkQ-q-~(2Bd*0c5Zn-Gpv^?ivTJ+#h3F`m{ zr09gO$9a_+FVbd?gj1_^6K3&1)6+j`6+1;a+=2FMs_`OLk=Wp5j+L8{(j z2OXd+-x|!qoH+2t32cYR6+)Oxaf1hZwX`=_zgWPUMp?6fy{Cz^S5&=Ib#D^gDSMDZ zc9#~{##6nv#AspjrB7`MA1}?zUM;bAECr_1ph{xyk47VOSEpv|$4-vLvK>=&O>JDh zmZ7&WQ7W#bH93^>+3PBBs+RX+-L2zh|<8E>y#5D(wm^~paVZQKZ z3+CzF_r57te(<$6@&zGxxs6P)!p~;+eaNv2E!ewaVy)sXNnNLzX8lEcuREe_W@*_; z9@mk*ID{c1kJT=_Se8BM^+KvTLOQ1&DQaAOb+|D5x9^BH^Ra>Rrn>RByW83r&~2qK zLx}>HF?Ls+@tjHUY3#CVVHIY4%Q^(;Bl-g^io;;3Y?(-bU7;29@ zOk-+IvhQ&zB~QLaxmI#Mrd{%31*(e9T^G7(uD18PV3L_2go_yJ;l}YlQ15p!$=fM3 zaw4m)HQ!L|Q)&7B+o}rR5+wn|vW8RxOV?xnZn&I0$S-xGs{F zo<{BB4Hm0SVj)B1?dx$enm2gWOrSb}TfCc_>6kPIZ0i(}XeNdc$Z~EL^K8vEGc@9b zX%t(!uuHonD4x`m+P!xCyWH{K=sM|Z8j+5g7OhL`GW=P(QayW4PTtJQi7HCzyJ7QG z7J)lUc~q*!lgpW%%O;ld8|ArYhYSIUxP!f?rCE1|N|Lh!I&G&p)d{WU-m`Z_D9Lmv zkr|DOaT4jPY}lt}i!hr-v`!$|rd@65FZ&J zns(THycN7@@W$-bx_j=dzUa3IJ*s%8*YVuD@XPVz8a!_}<~Tv$hpO7pQ{|qs2ih0h z>^h6%v4wtMIAGIpbzyTscA(|Y2elXl78n-RX9zJlRt_FtaS9}8U@{nk5-3QkY4!$1 zN-x|qjukt)OZ#IZScB}?w>WbJZe=zUUTZcO*2A;LD)CZ5@G5p|RzZJV6B@_*lBmXY}TOR zlhg1I-dmSwM?fu!4yr&~y4#V6-6I4S&FqXCD0|$Y*;sT?B;&vfM@HQY|C~6b78$lv5kuk-C3yccDRFQB& znNiZgM)0P(3M?0LBponOH35b!u~*o|^IA%~K5Td40&tEsmEOS$5^pv#MeIhi*el+a zy6Lf#-?!skg6`L}o;+LQG0S3Sr%q|T6g*(QU4~Gk9~?A6^k^t>qK0^^JZ=MSwj7mj zfdJ@4x^bi7a3w&d6}^}&O$|6K9a^$CZOP&S+Yl!~-vG-X9xAAUE<^&7Yz+(Of(ylu0`->(c+{~n=wvptGPX@NQqjYlD{rayi0ulD zie}zDq3$@Ve-x0cGAuW?2oZ%y$k-(qJB&M8M6FyUj3{8r1nXwL#dKnaPLM@UC-Fio zzD)RZt#o@CWZ0a-i!igOKu@)OE57qfE-b zWSGI5PEE#eV_1;q4jKZT2N(_yMbYTHL>`=QN49L#Kh!{N(sAGx(%98Fh6l@@w4@JhbAZ{(G3^E>p4GJ69C#S8$ z;JMy&<4WX}EOdLVfOCSw2cGxFfN&dOgOvJNJdBnyV&0((x0B<>&N^2o#}1SB{_(=(c+_a21#P3I zcB@hgYsWJ@EO(k`#)&#o>=0l*s-{-w#K3Wpg&7PpCRz@p_ZKsL>}i9EyTc3{QWl6{ zNOwV%6jx|fDHTy$I1DrHPiI)~YcXEJo}WG%-DkBQDtGO8o@-+`2vwI;7szFTq$`ZD ze8m<)MybU>p`b?GWhI&Wj&v?|JH7=$1r2V8LHt+H*db~HMGvPZ4;J?h*7YgLC(5Ty z=f#WCcU`<2G(6%7T%o;Wt`7a{;OTSXn?0V4_~rKuzPDOaBBdNs11wB30@7M=yBH2I zC@>*hHn8Z`l!7fDT7Kfe zJJ*RYXyKd6+E6hz_M-yV0)mo^hmEENgFz6~yCBaSvxY!~G8j)5ZZ}Y)BLafxCAPM9 ztY+5OQK^;57;s>hQWI^B;_4DA*%UPP2*@iJ40CP6#xYL9_z60j<6~@rEr(3&Np#X_ zwGt-|lYK1*8#|=Xw()e)W$iTNhU+9Et$m7(o0Q#fq!RaQN=@A7Hxf0X-X9vUbsDP; zs257Kv@bD6!;DtObl5rB>kwhFxgCq=AYMl{kF`c{ zZ#=NIjoYKBwZ#Fgx!!LC(Wnq7d$3p+3`X^r3?A`b8!vorhkfnl$4qzXtI1AW>dQIZ z8fR?DFuN5SF`Jw5m=7U$GePc_z=bEVTy1Dkr3DOE#Rn~(pkNz{84e^smo1GzrL~^f z8xOlp7TB4d?l*H87B-Y^Q<}PK6kvv}gvw0~@r~(nF42=LuDG2ZkW*VXT#?@yhLX+C zO&2XX+BFQCb4=qmAa$Xw-3G6mZx%L!3r{x-s0fjH?r8ZKo1O7a>$ZF1?)~qG-lv57 zgx-6Jw`a?|IH75H6QWR8i)33SY;v`zfMr-1(#<*LmX7h8n6<)O@Ch*`68l?A;siV( z+PTom3}!+=$h&VNDtZ%1FJv1L#^V;lrG>?@Q8p8!jfxv#xQ&{m=GeqoWEKU8HiU+- z)twUZ=Y0g$`y23v7`vMzo{ADPRypXA{_Q4z`94h)zu<@AT5ul^`JWYdfALcKeb4idbqTX|PKZIDf;*oL zaIC?`tNnU{dd`m;s~c4@jWoxFhGAXffd8f8?3AJHF1Uw8M*=A`i~Pj}uTC|>7Vsk3 zma#q+9(*=LBEFw(v2>U{(R|9q(b6FOX--WTUH?m)DC;G5v53U z&!YG?9D{RlmOe@yD;1O(i4C8KAJtfc|3%Q5EMFVEP2Y`{{ULOP@PEtLVIhPPjp1S1(|3(T|Iem+Z!*DvWg>Va&`V^e`<)?u<`iHWRKHUj{D@ZBS%-#utmp==Kqo{0n-`Q5mbx3|6&(ECB{ykvdY%SA}*o`C*rS#L63XRX)gAL|o*#_6MGv@G$24~~# zS)+EJQ|c?~r0Sv_F_uRm+g-Fud&(-8E-Kg}V;k>qL8G{HOTrrwmh^_Cx{Nt< zMw$LCMy9Hv6m*}{Vcdob&tFz|0o64&?2e>n2_!Oz+C-5${hNcqZFNEjfl;~&5lxM+KZOZDTiZRPO%I1up*=!} za@rEVPg7_kNK$N;!sPm@YKIFWg81hSApYlHRQx035cN#`8Q6IbXEaK_iCKlF~b~Z9Fe54~MWN^qds_iNgjvaVH0hfHEsFawN0_g>A zWf?k2=HCfK6;4`7h?m)GDK74vRT4PG$te>oYQ8f8b*q=^{WJ+yyWdUaDp3pv z^l{~0!n30DYRojeM&xJT>R)(fw+`rkcPvRJh888k8a)|!G8q>pRcPP!g15YnBwHD@&#AB7VKU@a?q_s9 zSln5L?3Ry2%-xmjCKG)i4?fOw%jz5;*$5zo^y|F*R9uQ9^)FC}f)p1&r|YSKY~O!T zShvf96#W}3A(N`RSF9{>)1tMjqmcpl2INbm)68~MAos>c3y4QH`kXc5p>{%L>|W+= zJtgHz{kyZQ{a|bb4j_n%&q+IDscl4t9?TQoqvZ?CvU#<)igm;h+5P=Yeyuj!Y%F7~ zJg7_Uef}nOC)Bw1Vl7R)9C|%HesyjVUSl&?E_{*0uM7pM`GUy%rDXBGqI)rdeioaf z;!@jZ+mH(88EH|UnX&e~_)sShnp4vzRz5y?#732a(c7jC^usvxU%}AcbisZMd4=1!RTwK>s zm;0YYYV}~Gy}yzq1$ti{uHD6BxkZFcB$H_!x_CU6WmUkYgq?SJX&2x8saz`0Z%v^1 zy&Ws<s ztr#99`G{0KG(8*){8y(dFrYPfPB7N`@dY@!B53(wp{2XIxMFl9Fhck-b^QMjW>(mb zeTGF+{8iaE#can}ro~dP7GIL(kjqr-a)iFyRVY7IU(}&O1bvXGs9OE!i zv%8_@catl$Qqf?73yg>7?;;)0$oWN}t@8w##!Z&3q*WzE_IJlCEY~pNOp3(H&6anI zIu_r7OElu1|KH^0c(@=*vz5;{Hzu=Nix(~v48G%hoWF$_|Sd zkvkzjgWc2EuB!XJ9&0l|dR25u_ACluOGzVI_lS%ASg1iEy1H3BpM+iUcH|C)I2`NnV=u;h8i%fe?3R z#`rlq(xp%ILJ*OIW~_xfm>DSxzkQ=+V82NqXatMQ0uCIQpfqHtB?^n%(8Ng+Fi1mU z1QZ%VKADq3BnDU{gr$NI28NP0K$13d?&F=Q_tq&>D&~kFgOWrY3Q&Vy z(K5^L9U#F%hDeJ9xXE&-2`*TnX_8>L%9Se$=L;Fx2JX0U$tKC&=t07&#W}5JDqQxd zD<(*w*$~a-_|bcsJXqlc^gzvD5HqYK$Ox*MLtzeziB`&qY^b!hpGTYEzh1pm;{1G= zipa^x2rw6LDl|?l5_%2~PeRTL;h=0#YPHW0YolO+Hz1LlBSQAbCAd+6TqLuH#P;BE6X9Ef$lCg+@ZpK-m9NHM1? zYQ#1-X~k?~g;kO`LOfV(8|d*>mrW znXDnRVts)wo7q)j#mTfx(w7qnWJC~w8!_$sMF_WEa#=mlB7_QQ0u2^8P-T^eBq%Ia z3VFW$vWE&73Rmk+I3X z>s`LjR|e~+vI-262q1v*ZF;!f3AxX`N+V__21Tn^(=($anJ8mDb3z703>hTSz8-vc ze7x|%Smwir3`9_4DM=(5NUTJd(P+tg6viQvJ#vSLrBWE!Tn)8=Zc(*kQEe*Jim1hn zEUQXXXecFZh>IvAZDlJ`p;kKf?>{6D`` z{2shMP5jqb$%H||PMz8AO?vup=c8wOGm=S(f<{t?&xt+OP<5stg6WMcA7F+~HIkgg zjX?uujFO}n{vxIR*NT+yO_WkbmG!udSwB_TBdm@Y+a z()EQjO~|z@=31ynBP0G^)8 zA|P%;AtWNB5C9~`BnS~%IFshDI~ zg9K5O%DYo2n$4(YTZ1)~s@;q!E!tKjX>5u`wxgS<)~O|ksgj14%9T~9T9#|DvZJgp zk-GwF*;Wb>Zl)cCDh^oGs9OrwgGwVHsKF3Hm59_UMG{s@S}1K|#-7U!xWxpB}MrI)bAP6N*4{kLlT{$xCx(u-^2@c#>CbT`pLT zmaU@d?*j^rV3PhhVX2{JFc``ghpm~%ts5?9M$8wXSV?My2y$*u5`9yC=}L;DMsSiR zXd3~sx}t!Jp3r1Ow3O3BRMSONNK-^XlapCwB`ifHRRa@LP(@5l6gikQG7Ye;YXt!i zV1j~*qKZ{iL{!vBQAR*vB~?TKKttG+F(-WVZFQ@4=#gS9K;F_0H$hEMp#uT*3Kf>m$7Da&d2deV3J5TBm}WbS1xSf(xGSO zKr_T{3`;MgZs5mcfJ}=+PdlM$*-Df*c?@8J5Re8ARC+V(nY1?9oTMazWWGq*NH}Q6 z)-^XT(_%mc%J)PQSIYeSU+o*w0~{Lx0;4{F;kp>tOCM{eAUHD2$P@QW8Z2z@4W5&m zd`L*H4p857yymq!LD;B#eUz^Z%%Tb?im@84Riz7QL@Px|t6OR^N{q6ojisuMrBc!( z2&F}pSPLqkh_Q@qVxkBF!i*|ZS#1g$Lm`Tcpn#;IrYa~F3JS4cjY@#FRjF06tZbAL zwyg><5v>)9BNjmr8;FD^E3DG?*>#OqrHw1bgpyMuAp$*o5%hG`^lLsf^*CR}#E_LC z?($gJ^lKF^;HidYC^(VJQACnQ#n!UI8Hwgx$f~>2SG{zMsMmF(fgF$tEal zN<;~exJY-AI|3oG+_j^|Ik&%!pfYjJDz3Esep{<5uv(S)6@j=I-@OUSMKAi$Z$__Z~;R!_oLqhJu zXD_aqE)+CphaQi=A+&+CA+(H%XNQa5-1*p>tLffuP2Z5o(^j zV&H7dBw2whMVP5L1jg3`-BP3{{FaYJjW$^2Rzq!GgD$Ix&(94h*LIf~_aU1Df z*v5#!WXmATjX*+jdJEZ6}{!26OuVP@!I{ zXDGWNg^3_W#%y6$s8R`Fple7-_RaF&jv5S#hR92839%w31;RUuEC%Mr1`r0Y5|hH^ zOvx23wH1J}*-=qzP*GOWu^AT7RTZ_RfkZ|&)+~*(qKg(&}DMLZb*POR)g5(r8q=GjBP%#@q5HU=K zAmD&F<~KC-cNd{35wVF47ivasu>_D74EV*bgA!p>lL!(}O=}4w(fOZ?n)lf>Z)p<* zA!Y~|#?n&+35-NE5JWa32sC@*-7;|ZG?j)u;At`gAqo??sJj@OK)!-IN;H8;&{Yc( zGzXaw$q?8AYygk6aP$Sg5fle*E8vVE5?zkS?vhaMoe9;jS?r2Uwk9UaW)I$g=!Gwkf4vOD-27Dgf(gU|=l z0#<4UhA}ai0cSPHGpD}kL*h7W4iC%^G};pde`= z;{bt}(UPVIbM&0vxNRe&Lu1ha3{8nyAQM7?q*bB@N=YK?wLE_J)^mid=thVrpsk_o zD6r(a5$Zs5x#LsG(D?d1(fIj6U>8u5;>l19fL0#>v&)(LX6U;I<4Gk~j7F3v)Pb+pEBghC95Q+7QK4X9 znQ{miu7LqCl5A}C3{>0cTxj*X=jZ{)7RY;1cmdGhkDO4z?L6j6!O;XTRP=|mG`ku!p z25kIdr(=n+8G1-ah@XlF>~mc3@%QQ-o*e7~xwu5J17hj`D+olgOdc`!qna=X$i&7B z(E=F5Y&L{R3u6)99U;ld+k(;1xz`OUXq?O(f{A(n7G5~f)?(OUlb{}t-_Nb_52JpM zqvZNR^#fZ{DMX({B&N{xo-#&)rRr;Ru+N~LOdLMhlV~{v36qjTh9x^E)FcDLdBvGE z$AWR8^$~&zmd?sI!Qy- z<^5pCGP=s138)T7c|JRLw*He!ehmMV-y4FJjzD=izbMY6%Y_E+J zG>B@$A|OiQNM?y42S%lz7|u@?DqltfB0?pILzP{G8VMp<8K7xOMW6(wQ1mz4@0;G- zB6|=kGYtv`uV38fiH{xbjU)_c&;X=_i2PF~4aeLZVRUyI0+K>ZVK>FBeN~dCw#pd< zGVruX2wx0Dj;KboW49}X+xs9;u?E^8h%S;yVhRij3oI;O|~>DavwBS# zUB^r-s!DtdVa9;!Pd!hE;ThZt#5a5foEDQ-Dh$=Kur6?wcgM=K-qg22dh)t}G z4Z}d$VIUZmSU`TDe)c<)-udWA1dt<4M4@!&-;7t|m{ey-fsGqu8y$cKZK)W> zr6@BB)+RK2RT)ey3>efr$p~XvK$W3bp;=9ip^Bdx^EqW1G-z0Y92$a40z*`3?26Qi z*2VY31{=r0@$6Gkwoej|!~pR53_f%N(Q9PXY`=>dn#45nAp;<5F+S;o*MEw9TgMHX z;iw53A*9$GLQ0UTG6;%*of{L+ZWXz#TIfb_1RBe@6lBycowxTSoTC5hJs+F)*X>O$$uoPL4tWc(~ z$yT-&(PEVtREnZQLk5!uHe5+APu~(>>v*x_-7G$&w$2TDgc<<@4UA2z43P;U`Qd&W zTQpp;C%LIOaE39YNP;0G+7SsVjJJL^aQ7qhb7pwe1tZ6u1Al}1&zD6}F7g_Bpoa-()W3sD(7FT=e;Ve^gWxp$+4G8rM^ zAaoUBH7%?MBqxftqB2aCg2|T6qSh{X~spf%7wUnK->wO3402Kz+rnLR`v{qY~cS|#Qfg}u5Pzo(4V>~;&bYh#Z&g?Uy5O5Px5(pE9w(_|b4CEq4 z^O1O+NNszCUt$O>If60$D<=69`J z{J8}(xYCiNLpI46#w7kcZ0WMRB({oSwBB3{(;!S3%cPP5zoTER;_xvyhC{MRBoUHZ zbaOZPpN^CYj6}?=1ViNe^cW`z@L53gA%%=ae+=RJ!@=u`7&8Ey5d%Vog=|7jVG9Sx zvk!L31ZuCnVVAqPIB?FI|WCekf#E)x( zAGk>a=zhUN_o#XaHh$*Pcp(EQKxjz-3{tX)b7@8`ElS0Ljkea+3XrKplt`kgiz8YT zv|5T)XrMDPoSG7-at4aYC@2AHOHgdKqE=KCR@oF)BB;`VL{(U#G_5F$7B#C`3eZGE zt`ZfES%w*rVJ#XgRVoWnR74d93n5t{QYeV9P=!fQYd}ycRv95fN)(M!xfvspk`9Nf zJbU8-VXv5=VPuh*S!%K4rtFK$Z(Rd6gAUGjO#&V)HbqnHEfY$cVTFotk%ON zD_(MQDk)f?p+!m|CF=&ztzyk|X<+uzcXsAIL1UOwREIM>ldY4S&UvvR#+5)TP>Uin zF=aZ%39)23g+N z=;}yYw%1s+Bk~xtUSa^Fz zB2lijc@(Kgs>)2v-jdWzoHYXvY#Kw9Es+|{O<5?E#9}J)aHk;XR4sfIkl;Zw(TB7eUMEZ9<6SQ|<#gJsf?~f? zZG7d@j4EomE5^4o$rZ+|bY&=4fm-KW)o@UG#dD0ShFr3QSsBbIP(%F7oV z%9-5Bs^KO{6?rwL@-u&aGYb=G787lFtM2^!)K4!$>Ch(e{#JfgJZP0+LH>Oh>x^7-jy%8NG> z-=}$1DNsdI&u_c6oTD*s8&^`G)jXJKa#E*4*Az1SL^?2TaxwbG>zT%oD@;$Z{TFyCH zK+R$TiRrwXN4V7$N!8m}{29$yRRFa|Nw36nkV;o;n9{ zooa)&o=hZKSL{<*WQ)b2!Wj3nesviWQo)08O6SG&$PW?w(8*}%d0w_4XsPg7^I=SC1%W%61? zQ4+y&*c{tcqnGW*9|-er-f!XZd=hn`-I^YniaN4AuX{v%DiN0BZQS3TS6(=58|KjQ z;QSYlm*6fqAw92=R~{E<+F?jp(}1QWAvP z(^3vrnJm6M zne!VcaX8#7-rCxhC!`ttBOZ8zx~PQnh|Dy32Frm8Z)xTVWI9&eqfIlnSVHT4xd1v< zG~C>>D8bx}yNKI{`L>*60!cjSbabXza-FQIPk6#QbL*&?Io|7Cm%Z$`=y4!lp@oNE zER-b4jtnbOa}-abGM8wTQtsgX5qLTeeCc(5>jiMP?rF)Y3 zk4+!iATD9S=ZTVa&uc(g8XWA@F!yO&P~IV+JiN|TSU~#bnB{raBVOsaS*PapbV$0U zk{(4wdWvKASDFcE!zXv9J@qZVWT#+Fr%9K+%G+#IH)>H8VP{H2F>Y}ox%8pd623h- z%r2V^mm1eiMds9b4H*_Rix6Modd^n5bG_fV?Re@tdT;b%sx~fe5uBafA29nxn$k^n zF2u}r?;vPd3Q!+0@~&L#F=$DCAY_RyfqFY*%trNl;pyt)^9wUC81%tcZ*w)&_S4{C_QdTt|N15ci9o=F!BQd1SoCoKa99&4ih)uR_561OCI1o(6(wJ z7~1oQM$jl8m^%_EUOV($newt5%L)80M~Ai3X-c-oePMMC1TIfBq?at{4|HFe^;Mmn z`2;#v3~N%b`pORNRo#cq>fftPQU>|TYJ*{{s4b7%e`4O`BQ*UGqPs0Dxn3=8c!?zYDaXPuo*4#B9?Rq~ZQp&cX6Iau%oY^hZoRw9J5t1N zem_K6r2+I|%Bd>yWT3jgVoOWY#hHn{OEQSF^n2smfMthVsJe2|b2P2!A}@($?$rnN zx86c#94vhzp$6&;DXe@Wa+r=+tp0ZW3#t?67;%r8x^EXnQezlYD%PL92HMUwkP4R9 zF?$7=S%w%kf`!Klwt9-<(_V7`6E%|06@bj=Ej89dtV_CqZ@6Y0BW*%SAW76p4t1yo z%t^*4K~e;O$Flt_Bh98dg##eDj_P`9B~ux7$X)nUhwr?x-P~^lk11`<$r4j-`5Pe0 z^QXQEm+OOA@64X6;$`1>-Iy|KK3#OKw8ujq*@NePUi4gHJJhP?)X%G88irESE6l6t zjRbTVc7r$8CIA)=9_1~n5o!47EaN*MOx5IZ^uiRE_va`)Xttblm# zHTN^OH2S{Pxfb+S(<*ma3^4C;CWbZ@8F$;+_JFr-5+Yg_FG)t9GYI<7uZfENRD+s4 z`Iya&wGX_E9GzD2oRj9s%mZnbU?^z@saw8Y+F>m!a{9eR&ZSto#q$>K8F;g(>!;%1 zwi(DeILJfG01KO5zO)rP0ir-1a@$YuCw|u5Z7HUXnG4O>Xuj4Wyrrt|Xe~xu7~SCp zS}N0Bh0T+U)Z{7OYPqNxeCL_hJ?ryojU!D9SG4E1d#)@e=@aUo{89#ymp0Kt()1aB@NzrOz`VuJ!7ab?VKkoVU75 zuyv<;O);5VW>~7m#O1`|H+?~0Io7!gz0hrgv!Q^)sx;kr;QLWV_kOrtM{ynwI>n1C zuE>2g$=pE?D_E+hA;d)a*H&o3USzdi7_1!QIw}pvzY;f@1#)C|_))Hx;;3cBV5-6P*g;5Odidsn!kaPy~V4sGY z#zdX8{NF=gF2{ZQm#ZINBwAu@voN4zW?i-0*rUu_^j~~|7o^qU6sk^FE)O&VKALsI zvJ>-o-1BZ1Cp!td73X=QqsI7BoA6tHP-7O^Dd4SYm3spPo%5W$0vWa+H)lN&g*%4h1%Tv({a zg<$vdE)LYaM{fnl;5zOPV?1{6qYigM<#RErTHkjrm1Leix|dOY!&PAy5NZdM;sd1c z)Sqk4(0p%&k33-pw-ao5@*qHhR}+_Xi-6wz1L2FW7SpItozGAoa_*o>a4lHYF4#vh z+F6~M9k;K@AmaJsOv}iA4I_H%sw9IBNgq7k-^W_m6*A7vR!%_-mMZ8ob6R~~&ITd@ zMPU<6gvM|In3;KjOQ_xlb&WT;B!*Ns=BXUxMI=gXTMj2D(YB5783`{ zspLfY#fq~^#SVxV#o|)s!z}E+G$ip+ z6LnClCYf<>IMth*a|~K(j7&LoX*@S)SkbahWaDE**3GbnuIdS_yvW<-LoVaE+1_pF zot`1cUgXznD~7oyVj^t=PVTLJYAZI)B+_Cp)^;S(wXzH@Z#w5)5y;8L=TPqIyD#5e zy?vn}+Rlc$0UY~C8~4MgIww{*(REcYUDWmQtPDAb;+k+U?ka7GU~Of@{N9YPQ@ zd!mAtr+ZORmo*F&qiKzuctq!unT--j>v-;s5bFA&(N5rzdVCpj^D7qjJFwZamyHp$ zQoQPzi-cb(+EUX9EW1){Bv^}_*6C%LMB;V|&YZ-Uf@aP&D-tYB+ZSq#xw|uJl(IPj zEaNuTiv*Gp2$T;Vm?05!Q!o<928hBf(-a7#kx01AY*}Fx<9-$dxNp*vR5duT9@(@R$)2oe%oOuKF% z?u%-Ou!BO4>^;5RL6R2l1?EpSx6Sc>9rkx* zkvAJmj*3A90#mb>m^wYBu@;|eWfW;+w)(b%qNHXC)QuqtZI;5-Nb1{`^KztbWm+3L zy_t?~vhSzgsQF)3bTzm(&hnN0$p8Qk(cajipO#dL{%Mmch%7-2kzlmgp^6`KkD1Te z(QE7Sn@Mh;kI~y`u{G~Ikx5E{hIwju;2<$fH&0-w2pmX(RiN(0zMLsjF`v8G`)tOF zZ{Hc=a~#fR129L$xJfv~Sr;xyg^>X?7Q;IZK<%Tr-0NXZ0Rd@{Li8tQ9oR=3s>q`} zUgl?%3hJeRpo}#J1dU+;!M%Zt3wN*I{wMYC*Vpp@`&zbmB{7G%sxWhs}(pIse35_3JBaxtC0@^Qc(O`Lm;+J@=aQ zJygk&a#w+ed)4Y(TU+EmYS%^@HgGVWM1605VvMW~nloyyMR*X!>C-66xlDqvHViRy zu4gGpy;9m;8s$yNu~Py+5(d<_CAG)Nl=j`Nx${?M6)6y#FPe-2(?B-OIwAKa&%;=AWmo zKC_?i`T6@F$o_}&5Bfs{2li&)myv*k|Fir~v$5`DUG7X(C{qN%X{ZH-uqLcfws;GinQL5TlEW?CGT2|RhEumXfqFX^*5wTV%j8S4) zw8*l?+!qzBh*h+xTR^CVsbN|ZG8DR1gLc-ajcqKpy6o3&SkgWM5fCs$UkA{`w!0sH z2gUoWSgb@G$zVu2VAhfhKQd#_1dAWBw*A+zTqNm22{1$R@*&y6@QBp_iS_J$TkwBZ z{U0PhtQic+@cLzD30CVv`Y$NRtY!rnge;dF3z3%&T>m6J`>)dv;4u3iDo5T|etEDy z(|x-u{)I0AOGT1$1NpLB*V&;7BQMhmRkzV_M%%Ohk<;J&LzQjVt>XAr;%}tOalBIC z#n8eK|D?te^?Ot5gR}*L5#r3}{00jO=->6H2)9&rl;9^O4@O>-9Dax2;QRO}xXp}! z!Kc@@LJ8^6IezRz^efNLMf7&oBip&;tmjJQ`iTYYr}(=RvIwGb2lF$4gAIEbasBP_b+IfLR0i!o?PkMs6EoBRjhQ~o~x0R14( z27|fhK%bq!u&e{P{{>%v+J_ZVL%-M3@|YZ;{g^T$BIBbzKhw6+RDLT*qyAxv8CutW z$kP8QHLu|bnxbba$b2vGQ+b9Vtt&~U4evIV5E%c_|B3%E&?{@E)2MwUVE8DHF*IpW_L8>b+rrt0N2&ExmJ@)G(NL&IFT zmx4la8IbAZD28Ox9ul{5Ab~K~pAv*z#oX+rbWsIxNy~}8S~K4C!wiv(d_OcZ&I)T_ zpO0TZ(;YWaZn@`{21W1B^7n$;zpOi)X~1D3MOqPbCM`^ZclIDr15#&>MlB}yC`B|T zET$3M@TD}OKcvXKj76Z%hX-Rquvr*%Kw(_7)L-8oAomIHiatA_U^T?K4^)}e&)okp z<~b5?4G&GxQaT)Ei43mabUv=zO16bk$a|oWCLdpl3<9 z^vMK|fW~vKN*|ew;}dlc_EaceNc(O33s?w%{qd5|3k{eL#gp>Chku2;AK4D=nCGP; znlUBEe3jgnsKL;7!lp{~&II2YoBcy+lFnYldSg-fVc7YdEcc%%QQ-vdr@neNb}V`u zBNr~Njkd8!CNBLa#p3C2e$6mMUhKTL zuh*mz=Q4S?(Af{QQDP%@Oba*lc3=n@FGH04KcA{+BkQbV#4wJ51Av%)hnBM?-Lq1jn|KcR%a&J5)%*P|M`Y!y>zqbRFu~|w zI5yWE;9F;Ak;)Lq-pYT5@tJkb9CZ_+j|<^QKDM89%Hj*SCR+$1G#2JXD`cGokp1F5J`JnZbL$LYXEF=x&bjdp{%O@T zK5a%H%5Qt+F(>O+D=74*d>FW}wUuV_)}%s%fQ>w6Z`)su#<{ODU~sT1Yr*aErc{3Mq1E%_2$$T5!(>lR-6ZIHR^{OS zkcj(K@Ih}draCI}J!=)w`O4!m=yH2p-!k-hj*<66S+OAkVxlY}%EBRfvH4e(f;;lO z=K(%O$h^BL2>CSE7)?qr2ecx^S;Ieh5W})PSvZ27=!M^0^_J!HesFdzXo&rSVhpAm zV;uVG64;(m#jjF^LnY|d2(8_@bLF|n!f!Tlw_{iAuI|%-NjTh{8$Vy8(%Rn7S8Cr3 zYaXG7$b0LOMbB6BCc(%bB$`cIgbut=tES4eC*efnDyvwgu}Nc87T&8_yh7O!dZbWX z1#PZaBFY3K&S&zi%M6NO1DB%XfC!S|zg?iwWDvJ5ZqP~M69G0g^FM#f-^77DI`J$!{c@Zb?Sl`H+NIvu zhaNQ&!w#^w)M2vYXo+7z4mYx}hhIp_(vF7DLRd>G5+4?YaMF*wAl|(`3s;iycI)Y1 zmX93t*yoJnkj|Zcfd10Nr|Sr0M5aT8RX>O_t85IYXr%i>PyM@YMPL@7FxgR2oz~dUXbrrJ zuTA86&#rsdwY8I6HIoMZqUVeJVu#f_MOoaflsV|vSwe@$ppNN0l4B-WLsj)`R4vus z$4hJC9y2|HrHfW|tihv^?w&o`JdE|W7+~(2T`Ig0@g{`1N+btmp&>zc>%#tvNB<26 z`lyfZzK9P8Bfl)eu)KR7>XU{QVOH#e-`&AnLH)Tw*SqRJo{k{qH~f>6#z{Mn)|>@Q zMHzSqDqE(vlSL%>#zF>|Y3gQ3GPG*?%_&4&Y-o5H1B}-S`>{`JglA2bvbsX1 zc0oZ!9kp$9W$vuijVE>PvYMPYcvCL5YeJ=^^8VpzFHz56y93Z5WDNN;LN3h01z(}prF+Wz z&pO6RK1P(dT3i?U`A@e$(0?D_BTr}G`9X82$FV*4*k5dnp&1i6BIBa+N3kWu zQN96#wG~6`K56MC`OEX-S(EkG@$FRf4D`g^Z~cBV{Ia$FBxgFtn@zXP-;ceeU^MMC zpNW|V(8BYjbuWy$dac zNJ7JJQ)NoqyBA}W#Yz|`ZRkuxO9`Whz6V7G{@v1fI1OKjCFxUcElIT=>52Jx9bBe@}Y8(xLOFDV>%xdJA#XTW^qmgn%Rr zBDmOgJ-9OikxG+%7-A#oEU&Kb32pHenX6)L5F04~HWVAprLxQnP<7+}g7{njUWgdS z=z8nqdCV|~(Y=g_8aB@xB;}6ewJ=~~V=OphYVCq9C@{)lV`2f3f?Oa- z+sH!0j;=Al=7==Z5b#%YJ;lGaRN1%KJ>lbKxqFt($Yk-BaN#~Je3ZdPWspU4Eg`fq zv@nLHiHQ=Hj`(@?@OC{r_Ue33Z;!lf4@Rh?_UY}-nSbmukUob+#ln64h4xMmF%CD` zu`E(G_2s|5r2gbipj2w?c*lXo7o0t^9$6|$=)P!HN}Jt+!{f^#-uC`GpIZUA1n+IKxP331Ytu*!~BWbz1uKr z_wD##dnI4g93up5X3PZmI!Ao?cokS!#T>_qVQSlia*$a z3nRm_9#ScAQ0H>y6<7RTGgu+tVa^CVyxrW;gC17wb=8RpG?8?Mdfu|EI!k1fnL@w0 z@X9`8-%(-=X&%qW&rE3%&|5w}CPZ#Tk|{SBgtTKl59@ie;#wG%g`IQcaC!((>=pIi zUS`^y4Rt6%gP@Lwj+v0$I_aem(6?vgL75FI6vxOAL`=w)9$FRO1v1u+z$Wt58d%1s zdV#vMuFTdEE^z`fMzflp--Ls$)*jyz+I*z<^G|H}Yd*E{G6N2qSe)u@4NIyaS`z>* z0Pu#$7O8`hp7r}rY0(NWUoC}xzNz%ZIXqx`v^^dR!oN14#hRZuqU4JO_w`IfYo-922|5Q6B5A^ES7>LpiNPAC?@v^0dOD@&_v1)zNSm*A|ct0X`Lh4hMZxs`2{!`QKuD6a6GV${+6kB_(SXEoNc0wv~%es5V(rWR(b^Y@&hwg?)HFw!D3? zK^AjlBM&MM^-aj<_;B>FDkA6nLl|nCWa#n2OJ+xLl^Gs4{@z;~xgp%zc)hG=c`x>3 z-XyVuM~`~Tm99Ri^6_)Jwrc&}#K*JJ#Yxd)?sn#lj>Z1&r@uhX6*JQC^BaJ8k(0P# ziJ*9U$}(?MH+>k;W_{3;2Q#6u`1~}y7Cys>B#0ySg){sYZT~;-PV`Q$*4FL`bx=wO z>Zw)Ll63{Nwv#f-vU1Uy%olZbg;OHR6{~e2WLqksj;hdZu_FGt$o4((NJF9M&=r|Fm5{Og)aMS8 zY{8=?4L~;8UlMTXuIE#&JV4EsCuWY#?lJ-fX;?EnG*?B;3@kB}_^HFYGh(JI!!&dl zG4tQQN3T3Sy^rhh<$F|fvWg2LtWa}eqDrl@RH;g~))|(WZr5hrs#~hvDpyuqnU1$u zQK@oewV{(#v{N}yQD7`J8*Pmm+S<0ZmbodCW&)CDTH6IiZA{yjRZXalskJkeb5|nW zv1H7MMgoyyr|;i~f$rLVk1Vg@`wg#Gp5MQew0k3oH`WAmr_nos8>s|i@!=Lso4nhf|qj=ee zMxqYw(Ynz-$V~;id$oF$`}Tha_DI@c-Tefg7W-vkt^XZhef4~4JZQO+F_ID1Hiw8! zn?E-?JyHmQ813Gfz>+PY0%z>I5pd1py^*!L)vxqqS;;^}T?)m=0*inYonbiP$JRu9 zBiLW80{H&5;OUUi{!g>+mlS8w`}+P`+&{nX)BAbccSA7s{psz>L|>xB^;yc5_Vl=b z-dPPD1F(kVxC4+5n*kA0EiK2?-C|~IExH;Qb)e86QlUTq>dtekI;zlviCVJA?m^YQ zt&&pQf)w@(%5YvSUW_oYvWC(do0(593F|;ux8*cT~MFdcYh`29bpSAn>j{*Ju55Xr%Q|OFT6W8oyvLs%SQ`~n3UG0OL3Az*Xf3OGji7zlL`yOB z17Z6XpGv_s9F4yZIu{(Cg@#3fV+Bczl2)@p!KRFlV}GIlHC}#?{(j|QKR?qrxn^hg z?0$HKUfb;X7xw4&^}9FH-9#8?pzArX^X5KFYjF}rMD5)-Ige`YJDr|hWq;FsaVXW+ zW~}KagDa$F!cng|7jj49vXno|CS5LKnT*XarB;Kx>ma}8#noF67QSBmK?S~16&Gx} zEja&-4zYOyW&6IW_KZ?*O|pQpl4l`=7;AMNH0GuhjGWFmBG zpp9n~%$Lf_)7V`jX;X$aV~M%p5jFoyjXNX%Gn>s=TU$U#lf%~bg({{qV%x8+P9f+j z93cvBzI)*=N>Dmc!UMph<4zrHe^b%yKW@$Vwr<`Ivs>(-M%Z} zsjp_vBQWcm6VGUKtYNz9!N=kzgZ!vxu7#UU#5Nn|WC)cqmP^AuGA+%$cA35L6fe}C zFky>`3QTZ%YleqI(T-6;)m4JxkrdiqQ5lgrRhn}lB@PIWDe}_q-f{;WaL4a;-tMm% zZ@z`p6B8kekc(1vL(zM?{qnWt`1V2A@_r1<{R~X;_c5O`k=1a3sKwhQYvL3cF68?! z6>b)?H5N3rxo!$io_8&E`2$cMuu6Y9L4qK}d6PSMsYQmLb7eh<-8yf%+;`SDa*A`= zD<4;A9JeHB6Pt5$&{D+U^Lujidk(P_Pi-^Q&&Hmhq6m~{tb)kh3L}MNyDHn=lA1y( zu78gIpY(r2{U7N5^gqCm0`Lf^;ef`!-c$MhH%KqKl32YBalC6bY#S3H1OIO%@6O{8 zA!KLxXhI=Tpu~;~=vKqOZI2m*VJbROCl;z0mGP{s*(RR{?W@BIEe`ujh^PqQ0j{%p=_kmyS6Z~mtbWBY_~ zpWRe{9(_m%Q~Vj}TL0hXiz03nkq3w~c4#?EUY5FH2UL)^fdez7h4!w4NO2b|MUn8g9&-T;YRM;`qLImP{Hy zk<*5Q+$@c4KiwRVKV@rMMFezRN&5Q{hj~Zu{sYLvMn5}!#{;m=cDvpXpsOr3WTU@B z3n)asthi(dW0D7MbIhqBxaT_~ck0W)xX{f~!9vPw2nf|I`tv&&j>4cJ(M~Ki_)~ClbWM85HhFZfa2LpV4Lc zE_rL4p(wu5LCWZ?w@WsE21l^Q2yemYxcCd;Apn$rd*|GEJ zGm{!0!g=8KAP4XeWHJOj7;ODuQ@E4s>XEp(?$;r55M0;nc&JD2KSg2t{lYpUtk3JG zPY~TujEoh;SqBXM*K66fsU#|~C_Npul_E}v9XNl%AMN}P_&4x=DE2=;QGc%&88Zam zMv3?MC|qlTAP^8Ro_zebuYB@NVg2L#`^WJASjVkoiA;=X5Pt@~H`;{#@4E&c^FKQs z4u`?W8e2I6@8CgFR0zxpKW1oTQy*S_=|122`$qqyTkNlBcrShr?CALzpqq)CtVKTURok|5mw zqW-k*gdh1@f~v{=ga3)dY3;JoFjkD>SJGsi~o5M7R{sjR`8(vOeumLpDGDfbE z+lAMuuVv?dqv1J4QR7<$1Em<_t%nKGD(kfk1ViCnU$OFC=K7FJ2^EmjU8d1x>~KSn zPxx2vSZL3HxrXEHzbK4(_U49+oUC%o;;vzc{-I!#ppKTZ>TOwAO7o9*Am);lUjHpF z{$Q-eZs5>W5H5aWrehbK744tQ=ZnUrLn@)(&d=AUv0hwFw{E=zco>gNZ*&|WM4<&R-c>_b#5|T=p)M^SpN6-XR60M z-_5?PMWW{F$ht(abBC8Z5^L5UFLhb(8pCku6PC;|wnKfE1Kh|M>C<*TVhm!PhF&+5W46#OK%7e# zR&N=hQcGox8|?BxFFfZDA}J3I&)USZb8W6O4V!muN3Sn(X|^@WHrIP(-*0tVv0FUk z9%6jK<}DcE78Q%vT&1G%i{1D^%R(;sl-(oHNeld~TRCCi zl!=<(dv!U&DBeq8)ez3T-Tv#l+d~9oMZp&sA%q#^!J(tMsS_Rnz={1Ba-Uw`+=say zHGqrF9Fb!8=OeN&R~Ro}Q;%Cm9b7Pcpf=hY{Svy9k8S7chTkdDh))QZyQMF>DO#4# zr4q-@@;)wzX%7E@`6oWSYOOvdg18x`u#O%gM#Ydv=!mC&vWA@N^E4#dL7vtoT{Nt zw|qyMJ&-;!PIjJD)HU2fmL0<%C}&X2b>AF)$@!cZa5AP~(S@nUmVuj=s+UZHFz@vN zRNnJp{~Em*7=h^nPvTJCc97u@v;)sln%Q1IK7S%B{qNt-S|k*88n4-U*Ciza#XluI zeU9(_|6G4$L_fkaFhn>Oq%#7IWoP{msT2_a?y_prImWA1g&SPpRf;T8RvANS+H%O+ zQ-kxi>A&FnN8@e|r^li0ejGbt+VFp``o_!u&)e9~6+S+v+~o*fUY#x!!|8&DK7ky- z-q^4of`&d{F7Q?n(+;*FUv=(y5V4+6`^HY3zHDo!%*&y}g}!|0%M>X?I>$?Ik<+_3 z@yuBFb-7V9TpFw%wU3j@6ppz2O@$V0ZB9Yz@G1PY+V!iDmUfmSG7 zR6Cf&3Sol~ct8+?cP2bvt67(lVhT4fHfz4=Ls+@GRQ^7boWYJLBpD2XNijxr_~EQv z(cf&rTWn>?f=Frdg^fJ-yS)p^_e||?5e(#WG$f$lk=BQ=y3pyZ6k1~$mO-F!-7hjJ zV;B!vuE7;j+M@vEuN_#YB`0cDE#}hdc6!%4E8LALbEBSh-FrJuOotqPGDz-Do% ztvecwqW50buXe{98mmWLVI51gE~PFu>{S-#)a%yb>1I;V|1C_FSI1^@&iK`?r()eAH$TE{S%$;~uhc-d zpvzcffgi&kj`rm05BMYcNsT-XP*Q34~YyKz$jqt1riVuxQUIB zB6?CpTt1(nua!^eOJN09jOUC9`e}<}`e2L=aD+g@@pMhBpC0e$*85Hhfmk}pJ)Z}| zUrXMQ_WBe3t^6V5CTsT3j!$nNDe|iS%W3PUpL7s)syZIRb=_uDSgGSxR*iINI`wk! zbxGeAwjiwp2uDo$RRRe$TV-v!&-zaYH4|j``Lmtn7JT_f;`p93)gP~&{(}Xb)_-Po zDxhSYTU5}ZCGRGPh%!qDLW*Q7G>BPtX|k;?=l)ZDpw!>prI~Rp z5qPAvinmtnQF;<6M~8dc(;zWd>&eBo@ogg_41f`vd^SK_E20?;go+vot*ULN?pye_ z-IXCQX=YmTt%4yCFGIA5a7Ltva*|GT1R#HUM{7%#%MGP8C5~+DkrWq2M83mTTRsmdnk?NqAOdsukAnA^(|#Yt-rfm zOS5W@Y9f^~Tu@t47FvF&Pav^zO8tK~{@U{S>X{L@oUHd=FmAIrd}yi(nQIu%Qy>sE zWNSv!XvoXg-;3aiJRmNUWt!1P+gsG4v5N&qU!C50_Rm6#I&Y_8cUj4^Ju8EMPQwx; zBoQQ&qe58Z^gd$S_~VbK>iXT~(>}436Xhr2lh3Eqy&LPkUnA}If$qu&9?NstaLMUB z`}A+qJsaPyif%icTKNf|C1C0moXE1MilFZ_6ZU=nulZk(-|_r2_v-(*`+R>L{yyL4 z;%pzSq(5)m8#s^qpV8mh>*s$TyXYkR)jXSP`_{qS76ze|p$0tj_93+Q>=GnN)1C3#v7b@>I3-%8&3LVF&F z4TbQlmN%gIb~t&mI$&WM=@^Oeg-~t^jYYLJuu73&XV9TB*w(yG z&WDKFuJ$u(c~&Cq8YWp%*+)_h^SW-F^BrlM~S;^TvMKJ)3ttpWYi= z`ZO=`wrm?!1-Ta=7N6C&kg&bDBpY`ZX30MKr`_^*J7dJ(2U|mFjV4K=0w5Es*H`*1*Y<0W#LS^? z#Ef5aTY9y|@{8VS!r!U{rETY; z7hEwqU)2W^8m(sMj>yY2C9U5l#j}h9OJMit-4*(|B#`cT?dsZ_!|u#ZAp#7Vm6CVW zn+2prd`cIlu5&vZ^m|-D)w)y(a>-xUf;gd@Itj74X*`KTxyHKBe&fzgE8)Xl{J*{< zn{w%IDPrz1EQSy0LV)hBcq5QE`nM+Ewhf6>aAbt#RWy3sg8}TirNxSVrd^s*b3l_XF zC5cO|ihDa-q(TmqiR)+b&jDUF|UzQpbK zoeGXvz%EF}%b#6iUbGUV120G3RAA9)ePryX$>!MS*G$#f!FC?n$e&RNK5up9{Jufh zfUi+qtjVQ1$G*-Z)?s%?H4|WlVsVYF<1n5)iesuT&^{ zi-xd4a{v#O_o>A|r;=6C46%&0lY=bqL_i8_-YmIJdVtDD;QCO|46EKS-ij=`cy1cY zt5Fcdi#i-OmVqRION{%GK;QOu9Po?Z<03&iDAC{3KW?KX{mN9Iytm=xQ&OIH&*#+q zPxpQ+p3l#A-Y)ES3$>KLA7dvR>HI*g%e3 zx+YuPYK6oV?e_;gZgR5Cb;K`NRcO%Z8etC+7_y%_o>FdCv+H=U=O!>mbBPsNNV+uI z5@F3TknT5!mBy}|S$$U`L3Zef{PCZAz+wbOdppZ}eUveq2B zIi)5xK~^4l9(BMgA{mw}{ABWjl4YCx^}Z}Z-xm1vY=}Bm_s?N`j)-H~bKZq^K}Jj7 z>ThCB)G=_Y(FM%dAx2P`=?f%ts2YewN5h z$-^sh%dC3zQ{Cnh*e>{KMOurW2Hv;hi*8LU_79pd^Wo>mw{|zP`_4)G@VX7@Dctqh z<>d=VL9*eo>T&C(yn8s}*X29Q`=pkP2mCl}EwbJD1^)lxVL{*W-*<}knQ7H*9 zBS7|-G<|G)PEQb@Op>j*N%i{wCw*P~3wDJbuALM7x3Z0GT^f33Y z!9*g16$$3>&1{Epr6bj#hrmljja7sKFS*d?T<&TeNL%*Or#+!shHEP&m(h2_IC7%Y zz7F#4DR!LlUv^~|cFz3uJJ*{u%&pkPrBG0*Z)}68+bdEcr4=mKMD5wPJg;qxg}u2^ ziFoy=JIe1lMBGu91JMYYZIF@(fk+y0atj6KIZ{-^k*pn8>Y!(rljU;2U0){%!a0jv zOTAeGox1^riu8Na1`b(u`cPdjVY+=aR^Ba#RT|GT$%PTK%8|3@A&)Jrkcu2xPgqc~ zk{iMk&|@n2gthc4ZFkeaO7_`ZE)I&2ibt^vtQ(1DA#`x4;{cAVaHdO9%`&|?Bg)-z z^hl zh+AaEBe4cA-cOh@nxnF@F;ZO|Ws;qP#JjhO{(4q0ZbqnL3V~s;g-d69_3C9LIg$E# zBK%Csi)}JiR9|;s&B}@g!Z{S?k)FGn-F^4$@~g@YlRk_U4Q`{M& z_c#*gMZrEZdoiHX>zj5-EkE#58xjY~^_ulesTv;t8XZuu%7X86@L36xMT;f@7zpg~ z?-|CN$@r;qxYlS;R8Q22&tQ zgX4*eWCd_ux#qUV%pgpKpN`(WgFiY@;%3srPTs0OduXnZ@Xvo6@_=n=&KmVqR*`HU zCIg{JqzP(+ZRXhZDAysD)92M4OXr(1r&fx2XfabIZV2X$EK_J&&yI6})7~WvW3UEPLF8DBg7`V)o>L&85<2V?=kQ zMTZOrEJcx@AD?6F`#z7wveMS;K@hh7HQLH`DN-!90YD-_(JUkjo$_;+XrFC#MrI2Y z-f~6Z)&%K^zKm(Pr$|>AY7W;-SV#*<6Wy|bO)h~*B^vj>QvzuLa zdM1F}lpz%8wkOh^N1{ox*|zKHF*b#pbt)*R8aS}5zh`WZ=)CE4I5SQ1FHCZ{d;)D9;>K8jaI0GBYwqIY9eXqwvblP3`1}d;1?(u z?R71?KEYY@H^+i#a6!dz!?s+z6jpAoH9aGK$XG@9{bU1WBO-r@ORm$J-bS)aqxf1Qu zrd5{qEGd+D}BXmIM(duNx-v4^v#rhu$M;=S`fRR$ejCV|1S zk_v-+WXLv;u2N))=<=XLopmXJ5&SDAlTf|-))>0>;;uC?rG{^PW!Fy64j_a}3oy&) zkE6xunOXFO^Llg$j6`sAtzzIS#)a#-*uH_vZV1&ML~wp(T(IH8Uj? z4vwKoqo&4N6hSA|^DnD;Hobb~h?qs^Y87qcsHeX!dl$nTqXG4h^;RVNZ=yAD#h?Z@Ul8(r5sJ8Pi2*xeG2 z=XV18+g(eY#R{u=w-XT$$k0cjY6zp%pI;US&#y<#^3|txExgV~3r$M9I9O`tx}No& zdYbvkvj?n{9!tx7O}N5a;kh2OxOaCW#;tc<4NH1Gwe4E>JKc6igQV!4lJ1x1g9c0}<8Lt(!f7mHL!5(Mc22R`_E%Lfq71EZ4@GUF8yCtD!{ zX4)hJ{3qxm=;&b03 zXeH#--G{FnY)T3;1r&aa5|tKKr#w}Lt`Czz9O$D(Mm2N!E!LX^u3nnU)afV7o7H9*yFsw8JXcrg!yb{3C*n>ImMdldTJ zW(g)hT?j=|6Zk+;_iQ}%@cGa_pVj&JkH@f6AzX_J*w%h{z1Ce~t#Z&+vnL~LwopYP zn)9c1y0M6i9YrcdRts%R%&%D6wt2?iww^Uu##OaqS{@fM*rw9HZ?5Jta#kGI!?xD* z1Xp#(HhnYb&fGkKHx^iLtIC&6?I4g};I<{v5?YBQ`>%}g!kx>nlnmaNzwHj<`KDJ)Wc zxvgZ|d9Bu$68n5^K^379=QEO5uC)~A4Z>O_dUMepJFa`o8G5$+TgP>!%l2*Bu@0E~ z*uS}LXth=$BVC?|EwWxTq`&bBu0#Zk6cLBz`<>ko!D!7ozrteok&EEkYt8i*-WK5Q z!p2wRlv&3mo5}pMdMo;gPv1GpjTdq)gBqXkh0~Z_0d_@vkC+TH+Bp{oN0s0g}IkR2rjRl8bg8Sp&Swguy(r=b>!Bpfn;K@C+^PQ8spmsvN42v(gW^XHQe^@ z*bj^9`R(}`Tj-I8Z%xO1ew{GE(FE%W5t5ev0R^nz>pIzKg4>%|D1K8TN{l&Yqb(#~ zhxqml968DMHffZ2WN?^m@!6yS&8jFM}gaB1vn48@AnIL83WjKM7 zXbxfsmal}o^Q#kk#G*6gp$d%;C*@;8(ghGq*gsBJBAuT|YIzvXj4v+$`#{|g=1Y); zx7oqkLBV2VmJD=DoS!1hg9fCCbsPzh5)s5#3?@GT%e-#-GdI>56X>X{RGw{F`*vUC6!|Pg30u z@9D&|&z8N%TnrKnLv;a-43B3j8G?o0F z7#=dlVAMicQUQSt==On%F`M&`SCMQPVeTUDav7ajwnR7IUN(2jEB6fDE|11N82yEp z`-c#3O=Tq7Y=(PzXVlg)AbU5qHrMzMZeZ~@v=k(NJ?+L7aP9SawA0qZ-(NlzDN%{Z zS8J8`e{G${|7T`w!y)ILJLs-9VEF6YzGtpvIdMRXF#f6V$H$KwY2-O+l2TueS7wXq(PY5CjUxs7~x)_x)m92sjMrFO{+JI-O)80 z;L#Ku*BSV<`1O2GUSFRt{1EX0eKY!JjP}Em?KLGa$H$E-Lm)l!Ix;~zvNdf?2?I#E z7aFSyG^NW;YcKq}x>t^EO*!K%>{_#p5c{!LQHF}WGp|iiVT2rr249jGWf-*1&>M6+ zI7YJiO8VozL>6r3G=-@;2_PBJ*GU1x3p#a6qmF{=E0-#=L@T;k+Y59DWN{8qgOFps zkR)F5N;z>TM3nWZVFz5713Q|0GJ=z+$7N^@n?0yHEkq2(rWF|l_JcZhwQ*qrk4Hmi zUGswc$yA6|0$~Z8q$@de1#Jq{tu1hYy6PJDov4nb^K2odhBl#~V*<4%R`J=ZxaGse z$hJFjvL9IJJ6%-V<=xC6kUKO-L=&wFD3CU1h*Pj-qoihZD{}9lM^HebX$Yh+?5eT3 zuH|8qyb`Fzc90mo4!305S!PHY7%uvP@_ZpTZBtYj#AVk_0_~X;hK}e7)X<{9$8$I zauB7ot}wg1G14vra{a3AsvZPE7T`Z*gnI zwFpAmT<0=ad)Qeedg{<0aY*#R-EGpW$2VAj=`Lq>ayn$EG zFe#o&imIE<%)>3WtwUj}1BB0W&DB5T(+2W+9_CgSWH5x5zsN27XS0UP`&s7* zE?wEF9iu9#b$n(xUU(UYgk3)zmuw-jCaE%kXcUdteRk^m@A@;M;EdOZv2%6S-e^Bd z$1pm7IJUg_Qay-EO)_s>=y_(NKLyYoV=UM>M3cVW;b^G!SEiFmi>DWV)#WuxEq6z- z#z6$x=+RhKq^xOQV`^4_NO7$;J5+6T<9%)*$ZtGnHQ1JNP;S~e-EiinN|gTw$CvBu z{7s>5rTTSwtT{JU+YL4DE^pF>>anw)e_& z(G@a^dEI{J%O?hWayS@U+~q+pmK#5J2RQ9PkFDXQG_2v(MVA>j`CRXP%#d}^r>>BP zrN>*UPW+N9*=HKFW{I-C7h=ud0VsKu#Dz$3pE^VK!e%H&64NGXFIk(cZcmXt!XOm;IT>VEIBh z=$q=dkLzIa{@+$ycDG_G*BMggd|m!_?@oZfg_}To)v&x0H9#<0n{l$^l+mTyYStFI zIOJ;Gb*)g+@dSHDfv*A3{BKKDl5?>jwJJ+K`O01; z%raj4o7|>cQ83W1Q18j-dK=Bo;xU8AcjN>pWaUDow%#$~F+&@*t&N)2^{g^*)u9L_ zhJ|ac(OL^8<)WvwVARaaSMmHexw$U0mX}&eEnyw1k#_I!TJlf@u%eD&!N5hNlB z(DM9FJk0f!O%r!9)Lq;?Fmh80blceEP?W!bdF!6%fP|5}Mx%7v38Xkeh}bAHO_cuk z62i%^vw8beO1V?oisjaaZtY%~3$^>k{Jp)WLgn2fFB3lK>r9F~ONUnHy5_1_MEKq~ zlCpji#5_aN`s3F}zHhHSx1Os$5VN92%HkSC=I&9wwG*Ql%ydj7H8P8-(q>f#VGJB0 z2Z8(ygJXlj`|g5`z`9r+BXLgKb@|z^Td_i2C{4@7uyWiGt&PkgpuJO=$b!=5M zgW^UW%L7)W_TkR3^7s*9Mo5y|>4X8W3QzSB$ldpQ!KXVEPVJz9FGV~kI8L)#!>JI^ zM^Hh5a;_@5(s=qo9;9Po8Lhhn8z#|keb^ol?N#&CfQ>W4NU}fGWh)%8svLsDwQ8ik zyAxBhL3hSF_Spoz9p`eA4p(u5J$XmZR=H%T#Ggr6uD-BRjIMm6UH6g>;dM7kS*XjZ zA_gegqh*ULWS>ZVA@VbjtjS+q>H9;(?^b&I@Rj)(ET*drYzyhhtLo76Xd$Iml(bVJ z1f~zgnBPaP-cj|(dhn>T=Qr1?z6@;&Cim#RdF8iJW7NH2l`UH`c%P2FN*;NPRH!d; zV#~0-W*+N{dz(>-AyNcHNf00<%Bm4ncH63H8ma`5Ax+eQHd92z3QYuXc9yK6zB_?8 zmp2n2g=Udd*)YsTuS(U6b^RAN&SqZ(x*R~n6S*H{?5IM$h9Ss_(RD$_(h-D9t|Xld zHt5-y&kI^Oz(-b1w;le`r%^ zQZPK4$zGEZGF$7)r-#(@0LJ>icHW@vRIsu5ShN|u%k@b13frY_w^km>Bdd3pXE$k>?I^3r%v8X6oL>NE zLc$qJ zT4z(lJRa-6XDiF=F(PHE7PBpELJ4rgJJM;ZPfG+MnqIo>y_1n$v>r2lpnFw4Yfvu9k6>8 zE>t6(13Rg;y}Z6tJHtdl>M@xQuJPO|gJs3prqLpBPhFnaPU?=xeM@@k3+Q!?UKrLb_d$+l4 zm?J!I2z_@N-yQNVjwx6m&=z$ph8;;KTu!>$9X8?3o^61`4Y3eb2I$j!Gg%?SMzNE+ zu#k#`5&t%|L~4#Q*2uYwe^(D22uM^xqRpSiOs*T!uUJD7(E@qWZy9s7p!R`+grqh= zb4G|XVry@a0q@Y#=QoVP_m6N15$(B$Ao?4)_@5}VTC#j`Z+5-R;6XZ3hS~(3e5(hU zbkL$g+?^6waawX^IELMovy7z8wm5J|Anp3(_1|xSaK|BqV+O;d)jv?MaM8m}-98;r z8tq}kN$T-|#^MLD!yo?dM-zYo9+o zcYb}ozb~Qu^PgZ!3Vn1C{D1bG5EDF=aT9(Zh2B4{kiou{4R6O^N_fb z2cl1H`CZh?`AB``acG}&y)w-IX3n@d2@u-`Hx4YwX|PSE_Qft=Uf4;;6Zb!fqW;;w zsjXbR{}hFSx1+aH{YUkMBqoEzpIjFO&WSc#CLmj5Ru2LPr84KL)vz-2t7fp=7`d?% z4Io7Q;TCtiV9}#2|B9UGjkCe~{BCQG%CxNlqJxQOfQ+O>MCe~XCoyD%8y%2POjzw0 zD1N z9pw0SFqge5s3K%;#jb#0jJ_JH_eiZ%__wMr=wxS{Z z4a=s;QrZwf@7eDCv0H+MSLF4d&w#WA9a#~W_0mv_A>0eZAY`2h`C1T9E}h~dk8Feyoi zr3(OIPjl5n&9(({rx-k`37T9@kTj~zJ!yqo6cW!r9^ zH4N<>cPKIghyv}@Yi0{UBV`cC6m!oH&an9Ry?nfP*gp*`3YE07DZ!KS+kffHTF0+; zl=suSkXeiRL}ke|3{Y$jR?iW(R10rU1r=v#b3}))KSAjW?_zQ;85VOY6lRhf^7Zk< z7YyVre?#_kE*^2^q0LAcAp5F}1g0{U`R;bRb$Oz#o%jhJ=DdHhRwjC5SCw1~<5{Og zNT#2bWWoKAa^LLw-N^?;1tv&xyL}DD$IYVx3a$0;~=y6 zZf0?@HsDdU{Rktk-;U+n^YtTNpxe5(HbDr6azX?UOM;n5p8CVTpAh&y%IWf~(nVvu zk!OlM?864)xEFqcd(MvyL6-I&mZi(ZtDD#FmLJ^wH%L_>QR=^h9<5v=2!UTjQEeT9 zi3eiYChO_pL(&pk9KnVTP+`qCbq7_@ICY#@wj2u>4{#Dg1b}d@-FVInFa7?1_`8xR K!i0wf;~DsPcD#=O literal 0 HcmV?d00001 diff --git a/hd_brainwallet.py b/hd_brainwallet.py index fec77fdf..22f343e6 100644 --- a/hd_brainwallet.py +++ b/hd_brainwallet.py @@ -46,6 +46,11 @@ def get_password(): mnemonic=user_input('Type your password mnemonic, one word at a time:\n') return mnemonic +@offlineonly +def get_entropy(): + choice=user_input('Do you want to use\n\t(1) System Entropy\n\t(2) User Entropy\n') + + def get_master_key(): words=' '.join(get_password().split()) try: @@ -117,6 +122,9 @@ def address(args): index=args.index address=bitcoin.pubtoaddr(bitcoin.bip32_descend(args.xpub,0,index)) print(address) + +def generate(args): + if __name__=="__main__": aparser=argparse.ArgumentParser() From 89f84f597c08f1b2d411a7eabf024c5724f50f0c Mon Sep 17 00:00:00 2001 From: Steven Braeger Date: Sat, 23 Jan 2016 21:23:59 -0500 Subject: [PATCH 12/14] Restored original bci --- bitcoin/bci.py | 871 +++++++++++++++++++++---------------------------- 1 file changed, 369 insertions(+), 502 deletions(-) diff --git a/bitcoin/bci.py b/bitcoin/bci.py index 470e9768..31f09bc0 100644 --- a/bitcoin/bci.py +++ b/bitcoin/bci.py @@ -33,8 +33,18 @@ def is_testnet(inp): if not inp or (inp.lower() in ("btc", "testnet")): pass + ## ADDRESSES + if inp[0] in "123mn": + if re.match("^[2mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): + return True + elif re.match("^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): + return False + else: + #sys.stderr.write("Bad address format %s") + return None + ## TXID - if re.match('^[0-9a-fA-F]{64}$', inp): + elif re.match('^[0-9a-fA-F]{64}$', inp): base_url = "http://api.blockcypher.com/v1/btc/{network}/txs/{txid}?includesHex=false" try: # try testnet fetchtx @@ -46,20 +56,10 @@ def is_testnet(inp): return False sys.stderr.write("TxID %s has no match for testnet or mainnet (Bad TxID)") return None - ## ADDRESSES - elif inp[0] in "123mn": - if re.match("^[2mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): - return True - elif re.match("^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): - return False - else: - #sys.stderr.write("Bad address format %s") - return None else: raise TypeError("{0} is unknown input".format(inp)) - def set_network(*args): '''Decides if args for unspent/fetchtx/pushtx are mainnet or testnet''' r = [] @@ -95,455 +95,90 @@ def parse_addr_args(*args): network = set_network(addr_args) return addr_args, network # note params are "reversed" now - -class _BlockchainInterfaceSet(object): - interfaces=[] - - def __getattr__(cls,key): - sort(cls.interfaces,key=lambda x: x.priority) - for c in cls.interfaces: - if(hasattr(c,key) and c.valid): - return getattr(c,key) - -class BlockchainInterface(object): - pass -# __metaclass__=_BlockchainInterfaceSet - - -_prioritycounter=0 -def blockchain_interface_impl(cls): - global _prioritycounter - cls.valid=True - cls.priority=_prioritycounter - _prioritycounter+=1 - _BlockchainInterfaceSet.interfaces.append(cls) - return cls - -@blockchain_interface_impl -class BlockchainInfo(BlockchainInterface): - @classmethod - def unspent(cls,*args): - addrs, network = parse_addr_args(*args) - u = [] - for a in addrs: - try: - data = make_request('https://blockchain.info/unspent?active='+a) - except Exception as e: - if str(e) == 'No free outputs to spend': - continue - else: - raise Exception(e) - try: - jsonobj = json.loads(data.decode("utf-8")) - for o in jsonobj["unspent_outputs"]: - h = o['tx_hash'].decode('hex')[::-1].encode('hex') - u.append({ - "output": h+':'+str(o['tx_output_n']), - "value": o['value'] - }) - except: - raise Exception("Failed to decode data: "+data) - return u - - @classmethod - def unspent_xpub(cls,*args): - u = [] - for a in args: - try: - data = make_request('https://blockchain.info/unspent?active='+a) - except Exception as e: - if str(e) == 'No free outputs to spend': - continue - else: - raise Exception(e) - try: - jsonobj = json.loads(data.strip().decode("utf-8")) - for o in jsonobj["unspent_outputs"]: - h = o['tx_hash'].decode('hex')[::-1].encode('hex') - u.append({ - "output": h+':'+str(o['tx_output_n']), - "value": o['value'], - "xpub": o['xpub'] - }) - except Exception as e: - print(e) - raise Exception("Failed to decode data: "+data) - return u - - - # Pushes a transaction to the network using https://blockchain.info/pushtx - @classmethod - def pushtx(cls,tx,network='btc'): - if(network != 'btc'): - raise Exception('Unsupported network {0} for BlockchainInfo'.format(network)) - - if not re.match('^[0-9a-fA-F]*$', tx): - tx = tx.encode('hex') - return make_request('https://blockchain.info/pushtx', 'tx='+tx) - - @classmethod - def fetchtx(cls,txhash,network='btc'): - if(network != 'btc'): - raise Exception('Unsupported network {0} for BlockchainInfo'.format(network)) - if isinstance(txhash, list): - return [cls.fetchtx(h,network) for h in txhash] - if not re.match('^[0-9a-fA-F]*$', txhash): - txhash = txhash.encode('hex') - data = make_request('https://blockchain.info/rawtx/'+txhash+'?format=hex') - return data - - @classmethod - def history(cls,*args):# Valid input formats: history([addr1, addr2,addr3]) - # history(addr1, addr2, addr3) - if len(args) == 0: - return [] - elif isinstance(args[0], list): - addrs = args[0] - else: - addrs = args - - txs = [] - for addr in addrs: - offset = 0 - while 1: - gathered = False - while not gathered: - try: - data = make_request( - 'https://blockchain.info/address/%s?format=json&offset=%s' % - (addr, offset)) - gathered = True - except Exception as e: - try: - sys.stderr.write(e.read().strip()) - except: - sys.stderr.write(str(e)) - gathered = False - try: - jsonobj = json.loads(data.decode("utf-8")) - except: - raise Exception("Failed to decode data: "+data) - txs.extend(jsonobj["txs"]) - if len(jsonobj["txs"]) < 50: - break - offset += 50 - sys.stderr.write("Fetching more transactions... "+str(offset)+'\n') - outs = {} - for tx in txs: - for o in tx["out"]: - if o.get('addr', None) in addrs: - key = str(tx["tx_index"])+':'+str(o["n"]) - outs[key] = { - "address": o["addr"], - "value": o["value"], - "output": tx["hash"]+':'+str(o["n"]), - "block_height": tx.get("block_height", None) - } - for tx in txs: - for i, inp in enumerate(tx["inputs"]): - if "prev_out" in inp: - if inp["prev_out"].get("addr", None) in addrs: - key = str(inp["prev_out"]["tx_index"]) + \ - ':'+str(inp["prev_out"]["n"]) - if outs.get(key): - outs[key]["spend"] = tx["hash"]+':'+str(i) - return [outs[k] for k in outs] - - @classmethod - def firstbits(cls,address): - if len(address) >= 25: - return make_request('https://blockchain.info/q/getfirstbits/'+address) - else: - return make_request( - 'https://blockchain.info/q/resolvefirstbits/'+address) - - @classmethod - def get_block_at_height(cls,height): - j = json.loads(make_request("https://blockchain.info/block-height/" + - str(height)+"?format=json").decode("utf-8")) - for b in j['blocks']: - if b['main_chain'] is True: - return b - raise Exception("Block at this height not found") - @classmethod - def _get_block(cls,inp): - if len(str(inp)) < 64: - return get_block_at_height(inp) - else: - return json.loads(make_request( - 'https://blockchain.info/rawblock/'+inp).decode("utf-8")) - - @classmethod - def get_block_header_data(cls,inp,network='btc'): - j = cls._get_block(inp) - return { - 'version': j['ver'], - 'hash': j['hash'], - 'prevhash': j['prev_block'], - 'timestamp': j['time'], - 'merkle_root': j['mrkl_root'], - 'bits': j['bits'], - 'nonce': j['nonce'], - } - - @classmethod - def get_txs_in_block(cls,inp): - j = cls._get_block(inp) - hashes = [t['hash'] for t in j['tx']] - return hashes - - @classmethod - def get_block_height(cls,txhash): - j = json.loads(make_request('https://blockchain.info/rawtx/'+txhash).decode("utf-8")) - return j['block_height'] - -@blockchain_interface_impl -class Blockr(BlockchainInterface): - @classmethod - def unspent(cls,*args): - network, addr_args = parse_addr_args(*args) - - if network == 'testnet': - blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' - elif network == 'btc': - blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' - else: - raise Exception( - 'Unsupported network {0} for blockr_unspent'.format(network)) - - if len(addr_args) == 0: - return [] - elif isinstance(addr_args[0], list): - addrs = addr_args[0] - else: - addrs = addr_args - res = make_request(blockr_url+','.join(addrs)) - data = json.loads(res.decode("utf-8"))['data'] - o = [] - if 'unspent' in data: - data = [data] - for dat in data: - for u in dat['unspent']: - o.append({ - "output": u['tx']+':'+str(u['n']), - "value": int(u['amount'].replace('.', '')) - }) - return o - - @classmethod - def pushtx(tx,network='btc'): - if network == 'testnet': - blockr_url = 'http://tbtc.blockr.io/api/v1/tx/push' - elif network == 'btc': - blockr_url = 'http://btc.blockr.io/api/v1/tx/push' - else: - raise Exception( - 'Unsupported network {0} for blockr_pushtx'.format(network)) - - if not re.match('^[0-9a-fA-F]*$', tx): - tx = tx.encode('hex') - return make_request(blockr_url, '{"hex":"%s"}' % tx) - - @classmethod - def blockr_fetchtx(cls,txhash, network='btc'): - if network == 'testnet': - blockr_url = 'http://tbtc.blockr.io/api/v1/tx/raw/' - elif network == 'btc': - blockr_url = 'http://btc.blockr.io/api/v1/tx/raw/' - else: - raise Exception( - 'Unsupported network {0} for blockr_fetchtx'.format(network)) - if isinstance(txhash, list): - txhash = ','.join([x.encode('hex') if not re.match('^[0-9a-fA-F]*$', x) - else x for x in txhash]) - jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) - return [d['tx']['hex'] for d in jsondata['data']] - else: - if not re.match('^[0-9a-fA-F]*$', txhash): - txhash = txhash.encode('hex') - jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) - return jsondata['data']['tx']['hex'] - - @classmethod - def blockr_get_block_header_data(cls,height, network='btc'): - if network == 'testnet': - blockr_url = "http://tbtc.blockr.io/api/v1/block/raw/" - elif network == 'btc': - blockr_url = "http://btc.blockr.io/api/v1/block/raw/" - else: - raise Exception( - 'Unsupported network {0} for blockr_get_block_header_data'.format(network)) - k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) - j = k['data'] - return { - 'version': j['version'], - 'hash': j['hash'], - 'prevhash': j['previousblockhash'], - 'timestamp': j['time'], - 'merkle_root': j['merkleroot'], - 'bits': int(j['bits'], 16), - 'nonce': j['nonce'], - } - - @classmethod - def get_block_timestamp(cls,height, network='btc'): - if network == 'testnet': - blockr_url = "http://tbtc.blockr.io/api/v1/block/info/" - elif network == 'btc': - blockr_url = "http://btc.blockr.io/api/v1/block/info/" - else: - raise Exception( - 'Unsupported network {0} for get_block_timestamp'.format(network)) - - import time, calendar - if isinstance(height, list): - k = json.loads(make_request(blockr_url + ','.join([str(x) for x in height])).decode("utf-8")) - o = {x['nb']: calendar.timegm(time.strptime(x['time_utc'], - "%Y-%m-%dT%H:%M:%SZ")) for x in k['data']} - return [o[x] for x in height] - else: - k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) - j = k['data']['time_utc'] - return calendar.timegm(time.strptime(j, "%Y-%m-%dT%H:%M:%SZ")) - -@blockchain_interface_impl -class HelloBlock(BlockchainInterface): - @classmethod - def unspent(cls,*args): - addrs, network = parse_addr_args(*args) - if network == 'testnet': - url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' - elif network == 'btc': - url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' - o = [] - for addr in addrs: - for offset in xrange(0, 10**9, 500): - res = make_request(url % (addr, offset)) - data = json.loads(res.decode("utf-8"))["data"] - if not len(data["unspents"]): - break - elif offset: - sys.stderr.write("Getting more unspents: %d\n" % offset) - for dat in data["unspents"]: - o.append({ - "output": dat["txHash"]+':'+str(dat["index"]), - "value": dat["value"], - }) - return o - - @classmethod - def pushtx(cls,tx,network='btc'): - if(network == 'testnet'): - url='https://testnet.helloblock.io/v1/transactions' - else: - url='https://mainnet.helloblock.io/v1/transactions' - - if not re.match('^[0-9a-fA-F]*$', tx): - tx = tx.encode('hex') - return make_request(url,'rawTxHex='+tx) - - @classmethod - def fetchtx(cls,txhash,network='btc'): - if isinstance(txhash, list): - return [helloblock_fetchtx(h) for h in txhash] - if not re.match('^[0-9a-fA-F]*$', txhash): - txhash = txhash.encode('hex') - if network == 'testnet': - url = 'https://testnet.helloblock.io/v1/transactions/' - elif network == 'btc': - url = 'https://mainnet.helloblock.io/v1/transactions/' - else: - raise Exception( - 'Unsupported network {0} for helloblock_fetchtx'.format(network)) - data = json.loads(make_request(url + txhash).decode("utf-8"))["data"]["transaction"] - o = { - "locktime": data["locktime"], - "version": data["version"], - "ins": [], - "outs": [] - } - for inp in data["inputs"]: - o["ins"].append({ - "script": inp["scriptSig"], - "outpoint": { - "index": inp["prevTxoutIndex"], - "hash": inp["prevTxHash"], - }, - "sequence": 4294967295 - }) - for outp in data["outputs"]: - o["outs"].append({ - "value": outp["value"], - "script": outp["scriptPubKey"] - }) - from bitcoin.transaction import serialize - from bitcoin.transaction import txhash as TXHASH - tx = serialize(o) - assert TXHASH(tx) == txhash - return tx - -@blockchain_interface_impl -class Eligius(BlockchainInterface): - @classmethod - def pushtx(cls,tx,network='btc'): - if(network != 'btc'): - raise Exception( - 'Unsupported network {0} for Eligius.'.format(network)) - - if not re.match('^[0-9a-fA-F]*$', tx): - tx = tx.encode('hex') - s = make_request( - 'http://eligius.st/~wizkid057/newstats/pushtxn.php', - 'transaction='+tx+'&send=Push') - strings = re.findall('string[^"]*"[^"]*"', s) - for string in strings: - quote = re.findall('"[^"]*"', string)[0] - if len(quote) >= 5: - return quote[1:-1] - -@blockchain_interface_impl -class BlockCypher(BlockchainInterface): - @classmethod - def get_tx_composite(cls,inputs, outputs, output_value, change_address=None, network=None): - """mktx using blockcypher API""" - inputs = [inputs] if not isinstance(inputs, list) else inputs - outputs = [outputs] if not isinstance(outputs, list) else outputs - network = set_network(change_address or inputs) if not network else network.lower() - url = "http://api.blockcypher.com/v1/btc/{network}/txs/new?includeToSignTx=true".format( - network=('test3' if network=='testnet' else 'main')) - is_address = lambda a: bool(re.match("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", a)) - if any([is_address(x) for x in inputs]): - inputs_type = 'addresses' # also accepts UTXOs, only addresses supported presently - if any([is_address(x) for x in outputs]): - outputs_type = 'addresses' # TODO: add UTXO support - data = { - 'inputs': [{inputs_type: inputs}], - 'confirmations': 0, - 'preference': 'high', - 'outputs': [{outputs_type: outputs, "value": output_value}] - } - if change_address: - data["change_address"] = change_address # - jdata = json.loads(make_request(url, data)) - hash, txh = jdata.get("tosign")[0], jdata.get("tosign_tx")[0] - assert bin_dbl_sha256(txh.decode('hex')).encode('hex') == hash, "checksum mismatch %s" % hash - return txh.encode("utf-8") - - -###########LEGACY API################# -###########LEGACY API################# -###########LEGACY API################# - # Gets the unspent outputs of one or more addresses def bci_unspent(*args): - return BlockchainInfo.unspent(*args) - + addrs, network = parse_addr_args(*args) + u = [] + for a in addrs: + try: + data = make_request('https://blockchain.info/unspent?active='+a) + except Exception as e: + if str(e) == 'No free outputs to spend': + continue + else: + raise Exception(e) + try: + jsonobj = json.loads(data.decode("utf-8")) + for o in jsonobj["unspent_outputs"]: + h = o['tx_hash'].decode('hex')[::-1].encode('hex') + u.append({ + "output": h+':'+str(o['tx_output_n']), + "value": o['value'] + }) + except: + raise Exception("Failed to decode data: "+data) + return u + + def blockr_unspent(*args): - return Blockr.unspent(args) - + # Valid input formats: blockr_unspent([addr1, addr2,addr3]) + # blockr_unspent(addr1, addr2, addr3) + # blockr_unspent([addr1, addr2, addr3], network) + # blockr_unspent(addr1, addr2, addr3, network) + # Where network is 'btc' or 'testnet' + network, addr_args = parse_addr_args(*args) + + if network == 'testnet': + blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' + elif network == 'btc': + blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' + else: + raise Exception( + 'Unsupported network {0} for blockr_unspent'.format(network)) + + if len(addr_args) == 0: + return [] + elif isinstance(addr_args[0], list): + addrs = addr_args[0] + else: + addrs = addr_args + res = make_request(blockr_url+','.join(addrs)) + data = json.loads(res.decode("utf-8"))['data'] + o = [] + if 'unspent' in data: + data = [data] + for dat in data: + for u in dat['unspent']: + o.append({ + "output": u['tx']+':'+str(u['n']), + "value": int(u['amount'].replace('.', '')) + }) + return o + + def helloblock_unspent(*args): - return HelloBlock.unspent(*args) + addrs, network = parse_addr_args(*args) + if network == 'testnet': + url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' + elif network == 'btc': + url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' + o = [] + for addr in addrs: + for offset in xrange(0, 10**9, 500): + res = make_request(url % (addr, offset)) + data = json.loads(res.decode("utf-8"))["data"] + if not len(data["unspents"]): + break + elif offset: + sys.stderr.write("Getting more unspents: %d\n" % offset) + for dat in data["unspents"]: + o.append({ + "output": dat["txHash"]+':'+str(dat["index"]), + "value": dat["value"], + }) + return o + unspent_getters = { 'bci': bci_unspent, @@ -551,22 +186,111 @@ def helloblock_unspent(*args): 'helloblock': helloblock_unspent } + def unspent(*args, **kwargs): f = unspent_getters.get(kwargs.get('source', ''), bci_unspent) return f(*args) - - # Pushes a transaction to the network using https://blockchain.info/pushtx -def bci_pushtx(tx,network='btc'): - return BlockchainInfo.pushtx(tx,network) + + +# Gets the transaction output history of a given set of addresses, +# including whether or not they have been spent +def history(*args): + # Valid input formats: history([addr1, addr2,addr3]) + # history(addr1, addr2, addr3) + if len(args) == 0: + return [] + elif isinstance(args[0], list): + addrs = args[0] + else: + addrs = args + + txs = [] + for addr in addrs: + offset = 0 + while 1: + gathered = False + while not gathered: + try: + data = make_request( + 'https://blockchain.info/address/%s?format=json&offset=%s' % + (addr, offset)) + gathered = True + except Exception as e: + try: + sys.stderr.write(e.read().strip()) + except: + sys.stderr.write(str(e)) + gathered = False + try: + jsonobj = json.loads(data.decode("utf-8")) + except: + raise Exception("Failed to decode data: "+data) + txs.extend(jsonobj["txs"]) + if len(jsonobj["txs"]) < 50: + break + offset += 50 + sys.stderr.write("Fetching more transactions... "+str(offset)+'\n') + outs = {} + for tx in txs: + for o in tx["out"]: + if o.get('addr', None) in addrs: + key = str(tx["tx_index"])+':'+str(o["n"]) + outs[key] = { + "address": o["addr"], + "value": o["value"], + "output": tx["hash"]+':'+str(o["n"]), + "block_height": tx.get("block_height", None) + } + for tx in txs: + for i, inp in enumerate(tx["inputs"]): + if "prev_out" in inp: + if inp["prev_out"].get("addr", None) in addrs: + key = str(inp["prev_out"]["tx_index"]) + \ + ':'+str(inp["prev_out"]["n"]) + if outs.get(key): + outs[key]["spend"] = tx["hash"]+':'+str(i) + return [outs[k] for k in outs] + + +# Pushes a transaction to the network using https://blockchain.info/pushtx +def bci_pushtx(tx): + if not re.match('^[0-9a-fA-F]*$', tx): + tx = tx.encode('hex') + return make_request('https://blockchain.info/pushtx', 'tx='+tx) + + +def eligius_pushtx(tx): + if not re.match('^[0-9a-fA-F]*$', tx): + tx = tx.encode('hex') + s = make_request( + 'http://eligius.st/~wizkid057/newstats/pushtxn.php', + 'transaction='+tx+'&send=Push') + strings = re.findall('string[^"]*"[^"]*"', s) + for string in strings: + quote = re.findall('"[^"]*"', string)[0] + if len(quote) >= 5: + return quote[1:-1] + def blockr_pushtx(tx, network='btc'): - return Blockr.pushtx(tx,network) + if network == 'testnet': + blockr_url = 'http://tbtc.blockr.io/api/v1/tx/push' + elif network == 'btc': + blockr_url = 'http://btc.blockr.io/api/v1/tx/push' + else: + raise Exception( + 'Unsupported network {0} for blockr_pushtx'.format(network)) + + if not re.match('^[0-9a-fA-F]*$', tx): + tx = tx.encode('hex') + return make_request(blockr_url, '{"hex":"%s"}' % tx) -def helloblock_pushtx(tx,network='btc'): - return HelloBlock.pushtx(tx,network) - -def eligius_pushtx(tx,network='btc'): - return Eligius.pushtx(tx,network) + +def helloblock_pushtx(tx): + if not re.match('^[0-9a-fA-F]*$', tx): + tx = tx.encode('hex') + return make_request('https://mainnet.helloblock.io/v1/transactions', + 'rawTxHex='+tx) pushtx_getters = { 'bci': bci_pushtx, @@ -574,19 +298,91 @@ def eligius_pushtx(tx,network='btc'): 'helloblock': helloblock_pushtx } + def pushtx(*args, **kwargs): f = pushtx_getters.get(kwargs.get('source', ''), bci_pushtx) return f(*args) + +def last_block_height(network='btc'): + if network == 'testnet': + data = make_request('http://tbtc.blockr.io/api/v1/block/info/last') + jsonobj = json.loads(data.decode("utf-8")) + return jsonobj["data"]["nb"] + + data = make_request('https://blockchain.info/latestblock') + jsonobj = json.loads(data.decode("utf-8")) + return jsonobj["height"] + + # Gets a specific transaction -def bci_fetchtx(txhash,network='btc'): - return BlockchainInfo.fetchtx(txhash,network) +def bci_fetchtx(txhash): + if isinstance(txhash, list): + return [bci_fetchtx(h) for h in txhash] + if not re.match('^[0-9a-fA-F]*$', txhash): + txhash = txhash.encode('hex') + data = make_request('https://blockchain.info/rawtx/'+txhash+'?format=hex') + return data + def blockr_fetchtx(txhash, network='btc'): - return Blockr.fetchtx(txhash,network) + if network == 'testnet': + blockr_url = 'http://tbtc.blockr.io/api/v1/tx/raw/' + elif network == 'btc': + blockr_url = 'http://btc.blockr.io/api/v1/tx/raw/' + else: + raise Exception( + 'Unsupported network {0} for blockr_fetchtx'.format(network)) + if isinstance(txhash, list): + txhash = ','.join([x.encode('hex') if not re.match('^[0-9a-fA-F]*$', x) + else x for x in txhash]) + jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) + return [d['tx']['hex'] for d in jsondata['data']] + else: + if not re.match('^[0-9a-fA-F]*$', txhash): + txhash = txhash.encode('hex') + jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) + return jsondata['data']['tx']['hex'] + def helloblock_fetchtx(txhash, network='btc'): - return HelloBlock.fetchtx(txhash,network) + if isinstance(txhash, list): + return [helloblock_fetchtx(h) for h in txhash] + if not re.match('^[0-9a-fA-F]*$', txhash): + txhash = txhash.encode('hex') + if network == 'testnet': + url = 'https://testnet.helloblock.io/v1/transactions/' + elif network == 'btc': + url = 'https://mainnet.helloblock.io/v1/transactions/' + else: + raise Exception( + 'Unsupported network {0} for helloblock_fetchtx'.format(network)) + data = json.loads(make_request(url + txhash).decode("utf-8"))["data"]["transaction"] + o = { + "locktime": data["locktime"], + "version": data["version"], + "ins": [], + "outs": [] + } + for inp in data["inputs"]: + o["ins"].append({ + "script": inp["scriptSig"], + "outpoint": { + "index": inp["prevTxoutIndex"], + "hash": inp["prevTxHash"], + }, + "sequence": 4294967295 + }) + for outp in data["outputs"]: + o["outs"].append({ + "value": outp["value"], + "script": outp["scriptPubKey"] + }) + from bitcoin.transaction import serialize + from bitcoin.transaction import txhash as TXHASH + tx = serialize(o) + assert TXHASH(tx) == txhash + return tx fetchtx_getters = { @@ -595,67 +391,138 @@ def helloblock_fetchtx(txhash, network='btc'): 'helloblock': helloblock_fetchtx } + def fetchtx(*args, **kwargs): f = fetchtx_getters.get(kwargs.get('source', ''), bci_fetchtx) return f(*args) -# Gets the transaction output history of a given set of addresses, -# including whether or not they have been spent -def history(*args): - return BlockchainInfo.history(*args) - + def firstbits(address): - return BlockchainInfo.firstbits(address) + if len(address) >= 25: + return make_request('https://blockchain.info/q/getfirstbits/'+address) + else: + return make_request( + 'https://blockchain.info/q/resolvefirstbits/'+address) def get_block_at_height(height): - return BlockchainInfo.get_block_at_height(height) + j = json.loads(make_request("https://blockchain.info/block-height/" + + str(height)+"?format=json").decode("utf-8")) + for b in j['blocks']: + if b['main_chain'] is True: + return b + raise Exception("Block at this height not found") -#def _get_block(inp): -# if len(str(inp)) < 64: -# return get_block_at_height(inp) -# else: -# return json.loads(make_request( -# 'https://blockchain.info/rawblock/'+inp).decode("utf-8")) -def last_block_height(network='btc'): - if network == 'testnet': - data = make_request('http://tbtc.blockr.io/api/v1/block/info/last') - jsonobj = json.loads(data.decode("utf-8")) - return jsonobj["data"]["nb"] +def _get_block(inp): + if len(str(inp)) < 64: + return get_block_at_height(inp) + else: + return json.loads(make_request( + 'https://blockchain.info/rawblock/'+inp).decode("utf-8")) + + +def bci_get_block_header_data(inp): + j = _get_block(inp) + return { + 'version': j['ver'], + 'hash': j['hash'], + 'prevhash': j['prev_block'], + 'timestamp': j['time'], + 'merkle_root': j['mrkl_root'], + 'bits': j['bits'], + 'nonce': j['nonce'], + } - data = make_request('https://blockchain.info/latestblock') - jsonobj = json.loads(data.decode("utf-8")) - return jsonobj["height"] - - -def bci_get_block_header_data(inp, network='btc'): - return BlockchainInfo.get_block_header_data(inp,network) - def blockr_get_block_header_data(height, network='btc'): - return Blockr.get_block_header_data(height,network) + if network == 'testnet': + blockr_url = "http://tbtc.blockr.io/api/v1/block/raw/" + elif network == 'btc': + blockr_url = "http://btc.blockr.io/api/v1/block/raw/" + else: + raise Exception( + 'Unsupported network {0} for blockr_get_block_header_data'.format(network)) + + k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) + j = k['data'] + return { + 'version': j['version'], + 'hash': j['hash'], + 'prevhash': j['previousblockhash'], + 'timestamp': j['time'], + 'merkle_root': j['merkleroot'], + 'bits': int(j['bits'], 16), + 'nonce': j['nonce'], + } + + +def get_block_timestamp(height, network='btc'): + if network == 'testnet': + blockr_url = "http://tbtc.blockr.io/api/v1/block/info/" + elif network == 'btc': + blockr_url = "http://btc.blockr.io/api/v1/block/info/" + else: + raise Exception( + 'Unsupported network {0} for get_block_timestamp'.format(network)) + + import time, calendar + if isinstance(height, list): + k = json.loads(make_request(blockr_url + ','.join([str(x) for x in height])).decode("utf-8")) + o = {x['nb']: calendar.timegm(time.strptime(x['time_utc'], + "%Y-%m-%dT%H:%M:%SZ")) for x in k['data']} + return [o[x] for x in height] + else: + k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) + j = k['data']['time_utc'] + return calendar.timegm(time.strptime(j, "%Y-%m-%dT%H:%M:%SZ")) + block_header_data_getters = { 'bci': bci_get_block_header_data, 'blockr': blockr_get_block_header_data } + def get_block_header_data(inp, **kwargs): f = block_header_data_getters.get(kwargs.get('source', ''), bci_get_block_header_data) return f(inp, **kwargs) -def get_block_timestamp(height, network='btc'): - return Blockr.get_block_timestamp(height,network) - + def get_txs_in_block(inp): - return BlockchainInfo.get_txs_in_block(inp) + j = _get_block(inp) + hashes = [t['hash'] for t in j['tx']] + return hashes + def get_block_height(txhash): - return BlockchainInfo.get_block_height(txhash) + j = json.loads(make_request('https://blockchain.info/rawtx/'+txhash).decode("utf-8")) + return j['block_height'] # fromAddr, toAddr, 12345, changeAddress def get_tx_composite(inputs, outputs, output_value, change_address=None, network=None): - return BlockCypher.get_tx_composite(inputs,outputs,output_value,change_address,network) + """mktx using blockcypher API""" + inputs = [inputs] if not isinstance(inputs, list) else inputs + outputs = [outputs] if not isinstance(outputs, list) else outputs + network = set_network(change_address or inputs) if not network else network.lower() + url = "http://api.blockcypher.com/v1/btc/{network}/txs/new?includeToSignTx=true".format( + network=('test3' if network=='testnet' else 'main')) + is_address = lambda a: bool(re.match("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", a)) + if any([is_address(x) for x in inputs]): + inputs_type = 'addresses' # also accepts UTXOs, only addresses supported presently + if any([is_address(x) for x in outputs]): + outputs_type = 'addresses' # TODO: add UTXO support + data = { + 'inputs': [{inputs_type: inputs}], + 'confirmations': 0, + 'preference': 'high', + 'outputs': [{outputs_type: outputs, "value": output_value}] + } + if change_address: + data["change_address"] = change_address # + jdata = json.loads(make_request(url, data)) + hash, txh = jdata.get("tosign")[0], jdata.get("tosign_tx")[0] + assert bin_dbl_sha256(txh.decode('hex')).encode('hex') == hash, "checksum mismatch %s" % hash + return txh.encode("utf-8") blockcypher_mktx = get_tx_composite From deff543b5569d72d2b2c4dd46405d2893b26d731 Mon Sep 17 00:00:00 2001 From: Steven Braeger Date: Sat, 23 Jan 2016 21:25:21 -0500 Subject: [PATCH 13/14] Removed brainwallet stuff --- hd_brainwallet.py | 154 ---------------------------------------------- 1 file changed, 154 deletions(-) delete mode 100644 hd_brainwallet.py diff --git a/hd_brainwallet.py b/hd_brainwallet.py deleted file mode 100644 index 22f343e6..00000000 --- a/hd_brainwallet.py +++ /dev/null @@ -1,154 +0,0 @@ -import bitcoin -import sys -import argparse -import urllib2 -import json - -require_offline=False -running_offline=None - -def user_input(s,expectednotchar=None): - sys.stderr.write(s) - q=raw_input() - if(expectednotchar and (q[0].lower() not in expectednotchar)): - quit() - return q - -def test_offline(): - global running_offline - if(running_offline is None): - user_input("Make sure you are offline, alone, and your screen is not visible. [OK]") - print("Testing if you are online...") - try: - result=urllib2.urlopen("https://google.com",timeout=3.0).read() - user_input("You lied about being offline! [OK]") - running_offline=False - return False - except Exception as e: - print(e) - running_offline=True - return True - else: - return running_offline - - -def offlineonly(f): - def wrapper(): - global require_offline - if(require_offline): - if(not test_offline()): - user_input('Warning! You are not in offline mode! You should immediately quit before executing this function! Do you want to do so now? [Y/n]','n') - return f() - return wrapper - -@offlineonly -def get_password(): - mnemonic=user_input('Type your password mnemonic, one word at a time:\n') - return mnemonic - -@offlineonly -def get_entropy(): - choice=user_input('Do you want to use\n\t(1) System Entropy\n\t(2) User Entropy\n') - - -def get_master_key(): - words=' '.join(get_password().split()) - try: - a=bitcoin.words_verify(words) - except Exception as e: - print(e) - a=False - - if(not a): - q=user_input("Warning! Mnemonic does not verify as a string of bip39 english space-seperated words! continue? [y/N]",'y') - - seed=bitcoin.mnemonic_to_seed(words) - master_key=bitcoin.bip32_master_key(seed) - return master_key - -def sign(args): - master_key=get_master_key() - account_privkey=bitcoin.hd_lookup(master_key,account=args.account) - input_transaction=json.load(args.input_transaction) - #compute the largest change address and largest address in the account (use xpub and bitcoin.bip32_string_to_path) - #compute all the underlying addresses and pkeys into a string hash - #decide on the change address - #build the transaction - #sign the transaction - #print the hex - -def pubkey(args): - master_key=get_master_key() - - if(args.root or (args.account and args.account < 0)): - #print("The following is your master root extended public key:") - print(bitcoin.bip32_privtopub(master_key)) - else: - account_privkey=bitcoin.hd_lookup(master_key,account=args.account) - #print("The following is the extended public key for account #%d:" % (args.account)) - print(bitcoin.bip32_privtopub(account_privkey)) - -def send(args): - if(len(args.outputs) % 2 != 0): - raise Exception("When sending, there must be an even number of arguments for the outputs (address,price)") - unspents=bitcoin.BlockchainInfo.unspent_xpub(args.xpub) - def btctosatoshi(vs): - return int(float(vs)*100000000.0) - fee=btctosatoshi(args.fee) - if(fee < 0): - fee=int(-0.0001*100000000) #todo do something to estimated fee...make it negative or something though - outaddrval=[(args.outputs[2*i],btctosatoshi(args.outputs[2*i+1])) for i in range(len(args.outputs)//2)] - outtotalval=sum([o[1] for o in outaddrval]) - unspenttotalval=sum([u['value'] for u in unspents]) - if(outtotalval+abs(fee) >= unspenttotalval): - raise Exception("There is unlikely to be enough unspent outputs to cover the transaction and fees") - out={} - out['unspents']=unspents - out['fee']=fee #negative if estimated - out['outputs']=outaddrval - - json.dump(out,sys.stdout) - -def address(args): - if(not args.index): - unspents=bitcoin.BlockchainInfo.unspent_xpub(args.xpub) - index=0 - for u in unspents: - upath=u['xpub']['path'] - cdex=bitcoin.bip32_path_from_string(upath)[-1] - index=max(cdex,index) - index+=1 - else: - index=args.index - address=bitcoin.pubtoaddr(bitcoin.bip32_descend(args.xpub,0,index)) - print(address) - -def generate(args): - - -if __name__=="__main__": - aparser=argparse.ArgumentParser() - subaparsers=aparser.add_subparsers() - aparse_send=subaparsers.add_parser('send',help="[online] Get the unspents and generate an unsigned transaction to some outputs") - aparse_send.add_argument('--xpub','-p',required=True,help="The xpubkey for the hdwallet account") - aparse_send.add_argument('--fee','-f',default=-1,type=float,help="The fee to use") - aparse_send.add_argument('outputs',help="The outputs, two at a time in format...e.g. 1L3qUmg3GeuGrGvi1JxT2jMhAdV76qVj7V 1.032",nargs='+') - aparse_send.set_defaults(func=send) - - aparse_pubkey=subaparsers.add_parser('pubkey',help='[offline] Get the extended HD pubkey for a particular account') - aparse_pubkey_accountgroup=aparse_pubkey.add_mutually_exclusive_group(required=True) - aparse_pubkey_accountgroup.add_argument('--account','-a',type=int,help="The number of the hd wallet account to export the pubkey for.") - aparse_pubkey_accountgroup.add_argument('--root','-r',action='store_true',help="The exported wallet account pubkey is the master extended pubkey.") - aparse_pubkey.set_defaults(func=pubkey) - - aparse_address=subaparsers.add_parser('address',help='[online or offline] Get an address for an account') - aparse_address.add_argument('--xpub','-p',required=True,help="The xpubkey for the hdwallet account") - aparse_address.add_argument('--index','-i','--address',type=int,help='The index of the address to get from the account') - aparse_address.set_defaults(func=address) - - args=aparser.parse_args() - args.func(args) - - - - From fb60e366f49896843ee314137b84af8fc7c84f7d Mon Sep 17 00:00:00 2001 From: Steven Braeger Date: Sat, 23 Jan 2016 21:30:07 -0500 Subject: [PATCH 14/14] Removing fee estimation stub and HD wallet test --- bitcoin/transaction.py | 3 --- testhd.py | 15 --------------- 2 files changed, 18 deletions(-) delete mode 100644 testhd.py diff --git a/bitcoin/transaction.py b/bitcoin/transaction.py index bcd37d5d..bd31d8ad 100644 --- a/bitcoin/transaction.py +++ b/bitcoin/transaction.py @@ -505,9 +505,6 @@ def mksend(*args): outputs2.append(o2) osum += o2["value"] -def estimate_fee(tx): - pass - if isum < osum+fee: raise Exception("Not enough money") elif isum > osum+fee+5430: diff --git a/testhd.py b/testhd.py deleted file mode 100644 index 9633acea..00000000 --- a/testhd.py +++ /dev/null @@ -1,15 +0,0 @@ -import bitcoin -from bitcoin.deterministic import bip32_harden as h - -mnemonic='saddle observe obtain scare burger nerve electric alone minute east walnut motor omit coyote time' -seed=bitcoin.mnemonic_to_seed(mnemonic) -mpriv=bitcoin.bip32_master_key(seed) - -accountroot=mpriv -accountroot=bitcoin.bip32_ckd(accountroot,h(44)) -accountroot=bitcoin.bip32_ckd(accountroot,h(0)) -accountroot=bitcoin.bip32_ckd(accountroot,h(0)) - -for i in range(19): - dkey=bitcoin.bip32_descend(accountroot,0,i) - print(bitcoin.privtoaddr(dkey)) \ No newline at end of file